import { MathUtils } from './MathUtils.js';
import { Matrix3 } from './Matrix3.js';
import { Vector3 } from './Vector3.js';
const matrix = new Matrix3();
const vector = new Vector3();
/**
* Class representing a quaternion.
*
* @author {@link https://github.com/Mugen87|Mugen87}
*/
class Quaternion {
/**
* Constructs a new quaternion with the given values.
*
* @param {Number} x - The x component.
* @param {Number} y - The y component.
* @param {Number} z - The z component.
* @param {Number} w - The w component.
*/
constructor( x = 0, y = 0, z = 0, w = 1 ) {
/**
* The x component.
* @type {Number}
*/
this.x = x;
/**
* The y component.
* @type {Number}
*/
this.y = y;
/**
* The z component.
* @type {Number}
*/
this.z = z;
/**
* The w component.
* @type {Number}
*/
this.w = w;
}
/**
* Sets the given values to this quaternion.
*
* @param {Number} x - The x component.
* @param {Number} y - The y component.
* @param {Number} z - The z component.
* @param {Number} w - The w component.
* @return {Quaternion} A reference to this quaternion.
*/
set( x, y, z, w ) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
return this;
}
/**
* Copies all values from the given quaternion to this quaternion.
*
* @param {Quaternion} q - The quaternion to copy.
* @return {Quaternion} A reference to this quaternion.
*/
copy( q ) {
this.x = q.x;
this.y = q.y;
this.z = q.z;
this.w = q.w;
return this;
}
/**
* Creates a new quaternion and copies all values from this quaternion.
*
* @return {Quaternion} A new quaternion.
*/
clone() {
return new this.constructor().copy( this );
}
/**
* Computes the inverse of this quaternion.
*
* @return {Quaternion} A reference to this quaternion.
*/
inverse() {
return this.conjugate().normalize();
}
/**
* Computes the conjugate of this quaternion.
*
* @return {Quaternion} A reference to this quaternion.
*/
conjugate() {
this.x *= - 1;
this.y *= - 1;
this.z *= - 1;
return this;
}
/**
* Computes the dot product of this and the given quaternion.
*
* @param {Quaternion} q - The given quaternion.
* @return {Quaternion} A reference to this quaternion.
*/
dot( q ) {
return ( this.x * q.x ) + ( this.y * q.y ) + ( this.z * q.z ) + ( this.w * q.w );
}
/**
* Computes the length of this quaternion.
*
* @return {Number} The length of this quaternion.
*/
length() {
return Math.sqrt( this.squaredLength() );
}
/**
* Computes the squared length of this quaternion.
*
* @return {Number} The squared length of this quaternion.
*/
squaredLength() {
return this.dot( this );
}
/**
* Normalizes this quaternion.
*
* @return {Quaternion} A reference to this quaternion.
*/
normalize() {
let l = this.length();
if ( l === 0 ) {
this.x = 0;
this.y = 0;
this.z = 0;
this.w = 1;
} else {
l = 1 / l;
this.x = this.x * l;
this.y = this.y * l;
this.z = this.z * l;
this.w = this.w * l;
}
return this;
}
/**
* Multiplies this quaternion with the given quaternion.
*
* @param {Quaternion} q - The quaternion to multiply.
* @return {Quaternion} A reference to this quaternion.
*/
multiply( q ) {
return this.multiplyQuaternions( this, q );
}
/**
* Multiplies the given quaternion with this quaternion.
* So the order of the multiplication is switched compared to {@link Quaternion#multiply}.
*
* @param {Quaternion} q - The quaternion to multiply.
* @return {Quaternion} A reference to this quaternion.
*/
premultiply( q ) {
return this.multiplyQuaternions( q, this );
}
/**
* Multiplies two given quaternions and stores the result in this quaternion.
*
* @param {Quaternion} a - The first quaternion of the operation.
* @param {Quaternion} b - The second quaternion of the operation.
* @return {Quaternion} A reference to this quaternion.
*/
multiplyQuaternions( a, b ) {
const qax = a.x, qay = a.y, qaz = a.z, qaw = a.w;
const qbx = b.x, qby = b.y, qbz = b.z, qbw = b.w;
this.x = ( qax * qbw ) + ( qaw * qbx ) + ( qay * qbz ) - ( qaz * qby );
this.y = ( qay * qbw ) + ( qaw * qby ) + ( qaz * qbx ) - ( qax * qbz );
this.z = ( qaz * qbw ) + ( qaw * qbz ) + ( qax * qby ) - ( qay * qbx );
this.w = ( qaw * qbw ) - ( qax * qbx ) - ( qay * qby ) - ( qaz * qbz );
return this;
}
/**
* Computes the shortest angle between two rotation defined by this quaternion and the given one.
*
* @param {Quaternion} q - The given quaternion.
* @return {Number} The angle in radians.
*/
angleTo( q ) {
return 2 * Math.acos( Math.abs( MathUtils.clamp( this.dot( q ), - 1, 1 ) ) );
}
/**
* Transforms this rotation defined by this quaternion towards the target rotation
* defined by the given quaternion by the given angular step. The rotation will not overshoot.
*
* @param {Quaternion} q - The target rotation.
* @param {Number} step - The maximum step in radians.
* @param {Number} tolerance - A tolerance value in radians to tweak the result
* when both rotations are considered to be equal.
* @return {Boolean} Whether the given quaternion already represents the target rotation.
*/
rotateTo( q, step, tolerance = 0.0001 ) {
const angle = this.angleTo( q );
if ( angle < tolerance ) return true;
const t = Math.min( 1, step / angle );
this.slerp( q, t );
return false;
}
/**
* Creates a quaternion that orients an object to face towards a specified target direction.
*
* @param {Vector3} localForward - Specifies the forward direction in the local space of the object.
* @param {Vector3} targetDirection - Specifies the desired world space direction the object should look at.
* @param {Vector3} localUp - Specifies the up direction in the local space of the object.
* @return {Quaternion} A reference to this quaternion.
*/
lookAt( localForward, targetDirection, localUp ) {
matrix.lookAt( localForward, targetDirection, localUp );
this.fromMatrix3( matrix );
}
/**
* Spherically interpolates between this quaternion and the given quaternion by t.
* The parameter t is clamped to the range [0, 1].
*
* @param {Quaternion} q - The target rotation.
* @param {Number} t - The interpolation parameter.
* @return {Quaternion} A reference to this quaternion.
*/
slerp( q, t ) {
if ( t === 0 ) return this;
if ( t === 1 ) return this.copy( q );
const x = this.x, y = this.y, z = this.z, w = this.w;
let cosHalfTheta = w * q.w + x * q.x + y * q.y + z * q.z;
if ( cosHalfTheta < 0 ) {
this.w = - q.w;
this.x = - q.x;
this.y = - q.y;
this.z = - q.z;
cosHalfTheta = - cosHalfTheta;
} else {
this.copy( q );
}
if ( cosHalfTheta >= 1.0 ) {
this.w = w;
this.x = x;
this.y = y;
this.z = z;
return this;
}
const sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta );
if ( Math.abs( sinHalfTheta ) < 0.001 ) {
this.w = 0.5 * ( w + this.w );
this.x = 0.5 * ( x + this.x );
this.y = 0.5 * ( y + this.y );
this.z = 0.5 * ( z + this.z );
return this;
}
const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta );
const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta;
const ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
this.w = ( w * ratioA ) + ( this.w * ratioB );
this.x = ( x * ratioA ) + ( this.x * ratioB );
this.y = ( y * ratioA ) + ( this.y * ratioB );
this.z = ( z * ratioA ) + ( this.z * ratioB );
return this;
}
/**
* Extracts the rotation of the given 4x4 matrix and stores it in this quaternion.
*
* @param {Matrix4} m - A 4x4 matrix.
* @return {Quaternion} A reference to this quaternion.
*/
extractRotationFromMatrix( m ) {
const e = matrix.elements;
const me = m.elements;
// remove scaling from the 3x3 portion
const sx = 1 / vector.fromMatrix4Column( m, 0 ).length();
const sy = 1 / vector.fromMatrix4Column( m, 1 ).length();
const sz = 1 / vector.fromMatrix4Column( m, 2 ).length();
e[ 0 ] = me[ 0 ] * sx;
e[ 1 ] = me[ 1 ] * sx;
e[ 2 ] = me[ 2 ] * sx;
e[ 3 ] = me[ 4 ] * sy;
e[ 4 ] = me[ 5 ] * sy;
e[ 5 ] = me[ 6 ] * sy;
e[ 6 ] = me[ 8 ] * sz;
e[ 7 ] = me[ 9 ] * sz;
e[ 8 ] = me[ 10 ] * sz;
this.fromMatrix3( matrix );
return this;
}
/**
* Sets the components of this quaternion from the given euler angle (YXZ order).
*
* @param {Number} x - Rotation around x axis in radians.
* @param {Number} y - Rotation around y axis in radians.
* @param {Number} z - Rotation around z axis in radians.
* @return {Quaternion} A reference to this quaternion.
*/
fromEuler( x, y, z ) {
// from 3D Math Primer for Graphics and Game Development
// 8.7.5 Converting Euler Angles to a Quaternion
// assuming YXZ (head/pitch/bank or yaw/pitch/roll) order
const c1 = Math.cos( y / 2 );
const c2 = Math.cos( x / 2 );
const c3 = Math.cos( z / 2 );
const s1 = Math.sin( y / 2 );
const s2 = Math.sin( x / 2 );
const s3 = Math.sin( z / 2 );
this.w = c1 * c2 * c3 + s1 * s2 * s3;
this.x = c1 * s2 * c3 + s1 * c2 * s3;
this.y = s1 * c2 * c3 - c1 * s2 * s3;
this.z = c1 * c2 * s3 - s1 * s2 * c3;
return this;
}
/**
* Returns an euler angel (YXZ order) representation of this quaternion.
*
* @param {Object} euler - The resulting euler angles.
* @return {Object} The resulting euler angles.
*/
toEuler( euler ) {
// from 3D Math Primer for Graphics and Game Development
// 8.7.6 Converting a Quaternion to Euler Angles
// extract pitch
const sp = - 2 * ( this.y * this.z - this.x * this.w );
// check for gimbal lock
if ( Math.abs( sp ) > 0.9999 ) {
// looking straight up or down
euler.x = Math.PI * 0.5 * sp;
euler.y = Math.atan2( this.x * this.z + this.w * this.y, 0.5 - this.x * this.x - this.y * this.y );
euler.z = 0;
} else { //todo test
euler.x = Math.asin( sp );
euler.y = Math.atan2( this.x * this.z + this.w * this.y, 0.5 - this.x * this.x - this.y * this.y );
euler.z = Math.atan2( this.x * this.y + this.w * this.z, 0.5 - this.x * this.x - this.z * this.z );
}
return euler;
}
/**
* Sets the components of this quaternion from the given 3x3 rotation matrix.
*
* @param {Matrix3} m - The rotation matrix.
* @return {Quaternion} A reference to this quaternion.
*/
fromMatrix3( m ) {
const e = m.elements;
const m11 = e[ 0 ], m12 = e[ 3 ], m13 = e[ 6 ];
const m21 = e[ 1 ], m22 = e[ 4 ], m23 = e[ 7 ];
const m31 = e[ 2 ], m32 = e[ 5 ], m33 = e[ 8 ];
const trace = m11 + m22 + m33;
if ( trace > 0 ) {
let s = 0.5 / Math.sqrt( trace + 1.0 );
this.w = 0.25 / s;
this.x = ( m32 - m23 ) * s;
this.y = ( m13 - m31 ) * s;
this.z = ( m21 - m12 ) * s;
} else if ( ( m11 > m22 ) && ( m11 > m33 ) ) {
let s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
this.w = ( m32 - m23 ) / s;
this.x = 0.25 * s;
this.y = ( m12 + m21 ) / s;
this.z = ( m13 + m31 ) / s;
} else if ( m22 > m33 ) {
let s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
this.w = ( m13 - m31 ) / s;
this.x = ( m12 + m21 ) / s;
this.y = 0.25 * s;
this.z = ( m23 + m32 ) / s;
} else {
let s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
this.w = ( m21 - m12 ) / s;
this.x = ( m13 + m31 ) / s;
this.y = ( m23 + m32 ) / s;
this.z = 0.25 * s;
}
return this;
}
/**
* Sets the components of this quaternion from an array.
*
* @param {Array<Number>} array - An array.
* @param {Number} offset - An optional offset.
* @return {Quaternion} A reference to this quaternion.
*/
fromArray( array, offset = 0 ) {
this.x = array[ offset + 0 ];
this.y = array[ offset + 1 ];
this.z = array[ offset + 2 ];
this.w = array[ offset + 3 ];
return this;
}
/**
* Copies all values of this quaternion to the given array.
*
* @param {Array<Number>} array - An array.
* @param {Number} offset - An optional offset.
* @return {Array<Number>} The array with the quaternion components.
*/
toArray( array, offset = 0 ) {
array[ offset + 0 ] = this.x;
array[ offset + 1 ] = this.y;
array[ offset + 2 ] = this.z;
array[ offset + 3 ] = this.w;
return array;
}
/**
* Returns true if the given quaternion is deep equal with this quaternion.
*
* @param {Quaternion} q - The quaternion to test.
* @return {Boolean} The result of the equality test.
*/
equals( q ) {
return ( ( q.x === this.x ) && ( q.y === this.y ) && ( q.z === this.z ) && ( q.w === this.w ) );
}
}
export { Quaternion };