package net.minecraft.client.renderer.block.model; import com.google.common.annotations.VisibleForTesting; import com.mojang.math.MatrixUtil; import com.mojang.math.Quadrant; import com.mojang.math.Transformation; import java.util.function.Consumer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.renderer.FaceInfo; import net.minecraft.client.renderer.FaceInfo.Constants; import net.minecraft.client.renderer.FaceInfo.VertexInfo; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.model.ModelState; import net.minecraft.core.Direction; import net.minecraft.util.Mth; import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; import org.joml.Matrix4fc; import org.joml.Quaternionf; import org.joml.Vector3f; import org.joml.Vector3fc; import org.joml.Vector4f; @Environment(EnvType.CLIENT) public class FaceBakery { public static final int VERTEX_INT_SIZE = 8; private static final float RESCALE_22_5 = 1.0F / (float)Math.cos((float) (Math.PI / 8)) - 1.0F; private static final float RESCALE_45 = 1.0F / (float)Math.cos((float) (Math.PI / 4)) - 1.0F; public static final int VERTEX_COUNT = 4; private static final int COLOR_INDEX = 3; public static final int UV_INDEX = 4; @VisibleForTesting static BlockElementFace.UVs defaultFaceUV(Vector3fc posFrom, Vector3fc posTo, Direction facing) { return switch (facing) { case DOWN -> new BlockElementFace.UVs(posFrom.x(), 16.0F - posTo.z(), posTo.x(), 16.0F - posFrom.z()); case UP -> new BlockElementFace.UVs(posFrom.x(), posFrom.z(), posTo.x(), posTo.z()); case NORTH -> new BlockElementFace.UVs(16.0F - posTo.x(), 16.0F - posTo.y(), 16.0F - posFrom.x(), 16.0F - posFrom.y()); case SOUTH -> new BlockElementFace.UVs(posFrom.x(), 16.0F - posTo.y(), posTo.x(), 16.0F - posFrom.y()); case WEST -> new BlockElementFace.UVs(posFrom.z(), 16.0F - posTo.y(), posTo.z(), 16.0F - posFrom.y()); case EAST -> new BlockElementFace.UVs(16.0F - posTo.z(), 16.0F - posTo.y(), 16.0F - posFrom.z(), 16.0F - posFrom.y()); }; } public static BakedQuad bakeQuad( Vector3fc posFrom, Vector3fc posTo, BlockElementFace face, TextureAtlasSprite sprite, Direction facing, ModelState modelState, @Nullable BlockElementRotation rotation, boolean shade, int lightEmission ) { BlockElementFace.UVs uVs = face.uvs(); if (uVs == null) { uVs = defaultFaceUV(posFrom, posTo, facing); } uVs = shrinkUVs(sprite, uVs); Matrix4fc matrix4fc = modelState.inverseFaceTransformation(facing); int[] is = makeVertices(uVs, face.rotation(), matrix4fc, sprite, facing, setupShape(posFrom, posTo), modelState.transformation(), rotation); Direction direction = calculateFacing(is); if (rotation == null) { recalculateWinding(is, direction); } return new BakedQuad(is, face.tintIndex(), direction, sprite, shade, lightEmission); } private static BlockElementFace.UVs shrinkUVs(TextureAtlasSprite sprite, BlockElementFace.UVs uvs) { float f = uvs.minU(); float g = uvs.minV(); float h = uvs.maxU(); float i = uvs.maxV(); float j = sprite.uvShrinkRatio(); float k = (f + f + h + h) / 4.0F; float l = (g + g + i + i) / 4.0F; return new BlockElementFace.UVs(Mth.lerp(j, f, k), Mth.lerp(j, g, l), Mth.lerp(j, h, k), Mth.lerp(j, i, l)); } private static int[] makeVertices( BlockElementFace.UVs uvs, Quadrant rotation, Matrix4fc inverseFaceTransform, TextureAtlasSprite sprite, Direction facing, float[] shape, Transformation transformation, @Nullable BlockElementRotation partRotation ) { FaceInfo faceInfo = FaceInfo.fromFacing(facing); int[] is = new int[32]; for (int i = 0; i < 4; i++) { bakeVertex(is, i, faceInfo, uvs, rotation, inverseFaceTransform, shape, sprite, transformation, partRotation); } return is; } private static float[] setupShape(Vector3fc posFrom, Vector3fc posTo) { float[] fs = new float[Direction.values().length]; fs[Constants.MIN_X] = posFrom.x() / 16.0F; fs[Constants.MIN_Y] = posFrom.y() / 16.0F; fs[Constants.MIN_Z] = posFrom.z() / 16.0F; fs[Constants.MAX_X] = posTo.x() / 16.0F; fs[Constants.MAX_Y] = posTo.y() / 16.0F; fs[Constants.MAX_Z] = posTo.z() / 16.0F; return fs; } private static void bakeVertex( int[] vertexData, int vertexIndex, FaceInfo faceInfo, BlockElementFace.UVs uvs, Quadrant rotation, Matrix4fc inverseFaceTransform, float[] shape, TextureAtlasSprite sprite, Transformation transformation, @Nullable BlockElementRotation partRotation ) { VertexInfo vertexInfo = faceInfo.getVertexInfo(vertexIndex); Vector3f vector3f = new Vector3f(shape[vertexInfo.xFace], shape[vertexInfo.yFace], shape[vertexInfo.zFace]); applyElementRotation(vector3f, partRotation); applyModelRotation(vector3f, transformation); float f = BlockElementFace.getU(uvs, rotation, vertexIndex); float g = BlockElementFace.getV(uvs, rotation, vertexIndex); float i; float h; if (MatrixUtil.isIdentity(inverseFaceTransform)) { h = f; i = g; } else { Vector3f vector3f2 = inverseFaceTransform.transformPosition(new Vector3f(cornerToCenter(f), cornerToCenter(g), 0.0F)); h = centerToCorner(vector3f2.x); i = centerToCorner(vector3f2.y); } fillVertex(vertexData, vertexIndex, vector3f, sprite, h, i); } private static float cornerToCenter(float coord) { return coord - 0.5F; } private static float centerToCorner(float coord) { return coord + 0.5F; } private static void fillVertex(int[] vertexData, int vertexIndex, Vector3f pos, TextureAtlasSprite sprite, float u, float v) { int i = vertexIndex * 8; vertexData[i] = Float.floatToRawIntBits(pos.x()); vertexData[i + 1] = Float.floatToRawIntBits(pos.y()); vertexData[i + 2] = Float.floatToRawIntBits(pos.z()); vertexData[i + 3] = -1; vertexData[i + 4] = Float.floatToRawIntBits(sprite.getU(u)); vertexData[i + 4 + 1] = Float.floatToRawIntBits(sprite.getV(v)); } private static void applyElementRotation(Vector3f vec, @Nullable BlockElementRotation partRotation) { if (partRotation != null) { Vector3f vector3f; Vector3f vector3f2; switch (partRotation.axis()) { case X: vector3f = new Vector3f(1.0F, 0.0F, 0.0F); vector3f2 = new Vector3f(0.0F, 1.0F, 1.0F); break; case Y: vector3f = new Vector3f(0.0F, 1.0F, 0.0F); vector3f2 = new Vector3f(1.0F, 0.0F, 1.0F); break; case Z: vector3f = new Vector3f(0.0F, 0.0F, 1.0F); vector3f2 = new Vector3f(1.0F, 1.0F, 0.0F); break; default: throw new IllegalArgumentException("There are only 3 axes"); } Quaternionf quaternionf = new Quaternionf().rotationAxis(partRotation.angle() * (float) (Math.PI / 180.0), vector3f); if (partRotation.rescale()) { if (Math.abs(partRotation.angle()) == 22.5F) { vector3f2.mul(RESCALE_22_5); } else { vector3f2.mul(RESCALE_45); } vector3f2.add(1.0F, 1.0F, 1.0F); } else { vector3f2.set(1.0F, 1.0F, 1.0F); } rotateVertexBy(vec, new Vector3f(partRotation.origin()), new Matrix4f().rotation(quaternionf), vector3f2); } } private static void applyModelRotation(Vector3f pos, Transformation transform) { if (transform != Transformation.identity()) { rotateVertexBy(pos, new Vector3f(0.5F, 0.5F, 0.5F), transform.getMatrix(), new Vector3f(1.0F, 1.0F, 1.0F)); } } private static void rotateVertexBy(Vector3f pos, Vector3fc origin, Matrix4fc transform, Vector3fc scale) { Vector4f vector4f = transform.transform(new Vector4f(pos.x() - origin.x(), pos.y() - origin.y(), pos.z() - origin.z(), 1.0F)); vector4f.mul(new Vector4f(scale, 1.0F)); pos.set(vector4f.x() + origin.x(), vector4f.y() + origin.y(), vector4f.z() + origin.z()); } private static Direction calculateFacing(int[] faceData) { Vector3f vector3f = vectorFromData(faceData, 0); Vector3f vector3f2 = vectorFromData(faceData, 8); Vector3f vector3f3 = vectorFromData(faceData, 16); Vector3f vector3f4 = new Vector3f(vector3f).sub(vector3f2); Vector3f vector3f5 = new Vector3f(vector3f3).sub(vector3f2); Vector3f vector3f6 = new Vector3f(vector3f5).cross(vector3f4).normalize(); if (!vector3f6.isFinite()) { return Direction.UP; } else { Direction direction = null; float f = 0.0F; for (Direction direction2 : Direction.values()) { float g = vector3f6.dot(direction2.getUnitVec3f()); if (g >= 0.0F && g > f) { f = g; direction = direction2; } } return direction == null ? Direction.UP : direction; } } private static float xFromData(int[] faceData, int index) { return Float.intBitsToFloat(faceData[index]); } private static float yFromData(int[] faceData, int index) { return Float.intBitsToFloat(faceData[index + 1]); } private static float zFromData(int[] faceData, int index) { return Float.intBitsToFloat(faceData[index + 2]); } private static Vector3f vectorFromData(int[] faceData, int index) { return new Vector3f(xFromData(faceData, index), yFromData(faceData, index), zFromData(faceData, index)); } private static void recalculateWinding(int[] vertices, Direction direction) { int[] is = new int[vertices.length]; System.arraycopy(vertices, 0, is, 0, vertices.length); float[] fs = new float[Direction.values().length]; fs[Constants.MIN_X] = 999.0F; fs[Constants.MIN_Y] = 999.0F; fs[Constants.MIN_Z] = 999.0F; fs[Constants.MAX_X] = -999.0F; fs[Constants.MAX_Y] = -999.0F; fs[Constants.MAX_Z] = -999.0F; for (int i = 0; i < 4; i++) { int j = 8 * i; float f = xFromData(is, j); float g = yFromData(is, j); float h = zFromData(is, j); if (f < fs[Constants.MIN_X]) { fs[Constants.MIN_X] = f; } if (g < fs[Constants.MIN_Y]) { fs[Constants.MIN_Y] = g; } if (h < fs[Constants.MIN_Z]) { fs[Constants.MIN_Z] = h; } if (f > fs[Constants.MAX_X]) { fs[Constants.MAX_X] = f; } if (g > fs[Constants.MAX_Y]) { fs[Constants.MAX_Y] = g; } if (h > fs[Constants.MAX_Z]) { fs[Constants.MAX_Z] = h; } } FaceInfo faceInfo = FaceInfo.fromFacing(direction); for (int jx = 0; jx < 4; jx++) { int k = 8 * jx; VertexInfo vertexInfo = faceInfo.getVertexInfo(jx); float hx = fs[vertexInfo.xFace]; float l = fs[vertexInfo.yFace]; float m = fs[vertexInfo.zFace]; vertices[k] = Float.floatToRawIntBits(hx); vertices[k + 1] = Float.floatToRawIntBits(l); vertices[k + 2] = Float.floatToRawIntBits(m); for (int n = 0; n < 4; n++) { int o = 8 * n; float p = xFromData(is, o); float q = yFromData(is, o); float r = zFromData(is, o); if (Mth.equal(hx, p) && Mth.equal(l, q) && Mth.equal(m, r)) { vertices[k + 4] = is[o + 4]; vertices[k + 4 + 1] = is[o + 4 + 1]; } } } } public static void extractPositions(int[] faceData, Consumer output) { for (int i = 0; i < 4; i++) { output.accept(vectorFromData(faceData, 8 * i)); } } }