Add SBB rotation parameter (#1530)

## Description

This pull request adds `rotation` parameter to `cubyz:sbb` simple
structure with default value `.random` which allows specifying fixed or
randomized rotation for structures generated. Rotation for structure is
only propagate through the strcuture children only for vertical child
blocks. Additionally, a `rotation` parameter was added to structure
building block children definition to allow overriding / re-enabling
random rotation for children of strcuture. Default value for that
parameter is `inherit` which takes the rotation of parent (only for
vertical child blocks until interrupted). Valid values for rotation are
`0`, `90`, `180`, `270`, `random`, `inherit`, all accepted as a string,
numeric values also accepted as floats and integers.

## Links

Resolves: #1529

---------

Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>
This commit is contained in:
Krzysztof Wiśniewski 2025-06-28 16:38:36 +02:00 committed by GitHub
parent 254546c789
commit 54aab15a4e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 104 additions and 24 deletions

View File

@ -20,36 +20,47 @@ const SbbGen = @This();
structureRef: *const sbb.StructureBuildingBlock,
placeMode: Blueprint.PasteMode,
rotation: sbb.Rotation,
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 structureId = parameters.get(?[]const u8, "structure", null) orelse {
main.utils.panicWithMessage("Error loading generator 'cubyz:sbb' structure field is mandatory.", .{});
};
const structureRef = sbb.getByStringId(structureId) orelse {
std.log.err("Could not find structure building block with id '{s}'", .{structureId});
unreachable;
main.utils.panicWithMessage("Could not find structure building block with id '{s}'", .{structureId});
};
const rotationParam = parameters.getChild("rotation");
const rotation = sbb.Rotation.fromZon(rotationParam) catch |err| blk: {
switch(err) {
error.UnknownString => std.log.err("Error loading generator 'cubyz:sbb' structure '{s}': Specified unknown rotation '{s}'", .{structureId, rotationParam.as([]const u8, "")}),
error.UnknownType => std.log.err("Error loading generator 'cubyz:sbb' structure '{s}': Unsupported type of rotation field '{s}'", .{structureId, @tagName(rotationParam)}),
}
break :blk .random;
};
const self = arenaAllocator.create(SbbGen);
self.* = .{
.structureRef = structureRef,
.placeMode = std.meta.stringToEnum(Blueprint.PasteMode, parameters.get([]const u8, "placeMode", "degradable")) orelse Blueprint.PasteMode.degradable,
.rotation = rotation,
};
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);
placeSbb(self, self.structureRef, Vec3i{x, y, z}, Neighbor.dirUp, self.rotation.getInitialRotation(seed), chunk, seed);
}
fn placeSbb(self: *SbbGen, structure: *const sbb.StructureBuildingBlock, placementPosition: Vec3i, placementDirection: Neighbor, chunk: *ServerChunk, seed: *u64) void {
fn placeSbb(self: *SbbGen, structure: *const sbb.StructureBuildingBlock, placementPosition: Vec3i, placementDirection: Neighbor, rotation: sbb.Rotation, chunk: *ServerChunk, seed: *u64) void {
const origin = structure.blueprints[0].originBlock;
const rotationCount = alignDirections(origin.direction(), placementDirection) catch |err| {
const blueprintRotation = rotation.apply(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 rotated = &structure.blueprints[@intFromEnum(blueprintRotation)];
const rotatedOrigin = rotated.originBlock.pos();
const pastePosition = placementPosition - rotatedOrigin - placementDirection.relPos();
@ -57,19 +68,13 @@ fn placeSbb(self: *SbbGen, structure: *const sbb.StructureBuildingBlock, placeme
for(rotated.childBlocks) |childBlock| {
const child = structure.pickChild(childBlock, seed) orelse continue;
placeSbb(self, child, pastePosition + childBlock.pos(), childBlock.direction(), chunk, seed);
const childRotation = rotation.getChildRotation(seed, child.rotation, childBlock.direction());
placeSbb(self, child, pastePosition + childBlock.pos(), childBlock.direction(), childRotation, 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;
fn alignDirections(input: Neighbor, desired: Neighbor) !sbb.Rotation.FixedRotation {
comptime var alignTable: [6][6]error{NotPossibleToAlign}!sbb.Rotation.FixedRotation = undefined;
comptime for(Neighbor.iterable) |in| {
for(Neighbor.iterable) |out| blk: {
var current = in;
@ -80,11 +85,8 @@ fn alignDirections(input: Neighbor, desired: Neighbor) !usize {
}
current = current.rotateZ();
}
alignTable[in.toInt()][out.toInt()] = Rotation.NotPossibleToAlign;
alignTable[in.toInt()][out.toInt()] = error.NotPossibleToAlign;
}
};
switch(alignTable[input.toInt()][desired.toInt()]) {
.NotPossibleToAlign => return error.NotPossibleToAlign,
else => |v| return @intFromEnum(v),
}
return alignTable[input.toInt()][desired.toInt()];
}

View File

@ -114,10 +114,78 @@ pub fn isOriginBlock(block: Block) bool {
return block.typ == originBlockNumericId;
}
pub const RotationMode = enum {
fixed,
random,
inherit,
};
pub const Rotation = union(RotationMode) {
fixed: FixedRotation,
random: void,
inherit: void,
pub const FixedRotation = enum(u2) {
@"0" = 0,
@"90" = 1,
@"180" = 2,
@"270" = 3,
};
pub fn apply(self: Rotation, rotation: FixedRotation) FixedRotation {
return switch(self) {
.fixed => |fixed| @enumFromInt(@intFromEnum(rotation) +% @intFromEnum(fixed)),
.random, .inherit => rotation,
};
}
pub fn getInitialRotation(self: Rotation, seed: *u64) Rotation {
return switch(self) {
.fixed => self,
.random => sampleRandom(seed),
.inherit => .{.fixed = .@"0"},
};
}
fn sampleRandom(seed: *u64) Rotation {
return .{.fixed = @enumFromInt(main.random.nextInt(u2, seed))};
}
pub fn getChildRotation(self: Rotation, seed: *u64, child: Rotation, direction: Neighbor) Rotation {
return switch(direction) {
.dirDown, .dirUp => switch(child) {
.random => sampleRandom(seed),
.inherit => self,
else => |r| r,
},
else => .{.fixed = .@"0"},
};
}
pub fn fromZon(zon: ZonElement) error{UnknownString, UnknownType}!Rotation {
return switch(zon) {
.string, .stringOwned => |str| {
if(std.meta.stringToEnum(FixedRotation, str)) |r| {
return .{.fixed = r};
}
if(std.meta.stringToEnum(RotationMode, str)) |mode| {
return switch(mode) {
.fixed => .{.fixed = .@"0"},
.random => .{.random = {}},
.inherit => .{.inherit = {}},
};
}
return error.UnknownString;
},
.int => |value| .{.fixed = @enumFromInt(@abs(@divTrunc(value, 90))%4)},
.float => |value| .{.fixed = @enumFromInt(@abs(@as(u64, @intFromFloat(value/90.0)))%4)},
.null => Rotation.random,
else => return error.UnknownType,
};
}
};
pub const StructureBuildingBlock = struct {
id: []const u8,
children: []AliasTable(Child),
blueprints: *[4]BlueprintEntry,
rotation: Rotation,
fn initFromZon(stringId: []const u8, zon: ZonElement) !StructureBuildingBlock {
const blueprintId = zon.get(?[]const u8, "blueprint", null) orelse {
@ -128,6 +196,14 @@ pub const StructureBuildingBlock = struct {
std.log.err("['{s}'] Could not find blueprint '{s}'.", .{stringId, blueprintId});
return error.MissingBlueprint;
};
const rotationParam = zon.getChild("rotation");
const rotation = Rotation.fromZon(rotationParam) catch |err| blk: {
switch(err) {
error.UnknownString => std.log.err("['{s}'] specified unknown rotation '{s}'", .{stringId, rotationParam.as([]const u8, "")}),
error.UnknownType => std.log.err("['{s}'] unsupported type of rotation field '{s}'", .{stringId, @tagName(rotationParam)}),
}
break :blk .inherit;
};
const blueprints = arenaAllocator.create([4]BlueprintEntry);
blueprints.* = blueprintsTemplate.*;
@ -136,6 +212,7 @@ pub const StructureBuildingBlock = struct {
.id = stringId,
.children = arenaAllocator.alloc(AliasTable(Child), childBlockStringId.items.len),
.blueprints = blueprints,
.rotation = rotation,
};
const childrenZon = zon.getChild("children");
for(childBlockStringId.items, 0..) |colorName, colorIndex| {
@ -186,6 +263,7 @@ pub const StructureBuildingBlock = struct {
return .{
.id = sbbId,
.children = &.{},
.rotation = .inherit,
.blueprints = blueprints,
};
}

View File

@ -2213,7 +2213,7 @@ test "NamedCallbacks registers functions" {
try std.testing.expectEqual(null, testFunctions.getFunctionPointer("wrongSignatureFunction"));
}
pub fn panicWithMessage(comptime fmt: []const u8, args: anytype) void {
pub fn panicWithMessage(comptime fmt: []const u8, args: anytype) noreturn {
const message = std.fmt.allocPrint(main.stackAllocator.allocator, fmt, args) catch unreachable;
@panic(message);
}