diff --git a/src/server/terrain/CaveMap.zig b/src/server/terrain/CaveMap.zig index 7500e09b31..1de72f7bf4 100644 --- a/src/server/terrain/CaveMap.zig +++ b/src/server/terrain/CaveMap.zig @@ -22,6 +22,11 @@ pub const CaveMapFragment = struct { // MARK: CaveMapFragment pub const height = 64; // Size of u64 pub const heightMask = height - 1; + pub const TerrainModifier = enum { + add, + remove, + }; + data: [width*width]u64 = undefined, pos: ChunkPosition, voxelShift: u5, @@ -77,6 +82,14 @@ pub const CaveMapFragment = struct { // MARK: CaveMapFragment self.data[getIndex(relX, relY)] &= getMask(start, end); } + pub fn modifyTerrain(self: *CaveMapFragment, comptime modifier: TerrainModifier, _relX: i32, _relY: i32, _start: i32, _end: i32) void { + if (modifier == .add) { + self.addRange(_relX, _relY, _start, _end); + } else { + self.removeRange(_relX, _relY, _start, _end); + } + } + pub fn getColumnData(self: *CaveMapFragment, _relX: i32, _relY: i32) u64 { const relX = _relX >> self.voxelShift; const relY = _relY >> self.voxelShift; diff --git a/src/server/terrain/cavegen/FractalCaveGenerator.zig b/src/server/terrain/cavegen/FractalCaveGenerator.zig index 5c38b0f93f..f4102b6308 100644 --- a/src/server/terrain/cavegen/FractalCaveGenerator.zig +++ b/src/server/terrain/cavegen/FractalCaveGenerator.zig @@ -58,72 +58,90 @@ pub fn generate(map: *CaveMapFragment, worldSeed: u64) void { } } -fn generateSphere_(seed: *u64, map: *CaveMapFragment, relPos: Vec3f, radius: f32, comptime addTerrain: bool) void { - const relX = relPos[0]; - const relY = relPos[1]; - const relZ = relPos[2]; - var xMin = @as(i32, @trunc(relX - radius)) - 1; - xMin = @max(xMin, 0); - var xMax = @as(i32, @trunc(relX + radius)) + 1; - xMax = @min(xMax, CaveMapFragment.width*map.pos.voxelSize); - var yMin = @as(i32, @trunc(relY - radius)) - 1; - yMin = @max(yMin, 0); - var yMax = @as(i32, @trunc(relY + radius)) + 1; - yMax = @min(yMax, CaveMapFragment.width*map.pos.voxelSize); - if (xMin >= xMax or yMin >= yMax or relZ - radius + 1 >= @as(f32, @floatFromInt(CaveMapFragment.height*map.pos.voxelSize)) or relZ + radius + 1 < 0) { - return; - } +fn getSphereBounds(center: Vec3f, radius: f32, maxExtent: Vec3i) struct { Vec3i, Vec3i } { + // the call to this in `generateSphere_` doesn't leak through to this function. + @setFloatMode(.optimized); + const vecRadius: Vec3f = @splat(radius); + const vec3iOne: Vec3i = @splat(1); + + const minBound = @as(Vec3i, @trunc(center - vecRadius)) - vec3iOne; + const maxBound = @as(Vec3i, @trunc(center + vecRadius)) + vec3iOne; + + return .{ + @max(minBound, @as(Vec3i, @splat(0))), + @min(maxBound, maxExtent), + }; +} + +fn generateSphere_(seed: *u64, map: *CaveMapFragment, relPos: Vec3f, radius: f32, comptime terrainModifier: CaveMapFragment.TerrainModifier) void { + @setFloatMode(.optimized); + + // Makes walls rough by adding a 1-in-roughnessChance chance that blocks + // remain unchanged. + const roughnessChance = 6; + const voxelSize = map.pos.voxelSize; + const scaledWidth = CaveMapFragment.width*voxelSize; + const scaledHeight = CaveMapFragment.height*voxelSize; + + const minDist, const maxDist = getSphereBounds(relPos, radius, .{scaledWidth, scaledWidth, scaledHeight}); + if (@reduce(.Or, minDist >= maxDist)) return; + + const relX, const relY, const relZ = relPos; + const minXDist, const minYDist, _ = minDist; + const maxXDist, const maxYDist, _ = maxDist; + + const radiusSquare = radius*radius; + const thresholdXY = 0.9*0.9*radiusSquare; + // Go through all blocks within range of the sphere center and remove them. - var curX = xMin; - while (curX < xMax) : (curX += map.pos.voxelSize) { - const distToCenterX = (@as(f32, @floatFromInt(curX)) - relX)/radius; - var curY = yMin; - while (curY < yMax) : (curY += map.pos.voxelSize) { - const distToCenterY = (@as(f32, @floatFromInt(curY)) - relY)/radius; - const xyDistanceSquared = distToCenterX*distToCenterX + distToCenterY*distToCenterY; + var curX = minXDist; + while (curX < maxXDist) : (curX += voxelSize) { + const dx = @as(f32, @floatFromInt(curX)) - relX; + + var curY = minYDist; + while (curY < maxYDist) : (curY += voxelSize) { + const dy = @as(f32, @floatFromInt(curY)) - relY; + const xySumSquare = @mulAdd(f32, dy, dy, dx*dx); + var zMin: i32 = @trunc(relZ); var zMax: i32 = @trunc(relZ); - if (xyDistanceSquared < 0.9*0.9) { - const zDistance = radius*@sqrt(0.9*0.9 - xyDistanceSquared); + if (xySumSquare < thresholdXY) { + const zDistance = @sqrt(thresholdXY - xySumSquare); zMin = @trunc(relZ - zDistance); zMax = @trunc(relZ + zDistance); - if (addTerrain) { - map.addRange(curX, curY, zMin, zMax); // Add the center range in a single call. - } else { - map.removeRange(curX, curY, zMin, zMax); // Remove the center range in a single call. - } + map.modifyTerrain(terrainModifier, curX, curY, zMin, zMax); } - // Add some roughness at the upper cave walls: + + // My rather poor attempt at explaining to whatever poor soul wants to + // understand this: + // + // (x - r_x)^2 + (y - r_y)^2 + (z - r_z)^2 = r^2 + // we already know x and y, so: + // (z - r_z)^2 = r^2 - xySumSquare + // z = r_z +- sqrt(r^2 - xySumSquare) + // where +z is the upper-bound, and -z is the lower-bound + // + // this calculation allows us to avoid checking per-iteration if a voxel is + // within the sphere that we were doing before + if (xySumSquare >= radiusSquare) continue; + const outerZDistance = @sqrt(radiusSquare - xySumSquare); + const outerZMax: i32 = @min(@as(i32, @trunc(relZ + outerZDistance)), scaledHeight); + const outerZMin: i32 = @max(@as(i32, @trunc(relZ - outerZDistance)), 0); + + // Add some roughness to the upper cave walls: var curZ: i32 = zMax; - while (curZ <= CaveMapFragment.height*map.pos.voxelSize) : (curZ += map.pos.voxelSize) { - const distToCenterZ = (@as(f32, @floatFromInt(curZ)) - relZ)/radius; - const distToCenter = distToCenterZ*distToCenterZ + xyDistanceSquared; - if (distToCenter < 1) { - // Add a small roughness parameter to make walls look a bit rough by filling only 5/6 of the blocks at the walls with air: - if (random.nextIntBounded(u8, seed, 6) != 0) { - if (addTerrain) { - map.addRange(curX, curY, curZ, curZ + 1); - } else { - map.removeRange(curX, curY, curZ, curZ + 1); - } - } - } else break; + while (curZ <= outerZMax) : (curZ += voxelSize) { + if (random.nextIntBounded(u8, seed, roughnessChance) != 0) { + map.modifyTerrain(terrainModifier, curX, curY, curZ, curZ + 1); + } } - // Add some roughness at the lower cave walls: + + // Add some roughness to the lower cave walls: curZ = zMin; - while (curZ >= 0) : (curZ -= map.pos.voxelSize) { - const distToCenterZ = (@as(f32, @floatFromInt(curZ)) - relZ)/radius; - const distToCenter = distToCenterZ*distToCenterZ + xyDistanceSquared; - if (distToCenter < 1) { - // Add a small roughness parameter to make walls look a bit rough by filling only 5/6 of the blocks at the walls with air: - if (random.nextIntBounded(u8, seed, 6) != 0) { - if (addTerrain) { - map.addRange(curX, curY, curZ, curZ + 1); - } else { - map.removeRange(curX, curY, curZ, curZ + 1); - } - } - } else break; + while (curZ >= outerZMin) : (curZ -= voxelSize) { + if (random.nextIntBounded(u8, seed, roughnessChance) != 0) { + map.modifyTerrain(terrainModifier, curX, curY, curZ, curZ + 1); + } } } } @@ -131,9 +149,9 @@ fn generateSphere_(seed: *u64, map: *CaveMapFragment, relPos: Vec3f, radius: f32 fn generateSphere(seed: *u64, map: *CaveMapFragment, relPos: Vec3f, radius: f32) void { if (radius < 0) { - generateSphere_(seed, map, relPos, -radius, true); + generateSphere_(seed, map, relPos, -radius, .add); } else { - generateSphere_(seed, map, relPos, radius, false); + generateSphere_(seed, map, relPos, radius, .remove); } }