mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 11:17:05 -04:00
Optimize meshing using bitmasks to reduce the number of blocks that need to be sampled from the chunk.
Improves meshing time from 30s to just 8s at render distance 12
This commit is contained in:
parent
fffb94046d
commit
5f9d740843
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,6 +3,7 @@ logs/
|
||||
saves/
|
||||
zig-out/
|
||||
zig-cache/
|
||||
.zig-cache/
|
||||
serverAssets/
|
||||
settings.json
|
||||
gui_layout.json
|
||||
|
@ -16,5 +16,5 @@
|
||||
|
||||
|
||||
"drops" : [],
|
||||
"model" : ""
|
||||
"model" : "none"
|
||||
}
|
@ -42,6 +42,9 @@ pub const Model = struct {
|
||||
internalQuads: []u16,
|
||||
neighborFacingQuads: [6][]u16,
|
||||
isNeighborOccluded: [6]bool,
|
||||
allNeighborsOccluded: bool,
|
||||
noNeighborsOccluded: bool,
|
||||
hasNeighborFacingQuads: bool,
|
||||
|
||||
fn getFaceNeighbor(quad: *const QuadInfo) ?u3 {
|
||||
var allZero: @Vector(3, bool) = .{true, true, true};
|
||||
@ -130,12 +133,18 @@ pub const Model = struct {
|
||||
internalIndex += 1;
|
||||
}
|
||||
}
|
||||
self.hasNeighborFacingQuads = false;
|
||||
self.allNeighborsOccluded = true;
|
||||
self.noNeighborsOccluded = true;
|
||||
for(0..6) |neighbor| {
|
||||
for(self.neighborFacingQuads[neighbor]) |quad| {
|
||||
if(fullyOccludesNeighbor(&quads.items[quad])) {
|
||||
self.isNeighborOccluded[neighbor] = true;
|
||||
}
|
||||
}
|
||||
self.hasNeighborFacingQuads = self.hasNeighborFacingQuads or self.neighborFacingQuads[neighbor].len != 0;
|
||||
self.allNeighborsOccluded = self.allNeighborsOccluded and self.isNeighborOccluded[neighbor];
|
||||
self.noNeighborsOccluded = self.noNeighborsOccluded and !self.isNeighborOccluded[neighbor];
|
||||
}
|
||||
return modelIndex;
|
||||
}
|
||||
@ -316,6 +325,8 @@ pub fn init() void {
|
||||
|
||||
nameToIndex = std.StringHashMap(u16).init(main.globalAllocator.allocator);
|
||||
|
||||
nameToIndex.put("none", Model.init(&.{})) catch unreachable;
|
||||
|
||||
const cube = Model.init(&box(.{0, 0, 0}, .{1, 1, 1}, .{0, 0}));
|
||||
nameToIndex.put("cube", cube) catch unreachable;
|
||||
fullCube = cube;
|
||||
|
@ -795,45 +795,229 @@ pub const ChunkMesh = struct {
|
||||
}
|
||||
|
||||
pub fn generateMesh(self: *ChunkMesh) void {
|
||||
var canSeeNeighbor: [6][chunk.chunkSize][chunk.chunkSize]u32 = undefined;
|
||||
@memset(std.mem.asBytes(&canSeeNeighbor), 0);
|
||||
var canSeeAllNeighbors: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
|
||||
@memset(std.mem.asBytes(&canSeeAllNeighbors), 0);
|
||||
var hasFaces: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
|
||||
@memset(std.mem.asBytes(&hasFaces), 0);
|
||||
self.mutex.lock();
|
||||
var n: u32 = 0;
|
||||
var x: u8 = 0;
|
||||
while(x < chunk.chunkSize): (x += 1) {
|
||||
var y: u8 = 0;
|
||||
while(y < chunk.chunkSize): (y += 1) {
|
||||
var z: u8 = 0;
|
||||
while(z < chunk.chunkSize): (z += 1) {
|
||||
const block = self.chunk.data.getValue(chunk.getIndex(x, y, z));
|
||||
if(block.typ == 0) continue;
|
||||
// Check all neighbors:
|
||||
for(chunk.Neighbors.iterable) |i| {
|
||||
n += 1;
|
||||
const x2 = x + chunk.Neighbors.relX[i];
|
||||
const y2 = y + chunk.Neighbors.relY[i];
|
||||
const z2 = z + chunk.Neighbors.relZ[i];
|
||||
if(x2&chunk.chunkMask != x2 or y2&chunk.chunkMask != y2 or z2&chunk.chunkMask != z2) continue; // Neighbor is outside the chunk.
|
||||
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(x2, y2, z2));
|
||||
if(canBeSeenThroughOtherBlock(block, neighborBlock, i)) {
|
||||
if(block.transparent()) {
|
||||
if(block.hasBackFace()) {
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, i ^ 1, x, y, z, true);
|
||||
}
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, i, x2, y2, z2, false);
|
||||
} else {
|
||||
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, i, x2, y2, z2, false);
|
||||
const OcclusionInfo = packed struct {
|
||||
canSeeNeighbor: u6 = 0,
|
||||
canSeeAllNeighbors: bool = false,
|
||||
hasExternalQuads: bool = false,
|
||||
hasInternalQuads: bool = false,
|
||||
};
|
||||
var paletteCache = main.stackAllocator.alloc(OcclusionInfo, self.chunk.data.paletteLength);
|
||||
defer main.stackAllocator.free(paletteCache);
|
||||
for(0..self.chunk.data.paletteLength) |i| {
|
||||
const block = self.chunk.data.palette[i];
|
||||
const model = &models.models.items[blocks.meshes.model(block)];
|
||||
var result: OcclusionInfo = .{};
|
||||
if(model.noNeighborsOccluded or block.viewThrough()) {
|
||||
result.canSeeAllNeighbors = true;
|
||||
} else if(!model.allNeighborsOccluded) {
|
||||
for(chunk.Neighbors.iterable) |neighbor| {
|
||||
if(!model.isNeighborOccluded[neighbor]) {
|
||||
result.canSeeNeighbor |= @as(u6, 1) << neighbor;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(model.hasNeighborFacingQuads) {
|
||||
result.hasExternalQuads = true;
|
||||
}
|
||||
if(model.internalQuads.len != 0) {
|
||||
result.hasInternalQuads = true;
|
||||
}
|
||||
paletteCache[i] = result;
|
||||
}
|
||||
// Generate the bitMasks:
|
||||
for(0..chunk.chunkSize) |_x| {
|
||||
const x: u5 = @intCast(_x);
|
||||
for(0..chunk.chunkSize) |_y| {
|
||||
const y: u5 = @intCast(_y);
|
||||
for(0..chunk.chunkSize) |_z| {
|
||||
const z: u5 = @intCast(_z);
|
||||
const paletteId = self.chunk.data.data.getValue(chunk.getIndex(x, y, z));
|
||||
const occlusionInfo = paletteCache[paletteId];
|
||||
const setBit = @as(u32, 1) << z;
|
||||
if(occlusionInfo.canSeeAllNeighbors) {
|
||||
canSeeAllNeighbors[x][y] |= setBit;
|
||||
} else if(occlusionInfo.canSeeNeighbor != 0) {
|
||||
for(chunk.Neighbors.iterable) |neighbor| {
|
||||
if(occlusionInfo.canSeeNeighbor & @as(u6, 1) << neighbor != 0) {
|
||||
canSeeNeighbor[neighbor][x][y] |= setBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(block.transparent()) {
|
||||
self.transparentMesh.appendInternalQuadsToCore(block, x, y, z, false);
|
||||
} else {
|
||||
self.opaqueMesh.appendInternalQuadsToCore(block, x, y, z, false);
|
||||
if(occlusionInfo.hasExternalQuads) {
|
||||
hasFaces[x][y] |= setBit;
|
||||
}
|
||||
if(occlusionInfo.hasInternalQuads) {
|
||||
const block = self.chunk.data.palette[paletteId];
|
||||
if(block.transparent()) {
|
||||
self.transparentMesh.appendInternalQuadsToCore(block, x, y, z, false);
|
||||
} else {
|
||||
self.opaqueMesh.appendInternalQuadsToCore(block, x, y, z, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Generate the meshes:
|
||||
{
|
||||
const neighbor = chunk.Neighbors.dirNegX;
|
||||
for(1..chunk.chunkSize) |x| {
|
||||
for(0..chunk.chunkSize) |y| {
|
||||
var bitMask = hasFaces[x][y] & (canSeeNeighbor[neighbor ^ 1][x - 1][y] | canSeeAllNeighbors[x - 1][y]);
|
||||
while(bitMask != 0) {
|
||||
const z = @ctz(bitMask);
|
||||
bitMask &= ~(@as(u32, 1) << @intCast(z));
|
||||
const block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
|
||||
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
|
||||
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x - 1), @intCast(y), z));
|
||||
if(std.meta.eql(block, neighborBlock)) continue;
|
||||
}
|
||||
if(block.transparent()) {
|
||||
if(block.hasBackFace()) {
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor ^ 1, @intCast(x), @intCast(y), z, true);
|
||||
}
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x - 1), @intCast(y), z, false);
|
||||
} else {
|
||||
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x - 1), @intCast(y), z, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
const neighbor = chunk.Neighbors.dirPosX;
|
||||
for(0..chunk.chunkSize-1) |x| {
|
||||
for(0..chunk.chunkSize) |y| {
|
||||
var bitMask = hasFaces[x][y] & (canSeeNeighbor[neighbor ^ 1][x + 1][y] | canSeeAllNeighbors[x + 1][y]);
|
||||
while(bitMask != 0) {
|
||||
const z = @ctz(bitMask);
|
||||
bitMask &= ~(@as(u32, 1) << @intCast(z));
|
||||
const block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
|
||||
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
|
||||
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x + 1), @intCast(y), z));
|
||||
if(std.meta.eql(block, neighborBlock)) continue;
|
||||
}
|
||||
if(block.transparent()) {
|
||||
if(block.hasBackFace()) {
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor ^ 1, @intCast(x), @intCast(y), z, true);
|
||||
}
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x + 1), @intCast(y), z, false);
|
||||
} else {
|
||||
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x + 1), @intCast(y), z, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
const neighbor = chunk.Neighbors.dirNegY;
|
||||
for(0..chunk.chunkSize) |x| {
|
||||
for(1..chunk.chunkSize) |y| {
|
||||
var bitMask = hasFaces[x][y] & (canSeeNeighbor[neighbor ^ 1][x][y - 1] | canSeeAllNeighbors[x][y - 1]);
|
||||
while(bitMask != 0) {
|
||||
const z = @ctz(bitMask);
|
||||
bitMask &= ~(@as(u32, 1) << @intCast(z));
|
||||
const block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
|
||||
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
|
||||
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y - 1), z));
|
||||
if(std.meta.eql(block, neighborBlock)) continue;
|
||||
}
|
||||
if(block.transparent()) {
|
||||
if(block.hasBackFace()) {
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor ^ 1, @intCast(x), @intCast(y), z, true);
|
||||
}
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y - 1), z, false);
|
||||
} else {
|
||||
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y - 1), z, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
const neighbor = chunk.Neighbors.dirPosY;
|
||||
for(0..chunk.chunkSize) |x| {
|
||||
for(0..chunk.chunkSize-1) |y| {
|
||||
var bitMask = hasFaces[x][y] & (canSeeNeighbor[neighbor ^ 1][x][y + 1] | canSeeAllNeighbors[x][y + 1]);
|
||||
while(bitMask != 0) {
|
||||
const z = @ctz(bitMask);
|
||||
bitMask &= ~(@as(u32, 1) << @intCast(z));
|
||||
const block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
|
||||
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
|
||||
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y + 1), z));
|
||||
if(std.meta.eql(block, neighborBlock)) continue;
|
||||
}
|
||||
if(block.transparent()) {
|
||||
if(block.hasBackFace()) {
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor ^ 1, @intCast(x), @intCast(y), z, true);
|
||||
}
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y + 1), z, false);
|
||||
} else {
|
||||
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y + 1), z, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
const neighbor = chunk.Neighbors.dirDown;
|
||||
for(0..chunk.chunkSize) |x| {
|
||||
for(0..chunk.chunkSize) |y| {
|
||||
var bitMask = hasFaces[x][y] & (canSeeNeighbor[neighbor ^ 1][x][y] | canSeeAllNeighbors[x][y]) << 1;
|
||||
while(bitMask != 0) {
|
||||
const z = @ctz(bitMask);
|
||||
bitMask &= ~(@as(u32, 1) << @intCast(z));
|
||||
const block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
|
||||
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
|
||||
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z - 1));
|
||||
if(std.meta.eql(block, neighborBlock)) continue;
|
||||
}
|
||||
if(block.transparent()) {
|
||||
if(block.hasBackFace()) {
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor ^ 1, @intCast(x), @intCast(y), z, true);
|
||||
}
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y), z - 1, false);
|
||||
} else {
|
||||
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y), z - 1, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
const neighbor = chunk.Neighbors.dirUp;
|
||||
for(0..chunk.chunkSize) |x| {
|
||||
for(0..chunk.chunkSize) |y| {
|
||||
var bitMask = hasFaces[x][y] & (canSeeNeighbor[neighbor ^ 1][x][y] | canSeeAllNeighbors[x][y]) >> 1;
|
||||
while(bitMask != 0) {
|
||||
const z = @ctz(bitMask);
|
||||
bitMask &= ~(@as(u32, 1) << @intCast(z));
|
||||
const block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
|
||||
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
|
||||
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z + 1));
|
||||
if(std.meta.eql(block, neighborBlock)) continue;
|
||||
}
|
||||
if(block.transparent()) {
|
||||
if(block.hasBackFace()) {
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor ^ 1, @intCast(x), @intCast(y), z, true);
|
||||
}
|
||||
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y), z + 1, false);
|
||||
} else {
|
||||
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y), z + 1, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check out the borders:
|
||||
x = 0;
|
||||
var x: u8 = 0;
|
||||
while(x < chunk.chunkSize): (x += 1) {
|
||||
var y: u8 = 0;
|
||||
while(y < chunk.chunkSize): (y += 1) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user