diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index 879238b0a2..23e8e3b896 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -21,13 +21,24 @@ #include /**jsdoc - * A Quaternion - * + * A quaternion value. See also the {@link Quat(0)|Quat} object. * @typedef {object} Quat - * @property {float} x imaginary component i. - * @property {float} y imaginary component j. - * @property {float} z imaginary component k. - * @property {float} w real component. + * @property {number} x - Imaginary component i. + * @property {number} y - Imaginary component j. + * @property {number} z - Imaginary component k. + * @property {number} w - Real component. + */ + +/**jsdoc + * The Quat API provides facilities for generating and manipulating quaternions. + * Quaternions should be used in preference to Euler angles wherever possible because quaternions don't suffer from the problem + * of gimbal lock. + * @namespace Quat + * @variation 0 + * @property IDENTITY {Quat} The identity rotation, i.e., no rotation. + * @example Print the IDENTITY value. + * print(JSON.stringify(Quat.IDENTITY)); // { x: 0, y: 0, z: 0, w: 1 } + * print(JSON.stringify(Quat.safeEulerAngles(Quat.IDENTITY))); // { x: 0, y: 0, z: 0 } */ /// Scriptable interface a Quaternion helper class object. Used exclusively in the JavaScript API @@ -36,33 +47,408 @@ class Quat : public QObject, protected QScriptable { Q_PROPERTY(glm::quat IDENTITY READ IDENTITY CONSTANT) public slots: + + /**jsdoc + * Multiply two quaternions. + * @function Quat(0).multiply + * @param {Quat} q1 - The first quaternion. + * @param {Quat} q2 - The second quaternion. + * @returns {Quat} q1 multiplied with q2. + * @example Calculate the orientation of your avatar's right hand in world coordinates. + * var handController = Controller.Standard.RightHand; + * var handPose = Controller.getPoseValue(handController); + * if (handPose.valid) { + * var handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); + * } + */ glm::quat multiply(const glm::quat& q1, const glm::quat& q2); + + /**jsdoc + * Normalizes a quaternion. + * @function Quat(0).normalize + * @param {Quat} q - The quaternion to normalize. + * @returns {Quat} q normalized to have unit length. + * @example Normalize a repeated delta rotation so that maths rounding errors don't accumulate. + * var deltaRotation = Quat.fromPitchYawRollDegrees(0, 0.1, 0); + * var currentRotation = Quat.ZERO; + * while (Quat.safeEulerAngles(currentRotation).y < 180) { + * currentRotation = Quat.multiply(deltaRotation, currentRotation); + * currentRotation = Quat.normalize(currentRotation); + * // Use currentRotatation for something. + * } + */ glm::quat normalize(const glm::quat& q); + + /**jsdoc + * Calculate the conjugate of a quaternion. For a unit quaternion, its conjugate is the same as its + * {@link Quat(0).inverse|Quat.inverse}. + * @function Quat(0).conjugate + * @param {Quat} q - The quaternion to conjugate. + * @returns {Quat} The conjugate of q. + * @example A unit quaternion multiplied by its conjugate is a zero rotation. + * var quaternion = Quat.fromPitchYawRollDegrees(10, 20, 30); + * Quat.print("quaternion", quaternion, true); // dvec3(10.000000, 20.000004, 30.000004) + * var conjugate = Quat.conjugate(quaternion); + * Quat.print("conjugate", conjugate, true); // dvec3(1.116056, -22.242186, -28.451778) + * var identity = Quat.multiply(conjugate, quaternion); + * Quat.print("identity", identity, true); // dvec3(0.000000, 0.000000, 0.000000) + */ glm::quat conjugate(const glm::quat& q); + + /**jsdoc + * Calculate a camera orientation given eye position, point of interest, and "up" direction. The camera's negative z-axis is + * the forward direction. The result has zero roll about its forward direction with respect to the given "up" direction. + * @function Quat(0).lookAt + * @param {Vec3} eye - The eye position. + * @param {Vec3} target - The point to look at. + * @param {Vec3} up - The "up" direction. + * @returns {Quat} A quaternion that orients the negative z-axis to point along the eye-to-target vector and the x-axis to + * be the cross product of the eye-to-target and up vectors. + * @example Rotate your view in independent mode to look at the world origin upside down. + * Camera.mode = "independent"; + * Camera.orientation = Quat.lookAt(Camera.position, Vec3.ZERO, Vec3.UNIT_NEG_Y); + */ glm::quat lookAt(const glm::vec3& eye, const glm::vec3& center, const glm::vec3& up); + + /**jsdoc + * Calculate a camera orientation given eye position and point of interest. The camera's negative z-axis is the forward + * direction. The result has zero roll about its forward direction. + * @function Quat(0).lookAtSimple + * @param {Vec3} eye - The eye position. + * @param {Vec3} target - The point to look at. + * @returns {Quat} A quaternion that orients the negative z-axis to point along the eye-to-target vector and the x-axis to be + * the cross product of the eye-to-target and an "up" vector. The "up" vector is the y-axis unless the eye-to-target + * vector is nearly aligned with it (i.e., looking near vertically up or down), in which case the x-axis is used as the + * "up" vector. + * @example Rotate your view in independent mode to look at the world origin. + * Camera.mode = "independent"; + * Camera.orientation = Quat.lookAtSimple(Camera.position, Vec3.ZERO); + */ glm::quat lookAtSimple(const glm::vec3& eye, const glm::vec3& center); + + /**jsdoc + * Calculate the shortest rotation from a first vector onto a second. + * @function Quat(0).rotationBetween + * @param {Vec3} v1 - The first vector. + * @param {Vec3} v2 - The second vector. + * @returns {Quat} The rotation from v1 onto v2. + * @example Apply a change in velocity to an entity and rotate it to face the direction it's travelling. + * var newVelocity = Vec3.sum(entityVelocity, deltaVelocity); + * var properties = { velocity: newVelocity }; + * if (Vec3.length(newVelocity) > 0.001) { + * properties.rotation = Quat.rotationBetween(entityVelocity, newVelocity); + * } + * Entities.editEntity(entityID, properties); + * entityVelocity = newVelocity; + */ glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2); - glm::quat fromVec3Degrees(const glm::vec3& vec3); // degrees - glm::quat fromVec3Radians(const glm::vec3& vec3); // radians - glm::quat fromPitchYawRollDegrees(float pitch, float yaw, float roll); // degrees - glm::quat fromPitchYawRollRadians(float pitch, float yaw, float roll); // radians + + /**jsdoc + * Generate a quaternion from a {@link Vec3} of Euler angles in degrees. + * @function Quat(0).fromVec3Degrees + * @param {Vec3} vector - A vector of three Euler angles in degrees, the angles being the rotations about the x, y, and z + * axes. + * @returns {Quat} A quaternion created from the Euler angles in vector. + * @example Zero out pitch and roll from an orientation. + * var eulerAngles = Quat.safeEulerAngles(orientation); + * eulerAngles.x = 0; + * eulerAngles.z = 0; + * var newOrientation = Quat.fromVec3Degrees(eulerAngles); + */ + glm::quat fromVec3Degrees(const glm::vec3& vec3); + + /**jsdoc + * Generate a quaternion from a {@link Vec3} of Euler angles in radians. + * @function Quat(0).fromVec3Radians + * @param {Vec3} vector - A vector of three Euler angles in radians, the angles being the rotations about the x, y, and z + * axes. + * @returns {Quat} A quaternion created using the Euler angles in vector. + * @example Create a rotation of 180 degrees about the y axis. + * var rotation = Quat.fromVec3Radians({ x: 0, y: Math.PI, z: 0 }); + */ + glm::quat fromVec3Radians(const glm::vec3& vec3); + + /**jsdoc + * Generate a quaternion from pitch, yaw, and roll values in degrees. + * @function Quat(0).fromPitchYawRollDegrees + * @param {number} pitch - The pitch angle in degrees. + * @param {number} yaw - The yaw angle in degrees. + * @param {number} roll - The roll angle in degrees. + * @returns {Quat} A quaternion created using the pitch, yaw, and roll Euler angles. + * @example Create a rotation of 180 degrees about the y axis. + * var rotation = Quat.fromPitchYawRollDgrees(0, 180, 0 ); + */ + glm::quat fromPitchYawRollDegrees(float pitch, float yaw, float roll); + + /**jsdoc + * Generate a quaternion from pitch, yaw, and roll values in radians. + * @function Quat(0).fromPitchYawRollRadians + * @param {number} pitch - The pitch angle in radians. + * @param {number} yaw - The yaw angle in radians. + * @param {number} roll - The roll angle in radians. + * @returns {Quat} A quaternion created from the pitch, yaw, and roll Euler angles. + * @example Create a rotation of 180 degrees about the y axis. + * var rotation = Quat.fromPitchYawRollRadians(0, Math.PI, 0); + */ + glm::quat fromPitchYawRollRadians(float pitch, float yaw, float roll); + + /**jsdoc + * Calculate the inverse of a quaternion. For a unit quaternion, its inverse is the same as its + * {@link Quat(0).conjugate|Quat.conjugate}. + * @function Quat(0).inverse + * @param {Quat} q - The quaternion. + * @returns {Quat} The inverse of q. + * @example A quaternion multiplied by its inverse is a zero rotation. + * var quaternion = Quat.fromPitchYawRollDegrees(10, 20, 30); + * Quat.print("quaternion", quaternion, true); // dvec3(10.000000, 20.000004, 30.000004) + * var inverse = Quat.invserse(quaternion); + * Quat.print("inverse", inverse, true); // dvec3(1.116056, -22.242186, -28.451778) + * var identity = Quat.multiply(inverse, quaternion); + * Quat.print("identity", identity, true); // dvec3(0.000000, 0.000000, 0.000000) + */ glm::quat inverse(const glm::quat& q); - // redundant, calls getForward which better describes the returned vector as a direction + + /**jsdoc + * Get the "front" direction that the camera would face if its orientation was set to the quaternion value. + * This is a synonym for {@link Quat(0).getForward|Quat.getForward}. + * The High Fidelity camera has axes x = right, y = up, -z = forward. + * @function Quat(0).getFront + * @param {Quat} orientation - A quaternion representing an orientation. + * @returns {Vec3} The negative z-axis rotated by orientation. + */ glm::vec3 getFront(const glm::quat& orientation) { return getForward(orientation); } + + /**jsdoc + * Get the "forward" direction that the camera would face if its orientation was set to the quaternion value. + * This is a synonym for {@link Quat(0).getFront|Quat.getFront}. + * The High Fidelity camera has axes x = right, y = up, -z = forward. + * @function Quat(0).getForward + * @param {Quat} orientation - A quaternion representing an orientation. + * @returns {Vec3} The negative z-axis rotated by orientation. + * @example Demonstrate that the "forward" vector is for the negative z-axis. + * var forward = Quat.getForward(Quat.IDENTITY); + * print(JSON.stringify(forward)); // {"x":0,"y":0,"z":-1} + */ glm::vec3 getForward(const glm::quat& orientation); + + /**jsdoc + * Get the "right" direction that the camera would have if its orientation was set to the quaternion value. + * The High Fidelity camera has axes x = right, y = up, -z = forward. + * @function Quat(0).getRight + * @param {Quat} orientation - A quaternion representing an orientation. + * @returns {Vec3} The x-axis rotated by orientation. + */ glm::vec3 getRight(const glm::quat& orientation); + + /**jsdoc + * Get the "up" direction that the camera would have if its orientation was set to the quaternion value. + * The High Fidelity camera has axes x = right, y = up, -z = forward. + * @function Quat(0).getUp + * @param {Quat} orientation - A quaternion representing an orientation. + * @returns {Vec3} The y-axis rotated by orientation. + */ glm::vec3 getUp(const glm::quat& orientation); - glm::vec3 safeEulerAngles(const glm::quat& orientation); // degrees - glm::quat angleAxis(float angle, const glm::vec3& v); // degrees + + /**jsdoc + * Calculate the Euler angles for the quaternion, in degrees. (The "safe" in the name signifies that the angle results will + * not be garbage even when the rotation is particularly difficult to decompose with pitches around +/-90 degrees.) + * @function Quat(0).safeEulerAngles + * @param {Quat} orientation - A quaternion representing an orientation. + * @returns {Vec3} A {@link Vec3} of Euler angles for the orientation, in degrees, the angles being the + * rotations about the x, y, and z axes. + * @example Report the camera yaw. + * var eulerAngles = Quat.safeEulerAngles(Camera.orientation); + * print("Camera yaw: " + eulerAngles.y); + */ + glm::vec3 safeEulerAngles(const glm::quat& orientation); + + /**jsdoc + * Generate a quaternion given an angle to rotate through and an axis to rotate about. + * @function Quat(0).angleAxis + * @param {number} angle - The angle to rotate through, in degrees. + * @param {Vec3} axis - The unit axis to rotate about. + * @returns {Quat} A quaternion that is a rotation through angle degrees about the axis. + * WARNING: This value is in degrees whereas the value returned by {@link Quat(0).angle|Quat.angle} is + * in radians. + * @example Calculate a rotation of 90 degrees about the direction your camera is looking. + * var rotation = Quat.angleAxis(90, Quat.getForward(Camera.orientation)); + */ + glm::quat angleAxis(float angle, const glm::vec3& v); + + /**jsdoc + * Get the rotation axis for a quaternion. + * @function Quat(0).axis + * @param {Quat} q - The quaternion. + * @returns {Vec3} The normalized rotation axis for q. + * @example Get the rotation axis of a quaternion. + * var forward = Quat.getForward(Camera.orientation); + * var rotation = Quat.angleAxis(90, forward); + * var axis = Quat.axis(rotation); + * print("Forward: " + JSON.stringify(forward)); + * print("Axis: " + JSON.stringify(axis)); // Same value as forward. + */ glm::vec3 axis(const glm::quat& orientation); + + /**jsdoc + * Get the rotation angle for a quaternion. + * @function Quat(0).angle + * @param {Quat} q - The quaternion. + * @returns {number} The rotation angle for q, in radians. WARNING: This value is in radians + * whereas the value used by {@link Quat(0).angleAxis|Quat.angleAxis} is in degrees. + * @example Get the rotation angle of a quaternion. + * var forward = Quat.getForward(Camera.orientation); + * var rotation = Quat.angleAxis(90, forward); + * var angle = Quat.angle(rotation); + * print("Angle: " + angle * 180 / Math.PI); // 90 degrees. + */ float angle(const glm::quat& orientation); + + // spherical linear interpolation + // alpha: 0.0 to 1.0? + /**jsdoc + * Compute a spherical linear interpolation between two rotations, safely handling two rotations that are very similar. + * See also, {@link Quat(0).slerp|Quat.slerp}. + * @function Quat(0).mix + * @param {Quat} q1 - The beginning rotation. + * @param {Quat} q2 - The ending rotation. + * @param {number} alpha - The mixture coefficient between 0.0 and 1.0. Specifies the proportion + * of q2's value to return in favor of q1's value. A value of 0.0 returns + * q1's value; 1.0 returns q2s's value. + * @returns {Quat} A spherical linear interpolation between rotations q1 and q2. + * @example Animate between one rotation and another. + * var dt = amountOfTimeThatHasPassed; + * var mixFactor = amountOfTimeThatHasPassed / TIME_TO_COMPLETE; + * if (mixFactor > 1) { + * mixFactor = 1; + * } + * var newRotation = Quat.mix(startRotation, endRotation, mixFactor); + */ glm::quat mix(const glm::quat& q1, const glm::quat& q2, float alpha); + + /**jsdoc + * Compute a spherical linear interpolation between two rotations, for rotations that are not very similar. + * See also, {@link Quat(0).mix|Quat.mix}. + * @function Quat(0).slerp + * @param {Quat} q1 - The beginning rotation. + * @param {Quat} q2 - The ending rotation. + * @param {number} alpha - The mixture coefficient between 0.0 and 1.0. Specifies the proportion + * of q2's value to return in favor of q1's value. A value of 0.0 returns + * q1's value; 1.0 returns q2s's value. + * @returns {Quat} A spherical linear interpolation between rotations q1 and q2. + */ glm::quat slerp(const glm::quat& q1, const glm::quat& q2, float alpha); + + /**jsdoc + * Compute a spherical quadrangle interpolation between two rotations along a path oriented toward two other rotations. + * Equivalent to: Quat.slerp(Quat.slerp(q1, q2, alpha), Quat.slerp(s1, s2, alpha), 2 * alpha * (1.0 - alpha)). + * @function Quat(0).squad + * @param {Quat} q1 - Initial rotation. + * @param {Quat} q2 - Final rotation. + * @param {Quat} s1 - First control point. + * @param {Quat} s2 - Second control point. + * @param {number} alpha - The mixture coefficient between 0.0 and 1.0. A value of + * 0.0 returns q1's value; 1.0 returns q2s's value. + * @returns {Quat} A spherical quadrangle interpolation between rotations q1 and q2 using control + * points s1 and s2. + */ glm::quat squad(const glm::quat& q1, const glm::quat& q2, const glm::quat& s1, const glm::quat& s2, float h); + + /**jsdoc + * Calculate the dot product of two quaternions. The closer the quaternions are to each other the more non-zero the value is + * (either positive or negative). Identical unit rotations have a dot product of +/- 1. + * @function Quat(0).dot + * @param {Quat} q1 - The first quaternion. + * @param {Quat} q2 - The second quaternion. + * @returns {number} The dot product of q1 and q2. + * @example Testing unit quaternions for equality. + * var q1 = Quat.fromPitchYawRollDegrees(0, 0, 0); + * var q2 = Quat.fromPitchYawRollDegrees(0, 0, 0); + * print(Quat.equal(q1, q2)); // true + * var q3 = Quat.fromPitchYawRollDegrees(0, 0, 359.95); + * print(Quat.equal(q1, q3)); // false + * + * var dot = Quat.dot(q1, q3); + * print(dot); // -0.9999999403953552 + * var equal = Math.abs(1 - Math.abs(dot)) < 0.000001; + * print(equal); // true + */ float dot(const glm::quat& q1, const glm::quat& q2); + + /**jsdoc + * Print to the program log a text label followed by a quaternion's pitch, yaw, and roll Euler angles. + * @function Quat(0).print + * @param {string} label - The label to print. + * @param {Quat} q - The quaternion to print. + * @param {boolean} [asDegrees=false] - If true the angle values are printed in degrees, otherwise they are + * printed in radians. + * @example Two ways of printing a label plus a quaternion's Euler angles. + * var quaternion = Quat.fromPitchYawRollDegrees(0, 45, 0); + * + * // Quaternion: dvec3(0.000000, 45.000004, 0.000000) + * Quat.print("Quaternion:", quaternion, true); + * + * // Quaternion: {"x":0,"y":45.000003814697266,"z":0} + * print("Quaternion: " + JSON.stringify(Quat.safeEulerAngles(quaternion))); + */ void print(const QString& label, const glm::quat& q, bool asDegrees = false); + + /**jsdoc + * Test whether two quaternions are equal. Note: The quaternions must be exactly equal in order for + * true to be returned; it is often better to use {@link Quat(0).dot|Quat.dot} and test for closeness to +/-1. + * @function Quat(0).equal + * @param {Quat} q1 - The first quaternion. + * @param {Quat} q2 - The second quaternion. + * @returns {boolean} true if the quaternions are equal, otherwise false. + * @example Testing unit quaternions for equality. + * var q1 = Quat.fromPitchYawRollDegrees(0, 0, 0); + * var q2 = Quat.fromPitchYawRollDegrees(0, 0, 0); + * print(Quat.equal(q1, q2)); // true + * var q3 = Quat.fromPitchYawRollDegrees(0, 0, 359.95); + * print(Quat.equal(q1, q3)); // false + * + * var dot = Quat.dot(q1, q3); + * print(dot); // -0.9999999403953552 + * var equal = Math.abs(1 - Math.abs(dot)) < 0.000001; + * print(equal); // true + */ bool equal(const glm::quat& q1, const glm::quat& q2); + + /**jsdoc + * Cancels out the roll and pitch component of a quaternion so that its completely horizontal with a yaw pointing in the + * given quaternion's direction. + * @function Quat(0).cancelOutRollAndPitch + * @param {Quat} orientation - A quaternion representing an orientation. + * @returns {Quat} orientation with its roll and pitch canceled out. + * @example Two ways of calculating a camera orientation in the x-z plane with a yaw pointing in the direction of + * a given quaternion. + * var quaternion = Quat.fromPitchYawRollDegrees(10, 20, 30); + * + * var noRollOrPitch = Quat.cancelOutRollAndPitch(quaternion); + * Quat.print("", noRollOrPitch, true); // dvec3(0.000000, 22.245995, 0.000000) + * + * var front = Quat.getFront(quaternion); + * var lookAt = Quat.lookAtSimple(Vec3.ZERO, { x: front.x, y: 0, z: front.z }); + * Quat.print("", lookAt, true); // dvec3(0.000000, 22.245996, 0.000000) + * + */ glm::quat cancelOutRollAndPitch(const glm::quat& q); + + /**jsdoc + * Cancels out the roll component of a quaternion so that its horizontal axis is level. + * @function Quat(0).cancelOutRoll + * @param {Quat} orientation - A quaternion representing an orientation. + * @returns {Quat} orientation with its roll canceled out. + * @example Two ways of calculating a camera orientation that points in the direction of a given quaternion but + * keeps the camera's horizontal axis level. + * var quaternion = Quat.fromPitchYawRollDegrees(10, 20, 30); + * + * var noRoll = Quat.cancelOutRoll(quaternion); + * Quat.print("", noRoll, true); // dvec3(-1.033004, 22.245996, -0.000000) + * + * var front = Quat.getFront(quaternion); + * var lookAt = Quat.lookAtSimple(Vec3.ZERO, front); + * Quat.print("", lookAt, true); // dvec3(-1.033004, 22.245996, -0.000000) + */ glm::quat cancelOutRoll(const glm::quat& q); private: