Pārlūkot izejas kodu

better Armature debugger

Nehon 7 gadi atpakaļ
vecāks
revīzija
ce170b8b53

+ 24 - 0
jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java

@@ -0,0 +1,24 @@
+package com.jme3.anim.util;
+
+import com.jme3.animation.AnimControl;
+import com.jme3.scene.SceneGraphVisitor;
+import com.jme3.scene.Spatial;
+
+public class AnimMigrationUtils {
+
+    public static Spatial migrate(Spatial source) {
+        //source.depthFirstTraversal();
+        return source;
+    }
+
+    private class AnimControlVisitor implements SceneGraphVisitor {
+
+        @Override
+        public void visit(Spatial spatial) {
+            AnimControl control = spatial.getControl(AnimControl.class);
+            if (control != null) {
+
+            }
+        }
+    }
+}

+ 63 - 0
jme3-core/src/main/java/com/jme3/math/MathUtils.java

@@ -1,5 +1,7 @@
 package com.jme3.math;
 
+import com.jme3.util.TempVars;
+
 /**
  * Created by Nehon on 23/04/2017.
  */
@@ -162,4 +164,65 @@ public class MathUtils {
     }
 
 
+    public static float raySegmentShortestDistance(Ray ray, Vector3f segStart, Vector3f segEnd) {
+        // Algorithm is ported from the C algorithm of
+        // Paul Bourke at http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline3d/
+        TempVars vars = TempVars.get();
+        Vector3f resultSegmentPoint1 = vars.vect1;
+        Vector3f resultSegmentPoint2 = vars.vect2;
+
+        Vector3f p1 = segStart;
+        Vector3f p2 = segEnd;
+        Vector3f p3 = ray.origin;
+        Vector3f p4 = vars.vect3.set(ray.getDirection()).multLocal(Math.min(ray.getLimit(), 1000)).addLocal(ray.getOrigin());
+        Vector3f p13 = vars.vect4.set(p1).subtractLocal(p3);
+        Vector3f p43 = vars.vect5.set(p4).subtractLocal(p3);
+
+        if (p43.lengthSquared() < 0.0001) {
+            vars.release();
+            return -1;
+        }
+        Vector3f p21 = vars.vect6.set(p2).subtractLocal(p1);
+        if (p21.lengthSquared() < 0.0001) {
+            vars.release();
+            return -1;
+        }
+
+        double d1343 = p13.x * (double) p43.x + (double) p13.y * p43.y + (double) p13.z * p43.z;
+        double d4321 = p43.x * (double) p21.x + (double) p43.y * p21.y + (double) p43.z * p21.z;
+        double d1321 = p13.x * (double) p21.x + (double) p13.y * p21.y + (double) p13.z * p21.z;
+        double d4343 = p43.x * (double) p43.x + (double) p43.y * p43.y + (double) p43.z * p43.z;
+        double d2121 = p21.x * (double) p21.x + (double) p21.y * p21.y + (double) p21.z * p21.z;
+
+        double denom = d2121 * d4343 - d4321 * d4321;
+        if (Math.abs(denom) < 0.0001) {
+            vars.release();
+            return -1;
+        }
+        double numer = d1343 * d4321 - d1321 * d4343;
+
+        double mua = numer / denom;
+        double mub = (d1343 + d4321 * (mua)) / d4343;
+
+        resultSegmentPoint1.x = (float) (p1.x + mua * p21.x);
+        resultSegmentPoint1.y = (float) (p1.y + mua * p21.y);
+        resultSegmentPoint1.z = (float) (p1.z + mua * p21.z);
+        resultSegmentPoint2.x = (float) (p3.x + mub * p43.x);
+        resultSegmentPoint2.y = (float) (p3.y + mub * p43.y);
+        resultSegmentPoint2.z = (float) (p3.z + mub * p43.z);
+
+        //check if result 1 is in the segment section.
+        float startToPoint = vars.vect3.set(resultSegmentPoint1).subtractLocal(segStart).lengthSquared();
+        float endToPoint = vars.vect3.set(resultSegmentPoint1).subtractLocal(segEnd).lengthSquared();
+        float segLength = vars.vect3.set(segEnd).subtractLocal(segStart).lengthSquared();
+        if (startToPoint > segLength || endToPoint > segLength) {
+            vars.release();
+            return -1;
+        }
+
+        float length = resultSegmentPoint1.subtractLocal(resultSegmentPoint2).length();
+        vars.release();
+        return length;
+    }
+
 }

+ 0 - 199
jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureBone.java

@@ -1,199 +0,0 @@
-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.anim.Armature;
-import com.jme3.anim.Joint;
-import com.jme3.bounding.*;
-import com.jme3.math.Quaternion;
-import com.jme3.math.Vector3f;
-import com.jme3.scene.*;
-import com.jme3.scene.shape.Sphere;
-
-import java.nio.FloatBuffer;
-import java.util.HashMap;
-import java.util.Map;
-
-import static com.jme3.util.BufferUtils.createFloatBuffer;
-
-/**
- * The class that displays either wires between the bones' heads if no length
- * data is supplied and full bones' shapes otherwise.
- */
-public class ArmatureBone extends Node {
-
-    /**
-     * The armature to be displayed.
-     */
-    private Armature armature;
-    /**
-     * The map between the bone index and its length.
-     */
-    private Map<Joint, Node> jointNode = new HashMap<>();
-    private Map<Node, Joint> nodeJoint = new HashMap<>();
-    private Node selectedNode = null;
-    private boolean guessJointsOrientation = 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 armature    the armature that will be shown
-     * @param boneLengths a map between the bone's index and the bone's length
-     */
-    public ArmatureBone(Armature armature, Map<Integer, Float> boneLengths, boolean guessJointsOrientation) {
-        this.armature = armature;
-        this.guessJointsOrientation = guessJointsOrientation;
-
-        BoneShape boneShape = new BoneShape();
-        Sphere jointShape = new Sphere(16, 16, 0.05f);
-        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 (Joint joint : armature.getRoots()) {
-            createSkeletonGeoms(joint, boneShape, jointShape, boneLengths, armature, this, guessJointsOrientation);
-        }
-        this.updateModelBound();
-
-
-        Sphere originShape = new Sphere(16, 16, 0.02f);
-        originShape.setBuffer(VertexBuffer.Type.Color, 4, createFloatBuffer(originShape.getVertexCount() * 4));
-        cb = originShape.getFloatBuffer(VertexBuffer.Type.Color);
-        cb.rewind();
-        for (int i = 0; i < jointShape.getVertexCount(); i++) {
-            cb.put(0.4f).put(0.4f).put(0.05f).put(1f);
-        }
-
-        Geometry origin = new Geometry("origin", originShape);
-        BoundingVolume bv = this.getWorldBound();
-        float scale = 1;
-        if (bv.getType() == BoundingVolume.Type.AABB) {
-            BoundingBox bb = (BoundingBox) bv;
-            scale = (bb.getXExtent() + bb.getYExtent() + bb.getZExtent()) / 3f;
-        } else if (bv.getType() == BoundingVolume.Type.Sphere) {
-            BoundingSphere bs = (BoundingSphere) bv;
-            scale = bs.getRadius();
-        }
-        origin.scale(scale);
-        attachChild(origin);
-    }
-
-    protected final void createSkeletonGeoms(Joint joint, Mesh boneShape, Mesh jointShape, Map<Integer, Float> boneLengths, Armature armature, Node parent, boolean guessBonesOrientation) {
-
-        Node n = new Node(joint.getName() + "Node");
-        Geometry bGeom = new Geometry(joint.getName() + "Bone", boneShape);
-        Geometry jGeom = new Geometry(joint.getName() + "Joint", jointShape);
-        n.setLocalTransform(joint.getLocalTransform());
-
-        float boneLength = boneLengths.get(armature.getJointIndex(joint));
-
-        if (guessBonesOrientation) {
-            //One child only, the bone direction is from the parent joint to the child joint.
-            if (joint.getChildren().size() == 1) {
-                Vector3f v = joint.getChildren().get(0).getLocalTranslation();
-                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 (joint.getChildren().isEmpty()) {
-                //no parent, let's use the bind orientation of the bone
-                Spatial s = parent.getChild(0);
-                if (s != null) {
-                    bGeom.setLocalRotation(s.getLocalRotation());
-                }
-            }
-        }
-
-        float boneScale = boneLength * 0.8f;
-        float scale = boneScale / 8f;
-        bGeom.setLocalScale(new Vector3f(scale, scale, boneScale));
-        Vector3f offset = new Vector3f(0, 0, boneLength * 0.1f);
-        bGeom.getLocalRotation().multLocal(offset);
-        bGeom.setLocalTranslation(offset);
-        jGeom.setLocalScale(boneLength);
-
-        if (joint.getChildren().size() <= 1) {
-            n.attachChild(bGeom);
-        }
-        n.attachChild(jGeom);
-
-        jointNode.put(joint, n);
-        nodeJoint.put(n, joint);
-        parent.attachChild(n);
-        for (Joint child : joint.getChildren()) {
-            createSkeletonGeoms(child, boneShape, jointShape, boneLengths, armature, n, guessBonesOrientation);
-        }
-    }
-
-    protected Joint select(Geometry g) {
-        Node parentNode = g.getParent();
-
-        if (parent != null) {
-            Joint j = nodeJoint.get(parentNode);
-            if (j != null) {
-                selectedNode = parentNode;
-            }
-            return j;
-        }
-        return null;
-    }
-
-    protected Node getSelectedNode() {
-        return selectedNode;
-    }
-
-
-    protected final void updateSkeletonGeoms(Joint joint) {
-        Node n = jointNode.get(joint);
-        n.setLocalTransform(joint.getLocalTransform());
-
-        for (Joint child : joint.getChildren()) {
-            updateSkeletonGeoms(child);
-        }
-    }
-
-    /**
-     * The method updates the geometry according to the positions of the bones.
-     */
-    public void updateGeometry() {
-        for (Joint joint : armature.getRoots()) {
-            updateSkeletonGeoms(joint);
-        }
-    }
-}

+ 44 - 47
jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java

@@ -8,12 +8,12 @@ import com.jme3.anim.*;
 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.light.DirectionalLight;
-import com.jme3.math.ColorRGBA;
-import com.jme3.math.Vector3f;
+import com.jme3.math.*;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.*;
 
@@ -28,7 +28,6 @@ public class ArmatureDebugAppState extends AbstractAppState {
     private Map<Armature, ArmatureDebugger> armatures = new HashMap<>();
     private Map<Armature, Joint> selectedBones = new HashMap<>();
     private Application app;
-
     @Override
     public void initialize(AppStateManager stateManager, Application app) {
         ViewPort vp = app.getRenderManager().createMainView("debug", app.getCamera());
@@ -46,7 +45,6 @@ public class ArmatureDebugAppState extends AbstractAppState {
         debugNode.addLight(new DirectionalLight(new Vector3f(-1f, -1f, -1f).normalizeLocal()));
 
         debugNode.addLight(new DirectionalLight(new Vector3f(1f, 1f, 1f).normalizeLocal(), new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)));
-
     }
 
     @Override
@@ -55,15 +53,15 @@ public class ArmatureDebugAppState extends AbstractAppState {
         debugNode.updateGeometricState();
     }
 
-    public ArmatureDebugger addArmature(SkinningControl skinningControl, boolean guessJointsOrientation) {
+    public ArmatureDebugger addArmature(SkinningControl skinningControl) {
         Armature armature = skinningControl.getArmature();
         Spatial forSpatial = skinningControl.getSpatial();
-        return addArmature(armature, forSpatial, guessJointsOrientation);
+        return addArmature(armature, forSpatial);
     }
 
-    public ArmatureDebugger addArmature(Armature armature, Spatial forSpatial, boolean guessJointsOrientation) {
+    public ArmatureDebugger addArmature(Armature armature, Spatial forSpatial) {
 
-        ArmatureDebugger ad = new ArmatureDebugger(forSpatial.getName() + "_Armature", armature, guessJointsOrientation);
+        ArmatureDebugger ad = new ArmatureDebugger(forSpatial.getName() + "_Armature", armature);
         ad.setLocalTransform(forSpatial.getWorldTransform());
         if (forSpatial instanceof Node) {
             List<Geometry> geoms = new ArrayList<>();
@@ -98,45 +96,44 @@ public class ArmatureDebugAppState extends AbstractAppState {
      */
     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 (ArmatureDebugger skeleton : armatures.values()) {
-//                        Joint selectedBone = skeleton.select(target);
-//                        if (selectedBone != null) {
-//                            selectedBones.put(skeleton.getArmature(), selectedBone);
-//                            System.err.println("-----------------------");
-//                            System.err.println("Selected Bone : " + selectedBone.getName() + " in skeleton " + skeleton.getName());
-//                            System.err.println("Root Bone : " + (selectedBone.getParent() == null));
-//                            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;
-//                        }
-//                    }
-//                }
-//            }
+            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) {
+                    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;
+                    }
+                }
+            }
         }
     };
 

+ 49 - 102
jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java

@@ -37,31 +37,39 @@ import com.jme3.anim.Joint;
 import com.jme3.animation.Bone;
 import com.jme3.asset.AssetManager;
 import com.jme3.material.Material;
-import com.jme3.math.ColorRGBA;
-import com.jme3.scene.*;
+import com.jme3.material.RenderState;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.texture.Texture;
 
-import java.nio.FloatBuffer;
-import java.util.*;
+import java.util.ArrayList;
+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 ArmatureDebugger extends BatchNode {
+public class ArmatureDebugger extends Node {
 
     /**
      * The lines of the bones or the wires between their heads.
      */
-    private ArmatureBone bones;
+    private ArmatureNode armatureNode;
 
     private Armature armature;
+
+    Node joints;
+    Node outlines;
+    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;
-    private Geometry wires;
+    //private Geometry wires;
     private List<Bone> selectedJoints = new ArrayList<Bone>();
 
     public ArmatureDebugger() {
@@ -75,110 +83,74 @@ public class ArmatureDebugger extends BatchNode {
      * @param name     the name of the debugger's node
      * @param armature the armature that will be shown
      */
-    public ArmatureDebugger(String name, Armature armature, boolean guessJointsOrientation) {
+    public ArmatureDebugger(String name, Armature armature) {
         super(name);
         this.armature = armature;
-//        armature.reset();
         armature.update();
-        //Joints have no length we want to display the as bones so we compute their length
-        Map<Integer, Float> bonesLength = new HashMap<Integer, Float>();
 
-        for (Joint joint : armature.getRoots()) {
-            computeLength(joint, bonesLength, armature);
-        }
 
-        bones = new ArmatureBone(armature, bonesLength, guessJointsOrientation);
+        joints = new Node("joints");
+        outlines = new Node("outlines");
+        wires = new Node("bones");
+        this.attachChild(joints);
+        this.attachChild(outlines);
+        this.attachChild(wires);
 
-        this.attachChild(bones);
+        armatureNode = new ArmatureNode(armature, joints, wires, outlines);
 
-        interJointWires = new ArmatureInterJointsWire(armature, bonesLength, guessJointsOrientation);
-        wires = new Geometry(name + "_interwires", interJointWires);
+        this.attachChild(armatureNode);
+
+        //interJointWires = new ArmatureInterJointsWire(armature, bonesLength, guessJointsOrientation);
+        //wires = new Geometry(name + "_interwires", interJointWires);
         //       this.attachChild(wires);
     }
 
     protected void initialize(AssetManager assetManager) {
-        Material mat = new Material(assetManager, "Common/MatDefs/Misc/fakeLighting.j3md");
-        mat.setColor("Color", new ColorRGBA(0.2f, 0.2f, 0.2f, 1));
-        setMaterial(mat);
-
         Material matWires = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
-        matWires.setColor("Color", ColorRGBA.Black);
+        matWires.setBoolean("VertexColor", true);
+        matWires.getAdditionalRenderState().setLineWidth(3);
         wires.setMaterial(matWires);
-        //wires.setQueueBucket(RenderQueue.Bucket.Transparent);
-//        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);
-        }
+        Material matOutline = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        matOutline.setBoolean("VertexColor", true);
+        //matOutline.setColor("Color", ColorRGBA.White);
+        matOutline.getAdditionalRenderState().setLineWidth(5);
+        outlines.setMaterial(matOutline);
+
+        Material matJoints = new Material(assetManager, "Common/MatDefs/Misc/Billboard.j3md");
+        Texture t = assetManager.loadTexture("Common/Textures/dot.png");
+//        matJoints.setBoolean("VertexColor", true);
+//        matJoints.setTexture("ColorMap", t);
+        matJoints.setTexture("Texture", t);
+        matJoints.getAdditionalRenderState().setDepthTest(false);
+        matJoints.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
+        joints.setQueueBucket(RenderQueue.Bucket.Translucent);
+        joints.setMaterial(matJoints);
+
     }
 
     public Armature getArmature() {
         return armature;
     }
 
-
-    private void computeLength(Joint joint, Map<Integer, Float> jointsLength, Armature armature) {
-        if (joint.getChildren().isEmpty()) {
-            if (joint.getParent() != null) {
-                jointsLength.put(armature.getJointIndex(joint), jointsLength.get(armature.getJointIndex(joint.getParent())) * 0.75f);
-            } else {
-                jointsLength.put(armature.getJointIndex(joint), 0.1f);
-            }
-        } else {
-            float length = Float.MAX_VALUE;
-            for (Joint child : joint.getChildren()) {
-                float len = joint.getModelTransform().getTranslation().subtract(child.getModelTransform().getTranslation()).length();
-                if (len < length) {
-                    length = len;
-                }
-            }
-            jointsLength.put(armature.getJointIndex(joint), length);
-            for (Joint child : joint.getChildren()) {
-                computeLength(child, jointsLength, armature);
-            }
-        }
-    }
-
     @Override
     public void updateLogicalState(float tpf) {
         super.updateLogicalState(tpf);
-        bones.updateGeometry();
+        armatureNode.updateGeometry();
         if (interJointWires != null) {
             //        interJointWires.updateGeometry();
         }
     }
 
-    ColorRGBA selectedColor = ColorRGBA.Orange;
-    ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f);
-
     protected Joint select(Geometry g) {
-        Node oldNode = bones.getSelectedNode();
-        Joint b = bones.select(g);
-        if (b == null) {
-            return null;
-        }
-        if (oldNode != null) {
-            markSelected(oldNode, false);
-        }
-        markSelected(bones.getSelectedNode(), true);
-        return b;
+        return armatureNode.select(g);
     }
 
     /**
      * @return the armature wires
      */
-    public ArmatureBone getBoneShapes() {
-        return bones;
+    public ArmatureNode getBoneShapes() {
+        return armatureNode;
     }
 
     /**
@@ -187,29 +159,4 @@ public class ArmatureDebugger extends BatchNode {
     public ArmatureInterJointsWire getInterJointWires() {
         return interJointWires;
     }
-
-    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);
-            }
-        }
-    }
 }

+ 245 - 0
jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java

@@ -0,0 +1,245 @@
+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.anim.Armature;
+import com.jme3.anim.Joint;
+import com.jme3.collision.*;
+import com.jme3.math.*;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.*;
+import com.jme3.scene.shape.Line;
+
+import java.nio.FloatBuffer;
+import java.util.HashMap;
+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.
+ */
+public class ArmatureNode extends Node {
+
+    /**
+     * The armature to be displayed.
+     */
+    private Armature armature;
+    /**
+     * The map between the bone index and its length.
+     */
+    private Map<Joint, Geometry[]> jointToGeoms = new HashMap<>();
+    private Map<Geometry, Joint> geomToJoint = new HashMap<>();
+    private Joint selectedJoint = null;
+    private Vector3f tmpStart = new Vector3f();
+    private Vector3f tmpEnd = new Vector3f();
+    ColorRGBA selectedColor = ColorRGBA.Orange;
+    ColorRGBA selectedColorJ = ColorRGBA.Yellow;
+    ;//new ColorRGBA(0.2f, 1f, 1.0f, 1.0f);
+    ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f);
+
+
+    /**
+     * 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 armature the armature that will be shown
+     */
+    public ArmatureNode(Armature armature, Node joints, Node wires, Node outlines) {
+        this.armature = armature;
+
+        for (Joint joint : armature.getRoots()) {
+            createSkeletonGeoms(joint, joints, wires, outlines);
+        }
+        this.updateModelBound();
+
+    }
+
+    protected final void createSkeletonGeoms(Joint joint, Node joints, Node wires, Node outlines) {
+        Vector3f start = joint.getModelTransform().getTranslation().clone();
+        Vector3f end = null;
+
+        //One child only, the bone direction is from the parent joint to the child joint.
+        if (joint.getChildren().size() == 1) {
+            end = joint.getChildren().get(0).getModelTransform().getTranslation().clone();
+        }
+
+        Geometry jGeom = new Geometry(joint.getName() + "Joint", new JointShape());
+        jGeom.setLocalTranslation(start);
+        joints.attachChild(jGeom);
+        Geometry bGeom = null;
+        Geometry bGeomO = null;
+        if (end != null) {
+            bGeom = new Geometry(joint.getName() + "Bone", new Line(start, end));
+            setColor(bGeom, baseColor);
+            geomToJoint.put(bGeom, joint);
+            bGeomO = new Geometry(joint.getName() + "BoneOutline", new Line(start, end));
+            setColor(bGeomO, ColorRGBA.White);
+            bGeom.setUserData("start", wires.getWorldTransform().transformVector(start, start));
+            bGeom.setUserData("end", wires.getWorldTransform().transformVector(end, end));
+            bGeom.setQueueBucket(RenderQueue.Bucket.Transparent);
+            wires.attachChild(bGeom);
+            outlines.attachChild(bGeomO);
+        }
+
+        jointToGeoms.put(joint, new Geometry[]{jGeom, bGeom, bGeomO});
+
+        for (Joint child : joint.getChildren()) {
+            createSkeletonGeoms(child, joints, wires, outlines);
+        }
+    }
+
+    protected Joint select(Geometry g) {
+        if (g == null) {
+            resetSelection();
+            return null;
+        }
+        Joint j = geomToJoint.get(g);
+        if (j != null) {
+            if (selectedJoint == j) {
+                return null;
+            }
+            resetSelection();
+            selectedJoint = j;
+            Geometry[] geomArray = jointToGeoms.get(selectedJoint);
+            setColor(geomArray[0], selectedColorJ);
+            setColor(geomArray[1], selectedColor);
+            setColor(geomArray[2], baseColor);
+            return j;
+        }
+        return null;
+    }
+
+    private void resetSelection() {
+        if (selectedJoint == null) {
+            return;
+        }
+        Geometry[] geoms = jointToGeoms.get(selectedJoint);
+        setColor(geoms[0], ColorRGBA.White);
+        setColor(geoms[1], baseColor);
+        setColor(geoms[2], ColorRGBA.White);
+        selectedJoint = null;
+    }
+
+    protected Joint getSelectedJoint() {
+        return selectedJoint;
+    }
+
+
+    protected final void updateSkeletonGeoms(Joint joint) {
+        Geometry[] geoms = jointToGeoms.get(joint);
+        if (geoms != null) {
+            Geometry jGeom = geoms[0];
+            jGeom.setLocalTranslation(joint.getModelTransform().getTranslation());
+            Geometry bGeom = geoms[1];
+            if (bGeom != null) {
+                tmpStart.set(joint.getModelTransform().getTranslation());
+                boolean hasEnd = false;
+                if (joint.getChildren().size() == 1) {
+                    tmpEnd.set(joint.getChildren().get(0).getModelTransform().getTranslation());
+                    hasEnd = true;
+                }
+                if (hasEnd) {
+                    updateBoneMesh(bGeom);
+                    Geometry bGeomO = geoms[2];
+                    updateBoneMesh(bGeomO);
+                    Vector3f start = bGeom.getUserData("start");
+                    Vector3f end = bGeom.getUserData("end");
+                    bGeom.setUserData("start", bGeom.getParent().getWorldTransform().transformVector(tmpStart, start));
+                    bGeom.setUserData("end", bGeom.getParent().getWorldTransform().transformVector(tmpEnd, end));
+                }
+            }
+        }
+
+        for (Joint child : joint.getChildren()) {
+            updateSkeletonGeoms(child);
+        }
+    }
+
+    @Override
+    public int collideWith(Collidable other, CollisionResults results) {
+        if (!(other instanceof Ray)) {
+            return 0;
+        }
+        int nbCol = 0;
+        for (Geometry g : geomToJoint.keySet()) {
+            float len = MathUtils.raySegmentShortestDistance((Ray) other, (Vector3f) g.getUserData("start"), (Vector3f) g.getUserData("end"));
+            if (len > 0 && len < 0.1f) {
+                CollisionResult res = new CollisionResult();
+                res.setGeometry(g);
+                results.addCollision(res);
+                nbCol++;
+            }
+        }
+        return nbCol;
+    }
+
+    private void updateBoneMesh(Geometry bGeom) {
+        VertexBuffer pos = bGeom.getMesh().getBuffer(VertexBuffer.Type.Position);
+        FloatBuffer fb = (FloatBuffer) pos.getData();
+        fb.rewind();
+        fb.put(new float[]{tmpStart.x, tmpStart.y, tmpStart.z,
+                tmpEnd.x, tmpEnd.y, tmpEnd.z,});
+        pos.updateData(fb);
+
+        bGeom.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) {
+            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);
+        if (colorBuff == null) {
+            g.getMesh().setBuffer(VertexBuffer.Type.Color, 4, colors);
+        } else {
+            FloatBuffer cBuff = (FloatBuffer) colorBuff.getData();
+            cBuff.rewind();
+            cBuff.put(colors);
+            colorBuff.updateData(cBuff);
+        }
+    }
+
+    /**
+     * The method updates the geometry according to the positions of the bones.
+     */
+    public void updateGeometry() {
+        armature.update();
+        for (Joint joint : armature.getRoots()) {
+            updateSkeletonGeoms(joint);
+        }
+    }
+}

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

@@ -1,171 +0,0 @@
-/*
- * Copyright (c) 2009-2018 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.math.*;
-import com.jme3.scene.VertexBuffer.Type;
-import com.jme3.scene.shape.AbstractBox;
-import com.jme3.util.BufferUtils;
-
-import java.nio.FloatBuffer;
-
-/**
- * A simple cylinder, defined by its 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 AbstractBox {
-
-    private static Vector3f topN = new Vector3f(0, 1, 0);
-    private static Vector3f botN = new Vector3f(0, -1, 0);
-    private static Vector3f rigN = new Vector3f(1, 0, 0);
-    private static Vector3f lefN = new Vector3f(-1, 0, 0);
-
-    static {
-        Quaternion q = new Quaternion().fromAngleAxis(-FastMath.PI / 16f, Vector3f.UNIT_X);
-        q.multLocal(topN);
-        q.inverseLocal();
-        q.multLocal(botN);
-        q = new Quaternion().fromAngleAxis(FastMath.PI / 16f, Vector3f.UNIT_Y);
-        q.multLocal(rigN);
-        q.inverseLocal();
-        q.multLocal(lefN);
-    }
-
-    private static final short[] GEOMETRY_INDICES_DATA = {
-            2, 1, 0, 3, 2, 0, // back
-            6, 5, 4, 7, 6, 4, // right
-            10, 9, 8, 11, 10, 8, // front
-            14, 13, 12, 15, 14, 12, // left
-            18, 17, 16, 19, 18, 16, // top
-            22, 21, 20, 23, 22, 20  // bottom
-    };
-
-    private static final float[] GEOMETRY_NORMALS_DATA = {
-            0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, // back
-            rigN.x, rigN.y, rigN.z, rigN.x, rigN.y, rigN.z, rigN.x, rigN.y, rigN.z, rigN.x, rigN.y, rigN.z, // right
-            0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // front
-            lefN.x, lefN.y, lefN.z, lefN.x, lefN.y, lefN.z, lefN.x, lefN.y, lefN.z, lefN.x, lefN.y, lefN.z, // left
-            topN.x, topN.y, topN.z, topN.x, topN.y, topN.z, topN.x, topN.y, topN.z, topN.x, topN.y, topN.z, // top
-            botN.x, botN.y, botN.z, botN.x, botN.y, botN.z, botN.x, botN.y, botN.z, botN.x, botN.y, botN.z  // bottom
-    };
-
-    private static final float[] GEOMETRY_TEXTURE_DATA = {
-            1, 0, 0, 0, 0, 1, 1, 1, // back
-            1, 0, 0, 0, 0, 1, 1, 1, // right
-            1, 0, 0, 0, 0, 1, 1, 1, // front
-            1, 0, 0, 0, 0, 1, 1, 1, // left
-            1, 0, 0, 0, 0, 1, 1, 1, // top
-            1, 0, 0, 0, 0, 1, 1, 1  // bottom
-    };
-
-    private static final float[] GEOMETRY_POSITION_DATA = {
-            -0.5f, -0.5f, 0, 0.5f, -0.5f, 0, 0.5f, 0.5f, 0, -0.5f, 0.5f, 0, //back
-            0.5f, -0.5f, 0, 0.25f, -0.25f, 1, 0.25f, 0.25f, 1, 0.5f, 0.5f, 0, //right
-            0.25f, -0.25f, 1, -0.25f, -0.25f, 1, -0.25f, 0.25f, 1, 0.25f, 0.25f, 1, //front
-            -0.25f, -0.25f, 1, -0.5f, -0.5f, 0, -0.5f, 0.5f, 0, -0.25f, 0.25f, 1, //left
-            0.5f, 0.5f, 0, 0.25f, 0.25f, 1, -0.25f, 0.25f, 1, -0.5f, 0.5f, 0, // top
-            -0.5f, -0.5f, 0, -0.25f, -0.25f, 1, 0.25f, -0.25f, 1, 0.5f, -0.5f, 0 // bottom
-    };
-
-    //0,1,2,3
-    //1,4,6,2
-    //4,5,7,6
-    //5,0,3,7,
-    //2,6,7,3
-    //0,5,4,1
-
-
-//    v[0].x, v[0].y, v[0].z, v[1].x, v[1].y, v[1].z, v[2].x, v[2].y, v[2].z, v[3].x, v[3].y, v[3].z, // back
-//    v[1].x, v[1].y, v[1].z, v[4].x, v[4].y, v[4].z, v[6].x, v[6].y, v[6].z, v[2].x, v[2].y, v[2].z, // right
-//    v[4].x, v[4].y, v[4].z, v[5].x, v[5].y, v[5].z, v[7].x, v[7].y, v[7].z, v[6].x, v[6].y, v[6].z, // front
-//    v[5].x, v[5].y, v[5].z, v[0].x, v[0].y, v[0].z, v[3].x, v[3].y, v[3].z, v[7].x, v[7].y, v[7].z, // left
-//    v[2].x, v[2].y, v[2].z, v[6].x, v[6].y, v[6].z, v[7].x, v[7].y, v[7].z, v[3].x, v[3].y, v[3].z, // top
-//    v[0].x, v[0].y, v[0].z, v[5].x, v[5].y, v[5].z, v[4].x, v[4].y, v[4].z, v[1].x, v[1].y, v[1].z  // bottom
-
-    /**
-     * Creates a new box.
-     * <p>
-     * The box has a center of 0,0,0 and extends in the out from the center by
-     * the given amount in <em>each</em> direction. So, for example, a box
-     * with extent of 0.5 would be the unit cube.
-     *
-     * @param x the size of the box along the x axis, in both directions.
-     * @param y the size of the box along the y axis, in both directions.
-     * @param z the size of the box along the z axis, in both directions.
-     */
-    public BoneShape() {
-        super();
-        updateGeometry();
-    }
-
-    /**
-     * Creates a clone of this box.
-     * <p>
-     * The cloned box will have '_clone' appended to it's name, but all other
-     * properties will be the same as this box.
-     */
-    @Override
-    public BoneShape clone() {
-        return new BoneShape();
-    }
-
-    protected void doUpdateGeometryIndices() {
-        if (getBuffer(Type.Index) == null) {
-            setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA));
-        }
-    }
-
-    protected void doUpdateGeometryNormals() {
-        if (getBuffer(Type.Normal) == null) {
-            setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(GEOMETRY_NORMALS_DATA));
-        }
-    }
-
-    protected void doUpdateGeometryTextures() {
-        if (getBuffer(Type.TexCoord) == null) {
-            setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA));
-        }
-    }
-
-    protected void doUpdateGeometryVertices() {
-        FloatBuffer fpb = BufferUtils.createVector3Buffer(24);
-        fpb.put(GEOMETRY_POSITION_DATA);
-        setBuffer(Type.Position, 3, fpb);
-        updateBound();
-    }
-
-
-}

+ 79 - 0
jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java

@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2009-2010 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.custom;
+
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+
+
+public class JointShape extends Mesh {
+
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public JointShape() {
+        float width = 1;
+        float height = 1;
+        setBuffer(Type.Position, 3, new float[]{-width * 0.5f, -width * 0.5f, 0,
+                width * 0.5f, -width * 0.5f, 0,
+                width * 0.5f, height * 0.5f, 0,
+                -width * 0.5f, height * 0.5f, 0
+        });
+
+
+        setBuffer(Type.TexCoord, 2, new float[]{0, 0,
+                1, 0,
+                1, 1,
+                0, 1});
+
+        setBuffer(Type.Normal, 3, new float[]{0, 0, 1,
+                0, 0, 1,
+                0, 0, 1,
+                0, 0, 1});
+
+        setBuffer(Type.Color, 4, new float[]{1, 1, 1, 1,
+                1, 1, 1, 1,
+                1, 1, 1, 1,
+                1, 1, 1, 1});
+
+        setBuffer(Type.Index, 3, new short[]{0, 1, 2,
+                0, 2, 3});
+
+
+        updateBound();
+        setStatic();
+    }
+
+
+}

+ 78 - 0
jme3-core/src/main/resources/Common/MatDefs/Misc/Billboard.j3md

@@ -0,0 +1,78 @@
+MaterialDef Billboard {
+    MaterialParameters {
+        Float SpriteHeight : 10
+        Texture2D Texture
+    }
+
+    Technique {
+        WorldParameters {
+            WorldViewMatrix
+            ProjectionMatrix
+            WorldMatrix
+            CameraDirection
+            ViewPort
+            CameraPosition
+        }
+
+        VertexShaderNodes {
+            ShaderNode TexCoord {
+                Definition: AttributeToVarying: Common/MatDefs/ShaderNodes/Basic/AttributeToVarying.j3sn
+                InputMappings {
+                    vec2Variable = Attr.inTexCoord
+                    vec4Variable = Attr.inColor
+                }
+                OutputMappings {
+                }
+            }
+            ShaderNode FixedScale {
+                Definition: FixedScale: Common/MatDefs/ShaderNodes/Common/FixedScale.j3sn
+                InputMappings {
+                    projectionMatrix = WorldParam.ProjectionMatrix
+                    worldMatrix = WorldParam.WorldMatrix
+                    cameraDir = WorldParam.CameraDirection
+                    viewport = WorldParam.ViewPort
+                    modelPosition = Attr.inPosition
+                    cameraPos = WorldParam.CameraPosition
+                    spriteHeight = MatParam.SpriteHeight
+                }
+                OutputMappings {
+                }
+            }
+            ShaderNode Billboard {
+                Definition: Billboard: Common/MatDefs/ShaderNodes/Common/Billboard.j3sn
+                InputMappings {
+                    worldViewMatrix = WorldParam.WorldViewMatrix
+                    projectionMatrix = WorldParam.ProjectionMatrix
+                    modelPosition = Attr.inPosition
+                    scale = FixedScale.scale
+                }
+                OutputMappings {
+                    Global.position = projPosition
+                }
+            }
+        }
+
+        FragmentShaderNodes {
+            ShaderNode TextureFetch {
+                Definition: TextureFetch: Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn
+                InputMappings {
+                    textureMap = MatParam.Texture
+                    texCoord = TexCoord.vec2Variable
+                }
+                OutputMappings {
+                }
+            }
+            ShaderNode ColorMult {
+                Definition: ColorMult: Common/MatDefs/ShaderNodes/Basic/ColorMult.j3sn
+                InputMappings {
+                    color1 = TextureFetch.outColor
+                    color2 = TexCoord.vec4Variable
+                }
+                OutputMappings {
+                    Global.color = outColor
+                }
+            }
+        }
+
+    }
+}

+ 35 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard.j3sn

@@ -0,0 +1,35 @@
+ShaderNodeDefinitions{
+    ShaderNodeDefinition Billboard {
+        //Vertex/Fragment
+        Type: Vertex
+
+        //Shader GLSL<version>: <Path to shader>
+        Shader GLSL100: Common/MatDefs/ShaderNodes/Common/Billboard100.frag
+
+        Documentation{
+            //type documentation here. This is optional but recommended
+
+            //@input <glsltype> <varName> <description>
+            @input mat4 worldViewMatrix The worldView matrix
+            @input mat4 projectionMatrix The projection matrix
+            @input vec3 modelPosition the vertex position
+            @input float scale the scale of the billboard (defautl 1)
+
+            //@output <glslType> <varName> <description>
+            @output vec4 projPosition The position in projection space
+        }
+        Input {
+            //all the node inputs
+            //<glslType> <varName>
+            mat4 worldViewMatrix
+            mat4 projectionMatrix
+            vec3 modelPosition
+            float scale 1
+        }
+        Output {
+            //all the node outputs
+            //<glslType> <varName>
+            vec4 projPosition
+        }
+    }
+}

+ 19 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard100.frag

@@ -0,0 +1,19 @@
+void main(){
+	// First colunm.
+  worldViewMatrix[0][0] = scale; 
+  worldViewMatrix[0][1] = 0.0; 
+  worldViewMatrix[0][2] = 0.0; 
+	  
+  // Second colunm.
+  worldViewMatrix[1][0] = 0.0; 
+  worldViewMatrix[1][1] = scale; 
+  worldViewMatrix[1][2] = 0.0; 
+	  
+  // Thrid colunm.
+  worldViewMatrix[2][0] = 0.0; 
+  worldViewMatrix[2][1] = 0.0; 
+  worldViewMatrix[2][2] = scale;  
+
+  vec4 position = worldViewMatrix * vec4(modelPosition,1.0);
+  projPosition = projectionMatrix * position;
+}

+ 41 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale.j3sn

@@ -0,0 +1,41 @@
+ShaderNodeDefinitions{
+    ShaderNodeDefinition FixedScale {
+        //Vertex/Fragment
+        Type: Vertex
+
+        //Shader GLSL<version>: <Path to shader>
+        Shader GLSL100: Common/MatDefs/ShaderNodes/Common/FixedScale100.frag
+
+        Documentation{
+            //type documentation here. This is optional but recommended
+
+            //@input <glsltype> <varName> <description>
+            @input vec4 viewport The viewport information (right, top, left, bottom)
+            @input vec3 cameraDir The direction of the camera
+            @input vec3 cameraPos The position of the camera
+            @input mat4 worldMatrix The world matrix
+            @input mat4 projectionMatrix The projection matrix
+            @input vec3 modelPosition the vertex position
+            @input float spriteHeight the desired image height in pixel
+
+            //@output <glslType> <varName> <description>
+            @output float scale The constant scale
+        }
+        Input {
+            //all the node inputs
+            //<glslType> <varName>
+            vec4 viewport
+            vec3 cameraDir
+            vec3 cameraPos
+            mat4 worldMatrix
+            mat4 projectionMatrix
+            vec3 modelPosition
+            float spriteHeight 10.0
+        }
+        Output {
+            //all the node outputs
+            //<glslType> <varName>
+            float scale
+        }
+    }
+}

+ 8 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale100.frag

@@ -0,0 +1,8 @@
+void main(){
+	vec4 worldPos = worldMatrix * vec4(modelPosition, 1.0);
+	vec3 dir = worldPos.xyz - cameraPos;
+	float distance = dot(cameraDir, dir);
+	float m11 = projectionMatrix[1][1];
+	float halfHeight = (viewport.w - viewport.y) * 0.5;	
+	scale = ((distance/halfHeight) * spriteHeight)/m11;
+}

+ 57 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/texCoord.j3sn

@@ -0,0 +1,57 @@
+ShaderNodeDefinitions{
+    ShaderNodeDefinition TexCoord {
+        //Vertex/Fragment
+        Type: Vertex
+
+        //Shader GLSL<version>: <Path to shader>
+        Shader GLSL100: Common/MatDefs/ShaderNodes/Common/texCoord100.frag
+
+        Documentation{
+            //type documentation here. This is optional but recommended
+
+            //@input <glsltype> <varName> <description>
+            @input vec2 texCoord The input texture Coord
+            @input vec2 texCoord2 The input texture Coord
+            @input vec2 texCoord3 The input texture Coord
+            @input vec2 texCoord4 The input texture Coord
+            @input vec2 texCoord5 The input texture Coord
+            @input vec2 texCoord6 The input texture Coord
+            @input vec2 texCoord7 The input texture Coord
+            @input vec2 texCoord8 The input texture Coord
+
+            //@output <glslType> <varName> <description>
+            @output vec2 texCoord The input texture Coord
+            @output vec2 texCoord2 The input texture Coord
+            @output vec2 texCoord3 The input texture Coord
+            @output vec2 texCoord4 The input texture Coord
+            @output vec2 texCoord5 The input texture Coord
+            @output vec2 texCoord6 The input texture Coord
+            @output vec2 texCoord7 The input texture Coord
+            @output vec2 texCoord8 The input texture Coord
+        }
+        Input {
+            //all the node inputs
+            //<glslType> <varName>
+            vec2 texCoord
+            vec2 texCoord2
+            vec2 texCoord3
+            vec2 texCoord4
+            vec2 texCoord5
+            vec2 texCoord6
+            vec2 texCoord7
+            vec2 texCoord8
+        }
+        Output {
+            //all the node outputs
+            //<glslType> <varName>
+            vec2 texCoord
+            vec2 texCoord2
+            vec2 texCoord3
+            vec2 texCoord4
+            vec2 texCoord5
+            vec2 texCoord6
+            vec2 texCoord7
+            vec2 texCoord8
+        }
+    }
+}

+ 2 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/texCoord100.frag

@@ -0,0 +1,2 @@
+void main(){
+}

BIN
jme3-core/src/main/resources/Common/Textures/dot.png


+ 9 - 6
jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java

@@ -40,12 +40,15 @@ public class TestArmature extends SimpleApplication {
         Joint root = new Joint("Root_Joint");
         j1 = new Joint("Joint_1");
         j2 = new Joint("Joint_2");
+        Joint j3 = new Joint("Joint_3");
         root.addChild(j1);
         j1.addChild(j2);
+        j2.addChild(j3);
         root.setLocalTranslation(new Vector3f(0, 0, 0.5f));
         j1.setLocalTranslation(new Vector3f(0, 0.0f, -0.5f));
-        j2.setLocalTranslation(new Vector3f(0, 0.0f, -0.2f));
-        Joint[] joints = new Joint[]{root, j1, j2};
+        j2.setLocalTranslation(new Vector3f(0, 0.0f, -0.3f));
+        j3.setLocalTranslation(new Vector3f(0, 0, -0.2f));
+        Joint[] joints = new Joint[]{root, j1, j2, j3};
 
         final Armature armature = new Armature(joints);
         armature.setBindPose();
@@ -64,16 +67,16 @@ public class TestArmature extends SimpleApplication {
         };
         Vector3f[] scales = new Vector3f[]{
                 new Vector3f(1, 1, 1),
-                new Vector3f(2, 2, 2),
+                new Vector3f(1, 1, 2),
                 new Vector3f(1, 1, 1),
         };
         Vector3f[] scales2 = new Vector3f[]{
                 new Vector3f(1, 1, 1),
-                new Vector3f(0.5f, 0.5f, 0.5f),
+                new Vector3f(1, 1, 0.5f),
                 new Vector3f(1, 1, 1),
         };
 
-        JointTrack track1 = new JointTrack(j1, times, null, rotations, null);
+        JointTrack track1 = new JointTrack(j1, times, null, rotations, scales);
         JointTrack track2 = new JointTrack(j2, times, null, rotations, null);
         clip.addTrack(track1);
         clip.addTrack(track2);
@@ -98,7 +101,7 @@ public class TestArmature extends SimpleApplication {
         composer.setCurrentAnimClip("anim");
 
         ArmatureDebugAppState debugAppState = new ArmatureDebugAppState();
-        debugAppState.addArmature(ac, true);
+        debugAppState.addArmature(ac);
         stateManager.attach(debugAppState);
 
         rootNode.addLight(new DirectionalLight(new Vector3f(-1f, -1f, -1f).normalizeLocal()));