This article will demonstrate how to build a simple 3D engine from scratch, using C# and Silverlight, which will allow you to:
 Draw a group of lines in three dimensions
 Rotate those lines around a central point
 Add or remove lines
You can see the finished application here
In order to help demonstrate how these techniques work, I have developed a Silverlight demonstration application, called the Vector Visualizer, on top of the 3D engine discussed in this article. You can see it in action here and the complete source code is available at Codeplex. My goal is to show you how matrix maths is used to produce some simple 3D projections and rotations and to convince you that this is actually not as difficult a task as one may think.
However, you may be asking yourself why you would need such a tool in the first place. Isn’t this a little bit like reinventing the wheel? After all, aren’t there many fully developed 3D engines already out there? On a philosophical note, I don’t really subscribe to the idea that “reinventing the wheel” is always a bad thing. Of course, there are times when deadline pressures make reuse of existing code a much more attractive option but, on the other hand, can you even fully understand the wheel if you never try to reinvent it? And if no one ever tried to reinvent it, wouldn’t we be using the same old wooden wheels people used thousands of years ago?
On a more practical note, there may be times when a fullblown 3D engine might be overkill for your requirements. If you want to implement a limited 3D feature that may not have all the aspects of a fully functional 3D engine but is more efficient in terms of performance, or if you need to perform a single transformation of a 2D object to simulate 3D effect, then this article will lay the foundation knowledge you need to do that. Even if you chose not to implement them in your own code, and instead use external libraries, the basic understanding of how it all works will give you more confidence and clarity when using those libraries.
Vectors and Matrixes
The basic goal of any 3D engine is to be able to show the projection of a three dimensional object in the plane of the observer. In order to achieve this goal in building our engine, we need first to understand a few linear algebra concepts.
NOTE:
For those interested to delving deeper into linear algebra concepts, I would recommend the lectures of Prof. Gilbert Strang from the Massachusetts Institute of Technology. They are freely available on YouTube here
In this article, I want focus on implementing only basic 3D functionality, so more complex issues such as zorder and drawing surfaces will not be covered. As such, we will only need to answer the following core questions:
 What is a matrix?
 What is a vector?
 How do I multiply vector by matrix?
 How do I multiply matrix by matrix?
A matrix is a rectangular table of numbers or functions. In our case, a vector will represent a point in threedimensional space. Our basic goal is to be able to plot a threedimensional point and then, using a matrix, track its projection on a given plane as we rotate the point in 3D space.
I am sure that the vast majority of people reading this article will be familiar with the movie, The Matrix, with Neo flying around and shooting evil agents. I’m also sure that many of you have heard analogies made to this film before, when the topic of matrixes has been raised. However, I ask you to bear with me one more time, as it really can shed some light on this subject.
The movie plot hinges on the idea that humans live in a virtual reality, with their entire world being controlled by a program called Matrix. People go about their daily life, completely unaware of the controlling influence that the matrix has over their lives. Likewise, on the plane of a normal observer, changes in the projection of a point rotating in 3D space can appear random and unpredictable. However, in fact, these patterns are precisely and accurately predicted by matrix operations. For one who can ‘see’ the Matrix, like our super hero Neo, the world is governed by an abstract principle invisible for the others but obvious to him.
And now for the important question: do you want the blue or the red pill? If you choose the blue pill, then stop reading this article, go download any 3D engine and stop trying to understand how it all actually works. If you choose the red pill then stay in wonderland and read on.
Operations with Matrixes
Now that we’ve established that the matrix determines the changes in projection of our threedimensional point, as it rotates, we can get deeper into the mechanisms of how it does so.
Say we want to rotate a point by 30 degrees, around the axisZ. First, we need to create the matrix that can perform such an operation. In other words, we need to find the matrix that will transform a threedimensional point such that it is rotated 30 degrees around the zaxis.
Figure: 1
We then multiply the 3D point (vector) by this matrix to produce another point that has been rotated appropriately.
We can also multiply one matrix by another matrix. Let’s say we know the matrix that rotates a point by 30 degrees around Z and we know the matrix that rotates a point by 10 degrees around Y. If we simply multiply those two matrixes together, we arrive at a single matrix that does both: rotates a point by 30 degrees around Z and by 10 degrees around Y.
Figure: 2
These examples illustrate that the two operations we need to understand in order to build our 3D engine are:

how to multiply a vector (in our case a 3D point) by a matrix,

how to multiply one matrix by another matrix.
VectorMatrix operations
We will use a three row by three column matrix, reflecting the fact that our point is located in three dimensions, and our vector will be a threedimensional point (X, Y, Z). So, let’s first see how to multiply a point by a matrix. Surprisingly, it is quite easy and straightforward. Here is an example of such a multiplication:
Figure: 3
Here we have an arbitrary matrix acting on a point with coordinates X = 5, Y = 1, Z = 2. We multiply each element of each row of the matrix by each element of the vector in order to obtain new values of (X, Y, Z) for the transformed point. So, for example, we multiply column 1 row 1 of the matrix by X, column 2 row 1 by Y, and column 3 row 1 by Z. We then sum these values to get the value of X for the transformed point. We repeat the process for rows 2 and 3 to arrive at the transformed values of Y and Z.
MatrixMatrix operations
The multiplication of two matrices looks very similar. Here is one example:
Figure: 4
As you can see we multiply each element of the row of the first matrix by each element of the column of the second matrix. We repeat that for all the rows and columns.
The Identity Matrix
The final concept that needs to be introduced, before we move on to creating matrixes for rotating a point around X,Y or Z, is that of the Identity Matrix. The matrixes that I will use will be built on top of the identity matrix. The following figure depicts a 3×3 identity matrix:
All of the numbers in the matrix are zero, except the numbers in the topleft to bottomright diagonal, which are set to 1. If you try to multiply any matrix by the identity matrix you will get the same matrix, just as any number multiplied by one gives the same number.
Writing Matrix Operations in C#
It is now time to translate these matrix concepts into a real C# code. In the source code that I referenced at the start of the article, you can find the following objects:
 Point3D object – this stores (X, Y, Z) values of a 3D point. We will use it to store both the initial and current values for X, Y and Z. Later I will explain why we need to store both.
 Matrix3D object – the Matrix3D object that has private property, _matrix, which is twodimensional array of type double. In one of the dimensions we will store the rows, and in the other the columns, of the matrix.
We can see here the elegance of C#, in that it allows us to use operator overloads for the multiplication of matrix and vector. In our case, we will simply multiply the Matrix3D by the Point3D objects. Following is the implementation of this operation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 
public static Point3D operator *(Matrix3D matrix1, Point3D point3D) { var x = point3D.InitialX * matrix1._matrix[0, 0] + point3D.InitialY * matrix1._matrix[0, 1] + point3D.InitialZ * matrix1._matrix[0, 2]; var y = point3D.InitialX * matrix1._matrix[1, 0] + point3D.InitialY * matrix1._matrix[1, 1] + point3D.InitialZ * matrix1._matrix[1, 2]; var z = point3D.InitialX * matrix1._matrix[2, 0] + point3D.InitialY * matrix1._matrix[2, 1] + point3D.InitialZ * matrix1._matrix[2, 2]; point3D.X = x; point3D.Y = y; point3D.Z = z; return point3D; } 
The reason that I store both the initial and current values of (X, Y, Z) is that I always transform a current point from the initial coordinates rather than the current coordinates. This is because during each transformation there will be a certain loss of precision. This means that if I rotate a point by a certain angle and then rotate it back by the same angle but with opposite sign, I will not return to the initial point because of that loss of precision. In order to avoid these complications, I always perform transformations from the initial point position.
Now let’s see how we would handle the multiplication of two matrices in C#:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 
public static Matrix3D operator *(Matrix3D matrix1, Matrix3D matrix2) { var matrix = new Matrix3D(); for(var i = 0; i < 3; i++) { for(var j = 0; j < 3; j++) { matrix._matrix[i, j] = (matrix2._matrix[i, 0] * matrix1._matrix[0, j]) + (matrix2._matrix[i, 1] * matrix1._matrix[1, j]) + (matrix2._matrix[i, 2] * matrix1._matrix[2, j]); } } return matrix; } 
The following C# code is used to set the current matrix as identity matrix:
1 2 3 4 5 6 7 8 
public Matrix3D MakeIdentity() { this._matrix[0, 0] = this._matrix[1, 1] = this._matrix[2, 2] = 1; this._matrix[0, 1] = this._matrix[0, 2] = this._matrix[1, 0] = this._matrix[1, 2] = this._matrix[2, 0] = this._matrix[2, 1] = 0; return this; } 
I call this method in the constructor of the Matrix3D object because all the matrixes I use are built on top of the identity matrix.
Now, we need to write the code for the matrix that will rotate our 3D point around the axes X, Y or Z. In fact, there are three matrices, one for each plane, and each one is built on top of the identity matrix:
Figure: 6
Translating this into C#, we get the following code;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 
public static Matrix3D NewRotateAroundX(double radians) { var matrix = new Matrix3D(); matrix._matrix[1, 1] = Math.Cos(radians); matrix._matrix[1, 2] = Math.Sin(radians); matrix._matrix[2, 1] = (Math.Sin(radians)); matrix._matrix[2, 2] = Math.Cos(radians); return matrix; } public static Matrix3D NewRotateAroundY(double radians) { var matrix = new Matrix3D(); matrix._matrix[0, 0] = Math.Cos(radians); matrix._matrix[0, 2] = (Math.Sin(radians)); matrix._matrix[2, 0] = Math.Sin(radians); matrix._matrix[2, 2] = Math.Cos(radians); return matrix; } public static Matrix3D NewRotateAroundZ(double radians) { var matrix = new Matrix3D(); matrix._matrix[0, 0] = Math.Cos(radians); matrix._matrix[0, 1] = Math.Sin(radians); matrix._matrix[1, 0] = (Math.Sin(radians)); matrix._matrix[1, 1] = Math.Cos(radians); return matrix; } 
In order to create a single matrix that rotates a point in all the three axes, simultaneously, we simply multiply these three matrices together, as follows:
1 2 3 4 5 6 7 
public static Matrix3D NewRotate(double radiansX, double radiansY, double radiansZ) { var matrix = NewRotateAroundX(radiansX); matrix = matrix * NewRotateAroundY(radiansY); matrix = matrix * NewRotateAroundZ(radiansZ); return matrix; } 
Radians or Degrees?
You will have noticed that I am passing angles in radians. Radians describe the angles as a relationship between radius and the length of an arc lying on an imaginary circle around the angle within this angle. However, because we live on a planet that revolves approximately 360 times around it’s axis for each rotation around our lovely star, the sun, our ancestors decided to split the circle into 360 parts and call each part a degree. So, out of respect for the knowledge and wisdom of our ancestors, we’ll use degrees for the angles we pass to the matrix. In order to do that, I have created a utility function that transforms degrees into radians as follows:
1 2 3 4 
public static double DegreesToRadians(double degrees) { return (Math.PI / 180) * degrees; } 
And in this case a rotation matrix can be created from angle in degrees as follow:
1 2 3 4 5 6 7 8 9 
public static Matrix3D NewRotateByDegrees (double degreesX, double degreesY, double degreesZ) { return NewRotate( Angle.DegreesToRadians(degreesX), Angle.DegreesToRadians(degreesY), Angle.DegreesToRadians(degreesZ) ); } 
Perspective Effect
Once we have multiplied the 3D point coordinates by the desired matrix, we can simply assign the new X and Y values to a point drawn on the screen, as follows:
1 2 
PointOnTheScreen.X = TransformedPoint3D.X PointOnTheScreen.Y = TransformedPoint3D.Y 
However, because the central point around which the points rotate is not necessarily the (0,0) point on the screen we have to adjust the position of this point by adding to it the distance from the (0,0) point on the screen. We do that as follows:
1 2 
PointOnTheScreen.X = TransformedPoint3D.X + RotationCenterPoint.X PointOnTheScreen.Y = TransformedPoint3D.Y + RotationCenterPoint.Y 
This is all we need to do in order to get the basic 3D transformation. However, if we want to have more realistic 3D Figure: we also have to consider the perspective effects. The closer an object, or part of the object is the bigger it will appear. Likewise, more distant objects will look smaller.
The following formula will transform the object in such a way so to make a closer one (one with higher Z value) to look bigger and more distant one (with smaller Z) to look smaller:
1 2 3 4 
PointOnTheScreen.X = TransformedPoint3D.X * Coefficient / (TransformedPoint3D.Z + Coefficient) + RotationCenterPoint.X PointOnTheScreen.Y = TransformedPoint3D.Y * Coefficient / (TransformedPoint3D.Z + Coefficient) + RotationCenterPoint.Y 
In this way, I can increase or decrease the coefficient to alter the effect of that change. If the coefficient is smaller it will look as if the rotating object is located at a certain distance from the eyes of the observer. If the coefficient is higher it will look as if it is very close to the eyes of the observer, or that it is very big in size. The bigger objects will experience that change more than the smaller ones.
In the source code, this functionality is implemented in the method, MapToPoints, of the class Line2D. This method performs the mapping from the three dimensional point of our object to a two dimensional point on the computer screen.
Conclusion
Once you wrap your head around a few basic operations with matrixes, implementing threedimensional transformations is quite easy, 3step process:
 Create the desired transforming matrix by multiplying different matrices
 Multiply that matrix by a point in 3D
 Assign that point to a 2D screen point by applying perspective formula
I hope I’ve provided some helpful insights into 3D transformations, which will be useful to you in writing your own 3D engine, or in understanding how a third party engine works. Hopefully, you’ll now look at the full source code here and try out the application here.
Load comments