A new year has begun and I started a new open source project at
CodePlex. It's called
Matrix3DEx and is an extension library for the Silverlight
Matrix3D struct. Most of the functionality I implemented in Matrix3DEx was originally required for another open source project I'm currently working on
(hint, hint), and as you might know I also
like to extend Silverlight's graphics functionality in a reusable manner. That's why I decided to extract the Matrix3D code into a separate open source project, add some more useful methods, document it and write a sample.
The
Matrix3DEx project description from the CodePlex site:
The Matrix3DEx library is a collection of extension and factory methods for Silverlight's Matrix3D struct. The Matrix3D struct represents a 4x4 matrix that is used in combination with the Matrix3DProjection to apply more complex semi-3D scenarios to any UIElement than are possible with the simple PlaneProjection. This makes it possible to apply arbitrary model transformation matrices and perspective matrices to Silverlight elements.
The Matrix3D struct is very minimalistic and has only a few members. The Matrix3DEx library tries to compensate that with extension and factory methods for common transformation matrices that are easy to use like built in methods.
Features:
- Factory methods
- Translation, scaling and rotation around x, y, z or any defined axis
- Perspective field of view and orthographic projection
- Camera (look-at) with position, target and up vector
- Support for left-handed and right-handed coordination systems
- Extension methods
- Calculation of the matrix' determinant
- Matrix transpose
- SwapHandedness to change from right-handed to left-handed coordination system and vice versa
- Dump of the values row by row into a formatted string
- Math helper methods
- Angle conversion from degrees to radians and vice versa
Some use cases where Silverlight's semi-3D projection is needed can be implemented with the PlaneProjection, but there are also some scenarios where the PlaneProjection wouldn't work or only with a lot of effort. One example is the usage of a physics library that returns a matrix for an object. Traditional matrix transformations are an elegant alternative for such cases and the Matrix3DEx library has factory and extension methods for all the common matrices.
Live
The application loads a bunch of photos asynchronously and randomizes the position vector of each. The sample uses most of the Matrix3DEx features and has some Sliders and CheckBoxes to change the parameters. Uncheck the "Animate" CheckBox to disable the camera movement and click on an Image to select it. The translation, scaling and the rotation matrices of the selected element can be changed with the corresponding Sliders. You can move the camera, change the target and the "Field Of View" with the other Sliders or fix the camera target at the selected element.
The basic functionality of the sample might also be done with the PlaneProjection, but a separate look-at matrix (camera) simplifies the code a lot and makes it easier to read. Other things like a custom
field of view are not possible with the PlaneProjection.
How it works
The sample uses a DipatcherTimer to call the Update method every 100 milliseconds.
The core of the Update method:
// Create global transformations
var vw = Viewport.Width;
var vh = Viewport.Height;
var invertYAxis = Matrix3DFactory.CreateScale(1, -1, 1);
var translate = Matrix3DFactory.CreateTranslation(TranslateX,
TranslateY,
TranslateZ);
var rotateX = Matrix3DFactory.CreateRotationX(MathHelper.ToRadians(RotateX));
var rotateY = Matrix3DFactory.CreateRotationY(MathHelper.ToRadians(RotateY));
var rotateZ = Matrix3DFactory.CreateRotationZ(MathHelper.ToRadians(RotateZ));
var scale = Matrix3DFactory.CreateScale(ScaleX, ScaleY, ScaleZ);
var lookAt = Matrix3DFactory.CreateLookAtLH(CameraX, CameraY, CameraZ,
CameraLookAtX,
CameraLookAtY,
CameraLookAtZ);
var viewport = Matrix3DFactory.CreateViewportTransformation(vw, vh);
var fieldOfView = MathHelper.ToRadians(FieldOfView);
var projection = Matrix3DFactory.CreatePerspectiveFieldOfViewLH(fieldOfView,
vw / vh,
NearPlane,
FarPlane);
// Transform all elements
var selectedMatrix = Matrix3D.Identity;
foreach (var elem in this.Elements)
{
// The UIElement
var e = elem.Element;
// Create transformation matrices for UIElement
var centerAtOrigin = Matrix3DFactory.CreateTranslation(-e.ActualWidth * 0.5,
-e.ActualHeight * 0.5,
0);
var baseTranslate = Matrix3DFactory.CreateTranslation(elem.PositionX,
elem.PositionY,
elem.PositionZ);
// Combine the transformation matrices
var m = Matrix3D.Identity;
m = m * centerAtOrigin;
m = m * invertYAxis;
// Apply addtional world transformations to the seleced element
if (elem == SelectedElement)
{
m = m * scale;
m = m * rotateX * rotateY * rotateZ;
m = m * translate;
// Should the camera target be fixed at the selected element?
if (ChkLookAtSelected.IsChecked.Value)
{
lookAt = Matrix3DFactory.CreateLookAtLH(CameraX, CameraY, CameraZ,
elem.PositionX,
elem.PositionY,
elem.PositionZ);
}
}
// Calculate the final view projection matrix
m = m * baseTranslate;
m = Matrix3DFactory.CreateViewportProjection(m, lookAt, projection, viewport);
// Apply the matrix to the UIElement
e.Projection = new Matrix3DProjection { ProjectionMatrix = m };
}
First the global transformation matrices like camera projection are created using the
left-handed Matrix3DEx factory methods. After that the element local transformations are calculated and the final matrix is applied to the UIElement's
Projection property.
See the
project site for another simplified code listing.
Go and grab it
The open source
Matrix3DEx library is hosted at CodePlex and released under the
Microsoft Public License (Ms-PL) license. If you have any comments, questions or suggestions don't hesitate and write a comment, use the
Issue Tracker on the CodePlex site or contact me via any other media.
Have fun with the library and let me know if it was useful for you.