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