UNPKG

cannon-es

Version:

A lightweight 3D physics engine written in JavaScript.

2,317 lines (1,882 loc) 346 kB
/** * Records what objects are colliding with each other */ class ObjectCollisionMatrix { /** * The matrix storage. */ /** * @todo Remove useless constructor */ constructor() { this.matrix = {}; } /** * get */ get(bi, bj) { let { id: i } = bi; let { id: j } = bj; if (j > i) { const temp = j; j = i; i = temp; } return `${i}-${j}` in this.matrix; } /** * set */ set(bi, bj, value) { let { id: i } = bi; let { id: j } = bj; if (j > i) { const temp = j; j = i; i = temp; } if (value) { this.matrix[`${i}-${j}`] = true; } else { delete this.matrix[`${i}-${j}`]; } } /** * Empty the matrix */ reset() { this.matrix = {}; } /** * Set max number of objects */ setNumObjects(n) {} } /** * A 3x3 matrix. * Authored by {@link http://github.com/schteppe/ schteppe} */ class Mat3 { /** * A vector of length 9, containing all matrix elements. */ /** * @param elements A vector of length 9, containing all matrix elements. */ constructor(elements) { if (elements === void 0) { elements = [0, 0, 0, 0, 0, 0, 0, 0, 0]; } this.elements = elements; } /** * Sets the matrix to identity * @todo Should perhaps be renamed to `setIdentity()` to be more clear. * @todo Create another function that immediately creates an identity matrix eg. `eye()` */ identity() { const e = this.elements; e[0] = 1; e[1] = 0; e[2] = 0; e[3] = 0; e[4] = 1; e[5] = 0; e[6] = 0; e[7] = 0; e[8] = 1; } /** * Set all elements to zero */ setZero() { const e = this.elements; e[0] = 0; e[1] = 0; e[2] = 0; e[3] = 0; e[4] = 0; e[5] = 0; e[6] = 0; e[7] = 0; e[8] = 0; } /** * Sets the matrix diagonal elements from a Vec3 */ setTrace(vector) { const e = this.elements; e[0] = vector.x; e[4] = vector.y; e[8] = vector.z; } /** * Gets the matrix diagonal elements */ getTrace(target) { if (target === void 0) { target = new Vec3(); } const e = this.elements; target.x = e[0]; target.y = e[4]; target.z = e[8]; return target; } /** * Matrix-Vector multiplication * @param v The vector to multiply with * @param target Optional, target to save the result in. */ vmult(v, target) { if (target === void 0) { target = new Vec3(); } const e = this.elements; const x = v.x; const y = v.y; const z = v.z; target.x = e[0] * x + e[1] * y + e[2] * z; target.y = e[3] * x + e[4] * y + e[5] * z; target.z = e[6] * x + e[7] * y + e[8] * z; return target; } /** * Matrix-scalar multiplication */ smult(s) { for (let i = 0; i < this.elements.length; i++) { this.elements[i] *= s; } } /** * Matrix multiplication * @param matrix Matrix to multiply with from left side. */ mmult(matrix, target) { if (target === void 0) { target = new Mat3(); } const A = this.elements; const B = matrix.elements; const T = target.elements; const a11 = A[0], a12 = A[1], a13 = A[2], a21 = A[3], a22 = A[4], a23 = A[5], a31 = A[6], a32 = A[7], a33 = A[8]; const b11 = B[0], b12 = B[1], b13 = B[2], b21 = B[3], b22 = B[4], b23 = B[5], b31 = B[6], b32 = B[7], b33 = B[8]; T[0] = a11 * b11 + a12 * b21 + a13 * b31; T[1] = a11 * b12 + a12 * b22 + a13 * b32; T[2] = a11 * b13 + a12 * b23 + a13 * b33; T[3] = a21 * b11 + a22 * b21 + a23 * b31; T[4] = a21 * b12 + a22 * b22 + a23 * b32; T[5] = a21 * b13 + a22 * b23 + a23 * b33; T[6] = a31 * b11 + a32 * b21 + a33 * b31; T[7] = a31 * b12 + a32 * b22 + a33 * b32; T[8] = a31 * b13 + a32 * b23 + a33 * b33; return target; } /** * Scale each column of the matrix */ scale(vector, target) { if (target === void 0) { target = new Mat3(); } const e = this.elements; const t = target.elements; for (let i = 0; i !== 3; i++) { t[3 * i + 0] = vector.x * e[3 * i + 0]; t[3 * i + 1] = vector.y * e[3 * i + 1]; t[3 * i + 2] = vector.z * e[3 * i + 2]; } return target; } /** * Solve Ax=b * @param b The right hand side * @param target Optional. Target vector to save in. * @return The solution x * @todo should reuse arrays */ solve(b, target) { if (target === void 0) { target = new Vec3(); } // Construct equations const nr = 3; // num rows const nc = 4; // num cols const eqns = []; let i; let j; for (i = 0; i < nr * nc; i++) { eqns.push(0); } for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { eqns[i + nc * j] = this.elements[i + 3 * j]; } } eqns[3 + 4 * 0] = b.x; eqns[3 + 4 * 1] = b.y; eqns[3 + 4 * 2] = b.z; // Compute right upper triangular version of the matrix - Gauss elimination let n = 3; const k = n; let np; const kp = 4; // num rows let p; do { i = k - n; if (eqns[i + nc * i] === 0) { // the pivot is null, swap lines for (j = i + 1; j < k; j++) { if (eqns[i + nc * j] !== 0) { np = kp; do { // do ligne( i ) = ligne( i ) + ligne( k ) p = kp - np; eqns[p + nc * i] += eqns[p + nc * j]; } while (--np); break; } } } if (eqns[i + nc * i] !== 0) { for (j = i + 1; j < k; j++) { const multiplier = eqns[i + nc * j] / eqns[i + nc * i]; np = kp; do { // do ligne( k ) = ligne( k ) - multiplier * ligne( i ) p = kp - np; eqns[p + nc * j] = p <= i ? 0 : eqns[p + nc * j] - eqns[p + nc * i] * multiplier; } while (--np); } } } while (--n); // Get the solution target.z = eqns[2 * nc + 3] / eqns[2 * nc + 2]; target.y = (eqns[1 * nc + 3] - eqns[1 * nc + 2] * target.z) / eqns[1 * nc + 1]; target.x = (eqns[0 * nc + 3] - eqns[0 * nc + 2] * target.z - eqns[0 * nc + 1] * target.y) / eqns[0 * nc + 0]; if (isNaN(target.x) || isNaN(target.y) || isNaN(target.z) || target.x === Infinity || target.y === Infinity || target.z === Infinity) { throw `Could not solve equation! Got x=[${target.toString()}], b=[${b.toString()}], A=[${this.toString()}]`; } return target; } /** * Get an element in the matrix by index. Index starts at 0, not 1!!! * @param value If provided, the matrix element will be set to this value. */ e(row, column, value) { if (value === undefined) { return this.elements[column + 3 * row]; } else { // Set value this.elements[column + 3 * row] = value; } } /** * Copy another matrix into this matrix object. */ copy(matrix) { for (let i = 0; i < matrix.elements.length; i++) { this.elements[i] = matrix.elements[i]; } return this; } /** * Returns a string representation of the matrix. */ toString() { let r = ''; const sep = ','; for (let i = 0; i < 9; i++) { r += this.elements[i] + sep; } return r; } /** * reverse the matrix * @param target Target matrix to save in. * @return The solution x */ reverse(target) { if (target === void 0) { target = new Mat3(); } // Construct equations const nr = 3; // num rows const nc = 6; // num cols const eqns = reverse_eqns; let i; let j; for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { eqns[i + nc * j] = this.elements[i + 3 * j]; } } eqns[3 + 6 * 0] = 1; eqns[3 + 6 * 1] = 0; eqns[3 + 6 * 2] = 0; eqns[4 + 6 * 0] = 0; eqns[4 + 6 * 1] = 1; eqns[4 + 6 * 2] = 0; eqns[5 + 6 * 0] = 0; eqns[5 + 6 * 1] = 0; eqns[5 + 6 * 2] = 1; // Compute right upper triangular version of the matrix - Gauss elimination let n = 3; const k = n; let np; const kp = nc; // num rows let p; do { i = k - n; if (eqns[i + nc * i] === 0) { // the pivot is null, swap lines for (j = i + 1; j < k; j++) { if (eqns[i + nc * j] !== 0) { np = kp; do { // do line( i ) = line( i ) + line( k ) p = kp - np; eqns[p + nc * i] += eqns[p + nc * j]; } while (--np); break; } } } if (eqns[i + nc * i] !== 0) { for (j = i + 1; j < k; j++) { const multiplier = eqns[i + nc * j] / eqns[i + nc * i]; np = kp; do { // do line( k ) = line( k ) - multiplier * line( i ) p = kp - np; eqns[p + nc * j] = p <= i ? 0 : eqns[p + nc * j] - eqns[p + nc * i] * multiplier; } while (--np); } } } while (--n); // eliminate the upper left triangle of the matrix i = 2; do { j = i - 1; do { const multiplier = eqns[i + nc * j] / eqns[i + nc * i]; np = nc; do { p = nc - np; eqns[p + nc * j] = eqns[p + nc * j] - eqns[p + nc * i] * multiplier; } while (--np); } while (j--); } while (--i); // operations on the diagonal i = 2; do { const multiplier = 1 / eqns[i + nc * i]; np = nc; do { p = nc - np; eqns[p + nc * i] = eqns[p + nc * i] * multiplier; } while (--np); } while (i--); i = 2; do { j = 2; do { p = eqns[nr + j + nc * i]; if (isNaN(p) || p === Infinity) { throw `Could not reverse! A=[${this.toString()}]`; } target.e(i, j, p); } while (j--); } while (i--); return target; } /** * Set the matrix from a quaterion */ setRotationFromQuaternion(q) { const x = q.x; const y = q.y; const z = q.z; const w = q.w; const x2 = x + x; const y2 = y + y; const z2 = z + z; const xx = x * x2; const xy = x * y2; const xz = x * z2; const yy = y * y2; const yz = y * z2; const zz = z * z2; const wx = w * x2; const wy = w * y2; const wz = w * z2; const e = this.elements; e[3 * 0 + 0] = 1 - (yy + zz); e[3 * 0 + 1] = xy - wz; e[3 * 0 + 2] = xz + wy; e[3 * 1 + 0] = xy + wz; e[3 * 1 + 1] = 1 - (xx + zz); e[3 * 1 + 2] = yz - wx; e[3 * 2 + 0] = xz - wy; e[3 * 2 + 1] = yz + wx; e[3 * 2 + 2] = 1 - (xx + yy); return this; } /** * Transpose the matrix * @param target Optional. Where to store the result. * @return The target Mat3, or a new Mat3 if target was omitted. */ transpose(target) { if (target === void 0) { target = new Mat3(); } const M = this.elements; const T = target.elements; let tmp; //Set diagonals T[0] = M[0]; T[4] = M[4]; T[8] = M[8]; tmp = M[1]; T[1] = M[3]; T[3] = tmp; tmp = M[2]; T[2] = M[6]; T[6] = tmp; tmp = M[5]; T[5] = M[7]; T[7] = tmp; return target; } } const reverse_eqns = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; /** * 3-dimensional vector * @example * const v = new Vec3(1, 2, 3) * console.log('x=' + v.x) // x=1 */ class Vec3 { constructor(x, y, z) { if (x === void 0) { x = 0.0; } if (y === void 0) { y = 0.0; } if (z === void 0) { z = 0.0; } this.x = x; this.y = y; this.z = z; } /** * Vector cross product * @param target Optional target to save in. */ cross(vector, target) { if (target === void 0) { target = new Vec3(); } const vx = vector.x; const vy = vector.y; const vz = vector.z; const x = this.x; const y = this.y; const z = this.z; target.x = y * vz - z * vy; target.y = z * vx - x * vz; target.z = x * vy - y * vx; return target; } /** * Set the vectors' 3 elements */ set(x, y, z) { this.x = x; this.y = y; this.z = z; return this; } /** * Set all components of the vector to zero. */ setZero() { this.x = this.y = this.z = 0; } /** * Vector addition */ vadd(vector, target) { if (target) { target.x = vector.x + this.x; target.y = vector.y + this.y; target.z = vector.z + this.z; } else { return new Vec3(this.x + vector.x, this.y + vector.y, this.z + vector.z); } } /** * Vector subtraction * @param target Optional target to save in. */ vsub(vector, target) { if (target) { target.x = this.x - vector.x; target.y = this.y - vector.y; target.z = this.z - vector.z; } else { return new Vec3(this.x - vector.x, this.y - vector.y, this.z - vector.z); } } /** * Get the cross product matrix a_cross from a vector, such that a x b = a_cross * b = c * * See {@link https://www8.cs.umu.se/kurser/TDBD24/VT06/lectures/Lecture6.pdf Umeå University Lecture} */ crossmat() { return new Mat3([0, -this.z, this.y, this.z, 0, -this.x, -this.y, this.x, 0]); } /** * Normalize the vector. Note that this changes the values in the vector. * @return Returns the norm of the vector */ normalize() { const x = this.x; const y = this.y; const z = this.z; const n = Math.sqrt(x * x + y * y + z * z); if (n > 0.0) { const invN = 1 / n; this.x *= invN; this.y *= invN; this.z *= invN; } else { // Make something up this.x = 0; this.y = 0; this.z = 0; } return n; } /** * Get the version of this vector that is of length 1. * @param target Optional target to save in * @return Returns the unit vector */ unit(target) { if (target === void 0) { target = new Vec3(); } const x = this.x; const y = this.y; const z = this.z; let ninv = Math.sqrt(x * x + y * y + z * z); if (ninv > 0.0) { ninv = 1.0 / ninv; target.x = x * ninv; target.y = y * ninv; target.z = z * ninv; } else { target.x = 1; target.y = 0; target.z = 0; } return target; } /** * Get the length of the vector */ length() { const x = this.x; const y = this.y; const z = this.z; return Math.sqrt(x * x + y * y + z * z); } /** * Get the squared length of the vector. */ lengthSquared() { return this.dot(this); } /** * Get distance from this point to another point */ distanceTo(p) { const x = this.x; const y = this.y; const z = this.z; const px = p.x; const py = p.y; const pz = p.z; return Math.sqrt((px - x) * (px - x) + (py - y) * (py - y) + (pz - z) * (pz - z)); } /** * Get squared distance from this point to another point */ distanceSquared(p) { const x = this.x; const y = this.y; const z = this.z; const px = p.x; const py = p.y; const pz = p.z; return (px - x) * (px - x) + (py - y) * (py - y) + (pz - z) * (pz - z); } /** * Multiply all the components of the vector with a scalar. * @param target The vector to save the result in. */ scale(scalar, target) { if (target === void 0) { target = new Vec3(); } const x = this.x; const y = this.y; const z = this.z; target.x = scalar * x; target.y = scalar * y; target.z = scalar * z; return target; } /** * Multiply the vector with an other vector, component-wise. * @param target The vector to save the result in. */ vmul(vector, target) { if (target === void 0) { target = new Vec3(); } target.x = vector.x * this.x; target.y = vector.y * this.y; target.z = vector.z * this.z; return target; } /** * Scale a vector and add it to this vector. Save the result in "target". (target = this + vector * scalar) * @param target The vector to save the result in. */ addScaledVector(scalar, vector, target) { if (target === void 0) { target = new Vec3(); } target.x = this.x + scalar * vector.x; target.y = this.y + scalar * vector.y; target.z = this.z + scalar * vector.z; return target; } /** * Calculate dot product * @param vector */ dot(vector) { return this.x * vector.x + this.y * vector.y + this.z * vector.z; } isZero() { return this.x === 0 && this.y === 0 && this.z === 0; } /** * Make the vector point in the opposite direction. * @param target Optional target to save in */ negate(target) { if (target === void 0) { target = new Vec3(); } target.x = -this.x; target.y = -this.y; target.z = -this.z; return target; } /** * Compute two artificial tangents to the vector * @param t1 Vector object to save the first tangent in * @param t2 Vector object to save the second tangent in */ tangents(t1, t2) { const norm = this.length(); if (norm > 0.0) { const n = Vec3_tangents_n; const inorm = 1 / norm; n.set(this.x * inorm, this.y * inorm, this.z * inorm); const randVec = Vec3_tangents_randVec; if (Math.abs(n.x) < 0.9) { randVec.set(1, 0, 0); n.cross(randVec, t1); } else { randVec.set(0, 1, 0); n.cross(randVec, t1); } n.cross(t1, t2); } else { // The normal length is zero, make something up t1.set(1, 0, 0); t2.set(0, 1, 0); } } /** * Converts to a more readable format */ toString() { return `${this.x},${this.y},${this.z}`; } /** * Converts to an array */ toArray() { return [this.x, this.y, this.z]; } /** * Copies value of source to this vector. */ copy(vector) { this.x = vector.x; this.y = vector.y; this.z = vector.z; return this; } /** * Do a linear interpolation between two vectors * @param t A number between 0 and 1. 0 will make this function return u, and 1 will make it return v. Numbers in between will generate a vector in between them. */ lerp(vector, t, target) { const x = this.x; const y = this.y; const z = this.z; target.x = x + (vector.x - x) * t; target.y = y + (vector.y - y) * t; target.z = z + (vector.z - z) * t; } /** * Check if a vector equals is almost equal to another one. */ almostEquals(vector, precision) { if (precision === void 0) { precision = 1e-6; } if (Math.abs(this.x - vector.x) > precision || Math.abs(this.y - vector.y) > precision || Math.abs(this.z - vector.z) > precision) { return false; } return true; } /** * Check if a vector is almost zero */ almostZero(precision) { if (precision === void 0) { precision = 1e-6; } if (Math.abs(this.x) > precision || Math.abs(this.y) > precision || Math.abs(this.z) > precision) { return false; } return true; } /** * Check if the vector is anti-parallel to another vector. * @param precision Set to zero for exact comparisons */ isAntiparallelTo(vector, precision) { this.negate(antip_neg); return antip_neg.almostEquals(vector, precision); } /** * Clone the vector */ clone() { return new Vec3(this.x, this.y, this.z); } } Vec3.ZERO = new Vec3(0, 0, 0); Vec3.UNIT_X = new Vec3(1, 0, 0); Vec3.UNIT_Y = new Vec3(0, 1, 0); Vec3.UNIT_Z = new Vec3(0, 0, 1); const Vec3_tangents_n = new Vec3(); const Vec3_tangents_randVec = new Vec3(); const antip_neg = new Vec3(); /** * Axis aligned bounding box class. */ class AABB { /** * The lower bound of the bounding box */ /** * The upper bound of the bounding box */ constructor(options) { if (options === void 0) { options = {}; } this.lowerBound = new Vec3(); this.upperBound = new Vec3(); if (options.lowerBound) { this.lowerBound.copy(options.lowerBound); } if (options.upperBound) { this.upperBound.copy(options.upperBound); } } /** * Set the AABB bounds from a set of points. * @param points An array of Vec3's. * @return The self object */ setFromPoints(points, position, quaternion, skinSize) { const l = this.lowerBound; const u = this.upperBound; const q = quaternion; // Set to the first point l.copy(points[0]); if (q) { q.vmult(l, l); } u.copy(l); for (let i = 1; i < points.length; i++) { let p = points[i]; if (q) { q.vmult(p, tmp$1); p = tmp$1; } if (p.x > u.x) { u.x = p.x; } if (p.x < l.x) { l.x = p.x; } if (p.y > u.y) { u.y = p.y; } if (p.y < l.y) { l.y = p.y; } if (p.z > u.z) { u.z = p.z; } if (p.z < l.z) { l.z = p.z; } } // Add offset if (position) { position.vadd(l, l); position.vadd(u, u); } if (skinSize) { l.x -= skinSize; l.y -= skinSize; l.z -= skinSize; u.x += skinSize; u.y += skinSize; u.z += skinSize; } return this; } /** * Copy bounds from an AABB to this AABB * @param aabb Source to copy from * @return The this object, for chainability */ copy(aabb) { this.lowerBound.copy(aabb.lowerBound); this.upperBound.copy(aabb.upperBound); return this; } /** * Clone an AABB */ clone() { return new AABB().copy(this); } /** * Extend this AABB so that it covers the given AABB too. */ extend(aabb) { this.lowerBound.x = Math.min(this.lowerBound.x, aabb.lowerBound.x); this.upperBound.x = Math.max(this.upperBound.x, aabb.upperBound.x); this.lowerBound.y = Math.min(this.lowerBound.y, aabb.lowerBound.y); this.upperBound.y = Math.max(this.upperBound.y, aabb.upperBound.y); this.lowerBound.z = Math.min(this.lowerBound.z, aabb.lowerBound.z); this.upperBound.z = Math.max(this.upperBound.z, aabb.upperBound.z); } /** * Returns true if the given AABB overlaps this AABB. */ overlaps(aabb) { const l1 = this.lowerBound; const u1 = this.upperBound; const l2 = aabb.lowerBound; const u2 = aabb.upperBound; // l2 u2 // |---------| // |--------| // l1 u1 const overlapsX = l2.x <= u1.x && u1.x <= u2.x || l1.x <= u2.x && u2.x <= u1.x; const overlapsY = l2.y <= u1.y && u1.y <= u2.y || l1.y <= u2.y && u2.y <= u1.y; const overlapsZ = l2.z <= u1.z && u1.z <= u2.z || l1.z <= u2.z && u2.z <= u1.z; return overlapsX && overlapsY && overlapsZ; } // Mostly for debugging volume() { const l = this.lowerBound; const u = this.upperBound; return (u.x - l.x) * (u.y - l.y) * (u.z - l.z); } /** * Returns true if the given AABB is fully contained in this AABB. */ contains(aabb) { const l1 = this.lowerBound; const u1 = this.upperBound; const l2 = aabb.lowerBound; const u2 = aabb.upperBound; // l2 u2 // |---------| // |---------------| // l1 u1 return l1.x <= l2.x && u1.x >= u2.x && l1.y <= l2.y && u1.y >= u2.y && l1.z <= l2.z && u1.z >= u2.z; } getCorners(a, b, c, d, e, f, g, h) { const l = this.lowerBound; const u = this.upperBound; a.copy(l); b.set(u.x, l.y, l.z); c.set(u.x, u.y, l.z); d.set(l.x, u.y, u.z); e.set(u.x, l.y, u.z); f.set(l.x, u.y, l.z); g.set(l.x, l.y, u.z); h.copy(u); } /** * Get the representation of an AABB in another frame. * @return The "target" AABB object. */ toLocalFrame(frame, target) { const corners = transformIntoFrame_corners; const a = corners[0]; const b = corners[1]; const c = corners[2]; const d = corners[3]; const e = corners[4]; const f = corners[5]; const g = corners[6]; const h = corners[7]; // Get corners in current frame this.getCorners(a, b, c, d, e, f, g, h); // Transform them to new local frame for (let i = 0; i !== 8; i++) { const corner = corners[i]; frame.pointToLocal(corner, corner); } return target.setFromPoints(corners); } /** * Get the representation of an AABB in the global frame. * @return The "target" AABB object. */ toWorldFrame(frame, target) { const corners = transformIntoFrame_corners; const a = corners[0]; const b = corners[1]; const c = corners[2]; const d = corners[3]; const e = corners[4]; const f = corners[5]; const g = corners[6]; const h = corners[7]; // Get corners in current frame this.getCorners(a, b, c, d, e, f, g, h); // Transform them to new local frame for (let i = 0; i !== 8; i++) { const corner = corners[i]; frame.pointToWorld(corner, corner); } return target.setFromPoints(corners); } /** * Check if the AABB is hit by a ray. */ overlapsRay(ray) { const { direction, from } = ray; // const t = 0 // ray.direction is unit direction vector of ray const dirFracX = 1 / direction.x; const dirFracY = 1 / direction.y; const dirFracZ = 1 / direction.z; // this.lowerBound is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner const t1 = (this.lowerBound.x - from.x) * dirFracX; const t2 = (this.upperBound.x - from.x) * dirFracX; const t3 = (this.lowerBound.y - from.y) * dirFracY; const t4 = (this.upperBound.y - from.y) * dirFracY; const t5 = (this.lowerBound.z - from.z) * dirFracZ; const t6 = (this.upperBound.z - from.z) * dirFracZ; // const tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4))); // const tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4))); const tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6)); const tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6)); // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us if (tmax < 0) { //t = tmax; return false; } // if tmin > tmax, ray doesn't intersect AABB if (tmin > tmax) { //t = tmax; return false; } return true; } } const tmp$1 = new Vec3(); const transformIntoFrame_corners = [new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3()]; /** * Collision "matrix". * It's actually a triangular-shaped array of whether two bodies are touching this step, for reference next step */ class ArrayCollisionMatrix { /** * The matrix storage. */ constructor() { this.matrix = []; } /** * Get an element */ get(bi, bj) { let { index: i } = bi; let { index: j } = bj; if (j > i) { const temp = j; j = i; i = temp; } return this.matrix[(i * (i + 1) >> 1) + j - 1]; } /** * Set an element */ set(bi, bj, value) { let { index: i } = bi; let { index: j } = bj; if (j > i) { const temp = j; j = i; i = temp; } this.matrix[(i * (i + 1) >> 1) + j - 1] = value ? 1 : 0; } /** * Sets all elements to zero */ reset() { for (let i = 0, l = this.matrix.length; i !== l; i++) { this.matrix[i] = 0; } } /** * Sets the max number of objects */ setNumObjects(n) { this.matrix.length = n * (n - 1) >> 1; } } /** * Base class for objects that dispatches events. */ class EventTarget { /** * Add an event listener * @return The self object, for chainability. */ addEventListener(type, listener) { if (this._listeners === undefined) { this._listeners = {}; } const listeners = this._listeners; if (listeners[type] === undefined) { listeners[type] = []; } if (!listeners[type].includes(listener)) { listeners[type].push(listener); } return this; } /** * Check if an event listener is added */ hasEventListener(type, listener) { if (this._listeners === undefined) { return false; } const listeners = this._listeners; if (listeners[type] !== undefined && listeners[type].includes(listener)) { return true; } return false; } /** * Check if any event listener of the given type is added */ hasAnyEventListener(type) { if (this._listeners === undefined) { return false; } const listeners = this._listeners; return listeners[type] !== undefined; } /** * Remove an event listener * @return The self object, for chainability. */ removeEventListener(type, listener) { if (this._listeners === undefined) { return this; } const listeners = this._listeners; if (listeners[type] === undefined) { return this; } const index = listeners[type].indexOf(listener); if (index !== -1) { listeners[type].splice(index, 1); } return this; } /** * Emit an event. * @return The self object, for chainability. */ dispatchEvent(event) { if (this._listeners === undefined) { return this; } const listeners = this._listeners; const listenerArray = listeners[event.type]; if (listenerArray !== undefined) { event.target = this; for (let i = 0, l = listenerArray.length; i < l; i++) { listenerArray[i].call(this, event); } } return this; } } /** * A Quaternion describes a rotation in 3D space. The Quaternion is mathematically defined as Q = x*i + y*j + z*k + w, where (i,j,k) are imaginary basis vectors. (x,y,z) can be seen as a vector related to the axis of rotation, while the real multiplier, w, is related to the amount of rotation. * @param x Multiplier of the imaginary basis vector i. * @param y Multiplier of the imaginary basis vector j. * @param z Multiplier of the imaginary basis vector k. * @param w Multiplier of the real part. * @see http://en.wikipedia.org/wiki/Quaternion */ class Quaternion { constructor(x, y, z, w) { if (x === void 0) { x = 0; } if (y === void 0) { y = 0; } if (z === void 0) { z = 0; } if (w === void 0) { w = 1; } this.x = x; this.y = y; this.z = z; this.w = w; } /** * Set the value of the quaternion. */ set(x, y, z, w) { this.x = x; this.y = y; this.z = z; this.w = w; return this; } /** * Convert to a readable format * @return "x,y,z,w" */ toString() { return `${this.x},${this.y},${this.z},${this.w}`; } /** * Convert to an Array * @return [x, y, z, w] */ toArray() { return [this.x, this.y, this.z, this.w]; } /** * Set the quaternion components given an axis and an angle in radians. */ setFromAxisAngle(vector, angle) { const s = Math.sin(angle * 0.5); this.x = vector.x * s; this.y = vector.y * s; this.z = vector.z * s; this.w = Math.cos(angle * 0.5); return this; } /** * Converts the quaternion to [ axis, angle ] representation. * @param targetAxis A vector object to reuse for storing the axis. * @return An array, first element is the axis and the second is the angle in radians. */ toAxisAngle(targetAxis) { if (targetAxis === void 0) { targetAxis = new Vec3(); } this.normalize(); // if w>1 acos and sqrt will produce errors, this cant happen if quaternion is normalised const angle = 2 * Math.acos(this.w); const s = Math.sqrt(1 - this.w * this.w); // assuming quaternion normalised then w is less than 1, so term always positive. if (s < 0.001) { // test to avoid divide by zero, s is always positive due to sqrt // if s close to zero then direction of axis not important targetAxis.x = this.x; // if it is important that axis is normalised then replace with x=1; y=z=0; targetAxis.y = this.y; targetAxis.z = this.z; } else { targetAxis.x = this.x / s; // normalise axis targetAxis.y = this.y / s; targetAxis.z = this.z / s; } return [targetAxis, angle]; } /** * Set the quaternion value given two vectors. The resulting rotation will be the needed rotation to rotate u to v. */ setFromVectors(u, v) { if (u.isAntiparallelTo(v)) { const t1 = sfv_t1; const t2 = sfv_t2; u.tangents(t1, t2); this.setFromAxisAngle(t1, Math.PI); } else { const a = u.cross(v); this.x = a.x; this.y = a.y; this.z = a.z; this.w = Math.sqrt(u.length() ** 2 * v.length() ** 2) + u.dot(v); this.normalize(); } return this; } /** * Multiply the quaternion with an other quaternion. */ mult(quat, target) { if (target === void 0) { target = new Quaternion(); } const ax = this.x; const ay = this.y; const az = this.z; const aw = this.w; const bx = quat.x; const by = quat.y; const bz = quat.z; const bw = quat.w; target.x = ax * bw + aw * bx + ay * bz - az * by; target.y = ay * bw + aw * by + az * bx - ax * bz; target.z = az * bw + aw * bz + ax * by - ay * bx; target.w = aw * bw - ax * bx - ay * by - az * bz; return target; } /** * Get the inverse quaternion rotation. */ inverse(target) { if (target === void 0) { target = new Quaternion(); } const x = this.x; const y = this.y; const z = this.z; const w = this.w; this.conjugate(target); const inorm2 = 1 / (x * x + y * y + z * z + w * w); target.x *= inorm2; target.y *= inorm2; target.z *= inorm2; target.w *= inorm2; return target; } /** * Get the quaternion conjugate */ conjugate(target) { if (target === void 0) { target = new Quaternion(); } target.x = -this.x; target.y = -this.y; target.z = -this.z; target.w = this.w; return target; } /** * Normalize the quaternion. Note that this changes the values of the quaternion. */ normalize() { let l = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); if (l === 0) { this.x = 0; this.y = 0; this.z = 0; this.w = 0; } else { l = 1 / l; this.x *= l; this.y *= l; this.z *= l; this.w *= l; } return this; } /** * Approximation of quaternion normalization. Works best when quat is already almost-normalized. * @author unphased, https://github.com/unphased */ normalizeFast() { const f = (3.0 - (this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w)) / 2.0; if (f === 0) { this.x = 0; this.y = 0; this.z = 0; this.w = 0; } else { this.x *= f; this.y *= f; this.z *= f; this.w *= f; } return this; } /** * Multiply the quaternion by a vector */ vmult(v, target) { if (target === void 0) { target = new Vec3(); } const x = v.x; const y = v.y; const z = v.z; const qx = this.x; const qy = this.y; const qz = this.z; const qw = this.w; // q*v const ix = qw * x + qy * z - qz * y; const iy = qw * y + qz * x - qx * z; const iz = qw * z + qx * y - qy * x; const iw = -qx * x - qy * y - qz * z; target.x = ix * qw + iw * -qx + iy * -qz - iz * -qy; target.y = iy * qw + iw * -qy + iz * -qx - ix * -qz; target.z = iz * qw + iw * -qz + ix * -qy - iy * -qx; return target; } /** * Copies value of source to this quaternion. * @return this */ copy(quat) { this.x = quat.x; this.y = quat.y; this.z = quat.z; this.w = quat.w; return this; } /** * Convert the quaternion to euler angle representation. Order: YZX, as this page describes: https://www.euclideanspace.com/maths/standards/index.htm * @param order Three-character string, defaults to "YZX" */ toEuler(target, order) { if (order === void 0) { order = 'YZX'; } let heading; let attitude; let bank; const x = this.x; const y = this.y; const z = this.z; const w = this.w; switch (order) { case 'YZX': const test = x * y + z * w; if (test > 0.499) { // singularity at north pole heading = 2 * Math.atan2(x, w); attitude = Math.PI / 2; bank = 0; } if (test < -0.499) { // singularity at south pole heading = -2 * Math.atan2(x, w); attitude = -Math.PI / 2; bank = 0; } if (heading === undefined) { const sqx = x * x; const sqy = y * y; const sqz = z * z; heading = Math.atan2(2 * y * w - 2 * x * z, 1 - 2 * sqy - 2 * sqz); // Heading attitude = Math.asin(2 * test); // attitude bank = Math.atan2(2 * x * w - 2 * y * z, 1 - 2 * sqx - 2 * sqz); // bank } break; default: throw new Error(`Euler order ${order} not supported yet.`); } target.y = heading; target.z = attitude; target.x = bank; } /** * Set the quaternion components given Euler angle representation. * * @param order The order to apply angles: 'XYZ' or 'YXZ' or any other combination. * * See {@link https://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors MathWorks} reference */ setFromEuler(x, y, z, order) { if (order === void 0) { order = 'XYZ'; } const c1 = Math.cos(x / 2); const c2 = Math.cos(y / 2); const c3 = Math.cos(z / 2); const s1 = Math.sin(x / 2); const s2 = Math.sin(y / 2); const s3 = Math.sin(z / 2); if (order === 'XYZ') { this.x = s1 * c2 * c3 + c1 * s2 * s3; this.y = c1 * s2 * c3 - s1 * c2 * s3; this.z = c1 * c2 * s3 + s1 * s2 * c3; this.w = c1 * c2 * c3 - s1 * s2 * s3; } else if (order === 'YXZ') { this.x = s1 * c2 * c3 + c1 * s2 * s3; this.y = c1 * s2 * c3 - s1 * c2 * s3; this.z = c1 * c2 * s3 - s1 * s2 * c3; this.w = c1 * c2 * c3 + s1 * s2 * s3; } else if (order === 'ZXY') { this.x = s1 * c2 * c3 - c1 * s2 * s3; this.y = c1 * s2 * c3 + s1 * c2 * s3; this.z = c1 * c2 * s3 + s1 * s2 * c3; this.w = c1 * c2 * c3 - s1 * s2 * s3; } else if (order === 'ZYX') { this.x = s1 * c2 * c3 - c1 * s2 * s3; this.y = c1 * s2 * c3 + s1 * c2 * s3; this.z = c1 * c2 * s3 - s1 * s2 * c3; this.w = c1 * c2 * c3 + s1 * s2 * s3; } else if (order === 'YZX') { this.x = s1 * c2 * c3 + c1 * s2 * s3; this.y = c1 * s2 * c3 + s1 * c2 * s3; this.z = c1 * c2 * s3 - s1 * s2 * c3; this.w = c1 * c2 * c3 - s1 * s2 * s3; } else if (order === 'XZY') { this.x = s1 * c2 * c3 - c1 * s2 * s3; this.y = c1 * s2 * c3 - s1 * c2 * s3; this.z = c1 * c2 * s3 + s1 * s2 * c3; this.w = c1 * c2 * c3 + s1 * s2 * s3; } return this; } clone() { return new Quaternion(this.x, this.y, this.z, this.w); } /** * Performs a spherical linear interpolation between two quat * * @param toQuat second operand * @param t interpolation amount between the self quaternion and toQuat * @param target A quaternion to store the result in. If not provided, a new one will be created. * @returns {Quaternion} The "target" object */ slerp(toQuat, t, target) { if (target === void 0) { target = new Quaternion(); } const ax = this.x; const ay = this.y; const az = this.z; const aw = this.w; let bx = toQuat.x; let by = toQuat.y; let bz = toQuat.z; let bw = toQuat.w; let omega; let cosom; let sinom; let scale0; let scale1; // calc cosine cosom = ax * bx + ay * by + az * bz + aw * bw; // adjust signs (if necessary) if (cosom < 0.0) { cosom = -cosom; bx = -bx; by = -by; bz = -bz; bw = -bw; } // calculate coefficients if (1.0 - cosom > 0.000001) { // standard case (slerp) omega = Math.acos(cosom); sinom = Math.sin(omega); scale0 = Math.sin((1.0 - t) * omega) / sinom; scale1 = Math.sin(t * omega) / sinom; } else { // "from" and "to" quaternions are very close // ... so we can do a linear interpolation scale0 = 1.0 - t; scale1 = t; } // calculate final values target.x = scale0 * ax + scale1 * bx; target.y = scale0 * ay + scale1 * by; target.z = scale0 * az + scale1 * bz; target.w = scale0 * aw + scale1 * bw; return target; } /** * Rotate an absolute orientation quaternion given an angular velocity and a time step. */ integrate(angularVelocity, dt, angularFactor, target) { if (target === void 0) { target = new Quaternion(); } const ax = angularVelocity.x * angularFactor.x, ay = angularVelocity.y * angularFactor.y, az = angularVelocity.z * angularFactor.z, bx = this.x, by = this.y, bz = this.z, bw = this.w; const half_dt = dt * 0.5; target.x += half_dt * (ax * bw + ay * bz - az * by); target.y += half_dt * (ay * bw + az * bx - ax * bz); target.z += half_dt * (az * bw + ax * by - ay * bx); target.w += half_dt * (-ax * bx - ay * by - az * bz); return target; } } const sfv_t1 = new Vec3(); const sfv_t2 = new Vec3(); /** * The available shape types. */ const SHAPE_TYPES = { /** SPHERE */ SPHERE: 1, /** PLANE */ PLANE: 2, /** BOX */ BOX: 4, /** COMPOUND */ COMPOUND: 8, /** CONVEXPOLYHEDRON */ CONVEXPOLYHEDRON: 16, /** HEIGHTFIELD */ HEIGHTFIELD: 32, /** PARTICLE */ PARTICLE: 64, /** CYLINDER */ CYLINDER: 128, /** TRIMESH */ TRIMESH: 256 }; /** * ShapeType */ /** * Base class for shapes */ class Shape { /** * Identifier of the Shape. */ /** * The type of this shape. Must be set to an int > 0 by subclasses. */ /** * The local bounding sphere radius of this shape. */ /** * Whether to produce contact forces when in contact with other bodies. Note that contacts will be generated, but they will be disabled. * @default true */ /** * @default 1 */ /** * @default -1 */ /** * Optional material of the shape that regulates contact properties. */ /** * The body to which the shape is added to. */ /** * All the Shape types. */ constructor(options) { if (options === void 0) { options = {}; } this.id = Shape.idCounter++; this.type = options.type || 0; this.boundingSphereRadius = 0; this.collisionResponse = options.collisionResponse ? options.collisionResponse : true; this.collisionFilterGroup = options.collisionFilterGroup !== undefined ? options.collisionFilterGroup : 1; this.collisionFilterMask = options.collisionFilterMask !== undefined ? options.collisionFilterMask : -1; this.material = options.material ? options.material : null; this.body = null; } /** * Computes the bounding sphere radius. * The result is stored in the property `.boundingSphereRadius` */ updateBoundingSphereRadius() { throw `computeBoundingSphereRadius() not implemented for shape type ${this.type}`; } /** * Get the volume of this shape */ volume() { throw `volume() not implemented for shape type ${this.type}`; } /** * Calculates the inertia in the local frame for this shape. * @see http://en.wikipedia.org/wiki/List_of_moments_of_inertia */ calculateLocalInertia(mass, target) { throw `calculateLocalInertia() not implemented for shape type ${this.type}`; } /** * @todo use abstract for these kind of methods */ calculateWorldAABB(pos, quat, min, max) { throw `calculateWorldAABB() not implemented for shape type ${this.type}`; } } Shape.idCounter = 0; Shape.types = SHAPE_TYPES; /** * Transformation utilities. */ class Transform { /** * position */ /** * quaternion */ constructor(options) { if (options === void 0) { options = {}; } this.position = new Vec3(); this.quaternion = new Quaternion(); if (options.position) { this.position.copy(options.position); } if (options.quaternion) { this.quaternion.copy(options.quaternion); } } /** * Get a global point in local transform coordinates. */ pointToLocal(worldPoint, result) { return Transform.pointToLocalFrame(this.position, this.quaternion, worldPoint, result); } /** * Get a local point in global transform coordinates. */ pointToWorld(localPoint, result) { return Transform.pointToWorldFrame(this.position, this.quaternion, localPoint, result); } /** * vectorToWorldFrame */ vectorToWorldFrame(localVector, result) { if (result === void 0) { result = new Vec3(); } this.quaternion.vmult(localVector, result); return result; } /** * pointToLocalFrame */ static pointToLocalFrame(position, quaternion, worldPoint, result) { if (result === void 0) { result = new Vec3(); } worldPoint.vsub(position, result); quaternion.conjugate(tmpQuat$1); tmpQuat$1.vmult(result, result); return result; } /** * pointToWorldFrame */ static pointToWorldFrame(position, quaternion, localPoint, result) { if (result === void 0) { result = new Vec3(); } quaternion.vmult(localPoint, result); result.vadd(position, result); return result; } /** * vectorToWorldFrame */ static vectorToWorldFrame(quaternion, localVector, result) { if (result === void 0) { result = new Vec3(); } quaternion.vmult(localVector, result); return result; } /** * vectorToLocalFrame */ static vectorToLocalFrame(position, quaternion, worldVector, result) { if (result === void 0) { result = new Vec3(); } quaternion.w *= -1; quaternion.vmult(worldVector, result); quaternion.w *= -1; return result; } } const tmpQuat$1 = new Quaternion(); /** * A set of polygons describing a convex shape. * * The shape MUST be convex for the code to work properly. No polygons may be coplanar (contained * in the same 3D plane), instead these should be merged into one polygon. * * @author qiao / https://github.com/qiao (original author, see https://github.com/qiao/three.js/commit/85026f0c769e4000148a67d45a9e9b9c5108836f) * @author schteppe / https://github.com/schteppe * @see https://www.altdevblogaday.com/2011/05/13/contact-generation-between-3d-convex-meshes/ * * @todo Move the clipping functions to ContactGenerator? * @todo Automatically merge coplanar polygons in constructor. * @example * const convexShape = new CANNON.ConvexPolyhedron({ vertices, faces }) * const convexBody = new CANNON.Body({ mass: 1, shape: convexShape }) * world.addBody(convexBody) */ class ConvexPolyhedron extends Shape { /** vertices */ /** * Array of integer arrays, indicating which vertices each face consists of */ /** faceNormals */ /** worldVertices */ /** worldVerticesNeedsUpdate */ /** worldFaceNormals */ /** worldFaceNormalsNeedsUpdate */ /** * If given, these locally defined, normalized axes are the only ones being checked when doing separating axis check. */ /** uniqueEdges */ /** * @param vertices An array of Vec3's * @param faces Array of integer arrays, describing which vertices that is included in each face. */ constructor(props) { if (props === void 0) { props = {}; } const { vertices = [], faces = [], normals = [], axes, boundingSphereRadius } = props; super({ type: Shape.types.CONVEXPOLYHEDRON }); this.vertices = vertices; this.faces = faces; this.faceNormals = normals; if (this.faceNormals.length === 0) { this.computeNormals(); } if (!boundingSphereRadius) { this.updateBoundingSphereRadius(); } else { this.boundingSphereRadius = boundingSphereRadius; } this.worldVertices = []; // World transformed version of .vertices this.worldVerticesNeedsUpdate = true; this.worldFaceNormals = []; // World transformed version of .faceNormals this.worldFaceNormalsNeedsUpdate = true; this.uniqueAxes = axes ? axes.slice() : null; this.uniqueEdges = []; this.computeEdges(); } /** * Computes uniqueEdges */ computeEdges() { const faces = this.faces; const vertices = this.vertices; const edges = this.uniqueEdges; edges.length = 0; const edge = new Vec3(); for (let i = 0; i !== faces.length; i++) { const face = faces[i]; const numVertices = face.length; for (let j = 0; j !== numVertices; j++) { const k = (j + 1) % numVertices; vertices[face[j]].vsub(vertices[face[k]], edge); edge.normalize(); let found = false; for (let p = 0; p !== edges.length; p++) { if (edges[p].almostEquals(edge) || edges[p].almostEquals(edge)) { found = true; break; } } if (!found) { edges.push(edge.clone()); } } } } /** * Compute the normals of the faces. * Will reuse existing Vec3 objects in the `faceNormals` array if they exist. */ computeNormals() { thi