From 29c4fddb901ba7e8d29442082bab7ecf295cbcc3 Mon Sep 17 00:00:00 2001 From: notevil Date: Wed, 22 Apr 2026 19:08:05 +0200 Subject: [PATCH] =?UTF-8?q?Phase=201.4=20:=20tests=20unitaires=20bridge=20?= =?UTF-8?q?GLB=E2=86=92SkinnedMesh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 8 tests GREEN : GlbJointAliasTableTest (5) : - mapLegacyPlayerAnimatorNames : body→Chest, leftUpperArm→Arm_L, leftLowerArm→Elbow_L, leftUpperLeg→Thigh_L, leftLowerLeg→Knee_L, etc. - isCaseInsensitive : BODY/LeftUpperArm/leftupperarm tous remappés - bypassBipedNames : Arm_L, Elbow_R, Head, Chest, Torso, Root non transformés - unknownReturnsNull : null pour nom inconnu / vide / null - isBipedJointNameDetection : _R/_L suffix + Root/Torso/Chest/Head GltfToSkinnedMeshTest (3) : - convertSyntheticGltfDoesNotThrow : 3 vertices + armature biped minimale (4 joints manuels Root→Chest→{Arm_L,Arm_R}) → SkinnedMesh non null - convertSyntheticGltfHasExpectedParts : partName dérivé du materialName de la primitive glTF - convertThrowsOnNullArmature : IllegalStateException si armature null Fixture : buildMinimalArmature() construit une hiérarchie 4 joints via Joint() + addSubJoints() + Armature(name, count, root, jointMap).bakeOriginMatrices(). buildSyntheticGltf() produit un triangle 3-vertices avec jointNames (body, leftUpperArm, rightUpperArm) pour tester le mapping PlayerAnimator→EF. --- .../rig/bridge/GlbJointAliasTableTest.java | 67 +++++++ .../rig/bridge/GltfToSkinnedMeshTest.java | 167 ++++++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 src/test/java/com/tiedup/remake/rig/bridge/GlbJointAliasTableTest.java create mode 100644 src/test/java/com/tiedup/remake/rig/bridge/GltfToSkinnedMeshTest.java diff --git a/src/test/java/com/tiedup/remake/rig/bridge/GlbJointAliasTableTest.java b/src/test/java/com/tiedup/remake/rig/bridge/GlbJointAliasTableTest.java new file mode 100644 index 0000000..6c19bc2 --- /dev/null +++ b/src/test/java/com/tiedup/remake/rig/bridge/GlbJointAliasTableTest.java @@ -0,0 +1,67 @@ +/* + * © 2026 TiedUp! Remake Contributors, distributed under GPLv3. + */ + +package com.tiedup.remake.rig.bridge; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class GlbJointAliasTableTest { + + @Test + void mapLegacyPlayerAnimatorNames() { + assertEquals("Chest", GlbJointAliasTable.mapGltfJointName("body")); + assertEquals("Chest", GlbJointAliasTable.mapGltfJointName("torso")); + assertEquals("Head", GlbJointAliasTable.mapGltfJointName("head")); + assertEquals("Arm_L", GlbJointAliasTable.mapGltfJointName("leftUpperArm")); + assertEquals("Arm_L", GlbJointAliasTable.mapGltfJointName("leftArm")); + assertEquals("Elbow_L", GlbJointAliasTable.mapGltfJointName("leftLowerArm")); + assertEquals("Elbow_L", GlbJointAliasTable.mapGltfJointName("leftForearm")); + assertEquals("Arm_R", GlbJointAliasTable.mapGltfJointName("rightUpperArm")); + assertEquals("Elbow_R", GlbJointAliasTable.mapGltfJointName("rightLowerArm")); + assertEquals("Thigh_L", GlbJointAliasTable.mapGltfJointName("leftUpperLeg")); + assertEquals("Knee_L", GlbJointAliasTable.mapGltfJointName("leftLowerLeg")); + assertEquals("Thigh_R", GlbJointAliasTable.mapGltfJointName("rightUpperLeg")); + assertEquals("Knee_R", GlbJointAliasTable.mapGltfJointName("rightLowerLeg")); + } + + @Test + void isCaseInsensitive() { + assertEquals("Chest", GlbJointAliasTable.mapGltfJointName("BODY")); + assertEquals("Arm_L", GlbJointAliasTable.mapGltfJointName("LeftUpperArm")); + assertEquals("Arm_L", GlbJointAliasTable.mapGltfJointName("leftupperarm")); + } + + @Test + void bypassBipedNames() { + assertEquals("Arm_L", GlbJointAliasTable.mapGltfJointName("Arm_L")); + assertEquals("Elbow_R", GlbJointAliasTable.mapGltfJointName("Elbow_R")); + assertEquals("Head", GlbJointAliasTable.mapGltfJointName("Head")); + assertEquals("Chest", GlbJointAliasTable.mapGltfJointName("Chest")); + assertEquals("Torso", GlbJointAliasTable.mapGltfJointName("Torso")); + assertEquals("Root", GlbJointAliasTable.mapGltfJointName("Root")); + } + + @Test + void unknownReturnsNull() { + assertNull(GlbJointAliasTable.mapGltfJointName("UnknownJoint")); + assertNull(GlbJointAliasTable.mapGltfJointName("")); + assertNull(GlbJointAliasTable.mapGltfJointName(null)); + } + + @Test + void isBipedJointNameDetection() { + assertTrue(GlbJointAliasTable.isBipedJointName("Arm_L")); + assertTrue(GlbJointAliasTable.isBipedJointName("Thigh_R")); + assertTrue(GlbJointAliasTable.isBipedJointName("Root")); + assertTrue(GlbJointAliasTable.isBipedJointName("Torso")); + assertTrue(GlbJointAliasTable.isBipedJointName("Head")); + // False cases + assertEquals(false, GlbJointAliasTable.isBipedJointName("leftUpperArm")); + assertEquals(false, GlbJointAliasTable.isBipedJointName("body")); + } +} diff --git a/src/test/java/com/tiedup/remake/rig/bridge/GltfToSkinnedMeshTest.java b/src/test/java/com/tiedup/remake/rig/bridge/GltfToSkinnedMeshTest.java new file mode 100644 index 0000000..74cb36b --- /dev/null +++ b/src/test/java/com/tiedup/remake/rig/bridge/GltfToSkinnedMeshTest.java @@ -0,0 +1,167 @@ +/* + * © 2026 TiedUp! Remake Contributors, distributed under GPLv3. + */ + +package com.tiedup.remake.rig.bridge; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.joml.Matrix4f; +import org.joml.Quaternionf; +import org.joml.Vector3f; +import org.junit.jupiter.api.Test; + +import net.minecraft.resources.ResourceLocation; + +import com.tiedup.remake.client.gltf.GltfData; +import com.tiedup.remake.client.gltf.GltfData.Primitive; +import com.tiedup.remake.rig.armature.Armature; +import com.tiedup.remake.rig.armature.Joint; +import com.tiedup.remake.rig.asset.AssetAccessor; +import com.tiedup.remake.rig.math.OpenMatrix4f; +import com.tiedup.remake.rig.mesh.SkinnedMesh; + +class GltfToSkinnedMeshTest { + + // ---------- Test fixtures ---------- + + /** + * Armature biped minimale pour test : Root → Chest → {Arm_L, Arm_R}. + * 4 joints avec IDs 0..3. Suffisant pour valider le mapping des noms legacy + * sans dépendre d'un resource reload. + */ + private static AssetAccessor buildMinimalArmature() { + Joint root = new Joint("Root", 0, new OpenMatrix4f()); + Joint chest = new Joint("Chest", 1, new OpenMatrix4f()); + Joint armL = new Joint("Arm_L", 2, new OpenMatrix4f()); + Joint armR = new Joint("Arm_R", 3, new OpenMatrix4f()); + + root.addSubJoints(chest); + chest.addSubJoints(armL, armR); + + Map jointMap = new LinkedHashMap<>(); + jointMap.put("Root", root); + jointMap.put("Chest", chest); + jointMap.put("Arm_L", armL); + jointMap.put("Arm_R", armR); + + Armature arm = new Armature("test_biped", 4, root, jointMap); + arm.bakeOriginMatrices(); + + return new AssetAccessor<>() { + @Override public Armature get() { return arm; } + @Override public ResourceLocation registryName() { + return ResourceLocation.fromNamespaceAndPath("test", "armature/test_biped"); + } + @Override public boolean inRegistry() { return false; } + }; + } + + /** + * GltfData synthétique : 3 vertices (triangle), 3 joints glTF + * (body, leftUpperArm, rightUpperArm). Chaque vertex attaché à un joint + * différent avec poids 1.0 sur le joint principal + 0 sur les 3 autres. + * Permet de vérifier le mapping glTF→biped via le jointIdMap. + */ + private static GltfData buildSyntheticGltf() { + int vertexCount = 3; + + float[] positions = { + 0.0F, 0.0F, 0.0F, // v0 - attached to body/Chest + 1.0F, 0.0F, 0.0F, // v1 - attached to leftUpperArm/Arm_L + -1.0F, 0.0F, 0.0F // v2 - attached to rightUpperArm/Arm_R + }; + float[] normals = { + 0.0F, 1.0F, 0.0F, + 0.0F, 1.0F, 0.0F, + 0.0F, 1.0F, 0.0F + }; + float[] texCoords = { + 0.5F, 0.5F, + 1.0F, 0.5F, + 0.0F, 0.5F + }; + int[] indices = { 0, 1, 2 }; + + // 4 joints/weights par vertex. Le 4e est toujours 0 → sera drop. + int[] joints = { + 0, 1, 2, 0, // v0 + 1, 0, 2, 0, // v1 + 2, 0, 1, 0 // v2 + }; + float[] weights = { + 1.0F, 0.0F, 0.0F, 0.0F, // v0 : 100% body + 1.0F, 0.0F, 0.0F, 0.0F, // v1 : 100% leftUpperArm + 1.0F, 0.0F, 0.0F, 0.0F // v2 : 100% rightUpperArm + }; + + String[] jointNames = { "body", "leftUpperArm", "rightUpperArm" }; + int[] parentJoints = { -1, 0, 0 }; + Matrix4f[] invBind = { + new Matrix4f().identity(), + new Matrix4f().identity(), + new Matrix4f().identity() + }; + Quaternionf[] restRot = { + new Quaternionf(), + new Quaternionf(), + new Quaternionf() + }; + Vector3f[] restTrans = { + new Vector3f(), + new Vector3f(), + new Vector3f() + }; + + return new GltfData( + positions, normals, texCoords, indices, + joints, weights, + jointNames, parentJoints, invBind, restRot, restTrans, + restRot, // rawGltfRestRotations + null, // rawGltfAnimation + null, // animation + new LinkedHashMap<>(), // namedAnimations + new LinkedHashMap<>(), // rawNamedAnimations + List.of(new Primitive(indices, "material_0", false, null)), + vertexCount, jointNames.length + ); + } + + // ---------- Tests ---------- + + @Test + void convertSyntheticGltfDoesNotThrow() { + GltfData data = buildSyntheticGltf(); + AssetAccessor armature = buildMinimalArmature(); + + SkinnedMesh mesh = assertDoesNotThrow(() -> GltfToSkinnedMesh.convert(data, armature)); + assertNotNull(mesh); + } + + @Test + void convertSyntheticGltfHasExpectedParts() { + GltfData data = buildSyntheticGltf(); + AssetAccessor armature = buildMinimalArmature(); + + SkinnedMesh mesh = GltfToSkinnedMesh.convert(data, armature); + + // Une seule primitive → une seule part (partName = "material_0") + assertNotNull(mesh); + assertEquals(1, mesh.getAllParts().size(), "Expected one part per primitive"); + assertTrue(mesh.hasPart("material_0"), "Part name should match material name"); + } + + @Test + void convertThrowsOnNullArmature() { + GltfData data = buildSyntheticGltf(); + assertThrows(IllegalStateException.class, () -> GltfToSkinnedMesh.convert(data, null)); + } +}