Improved bone debugger

Hey guys.

Below I posted a coode that improves the BoneDebugger. I made it because it allows me better to see how the bones really look like.
The current debugger does not show it correctly when the child bone is not connected directly to its parent and when bone has no children at all (only a point is shown).

I made the changes that showed full bones’ lines (even for those who have no children) and added a dotted lines between connections of the bones so that we can see the bone is not connected to its parent.

Unfortunately to make this work, the debugger needs to be supplied with the bones’ lengths, because the current bone implementation does not store this data.
To do this I added additional constructors that take a map between the bone index and its length.

Take a look at the code and give me some feedback and if you like it I will make a commit.

SkeletonDebugger.java
[java]
/**

  • 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.

  • @author skye.book
    /
    public class SkeletonDebugger extends Node {
    /
    * The lines of the bones or the wires between their heads. /
    private SkeletonWire wires;
    /
    * The heads and tails points of the bones or only heads if no length data is available. /
    private SkeletonPoints points;
    /
    * The dotted lines between a bone’s tail and the had of its children. Not available if the length data was not provided. */
    private SkeletonInterBoneWire interBoneWires;

    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) {
    this(name, skeleton, null);
    }

    /**

    • Creates a debugger with bone lengths data. If the data is supplied then the wires will show each full bone (from head to tail),
    • the points will display both heads and tails of the bones and dotted lines between bones will be seen.
    • @param name
    •        the name of the debugger's node
      
    • @param skeleton
    •        the skeleton that will be shown
      
    • @param boneLengths
    •        a map between the bone's index and the bone's length
      

    */
    public SkeletonDebugger(String name, Skeleton skeleton, Map<Integer, Float> boneLengths) {
    super(name);

     wires = new SkeletonWire(skeleton, boneLengths);
     points = new SkeletonPoints(skeleton, boneLengths);
    
     this.attachChild(new Geometry(name + "_wires", wires));
     this.attachChild(new Geometry(name + "_points", points));
     if (boneLengths != null) {
         interBoneWires = new SkeletonInterBoneWire(skeleton, boneLengths);
         this.attachChild(new Geometry(name + "_interwires", interBoneWires));
     }
    
     this.setQueueBucket(Bucket.Transparent);
    

    }

    @Override
    public void updateLogicalState(float tpf) {
    super.updateLogicalState(tpf);
    wires.updateGeometry();
    points.updateGeometry();
    interBoneWires.updateGeometry();
    }

    /**

    • @return the skeleton points
      */
      public SkeletonPoints getPoints() {
      return points;
      }

    /**

    • @return the skeleton wires
      */
      public SkeletonWire getWires() {
      return wires;
      }

    /**

    • @return the dotted line between bones (can be null)
      */
      public SkeletonInterBoneWire getInterBoneWires() {
      return interBoneWires;
      }
      }
      [/java]

SkeletonPoints.java
[java]
/**

  • The class that displays either heads of the bones if no length data is supplied or both heads and tails otherwise.

  • @author skye.book
    /
    public class SkeletonPoints extends Mesh {
    /
    * The skeleton to be displayed. /
    private Skeleton skeleton;
    /
    * The map between the bone index and its length. */
    private Map<Integer, Float> boneLengths;

    /**

    • Creates a points with no length data. The points will only show the bone’s heads.
    • @param skeleton
    •        the skeleton that will be shown
      

    */
    public SkeletonPoints(Skeleton skeleton) {
    this(skeleton, null);
    }

    /**

    • Creates a points with bone lengths data. If the data is supplied then the points will show both head and tail of each bone.
    • @param skeleton
    •        the skeleton that will be shown
      
    • @param boneLengths
    •        a map between the bone's index and the bone's length
      

    */
    public SkeletonPoints(Skeleton skeleton, Map<Integer, Float> boneLengths) {
    this.skeleton = skeleton;
    this.setMode(Mode.Points);
    int pointsCount = skeleton.getBoneCount();

     if (boneLengths != null) {
         this.boneLengths = boneLengths;
         pointsCount *= 2;
     }
    
     VertexBuffer pb = new VertexBuffer(Type.Position);
     FloatBuffer fpb = BufferUtils.createFloatBuffer(pointsCount * 3);
     pb.setupData(Usage.Stream, 3, Format.Float, fpb);
     this.setBuffer(pb);
    
     this.setPointSize(7);
     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 head = bone.getModelSpacePosition();

       posBuf.put(head.getX()).put(head.getY()).put(head.getZ());
       if (boneLengths != null) {
           Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneLengths.get(i))));
           posBuf.put(tail.getX()).put(tail.getY()).put(tail.getZ());
       }
      

      }
      posBuf.flip();
      vb.updateData(posBuf);

      this.updateBound();
      }
      }
      [/java]

SkeletonWire.java
[java]
/**

  • The class that displays either wires between the bones’ heads if no length data is supplied and

  • full bones’ shapes otherwise.

  • @author skye.book
    /
    public class SkeletonWire extends Mesh {
    /
    * The number of bones’ connections. Used in non-length mode. /
    private int numConnections;
    /
    * The skeleton to be displayed. /
    private Skeleton skeleton;
    /
    * The map between the bone index and its length. */
    private Map<Integer, Float> boneLengths;

    /**

    • Creates a wire with no length data. The wires will be a connection between the bones’ heads only.
    • @param skeleton
    •        the skeleton that will be shown
      

    */
    public SkeletonWire(Skeleton skeleton) {
    this(skeleton, null);
    }

    /**

    • Creates a wire with bone lengths data. If the data is supplied then the wires will show each full bone (from head to tail).
    • @param skeleton
    •        the skeleton that will be shown
      
    • @param boneLengths
    •        a map between the bone's index and the bone's length
      

    */
    public SkeletonWire(Skeleton skeleton, Map<Integer, Float> boneLengths) {
    this.skeleton = skeleton;

     for (Bone bone : skeleton.getRoots()) {
         this.countConnections(bone);
     }
    
     this.setMode(Mode.Lines);
     int lineVerticesCount = skeleton.getBoneCount();
     if (boneLengths != null) {
         this.boneLengths = boneLengths;
         lineVerticesCount *= 2;
     }
    
     VertexBuffer pb = new VertexBuffer(Type.Position);
     FloatBuffer fpb = BufferUtils.createFloatBuffer(lineVerticesCount * 3);
     pb.setupData(Usage.Stream, 3, Format.Float, fpb);
     this.setBuffer(pb);
    
     VertexBuffer ib = new VertexBuffer(Type.Index);
     ShortBuffer sib = BufferUtils.createShortBuffer(boneLengths != null ? lineVerticesCount : numConnections * 2);
     ib.setupData(Usage.Static, 2, Format.UnsignedShort, sib);
     this.setBuffer(ib);
    
     if (boneLengths != null) {
         for (int i = 0; i < lineVerticesCount; ++i) {
             sib.put((short) i);
         }
     } else {
         for (Bone bone : skeleton.getRoots()) {
             this.writeConnections(sib, bone);
         }
     }
     sib.flip();
    
     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 head = bone.getModelSpacePosition();

       posBuf.put(head.getX()).put(head.getY()).put(head.getZ());
       if (boneLengths != null) {
           Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneLengths.get(i))));
           posBuf.put(tail.getX()).put(tail.getY()).put(tail.getZ());
       }
      

      }
      posBuf.flip();
      vb.updateData(posBuf);

      this.updateBound();
      }

    /**

    • Th method couns the connections between bones.
    • @param bone
    •        the bone where counting starts
      

    */
    private void countConnections(Bone bone) {
    for (Bone child : bone.getChildren()) {
    numConnections++;
    this.countConnections(child);
    }
    }

    /**

    • The method writes the indexes for the connection vertices. Used in non-length mode.
    • @param indexBuf
    •        the index buffer
      
    • @param bone
    •        the bone
      

    */
    private void writeConnections(ShortBuffer indexBuf, Bone bone) {
    for (Bone child : bone.getChildren()) {
    // write myself
    indexBuf.put((short) skeleton.getBoneIndex(bone));
    // write the child
    indexBuf.put((short) skeleton.getBoneIndex(child));

         this.writeConnections(indexBuf, child);
     }
    

    }
    }
    [/java]

And one additional class: SkeletonInterBoneWire.java
[java]
/**

  • 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;

    /**

    • 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) {
    this.skeleton = skeleton;

     for (Bone bone : skeleton.getRoots()) {
         this.countConnections(bone);
     }
    
     this.setMode(Mode.Points);
     this.setPointSize(1);
     this.boneLengths = boneLengths;
    
     VertexBuffer pb = new VertexBuffer(Type.Position);
     FloatBuffer fpb = BufferUtils.createFloatBuffer(POINT_AMOUNT * connectionsAmount * 3);
     pb.setupData(Usage.Stream, 3, Format.Float, fpb);
     this.setBuffer(pb);
    
     this.updateCounts();
    

    }

    /**

    • The method updates the geometry according to the poitions of the bones.
      */
      public void updateGeometry() {
      VertexBuffer vb = this.getBuffer(Type.Position);
      FloatBuffer posBuf = this.getFloatBuffer(Type.Position);
      posBuf.clear();
      for (int i = 0; i < skeleton.getBoneCount(); ++i) {
      Bone bone = skeleton.getBone(i);
      Vector3f parentTail = bone.getModelSpacePosition().add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneLengths.get(i))));

       for (Bone child : bone.getChildren()) {
           Vector3f childHead = child.getModelSpacePosition();
           Vector3f v = childHead.subtract(parentTail);
           float pointDelta = v.length() / POINT_AMOUNT;
           v.normalizeLocal().multLocal(pointDelta);
           Vector3f pointPosition = parentTail.clone();
           for (int j = 0; j < POINT_AMOUNT; ++j) {
               posBuf.put(pointPosition.getX()).put(pointPosition.getY()).put(pointPosition.getZ());
               pointPosition.addLocal(v);
           }
       }
      

      }
      posBuf.flip();
      vb.updateData(posBuf);

      this.updateBound();
      }

    /**

    • Th method couns the connections between bones.
    • @param bone
    •        the bone where counting starts
      

    */
    private void countConnections(Bone bone) {
    for (Bone child : bone.getChildren()) {
    ++connectionsAmount;
    this.countConnections(child);
    }
    }
    }
    [/java]

5 Likes

Would it be worth adding the length of the bones into the implementation?

For me it would be great. It would save me problems with storing it outside the bone. :wink:
But I am not sure if this would be useful data for the application itself.

So how about the code ?
Shall I commit it or rather wait for adding length to the Bone class ? :slight_smile:

@zarch said: Would it be worth adding the length of the bones into the implementation?
Our current bone animation system doesn't need it. Actually Bones in JME are more joints. I don't really want to add this data only for debugging purpose. @Kaelthas I can see that you made the length data optional and the SkeletonDebugger works as before if you do not provide it so IMO it's ok. This class is for debugging purpose only so IMO it should provide as much feature as possible even if the API is a bit clumsy.

OK I made a commit of this feature. Hope it will be useful :slight_smile:

1 Like