/******************************************************************************
* Copyright (C) Ultraleap, Inc. 2011-2021. *
* *
* Use subject to the terms of the Apache License 2.0 available at *
* http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
* between Ultraleap and you, your company or other organization. *
******************************************************************************/
using System;
using UnityEngine;
namespace Leap.Unity.Animation
{
///
/// Represents a spline for the rotation of a rigid body from one orientation in space
/// to another over a specified time frame. The two endpoints are specified, as well
/// as the instantaneous angular velocity at those two endpoints.
///
/// You may ask for the position, rotation, velocity, or angular velocity at any time
/// along the spline's duration.
///
[Serializable]
public struct HermiteQuaternionSpline : ISpline
{
public float t0, t1;
public Quaternion rot0, rot1;
public Vector3 angVel0, angVel1;
///
/// Constructs a Quaternion spline by specifying the rotations at the two endpoints.
/// The angular velocity at each endpoint is zero, and the time range of the spline
/// is 0 to 1.
///
public HermiteQuaternionSpline(Quaternion rot0, Quaternion rot1)
{
t0 = 0;
t1 = 1;
this.angVel0 = default(Vector3);
this.angVel1 = default(Vector3);
this.rot0 = rot0;
this.rot1 = rot1;
}
///
/// Constructs a Quaternion spline by specifying the rotations (as quaternions) and
/// angular velocities (as axis * angle vectors) at the endpoints.
///
/// The time range of the spline is 0 to 1.
///
public HermiteQuaternionSpline(Quaternion rot0, Quaternion rot1,
Vector3 angVel0, Vector3 angVel1)
{
t0 = 0;
t1 = 1;
this.angVel0 = angVel0;
this.angVel1 = angVel1;
this.rot0 = rot0;
this.rot1 = rot1;
}
///
/// Constructs a Quaternion spline by specifying the rotations (as quaternions) and
/// angular velocities (as axis * angle vectors) at the endpoints.
///
/// The time range of the spline is 0 to duration.
///
public HermiteQuaternionSpline(Quaternion rot0, Quaternion rot1,
Vector3 angVel0, Vector3 angVel1,
float duration)
{
t0 = 0;
t1 = duration;
this.angVel0 = angVel0;
this.angVel1 = angVel1;
this.rot0 = rot0;
this.rot1 = rot1;
}
///
/// Constructs a spline by specifying the rotations, angular velocities, and times of
/// the endpoints.
///
public HermiteQuaternionSpline(float t0, float t1,
Quaternion rot0, Quaternion rot1,
Vector3 angVel0, Vector3 angVel1)
{
this.t0 = t0;
this.t1 = t1;
this.angVel0 = angVel0;
this.angVel1 = angVel1;
this.rot0 = rot0.ToNormalized();
this.rot1 = rot1.ToNormalized();
}
///
/// Gets the rotation at time t along this spline. The time is clamped within the
/// t0 - t1 range.
///
public Quaternion RotationAt(float t)
{
if (t > t1)
{
float i = ((t - t0) / (t1 - t0)) - 1f;
return rot1 * Mathq.Exp(angVel1 * i); //unsure of the ordering here...
}
else
{
float i = Mathf.Clamp01((t - t0) / (t1 - t0));
float i2 = i * i;
float i3 = i2 * i;
float dt = t1 - t0;
var oneThird = 1 / 3f;
var w1 = Quaternion.Inverse(rot0) * angVel0 * dt * oneThird;
var w3 = Quaternion.Inverse(rot1) * angVel1 * dt * oneThird;
var w2 = Mathq.Log(Mathq.Exp(-w1)
* Quaternion.Inverse(rot0)
* rot1
* Mathq.Exp(-w3));
var beta1 = i3 - (3 * i2) + (3 * i);
var beta2 = -2 * i3 + 3 * i2;
var beta3 = i3;
return rot0 * Mathq.Exp(w1 * beta1) * Mathq.Exp(w2 * beta2) * Mathq.Exp(w3 * beta3);
}
// A cubic Bezier quaternion curve can be used to define a Hermite quaternion curve
// which interpolates two end unit quaternions, q_a and q_b, and two angular
// velocities omega_a and omega_b.
//
// var q_at_t = q_0 * PRODUCT(i = 1, i < 3, exp(omega_i * beta_q(i, t))
// "where beta_q(i, t) is beta_q(i, 3, t) for i = 1, 2, 3."
//
// q coefficients:
// q_0 = q_a
// q_1 = q_a * exp(omega_a / 3)
// q_2 = q_b * exp(omega_b / 3)^-1
// q_3 = q_b
//
// omega coefficients:
// omega_1 = omega_a / 3
// omega_2 = log(exp(omega_a / 3)^-1 * q_a^-1 * q_b * exp(omega_b / 3)^-1)
// omega_3 = omega_b / 3
//
// Dependencies/definitions:
// "Bernstein basis", referred to as "beta"
// beta(i, n, t) = (n Choose i) * (1 - t)^(n - i) * t^i
// The form used in the formula are the cumulative basis functions:
// beta_q(i, n, t) = SUM(j = i, n, beta(i, n, t))
//
// The Exponential Mapping exp(v) and its Inverse
// "The exponential map can be interpreted as a mapping from the angular velocity
// vector (measured in S^3) into the unit quaternion which represents the rotation."
// ...
// "Given a vector v = theta * v_norm (in R^3), the exponential:
// exp(v) = SUM(i = 0, i -> inf, v^i) = (cos(theta), v_norm * sin(theta)) in S^3
// becomes the unit quaternion which represents the rotation by angle 2*theta
// about the axis v_norm, where v^i is computed using the quaternion multiplication."
//
// In other words... The Exponential converts a VECTOR represented by an ANGLE
// and a normalized AXIS to a quaternion. A Unity function for this:
// exp(v) -> Q := Quaternion.AngleAxis(v.magnitude, v.normalized);
//
// Its inverse map, log, would thus be the conversion from a Quaternion to an
// Angle-Axis representation. Quaternion.ToAngleAxisVector():
// log(Q) -> v := Q.ToAngleAxisVector() -> v
}
///
/// Gets the first derivative of rotation at time t. The time is clamped within the
/// t0 - t1 range. Angular velocity is encoded as an angle-axis vector.
///
public Vector3 AngularVelocityAt(float t)
{
float i = Mathf.Clamp01((t - t0) / (t1 - t0));
float i2 = i * i;
float i3 = i2 * i;
float dt = t1 - t0;
var oneThird = 1 / 3f;
var w1 = Quaternion.Inverse(rot0) * angVel0 * dt * oneThird;
var w3 = Quaternion.Inverse(rot1) * angVel1 * dt * oneThird;
var w2 = (Mathq.Exp(-w1)
* Quaternion.Inverse(rot0)
* rot1
* Mathq.Exp(-w3)).ToAngleAxisVector();
var beta1 = i3 - (3 * i2) + (3 * i);
var beta2 = -2 * i3 + 3 * i2;
//var beta3 = i3;
// Derivatives of beta1, beta2, beta3
var dotBeta1 = 3 * i2 - 5 * i + 3;
var dotBeta2 = -6 * i2 + 6 * i;
var dotBeta3 = 3 * i2;
var rot0_times_w1beta1 = rot0 * Mathq.Exp(w1 * beta1);
return
rot0 * w1 * dotBeta1 +
rot0_times_w1beta1 * w2 * dotBeta2 +
rot0_times_w1beta1 * Mathq.Exp(w2 * beta2) * w3 * dotBeta3;
}
///
/// Gets both the rotation and the first derivative of rotation at time t. The time
/// is clamped within the t0 - t1 range. Angular velocity is encoded as an angle-axis
/// vector.
///
public void RotationAndAngVelAt(float t, out Quaternion rotation,
out Vector3 angularVelocity)
{
float i = Mathf.Clamp01((t - t0) / (t1 - t0));
float i2 = i * i;
float i3 = i2 * i;
float dt = t1 - t0;
var oneThird = 1 / 3f;
var w1 = Quaternion.Inverse(rot0) * angVel0 * dt * oneThird;
var w3 = Quaternion.Inverse(rot1) * angVel1 * dt * oneThird;
var w2 = Mathq.Log(Mathq.Exp(-w1)
* Quaternion.Inverse(rot0)
* rot1
* Mathq.Exp(-w3));
var beta1 = i3 - (3 * i2) + (3 * i);
var beta2 = -2 * i3 + 3 * i2;
var beta3 = i3;
rotation =
rot0 * Mathq.Exp(w1 * beta1) * Mathq.Exp(w2 * beta2) * Mathq.Exp(w3 * beta3);
// Derivatives of beta1, beta2, beta3
var dotBeta1 = 3 * i2 - 5 * i + 3;
var dotBeta2 = -6 * i2 + 6 * i;
var dotBeta3 = 3 * i2;
var rot0_times_w1beta1 = rot0 * Mathq.Exp(w1 * beta1);
angularVelocity =
rot0 * w1 * dotBeta1 +
rot0_times_w1beta1 * w2 * dotBeta2 +
rot0_times_w1beta1 * Mathq.Exp(w2 * beta2) * w3 * dotBeta3;
}
#region ISpline
public float minT { get { return t0; } }
public float maxT { get { return t1; } }
public Quaternion ValueAt(float t)
{
return RotationAt(t);
}
public Vector3 DerivativeAt(float t)
{
return AngularVelocityAt(t);
}
public void ValueAndDerivativeAt(float t, out Quaternion value,
out Vector3 deltaValuePerSec)
{
RotationAndAngVelAt(t, out value, out deltaValuePerSec);
}
#endregion
}
///
/// Quaternion math.
///
public static class Mathq
{
#region Exponential Quaternion Map
// Quaternion CHS reference:
// Kim, Kim, and Shin, 1995.
// A General Construction Scheme for Unit Quaternion Curves with Simple High Order
// Derivatives.
// http://graphics.cs.cmu.edu/nsp/course/15-464/Fall05/papers/kimKimShin.pdf
// also see Slide 41 of:
// https://www.cs.indiana.edu/ftp/hanson/Siggraph01QuatCourse/quatvis2.pdf
// for converting
///
/// Exponential Quaternion Map function. Maps Euclidean 3D input space to a
/// Quaternion for spline math.
///
/// Some helpful references:
/// http://graphics.cs.cmu.edu/nsp/course/15-464/Fall05/papers/kimKimShin.pdf
/// https://www.cs.indiana.edu/ftp/hanson/Siggraph01QuatCourse/quatvis2.pdf
///
public static Quaternion Exp(Vector3 angleAxisVector)
{
var angle = angleAxisVector.magnitude;
var axis = angleAxisVector / angle;
return Quaternion.AngleAxis(angle, axis);
}
///
/// Exponential Quaternion Map function (inverse). Maps a Quaternion to reduced
/// Euclidean 3D space for spline math.
///
/// Some helpful references:
/// http://graphics.cs.cmu.edu/nsp/course/15-464/Fall05/papers/kimKimShin.pdf
/// https://www.cs.indiana.edu/ftp/hanson/Siggraph01QuatCourse/quatvis2.pdf
///
public static Vector3 Log(Quaternion quaternion)
{
return quaternion.ToAngleAxisVector();
}
#endregion
}
}