mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-09-09 03:59:53 -04:00
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:
parent
a6693d4113
commit
358c0f38a8
@ -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
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
78
src/server/terrain/simple_structures/Stalagmite.zig
Normal file
78
src/server/terrain/simple_structures/Stalagmite.zig
Normal 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.
|
||||
}
|
||||
}
|
@ -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");
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user