|
@@ -1,5 +1,5 @@
|
|
/*
|
|
/*
|
|
- * Copyright (c) 2009-2021 jMonkeyEngine
|
|
|
|
|
|
+ * Copyright (c) 2009-2025 jMonkeyEngine
|
|
* All rights reserved.
|
|
* All rights reserved.
|
|
*
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* Redistribution and use in source and binary forms, with or without
|
|
@@ -31,59 +31,115 @@
|
|
*/
|
|
*/
|
|
package com.jme3.scene.debug.custom;
|
|
package com.jme3.scene.debug.custom;
|
|
|
|
|
|
-import com.jme3.anim.*;
|
|
|
|
|
|
+import com.jme3.anim.Armature;
|
|
|
|
+import com.jme3.anim.Joint;
|
|
|
|
+import com.jme3.anim.SkinningControl;
|
|
import com.jme3.app.Application;
|
|
import com.jme3.app.Application;
|
|
import com.jme3.app.state.BaseAppState;
|
|
import com.jme3.app.state.BaseAppState;
|
|
|
|
+import com.jme3.asset.AssetManager;
|
|
import com.jme3.collision.CollisionResults;
|
|
import com.jme3.collision.CollisionResults;
|
|
|
|
+import com.jme3.input.InputManager;
|
|
import com.jme3.input.KeyInput;
|
|
import com.jme3.input.KeyInput;
|
|
import com.jme3.input.MouseInput;
|
|
import com.jme3.input.MouseInput;
|
|
-import com.jme3.input.controls.*;
|
|
|
|
-import com.jme3.light.DirectionalLight;
|
|
|
|
-import com.jme3.math.*;
|
|
|
|
|
|
+import com.jme3.input.controls.ActionListener;
|
|
|
|
+import com.jme3.input.controls.KeyTrigger;
|
|
|
|
+import com.jme3.input.controls.MouseButtonTrigger;
|
|
|
|
+import com.jme3.math.Ray;
|
|
|
|
+import com.jme3.math.Vector2f;
|
|
|
|
+import com.jme3.math.Vector3f;
|
|
import com.jme3.renderer.Camera;
|
|
import com.jme3.renderer.Camera;
|
|
|
|
+import com.jme3.renderer.RenderManager;
|
|
import com.jme3.renderer.ViewPort;
|
|
import com.jme3.renderer.ViewPort;
|
|
-import com.jme3.scene.*;
|
|
|
|
|
|
+import com.jme3.scene.Geometry;
|
|
|
|
+import com.jme3.scene.Node;
|
|
|
|
+import com.jme3.scene.SceneGraphVisitorAdapter;
|
|
|
|
+import com.jme3.scene.Spatial;
|
|
|
|
+import com.jme3.scene.control.AbstractControl;
|
|
|
|
+import com.jme3.util.TempVars;
|
|
|
|
|
|
-import java.util.*;
|
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
+import java.util.HashMap;
|
|
|
|
+import java.util.List;
|
|
|
|
+import java.util.Map;
|
|
|
|
+import java.util.function.Consumer;
|
|
|
|
+import java.util.logging.Level;
|
|
|
|
+import java.util.logging.Logger;
|
|
|
|
|
|
/**
|
|
/**
|
|
|
|
+ * A debug application state for visualizing and interacting with JME3 armatures (skeletons).
|
|
|
|
+ * This state allows users to see the joints of an armature, select individual joints
|
|
|
|
+ * by clicking on them, and view their local and model transforms.
|
|
|
|
+ * It also provides a toggle to display non-deforming joints.
|
|
|
|
+ * <p>
|
|
|
|
+ * This debug state operates on its own `ViewPort` and `debugNode` to prevent
|
|
|
|
+ * interference with the main scene's rendering.
|
|
|
|
+ *
|
|
* @author Nehon
|
|
* @author Nehon
|
|
|
|
+ * @author capdevon
|
|
*/
|
|
*/
|
|
public class ArmatureDebugAppState extends BaseAppState {
|
|
public class ArmatureDebugAppState extends BaseAppState {
|
|
|
|
|
|
|
|
+ private static final Logger logger = Logger.getLogger(ArmatureDebugAppState.class.getName());
|
|
|
|
+
|
|
|
|
+ private static final String PICK_JOINT = "ArmatureDebugAppState_PickJoint";
|
|
|
|
+ private static final String TOGGLE_JOINTS = "ArmatureDebugAppState_DisplayAllJoints";
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * The maximum delay for a mouse click to be registered as a single click.
|
|
|
|
+ */
|
|
public static final float CLICK_MAX_DELAY = 0.2f;
|
|
public static final float CLICK_MAX_DELAY = 0.2f;
|
|
- private Node debugNode = new Node("debugNode");
|
|
|
|
- private Map<Armature, ArmatureDebugger> armatures = new HashMap<>();
|
|
|
|
- private Map<Armature, Joint> selectedBones = new HashMap<>();
|
|
|
|
- private Application app;
|
|
|
|
- private boolean displayAllJoints = false;
|
|
|
|
|
|
+
|
|
|
|
+ private Node debugNode = new Node("ArmaturesDebugNode");
|
|
|
|
+ private final Map<Armature, ArmatureDebugger> armatures = new HashMap<>();
|
|
|
|
+ private final List<Consumer<Joint>> selectionListeners = new ArrayList<>();
|
|
|
|
+ private boolean displayNonDeformingJoints = false;
|
|
private float clickDelay = -1;
|
|
private float clickDelay = -1;
|
|
- Vector3f tmp = new Vector3f();
|
|
|
|
- Vector3f tmp2 = new Vector3f();
|
|
|
|
- ViewPort vp;
|
|
|
|
|
|
+ private ViewPort vp;
|
|
|
|
+ private Camera cam;
|
|
|
|
+ private InputManager inputManager;
|
|
|
|
+ private boolean showOnTop = true;
|
|
|
|
+ private boolean enableJointInfoLogging = true;
|
|
|
|
|
|
@Override
|
|
@Override
|
|
protected void initialize(Application app) {
|
|
protected void initialize(Application app) {
|
|
- vp = app.getRenderManager().createMainView("debug", app.getCamera());
|
|
|
|
|
|
+
|
|
|
|
+ inputManager = app.getInputManager();
|
|
|
|
+ cam = app.getCamera();
|
|
|
|
+
|
|
|
|
+ vp = app.getRenderManager().createMainView("ArmatureDebugView", cam);
|
|
vp.attachScene(debugNode);
|
|
vp.attachScene(debugNode);
|
|
- vp.setClearDepth(true);
|
|
|
|
- this.app = app;
|
|
|
|
- for (ArmatureDebugger armatureDebugger : armatures.values()) {
|
|
|
|
- armatureDebugger.initialize(app.getAssetManager(), app.getCamera());
|
|
|
|
- }
|
|
|
|
- app.getInputManager().addListener(actionListener, "shoot", "toggleJoints");
|
|
|
|
- app.getInputManager().addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
|
|
|
|
- app.getInputManager().addMapping("toggleJoints", new KeyTrigger(KeyInput.KEY_F10));
|
|
|
|
|
|
+ vp.setClearDepth(showOnTop);
|
|
|
|
|
|
- debugNode.addLight(new DirectionalLight(new Vector3f(-1f, -1f, -1f).normalizeLocal()));
|
|
|
|
|
|
+ for (ArmatureDebugger debugger : armatures.values()) {
|
|
|
|
+ debugger.initialize(app.getAssetManager(), cam);
|
|
|
|
+ }
|
|
|
|
|
|
- debugNode.addLight(new DirectionalLight(new Vector3f(1f, 1f, 1f).normalizeLocal(), new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)));
|
|
|
|
|
|
+ // Initially disable the viewport until the state is enabled
|
|
vp.setEnabled(false);
|
|
vp.setEnabled(false);
|
|
|
|
+
|
|
|
|
+ registerInput();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void registerInput() {
|
|
|
|
+ inputManager.addMapping(PICK_JOINT, new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
|
|
|
|
+ inputManager.addMapping(TOGGLE_JOINTS, new KeyTrigger(KeyInput.KEY_F10));
|
|
|
|
+ inputManager.addListener(actionListener, PICK_JOINT, TOGGLE_JOINTS);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void unregisterInput() {
|
|
|
|
+ inputManager.deleteMapping(PICK_JOINT);
|
|
|
|
+ inputManager.deleteMapping(TOGGLE_JOINTS);
|
|
|
|
+ inputManager.removeListener(actionListener);
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
protected void cleanup(Application app) {
|
|
protected void cleanup(Application app) {
|
|
-
|
|
|
|
|
|
+ unregisterInput();
|
|
|
|
+ app.getRenderManager().removeMainView(vp);
|
|
|
|
+ // Clear maps to release references
|
|
|
|
+ armatures.clear();
|
|
|
|
+ selectionListeners.clear();
|
|
|
|
+ debugNode.detachAllChildren();
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
@@ -102,139 +158,311 @@ public class ArmatureDebugAppState extends BaseAppState {
|
|
clickDelay += tpf;
|
|
clickDelay += tpf;
|
|
}
|
|
}
|
|
debugNode.updateLogicalState(tpf);
|
|
debugNode.updateLogicalState(tpf);
|
|
- debugNode.updateGeometricState();
|
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public void render(RenderManager rm) {
|
|
|
|
+ debugNode.updateGeometricState();
|
|
}
|
|
}
|
|
|
|
|
|
- public ArmatureDebugger addArmatureFrom(SkinningControl skinningControl) {
|
|
|
|
- Armature armature = skinningControl.getArmature();
|
|
|
|
- Spatial forSpatial = skinningControl.getSpatial();
|
|
|
|
- return addArmatureFrom(armature, forSpatial);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Adds an ArmatureDebugger for the armature associated with a given SkinningControl.
|
|
|
|
+ *
|
|
|
|
+ * @param skControl The SkinningControl whose armature needs to be debugged.
|
|
|
|
+ * @return The newly created or existing ArmatureDebugger for the given armature.
|
|
|
|
+ */
|
|
|
|
+ public ArmatureDebugger addArmatureFrom(SkinningControl skControl) {
|
|
|
|
+ return addArmatureFrom(skControl.getArmature(), skControl.getSpatial());
|
|
}
|
|
}
|
|
|
|
|
|
- public ArmatureDebugger addArmatureFrom(Armature armature, Spatial forSpatial) {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Adds an ArmatureDebugger for a specific Armature, associating it with a Spatial.
|
|
|
|
+ * If an ArmatureDebugger for this armature already exists, it is returned.
|
|
|
|
+ * Otherwise, a new ArmatureDebugger is created, initialized, and attached to the debug node.
|
|
|
|
+ *
|
|
|
|
+ * @param armature The Armature to debug.
|
|
|
|
+ * @param sp The Spatial associated with this armature (used for determining world transform and deforming joints).
|
|
|
|
+ * @return The newly created or existing ArmatureDebugger for the given armature.
|
|
|
|
+ */
|
|
|
|
+ public ArmatureDebugger addArmatureFrom(Armature armature, Spatial sp) {
|
|
|
|
|
|
- ArmatureDebugger ad = armatures.get(armature);
|
|
|
|
- if(ad != null){
|
|
|
|
- return ad;
|
|
|
|
|
|
+ ArmatureDebugger debugger = armatures.get(armature);
|
|
|
|
+ if (debugger != null) {
|
|
|
|
+ return debugger;
|
|
}
|
|
}
|
|
|
|
|
|
- JointInfoVisitor visitor = new JointInfoVisitor(armature);
|
|
|
|
- forSpatial.depthFirstTraversal(visitor);
|
|
|
|
|
|
+ // Use a visitor to find joints that actually deform the mesh
|
|
|
|
+ JointInfoVisitor jointVisitor = new JointInfoVisitor(armature);
|
|
|
|
+ sp.depthFirstTraversal(jointVisitor);
|
|
|
|
+
|
|
|
|
+ Spatial target = sp;
|
|
|
|
|
|
- ad = new ArmatureDebugger(forSpatial.getName() + "_Armature", armature, visitor.deformingJoints);
|
|
|
|
- ad.setLocalTransform(forSpatial.getWorldTransform());
|
|
|
|
- if (forSpatial instanceof Node) {
|
|
|
|
|
|
+ if (sp instanceof Node) {
|
|
List<Geometry> geoms = new ArrayList<>();
|
|
List<Geometry> geoms = new ArrayList<>();
|
|
- findGeoms((Node) forSpatial, geoms);
|
|
|
|
|
|
+ collectGeometries((Node) sp, geoms);
|
|
if (geoms.size() == 1) {
|
|
if (geoms.size() == 1) {
|
|
- ad.setLocalTransform(geoms.get(0).getWorldTransform());
|
|
|
|
|
|
+ target = geoms.get(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- armatures.put(armature, ad);
|
|
|
|
- debugNode.attachChild(ad);
|
|
|
|
|
|
+
|
|
|
|
+ // Create a new ArmatureDebugger
|
|
|
|
+ debugger = new ArmatureDebugger(sp.getName() + "_ArmatureDebugger", armature, jointVisitor.deformingJoints);
|
|
|
|
+ debugger.addControl(new ArmatureDebuggerLink(target));
|
|
|
|
+
|
|
|
|
+ // Store and attach the new debugger
|
|
|
|
+ armatures.put(armature, debugger);
|
|
|
|
+ debugNode.attachChild(debugger);
|
|
|
|
+
|
|
|
|
+ // If the AppState is already initialized, initialize the new ArmatureDebugger immediately
|
|
if (isInitialized()) {
|
|
if (isInitialized()) {
|
|
- ad.initialize(app.getAssetManager(), app.getCamera());
|
|
|
|
|
|
+ AssetManager assetManager = getApplication().getAssetManager();
|
|
|
|
+ debugger.initialize(assetManager, cam);
|
|
}
|
|
}
|
|
- return ad;
|
|
|
|
|
|
+ return debugger;
|
|
}
|
|
}
|
|
|
|
|
|
- private void findGeoms(Node node, List<Geometry> geoms) {
|
|
|
|
- for (Spatial spatial : node.getChildren()) {
|
|
|
|
- if (spatial instanceof Geometry) {
|
|
|
|
- geoms.add((Geometry) spatial);
|
|
|
|
- } else if (spatial instanceof Node) {
|
|
|
|
- findGeoms((Node) spatial, geoms);
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Recursively finds all `Geometry` instances within a given `Node` and its children.
|
|
|
|
+ *
|
|
|
|
+ * @param node The starting `Node` to search from.
|
|
|
|
+ * @param geometries The list to which found `Geometry` instances will be added.
|
|
|
|
+ */
|
|
|
|
+ private void collectGeometries(Node node, List<Geometry> geometries) {
|
|
|
|
+ for (Spatial s : node.getChildren()) {
|
|
|
|
+ if (s instanceof Geometry) {
|
|
|
|
+ geometries.add((Geometry) s);
|
|
|
|
+ } else if (s instanceof Node) {
|
|
|
|
+ collectGeometries((Node) s, geometries);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- final private ActionListener actionListener = new ActionListener() {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * The ActionListener implementation to handle input events.
|
|
|
|
+ * Specifically, it processes mouse clicks for joint selection and
|
|
|
|
+ * the F10 key press for toggling display of all joints.
|
|
|
|
+ */
|
|
|
|
+ private final ActionListener actionListener = new ActionListener() {
|
|
|
|
+
|
|
|
|
+ private final CollisionResults results = new CollisionResults();
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
public void onAction(String name, boolean isPressed, float tpf) {
|
|
public void onAction(String name, boolean isPressed, float tpf) {
|
|
- if (name.equals("shoot") && isPressed) {
|
|
|
|
- clickDelay = 0;
|
|
|
|
- }
|
|
|
|
- if (name.equals("shoot") && !isPressed && clickDelay < CLICK_MAX_DELAY) {
|
|
|
|
- Vector2f click2d = app.getInputManager().getCursorPosition();
|
|
|
|
- CollisionResults results = new CollisionResults();
|
|
|
|
-
|
|
|
|
- Camera camera = app.getCamera();
|
|
|
|
- Vector3f click3d = camera.getWorldCoordinates(click2d, 0f, tmp);
|
|
|
|
- Vector3f dir = camera.getWorldCoordinates(click2d, 1f, tmp2)
|
|
|
|
- .subtractLocal(click3d)
|
|
|
|
- .normalizeLocal();
|
|
|
|
- Ray ray = new Ray(click3d, dir);
|
|
|
|
- debugNode.collideWith(ray, results);
|
|
|
|
-
|
|
|
|
- if (results.size() == 0) {
|
|
|
|
- for (ArmatureDebugger ad : armatures.values()) {
|
|
|
|
- ad.select(null);
|
|
|
|
- }
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // The closest result is the target that the player picked:
|
|
|
|
- Geometry target = results.getClosestCollision().getGeometry();
|
|
|
|
- for (ArmatureDebugger ad : armatures.values()) {
|
|
|
|
- Joint selectedjoint = ad.select(target);
|
|
|
|
- if (selectedjoint != null) {
|
|
|
|
- selectedBones.put(ad.getArmature(), selectedjoint);
|
|
|
|
- System.err.println("-----------------------");
|
|
|
|
- System.err.println("Selected Joint : " + selectedjoint.getName() + " in armature " + ad.getName());
|
|
|
|
- System.err.println("Root Bone : " + (selectedjoint.getParent() == null));
|
|
|
|
- System.err.println("-----------------------");
|
|
|
|
- System.err.println("Local translation: " + selectedjoint.getLocalTranslation());
|
|
|
|
- System.err.println("Local rotation: " + selectedjoint.getLocalRotation());
|
|
|
|
- System.err.println("Local scale: " + selectedjoint.getLocalScale());
|
|
|
|
- System.err.println("---");
|
|
|
|
- System.err.println("Model translation: " + selectedjoint.getModelTransform().getTranslation());
|
|
|
|
- System.err.println("Model rotation: " + selectedjoint.getModelTransform().getRotation());
|
|
|
|
- System.err.println("Model scale: " + selectedjoint.getModelTransform().getScale());
|
|
|
|
- System.err.println("---");
|
|
|
|
- System.err.println("Bind inverse Transform: ");
|
|
|
|
- System.err.println(selectedjoint.getInverseModelBindMatrix());
|
|
|
|
- return;
|
|
|
|
|
|
+ if (name.equals(PICK_JOINT)) {
|
|
|
|
+ if (isPressed) {
|
|
|
|
+ // Start counting click delay on mouse press
|
|
|
|
+ clickDelay = 0;
|
|
|
|
+
|
|
|
|
+ } else if (clickDelay < CLICK_MAX_DELAY) {
|
|
|
|
+ // Process click only if it's a quick release (not a hold)
|
|
|
|
+ Ray ray = screenPointToRay(cam, inputManager.getCursorPosition());
|
|
|
|
+ results.clear();
|
|
|
|
+ debugNode.collideWith(ray, results);
|
|
|
|
+
|
|
|
|
+ if (results.size() == 0) {
|
|
|
|
+ // If no collision, deselect all joints in all armatures
|
|
|
|
+ for (ArmatureDebugger ad : armatures.values()) {
|
|
|
|
+ ad.select(null);
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ // Get the closest geometry hit by the ray
|
|
|
|
+ Geometry target = results.getClosestCollision().getGeometry();
|
|
|
|
+ logger.log(Level.INFO, "Pick: {0}", target);
|
|
|
|
+
|
|
|
|
+ for (ArmatureDebugger ad : armatures.values()) {
|
|
|
|
+ Joint selectedjoint = ad.select(target);
|
|
|
|
+
|
|
|
|
+ if (selectedjoint != null) {
|
|
|
|
+ // If a joint was selected, notify it and print its properties
|
|
|
|
+ notifySelectionListeners(selectedjoint);
|
|
|
|
+ printJointInfo(selectedjoint, ad);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- if (name.equals("toggleJoints") && isPressed) {
|
|
|
|
- displayAllJoints = !displayAllJoints;
|
|
|
|
|
|
+ else if (name.equals(TOGGLE_JOINTS) && isPressed) {
|
|
|
|
+ displayNonDeformingJoints = !displayNonDeformingJoints;
|
|
for (ArmatureDebugger ad : armatures.values()) {
|
|
for (ArmatureDebugger ad : armatures.values()) {
|
|
- ad.displayNonDeformingJoint(displayAllJoints);
|
|
|
|
|
|
+ ad.displayNonDeformingJoint(displayNonDeformingJoints);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ private void printJointInfo(Joint selectedjoint, ArmatureDebugger ad) {
|
|
|
|
+ if (enableJointInfoLogging) {
|
|
|
|
+ System.err.println("-----------------------");
|
|
|
|
+ System.err.println("Selected Joint : " + selectedjoint.getName() + " in armature " + ad.getName());
|
|
|
|
+ System.err.println("Root Bone : " + (selectedjoint.getParent() == null));
|
|
|
|
+ System.err.println("-----------------------");
|
|
|
|
+ System.err.println("Local translation: " + selectedjoint.getLocalTranslation());
|
|
|
|
+ System.err.println("Local rotation: " + selectedjoint.getLocalRotation());
|
|
|
|
+ System.err.println("Local scale: " + selectedjoint.getLocalScale());
|
|
|
|
+ System.err.println("---");
|
|
|
|
+ System.err.println("Model translation: " + selectedjoint.getModelTransform().getTranslation());
|
|
|
|
+ System.err.println("Model rotation: " + selectedjoint.getModelTransform().getRotation());
|
|
|
|
+ System.err.println("Model scale: " + selectedjoint.getModelTransform().getScale());
|
|
|
|
+ System.err.println("---");
|
|
|
|
+ System.err.println("Bind inverse Transform: ");
|
|
|
|
+ System.err.println(selectedjoint.getInverseModelBindMatrix());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Creates a `Ray` from a 2D screen point (e.g., mouse cursor position).
|
|
|
|
+ *
|
|
|
|
+ * @param cam The camera to use for ray projection.
|
|
|
|
+ * @param screenPoint The 2D screen coordinates.
|
|
|
|
+ * @return A `Ray` originating from the near plane and extending into the scene.
|
|
|
|
+ */
|
|
|
|
+ private Ray screenPointToRay(Camera cam, Vector2f screenPoint) {
|
|
|
|
+ TempVars vars = TempVars.get();
|
|
|
|
+ Vector3f nearPoint = vars.vect1;
|
|
|
|
+ Vector3f farPoint = vars.vect2;
|
|
|
|
+
|
|
|
|
+ // Get the world coordinates for the near and far points
|
|
|
|
+ cam.getWorldCoordinates(screenPoint, 0, nearPoint);
|
|
|
|
+ cam.getWorldCoordinates(screenPoint, 1, farPoint);
|
|
|
|
+
|
|
|
|
+ // Calculate direction and normalize
|
|
|
|
+ Vector3f direction = farPoint.subtractLocal(nearPoint).normalizeLocal();
|
|
|
|
+ Ray ray = new Ray(nearPoint, direction);
|
|
|
|
+
|
|
|
|
+ vars.release();
|
|
|
|
+ return ray;
|
|
|
|
+ }
|
|
};
|
|
};
|
|
|
|
|
|
-// public Map<Skeleton, Bone> getSelectedBones() {
|
|
|
|
-// return selectedBones;
|
|
|
|
-// }
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Notifies all registered {@code Consumer<Joint>} listeners about the selected joint.
|
|
|
|
+ *
|
|
|
|
+ * @param selectedJoint The joint that was selected.
|
|
|
|
+ */
|
|
|
|
+ private void notifySelectionListeners(Joint selectedJoint) {
|
|
|
|
+ for (Consumer<Joint> listener : selectionListeners) {
|
|
|
|
+ listener.accept(selectedJoint);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Adds a listener that will be notified when a joint is selected.
|
|
|
|
+ *
|
|
|
|
+ * @param listener The {@code Consumer<Joint>} listener to add.
|
|
|
|
+ */
|
|
|
|
+ public void addSelectionListener(Consumer<Joint> listener) {
|
|
|
|
+ selectionListeners.add(listener);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Removes a previously added selection listener.
|
|
|
|
+ *
|
|
|
|
+ * @param listener The {@code Consumer<Joint>} listener to remove.
|
|
|
|
+ */
|
|
|
|
+ public void removeSelectionListener(Consumer<Joint> listener) {
|
|
|
|
+ selectionListeners.remove(listener);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Clears all registered selection listeners.
|
|
|
|
+ */
|
|
|
|
+ public void clearSelectionListeners() {
|
|
|
|
+ selectionListeners.clear();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Checks if the armature debug gizmos are set to always
|
|
|
|
+ * render on top of other scene geometry.
|
|
|
|
+ *
|
|
|
|
+ * @return true if gizmos always render on top, false otherwise.
|
|
|
|
+ */
|
|
|
|
+ public boolean isShowOnTop() {
|
|
|
|
+ return showOnTop;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Sets whether armature debug gizmos should always
|
|
|
|
+ * render on top of other scene geometry.
|
|
|
|
+ *
|
|
|
|
+ * @param showOnTop true to always show gizmos on top, false to respect depth.
|
|
|
|
+ */
|
|
|
|
+ public void setShowOnTop(boolean showOnTop) {
|
|
|
|
+ this.showOnTop = showOnTop;
|
|
|
|
+ if (vp != null) {
|
|
|
|
+ vp.setClearDepth(showOnTop);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- public Node getDebugNode() {
|
|
|
|
- return debugNode;
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Returns whether logging of detailed joint information to `System.err` is currently enabled.
|
|
|
|
+ *
|
|
|
|
+ * @return true if logging is enabled, false otherwise.
|
|
|
|
+ */
|
|
|
|
+ public boolean isJointInfoLoggingEnabled() {
|
|
|
|
+ return enableJointInfoLogging;
|
|
}
|
|
}
|
|
|
|
|
|
- public void setDebugNode(Node debugNode) {
|
|
|
|
- this.debugNode = debugNode;
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Sets whether logging of detailed joint information to `System.err` should be enabled.
|
|
|
|
+ *
|
|
|
|
+ * @param enableJointInfoLogging true to enable logging, false to disable.
|
|
|
|
+ */
|
|
|
|
+ public void setJointInfoLoggingEnabled(boolean enableJointInfoLogging) {
|
|
|
|
+ this.enableJointInfoLogging = enableJointInfoLogging;
|
|
}
|
|
}
|
|
|
|
|
|
- private class JointInfoVisitor extends SceneGraphVisitorAdapter {
|
|
|
|
|
|
+ /**
|
|
|
|
+ * A utility visitor class to traverse the scene graph and identify
|
|
|
|
+ * which joints in a given armature are actually deforming a mesh.
|
|
|
|
+ */
|
|
|
|
+ private static class JointInfoVisitor extends SceneGraphVisitorAdapter {
|
|
|
|
|
|
- List<Joint> deformingJoints = new ArrayList<>();
|
|
|
|
- Armature armature;
|
|
|
|
|
|
+ private final List<Joint> deformingJoints = new ArrayList<>();
|
|
|
|
+ private final Armature armature;
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Constructs a JointInfoVisitor for a specific armature.
|
|
|
|
+ *
|
|
|
|
+ * @param armature The armature whose deforming joints are to be identified.
|
|
|
|
+ */
|
|
public JointInfoVisitor(Armature armature) {
|
|
public JointInfoVisitor(Armature armature) {
|
|
this.armature = armature;
|
|
this.armature = armature;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Visits a Geometry node in the scene graph.
|
|
|
|
+ * For each Geometry, it checks all joints in the associated armature
|
|
|
|
+ * to see if they influence this mesh.
|
|
|
|
+ *
|
|
|
|
+ * @param geo The Geometry node being visited.
|
|
|
|
+ */
|
|
@Override
|
|
@Override
|
|
- public void visit(Geometry g) {
|
|
|
|
|
|
+ public void visit(Geometry geo) {
|
|
for (Joint joint : armature.getJointList()) {
|
|
for (Joint joint : armature.getJointList()) {
|
|
- if (g.getMesh().isAnimatedByJoint(armature.getJointIndex(joint))) {
|
|
|
|
|
|
+ int index = armature.getJointIndex(joint);
|
|
|
|
+ if (geo.getMesh().isAnimatedByJoint(index)) {
|
|
deformingJoints.add(joint);
|
|
deformingJoints.add(joint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static class ArmatureDebuggerLink extends AbstractControl {
|
|
|
|
+
|
|
|
|
+ private final Spatial target;
|
|
|
|
+
|
|
|
|
+ public ArmatureDebuggerLink(Spatial target) {
|
|
|
|
+ this.target = target;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ protected void controlUpdate(float tpf) {
|
|
|
|
+ spatial.setLocalTransform(target.getWorldTransform());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ protected void controlRender(RenderManager rm, ViewPort vp) {
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|