1 module raylib.raymathext;
2 
3 import raylib;
4 import std.math;
5 
6 pragma(inline, true):
7 
8 // Bivector2 type
9 struct Bivector2
10 {
11     float xy = 0.0f;
12     alias xy this;
13     mixin Linear;
14 }
15 
16 // Bivector3 type
17 /// Beware of the field order
18 /// xy is the first field
19 struct Bivector3
20 {
21     float xy = 0.0f;
22     float yz = 0.0f;
23     float zx = 0.0f;
24     mixin Linear;
25 }
26 
27 // Rotor type
28 struct Rotor3
29 {
30     float a = 1.0f;
31     float xy = 0.0f;
32     float yz = 0.0f;
33     float zx = 0.0f;
34     mixin Linear;
35 
36     alias i = yz;
37     alias j = zx;
38     alias k = xy;
39 
40     @property Bivector3 b()
41     {
42         return Bivector3(xy, yz, zx);
43     }
44 
45     @property Bivector3 b(Bivector3 _b)
46     {
47         xy = _b.xy;
48         yz = _b.yz;
49         zx = _b.zx;
50         return _b;
51     }
52 
53     this(float _a, Bivector3 _b)
54     {
55         a = _a;
56         b = _b;
57     }
58 
59     this(float _a, float _xy, float _yz, float _zx)
60     {
61         a = _a;
62         xy = _xy;
63         yz = _yz;
64         zx = _zx;
65     }
66 }
67 
68 alias Matrix4 = Matrix;
69 
70 mixin template Linear()
71 {
72     import std.algorithm : canFind, map;
73     import std.range : join;
74     import std.traits : FieldNameTuple;
75 
76     private static alias T = typeof(this);
77 
78     static T zero()
79     {
80         enum fragment = [FieldNameTuple!T].map!(field => "0.").join(",");
81         return mixin("T(" ~ fragment ~ ")");
82     }
83 
84     static T one()
85     {
86         enum fragment = [FieldNameTuple!T].map!(field => "1.").join(",");
87         return mixin("T(" ~ fragment ~ ")");
88     }
89 
90     inout T opUnary(string op)() if (["+", "-"].canFind(op))
91     {
92         enum fragment = [FieldNameTuple!T].map!(field => op ~ field).join(",");
93         return mixin("T(" ~ fragment ~ ")");
94     }
95 
96     static if (is(T == Rotor3))
97     {
98         /// Returns a rotor equivalent to first apply p, then apply q
99         inout Rotor3 opBinary(string op)(inout Rotor3 q) if (op == "*")
100         {
101             alias p = this;
102             Rotor3 r;
103             r.a = p.a * q.a - p.i * q.i - p.j * q.j - p.k * q.k;
104             r.i = p.i * q.a + p.a * q.i + p.j * q.k - p.k * q.j;
105             r.j = p.j * q.a + p.a * q.j + p.k * q.i - p.i * q.k;
106             r.k = p.k * q.a + p.a * q.k + p.i * q.j - p.j * q.i;
107             return r;
108         }
109 
110         inout Vector3 opBinary(string op)(inout Vector3 v) if (op == "*")
111         {
112             Vector3 rv;
113             rv.x = a * v.x + xy * v.y - zx * v.z;
114             rv.y = a * v.y + yz * v.z - xy * v.x;
115             rv.z = a * v.z + zx * v.x - yz * v.y;
116             return rv;
117         }
118 
119         inout Vector3 opBinaryRight(string op)(inout Vector3 v) if (op == "*")
120         {
121             Vector3 vr;
122             vr.x = v.x * a - v.y * xy + v.z * zx;
123             vr.y = v.y * a - v.z * yz + v.x * xy;
124             vr.z = v.z * a - v.x * zx + v.y * yz;
125             return vr;
126         }
127     }
128     else
129     {
130         inout T opBinary(string op)(inout T rhs) if (["+", "-"].canFind(op))
131         {
132             enum fragment = [FieldNameTuple!T].map!(field => field ~ op ~ "rhs." ~ field).join(",");
133             return mixin("T(" ~ fragment ~ ")");
134         }
135 
136         ref T opOpAssign(string op)(inout T rhs) if (["+", "-"].canFind(op))
137         {
138             static foreach (field; [FieldNameTuple!T])
139                 mixin(field ~ op ~ "= rhs." ~ field ~ ";");
140             return this;
141         }
142     }
143 
144     inout T opBinary(string op)(inout float rhs) if (["+", "-", "*", "/"].canFind(op))
145     {
146         enum fragment = [FieldNameTuple!T].map!(field => field ~ op ~ "rhs").join(",");
147         return mixin("T(" ~ fragment ~ ")");
148     }
149 
150     inout T opBinaryRight(string op)(inout float lhs) if (["+", "-", "*", "/"].canFind(op))
151     {
152         enum fragment = [FieldNameTuple!T].map!(field => "lhs" ~ op ~ field).join(",");
153         return mixin("T(" ~ fragment ~ ")");
154     }
155 
156     ref T opOpAssign(string op)(inout float rhs) if (["+", "-", "*", "/"].canFind(op))
157     {
158         static foreach (field; [FieldNameTuple!T])
159             mixin(field ~ op ~ "= rhs;");
160         return this;
161     }
162 }
163 
164 unittest
165 {
166     assert(Vector2.init == Vector2.zero);
167     assert(Vector2() == Vector2.zero);
168     assert(-Vector2(1, 2) == Vector2(-1, -2));
169     auto a = Vector3(1, 2, 9);
170     immutable b = Vector3(3, 4, 9);
171     Vector3 c = a + b;
172     assert(c == Vector3(4, 6, 18));
173     assert(4.0f - Vector2.zero == Vector2(4, 4));
174     assert(Vector2.one - 3.0f == Vector2(-2, -2));
175     a += 5;
176     assert(a == Vector3(6, 7, 14));
177     a *= 0.5;
178     assert(a == Vector3(3, 3.5, 7));
179     a += Vector3(3, 2.5, -1);
180     assert(a == Vector3(6, 6, 6));
181 }
182 
183 import std.traits : FieldNameTuple;
184 import std.algorithm : map;
185 import std.range : join;
186 
187 float length(T)(T v)
188 {
189     enum fragment = [FieldNameTuple!T].map!(field => "v." ~ field ~ "*" ~ "v." ~ field).join("+");
190     return mixin("sqrt(" ~ fragment ~ ")");
191 }
192 
193 T normal(T)(T v)
194 {
195     return v / v.length;
196 }
197 
198 float distance(T)(T lhs, T rhs)
199 {
200     return (lhs - rhs).length;
201 }
202 
203 float dot(T)(T lhs, T rhs)
204 {
205     enum fragment = [FieldNameTuple!T].map!(field => "lhs." ~ field ~ "*" ~ "rhs." ~ field).join(
206                 "+");
207     return mixin(fragment);
208 }
209 
210 unittest
211 {
212     assert(Vector2(3, 4).length == 5);
213     const a = Vector2(-3, 4);
214     assert(a.normal == Vector2(-3. / 5., 4. / 5.));
215     immutable b = Vector2(9, 8);
216     assert(b.distance(Vector2(-3, 3)) == 13);
217     assert(Vector3(2, 3, 4).dot(Vector3(4, 5, 6)) == 47);
218     assert(Vector2.one.length == sqrt(2.0f));
219 }
220 
221 unittest
222 {
223     assert(Rotor3(1, 2, 3, 4) == Rotor3(1, Bivector3(2, 3, 4)));
224 }
225 
226 /// Mix `amount` of `lhs` with `1-amount` of `rhs`
227 ///   `amount` should be between 0 and 1, but can be anything
228 ///   lerp(lhs, rhs, 0) == lhs
229 ///   lerp(lhs, rhs, 1) == rhs
230 T lerp(T)(T lhs, T rhs, float amount)
231 {
232     return lhs + amount * (rhs - lhs);
233 }
234 
235 /// angle betwenn vector and x-axis (+y +x -> positive)
236 float angle(Vector2 v)
237 {
238     return atan2(v.y, v.x);
239 }
240 
241 Vector2 rotate(Vector2 v, float angle)
242 {
243     return Vector2(v.x * cos(angle) - v.y * sin(angle), v.x * sin(angle) + v.y * cos(angle));
244 }
245 
246 Vector2 slide(Vector2 v, Vector2 along)
247 {
248     return along.normal * dot(v, along);
249 }
250 
251 Bivector2 wedge(Vector2 lhs, Vector2 rhs)
252 {
253     Bivector2 result = {xy: lhs.x * rhs.y - lhs.y * rhs.x};
254     return result;
255 }
256 
257 // dfmt off
258 Bivector3 wedge(Vector3 lhs, Vector3 rhs)
259 {
260     Bivector3 result = {
261         xy: lhs.x * rhs.y - lhs.y * rhs.x,
262         yz: lhs.y * rhs.z - lhs.z * rhs.y,
263         zx: lhs.z * rhs.x - lhs.x * rhs.z,
264     };
265     return result;
266 }
267 
268 Vector3 transform(Vector3 v, Matrix4 mat)
269 {
270     with (v) with (mat)
271         return Vector3(
272             m0 * x + m4 * y + m8 * z + m12,
273             m1 * x + m5 * y + m9 * z + m13,
274             m2 * x + m6 * y + m10 * z + m14
275         );
276 }
277 // dfmt on
278 
279 Vector3 cross(Vector3 lhs, Vector3 rhs)
280 {
281     auto v = wedge(lhs, rhs);
282     return Vector3(v.yz, v.zx, v.xy);
283 }
284 
285 unittest {
286     // TODO
287 }
288 
289 /// Returns a unit rotor that rotates `from` to `to`
290 Rotor3 rotation(Vector3 from, Vector3 to)
291 {
292     return Rotor3(1 + dot(to, from), wedge(to, from)).normal;
293 }
294 
295 Rotor3 rotation(float angle, Bivector3 plane)
296 {
297     return Rotor3(cos(angle / 2.0f), -sin(angle / 2.0f) * plane);
298 }
299 
300 /// Rotate q by p
301 Rotor3 rotate(Rotor3 p, Rotor3 q)
302 {
303     return p * q * p.reverse;
304 }
305 
306 /// Rotate v by r
307 Vector3 rotate(Rotor3 r, Vector3 v)
308 {
309     return r * v * r.reverse;
310 }
311 
312 Rotor3 reverse(Rotor3 r)
313 {
314     return Rotor3(r.a, -r.b);
315 }
316 
317 unittest
318 {
319     // TODO
320 }