Phase 1.4 : tests unitaires bridge GLB→SkinnedMesh
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.
This commit is contained in:
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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<Armature> 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<String, Joint> 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> armature = buildMinimalArmature();
|
||||
|
||||
SkinnedMesh mesh = assertDoesNotThrow(() -> GltfToSkinnedMesh.convert(data, armature));
|
||||
assertNotNull(mesh);
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertSyntheticGltfHasExpectedParts() {
|
||||
GltfData data = buildSyntheticGltf();
|
||||
AssetAccessor<Armature> 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user