Remove internal phase comments and format code
Strip all Phase references, TODO/FUTURE roadmap notes, and internal planning comments from the codebase. Run Prettier for consistent formatting across all Java files.
This commit is contained in:
@@ -31,7 +31,7 @@ public final class GlbParser {
|
||||
private static final int GLB_MAGIC = 0x46546C67; // "glTF"
|
||||
private static final int GLB_VERSION = 2;
|
||||
private static final int CHUNK_JSON = 0x4E4F534A; // "JSON"
|
||||
private static final int CHUNK_BIN = 0x004E4942; // "BIN\0"
|
||||
private static final int CHUNK_BIN = 0x004E4942; // "BIN\0"
|
||||
|
||||
private GlbParser() {}
|
||||
|
||||
@@ -43,9 +43,12 @@ public final class GlbParser {
|
||||
* @return parsed GltfData
|
||||
* @throws IOException if the file is malformed or I/O fails
|
||||
*/
|
||||
public static GltfData parse(InputStream input, String debugName) throws IOException {
|
||||
public static GltfData parse(InputStream input, String debugName)
|
||||
throws IOException {
|
||||
byte[] allBytes = input.readAllBytes();
|
||||
ByteBuffer buf = ByteBuffer.wrap(allBytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||
ByteBuffer buf = ByteBuffer.wrap(allBytes).order(
|
||||
ByteOrder.LITTLE_ENDIAN
|
||||
);
|
||||
|
||||
// -- Header --
|
||||
int magic = buf.getInt();
|
||||
@@ -54,7 +57,9 @@ public final class GlbParser {
|
||||
}
|
||||
int version = buf.getInt();
|
||||
if (version != GLB_VERSION) {
|
||||
throw new IOException("Unsupported GLB version " + version + " in " + debugName);
|
||||
throw new IOException(
|
||||
"Unsupported GLB version " + version + " in " + debugName
|
||||
);
|
||||
}
|
||||
int totalLength = buf.getInt();
|
||||
|
||||
@@ -105,12 +110,18 @@ public final class GlbParser {
|
||||
for (int j = 0; j < skinJoints.size(); j++) {
|
||||
int nodeIdx = skinJoints.get(j).getAsInt();
|
||||
JsonObject node = nodes.get(nodeIdx).getAsJsonObject();
|
||||
String name = node.has("name") ? node.get("name").getAsString() : "joint_" + j;
|
||||
String name = node.has("name")
|
||||
? node.get("name").getAsString()
|
||||
: "joint_" + j;
|
||||
if (GltfBoneMapper.isKnownBone(name)) {
|
||||
skinJointRemap[j] = filteredJointNodes.size();
|
||||
filteredJointNodes.add(nodeIdx);
|
||||
} else {
|
||||
LOGGER.debug("[GltfPipeline] Skipping non-deforming bone: '{}' (node {})", name, nodeIdx);
|
||||
LOGGER.debug(
|
||||
"[GltfPipeline] Skipping non-deforming bone: '{}' (node {})",
|
||||
name,
|
||||
nodeIdx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,14 +145,18 @@ public final class GlbParser {
|
||||
int nodeIdx = filteredJointNodes.get(j);
|
||||
JsonObject node = nodes.get(nodeIdx).getAsJsonObject();
|
||||
|
||||
jointNames[j] = node.has("name") ? node.get("name").getAsString() : "joint_" + j;
|
||||
jointNames[j] = node.has("name")
|
||||
? node.get("name").getAsString()
|
||||
: "joint_" + j;
|
||||
|
||||
// Rest rotation
|
||||
if (node.has("rotation")) {
|
||||
JsonArray r = node.getAsJsonArray("rotation");
|
||||
restRotations[j] = new Quaternionf(
|
||||
r.get(0).getAsFloat(), r.get(1).getAsFloat(),
|
||||
r.get(2).getAsFloat(), r.get(3).getAsFloat()
|
||||
r.get(0).getAsFloat(),
|
||||
r.get(1).getAsFloat(),
|
||||
r.get(2).getAsFloat(),
|
||||
r.get(3).getAsFloat()
|
||||
);
|
||||
} else {
|
||||
restRotations[j] = new Quaternionf(); // identity
|
||||
@@ -151,7 +166,9 @@ public final class GlbParser {
|
||||
if (node.has("translation")) {
|
||||
JsonArray t = node.getAsJsonArray("translation");
|
||||
restTranslations[j] = new Vector3f(
|
||||
t.get(0).getAsFloat(), t.get(1).getAsFloat(), t.get(2).getAsFloat()
|
||||
t.get(0).getAsFloat(),
|
||||
t.get(1).getAsFloat(),
|
||||
t.get(2).getAsFloat()
|
||||
);
|
||||
} else {
|
||||
restTranslations[j] = new Vector3f();
|
||||
@@ -179,7 +196,12 @@ public final class GlbParser {
|
||||
Matrix4f[] inverseBindMatrices = new Matrix4f[jointCount];
|
||||
if (skin.has("inverseBindMatrices")) {
|
||||
int ibmAccessor = skin.get("inverseBindMatrices").getAsInt();
|
||||
float[] ibmData = GlbParserUtils.readFloatAccessor(accessors, bufferViews, binData, ibmAccessor);
|
||||
float[] ibmData = GlbParserUtils.readFloatAccessor(
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
ibmAccessor
|
||||
);
|
||||
for (int origJ = 0; origJ < skinJoints.size(); origJ++) {
|
||||
int newJ = skinJointRemap[origJ];
|
||||
if (newJ >= 0) {
|
||||
@@ -200,7 +222,9 @@ public final class GlbParser {
|
||||
if (meshes != null) {
|
||||
for (int mi = 0; mi < meshes.size(); mi++) {
|
||||
JsonObject mesh = meshes.get(mi).getAsJsonObject();
|
||||
String meshName = mesh.has("name") ? mesh.get("name").getAsString() : "";
|
||||
String meshName = mesh.has("name")
|
||||
? mesh.get("name").getAsString()
|
||||
: "";
|
||||
if (!"Player".equals(meshName)) {
|
||||
targetMeshIdx = mi;
|
||||
}
|
||||
@@ -238,15 +262,27 @@ public final class GlbParser {
|
||||
|
||||
// -- Read this primitive's vertex data --
|
||||
float[] primPositions = GlbParserUtils.readFloatAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
attributes.get("POSITION").getAsInt()
|
||||
);
|
||||
float[] primNormals = attributes.has("NORMAL")
|
||||
? GlbParserUtils.readFloatAccessor(accessors, bufferViews, binData, attributes.get("NORMAL").getAsInt())
|
||||
? GlbParserUtils.readFloatAccessor(
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
attributes.get("NORMAL").getAsInt()
|
||||
)
|
||||
: new float[primPositions.length];
|
||||
float[] primTexCoords = attributes.has("TEXCOORD_0")
|
||||
? GlbParserUtils.readFloatAccessor(accessors, bufferViews, binData, attributes.get("TEXCOORD_0").getAsInt())
|
||||
: new float[primPositions.length / 3 * 2];
|
||||
? GlbParserUtils.readFloatAccessor(
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
attributes.get("TEXCOORD_0").getAsInt()
|
||||
)
|
||||
: new float[(primPositions.length / 3) * 2];
|
||||
|
||||
int primVertexCount = primPositions.length / 3;
|
||||
|
||||
@@ -254,13 +290,16 @@ public final class GlbParser {
|
||||
int[] primIndices;
|
||||
if (primitive.has("indices")) {
|
||||
primIndices = GlbParserUtils.readIntAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
primitive.get("indices").getAsInt()
|
||||
);
|
||||
} else {
|
||||
// Non-indexed: generate sequential indices
|
||||
primIndices = new int[primVertexCount];
|
||||
for (int i = 0; i < primVertexCount; i++) primIndices[i] = i;
|
||||
for (int i = 0; i < primVertexCount; i++) primIndices[i] =
|
||||
i;
|
||||
}
|
||||
|
||||
// Offset indices by cumulative vertex count from prior primitives
|
||||
@@ -276,14 +315,19 @@ public final class GlbParser {
|
||||
|
||||
if (attributes.has("JOINTS_0")) {
|
||||
primJoints = GlbParserUtils.readIntAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
attributes.get("JOINTS_0").getAsInt()
|
||||
);
|
||||
// Remap vertex joint indices from original skin order to filtered order
|
||||
for (int i = 0; i < primJoints.length; i++) {
|
||||
int origIdx = primJoints[i];
|
||||
if (origIdx >= 0 && origIdx < skinJointRemap.length) {
|
||||
primJoints[i] = skinJointRemap[origIdx] >= 0 ? skinJointRemap[origIdx] : 0;
|
||||
primJoints[i] =
|
||||
skinJointRemap[origIdx] >= 0
|
||||
? skinJointRemap[origIdx]
|
||||
: 0;
|
||||
} else {
|
||||
primJoints[i] = 0;
|
||||
}
|
||||
@@ -291,7 +335,9 @@ public final class GlbParser {
|
||||
}
|
||||
if (attributes.has("WEIGHTS_0")) {
|
||||
primWeights = GlbParserUtils.readFloatAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
attributes.get("WEIGHTS_0").getAsInt()
|
||||
);
|
||||
}
|
||||
@@ -304,10 +350,18 @@ public final class GlbParser {
|
||||
matName = materialNames[matIdx];
|
||||
}
|
||||
}
|
||||
boolean isTintable = matName != null && matName.startsWith("tintable_");
|
||||
boolean isTintable =
|
||||
matName != null && matName.startsWith("tintable_");
|
||||
String tintChannel = isTintable ? matName : null;
|
||||
|
||||
parsedPrimitives.add(new GltfData.Primitive(primIndices, matName, isTintable, tintChannel));
|
||||
parsedPrimitives.add(
|
||||
new GltfData.Primitive(
|
||||
primIndices,
|
||||
matName,
|
||||
isTintable,
|
||||
tintChannel
|
||||
)
|
||||
);
|
||||
|
||||
allPositions.add(primPositions);
|
||||
allNormals.add(primNormals);
|
||||
@@ -327,16 +381,26 @@ public final class GlbParser {
|
||||
|
||||
// Build union of all primitive indices (for backward-compat indices() accessor)
|
||||
int totalIndices = 0;
|
||||
for (GltfData.Primitive p : parsedPrimitives) totalIndices += p.indices().length;
|
||||
for (GltfData.Primitive p : parsedPrimitives)
|
||||
totalIndices += p.indices().length;
|
||||
indices = new int[totalIndices];
|
||||
int offset = 0;
|
||||
for (GltfData.Primitive p : parsedPrimitives) {
|
||||
System.arraycopy(p.indices(), 0, indices, offset, p.indices().length);
|
||||
System.arraycopy(
|
||||
p.indices(),
|
||||
0,
|
||||
indices,
|
||||
offset,
|
||||
p.indices().length
|
||||
);
|
||||
offset += p.indices().length;
|
||||
}
|
||||
} else {
|
||||
// Animation-only GLB: no mesh data
|
||||
LOGGER.info("[GltfPipeline] No mesh found in '{}' (animation-only GLB)", debugName);
|
||||
LOGGER.info(
|
||||
"[GltfPipeline] No mesh found in '{}' (animation-only GLB)",
|
||||
debugName
|
||||
);
|
||||
positions = new float[0];
|
||||
normals = new float[0];
|
||||
texCoords = new float[0];
|
||||
@@ -352,12 +416,23 @@ public final class GlbParser {
|
||||
if (animations != null) {
|
||||
for (int ai = 0; ai < animations.size(); ai++) {
|
||||
JsonObject anim = animations.get(ai).getAsJsonObject();
|
||||
String animName = anim.has("name") ? anim.get("name").getAsString() : "animation_" + ai;
|
||||
String animName = anim.has("name")
|
||||
? anim.get("name").getAsString()
|
||||
: "animation_" + ai;
|
||||
// Strip the "ArmatureName|" prefix if present (Blender convention)
|
||||
if (animName.contains("|")) {
|
||||
animName = animName.substring(animName.lastIndexOf('|') + 1);
|
||||
animName = animName.substring(
|
||||
animName.lastIndexOf('|') + 1
|
||||
);
|
||||
}
|
||||
GltfData.AnimationClip clip = parseAnimation(anim, accessors, bufferViews, binData, nodeToJoint, jointCount);
|
||||
GltfData.AnimationClip clip = parseAnimation(
|
||||
anim,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
nodeToJoint,
|
||||
jointCount
|
||||
);
|
||||
if (clip != null) {
|
||||
allClips.put(animName, clip);
|
||||
}
|
||||
@@ -365,19 +440,39 @@ public final class GlbParser {
|
||||
}
|
||||
|
||||
// Default animation = first clip (for backward compat)
|
||||
GltfData.AnimationClip animClip = allClips.isEmpty() ? null : allClips.values().iterator().next();
|
||||
GltfData.AnimationClip animClip = allClips.isEmpty()
|
||||
? null
|
||||
: allClips.values().iterator().next();
|
||||
|
||||
LOGGER.info("[GltfPipeline] Parsed '{}': vertices={}, indices={}, joints={}, animations={}",
|
||||
debugName, vertexCount, indices.length, jointCount, allClips.size());
|
||||
LOGGER.info(
|
||||
"[GltfPipeline] Parsed '{}': vertices={}, indices={}, joints={}, animations={}",
|
||||
debugName,
|
||||
vertexCount,
|
||||
indices.length,
|
||||
jointCount,
|
||||
allClips.size()
|
||||
);
|
||||
for (String name : allClips.keySet()) {
|
||||
LOGGER.debug("[GltfPipeline] animation: '{}'", name);
|
||||
}
|
||||
for (int j = 0; j < jointCount; j++) {
|
||||
Quaternionf rq = restRotations[j];
|
||||
Vector3f rt = restTranslations[j];
|
||||
LOGGER.debug(String.format("[GltfPipeline] joint[%d] = '%s', parent=%d, restQ=(%.3f,%.3f,%.3f,%.3f) restT=(%.3f,%.3f,%.3f)",
|
||||
j, jointNames[j], parentJointIndices[j],
|
||||
rq.x, rq.y, rq.z, rq.w, rt.x, rt.y, rt.z));
|
||||
LOGGER.debug(
|
||||
String.format(
|
||||
"[GltfPipeline] joint[%d] = '%s', parent=%d, restQ=(%.3f,%.3f,%.3f,%.3f) restT=(%.3f,%.3f,%.3f)",
|
||||
j,
|
||||
jointNames[j],
|
||||
parentJointIndices[j],
|
||||
rq.x,
|
||||
rq.y,
|
||||
rq.z,
|
||||
rq.w,
|
||||
rt.x,
|
||||
rt.y,
|
||||
rt.z
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Log animation translation channels for default clip (BEFORE MC conversion)
|
||||
@@ -387,16 +482,28 @@ public final class GlbParser {
|
||||
if (j < animTrans.length && animTrans[j] != null) {
|
||||
Vector3f at = animTrans[j][0]; // first frame
|
||||
Vector3f rt = restTranslations[j];
|
||||
LOGGER.debug(String.format(
|
||||
"[GltfPipeline] joint[%d] '%s' has ANIM TRANSLATION: (%.4f,%.4f,%.4f) vs rest (%.4f,%.4f,%.4f) delta=(%.4f,%.4f,%.4f)",
|
||||
j, jointNames[j],
|
||||
at.x, at.y, at.z,
|
||||
rt.x, rt.y, rt.z,
|
||||
at.x - rt.x, at.y - rt.y, at.z - rt.z));
|
||||
LOGGER.debug(
|
||||
String.format(
|
||||
"[GltfPipeline] joint[%d] '%s' has ANIM TRANSLATION: (%.4f,%.4f,%.4f) vs rest (%.4f,%.4f,%.4f) delta=(%.4f,%.4f,%.4f)",
|
||||
j,
|
||||
jointNames[j],
|
||||
at.x,
|
||||
at.y,
|
||||
at.z,
|
||||
rt.x,
|
||||
rt.y,
|
||||
rt.z,
|
||||
at.x - rt.x,
|
||||
at.y - rt.y,
|
||||
at.z - rt.z
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGGER.debug("[GltfPipeline] Default animation has NO translation channels");
|
||||
LOGGER.debug(
|
||||
"[GltfPipeline] Default animation has NO translation channels"
|
||||
);
|
||||
}
|
||||
|
||||
// Save raw glTF rotations BEFORE coordinate conversion (for pose converter)
|
||||
@@ -409,10 +516,18 @@ public final class GlbParser {
|
||||
|
||||
// Build raw copies of ALL animation clips (before MC conversion)
|
||||
Map<String, GltfData.AnimationClip> rawAllClips = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, GltfData.AnimationClip> entry : allClips.entrySet()) {
|
||||
rawAllClips.put(entry.getKey(), GlbParserUtils.deepCopyClip(entry.getValue()));
|
||||
for (Map.Entry<
|
||||
String,
|
||||
GltfData.AnimationClip
|
||||
> entry : allClips.entrySet()) {
|
||||
rawAllClips.put(
|
||||
entry.getKey(),
|
||||
GlbParserUtils.deepCopyClip(entry.getValue())
|
||||
);
|
||||
}
|
||||
GltfData.AnimationClip rawAnimClip = rawAllClips.isEmpty() ? null : rawAllClips.values().iterator().next();
|
||||
GltfData.AnimationClip rawAnimClip = rawAllClips.isEmpty()
|
||||
? null
|
||||
: rawAllClips.values().iterator().next();
|
||||
|
||||
// Convert from glTF coordinate system (Y-up, faces +Z) to MC (Y-up, faces -Z)
|
||||
// This is a 180° rotation around Y: negate X and Z for all spatial data
|
||||
@@ -420,22 +535,39 @@ public final class GlbParser {
|
||||
for (GltfData.AnimationClip clip : allClips.values()) {
|
||||
GlbParserUtils.convertAnimationToMinecraftSpace(clip, jointCount);
|
||||
}
|
||||
convertToMinecraftSpace(positions, normals, restTranslations, restRotations,
|
||||
inverseBindMatrices, null, jointCount); // pass null — clips already converted above
|
||||
LOGGER.debug("[GltfPipeline] Converted all data to Minecraft coordinate space");
|
||||
convertToMinecraftSpace(
|
||||
positions,
|
||||
normals,
|
||||
restTranslations,
|
||||
restRotations,
|
||||
inverseBindMatrices,
|
||||
null,
|
||||
jointCount
|
||||
); // pass null — clips already converted above
|
||||
LOGGER.debug(
|
||||
"[GltfPipeline] Converted all data to Minecraft coordinate space"
|
||||
);
|
||||
|
||||
return new GltfData(
|
||||
positions, normals, texCoords,
|
||||
indices, meshJoints, weights,
|
||||
jointNames, parentJointIndices,
|
||||
positions,
|
||||
normals,
|
||||
texCoords,
|
||||
indices,
|
||||
meshJoints,
|
||||
weights,
|
||||
jointNames,
|
||||
parentJointIndices,
|
||||
inverseBindMatrices,
|
||||
restRotations, restTranslations,
|
||||
restRotations,
|
||||
restTranslations,
|
||||
rawRestRotations,
|
||||
rawAnimClip,
|
||||
animClip,
|
||||
allClips, rawAllClips,
|
||||
allClips,
|
||||
rawAllClips,
|
||||
parsedPrimitives,
|
||||
vertexCount, jointCount
|
||||
vertexCount,
|
||||
jointCount
|
||||
);
|
||||
}
|
||||
|
||||
@@ -443,9 +575,11 @@ public final class GlbParser {
|
||||
|
||||
private static GltfData.AnimationClip parseAnimation(
|
||||
JsonObject animation,
|
||||
JsonArray accessors, JsonArray bufferViews,
|
||||
JsonArray accessors,
|
||||
JsonArray bufferViews,
|
||||
ByteBuffer binData,
|
||||
int[] nodeToJoint, int jointCount
|
||||
int[] nodeToJoint,
|
||||
int jointCount
|
||||
) {
|
||||
JsonArray channels = animation.getAsJsonArray("channels");
|
||||
JsonArray samplers = animation.getAsJsonArray("samplers");
|
||||
@@ -465,27 +599,35 @@ public final class GlbParser {
|
||||
String path = target.get("path").getAsString();
|
||||
|
||||
int nodeIdx = target.get("node").getAsInt();
|
||||
if (nodeIdx >= nodeToJoint.length || nodeToJoint[nodeIdx] < 0) continue;
|
||||
if (
|
||||
nodeIdx >= nodeToJoint.length || nodeToJoint[nodeIdx] < 0
|
||||
) continue;
|
||||
int jointIdx = nodeToJoint[nodeIdx];
|
||||
|
||||
int samplerIdx = channel.get("sampler").getAsInt();
|
||||
JsonObject sampler = samplers.get(samplerIdx).getAsJsonObject();
|
||||
|
||||
float[] times = GlbParserUtils.readFloatAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
sampler.get("input").getAsInt()
|
||||
);
|
||||
|
||||
if ("rotation".equals(path)) {
|
||||
float[] quats = GlbParserUtils.readFloatAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
sampler.get("output").getAsInt()
|
||||
);
|
||||
Quaternionf[] qArr = new Quaternionf[times.length];
|
||||
for (int i = 0; i < times.length; i++) {
|
||||
qArr[i] = new Quaternionf(
|
||||
quats[i * 4], quats[i * 4 + 1],
|
||||
quats[i * 4 + 2], quats[i * 4 + 3]
|
||||
quats[i * 4],
|
||||
quats[i * 4 + 1],
|
||||
quats[i * 4 + 2],
|
||||
quats[i * 4 + 3]
|
||||
);
|
||||
}
|
||||
rotJoints.add(jointIdx);
|
||||
@@ -493,13 +635,17 @@ public final class GlbParser {
|
||||
rotValues.add(qArr);
|
||||
} else if ("translation".equals(path)) {
|
||||
float[] vecs = GlbParserUtils.readFloatAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
sampler.get("output").getAsInt()
|
||||
);
|
||||
Vector3f[] tArr = new Vector3f[times.length];
|
||||
for (int i = 0; i < times.length; i++) {
|
||||
tArr[i] = new Vector3f(
|
||||
vecs[i * 3], vecs[i * 3 + 1], vecs[i * 3 + 2]
|
||||
vecs[i * 3],
|
||||
vecs[i * 3 + 1],
|
||||
vecs[i * 3 + 2]
|
||||
);
|
||||
}
|
||||
transJoints.add(jointIdx);
|
||||
@@ -523,7 +669,8 @@ public final class GlbParser {
|
||||
Quaternionf[] vals = rotValues.get(i);
|
||||
rotations[jIdx] = new Quaternionf[frameCount];
|
||||
for (int f = 0; f < frameCount; f++) {
|
||||
rotations[jIdx][f] = f < vals.length ? vals[f] : vals[vals.length - 1];
|
||||
rotations[jIdx][f] =
|
||||
f < vals.length ? vals[f] : vals[vals.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,19 +681,27 @@ public final class GlbParser {
|
||||
Vector3f[] vals = transValues.get(i);
|
||||
translations[jIdx] = new Vector3f[frameCount];
|
||||
for (int f = 0; f < frameCount; f++) {
|
||||
translations[jIdx][f] = f < vals.length
|
||||
? new Vector3f(vals[f])
|
||||
: new Vector3f(vals[vals.length - 1]);
|
||||
translations[jIdx][f] =
|
||||
f < vals.length
|
||||
? new Vector3f(vals[f])
|
||||
: new Vector3f(vals[vals.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Log translation channels found
|
||||
if (!transJoints.isEmpty()) {
|
||||
LOGGER.debug("[GltfPipeline] Animation has {} translation channel(s)",
|
||||
transJoints.size());
|
||||
LOGGER.debug(
|
||||
"[GltfPipeline] Animation has {} translation channel(s)",
|
||||
transJoints.size()
|
||||
);
|
||||
}
|
||||
|
||||
return new GltfData.AnimationClip(timestamps, rotations, translations, frameCount);
|
||||
return new GltfData.AnimationClip(
|
||||
timestamps,
|
||||
rotations,
|
||||
translations,
|
||||
frameCount
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Coordinate system conversion ----
|
||||
@@ -562,14 +717,17 @@ public final class GlbParser {
|
||||
* For matrices: M → C * M * C where C = diag(-1, -1, 1, 1)
|
||||
*/
|
||||
private static void convertToMinecraftSpace(
|
||||
float[] positions, float[] normals,
|
||||
Vector3f[] restTranslations, Quaternionf[] restRotations,
|
||||
float[] positions,
|
||||
float[] normals,
|
||||
Vector3f[] restTranslations,
|
||||
Quaternionf[] restRotations,
|
||||
Matrix4f[] inverseBindMatrices,
|
||||
GltfData.AnimationClip animClip, int jointCount
|
||||
GltfData.AnimationClip animClip,
|
||||
int jointCount
|
||||
) {
|
||||
// Vertex positions: negate X and Y
|
||||
for (int i = 0; i < positions.length; i += 3) {
|
||||
positions[i] = -positions[i]; // X
|
||||
positions[i] = -positions[i]; // X
|
||||
positions[i + 1] = -positions[i + 1]; // Y
|
||||
}
|
||||
|
||||
|
||||
@@ -77,8 +77,10 @@ public final class GlbParserUtils {
|
||||
// ---- Accessor reading utilities ----
|
||||
|
||||
public static float[] readFloatAccessor(
|
||||
JsonArray accessors, JsonArray bufferViews,
|
||||
ByteBuffer binData, int accessorIdx
|
||||
JsonArray accessors,
|
||||
JsonArray bufferViews,
|
||||
ByteBuffer binData,
|
||||
int accessorIdx
|
||||
) {
|
||||
JsonObject accessor = accessors.get(accessorIdx).getAsJsonObject();
|
||||
int count = accessor.get("count").getAsInt();
|
||||
@@ -88,9 +90,14 @@ public final class GlbParserUtils {
|
||||
|
||||
int bvIdx = accessor.get("bufferView").getAsInt();
|
||||
JsonObject bv = bufferViews.get(bvIdx).getAsJsonObject();
|
||||
int byteOffset = (bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0)
|
||||
+ (accessor.has("byteOffset") ? accessor.get("byteOffset").getAsInt() : 0);
|
||||
int byteStride = bv.has("byteStride") ? bv.get("byteStride").getAsInt() : 0;
|
||||
int byteOffset =
|
||||
(bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0) +
|
||||
(accessor.has("byteOffset")
|
||||
? accessor.get("byteOffset").getAsInt()
|
||||
: 0);
|
||||
int byteStride = bv.has("byteStride")
|
||||
? bv.get("byteStride").getAsInt()
|
||||
: 0;
|
||||
|
||||
int totalElements = count * components;
|
||||
float[] result = new float[totalElements];
|
||||
@@ -102,7 +109,10 @@ public final class GlbParserUtils {
|
||||
int pos = byteOffset + i * stride;
|
||||
for (int c = 0; c < components; c++) {
|
||||
binData.position(pos + c * componentSize);
|
||||
result[i * components + c] = readComponentAsFloat(binData, componentType);
|
||||
result[i * components + c] = readComponentAsFloat(
|
||||
binData,
|
||||
componentType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,8 +120,10 @@ public final class GlbParserUtils {
|
||||
}
|
||||
|
||||
public static int[] readIntAccessor(
|
||||
JsonArray accessors, JsonArray bufferViews,
|
||||
ByteBuffer binData, int accessorIdx
|
||||
JsonArray accessors,
|
||||
JsonArray bufferViews,
|
||||
ByteBuffer binData,
|
||||
int accessorIdx
|
||||
) {
|
||||
JsonObject accessor = accessors.get(accessorIdx).getAsJsonObject();
|
||||
int count = accessor.get("count").getAsInt();
|
||||
@@ -121,9 +133,14 @@ public final class GlbParserUtils {
|
||||
|
||||
int bvIdx = accessor.get("bufferView").getAsInt();
|
||||
JsonObject bv = bufferViews.get(bvIdx).getAsJsonObject();
|
||||
int byteOffset = (bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0)
|
||||
+ (accessor.has("byteOffset") ? accessor.get("byteOffset").getAsInt() : 0);
|
||||
int byteStride = bv.has("byteStride") ? bv.get("byteStride").getAsInt() : 0;
|
||||
int byteOffset =
|
||||
(bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0) +
|
||||
(accessor.has("byteOffset")
|
||||
? accessor.get("byteOffset").getAsInt()
|
||||
: 0);
|
||||
int byteStride = bv.has("byteStride")
|
||||
? bv.get("byteStride").getAsInt()
|
||||
: 0;
|
||||
|
||||
int totalElements = count * components;
|
||||
int[] result = new int[totalElements];
|
||||
@@ -135,22 +152,31 @@ public final class GlbParserUtils {
|
||||
int pos = byteOffset + i * stride;
|
||||
for (int c = 0; c < components; c++) {
|
||||
binData.position(pos + c * componentSize);
|
||||
result[i * components + c] = readComponentAsInt(binData, componentType);
|
||||
result[i * components + c] = readComponentAsInt(
|
||||
binData,
|
||||
componentType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static float readComponentAsFloat(ByteBuffer buf, int componentType) {
|
||||
public static float readComponentAsFloat(
|
||||
ByteBuffer buf,
|
||||
int componentType
|
||||
) {
|
||||
return switch (componentType) {
|
||||
case FLOAT -> buf.getFloat();
|
||||
case BYTE -> buf.get() / 127.0f;
|
||||
case UNSIGNED_BYTE -> (buf.get() & 0xFF) / 255.0f;
|
||||
case SHORT -> buf.getShort() / 32767.0f;
|
||||
case UNSIGNED_SHORT -> (buf.getShort() & 0xFFFF) / 65535.0f;
|
||||
case UNSIGNED_INT -> (buf.getInt() & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL;
|
||||
default -> throw new IllegalArgumentException("Unknown component type: " + componentType);
|
||||
case UNSIGNED_INT -> (buf.getInt() & 0xFFFFFFFFL) /
|
||||
(float) 0xFFFFFFFFL;
|
||||
default -> throw new IllegalArgumentException(
|
||||
"Unknown component type: " + componentType
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -162,7 +188,9 @@ public final class GlbParserUtils {
|
||||
case UNSIGNED_SHORT -> buf.getShort() & 0xFFFF;
|
||||
case UNSIGNED_INT -> buf.getInt();
|
||||
case FLOAT -> (int) buf.getFloat();
|
||||
default -> throw new IllegalArgumentException("Unknown component type: " + componentType);
|
||||
default -> throw new IllegalArgumentException(
|
||||
"Unknown component type: " + componentType
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -173,7 +201,9 @@ public final class GlbParserUtils {
|
||||
case "VEC3" -> 3;
|
||||
case "VEC4" -> 4;
|
||||
case "MAT4" -> 16;
|
||||
default -> throw new IllegalArgumentException("Unknown accessor type: " + type);
|
||||
default -> throw new IllegalArgumentException(
|
||||
"Unknown accessor type: " + type
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -182,7 +212,9 @@ public final class GlbParserUtils {
|
||||
case BYTE, UNSIGNED_BYTE -> 1;
|
||||
case SHORT, UNSIGNED_SHORT -> 2;
|
||||
case UNSIGNED_INT, FLOAT -> 4;
|
||||
default -> throw new IllegalArgumentException("Unknown component type: " + componentType);
|
||||
default -> throw new IllegalArgumentException(
|
||||
"Unknown component type: " + componentType
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -191,13 +223,18 @@ public final class GlbParserUtils {
|
||||
/**
|
||||
* Deep-copy an AnimationClip (preserves original data before MC conversion).
|
||||
*/
|
||||
public static GltfData.AnimationClip deepCopyClip(GltfData.AnimationClip clip) {
|
||||
Quaternionf[][] rawRotations = new Quaternionf[clip.rotations().length][];
|
||||
public static GltfData.AnimationClip deepCopyClip(
|
||||
GltfData.AnimationClip clip
|
||||
) {
|
||||
Quaternionf[][] rawRotations =
|
||||
new Quaternionf[clip.rotations().length][];
|
||||
for (int j = 0; j < clip.rotations().length; j++) {
|
||||
if (clip.rotations()[j] != null) {
|
||||
rawRotations[j] = new Quaternionf[clip.rotations()[j].length];
|
||||
for (int f = 0; f < clip.rotations()[j].length; f++) {
|
||||
rawRotations[j][f] = new Quaternionf(clip.rotations()[j][f]);
|
||||
rawRotations[j][f] = new Quaternionf(
|
||||
clip.rotations()[j][f]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,15 +243,20 @@ public final class GlbParserUtils {
|
||||
rawTranslations = new Vector3f[clip.translations().length][];
|
||||
for (int j = 0; j < clip.translations().length; j++) {
|
||||
if (clip.translations()[j] != null) {
|
||||
rawTranslations[j] = new Vector3f[clip.translations()[j].length];
|
||||
rawTranslations[j] =
|
||||
new Vector3f[clip.translations()[j].length];
|
||||
for (int f = 0; f < clip.translations()[j].length; f++) {
|
||||
rawTranslations[j][f] = new Vector3f(clip.translations()[j][f]);
|
||||
rawTranslations[j][f] = new Vector3f(
|
||||
clip.translations()[j][f]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new GltfData.AnimationClip(
|
||||
clip.timestamps().clone(), rawRotations, rawTranslations,
|
||||
clip.timestamps().clone(),
|
||||
rawRotations,
|
||||
rawTranslations,
|
||||
clip.frameCount()
|
||||
);
|
||||
}
|
||||
@@ -225,7 +267,10 @@ public final class GlbParserUtils {
|
||||
* Convert an animation clip's rotations and translations to MC space.
|
||||
* Negate qx/qy for rotations and negate tx/ty for translations.
|
||||
*/
|
||||
public static void convertAnimationToMinecraftSpace(GltfData.AnimationClip clip, int jointCount) {
|
||||
public static void convertAnimationToMinecraftSpace(
|
||||
GltfData.AnimationClip clip,
|
||||
int jointCount
|
||||
) {
|
||||
if (clip == null) return;
|
||||
|
||||
Quaternionf[][] rotations = clip.rotations();
|
||||
|
||||
@@ -13,7 +13,6 @@ import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
@@ -22,6 +21,7 @@ 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.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* V2 Animation Applier -- manages dual-layer animation for V2 bondage items.
|
||||
@@ -59,22 +59,23 @@ public final class GltfAnimationApplier {
|
||||
* Keyed by "animSource#context#ownedPartsHash".
|
||||
* Same GLB + same context + same owned parts = same KeyframeAnimation.
|
||||
*/
|
||||
private static final Map<String, KeyframeAnimation> itemAnimCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, KeyframeAnimation> itemAnimCache =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Track which composite state is currently active per entity, to avoid redundant replays.
|
||||
* Keyed by entity UUID, value is "animSource|context|sortedParts".
|
||||
*/
|
||||
private static final Map<UUID, String> activeStateKeys = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, String> activeStateKeys =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/** Track cache keys where GLB loading failed, to avoid per-tick retries. */
|
||||
private static final Set<String> failedLoadKeys = ConcurrentHashMap.newKeySet();
|
||||
private static final Set<String> failedLoadKeys =
|
||||
ConcurrentHashMap.newKeySet();
|
||||
|
||||
private GltfAnimationApplier() {}
|
||||
|
||||
// ========================================
|
||||
// INIT (legacy)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Legacy init method -- called by GltfClientSetup.
|
||||
@@ -84,9 +85,7 @@ public final class GltfAnimationApplier {
|
||||
// No-op: animation layers are managed by BondageAnimationManager
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// V2 DUAL-LAYER API
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Apply the full V2 animation state: context layer + item layer.
|
||||
@@ -113,12 +112,17 @@ public final class GltfAnimationApplier {
|
||||
* @param ownership bone ownership: which parts this item owns vs other items
|
||||
* @return true if the item layer animation was applied successfully
|
||||
*/
|
||||
public static boolean applyV2Animation(LivingEntity entity, ResourceLocation modelLoc,
|
||||
@Nullable ResourceLocation animationSource,
|
||||
AnimationContext context, RegionBoneMapper.BoneOwnership ownership) {
|
||||
public static boolean applyV2Animation(
|
||||
LivingEntity entity,
|
||||
ResourceLocation modelLoc,
|
||||
@Nullable ResourceLocation animationSource,
|
||||
AnimationContext context,
|
||||
RegionBoneMapper.BoneOwnership ownership
|
||||
) {
|
||||
if (entity == null || modelLoc == null) return false;
|
||||
|
||||
ResourceLocation animSource = animationSource != null ? animationSource : modelLoc;
|
||||
ResourceLocation animSource =
|
||||
animationSource != null ? animationSource : modelLoc;
|
||||
// Cache key includes both owned and enabled parts for full disambiguation
|
||||
String ownedKey = canonicalPartsKey(ownership.thisParts());
|
||||
String enabledKey = canonicalPartsKey(ownership.enabledParts());
|
||||
@@ -135,7 +139,9 @@ public final class GltfAnimationApplier {
|
||||
// Parts owned by ANY item (this or others) are disabled on the context layer.
|
||||
// Only free parts remain enabled on context.
|
||||
KeyframeAnimation contextAnim = ContextAnimationFactory.create(
|
||||
context, ownership.disabledOnContext());
|
||||
context,
|
||||
ownership.disabledOnContext()
|
||||
);
|
||||
if (contextAnim != null) {
|
||||
BondageAnimationManager.playContext(entity, contextAnim);
|
||||
}
|
||||
@@ -151,18 +157,31 @@ public final class GltfAnimationApplier {
|
||||
|
||||
KeyframeAnimation itemAnim = itemAnimCache.get(itemCacheKey);
|
||||
if (itemAnim == null) {
|
||||
GltfData animData = GlbAnimationResolver.resolveAnimationData(modelLoc, animationSource);
|
||||
GltfData animData = GlbAnimationResolver.resolveAnimationData(
|
||||
modelLoc,
|
||||
animationSource
|
||||
);
|
||||
if (animData == null) {
|
||||
LOGGER.warn("[GltfPipeline] Failed to load animation GLB: {}", animSource);
|
||||
LOGGER.warn(
|
||||
"[GltfPipeline] Failed to load animation GLB: {}",
|
||||
animSource
|
||||
);
|
||||
failedLoadKeys.add(itemCacheKey);
|
||||
activeStateKeys.put(entity.getUUID(), stateKey);
|
||||
return false;
|
||||
}
|
||||
// Resolve which named animation to use (with fallback chain + variant selection)
|
||||
String glbAnimName = GlbAnimationResolver.resolve(animData, context);
|
||||
String glbAnimName = GlbAnimationResolver.resolve(
|
||||
animData,
|
||||
context
|
||||
);
|
||||
// Pass both owned parts and enabled parts (owned + free) for selective enabling
|
||||
itemAnim = GltfPoseConverter.convertSelective(
|
||||
animData, glbAnimName, ownership.thisParts(), ownership.enabledParts());
|
||||
animData,
|
||||
glbAnimName,
|
||||
ownership.thisParts(),
|
||||
ownership.enabledParts()
|
||||
);
|
||||
itemAnimCache.put(itemCacheKey, itemAnim);
|
||||
}
|
||||
|
||||
@@ -185,16 +204,24 @@ public final class GltfAnimationApplier {
|
||||
* @param allOwnedParts union of all owned parts across all items
|
||||
* @return true if the composite animation was applied
|
||||
*/
|
||||
public static boolean applyMultiItemV2Animation(LivingEntity entity,
|
||||
List<RegionBoneMapper.V2ItemAnimInfo> items,
|
||||
AnimationContext context, Set<String> allOwnedParts) {
|
||||
public static boolean applyMultiItemV2Animation(
|
||||
LivingEntity entity,
|
||||
List<RegionBoneMapper.V2ItemAnimInfo> items,
|
||||
AnimationContext context,
|
||||
Set<String> allOwnedParts
|
||||
) {
|
||||
if (entity == null || items.isEmpty()) return false;
|
||||
|
||||
// Build composite state key
|
||||
StringBuilder keyBuilder = new StringBuilder();
|
||||
for (RegionBoneMapper.V2ItemAnimInfo item : items) {
|
||||
ResourceLocation src = item.animSource() != null ? item.animSource() : item.modelLoc();
|
||||
keyBuilder.append(src).append(':').append(canonicalPartsKey(item.ownedParts())).append(';');
|
||||
ResourceLocation src =
|
||||
item.animSource() != null ? item.animSource() : item.modelLoc();
|
||||
keyBuilder
|
||||
.append(src)
|
||||
.append(':')
|
||||
.append(canonicalPartsKey(item.ownedParts()))
|
||||
.append(';');
|
||||
}
|
||||
keyBuilder.append(context.name());
|
||||
String stateKey = keyBuilder.toString();
|
||||
@@ -205,7 +232,10 @@ public final class GltfAnimationApplier {
|
||||
}
|
||||
|
||||
// === Layer 1: Context animation ===
|
||||
KeyframeAnimation contextAnim = ContextAnimationFactory.create(context, allOwnedParts);
|
||||
KeyframeAnimation contextAnim = ContextAnimationFactory.create(
|
||||
context,
|
||||
allOwnedParts
|
||||
);
|
||||
if (contextAnim != null) {
|
||||
BondageAnimationManager.playContext(entity, contextAnim);
|
||||
}
|
||||
@@ -222,7 +252,8 @@ public final class GltfAnimationApplier {
|
||||
if (compositeAnim == null) {
|
||||
KeyframeAnimation.AnimationBuilder builder =
|
||||
new KeyframeAnimation.AnimationBuilder(
|
||||
dev.kosmx.playerAnim.core.data.AnimationFormat.JSON_EMOTECRAFT);
|
||||
dev.kosmx.playerAnim.core.data.AnimationFormat.JSON_EMOTECRAFT
|
||||
);
|
||||
builder.beginTick = 0;
|
||||
builder.endTick = 1;
|
||||
builder.stopTick = 1;
|
||||
@@ -234,15 +265,27 @@ public final class GltfAnimationApplier {
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
RegionBoneMapper.V2ItemAnimInfo item = items.get(i);
|
||||
ResourceLocation animSource = item.animSource() != null ? item.animSource() : item.modelLoc();
|
||||
ResourceLocation animSource =
|
||||
item.animSource() != null
|
||||
? item.animSource()
|
||||
: item.modelLoc();
|
||||
|
||||
GltfData animData = GlbAnimationResolver.resolveAnimationData(item.modelLoc(), item.animSource());
|
||||
GltfData animData = GlbAnimationResolver.resolveAnimationData(
|
||||
item.modelLoc(),
|
||||
item.animSource()
|
||||
);
|
||||
if (animData == null) {
|
||||
LOGGER.warn("[GltfPipeline] Failed to load GLB for multi-item: {}", animSource);
|
||||
LOGGER.warn(
|
||||
"[GltfPipeline] Failed to load GLB for multi-item: {}",
|
||||
animSource
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
String glbAnimName = GlbAnimationResolver.resolve(animData, context);
|
||||
String glbAnimName = GlbAnimationResolver.resolve(
|
||||
animData,
|
||||
context
|
||||
);
|
||||
GltfData.AnimationClip rawClip;
|
||||
if (glbAnimName != null) {
|
||||
rawClip = animData.getRawAnimation(glbAnimName);
|
||||
@@ -257,7 +300,9 @@ public final class GltfAnimationApplier {
|
||||
// if the item declares per-animation bone filtering.
|
||||
Set<String> effectiveParts = item.ownedParts();
|
||||
if (glbAnimName != null && !item.animationBones().isEmpty()) {
|
||||
Set<String> override = item.animationBones().get(glbAnimName);
|
||||
Set<String> override = item
|
||||
.animationBones()
|
||||
.get(glbAnimName);
|
||||
if (override != null) {
|
||||
Set<String> filtered = new HashSet<>(override);
|
||||
filtered.retainAll(item.ownedParts());
|
||||
@@ -268,11 +313,20 @@ public final class GltfAnimationApplier {
|
||||
}
|
||||
|
||||
GltfPoseConverter.addBonesToBuilder(
|
||||
builder, animData, rawClip, effectiveParts);
|
||||
builder,
|
||||
animData,
|
||||
rawClip,
|
||||
effectiveParts
|
||||
);
|
||||
anyLoaded = true;
|
||||
|
||||
LOGGER.debug("[GltfPipeline] Multi-item: {} -> owned={}, effective={}, anim={}",
|
||||
animSource, item.ownedParts(), effectiveParts, glbAnimName);
|
||||
LOGGER.debug(
|
||||
"[GltfPipeline] Multi-item: {} -> owned={}, effective={}, anim={}",
|
||||
animSource,
|
||||
item.ownedParts(),
|
||||
effectiveParts,
|
||||
glbAnimName
|
||||
);
|
||||
}
|
||||
|
||||
if (!anyLoaded) {
|
||||
@@ -284,9 +338,19 @@ public final class GltfAnimationApplier {
|
||||
// Enable only owned parts on the item layer.
|
||||
// Free parts (head, body, etc. not owned by any item) are disabled here
|
||||
// so they pass through to the context layer / vanilla animation.
|
||||
String[] allPartNames = {"head", "body", "rightArm", "leftArm", "rightLeg", "leftLeg"};
|
||||
String[] allPartNames = {
|
||||
"head",
|
||||
"body",
|
||||
"rightArm",
|
||||
"leftArm",
|
||||
"rightLeg",
|
||||
"leftLeg",
|
||||
};
|
||||
for (String partName : allPartNames) {
|
||||
KeyframeAnimation.StateCollection part = getPartByName(builder, partName);
|
||||
KeyframeAnimation.StateCollection part = getPartByName(
|
||||
builder,
|
||||
partName
|
||||
);
|
||||
if (part != null) {
|
||||
if (allOwnedParts.contains(partName)) {
|
||||
part.fullyEnablePart(false);
|
||||
@@ -305,9 +369,7 @@ public final class GltfAnimationApplier {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CLEAR / QUERY
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Clear all V2 animation layers from an entity and remove tracking.
|
||||
@@ -342,9 +404,7 @@ public final class GltfAnimationApplier {
|
||||
activeStateKeys.remove(entityId);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CACHE MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Invalidate all cached item animations and tracking state.
|
||||
@@ -373,9 +433,7 @@ public final class GltfAnimationApplier {
|
||||
ContextAnimationFactory.clearCache();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// LEGACY F9 DEBUG TOGGLE
|
||||
// ========================================
|
||||
|
||||
private static boolean debugEnabled = false;
|
||||
|
||||
@@ -386,19 +444,29 @@ public final class GltfAnimationApplier {
|
||||
*/
|
||||
public static void toggle() {
|
||||
debugEnabled = !debugEnabled;
|
||||
LOGGER.info("[GltfPipeline] Debug toggle: {}", debugEnabled ? "ON" : "OFF");
|
||||
LOGGER.info(
|
||||
"[GltfPipeline] Debug toggle: {}",
|
||||
debugEnabled ? "ON" : "OFF"
|
||||
);
|
||||
|
||||
AbstractClientPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) return;
|
||||
|
||||
if (debugEnabled) {
|
||||
ResourceLocation modelLoc = ResourceLocation.fromNamespaceAndPath(
|
||||
"tiedup", "models/gltf/v2/handcuffs/cuffs_prototype.glb"
|
||||
"tiedup",
|
||||
"models/gltf/v2/handcuffs/cuffs_prototype.glb"
|
||||
);
|
||||
Set<String> armParts = Set.of("rightArm", "leftArm");
|
||||
RegionBoneMapper.BoneOwnership debugOwnership =
|
||||
new RegionBoneMapper.BoneOwnership(armParts, Set.of());
|
||||
applyV2Animation(player, modelLoc, null, AnimationContext.STAND_IDLE, debugOwnership);
|
||||
applyV2Animation(
|
||||
player,
|
||||
modelLoc,
|
||||
null,
|
||||
AnimationContext.STAND_IDLE,
|
||||
debugOwnership
|
||||
);
|
||||
} else {
|
||||
clearV2Animation(player);
|
||||
}
|
||||
@@ -411,16 +479,17 @@ public final class GltfAnimationApplier {
|
||||
return debugEnabled;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// INTERNAL
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Build cache key for item-layer animations.
|
||||
* Format: "animSource#contextName#sortedParts"
|
||||
*/
|
||||
private static String buildItemCacheKey(ResourceLocation animSource,
|
||||
AnimationContext context, String partsKey) {
|
||||
private static String buildItemCacheKey(
|
||||
ResourceLocation animSource,
|
||||
AnimationContext context,
|
||||
String partsKey
|
||||
) {
|
||||
return animSource + "#" + context.name() + "#" + partsKey;
|
||||
}
|
||||
|
||||
@@ -436,7 +505,9 @@ public final class GltfAnimationApplier {
|
||||
* Look up an {@link KeyframeAnimation.StateCollection} by part name on a builder.
|
||||
*/
|
||||
private static KeyframeAnimation.StateCollection getPartByName(
|
||||
KeyframeAnimation.AnimationBuilder builder, String name) {
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
String name
|
||||
) {
|
||||
return switch (name) {
|
||||
case "head" -> builder.head;
|
||||
case "body" -> builder.body;
|
||||
|
||||
@@ -20,16 +20,22 @@ public final class GltfBoneMapper {
|
||||
|
||||
/** Lower bones that represent bend (elbow/knee) */
|
||||
private static final Set<String> LOWER_BONES = Set.of(
|
||||
"leftLowerArm", "rightLowerArm",
|
||||
"leftLowerLeg", "rightLowerLeg"
|
||||
"leftLowerArm",
|
||||
"rightLowerArm",
|
||||
"leftLowerLeg",
|
||||
"rightLowerLeg"
|
||||
);
|
||||
|
||||
/** Maps lower bone name -> corresponding upper bone name */
|
||||
private static final Map<String, String> LOWER_TO_UPPER = Map.of(
|
||||
"leftLowerArm", "leftUpperArm",
|
||||
"rightLowerArm", "rightUpperArm",
|
||||
"leftLowerLeg", "leftUpperLeg",
|
||||
"rightLowerLeg", "rightUpperLeg"
|
||||
"leftLowerArm",
|
||||
"leftUpperArm",
|
||||
"rightLowerArm",
|
||||
"rightUpperArm",
|
||||
"leftLowerLeg",
|
||||
"leftUpperLeg",
|
||||
"rightLowerLeg",
|
||||
"rightUpperLeg"
|
||||
);
|
||||
|
||||
static {
|
||||
@@ -55,7 +61,10 @@ public final class GltfBoneMapper {
|
||||
* @param boneName glTF bone name
|
||||
* @return the ModelPart, or null if not mapped
|
||||
*/
|
||||
public static ModelPart getModelPart(HumanoidModel<?> model, String boneName) {
|
||||
public static ModelPart getModelPart(
|
||||
HumanoidModel<?> model,
|
||||
String boneName
|
||||
) {
|
||||
String partName = BONE_TO_PART.get(boneName);
|
||||
if (partName == null) return null;
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ import org.apache.logging.log4j.Logger;
|
||||
public final class GltfCache {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");
|
||||
private static final Map<ResourceLocation, GltfData> CACHE = new ConcurrentHashMap<>();
|
||||
private static final Map<ResourceLocation, GltfData> CACHE =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
private GltfCache() {}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemReloadListener;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.EntityRenderersEvent;
|
||||
@@ -17,7 +18,6 @@ import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@@ -59,7 +59,9 @@ public final class GltfClientSetup {
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRegisterKeybindings(RegisterKeyMappingsEvent event) {
|
||||
public static void onRegisterKeybindings(
|
||||
RegisterKeyMappingsEvent event
|
||||
) {
|
||||
event.register(TOGGLE_KEY);
|
||||
LOGGER.info("[GltfPipeline] Keybind registered: F9");
|
||||
}
|
||||
@@ -71,16 +73,24 @@ public final class GltfClientSetup {
|
||||
var defaultRenderer = event.getSkin("default");
|
||||
if (defaultRenderer instanceof PlayerRenderer playerRenderer) {
|
||||
playerRenderer.addLayer(new GltfRenderLayer(playerRenderer));
|
||||
playerRenderer.addLayer(new V2BondageRenderLayer<>(playerRenderer));
|
||||
LOGGER.info("[GltfPipeline] Render layers added to 'default' player renderer");
|
||||
playerRenderer.addLayer(
|
||||
new V2BondageRenderLayer<>(playerRenderer)
|
||||
);
|
||||
LOGGER.info(
|
||||
"[GltfPipeline] Render layers added to 'default' player renderer"
|
||||
);
|
||||
}
|
||||
|
||||
// Add both layers to slim player renderer (Alex)
|
||||
var slimRenderer = event.getSkin("slim");
|
||||
if (slimRenderer instanceof PlayerRenderer playerRenderer) {
|
||||
playerRenderer.addLayer(new GltfRenderLayer(playerRenderer));
|
||||
playerRenderer.addLayer(new V2BondageRenderLayer<>(playerRenderer));
|
||||
LOGGER.info("[GltfPipeline] Render layers added to 'slim' player renderer");
|
||||
playerRenderer.addLayer(
|
||||
new V2BondageRenderLayer<>(playerRenderer)
|
||||
);
|
||||
LOGGER.info(
|
||||
"[GltfPipeline] Render layers added to 'slim' player renderer"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,32 +99,47 @@ public final class GltfClientSetup {
|
||||
* This ensures re-exported GLB models are picked up without restarting the game.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onRegisterReloadListeners(RegisterClientReloadListenersEvent event) {
|
||||
event.registerReloadListener(new SimplePreparableReloadListener<Void>() {
|
||||
@Override
|
||||
protected Void prepare(ResourceManager resourceManager, ProfilerFiller profiler) {
|
||||
return null;
|
||||
}
|
||||
public static void onRegisterReloadListeners(
|
||||
RegisterClientReloadListenersEvent event
|
||||
) {
|
||||
event.registerReloadListener(
|
||||
new SimplePreparableReloadListener<Void>() {
|
||||
@Override
|
||||
protected Void prepare(
|
||||
ResourceManager resourceManager,
|
||||
ProfilerFiller profiler
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void apply(Void nothing, ResourceManager resourceManager, ProfilerFiller profiler) {
|
||||
GltfCache.clearCache();
|
||||
GltfAnimationApplier.invalidateCache();
|
||||
GltfMeshRenderer.clearRenderTypeCache();
|
||||
// Reload context GLB animations from resource packs FIRST,
|
||||
// then clear the factory cache so it rebuilds against the
|
||||
// new GLB registry (prevents stale JSON fallback caching).
|
||||
ContextGlbRegistry.reload(resourceManager);
|
||||
ContextAnimationFactory.clearCache();
|
||||
com.tiedup.remake.v2.furniture.client.FurnitureGltfCache.clear();
|
||||
LOGGER.info("[GltfPipeline] Caches cleared on resource reload");
|
||||
@Override
|
||||
protected void apply(
|
||||
Void nothing,
|
||||
ResourceManager resourceManager,
|
||||
ProfilerFiller profiler
|
||||
) {
|
||||
GltfCache.clearCache();
|
||||
GltfAnimationApplier.invalidateCache();
|
||||
GltfMeshRenderer.clearRenderTypeCache();
|
||||
// Reload context GLB animations from resource packs FIRST,
|
||||
// then clear the factory cache so it rebuilds against the
|
||||
// new GLB registry (prevents stale JSON fallback caching).
|
||||
ContextGlbRegistry.reload(resourceManager);
|
||||
ContextAnimationFactory.clearCache();
|
||||
com.tiedup.remake.v2.furniture.client.FurnitureGltfCache.clear();
|
||||
LOGGER.info(
|
||||
"[GltfPipeline] Caches cleared on resource reload"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
LOGGER.info("[GltfPipeline] Resource reload listener registered");
|
||||
|
||||
// Data-driven bondage item definitions (tiedup_items/*.json)
|
||||
event.registerReloadListener(new DataDrivenItemReloadListener());
|
||||
LOGGER.info("[GltfPipeline] Data-driven item reload listener registered");
|
||||
LOGGER.info(
|
||||
"[GltfPipeline] Data-driven item reload listener registered"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,14 +21,14 @@ import org.joml.Vector3f;
|
||||
public final class GltfData {
|
||||
|
||||
// -- Mesh geometry (flattened arrays) --
|
||||
private final float[] positions; // VEC3, length = vertexCount * 3
|
||||
private final float[] normals; // VEC3, length = vertexCount * 3
|
||||
private final float[] texCoords; // VEC2, length = vertexCount * 2
|
||||
private final int[] indices; // triangle indices
|
||||
private final float[] positions; // VEC3, length = vertexCount * 3
|
||||
private final float[] normals; // VEC3, length = vertexCount * 3
|
||||
private final float[] texCoords; // VEC2, length = vertexCount * 2
|
||||
private final int[] indices; // triangle indices
|
||||
|
||||
// -- Skinning data (per-vertex, 4 influences) --
|
||||
private final int[] joints; // 4 joint indices per vertex, length = vertexCount * 4
|
||||
private final float[] weights; // 4 weights per vertex, length = vertexCount * 4
|
||||
private final int[] joints; // 4 joint indices per vertex, length = vertexCount * 4
|
||||
private final float[] weights; // 4 weights per vertex, length = vertexCount * 4
|
||||
|
||||
// -- Bone hierarchy (MC-converted for skinning) --
|
||||
private final String[] jointNames;
|
||||
@@ -39,6 +39,7 @@ public final class GltfData {
|
||||
|
||||
// -- Raw glTF rotations (unconverted, for pose conversion) --
|
||||
private final Quaternionf[] rawGltfRestRotations;
|
||||
|
||||
@Nullable
|
||||
private final AnimationClip rawGltfAnimation;
|
||||
|
||||
@@ -47,8 +48,8 @@ public final class GltfData {
|
||||
private final AnimationClip animation;
|
||||
|
||||
// -- Multiple named animations --
|
||||
private final Map<String, AnimationClip> namedAnimations; // MC-converted
|
||||
private final Map<String, AnimationClip> rawNamedAnimations; // raw glTF space
|
||||
private final Map<String, AnimationClip> namedAnimations; // MC-converted
|
||||
private final Map<String, AnimationClip> rawNamedAnimations; // raw glTF space
|
||||
|
||||
// -- Per-primitive material/tint info --
|
||||
private final List<Primitive> primitives;
|
||||
@@ -61,18 +62,25 @@ public final class GltfData {
|
||||
* Full constructor with multiple named animations and per-primitive data.
|
||||
*/
|
||||
public GltfData(
|
||||
float[] positions, float[] normals, float[] texCoords,
|
||||
int[] indices, int[] joints, float[] weights,
|
||||
String[] jointNames, int[] parentJointIndices,
|
||||
float[] positions,
|
||||
float[] normals,
|
||||
float[] texCoords,
|
||||
int[] indices,
|
||||
int[] joints,
|
||||
float[] weights,
|
||||
String[] jointNames,
|
||||
int[] parentJointIndices,
|
||||
Matrix4f[] inverseBindMatrices,
|
||||
Quaternionf[] restRotations, Vector3f[] restTranslations,
|
||||
Quaternionf[] restRotations,
|
||||
Vector3f[] restTranslations,
|
||||
Quaternionf[] rawGltfRestRotations,
|
||||
@Nullable AnimationClip rawGltfAnimation,
|
||||
@Nullable AnimationClip animation,
|
||||
Map<String, AnimationClip> namedAnimations,
|
||||
Map<String, AnimationClip> rawNamedAnimations,
|
||||
List<Primitive> primitives,
|
||||
int vertexCount, int jointCount
|
||||
int vertexCount,
|
||||
int jointCount
|
||||
) {
|
||||
this.positions = positions;
|
||||
this.normals = normals;
|
||||
@@ -88,8 +96,12 @@ public final class GltfData {
|
||||
this.rawGltfRestRotations = rawGltfRestRotations;
|
||||
this.rawGltfAnimation = rawGltfAnimation;
|
||||
this.animation = animation;
|
||||
this.namedAnimations = Collections.unmodifiableMap(new LinkedHashMap<>(namedAnimations));
|
||||
this.rawNamedAnimations = Collections.unmodifiableMap(new LinkedHashMap<>(rawNamedAnimations));
|
||||
this.namedAnimations = Collections.unmodifiableMap(
|
||||
new LinkedHashMap<>(namedAnimations)
|
||||
);
|
||||
this.rawNamedAnimations = Collections.unmodifiableMap(
|
||||
new LinkedHashMap<>(rawNamedAnimations)
|
||||
);
|
||||
this.primitives = List.copyOf(primitives);
|
||||
this.vertexCount = vertexCount;
|
||||
this.jointCount = jointCount;
|
||||
@@ -99,81 +111,175 @@ public final class GltfData {
|
||||
* Legacy constructor for backward compatibility (single animation only).
|
||||
*/
|
||||
public GltfData(
|
||||
float[] positions, float[] normals, float[] texCoords,
|
||||
int[] indices, int[] joints, float[] weights,
|
||||
String[] jointNames, int[] parentJointIndices,
|
||||
float[] positions,
|
||||
float[] normals,
|
||||
float[] texCoords,
|
||||
int[] indices,
|
||||
int[] joints,
|
||||
float[] weights,
|
||||
String[] jointNames,
|
||||
int[] parentJointIndices,
|
||||
Matrix4f[] inverseBindMatrices,
|
||||
Quaternionf[] restRotations, Vector3f[] restTranslations,
|
||||
Quaternionf[] restRotations,
|
||||
Vector3f[] restTranslations,
|
||||
Quaternionf[] rawGltfRestRotations,
|
||||
@Nullable AnimationClip rawGltfAnimation,
|
||||
@Nullable AnimationClip animation,
|
||||
int vertexCount, int jointCount
|
||||
int vertexCount,
|
||||
int jointCount
|
||||
) {
|
||||
this(positions, normals, texCoords, indices, joints, weights,
|
||||
jointNames, parentJointIndices, inverseBindMatrices,
|
||||
restRotations, restTranslations, rawGltfRestRotations,
|
||||
rawGltfAnimation, animation,
|
||||
new LinkedHashMap<>(), new LinkedHashMap<>(),
|
||||
this(
|
||||
positions,
|
||||
normals,
|
||||
texCoords,
|
||||
indices,
|
||||
joints,
|
||||
weights,
|
||||
jointNames,
|
||||
parentJointIndices,
|
||||
inverseBindMatrices,
|
||||
restRotations,
|
||||
restTranslations,
|
||||
rawGltfRestRotations,
|
||||
rawGltfAnimation,
|
||||
animation,
|
||||
new LinkedHashMap<>(),
|
||||
new LinkedHashMap<>(),
|
||||
List.of(new Primitive(indices, null, false, null)),
|
||||
vertexCount, jointCount);
|
||||
vertexCount,
|
||||
jointCount
|
||||
);
|
||||
}
|
||||
|
||||
public float[] positions() {
|
||||
return positions;
|
||||
}
|
||||
|
||||
public float[] normals() {
|
||||
return normals;
|
||||
}
|
||||
|
||||
public float[] texCoords() {
|
||||
return texCoords;
|
||||
}
|
||||
|
||||
public int[] indices() {
|
||||
return indices;
|
||||
}
|
||||
|
||||
public int[] joints() {
|
||||
return joints;
|
||||
}
|
||||
|
||||
public float[] weights() {
|
||||
return weights;
|
||||
}
|
||||
|
||||
public String[] jointNames() {
|
||||
return jointNames;
|
||||
}
|
||||
|
||||
public int[] parentJointIndices() {
|
||||
return parentJointIndices;
|
||||
}
|
||||
|
||||
public Matrix4f[] inverseBindMatrices() {
|
||||
return inverseBindMatrices;
|
||||
}
|
||||
|
||||
public Quaternionf[] restRotations() {
|
||||
return restRotations;
|
||||
}
|
||||
|
||||
public Vector3f[] restTranslations() {
|
||||
return restTranslations;
|
||||
}
|
||||
|
||||
public Quaternionf[] rawGltfRestRotations() {
|
||||
return rawGltfRestRotations;
|
||||
}
|
||||
|
||||
public float[] positions() { return positions; }
|
||||
public float[] normals() { return normals; }
|
||||
public float[] texCoords() { return texCoords; }
|
||||
public int[] indices() { return indices; }
|
||||
public int[] joints() { return joints; }
|
||||
public float[] weights() { return weights; }
|
||||
public String[] jointNames() { return jointNames; }
|
||||
public int[] parentJointIndices() { return parentJointIndices; }
|
||||
public Matrix4f[] inverseBindMatrices() { return inverseBindMatrices; }
|
||||
public Quaternionf[] restRotations() { return restRotations; }
|
||||
public Vector3f[] restTranslations() { return restTranslations; }
|
||||
public Quaternionf[] rawGltfRestRotations() { return rawGltfRestRotations; }
|
||||
@Nullable
|
||||
public AnimationClip rawGltfAnimation() { return rawGltfAnimation; }
|
||||
public AnimationClip rawGltfAnimation() {
|
||||
return rawGltfAnimation;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AnimationClip animation() { return animation; }
|
||||
public int vertexCount() { return vertexCount; }
|
||||
public int jointCount() { return jointCount; }
|
||||
public AnimationClip animation() {
|
||||
return animation;
|
||||
}
|
||||
|
||||
public int vertexCount() {
|
||||
return vertexCount;
|
||||
}
|
||||
|
||||
public int jointCount() {
|
||||
return jointCount;
|
||||
}
|
||||
|
||||
/** Per-primitive material and tint metadata. One entry per glTF primitive in the mesh. */
|
||||
public List<Primitive> primitives() { return primitives; }
|
||||
public List<Primitive> primitives() {
|
||||
return primitives;
|
||||
}
|
||||
|
||||
/** All named animations in MC-converted space. Keys are animation names (e.g. "BasicPose", "Struggle"). */
|
||||
public Map<String, AnimationClip> namedAnimations() { return namedAnimations; }
|
||||
public Map<String, AnimationClip> namedAnimations() {
|
||||
return namedAnimations;
|
||||
}
|
||||
|
||||
/** Get a specific named animation in MC-converted space, or null if not found. */
|
||||
@Nullable
|
||||
public AnimationClip getAnimation(String name) { return namedAnimations.get(name); }
|
||||
public AnimationClip getAnimation(String name) {
|
||||
return namedAnimations.get(name);
|
||||
}
|
||||
|
||||
/** Get a specific named animation in raw glTF space, or null if not found. */
|
||||
@Nullable
|
||||
public AnimationClip getRawAnimation(String name) { return rawNamedAnimations.get(name); }
|
||||
public AnimationClip getRawAnimation(String name) {
|
||||
return rawNamedAnimations.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animation clip: per-bone timestamps, quaternion rotations, and optional translations.
|
||||
*/
|
||||
public static final class AnimationClip {
|
||||
private final float[] timestamps; // shared timestamps
|
||||
private final Quaternionf[][] rotations; // [jointIndex][frameIndex], null if no anim
|
||||
|
||||
private final float[] timestamps; // shared timestamps
|
||||
private final Quaternionf[][] rotations; // [jointIndex][frameIndex], null if no anim
|
||||
|
||||
@Nullable
|
||||
private final Vector3f[][] translations; // [jointIndex][frameIndex], null if no anim
|
||||
private final Vector3f[][] translations; // [jointIndex][frameIndex], null if no anim
|
||||
|
||||
private final int frameCount;
|
||||
|
||||
public AnimationClip(float[] timestamps, Quaternionf[][] rotations,
|
||||
@Nullable Vector3f[][] translations, int frameCount) {
|
||||
public AnimationClip(
|
||||
float[] timestamps,
|
||||
Quaternionf[][] rotations,
|
||||
@Nullable Vector3f[][] translations,
|
||||
int frameCount
|
||||
) {
|
||||
this.timestamps = timestamps;
|
||||
this.rotations = rotations;
|
||||
this.translations = translations;
|
||||
this.frameCount = frameCount;
|
||||
}
|
||||
|
||||
public float[] timestamps() { return timestamps; }
|
||||
public Quaternionf[][] rotations() { return rotations; }
|
||||
public float[] timestamps() {
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
public Quaternionf[][] rotations() {
|
||||
return rotations;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Vector3f[][] translations() { return translations; }
|
||||
public int frameCount() { return frameCount; }
|
||||
public Vector3f[][] translations() {
|
||||
return translations;
|
||||
}
|
||||
|
||||
public int frameCount() {
|
||||
return frameCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,7 +60,9 @@ public final class GltfLiveBoneReader {
|
||||
* @return array of joint matrices ready for skinning, or null on failure
|
||||
*/
|
||||
public static Matrix4f[] computeJointMatricesFromModel(
|
||||
HumanoidModel<?> model, GltfData data, LivingEntity entity
|
||||
HumanoidModel<?> model,
|
||||
GltfData data,
|
||||
LivingEntity entity
|
||||
) {
|
||||
if (model == null || data == null || entity == null) return null;
|
||||
|
||||
@@ -83,14 +85,19 @@ public final class GltfLiveBoneReader {
|
||||
if (GltfBoneMapper.isLowerBone(boneName)) {
|
||||
// --- Lower bone: reconstruct from bend values ---
|
||||
localRot = computeLowerBoneLocalRotation(
|
||||
boneName, j, restRotations, emote
|
||||
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
|
||||
part,
|
||||
j,
|
||||
restRotations
|
||||
);
|
||||
} else {
|
||||
// Fallback: use rest rotation
|
||||
@@ -108,14 +115,17 @@ public final class GltfLiveBoneReader {
|
||||
|
||||
// Compose with parent to get world transform
|
||||
if (parents[j] >= 0 && worldTransforms[parents[j]] != null) {
|
||||
worldTransforms[j] = new Matrix4f(worldTransforms[parents[j]]).mul(local);
|
||||
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]);
|
||||
jointMatrices[j] = new Matrix4f(worldTransforms[j]).mul(
|
||||
data.inverseBindMatrices()[j]
|
||||
);
|
||||
}
|
||||
|
||||
return jointMatrices;
|
||||
@@ -138,11 +148,16 @@ public final class GltfLiveBoneReader {
|
||||
* the frame relationship.
|
||||
*/
|
||||
private static Quaternionf computeUpperBoneLocalRotation(
|
||||
ModelPart part, int jointIndex,
|
||||
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);
|
||||
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]);
|
||||
}
|
||||
@@ -160,7 +175,8 @@ public final class GltfLiveBoneReader {
|
||||
* No de-parenting needed — same reasoning as upper bones.
|
||||
*/
|
||||
private static Quaternionf computeLowerBoneLocalRotation(
|
||||
String boneName, int jointIndex,
|
||||
String boneName,
|
||||
int jointIndex,
|
||||
Quaternionf[] restRotations,
|
||||
AnimationApplier emote
|
||||
) {
|
||||
@@ -183,11 +199,16 @@ public final class GltfLiveBoneReader {
|
||||
float halfAngle = bendValue * 0.5f;
|
||||
float s = (float) Math.sin(halfAngle);
|
||||
Quaternionf bendQuat = new Quaternionf(
|
||||
ax * s, 0, az * s, (float) Math.cos(halfAngle)
|
||||
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]);
|
||||
return new Quaternionf(bendQuat).mul(
|
||||
restRotations[jointIndex]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,12 +239,12 @@ public final class GltfLiveBoneReader {
|
||||
// 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 "head" -> true;
|
||||
case "leftUpperArm" -> true;
|
||||
case "rightUpperArm"-> true;
|
||||
case "rightUpperArm" -> true;
|
||||
case "leftUpperLeg" -> true;
|
||||
case "rightUpperLeg"-> true;
|
||||
default -> false; // body, torso, lower bones, unknown
|
||||
case "rightUpperLeg" -> true;
|
||||
default -> false; // body, torso, lower bones, unknown
|
||||
};
|
||||
}
|
||||
|
||||
@@ -236,8 +257,11 @@ public final class GltfLiveBoneReader {
|
||||
try {
|
||||
return animated.playerAnimator_getAnimation();
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug("[GltfPipeline] Could not get AnimationApplier for {}: {}",
|
||||
entity.getClass().getSimpleName(), e.getMessage());
|
||||
LOGGER.debug(
|
||||
"[GltfPipeline] Could not get AnimationApplier for {}: {}",
|
||||
entity.getClass().getSimpleName(),
|
||||
e.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -25,13 +25,17 @@ import org.joml.Vector4f;
|
||||
public final class GltfMeshRenderer extends RenderStateShard {
|
||||
|
||||
private static final ResourceLocation WHITE_TEXTURE =
|
||||
ResourceLocation.fromNamespaceAndPath("tiedup", "models/obj/shared/white.png");
|
||||
ResourceLocation.fromNamespaceAndPath(
|
||||
"tiedup",
|
||||
"models/obj/shared/white.png"
|
||||
);
|
||||
|
||||
/** Cached default RenderType (white texture). Created once, reused every frame. */
|
||||
private static RenderType cachedDefaultRenderType;
|
||||
|
||||
/** Cache for texture-specific RenderTypes, keyed by ResourceLocation. */
|
||||
private static final Map<ResourceLocation, RenderType> RENDER_TYPE_CACHE = new ConcurrentHashMap<>();
|
||||
private static final Map<ResourceLocation, RenderType> RENDER_TYPE_CACHE =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
private GltfMeshRenderer() {
|
||||
super("tiedup_gltf_renderer", () -> {}, () -> {});
|
||||
@@ -61,15 +65,21 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
* @param texture the texture ResourceLocation
|
||||
* @return the cached or newly created RenderType
|
||||
*/
|
||||
private static RenderType getRenderTypeForTexture(ResourceLocation texture) {
|
||||
return RENDER_TYPE_CACHE.computeIfAbsent(texture,
|
||||
GltfMeshRenderer::createTriangleRenderType);
|
||||
private static RenderType getRenderTypeForTexture(
|
||||
ResourceLocation texture
|
||||
) {
|
||||
return RENDER_TYPE_CACHE.computeIfAbsent(
|
||||
texture,
|
||||
GltfMeshRenderer::createTriangleRenderType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a TRIANGLES-mode RenderType for glTF mesh rendering with the given texture.
|
||||
*/
|
||||
private static RenderType createTriangleRenderType(ResourceLocation texture) {
|
||||
private static RenderType createTriangleRenderType(
|
||||
ResourceLocation texture
|
||||
) {
|
||||
RenderType.CompositeState state = RenderType.CompositeState.builder()
|
||||
.setShaderState(RENDERTYPE_ENTITY_CUTOUT_NO_CULL_SHADER)
|
||||
.setTextureState(
|
||||
@@ -112,12 +122,22 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
* @param packedOverlay packed overlay value
|
||||
*/
|
||||
public static void renderSkinned(
|
||||
GltfData data, Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack, MultiBufferSource buffer,
|
||||
int packedLight, int packedOverlay
|
||||
GltfData data,
|
||||
Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack,
|
||||
MultiBufferSource buffer,
|
||||
int packedLight,
|
||||
int packedOverlay
|
||||
) {
|
||||
renderSkinnedInternal(data, jointMatrices, poseStack, buffer,
|
||||
packedLight, packedOverlay, getDefaultRenderType());
|
||||
renderSkinnedInternal(
|
||||
data,
|
||||
jointMatrices,
|
||||
poseStack,
|
||||
buffer,
|
||||
packedLight,
|
||||
packedOverlay,
|
||||
getDefaultRenderType()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,22 +152,35 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
* @param texture the texture to use for rendering
|
||||
*/
|
||||
public static void renderSkinned(
|
||||
GltfData data, Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack, MultiBufferSource buffer,
|
||||
int packedLight, int packedOverlay,
|
||||
GltfData data,
|
||||
Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack,
|
||||
MultiBufferSource buffer,
|
||||
int packedLight,
|
||||
int packedOverlay,
|
||||
ResourceLocation texture
|
||||
) {
|
||||
renderSkinnedInternal(data, jointMatrices, poseStack, buffer,
|
||||
packedLight, packedOverlay, getRenderTypeForTexture(texture));
|
||||
renderSkinnedInternal(
|
||||
data,
|
||||
jointMatrices,
|
||||
poseStack,
|
||||
buffer,
|
||||
packedLight,
|
||||
packedOverlay,
|
||||
getRenderTypeForTexture(texture)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal rendering implementation shared by both overloads.
|
||||
*/
|
||||
private static void renderSkinnedInternal(
|
||||
GltfData data, Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack, MultiBufferSource buffer,
|
||||
int packedLight, int packedOverlay,
|
||||
GltfData data,
|
||||
Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack,
|
||||
MultiBufferSource buffer,
|
||||
int packedLight,
|
||||
int packedOverlay,
|
||||
RenderType renderType
|
||||
) {
|
||||
Matrix4f pose = poseStack.last().pose();
|
||||
@@ -167,13 +200,22 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
|
||||
for (int idx : indices) {
|
||||
// Skin this vertex
|
||||
GltfSkinningEngine.skinVertex(data, idx, jointMatrices, outPos, outNormal, tmpPos, tmpNorm);
|
||||
GltfSkinningEngine.skinVertex(
|
||||
data,
|
||||
idx,
|
||||
jointMatrices,
|
||||
outPos,
|
||||
outNormal,
|
||||
tmpPos,
|
||||
tmpNorm
|
||||
);
|
||||
|
||||
// UV coordinates
|
||||
float u = texCoords[idx * 2];
|
||||
float v = texCoords[idx * 2 + 1];
|
||||
|
||||
vc.vertex(pose, outPos[0], outPos[1], outPos[2])
|
||||
vc
|
||||
.vertex(pose, outPos[0], outPos[1], outPos[2])
|
||||
.color(255, 255, 255, 255)
|
||||
.uv(u, 1.0f - v)
|
||||
.overlayCoords(packedOverlay)
|
||||
@@ -205,9 +247,12 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
* @param tintColors channel name to RGB int (0xRRGGBB); empty map = white everywhere
|
||||
*/
|
||||
public static void renderSkinnedTinted(
|
||||
GltfData data, Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack, MultiBufferSource buffer,
|
||||
int packedLight, int packedOverlay,
|
||||
GltfData data,
|
||||
Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack,
|
||||
MultiBufferSource buffer,
|
||||
int packedLight,
|
||||
int packedOverlay,
|
||||
RenderType renderType,
|
||||
Map<String, Integer> tintColors
|
||||
) {
|
||||
@@ -226,7 +271,9 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
|
||||
for (GltfData.Primitive prim : primitives) {
|
||||
// Determine color for this primitive
|
||||
int r = 255, g = 255, b = 255;
|
||||
int r = 255,
|
||||
g = 255,
|
||||
b = 255;
|
||||
if (prim.tintable() && prim.tintChannel() != null) {
|
||||
Integer colorInt = tintColors.get(prim.tintChannel());
|
||||
if (colorInt != null) {
|
||||
@@ -237,12 +284,21 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
}
|
||||
|
||||
for (int idx : prim.indices()) {
|
||||
GltfSkinningEngine.skinVertex(data, idx, jointMatrices, outPos, outNormal, tmpPos, tmpNorm);
|
||||
GltfSkinningEngine.skinVertex(
|
||||
data,
|
||||
idx,
|
||||
jointMatrices,
|
||||
outPos,
|
||||
outNormal,
|
||||
tmpPos,
|
||||
tmpNorm
|
||||
);
|
||||
|
||||
float u = texCoords[idx * 2];
|
||||
float v = texCoords[idx * 2 + 1];
|
||||
|
||||
vc.vertex(pose, outPos[0], outPos[1], outPos[2])
|
||||
vc
|
||||
.vertex(pose, outPos[0], outPos[1], outPos[2])
|
||||
.color(r, g, b, 255)
|
||||
.uv(u, 1.0f - v)
|
||||
.overlayCoords(packedOverlay)
|
||||
|
||||
@@ -5,11 +5,11 @@ import dev.kosmx.playerAnim.core.data.KeyframeAnimation;
|
||||
import dev.kosmx.playerAnim.core.util.Ease;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
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.jetbrains.annotations.Nullable;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
@@ -52,10 +52,16 @@ public final class GltfPoseConverter {
|
||||
* @param animationName the name of the animation to convert (e.g. "Struggle", "Idle")
|
||||
* @return a static looping KeyframeAnimation suitable for PlayerAnimator
|
||||
*/
|
||||
public static KeyframeAnimation convert(GltfData data, String animationName) {
|
||||
public static KeyframeAnimation convert(
|
||||
GltfData data,
|
||||
String animationName
|
||||
) {
|
||||
GltfData.AnimationClip rawClip = data.getRawAnimation(animationName);
|
||||
if (rawClip == null) {
|
||||
LOGGER.warn("[GltfPipeline] Animation '{}' not found, falling back to default", animationName);
|
||||
LOGGER.warn(
|
||||
"[GltfPipeline] Animation '{}' not found, falling back to default",
|
||||
animationName
|
||||
);
|
||||
return convert(data);
|
||||
}
|
||||
return convertClip(data, rawClip, "gltf_" + animationName);
|
||||
@@ -76,8 +82,12 @@ public final class GltfPoseConverter {
|
||||
* are only enabled if the GLB has keyframes for them
|
||||
* @return KeyframeAnimation with selective parts active
|
||||
*/
|
||||
public static KeyframeAnimation convertSelective(GltfData data, @Nullable String animationName,
|
||||
Set<String> ownedParts, Set<String> enabledParts) {
|
||||
public static KeyframeAnimation convertSelective(
|
||||
GltfData data,
|
||||
@Nullable String animationName,
|
||||
Set<String> ownedParts,
|
||||
Set<String> enabledParts
|
||||
) {
|
||||
GltfData.AnimationClip rawClip;
|
||||
String animName;
|
||||
if (animationName != null) {
|
||||
@@ -90,7 +100,13 @@ public final class GltfPoseConverter {
|
||||
if (rawClip == null) {
|
||||
rawClip = data.rawGltfAnimation();
|
||||
}
|
||||
return convertClipSelective(data, rawClip, animName, ownedParts, enabledParts);
|
||||
return convertClipSelective(
|
||||
data,
|
||||
rawClip,
|
||||
animName,
|
||||
ownedParts,
|
||||
enabledParts
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,10 +121,17 @@ public final class GltfPoseConverter {
|
||||
* @param ownedParts parts the item explicitly owns (always enabled)
|
||||
* @param enabledParts parts the item may animate (owned + free)
|
||||
*/
|
||||
private static KeyframeAnimation convertClipSelective(GltfData data, GltfData.AnimationClip rawClip,
|
||||
String animName, Set<String> ownedParts, Set<String> enabledParts) {
|
||||
private static KeyframeAnimation convertClipSelective(
|
||||
GltfData data,
|
||||
GltfData.AnimationClip rawClip,
|
||||
String animName,
|
||||
Set<String> ownedParts,
|
||||
Set<String> enabledParts
|
||||
) {
|
||||
KeyframeAnimation.AnimationBuilder builder =
|
||||
new KeyframeAnimation.AnimationBuilder(AnimationFormat.JSON_EMOTECRAFT);
|
||||
new KeyframeAnimation.AnimationBuilder(
|
||||
AnimationFormat.JSON_EMOTECRAFT
|
||||
);
|
||||
|
||||
builder.beginTick = 0;
|
||||
builder.endTick = 1;
|
||||
@@ -129,21 +152,27 @@ public final class GltfPoseConverter {
|
||||
|
||||
// Check if this joint has explicit animation data (not just rest pose fallback).
|
||||
// A bone counts as explicitly animated if it has rotation OR translation keyframes.
|
||||
boolean hasExplicitAnim = rawClip != null && (
|
||||
(j < rawClip.rotations().length && rawClip.rotations()[j] != null)
|
||||
|| (rawClip.translations() != null
|
||||
&& j < rawClip.translations().length
|
||||
&& rawClip.translations()[j] != null)
|
||||
);
|
||||
boolean hasExplicitAnim =
|
||||
rawClip != null &&
|
||||
((j < rawClip.rotations().length &&
|
||||
rawClip.rotations()[j] != null) ||
|
||||
(rawClip.translations() != null &&
|
||||
j < rawClip.translations().length &&
|
||||
rawClip.translations()[j] != null));
|
||||
|
||||
Quaternionf animQ = getRawAnimQuaternion(rawClip, rawRestRotations, j);
|
||||
Quaternionf animQ = getRawAnimQuaternion(
|
||||
rawClip,
|
||||
rawRestRotations,
|
||||
j
|
||||
);
|
||||
Quaternionf restQ = rawRestRotations[j];
|
||||
|
||||
// delta_local = inverse(rest_q) * anim_q (in bone-local frame)
|
||||
Quaternionf deltaLocal = new Quaternionf(restQ).invert().mul(animQ);
|
||||
|
||||
// Convert to PARENT frame: delta_parent = rest * delta_local * inv(rest)
|
||||
Quaternionf deltaParent = new Quaternionf(restQ).mul(deltaLocal)
|
||||
Quaternionf deltaParent = new Quaternionf(restQ)
|
||||
.mul(deltaLocal)
|
||||
.mul(new Quaternionf(restQ).invert());
|
||||
|
||||
// Convert from glTF parent frame to MC model-def frame.
|
||||
@@ -168,7 +197,9 @@ public final class GltfPoseConverter {
|
||||
if (GltfBoneMapper.isLowerBone(boneName)) {
|
||||
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
|
||||
if (upperBone != null) {
|
||||
String upperPart = GltfBoneMapper.getAnimPartName(upperBone);
|
||||
String upperPart = GltfBoneMapper.getAnimPartName(
|
||||
upperBone
|
||||
);
|
||||
if (upperPart != null) {
|
||||
partsWithKeyframes.add(upperPart);
|
||||
}
|
||||
@@ -178,11 +209,21 @@ public final class GltfPoseConverter {
|
||||
}
|
||||
|
||||
// Selective: enable owned parts always, free parts only if they have keyframes
|
||||
enableSelectiveParts(builder, ownedParts, enabledParts, partsWithKeyframes);
|
||||
enableSelectiveParts(
|
||||
builder,
|
||||
ownedParts,
|
||||
enabledParts,
|
||||
partsWithKeyframes
|
||||
);
|
||||
|
||||
KeyframeAnimation anim = builder.build();
|
||||
LOGGER.debug("[GltfPipeline] Converted selective animation '{}' (owned: {}, enabled: {}, withKeyframes: {})",
|
||||
animName, ownedParts, enabledParts, partsWithKeyframes);
|
||||
LOGGER.debug(
|
||||
"[GltfPipeline] Converted selective animation '{}' (owned: {}, enabled: {}, withKeyframes: {})",
|
||||
animName,
|
||||
ownedParts,
|
||||
enabledParts,
|
||||
partsWithKeyframes
|
||||
);
|
||||
return anim;
|
||||
}
|
||||
|
||||
@@ -200,10 +241,11 @@ public final class GltfPoseConverter {
|
||||
* @return set of part names that received actual keyframe data from the GLB
|
||||
*/
|
||||
public static Set<String> addBonesToBuilder(
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
GltfData data, @Nullable GltfData.AnimationClip rawClip,
|
||||
Set<String> ownedParts) {
|
||||
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
GltfData data,
|
||||
@Nullable GltfData.AnimationClip rawClip,
|
||||
Set<String> ownedParts
|
||||
) {
|
||||
String[] jointNames = data.jointNames();
|
||||
Quaternionf[] rawRestRotations = data.rawGltfRestRotations();
|
||||
Set<String> partsWithKeyframes = new HashSet<>();
|
||||
@@ -221,23 +263,33 @@ public final class GltfPoseConverter {
|
||||
if (GltfBoneMapper.isLowerBone(boneName)) {
|
||||
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
|
||||
if (upperBone != null) {
|
||||
String upperPart = GltfBoneMapper.getAnimPartName(upperBone);
|
||||
if (upperPart == null || !ownedParts.contains(upperPart)) continue;
|
||||
String upperPart = GltfBoneMapper.getAnimPartName(
|
||||
upperBone
|
||||
);
|
||||
if (
|
||||
upperPart == null || !ownedParts.contains(upperPart)
|
||||
) continue;
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasExplicitAnim = rawClip != null && (
|
||||
(j < rawClip.rotations().length && rawClip.rotations()[j] != null)
|
||||
|| (rawClip.translations() != null
|
||||
&& j < rawClip.translations().length
|
||||
&& rawClip.translations()[j] != null)
|
||||
);
|
||||
boolean hasExplicitAnim =
|
||||
rawClip != null &&
|
||||
((j < rawClip.rotations().length &&
|
||||
rawClip.rotations()[j] != null) ||
|
||||
(rawClip.translations() != null &&
|
||||
j < rawClip.translations().length &&
|
||||
rawClip.translations()[j] != null));
|
||||
|
||||
Quaternionf animQ = getRawAnimQuaternion(rawClip, rawRestRotations, j);
|
||||
Quaternionf animQ = getRawAnimQuaternion(
|
||||
rawClip,
|
||||
rawRestRotations,
|
||||
j
|
||||
);
|
||||
Quaternionf restQ = rawRestRotations[j];
|
||||
|
||||
Quaternionf deltaLocal = new Quaternionf(restQ).invert().mul(animQ);
|
||||
Quaternionf deltaParent = new Quaternionf(restQ).mul(deltaLocal)
|
||||
Quaternionf deltaParent = new Quaternionf(restQ)
|
||||
.mul(deltaLocal)
|
||||
.mul(new Quaternionf(restQ).invert());
|
||||
|
||||
Quaternionf deltaQ = new Quaternionf(deltaParent);
|
||||
@@ -255,8 +307,12 @@ public final class GltfPoseConverter {
|
||||
if (GltfBoneMapper.isLowerBone(boneName)) {
|
||||
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
|
||||
if (upperBone != null) {
|
||||
String upperPart = GltfBoneMapper.getAnimPartName(upperBone);
|
||||
if (upperPart != null) partsWithKeyframes.add(upperPart);
|
||||
String upperPart = GltfBoneMapper.getAnimPartName(
|
||||
upperBone
|
||||
);
|
||||
if (upperPart != null) partsWithKeyframes.add(
|
||||
upperPart
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,16 +337,25 @@ public final class GltfPoseConverter {
|
||||
* @return a static looping KeyframeAnimation with all parts enabled
|
||||
*/
|
||||
public static KeyframeAnimation convertWithSkeleton(
|
||||
GltfData skeleton, GltfData.AnimationClip clip, String animName) {
|
||||
GltfData skeleton,
|
||||
GltfData.AnimationClip clip,
|
||||
String animName
|
||||
) {
|
||||
return convertClip(skeleton, clip, animName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal: convert a specific raw animation clip to a KeyframeAnimation.
|
||||
*/
|
||||
private static KeyframeAnimation convertClip(GltfData data, GltfData.AnimationClip rawClip, String animName) {
|
||||
private static KeyframeAnimation convertClip(
|
||||
GltfData data,
|
||||
GltfData.AnimationClip rawClip,
|
||||
String animName
|
||||
) {
|
||||
KeyframeAnimation.AnimationBuilder builder =
|
||||
new KeyframeAnimation.AnimationBuilder(AnimationFormat.JSON_EMOTECRAFT);
|
||||
new KeyframeAnimation.AnimationBuilder(
|
||||
AnimationFormat.JSON_EMOTECRAFT
|
||||
);
|
||||
|
||||
builder.beginTick = 0;
|
||||
builder.endTick = 1;
|
||||
@@ -307,7 +372,11 @@ public final class GltfPoseConverter {
|
||||
|
||||
if (!GltfBoneMapper.isKnownBone(boneName)) continue;
|
||||
|
||||
Quaternionf animQ = getRawAnimQuaternion(rawClip, rawRestRotations, j);
|
||||
Quaternionf animQ = getRawAnimQuaternion(
|
||||
rawClip,
|
||||
rawRestRotations,
|
||||
j
|
||||
);
|
||||
Quaternionf restQ = rawRestRotations[j];
|
||||
|
||||
// delta_local = inverse(rest_q) * anim_q (in bone-local frame)
|
||||
@@ -315,7 +384,8 @@ public final class GltfPoseConverter {
|
||||
|
||||
// Convert to PARENT frame: delta_parent = rest * delta_local * inv(rest)
|
||||
// Simplifies algebraically to: animQ * inv(restQ)
|
||||
Quaternionf deltaParent = new Quaternionf(restQ).mul(deltaLocal)
|
||||
Quaternionf deltaParent = new Quaternionf(restQ)
|
||||
.mul(deltaLocal)
|
||||
.mul(new Quaternionf(restQ).invert());
|
||||
|
||||
// Convert from glTF parent frame to MC model-def frame.
|
||||
@@ -324,12 +394,24 @@ public final class GltfPoseConverter {
|
||||
deltaQ.x = -deltaQ.x;
|
||||
deltaQ.y = -deltaQ.y;
|
||||
|
||||
LOGGER.debug(String.format(
|
||||
"[GltfPipeline] Bone '%s': restQ=(%.3f,%.3f,%.3f,%.3f) animQ=(%.3f,%.3f,%.3f,%.3f) deltaQ=(%.3f,%.3f,%.3f,%.3f)",
|
||||
boneName,
|
||||
restQ.x, restQ.y, restQ.z, restQ.w,
|
||||
animQ.x, animQ.y, animQ.z, animQ.w,
|
||||
deltaQ.x, deltaQ.y, deltaQ.z, deltaQ.w));
|
||||
LOGGER.debug(
|
||||
String.format(
|
||||
"[GltfPipeline] Bone '%s': restQ=(%.3f,%.3f,%.3f,%.3f) animQ=(%.3f,%.3f,%.3f,%.3f) deltaQ=(%.3f,%.3f,%.3f,%.3f)",
|
||||
boneName,
|
||||
restQ.x,
|
||||
restQ.y,
|
||||
restQ.z,
|
||||
restQ.w,
|
||||
animQ.x,
|
||||
animQ.y,
|
||||
animQ.z,
|
||||
animQ.w,
|
||||
deltaQ.x,
|
||||
deltaQ.y,
|
||||
deltaQ.z,
|
||||
deltaQ.w
|
||||
)
|
||||
);
|
||||
|
||||
if (GltfBoneMapper.isLowerBone(boneName)) {
|
||||
convertLowerBone(builder, boneName, deltaQ);
|
||||
@@ -341,7 +423,10 @@ public final class GltfPoseConverter {
|
||||
builder.fullyEnableParts();
|
||||
|
||||
KeyframeAnimation anim = builder.build();
|
||||
LOGGER.debug("[GltfPipeline] Converted glTF animation '{}' to KeyframeAnimation", animName);
|
||||
LOGGER.debug(
|
||||
"[GltfPipeline] Converted glTF animation '{}' to KeyframeAnimation",
|
||||
animName
|
||||
);
|
||||
return anim;
|
||||
}
|
||||
|
||||
@@ -350,10 +435,15 @@ public final class GltfPoseConverter {
|
||||
* Falls back to rest rotation if the clip is null or has no data for this joint.
|
||||
*/
|
||||
private static Quaternionf getRawAnimQuaternion(
|
||||
GltfData.AnimationClip rawClip, Quaternionf[] rawRestRotations, int jointIndex
|
||||
GltfData.AnimationClip rawClip,
|
||||
Quaternionf[] rawRestRotations,
|
||||
int jointIndex
|
||||
) {
|
||||
if (rawClip != null && jointIndex < rawClip.rotations().length
|
||||
&& rawClip.rotations()[jointIndex] != null) {
|
||||
if (
|
||||
rawClip != null &&
|
||||
jointIndex < rawClip.rotations().length &&
|
||||
rawClip.rotations()[jointIndex] != null
|
||||
) {
|
||||
return rawClip.rotations()[jointIndex][0]; // first frame
|
||||
}
|
||||
return rawRestRotations[jointIndex]; // fallback to rest
|
||||
@@ -361,29 +451,36 @@ public final class GltfPoseConverter {
|
||||
|
||||
private static void convertUpperBone(
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
String boneName, Quaternionf deltaQ
|
||||
String boneName,
|
||||
Quaternionf deltaQ
|
||||
) {
|
||||
// Decompose delta quaternion to Euler ZYX
|
||||
// JOML's getEulerAnglesZYX stores: euler.x = X rotation, euler.y = Y rotation, euler.z = Z rotation
|
||||
// (the "ZYX" refers to rotation ORDER, not storage order)
|
||||
Vector3f euler = new Vector3f();
|
||||
deltaQ.getEulerAnglesZYX(euler);
|
||||
float pitch = euler.x; // X rotation (pitch)
|
||||
float yaw = euler.y; // Y rotation (yaw)
|
||||
float roll = euler.z; // Z rotation (roll)
|
||||
float pitch = euler.x; // X rotation (pitch)
|
||||
float yaw = euler.y; // Y rotation (yaw)
|
||||
float roll = euler.z; // Z rotation (roll)
|
||||
|
||||
LOGGER.debug(String.format(
|
||||
"[GltfPipeline] Upper bone '%s': pitch=%.1f° yaw=%.1f° roll=%.1f°",
|
||||
boneName,
|
||||
Math.toDegrees(pitch),
|
||||
Math.toDegrees(yaw),
|
||||
Math.toDegrees(roll)));
|
||||
LOGGER.debug(
|
||||
String.format(
|
||||
"[GltfPipeline] Upper bone '%s': pitch=%.1f° yaw=%.1f° roll=%.1f°",
|
||||
boneName,
|
||||
Math.toDegrees(pitch),
|
||||
Math.toDegrees(yaw),
|
||||
Math.toDegrees(roll)
|
||||
)
|
||||
);
|
||||
|
||||
// Get the StateCollection for this body part
|
||||
String animPart = GltfBoneMapper.getAnimPartName(boneName);
|
||||
if (animPart == null) return;
|
||||
|
||||
KeyframeAnimation.StateCollection part = getPartByName(builder, animPart);
|
||||
KeyframeAnimation.StateCollection part = getPartByName(
|
||||
builder,
|
||||
animPart
|
||||
);
|
||||
if (part == null) return;
|
||||
|
||||
part.pitch.addKeyFrame(0, pitch, Ease.CONSTANT);
|
||||
@@ -393,12 +490,12 @@ public final class GltfPoseConverter {
|
||||
|
||||
private static void convertLowerBone(
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
String boneName, Quaternionf deltaQ
|
||||
String boneName,
|
||||
Quaternionf deltaQ
|
||||
) {
|
||||
// Extract bend angle and axis from the delta quaternion
|
||||
float angle = 2.0f * (float) Math.acos(
|
||||
Math.min(1.0, Math.abs(deltaQ.w))
|
||||
);
|
||||
float angle =
|
||||
2.0f * (float) Math.acos(Math.min(1.0, Math.abs(deltaQ.w)));
|
||||
|
||||
// Determine bend direction from axis
|
||||
float bendDirection = 0.0f;
|
||||
@@ -411,11 +508,14 @@ public final class GltfPoseConverter {
|
||||
angle = -angle;
|
||||
}
|
||||
|
||||
LOGGER.debug(String.format(
|
||||
"[GltfPipeline] Lower bone '%s': bendAngle=%.1f° bendDir=%.1f°",
|
||||
boneName,
|
||||
Math.toDegrees(angle),
|
||||
Math.toDegrees(bendDirection)));
|
||||
LOGGER.debug(
|
||||
String.format(
|
||||
"[GltfPipeline] Lower bone '%s': bendAngle=%.1f° bendDir=%.1f°",
|
||||
boneName,
|
||||
Math.toDegrees(angle),
|
||||
Math.toDegrees(bendDirection)
|
||||
)
|
||||
);
|
||||
|
||||
// Apply bend to the upper bone's StateCollection
|
||||
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
|
||||
@@ -424,7 +524,10 @@ public final class GltfPoseConverter {
|
||||
String animPart = GltfBoneMapper.getAnimPartName(upperBone);
|
||||
if (animPart == null) return;
|
||||
|
||||
KeyframeAnimation.StateCollection part = getPartByName(builder, animPart);
|
||||
KeyframeAnimation.StateCollection part = getPartByName(
|
||||
builder,
|
||||
animPart
|
||||
);
|
||||
if (part == null || !part.isBendable) return;
|
||||
|
||||
part.bend.addKeyFrame(0, angle, Ease.CONSTANT);
|
||||
@@ -432,7 +535,8 @@ public final class GltfPoseConverter {
|
||||
}
|
||||
|
||||
private static KeyframeAnimation.StateCollection getPartByName(
|
||||
KeyframeAnimation.AnimationBuilder builder, String name
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
String name
|
||||
) {
|
||||
return switch (name) {
|
||||
case "head" -> builder.head;
|
||||
@@ -461,17 +565,32 @@ public final class GltfPoseConverter {
|
||||
* @param partsWithKeyframes parts that received actual animation data from the GLB
|
||||
*/
|
||||
private static void enableSelectiveParts(
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
Set<String> ownedParts, Set<String> enabledParts,
|
||||
Set<String> partsWithKeyframes) {
|
||||
String[] allParts = {"head", "body", "rightArm", "leftArm", "rightLeg", "leftLeg"};
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
Set<String> ownedParts,
|
||||
Set<String> enabledParts,
|
||||
Set<String> partsWithKeyframes
|
||||
) {
|
||||
String[] allParts = {
|
||||
"head",
|
||||
"body",
|
||||
"rightArm",
|
||||
"leftArm",
|
||||
"rightLeg",
|
||||
"leftLeg",
|
||||
};
|
||||
for (String partName : allParts) {
|
||||
KeyframeAnimation.StateCollection part = getPartByName(builder, partName);
|
||||
KeyframeAnimation.StateCollection part = getPartByName(
|
||||
builder,
|
||||
partName
|
||||
);
|
||||
if (part != null) {
|
||||
if (ownedParts.contains(partName)) {
|
||||
// Always enable owned parts — the item controls these bones
|
||||
part.fullyEnablePart(false);
|
||||
} else if (enabledParts.contains(partName) && partsWithKeyframes.contains(partName)) {
|
||||
} else if (
|
||||
enabledParts.contains(partName) &&
|
||||
partsWithKeyframes.contains(partName)
|
||||
) {
|
||||
// Free part WITH keyframes: enable so the GLB animation drives it
|
||||
part.fullyEnablePart(false);
|
||||
} else {
|
||||
|
||||
@@ -24,17 +24,22 @@ import org.joml.Matrix4f;
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class GltfRenderLayer
|
||||
extends RenderLayer<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>> {
|
||||
extends RenderLayer<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>>
|
||||
{
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");
|
||||
|
||||
private static final ResourceLocation CUFFS_MODEL =
|
||||
ResourceLocation.fromNamespaceAndPath(
|
||||
"tiedup", "models/gltf/v2/handcuffs/cuffs_prototype.glb"
|
||||
"tiedup",
|
||||
"models/gltf/v2/handcuffs/cuffs_prototype.glb"
|
||||
);
|
||||
|
||||
public GltfRenderLayer(
|
||||
RenderLayerParent<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>> renderer
|
||||
RenderLayerParent<
|
||||
AbstractClientPlayer,
|
||||
PlayerModel<AbstractClientPlayer>
|
||||
> renderer
|
||||
) {
|
||||
super(renderer);
|
||||
}
|
||||
@@ -71,7 +76,9 @@ public class GltfRenderLayer
|
||||
// Live path: read skeleton from HumanoidModel (after PlayerAnimator)
|
||||
PlayerModel<AbstractClientPlayer> parentModel = this.getParentModel();
|
||||
Matrix4f[] joints = GltfLiveBoneReader.computeJointMatricesFromModel(
|
||||
parentModel, data, entity
|
||||
parentModel,
|
||||
data,
|
||||
entity
|
||||
);
|
||||
if (joints == null) {
|
||||
// Fallback to GLB-internal path if live reading fails
|
||||
@@ -84,10 +91,15 @@ public class GltfRenderLayer
|
||||
poseStack.translate(0, ALIGNMENT_Y, 0);
|
||||
|
||||
GltfMeshRenderer.renderSkinned(
|
||||
data, joints, poseStack, buffer,
|
||||
data,
|
||||
joints,
|
||||
poseStack,
|
||||
buffer,
|
||||
packedLight,
|
||||
net.minecraft.client.renderer.entity.LivingEntityRenderer
|
||||
.getOverlayCoords(entity, 0.0f)
|
||||
net.minecraft.client.renderer.entity.LivingEntityRenderer.getOverlayCoords(
|
||||
entity,
|
||||
0.0f
|
||||
)
|
||||
);
|
||||
poseStack.popPose();
|
||||
}
|
||||
|
||||
@@ -43,7 +43,9 @@ public final class GltfSkinningEngine {
|
||||
* @return interpolated joint matrices ready for skinning
|
||||
*/
|
||||
public static Matrix4f[] computeJointMatricesAnimated(
|
||||
GltfData data, GltfData.AnimationClip clip, float time
|
||||
GltfData data,
|
||||
GltfData.AnimationClip clip,
|
||||
float time
|
||||
) {
|
||||
int jointCount = data.jointCount();
|
||||
Matrix4f[] jointMatrices = new Matrix4f[jointCount];
|
||||
@@ -59,14 +61,17 @@ public final class GltfSkinningEngine {
|
||||
|
||||
// Compose with parent
|
||||
if (parents[j] >= 0 && worldTransforms[parents[j]] != null) {
|
||||
worldTransforms[j] = new Matrix4f(worldTransforms[parents[j]]).mul(local);
|
||||
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]);
|
||||
jointMatrices[j] = new Matrix4f(worldTransforms[j]).mul(
|
||||
data.inverseBindMatrices()[j]
|
||||
);
|
||||
}
|
||||
|
||||
return jointMatrices;
|
||||
@@ -75,7 +80,10 @@ public final class GltfSkinningEngine {
|
||||
/**
|
||||
* Internal: compute joint matrices from a specific animation clip.
|
||||
*/
|
||||
private static Matrix4f[] computeJointMatricesFromClip(GltfData data, GltfData.AnimationClip clip) {
|
||||
private static Matrix4f[] computeJointMatricesFromClip(
|
||||
GltfData data,
|
||||
GltfData.AnimationClip clip
|
||||
) {
|
||||
int jointCount = data.jointCount();
|
||||
Matrix4f[] jointMatrices = new Matrix4f[jointCount];
|
||||
Matrix4f[] worldTransforms = new Matrix4f[jointCount];
|
||||
@@ -90,14 +98,17 @@ public final class GltfSkinningEngine {
|
||||
|
||||
// Compose with parent
|
||||
if (parents[j] >= 0 && worldTransforms[parents[j]] != null) {
|
||||
worldTransforms[j] = new Matrix4f(worldTransforms[parents[j]]).mul(local);
|
||||
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]);
|
||||
jointMatrices[j] = new Matrix4f(worldTransforms[j]).mul(
|
||||
data.inverseBindMatrices()[j]
|
||||
);
|
||||
}
|
||||
|
||||
return jointMatrices;
|
||||
@@ -107,9 +118,16 @@ public final class GltfSkinningEngine {
|
||||
* Get the animation rotation for a joint (MC-converted).
|
||||
* Falls back to rest rotation if no animation.
|
||||
*/
|
||||
private static Quaternionf getAnimRotation(GltfData data, GltfData.AnimationClip clip, int jointIndex) {
|
||||
if (clip != null && jointIndex < clip.rotations().length
|
||||
&& clip.rotations()[jointIndex] != null) {
|
||||
private static Quaternionf getAnimRotation(
|
||||
GltfData data,
|
||||
GltfData.AnimationClip clip,
|
||||
int jointIndex
|
||||
) {
|
||||
if (
|
||||
clip != null &&
|
||||
jointIndex < clip.rotations().length &&
|
||||
clip.rotations()[jointIndex] != null
|
||||
) {
|
||||
return clip.rotations()[jointIndex][0]; // first frame
|
||||
}
|
||||
return data.restRotations()[jointIndex];
|
||||
@@ -119,10 +137,17 @@ public final class GltfSkinningEngine {
|
||||
* Get the animation translation for a joint (MC-converted).
|
||||
* Falls back to rest translation if no animation translation exists.
|
||||
*/
|
||||
private static Vector3f getAnimTranslation(GltfData data, GltfData.AnimationClip clip, int jointIndex) {
|
||||
if (clip != null && clip.translations() != null
|
||||
&& jointIndex < clip.translations().length
|
||||
&& clip.translations()[jointIndex] != null) {
|
||||
private static Vector3f getAnimTranslation(
|
||||
GltfData data,
|
||||
GltfData.AnimationClip clip,
|
||||
int jointIndex
|
||||
) {
|
||||
if (
|
||||
clip != null &&
|
||||
clip.translations() != null &&
|
||||
jointIndex < clip.translations().length &&
|
||||
clip.translations()[jointIndex] != null
|
||||
) {
|
||||
return clip.translations()[jointIndex][0]; // first frame
|
||||
}
|
||||
return data.restTranslations()[jointIndex];
|
||||
@@ -144,10 +169,16 @@ public final class GltfSkinningEngine {
|
||||
* @return new Quaternionf with the interpolated rotation (never mutates source data)
|
||||
*/
|
||||
private static Quaternionf getInterpolatedRotation(
|
||||
GltfData data, GltfData.AnimationClip clip, int jointIndex, float time
|
||||
GltfData data,
|
||||
GltfData.AnimationClip clip,
|
||||
int jointIndex,
|
||||
float time
|
||||
) {
|
||||
if (clip == null || jointIndex >= clip.rotations().length
|
||||
|| clip.rotations()[jointIndex] == null) {
|
||||
if (
|
||||
clip == null ||
|
||||
jointIndex >= clip.rotations().length ||
|
||||
clip.rotations()[jointIndex] == null
|
||||
) {
|
||||
// No animation data for this joint -- use rest pose (copy to avoid mutation)
|
||||
Quaternionf rest = data.restRotations()[jointIndex];
|
||||
return new Quaternionf(rest);
|
||||
@@ -187,11 +218,17 @@ public final class GltfSkinningEngine {
|
||||
* @return new Vector3f with the interpolated translation (never mutates source data)
|
||||
*/
|
||||
private static Vector3f getInterpolatedTranslation(
|
||||
GltfData data, GltfData.AnimationClip clip, int jointIndex, float time
|
||||
GltfData data,
|
||||
GltfData.AnimationClip clip,
|
||||
int jointIndex,
|
||||
float time
|
||||
) {
|
||||
if (clip == null || clip.translations() == null
|
||||
|| jointIndex >= clip.translations().length
|
||||
|| clip.translations()[jointIndex] == null) {
|
||||
if (
|
||||
clip == null ||
|
||||
clip.translations() == null ||
|
||||
jointIndex >= clip.translations().length ||
|
||||
clip.translations()[jointIndex] == null
|
||||
) {
|
||||
// No animation data for this joint -- use rest pose (copy to avoid mutation)
|
||||
Vector3f rest = data.restTranslations()[jointIndex];
|
||||
return new Vector3f(rest);
|
||||
@@ -232,9 +269,13 @@ public final class GltfSkinningEngine {
|
||||
* @param tmpNorm pre-allocated scratch Vector4f for normal transforms
|
||||
*/
|
||||
public static void skinVertex(
|
||||
GltfData data, int vertexIdx, Matrix4f[] jointMatrices,
|
||||
float[] outPos, float[] outNormal,
|
||||
Vector4f tmpPos, Vector4f tmpNorm
|
||||
GltfData data,
|
||||
int vertexIdx,
|
||||
Matrix4f[] jointMatrices,
|
||||
float[] outPos,
|
||||
float[] outNormal,
|
||||
Vector4f tmpPos,
|
||||
Vector4f tmpNorm
|
||||
) {
|
||||
float[] positions = data.positions();
|
||||
float[] normals = data.normals();
|
||||
@@ -252,8 +293,12 @@ public final class GltfSkinningEngine {
|
||||
float nz = normals[vertexIdx * 3 + 2];
|
||||
|
||||
// LBS: v_skinned = Σ(w[i] * jointMatrix[j[i]] * v_rest)
|
||||
float sx = 0, sy = 0, sz = 0;
|
||||
float snx = 0, sny = 0, snz = 0;
|
||||
float sx = 0,
|
||||
sy = 0,
|
||||
sz = 0;
|
||||
float snx = 0,
|
||||
sny = 0,
|
||||
snz = 0;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int ji = joints[vertexIdx * 4 + i];
|
||||
|
||||
Reference in New Issue
Block a user