Clean repo for open source release

Remove build artifacts, dev tool configs, unused dependencies,
and third-party source dumps. Add proper README, update .gitignore,
clean up Makefile.
This commit is contained in:
NotEvil
2026-04-12 00:51:22 +02:00
parent 2e7a1d403b
commit f6466360b6
1947 changed files with 238025 additions and 1 deletions

View File

@@ -0,0 +1,245 @@
package com.tiedup.remake.client.gltf;
import dev.kosmx.playerAnim.core.util.Pair;
import dev.kosmx.playerAnim.impl.IAnimatedPlayer;
import dev.kosmx.playerAnim.impl.animation.AnimationApplier;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import org.joml.Vector3f;
/**
* Reads the LIVE skeleton state from HumanoidModel (after PlayerAnimator + bendy-lib
* have applied all rotations for the current frame) and produces joint matrices
* compatible with {@link GltfSkinningEngine#skinVertex}.
* <p>
* KEY INSIGHT: The ModelPart xRot/yRot/zRot values set by PlayerAnimator represent
* DELTA rotations (difference from rest pose) expressed in the MC model-def frame.
* GltfPoseConverter computed them as parent-frame deltas, decomposed to Euler ZYX.
* <p>
* To reconstruct the correct LOCAL rotation for the glTF hierarchy:
* <pre>
* delta = rotationZYX(zRot, yRot, xRot) // MC-frame delta from ModelPart
* localRot = delta * restQ_mc // delta applied on top of local rest
* </pre>
* No de-parenting is needed because both delta and restQ_mc are already in the
* parent's local frame. The MC-to-glTF conjugation (negate qx,qy) is a homomorphism,
* so frame relationships are preserved through the conversion.
* <p>
* For bones WITHOUT a MC ModelPart (root, torso), use the MC-converted rest rotation
* directly from GltfData.
*/
@OnlyIn(Dist.CLIENT)
public final class GltfLiveBoneReader {
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");
private GltfLiveBoneReader() {}
/**
* Compute joint matrices by reading live skeleton state from the HumanoidModel.
* <p>
* For upper bones: reconstructs the MC-frame delta from ModelPart euler angles,
* then composes with the MC-converted rest rotation to get the local rotation.
* For lower bones: reads bend values from the entity's AnimationApplier and
* composes the bend delta with the local rest rotation.
* For non-animated bones: uses rest rotation from GltfData directly.
* <p>
* The resulting joint matrices should match {@link GltfSkinningEngine#computeJointMatrices}
* when the player is in the rest pose (no animation active).
*
* @param model the HumanoidModel after PlayerAnimator has applied rotations
* @param data parsed glTF data (MC-converted)
* @param entity the living entity being rendered
* @return array of joint matrices ready for skinning, or null on failure
*/
public static Matrix4f[] computeJointMatricesFromModel(
HumanoidModel<?> model, GltfData data, LivingEntity entity
) {
if (model == null || data == null || entity == null) return null;
int jointCount = data.jointCount();
Matrix4f[] jointMatrices = new Matrix4f[jointCount];
Matrix4f[] worldTransforms = new Matrix4f[jointCount];
int[] parents = data.parentJointIndices();
String[] jointNames = data.jointNames();
Quaternionf[] restRotations = data.restRotations();
Vector3f[] restTranslations = data.restTranslations();
// Get the AnimationApplier for bend values (may be null)
AnimationApplier emote = getAnimationApplier(entity);
for (int j = 0; j < jointCount; j++) {
String boneName = jointNames[j];
Quaternionf localRot;
if (GltfBoneMapper.isLowerBone(boneName)) {
// --- Lower bone: reconstruct from bend values ---
localRot = computeLowerBoneLocalRotation(
boneName, j, restRotations, emote
);
} else if (hasUniqueModelPart(boneName)) {
// --- Upper bone with a unique ModelPart ---
ModelPart part = GltfBoneMapper.getModelPart(model, boneName);
if (part != null) {
localRot = computeUpperBoneLocalRotation(
part, j, restRotations
);
} else {
// Fallback: use rest rotation
localRot = new Quaternionf(restRotations[j]);
}
} else {
// --- Non-animated bone (root, torso, etc.): use rest rotation ---
localRot = new Quaternionf(restRotations[j]);
}
// Build local transform: translate(restTranslation) * rotate(localRot)
Matrix4f local = new Matrix4f();
local.translate(restTranslations[j]);
local.rotate(localRot);
// Compose with parent to get world transform
if (parents[j] >= 0 && worldTransforms[parents[j]] != null) {
worldTransforms[j] = new Matrix4f(worldTransforms[parents[j]]).mul(local);
} else {
worldTransforms[j] = new Matrix4f(local);
}
// Final joint matrix = worldTransform * inverseBindMatrix
jointMatrices[j] = new Matrix4f(worldTransforms[j])
.mul(data.inverseBindMatrices()[j]);
}
return jointMatrices;
}
/**
* Compute local rotation for an upper bone that has a unique ModelPart.
* <p>
* ModelPart xRot/yRot/zRot are DELTA rotations (set by PlayerAnimator) expressed
* as ZYX Euler angles in the MC model-def frame. These deltas were originally
* computed by GltfPoseConverter as parent-frame quantities.
* <p>
* The local rotation for the glTF hierarchy is simply:
* <pre>
* delta = rotationZYX(zRot, yRot, xRot)
* localRot = delta * restQ_mc
* </pre>
* No de-parenting is needed: both delta and restQ_mc are already in the parent's
* frame. The MC-to-glTF negate-xy conjugation is a group homomorphism, preserving
* the frame relationship.
*/
private static Quaternionf computeUpperBoneLocalRotation(
ModelPart part, int jointIndex,
Quaternionf[] restRotations
) {
// Reconstruct the MC-frame delta from ModelPart euler angles.
Quaternionf delta = new Quaternionf().rotationZYX(part.zRot, part.yRot, part.xRot);
// Local rotation = delta applied on top of the local rest rotation.
return new Quaternionf(delta).mul(restRotations[jointIndex]);
}
/**
* Compute local rotation for a lower bone (elbow/knee) from bend values.
* <p>
* Bend values are read from the entity's AnimationApplier. The bend delta is
* reconstructed as a quaternion rotation around the bend axis, then composed
* with the local rest rotation:
* <pre>
* bendQuat = axisAngle(cos(bendAxis)*s, 0, sin(bendAxis)*s, cos(halfAngle))
* localRot = bendQuat * restQ_mc
* </pre>
* No de-parenting needed — same reasoning as upper bones.
*/
private static Quaternionf computeLowerBoneLocalRotation(
String boneName, int jointIndex,
Quaternionf[] restRotations,
AnimationApplier emote
) {
if (emote != null) {
// Get the MC part name for the upper bone of this lower bone
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
String animPartName = (upperBone != null)
? GltfBoneMapper.getAnimPartName(upperBone)
: null;
if (animPartName != null) {
Pair<Float, Float> bend = emote.getBend(animPartName);
if (bend != null) {
float bendAxis = bend.getLeft();
float bendValue = bend.getRight();
// Reconstruct bend as quaternion (this is the delta)
float ax = (float) Math.cos(bendAxis);
float az = (float) Math.sin(bendAxis);
float halfAngle = bendValue * 0.5f;
float s = (float) Math.sin(halfAngle);
Quaternionf bendQuat = new Quaternionf(
ax * s, 0, az * s, (float) Math.cos(halfAngle)
);
// Local rotation = bend delta applied on top of local rest rotation
return new Quaternionf(bendQuat).mul(restRotations[jointIndex]);
}
}
}
// No bend data or no AnimationApplier — use rest rotation (identity delta)
return new Quaternionf(restRotations[jointIndex]);
}
/**
* Check if a bone name corresponds to a bone that has its OWN unique ModelPart
* (not just a mapping — it must be the PRIMARY bone for that ModelPart).
* <p>
* "torso" maps to model.body but "body" is the primary bone for it.
* Lower bones share a ModelPart with their upper bone.
* Unknown bones (e.g., "PlayerArmature") have no ModelPart at all.
*/
private static boolean hasUniqueModelPart(String boneName) {
// Bones that should read their rotation from the live HumanoidModel.
//
// NOTE: "body" is deliberately EXCLUDED. MC's HumanoidModel is FLAT —
// body, arms, legs, head are all siblings with ABSOLUTE rotations.
// But the GLB skeleton is HIERARCHICAL (body → torso → arms).
// If we read body's live rotation (e.g., attack swing yRot), it propagates
// to arms/head through the hierarchy, but MC's flat model does NOT do this.
// Result: cuffs mesh rotates with body during attack while arms stay put.
//
// Body rotation effects that matter (sneak lean, sitting) are handled by
// LivingEntityRenderer's PoseStack transform, which applies to the entire
// mesh uniformly. No need to read body rotation into joint matrices.
return switch (boneName) {
case "head" -> true;
case "leftUpperArm" -> true;
case "rightUpperArm"-> true;
case "leftUpperLeg" -> true;
case "rightUpperLeg"-> true;
default -> false; // body, torso, lower bones, unknown
};
}
/**
* Get the AnimationApplier from an entity, if available.
* Works for both players (via mixin) and NPCs implementing IAnimatedPlayer.
*/
private static AnimationApplier getAnimationApplier(LivingEntity entity) {
if (entity instanceof IAnimatedPlayer animated) {
try {
return animated.playerAnimator_getAnimation();
} catch (Exception e) {
LOGGER.debug("[GltfPipeline] Could not get AnimationApplier for {}: {}",
entity.getClass().getSimpleName(), e.getMessage());
}
}
return null;
}
}