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 }