Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Ask the ZMan: A Holiday Screen Saver with DirectX, Part 2

  The project is a holiday-themed screen saver called DirectXmas
ZMan's Diary

Difficulty: Intermediate
Time Required: 1-3 hours
Cost: Free
Software: C# 2005 Express Edition, October 2005 DirectX SDK
Hardware:
Download:

 

Last time you created the basic shell of a Managed DirectX screen saver. If you worked on the "homework" you should have a rotating earth screen saver. If you did not do the homework, then shame on you. You can download the slightly modified source code from the DirectXmas Homework for Part 1 link above.

This week I will begin the process of adding shapes into the scene to create the holiday theme and you will learn about scene graphs and the DirectX MatrixStack.

Drawing a Tree

In the texturing articles I talked about how DirectX does not contain APIs for mapping textures because it is something that is better done by an artist. Well, creating models falls into the same category. If you want a great-looking Christmas tree, you find one on the web, you learn to create models, or you pay an artist. Since this column is about programming, however, I will make a simple tree from the basic shapes in the API. You have a sphere, box, cylinder and teapot to choose from. The cylinder allows you to set a radius for the top and bottom, allowing us to make cone shapes. Using a thin cylinder for the trunk and 3 cone shapes for the branches, you can make a tree shape. It will look a little like it came from a cartoon, but that's what game developers call "Programmer Art"!

Start by replacing the code that renders the earth with the following:

Class declarations: This code creates transformation matrices to place each of the four objects in the correct place in the world. Since cylinders are created with their length pointing in the wrong direction, you have to rotate them as well as translate them:

private Mesh trunk;
private Mesh treeBottom;
private Mesh treeMiddle;
private Mesh treeTop;
private Material trunkMaterial;
private Material treeMaterial;
private static Matrix treeOrientation = Matrix.RotationX((float)(-Math.PI / 2.0));
private Matrix trunkPosition = treeOrientation * Matrix.Translation(0f, -1.1f, 0f);
private Matrix treeBottomPosition = treeOrientation * Matrix.Translation(0f, -.4f, 0f);
private Matrix treeMiddlePosition = treeOrientation * Matrix.Translation(0f, .4f, 0f);
private Matrix treeTopPosition = treeOrientation * Matrix.Translation(0f, 1.2f, 0f);

OnCreateDevice: This code creates a cylinder for the trunk and three cone shapes for the tree body:

//Create materials and Meshes here
trunkMaterial = new Material();
trunkMaterial.Ambient = Color.Brown;
trunkMaterial.Diffuse = trunkMaterial.Ambient;
treeMaterial = new Material();
treeMaterial.Ambient = Color.Green;
treeMaterial.Diffuse = treeMaterial.Ambient;
trunk = Mesh.Cylinder(e.Device, .2f, .2f, .4f, 32, 16);
treeBottom = Mesh.Cylinder(e.Device, 1.0f, .4f, 1f, 32, 16);
treeMiddle = Mesh.Cylinder(e.Device, .8f, .2f, 1f, 32, 16);
treeTop = Mesh.Cylinder(e.Device, .6f, 0f, 1f, 32, 16);

OnDestroyDevice:

//Dispose meshes here
if (trunk != null) trunk.Dispose();
if (treeBottom != null) treeBottom.Dispose();
if (treeMiddle != null) treeMiddle.Dispose();
if (treeTop != null) treeTop.Dispose();

OnFrameRender: In each render pass the trunk and three tree sections are drawn with the correct material and at the correct position and orientation:

//Draw the tree
device.Material = trunkMaterial;
device.Transform.World = trunkPosition;
trunk.DrawSubset(0);
device.Material = treeMaterial; ;
device.Transform.World = treeBottomPosition;
treeBottom.DrawSubset(0);
device.Transform.World = treeMiddlePosition;
treeMiddle.DrawSubset(0);
device.Transform.World = treeTopPosition;
treeTop.DrawSubset(0);

You may well ask where I got all of the numbers for the sizes and positions. I sketched it out on a good old-fashioned piece of paper and then tweaked them once I had the tree rendering.

There are a few extra lines of code to remove that had to do with the texturing, but once that is all done, the result should look something like this:

Problems, Problems, Problems

I'm sure many of you are thinking "that doesn't look like a very scalable way of doing 3D graphics" and you would be correct. I wanted to show the basics of drawing multiple objects in as simple a way as possible. You can see that for each object you have to set the state (materials, transformations, render states, textures, texture states, etc.) and then actually draw it. Position, scale and orientation of the objects is done by setting the world transformation before each draw call. You only have to set a state if it changes. For example, notice that I don't set the material for each of the tree segments because it's the same for all of them.

For a world which may have hundreds or thousands of objects to worry about this is not something that is going to work. A good object-oriented architecture groups together information about each object in a single class; and obviously, when you have multiple objects it's smart to store them in a collection. Before I solve those problems though, let's look at one other problem with the current code.

Imagine if you want to move the tree to the left. You might think that changing the position of the trunk like this would work, but it doesn't:

private Matrix trunkPosition = treeOrientation * Matrix.Translation(-1f, -1.1f, 0f); 

It only moves the trunk. Although the tree looks like it is a single object it's actually 4 parts with no relationship between them. So you would have to alter the X position of each part to move the whole tree. Think about your favourite RPG game: When the character moves, his sword moves with him and so the tree should move when the trunk moves, as should any ornaments on the tree. The problem here is that each object is positioned absolutely. In other words, when you specify the position you are saying exactly where in the world it should be placed. To be able to connect objects you need to use relative positions. This means that instead of treeBottom being at position (x,y,z) it is at (dx, dy, dz) where dx is the change in position from the object to which it is connected—in this case, the trunk. Those of you who have done any Web or Windows Forms programming should know all about relative and absolute positioning. This is exactly the same, just with an extra dimension. It means that as well as state and position, it's necessary to know which object this one is relative to. If you drew the relationships out for our tree (and some ornaments) it would look something like this:

The tree object now has a single root called trunk and everything else is described in a way that is relative to the object above it in the structure. This means that if you move or rotate the root, everything else will move and rotate with it. I'm sure you have seen a tree structure like this in other code and you know they are fairly easy to implement. Each node in the tree has a collection of child objects of the same type. If you have done any reading on 3D programming theory then you already know that this is known as a scene graph. The "scene" is obvious because it represents the objects in our scene; the "graph" part comes from the fact that this kind of structure is more correctly known as a directed acyclic graph . I will leave it up to Wikipedia to tell you all about that!

Solutions, Solutions, Solutions

There are 3 problems to solve:

  1. Collect all of the information about an object together
  2. Store it in a structure for easier manipulation
  3. Change the objects so that they draw relative to their parent objects

Add a new class to your solution called GameObject. (I know this isn't a game, but you can't use 3dObject as a class name.) Add the DirectX namespaces and System.Drawing. Make sure you use the same namespace as your main source code file, or include the correct using statements:

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace AskZman
{
public class GameObject
{

}
}

You need to add property accessors for a mesh, a color, a position, an orientation and the children collection. This is a great opportunity to use the snippets functionality in Visual C# 2005 Express Edition. Type "prop" and press Tab twice. You will see a template for a property with a private member and a public get/set accessor.

Type in the type—"Mesh"—and press Tab. Type in the name of the private member variable—"mesh"—and press Tab again. Finally, type in the name of the public property—"Mesh"—and press Enter. Notice that Visual Studio has filled in the correct places in the rest of the template for you.

You should repeat this for the following properties:

Type
private name
public name

Vector3
position
Position

Vector3
orientation
Orientation

List<GameObject>
children
Children

Notice that I am using the new generic collection types from .NET 2.0. Previously, this collection would have been just a List, which can only store things of type Object and so is not strongly typed. If you've written code using List you will know that your code gets filled with type conversions. With the generic collections everything is strongly typed, so the compiler and VS know exactly what is stored and will enforce it for you.

Modify the private definitions to set some sensible defaults:

private Vector3 position = Vector3.Empty;
private Vector3 orientation = Vector3.Empty;
private List<GameObject> children = new List<GameObject>();

Our example only needs to expose a general color rather than the entire material, so the property can be simplified to take a color and create the material as needed:

private Material material = new Material();

public Color Color
{
get { return material.Ambient; }
set
{
material.Ambient = value;
material.Diffuse = value;
}
}

Since, most of the time, you will set all of the properties at the same time, it makes sense to have a single constructor to do that for you:

public GameObject(Mesh Mesh, Color Color, Vector3 Position, Vector3 Orientation)
{
this.Mesh = Mesh;
this.Color = Color;
this.Position = Position;
this.Orientation = Orientation;
}

The only additional code that is needed in this class is the code to render the GameObject. Since the data structure stores all of the relationships between the objects, all it takes to draw the entire object is to call a method on the root node. The root knows that it has to draw itself and all of its children. The code for rendering can be copied from the OnRender routine; rendering the children is a foreach loop and a recursive call to render each child:

public void Render(Device device)
{
//draw this object
device.Material = material; ;
device.Transform.World =
Matrix.RotationYawPitchRoll(orientation.Z, orientation.Y, orientation.X)
* Matrix.Translation(Position.X, Position.Y, Position.Z);
mesh.DrawSubset(0);

//if there are any children then draw them too
foreach (GameObject child in Children)
{
child.Render(device);
}
}

The world transformation is created from the orientation and position members for this object. This could be optimized a little by only creating the world matrix when position or orientation is changed, but I wanted to ensure everyone understands where it comes from.

Drawing a Tree for the Second Time

So let's start again now that there is a better architecture. In the final source code I have left the original code, but commented out, so that you can see both versions side by side.

Add a using clause for the generic collections:

using System.Collections.Generic; 

Class Declarations:

private Mesh trunk;
private Mesh treeBottom;
private Mesh treeMiddle;
private Mesh treeTop;
private GameObject tree;

OnCreateDevice: Notice that the rotation is only applied to the root node. Since all of the transformations are relative, it means that any transformation applied to a parent node applies to all of the children below it; so rotating the trunk in the correct direction rotates the whole tree. Also notice that the positions are now relative and therefore easier to understand:

//Create materials and Meshes here
trunk = Mesh.Cylinder(e.Device, .2f, .2f, .4f, 32, 16);
treeBottom = Mesh.Cylinder(e.Device, 1.0f, .4f, 1f, 32, 16);
treeMiddle = Mesh.Cylinder(e.Device, .8f, .2f, 1f, 32, 16);
treeTop = Mesh.Cylinder(e.Device, .6f, 0f, 1f, 32, 16);

//For the screen saver we create the meshes and the scene graph at the same time
//this may not be the most efficient thing for your application to do
tree = new GameObject(trunk, Color.Brown, new Vector3(0f, -1.1f, 0f),
new Vector3(0f, (float)(-Math.PI / 2), 0f));
GameObject bottom = new GameObject(treeBottom, Color.Green,
new Vector3(0f, 1f, 0f), Vector3.Empty);
GameObject middle = new GameObject(treeMiddle, Color.Green,
new Vector3(0f, 1f, 0f), Vector3.Empty);
GameObject top = new GameObject(treeTop, Color.Green,
new Vector3(0f, 1f, 0f), Vector3.Empty);
tree.Children.Add(bottom);
bottom.Children.Add(middle);
middle.Children.Add(top);

OnFrameRender:

//Draw the tree
tree.Render(device);

But if you run that program you can see it really doesn't look much like a tree any more.

Although the code stores the positions and rotations as relative to each other, DirectX does not work that way. All DirectX knows about is a single world transform applied before a draw call. So the trunk is positioned and rotated correctly because it is at the root. But the other objects are all at absolute position (0,1,0) with no rotation instead of being relative to the trunk. The Render() routine needs to be modified to remember the position of the parent so that the children can take that into account when they are drawn.

Since this is such a common way of rendering a scene, DirectX provides helper routines. They are encapsulated in the MatrixStack class.

Drawing a Tree Using the MatrixStack

The matrix stack class provides a way for your code to remember the correct world transformation for each node in the tree as you walk through it. As you begin to draw each object, you save the current world transformation using the Push method. You then add in the transformations for this particular node and they are combined with the transformations already on the stack. You retrieve the final combined transformation by reading the Top property and use this to set the world transformation. Then you can draw the node and its associated children. Remember that each child will also use the MatrixStack to remember its transformation. After you have drawn the node and its children, you restore the state of the world transformation by calling Pop and you can then return from your drawing routine.

Modify the GameObject class to have a single static MatrixStack:

static MatrixStack stack = new MatrixStack(); 

Update the Render() method to push and pop and use the MatrixStack operations to do the transformations:

public void Render(Device device)
{
//draw this object
device.Material = material; ;
stack.Push();
stack.TranslateLocal(position.X, position.Y, position.Z);
stack.RotateYawPitchRollLocal(orientation.Z, orientation.Y, orientation.X);
device.Transform.World = stack.Top;
mesh.DrawSubset(0);

//if there are any children then draw them too
foreach (GameObject child in Children)
{
child.Render(device);
}

//Restore the transformations
stack.Pop();
}

The final rendering should look like a tree once again. You should be able to change the positions and rotations of the individual items and see that the translation affects all of the children automatically.

For the final source code I have added some decorations to the tree.

Homework

You've seen the tree made out of DirectX primitives. Try to make some other holiday models by using just these primitives along with transformations, rotations and the basic scene graph you created. If I get some good ones I will actually use them in the next column with your name attached if you want.

The code in this article uses Visual C# 2005 Express Edition and the October 2005 DirectX SDK

The ZMan is here to solve your Managed DirectX programming problems. If you have a question for the ZMan then send it to zman@thezbuffer.com.

Credits:

Thanks to

Follow the Discussion

  • tatiltatil

    i can not see all links what s wrong ?

  • Clint RutkasClint I'm a "developer"

    @tatil Ths is an extremely old article from when Coding4Fun lived in a dfferet location, thanks for noticing this, I'll fix this in the near future.  Sorry about that.

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.