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