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     // zig stdlib no-libc math functions
17     enum PI = 3.14159265358979323846264338327950288419716939937510;
18     double zig_cos(double value) @nogc nothrow @trusted;
19     double zig_floor(double value) @nogc nothrow @trusted;
20     double zig_sin(double value) @nogc nothrow @trusted;
21     double zig_sqrt(size_t value) @nogc nothrow @trusted;
22     double zig_sqrtf(double value) @nogc nothrow @trusted;
23     double zig_tan(double value) @nogc nothrow @trusted;
24     alias cos = zig_cos;
25     alias floor = zig_floor;
26     alias sin = zig_sin;
27     alias tan = zig_tan;
28 
29     auto sqrt(T)(T value) {
30         static if (is(T == double) || is(T == float)) {
31             return zig_sqrtf(value);
32         } else {
33             return zig_sqrt(value);
34         }
35     }
36 } else {
37     public import core.stdc.math : sqrt, cos, sin, tan, floor;
38     public import std.math : PI;
39 }
40 
41 struct Vec2 {
42     float x = 0.0, y = 0.0;
43 
44     static Vec2 zero() @nogc nothrow {
45         return Vec2(0, 0);
46     }
47 
48     this(float x, float y) @nogc nothrow {
49         this.x = x;
50         this.y = y;
51     }
52 }
53 
54 struct Vec3 {
55     float x = 0.0, y = 0.0, z = 0.0;
56 
57     static Vec3 zero() @nogc nothrow {
58         return Vec3(0, 0, 0);
59     }
60 
61     this(float x, float y, float z) @nogc nothrow {
62         this.x = x;
63         this.y = y;
64         this.z = z;
65     }
66 
67     static Vec3 up() @nogc nothrow {
68         return Vec3(0, 1, 0);
69     }
70 
71     float len() const @nogc nothrow {
72         return sqrt(dot(this, this));
73     }
74 
75     static Vec3 add(Vec3 left, Vec3 right) @nogc nothrow {
76         return Vec3(left.x + right.x, left.y + right.y, left.z + right.z);
77     }
78 
79     static Vec3 sub(Vec3 left, Vec3 right) @nogc nothrow {
80         return Vec3(left.x - right.x, left.y - right.y, left.z - right.z);
81     }
82 
83     static Vec3 mul(Vec3 v, float s) @nogc nothrow {
84         return Vec3(v.x * s, v.y * s, v.z * s);
85     }
86 
87     static Vec3 norm(Vec3 v) @nogc nothrow {
88         auto l = v.len();
89         if (l != 0) {
90             return Vec3(v.x / l, v.y / l, v.z / l);
91         } else {
92             return Vec3.zero;
93         }
94     }
95 
96     static Vec3 cross(Vec3 v0, Vec3 v1) @nogc nothrow {
97         return Vec3(
98             (v0.y * v1.z) - (v0.z * v1.y),
99             (v0.z * v1.x) - (v0.x * v1.z),
100             (v0.x * v1.y) - (v0.y * v1.x)
101         );
102     }
103 
104     static float dot(Vec3 v0, Vec3 v1) @nogc nothrow {
105         return v0.x * v1.x + v0.y * v1.y + v0.z * v1.z;
106     }
107 }
108 
109 struct Mat4 {
110     float[4][4] m;
111 
112     static Mat4 identity() @nogc nothrow {
113         return Mat4([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]);
114     }
115 
116     static Mat4 zero() @nogc nothrow {
117         return Mat4([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]);
118     }
119 
120     static Mat4 mul(Mat4 left, Mat4 right) @nogc nothrow {
121         Mat4 result = Mat4.zero;
122         foreach (col; 0 .. 4) {
123             foreach (row; 0 .. 4) {
124                 result.m[col][row] =
125                       left.m[0][row] * right.m[col][0]
126                     + left.m[1][row] * right.m[col][1]
127                     + left.m[2][row] * right.m[col][2]
128                     + left.m[3][row] * right.m[col][3];
129             }
130         }
131         return result;
132     }
133 
134     static Mat4 perspective(float fov, float aspect, float near, float far) @nogc nothrow {
135         Mat4 result = Mat4.identity;
136 
137         float t = tan(fov * (PI / 360.0));
138         result.m[0][0] = 1.0 / t;
139         result.m[1][1] = aspect / t;
140         result.m[2][3] = -1.0;
141         result.m[2][2] = (near + far) / (near - far);
142         result.m[3][2] = (2.0 * near * far) / (near - far);
143         result.m[3][3] = 0.0;
144 
145         return result;
146     }
147 
148     static Mat4 lookAt(Vec3 eye, Vec3 center, Vec3 up) @nogc nothrow {
149         Mat4 result = Mat4.zero();
150 
151         Vec3 f = Vec3.norm(Vec3.sub(center, eye));
152         Vec3 s = Vec3.norm(Vec3.cross(f, up));
153         Vec3 u = Vec3.cross(s, f);
154 
155         result.m[0][0] = s.x;
156         result.m[0][1] = u.x;
157         result.m[0][2] = -f.x;
158 
159         result.m[1][0] = s.y;
160         result.m[1][1] = u.y;
161         result.m[1][2] = -f.y;
162 
163         result.m[2][0] = s.z;
164         result.m[2][1] = u.z;
165         result.m[2][2] = -f.z;
166 
167         result.m[3][0] = -Vec3.dot(s, eye);
168         result.m[3][1] = -Vec3.dot(u, eye);
169         result.m[3][2] = Vec3.dot(f, eye);
170         result.m[3][3] = 1;
171 
172         return result;
173     }
174 
175     static Mat4 rotate(float angle, Vec3 axis) @nogc nothrow {
176         Mat4 result = Mat4.identity;
177 
178         axis = Vec3.norm(axis);
179         float sinTheta = sin(radians(angle));
180         float cosTheta = cos(radians(angle));
181         float cosValue = 1.0 - cosTheta;
182 
183         result.m[0][0] = (axis.x * axis.x * cosValue) + cosTheta;
184         result.m[0][1] = (axis.x * axis.y * cosValue) + (axis.z * sinTheta);
185         result.m[0][2] = (axis.x * axis.z * cosValue) - (axis.y * sinTheta);
186 
187         result.m[1][0] = (axis.y * axis.x * cosValue) - (axis.z * sinTheta);
188         result.m[1][1] = (axis.y * axis.y * cosValue) + cosTheta;
189         result.m[1][2] = (axis.y * axis.z * cosValue) + (axis.x * sinTheta);
190 
191         result.m[2][0] = (axis.z * axis.x * cosValue) + (axis.y * sinTheta);
192         result.m[2][1] = (axis.z * axis.y * cosValue) - (axis.x * sinTheta);
193         result.m[2][2] = (axis.z * axis.z * cosValue) + cosTheta;
194 
195         return result;
196     }
197 
198     static Mat4 translate(Vec3 translation) @nogc nothrow {
199         Mat4 result = Mat4.identity;
200 
201         result.m[3][0] = translation.x;
202         result.m[3][1] = translation.y;
203         result.m[3][2] = translation.z;
204 
205         return result;
206     }
207 }
208 
209 float radians(float deg) @nogc nothrow {
210     return deg * (PI / 180.0);
211 }
212 
213 unittest {
214     import std.math : isClose;
215 
216     // Vec3.zero test
217     {
218         auto v = Vec3.zero();
219         assert(v.x.isClose(0.0));
220         assert(v.y.isClose(0.0));
221         assert(v.z.isClose(0.0));
222     }
223 
224 }
225 
226 unittest {
227     import std.math : isClose;
228 
229     // Vec3.new test
230     {
231         auto v = Vec3(1.0, 2.0, 3.0);
232         assert(v.x.isClose(1.0));
233         assert(v.y.isClose(2.0));
234         assert(v.z.isClose(3.0));
235     }
236 
237 }
238 
239 unittest {
240     import std.math : isClose;
241 
242     // Mat4.identity test
243     {
244         auto m = Mat4.identity();
245         foreach (i; 0 .. 4) {
246             foreach (j; 0 .. 4) {
247                 if (i == j) {
248                     assert(m.m[i][j].isClose(1.0));
249                 } else {
250                     assert(m.m[i][j].isClose(0.0));
251                 }
252             }
253         }
254     }
255 }