From 20fd55ed22612d067cd21398a0397a8a69f8cfc6 Mon Sep 17 00:00:00 2001 From: Wyatt Gillette Date: Thu, 26 Jun 2025 10:55:49 +0200 Subject: [PATCH 1/4] Update Armature.java --- .../src/main/java/com/jme3/anim/Armature.java | 154 ++++++++++++------ 1 file changed, 101 insertions(+), 53 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/Armature.java b/jme3-core/src/main/java/com/jme3/anim/Armature.java index 197242cfa1..0cd25bf58a 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Armature.java +++ b/jme3-core/src/main/java/com/jme3/anim/Armature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2024 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,27 +33,53 @@ import com.jme3.anim.util.JointModelTransform; import com.jme3.asset.AssetLoadException; -import com.jme3.export.*; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; import com.jme3.math.Matrix4f; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; + import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** - * Created by Nehon on 15/12/2017. + * An `Armature` represents a skeletal structure composed of {@link Joint} objects. + * It manages the hierarchy of joints, their transformations, and provides methods + * for updating the armature's pose, computing skinning matrices, and handling + * serialization/deserialization. + *

+ * The `Armature` is a fundamental component for character animation, allowing + * joints to be manipulated to deform a mesh based on their transformations. + * + * @author Nehon */ public class Armature implements JmeCloneable, Savable { + /** + * The array of root joints in this armature. These are joints that have no parent. + */ private Joint[] rootJoints; + /** + * A flat list of all joints managed by this armature, indexed by their ID. + */ private Joint[] jointList; - /** - * Contains the skinning matrices, multiplying it by a vertex effected by a bone - * will cause it to go to the animated position. + * Contains the skinning matrices. Multiplying a vertex affected by a joint + * by its corresponding skinning matrix will transform the vertex to the + * animated position. + */ + private transient Matrix4f[] skinningMatrices; + /** + * The class used to instantiate {@link JointModelTransform} instances for each joint. + * Defaults to {@link SeparateJointModelTransform}. This allows for customization + * of how joint model transformations are handled (e.g., separate scale/rotation/translation + * or combined in a single matrix). */ - private transient Matrix4f[] skinningMatrixes; private Class modelTransformClass = SeparateJointModelTransform.class; /** @@ -63,13 +89,16 @@ protected Armature() { } /** - * Creates an armature from a joint list. - * The root joints are found automatically. + * Creates an `Armature` from a given list of joints. + * The root joints are automatically identified based on their parent-child relationships. + * Each joint is assigned an ID corresponding to its index in the `jointList`. *

- * Note that using this constructor will cause the joints in the list - * to have their bind pose recomputed based on their local transforms. + * Note that calling this constructor will cause the bind pose of the joints + * in the list to be recomputed based on their initial local transforms. + * The initial pose is also applied after construction. + *

* - * @param jointList The list of joints to manage by this Armature + * @param jointList An array of {@link Joint} objects that will be managed by this `Armature`. */ public Armature(Joint[] jointList) { this.jointList = jointList; @@ -83,7 +112,7 @@ public Armature(Joint[] jointList) { rootJointList.add(joint); } } - rootJoints = rootJointList.toArray(new Joint[rootJointList.size()]); + rootJoints = rootJointList.toArray(new Joint[0]); createSkinningMatrices(); @@ -102,39 +131,56 @@ public void update() { } } + /** + * Initializes or re-initializes the array of skinning matrices. + * Each matrix in this array corresponds to a joint in the `jointList` + * and is used to transform vertices affected by that joint into their + * animated position. + */ private void createSkinningMatrices() { - skinningMatrixes = new Matrix4f[jointList.length]; - for (int i = 0; i < skinningMatrixes.length; i++) { - skinningMatrixes[i] = new Matrix4f(); + skinningMatrices = new Matrix4f[jointList.length]; + for (int i = 0; i < skinningMatrices.length; i++) { + skinningMatrices[i] = new Matrix4f(); } } /** - * Sets the JointModelTransform implementation - * Default is {@link SeparateJointModelTransform} + * Sets the {@link JointModelTransform} implementation to be used by all joints + * in this `Armature`. This allows customizing how joint transformations + * (scale, rotation, translation) are managed internally. + *

+ * By default, {@link SeparateJointModelTransform} is used. * - * @param modelTransformClass which implementation to use + * @param aClass The {@link Class} object representing the desired + * {@link JointModelTransform} implementation. * @see JointModelTransform * @see MatrixJointModelTransform * @see SeparateJointModelTransform */ - public void setModelTransformClass(Class modelTransformClass) { - this.modelTransformClass = modelTransformClass; - if (jointList == null) { - return; - } - for (Joint joint : jointList) { - instantiateJointModelTransform(joint); + public void setModelTransformClass(Class aClass) { + this.modelTransformClass = aClass; + // Only re-instantiate if jointList is already populated + if (jointList != null) { + for (Joint joint : jointList) { + instantiateJointModelTransform(joint); + } } } + /** + * Instantiates a new {@link JointModelTransform} object of the type + * specified by `modelTransformClass` and sets it on the given joint. + * + * @param joint The {@link Joint} for which to instantiate and set the + * {@link JointModelTransform}. + */ private void instantiateJointModelTransform(Joint joint) { try { - joint.setJointModelTransform(modelTransformClass.getDeclaredConstructor().newInstance()); - } catch (InstantiationException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException - | NoSuchMethodException | SecurityException e) { - throw new IllegalArgumentException(e); + JointModelTransform transform = modelTransformClass.getDeclaredConstructor().newInstance(); + joint.setJointModelTransform(transform); + + } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException ex) { + throw new IllegalArgumentException("Failed to instantiate JointModelTransform for joint: " + joint.getName(), ex); } } @@ -173,9 +219,9 @@ public Joint getJoint(int index) { * @return the pre-existing instance or null if not found */ public Joint getJoint(String name) { - for (int i = 0; i < jointList.length; i++) { - if (jointList[i].getName().equals(name)) { - return jointList[i]; + for (Joint joint : jointList) { + if (joint.getName().equals(name)) { + return joint; } } return null; @@ -257,15 +303,17 @@ public void applyInitialPose() { } /** - * Compute the skinning matrices for each bone of the armature that would be used to transform vertices of associated meshes + * Computes the skinning matrices for each joint in the `Armature`. + * These matrices are essential for skinning (vertex deformation), as they + * transform vertices from model space to the animated joint's space. * * @return the pre-existing array */ public Matrix4f[] computeSkinningMatrices() { for (int i = 0; i < jointList.length; i++) { - jointList[i].getOffsetTransform(skinningMatrixes[i]); + jointList[i].getOffsetTransform(skinningMatrices[i]); } - return skinningMatrixes; + return skinningMatrices; } /** @@ -291,7 +339,7 @@ public Object jmeClone() { public void cloneFields(Cloner cloner, Object original) { this.rootJoints = cloner.clone(rootJoints); this.jointList = cloner.clone(jointList); - this.skinningMatrixes = cloner.clone(skinningMatrixes); + this.skinningMatrices = cloner.clone(skinningMatrices); for (Joint joint : jointList) { instantiateJointModelTransform(joint); } @@ -307,21 +355,21 @@ public void cloneFields(Cloner cloner, Object original) { @Override @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { - InputCapsule input = im.getCapsule(this); + InputCapsule ic = im.getCapsule(this); - Savable[] jointRootsAsSavable = input.readSavableArray("rootJoints", null); - rootJoints = new Joint[jointRootsAsSavable.length]; - System.arraycopy(jointRootsAsSavable, 0, rootJoints, 0, jointRootsAsSavable.length); + Savable[] rootJointsSavable = ic.readSavableArray("rootJoints", null); + rootJoints = new Joint[rootJointsSavable.length]; + System.arraycopy(rootJointsSavable, 0, rootJoints, 0, rootJointsSavable.length); - Savable[] jointListAsSavable = input.readSavableArray("jointList", null); - jointList = new Joint[jointListAsSavable.length]; - System.arraycopy(jointListAsSavable, 0, jointList, 0, jointListAsSavable.length); + Savable[] jointListSavable = ic.readSavableArray("jointList", null); + jointList = new Joint[jointListSavable.length]; + System.arraycopy(jointListSavable, 0, jointList, 0, jointListSavable.length); - String className = input.readString("modelTransformClass", MatrixJointModelTransform.class.getCanonicalName()); + String className = ic.readString("modelTransformClass", MatrixJointModelTransform.class.getCanonicalName()); try { modelTransformClass = (Class) Class.forName(className); } catch (ClassNotFoundException e) { - throw new AssetLoadException("Cannot find class for name " + className); + throw new AssetLoadException("Cannot find class for name '" + className + "' specified for JointModelTransform.", e); } int i = 0; @@ -346,9 +394,9 @@ public void read(JmeImporter im) throws IOException { */ @Override public void write(JmeExporter ex) throws IOException { - OutputCapsule output = ex.getCapsule(this); - output.write(rootJoints, "rootJoints", null); - output.write(jointList, "jointList", null); - output.write(modelTransformClass.getCanonicalName(), "modelTransformClass", MatrixJointModelTransform.class.getCanonicalName()); + OutputCapsule oc = ex.getCapsule(this); + oc.write(rootJoints, "rootJoints", null); + oc.write(jointList, "jointList", null); + oc.write(modelTransformClass.getCanonicalName(), "modelTransformClass", MatrixJointModelTransform.class.getCanonicalName()); } } From e238734b8ed7335078323b737ee2844441436a62 Mon Sep 17 00:00:00 2001 From: Wyatt Gillette Date: Thu, 26 Jun 2025 11:52:28 +0200 Subject: [PATCH 2/4] Armature: simplify javadoc --- jme3-core/src/main/java/com/jme3/anim/Armature.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/Armature.java b/jme3-core/src/main/java/com/jme3/anim/Armature.java index 0cd25bf58a..5018395ae0 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Armature.java +++ b/jme3-core/src/main/java/com/jme3/anim/Armature.java @@ -50,11 +50,7 @@ /** * An `Armature` represents a skeletal structure composed of {@link Joint} objects. * It manages the hierarchy of joints, their transformations, and provides methods - * for updating the armature's pose, computing skinning matrices, and handling - * serialization/deserialization. - *

- * The `Armature` is a fundamental component for character animation, allowing - * joints to be manipulated to deform a mesh based on their transformations. + * for updating the armature's pose and computing skinning matrices. * * @author Nehon */ From 07ca73ce8c46d13a232c1a85e3fef020f132dcd7 Mon Sep 17 00:00:00 2001 From: Wyatt Gillette Date: Mon, 7 Jul 2025 19:40:53 +0200 Subject: [PATCH 3/4] Update Armature.java --- jme3-core/src/main/java/com/jme3/anim/Armature.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/Armature.java b/jme3-core/src/main/java/com/jme3/anim/Armature.java index 5018395ae0..bcf4965fdd 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Armature.java +++ b/jme3-core/src/main/java/com/jme3/anim/Armature.java @@ -108,7 +108,7 @@ public Armature(Joint[] jointList) { rootJointList.add(joint); } } - rootJoints = rootJointList.toArray(new Joint[0]); + rootJoints = rootJointList.toArray(new Joint[rootJointList.size()]); createSkinningMatrices(); From b80e22947dacd586899e82ac0cd74b4d834ff1eb Mon Sep 17 00:00:00 2001 From: Wyatt Gillette Date: Tue, 12 Aug 2025 19:10:49 +0200 Subject: [PATCH 4/4] Update Armature.java --- .../src/main/java/com/jme3/anim/Armature.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/anim/Armature.java b/jme3-core/src/main/java/com/jme3/anim/Armature.java index bcf4965fdd..cdd1178f44 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Armature.java +++ b/jme3-core/src/main/java/com/jme3/anim/Armature.java @@ -108,7 +108,7 @@ public Armature(Joint[] jointList) { rootJointList.add(joint); } } - rootJoints = rootJointList.toArray(new Joint[rootJointList.size()]); + rootJoints = rootJointList.toArray(new Joint[0]); createSkinningMatrices(); @@ -215,9 +215,9 @@ public Joint getJoint(int index) { * @return the pre-existing instance or null if not found */ public Joint getJoint(String name) { - for (Joint joint : jointList) { - if (joint.getName().equals(name)) { - return joint; + for (int i = 0; i < jointList.length; i++) { + if (jointList[i].getName().equals(name)) { + return jointList[i]; } } return null; @@ -353,13 +353,13 @@ public void cloneFields(Cloner cloner, Object original) { public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); - Savable[] rootJointsSavable = ic.readSavableArray("rootJoints", null); - rootJoints = new Joint[rootJointsSavable.length]; - System.arraycopy(rootJointsSavable, 0, rootJoints, 0, rootJointsSavable.length); + Savable[] jointRootsAsSavable = ic.readSavableArray("rootJoints", null); + rootJoints = new Joint[jointRootsAsSavable.length]; + System.arraycopy(jointRootsAsSavable, 0, rootJoints, 0, jointRootsAsSavable.length); - Savable[] jointListSavable = ic.readSavableArray("jointList", null); - jointList = new Joint[jointListSavable.length]; - System.arraycopy(jointListSavable, 0, jointList, 0, jointListSavable.length); + Savable[] jointListAsSavable = ic.readSavableArray("jointList", null); + jointList = new Joint[jointListAsSavable.length]; + System.arraycopy(jointListAsSavable, 0, jointList, 0, jointListAsSavable.length); String className = ic.readString("modelTransformClass", MatrixJointModelTransform.class.getCanonicalName()); try {