A learning-focused math library for graphics and game programming.
#include "math/forge_math.h"
// Create vectors
vec3 position = vec3_create(0.0f, 1.0f, 0.0f);
vec3 velocity = vec3_create(1.0f, 0.0f, 0.0f);
// Add vectors
vec3 new_position = vec3_add(position, velocity);
// Create a rotation matrix
mat4 rotation = mat4_rotate_z(FORGE_PI / 4.0f); // 45° rotation
// Transform a vector
vec4 v = vec4_create(1.0f, 0.0f, 0.0f, 1.0f);
vec4 rotated = mat4_multiply_vec4(rotation, v);vec2— 2D vectors (x, y) — maps to HLSLfloat2vec3— 3D vectors (x, y, z) — maps to HLSLfloat3vec4— 4D vectors (x, y, z, w) — maps to HLSLfloat4mat2— 2×2 matrices (column-major) — Jacobian analysis, anisotropymat3— 3×3 matrices (column-major) — maps to HLSLfloat3x3mat4— 4×4 matrices (column-major) — maps to HLSLfloat4x4quat— Quaternions (w, x, y, z) for 3D rotations — pass as HLSLfloat4
Note on naming: We use vec2/vec3/vec4 instead of HLSL's float2/float3/float4
to keep the math library portable and follow C math library conventions. The mapping
is straightforward: when passing data to HLSL shaders, vec3 in C becomes float3
in the shader.
- Interpolation:
forge_lerpf(a, b, t)— scalar lerp - Bilinear interpolation:
forge_bilerpf(c00, c10, c01, c11, tx, ty)— blend 4 grid values - Trilinear interpolation:
forge_trilerpf(c000..c111, tx, ty, tz)— blend 8 cube corners - Logarithm:
forge_log2f(x)— base-2 logarithm (mip level count) - Trig wrappers:
forge_sinf(x),forge_cosf(x)— thin wrappers overSDL_sinf/SDL_cosffor naming consistency - Clamping:
forge_clampf(x, lo, hi)— clamp scalar to range - Comparison:
forge_approx_equalf(a, b, tolerance)— absolute tolerance,forge_rel_equalf(a, b, tolerance)— relative tolerance
Each vector type supports:
- Construction:
vec3_create(x, y, z) - Arithmetic:
add,sub,scale - Dot product:
vec3_dot(a, b) - Length:
vec3_length(v),vec3_length_squared(v) - Normalization:
vec3_normalize(v) - Interpolation:
vec3_lerp(a, b, t) - Bilinear interpolation:
vec3_bilerp(...),vec4_bilerp(...)— blend 4 grid values in 2D - Trilinear interpolation:
vec3_trilerp(...),vec4_trilerp(...)— blend 8 cube corners in 3D - Cross product:
vec3_cross(a, b)(3D only) - Negate:
vec3_negate(v)(3D) - Reflect:
vec3_reflect(incident, normal)— reflect vector about a surface normal (3D)
- Construction:
mat2_create(4 floats)(row-major input),mat2_identity() - Multiplication:
mat2_multiply(a, b),mat2_multiply_vec2(m, v) - Analysis:
mat2_transpose(m),mat2_determinant(m) - Anisotropy:
mat2_singular_values(m)— pixel footprint axes,mat2_anisotropy_ratio(m)
- Construction:
mat3_create(9 floats)(row-major input),mat3_identity() - Multiplication:
mat3_multiply(a, b),mat3_multiply_vec3(m, v) - Analysis:
mat3_transpose(m),mat3_determinant(m),mat3_inverse(m) - 2D Transforms:
mat3_rotate(angle),mat3_scale(vec2)
- Construction:
mat4_identity(),mat4_from_mat3(m) - Multiplication:
mat4_multiply(a, b),mat4_multiply_vec4(m, v) - Analysis:
mat4_transpose(m),mat4_determinant(m),mat4_inverse(m) - Transformations:
mat4_translate(offset)mat4_scale(scale),mat4_scale_uniform(s)mat4_rotate_x/y/z(angle_radians)
- Projection:
mat4_perspective(fov, aspect, near, far)— Symmetric perspective projection (3D scenes)mat4_perspective_from_planes(l, r, b, t, near, far)— Asymmetric perspective (VR, multi-monitor)mat4_orthographic(l, r, b, t, near, far)— Orthographic projection (2D, shadow maps)vec3_perspective_divide(clip)— Clip-space vec4 → NDC vec3 (divide by w)
- Camera:
mat4_look_at(eye, target, up)— View matrix from camera parameters
- Construction:
quat_create(w, x, y, z),quat_identity() - Properties:
quat_dot(a, b),quat_length(q),quat_length_sq(q) - Operations:
quat_normalize(q),quat_conjugate(q),quat_inverse(q),quat_negate(q) - Composition:
quat_multiply(a, b)— compose rotations (apply b first) - Rotation:
quat_rotate_vec3(q, v)— rotate vector by quaternion - Conversions:
quat_from_axis_angle(axis, angle)/quat_to_axis_angle(q, &axis, &angle)quat_from_euler(yaw, pitch, roll)/quat_to_euler(q)— intrinsic Y-X-Z orderquat_to_mat4(q)/quat_from_mat4(m)— quaternion to/from rotation matrix
- Interpolation:
quat_slerp(a, b, t),quat_nlerp(a, b, t) - Direction extraction:
quat_forward(q),quat_right(q),quat_up(q)— extract basis vectors - View matrix:
mat4_view_from_quat(position, orientation)— camera view from quaternion - Rodrigues:
vec3_rotate_axis_angle(v, axis, angle)— rotate vector around arbitrary axis
Functions for converting between color spaces and applying tone mapping:
- sRGB ↔ linear:
color_srgb_to_linear(s),color_linear_to_srgb(linear),color_srgb_to_linear_rgb(srgb),color_linear_to_srgb_rgb(linear) - Luminance:
color_luminance(linear_rgb)— BT.709 relative luminance - HSL ↔ RGB:
color_rgb_to_hsl(rgb),color_hsl_to_rgb(hsl) - HSV ↔ RGB:
color_rgb_to_hsv(rgb),color_hsv_to_rgb(hsv) - CIE XYZ ↔ RGB:
color_linear_rgb_to_xyz(rgb),color_xyz_to_linear_rgb(xyz) - CIE xyY ↔ XYZ:
color_xyz_to_xyY(xyz),color_xyY_to_xyz(xyY) - Tone mapping:
color_tonemap_reinhard(hdr),color_tonemap_aces(hdr) - Exposure:
color_apply_exposure(hdr, exposure_ev)
Integer hash functions for procedural generation, noise, and sampling:
- Single-value:
forge_hash_wang(key),forge_hash_pcg(input),forge_hash_xxhash32(h) - Combining:
forge_hash_combine(seed, value)— boost-style hash combine - Coordinate hashing:
forge_hash2d(x, y),forge_hash3d(x, y, z) - Hash to float:
forge_hash_to_float(h)→[0, 1),forge_hash_to_sfloat(h)→[-1, 1)
Perlin, simplex, and fractal noise for procedural content:
- Fade curve:
forge_noise_fade(t)— quintic smoothstep - Gradient helpers:
forge_noise_grad1d(hash, dx),forge_noise_grad2d(hash, dx, dy),forge_noise_grad3d(hash, dx, dy, dz) - Perlin noise:
forge_noise_perlin1d(x, seed),forge_noise_perlin2d(x, y, seed),forge_noise_perlin3d(x, y, z, seed) - Simplex noise:
forge_noise_simplex2d(x, y, seed) - Fractal Brownian motion:
forge_noise_fbm2d(x, y, seed, octaves, lacunarity, persistence),forge_noise_fbm3d(x, y, z, seed, octaves, lacunarity, persistence) - Domain warping:
forge_noise_domain_warp2d(x, y, seed, warp_strength)
Quasi-random and blue noise sequences for sampling:
- Halton:
forge_halton(index, base)— radical inverse sequence - R-sequences:
forge_r1(index),forge_r2(index, out_x, out_y)— additive recurrence sequences - Sobol:
forge_sobol_2d(index, out_x, out_y)— 2D Sobol sequence - Blue noise:
forge_blue_noise_2d(out_x, out_y, count, candidates, seed)— Mitchell's best candidate algorithm - Measurement:
forge_star_discrepancy_2d(xs, ys, count)— star discrepancy
Quadratic and cubic Bezier curve evaluation and utilities:
- Evaluation:
vec2_bezier_quadratic(p0, p1, p2, t),vec2_bezier_cubic(p0, p1, p2, p3, t)(alsovec3variants) - Tangents:
vec2_bezier_quadratic_tangent(...),vec2_bezier_cubic_tangent(...)(alsovec3variants) - Arc length:
vec2_bezier_quadratic_length(...),vec2_bezier_cubic_length(...) - Splitting:
vec2_bezier_quadratic_split(...),vec2_bezier_cubic_split(...) - Degree elevation:
vec2_bezier_quadratic_to_cubic(...)— convert quadratic to cubic - Flatness test:
vec2_bezier_quadratic_is_flat(...),vec2_bezier_cubic_is_flat(...) - Adaptive flattening:
vec2_bezier_quadratic_flatten(...),vec2_bezier_cubic_flatten(...)— convert curves to line segments
Signed distance functions for implicit curve representation:
- Circle:
sdf2_circle(p, radius)— distance to circle boundary - Box:
sdf2_box(p, half_extents)— distance to axis-aligned rectangle - Rounded box:
sdf2_rounded_box(p, half_extents, radius)— rounded rectangle - Segment:
sdf2_segment(p, a, b)— distance to line segment
Combine and blend SDF primitives:
- Union:
sdf_union(d1, d2)— combine (min) - Intersection:
sdf_intersection(d1, d2)— intersect (max) - Subtraction:
sdf_subtraction(d1, d2)— carve out - Smooth union:
sdf_smooth_union(d1, d2, k)— blended combine - Smooth intersection:
sdf_smooth_intersection(d1, d2, k)— blended intersect
Numerical gradient and Laplacian computation for arbitrary scalar fields:
- Type:
forge_field2d_fn— function pointerfloat (*)(float x, float y, void *ctx) - Gradient:
forge_field2d_gradient(f, x, y, eps, ctx)— returnsvec2gradient via central differences - Laplacian:
forge_field2d_laplacian(f, x, y, eps, ctx)— sum of second partial derivatives - Height map normal:
forge_heightmap_normal(heights, x, z, w, h, spacing_x, spacing_z)— surface normal from height grid via cross product of tangent vectors
FORGE_PI— π (3.14159...)FORGE_TAU— 2π (6.28318...)FORGE_DEG2RAD— Convert degrees to radiansFORGE_RAD2DEG— Convert radians to degreesFORGE_EPSILON— Machine epsilon for 32-bit float (1.192e-7)FORGE_BEZIER_DEGENERATE_EPSILON— Degenerate Bezier threshold (1e-12)
Right-handed, Y-up:
+Y (up)
|
|
+----> +X (right)
/
/
+Z (forward, toward camera)
Winding order: Counter-clockwise (CCW) for front-facing triangles.
Matrix layout: Column-major storage and math (matches HLSL shaders).
See DESIGN.md for detailed explanations.
Every function in forge_math.h includes:
- Clear documentation
- Usage examples
- Geometric intuition
- References to math lessons
Standalone programs teaching each concept in depth:
lessons/math/01-vectors/— Vectors, dot product, cross product, normalizationlessons/math/02-coordinate-spaces/— Coordinate spaces, view and projection matriceslessons/math/03-bilinear-interpolation/— Bilinear interpolation, LINEAR texture filteringlessons/math/04-mipmaps-and-lod/— Mip chains, trilinear interpolation, LOD selectionlessons/math/05-matrices/— Matrix math, multiplication, basis vectors, transpose, determinant, inverselessons/math/06-projections/— Perspective, orthographic, frustums, clip space, NDClessons/math/07-floating-point/— IEEE 754, precision, epsilon, z-fightinglessons/math/08-orientation/— Quaternions, Euler angles, axis-angle, rotation matrices, slerplessons/math/09-view-matrix/— Camera as inverse transform, look-at, MVP pipelinelessons/math/10-anisotropy/— Jacobian, singular values, anisotropic filtering, noise, frictionlessons/math/11-color-spaces/— sRGB, linear, HSL, HSV, CIE XYZ, tone mappinglessons/math/12-hash-functions/— Integer hashing, coordinate hashing, hash-to-floatlessons/math/13-gradient-noise/— Perlin noise, simplex noise, fBm, domain warpinglessons/math/14-blue-noise-sequences/— Halton, R2, Sobol, Mitchell's best candidatelessons/math/15-bezier-curves/— Quadratic/cubic evaluation, tangents, splitting, flatteninglessons/math/16-density-functions/— Density as ratio, PDFs, integration, radiometrylessons/math/17-implicit-curves/— Implicit curves, SDFs, CSG booleans, smooth blendinglessons/math/18-scalar-field-gradients/— Partial derivatives, gradient, Laplacian, height map normals
Each lesson includes a demo program and README explaining the math.
See how these functions are used in real code:
lessons/gpu/02-first-triangle/— Usesvec2andvec3for vertices and colorslessons/gpu/03-uniforms-and-motion/— Usesmat4_rotate_zfor animationlessons/gpu/05-mipmaps/— Usesforge_log2ffor mip level count
- Readability over performance — This code is meant to be learned from
- Header-only — Just include
forge_math.h, no build config needed - Well-documented — Every function explains what, why, and where it's used
- No dependencies — Works in any C project, not just SDL GPU
- Extensible — Add new operations via
/dev-math-lessonskill
Need a function that doesn't exist yet?
- Check the lessons — The concept might already be implemented
- Use
/dev-math-lessonskill — Creates a lesson + updates the library - Or add manually — Follow the documentation style in
forge_math.h
Every new math operation should have:
- A clear use case (which lesson/project needs it?)
- A corresponding lesson teaching the concept
- Comprehensive documentation with examples
Our C types map directly to HLSL types when uploading data to shaders:
| C Type | HLSL Type | Memory Layout |
|---|---|---|
mat2 |
float2x2 |
16 bytes (4 floats, column-major) |
vec2 |
float2 |
8 bytes (2 floats) |
vec3 |
float3 |
12 bytes (3 floats) |
vec4 |
float4 |
16 bytes (4 floats) |
mat3 |
float3x3 |
36 bytes (9 floats, column-major) |
mat4 |
float4x4 |
64 bytes (16 floats, column-major) |
quat |
float4 |
16 bytes (4 floats: w, x, y, z) |
Example: Vertex struct in C and HLSL
C code (CPU side):
typedef struct Vertex {
vec2 position; // 8 bytes
vec3 color; // 12 bytes
} Vertex;
Vertex v = {
.position = vec2_create(0.5f, 0.5f),
.color = vec3_create(1.0f, 0.0f, 0.0f)
};HLSL shader (GPU side):
struct VSInput {
float2 position : TEXCOORD0;
float3 color : TEXCOORD1;
};The memory layout matches perfectly — no conversion needed when uploading to the GPU.
// Create a point at the origin
vec4 point = vec4_create(0.0f, 0.0f, 0.0f, 1.0f);
// Move it right by 5 units
mat4 translation = mat4_translate(vec3_create(5.0f, 0.0f, 0.0f));
point = mat4_multiply_vec4(translation, point); // (5, 0, 0, 1)
// Rotate 90° around Z axis
mat4 rotation = mat4_rotate_z(FORGE_PI / 2.0f);
point = mat4_multiply_vec4(rotation, point); // (0, 5, 0, 1)vec3 direction = vec3_create(3.0f, 4.0f, 0.0f);
float length = vec3_length(direction); // 5.0
vec3 normalized = vec3_normalize(direction); // (0.6, 0.8, 0.0)
// Now normalized has length 1.0 and points in the same direction// Given an up vector and a forward direction, compute right
vec3 up = vec3_create(0.0f, 1.0f, 0.0f);
vec3 forward = vec3_create(0.0f, 0.0f, 1.0f);
vec3 right = vec3_cross(up, forward); // (1, 0, 0)
// The cross product is perpendicular to both input vectorszlib — Same as SDL and the rest of forge-gpu.