diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java b/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java index 2a1f41df6..b633ab5b8 100644 --- a/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java +++ b/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java @@ -31,49 +31,95 @@ */ package com.jme3.scene.debug; +import java.util.Map; + import com.jme3.animation.Skeleton; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Geometry; import com.jme3.scene.Node; +/** + * The class that creates a mesh to display how bones behave. + * If it is supplied with the bones' lengths it will show exactly how the bones look like on the scene. + * If not then only connections between each bone heads will be shown. + */ public class SkeletonDebugger extends Node { + /** The lines of the bones or the wires between their heads. */ + private SkeletonWire wires; + /** The heads and tails points of the bones or only heads if no length data is available. */ + private SkeletonPoints points; + /** The dotted lines between a bone's tail and the had of its children. Not available if the length data was not provided. */ + private SkeletonInterBoneWire interBoneWires; - private SkeletonWire wires; - private SkeletonPoints points; - private Skeleton skeleton; + public SkeletonDebugger() { + } - public SkeletonDebugger(String name, Skeleton skeleton){ - super(name); + /** + * 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. + * @param name + * the name of the debugger's node + * @param skeleton + * the skeleton that will be shown + */ + public SkeletonDebugger(String name, Skeleton skeleton) { + this(name, skeleton, null); + } - this.skeleton = skeleton; - wires = new SkeletonWire(skeleton); - points = new SkeletonPoints(skeleton); + /** + * Creates a debugger with bone lengths data. If the data is supplied then the wires will show each full bone (from head to tail), + * the points will display both heads and tails of the bones and dotted lines between bones will be seen. + * @param name + * the name of the debugger's node + * @param skeleton + * the skeleton that will be shown + * @param boneLengths + * a map between the bone's index and the bone's length + */ + public SkeletonDebugger(String name, Skeleton skeleton, Map boneLengths) { + super(name); - attachChild(new Geometry(name+"_wires", wires)); - attachChild(new Geometry(name+"_points", points)); + wires = new SkeletonWire(skeleton, boneLengths); + points = new SkeletonPoints(skeleton, boneLengths); - setQueueBucket(Bucket.Transparent); - } + this.attachChild(new Geometry(name + "_wires", wires)); + this.attachChild(new Geometry(name + "_points", points)); + if (boneLengths != null) { + interBoneWires = new SkeletonInterBoneWire(skeleton, boneLengths); + this.attachChild(new Geometry(name + "_interwires", interBoneWires)); + } - public SkeletonDebugger(){ + this.setQueueBucket(Bucket.Transparent); } @Override - public void updateLogicalState(float tpf){ + public void updateLogicalState(float tpf) { super.updateLogicalState(tpf); - -// skeleton.resetAndUpdate(); wires.updateGeometry(); points.updateGeometry(); + if(interBoneWires != null) { + interBoneWires.updateGeometry(); + } } + /** + * @return the skeleton points + */ public SkeletonPoints getPoints() { return points; } + /** + * @return the skeleton wires + */ public SkeletonWire getWires() { return wires; } - - -} + + /** + * @return the dotted line between bones (can be null) + */ + public SkeletonInterBoneWire getInterBoneWires() { + return interBoneWires; + } +} \ No newline at end of file diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonInterBoneWire.java b/engine/src/core/com/jme3/scene/debug/SkeletonInterBoneWire.java new file mode 100644 index 000000000..ccf97aa2e --- /dev/null +++ b/engine/src/core/com/jme3/scene/debug/SkeletonInterBoneWire.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * 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; + +import java.nio.FloatBuffer; +import java.util.Map; + +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.util.BufferUtils; + +/** + * A class that displays a dotted line between a bone tail and its childrens' heads. + * + * @author Marcin Roguski (Kaelthas) + */ +public class SkeletonInterBoneWire extends Mesh { + private static final int POINT_AMOUNT = 10; + /** The amount of connections between bones. */ + private int connectionsAmount; + /** The skeleton that will be showed. */ + private Skeleton skeleton; + /** The map between the bone index and its length. */ + private Map boneLengths; + + /** + * Creates buffers for points. Each line has POINT_AMOUNT of points. + * @param skeleton + * the skeleton that will be showed + * @param boneLengths + * the lengths of the bones + */ + public SkeletonInterBoneWire(Skeleton skeleton, Map boneLengths) { + this.skeleton = skeleton; + + for (Bone bone : skeleton.getRoots()) { + this.countConnections(bone); + } + + this.setMode(Mode.Points); + this.setPointSize(1); + this.boneLengths = boneLengths; + + VertexBuffer pb = new VertexBuffer(Type.Position); + FloatBuffer fpb = BufferUtils.createFloatBuffer(POINT_AMOUNT * connectionsAmount * 3); + pb.setupData(Usage.Stream, 3, Format.Float, fpb); + this.setBuffer(pb); + + this.updateCounts(); + } + + /** + * The method updates the geometry according to the poitions of the bones. + */ + public void updateGeometry() { + VertexBuffer vb = this.getBuffer(Type.Position); + FloatBuffer posBuf = this.getFloatBuffer(Type.Position); + posBuf.clear(); + for (int i = 0; i < skeleton.getBoneCount(); ++i) { + Bone bone = skeleton.getBone(i); + Vector3f parentTail = bone.getModelSpacePosition().add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneLengths.get(i)))); + + for (Bone child : bone.getChildren()) { + Vector3f childHead = child.getModelSpacePosition(); + Vector3f v = childHead.subtract(parentTail); + float pointDelta = v.length() / POINT_AMOUNT; + v.normalizeLocal().multLocal(pointDelta); + Vector3f pointPosition = parentTail.clone(); + for (int j = 0; j < POINT_AMOUNT; ++j) { + posBuf.put(pointPosition.getX()).put(pointPosition.getY()).put(pointPosition.getZ()); + pointPosition.addLocal(v); + } + } + } + posBuf.flip(); + vb.updateData(posBuf); + + this.updateBound(); + } + + /** + * Th method couns the connections between bones. + * @param bone + * the bone where counting starts + */ + private void countConnections(Bone bone) { + for (Bone child : bone.getChildren()) { + ++connectionsAmount; + this.countConnections(child); + } + } +} diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java b/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java index 7549d8836..83b08402b 100644 --- a/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java +++ b/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java @@ -40,40 +40,75 @@ import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.util.BufferUtils; + import java.nio.FloatBuffer; +import java.util.Map; +/** + * The class that displays either heads of the bones if no length data is supplied or both heads and tails otherwise. + */ public class SkeletonPoints extends Mesh { + /** The skeleton to be displayed. */ + private Skeleton skeleton; + /** The map between the bone index and its length. */ + private Map boneLengths; - private Skeleton skeleton; + /** + * Creates a points with no length data. The points will only show the bone's heads. + * @param skeleton + * the skeleton that will be shown + */ + public SkeletonPoints(Skeleton skeleton) { + this(skeleton, null); + } - public SkeletonPoints(Skeleton skeleton){ + /** + * Creates a points with bone lengths data. If the data is supplied then the points will show both head and tail of each bone. + * @param skeleton + * the skeleton that will be shown + * @param boneLengths + * a map between the bone's index and the bone's length + */ + public SkeletonPoints(Skeleton skeleton, Map boneLengths) { this.skeleton = skeleton; + this.setMode(Mode.Points); + int pointsCount = skeleton.getBoneCount(); - setMode(Mode.Points); + if (boneLengths != null) { + this.boneLengths = boneLengths; + pointsCount *= 2; + } VertexBuffer pb = new VertexBuffer(Type.Position); - FloatBuffer fpb = BufferUtils.createFloatBuffer(skeleton.getBoneCount() * 3); + FloatBuffer fpb = BufferUtils.createFloatBuffer(pointsCount * 3); pb.setupData(Usage.Stream, 3, Format.Float, fpb); - setBuffer(pb); + this.setBuffer(pb); - setPointSize(7); + this.setPointSize(7); + this.updateCounts(); - updateCounts(); } - public void updateGeometry(){ - VertexBuffer vb = getBuffer(Type.Position); - FloatBuffer posBuf = getFloatBuffer(Type.Position); + /** + * The method updates the geometry according to the poitions of the bones. + */ + public void updateGeometry() { + VertexBuffer vb = this.getBuffer(Type.Position); + FloatBuffer posBuf = this.getFloatBuffer(Type.Position); posBuf.clear(); - for (int i = 0; i < skeleton.getBoneCount(); i++){ + for (int i = 0; i < skeleton.getBoneCount(); ++i) { Bone bone = skeleton.getBone(i); - Vector3f bonePos = bone.getModelSpacePosition(); + Vector3f head = bone.getModelSpacePosition(); - posBuf.put(bonePos.getX()).put(bonePos.getY()).put(bonePos.getZ()); + posBuf.put(head.getX()).put(head.getY()).put(head.getZ()); + if (boneLengths != null) { + Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneLengths.get(i)))); + posBuf.put(tail.getX()).put(tail.getY()).put(tail.getZ()); + } } posBuf.flip(); vb.updateData(posBuf); - updateBound(); + this.updateBound(); } } diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonWire.java b/engine/src/core/com/jme3/scene/debug/SkeletonWire.java index e71740656..b907a4c14 100644 --- a/engine/src/core/com/jme3/scene/debug/SkeletonWire.java +++ b/engine/src/core/com/jme3/scene/debug/SkeletonWire.java @@ -31,6 +31,10 @@ */ package com.jme3.scene.debug; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; +import java.util.Map; + import com.jme3.animation.Bone; import com.jme3.animation.Skeleton; import com.jme3.math.Vector3f; @@ -40,69 +44,123 @@ import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.util.BufferUtils; -import java.nio.FloatBuffer; -import java.nio.ShortBuffer; +/** + * The class that displays either wires between the bones' heads if no length data is supplied and + * full bones' shapes otherwise. + */ public class SkeletonWire extends Mesh { + /** The number of bones' connections. Used in non-length mode. */ + private int numConnections; + /** The skeleton to be displayed. */ + private Skeleton skeleton; + /** The map between the bone index and its length. */ + private Map boneLengths; - private int numConnections = 0; - private Skeleton skeleton; - - private void countConnections(Bone bone){ - for (Bone child : bone.getChildren()){ - numConnections ++; - countConnections(child); - } + /** + * Creates a wire with no length data. The wires will be a connection between the bones' heads only. + * @param skeleton + * the skeleton that will be shown + */ + public SkeletonWire(Skeleton skeleton) { + this(skeleton, null); } - private void writeConnections(ShortBuffer indexBuf, Bone bone){ - for (Bone child : bone.getChildren()){ - // write myself - indexBuf.put( (short) skeleton.getBoneIndex(bone) ); - // write the child - indexBuf.put( (short) skeleton.getBoneIndex(child) ); + /** + * Creates a wire with bone lengths data. If the data is supplied then the wires will show each full bone (from head to tail). + * @param skeleton + * the skeleton that will be shown + * @param boneLengths + * a map between the bone's index and the bone's length + */ + public SkeletonWire(Skeleton skeleton, Map boneLengths) { + this.skeleton = skeleton; - writeConnections(indexBuf, child); + for (Bone bone : skeleton.getRoots()) { + this.countConnections(bone); } - } - public SkeletonWire(Skeleton skeleton){ - this.skeleton = skeleton; - for (Bone bone : skeleton.getRoots()) - countConnections(bone); - - setMode(Mode.Lines); + this.setMode(Mode.Lines); + int lineVerticesCount = skeleton.getBoneCount(); + if (boneLengths != null) { + this.boneLengths = boneLengths; + lineVerticesCount *= 2; + } VertexBuffer pb = new VertexBuffer(Type.Position); - FloatBuffer fpb = BufferUtils.createFloatBuffer(skeleton.getBoneCount() * 3); + FloatBuffer fpb = BufferUtils.createFloatBuffer(lineVerticesCount * 3); pb.setupData(Usage.Stream, 3, Format.Float, fpb); - setBuffer(pb); + this.setBuffer(pb); VertexBuffer ib = new VertexBuffer(Type.Index); - ShortBuffer sib = BufferUtils.createShortBuffer(numConnections * 2); + ShortBuffer sib = BufferUtils.createShortBuffer(boneLengths != null ? lineVerticesCount : numConnections * 2); ib.setupData(Usage.Static, 2, Format.UnsignedShort, sib); - setBuffer(ib); + this.setBuffer(ib); - for (Bone bone : skeleton.getRoots()) - writeConnections(sib, bone); + if (boneLengths != null) { + for (int i = 0; i < lineVerticesCount; ++i) { + sib.put((short) i); + } + } else { + for (Bone bone : skeleton.getRoots()) { + this.writeConnections(sib, bone); + } + } sib.flip(); - updateCounts(); + this.updateCounts(); } - public void updateGeometry(){ - VertexBuffer vb = getBuffer(Type.Position); - FloatBuffer posBuf = getFloatBuffer(Type.Position); + /** + * The method updates the geometry according to the poitions of the bones. + */ + public void updateGeometry() { + VertexBuffer vb = this.getBuffer(Type.Position); + FloatBuffer posBuf = this.getFloatBuffer(Type.Position); posBuf.clear(); - for (int i = 0; i < skeleton.getBoneCount(); i++){ + for (int i = 0; i < skeleton.getBoneCount(); ++i) { Bone bone = skeleton.getBone(i); - Vector3f bonePos = bone.getModelSpacePosition(); + Vector3f head = bone.getModelSpacePosition(); - posBuf.put(bonePos.getX()).put(bonePos.getY()).put(bonePos.getZ()); + posBuf.put(head.getX()).put(head.getY()).put(head.getZ()); + if (boneLengths != null) { + Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneLengths.get(i)))); + posBuf.put(tail.getX()).put(tail.getY()).put(tail.getZ()); + } } posBuf.flip(); vb.updateData(posBuf); - updateBound(); + this.updateBound(); + } + + /** + * Th method couns the connections between bones. + * @param bone + * the bone where counting starts + */ + private void countConnections(Bone bone) { + for (Bone child : bone.getChildren()) { + numConnections++; + this.countConnections(child); + } + } + + /** + * The method writes the indexes for the connection vertices. Used in non-length mode. + * @param indexBuf + * the index buffer + * @param bone + * the bone + */ + private void writeConnections(ShortBuffer indexBuf, Bone bone) { + for (Bone child : bone.getChildren()) { + // write myself + indexBuf.put((short) skeleton.getBoneIndex(bone)); + // write the child + indexBuf.put((short) skeleton.getBoneIndex(child)); + + this.writeConnections(indexBuf, child); + } } }