diff --git a/src/chunk.zig b/src/chunk.zig index e8104aaf..4e8f1ecc 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -385,6 +385,20 @@ pub const ServerChunk = struct { // MARK: ServerChunk self.super.data.setValue(index, newBlock); } + /// Updates a block if it is inside this chunk. Should be used in generation to prevent accidently storing these as changes. + /// Does not do any bound checks. They are expected to be done with the `liesInChunk` function. + pub fn updateBlockColumnInGeneration(self: *ServerChunk, _x: i32, _y: i32, _zStartInclusive: i32, _zEndInclusive: i32, newBlock: Block) void { + std.debug.assert(_zStartInclusive <= _zEndInclusive); + main.utils.assertLocked(&self.mutex); + const x = _x >> self.super.voxelSizeShift; + const y = _y >> self.super.voxelSizeShift; + const zStartInclusive = _zStartInclusive >> self.super.voxelSizeShift; + const zEndInclusive = _zEndInclusive >> self.super.voxelSizeShift; + const indexStart = getIndex(x, y, zStartInclusive); + const indexEnd = getIndex(x, y, zEndInclusive) + 1; + self.super.data.setValueInColumn(indexStart, indexEnd, newBlock); + } + pub fn updateFromLowerResolution(self: *ServerChunk, other: *ServerChunk) void { const xOffset = if(other.super.pos.wx != self.super.pos.wx) chunkSize/2 else 0; // Offsets of the lower resolution chunk in this chunk. const yOffset = if(other.super.pos.wy != self.super.pos.wy) chunkSize/2 else 0; diff --git a/src/server/terrain/CaveBiomeMap.zig b/src/server/terrain/CaveBiomeMap.zig index 77e036d7..07d678fc 100644 --- a/src/server/terrain/CaveBiomeMap.zig +++ b/src/server/terrain/CaveBiomeMap.zig @@ -46,7 +46,7 @@ pub const CaveBiomeMapFragment = struct { // MARK: caveBiomeMapFragment @Vector(3, i64){-12*fac, 15*fac, 16*fac}, }; // divide result by shift to do a proper rotation - const inverseRotationMatrix = .{ + const transposeRotationMatrix = .{ @Vector(3, i64){20*fac, 9*fac, -12*fac}, @Vector(3, i64){ 0*fac, 20*fac, 15*fac}, @Vector(3, i64){15*fac, -12*fac, 16*fac}, @@ -62,9 +62,9 @@ pub const CaveBiomeMapFragment = struct { // MARK: caveBiomeMapFragment pub fn rotateInverse(in: Vec3i) Vec3i { return @truncate(@Vector(3, i64){ - vec.dot(inverseRotationMatrix[0], in) >> rotationMatrixShift, - vec.dot(inverseRotationMatrix[1], in) >> rotationMatrixShift, - vec.dot(inverseRotationMatrix[2], in) >> rotationMatrixShift, + vec.dot(transposeRotationMatrix[0], in) >> rotationMatrixShift, + vec.dot(transposeRotationMatrix[1], in) >> rotationMatrixShift, + vec.dot(transposeRotationMatrix[2], in) >> rotationMatrixShift, }); } @@ -402,15 +402,14 @@ pub const InterpolatableCaveBiomeMapView = struct { // MARK: InterpolatableCaveB return frag.biomeMap[indexInArray][map]; } - fn getGridPoint(pos: Vec3i, map: *u1) Vec3i { - const rotatedPos = CaveBiomeMapFragment.rotate(pos); + fn getGridPointFromPrerotated(rotatedPos: Vec3i, map: *u1) Vec3i { var gridPoint = rotatedPos +% @as(Vec3i, @splat(CaveBiomeMapFragment.caveBiomeSize/2)) & @as(Vec3i, @splat(~@as(i32, CaveBiomeMapFragment.caveBiomeMask))); const distance = rotatedPos -% gridPoint; const totalDistance = @reduce(.Add, @abs(distance)); if(totalDistance > CaveBiomeMapFragment.caveBiomeSize*3/4) { // Or with 1 to prevent errors if the value is 0. - gridPoint +%= (std.math.sign(distance | @as(Vec3i, @splat(1)) - @as(Vec3i, @splat(1))))*@as(Vec3i, @splat(CaveBiomeMapFragment.caveBiomeSize/2)); + gridPoint +%= std.math.sign(distance)*@as(Vec3i, @splat(CaveBiomeMapFragment.caveBiomeSize/2)); map.* = 1; } else { map.* = 0; @@ -418,6 +417,42 @@ pub const InterpolatableCaveBiomeMapView = struct { // MARK: InterpolatableCaveB return gridPoint; } + fn getGridPoint(pos: Vec3i, map: *u1) Vec3i { + const rotatedPos = CaveBiomeMapFragment.rotate(pos); + return getGridPointFromPrerotated(rotatedPos, map); + } + + fn getGridPointAndHeight(pos: Vec3i, map: *u1, returnHeight: *i32, voxelSize: u31) Vec3i { + const preRotatedPos = @Vector(3, i64){ + vec.dot(CaveBiomeMapFragment.rotationMatrix[0], pos), + vec.dot(CaveBiomeMapFragment.rotationMatrix[1], pos), + vec.dot(CaveBiomeMapFragment.rotationMatrix[2], pos), + }; + var startMap: u1 = undefined; + const gridPoint = getGridPointFromPrerotated(@truncate(preRotatedPos >> @splat(CaveBiomeMapFragment.rotationMatrixShift)), &startMap); + + var start: i32 = 0; + var end = @min(returnHeight.*, @as(comptime_int, @intFromFloat(@ceil(CaveBiomeMapFragment.caveBiomeSize*@sqrt(5.0)/2.0)))) & ~@as(i32, voxelSize-1); + { + var otherMap: u1 = undefined; + const nextGridPoint = getGridPointFromPrerotated(@truncate(preRotatedPos +% CaveBiomeMapFragment.transposeRotationMatrix[2]*@as(Vec3i, @splat(end)) >> @splat(CaveBiomeMapFragment.rotationMatrixShift)), &otherMap); + if(@reduce(.And, nextGridPoint == gridPoint) and otherMap == startMap) start = end; + } + while(start + voxelSize < end) { + const mid = start +% @divTrunc(end -% start, 2) & ~@as(i32, voxelSize-1); + var otherMap: u1 = undefined; + const nextGridPoint = getGridPointFromPrerotated(@truncate(preRotatedPos +% CaveBiomeMapFragment.transposeRotationMatrix[2]*@as(Vec3i, @splat(mid)) >> @splat(CaveBiomeMapFragment.rotationMatrixShift)), &otherMap); + if(@reduce(.Or, nextGridPoint != gridPoint) or otherMap != startMap) { + end = mid; + } else { + start = mid; + } + } + returnHeight.* = end; + map.* = startMap; + return gridPoint; + } + /// Useful when the rough biome location is enough, for example for music. pub noinline fn getRoughBiome(self: InterpolatableCaveBiomeMapView, wx: i32, wy: i32, wz: i32, comptime getSeed: bool, seed: *u64, comptime _checkSurfaceBiome: bool) *const Biome { if(_checkSurfaceBiome) { @@ -444,25 +479,7 @@ pub const InterpolatableCaveBiomeMapView = struct { // MARK: InterpolatableCaveB } } var map: u1 = undefined; - const gridPoint = getGridPoint(.{wx, wy, wz}, &map); - var start = wz; - var end = start +% returnHeight.*; - { - var otherMap: u1 = undefined; - const nextGridPoint = getGridPoint(.{wx, wy, end}, &otherMap); - if(@reduce(.And, nextGridPoint == gridPoint) and otherMap == map) start = end; - } - while(start + 1 < end) { - const mid = start +% @divTrunc(end -% start, 2); - var otherMap: u1 = undefined; - const nextGridPoint = getGridPoint(.{wx, wy, mid}, &otherMap); - if(@reduce(.Or, nextGridPoint != gridPoint) or otherMap != map) { - end = mid; - } else { - start = mid; - } - } - returnHeight.* = end -% wz; + const gridPoint = getGridPointAndHeight(.{wx, wy, wz}, &map, returnHeight, self.pos.voxelSize); if(getSeed) { // A good old "I don't know what I'm doing" hash (TODO: Use some standard hash maybe): diff --git a/src/server/terrain/chunkgen/TerrainGenerator.zig b/src/server/terrain/chunkgen/TerrainGenerator.zig index b1ff7028..b21b7db5 100644 --- a/src/server/terrain/chunkgen/TerrainGenerator.zig +++ b/src/server/terrain/chunkgen/TerrainGenerator.zig @@ -42,57 +42,75 @@ pub fn generate(worldSeed: u64, chunk: *main.chunk.ServerChunk, caveMap: CaveMap var baseSeed: u64 = undefined; const biome = biomeMap.getBiomeColumnAndSeed(x, y, zBiome, true, &baseSeed, &biomeHeight); defer zBiome = chunk.startIndex(zBiome + biomeHeight - 1 + chunk.super.pos.voxelSize); - var makeSurfaceStructure = true; var z: i32 = @min(chunk.super.width - chunk.super.pos.voxelSize, chunk.startIndex(zBiome + biomeHeight - 1)); while(z >= zBiome) : (z -= chunk.super.pos.voxelSize) { const mask = @as(u64, 1) << @intCast(z >> voxelSizeShift); if(heightData & mask != 0) { - if(makeSurfaceStructure) { - const surfaceBlock = caveMap.findTerrainChangeAbove(x, y, z) - chunk.super.pos.voxelSize; - var bseed: u64 = random.initSeed3D(worldSeed, .{chunk.super.pos.wx + x, chunk.super.pos.wy + y, chunk.super.pos.wz + z}); - // Add the biomes surface structure: - z = @min(z + chunk.super.pos.voxelSize, biome.structure.addSubTerranian(chunk, surfaceBlock, @max(caveMap.findTerrainChangeBelow(x, y, z), zBiome - 1), x, y, &bseed)); - makeSurfaceStructure = false; - } else { - var typ = biome.stoneBlockType; - var seed = baseSeed; - for (biome.stripes) |stripe| { - const pos: Vec3d = .{ - @as(f64, @floatFromInt(x + chunk.super.pos.wx)), - @as(f64, @floatFromInt(y + chunk.super.pos.wy)), - @as(f64, @floatFromInt(z + chunk.super.pos.wz)) - }; - var d: f64 = 0; - if (stripe.direction) |direction| { - d = vec.dot(direction, pos); - } else { - const dx = main.random.nextDoubleSigned(&seed); - const dy = main.random.nextDoubleSigned(&seed); - const dz = main.random.nextDoubleSigned(&seed); - const dir: Vec3d = .{dx, dy, dz}; - d = vec.dot(vec.normalize(dir), pos); - } - - const distance = (stripe.maxDistance - stripe.minDistance) * main.random.nextDouble(&seed) + stripe.minDistance; - - const offset = (stripe.maxOffset - stripe.minOffset) * main.random.nextDouble(&seed) + stripe.minOffset; - - const width = (stripe.maxWidth - stripe.minWidth) * main.random.nextDouble(&seed) + stripe.minWidth; - - if (@mod(d + offset, distance) < width) { - typ = stripe.block; - break; + const surfaceBlock = caveMap.findTerrainChangeAbove(x, y, z) - chunk.super.pos.voxelSize; + var bseed: u64 = random.initSeed3D(worldSeed, .{chunk.super.pos.wx + x, chunk.super.pos.wy + y, chunk.super.pos.wz + z}); + const airBlockBelow = caveMap.findTerrainChangeBelow(x, y, z); + // Add the biomes surface structure: + z = @min(z + chunk.super.pos.voxelSize, biome.structure.addSubTerranian(chunk, surfaceBlock, @max(airBlockBelow, zBiome - 1), x, y, &bseed)); + z -= chunk.super.pos.voxelSize; + if(z < zBiome) break; + if(z > airBlockBelow) { + const zMin = @max(airBlockBelow + 1, zBiome); + if(biome.stripes.len == 0) { + const typ = biome.stoneBlockType; + chunk.updateBlockColumnInGeneration(x, y, zMin, z, .{.typ = typ, .data = 0}); // TODO: Natural standard. + z = zMin; + } else { + while(z >= zMin) : (z -= chunk.super.pos.voxelSize) { + var typ = biome.stoneBlockType; + var seed = baseSeed; + for (biome.stripes) |stripe| { + const pos: Vec3d = .{ + @as(f64, @floatFromInt(x + chunk.super.pos.wx)), + @as(f64, @floatFromInt(y + chunk.super.pos.wy)), + @as(f64, @floatFromInt(z + chunk.super.pos.wz)) + }; + var d: f64 = 0; + if (stripe.direction) |direction| { + d = vec.dot(direction, pos); + } else { + const dx = main.random.nextDoubleSigned(&seed); + const dy = main.random.nextDoubleSigned(&seed); + const dz = main.random.nextDoubleSigned(&seed); + const dir: Vec3d = .{dx, dy, dz}; + d = vec.dot(vec.normalize(dir), pos); + } + + const distance = (stripe.maxDistance - stripe.minDistance) * main.random.nextDouble(&seed) + stripe.minDistance; + + const offset = (stripe.maxOffset - stripe.minOffset) * main.random.nextDouble(&seed) + stripe.minOffset; + + const width = (stripe.maxWidth - stripe.minWidth) * main.random.nextDouble(&seed) + stripe.minWidth; + + if (@mod(d + offset, distance) < width) { + typ = stripe.block; + break; + } + } + chunk.updateBlockInGeneration(x, y, z, .{.typ = typ, .data = 0}); // TODO: Natural standard. } + z += chunk.super.pos.voxelSize; } - chunk.updateBlockInGeneration(x, y, z, .{.typ = typ, .data = 0}); // TODO: Natural standard. } } else { - if(z + chunk.super.pos.wz < 0 and z + chunk.super.pos.wz >= biomeMap.getSurfaceHeight(x + chunk.super.pos.wx, y + chunk.super.pos.wy) - (chunk.super.pos.voxelSize - 1)) { - chunk.updateBlockInGeneration(x, y, z, .{.typ = water, .data = 0}); // TODO: Natural standard. + const surface = biomeMap.getSurfaceHeight(x + chunk.super.pos.wx, y + chunk.super.pos.wy) - (chunk.super.pos.voxelSize - 1) -% chunk.super.pos.wz; + const oceanHeight = 0 -% chunk.super.pos.wz; + const airVolumeStart = caveMap.findTerrainChangeBelow(x, y, z) + chunk.super.pos.voxelSize; + const zStart = @max(airVolumeStart, zBiome); + if(z < surface or zStart >= oceanHeight) { + chunk.updateBlockColumnInGeneration(x, y, zStart, z, .{.typ = 0, .data = 0}); } else { - chunk.updateBlockInGeneration(x, y, z, .{.typ = 0, .data = 0}); + if(z >= oceanHeight) { + chunk.updateBlockColumnInGeneration(x, y, oceanHeight, z, .{.typ = 0, .data = 0}); + z = oceanHeight - chunk.super.pos.voxelSize; + } + chunk.updateBlockColumnInGeneration(x, y, zStart, z, .{.typ = water, .data = 0}); // TODO: Natural standard. } - makeSurfaceStructure = true; + z = zStart; } } } diff --git a/src/utils.zig b/src/utils.zig index b2d29858..6bd8ae91 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -1252,7 +1252,7 @@ pub fn PaletteCompressedRegion(T: type, size: comptime_int) type { // MARK: Pale return self.palette[self.data.getValue(i)]; } - pub fn setValue(noalias self: *Self, i: usize, val: T) void { + fn getOrInsertPaletteIndex(noalias self: *Self, val: T) u32 { std.debug.assert(self.paletteLength <= self.palette.len); var paletteIndex: u32 = 0; while(paletteIndex < self.paletteLength) : (paletteIndex += 1) { // TODO: There got to be a faster way to do this. Either using SIMD or using a cache or hashmap. @@ -1272,7 +1272,11 @@ pub fn PaletteCompressedRegion(T: type, size: comptime_int) type { // MARK: Pale self.paletteLength += 1; std.debug.assert(self.paletteLength <= self.palette.len); } + return paletteIndex; + } + pub fn setValue(noalias self: *Self, i: usize, val: T) void { + const paletteIndex = self.getOrInsertPaletteIndex(val); const previousPaletteIndex = self.data.setAndGetValue(i, paletteIndex); if(previousPaletteIndex != paletteIndex) { if(self.paletteOccupancy[paletteIndex] == 0) { @@ -1286,6 +1290,22 @@ pub fn PaletteCompressedRegion(T: type, size: comptime_int) type { // MARK: Pale } } + pub fn setValueInColumn(noalias self: *Self, startIndex: usize, endIndex: usize, val: T) void { + std.debug.assert(startIndex < endIndex); + const paletteIndex = self.getOrInsertPaletteIndex(val); + for(startIndex..endIndex) |i| { + const previousPaletteIndex = self.data.setAndGetValue(i, paletteIndex); + self.paletteOccupancy[previousPaletteIndex] -= 1; + if(self.paletteOccupancy[previousPaletteIndex] == 0) { + self.activePaletteEntries -= 1; + } + } + if(self.paletteOccupancy[paletteIndex] == 0) { + self.activePaletteEntries += 1; + } + self.paletteOccupancy[paletteIndex] += @intCast(endIndex - startIndex); + } + pub fn optimizeLayout(self: *Self) void { if(std.math.log2_int_ceil(usize, self.palette.len) == std.math.log2_int_ceil(usize, self.activePaletteEntries)) return;