diff --git a/assets/cubyz/biomes/peak.json b/assets/cubyz/biomes/peak.json index b966313ce..43bf8deed 100644 --- a/assets/cubyz/biomes/peak.json +++ b/assets/cubyz/biomes/peak.json @@ -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 }, ] } diff --git a/src/random.zig b/src/random.zig index 520a59e89..99dbe4fc7 100644 --- a/src/random.zig +++ b/src/random.zig @@ -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) { diff --git a/src/server/terrain/biomes.zig b/src/server/terrain/biomes.zig index 496b56af3..de9f6e900 100644 --- a/src/server/terrain/biomes.zig +++ b/src/server/terrain/biomes.zig @@ -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; } diff --git a/src/server/terrain/simple_structures/Boulder.zig b/src/server/terrain/simple_structures/Boulder.zig index fe37220c5..c46265065 100644 --- a/src/server/terrain/simple_structures/Boulder.zig +++ b/src/server/terrain/simple_structures/Boulder.zig @@ -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. diff --git a/src/server/terrain/simple_structures/FallenTree.zig b/src/server/terrain/simple_structures/FallenTree.zig index be9d0e591..949495914 100644 --- a/src/server/terrain/simple_structures/FallenTree.zig +++ b/src/server/terrain/simple_structures/FallenTree.zig @@ -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); diff --git a/src/server/terrain/simple_structures/GroundPatch.zig b/src/server/terrain/simple_structures/GroundPatch.zig index ae947c09c..f3a65ca72 100644 --- a/src/server/terrain/simple_structures/GroundPatch.zig +++ b/src/server/terrain/simple_structures/GroundPatch.zig @@ -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); diff --git a/src/server/terrain/simple_structures/SimpleTreeModel.zig b/src/server/terrain/simple_structures/SimpleTreeModel.zig index f1f3d7a29..c9d5704d0 100644 --- a/src/server/terrain/simple_structures/SimpleTreeModel.zig +++ b/src/server/terrain/simple_structures/SimpleTreeModel.zig @@ -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; diff --git a/src/server/terrain/simple_structures/SimpleVegetation.zig b/src/server/terrain/simple_structures/SimpleVegetation.zig index 1dcc0f590..4b11bb7aa 100644 --- a/src/server/terrain/simple_structures/SimpleVegetation.zig +++ b/src/server/terrain/simple_structures/SimpleVegetation.zig @@ -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); diff --git a/src/server/terrain/simple_structures/Stalagmite.zig b/src/server/terrain/simple_structures/Stalagmite.zig new file mode 100644 index 000000000..5b5b62e64 --- /dev/null +++ b/src/server/terrain/simple_structures/Stalagmite.zig @@ -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. + } +} diff --git a/src/server/terrain/simple_structures/_list.zig b/src/server/terrain/simple_structures/_list.zig index fd002bc72..6b181adf3 100644 --- a/src/server/terrain/simple_structures/_list.zig +++ b/src/server/terrain/simple_structures/_list.zig @@ -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"); \ No newline at end of file +pub const SimpleVegetation = @import("SimpleVegetation.zig"); +pub const Stalagmite = @import("Stalagmite.zig"); \ No newline at end of file diff --git a/src/server/terrain/structuremapgen/SimpleStructureGen.zig b/src/server/terrain/structuremapgen/SimpleStructureGen.zig index 61d1d6835..cc0bf7ec1 100644 --- a/src/server/terrain/structuremapgen/SimpleStructureGen.zig +++ b/src/server/terrain/structuremapgen/SimpleStructureGen.zig @@ -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); } };