Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
52ec760
Revert "Remove SBBGen"
Argmaster Mar 22, 2025
388e3fc
Revert "Remove example SBB"
Argmaster Mar 22, 2025
3e17dd5
Revert "Remove blueprint code"
Argmaster Mar 22, 2025
dfeb882
Merge remote-tracking branch 'origin/master' into feature/SBBGen
Argmaster Mar 28, 2025
c12c62c
Fix compilation errors
Argmaster Mar 29, 2025
3eae3a9
Fix compilation errors #2
Argmaster Mar 29, 2025
89f5674
Fix test errors
Argmaster Mar 29, 2025
992d7a0
Fix rotateZ
Argmaster Mar 29, 2025
09f8542
Resolve structure reference while instantiating SBBGen
Argmaster Mar 29, 2025
7524338
Fix formatting issues
Argmaster Mar 29, 2025
2e1125f
Merge remote-tracking branch 'origin/master' into feature/SBBGen
Argmaster Mar 29, 2025
62e7850
Add new trees to forest
Argmaster Mar 30, 2025
2b632c2
Add new trees to grassland
Argmaster Mar 30, 2025
5a9143f
Decrease forest density so you can find new trees
Argmaster Mar 30, 2025
b534531
Add degradable paste mode
Argmaster Mar 30, 2025
fea832e
Remove substitutions
Argmaster Apr 19, 2025
71c03e3
Apply review suggestions
Argmaster Apr 20, 2025
21f46e2
Use lookup table for alignment
Argmaster Apr 20, 2025
a5d3ca9
Apply suggestions from code review
Argmaster Apr 20, 2025
1c0f0b5
Update src/server/terrain/simple_structures/_list.zig
Argmaster Apr 20, 2025
7c85c52
Merge remote-tracking branch 'upstream/master' into feature/SBBGen
Argmaster Apr 20, 2025
1bb2642
Fix inital SBB offset
Argmaster Apr 20, 2025
642ae5b
Never place air in placeInGeneration
Argmaster Apr 20, 2025
1ed8d52
Integrate void block with paste
Argmaster Apr 22, 2025
5499d1a
Merge remote-tracking branch 'origin/master' into feature/SBBGen
Argmaster Apr 22, 2025
9482d4b
Revert "Move hashInt and hashCombine to utils"
Argmaster Apr 22, 2025
27a6307
Merge branch 'feature/paste-void' into feature/SBBGen
Argmaster Apr 22, 2025
4817f87
Make PasteMode comptime
Argmaster Apr 22, 2025
7689b36
Merge remote-tracking branch 'origin/master' into feature/SBBGen
Argmaster Apr 23, 2025
629b20b
Fix remaining issues with void block integration
Argmaster Apr 23, 2025
49c565d
Fix formatting
Argmaster Apr 24, 2025
0bdf687
Merge remote-tracking branch 'origin/master' into feature/SBBGen
Argmaster Apr 25, 2025
2fbcd50
Apply review change requests
Argmaster Apr 26, 2025
948c941
Remove origin and child blocks while resolving sbbs
Argmaster Apr 26, 2025
d17b5cb
Apply review change requests
Argmaster Apr 27, 2025
adaf49e
I hate indexing
Argmaster Apr 27, 2025
cba7a16
Fix example tree models
Argmaster Apr 27, 2025
17b2309
Merge remote-tracking branch 'upstream/master' into feature/SBBGen
Argmaster Apr 30, 2025
bd8d67b
Use single index for chunk and blueprint in pasteInGeneration
Argmaster May 1, 2025
6a9298d
Fix formatting
Argmaster May 1, 2025
9118579
Extract blueprintOffset
Argmaster May 1, 2025
abca432
Fix formatting
Argmaster May 1, 2025
73551f0
Apply suggestions from code review
Argmaster May 2, 2025
5de9483
Apply Quantums suggestion for Y and Z
Argmaster May 2, 2025
20827e7
No cast
Argmaster May 2, 2025
6961d4f
Use pos instead of chunkOffset
Argmaster May 2, 2025
cdcf5b1
Remove test tree
Argmaster May 2, 2025
e31a469
Apply suggestions from code review
Argmaster May 2, 2025
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
41 changes: 41 additions & 0 deletions src/blueprint.zig
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,46 @@ pub const Blueprint = struct {
}
return .{.success = self};
}

pub const PasteMode = enum {all, degradable};

pub fn pasteInGeneration(self: Blueprint, pos: Vec3i, chunk: *ServerChunk, mode: PasteMode) void {
switch(mode) {
inline else => |comptimeMode| _pasteInGeneration(self, pos, chunk, comptimeMode),
}
}

fn _pasteInGeneration(self: Blueprint, pos: Vec3i, chunk: *ServerChunk, comptime mode: PasteMode) void {
const indexEndX: i32 = @min(@as(i32, chunk.super.width) - pos[0], @as(i32, @intCast(self.blocks.width)));
const indexEndY: i32 = @min(@as(i32, chunk.super.width) - pos[1], @as(i32, @intCast(self.blocks.depth)));
const indexEndZ: i32 = @min(@as(i32, chunk.super.width) - pos[2], @as(i32, @intCast(self.blocks.height)));

var indexX: u31 = @max(0, -pos[0]);
while(indexX < indexEndX) : (indexX += chunk.super.pos.voxelSize) {
var indexY: u31 = @max(0, -pos[1]);
while(indexY < indexEndY) : (indexY += chunk.super.pos.voxelSize) {
var indexZ: u31 = @max(0, -pos[2]);
while(indexZ < indexEndZ) : (indexZ += chunk.super.pos.voxelSize) {
const block = self.blocks.get(indexX, indexY, indexZ);

if(block.typ == voidType) continue;

const chunkX = indexX + pos[0];
const chunkY = indexY + pos[1];
const chunkZ = indexZ + pos[2];
switch(mode) {
.all => chunk.updateBlockInGeneration(chunkX, chunkY, chunkZ, block),
.degradable => chunk.updateBlockIfDegradable(chunkX, chunkY, chunkZ, block),
}
}
}
}
}

pub const PasteFlags = struct {
preserveVoid: bool = false,
};

pub fn paste(self: Blueprint, pos: Vec3i, flags: PasteFlags) void {
const startX = pos[0];
const startY = pos[1];
Expand Down Expand Up @@ -280,3 +317,7 @@ pub fn registerVoidBlock(block: Block) void {
voidType = block.typ;
std.debug.assert(voidType != 0);
}

pub fn getVoidBlock() Block {
return Block{.typ = voidType.?, .data = 0};
}
90 changes: 90 additions & 0 deletions src/server/terrain/simple_structures/SbbGen.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
const std = @import("std");

const main = @import("main");
const terrain = main.server.terrain;
const Vec3i = main.vec.Vec3i;
const GenerationMode = terrain.biomes.SimpleStructureModel.GenerationMode;
const CaveMapView = terrain.CaveMap.CaveMapView;
const CaveBiomeMapView = terrain.CaveBiomeMap.CaveBiomeMapView;
const sbb = terrain.structure_building_blocks;
const Blueprint = main.blueprint.Blueprint;
const ZonElement = main.ZonElement;
const Neighbor = main.chunk.Neighbor;
const ServerChunk = main.chunk.ServerChunk;
const NeverFailingAllocator = main.heap.NeverFailingAllocator;

pub const id = "cubyz:sbb";
pub const generationMode = .floor;

const SbbGen = @This();

structureRef: *const sbb.StructureBuildingBlock,
placeMode: Blueprint.PasteMode,

pub fn getHash(self: SbbGen) u64 {
return std.hash.Wyhash.hash(@intFromEnum(self.placeMode), self.structureRef.id);
}

pub fn loadModel(arenaAllocator: NeverFailingAllocator, parameters: ZonElement) *SbbGen {
const structureId = parameters.get(?[]const u8, "structure", null) orelse unreachable;
const structureRef = sbb.getByStringId(structureId) orelse {
std.log.err("Could not find structure building block with id '{s}'", .{structureId});
unreachable;
};
const self = arenaAllocator.create(SbbGen);
self.* = .{
.structureRef = structureRef,
.placeMode = std.meta.stringToEnum(Blueprint.PasteMode, parameters.get([]const u8, "placeMode", "degradable")) orelse Blueprint.PasteMode.degradable,
};
return self;
}

pub fn generate(self: *SbbGen, _: GenerationMode, x: i32, y: i32, z: i32, chunk: *ServerChunk, _: CaveMapView, _: CaveBiomeMapView, seed: *u64, _: bool) void {
placeSbb(self, self.structureRef, Vec3i{x, y, z}, Neighbor.dirUp, chunk, seed);
}

fn placeSbb(self: *SbbGen, structure: *const sbb.StructureBuildingBlock, placementPosition: Vec3i, placementDirection: Neighbor, chunk: *ServerChunk, seed: *u64) void {
const origin = structure.blueprints[0].originBlock;
const rotationCount = alignDirections(origin.direction(), placementDirection) catch |err| {
std.log.err("Could not align directions for structure '{s}' for directions '{s}'' and '{s}', error: {s}", .{structure.id, @tagName(origin.direction()), @tagName(placementDirection), @errorName(err)});
return;
};
const rotated = &structure.blueprints[rotationCount];
const rotatedOrigin = rotated.originBlock.pos();
const pastePosition = placementPosition - rotatedOrigin - placementDirection.relPos();

rotated.blueprint.pasteInGeneration(pastePosition, chunk, self.placeMode);

for(rotated.childBlocks) |childBlock| {
const child = structure.pickChild(childBlock, seed);
placeSbb(self, child, pastePosition + childBlock.pos(), childBlock.direction(), chunk, seed);
}
}

fn alignDirections(input: Neighbor, desired: Neighbor) !usize {
const Rotation = enum(u3) {
@"0" = 0,
@"90" = 1,
@"180" = 2,
@"270" = 3,
NotPossibleToAlign = 4,
};
comptime var alignTable: [6][6]Rotation = undefined;
comptime for(Neighbor.iterable) |in| {
for(Neighbor.iterable) |out| blk: {
var current = in;
for(0..4) |i| {
if(current == out) {
alignTable[in.toInt()][out.toInt()] = @enumFromInt(i);
break :blk;
}
current = current.rotateZ();
}
alignTable[in.toInt()][out.toInt()] = Rotation.NotPossibleToAlign;
}
};
switch(alignTable[input.toInt()][desired.toInt()]) {
.NotPossibleToAlign => return error.NotPossibleToAlign,
else => |v| return @intFromEnum(v),
}
}
1 change: 1 addition & 0 deletions src/server/terrain/simple_structures/_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub const Boulder = @import("Boulder.zig");
pub const FallenTree = @import("FallenTree.zig");
pub const FlowerPatch = @import("FlowerPatch.zig");
pub const GroundPatch = @import("GroundPatch.zig");
pub const SbbGen = @import("SbbGen.zig");
pub const SimpleTreeModel = @import("SimpleTreeModel.zig");
pub const SimpleVegetation = @import("SimpleVegetation.zig");
pub const Stalagmite = @import("Stalagmite.zig");
22 changes: 18 additions & 4 deletions src/server/terrain/structure_building_blocks.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const std = @import("std");

const main = @import("main");
const Vec3i = main.vec.Vec3i;
const ZonElement = main.ZonElement;
const Blueprint = main.blueprint.Blueprint;
const List = main.List;
Expand Down Expand Up @@ -40,6 +41,10 @@ const BlueprintEntry = struct {
pub inline fn direction(self: StructureBlock) Neighbor {
return @enumFromInt(self.data);
}

pub inline fn pos(self: StructureBlock) Vec3i {
return Vec3i{self.x, self.y, self.z};
}
};

fn init(blueprint: Blueprint, stringId: []const u8) !BlueprintEntry {
Expand Down Expand Up @@ -70,6 +75,7 @@ const BlueprintEntry = struct {
.data = block.data,
};
hasOrigin = true;
self.blueprint.blocks.set(x, y, z, main.blueprint.getVoidBlock());
}
} else if(isChildBlock(block)) {
const childBlockLocalId = childBlockNumericIdMap.get(block.typ) orelse return error.ChildBlockNotRecognized;
Expand All @@ -80,6 +86,7 @@ const BlueprintEntry = struct {
.index = childBlockLocalId,
.data = block.data,
});
self.blueprint.blocks.set(x, y, z, main.blueprint.getVoidBlock());
}
}
}
Expand All @@ -103,6 +110,7 @@ pub fn isOriginBlock(block: Block) bool {
}

pub const StructureBuildingBlock = struct {
id: []const u8,
children: []AliasTable(Child),
blueprints: *[4]BlueprintEntry,

Expand All @@ -116,6 +124,7 @@ pub const StructureBuildingBlock = struct {
return error.MissingBlueprint;
};
const self = StructureBuildingBlock{
.id = stringId,
.children = arenaAllocator.alloc(AliasTable(Child), childBlockStringId.items.len),
.blueprints = blueprints,
};
Expand Down Expand Up @@ -172,6 +181,7 @@ pub fn registerSBB(structures: *std.StringHashMap(ZonElement)) !void {
std.debug.assert(structureCache.capacity() == 0);
structureCache.ensureTotalCapacity(arenaAllocator.allocator, structures.count()) catch unreachable;
childrenToResolve = .init(main.stackAllocator);
defer childrenToResolve.deinit();
{
var iterator = structures.iterator();
while(iterator.next()) |entry| {
Expand All @@ -195,7 +205,6 @@ pub fn registerSBB(structures: *std.StringHashMap(ZonElement)) !void {
std.log.debug("Resolved child structure '{s}'->'{s}'->'{d}' to '{s}'", .{entry.parentId, entry.colorName, entry.childIndex, entry.structureId});
parent.children[entry.colorIndex].items[entry.childIndex].structure = child;
}
childrenToResolve.deinit();
}
}

Expand All @@ -221,17 +230,22 @@ pub fn registerBlueprints(blueprints: *std.StringHashMap([]u8)) !void {
var iterator = blueprints.iterator();
while(iterator.next()) |entry| {
const stringId = entry.key_ptr.*;
// Rotated copies need to be made before initializing BlueprintEntry as it removes origin and child blocks.
const blueprint0 = Blueprint.load(arenaAllocator, entry.value_ptr.*) catch |err| {
std.log.err("Could not load blueprint '{s}' ({s})", .{stringId, @errorName(err)});
continue;
};
const blueprint90 = blueprint0.rotateZ(arenaAllocator, .@"90");
const blueprint180 = blueprint0.rotateZ(arenaAllocator, .@"180");
const blueprint270 = blueprint0.rotateZ(arenaAllocator, .@"270");

const rotatedBlueprints = arenaAllocator.create([4]BlueprintEntry);

rotatedBlueprints.* = .{
BlueprintEntry.init(blueprint0, stringId) catch continue,
BlueprintEntry.init(blueprint0.rotateZ(arenaAllocator, .@"90"), stringId) catch continue,
BlueprintEntry.init(blueprint0.rotateZ(arenaAllocator, .@"180"), stringId) catch continue,
BlueprintEntry.init(blueprint0.rotateZ(arenaAllocator, .@"270"), stringId) catch continue,
BlueprintEntry.init(blueprint90, stringId) catch continue,
BlueprintEntry.init(blueprint180, stringId) catch continue,
BlueprintEntry.init(blueprint270, stringId) catch continue,
};

blueprintCache.put(arenaAllocator.allocator, arenaAllocator.dupe(u8, stringId), rotatedBlueprints) catch unreachable;
Expand Down
Loading