1 module raymathext;
2 
3 import raylib;
4 import std.math;
5 
6 pragma(inline, true):
7 
8 version (unittest)
9 {
10     import fluent.asserts;
11 }
12 
13 mixin template Linear()
14 {
15     import std.algorithm : canFind, map;
16     import std.range : join;
17     import std.traits : FieldNameTuple;
18 
19     private static alias T = typeof(this);
20 
21     static T zero()
22     {
23         enum fragment = [FieldNameTuple!T].map!(field => "0.").join(",");
24         return mixin("T(" ~ fragment ~ ")");
25     }
26 
27     static T one()
28     {
29         enum fragment = [FieldNameTuple!T].map!(field => "1.").join(",");
30         return mixin("T(" ~ fragment ~ ")");
31     }
32 
33     inout T opUnary(string op)() if (["+", "-"].canFind(op))
34     {
35         enum fragment = [FieldNameTuple!T].map!(field => op ~ field).join(",");
36         return mixin("T(" ~ fragment ~ ")");
37     }
38 
39     inout T opBinary(string op)(inout T rhs) if (["+", "-"].canFind(op))
40     {
41         enum fragment = [FieldNameTuple!T].map!(field => field ~ op ~ "rhs." ~ field).join(",");
42         return mixin("T(" ~ fragment ~ ")");
43     }
44 
45     inout T opBinary(string op)(inout float rhs) if (["+", "-", "*", "/"].canFind(op))
46     {
47         enum fragment = [FieldNameTuple!T].map!(field => field ~ op ~ "rhs").join(",");
48         return mixin("T(" ~ fragment ~ ")");
49     }
50 
51     inout T opBinaryRight(string op)(inout float lhs) if (["+", "-", "*", "/"].canFind(op))
52     {
53         enum fragment = [FieldNameTuple!T].map!(field => "lhs" ~ op ~ field).join(",");
54         return mixin("T(" ~ fragment ~ ")");
55     }
56 }
57 
58 unittest
59 {
60     Assert.equal(Vector2.init, Vector2.zero);
61     Assert.equal(Vector2(), Vector2.zero);
62     Assert.equal(-Vector2(1, 2), Vector2(-1, -2));
63     auto a = Vector3(1, 2, 9);
64     immutable b = Vector3(3, 4, 9);
65     Vector3 c = a + b;
66     Assert.equal(c, Vector3(4, 6, 18));
67     Assert.equal(4.0f - Vector2.zero, Vector2(4, 4));
68     Assert.equal(Vector2.one - 3.0f, Vector2(-2, -2));
69 }
70 
71 import std.traits : FieldNameTuple;
72 import std.algorithm : map;
73 import std.range : join;
74 
75 float length(T)(T v)
76 {
77     enum fragment = [FieldNameTuple!T].map!(field => "v." ~ field ~ "*" ~ "v." ~ field).join("+");
78     return mixin("sqrt(" ~ fragment ~ ")");
79 }
80 
81 T normal(T)(T v)
82 {
83     return v / v.length;
84 }
85 
86 float distance(T)(T lhs, T rhs)
87 {
88     return (lhs - rhs).length;
89 }
90 
91 float dot(T)(T lhs, T rhs)
92 {
93     enum fragment = [FieldNameTuple!T].map!(field => "lhs." ~ field ~ "*" ~ "rhs." ~ field).join(
94                 "+");
95     return mixin(fragment);
96 }
97 
98 unittest
99 {
100     Assert.equal(Vector2(3, 4).length, 5);
101     const a = Vector2(-3, 4);
102     Assert.equal(a.normal, Vector2(-3. / 5., 4. / 5.));
103     immutable b = Vector2(9, 8);
104     Assert.equal(b.distance(Vector2(-3, 3)), 13);
105     Assert.equal(Vector3(2, 3, 4).dot(Vector3(4, 5, 6)), 47);
106     Assert.equal(Vector2.one.length, sqrt(2.0f));
107 }
108 
109 unittest
110 {
111     Assert.equal(Rotor3(1, 2, 3, 4), Rotor3(1, Bivector3(2, 3, 4)));
112 }
113 
114 /// Mix `amount` of `lhs` with `1-amount` of `rhs`
115 ///   `amount` should be between 0 and 1, but can be anything
116 ///   lerp(lhs, rhs, 0) == lhs
117 ///   lerp(lhs, rhs, 1) == rhs
118 T lerp(T)(T lhs, T rhs, float amount)
119 {
120     return lhs + amount * (rhs - lhs);
121 }
122 
123 /// angle betwenn vector and x-axis (+y +x -> positive)
124 float angle(Vector2 v)
125 {
126     return atan2(v.y, v.x);
127 }
128 
129 Vector2 rotate(Vector2 v, float angle)
130 {
131     return Vector2(v.x * cos(angle) - v.y * sin(angle), v.x * sin(angle) + v.y * cos(angle));
132 }
133 
134 Vector2 slide(Vector2 v, Vector2 along)
135 {
136     return along.normal * dot(v, along);
137 }
138 
139 Vector3 transform(Vector3 v, Matrix4 mat)
140 {
141     with (v) with (mat)
142         return Vector3(
143             m0 * x + m4 * y + m8 * z + m12,
144             m1 * x + m5 * y + m9 * z + m13,
145             m2 * x + m6 * y + m10 * z + m14
146         );
147 }
148 // dfmt on
149 
150 unittest {
151     // TODO
152 }