/* * © 2026 TiedUp! Remake Contributors, distributed under GPLv3. */ package com.tiedup.remake.rig.bridge; import java.util.Map; import javax.annotation.Nullable; import com.google.common.collect.ImmutableMap; /** * Table d'alias runtime pour mapper les noms de joints des GLB legacy TiedUp * (riggés via PlayerAnimator / bendy-lib avec noms type {@code leftUpperArm}) * vers le skeleton biped Epic Fight utilisé par RIG ({@code Arm_L}, etc.). * *

Voir {@code docs/plans/rig/ARCHITECTURE.md §6.3} pour la source de vérité * du mapping. Tout joint inconnu après lookup doit être loggé WARN par le * caller et fallback sur {@code Root}.

* *

Cas spécial "body/torso" — le GLB legacy a souvent un unique joint * couvrant l'ensemble du torse. On le mappe sur {@code Chest} par défaut * (meilleur fit pour les items bondage majoritairement attachés au haut du * corps : harnais, menottes de poitrine, collier). Si un item a besoin * d'attachement à {@code Torso} (ceinture), le modeler devra renommer son * joint en {@code waist} explicitement.

*/ public final class GlbJointAliasTable { /** * Mapping direct PlayerAnimator → biped EF. Les clés sont la forme * lowercase EXACTE des noms exportés par les GLB legacy. */ private static final Map ALIAS = ImmutableMap.builder() // Torso region .put("body", "Chest") .put("torso", "Chest") .put("chest", "Chest") .put("waist", "Torso") .put("hip", "Torso") // Head .put("head", "Head") // Arms left .put("leftshoulder", "Shoulder_L") .put("leftupperarm", "Arm_L") .put("leftarm", "Arm_L") .put("leftlowerarm", "Elbow_L") .put("leftforearm", "Elbow_L") .put("leftelbow", "Elbow_L") .put("lefthand", "Hand_L") // Arms right .put("rightshoulder", "Shoulder_R") .put("rightupperarm", "Arm_R") .put("rightarm", "Arm_R") .put("rightlowerarm", "Elbow_R") .put("rightforearm", "Elbow_R") .put("rightelbow", "Elbow_R") .put("righthand", "Hand_R") // Legs left .put("leftupperleg", "Thigh_L") .put("leftleg", "Thigh_L") .put("leftlowerleg", "Knee_L") .put("leftknee", "Knee_L") .put("leftfoot", "Leg_L") // Legs right .put("rightupperleg", "Thigh_R") .put("rightleg", "Thigh_R") .put("rightlowerleg", "Knee_R") .put("rightknee", "Knee_R") .put("rightfoot", "Leg_R") // Root fallback (déjà nommé Root dans GLB modernes) .put("root", "Root") .put("armature", "Root") .build(); private GlbJointAliasTable() {} /** * Traduit un nom de joint GLB legacy vers le nom biped EF équivalent. * Case-insensitive. Les noms déjà au format biped EF (ex: {@code Arm_L}) sont * retournés tels quels après vérification. * * @param gltfJointName nom tel qu'exporté dans le GLB (jointNames[]) * @return nom biped EF (ex: {@code Arm_L}), ou null si inconnu */ @Nullable public static String mapGltfJointName(String gltfJointName) { if (gltfJointName == null || gltfJointName.isEmpty()) { return null; } // Direct hit sur le biped EF (GLB moderne déjà bien rigged). if (isBipedJointName(gltfJointName)) { return gltfJointName; } return ALIAS.get(gltfJointName.toLowerCase()); } /** * Vérifie si un nom est déjà au format biped EF. Utilisé pour court-circuiter * l'alias lookup sur les GLB modernes. */ public static boolean isBipedJointName(String name) { // Heuristique : les noms biped EF sont en PascalCase avec suffixe _R/_L, // ou parmi {Root, Torso, Chest, Head}. return switch (name) { case "Root", "Torso", "Chest", "Head" -> true; default -> name.endsWith("_R") || name.endsWith("_L"); }; } }