package com.tiedup.remake.client.gltf; import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.blaze3d.vertex.VertexFormat; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderStateShard; import net.minecraft.client.renderer.RenderType; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import org.joml.Matrix3f; import org.joml.Matrix4f; import org.joml.Vector4f; /** * Submits CPU-skinned glTF mesh vertices to Minecraft's rendering pipeline. * Uses TRIANGLES mode RenderType (same pattern as ObjModelRenderer). */ @OnlyIn(Dist.CLIENT) public final class GltfMeshRenderer extends RenderStateShard { private static final ResourceLocation WHITE_TEXTURE = ResourceLocation.fromNamespaceAndPath( "tiedup", "models/obj/shared/white.png" ); /** Cached default RenderType (white texture). Created once, reused every frame. */ private static RenderType cachedDefaultRenderType; /** Cache for texture-specific RenderTypes, keyed by ResourceLocation. */ private static final Map RENDER_TYPE_CACHE = new ConcurrentHashMap<>(); private GltfMeshRenderer() { super("tiedup_gltf_renderer", () -> {}, () -> {}); } /** * Get the default TRIANGLES-mode RenderType (white texture), creating it once if needed. */ private static RenderType getDefaultRenderType() { if (cachedDefaultRenderType == null) { cachedDefaultRenderType = createTriangleRenderType(WHITE_TEXTURE); } return cachedDefaultRenderType; } /** * Public accessor for the default RenderType (white texture). * Used by external renderers that need the same RenderType for tinted rendering. */ public static RenderType getRenderTypeForDefaultTexture() { return getDefaultRenderType(); } /** * Get a RenderType for a specific texture, caching it for reuse. * * @param texture the texture ResourceLocation * @return the cached or newly created RenderType */ private static RenderType getRenderTypeForTexture( ResourceLocation texture ) { return RENDER_TYPE_CACHE.computeIfAbsent( texture, GltfMeshRenderer::createTriangleRenderType ); } /** * Create a TRIANGLES-mode RenderType for glTF mesh rendering with the given texture. */ private static RenderType createTriangleRenderType( ResourceLocation texture ) { RenderType.CompositeState state = RenderType.CompositeState.builder() .setShaderState(RENDERTYPE_ENTITY_CUTOUT_NO_CULL_SHADER) .setTextureState( new RenderStateShard.TextureStateShard(texture, false, false) ) .setTransparencyState(NO_TRANSPARENCY) .setCullState(NO_CULL) .setLightmapState(LIGHTMAP) .setOverlayState(OVERLAY) .createCompositeState(true); return RenderType.create( "tiedup_gltf_triangles", DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.TRIANGLES, 256 * 1024, true, false, state ); } /** * Clear cached RenderTypes. Call on resource reload so that re-exported * textures are picked up without restarting the game. */ public static void clearRenderTypeCache() { cachedDefaultRenderType = null; RENDER_TYPE_CACHE.clear(); } /** * Render a skinned glTF mesh using the default white texture. * * @param data parsed glTF data * @param jointMatrices computed joint matrices from skinning engine * @param poseStack current pose stack * @param buffer multi-buffer source * @param packedLight packed light value * @param packedOverlay packed overlay value */ public static void renderSkinned( GltfData data, Matrix4f[] jointMatrices, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay ) { renderSkinnedInternal( data, jointMatrices, poseStack, buffer, packedLight, packedOverlay, getDefaultRenderType() ); } /** * Render a skinned glTF mesh using a custom texture. * * @param data parsed glTF data * @param jointMatrices computed joint matrices from skinning engine * @param poseStack current pose stack * @param buffer multi-buffer source * @param packedLight packed light value * @param packedOverlay packed overlay value * @param texture the texture to use for rendering */ public static void renderSkinned( GltfData data, Matrix4f[] jointMatrices, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay, ResourceLocation texture ) { renderSkinnedInternal( data, jointMatrices, poseStack, buffer, packedLight, packedOverlay, getRenderTypeForTexture(texture) ); } /** * Internal rendering implementation shared by both overloads. */ private static void renderSkinnedInternal( GltfData data, Matrix4f[] jointMatrices, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay, RenderType renderType ) { Matrix4f pose = poseStack.last().pose(); Matrix3f normalMat = poseStack.last().normal(); VertexConsumer vc = buffer.getBuffer(renderType); int[] indices = data.indices(); float[] texCoords = data.texCoords(); float[] outPos = new float[3]; float[] outNormal = new float[3]; // Pre-allocate scratch vectors outside the loop to avoid per-vertex allocations Vector4f tmpPos = new Vector4f(); Vector4f tmpNorm = new Vector4f(); for (int idx : indices) { // Skin this vertex GltfSkinningEngine.skinVertex( data, idx, jointMatrices, outPos, outNormal, tmpPos, tmpNorm ); // UV coordinates float u = texCoords[idx * 2]; float v = texCoords[idx * 2 + 1]; vc .vertex(pose, outPos[0], outPos[1], outPos[2]) .color(255, 255, 255, 255) .uv(u, 1.0f - v) .overlayCoords(packedOverlay) .uv2(packedLight) .normal(normalMat, outNormal[0], outNormal[1], outNormal[2]) .endVertex(); } } /** * Render a skinned glTF mesh with per-primitive tint colors. * *

Each primitive in the mesh is checked against the tintColors map. * If a primitive is tintable and its channel is present in the map, * the corresponding RGB color is applied as vertex color (multiplied * against the texture by the {@code rendertype_entity_cutout_no_cull} shader). * Non-tintable primitives render with white (no tint).

* *

This is a single VertexConsumer stream — all primitives share the * same RenderType and draw call, only the vertex color differs per range.

* * @param data parsed glTF data (must have primitives) * @param jointMatrices computed joint matrices from skinning engine * @param poseStack current pose stack * @param buffer multi-buffer source * @param packedLight packed light value * @param packedOverlay packed overlay value * @param renderType the RenderType to use * @param tintColors channel name to RGB int (0xRRGGBB); empty map = white everywhere */ public static void renderSkinnedTinted( GltfData data, Matrix4f[] jointMatrices, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay, RenderType renderType, Map tintColors ) { Matrix4f pose = poseStack.last().pose(); Matrix3f normalMat = poseStack.last().normal(); VertexConsumer vc = buffer.getBuffer(renderType); float[] texCoords = data.texCoords(); float[] outPos = new float[3]; float[] outNormal = new float[3]; Vector4f tmpPos = new Vector4f(); Vector4f tmpNorm = new Vector4f(); List primitives = data.primitives(); for (GltfData.Primitive prim : primitives) { // Determine color for this primitive int r = 255, g = 255, b = 255; if (prim.tintable() && prim.tintChannel() != null) { Integer colorInt = tintColors.get(prim.tintChannel()); if (colorInt != null) { r = (colorInt >> 16) & 0xFF; g = (colorInt >> 8) & 0xFF; b = colorInt & 0xFF; } } for (int idx : prim.indices()) { GltfSkinningEngine.skinVertex( data, idx, jointMatrices, outPos, outNormal, tmpPos, tmpNorm ); float u = texCoords[idx * 2]; float v = texCoords[idx * 2 + 1]; vc .vertex(pose, outPos[0], outPos[1], outPos[2]) .color(r, g, b, 255) .uv(u, 1.0f - v) .overlayCoords(packedOverlay) .uv2(packedLight) .normal(normalMat, outNormal[0], outNormal[1], outNormal[2]) .endVertex(); } } } }