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 static if (is(T == Rotor3)) 40 { 41 /// Returns a rotor equivalent to first apply p, then apply q 42 inout Rotor3 opBinary(string op)(inout Rotor3 q) if (op == "*") 43 { 44 alias p = this; 45 Rotor3 r; 46 r.a = p.a * q.a - p.i * q.i - p.j * q.j - p.k * q.k; 47 r.i = p.i * q.a + p.a * q.i + p.j * q.k - p.k * q.j; 48 r.j = p.j * q.a + p.a * q.j + p.k * q.i - p.i * q.k; 49 r.k = p.k * q.a + p.a * q.k + p.i * q.j - p.j * q.i; 50 return r; 51 } 52 53 inout Vector3 opBinary(string op)(inout Vector3 v) if (op == "*") 54 { 55 Vector3 rv; 56 rv.x = a * v.x + xy * v.y - zx * v.z; 57 rv.y = a * v.y + yz * v.z - xy * v.x; 58 rv.z = a * v.z + zx * v.x - yz * v.y; 59 return rv; 60 } 61 62 inout Vector3 opBinaryRight(string op)(inout Vector3 v) if (op == "*") 63 { 64 Vector3 vr; 65 vr.x = v.x * a - v.y * xy + v.z * zx; 66 vr.y = v.y * a - v.z * yz + v.x * xy; 67 vr.z = v.z * a - v.x * zx + v.y * yz; 68 return vr; 69 } 70 } 71 else 72 { 73 inout T opBinary(string op)(inout T rhs) if (["+", "-"].canFind(op)) 74 { 75 enum fragment = [FieldNameTuple!T].map!(field => field ~ op ~ "rhs." ~ field).join(","); 76 return mixin("T(" ~ fragment ~ ")"); 77 } 78 79 ref T opOpAssign(string op)(inout T rhs) if (["+", "-"].canFind(op)) 80 { 81 static foreach (field; [FieldNameTuple!T]) 82 mixin(field ~ op ~ "= rhs." ~ field ~ ";"); 83 return this; 84 } 85 } 86 87 inout T opBinary(string op)(inout float rhs) if (["+", "-", "*", "/"].canFind(op)) 88 { 89 enum fragment = [FieldNameTuple!T].map!(field => field ~ op ~ "rhs").join(","); 90 return mixin("T(" ~ fragment ~ ")"); 91 } 92 93 inout T opBinaryRight(string op)(inout float lhs) if (["+", "-", "*", "/"].canFind(op)) 94 { 95 enum fragment = [FieldNameTuple!T].map!(field => "lhs" ~ op ~ field).join(","); 96 return mixin("T(" ~ fragment ~ ")"); 97 } 98 99 ref T opOpAssign(string op)(inout float rhs) if (["+", "-", "*", "/"].canFind(op)) 100 { 101 static foreach (field; [FieldNameTuple!T]) 102 mixin(field ~ op ~ "= rhs;"); 103 return this; 104 } 105 } 106 107 unittest 108 { 109 Assert.equal(Vector2.init, Vector2.zero); 110 Assert.equal(Vector2(), Vector2.zero); 111 Assert.equal(-Vector2(1, 2), Vector2(-1, -2)); 112 auto a = Vector3(1, 2, 9); 113 immutable b = Vector3(3, 4, 9); 114 Vector3 c = a + b; 115 Assert.equal(c, Vector3(4, 6, 18)); 116 Assert.equal(4.0f - Vector2.zero, Vector2(4, 4)); 117 Assert.equal(Vector2.one - 3.0f, Vector2(-2, -2)); 118 a += 5; 119 Assert.equal(a, Vector3(6, 7, 14)); 120 a *= 0.5; 121 Assert.equal(a, Vector3(3, 3.5, 7)); 122 a += Vector3(3, 2.5, -1); 123 Assert.equal(a, Vector3(6, 6, 6)); 124 } 125 126 import std.traits : FieldNameTuple; 127 import std.algorithm : map; 128 import std.range : join; 129 130 float length(T)(T v) 131 { 132 enum fragment = [FieldNameTuple!T].map!(field => "v." ~ field ~ "*" ~ "v." ~ field).join("+"); 133 return mixin("sqrt(" ~ fragment ~ ")"); 134 } 135 136 T normal(T)(T v) 137 { 138 return v / v.length; 139 } 140 141 float distance(T)(T lhs, T rhs) 142 { 143 return (lhs - rhs).length; 144 } 145 146 float dot(T)(T lhs, T rhs) 147 { 148 enum fragment = [FieldNameTuple!T].map!(field => "lhs." ~ field ~ "*" ~ "rhs." ~ field).join( 149 "+"); 150 return mixin(fragment); 151 } 152 153 unittest 154 { 155 Assert.equal(Vector2(3, 4).length, 5); 156 const a = Vector2(-3, 4); 157 Assert.equal(a.normal, Vector2(-3. / 5., 4. / 5.)); 158 immutable b = Vector2(9, 8); 159 Assert.equal(b.distance(Vector2(-3, 3)), 13); 160 Assert.equal(Vector3(2, 3, 4).dot(Vector3(4, 5, 6)), 47); 161 Assert.equal(Vector2.one.length, sqrt(2.0f)); 162 } 163 164 unittest 165 { 166 Assert.equal(Rotor3(1, 2, 3, 4), Rotor3(1, Bivector3(2, 3, 4))); 167 } 168 169 /// Mix `amount` of `lhs` with `1-amount` of `rhs` 170 /// `amount` should be between 0 and 1, but can be anything 171 /// lerp(lhs, rhs, 0) == lhs 172 /// lerp(lhs, rhs, 1) == rhs 173 T lerp(T)(T lhs, T rhs, float amount) 174 { 175 return lhs + amount * (rhs - lhs); 176 } 177 178 /// angle betwenn vector and x-axis (+y +x -> positive) 179 float angle(Vector2 v) 180 { 181 return atan2(v.y, v.x); 182 } 183 184 Vector2 rotate(Vector2 v, float angle) 185 { 186 return Vector2(v.x * cos(angle) - v.y * sin(angle), v.x * sin(angle) + v.y * cos(angle)); 187 } 188 189 Vector2 slide(Vector2 v, Vector2 along) 190 { 191 return along.normal * dot(v, along); 192 } 193 194 Bivector2 wedge(Vector2 lhs, Vector2 rhs) 195 { 196 Bivector2 result = {xy: lhs.x * rhs.y - lhs.y * rhs.x}; 197 return result; 198 } 199 200 // dfmt off 201 Bivector3 wedge(Vector3 lhs, Vector3 rhs) 202 { 203 Bivector3 result = { 204 xy: lhs.x * rhs.y - lhs.y * rhs.x, 205 yz: lhs.y * rhs.z - lhs.z * rhs.y, 206 zx: lhs.z * rhs.x - lhs.x * rhs.z, 207 }; 208 return result; 209 } 210 211 Vector3 transform(Vector3 v, Matrix4 mat) 212 { 213 with (v) with (mat) 214 return Vector3( 215 m0 * x + m4 * y + m8 * z + m12, 216 m1 * x + m5 * y + m9 * z + m13, 217 m2 * x + m6 * y + m10 * z + m14 218 ); 219 } 220 // dfmt on 221 222 Vector3 cross(Vector3 lhs, Vector3 rhs) 223 { 224 auto v = wedge(lhs, rhs); 225 return Vector3(v.yz, v.zx, v.xy); 226 } 227 228 unittest { 229 // TODO 230 } 231 232 /// Returns a unit rotor that rotates `from` to `to` 233 Rotor3 rotation(Vector3 from, Vector3 to) 234 { 235 return Rotor3(1 + dot(to, from), wedge(to, from)).normal; 236 } 237 238 Rotor3 rotation(float angle, Bivector3 plane) 239 { 240 return Rotor3(cos(angle / 2.0f), -sin(angle / 2.0f) * plane); 241 } 242 243 /// Rotate q by p 244 Rotor3 rotate(Rotor3 p, Rotor3 q) 245 { 246 return p * q * p.reverse; 247 } 248 249 /// Rotate v by r 250 Vector3 rotate(Rotor3 r, Vector3 v) 251 { 252 return r * v * r.reverse; 253 } 254 255 Rotor3 reverse(Rotor3 r) 256 { 257 return Rotor3(r.a, -r.b); 258 } 259 260 unittest 261 { 262 // TODO 263 }