Cubyz/mods/cubyz/rotation/stairs.zig
Krzysztof Wiśniewski 254546c789
Use enum instead of packed struct for *Index objects (#1640)
Resolves: #1600

---------

Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>
2025-06-28 15:45:34 +02:00

313 lines
11 KiB
Zig

const std = @import("std");
const main = @import("main");
const blocks = main.blocks;
const Block = blocks.Block;
const Neighbor = main.chunk.Neighbor;
const ModelIndex = main.models.ModelIndex;
const rotation = main.rotation;
const Degrees = rotation.Degrees;
const RayIntersectionResult = rotation.RayIntersectionResult;
const RotationMode = rotation.RotationMode;
const vec = main.vec;
const Mat4f = vec.Mat4f;
const Vec2f = vec.Vec2f;
const Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const ZonElement = main.ZonElement;
var modelIndex: ?ModelIndex = null;
fn subBlockMask(x: u1, y: u1, z: u1) u8 {
return @as(u8, 1) << ((@as(u3, x)*2 + @as(u3, y))*2 + z);
}
fn hasSubBlock(stairData: u8, x: u1, y: u1, z: u1) bool {
return stairData & subBlockMask(x, y, z) == 0;
}
pub fn rotateZ(data: u16, angle: Degrees) u16 {
@setEvalBranchQuota(65_536);
comptime var rotationTable: [4][256]u8 = undefined;
comptime for(0..4) |a| {
for(0..256) |old| {
var new: u8 = 0b11_11_11_11;
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 cos: f32 = @cos((std.math.pi/2.0)*@as(f32, @floatFromInt(a)));
const x: f32 = (@as(f32, @floatFromInt(i)) - 0.5)*2.0;
const y: f32 = (@as(f32, @floatFromInt(j)) - 0.5)*2.0;
const rX = @intFromBool(x*cos - y*sin > 0);
const rY = @intFromBool(x*sin + y*cos > 0);
if(hasSubBlock(@intCast(old), @intCast(i), @intCast(j), @intCast(k))) {
new &= ~subBlockMask(rX, rY, @intCast(k));
}
};
rotationTable[a][old] = new;
}
};
if(data >= 256) return 0;
return rotationTable[@intFromEnum(angle)][data];
}
pub fn init() void {}
pub fn deinit() void {}
pub fn reset() void {
modelIndex = null;
}
const GreedyFaceInfo = struct {min: Vec2f, max: Vec2f};
fn mergeFaces(faceVisible: [2][2]bool, mem: []GreedyFaceInfo) []GreedyFaceInfo {
var faces: usize = 0;
if(faceVisible[0][0]) {
if(faceVisible[0][1]) {
if(faceVisible[1][0] and faceVisible[1][1]) {
// One big face:
mem[faces] = .{.min = .{0, 0}, .max = .{1, 1}};
faces += 1;
} else {
mem[faces] = .{.min = .{0, 0}, .max = .{0.5, 1}};
faces += 1;
if(faceVisible[1][0]) {
mem[faces] = .{.min = .{0.5, 0}, .max = .{1, 0.5}};
faces += 1;
}
if(faceVisible[1][1]) {
mem[faces] = .{.min = .{0.5, 0.5}, .max = .{1, 1}};
faces += 1;
}
}
} else {
if(faceVisible[1][0]) {
mem[faces] = .{.min = .{0, 0}, .max = .{1.0, 0.5}};
faces += 1;
} else {
mem[faces] = .{.min = .{0, 0}, .max = .{0.5, 0.5}};
faces += 1;
}
if(faceVisible[1][1]) {
mem[faces] = .{.min = .{0.5, 0.5}, .max = .{1, 1}};
faces += 1;
}
}
} else {
if(faceVisible[0][1]) {
if(faceVisible[1][1]) {
mem[faces] = .{.min = .{0, 0.5}, .max = .{1, 1}};
faces += 1;
} else {
mem[faces] = .{.min = .{0, 0.5}, .max = .{0.5, 1}};
faces += 1;
}
if(faceVisible[1][0]) {
mem[faces] = .{.min = .{0.5, 0}, .max = .{1, 0.5}};
faces += 1;
}
} else {
if(faceVisible[1][0]) {
if(faceVisible[1][1]) {
mem[faces] = .{.min = .{0.5, 0}, .max = .{1, 1.0}};
faces += 1;
} else {
mem[faces] = .{.min = .{0.5, 0}, .max = .{1, 0.5}};
faces += 1;
}
} else if(faceVisible[1][1]) {
mem[faces] = .{.min = .{0.5, 0.5}, .max = .{1, 1}};
faces += 1;
}
}
}
return mem[0..faces];
}
pub fn createBlockModel(_: Block, _: *u16, _: ZonElement) ModelIndex {
if(modelIndex) |idx| return idx;
for(0..256) |i| {
var quads = main.List(main.models.QuadInfo).init(main.stackAllocator);
defer quads.deinit();
for(Neighbor.iterable) |neighbor| {
const xComponent = @abs(neighbor.textureX());
const yComponent = @abs(neighbor.textureY());
const normal = Vec3i{neighbor.relX(), neighbor.relY(), neighbor.relZ()};
const zComponent = @abs(normal);
const zMap: [2]@Vector(3, u32) = if(@reduce(.Add, normal) > 0) .{@splat(0), @splat(1)} else .{@splat(1), @splat(0)};
var visibleFront: [2][2]bool = undefined;
var visibleMiddle: [2][2]bool = undefined;
for(0..2) |x| {
for(0..2) |y| {
const xSplat: @TypeOf(xComponent) = @splat(@intCast(x));
const ySplat: @TypeOf(xComponent) = @splat(@intCast(y));
const posFront = xComponent*xSplat + yComponent*ySplat + zComponent*zMap[1];
const posBack = xComponent*xSplat + yComponent*ySplat + zComponent*zMap[0];
visibleFront[x][y] = hasSubBlock(@intCast(i), @intCast(posFront[0]), @intCast(posFront[1]), @intCast(posFront[2]));
visibleMiddle[x][y] = !visibleFront[x][y] and hasSubBlock(@intCast(i), @intCast(posBack[0]), @intCast(posBack[1]), @intCast(posBack[2]));
}
}
const xAxis = @as(Vec3f, @floatFromInt(neighbor.textureX()));
const yAxis = @as(Vec3f, @floatFromInt(neighbor.textureY()));
const zAxis = @as(Vec3f, @floatFromInt(normal));
// Greedy mesh it:
var faces: [2]GreedyFaceInfo = undefined;
const frontFaces = mergeFaces(visibleFront, &faces);
for(frontFaces) |*face| {
var xLower = @abs(xAxis)*@as(Vec3f, @splat(face.min[0]));
var xUpper = @abs(xAxis)*@as(Vec3f, @splat(face.max[0]));
if(@reduce(.Add, xAxis) < 0) std.mem.swap(Vec3f, &xLower, &xUpper);
var yLower = @abs(yAxis)*@as(Vec3f, @splat(face.min[1]));
var yUpper = @abs(yAxis)*@as(Vec3f, @splat(face.max[1]));
if(@reduce(.Add, yAxis) < 0) std.mem.swap(Vec3f, &yLower, &yUpper);
const zValue: Vec3f = @floatFromInt(zComponent*zMap[1]);
if(neighbor == .dirNegX or neighbor == .dirPosY) {
face.min[0] = 1 - face.min[0];
face.max[0] = 1 - face.max[0];
const swap = face.min[0];
face.min[0] = face.max[0];
face.max[0] = swap;
}
if(neighbor == .dirUp) {
face.min = Vec2f{1, 1} - face.min;
face.max = Vec2f{1, 1} - face.max;
std.mem.swap(Vec2f, &face.min, &face.max);
}
if(neighbor == .dirDown) {
face.min[1] = 1 - face.min[1];
face.max[1] = 1 - face.max[1];
const swap = face.min[1];
face.min[1] = face.max[1];
face.max[1] = swap;
}
quads.append(.{
.normal = zAxis,
.corners = .{
xLower + yLower + zValue,
xLower + yUpper + zValue,
xUpper + yLower + zValue,
xUpper + yUpper + zValue,
},
.cornerUV = .{.{face.min[0], face.min[1]}, .{face.min[0], face.max[1]}, .{face.max[0], face.min[1]}, .{face.max[0], face.max[1]}},
.textureSlot = neighbor.toInt(),
});
}
const middleFaces = mergeFaces(visibleMiddle, &faces);
for(middleFaces) |*face| {
var xLower = @abs(xAxis)*@as(Vec3f, @splat(face.min[0]));
var xUpper = @abs(xAxis)*@as(Vec3f, @splat(face.max[0]));
if(@reduce(.Add, xAxis) < 0) std.mem.swap(Vec3f, &xLower, &xUpper);
var yLower = @abs(yAxis)*@as(Vec3f, @splat(face.min[1]));
var yUpper = @abs(yAxis)*@as(Vec3f, @splat(face.max[1]));
if(@reduce(.Add, yAxis) < 0) std.mem.swap(Vec3f, &yLower, &yUpper);
const zValue = @as(Vec3f, @floatFromInt(zComponent))*@as(Vec3f, @splat(0.5));
if(neighbor == .dirNegX or neighbor == .dirPosY) {
face.min[0] = 1 - face.min[0];
face.max[0] = 1 - face.max[0];
const swap = face.min[0];
face.min[0] = face.max[0];
face.max[0] = swap;
}
if(neighbor == .dirUp) {
face.min = Vec2f{1, 1} - face.min;
face.max = Vec2f{1, 1} - face.max;
std.mem.swap(Vec2f, &face.min, &face.max);
}
if(neighbor == .dirDown) {
face.min[1] = 1 - face.min[1];
face.max[1] = 1 - face.max[1];
const swap = face.min[1];
face.min[1] = face.max[1];
face.max[1] = swap;
}
quads.append(.{
.normal = zAxis,
.corners = .{
xLower + yLower + zValue,
xLower + yUpper + zValue,
xUpper + yLower + zValue,
xUpper + yUpper + zValue,
},
.cornerUV = .{.{face.min[0], face.min[1]}, .{face.min[0], face.max[1]}, .{face.max[0], face.min[1]}, .{face.max[0], face.max[1]}},
.textureSlot = neighbor.toInt(),
});
}
}
const index = main.models.Model.init(quads.items);
if(i == 0) {
modelIndex = index;
}
}
return modelIndex.?;
}
pub fn model(block: Block) ModelIndex {
return blocks.meshes.modelIndexStart(block).add(block.data & 255);
}
pub fn generateData(_: *main.game.World, _: Vec3i, _: Vec3f, _: Vec3f, _: Vec3i, _: ?Neighbor, currentData: *Block, _: Block, blockPlacing: bool) bool {
if(blockPlacing) {
currentData.data = 0;
return true;
}
return false;
}
fn closestRay(comptime typ: enum {bit, intersection}, block: Block, relativePlayerPos: Vec3f, playerDir: Vec3f) if(typ == .intersection) ?RayIntersectionResult else u16 {
var result: ?RayIntersectionResult = null;
var resultBit: u16 = 0;
for([_]u16{1, 2, 4, 8, 16, 32, 64, 128}) |bit| {
if(block.data & bit == 0) {
const cornerModelIndex: ModelIndex = blocks.meshes.modelIndexStart(block).add(255 ^ bit);
if(RotationMode.DefaultFunctions.rayModelIntersection(cornerModelIndex, relativePlayerPos, playerDir)) |intersection| {
if(result == null or intersection.distance < result.?.distance) {
result = intersection;
resultBit = bit;
}
}
}
}
if(typ == .bit) return resultBit;
return result;
}
pub fn rayIntersection(block: Block, item: ?main.items.Item, relativePlayerPos: Vec3f, playerDir: Vec3f) ?RayIntersectionResult {
if(item) |_item| {
switch(_item) {
.baseItem => |baseItem| {
if(std.mem.eql(u8, baseItem.id(), "cubyz:chisel")) { // Select only one eighth of a block
return closestRay(.intersection, block, relativePlayerPos, playerDir);
}
},
else => {},
}
}
return RotationMode.DefaultFunctions.rayIntersection(block, item, relativePlayerPos, playerDir);
}
pub fn onBlockBreaking(item: ?main.items.Item, relativePlayerPos: Vec3f, playerDir: Vec3f, currentData: *Block) void {
if(item) |_item| {
switch(_item) {
.baseItem => |baseItem| {
if(std.mem.eql(u8, baseItem.id(), "cubyz:chisel")) { // Break only one eigth of a block
currentData.data |= closestRay(.bit, currentData.*, relativePlayerPos, playerDir);
if(currentData.data == 255) currentData.* = .{.typ = 0, .data = 0};
return;
}
},
else => {},
}
}
return RotationMode.DefaultFunctions.onBlockBreaking(item, relativePlayerPos, playerDir, currentData);
}
pub fn canBeChangedInto(oldBlock: Block, newBlock: Block, item: main.items.ItemStack, shouldDropSourceBlockOnSuccess: *bool) RotationMode.CanBeChangedInto {
if(oldBlock.typ != newBlock.typ) return RotationMode.DefaultFunctions.canBeChangedInto(oldBlock, newBlock, item, shouldDropSourceBlockOnSuccess);
if(oldBlock.data == newBlock.data) return .no;
if(item.item != null and item.item.? == .baseItem and std.mem.eql(u8, item.item.?.baseItem.id(), "cubyz:chisel")) {
return .yes; // TODO: Durability change, after making the chisel a proper tool.
}
return .no;
}