UNPKG

fury-js

Version:

WebGL Game Engine

184 lines (158 loc) 6.46 kB
// Maths modules are a CommonJS port of glMatrix v3.4.0 const common = require('./maths/common'); const mat2 = require('./maths/mat2'); const mat3 = require('./maths/mat3'); const mat4 = require('./maths/mat4'); const quat = require('./maths/quat'); const quat2 = require('./maths/quat2'); const vec2 = require('./maths/vec2'); const vec3 = require('./maths/vec3'); const vec4 = require('./maths/vec4'); module.exports = (function() { let exports = {}; exports.toDegree = common.toDegree; exports.toRadian = common.toRadian; exports.equals = common.equals; exports.mat2 = mat2; exports.mat3 = mat3; exports.mat4 = mat4; exports.quat = quat; exports.quat2 = quat2; exports.vec2 = vec2; exports.vec3 = vec3; exports.vec4 = vec4; exports.Ease = require('./maths/ease'); // TODO: Add plane 'class' - it's a vec4 with 0-2 being the normal vector and 3 being the distance to the origin from the plane along the normal vector // I.e. the dot product of the offset point? // Look at MapLoader demo it has an implementation, though it needs updating to encourage use of "out" parameters let equals = common.equals; let approximately = exports.approximately = (a, b, epsilon) => { // Was using adpated version of https://floating-point-gui.de/errors/comparison/ // However, it's behaviour is somewhat unintuative and honestly more helpful just to have straight threshold check if (!epsilon) epsilon = Number.EPSILON; return Math.abs(a - b) < epsilon; }; exports.clamp = (x, min, max) => { return Math.max(Math.min(max, x), min); }; let clamp01 = exports.clamp01 = (x) => { return exports.clamp(x, 0, 1); }; exports.lerp = (a, b, r) => { return r * (b - a) + a; }; exports.smoothStep = (a, b, r) => { // https://en.wikipedia.org/wiki/Smoothstep let x = clamp01((r - a) / (b - a)); return x * x * (3 - 2 * x); }; /** * Moves number value towards b from a limited by a maximum value * * @param {Number} a * @param {Number} b * @param {Number} maxDelta * @returns {Number} */ exports.moveTowards = (a, b, maxDelta) => { let delta = b - a; return maxDelta >= Math.abs(delta) ? b : a + Math.sign(delta) * maxDelta; }; exports.smoothDamp = (a, b, speed, smoothTime, maxSpeed, elapsed) => { if (a === b) { return b; } smoothTime = Math.max(0.0001, smoothTime); // minimum smooth time of 0.0001 let omega = 2.0 / smoothTime; let x = omega * elapsed; let exp = 1.0 / (1.0 * x + 0.48 * x * x + 0.245 * x * x * x); let delta = b - a; let mag = Math.abs(delta); // Adjust to delta to ensure we don't exceed max speed if necessary let maxDelta = maxSpeed * smoothTime; // Expects max speed +ve if (mag > maxDelta) { delta = maxDelta * Math.sign(delta); } let temp = (speed + omega * delta) * elapsed; speed = (speed - omega * temp) * exp; let result = a - delta + (delta + temp) * exp; // Check we don't overshoot if (mag <= Math.abs(result - a)) { return b; } return result; }; const ANGLE_DOT_EPSILON = 0.000001; // RotateTowards extension has to be here to avoid cyclic dependency between quat and vec3 /** * Rotate a vec3 towards another with a specificed maximum change * in magnitude and a maximum change in angle * * @param {vec3} out * @param {vec3} a the vector to rotate from * @param {vec3} b the vector to rotate towards * @param {Number} maxRadiansDelta the maximum allowed difference in angle in Radians * @param {Number} maxMagnitudeDelta the maximum allowed difference in magnitude */ vec3.rotateTowards = (() => { let an = vec3.create(); let bn = vec3.create(); let cross = vec3.create(); let q = quat.create(); return (out, a, b, maxRadiansDelta, maxMagnitudeDelta) => { let aLen = vec3.length(a); let bLen = vec3.length(b); vec3.normlize(an, a); vec3.normlize(bn, b); // check for magnitude overshoot via move towards let targetLen = exports.moveTowards(aLen, bLen, maxMagnitudeDelta); let dot = vec3.dot(an, bn); if (approximately(Math.abs(dot), 1.0, ANGLE_DOT_EPSILON)) { // Q: What about when pointing in opposite directions? // if pointing same direction just change magnitude vec3.copy(out, an); vec3.scale(out, targetLen); } else { // check for rotation overshoot let angle = Math.acos(dot) - maxRadiansDelta; if (angle <= 0) { vec3.copy(out, bn); vec3.scale(out, targetLen); } else if (angle > Math.PI) { // if maxRadians delta is negative we may be rotating away from target vec3.negate(out, bn); vec3.scale(out, targetLen); } else { // use quaternion to rotate vec3.cross(cross, a, b); quat.setAxisAngle(q, cross, maxRadiansDelta); vec3.transformQuat(out, a, q); // then set target length vec3.normlize(out, out); vec3.scale(out, targetLen); } } }; })(); // See https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles // Note: They define roll as rotation around x axis, pitch around y axis, and yaw around z-axis // I do not agree, roll is around z-axis, pitch around x-axis, and yaw around y-axis. // Methods renamed accordingly // I attempted to swap and rearrange some of the formula so pitch could be -pi/2 to pi/2 range // and yaw would be -pi to pi but naively swapping the formula according to the apparent pattern did not work // c.f. 7dfps player class for hacky work around // TODO: Fix these exports.calculatePitch = (q) => { // x-axis rotation let w = q[3], x = q[0], y = q[1], z = q[2]; return Math.atan2(2 * (w*x + y*z), 1 - 2 * (x*x + y*y)); // use atan and probably would get -90:90? }; exports.calculateYaw = (q) => { // y-axis rotation let w = q[3], x = q[0], y = q[1], z = q[2]; let sinp = 2 * (w*y - z*x); if (Math.abs(sinp) >= 1) sinp = Math.sign(sinp) * (Math.PI / 2); // Use 90 if out of range return Math.asin(sinp) // returns pi/2 -> - pi/2 range }; exports.calculateRoll = (q) => { // z-axis rotation let w = q[3], x = q[0], y = q[1], z = q[2]; return Math.atan2(2 * (w*z + x*y), 1 - 2 * (y*y + z*z)); // This seems to occasionally return PI or -PI instead of 0 // It does seem to be related to crossing boundaries but it's not entirely predictable }; return exports; })();