## Introduction

Yes, this is another rotating cube example in WPF. But it's also the first part of my series that will present how to make an animated walking robot in WPF using C# code. Very little XAML is used in this project because we are modeling everything in C# code. In this first part, we will introduce some basic reusable classes for building shapes in WPF and set up a simple animated scene. In future instalments, we will talk about other kinds of shapes, back materials, smooth shading and flat shading, and storyboards. The classes we use to make the cube will be combined to make our robot, a simple character with its own movements. All the source is included for each instalment of the series.

We begin by starting a new project in Visual Studio 2010. Choose WPF Application as the project type. You will have to add this XAML to the `MainWindow`

:

<ContentControl Name="contentControl2">
<Viewport3D ClipToBounds="True" Width="Auto" Height="Auto">
</Viewport3D>
</ContentControl>

You will need to add a couple of `using `

statements to your *MainWindow.xaml.cs*:

using System.Windows.Media.Animation;
using System.Windows.Media.Media3D;

We will need some basic classes to do the work of modeling our cubes. You know that 3D geometry models in WPF are made of triangle meshes. So we will need a C# class to build a triangle.

Add a class to your project and start adding some variables. Obviously we need 3 points and a constructor:

private Point3D p1;
private Point3D p2;
private Point3D p3;
public WpfTriangle(Point3D P1, Point3D P2, Point3D P3)
{
p1 = P1;
p2 = P2;
p3 = P3;
}

We will need some code to add our points to a `GeometryMesh3D `

so they can be used in a model. Since we are going to be combining `triangle`

s from different shape classes together to make a single model, we will pass in the mesh variable and add our triangle points to it:

public static void addTriangleToMesh(Point3D p0, Point3D p1, Point3D p2,
MeshGeometry3D mesh, bool combine_vertices)
{
Vector3D normal = CalculateNormal(p0, p1, p2);
if (combine_vertices)
{
addPointCombined(p0, mesh, normal);
addPointCombined(p1, mesh, normal);
addPointCombined(p2, mesh, normal);
}
else
{
mesh.Positions.Add(p0);
mesh.Positions.Add(p1);
mesh.Positions.Add(p2);
mesh.TriangleIndices.Add(mesh.TriangleIndices.Count);
mesh.TriangleIndices.Add(mesh.TriangleIndices.Count);
mesh.TriangleIndices.Add(mesh.TriangleIndices.Count);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
}
}

We have made this a `static `

function because we will usually want to add a `triangle `

only as part of a larger form. Rarely would we ever construct a `triangle `

model on its own. We will be using hundreds of `triangle`

s, so we don't want to instantiate a `triangle `

class just to add a `triangle `

to a mesh.

You will notice that we have included an argument called `combine_vertices`

. This is very important because of the difference between flat shading and smooth shading in WPF. For our cube model, we want to use flat shading. If you combine vertices for your triangle mesh, it leads to smooth shading using the `Gouraud `

method. Later on, we will show how this can apply to shapes such as a cylinder where we might want to use smooth shading. For now, we will add the capability for combing vertices to our triangle class, even though it won't be used for the cube example:

public static void addPointCombined(Point3D point, MeshGeometry3D mesh, Vector3D normal)
{
bool found = false;
int i = 0;
foreach (Point3D p in mesh.Positions)
{
if (p.Equals(point))
{
found = true;
mesh.TriangleIndices.Add(i);
mesh.Positions.Add(point);
mesh.Normals.Add(normal);
break;
}
i++;
}
if (!found)
{
mesh.Positions.Add(point);
mesh.TriangleIndices.Add(mesh.TriangleIndices.Count);
mesh.Normals.Add(normal);
}
}

When combining points in a triangle mesh, each unique point (`Position`

) is only listed once in the mesh, even though it may belong to several triangles. A separate list (`TriangleIndices`

) is used to store the indices for each point that is used to make each `triangle`

. A third list is needed to provide the normals for each triangle. This is necessary for calculating the angle of incidence between a light source and the surface of each `triangle`

. This is how WPF does flat shading. It uses Gouraud interpolation to smooth out the shading, but only if you combine the points that are the same. If you list each point separately, flat shading will be the result. This is what we want for our cube.

Normals are calculated using a simple cross product. WPF gives you a built in Vector member function for that. Here is our member function that calculates a normal for our `triangle`

:

public static Vector3D CalculateNormal(Point3D P0, Point3D P1, Point3D P2)
{
Vector3D v0 = new Vector3D(P1.X - P0.X, P1.Y - P0.Y, P1.Z - P0.Z);
Vector3D v1 = new Vector3D(P2.X - P1.X, P2.Y - P1.Y, P2.Z - P1.Z);
return Vector3D.CrossProduct(v0, v1);
}

This allows us to fill the Normals list while we are building our mesh.

We add another member function to construct a `triangle GeometryModel3D`

, just in case we wanted to do that. It won't be needed for this project, but I include it anyway. It takes a color as an argument and constructs the model using a `DiffuseMaterial `

of that solid color:

public static GeometryModel3D CreateTriangleModel
(Point3D P0, Point3D P1, Point3D P2, Color color)
{
MeshGeometry3D mesh = new MeshGeometry3D();
addTriangleToMesh(P0, P1, P2, mesh);
Material material = new DiffuseMaterial(new SolidColorBrush(color));
GeometryModel3D model = new GeometryModel3D(mesh, material);
return model;
}

Now since we are making a cube, we need a `rectangle `

class to use for each side of the cube. Conveniently, a `rectangle `

can be made of just two `triangle`

s. We only need 4 points to specify our `rectangle`

:

private Point3D p0;
private Point3D p1;
private Point3D p2;
private Point3D p3;
public WpfRectangle(Point3D P0, Point3D P1, Point3D P2, Point3D P3)
{
p0 = P0;
p1 = P1;
p2 = P2;
p3 = P3;
}

As with the `triangle`

, we want to add the `rectangle `

to a `MeshGeometry3D `

that we are building. The member function to do that looks similar, and builds on what we created for our triangle:

public static void addRectangleToMesh
(Point3D p0, Point3D p1, Point3D p2, Point3D p3, MeshGeometry3D mesh)
{
WpfTriangle.addTriangleToMesh(p0, p1, p2, mesh);
WpfTriangle.addTriangleToMesh(p2, p3, p0, mesh);
}

As with the `triangle`

, we will not always want to instantiate a `rectangle `

by itself, so we make this member function `static `

and pass in the points each time. The order of the points when we call `WpfTriangle.addTriangleToMesh `

will ensure that each `triangle `

has the same winding direction. This is important because by default, shapes in WPF have only one side and are invisible from the other side. This is determined by the winding direction or order of the points in each `triangle`

, so it is important to keep this consistent for all triangles in your model.

We add another member function for when we have an instance of a `rectangle `

and want to add it to a mesh without specifying the points, letting it use its member points instead:

public void addToMesh(MeshGeometry3D mesh)
{
WpfTriangle.addTriangleToMesh(p0, p1, p2, mesh);
WpfTriangle.addTriangleToMesh(p2, p3, p0, mesh);
}

The complete code for the `triangle `

and `rectangle `

classes is available in the zip file for this article. Let's move on to the cube. Only 4 member variables are necessary in order to completely describe our cube:

private Point3D origin;
private double width;
private double height;
private double depth;
public WpfCube(Point3D P0, double w, double h, double d)
{
width = w;
height = h;
depth = d;
origin = P0;
}

We provide a member function for adding a cube to a mesh. For convenience, it is a `static `

function that will construct a cube and then add it to the mesh. It does this by constructing a `rectangle `

for each side and then adding each of those to the mesh:

public static void addCubeToMesh(Point3D p0, double w, double h, double d,
MeshGeometry3D mesh)
{
WpfCube cube = new WpfCube(p0, w, h, d);
double maxDimension = Math.Max(d, Math.Max(w, h));
WpfRectangle front = cube.Front();
WpfRectangle back = cube.Back();
WpfRectangle right = cube.Right();
WpfRectangle left = cube.Left();
WpfRectangle top = cube.Top();
WpfRectangle bottom = cube.Bottom();
front.addToMesh(mesh);
back.addToMesh(mesh);
right.addToMesh(mesh);
left.addToMesh(mesh);
top.addToMesh(mesh);
bottom.addToMesh(mesh);
}

We also provide a version for an instance of a cube. It does its work by calling the `static `

version:

public GeometryModel3D CreateModel(Color color)
{
return CreateCubeModel(origin, width, height, depth, color);
}

The cube has member functions that can be called to make each component rectangle based on its width, height, depth and origin:

public WpfRectangle Front()
{
WpfRectangle r = new WpfRectangle(origin, width, height, 0);
return r;
}
public WpfRectangle Back()
{
WpfRectangle r = new WpfRectangle(new Point3D
(origin.X + width, origin.Y, origin.Z + depth), -width, height, 0);
return r;
}
public WpfRectangle Left()
{
WpfRectangle r = new WpfRectangle(new Point3D(origin.X, origin.Y, origin.Z + depth),
0, height, -depth);
return r;
}
public WpfRectangle Right()
{
WpfRectangle r = new WpfRectangle(new Point3D(origin.X + width, origin.Y, origin.Z),
0, height, depth);
return r;
}
public WpfRectangle Top()
{
WpfRectangle r = new WpfRectangle(origin, width, 0, depth);
return r;
}
public WpfRectangle Bottom()
{
WpfRectangle r = new WpfRectangle
(new Point3D(origin.X + width, origin.Y - height, origin.Z),
-width, 0, depth);
return r;
}

These 6 member functions are called when adding the cube to our `MeshGeometry3D`

. The complete code for the cube can be found in the zip file for this example.

Those are all the classes we will need. We now just have to add some code to our *MainWindow.xaml.cs* to make our cube and create a scene.

private double sceneSize = 10;
Point3D lookat = new Point3D(0, 0, 0);

The above variables define an arbitrary size for our scene coordinates and a lookat point for our camera.

Right click on the `Viewport3D `

that you added in the designer view and add a `Loaded `

event. See the comments for an explanation of what it does:

private void Viewport3D_Loaded(object sender, RoutedEventArgs e)
{
if (sender is Viewport3D)
{
Viewport3D viewport = (Viewport3D)sender;
WpfCube cube = new WpfCube(new System.Windows.Media.Media3D.Point3D
(0, 0, 0), sceneSize / 6, sceneSize / 6, sceneSize / 6);
GeometryModel3D cubeModel = cube.CreateModel(Colors.Aquamarine);
Model3DGroup groupScene = new Model3DGroup();
groupScene.Children.Add(cubeModel);
groupScene.Children.Add(positionLight
(new Point3D(-sceneSize, sceneSize / 2, 0.0)));
groupScene.Children.Add(new AmbientLight(Colors.Gray));
viewport.Camera = camera();
ModelVisual3D visual = new ModelVisual3D();
visual.Content = groupScene;
viewport.Children.Add(visual);
turnModel(cube.center(), cubeModel, 0, 360, 3, true);
}
}

We need some helper functions in our window to accomplish some of the work that we listed above. These functions create out lights and camera. They also make use of the `sceneSize `

variable we defined earlier, so if you want to change the coordinate base, everything will still work together:

public PerspectiveCamera camera()
{
PerspectiveCamera perspectiveCamera = new PerspectiveCamera();
perspectiveCamera.Position = new Point3D(-sceneSize, sceneSize / 2, sceneSize);
perspectiveCamera.LookDirection = new Vector3D
(lookat.X - perspectiveCamera.Position.X,
lookat.Y - perspectiveCamera.Position.Y,
lookat.Z - perspectiveCamera.Position.Z);
perspectiveCamera.FieldOfView = 60;
return perspectiveCamera;
}
public DirectionalLight positionLight(Point3D position)
{
DirectionalLight directionalLight = new DirectionalLight();
directionalLight.Color = Colors.Gray;
directionalLight.Direction = new Point3D(0, 0, 0) - position;
return directionalLight;
}

Here is the helper function that animates our cube. You can read the comments to see what it is doing:

public void turnModel(Point3D center, GeometryModel3D model,
double beginAngle, double endAngle, double seconds, bool forever)
{
Vector3D vector = new Vector3D(0, 1, 0);
Vector3D vector2 = new Vector3D(1, 0, 0);
AxisAngleRotation3D rotation = new AxisAngleRotation3D(vector, 0.0);
AxisAngleRotation3D rotation2 = new AxisAngleRotation3D(vector2, 0.0);
DoubleAnimation doubleAnimation =
new DoubleAnimation(beginAngle, endAngle, durationTS(seconds));
DoubleAnimation doubleAnimation2 =
new DoubleAnimation(beginAngle, endAngle, durationTS(seconds));
if (forever)
{
doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
doubleAnimation2.RepeatBehavior = RepeatBehavior.Forever;
}
doubleAnimation.BeginTime = durationTS(0.0);
doubleAnimation2.BeginTime = durationTS(0.0);
RotateTransform3D rotateTransform = new RotateTransform3D(rotation, center);
RotateTransform3D rotateTransform2 = new RotateTransform3D(rotation2, center);
Transform3DGroup transformGroup = new Transform3DGroup();
transformGroup.Children.Add(rotateTransform);
transformGroup.Children.Add(rotateTransform2);
model.Transform = transformGroup;
rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, doubleAnimation);
rotation2.BeginAnimation(AxisAngleRotation3D.AngleProperty, doubleAnimation2);
}

We just need a couple more helper functions that help us specify our animation durations in seconds and convert them to time spans that WPF can use:

private int durationM(double seconds)
{
int milliseconds = (int)(seconds * 1000);
return milliseconds;
}
public TimeSpan durationTS(double seconds)
{
TimeSpan ts = new TimeSpan(0, 0, 0, 0, durationM(seconds));
return ts;
}

So that is our simple rotating cube. The triangle and other classes that we made will be useful in constructing our simple scene with a walking robot character. The animation concepts will also come in handy later for making the robot's arms and legs move. In the next article in the series, we will add a `cylinder `

class and demonstrate the difference between flat shading and smooth shading.

## History

- 1
^{st} November, 2010: Initial version