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);
}
pub inline fn model(block: Block) rotation.RotatedModel {
pub inline fn model(block: Block) u16 {
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) = .{};
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);
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);

View File

@ -761,7 +761,7 @@ pub const ItemDropRenderer = struct {
if(item == .baseItem and item.baseItem.block != null) {
const blockType = item.baseItem.block.?;
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);
} else {
const index = getModelIndex(item);

View File

@ -14,7 +14,7 @@ const NeverFailingAllocator = main.utils.NeverFailingAllocator;
var quadSSBO: graphics.SSBO = undefined;
const QuadInfo = extern struct {
pub const QuadInfo = extern struct {
normal: Vec3f,
corners: [4]Vec3f,
cornerUV: [4]Vec2f,
@ -98,7 +98,7 @@ const Model = struct {
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);
defer quadList.deinit();
for(model.internalQuads) |quadIndex| {
@ -114,10 +114,7 @@ const Model = struct {
}
}
for(quadList.items) |*quad| {
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};
}
@call(.auto, transformFunction, .{quad} ++ transformFunctionParameters);
}
return Model.init(main.globalAllocator, quadList.items);
}

View File

@ -731,7 +731,7 @@ pub const MeshSelection = struct {
if(block.typ != 0) {
// Check the true bounding box (using this algorithm here: https://tavianator.com/2011/ray_box.html):
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 max: Vec3d = @floatCast(modelData.max);
const voxelPosFloat: Vec3d = @floatFromInt(voxelPos);
@ -864,7 +864,7 @@ pub const MeshSelection = struct {
c.glPolygonOffset(-2, 0);
const block = mesh_storage.getBlockFromRenderThread(_selectedBlockPos[0], _selectedBlockPos[1], _selectedBlockPos[2]) orelse return;
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 max: Vec3f = @floatCast(modelData.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 {
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 {
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 {
const model = blocks.meshes.model(block);
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 {
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 {
const rotatedModel = blocks.meshes.model(block);
const model = &models.models.items[rotatedModel.modelIndex];
const model = &models.models.items[rotatedModel];
_ = neighbor;
_ = model; // TODO: Check if the neighbor model occludes this one. (maybe not that relevant)
return block.typ != 0 and (
other.typ == 0
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;
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?
/// 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`.
/// With the `RotationMode` interface there is almost no limit to what can be done with those 16 bit.
pub const RotationMode = struct {
const DefaultFunctions = struct {
fn model(block: Block) RotatedModel {
return RotatedModel{
.modelIndex = blocks.meshes.modelIndexStart(block),
};
fn model(block: Block) u16 {
return blocks.meshes.modelIndexStart(block);
}
fn generateData(_: *main.game.World, _: Vec3i, _: Vec3d, _: Vec3f, _: Vec3i, _: *Block, blockPlacing: bool) bool {
return blockPlacing;
@ -142,32 +32,17 @@ pub const RotationMode = struct {
/// if the block should be destroyed or changed when a certain neighbor is removed.
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.
/// 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,
createBlockModel: *const fn(modelId: []const u8) u16 = &DefaultFunctions.createBlockModel,
};
//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().
// * If the returned value is null, then the block will be removed instead of only updating the data.
// * @param oldBlock
@ -187,12 +62,6 @@ pub const RotationMode = struct {
// * @return standard data for natural generation.
// */
// 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.
@ -211,6 +80,13 @@ pub const RotationMode = struct {
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.
const RotationModes = struct {
@ -231,29 +107,26 @@ const RotationModes = struct {
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 {
if(rotatedModels.get(modelId)) |modelIndex| return modelIndex;
const baseModelIndex = main.models.getModelIndex(modelId);
const baseModel = main.models.models.items[baseModelIndex];
// Rotate the model:
const modelIndex: u16 = baseModel.transformModel(Mat4f.identity());
_ = baseModel.transformModel(Mat4f.rotationY(std.math.pi));
_ = baseModel.transformModel(Mat4f.rotationY(std.math.pi/2.0));
_ = baseModel.transformModel(Mat4f.rotationY(-std.math.pi/2.0));
_ = baseModel.transformModel(Mat4f.rotationX(-std.math.pi/2.0));
_ = baseModel.transformModel(Mat4f.rotationX(std.math.pi/2.0));
const modelIndex: u16 = baseModel.transformModel(rotationMatrixTransform, .{Mat4f.identity()});
_ = baseModel.transformModel(rotationMatrixTransform, .{Mat4f.rotationY(std.math.pi)});
_ = baseModel.transformModel(rotationMatrixTransform, .{Mat4f.rotationY(std.math.pi/2.0)});
_ = baseModel.transformModel(rotationMatrixTransform, .{Mat4f.rotationY(-std.math.pi/2.0)});
_ = baseModel.transformModel(rotationMatrixTransform, .{Mat4f.rotationX(-std.math.pi/2.0)});
_ = baseModel.transformModel(rotationMatrixTransform, .{Mat4f.rotationX(std.math.pi/2.0)});
rotatedModels.put(modelId, modelIndex) catch unreachable;
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 {
if(blockPlacing) {
if(relativeDir[0] == 1) currentData.data = chunk.Neighbors.dirNegX;
@ -269,52 +142,60 @@ const RotationModes = struct {
};
pub const Fence = struct {
pub const id: []const u8 = "fence";
// TODO:
fn init() void {}
fn deinit() void {}
pub const dependsOnNeighbors = true;
var fenceModels: std.StringHashMap(u16) = undefined;
const FenceData = packed struct(u4) {
isConnectedNegX: bool,
isConnectedPosX: bool,
isConnectedNegY: bool,
isConnectedPosY: bool,
};
pub fn model(block: Block) RotatedModel {
const data = block.data>>2 & 15; // TODO: This is just for compatibility with the java version. Remove it.
const modelIndexOffsets = [16]u16 {
0, // 0b0000
1, // 0b0001
1, // 0b0010
3, // 0b0011
1, // 0b0100
2, // 0b0101
2, // 0b0110
4, // 0b0111
1, // 0b1000
2, // 0b1001
2, // 0b1010
4, // 0b1011
3, // 0b1100
4, // 0b1101
4, // 0b1110
5, // 0b1111
};
const permutations = [16]Permutation {
Permutation{}, // 0b0000
Permutation{.mirrorX = true, .mirrorZ = true}, // 0b0001
Permutation{}, // 0b0010
Permutation{}, // 0b0011
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
Permutation{.permutationX = 2, .mirrorX = true}, // 0b1001
Permutation{}, // 0b1010
Permutation{.permutationX = 2, .mirrorZ = true}, // 0b1011
Permutation{.permutationX = 2, .mirrorX = true}, // 0b1100
Permutation{}, // 0b1101
Permutation{.mirrorX = true, .mirrorZ = true}, // 0b1110
Permutation{}, // 0b1111
};
return RotatedModel{
.modelIndex = blocks.meshes.modelIndexStart(block) + modelIndexOffsets[data],
.permutation = permutations[data],
};
fn init() void {
fenceModels = std.StringHashMap(u16).init(main.globalAllocator.allocator);
}
fn deinit() void {
fenceModels.deinit();
}
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;
cornerUV[0] = 0.5;
}
if(!data.isConnectedPosY and corner[1] == 1) {
corner[1] = 0.5;
cornerUV[0] = 0.5;
}
}
}
pub fn createBlockModel(modelId: []const u8) u16 {
if(fenceModels.get(modelId)) |modelIndex| return modelIndex;
const baseModelIndex = main.models.getModelIndex(modelId);
const baseModel = main.models.models.items[baseModelIndex];
// Rotate the model:
const modelIndex: u16 = 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) u16 {
return blocks.meshes.modelIndexStart(block) + @min(block.data, 15);
}
};
};