Added: - TransformSheetTest (10 tests) : binary search getInterpolationInfo edge cases (empty, negative-wrap, exact boundary, multi-keyframe segment), maxFrameTime sentinel, copyAll/copy subrange - PoseTest (10 tests) : interpolatePose merge + lerp correctness at 0/0.5/1, orElseEmpty fallback, load() all three LoadOperation modes, disableAllJoints - LivingMotionIsSameTest (7 tests) : IDLE==INACTION symmetry, uniqueness of universalOrdinal across all LivingMotions values - TimePairListTest (7 tests) : odd-arg rejection, empty list, inclusive begin / exclusive end boundary, multi-pair gap - RigAnimationTickHandlerTest (2 tests) : resetLoggedErrors idempotency Skipped (MC runtime dep): - TiedUpCapabilityEventsTest : AttachCapabilitiesEvent + live ForgeEventBus - EntityPatchProviderInvalidateTest : LazyOptional is a Forge runtime class - LivingEntityPatch.onConstructed : requires real LivingEntity hierarchy - RigAnimationTickHandler.tickPlayer/maybePlayIdle : require TiedUpCapabilities.getEntityPatch + ClientAnimator + LivingEntityPatch Bug flagged (no fix) : - TransformSheet.getFirstFrame() calls copy(0,2) without guarding size >= 2; a single-keyframe sheet would throw ArrayIndexOutOfBoundsException
190 lines
6.7 KiB
Java
190 lines
6.7 KiB
Java
/*
|
|
* © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
|
*/
|
|
|
|
package com.tiedup.remake.rig.anim;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
import com.tiedup.remake.rig.armature.JointTransform;
|
|
import com.tiedup.remake.rig.math.Vec3f;
|
|
|
|
/**
|
|
* Tests de {@link Pose} : interpolatePose, orElseEmpty, load (SET / OVERWRITE /
|
|
* APPEND_ABSENT), disableAllJoints.
|
|
*
|
|
* Aucun MC runtime requis — Pose utilise uniquement des collections Java et
|
|
* JointTransform (JOML + Vec3f, pas de Minecraft bootstrap).
|
|
*/
|
|
class PoseTest {
|
|
|
|
// --- helpers ---
|
|
|
|
private static JointTransform translation(float x, float y, float z) {
|
|
return JointTransform.translation(new Vec3f(x, y, z));
|
|
}
|
|
|
|
// --- orElseEmpty ---
|
|
|
|
/** Un joint absent retourne JointTransform.empty() (pas null). */
|
|
@Test
|
|
void orElseEmpty_unknownJoint_returnsEmpty() {
|
|
Pose pose = new Pose();
|
|
JointTransform result = pose.orElseEmpty("nonexistent");
|
|
assertNotNull(result, "orElseEmpty ne doit pas retourner null");
|
|
// Verifier que la translation est zeros (empty = identite)
|
|
assertEquals(0.0f, result.translation().x, 1e-5f);
|
|
assertEquals(0.0f, result.translation().y, 1e-5f);
|
|
assertEquals(0.0f, result.translation().z, 1e-5f);
|
|
}
|
|
|
|
/** Un joint present retourne son transform, pas le fallback. */
|
|
@Test
|
|
void orElseEmpty_knownJoint_returnsTransform() {
|
|
Pose pose = new Pose();
|
|
JointTransform jt = translation(1.0f, 2.0f, 3.0f);
|
|
pose.putJointData("spine", jt);
|
|
assertEquals(jt, pose.orElseEmpty("spine"));
|
|
}
|
|
|
|
// --- interpolatePose ---
|
|
|
|
/**
|
|
* interpolatePose a progression 0.0 doit retourner les valeurs de pose1.
|
|
*/
|
|
@Test
|
|
void interpolatePose_progressionZero_returnsFirstPose() {
|
|
Pose pose1 = new Pose();
|
|
pose1.putJointData("arm", translation(0.0f, 0.0f, 0.0f));
|
|
|
|
Pose pose2 = new Pose();
|
|
pose2.putJointData("arm", translation(1.0f, 0.0f, 0.0f));
|
|
|
|
Pose result = Pose.interpolatePose(pose1, pose2, 0.0f);
|
|
assertTrue(result.hasTransform("arm"));
|
|
assertEquals(0.0f, result.get("arm").translation().x, 1e-5f);
|
|
}
|
|
|
|
/**
|
|
* interpolatePose a progression 1.0 doit retourner les valeurs de pose2.
|
|
*/
|
|
@Test
|
|
void interpolatePose_progressionOne_returnsSecondPose() {
|
|
Pose pose1 = new Pose();
|
|
pose1.putJointData("arm", translation(0.0f, 0.0f, 0.0f));
|
|
|
|
Pose pose2 = new Pose();
|
|
pose2.putJointData("arm", translation(10.0f, 0.0f, 0.0f));
|
|
|
|
Pose result = Pose.interpolatePose(pose1, pose2, 1.0f);
|
|
assertEquals(10.0f, result.get("arm").translation().x, 1e-4f);
|
|
}
|
|
|
|
/**
|
|
* interpolatePose fusionne les joints des deux poses.
|
|
* Un joint present dans pose2 seulement doit apparaitre dans le resultat
|
|
* (interpole avec JointTransform.empty() comme pose1).
|
|
*/
|
|
@Test
|
|
void interpolatePose_mergesJointsFromBothPoses() {
|
|
Pose pose1 = new Pose();
|
|
pose1.putJointData("head", translation(0.0f, 1.0f, 0.0f));
|
|
|
|
Pose pose2 = new Pose();
|
|
pose2.putJointData("hand", translation(2.0f, 0.0f, 0.0f));
|
|
|
|
Pose result = Pose.interpolatePose(pose1, pose2, 0.5f);
|
|
// Les deux joints doivent etre presents
|
|
assertTrue(result.hasTransform("head"), "head from pose1 doit etre dans le resultat");
|
|
assertTrue(result.hasTransform("hand"), "hand from pose2 doit etre dans le resultat");
|
|
}
|
|
|
|
/**
|
|
* interpolatePose a 0.5 doit etre a mi-chemin de la translation.
|
|
*/
|
|
@Test
|
|
void interpolatePose_halfProgression_midpointTranslation() {
|
|
Pose pose1 = new Pose();
|
|
pose1.putJointData("leg", translation(0.0f, 0.0f, 0.0f));
|
|
|
|
Pose pose2 = new Pose();
|
|
pose2.putJointData("leg", translation(4.0f, 0.0f, 0.0f));
|
|
|
|
Pose result = Pose.interpolatePose(pose1, pose2, 0.5f);
|
|
assertEquals(2.0f, result.get("leg").translation().x, 1e-4f);
|
|
}
|
|
|
|
// --- load(LoadOperation) ---
|
|
|
|
/** SET efface les joints existants puis copie depuis la source. */
|
|
@Test
|
|
void load_set_replacesAllJoints() {
|
|
Pose dest = new Pose();
|
|
dest.putJointData("old_joint", JointTransform.empty());
|
|
|
|
Pose src = new Pose();
|
|
src.putJointData("new_joint", translation(1.0f, 0.0f, 0.0f));
|
|
|
|
dest.load(src, Pose.LoadOperation.SET);
|
|
|
|
assertFalse(dest.hasTransform("old_joint"),
|
|
"SET doit supprimer les joints de la destination avant fusion");
|
|
assertTrue(dest.hasTransform("new_joint"),
|
|
"SET doit copier les joints source");
|
|
}
|
|
|
|
/** OVERWRITE ecrase les joints communs sans supprimer les joints uniquement dans dest. */
|
|
@Test
|
|
void load_overwrite_keepsExistingAndOverwritesCommon() {
|
|
Pose dest = new Pose();
|
|
dest.putJointData("kept", JointTransform.empty());
|
|
dest.putJointData("shared", translation(0.0f, 0.0f, 0.0f));
|
|
|
|
Pose src = new Pose();
|
|
src.putJointData("shared", translation(5.0f, 0.0f, 0.0f));
|
|
|
|
dest.load(src, Pose.LoadOperation.OVERWRITE);
|
|
|
|
assertTrue(dest.hasTransform("kept"),
|
|
"OVERWRITE doit conserver les joints de dest non presents dans src");
|
|
assertEquals(5.0f, dest.get("shared").translation().x, 1e-4f,
|
|
"OVERWRITE doit ecraser le joint commun avec la valeur src");
|
|
}
|
|
|
|
/** APPEND_ABSENT n'ajoute que les joints absents de dest. */
|
|
@Test
|
|
void load_appendAbsent_doesNotOverwriteExisting() {
|
|
Pose dest = new Pose();
|
|
dest.putJointData("existing", translation(9.0f, 0.0f, 0.0f));
|
|
|
|
Pose src = new Pose();
|
|
src.putJointData("existing", translation(1.0f, 0.0f, 0.0f));
|
|
src.putJointData("added", translation(2.0f, 0.0f, 0.0f));
|
|
|
|
dest.load(src, Pose.LoadOperation.APPEND_ABSENT);
|
|
|
|
assertEquals(9.0f, dest.get("existing").translation().x, 1e-4f,
|
|
"APPEND_ABSENT ne doit pas ecraser un joint existant");
|
|
assertTrue(dest.hasTransform("added"),
|
|
"APPEND_ABSENT doit ajouter les joints absents");
|
|
}
|
|
|
|
// --- disableAllJoints ---
|
|
|
|
/** disableAllJoints vide la map de transforms. */
|
|
@Test
|
|
void disableAllJoints_clearsTransforms() {
|
|
Pose pose = new Pose();
|
|
pose.putJointData("head", JointTransform.empty());
|
|
pose.putJointData("arm", JointTransform.empty());
|
|
pose.disableAllJoints();
|
|
assertTrue(pose.getJointTransformData().isEmpty(),
|
|
"disableAllJoints doit vider la map de transforms");
|
|
}
|
|
}
|