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