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 }