/**
* Provides an interface for quaternions.
* They express 3D orientations in space with complex numbers.
* These are preferred over {@linkplain syngen.utility.euler|euler angles} to avoid gimbal lock.
* @interface
* @see syngen.utility.quaternion.create
*/
syngen.utility.quaternion = {}
/**
* Instantiates a new quaternion.
* @param {syngen.utility.quaternion|Object} [options={}]
* @param {Number} [options.w=1]
* @param {Number} [options.x=0]
* @param {Number} [options.y=0]
* @param {Number} [options.z=0]
* @returns {syngen.utility.quaternion}
* @static
*/
syngen.utility.quaternion.create = function (options = {}) {
return Object.create(this.prototype).construct(options)
}
/**
* Converts an Euler angle to a quaternion.
* @param {syngen.utility.euler} euler
* @param {String} [sequence={@link syngen.const.eulerToQuaternion}]
* @returns {syngen.utility.quaternion}
* @see syngen.const.eulerToQuaternion
* @static
*/
syngen.utility.quaternion.fromEuler = function ({
pitch = 0,
roll = 0,
yaw = 0,
} = {}, sequence = syngen.const.eulerToQuaternion) {
// SEE: https://github.com/infusion/Quaternion.js/blob/master/quaternion.js
sequence = sequence.toUpperCase()
const x = roll / 2,
y = pitch / 2,
z = yaw / 2
const cx = Math.cos(x),
cy = Math.cos(y),
cz = Math.cos(z),
sx = Math.sin(x),
sy = Math.sin(y),
sz = Math.sin(z)
switch (sequence) {
case 'XYZ':
return this.create({
w: (cx * cy * cz) - (sx * sy * sz),
x: (sx * cy * cz) + (cx * sy * sz),
y: (cx * sy * cz) - (sx * cy * sz),
z: (cx * cy * sz) + (sx * sy * cz),
})
case 'XZY':
return this.create({
w: (cx * cy * cz) + (sx * sy * sz),
x: (sx * cy * cz) - (cx * sy * sz),
y: (cx * sy * cz) - (sx * cy * sz),
z: (cx * cy * sz) + (sx * sy * cz),
})
case 'YXZ':
return this.create({
w: (cx * cy * cz) + (sx * sy * sz),
x: (sx * cy * cz) + (cx * sy * sz),
y: (cx * sy * cz) - (sx * cy * sz),
z: (cx * cy * sz) - (sx * sy * cz),
})
case 'YZX':
return this.create({
w: (cx * cy * cz) - (sx * sy * sz),
x: (sx * cy * cz) + (cx * sy * sz),
y: (cx * sy * cz) + (sx * cy * sz),
z: (cx * cy * sz) - (sx * sy * cz),
})
case 'ZXY':
return this.create({
w: (cx * cy * cz) - (sx * sy * sz),
x: (sx * cy * cz) - (cx * sy * sz),
y: (cx * sy * cz) + (sx * cy * sz),
z: (cx * cy * sz) + (sx * sy * cz),
})
case 'ZYX':
return this.create({
w: (cx * cy * cz) + (sx * sy * sz),
x: (sx * cy * cz) - (cx * sy * sz),
y: (cx * sy * cz) + (sx * cy * sz),
z: (cx * cy * sz) - (sx * sy * cz),
})
}
}
syngen.utility.quaternion.prototype = {
/**
* Returns a new instance with the same properties.
* @instance
* @returns {syngen.utility.quaternion}
*/
clone: function () {
return syngen.utility.quaternion.create(this)
},
/**
* Returns the conjugate as a new instance.
* This represents the reverse orientation.
* @instance
* @returns {syngen.utility.quaternion}
*/
conjugate: function () {
return syngen.utility.quaternion.create({
w: this.w,
x: -this.x,
y: -this.y,
z: -this.z,
})
},
/**
* Initializes the instance with `options`.
* These values are best derived from {@link syngen.utility.quaternion.fromEuler} or other quaternions.
* @instance
* @param {syngen.utility.quaternion|Object} [options={}]
* @private
*/
construct: function ({
w = 1,
x = 0,
y = 0,
z = 0,
} = {}) {
this.w = w
this.x = x
this.y = y
this.z = z
return this
},
/**
* Calculates the magnitude (Euclidean distance).
* @instance
* @returns {Number}
*/
distance: function () {
return Math.sqrt((this.w ** 2) + (this.x ** 2) + (this.y ** 2) + (this.z ** 2))
},
/**
* Calculates the norm (squared Euclidean distance).
* @instance
* @returns {Number}
*/
distance2: function () {
return (this.w ** 2) + (this.x ** 2) + (this.y ** 2) + (this.z ** 2)
},
/**
* Multiplies this by the inverse of `quaternion` to return their difference as a new instance.
* @instance
* @param {syngen.utility.quaternion|Object} [quaternion]
* @returns {syngen.utility.quaternion}
*/
divide: function (divisor) {
if (!syngen.utility.quaternion.prototype.isPrototypeOf(quaternion)) {
quaternion = syngen.utility.quaternion.create(quaternion)
}
return this.multiply(quaternion.inverse())
},
/**
* Returns whether this is equal to `quaternion`.
* @instance
* @param {syngen.utility.quaternion|Object} [quaternion]
* @returns {Boolean}
*/
equals: function ({
w = 1,
x = 0,
y = 0,
z = 0,
} = {}) {
return (this.w == w) && (this.x == x) && (this.y == y) && (this.z == z)
},
/**
* Returns the unit vector that's ahead of the orientation.
* The vector can be inverted to receive a vector behind.
* @instance
* @returns {syngen.utility.vector3d}
*/
forward: function () {
return syngen.utility.vector3d.unitX().rotateQuaternion(this)
},
/**
* Returns the multiplicative inverse as a new instance.
* @instance
* @returns {syngen.utility.quaternion}
*/
inverse: function () {
const scalar = 1 / this.distance2()
if (!isFinite(scalar)) {
return this.conjugate()
}
return this.conjugate().scale(scalar)
},
/**
* Returns whether this is equal to the identity quaternion.
* @instance
* @returns {Boolean}
*/
isZero: function () {
return (this.w == 1) && !this.x && !this.y && !this.z
},
/**
* Linearly interpolates `quaternion` to this and returns it as a new instance.
* @instance
* @param {syngen.utility.quaternion|Object} quaternion
* @returns {syngen.utility.quaternion}
* @todo Create syngen.utility.quaternion.slerpFrom for spherical interpolation
*/
lerpFrom: function ({
w = 1,
x = 0,
y = 0,
z = 0,
} = {}, value = 0) {
return syngen.utility.quaternion.create({
w: syngen.utility.lerp(w, this.w, value),
x: syngen.utility.lerp(x, this.x, value),
y: syngen.utility.lerp(y, this.y, value),
z: syngen.utility.lerp(z, this.z, value),
})
},
/**
* Linearly interpolates this to `quaternion` and returns it as a new instance.
* @instance
* @param {syngen.utility.quaternion|Object} quaternion
* @returns {syngen.utility.quaternion}
* @todo Create syngen.utility.quaternion.slerpTo for spherical interpolation
*/
lerpTo: function ({
w = 1,
x = 0,
y = 0,
z = 0,
} = {}, value = 0) {
return syngen.utility.quaternion.create({
w: syngen.utility.lerp(this.w, w, value),
x: syngen.utility.lerp(this.x, x, value),
y: syngen.utility.lerp(this.y, y, value),
z: syngen.utility.lerp(this.z, z, value),
})
},
/**
* Multiplies this by `quaternion` to return their sum as a new instance.
* @instance
* @param {syngen.utility.quaternion|Object} [quaternion]
* @returns {syngen.utility.quaternion}
*/
multiply: function ({
w = 1,
x = 0,
y = 0,
z = 0,
} = {}) {
return syngen.utility.quaternion.create({
w: (this.w * w) - (this.x * x) - (this.y * y) - (this.z * z),
x: (this.w * x) + (this.x * w) + (this.y * z) - (this.z * y),
y: (this.w * y) + (this.y * w) + (this.z * x) - (this.x * z),
z: (this.w * z) + (this.z * w) + (this.x * y) - (this.y * x),
})
},
/**
* Normalizes this and returns it as a new instance.
* @instance
* @returns {syngen.utility.quaternion}
*/
normalize: function () {
const distance = this.distance()
if (!distance) {
return this.clone()
}
return this.scale(1 / distance)
},
/**
* Returns the unit vector that's to the right of the orientation.
* The vector can be inverted to receive a vector to its left.
* @instance
* @returns {syngen.utility.vector3d}
*/
right: function () {
return syngen.utility.vector3d.unitY().rotateQuaternion(this)
},
/**
* Multiplies this by `scalar` and returns it as a new instance.
* Typically it's nonsensical to use this manually.
* @instance
* @param {Number} [scalar=0]
* @returns {syngen.utility.quaternion}
* @private
*/
scale: function (scalar = 0) {
return syngen.utility.quaternion.create({
w: this.w * scalar,
x: this.x * scalar,
y: this.y * scalar,
z: this.z * scalar,
})
},
/**
* Sets all properties with `options`.
* These values are best derived from {@link syngen.utility.quaternion.fromEuler} or other quaternions.
* @instance
* @param {syngen.utility.quaternion|Object} [options]
* @param {Number} [options.w=1]
* @param {Number} [options.x=0]
* @param {Number} [options.y=0]
* @param {Number} [options.z=0]
*/
set: function ({
w = 1,
x = 0,
y = 0,
z = 0,
} = {}) {
this.w = w
this.x = x
this.y = y
this.z = z
return this
},
/**
* Returns the unit vector that's above of the orientation.
* The vector can be inverted to receive a vector below.
* @instance
* @returns {syngen.utility.vector3d}
*/
up: function () {
return syngen.utility.vector3d.unitZ().rotateQuaternion(this)
},
/**
* The real w-component of the quaternion.
* Implementations are discouraged from modifying this directly.
* @instance
* @type {Number}
*/
w: 1,
/**
* The imaginary x-component of the quaternion.
* Implementations are discouraged from modifying this directly.
* @instance
* @type {Number}
*/
x: 0,
/**
* The imaginary y-component of the quaternion.
* Implementations are discouraged from modifying this directly.
* @instance
* @type {Number}
*/
y: 0,
/**
* The imaginary z-component of the quaternion.
* Implementations are discouraged from modifying this directly.
* @instance
* @type {Number}
*/
z: 0,
}
/**
* Instantiates an identity quaternion.
* @returns {syngen.utility.quaternion}
* @static
*/
syngen.utility.quaternion.identity = function () {
return Object.create(this.prototype).construct({
w: 1,
})
}