/**
* Provides an interface for Euler angles.
* They express 3D orientations in space with pitch, roll, and yaw.
* Although they're explicitly easier to use, implementations should prefer {@linkplain syngen.utility.quaternion|quaternions} to avoid gimbal lock.
* @interface
* @see syngen.utility.euler.create
*/
syngen.utility.euler = {}
/**
* Instantiates a new Euler angle.
* @param {syngen.utility.euler|Object} [options={}]
* @param {Number} [options.pitch=0]
* @param {Number} [options.roll=0]
* @param {Number} [options.yaw=0]
* @returns {syngen.utility.euler}
* @static
*/
syngen.utility.euler.create = function (options = {}) {
return Object.create(this.prototype).construct(options)
}
/**
* Converts a quaternion to an Euler angle.
* @param {syngen.utility.quaternion} quaternion
* @param {String} [sequence={@link syngen.const.eulerToQuaternion}]
* @returns {syngen.utility.euler}
* @see syngen.const.eulerToQuaternion
* @static
*/
syngen.utility.euler.fromQuaternion = function ({
w = 0,
x = 0,
y = 0,
z = 0,
} = {}, sequence = syngen.const.eulerToQuaternion) {
// SEE: http://bediyap.com/programming/convert-quaternion-to-euler-rotations/
const w2 = w ** 2,
wx = w * x,
wy = w * y,
wz = w * z,
x2 = x ** 2,
xy = x * y,
xz = x * z,
y2 = y ** 2,
yz = y * z,
z2 = z ** 2
switch (sequence) {
case 'XYZ':
return this.create({
pitch: Math.asin(2 * (xz + wy)),
roll: Math.atan2(-2 * (yz - wx), w2 - x2 - y2 + z2),
yaw: Math.atan2(-2 * (xy - wz), w2 + x2 - y2 - z2),
})
case 'XZY':
return this.create({
pitch: Math.atan2(2 * (xz + wy), w2 + x2 - y2 - z2),
roll: Math.atan2(2 * (yz + wx), w2 - x2 + y2 - z2),
yaw: Math.asin(-2 * (xy - wz)),
})
case 'YXZ':
return this.create({
pitch: Math.atan2(2 * (xz + wy), w2 - x2 - y2 + z2),
roll: Math.asin(-2 * (yz - wx)),
yaw: Math.atan2(2 * (xy + wz), w2 - x2 + y2 - z2),
})
case 'YZX':
return this.create({
pitch: Math.atan2(-2 * (xz - wy), w2 + x2 - y2 - z2),
roll: Math.atan2(-2 * (yz - wx), w2 - x2 + y2 - z2),
yaw: Math.asin(2 * (xy + wz)),
})
case 'ZXY':
return this.create({
pitch: Math.atan2(-2 * (xz - wy), w2 - x2 - y2 + z2),
roll: Math.asin(2 * (yz + wx)),
yaw: Math.atan2(-2 * (xy - wz), w2 - x2 + y2 - z2),
})
case 'ZYX':
return this.create({
pitch: Math.asin(-2 * (xz - wy)),
roll: Math.atan2(2 * (yz + wx), w2 - x2 - y2 + z2),
yaw: Math.atan2(2 * (xy + wz), w2 + x2 - y2 - z2),
})
}
}
syngen.utility.euler.prototype = {
/**
* Returns a new instance with the same properties.
* @instance
* @returns {syngen.utility.euler}
*/
clone: function () {
return syngen.utility.euler.create(this)
},
/**
* Initializes the instance with `options`.
* @instance
* @param {Object} [options={}]
* @private
*/
construct: function ({
pitch = 0,
roll = 0,
yaw = 0,
} = {}) {
this.pitch = pitch
this.roll = roll
this.yaw = yaw
return this
},
/**
* Returns whether this is equal to `euler`.
* @instance
* @param {syngen.utility.euler|Object} [euler]
* @returns {Boolean}
*/
equals: function ({
pitch = 0,
roll = 0,
yaw = 0,
} = {}) {
return (this.pitch == pitch) && (this.roll == roll) && (this.yaw == yaw)
},
/**
* 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().rotateEuler(this)
},
/**
* Returns whether all properties are zero.
* @instance
* @returns {Boolean}
*/
isZero: function () {
return !this.pitch && !this.roll && !this.yaw
},
/**
* Rotation along the y-axis.
* Normally within `[-π/2, π/2]`.
* @instance
* @type {Number}
*/
pitch: 0,
/**
* 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().rotateEuler(this)
},
/**
* Rotation along the x-axis.
* Normally within `[-π, π]`.
* @instance
* @type {Number}
*/
roll: 0,
/**
* Multiplies this by `scalar` and returns it as a new instance.
* @instance
* @param {Number} [scalar=0]
* @returns {syngen.utility.euler}
*/
scale: function (scalar = 0) {
return syngen.utility.euler.create({
pitch: this.pitch * scalar,
roll: this.roll * scalar,
yaw: this.yaw * scalar,
})
},
/**
* Sets all properties to `options`.
* @instance
* @param {syngen.utility.euler|Object} [options]
* @param {Number} [options.pitch=0]
* @param {Number} [options.roll=0]
* @param {Number} [options.yaw=0]
*/
set: function ({
pitch = 0,
roll = 0,
yaw = 0,
} = {}) {
this.pitch = pitch
this.roll = roll
this.yaw = yaw
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().rotateEuler(this)
},
/**
* Rotation along the z-axis.
* Normally within `[-π, π]`.
* @instance
* @type {Number}
*/
yaw: 0,
}