/** * @file math3d.hpp * @brief Minimal 3D math library for render pipeline visualization */ #pragma once #include #include constexpr float PI = 3.14159265358979323846f; struct Vec3 { float x = 0, y = 0, z = 0; Vec3 operator+(Vec3 o) const { return {x+o.x, y+o.y, z+o.z}; } Vec3 operator-(Vec3 o) const { return {x-o.x, y-o.y, z-o.z}; } Vec3 operator*(float s) const { return {x*s, y*s, z*s}; } float dot(Vec3 o) const { return x*o.x + y*o.y + z*o.z; } Vec3 cross(Vec3 o) const { return {y*o.z - z*o.y, z*o.x - x*o.z, x*o.y - y*o.x}; } float length() const { return std::sqrt(x*x + y*y + z*z); } Vec3 normalized() const { float l = length(); return l > 0 ? Vec3{x/l, y/l, z/l} : Vec3{0, 0, 0}; } }; inline Vec3 lerp(Vec3 a, Vec3 b, float t) { return {a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t}; } inline float smoothstep(float t) { return t * t * (3.0f - 2.0f * t); } struct Vec4 { float x = 0, y = 0, z = 0, w = 1; Vec3 xyz() const { return {x, y, z}; } Vec3 perspDiv() const { return w != 0 ? Vec3{x/w, y/w, z/w} : Vec3{x, y, z}; } }; struct Mat4 { float m[4][4] = {}; static Mat4 identity() { Mat4 r; r.m[0][0] = r.m[1][1] = r.m[2][2] = r.m[3][3] = 1; return r; } Vec4 operator*(Vec4 v) const { return { m[0][0]*v.x + m[0][1]*v.y + m[0][2]*v.z + m[0][3]*v.w, m[1][0]*v.x + m[1][1]*v.y + m[1][2]*v.z + m[1][3]*v.w, m[2][0]*v.x + m[2][1]*v.y + m[2][2]*v.z + m[2][3]*v.w, m[3][0]*v.x + m[3][1]*v.y + m[3][2]*v.z + m[3][3]*v.w, }; } Mat4 operator*(const Mat4& o) const { Mat4 r; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) for (int k = 0; k < 4; k++) r.m[i][j] += m[i][k] * o.m[k][j]; return r; } static Mat4 translate(float x, float y, float z) { Mat4 r = identity(); r.m[0][3] = x; r.m[1][3] = y; r.m[2][3] = z; return r; } static Mat4 scale(float sx, float sy, float sz) { Mat4 r = identity(); r.m[0][0] = sx; r.m[1][1] = sy; r.m[2][2] = sz; return r; } static Mat4 rotateX(float angle) { float c = std::cos(angle), s = std::sin(angle); Mat4 r = identity(); r.m[1][1] = c; r.m[1][2] = -s; r.m[2][1] = s; r.m[2][2] = c; return r; } static Mat4 rotateY(float angle) { float c = std::cos(angle), s = std::sin(angle); Mat4 r = identity(); r.m[0][0] = c; r.m[0][2] = s; r.m[2][0] = -s; r.m[2][2] = c; return r; } static Mat4 rotateZ(float angle) { float c = std::cos(angle), s = std::sin(angle); Mat4 r = identity(); r.m[0][0] = c; r.m[0][1] = -s; r.m[1][0] = s; r.m[1][1] = c; return r; } // Standard OpenGL-style perspective (right-handed, -Z forward) static Mat4 perspective(float fov, float aspect, float near, float far) { float t = std::tan(fov * 0.5f); Mat4 r; r.m[0][0] = 1.0f / (aspect * t); r.m[1][1] = 1.0f / t; r.m[2][2] = -(far + near) / (far - near); r.m[2][3] = -(2.0f * far * near) / (far - near); r.m[3][2] = -1.0f; return r; } // Standard lookAt (right-handed) static Mat4 lookAt(Vec3 eye, Vec3 target, Vec3 up) { Vec3 f = (target - eye).normalized(); Vec3 r = f.cross(up).normalized(); Vec3 u = r.cross(f); Mat4 mat = identity(); mat.m[0][0] = r.x; mat.m[0][1] = r.y; mat.m[0][2] = r.z; mat.m[0][3] = -r.dot(eye); mat.m[1][0] = u.x; mat.m[1][1] = u.y; mat.m[1][2] = u.z; mat.m[1][3] = -u.dot(eye); mat.m[2][0] = -f.x; mat.m[2][1] = -f.y; mat.m[2][2] = -f.z; mat.m[2][3] = f.dot(eye); return mat; } };