Obj модель forge 1.20.1

Версия Minecraft
1.20.1
API
Forge
Здравствуйте! Хотел сделать броню и все вроде шло достаточно не плохо , но после того как я отрисовал броню и зашел в игру я заметил что броня поломана , имеет какието артефакты и дырки(фото как это выглядит отправил) , можете подсказать как это пофиксить?
ObjArmorRenderer:
package com.example.examplemod.client.renderer.armor;

import com.example.examplemod.Items.ItemArmorObj;
import com.google.common.collect.ImmutableMap;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import org.joml.Vector3f;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class ObjArmorRenderer extends HumanoidModel<LivingEntity> {

    private static final Map<String, ObjModel> MODEL_CACHE = new HashMap<>();
    private static final Vector3f DEFAULT_TEXCOORD = new Vector3f(0, 0, 0);
    private static final Vector3f DEFAULT_NORMAL = new Vector3f(0, 1, 0);

    private final ModelPart root;
    private ObjModel currentModel;
    private ResourceLocation textureLocation;
    private ItemArmorObj currentArmorItem;

    private static final Map<Item, Map<String, ModelPart>> PART_MAP_CACHE = new HashMap<>();

    public ObjArmorRenderer(ModelPart root) {
        super(root);
        this.root = root;
    }

    private Map<String, ModelPart> getPartMap(ItemArmorObj item) {
        return PART_MAP_CACHE.computeIfAbsent(item, key ->
                ImmutableMap.<String, ModelPart>builder()
                        .put(item.Head(), this.root.getChild("head"))
                        .put(item.Body(), this.root.getChild("body"))
                        .put(item.RightArm(), this.root.getChild("right_arm"))
                        .put(item.LeftArm(), this.root.getChild("left_arm"))
                        .put(item.RightLeg(), this.root.getChild("right_leg"))
                        .put(item.LeftLeg(), this.root.getChild("left_leg"))
                        .build()
        );
    }

    @Override
    public void setupAnim(LivingEntity entity, float limbSwing, float limbSwingAmount,
                          float ageInTicks, float netHeadYaw, float headPitch) {

        if (currentArmorItem == null) return;

        super.setupAnim(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch);

        Map<String, ModelPart> partMap = getPartMap(currentArmorItem);

        partMap.get(currentArmorItem.Head()).copyFrom(this.head);
        partMap.get(currentArmorItem.Body()).copyFrom(this.body);
        partMap.get(currentArmorItem.RightArm()).copyFrom(this.rightArm);
        partMap.get(currentArmorItem.LeftArm()).copyFrom(this.leftArm);
        partMap.get(currentArmorItem.RightLeg()).copyFrom(this.rightLeg);
        partMap.get(currentArmorItem.LeftLeg()).copyFrom(this.leftLeg);
    }

    public void prepForRender(LivingEntity entity, ItemStack stack) {
        if (!(stack.getItem() instanceof ItemArmorObj armor)) return;

        this.currentArmorItem = armor;

        this.setupAnim(
                entity,
                entity.walkAnimation.position(),
                entity.walkAnimation.speed(),
                entity.tickCount,
                entity.yHeadRot,
                entity.getXRot()
        );

        String modelPath = armor.getModelPath();

        this.textureLocation = ResourceLocation.fromNamespaceAndPath(
                armor.getCreatorModId(stack),
                modelPath.replace("models/armor/", "textures/armor/").replace(".obj", ".png")
        );

        ResourceLocation modelLocation = ResourceLocation.fromNamespaceAndPath(
                armor.getCreatorModId(stack),
                modelPath
        );

        this.currentModel = MODEL_CACHE.computeIfAbsent(modelLocation.toString(), k -> {
            try {
                return loadModel(modelLocation);
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        });
    }

    @Override
    public void renderToBuffer(
            PoseStack poseStack,
            VertexConsumer vertexConsumer,
            int packedLight,
            int packedOverlay,
            float red,
            float green,
            float blue,
            float alpha
    ) {
        if (currentModel == null || currentArmorItem == null || textureLocation == null) return;

        VertexConsumer consumer =
                Minecraft.getInstance()
                        .renderBuffers()
                        .bufferSource()
                        .getBuffer(RenderType.entityCutout(textureLocation));

        Map<String, ModelPart> partMap = getPartMap(currentArmorItem);

        for (Map.Entry<String, ModelPart> entry : partMap.entrySet()) {
            List<ObjModel.Triangle> tris = currentModel.groups.get(entry.getKey());
            if (tris == null || tris.isEmpty()) continue;

            renderGroup(
                    entry.getValue(),
                    entry.getKey(),
                    tris,
                    poseStack,
                    consumer,
                    packedLight,
                    packedOverlay,
                    red, green, blue, alpha
            );
        }
    }

    private void renderGroup(
            ModelPart part,
            String group,
            List<ObjModel.Triangle> tris,
            PoseStack poseStack,
            VertexConsumer consumer,
            int light,
            int overlay,
            float r, float g, float b, float a
    ) {
        poseStack.pushPose();
        part.translateAndRotate(poseStack);

        poseStack.scale(1.1F, 1.1F, 1.1F);
        poseStack.mulPose(Axis.YP.rotationDegrees(180.0F));
        poseStack.mulPose(Axis.XP.rotationDegrees(180.0F));
        poseStack.translate(0.0F, -1.4F, 0.0F);

        if (group.equals(currentArmorItem.RightArm())) poseStack.translate(-0.25F, 0, 0);
        else if (group.equals(currentArmorItem.LeftArm())) poseStack.translate(0.25F, 0, 0);
        else if (group.equals(currentArmorItem.RightLeg())) poseStack.translate(-0.1F, 0.75F, 0);
        else if (group.equals(currentArmorItem.LeftLeg())) poseStack.translate(0.1F, 0.75F, 0);

        PoseStack.Pose pose = poseStack.last();

        for (ObjModel.Triangle t : tris) {
            for (int i = 0; i < 3; i++) {
                Vector3f v = t.vertices[i];
                Vector3f uv = t.texCoords[i];

                consumer.vertex(pose.pose(), v.x(), v.y(), v.z())
                        .color(r, g, b, a)
                        .uv(uv.x(), 1.0F - uv.y())
                        .overlayCoords(overlay)
                        .uv2(light)
                        .normal(pose.normal(), 0.0F, 1.0F, 0.0F)
                        .endVertex();
            }
        }

        poseStack.popPose();
    }

    private ObjModel loadModel(ResourceLocation loc) throws IOException {
        ObjModel model = new ObjModel();

        Resource res = Minecraft.getInstance()
                .getResourceManager()
                .getResource(loc)
                .orElseThrow();

        try (BufferedReader br = new BufferedReader(new InputStreamReader(res.open()))) {
            String group = "default";
            String line;

            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (line.isEmpty() || line.startsWith("#")) continue;

                String[] p = line.split("\\s+");


                switch (p[0]) {
                    case "v" -> model.vertices.add(new Vector3f(
                            Float.parseFloat(p[1]),
                            Float.parseFloat(p[2]),
                            Float.parseFloat(p[3])
                    ));
                    case "vt" -> model.texCoords.add(new Vector3f(
                            Float.parseFloat(p[1]),
                            Float.parseFloat(p[2]),
                            0
                    ));
                    case "vn" -> model.normals.add(new Vector3f(
                            Float.parseFloat(p[1]),
                            Float.parseFloat(p[2]),
                            Float.parseFloat(p[3])
                    ));
                    case "g", "o" -> group = p[1];
                    case "f" -> parseFace(model, group, p);
                }
            }
        }
        return model;
    }

    private void parseFace(ObjModel model, String group, String[] parts) {
        List<ObjModel.Triangle> list =
                model.groups.computeIfAbsent(group, k -> new ArrayList<>());

        int[][] idx = new int[parts.length - 1][3];

        for (int i = 1; i < parts.length; i++) {
            String[] f = parts[i].split("/");
            idx[i - 1][0] = Integer.parseInt(f[0]) - 1;
            idx[i - 1][1] = f.length > 1 && !f[1].isEmpty() ? Integer.parseInt(f[1]) - 1 : -1;
        }

        for (int i = 1; i < idx.length - 1; i++) {
            ObjModel.Triangle t = new ObjModel.Triangle();

            t.vertices[0] = new Vector3f(model.vertices.get(idx[0][0]));
            t.vertices[1] = new Vector3f(model.vertices.get(idx[i][0]));
            t.vertices[2] = new Vector3f(model.vertices.get(idx[i + 1][0]));

            t.texCoords[0] = idx[0][1] >= 0 ? new Vector3f(model.texCoords.get(idx[0][1])) : DEFAULT_TEXCOORD;
            t.texCoords[1] = idx[i][1] >= 0 ? new Vector3f(model.texCoords.get(idx[i][1])) : DEFAULT_TEXCOORD;
            t.texCoords[2] = idx[i + 1][1] >= 0 ? new Vector3f(model.texCoords.get(idx[i + 1][1])) : DEFAULT_TEXCOORD;

            list.add(t);
        }
    }

    private static class ObjModel {
        List<Vector3f> vertices = new ArrayList<>();
        List<Vector3f> texCoords = new ArrayList<>();
        List<Vector3f> normals = new ArrayList<>();
        Map<String, List<Triangle>> groups = new HashMap<>();

        static class Triangle {
            Vector3f[] vertices = new Vector3f[3];
            Vector3f[] texCoords = new Vector3f[3];
        }
    }
}
2025-12-15_19.41.39.png2025-12-15_19.41.07.png
 
Решение
Такое бывает когда "потеряна" часть вершин при рендере когда рендеришь треугольниками (for (ObjModel.Triangle t : tris) {), а модели не трангулированы полностью (т.е. состоят не из треугольников, и в .obj файле там в строке 4 координаты, а не 3).
Сделай триангуляцию в том же Blender'е - загрузи модель, выдели все вершины (a в англ раскладке), Modelling -> Faces -> Triangulate faces и экспортируй модель, затем используй триангулированную модель

photo_2025-12-16_18-39-36.jpg
Такое бывает когда "потеряна" часть вершин при рендере когда рендеришь треугольниками (for (ObjModel.Triangle t : tris) {), а модели не трангулированы полностью (т.е. состоят не из треугольников, и в .obj файле там в строке 4 координаты, а не 3).
Сделай триангуляцию в том же Blender'е - загрузи модель, выдели все вершины (a в англ раскладке), Modelling -> Faces -> Triangulate faces и экспортируй модель, затем используй триангулированную модель

photo_2025-12-16_18-39-36.jpg
 
Назад
Сверху