Remove the Permutation and generate all variants of the fence model.

Progess towards #68
This commit is contained in:
IntegratedQuantum 2024-03-22 21:27:10 +01:00
parent e248d85e1c
commit 0159b65e51
7 changed files with 89 additions and 211 deletions

View File

@ -414,7 +414,7 @@ pub const meshes = struct {
_ = arenaForWorld.reset(.free_all); _ = arenaForWorld.reset(.free_all);
} }
pub inline fn model(block: Block) rotation.RotatedModel { pub inline fn model(block: Block) u16 {
return block.mode().model(block); return block.mode().model(block);
} }

View File

@ -1880,7 +1880,7 @@ pub fn generateBlockTexture(blockType: u16) Texture {
var faceData: main.ListUnmanaged(main.renderer.chunk_meshing.FaceData) = .{}; var faceData: main.ListUnmanaged(main.renderer.chunk_meshing.FaceData) = .{};
defer faceData.deinit(main.stackAllocator); defer faceData.deinit(main.stackAllocator);
const model = &main.models.models.items[main.blocks.meshes.model(block).modelIndex]; const model = &main.models.models.items[main.blocks.meshes.model(block)];
model.appendInternalQuadsToList(&faceData, main.stackAllocator, block, 1, 1, 1, false); model.appendInternalQuadsToList(&faceData, main.stackAllocator, block, 1, 1, 1, false);
for(main.chunk.Neighbors.iterable) |neighbor| { for(main.chunk.Neighbors.iterable) |neighbor| {
model.appendNeighborFacingQuadsToList(&faceData, main.stackAllocator, block, neighbor, 1 + main.chunk.Neighbors.relX[neighbor], 1 + main.chunk.Neighbors.relY[neighbor], 1 + main.chunk.Neighbors.relZ[neighbor], false); model.appendNeighborFacingQuadsToList(&faceData, main.stackAllocator, block, neighbor, 1 + main.chunk.Neighbors.relX[neighbor], 1 + main.chunk.Neighbors.relY[neighbor], 1 + main.chunk.Neighbors.relZ[neighbor], false);

View File

@ -761,7 +761,7 @@ pub const ItemDropRenderer = struct {
if(item == .baseItem and item.baseItem.block != null) { if(item == .baseItem and item.baseItem.block != null) {
const blockType = item.baseItem.block.?; const blockType = item.baseItem.block.?;
const block = blocks.Block{.typ = blockType, .data = 0}; const block = blocks.Block{.typ = blockType, .data = 0};
c.glUniform1i(itemUniforms.modelIndex, block.mode().model(block).modelIndex); c.glUniform1i(itemUniforms.modelIndex, block.mode().model(block));
c.glUniform1i(itemUniforms.block, blockType); c.glUniform1i(itemUniforms.block, blockType);
} else { } else {
const index = getModelIndex(item); const index = getModelIndex(item);

View File

@ -14,7 +14,7 @@ const NeverFailingAllocator = main.utils.NeverFailingAllocator;
var quadSSBO: graphics.SSBO = undefined; var quadSSBO: graphics.SSBO = undefined;
const QuadInfo = extern struct { pub const QuadInfo = extern struct {
normal: Vec3f, normal: Vec3f,
corners: [4]Vec3f, corners: [4]Vec3f,
cornerUV: [4]Vec2f, cornerUV: [4]Vec2f,
@ -98,7 +98,7 @@ const Model = struct {
allocator.free(self.internalQuads); allocator.free(self.internalQuads);
} }
pub fn transformModel(model: Model, transformMatrix: Mat4f) u16 { pub fn transformModel(model: Model, transformFunction: anytype, transformFunctionParameters: anytype) u16 {
var quadList = main.List(QuadInfo).init(main.stackAllocator); var quadList = main.List(QuadInfo).init(main.stackAllocator);
defer quadList.deinit(); defer quadList.deinit();
for(model.internalQuads) |quadIndex| { for(model.internalQuads) |quadIndex| {
@ -114,10 +114,7 @@ const Model = struct {
} }
} }
for(quadList.items) |*quad| { for(quadList.items) |*quad| {
quad.normal = vec.xyz(Mat4f.mulVec(transformMatrix, vec.combine(quad.normal, 0))); @call(.auto, transformFunction, .{quad} ++ transformFunctionParameters);
for(&quad.corners) |*corner| {
corner.* = vec.xyz(Mat4f.mulVec(transformMatrix, vec.combine(corner.* - Vec3f{0.5, 0.5, 0.5}, 1))) + Vec3f{0.5, 0.5, 0.5};
}
} }
return Model.init(main.globalAllocator, quadList.items); return Model.init(main.globalAllocator, quadList.items);
} }

View File

@ -731,7 +731,7 @@ pub const MeshSelection = struct {
if(block.typ != 0) { if(block.typ != 0) {
// Check the true bounding box (using this algorithm here: https://tavianator.com/2011/ray_box.html): // Check the true bounding box (using this algorithm here: https://tavianator.com/2011/ray_box.html):
const model = blocks.meshes.model(block); const model = blocks.meshes.model(block);
const modelData = &models.models.items[model.modelIndex]; const modelData = &models.models.items[model];
const min: Vec3d = @floatCast(modelData.min); const min: Vec3d = @floatCast(modelData.min);
const max: Vec3d = @floatCast(modelData.max); const max: Vec3d = @floatCast(modelData.max);
const voxelPosFloat: Vec3d = @floatFromInt(voxelPos); const voxelPosFloat: Vec3d = @floatFromInt(voxelPos);
@ -864,7 +864,7 @@ pub const MeshSelection = struct {
c.glPolygonOffset(-2, 0); c.glPolygonOffset(-2, 0);
const block = mesh_storage.getBlockFromRenderThread(_selectedBlockPos[0], _selectedBlockPos[1], _selectedBlockPos[2]) orelse return; const block = mesh_storage.getBlockFromRenderThread(_selectedBlockPos[0], _selectedBlockPos[1], _selectedBlockPos[2]) orelse return;
const model = blocks.meshes.model(block); const model = blocks.meshes.model(block);
const modelData = &models.models.items[model.modelIndex]; const modelData = &models.models.items[model];
const min: Vec3f = @floatCast(modelData.min); const min: Vec3f = @floatCast(modelData.min);
const max: Vec3f = @floatCast(modelData.max); const max: Vec3f = @floatCast(modelData.max);
drawCube(projectionMatrix, viewMatrix, @as(Vec3d, @floatFromInt(_selectedBlockPos)) - playerPos, min, max); drawCube(projectionMatrix, viewMatrix, @as(Vec3d, @floatFromInt(_selectedBlockPos)) - playerPos, min, max);

View File

@ -188,20 +188,20 @@ const PrimitiveMesh = struct {
fn appendInternalQuadsToCore(self: *PrimitiveMesh, block: Block, x: i32, y: i32, z: i32, comptime backFace: bool) void { fn appendInternalQuadsToCore(self: *PrimitiveMesh, block: Block, x: i32, y: i32, z: i32, comptime backFace: bool) void {
const model = blocks.meshes.model(block); const model = blocks.meshes.model(block);
models.models.items[model.modelIndex].appendInternalQuadsToList(&self.coreFaces, main.globalAllocator, block, x, y, z, backFace); models.models.items[model].appendInternalQuadsToList(&self.coreFaces, main.globalAllocator, block, x, y, z, backFace);
} }
fn appendNeighborFacingQuadsToCore(self: *PrimitiveMesh, block: Block, neighbor: u3, x: i32, y: i32, z: i32, comptime backFace: bool) void { fn appendNeighborFacingQuadsToCore(self: *PrimitiveMesh, block: Block, neighbor: u3, x: i32, y: i32, z: i32, comptime backFace: bool) void {
const model = blocks.meshes.model(block); const model = blocks.meshes.model(block);
models.models.items[model.modelIndex].appendNeighborFacingQuadsToList(&self.coreFaces, main.globalAllocator, block, neighbor, x, y, z, backFace); models.models.items[model].appendNeighborFacingQuadsToList(&self.coreFaces, main.globalAllocator, block, neighbor, x, y, z, backFace);
} }
fn appendNeighborFacingQuadsToNeighbor(self: *PrimitiveMesh, block: Block, neighbor: u3, x: i32, y: i32, z: i32, comptime backFace: bool, comptime isLod: bool) void { fn appendNeighborFacingQuadsToNeighbor(self: *PrimitiveMesh, block: Block, neighbor: u3, x: i32, y: i32, z: i32, comptime backFace: bool, comptime isLod: bool) void {
const model = blocks.meshes.model(block); const model = blocks.meshes.model(block);
if(isLod) { if(isLod) {
models.models.items[model.modelIndex].appendNeighborFacingQuadsToList(&self.neighborFacesHigherLod[neighbor ^ 1], main.globalAllocator, block, neighbor, x, y, z, backFace); models.models.items[model].appendNeighborFacingQuadsToList(&self.neighborFacesHigherLod[neighbor ^ 1], main.globalAllocator, block, neighbor, x, y, z, backFace);
} else { } else {
models.models.items[model.modelIndex].appendNeighborFacingQuadsToList(&self.neighborFacesSameLod[neighbor ^ 1], main.globalAllocator, block, neighbor, x, y, z, backFace); models.models.items[model].appendNeighborFacingQuadsToList(&self.neighborFacesSameLod[neighbor ^ 1], main.globalAllocator, block, neighbor, x, y, z, backFace);
} }
} }
@ -526,13 +526,13 @@ pub const ChunkMesh = struct {
fn canBeSeenThroughOtherBlock(block: Block, other: Block, neighbor: u3) bool { fn canBeSeenThroughOtherBlock(block: Block, other: Block, neighbor: u3) bool {
const rotatedModel = blocks.meshes.model(block); const rotatedModel = blocks.meshes.model(block);
const model = &models.models.items[rotatedModel.modelIndex]; const model = &models.models.items[rotatedModel];
_ = neighbor; _ = neighbor;
_ = model; // TODO: Check if the neighbor model occludes this one. (maybe not that relevant) _ = model; // TODO: Check if the neighbor model occludes this one. (maybe not that relevant)
return block.typ != 0 and ( return block.typ != 0 and (
other.typ == 0 other.typ == 0
or (!std.meta.eql(block, other) and other.viewThrough()) or (!std.meta.eql(block, other) and other.viewThrough())
or blocks.meshes.model(other).modelIndex != 0 or blocks.meshes.model(other) != 0
); );
} }

View File

@ -12,124 +12,14 @@ const Vec3d = vec.Vec3d;
const Mat4f = vec.Mat4f; const Mat4f = vec.Mat4f;
pub const Permutation = packed struct(u6) {
/// 0 if x is x, 1 if x and y are swapped, 2 if x and z are swapped
permutationX: u2 = 0,
/// whether y and z are swapped (applied after permutationX)
permutationYZ: bool = false,
/// whether the x coordinate of the original model(applied before permutations) is flipped
mirrorX: bool = false,
/// whether the y coordinate of the original model(applied before permutations) is flipped
mirrorY: bool = false,
/// whether the z coordinate of the original model(applied before permutations) is flipped
mirrorZ: bool = false,
pub fn toInt(self: Permutation) u6 {
return @bitCast(self);
}
pub fn transform(self: Permutation, _x: anytype) @TypeOf(_x) {
var x = _x;
if(@typeInfo(@TypeOf(x)) != .Vector) @compileError("Can only transform vector types.");
if(@typeInfo(@TypeOf(x)).Vector.len != 3) @compileError("Vector needs to have length 3.");
if(self.mirrorX) x[0] = -x[0];
if(self.mirrorY) x[1] = -x[1];
if(self.mirrorZ) x[2] = -x[2];
switch(self.permutationX) {
0 => {},
1 => {
const swap = x[0];
x[0] = x[1];
x[1] = swap;
},
2 => {
const swap = x[0];
x[0] = x[2];
x[2] = swap;
},
else => unreachable,
}
if(self.permutationYZ) {
const swap = x[1];
x[1] = x[2];
x[2] = swap;
}
return x;
}
pub fn permuteNeighborIndex(self: Permutation, neighbor: u3) u3 {
// TODO: Make this more readable. Not sure how though.
const mirrored: u3 = switch(neighbor) {
Neighbors.dirNegX,
Neighbors.dirPosX => (
if(self.mirrorX) neighbor ^ 1
else neighbor
),
Neighbors.dirNegY,
Neighbors.dirPosY => (
if(self.mirrorY) neighbor ^ 1
else neighbor
),
Neighbors.dirDown,
Neighbors.dirUp => (
if(self.mirrorZ) neighbor ^ 1
else neighbor
),
else => unreachable,
};
const afterXPermutation: u3 = switch(mirrored) {
Neighbors.dirNegX,
Neighbors.dirPosX => (
if(self.permutationX == 1) mirrored +% (Neighbors.dirNegY -% Neighbors.dirNegX)
else if(self.permutationX == 2) mirrored +% (Neighbors.dirDown -% Neighbors.dirNegX)
else mirrored
),
Neighbors.dirNegY,
Neighbors.dirPosY => (
if(self.permutationX == 1) mirrored +% (Neighbors.dirNegX -% Neighbors.dirNegY)
else mirrored
),
Neighbors.dirDown,
Neighbors.dirUp => (
if(self.permutationX == 2) mirrored +% (Neighbors.dirNegX -% Neighbors.dirDown)
else mirrored
),
else => unreachable,
};
const afterYZPermutation: u3 = switch(afterXPermutation) {
Neighbors.dirNegX,
Neighbors.dirPosX => afterXPermutation,
Neighbors.dirNegY,
Neighbors.dirPosY => (
if(self.permutationYZ) afterXPermutation +% (Neighbors.dirDown -% Neighbors.dirNegY)
else afterXPermutation
),
Neighbors.dirDown,
Neighbors.dirUp => (
if(self.permutationYZ) afterXPermutation +% (Neighbors.dirNegY -% Neighbors.dirDown)
else afterXPermutation
),
else => unreachable,
};
return afterYZPermutation;
}
};
pub const RotatedModel = struct {
modelIndex: u16,
permutation: Permutation = Permutation{},
};
// TODO: Why not just use a tagged union? // TODO: Why not just use a tagged union?
/// Each block gets 16 bit of additional storage(apart from the reference to the block type). /// Each block gets 16 bit of additional storage(apart from the reference to the block type).
/// These 16 bits are accessed and interpreted by the `RotationMode`. /// These 16 bits are accessed and interpreted by the `RotationMode`.
/// With the `RotationMode` interface there is almost no limit to what can be done with those 16 bit. /// With the `RotationMode` interface there is almost no limit to what can be done with those 16 bit.
pub const RotationMode = struct { pub const RotationMode = struct {
const DefaultFunctions = struct { const DefaultFunctions = struct {
fn model(block: Block) RotatedModel { fn model(block: Block) u16 {
return RotatedModel{ return blocks.meshes.modelIndexStart(block);
.modelIndex = blocks.meshes.modelIndexStart(block),
};
} }
fn generateData(_: *main.game.World, _: Vec3i, _: Vec3d, _: Vec3f, _: Vec3i, _: *Block, blockPlacing: bool) bool { fn generateData(_: *main.game.World, _: Vec3i, _: Vec3d, _: Vec3f, _: Vec3i, _: *Block, blockPlacing: bool) bool {
return blockPlacing; return blockPlacing;
@ -142,32 +32,17 @@ pub const RotationMode = struct {
/// if the block should be destroyed or changed when a certain neighbor is removed. /// if the block should be destroyed or changed when a certain neighbor is removed.
dependsOnNeighbors: bool = false, dependsOnNeighbors: bool = false,
model: *const fn(block: Block) RotatedModel = &DefaultFunctions.model, model: *const fn(block: Block) u16 = &DefaultFunctions.model,
createBlockModel: *const fn(modelId: []const u8) u16 = &DefaultFunctions.createBlockModel,
/// Updates the block data of a block in the world or places a block in the world. /// Updates the block data of a block in the world or places a block in the world.
/// return true if the placing was successful, false otherwise. /// return true if the placing was successful, false otherwise.
generateData: *const fn(world: *main.game.World, pos: Vec3i, relativePlayerPos: Vec3d, playerDir: Vec3f, relativeDir: Vec3i, currentData: *Block, blockPlacing: bool) bool = DefaultFunctions.generateData, generateData: *const fn(world: *main.game.World, pos: Vec3i, relativePlayerPos: Vec3d, playerDir: Vec3f, relativeDir: Vec3i, currentData: *Block, blockPlacing: bool) bool = DefaultFunctions.generateData,
createBlockModel: *const fn(modelId: []const u8) u16 = &DefaultFunctions.createBlockModel,
}; };
//public interface RotationMode extends RegistryElement { //public interface RotationMode extends RegistryElement {
// /** // /**
// * Update or place a block.
// * @param world
// * @param x
// * @param y
// * @param z
// * @param relativePlayerPosition Position of the player head relative to the (0, 0, 0) corner of the block.
// * @param playerDirection
// * @param relativeDir the direction in which the selected neighbor is.
// * @param currentData 0 if no block was there before.
// * @param blockPlacing true if the position of the block was previously empty/nonsolid.
// * @return true if the placing was successful, false otherwise.
// */
// boolean generateData(World world, int x, int y, int z, Vector3d relativePlayerPosition, Vector3f playerDirection, Vector3i relativeDir, IntWrapper currentData, boolean blockPlacing);
//
// /**
// * Updates data of a placed block if the RotationMode dependsOnNeighbors(). // * Updates data of a placed block if the RotationMode dependsOnNeighbors().
// * If the returned value is null, then the block will be removed instead of only updating the data. // * If the returned value is null, then the block will be removed instead of only updating the data.
// * @param oldBlock // * @param oldBlock
@ -187,12 +62,6 @@ pub const RotationMode = struct {
// * @return standard data for natural generation. // * @return standard data for natural generation.
// */ // */
// int getNaturalStandard(int block); // int getNaturalStandard(int block);
//
// /**
// * @param min minimal point of the surrounding block. May be overwritten.
// * @param max maximal point of the surrounding block. May be overwritten.
// */
// float getRayIntersection(RayAabIntersection intersection, int block, Vector3f min, Vector3f max, Vector3f transformedPosition);
// //
// /** // /**
// * Check if the entity would collide with the block. // * Check if the entity would collide with the block.
@ -211,6 +80,13 @@ pub const RotationMode = struct {
var rotationModes: std.StringHashMap(RotationMode) = undefined; var rotationModes: std.StringHashMap(RotationMode) = undefined;
fn rotationMatrixTransform(quad: *main.models.QuadInfo, transformMatrix: Mat4f) void {
quad.normal = vec.xyz(Mat4f.mulVec(transformMatrix, vec.combine(quad.normal, 0)));
for(&quad.corners) |*corner| {
corner.* = vec.xyz(Mat4f.mulVec(transformMatrix, vec.combine(corner.* - Vec3f{0.5, 0.5, 0.5}, 1))) + Vec3f{0.5, 0.5, 0.5};
}
}
// TODO: Instead of using a permutation, rotation modes should directly return a rotated version of the model. // TODO: Instead of using a permutation, rotation modes should directly return a rotated version of the model.
const RotationModes = struct { const RotationModes = struct {
@ -231,29 +107,26 @@ const RotationModes = struct {
rotatedModels.deinit(); rotatedModels.deinit();
} }
pub fn model(block: Block) RotatedModel {
return RotatedModel{
.modelIndex = blocks.meshes.modelIndexStart(block) + @min(block.data, 5),
.permutation = undefined,
};
}
pub fn createBlockModel(modelId: []const u8) u16 { pub fn createBlockModel(modelId: []const u8) u16 {
if(rotatedModels.get(modelId)) |modelIndex| return modelIndex; if(rotatedModels.get(modelId)) |modelIndex| return modelIndex;
const baseModelIndex = main.models.getModelIndex(modelId); const baseModelIndex = main.models.getModelIndex(modelId);
const baseModel = main.models.models.items[baseModelIndex]; const baseModel = main.models.models.items[baseModelIndex];
// Rotate the model: // Rotate the model:
const modelIndex: u16 = baseModel.transformModel(Mat4f.identity()); const modelIndex: u16 = baseModel.transformModel(rotationMatrixTransform, .{Mat4f.identity()});
_ = baseModel.transformModel(Mat4f.rotationY(std.math.pi)); _ = baseModel.transformModel(rotationMatrixTransform, .{Mat4f.rotationY(std.math.pi)});
_ = baseModel.transformModel(Mat4f.rotationY(std.math.pi/2.0)); _ = baseModel.transformModel(rotationMatrixTransform, .{Mat4f.rotationY(std.math.pi/2.0)});
_ = baseModel.transformModel(Mat4f.rotationY(-std.math.pi/2.0)); _ = baseModel.transformModel(rotationMatrixTransform, .{Mat4f.rotationY(-std.math.pi/2.0)});
_ = baseModel.transformModel(Mat4f.rotationX(-std.math.pi/2.0)); _ = baseModel.transformModel(rotationMatrixTransform, .{Mat4f.rotationX(-std.math.pi/2.0)});
_ = baseModel.transformModel(Mat4f.rotationX(std.math.pi/2.0)); _ = baseModel.transformModel(rotationMatrixTransform, .{Mat4f.rotationX(std.math.pi/2.0)});
rotatedModels.put(modelId, modelIndex) catch unreachable; rotatedModels.put(modelId, modelIndex) catch unreachable;
return modelIndex; return modelIndex;
} }
pub fn model(block: Block) u16 {
return blocks.meshes.modelIndexStart(block) + @min(block.data, 5);
}
pub fn generateData(_: *main.game.World, _: Vec3i, _: Vec3d, _: Vec3f, relativeDir: Vec3i, currentData: *Block, blockPlacing: bool) bool { pub fn generateData(_: *main.game.World, _: Vec3i, _: Vec3d, _: Vec3f, relativeDir: Vec3i, currentData: *Block, blockPlacing: bool) bool {
if(blockPlacing) { if(blockPlacing) {
if(relativeDir[0] == 1) currentData.data = chunk.Neighbors.dirNegX; if(relativeDir[0] == 1) currentData.data = chunk.Neighbors.dirNegX;
@ -269,52 +142,60 @@ const RotationModes = struct {
}; };
pub const Fence = struct { pub const Fence = struct {
pub const id: []const u8 = "fence"; pub const id: []const u8 = "fence";
// TODO: pub const dependsOnNeighbors = true;
fn init() void {} var fenceModels: std.StringHashMap(u16) = undefined;
fn deinit() void {} const FenceData = packed struct(u4) {
isConnectedNegX: bool,
isConnectedPosX: bool,
isConnectedNegY: bool,
isConnectedPosY: bool,
};
pub fn model(block: Block) RotatedModel { fn init() void {
const data = block.data>>2 & 15; // TODO: This is just for compatibility with the java version. Remove it. fenceModels = std.StringHashMap(u16).init(main.globalAllocator.allocator);
const modelIndexOffsets = [16]u16 { }
0, // 0b0000
1, // 0b0001 fn deinit() void {
1, // 0b0010 fenceModels.deinit();
3, // 0b0011 }
1, // 0b0100
2, // 0b0101 fn fenceTransform(quad: *main.models.QuadInfo, data: FenceData) void {
2, // 0b0110 for(&quad.corners, &quad.cornerUV) |*corner, *cornerUV| {
4, // 0b0111 if(!data.isConnectedNegX and corner[0] == 0) {
1, // 0b1000 corner[0] = 0.5;
2, // 0b1001 cornerUV[0] = 0.5;
2, // 0b1010 }
4, // 0b1011 if(!data.isConnectedPosX and corner[0] == 1) {
3, // 0b1100 corner[0] = 0.5;
4, // 0b1101 cornerUV[0] = 0.5;
4, // 0b1110 }
5, // 0b1111 if(!data.isConnectedNegY and corner[1] == 0) {
}; corner[1] = 0.5;
const permutations = [16]Permutation { cornerUV[0] = 0.5;
Permutation{}, // 0b0000 }
Permutation{.mirrorX = true, .mirrorZ = true}, // 0b0001 if(!data.isConnectedPosY and corner[1] == 1) {
Permutation{}, // 0b0010 corner[1] = 0.5;
Permutation{}, // 0b0011 cornerUV[0] = 0.5;
Permutation{.permutationX = 2, .mirrorZ = true}, // 0b0100 }
Permutation{.mirrorX = true, .mirrorZ = true}, // 0b0101 }
Permutation{.permutationX = 2, .mirrorZ = true}, // 0b0110 }
Permutation{.permutationX = 2, .mirrorX = true}, // 0b0111
Permutation{.permutationX = 2, .mirrorX = true}, // 0b1000 pub fn createBlockModel(modelId: []const u8) u16 {
Permutation{.permutationX = 2, .mirrorX = true}, // 0b1001 if(fenceModels.get(modelId)) |modelIndex| return modelIndex;
Permutation{}, // 0b1010
Permutation{.permutationX = 2, .mirrorZ = true}, // 0b1011 const baseModelIndex = main.models.getModelIndex(modelId);
Permutation{.permutationX = 2, .mirrorX = true}, // 0b1100 const baseModel = main.models.models.items[baseModelIndex];
Permutation{}, // 0b1101 // Rotate the model:
Permutation{.mirrorX = true, .mirrorZ = true}, // 0b1110 const modelIndex: u16 = baseModel.transformModel(fenceTransform, .{@as(FenceData, @bitCast(@as(u4, 0)))});
Permutation{}, // 0b1111 for(1..16) |fenceData| {
}; _ = baseModel.transformModel(fenceTransform, .{@as(FenceData, @bitCast(@as(u4, @intCast(fenceData))))});
return RotatedModel{ }
.modelIndex = blocks.meshes.modelIndexStart(block) + modelIndexOffsets[data], fenceModels.put(modelId, modelIndex) catch unreachable;
.permutation = permutations[data], return modelIndex;
}; }
pub fn model(block: Block) u16 {
return blocks.meshes.modelIndexStart(block) + @min(block.data, 15);
} }
}; };
}; };