Quaternion class
class Quaternion
{
final Float32List storage;
double get x => storage[0];
double get y => storage[1];
double get z => storage[2];
double get w => storage[3];
set x(double x) { storage[0] = x; }
set y(double y) { storage[1] = y; }
set z(double z) { storage[2] = z; }
set w(double w) { storage[3] = w; }
/// Constructs a quaternion using the raw values [x], [y], [z], and [w]
Quaternion(double x, double y, double z, double w) :
storage = new Float32List(4) {
storage[0] = x;
storage[1] = y;
storage[2] = z;
storage[3] = w;
}
/// From a rotation matrix [rotationMatrix].
Quaternion.fromRotation(Matrix3 rotationMatrix) :
storage = new Float32List(4) {
double trace = rotationMatrix.trace();
if (trace > 0.0) {
double s = Math.sqrt(trace + 1.0);
storage[3] = s * 0.5;
s = 0.5 / s;
storage[0] = (rotationMatrix.storage[5] -
rotationMatrix.storage[7]) * s;
storage[1] = (rotationMatrix.storage[6] -
rotationMatrix.storage[2]) * s;
storage[2] = (rotationMatrix.storage[1] -
rotationMatrix.storage[3]) * s;
} else {
int i = rotationMatrix.storage[0] < rotationMatrix.storage[4] ?
(rotationMatrix.storage[4] < rotationMatrix.storage[8] ? 2 : 1)
:
(rotationMatrix.storage[0] < rotationMatrix.storage[8] ? 2 : 0);
int j = (i + 1) % 3;
int k = (i + 2) % 3;
double s = Math.sqrt(rotationMatrix.entry(i,i) -
rotationMatrix.entry(j,j) -
rotationMatrix.entry(k,k) + 1.0);
storage[i] = s * 0.5;
s = 0.5 / s;
storage[3] = (rotationMatrix.entry(k,j) - rotationMatrix.entry(j,k)) * s;
storage[j] = (rotationMatrix.entry(j,i) + rotationMatrix.entry(i,j)) * s;
storage[k] = (rotationMatrix.entry(k,i) + rotationMatrix.entry(i,k)) * s;
}
}
/// Rotation of [angle] around [axis].
Quaternion.axisAngle(Vector3 axis, double angle) :
storage = new Float32List(4) {
setAxisAngle(axis, angle);
}
/// Copies [original].
Quaternion.copy(Quaternion original) : storage = new Float32List(4) {
storage[0] = original.storage[0];
storage[1] = original.storage[1];
storage[2] = original.storage[2];
storage[3] = original.storage[3];
}
/// Random rotation.
Quaternion.random(Math.Random rn) : storage = new Float32List(4) {
// From: "Uniform Random Rotations", Ken Shoemake, Graphics Gems III,
// pg. 124-132.
double x0 = rn.nextDouble();
double r1 = Math.sqrt(1.0 - x0);
double r2 = Math.sqrt(x0);
double t1 = Math.PI*2.0 * rn.nextDouble();
double t2 = Math.PI*2.0 * rn.nextDouble();
double c1 = Math.cos(t1);
double s1 = Math.sin(t1);
double c2 = Math.cos(t2);
double s2 = Math.sin(t2);
storage[0] = s1 * r1;
storage[1] = c1 * r1;
storage[2] = s2 * r2;
storage[3] = c2 * r2;
}
/// Constructs the identity quaternion
Quaternion.identity() : storage = new Float32List(4) {
storage[3] = 1.0;
}
/// Time derivative of [q] with angular velocity [omega].
Quaternion.dq(Quaternion q, Vector3 omega) : storage = new Float32List(4) {
double qx = q.storage[0];
double qy = q.storage[1];
double qz = q.storage[2];
double qw = q.storage[3];
double ox = omega.storage[0];
double oy = omega.storage[1];
double oz = omega.storage[2];
double _x = ox * qw + oy * qz - oz * qy;
double _y = oy * qw + oz * qx - ox * qz;
double _z = oz * qw + ox * qy - oy * qx;
double _w = -ox * qx - oy * qy - oz * qz;
storage[0] = _x * 0.5;
storage[1] = _y * 0.5;
storage[2] = _z * 0.5;
storage[3] = _w * 0.5;
}
/// Quaternion view of [storage].
Quaternion.view(this.storage);
/// Returns a new copy of this
Quaternion clone() {
return new Quaternion.copy(this);
}
/// Copy [source] into [this]
void copyFrom(Quaternion source) {
storage[0] = source.storage[0];
storage[1] = source.storage[1];
storage[2] = source.storage[2];
storage[3] = source.storage[3];
}
/// Copy [this] into [target].
void copyTo(Quaternion target) {
target.storage[0] = storage[0];
target.storage[1] = storage[1];
target.storage[2] = storage[2];
target.storage[3] = storage[3];
}
/// Set quaternion with rotation of [radians] around [axis].
void setAxisAngle(Vector3 axis, double radians) {
double len = axis.length;
if (len == 0.0) {
return;
}
double halfSin = Math.sin(radians * 0.5) / len;
storage[0] = axis.storage[0] * halfSin;
storage[1] = axis.storage[1] * halfSin;
storage[2] = axis.storage[2] * halfSin;
storage[3] = Math.cos(radians * 0.5);
}
/// Set quaternion with rotation of [yaw], [pitch] and [roll].
void setEuler(double yaw, double pitch, double roll) {
double halfYaw = yaw * 0.5;
double halfPitch = pitch * 0.5;
double halfRoll = roll * 0.5;
double cosYaw = Math.cos(halfYaw);
double sinYaw = Math.sin(halfYaw);
double cosPitch = Math.cos(halfPitch);
double sinPitch = Math.sin(halfPitch);
double cosRoll = Math.cos(halfRoll);
double sinRoll = Math.sin(halfRoll);
storage[0] = cosRoll * sinPitch * cosYaw + sinRoll * cosPitch * sinYaw;
storage[1] = cosRoll * cosPitch * sinYaw - sinRoll * sinPitch * cosYaw;
storage[2] = sinRoll * cosPitch * cosYaw - cosRoll * sinPitch * sinYaw;
storage[3] = cosRoll * cosPitch * cosYaw + sinRoll * sinPitch * sinYaw;
}
/// Normalize [this].
Quaternion normalize() {
double l = length;
if (l == 0.0) {
return this;
}
l = 1.0 / l;
storage[3] = storage[3] * l;
storage[2] = storage[2] * l;
storage[1] = storage[1] * l;
storage[0] = storage[0] * l;
return this;
}
/// Conjugate [this].
Quaternion conjugate() {
storage[2] = -storage[2];
storage[1] = -storage[1];
storage[0] = -storage[0];
return this;
}
/// Invert [this].
Quaternion inverse() {
double l = 1.0 / length2;
storage[3] = storage[3] * l;
storage[2] = -storage[2] * l;
storage[1] = -storage[1] * l;
storage[0] = -storage[0] * l;
return this;
}
/// Normalized copy of [this].
Quaternion normalized() {
return new Quaternion.copy(this).normalize();
}
/// Conjugated copy of [this].
Quaternion conjugated() {
return new Quaternion.copy(this).conjugate();
}
/// Inverted copy of [this].
Quaternion inverted() {
return new Quaternion.copy(this).inverse();
}
/// Radians of rotation.
double get radians => 2.0 * Math.acos(storage[3]);
/// Axis of rotation.
Vector3 get axis {
double scale = 1.0 / (1.0 - (storage[3] * storage[3]));
return new Vector3(storage[0] * scale,
storage[1] * scale,
storage[2] * scale);
}
/// Length squared.
double get length2 {
double _x = storage[0];
double _y = storage[1];
double _z = storage[2];
double _w = storage[3];
return (_x * _x) + (_y * _y) + (_z * _z) + (_w * _w);
}
/// Length.
double get length {
return Math.sqrt(length2);
}
/// Returns a copy of [v] rotated by quaternion.
Vector3 rotated(Vector3 v) {
Vector3 out = new Vector3.copy(v);
return rotate(out);
}
/// Rotates [v] by [this].
Vector3 rotate(Vector3 v) {
// conjugate(this) * [v,0] * this
double _w = storage[3];
double _z = storage[2];
double _y = storage[1];
double _x = storage[0];
double tiw = _w;
double tiz = -_z;
double tiy = -_y;
double tix = -_x;
double tx = tiw * v.x + tix * 0.0 + tiy * v.z - tiz * v.y;
double ty = tiw * v.y + tiy * 0.0 + tiz * v.x - tix * v.z;
double tz = tiw * v.z + tiz * 0.0 + tix * v.y - tiy * v.x;
double tw = tiw * 0.0 - tix * v.x - tiy * v.y - tiz * v.z;
double result_x = tw * _x + tx * _w + ty * _z - tz * _y;
double result_y = tw * _y + ty * _w + tz * _x - tx * _z;
double result_z = tw * _z + tz * _w + tx * _y - ty * _x;
v.storage[2] = result_z;
v.storage[1] = result_y;
v.storage[0] = result_x;
return v;
}
/// Scales [this] by [scale].
Quaternion scale(double scale) {
storage[3] = storage[3] * scale;
storage[2] = storage[2] * scale;
storage[1] = storage[1] * scale;
storage[0] = storage[0] * scale;
return this;
}
/// Scaled copy of [this].
Quaternion scaled(double scale) {
Quaternion q = new Quaternion.copy(this);
return q.scale(scale);
}
/// [this] rotated by [other].
Quaternion operator*(Quaternion other) {
double _w = storage[3];
double _z = storage[2];
double _y = storage[1];
double _x = storage[0];
double ow = other.storage[3];
double oz = other.storage[2];
double oy = other.storage[1];
double ox = other.storage[0];
return new Quaternion(_w * ox + _x * ow + _y * oz - _z * oy,
_w * oy + _y * ow + _z * ox - _x * oz,
_w * oz + _z * ow + _x * oy - _y * ox,
_w * ow - _x * ox - _y * oy - _z * oz);
}
/// Returns copy of [this] + [other].
Quaternion operator+(Quaternion other) {
return new Quaternion(storage[0] + other.storage[0],
storage[1] + other.storage[1],
storage[2] + other.storage[2],
storage[3] + other.storage[3]);
}
/// Returns copy of [this] - [other].
Quaternion operator-(Quaternion other) {
return new Quaternion(storage[0] - other.storage[0],
storage[1] - other.storage[1],
storage[2] - other.storage[2],
storage[3] - other.storage[3]);
}
/// Returns negated copy of [this].
Quaternion operator-() {
return new Quaternion(-storage[0], -storage[1], -storage[2], -storage[3]);
}
double operator[](int i) => storage[i];
void operator[]=(int i, double arg) {
storage[i] = arg;
}
/// Returns a rotation matrix containing the same rotation as [this].
Matrix3 asRotationMatrix() {
double d = length2;
assert(d != 0.0);
double s = 2.0 / d;
double _x = storage[0];
double _y = storage[1];
double _z = storage[2];
double _w = storage[3];
double xs = _x * s;
double ys = _y * s;
double zs = _z * s;
double wx = _w * xs;
double wy = _w * ys;
double wz = _w * zs;
double xx = _x * xs;
double xy = _x * ys;
double xz = _x * zs;
double yy = _y * ys;
double yz = _y * zs;
double zz = _z * zs;
return new Matrix3(1.0 - (yy + zz), xy + wz, xz - wy, // column 0
xy - wz, 1.0 - (xx + zz), yz + wx, // column 1
xz + wy, yz - wx, 1.0 - (xx + yy)); // column 2
}
/// Printable string.
String toString() {
return '${storage[0]}, ${storage[1]}, ${storage[2]} @ ${storage[3]}';
}
/// Relative error between [this] and [correct].
double relativeError(Quaternion correct) {
Quaternion diff = correct - this;
double norm_diff = diff.length;
double correct_norm = correct.length;
return norm_diff/correct_norm;
}
/// Absolute error between [this] and [correct].
double absoluteError(Quaternion correct) {
double this_norm = length;
double correct_norm = correct.length;
double norm_diff = (this_norm - correct_norm).abs();
return norm_diff;
}
}
Constructors
new Quaternion(double x, double y, double z, double w) #
Constructs a quaternion using the raw values x, y, z, and w
Quaternion(double x, double y, double z, double w) :
storage = new Float32List(4) {
storage[0] = x;
storage[1] = y;
storage[2] = z;
storage[3] = w;
}
new Quaternion.axisAngle(Vector3 axis, double angle) #
Rotation of angle around axis.
Quaternion.axisAngle(Vector3 axis, double angle) :
storage = new Float32List(4) {
setAxisAngle(axis, angle);
}
new Quaternion.copy(Quaternion original) #
Copies original.
Quaternion.copy(Quaternion original) : storage = new Float32List(4) {
storage[0] = original.storage[0];
storage[1] = original.storage[1];
storage[2] = original.storage[2];
storage[3] = original.storage[3];
}
new Quaternion.dq(Quaternion q, Vector3 omega) #
Time derivative of q with angular velocity omega.
Quaternion.dq(Quaternion q, Vector3 omega) : storage = new Float32List(4) {
double qx = q.storage[0];
double qy = q.storage[1];
double qz = q.storage[2];
double qw = q.storage[3];
double ox = omega.storage[0];
double oy = omega.storage[1];
double oz = omega.storage[2];
double _x = ox * qw + oy * qz - oz * qy;
double _y = oy * qw + oz * qx - ox * qz;
double _z = oz * qw + ox * qy - oy * qx;
double _w = -ox * qx - oy * qy - oz * qz;
storage[0] = _x * 0.5;
storage[1] = _y * 0.5;
storage[2] = _z * 0.5;
storage[3] = _w * 0.5;
}
new Quaternion.fromRotation(Matrix3 rotationMatrix) #
From a rotation matrix rotationMatrix.
Quaternion.fromRotation(Matrix3 rotationMatrix) :
storage = new Float32List(4) {
double trace = rotationMatrix.trace();
if (trace > 0.0) {
double s = Math.sqrt(trace + 1.0);
storage[3] = s * 0.5;
s = 0.5 / s;
storage[0] = (rotationMatrix.storage[5] -
rotationMatrix.storage[7]) * s;
storage[1] = (rotationMatrix.storage[6] -
rotationMatrix.storage[2]) * s;
storage[2] = (rotationMatrix.storage[1] -
rotationMatrix.storage[3]) * s;
} else {
int i = rotationMatrix.storage[0] < rotationMatrix.storage[4] ?
(rotationMatrix.storage[4] < rotationMatrix.storage[8] ? 2 : 1)
:
(rotationMatrix.storage[0] < rotationMatrix.storage[8] ? 2 : 0);
int j = (i + 1) % 3;
int k = (i + 2) % 3;
double s = Math.sqrt(rotationMatrix.entry(i,i) -
rotationMatrix.entry(j,j) -
rotationMatrix.entry(k,k) + 1.0);
storage[i] = s * 0.5;
s = 0.5 / s;
storage[3] = (rotationMatrix.entry(k,j) - rotationMatrix.entry(j,k)) * s;
storage[j] = (rotationMatrix.entry(j,i) + rotationMatrix.entry(i,j)) * s;
storage[k] = (rotationMatrix.entry(k,i) + rotationMatrix.entry(i,k)) * s;
}
}
new Quaternion.identity() #
Constructs the identity quaternion
Quaternion.identity() : storage = new Float32List(4) {
storage[3] = 1.0;
}
new Quaternion.random(Random rn) #
Random rotation.
Quaternion.random(Math.Random rn) : storage = new Float32List(4) {
// From: "Uniform Random Rotations", Ken Shoemake, Graphics Gems III,
// pg. 124-132.
double x0 = rn.nextDouble();
double r1 = Math.sqrt(1.0 - x0);
double r2 = Math.sqrt(x0);
double t1 = Math.PI*2.0 * rn.nextDouble();
double t2 = Math.PI*2.0 * rn.nextDouble();
double c1 = Math.cos(t1);
double s1 = Math.sin(t1);
double c2 = Math.cos(t2);
double s2 = Math.sin(t2);
storage[0] = s1 * r1;
storage[1] = c1 * r1;
storage[2] = s2 * r2;
storage[3] = c2 * r2;
}
new Quaternion.view(Float32List storage) #
Quaternion view of storage.
Quaternion.view(this.storage);
Properties
final Vector3 axis #
Axis of rotation.
Vector3 get axis {
double scale = 1.0 / (1.0 - (storage[3] * storage[3]));
return new Vector3(storage[0] * scale,
storage[1] * scale,
storage[2] * scale);
}
final double length2 #
Length squared.
double get length2 {
double _x = storage[0];
double _y = storage[1];
double _z = storage[2];
double _w = storage[3];
return (_x * _x) + (_y * _y) + (_z * _z) + (_w * _w);
}
final Float32List storage #
final Float32List storage
Operators
Quaternion operator +(Quaternion other) #
Returns copy of this +
other.
Quaternion operator+(Quaternion other) {
return new Quaternion(storage[0] + other.storage[0],
storage[1] + other.storage[1],
storage[2] + other.storage[2],
storage[3] + other.storage[3]);
}
Quaternion operator -() #
Returns negated copy of this.
Quaternion operator-() {
return new Quaternion(-storage[0], -storage[1], -storage[2], -storage[3]);
}
Quaternion operator -(Quaternion other) #
Returns copy of this -
other.
Quaternion operator-(Quaternion other) {
return new Quaternion(storage[0] - other.storage[0],
storage[1] - other.storage[1],
storage[2] - other.storage[2],
storage[3] - other.storage[3]);
}
Quaternion operator *(Quaternion other) #
this rotated by
other.
Quaternion operator*(Quaternion other) {
double _w = storage[3];
double _z = storage[2];
double _y = storage[1];
double _x = storage[0];
double ow = other.storage[3];
double oz = other.storage[2];
double oy = other.storage[1];
double ox = other.storage[0];
return new Quaternion(_w * ox + _x * ow + _y * oz - _z * oy,
_w * oy + _y * ow + _z * ox - _x * oz,
_w * oz + _z * ow + _x * oy - _y * ox,
_w * ow - _x * ox - _y * oy - _z * oz);
}
Methods
double absoluteError(Quaternion correct) #
Absolute error between this and
correct.
double absoluteError(Quaternion correct) {
double this_norm = length;
double correct_norm = correct.length;
double norm_diff = (this_norm - correct_norm).abs();
return norm_diff;
}
Matrix3 asRotationMatrix() #
Returns a rotation matrix containing the same rotation as this.
Matrix3 asRotationMatrix() {
double d = length2;
assert(d != 0.0);
double s = 2.0 / d;
double _x = storage[0];
double _y = storage[1];
double _z = storage[2];
double _w = storage[3];
double xs = _x * s;
double ys = _y * s;
double zs = _z * s;
double wx = _w * xs;
double wy = _w * ys;
double wz = _w * zs;
double xx = _x * xs;
double xy = _x * ys;
double xz = _x * zs;
double yy = _y * ys;
double yz = _y * zs;
double zz = _z * zs;
return new Matrix3(1.0 - (yy + zz), xy + wz, xz - wy, // column 0
xy - wz, 1.0 - (xx + zz), yz + wx, // column 1
xz + wy, yz - wx, 1.0 - (xx + yy)); // column 2
}
Quaternion clone() #
Returns a new copy of this
Quaternion clone() {
return new Quaternion.copy(this);
}
Quaternion conjugate() #
Conjugate this.
Quaternion conjugate() {
storage[2] = -storage[2];
storage[1] = -storage[1];
storage[0] = -storage[0];
return this;
}
Quaternion conjugated() #
Conjugated copy of this.
Quaternion conjugated() {
return new Quaternion.copy(this).conjugate();
}
void copyFrom(Quaternion source) #
Copy
source into this
void copyFrom(Quaternion source) {
storage[0] = source.storage[0];
storage[1] = source.storage[1];
storage[2] = source.storage[2];
storage[3] = source.storage[3];
}
void copyTo(Quaternion target) #
Copy this into
target.
void copyTo(Quaternion target) {
target.storage[0] = storage[0];
target.storage[1] = storage[1];
target.storage[2] = storage[2];
target.storage[3] = storage[3];
}
Quaternion inverse() #
Invert this.
Quaternion inverse() {
double l = 1.0 / length2;
storage[3] = storage[3] * l;
storage[2] = -storage[2] * l;
storage[1] = -storage[1] * l;
storage[0] = -storage[0] * l;
return this;
}
Quaternion inverted() #
Inverted copy of this.
Quaternion inverted() {
return new Quaternion.copy(this).inverse();
}
Quaternion normalize() #
Normalize this.
Quaternion normalize() {
double l = length;
if (l == 0.0) {
return this;
}
l = 1.0 / l;
storage[3] = storage[3] * l;
storage[2] = storage[2] * l;
storage[1] = storage[1] * l;
storage[0] = storage[0] * l;
return this;
}
Quaternion normalized() #
Normalized copy of this.
Quaternion normalized() {
return new Quaternion.copy(this).normalize();
}
double relativeError(Quaternion correct) #
Relative error between this and
correct.
double relativeError(Quaternion correct) {
Quaternion diff = correct - this;
double norm_diff = diff.length;
double correct_norm = correct.length;
return norm_diff/correct_norm;
}
Vector3 rotate(Vector3 v) #
Rotates
v by this.
Vector3 rotate(Vector3 v) {
// conjugate(this) * [v,0] * this
double _w = storage[3];
double _z = storage[2];
double _y = storage[1];
double _x = storage[0];
double tiw = _w;
double tiz = -_z;
double tiy = -_y;
double tix = -_x;
double tx = tiw * v.x + tix * 0.0 + tiy * v.z - tiz * v.y;
double ty = tiw * v.y + tiy * 0.0 + tiz * v.x - tix * v.z;
double tz = tiw * v.z + tiz * 0.0 + tix * v.y - tiy * v.x;
double tw = tiw * 0.0 - tix * v.x - tiy * v.y - tiz * v.z;
double result_x = tw * _x + tx * _w + ty * _z - tz * _y;
double result_y = tw * _y + ty * _w + tz * _x - tx * _z;
double result_z = tw * _z + tz * _w + tx * _y - ty * _x;
v.storage[2] = result_z;
v.storage[1] = result_y;
v.storage[0] = result_x;
return v;
}
Vector3 rotated(Vector3 v) #
Returns a copy of v rotated by quaternion.
Vector3 rotated(Vector3 v) {
Vector3 out = new Vector3.copy(v);
return rotate(out);
}
Quaternion scale(double scale) #
Scales this by
scale.
Quaternion scale(double scale) {
storage[3] = storage[3] * scale;
storage[2] = storage[2] * scale;
storage[1] = storage[1] * scale;
storage[0] = storage[0] * scale;
return this;
}
Quaternion scaled(double scale) #
Scaled copy of this.
Quaternion scaled(double scale) {
Quaternion q = new Quaternion.copy(this);
return q.scale(scale);
}
void setAxisAngle(Vector3 axis, double radians) #
Set quaternion with rotation of radians around axis.
void setAxisAngle(Vector3 axis, double radians) {
double len = axis.length;
if (len == 0.0) {
return;
}
double halfSin = Math.sin(radians * 0.5) / len;
storage[0] = axis.storage[0] * halfSin;
storage[1] = axis.storage[1] * halfSin;
storage[2] = axis.storage[2] * halfSin;
storage[3] = Math.cos(radians * 0.5);
}
void setEuler(double yaw, double pitch, double roll) #
Set quaternion with rotation of yaw, pitch and roll.
void setEuler(double yaw, double pitch, double roll) {
double halfYaw = yaw * 0.5;
double halfPitch = pitch * 0.5;
double halfRoll = roll * 0.5;
double cosYaw = Math.cos(halfYaw);
double sinYaw = Math.sin(halfYaw);
double cosPitch = Math.cos(halfPitch);
double sinPitch = Math.sin(halfPitch);
double cosRoll = Math.cos(halfRoll);
double sinRoll = Math.sin(halfRoll);
storage[0] = cosRoll * sinPitch * cosYaw + sinRoll * cosPitch * sinYaw;
storage[1] = cosRoll * cosPitch * sinYaw - sinRoll * sinPitch * cosYaw;
storage[2] = sinRoll * cosPitch * cosYaw - cosRoll * sinPitch * sinYaw;
storage[3] = cosRoll * cosPitch * cosYaw + sinRoll * sinPitch * sinYaw;
}