Tutorial 41: Vector2, Vector3, Vector4 Math

CNA — C++ XNA 4.0 reimplementation

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_;
};