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 }