To handle 3D transformations, WPF has high-level classes (TranslateTransform3D, ScaleTransform3D and RotateTransform3D).

But for various reasons, it may be interesting to use the lower-level technique of matrices. For educational purposes first. But also to keep reflexes acquired with frameworks such as Direct3D or OpenGL.

First let introduce the 3D coordinate systems.

The three axes of a 3D coordinate system can be represented through the fingers of one hand : the thumb represents the x-axis, the index finger the y-axis and the middle finger the z-axis. The axes are perpendicular to each other. The x-axis is usually directed towards the right and the y-axis upwards. The direction of the z-axis differenciates two different systems : the "right-handed" system and the "left-handed" system.

In the "right-handed" system, the z-axis is directed forward (it leaves the screen). Moreover, in this system, a rotation according to a positive angle value around an axis is always in a counter-clockwise direction (right hand folded around the axis, thumb in the positive direction (*)). The "right-handed" is used by WPF and OpenGL.

In "left-handed" system, the z-axis is directed backwards. A rotation according to a positive angle value around an axis is always in a clockwise direction (left hand folded around the axis, thumb in the positive direction (*)). This system is used by Direct3D, POV-Ray and in some projections (upon which we will return).

(*) The POV-Ray documentation shows how to determine with the hand the rotation direction of a positive angle.

A matrix is an array of values that defines a transformation of coordinates.

The Matrix3D structure represents a 3D matrix, with 4 columns and 4 rows :

M11

M12

M13

M14

M21

M22

M23

M24

M31

M32

M33

M34

OffsetX (M41)

OffsetY (M42)

OffsetZ (M43)

M44

M11, M12, M13, M14, M21, M22, M23, M24, M31, M32, M33, M34, OffsetX, OffsetY, OffsetZ and M44 are properties of the Matrix3D structure (M41, M42 and M43 don't exist as properties).

A transformation can be applied to a point by multiplying it by a matrix. It is also possible to use the Transform methods of the Matrix3D and MatrixTransform3D classes. The resulting point has the following coordinates :

x’ = (M11x + M21y + M31z + OffsetX) / f y’ = (M12x + M22y + M32z + OffsetY) / f z’ = (M13x + M23y + M33z + OffsetZ) / f

where f = M14x + M24y + M34z + M44

The fourth column of the matrix (represented by the f factor), defines non-affine transformations, ie transformations that do not respect the parallelism of the sides of a rectangle, typically like a perspective view...

The identity matrix has a value 1 for M11, M22, M33 and M44, and 0 for the other cells. The f factor has the the value 1. The constructor of the Matrix3D structure creates by default an identity matrix.

A transformation can be applied to a vector according to the same principle as for a point.

The M11, M22 and M33 properties determine the scale factor, respectively on the X, Y and Z axis, as shown by the equation :

x’ = M11x y’ = M22y z’ = M33z

The Helper3D Class of Perspective library provides the following static method which returns a scaling matrix :

public static Matrix3D GetScaleMatrix(double scaleX, double scaleY, double scaleZ) { return new Matrix3D { M11 = scaleX, M22 = scaleY, M33 = scaleZ }; }

This method could also be written as follows :

public static Matrix3D GetScaleMatrix(double scaleX, double scaleY, double scaleZ) { var m = new Matrix3D(); m.Scale(new Vector3D(scaleX, scaleY, scaleZ)); return m; }

A translation is defined by properties OffsetX, OffsetY and OffsetZ :

public static Matrix3D GetTranslationMatrix(double offsetX, double offsetY, double offsetZ) { return new Matrix3D { OffsetX = offsetX, OffsetY = offsetY, OffsetZ = offsetZ }; }

This matrix corresponds to the following equation :

x’ = x + OffsetX y’ = y + OffsetY z’ = z + OffsetZ

This method could also be written as follows :

public static Matrix3D GetTranslationMatrix(double offsetX, double offsetY, double offsetZ) { var m = new Matrix3D(); m.Translate(new Vector3D(offsetX, offsetY, offsetZ)); return m; }

For rotation, things get a little complicated, and involve trigonometry.

In 2D, the rotation of a point at an angle beta gives the following equation, as demonstrated in my previous article WPF 2D matrices :

x' = x * cos(beta) - y * sin(beta) y' = y * cos(beta) + x * sin(beta)

In 3D, the équation becomes :

x' = x * cos(beta) - y * sin(beta) y' = y * cos(beta) + x * sin(beta) z' = z

So, the 3D matrix corresponding to this equation is :

M11 = cos(beta)

M21 = -sin(beta)

M31 = 0

M41 = 0

M12 = sin(beta)

M22 = cos(beta)

M32 = 0

M42 = 0

M13 = 0

M23 = 0

M33 = 1

M43 = 0

OffsetX=0

OffsetY=0

OffsetZ=0

M44=1

The following method returns a Matrix3D object defining a rotation around the z-axis at an angle expressed in degrees :

public static Matrix3D GetZRotationMatrix(double angle) { var radianAngle = GeometryHelper.DegreeToRadian(angle); return new Matrix3D { M11 = Math.Cos(radianAngle), M12 = Math.Sin(radianAngle), M21 = -Math.Sin(radianAngle), M22 = Math.Cos(radianAngle) }; }

This method is used as follows :

Point3D p3 = new Point3D(1.0, 0.0, 0.0); p3 *= Helper3D.GetZRotationMatrix(initialAngle); DisplayPoint(p3, zWorkshop, grayMaterial); p3 *= Helper3D.GetZRotationMatrix(angle); DisplayPoint(p3, zWorkshop);

The code of this example is available in the Matrix3DDemo.xaml page of the Perspective library example application.

By the same principle, a rotation around the x-axis is defined by the following equation:

x' = x y' = y * cos(beta) - z * sin(beta) z' = z * cos(beta) + y * sin(beta)

Then the 3D matrix corresponding to the rotation of a point around the X axis (in the YZ plane) is :

M11 = 1

M21 = 0

M31 = 0

M41 = 0

M12 = 0

M22 = cos(beta)

M32 = -sin(beta)

M42 = 0

M13 = 0

M23 = sin(beta)

M33 = cos(beta)

M43 = 0

OffsetX=0

OffsetY=0

OffsetZ=0

M44=1

The following method returns a Matrix3D object defining a rotation around the x-axis at an angle expressed in degrees :

public static Matrix3D GetXRotationMatrix(double angle) { var radianAngle = GeometryHelper.DegreeToRadian(angle); return new Matrix3D { M22 = Math.Cos(radianAngle), M32 = -Math.Sin(radianAngle), M23 = Math.Sin(radianAngle), M33 = Math.Cos(radianAngle) }; }

Finally, a rotation around the y-axis is defined by the following equation:

x' = x * cos(2 * PI - beta) - z * sin(2 * PI - beta) y' = y z' = z * cos(2 * PI - beta) + x * sin(2 * PI - beta)

Because WPF is a right-handed 3D system, a positive angle value must result in a counter-clockwise rotation around the axis. In respect to the relative orientation of Z to X, the beta angle is inverted (2 * PI - beta).

The 3D matrix corresponding to the rotation of a point around the y-axis (in the XZ plane) is then :

M11 = cos(2 * PI - beta)

M21 = 0

M31 = -sin(2 * PI - beta)

M41 = 0

M12 = 0

M22 = 1

M32 = 0

M42 = 0

M13 = sin(2 * PI - beta)

M23 = 0

M33 = cos(2 * PI - beta)

M43 = 0

OffsetX=0

OffsetY=0

OffsetZ=0

M44=1

The following method returns a Matrix3D object defining a rotation around the y-axis at an angle expressed in degrees :

public static Matrix3D GetYRotationMatrix(double angle) { var radianAngle = (2 * Math.PI) - GeometryHelper.DegreeToRadian(angle); return new Matrix3D { M11 = Math.Cos(radianAngle), M31 = -Math.Sin(radianAngle), M13 = Math.Sin(radianAngle), M33 = Math.Cos(radianAngle) }; }

The rotations presented above are suitable for a right-handed coordinate system (WPF). In a left-handed one, the direction of rotation must be reversed. The Perspective library automatically adjusts the rotation type to the handedness of the coordinate system.

Several matrices (each representing a transformation) may be combined by multiplying them together. Matrix multiplication is not commutative (m1 * m2 does not give the same result as m1 * m2).

Matrix m1 = Helper3D.GetZRotationMatrix(45.0); Matrix m2 = Helper3D.GetTranslationMatrix(50.0, 0.0, 0.0); _currentPoint *= m1 * m2;

The following example applies 2 rotations to a BOX3D cube by multiplying 2 matrices together and pass them to the constructor of a MatrixTransform3D object :

Matrix3D mXRotation = Helper3D.GetXRotationMatrix(45.0); Matrix3D mZRotation = Helper3D.GetZRotationMatrix(45.0); MatrixTransform3D mt = new MatrixTransform3D(mXRotation * mZRotation); box.Transform = mt;

Basically, to represent a 3D model on screen, several spaces should be successively implemented : the model space, the world, the view, the projection and the screen viewport. Each space has its own coordinate system, and the transition from one system to another is done by applying a 3D matrix.

The model space is the one in which the 3D model is defined ; its origin is usually at the center of the model, or at the first point of the structure.

The world, or universe, is the 3D space of the application in which the model evolves ; the coordinates of the model are defined by the World matrix, which is generally derived from a combination of translation, rotation and scaling matrices.

The view is a transformation of the universe so that it seems to be seen by the point of view of a virtual camera ; this transformation is defined using the View matrix, which determines the position and orientation of the camera.

The projection defines the 2D representation of the 3D scene, using the Projection matrix.

Finally, the screen matrix screen (or Viewport matrix) defines the dimensions and position of the scene in the 2D container.

Each of these matrices may be derived from the combination of several matrices.

While Direct3D uses matrices to define those various spaces, WPF offers an higher level approach and automatically handles view and projection using the Camera object of the Viewport3D control. The World matrix is determined by the transformations applied to the model.

Powered by the Perspective WebApp - © Olivier Dewit - Lyon, Paris, France - Legal informations