Tutorial 42: Matrix Operations and Transforms
The CNA Matrix type is a 4×4 row-major float matrix. It covers the full XNA 4.0 matrix API including factory methods for common transforms, decomposition, shadow projection, reflection planes, and billboards.
Identity and basic construction
#include "Microsoft/Xna/Framework/Matrix.hpp"
using namespace Microsoft::Xna::Framework;
Matrix I = Matrix::Identity; // no-op transform
// Build a world matrix from TRS components
Matrix world = Matrix::CreateScale(2.0f)
* Matrix::CreateRotationY(MathHelper::PiOver4)
* Matrix::CreateTranslation(5.0f, 0.0f, 0.0f);
// Non-uniform scale
Matrix stretch = Matrix::CreateScale(1.0f, 2.0f, 1.0f);
Multiply
Matrix multiplication in CNA follows the XNA row-vector convention. Transforms are applied left-to-right: Scale * Rotation * Translation applies scale first, then rotation, then translation.
Matrix scale = Matrix::CreateScale(3.0f);
Matrix rot = Matrix::CreateRotationZ(MathHelper::PiOver2);
Matrix trans = Matrix::CreateTranslation(10, 0, 0);
// SRT order (common for world matrices)
Matrix srt = scale * rot * trans;
// Equivalent static helper
Matrix srt2 = Matrix::Multiply(scale, Matrix::Multiply(rot, trans));
Transpose
Matrix m = Matrix::CreateRotationX(0.5f);
Matrix t = Matrix::Transpose(m);
// For orthonormal rotation matrices, Transpose == Inverse
Invert
Matrix view = Matrix::CreateLookAt(
Vector3(0, 5, 10), Vector3::Zero, Vector3::Up);
Matrix invView;
bool ok = Matrix::Invert(view, invView);
if (ok) {
// invView transforms from clip space back to world space
// (used for ray casting from screen coordinates)
}
Decompose — extract translation, rotation, scale
Matrix world2 = Matrix::CreateScale(2, 1, 3)
* Matrix::CreateRotationY(1.0f)
* Matrix::CreateTranslation(7, -2, 4);
Vector3 scale2;
Quaternion rotation;
Vector3 translation;
bool ok2 = world2.Decompose(scale2, rotation, translation);
// scale2 ≈ (2, 1, 3)
// rotation encodes the Y-axis rotation
// translation ≈ (7, -2, 4)
CreateShadow — project geometry onto a plane
// Directional light pointing down-left
Vector3 lightDir = Vector3::Normalize(Vector3(-1, -1, 0));
// Ground plane (Y=0, normal pointing up)
Plane ground(Vector3::Up, 0.0f);
Matrix shadow = Matrix::CreateShadow(lightDir, ground);
// Multiply an object's world matrix by shadow to draw its flat projection
CreateReflection — mirror geometry
// Mirror plane: X=0 (the YZ plane)
Plane mirror(Vector3::Right, 0.0f);
Matrix reflection = Matrix::CreateReflection(mirror);
// Typically set as the world matrix of the mirrored scene pass
effect_->setWorld(reflection * objectWorld);
CreateBillboard — always facing the camera
Vector3 objectPosition(3, 0, -2);
Vector3 cameraPosition(0, 5, 5);
Vector3 cameraUp = Vector3::Up;
Matrix billboard = Matrix::CreateBillboard(
objectPosition,
cameraPosition,
cameraUp,
nullptr // optional camera-forward override
);
// Also available: CreateConstrainedBillboard for axis-locked billboards
Matrix axisLocked = Matrix::CreateConstrainedBillboard(
objectPosition, cameraPosition,
Vector3::Up, // rotate only around this axis
nullptr, nullptr);
Transform points and normals
Matrix xform = Matrix::CreateRotationY(1.0f)
* Matrix::CreateTranslation(5, 0, 0);
// Transform a position (w=1): translation IS applied
Vector3 worldPos = Vector3::Transform(Vector3::Zero, xform);
// Transform a direction / normal (w=0): translation is NOT applied
// Use TransformNormal so normals stay perpendicular after scaling
Vector3 localNormal(0, 1, 0);
Vector3 worldNormal = Vector3::TransformNormal(localNormal, xform);
worldNormal = Vector3::Normalize(worldNormal); // renormalize if scaled
Code example: billboard sprite always facing the camera
class BillboardDemo final : public Game {
public:
BillboardDemo() : graphics_(this) {
graphics_.setPreferredBackBufferWidth(800);
graphics_.setPreferredBackBufferHeight(600);
}
protected:
void LoadContent() override {
effect_ = std::make_unique<BasicEffect>(getGraphicsDeviceProperty());
texture_ = std::make_unique<Texture2D>("assets/particle.png",
getGraphicsDeviceProperty());
effect_->setTextureEnabled(true);
effect_->setTexture(texture_.get());
// A simple quad: two triangles, four vertices
VertexPositionTexture quad[4] = {
{ Vector3(-0.5f, 0.5f, 0.0f), Vector2(0, 0) },
{ Vector3( 0.5f, 0.5f, 0.0f), Vector2(1, 0) },
{ Vector3(-0.5f, -0.5f, 0.0f), Vector2(0, 1) },
{ Vector3( 0.5f, -0.5f, 0.0f), Vector2(1, 1) },
};
vb_ = std::make_unique<VertexBuffer>(getGraphicsDeviceProperty(),
VertexPositionTexture::VertexDeclaration, 4, BufferUsage::None);
vb_->SetData(quad, 4);
uint16_t idx[] = { 0,1,2, 1,3,2 };
ib_ = std::make_unique<IndexBuffer>(getGraphicsDeviceProperty(),
IndexElementSize::SixteenBits, 6, BufferUsage::None);
ib_->SetData(idx, 6);
}
void Update(GameTime& gameTime) override {
float t = static_cast<float>(gameTime.TotalGameTime.TotalSeconds());
// Camera orbits around Y axis
camPos_ = Vector3(5 * std::cos(t * 0.5f), 2, 5 * std::sin(t * 0.5f));
}
void Draw(const GameTime&) override {
auto& gd = getGraphicsDeviceProperty();
gd.Clear(Color::CornflowerBlue);
Matrix view = Matrix::CreateLookAt(camPos_, Vector3::Zero, Vector3::Up);
Matrix proj = Matrix::CreatePerspectiveFieldOfView(
MathHelper::PiOver4, 800.0f / 600.0f, 0.1f, 100.0f);
// Billboard always faces the camera
Matrix bill = Matrix::CreateBillboard(
Vector3::Zero, camPos_, Vector3::Up, nullptr);
effect_->setWorld(bill);
effect_->setView(view);
effect_->setProjection(proj);
gd.setVertexBuffer(*vb_);
gd.setIndexBuffer(*ib_);
for (auto& pass : effect_->getCurrentTechnique().Passes) {
pass.Apply();
gd.DrawIndexedPrimitives(PrimitiveType::TriangleList, 0, 0, 2);
}
gd.Present();
}
private:
GraphicsDeviceManager graphics_;
std::unique_ptr<BasicEffect> effect_;
std::unique_ptr<Texture2D> texture_;
std::unique_ptr<VertexBuffer> vb_;
std::unique_ptr<IndexBuffer> ib_;
Vector3 camPos_;
};