package com.tiedup.remake.client.gltf; import java.util.HashMap; import java.util.Map; import java.util.Set; import net.minecraft.client.model.HumanoidModel; import net.minecraft.client.model.geom.ModelPart; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import org.jetbrains.annotations.Nullable; /** * Maps glTF bone names to Minecraft HumanoidModel parts. * Handles upper bones (full rotation) and lower bones (bend only). */ @OnlyIn(Dist.CLIENT) public final class GltfBoneMapper { /** Maps glTF bone name -> MC model part field name */ private static final Map BONE_TO_PART = new HashMap<>(); /** Lower bones that represent bend (elbow/knee) */ private static final Set LOWER_BONES = Set.of( "leftLowerArm", "rightLowerArm", "leftLowerLeg", "rightLowerLeg" ); /** Maps lower bone name -> corresponding upper bone name */ private static final Map LOWER_TO_UPPER = Map.of( "leftLowerArm", "leftUpperArm", "rightLowerArm", "rightUpperArm", "leftLowerLeg", "leftUpperLeg", "rightLowerLeg", "rightUpperLeg" ); static { BONE_TO_PART.put("body", "body"); BONE_TO_PART.put("torso", "body"); BONE_TO_PART.put("head", "head"); BONE_TO_PART.put("leftUpperArm", "leftArm"); BONE_TO_PART.put("leftLowerArm", "leftArm"); BONE_TO_PART.put("rightUpperArm", "rightArm"); BONE_TO_PART.put("rightLowerArm", "rightArm"); BONE_TO_PART.put("leftUpperLeg", "leftLeg"); BONE_TO_PART.put("leftLowerLeg", "leftLeg"); BONE_TO_PART.put("rightUpperLeg", "rightLeg"); BONE_TO_PART.put("rightLowerLeg", "rightLeg"); } private GltfBoneMapper() {} /** * Get the ModelPart corresponding to a glTF bone name. * * @param model the HumanoidModel * @param boneName glTF bone name * @return the ModelPart, or null if not mapped */ public static ModelPart getModelPart( HumanoidModel model, String boneName ) { String partName = BONE_TO_PART.get(boneName); if (partName == null) return null; return switch (partName) { case "body" -> model.body; case "head" -> model.head; case "leftArm" -> model.leftArm; case "rightArm" -> model.rightArm; case "leftLeg" -> model.leftLeg; case "rightLeg" -> model.rightLeg; default -> null; }; } /** * Check if this bone represents a lower segment (bend: elbow/knee). */ public static boolean isLowerBone(String boneName) { return LOWER_BONES.contains(boneName); } /** * Get the upper bone name for a given lower bone. * Returns null if not a lower bone. */ public static String getUpperBoneFor(String lowerBoneName) { return LOWER_TO_UPPER.get(lowerBoneName); } /** * Get the PlayerAnimator part name for a glTF bone. * Both glTF and PlayerAnimator use "body" for the torso part. */ public static String getAnimPartName(String boneName) { String partName = BONE_TO_PART.get(boneName); if (partName == null) return null; return partName; } /** * Check if a bone name is known/mapped. */ public static boolean isKnownBone(String boneName) { return BONE_TO_PART.containsKey(boneName); } /** * Get all known bone names for validation/suggestion purposes. */ public static Set knownBoneNames() { return BONE_TO_PART.keySet(); } /** * Suggest a known bone name for a case-insensitive match. * Returns null if no case-insensitive match is found. */ @Nullable public static String suggestBoneName(String unknownBone) { for (String known : BONE_TO_PART.keySet()) { if (known.equalsIgnoreCase(unknownBone)) { return known; } } return null; } }