Code Tricks and Tools

Already in my life, I have written quite a bit of code. My fated path to programming began back in high school. One spring break, upon discovering that I could make my calculator "do stuff" I pulled out a dust-covered manual and learned the basics of syntax. I was on a ski trip that break and when I wasn't earning my well deserved nickname "Yard Sale Boy" I was furiously punching keys on my TI-83+.

Naturally, some of the first things I programmed were games. Games that would have made Shigeru Miyamoto weep for the despair of all mankind... But I did get better.

For those of you that have not yet lost interest, I'd like to make my humble offering to the developer community. I love making tools and apps that let me make sweet, sweet candy-coated graphics. Hopefully I'll continue updating this site with new tid bits from time to time.

In addition to the content on this page. You can check out my code for laser pointer tracking and collision detection and handling. This code is not complete but can serve as a good reference for developing your own applications with these functions.

Environment Map Maker

EnvironmentMapMaker.zip Environment Map Maker application

In my travels through game development and XNA I've frequently come across the need to create pre-generated environment maps for my 3D scenes. I have never been able to find any utility for simplifying this process so I created my own little app. If you share my pain please give it a try.

Take a look at the readme for a thorough walkthrough on how to use the application. To make full use of this utility you'll need Maya 7.0 or later. Feel free to send me bug reports or suggestions using my contact form.

MEL Utility Library

MEL_Library.zip

This zip file contains a dozen MEL script files each of which contains a collection of utility functions. Some of these functions are simple wrappers to built-in MEL commands; however I find it easier to use the functions because it is easier to remember the syntax and makes for cleaner looking code. There is no documentation available but everything is commented and should be fairly easy to incorporate into your own MEL creations.

Scene Hierarchy Classes

SceneGraph.zip

These three classes are the basic tools I use in my XNA framework to build a scene graph. I've simplified them to be easily adapted to your own preferred methods of management and rendering. These classes could be easily converted to other pipelines besides XNA simply by replacing the matrix methods used in the TransformNode and BillboardTransform classes.

SceneItem - SceneItem is a generic scene node that serves as the basic building block for creating a 3D scene graph. A scene item can be transformed by parenting it to a TransformNode.

(show)
//SceneItem class...

public class SceneItem
{
       /*
        * CLASS PROPERTIES
        */
    /// <summary>
    /// visible state of the scene item
    /// </summary>
    protected bool visible;
    /// <summary>
    /// the scene item's parent (or null if none)
    /// </summary>
    protected SceneItem parent;
    /// <summary>
    /// the scene item's children
    /// </summary>
    protected List<SceneItem> children;

    /// <summary>
    /// the transfromation matrix that describes the
    /// translation, rotation and scale of this scene item
    /// </summary>
    private Matrix worldMatrix;
    /// <summary>
    /// indicates if the inverse matrix property is up to date
    /// </summary>
    private bool invNeedsUpdate;
    /// <summary>
    /// the inverse matrix of the world matrix
    /// </summary>
    private Matrix invWorld;

       /*
        * CONSTRUCTORS
        */
    /// <summary>
    /// main constructor for a scene item
    /// </summary>
    public SceneItem()
    {
        init();
    }

    private void init()
    {
        visible = true;
        parent = null;
        children = new List<SceneItem>();

        worldMatrix = Matrix.Identity;
        invWorld = Matrix.Identity;

        invNeedsUpdate = false;
    }
    
       /*
        * UNLOADING
        */
    /// <summary>
    /// unloads this scene item and all of its children
    /// </summary>
    /// <param name="disposeResources"></param>
    public virtual void Unload(bool disposeResources)
    {
        //remove from parent's hierarchy
        if (parent != null)
            parent.RemoveChild(this);

        if (children != null)
        {
            //unload all children...
            SceneItem[] childArray = children.ToArray();
            for (int i = 0; i < childArray.Length; i++)
                childArray[i].Unload(disposeResources);
            children.Clear();
        }
        children = null;
        parent = null;
        visible = false;
    }

       /*
        * ENCAPSULATORS
        */
    /// <summary>
    /// gets the current transformed position of this scene
    /// item in world coordinates
    /// </summary>
    public virtual Vector3 WorldPosition
    {
        get { return worldMatrix.Translation; }
    }

    /// <summary>
    /// gets /sets the world matrix
    /// transformation for this SceneItem
    /// </summary>
    public virtual Matrix WorldMatrix
    {
        get { return worldMatrix; }
        set
        {
            worldMatrix = value;
            invNeedsUpdate = true;

            //children inherit world matrix property
            foreach (SceneItem child in children)
                child.WorldMatrix = worldMatrix;
        }
    }

    /// <summary>
    /// the inverse of the world matrix
    /// </summary>
    public virtual Matrix WorldInverse
    {
        get
        {
            if (invNeedsUpdate)
            {
                invWorld = Matrix.Invert(worldMatrix);
                invNeedsUpdate = false;
            }
            return invWorld;
        }
    }

    /// <summary>
    /// indicates if this scene item is the child of a
    /// TransformNode object.
    /// </summary>
    public bool HasParentTransform
    {
        get { return (parent is TransformNode); }
    }

    /// <summary>
    /// gets the direct parent of this
    /// scene item as a TransformNode.
    /// Or null if there is not one.
    /// </summary>
    public virtual TransformNode ParentTransform
    {
        get { return (parent as TransformNode); }
    }

       /*
        * POSITION AND NORMAL TRANSFORMATIONS
        */
    /// <summary>
    /// transforms a position from the local space of this SceneItem node to the global space
    /// </summary>
    /// <param name="localPos">a position in local space</param>
    /// <returns>a position in global space</returns>
    public Vector3 LocalToGlobal(Vector3 localPos)
    {
        return Vector3.Transform(localPos, worldMatrix);
    }

    /// <summary>
    /// transforms a position from a global space to the local space of this SceneItem node
    /// </summary>
    /// <param name="globalPos">a position in global space</param>
    /// <returns>a position in local space</returns>
    public Vector3 GlobalToLocal(Vector3 globalPos)
    {
        return Vector3.Transform(globalPos, this.WorldInverse);
    }

    /// <summary>
    /// transforms a normal from the local space of this SceneItem node to the global space
    /// </summary>
    /// <param name="localNormal">a normal in local space</param>
    /// <returns>a normal in global space</returns>
    public Vector3 LocalToGlobalNormal(Vector3 localNormal)
    {
        return Vector3.TransformNormal(localNormal, worldMatrix);
    }

    /// <summary>
    /// transforms a normal from a global space to the local space of this SceneItem node
    /// </summary>
    /// <param name="globalPos">a normal in global space</param>
    /// <returns>a normal in local space</returns>
    public Vector3 GlobalToLocalNormal(Vector3 globalNormal)
    {
        return Vector3.TransformNormal(globalNormal, this.WorldInverse);
    }

    /// <summary>
    /// transforms a position from the local space of on SceneItem node to another
    /// </summary>
    /// <param name="localPos">the local position in the space of the SceneItem node "localSpace"</param>
    /// <param name="localSpace">the SceneItem node that has the "localPos"</param>
    /// <param name="targetSpace">the returned position will be in the local space of this SceneItem node</param>
    /// <returns>the position in the local space of "targetSpace"</returns>
    public static Vector3 LocalToLocal(Vector3 localPos,
			SceneItem localSpace, SceneItem targetSpace)
    {
        return targetSpace.GlobalToLocal(localSpace.LocalToGlobal(localPos));
    }

    /// <summary>
    /// transforms a position from the local space of one SceneItem node to another
    /// </summary>
    /// <param name="localPos">the local position in the space of the SceneItem node "localSpace"</param>
    /// <param name="currentSpace">the SceneItem node that has the "localPos"</param>
    /// <param name="targetSpace">the returned position will be in the local space of this SceneItem node</param>
    /// <returns>the position in the local space of "targetSpace"</returns>
    public static Vector3 LocalToLocalNormal(Vector3 localDirection,
				SceneItem currentSpace, SceneItem targetSpace)
    {
        return targetSpace.GlobalToLocalNormal(
		currentSpace.LocalToGlobalNormal(localDirection));
    }

    /// <summary>
    /// gets the matrix that will transform points
    /// from the current space to the target space
    /// </summary>
    /// <param name="currentSpace">the SceneItem node that is the current local space</param>
    /// <param name="targetSpace">the trnasformable node that is the target local space</param>
    /// <returns>a matrix that will transform points or directions from the current space to the target space</returns>
    public static Matrix GetTransform(SceneItem currentSpace, SceneItem targetSpace)
    {
        return (targetSpace.WorldMatrix * currentSpace.WorldInverse);
    }

    /// <summary>
    /// gets / sets the visiblity of
    /// this item and its children
    /// </summary>
    public bool Visible
    {
        get { return visible; }
        set { visible = value; }
    }


       /*
        * HIERARCHY CREATION / MANIPULATION
        */
    /// <summary>
    /// gets or sets the parent of this scene item
    /// </summary>
    public virtual SceneItem Parent
    {
        get { return parent; }
        set
        {
            if (value == null)
                parent = value;
            else
                value.AddChild(this);
        }
    }

    /// <summary>
    /// add achild to this scene item
    /// </summary>
    /// <param name="newChild">the child to add</param>
    public virtual void AddChild(SceneItem newChild)
    {
        if (newChild == this)
            throw new ArgumentException("Attempted to parent a scene item to itself.");
        if (newChild.descendant(this, 1, -1))
            throw new ArgumentException("Cannot make a scene item as one
								of it's own descendants");
        children.Add(newChild);

        //remove from old parent
        if (newChild.HasParent)
            newChild.parent.RemoveChild(newChild);

        //add to hierarchy
        newChild.parent = this;
        newChild.WorldMatrix = this.WorldMatrix;
    }

    /// <summary>
    /// remove all children from this scene item
    /// </summary>
    public virtual void RemoveAllChildren()
    {
        while (children.Count > 0)
        {
            //remove children from the top down
            RemoveChild(children[children.Count - 1]);
        }
        children.Clear();
    }

    /// <summary>
    /// remove the specified child from this scene item
    /// </summary>
    /// <param name="xChild">the child to be remove</param>
    public virtual void RemoveChild(SceneItem xChild)
    {
        if (children.Contains(xChild))
        {
            xChild.parent = null;
            xChild.WorldMatrix = Matrix.Identity;
            children.Remove(xChild);
        }
    }

    /// <summary>
    /// removes the child at the specified index from this scene item
    /// </summary>
    /// <param name="index">the index of the child to remove</param>
    public virtual void RemoveChildAt(int index)
    {
        if (index >= 0 && index < children.Count)
            this.RemoveChild(children[index]);
    }

    /// <summary>
    /// gets the child at the specified index
    /// </summary>
    /// <param name="index">the index in the children list</param>
    /// <returns>the child, or null if index is invalid</returns>
    public virtual SceneItem GetChildAt(int index)
    {
        if (index >= 0 && index < this.children.Count)
            return children[index];
        return null;
    }

    /// <summary>
    /// returns true if the specified scene item is
    /// an immediate child of this scene item
    /// </summary>
    public virtual bool Contains(SceneItem curChild)
    {
        return children.Contains(curChild);
    }

    /// <summary>
    /// returns true if the specified scene item is a descendant of this scene item
    /// </summary>
    /// <param name="startDepth">setting this value to 1 will cause the check to skip the first
    /// level of the hierarchy, 2 will skip the first two levels and so on</param>
    /// <param name="maxDepth">the maximum depth into the hierarchy past the start depth
    /// to check or -1 to check entire hirearchy</param>
    protected virtual bool descendant(SceneItem item, int startDepth, int maxDepth)
    {
        if (startDepth <= 0 && this == item)
            return true;

        startDepth--;
        if (maxDepth >= 0 && startDepth < -maxDepth)
            return false;

        for (int i = 0; i < children.Count; i++)
        {
            if (children[i].descendant(item, startDepth, maxDepth))
                return true;
        }
        return false;
    }

       /*
        * HIERARCHY ACCESORS
        */
    /// <summary>
    /// gets the list of children for this node
    /// </summary>
    public List<SceneItem> Children
    {
        get { return this.children; }
    }

    /// <summary>
    /// Gets the number of immediate
    /// children of this scene item
    /// </summary>
    public int Nuchildren
    {
        get { return children.Count; }
    }

    /// <summary>
    /// gets the total number of children,
    /// in the hierachy of this scene item
    /// </summary>
    public int NumTotalChildren
    {
        get
        {
            int totalChildren = 0;
            foreach(SceneItem child in children)
                totalChildren += child.NumTotalChildren;
            return totalChildren;
        }
    }

    /// <summary>
    /// gets true if this scene item has a parent node
    /// </summary>
    public bool HasParent
    {
        get { return (parent != null); }
    }

       /*
        * SIBLING SORTING
        */
    /// <summary>
    /// swap the children at the specified indeces
    /// </summary>
    /// <param name="index1">the index of the first child in the swap</param>
    /// <param name="index2">the index of the second child in the swap</param>
    public virtual void SwapChildrenAt(int index1, int index2)
    {
        if (index1 >= 0 && index1 < children.Count && index2 >= 0 &&
							index2 < children.Count)
        {
            SceneItem temp = children[index1];
            children[index1] = children[index2];
            children[index2] = temp;
        }
    }

    /// <summary>
    /// swap the render order of the two specified children
    /// </summary>
    /// <param name="item1">the first child in the swap</param>
    /// <param name="item2">the second child in the swap</param>
    public virtual void SwapChildren(SceneItem item1, SceneItem item2)
    {
        int index1 = children.IndexOf(item1);
        int index2 = children.IndexOf(item2);

        if (index1 >= 0 && index2 >= 0)
        {
            children[index1] = item2;
            children[index2] = item1;
        }
    }

    /// <summary>
    /// sets the specified child to be drawn last in
    /// the render order effectively bringing it to the front 
    /// </summary>
    public virtual void BringToFront(SceneItem curChild)
    {
        if (children.Contains(curChild))
        {
            int curIndex = children.IndexOf(curChild);
            if (curIndex < children.Count - 1)
            {
                children[curIndex] = children[children.Count - 1];
                children[children.Count - 1] = curChild;
            }
        }
    }

    /// <summary>
    /// sets the specified child to be drawn first in
    /// the render order effectively send it to the back
    /// </summary>
    public virtual void SendToBack(SceneItem curChild)
    {
        if (children.Contains(curChild))
        {
            int curIndex = children.IndexOf(curChild);
            if (curIndex > 0)
            {
                children[curIndex] = children[0];
                children[0] = curChild;
            }
        }
    }

    /// <summary>
    /// bring this item to the front
    /// of its parent's display order
    /// </summary>
    public virtual void BringToFront()
    {
        if (this.HasParent)
            parent.BringToFront(this);
    }

    /// <summary>
    /// send this item to the back of
    /// its parent's display order
    /// </summary>
    public virtual void SendToBack()
    {
        if (this.HasParent)
            parent.SendToBack(this);
    }

      /*
       * UPDATE AND RENDER
       */
    /// <summary>
    /// updates this node and all its children
    /// </summary>
    public virtual void Update(Matrix viewMatrix, Matrix projMatrix)
    {
        foreach (SceneItem child in children)
            child.Update(viewMatrix, projMatrix);
    }

    /// <summary>
    /// Renders this object and all its children if visibiltiy is on.
    /// </summary>
    public virtual void RenderChildren()
    {
        if (visible)
        {
            //render the object content itself
            Render();

            //render any children of this object
            foreach (SceneItem child in children)
                child.RenderChildren();
        }
    }

    /// <summary>
    /// Renders the content of just this scene item (not its children).
    /// This method is called by the RenderChild method if the object
    /// is visible.
    /// </summary>
    public virtual void Render()
    {
        //overwritten by any class with content to render
    }
}

TransformNode - The transform node uses the XNA matrix struct to create cumulative similarity transforms. This class provides a variety of encapsulators to manipulate translation, rotation and scale.

(show)
//TransformNode class...

/// <summary>
/// Euler rotation axis order.
/// </summary>
public enum EulerRotateOrder
{
    XYZ,
    XZY,
    YXZ,
    YZX,
    ZXY,
    ZYX
}

public class TransformNode : SceneItem
{
    /// <summary>
    /// the ratio that converts radians to degrees
    /// </summary>
    public const float RADIANS_TO_DEGREES = 57.2957795f;
    /// <summary>
    /// the ratio that converts degrees to radians
    /// </summary>
    public const float DEGREES_TO_RADIANS = 0.0174532925f;

    /// <summary>
    /// the transformation matrix added in the
    /// hierarchy by this TransformNode
    /// </summary>
    protected Matrix mTransformation;
    /// <summary>
    /// the transformation added by the
    /// parent of this node
    /// </summary>
    protected Matrix mParentMatrix;

    //current translation
    protected EulerRotateOrder rotateOrder; //euler rotaion order
    protected Vector3 curRotation; //current rotation
    protected Vector3 curScale; //current scale

    //individual transformations
    protected Matrix translateMat; //translation matrix
    protected Matrix rotateMat; //rotation matrix
    protected Matrix scaleMat; //scale matrix

    /// <summary>
    /// constructor to initialze the transform node with
    /// the given name and rotation order
    /// </summary>
    /// <param name="newOrder">the euler rotation order</param>
    public TransformNode(EulerRotateOrder newOrder)
        : base()
    {
        rotateOrder = newOrder;

        init();
    }

    /// <summary>
    /// constructor to initialze the transform node with the
    /// given name and the specified transformation
    /// </summary>
    /// <param name="newTranslate">new translate offset</param>
    /// <param name="newRotate">new rotation (X,Y,Z indicate offset from 0 measured in radians)</param>
    /// <param name="newScale">new scale factor (no scale is (1,1,1)</param>
    /// <param name="newOrder">new euler rotation order</param>
    public TransformNode(Vector3 newTranslate, Vector3 newRotate,
        Vector3 newScale, EulerRotateOrder newOrder)
        : base()
    {
        rotateOrder = newOrder;
        curRotation = newRotate;
        curScale = newScale;

        translateMat = Matrix.CreateTranslation(newTranslate);
        scaleMat = Matrix.CreateScale(curScale);

        mTransformation = Matrix.Identity; //local transform

        updateRotation();
    }

    /// <summary>
    /// sets this transformation matrix to be the identity matrix
    /// </summary>
    private void init()
    {
        curRotation = Vector3.Zero;
        curScale = Vector3.One;

        //initialize to having no transformation
        mTransformation = Matrix.Identity;
        translateMat = Matrix.Identity;
        rotateMat = Matrix.Identity;
        scaleMat = Matrix.Identity;
    }

      /*
       * UPDATE TRANSFORMATIONS
       */
    /// <summary>
    /// update rotation using the Euler transforms
    /// </summary>
    protected virtual void updateRotation()
    {
        switch (rotateOrder)
        {
            case EulerRotateOrder.XYZ:
                rotateMat = Matrix.CreateRotationX(curRotation.X)
					* Matrix.CreateRotationY(curRotation.Y)
					* Matrix.CreateRotationZ(curRotation.Z);
                break;
            case EulerRotateOrder.XZY:
                rotateMat = Matrix.CreateRotationX(curRotation.X)
					* Matrix.CreateRotationZ(curRotation.Z)
					* Matrix.CreateRotationY(curRotation.Y);
                break;
            case EulerRotateOrder.YXZ:
                rotateMat = Matrix.CreateRotationY(curRotation.Y)
					* Matrix.CreateRotationX(curRotation.X)
					* Matrix.CreateRotationZ(curRotation.Z);
                break;
            case EulerRotateOrder.YZX:
                rotateMat = Matrix.CreateRotationY(curRotation.Y)
					* Matrix.CreateRotationZ(curRotation.Z)
					* Matrix.CreateRotationX(curRotation.X);
                break;
            case EulerRotateOrder.ZXY:
                rotateMat = Matrix.CreateRotationZ(curRotation.Z)
					* Matrix.CreateRotationX(curRotation.X)
					* Matrix.CreateRotationY(curRotation.Y);
                break;
            case EulerRotateOrder.ZYX:
                rotateMat = Matrix.CreateRotationZ(curRotation.Z)
					* Matrix.CreateRotationY(curRotation.Y)
					* Matrix.CreateRotationX(curRotation.X);
                break;
        }
        updateTranformation();
    }

    /// <summary>
    /// update the local transform matrix by combining the scale, rotation and translation matrices
    /// </summary>
    protected void updateTranformation()
    {
        //remove identity matrix from transformation product
        mTransformation = scaleMat * rotateMat * translateMat;
        updateCumulative();
    }

    /// <summary>
    /// gets the cumulative transformation matrix
    /// for this transform node
    /// </summary>
    public Matrix Transformation
    {
        get { return mTransformation; }
    }

    /// <summary>
    /// sets the world matrix of all child nodes to be
    /// Transformation * ParentMatrix
    /// </summary>
    private void updateCumulative()
    {
        base.WorldMatrix = mTransformation * mParentMatrix;
    }

    /// <summary>
    /// gets / sets the world transformation matrix
    /// </summary>
    /// <remarks>set the world matrix is really only setting
    /// the ParentMatrix property of the TransformNode. Children
    /// of this node will receive the cumulative world matrix of
    /// Transformation * ParentMatrix</remarks>
    public override Matrix WorldMatrix
    {
        get { return base.WorldMatrix; }
        set
        {
            mParentMatrix = value;
            updateCumulative();
        }
    }

      /*
       * RESET TRANSFORMATIONS
       */
    /// <summary>
    /// reset to no translation
    /// </summary>
    public void resetTranslate()
    {
        translateMat = Matrix.Identity;
        updateTranformation();
    }

    /// <summary>
    /// reset to no rotation
    /// </summary>
    public void resetRotate()
    {
        curRotation = Vector3.Zero;
        rotateMat = Matrix.Identity;
        updateTranformation();
    }

    /// <summary>
    /// reset to no scaling
    /// </summary>
    public void resetScale()
    {
        curScale = Vector3.One;
        scaleMat = Matrix.Identity;
        updateTranformation();
    }

      /*
       * TRANSLATION
       */
    /// <summary>
    /// offset translation by specified vector
    /// </summary>
    /// <param name="translate">amout to translate the transformation</param>
    public void Translate(Vector3 translate)
    {
        translateMat = Matrix.CreateTranslation(translateMat.Translation + translate);
        updateTranformation();
    }

    /// <summary>
    /// alternate name for position
    /// </summary>
    public Vector3 Translation
    {
        get { return this.Position; }
        set { this.Position = value; }
    }

    /// <summary>
    /// get / sets the position from
    /// the origin of this node
    /// </summary>
    /// <param name="translate"></param>
    public Vector3 Position
    {
        get { return translateMat.Translation; }
        set
        {
            translateMat = Matrix.CreateTranslation(value);
            updateTranformation();
        }
    }

    /// <summary>
    /// gets or sets the X translation of
    /// this transform node
    /// </summary>
    public float X
    {
        get { return translateMat.Translation.X; }
        set
        {
            translateMat = Matrix.CreateTranslation(
			new Vector3(value, translateMat.Translation.Y,
			translateMat.Translation.Z));

            updateTranformation();
        }
    }
    /// <summary>
    /// gets or sets the Y translation of this transform node
    /// </summary>
    public float Y
    {
        get { return translateMat.Translation.Y; }
        set
        {
            translateMat = Matrix.CreateTranslation(
			new Vector3(translateMat.Translation.X,
                                    value, translateMat.Translation.Z));

            updateTranformation();
        }
    }

    /// <summary>
    /// gets or sets the Z translation of this transform node
    /// </summary>
    public float Z
    {
        get { return translateMat.Translation.Z; }
        set
        {
            translateMat = Matrix.CreateTranslation(
			new Vector3(translateMat.Translation.X,
					translateMat.Translation.Y, value));

            updateTranformation();
        }
    }

      /*
       * ROTATIONS
       */
    /// <summary>
    /// offset rotation of node around X,Y, and Z axis
    /// using a standard Euler transformation
    /// </summary>
    /// <param name="rad"></param>
    public virtual void Rotate(Vector3 rad)
    {
        curRotation = curRotation + rad;
        updateRotation();
    }

    /// <summary>
    /// rotates around the X axis by the
    /// specified number of radians
    /// </summary>
    /// <param name="rad"></param>
    public virtual void RotateX(float rad)
    {
        curRotation.X += rad;
        updateRotation();
    }

    /// <summary>
    /// rotates around the Y axis by the
    /// specified number of radians
    /// </summary>
    /// <param name="rad"></param>
    public virtual void RotateY(float rad)
    {
        curRotation.Y += rad;
        updateRotation();
    }

    /// <summary>
    /// rotates around the Z axis by the
    /// specified number of radians
    /// </summary>
    /// <param name="rad"></param>
    public virtual void RotateZ(float rad)
    {
        curRotation.Z += rad;
        updateRotation();
    }

    /// <summary>
    /// get / sets the amount of rotation for the
    /// transform as Euler rotations in radians
    /// </summary>
    public virtual Vector3 Rotation
    {
        get { return curRotation; }
        set
        {
            curRotation = value;
            updateRotation();
        }
    }

    /// <summary>
    /// get / sets the amount of rotation for the
    /// transform as Euler rotations in degrees
    /// </summary>
    public virtual Vector3 RotationDegrees
    {
        get
        {
            Vector3 degRotation;
            degRotation.X = TransformNode.RADIANS_TO_DEGREES * curRotation.X;
            degRotation.Y = TransformNode.RADIANS_TO_DEGREES * curRotation.Y;
            degRotation.Z = TransformNode.RADIANS_TO_DEGREES * curRotation.Z;
            return degRotation;
        }
        set
        {
            curRotation.X = TransformNode.DEGREES_TO_RADIANS * value.X;
            curRotation.Y = TransformNode.DEGREES_TO_RADIANS * value.Y;
            curRotation.Z = TransformNode.DEGREES_TO_RADIANS * value.Z;

            updateRotation();
        }
    }

    /// <summary>
    /// get / sets the current rotation for the
    /// node around the X axis in radians
    /// </summary>
    public virtual float RotationX
    {
        get { return curRotation.X; }
        set
        {
            curRotation.X = value;
            updateRotation();
        }
    }
    
    /// <summary>
    /// get / sets the current rotation for the
    /// node around the Y axis in radians
    /// </summary>
    public virtual float RotationY
    {
        get { return curRotation.Y; }
        set
        {
            curRotation.Y = value;
            updateRotation();
        }
    }
    
    /// <summary>
    /// get / sets the current rotation for the 
    /// node around the Z axis in radians
    /// </summary>
    public virtual float RotationZ
    {
        get { return curRotation.Z; }
        set
        {
            curRotation.Z = value;
            updateRotation();
        }
    }
    
    /// <summary>
    /// rotates around an arbitrary axis, 
    /// WARNING: using this method will invalidate the
    /// use of other rotation methods
    /// </summary>
    /// <param name="axis">the axis to rotate around</param>
    /// <param name="deg">the angle in radians to rotate</param>
    public virtual void RotateAxis(Vector3 axis, float deg)
    {
        rotateMat = Matrix.Identity * Matrix.CreateFromAxisAngle(axis, deg) * rotateMat;
        updateTranformation();
    }

      /*
       * SCALING
       */
    /// <summary>
    /// modify scale factor by specified amount
    /// </summary>
    /// <param name="scale"></param>
    public void Scale(Vector3 scale)
    {
        curScale.X = scale.X * curScale.X;
        curScale.Y = scale.Y * curScale.Y;
        curScale.Z = scale.Z * curScale.Z;
        scaleMat = Matrix.CreateScale(curScale);
        updateTranformation();
    }

    /// <summary>
    /// get / sets the scale vector for the node
    /// </summary>
    /// <param name="translate"></param>
    public Vector3 CurrentScale
    {
        get { return curScale; }
        set
        {
            curScale = value;
            scaleMat = Matrix.Identity * Matrix.CreateScale(curScale);
            updateTranformation();
        }
    }

      /*
       * ENCAPSULATORS
       */
    /// <summary>
    /// gets / sets the euler rotation order
    /// </summary>
    public EulerRotateOrder RotateOrder
    {
        get { return rotateOrder; }
        set
        {
            rotateOrder = value;
            updateRotation();
        }
    }

    /// <summary>
    /// the translation part of the transformation matrix
    /// </summary>
    public Matrix TranslationMatrix
    {
        get { return translateMat; }
    }
    
    /// <summary>
    /// the scale part of the transformation matrix
    /// </summary>
    public Matrix ScaleMatrix
    {
        get { return scaleMat; }
    }
    
    /// <summary>
    /// the rotation part of the transformation matrix
    /// </summary>
    public Matrix RotationMatrix
    {
        get { return rotateMat; }
    }
}

BillboardTransform - A billboarding transform node that tracks another SceneItem's position. The primary use of this class is for creating 3D sprites.

(show)
//BillboardTransform class...

public class BillboardTransform : TransformNode
{
    /// <summary>
    /// the default up direction to use when
    /// calculating the billboard facing direction
    /// </summary>
    public static Vector3 DEFAULT_UP = Vector3.UnitY;
    /// <summary>
    /// the up direction to use if forward axis == DEFAULT_UP
    /// </summary>
    public static Vector3 ALTERNATE_UP = Vector3.UnitX;

     /*
      * CLASS PROPERTIES
      */
    // the target the billboard will track
    protected SceneItem mTrackTarget;

    // the position of the tracker the last
    // time the billboard matrix was updated
    private Vector3 lastTrackerPosition;
    // the position of this transform node
    // the last time the billboard matrix was updated
    private Vector3 lastBillboardPosition;

    // a rotation matrix that can rotate
    // the billboard around its forward axis
    protected Matrix axisRotationMat;
    // the amoutn of rotation to apply around the forward axis
    protected float axisRotation;
    // the constraining up vector that orients the billboard
    protected Vector3 upVector;

     /*
      * CONSTRUCTOR
      */
    /// <summary>
    /// Basic constructor for a new billboard transform.
    /// </summary>
    /// <param name="newTrackTarget">The new target the billboard
    /// transform should track. Typically this is the camera.</param>
    public BillboardTransform(TransformNode newTrackTarget)
        : base(EulerRotateOrder.XYZ)
    {
        mTrackTarget = newTrackTarget;
        init();
    }

    private void init()
    {
        upVector = BillboardTransform.DEFAULT_UP;

        axisRotationMat = Matrix.Identity;
        lastTrackerPosition = Vector3.Zero;
        lastBillboardPosition = Vector3.Zero;
        axisRotation = 0;
    }

     /*
      * UPDATE ROTATION MATRIX
      */
    /// <summary>
    /// updates the rotation matrix to
    /// apply the current billboarding transform
    /// </summary>
    protected override void updateRotation()
    {
        if (mTrackTarget == null)
        {
            rotateMat = Matrix.Identity;
            return;
        }
        lastTrackerPosition = mTrackTarget.WorldPosition;
        lastBillboardPosition = this.WorldPosition;

        //determine billboard matrix
        Matrix lookAt = Matrix.Identity;
        lookAt.Forward = Vector3.Normalize(lastTrackerPosition - lastBillboardPosition);

        Vector3 up = upVector;
        if (up == lookAt.Forward)
            up = BillboardTransform.ALTERNATE_UP;

        lookAt.Right = Vector3.Normalize(Vector3.Cross(lookAt.Forward, up));
        lookAt.Up = Vector3.Cross(lookAt.Forward, lookAt.Right);

        //add axis rotation
        rotateMat = axisRotationMat * lookAt;
        base.updateTranformation();
    }


     /*
      * ENCAPSULATORS
      */
    /// <summary>
    /// The target which the billboard will track. Typically
    /// this will be the camera object though any transform
    /// node can be tracked.
    /// </summary>
    public SceneItem TrackTarget
    {
        get { return mTrackTarget; }
        set
        {
            mTrackTarget = value;
            lastTrackerPosition = Vector3.Zero;
            UpdateBillboard();
        }
    }
    /// <summary>
    /// the up direction to use in orienting the billboard
    /// </summary>
    public Vector3 UpVector
    {
        get { return upVector; }
        set { upVector = value; }
    }

    /// <summary>
    /// the rotation of the billboard around
    /// its forward axis measured in radians
    /// </summary>
    public virtual float AxisRotation
    {
        get { return axisRotation; }
        set
        {
            axisRotation = value;
            axisRotationMat = Matrix.CreateRotationZ(axisRotation);
            updateRotation();
        }
    }

     /*
      * UPDATE
      */
    /// <summary>
    /// updates the billboard transform if its position or
    /// its tracking target's position has changed
    /// </summary>
    public void UpdateBillboard()
    {
        if (mTrackTarget != null)
        {
            if (mTrackTarget.WorldPosition != lastTrackerPosition ||
                this.WorldPosition != lastBillboardPosition)
            {
                updateRotation();
            }
        }
    }

    public override void Update(Matrix viewMatrix, Matrix projMatrix)
    {
        base.Update(viewMatrix, projMatrix);
        UpdateBillboard();
    }

     /*
      * ROTATIONS OVERRIDE
      */
    // rotation overrides omitted
 
}