Cubyz/src/server/terrain/simple_structures/SimpleTreeModel.zig

157 lines
6.3 KiB
Zig

const std = @import("std");
const main = @import("root");
const random = main.random;
const ZonElement = main.ZonElement;
const terrain = main.server.terrain;
const CaveBiomeMapView = terrain.CaveBiomeMap.CaveBiomeMapView;
const CaveMapView = terrain.CaveMap.CaveMapView;
const GenerationMode = terrain.biomes.SimpleStructureModel.GenerationMode;
const vec = main.vec;
const Vec3d = vec.Vec3d;
const Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const NeverFailingAllocator = main.heap.NeverFailingAllocator;
pub const id = "cubyz:simple_tree";
const SimpleTreeModel = @This();
pub const generationMode = .floor;
const Type = enum {
pyramid,
round,
};
typ: Type,
leavesBlock: main.blocks.Block,
woodBlock: main.blocks.Block,
topWoodBlock: main.blocks.Block,
height0: i32,
deltaHeight: u31,
leafRadius: f32,
deltaLeafRadius: f32,
leafElongation: f32,
deltaLeafElongation: f32,
branched: bool,
pub fn loadModel(arenaAllocator: NeverFailingAllocator, parameters: ZonElement) *SimpleTreeModel {
const self = arenaAllocator.create(SimpleTreeModel);
self.* = .{
.typ = std.meta.stringToEnum(Type, parameters.get([]const u8, "type", "")) orelse blk: {
if(parameters.get(?[]const u8, "type", null)) |typ| std.log.err("Unknown tree type \"{s}\"", .{typ});
break :blk .round;
},
.leavesBlock = main.blocks.parseBlock(parameters.get([]const u8, "leaves", "cubyz:oak_leaves")),
.woodBlock = main.blocks.parseBlock(parameters.get([]const u8, "log", "cubyz:oak_log")),
.topWoodBlock = main.blocks.parseBlock(parameters.get([]const u8, "top", "cubyz:oak_top")),
.height0 = parameters.get(i32, "height", 6),
.deltaHeight = parameters.get(u31, "height_variation", 3),
.leafRadius = parameters.get(f32, "leafRadius", (1 + parameters.get(f32, "height", 6))/2),
.deltaLeafRadius = parameters.get(f32, "leafRadius_variation", parameters.get(f32, "height_variation", 3)/2),
.leafElongation = parameters.get(f32, "leafElongation", 1),
.deltaLeafElongation = parameters.get(f32, "deltaLeafElongation", 0),
.branched = parameters.get(bool, "branched", true),
};
return self;
}
pub fn generateStem(self: *SimpleTreeModel, x: i32, y: i32, z: i32, height: i32, chunk: *main.chunk.ServerChunk, seed: *u64) void {
if(chunk.super.pos.voxelSize <= 2) {
var pz: i32 = chunk.startIndex(z);
while(pz < z + height) : (pz += chunk.super.pos.voxelSize) {
if(chunk.liesInChunk(x, y, pz)) {
chunk.updateBlockIfDegradable(x, y, pz, if(pz == z + height - 1) self.topWoodBlock else self.woodBlock);
if(self.branched) {
const chance = @sqrt(@as(f32, @floatFromInt(pz - z))/@as(f32, @floatFromInt(height*2)));
if(main.random.nextFloat(seed) < chance) {
const d = main.random.nextIntBounded(u32, seed, 4);
generateBranch(self, x, y, pz, d, chunk, seed);
}
}
}
}
}
}
pub fn generateBranch(self: *SimpleTreeModel, x: i32, y: i32, z: i32, d: u32, chunk: *main.chunk.ServerChunk, seed: *u64) void {
_ = seed;
if(d == 0 and chunk.liesInChunk(x + 1, y, z)) {
chunk.updateBlockIfDegradable(x + 1, y, z, .{.typ = self.topWoodBlock.typ, .data = 2});
} else if(d == 1 and chunk.liesInChunk(x - 1, y, z)) {
chunk.updateBlockIfDegradable(x - 1, y, z, .{.typ = self.topWoodBlock.typ, .data = 3});
} else if(d == 2 and chunk.liesInChunk(x, y + 1, z)) {
chunk.updateBlockIfDegradable(x, y + 1, z, .{.typ = self.topWoodBlock.typ, .data = 4});
} else if(d == 3 and chunk.liesInChunk(x, y - 1, z)) {
chunk.updateBlockIfDegradable(x, y - 1, z, .{.typ = self.topWoodBlock.typ, .data = 5});
}
}
pub fn generate(self: *SimpleTreeModel, _: GenerationMode, x: i32, y: i32, z: i32, chunk: *main.chunk.ServerChunk, caveMap: CaveMapView, _: CaveBiomeMapView, 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;
const leafElongation: f32 = self.leafElongation + random.nextFloatSigned(seed)*self.deltaLeafElongation;
if(z + height >= caveMap.findTerrainChangeAbove(x, y, z)) // Space is too small.Allocator
return;
if(z > chunk.super.width) return;
if(chunk.super.pos.voxelSize >= 16) {
// Ensures that even at lowest resolution some leaves are rendered for smaller trees.
if(chunk.liesInChunk(x, y, z)) {
chunk.updateBlockIfDegradable(x, y, z, self.leavesBlock);
}
if(chunk.liesInChunk(x, y, z + chunk.super.pos.voxelSize)) {
chunk.updateBlockIfDegradable(x, y, z + chunk.super.pos.voxelSize, self.leavesBlock);
}
}
switch(self.typ) {
.pyramid => {
self.generateStem(x, y, z, height, chunk, seed);
// Position of the first block of leaves
height = 3*height >> 1;
var pz = chunk.startIndex(z + @divTrunc(height, 3));
while(pz < z + height) : (pz += chunk.super.pos.voxelSize) {
const j = @divFloor(height - (pz - z), 2);
var px = chunk.startIndex(x + 1 - j);
while(px < x + j) : (px += chunk.super.pos.voxelSize) {
var py = chunk.startIndex(y + 1 - j);
while(py < y + j) : (py += chunk.super.pos.voxelSize) {
if(chunk.liesInChunk(px, py, pz))
chunk.updateBlockIfDegradable(px, py, pz, self.leavesBlock);
}
}
}
},
.round => {
self.generateStem(x, y, z, height, chunk, seed);
const ceilZRadius: i32 = @intFromFloat(@ceil(leafRadius*leafElongation));
const ceilRadius: i32 = @intFromFloat(@ceil(leafRadius));
const radiusSqr: f32 = leafRadius*leafRadius;
const randomRadiusSqr: f32 = (leafRadius - 0.25)*(leafRadius - 0.25);
const invLeafElongationSqr = 1.0/(leafElongation*leafElongation);
const center = z + height;
var pz = chunk.startIndex(center - ceilZRadius);
while(pz < center + ceilZRadius) : (pz += chunk.super.pos.voxelSize) {
var px = chunk.startIndex(x - ceilRadius);
while(px < x + ceilRadius) : (px += chunk.super.pos.voxelSize) {
var py = chunk.startIndex(y - ceilRadius);
while(py < y + ceilRadius) : (py += chunk.super.pos.voxelSize) {
const distSqr = @as(f32, @floatFromInt((pz - center)*(pz - center)))*invLeafElongationSqr + @as(f32, @floatFromInt((px - x)*(px - x) + (py - y)*(py - y)));
if(chunk.liesInChunk(px, py, pz) and distSqr < radiusSqr and (distSqr < randomRadiusSqr or random.nextInt(u1, seed) != 0)) { // TODO: Use another seed to make this more reliable!
chunk.updateBlockIfDegradable(px, py, pz, self.leavesBlock);
}
}
}
}
},
}
}