Introduction
In Clipping Plane in WPF 3D, Part 2, I demonstrated how a finite clipping plane could "slice" a model, removing the pieces on one side of the clipping plane using an algorithm that determines if a given point's projection lies within a plane defined by two triangles. In this Part 3, I will demonstrate how to rotate the sponge about its own axes, and translate it along the World Coordinate System.
Background
I'm assuming the reader has read the previous articles on WPF and has some familiarity with matrix algebra.
Using the Code
Download and Build
Download the MengerSpongeClipping.zip test file and extract it. Open the MengerSpongeClipping
project with Visual Studio. It consists of two projects: MengerSpongeClipping
WPF application and the Microsoft 3DTools library. Build it by pressing F6; the project should build successfully with no errors. Press F5 to run the MengerSpongeClipping
project in debug mode.
World and Local Coordinate Systems
The term World Coordinate System refers to the main Cartesian coordinate system of the scene. In Part 2, I showed how the background box (toggled on and off using the B key) represents the World Coordinate System with the X axis in South direction, the Z axis in the West direction, and the Y axis pointing up.
The sponge (or cube as I will refer to it hereafter) has its own local coordinate system with its XYZ axes with the origin at the center, initially aligned with the World Coordinate System. In general, rotations in three dimensional space are not commutative. To demonstrate, the first image below shows the cube after it has been rotated 90 degrees about its X axis, followed by a rotation of 90 degrees about its Y axis, followed by a rotation of 90 degrees about its Z axis. Note how the cube's X axis is in the West direction (the World Z axis), the cube's Y axis is in the down direction, and the cube's Z axis is in the South direction (the World X axis). The second image shows the cube after it has been rotated 90 degrees about its Z axis, followed by a rotation of 90 degrees about its Y axis, followed by a rotation of 90 degrees about its X axis. The rotations are the same amount and around the same axes as the first image, but in a different order, and thus the results are different. In the second case, the cube's X axis is in the East direction (the World -Z axis), the cube's Y axis is in the up direction. (The cube's Z axis remains in the South direction.)
Before I get into the details of the rotation and translation code, I first want to talk about the cosmetic and other changes from Version 2.
New Features with Version 3
It seemed to me that the layout was getting too "top-heavy" with the controls on the top, so I changed it to a horizontal layout with the controls on the left-hand side and the 3D Viewport on the right. This shows the use of WPF's <Grid>
container with the 3D Viewport in the far right column and spanning all the rows by setting Grid.RowSpan="12"
. The current Transformation Matrix is now displayed on the left-hand side. I've also added face labels to the cube to make it easier to see the results of rotations.
You can rotate the cube about its own axes, and you can translate it along the World Coordinate System axes. The amount of rotation and translation are configurable. I originally had the option of using either sliders or the keyboard to perform rotations, but keeping the two in synch made the code difficult to maintain, and I found that the keyboard allows more precise control, so I ended up removing the sliders. The new keys and buttons in this Version 3 for rotation and translation are:
- Use X, Y, Z to rotate the cube about its X, Y and Z axes, respectively, by an amount specified in the app.config file as
SpongeRotateIncrement
. The default SpongeRotateIncrement
is 15 degrees, so pressing the X key six times will rotate the cube by 90 degrees about its x axis. - Shift X, Y, Z will rotate the cube in the negative direction by
SpongeRotateIncrement
degrees. - Ctrl-Z will undo the last rotation.
- There are two translations, a coarse and a fine translation.
- To translate the cube in World coordinates by an amount specified by in the app.config as
SpongeTranslateIncrementCoarse
, use the J,K, and L to translate it along the positive X, Y, and Z axes, respectively. The default SpongeTranslateIncrementCoarse
is 4.0
, so pressing the J key 2 times will translate the cube 8 units in the X direction (South). - Use Shift J, K, and L to translate the cube along the negative X, Y, and Z axes.
- To translate the cube by an amount specified by in the app.config as SpongeTranslateIncrementFine, use CtrlJ, K, and L to translate it along the positive X, Y, and Z axes, respectively. The default
SpongeTranslateIncrementFine
is 0.5
, so pressing the Ctrl-L key 3 times will translate the cube 1.5 units in the Z direction. - Use Ctrl-Shift J, K, and L to translate the cube along the negative X, Y, and Z axes by an amount specified by in the app.config as
SpongeTranslateIncrementFine
. - To clear all the rotations, click the "Clear Rotations" button. To clear all the translations, click the "Clear Translations" button.
Here is an example of setting SpongeRotateIncrement
, SpongeTranslateIncrementCoarse
, and SpongeTranslateIncrementFine
in app.config:
<setting name="SpongeRotateIncrement" serializeAs="String">
<value>15</value>
</setting>
<setting name="SpongeTranslateIncrementCoarse" serializeAs="String">
<value>4</value>
</setting>
<setting name="SpongeTranslateIncrementFine" serializeAs="String">
<value>0.5</value>
</setting>
With a little practice, you will find that you can move the cube with ease and precision. In the image above, I used the keyboard to orient the cube using the following steps:
- turned on the background using the B key and zoomed out using the minus key
- rotated the cube along its X axis 15° by pressing the X key once
- rotated the cube along its Y axis -30° by pressing the Shift-Y key twice
- rotated the cube along its Z axis 45° by pressing the Z key 3 times
- translated it along the World Coordinate X axis (South) 8 units by pressing the J key twice
- translated it along the World Coordinate Y axis (up) 8.5 units by pressing the K key twice, and by pressing Ctrl-K
- translated it along the negative World Coordinate Z (east) axis 0.5 units by pressing Ctrl-Shift-L
Rotation
Rotations and translations in 3D graphics are based on a 4x4 matrix called the Transformation Matrix. The 3x3 submatrix in the upper left represents rotation, and the first three elements of the bottom row of the matrix represents translation. For a rotation of an angle theta about the X-axis, the Transformation Matrix is:
1 0 0 0
0 +cos(theta) +sin(theta) 0
0 -sin(theta) +cos(theta) 0
0 0 0 1
If you click the "Clear Rotations" button, then press the X key for a rotation angle of 15 degrees about the X axis, for example, (where the cosine of 15 degrees is 0.966 and the sine of 15 degrees is 0.259) the matrix has the values as displayed on the left-hand side of the WPF app as shown in the image below:
The code to create this Transformation Matrix is:
RotateTransform3D spongeRotateTransform3D = new RotateTransform3D();
spongeAxisAngleRotation3d.Axis = new Vector3D(1.0, 0.0, 0.0);
spongeAxisAngleRotation3d.Angle = 15.0;
spongeRotateTransform3D.Rotation = spongeAxisAngleRotation3d;
Similarly, the Transformation Matrix for a rotation about the Y axis is:
+cos(theta) 0 -sin(theta) 0
0 1 0 0
+sin(theta) 0 +cos(theta) 0
0 0 0 1
If you click the "Clear Rotations" button, then press the Y key for a rotation angle of 15 degrees about the Y axis, for example, the matrix has the values as displayed on the left-hand side of the WPF app as shown in the image below:
The code to create this Transformation Matrix is the same as the code above for the X axis rotation except that the axis of rotation is the Y axis:
spongeAxisAngleRotation3d.Axis = new Vector3D(0.0, 1.0, 0.0);
Finally, the Transformation Matrix for a rotation about the Z axis is:
+cos(theta) +sin(theta) 0 0
-sin(theta) +cos(theta) 0 0
0 0 1 0
0 0 0 1
If you click the "Clear Rotations" button, then press the Shift-Z key twice for a rotation angle of -30 degrees (note the minus sign) about the Z axis, for example (where the cosine of -30 degrees is 0.866 and the sine of -30 degrees is -0.5), the matrix has the values as displayed on the left-hand side of the WPF app as shown in the image below:
The code to create this Transformation Matrix is the same as the code above for the X axis rotation except that the axis of rotation is the Z axis, and the angle is -30
:
spongeAxisAngleRotation3d.Axis = new Vector3D(0.0, 0.0, 1.0);
spongeAxisAngleRotation3d.Angle = -30.0;
Rotation Implementation
The first step in rotating the cube about its own axis is to determine the axis of rotation by transforming the World Coordinate axes (as unit vectors) by the previous cumulative rotations. For example, suppose after some arbitrary number of rotations have been applied, the X key is pressed, meaning we want to rotate the cube about its X axis. First we transform the Word Coordinate X axis (the first element of the Point3D[] axes
array) into its rotated counterpart, Vector3D newX
by multiplying it by the cumulative rotation matrix, saveCumulativeRotationMatrix
.
Point3D[] axes = new Point3D[] { new Point3D(1.0, 0.0, 0.0),
new Point3D(0.0, 1.0, 0.0), new Point3D(0.0, 0.0, 1.0) };
saveCumulativeRotationMatrix.Transform(axes);
newX = new Vector3D(axes[0].X, axes[0].Y, axes[0].Z);
We then use the rotated X axis and rotation increment (in degrees, and negative if the shift was pressed) to compute the current Transformation Matrix Matrix3D currentRotation
like the code above:
spongeAxisAngleRotation3d.Axis = newX;
spongeAxisAngleRotation3d.Angle = currentShift ? -rotateIncrement : +rotateIncrement;
spongeRotateTransform3D.Rotation = spongeAxisAngleRotation3d;
Matrix3D currentRotation = spongeRotateTransform3D.Value;
In order to preserve the order of rotations, cumulative rotations are stored in a matrix Matrix3D saveCumulativeRotationMatrix
which is multiplied by each successive rotation matrix. Since each successive rotation is defined by Matrix3D currentRotation
, the multiplication is:
Matrix3D saveCumulativeRotationMatrix = Matrix3D.Multiply(saveCumulativeRotationMatrix, currentRotation)
To apply the rotations to the model, the model's Transform
property is set to the saveCumulativeRotationMatrix
matrix:
MatrixTransform3D matrixTransform3D = newMatrixTransform3D();
matrixTransform3D.Matrix = saveCumulativeRotationMatrix;
this.myGeometryModel.Transform = matrixTransform3D;
Note that this same Transform
is used by anything that rotates with the cube: the clipping plane, the axes, and the face labels.
In order to have the ability to undo a rotation using Ctrl-Z, the cumulative rotation matrix is added to a list, private List<RotationHistory> previousRotationList
. The class RotationHistory
holds the matrix and the x, y, and z rotations. As Ctrl-Z is pressed, the last matrix that was added to the list is removed, and the one prior to that is used as the current cumulative rotation matrix, thereby "undoing" the last rotation.
Translations
In the previous section, we were rotating the cube about its own axis (and therefore about its origin), and for clarity, did not consider translations. In this section, we will consider translations. When we translate the cube, we are translating it with respect to the World Coordinate System. (Unlike the rotations, which occur with respect to the cube's coordinate system.) As mentioned previously, the first three elements of bottom row of the 4x4 Transformation Matrix correspond to translation. For a translation of Xoffset
, Yoffset
, and Zoffset
units in the X, Y, and Z directions, respectively, the Transformation Matrix is:
1 0 0 0
0 1 0 0
0 0 1 0
Xoffset Yoffset Zoffset 1
For example, for a translation of 4 units in the X direction, 8 units in the Y direction, and -1.5 units in the Z direction (and no rotations), the Transformation Matrix has the values as displayed on the left-hand side of the WPF app as shown in the image below:
In order to accommodate the translation, a translation matrix Matrix3D spongeTranslationMatrix
is used. The code to create this matrix using the above values is:
Transform3DGroup spongeTransformTranslateGroup = new Transform3DGroup();
TranslateTransform3D translateTransform3D = new TranslateTransform3D();
translateTransform3D.OffsetX = 4.0;
translateTransform3D.OffsetY = 8.0;
translateTransform3D.OffsetZ = -1.5;
spongeTransformTranslateGroup.Children.Add(translateTransform3D);
spongeTranslationMatrix = spongeTransformTranslateGroup.Value;
The actual values for OffsetX
, OffsetY
and OffsetZ
are calculated by TranslationKeys.HandleKeys()
depending on whether the J, K or L was pressed and state of the Shift and Ctrl keys.
Matrix3D spongeTranslationMatrix
contains the X, Y and Z translations along the World Coordinate System. These translations are applied before the rotations, so the complete code to translate the cube and rotate it about its own axes is:
Matrix3D currentRotation = this.spongeTransformRotateGroup.Value;
Matrix3D cumulativeRotation = Matrix3D.Multiply(saveCumulativeRotationMatrix, currentRotation);
Matrix3D translationAndRotation = Matrix3D.Multiply(currentRotation, spongeTranslationMatrix);
cumulativeRotationAndTranslation =
Matrix3D.Multiply(saveCumulativeRotationMatrix, translationAndRotation);
MatrixTransform3D matrixTransform3D = newMatrixTransform3D();
matrixTransform3D.Matrix = cumulativeRotationAndTranslation;
this.myGeometryModel.Transform = matrixTransform3D;
Note that this same Transform is used by anything that translates with the cube: the two flashlights and their labels.
Points of Interest
It is remarkable that regardless of the number of translations and rotations of a 3D model, it can be represented by a single 4x4 Transformation Matrix.
History