|
@@ -1,6 +1,13 @@
|
|
|
import raylib;
|
|
|
import std.math;
|
|
|
|
|
|
+pragma(inline, true):
|
|
|
+
|
|
|
+version (unittest)
|
|
|
+{
|
|
|
+ import fluent.asserts;
|
|
|
+}
|
|
|
+
|
|
|
mixin template Linear()
|
|
|
{
|
|
|
import std.algorithm : canFind, map;
|
|
@@ -27,10 +34,45 @@ mixin template Linear()
|
|
|
return mixin("T(" ~ fragment ~ ")");
|
|
|
}
|
|
|
|
|
|
- T opBinary(string op)(T rhs) if (["+", "-", "*", "/"].canFind(op))
|
|
|
+ static if (is(T == Rotor3))
|
|
|
{
|
|
|
- enum fragment = [FieldNameTuple!T].map!(field => field ~ op ~ "rhs." ~ field).join(",");
|
|
|
- return mixin("T(" ~ fragment ~ ")");
|
|
|
+ /// Returns a rotor equivalent to first apply p, then apply q
|
|
|
+ Rotor3 opBinary(string op)(Rotor3 q) if (op == "*")
|
|
|
+ {
|
|
|
+ alias p = this;
|
|
|
+ Rotor3 r;
|
|
|
+ r.a = p.a * q.a - p.i * q.i - p.j * q.j - p.k * q.k;
|
|
|
+ r.i = p.i * q.a + p.a * q.i + p.j * q.k - p.k * q.j;
|
|
|
+ r.j = p.j * q.a + p.a * q.j + p.k * q.i - p.i * q.k;
|
|
|
+ r.k = p.k * q.a + p.a * q.k + p.i * q.j - p.j * q.i;
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+
|
|
|
+ Vector3 opBinary(string op)(Vector3 v) if (op == "*")
|
|
|
+ {
|
|
|
+ Vector3 rv;
|
|
|
+ rv.x = a * v.x + xy * v.y - zx * v.z;
|
|
|
+ rv.y = a * v.y + yz * v.z - xy * v.x;
|
|
|
+ rv.z = a * v.z + zx * v.x - yz * v.y;
|
|
|
+ return rv;
|
|
|
+ }
|
|
|
+
|
|
|
+ Vector3 opBinaryRight(string op)(Vector3 v) if (op == "*")
|
|
|
+ {
|
|
|
+ Vector3 vr;
|
|
|
+ vr.x = v.x * a - v.y * xy + v.z * zx;
|
|
|
+ vr.y = v.y * a - v.z * yz + v.x * xy;
|
|
|
+ vr.z = v.z * a - v.x * zx + v.y * yz;
|
|
|
+ return vr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ T opBinary(string op)(T rhs) if (["+", "-"].canFind(op))
|
|
|
+ {
|
|
|
+ enum fragment = [FieldNameTuple!T].map!(field => field ~ op ~ "rhs." ~ field).join(",");
|
|
|
+ return mixin("T(" ~ fragment ~ ")");
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
T opBinary(string op)(float rhs) if (["+", "-", "*", "/"].canFind(op))
|
|
@@ -46,6 +88,14 @@ mixin template Linear()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+unittest
|
|
|
+{
|
|
|
+ Assert.equal(-Vector2(1, 2), Vector2(-1, -2));
|
|
|
+ Assert.equal(Vector3(1, 2, 9) + Vector3(3, 4, 9), Vector3(4, 6, 18));
|
|
|
+ Assert.equal(4.0f - Vector2.zero, Vector2(4, 4));
|
|
|
+ Assert.equal(Vector2.one - 3.0f, Vector2(-2, -2));
|
|
|
+}
|
|
|
+
|
|
|
import std.traits : FieldNameTuple;
|
|
|
import std.algorithm : map;
|
|
|
import std.range : join;
|
|
@@ -56,7 +106,7 @@ float length(T)(T v)
|
|
|
return mixin("sqrt(" ~ fragment ~ ")");
|
|
|
}
|
|
|
|
|
|
-T normalize(T)(T v)
|
|
|
+T normal(T)(T v)
|
|
|
{
|
|
|
return v / v.length;
|
|
|
}
|
|
@@ -66,18 +116,25 @@ float distance(T)(T lhs, T rhs)
|
|
|
return (lhs - rhs).length;
|
|
|
}
|
|
|
|
|
|
-T dot(T)(T lhs, T rhs)
|
|
|
+float dot(T)(T lhs, T rhs)
|
|
|
{
|
|
|
- return lhs * rhs;
|
|
|
+ enum fragment = [FieldNameTuple!T].map!(field => "lhs." ~ field ~ "*" ~ "rhs." ~ field).join(
|
|
|
+ "+");
|
|
|
+ return mixin(fragment);
|
|
|
}
|
|
|
|
|
|
unittest
|
|
|
{
|
|
|
- assert(-Vector2(1, 2) == Vector2(-1, -2));
|
|
|
- assert(Vector3(1, 2, 9) + Vector3(3, 4, 9) == Vector3(4, 6, 18));
|
|
|
- assert(4.0f - Vector2.zero == Vector2(4, 4));
|
|
|
- assert(Vector2.one - 3.0f == Vector2(-2, -2));
|
|
|
- assert(Vector2(3, 4).length == 5.0);
|
|
|
+ Assert.equal(Vector2(3, 4).length, 5);
|
|
|
+ Assert.equal(cast(const) Vector2(-3, 4).normal, Vector2(-3. / 5., 4. / 5.));
|
|
|
+ Assert.equal(cast(immutable) Vector2(9, 8).distance(Vector2(-3, 3)), 13);
|
|
|
+ Assert.equal(Vector3(2, 3, 4).dot(Vector3(4, 5, 6)), 47);
|
|
|
+ Assert.equal(Vector2.one.length, sqrt(2.0f));
|
|
|
+}
|
|
|
+
|
|
|
+unittest
|
|
|
+{
|
|
|
+ Assert.equal(Rotor3(1, 2, 3, 4), Rotor3(1, Bivector3(2, 3, 4)));
|
|
|
}
|
|
|
|
|
|
/// Mix `amount` of `lhs` with `1-amount` of `rhs`
|
|
@@ -89,17 +146,12 @@ T lerp(T)(T lhs, T rhs, float amount)
|
|
|
return lhs + amount * (rhs - lhs);
|
|
|
}
|
|
|
|
|
|
+/// angle betwenn vector and x-axis (+y +x -> positive)
|
|
|
float angle(Vector2 v)
|
|
|
{
|
|
|
return atan2(v.y, v.x);
|
|
|
}
|
|
|
|
|
|
-/// Rotate on imaginary plane
|
|
|
-Vector2 rotate(Vector2 lhs, Vector2 rhs)
|
|
|
-{
|
|
|
- return Vector2(lhs.x * rhs.x - lhs.y * rhs.y, lhs.x * rhs.y + lhs.y * rhs.x);
|
|
|
-}
|
|
|
-
|
|
|
Vector2 rotate(Vector2 v, float angle)
|
|
|
{
|
|
|
return Vector2(v.x * cos(angle) - v.y * sin(angle), v.x * sin(angle) + v.y * cos(angle));
|
|
@@ -107,22 +159,24 @@ Vector2 rotate(Vector2 v, float angle)
|
|
|
|
|
|
Vector2 slide(Vector2 v, Vector2 along)
|
|
|
{
|
|
|
- return along.normalize * dot(v, along);
|
|
|
+ return along.normal * dot(v, along);
|
|
|
}
|
|
|
|
|
|
Bivector2 wedge(Vector2 lhs, Vector2 rhs)
|
|
|
{
|
|
|
- return Bivector2(lhs.x * rhs.y - lhs.y * rhs.x);
|
|
|
+ Bivector2 result = {xy: lhs.x * rhs.y - lhs.y * rhs.x};
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
// dfmt off
|
|
|
Bivector3 wedge(Vector3 lhs, Vector3 rhs)
|
|
|
{
|
|
|
- return Bivector3(
|
|
|
- lhs.y * rhs.z - lhs.z * rhs.y,
|
|
|
- lhs.z * rhs.x - lhs.x * rhs.z,
|
|
|
- lhs.x * rhs.y - lhs.y * rhs.x,
|
|
|
- );
|
|
|
+ Bivector3 result = {
|
|
|
+ xy: lhs.x * rhs.y - lhs.y * rhs.x,
|
|
|
+ yz: lhs.y * rhs.z - lhs.z * rhs.y,
|
|
|
+ zx: lhs.z * rhs.x - lhs.x * rhs.z,
|
|
|
+ };
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
Vector3 transform(Vector3 v, Matrix4 mat)
|
|
@@ -142,14 +196,39 @@ Vector3 cross(Vector3 lhs, Vector3 rhs)
|
|
|
return Vector3(v.yz, v.zx, v.xy);
|
|
|
}
|
|
|
|
|
|
-// TODO implement rotor3
|
|
|
-// Vector3 rotate(Vector3 v, Rotor3 r) {
|
|
|
-// return ;
|
|
|
-// }
|
|
|
+unittest {
|
|
|
+ // TODO
|
|
|
+}
|
|
|
+
|
|
|
+/// Returns a unit rotor that rotates `from` to `to`
|
|
|
+Rotor3 rotation(Vector3 from, Vector3 to)
|
|
|
+{
|
|
|
+ return Rotor3(1 + dot(to, from), wedge(to, from)).normal;
|
|
|
+}
|
|
|
+
|
|
|
+Rotor3 rotation(float angle, Bivector3 plane)
|
|
|
+{
|
|
|
+ return Rotor3(cos(angle / 2.0f), -sin(angle / 2.0f) * plane);
|
|
|
+}
|
|
|
+
|
|
|
+/// Rotate q by p
|
|
|
+Rotor3 rotate(Rotor3 p, Rotor3 q)
|
|
|
+{
|
|
|
+ return p * q * p.reverse;
|
|
|
+}
|
|
|
+
|
|
|
+/// Rotate v by r
|
|
|
+Vector3 rotate(Rotor3 r, Vector3 v)
|
|
|
+{
|
|
|
+ return r * v * r.reverse;
|
|
|
+}
|
|
|
+
|
|
|
+Rotor3 reverse(Rotor3 r)
|
|
|
+{
|
|
|
+ return Rotor3(r.a, -r.b);
|
|
|
+}
|
|
|
|
|
|
unittest
|
|
|
{
|
|
|
- import std.format;
|
|
|
- const v = Vector2.one;
|
|
|
- assert(v.length == sqrt(2.0f), "%s %s".format(v,v.length));
|
|
|
-}
|
|
|
+ // TODO
|
|
|
+}
|