Skip to content

Feat: Improve ArmatureDebugger #2493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.jme3.scene.debug.custom;

/*
* Copyright (c) 2009-2021 jMonkeyEngine
* Copyright (c) 2009-2025 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -31,9 +29,11 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.debug.custom;

import com.jme3.anim.Armature;
import com.jme3.anim.Joint;
import com.jme3.anim.SkinningControl;
import com.jme3.asset.AssetManager;
import com.jme3.collision.Collidable;
import com.jme3.collision.CollisionResults;
Expand All @@ -55,104 +55,176 @@
public class ArmatureDebugger extends Node {

/**
* The lines of the bones or the wires between their heads.
* The node responsible for rendering the bones/wires and their outlines.
*/
private ArmatureNode armatureNode;

/**
* The {@link Armature} instance being debugged.
*/
private Armature armature;

/**
* A node containing all {@link Geometry} objects representing the joint points.
*/
private Node joints;
/**
* A node containing all {@link Geometry} objects representing the bone outlines.
*/
private Node outlines;
/**
* A node containing all {@link Geometry} objects representing the bone wires/lines.
*/
private Node wires;
/**
* The dotted lines between a bone's tail and the had of its children. Not
* available if the length data was not provided.
*/
private ArmatureInterJointsWire interJointWires;

/**
* Default constructor for `ArmatureDebugger`.
* Use {@link #ArmatureDebugger(String, Armature, List)} for a functional instance.
*/
public ArmatureDebugger() {
}

/**
* Creates a debugger with no length data. The wires will be a connection
* between the bones' heads only. The points will show the bones' heads only
* and no dotted line of inter bones connection will be visible.
* Convenience constructor that creates an {@code ArmatureDebugger} and immediately
* initializes its materials based on the provided {@code AssetManager}
* and {@code SkinningControl}.
*
* @param assetManager The {@link AssetManager} used to load textures and materials
* for the debug visualization.
* @param skControl The {@link SkinningControl} from which to extract the
* {@link Armature} and its associated joints.
*/
public ArmatureDebugger(AssetManager assetManager, SkinningControl skControl) {
this(null, skControl.getArmature(), skControl.getArmature().getJointList());
initialize(assetManager, null);
}

/**
* Creates an `ArmatureDebugger` instance without explicit bone length data.
* In this configuration, the visual representation will consist of wires
* connecting the bone heads, and points representing the bone heads.
* No dotted lines for inter-bone connections will be visible.
*
* @param name the name of the debugger's node
* @param armature the armature that will be shown
* @param deformingJoints a list of joints
* @param name The name of this debugger's root node.
* @param armature The {@link Armature} to be visualized.
* @param deformingJoints A {@link List} of {@link Joint} objects that are
* considered deforming joints.
*/
public ArmatureDebugger(String name, Armature armature, List<Joint> deformingJoints) {
super(name);
this.armature = armature;
// Ensure the armature's world transforms are up-to-date before visualization.
armature.update();

// Initialize the main container nodes for different visual elements.
joints = new Node("joints");
outlines = new Node("outlines");
wires = new Node("bones");
this.attachChild(joints);
this.attachChild(outlines);
this.attachChild(wires);
Node ndJoints = new Node("non deforming Joints");
Node ndOutlines = new Node("non deforming Joints outlines");
Node ndWires = new Node("non deforming Joints wires");
joints.attachChild(ndJoints);
outlines.attachChild(ndOutlines);
wires.attachChild(ndWires);
Node outlineDashed = new Node("Outlines Dashed");
Node wiresDashed = new Node("Wires Dashed");
wiresDashed.attachChild(new Node("dashed non defrom"));
outlineDashed.attachChild(new Node("dashed non defrom"));

// Create child nodes specifically for non-deforming joints' visualization
joints.attachChild(new Node("NonDeformingJoints"));
outlines.attachChild(new Node("NonDeformingOutlines"));
wires.attachChild(new Node("NonDeformingWires"));

Node outlineDashed = new Node("DashedOutlines");
outlineDashed.attachChild(new Node("DashedNonDeformingOutlines"));
outlines.attachChild(outlineDashed);

Node wiresDashed = new Node("DashedWires");
wiresDashed.attachChild(new Node("DashedNonDeformingWires"));
wires.attachChild(wiresDashed);

// Initialize the core ArmatureNode which handles the actual mesh generation.
armatureNode = new ArmatureNode(armature, joints, wires, outlines, deformingJoints);

this.attachChild(armatureNode);

// By default, non-deforming joints are hidden.
displayNonDeformingJoint(false);
}

/**
* Sets the visibility of non-deforming joints and their associated outlines and wires.
*
* @param display `true` to make non-deforming joints visible, `false` to hide them.
*/
public void displayNonDeformingJoint(boolean display) {
joints.getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always);
outlines.getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always);
wires.getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always);
((Node) outlines.getChild(1)).getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always);
((Node) wires.getChild(1)).getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always);
CullHint cullHint = display ? CullHint.Dynamic : CullHint.Always;

joints.getChild(0).setCullHint(cullHint);
outlines.getChild(0).setCullHint(cullHint);
wires.getChild(0).setCullHint(cullHint);

((Node) outlines.getChild(1)).getChild(0).setCullHint(cullHint);
((Node) wires.getChild(1)).getChild(0).setCullHint(cullHint);
}

/**
* Initializes the materials and camera for the debugger's visual components.
* This method should be called after the `ArmatureDebugger` is added to a scene graph
* and an {@link AssetManager} and {@link Camera} are available.
*
* @param assetManager The {@link AssetManager} to load textures and materials.
* @param camera The scene's primary {@link Camera}, used by the `ArmatureNode`
* for billboard rendering of joint points.
*/
public void initialize(AssetManager assetManager, Camera camera) {

armatureNode.setCamera(camera);

Material matJoints = new Material(assetManager, "Common/MatDefs/Misc/Billboard.j3md");
Texture t = assetManager.loadTexture("Common/Textures/dot.png");
matJoints.setTexture("Texture", t);
matJoints.getAdditionalRenderState().setDepthTest(false);
matJoints.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
// Material for joint points (billboard dots).
Material matJoints = getJointMaterial(assetManager);
joints.setQueueBucket(RenderQueue.Bucket.Translucent);
joints.setMaterial(matJoints);

Material matWires = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matWires.setBoolean("VertexColor", true);
matWires.getAdditionalRenderState().setLineWidth(1f);
// Material for bone wires/lines (unshaded, vertex colored).
Material matWires = getUnshadedMaterial(assetManager);
wires.setMaterial(matWires);

Material matOutline = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matOutline.setBoolean("VertexColor", true);
matOutline.getAdditionalRenderState().setLineWidth(1f);
// Material for dashed wires ("DashedLine.j3md" shader).
Material matWires2 = getDashedMaterial(assetManager);
wires.getChild(1).setMaterial(matWires2);

// Material for bone outlines (unshaded, vertex colored).
Material matOutline = getUnshadedMaterial(assetManager);
outlines.setMaterial(matOutline);

Material matOutline2 = new Material(assetManager, "Common/MatDefs/Misc/DashedLine.j3md");
matOutline2.getAdditionalRenderState().setLineWidth(1);
// Material for dashed outlines ("DashedLine.j3md" shader).
Material matOutline2 = getDashedMaterial(assetManager);
outlines.getChild(1).setMaterial(matOutline2);
}

Material matWires2 = new Material(assetManager, "Common/MatDefs/Misc/DashedLine.j3md");
matWires2.getAdditionalRenderState().setLineWidth(1);
wires.getChild(1).setMaterial(matWires2);
private Material getJointMaterial(AssetManager asm) {
Material mat = new Material(asm, "Common/MatDefs/Misc/Billboard.j3md");
Texture tex = asm.loadTexture("Common/Textures/dot.png");
mat.setTexture("Texture", tex);
mat.getAdditionalRenderState().setDepthTest(false);
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
return mat;
}

private Material getUnshadedMaterial(AssetManager asm) {
Material mat = new Material(asm, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setBoolean("VertexColor", true);
mat.getAdditionalRenderState().setDepthTest(false);
return mat;
}

private Material getDashedMaterial(AssetManager asm) {
Material mat = new Material(asm, "Common/MatDefs/Misc/DashedLine.j3md");
mat.getAdditionalRenderState().setDepthTest(false);
return mat;
}

/**
* Returns the {@link Armature} instance associated with this debugger.
*
* @return The {@link Armature} being debugged.
*/
public Armature getArmature() {
return armature;
}
Expand All @@ -168,21 +240,35 @@ public int collideWith(Collidable other, CollisionResults results) {
return armatureNode.collideWith(other, results);
}

protected Joint select(Geometry g) {
return armatureNode.select(g);
/**
* Selects and returns the {@link Joint} associated with a given {@link Geometry}.
* This is an internal helper method, likely used for picking operations.
*
* @param geo The {@link Geometry} representing a part of a joint.
* @return The {@link Joint} corresponding to the geometry, or `null` if not found.
*/
protected Joint select(Geometry geo) {
return armatureNode.select(geo);
}

/**
* @return the armature wires
* Returns the {@link ArmatureNode} which is responsible for generating and
* managing the visual mesh of the bones and wires.
*
* @return The {@link ArmatureNode} instance.
*/
public ArmatureNode getBoneShapes() {
return armatureNode;
}

/**
* @return the dotted line between bones (can be null)
* Returns the {@link ArmatureInterJointsWire} instance, which represents the
* dotted lines connecting a bone's tail to the head of its children.
* This will be `null` if the debugger was created without bone length data.
*
* @return The {@link ArmatureInterJointsWire} instance, or `null` if not present.
*/
public ArmatureInterJointsWire getInterJointWires() {
return interJointWires;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.jme3.scene.debug.custom;

/*
* Copyright (c) 2009-2021 jMonkeyEngine
* Copyright (c) 2009-2025 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -31,7 +29,7 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.jme3.scene.debug.custom;

import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
Expand All @@ -46,20 +44,38 @@
* @author Marcin Roguski (Kaelthas)
*/
public class ArmatureInterJointsWire extends Mesh {
private final Vector3f tmp = new Vector3f();

/**
* A temporary {@link Vector3f} used for calculations to avoid object allocation.
*/
private final Vector3f tempVec = new Vector3f();

/**
* For serialization only. Do not use.
*/
protected ArmatureInterJointsWire() {
}

/**
* Creates a new {@code ArmatureInterJointsWire} mesh.
* The mesh will be set up to draw lines from the {@code start} vector to each of the {@code ends} vectors.
*
* @param start The starting point of the lines (e.g., the bone tail's position). Not null.
* @param ends An array of ending points for the lines (e.g., the children's head positions). Not null.
*/
public ArmatureInterJointsWire(Vector3f start, Vector3f[] ends) {
setMode(Mode.Lines);
updateGeometry(start, ends);
}

/**
* For serialization only. Do not use.
* Updates the geometry of this mesh based on the provided start and end points.
* This method re-generates the position, texture coordinate, normal, and index buffers
* for the mesh.
*
* @param start The new starting point for the lines. Not null.
* @param ends An array of new ending points for the lines. Not null.
*/
protected ArmatureInterJointsWire() {
}

protected void updateGeometry(Vector3f start, Vector3f[] ends) {
float[] pos = new float[ends.length * 3 + 3];
pos[0] = start.x;
Expand All @@ -78,7 +94,7 @@ protected void updateGeometry(Vector3f start, Vector3f[] ends) {
texCoord[0] = 0;
texCoord[1] = 0;
for (int i = 0; i < ends.length * 2; i++) {
texCoord[i + 2] = tmp.set(start).subtractLocal(ends[i / 2]).length();
texCoord[i + 2] = tempVec.set(start).subtractLocal(ends[i / 2]).length();
}
setBuffer(Type.TexCoord, 2, texCoord);

Expand Down
Loading