Add WRot SLerp and axis-angle ctor.

These allow more complex calculations to be made
using rotations.
This commit is contained in:
Paul Chote
2021-08-21 10:17:11 +01:00
committed by reaperrr
parent d509d3f5f9
commit c88d1bbefa

View File

@@ -24,6 +24,9 @@ namespace OpenRA
// Internal calculations use the quaternion form // Internal calculations use the quaternion form
readonly int x, y, z, w; readonly int x, y, z, w;
/// <summary>
/// Construct a rotation from euler angles.
/// </summary>
public WRot(WAngle roll, WAngle pitch, WAngle yaw) public WRot(WAngle roll, WAngle pitch, WAngle yaw)
{ {
Roll = roll; Roll = roll;
@@ -48,6 +51,21 @@ namespace OpenRA
w = (int)((cr * cp * cy + sr * sp * sy) / 1048576); w = (int)((cr * cp * cy + sr * sp * sy) / 1048576);
} }
/// <summary>
/// Construct a rotation from an axis and angle.
/// The axis is expected to be normalized to length 1024
/// </summary>
public WRot(WVec axis, WAngle angle)
{
// Angles increase clockwise
x = axis.X * new WAngle(-angle.Angle / 2).Sin() / 1024;
y = axis.Y * new WAngle(-angle.Angle / 2).Sin() / 1024;
z = axis.Z * new WAngle(-angle.Angle / 2).Sin() / 1024;
w = new WAngle(-angle.Angle / 2).Cos();
(Roll, Pitch, Yaw) = QuaternionToEuler(x, y, z, w);
}
WRot(int x, int y, int z, int w) WRot(int x, int y, int z, int w)
{ {
this.x = x; this.x = x;
@@ -55,6 +73,11 @@ namespace OpenRA
this.z = z; this.z = z;
this.w = w; this.w = w;
(Roll, Pitch, Yaw) = QuaternionToEuler(x, y, z, w);
}
static (WAngle, WAngle, WAngle) QuaternionToEuler(int x, int y, int z, int w)
{
// Theoretically 1024 squared, but may differ slightly due to rounding // Theoretically 1024 squared, but may differ slightly due to rounding
var lsq = x * x + y * y + z * z + w * w; var lsq = x * x + y * y + z * z + w * w;
@@ -64,9 +87,11 @@ namespace OpenRA
var sycp = 2 * (w * z + x * y); var sycp = 2 * (w * z + x * y);
var cycp = lsq - 2 * (y * y + z * z); var cycp = lsq - 2 * (y * y + z * z);
Roll = -WAngle.ArcTan(srcp, crcp); var roll = -WAngle.ArcTan(srcp, crcp);
Pitch = -(Math.Abs(sp) >= 1024 ? new WAngle(Math.Sign(sp) * 256) : WAngle.ArcSin(sp)); var pitch = -(Math.Abs(sp) >= 1024 ? new WAngle(Math.Sign(sp) * 256) : WAngle.ArcSin(sp));
Yaw = -WAngle.ArcTan(sycp, cycp); var yaw = -WAngle.ArcTan(sycp, cycp);
return (roll, pitch, yaw);
} }
WRot(int x, int y, int z, int w, WAngle roll, WAngle pitch, WAngle yaw) WRot(int x, int y, int z, int w, WAngle roll, WAngle pitch, WAngle yaw)
@@ -168,5 +193,32 @@ namespace OpenRA
public override bool Equals(object obj) { return obj is WRot && Equals((WRot)obj); } public override bool Equals(object obj) { return obj is WRot && Equals((WRot)obj); }
public override string ToString() { return Roll + "," + Pitch + "," + Yaw; } public override string ToString() { return Roll + "," + Pitch + "," + Yaw; }
public static WRot SLerp(in WRot a, in WRot b, int mul, int div)
{
// This implements the standard spherical linear interpolation
// between two quaternions, accounting for OpenRA's integer math
// conventions and WRot always using (nearly) normalized quaternions
var dot = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
var flip = dot >= 0 ? 1 : -1;
// a and b describe the same rotation
if (flip * dot >= 1024 * 1024)
return a;
var theta = WAngle.ArcCos(dot / 1024);
var s1 = new WAngle((div - mul) * theta.Angle / div).Sin();
var s2 = new WAngle(mul * theta.Angle / div).Sin();
var s3 = theta.Sin();
var x = ((long)a.x * s1 + flip * b.x * s2) / s3;
var y = ((long)a.y * s1 + flip * b.y * s2) / s3;
var z = ((long)a.z * s1 + flip * b.z * s2) / s3;
var w = ((long)a.w * s1 + flip * b.w * s2) / s3;
// Normalize to 1024 == 1.0
var l = Exts.ISqrt(x * x + y * y + z * z + w * w);
return new WRot((int)(1024 * x / l), (int)(1024 * y / l), (int)(1024 * z / l), (int)(1024 * w / l));
}
} }
} }