Sgt. Conker We are "absolutely fine"

28Oct/093

Article : Creating A Scene-Graph in XNA

The Basics

Constructing the Tree

As previously mentioned, a scene-graph is a tree structure. This tree structure will be represented by two classes: SceneNodeCollection and SceneNode. A third class, IController, will be used later to animate the nodes.


    public class SceneNodeCollection : ICollection<scenenode>
    {
    [This code simply implements a standard collection and is omitted]
    }

    public interface IController
    {
        void UpdateSceneNode(SceneNode node, GameTime gameTime);
    }

    public class SceneNode
    {
        // Fields

        SceneNodeCollection children = new SceneNodeCollection();
        Vector3 position = Vector3.Zero;
        Vector3 offset = Vector3.Zero;
        Quaternion rotation = Quaternion.Identity;
        bool visible = true;
        Matrix absoluteTransform = Matrix.Identity;
        IController controller;

        // Properties

        public SceneNodeCollection Children
        {
            get { return children; }
        }

        public Vector3 Position
        {
            get { return position; }
            set { position = value; }
        }

        public Vector3 Offset
        {
            get { return offset; }
            set { offset = value; }
        }

        public Quaternion Rotation
        {
            get { return rotation; }
            set { rotation = value; }
        }

        public bool Visible
        {
            get { return visible; }
            set { visible = value; }
        }

        public BoundingSphere BoundingSphere
        {
            get { return GetBoundingSphere(); }
        }

        public Matrix AbsoluteTransform
        {
            get { return absoluteTransform; }
            set { absoluteTransform = value; }
        }

        public IController Controller
        {
            get { return controller; }
            set { controller = value; }
        }

        // Default Constructor

        public SceneNode()
        {
        }

        //Methods

        protected virtual BoundingSphere GetBoundingSphere()
        {
            return new BoundingSphere(center, 0);
        }

        public virtual void Update(SceneGraph sceneGraph)
        {
            if (controller != null)
                controller.UpdateSceneNode(this, sceneGraph.GameTime);
        }

        public virtual void Draw(SceneGraph sceneGraph)
        {
        }
    }

The SceneNodeCollection class implements a standard collection. This class could be replaced by a List class; however, coding standards discourage the use of List as a publicly exposed item.

The SceneNode class implements the leafs of the tree. Each SceneNode maintains a list of children as a SceneNodeCollection. This allows the hiearchial nature of the scene graph to be expressed. Position and Rotation properties on the class allow the location and orientation of the leaf to be expressed in coordinates relative to the parent. The world space coordinates are then calculated from these values by the scene graph. The BoundingSphere property allows the scene graph to determine if the node should be draw by testing it against the view frustum. Finally, the Update method allows the scene graph to change its internal state, and the Draw method allows it to display itself.

Creating visuals

Now that the basis for the scene graph is constructed, a scene node that displays an XNA model will be added.

 public class ModelSceneNode : SceneNode
    {
        // Fields

        Model model;
        BoundingSphere modelSphere;

        // Properties
        public Model Model
        {
            get { return model; }
            set
            {
                model = value;

                if (model == null)
                    modelSphere = new BoundingSphere(Center, 0);
                else
                    CalculateBoundingSphere();
            }
        }

        // Constructors

        public ModelSceneNode()
        {
        }

        public ModelSceneNode(Model model)
        {
            this.model = model;
            CalculateBoundingSphere();
        }

        // Methods

        private void CalculateBoundingSphere()
        {
            //Calculate the bounding sphere for the entire model

            modelSphere = new BoundingSphere();

            foreach (ModelMesh mesh in model.Meshes)
            {
                modelSphere = Microsoft.Xna.Framework.BoundingSphere.CreateMerged(
                                    modelSphere,
                                    model.Meshes[0].BoundingSphere);
            }
        }

        protected override BoundingSphere GetBoundingSphere()
        {
            return modelSphere;
        }

        public override void Draw(SceneGraph sceneGraph)
        {
            if (sceneGraph.Camera != null)
            {
                if (model != null)
                {
                    // Copy the model hierarchy transforms
                    Matrix[] transforms = new Matrix[model.Bones.Count];
                    model.CopyAbsoluteBoneTransformsTo(transforms);

                    // Render each mesh in the model
                    foreach (ModelMesh mesh in model.Meshes)
                    {
                        foreach (Effect effect in mesh.Effects)
                        {
                            BasicEffect basicEffect = effect as BasicEffect;

                            if (basicEffect == null)
                            {
                                    throw new NotSupportedException(
                                            "Only the BasicEffect is supported in a model.");
                            }

                            //Set the matrices
                            basicEffect.World = transforms[mesh.ParentBone.Index] *
                                                    AbsoluteTransform;
                            basicEffect.View = sceneGraph.Camera.View;
                            basicEffect.Projection = sceneGraph.Camera.Projection;

                            basicEffect.EnableDefaultLighting();
                        }

                        mesh.Draw(SaveStateMode.SaveState);
                    }
                }
            }
        }
    }

The ModelSceneNode class inherits from the SceneNode class created earlier. It adds a Model property to allow changing the model it displays. It overrides the CalculateBoundingSphere method to return a custom bounding sphere that encloses the entire model. The Draw method is also overriden to display the model by multiplying (concatenating) each mesh's matrix with the matrix for the scene node (AbsoulteTransform) calculated by the scene graph. This has the effect of allowing the model to be placed anywhere in the scene and follow its parent.

As mentioned previously, a camera allows the scene to be viewed from any angle without the need to reposition the model.


    public class Camera
    {
        // Fields

        Vector3 position = Vector3.Zero;
        Quaternion rotation = Quaternion.Identity;
        float fieldOfView = (float)(System.Math.PI / 4.0);
        float aspectRatio = 4f / 3f;  // Width/Height
        float nearPlane = 1.0f;
        float farPlane = 10500.0f;
        Matrix view;
        Matrix projection;
        BoundingFrustum frustum;

        // Properties
        public Vector3 Position
        {
            get { return position; }
            set { position = value; }
        }
        public Quaternion Rotation
        {
            get { return rotation; }
            set { rotation = value; }
        }
        public float FieldOfView
        {
            get { return fieldOfView; }
            set { fieldOfView = value; }
        }
        public float AspectRatio
        {
            get { return aspectRatio; }
            set { aspectRatio = value; }
        }

        public float NearPlane
        {
            get { return nearPlane; }
            set { nearPlane = value; }
        }

        public float FarPlane
        {
            get { return farPlane; }
            set { farPlane = value; }
        }

        public Matrix View
        {
            get { return view; }
        }

        public Matrix Projection
        {
            get { return projection; }
        }

        public BoundingFrustum Frustum
        {
            get { return frustum; }
        }

        // Default Constructor

        public Camera()
        {
        }

        // Methods
        public void LookAt(Vector3 target, Vector3 up)
        {
            view = Matrix.CreateLookAt(position, target, up);

            rotation = Quaternion.CreateFromRotationMatrix(Matrix.Invert(view));
        }

        public virtual void Update(SceneGraph sceneGraph)
        {
            projection = Matrix.CreatePerspectiveFieldOfView(fieldOfView,
                                                             aspectRatio,
                                                             nearPlane,
                                                             farPlane);

            Matrix matrix = Matrix.CreateFromQuaternion(rotation);
            matrix.Translation = position;

            view = Matrix.Invert(matrix);

            frustum = new BoundingFrustum( Matrix.Multiply(view, projection));
        }
    }

The camera class provides Position and Rotation properties that describe the location of the camera in the world and its orientation. The LookAt method provides a simple means to aim the camera. The Update method performs the necessary calculations to create a view and projection matrix and calculates a new frustum.

Next Page

About Sgt. Conker

The Sergeant!
Comments (3) Trackbacks (0)
  1. This is extremely useful, thank you.

  2. Cheers! This is excellent

  3. great share, great article, very usefull for me…thank

    you


Leave a comment


No trackbacks yet.