Blog

Visualization of a Model Skeleton

Aug 12

Written by:
Friday, August 12, 2011  RssIcon

Here is a code snippet that draws the bones of a 3D character skeleton for debugging (similar to the original SkeletonHelper.DrawBones method). The result looks like this:

Skeleton_Debug_Visualization (Click image to enlarge.)

Here is the code: (Click here to download.)

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


namespace DigitalRune.Animation.Character
{
  public static class MySkeletonHelper
  {
    /// <summary>
    /// Draws the skeleton bones, bone space axes and bone names for debugging. 
    /// </summary>
    /// <param name="skeletonPose">The skeleton pose.</param>
    /// <param name="graphicsDevice">The graphics device.</param>
    /// <param name="effect">
    /// An initialized basic effect instance. BasicEffect.World, BasicEffect.View and 
    /// BasicEffect.Projection must be correctly initialized before this method is called.
    /// </param>
    /// <param name="axisLength">The visible length of the bone space axes.</param>
    /// <param name="spriteBatch"> A SpriteBatch. Can be null to skip text rendering.  </param>
    /// <param name="spriteFont"> A SpriteFont. Can be null to skip text rendering.  </param>
    /// <param name="color">The color for the bones and the bone names.</param>
    public static void DrawBones(this SkeletonPose skeletonPose, GraphicsDevice graphicsDevice,
      BasicEffect effect, float axisLength, SpriteBatch spriteBatch, SpriteFont spriteFont,
      Color color)
    {
      if (skeletonPose == null)
        throw new ArgumentNullException("skeletonPose");
      if (graphicsDevice == null)
        throw new ArgumentNullException("graphicsDevice");
      if (effect == null)
        throw new ArgumentNullException("effect");

      var oldVertexColorEnabled = effect.VertexColorEnabled;
      effect.VertexColorEnabled = true;

      // No font, then we don't need the sprite batch.
      if (spriteFont == null)
        spriteBatch = null;

      if (spriteBatch != null)
        spriteBatch.Begin();

      List<VertexPositionColor> vertices = new List<VertexPositionColor>();

      var skeleton = skeletonPose.Skeleton;
      for (int i = 0; i < skeleton.NumberOfBones; i++)
      {
        // Data of bone i:
        string name = skeleton.GetName(i);
        SrtTransform bonePose = skeletonPose.GetBonePoseAbsolute(i);
        var translation = (Vector3)bonePose.Translation;
        var rotation = (Quaternion)bonePose.Rotation;

        // Draw line to parent joint representing the parent bone.
        int parentIndex = skeleton.GetParent(i);
        if (parentIndex >= 0)
        {
          SrtTransform parentPose = skeletonPose.GetBonePoseAbsolute(parentIndex);
          vertices.Add(new VertexPositionColor(translation, color));
          vertices.Add(new VertexPositionColor((Vector3)parentPose.Translation, color));
        }

        // Add three lines in Red, Green and Blue that visualize the bone space.
        vertices.Add(new VertexPositionColor(translation, Color.Red));
        vertices.Add(new VertexPositionColor(
          translation + Vector3.Transform(Vector3.UnitX, rotation) * axisLength, Color.Red));
        vertices.Add(new VertexPositionColor(translation, Color.Green));
        vertices.Add(new VertexPositionColor(
          translation + Vector3.Transform(Vector3.UnitY, rotation) * axisLength, Color.Green));
        vertices.Add(new VertexPositionColor(translation, Color.Blue));
        vertices.Add(new VertexPositionColor(
          translation + Vector3.Transform(Vector3.UnitZ, rotation) * axisLength, Color.Blue));

        // Draw name.
        if (spriteBatch != null && !string.IsNullOrEmpty(name))
        {
          // Compute the 3D position in view space. Text is rendered near drawn x axis.
          Vector3 textPosition = translation + Vector3.TransformNormal(Vector3.UnitX, bonePose)
                                 * axisLength * 0.5f;
          var textPositionWorld = Vector3.Transform(textPosition, effect.World);
          var textPositionView = Vector3.Transform(textPositionWorld, effect.View);

          // Check if the text is in front of the camera.
          if (textPositionView.Z < 0)
          {
            // Project text position to screen.            
            Vector3 textPositionProjected = graphicsDevice.Viewport.Project(
              textPosition, effect.Projection, effect.View, effect.World);

            spriteBatch.DrawString(spriteFont, name + " " + i,
              new Vector2(textPositionProjected.X, textPositionProjected.Y), color);
          }
        }
      }

      if (spriteBatch != null)
        spriteBatch.End();

      // Draw axis lines in one batch.
      graphicsDevice.DepthStencilState = DepthStencilState.None;
      effect.CurrentTechnique.Passes[0].Apply();
      graphicsDevice.DrawUserPrimitives(PrimitiveType.LineList, vertices.ToArray(), 0,
        vertices.Count / 2);

      effect.VertexColorEnabled = oldVertexColorEnabled;
    }
  }
}


Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel