class Mat3 { constructor(elements) { if (elements === void 0) { elements = [0, 0, 0, 0, 0, 0, 0, 0, 0]; } this.elements = elements; } 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; } 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; } setTrace(vector) { const e = this.elements; e[0] = vector.x; e[4] = vector.y; e[8] = vector.z; } 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; } 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; } smult(s) { for (let i = 0; i < this.elements.length; i++) { this.elements[i] *= s; } } 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(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(b2, target) { if (target === void 0) { target = new Vec3(); } const nr = 3; const nc = 4; 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] = b2.x; eqns[3 + 4 * 1] = b2.y; eqns[3 + 4 * 2] = b2.z; let n = 3; const k = n; let np; const kp = 4; let p; do { i = k - n; if (eqns[i + nc * i] === 0) { for (j = i + 1; j < k; j++) { if (eqns[i + nc * j] !== 0) { np = kp; do { 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 { p = kp - np; eqns[p + nc * j] = p <= i ? 0 : eqns[p + nc * j] - eqns[p + nc * i] * multiplier; } while (--np); } } } while (--n); 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=[${b2.toString()}], A=[${this.toString()}]`; } return target; } e(row, column, value) { if (value === void 0) { return this.elements[column + 3 * row]; } else { this.elements[column + 3 * row] = value; } } copy(matrix) { for (let i = 0; i < matrix.elements.length; i++) { this.elements[i] = matrix.elements[i]; } return this; } toString() { let r = ""; const sep = ","; for (let i = 0; i < 9; i++) { r += this.elements[i] + sep; } return r; } reverse(target) { if (target === void 0) { target = new Mat3(); } const nr = 3; const nc = 6; 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; let n = 3; const k = n; let np; const kp = nc; let p; do { i = k - n; if (eqns[i + nc * i] === 0) { for (j = i + 1; j < k; j++) { if (eqns[i + nc * j] !== 0) { np = kp; do { 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 { p = kp - np; eqns[p + nc * j] = p <= i ? 0 : eqns[p + nc * j] - eqns[p + nc * i] * multiplier; } while (--np); } } } while (--n); 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); 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; } 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(target) { if (target === void 0) { target = new Mat3(); } const M = this.elements; const T = target.elements; let tmp2; T[0] = M[0]; T[4] = M[4]; T[8] = M[8]; tmp2 = M[1]; T[1] = M[3]; T[3] = tmp2; tmp2 = M[2]; T[2] = M[6]; T[6] = tmp2; tmp2 = M[5]; T[5] = M[7]; T[7] = tmp2; return target; } } const reverse_eqns = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; class Vec3 { constructor(x, y, z) { if (x === void 0) { x = 0; } if (y === void 0) { y = 0; } if (z === void 0) { z = 0; } this.x = x; this.y = y; this.z = z; } 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(x, y, z) { this.x = x; this.y = y; this.z = z; return this; } setZero() { this.x = this.y = this.z = 0; } 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); } } 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); } } crossmat() { return new Mat3([0, -this.z, this.y, this.z, 0, -this.x, -this.y, this.x, 0]); } 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) { const invN = 1 / n; this.x *= invN; this.y *= invN; this.z *= invN; } else { this.x = 0; this.y = 0; this.z = 0; } return n; } 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) { ninv = 1 / 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; } length() { const x = this.x; const y = this.y; const z = this.z; return Math.sqrt(x * x + y * y + z * z); } lengthSquared() { return this.dot(this); } 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)); } 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); } 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; } 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; } 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; } 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; } negate(target) { if (target === void 0) { target = new Vec3(); } target.x = -this.x; target.y = -this.y; target.z = -this.z; return target; } tangents(t1, t2) { const norm = this.length(); if (norm > 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 { t1.set(1, 0, 0); t2.set(0, 1, 0); } } toString() { return `${this.x},${this.y},${this.z}`; } toArray() { return [this.x, this.y, this.z]; } copy(vector) { this.x = vector.x; this.y = vector.y; this.z = vector.z; return this; } 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; } 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; } 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; } isAntiparallelTo(vector, precision) { this.negate(antip_neg); return antip_neg.almostEquals(vector, precision); } 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(); class AABB { 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); } } setFromPoints(points, position, quaternion, skinSize) { const l = this.lowerBound; const u = this.upperBound; const q = quaternion; 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; } } 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(aabb) { this.lowerBound.copy(aabb.lowerBound); this.upperBound.copy(aabb.upperBound); return this; } clone() { return new AABB().copy(this); } 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); } overlaps(aabb) { const l1 = this.lowerBound; const u1 = this.upperBound; const l2 = aabb.lowerBound; const u2 = aabb.upperBound; 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; } volume() { const l = this.lowerBound; const u = this.upperBound; return (u.x - l.x) * (u.y - l.y) * (u.z - l.z); } contains(aabb) { const l1 = this.lowerBound; const u1 = this.upperBound; const l2 = aabb.lowerBound; const u2 = aabb.upperBound; 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(a2, b2, c2, d, e, f, g, h) { const l = this.lowerBound; const u = this.upperBound; a2.copy(l); b2.set(u.x, l.y, l.z); c2.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); } toLocalFrame(frame, target) { const corners = transformIntoFrame_corners; const a2 = corners[0]; const b2 = corners[1]; const c2 = corners[2]; const d = corners[3]; const e = corners[4]; const f = corners[5]; const g = corners[6]; const h = corners[7]; this.getCorners(a2, b2, c2, d, e, f, g, h); for (let i = 0; i !== 8; i++) { const corner = corners[i]; frame.pointToLocal(corner, corner); } return target.setFromPoints(corners); } toWorldFrame(frame, target) { const corners = transformIntoFrame_corners; const a2 = corners[0]; const b2 = corners[1]; const c2 = corners[2]; const d = corners[3]; const e = corners[4]; const f = corners[5]; const g = corners[6]; const h = corners[7]; this.getCorners(a2, b2, c2, d, e, f, g, h); for (let i = 0; i !== 8; i++) { const corner = corners[i]; frame.pointToWorld(corner, corner); } return target.setFromPoints(corners); } overlapsRay(ray) { const { direction, from } = ray; const dirFracX = 1 / direction.x; const dirFracY = 1 / direction.y; const dirFracZ = 1 / direction.z; 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)), 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) { return false; } if (tmin > 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()]; class ArrayCollisionMatrix { constructor() { this.matrix = []; } 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(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; } reset() { for (let i = 0, l = this.matrix.length; i !== l; i++) { this.matrix[i] = 0; } } setNumObjects(n) { this.matrix.length = n * (n - 1) >> 1; } } class EventTarget { addEventListener(type, listener) { if (this._listeners === void 0) { this._listeners = {}; } const listeners = this._listeners; if (listeners[type] === void 0) { listeners[type] = []; } if (!listeners[type].includes(listener)) { listeners[type].push(listener); } return this; } hasEventListener(type, listener) { if (this._listeners === void 0) { return false; } const listeners = this._listeners; if (listeners[type] !== void 0 && listeners[type].includes(listener)) { return true; } return false; } hasAnyEventListener(type) { if (this._listeners === void 0) { return false; } const listeners = this._listeners; return listeners[type] !== void 0; } removeEventListener(type, listener) { if (this._listeners === void 0) { return this; } const listeners = this._listeners; if (listeners[type] === void 0) { return this; } const index = listeners[type].indexOf(listener); if (index !== -1) { listeners[type].splice(index, 1); } return this; } dispatchEvent(event) { if (this._listeners === void 0) { return this; } const listeners = this._listeners; const listenerArray = listeners[event.type]; if (listenerArray !== void 0) { event.target = this; for (let i = 0, l = listenerArray.length; i < l; i++) { listenerArray[i].call(this, event); } } return this; } } 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(x, y, z, w) { this.x = x; this.y = y; this.z = z; this.w = w; return this; } toString() { return `${this.x},${this.y},${this.z},${this.w}`; } toArray() { return [this.x, this.y, this.z, this.w]; } 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; } toAxisAngle(targetAxis) { if (targetAxis === void 0) { targetAxis = new Vec3(); } this.normalize(); const angle = 2 * Math.acos(this.w); const s = Math.sqrt(1 - this.w * this.w); if (s < 1e-3) { targetAxis.x = this.x; targetAxis.y = this.y; targetAxis.z = this.z; } else { targetAxis.x = this.x / s; targetAxis.y = this.y / s; targetAxis.z = this.z / s; } return [targetAxis, angle]; } 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 a2 = u.cross(v); this.x = a2.x; this.y = a2.y; this.z = a2.z; this.w = Math.sqrt(u.length() ** 2 * v.length() ** 2) + u.dot(v); this.normalize(); } return this; } 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; } 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; } 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() { 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; } normalizeFast() { const f = (3 - (this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w)) / 2; 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; } 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; 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; } copy(quat) { this.x = quat.x; this.y = quat.y; this.z = quat.z; this.w = quat.w; return this; } 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) { heading = 2 * Math.atan2(x, w); attitude = Math.PI / 2; bank = 0; } if (test < -0.499) { heading = -2 * Math.atan2(x, w); attitude = -Math.PI / 2; bank = 0; } if (heading === void 0) { 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); attitude = Math.asin(2 * test); bank = Math.atan2(2 * x * w - 2 * y * z, 1 - 2 * sqx - 2 * sqz); } break; default: throw new Error(`Euler order ${order} not supported yet.`); } target.y = heading; target.z = attitude; target.x = bank; } 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); } 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; cosom = ax * bx + ay * by + az * bz + aw * bw; if (cosom < 0) { cosom = -cosom; bx = -bx; by = -by; bz = -bz; bw = -bw; } if (1 - cosom > 1e-6) { omega = Math.acos(cosom); sinom = Math.sin(omega); scale0 = Math.sin((1 - t) * omega) / sinom; scale1 = Math.sin(t * omega) / sinom; } else { scale0 = 1 - t; scale1 = t; } 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; } 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(); const SHAPE_TYPES = { SPHERE: 1, PLANE: 2, BOX: 4, COMPOUND: 8, CONVEXPOLYHEDRON: 16, HEIGHTFIELD: 32, PARTICLE: 64, CYLINDER: 128, TRIMESH: 256 }; class Shape { 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 !== void 0 ? options.collisionFilterGroup : 1; this.collisionFilterMask = options.collisionFilterMask !== void 0 ? options.collisionFilterMask : -1; this.material = options.material ? options.material : null; this.body = null; } updateBoundingSphereRadius() { throw `computeBoundingSphereRadius() not implemented for shape type ${this.type}`; } volume() { throw `volume() not implemented for shape type ${this.type}`; } calculateLocalInertia(mass, target) { throw `calculateLocalInertia() not implemented for shape type ${this.type}`; } calculateWorldAABB(pos, quat, min, max) { throw `calculateWorldAABB() not implemented for shape type ${this.type}`; } } Shape.idCounter = 0; Shape.types = SHAPE_TYPES; class Transform { 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); } } pointToLocal(worldPoint, result) { return Transform.pointToLocalFrame(this.position, this.quaternion, worldPoint, result); } pointToWorld(localPoint, result) { return Transform.pointToWorldFrame(this.position, this.quaternion, localPoint, result); } vectorToWorldFrame(localVector, result) { if (result === void 0) { result = new Vec3(); } this.quaternion.vmult(localVector, result); return result; } 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; } static pointToWorldFrame(position, quaternion, localPoint, result) { if (result === void 0) { result = new Vec3(); } quaternion.vmult(localPoint, result); result.vadd(position, result); return result; } static vectorToWorldFrame(quaternion, localVector, result) { if (result === void 0) { result = new Vec3(); } quaternion.vmult(localVector, result); return result; } 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(); class ConvexPolyhedron extends Shape { 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 = []; this.worldVerticesNeedsUpdate = true; this.worldFaceNormals = []; this.worldFaceNormalsNeedsUpdate = true; this.uniqueAxes = axes ? axes.slice() : null; this.uniqueEdges = []; this.computeEdges(); } 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()); } } } } computeNormals() { this.faceNormals.length = this.faces.length; for (let i = 0; i < this.faces.length; i++) { for (let j = 0; j < this.faces[i].length; j++) { if (!this.vertices[this.faces[i][j]]) { throw new Error(`Vertex ${this.faces[i][j]} not found!`); } } const n = this.faceNormals[i] || new Vec3(); this.getFaceNormal(i, n); n.negate(n); this.faceNormals[i] = n; const vertex = this.vertices[this.faces[i][0]]; if (n.dot(vertex) < 0) { console.error(`.faceNormals[${i}] = Vec3(${n.toString()}) looks like it points into the shape? The vertices follow. Make sure they are ordered CCW around the normal, using the right hand rule.`); for (let j = 0; j < this.faces[i].length; j++) { console.warn(`.vertices[${this.faces[i][j]}] = Vec3(${this.vertices[this.faces[i][j]].toString()})`); } } } } getFaceNormal(i, target) { const f = this.faces[i]; const va2 = this.vertices[f[0]]; const vb2 = this.vertices[f[1]]; const vc2 = this.vertices[f[2]]; ConvexPolyhedron.computeNormal(va2, vb2, vc2, target); } static computeNormal(va2, vb2, vc2, target) { const cb2 = new Vec3(); const ab2 = new Vec3(); vb2.vsub(va2, ab2); vc2.vsub(vb2, cb2); cb2.cross(ab2, target); if (!target.isZero()) { target.normalize(); } } clipAgainstHull(posA, quatA, hullB, posB, quatB, separatingNormal, minDist, maxDist, result) { const WorldNormal = new Vec3(); let closestFaceB = -1; let dmax = -Number.MAX_VALUE; for (let face = 0; face < hullB.faces.length; face++) { WorldNormal.copy(hullB.faceNormals[face]); quatB.vmult(WorldNormal, WorldNormal); const d = WorldNormal.dot(separatingNormal); if (d > dmax) { dmax = d; closestFaceB = face; } } const worldVertsB1 = []; for (let i = 0; i < hullB.faces[closestFaceB].length; i++) { const b2 = hullB.vertices[hullB.faces[closestFaceB][i]]; const worldb = new Vec3(); worldb.copy(b2); quatB.vmult(worldb, worldb); posB.vadd(worldb, worldb); worldVertsB1.push(worldb); } if (closestFaceB >= 0) { this.clipFaceAgainstHull(separatingNormal, posA, quatA, worldVertsB1, minDist, maxDist, result); } } findSeparatingAxis(hullB, posA, quatA, posB, quatB, target, faceListA, faceListB) { const faceANormalWS3 = new Vec3(); const Worldnormal1 = new Vec3(); const deltaC = new Vec3(); const worldEdge0 = new Vec3(); const worldEdge1 = new Vec3(); const Cross = new Vec3(); let dmin = Number.MAX_VALUE; const hullA = this; if (!hullA.uniqueAxes) { const numFacesA = faceListA ? faceListA.length : hullA.faces.length; for (let i = 0; i < numFacesA; i++) { const fi = faceListA ? faceListA[i] : i; faceANormalWS3.copy(hullA.faceNormals[fi]); quatA.vmult(faceANormalWS3, faceANormalWS3); const d = hullA.testSepAxis(faceANormalWS3, hullB, posA, quatA, posB, quatB); if (d === false) { return false; } if (d < dmin) { dmin = d; target.copy(faceANormalWS3); } } } else { for (let i = 0; i !== hullA.uniqueAxes.length; i++) { quatA.vmult(hullA.uniqueAxes[i], faceANormalWS3); const d = hullA.testSepAxis(faceANormalWS3, hullB, posA, quatA, posB, quatB); if (d === false) { return false; } if (d < dmin) { dmin = d; target.copy(faceANormalWS3); } } } if (!hullB.uniqueAxes) { const numFacesB = faceListB ? faceListB.length : hullB.faces.length; for (let i = 0; i < numFacesB; i++) { const fi = faceListB ? faceListB[i] : i; Worldnormal1.copy(hullB.faceNormals[fi]); quatB.vmult(Worldnormal1, Worldnormal1); const d = hullA.testSepAxis(Worldnormal1, hullB, posA, quatA, posB, quatB); if (d === false) { return false; } if (d < dmin) { dmin = d; target.copy(Worldnormal1); } } } else { for (let i = 0; i !== hullB.uniqueAxes.length; i++) { quatB.vmult(hullB.uniqueAxes[i], Worldnormal1); const d = hullA.testSepAxis(Worldnormal1, hullB, posA, quatA, posB, quatB); if (d === false) { return false; } if (d < dmin) { dmin = d; target.copy(Worldnormal1); } } } for (let e0 = 0; e0 !== hullA.uniqueEdges.length; e0++) { quatA.vmult(hullA.uniqueEdges[e0], worldEdge0); for (let e1 = 0; e1 !== hullB.uniqueEdges.length; e1++) { quatB.vmult(hullB.uniqueEdges[e1], worldEdge1); worldEdge0.cross(worldEdge1, Cross); if (!Cross.almostZero()) { Cross.normalize(); const dist = hullA.testSepAxis(Cross, hullB, posA, quatA, posB, quatB); if (dist === false) { return false; } if (dist < dmin) { dmin = dist; target.copy(Cross); } } } } posB.vsub(posA, deltaC); if (deltaC.dot(target) > 0) { target.negate(target); } return true; } testSepAxis(axis, hullB, posA, quatA, posB, quatB) { const hullA = this; ConvexPolyhedron.project(hullA, axis, posA, quatA, maxminA); ConvexPolyhedron.project(hullB, axis, posB, quatB, maxminB); const maxA = maxminA[0]; const minA = maxminA[1]; const maxB = maxminB[0]; const minB = maxminB[1]; if (maxA < minB || maxB < minA) { return false; } const d0 = maxA - minB; const d1 = maxB - minA; const depth = d0 < d1 ? d0 : d1; return depth; } calculateLocalInertia(mass, target) { const aabbmax = new Vec3(); const aabbmin = new Vec3(); this.computeLocalAABB(aabbmin, aabbmax); const x = aabbmax.x - aabbmin.x; const y = aabbmax.y - aabbmin.y; const z = aabbmax.z - aabbmin.z; target.x = 1 / 12 * mass * (2 * y * 2 * y + 2 * z * 2 * z); target.y = 1 / 12 * mass * (2 * x * 2 * x + 2 * z * 2 * z); target.z = 1 / 12 * mass * (2 * y * 2 * y + 2 * x * 2 * x); } getPlaneConstantOfFace(face_i) { const f = this.faces[face_i]; const n = this.faceNormals[face_i]; const v = this.vertices[f[0]]; const c2 = -n.dot(v); return c2; } clipFaceAgainstHull(separatingNormal, posA, quatA, worldVertsB1, minDist, maxDist, result) { const faceANormalWS = new Vec3(); const edge0 = new Vec3(); const WorldEdge0 = new Vec3(); const worldPlaneAnormal1 = new Vec3(); const planeNormalWS1 = new Vec3(); const worldA1 = new Vec3(); const localPlaneNormal = new Vec3(); const planeNormalWS = new Vec3(); const hullA = this; const worldVertsB2 = []; const pVtxIn = worldVertsB1; const pVtxOut = worldVertsB2; let closestFaceA = -1; let dmin = Number.MAX_VALUE; for (let face = 0; face < hullA.faces.length; face++) { faceANormalWS.copy(hullA.faceNormals[face]); quatA.vmult(faceANormalWS, faceANormalWS); const d = faceANormalWS.dot(separatingNormal); if (d < dmin) { dmin = d; closestFaceA = face; } } if (closestFaceA < 0) { return; } const polyA = hullA.faces[closestFaceA]; polyA.connectedFaces = []; for (let i = 0; i < hullA.faces.length; i++) { for (let j = 0; j < hullA.faces[i].length; j++) { if (polyA.indexOf(hullA.faces[i][j]) !== -1 && i !== closestFaceA && polyA.connectedFaces.indexOf(i) === -1) { polyA.connectedFaces.push(i); } } } const numVerticesA = polyA.length; for (let i = 0; i < numVerticesA; i++) { const a2 = hullA.vertices[polyA[i]]; const b2 = hullA.vertices[polyA[(i + 1) % numVerticesA]]; a2.vsub(b2, edge0); WorldEdge0.copy(edge0); quatA.vmult(WorldEdge0, WorldEdge0); posA.vadd(WorldEdge0, WorldEdge0); worldPlaneAnormal1.copy(this.faceNormals[closestFaceA]); quatA.vmult(worldPlaneAnormal1, worldPlaneAnormal1); posA.vadd(worldPlaneAnormal1, worldPlaneAnormal1); WorldEdge0.cross(worldPlaneAnormal1, planeNormalWS1); planeNormalWS1.negate(planeNormalWS1); worldA1.copy(a2); quatA.vmult(worldA1, worldA1); posA.vadd(worldA1, worldA1); const otherFace = polyA.connectedFaces[i]; localPlaneNormal.copy(this.faceNormals[otherFace]); const localPlaneEq2 = this.getPlaneConstantOfFace(otherFace); planeNormalWS.copy(localPlaneNormal); quatA.vmult(planeNormalWS, planeNormalWS); const planeEqWS2 = localPlaneEq2 - planeNormalWS.dot(posA); this.clipFaceAgainstPlane(pVtxIn, pVtxOut, planeNormalWS, planeEqWS2); while (pVtxIn.length) { pVtxIn.shift(); } while (pVtxOut.length) { pVtxIn.push(pVtxOut.shift()); } } localPlaneNormal.copy(this.faceNormals[closestFaceA]); const localPlaneEq = this.getPlaneConstantOfFace(closestFaceA); planeNormalWS.copy(localPlaneNormal); quatA.vmult(planeNormalWS, planeNormalWS); const planeEqWS = localPlaneEq - planeNormalWS.dot(posA); for (let i = 0; i < pVtxIn.length; i++) { let depth = planeNormalWS.dot(pVtxIn[i]) + planeEqWS; if (depth <= minDist) { console.log(`clamped: depth=${depth} to minDist=${minDist}`); depth = minDist; } if (depth <= maxDist) { const point = pVtxIn[i]; if (depth <= 1e-6) { const p = { point, normal: planeNormalWS, depth }; result.push(p); } } } } clipFaceAgainstPlane(inVertices, outVertices, planeNormal, planeConstant) { let n_dot_first; let n_dot_last; const numVerts = inVertices.length; if (numVerts < 2) { return outVertices; } let firstVertex = inVertices[inVertices.length - 1]; let lastVertex = inVertices[0]; n_dot_first = planeNormal.dot(firstVertex) + planeConstant; for (let vi = 0; vi < numVerts; vi++) { lastVertex = inVertices[vi]; n_dot_last = planeNormal.dot(lastVertex) + planeConstant; if (n_dot_first < 0) { if (n_dot_last < 0) { const newv = new Vec3(); newv.copy(lastVertex); outVertices.push(newv); } else { const newv = new Vec3(); firstVertex.lerp(lastVertex, n_dot_first / (n_dot_first - n_dot_last), newv); outVertices.push(newv); } } else { if (n_dot_last < 0) { const newv = new Vec3(); firstVertex.lerp(lastVertex, n_dot_first / (n_dot_first - n_dot_last), newv); outVertices.push(newv); outVertices.push(lastVertex); } } firstVertex = lastVertex; n_dot_first = n_dot_last; } return outVertices; } computeWorldVertices(position, quat) { while (this.worldVertices.length < this.vertices.length) { this.worldVertices.push(new Vec3()); } const verts = this.vertices; const worldVerts = this.worldVertices; for (let i = 0; i !== this.vertices.length; i++) { quat.vmult(verts[i], worldVerts[i]); position.vadd(worldVerts[i], worldVerts[i]); } this.worldVerticesNeedsUpdate = false; } computeLocalAABB(aabbmin, aabbmax) { const vertices = this.vertices; aabbmin.set(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); aabbmax.set(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE); for (let i = 0; i < this.vertices.length; i++) { const v = vertices[i]; if (v.x < aabbmin.x) { aabbmin.x = v.x; } else if (v.x > aabbmax.x) { aabbmax.x = v.x; } if (v.y < aabbmin.y) { aabbmin.y = v.y; } else if (v.y > aabbmax.y) { aabbmax.y = v.y; } if (v.z < aabbmin.z) { aabbmin.z = v.z; } else if (v.z > aabbmax.z) { aabbmax.z = v.z; } } } computeWorldFaceNormals(quat) { const N = this.faceNormals.length; while (this.worldFaceNormals.length < N) { this.worldFaceNormals.push(new Vec3()); } const normals = this.faceNormals; const worldNormals = this.worldFaceNormals; for (let i = 0; i !== N; i++) { quat.vmult(normals[i], worldNormals[i]); } this.worldFaceNormalsNeedsUpdate = false; } updateBoundingSphereRadius() { let max2 = 0; const verts = this.vertices; for (let i = 0; i !== verts.length; i++) { const norm2 = verts[i].lengthSquared(); if (norm2 > max2) { max2 = norm2; } } this.boundingSphereRadius = Math.sqrt(max2); } calculateWorldAABB(pos, quat, min, max) { const verts = this.vertices; let minx; let miny; let minz; let maxx; let maxy; let maxz; let tempWorldVertex = new Vec3(); for (let i = 0; i < verts.length; i++) { tempWorldVertex.copy(verts[i]); quat.vmult(tempWorldVertex, tempWorldVertex); pos.vadd(tempWorldVertex, tempWorldVertex); const v = tempWorldVertex; if (minx === void 0 || v.x < minx) { minx = v.x; } if (maxx === void 0 || v.x > maxx) { maxx = v.x; } if (miny === void 0 || v.y < miny) { miny = v.y; } if (maxy === void 0 || v.y > maxy) { maxy = v.y; } if (minz === void 0 || v.z < minz) { minz = v.z; } if (maxz === void 0 || v.z > maxz) { maxz = v.z; } } min.set(minx, miny, minz); max.set(maxx, maxy, maxz); } volume() { return 4 * Math.PI * this.boundingSphereRadius / 3; } getAveragePointLocal(target) { if (target === void 0) { target = new Vec3(); } const verts = this.vertices; for (let i = 0; i < verts.length; i++) { target.vadd(verts[i], target); } target.scale(1 / verts.length, target); return target; } transformAllPoints(offset, quat) { const n = this.vertices.length; const verts = this.vertices; if (quat) { for (let i = 0; i < n; i++) { const v = verts[i]; quat.vmult(v, v); } for (let i = 0; i < this.faceNormals.length; i++) { const v = this.faceNormals[i]; quat.vmult(v, v); } } if (offset) { for (let i = 0; i < n; i++) { const v = verts[i]; v.vadd(offset, v); } } } pointIsInside(p) { const verts = this.vertices; const faces = this.faces; const normals = this.faceNormals; const positiveResult = null; const pointInside = new Vec3(); this.getAveragePointLocal(pointInside); for (let i = 0; i < this.faces.length; i++) { let n = normals[i]; const v = verts[faces[i][0]]; const vToP = new Vec3(); p.vsub(v, vToP); const r1 = n.dot(vToP); const vToPointInside = new Vec3(); pointInside.vsub(v, vToPointInside); const r2 = n.dot(vToPointInside); if (r1 < 0 && r2 > 0 || r1 > 0 && r2 < 0) { return false; } } return positiveResult ? 1 : -1; } static project(shape, axis, pos, quat, result) { const n = shape.vertices.length; const localAxis = project_localAxis; let max = 0; let min = 0; const localOrigin = project_localOrigin; const vs = shape.vertices; localOrigin.setZero(); Transform.vectorToLocalFrame(pos, quat, axis, localAxis); Transform.pointToLocalFrame(pos, quat, localOrigin, localOrigin); const add = localOrigin.dot(localAxis); min = max = vs[0].dot(localAxis); for (let i = 1; i < n; i++) { const val = vs[i].dot(localAxis); if (val > max) { max = val; } if (val < min) { min = val; } } min -= add; max -= add; if (min > max) { const temp = min; min = max; max = temp; } result[0] = max; result[1] = min; } } const maxminA = []; const maxminB = []; new Vec3(); const project_localAxis = new Vec3(); const project_localOrigin = new Vec3(); class Box extends Shape { constructor(halfExtents) { super({ type: Shape.types.BOX }); this.halfExtents = halfExtents; this.convexPolyhedronRepresentation = null; this.updateConvexPolyhedronRepresentation(); this.updateBoundingSphereRadius(); } updateConvexPolyhedronRepresentation() { const sx = this.halfExtents.x; const sy = this.halfExtents.y; const sz = this.halfExtents.z; const V = Vec3; const vertices = [new V(-sx, -sy, -sz), new V(sx, -sy, -sz), new V(sx, sy, -sz), new V(-sx, sy, -sz), new V(-sx, -sy, sz), new V(sx, -sy, sz), new V(sx, sy, sz), new V(-sx, sy, sz)]; const faces = [ [3, 2, 1, 0], [4, 5, 6, 7], [5, 4, 0, 1], [2, 3, 7, 6], [0, 4, 7, 3], [1, 2, 6, 5] ]; const axes = [new V(0, 0, 1), new V(0, 1, 0), new V(1, 0, 0)]; const h = new ConvexPolyhedron({ vertices, faces, axes }); this.convexPolyhedronRepresentation = h; h.material = this.material; } calculateLocalInertia(mass, target) { if (target === void 0) { target = new Vec3(); } Box.calculateInertia(this.halfExtents, mass, target); return target; } static calculateInertia(halfExtents, mass, target) { const e = halfExtents; target.x = 1 / 12 * mass * (2 * e.y * 2 * e.y + 2 * e.z * 2 * e.z); target.y = 1 / 12 * mass * (2 * e.x * 2 * e.x + 2 * e.z * 2 * e.z); target.z = 1 / 12 * mass * (2 * e.y * 2 * e.y + 2 * e.x * 2 * e.x); } getSideNormals(sixTargetVectors, quat) { const sides = sixTargetVectors; const ex = this.halfExtents; sides[0].set(ex.x, 0, 0); sides[1].set(0, ex.y, 0); sides[2].set(0, 0, ex.z); sides[3].set(-ex.x, 0, 0); sides[4].set(0, -ex.y, 0); sides[5].set(0, 0, -ex.z); if (quat !== void 0) { for (let i = 0; i !== sides.length; i++) { quat.vmult(sides[i], sides[i]); } } return sides; } volume() { return 8 * this.halfExtents.x * this.halfExtents.y * this.halfExtents.z; } updateBoundingSphereRadius() { this.boundingSphereRadius = this.halfExtents.length(); } forEachWorldCorner(pos, quat, callback) { const e = this.halfExtents; const corners = [[e.x, e.y, e.z], [-e.x, e.y, e.z], [-e.x, -e.y, e.z], [-e.x, -e.y, -e.z], [e.x, -e.y, -e.z], [e.x, e.y, -e.z], [-e.x, e.y, -e.z], [e.x, -e.y, e.z]]; for (let i = 0; i < corners.length; i++) { worldCornerTempPos.set(corners[i][0], corners[i][1], corners[i][2]); quat.vmult(worldCornerTempPos, worldCornerTempPos); pos.vadd(worldCornerTempPos, worldCornerTempPos); callback(worldCornerTempPos.x, worldCornerTempPos.y, worldCornerTempPos.z); } } calculateWorldAABB(pos, quat, min, max) { const e = this.halfExtents; worldCornersTemp[0].set(e.x, e.y, e.z); worldCornersTemp[1].set(-e.x, e.y, e.z); worldCornersTemp[2].set(-e.x, -e.y, e.z); worldCornersTemp[3].set(-e.x, -e.y, -e.z); worldCornersTemp[4].set(e.x, -e.y, -e.z); worldCornersTemp[5].set(e.x, e.y, -e.z); worldCornersTemp[6].set(-e.x, e.y, -e.z); worldCornersTemp[7].set(e.x, -e.y, e.z); const wc = worldCornersTemp[0]; quat.vmult(wc, wc); pos.vadd(wc, wc); max.copy(wc); min.copy(wc); for (let i = 1; i < 8; i++) { const wc2 = worldCornersTemp[i]; quat.vmult(wc2, wc2); pos.vadd(wc2, wc2); const x = wc2.x; const y = wc2.y; const z = wc2.z; if (x > max.x) { max.x = x; } if (y > max.y) { max.y = y; } if (z > max.z) { max.z = z; } if (x < min.x) { min.x = x; } if (y < min.y) { min.y = y; } if (z < min.z) { min.z = z; } } } } const worldCornerTempPos = new Vec3(); const worldCornersTemp = [new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3()]; const BODY_TYPES = { DYNAMIC: 1, STATIC: 2, KINEMATIC: 4 }; const BODY_SLEEP_STATES = { AWAKE: 0, SLEEPY: 1, SLEEPING: 2 }; class Body extends EventTarget { constructor(options) { if (options === void 0) { options = {}; } super(); this.id = Body.idCounter++; this.index = -1; this.world = null; this.vlambda = new Vec3(); this.collisionFilterGroup = typeof options.collisionFilterGroup === "number" ? options.collisionFilterGroup : 1; this.collisionFilterMask = typeof options.collisionFilterMask === "number" ? options.collisionFilterMask : -1; this.collisionResponse = typeof options.collisionResponse === "boolean" ? options.collisionResponse : true; this.position = new Vec3(); this.previousPosition = new Vec3(); this.interpolatedPosition = new Vec3(); this.initPosition = new Vec3(); if (options.position) { this.position.copy(options.position); this.previousPosition.copy(options.position); this.interpolatedPosition.copy(options.position); this.initPosition.copy(options.position); } this.velocity = new Vec3(); if (options.velocity) { this.velocity.copy(options.velocity); } this.initVelocity = new Vec3(); this.force = new Vec3(); const mass = typeof options.mass === "number" ? options.mass : 0; this.mass = mass; this.invMass = mass > 0 ? 1 / mass : 0; this.material = options.material || null; this.linearDamping = typeof options.linearDamping === "number" ? options.linearDamping : 0.01; this.type = mass <= 0 ? Body.STATIC : Body.DYNAMIC; if (typeof options.type === typeof Body.STATIC) { this.type = options.type; } this.allowSleep = typeof options.allowSleep !== "undefined" ? options.allowSleep : true; this.sleepState = Body.AWAKE; this.sleepSpeedLimit = typeof options.sleepSpeedLimit !== "undefined" ? options.sleepSpeedLimit : 0.1; this.sleepTimeLimit = typeof options.sleepTimeLimit !== "undefined" ? options.sleepTimeLimit : 1; this.timeLastSleepy = 0; this.wakeUpAfterNarrowphase = false; this.torque = new Vec3(); this.quaternion = new Quaternion(); this.initQuaternion = new Quaternion(); this.previousQuaternion = new Quaternion(); this.interpolatedQuaternion = new Quaternion(); if (options.quaternion) { this.quaternion.copy(options.quaternion); this.initQuaternion.copy(options.quaternion); this.previousQuaternion.copy(options.quaternion); this.interpolatedQuaternion.copy(options.quaternion); } this.angularVelocity = new Vec3(); if (options.angularVelocity) { this.angularVelocity.copy(options.angularVelocity); } this.initAngularVelocity = new Vec3(); this.shapes = []; this.shapeOffsets = []; this.shapeOrientations = []; this.inertia = new Vec3(); this.invInertia = new Vec3(); this.invInertiaWorld = new Mat3(); this.invMassSolve = 0; this.invInertiaSolve = new Vec3(); this.invInertiaWorldSolve = new Mat3(); this.fixedRotation = typeof options.fixedRotation !== "undefined" ? options.fixedRotation : false; this.angularDamping = typeof options.angularDamping !== "undefined" ? options.angularDamping : 0.01; this.linearFactor = new Vec3(1, 1, 1); if (options.linearFactor) { this.linearFactor.copy(options.linearFactor); } this.angularFactor = new Vec3(1, 1, 1); if (options.angularFactor) { this.angularFactor.copy(options.angularFactor); } this.aabb = new AABB(); this.aabbNeedsUpdate = true; this.boundingRadius = 0; this.wlambda = new Vec3(); this.isTrigger = Boolean(options.isTrigger); if (options.shape) { this.addShape(options.shape); } this.updateMassProperties(); } wakeUp() { const prevState = this.sleepState; this.sleepState = Body.AWAKE; this.wakeUpAfterNarrowphase = false; if (prevState === Body.SLEEPING) { this.dispatchEvent(Body.wakeupEvent); } } sleep() { this.sleepState = Body.SLEEPING; this.velocity.set(0, 0, 0); this.angularVelocity.set(0, 0, 0); this.wakeUpAfterNarrowphase = false; } sleepTick(time) { if (this.allowSleep) { const sleepState = this.sleepState; const speedSquared = this.velocity.lengthSquared() + this.angularVelocity.lengthSquared(); const speedLimitSquared = this.sleepSpeedLimit ** 2; if (sleepState === Body.AWAKE && speedSquared < speedLimitSquared) { this.sleepState = Body.SLEEPY; this.timeLastSleepy = time; this.dispatchEvent(Body.sleepyEvent); } else if (sleepState === Body.SLEEPY && speedSquared > speedLimitSquared) { this.wakeUp(); } else if (sleepState === Body.SLEEPY && time - this.timeLastSleepy > this.sleepTimeLimit) { this.sleep(); this.dispatchEvent(Body.sleepEvent); } } } updateSolveMassProperties() { if (this.sleepState === Body.SLEEPING || this.type === Body.KINEMATIC) { this.invMassSolve = 0; this.invInertiaSolve.setZero(); this.invInertiaWorldSolve.setZero(); } else { this.invMassSolve = this.invMass; this.invInertiaSolve.copy(this.invInertia); this.invInertiaWorldSolve.copy(this.invInertiaWorld); } } pointToLocalFrame(worldPoint, result) { if (result === void 0) { result = new Vec3(); } worldPoint.vsub(this.position, result); this.quaternion.conjugate().vmult(result, result); return result; } vectorToLocalFrame(worldVector, result) { if (result === void 0) { result = new Vec3(); } this.quaternion.conjugate().vmult(worldVector, result); return result; } pointToWorldFrame(localPoint, result) { if (result === void 0) { result = new Vec3(); } this.quaternion.vmult(localPoint, result); result.vadd(this.position, result); return result; } vectorToWorldFrame(localVector, result) { if (result === void 0) { result = new Vec3(); } this.quaternion.vmult(localVector, result); return result; } addShape(shape, _offset, _orientation) { const offset = new Vec3(); const orientation = new Quaternion(); if (_offset) { offset.copy(_offset); } if (_orientation) { orientation.copy(_orientation); } this.shapes.push(shape); this.shapeOffsets.push(offset); this.shapeOrientations.push(orientation); this.updateMassProperties(); this.updateBoundingRadius(); this.aabbNeedsUpdate = true; shape.body = this; return this; } removeShape(shape) { const index = this.shapes.indexOf(shape); if (index === -1) { console.warn("Shape does not belong to the body"); return this; } this.shapes.splice(index, 1); this.shapeOffsets.splice(index, 1); this.shapeOrientations.splice(index, 1); this.updateMassProperties(); this.updateBoundingRadius(); this.aabbNeedsUpdate = true; shape.body = null; return this; } updateBoundingRadius() { const shapes = this.shapes; const shapeOffsets = this.shapeOffsets; const N = shapes.length; let radius = 0; for (let i = 0; i !== N; i++) { const shape = shapes[i]; shape.updateBoundingSphereRadius(); const offset = shapeOffsets[i].length(); const r = shape.boundingSphereRadius; if (offset + r > radius) { radius = offset + r; } } this.boundingRadius = radius; } updateAABB() { const shapes = this.shapes; const shapeOffsets = this.shapeOffsets; const shapeOrientations = this.shapeOrientations; const N = shapes.length; const offset = tmpVec; const orientation = tmpQuat; const bodyQuat = this.quaternion; const aabb = this.aabb; const shapeAABB = updateAABB_shapeAABB; for (let i = 0; i !== N; i++) { const shape = shapes[i]; bodyQuat.vmult(shapeOffsets[i], offset); offset.vadd(this.position, offset); bodyQuat.mult(shapeOrientations[i], orientation); shape.calculateWorldAABB(offset, orientation, shapeAABB.lowerBound, shapeAABB.upperBound); if (i === 0) { aabb.copy(shapeAABB); } else { aabb.extend(shapeAABB); } } this.aabbNeedsUpdate = false; } updateInertiaWorld(force) { const I = this.invInertia; if (I.x === I.y && I.y === I.z && !force) ; else { const m1 = uiw_m1; const m2 = uiw_m2; uiw_m3; m1.setRotationFromQuaternion(this.quaternion); m1.transpose(m2); m1.scale(I, m1); m1.mmult(m2, this.invInertiaWorld); } } applyForce(force, relativePoint) { if (relativePoint === void 0) { relativePoint = new Vec3(); } if (this.type !== Body.DYNAMIC) { return; } if (this.sleepState === Body.SLEEPING) { this.wakeUp(); } const rotForce = Body_applyForce_rotForce; relativePoint.cross(force, rotForce); this.force.vadd(force, this.force); this.torque.vadd(rotForce, this.torque); } applyLocalForce(localForce, localPoint) { if (localPoint === void 0) { localPoint = new Vec3(); } if (this.type !== Body.DYNAMIC) { return; } const worldForce = Body_applyLocalForce_worldForce; const relativePointWorld = Body_applyLocalForce_relativePointWorld; this.vectorToWorldFrame(localForce, worldForce); this.vectorToWorldFrame(localPoint, relativePointWorld); this.applyForce(worldForce, relativePointWorld); } applyTorque(torque2) { if (this.type !== Body.DYNAMIC) { return; } if (this.sleepState === Body.SLEEPING) { this.wakeUp(); } this.torque.vadd(torque2, this.torque); } applyImpulse(impulse, relativePoint) { if (relativePoint === void 0) { relativePoint = new Vec3(); } if (this.type !== Body.DYNAMIC) { return; } if (this.sleepState === Body.SLEEPING) { this.wakeUp(); } const r = relativePoint; const velo = Body_applyImpulse_velo; velo.copy(impulse); velo.scale(this.invMass, velo); this.velocity.vadd(velo, this.velocity); const rotVelo = Body_applyImpulse_rotVelo; r.cross(impulse, rotVelo); this.invInertiaWorld.vmult(rotVelo, rotVelo); this.angularVelocity.vadd(rotVelo, this.angularVelocity); } applyLocalImpulse(localImpulse, localPoint) { if (localPoint === void 0) { localPoint = new Vec3(); } if (this.type !== Body.DYNAMIC) { return; } const worldImpulse = Body_applyLocalImpulse_worldImpulse; const relativePointWorld = Body_applyLocalImpulse_relativePoint; this.vectorToWorldFrame(localImpulse, worldImpulse); this.vectorToWorldFrame(localPoint, relativePointWorld); this.applyImpulse(worldImpulse, relativePointWorld); } updateMassProperties() { const halfExtents = Body_updateMassProperties_halfExtents; this.invMass = this.mass > 0 ? 1 / this.mass : 0; const I = this.inertia; const fixed = this.fixedRotation; this.updateAABB(); halfExtents.set((this.aabb.upperBound.x - this.aabb.lowerBound.x) / 2, (this.aabb.upperBound.y - this.aabb.lowerBound.y) / 2, (this.aabb.upperBound.z - this.aabb.lowerBound.z) / 2); Box.calculateInertia(halfExtents, this.mass, I); this.invInertia.set(I.x > 0 && !fixed ? 1 / I.x : 0, I.y > 0 && !fixed ? 1 / I.y : 0, I.z > 0 && !fixed ? 1 / I.z : 0); this.updateInertiaWorld(true); } getVelocityAtWorldPoint(worldPoint, result) { const r = new Vec3(); worldPoint.vsub(this.position, r); this.angularVelocity.cross(r, result); this.velocity.vadd(result, result); return result; } integrate(dt, quatNormalize, quatNormalizeFast) { this.previousPosition.copy(this.position); this.previousQuaternion.copy(this.quaternion); if (!(this.type === Body.DYNAMIC || this.type === Body.KINEMATIC) || this.sleepState === Body.SLEEPING) { return; } const velo = this.velocity; const angularVelo = this.angularVelocity; const pos = this.position; const force = this.force; const torque2 = this.torque; const quat = this.quaternion; const invMass = this.invMass; const invInertia = this.invInertiaWorld; const linearFactor = this.linearFactor; const iMdt = invMass * dt; velo.x += force.x * iMdt * linearFactor.x; velo.y += force.y * iMdt * linearFactor.y; velo.z += force.z * iMdt * linearFactor.z; const e = invInertia.elements; const angularFactor = this.angularFactor; const tx = torque2.x * angularFactor.x; const ty = torque2.y * angularFactor.y; const tz = torque2.z * angularFactor.z; angularVelo.x += dt * (e[0] * tx + e[1] * ty + e[2] * tz); angularVelo.y += dt * (e[3] * tx + e[4] * ty + e[5] * tz); angularVelo.z += dt * (e[6] * tx + e[7] * ty + e[8] * tz); pos.x += velo.x * dt; pos.y += velo.y * dt; pos.z += velo.z * dt; quat.integrate(this.angularVelocity, dt, this.angularFactor, quat); if (quatNormalize) { if (quatNormalizeFast) { quat.normalizeFast(); } else { quat.normalize(); } } this.aabbNeedsUpdate = true; this.updateInertiaWorld(); } } Body.idCounter = 0; Body.COLLIDE_EVENT_NAME = "collide"; Body.DYNAMIC = BODY_TYPES.DYNAMIC; Body.STATIC = BODY_TYPES.STATIC; Body.KINEMATIC = BODY_TYPES.KINEMATIC; Body.AWAKE = BODY_SLEEP_STATES.AWAKE; Body.SLEEPY = BODY_SLEEP_STATES.SLEEPY; Body.SLEEPING = BODY_SLEEP_STATES.SLEEPING; Body.wakeupEvent = { type: "wakeup" }; Body.sleepyEvent = { type: "sleepy" }; Body.sleepEvent = { type: "sleep" }; const tmpVec = new Vec3(); const tmpQuat = new Quaternion(); const updateAABB_shapeAABB = new AABB(); const uiw_m1 = new Mat3(); const uiw_m2 = new Mat3(); const uiw_m3 = new Mat3(); const Body_applyForce_rotForce = new Vec3(); const Body_applyLocalForce_worldForce = new Vec3(); const Body_applyLocalForce_relativePointWorld = new Vec3(); const Body_applyImpulse_velo = new Vec3(); const Body_applyImpulse_rotVelo = new Vec3(); const Body_applyLocalImpulse_worldImpulse = new Vec3(); const Body_applyLocalImpulse_relativePoint = new Vec3(); const Body_updateMassProperties_halfExtents = new Vec3(); class Broadphase { constructor() { this.world = null; this.useBoundingBoxes = false; this.dirty = true; } collisionPairs(world, p1, p2) { throw new Error("collisionPairs not implemented for this BroadPhase class!"); } needBroadphaseCollision(bodyA, bodyB) { if ((bodyA.collisionFilterGroup & bodyB.collisionFilterMask) === 0 || (bodyB.collisionFilterGroup & bodyA.collisionFilterMask) === 0) { return false; } if (((bodyA.type & Body.STATIC) !== 0 || bodyA.sleepState === Body.SLEEPING) && ((bodyB.type & Body.STATIC) !== 0 || bodyB.sleepState === Body.SLEEPING)) { return false; } return true; } intersectionTest(bodyA, bodyB, pairs1, pairs2) { if (this.useBoundingBoxes) { this.doBoundingBoxBroadphase(bodyA, bodyB, pairs1, pairs2); } else { this.doBoundingSphereBroadphase(bodyA, bodyB, pairs1, pairs2); } } doBoundingSphereBroadphase(bodyA, bodyB, pairs1, pairs2) { const r = Broadphase_collisionPairs_r; bodyB.position.vsub(bodyA.position, r); const boundingRadiusSum2 = (bodyA.boundingRadius + bodyB.boundingRadius) ** 2; const norm2 = r.lengthSquared(); if (norm2 < boundingRadiusSum2) { pairs1.push(bodyA); pairs2.push(bodyB); } } doBoundingBoxBroadphase(bodyA, bodyB, pairs1, pairs2) { if (bodyA.aabbNeedsUpdate) { bodyA.updateAABB(); } if (bodyB.aabbNeedsUpdate) { bodyB.updateAABB(); } if (bodyA.aabb.overlaps(bodyB.aabb)) { pairs1.push(bodyA); pairs2.push(bodyB); } } makePairsUnique(pairs1, pairs2) { const t = Broadphase_makePairsUnique_temp; const p1 = Broadphase_makePairsUnique_p1; const p2 = Broadphase_makePairsUnique_p2; const N = pairs1.length; for (let i = 0; i !== N; i++) { p1[i] = pairs1[i]; p2[i] = pairs2[i]; } pairs1.length = 0; pairs2.length = 0; for (let i = 0; i !== N; i++) { const id1 = p1[i].id; const id2 = p2[i].id; const key = id1 < id2 ? `${id1},${id2}` : `${id2},${id1}`; t[key] = i; t.keys.push(key); } for (let i = 0; i !== t.keys.length; i++) { const key = t.keys.pop(); const pairIndex = t[key]; pairs1.push(p1[pairIndex]); pairs2.push(p2[pairIndex]); delete t[key]; } } setWorld(world) { } static boundingSphereCheck(bodyA, bodyB) { const dist = new Vec3(); bodyA.position.vsub(bodyB.position, dist); const sa = bodyA.shapes[0]; const sb = bodyB.shapes[0]; return Math.pow(sa.boundingSphereRadius + sb.boundingSphereRadius, 2) > dist.lengthSquared(); } aabbQuery(world, aabb, result) { console.warn(".aabbQuery is not implemented in this Broadphase subclass."); return []; } } const Broadphase_collisionPairs_r = new Vec3(); new Vec3(); new Quaternion(); new Vec3(); const Broadphase_makePairsUnique_temp = { keys: [] }; const Broadphase_makePairsUnique_p1 = []; const Broadphase_makePairsUnique_p2 = []; new Vec3(); new Vec3(); new Vec3(); class NaiveBroadphase extends Broadphase { constructor() { super(); } collisionPairs(world, pairs1, pairs2) { const bodies = world.bodies; const n = bodies.length; let bi; let bj; for (let i = 0; i !== n; i++) { for (let j = 0; j !== i; j++) { bi = bodies[i]; bj = bodies[j]; if (!this.needBroadphaseCollision(bi, bj)) { continue; } this.intersectionTest(bi, bj, pairs1, pairs2); } } } aabbQuery(world, aabb, result) { if (result === void 0) { result = []; } for (let i = 0; i < world.bodies.length; i++) { const b2 = world.bodies[i]; if (b2.aabbNeedsUpdate) { b2.updateAABB(); } if (b2.aabb.overlaps(aabb)) { result.push(b2); } } return result; } } class RaycastResult { constructor() { this.rayFromWorld = new Vec3(); this.rayToWorld = new Vec3(); this.hitNormalWorld = new Vec3(); this.hitPointWorld = new Vec3(); this.hasHit = false; this.shape = null; this.body = null; this.hitFaceIndex = -1; this.distance = -1; this.shouldStop = false; } reset() { this.rayFromWorld.setZero(); this.rayToWorld.setZero(); this.hitNormalWorld.setZero(); this.hitPointWorld.setZero(); this.hasHit = false; this.shape = null; this.body = null; this.hitFaceIndex = -1; this.distance = -1; this.shouldStop = false; } abort() { this.shouldStop = true; } set(rayFromWorld, rayToWorld, hitNormalWorld, hitPointWorld, shape, body, distance) { this.rayFromWorld.copy(rayFromWorld); this.rayToWorld.copy(rayToWorld); this.hitNormalWorld.copy(hitNormalWorld); this.hitPointWorld.copy(hitPointWorld); this.shape = shape; this.body = body; this.distance = distance; } } let _Shape$types$SPHERE, _Shape$types$PLANE, _Shape$types$BOX, _Shape$types$CYLINDER, _Shape$types$CONVEXPO, _Shape$types$HEIGHTFI, _Shape$types$TRIMESH; const RAY_MODES = { CLOSEST: 1, ANY: 2, ALL: 4 }; _Shape$types$SPHERE = Shape.types.SPHERE; _Shape$types$PLANE = Shape.types.PLANE; _Shape$types$BOX = Shape.types.BOX; _Shape$types$CYLINDER = Shape.types.CYLINDER; _Shape$types$CONVEXPO = Shape.types.CONVEXPOLYHEDRON; _Shape$types$HEIGHTFI = Shape.types.HEIGHTFIELD; _Shape$types$TRIMESH = Shape.types.TRIMESH; class Ray { get [_Shape$types$SPHERE]() { return this._intersectSphere; } get [_Shape$types$PLANE]() { return this._intersectPlane; } get [_Shape$types$BOX]() { return this._intersectBox; } get [_Shape$types$CYLINDER]() { return this._intersectConvex; } get [_Shape$types$CONVEXPO]() { return this._intersectConvex; } get [_Shape$types$HEIGHTFI]() { return this._intersectHeightfield; } get [_Shape$types$TRIMESH]() { return this._intersectTrimesh; } constructor(from, to) { if (from === void 0) { from = new Vec3(); } if (to === void 0) { to = new Vec3(); } this.from = from.clone(); this.to = to.clone(); this.direction = new Vec3(); this.precision = 1e-4; this.checkCollisionResponse = true; this.skipBackfaces = false; this.collisionFilterMask = -1; this.collisionFilterGroup = -1; this.mode = Ray.ANY; this.result = new RaycastResult(); this.hasHit = false; this.callback = (result) => { }; } intersectWorld(world, options) { this.mode = options.mode || Ray.ANY; this.result = options.result || new RaycastResult(); this.skipBackfaces = !!options.skipBackfaces; this.collisionFilterMask = typeof options.collisionFilterMask !== "undefined" ? options.collisionFilterMask : -1; this.collisionFilterGroup = typeof options.collisionFilterGroup !== "undefined" ? options.collisionFilterGroup : -1; this.checkCollisionResponse = typeof options.checkCollisionResponse !== "undefined" ? options.checkCollisionResponse : true; if (options.from) { this.from.copy(options.from); } if (options.to) { this.to.copy(options.to); } this.callback = options.callback || (() => { }); this.hasHit = false; this.result.reset(); this.updateDirection(); this.getAABB(tmpAABB$1); tmpArray.length = 0; world.broadphase.aabbQuery(world, tmpAABB$1, tmpArray); this.intersectBodies(tmpArray); return this.hasHit; } intersectBody(body, result) { if (result) { this.result = result; this.updateDirection(); } const checkCollisionResponse = this.checkCollisionResponse; if (checkCollisionResponse && !body.collisionResponse) { return; } if ((this.collisionFilterGroup & body.collisionFilterMask) === 0 || (body.collisionFilterGroup & this.collisionFilterMask) === 0) { return; } const xi = intersectBody_xi; const qi = intersectBody_qi; for (let i = 0, N = body.shapes.length; i < N; i++) { const shape = body.shapes[i]; if (checkCollisionResponse && !shape.collisionResponse) { continue; } body.quaternion.mult(body.shapeOrientations[i], qi); body.quaternion.vmult(body.shapeOffsets[i], xi); xi.vadd(body.position, xi); this.intersectShape(shape, qi, xi, body); if (this.result.shouldStop) { break; } } } intersectBodies(bodies, result) { if (result) { this.result = result; this.updateDirection(); } for (let i = 0, l = bodies.length; !this.result.shouldStop && i < l; i++) { this.intersectBody(bodies[i]); } } updateDirection() { this.to.vsub(this.from, this.direction); this.direction.normalize(); } intersectShape(shape, quat, position, body) { const from = this.from; const distance = distanceFromIntersection(from, this.direction, position); if (distance > shape.boundingSphereRadius) { return; } const intersectMethod = this[shape.type]; if (intersectMethod) { intersectMethod.call(this, shape, quat, position, body, shape); } } _intersectBox(box, quat, position, body, reportedShape) { return this._intersectConvex(box.convexPolyhedronRepresentation, quat, position, body, reportedShape); } _intersectPlane(shape, quat, position, body, reportedShape) { const from = this.from; const to = this.to; const direction = this.direction; const worldNormal = new Vec3(0, 0, 1); quat.vmult(worldNormal, worldNormal); const len = new Vec3(); from.vsub(position, len); const planeToFrom = len.dot(worldNormal); to.vsub(position, len); const planeToTo = len.dot(worldNormal); if (planeToFrom * planeToTo > 0) { return; } if (from.distanceTo(to) < planeToFrom) { return; } const n_dot_dir = worldNormal.dot(direction); if (Math.abs(n_dot_dir) < this.precision) { return; } const planePointToFrom = new Vec3(); const dir_scaled_with_t = new Vec3(); const hitPointWorld = new Vec3(); from.vsub(position, planePointToFrom); const t = -worldNormal.dot(planePointToFrom) / n_dot_dir; direction.scale(t, dir_scaled_with_t); from.vadd(dir_scaled_with_t, hitPointWorld); this.reportIntersection(worldNormal, hitPointWorld, reportedShape, body, -1); } getAABB(aabb) { const { lowerBound, upperBound } = aabb; const to = this.to; const from = this.from; lowerBound.x = Math.min(to.x, from.x); lowerBound.y = Math.min(to.y, from.y); lowerBound.z = Math.min(to.z, from.z); upperBound.x = Math.max(to.x, from.x); upperBound.y = Math.max(to.y, from.y); upperBound.z = Math.max(to.z, from.z); } _intersectHeightfield(shape, quat, position, body, reportedShape) { shape.data; shape.elementSize; const localRay = intersectHeightfield_localRay; localRay.from.copy(this.from); localRay.to.copy(this.to); Transform.pointToLocalFrame(position, quat, localRay.from, localRay.from); Transform.pointToLocalFrame(position, quat, localRay.to, localRay.to); localRay.updateDirection(); const index = intersectHeightfield_index; let iMinX; let iMinY; let iMaxX; let iMaxY; iMinX = iMinY = 0; iMaxX = iMaxY = shape.data.length - 1; const aabb = new AABB(); localRay.getAABB(aabb); shape.getIndexOfPosition(aabb.lowerBound.x, aabb.lowerBound.y, index, true); iMinX = Math.max(iMinX, index[0]); iMinY = Math.max(iMinY, index[1]); shape.getIndexOfPosition(aabb.upperBound.x, aabb.upperBound.y, index, true); iMaxX = Math.min(iMaxX, index[0] + 1); iMaxY = Math.min(iMaxY, index[1] + 1); for (let i = iMinX; i < iMaxX; i++) { for (let j = iMinY; j < iMaxY; j++) { if (this.result.shouldStop) { return; } shape.getAabbAtIndex(i, j, aabb); if (!aabb.overlapsRay(localRay)) { continue; } shape.getConvexTrianglePillar(i, j, false); Transform.pointToWorldFrame(position, quat, shape.pillarOffset, worldPillarOffset); this._intersectConvex(shape.pillarConvex, quat, worldPillarOffset, body, reportedShape, intersectConvexOptions); if (this.result.shouldStop) { return; } shape.getConvexTrianglePillar(i, j, true); Transform.pointToWorldFrame(position, quat, shape.pillarOffset, worldPillarOffset); this._intersectConvex(shape.pillarConvex, quat, worldPillarOffset, body, reportedShape, intersectConvexOptions); } } } _intersectSphere(sphere, quat, position, body, reportedShape) { const from = this.from; const to = this.to; const r = sphere.radius; const a2 = (to.x - from.x) ** 2 + (to.y - from.y) ** 2 + (to.z - from.z) ** 2; const b2 = 2 * ((to.x - from.x) * (from.x - position.x) + (to.y - from.y) * (from.y - position.y) + (to.z - from.z) * (from.z - position.z)); const c2 = (from.x - position.x) ** 2 + (from.y - position.y) ** 2 + (from.z - position.z) ** 2 - r ** 2; const delta = b2 ** 2 - 4 * a2 * c2; const intersectionPoint = Ray_intersectSphere_intersectionPoint; const normal = Ray_intersectSphere_normal; if (delta < 0) { return; } else if (delta === 0) { from.lerp(to, delta, intersectionPoint); intersectionPoint.vsub(position, normal); normal.normalize(); this.reportIntersection(normal, intersectionPoint, reportedShape, body, -1); } else { const d1 = (-b2 - Math.sqrt(delta)) / (2 * a2); const d2 = (-b2 + Math.sqrt(delta)) / (2 * a2); if (d1 >= 0 && d1 <= 1) { from.lerp(to, d1, intersectionPoint); intersectionPoint.vsub(position, normal); normal.normalize(); this.reportIntersection(normal, intersectionPoint, reportedShape, body, -1); } if (this.result.shouldStop) { return; } if (d2 >= 0 && d2 <= 1) { from.lerp(to, d2, intersectionPoint); intersectionPoint.vsub(position, normal); normal.normalize(); this.reportIntersection(normal, intersectionPoint, reportedShape, body, -1); } } } _intersectConvex(shape, quat, position, body, reportedShape, options) { const normal = intersectConvex_normal; const vector = intersectConvex_vector; const faceList = options && options.faceList || null; const faces = shape.faces; const vertices = shape.vertices; const normals = shape.faceNormals; const direction = this.direction; const from = this.from; const to = this.to; const fromToDistance = from.distanceTo(to); const Nfaces = faceList ? faceList.length : faces.length; const result = this.result; for (let j = 0; !result.shouldStop && j < Nfaces; j++) { const fi = faceList ? faceList[j] : j; const face = faces[fi]; const faceNormal = normals[fi]; const q = quat; const x = position; vector.copy(vertices[face[0]]); q.vmult(vector, vector); vector.vadd(x, vector); vector.vsub(from, vector); q.vmult(faceNormal, normal); const dot = direction.dot(normal); if (Math.abs(dot) < this.precision) { continue; } const scalar = normal.dot(vector) / dot; if (scalar < 0) { continue; } direction.scale(scalar, intersectPoint); intersectPoint.vadd(from, intersectPoint); a.copy(vertices[face[0]]); q.vmult(a, a); x.vadd(a, a); for (let i = 1; !result.shouldStop && i < face.length - 1; i++) { b.copy(vertices[face[i]]); c.copy(vertices[face[i + 1]]); q.vmult(b, b); q.vmult(c, c); x.vadd(b, b); x.vadd(c, c); const distance = intersectPoint.distanceTo(from); if (!(Ray.pointInTriangle(intersectPoint, a, b, c) || Ray.pointInTriangle(intersectPoint, b, a, c)) || distance > fromToDistance) { continue; } this.reportIntersection(normal, intersectPoint, reportedShape, body, fi); } } } _intersectTrimesh(mesh, quat, position, body, reportedShape, options) { const normal = intersectTrimesh_normal; const triangles = intersectTrimesh_triangles; const treeTransform = intersectTrimesh_treeTransform; const vector = intersectConvex_vector; const localDirection = intersectTrimesh_localDirection; const localFrom = intersectTrimesh_localFrom; const localTo = intersectTrimesh_localTo; const worldIntersectPoint = intersectTrimesh_worldIntersectPoint; const worldNormal = intersectTrimesh_worldNormal; const indices = mesh.indices; mesh.vertices; const from = this.from; const to = this.to; const direction = this.direction; treeTransform.position.copy(position); treeTransform.quaternion.copy(quat); Transform.vectorToLocalFrame(position, quat, direction, localDirection); Transform.pointToLocalFrame(position, quat, from, localFrom); Transform.pointToLocalFrame(position, quat, to, localTo); localTo.x *= mesh.scale.x; localTo.y *= mesh.scale.y; localTo.z *= mesh.scale.z; localFrom.x *= mesh.scale.x; localFrom.y *= mesh.scale.y; localFrom.z *= mesh.scale.z; localTo.vsub(localFrom, localDirection); localDirection.normalize(); const fromToDistanceSquared = localFrom.distanceSquared(localTo); mesh.tree.rayQuery(this, treeTransform, triangles); for (let i = 0, N = triangles.length; !this.result.shouldStop && i !== N; i++) { const trianglesIndex = triangles[i]; mesh.getNormal(trianglesIndex, normal); mesh.getVertex(indices[trianglesIndex * 3], a); a.vsub(localFrom, vector); const dot = localDirection.dot(normal); const scalar = normal.dot(vector) / dot; if (scalar < 0) { continue; } localDirection.scale(scalar, intersectPoint); intersectPoint.vadd(localFrom, intersectPoint); mesh.getVertex(indices[trianglesIndex * 3 + 1], b); mesh.getVertex(indices[trianglesIndex * 3 + 2], c); const squaredDistance = intersectPoint.distanceSquared(localFrom); if (!(Ray.pointInTriangle(intersectPoint, b, a, c) || Ray.pointInTriangle(intersectPoint, a, b, c)) || squaredDistance > fromToDistanceSquared) { continue; } Transform.vectorToWorldFrame(quat, normal, worldNormal); Transform.pointToWorldFrame(position, quat, intersectPoint, worldIntersectPoint); this.reportIntersection(worldNormal, worldIntersectPoint, reportedShape, body, trianglesIndex); } triangles.length = 0; } reportIntersection(normal, hitPointWorld, shape, body, hitFaceIndex) { const from = this.from; const to = this.to; const distance = from.distanceTo(hitPointWorld); const result = this.result; if (this.skipBackfaces && normal.dot(this.direction) > 0) { return; } result.hitFaceIndex = typeof hitFaceIndex !== "undefined" ? hitFaceIndex : -1; switch (this.mode) { case Ray.ALL: this.hasHit = true; result.set(from, to, normal, hitPointWorld, shape, body, distance); result.hasHit = true; this.callback(result); break; case Ray.CLOSEST: if (distance < result.distance || !result.hasHit) { this.hasHit = true; result.hasHit = true; result.set(from, to, normal, hitPointWorld, shape, body, distance); } break; case Ray.ANY: this.hasHit = true; result.hasHit = true; result.set(from, to, normal, hitPointWorld, shape, body, distance); result.shouldStop = true; break; } } static pointInTriangle(p, a2, b2, c2) { c2.vsub(a2, v0); b2.vsub(a2, v1); p.vsub(a2, v2); const dot00 = v0.dot(v0); const dot01 = v0.dot(v1); const dot02 = v0.dot(v2); const dot11 = v1.dot(v1); const dot12 = v1.dot(v2); let u; let v; return (u = dot11 * dot02 - dot01 * dot12) >= 0 && (v = dot00 * dot12 - dot01 * dot02) >= 0 && u + v < dot00 * dot11 - dot01 * dot01; } } Ray.CLOSEST = RAY_MODES.CLOSEST; Ray.ANY = RAY_MODES.ANY; Ray.ALL = RAY_MODES.ALL; const tmpAABB$1 = new AABB(); const tmpArray = []; const v1 = new Vec3(); const v2 = new Vec3(); const intersectBody_xi = new Vec3(); const intersectBody_qi = new Quaternion(); const intersectPoint = new Vec3(); const a = new Vec3(); const b = new Vec3(); const c = new Vec3(); new Vec3(); new RaycastResult(); const intersectConvexOptions = { faceList: [0] }; const worldPillarOffset = new Vec3(); const intersectHeightfield_localRay = new Ray(); const intersectHeightfield_index = []; const Ray_intersectSphere_intersectionPoint = new Vec3(); const Ray_intersectSphere_normal = new Vec3(); const intersectConvex_normal = new Vec3(); new Vec3(); new Vec3(); const intersectConvex_vector = new Vec3(); const intersectTrimesh_normal = new Vec3(); const intersectTrimesh_localDirection = new Vec3(); const intersectTrimesh_localFrom = new Vec3(); const intersectTrimesh_localTo = new Vec3(); const intersectTrimesh_worldNormal = new Vec3(); const intersectTrimesh_worldIntersectPoint = new Vec3(); new AABB(); const intersectTrimesh_triangles = []; const intersectTrimesh_treeTransform = new Transform(); const v0 = new Vec3(); const intersect = new Vec3(); function distanceFromIntersection(from, direction, position) { position.vsub(from, v0); const dot = v0.dot(direction); direction.scale(dot, intersect); intersect.vadd(from, intersect); const distance = position.distanceTo(intersect); return distance; } class Utils { static defaults(options, defaults) { if (options === void 0) { options = {}; } for (let key in defaults) { if (!(key in options)) { options[key] = defaults[key]; } } return options; } } class JacobianElement { constructor() { this.spatial = new Vec3(); this.rotational = new Vec3(); } multiplyElement(element) { return element.spatial.dot(this.spatial) + element.rotational.dot(this.rotational); } multiplyVectors(spatial, rotational) { return spatial.dot(this.spatial) + rotational.dot(this.rotational); } } class Equation { constructor(bi, bj, minForce, maxForce) { if (minForce === void 0) { minForce = -1e6; } if (maxForce === void 0) { maxForce = 1e6; } this.id = Equation.idCounter++; this.minForce = minForce; this.maxForce = maxForce; this.bi = bi; this.bj = bj; this.a = 0; this.b = 0; this.eps = 0; this.jacobianElementA = new JacobianElement(); this.jacobianElementB = new JacobianElement(); this.enabled = true; this.multiplier = 0; this.setSpookParams(1e7, 4, 1 / 60); } setSpookParams(stiffness, relaxation, timeStep) { const d = relaxation; const k = stiffness; const h = timeStep; this.a = 4 / (h * (1 + 4 * d)); this.b = 4 * d / (1 + 4 * d); this.eps = 4 / (h * h * k * (1 + 4 * d)); } computeB(a2, b2, h) { const GW = this.computeGW(); const Gq = this.computeGq(); const GiMf = this.computeGiMf(); return -Gq * a2 - GW * b2 - GiMf * h; } computeGq() { const GA = this.jacobianElementA; const GB = this.jacobianElementB; const bi = this.bi; const bj = this.bj; const xi = bi.position; const xj = bj.position; return GA.spatial.dot(xi) + GB.spatial.dot(xj); } computeGW() { const GA = this.jacobianElementA; const GB = this.jacobianElementB; const bi = this.bi; const bj = this.bj; const vi = bi.velocity; const vj = bj.velocity; const wi = bi.angularVelocity; const wj = bj.angularVelocity; return GA.multiplyVectors(vi, wi) + GB.multiplyVectors(vj, wj); } computeGWlambda() { const GA = this.jacobianElementA; const GB = this.jacobianElementB; const bi = this.bi; const bj = this.bj; const vi = bi.vlambda; const vj = bj.vlambda; const wi = bi.wlambda; const wj = bj.wlambda; return GA.multiplyVectors(vi, wi) + GB.multiplyVectors(vj, wj); } computeGiMf() { const GA = this.jacobianElementA; const GB = this.jacobianElementB; const bi = this.bi; const bj = this.bj; const fi = bi.force; const ti = bi.torque; const fj = bj.force; const tj = bj.torque; const invMassi = bi.invMassSolve; const invMassj = bj.invMassSolve; fi.scale(invMassi, iMfi); fj.scale(invMassj, iMfj); bi.invInertiaWorldSolve.vmult(ti, invIi_vmult_taui); bj.invInertiaWorldSolve.vmult(tj, invIj_vmult_tauj); return GA.multiplyVectors(iMfi, invIi_vmult_taui) + GB.multiplyVectors(iMfj, invIj_vmult_tauj); } computeGiMGt() { const GA = this.jacobianElementA; const GB = this.jacobianElementB; const bi = this.bi; const bj = this.bj; const invMassi = bi.invMassSolve; const invMassj = bj.invMassSolve; const invIi = bi.invInertiaWorldSolve; const invIj = bj.invInertiaWorldSolve; let result = invMassi + invMassj; invIi.vmult(GA.rotational, tmp); result += tmp.dot(GA.rotational); invIj.vmult(GB.rotational, tmp); result += tmp.dot(GB.rotational); return result; } addToWlambda(deltalambda) { const GA = this.jacobianElementA; const GB = this.jacobianElementB; const bi = this.bi; const bj = this.bj; const temp = addToWlambda_temp; bi.vlambda.addScaledVector(bi.invMassSolve * deltalambda, GA.spatial, bi.vlambda); bj.vlambda.addScaledVector(bj.invMassSolve * deltalambda, GB.spatial, bj.vlambda); bi.invInertiaWorldSolve.vmult(GA.rotational, temp); bi.wlambda.addScaledVector(deltalambda, temp, bi.wlambda); bj.invInertiaWorldSolve.vmult(GB.rotational, temp); bj.wlambda.addScaledVector(deltalambda, temp, bj.wlambda); } computeC() { return this.computeGiMGt() + this.eps; } } Equation.idCounter = 0; const iMfi = new Vec3(); const iMfj = new Vec3(); const invIi_vmult_taui = new Vec3(); const invIj_vmult_tauj = new Vec3(); const tmp = new Vec3(); const addToWlambda_temp = new Vec3(); class ContactEquation extends Equation { constructor(bodyA, bodyB, maxForce) { if (maxForce === void 0) { maxForce = 1e6; } super(bodyA, bodyB, 0, maxForce); this.restitution = 0; this.ri = new Vec3(); this.rj = new Vec3(); this.ni = new Vec3(); } computeB(h) { const a2 = this.a; const b2 = this.b; const bi = this.bi; const bj = this.bj; const ri = this.ri; const rj = this.rj; const rixn = ContactEquation_computeB_temp1; const rjxn = ContactEquation_computeB_temp2; const vi = bi.velocity; const wi = bi.angularVelocity; bi.force; bi.torque; const vj = bj.velocity; const wj = bj.angularVelocity; bj.force; bj.torque; const penetrationVec = ContactEquation_computeB_temp3; const GA = this.jacobianElementA; const GB = this.jacobianElementB; const n = this.ni; ri.cross(n, rixn); rj.cross(n, rjxn); n.negate(GA.spatial); rixn.negate(GA.rotational); GB.spatial.copy(n); GB.rotational.copy(rjxn); penetrationVec.copy(bj.position); penetrationVec.vadd(rj, penetrationVec); penetrationVec.vsub(bi.position, penetrationVec); penetrationVec.vsub(ri, penetrationVec); const g = n.dot(penetrationVec); const ePlusOne = this.restitution + 1; const GW = ePlusOne * vj.dot(n) - ePlusOne * vi.dot(n) + wj.dot(rjxn) - wi.dot(rixn); const GiMf = this.computeGiMf(); const B = -g * a2 - GW * b2 - h * GiMf; return B; } getImpactVelocityAlongNormal() { const vi = ContactEquation_getImpactVelocityAlongNormal_vi; const vj = ContactEquation_getImpactVelocityAlongNormal_vj; const xi = ContactEquation_getImpactVelocityAlongNormal_xi; const xj = ContactEquation_getImpactVelocityAlongNormal_xj; const relVel = ContactEquation_getImpactVelocityAlongNormal_relVel; this.bi.position.vadd(this.ri, xi); this.bj.position.vadd(this.rj, xj); this.bi.getVelocityAtWorldPoint(xi, vi); this.bj.getVelocityAtWorldPoint(xj, vj); vi.vsub(vj, relVel); return this.ni.dot(relVel); } } const ContactEquation_computeB_temp1 = new Vec3(); const ContactEquation_computeB_temp2 = new Vec3(); const ContactEquation_computeB_temp3 = new Vec3(); const ContactEquation_getImpactVelocityAlongNormal_vi = new Vec3(); const ContactEquation_getImpactVelocityAlongNormal_vj = new Vec3(); const ContactEquation_getImpactVelocityAlongNormal_xi = new Vec3(); const ContactEquation_getImpactVelocityAlongNormal_xj = new Vec3(); const ContactEquation_getImpactVelocityAlongNormal_relVel = new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); class FrictionEquation extends Equation { constructor(bodyA, bodyB, slipForce) { super(bodyA, bodyB, -slipForce, slipForce); this.ri = new Vec3(); this.rj = new Vec3(); this.t = new Vec3(); } computeB(h) { this.a; const b2 = this.b; this.bi; this.bj; const ri = this.ri; const rj = this.rj; const rixt = FrictionEquation_computeB_temp1; const rjxt = FrictionEquation_computeB_temp2; const t = this.t; ri.cross(t, rixt); rj.cross(t, rjxt); const GA = this.jacobianElementA; const GB = this.jacobianElementB; t.negate(GA.spatial); rixt.negate(GA.rotational); GB.spatial.copy(t); GB.rotational.copy(rjxt); const GW = this.computeGW(); const GiMf = this.computeGiMf(); const B = -GW * b2 - h * GiMf; return B; } } const FrictionEquation_computeB_temp1 = new Vec3(); const FrictionEquation_computeB_temp2 = new Vec3(); class ContactMaterial { constructor(m1, m2, options) { options = Utils.defaults(options, { friction: 0.3, restitution: 0.3, contactEquationStiffness: 1e7, contactEquationRelaxation: 3, frictionEquationStiffness: 1e7, frictionEquationRelaxation: 3 }); this.id = ContactMaterial.idCounter++; this.materials = [m1, m2]; this.friction = options.friction; this.restitution = options.restitution; this.contactEquationStiffness = options.contactEquationStiffness; this.contactEquationRelaxation = options.contactEquationRelaxation; this.frictionEquationStiffness = options.frictionEquationStiffness; this.frictionEquationRelaxation = options.frictionEquationRelaxation; } } ContactMaterial.idCounter = 0; class Material { constructor(options) { if (options === void 0) { options = {}; } let name = ""; if (typeof options === "string") { name = options; options = {}; } this.name = name; this.id = Material.idCounter++; this.friction = typeof options.friction !== "undefined" ? options.friction : -1; this.restitution = typeof options.restitution !== "undefined" ? options.restitution : -1; } } Material.idCounter = 0; new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Ray(); new Vec3(); new Vec3(); new Vec3(); [new Vec3(1, 0, 0), new Vec3(0, 1, 0), new Vec3(0, 0, 1)]; new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); class Sphere extends Shape { constructor(radius) { super({ type: Shape.types.SPHERE }); this.radius = radius !== void 0 ? radius : 1; if (this.radius < 0) { throw new Error("The sphere radius cannot be negative."); } this.updateBoundingSphereRadius(); } calculateLocalInertia(mass, target) { if (target === void 0) { target = new Vec3(); } const I = 2 * mass * this.radius * this.radius / 5; target.x = I; target.y = I; target.z = I; return target; } volume() { return 4 * Math.PI * Math.pow(this.radius, 3) / 3; } updateBoundingSphereRadius() { this.boundingSphereRadius = this.radius; } calculateWorldAABB(pos, quat, min, max) { const r = this.radius; const axes = ["x", "y", "z"]; for (let i = 0; i < axes.length; i++) { const ax = axes[i]; min[ax] = pos[ax] - r; max[ax] = pos[ax] + r; } } } new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); class Cylinder extends ConvexPolyhedron { constructor(radiusTop, radiusBottom, height, numSegments) { if (radiusTop === void 0) { radiusTop = 1; } if (radiusBottom === void 0) { radiusBottom = 1; } if (height === void 0) { height = 1; } if (numSegments === void 0) { numSegments = 8; } if (radiusTop < 0) { throw new Error("The cylinder radiusTop cannot be negative."); } if (radiusBottom < 0) { throw new Error("The cylinder radiusBottom cannot be negative."); } const N = numSegments; const vertices = []; const axes = []; const faces = []; const bottomface = []; const topface = []; const cos = Math.cos; const sin = Math.sin; vertices.push(new Vec3(-radiusBottom * sin(0), -height * 0.5, radiusBottom * cos(0))); bottomface.push(0); vertices.push(new Vec3(-radiusTop * sin(0), height * 0.5, radiusTop * cos(0))); topface.push(1); for (let i = 0; i < N; i++) { const theta = 2 * Math.PI / N * (i + 1); const thetaN = 2 * Math.PI / N * (i + 0.5); if (i < N - 1) { vertices.push(new Vec3(-radiusBottom * sin(theta), -height * 0.5, radiusBottom * cos(theta))); bottomface.push(2 * i + 2); vertices.push(new Vec3(-radiusTop * sin(theta), height * 0.5, radiusTop * cos(theta))); topface.push(2 * i + 3); faces.push([2 * i, 2 * i + 1, 2 * i + 3, 2 * i + 2]); } else { faces.push([2 * i, 2 * i + 1, 1, 0]); } if (N % 2 === 1 || i < N / 2) { axes.push(new Vec3(-sin(thetaN), 0, cos(thetaN))); } } faces.push(bottomface); axes.push(new Vec3(0, 1, 0)); const temp = []; for (let i = 0; i < topface.length; i++) { temp.push(topface[topface.length - i - 1]); } faces.push(temp); super({ vertices, faces, axes }); this.type = Shape.types.CYLINDER; this.radiusTop = radiusTop; this.radiusBottom = radiusBottom; this.height = height; this.numSegments = numSegments; } } new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new AABB(); new Vec3(); new AABB(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new Vec3(); new AABB(); new Vec3(); new Transform(); new AABB(); class Solver { constructor() { this.equations = []; } solve(dt, world) { return 0; } addEquation(eq) { if (eq.enabled && !eq.bi.isTrigger && !eq.bj.isTrigger) { this.equations.push(eq); } } removeEquation(eq) { const eqs = this.equations; const i = eqs.indexOf(eq); if (i !== -1) { eqs.splice(i, 1); } } removeAllEquations() { this.equations.length = 0; } } class GSSolver extends Solver { constructor() { super(); this.iterations = 10; this.tolerance = 1e-7; } solve(dt, world) { let iter = 0; const maxIter = this.iterations; const tolSquared = this.tolerance * this.tolerance; const equations = this.equations; const Neq = equations.length; const bodies = world.bodies; const Nbodies = bodies.length; const h = dt; let B; let invC; let deltalambda; let deltalambdaTot; let GWlambda; let lambdaj; if (Neq !== 0) { for (let i = 0; i !== Nbodies; i++) { bodies[i].updateSolveMassProperties(); } } const invCs = GSSolver_solve_invCs; const Bs = GSSolver_solve_Bs; const lambda = GSSolver_solve_lambda; invCs.length = Neq; Bs.length = Neq; lambda.length = Neq; for (let i = 0; i !== Neq; i++) { const c2 = equations[i]; lambda[i] = 0; Bs[i] = c2.computeB(h); invCs[i] = 1 / c2.computeC(); } if (Neq !== 0) { for (let i = 0; i !== Nbodies; i++) { const b2 = bodies[i]; const vlambda = b2.vlambda; const wlambda = b2.wlambda; vlambda.set(0, 0, 0); wlambda.set(0, 0, 0); } for (iter = 0; iter !== maxIter; iter++) { deltalambdaTot = 0; for (let j = 0; j !== Neq; j++) { const c2 = equations[j]; B = Bs[j]; invC = invCs[j]; lambdaj = lambda[j]; GWlambda = c2.computeGWlambda(); deltalambda = invC * (B - GWlambda - c2.eps * lambdaj); if (lambdaj + deltalambda < c2.minForce) { deltalambda = c2.minForce - lambdaj; } else if (lambdaj + deltalambda > c2.maxForce) { deltalambda = c2.maxForce - lambdaj; } lambda[j] += deltalambda; deltalambdaTot += deltalambda > 0 ? deltalambda : -deltalambda; c2.addToWlambda(deltalambda); } if (deltalambdaTot * deltalambdaTot < tolSquared) { break; } } for (let i = 0; i !== Nbodies; i++) { const b2 = bodies[i]; const v = b2.velocity; const w = b2.angularVelocity; b2.vlambda.vmul(b2.linearFactor, b2.vlambda); v.vadd(b2.vlambda, v); b2.wlambda.vmul(b2.angularFactor, b2.wlambda); w.vadd(b2.wlambda, w); } let l = equations.length; const invDt = 1 / h; while (l--) { equations[l].multiplier = lambda[l] * invDt; } } return iter; } } const GSSolver_solve_lambda = []; const GSSolver_solve_invCs = []; const GSSolver_solve_Bs = []; class SplitSolver extends Solver { constructor(subsolver) { super(); this.iterations = 10; this.tolerance = 1e-7; this.subsolver = subsolver; this.nodes = []; this.nodePool = []; while (this.nodePool.length < 128) { this.nodePool.push(this.createNode()); } } createNode() { return { body: null, children: [], eqs: [], visited: false }; } solve(dt, world) { const nodes = SplitSolver_solve_nodes; const nodePool = this.nodePool; const bodies = world.bodies; const equations = this.equations; const Neq = equations.length; const Nbodies = bodies.length; const subsolver = this.subsolver; while (nodePool.length < Nbodies) { nodePool.push(this.createNode()); } nodes.length = Nbodies; for (let i = 0; i < Nbodies; i++) { nodes[i] = nodePool[i]; } for (let i = 0; i !== Nbodies; i++) { const node = nodes[i]; node.body = bodies[i]; node.children.length = 0; node.eqs.length = 0; node.visited = false; } for (let k = 0; k !== Neq; k++) { const eq = equations[k]; const i = bodies.indexOf(eq.bi); const j = bodies.indexOf(eq.bj); const ni = nodes[i]; const nj = nodes[j]; ni.children.push(nj); ni.eqs.push(eq); nj.children.push(ni); nj.eqs.push(eq); } let child; let n = 0; let eqs = SplitSolver_solve_eqs; subsolver.tolerance = this.tolerance; subsolver.iterations = this.iterations; const dummyWorld = SplitSolver_solve_dummyWorld; while (child = getUnvisitedNode(nodes)) { eqs.length = 0; dummyWorld.bodies.length = 0; bfs(child, visitFunc, dummyWorld.bodies, eqs); const Neqs = eqs.length; eqs = eqs.sort(sortById); for (let i = 0; i !== Neqs; i++) { subsolver.addEquation(eqs[i]); } subsolver.solve(dt, dummyWorld); subsolver.removeAllEquations(); n++; } return n; } } const SplitSolver_solve_nodes = []; const SplitSolver_solve_eqs = []; const SplitSolver_solve_dummyWorld = { bodies: [] }; const STATIC = Body.STATIC; function getUnvisitedNode(nodes) { const Nnodes = nodes.length; for (let i = 0; i !== Nnodes; i++) { const node = nodes[i]; if (!node.visited && !(node.body.type & STATIC)) { return node; } } return false; } const queue = []; function bfs(root, visitFunc2, bds, eqs) { queue.push(root); root.visited = true; visitFunc2(root, bds, eqs); while (queue.length) { const node = queue.pop(); let child; while (child = getUnvisitedNode(node.children)) { child.visited = true; visitFunc2(child, bds, eqs); queue.push(child); } } } function visitFunc(node, bds, eqs) { bds.push(node.body); const Neqs = node.eqs.length; for (let i = 0; i !== Neqs; i++) { const eq = node.eqs[i]; if (!eqs.includes(eq)) { eqs.push(eq); } } } function sortById(a2, b2) { return b2.id - a2.id; } class Pool { constructor() { this.objects = []; this.type = Object; } release() { const Nargs = arguments.length; for (let i = 0; i !== Nargs; i++) { this.objects.push(i < 0 || arguments.length <= i ? void 0 : arguments[i]); } return this; } get() { if (this.objects.length === 0) { return this.constructObject(); } else { return this.objects.pop(); } } constructObject() { throw new Error("constructObject() not implemented in this Pool subclass yet!"); } resize(size) { const objects = this.objects; while (objects.length > size) { objects.pop(); } while (objects.length < size) { objects.push(this.constructObject()); } return this; } } class Vec3Pool extends Pool { constructor() { super(...arguments); this.type = Vec3; } constructObject() { return new Vec3(); } } const COLLISION_TYPES = { sphereSphere: Shape.types.SPHERE, spherePlane: Shape.types.SPHERE | Shape.types.PLANE, boxBox: Shape.types.BOX | Shape.types.BOX, sphereBox: Shape.types.SPHERE | Shape.types.BOX, planeBox: Shape.types.PLANE | Shape.types.BOX, convexConvex: Shape.types.CONVEXPOLYHEDRON, sphereConvex: Shape.types.SPHERE | Shape.types.CONVEXPOLYHEDRON, planeConvex: Shape.types.PLANE | Shape.types.CONVEXPOLYHEDRON, boxConvex: Shape.types.BOX | Shape.types.CONVEXPOLYHEDRON, sphereHeightfield: Shape.types.SPHERE | Shape.types.HEIGHTFIELD, boxHeightfield: Shape.types.BOX | Shape.types.HEIGHTFIELD, convexHeightfield: Shape.types.CONVEXPOLYHEDRON | Shape.types.HEIGHTFIELD, sphereParticle: Shape.types.PARTICLE | Shape.types.SPHERE, planeParticle: Shape.types.PLANE | Shape.types.PARTICLE, boxParticle: Shape.types.BOX | Shape.types.PARTICLE, convexParticle: Shape.types.PARTICLE | Shape.types.CONVEXPOLYHEDRON, cylinderCylinder: Shape.types.CYLINDER, sphereCylinder: Shape.types.SPHERE | Shape.types.CYLINDER, planeCylinder: Shape.types.PLANE | Shape.types.CYLINDER, boxCylinder: Shape.types.BOX | Shape.types.CYLINDER, convexCylinder: Shape.types.CONVEXPOLYHEDRON | Shape.types.CYLINDER, heightfieldCylinder: Shape.types.HEIGHTFIELD | Shape.types.CYLINDER, particleCylinder: Shape.types.PARTICLE | Shape.types.CYLINDER, sphereTrimesh: Shape.types.SPHERE | Shape.types.TRIMESH, planeTrimesh: Shape.types.PLANE | Shape.types.TRIMESH }; class Narrowphase { get [COLLISION_TYPES.sphereSphere]() { return this.sphereSphere; } get [COLLISION_TYPES.spherePlane]() { return this.spherePlane; } get [COLLISION_TYPES.boxBox]() { return this.boxBox; } get [COLLISION_TYPES.sphereBox]() { return this.sphereBox; } get [COLLISION_TYPES.planeBox]() { return this.planeBox; } get [COLLISION_TYPES.convexConvex]() { return this.convexConvex; } get [COLLISION_TYPES.sphereConvex]() { return this.sphereConvex; } get [COLLISION_TYPES.planeConvex]() { return this.planeConvex; } get [COLLISION_TYPES.boxConvex]() { return this.boxConvex; } get [COLLISION_TYPES.sphereHeightfield]() { return this.sphereHeightfield; } get [COLLISION_TYPES.boxHeightfield]() { return this.boxHeightfield; } get [COLLISION_TYPES.convexHeightfield]() { return this.convexHeightfield; } get [COLLISION_TYPES.sphereParticle]() { return this.sphereParticle; } get [COLLISION_TYPES.planeParticle]() { return this.planeParticle; } get [COLLISION_TYPES.boxParticle]() { return this.boxParticle; } get [COLLISION_TYPES.convexParticle]() { return this.convexParticle; } get [COLLISION_TYPES.cylinderCylinder]() { return this.convexConvex; } get [COLLISION_TYPES.sphereCylinder]() { return this.sphereConvex; } get [COLLISION_TYPES.planeCylinder]() { return this.planeConvex; } get [COLLISION_TYPES.boxCylinder]() { return this.boxConvex; } get [COLLISION_TYPES.convexCylinder]() { return this.convexConvex; } get [COLLISION_TYPES.heightfieldCylinder]() { return this.heightfieldCylinder; } get [COLLISION_TYPES.particleCylinder]() { return this.particleCylinder; } get [COLLISION_TYPES.sphereTrimesh]() { return this.sphereTrimesh; } get [COLLISION_TYPES.planeTrimesh]() { return this.planeTrimesh; } constructor(world) { this.contactPointPool = []; this.frictionEquationPool = []; this.result = []; this.frictionResult = []; this.v3pool = new Vec3Pool(); this.world = world; this.currentContactMaterial = world.defaultContactMaterial; this.enableFrictionReduction = false; } createContactEquation(bi, bj, si, sj, overrideShapeA, overrideShapeB) { let c2; if (this.contactPointPool.length) { c2 = this.contactPointPool.pop(); c2.bi = bi; c2.bj = bj; } else { c2 = new ContactEquation(bi, bj); } c2.enabled = bi.collisionResponse && bj.collisionResponse && si.collisionResponse && sj.collisionResponse; const cm = this.currentContactMaterial; c2.restitution = cm.restitution; c2.setSpookParams(cm.contactEquationStiffness, cm.contactEquationRelaxation, this.world.dt); const matA = si.material || bi.material; const matB = sj.material || bj.material; if (matA && matB && matA.restitution >= 0 && matB.restitution >= 0) { c2.restitution = matA.restitution * matB.restitution; } c2.si = overrideShapeA || si; c2.sj = overrideShapeB || sj; return c2; } createFrictionEquationsFromContact(contactEquation, outArray) { const bodyA = contactEquation.bi; const bodyB = contactEquation.bj; const shapeA = contactEquation.si; const shapeB = contactEquation.sj; const world = this.world; const cm = this.currentContactMaterial; let friction = cm.friction; const matA = shapeA.material || bodyA.material; const matB = shapeB.material || bodyB.material; if (matA && matB && matA.friction >= 0 && matB.friction >= 0) { friction = matA.friction * matB.friction; } if (friction > 0) { const mug = friction * world.gravity.length(); let reducedMass = bodyA.invMass + bodyB.invMass; if (reducedMass > 0) { reducedMass = 1 / reducedMass; } const pool = this.frictionEquationPool; const c1 = pool.length ? pool.pop() : new FrictionEquation(bodyA, bodyB, mug * reducedMass); const c2 = pool.length ? pool.pop() : new FrictionEquation(bodyA, bodyB, mug * reducedMass); c1.bi = c2.bi = bodyA; c1.bj = c2.bj = bodyB; c1.minForce = c2.minForce = -mug * reducedMass; c1.maxForce = c2.maxForce = mug * reducedMass; c1.ri.copy(contactEquation.ri); c1.rj.copy(contactEquation.rj); c2.ri.copy(contactEquation.ri); c2.rj.copy(contactEquation.rj); contactEquation.ni.tangents(c1.t, c2.t); c1.setSpookParams(cm.frictionEquationStiffness, cm.frictionEquationRelaxation, world.dt); c2.setSpookParams(cm.frictionEquationStiffness, cm.frictionEquationRelaxation, world.dt); c1.enabled = c2.enabled = contactEquation.enabled; outArray.push(c1, c2); return true; } return false; } createFrictionFromAverage(numContacts) { let c2 = this.result[this.result.length - 1]; if (!this.createFrictionEquationsFromContact(c2, this.frictionResult) || numContacts === 1) { return; } const f1 = this.frictionResult[this.frictionResult.length - 2]; const f2 = this.frictionResult[this.frictionResult.length - 1]; averageNormal.setZero(); averageContactPointA.setZero(); averageContactPointB.setZero(); const bodyA = c2.bi; c2.bj; for (let i = 0; i !== numContacts; i++) { c2 = this.result[this.result.length - 1 - i]; if (c2.bi !== bodyA) { averageNormal.vadd(c2.ni, averageNormal); averageContactPointA.vadd(c2.ri, averageContactPointA); averageContactPointB.vadd(c2.rj, averageContactPointB); } else { averageNormal.vsub(c2.ni, averageNormal); averageContactPointA.vadd(c2.rj, averageContactPointA); averageContactPointB.vadd(c2.ri, averageContactPointB); } } const invNumContacts = 1 / numContacts; averageContactPointA.scale(invNumContacts, f1.ri); averageContactPointB.scale(invNumContacts, f1.rj); f2.ri.copy(f1.ri); f2.rj.copy(f1.rj); averageNormal.normalize(); averageNormal.tangents(f1.t, f2.t); } getContacts(p1, p2, world, result, oldcontacts, frictionResult, frictionPool) { this.contactPointPool = oldcontacts; this.frictionEquationPool = frictionPool; this.result = result; this.frictionResult = frictionResult; const qi = tmpQuat1; const qj = tmpQuat2; const xi = tmpVec1; const xj = tmpVec2; for (let k = 0, N = p1.length; k !== N; k++) { const bi = p1[k]; const bj = p2[k]; let bodyContactMaterial = null; if (bi.material && bj.material) { bodyContactMaterial = world.getContactMaterial(bi.material, bj.material) || null; } const justTest = bi.type & Body.KINEMATIC && bj.type & Body.STATIC || bi.type & Body.STATIC && bj.type & Body.KINEMATIC || bi.type & Body.KINEMATIC && bj.type & Body.KINEMATIC; for (let i = 0; i < bi.shapes.length; i++) { bi.quaternion.mult(bi.shapeOrientations[i], qi); bi.quaternion.vmult(bi.shapeOffsets[i], xi); xi.vadd(bi.position, xi); const si = bi.shapes[i]; for (let j = 0; j < bj.shapes.length; j++) { bj.quaternion.mult(bj.shapeOrientations[j], qj); bj.quaternion.vmult(bj.shapeOffsets[j], xj); xj.vadd(bj.position, xj); const sj = bj.shapes[j]; if (!(si.collisionFilterMask & sj.collisionFilterGroup && sj.collisionFilterMask & si.collisionFilterGroup)) { continue; } if (xi.distanceTo(xj) > si.boundingSphereRadius + sj.boundingSphereRadius) { continue; } let shapeContactMaterial = null; if (si.material && sj.material) { shapeContactMaterial = world.getContactMaterial(si.material, sj.material) || null; } this.currentContactMaterial = shapeContactMaterial || bodyContactMaterial || world.defaultContactMaterial; const resolverIndex = si.type | sj.type; const resolver = this[resolverIndex]; if (resolver) { let retval = false; if (si.type < sj.type) { retval = resolver.call(this, si, sj, xi, xj, qi, qj, bi, bj, si, sj, justTest); } else { retval = resolver.call(this, sj, si, xj, xi, qj, qi, bj, bi, si, sj, justTest); } if (retval && justTest) { world.shapeOverlapKeeper.set(si.id, sj.id); world.bodyOverlapKeeper.set(bi.id, bj.id); } } } } } } sphereSphere(si, sj, xi, xj, qi, qj, bi, bj, rsi, rsj, justTest) { if (justTest) { return xi.distanceSquared(xj) < (si.radius + sj.radius) ** 2; } const contactEq = this.createContactEquation(bi, bj, si, sj, rsi, rsj); xj.vsub(xi, contactEq.ni); contactEq.ni.normalize(); contactEq.ri.copy(contactEq.ni); contactEq.rj.copy(contactEq.ni); contactEq.ri.scale(si.radius, contactEq.ri); contactEq.rj.scale(-sj.radius, contactEq.rj); contactEq.ri.vadd(xi, contactEq.ri); contactEq.ri.vsub(bi.position, contactEq.ri); contactEq.rj.vadd(xj, contactEq.rj); contactEq.rj.vsub(bj.position, contactEq.rj); this.result.push(contactEq); this.createFrictionEquationsFromContact(contactEq, this.frictionResult); } spherePlane(si, sj, xi, xj, qi, qj, bi, bj, rsi, rsj, justTest) { const r = this.createContactEquation(bi, bj, si, sj, rsi, rsj); r.ni.set(0, 0, 1); qj.vmult(r.ni, r.ni); r.ni.negate(r.ni); r.ni.normalize(); r.ni.scale(si.radius, r.ri); xi.vsub(xj, point_on_plane_to_sphere); r.ni.scale(r.ni.dot(point_on_plane_to_sphere), plane_to_sphere_ortho); point_on_plane_to_sphere.vsub(plane_to_sphere_ortho, r.rj); if (-point_on_plane_to_sphere.dot(r.ni) <= si.radius) { if (justTest) { return true; } const ri = r.ri; const rj = r.rj; ri.vadd(xi, ri); ri.vsub(bi.position, ri); rj.vadd(xj, rj); rj.vsub(bj.position, rj); this.result.push(r); this.createFrictionEquationsFromContact(r, this.frictionResult); } } boxBox(si, sj, xi, xj, qi, qj, bi, bj, rsi, rsj, justTest) { si.convexPolyhedronRepresentation.material = si.material; sj.convexPolyhedronRepresentation.material = sj.material; si.convexPolyhedronRepresentation.collisionResponse = si.collisionResponse; sj.convexPolyhedronRepresentation.collisionResponse = sj.collisionResponse; return this.convexConvex(si.convexPolyhedronRepresentation, sj.convexPolyhedronRepresentation, xi, xj, qi, qj, bi, bj, si, sj, justTest); } sphereBox(si, sj, xi, xj, qi, qj, bi, bj, rsi, rsj, justTest) { const v3pool = this.v3pool; const sides = sphereBox_sides; xi.vsub(xj, box_to_sphere); sj.getSideNormals(sides, qj); const R = si.radius; let found = false; const side_ns = sphereBox_side_ns; const side_ns1 = sphereBox_side_ns1; const side_ns2 = sphereBox_side_ns2; let side_h = null; let side_penetrations = 0; let side_dot1 = 0; let side_dot2 = 0; let side_distance = null; for (let idx = 0, nsides = sides.length; idx !== nsides && found === false; idx++) { const ns = sphereBox_ns; ns.copy(sides[idx]); const h = ns.length(); ns.normalize(); const dot = box_to_sphere.dot(ns); if (dot < h + R && dot > 0) { const ns1 = sphereBox_ns1; const ns2 = sphereBox_ns2; ns1.copy(sides[(idx + 1) % 3]); ns2.copy(sides[(idx + 2) % 3]); const h1 = ns1.length(); const h2 = ns2.length(); ns1.normalize(); ns2.normalize(); const dot1 = box_to_sphere.dot(ns1); const dot2 = box_to_sphere.dot(ns2); if (dot1 < h1 && dot1 > -h1 && dot2 < h2 && dot2 > -h2) { const dist2 = Math.abs(dot - h - R); if (side_distance === null || dist2 < side_distance) { side_distance = dist2; side_dot1 = dot1; side_dot2 = dot2; side_h = h; side_ns.copy(ns); side_ns1.copy(ns1); side_ns2.copy(ns2); side_penetrations++; if (justTest) { return true; } } } } } if (side_penetrations) { found = true; const r2 = this.createContactEquation(bi, bj, si, sj, rsi, rsj); side_ns.scale(-R, r2.ri); r2.ni.copy(side_ns); r2.ni.negate(r2.ni); side_ns.scale(side_h, side_ns); side_ns1.scale(side_dot1, side_ns1); side_ns.vadd(side_ns1, side_ns); side_ns2.scale(side_dot2, side_ns2); side_ns.vadd(side_ns2, r2.rj); r2.ri.vadd(xi, r2.ri); r2.ri.vsub(bi.position, r2.ri); r2.rj.vadd(xj, r2.rj); r2.rj.vsub(bj.position, r2.rj); this.result.push(r2); this.createFrictionEquationsFromContact(r2, this.frictionResult); } let rj = v3pool.get(); const sphere_to_corner = sphereBox_sphere_to_corner; for (let j = 0; j !== 2 && !found; j++) { for (let k = 0; k !== 2 && !found; k++) { for (let l = 0; l !== 2 && !found; l++) { rj.set(0, 0, 0); if (j) { rj.vadd(sides[0], rj); } else { rj.vsub(sides[0], rj); } if (k) { rj.vadd(sides[1], rj); } else { rj.vsub(sides[1], rj); } if (l) { rj.vadd(sides[2], rj); } else { rj.vsub(sides[2], rj); } xj.vadd(rj, sphere_to_corner); sphere_to_corner.vsub(xi, sphere_to_corner); if (sphere_to_corner.lengthSquared() < R * R) { if (justTest) { return true; } found = true; const r2 = this.createContactEquation(bi, bj, si, sj, rsi, rsj); r2.ri.copy(sphere_to_corner); r2.ri.normalize(); r2.ni.copy(r2.ri); r2.ri.scale(R, r2.ri); r2.rj.copy(rj); r2.ri.vadd(xi, r2.ri); r2.ri.vsub(bi.position, r2.ri); r2.rj.vadd(xj, r2.rj); r2.rj.vsub(bj.position, r2.rj); this.result.push(r2); this.createFrictionEquationsFromContact(r2, this.frictionResult); } } } } v3pool.release(rj); rj = null; const edgeTangent = v3pool.get(); const edgeCenter = v3pool.get(); const r = v3pool.get(); const orthogonal = v3pool.get(); const dist = v3pool.get(); const Nsides = sides.length; for (let j = 0; j !== Nsides && !found; j++) { for (let k = 0; k !== Nsides && !found; k++) { if (j % 3 !== k % 3) { sides[k].cross(sides[j], edgeTangent); edgeTangent.normalize(); sides[j].vadd(sides[k], edgeCenter); r.copy(xi); r.vsub(edgeCenter, r); r.vsub(xj, r); const orthonorm = r.dot(edgeTangent); edgeTangent.scale(orthonorm, orthogonal); let l = 0; while (l === j % 3 || l === k % 3) { l++; } dist.copy(xi); dist.vsub(orthogonal, dist); dist.vsub(edgeCenter, dist); dist.vsub(xj, dist); const tdist = Math.abs(orthonorm); const ndist = dist.length(); if (tdist < sides[l].length() && ndist < R) { if (justTest) { return true; } found = true; const res = this.createContactEquation(bi, bj, si, sj, rsi, rsj); edgeCenter.vadd(orthogonal, res.rj); res.rj.copy(res.rj); dist.negate(res.ni); res.ni.normalize(); res.ri.copy(res.rj); res.ri.vadd(xj, res.ri); res.ri.vsub(xi, res.ri); res.ri.normalize(); res.ri.scale(R, res.ri); res.ri.vadd(xi, res.ri); res.ri.vsub(bi.position, res.ri); res.rj.vadd(xj, res.rj); res.rj.vsub(bj.position, res.rj); this.result.push(res); this.createFrictionEquationsFromContact(res, this.frictionResult); } } } } v3pool.release(edgeTangent, edgeCenter, r, orthogonal, dist); } planeBox(si, sj, xi, xj, qi, qj, bi, bj, rsi, rsj, justTest) { sj.convexPolyhedronRepresentation.material = sj.material; sj.convexPolyhedronRepresentation.collisionResponse = sj.collisionResponse; sj.convexPolyhedronRepresentation.id = sj.id; return this.planeConvex(si, sj.convexPolyhedronRepresentation, xi, xj, qi, qj, bi, bj, si, sj, justTest); } convexConvex(si, sj, xi, xj, qi, qj, bi, bj, rsi, rsj, justTest, faceListA, faceListB) { const sepAxis = convexConvex_sepAxis; if (xi.distanceTo(xj) > si.boundingSphereRadius + sj.boundingSphereRadius) { return; } if (si.findSeparatingAxis(sj, xi, qi, xj, qj, sepAxis, faceListA, faceListB)) { const res = []; const q = convexConvex_q; si.clipAgainstHull(xi, qi, sj, xj, qj, sepAxis, -100, 100, res); let numContacts = 0; for (let j = 0; j !== res.length; j++) { if (justTest) { return true; } const r = this.createContactEquation(bi, bj, si, sj, rsi, rsj); const ri = r.ri; const rj = r.rj; sepAxis.negate(r.ni); res[j].normal.negate(q); q.scale(res[j].depth, q); res[j].point.vadd(q, ri); rj.copy(res[j].point); ri.vsub(xi, ri); rj.vsub(xj, rj); ri.vadd(xi, ri); ri.vsub(bi.position, ri); rj.vadd(xj, rj); rj.vsub(bj.position, rj); this.result.push(r); numContacts++; if (!this.enableFrictionReduction) { this.createFrictionEquationsFromContact(r, this.frictionResult); } } if (this.enableFrictionReduction && numContacts) { this.createFrictionFromAverage(numContacts); } } } sphereConvex(si, sj, xi, xj, qi, qj, bi, bj, rsi, rsj, justTest) { const v3pool = this.v3pool; xi.vsub(xj, convex_to_sphere); const normals = sj.faceNormals; const faces = sj.faces; const verts = sj.vertices; const R = si.radius; let found = false; for (let i = 0; i !== verts.length; i++) { const v = verts[i]; const worldCorner = sphereConvex_worldCorner; qj.vmult(v, worldCorner); xj.vadd(worldCorner, worldCorner); const sphere_to_corner = sphereConvex_sphereToCorner; worldCorner.vsub(xi, sphere_to_corner); if (sphere_to_corner.lengthSquared() < R * R) { if (justTest) { return true; } found = true; const r = this.createContactEquation(bi, bj, si, sj, rsi, rsj); r.ri.copy(sphere_to_corner); r.ri.normalize(); r.ni.copy(r.ri); r.ri.scale(R, r.ri); worldCorner.vsub(xj, r.rj); r.ri.vadd(xi, r.ri); r.ri.vsub(bi.position, r.ri); r.rj.vadd(xj, r.rj); r.rj.vsub(bj.position, r.rj); this.result.push(r); this.createFrictionEquationsFromContact(r, this.frictionResult); return; } } for (let i = 0, nfaces = faces.length; i !== nfaces && found === false; i++) { const normal = normals[i]; const face = faces[i]; const worldNormal = sphereConvex_worldNormal; qj.vmult(normal, worldNormal); const worldPoint = sphereConvex_worldPoint; qj.vmult(verts[face[0]], worldPoint); worldPoint.vadd(xj, worldPoint); const worldSpherePointClosestToPlane = sphereConvex_worldSpherePointClosestToPlane; worldNormal.scale(-R, worldSpherePointClosestToPlane); xi.vadd(worldSpherePointClosestToPlane, worldSpherePointClosestToPlane); const penetrationVec = sphereConvex_penetrationVec; worldSpherePointClosestToPlane.vsub(worldPoint, penetrationVec); const penetration = penetrationVec.dot(worldNormal); const worldPointToSphere = sphereConvex_sphereToWorldPoint; xi.vsub(worldPoint, worldPointToSphere); if (penetration < 0 && worldPointToSphere.dot(worldNormal) > 0) { const faceVerts = []; for (let j = 0, Nverts = face.length; j !== Nverts; j++) { const worldVertex = v3pool.get(); qj.vmult(verts[face[j]], worldVertex); xj.vadd(worldVertex, worldVertex); faceVerts.push(worldVertex); } if (pointInPolygon(faceVerts, worldNormal, xi)) { if (justTest) { return true; } found = true; const r = this.createContactEquation(bi, bj, si, sj, rsi, rsj); worldNormal.scale(-R, r.ri); worldNormal.negate(r.ni); const penetrationVec2 = v3pool.get(); worldNormal.scale(-penetration, penetrationVec2); const penetrationSpherePoint = v3pool.get(); worldNormal.scale(-R, penetrationSpherePoint); xi.vsub(xj, r.rj); r.rj.vadd(penetrationSpherePoint, r.rj); r.rj.vadd(penetrationVec2, r.rj); r.rj.vadd(xj, r.rj); r.rj.vsub(bj.position, r.rj); r.ri.vadd(xi, r.ri); r.ri.vsub(bi.position, r.ri); v3pool.release(penetrationVec2); v3pool.release(penetrationSpherePoint); this.result.push(r); this.createFrictionEquationsFromContact(r, this.frictionResult); for (let j = 0, Nfaceverts = faceVerts.length; j !== Nfaceverts; j++) { v3pool.release(faceVerts[j]); } return; } else { for (let j = 0; j !== face.length; j++) { const v12 = v3pool.get(); const v22 = v3pool.get(); qj.vmult(verts[face[(j + 1) % face.length]], v12); qj.vmult(verts[face[(j + 2) % face.length]], v22); xj.vadd(v12, v12); xj.vadd(v22, v22); const edge = sphereConvex_edge; v22.vsub(v12, edge); const edgeUnit = sphereConvex_edgeUnit; edge.unit(edgeUnit); const p = v3pool.get(); const v1_to_xi = v3pool.get(); xi.vsub(v12, v1_to_xi); const dot = v1_to_xi.dot(edgeUnit); edgeUnit.scale(dot, p); p.vadd(v12, p); const xi_to_p = v3pool.get(); p.vsub(xi, xi_to_p); if (dot > 0 && dot * dot < edge.lengthSquared() && xi_to_p.lengthSquared() < R * R) { if (justTest) { return true; } const r = this.createContactEquation(bi, bj, si, sj, rsi, rsj); p.vsub(xj, r.rj); p.vsub(xi, r.ni); r.ni.normalize(); r.ni.scale(R, r.ri); r.rj.vadd(xj, r.rj); r.rj.vsub(bj.position, r.rj); r.ri.vadd(xi, r.ri); r.ri.vsub(bi.position, r.ri); this.result.push(r); this.createFrictionEquationsFromContact(r, this.frictionResult); for (let j2 = 0, Nfaceverts = faceVerts.length; j2 !== Nfaceverts; j2++) { v3pool.release(faceVerts[j2]); } v3pool.release(v12); v3pool.release(v22); v3pool.release(p); v3pool.release(xi_to_p); v3pool.release(v1_to_xi); return; } v3pool.release(v12); v3pool.release(v22); v3pool.release(p); v3pool.release(xi_to_p); v3pool.release(v1_to_xi); } } for (let j = 0, Nfaceverts = faceVerts.length; j !== Nfaceverts; j++) { v3pool.release(faceVerts[j]); } } } } planeConvex(planeShape, convexShape, planePosition, convexPosition, planeQuat, convexQuat, planeBody, convexBody, si, sj, justTest) { const worldVertex = planeConvex_v; const worldNormal = planeConvex_normal; worldNormal.set(0, 0, 1); planeQuat.vmult(worldNormal, worldNormal); let numContacts = 0; const relpos2 = planeConvex_relpos; for (let i = 0; i !== convexShape.vertices.length; i++) { worldVertex.copy(convexShape.vertices[i]); convexQuat.vmult(worldVertex, worldVertex); convexPosition.vadd(worldVertex, worldVertex); worldVertex.vsub(planePosition, relpos2); const dot = worldNormal.dot(relpos2); if (dot <= 0) { if (justTest) { return true; } const r = this.createContactEquation(planeBody, convexBody, planeShape, convexShape, si, sj); const projected = planeConvex_projected; worldNormal.scale(worldNormal.dot(relpos2), projected); worldVertex.vsub(projected, projected); projected.vsub(planePosition, r.ri); r.ni.copy(worldNormal); worldVertex.vsub(convexPosition, r.rj); r.ri.vadd(planePosition, r.ri); r.ri.vsub(planeBody.position, r.ri); r.rj.vadd(convexPosition, r.rj); r.rj.vsub(convexBody.position, r.rj); this.result.push(r); numContacts++; if (!this.enableFrictionReduction) { this.createFrictionEquationsFromContact(r, this.frictionResult); } } } if (this.enableFrictionReduction && numContacts) { this.createFrictionFromAverage(numContacts); } } boxConvex(si, sj, xi, xj, qi, qj, bi, bj, rsi, rsj, justTest) { si.convexPolyhedronRepresentation.material = si.material; si.convexPolyhedronRepresentation.collisionResponse = si.collisionResponse; return this.convexConvex(si.convexPolyhedronRepresentation, sj, xi, xj, qi, qj, bi, bj, si, sj, justTest); } sphereHeightfield(sphereShape, hfShape, spherePos, hfPos, sphereQuat, hfQuat, sphereBody, hfBody, rsi, rsj, justTest) { const data = hfShape.data; const radius = sphereShape.radius; const w = hfShape.elementSize; const worldPillarOffset2 = sphereHeightfield_tmp2; const localSpherePos = sphereHeightfield_tmp1; Transform.pointToLocalFrame(hfPos, hfQuat, spherePos, localSpherePos); let iMinX = Math.floor((localSpherePos.x - radius) / w) - 1; let iMaxX = Math.ceil((localSpherePos.x + radius) / w) + 1; let iMinY = Math.floor((localSpherePos.y - radius) / w) - 1; let iMaxY = Math.ceil((localSpherePos.y + radius) / w) + 1; if (iMaxX < 0 || iMaxY < 0 || iMinX > data.length || iMinY > data[0].length) { return; } if (iMinX < 0) { iMinX = 0; } if (iMaxX < 0) { iMaxX = 0; } if (iMinY < 0) { iMinY = 0; } if (iMaxY < 0) { iMaxY = 0; } if (iMinX >= data.length) { iMinX = data.length - 1; } if (iMaxX >= data.length) { iMaxX = data.length - 1; } if (iMaxY >= data[0].length) { iMaxY = data[0].length - 1; } if (iMinY >= data[0].length) { iMinY = data[0].length - 1; } const minMax = []; hfShape.getRectMinMax(iMinX, iMinY, iMaxX, iMaxY, minMax); const min = minMax[0]; const max = minMax[1]; if (localSpherePos.z - radius > max || localSpherePos.z + radius < min) { return; } const result = this.result; for (let i = iMinX; i < iMaxX; i++) { for (let j = iMinY; j < iMaxY; j++) { const numContactsBefore = result.length; let intersecting = false; hfShape.getConvexTrianglePillar(i, j, false); Transform.pointToWorldFrame(hfPos, hfQuat, hfShape.pillarOffset, worldPillarOffset2); if (spherePos.distanceTo(worldPillarOffset2) < hfShape.pillarConvex.boundingSphereRadius + sphereShape.boundingSphereRadius) { intersecting = this.sphereConvex(sphereShape, hfShape.pillarConvex, spherePos, worldPillarOffset2, sphereQuat, hfQuat, sphereBody, hfBody, sphereShape, hfShape, justTest); } if (justTest && intersecting) { return true; } hfShape.getConvexTrianglePillar(i, j, true); Transform.pointToWorldFrame(hfPos, hfQuat, hfShape.pillarOffset, worldPillarOffset2); if (spherePos.distanceTo(worldPillarOffset2) < hfShape.pillarConvex.boundingSphereRadius + sphereShape.boundingSphereRadius) { intersecting = this.sphereConvex(sphereShape, hfShape.pillarConvex, spherePos, worldPillarOffset2, sphereQuat, hfQuat, sphereBody, hfBody, sphereShape, hfShape, justTest); } if (justTest && intersecting) { return true; } const numContacts = result.length - numContactsBefore; if (numContacts > 2) { return; } } } } boxHeightfield(si, sj, xi, xj, qi, qj, bi, bj, rsi, rsj, justTest) { si.convexPolyhedronRepresentation.material = si.material; si.convexPolyhedronRepresentation.collisionResponse = si.collisionResponse; return this.convexHeightfield(si.convexPolyhedronRepresentation, sj, xi, xj, qi, qj, bi, bj, si, sj, justTest); } convexHeightfield(convexShape, hfShape, convexPos, hfPos, convexQuat, hfQuat, convexBody, hfBody, rsi, rsj, justTest) { const data = hfShape.data; const w = hfShape.elementSize; const radius = convexShape.boundingSphereRadius; const worldPillarOffset2 = convexHeightfield_tmp2; const faceList = convexHeightfield_faceList; const localConvexPos = convexHeightfield_tmp1; Transform.pointToLocalFrame(hfPos, hfQuat, convexPos, localConvexPos); let iMinX = Math.floor((localConvexPos.x - radius) / w) - 1; let iMaxX = Math.ceil((localConvexPos.x + radius) / w) + 1; let iMinY = Math.floor((localConvexPos.y - radius) / w) - 1; let iMaxY = Math.ceil((localConvexPos.y + radius) / w) + 1; if (iMaxX < 0 || iMaxY < 0 || iMinX > data.length || iMinY > data[0].length) { return; } if (iMinX < 0) { iMinX = 0; } if (iMaxX < 0) { iMaxX = 0; } if (iMinY < 0) { iMinY = 0; } if (iMaxY < 0) { iMaxY = 0; } if (iMinX >= data.length) { iMinX = data.length - 1; } if (iMaxX >= data.length) { iMaxX = data.length - 1; } if (iMaxY >= data[0].length) { iMaxY = data[0].length - 1; } if (iMinY >= data[0].length) { iMinY = data[0].length - 1; } const minMax = []; hfShape.getRectMinMax(iMinX, iMinY, iMaxX, iMaxY, minMax); const min = minMax[0]; const max = minMax[1]; if (localConvexPos.z - radius > max || localConvexPos.z + radius < min) { return; } for (let i = iMinX; i < iMaxX; i++) { for (let j = iMinY; j < iMaxY; j++) { let intersecting = false; hfShape.getConvexTrianglePillar(i, j, false); Transform.pointToWorldFrame(hfPos, hfQuat, hfShape.pillarOffset, worldPillarOffset2); if (convexPos.distanceTo(worldPillarOffset2) < hfShape.pillarConvex.boundingSphereRadius + convexShape.boundingSphereRadius) { intersecting = this.convexConvex(convexShape, hfShape.pillarConvex, convexPos, worldPillarOffset2, convexQuat, hfQuat, convexBody, hfBody, null, null, justTest, faceList, null); } if (justTest && intersecting) { return true; } hfShape.getConvexTrianglePillar(i, j, true); Transform.pointToWorldFrame(hfPos, hfQuat, hfShape.pillarOffset, worldPillarOffset2); if (convexPos.distanceTo(worldPillarOffset2) < hfShape.pillarConvex.boundingSphereRadius + convexShape.boundingSphereRadius) { intersecting = this.convexConvex(convexShape, hfShape.pillarConvex, convexPos, worldPillarOffset2, convexQuat, hfQuat, convexBody, hfBody, null, null, justTest, faceList, null); } if (justTest && intersecting) { return true; } } } } sphereParticle(sj, si, xj, xi, qj, qi, bj, bi, rsi, rsj, justTest) { const normal = particleSphere_normal; normal.set(0, 0, 1); xi.vsub(xj, normal); const lengthSquared = normal.lengthSquared(); if (lengthSquared <= sj.radius * sj.radius) { if (justTest) { return true; } const r = this.createContactEquation(bi, bj, si, sj, rsi, rsj); normal.normalize(); r.rj.copy(normal); r.rj.scale(sj.radius, r.rj); r.ni.copy(normal); r.ni.negate(r.ni); r.ri.set(0, 0, 0); this.result.push(r); this.createFrictionEquationsFromContact(r, this.frictionResult); } } planeParticle(sj, si, xj, xi, qj, qi, bj, bi, rsi, rsj, justTest) { const normal = particlePlane_normal; normal.set(0, 0, 1); bj.quaternion.vmult(normal, normal); const relpos2 = particlePlane_relpos; xi.vsub(bj.position, relpos2); const dot = normal.dot(relpos2); if (dot <= 0) { if (justTest) { return true; } const r = this.createContactEquation(bi, bj, si, sj, rsi, rsj); r.ni.copy(normal); r.ni.negate(r.ni); r.ri.set(0, 0, 0); const projected = particlePlane_projected; normal.scale(normal.dot(xi), projected); xi.vsub(projected, projected); r.rj.copy(projected); this.result.push(r); this.createFrictionEquationsFromContact(r, this.frictionResult); } } boxParticle(si, sj, xi, xj, qi, qj, bi, bj, rsi, rsj, justTest) { si.convexPolyhedronRepresentation.material = si.material; si.convexPolyhedronRepresentation.collisionResponse = si.collisionResponse; return this.convexParticle(si.convexPolyhedronRepresentation, sj, xi, xj, qi, qj, bi, bj, si, sj, justTest); } convexParticle(sj, si, xj, xi, qj, qi, bj, bi, rsi, rsj, justTest) { let penetratedFaceIndex = -1; const penetratedFaceNormal = convexParticle_penetratedFaceNormal; const worldPenetrationVec = convexParticle_worldPenetrationVec; let minPenetration = null; const local = convexParticle_local; local.copy(xi); local.vsub(xj, local); qj.conjugate(cqj); cqj.vmult(local, local); if (sj.pointIsInside(local)) { if (sj.worldVerticesNeedsUpdate) { sj.computeWorldVertices(xj, qj); } if (sj.worldFaceNormalsNeedsUpdate) { sj.computeWorldFaceNormals(qj); } for (let i = 0, nfaces = sj.faces.length; i !== nfaces; i++) { const verts = [sj.worldVertices[sj.faces[i][0]]]; const normal = sj.worldFaceNormals[i]; xi.vsub(verts[0], convexParticle_vertexToParticle); const penetration = -normal.dot(convexParticle_vertexToParticle); if (minPenetration === null || Math.abs(penetration) < Math.abs(minPenetration)) { if (justTest) { return true; } minPenetration = penetration; penetratedFaceIndex = i; penetratedFaceNormal.copy(normal); } } if (penetratedFaceIndex !== -1) { const r = this.createContactEquation(bi, bj, si, sj, rsi, rsj); penetratedFaceNormal.scale(minPenetration, worldPenetrationVec); worldPenetrationVec.vadd(xi, worldPenetrationVec); worldPenetrationVec.vsub(xj, worldPenetrationVec); r.rj.copy(worldPenetrationVec); penetratedFaceNormal.negate(r.ni); r.ri.set(0, 0, 0); const ri = r.ri; const rj = r.rj; ri.vadd(xi, ri); ri.vsub(bi.position, ri); rj.vadd(xj, rj); rj.vsub(bj.position, rj); this.result.push(r); this.createFrictionEquationsFromContact(r, this.frictionResult); } else { console.warn("Point found inside convex, but did not find penetrating face!"); } } } heightfieldCylinder(hfShape, convexShape, hfPos, convexPos, hfQuat, convexQuat, hfBody, convexBody, rsi, rsj, justTest) { return this.convexHeightfield(convexShape, hfShape, convexPos, hfPos, convexQuat, hfQuat, convexBody, hfBody, rsi, rsj, justTest); } particleCylinder(si, sj, xi, xj, qi, qj, bi, bj, rsi, rsj, justTest) { return this.convexParticle(sj, si, xj, xi, qj, qi, bj, bi, rsi, rsj, justTest); } sphereTrimesh(sphereShape, trimeshShape, spherePos, trimeshPos, sphereQuat, trimeshQuat, sphereBody, trimeshBody, rsi, rsj, justTest) { const edgeVertexA = sphereTrimesh_edgeVertexA; const edgeVertexB = sphereTrimesh_edgeVertexB; const edgeVector = sphereTrimesh_edgeVector; const edgeVectorUnit = sphereTrimesh_edgeVectorUnit; const localSpherePos = sphereTrimesh_localSpherePos; const tmp2 = sphereTrimesh_tmp; const localSphereAABB = sphereTrimesh_localSphereAABB; const v22 = sphereTrimesh_v2; const relpos2 = sphereTrimesh_relpos; const triangles = sphereTrimesh_triangles; Transform.pointToLocalFrame(trimeshPos, trimeshQuat, spherePos, localSpherePos); const sphereRadius = sphereShape.radius; localSphereAABB.lowerBound.set(localSpherePos.x - sphereRadius, localSpherePos.y - sphereRadius, localSpherePos.z - sphereRadius); localSphereAABB.upperBound.set(localSpherePos.x + sphereRadius, localSpherePos.y + sphereRadius, localSpherePos.z + sphereRadius); trimeshShape.getTrianglesInAABB(localSphereAABB, triangles); const v = sphereTrimesh_v; const radiusSquared = sphereShape.radius * sphereShape.radius; for (let i = 0; i < triangles.length; i++) { for (let j = 0; j < 3; j++) { trimeshShape.getVertex(trimeshShape.indices[triangles[i] * 3 + j], v); v.vsub(localSpherePos, relpos2); if (relpos2.lengthSquared() <= radiusSquared) { v22.copy(v); Transform.pointToWorldFrame(trimeshPos, trimeshQuat, v22, v); v.vsub(spherePos, relpos2); if (justTest) { return true; } let r = this.createContactEquation(sphereBody, trimeshBody, sphereShape, trimeshShape, rsi, rsj); r.ni.copy(relpos2); r.ni.normalize(); r.ri.copy(r.ni); r.ri.scale(sphereShape.radius, r.ri); r.ri.vadd(spherePos, r.ri); r.ri.vsub(sphereBody.position, r.ri); r.rj.copy(v); r.rj.vsub(trimeshBody.position, r.rj); this.result.push(r); this.createFrictionEquationsFromContact(r, this.frictionResult); } } } for (let i = 0; i < triangles.length; i++) { for (let j = 0; j < 3; j++) { trimeshShape.getVertex(trimeshShape.indices[triangles[i] * 3 + j], edgeVertexA); trimeshShape.getVertex(trimeshShape.indices[triangles[i] * 3 + (j + 1) % 3], edgeVertexB); edgeVertexB.vsub(edgeVertexA, edgeVector); localSpherePos.vsub(edgeVertexB, tmp2); const positionAlongEdgeB = tmp2.dot(edgeVector); localSpherePos.vsub(edgeVertexA, tmp2); let positionAlongEdgeA = tmp2.dot(edgeVector); if (positionAlongEdgeA > 0 && positionAlongEdgeB < 0) { localSpherePos.vsub(edgeVertexA, tmp2); edgeVectorUnit.copy(edgeVector); edgeVectorUnit.normalize(); positionAlongEdgeA = tmp2.dot(edgeVectorUnit); edgeVectorUnit.scale(positionAlongEdgeA, tmp2); tmp2.vadd(edgeVertexA, tmp2); const dist = tmp2.distanceTo(localSpherePos); if (dist < sphereShape.radius) { if (justTest) { return true; } const r = this.createContactEquation(sphereBody, trimeshBody, sphereShape, trimeshShape, rsi, rsj); tmp2.vsub(localSpherePos, r.ni); r.ni.normalize(); r.ni.scale(sphereShape.radius, r.ri); r.ri.vadd(spherePos, r.ri); r.ri.vsub(sphereBody.position, r.ri); Transform.pointToWorldFrame(trimeshPos, trimeshQuat, tmp2, tmp2); tmp2.vsub(trimeshBody.position, r.rj); Transform.vectorToWorldFrame(trimeshQuat, r.ni, r.ni); Transform.vectorToWorldFrame(trimeshQuat, r.ri, r.ri); this.result.push(r); this.createFrictionEquationsFromContact(r, this.frictionResult); } } } } const va2 = sphereTrimesh_va; const vb2 = sphereTrimesh_vb; const vc2 = sphereTrimesh_vc; const normal = sphereTrimesh_normal; for (let i = 0, N = triangles.length; i !== N; i++) { trimeshShape.getTriangleVertices(triangles[i], va2, vb2, vc2); trimeshShape.getNormal(triangles[i], normal); localSpherePos.vsub(va2, tmp2); let dist = tmp2.dot(normal); normal.scale(dist, tmp2); localSpherePos.vsub(tmp2, tmp2); dist = tmp2.distanceTo(localSpherePos); if (Ray.pointInTriangle(tmp2, va2, vb2, vc2) && dist < sphereShape.radius) { if (justTest) { return true; } let r = this.createContactEquation(sphereBody, trimeshBody, sphereShape, trimeshShape, rsi, rsj); tmp2.vsub(localSpherePos, r.ni); r.ni.normalize(); r.ni.scale(sphereShape.radius, r.ri); r.ri.vadd(spherePos, r.ri); r.ri.vsub(sphereBody.position, r.ri); Transform.pointToWorldFrame(trimeshPos, trimeshQuat, tmp2, tmp2); tmp2.vsub(trimeshBody.position, r.rj); Transform.vectorToWorldFrame(trimeshQuat, r.ni, r.ni); Transform.vectorToWorldFrame(trimeshQuat, r.ri, r.ri); this.result.push(r); this.createFrictionEquationsFromContact(r, this.frictionResult); } } triangles.length = 0; } planeTrimesh(planeShape, trimeshShape, planePos, trimeshPos, planeQuat, trimeshQuat, planeBody, trimeshBody, rsi, rsj, justTest) { const v = new Vec3(); const normal = planeTrimesh_normal; normal.set(0, 0, 1); planeQuat.vmult(normal, normal); for (let i = 0; i < trimeshShape.vertices.length / 3; i++) { trimeshShape.getVertex(i, v); const v22 = new Vec3(); v22.copy(v); Transform.pointToWorldFrame(trimeshPos, trimeshQuat, v22, v); const relpos2 = planeTrimesh_relpos; v.vsub(planePos, relpos2); const dot = normal.dot(relpos2); if (dot <= 0) { if (justTest) { return true; } const r = this.createContactEquation(planeBody, trimeshBody, planeShape, trimeshShape, rsi, rsj); r.ni.copy(normal); const projected = planeTrimesh_projected; normal.scale(relpos2.dot(normal), projected); v.vsub(projected, projected); r.ri.copy(projected); r.ri.vsub(planeBody.position, r.ri); r.rj.copy(v); r.rj.vsub(trimeshBody.position, r.rj); this.result.push(r); this.createFrictionEquationsFromContact(r, this.frictionResult); } } } } const averageNormal = new Vec3(); const averageContactPointA = new Vec3(); const averageContactPointB = new Vec3(); const tmpVec1 = new Vec3(); const tmpVec2 = new Vec3(); const tmpQuat1 = new Quaternion(); const tmpQuat2 = new Quaternion(); const planeTrimesh_normal = new Vec3(); const planeTrimesh_relpos = new Vec3(); const planeTrimesh_projected = new Vec3(); const sphereTrimesh_normal = new Vec3(); const sphereTrimesh_relpos = new Vec3(); new Vec3(); const sphereTrimesh_v = new Vec3(); const sphereTrimesh_v2 = new Vec3(); const sphereTrimesh_edgeVertexA = new Vec3(); const sphereTrimesh_edgeVertexB = new Vec3(); const sphereTrimesh_edgeVector = new Vec3(); const sphereTrimesh_edgeVectorUnit = new Vec3(); const sphereTrimesh_localSpherePos = new Vec3(); const sphereTrimesh_tmp = new Vec3(); const sphereTrimesh_va = new Vec3(); const sphereTrimesh_vb = new Vec3(); const sphereTrimesh_vc = new Vec3(); const sphereTrimesh_localSphereAABB = new AABB(); const sphereTrimesh_triangles = []; const point_on_plane_to_sphere = new Vec3(); const plane_to_sphere_ortho = new Vec3(); const pointInPolygon_edge = new Vec3(); const pointInPolygon_edge_x_normal = new Vec3(); const pointInPolygon_vtp = new Vec3(); function pointInPolygon(verts, normal, p) { let positiveResult = null; const N = verts.length; for (let i = 0; i !== N; i++) { const v = verts[i]; const edge = pointInPolygon_edge; verts[(i + 1) % N].vsub(v, edge); const edge_x_normal = pointInPolygon_edge_x_normal; edge.cross(normal, edge_x_normal); const vertex_to_p = pointInPolygon_vtp; p.vsub(v, vertex_to_p); const r = edge_x_normal.dot(vertex_to_p); if (positiveResult === null || r > 0 && positiveResult === true || r <= 0 && positiveResult === false) { if (positiveResult === null) { positiveResult = r > 0; } continue; } else { return false; } } return true; } const box_to_sphere = new Vec3(); const sphereBox_ns = new Vec3(); const sphereBox_ns1 = new Vec3(); const sphereBox_ns2 = new Vec3(); const sphereBox_sides = [new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3()]; const sphereBox_sphere_to_corner = new Vec3(); const sphereBox_side_ns = new Vec3(); const sphereBox_side_ns1 = new Vec3(); const sphereBox_side_ns2 = new Vec3(); const convex_to_sphere = new Vec3(); const sphereConvex_edge = new Vec3(); const sphereConvex_edgeUnit = new Vec3(); const sphereConvex_sphereToCorner = new Vec3(); const sphereConvex_worldCorner = new Vec3(); const sphereConvex_worldNormal = new Vec3(); const sphereConvex_worldPoint = new Vec3(); const sphereConvex_worldSpherePointClosestToPlane = new Vec3(); const sphereConvex_penetrationVec = new Vec3(); const sphereConvex_sphereToWorldPoint = new Vec3(); new Vec3(); new Vec3(); const planeConvex_v = new Vec3(); const planeConvex_normal = new Vec3(); const planeConvex_relpos = new Vec3(); const planeConvex_projected = new Vec3(); const convexConvex_sepAxis = new Vec3(); const convexConvex_q = new Vec3(); const particlePlane_normal = new Vec3(); const particlePlane_relpos = new Vec3(); const particlePlane_projected = new Vec3(); const particleSphere_normal = new Vec3(); const cqj = new Quaternion(); const convexParticle_local = new Vec3(); new Vec3(); const convexParticle_penetratedFaceNormal = new Vec3(); const convexParticle_vertexToParticle = new Vec3(); const convexParticle_worldPenetrationVec = new Vec3(); const convexHeightfield_tmp1 = new Vec3(); const convexHeightfield_tmp2 = new Vec3(); const convexHeightfield_faceList = [0]; const sphereHeightfield_tmp1 = new Vec3(); const sphereHeightfield_tmp2 = new Vec3(); class OverlapKeeper { constructor() { this.current = []; this.previous = []; } getKey(i, j) { if (j < i) { const temp = j; j = i; i = temp; } return i << 16 | j; } set(i, j) { const key = this.getKey(i, j); const current = this.current; let index = 0; while (key > current[index]) { index++; } if (key === current[index]) { return; } for (let j2 = current.length - 1; j2 >= index; j2--) { current[j2 + 1] = current[j2]; } current[index] = key; } tick() { const tmp2 = this.current; this.current = this.previous; this.previous = tmp2; this.current.length = 0; } getDiff(additions2, removals2) { const a2 = this.current; const b2 = this.previous; const al = a2.length; const bl = b2.length; let j = 0; for (let i = 0; i < al; i++) { let found = false; const keyA = a2[i]; while (keyA > b2[j]) { j++; } found = keyA === b2[j]; if (!found) { unpackAndPush(additions2, keyA); } } j = 0; for (let i = 0; i < bl; i++) { let found = false; const keyB = b2[i]; while (keyB > a2[j]) { j++; } found = a2[j] === keyB; if (!found) { unpackAndPush(removals2, keyB); } } } } function unpackAndPush(array, key) { array.push((key & 4294901760) >> 16, key & 65535); } const getKey = (i, j) => i < j ? `${i}-${j}` : `${j}-${i}`; class TupleDictionary { constructor() { this.data = { keys: [] }; } get(i, j) { const key = getKey(i, j); return this.data[key]; } set(i, j, value) { const key = getKey(i, j); if (!this.get(i, j)) { this.data.keys.push(key); } this.data[key] = value; } delete(i, j) { const key = getKey(i, j); const index = this.data.keys.indexOf(key); if (index !== -1) { this.data.keys.splice(index, 1); } delete this.data[key]; } reset() { const data = this.data; const keys = data.keys; while (keys.length > 0) { const key = keys.pop(); delete data[key]; } } } class World extends EventTarget { constructor(options) { if (options === void 0) { options = {}; } super(); this.dt = -1; this.allowSleep = !!options.allowSleep; this.contacts = []; this.frictionEquations = []; this.quatNormalizeSkip = options.quatNormalizeSkip !== void 0 ? options.quatNormalizeSkip : 0; this.quatNormalizeFast = options.quatNormalizeFast !== void 0 ? options.quatNormalizeFast : false; this.time = 0; this.stepnumber = 0; this.default_dt = 1 / 60; this.nextId = 0; this.gravity = new Vec3(); if (options.gravity) { this.gravity.copy(options.gravity); } this.broadphase = options.broadphase !== void 0 ? options.broadphase : new NaiveBroadphase(); this.bodies = []; this.hasActiveBodies = false; this.solver = options.solver !== void 0 ? options.solver : new GSSolver(); this.constraints = []; this.narrowphase = new Narrowphase(this); this.collisionMatrix = new ArrayCollisionMatrix(); this.collisionMatrixPrevious = new ArrayCollisionMatrix(); this.bodyOverlapKeeper = new OverlapKeeper(); this.shapeOverlapKeeper = new OverlapKeeper(); this.contactmaterials = []; this.contactMaterialTable = new TupleDictionary(); this.defaultMaterial = new Material("default"); this.defaultContactMaterial = new ContactMaterial(this.defaultMaterial, this.defaultMaterial, { friction: 0.3, restitution: 0 }); this.doProfiling = false; this.profile = { solve: 0, makeContactConstraints: 0, broadphase: 0, integrate: 0, narrowphase: 0 }; this.accumulator = 0; this.subsystems = []; this.addBodyEvent = { type: "addBody", body: null }; this.removeBodyEvent = { type: "removeBody", body: null }; this.idToBodyMap = {}; this.broadphase.setWorld(this); } getContactMaterial(m1, m2) { return this.contactMaterialTable.get(m1.id, m2.id); } collisionMatrixTick() { const temp = this.collisionMatrixPrevious; this.collisionMatrixPrevious = this.collisionMatrix; this.collisionMatrix = temp; this.collisionMatrix.reset(); this.bodyOverlapKeeper.tick(); this.shapeOverlapKeeper.tick(); } addConstraint(c2) { this.constraints.push(c2); } removeConstraint(c2) { const idx = this.constraints.indexOf(c2); if (idx !== -1) { this.constraints.splice(idx, 1); } } rayTest(from, to, result) { if (result instanceof RaycastResult) { this.raycastClosest(from, to, { skipBackfaces: true }, result); } else { this.raycastAll(from, to, { skipBackfaces: true }, result); } } raycastAll(from, to, options, callback) { if (options === void 0) { options = {}; } options.mode = Ray.ALL; options.from = from; options.to = to; options.callback = callback; return tmpRay.intersectWorld(this, options); } raycastAny(from, to, options, result) { if (options === void 0) { options = {}; } options.mode = Ray.ANY; options.from = from; options.to = to; options.result = result; return tmpRay.intersectWorld(this, options); } raycastClosest(from, to, options, result) { if (options === void 0) { options = {}; } options.mode = Ray.CLOSEST; options.from = from; options.to = to; options.result = result; return tmpRay.intersectWorld(this, options); } addBody(body) { if (this.bodies.includes(body)) { return; } body.index = this.bodies.length; this.bodies.push(body); body.world = this; body.initPosition.copy(body.position); body.initVelocity.copy(body.velocity); body.timeLastSleepy = this.time; if (body instanceof Body) { body.initAngularVelocity.copy(body.angularVelocity); body.initQuaternion.copy(body.quaternion); } this.collisionMatrix.setNumObjects(this.bodies.length); this.addBodyEvent.body = body; this.idToBodyMap[body.id] = body; this.dispatchEvent(this.addBodyEvent); } removeBody(body) { body.world = null; const n = this.bodies.length - 1; const bodies = this.bodies; const idx = bodies.indexOf(body); if (idx !== -1) { bodies.splice(idx, 1); for (let i = 0; i !== bodies.length; i++) { bodies[i].index = i; } this.collisionMatrix.setNumObjects(n); this.removeBodyEvent.body = body; delete this.idToBodyMap[body.id]; this.dispatchEvent(this.removeBodyEvent); } } getBodyById(id) { return this.idToBodyMap[id]; } getShapeById(id) { const bodies = this.bodies; for (let i = 0; i < bodies.length; i++) { const shapes = bodies[i].shapes; for (let j = 0; j < shapes.length; j++) { const shape = shapes[j]; if (shape.id === id) { return shape; } } } return null; } addContactMaterial(cmat) { this.contactmaterials.push(cmat); this.contactMaterialTable.set(cmat.materials[0].id, cmat.materials[1].id, cmat); } removeContactMaterial(cmat) { const idx = this.contactmaterials.indexOf(cmat); if (idx === -1) { return; } this.contactmaterials.splice(idx, 1); this.contactMaterialTable.delete(cmat.materials[0].id, cmat.materials[1].id); } fixedStep(dt, maxSubSteps) { if (dt === void 0) { dt = 1 / 60; } if (maxSubSteps === void 0) { maxSubSteps = 10; } const time = performance.now() / 1e3; if (!this.lastCallTime) { this.step(dt, void 0, maxSubSteps); } else { const timeSinceLastCalled = time - this.lastCallTime; this.step(dt, timeSinceLastCalled, maxSubSteps); } this.lastCallTime = time; } step(dt, timeSinceLastCalled, maxSubSteps) { if (maxSubSteps === void 0) { maxSubSteps = 10; } if (timeSinceLastCalled === void 0) { this.internalStep(dt); this.time += dt; } else { this.accumulator += timeSinceLastCalled; const t0 = performance.now(); let substeps = 0; while (this.accumulator >= dt && substeps < maxSubSteps) { this.internalStep(dt); this.accumulator -= dt; substeps++; if (performance.now() - t0 > dt * 1e3) { break; } } this.accumulator = this.accumulator % dt; const t = this.accumulator / dt; for (let j = 0; j !== this.bodies.length; j++) { const b2 = this.bodies[j]; b2.previousPosition.lerp(b2.position, t, b2.interpolatedPosition); b2.previousQuaternion.slerp(b2.quaternion, t, b2.interpolatedQuaternion); b2.previousQuaternion.normalize(); } this.time += timeSinceLastCalled; } } internalStep(dt) { this.dt = dt; const contacts = this.contacts; const p1 = World_step_p1; const p2 = World_step_p2; const N = this.bodies.length; const bodies = this.bodies; const solver = this.solver; const gravity = this.gravity; const doProfiling = this.doProfiling; const profile = this.profile; const DYNAMIC = Body.DYNAMIC; let profilingStart = -Infinity; const constraints = this.constraints; const frictionEquationPool = World_step_frictionEquationPool; gravity.length(); const gx = gravity.x; const gy = gravity.y; const gz = gravity.z; let i = 0; if (doProfiling) { profilingStart = performance.now(); } for (i = 0; i !== N; i++) { const bi = bodies[i]; if (bi.type === DYNAMIC) { const f = bi.force; const m = bi.mass; f.x += m * gx; f.y += m * gy; f.z += m * gz; } } for (let i2 = 0, Nsubsystems = this.subsystems.length; i2 !== Nsubsystems; i2++) { this.subsystems[i2].update(); } if (doProfiling) { profilingStart = performance.now(); } p1.length = 0; p2.length = 0; this.broadphase.collisionPairs(this, p1, p2); if (doProfiling) { profile.broadphase = performance.now() - profilingStart; } let Nconstraints = constraints.length; for (i = 0; i !== Nconstraints; i++) { const c2 = constraints[i]; if (!c2.collideConnected) { for (let j = p1.length - 1; j >= 0; j -= 1) { if (c2.bodyA === p1[j] && c2.bodyB === p2[j] || c2.bodyB === p1[j] && c2.bodyA === p2[j]) { p1.splice(j, 1); p2.splice(j, 1); } } } } this.collisionMatrixTick(); if (doProfiling) { profilingStart = performance.now(); } const oldcontacts = World_step_oldContacts; const NoldContacts = contacts.length; for (i = 0; i !== NoldContacts; i++) { oldcontacts.push(contacts[i]); } contacts.length = 0; const NoldFrictionEquations = this.frictionEquations.length; for (i = 0; i !== NoldFrictionEquations; i++) { frictionEquationPool.push(this.frictionEquations[i]); } this.frictionEquations.length = 0; this.narrowphase.getContacts(p1, p2, this, contacts, oldcontacts, this.frictionEquations, frictionEquationPool); if (doProfiling) { profile.narrowphase = performance.now() - profilingStart; } if (doProfiling) { profilingStart = performance.now(); } for (i = 0; i < this.frictionEquations.length; i++) { solver.addEquation(this.frictionEquations[i]); } const ncontacts = contacts.length; for (let k = 0; k !== ncontacts; k++) { const c2 = contacts[k]; const bi = c2.bi; const bj = c2.bj; const si = c2.si; const sj = c2.sj; let cm; if (bi.material && bj.material) { cm = this.getContactMaterial(bi.material, bj.material) || this.defaultContactMaterial; } else { cm = this.defaultContactMaterial; } cm.friction; if (bi.material && bj.material) { if (bi.material.friction >= 0 && bj.material.friction >= 0) { bi.material.friction * bj.material.friction; } if (bi.material.restitution >= 0 && bj.material.restitution >= 0) { c2.restitution = bi.material.restitution * bj.material.restitution; } } solver.addEquation(c2); if (bi.allowSleep && bi.type === Body.DYNAMIC && bi.sleepState === Body.SLEEPING && bj.sleepState === Body.AWAKE && bj.type !== Body.STATIC) { const speedSquaredB = bj.velocity.lengthSquared() + bj.angularVelocity.lengthSquared(); const speedLimitSquaredB = bj.sleepSpeedLimit ** 2; if (speedSquaredB >= speedLimitSquaredB * 2) { bi.wakeUpAfterNarrowphase = true; } } if (bj.allowSleep && bj.type === Body.DYNAMIC && bj.sleepState === Body.SLEEPING && bi.sleepState === Body.AWAKE && bi.type !== Body.STATIC) { const speedSquaredA = bi.velocity.lengthSquared() + bi.angularVelocity.lengthSquared(); const speedLimitSquaredA = bi.sleepSpeedLimit ** 2; if (speedSquaredA >= speedLimitSquaredA * 2) { bj.wakeUpAfterNarrowphase = true; } } this.collisionMatrix.set(bi, bj, true); if (!this.collisionMatrixPrevious.get(bi, bj)) { World_step_collideEvent.body = bj; World_step_collideEvent.contact = c2; bi.dispatchEvent(World_step_collideEvent); World_step_collideEvent.body = bi; bj.dispatchEvent(World_step_collideEvent); } this.bodyOverlapKeeper.set(bi.id, bj.id); this.shapeOverlapKeeper.set(si.id, sj.id); } this.emitContactEvents(); if (doProfiling) { profile.makeContactConstraints = performance.now() - profilingStart; profilingStart = performance.now(); } for (i = 0; i !== N; i++) { const bi = bodies[i]; if (bi.wakeUpAfterNarrowphase) { bi.wakeUp(); bi.wakeUpAfterNarrowphase = false; } } Nconstraints = constraints.length; for (i = 0; i !== Nconstraints; i++) { const c2 = constraints[i]; c2.update(); for (let j = 0, Neq = c2.equations.length; j !== Neq; j++) { const eq = c2.equations[j]; solver.addEquation(eq); } } solver.solve(dt, this); if (doProfiling) { profile.solve = performance.now() - profilingStart; } solver.removeAllEquations(); const pow = Math.pow; for (i = 0; i !== N; i++) { const bi = bodies[i]; if (bi.type & DYNAMIC) { const ld = pow(1 - bi.linearDamping, dt); const v = bi.velocity; v.scale(ld, v); const av = bi.angularVelocity; if (av) { const ad = pow(1 - bi.angularDamping, dt); av.scale(ad, av); } } } this.dispatchEvent(World_step_preStepEvent); if (doProfiling) { profilingStart = performance.now(); } const stepnumber = this.stepnumber; const quatNormalize = stepnumber % (this.quatNormalizeSkip + 1) === 0; const quatNormalizeFast = this.quatNormalizeFast; for (i = 0; i !== N; i++) { bodies[i].integrate(dt, quatNormalize, quatNormalizeFast); } this.clearForces(); this.broadphase.dirty = true; if (doProfiling) { profile.integrate = performance.now() - profilingStart; } this.stepnumber += 1; this.dispatchEvent(World_step_postStepEvent); let hasActiveBodies = true; if (this.allowSleep) { hasActiveBodies = false; for (i = 0; i !== N; i++) { const bi = bodies[i]; bi.sleepTick(this.time); if (bi.sleepState !== Body.SLEEPING) { hasActiveBodies = true; } } } this.hasActiveBodies = hasActiveBodies; } emitContactEvents() { const hasBeginContact = this.hasAnyEventListener("beginContact"); const hasEndContact = this.hasAnyEventListener("endContact"); if (hasBeginContact || hasEndContact) { this.bodyOverlapKeeper.getDiff(additions, removals); } if (hasBeginContact) { for (let i = 0, l = additions.length; i < l; i += 2) { beginContactEvent.bodyA = this.getBodyById(additions[i]); beginContactEvent.bodyB = this.getBodyById(additions[i + 1]); this.dispatchEvent(beginContactEvent); } beginContactEvent.bodyA = beginContactEvent.bodyB = null; } if (hasEndContact) { for (let i = 0, l = removals.length; i < l; i += 2) { endContactEvent.bodyA = this.getBodyById(removals[i]); endContactEvent.bodyB = this.getBodyById(removals[i + 1]); this.dispatchEvent(endContactEvent); } endContactEvent.bodyA = endContactEvent.bodyB = null; } additions.length = removals.length = 0; const hasBeginShapeContact = this.hasAnyEventListener("beginShapeContact"); const hasEndShapeContact = this.hasAnyEventListener("endShapeContact"); if (hasBeginShapeContact || hasEndShapeContact) { this.shapeOverlapKeeper.getDiff(additions, removals); } if (hasBeginShapeContact) { for (let i = 0, l = additions.length; i < l; i += 2) { const shapeA = this.getShapeById(additions[i]); const shapeB = this.getShapeById(additions[i + 1]); beginShapeContactEvent.shapeA = shapeA; beginShapeContactEvent.shapeB = shapeB; if (shapeA) beginShapeContactEvent.bodyA = shapeA.body; if (shapeB) beginShapeContactEvent.bodyB = shapeB.body; this.dispatchEvent(beginShapeContactEvent); } beginShapeContactEvent.bodyA = beginShapeContactEvent.bodyB = beginShapeContactEvent.shapeA = beginShapeContactEvent.shapeB = null; } if (hasEndShapeContact) { for (let i = 0, l = removals.length; i < l; i += 2) { const shapeA = this.getShapeById(removals[i]); const shapeB = this.getShapeById(removals[i + 1]); endShapeContactEvent.shapeA = shapeA; endShapeContactEvent.shapeB = shapeB; if (shapeA) endShapeContactEvent.bodyA = shapeA.body; if (shapeB) endShapeContactEvent.bodyB = shapeB.body; this.dispatchEvent(endShapeContactEvent); } endShapeContactEvent.bodyA = endShapeContactEvent.bodyB = endShapeContactEvent.shapeA = endShapeContactEvent.shapeB = null; } } clearForces() { const bodies = this.bodies; const N = bodies.length; for (let i = 0; i !== N; i++) { const b2 = bodies[i]; b2.force; b2.torque; b2.force.set(0, 0, 0); b2.torque.set(0, 0, 0); } } } new AABB(); const tmpRay = new Ray(); const performance = globalThis.performance || {}; if (!performance.now) { let nowOffset = Date.now(); if (performance.timing && performance.timing.navigationStart) { nowOffset = performance.timing.navigationStart; } performance.now = () => Date.now() - nowOffset; } new Vec3(); const World_step_postStepEvent = { type: "postStep" }; const World_step_preStepEvent = { type: "preStep" }; const World_step_collideEvent = { type: Body.COLLIDE_EVENT_NAME, body: null, contact: null }; const World_step_oldContacts = []; const World_step_frictionEquationPool = []; const World_step_p1 = []; const World_step_p2 = []; const additions = []; const removals = []; const beginContactEvent = { type: "beginContact", bodyA: null, bodyB: null }; const endContactEvent = { type: "endContact", bodyA: null, bodyB: null }; const beginShapeContactEvent = { type: "beginShapeContact", bodyA: null, bodyB: null, shapeA: null, shapeB: null }; const endShapeContactEvent = { type: "endShapeContact", bodyA: null, bodyB: null, shapeA: null, shapeB: null }; export { AABB, ArrayCollisionMatrix, BODY_SLEEP_STATES, BODY_TYPES, Body, Box, Broadphase, COLLISION_TYPES, ContactEquation, ContactMaterial, ConvexPolyhedron, Cylinder, Equation, EventTarget, FrictionEquation, GSSolver, JacobianElement, Mat3, Material, NaiveBroadphase, Narrowphase, Pool, Quaternion, RAY_MODES, Ray, RaycastResult, SHAPE_TYPES, Shape, Solver, Sphere, SplitSolver, Transform, Vec3, Vec3Pool, World }; //# sourceMappingURL=cannon-es.mjs.map