diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java index d7973eabc1..0978dff5dc 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java @@ -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 @@ -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; @@ -55,104 +55,166 @@ 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 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"); + + // Create child nodes specifically for non-deforming joints' visualization + Node ndJoints = new Node("NonDeformingJoints"); + Node ndOutlines = new Node("NonDeformingOutlines"); + Node ndWires = new Node("NonDeformingWires"); 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")); + + Node outlineDashed = new Node("DashedOutlines"); + Node wiresDashed = new Node("DashedWires"); + wiresDashed.attachChild(new Node("DashedNonDeformingWires")); + outlineDashed.attachChild(new Node("DashedNonDeformingOutlines")); outlines.attachChild(outlineDashed); 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 for joint points (billboarded dots). Material matJoints = new Material(assetManager, "Common/MatDefs/Misc/Billboard.j3md"); - Texture t = assetManager.loadTexture("Common/Textures/dot.png"); - matJoints.setTexture("Texture", t); + Texture tex = assetManager.loadTexture("Common/Textures/dot.png"); + matJoints.setTexture("Texture", tex); matJoints.getAdditionalRenderState().setDepthTest(false); matJoints.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); joints.setQueueBucket(RenderQueue.Bucket.Translucent); joints.setMaterial(matJoints); + // Material for bone wires/lines (unshaded, vertex colored). Material matWires = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); matWires.setBoolean("VertexColor", true); - matWires.getAdditionalRenderState().setLineWidth(1f); + matWires.getAdditionalRenderState().setDepthTest(false); wires.setMaterial(matWires); + // Material for bone outlines (unshaded, vertex colored). Material matOutline = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); matOutline.setBoolean("VertexColor", true); - matOutline.getAdditionalRenderState().setLineWidth(1f); + matOutline.getAdditionalRenderState().setDepthTest(false); outlines.setMaterial(matOutline); + // Material for dashed outlines. This assumes a "DashedLine.j3md" shader. Material matOutline2 = new Material(assetManager, "Common/MatDefs/Misc/DashedLine.j3md"); - matOutline2.getAdditionalRenderState().setLineWidth(1); + matOutline2.getAdditionalRenderState().setDepthTest(false); outlines.getChild(1).setMaterial(matOutline2); + // Material for dashed wires. This assumes a "DashedLine.j3md" shader. Material matWires2 = new Material(assetManager, "Common/MatDefs/Misc/DashedLine.j3md"); - matWires2.getAdditionalRenderState().setLineWidth(1); + matWires2.getAdditionalRenderState().setDepthTest(false); wires.getChild(1).setMaterial(matWires2); - } + /** + * Returns the {@link Armature} instance associated with this debugger. + * + * @return The {@link Armature} being debugged. + */ public Armature getArmature() { return armature; } @@ -168,21 +230,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; } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java index c46466f181..72089460f2 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java @@ -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 @@ -31,43 +29,73 @@ * 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.collision.*; -import com.jme3.math.*; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.math.ColorRGBA; +import com.jme3.math.MathUtils; +import com.jme3.math.Ray; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.*; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.VertexBuffer; import com.jme3.scene.shape.Line; import java.nio.FloatBuffer; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** - * The class that displays either wires between the bones' heads if no length - * data is supplied and full bones' shapes otherwise. + * Renders an {@link Armature} for debugging purposes. It can display either + * wires connecting the heads of bones (if no length data is available) or + * full bone shapes (from head to tail) when length data is supplied. */ public class ArmatureNode extends Node { + /** + * The size of the picking box in pixels for joint selection. + */ public static final float PIXEL_BOX = 10f; /** * The armature to be displayed. */ private final Armature armature; /** - * The map between the bone index and its length. + * Maps a {@link Joint} to its corresponding {@link Geometry} array. + * The array typically contains [jointGeometry, boneWireGeometry, boneOutlineGeometry]. */ private final Map jointToGeoms = new HashMap<>(); + /** + * Maps a {@link Geometry} to its associated {@link Joint}. Used for picking. + */ private final Map geomToJoint = new HashMap<>(); + /** + * The currently selected joint. + */ private Joint selectedJoint = null; - private final Vector3f tmp = new Vector3f(); - private final Vector2f tmpv2 = new Vector2f(); + + // Temporary vectors for calculations to avoid repeated allocations + private final Vector3f tempVec3f = new Vector3f(); + private final Vector2f tempVec2f = new Vector2f(); + + // Color constants for rendering private static final ColorRGBA selectedColor = ColorRGBA.Orange; - private static final ColorRGBA selectedColorJ = ColorRGBA.Yellow; + private static final ColorRGBA selectedColorJoint = ColorRGBA.Yellow; private static final ColorRGBA outlineColor = ColorRGBA.LightGray; private static final ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f); + /** + * The camera used for 2D picking calculations. + */ private Camera camera; @@ -88,27 +116,36 @@ public ArmatureNode(Armature armature, Node joints, Node wires, Node outlines, L setColor(origin, ColorRGBA.Green); attach(joints, true, origin); + // Recursively create geometries for all joints and bones in the armature for (Joint joint : armature.getRoots()) { createSkeletonGeoms(joint, joints, wires, outlines, deformingJoints); } this.updateModelBound(); - } + /** + * Recursively creates the geometries for a given joint and its children. + * + * @param joint The current joint for which to create geometries. + * @param joints The node for joint geometries. + * @param wires The node for bone wire geometries. + * @param outlines The node for bone outline geometries. + * @param deformingJoints A list of deforming joints. + */ protected final void createSkeletonGeoms(Joint joint, Node joints, Node wires, Node outlines, List deformingJoints) { Vector3f start = joint.getModelTransform().getTranslation().clone(); Vector3f[] ends = null; if (!joint.getChildren().isEmpty()) { ends = new Vector3f[joint.getChildren().size()]; - } - - for (int i = 0; i < joint.getChildren().size(); i++) { - ends[i] = joint.getChildren().get(i).getModelTransform().getTranslation().clone(); + for (int i = 0; i < ends.length; i++) { + ends[i] = joint.getChildren().get(i).getModelTransform().getTranslation().clone(); + } } boolean deforms = deformingJoints.contains(joint); + // Create geometry for the joint head Geometry jGeom = new Geometry(joint.getName() + "Joint", new JointShape()); jGeom.setLocalTranslation(start); attach(joints, deforms, jGeom); @@ -134,8 +171,8 @@ protected final void createSkeletonGeoms(Joint joint, Node joints, Node wires, N setColor(bGeom, outlinesAttach == null ? outlineColor : baseColor); geomToJoint.put(bGeom, joint); bGeom.setUserData("start", getWorldTransform().transformVector(start, start)); - for (int i = 0; i < ends.length; i++) { - getWorldTransform().transformVector(ends[i], ends[i]); + for (Vector3f end : ends) { + getWorldTransform().transformVector(end, end); } bGeom.setUserData("end", ends); bGeom.setQueueBucket(RenderQueue.Bucket.Transparent); @@ -148,11 +185,17 @@ protected final void createSkeletonGeoms(Joint joint, Node joints, Node wires, N } jointToGeoms.put(joint, new Geometry[]{jGeom, bGeom, bGeomO}); + // Recursively call for children for (Joint child : joint.getChildren()) { createSkeletonGeoms(child, joints, wires, outlines, deformingJoints); } } + /** + * Sets the camera to be used for 2D picking calculations. + * + * @param camera The camera to set. + */ public void setCamera(Camera camera) { this.camera = camera; } @@ -165,53 +208,83 @@ private void attach(Node parent, boolean deforms, Geometry geom) { } } - protected Joint select(Geometry g) { - if (g == null) { + /** + * Selects a joint based on its associated geometry. + * If the selected geometry is already the current selection, no change occurs. + * Resets the selection if {@code geometry} is null. + * + * @param geo The geometry representing the joint or bone to select. + * @return The newly selected {@link Joint}, or null if no joint was selected or the selection was reset. + */ + protected Joint select(Geometry geo) { + if (geo == null) { resetSelection(); return null; } - Joint j = geomToJoint.get(g); - if (j != null) { - if (selectedJoint == j) { + Joint jointToSelect = geomToJoint.get(geo); + if (jointToSelect != null) { + if (selectedJoint == jointToSelect) { return null; } resetSelection(); - selectedJoint = j; + selectedJoint = jointToSelect; Geometry[] geomArray = jointToGeoms.get(selectedJoint); - setColor(geomArray[0], selectedColorJ); + // Color the joint head + setColor(geomArray[0], selectedColorJoint); + // Color the bone wire if (geomArray[1] != null) { setColor(geomArray[1], selectedColor); } + // Restore outline color if present (as it's often the base color when bone is selected) if (geomArray[2] != null) { setColor(geomArray[2], baseColor); } - return j; + return jointToSelect; } return null; } + /** + * Resets the color of the currently selected joint and bone geometries to their default colors + * and clears the {@code selectedJoint}. + */ private void resetSelection() { if (selectedJoint == null) { return; } Geometry[] geoms = jointToGeoms.get(selectedJoint); + // Reset joint head color setColor(geoms[0], ColorRGBA.White); + + // Reset bone wire color (depends on whether it has an outline) if (geoms[1] != null) { setColor(geoms[1], geoms[2] == null ? outlineColor : baseColor); } + + // Reset bone outline color if (geoms[2] != null) { setColor(geoms[2], outlineColor); } selectedJoint = null; } + /** + * Returns the currently selected joint. + * + * @return The {@link Joint} that is currently selected, or null if no joint is selected. + */ protected Joint getSelectedJoint() { return selectedJoint; } - + /** + * Updates the geometries associated with a given joint and its children to reflect their + * current model transforms. This method is called recursively. + * + * @param joint The joint to update. + */ protected final void updateSkeletonGeoms(Joint joint) { Geometry[] geoms = jointToGeoms.get(joint); if (geoms != null) { @@ -232,70 +305,142 @@ protected final void updateSkeletonGeoms(Joint joint) { updateBoneMesh(bGeomO, start, ends); } bGeom.setUserData("start", getWorldTransform().transformVector(start, start)); - for (int i = 0; i < ends.length; i++) { - getWorldTransform().transformVector(ends[i], ends[i]); + for (Vector3f end : ends) { + getWorldTransform().transformVector(end, end); } bGeom.setUserData("end", ends); - } } } + // Recursively update children for (Joint child : joint.getChildren()) { updateSkeletonGeoms(child); } } + /** + * Sets the color of the head geometry for a specific joint. + * + * @param joint The joint whose head color is to be set. + * @param color The new color for the joint head. + */ + public void setHeadColor(Joint joint, ColorRGBA color) { + Geometry[] geomArray = jointToGeoms.get(joint); + setColor(geomArray[0], color); + } + + /** + * Sets the color of all joint head geometries. + * + * @param color The new color for all joint heads. + */ + public void setHeadColor(ColorRGBA color) { + for (Geometry[] geomArray : jointToGeoms.values()) { + setColor(geomArray[0], color); + } + } + + /** + * Sets the color of all bone line geometries. + * + * @param color The new color for all bone lines. + */ + public void setLineColor(ColorRGBA color) { + for (Geometry[] geomArray : jointToGeoms.values()) { + if (geomArray[1] != null) { + setColor(geomArray[1], color); + } + } + } + + /** + * Performs a 2D pick operation to find joints or bones near the given cursor position. + * This method primarily checks for joint heads within a {@link #PIXEL_BOX} box + * around the cursor, and then checks for bone wires. + * + * @param cursor The 2D screen coordinates of the pick ray origin. + * @param results The {@link CollisionResults} to store the pick results. + * @return The number of collisions found. + */ public int pick(Vector2f cursor, CollisionResults results) { + if (camera == null) { + return 0; + } - for (Geometry g : geomToJoint.keySet()) { - if (g.getMesh() instanceof JointShape) { - camera.getScreenCoordinates(g.getWorldTranslation(), tmp); - if (cursor.x <= tmp.x + PIXEL_BOX && cursor.x >= tmp.x - PIXEL_BOX - && cursor.y <= tmp.y + PIXEL_BOX && cursor.y >= tmp.y - PIXEL_BOX) { + int collisions = 0; + for (Geometry geo : geomToJoint.keySet()) { + if (geo.getMesh() instanceof JointShape) { + camera.getScreenCoordinates(geo.getWorldTranslation(), tempVec3f); + if (cursor.x <= tempVec3f.x + PIXEL_BOX && cursor.x >= tempVec3f.x - PIXEL_BOX + && cursor.y <= tempVec3f.y + PIXEL_BOX && cursor.y >= tempVec3f.y - PIXEL_BOX) { CollisionResult res = new CollisionResult(); - res.setGeometry(g); + res.setGeometry(geo); results.addCollision(res); + collisions++; } } } - return 0; + return collisions; } + /** + * Collides this {@code ArmatureNode} with a {@link Collidable} object, typically a {@link Ray}. + * It prioritizes 2D picking for joint heads and then performs a distance-based check for bone wires. + * + * @param other The {@link Collidable} object to collide with. + * @param results The {@link CollisionResults} to store the collision information. + * @return The number of collisions found. + */ @Override public int collideWith(Collidable other, CollisionResults results) { - if (!(other instanceof Ray)) { + if (!(other instanceof Ray) || camera == null) { return 0; } - // first try a 2D pick; - camera.getScreenCoordinates(((Ray)other).getOrigin(),tmp); - tmpv2.x = tmp.x; - tmpv2.y = tmp.y; - int nbHit = pick(tmpv2, results); - if (nbHit > 0) { - return nbHit; + // First, try a 2D pick for joint heads + camera.getScreenCoordinates(((Ray) other).getOrigin(), tempVec3f); + tempVec2f.x = tempVec3f.x; + tempVec2f.y = tempVec3f.y; + int hitCount = pick(tempVec2f, results); + + // If 2D pick found hits, return them. Otherwise, proceed with bone wire collision. + if (hitCount > 0) { + return hitCount; } + // Check for bone wire collisions for (Geometry g : geomToJoint.keySet()) { if (g.getMesh() instanceof JointShape) { + // Skip joint heads, already handled by 2D pick continue; } + Vector3f start = g.getUserData("start"); Vector3f[] ends = g.getUserData("end"); - for (int i = 0; i < ends.length; i++) { - float len = MathUtils.raySegmentShortestDistance((Ray) other, start, ends[i], camera); - if (len > 0 && len < PIXEL_BOX) { + + for (Vector3f end : ends) { + // Calculate shortest distance from ray to bone segment + float dist = MathUtils.raySegmentShortestDistance((Ray) other, start, end, camera); + if (dist > 0 && dist < PIXEL_BOX) { CollisionResult res = new CollisionResult(); res.setGeometry(g); results.addCollision(res); - nbHit++; + hitCount++; } } } - return nbHit; + return hitCount; } + /** + * Updates the mesh of a bone geometry (either {@link ArmatureInterJointsWire} or {@link Line}) + * with new start and end points. + * + * @param geom The bone geometry whose mesh needs updating. + * @param start The new starting point of the bone. + * @param ends The new ending points of the bone (can be multiple for {@link ArmatureInterJointsWire}). + */ private void updateBoneMesh(Geometry geom, Vector3f start, Vector3f[] ends) { if (geom.getMesh() instanceof ArmatureInterJointsWire) { ((ArmatureInterJointsWire) geom.getMesh()).updatePoints(start, ends); @@ -305,18 +450,31 @@ private void updateBoneMesh(Geometry geom, Vector3f start, Vector3f[] ends) { geom.updateModelBound(); } - private void setColor(Geometry g, ColorRGBA color) { - float[] colors = new float[g.getMesh().getVertexCount() * 4]; - for (int i = 0; i < g.getMesh().getVertexCount() * 4; i += 4) { + /** + * Sets the color of a given geometry's vertex buffer. + * This method creates a new color buffer or updates an existing one with the specified color. + * + * @param geo The geometry whose color is to be set. + * @param color The {@link ColorRGBA} to apply. + */ + private void setColor(Geometry geo, ColorRGBA color) { + Mesh mesh = geo.getMesh(); + int vertexCount = mesh.getVertexCount(); + + float[] colors = new float[vertexCount * 4]; + for (int i = 0; i < colors.length; i += 4) { colors[i] = color.r; colors[i + 1] = color.g; colors[i + 2] = color.b; colors[i + 3] = color.a; } - VertexBuffer colorBuff = g.getMesh().getBuffer(VertexBuffer.Type.Color); + + VertexBuffer colorBuff = geo.getMesh().getBuffer(VertexBuffer.Type.Color); if (colorBuff == null) { - g.getMesh().setBuffer(VertexBuffer.Type.Color, 4, colors); + // If no color buffer exists, create a new one + geo.getMesh().setBuffer(VertexBuffer.Type.Color, 4, colors); } else { + // If a color buffer exists, update its data FloatBuffer cBuff = (FloatBuffer) colorBuff.getData(); cBuff.rewind(); cBuff.put(colors); @@ -327,7 +485,7 @@ private void setColor(Geometry g, ColorRGBA color) { /** * The method updates the geometry according to the positions of the bones. */ - public void updateGeometry() { + void updateGeometry() { armature.update(); for (Joint joint : armature.getRoots()) { updateSkeletonGeoms(joint);