This repo discusses two possible ways to interpolate rigid body motions in Unity.
The scripts were tested in Unity v2020.3.f25f1
.
In Unity, coordinate frames or poses are represented by Quaternion
(representing rotation), and Vector3
(representing translation).
Each component (rotation or translation) has its respective interpolation method implemented out-of-the-box in Unity, to transition smoothly from one pose to another. Namely, they are Quarternion.Slerp
for interpolating rotations and Vector3.Lerp
for interpolating translations.
One direct way to interpolate rigid body motion in is to interpolate two distinct poses independently in their rotation and translation component.
The code snippet below (from MoveIndependent.cs) shows how to achieve this rigid body motion interpolation with built-in Unity functionalities.
public class MoveIndependent : MonoBehaviour
{
Quaternion startRotation;
Quaternion endRotation;
Vector3 startPosition;
Vector3 endPosition;
LineRenderer lr0;
int totalFrames = 50*10;
int counter;
// Start is called before the first frame update
void Start()
{
lr0 = new GameObject().AddComponent<LineRenderer>();
lr0.gameObject.transform.SetParent(transform);
lr0.gameObject.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
startRotation = Quaternion.Euler(0f, 0f, 0f);
startPosition = Vector3.zero;
endPosition = new Vector3(-10f, 10f, 10f);
endRotation = Quaternion.Euler(180f,0f,0f);
counter = 0;
transform.position = startPosition;
transform.rotation = startRotation;
}
private void FixedUpdate() {
if (counter < totalFrames) {
counter += 1;
// interpolation
transform.rotation = Quaternion.Slerp(startRotation, endRotation, (float)counter / totalFrames);
transform.position = Vector3.Lerp(startPosition, endPosition, (float) counter / totalFrames);
// visualization
List<Vector3> line0 = new List<Vector3> { startPosition, endPosition };
lr0.startWidth = 0.1f;
lr0.endWidth = 0.1f;
lr0.SetPositions(line0.ToArray());
}
}
}
Full script: MoveIndependent.cs
Alternatively, one can interpolate rigid body motion between two poses along a screw motion as illustrated below:
The code snippet below (from MoveDQ.cs) shows how to achieve this rigid body motion interpolation using a custom defined class DualQuat
(which implementation is also defined in MoveDQ.cs).
public class MoveDQ : MonoBehaviour
{
Quaternion startRotation;
Quaternion endRotation;
Vector3 startPosition;
Vector3 endPosition;
DualQuat startDQ;
DualQuat endDQ;
LineRenderer lr0;
LineRenderer lr1;
LineRenderer lr2;
int totalFrames = 50*10;
int counter;
// Start is called before the first frame update
void Start() {
lr0 = new GameObject().AddComponent<LineRenderer>();
lr0.gameObject.transform.SetParent(transform);
lr0.gameObject.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
lr1 = new GameObject().AddComponent<LineRenderer>();
lr1.gameObject.transform.SetParent(transform);
lr1.gameObject.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
lr2 = new GameObject().AddComponent<LineRenderer>();
lr2.gameObject.transform.SetParent(transform);
lr2.gameObject.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
startRotation = Quaternion.Euler(0f, 0f, 0f);
startPosition = Vector3.zero;
startDQ = DualQuat.FromPose(startRotation, startPosition);
endPosition = new Vector3(-10f, 10f, 10f);
endRotation = Quaternion.Euler(180f, 0f, 0f);
endDQ = DualQuat.FromPose(endRotation, endPosition);
counter = 0;
transform.position = startPosition;
transform.rotation = startRotation;
}
private void FixedUpdate() {
if (counter < totalFrames) {
counter += 1;
// interpolation
var dq = DualQuat.Interpolate(startDQ, endDQ, (float)counter / totalFrames);
DualQuat.ToPose(dq, out var rotation, out var position);
transform.rotation = rotation;
transform.position = position;
// visualization
DualQuat.ToScrew(endDQ, out var theta, out var axis, out var d, out var moment);
var p = Vector3.Cross(axis, moment);
var pStart = p - 20 * axis;
var pEnd = p + 10 * axis;
List<Vector3> line0 = new List<Vector3> { startPosition, endPosition };
lr0.startWidth = 0.1f;
lr0.endWidth = 0.1f;
lr0.SetPositions(line0.ToArray());
List<Vector3> line1 = new List<Vector3> { pStart, pEnd };
lr1.startWidth = 0.1f;
lr1.endWidth = 0.1f;
lr1.SetPositions(line1.ToArray());
Vector3 temp = position - pStart;
var offset = Vector3.Dot(temp, axis);
var shortestIntersect = pStart + offset * axis;
List<Vector3> line2 = new List<Vector3> { position, shortestIntersect };
lr2.startWidth = 0.1f;
lr2.endWidth = 0.1f;
lr2.SetPositions(line2.ToArray());
var distance = Vector3.Distance(position, shortestIntersect);
Debug.Log($"Distance to screw axis [{distance}]");
}
}
}
Full script: MoveDQ.cs
Quaternion
Quaternion, , where and where .
or more explicitly,
In Unity convention,
var q = new Quaternion(x:u1,y:u2,z:u3,w:u0);
Quaternion product
The product of two quaternions satisfies these fundamental rules introduced by Hamilton,
Product of 2 quaternions and is defined as
Quaternion conjugate
For
Quaternion conjugate,
Representing rotation with unit quaternion
Rotation around an axis, by an angle , where can be represented by a unit quaternion
Dual quaternion
,
where
, real part
, dual part
Dual quaternion product
Product of 2 dual quaternions , and is defined as
Dual quaternion conjugate
For ,
Dual quaternion conjugate,
where and are quaternion conjugates for real and dual part respectively.
Representing rigid transformation with dual quaternion (rotation + translation)
where translation,
Representing rigid transformation with dual quaternion (screw)
The elements of a unit dual quaternion are related to the screw parameter of a 3D rigid transformation as:
where
is the screw axis, with and .
is the moment of the screw axis w.r.t. the origin, with .
is the screw angle
is the screw translation
Power of a dual quaternion
Dual quaternion interpolation
ScLERP interpolates between two unit dual quaternions , with parameter
ScLERP