Move rotation modes into separate files.

This commit is contained in:
IntegratedQuantum 2025-03-24 20:32:30 +01:00
parent f5a2e3d3c1
commit 1c2899eafd
12 changed files with 1645 additions and 1520 deletions

File diff suppressed because it is too large Load Diff

10
src/rotation/_list.zig Normal file
View File

@ -0,0 +1,10 @@
pub const branch = @import("branch.zig");
pub const carpet = @import("carpet.zig");
pub const fence = @import("fence.zig");
pub const log = @import("log.zig");
pub const no_rotation = @import("no_rotation.zig");
pub const ore = @import("ore.zig");
pub const planar = @import("planar.zig");
pub const stairs = @import("stairs.zig");
pub const texture_pile = @import("texture_pile.zig");
pub const torch = @import("torch.zig");

430
src/rotation/branch.zig Normal file
View File

@ -0,0 +1,430 @@
const std = @import("std");
const main = @import("root");
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 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;
pub const dependsOnNeighbors = true;
var branchModels: std.HashMap(HashMapKey, ModelIndex, HashMapKey, std.hash_map.default_max_load_percentage) = undefined;
const HashMapKey = struct {
radius: u16,
shellModelId: []const u8,
textureSlotOffset: u32,
pub fn hash(_: HashMapKey, val: HashMapKey) u64 {
var hasher = std.hash.Wyhash.init(0);
std.hash.autoHashStrat(&hasher, val, .DeepRecursive);
return hasher.final();
}
pub fn eql(_: HashMapKey, val1: HashMapKey, val2: HashMapKey) bool {
if(val1.radius != val2.radius) return false;
if(val1.textureSlotOffset != val2.textureSlotOffset) return false;
return std.mem.eql(u8, val1.shellModelId, val2.shellModelId);
}
};
const BranchData = packed struct(u6) {
enabledConnections: u6,
pub inline fn init(blockData: u16) BranchData {
return .{.enabledConnections = @truncate(blockData)};
}
pub inline fn isConnected(self: @This(), neighbor: Neighbor) bool {
return (self.enabledConnections & Neighbor.bitMask(neighbor)) != 0;
}
pub inline fn setConnection(self: *@This(), neighbor: Neighbor, value: bool) void {
if(value) {
self.enabledConnections |= Neighbor.bitMask(neighbor);
} else {
self.enabledConnections &= ~Neighbor.bitMask(neighbor);
}
}
};
pub fn init() void {
branchModels = .initContext(main.globalAllocator.allocator, undefined);
}
pub fn deinit() void {
branchModels.deinit();
}
pub fn reset() void {
branchModels.clearRetainingCapacity();
}
const Direction = enum(u2) {
negYDir = 0,
posXDir = 1,
posYDir = 2,
negXDir = 3,
};
const Pattern = union(enum) {
dot: void,
halfLine: struct {
dir: Direction,
},
line: struct {
dir: Direction,
},
bend: struct {
dir: Direction,
},
intersection: struct {
dir: Direction,
},
cross: void,
};
fn rotateQuad(originalCorners: [4]Vec2f, pattern: Pattern, min: f32, max: f32, side: Neighbor, textureSlotOffset: u32) main.models.QuadInfo {
var corners: [4]Vec2f = originalCorners;
switch(pattern) {
.dot, .cross => {},
inline else => |typ| {
const angle: f32 = @as(f32, @floatFromInt(@intFromEnum(typ.dir)))*std.math.pi/2.0;
corners = .{
vec.rotate2d(originalCorners[0], angle, @splat(0.5)),
vec.rotate2d(originalCorners[1], angle, @splat(0.5)),
vec.rotate2d(originalCorners[2], angle, @splat(0.5)),
vec.rotate2d(originalCorners[3], angle, @splat(0.5)),
};
},
}
const offX: f32 = @floatFromInt(@intFromBool(@reduce(.Add, side.textureX()) < 0));
const offY: f32 = @floatFromInt(@intFromBool(@reduce(.Add, side.textureY()) < 0));
const corners3d = .{
@as(Vec3f, @floatFromInt(side.textureX()))*@as(Vec3f, @splat(corners[0][0] - offX)) + @as(Vec3f, @floatFromInt(side.textureY()))*@as(Vec3f, @splat(corners[0][1] - offY)),
@as(Vec3f, @floatFromInt(side.textureX()))*@as(Vec3f, @splat(corners[1][0] - offX)) + @as(Vec3f, @floatFromInt(side.textureY()))*@as(Vec3f, @splat(corners[1][1] - offY)),
@as(Vec3f, @floatFromInt(side.textureX()))*@as(Vec3f, @splat(corners[2][0] - offX)) + @as(Vec3f, @floatFromInt(side.textureY()))*@as(Vec3f, @splat(corners[2][1] - offY)),
@as(Vec3f, @floatFromInt(side.textureX()))*@as(Vec3f, @splat(corners[3][0] - offX)) + @as(Vec3f, @floatFromInt(side.textureY()))*@as(Vec3f, @splat(corners[3][1] - offY)),
};
var offset: Vec3f = .{0.0, 0.0, 0.0};
offset[@intFromEnum(side.vectorComponent())] = if(side.isPositive()) max else min;
const res: main.models.QuadInfo = .{
.corners = .{
corners3d[0] + offset,
corners3d[1] + offset,
corners3d[2] + offset,
corners3d[3] + offset,
},
.cornerUV = originalCorners,
.normal = @floatFromInt(side.relPos()),
.textureSlot = textureSlotOffset + @intFromEnum(pattern),
};
return res;
}
fn addQuads(pattern: Pattern, side: Neighbor, radius: f32, out: *main.List(main.models.QuadInfo), textureSlotOffset: u32) void {
const min: f32 = (8.0 - radius)/16.0;
const max: f32 = (8.0 + radius)/16.0;
switch(pattern) {
.dot => {
out.append(rotateQuad(.{
.{min, min},
.{min, max},
.{max, min},
.{max, max},
}, pattern, min, max, side, textureSlotOffset));
},
.halfLine => {
out.append(rotateQuad(.{
.{min, 0.0},
.{min, max},
.{max, 0.0},
.{max, max},
}, pattern, min, max, side, textureSlotOffset));
},
.line => {
out.append(rotateQuad(.{
.{min, 0.0},
.{min, 1.0},
.{max, 0.0},
.{max, 1.0},
}, pattern, min, max, side, textureSlotOffset));
},
.bend => {
out.append(rotateQuad(.{
.{0.0, 0.0},
.{0.0, max},
.{max, 0.0},
.{max, max},
}, pattern, min, max, side, textureSlotOffset));
},
.intersection => {
out.append(rotateQuad(.{
.{0.0, 0.0},
.{0.0, max},
.{1.0, 0.0},
.{1.0, max},
}, pattern, min, max, side, textureSlotOffset));
},
.cross => {
out.append(rotateQuad(.{
.{0.0, 0.0},
.{0.0, 1.0},
.{1.0, 0.0},
.{1.0, 1.0},
}, pattern, min, max, side, textureSlotOffset));
},
}
}
fn getPattern(data: BranchData, side: Neighbor) ?Pattern {
const posX = Neighbor.fromRelPos(side.textureX()).?;
const negX = Neighbor.fromRelPos(side.textureX()).?.reverse();
const posY = Neighbor.fromRelPos(side.textureY()).?;
const negY = Neighbor.fromRelPos(side.textureY()).?.reverse();
const connectedPosX = data.isConnected(posX);
const connectedNegX = data.isConnected(negX);
const connectedPosY = data.isConnected(posY);
const connectedNegY = data.isConnected(negY);
const count: u6 = @as(u6, @intFromBool(connectedPosX)) + @as(u6, @intFromBool(connectedNegX)) + @as(u6, @intFromBool(connectedPosY)) + @as(u6, @intFromBool(connectedNegY));
return switch(count) {
0 => {
if(data.isConnected(side)) {
return null;
}
return .dot;
},
1 => {
var dir: Direction = .negXDir;
if(connectedNegY) {
dir = .negYDir;
} else if(connectedPosX) {
dir = .posXDir;
} else if(connectedPosY) {
dir = .posYDir;
}
return .{.halfLine = .{.dir = dir}};
},
2 => {
if((connectedPosX and connectedNegX) or (connectedPosY and connectedNegY)) {
var dir: Direction = .negYDir;
if(connectedPosX and connectedNegX) {
dir = .posXDir;
}
return .{.line = .{.dir = dir}};
}
var dir: Direction = .negXDir;
if(connectedNegY) {
dir = .negYDir;
if(connectedPosX) {
dir = .posXDir;
}
} else if(connectedPosX) {
dir = .posXDir;
if(connectedPosY) {
dir = .posYDir;
}
} else if(connectedPosY) {
dir = .posYDir;
if(connectedNegX) {
dir = .negXDir;
}
}
return .{.bend = .{.dir = dir}};
},
3 => {
var dir: Direction = undefined;
if(!connectedPosY) dir = .negYDir;
if(!connectedNegX) dir = .posXDir;
if(!connectedNegY) dir = .posYDir;
if(!connectedPosX) dir = .negXDir;
return .{.intersection = .{.dir = dir}};
},
4 => {
return .cross;
},
else => undefined,
};
}
pub fn createBlockModel(_: Block, modeData: *u16, zon: ZonElement) ModelIndex {
var radius = zon.get(f32, "radius", 4);
const radiusForComparisons = std.math.lossyCast(u16, @round(radius*65536.0/16.0));
radius = @as(f32, @floatFromInt(radiusForComparisons))*16.0/65536.0;
modeData.* = radiusForComparisons;
const shellModelId = zon.get([]const u8, "shellModel", "");
const textureSlotOffset = zon.get(u32, "textureSlotOffset", 0);
if(branchModels.get(.{.radius = radiusForComparisons, .shellModelId = shellModelId, .textureSlotOffset = textureSlotOffset})) |modelIndex| return modelIndex;
var shellQuads = main.List(main.models.QuadInfo).init(main.stackAllocator);
defer shellQuads.deinit();
if(shellModelId.len != 0) {
const shellModel = main.models.getModelIndex(shellModelId).model();
shellModel.getRawFaces(&shellQuads);
}
var modelIndex: ModelIndex = undefined;
for(0..64) |i| {
var quads = main.List(main.models.QuadInfo).init(main.stackAllocator);
defer quads.deinit();
quads.appendSlice(shellQuads.items);
for(Neighbor.iterable) |neighbor| {
const pattern = getPattern(BranchData.init(@intCast(i)), neighbor);
if(pattern) |pat| {
addQuads(pat, neighbor, radius, &quads, textureSlotOffset);
}
}
const index = main.models.Model.init(quads.items);
if(i == 0) {
modelIndex = index;
}
}
branchModels.put(.{.radius = radiusForComparisons, .shellModelId = shellModelId, .textureSlotOffset = textureSlotOffset}, modelIndex) catch unreachable;
return modelIndex;
}
pub fn model(block: Block) ModelIndex {
return .{.index = blocks.meshes.modelIndexStart(block).index + (block.data & 63)};
}
fn rotateZ(data: u16, angle: Degrees) u16 {
@setEvalBranchQuota(65_536);
comptime var rotationTable: [4][16]u8 = undefined;
comptime for(0..16) |i| {
rotationTable[0][i] = @intCast(i << 2);
};
comptime for(1..4) |a| {
for(0..16) |i| {
const old: BranchData = .init(rotationTable[a - 1][i]);
var new: BranchData = .init(0);
new.setConnection(Neighbor.dirPosX.rotateZ(), old.isConnected(Neighbor.dirPosX));
new.setConnection(Neighbor.dirNegX.rotateZ(), old.isConnected(Neighbor.dirNegX));
new.setConnection(Neighbor.dirPosY.rotateZ(), old.isConnected(Neighbor.dirPosY));
new.setConnection(Neighbor.dirNegY.rotateZ(), old.isConnected(Neighbor.dirNegY));
rotationTable[a][i] = new.enabledConnections;
}
};
if(data >= 0b111111) return 0;
const rotationIndex = (data & 0b111100) >> 2;
const upDownFlags = data & 0b000011;
return rotationTable[@intFromEnum(angle)][rotationIndex] | upDownFlags;
}
pub fn generateData(
_: *main.game.World,
_: Vec3i,
_: Vec3f,
_: Vec3f,
_: Vec3i,
neighbor: ?Neighbor,
currentBlock: *Block,
neighborBlock: Block,
blockPlacing: bool,
) bool {
const canConnectToNeighbor = currentBlock.mode() == neighborBlock.mode() and currentBlock.modeData() == neighborBlock.modeData();
if(blockPlacing or canConnectToNeighbor or neighborBlock.solid()) {
const neighborModel = blocks.meshes.model(neighborBlock).model();
var currentData = BranchData.init(currentBlock.data);
// Branch block upon placement should extend towards a block it was placed
// on if the block is solid or also uses branch model.
const targetVal = ((neighborBlock.solid() and (!neighborBlock.viewThrough() or canConnectToNeighbor)) and (canConnectToNeighbor or neighborModel.isNeighborOccluded[neighbor.?.reverse().toInt()]));
currentData.setConnection(neighbor.?, targetVal);
const result: u16 = currentData.enabledConnections;
if(result == currentBlock.data) return false;
currentBlock.data = result;
return true;
}
return false;
}
pub fn updateData(block: *Block, neighbor: Neighbor, neighborBlock: Block) bool {
const canConnectToNeighbor = block.mode() == neighborBlock.mode() and block.modeData() == neighborBlock.modeData();
var currentData = BranchData.init(block.data);
// Handle joining with other branches. While placed, branches extend in a
// opposite direction than they were placed from, effectively connecting
// to the block they were placed at.
if(canConnectToNeighbor) {
const neighborData = BranchData.init(neighborBlock.data);
currentData.setConnection(neighbor, neighborData.isConnected(neighbor.reverse()));
} else if(!neighborBlock.solid()) {
currentData.setConnection(neighbor, false);
}
const result: u16 = currentData.enabledConnections;
if(result == block.data) return false;
block.data = result;
return true;
}
fn closestRay(block: Block, relativePlayerPos: Vec3f, playerDir: Vec3f) ?u16 {
var closestIntersectionDistance: f64 = std.math.inf(f64);
var resultBitMask: ?u16 = null;
{
const modelIndex = blocks.meshes.modelIndexStart(block);
if(RotationMode.DefaultFunctions.rayModelIntersection(modelIndex, relativePlayerPos, playerDir)) |intersection| {
closestIntersectionDistance = intersection.distance;
resultBitMask = 0;
}
}
for(Neighbor.iterable) |direction| {
const directionBitMask = Neighbor.bitMask(direction);
if((block.data & directionBitMask) != 0) {
const modelIndex = ModelIndex{.index = blocks.meshes.modelIndexStart(block).index + directionBitMask};
if(RotationMode.DefaultFunctions.rayModelIntersection(modelIndex, relativePlayerPos, playerDir)) |intersection| {
if(@abs(closestIntersectionDistance) > @abs(intersection.distance)) {
closestIntersectionDistance = intersection.distance;
resultBitMask = direction.bitMask();
}
}
}
}
return resultBitMask;
}
pub fn onBlockBreaking(_: ?main.items.Item, relativePlayerPos: Vec3f, playerDir: Vec3f, currentData: *Block) void {
if(closestRay(currentData.*, relativePlayerPos, playerDir)) |directionBitMask| {
// If player destroys a central part of branch block, branch block is completely destroyed.
if(directionBitMask == 0) {
currentData.typ = 0;
currentData.data = 0;
return;
}
// Otherwise only the connection player aimed at is destroyed.
currentData.data &= ~directionBitMask;
}
}

182
src/rotation/carpet.zig Normal file
View File

@ -0,0 +1,182 @@
const std = @import("std");
const main = @import("root");
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 Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const ZonElement = main.ZonElement;
const torch = @import("torch.zig");
pub const naturalStandard: u16 = 0b10000;
var rotatedModels: std.StringHashMap(ModelIndex) = undefined;
const CarpetData = packed struct(u6) {
negX: bool,
posX: bool,
negY: bool,
posY: bool,
negZ: bool,
posZ: bool,
};
fn rotateZ(data: u16, angle: Degrees) u16 {
comptime var rotationTable: [4][64]u8 = undefined;
comptime for(0..64) |i| {
rotationTable[0][i] = @intCast(i);
};
comptime for(1..4) |a| {
for(0..64) |i| {
const old: CarpetData = @bitCast(@as(u6, @intCast(rotationTable[a - 1][i])));
const new: CarpetData = .{
.posZ = old.posZ,
.negZ = old.negZ,
.posY = old.posX,
.negY = old.negX,
.negX = old.posY,
.posX = old.negY,
};
rotationTable[a][i] = @as(u6, @bitCast(new));
}
};
if(data >= 64) return 0;
return rotationTable[@intFromEnum(angle)][data];
}
pub fn init() void {
rotatedModels = .init(main.globalAllocator.allocator);
}
pub fn deinit() void {
rotatedModels.deinit();
}
pub fn reset() void {
rotatedModels.clearRetainingCapacity();
}
pub fn createBlockModel(_: Block, _: *u16, zon: ZonElement) ModelIndex {
const modelId = zon.as([]const u8, "cubyz:cube");
if(rotatedModels.get(modelId)) |modelIndex| return modelIndex;
const baseModel = main.models.getModelIndex(modelId).model();
// Rotate the model:
var negXModel: ModelIndex = undefined;
var posXModel: ModelIndex = undefined;
var negYModel: ModelIndex = undefined;
var posYModel: ModelIndex = undefined;
var negZModel: ModelIndex = undefined;
var posZModel: ModelIndex = undefined;
for(1..64) |i| {
const carpetData: CarpetData = @bitCast(@as(u6, @intCast(i)));
if(i & i - 1 == 0) {
if(carpetData.negX) negXModel = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(-std.math.pi/2.0).mul(Mat4f.rotationX(-std.math.pi/2.0))});
if(carpetData.posX) posXModel = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(std.math.pi/2.0).mul(Mat4f.rotationX(-std.math.pi/2.0))});
if(carpetData.negY) negYModel = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationX(-std.math.pi/2.0)});
if(carpetData.posY) posYModel = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(std.math.pi).mul(Mat4f.rotationX(-std.math.pi/2.0))});
if(carpetData.negZ) negZModel = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.identity()});
if(carpetData.posZ) posZModel = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationY(std.math.pi)});
} else {
var models: [6]ModelIndex = undefined;
var amount: usize = 0;
if(carpetData.negX) {
models[amount] = negXModel;
amount += 1;
}
if(carpetData.posX) {
models[amount] = posXModel;
amount += 1;
}
if(carpetData.negY) {
models[amount] = negYModel;
amount += 1;
}
if(carpetData.posY) {
models[amount] = posYModel;
amount += 1;
}
if(carpetData.negZ) {
models[amount] = negZModel;
amount += 1;
}
if(carpetData.posZ) {
models[amount] = posZModel;
amount += 1;
}
_ = main.models.Model.mergeModels(models[0..amount]);
}
}
const modelIndex = negXModel;
rotatedModels.put(modelId, modelIndex) catch unreachable;
return modelIndex;
}
pub fn model(block: Block) ModelIndex {
return .{.index = blocks.meshes.modelIndexStart(block).index + (@as(u6, @truncate(block.data)) -| 1)};
}
pub fn generateData(_: *main.game.World, _: Vec3i, relativePlayerPos: Vec3f, playerDir: Vec3f, relativeDir: Vec3i, _: ?Neighbor, currentData: *Block, neighbor: Block, _: bool) bool {
if(neighbor.mode() == currentData.mode()) parallelPlacing: {
const bit = closestRay(.bit, neighbor, null, relativePlayerPos - @as(Vec3f, @floatFromInt(relativeDir)), playerDir);
const bitData: CarpetData = @bitCast(@as(u6, @truncate(bit)));
if((bitData.negX or bitData.posX) and relativeDir[0] != 0) break :parallelPlacing;
if((bitData.negY or bitData.posY) and relativeDir[1] != 0) break :parallelPlacing;
if((bitData.negZ or bitData.posZ) and relativeDir[2] != 0) break :parallelPlacing;
if(currentData.data & bit == bit) return false;
currentData.data |= bit;
return true;
}
var data: CarpetData = @bitCast(@as(u6, @truncate(currentData.data)));
if(relativeDir[0] == 1) data.posX = true;
if(relativeDir[0] == -1) data.negX = true;
if(relativeDir[1] == 1) data.posY = true;
if(relativeDir[1] == -1) data.negY = true;
if(relativeDir[2] == 1) data.posZ = true;
if(relativeDir[2] == -1) data.negZ = true;
if(@as(u6, @bitCast(data)) != currentData.data) {
currentData.data = @as(u6, @bitCast(data));
return true;
} else {
return false;
}
}
fn closestRay(comptime typ: enum {bit, intersection}, block: Block, _: ?main.items.Item, 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}) |bit| {
if(block.data & bit != 0) {
const modelIndex = ModelIndex{.index = blocks.meshes.modelIndexStart(block).index + bit - 1};
if(RotationMode.DefaultFunctions.rayModelIntersection(modelIndex, relativePlayerPos, playerDir)) |intersection| {
if(result == null or result.?.distance > intersection.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 {
return closestRay(.intersection, block, item, relativePlayerPos, playerDir);
}
pub fn onBlockBreaking(item: ?main.items.Item, relativePlayerPos: Vec3f, playerDir: Vec3f, currentData: *Block) void {
const bit = closestRay(.bit, currentData.*, item, relativePlayerPos, playerDir);
currentData.data &= ~bit;
if(currentData.data == 0) currentData.typ = 0;
}
pub fn canBeChangedInto(oldBlock: Block, newBlock: Block, item: main.items.ItemStack, shouldDropSourceBlockOnSuccess: *bool) RotationMode.CanBeChangedInto {
return torch.canBeChangedInto(oldBlock, newBlock, item, shouldDropSourceBlockOnSuccess);
}

130
src/rotation/fence.zig Normal file
View File

@ -0,0 +1,130 @@
const std = @import("std");
const main = @import("root");
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 vec = main.vec;
const Mat4f = vec.Mat4f;
const Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const ZonElement = main.ZonElement;
pub const dependsOnNeighbors = true;
var fenceModels: std.StringHashMap(ModelIndex) = undefined;
const FenceData = packed struct(u4) {
isConnectedNegX: bool,
isConnectedPosX: bool,
isConnectedNegY: bool,
isConnectedPosY: bool,
};
pub fn init() void {
fenceModels = .init(main.globalAllocator.allocator);
}
pub fn deinit() void {
fenceModels.deinit();
}
pub fn reset() void {
fenceModels.clearRetainingCapacity();
}
fn rotateZ(data: u16, angle: Degrees) u16 {
comptime var rotationTable: [4][16]u8 = undefined;
comptime for(0..16) |i| {
rotationTable[0][i] = @intCast(i);
};
comptime for(1..4) |a| {
for(0..16) |i| {
const old: FenceData = @bitCast(@as(u4, @intCast(rotationTable[a - 1][i])));
const new: FenceData = .{
.isConnectedNegY = old.isConnectedNegX,
.isConnectedPosY = old.isConnectedPosX,
.isConnectedPosX = old.isConnectedNegY,
.isConnectedNegX = old.isConnectedPosY,
};
rotationTable[a][i] = @as(u4, @bitCast(new));
}
};
if(data >= 16) return 0;
return rotationTable[@intFromEnum(angle)][data];
}
fn fenceTransform(quad: *main.models.QuadInfo, data: FenceData) void {
for(&quad.corners, &quad.cornerUV) |*corner, *cornerUV| {
if(!data.isConnectedNegX and corner[0] == 0) {
corner[0] = 0.5;
cornerUV[0] = 0.5;
}
if(!data.isConnectedPosX and corner[0] == 1) {
corner[0] = 0.5;
cornerUV[0] = 0.5;
}
if(!data.isConnectedNegY and corner[1] == 0) {
corner[1] = 0.5;
if(@abs(quad.normal[2]) > 0.7) {
cornerUV[1] = 0.5;
} else {
cornerUV[0] = 0.5;
}
}
if(!data.isConnectedPosY and corner[1] == 1) {
corner[1] = 0.5;
if(@abs(quad.normal[2]) > 0.7) {
cornerUV[1] = 0.5;
} else {
cornerUV[0] = 0.5;
}
}
}
}
pub fn createBlockModel(_: Block, _: *u16, zon: ZonElement) ModelIndex {
const modelId = zon.as([]const u8, "cubyz:cube");
if(fenceModels.get(modelId)) |modelIndex| return modelIndex;
const baseModel = main.models.getModelIndex(modelId).model();
// Rotate the model:
const modelIndex: ModelIndex = baseModel.transformModel(fenceTransform, .{@as(FenceData, @bitCast(@as(u4, 0)))});
for(1..16) |fenceData| {
_ = baseModel.transformModel(fenceTransform, .{@as(FenceData, @bitCast(@as(u4, @intCast(fenceData))))});
}
fenceModels.put(modelId, modelIndex) catch unreachable;
return modelIndex;
}
pub fn model(block: Block) ModelIndex {
return .{.index = blocks.meshes.modelIndexStart(block).index + (block.data & 15)};
}
pub fn updateData(block: *Block, neighbor: Neighbor, neighborBlock: Block) bool {
const blockBaseModelIndex = blocks.meshes.modelIndexStart(block.*);
const neighborBaseModelIndex = blocks.meshes.modelIndexStart(neighborBlock);
const neighborModel = blocks.meshes.model(neighborBlock).model();
const targetVal = neighborBlock.solid() and (blockBaseModelIndex == neighborBaseModelIndex or neighborModel.isNeighborOccluded[neighbor.reverse().toInt()]);
var currentData: FenceData = @bitCast(@as(u4, @truncate(block.data)));
switch(neighbor) {
.dirNegX => {
currentData.isConnectedNegX = targetVal;
},
.dirPosX => {
currentData.isConnectedPosX = targetVal;
},
.dirNegY => {
currentData.isConnectedNegY = targetVal;
},
.dirPosY => {
currentData.isConnectedPosY = targetVal;
},
else => {},
}
const result: u16 = @as(u4, @bitCast(currentData));
if(result == block.data) return false;
block.data = result;
return true;
}

71
src/rotation/log.zig Normal file
View File

@ -0,0 +1,71 @@
const std = @import("std");
const main = @import("root");
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 vec = main.vec;
const Mat4f = vec.Mat4f;
const Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const ZonElement = main.ZonElement;
var rotatedModels: std.StringHashMap(ModelIndex) = undefined;
pub fn init() void {
rotatedModels = .init(main.globalAllocator.allocator);
}
pub fn deinit() void {
rotatedModels.deinit();
}
pub fn reset() void {
rotatedModels.clearRetainingCapacity();
}
pub fn createBlockModel(_: Block, _: *u16, zon: ZonElement) ModelIndex {
const modelId = zon.as([]const u8, "cubyz:cube");
if(rotatedModels.get(modelId)) |modelIndex| return modelIndex;
const baseModel = main.models.getModelIndex(modelId).model();
// Rotate the model:
const modelIndex = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.identity()});
_ = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationY(std.math.pi)});
_ = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(-std.math.pi/2.0).mul(Mat4f.rotationX(-std.math.pi/2.0))});
_ = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(std.math.pi/2.0).mul(Mat4f.rotationX(-std.math.pi/2.0))});
_ = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationX(-std.math.pi/2.0)});
_ = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(std.math.pi).mul(Mat4f.rotationX(-std.math.pi/2.0))});
rotatedModels.put(modelId, modelIndex) catch unreachable;
return modelIndex;
}
pub fn model(block: Block) ModelIndex {
return .{.index = blocks.meshes.modelIndexStart(block).index + @min(block.data, 5)};
}
fn rotateZ(data: u16, angle: Degrees) u16 {
comptime var rotationTable: [4][6]u8 = undefined;
comptime for(0..6) |i| {
rotationTable[0][i] = i;
};
comptime for(1..4) |a| {
for(0..6) |i| {
const neighbor: Neighbor = @enumFromInt(rotationTable[a - 1][i]);
rotationTable[a][i] = neighbor.rotateZ().toInt();
}
};
if(data >= 6) return 0;
return rotationTable[@intFromEnum(angle)][data];
}
pub fn generateData(_: *main.game.World, _: Vec3i, _: Vec3f, _: Vec3f, _: Vec3i, neighbor: ?Neighbor, currentData: *Block, _: Block, blockPlacing: bool) bool {
if(blockPlacing) {
currentData.data = neighbor.?.reverse().toInt();
return true;
}
return false;
}

View File

@ -0,0 +1,3 @@
pub fn init() void {}
pub fn deinit() void {}
pub fn reset() void {}

74
src/rotation/ore.zig Normal file
View File

@ -0,0 +1,74 @@
const std = @import("std");
const main = @import("root");
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 Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const ZonElement = main.ZonElement;
var modelCache: ?ModelIndex = null;
pub fn init() void {}
pub fn deinit() void {}
pub fn reset() void {
modelCache = null;
}
pub fn createBlockModel(_: Block, _: *u16, zon: ZonElement) ModelIndex {
const modelId = zon.as([]const u8, "cubyz:cube");
if(!std.mem.eql(u8, modelId, "cubyz:cube")) {
std.log.err("Ores can only be use on cube models.", .{modelId});
}
if(modelCache) |modelIndex| return modelIndex;
const baseModel = main.models.getModelIndex("cubyz:cube").model();
var quadList = main.List(main.models.QuadInfo).init(main.stackAllocator);
defer quadList.deinit();
baseModel.getRawFaces(&quadList);
const len = quadList.items.len;
for(0..len) |i| {
quadList.append(quadList.items[i]);
quadList.items[i + len].textureSlot += 16;
quadList.items[i].opaqueInLod = 2;
}
const modelIndex = main.models.Model.init(quadList.items);
modelCache = modelIndex;
return modelIndex;
}
pub fn generateData(_: *main.game.World, _: Vec3i, _: Vec3f, _: Vec3f, _: Vec3i, _: ?Neighbor, _: *Block, _: Block, _: bool) bool {
return false;
}
pub fn modifyBlock(block: *Block, newBlockType: u16) bool {
if(block.transparent() or block.viewThrough()) return false;
if(!main.blocks.meshes.modelIndexStart(block.*).model().allNeighborsOccluded) return false;
if(block.data != 0) return false;
block.data = block.typ;
block.typ = newBlockType;
return true;
}
pub fn canBeChangedInto(oldBlock: Block, newBlock: Block, _: main.items.ItemStack, shouldDropSourceBlockOnSuccess: *bool) RotationMode.CanBeChangedInto {
if(oldBlock == newBlock) return .no;
if(oldBlock.transparent() or oldBlock.viewThrough()) return .no;
if(!main.blocks.meshes.modelIndexStart(oldBlock).model().allNeighborsOccluded) return .no;
if(oldBlock.data != 0) return .no;
if(newBlock.data != oldBlock.typ) return .no;
shouldDropSourceBlockOnSuccess.* = false;
return .{.yes_costsItems = 1};
}
pub fn onBlockBreaking(_: ?main.items.Item, _: Vec3f, _: Vec3f, currentData: *Block) void {
currentData.typ = currentData.data;
currentData.data = 0;
}

75
src/rotation/planar.zig Normal file
View File

@ -0,0 +1,75 @@
const std = @import("std");
const main = @import("root");
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 vec = main.vec;
const Mat4f = vec.Mat4f;
const Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const ZonElement = main.ZonElement;
var rotatedModels: std.StringHashMap(ModelIndex) = undefined;
pub fn init() void {
rotatedModels = .init(main.globalAllocator.allocator);
}
pub fn deinit() void {
rotatedModels.deinit();
}
pub fn reset() void {
rotatedModels.clearRetainingCapacity();
}
pub fn createBlockModel(_: Block, _: *u16, zon: ZonElement) ModelIndex {
const modelId = zon.as([]const u8, "cubyz:cube");
if(rotatedModels.get(modelId)) |modelIndex| return modelIndex;
const baseModel = main.models.getModelIndex(modelId).model();
// Rotate the model:
const modelIndex: ModelIndex = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(std.math.pi/2.0)});
_ = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(-std.math.pi/2.0)});
_ = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(std.math.pi)});
_ = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.identity()});
rotatedModels.put(modelId, modelIndex) catch unreachable;
return modelIndex;
}
pub fn model(block: Block) ModelIndex {
return .{.index = blocks.meshes.modelIndexStart(block).index + @min(block.data, 3)};
}
fn rotateZ(data: u16, angle: Degrees) u16 {
comptime var rotationTable: [4][4]u8 = undefined;
comptime for(0..4) |i| {
rotationTable[0][i] = i;
};
comptime for(1..4) |a| {
for(0..4) |i| {
const neighbor: Neighbor = @enumFromInt(rotationTable[a - 1][i] + 2);
rotationTable[a][i] = neighbor.rotateZ().toInt() - 2;
}
};
if(data >= 4) return 0;
return rotationTable[@intFromEnum(angle)][data];
}
pub fn generateData(_: *main.game.World, _: Vec3i, _: Vec3f, playerDir: Vec3f, _: Vec3i, _: ?Neighbor, currentData: *Block, _: Block, blockPlacing: bool) bool {
if(blockPlacing) {
if(@abs(playerDir[0]) > @abs(playerDir[1])) {
const dir: Neighbor = if(playerDir[0] < 0) .dirNegX else .dirPosX;
currentData.data = dir.toInt() - 2;
} else {
const dir: Neighbor = if(playerDir[1] < 0) .dirNegY else .dirPosY;
currentData.data = dir.toInt() - 2;
}
return true;
}
return false;
}

343
src/rotation/stairs.zig Normal file
View File

@ -0,0 +1,343 @@
const std = @import("std");
const main = @import("root");
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;
}
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 .{.index = blocks.meshes.modelIndexStart(block).index + (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 intersectHalfUnitBox(start: Vec3f, invDir: Vec3f) ?f32 {
const t0 = start*invDir;
const t1 = (start + Vec3f{0.5, 0.5, 0.5})*invDir;
const entry = @reduce(.Max, @min(t0, t1));
const exit = @reduce(.Min, @max(t0, t1));
if(entry > exit or exit < 0) {
return null;
} else return entry;
}
fn intersectionPos(block: Block, relativePlayerPos: Vec3f, playerDir: Vec3f) ?struct {minT: f32, minPos: @Vector(3, u1)} {
const invDir = @as(Vec3f, @splat(1))/playerDir;
const relPos: Vec3f = @floatCast(-relativePlayerPos);
const data: u8 = @truncate(block.data);
var minT: f32 = std.math.floatMax(f32);
var minPos: @Vector(3, u1) = undefined;
for(0..8) |i| {
const subPos: @Vector(3, u1) = .{
@truncate(i >> 2),
@truncate(i >> 1),
@truncate(i),
};
if(hasSubBlock(data, subPos[0], subPos[1], subPos[2])) {
const relSubPos = relPos + @as(Vec3f, @floatFromInt(subPos))*@as(Vec3f, @splat(0.5));
if(intersectHalfUnitBox(relSubPos, invDir)) |t| {
if(t < minT) {
minT = t;
minPos = subPos;
}
}
}
}
if(minT != std.math.floatMax(f32)) {
return .{.minT = minT, .minPos = minPos};
}
return null;
}
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 eigth of a block
if(intersectionPos(block, relativePlayerPos, playerDir)) |intersection| {
const offset: Vec3f = @floatFromInt(intersection.minPos);
const half: Vec3f = @splat(0.5);
return .{
.distance = intersection.minT,
.min = half*offset,
.max = half + half*offset,
};
}
return null;
}
},
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
if(intersectionPos(currentData.*, relativePlayerPos, playerDir)) |intersection| {
currentData.data = currentData.data | subBlockMask(intersection.minPos[0], intersection.minPos[1], intersection.minPos[2]);
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;
}

View File

@ -0,0 +1,93 @@
const std = @import("std");
const main = @import("root");
const blocks = main.blocks;
const Block = blocks.Block;
const Neighbor = main.chunk.Neighbor;
const ModelIndex = main.models.ModelIndex;
const rotation = main.rotation;
const RotationMode = rotation.RotationMode;
const vec = main.vec;
const Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const ZonElement = main.ZonElement;
var rotatedModels: std.StringHashMap(ModelIndex) = undefined;
pub fn init() void {
rotatedModels = .init(main.globalAllocator.allocator);
}
pub fn deinit() void {
rotatedModels.deinit();
}
pub fn reset() void {
rotatedModels.clearRetainingCapacity();
}
fn transform(quad: *main.models.QuadInfo, data: u16) void {
quad.textureSlot = data%16;
}
pub fn createBlockModel(block: Block, modeData: *u16, zon: ZonElement) ModelIndex {
const modelId = zon.get([]const u8, "model", "cubyz:cube");
const stateCount = zon.get(u16, "states", 2);
const blockId = block.id();
if(stateCount <= 1) {
std.log.err("Block '{s}' uses texture pile with {} states. 'texturePile' should have at least 2 states, use 'no_rotation' instead", .{blockId, stateCount});
} else if(stateCount > 16) {
std.log.err("Block '{s}' uses texture pile with {} states. 'texturePile' can have at most 16 states.", .{blockId, stateCount});
}
modeData.* = stateCount;
if(rotatedModels.get(modelId)) |modelIndex| return modelIndex;
const baseModel = main.models.getModelIndex(modelId).model();
const modelIndex = baseModel.transformModel(transform, .{@as(u16, @intCast(0))});
for(1..16) |data| {
_ = baseModel.transformModel(transform, .{@as(u16, @intCast(data))});
}
rotatedModels.put(modelId, modelIndex) catch unreachable;
return modelIndex;
}
pub fn model(block: Block) ModelIndex {
return .{.index = blocks.meshes.modelIndexStart(block).index + @min(block.data, block.modeData() - 1)};
}
pub fn generateData(_: *main.game.World, _: Vec3i, _: Vec3f, _: Vec3f, _: Vec3i, _: ?Neighbor, currentData: *Block, _: Block, blockPlacing: bool) bool {
if(blockPlacing) {
currentData.data = 0;
return true;
}
if(currentData.data >= currentData.modeData() - 1) {
return false;
}
currentData.data = currentData.data + 1;
return true;
}
pub fn onBlockBreaking(_: ?main.items.Item, _: Vec3f, _: Vec3f, currentData: *Block) void {
if(currentData.data == 0) {
currentData.* = .{.typ = 0, .data = 0};
} else {
currentData.data = currentData.data - 1;
}
}
pub fn canBeChangedInto(oldBlock: Block, newBlock: Block, item: main.items.ItemStack, shouldDropSourceBlockOnSuccess: *bool) RotationMode.CanBeChangedInto {
switch(RotationMode.DefaultFunctions.canBeChangedInto(oldBlock, newBlock, item, shouldDropSourceBlockOnSuccess)) {
.no, .yes_costsDurability, .yes_dropsItems => return .no,
.yes, .yes_costsItems => {
const amountChange = @as(i32, newBlock.data) - if(oldBlock.typ == newBlock.typ) @as(i32, oldBlock.data) else 0;
if(amountChange <= 0) {
return .{.yes_dropsItems = @intCast(-amountChange)};
} else {
if(item.item == null or item.item.? != .baseItem or item.item.?.baseItem.block != newBlock.typ) return .no;
return .{.yes_costsItems = @intCast(amountChange)};
}
},
}
}

211
src/rotation/torch.zig Normal file
View File

@ -0,0 +1,211 @@
const std = @import("std");
const main = @import("root");
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 Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const ZonElement = main.ZonElement;
pub const naturalStandard: u16 = 1;
pub const dependsOnNeighbors = true;
var rotatedModels: std.StringHashMap(ModelIndex) = undefined;
const TorchData = packed struct(u5) {
center: bool,
negX: bool,
posX: bool,
negY: bool,
posY: bool,
};
pub fn init() void {
rotatedModels = .init(main.globalAllocator.allocator);
}
pub fn deinit() void {
rotatedModels.deinit();
}
pub fn reset() void {
rotatedModels.clearRetainingCapacity();
}
pub fn createBlockModel(_: Block, _: *u16, zon: ZonElement) ModelIndex {
const baseModelId: []const u8 = zon.get([]const u8, "base", "cubyz:cube");
const sideModelId: []const u8 = zon.get([]const u8, "side", "cubyz:cube");
const key: []const u8 = std.mem.concat(main.stackAllocator.allocator, u8, &.{baseModelId, sideModelId}) catch unreachable;
defer main.stackAllocator.free(key);
if(rotatedModels.get(key)) |modelIndex| return modelIndex;
const baseModel = main.models.getModelIndex(baseModelId).model();
const sideModel = main.models.getModelIndex(sideModelId).model();
// Rotate the model:
var centerModel: ModelIndex = undefined;
var negXModel: ModelIndex = undefined;
var posXModel: ModelIndex = undefined;
var negYModel: ModelIndex = undefined;
var posYModel: ModelIndex = undefined;
for(1..32) |i| {
const torchData: TorchData = @bitCast(@as(u5, @intCast(i)));
if(i & i - 1 == 0) {
if(torchData.center) centerModel = baseModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.identity()});
if(torchData.negX) negXModel = sideModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(0)});
if(torchData.posX) posXModel = sideModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(std.math.pi)});
if(torchData.negY) negYModel = sideModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(std.math.pi/2.0)});
if(torchData.posY) posYModel = sideModel.transformModel(rotation.rotationMatrixTransform, .{Mat4f.rotationZ(-std.math.pi/2.0)});
} else {
var models: [5]ModelIndex = undefined;
var amount: usize = 0;
if(torchData.center) {
models[amount] = centerModel;
amount += 1;
}
if(torchData.negX) {
models[amount] = negXModel;
amount += 1;
}
if(torchData.posX) {
models[amount] = posXModel;
amount += 1;
}
if(torchData.negY) {
models[amount] = negYModel;
amount += 1;
}
if(torchData.posY) {
models[amount] = posYModel;
amount += 1;
}
_ = main.models.Model.mergeModels(models[0..amount]);
}
}
const modelIndex = centerModel;
rotatedModels.put(key, modelIndex) catch unreachable;
return modelIndex;
}
pub fn model(block: Block) ModelIndex {
return .{.index = blocks.meshes.modelIndexStart(block).index + (@as(u5, @truncate(block.data)) -| 1)};
}
fn rotateZ(data: u16, angle: Degrees) u16 {
comptime var rotationTable: [4][32]u8 = undefined;
comptime for(0..32) |i| {
rotationTable[0][i] = @intCast(i);
};
comptime for(1..4) |a| {
for(0..32) |i| {
const old: TorchData = @bitCast(@as(u5, @intCast(rotationTable[a - 1][i])));
const new: TorchData = .{
.center = old.center,
.negY = old.negX,
.posY = old.posX,
.posX = old.negY,
.negX = old.posY,
};
rotationTable[a][i] = @as(u5, @bitCast(new));
}
};
if(data >= 32) return 0;
return rotationTable[@intFromEnum(angle)][data];
}
pub fn generateData(_: *main.game.World, _: Vec3i, _: Vec3f, _: Vec3f, relativeDir: Vec3i, neighbor: ?Neighbor, currentData: *Block, neighborBlock: Block, _: bool) bool {
if(neighbor == null) return false;
const neighborModel = blocks.meshes.model(neighborBlock).model();
const neighborSupport = neighborBlock.solid() and neighborModel.neighborFacingQuads[neighbor.?.reverse().toInt()].len != 0;
if(!neighborSupport) return false;
var data: TorchData = @bitCast(@as(u5, @truncate(currentData.data)));
if(relativeDir[0] == 1) data.posX = true;
if(relativeDir[0] == -1) data.negX = true;
if(relativeDir[1] == 1) data.posY = true;
if(relativeDir[1] == -1) data.negY = true;
if(relativeDir[2] == -1) data.center = true;
if(@as(u5, @bitCast(data)) != currentData.data) {
currentData.data = @as(u5, @bitCast(data));
return true;
} else {
return false;
}
}
pub fn updateData(block: *Block, neighbor: Neighbor, neighborBlock: Block) bool {
const neighborModel = blocks.meshes.model(neighborBlock).model();
const neighborSupport = neighborBlock.solid() and neighborModel.neighborFacingQuads[neighbor.reverse().toInt()].len != 0;
var currentData: TorchData = @bitCast(@as(u5, @truncate(block.data)));
switch(neighbor) {
.dirNegX => {
currentData.negX = currentData.negX and neighborSupport;
},
.dirPosX => {
currentData.posX = currentData.posX and neighborSupport;
},
.dirNegY => {
currentData.negY = currentData.negY and neighborSupport;
},
.dirPosY => {
currentData.posY = currentData.posY and neighborSupport;
},
.dirDown => {
currentData.center = currentData.center and neighborSupport;
},
else => {},
}
const result: u16 = @as(u5, @bitCast(currentData));
if(result == block.data) return false;
block.data = result;
if(result == 0) block.typ = 0;
return true;
}
fn closestRay(comptime typ: enum {bit, intersection}, block: Block, _: ?main.items.Item, 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}) |bit| {
if(block.data & bit != 0) {
const modelIndex = ModelIndex{.index = blocks.meshes.modelIndexStart(block).index + bit - 1};
if(RotationMode.DefaultFunctions.rayModelIntersection(modelIndex, relativePlayerPos, playerDir)) |intersection| {
if(result == null or result.?.distance > intersection.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 {
return closestRay(.intersection, block, item, relativePlayerPos, playerDir);
}
pub fn onBlockBreaking(item: ?main.items.Item, relativePlayerPos: Vec3f, playerDir: Vec3f, currentData: *Block) void {
const bit = closestRay(.bit, currentData.*, item, relativePlayerPos, playerDir);
currentData.data &= ~bit;
if(currentData.data == 0) currentData.typ = 0;
}
pub fn canBeChangedInto(oldBlock: Block, newBlock: Block, item: main.items.ItemStack, shouldDropSourceBlockOnSuccess: *bool) RotationMode.CanBeChangedInto {
switch(RotationMode.DefaultFunctions.canBeChangedInto(oldBlock, newBlock, item, shouldDropSourceBlockOnSuccess)) {
.no, .yes_costsDurability, .yes_dropsItems => return .no,
.yes, .yes_costsItems => {
const torchAmountChange = @as(i32, @popCount(newBlock.data)) - if(oldBlock.typ == newBlock.typ) @as(i32, @popCount(oldBlock.data)) else 0;
if(torchAmountChange <= 0) {
return .{.yes_dropsItems = @intCast(-torchAmountChange)};
} else {
if(item.item == null or item.item.? != .baseItem or !std.meta.eql(item.item.?.baseItem.block, newBlock.typ)) return .no;
return .{.yes_costsItems = @intCast(torchAmountChange)};
}
},
}
}