Explorar el Código

Added a better skeleton debugger

Rémy Bouquet hace 8 años
padre
commit
b129d2954f

+ 417 - 0
jme3-core/src/main/java/com/jme3/scene/debug/custom/BoneShape.java

@@ -0,0 +1,417 @@
+/*
+ * 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.
+ */
+// $Id: Cylinder.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.debug.custom;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.BufferUtils;
+
+import static com.jme3.util.BufferUtils.*;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ * A simple cylinder, defined by it's height and radius.
+ * (Ported to jME3)
+ *
+ * @author Mark Powell
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class BoneShape extends Mesh {
+
+    private int axisSamples;
+
+    private int radialSamples;
+
+    private float radius;
+    private float radius2;
+
+    private float height;
+    private boolean closed;
+    private boolean inverted;
+
+    /**
+     * Default constructor for serialization only. Do not use.
+     */
+    public BoneShape() {
+    }
+
+    /**
+     * Creates a new Cylinder. By default its center is the origin. Usually, a
+     * higher sample number creates a better looking cylinder, but at the cost
+     * of more vertex information.
+     *
+     * @param axisSamples   Number of triangle samples along the axis.
+     * @param radialSamples Number of triangle samples along the radial.
+     * @param radius        The radius of the cylinder.
+     * @param height        The cylinder's height.
+     */
+    public BoneShape(int axisSamples, int radialSamples,
+                     float radius, float height) {
+        this(axisSamples, radialSamples, radius, height, false);
+    }
+
+    /**
+     * Creates a new Cylinder. By default its center is the origin. Usually, a
+     * higher sample number creates a better looking cylinder, but at the cost
+     * of more vertex information. <br>
+     * If the cylinder is closed the texture is split into axisSamples parts:
+     * top most and bottom most part is used for top and bottom of the cylinder,
+     * rest of the texture for the cylinder wall. The middle of the top is
+     * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need
+     * a suited distorted texture.
+     *
+     * @param axisSamples   Number of triangle samples along the axis.
+     * @param radialSamples Number of triangle samples along the radial.
+     * @param radius        The radius of the cylinder.
+     * @param height        The cylinder's height.
+     * @param closed        true to create a cylinder with top and bottom surface
+     */
+    public BoneShape(int axisSamples, int radialSamples,
+                     float radius, float height, boolean closed) {
+        this(axisSamples, radialSamples, radius, height, closed, false);
+    }
+
+    /**
+     * Creates a new Cylinder. By default its center is the origin. Usually, a
+     * higher sample number creates a better looking cylinder, but at the cost
+     * of more vertex information. <br>
+     * If the cylinder is closed the texture is split into axisSamples parts:
+     * top most and bottom most part is used for top and bottom of the cylinder,
+     * rest of the texture for the cylinder wall. The middle of the top is
+     * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need
+     * a suited distorted texture.
+     *
+     * @param axisSamples   Number of triangle samples along the axis.
+     * @param radialSamples Number of triangle samples along the radial.
+     * @param radius        The radius of the cylinder.
+     * @param height        The cylinder's height.
+     * @param closed        true to create a cylinder with top and bottom surface
+     * @param inverted      true to create a cylinder that is meant to be viewed from the
+     *                      interior.
+     */
+    public BoneShape(int axisSamples, int radialSamples,
+                     float radius, float height, boolean closed, boolean inverted) {
+        this(axisSamples, radialSamples, radius, radius, height, closed, inverted);
+    }
+
+    public BoneShape(int axisSamples, int radialSamples,
+                     float radius, float radius2, float height, boolean closed, boolean inverted) {
+        super();
+        updateGeometry(axisSamples, radialSamples, radius, radius2, height, closed, inverted);
+    }
+
+    /**
+     * @return the number of samples along the cylinder axis
+     */
+    public int getAxisSamples() {
+        return axisSamples;
+    }
+
+    /**
+     * @return Returns the height.
+     */
+    public float getHeight() {
+        return height;
+    }
+
+    /**
+     * @return number of samples around cylinder
+     */
+    public int getRadialSamples() {
+        return radialSamples;
+    }
+
+    /**
+     * @return Returns the radius.
+     */
+    public float getRadius() {
+        return radius;
+    }
+
+    public float getRadius2() {
+        return radius2;
+    }
+
+    /**
+     * @return true if end caps are used.
+     */
+    public boolean isClosed() {
+        return closed;
+    }
+
+    /**
+     * @return true if normals and uvs are created for interior use
+     */
+    public boolean isInverted() {
+        return inverted;
+    }
+
+    /**
+     * Rebuilds the cylinder based on a new set of parameters.
+     *
+     * @param axisSamples   the number of samples along the axis.
+     * @param radialSamples the number of samples around the radial.
+     * @param radius        the radius of the bottom of the cylinder.
+     * @param radius2       the radius of the top of the cylinder.
+     * @param height        the cylinder's height.
+     * @param closed        should the cylinder have top and bottom surfaces.
+     * @param inverted      is the cylinder is meant to be viewed from the inside.
+     */
+    public void updateGeometry(int axisSamples, int radialSamples,
+                               float radius, float radius2, float height, boolean closed, boolean inverted) {
+        this.axisSamples = axisSamples + (closed ? 2 : 0);
+        this.radialSamples = radialSamples;
+        this.radius = radius;
+        this.radius2 = radius2;
+        this.height = height;
+        this.closed = closed;
+        this.inverted = inverted;
+
+//        VertexBuffer pvb = getBuffer(Type.Position);
+//        VertexBuffer nvb = getBuffer(Type.Normal);
+//        VertexBuffer tvb = getBuffer(Type.TexCoord);
+
+        // Vertices
+        int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0);
+
+        setBuffer(Type.Position, 3, createVector3Buffer(getFloatBuffer(Type.Position), vertCount));
+
+        // Normals
+        setBuffer(Type.Normal, 3, createVector3Buffer(getFloatBuffer(Type.Normal), vertCount));
+
+        // Texture co-ordinates
+        setBuffer(Type.TexCoord, 2, createVector2Buffer(vertCount));
+
+        int triCount = ((closed ? 2 : 0) + 2 * (axisSamples - 1)) * radialSamples;
+
+        setBuffer(Type.Index, 3, createShortBuffer(getShortBuffer(Type.Index), 3 * triCount));
+
+        //Color
+        setBuffer(Type.Color, 4, createFloatBuffer(vertCount * 4));
+
+        // generate geometry
+        float inverseRadial = 1.0f / radialSamples;
+        float inverseAxisLess = 1.0f / (closed ? axisSamples - 3 : axisSamples - 1);
+        float inverseAxisLessTexture = 1.0f / (axisSamples - 1);
+        float halfHeight = 0.5f * height;
+
+        // Generate points on the unit circle to be used in computing the mesh
+        // points on a cylinder slice.
+        float[] sin = new float[radialSamples + 1];
+        float[] cos = new float[radialSamples + 1];
+
+        for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
+            float angle = FastMath.TWO_PI * inverseRadial * radialCount;
+            cos[radialCount] = FastMath.cos(angle);
+            sin[radialCount] = FastMath.sin(angle);
+        }
+        sin[radialSamples] = sin[0];
+        cos[radialSamples] = cos[0];
+
+        // calculate normals
+        Vector3f[] vNormals = null;
+        Vector3f vNormal = Vector3f.UNIT_Z;
+
+        if ((height != 0.0f) && (radius != radius2)) {
+            vNormals = new Vector3f[radialSamples];
+            Vector3f vHeight = Vector3f.UNIT_Z.mult(height);
+            Vector3f vRadial = new Vector3f();
+
+            for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
+                vRadial.set(cos[radialCount], sin[radialCount], 0.0f);
+                Vector3f vRadius = vRadial.mult(radius);
+                Vector3f vRadius2 = vRadial.mult(radius2);
+                Vector3f vMantle = vHeight.subtract(vRadius2.subtract(vRadius));
+                Vector3f vTangent = vRadial.cross(Vector3f.UNIT_Z);
+                vNormals[radialCount] = vMantle.cross(vTangent).normalize();
+            }
+        }
+
+        FloatBuffer nb = getFloatBuffer(Type.Normal);
+        FloatBuffer pb = getFloatBuffer(Type.Position);
+        FloatBuffer tb = getFloatBuffer(Type.TexCoord);
+        FloatBuffer cb = getFloatBuffer(Type.Color);
+
+        cb.rewind();
+        for (int i = 0; i < vertCount; i++) {
+            cb.put(0.05f).put(0.05f).put(0.05f).put(1f);
+        }
+
+        // generate the cylinder itself
+        Vector3f tempNormal = new Vector3f();
+        for (int axisCount = 0, i = 0; axisCount < axisSamples; axisCount++, i++) {
+            float axisFraction;
+            float axisFractionTexture;
+            int topBottom = 0;
+            if (!closed) {
+                axisFraction = axisCount * inverseAxisLess; // in [0,1]
+                axisFractionTexture = axisFraction;
+            } else {
+                if (axisCount == 0) {
+                    topBottom = -1; // bottom
+                    axisFraction = 0;
+                    axisFractionTexture = inverseAxisLessTexture;
+                } else if (axisCount == axisSamples - 1) {
+                    topBottom = 1; // top
+                    axisFraction = 1;
+                    axisFractionTexture = 1 - inverseAxisLessTexture;
+                } else {
+                    axisFraction = (axisCount - 1) * inverseAxisLess;
+                    axisFractionTexture = axisCount * inverseAxisLessTexture;
+                }
+            }
+
+            // compute center of slice
+            float z = height * axisFraction;
+            Vector3f sliceCenter = new Vector3f(0, 0, z);
+
+            // compute slice vertices with duplication at end point
+            int save = i;
+            for (int radialCount = 0; radialCount < radialSamples; radialCount++, i++) {
+                float radialFraction = radialCount * inverseRadial; // in [0,1)
+                tempNormal.set(cos[radialCount], sin[radialCount], 0.0f);
+
+                if (vNormals != null) {
+                    vNormal = vNormals[radialCount];
+                } else if (radius == radius2) {
+                    vNormal = tempNormal;
+                }
+
+                if (topBottom == 0) {
+                    if (!inverted)
+                        nb.put(vNormal.x).put(vNormal.y).put(vNormal.z);
+                    else
+                        nb.put(-vNormal.x).put(-vNormal.y).put(-vNormal.z);
+                } else {
+                    nb.put(0).put(0).put(topBottom * (inverted ? -1 : 1));
+                }
+
+                tempNormal.multLocal((radius - radius2) * axisFraction + radius2)
+                        .addLocal(sliceCenter);
+                pb.put(tempNormal.x).put(tempNormal.y).put(tempNormal.z);
+
+                tb.put((inverted ? 1 - radialFraction : radialFraction))
+                        .put(axisFractionTexture);
+            }
+
+            BufferUtils.copyInternalVector3(pb, save, i);
+            BufferUtils.copyInternalVector3(nb, save, i);
+
+            tb.put((inverted ? 0.0f : 1.0f))
+                    .put(axisFractionTexture);
+        }
+
+        if (closed) {
+            pb.put(0).put(0).put(-halfHeight); // bottom center
+            nb.put(0).put(0).put(-1 * (inverted ? -1 : 1));
+            tb.put(0.5f).put(0);
+            pb.put(0).put(0).put(halfHeight); // top center
+            nb.put(0).put(0).put(1 * (inverted ? -1 : 1));
+            tb.put(0.5f).put(1);
+        }
+
+        IndexBuffer ib = getIndexBuffer();
+        int index = 0;
+        // Connectivity
+        for (int axisCount = 0, axisStart = 0; axisCount < axisSamples - 1; axisCount++) {
+            int i0 = axisStart;
+            int i1 = i0 + 1;
+            axisStart += radialSamples + 1;
+            int i2 = axisStart;
+            int i3 = i2 + 1;
+            for (int i = 0; i < radialSamples; i++) {
+                if (closed && axisCount == 0) {
+                    if (!inverted) {
+                        ib.put(index++, i0++);
+                        ib.put(index++, vertCount - 2);
+                        ib.put(index++, i1++);
+                    } else {
+                        ib.put(index++, i0++);
+                        ib.put(index++, i1++);
+                        ib.put(index++, vertCount - 2);
+                    }
+                } else if (closed && axisCount == axisSamples - 2) {
+                    ib.put(index++, i2++);
+                    ib.put(index++, inverted ? vertCount - 1 : i3++);
+                    ib.put(index++, inverted ? i3++ : vertCount - 1);
+                } else {
+                    ib.put(index++, i0++);
+                    ib.put(index++, inverted ? i2 : i1);
+                    ib.put(index++, inverted ? i1 : i2);
+                    ib.put(index++, i1++);
+                    ib.put(index++, inverted ? i2++ : i3++);
+                    ib.put(index++, inverted ? i3++ : i2++);
+                }
+            }
+        }
+
+        updateBound();
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        super.read(e);
+        InputCapsule capsule = e.getCapsule(this);
+        axisSamples = capsule.readInt("axisSamples", 0);
+        radialSamples = capsule.readInt("radialSamples", 0);
+        radius = capsule.readFloat("radius", 0);
+        radius2 = capsule.readFloat("radius2", 0);
+        height = capsule.readFloat("height", 0);
+        closed = capsule.readBoolean("closed", false);
+        inverted = capsule.readBoolean("inverted", false);
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        super.write(e);
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(axisSamples, "axisSamples", 0);
+        capsule.write(radialSamples, "radialSamples", 0);
+        capsule.write(radius, "radius", 0);
+        capsule.write(radius2, "radius2", 0);
+        capsule.write(height, "height", 0);
+        capsule.write(closed, "closed", false);
+        capsule.write(inverted, "inverted", false);
+    }
+
+
+}

+ 216 - 0
jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonBone.java

@@ -0,0 +1,216 @@
+package com.jme3.scene.debug.custom;
+
+/*
+ * 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.
+ */
+
+import java.util.Map;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+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.Sphere;
+
+import static com.jme3.util.BufferUtils.createFloatBuffer;
+
+import java.nio.FloatBuffer;
+import java.util.HashMap;
+
+/**
+ * The class that displays either wires between the bones' heads if no length
+ * data is supplied and full bones' shapes otherwise.
+ */
+public class SkeletonBone extends Node {
+
+    /**
+     * The skeleton to be displayed.
+     */
+    private Skeleton skeleton;
+    /**
+     * The map between the bone index and its length.
+     */
+    private Map<Bone, Node> boneNodes = new HashMap<Bone, Node>();
+    private Map<Node, Bone> nodeBones = new HashMap<Node, Bone>();
+    private Node selectedNode = null;
+    private boolean guessBonesOrientation = false;
+
+    /**
+     * 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 SkeletonBone(Skeleton skeleton, Map<Integer, Float> boneLengths, boolean guessBonesOrientation) {
+        this.skeleton = skeleton;
+        this.skeleton.reset();
+        this.skeleton.updateWorldVectors();
+        this.guessBonesOrientation = guessBonesOrientation;
+
+        BoneShape boneShape = new BoneShape(5, 12, 0.02f, 0.07f, 1f, false, false);
+        Sphere jointShape = new Sphere(10, 10, 0.1f);
+        jointShape.setBuffer(VertexBuffer.Type.Color, 4, createFloatBuffer(jointShape.getVertexCount() * 4));
+        FloatBuffer cb = jointShape.getFloatBuffer(VertexBuffer.Type.Color);
+
+        cb.rewind();
+        for (int i = 0; i < jointShape.getVertexCount(); i++) {
+            cb.put(0.05f).put(0.05f).put(0.05f).put(1f);
+        }
+
+        for (Bone bone : skeleton.getRoots()) {
+            createSkeletonGeoms(bone, boneShape, jointShape, boneLengths, skeleton, this, guessBonesOrientation);
+        }
+    }
+
+    protected final void createSkeletonGeoms(Bone bone, Mesh boneShape, Mesh jointShape, Map<Integer, Float> boneLengths, Skeleton skeleton, Node parent, boolean guessBonesOrientation) {
+
+        if (guessBonesOrientation && bone.getName().equalsIgnoreCase("Site")) {
+            //BVH skeleton have a useless end point bone named Site
+            return;
+        }
+        Node n = new Node(bone.getName() + "Node");
+        Geometry bGeom = new Geometry(bone.getName(), boneShape);
+        Geometry jGeom = new Geometry(bone.getName() + "Joint", jointShape);
+        n.setLocalTranslation(bone.getLocalPosition());
+        n.setLocalRotation(bone.getLocalRotation());
+
+        float boneLength = boneLengths.get(skeleton.getBoneIndex(bone));
+        n.setLocalScale(bone.getLocalScale());
+
+        bGeom.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X).normalizeLocal());
+
+        if (guessBonesOrientation) {
+            //One child only, the bone direction is from the parent joint to the child joint.
+            if (bone.getChildren().size() == 1) {
+                Vector3f v = bone.getChildren().get(0).getLocalPosition();
+                Quaternion q = new Quaternion();
+                q.lookAt(v, Vector3f.UNIT_Z);
+                bGeom.setLocalRotation(q);
+            }
+            //no child, the bone has the same direction as the parent bone.
+            if (bone.getChildren().isEmpty()) {
+                if (parent.getChildren().size() > 0) {
+                    bGeom.setLocalRotation(parent.getChild(0).getLocalRotation());
+                } else {
+                    //no parent, let's use the bind orientation of the bone
+                    bGeom.setLocalRotation(bone.getBindRotation());
+                }
+            }
+        }
+        bGeom.setLocalScale(boneLength);
+        jGeom.setLocalScale(boneLength);
+
+        n.attachChild(bGeom);
+        n.attachChild(jGeom);
+
+        //tip
+        if (bone.getChildren().size() != 1) {
+            Geometry gt = jGeom.clone();
+            gt.scale(0.8f);
+            Vector3f v = new Vector3f(0, boneLength, 0);
+            if (guessBonesOrientation) {
+                if (bone.getChildren().isEmpty()) {
+                    if (parent.getChildren().size() > 0) {
+                        gt.setLocalTranslation(bGeom.getLocalRotation().mult(parent.getChild(0).getLocalRotation()).mult(v, v));
+                    } else {
+                        gt.setLocalTranslation(bGeom.getLocalRotation().mult(bone.getBindRotation()).mult(v, v));
+                    }
+                }
+            } else {
+                gt.setLocalTranslation(v);
+            }
+
+            n.attachChild(gt);
+        }
+
+
+        boneNodes.put(bone, n);
+        nodeBones.put(n, bone);
+        parent.attachChild(n);
+        for (Bone childBone : bone.getChildren()) {
+            createSkeletonGeoms(childBone, boneShape, jointShape, boneLengths, skeleton, n, guessBonesOrientation);
+        }
+    }
+
+    protected Bone select(Geometry g) {
+        Node parentNode = g.getParent();
+
+        if (parent != null) {
+            Bone b = nodeBones.get(parentNode);
+            if (b != null) {
+                selectedNode = parentNode;
+            }
+            return b;
+        }
+        return null;
+    }
+
+    protected Node getSelectedNode() {
+        return selectedNode;
+    }
+
+
+//    private Quaternion getRotationBetweenVect(Vector3f v1, Vector3f v2){       
+//        Vector3f a =v1.cross(v2);
+//        float w = FastMath.sqrt((v1.length() * v1.length()) * (v2.length() * v2.length())) + v1.dot(v2);       
+//        return new Quaternion(a.x, a.y, a.z, w).normalizeLocal() ;
+//    }
+
+    protected final void updateSkeletonGeoms(Bone bone) {
+        if (guessBonesOrientation && bone.getName().equalsIgnoreCase("Site")) {
+            return;
+        }
+        Node n = boneNodes.get(bone);
+        n.setLocalTranslation(bone.getLocalPosition());
+        n.setLocalRotation(bone.getLocalRotation());
+        n.setLocalScale(bone.getLocalScale());
+
+        for (Bone childBone : bone.getChildren()) {
+            updateSkeletonGeoms(childBone);
+        }
+    }
+
+    /**
+     * The method updates the geometry according to the poitions of the bones.
+     */
+    public void updateGeometry() {
+
+        for (Bone bone : skeleton.getRoots()) {
+            updateSkeletonGeoms(bone);
+        }
+    }
+}

+ 150 - 0
jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugAppState.java

@@ -0,0 +1,150 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.scene.debug.custom;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.animation.SkeletonControl;
+import com.jme3.app.Application;
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.collision.CollisionResults;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+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.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Nehon
+ */
+public class SkeletonDebugAppState extends AbstractAppState {
+
+    private Node debugNode = new Node("debugNode");
+    private Map<Skeleton, SkeletonDebugger> skeletons = new HashMap<Skeleton, SkeletonDebugger>();
+    private Map<Skeleton, Bone> selectedBones = new HashMap<Skeleton, Bone>();
+    private Application app;
+
+    @Override
+    public void initialize(AppStateManager stateManager, Application app) {
+        ViewPort vp = app.getRenderManager().createMainView("debug", app.getCamera());
+        vp.attachScene(debugNode);
+        vp.setClearDepth(true);
+        this.app = app;
+        for (SkeletonDebugger skeletonDebugger : skeletons.values()) {
+            skeletonDebugger.initialize(app.getAssetManager());
+        }
+        app.getInputManager().addListener(actionListener, "shoot");
+        app.getInputManager().addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
+        super.initialize(stateManager, app);
+    }
+
+    @Override
+    public void update(float tpf) {
+        debugNode.updateLogicalState(tpf);
+        debugNode.updateGeometricState();
+    }
+
+    public SkeletonDebugger addSkeleton(SkeletonControl skeletonControl, boolean guessBonesOrientation) {
+        Skeleton skeleton = skeletonControl.getSkeleton();
+        Spatial forSpatial = skeletonControl.getSpatial();
+        SkeletonDebugger sd = new SkeletonDebugger(forSpatial.getName() + "_Skeleton", skeleton, guessBonesOrientation);
+        sd.setLocalTransform(forSpatial.getWorldTransform());
+        if (forSpatial instanceof Node) {
+            List<Geometry> geoms = new ArrayList<>();
+            findGeoms((Node) forSpatial, geoms);
+            if (geoms.size() == 1) {
+                sd.setLocalTransform(geoms.get(0).getWorldTransform());
+            }
+        }
+        skeletons.put(skeleton, sd);
+        debugNode.attachChild(sd);
+        if (isInitialized()) {
+            sd.initialize(app.getAssetManager());
+        }
+        return sd;
+    }
+
+    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);
+            }
+        }
+    }
+
+    /**
+     * Pick a Target Using the Mouse Pointer. <ol><li>Map "pick target" action
+     * to a MouseButtonTrigger. <li>flyCam.setEnabled(false);
+     * <li>inputManager.setCursorVisible(true); <li>Implement action in
+     * AnalogListener (TODO).</ol>
+     */
+    private ActionListener actionListener = new ActionListener() {
+        public void onAction(String name, boolean isPressed, float tpf) {
+            if (name.equals("shoot") && isPressed) {
+                CollisionResults results = new CollisionResults();
+                Vector2f click2d = app.getInputManager().getCursorPosition();
+                Vector3f click3d = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f).clone();
+                Vector3f dir = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d);
+                Ray ray = new Ray(click3d, dir);
+
+                debugNode.collideWith(ray, results);
+
+                if (results.size() > 0) {
+                    // The closest result is the target that the player picked:
+                    Geometry target = results.getClosestCollision().getGeometry();
+                    for (SkeletonDebugger skeleton : skeletons.values()) {
+                        Bone selectedBone = skeleton.select(target);
+                        if (selectedBone != null) {
+                            selectedBones.put(skeleton.getSkeleton(), selectedBone);
+                            System.err.println("-----------------------");
+                            System.err.println("Selected Bone : " + selectedBone.getName() + " in skeleton " + skeleton.getName());
+                            System.err.println("-----------------------");
+                            System.err.println("Bind translation: " + selectedBone.getBindPosition());
+                            System.err.println("Bind rotation: " + selectedBone.getBindRotation());
+                            System.err.println("Bind scale: " + selectedBone.getBindScale());
+                            System.err.println("---");
+                            System.err.println("Local translation: " + selectedBone.getLocalPosition());
+                            System.err.println("Local rotation: " + selectedBone.getLocalRotation());
+                            System.err.println("Local scale: " + selectedBone.getLocalScale());
+                            System.err.println("---");
+                            System.err.println("Model translation: " + selectedBone.getModelSpacePosition());
+                            System.err.println("Model rotation: " + selectedBone.getModelSpaceRotation());
+                            System.err.println("Model scale: " + selectedBone.getModelSpaceScale());
+                            System.err.println("---");
+                            System.err.println("Bind inverse Transform: ");
+                            System.err.println(selectedBone.getBindInverseTransform());
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+    };
+
+    public Map<Skeleton, Bone> getSelectedBones() {
+        return selectedBones;
+    }
+
+    public Node getDebugNode() {
+        return debugNode;
+    }
+
+    public void setDebugNode(Node debugNode) {
+        this.debugNode = debugNode;
+    }
+}

+ 218 - 0
jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugger.java

@@ -0,0 +1,218 @@
+package com.jme3.scene.debug.custom;
+
+/*
+ * 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.
+ */
+
+import com.jme3.animation.Bone;
+
+import java.util.Map;
+
+import com.jme3.animation.Skeleton;
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.BatchNode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer;
+
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * 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 BatchNode {
+
+    /**
+     * The lines of the bones or the wires between their heads.
+     */
+    private SkeletonBone bones;
+
+    private Skeleton skeleton;
+    /**
+     * 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 List<Bone> selectedBones = new ArrayList<Bone>();
+
+    public SkeletonDebugger() {
+    }
+
+    /**
+     * 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, boolean guessBonesOrientation) {
+        super(name);
+        this.skeleton = skeleton;
+        skeleton.reset();
+        skeleton.updateWorldVectors();
+        Map<Integer, Float> boneLengths = new HashMap<Integer, Float>();
+
+        for (Bone bone : skeleton.getRoots()) {
+            computeLength(bone, boneLengths, skeleton);
+        }
+
+        bones = new SkeletonBone(skeleton, boneLengths, guessBonesOrientation);
+
+        this.attachChild(bones);
+
+        interBoneWires = new SkeletonInterBoneWire(skeleton, boneLengths, guessBonesOrientation);
+        Geometry g = new Geometry(name + "_interwires", interBoneWires);
+        g.setBatchHint(BatchHint.Never);
+        this.attachChild(g);
+    }
+
+    protected void initialize(AssetManager assetManager) {
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", new ColorRGBA(0.05f, 0.05f, 0.05f, 1.0f));//new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f)   
+        setMaterial(mat);
+        Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat2.setBoolean("VertexColor", true);
+        bones.setMaterial(mat2);
+        batch();
+
+    }
+
+    @Override
+    public final void setMaterial(Material material) {
+        if (batches.isEmpty()) {
+            for (int i = 0; i < children.size(); i++) {
+                children.get(i).setMaterial(material);
+            }
+        } else {
+            super.setMaterial(material);
+        }
+    }
+
+    public Skeleton getSkeleton() {
+        return skeleton;
+    }
+
+
+    private void computeLength(Bone b, Map<Integer, Float> boneLengths, Skeleton skeleton) {
+        if (b.getChildren().isEmpty()) {
+            if (b.getParent() != null) {
+                boneLengths.put(skeleton.getBoneIndex(b), boneLengths.get(skeleton.getBoneIndex(b.getParent())) * 0.75f);
+            } else {
+                boneLengths.put(skeleton.getBoneIndex(b), 0.1f);
+            }
+        } else {
+            float length = Float.MAX_VALUE;
+            for (Bone bone : b.getChildren()) {
+                float len = b.getModelSpacePosition().subtract(bone.getModelSpacePosition()).length();
+                if (len < length) {
+                    length = len;
+                }
+            }
+            boneLengths.put(skeleton.getBoneIndex(b), length);
+            for (Bone bone : b.getChildren()) {
+                computeLength(bone, boneLengths, skeleton);
+            }
+        }
+    }
+
+    @Override
+    public void updateLogicalState(float tpf) {
+        super.updateLogicalState(tpf);
+        bones.updateGeometry();
+        if (interBoneWires != null) {
+            interBoneWires.updateGeometry();
+        }
+    }
+
+    ColorRGBA selectedColor = ColorRGBA.Orange;
+    ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f);
+
+    protected Bone select(Geometry g) {
+        Node oldNode = bones.getSelectedNode();
+        Bone b = bones.select(g);
+        if (b == null) {
+            return null;
+        }
+        if (oldNode != null) {
+            markSelected(oldNode, false);
+        }
+        markSelected(bones.getSelectedNode(), true);
+        return b;
+    }
+
+    /**
+     * @return the skeleton wires
+     */
+    public SkeletonBone getBoneShapes() {
+        return bones;
+    }
+
+    /**
+     * @return the dotted line between bones (can be null)
+     */
+    public SkeletonInterBoneWire getInterBoneWires() {
+        return interBoneWires;
+    }
+
+    protected void markSelected(Node n, boolean selected) {
+        ColorRGBA c = baseColor;
+        if (selected) {
+            c = selectedColor;
+        }
+        for (Spatial spatial : n.getChildren()) {
+            if (spatial instanceof Geometry) {
+                Geometry geom = (Geometry) spatial;
+
+                Geometry batch = (Geometry) getChild(getName() + "-batch0");
+                VertexBuffer vb = batch.getMesh().getBuffer(VertexBuffer.Type.Color);
+                FloatBuffer color = (FloatBuffer) vb.getData();
+                //  System.err.println(getName() + "." + geom.getName() + " index " + getGeometryStartIndex(geom) * 4 + "/" + color.limit());
+
+                color.position(getGeometryStartIndex(geom) * 4);
+
+                for (int i = 0; i < geom.getVertexCount(); i++) {
+                    color.put(c.r).put(c.g).put(c.b).put(c.a);
+                }
+                color.rewind();
+                vb.updateData(color);
+            }
+        }
+    }
+}

+ 141 - 0
jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonInterBoneWire.java

@@ -0,0 +1,141 @@
+package com.jme3.scene.debug.custom;
+
+/*
+ * 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.
+ */
+
+
+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<Integer, Float> boneLengths;
+
+    private boolean guessBonesOrientation = false;
+
+    /**
+     * 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<Integer, Float> boneLengths, boolean guessBonesOrientation) {
+        this.skeleton = skeleton;
+
+        for (Bone bone : skeleton.getRoots()) {
+            this.countConnections(bone);
+        }
+
+        this.setMode(Mode.Points);
+        this.setPointSize(2);
+        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.guessBonesOrientation = guessBonesOrientation;
+        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))));
+
+            if (guessBonesOrientation) {
+                parentTail = bone.getModelSpacePosition();
+            }
+
+            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);
+        }
+    }
+}