diff --git a/src/api/java/net/caffeinemc/mods/sodium/api/math/MatrixHelper.java b/src/api/java/net/caffeinemc/mods/sodium/api/math/MatrixHelper.java index 393cf3e075..cb9525b98d 100644 --- a/src/api/java/net/caffeinemc/mods/sodium/api/math/MatrixHelper.java +++ b/src/api/java/net/caffeinemc/mods/sodium/api/math/MatrixHelper.java @@ -159,12 +159,32 @@ public static int transformNormal(Matrix3f matrix, Direction direction) { */ public static void transformNormal(Vector3f result, Matrix3f matrix, Direction direction) { switch (direction) { - case DOWN -> result.set(-matrix.m10, -matrix.m11, -matrix.m12); - case UP -> result.set( matrix.m10, matrix.m11, matrix.m12); - case NORTH -> result.set(-matrix.m20, -matrix.m21, -matrix.m22); - case SOUTH -> result.set( matrix.m20, matrix.m21, matrix.m22); - case WEST -> result.set(-matrix.m00, -matrix.m01, -matrix.m02); - case EAST -> result.set( matrix.m00, matrix.m01, matrix.m02); + case DOWN -> result.set(-matrix.m10(), -matrix.m11(), -matrix.m12()); + case UP -> result.set( matrix.m10(), matrix.m11(), matrix.m12()); + case NORTH -> result.set(-matrix.m20(), -matrix.m21(), -matrix.m22()); + case SOUTH -> result.set( matrix.m20(), matrix.m21(), matrix.m22()); + case WEST -> result.set(-matrix.m00(), -matrix.m01(), -matrix.m02()); + case EAST -> result.set( matrix.m00(), matrix.m01(), matrix.m02()); + }; + } + + /** + * Writes the transformed normal vector for a given unit vector (direction) into {@param result}. This is + * significantly faster than transforming the vector directly (i.e. with {@link Matrix3f#transform(Vector3f)}), + * as it can simply extract the values from the provided matrix (rather than transforming the vertices.) + * + * @param matrix The transformation matrix + * @param direction The unit vector (direction) to use + * @return A transformed normal in packed format + */ + public static void transformNormal(Vector3f result, Matrix4f matrix, Direction direction) { + switch (direction) { + case DOWN -> result.set(-matrix.m10(), -matrix.m11(), -matrix.m12()); + case UP -> result.set( matrix.m10(), matrix.m11(), matrix.m12()); + case NORTH -> result.set(-matrix.m20(), -matrix.m21(), -matrix.m22()); + case SOUTH -> result.set( matrix.m20(), matrix.m21(), matrix.m22()); + case WEST -> result.set(-matrix.m00(), -matrix.m01(), -matrix.m02()); + case EAST -> result.set( matrix.m00(), matrix.m01(), matrix.m02()); }; } } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/immediate/model/EntityRenderer.java b/src/main/java/me/jellysquid/mods/sodium/client/render/immediate/model/EntityRenderer.java index 35f77ec7fa..791b748471 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/immediate/model/EntityRenderer.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/immediate/model/EntityRenderer.java @@ -1,7 +1,6 @@ package me.jellysquid.mods.sodium.client.render.immediate.model; import net.caffeinemc.mods.sodium.api.math.MatrixHelper; -import net.caffeinemc.mods.sodium.api.util.NormI8; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import net.caffeinemc.mods.sodium.api.vertex.format.common.ModelVertex; import net.minecraft.client.model.ModelPart; @@ -57,8 +56,12 @@ public class EntityRenderer { private static final Vector2f[][] VERTEX_TEXTURES = new Vector2f[NUM_CUBE_FACES][NUM_FACE_VERTICES]; private static final Vector2f[][] VERTEX_TEXTURES_MIRRORED = new Vector2f[NUM_CUBE_FACES][NUM_FACE_VERTICES]; - private static final Vector3f[] CUBE_NORMALS = new Vector3f[NUM_CUBE_FACES]; - private static final Vector3f[] CUBE_NORMALS_MIRRORED = new Vector3f[NUM_CUBE_FACES]; + private static final int[] LIGHT_NORMALS = new int[NUM_CUBE_FACES]; + private static final int[] LIGHT_NORMALS_MIRRORED = new int[NUM_CUBE_FACES]; + + private static final Vector3f[] FACE_NORMALS = new Vector3f[NUM_CUBE_FACES]; + + private static final Matrix4f FACE_NORMAL_MATRIX = new Matrix4f(); static { for (int cornerIndex = 0; cornerIndex < NUM_CUBE_VERTICES; cornerIndex++) { @@ -66,7 +69,7 @@ public class EntityRenderer { } for (int quadIndex = 0; quadIndex < NUM_CUBE_FACES; quadIndex++) { - CUBE_NORMALS[quadIndex] = new Vector3f(); + FACE_NORMALS[quadIndex] = new Vector3f(); for (int vertexIndex = 0; vertexIndex < NUM_FACE_VERTICES; vertexIndex++) { VERTEX_TEXTURES[quadIndex][vertexIndex] = new Vector2f(); @@ -81,13 +84,14 @@ public class EntityRenderer { } } - // When mirroring is used, the normals for EAST and WEST are swapped. - CUBE_NORMALS_MIRRORED[FACE_NEG_Y] = CUBE_NORMALS[FACE_NEG_Y]; - CUBE_NORMALS_MIRRORED[FACE_POS_Y] = CUBE_NORMALS[FACE_POS_Y]; - CUBE_NORMALS_MIRRORED[FACE_NEG_Z] = CUBE_NORMALS[FACE_NEG_Z]; - CUBE_NORMALS_MIRRORED[FACE_POS_Z] = CUBE_NORMALS[FACE_POS_Z]; - CUBE_NORMALS_MIRRORED[FACE_POS_X] = CUBE_NORMALS[FACE_NEG_X]; // mirrored - CUBE_NORMALS_MIRRORED[FACE_NEG_X] = CUBE_NORMALS[FACE_POS_X]; // mirrored + // This is a land mine. Do not touch this code. + // When mirroring is used, the normals for EAST and WEST are swapped. Why only these normals? Who knows. + LIGHT_NORMALS_MIRRORED[FACE_NEG_Y] = LIGHT_NORMALS[FACE_NEG_Y]; + LIGHT_NORMALS_MIRRORED[FACE_POS_Y] = LIGHT_NORMALS[FACE_POS_Y]; + LIGHT_NORMALS_MIRRORED[FACE_NEG_Z] = LIGHT_NORMALS[FACE_NEG_Z]; + LIGHT_NORMALS_MIRRORED[FACE_POS_Z] = LIGHT_NORMALS[FACE_POS_Z]; + LIGHT_NORMALS_MIRRORED[FACE_POS_X] = LIGHT_NORMALS[FACE_NEG_X]; // mirrored + LIGHT_NORMALS_MIRRORED[FACE_NEG_X] = LIGHT_NORMALS[FACE_POS_X]; // mirrored } public static void render(MatrixStack matrixStack, VertexBufferWriter writer, ModelPart part, int light, int overlay, int color) { @@ -129,7 +133,7 @@ private static void renderCuboids(MatrixStack.Entry matrices, VertexBufferWriter for (ModelCuboid cuboid : cuboids) { prepareVertices(matrices, cuboid); - var vertexCount = renderCuboid(cuboid, color, overlay, light); + var vertexCount = renderCuboid(matrices, cuboid, color, overlay, light); try (MemoryStack stack = MemoryStack.stackPush()) { writer.push(stack, SCRATCH_BUFFER, vertexCount, ModelVertex.FORMAT); @@ -137,8 +141,8 @@ private static void renderCuboids(MatrixStack.Entry matrices, VertexBufferWriter } } - private static int renderCuboid(ModelCuboid cuboid, int color, int overlay, int light) { - final int faces = cuboid.faces & getVisibleFaces(); + private static int renderCuboid(MatrixStack.Entry matrices, ModelCuboid cuboid, int color, int overlay, int light) { + final int faces = cuboid.faces & getVisibleFaces(matrices); if (faces == 0) { // No faces are visible, so zero vertices will be produced. @@ -147,29 +151,27 @@ private static int renderCuboid(ModelCuboid cuboid, int color, int overlay, int final var positions = cuboid.mirror ? VERTEX_POSITIONS_MIRRORED : VERTEX_POSITIONS; final var textures = cuboid.mirror ? VERTEX_TEXTURES_MIRRORED : VERTEX_TEXTURES; - final var normals = cuboid.mirror ? CUBE_NORMALS_MIRRORED : CUBE_NORMALS; + final var normals = cuboid.mirror ? LIGHT_NORMALS_MIRRORED : LIGHT_NORMALS; var vertexCount = 0; long ptr = SCRATCH_BUFFER; for (int quadIndex = 0; quadIndex < NUM_CUBE_FACES; quadIndex++) { - if ((faces & (1 << quadIndex)) != 0) { + if ((faces & (1 << quadIndex)) == 0) { continue; } - var normal = NormI8.pack(normals[quadIndex]); - - emitVertex(ptr, positions[quadIndex][0], color, textures[quadIndex][0], overlay, light, normal); + emitVertex(ptr, positions[quadIndex][0], color, textures[quadIndex][0], overlay, light, normals[quadIndex]); ptr += ModelVertex.STRIDE; - emitVertex(ptr, positions[quadIndex][1], color, textures[quadIndex][1], overlay, light, normal); + emitVertex(ptr, positions[quadIndex][1], color, textures[quadIndex][1], overlay, light, normals[quadIndex]); ptr += ModelVertex.STRIDE; - emitVertex(ptr, positions[quadIndex][2], color, textures[quadIndex][2], overlay, light, normal); + emitVertex(ptr, positions[quadIndex][2], color, textures[quadIndex][2], overlay, light, normals[quadIndex]); ptr += ModelVertex.STRIDE; - emitVertex(ptr, positions[quadIndex][3], color, textures[quadIndex][3], overlay, light, normal); + emitVertex(ptr, positions[quadIndex][3], color, textures[quadIndex][3], overlay, light, normals[quadIndex]); ptr += ModelVertex.STRIDE; vertexCount += 4; @@ -183,13 +185,16 @@ private static void emitVertex(long ptr, Vector3f pos, int color, Vector2f tex, } - private static int getVisibleFaces() { - final var min = CUBE_CORNERS[VERTEX_X1_Y1_Z1]; - final var max = CUBE_CORNERS[VERTEX_X2_Y2_Z2]; - - int faces = 0; + private static int getVisibleFaces(MatrixStack.Entry matrices) { + // For orthogonal matrices (used in inventory rendering), this trick does not seem to work. Trying to debug + // how the orthogonal matrix is created made me immediately want to light my computer on fire, so we're instead + // just going to ignore the problem. + if ((matrices.getPositionMatrix().properties() & Matrix4f.PROPERTY_ORTHONORMAL) == 0) { + // All faces are visible. + return 0b111111; + } - // If the dot product between any vertex of a primitive and the normal is positive, then the primitive + // If the dot product between any vertex of a primitive and the normal is negative, then the primitive // is front facing, and considered "visible". // // Since we only need to perform a dot product against *one* vertex of the primitive, we simply use the @@ -202,32 +207,35 @@ private static int getVisibleFaces() { // However, Minecraft is not a reasonable game, and cube normals are defined arbitrarily. Worse yet, the faces // -X and +X also have their normals flipped when the cuboid is using "mirrored" texturing. So we have to // completely ignore the normals Minecraft *normally* uses for rendering (which affects lighting) and use the - // "true" normals here. This is why you don't see (cuboid.mirror ? CUBE_NORMALS_MIRRORED : CUBE_NORMALS) used. - // - // The vertex used for each face in the comparisons below will depend on whether the indices for CUBE_NORMAL[face] - // contain either the minimum or maximum vertex. - if (min.dot(CUBE_NORMALS[FACE_NEG_Y]) > 0.0f) { + // "true" normals here. + + final var min = CUBE_CORNERS[VERTEX_X1_Y1_Z1]; + final var max = CUBE_CORNERS[VERTEX_X2_Y2_Z2]; + + int faces = 0; + + if (min.dot(FACE_NORMALS[FACE_NEG_Y]) < 0.0f) { faces |= 1 << FACE_NEG_Y; } - if (max.dot(CUBE_NORMALS[FACE_POS_Y]) > 0.0f) { + if (max.dot(FACE_NORMALS[FACE_POS_Y]) < 0.0f) { faces |= 1 << FACE_POS_Y; } - if (min.dot(CUBE_NORMALS[FACE_NEG_Z]) > 0.0f) { + if (min.dot(FACE_NORMALS[FACE_NEG_Z]) < 0.0f) { faces |= 1 << FACE_NEG_Z; } - if (min.dot(CUBE_NORMALS[FACE_POS_X]) > 0.0f) { - faces |= 1 << FACE_POS_X; + if (max.dot(FACE_NORMALS[FACE_POS_Z]) < 0.0f) { + faces |= 1 << FACE_POS_Z; } - if (max.dot(CUBE_NORMALS[FACE_NEG_X]) > 0.0f) { - faces |= 1 << FACE_NEG_X; + if (min.dot(FACE_NORMALS[FACE_POS_X]) < 0.0f) { + faces |= 1 << FACE_POS_X; } - if (max.dot(CUBE_NORMALS[FACE_POS_Z]) > 0.0f) { - faces |= 1 << FACE_POS_Z; + if (max.dot(FACE_NORMALS[FACE_NEG_X]) < 0.0f) { + faces |= 1 << FACE_NEG_X; } return faces; @@ -252,14 +260,35 @@ private static void prepareVertices(MatrixStack.Entry matrices, ModelCuboid cubo } private static void prepareNormals(MatrixStack.Entry matrices) { - MatrixHelper.transformNormal(CUBE_NORMALS[FACE_NEG_Y], matrices.getNormalMatrix(), Direction.DOWN); - MatrixHelper.transformNormal(CUBE_NORMALS[FACE_POS_Y], matrices.getNormalMatrix(), Direction.UP); - MatrixHelper.transformNormal(CUBE_NORMALS[FACE_NEG_Z], matrices.getNormalMatrix(), Direction.NORTH); - MatrixHelper.transformNormal(CUBE_NORMALS[FACE_POS_Z], matrices.getNormalMatrix(), Direction.SOUTH); - MatrixHelper.transformNormal(CUBE_NORMALS[FACE_POS_X], matrices.getNormalMatrix(), Direction.WEST); - MatrixHelper.transformNormal(CUBE_NORMALS[FACE_NEG_X], matrices.getNormalMatrix(), Direction.EAST); + LIGHT_NORMALS[FACE_NEG_Y] = MatrixHelper.transformNormal(matrices.getNormalMatrix(), Direction.DOWN); + LIGHT_NORMALS[FACE_POS_Y] = MatrixHelper.transformNormal(matrices.getNormalMatrix(), Direction.UP); + LIGHT_NORMALS[FACE_NEG_Z] = MatrixHelper.transformNormal(matrices.getNormalMatrix(), Direction.NORTH); + LIGHT_NORMALS[FACE_POS_Z] = MatrixHelper.transformNormal(matrices.getNormalMatrix(), Direction.SOUTH); + LIGHT_NORMALS[FACE_POS_X] = MatrixHelper.transformNormal(matrices.getNormalMatrix(), Direction.WEST); + LIGHT_NORMALS[FACE_NEG_X] = MatrixHelper.transformNormal(matrices.getNormalMatrix(), Direction.EAST); + + // When mirroring is used, the light normals for EAST and WEST are swapped. + LIGHT_NORMALS_MIRRORED[FACE_NEG_Y] = LIGHT_NORMALS[FACE_NEG_Y]; + LIGHT_NORMALS_MIRRORED[FACE_POS_Y] = LIGHT_NORMALS[FACE_POS_Y]; + LIGHT_NORMALS_MIRRORED[FACE_NEG_Z] = LIGHT_NORMALS[FACE_NEG_Z]; + LIGHT_NORMALS_MIRRORED[FACE_POS_Z] = LIGHT_NORMALS[FACE_POS_Z]; + LIGHT_NORMALS_MIRRORED[FACE_POS_X] = LIGHT_NORMALS[FACE_NEG_X]; // mirrored + LIGHT_NORMALS_MIRRORED[FACE_NEG_X] = LIGHT_NORMALS[FACE_POS_X]; // mirrored + + // The normal matrix given to us is not useful, because Minecraft modifies it in all kinds of arbitrary ways + // to adjust lighting. We need *actual* surface normals, not whatever Minecraft is treating it as. + matrices.getPositionMatrix() + .normal(FACE_NORMAL_MATRIX); + + MatrixHelper.transformNormal(FACE_NORMALS[FACE_NEG_Y], FACE_NORMAL_MATRIX, Direction.DOWN); + MatrixHelper.transformNormal(FACE_NORMALS[FACE_POS_Y], FACE_NORMAL_MATRIX, Direction.UP); + MatrixHelper.transformNormal(FACE_NORMALS[FACE_NEG_Z], FACE_NORMAL_MATRIX, Direction.NORTH); + MatrixHelper.transformNormal(FACE_NORMALS[FACE_POS_Z], FACE_NORMAL_MATRIX, Direction.SOUTH); + MatrixHelper.transformNormal(FACE_NORMALS[FACE_NEG_X], FACE_NORMAL_MATRIX, Direction.EAST); + MatrixHelper.transformNormal(FACE_NORMALS[FACE_POS_X], FACE_NORMAL_MATRIX, Direction.WEST); } + private static void buildVertexPosition(Vector3f vector, float x, float y, float z, Matrix4f matrix) { vector.x = MatrixHelper.transformPositionX(matrix, x, y, z); vector.y = MatrixHelper.transformPositionY(matrix, x, y, z);