From 3aec6814367b1b4e9ad9a2f00e4cd759c484e5e0 Mon Sep 17 00:00:00 2001 From: notevil Date: Wed, 22 Apr 2026 20:59:32 +0200 Subject: [PATCH] =?UTF-8?q?Phase=202.2=20:=20fork=20mesh/transformer/=20(v?= =?UTF-8?q?anilla=20PlayerModel=20=E2=86=92=20SkinnedMesh)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit COPY verbatim EF + rewrite imports : - rig/mesh/transformer/VanillaModelTransformer.java (~700 LOC) Transformer principal : bake PlayerModel vanilla / HumanoidModel → SkinnedMesh biped EF. Utilise les PartTransformer (HEAD/ARM/LEG/CHEST) avec AABB cover area. - rig/mesh/transformer/HumanoidModelTransformer.java (~70 LOC) Base abstract commune aux transformers humanoïdes. - rig/mesh/transformer/HumanoidModelBaker.java (~115 LOC) Entry point bake() + export JSON + registry MODEL_TRANSFORMERS. L'ancienne stub de VanillaMeshPartDefinition (record 55 LOC) est remplacée par la vraie record dans le fork — API identique (of(partName), of(partName, path, invertedParentTransform, root)). Ajouté mixin accessor : - rig/mixin/client/MixinAgeableListModel.java (@Invoker pour headParts/bodyParts sur AgeableListModel). - src/main/resources/tiedup-rig.mixins.json (nouveau mixin config, package com.tiedup.remake.rig.mixin). - build.gradle : args '-mixin.config=tiedup-rig.mixins.json' dans client+server run configs. - META-INF/mods.toml : [[mixins]] config="tiedup-rig.mixins.json" Logger EpicFightMod.LOGGER → TiedUpRigConstants.LOGGER dans HumanoidModelBaker. Packages correctement rewrités par scripts/rig-rewrite-imports.sh. Compile BUILD SUCCESSFUL maintenu. --- build.gradle | 2 + .../mesh/transformer/HumanoidModelBaker.java | 114 +++ .../transformer/HumanoidModelTransformer.java | 82 ++ .../transformer/VanillaModelTransformer.java | 757 +++++++++++++++++- .../mixin/client/MixinAgeableListModel.java | 29 + src/main/resources/META-INF/mods.toml | 3 + src/main/resources/tiedup-rig.mixins.json | 14 + 7 files changed, 972 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/tiedup/remake/rig/mesh/transformer/HumanoidModelBaker.java create mode 100644 src/main/java/com/tiedup/remake/rig/mesh/transformer/HumanoidModelTransformer.java create mode 100644 src/main/java/com/tiedup/remake/rig/mixin/client/MixinAgeableListModel.java create mode 100644 src/main/resources/tiedup-rig.mixins.json diff --git a/build.gradle b/build.gradle index 0d0a235..f94ed8b 100644 --- a/build.gradle +++ b/build.gradle @@ -105,6 +105,7 @@ minecraft { // Mixin config arg args '-mixin.config=tiedup.mixins.json' args '-mixin.config=tiedup-compat.mixins.json' + args '-mixin.config=tiedup-rig.mixins.json' } server { @@ -118,6 +119,7 @@ minecraft { // Mixin config arg args '-mixin.config=tiedup.mixins.json' args '-mixin.config=tiedup-compat.mixins.json' + args '-mixin.config=tiedup-rig.mixins.json' } // Additional client instances for multiplayer testing diff --git a/src/main/java/com/tiedup/remake/rig/mesh/transformer/HumanoidModelBaker.java b/src/main/java/com/tiedup/remake/rig/mesh/transformer/HumanoidModelBaker.java new file mode 100644 index 0000000..86f72e5 --- /dev/null +++ b/src/main/java/com/tiedup/remake/rig/mesh/transformer/HumanoidModelBaker.java @@ -0,0 +1,114 @@ +/* + * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight) + * by the Epic Fight Team, licensed under GPLv3. + * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3. + */ + +package com.tiedup.remake.rig.mesh.transformer; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; + +import net.minecraft.SharedConstants; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.Model; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackType; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ArmorItem; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.registries.ForgeRegistries; +import com.tiedup.remake.rig.mesh.SkinnedMesh; +import com.tiedup.remake.rig.mesh.HumanoidMesh; +import com.tiedup.remake.rig.TiedUpRigConstants; + +public class HumanoidModelBaker { + static final Map BAKED_MODELS = Maps.newHashMap(); + static final List MODEL_TRANSFORMERS = Lists.newArrayList(); + + static final Set EXCEPTIONAL_MODELS = Sets.newHashSet(); + static final Set MODEL_PARTS = Sets.newHashSet(); + + public static final HumanoidModelTransformer VANILLA_TRANSFORMER = new VanillaModelTransformer(); + + public interface ModelProvider { + public Model get(LivingEntity entityLiving, ItemStack itemStack, EquipmentSlot slot, HumanoidModel _default); + } + + public static void registerNewTransformer(HumanoidModelTransformer transformer) { + MODEL_TRANSFORMERS.add(transformer); + } + + public static void exportModels(File resourcePackDirectory) throws IOException { + File zipFile = new File(resourcePackDirectory, "epicfight_custom_armors.zip"); + ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile)); + + for (Map.Entry entry : BAKED_MODELS.entrySet()) { + ZipEntry zipEntry = new ZipEntry(String.format("assets/%s/animmodels/armor/%s.json", entry.getKey().getNamespace(), entry.getKey().getPath())); + Gson gson = new GsonBuilder().create(); + out.putNextEntry(zipEntry); + out.write(gson.toJson(entry.getValue().toJsonObject()).getBytes()); + out.closeEntry(); + TiedUpRigConstants.LOGGER.info("Exported custom armor model : " + entry.getKey()); + } + + ZipEntry zipEntry = new ZipEntry("pack.mcmeta"); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + JsonObject root = new JsonObject(); + JsonObject pack = new JsonObject(); + pack.addProperty("description", "epicfight_custom_armor_models"); + pack.addProperty("pack_format", SharedConstants.getCurrentVersion().getPackVersion(PackType.CLIENT_RESOURCES)); + root.add("pack", pack); + out.putNextEntry(zipEntry); + out.write(gson.toJson(root).getBytes()); + out.closeEntry(); + out.close(); + } + + public static SkinnedMesh bakeArmor(LivingEntity entityLiving, ItemStack itemstack, ArmorItem armorItem, EquipmentSlot slot, HumanoidModel originalModel, Model forgeModel, HumanoidModel entityModel, HumanoidMesh entityMesh) { + SkinnedMesh skinnedArmorModel = null; + + if (!EXCEPTIONAL_MODELS.contains(armorItem)) { + if (forgeModel == originalModel || !(forgeModel instanceof HumanoidModel humanoidModel)) { + return entityMesh.getHumanoidArmorModel(slot).get(); + } + + for (HumanoidModelTransformer modelTransformer : MODEL_TRANSFORMERS) { + try { + skinnedArmorModel = modelTransformer.transformArmorModel(humanoidModel); + } catch (Exception e) { + TiedUpRigConstants.LOGGER.warn("Can't transform the model of " + ForgeRegistries.ITEMS.getKey(armorItem) + " because of :"); + e.printStackTrace(); + EXCEPTIONAL_MODELS.add(armorItem); + } + + if (skinnedArmorModel != null) { + break; + } + } + + if (skinnedArmorModel == null) { + skinnedArmorModel = VANILLA_TRANSFORMER.transformArmorModel(humanoidModel); + } + } + + BAKED_MODELS.put(ForgeRegistries.ITEMS.getKey(armorItem), skinnedArmorModel); + + return skinnedArmorModel; + } +} \ No newline at end of file diff --git a/src/main/java/com/tiedup/remake/rig/mesh/transformer/HumanoidModelTransformer.java b/src/main/java/com/tiedup/remake/rig/mesh/transformer/HumanoidModelTransformer.java new file mode 100644 index 0000000..e7c340d --- /dev/null +++ b/src/main/java/com/tiedup/remake/rig/mesh/transformer/HumanoidModelTransformer.java @@ -0,0 +1,82 @@ +/* + * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight) + * by the Epic Fight Team, licensed under GPLv3. + * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3. + */ + +package com.tiedup.remake.rig.mesh.transformer; + +import java.util.List; +import java.util.Map; + +import com.mojang.blaze3d.vertex.PoseStack; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.client.model.HumanoidModel; +import com.tiedup.remake.rig.mesh.MeshPartDefinition; +import com.tiedup.remake.rig.mesh.SingleGroupVertexBuilder; +import com.tiedup.remake.rig.mesh.SkinnedMesh; + +public abstract class HumanoidModelTransformer { + public abstract SkinnedMesh transformArmorModel(HumanoidModel humanoidModel); + + public static abstract class PartTransformer { + public abstract void bakeCube(PoseStack poseStack, MeshPartDefinition partDefinition, T cube, List vertices, Map indices, IndexCounter indexCounter); + + static void triangluatePolygon(Map indices, MeshPartDefinition partDefinition, IndexCounter indexCounter) { + IntList list = indices.computeIfAbsent(partDefinition, (key) -> new IntArrayList()); + + //Optimization: do not split vertices in a cube. + for (int i = 0; i < 3; i++) { + list.add(indexCounter.first()); + } + + for (int i = 0; i < 3; i++) { + list.add(indexCounter.second()); + } + + for (int i = 0; i < 3; i++) { + list.add(indexCounter.fourth()); + } + + for (int i = 0; i < 3; i++) { + list.add(indexCounter.fourth()); + } + + for (int i = 0; i < 3; i++) { + list.add(indexCounter.second()); + } + + for (int i = 0; i < 3; i++) { + list.add(indexCounter.third()); + } + + indexCounter.count(); + } + + public static class IndexCounter { + private int indexCounter = 0; + + private int first() { + return this.indexCounter; + } + + private int second() { + return this.indexCounter + 1; + } + + private int third() { + return this.indexCounter + 2; + } + + private int fourth() { + return this.indexCounter + 3; + } + + private void count() { + this.indexCounter += 4; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tiedup/remake/rig/mesh/transformer/VanillaModelTransformer.java b/src/main/java/com/tiedup/remake/rig/mesh/transformer/VanillaModelTransformer.java index aa34dba..bb78f81 100644 --- a/src/main/java/com/tiedup/remake/rig/mesh/transformer/VanillaModelTransformer.java +++ b/src/main/java/com/tiedup/remake/rig/mesh/transformer/VanillaModelTransformer.java @@ -6,50 +6,749 @@ package com.tiedup.remake.rig.mesh.transformer; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.function.Supplier; +import org.joml.Matrix4f; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.joml.Vector4f; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.mojang.blaze3d.vertex.PoseStack; + +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.client.model.HumanoidModel; import net.minecraft.client.model.geom.ModelPart; - -import com.tiedup.remake.rig.math.OpenMatrix4f; -import com.tiedup.remake.rig.mesh.Mesh; +import net.minecraft.client.model.geom.PartPose; +import net.minecraft.core.Direction; +import net.minecraft.core.Vec3i; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import com.tiedup.remake.rig.mesh.Mesh.RenderProperties; import com.tiedup.remake.rig.mesh.MeshPartDefinition; +import com.tiedup.remake.rig.mesh.SingleGroupVertexBuilder; +import com.tiedup.remake.rig.mesh.SkinnedMesh; +import com.tiedup.remake.rig.math.OpenMatrix4f; +import com.tiedup.remake.rig.math.QuaternionUtils; +import com.tiedup.remake.rig.math.Vec2f; +import com.tiedup.remake.rig.math.Vec3f; +import com.tiedup.remake.rig.mixin.client.MixinAgeableListModel; -/** - * Stub RIG Phase 0 — le transformer complet EF (HumanoidModel → SkinnedMesh - * runtime conversion) n'est pas porté : TiedUp utilise exclusivement GLB + - * JSON EF pour la définition des meshes. Seule la record - * {@link VanillaMeshPartDefinition} est conservée, car référencée par - * {@code JsonAssetLoader} pour instancier les parts nommées. - * - *

Phase 2 : décider si on fork le transformer pour garder la compat runtime - * avec les modèles vanilla (armor rendering), ou si on reroute vers GLB only.

- */ -public class VanillaModelTransformer { - - public record VanillaMeshPartDefinition( - String partName, - Mesh.RenderProperties renderProperties, - List path, - OpenMatrix4f invertedParentTransform, - ModelPart root - ) implements MeshPartDefinition { - - public static MeshPartDefinition of(String partName, Mesh.RenderProperties renderProperties) { +public class VanillaModelTransformer extends HumanoidModelTransformer { + public static final SimpleTransformer HEAD = new SimpleTransformer(AABB.ofSize(new Vec3(0.0D, -4.0D, 0.0D), 8.0D, 8.0D, 8.0D), 9); + public static final SimpleTransformer LEFT_FEET = new SimpleTransformer(AABB.ofSize(new Vec3(0.0D, -4.0D, 0.0D), 8.0D, 8.0D, 8.0D), 5); + public static final SimpleTransformer RIGHT_FEET = new SimpleTransformer(AABB.ofSize(new Vec3(0.0D, -4.0D, 0.0D), 8.0D, 8.0D, 8.0D), 2); + public static final LimbPartTransformer LEFT_ARM = new LimbPartTransformer(AABB.ofSize(new Vec3(1.0D, 6.0D, 0.0D), 4.0D, 12.0D, 4.0D), 16, 17, 19, 19.0F, false, AABB.ofSize(new Vec3(-6.0D, 18.0D, 0), 8.0D, 14.0D, 8.0D)); + public static final LimbPartTransformer RIGHT_ARM = new LimbPartTransformer(AABB.ofSize(new Vec3(-1.0D, 6.0D, 0.0D), 4.0D, 12.0D, 4.0D), 11, 12, 14, 19.0F, false, AABB.ofSize(new Vec3(6.0D, 18.0D, 0), 8.0D, 14.0D, 8.0D)); + public static final LimbPartTransformer LEFT_LEG = new LimbPartTransformer(AABB.ofSize(new Vec3(1.9D, 18.0D, 0.0D), 4.0D, 12.0D, 4.0D), 4, 5, 6, 6.0F, true, AABB.ofSize(new Vec3(-2.0D, 6.0D, 0), 8.0D, 14.0D, 8.0D)); + public static final LimbPartTransformer RIGHT_LEG = new LimbPartTransformer(AABB.ofSize(new Vec3(-1.9D, 18.0D, 0.0D), 4.0D, 12.0D, 4.0D), 1, 2, 3, 6.0F, true, AABB.ofSize(new Vec3(2.0D, 6.0D, 0), 8.0D, 14.0D, 8.0D)); + public static final ChestPartTransformer CHEST = new ChestPartTransformer(AABB.ofSize(new Vec3(0.0D, 6.0D, 0.0D), 8.0D, 12.0D, 4.0D), 8, 7, 18.0F, AABB.ofSize(new Vec3(0, 18.0D, 0), 12.0D, 14.0D, 6.0D)); + + private static PartTransformer getModelPartTransformer(ModelPart modelPart) { + if (HEAD.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) { + return HEAD; + } else if (LEFT_FEET.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) { + return LEFT_FEET; + } else if (RIGHT_FEET.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) { + return RIGHT_FEET; + } else if (LEFT_ARM.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) { + return LEFT_ARM; + } else if (RIGHT_ARM.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) { + return RIGHT_ARM; + } else if (LEFT_LEG.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) { + return LEFT_LEG; + } else if (RIGHT_LEG.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) { + return RIGHT_LEG; + } else if (CHEST.coverArea.contains(modelPart.x, modelPart.y, modelPart.z)) { + return CHEST; + } + + return CHEST; + } + + static record VanillaModelPartition(PartTransformer partTransformer, ModelPart modelPart, String partName) { + } + + @Override + public SkinnedMesh transformArmorModel(HumanoidModel humanoidModel) { + List partitions = Lists.newArrayList(); + + //Remove entity animation + humanoidModel.head.loadPose(humanoidModel.head.getInitialPose()); + humanoidModel.hat.loadPose(humanoidModel.hat.getInitialPose()); + humanoidModel.body.loadPose(humanoidModel.body.getInitialPose()); + humanoidModel.leftArm.loadPose(humanoidModel.leftArm.getInitialPose()); + humanoidModel.rightArm.loadPose(humanoidModel.rightArm.getInitialPose()); + humanoidModel.leftLeg.loadPose(humanoidModel.leftLeg.getInitialPose()); + humanoidModel.rightLeg.loadPose(humanoidModel.rightLeg.getInitialPose()); + + List modelParts = Lists.newArrayList(); + MixinAgeableListModel accessorAgeableListModel = ((MixinAgeableListModel)humanoidModel); + + Iterable headParts = accessorAgeableListModel.invoke_headParts(); + Iterable bodyParts = accessorAgeableListModel.invoke_bodyParts(); + + if (headParts != null) { + headParts.forEach(modelParts::add); + } + + if (bodyParts != null) { + bodyParts.forEach(modelParts::add); + } + + modelParts.forEach((modelPart) -> modelPart.loadPose(modelPart.getInitialPose())); + + if (humanoidModel.head.skipDraw || humanoidModel.head.visible) { + partitions.add(new VanillaModelPartition(HEAD, humanoidModel.head, "head")); + } + + if (humanoidModel.hat.skipDraw || humanoidModel.hat.visible) { + partitions.add(new VanillaModelPartition(HEAD, humanoidModel.hat, "hat")); + } + + if (humanoidModel.body.skipDraw || humanoidModel.body.visible) { + partitions.add(new VanillaModelPartition(CHEST, humanoidModel.body, "body")); + } + + if (humanoidModel.rightArm.skipDraw || humanoidModel.rightArm.visible) { + partitions.add(new VanillaModelPartition(RIGHT_ARM, humanoidModel.rightArm, "rightArm")); + } + + if (humanoidModel.leftArm.skipDraw || humanoidModel.leftArm.visible) { + partitions.add(new VanillaModelPartition(LEFT_ARM, humanoidModel.leftArm, "leftArm")); + } + + if (humanoidModel.leftLeg.skipDraw || humanoidModel.leftLeg.visible) { + partitions.add(new VanillaModelPartition(LEFT_LEG, humanoidModel.leftLeg, "leftLeg")); + } + + if (humanoidModel.rightLeg.skipDraw || humanoidModel.rightLeg.visible) { + partitions.add(new VanillaModelPartition(RIGHT_LEG, humanoidModel.rightLeg, "rightLeg")); + } + + modelParts.remove(humanoidModel.head); + modelParts.remove(humanoidModel.hat); + modelParts.remove(humanoidModel.body); + modelParts.remove(humanoidModel.rightArm); + modelParts.remove(humanoidModel.leftArm); + modelParts.remove(humanoidModel.rightLeg); + modelParts.remove(humanoidModel.leftLeg); + + int i = 0; + + for (ModelPart modelpart : modelParts) { + if (modelpart.skipDraw || modelpart.visible) { + partitions.add(new VanillaModelPartition(getModelPartTransformer(modelpart), modelpart, "part" + (i++))); + } + } + + return bakeMeshFromCubes(partitions); + } + + private static SkinnedMesh bakeMeshFromCubes(List partitions) { + List vertices = Lists.newArrayList(); + Map indices = Maps.newHashMap(); + PoseStack poseStack = new PoseStack(); + PartTransformer.IndexCounter indexCounter = new PartTransformer.IndexCounter(); + + poseStack.mulPose(QuaternionUtils.YP.rotationDegrees(180.0F)); + poseStack.mulPose(QuaternionUtils.XP.rotationDegrees(180.0F)); + poseStack.translate(0.0F, -24.0F, 0.0F); + + for (VanillaModelPartition modelpartition : partitions) { + bake(poseStack, modelpartition.partName, modelpartition, modelpartition.modelPart, vertices, indices, Lists.newArrayList(), indexCounter, false); + } + + return SingleGroupVertexBuilder.loadVertexInformation(vertices, indices); + } + + private static void bake(PoseStack poseStack, String partName, VanillaModelPartition modelpartition, ModelPart part, List vertices, Map indices, List path, PartTransformer.IndexCounter indexCounter, boolean bindPart) { + PartPose initialPose = part.getInitialPose(); + + poseStack.pushPose(); + poseStack.translate(initialPose.x, initialPose.y, initialPose.z); + poseStack.mulPose(new Quaternionf().rotationZYX(initialPose.zRot, initialPose.yRot, initialPose.xRot)); + + if (!bindPart) { + poseStack.scale(part.xScale, part.yScale, part.zScale); + } + + List newList = new ArrayList<>(path); + + if (bindPart) { + newList.add(partName); + } + + if (part.visible && !part.skipDraw) { + MeshPartDefinition partDefinition = VanillaMeshPartDefinition.of(partName); + + if (bindPart) { + OpenMatrix4f invertedParentTransform = OpenMatrix4f.importFromMojangMatrix(poseStack.last().pose()); + invertedParentTransform.m30 *= 0.0625F; + invertedParentTransform.m31 *= 0.0625F; + invertedParentTransform.m32 *= 0.0625F; + invertedParentTransform.invert(); + partDefinition = VanillaMeshPartDefinition.of(partName, newList, invertedParentTransform, modelpartition.modelPart); + } + + for (ModelPart.Cube cube : part.cubes) { + modelpartition.partTransformer.bakeCube(poseStack, partDefinition, cube, vertices, indices, indexCounter); + } + } + + for (Map.Entry child : part.children.entrySet()) { + bake(poseStack, child.getKey(), modelpartition, child.getValue(), vertices, indices, newList, indexCounter, true); + } + + poseStack.popPose(); + } + + static class SimpleTransformer extends PartTransformer { + final int jointId; + final AABB coverArea; + + public SimpleTransformer(AABB coverArea, int jointId) { + this.coverArea = coverArea; + this.jointId = jointId; + } + + public void bakeCube(PoseStack poseStack, MeshPartDefinition partDefinition, ModelPart.Cube cube, List vertices, Map indices, PartTransformer.IndexCounter indexCounter) { + for (ModelPart.Polygon quad : cube.polygons) { + Vector3f norm = new Vector3f(quad.normal); + norm.mul(poseStack.last().normal()); + + for (ModelPart.Vertex vertex : quad.vertices) { + Vector4f pos = new Vector4f(vertex.pos, 1.0F); + pos.mul(poseStack.last().pose()); + vertices.add(new SingleGroupVertexBuilder() + .setPosition(new Vec3f(pos.x(), pos.y(), pos.z()).scale(0.0625F)) + .setNormal(new Vec3f(norm.x(), norm.y(), norm.z())) + .setTextureCoordinate(new Vec2f(vertex.u, vertex.v)) + .setEffectiveJointIDs(new Vec3f(this.jointId, 0, 0)) + .setEffectiveJointWeights(new Vec3f(1.0F, 0.0F, 0.0F)) + .setEffectiveJointNumber(1) + ); + } + + triangluatePolygon(indices, partDefinition, indexCounter); + } + } + } + + static class ChestPartTransformer extends PartTransformer { + static final float X_PLANE = 0.0F; + static final VertexWeight[] WEIGHT_ALONG_Y = { new VertexWeight(13.6666F, 0.230F, 0.770F), new VertexWeight(15.8333F, 0.254F, 0.746F), new VertexWeight(18.0F, 0.5F, 0.5F), new VertexWeight(20.1666F, 0.744F, 0.256F), new VertexWeight(22.3333F, 0.770F, 0.230F)}; + final SimpleTransformer upperAttachmentTransformer; + final SimpleTransformer lowerAttachmentTransformer; + final AABB noneAttachmentArea; + final AABB coverArea; + final float yClipCoord; + + public ChestPartTransformer(AABB coverArea, int upperJoint, int lowerJoint, float yBasis, AABB noneAttachmentArea) { + this.coverArea = coverArea; + this.noneAttachmentArea = noneAttachmentArea; + this.upperAttachmentTransformer = new SimpleTransformer(null, upperJoint); + this.lowerAttachmentTransformer = new SimpleTransformer(null, lowerJoint); + this.yClipCoord = yBasis; + } + + @Override + public void bakeCube(PoseStack poseStack, MeshPartDefinition partDefinition, ModelPart.Cube cube, List vertices, Map indices, PartTransformer.IndexCounter indexCounter) { + Vec3 centerOfCube = getCenterOfCube(poseStack, cube); + + if (!this.noneAttachmentArea.contains(centerOfCube)) { + if (centerOfCube.y < this.yClipCoord) { + this.lowerAttachmentTransformer.bakeCube(poseStack, partDefinition, cube, vertices, indices, indexCounter); + } else { + this.upperAttachmentTransformer.bakeCube(poseStack, partDefinition, cube, vertices, indices, indexCounter); + } + + return; + } + + + List xClipPolygons = Lists.newArrayList(); + List xyClipPolygons = Lists.newArrayList(); + + for (ModelPart.Polygon polygon : cube.polygons) { + Matrix4f matrix = poseStack.last().pose(); + + ModelPart.Vertex pos0 = getTranslatedVertex(polygon.vertices[0], matrix); + ModelPart.Vertex pos1 = getTranslatedVertex(polygon.vertices[1], matrix); + ModelPart.Vertex pos2 = getTranslatedVertex(polygon.vertices[2], matrix); + ModelPart.Vertex pos3 = getTranslatedVertex(polygon.vertices[3], matrix); + Direction direction = getDirectionFromVector(polygon.normal); + + VertexWeight pos0Weight = getYClipWeight(pos0.pos.y()); + VertexWeight pos1Weight = getYClipWeight(pos1.pos.y()); + VertexWeight pos2Weight = getYClipWeight(pos2.pos.y()); + VertexWeight pos3Weight = getYClipWeight(pos3.pos.y()); + + if (pos1.pos.x() > X_PLANE != pos2.pos.x() > X_PLANE) { + float distance = pos2.pos.x() - pos1.pos.x(); + float textureU = pos1.u + (pos2.u - pos1.u) * ((X_PLANE - pos1.pos.x()) / distance); + ModelPart.Vertex pos4 = new ModelPart.Vertex(X_PLANE, pos0.pos.y(), pos0.pos.z(), textureU, pos0.v); + ModelPart.Vertex pos5 = new ModelPart.Vertex(X_PLANE, pos1.pos.y(), pos1.pos.z(), textureU, pos1.v); + + xClipPolygons.add(new AnimatedPolygon(new AnimatedVertex[] { + new AnimatedVertex(pos0, 8, 7, 0, pos0Weight.chestWeight, pos0Weight.torsoWeight, 0), + new AnimatedVertex(pos4, 8, 7, 0, pos0Weight.chestWeight, pos0Weight.torsoWeight, 0), + new AnimatedVertex(pos5, 8, 7, 0, pos1Weight.chestWeight, pos1Weight.torsoWeight, 0), + new AnimatedVertex(pos3, 8, 7, 0, pos3Weight.chestWeight, pos3Weight.torsoWeight, 0) + }, direction)); + xClipPolygons.add(new AnimatedPolygon(new AnimatedVertex[] { + new AnimatedVertex(pos4, 8, 7, 0, pos0Weight.chestWeight, pos0Weight.torsoWeight, 0), + new AnimatedVertex(pos1, 8, 7, 0, pos1Weight.chestWeight, pos1Weight.torsoWeight, 0), + new AnimatedVertex(pos2, 8, 7, 0, pos2Weight.chestWeight, pos2Weight.torsoWeight, 0), + new AnimatedVertex(pos5, 8, 7, 0, pos1Weight.chestWeight, pos1Weight.torsoWeight, 0) + }, direction)); + } else { + xClipPolygons.add(new AnimatedPolygon(new AnimatedVertex[] { + new AnimatedVertex(pos0, 8, 7, 0, pos0Weight.chestWeight, pos0Weight.torsoWeight, 0), + new AnimatedVertex(pos1, 8, 7, 0, pos1Weight.chestWeight, pos1Weight.torsoWeight, 0), + new AnimatedVertex(pos2, 8, 7, 0, pos2Weight.chestWeight, pos2Weight.torsoWeight, 0), + new AnimatedVertex(pos3, 8, 7, 0, pos3Weight.chestWeight, pos3Weight.torsoWeight, 0) + }, direction)); + } + } + + for (AnimatedPolygon polygon : xClipPolygons) { + boolean upsideDown = polygon.animatedVertexPositions[1].pos.y() > polygon.animatedVertexPositions[2].pos.y(); + AnimatedVertex pos0 = upsideDown ? polygon.animatedVertexPositions[2] : polygon.animatedVertexPositions[0]; + AnimatedVertex pos1 = upsideDown ? polygon.animatedVertexPositions[3] : polygon.animatedVertexPositions[1]; + AnimatedVertex pos2 = upsideDown ? polygon.animatedVertexPositions[0] : polygon.animatedVertexPositions[2]; + AnimatedVertex pos3 = upsideDown ? polygon.animatedVertexPositions[1] : polygon.animatedVertexPositions[3]; + Direction direction = getDirectionFromVector(polygon.normal); + List vertexWeights = getMiddleYClipWeights(pos1.pos.y(), pos2.pos.y()); + List animatedVertices = Lists.newArrayList(); + animatedVertices.add(pos0); + animatedVertices.add(pos1); + + if (vertexWeights.size() > 0) { + for (VertexWeight vertexWeight : vertexWeights) { + float distance = pos2.pos.y() - pos1.pos.y(); + float textureV = pos1.v + (pos2.v - pos1.v) * ((vertexWeight.yClipCoord - pos1.pos.y()) / distance); + Vector3f clipPos1 = getClipPoint(pos1.pos, pos2.pos, vertexWeight.yClipCoord); + Vector3f clipPos2 = getClipPoint(pos0.pos, pos3.pos, vertexWeight.yClipCoord); + ModelPart.Vertex pos4 = new ModelPart.Vertex(clipPos2, pos0.u, textureV); + ModelPart.Vertex pos5 = new ModelPart.Vertex(clipPos1, pos1.u, textureV); + animatedVertices.add(new AnimatedVertex(pos4, 8, 7, 0, vertexWeight.chestWeight, vertexWeight.torsoWeight, 0)); + animatedVertices.add(new AnimatedVertex(pos5, 8, 7, 0, vertexWeight.chestWeight, vertexWeight.torsoWeight, 0)); + } + } + + animatedVertices.add(pos3); + animatedVertices.add(pos2); + + for (int i = 0; i < (animatedVertices.size() - 2) / 2; i++) { + int start = i*2; + AnimatedVertex p0 = animatedVertices.get(start); + AnimatedVertex p1 = animatedVertices.get(start + 1); + AnimatedVertex p2 = animatedVertices.get(start + 3); + AnimatedVertex p3 = animatedVertices.get(start + 2); + xyClipPolygons.add(new AnimatedPolygon(new AnimatedVertex[] { + new AnimatedVertex(p0, 8, 7, 0, p0.weight.x, p0.weight.y, 0), + new AnimatedVertex(p1, 8, 7, 0, p1.weight.x, p1.weight.y, 0), + new AnimatedVertex(p2, 8, 7, 0, p2.weight.x, p2.weight.y, 0), + new AnimatedVertex(p3, 8, 7, 0, p3.weight.x, p3.weight.y, 0) + }, direction)); + } + } + + for (AnimatedPolygon polygon : xyClipPolygons) { + Vector3f norm = new Vector3f(polygon.normal); + norm.mul(poseStack.last().normal()); + + for (AnimatedVertex vertex : polygon.animatedVertexPositions) { + Vector4f pos = new Vector4f(vertex.pos, 1.0F); + float weight1 = vertex.weight.x; + float weight2 = vertex.weight.y; + int joint1 = vertex.jointId.getX(); + int joint2 = vertex.jointId.getY(); + int count = weight1 > 0.0F && weight2 > 0.0F ? 2 : 1; + + if (weight1 <= 0.0F) { + joint1 = joint2; + weight1 = weight2; + } + + vertices.add(new SingleGroupVertexBuilder() + .setPosition(new Vec3f(pos.x(), pos.y(), pos.z()).scale(0.0625F)) + .setNormal(new Vec3f(norm.x(), norm.y(), norm.z())) + .setTextureCoordinate(new Vec2f(vertex.u, vertex.v)) + .setEffectiveJointIDs(new Vec3f(joint1, joint2, 0)) + .setEffectiveJointWeights(new Vec3f(weight1, weight2, 0.0F)) + .setEffectiveJointNumber(count) + ); + } + + triangluatePolygon(indices, partDefinition, indexCounter); + } + } + + static VertexWeight getYClipWeight(float y) { + if (y < WEIGHT_ALONG_Y[0].yClipCoord) { + return new VertexWeight(y, 0.0F, 1.0F); + } + + int index = -1; + for (int i = 0; i < WEIGHT_ALONG_Y.length; i++) { + + } + + if (index > 0) { + VertexWeight pair = WEIGHT_ALONG_Y[index]; + return new VertexWeight(y, pair.chestWeight, pair.torsoWeight); + } + + return new VertexWeight(y, 1.0F, 0.0F); + } + + static class VertexWeight { + final float yClipCoord; + final float chestWeight; + final float torsoWeight; + + public VertexWeight(float yClipCoord, float chestWeight, float torsoWeight) { + this.yClipCoord = yClipCoord; + this.chestWeight = chestWeight; + this.torsoWeight = torsoWeight; + } + } + + static List getMiddleYClipWeights(float minY, float maxY) { + List cutYs = Lists.newArrayList(); + for (VertexWeight vertexWeight : WEIGHT_ALONG_Y) { + if (vertexWeight.yClipCoord > minY && maxY >= vertexWeight.yClipCoord) { + cutYs.add(vertexWeight); + } + } + return cutYs; + } + } + + static class LimbPartTransformer extends PartTransformer { + final int upperJoint; + final int lowerJoint; + final int middleJoint; + final boolean bendInFront; + final SimpleTransformer upperAttachmentTransformer; + final SimpleTransformer lowerAttachmentTransformer; + final AABB noneAttachmentArea; + final AABB coverArea; + final float yClipCoord; + + public LimbPartTransformer(AABB coverArea, int upperJoint, int lowerJoint, int middleJoint, float yClipCoord, boolean bendInFront, AABB noneAttachmentArea) { + this.upperJoint = upperJoint; + this.lowerJoint = lowerJoint; + this.middleJoint = middleJoint; + this.bendInFront = bendInFront; + this.upperAttachmentTransformer = new SimpleTransformer(null, upperJoint); + this.lowerAttachmentTransformer = new SimpleTransformer(null, lowerJoint); + this.noneAttachmentArea = noneAttachmentArea; + this.coverArea = coverArea; + this.yClipCoord = yClipCoord; + } + + @Override + public void bakeCube(PoseStack poseStack, MeshPartDefinition partDefinition, ModelPart.Cube cube, List vertices, Map indices, PartTransformer.IndexCounter indexCounter) { + List polygons = Lists.newArrayList(); + + for (ModelPart.Polygon quad : cube.polygons) { + Matrix4f matrix = poseStack.last().pose(); + ModelPart.Vertex pos0 = getTranslatedVertex(quad.vertices[0], matrix); + ModelPart.Vertex pos1 = getTranslatedVertex(quad.vertices[1], matrix); + ModelPart.Vertex pos2 = getTranslatedVertex(quad.vertices[2], matrix); + ModelPart.Vertex pos3 = getTranslatedVertex(quad.vertices[3], matrix); + Direction direction = getDirectionFromVector(quad.normal); + + if (pos1.pos.y() > this.yClipCoord != pos2.pos.y() > this.yClipCoord) { + float distance = pos2.pos.y() - pos1.pos.y(); + float textureV = pos1.v + (pos2.v - pos1.v) * ((this.yClipCoord - pos1.pos.y()) / distance); + Vector3f clipPos1 = getClipPoint(pos1.pos, pos2.pos, this.yClipCoord); + Vector3f clipPos2 = getClipPoint(pos0.pos, pos3.pos, this.yClipCoord); + ModelPart.Vertex pos4 = new ModelPart.Vertex(clipPos2, pos0.u, textureV); + ModelPart.Vertex pos5 = new ModelPart.Vertex(clipPos1, pos1.u, textureV); + + int upperId, lowerId; + if (distance > 0) { + upperId = this.lowerJoint; + lowerId = this.upperJoint; + } else { + upperId = this.upperJoint; + lowerId = this.lowerJoint; + } + + polygons.add(new AnimatedPolygon(new AnimatedVertex[] { + new AnimatedVertex(pos0, upperId), new AnimatedVertex(pos1, upperId), + new AnimatedVertex(pos5, upperId), new AnimatedVertex(pos4, upperId) + }, direction)); + polygons.add(new AnimatedPolygon(new AnimatedVertex[] { + new AnimatedVertex(pos4, lowerId), new AnimatedVertex(pos5, lowerId), + new AnimatedVertex(pos2, lowerId), new AnimatedVertex(pos3, lowerId) + }, direction)); + + boolean hasSameZ = pos4.pos.z() < 0.0F == pos5.pos.z() < 0.0F; + boolean isFront = hasSameZ && (pos4.pos.z() < 0.0F == this.bendInFront); + + if (isFront) { + polygons.add(new AnimatedPolygon(new AnimatedVertex[] { + new AnimatedVertex(pos4, this.middleJoint), new AnimatedVertex(pos5, this.middleJoint), + new AnimatedVertex(pos5, this.upperJoint), new AnimatedVertex(pos4, this.upperJoint) + }, 0.001F, direction)); + polygons.add(new AnimatedPolygon(new AnimatedVertex[] { + new AnimatedVertex(pos4, this.lowerJoint), new AnimatedVertex(pos5, this.lowerJoint), + new AnimatedVertex(pos5, this.middleJoint), new AnimatedVertex(pos4, this.middleJoint) + }, 0.001F, direction)); + } else if (!hasSameZ) { + boolean startFront = pos4.pos.z() > 0; + int firstJoint = this.lowerJoint; + int secondJoint = this.lowerJoint; + int thirdJoint = startFront ? this.upperJoint : this.middleJoint; + int fourthJoint = startFront ? this.middleJoint : this.upperJoint; + int fifthJoint = this.upperJoint; + int sixthJoint = this.upperJoint; + + polygons.add(new AnimatedPolygon(new AnimatedVertex[] { + new AnimatedVertex(pos4, firstJoint), new AnimatedVertex(pos5, secondJoint), + new AnimatedVertex(pos5, thirdJoint), new AnimatedVertex(pos4, fourthJoint) + }, 0.001F, direction)); + polygons.add(new AnimatedPolygon(new AnimatedVertex[] { + new AnimatedVertex(pos4, fourthJoint), new AnimatedVertex(pos5, thirdJoint), + new AnimatedVertex(pos5, fifthJoint), new AnimatedVertex(pos4, sixthJoint) + }, 0.001F, direction)); + } + } else { + int jointId = pos0.pos.y() > this.yClipCoord ? this.upperJoint : this.lowerJoint; + polygons.add(new AnimatedPolygon(new AnimatedVertex[] { + new AnimatedVertex(pos0, jointId), new AnimatedVertex(pos1, jointId), + new AnimatedVertex(pos2, jointId), new AnimatedVertex(pos3, jointId) + }, direction)); + } + } + + for (AnimatedPolygon quad : polygons) { + Vector3f norm = new Vector3f(quad.normal); + norm.mul(poseStack.last().normal()); + + for (AnimatedVertex vertex : quad.animatedVertexPositions) { + Vector4f pos = new Vector4f(vertex.pos, 1.0F); + vertices.add(new SingleGroupVertexBuilder() + .setPosition(new Vec3f(pos.x(), pos.y(), pos.z()).scale(0.0625F)) + .setNormal(new Vec3f(norm.x(), norm.y(), norm.z())) + .setTextureCoordinate(new Vec2f(vertex.u, vertex.v)) + .setEffectiveJointIDs(new Vec3f(vertex.jointId.getX(), 0, 0)) + .setEffectiveJointWeights(new Vec3f(1.0F, 0.0F, 0.0F)) + .setEffectiveJointNumber(1) + ); + } + + triangluatePolygon(indices, partDefinition, indexCounter); + } + } + } + + static Direction getDirectionFromVector(Vector3f directionVec) { + for (Direction direction : Direction.values()) { + Vector3f direcVec = new Vector3f(Float.compare(directionVec.x(), -0.0F) == 0 ? 0.0F : directionVec.x(), directionVec.y(), directionVec.z()); + if (direcVec.equals(direction.step())) { + return direction; + } + } + + return null; + } + + static Vec3 getCenterOfCube(PoseStack poseStack, ModelPart.Cube cube) { + double minX = Double.MAX_VALUE; + double minY = Double.MAX_VALUE; + double minZ = Double.MAX_VALUE; + double maxX = Double.MIN_VALUE; + double maxY = Double.MIN_VALUE; + double maxZ = Double.MIN_VALUE; + + Matrix4f matrix = poseStack.last().pose(); + + for (ModelPart.Polygon quad : cube.polygons) { + for (ModelPart.Vertex v : quad.vertices) { + Vector4f translatedPosition = new Vector4f(v.pos, 1.0F); + translatedPosition.mul(matrix); + + if (minX > translatedPosition.x()) { + minX = translatedPosition.x(); + } + + if (minY > translatedPosition.y()) { + minY = translatedPosition.y(); + } + + if (minZ > translatedPosition.z()) { + minZ = translatedPosition.z(); + } + + if (maxX < translatedPosition.x()) { + maxX = translatedPosition.x(); + } + + if (maxY < translatedPosition.y()) { + maxY = translatedPosition.y(); + } + + if (maxZ < translatedPosition.z()) { + maxZ = translatedPosition.z(); + } + } + } + + return new Vec3(minX + (maxX - minX) * 0.5D, minY + (maxY - minY) * 0.5D, minZ + (maxZ - minZ) * 0.5D); + } + + static Vector3f getClipPoint(Vector3f pos1, Vector3f pos2, float yClip) { + Vector3f direct = new Vector3f(pos2); + direct.sub(pos1); + direct.mul((yClip - pos1.y()) / (pos2.y() - pos1.y())); + + Vector3f clipPoint = new Vector3f(pos1); + clipPoint.add(direct); + + return clipPoint; + } + + static ModelPart.Vertex getTranslatedVertex(ModelPart.Vertex original, Matrix4f matrix) { + Vector4f translatedPosition = new Vector4f(original.pos, 1.0F); + translatedPosition.mul(matrix); + + return new ModelPart.Vertex(translatedPosition.x(), translatedPosition.y(), translatedPosition.z(), original.u, original.v); + } + + static class AnimatedVertex extends ModelPart.Vertex { + final Vec3i jointId; + final Vec3f weight; + + public AnimatedVertex(ModelPart.Vertex posTexVertx, int jointId) { + this(posTexVertx, jointId, 0, 0, 1.0F, 0.0F, 0.0F); + } + + public AnimatedVertex(ModelPart.Vertex posTexVertx, int jointId1, int jointId2, int jointId3, float weight1, float weight2, float weight3) { + this(posTexVertx, new Vec3i(jointId1, jointId2, jointId3), new Vec3f(weight1, weight2, weight3)); + } + + public AnimatedVertex(ModelPart.Vertex posTexVertx, Vec3i ids, Vec3f weights) { + this(posTexVertx, posTexVertx.u, posTexVertx.v, ids, weights); + } + + public AnimatedVertex(ModelPart.Vertex posTexVertx, float u, float v, Vec3i ids, Vec3f weights) { + super(posTexVertx.pos.x(), posTexVertx.pos.y(), posTexVertx.pos.z(), u, v); + this.jointId = ids; + this.weight = weights; + } + } + + static class AnimatedPolygon { + public final AnimatedVertex[] animatedVertexPositions; + public final Vector3f normal; + + public AnimatedPolygon(AnimatedVertex[] positionsIn, Direction directionIn) { + this.animatedVertexPositions = positionsIn; + this.normal = directionIn.step(); + } + + public AnimatedPolygon(AnimatedVertex[] positionsIn, float cor, Direction directionIn) { + this.animatedVertexPositions = positionsIn; + positionsIn[0] = new AnimatedVertex(positionsIn[0], positionsIn[0].u, positionsIn[0].v + cor, positionsIn[0].jointId, positionsIn[0].weight); + positionsIn[1] = new AnimatedVertex(positionsIn[1], positionsIn[1].u, positionsIn[1].v + cor, positionsIn[1].jointId, positionsIn[1].weight); + positionsIn[2] = new AnimatedVertex(positionsIn[2], positionsIn[2].u, positionsIn[2].v - cor, positionsIn[2].jointId, positionsIn[2].weight); + positionsIn[3] = new AnimatedVertex(positionsIn[3], positionsIn[3].u, positionsIn[3].v - cor, positionsIn[3].jointId, positionsIn[3].weight); + this.normal = directionIn.step(); + } + } + + public record VanillaMeshPartDefinition(String partName, RenderProperties renderProperties, List path, OpenMatrix4f invertedParentTransform, ModelPart root) implements MeshPartDefinition { + public static MeshPartDefinition of(String partName, RenderProperties renderProperties) { return new VanillaMeshPartDefinition(partName, renderProperties, null, null, null); } - + public static MeshPartDefinition of(String partName) { return new VanillaMeshPartDefinition(partName, null, null, null, null); } - + + /** + * For animated models + * @param partName + * @param path + * @param invertedParentTransform + * @param root + * @return + */ public static MeshPartDefinition of(String partName, List path, OpenMatrix4f invertedParentTransform, ModelPart root) { return new VanillaMeshPartDefinition(partName, null, path, invertedParentTransform, root); } - - @Override + public Supplier getModelPartAnimationProvider() { - return () -> null; + return this.root == null ? () -> null : () -> { + PoseStack poseStack = new PoseStack(); + poseStack.mulPose(QuaternionUtils.YP.rotationDegrees(180.0F)); + poseStack.mulPose(QuaternionUtils.XP.rotationDegrees(180.0F)); + poseStack.translate(0.0F, -24.0F, 0.0F); + + this.progress(this.root, poseStack, false); + ModelPart part = this.root; + int idx = 0; + + for (String childPartName : this.path) { + idx++; + part = part.getChild(childPartName); + this.progress(part, poseStack, idx == this.path.size()); + } + + OpenMatrix4f animParentTransform = OpenMatrix4f.importFromMojangMatrix(poseStack.last().pose()); + animParentTransform.m30 *= 0.0625F; + animParentTransform.m31 *= 0.0625F; + animParentTransform.m32 *= 0.0625F; + + ModelPart lastPart = part; + PartPose partPose = part.getInitialPose(); + OpenMatrix4f partAnimation = OpenMatrix4f.mulMatrices(animParentTransform, + new OpenMatrix4f().mulBack(OpenMatrix4f.fromQuaternion(new Quaternionf().rotationZYX(partPose.zRot, partPose.yRot, partPose.xRot)).transpose().invert()) + .translate(new Vec3f(lastPart.x - partPose.x, lastPart.y - partPose.y, lastPart.z - partPose.z).scale(0.0625F)) + .mulBack(OpenMatrix4f.fromQuaternion(new Quaternionf().rotationZYX(partPose.zRot, partPose.yRot, partPose.xRot)).transpose()) + .mulBack(OpenMatrix4f.fromQuaternion(new Quaternionf().rotationZYX(lastPart.zRot - partPose.zRot, lastPart.yRot - partPose.yRot, lastPart.xRot - partPose.xRot)).transpose()) + .scale(new Vec3f(lastPart.xScale, lastPart.yScale, lastPart.zScale)), + this.invertedParentTransform); + + return partAnimation; + }; + } + + private void progress(ModelPart part, PoseStack poseStack, boolean last) { + PartPose initialPose = part.getInitialPose(); + + if (last) { + poseStack.translate(initialPose.x, initialPose.y, initialPose.z); + poseStack.mulPose(new Quaternionf().rotationZYX(initialPose.zRot, initialPose.yRot, initialPose.xRot)); + } else { + poseStack.translate(part.x, part.y, part.z); + poseStack.mulPose(new Quaternionf().rotationZYX(part.zRot, part.yRot, part.xRot)); + poseStack.scale(part.xScale, part.yScale, part.zScale); + } + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof MeshPartDefinition comparision) { + return this.partName.equals(comparision.partName()); + } + + return false; + } + + public int hashCode() { + return this.partName.hashCode(); } } } diff --git a/src/main/java/com/tiedup/remake/rig/mixin/client/MixinAgeableListModel.java b/src/main/java/com/tiedup/remake/rig/mixin/client/MixinAgeableListModel.java new file mode 100644 index 0000000..503c11d --- /dev/null +++ b/src/main/java/com/tiedup/remake/rig/mixin/client/MixinAgeableListModel.java @@ -0,0 +1,29 @@ +/* + * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight) + * by the Epic Fight Team, licensed under GPLv3. + * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3. + */ + +package com.tiedup.remake.rig.mixin.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import net.minecraft.client.model.AgeableListModel; +import net.minecraft.client.model.geom.ModelPart; + +/** + * Expose les méthodes protected {@code headParts()} et {@code bodyParts()} de + * {@link AgeableListModel} pour que {@code VanillaModelTransformer.transformArmorModel(...)} + * puisse itérer les ModelPart sans les hard-coder. Pure @Invoker mixin, pas de + * logique injectée. + */ +@Mixin(value = AgeableListModel.class) +public interface MixinAgeableListModel { + + @Invoker("headParts") + Iterable invoke_headParts(); + + @Invoker("bodyParts") + Iterable invoke_bodyParts(); +} diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index b615014..af2fdb2 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -81,6 +81,9 @@ config="tiedup.mixins.json" [[mixins]] config="tiedup-compat.mixins.json" +[[mixins]] +config="tiedup-rig.mixins.json" + # Features are specific properties of the game environment, that you may want to declare you require. This example declares # that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't # stop your mod loading on the server for example. diff --git a/src/main/resources/tiedup-rig.mixins.json b/src/main/resources/tiedup-rig.mixins.json new file mode 100644 index 0000000..3ca0e1c --- /dev/null +++ b/src/main/resources/tiedup-rig.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.tiedup.remake.rig.mixin", + "compatibilityLevel": "JAVA_17", + "refmap": "tiedup-rig.refmap.json", + "mixins": [], + "client": [ + "client.MixinAgeableListModel" + ], + "injectors": { + "defaultRequire": 1 + } +}