Tutorial 41: Vector2, Vector3, Vector4 Math
CNA implements the full XNA 4.0 vector library in C++. Vector2, Vector3, and Vector4 are plain structs with static utility methods — no virtual dispatch, no heap allocation, suitable for hot paths.
Construction
#include "Microsoft/Xna/Framework/Vector2.hpp"
#include "Microsoft/Xna/Framework/Vector3.hpp"
#include "Microsoft/Xna/Framework/Vector4.hpp"
using namespace Microsoft::Xna::Framework;
// Scalar broadcast
Vector2 zero2 = Vector2::Zero; // (0, 0)
Vector3 one3 = Vector3::One; // (1, 1, 1)
Vector3 up = Vector3::Up; // (0, 1, 0)
Vector3 right = Vector3::Right; // (1, 0, 0)
Vector4 unitX = Vector4::UnitX; // (1, 0, 0, 0)
// Component-wise
Vector2 a(3.0f, 4.0f);
Vector3 b(1.0f, 2.0f, 3.0f);
Vector4 c(0.5f, 0.5f, 0.5f, 1.0f); // typical RGBA
Add, Subtract, Scale
Vector3 pos(1, 0, 0);
Vector3 vel(0, 1, 0);
float dt = 0.016f;
// Operator overloads
Vector3 newPos = pos + vel * dt; // (1, 0.016, 0)
Vector3 delta = newPos - pos; // (0, 0.016, 0)
Vector3 scaled = Vector3::Multiply(vel, 2.0f); // (0, 2, 0)
Vector3 negated = -vel; // (0, -1, 0)
Dot product — angle between vectors
The dot product of two unit vectors equals the cosine of the angle between them. It is also the projection length of one vector onto another.
Vector3 forward = Vector3::Normalize(Vector3(1, 1, 0)); // diagonal
Vector3 up2 = Vector3::Up;
float dot = Vector3::Dot(forward, up2); // cos(45 deg) ≈ 0.7071
float angle = std::acos(dot); // radians
// "Is the enemy in front of the player?"
bool inFront = Vector3::Dot(playerForward, dirToEnemy) > 0.0f;
Cross product — perpendicular vector
Only defined for Vector3. The result is perpendicular to both inputs. The magnitude equals the area of the parallelogram they form.
Vector3 tangent = Vector3::Right;
Vector3 binormal = Vector3::Up;
Vector3 normal = Vector3::Cross(tangent, binormal);
// normal == Vector3::Backward (0, 0, -1) with CCW convention
// Build a local coordinate frame from a surface normal
Vector3 surfNorm = Vector3::Normalize(Vector3(0.3f, 1.0f, 0.1f));
Vector3 arb = Vector3::Right;
Vector3 tangent2 = Vector3::Normalize(Vector3::Cross(arb, surfNorm));
Vector3 bitang = Vector3::Cross(surfNorm, tangent2);
Normalize, Length, LengthSquared
Vector3 v(3.0f, 0.0f, 4.0f);
float len = v.Length(); // 5.0
float len2 = v.LengthSquared(); // 25.0 — cheaper, avoids sqrt
Vector3 n = Vector3::Normalize(v); // (0.6, 0, 0.8)
// Prefer LengthSquared for comparisons to avoid sqrt
const float kMaxRange = 10.0f;
if ((target - origin).LengthSquared() < kMaxRange * kMaxRange) {
// within range
}
Lerp, SmoothStep, Hermite
Vector3 start(0, 0, 0);
Vector3 end(10, 5, 0);
float t = 0.5f; // [0, 1]
// Linear interpolation
Vector3 mid = Vector3::Lerp(start, end, t); // (5, 2.5, 0)
// SmoothStep: ease-in/ease-out (t remapped with 3t^2 - 2t^3)
Vector3 smooth = Vector3::SmoothStep(start, end, t); // (5, 2.5, 0) at t=0.5
// Hermite: cubic with tangent control
Vector3 tan1(2, 0, 0); // departure tangent at start
Vector3 tan2(0, 2, 0); // arrival tangent at end
Vector3 h = Vector3::Hermite(start, tan1, end, tan2, t);
Distance
Vector2 player(100, 200);
Vector2 enemy(150, 250);
float dist = Vector2::Distance(player, enemy); // sqrt(5000) ≈ 70.7
float dist2 = Vector2::DistanceSquared(player, enemy); // 5000 — no sqrt
Reflect
Compute the reflection of an incident vector off a surface with a given normal. Used for bouncing projectiles, mirrors, and light rays.
// Velocity bouncing off a horizontal floor
Vector2 velocity( 1.0f, -2.0f);
Vector2 normal ( 0.0f, 1.0f); // floor points up
Vector2 reflected = Vector2::Reflect(velocity, normal);
// reflected == (1, 2) — X unchanged, Y flipped
Clamp
Vector3 raw(-5, 0.5f, 3);
Vector3 clamped = Vector3::Clamp(raw, Vector3::Zero, Vector3::One);
// clamped == (0, 0.5, 1)
Transform by Matrix
Matrix world = Matrix::CreateRotationY(MathHelper::PiOver4) *
Matrix::CreateTranslation(2, 0, 0);
// Transform a position (w=1, applies translation)
Vector3 pos2 = Vector3::Transform(Vector3::Zero, world);
// Transform a direction (w=0, translation ignored)
Vector3 dir = Vector3::TransformNormal(Vector3::Right, world);
// Transform a Vector2 (treated as (x, y, 0, 1))
Matrix uiScale = Matrix::CreateScale(2.0f);
Vector2 uiPos = Vector2::Transform(Vector2(100, 50), uiScale);
Code example: projectile physics with reflection
struct Projectile {
Vector2 position;
Vector2 velocity;
bool active = true;
};
class ProjectileDemo final : public Game {
public:
ProjectileDemo() : graphics_(this) {}
protected:
void Initialize() override {
Game::Initialize();
proj_.position = Vector2(400, 300);
proj_.velocity = Vector2(3.0f, -4.0f); // pixels per frame
}
void Update(GameTime& gameTime) override {
if (!proj_.active) return;
float dt = static_cast<float>(gameTime.ElapsedGameTime.TotalSeconds());
Vector2 gravity(0.0f, 200.0f); // pixels/s^2
proj_.velocity = proj_.velocity + gravity * dt;
proj_.position = proj_.position + proj_.velocity * dt;
// Reflect off left/right walls
auto& vp = getGraphicsDeviceProperty().getViewport();
if (proj_.position.X < 0 || proj_.position.X > vp.Width) {
proj_.velocity = Vector2::Reflect(proj_.velocity, Vector2::UnitX);
proj_.position.X = MathHelper::Clamp(proj_.position.X, 0, (float)vp.Width);
}
// Reflect off floor
if (proj_.position.Y > vp.Height) {
proj_.velocity = Vector2::Reflect(proj_.velocity, Vector2::UnitY);
proj_.velocity = proj_.velocity * 0.85f; // damping
proj_.position.Y = (float)vp.Height;
if (proj_.velocity.LengthSquared() < 1.0f)
proj_.active = false;
}
}
void Draw(const GameTime&) override {
getGraphicsDeviceProperty().Clear(Color::CornflowerBlue);
// ... draw proj_ as a sprite ...
getGraphicsDeviceProperty().Present();
}
private:
GraphicsDeviceManager graphics_;
Projectile proj_;
};