Add /rotate command (#1225)

* Revert "Remove rotate command"

This reverts commit 3a84e03d158e5cc038ae3e936384c348431a9ac4.

* Fix stair rotation

* Add rotate fn to Blueprint

* Apply suggestions from code review

Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>

* Replace cos sin with switch

* Add angle parameter to /rotate

* Update src/blueprint.zig

* Apply review suggestions

* Fix formatting issues

* Update src/rotation.zig

---------

Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>
This commit is contained in:
Krzysztof Wiśniewski 2025-03-22 19:33:53 +01:00 committed by GitHub
parent e815d98b89
commit 5845da7ca9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 59 additions and 2 deletions

View File

@ -10,6 +10,8 @@ const Array3D = main.utils.Array3D;
const Block = main.blocks.Block; const Block = main.blocks.Block;
const NeverFailingAllocator = main.heap.NeverFailingAllocator; const NeverFailingAllocator = main.heap.NeverFailingAllocator;
const User = main.server.User; const User = main.server.User;
const ServerChunk = main.chunk.ServerChunk;
const Degrees = main.rotation.Degrees;
const GameIdToBlueprintIdMapType = std.AutoHashMap(u16, u16); const GameIdToBlueprintIdMapType = std.AutoHashMap(u16, u16);
const BlockIdSizeType = u32; const BlockIdSizeType = u32;
@ -36,6 +38,32 @@ pub const Blueprint = struct {
pub fn clone(self: *Blueprint, allocator: NeverFailingAllocator) Blueprint { pub fn clone(self: *Blueprint, allocator: NeverFailingAllocator) Blueprint {
return .{.blocks = self.blocks.clone(allocator)}; return .{.blocks = self.blocks.clone(allocator)};
} }
pub fn rotateZ(self: Blueprint, allocator: NeverFailingAllocator, angle: Degrees) Blueprint {
var new = Blueprint{
.blocks = switch(angle) {
.@"0", .@"180" => .init(allocator, self.blocks.width, self.blocks.depth, self.blocks.height),
.@"90", .@"270" => .init(allocator, self.blocks.depth, self.blocks.width, self.blocks.height),
},
};
for(0..self.blocks.width) |xOld| {
for(0..self.blocks.depth) |yOld| {
const xNew, const yNew = switch(angle) {
.@"0" => .{xOld, yOld},
.@"90" => .{new.blocks.width - yOld - 1, xOld},
.@"180" => .{new.blocks.width - xOld - 1, new.blocks.depth - yOld - 1},
.@"270" => .{yOld, new.blocks.depth - xOld - 1},
};
for(0..self.blocks.height) |z| {
const block = self.blocks.get(xOld, yOld, z);
new.blocks.set(xNew, yNew, z, block.rotateZ(angle));
}
}
}
return new;
}
const CaptureResult = union(enum) { const CaptureResult = union(enum) {
success: Blueprint, success: Blueprint,
failure: struct {pos: Vec3i, message: []const u8}, failure: struct {pos: Vec3i, message: []const u8},

View File

@ -806,7 +806,7 @@ pub const RotationModes = struct {
comptime var rotationTable: [4][256]u8 = undefined; comptime var rotationTable: [4][256]u8 = undefined;
comptime for(0..4) |a| { comptime for(0..4) |a| {
for(0..256) |old| { for(0..256) |old| {
var new: u8 = 0; var new: u8 = 0b11_11_11_11;
for(0..2) |i| for(0..2) |j| for(0..2) |k| { for(0..2) |i| for(0..2) |j| for(0..2) |k| {
const sin: f32 = @sin((std.math.pi/2.0)*@as(f32, @floatFromInt(a))); const sin: f32 = @sin((std.math.pi/2.0)*@as(f32, @floatFromInt(a)));
@ -819,7 +819,7 @@ pub const RotationModes = struct {
const rY = @intFromBool(x*sin + y*cos > 0); const rY = @intFromBool(x*sin + y*cos > 0);
if(hasSubBlock(@intCast(old), @intCast(i), @intCast(j), @intCast(k))) { if(hasSubBlock(@intCast(old), @intCast(i), @intCast(j), @intCast(k))) {
new |= subBlockMask(rX, rY, @intCast(k)); new &= ~subBlockMask(rX, rY, @intCast(k));
} }
}; };
rotationTable[a][old] = new; rotationTable[a][old] = new;

View File

@ -12,3 +12,4 @@ pub const deselect = @import("worldedit/deselect.zig");
pub const copy = @import("worldedit/copy.zig"); pub const copy = @import("worldedit/copy.zig");
pub const paste = @import("worldedit/paste.zig"); pub const paste = @import("worldedit/paste.zig");
pub const blueprint = @import("worldedit/blueprint.zig"); pub const blueprint = @import("worldedit/blueprint.zig");
pub const rotate = @import("worldedit/rotate.zig");

View File

@ -0,0 +1,28 @@
const std = @import("std");
const main = @import("root");
const User = main.server.User;
const Degrees = main.rotation.Degrees;
pub const description = "rotate clipboard content around Z axis counterclockwise.";
pub const usage =
\\/rotate
\\/rotate <0/90/180/270>
;
pub fn execute(args: []const u8, source: *User) void {
var angle: Degrees = .@"90";
if(args.len != 0) {
angle = std.meta.stringToEnum(Degrees, args) orelse {
source.sendMessage("#ff0000Error: Invalid angle '{s}'. Use 0, 90, 180 or 270.", .{args});
return;
};
}
if(source.worldEditData.clipboard == null) {
source.sendMessage("#ff0000Error: No clipboard content to rotate.", .{});
return;
}
const current = source.worldEditData.clipboard.?;
defer current.deinit(main.globalAllocator);
source.worldEditData.clipboard = current.rotateZ(main.globalAllocator, angle);
}