Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/server/terrain/CaveMap.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
136 changes: 77 additions & 59 deletions src/server/terrain/cavegen/FractalCaveGenerator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -58,82 +58,100 @@ 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems odd to explain it here when the same math was used literally 4 lines above.

// understand this:
//
// (x - r_x)^2 + (y - r_y)^2 + (z - r_z)^2 = r^2

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For math I generally suggest to use unicode symbols, it makes it a lot nicer to read.

Suggested change
// (x - r_x)^2 + (y - r_y)^2 + (z - r_z)^2 = r^2
// (x - r_x)² + (y - r_y)² + (z - r_z)² = r²

Sadly there are no subscripts for y and z though, only for ₓ, and it would look odd to only use one subscript.

// 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);
}
}
}
}
}

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);
}
}

Expand Down
Loading