1 //------------------------------------------------------------------------------ 2 // math.d 3 // 4 // minimal vector math helper functions, just the stuff needed for 5 // the sokol-samples 6 // 7 // Ported from HandmadeMath.h 8 //------------------------------------------------------------------------------ 9 10 module handmade.math; 11 12 extern (C): 13 @safe: 14 15 version (WebAssembly) 16 { 17 /** Approximate value of pi*/ 18 enum PI = 3.14159265358979323846264338327950288419716939937510; 19 20 /** Cosine function from Zig standard library */ 21 double zig_cos(double value) @nogc nothrow @trusted; 22 23 /** Floor function from Zig standard library */ 24 double zig_floor(double value) @nogc nothrow @trusted; 25 26 /** Sine function from Zig standard library */ 27 double zig_sin(double value) @nogc nothrow @trusted; 28 29 /** Square root function for size_t from Zig standard library */ 30 double zig_sqrt(size_t value) @nogc nothrow @trusted; 31 32 /** Square root function for floating point from Zig standard library */ 33 double zig_sqrtf(double value) @nogc nothrow @trusted; 34 35 /** Tangent function from Zig standard library */ 36 double zig_tan(double value) @nogc nothrow @trusted; 37 38 alias cos = zig_cos; 39 alias floor = zig_floor; 40 alias sin = zig_sin; 41 alias tan = zig_tan; 42 43 /** Generic square root function */ 44 auto sqrt(T)(T value) 45 { 46 static if (is(T == double) || is(T == float)) 47 { 48 return zig_sqrtf(value); 49 } 50 else 51 { 52 return zig_sqrt(value); 53 } 54 } 55 } 56 else 57 { 58 public import core.stdc.math : sqrt, cos, sin, tan, floor; 59 public import std.math : PI; 60 } 61 62 /** 2D Vector structure with x and y coordinates */ 63 struct Vec2 64 { 65 float x = 0.0, y = 0.0; 66 67 /** Creates a zero vector */ 68 static Vec2 zero() @nogc nothrow 69 { 70 return Vec2(0, 0); 71 } 72 73 /** Constructor for Vec2 */ 74 this(float x, float y) @nogc nothrow 75 { 76 this.x = x; 77 this.y = y; 78 } 79 } 80 81 /** 3D Vector structure with x, y, and z coordinates */ 82 struct Vec3 83 { 84 float x = 0.0, y = 0.0, z = 0.0; 85 86 /** Creates a zero vector */ 87 static Vec3 zero() @nogc nothrow 88 { 89 return Vec3(0, 0, 0); 90 } 91 92 /** Constructor for Vec3 */ 93 this(float x, float y, float z) @nogc nothrow 94 { 95 this.x = x; 96 this.y = y; 97 this.z = z; 98 } 99 100 /** Returns a vector pointing upwards (0, 1, 0) */ 101 static Vec3 up() @nogc nothrow 102 { 103 return Vec3(0, 1, 0); 104 } 105 106 /** Calculates the length of the vector */ 107 float len() const @nogc nothrow 108 { 109 return sqrt(dot(this, this)); 110 } 111 112 /** Adds two vectors component-wise */ 113 static Vec3 add(Vec3 left, Vec3 right) @nogc nothrow 114 { 115 return Vec3(left.x + right.x, left.y + right.y, left.z + right.z); 116 } 117 118 /** Subtracts two vectors component-wise */ 119 static Vec3 sub(Vec3 left, Vec3 right) @nogc nothrow 120 { 121 return Vec3(left.x - right.x, left.y - right.y, left.z - right.z); 122 } 123 124 /** Multiplies a vector by a scalar */ 125 static Vec3 mul(Vec3 v, float s) @nogc nothrow 126 { 127 return Vec3(v.x * s, v.y * s, v.z * s); 128 } 129 130 /** Normalizes a vector */ 131 static Vec3 norm(Vec3 v) @nogc nothrow 132 { 133 auto l = v.len(); 134 if (l != 0) 135 { 136 return Vec3(v.x / l, v.y / l, v.z / l); 137 } 138 else 139 { 140 return Vec3.zero; 141 } 142 } 143 144 /** Calculates the cross product of two vectors */ 145 static Vec3 cross(Vec3 v0, Vec3 v1) @nogc nothrow 146 { 147 return Vec3( 148 (v0.y * v1.z) - (v0.z * v1.y), 149 (v0.z * v1.x) - (v0.x * v1.z), 150 (v0.x * v1.y) - (v0.y * v1.x) 151 ); 152 } 153 154 /** Calculates the dot product of two vectors */ 155 static float dot(Vec3 v0, Vec3 v1) @nogc nothrow 156 { 157 return v0.x * v1.x + v0.y * v1.y + v0.z * v1.z; 158 } 159 } 160 161 /** 4x4 Matrix structure for transformations */ 162 struct Mat4 163 { 164 float[4][4] m; 165 166 /** Creates an identity matrix */ 167 static Mat4 identity() @nogc nothrow 168 { 169 return Mat4([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); 170 } 171 172 /** Creates a zero matrix */ 173 static Mat4 zero() @nogc nothrow 174 { 175 return Mat4([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]); 176 } 177 178 /** Multiplies two matrices */ 179 static Mat4 mul(Mat4 left, Mat4 right) @nogc nothrow 180 { 181 Mat4 result = Mat4.zero; 182 foreach (col; 0 .. 4) 183 { 184 foreach (row; 0 .. 4) 185 { 186 result.m[col][row] = 187 left.m[0][row] * right.m[col][0] 188 + left.m[1][row] * right.m[col][1] 189 + left.m[2][row] * right.m[col][2] 190 + left.m[3][row] * right.m[col][3]; 191 } 192 } 193 return result; 194 } 195 196 /** Creates a perspective projection matrix */ 197 static Mat4 perspective(float fov, float aspect, float near, float far) @nogc nothrow 198 { 199 Mat4 result = Mat4.identity; 200 201 float t = tan(fov * (PI / 360.0)); 202 result.m[0][0] = 1.0 / t; 203 result.m[1][1] = aspect / t; 204 result.m[2][3] = -1.0; 205 result.m[2][2] = (near + far) / (near - far); 206 result.m[3][2] = (2.0 * near * far) / (near - far); 207 result.m[3][3] = 0.0; 208 209 return result; 210 } 211 212 /** Creates a view matrix for camera positioning */ 213 static Mat4 lookAt(Vec3 eye, Vec3 center, Vec3 up) @nogc nothrow 214 { 215 Mat4 result = Mat4.zero(); 216 217 Vec3 f = Vec3.norm(Vec3.sub(center, eye)); 218 Vec3 s = Vec3.norm(Vec3.cross(f, up)); 219 Vec3 u = Vec3.cross(s, f); 220 221 result.m[0][0] = s.x; 222 result.m[0][1] = u.x; 223 result.m[0][2] = -f.x; 224 225 result.m[1][0] = s.y; 226 result.m[1][1] = u.y; 227 result.m[1][2] = -f.y; 228 229 result.m[2][0] = s.z; 230 result.m[2][1] = u.z; 231 result.m[2][2] = -f.z; 232 233 result.m[3][0] = -Vec3.dot(s, eye); 234 result.m[3][1] = -Vec3.dot(u, eye); 235 result.m[3][2] = Vec3.dot(f, eye); 236 result.m[3][3] = 1; 237 238 return result; 239 } 240 241 /** Creates a rotation matrix around a specified axis */ 242 static Mat4 rotate(float angle, Vec3 axis) @nogc nothrow 243 { 244 Mat4 result = Mat4.identity; 245 246 axis = Vec3.norm(axis); 247 float sinTheta = sin(radians(angle)); 248 float cosTheta = cos(radians(angle)); 249 float cosValue = 1.0 - cosTheta; 250 251 result.m[0][0] = (axis.x * axis.x * cosValue) + cosTheta; 252 result.m[0][1] = (axis.x * axis.y * cosValue) + (axis.z * sinTheta); 253 result.m[0][2] = (axis.x * axis.z * cosValue) - (axis.y * sinTheta); 254 255 result.m[1][0] = (axis.y * axis.x * cosValue) - (axis.z * sinTheta); 256 result.m[1][1] = (axis.y * axis.y * cosValue) + cosTheta; 257 result.m[1][2] = (axis.y * axis.z * cosValue) + (axis.x * sinTheta); 258 259 result.m[2][0] = (axis.z * axis.x * cosValue) + (axis.y * sinTheta); 260 result.m[2][1] = (axis.z * axis.y * cosValue) - (axis.x * sinTheta); 261 result.m[2][2] = (axis.z * axis.z * cosValue) + cosTheta; 262 263 return result; 264 } 265 266 /** Creates a translation matrix */ 267 static Mat4 translate(Vec3 translation) @nogc nothrow 268 { 269 Mat4 result = Mat4.identity; 270 271 result.m[3][0] = translation.x; 272 result.m[3][1] = translation.y; 273 result.m[3][2] = translation.z; 274 275 return result; 276 } 277 } 278 279 /** Converts degrees to radians */ 280 float radians(float deg) @nogc nothrow 281 { 282 return deg * (PI / 180.0); 283 } 284 285 unittest 286 { 287 import std.math : isClose; 288 289 // Vec3.zero test 290 { 291 auto v = Vec3.zero(); 292 assert(v.x.isClose(0.0)); 293 assert(v.y.isClose(0.0)); 294 assert(v.z.isClose(0.0)); 295 } 296 297 } 298 299 unittest 300 { 301 import std.math : isClose; 302 303 // Vec3.new test 304 { 305 auto v = Vec3(1.0, 2.0, 3.0); 306 assert(v.x.isClose(1.0)); 307 assert(v.y.isClose(2.0)); 308 assert(v.z.isClose(3.0)); 309 } 310 311 } 312 313 unittest 314 { 315 import std.math : isClose; 316 317 // Mat4.identity test 318 { 319 auto m = Mat4.identity(); 320 foreach (i; 0 .. 4) 321 { 322 foreach (j; 0 .. 4) 323 { 324 if (i == j) 325 { 326 assert(m.m[i][j].isClose(1.0)); 327 } 328 else 329 { 330 assert(m.m[i][j].isClose(0.0)); 331 } 332 } 333 } 334 } 335 }