Implement Stalagmites as their own simple structure model, which allows configuring the parameters in detail, potentially even adding multiple stalagmites with different block types..

Could also help with #547 once the biomes in question are transitioned to the new stalagmite structures, instead of using the old, dedicated generator.
This commit is contained in:
IntegratedQuantum 2024-07-11 21:12:32 +02:00
parent a6693d4113
commit 358c0f38a8
11 changed files with 156 additions and 19 deletions

View File

@ -15,11 +15,11 @@
"stoneBlock" : "cubyz:glacite",
"structures" : [
{
"id" : "cubyz:simple_vegetation",
"id" : "cubyz:stalagmite",
"block" : "cubyz:ice",
"chance" : 0.005,
"height" : 2,
"height_variation" : 5
"size" : 3,
"size_variation" : 5
},
]
}

View File

@ -36,7 +36,7 @@ pub fn nextInt(comptime T: type, seed: *u64) T {
pub fn nextIntBounded(comptime T: type, seed: *u64, bound: T) T {
if(@typeInfo(T) != .Int) @compileError("Type must be integer.");
if(@typeInfo(T).Int.signedness == .signed) @compileError("Type must be unsigned.");
if(@typeInfo(T).Int.signedness == .signed) return nextIntBounded(std.meta.Int(.unsigned, @bitSizeOf(T) - 1), seed, @intCast(bound));
const bitSize = std.math.log2_int_ceil(T, bound);
var result = nextWithBitSize(T, seed, bitSize);
while(result >= bound) {

View File

@ -11,10 +11,18 @@ const Vec3f = main.vec.Vec3f;
const Vec3d = main.vec.Vec3d;
pub const SimpleStructureModel = struct {
const GenerationMode = enum {
floor,
ceiling,
floor_and_ceiling,
air,
underground,
};
const VTable = struct {
loadModel: *const fn(arenaAllocator: NeverFailingAllocator, parameters: JsonElement) *anyopaque,
generate: *const fn(self: *anyopaque, x: i32, y: i32, z: i32, chunk: *ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64) void,
generate: *const fn(self: *anyopaque, x: i32, y: i32, z: i32, chunk: *ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64, isCeiling: bool) void,
hashFunction: *const fn(self: *anyopaque) u64,
generationMode: GenerationMode,
};
vtable: VTable,
@ -34,8 +42,8 @@ pub const SimpleStructureModel = struct {
};
}
pub fn generate(self: SimpleStructureModel, x: i32, y: i32, z: i32, chunk: *ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64) void {
self.vtable.generate(self.data, x, y, z, chunk, caveMap, seed);
pub fn generate(self: SimpleStructureModel, x: i32, y: i32, z: i32, chunk: *ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64, isCeiling: bool) void {
self.vtable.generate(self.data, x, y, z, chunk, caveMap, seed, isCeiling);
}
@ -55,6 +63,7 @@ pub const SimpleStructureModel = struct {
return hashGeneric(ptr.*);
}
}.hash);
self.generationMode = Generator.generationMode;
modelRegistry.put(main.globalAllocator.allocator, Generator.id, self) catch unreachable;
}

View File

@ -13,6 +13,8 @@ const NeverFailingAllocator = main.utils.NeverFailingAllocator;
pub const id = "cubyz:boulder";
pub const generationMode = .floor;
const Boulder = @This();
blockType: u16,
@ -29,7 +31,7 @@ pub fn loadModel(arenaAllocator: NeverFailingAllocator, parameters: JsonElement)
return self;
}
pub fn generate(self: *Boulder, x: i32, y: i32, z: i32, chunk: *main.chunk.ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64) void {
pub fn generate(self: *Boulder, x: i32, y: i32, z: i32, chunk: *main.chunk.ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64, _: bool) void {
_ = caveMap;
const radius = self.size + self.sizeVariation*(random.nextFloat(seed)*2 - 1);
// My basic idea is to use a point cloud and a potential function to achieve somewhat smooth boulders without being a sphere.

View File

@ -13,6 +13,8 @@ const NeverFailingAllocator = main.utils.NeverFailingAllocator;
pub const id = "cubyz:fallen_tree";
pub const generationMode = .floor;
const FallenTree = @This();
woodBlock: u16,
@ -99,7 +101,7 @@ pub fn generateFallen(self: *FallenTree, x: i32, y: i32, z: i32, length: u32, ch
}
}
pub fn generate(self: *FallenTree, x: i32, y: i32, z: i32, chunk: *main.chunk.ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64) void {
pub fn generate(self: *FallenTree, x: i32, y: i32, z: i32, chunk: *main.chunk.ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64, _: bool) void {
const height = self.height0 + random.nextIntBounded(u31, seed, self.deltaHeight);

View File

@ -13,6 +13,8 @@ const NeverFailingAllocator = main.utils.NeverFailingAllocator;
pub const id = "cubyz:ground_patch";
pub const generationMode = .floor;
const GroundPatch = @This();
blockType: u16,
@ -33,7 +35,7 @@ pub fn loadModel(arenaAllocator: NeverFailingAllocator, parameters: JsonElement)
return self;
}
pub fn generate(self: *GroundPatch, x: i32, y: i32, z: i32, chunk: *main.chunk.ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64) void {
pub fn generate(self: *GroundPatch, x: i32, y: i32, z: i32, chunk: *main.chunk.ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64, _: bool) void {
const width = self.width + (random.nextFloat(seed) - 0.5)*self.variation;
const orientation = 2*std.math.pi*random.nextFloat(seed);
const ellipseParam = 1 + random.nextFloat(seed);

View File

@ -15,6 +15,8 @@ pub const id = "cubyz:simple_tree";
const SimpleTreeModel = @This();
pub const generationMode = .floor;
const Type = enum {
pyramid,
round,
@ -83,7 +85,7 @@ pub fn generateBranch(self: *SimpleTreeModel, x: i32, y: i32, z: i32, d: u32, ch
}
}
pub fn generate(self: *SimpleTreeModel, x: i32, y: i32, z: i32, chunk: *main.chunk.ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64) void {
pub fn generate(self: *SimpleTreeModel, x: i32, y: i32, z: i32, chunk: *main.chunk.ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64, _: bool) void {
const factor = random.nextFloat(seed);
var height = self.height0 + @as(i32, @intFromFloat(factor*@as(f32, @floatFromInt(self.deltaHeight))));
const leafRadius = self.leafRadius + factor*self.deltaLeafRadius;

View File

@ -13,6 +13,8 @@ const NeverFailingAllocator = main.utils.NeverFailingAllocator;
pub const id = "cubyz:simple_vegetation";
pub const generationMode = .floor;
const SimpleVegetation = @This();
blockType: u16,
@ -29,7 +31,7 @@ pub fn loadModel(arenaAllocator: NeverFailingAllocator, parameters: JsonElement)
return self;
}
pub fn generate(self: *SimpleVegetation, x: i32, y: i32, z: i32, chunk: *main.chunk.ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64) void {
pub fn generate(self: *SimpleVegetation, x: i32, y: i32, z: i32, chunk: *main.chunk.ServerChunk, caveMap: terrain.CaveMap.CaveMapView, seed: *u64, _: bool) void {
if(chunk.super.pos.voxelSize > 2 and (x & chunk.super.pos.voxelSize-1 != 0 or y & chunk.super.pos.voxelSize-1 != 0)) return;
if(chunk.liesInChunk(x, y, z)) {
const height = self.height0 + random.nextIntBounded(u31, seed, self.deltaHeight+1);

View File

@ -0,0 +1,78 @@
const std = @import("std");
const main = @import("root");
const random = main.random;
const JsonElement = main.JsonElement;
const terrain = main.server.terrain;
const CaveMap = terrain.CaveMap;
const vec = main.vec;
const Vec3d = vec.Vec3d;
const Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const NeverFailingAllocator = main.utils.NeverFailingAllocator;
pub const id = "cubyz:stalagmite";
pub const generationMode = .floor_and_ceiling;
const Stalagmite = @This();
blockType: u16,
size: f32,
sizeVariation: f32,
pub fn loadModel(arenaAllocator: NeverFailingAllocator, parameters: JsonElement) *Stalagmite {
const self = arenaAllocator.create(Stalagmite);
self.* = .{
.blockType = main.blocks.getByID(parameters.get([]const u8, "block", "cubyz:stalagmite")),
.size = parameters.get(f32, "size", 12),
.sizeVariation = parameters.get(f32, "size_variation", 8),
};
return self;
}
pub fn generate(self: *Stalagmite, x: i32, y: i32, z: i32, chunk: *main.chunk.ServerChunk, _: terrain.CaveMap.CaveMapView, seed: *u64, isCeiling: bool) void {
const relX: f32 = @as(f32, @floatFromInt(x)) + main.random.nextFloat(seed);
const relY: f32 = @as(f32, @floatFromInt(y)) + main.random.nextFloat(seed);
var relZ: f32 = @as(f32, @floatFromInt(z)) + main.random.nextFloat(seed);
var length = self.size + random.nextFloat(seed)*self.sizeVariation;
const delZ: f32 = if(isCeiling) -1 else 1;
relZ -= delZ*length/4;
length += length/4;
var j: f32 = 0;
while(j < length) {
const z2 = relZ + delZ*j;
var size: f32 = 0;
size = (length - j)/4;
const xMin: i32 = @intFromFloat(relX - size);
const xMax: i32 = @intFromFloat(relX + size);
const yMin: i32 = @intFromFloat(relY - size);
const yMax: i32 = @intFromFloat(relY + size);
const zMin: i32 = @intFromFloat(z2 - size);
const zMax: i32 = @intFromFloat(z2 + size);
var x3: i32 = xMin;
while(x3 <= xMax) : (x3 += 1) {
var y3: i32 = yMin;
while(y3 <= yMax) : (y3 += 1) {
var z3: i32 = zMin;
while(z3 <= zMax) : (z3 += 1) {
const dist = vec.lengthSquare(Vec3f{@as(f32, @floatFromInt(x3)) - relX, @as(f32, @floatFromInt(y3)) - relY, @as(f32, @floatFromInt(z3)) - z2});
if(dist < size*size) {
if(x3 >= 0 and x3 < chunk.super.width and y3 >= 0 and y3 < chunk.super.width and z3 >= 0 and z3 < chunk.super.width) {
const block: main.blocks.Block = chunk.getBlock(x3, y3, z3);
if(block.typ == 0 or block.degradable() or block.blockClass() == .fluid) {
chunk.updateBlockInGeneration(x3, y3, z3, .{.typ = self.blockType, .data = 0}); // TODO: Use natural standard.
}
}
}
}
}
}
if(size > 2) size = 2;
j += size/2; // Make sure there are no stalagmite bits floating in the air.
if(size < 0.5) break; // Also preventing floating stalagmite bits.
}
}

View File

@ -4,4 +4,5 @@ pub const Boulder = @import("Boulder.zig");
pub const GroundPatch = @import("GroundPatch.zig");
pub const SimpleTreeModel = @import("SimpleTreeModel.zig");
pub const FallenTree = @import("FallenTree.zig");
pub const SimpleVegetation = @import("SimpleVegetation.zig");
pub const SimpleVegetation = @import("SimpleVegetation.zig");
pub const Stalagmite = @import("Stalagmite.zig");

View File

@ -126,12 +126,51 @@ const SimpleStructure = struct {
const px = self.wx - chunk.super.pos.wx;
const py = self.wy - chunk.super.pos.wy;
var relZ = self.wz -% chunk.super.pos.wz;
if(caveMap.isSolid(px, py, relZ)) {
relZ = caveMap.findTerrainChangeAbove(px, py, relZ);
} else {
relZ = caveMap.findTerrainChangeBelow(px, py, relZ) + chunk.super.pos.voxelSize;
var isCeiling: bool = false;
switch(self.model.vtable.generationMode) {
.floor => {
if(caveMap.isSolid(px, py, relZ)) {
relZ = caveMap.findTerrainChangeAbove(px, py, relZ);
} else {
relZ = caveMap.findTerrainChangeBelow(px, py, relZ) + chunk.super.pos.voxelSize;
}
if(relZ & ~@as(i32, 31) != self.wz -% chunk.super.pos.wz & ~@as(i32, 31)) return; // Too far from the surface.
},
.ceiling => {
isCeiling = true;
if(caveMap.isSolid(px, py, relZ)) {
relZ = caveMap.findTerrainChangeBelow(px, py, relZ) - chunk.super.pos.voxelSize;
} else {
relZ = caveMap.findTerrainChangeAbove(px, py, relZ);
}
if(relZ & ~@as(i32, 31) != self.wz -% chunk.super.pos.wz & ~@as(i32, 31)) return; // Too far from the surface.
},
.floor_and_ceiling => {
if(random.nextInt(u1, &seed) != 0) {
if(caveMap.isSolid(px, py, relZ)) {
relZ = caveMap.findTerrainChangeAbove(px, py, relZ);
} else {
relZ = caveMap.findTerrainChangeBelow(px, py, relZ) + chunk.super.pos.voxelSize;
}
} else {
isCeiling = true;
if(caveMap.isSolid(px, py, relZ)) {
relZ = caveMap.findTerrainChangeBelow(px, py, relZ) - chunk.super.pos.voxelSize;
} else {
relZ = caveMap.findTerrainChangeAbove(px, py, relZ);
}
}
if(relZ & ~@as(i32, 31) != self.wz -% chunk.super.pos.wz & ~@as(i32, 31)) return; // Too far from the surface.
},
.air => {
relZ += -16 + random.nextIntBounded(i32, &seed, 32);
if(caveMap.isSolid(px, py, relZ)) return;
},
.underground => {
relZ += -16 + random.nextIntBounded(i32, &seed, 32);
if(!caveMap.isSolid(px, py, relZ)) return;
},
}
if(relZ & ~@as(i32, 31) != self.wz -% chunk.super.pos.wz & ~@as(i32, 31)) return; // Too far from the surface.
self.model.generate(px, py, relZ, chunk, caveMap, &seed);
self.model.generate(px, py, relZ, chunk, caveMap, &seed, isCeiling);
}
};