diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 100% rename from .eslintrc.js rename to .eslintrc.cjs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3603a39e..11516e25 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: - node-version: '14' + node-version: '18' - name: npm-install run: npm install diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 00000000..aaca5dc6 --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,6 @@ +{ + "node-option": [ + "experimental-specifier-resolution=node", + "loader=ts-node/esm" + ] +} \ No newline at end of file diff --git a/build-utils/declarationTransformer.js b/build-utils/declarationTransformer.cjs similarity index 94% rename from build-utils/declarationTransformer.js rename to build-utils/declarationTransformer.cjs index fc294be9..4435264e 100644 --- a/build-utils/declarationTransformer.js +++ b/build-utils/declarationTransformer.cjs @@ -1,6 +1,6 @@ -import ts from 'typescript'; +const ts = require('typescript'); -export default options => context => node => { +module.exports = options => context => node => { return ts.visitEachChild(node, (node) => visitor(context.factory, node, options), context); } @@ -9,10 +9,10 @@ function visitor(factory, node, options) { !isClassIncluded(node.name.escapedText, options)) { return node; } - if (isClassDerived(node)) { - // workaround for https://github.com/microsoft/TypeScript/issues/46503 - return splitClassIntoConstAndInterface(factory, node); - } + // if (isClassDerived(node)) { + // // workaround for https://github.com/microsoft/TypeScript/issues/46503 + // return splitClassIntoConstAndInterface(factory, node); + // } return createNodeWithFactories(factory, node); } diff --git a/build-utils/license.js b/build-utils/license.cjs similarity index 98% rename from build-utils/license.js rename to build-utils/license.cjs index 83066aa4..35ea604c 100644 --- a/build-utils/license.js +++ b/build-utils/license.cjs @@ -1,6 +1,6 @@ const version = process.env.npm_package_version; -export default ` +module.exports = ` Planck.js v${version} @license The MIT license @copyright Copyright (c) 2021 Erin Catto, Ali Shakiba diff --git a/dist/planck-with-testbed.cjs b/dist/planck-with-testbed.cjs new file mode 100644 index 00000000..18732188 --- /dev/null +++ b/dist/planck-with-testbed.cjs @@ -0,0 +1,18310 @@ +/** + * Planck.js v1.0.0-beta.0 + * @license The MIT license + * @copyright Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +var __defProp = Object.defineProperty; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __publicField = (obj, key, value) => { + __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); + return value; +}; +const stats$1 = { + create: 0, + tick: 0, + node: 0, + draw: 0, + fps: 0 +}; +class Matrix { + constructor(a, b, c, d, e, f) { + this.reset(a, b, c, d, e, f); + } + toString() { + return "[" + this.a + ", " + this.b + ", " + this.c + ", " + this.d + ", " + this.e + ", " + this.f + "]"; + } + clone() { + return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f); + } + reset(a, b, c, d, e, f) { + this._dirty = true; + if (typeof a === "object") { + this.a = a.a, this.d = a.d; + this.b = a.b, this.c = a.c; + this.e = a.e, this.f = a.f; + } else { + this.a = a || 1, this.d = d || 1; + this.b = b || 0, this.c = c || 0; + this.e = e || 0, this.f = f || 0; + } + return this; + } + identity() { + this._dirty = true; + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.e = 0; + this.f = 0; + return this; + } + rotate(angle) { + if (!angle) { + return this; + } + this._dirty = true; + var u = angle ? Math.cos(angle) : 1; + var v = angle ? Math.sin(angle) : 0; + var a = u * this.a - v * this.b; + var b = u * this.b + v * this.a; + var c = u * this.c - v * this.d; + var d = u * this.d + v * this.c; + var e = u * this.e - v * this.f; + var f = u * this.f + v * this.e; + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + return this; + } + translate(x, y) { + if (!x && !y) { + return this; + } + this._dirty = true; + this.e += x; + this.f += y; + return this; + } + scale(x, y) { + if (!(x - 1) && !(y - 1)) { + return this; + } + this._dirty = true; + this.a *= x; + this.b *= y; + this.c *= x; + this.d *= y; + this.e *= x; + this.f *= y; + return this; + } + skew(x, y) { + if (!x && !y) { + return this; + } + this._dirty = true; + var a = this.a + this.b * x; + var b = this.b + this.a * y; + var c = this.c + this.d * x; + var d = this.d + this.c * y; + var e = this.e + this.f * x; + var f = this.f + this.e * y; + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + return this; + } + concat(m) { + this._dirty = true; + var n = this; + var a = n.a * m.a + n.b * m.c; + var b = n.b * m.d + n.a * m.b; + var c = n.c * m.a + n.d * m.c; + var d = n.d * m.d + n.c * m.b; + var e = n.e * m.a + m.e + n.f * m.c; + var f = n.f * m.d + m.f + n.e * m.b; + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + return this; + } + inverse() { + if (this._dirty) { + this._dirty = false; + this.inverted = this.inverted || new Matrix(); + var z = this.a * this.d - this.b * this.c; + this.inverted.a = this.d / z; + this.inverted.b = -this.b / z; + this.inverted.c = -this.c / z; + this.inverted.d = this.a / z; + this.inverted.e = (this.c * this.f - this.e * this.d) / z; + this.inverted.f = (this.e * this.b - this.a * this.f) / z; + } + return this.inverted; + } + map(p, q) { + q = q || {}; + q.x = this.a * p.x + this.c * p.y + this.e; + q.y = this.b * p.x + this.d * p.y + this.f; + return q; + } + mapX(x, y) { + if (typeof x === "object") + y = x.y, x = x.x; + return this.a * x + this.c * y + this.e; + } + mapY(x, y) { + if (typeof x === "object") + y = x.y, x = x.x; + return this.b * x + this.d * y + this.f; + } +} +var iid$1 = 0; +function Pin(owner) { + this._owner = owner; + this._parent = null; + this._relativeMatrix = new Matrix(); + this._absoluteMatrix = new Matrix(); + this.reset(); +} +Pin.prototype.reset = function() { + this._textureAlpha = 1; + this._alpha = 1; + this._width = 0; + this._height = 0; + this._scaleX = 1; + this._scaleY = 1; + this._skewX = 0; + this._skewY = 0; + this._rotation = 0; + this._pivoted = false; + this._pivotX = null; + this._pivotY = null; + this._handled = false; + this._handleX = 0; + this._handleY = 0; + this._aligned = false; + this._alignX = 0; + this._alignY = 0; + this._offsetX = 0; + this._offsetY = 0; + this._boxX = 0; + this._boxY = 0; + this._boxWidth = this._width; + this._boxHeight = this._height; + this._ts_translate = ++iid$1; + this._ts_transform = ++iid$1; + this._ts_matrix = ++iid$1; +}; +Pin.prototype._update = function() { + this._parent = this._owner._parent && this._owner._parent._pin; + if (this._handled && this._mo_handle != this._ts_transform) { + this._mo_handle = this._ts_transform; + this._ts_translate = ++iid$1; + } + if (this._aligned && this._parent && this._mo_align != this._parent._ts_transform) { + this._mo_align = this._parent._ts_transform; + this._ts_translate = ++iid$1; + } + return this; +}; +Pin.prototype.toString = function() { + return this._owner + " (" + (this._parent ? this._parent._owner : null) + ")"; +}; +Pin.prototype.absoluteMatrix = function() { + this._update(); + var ts = Math.max( + this._ts_transform, + this._ts_translate, + this._parent ? this._parent._ts_matrix : 0 + ); + if (this._mo_abs == ts) { + return this._absoluteMatrix; + } + this._mo_abs = ts; + var abs2 = this._absoluteMatrix; + abs2.reset(this.relativeMatrix()); + this._parent && abs2.concat(this._parent._absoluteMatrix); + this._ts_matrix = ++iid$1; + return abs2; +}; +Pin.prototype.relativeMatrix = function() { + this._update(); + var ts = Math.max( + this._ts_transform, + this._ts_translate, + this._parent ? this._parent._ts_transform : 0 + ); + if (this._mo_rel == ts) { + return this._relativeMatrix; + } + this._mo_rel = ts; + var rel2 = this._relativeMatrix; + rel2.identity(); + if (this._pivoted) { + rel2.translate(-this._pivotX * this._width, -this._pivotY * this._height); + } + rel2.scale(this._scaleX, this._scaleY); + rel2.skew(this._skewX, this._skewY); + rel2.rotate(this._rotation); + if (this._pivoted) { + rel2.translate(this._pivotX * this._width, this._pivotY * this._height); + } + if (this._pivoted) { + this._boxX = 0; + this._boxY = 0; + this._boxWidth = this._width; + this._boxHeight = this._height; + } else { + var p, q; + if (rel2.a > 0 && rel2.c > 0 || rel2.a < 0 && rel2.c < 0) { + p = 0, q = rel2.a * this._width + rel2.c * this._height; + } else { + p = rel2.a * this._width, q = rel2.c * this._height; + } + if (p > q) { + this._boxX = q; + this._boxWidth = p - q; + } else { + this._boxX = p; + this._boxWidth = q - p; + } + if (rel2.b > 0 && rel2.d > 0 || rel2.b < 0 && rel2.d < 0) { + p = 0, q = rel2.b * this._width + rel2.d * this._height; + } else { + p = rel2.b * this._width, q = rel2.d * this._height; + } + if (p > q) { + this._boxY = q; + this._boxHeight = p - q; + } else { + this._boxY = p; + this._boxHeight = q - p; + } + } + this._x = this._offsetX; + this._y = this._offsetY; + this._x -= this._boxX + this._handleX * this._boxWidth; + this._y -= this._boxY + this._handleY * this._boxHeight; + if (this._aligned && this._parent) { + this._parent.relativeMatrix(); + this._x += this._alignX * this._parent._width; + this._y += this._alignY * this._parent._height; + } + rel2.translate(this._x, this._y); + return this._relativeMatrix; +}; +Pin.prototype.get = function(key) { + if (typeof getters[key] === "function") { + return getters[key](this); + } +}; +Pin.prototype.set = function(a, b) { + if (typeof a === "string") { + if (typeof setters[a] === "function" && typeof b !== "undefined") { + setters[a](this, b); + } + } else if (typeof a === "object") { + for (b in a) { + if (typeof setters[b] === "function" && typeof a[b] !== "undefined") { + setters[b](this, a[b], a); + } + } + } + if (this._owner) { + this._owner._ts_pin = ++iid$1; + this._owner.touch(); + } + return this; +}; +var getters = { + alpha: function(pin) { + return pin._alpha; + }, + textureAlpha: function(pin) { + return pin._textureAlpha; + }, + width: function(pin) { + return pin._width; + }, + height: function(pin) { + return pin._height; + }, + boxWidth: function(pin) { + return pin._boxWidth; + }, + boxHeight: function(pin) { + return pin._boxHeight; + }, + // scale : function(pin) { + // }, + scaleX: function(pin) { + return pin._scaleX; + }, + scaleY: function(pin) { + return pin._scaleY; + }, + // skew : function(pin) { + // }, + skewX: function(pin) { + return pin._skewX; + }, + skewY: function(pin) { + return pin._skewY; + }, + rotation: function(pin) { + return pin._rotation; + }, + // pivot : function(pin) { + // }, + pivotX: function(pin) { + return pin._pivotX; + }, + pivotY: function(pin) { + return pin._pivotY; + }, + // offset : function(pin) { + // }, + offsetX: function(pin) { + return pin._offsetX; + }, + offsetY: function(pin) { + return pin._offsetY; + }, + // align : function(pin) { + // }, + alignX: function(pin) { + return pin._alignX; + }, + alignY: function(pin) { + return pin._alignY; + }, + // handle : function(pin) { + // }, + handleX: function(pin) { + return pin._handleX; + }, + handleY: function(pin) { + return pin._handleY; + } +}; +var setters = { + alpha: function(pin, value) { + pin._alpha = value; + }, + textureAlpha: function(pin, value) { + pin._textureAlpha = value; + }, + width: function(pin, value) { + pin._width_ = value; + pin._width = value; + pin._ts_transform = ++iid$1; + }, + height: function(pin, value) { + pin._height_ = value; + pin._height = value; + pin._ts_transform = ++iid$1; + }, + scale: function(pin, value) { + pin._scaleX = value; + pin._scaleY = value; + pin._ts_transform = ++iid$1; + }, + scaleX: function(pin, value) { + pin._scaleX = value; + pin._ts_transform = ++iid$1; + }, + scaleY: function(pin, value) { + pin._scaleY = value; + pin._ts_transform = ++iid$1; + }, + skew: function(pin, value) { + pin._skewX = value; + pin._skewY = value; + pin._ts_transform = ++iid$1; + }, + skewX: function(pin, value) { + pin._skewX = value; + pin._ts_transform = ++iid$1; + }, + skewY: function(pin, value) { + pin._skewY = value; + pin._ts_transform = ++iid$1; + }, + rotation: function(pin, value) { + pin._rotation = value; + pin._ts_transform = ++iid$1; + }, + pivot: function(pin, value) { + pin._pivotX = value; + pin._pivotY = value; + pin._pivoted = true; + pin._ts_transform = ++iid$1; + }, + pivotX: function(pin, value) { + pin._pivotX = value; + pin._pivoted = true; + pin._ts_transform = ++iid$1; + }, + pivotY: function(pin, value) { + pin._pivotY = value; + pin._pivoted = true; + pin._ts_transform = ++iid$1; + }, + offset: function(pin, value) { + pin._offsetX = value; + pin._offsetY = value; + pin._ts_translate = ++iid$1; + }, + offsetX: function(pin, value) { + pin._offsetX = value; + pin._ts_translate = ++iid$1; + }, + offsetY: function(pin, value) { + pin._offsetY = value; + pin._ts_translate = ++iid$1; + }, + align: function(pin, value) { + this.alignX(pin, value); + this.alignY(pin, value); + }, + alignX: function(pin, value) { + pin._alignX = value; + pin._aligned = true; + pin._ts_translate = ++iid$1; + this.handleX(pin, value); + }, + alignY: function(pin, value) { + pin._alignY = value; + pin._aligned = true; + pin._ts_translate = ++iid$1; + this.handleY(pin, value); + }, + handle: function(pin, value) { + this.handleX(pin, value); + this.handleY(pin, value); + }, + handleX: function(pin, value) { + pin._handleX = value; + pin._handled = true; + pin._ts_translate = ++iid$1; + }, + handleY: function(pin, value) { + pin._handleY = value; + pin._handled = true; + pin._ts_translate = ++iid$1; + }, + resizeMode: function(pin, value, all) { + if (all) { + if (value == "in") { + value = "in-pad"; + } else if (value == "out") { + value = "out-crop"; + } + scaleTo(pin, all.resizeWidth, all.resizeHeight, value); + } + }, + resizeWidth: function(pin, value, all) { + if (!all || !all.resizeMode) { + scaleTo(pin, value, null); + } + }, + resizeHeight: function(pin, value, all) { + if (!all || !all.resizeMode) { + scaleTo(pin, null, value); + } + }, + scaleMode: function(pin, value, all) { + if (all) { + scaleTo(pin, all.scaleWidth, all.scaleHeight, value); + } + }, + scaleWidth: function(pin, value, all) { + if (!all || !all.scaleMode) { + scaleTo(pin, value, null); + } + }, + scaleHeight: function(pin, value, all) { + if (!all || !all.scaleMode) { + scaleTo(pin, null, value); + } + }, + matrix: function(pin, value) { + this.scaleX(pin, value.a); + this.skewX(pin, value.c / value.d); + this.skewY(pin, value.b / value.a); + this.scaleY(pin, value.d); + this.offsetX(pin, value.e); + this.offsetY(pin, value.f); + this.rotation(pin, 0); + } +}; +Pin.prototype.scaleTo = function(width, height, mode) { + scaleTo(this, width, height, mode); +}; +function scaleTo(pin, width, height, mode) { + var w = typeof width === "number"; + var h = typeof height === "number"; + var m = typeof mode === "string"; + pin._ts_transform = ++iid$1; + if (w) { + pin._scaleX = width / pin._width_; + pin._width = pin._width_; + } + if (h) { + pin._scaleY = height / pin._height_; + pin._height = pin._height_; + } + if (w && h && m) { + if (mode == "out" || mode == "out-crop") { + pin._scaleX = pin._scaleY = Math.max(pin._scaleX, pin._scaleY); + } else if (mode == "in" || mode == "in-pad") { + pin._scaleX = pin._scaleY = Math.min(pin._scaleX, pin._scaleY); + } + if (mode == "out-crop" || mode == "in-pad") { + pin._width = width / pin._scaleX; + pin._height = height / pin._scaleY; + } + } +} +Pin._add_shortcuts = function(prototype) { + prototype.size = function(w, h) { + this.pin("width", w); + this.pin("height", h); + return this; + }; + prototype.width = function(w) { + if (typeof w === "undefined") { + return this.pin("width"); + } + this.pin("width", w); + return this; + }; + prototype.height = function(h) { + if (typeof h === "undefined") { + return this.pin("height"); + } + this.pin("height", h); + return this; + }; + prototype.offset = function(a, b) { + if (typeof a === "object") + b = a.y, a = a.x; + this.pin("offsetX", a); + this.pin("offsetY", b); + return this; + }; + prototype.rotate = function(a) { + this.pin("rotation", a); + return this; + }; + prototype.skew = function(a, b) { + if (typeof a === "object") + b = a.y, a = a.x; + else if (typeof b === "undefined") + b = a; + this.pin("skewX", a); + this.pin("skewY", b); + return this; + }; + prototype.scale = function(a, b) { + if (typeof a === "object") + b = a.y, a = a.x; + else if (typeof b === "undefined") + b = a; + this.pin("scaleX", a); + this.pin("scaleY", b); + return this; + }; + prototype.alpha = function(a, ta) { + this.pin("alpha", a); + if (typeof ta !== "undefined") { + this.pin("textureAlpha", ta); + } + return this; + }; +}; +var iid = 0; +stats$1.create = 0; +function assertType(obj) { + if (obj && obj instanceof Node) { + return obj; + } + throw "Invalid node: " + obj; +} +const create = function() { + return new Node(); +}; +function Node() { + stats$1.create++; + this._pin = new Pin(this); +} +Node.prototype.matrix = function(relative) { + if (relative === true) { + return this._pin.relativeMatrix(); + } + return this._pin.absoluteMatrix(); +}; +Node.prototype.pin = function(a, b) { + if (typeof a === "object") { + this._pin.set(a); + return this; + } else if (typeof a === "string") { + if (typeof b === "undefined") { + return this._pin.get(a); + } else { + this._pin.set(a, b); + return this; + } + } else if (typeof a === "undefined") { + return this._pin; + } +}; +Node.prototype.scaleTo = function(a, b, c) { + if (typeof a === "object") + c = b, b = a.y, a = a.x; + this._pin.scaleTo(a, b, c); + return this; +}; +Pin._add_shortcuts(Node.prototype); +Node.prototype._label = ""; +Node.prototype._visible = true; +Node.prototype._parent = null; +Node.prototype._next = null; +Node.prototype._prev = null; +Node.prototype._first = null; +Node.prototype._last = null; +Node.prototype._attrs = null; +Node.prototype._flags = null; +Node.prototype.toString = function() { + return "[" + this._label + "]"; +}; +Node.prototype.id = function(id) { + return this.label(id); +}; +Node.prototype.label = function(label) { + if (typeof label === "undefined") { + return this._label; + } + this._label = label; + return this; +}; +Node.prototype.attr = function(name, value) { + if (typeof value === "undefined") { + return this._attrs !== null ? this._attrs[name] : void 0; + } + (this._attrs !== null ? this._attrs : this._attrs = {})[name] = value; + return this; +}; +Node.prototype.visible = function(visible) { + if (typeof visible === "undefined") { + return this._visible; + } + this._visible = visible; + this._parent && (this._parent._ts_children = ++iid); + this._ts_pin = ++iid; + this.touch(); + return this; +}; +Node.prototype.hide = function() { + return this.visible(false); +}; +Node.prototype.show = function() { + return this.visible(true); +}; +Node.prototype.parent = function() { + return this._parent; +}; +Node.prototype.next = function(visible) { + var next = this._next; + while (next && visible && !next._visible) { + next = next._next; + } + return next; +}; +Node.prototype.prev = function(visible) { + var prev = this._prev; + while (prev && visible && !prev._visible) { + prev = prev._prev; + } + return prev; +}; +Node.prototype.first = function(visible) { + var next = this._first; + while (next && visible && !next._visible) { + next = next._next; + } + return next; +}; +Node.prototype.last = function(visible) { + var prev = this._last; + while (prev && visible && !prev._visible) { + prev = prev._prev; + } + return prev; +}; +Node.prototype.visit = function(visitor, data) { + var reverse = visitor.reverse; + var visible = visitor.visible; + if (visitor.start && visitor.start(this, data)) { + return; + } + var child, next = reverse ? this.last(visible) : this.first(visible); + while (child = next) { + next = reverse ? child.prev(visible) : child.next(visible); + if (child.visit(visitor, data)) { + return true; + } + } + return visitor.end && visitor.end(this, data); +}; +Node.prototype.append = function(child, more) { + if (Array.isArray(child)) + for (var i = 0; i < child.length; i++) + append(this, child[i]); + else if (typeof more !== "undefined") + for (var i = 0; i < arguments.length; i++) + append(this, arguments[i]); + else if (typeof child !== "undefined") + append(this, child); + return this; +}; +Node.prototype.prepend = function(child, more) { + if (Array.isArray(child)) + for (var i = child.length - 1; i >= 0; i--) + prepend(this, child[i]); + else if (typeof more !== "undefined") + for (var i = arguments.length - 1; i >= 0; i--) + prepend(this, arguments[i]); + else if (typeof child !== "undefined") + prepend(this, child); + return this; +}; +Node.prototype.appendTo = function(parent) { + append(parent, this); + return this; +}; +Node.prototype.prependTo = function(parent) { + prepend(parent, this); + return this; +}; +Node.prototype.insertNext = function(sibling, more) { + if (Array.isArray(sibling)) + for (var i = 0; i < sibling.length; i++) + insertAfter(sibling[i], this); + else if (typeof more !== "undefined") + for (var i = 0; i < arguments.length; i++) + insertAfter(arguments[i], this); + else if (typeof sibling !== "undefined") + insertAfter(sibling, this); + return this; +}; +Node.prototype.insertPrev = function(sibling, more) { + if (Array.isArray(sibling)) + for (var i = sibling.length - 1; i >= 0; i--) + insertBefore(sibling[i], this); + else if (typeof more !== "undefined") + for (var i = arguments.length - 1; i >= 0; i--) + insertBefore(arguments[i], this); + else if (typeof sibling !== "undefined") + insertBefore(sibling, this); + return this; +}; +Node.prototype.insertAfter = function(prev) { + insertAfter(this, prev); + return this; +}; +Node.prototype.insertBefore = function(next) { + insertBefore(this, next); + return this; +}; +function append(parent, child) { + assertType(child); + assertType(parent); + child.remove(); + if (parent._last) { + parent._last._next = child; + child._prev = parent._last; + } + child._parent = parent; + parent._last = child; + if (!parent._first) { + parent._first = child; + } + child._parent._flag(child, true); + child._ts_parent = ++iid; + parent._ts_children = ++iid; + parent.touch(); +} +function prepend(parent, child) { + assertType(child); + assertType(parent); + child.remove(); + if (parent._first) { + parent._first._prev = child; + child._next = parent._first; + } + child._parent = parent; + parent._first = child; + if (!parent._last) { + parent._last = child; + } + child._parent._flag(child, true); + child._ts_parent = ++iid; + parent._ts_children = ++iid; + parent.touch(); +} +function insertBefore(self, next) { + assertType(self); + assertType(next); + self.remove(); + var parent = next._parent; + var prev = next._prev; + next._prev = self; + prev && (prev._next = self) || parent && (parent._first = self); + self._parent = parent; + self._prev = prev; + self._next = next; + self._parent._flag(self, true); + self._ts_parent = ++iid; + self.touch(); +} +function insertAfter(self, prev) { + assertType(self); + assertType(prev); + self.remove(); + var parent = prev._parent; + var next = prev._next; + prev._next = self; + next && (next._prev = self) || parent && (parent._last = self); + self._parent = parent; + self._prev = prev; + self._next = next; + self._parent._flag(self, true); + self._ts_parent = ++iid; + self.touch(); +} +Node.prototype.remove = function(child, more) { + if (typeof child !== "undefined") { + if (Array.isArray(child)) { + for (var i = 0; i < child.length; i++) + assertType(child[i]).remove(); + } else if (typeof more !== "undefined") { + for (var i = 0; i < arguments.length; i++) + assertType(arguments[i]).remove(); + } else { + assertType(child).remove(); + } + return this; + } + if (this._prev) { + this._prev._next = this._next; + } + if (this._next) { + this._next._prev = this._prev; + } + if (this._parent) { + if (this._parent._first === this) { + this._parent._first = this._next; + } + if (this._parent._last === this) { + this._parent._last = this._prev; + } + this._parent._flag(this, false); + this._parent._ts_children = ++iid; + this._parent.touch(); + } + this._prev = this._next = this._parent = null; + this._ts_parent = ++iid; + return this; +}; +Node.prototype.empty = function() { + var child, next = this._first; + while (child = next) { + next = child._next; + child._prev = child._next = child._parent = null; + this._flag(child, false); + } + this._first = this._last = null; + this._ts_children = ++iid; + this.touch(); + return this; +}; +Node.prototype._ts_touch = null; +Node.prototype.touch = function() { + this._ts_touch = ++iid; + this._parent && this._parent.touch(); + return this; +}; +Node.prototype._flag = function(obj, name) { + if (typeof name === "undefined") { + return this._flags !== null && this._flags[obj] || 0; + } + if (typeof obj === "string") { + if (name) { + this._flags = this._flags || {}; + if (!this._flags[obj] && this._parent) { + this._parent._flag(obj, true); + } + this._flags[obj] = (this._flags[obj] || 0) + 1; + } else if (this._flags && this._flags[obj] > 0) { + if (this._flags[obj] == 1 && this._parent) { + this._parent._flag(obj, false); + } + this._flags[obj] = this._flags[obj] - 1; + } + } + if (typeof obj === "object") { + if (obj._flags) { + for (var type in obj._flags) { + if (obj._flags[type] > 0) { + this._flag(type, name); + } + } + } + } + return this; +}; +Node.prototype.hitTest = function(hit) { + var width = this._pin._width; + var height = this._pin._height; + return hit.x >= 0 && hit.x <= width && hit.y >= 0 && hit.y <= height; +}; +Node.prototype._textures = null; +Node.prototype._alpha = 1; +Node.prototype.render = function(context) { + if (!this._visible) { + return; + } + stats$1.node++; + var m = this.matrix(); + context.setTransform(m.a, m.b, m.c, m.d, m.e, m.f); + this._alpha = this._pin._alpha * (this._parent ? this._parent._alpha : 1); + var alpha = this._pin._textureAlpha * this._alpha; + if (context.globalAlpha != alpha) { + context.globalAlpha = alpha; + } + if (this._textures !== null) { + for (var i = 0, n = this._textures.length; i < n; i++) { + this._textures[i].draw(context); + } + } + if (context.globalAlpha != this._alpha) { + context.globalAlpha = this._alpha; + } + var child, next = this._first; + while (child = next) { + next = child._next; + child.render(context); + } +}; +Node.prototype._tickBefore = null; +Node.prototype._tickAfter = null; +Node.prototype.MAX_ELAPSE = Infinity; +Node.prototype._tick = function(elapsed, now, last) { + if (!this._visible) { + return; + } + if (elapsed > this.MAX_ELAPSE) { + elapsed = this.MAX_ELAPSE; + } + var ticked = false; + if (this._tickBefore !== null) { + for (var i = 0; i < this._tickBefore.length; i++) { + stats$1.tick++; + var tickFn = this._tickBefore[i]; + ticked = tickFn.call(this, elapsed, now, last) === true || ticked; + } + } + var child, next = this._first; + while (child = next) { + next = child._next; + if (child._flag("_tick")) { + ticked = child._tick(elapsed, now, last) === true ? true : ticked; + } + } + if (this._tickAfter !== null) { + for (var i = 0; i < this._tickAfter.length; i++) { + stats$1.tick++; + var tickFn = this._tickAfter[i]; + ticked = tickFn.call(this, elapsed, now, last) === true || ticked; + } + } + return ticked; +}; +Node.prototype.tick = function(ticker, before) { + if (typeof ticker !== "function") { + return; + } + if (before) { + if (this._tickBefore === null) { + this._tickBefore = []; + } + this._tickBefore.push(ticker); + } else { + if (this._tickAfter === null) { + this._tickAfter = []; + } + this._tickAfter.push(ticker); + } + this._flag("_tick", this._tickAfter !== null && this._tickAfter.length > 0 || this._tickBefore !== null && this._tickBefore.length > 0); +}; +Node.prototype.untick = function(ticker) { + if (typeof ticker !== "function") { + return; + } + var i; + if (this._tickBefore !== null && (i = this._tickBefore.indexOf(ticker)) >= 0) { + this._tickBefore.splice(i, 1); + } + if (this._tickAfter !== null && (i = this._tickAfter.indexOf(ticker)) >= 0) { + this._tickAfter.splice(i, 1); + } +}; +Node.prototype.timeout = function(fn, time) { + this.setTimeout(fn, time); +}; +Node.prototype.setTimeout = function(fn, time) { + function timer(t) { + if ((time -= t) < 0) { + this.untick(timer); + fn.call(this); + } else { + return true; + } + } + this.tick(timer); + return timer; +}; +Node.prototype.clearTimeout = function(timer) { + this.untick(timer); +}; +Node.prototype._listeners = null; +Node.prototype._event_callback = function(name, on) { + this._flag(name, on); +}; +Node.prototype.on = function(types, listener) { + if (!types || !types.length || typeof listener !== "function") { + return this; + } + if (this._listeners === null) { + this._listeners = {}; + } + var isarray = typeof types !== "string" && typeof types.join === "function"; + if (types = (isarray ? types.join(" ") : types).match(/\S+/g)) { + for (var i = 0; i < types.length; i++) { + var type = types[i]; + this._listeners[type] = this._listeners[type] || []; + this._listeners[type].push(listener); + if (typeof this._event_callback === "function") { + this._event_callback(type, true); + } + } + } + return this; +}; +Node.prototype.off = function(types, listener) { + if (!types || !types.length || typeof listener !== "function") { + return this; + } + if (this._listeners === null) { + return this; + } + var isarray = typeof types !== "string" && typeof types.join === "function"; + if (types = (isarray ? types.join(" ") : types).match(/\S+/g)) { + for (var i = 0; i < types.length; i++) { + var type = types[i], all = this._listeners[type], index; + if (all && (index = all.indexOf(listener)) >= 0) { + all.splice(index, 1); + if (!all.length) { + delete this._listeners[type]; + } + if (typeof this._event_callback === "function") { + this._event_callback(type, false); + } + } + } + } + return this; +}; +Node.prototype.listeners = function(type) { + return this._listeners && this._listeners[type]; +}; +Node.prototype.publish = function(name, args) { + var listeners = this.listeners(name); + if (!listeners || !listeners.length) { + return 0; + } + for (var l = 0; l < listeners.length; l++) { + listeners[l].apply(this, args); + } + return listeners.length; +}; +Node.prototype.trigger = function(name, args) { + this.publish(name, args); + return this; +}; +var native = Math; +const math$1 = Object.create(Math); +math$1.random = function(min, max) { + if (typeof min === "undefined") { + max = 1, min = 0; + } else if (typeof max === "undefined") { + max = min, min = 0; + } + return min == max ? min : native.random() * (max - min) + min; +}; +math$1.wrap = function(num, min, max) { + if (typeof min === "undefined") { + max = 1, min = 0; + } else if (typeof max === "undefined") { + max = min, min = 0; + } + if (max > min) { + num = (num - min) % (max - min); + return num + (num < 0 ? max : min); + } else { + num = (num - max) % (min - max); + return num + (num <= 0 ? min : max); + } +}; +math$1.clamp = function(num, min, max) { + if (num < min) { + return min; + } else if (num > max) { + return max; + } else { + return num; + } +}; +math$1.length = function(x, y) { + return native.sqrt(x * x + y * y); +}; +math$1.rotate = math$1.wrap; +math$1.limit = math$1.clamp; +const isFn = function(value) { + var str = Object.prototype.toString.call(value); + return str === "[object Function]" || str === "[object GeneratorFunction]" || str === "[object AsyncFunction]"; +}; +const isHash = function(value) { + return Object.prototype.toString.call(value) === "[object Object]" && value.constructor === Object; +}; +class Texture { + constructor(texture2, ratio) { + if (typeof texture2 === "object") { + this.src(texture2, ratio); + } + } + pipe() { + return new Texture(this); + } + /** + * Signatures: (texture), (x, y, w, h), (w, h) + */ + src(x, y, w, h) { + if (typeof x === "object") { + var drawable = x, ratio = y || 1; + this._image = drawable; + this._sx = this._dx = 0; + this._sy = this._dy = 0; + this._sw = this._dw = drawable.width / ratio; + this._sh = this._dh = drawable.height / ratio; + this.width = drawable.width / ratio; + this.height = drawable.height / ratio; + this.ratio = ratio; + } else { + if (typeof w === "undefined") { + w = x, h = y; + } else { + this._sx = x, this._sy = y; + } + this._sw = this._dw = w; + this._sh = this._dh = h; + this.width = w; + this.height = h; + } + return this; + } + /** + * Signatures: (x, y, w, h), (x, y) + */ + dest(x, y, w, h) { + this._dx = x, this._dy = y; + this._dx = x, this._dy = y; + if (typeof w !== "undefined") { + this._dw = w, this._dh = h; + this.width = w, this.height = h; + } + return this; + } + draw(context, x1, y1, x2, y2, x3, y3, x4, y4) { + var drawable = this._image; + if (drawable === null || typeof drawable !== "object") { + return; + } + var sx = this._sx, sy = this._sy; + var sw = this._sw, sh = this._sh; + var dx = this._dx, dy = this._dy; + var dw = this._dw, dh = this._dh; + if (typeof x3 !== "undefined") { + x1 = math$1.clamp(x1, 0, this._sw), x2 = math$1.clamp(x2, 0, this._sw - x1); + y1 = math$1.clamp(y1, 0, this._sh), y2 = math$1.clamp(y2, 0, this._sh - y1); + sx += x1, sy += y1, sw = x2, sh = y2; + dx = x3, dy = y3, dw = x4, dh = y4; + } else if (typeof x2 !== "undefined") { + dx = x1, dy = y1, dw = x2, dh = y2; + } else if (typeof x1 !== "undefined") { + dw = x1, dh = y1; + } + var ratio = this.ratio || 1; + sx *= ratio, sy *= ratio, sw *= ratio, sh *= ratio; + try { + if (typeof drawable.draw === "function") { + drawable.draw(context, sx, sy, sw, sh, dx, dy, dw, dh); + } else { + stats$1.draw++; + context.drawImage(drawable, sx, sy, sw, sh, dx, dy, dw, dh); + } + } catch (ex) { + if (!drawable._draw_failed) { + console.log("Unable to draw: ", drawable); + console.log(ex); + drawable._draw_failed = true; + } + } + } +} +var NO_TEXTURE = new class extends Texture { + constructor() { + super(); + __publicField(this, "pipe", function() { + return this; + }); + __publicField(this, "src", function() { + return this; + }); + __publicField(this, "dest", function() { + return this; + }); + __publicField(this, "draw", function() { + }); + this.x = this.y = this.width = this.height = 0; + } +}(); +var NO_SELECTION = new Selection(NO_TEXTURE); +function preloadImage(src) { + console.log("Loading image: " + src); + return new Promise(function(resolve, reject) { + const img = new Image(); + img.onload = function() { + console.log("Image loaded: " + src); + resolve(img); + }; + img.onerror = function(error) { + console.log("Loading failed: " + src); + reject(error); + }; + img.src = src; + }); +} +var _atlases_map = {}; +var _atlases_arr = []; +const atlas = async function(def) { + var atlas2 = isFn(def.draw) ? def : new Atlas(def); + if (def.name) { + _atlases_map[def.name] = atlas2; + } + _atlases_arr.push(atlas2); + deprecated(def, "imagePath"); + deprecated(def, "imageRatio"); + var url = def.imagePath; + var ratio = def.imageRatio || 1; + if ("string" === typeof def.image) { + url = def.image; + } else if (isHash(def.image)) { + url = def.image.src || def.image.url; + ratio = def.image.ratio || ratio; + } + if (url) { + const image2 = await preloadImage(url); + atlas2.src(image2, ratio); + } + return atlas2; +}; +class Atlas extends Texture { + constructor(def) { + super(); + var atlas2 = this; + deprecated(def, "filter"); + deprecated(def, "cutouts"); + deprecated(def, "sprites"); + deprecated(def, "factory"); + var map = def.map || def.filter; + var ppu = def.ppu || def.ratio || 1; + var trim = def.trim || 0; + var textures = def.textures; + var factory = def.factory; + var cutouts = def.cutouts || def.sprites; + function make(def2) { + if (!def2 || isFn(def2.draw)) { + return def2; + } + def2 = Object.assign({}, def2); + if (isFn(map)) { + def2 = map(def2); + } + if (ppu != 1) { + def2.x *= ppu, def2.y *= ppu; + def2.width *= ppu, def2.height *= ppu; + def2.top *= ppu, def2.bottom *= ppu; + def2.left *= ppu, def2.right *= ppu; + } + if (trim != 0) { + def2.x += trim, def2.y += trim; + def2.width -= 2 * trim, def2.height -= 2 * trim; + def2.top -= trim, def2.bottom -= trim; + def2.left -= trim, def2.right -= trim; + } + var texture2 = atlas2.pipe(); + texture2.top = def2.top, texture2.bottom = def2.bottom; + texture2.left = def2.left, texture2.right = def2.right; + texture2.src(def2.x, def2.y, def2.width, def2.height); + return texture2; + } + function find(query) { + if (textures) { + if (isFn(textures)) { + return textures(query); + } else if (isHash(textures)) { + return textures[query]; + } + } + if (cutouts) { + var result = null, n = 0; + for (var i = 0; i < cutouts.length; i++) { + if (string.startsWith(cutouts[i].name, query)) { + if (n === 0) { + result = cutouts[i]; + } else if (n === 1) { + result = [result, cutouts[i]]; + } else { + result.push(cutouts[i]); + } + n++; + } + } + if (n === 0 && isFn(factory)) { + result = function(subquery) { + return factory(query + (subquery ? subquery : "")); + }; + } + return result; + } + } + this.select = function(query) { + if (!query) { + return new Selection(this.pipe()); + } + var found = find(query); + if (found) { + return new Selection(found, find, make); + } + }; + } +} +function Selection(result, find, make) { + function link(result2, subquery) { + if (!result2) { + return NO_TEXTURE; + } else if (isFn(result2.draw)) { + return result2; + } else if (isHash(result2) && "number" === typeof result2.width && "number" === typeof result2.height && isFn(make)) { + return make(result2); + } else if (isHash(result2) && "undefined" !== typeof subquery) { + return link(result2[subquery]); + } else if (isFn(result2)) { + return link(result2(subquery)); + } else if (Array.isArray(result2)) { + return link(result2[0]); + } else if ("string" === typeof result2 && isFn(find)) { + return link(find(result2)); + } + } + this.one = function(subquery) { + return link(result, subquery); + }; + this.array = function(arr) { + var array = Array.isArray(arr) ? arr : []; + if (Array.isArray(result)) { + for (var i = 0; i < result.length; i++) { + array[i] = link(result[i]); + } + } else { + array[0] = link(result); + } + return array; + }; +} +const texture = function(query) { + if (!("string" === typeof query)) { + return new Selection(query); + } + var result = null, atlas2, i; + if ((i = query.indexOf(":")) > 0 && query.length > i + 1) { + atlas2 = _atlases_map[query.slice(0, i)]; + result = atlas2 && atlas2.select(query.slice(i + 1)); + } + if (!result && (atlas2 = _atlases_map[query])) { + result = atlas2.select(); + } + for (i = 0; !result && i < _atlases_arr.length; i++) { + result = _atlases_arr[i].select(query); + } + if (!result) { + console.error("Texture not found: " + query); + result = NO_SELECTION; + } + return result; +}; +function deprecated(hash, name, msg) { + if (name in hash) + console.log(msg ? msg.replace("%name", name) : "'" + name + "' field of texture atlas is deprecated."); +} +const canvas = function(type, attributes, plotter) { + if (typeof type === "string") { + if (typeof attributes === "object") + ; + else { + if (typeof attributes === "function") { + plotter = attributes; + } + attributes = {}; + } + } else { + if (typeof type === "function") { + plotter = type; + } + attributes = {}; + type = "2d"; + } + var canvas2 = document.createElement("canvas"); + var context = canvas2.getContext(type, attributes); + var texture2 = new Texture(canvas2); + texture2.context = function() { + return context; + }; + texture2.size = function(width, height, ratio) { + ratio = ratio || 1; + canvas2.width = width * ratio; + canvas2.height = height * ratio; + this.src(canvas2, ratio); + return this; + }; + texture2.canvas = function(fn) { + if (typeof fn === "function") { + fn.call(this, context); + } else if (typeof fn === "undefined" && typeof plotter === "function") { + plotter.call(this, context); + } + return this; + }; + if (typeof plotter === "function") { + plotter.call(texture2, context); + } + return texture2; +}; +const PIXEL_RATIO = window.devicePixelRatio || 1; +let M; +function memoizeDraw(callback, memoKey = () => null) { + let lastRatio = 0; + let lastSelection = void 0; + let texture2 = Stage.canvas(); + let sprite2 = Stage.sprite(); + let first = true; + sprite2.tick(function() { + let m = this._parent.matrix(); + if (first) { + first = false; + if (!(m = M)) { + return; + } + } + M = m; + let newRatio = Math.max(Math.abs(m.a), Math.abs(m.b)); + let rationChange = lastRatio / newRatio; + if (lastRatio === 0 || rationChange > 1.25 || rationChange < 0.8) { + const newSelection = memoKey(); + if (lastSelection !== newSelection) { + lastRatio = newRatio; + callback(2.5 * newRatio / PIXEL_RATIO, texture2, sprite2); + sprite2.texture(texture2); + sprite2.__timestamp = Date.now(); + } + } + }, false); + return sprite2; +} +class Mouse { + constructor() { + __publicField(this, "x", 0); + __publicField(this, "y", 0); + __publicField(this, "ratio", 1); + __publicField(this, "stage"); + __publicField(this, "elem"); + __publicField(this, "clicklist", []); + __publicField(this, "cancellist", []); + __publicField(this, "handleStart", (event) => { + event.preventDefault(); + this.locate(event); + this.publish(event.type, event); + this.lookup("click", this.clicklist); + this.lookup("mousecancel", this.cancellist); + }); + __publicField(this, "handleMove", (event) => { + event.preventDefault(); + this.locate(event); + this.publish(event.type, event); + }); + __publicField(this, "handleEnd", (event) => { + event.preventDefault(); + this.publish(event.type, event); + if (this.clicklist.length) { + this.publish("click", event, this.clicklist); + } + this.cancellist.length = 0; + }); + __publicField(this, "handleCancel", (event) => { + if (this.cancellist.length) { + this.publish("mousecancel", event, this.cancellist); + } + this.clicklist.length = 0; + }); + __publicField(this, "toString", function() { + return (this.x | 0) + "x" + (this.y | 0); + }); + __publicField(this, "locate", function(event) { + const elem = this.elem; + let x; + let y; + if (event.touches && event.touches.length) { + x = event.touches[0].clientX; + y = event.touches[0].clientY; + } else { + x = event.clientX; + y = event.clientY; + } + var rect = elem.getBoundingClientRect(); + x -= rect.left; + y -= rect.top; + x -= elem.clientLeft | 0; + y -= elem.clientTop | 0; + this.x = x * this.ratio; + this.y = y * this.ratio; + }); + __publicField(this, "lookup", function(type, collect) { + this.type = type; + this.root = this.stage; + this.event = null; + collect.length = 0; + this.collect = collect; + this.root.visit({ + reverse: true, + visible: true, + start: this.visitStart, + end: this.visitEnd + }, this); + }); + __publicField(this, "publish", function(type, event, targets) { + this.type = type; + this.root = this.stage; + this.event = event; + this.collect = false; + this.timeStamp = Date.now(); + if (type !== "mousemove" && type !== "touchmove") { + console.log(this.type + " " + this); + } + if (targets) { + while (targets.length) + if (this.visitEnd(targets.shift())) + break; + targets.length = 0; + } else { + this.root.visit({ + reverse: true, + visible: true, + start: this.visitStart, + end: this.visitEnd + }, this); + } + }); + __publicField(this, "visitStart", (node) => { + return !node._flag(this.type); + }); + __publicField(this, "visitEnd", (node) => { + rel.raw = this.event; + rel.type = this.type; + rel.timeStamp = this.timeStamp; + rel.abs.x = this.x; + rel.abs.y = this.y; + var listeners = node.listeners(this.type); + if (!listeners) { + return; + } + node.matrix().inverse().map(this, rel); + if (!(node === this.root || node.attr("spy") || node.hitTest(rel))) { + return; + } + if (this.collect) { + this.collect.push(node); + } + if (this.event) { + var cancel = false; + for (var l = 0; l < listeners.length; l++) { + cancel = listeners[l].call(node, rel) ? true : cancel; + } + return cancel; + } + }); + } + mount(stage, elem) { + this.stage = stage; + this.elem = elem; + this.ratio = stage.viewport().ratio || 1; + stage.on("viewport", (size) => { + this.ratio = size.ratio ?? this.ratio; + }); + elem.addEventListener("touchstart", this.handleStart); + elem.addEventListener("touchend", this.handleEnd); + elem.addEventListener("touchmove", this.handleMove); + elem.addEventListener("touchcancel", this.handleCancel); + elem.addEventListener("mousedown", this.handleStart); + elem.addEventListener("mouseup", this.handleEnd); + elem.addEventListener("mousemove", this.handleMove); + document.addEventListener("mouseup", this.handleCancel); + window.addEventListener("blur", this.handleCancel); + return this; + } + unmount() { + const elem = this.elem; + elem.removeEventListener("touchstart", this.handleStart); + elem.removeEventListener("touchend", this.handleEnd); + elem.removeEventListener("touchmove", this.handleMove); + elem.removeEventListener("touchcancel", this.handleCancel); + elem.removeEventListener("mousedown", this.handleStart); + elem.removeEventListener("mouseup", this.handleEnd); + elem.removeEventListener("mousemove", this.handleMove); + document.removeEventListener("mouseup", this.handleCancel); + window.removeEventListener("blur", this.handleCancel); + return this; + } +} +__publicField(Mouse, "CLICK", "click"); +__publicField(Mouse, "START", "touchstart mousedown"); +__publicField(Mouse, "MOVE", "touchmove mousemove"); +__publicField(Mouse, "END", "touchend mouseup"); +__publicField(Mouse, "CANCEL", "touchcancel mousecancel"); +var rel = {}, abs = {}; +defineValue(rel, "clone", function(obj) { + obj = obj || {}, obj.x = this.x, obj.y = this.y; + return obj; +}); +defineValue(rel, "toString", function() { + return (this.x | 0) + "x" + (this.y | 0) + " (" + this.abs + ")"; +}); +defineValue(rel, "abs", abs); +defineValue(abs, "clone", function(obj) { + obj = obj || {}, obj.x = this.x, obj.y = this.y; + return obj; +}); +defineValue(abs, "toString", function() { + return (this.x | 0) + "x" + (this.y | 0); +}); +function defineValue(obj, name, value) { + Object.defineProperty(obj, name, { + value + }); +} +function IDENTITY(x) { + return x; +} +var _cache = {}; +var _modes = {}; +var _easings = {}; +class Easing { + static get(token, fallback = IDENTITY) { + if (typeof token === "function") { + return token; + } + if (typeof token !== "string") { + return fallback; + } + var fn = _cache[token]; + if (fn) { + return fn; + } + var match = /^(\w+)(-(in|out|in-out|out-in))?(\((.*)\))?$/i.exec(token); + if (!match || !match.length) { + return fallback; + } + var easing = _easings[match[1]]; + var mode = _modes[match[3]]; + var params = match[5]; + if (easing && easing.fn) { + fn = easing.fn; + } else if (easing && easing.fc) { + fn = easing.fc.apply(easing.fc, params && params.replace(/\s+/, "").split(",")); + } else { + fn = fallback; + } + if (mode) { + fn = mode.fn(fn); + } + _cache[token] = fn; + return fn; + } + static add(data) { + var names = (data.name || data.mode).split(/\s+/); + for (var i = 0; i < names.length; i++) { + var name = names[i]; + if (name) { + (data.name ? _easings : _modes)[name] = data; + } + } + } +} +Easing.add({ + mode: "in", + fn: function(f) { + return f; + } +}); +Easing.add({ + mode: "out", + fn: function(f) { + return function(t) { + return 1 - f(1 - t); + }; + } +}); +Easing.add({ + mode: "in-out", + fn: function(f) { + return function(t) { + return t < 0.5 ? f(2 * t) / 2 : 1 - f(2 * (1 - t)) / 2; + }; + } +}); +Easing.add({ + mode: "out-in", + fn: function(f) { + return function(t) { + return t < 0.5 ? 1 - f(2 * (1 - t)) / 2 : f(2 * t) / 2; + }; + } +}); +Easing.add({ + name: "linear", + fn: function(t) { + return t; + } +}); +Easing.add({ + name: "quad", + fn: function(t) { + return t * t; + } +}); +Easing.add({ + name: "cubic", + fn: function(t) { + return t * t * t; + } +}); +Easing.add({ + name: "quart", + fn: function(t) { + return t * t * t * t; + } +}); +Easing.add({ + name: "quint", + fn: function(t) { + return t * t * t * t * t; + } +}); +Easing.add({ + name: "sin sine", + fn: function(t) { + return 1 - Math.cos(t * Math.PI / 2); + } +}); +Easing.add({ + name: "exp expo", + fn: function(t) { + return t == 0 ? 0 : Math.pow(2, 10 * (t - 1)); + } +}); +Easing.add({ + name: "circle circ", + fn: function(t) { + return 1 - Math.sqrt(1 - t * t); + } +}); +Easing.add({ + name: "bounce", + fn: function(t) { + return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + 0.75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375 : 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375; + } +}); +Easing.add({ + name: "poly", + fc: function(e) { + return function(t) { + return Math.pow(t, e); + }; + } +}); +Easing.add({ + name: "elastic", + fc: function(a, p) { + p = p || 0.45; + a = a || 1; + var s = p / (2 * Math.PI) * Math.asin(1 / a); + return function(t) { + return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p); + }; + } +}); +Easing.add({ + name: "back", + fc: function(s) { + s = typeof s !== "undefined" ? s : 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + } +}); +Node.prototype.tween = function(a, b, c) { + let options; + if (typeof a === "object" && a !== null) { + options = a; + } else if (typeof a === "number" && typeof b === "number") { + options = { + duration: a, + delay: b, + append: c + }; + } else if (typeof a === "number") { + options = { + duration: a, + delay: 0, + append: b + }; + } else { + options = { + duration: 400, + delay: 0, + append: a + }; + } + if (!this._tweens) { + this._tweens = []; + var ticktime = 0; + this.tick(function(elapsed, now, last) { + if (!this._tweens.length) { + return false; + } + var ignore = ticktime != last; + ticktime = now; + if (ignore) { + return true; + } + var head = this._tweens[0]; + var ended = head.tick(this, elapsed, now, last); + if (ended) { + if (head === this._tweens[0]) { + this._tweens.shift(); + } + var next = head.finish(); + if (next) { + this._tweens.unshift(next); + } + } + return true; + }, true); + } + this.touch(); + if (!options.append) { + this._tweens.length = 0; + } + var tween = new Tween(this, options); + this._tweens.push(tween); + return tween; +}; +class Tween { + constructor(owner, options = {}) { + __publicField(this, "_ending", []); + this._end = {}; + this._duration = options.duration || 400; + this._delay = options.delay || 0; + this._owner = owner; + this._time = 0; + } + // @internal + tick(node, elapsed, now, last) { + this._time += elapsed; + if (this._time < this._delay) { + return; + } + var time = this._time - this._delay; + if (!this._start) { + this._start = {}; + for (var key in this._end) { + this._start[key] = this._owner.pin(key); + } + } + var p = Math.min(time / this._duration, 1); + var ended = p >= 1; + if (typeof this._easing == "function") { + p = this._easing(p); + } + var q = 1 - p; + for (var key in this._end) { + this._owner.pin(key, this._start[key] * q + this._end[key] * p); + } + return ended; + } + // @internal + finish() { + this._ending.forEach((callback) => { + try { + callback.call(this._owner); + } catch (e) { + console.error(e); + } + }); + return this._next; + } + tween(duration, delay) { + return this._next = new Tween(this._owner, duration, delay); + } + duration(duration) { + this._duration = duration; + return this; + } + delay(delay) { + this._delay = delay; + return this; + } + ease(easing) { + this._easing = Easing.get(easing); + return this; + } + done(fn) { + this._ending.push(fn); + return this; + } + hide() { + this._ending.push(function() { + this.hide(); + }); + this._hide = true; + return this; + } + remove() { + this._ending.push(function() { + this.remove(); + }); + this._remove = true; + return this; + } + pin(a, b) { + if (typeof a === "object") { + for (var attr in a) { + pinning(this._owner, this._end, attr, a[attr]); + } + } else if (typeof b !== "undefined") { + pinning(this._owner, this._end, a, b); + } + return this; + } + /** + * @deprecated Use .done(fn) instead. + */ + then(fn) { + this.done(fn); + return this; + } + /** + * @deprecated this doesn't do anything anymore, call tween on the node instead. + */ + clear(forward) { + return this; + } +} +function pinning(node, map, key, value) { + if (typeof node.pin(key) === "number") { + map[key] = value; + } else if (typeof node.pin(key + "X") === "number" && typeof node.pin(key + "Y") === "number") { + map[key + "X"] = value; + map[key + "Y"] = value; + } +} +Pin._add_shortcuts(Tween.prototype); +const _stages = []; +const pause = function() { + for (let i = _stages.length - 1; i >= 0; i--) { + _stages[i].pause(); + } +}; +const resume = function() { + for (let i = _stages.length - 1; i >= 0; i--) { + _stages[i].resume(); + } +}; +const mount = function(configs = {}) { + let root = new Root(); + root.mount(configs); + root.mouse = new Mouse().mount(root, root.dom); + return root; +}; +class Root extends Node { + constructor() { + super(); + __publicField(this, "canvas", null); + __publicField(this, "dom", null); + __publicField(this, "context", null); + __publicField(this, "pixelWidth", -1); + __publicField(this, "pixelHeight", -1); + __publicField(this, "pixelRatio", 1); + __publicField(this, "drawingWidth", 0); + __publicField(this, "drawingHeight", 0); + __publicField(this, "mounted", false); + __publicField(this, "paused", false); + __publicField(this, "sleep", false); + __publicField(this, "mount", (configs = {}) => { + if (typeof configs.canvas === "string") { + this.canvas = document.getElementById(configs.canvas); + } else if (configs.canvas instanceof HTMLCanvasElement) { + this.canvas = configs.canvas; + } else if (configs.canvas) + ; + if (!this.canvas) { + this.canvas = document.getElementById("cutjs") || document.getElementById("stage"); + } + if (!this.canvas) { + console.log("Creating Canvas..."); + this.canvas = document.createElement("canvas"); + Object.assign(this.canvas.style, { + position: "absolute", + display: "block", + top: "0", + left: "0", + bottom: "0", + right: "0", + width: "100%", + height: "100%" + }); + let body = document.body; + body.insertBefore(this.canvas, body.firstChild); + } + this.dom = this.canvas; + this.context = this.canvas.getContext("2d"); + this.devicePixelRatio = window.devicePixelRatio || 1; + this.backingStoreRatio = this.context.webkitBackingStorePixelRatio || this.context.mozBackingStorePixelRatio || this.context.msBackingStorePixelRatio || this.context.oBackingStorePixelRatio || this.context.backingStorePixelRatio || 1; + this.pixelRatio = this.devicePixelRatio / this.backingStoreRatio; + this.mounted = true; + _stages.push(this); + this.requestFrame(this.onFrame); + }); + __publicField(this, "frameRequested", false); + __publicField(this, "requestFrame", () => { + if (!this.frameRequested) { + this.frameRequested = true; + requestAnimationFrame(this.onFrame); + } + }); + __publicField(this, "lastTime", 0); + __publicField(this, "_mo_touch", null); + // monitor touch + __publicField(this, "onFrame", (now) => { + this.frameRequested = false; + if (!this.mounted) { + return; + } + this.requestFrame(); + const newPixelWidth = this.canvas.clientWidth; + const newPixelHeight = this.canvas.clientHeight; + if (this.pixelWidth !== newPixelWidth || this.pixelHeight !== newPixelHeight) { + this.pixelWidth = newPixelWidth; + this.pixelHeight = newPixelHeight; + this.drawingWidth = newPixelWidth * this.pixelRatio; + this.drawingHeight = newPixelHeight * this.pixelRatio; + if (this.canvas.width !== this.drawingWidth || this.canvas.height !== this.drawingHeight) { + this.canvas.width = this.drawingWidth; + this.canvas.height = this.drawingHeight; + console.log("Resize: [" + this.drawingWidth + ", " + this.drawingHeight + "] = " + this.pixelRatio + " x [" + this.pixelWidth + ", " + this.pixelHeight + "]"); + this.viewport({ + width: this.drawingWidth, + height: this.drawingHeight, + ratio: this.pixelRatio + }); + } + } + let last = this.lastTime || now; + let elapsed = now - last; + if (!this.mounted || this.paused || this.sleep) { + return; + } + this.lastTime = now; + let tickRequest = this._tick(elapsed, now, last); + if (this._mo_touch != this._ts_touch) { + this._mo_touch = this._ts_touch; + this.sleep = false; + if (this.drawingWidth > 0 && this.drawingHeight > 0) { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0, 0, this.drawingWidth, this.drawingHeight); + this.render(this.context); + } + } else if (tickRequest) { + this.sleep = false; + } else { + this.sleep = true; + } + stats$1.fps = elapsed ? 1e3 / elapsed : 0; + }); + this.label("Root"); + } + resume() { + if (this.sleep || this.paused) { + this.requestFrame(); + } + this.paused = false; + this.sleep = false; + this.publish("resume"); + return this; + } + pause() { + if (!this.paused) { + this.publish("pause"); + } + this.paused = true; + return this; + } + touch() { + if (this.sleep || this.paused) { + this.requestFrame(); + } + this.sleep = false; + return Node.prototype.touch(); + } + unmount() { + var _a; + this.mounted = false; + let index = _stages.indexOf(this); + if (index >= 0) { + _stages.splice(index, 1); + } + (_a = this.mouse) == null ? void 0 : _a.unmount(); + return this; + } + background(color) { + this.dom.style.backgroundColor = color; + return this; + } + /** + * Set/Get viewport. + * This is used along with viewbox to determine the scale and position of the viewbox within the viewport. + * Viewport is the size of the container, for example size of the canvas element. + * Viewbox is provided by the user, and is the ideal size of the content. + */ + viewport(width, height, ratio) { + if (typeof width === "undefined") { + return Object.assign({}, this._viewport); + } + if (typeof width === "object") { + const options = width; + width = options.width; + height = options.height; + ratio = options.ratio; + } + this._viewport = { + width, + height, + ratio: ratio || 1 + }; + this.viewbox(); + let data = Object.assign({}, this._viewport); + this.visit({ + start: function(node) { + if (!node._flag("viewport")) { + return true; + } + node.publish("viewport", [data]); + } + }); + return this; + } + /** + * Set viewbox. + * + * @param {mode} string - One of: 'in-pad' (like css object-fit: 'contain'), 'in', 'out-crop' (like css object-fit: 'cover'), 'out' + */ + // TODO: static/fixed viewbox + // TODO: use css object-fit values + viewbox(width, height, mode) { + if (typeof width === "number" && typeof height === "number") { + this._viewbox = { + width, + height, + mode + }; + } else if (typeof width === "object" && width !== null) { + this._viewbox = { + ...width + }; + } + this.rescale(); + return this; + } + camera(matrix) { + this._camera = matrix; + this.rescale(); + return this; + } + rescale() { + let viewbox = this._viewbox; + let viewport = this._viewport; + let camera = this._camera; + if (viewport && viewbox) { + const viewportWidth = viewport.width; + const viewportHeight = viewport.height; + const viewboxMode = /^(in|out|in-pad|out-crop)$/.test(viewbox.mode) ? viewbox.mode : "in-pad"; + const viewboxWidth = viewbox.width; + const viewboxHeight = viewbox.height; + this.pin({ + width: viewboxWidth, + height: viewboxHeight + }); + this.scaleTo(viewportWidth, viewportHeight, viewboxMode); + const viewboxX = viewbox.x || 0; + const viewboxY = viewbox.y || 0; + const cameraZoom = (camera == null ? void 0 : camera.a) || 1; + const cameraX = (camera == null ? void 0 : camera.e) || 0; + const cameraY = (camera == null ? void 0 : camera.f) || 0; + const scaleX = this.pin("scaleX"); + const scaleY = this.pin("scaleY"); + this.pin("scaleX", scaleX * cameraZoom); + this.pin("scaleY", scaleY * cameraZoom); + this.pin("offsetX", cameraX - viewboxX * scaleX * cameraZoom); + this.pin("offsetY", cameraY - viewboxY * scaleY * cameraZoom); + } else if (viewport) { + this.pin({ + width: viewport.width, + height: viewport.height + }); + } + return this; + } +} +const sprite = function(frame) { + var sprite2 = new Sprite(); + frame && sprite2.texture(frame); + return sprite2; +}; +Sprite._super = Node; +Sprite.prototype = Object.create(Sprite._super.prototype); +function Sprite() { + Sprite._super.call(this); + this.label("Sprite"); + this._textures = []; + this._image = null; +} +Sprite.prototype.texture = function(frame) { + this._image = texture(frame).one(); + this.pin("width", this._image ? this._image.width : 0); + this.pin("height", this._image ? this._image.height : 0); + this._textures[0] = this._image.pipe(); + this._textures.length = 1; + return this; +}; +Sprite.prototype.tile = function(inner) { + this._repeat(false, inner); + return this; +}; +Sprite.prototype.stretch = function(inner) { + this._repeat(true, inner); + return this; +}; +Sprite.prototype._repeat = function(stretch, inner) { + var self = this; + this.untick(this._repeatTicker); + this.tick(this._repeatTicker = function() { + if (this._mo_stretch == this._pin._ts_transform) { + return; + } + this._mo_stretch = this._pin._ts_transform; + var width = this.pin("width"); + var height = this.pin("height"); + this._textures.length = repeat(this._image, width, height, stretch, inner, insert); + }); + function insert(i, sx, sy, sw, sh, dx, dy, dw, dh) { + var repeat2 = self._textures.length > i ? self._textures[i] : self._textures[i] = self._image.pipe(); + repeat2.src(sx, sy, sw, sh); + repeat2.dest(dx, dy, dw, dh); + } +}; +function repeat(img, owidth, oheight, stretch, inner, insert) { + var width = img.width; + var height = img.height; + var left = img.left; + var right = img.right; + var top = img.top; + var bottom = img.bottom; + left = typeof left === "number" && left === left ? left : 0; + right = typeof right === "number" && right === right ? right : 0; + top = typeof top === "number" && top === top ? top : 0; + bottom = typeof bottom === "number" && bottom === bottom ? bottom : 0; + width = width - left - right; + height = height - top - bottom; + if (!inner) { + owidth = Math.max(owidth - left - right, 0); + oheight = Math.max(oheight - top - bottom, 0); + } + var i = 0; + if (top > 0 && left > 0) + insert(i++, 0, 0, left, top, 0, 0, left, top); + if (bottom > 0 && left > 0) + insert(i++, 0, height + top, left, bottom, 0, oheight + top, left, bottom); + if (top > 0 && right > 0) + insert(i++, width + left, 0, right, top, owidth + left, 0, right, top); + if (bottom > 0 && right > 0) + insert( + i++, + width + left, + height + top, + right, + bottom, + owidth + left, + oheight + top, + right, + bottom + ); + if (stretch) { + if (top > 0) + insert(i++, left, 0, width, top, left, 0, owidth, top); + if (bottom > 0) + insert( + i++, + left, + height + top, + width, + bottom, + left, + oheight + top, + owidth, + bottom + ); + if (left > 0) + insert(i++, 0, top, left, height, 0, top, left, oheight); + if (right > 0) + insert( + i++, + width + left, + top, + right, + height, + owidth + left, + top, + right, + oheight + ); + insert(i++, left, top, width, height, left, top, owidth, oheight); + } else { + var l = left, r = owidth, w; + while (r > 0) { + w = Math.min(width, r), r -= width; + var t = top, b = oheight, h; + while (b > 0) { + h = Math.min(height, b), b -= height; + insert(i++, left, top, w, h, l, t, w, h); + if (r <= 0) { + if (left) + insert(i++, 0, top, left, h, 0, t, left, h); + if (right) + insert(i++, width + left, top, right, h, l + w, t, right, h); + } + t += h; + } + if (top) + insert(i++, left, 0, w, top, l, 0, w, top); + if (bottom) + insert(i++, left, height + top, w, bottom, l, t, w, bottom); + l += w; + } + } + return i; +} +Sprite.prototype.image = Sprite.prototype.texture; +const image = sprite; +const Image$1 = Sprite; +const anim = function(frames, fps) { + var anim2 = new Anim(); + anim2.frames(frames).gotoFrame(0); + fps && anim2.fps(fps); + return anim2; +}; +Anim._super = Node; +Anim.prototype = Object.create(Anim._super.prototype); +const FPS = 15; +function Anim() { + Anim._super.call(this); + this.label("Anim"); + this._textures = []; + this._fps = FPS; + this._ft = 1e3 / this._fps; + this._time = -1; + this._repeat = 0; + this._index = 0; + this._frames = []; + var lastTime = 0; + this.tick(function(t, now, last) { + if (this._time < 0 || this._frames.length <= 1) { + return; + } + var ignore = lastTime != last; + lastTime = now; + if (ignore) { + return true; + } + this._time += t; + if (this._time < this._ft) { + return true; + } + var n = this._time / this._ft | 0; + this._time -= n * this._ft; + this.moveFrame(n); + if (this._repeat > 0 && (this._repeat -= n) <= 0) { + this.stop(); + this._callback && this._callback(); + return false; + } + return true; + }, false); +} +Anim.prototype.fps = function(fps) { + if (typeof fps === "undefined") { + return this._fps; + } + this._fps = fps > 0 ? fps : FPS; + this._ft = 1e3 / this._fps; + return this; +}; +Anim.prototype.setFrames = function(a, b, c) { + return this.frames(a, b, c); +}; +Anim.prototype.frames = function(frames) { + this._index = 0; + this._frames = texture(frames).array(); + this.touch(); + return this; +}; +Anim.prototype.length = function() { + return this._frames ? this._frames.length : 0; +}; +Anim.prototype.gotoFrame = function(frame, resize) { + this._index = math$1.wrap(frame, this._frames.length) | 0; + resize = resize || !this._textures[0]; + this._textures[0] = this._frames[this._index]; + if (resize) { + this.pin("width", this._textures[0].width); + this.pin("height", this._textures[0].height); + } + this.touch(); + return this; +}; +Anim.prototype.moveFrame = function(move) { + return this.gotoFrame(this._index + move); +}; +Anim.prototype.repeat = function(repeat2, callback) { + this._repeat = repeat2 * this._frames.length - 1; + this._callback = callback; + this.play(); + return this; +}; +Anim.prototype.play = function(frame) { + if (typeof frame !== "undefined") { + this.gotoFrame(frame); + this._time = 0; + } else if (this._time < 0) { + this._time = 0; + } + this.touch(); + return this; +}; +Anim.prototype.stop = function(frame) { + this._time = -1; + if (typeof frame !== "undefined") { + this.gotoFrame(frame); + } + return this; +}; +const string$1 = function(frames) { + return new Str().frames(frames); +}; +Str._super = Node; +Str.prototype = Object.create(Str._super.prototype); +function Str() { + Str._super.call(this); + this.label("String"); + this._textures = []; +} +Str.prototype.setFont = function(a, b, c) { + return this.frames(a, b, c); +}; +Str.prototype.frames = function(frames) { + this._textures = []; + if (typeof frames == "string") { + frames = texture(frames); + this._item = function(value) { + return frames.one(value); + }; + } else if (typeof frames === "object") { + this._item = function(value) { + return frames[value]; + }; + } else if (typeof frames === "function") { + this._item = frames; + } + return this; +}; +Str.prototype.setValue = function(a, b, c) { + return this.value(a, b, c); +}; +Str.prototype.value = function(value) { + if (typeof value === "undefined") { + return this._value; + } + if (this._value === value) { + return this; + } + this._value = value; + if (value === null) { + value = ""; + } else if (typeof value !== "string" && !Array.isArray(value)) { + value = value.toString(); + } + this._spacing = this._spacing || 0; + var width = 0, height = 0; + for (var i = 0; i < value.length; i++) { + var texture2 = this._textures[i] = this._item(value[i]); + width += i > 0 ? this._spacing : 0; + texture2.dest(width, 0); + width = width + texture2.width; + height = Math.max(height, texture2.height); + } + this.pin("width", width); + this.pin("height", height); + this._textures.length = value.length; + return this; +}; +const row = function(align) { + return create().row(align).label("Row"); +}; +Node.prototype.row = function(align) { + this.align("row", align); + return this; +}; +const column = function(align) { + return create().column(align).label("Row"); +}; +Node.prototype.column = function(align) { + this.align("column", align); + return this; +}; +Node.prototype.align = function(type, align) { + this._padding = this._padding || 0; + this._spacing = this._spacing || 0; + this.untick(this._layoutTiker); + this.tick(this._layoutTiker = function() { + if (this._mo_seq == this._ts_touch) { + return; + } + this._mo_seq = this._ts_touch; + var alignChildren = this._mo_seqAlign != this._ts_children; + this._mo_seqAlign = this._ts_children; + var width = 0, height = 0; + var child, next = this.first(true); + var first = true; + while (child = next) { + next = child.next(true); + child.matrix(true); + var w = child.pin("boxWidth"); + var h = child.pin("boxHeight"); + if (type == "column") { + !first && (height += this._spacing); + child.pin("offsetY") != height && child.pin("offsetY", height); + width = Math.max(width, w); + height = height + h; + alignChildren && child.pin("alignX", align); + } else if (type == "row") { + !first && (width += this._spacing); + child.pin("offsetX") != width && child.pin("offsetX", width); + width = width + w; + height = Math.max(height, h); + alignChildren && child.pin("alignY", align); + } + first = false; + } + width += 2 * this._padding; + height += 2 * this._padding; + this.pin("width") != width && this.pin("width", width); + this.pin("height") != height && this.pin("height", height); + }); + return this; +}; +const box = function() { + return create().box().label("Box"); +}; +Node.prototype.box = function() { + this._padding = this._padding || 0; + this.untick(this._layoutTiker); + this.tick(this._layoutTiker = function() { + if (this._mo_box == this._ts_touch) { + return; + } + this._mo_box = this._ts_touch; + var width = 0, height = 0; + var child, next = this.first(true); + while (child = next) { + next = child.next(true); + child.matrix(true); + var w = child.pin("boxWidth"); + var h = child.pin("boxHeight"); + width = Math.max(width, w); + height = Math.max(height, h); + } + width += 2 * this._padding; + height += 2 * this._padding; + this.pin("width") != width && this.pin("width", width); + this.pin("height") != height && this.pin("height", height); + }); + return this; +}; +const layer = function() { + return create().layer().label("Layer"); +}; +Node.prototype.layer = function() { + this.untick(this._layoutTiker); + this.tick(this._layoutTiker = function() { + var parent = this.parent(); + if (parent) { + var width = parent.pin("width"); + if (this.pin("width") != width) { + this.pin("width", width); + } + var height = parent.pin("height"); + if (this.pin("height") != height) { + this.pin("height", height); + } + } + }, true); + return this; +}; +Node.prototype.padding = function(pad) { + this._padding = pad; + return this; +}; +Node.prototype.spacing = function(space) { + this._spacing = space; + return this; +}; +const Stage$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ + __proto__: null, + Anim, + Atlas, + Image: Image$1, + Math: math$1, + Matrix, + Mouse, + Node, + Pin, + Root, + Sprite, + Str, + Texture, + Tween, + anim, + atlas, + box, + canvas, + column, + create, + image, + layer, + math: math$1, + memoizeDraw, + mount, + pause, + resume, + row, + sprite, + string: string$1, + texture +}, Symbol.toStringTag, { value: "Module" })); + +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; + +var options = function (input, defaults) { + if (input === null || typeof input === 'undefined') { + // tslint:disable-next-line:no-object-literal-type-assertion + input = {}; + } + var output = __assign({}, input); + // tslint:disable-next-line:no-for-in + for (var key in defaults) { + if (defaults.hasOwnProperty(key) && typeof input[key] === 'undefined') { + output[key] = defaults[key]; + } + } + if (typeof Object.getOwnPropertySymbols === 'function') { + var symbols = Object.getOwnPropertySymbols(defaults); + for (var i = 0; i < symbols.length; i++) { + var symbol = symbols[i]; + if (defaults.propertyIsEnumerable(symbol) && typeof input[symbol] === 'undefined') { + output[symbol] = defaults[symbol]; + } + } + } + return output; +}; + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +var math = Object.assign(Object.create(Math), { + EPSILON: 1e-9, + /** + * This function is used to ensure that a floating point number is not a NaN or + * infinity. + */ + isFinite: function (x) { + return (typeof x === 'number') && isFinite(x) && !isNaN(x); + }, + assert: function (x) { + }, + /** + * Next Largest Power of 2 Given a binary integer value x, the next largest + * power of 2 can be computed by a SWAR algorithm that recursively "folds" the + * upper bits into the lower bits. This process yields a bit vector with the + * same most significant 1 as x, but all 1's below it. Adding 1 to that value + * yields the next largest power of 2. For a 32-bit value: + */ + nextPowerOfTwo: function (x) { + // TODO + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + return x + 1; + }, + isPowerOfTwo: function (x) { + return x > 0 && (x & (x - 1)) === 0; + }, + mod: function (num, min, max) { + if (typeof min === 'undefined') { + max = 1; + min = 0; + } + else if (typeof max === 'undefined') { + max = min; + min = 0; + } + if (max > min) { + num = (num - min) % (max - min); + return num + (num < 0 ? max : min); + } + else { + num = (num - max) % (min - max); + return num + (num <= 0 ? min : max); + } + }, + /** + * Returns a min if num is less than min, and max if more than max, otherwise returns num. + */ + clamp: function (num, min, max) { + if (num < min) { + return min; + } + else if (num > max) { + return max; + } + else { + return num; + } + }, + /** + * Returns a random number between min and max when two arguments are provided. + * If one arg is provided between 0 to max. + * If one arg is passed between 0 to 1. + */ + random: function (min, max) { + if (typeof min === 'undefined') { + max = 1; + min = 0; + } + else if (typeof max === 'undefined') { + max = min; + min = 0; + } + return min === max ? min : Math.random() * (max - min) + min; + } +}); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +var Vec2 = /** @class */ (function () { + // tslint:disable-next-line:typedef + function Vec2(x, y) { + if (!(this instanceof Vec2)) { + return new Vec2(x, y); + } + if (typeof x === 'undefined') { + this.x = 0; + this.y = 0; + } + else if (typeof x === 'object') { + this.x = x.x; + this.y = x.y; + } + else { + this.x = x; + this.y = y; + } + } + /** @internal */ + Vec2.prototype._serialize = function () { + return { + x: this.x, + y: this.y + }; + }; + /** @internal */ + Vec2._deserialize = function (data) { + var obj = Object.create(Vec2.prototype); + obj.x = data.x; + obj.y = data.y; + return obj; + }; + Vec2.zero = function () { + var obj = Object.create(Vec2.prototype); + obj.x = 0; + obj.y = 0; + return obj; + }; + /** @internal */ + Vec2.neo = function (x, y) { + var obj = Object.create(Vec2.prototype); + obj.x = x; + obj.y = y; + return obj; + }; + Vec2.clone = function (v) { + return Vec2.neo(v.x, v.y); + }; + /** @internal */ + Vec2.prototype.toString = function () { + return JSON.stringify(this); + }; + /** + * Does this vector contain finite coordinates? + */ + Vec2.isValid = function (obj) { + if (obj === null || typeof obj === 'undefined') { + return false; + } + return math.isFinite(obj.x) && math.isFinite(obj.y); + }; + Vec2.assert = function (o) { + }; + Vec2.prototype.clone = function () { + return Vec2.clone(this); + }; + /** + * Set this vector to all zeros. + * + * @returns this + */ + Vec2.prototype.setZero = function () { + this.x = 0.0; + this.y = 0.0; + return this; + }; + /** + * Set this vector to some specified coordinates. + * + * @returns this + */ + // tslint:disable-next-line:typedef + Vec2.prototype.set = function (x, y) { + if (typeof x === 'object') { + this.x = x.x; + this.y = x.y; + } + else { + this.x = x; + this.y = y; + } + return this; + }; + /** + * Set this vector to some specified coordinates. + * + * @returns this + */ + Vec2.prototype.setNum = function (x, y) { + this.x = x; + this.y = y; + return this; + }; + /** + * Set this vector to some specified coordinates. + * + * @returns this + */ + Vec2.prototype.setVec2 = function (value) { + this.x = value.x; + this.y = value.y; + return this; + }; + /** + * @internal + * @deprecated Use setCombine or setMul + */ + Vec2.prototype.wSet = function (a, v, b, w) { + if (typeof b !== 'undefined' || typeof w !== 'undefined') { + return this.setCombine(a, v, b, w); + } + else { + return this.setMul(a, v); + } + }; + /** + * Set linear combination of v and w: `a * v + b * w` + */ + Vec2.prototype.setCombine = function (a, v, b, w) { + var x = a * v.x + b * w.x; + var y = a * v.y + b * w.y; + // `this` may be `w` + this.x = x; + this.y = y; + return this; + }; + Vec2.prototype.setMul = function (a, v) { + var x = a * v.x; + var y = a * v.y; + this.x = x; + this.y = y; + return this; + }; + /** + * Add a vector to this vector. + * + * @returns this + */ + Vec2.prototype.add = function (w) { + this.x += w.x; + this.y += w.y; + return this; + }; + /** + * @internal + * @deprecated Use addCombine or addMul + */ + Vec2.prototype.wAdd = function (a, v, b, w) { + if (typeof b !== 'undefined' || typeof w !== 'undefined') { + return this.addCombine(a, v, b, w); + } + else { + return this.addMul(a, v); + } + }; + /** + * Add linear combination of v and w: `a * v + b * w` + */ + Vec2.prototype.addCombine = function (a, v, b, w) { + var x = a * v.x + b * w.x; + var y = a * v.y + b * w.y; + // `this` may be `w` + this.x += x; + this.y += y; + return this; + }; + Vec2.prototype.addMul = function (a, v) { + var x = a * v.x; + var y = a * v.y; + this.x += x; + this.y += y; + return this; + }; + /** + * @deprecated Use subCombine or subMul + */ + Vec2.prototype.wSub = function (a, v, b, w) { + if (typeof b !== 'undefined' || typeof w !== 'undefined') { + return this.subCombine(a, v, b, w); + } + else { + return this.subMul(a, v); + } + }; + /** + * Subtract linear combination of v and w: `a * v + b * w` + */ + Vec2.prototype.subCombine = function (a, v, b, w) { + var x = a * v.x + b * w.x; + var y = a * v.y + b * w.y; + // `this` may be `w` + this.x -= x; + this.y -= y; + return this; + }; + Vec2.prototype.subMul = function (a, v) { + var x = a * v.x; + var y = a * v.y; + this.x -= x; + this.y -= y; + return this; + }; + /** + * Subtract a vector from this vector + * + * @returns this + */ + Vec2.prototype.sub = function (w) { + this.x -= w.x; + this.y -= w.y; + return this; + }; + /** + * Multiply this vector by a scalar. + * + * @returns this + */ + Vec2.prototype.mul = function (m) { + this.x *= m; + this.y *= m; + return this; + }; + /** + * Get the length of this vector (the norm). + * + * For performance, use this instead of lengthSquared (if possible). + */ + Vec2.prototype.length = function () { + return Vec2.lengthOf(this); + }; + /** + * Get the length squared. + */ + Vec2.prototype.lengthSquared = function () { + return Vec2.lengthSquared(this); + }; + /** + * Convert this vector into a unit vector. + * + * @returns old length + */ + Vec2.prototype.normalize = function () { + var length = this.length(); + if (length < math.EPSILON) { + return 0.0; + } + var invLength = 1.0 / length; + this.x *= invLength; + this.y *= invLength; + return length; + }; + /** + * Get the length of this vector (the norm). + * + * For performance, use this instead of lengthSquared (if possible). + */ + Vec2.lengthOf = function (v) { + return math.sqrt(v.x * v.x + v.y * v.y); + }; + /** + * Get the length squared. + */ + Vec2.lengthSquared = function (v) { + return v.x * v.x + v.y * v.y; + }; + Vec2.distance = function (v, w) { + var dx = v.x - w.x; + var dy = v.y - w.y; + return math.sqrt(dx * dx + dy * dy); + }; + Vec2.distanceSquared = function (v, w) { + var dx = v.x - w.x; + var dy = v.y - w.y; + return dx * dx + dy * dy; + }; + Vec2.areEqual = function (v, w) { + return v === w || typeof w === 'object' && w !== null && v.x === w.x && v.y === w.y; + }; + /** + * Get the skew vector such that dot(skew_vec, other) == cross(vec, other) + */ + Vec2.skew = function (v) { + return Vec2.neo(-v.y, v.x); + }; + /** + * Perform the dot product on two vectors. + */ + Vec2.dot = function (v, w) { + return v.x * w.x + v.y * w.y; + }; + /** + * Perform the cross product on two vectors. In 2D this produces a scalar. + * + * Perform the cross product on a vector and a scalar. In 2D this produces a + * vector. + */ + // tslint:disable-next-line:typedef + Vec2.cross = function (v, w) { + if (typeof w === 'number') { + return Vec2.neo(w * v.y, -w * v.x); + } + else if (typeof v === 'number') { + return Vec2.neo(-v * w.y, v * w.x); + } + else { + return v.x * w.y - v.y * w.x; + } + }; + /** + * Perform the cross product on two vectors. In 2D this produces a scalar. + */ + Vec2.crossVec2Vec2 = function (v, w) { + return v.x * w.y - v.y * w.x; + }; + /** + * Perform the cross product on a vector and a scalar. In 2D this produces a + * vector. + */ + Vec2.crossVec2Num = function (v, w) { + return Vec2.neo(w * v.y, -w * v.x); + }; + /** + * Perform the cross product on a vector and a scalar. In 2D this produces a + * vector. + */ + Vec2.crossNumVec2 = function (v, w) { + return Vec2.neo(-v * w.y, v * w.x); + }; + /** + * Returns `a + (v x w)` + */ + // tslint:disable-next-line:typedef + Vec2.addCross = function (a, v, w) { + if (typeof w === 'number') { + return Vec2.neo(w * v.y + a.x, -w * v.x + a.y); + } + else if (typeof v === 'number') { + return Vec2.neo(-v * w.y + a.x, v * w.x + a.y); + } + }; + /** + * Returns `a + (v x w)` + */ + Vec2.addCrossVec2Num = function (a, v, w) { + return Vec2.neo(w * v.y + a.x, -w * v.x + a.y); + }; + /** + * Returns `a + (v x w)` + */ + Vec2.addCrossNumVec2 = function (a, v, w) { + return Vec2.neo(-v * w.y + a.x, v * w.x + a.y); + }; + Vec2.add = function (v, w) { + return Vec2.neo(v.x + w.x, v.y + w.y); + }; + /** @internal @deprecated */ + Vec2.wAdd = function (a, v, b, w) { + if (typeof b !== 'undefined' || typeof w !== 'undefined') { + return Vec2.combine(a, v, b, w); + } + else { + return Vec2.mulNumVec2(a, v); + } + }; + Vec2.combine = function (a, v, b, w) { + return Vec2.zero().setCombine(a, v, b, w); + }; + Vec2.sub = function (v, w) { + return Vec2.neo(v.x - w.x, v.y - w.y); + }; + // tslint:disable-next-line:typedef + Vec2.mul = function (a, b) { + if (typeof a === 'object') { + return Vec2.neo(a.x * b, a.y * b); + } + else if (typeof b === 'object') { + return Vec2.neo(a * b.x, a * b.y); + } + }; + Vec2.mulVec2Num = function (a, b) { + return Vec2.neo(a.x * b, a.y * b); + }; + Vec2.mulNumVec2 = function (a, b) { + return Vec2.neo(a * b.x, a * b.y); + }; + Vec2.prototype.neg = function () { + this.x = -this.x; + this.y = -this.y; + return this; + }; + Vec2.neg = function (v) { + return Vec2.neo(-v.x, -v.y); + }; + Vec2.abs = function (v) { + return Vec2.neo(math.abs(v.x), math.abs(v.y)); + }; + Vec2.mid = function (v, w) { + return Vec2.neo((v.x + w.x) * 0.5, (v.y + w.y) * 0.5); + }; + Vec2.upper = function (v, w) { + return Vec2.neo(math.max(v.x, w.x), math.max(v.y, w.y)); + }; + Vec2.lower = function (v, w) { + return Vec2.neo(math.min(v.x, w.x), math.min(v.y, w.y)); + }; + Vec2.prototype.clamp = function (max) { + var lengthSqr = this.x * this.x + this.y * this.y; + if (lengthSqr > max * max) { + var scale = max / math.sqrt(lengthSqr); + this.x *= scale; + this.y *= scale; + } + return this; + }; + Vec2.clamp = function (v, max) { + var r = Vec2.neo(v.x, v.y); + r.clamp(max); + return r; + }; + /** @internal @deprecated */ + // tslint:disable-next-line:typedef + Vec2.scaleFn = function (x, y) { + return function (v) { + return Vec2.neo(v.x * x, v.y * y); + }; + }; + /** @internal @deprecated */ + // tslint:disable-next-line:typedef + Vec2.translateFn = function (x, y) { + return function (v) { + return Vec2.neo(v.x + x, v.y + y); + }; + }; + return Vec2; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +var AABB = /** @class */ (function () { + function AABB(lower, upper) { + if (!(this instanceof AABB)) { + return new AABB(lower, upper); + } + this.lowerBound = Vec2.zero(); + this.upperBound = Vec2.zero(); + if (typeof lower === 'object') { + this.lowerBound.setVec2(lower); + } + if (typeof upper === 'object') { + this.upperBound.setVec2(upper); + } + else if (typeof lower === 'object') { + this.upperBound.setVec2(lower); + } + } + /** + * Verify that the bounds are sorted. + */ + AABB.prototype.isValid = function () { + return AABB.isValid(this); + }; + AABB.isValid = function (obj) { + if (obj === null || typeof obj === 'undefined') { + return false; + } + return Vec2.isValid(obj.lowerBound) && Vec2.isValid(obj.upperBound) && Vec2.sub(obj.upperBound, obj.lowerBound).lengthSquared() >= 0; + }; + AABB.assert = function (o) { + }; + /** + * Get the center of the AABB. + */ + AABB.prototype.getCenter = function () { + return Vec2.neo((this.lowerBound.x + this.upperBound.x) * 0.5, (this.lowerBound.y + this.upperBound.y) * 0.5); + }; + /** + * Get the extents of the AABB (half-widths). + */ + AABB.prototype.getExtents = function () { + return Vec2.neo((this.upperBound.x - this.lowerBound.x) * 0.5, (this.upperBound.y - this.lowerBound.y) * 0.5); + }; + /** + * Get the perimeter length. + */ + AABB.prototype.getPerimeter = function () { + return 2.0 * (this.upperBound.x - this.lowerBound.x + this.upperBound.y - this.lowerBound.y); + }; + /** + * Combine one or two AABB into this one. + */ + AABB.prototype.combine = function (a, b) { + b = b || this; + var lowerA = a.lowerBound; + var upperA = a.upperBound; + var lowerB = b.lowerBound; + var upperB = b.upperBound; + var lowerX = math.min(lowerA.x, lowerB.x); + var lowerY = math.min(lowerA.y, lowerB.y); + var upperX = math.max(upperB.x, upperA.x); + var upperY = math.max(upperB.y, upperA.y); + this.lowerBound.setNum(lowerX, lowerY); + this.upperBound.setNum(upperX, upperY); + }; + AABB.prototype.combinePoints = function (a, b) { + this.lowerBound.setNum(math.min(a.x, b.x), math.min(a.y, b.y)); + this.upperBound.setNum(math.max(a.x, b.x), math.max(a.y, b.y)); + }; + AABB.prototype.set = function (aabb) { + this.lowerBound.setNum(aabb.lowerBound.x, aabb.lowerBound.y); + this.upperBound.setNum(aabb.upperBound.x, aabb.upperBound.y); + }; + AABB.prototype.contains = function (aabb) { + var result = true; + result = result && this.lowerBound.x <= aabb.lowerBound.x; + result = result && this.lowerBound.y <= aabb.lowerBound.y; + result = result && aabb.upperBound.x <= this.upperBound.x; + result = result && aabb.upperBound.y <= this.upperBound.y; + return result; + }; + AABB.prototype.extend = function (value) { + AABB.extend(this, value); + return this; + }; + AABB.extend = function (aabb, value) { + aabb.lowerBound.x -= value; + aabb.lowerBound.y -= value; + aabb.upperBound.x += value; + aabb.upperBound.y += value; + }; + AABB.testOverlap = function (a, b) { + var d1x = b.lowerBound.x - a.upperBound.x; + var d2x = a.lowerBound.x - b.upperBound.x; + var d1y = b.lowerBound.y - a.upperBound.y; + var d2y = a.lowerBound.y - b.upperBound.y; + if (d1x > 0 || d1y > 0 || d2x > 0 || d2y > 0) { + return false; + } + return true; + }; + AABB.areEqual = function (a, b) { + return Vec2.areEqual(a.lowerBound, b.lowerBound) && Vec2.areEqual(a.upperBound, b.upperBound); + }; + AABB.diff = function (a, b) { + var wD = math.max(0, math.min(a.upperBound.x, b.upperBound.x) - math.max(b.lowerBound.x, a.lowerBound.x)); + var hD = math.max(0, math.min(a.upperBound.y, b.upperBound.y) - math.max(b.lowerBound.y, a.lowerBound.y)); + var wA = a.upperBound.x - a.lowerBound.x; + var hA = a.upperBound.y - a.lowerBound.y; + var wB = b.upperBound.x - b.lowerBound.x; + var hB = b.upperBound.y - b.lowerBound.y; + return wA * hA + wB * hB - wD * hD; + }; + AABB.prototype.rayCast = function (output, input) { + // From Real-time Collision Detection, p179. + var tmin = -Infinity; + var tmax = Infinity; + var p = input.p1; + var d = Vec2.sub(input.p2, input.p1); + var absD = Vec2.abs(d); + var normal = Vec2.zero(); + for (var f = 'x'; f !== null; f = (f === 'x' ? 'y' : null)) { + if (absD.x < math.EPSILON) { + // Parallel. + if (p[f] < this.lowerBound[f] || this.upperBound[f] < p[f]) { + return false; + } + } + else { + var inv_d = 1.0 / d[f]; + var t1 = (this.lowerBound[f] - p[f]) * inv_d; + var t2 = (this.upperBound[f] - p[f]) * inv_d; + // Sign of the normal vector. + var s = -1.0; + if (t1 > t2) { + var temp = t1; + t1 = t2; + t2 = temp; + s = 1.0; + } + // Push the min up + if (t1 > tmin) { + normal.setZero(); + normal[f] = s; + tmin = t1; + } + // Pull the max down + tmax = math.min(tmax, t2); + if (tmin > tmax) { + return false; + } + } + } + // Does the ray start inside the box? + // Does the ray intersect beyond the max fraction? + if (tmin < 0.0 || input.maxFraction < tmin) { + return false; + } + // Intersection. + output.fraction = tmin; + output.normal = normal; + return true; + }; + /** @internal */ + AABB.prototype.toString = function () { + return JSON.stringify(this); + }; + return AABB; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +// TODO merge with World options? +/** + * Tuning constants based on meters-kilograms-seconds (MKS) units. + */ +// tslint:disable-next-line:no-unnecessary-class +var Settings = /** @class */ (function () { + function Settings() { + } + Object.defineProperty(Settings, "linearSlopSquared", { + get: function () { return Settings.linearSlop * Settings.linearSlop; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Settings, "polygonRadius", { + /** + * The radius of the polygon/edge shape skin. This should not be modified. + * Making this smaller means polygons will have an insufficient buffer for + * continuous collision. Making it larger may create artifacts for vertex + * collision. + */ + get: function () { return 2.0 * Settings.linearSlop; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Settings, "maxTranslationSquared", { + get: function () { return Settings.maxTranslation * Settings.maxTranslation; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Settings, "maxRotationSquared", { + get: function () { return Settings.maxRotation * Settings.maxRotation; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Settings, "linearSleepToleranceSqr", { + get: function () { return Math.pow(Settings.linearSleepTolerance, 2); }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Settings, "angularSleepToleranceSqr", { + get: function () { return Math.pow(Settings.angularSleepTolerance, 2); }, + enumerable: false, + configurable: true + }); + // Collision + /** + * The maximum number of contact points between two convex shapes. Do not change + * this value. + */ + Settings.maxManifoldPoints = 2; + /** + * The maximum number of vertices on a convex polygon. You cannot increase this + * too much because BlockAllocator has a maximum object size. + */ + Settings.maxPolygonVertices = 12; + /** + * This is used to fatten AABBs in the dynamic tree. This allows proxies to move + * by a small amount without triggering a tree adjustment. This is in meters. + */ + Settings.aabbExtension = 0.1; + /** + * This is used to fatten AABBs in the dynamic tree. This is used to predict the + * future position based on the current displacement. This is a dimensionless + * multiplier. + */ + Settings.aabbMultiplier = 2.0; + /** + * A small length used as a collision and constraint tolerance. Usually it is + * chosen to be numerically significant, but visually insignificant. + */ + Settings.linearSlop = 0.005; + /** + * A small angle used as a collision and constraint tolerance. Usually it is + * chosen to be numerically significant, but visually insignificant. + */ + Settings.angularSlop = (2.0 / 180.0 * Math.PI); + /** + * Maximum number of sub-steps per contact in continuous physics simulation. + */ + Settings.maxSubSteps = 8; + // Dynamics + /** + * Maximum number of contacts to be handled to solve a TOI impact. + */ + Settings.maxTOIContacts = 32; + /** + * Maximum iterations to solve a TOI. + */ + Settings.maxTOIIterations = 20; + /** + * Maximum iterations to find Distance. + */ + Settings.maxDistnceIterations = 20; + /** + * A velocity threshold for elastic collisions. Any collision with a relative + * linear velocity below this threshold will be treated as inelastic. + */ + Settings.velocityThreshold = 1.0; + /** + * The maximum linear position correction used when solving constraints. This + * helps to prevent overshoot. + */ + Settings.maxLinearCorrection = 0.2; + /** + * The maximum angular position correction used when solving constraints. This + * helps to prevent overshoot. + */ + Settings.maxAngularCorrection = (8.0 / 180.0 * Math.PI); + /** + * The maximum linear velocity of a body. This limit is very large and is used + * to prevent numerical problems. You shouldn't need to adjust Settings. + */ + Settings.maxTranslation = 2.0; + /** + * The maximum angular velocity of a body. This limit is very large and is used + * to prevent numerical problems. You shouldn't need to adjust Settings. + */ + Settings.maxRotation = (0.5 * Math.PI); + /** + * This scale factor controls how fast overlap is resolved. Ideally this would + * be 1 so that overlap is removed in one time step. However using values close + * to 1 often lead to overshoot. + */ + Settings.baumgarte = 0.2; + Settings.toiBaugarte = 0.75; + // Sleep + /** + * The time that a body must be still before it will go to sleep. + */ + Settings.timeToSleep = 0.5; + /** + * A body cannot sleep if its linear velocity is above this tolerance. + */ + Settings.linearSleepTolerance = 0.01; + /** + * A body cannot sleep if its angular velocity is above this tolerance. + */ + Settings.angularSleepTolerance = (2.0 / 180.0 * Math.PI); + return Settings; +}()); + +/* + * Copyright (c) 2016-2018 Ali Shakiba http://shakiba.me/planck.js + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ +var Pool = /** @class */ (function () { + function Pool(opts) { + this._list = []; + this._max = Infinity; + this._createCount = 0; + this._outCount = 0; + this._inCount = 0; + this._discardCount = 0; + this._list = []; + this._max = opts.max || this._max; + this._createFn = opts.create; + this._outFn = opts.allocate; + this._inFn = opts.release; + this._discardFn = opts.discard; + } + Pool.prototype.max = function (n) { + if (typeof n === 'number') { + this._max = n; + return this; + } + return this._max; + }; + Pool.prototype.size = function () { + return this._list.length; + }; + Pool.prototype.allocate = function () { + var item; + if (this._list.length > 0) { + item = this._list.shift(); + } + else { + this._createCount++; + if (typeof this._createFn === 'function') { + item = this._createFn(); + } + else { + // tslint:disable-next-line:no-object-literal-type-assertion + item = {}; + } + } + this._outCount++; + if (typeof this._outFn === 'function') { + this._outFn(item); + } + return item; + }; + Pool.prototype.release = function (item) { + if (this._list.length < this._max) { + this._inCount++; + if (typeof this._inFn === 'function') { + this._inFn(item); + } + this._list.push(item); + } + else { + this._discardCount++; + if (typeof this._discardFn === 'function') { + item = this._discardFn(item); + } + } + }; + /** @internal */ + Pool.prototype.toString = function () { + return " +" + this._createCount + " >" + this._outCount + " <" + this._inCount + " -" + + this._discardCount + " =" + this._list.length + "/" + this._max; + }; + return Pool; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * A node in the dynamic tree. The client does not interact with this directly. + */ +var TreeNode = /** @class */ (function () { + function TreeNode(id) { + /** Enlarged AABB */ + this.aabb = new AABB(); + this.userData = null; + this.parent = null; + this.child1 = null; + this.child2 = null; + /** 0: leaf, -1: free node */ + this.height = -1; + this.id = id; + } + /** @internal */ + TreeNode.prototype.toString = function () { + return this.id + ": " + this.userData; + }; + TreeNode.prototype.isLeaf = function () { + return this.child1 == null; + }; + return TreeNode; +}()); +/** + * A dynamic AABB tree broad-phase, inspired by Nathanael Presson's btDbvt. A + * dynamic tree arranges data in a binary tree to accelerate queries such as + * volume queries and ray casts. Leafs are proxies with an AABB. In the tree we + * expand the proxy AABB by `aabbExtension` so that the proxy AABB is bigger + * than the client object. This allows the client object to move by small + * amounts without triggering a tree update. + * + * Nodes are pooled and relocatable, so we use node indices rather than + * pointers. + */ +var DynamicTree = /** @class */ (function () { + function DynamicTree() { + this.inputPool = new Pool({ + create: function () { + // tslint:disable-next-line:no-object-literal-type-assertion + return {}; + }, + release: function (stack) { + } + }); + this.stackPool = new Pool({ + create: function () { + return []; + }, + release: function (stack) { + stack.length = 0; + } + }); + this.iteratorPool = new Pool({ + create: function () { + return new Iterator(); + }, + release: function (iterator) { + iterator.close(); + } + }); + this.m_root = null; + this.m_nodes = {}; + this.m_lastProxyId = 0; + this.m_pool = new Pool({ + create: function () { + return new TreeNode(); + } + }); + } + /** + * Get proxy user data. + * + * @return the proxy user data or 0 if the id is invalid. + */ + DynamicTree.prototype.getUserData = function (id) { + var node = this.m_nodes[id]; + return node.userData; + }; + /** + * Get the fat AABB for a node id. + * + * @return the proxy user data or 0 if the id is invalid. + */ + DynamicTree.prototype.getFatAABB = function (id) { + var node = this.m_nodes[id]; + return node.aabb; + }; + DynamicTree.prototype.allocateNode = function () { + var node = this.m_pool.allocate(); + node.id = ++this.m_lastProxyId; + node.userData = null; + node.parent = null; + node.child1 = null; + node.child2 = null; + node.height = -1; + this.m_nodes[node.id] = node; + return node; + }; + DynamicTree.prototype.freeNode = function (node) { + this.m_pool.release(node); + node.height = -1; + // tslint:disable-next-line:no-dynamic-delete + delete this.m_nodes[node.id]; + }; + /** + * Create a proxy in the tree as a leaf node. We return the index of the node + * instead of a pointer so that we can grow the node pool. + * + * Create a proxy. Provide a tight fitting AABB and a userData pointer. + */ + DynamicTree.prototype.createProxy = function (aabb, userData) { + var node = this.allocateNode(); + node.aabb.set(aabb); + // Fatten the aabb. + AABB.extend(node.aabb, Settings.aabbExtension); + node.userData = userData; + node.height = 0; + this.insertLeaf(node); + return node.id; + }; + /** + * Destroy a proxy. This asserts if the id is invalid. + */ + DynamicTree.prototype.destroyProxy = function (id) { + var node = this.m_nodes[id]; + this.removeLeaf(node); + this.freeNode(node); + }; + /** + * Move a proxy with a swepted AABB. If the proxy has moved outside of its + * fattened AABB, then the proxy is removed from the tree and re-inserted. + * Otherwise the function returns immediately. + * + * @param d Displacement + * + * @return true if the proxy was re-inserted. + */ + DynamicTree.prototype.moveProxy = function (id, aabb, d) { + var node = this.m_nodes[id]; + if (node.aabb.contains(aabb)) { + return false; + } + this.removeLeaf(node); + node.aabb.set(aabb); + // Extend AABB. + aabb = node.aabb; + AABB.extend(aabb, Settings.aabbExtension); + // Predict AABB displacement. + // const d = Vec2.mul(Settings.aabbMultiplier, displacement); + if (d.x < 0.0) { + aabb.lowerBound.x += d.x * Settings.aabbMultiplier; + } + else { + aabb.upperBound.x += d.x * Settings.aabbMultiplier; + } + if (d.y < 0.0) { + aabb.lowerBound.y += d.y * Settings.aabbMultiplier; + } + else { + aabb.upperBound.y += d.y * Settings.aabbMultiplier; + } + this.insertLeaf(node); + return true; + }; + DynamicTree.prototype.insertLeaf = function (leaf) { + if (this.m_root == null) { + this.m_root = leaf; + this.m_root.parent = null; + return; + } + // Find the best sibling for this node + var leafAABB = leaf.aabb; + var index = this.m_root; + while (!index.isLeaf()) { + var child1 = index.child1; + var child2 = index.child2; + var area = index.aabb.getPerimeter(); + var combinedAABB = new AABB(); + combinedAABB.combine(index.aabb, leafAABB); + var combinedArea = combinedAABB.getPerimeter(); + // Cost of creating a new parent for this node and the new leaf + var cost = 2.0 * combinedArea; + // Minimum cost of pushing the leaf further down the tree + var inheritanceCost = 2.0 * (combinedArea - area); + // Cost of descending into child1 + var cost1 = void 0; + if (child1.isLeaf()) { + var aabb = new AABB(); + aabb.combine(leafAABB, child1.aabb); + cost1 = aabb.getPerimeter() + inheritanceCost; + } + else { + var aabb = new AABB(); + aabb.combine(leafAABB, child1.aabb); + var oldArea = child1.aabb.getPerimeter(); + var newArea = aabb.getPerimeter(); + cost1 = (newArea - oldArea) + inheritanceCost; + } + // Cost of descending into child2 + var cost2 = void 0; + if (child2.isLeaf()) { + var aabb = new AABB(); + aabb.combine(leafAABB, child2.aabb); + cost2 = aabb.getPerimeter() + inheritanceCost; + } + else { + var aabb = new AABB(); + aabb.combine(leafAABB, child2.aabb); + var oldArea = child2.aabb.getPerimeter(); + var newArea = aabb.getPerimeter(); + cost2 = newArea - oldArea + inheritanceCost; + } + // Descend according to the minimum cost. + if (cost < cost1 && cost < cost2) { + break; + } + // Descend + if (cost1 < cost2) { + index = child1; + } + else { + index = child2; + } + } + var sibling = index; + // Create a new parent. + var oldParent = sibling.parent; + var newParent = this.allocateNode(); + newParent.parent = oldParent; + newParent.userData = null; + newParent.aabb.combine(leafAABB, sibling.aabb); + newParent.height = sibling.height + 1; + if (oldParent != null) { + // The sibling was not the root. + if (oldParent.child1 === sibling) { + oldParent.child1 = newParent; + } + else { + oldParent.child2 = newParent; + } + newParent.child1 = sibling; + newParent.child2 = leaf; + sibling.parent = newParent; + leaf.parent = newParent; + } + else { + // The sibling was the root. + newParent.child1 = sibling; + newParent.child2 = leaf; + sibling.parent = newParent; + leaf.parent = newParent; + this.m_root = newParent; + } + // Walk back up the tree fixing heights and AABBs + index = leaf.parent; + while (index != null) { + index = this.balance(index); + var child1 = index.child1; + var child2 = index.child2; + index.height = 1 + math.max(child1.height, child2.height); + index.aabb.combine(child1.aabb, child2.aabb); + index = index.parent; + } + // validate(); + }; + DynamicTree.prototype.removeLeaf = function (leaf) { + if (leaf === this.m_root) { + this.m_root = null; + return; + } + var parent = leaf.parent; + var grandParent = parent.parent; + var sibling; + if (parent.child1 === leaf) { + sibling = parent.child2; + } + else { + sibling = parent.child1; + } + if (grandParent != null) { + // Destroy parent and connect sibling to grandParent. + if (grandParent.child1 === parent) { + grandParent.child1 = sibling; + } + else { + grandParent.child2 = sibling; + } + sibling.parent = grandParent; + this.freeNode(parent); + // Adjust ancestor bounds. + var index = grandParent; + while (index != null) { + index = this.balance(index); + var child1 = index.child1; + var child2 = index.child2; + index.aabb.combine(child1.aabb, child2.aabb); + index.height = 1 + math.max(child1.height, child2.height); + index = index.parent; + } + } + else { + this.m_root = sibling; + sibling.parent = null; + this.freeNode(parent); + } + // validate(); + }; + /** + * Perform a left or right rotation if node A is imbalanced. Returns the new + * root index. + */ + DynamicTree.prototype.balance = function (iA) { + var A = iA; + if (A.isLeaf() || A.height < 2) { + return iA; + } + var B = A.child1; + var C = A.child2; + var balance = C.height - B.height; + // Rotate C up + if (balance > 1) { + var F = C.child1; + var G = C.child2; + // Swap A and C + C.child1 = A; + C.parent = A.parent; + A.parent = C; + // A's old parent should point to C + if (C.parent != null) { + if (C.parent.child1 === iA) { + C.parent.child1 = C; + } + else { + C.parent.child2 = C; + } + } + else { + this.m_root = C; + } + // Rotate + if (F.height > G.height) { + C.child2 = F; + A.child2 = G; + G.parent = A; + A.aabb.combine(B.aabb, G.aabb); + C.aabb.combine(A.aabb, F.aabb); + A.height = 1 + math.max(B.height, G.height); + C.height = 1 + math.max(A.height, F.height); + } + else { + C.child2 = G; + A.child2 = F; + F.parent = A; + A.aabb.combine(B.aabb, F.aabb); + C.aabb.combine(A.aabb, G.aabb); + A.height = 1 + math.max(B.height, F.height); + C.height = 1 + math.max(A.height, G.height); + } + return C; + } + // Rotate B up + if (balance < -1) { + var D = B.child1; + var E = B.child2; + // Swap A and B + B.child1 = A; + B.parent = A.parent; + A.parent = B; + // A's old parent should point to B + if (B.parent != null) { + if (B.parent.child1 === A) { + B.parent.child1 = B; + } + else { + B.parent.child2 = B; + } + } + else { + this.m_root = B; + } + // Rotate + if (D.height > E.height) { + B.child2 = D; + A.child1 = E; + E.parent = A; + A.aabb.combine(C.aabb, E.aabb); + B.aabb.combine(A.aabb, D.aabb); + A.height = 1 + math.max(C.height, E.height); + B.height = 1 + math.max(A.height, D.height); + } + else { + B.child2 = E; + A.child1 = D; + D.parent = A; + A.aabb.combine(C.aabb, D.aabb); + B.aabb.combine(A.aabb, E.aabb); + A.height = 1 + math.max(C.height, D.height); + B.height = 1 + math.max(A.height, E.height); + } + return B; + } + return A; + }; + /** + * Compute the height of the binary tree in O(N) time. Should not be called + * often. + */ + DynamicTree.prototype.getHeight = function () { + if (this.m_root == null) { + return 0; + } + return this.m_root.height; + }; + /** + * Get the ratio of the sum of the node areas to the root area. + */ + DynamicTree.prototype.getAreaRatio = function () { + if (this.m_root == null) { + return 0.0; + } + var root = this.m_root; + var rootArea = root.aabb.getPerimeter(); + var totalArea = 0.0; + var node; + var it = this.iteratorPool.allocate().preorder(this.m_root); + while (node = it.next()) { + if (node.height < 0) { + // Free node in pool + continue; + } + totalArea += node.aabb.getPerimeter(); + } + this.iteratorPool.release(it); + return totalArea / rootArea; + }; + /** + * Compute the height of a sub-tree. + */ + DynamicTree.prototype.computeHeight = function (id) { + var node; + if (typeof id !== 'undefined') { + node = this.m_nodes[id]; + } + else { + node = this.m_root; + } + // false && console.assert(0 <= id && id < this.m_nodeCapacity); + if (node.isLeaf()) { + return 0; + } + var height1 = this.computeHeight(node.child1.id); + var height2 = this.computeHeight(node.child2.id); + return 1 + math.max(height1, height2); + }; + DynamicTree.prototype.validateStructure = function (node) { + if (node == null) { + return; + } + if (node === this.m_root) ; + var child1 = node.child1; + var child2 = node.child2; + if (node.isLeaf()) { + return; + } + this.validateStructure(child1); + this.validateStructure(child2); + }; + DynamicTree.prototype.validateMetrics = function (node) { + if (node == null) { + return; + } + var child1 = node.child1; + var child2 = node.child2; + if (node.isLeaf()) { + return; + } + // false && console.assert(0 <= child1 && child1 < this.m_nodeCapacity); + // false && console.assert(0 <= child2 && child2 < this.m_nodeCapacity); + var height1 = child1.height; + var height2 = child2.height; + 1 + math.max(height1, height2); + var aabb = new AABB(); + aabb.combine(child1.aabb, child2.aabb); + this.validateMetrics(child1); + this.validateMetrics(child2); + }; + /** + * Validate this tree. For testing. + */ + DynamicTree.prototype.validate = function () { + this.validateStructure(this.m_root); + this.validateMetrics(this.m_root); + }; + /** + * Get the maximum balance of an node in the tree. The balance is the difference + * in height of the two children of a node. + */ + DynamicTree.prototype.getMaxBalance = function () { + var maxBalance = 0; + var node; + var it = this.iteratorPool.allocate().preorder(this.m_root); + while (node = it.next()) { + if (node.height <= 1) { + continue; + } + var balance = math.abs(node.child2.height - node.child1.height); + maxBalance = math.max(maxBalance, balance); + } + this.iteratorPool.release(it); + return maxBalance; + }; + /** + * Build an optimal tree. Very expensive. For testing. + */ + DynamicTree.prototype.rebuildBottomUp = function () { + var nodes = []; + var count = 0; + // Build array of leaves. Free the rest. + var node; + var it = this.iteratorPool.allocate().preorder(this.m_root); + while (node = it.next()) { + if (node.height < 0) { + // free node in pool + continue; + } + if (node.isLeaf()) { + node.parent = null; + nodes[count] = node; + ++count; + } + else { + this.freeNode(node); + } + } + this.iteratorPool.release(it); + while (count > 1) { + var minCost = Infinity; + var iMin = -1; + var jMin = -1; + for (var i = 0; i < count; ++i) { + var aabbi = nodes[i].aabb; + for (var j = i + 1; j < count; ++j) { + var aabbj = nodes[j].aabb; + var b = new AABB(); + b.combine(aabbi, aabbj); + var cost = b.getPerimeter(); + if (cost < minCost) { + iMin = i; + jMin = j; + minCost = cost; + } + } + } + var child1 = nodes[iMin]; + var child2 = nodes[jMin]; + var parent_1 = this.allocateNode(); + parent_1.child1 = child1; + parent_1.child2 = child2; + parent_1.height = 1 + math.max(child1.height, child2.height); + parent_1.aabb.combine(child1.aabb, child2.aabb); + parent_1.parent = null; + child1.parent = parent_1; + child2.parent = parent_1; + nodes[jMin] = nodes[count - 1]; + nodes[iMin] = parent_1; + --count; + } + this.m_root = nodes[0]; + }; + /** + * Shift the world origin. Useful for large worlds. The shift formula is: + * position -= newOrigin + * + * @param newOrigin The new origin with respect to the old origin + */ + DynamicTree.prototype.shiftOrigin = function (newOrigin) { + // Build array of leaves. Free the rest. + var node; + var it = this.iteratorPool.allocate().preorder(this.m_root); + while (node = it.next()) { + var aabb = node.aabb; + aabb.lowerBound.x -= newOrigin.x; + aabb.lowerBound.y -= newOrigin.y; + aabb.upperBound.x -= newOrigin.x; + aabb.upperBound.y -= newOrigin.y; + } + this.iteratorPool.release(it); + }; + /** + * Query an AABB for overlapping proxies. The callback class is called for each + * proxy that overlaps the supplied AABB. + */ + DynamicTree.prototype.query = function (aabb, queryCallback) { + var stack = this.stackPool.allocate(); + stack.push(this.m_root); + while (stack.length > 0) { + var node = stack.pop(); + if (node == null) { + continue; + } + if (AABB.testOverlap(node.aabb, aabb)) { + if (node.isLeaf()) { + var proceed = queryCallback(node.id); + if (proceed === false) { + return; + } + } + else { + stack.push(node.child1); + stack.push(node.child2); + } + } + } + this.stackPool.release(stack); + }; + /** + * Ray-cast against the proxies in the tree. This relies on the callback to + * perform a exact ray-cast in the case were the proxy contains a shape. The + * callback also performs the any collision filtering. This has performance + * roughly equal to k * log(n), where k is the number of collisions and n is the + * number of proxies in the tree. + * + * @param input The ray-cast input data. The ray extends from `p1` to `p1 + maxFraction * (p2 - p1)`. + * @param rayCastCallback A function that is called for each proxy that is hit by the ray. + */ + DynamicTree.prototype.rayCast = function (input, rayCastCallback) { + var p1 = input.p1; + var p2 = input.p2; + var r = Vec2.sub(p2, p1); + r.normalize(); + // v is perpendicular to the segment. + var v = Vec2.crossNumVec2(1.0, r); + var abs_v = Vec2.abs(v); + // Separating axis for segment (Gino, p80). + // |dot(v, p1 - c)| > dot(|v|, h) + var maxFraction = input.maxFraction; + // Build a bounding box for the segment. + var segmentAABB = new AABB(); + var t = Vec2.combine((1 - maxFraction), p1, maxFraction, p2); + segmentAABB.combinePoints(p1, t); + var stack = this.stackPool.allocate(); + var subInput = this.inputPool.allocate(); + stack.push(this.m_root); + while (stack.length > 0) { + var node = stack.pop(); + if (node == null) { + continue; + } + if (AABB.testOverlap(node.aabb, segmentAABB) === false) { + continue; + } + // Separating axis for segment (Gino, p80). + // |dot(v, p1 - c)| > dot(|v|, h) + var c = node.aabb.getCenter(); + var h = node.aabb.getExtents(); + var separation = math.abs(Vec2.dot(v, Vec2.sub(p1, c))) - Vec2.dot(abs_v, h); + if (separation > 0.0) { + continue; + } + if (node.isLeaf()) { + subInput.p1 = Vec2.clone(input.p1); + subInput.p2 = Vec2.clone(input.p2); + subInput.maxFraction = maxFraction; + var value = rayCastCallback(subInput, node.id); + if (value === 0.0) { + // The client has terminated the ray cast. + return; + } + if (value > 0.0) { + // update segment bounding box. + maxFraction = value; + t = Vec2.combine((1 - maxFraction), p1, maxFraction, p2); + segmentAABB.combinePoints(p1, t); + } + } + else { + stack.push(node.child1); + stack.push(node.child2); + } + } + this.stackPool.release(stack); + this.inputPool.release(subInput); + }; + return DynamicTree; +}()); +var Iterator = /** @class */ (function () { + function Iterator() { + this.parents = []; + this.states = []; + } + Iterator.prototype.preorder = function (root) { + this.parents.length = 0; + this.parents.push(root); + this.states.length = 0; + this.states.push(0); + return this; + }; + Iterator.prototype.next = function () { + while (this.parents.length > 0) { + var i = this.parents.length - 1; + var node = this.parents[i]; + if (this.states[i] === 0) { + this.states[i] = 1; + return node; + } + if (this.states[i] === 1) { + this.states[i] = 2; + if (node.child1) { + this.parents.push(node.child1); + this.states.push(1); + return node.child1; + } + } + if (this.states[i] === 2) { + this.states[i] = 3; + if (node.child2) { + this.parents.push(node.child2); + this.states.push(1); + return node.child2; + } + } + this.parents.pop(); + this.states.pop(); + } + }; + Iterator.prototype.close = function () { + this.parents.length = 0; + }; + return Iterator; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * The broad-phase wraps and extends a dynamic-tree to keep track of moved + * objects and query them on update. + */ +var BroadPhase = /** @class */ (function () { + function BroadPhase() { + var _this = this; + this.m_tree = new DynamicTree(); + this.m_proxyCount = 0; + this.m_moveBuffer = []; + /** + * Query an AABB for overlapping proxies. The callback class is called for each + * proxy that overlaps the supplied AABB. + */ + this.query = function (aabb, queryCallback) { + _this.m_tree.query(aabb, queryCallback); + }; + this.queryCallback = function (proxyId) { + // A proxy cannot form a pair with itself. + if (proxyId === _this.m_queryProxyId) { + return true; + } + var proxyIdA = math.min(proxyId, _this.m_queryProxyId); + var proxyIdB = math.max(proxyId, _this.m_queryProxyId); + // TODO: Skip any duplicate pairs. + var userDataA = _this.m_tree.getUserData(proxyIdA); + var userDataB = _this.m_tree.getUserData(proxyIdB); + // Send the pairs back to the client. + _this.m_callback(userDataA, userDataB); + return true; + }; + } + /** + * Get user data from a proxy. Returns null if the id is invalid. + */ + BroadPhase.prototype.getUserData = function (proxyId) { + return this.m_tree.getUserData(proxyId); + }; + /** + * Test overlap of fat AABBs. + */ + BroadPhase.prototype.testOverlap = function (proxyIdA, proxyIdB) { + var aabbA = this.m_tree.getFatAABB(proxyIdA); + var aabbB = this.m_tree.getFatAABB(proxyIdB); + return AABB.testOverlap(aabbA, aabbB); + }; + /** + * Get the fat AABB for a proxy. + */ + BroadPhase.prototype.getFatAABB = function (proxyId) { + return this.m_tree.getFatAABB(proxyId); + }; + /** + * Get the number of proxies. + */ + BroadPhase.prototype.getProxyCount = function () { + return this.m_proxyCount; + }; + /** + * Get the height of the embedded tree. + */ + BroadPhase.prototype.getTreeHeight = function () { + return this.m_tree.getHeight(); + }; + /** + * Get the balance (integer) of the embedded tree. + */ + BroadPhase.prototype.getTreeBalance = function () { + return this.m_tree.getMaxBalance(); + }; + /** + * Get the quality metric of the embedded tree. + */ + BroadPhase.prototype.getTreeQuality = function () { + return this.m_tree.getAreaRatio(); + }; + /** + * Ray-cast against the proxies in the tree. This relies on the callback to + * perform a exact ray-cast in the case were the proxy contains a shape. The + * callback also performs the any collision filtering. This has performance + * roughly equal to k * log(n), where k is the number of collisions and n is the + * number of proxies in the tree. + * + * @param input The ray-cast input data. The ray extends from `p1` to `p1 + maxFraction * (p2 - p1)`. + * @param rayCastCallback A function that is called for each proxy that is hit by the ray. + */ + BroadPhase.prototype.rayCast = function (input, rayCastCallback) { + this.m_tree.rayCast(input, rayCastCallback); + }; + /** + * Shift the world origin. Useful for large worlds. The shift formula is: + * position -= newOrigin + * + * @param newOrigin The new origin with respect to the old origin + */ + BroadPhase.prototype.shiftOrigin = function (newOrigin) { + this.m_tree.shiftOrigin(newOrigin); + }; + /** + * Create a proxy with an initial AABB. Pairs are not reported until UpdatePairs + * is called. + */ + BroadPhase.prototype.createProxy = function (aabb, userData) { + var proxyId = this.m_tree.createProxy(aabb, userData); + this.m_proxyCount++; + this.bufferMove(proxyId); + return proxyId; + }; + /** + * Destroy a proxy. It is up to the client to remove any pairs. + */ + BroadPhase.prototype.destroyProxy = function (proxyId) { + this.unbufferMove(proxyId); + this.m_proxyCount--; + this.m_tree.destroyProxy(proxyId); + }; + /** + * Call moveProxy as many times as you like, then when you are done call + * UpdatePairs to finalized the proxy pairs (for your time step). + */ + BroadPhase.prototype.moveProxy = function (proxyId, aabb, displacement) { + var changed = this.m_tree.moveProxy(proxyId, aabb, displacement); + if (changed) { + this.bufferMove(proxyId); + } + }; + /** + * Call to trigger a re-processing of it's pairs on the next call to + * UpdatePairs. + */ + BroadPhase.prototype.touchProxy = function (proxyId) { + this.bufferMove(proxyId); + }; + BroadPhase.prototype.bufferMove = function (proxyId) { + this.m_moveBuffer.push(proxyId); + }; + BroadPhase.prototype.unbufferMove = function (proxyId) { + for (var i = 0; i < this.m_moveBuffer.length; ++i) { + if (this.m_moveBuffer[i] === proxyId) { + this.m_moveBuffer[i] = null; + } + } + }; + /** + * Update the pairs. This results in pair callbacks. This can only add pairs. + */ + BroadPhase.prototype.updatePairs = function (addPairCallback) { + this.m_callback = addPairCallback; + // Perform tree queries for all moving proxies. + while (this.m_moveBuffer.length > 0) { + this.m_queryProxyId = this.m_moveBuffer.pop(); + if (this.m_queryProxyId === null) { + continue; + } + // We have to query the tree with the fat AABB so that + // we don't fail to create a pair that may touch later. + var fatAABB = this.m_tree.getFatAABB(this.m_queryProxyId); + // Query tree, create pairs and add them pair buffer. + this.m_tree.query(fatAABB, this.queryCallback); + } + // Try to keep the tree balanced. + // this.m_tree.rebalance(4); + }; + return BroadPhase; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +var Rot = /** @class */ (function () { + /** Initialize from an angle in radians. */ + function Rot(angle) { + if (!(this instanceof Rot)) { + return new Rot(angle); + } + if (typeof angle === 'number') { + this.setAngle(angle); + } + else if (typeof angle === 'object') { + this.setRot(angle); + } + else { + this.setIdentity(); + } + } + /** @internal */ + Rot.neo = function (angle) { + var obj = Object.create(Rot.prototype); + obj.setAngle(angle); + return obj; + }; + Rot.clone = function (rot) { + var obj = Object.create(Rot.prototype); + obj.s = rot.s; + obj.c = rot.c; + return obj; + }; + Rot.identity = function () { + var obj = Object.create(Rot.prototype); + obj.s = 0.0; + obj.c = 1.0; + return obj; + }; + Rot.isValid = function (obj) { + if (obj === null || typeof obj === 'undefined') { + return false; + } + return math.isFinite(obj.s) && math.isFinite(obj.c); + }; + Rot.assert = function (o) { + }; + /** Set to the identity rotation. */ + Rot.prototype.setIdentity = function () { + this.s = 0.0; + this.c = 1.0; + }; + Rot.prototype.set = function (angle) { + if (typeof angle === 'object') { + this.s = angle.s; + this.c = angle.c; + } + else { + // TODO_ERIN optimize + this.s = math.sin(angle); + this.c = math.cos(angle); + } + }; + Rot.prototype.setRot = function (angle) { + this.s = angle.s; + this.c = angle.c; + }; + /** Set using an angle in radians. */ + Rot.prototype.setAngle = function (angle) { + // TODO_ERIN optimize + this.s = math.sin(angle); + this.c = math.cos(angle); + }; + /** Get the angle in radians. */ + Rot.prototype.getAngle = function () { + return math.atan2(this.s, this.c); + }; + /** Get the x-axis. */ + Rot.prototype.getXAxis = function () { + return Vec2.neo(this.c, this.s); + }; + /** Get the u-axis. */ + Rot.prototype.getYAxis = function () { + return Vec2.neo(-this.s, this.c); + }; + // tslint:disable-next-line:typedef + Rot.mul = function (rot, m) { + if ('c' in m && 's' in m) { + // [qc -qs] * [rc -rs] = [qc*rc-qs*rs -qc*rs-qs*rc] + // [qs qc] [rs rc] [qs*rc+qc*rs -qs*rs+qc*rc] + // s = qs * rc + qc * rs + // c = qc * rc - qs * rs + var qr = Rot.identity(); + qr.s = rot.s * m.c + rot.c * m.s; + qr.c = rot.c * m.c - rot.s * m.s; + return qr; + } + else if ('x' in m && 'y' in m) { + return Vec2.neo(rot.c * m.x - rot.s * m.y, rot.s * m.x + rot.c * m.y); + } + }; + /** Multiply two rotations: q * r */ + Rot.mulRot = function (rot, m) { + // [qc -qs] * [rc -rs] = [qc*rc-qs*rs -qc*rs-qs*rc] + // [qs qc] [rs rc] [qs*rc+qc*rs -qs*rs+qc*rc] + // s = qs * rc + qc * rs + // c = qc * rc - qs * rs + var qr = Rot.identity(); + qr.s = rot.s * m.c + rot.c * m.s; + qr.c = rot.c * m.c - rot.s * m.s; + return qr; + }; + /** Rotate a vector */ + Rot.mulVec2 = function (rot, m) { + return Vec2.neo(rot.c * m.x - rot.s * m.y, rot.s * m.x + rot.c * m.y); + }; + Rot.mulSub = function (rot, v, w) { + var x = rot.c * (v.x - w.x) - rot.s * (v.y - w.y); + var y = rot.s * (v.x - w.x) + rot.c * (v.y - w.y); + return Vec2.neo(x, y); + }; + // tslint:disable-next-line:typedef + Rot.mulT = function (rot, m) { + if ('c' in m && 's' in m) { + // [ qc qs] * [rc -rs] = [qc*rc+qs*rs -qc*rs+qs*rc] + // [-qs qc] [rs rc] [-qs*rc+qc*rs qs*rs+qc*rc] + // s = qc * rs - qs * rc + // c = qc * rc + qs * rs + var qr = Rot.identity(); + qr.s = rot.c * m.s - rot.s * m.c; + qr.c = rot.c * m.c + rot.s * m.s; + return qr; + } + else if ('x' in m && 'y' in m) { + return Vec2.neo(rot.c * m.x + rot.s * m.y, -rot.s * m.x + rot.c * m.y); + } + }; + /** Transpose multiply two rotations: qT * r */ + Rot.mulTRot = function (rot, m) { + // [ qc qs] * [rc -rs] = [qc*rc+qs*rs -qc*rs+qs*rc] + // [-qs qc] [rs rc] [-qs*rc+qc*rs qs*rs+qc*rc] + // s = qc * rs - qs * rc + // c = qc * rc + qs * rs + var qr = Rot.identity(); + qr.s = rot.c * m.s - rot.s * m.c; + qr.c = rot.c * m.c + rot.s * m.s; + return qr; + }; + /** Inverse rotate a vector */ + Rot.mulTVec2 = function (rot, m) { + return Vec2.neo(rot.c * m.x + rot.s * m.y, -rot.s * m.x + rot.c * m.y); + }; + return Rot; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * A transform contains translation and rotation. It is used to represent the + * position and orientation of rigid frames. Initialize using a position vector + * and a rotation. + */ +var Transform = /** @class */ (function () { + function Transform(position, rotation) { + if (!(this instanceof Transform)) { + return new Transform(position, rotation); + } + this.p = Vec2.zero(); + this.q = Rot.identity(); + if (typeof position !== 'undefined') { + this.p.setVec2(position); + } + if (typeof rotation !== 'undefined') { + this.q.setAngle(rotation); + } + } + Transform.clone = function (xf) { + var obj = Object.create(Transform.prototype); + obj.p = Vec2.clone(xf.p); + obj.q = Rot.clone(xf.q); + return obj; + }; + /** @internal */ + Transform.neo = function (position, rotation) { + var obj = Object.create(Transform.prototype); + obj.p = Vec2.clone(position); + obj.q = Rot.clone(rotation); + return obj; + }; + Transform.identity = function () { + var obj = Object.create(Transform.prototype); + obj.p = Vec2.zero(); + obj.q = Rot.identity(); + return obj; + }; + /** + * Set this to the identity transform. + */ + Transform.prototype.setIdentity = function () { + this.p.setZero(); + this.q.setIdentity(); + }; + /** + * Set this based on the position and angle. + */ + // tslint:disable-next-line:typedef + Transform.prototype.set = function (a, b) { + if (typeof b === 'undefined') { + this.p.set(a.p); + this.q.set(a.q); + } + else { + this.p.set(a); + this.q.set(b); + } + }; + /** + * Set this based on the position and angle. + */ + Transform.prototype.setNum = function (position, rotation) { + this.p.setVec2(position); + this.q.setAngle(rotation); + }; + Transform.prototype.setTransform = function (xf) { + this.p.setVec2(xf.p); + this.q.setRot(xf.q); + }; + Transform.isValid = function (obj) { + if (obj === null || typeof obj === 'undefined') { + return false; + } + return Vec2.isValid(obj.p) && Rot.isValid(obj.q); + }; + Transform.assert = function (o) { + }; + // static mul(a: Transform, b: Vec2Value[]): Vec2[]; + // static mul(a: Transform, b: Transform[]): Transform[]; + // tslint:disable-next-line:typedef + Transform.mul = function (a, b) { + if (Array.isArray(b)) { + var arr = []; + for (var i = 0; i < b.length; i++) { + arr[i] = Transform.mul(a, b[i]); + } + return arr; + } + else if ('x' in b && 'y' in b) { + return Transform.mulVec2(a, b); + } + else if ('p' in b && 'q' in b) { + return Transform.mulXf(a, b); + } + }; + // tslint:disable-next-line:typedef + Transform.mulAll = function (a, b) { + var arr = []; + for (var i = 0; i < b.length; i++) { + arr[i] = Transform.mul(a, b[i]); + } + return arr; + }; + /** @internal @deprecated */ + // tslint:disable-next-line:typedef + Transform.mulFn = function (a) { + return function (b) { + return Transform.mul(a, b); + }; + }; + Transform.mulVec2 = function (a, b) { + var x = (a.q.c * b.x - a.q.s * b.y) + a.p.x; + var y = (a.q.s * b.x + a.q.c * b.y) + a.p.y; + return Vec2.neo(x, y); + }; + Transform.mulXf = function (a, b) { + // v2 = A.q.Rot(B.q.Rot(v1) + B.p) + A.p + // = (A.q * B.q).Rot(v1) + A.q.Rot(B.p) + A.p + var xf = Transform.identity(); + xf.q = Rot.mulRot(a.q, b.q); + xf.p = Vec2.add(Rot.mulVec2(a.q, b.p), a.p); + return xf; + }; + // tslint:disable-next-line:typedef + Transform.mulT = function (a, b) { + if ('x' in b && 'y' in b) { + return Transform.mulTVec2(a, b); + } + else if ('p' in b && 'q' in b) { + return Transform.mulTXf(a, b); + } + }; + Transform.mulTVec2 = function (a, b) { + var px = b.x - a.p.x; + var py = b.y - a.p.y; + var x = (a.q.c * px + a.q.s * py); + var y = (-a.q.s * px + a.q.c * py); + return Vec2.neo(x, y); + }; + Transform.mulTXf = function (a, b) { + // v2 = A.q' * (B.q * v1 + B.p - A.p) + // = A.q' * B.q * v1 + A.q' * (B.p - A.p) + var xf = Transform.identity(); + xf.q.setRot(Rot.mulTRot(a.q, b.q)); + xf.p.setVec2(Rot.mulTVec2(a.q, Vec2.sub(b.p, a.p))); + return xf; + }; + return Transform; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * This describes the motion of a body/shape for TOI computation. Shapes are + * defined with respect to the body origin, which may not coincide with the + * center of mass. However, to support dynamics we must interpolate the center + * of mass position. + */ +var Sweep = /** @class */ (function () { + function Sweep(c, a) { + this.localCenter = Vec2.zero(); + this.c = Vec2.zero(); + this.a = 0; + this.alpha0 = 0; + this.c0 = Vec2.zero(); + this.a0 = 0; + } + Sweep.prototype.setTransform = function (xf) { + var c = Transform.mulVec2(xf, this.localCenter); + this.c.setVec2(c); + this.c0.setVec2(c); + this.a = xf.q.getAngle(); + this.a0 = xf.q.getAngle(); + }; + Sweep.prototype.setLocalCenter = function (localCenter, xf) { + this.localCenter.setVec2(localCenter); + var c = Transform.mulVec2(xf, this.localCenter); + this.c.setVec2(c); + this.c0.setVec2(c); + }; + /** + * Get the interpolated transform at a specific time. + * + * @param xf + * @param beta A factor in [0,1], where 0 indicates alpha0 + */ + Sweep.prototype.getTransform = function (xf, beta) { + if (beta === void 0) { beta = 0; } + xf.q.setAngle((1.0 - beta) * this.a0 + beta * this.a); + xf.p.setCombine((1.0 - beta), this.c0, beta, this.c); + // shift to origin + xf.p.sub(Rot.mulVec2(xf.q, this.localCenter)); + }; + /** + * Advance the sweep forward, yielding a new initial state. + * + * @param alpha The new initial time + */ + Sweep.prototype.advance = function (alpha) { + var beta = (alpha - this.alpha0) / (1.0 - this.alpha0); + this.c0.setCombine(beta, this.c, 1 - beta, this.c0); + this.a0 = beta * this.a + (1 - beta) * this.a0; + this.alpha0 = alpha; + }; + Sweep.prototype.forward = function () { + this.a0 = this.a; + this.c0.setVec2(this.c); + }; + /** + * normalize the angles in radians to be between -pi and pi. + */ + Sweep.prototype.normalize = function () { + var a0 = math.mod(this.a0, -math.PI, +math.PI); + this.a -= this.a0 - a0; + this.a0 = a0; + }; + Sweep.prototype.clone = function () { + var clone = new Sweep(); + clone.localCenter.setVec2(this.localCenter); + clone.alpha0 = this.alpha0; + clone.a0 = this.a0; + clone.a = this.a; + clone.c0.setVec2(this.c0); + clone.c.setVec2(this.c); + return clone; + }; + Sweep.prototype.set = function (that) { + this.localCenter.setVec2(that.localCenter); + this.alpha0 = that.alpha0; + this.a0 = that.a0; + this.a = that.a; + this.c0.setVec2(that.c0); + this.c.setVec2(that.c); + }; + return Sweep; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +var Velocity = /** @class */ (function () { + function Velocity() { + this.v = Vec2.zero(); + this.w = 0; + } + return Velocity; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +var Position = /** @class */ (function () { + function Position() { + this.c = Vec2.zero(); + this.a = 0; + } + Position.prototype.getTransform = function (xf, p) { + xf.q.setAngle(this.a); + xf.p.setVec2(Vec2.sub(this.c, Rot.mulVec2(xf.q, p))); + return xf; + }; + return Position; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +// todo make shape an interface +/** + * A shape is used for collision detection. You can create a shape however you + * like. Shapes used for simulation in World are created automatically when a + * Fixture is created. Shapes may encapsulate one or more child shapes. + */ +var Shape = /** @class */ (function () { + function Shape() { + } + Shape.isValid = function (obj) { + if (obj === null || typeof obj === 'undefined') { + return false; + } + return typeof obj.m_type === 'string' && typeof obj.m_radius === 'number'; + }; + return Shape; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +var FixtureDefDefault = { + userData: null, + friction: 0.2, + restitution: 0.0, + density: 0.0, + isSensor: false, + filterGroupIndex: 0, + filterCategoryBits: 0x0001, + filterMaskBits: 0xFFFF +}; +/** + * This proxy is used internally to connect shape children to the broad-phase. + */ +var FixtureProxy = /** @class */ (function () { + function FixtureProxy(fixture, childIndex) { + this.aabb = new AABB(); + this.fixture = fixture; + this.childIndex = childIndex; + this.proxyId; + } + return FixtureProxy; +}()); +/** + * A fixture is used to attach a shape to a body for collision detection. A + * fixture inherits its transform from its parent. Fixtures hold additional + * non-geometric data such as friction, collision filters, etc. + * + * To create a new Fixture use {@link Body.createFixture}. + */ +var Fixture = /** @class */ (function () { + // tslint:disable-next-line:typedef + /** @internal */ function Fixture(body, shape, def) { + if (shape.shape) { + def = shape; + shape = shape.shape; + } + else if (typeof def === 'number') { + def = { density: def }; + } + def = options(def, FixtureDefDefault); + this.m_body = body; + this.m_friction = def.friction; + this.m_restitution = def.restitution; + this.m_density = def.density; + this.m_isSensor = def.isSensor; + this.m_filterGroupIndex = def.filterGroupIndex; + this.m_filterCategoryBits = def.filterCategoryBits; + this.m_filterMaskBits = def.filterMaskBits; + // TODO validate shape + this.m_shape = shape; // .clone(); + this.m_next = null; + this.m_proxies = []; + this.m_proxyCount = 0; + var childCount = this.m_shape.getChildCount(); + for (var i = 0; i < childCount; ++i) { + this.m_proxies[i] = new FixtureProxy(this, i); + } + this.m_userData = def.userData; + } + /** + * Re-setup fixture. + * @internal + */ + Fixture.prototype._reset = function () { + var body = this.getBody(); + var broadPhase = body.m_world.m_broadPhase; + this.destroyProxies(broadPhase); + if (this.m_shape._reset) { + this.m_shape._reset(); + } + var childCount = this.m_shape.getChildCount(); + for (var i = 0; i < childCount; ++i) { + this.m_proxies[i] = new FixtureProxy(this, i); + } + this.createProxies(broadPhase, body.m_xf); + body.resetMassData(); + }; + /** @internal */ + Fixture.prototype._serialize = function () { + return { + friction: this.m_friction, + restitution: this.m_restitution, + density: this.m_density, + isSensor: this.m_isSensor, + filterGroupIndex: this.m_filterGroupIndex, + filterCategoryBits: this.m_filterCategoryBits, + filterMaskBits: this.m_filterMaskBits, + shape: this.m_shape, + }; + }; + /** @internal */ + Fixture._deserialize = function (data, body, restore) { + var shape = restore(Shape, data.shape); + var fixture = shape && new Fixture(body, shape, data); + return fixture; + }; + /** + * Get the type of the child shape. You can use this to down cast to the + * concrete shape. + */ + Fixture.prototype.getType = function () { + return this.m_shape.getType(); + }; + /** + * Get the child shape. You can modify the child shape, however you should not + * change the number of vertices because this will crash some collision caching + * mechanisms. Manipulating the shape may lead to non-physical behavior. + */ + Fixture.prototype.getShape = function () { + return this.m_shape; + }; + /** + * A sensor shape collects contact information but never generates a collision + * response. + */ + Fixture.prototype.isSensor = function () { + return this.m_isSensor; + }; + /** + * Set if this fixture is a sensor. + */ + Fixture.prototype.setSensor = function (sensor) { + if (sensor != this.m_isSensor) { + this.m_body.setAwake(true); + this.m_isSensor = sensor; + } + }; + // /** + // * Get the contact filtering data. + // */ + // getFilterData() { + // return this.m_filter; + // } + /** + * Get the user data that was assigned in the fixture definition. Use this to + * store your application specific data. + */ + Fixture.prototype.getUserData = function () { + return this.m_userData; + }; + /** + * Set the user data. Use this to store your application specific data. + */ + Fixture.prototype.setUserData = function (data) { + this.m_userData = data; + }; + /** + * Get the parent body of this fixture. This is null if the fixture is not + * attached. + */ + Fixture.prototype.getBody = function () { + return this.m_body; + }; + /** + * Get the next fixture in the parent body's fixture list. + */ + Fixture.prototype.getNext = function () { + return this.m_next; + }; + /** + * Get the density of this fixture. + */ + Fixture.prototype.getDensity = function () { + return this.m_density; + }; + /** + * Set the density of this fixture. This will _not_ automatically adjust the + * mass of the body. You must call Body.resetMassData to update the body's mass. + */ + Fixture.prototype.setDensity = function (density) { + this.m_density = density; + }; + /** + * Get the coefficient of friction, usually in the range [0,1]. + */ + Fixture.prototype.getFriction = function () { + return this.m_friction; + }; + /** + * Set the coefficient of friction. This will not change the friction of + * existing contacts. + */ + Fixture.prototype.setFriction = function (friction) { + this.m_friction = friction; + }; + /** + * Get the coefficient of restitution. + */ + Fixture.prototype.getRestitution = function () { + return this.m_restitution; + }; + /** + * Set the coefficient of restitution. This will not change the restitution of + * existing contacts. + */ + Fixture.prototype.setRestitution = function (restitution) { + this.m_restitution = restitution; + }; + /** + * Test a point in world coordinates for containment in this fixture. + */ + Fixture.prototype.testPoint = function (p) { + return this.m_shape.testPoint(this.m_body.getTransform(), p); + }; + /** + * Cast a ray against this shape. + */ + Fixture.prototype.rayCast = function (output, input, childIndex) { + return this.m_shape.rayCast(output, input, this.m_body.getTransform(), childIndex); + }; + /** + * Get the mass data for this fixture. The mass data is based on the density and + * the shape. The rotational inertia is about the shape's origin. This operation + * may be expensive. + */ + Fixture.prototype.getMassData = function (massData) { + this.m_shape.computeMass(massData, this.m_density); + }; + /** + * Get the fixture's AABB. This AABB may be enlarge and/or stale. If you need a + * more accurate AABB, compute it using the shape and the body transform. + */ + Fixture.prototype.getAABB = function (childIndex) { + return this.m_proxies[childIndex].aabb; + }; + /** + * These support body activation/deactivation. + */ + Fixture.prototype.createProxies = function (broadPhase, xf) { + // Create proxies in the broad-phase. + this.m_proxyCount = this.m_shape.getChildCount(); + for (var i = 0; i < this.m_proxyCount; ++i) { + var proxy = this.m_proxies[i]; + this.m_shape.computeAABB(proxy.aabb, xf, i); + proxy.proxyId = broadPhase.createProxy(proxy.aabb, proxy); + } + }; + Fixture.prototype.destroyProxies = function (broadPhase) { + // Destroy proxies in the broad-phase. + for (var i = 0; i < this.m_proxyCount; ++i) { + var proxy = this.m_proxies[i]; + broadPhase.destroyProxy(proxy.proxyId); + proxy.proxyId = null; + } + this.m_proxyCount = 0; + }; + /** + * Updates this fixture proxy in broad-phase (with combined AABB of current and + * next transformation). + */ + Fixture.prototype.synchronize = function (broadPhase, xf1, xf2) { + for (var i = 0; i < this.m_proxyCount; ++i) { + var proxy = this.m_proxies[i]; + // Compute an AABB that covers the swept shape (may miss some rotation + // effect). + var aabb1 = new AABB(); + var aabb2 = new AABB(); + this.m_shape.computeAABB(aabb1, xf1, proxy.childIndex); + this.m_shape.computeAABB(aabb2, xf2, proxy.childIndex); + proxy.aabb.combine(aabb1, aabb2); + var displacement = Vec2.sub(xf2.p, xf1.p); + broadPhase.moveProxy(proxy.proxyId, proxy.aabb, displacement); + } + }; + /** + * Set the contact filtering data. This will not update contacts until the next + * time step when either parent body is active and awake. This automatically + * calls refilter. + */ + Fixture.prototype.setFilterData = function (filter) { + this.m_filterGroupIndex = filter.groupIndex; + this.m_filterCategoryBits = filter.categoryBits; + this.m_filterMaskBits = filter.maskBits; + this.refilter(); + }; + Fixture.prototype.getFilterGroupIndex = function () { + return this.m_filterGroupIndex; + }; + Fixture.prototype.setFilterGroupIndex = function (groupIndex) { + this.m_filterGroupIndex = groupIndex; + }; + Fixture.prototype.getFilterCategoryBits = function () { + return this.m_filterCategoryBits; + }; + Fixture.prototype.setFilterCategoryBits = function (categoryBits) { + this.m_filterCategoryBits = categoryBits; + }; + Fixture.prototype.getFilterMaskBits = function () { + return this.m_filterMaskBits; + }; + Fixture.prototype.setFilterMaskBits = function (maskBits) { + this.m_filterMaskBits = maskBits; + }; + /** + * Call this if you want to establish collision that was previously disabled by + * ContactFilter. + */ + Fixture.prototype.refilter = function () { + if (this.m_body == null) { + return; + } + // Flag associated contacts for filtering. + var edge = this.m_body.getContactList(); + while (edge) { + var contact = edge.contact; + var fixtureA = contact.getFixtureA(); + var fixtureB = contact.getFixtureB(); + if (fixtureA == this || fixtureB == this) { + contact.flagForFiltering(); + } + edge = edge.next; + } + var world = this.m_body.getWorld(); + if (world == null) { + return; + } + // Touch each proxy so that new pairs may be created + var broadPhase = world.m_broadPhase; + for (var i = 0; i < this.m_proxyCount; ++i) { + broadPhase.touchProxy(this.m_proxies[i].proxyId); + } + }; + /** + * Implement this method to provide collision filtering, if you want finer + * control over contact creation. + * + * Return true if contact calculations should be performed between these two + * fixtures. + * + * Warning: for performance reasons this is only called when the AABBs begin to + * overlap. + */ + Fixture.prototype.shouldCollide = function (that) { + if (that.m_filterGroupIndex === this.m_filterGroupIndex && that.m_filterGroupIndex !== 0) { + return that.m_filterGroupIndex > 0; + } + var collideA = (that.m_filterMaskBits & this.m_filterCategoryBits) !== 0; + var collideB = (that.m_filterCategoryBits & this.m_filterMaskBits) !== 0; + var collide = collideA && collideB; + return collide; + }; + return Fixture; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +var STATIC = 'static'; +var KINEMATIC = 'kinematic'; +var DYNAMIC = 'dynamic'; +var BodyDefDefault = { + type: STATIC, + position: Vec2.zero(), + angle: 0.0, + linearVelocity: Vec2.zero(), + angularVelocity: 0.0, + linearDamping: 0.0, + angularDamping: 0.0, + fixedRotation: false, + bullet: false, + gravityScale: 1.0, + allowSleep: true, + awake: true, + active: true, + userData: null +}; +/** + * MassData This holds the mass data computed for a shape. + */ +var MassData = /** @class */ (function () { + function MassData() { + /** The mass of the shape, usually in kilograms. */ + this.mass = 0; + /** The position of the shape's centroid relative to the shape's origin. */ + this.center = Vec2.zero(); + /** The rotational inertia of the shape about the local origin. */ + this.I = 0; + } + return MassData; +}()); +/** + * A rigid body composed of one or more fixtures. + * + * To create a new Body use {@link World.createBody}. + */ +var Body = /** @class */ (function () { + /** @internal */ + function Body(world, def) { + def = options(def, BodyDefDefault); + this.m_world = world; + this.m_awakeFlag = def.awake; + this.m_autoSleepFlag = def.allowSleep; + this.m_bulletFlag = def.bullet; + this.m_fixedRotationFlag = def.fixedRotation; + this.m_activeFlag = def.active; + this.m_islandFlag = false; + this.m_toiFlag = false; + this.m_userData = def.userData; + this.m_type = def.type; + if (this.m_type == DYNAMIC) { + this.m_mass = 1.0; + this.m_invMass = 1.0; + } + else { + this.m_mass = 0.0; + this.m_invMass = 0.0; + } + // Rotational inertia about the center of mass. + this.m_I = 0.0; + this.m_invI = 0.0; + // the body origin transform + this.m_xf = Transform.identity(); + this.m_xf.p = Vec2.clone(def.position); + this.m_xf.q.setAngle(def.angle); + // the swept motion for CCD + this.m_sweep = new Sweep(); + this.m_sweep.setTransform(this.m_xf); + // position and velocity correction + this.c_velocity = new Velocity(); + this.c_position = new Position(); + this.m_force = Vec2.zero(); + this.m_torque = 0.0; + this.m_linearVelocity = Vec2.clone(def.linearVelocity); + this.m_angularVelocity = def.angularVelocity; + this.m_linearDamping = def.linearDamping; + this.m_angularDamping = def.angularDamping; + this.m_gravityScale = def.gravityScale; + this.m_sleepTime = 0.0; + this.m_jointList = null; + this.m_contactList = null; + this.m_fixtureList = null; + this.m_prev = null; + this.m_next = null; + this.m_destroyed = false; + } + /** @internal */ + Body.prototype._serialize = function () { + var fixtures = []; + for (var f = this.m_fixtureList; f; f = f.m_next) { + fixtures.push(f); + } + return { + type: this.m_type, + bullet: this.m_bulletFlag, + position: this.m_xf.p, + angle: this.m_xf.q.getAngle(), + linearVelocity: this.m_linearVelocity, + angularVelocity: this.m_angularVelocity, + fixtures: fixtures, + }; + }; + /** @internal */ + Body._deserialize = function (data, world, restore) { + var body = new Body(world, data); + if (data.fixtures) { + for (var i = data.fixtures.length - 1; i >= 0; i--) { + var fixture = restore(Fixture, data.fixtures[i], body); + body._addFixture(fixture); + } + } + return body; + }; + Body.prototype.isWorldLocked = function () { + return this.m_world && this.m_world.isLocked() ? true : false; + }; + Body.prototype.getWorld = function () { + return this.m_world; + }; + Body.prototype.getNext = function () { + return this.m_next; + }; + Body.prototype.setUserData = function (data) { + this.m_userData = data; + }; + Body.prototype.getUserData = function () { + return this.m_userData; + }; + Body.prototype.getFixtureList = function () { + return this.m_fixtureList; + }; + Body.prototype.getJointList = function () { + return this.m_jointList; + }; + /** + * Warning: this list changes during the time step and you may miss some + * collisions if you don't use ContactListener. + */ + Body.prototype.getContactList = function () { + return this.m_contactList; + }; + Body.prototype.isStatic = function () { + return this.m_type == STATIC; + }; + Body.prototype.isDynamic = function () { + return this.m_type == DYNAMIC; + }; + Body.prototype.isKinematic = function () { + return this.m_type == KINEMATIC; + }; + /** + * This will alter the mass and velocity. + */ + Body.prototype.setStatic = function () { + this.setType(STATIC); + return this; + }; + Body.prototype.setDynamic = function () { + this.setType(DYNAMIC); + return this; + }; + Body.prototype.setKinematic = function () { + this.setType(KINEMATIC); + return this; + }; + /** + * @internal + */ + Body.prototype.getType = function () { + return this.m_type; + }; + /** + * @internal + */ + Body.prototype.setType = function (type) { + if (this.isWorldLocked() == true) { + return; + } + if (this.m_type == type) { + return; + } + this.m_type = type; + this.resetMassData(); + if (this.m_type == STATIC) { + this.m_linearVelocity.setZero(); + this.m_angularVelocity = 0.0; + this.m_sweep.forward(); + this.synchronizeFixtures(); + } + this.setAwake(true); + this.m_force.setZero(); + this.m_torque = 0.0; + // Delete the attached contacts. + var ce = this.m_contactList; + while (ce) { + var ce0 = ce; + ce = ce.next; + this.m_world.destroyContact(ce0.contact); + } + this.m_contactList = null; + // Touch the proxies so that new contacts will be created (when appropriate) + var broadPhase = this.m_world.m_broadPhase; + for (var f = this.m_fixtureList; f; f = f.m_next) { + var proxyCount = f.m_proxyCount; + for (var i = 0; i < proxyCount; ++i) { + broadPhase.touchProxy(f.m_proxies[i].proxyId); + } + } + }; + Body.prototype.isBullet = function () { + return this.m_bulletFlag; + }; + /** + * Should this body be treated like a bullet for continuous collision detection? + */ + Body.prototype.setBullet = function (flag) { + this.m_bulletFlag = !!flag; + }; + Body.prototype.isSleepingAllowed = function () { + return this.m_autoSleepFlag; + }; + Body.prototype.setSleepingAllowed = function (flag) { + this.m_autoSleepFlag = !!flag; + if (this.m_autoSleepFlag == false) { + this.setAwake(true); + } + }; + Body.prototype.isAwake = function () { + return this.m_awakeFlag; + }; + /** + * Set the sleep state of the body. A sleeping body has very low CPU cost. + * + * @param flag Set to true to wake the body, false to put it to sleep. + */ + Body.prototype.setAwake = function (flag) { + if (flag) { + if (this.m_awakeFlag == false) { + this.m_awakeFlag = true; + this.m_sleepTime = 0.0; + } + } + else { + this.m_awakeFlag = false; + this.m_sleepTime = 0.0; + this.m_linearVelocity.setZero(); + this.m_angularVelocity = 0.0; + this.m_force.setZero(); + this.m_torque = 0.0; + } + }; + Body.prototype.isActive = function () { + return this.m_activeFlag; + }; + /** + * Set the active state of the body. An inactive body is not simulated and + * cannot be collided with or woken up. If you pass a flag of true, all fixtures + * will be added to the broad-phase. If you pass a flag of false, all fixtures + * will be removed from the broad-phase and all contacts will be destroyed. + * Fixtures and joints are otherwise unaffected. + * + * You may continue to create/destroy fixtures and joints on inactive bodies. + * Fixtures on an inactive body are implicitly inactive and will not participate + * in collisions, ray-casts, or queries. Joints connected to an inactive body + * are implicitly inactive. An inactive body is still owned by a World object + * and remains + */ + Body.prototype.setActive = function (flag) { + if (flag == this.m_activeFlag) { + return; + } + this.m_activeFlag = !!flag; + if (this.m_activeFlag) { + // Create all proxies. + var broadPhase = this.m_world.m_broadPhase; + for (var f = this.m_fixtureList; f; f = f.m_next) { + f.createProxies(broadPhase, this.m_xf); + } + // Contacts are created the next time step. + } + else { + // Destroy all proxies. + var broadPhase = this.m_world.m_broadPhase; + for (var f = this.m_fixtureList; f; f = f.m_next) { + f.destroyProxies(broadPhase); + } + // Destroy the attached contacts. + var ce = this.m_contactList; + while (ce) { + var ce0 = ce; + ce = ce.next; + this.m_world.destroyContact(ce0.contact); + } + this.m_contactList = null; + } + }; + Body.prototype.isFixedRotation = function () { + return this.m_fixedRotationFlag; + }; + /** + * Set this body to have fixed rotation. This causes the mass to be reset. + */ + Body.prototype.setFixedRotation = function (flag) { + if (this.m_fixedRotationFlag == flag) { + return; + } + this.m_fixedRotationFlag = !!flag; + this.m_angularVelocity = 0.0; + this.resetMassData(); + }; + /** + * Get the world transform for the body's origin. + */ + Body.prototype.getTransform = function () { + return this.m_xf; + }; + /** + * Set the position of the body's origin and rotation. Manipulating a body's + * transform may cause non-physical behavior. Note: contacts are updated on the + * next call to World.step. + * + * @param position The world position of the body's local origin. + * @param angle The world rotation in radians. + */ + Body.prototype.setTransform = function (position, angle) { + if (this.isWorldLocked() == true) { + return; + } + this.m_xf.setNum(position, angle); + this.m_sweep.setTransform(this.m_xf); + var broadPhase = this.m_world.m_broadPhase; + for (var f = this.m_fixtureList; f; f = f.m_next) { + f.synchronize(broadPhase, this.m_xf, this.m_xf); + } + }; + Body.prototype.synchronizeTransform = function () { + this.m_sweep.getTransform(this.m_xf, 1); + }; + /** + * Update fixtures in broad-phase. + */ + Body.prototype.synchronizeFixtures = function () { + var xf = Transform.identity(); + this.m_sweep.getTransform(xf, 0); + var broadPhase = this.m_world.m_broadPhase; + for (var f = this.m_fixtureList; f; f = f.m_next) { + f.synchronize(broadPhase, xf, this.m_xf); + } + }; + /** + * Used in TOI. + */ + Body.prototype.advance = function (alpha) { + // Advance to the new safe time. This doesn't sync the broad-phase. + this.m_sweep.advance(alpha); + this.m_sweep.c.setVec2(this.m_sweep.c0); + this.m_sweep.a = this.m_sweep.a0; + this.m_sweep.getTransform(this.m_xf, 1); + }; + /** + * Get the world position for the body's origin. + */ + Body.prototype.getPosition = function () { + return this.m_xf.p; + }; + Body.prototype.setPosition = function (p) { + this.setTransform(p, this.m_sweep.a); + }; + /** + * Get the current world rotation angle in radians. + */ + Body.prototype.getAngle = function () { + return this.m_sweep.a; + }; + Body.prototype.setAngle = function (angle) { + this.setTransform(this.m_xf.p, angle); + }; + /** + * Get the world position of the center of mass. + */ + Body.prototype.getWorldCenter = function () { + return this.m_sweep.c; + }; + /** + * Get the local position of the center of mass. + */ + Body.prototype.getLocalCenter = function () { + return this.m_sweep.localCenter; + }; + /** + * Get the linear velocity of the center of mass. + * + * @return the linear velocity of the center of mass. + */ + Body.prototype.getLinearVelocity = function () { + return this.m_linearVelocity; + }; + /** + * Get the world linear velocity of a world point attached to this body. + * + * @param worldPoint A point in world coordinates. + */ + Body.prototype.getLinearVelocityFromWorldPoint = function (worldPoint) { + var localCenter = Vec2.sub(worldPoint, this.m_sweep.c); + return Vec2.add(this.m_linearVelocity, Vec2.crossNumVec2(this.m_angularVelocity, localCenter)); + }; + /** + * Get the world velocity of a local point. + * + * @param localPoint A point in local coordinates. + */ + Body.prototype.getLinearVelocityFromLocalPoint = function (localPoint) { + return this.getLinearVelocityFromWorldPoint(this.getWorldPoint(localPoint)); + }; + /** + * Set the linear velocity of the center of mass. + * + * @param v The new linear velocity of the center of mass. + */ + Body.prototype.setLinearVelocity = function (v) { + if (this.m_type == STATIC) { + return; + } + if (Vec2.dot(v, v) > 0.0) { + this.setAwake(true); + } + this.m_linearVelocity.setVec2(v); + }; + /** + * Get the angular velocity. + * + * @returns the angular velocity in radians/second. + */ + Body.prototype.getAngularVelocity = function () { + return this.m_angularVelocity; + }; + /** + * Set the angular velocity. + * + * @param omega The new angular velocity in radians/second. + */ + Body.prototype.setAngularVelocity = function (w) { + if (this.m_type == STATIC) { + return; + } + if (w * w > 0.0) { + this.setAwake(true); + } + this.m_angularVelocity = w; + }; + Body.prototype.getLinearDamping = function () { + return this.m_linearDamping; + }; + Body.prototype.setLinearDamping = function (linearDamping) { + this.m_linearDamping = linearDamping; + }; + Body.prototype.getAngularDamping = function () { + return this.m_angularDamping; + }; + Body.prototype.setAngularDamping = function (angularDamping) { + this.m_angularDamping = angularDamping; + }; + Body.prototype.getGravityScale = function () { + return this.m_gravityScale; + }; + /** + * Scale the gravity applied to this body. + */ + Body.prototype.setGravityScale = function (scale) { + this.m_gravityScale = scale; + }; + /** + * Get the total mass of the body. + * + * @returns The mass, usually in kilograms (kg). + */ + Body.prototype.getMass = function () { + return this.m_mass; + }; + /** + * Get the rotational inertia of the body about the local origin. + * + * @return the rotational inertia, usually in kg-m^2. + */ + Body.prototype.getInertia = function () { + return this.m_I + this.m_mass + * Vec2.dot(this.m_sweep.localCenter, this.m_sweep.localCenter); + }; + /** + * Copy the mass data of the body to data. + */ + Body.prototype.getMassData = function (data) { + data.mass = this.m_mass; + data.I = this.getInertia(); + data.center.setVec2(this.m_sweep.localCenter); + }; + /** + * This resets the mass properties to the sum of the mass properties of the + * fixtures. This normally does not need to be called unless you called + * SetMassData to override the mass and you later want to reset the mass. + */ + Body.prototype.resetMassData = function () { + // Compute mass data from shapes. Each shape has its own density. + this.m_mass = 0.0; + this.m_invMass = 0.0; + this.m_I = 0.0; + this.m_invI = 0.0; + this.m_sweep.localCenter.setZero(); + // Static and kinematic bodies have zero mass. + if (this.isStatic() || this.isKinematic()) { + this.m_sweep.c0.setVec2(this.m_xf.p); + this.m_sweep.c.setVec2(this.m_xf.p); + this.m_sweep.a0 = this.m_sweep.a; + return; + } + // Accumulate mass over all fixtures. + var localCenter = Vec2.zero(); + for (var f = this.m_fixtureList; f; f = f.m_next) { + if (f.m_density == 0.0) { + continue; + } + var massData = new MassData(); + f.getMassData(massData); + this.m_mass += massData.mass; + localCenter.addMul(massData.mass, massData.center); + this.m_I += massData.I; + } + // Compute center of mass. + if (this.m_mass > 0.0) { + this.m_invMass = 1.0 / this.m_mass; + localCenter.mul(this.m_invMass); + } + else { + // Force all dynamic bodies to have a positive mass. + this.m_mass = 1.0; + this.m_invMass = 1.0; + } + if (this.m_I > 0.0 && this.m_fixedRotationFlag == false) { + // Center the inertia about the center of mass. + this.m_I -= this.m_mass * Vec2.dot(localCenter, localCenter); + this.m_invI = 1.0 / this.m_I; + } + else { + this.m_I = 0.0; + this.m_invI = 0.0; + } + // Move center of mass. + var oldCenter = Vec2.clone(this.m_sweep.c); + this.m_sweep.setLocalCenter(localCenter, this.m_xf); + // Update center of mass velocity. + this.m_linearVelocity.add(Vec2.crossNumVec2(this.m_angularVelocity, Vec2.sub(this.m_sweep.c, oldCenter))); + }; + /** + * Set the mass properties to override the mass properties of the fixtures. Note + * that this changes the center of mass position. Note that creating or + * destroying fixtures can also alter the mass. This function has no effect if + * the body isn't dynamic. + * + * @param massData The mass properties. + */ + Body.prototype.setMassData = function (massData) { + if (this.isWorldLocked() == true) { + return; + } + if (this.m_type != DYNAMIC) { + return; + } + this.m_invMass = 0.0; + this.m_I = 0.0; + this.m_invI = 0.0; + this.m_mass = massData.mass; + if (this.m_mass <= 0.0) { + this.m_mass = 1.0; + } + this.m_invMass = 1.0 / this.m_mass; + if (massData.I > 0.0 && this.m_fixedRotationFlag == false) { + this.m_I = massData.I - this.m_mass + * Vec2.dot(massData.center, massData.center); + this.m_invI = 1.0 / this.m_I; + } + // Move center of mass. + var oldCenter = Vec2.clone(this.m_sweep.c); + this.m_sweep.setLocalCenter(massData.center, this.m_xf); + // Update center of mass velocity. + this.m_linearVelocity.add(Vec2.crossNumVec2(this.m_angularVelocity, Vec2.sub(this.m_sweep.c, oldCenter))); + }; + /** + * Apply a force at a world point. If the force is not applied at the center of + * mass, it will generate a torque and affect the angular velocity. This wakes + * up the body. + * + * @param force The world force vector, usually in Newtons (N). + * @param point The world position of the point of application. + * @param wake Also wake up the body + */ + Body.prototype.applyForce = function (force, point, wake) { + if (wake === void 0) { wake = true; } + if (this.m_type != DYNAMIC) { + return; + } + if (wake && this.m_awakeFlag == false) { + this.setAwake(true); + } + // Don't accumulate a force if the body is sleeping. + if (this.m_awakeFlag) { + this.m_force.add(force); + this.m_torque += Vec2.crossVec2Vec2(Vec2.sub(point, this.m_sweep.c), force); + } + }; + /** + * Apply a force to the center of mass. This wakes up the body. + * + * @param force The world force vector, usually in Newtons (N). + * @param wake Also wake up the body + */ + Body.prototype.applyForceToCenter = function (force, wake) { + if (wake === void 0) { wake = true; } + if (this.m_type != DYNAMIC) { + return; + } + if (wake && this.m_awakeFlag == false) { + this.setAwake(true); + } + // Don't accumulate a force if the body is sleeping + if (this.m_awakeFlag) { + this.m_force.add(force); + } + }; + /** + * Apply a torque. This affects the angular velocity without affecting the + * linear velocity of the center of mass. This wakes up the body. + * + * @param torque About the z-axis (out of the screen), usually in N-m. + * @param wake Also wake up the body + */ + Body.prototype.applyTorque = function (torque, wake) { + if (wake === void 0) { wake = true; } + if (this.m_type != DYNAMIC) { + return; + } + if (wake && this.m_awakeFlag == false) { + this.setAwake(true); + } + // Don't accumulate a force if the body is sleeping + if (this.m_awakeFlag) { + this.m_torque += torque; + } + }; + /** + * Apply an impulse at a point. This immediately modifies the velocity. It also + * modifies the angular velocity if the point of application is not at the + * center of mass. This wakes up the body. + * + * @param impulse The world impulse vector, usually in N-seconds or kg-m/s. + * @param point The world position of the point of application. + * @param wake Also wake up the body + */ + Body.prototype.applyLinearImpulse = function (impulse, point, wake) { + if (wake === void 0) { wake = true; } + if (this.m_type != DYNAMIC) { + return; + } + if (wake && this.m_awakeFlag == false) { + this.setAwake(true); + } + // Don't accumulate velocity if the body is sleeping + if (this.m_awakeFlag) { + this.m_linearVelocity.addMul(this.m_invMass, impulse); + this.m_angularVelocity += this.m_invI * Vec2.crossVec2Vec2(Vec2.sub(point, this.m_sweep.c), impulse); + } + }; + /** + * Apply an angular impulse. + * + * @param impulse The angular impulse in units of kg*m*m/s + * @param wake Also wake up the body + */ + Body.prototype.applyAngularImpulse = function (impulse, wake) { + if (wake === void 0) { wake = true; } + if (this.m_type != DYNAMIC) { + return; + } + if (wake && this.m_awakeFlag == false) { + this.setAwake(true); + } + // Don't accumulate velocity if the body is sleeping + if (this.m_awakeFlag) { + this.m_angularVelocity += this.m_invI * impulse; + } + }; + /** + * This is used to prevent connected bodies (by joints) from colliding, + * depending on the joint's collideConnected flag. + */ + Body.prototype.shouldCollide = function (that) { + // At least one body should be dynamic. + if (this.m_type != DYNAMIC && that.m_type != DYNAMIC) { + return false; + } + // Does a joint prevent collision? + for (var jn = this.m_jointList; jn; jn = jn.next) { + if (jn.other == that) { + if (jn.joint.m_collideConnected == false) { + return false; + } + } + } + return true; + }; + /** + * @internal Used for deserialize. + */ + Body.prototype._addFixture = function (fixture) { + if (this.isWorldLocked() == true) { + return null; + } + if (this.m_activeFlag) { + var broadPhase = this.m_world.m_broadPhase; + fixture.createProxies(broadPhase, this.m_xf); + } + fixture.m_next = this.m_fixtureList; + this.m_fixtureList = fixture; + // Adjust mass properties if needed. + if (fixture.m_density > 0.0) { + this.resetMassData(); + } + // Let the world know we have a new fixture. This will cause new contacts + // to be created at the beginning of the next time step. + this.m_world.m_newFixture = true; + return fixture; + }; + // tslint:disable-next-line:typedef + Body.prototype.createFixture = function (shape, fixdef) { + if (this.isWorldLocked() == true) { + return null; + } + var fixture = new Fixture(this, shape, fixdef); + this._addFixture(fixture); + return fixture; + }; + /** + * Destroy a fixture. This removes the fixture from the broad-phase and destroys + * all contacts associated with this fixture. This will automatically adjust the + * mass of the body if the body is dynamic and the fixture has positive density. + * All fixtures attached to a body are implicitly destroyed when the body is + * destroyed. + * + * Warning: This function is locked during callbacks. + * + * @param fixture The fixture to be removed. + */ + Body.prototype.destroyFixture = function (fixture) { + if (this.isWorldLocked() == true) { + return; + } + if (this.m_fixtureList === fixture) { + this.m_fixtureList = fixture.m_next; + } + else { + var node = this.m_fixtureList; + while (node != null) { + if (node.m_next === fixture) { + node.m_next = fixture.m_next; + break; + } + node = node.m_next; + } + } + // Destroy any contacts associated with the fixture. + var edge = this.m_contactList; + while (edge) { + var c = edge.contact; + edge = edge.next; + var fixtureA = c.getFixtureA(); + var fixtureB = c.getFixtureB(); + if (fixture == fixtureA || fixture == fixtureB) { + // This destroys the contact and removes it from + // this body's contact list. + this.m_world.destroyContact(c); + } + } + if (this.m_activeFlag) { + var broadPhase = this.m_world.m_broadPhase; + fixture.destroyProxies(broadPhase); + } + fixture.m_body = null; + fixture.m_next = null; + this.m_world.publish('remove-fixture', fixture); + // Reset the mass data. + this.resetMassData(); + }; + /** + * Get the corresponding world point of a local point. + */ + Body.prototype.getWorldPoint = function (localPoint) { + return Transform.mulVec2(this.m_xf, localPoint); + }; + /** + * Get the corresponding world vector of a local vector. + */ + Body.prototype.getWorldVector = function (localVector) { + return Rot.mulVec2(this.m_xf.q, localVector); + }; + /** + * Gets the corresponding local point of a world point. + */ + Body.prototype.getLocalPoint = function (worldPoint) { + return Transform.mulTVec2(this.m_xf, worldPoint); + }; + /** + * Gets the corresponding local vector of a world vector. + */ + Body.prototype.getLocalVector = function (worldVector) { + return Rot.mulTVec2(this.m_xf.q, worldVector); + }; + /** + * A static body does not move under simulation and behaves as if it has infinite mass. + * Internally, zero is stored for the mass and the inverse mass. + * Static bodies can be moved manually by the user. + * A static body has zero velocity. + * Static bodies do not collide with other static or kinematic bodies. + */ + Body.STATIC = 'static'; + /** + * A kinematic body moves under simulation according to its velocity. + * Kinematic bodies do not respond to forces. + * They can be moved manually by the user, but normally a kinematic body is moved by setting its velocity. + * A kinematic body behaves as if it has infinite mass, however, zero is stored for the mass and the inverse mass. + * Kinematic bodies do not collide with other kinematic or static bodies. + */ + Body.KINEMATIC = 'kinematic'; + /** + * A dynamic body is fully simulated. + * They can be moved manually by the user, but normally they move according to forces. + * A dynamic body can collide with all body types. + * A dynamic body always has finite, non-zero mass. + * If you try to set the mass of a dynamic body to zero, it will automatically acquire a mass of one kilogram and it won't rotate. + */ + Body.DYNAMIC = 'dynamic'; + return Body; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * A joint edge is used to connect bodies and joints together in a joint graph + * where each body is a node and each joint is an edge. A joint edge belongs to + * a doubly linked list maintained in each attached body. Each joint has two + * joint nodes, one for each attached body. + */ +var JointEdge = /** @class */ (function () { + function JointEdge() { + /** + * provides quick access to the other body attached. + */ + this.other = null; + /** + * the joint + */ + this.joint = null; + /** + * prev the previous joint edge in the body's joint list + */ + this.prev = null; + /** + * the next joint edge in the body's joint list + */ + this.next = null; + } + return JointEdge; +}()); +/** + * The base joint class. Joints are used to constraint two bodies together in + * various fashions. Some joints also feature limits and motors. + */ +var Joint = /** @class */ (function () { + function Joint(def, bodyA, bodyB) { + /** @internal */ this.m_type = 'unknown-joint'; + /** @internal */ this.m_prev = null; + /** @internal */ this.m_next = null; + /** @internal */ this.m_edgeA = new JointEdge(); + /** @internal */ this.m_edgeB = new JointEdge(); + /** @internal */ this.m_islandFlag = false; + bodyA = 'bodyA' in def ? def.bodyA : bodyA; + bodyB = 'bodyB' in def ? def.bodyB : bodyB; + this.m_bodyA = bodyA; + this.m_bodyB = bodyB; + this.m_collideConnected = !!def.collideConnected; + this.m_userData = def.userData; + } + /** + * Short-cut function to determine if either body is inactive. + */ + Joint.prototype.isActive = function () { + return this.m_bodyA.isActive() && this.m_bodyB.isActive(); + }; + /** + * Get the type of the concrete joint. + */ + Joint.prototype.getType = function () { + return this.m_type; + }; + /** + * Get the first body attached to this joint. + */ + Joint.prototype.getBodyA = function () { + return this.m_bodyA; + }; + /** + * Get the second body attached to this joint. + */ + Joint.prototype.getBodyB = function () { + return this.m_bodyB; + }; + /** + * Get the next joint the world joint list. + */ + Joint.prototype.getNext = function () { + return this.m_next; + }; + Joint.prototype.getUserData = function () { + return this.m_userData; + }; + Joint.prototype.setUserData = function (data) { + this.m_userData = data; + }; + /** + * Get collide connected. Note: modifying the collide connect flag won't work + * correctly because the flag is only checked when fixture AABBs begin to + * overlap. + */ + Joint.prototype.getCollideConnected = function () { + return this.m_collideConnected; + }; + /** + * Shift the origin for any points stored in world coordinates. + */ + Joint.prototype.shiftOrigin = function (newOrigin) { }; + return Joint; +}()); + +var stats = { + gjkCalls: 0, + gjkIters: 0, + gjkMaxIters: 0, + toiTime: 0, + toiMaxTime: 0, + toiCalls: 0, + toiIters: 0, + toiMaxIters: 0, + toiRootIters: 0, + toiMaxRootIters: 0, + toString: function (newline) { + newline = typeof newline === 'string' ? newline : '\n'; + var string = ""; + // tslint:disable-next-line:no-for-in + for (var name_1 in this) { + if (typeof this[name_1] !== 'function' && typeof this[name_1] !== 'object') { + string += name_1 + ': ' + this[name_1] + newline; + } + } + return string; + } +}; + +var now = function () { + return Date.now(); +}; +var diff = function (time) { + return Date.now() - time; +}; +var Timer = { + now: now, + diff: diff, +}; + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * GJK using Voronoi regions (Christer Ericson) and Barycentric coordinates. + */ +stats.gjkCalls = 0; +stats.gjkIters = 0; +stats.gjkMaxIters = 0; +/** + * Input for Distance. You have to option to use the shape radii in the + * computation. Even + */ +var DistanceInput = /** @class */ (function () { + function DistanceInput() { + this.proxyA = new DistanceProxy(); + this.proxyB = new DistanceProxy(); + this.transformA = null; + this.transformB = null; + this.useRadii = false; + } + return DistanceInput; +}()); +/** + * Output for Distance. + * + * @prop {Vec2} pointA closest point on shapeA + * @prop {Vec2} pointB closest point on shapeB + * @prop distance + * @prop iterations number of GJK iterations used + */ +var DistanceOutput = /** @class */ (function () { + function DistanceOutput() { + this.pointA = Vec2.zero(); + this.pointB = Vec2.zero(); + } + return DistanceOutput; +}()); +/** + * Used to warm start Distance. Set count to zero on first call. + * + * @prop {number} metric length or area + * @prop {array} indexA vertices on shape A + * @prop {array} indexB vertices on shape B + * @prop {number} count + */ +var SimplexCache = /** @class */ (function () { + function SimplexCache() { + this.metric = 0; + this.indexA = []; + this.indexB = []; + this.count = 0; + } + return SimplexCache; +}()); +/** + * Compute the closest points between two shapes. Supports any combination of: + * CircleShape, PolygonShape, EdgeShape. The simplex cache is input/output. On + * the first call set SimplexCache.count to zero. + */ +var Distance = function (output, cache, input) { + ++stats.gjkCalls; + var proxyA = input.proxyA; + var proxyB = input.proxyB; + var xfA = input.transformA; + var xfB = input.transformB; + // Initialize the simplex. + var simplex = new Simplex(); + simplex.readCache(cache, proxyA, xfA, proxyB, xfB); + // Get simplex vertices as an array. + var vertices = simplex.m_v; + var k_maxIters = Settings.maxDistnceIterations; + // These store the vertices of the last simplex so that we + // can check for duplicates and prevent cycling. + var saveA = []; + var saveB = []; // int[3] + var saveCount = 0; + // Main iteration loop. + var iter = 0; + while (iter < k_maxIters) { + // Copy simplex so we can identify duplicates. + saveCount = simplex.m_count; + for (var i = 0; i < saveCount; ++i) { + saveA[i] = vertices[i].indexA; + saveB[i] = vertices[i].indexB; + } + simplex.solve(); + // If we have 3 points, then the origin is in the corresponding triangle. + if (simplex.m_count === 3) { + break; + } + // Compute closest point. + var p = simplex.getClosestPoint(); + p.lengthSquared(); + // Get search direction. + var d = simplex.getSearchDirection(); + // Ensure the search direction is numerically fit. + if (d.lengthSquared() < math.EPSILON * math.EPSILON) { + // The origin is probably contained by a line segment + // or triangle. Thus the shapes are overlapped. + // We can't return zero here even though there may be overlap. + // In case the simplex is a point, segment, or triangle it is difficult + // to determine if the origin is contained in the CSO or very close to it. + break; + } + // Compute a tentative new simplex vertex using support points. + var vertex = vertices[simplex.m_count]; // SimplexVertex + vertex.indexA = proxyA.getSupport(Rot.mulTVec2(xfA.q, Vec2.neg(d))); + vertex.wA = Transform.mulVec2(xfA, proxyA.getVertex(vertex.indexA)); + vertex.indexB = proxyB.getSupport(Rot.mulTVec2(xfB.q, d)); + vertex.wB = Transform.mulVec2(xfB, proxyB.getVertex(vertex.indexB)); + vertex.w = Vec2.sub(vertex.wB, vertex.wA); + // Iteration count is equated to the number of support point calls. + ++iter; + ++stats.gjkIters; + // Check for duplicate support points. This is the main termination + // criteria. + var duplicate = false; + for (var i = 0; i < saveCount; ++i) { + if (vertex.indexA === saveA[i] && vertex.indexB === saveB[i]) { + duplicate = true; + break; + } + } + // If we found a duplicate support point we must exit to avoid cycling. + if (duplicate) { + break; + } + // New vertex is ok and needed. + ++simplex.m_count; + } + stats.gjkMaxIters = math.max(stats.gjkMaxIters, iter); + // Prepare output. + simplex.getWitnessPoints(output.pointA, output.pointB); + output.distance = Vec2.distance(output.pointA, output.pointB); + output.iterations = iter; + // Cache the simplex. + simplex.writeCache(cache); + // Apply radii if requested. + if (input.useRadii) { + var rA = proxyA.m_radius; + var rB = proxyB.m_radius; + if (output.distance > rA + rB && output.distance > math.EPSILON) { + // Shapes are still no overlapped. + // Move the witness points to the outer surface. + output.distance -= rA + rB; + var normal = Vec2.sub(output.pointB, output.pointA); + normal.normalize(); + output.pointA.addMul(rA, normal); + output.pointB.subMul(rB, normal); + } + else { + // Shapes are overlapped when radii are considered. + // Move the witness points to the middle. + var p = Vec2.mid(output.pointA, output.pointB); + output.pointA.setVec2(p); + output.pointB.setVec2(p); + output.distance = 0.0; + } + } +}; +/** + * A distance proxy is used by the GJK algorithm. It encapsulates any shape. + */ +var DistanceProxy = /** @class */ (function () { + function DistanceProxy() { + this.m_buffer = []; // Vec2[2] + this.m_vertices = []; // Vec2[] + this.m_count = 0; + this.m_radius = 0; + } + /** + * Get the vertex count. + */ + DistanceProxy.prototype.getVertexCount = function () { + return this.m_count; + }; + /** + * Get a vertex by index. Used by Distance. + */ + DistanceProxy.prototype.getVertex = function (index) { + return this.m_vertices[index]; + }; + /** + * Get the supporting vertex index in the given direction. + */ + DistanceProxy.prototype.getSupport = function (d) { + var bestIndex = 0; + var bestValue = Vec2.dot(this.m_vertices[0], d); + for (var i = 0; i < this.m_count; ++i) { + var value = Vec2.dot(this.m_vertices[i], d); + if (value > bestValue) { + bestIndex = i; + bestValue = value; + } + } + return bestIndex; + }; + /** + * Get the supporting vertex in the given direction. + */ + DistanceProxy.prototype.getSupportVertex = function (d) { + return this.m_vertices[this.getSupport(d)]; + }; + /** + * Initialize the proxy using the given shape. The shape must remain in scope + * while the proxy is in use. + */ + DistanceProxy.prototype.set = function (shape, index) { + shape.computeDistanceProxy(this, index); + }; + return DistanceProxy; +}()); +var SimplexVertex = /** @class */ (function () { + function SimplexVertex() { + /** support point in proxyA */ + this.wA = Vec2.zero(); + /** support point in proxyB */ + this.wB = Vec2.zero(); + /** wB - wA; */ + this.w = Vec2.zero(); + } + SimplexVertex.prototype.set = function (v) { + this.indexA = v.indexA; + this.indexB = v.indexB; + this.wA = Vec2.clone(v.wA); + this.wB = Vec2.clone(v.wB); + this.w = Vec2.clone(v.w); + this.a = v.a; + }; + return SimplexVertex; +}()); +var Simplex = /** @class */ (function () { + function Simplex() { + this.m_v1 = new SimplexVertex(); + this.m_v2 = new SimplexVertex(); + this.m_v3 = new SimplexVertex(); + this.m_v = [this.m_v1, this.m_v2, this.m_v3]; + this.m_count; + } + /** @internal */ + Simplex.prototype.toString = function () { + if (this.m_count === 3) { + return ["+" + this.m_count, + this.m_v1.a, this.m_v1.wA.x, this.m_v1.wA.y, this.m_v1.wB.x, this.m_v1.wB.y, + this.m_v2.a, this.m_v2.wA.x, this.m_v2.wA.y, this.m_v2.wB.x, this.m_v2.wB.y, + this.m_v3.a, this.m_v3.wA.x, this.m_v3.wA.y, this.m_v3.wB.x, this.m_v3.wB.y + ].toString(); + } + else if (this.m_count === 2) { + return ["+" + this.m_count, + this.m_v1.a, this.m_v1.wA.x, this.m_v1.wA.y, this.m_v1.wB.x, this.m_v1.wB.y, + this.m_v2.a, this.m_v2.wA.x, this.m_v2.wA.y, this.m_v2.wB.x, this.m_v2.wB.y + ].toString(); + } + else if (this.m_count === 1) { + return ["+" + this.m_count, + this.m_v1.a, this.m_v1.wA.x, this.m_v1.wA.y, this.m_v1.wB.x, this.m_v1.wB.y + ].toString(); + } + else { + return "+" + this.m_count; + } + }; + Simplex.prototype.readCache = function (cache, proxyA, transformA, proxyB, transformB) { + // Copy data from cache. + this.m_count = cache.count; + for (var i = 0; i < this.m_count; ++i) { + var v = this.m_v[i]; + v.indexA = cache.indexA[i]; + v.indexB = cache.indexB[i]; + var wALocal = proxyA.getVertex(v.indexA); + var wBLocal = proxyB.getVertex(v.indexB); + v.wA = Transform.mulVec2(transformA, wALocal); + v.wB = Transform.mulVec2(transformB, wBLocal); + v.w = Vec2.sub(v.wB, v.wA); + v.a = 0.0; + } + // Compute the new simplex metric, if it is substantially different than + // old metric then flush the simplex. + if (this.m_count > 1) { + var metric1 = cache.metric; + var metric2 = this.getMetric(); + if (metric2 < 0.5 * metric1 || 2.0 * metric1 < metric2 + || metric2 < math.EPSILON) { + // Reset the simplex. + this.m_count = 0; + } + } + // If the cache is empty or invalid... + if (this.m_count === 0) { + var v = this.m_v[0]; + v.indexA = 0; + v.indexB = 0; + var wALocal = proxyA.getVertex(0); + var wBLocal = proxyB.getVertex(0); + v.wA = Transform.mulVec2(transformA, wALocal); + v.wB = Transform.mulVec2(transformB, wBLocal); + v.w = Vec2.sub(v.wB, v.wA); + v.a = 1.0; + this.m_count = 1; + } + }; + Simplex.prototype.writeCache = function (cache) { + cache.metric = this.getMetric(); + cache.count = this.m_count; + for (var i = 0; i < this.m_count; ++i) { + cache.indexA[i] = this.m_v[i].indexA; + cache.indexB[i] = this.m_v[i].indexB; + } + }; + Simplex.prototype.getSearchDirection = function () { + switch (this.m_count) { + case 1: + return Vec2.neg(this.m_v1.w); + case 2: { + var e12 = Vec2.sub(this.m_v2.w, this.m_v1.w); + var sgn = Vec2.crossVec2Vec2(e12, Vec2.neg(this.m_v1.w)); + if (sgn > 0.0) { + // Origin is left of e12. + return Vec2.crossNumVec2(1.0, e12); + } + else { + // Origin is right of e12. + return Vec2.crossVec2Num(e12, 1.0); + } + } + default: + return Vec2.zero(); + } + }; + Simplex.prototype.getClosestPoint = function () { + switch (this.m_count) { + case 0: + return Vec2.zero(); + case 1: + return Vec2.clone(this.m_v1.w); + case 2: + return Vec2.combine(this.m_v1.a, this.m_v1.w, this.m_v2.a, this.m_v2.w); + case 3: + return Vec2.zero(); + default: + return Vec2.zero(); + } + }; + Simplex.prototype.getWitnessPoints = function (pA, pB) { + switch (this.m_count) { + case 0: + break; + case 1: + pA.setVec2(this.m_v1.wA); + pB.setVec2(this.m_v1.wB); + break; + case 2: + pA.setCombine(this.m_v1.a, this.m_v1.wA, this.m_v2.a, this.m_v2.wA); + pB.setCombine(this.m_v1.a, this.m_v1.wB, this.m_v2.a, this.m_v2.wB); + break; + case 3: + pA.setCombine(this.m_v1.a, this.m_v1.wA, this.m_v2.a, this.m_v2.wA); + pA.addMul(this.m_v3.a, this.m_v3.wA); + pB.setVec2(pA); + break; + } + }; + Simplex.prototype.getMetric = function () { + switch (this.m_count) { + case 0: + return 0.0; + case 1: + return 0.0; + case 2: + return Vec2.distance(this.m_v1.w, this.m_v2.w); + case 3: + return Vec2.crossVec2Vec2(Vec2.sub(this.m_v2.w, this.m_v1.w), Vec2.sub(this.m_v3.w, this.m_v1.w)); + default: + return 0.0; + } + }; + Simplex.prototype.solve = function () { + switch (this.m_count) { + case 1: + break; + case 2: + this.solve2(); + break; + case 3: + this.solve3(); + break; + } + }; + // Solve a line segment using barycentric coordinates. + // + // p = a1 * w1 + a2 * w2 + // a1 + a2 = 1 + // + // The vector from the origin to the closest point on the line is + // perpendicular to the line. + // e12 = w2 - w1 + // dot(p, e) = 0 + // a1 * dot(w1, e) + a2 * dot(w2, e) = 0 + // + // 2-by-2 linear system + // [1 1 ][a1] = [1] + // [w1.e12 w2.e12][a2] = [0] + // + // Define + // d12_1 = dot(w2, e12) + // d12_2 = -dot(w1, e12) + // d12 = d12_1 + d12_2 + // + // Solution + // a1 = d12_1 / d12 + // a2 = d12_2 / d12 + Simplex.prototype.solve2 = function () { + var w1 = this.m_v1.w; + var w2 = this.m_v2.w; + var e12 = Vec2.sub(w2, w1); + // w1 region + var d12_2 = -Vec2.dot(w1, e12); + if (d12_2 <= 0.0) { + // a2 <= 0, so we clamp it to 0 + this.m_v1.a = 1.0; + this.m_count = 1; + return; + } + // w2 region + var d12_1 = Vec2.dot(w2, e12); + if (d12_1 <= 0.0) { + // a1 <= 0, so we clamp it to 0 + this.m_v2.a = 1.0; + this.m_count = 1; + this.m_v1.set(this.m_v2); + return; + } + // Must be in e12 region. + var inv_d12 = 1.0 / (d12_1 + d12_2); + this.m_v1.a = d12_1 * inv_d12; + this.m_v2.a = d12_2 * inv_d12; + this.m_count = 2; + }; + // Possible regions: + // - points[2] + // - edge points[0]-points[2] + // - edge points[1]-points[2] + // - inside the triangle + Simplex.prototype.solve3 = function () { + var w1 = this.m_v1.w; + var w2 = this.m_v2.w; + var w3 = this.m_v3.w; + // Edge12 + // [1 1 ][a1] = [1] + // [w1.e12 w2.e12][a2] = [0] + // a3 = 0 + var e12 = Vec2.sub(w2, w1); + var w1e12 = Vec2.dot(w1, e12); + var w2e12 = Vec2.dot(w2, e12); + var d12_1 = w2e12; + var d12_2 = -w1e12; + // Edge13 + // [1 1 ][a1] = [1] + // [w1.e13 w3.e13][a3] = [0] + // a2 = 0 + var e13 = Vec2.sub(w3, w1); + var w1e13 = Vec2.dot(w1, e13); + var w3e13 = Vec2.dot(w3, e13); + var d13_1 = w3e13; + var d13_2 = -w1e13; + // Edge23 + // [1 1 ][a2] = [1] + // [w2.e23 w3.e23][a3] = [0] + // a1 = 0 + var e23 = Vec2.sub(w3, w2); + var w2e23 = Vec2.dot(w2, e23); + var w3e23 = Vec2.dot(w3, e23); + var d23_1 = w3e23; + var d23_2 = -w2e23; + // Triangle123 + var n123 = Vec2.crossVec2Vec2(e12, e13); + var d123_1 = n123 * Vec2.crossVec2Vec2(w2, w3); + var d123_2 = n123 * Vec2.crossVec2Vec2(w3, w1); + var d123_3 = n123 * Vec2.crossVec2Vec2(w1, w2); + // w1 region + if (d12_2 <= 0.0 && d13_2 <= 0.0) { + this.m_v1.a = 1.0; + this.m_count = 1; + return; + } + // e12 + if (d12_1 > 0.0 && d12_2 > 0.0 && d123_3 <= 0.0) { + var inv_d12 = 1.0 / (d12_1 + d12_2); + this.m_v1.a = d12_1 * inv_d12; + this.m_v2.a = d12_2 * inv_d12; + this.m_count = 2; + return; + } + // e13 + if (d13_1 > 0.0 && d13_2 > 0.0 && d123_2 <= 0.0) { + var inv_d13 = 1.0 / (d13_1 + d13_2); + this.m_v1.a = d13_1 * inv_d13; + this.m_v3.a = d13_2 * inv_d13; + this.m_count = 2; + this.m_v2.set(this.m_v3); + return; + } + // w2 region + if (d12_1 <= 0.0 && d23_2 <= 0.0) { + this.m_v2.a = 1.0; + this.m_count = 1; + this.m_v1.set(this.m_v2); + return; + } + // w3 region + if (d13_1 <= 0.0 && d23_1 <= 0.0) { + this.m_v3.a = 1.0; + this.m_count = 1; + this.m_v1.set(this.m_v3); + return; + } + // e23 + if (d23_1 > 0.0 && d23_2 > 0.0 && d123_1 <= 0.0) { + var inv_d23 = 1.0 / (d23_1 + d23_2); + this.m_v2.a = d23_1 * inv_d23; + this.m_v3.a = d23_2 * inv_d23; + this.m_count = 2; + this.m_v1.set(this.m_v3); + return; + } + // Must be in triangle123 + var inv_d123 = 1.0 / (d123_1 + d123_2 + d123_3); + this.m_v1.a = d123_1 * inv_d123; + this.m_v2.a = d123_2 * inv_d123; + this.m_v3.a = d123_3 * inv_d123; + this.m_count = 3; + }; + return Simplex; +}()); +/** + * Determine if two generic shapes overlap. + */ +var testOverlap = function (shapeA, indexA, shapeB, indexB, xfA, xfB) { + var input = new DistanceInput(); + input.proxyA.set(shapeA, indexA); + input.proxyB.set(shapeB, indexB); + input.transformA = xfA; + input.transformB = xfB; + input.useRadii = true; + var cache = new SimplexCache(); + var output = new DistanceOutput(); + Distance(output, cache, input); + return output.distance < 10.0 * math.EPSILON; +}; +// legacy exports +Distance.testOverlap = testOverlap; +Distance.Input = DistanceInput; +Distance.Output = DistanceOutput; +Distance.Proxy = DistanceProxy; +Distance.Cache = SimplexCache; + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * Input parameters for TimeOfImpact. + */ +var TOIInput = /** @class */ (function () { + function TOIInput() { + this.proxyA = new DistanceProxy(); + this.proxyB = new DistanceProxy(); + this.sweepA = new Sweep(); + this.sweepB = new Sweep(); + } + return TOIInput; +}()); +exports.TOIOutputState = void 0; +(function (TOIOutputState) { + TOIOutputState[TOIOutputState["e_unknown"] = 0] = "e_unknown"; + TOIOutputState[TOIOutputState["e_failed"] = 1] = "e_failed"; + TOIOutputState[TOIOutputState["e_overlapped"] = 2] = "e_overlapped"; + TOIOutputState[TOIOutputState["e_touching"] = 3] = "e_touching"; + TOIOutputState[TOIOutputState["e_separated"] = 4] = "e_separated"; +})(exports.TOIOutputState || (exports.TOIOutputState = {})); +/** + * Output parameters for TimeOfImpact. + */ +var TOIOutput = /** @class */ (function () { + function TOIOutput() { + } + return TOIOutput; +}()); +stats.toiTime = 0; +stats.toiMaxTime = 0; +stats.toiCalls = 0; +stats.toiIters = 0; +stats.toiMaxIters = 0; +stats.toiRootIters = 0; +stats.toiMaxRootIters = 0; +/** + * Compute the upper bound on time before two shapes penetrate. Time is + * represented as a fraction between [0,tMax]. This uses a swept separating axis + * and may miss some intermediate, non-tunneling collision. If you change the + * time interval, you should call this function again. + * + * Note: use Distance to compute the contact point and normal at the time of + * impact. + * + * CCD via the local separating axis method. This seeks progression by computing + * the largest time at which separation is maintained. + */ +var TimeOfImpact = function (output, input) { + var timer = Timer.now(); + ++stats.toiCalls; + output.state = exports.TOIOutputState.e_unknown; + output.t = input.tMax; + var proxyA = input.proxyA; // DistanceProxy + var proxyB = input.proxyB; // DistanceProxy + var sweepA = input.sweepA; // Sweep + var sweepB = input.sweepB; // Sweep + // Large rotations can make the root finder fail, so we normalize the + // sweep angles. + sweepA.normalize(); + sweepB.normalize(); + var tMax = input.tMax; + var totalRadius = proxyA.m_radius + proxyB.m_radius; + var target = math.max(Settings.linearSlop, totalRadius - 3.0 * Settings.linearSlop); + var tolerance = 0.25 * Settings.linearSlop; + var t1 = 0.0; + var k_maxIterations = Settings.maxTOIIterations; + var iter = 0; + // Prepare input for distance query. + var cache = new SimplexCache(); + var distanceInput = new DistanceInput(); + distanceInput.proxyA = input.proxyA; + distanceInput.proxyB = input.proxyB; + distanceInput.useRadii = false; + // The outer loop progressively attempts to compute new separating axes. + // This loop terminates when an axis is repeated (no progress is made). + while (true) { + var xfA = Transform.identity(); + var xfB = Transform.identity(); + sweepA.getTransform(xfA, t1); + sweepB.getTransform(xfB, t1); + // Get the distance between shapes. We can also use the results + // to get a separating axis. + distanceInput.transformA = xfA; + distanceInput.transformB = xfB; + var distanceOutput = new DistanceOutput(); + Distance(distanceOutput, cache, distanceInput); + // If the shapes are overlapped, we give up on continuous collision. + if (distanceOutput.distance <= 0.0) { + // Failure! + output.state = exports.TOIOutputState.e_overlapped; + output.t = 0.0; + break; + } + if (distanceOutput.distance < target + tolerance) { + // Victory! + output.state = exports.TOIOutputState.e_touching; + output.t = t1; + break; + } + // Initialize the separating axis. + var fcn = new SeparationFunction(); + fcn.initialize(cache, proxyA, sweepA, proxyB, sweepB, t1); + // if (false) { + // // Dump the curve seen by the root finder + // const N = 100; + // const dx = 1.0 / N; + // const xs = []; // [ N + 1 ]; + // const fs = []; // [ N + 1 ]; + // const x = 0.0; + // for (const i = 0; i <= N; ++i) { + // sweepA.getTransform(xfA, x); + // sweepB.getTransform(xfB, x); + // const f = fcn.evaluate(xfA, xfB) - target; + // printf("%g %g\n", x, f); + // xs[i] = x; + // fs[i] = f; + // x += dx; + // } + // } + // Compute the TOI on the separating axis. We do this by successively + // resolving the deepest point. This loop is bounded by the number of + // vertices. + var done = false; + var t2 = tMax; + var pushBackIter = 0; + while (true) { + // Find the deepest point at t2. Store the witness point indices. + var s2 = fcn.findMinSeparation(t2); + // const indexA = fcn.indexA; + // const indexB = fcn.indexB; + // Is the final configuration separated? + if (s2 > target + tolerance) { + // Victory! + output.state = exports.TOIOutputState.e_separated; + output.t = tMax; + done = true; + break; + } + // Has the separation reached tolerance? + if (s2 > target - tolerance) { + // Advance the sweeps + t1 = t2; + break; + } + // Compute the initial separation of the witness points. + var s1 = fcn.evaluate(t1); + // const indexA = fcn.indexA; + // const indexB = fcn.indexB; + // Check for initial overlap. This might happen if the root finder + // runs out of iterations. + if (s1 < target - tolerance) { + output.state = exports.TOIOutputState.e_failed; + output.t = t1; + done = true; + break; + } + // Check for touching + if (s1 <= target + tolerance) { + // Victory! t1 should hold the TOI (could be 0.0). + output.state = exports.TOIOutputState.e_touching; + output.t = t1; + done = true; + break; + } + // Compute 1D root of: f(x) - target = 0 + var rootIterCount = 0; + var a1 = t1; + var a2 = t2; + while (true) { + // Use a mix of the secant rule and bisection. + var t = void 0; + if (rootIterCount & 1) { + // Secant rule to improve convergence. + t = a1 + (target - s1) * (a2 - a1) / (s2 - s1); + } + else { + // Bisection to guarantee progress. + t = 0.5 * (a1 + a2); + } + ++rootIterCount; + ++stats.toiRootIters; + var s = fcn.evaluate(t); + fcn.indexA; + fcn.indexB; + if (math.abs(s - target) < tolerance) { + // t2 holds a tentative value for t1 + t2 = t; + break; + } + // Ensure we continue to bracket the root. + if (s > target) { + a1 = t; + s1 = s; + } + else { + a2 = t; + s2 = s; + } + if (rootIterCount === 50) { + break; + } + } + stats.toiMaxRootIters = math.max(stats.toiMaxRootIters, rootIterCount); + ++pushBackIter; + if (pushBackIter === Settings.maxPolygonVertices) { + break; + } + } + ++iter; + ++stats.toiIters; + if (done) { + break; + } + if (iter === k_maxIterations) { + // Root finder got stuck. Semi-victory. + output.state = exports.TOIOutputState.e_failed; + output.t = t1; + break; + } + } + stats.toiMaxIters = math.max(stats.toiMaxIters, iter); + var time = Timer.diff(timer); + stats.toiMaxTime = math.max(stats.toiMaxTime, time); + stats.toiTime += time; +}; +var SeparationFunctionType; +(function (SeparationFunctionType) { + SeparationFunctionType[SeparationFunctionType["e_points"] = 1] = "e_points"; + SeparationFunctionType[SeparationFunctionType["e_faceA"] = 2] = "e_faceA"; + SeparationFunctionType[SeparationFunctionType["e_faceB"] = 3] = "e_faceB"; +})(SeparationFunctionType || (SeparationFunctionType = {})); +var SeparationFunction = /** @class */ (function () { + function SeparationFunction() { + this.m_proxyA = new DistanceProxy(); + this.m_proxyB = new DistanceProxy(); + this.m_localPoint = Vec2.zero(); + this.m_axis = Vec2.zero(); + } + // TODO_ERIN might not need to return the separation + SeparationFunction.prototype.initialize = function (cache, proxyA, sweepA, proxyB, sweepB, t1) { + this.m_proxyA = proxyA; + this.m_proxyB = proxyB; + var count = cache.count; + this.m_sweepA = sweepA; + this.m_sweepB = sweepB; + var xfA = Transform.identity(); + var xfB = Transform.identity(); + this.m_sweepA.getTransform(xfA, t1); + this.m_sweepB.getTransform(xfB, t1); + if (count === 1) { + this.m_type = SeparationFunctionType.e_points; + var localPointA = this.m_proxyA.getVertex(cache.indexA[0]); + var localPointB = this.m_proxyB.getVertex(cache.indexB[0]); + var pointA = Transform.mulVec2(xfA, localPointA); + var pointB = Transform.mulVec2(xfB, localPointB); + this.m_axis.setCombine(1, pointB, -1, pointA); + var s = this.m_axis.normalize(); + return s; + } + else if (cache.indexA[0] === cache.indexA[1]) { + // Two points on B and one on A. + this.m_type = SeparationFunctionType.e_faceB; + var localPointB1 = proxyB.getVertex(cache.indexB[0]); + var localPointB2 = proxyB.getVertex(cache.indexB[1]); + this.m_axis = Vec2.crossVec2Num(Vec2.sub(localPointB2, localPointB1), 1.0); + this.m_axis.normalize(); + var normal = Rot.mulVec2(xfB.q, this.m_axis); + this.m_localPoint = Vec2.mid(localPointB1, localPointB2); + var pointB = Transform.mulVec2(xfB, this.m_localPoint); + var localPointA = proxyA.getVertex(cache.indexA[0]); + var pointA = Transform.mulVec2(xfA, localPointA); + var s = Vec2.dot(pointA, normal) - Vec2.dot(pointB, normal); + if (s < 0.0) { + this.m_axis = Vec2.neg(this.m_axis); + s = -s; + } + return s; + } + else { + // Two points on A and one or two points on B. + this.m_type = SeparationFunctionType.e_faceA; + var localPointA1 = this.m_proxyA.getVertex(cache.indexA[0]); + var localPointA2 = this.m_proxyA.getVertex(cache.indexA[1]); + this.m_axis = Vec2.crossVec2Num(Vec2.sub(localPointA2, localPointA1), 1.0); + this.m_axis.normalize(); + var normal = Rot.mulVec2(xfA.q, this.m_axis); + this.m_localPoint = Vec2.mid(localPointA1, localPointA2); + var pointA = Transform.mulVec2(xfA, this.m_localPoint); + var localPointB = this.m_proxyB.getVertex(cache.indexB[0]); + var pointB = Transform.mulVec2(xfB, localPointB); + var s = Vec2.dot(pointB, normal) - Vec2.dot(pointA, normal); + if (s < 0.0) { + this.m_axis = Vec2.neg(this.m_axis); + s = -s; + } + return s; + } + }; + SeparationFunction.prototype.compute = function (find, t) { + // It was findMinSeparation and evaluate + var xfA = Transform.identity(); + var xfB = Transform.identity(); + this.m_sweepA.getTransform(xfA, t); + this.m_sweepB.getTransform(xfB, t); + switch (this.m_type) { + case SeparationFunctionType.e_points: { + if (find) { + var axisA = Rot.mulTVec2(xfA.q, this.m_axis); + var axisB = Rot.mulTVec2(xfB.q, Vec2.neg(this.m_axis)); + this.indexA = this.m_proxyA.getSupport(axisA); + this.indexB = this.m_proxyB.getSupport(axisB); + } + var localPointA = this.m_proxyA.getVertex(this.indexA); + var localPointB = this.m_proxyB.getVertex(this.indexB); + var pointA = Transform.mulVec2(xfA, localPointA); + var pointB = Transform.mulVec2(xfB, localPointB); + var sep = Vec2.dot(pointB, this.m_axis) - Vec2.dot(pointA, this.m_axis); + return sep; + } + case SeparationFunctionType.e_faceA: { + var normal = Rot.mulVec2(xfA.q, this.m_axis); + var pointA = Transform.mulVec2(xfA, this.m_localPoint); + if (find) { + var axisB = Rot.mulTVec2(xfB.q, Vec2.neg(normal)); + this.indexA = -1; + this.indexB = this.m_proxyB.getSupport(axisB); + } + var localPointB = this.m_proxyB.getVertex(this.indexB); + var pointB = Transform.mulVec2(xfB, localPointB); + var sep = Vec2.dot(pointB, normal) - Vec2.dot(pointA, normal); + return sep; + } + case SeparationFunctionType.e_faceB: { + var normal = Rot.mulVec2(xfB.q, this.m_axis); + var pointB = Transform.mulVec2(xfB, this.m_localPoint); + if (find) { + var axisA = Rot.mulTVec2(xfA.q, Vec2.neg(normal)); + this.indexB = -1; + this.indexA = this.m_proxyA.getSupport(axisA); + } + var localPointA = this.m_proxyA.getVertex(this.indexA); + var pointA = Transform.mulVec2(xfA, localPointA); + var sep = Vec2.dot(pointA, normal) - Vec2.dot(pointB, normal); + return sep; + } + default: + if (find) { + this.indexA = -1; + this.indexB = -1; + } + return 0.0; + } + }; + SeparationFunction.prototype.findMinSeparation = function (t) { + return this.compute(true, t); + }; + SeparationFunction.prototype.evaluate = function (t) { + return this.compute(false, t); + }; + return SeparationFunction; +}()); +new SeparationFunction(); +// legacy exports +TimeOfImpact.Input = TOIInput; +TimeOfImpact.Output = TOIOutput; + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +var TimeStep = /** @class */ (function () { + function TimeStep() { + /** time step */ + this.dt = 0; + /** inverse time step (0 if dt == 0) */ + this.inv_dt = 0; + this.velocityIterations = 0; + this.positionIterations = 0; + this.warmStarting = false; + this.blockSolve = true; + /** timestep ratio for variable timestep */ + this.inv_dt0 = 0.0; + /** dt * inv_dt0 */ + this.dtRatio = 1; + } + TimeStep.prototype.reset = function (dt) { + if (this.dt > 0.0) { + this.inv_dt0 = this.inv_dt; + } + this.dt = dt; + this.inv_dt = dt == 0 ? 0 : 1 / dt; + this.dtRatio = dt * this.inv_dt0; + }; + return TimeStep; +}()); +// reuse +var s_subStep = new TimeStep(); +/** + * Contact impulses for reporting. Impulses are used instead of forces because + * sub-step forces may approach infinity for rigid body collisions. These match + * up one-to-one with the contact points in Manifold. + */ +var ContactImpulse = /** @class */ (function () { + function ContactImpulse(contact) { + this.contact = contact; + this.normals = []; + this.tangents = []; + } + Object.defineProperty(ContactImpulse.prototype, "normalImpulses", { + get: function () { + var contact = this.contact; + var normals = this.normals; + normals.length = 0; + for (var p = 0; p < contact.v_points.length; ++p) { + normals.push(contact.v_points[p].normalImpulse); + } + return normals; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ContactImpulse.prototype, "tangentImpulses", { + get: function () { + var contact = this.contact; + var tangents = this.tangents; + tangents.length = 0; + for (var p = 0; p < contact.v_points.length; ++p) { + tangents.push(contact.v_points[p].tangentImpulse); + } + return tangents; + }, + enumerable: false, + configurable: true + }); + return ContactImpulse; +}()); +/** + * Finds and solves islands. An island is a connected subset of the world. + */ +var Solver = /** @class */ (function () { + function Solver(world) { + this.m_world = world; + this.m_stack = []; + this.m_bodies = []; + this.m_contacts = []; + this.m_joints = []; + } + Solver.prototype.clear = function () { + this.m_stack.length = 0; + this.m_bodies.length = 0; + this.m_contacts.length = 0; + this.m_joints.length = 0; + }; + Solver.prototype.addBody = function (body) { + this.m_bodies.push(body); + // why? + // body.c_position.c.setZero(); + // body.c_position.a = 0; + // body.c_velocity.v.setZero(); + // body.c_velocity.w = 0; + }; + Solver.prototype.addContact = function (contact) { + // false && console.assert(contact instanceof Contact, 'Not a Contact!', contact); + this.m_contacts.push(contact); + }; + Solver.prototype.addJoint = function (joint) { + this.m_joints.push(joint); + }; + Solver.prototype.solveWorld = function (step) { + var world = this.m_world; + // Clear all the island flags. + for (var b = world.m_bodyList; b; b = b.m_next) { + b.m_islandFlag = false; + } + for (var c = world.m_contactList; c; c = c.m_next) { + c.m_islandFlag = false; + } + for (var j = world.m_jointList; j; j = j.m_next) { + j.m_islandFlag = false; + } + // Build and simulate all awake islands. + var stack = this.m_stack; + for (var seed = world.m_bodyList; seed; seed = seed.m_next) { + if (seed.m_islandFlag) { + continue; + } + if (seed.isAwake() == false || seed.isActive() == false) { + continue; + } + // The seed can be dynamic or kinematic. + if (seed.isStatic()) { + continue; + } + // Reset island and stack. + this.clear(); + stack.push(seed); + seed.m_islandFlag = true; + // Perform a depth first search (DFS) on the constraint graph. + while (stack.length > 0) { + // Grab the next body off the stack and add it to the island. + var b = stack.pop(); + this.addBody(b); + // Make sure the body is awake. + b.setAwake(true); + // To keep islands as small as possible, we don't + // propagate islands across static bodies. + if (b.isStatic()) { + continue; + } + // Search all contacts connected to this body. + for (var ce = b.m_contactList; ce; ce = ce.next) { + var contact = ce.contact; + // Has this contact already been added to an island? + if (contact.m_islandFlag) { + continue; + } + // Is this contact solid and touching? + if (contact.isEnabled() == false || contact.isTouching() == false) { + continue; + } + // Skip sensors. + var sensorA = contact.m_fixtureA.m_isSensor; + var sensorB = contact.m_fixtureB.m_isSensor; + if (sensorA || sensorB) { + continue; + } + this.addContact(contact); + contact.m_islandFlag = true; + var other = ce.other; + // Was the other body already added to this island? + if (other.m_islandFlag) { + continue; + } + // false && console.assert(stack.length < world.m_bodyCount); + stack.push(other); + other.m_islandFlag = true; + } + // Search all joints connect to this body. + for (var je = b.m_jointList; je; je = je.next) { + if (je.joint.m_islandFlag == true) { + continue; + } + var other = je.other; + // Don't simulate joints connected to inactive bodies. + if (other.isActive() == false) { + continue; + } + this.addJoint(je.joint); + je.joint.m_islandFlag = true; + if (other.m_islandFlag) { + continue; + } + // false && console.assert(stack.length < world.m_bodyCount); + stack.push(other); + other.m_islandFlag = true; + } + } + this.solveIsland(step); + // Post solve cleanup. + for (var i = 0; i < this.m_bodies.length; ++i) { + // Allow static bodies to participate in other islands. + // TODO: are they added at all? + var b = this.m_bodies[i]; + if (b.isStatic()) { + b.m_islandFlag = false; + } + } + } + }; + Solver.prototype.solveIsland = function (step) { + // B2: Island Solve + var world = this.m_world; + var gravity = world.m_gravity; + var allowSleep = world.m_allowSleep; + var h = step.dt; + // Integrate velocities and apply damping. Initialize the body state. + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + var c = Vec2.clone(body.m_sweep.c); + var a = body.m_sweep.a; + var v = Vec2.clone(body.m_linearVelocity); + var w = body.m_angularVelocity; + // Store positions for continuous collision. + body.m_sweep.c0.setVec2(body.m_sweep.c); + body.m_sweep.a0 = body.m_sweep.a; + if (body.isDynamic()) { + // Integrate velocities. + v.addMul(h * body.m_gravityScale, gravity); + v.addMul(h * body.m_invMass, body.m_force); + w += h * body.m_invI * body.m_torque; + /** + *
+ * Apply damping. + * ODE: dv/dt + c * v = 0 + * Solution: v(t) = v0 * exp(-c * t) + * Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt) + * v2 = exp(-c * dt) * v1 + * Pade approximation: + * v2 = v1 * 1 / (1 + c * dt) + *+ */ + v.mul(1.0 / (1.0 + h * body.m_linearDamping)); + w *= 1.0 / (1.0 + h * body.m_angularDamping); + } + body.c_position.c = c; + body.c_position.a = a; + body.c_velocity.v = v; + body.c_velocity.w = w; + } + for (var i = 0; i < this.m_contacts.length; ++i) { + var contact = this.m_contacts[i]; + contact.initConstraint(step); + } + for (var i = 0; i < this.m_contacts.length; ++i) { + var contact = this.m_contacts[i]; + contact.initVelocityConstraint(step); + } + if (step.warmStarting) { + // Warm start. + for (var i = 0; i < this.m_contacts.length; ++i) { + var contact = this.m_contacts[i]; + contact.warmStartConstraint(step); + } + } + for (var i = 0; i < this.m_joints.length; ++i) { + var joint = this.m_joints[i]; + joint.initVelocityConstraints(step); + } + // Solve velocity constraints + for (var i = 0; i < step.velocityIterations; ++i) { + for (var j = 0; j < this.m_joints.length; ++j) { + var joint = this.m_joints[j]; + joint.solveVelocityConstraints(step); + } + for (var j = 0; j < this.m_contacts.length; ++j) { + var contact = this.m_contacts[j]; + contact.solveVelocityConstraint(step); + } + } + // Store impulses for warm starting + for (var i = 0; i < this.m_contacts.length; ++i) { + var contact = this.m_contacts[i]; + contact.storeConstraintImpulses(step); + } + // Integrate positions + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + var c = Vec2.clone(body.c_position.c); + var a = body.c_position.a; + var v = Vec2.clone(body.c_velocity.v); + var w = body.c_velocity.w; + // Check for large velocities + var translation = Vec2.mulNumVec2(h, v); + if (Vec2.lengthSquared(translation) > Settings.maxTranslationSquared) { + var ratio = Settings.maxTranslation / translation.length(); + v.mul(ratio); + } + var rotation = h * w; + if (rotation * rotation > Settings.maxRotationSquared) { + var ratio = Settings.maxRotation / math.abs(rotation); + w *= ratio; + } + // Integrate + c.addMul(h, v); + a += h * w; + body.c_position.c.setVec2(c); + body.c_position.a = a; + body.c_velocity.v.setVec2(v); + body.c_velocity.w = w; + } + // Solve position constraints + var positionSolved = false; + for (var i = 0; i < step.positionIterations; ++i) { + var minSeparation = 0.0; + for (var j = 0; j < this.m_contacts.length; ++j) { + var contact = this.m_contacts[j]; + var separation = contact.solvePositionConstraint(step); + minSeparation = math.min(minSeparation, separation); + } + // We can't expect minSpeparation >= -Settings.linearSlop because we don't + // push the separation above -Settings.linearSlop. + var contactsOkay = minSeparation >= -3.0 * Settings.linearSlop; + var jointsOkay = true; + for (var j = 0; j < this.m_joints.length; ++j) { + var joint = this.m_joints[j]; + var jointOkay = joint.solvePositionConstraints(step); + jointsOkay = jointsOkay && jointOkay; + } + if (contactsOkay && jointsOkay) { + // Exit early if the position errors are small. + positionSolved = true; + break; + } + } + // Copy state buffers back to the bodies + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + body.m_sweep.c.setVec2(body.c_position.c); + body.m_sweep.a = body.c_position.a; + body.m_linearVelocity.setVec2(body.c_velocity.v); + body.m_angularVelocity = body.c_velocity.w; + body.synchronizeTransform(); + } + this.postSolveIsland(); + if (allowSleep) { + var minSleepTime = Infinity; + var linTolSqr = Settings.linearSleepToleranceSqr; + var angTolSqr = Settings.angularSleepToleranceSqr; + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + if (body.isStatic()) { + continue; + } + if ((body.m_autoSleepFlag == false) + || (body.m_angularVelocity * body.m_angularVelocity > angTolSqr) + || (Vec2.lengthSquared(body.m_linearVelocity) > linTolSqr)) { + body.m_sleepTime = 0.0; + minSleepTime = 0.0; + } + else { + body.m_sleepTime += h; + minSleepTime = math.min(minSleepTime, body.m_sleepTime); + } + } + if (minSleepTime >= Settings.timeToSleep && positionSolved) { + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + body.setAwake(false); + } + } + } + }; + /** + * Find TOI contacts and solve them. + */ + Solver.prototype.solveWorldTOI = function (step) { + var world = this.m_world; + if (world.m_stepComplete) { + for (var b = world.m_bodyList; b; b = b.m_next) { + b.m_islandFlag = false; + b.m_sweep.alpha0 = 0.0; + } + for (var c = world.m_contactList; c; c = c.m_next) { + // Invalidate TOI + c.m_toiFlag = false; + c.m_islandFlag = false; + c.m_toiCount = 0; + c.m_toi = 1.0; + } + } + // Find TOI events and solve them. + while (true) { + // Find the first TOI. + var minContact = null; // Contact + var minAlpha = 1.0; + for (var c = world.m_contactList; c; c = c.m_next) { + // Is this contact disabled? + if (c.isEnabled() == false) { + continue; + } + // Prevent excessive sub-stepping. + if (c.m_toiCount > Settings.maxSubSteps) { + continue; + } + var alpha = 1.0; + if (c.m_toiFlag) { + // This contact has a valid cached TOI. + alpha = c.m_toi; + } + else { + var fA_1 = c.getFixtureA(); + var fB_1 = c.getFixtureB(); + // Is there a sensor? + if (fA_1.isSensor() || fB_1.isSensor()) { + continue; + } + var bA_1 = fA_1.getBody(); + var bB_1 = fB_1.getBody(); + var activeA = bA_1.isAwake() && !bA_1.isStatic(); + var activeB = bB_1.isAwake() && !bB_1.isStatic(); + // Is at least one body active (awake and dynamic or kinematic)? + if (activeA == false && activeB == false) { + continue; + } + var collideA = bA_1.isBullet() || !bA_1.isDynamic(); + var collideB = bB_1.isBullet() || !bB_1.isDynamic(); + // Are these two non-bullet dynamic bodies? + if (collideA == false && collideB == false) { + continue; + } + // Compute the TOI for this contact. + // Put the sweeps onto the same time interval. + var alpha0 = bA_1.m_sweep.alpha0; + if (bA_1.m_sweep.alpha0 < bB_1.m_sweep.alpha0) { + alpha0 = bB_1.m_sweep.alpha0; + bA_1.m_sweep.advance(alpha0); + } + else if (bB_1.m_sweep.alpha0 < bA_1.m_sweep.alpha0) { + alpha0 = bA_1.m_sweep.alpha0; + bB_1.m_sweep.advance(alpha0); + } + var indexA = c.getChildIndexA(); + var indexB = c.getChildIndexB(); + bA_1.m_sweep; + bB_1.m_sweep; + // Compute the time of impact in interval [0, minTOI] + var input = new TOIInput(); // TODO: reuse + input.proxyA.set(fA_1.getShape(), indexA); + input.proxyB.set(fB_1.getShape(), indexB); + input.sweepA.set(bA_1.m_sweep); + input.sweepB.set(bB_1.m_sweep); + input.tMax = 1.0; + var output = new TOIOutput(); // TODO: reuse + TimeOfImpact(output, input); + // Beta is the fraction of the remaining portion of the [time?]. + var beta = output.t; + if (output.state == exports.TOIOutputState.e_touching) { + alpha = math.min(alpha0 + (1.0 - alpha0) * beta, 1.0); + } + else { + alpha = 1.0; + } + c.m_toi = alpha; + c.m_toiFlag = true; + } + if (alpha < minAlpha) { + // This is the minimum TOI found so far. + minContact = c; + minAlpha = alpha; + } + } + if (minContact == null || 1.0 - 10.0 * math.EPSILON < minAlpha) { + // No more TOI events. Done! + world.m_stepComplete = true; + break; + } + // Advance the bodies to the TOI. + var fA = minContact.getFixtureA(); + var fB = minContact.getFixtureB(); + var bA = fA.getBody(); + var bB = fB.getBody(); + var backup1 = bA.m_sweep.clone(); + var backup2 = bB.m_sweep.clone(); + bA.advance(minAlpha); + bB.advance(minAlpha); + // The TOI contact likely has some new contact points. + minContact.update(world); + minContact.m_toiFlag = false; + ++minContact.m_toiCount; + // Is the contact solid? + if (minContact.isEnabled() == false || minContact.isTouching() == false) { + // Restore the sweeps. + minContact.setEnabled(false); + bA.m_sweep.set(backup1); + bB.m_sweep.set(backup2); + bA.synchronizeTransform(); + bB.synchronizeTransform(); + continue; + } + bA.setAwake(true); + bB.setAwake(true); + // Build the island + this.clear(); + this.addBody(bA); + this.addBody(bB); + this.addContact(minContact); + bA.m_islandFlag = true; + bB.m_islandFlag = true; + minContact.m_islandFlag = true; + // Get contacts on bodyA and bodyB. + var bodies = [bA, bB]; + for (var i = 0; i < bodies.length; ++i) { + var body = bodies[i]; + if (body.isDynamic()) { + for (var ce = body.m_contactList; ce; ce = ce.next) { + // if (this.m_bodyCount == this.m_bodyCapacity) { break; } + // if (this.m_contactCount == this.m_contactCapacity) { break; } + var contact = ce.contact; + // Has this contact already been added to the island? + if (contact.m_islandFlag) { + continue; + } + // Only add if either is static, kinematic or bullet. + var other = ce.other; + if (other.isDynamic() && !body.isBullet() && !other.isBullet()) { + continue; + } + // Skip sensors. + var sensorA = contact.m_fixtureA.m_isSensor; + var sensorB = contact.m_fixtureB.m_isSensor; + if (sensorA || sensorB) { + continue; + } + // Tentatively advance the body to the TOI. + var backup = other.m_sweep.clone(); + if (other.m_islandFlag == false) { + other.advance(minAlpha); + } + // Update the contact points + contact.update(world); + // Was the contact disabled by the user? + // Are there contact points? + if (contact.isEnabled() == false || contact.isTouching() == false) { + other.m_sweep.set(backup); + other.synchronizeTransform(); + continue; + } + // Add the contact to the island + contact.m_islandFlag = true; + this.addContact(contact); + // Has the other body already been added to the island? + if (other.m_islandFlag) { + continue; + } + // Add the other body to the island. + other.m_islandFlag = true; + if (!other.isStatic()) { + other.setAwake(true); + } + this.addBody(other); + } + } + } + s_subStep.reset((1.0 - minAlpha) * step.dt); + s_subStep.dtRatio = 1.0; + s_subStep.positionIterations = 20; + s_subStep.velocityIterations = step.velocityIterations; + s_subStep.warmStarting = false; + this.solveIslandTOI(s_subStep, bA, bB); + // Reset island flags and synchronize broad-phase proxies. + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + body.m_islandFlag = false; + if (!body.isDynamic()) { + continue; + } + body.synchronizeFixtures(); + // Invalidate all contact TOIs on this displaced body. + for (var ce = body.m_contactList; ce; ce = ce.next) { + ce.contact.m_toiFlag = false; + ce.contact.m_islandFlag = false; + } + } + // Commit fixture proxy movements to the broad-phase so that new contacts + // are created. + // Also, some contacts can be destroyed. + world.findNewContacts(); + if (world.m_subStepping) { + world.m_stepComplete = false; + break; + } + } + }; + Solver.prototype.solveIslandTOI = function (subStep, toiA, toiB) { + this.m_world; + // Initialize the body state. + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + body.c_position.c.setVec2(body.m_sweep.c); + body.c_position.a = body.m_sweep.a; + body.c_velocity.v.setVec2(body.m_linearVelocity); + body.c_velocity.w = body.m_angularVelocity; + } + for (var i = 0; i < this.m_contacts.length; ++i) { + var contact = this.m_contacts[i]; + contact.initConstraint(subStep); + } + // Solve position constraints. + for (var i = 0; i < subStep.positionIterations; ++i) { + var minSeparation = 0.0; + for (var j = 0; j < this.m_contacts.length; ++j) { + var contact = this.m_contacts[j]; + var separation = contact.solvePositionConstraintTOI(subStep, toiA, toiB); + minSeparation = math.min(minSeparation, separation); + } + // We can't expect minSpeparation >= -Settings.linearSlop because we don't + // push the separation above -Settings.linearSlop. + var contactsOkay = minSeparation >= -1.5 * Settings.linearSlop; + if (contactsOkay) { + break; + } + } + var i, c; + // Leap of faith to new safe state. + toiA.m_sweep.c0.setVec2(toiA.c_position.c); + toiA.m_sweep.a0 = toiA.c_position.a; + toiB.m_sweep.c0.setVec2(toiB.c_position.c); + toiB.m_sweep.a0 = toiB.c_position.a; + // No warm starting is needed for TOI events because warm + // starting impulses were applied in the discrete solver. + for (var i = 0; i < this.m_contacts.length; ++i) { + var contact = this.m_contacts[i]; + contact.initVelocityConstraint(subStep); + } + // Solve velocity constraints. + for (var i = 0; i < subStep.velocityIterations; ++i) { + for (var j = 0; j < this.m_contacts.length; ++j) { + var contact = this.m_contacts[j]; + contact.solveVelocityConstraint(subStep); + } + } + // Don't store the TOI contact forces for warm starting + // because they can be quite large. + var h = subStep.dt; + // Integrate positions + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + var c = Vec2.clone(body.c_position.c); + var a = body.c_position.a; + var v = Vec2.clone(body.c_velocity.v); + var w = body.c_velocity.w; + // Check for large velocities + var translation = Vec2.mulNumVec2(h, v); + if (Vec2.dot(translation, translation) > Settings.maxTranslationSquared) { + var ratio = Settings.maxTranslation / translation.length(); + v.mul(ratio); + } + var rotation = h * w; + if (rotation * rotation > Settings.maxRotationSquared) { + var ratio = Settings.maxRotation / math.abs(rotation); + w *= ratio; + } + // Integrate + c.addMul(h, v); + a += h * w; + body.c_position.c = c; + body.c_position.a = a; + body.c_velocity.v = v; + body.c_velocity.w = w; + // Sync bodies + body.m_sweep.c = c; + body.m_sweep.a = a; + body.m_linearVelocity = v; + body.m_angularVelocity = w; + body.synchronizeTransform(); + } + this.postSolveIsland(); + }; + /** @internal */ + Solver.prototype.postSolveIsland = function () { + for (var c = 0; c < this.m_contacts.length; ++c) { + var contact = this.m_contacts[c]; + this.m_world.postSolve(contact, contact.m_impulse); + } + }; + return Solver; +}()); +// @ts-ignore +Solver.TimeStep = TimeStep; + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * A 2-by-2 matrix. Stored in column-major order. + */ +var Mat22 = /** @class */ (function () { + // tslint:disable-next-line:typedef + function Mat22(a, b, c, d) { + if (typeof a === 'object' && a !== null) { + this.ex = Vec2.clone(a); + this.ey = Vec2.clone(b); + } + else if (typeof a === 'number') { + this.ex = Vec2.neo(a, c); + this.ey = Vec2.neo(b, d); + } + else { + this.ex = Vec2.zero(); + this.ey = Vec2.zero(); + } + } + /** @internal */ + Mat22.prototype.toString = function () { + return JSON.stringify(this); + }; + Mat22.isValid = function (obj) { + if (obj === null || typeof obj === 'undefined') { + return false; + } + return Vec2.isValid(obj.ex) && Vec2.isValid(obj.ey); + }; + Mat22.assert = function (o) { + }; + // tslint:disable-next-line:typedef + Mat22.prototype.set = function (a, b, c, d) { + if (typeof a === 'number' && typeof b === 'number' && typeof c === 'number' + && typeof d === 'number') { + this.ex.setNum(a, c); + this.ey.setNum(b, d); + } + else if (typeof a === 'object' && typeof b === 'object') { + this.ex.setVec2(a); + this.ey.setVec2(b); + } + else if (typeof a === 'object') { + this.ex.setVec2(a.ex); + this.ey.setVec2(a.ey); + } + else ; + }; + Mat22.prototype.setIdentity = function () { + this.ex.x = 1.0; + this.ey.x = 0.0; + this.ex.y = 0.0; + this.ey.y = 1.0; + }; + Mat22.prototype.setZero = function () { + this.ex.x = 0.0; + this.ey.x = 0.0; + this.ex.y = 0.0; + this.ey.y = 0.0; + }; + Mat22.prototype.getInverse = function () { + var a = this.ex.x; + var b = this.ey.x; + var c = this.ex.y; + var d = this.ey.y; + var det = a * d - b * c; + if (det !== 0.0) { + det = 1.0 / det; + } + var imx = new Mat22(); + imx.ex.x = det * d; + imx.ey.x = -det * b; + imx.ex.y = -det * c; + imx.ey.y = det * a; + return imx; + }; + /** + * Solve A * x = b, where b is a column vector. This is more efficient than + * computing the inverse in one-shot cases. + */ + Mat22.prototype.solve = function (v) { + var a = this.ex.x; + var b = this.ey.x; + var c = this.ex.y; + var d = this.ey.y; + var det = a * d - b * c; + if (det !== 0.0) { + det = 1.0 / det; + } + var w = Vec2.zero(); + w.x = det * (d * v.x - b * v.y); + w.y = det * (a * v.y - c * v.x); + return w; + }; + // tslint:disable-next-line:typedef + Mat22.mul = function (mx, v) { + if (v && 'x' in v && 'y' in v) { + var x = mx.ex.x * v.x + mx.ey.x * v.y; + var y = mx.ex.y * v.x + mx.ey.y * v.y; + return Vec2.neo(x, y); + } + else if (v && 'ex' in v && 'ey' in v) { // Mat22 + // return new Mat22(Vec2.mul(mx, v.ex), Vec2.mul(mx, v.ey)); + var a = mx.ex.x * v.ex.x + mx.ey.x * v.ex.y; + var b = mx.ex.x * v.ey.x + mx.ey.x * v.ey.y; + var c = mx.ex.y * v.ex.x + mx.ey.y * v.ex.y; + var d = mx.ex.y * v.ey.x + mx.ey.y * v.ey.y; + return new Mat22(a, b, c, d); + } + }; + Mat22.mulVec2 = function (mx, v) { + var x = mx.ex.x * v.x + mx.ey.x * v.y; + var y = mx.ex.y * v.x + mx.ey.y * v.y; + return Vec2.neo(x, y); + }; + Mat22.mulMat22 = function (mx, v) { + // return new Mat22(Vec2.mul(mx, v.ex), Vec2.mul(mx, v.ey)); + var a = mx.ex.x * v.ex.x + mx.ey.x * v.ex.y; + var b = mx.ex.x * v.ey.x + mx.ey.x * v.ey.y; + var c = mx.ex.y * v.ex.x + mx.ey.y * v.ex.y; + var d = mx.ex.y * v.ey.x + mx.ey.y * v.ey.y; + return new Mat22(a, b, c, d); + }; + // tslint:disable-next-line:typedef + Mat22.mulT = function (mx, v) { + if (v && 'x' in v && 'y' in v) { // Vec2 + return Vec2.neo(Vec2.dot(v, mx.ex), Vec2.dot(v, mx.ey)); + } + else if (v && 'ex' in v && 'ey' in v) { // Mat22 + var c1 = Vec2.neo(Vec2.dot(mx.ex, v.ex), Vec2.dot(mx.ey, v.ex)); + var c2 = Vec2.neo(Vec2.dot(mx.ex, v.ey), Vec2.dot(mx.ey, v.ey)); + return new Mat22(c1, c2); + } + }; + Mat22.mulTVec2 = function (mx, v) { + return Vec2.neo(Vec2.dot(v, mx.ex), Vec2.dot(v, mx.ey)); + }; + Mat22.mulTMat22 = function (mx, v) { + var c1 = Vec2.neo(Vec2.dot(mx.ex, v.ex), Vec2.dot(mx.ey, v.ex)); + var c2 = Vec2.neo(Vec2.dot(mx.ex, v.ey), Vec2.dot(mx.ey, v.ey)); + return new Mat22(c1, c2); + }; + Mat22.abs = function (mx) { + return new Mat22(Vec2.abs(mx.ex), Vec2.abs(mx.ey)); + }; + Mat22.add = function (mx1, mx2) { + return new Mat22(Vec2.add(mx1.ex, mx2.ex), Vec2.add(mx1.ey, mx2.ey)); + }; + return Mat22; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +exports.ManifoldType = void 0; +(function (ManifoldType) { + ManifoldType[ManifoldType["e_circles"] = 0] = "e_circles"; + ManifoldType[ManifoldType["e_faceA"] = 1] = "e_faceA"; + ManifoldType[ManifoldType["e_faceB"] = 2] = "e_faceB"; +})(exports.ManifoldType || (exports.ManifoldType = {})); +exports.ContactFeatureType = void 0; +(function (ContactFeatureType) { + ContactFeatureType[ContactFeatureType["e_vertex"] = 0] = "e_vertex"; + ContactFeatureType[ContactFeatureType["e_face"] = 1] = "e_face"; +})(exports.ContactFeatureType || (exports.ContactFeatureType = {})); +/** + * This is used for determining the state of contact points. + */ +exports.PointState = void 0; +(function (PointState) { + /** Point does not exist */ + PointState[PointState["nullState"] = 0] = "nullState"; + /** Point was added in the update */ + PointState[PointState["addState"] = 1] = "addState"; + /** Point persisted across the update */ + PointState[PointState["persistState"] = 2] = "persistState"; + /** Point was removed in the update */ + PointState[PointState["removeState"] = 3] = "removeState"; +})(exports.PointState || (exports.PointState = {})); +/** + * Used for computing contact manifolds. + */ +var ClipVertex = /** @class */ (function () { + function ClipVertex() { + this.v = Vec2.zero(); + this.id = new ContactID(); + } + ClipVertex.prototype.set = function (o) { + this.v.setVec2(o.v); + this.id.set(o.id); + }; + return ClipVertex; +}()); +/** + * A manifold for two touching convex shapes. Manifolds are created in `evaluate` + * method of Contact subclasses. + * + * Supported manifold types are e_faceA or e_faceB for clip point versus plane + * with radius and e_circles point versus point with radius. + * + * We store contacts in this way so that position correction can account for + * movement, which is critical for continuous physics. All contact scenarios + * must be expressed in one of these types. This structure is stored across time + * steps, so we keep it small. + * + * @prop type e_circle, e_faceA, e_faceB + * @prop localPoint Usage depends on manifold type:
\n * Apply damping.\n * ODE: dv/dt + c * v = 0\n * Solution: v(t) = v0 * exp(-c * t)\n * Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt)\n * v2 = exp(-c * dt) * v1\n * Pade approximation:\n * v2 = v1 * 1 / (1 + c * dt)\n *\n */\n v.mul(1.0 / (1.0 + h * body.m_linearDamping));\n w *= 1.0 / (1.0 + h * body.m_angularDamping);\n }\n\n body.c_position.c = c;\n body.c_position.a = a;\n body.c_velocity.v = v;\n body.c_velocity.w = w;\n }\n\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.initConstraint(step);\n }\n\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.initVelocityConstraint(step);\n }\n\n if (step.warmStarting) {\n // Warm start.\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.warmStartConstraint(step);\n }\n }\n\n for (let i = 0; i < this.m_joints.length; ++i) {\n const joint = this.m_joints[i];\n joint.initVelocityConstraints(step);\n }\n\n // Solve velocity constraints\n for (let i = 0; i < step.velocityIterations; ++i) {\n for (let j = 0; j < this.m_joints.length; ++j) {\n const joint = this.m_joints[j];\n joint.solveVelocityConstraints(step);\n }\n\n for (let j = 0; j < this.m_contacts.length; ++j) {\n const contact = this.m_contacts[j];\n contact.solveVelocityConstraint(step);\n }\n }\n\n // Store impulses for warm starting\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.storeConstraintImpulses(step);\n }\n\n // Integrate positions\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n\n const c = Vec2.clone(body.c_position.c);\n let a = body.c_position.a;\n const v = Vec2.clone(body.c_velocity.v);\n let w = body.c_velocity.w;\n\n // Check for large velocities\n const translation = Vec2.mulNumVec2(h, v);\n if (Vec2.lengthSquared(translation) > Settings.maxTranslationSquared) {\n const ratio = Settings.maxTranslation / translation.length();\n v.mul(ratio);\n }\n\n const rotation = h * w;\n if (rotation * rotation > Settings.maxRotationSquared) {\n const ratio = Settings.maxRotation / Math.abs(rotation);\n w *= ratio;\n }\n\n // Integrate\n c.addMul(h, v);\n a += h * w;\n\n body.c_position.c.setVec2(c);\n body.c_position.a = a;\n body.c_velocity.v.setVec2(v);\n body.c_velocity.w = w;\n }\n\n // Solve position constraints\n let positionSolved = false;\n for (let i = 0; i < step.positionIterations; ++i) {\n let minSeparation = 0.0;\n for (let j = 0; j < this.m_contacts.length; ++j) {\n const contact = this.m_contacts[j];\n const separation = contact.solvePositionConstraint(step);\n minSeparation = Math.min(minSeparation, separation);\n }\n // We can't expect minSpeparation >= -Settings.linearSlop because we don't\n // push the separation above -Settings.linearSlop.\n const contactsOkay = minSeparation >= -3.0 * Settings.linearSlop;\n\n let jointsOkay = true;\n for (let j = 0; j < this.m_joints.length; ++j) {\n const joint = this.m_joints[j];\n const jointOkay = joint.solvePositionConstraints(step);\n jointsOkay = jointsOkay && jointOkay;\n }\n\n if (contactsOkay && jointsOkay) {\n // Exit early if the position errors are small.\n positionSolved = true;\n break;\n }\n }\n\n // Copy state buffers back to the bodies\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n\n body.m_sweep.c.setVec2(body.c_position.c);\n body.m_sweep.a = body.c_position.a;\n body.m_linearVelocity.setVec2(body.c_velocity.v);\n body.m_angularVelocity = body.c_velocity.w;\n body.synchronizeTransform();\n }\n\n this.postSolveIsland();\n\n if (allowSleep) {\n let minSleepTime = Infinity;\n\n const linTolSqr = Settings.linearSleepToleranceSqr;\n const angTolSqr = Settings.angularSleepToleranceSqr;\n\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n if (body.isStatic()) {\n continue;\n }\n\n if ((body.m_autoSleepFlag == false)\n || (body.m_angularVelocity * body.m_angularVelocity > angTolSqr)\n || (Vec2.lengthSquared(body.m_linearVelocity) > linTolSqr)) {\n body.m_sleepTime = 0.0;\n minSleepTime = 0.0;\n } else {\n body.m_sleepTime += h;\n minSleepTime = Math.min(minSleepTime, body.m_sleepTime);\n }\n }\n\n if (minSleepTime >= Settings.timeToSleep && positionSolved) {\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n body.setAwake(false);\n }\n }\n }\n }\n\n /**\n * Find TOI contacts and solve them.\n */\n solveWorldTOI(step: TimeStep): void {\n const world = this.m_world;\n\n if (world.m_stepComplete) {\n for (let b = world.m_bodyList; b; b = b.m_next) {\n b.m_islandFlag = false;\n b.m_sweep.alpha0 = 0.0;\n }\n\n for (let c = world.m_contactList; c; c = c.m_next) {\n // Invalidate TOI\n c.m_toiFlag = false;\n c.m_islandFlag = false;\n c.m_toiCount = 0;\n c.m_toi = 1.0;\n }\n }\n\n // Find TOI events and solve them.\n while (true) {\n // Find the first TOI.\n let minContact = null; // Contact\n let minAlpha = 1.0;\n\n for (let c = world.m_contactList; c; c = c.m_next) {\n // Is this contact disabled?\n if (c.isEnabled() == false) {\n continue;\n }\n\n // Prevent excessive sub-stepping.\n if (c.m_toiCount > Settings.maxSubSteps) {\n continue;\n }\n\n let alpha = 1.0;\n if (c.m_toiFlag) {\n // This contact has a valid cached TOI.\n alpha = c.m_toi;\n } else {\n const fA = c.getFixtureA();\n const fB = c.getFixtureB();\n\n // Is there a sensor?\n if (fA.isSensor() || fB.isSensor()) {\n continue;\n }\n\n const bA = fA.getBody();\n const bB = fB.getBody();\n\n _ASSERT && console.assert(bA.isDynamic() || bB.isDynamic());\n\n const activeA = bA.isAwake() && !bA.isStatic();\n const activeB = bB.isAwake() && !bB.isStatic();\n\n // Is at least one body active (awake and dynamic or kinematic)?\n if (activeA == false && activeB == false) {\n continue;\n }\n\n const collideA = bA.isBullet() || !bA.isDynamic();\n const collideB = bB.isBullet() || !bB.isDynamic();\n\n // Are these two non-bullet dynamic bodies?\n if (collideA == false && collideB == false) {\n continue;\n }\n\n // Compute the TOI for this contact.\n // Put the sweeps onto the same time interval.\n let alpha0 = bA.m_sweep.alpha0;\n\n if (bA.m_sweep.alpha0 < bB.m_sweep.alpha0) {\n alpha0 = bB.m_sweep.alpha0;\n bA.m_sweep.advance(alpha0);\n } else if (bB.m_sweep.alpha0 < bA.m_sweep.alpha0) {\n alpha0 = bA.m_sweep.alpha0;\n bB.m_sweep.advance(alpha0);\n }\n\n _ASSERT && console.assert(alpha0 < 1.0);\n\n const indexA = c.getChildIndexA();\n const indexB = c.getChildIndexB();\n\n const sweepA = bA.m_sweep;\n const sweepB = bB.m_sweep;\n\n // Compute the time of impact in interval [0, minTOI]\n const input = new TOIInput(); // TODO: reuse\n input.proxyA.set(fA.getShape(), indexA);\n input.proxyB.set(fB.getShape(), indexB);\n input.sweepA.set(bA.m_sweep);\n input.sweepB.set(bB.m_sweep);\n input.tMax = 1.0;\n\n const output = new TOIOutput(); // TODO: reuse\n TimeOfImpact(output, input);\n\n // Beta is the fraction of the remaining portion of the [time?].\n const beta = output.t;\n if (output.state == TOIOutputState.e_touching) {\n alpha = Math.min(alpha0 + (1.0 - alpha0) * beta, 1.0);\n } else {\n alpha = 1.0;\n }\n\n c.m_toi = alpha;\n c.m_toiFlag = true;\n }\n\n if (alpha < minAlpha) {\n // This is the minimum TOI found so far.\n minContact = c;\n minAlpha = alpha;\n }\n }\n\n if (minContact == null || 1.0 - 10.0 * Math.EPSILON < minAlpha) {\n // No more TOI events. Done!\n world.m_stepComplete = true;\n break;\n }\n\n // Advance the bodies to the TOI.\n const fA = minContact.getFixtureA();\n const fB = minContact.getFixtureB();\n const bA = fA.getBody();\n const bB = fB.getBody();\n\n const backup1 = bA.m_sweep.clone();\n const backup2 = bB.m_sweep.clone();\n\n bA.advance(minAlpha);\n bB.advance(minAlpha);\n\n // The TOI contact likely has some new contact points.\n minContact.update(world);\n minContact.m_toiFlag = false;\n ++minContact.m_toiCount;\n\n // Is the contact solid?\n if (minContact.isEnabled() == false || minContact.isTouching() == false) {\n // Restore the sweeps.\n minContact.setEnabled(false);\n bA.m_sweep.set(backup1);\n bB.m_sweep.set(backup2);\n bA.synchronizeTransform();\n bB.synchronizeTransform();\n continue;\n }\n\n bA.setAwake(true);\n bB.setAwake(true);\n\n // Build the island\n this.clear();\n this.addBody(bA);\n this.addBody(bB);\n this.addContact(minContact);\n\n bA.m_islandFlag = true;\n bB.m_islandFlag = true;\n minContact.m_islandFlag = true;\n\n // Get contacts on bodyA and bodyB.\n const bodies = [ bA, bB ];\n for (let i = 0; i < bodies.length; ++i) {\n const body = bodies[i];\n if (body.isDynamic()) {\n for (let ce = body.m_contactList; ce; ce = ce.next) {\n // if (this.m_bodyCount == this.m_bodyCapacity) { break; }\n // if (this.m_contactCount == this.m_contactCapacity) { break; }\n\n const contact = ce.contact;\n\n // Has this contact already been added to the island?\n if (contact.m_islandFlag) {\n continue;\n }\n\n // Only add if either is static, kinematic or bullet.\n const other = ce.other;\n if (other.isDynamic() && !body.isBullet() && !other.isBullet()) {\n continue;\n }\n\n // Skip sensors.\n const sensorA = contact.m_fixtureA.m_isSensor;\n const sensorB = contact.m_fixtureB.m_isSensor;\n if (sensorA || sensorB) {\n continue;\n }\n\n // Tentatively advance the body to the TOI.\n const backup = other.m_sweep.clone();\n if (other.m_islandFlag == false) {\n other.advance(minAlpha);\n }\n\n // Update the contact points\n contact.update(world);\n\n // Was the contact disabled by the user?\n // Are there contact points?\n if (contact.isEnabled() == false || contact.isTouching() == false) {\n other.m_sweep.set(backup);\n other.synchronizeTransform();\n continue;\n }\n\n // Add the contact to the island\n contact.m_islandFlag = true;\n this.addContact(contact);\n\n // Has the other body already been added to the island?\n if (other.m_islandFlag) {\n continue;\n }\n\n // Add the other body to the island.\n other.m_islandFlag = true;\n\n if (!other.isStatic()) {\n other.setAwake(true);\n }\n\n this.addBody(other);\n }\n }\n }\n\n s_subStep.reset((1.0 - minAlpha) * step.dt);\n s_subStep.dtRatio = 1.0;\n s_subStep.positionIterations = 20;\n s_subStep.velocityIterations = step.velocityIterations;\n s_subStep.warmStarting = false;\n\n this.solveIslandTOI(s_subStep, bA, bB);\n\n // Reset island flags and synchronize broad-phase proxies.\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n body.m_islandFlag = false;\n\n if (!body.isDynamic()) {\n continue;\n }\n\n body.synchronizeFixtures();\n\n // Invalidate all contact TOIs on this displaced body.\n for (let ce = body.m_contactList; ce; ce = ce.next) {\n ce.contact.m_toiFlag = false;\n ce.contact.m_islandFlag = false;\n }\n }\n\n // Commit fixture proxy movements to the broad-phase so that new contacts\n // are created.\n // Also, some contacts can be destroyed.\n world.findNewContacts();\n\n if (world.m_subStepping) {\n world.m_stepComplete = false;\n break;\n }\n }\n }\n\n solveIslandTOI(subStep: TimeStep, toiA: Body, toiB: Body): void {\n const world = this.m_world;\n\n // Initialize the body state.\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n body.c_position.c.setVec2(body.m_sweep.c);\n body.c_position.a = body.m_sweep.a;\n body.c_velocity.v.setVec2(body.m_linearVelocity);\n body.c_velocity.w = body.m_angularVelocity;\n }\n\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.initConstraint(subStep);\n }\n\n // Solve position constraints.\n for (let i = 0; i < subStep.positionIterations; ++i) {\n let minSeparation = 0.0;\n for (let j = 0; j < this.m_contacts.length; ++j) {\n const contact = this.m_contacts[j];\n const separation = contact.solvePositionConstraintTOI(subStep, toiA, toiB);\n minSeparation = Math.min(minSeparation, separation);\n }\n // We can't expect minSpeparation >= -Settings.linearSlop because we don't\n // push the separation above -Settings.linearSlop.\n const contactsOkay = minSeparation >= -1.5 * Settings.linearSlop;\n if (contactsOkay) {\n break;\n }\n }\n\n if (false) {\n // Is the new position really safe?\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const c = this.m_contacts[i];\n const fA = c.getFixtureA();\n const fB = c.getFixtureB();\n\n const bA = fA.getBody();\n const bB = fB.getBody();\n\n const indexA = c.getChildIndexA();\n const indexB = c.getChildIndexB();\n\n const input = new DistanceInput();\n input.proxyA.set(fA.getShape(), indexA);\n input.proxyB.set(fB.getShape(), indexB);\n input.transformA = bA.getTransform();\n input.transformB = bB.getTransform();\n input.useRadii = false;\n\n const output = new DistanceOutput();\n const cache = new SimplexCache();\n Distance(output, cache, input);\n\n if (output.distance == 0 || cache.count == 3) {\n cache.count += 0;\n }\n }\n }\n\n // Leap of faith to new safe state.\n toiA.m_sweep.c0.setVec2(toiA.c_position.c);\n toiA.m_sweep.a0 = toiA.c_position.a;\n toiB.m_sweep.c0.setVec2(toiB.c_position.c);\n toiB.m_sweep.a0 = toiB.c_position.a;\n\n // No warm starting is needed for TOI events because warm\n // starting impulses were applied in the discrete solver.\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.initVelocityConstraint(subStep);\n }\n\n // Solve velocity constraints.\n for (let i = 0; i < subStep.velocityIterations; ++i) {\n for (let j = 0; j < this.m_contacts.length; ++j) {\n const contact = this.m_contacts[j];\n contact.solveVelocityConstraint(subStep);\n }\n }\n\n // Don't store the TOI contact forces for warm starting\n // because they can be quite large.\n\n const h = subStep.dt;\n\n // Integrate positions\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n\n const c = Vec2.clone(body.c_position.c);\n let a = body.c_position.a;\n const v = Vec2.clone(body.c_velocity.v);\n let w = body.c_velocity.w;\n\n // Check for large velocities\n const translation = Vec2.mulNumVec2(h, v);\n if (Vec2.dot(translation, translation) > Settings.maxTranslationSquared) {\n const ratio = Settings.maxTranslation / translation.length();\n v.mul(ratio);\n }\n\n const rotation = h * w;\n if (rotation * rotation > Settings.maxRotationSquared) {\n const ratio = Settings.maxRotation / Math.abs(rotation);\n w *= ratio;\n }\n\n // Integrate\n c.addMul(h, v);\n a += h * w;\n\n body.c_position.c = c;\n body.c_position.a = a;\n body.c_velocity.v = v;\n body.c_velocity.w = w;\n\n // Sync bodies\n body.m_sweep.c = c;\n body.m_sweep.a = a;\n body.m_linearVelocity = v;\n body.m_angularVelocity = w;\n body.synchronizeTransform();\n }\n\n this.postSolveIsland();\n }\n\n /** @internal */\n postSolveIsland(): void {\n for (let c = 0; c < this.m_contacts.length; ++c) {\n const contact = this.m_contacts[c];\n this.m_world.postSolve(contact, contact.m_impulse);\n }\n }\n}\n\n// @ts-ignore\nSolver.TimeStep = TimeStep;\n","/*\n * Planck.js\n * The MIT License\n * Copyright (c) 2021 Erin Catto, Ali Shakiba\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { Vec2 } from './Vec2';\n\n\nconst _ASSERT = typeof ASSERT === 'undefined' ? false : ASSERT;\n\n\n/**\n * A 2-by-2 matrix. Stored in column-major order.\n */\nexport class Mat22 {\n ex: Vec2;\n ey: Vec2;\n\n constructor(a: number, b: number, c: number, d: number);\n constructor(a: { x: number; y: number }, b: { x: number; y: number });\n constructor();\n // tslint:disable-next-line:typedef\n constructor(a?, b?, c?, d?) {\n if (typeof a === 'object' && a !== null) {\n this.ex = Vec2.clone(a);\n this.ey = Vec2.clone(b);\n } else if (typeof a === 'number') {\n this.ex = Vec2.neo(a, c);\n this.ey = Vec2.neo(b, d);\n } else {\n this.ex = Vec2.zero();\n this.ey = Vec2.zero();\n }\n }\n\n /** @internal */\n toString(): string {\n return JSON.stringify(this);\n }\n\n static isValid(obj: any): boolean {\n if (obj === null || typeof obj === 'undefined') {\n return false;\n }\n return Vec2.isValid(obj.ex) && Vec2.isValid(obj.ey);\n }\n\n static assert(o: any): void {\n _ASSERT && console.assert(!Mat22.isValid(o), 'Invalid Mat22!', o);\n }\n\n set(a: Mat22): void;\n set(a: Vec2, b: Vec2): void;\n set(a: number, b: number, c: number, d: number): void;\n // tslint:disable-next-line:typedef\n set(a, b?, c?, d?): void {\n if (typeof a === 'number' && typeof b === 'number' && typeof c === 'number'\n && typeof d === 'number') {\n this.ex.setNum(a, c);\n this.ey.setNum(b, d);\n\n } else if (typeof a === 'object' && typeof b === 'object') {\n this.ex.setVec2(a);\n this.ey.setVec2(b);\n\n } else if (typeof a === 'object') {\n _ASSERT && Mat22.assert(a);\n this.ex.setVec2(a.ex);\n this.ey.setVec2(a.ey);\n\n } else {\n _ASSERT && console.assert(false);\n }\n }\n\n setIdentity(): void {\n this.ex.x = 1.0;\n this.ey.x = 0.0;\n this.ex.y = 0.0;\n this.ey.y = 1.0;\n }\n\n setZero(): void {\n this.ex.x = 0.0;\n this.ey.x = 0.0;\n this.ex.y = 0.0;\n this.ey.y = 0.0;\n }\n\n getInverse(): Mat22 {\n const a = this.ex.x;\n const b = this.ey.x;\n const c = this.ex.y;\n const d = this.ey.y;\n let det = a * d - b * c;\n if (det !== 0.0) {\n det = 1.0 / det;\n }\n const imx = new Mat22();\n imx.ex.x = det * d;\n imx.ey.x = -det * b;\n imx.ex.y = -det * c;\n imx.ey.y = det * a;\n return imx;\n }\n\n /**\n * Solve A * x = b, where b is a column vector. This is more efficient than\n * computing the inverse in one-shot cases.\n */\n solve(v: Vec2): Vec2 {\n _ASSERT && Vec2.assert(v);\n const a = this.ex.x;\n const b = this.ey.x;\n const c = this.ex.y;\n const d = this.ey.y;\n let det = a * d - b * c;\n if (det !== 0.0) {\n det = 1.0 / det;\n }\n const w = Vec2.zero();\n w.x = det * (d * v.x - b * v.y);\n w.y = det * (a * v.y - c * v.x);\n return w;\n }\n\n /**\n * Multiply a matrix times a vector. If a rotation matrix is provided, then this\n * transforms the vector from one frame to another.\n */\n static mul(mx: Mat22, my: Mat22): Mat22;\n static mul(mx: Mat22, v: Vec2): Vec2;\n // tslint:disable-next-line:typedef\n static mul(mx, v) {\n if (v && 'x' in v && 'y' in v) {\n _ASSERT && Vec2.assert(v);\n const x = mx.ex.x * v.x + mx.ey.x * v.y;\n const y = mx.ex.y * v.x + mx.ey.y * v.y;\n return Vec2.neo(x, y);\n\n } else if (v && 'ex' in v && 'ey' in v) { // Mat22\n _ASSERT && Mat22.assert(v);\n // return new Mat22(Vec2.mul(mx, v.ex), Vec2.mul(mx, v.ey));\n const a = mx.ex.x * v.ex.x + mx.ey.x * v.ex.y;\n const b = mx.ex.x * v.ey.x + mx.ey.x * v.ey.y;\n const c = mx.ex.y * v.ex.x + mx.ey.y * v.ex.y;\n const d = mx.ex.y * v.ey.x + mx.ey.y * v.ey.y;\n return new Mat22(a, b, c, d);\n }\n\n _ASSERT && console.assert(false);\n }\n\n static mulVec2(mx: Mat22, v: Vec2): Vec2 {\n _ASSERT && Vec2.assert(v);\n const x = mx.ex.x * v.x + mx.ey.x * v.y;\n const y = mx.ex.y * v.x + mx.ey.y * v.y;\n return Vec2.neo(x, y);\n }\n\n static mulMat22(mx: Mat22, v: Mat22): Mat22 {\n _ASSERT && Mat22.assert(v);\n // return new Mat22(Vec2.mul(mx, v.ex), Vec2.mul(mx, v.ey));\n const a = mx.ex.x * v.ex.x + mx.ey.x * v.ex.y;\n const b = mx.ex.x * v.ey.x + mx.ey.x * v.ey.y;\n const c = mx.ex.y * v.ex.x + mx.ey.y * v.ex.y;\n const d = mx.ex.y * v.ey.x + mx.ey.y * v.ey.y;\n return new Mat22(a, b, c, d);\n }\n\n /**\n * Multiply a matrix transpose times a vector. If a rotation matrix is provided,\n * then this transforms the vector from one frame to another (inverse\n * transform).\n */\n static mulT(mx: Mat22, my: Mat22): Mat22;\n static mulT(mx: Mat22, v: Vec2): Vec2;\n // tslint:disable-next-line:typedef\n static mulT(mx, v) {\n if (v && 'x' in v && 'y' in v) { // Vec2\n _ASSERT && Vec2.assert(v);\n return Vec2.neo(Vec2.dot(v, mx.ex), Vec2.dot(v, mx.ey));\n\n } else if (v && 'ex' in v && 'ey' in v) { // Mat22\n _ASSERT && Mat22.assert(v);\n const c1 = Vec2.neo(Vec2.dot(mx.ex, v.ex), Vec2.dot(mx.ey, v.ex));\n const c2 = Vec2.neo(Vec2.dot(mx.ex, v.ey), Vec2.dot(mx.ey, v.ey));\n return new Mat22(c1, c2);\n }\n\n _ASSERT && console.assert(false);\n }\n\n static mulTVec2(mx: Mat22, v: Vec2): Vec2 {\n _ASSERT && Mat22.assert(mx);\n _ASSERT && Vec2.assert(v);\n return Vec2.neo(Vec2.dot(v, mx.ex), Vec2.dot(v, mx.ey));\n }\n\n static mulTMat22(mx: Mat22, v: Mat22): Mat22 {\n _ASSERT && Mat22.assert(mx);\n _ASSERT && Mat22.assert(v);\n const c1 = Vec2.neo(Vec2.dot(mx.ex, v.ex), Vec2.dot(mx.ey, v.ex));\n const c2 = Vec2.neo(Vec2.dot(mx.ex, v.ey), Vec2.dot(mx.ey, v.ey));\n return new Mat22(c1, c2);\n }\n\n static abs(mx: Mat22): Mat22 {\n _ASSERT && Mat22.assert(mx);\n return new Mat22(Vec2.abs(mx.ex), Vec2.abs(mx.ey));\n }\n\n static add(mx1: Mat22, mx2: Mat22): Mat22 {\n _ASSERT && Mat22.assert(mx1);\n _ASSERT && Mat22.assert(mx2);\n return new Mat22(Vec2.add(mx1.ex, mx2.ex), Vec2.add(mx1.ey, mx2.ey));\n }\n}\n","/*\n * Planck.js\n * The MIT License\n * Copyright (c) 2021 Erin Catto, Ali Shakiba\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { Vec2 } from '../common/Vec2';\nimport { Transform } from '../common/Transform';\nimport { math as Math } from '../common/Math';\nimport { Rot } from '../common/Rot';\n\nexport enum ManifoldType {\n e_circles = 0,\n e_faceA = 1,\n e_faceB = 2\n}\n\nexport enum ContactFeatureType {\n e_vertex = 0,\n e_face = 1\n}\n\n/**\n * This is used for determining the state of contact points.\n */\n export enum PointState {\n /** Point does not exist */\n nullState = 0,\n /** Point was added in the update */\n addState = 1,\n /** Point persisted across the update */\n persistState = 2,\n /** Point was removed in the update */\n removeState = 3\n}\n\n/**\n * Used for computing contact manifolds.\n */\n export class ClipVertex {\n v: Vec2 = Vec2.zero();\n id: ContactID = new ContactID();\n\n set(o: ClipVertex): void {\n this.v.setVec2(o.v);\n this.id.set(o.id);\n }\n}\n\n/**\n * A manifold for two touching convex shapes. Manifolds are created in `evaluate`\n * method of Contact subclasses.\n *\n * Supported manifold types are e_faceA or e_faceB for clip point versus plane\n * with radius and e_circles point versus point with radius.\n *\n * We store contacts in this way so that position correction can account for\n * movement, which is critical for continuous physics. All contact scenarios\n * must be expressed in one of these types. This structure is stored across time\n * steps, so we keep it small.\n *\n * @prop type e_circle, e_faceA, e_faceB\n * @prop localPoint Usage depends on manifold type:
- * Apply damping. - * ODE: dv/dt + c * v = 0 - * Solution: v(t) = v0 * exp(-c * t) - * Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt) - * v2 = exp(-c * dt) * v1 - * Pade approximation: - * v2 = v1 * 1 / (1 + c * dt) - *- */ - v.mul(1.0 / (1.0 + h * body.m_linearDamping)); - w *= 1.0 / (1.0 + h * body.m_angularDamping); - } - body.c_position.c = c; - body.c_position.a = a; - body.c_velocity.v = v; - body.c_velocity.w = w; - } - for (var i = 0; i < this.m_contacts.length; ++i) { - var contact = this.m_contacts[i]; - contact.initConstraint(step); - } - for (var i = 0; i < this.m_contacts.length; ++i) { - var contact = this.m_contacts[i]; - contact.initVelocityConstraint(step); - } - if (step.warmStarting) { - // Warm start. - for (var i = 0; i < this.m_contacts.length; ++i) { - var contact = this.m_contacts[i]; - contact.warmStartConstraint(step); - } - } - for (var i = 0; i < this.m_joints.length; ++i) { - var joint = this.m_joints[i]; - joint.initVelocityConstraints(step); - } - // Solve velocity constraints - for (var i = 0; i < step.velocityIterations; ++i) { - for (var j = 0; j < this.m_joints.length; ++j) { - var joint = this.m_joints[j]; - joint.solveVelocityConstraints(step); - } - for (var j = 0; j < this.m_contacts.length; ++j) { - var contact = this.m_contacts[j]; - contact.solveVelocityConstraint(step); - } - } - // Store impulses for warm starting - for (var i = 0; i < this.m_contacts.length; ++i) { - var contact = this.m_contacts[i]; - contact.storeConstraintImpulses(step); - } - // Integrate positions - for (var i = 0; i < this.m_bodies.length; ++i) { - var body = this.m_bodies[i]; - var c = Vec2.clone(body.c_position.c); - var a = body.c_position.a; - var v = Vec2.clone(body.c_velocity.v); - var w = body.c_velocity.w; - // Check for large velocities - var translation = Vec2.mulNumVec2(h, v); - if (Vec2.lengthSquared(translation) > Settings.maxTranslationSquared) { - var ratio = Settings.maxTranslation / translation.length(); - v.mul(ratio); - } - var rotation = h * w; - if (rotation * rotation > Settings.maxRotationSquared) { - var ratio = Settings.maxRotation / math$1.abs(rotation); - w *= ratio; - } - // Integrate - c.addMul(h, v); - a += h * w; - body.c_position.c.setVec2(c); - body.c_position.a = a; - body.c_velocity.v.setVec2(v); - body.c_velocity.w = w; - } - // Solve position constraints - var positionSolved = false; - for (var i = 0; i < step.positionIterations; ++i) { - var minSeparation = 0.0; - for (var j = 0; j < this.m_contacts.length; ++j) { - var contact = this.m_contacts[j]; - var separation = contact.solvePositionConstraint(step); - minSeparation = math$1.min(minSeparation, separation); - } - // We can't expect minSpeparation >= -Settings.linearSlop because we don't - // push the separation above -Settings.linearSlop. - var contactsOkay = minSeparation >= -3.0 * Settings.linearSlop; - var jointsOkay = true; - for (var j = 0; j < this.m_joints.length; ++j) { - var joint = this.m_joints[j]; - var jointOkay = joint.solvePositionConstraints(step); - jointsOkay = jointsOkay && jointOkay; - } - if (contactsOkay && jointsOkay) { - // Exit early if the position errors are small. - positionSolved = true; - break; - } - } - // Copy state buffers back to the bodies - for (var i = 0; i < this.m_bodies.length; ++i) { - var body = this.m_bodies[i]; - body.m_sweep.c.setVec2(body.c_position.c); - body.m_sweep.a = body.c_position.a; - body.m_linearVelocity.setVec2(body.c_velocity.v); - body.m_angularVelocity = body.c_velocity.w; - body.synchronizeTransform(); - } - this.postSolveIsland(); - if (allowSleep) { - var minSleepTime = Infinity; - var linTolSqr = Settings.linearSleepToleranceSqr; - var angTolSqr = Settings.angularSleepToleranceSqr; - for (var i = 0; i < this.m_bodies.length; ++i) { - var body = this.m_bodies[i]; - if (body.isStatic()) { - continue; - } - if ((body.m_autoSleepFlag == false) - || (body.m_angularVelocity * body.m_angularVelocity > angTolSqr) - || (Vec2.lengthSquared(body.m_linearVelocity) > linTolSqr)) { - body.m_sleepTime = 0.0; - minSleepTime = 0.0; - } - else { - body.m_sleepTime += h; - minSleepTime = math$1.min(minSleepTime, body.m_sleepTime); - } - } - if (minSleepTime >= Settings.timeToSleep && positionSolved) { - for (var i = 0; i < this.m_bodies.length; ++i) { - var body = this.m_bodies[i]; - body.setAwake(false); - } - } - } - }; - /** @internal */ - Solver.prototype.printBodies = function (tag) { - for (var i = 0; i < this.m_bodies.length; ++i) { - var b = this.m_bodies[i]; - common.debug(tag, b.c_position.a, b.c_position.c.x, b.c_position.c.y, b.c_velocity.w, b.c_velocity.v.x, b.c_velocity.v.y); - } - }; - /** - * Find TOI contacts and solve them. - */ - Solver.prototype.solveWorldTOI = function (step) { - var world = this.m_world; - if (world.m_stepComplete) { - for (var b = world.m_bodyList; b; b = b.m_next) { - b.m_islandFlag = false; - b.m_sweep.alpha0 = 0.0; - } - for (var c = world.m_contactList; c; c = c.m_next) { - // Invalidate TOI - c.m_toiFlag = false; - c.m_islandFlag = false; - c.m_toiCount = 0; - c.m_toi = 1.0; - } - } - // Find TOI events and solve them. - while (true) { - // Find the first TOI. - var minContact = null; // Contact - var minAlpha = 1.0; - for (var c = world.m_contactList; c; c = c.m_next) { - // Is this contact disabled? - if (c.isEnabled() == false) { - continue; - } - // Prevent excessive sub-stepping. - if (c.m_toiCount > Settings.maxSubSteps) { - continue; - } - var alpha = 1.0; - if (c.m_toiFlag) { - // This contact has a valid cached TOI. - alpha = c.m_toi; - } - else { - var fA_1 = c.getFixtureA(); - var fB_1 = c.getFixtureB(); - // Is there a sensor? - if (fA_1.isSensor() || fB_1.isSensor()) { - continue; - } - var bA_1 = fA_1.getBody(); - var bB_1 = fB_1.getBody(); - var activeA = bA_1.isAwake() && !bA_1.isStatic(); - var activeB = bB_1.isAwake() && !bB_1.isStatic(); - // Is at least one body active (awake and dynamic or kinematic)? - if (activeA == false && activeB == false) { - continue; - } - var collideA = bA_1.isBullet() || !bA_1.isDynamic(); - var collideB = bB_1.isBullet() || !bB_1.isDynamic(); - // Are these two non-bullet dynamic bodies? - if (collideA == false && collideB == false) { - continue; - } - // Compute the TOI for this contact. - // Put the sweeps onto the same time interval. - var alpha0 = bA_1.m_sweep.alpha0; - if (bA_1.m_sweep.alpha0 < bB_1.m_sweep.alpha0) { - alpha0 = bB_1.m_sweep.alpha0; - bA_1.m_sweep.advance(alpha0); - } - else if (bB_1.m_sweep.alpha0 < bA_1.m_sweep.alpha0) { - alpha0 = bA_1.m_sweep.alpha0; - bB_1.m_sweep.advance(alpha0); - } - var indexA = c.getChildIndexA(); - var indexB = c.getChildIndexB(); - bA_1.m_sweep; - bB_1.m_sweep; - // Compute the time of impact in interval [0, minTOI] - var input = new TOIInput(); // TODO: reuse - input.proxyA.set(fA_1.getShape(), indexA); - input.proxyB.set(fB_1.getShape(), indexB); - input.sweepA.set(bA_1.m_sweep); - input.sweepB.set(bB_1.m_sweep); - input.tMax = 1.0; - var output = new TOIOutput(); // TODO: reuse - TimeOfImpact(output, input); - // Beta is the fraction of the remaining portion of the [time?]. - var beta = output.t; - if (output.state == TOIOutputState.e_touching) { - alpha = math$1.min(alpha0 + (1.0 - alpha0) * beta, 1.0); - } - else { - alpha = 1.0; - } - c.m_toi = alpha; - c.m_toiFlag = true; - } - if (alpha < minAlpha) { - // This is the minimum TOI found so far. - minContact = c; - minAlpha = alpha; - } - } - if (minContact == null || 1.0 - 10.0 * math$1.EPSILON < minAlpha) { - // No more TOI events. Done! - world.m_stepComplete = true; - break; - } - // Advance the bodies to the TOI. - var fA = minContact.getFixtureA(); - var fB = minContact.getFixtureB(); - var bA = fA.getBody(); - var bB = fB.getBody(); - var backup1 = bA.m_sweep.clone(); - var backup2 = bB.m_sweep.clone(); - bA.advance(minAlpha); - bB.advance(minAlpha); - // The TOI contact likely has some new contact points. - minContact.update(world); - minContact.m_toiFlag = false; - ++minContact.m_toiCount; - // Is the contact solid? - if (minContact.isEnabled() == false || minContact.isTouching() == false) { - // Restore the sweeps. - minContact.setEnabled(false); - bA.m_sweep.set(backup1); - bB.m_sweep.set(backup2); - bA.synchronizeTransform(); - bB.synchronizeTransform(); - continue; - } - bA.setAwake(true); - bB.setAwake(true); - // Build the island - this.clear(); - this.addBody(bA); - this.addBody(bB); - this.addContact(minContact); - bA.m_islandFlag = true; - bB.m_islandFlag = true; - minContact.m_islandFlag = true; - // Get contacts on bodyA and bodyB. - var bodies = [bA, bB]; - for (var i = 0; i < bodies.length; ++i) { - var body = bodies[i]; - if (body.isDynamic()) { - for (var ce = body.m_contactList; ce; ce = ce.next) { - // if (this.m_bodyCount == this.m_bodyCapacity) { break; } - // if (this.m_contactCount == this.m_contactCapacity) { break; } - var contact = ce.contact; - // Has this contact already been added to the island? - if (contact.m_islandFlag) { - continue; - } - // Only add if either is static, kinematic or bullet. - var other = ce.other; - if (other.isDynamic() && !body.isBullet() && !other.isBullet()) { - continue; - } - // Skip sensors. - var sensorA = contact.m_fixtureA.m_isSensor; - var sensorB = contact.m_fixtureB.m_isSensor; - if (sensorA || sensorB) { - continue; - } - // Tentatively advance the body to the TOI. - var backup = other.m_sweep.clone(); - if (other.m_islandFlag == false) { - other.advance(minAlpha); - } - // Update the contact points - contact.update(world); - // Was the contact disabled by the user? - // Are there contact points? - if (contact.isEnabled() == false || contact.isTouching() == false) { - other.m_sweep.set(backup); - other.synchronizeTransform(); - continue; - } - // Add the contact to the island - contact.m_islandFlag = true; - this.addContact(contact); - // Has the other body already been added to the island? - if (other.m_islandFlag) { - continue; - } - // Add the other body to the island. - other.m_islandFlag = true; - if (!other.isStatic()) { - other.setAwake(true); - } - this.addBody(other); - } - } - } - s_subStep.reset((1.0 - minAlpha) * step.dt); - s_subStep.dtRatio = 1.0; - s_subStep.positionIterations = 20; - s_subStep.velocityIterations = step.velocityIterations; - s_subStep.warmStarting = false; - this.solveIslandTOI(s_subStep, bA, bB); - // Reset island flags and synchronize broad-phase proxies. - for (var i = 0; i < this.m_bodies.length; ++i) { - var body = this.m_bodies[i]; - body.m_islandFlag = false; - if (!body.isDynamic()) { - continue; - } - body.synchronizeFixtures(); - // Invalidate all contact TOIs on this displaced body. - for (var ce = body.m_contactList; ce; ce = ce.next) { - ce.contact.m_toiFlag = false; - ce.contact.m_islandFlag = false; - } - } - // Commit fixture proxy movements to the broad-phase so that new contacts - // are created. - // Also, some contacts can be destroyed. - world.findNewContacts(); - if (world.m_subStepping) { - world.m_stepComplete = false; - break; - } - } - var b, c; - }; - Solver.prototype.solveIslandTOI = function (subStep, toiA, toiB) { - this.m_world; - // Initialize the body state. - for (var i = 0; i < this.m_bodies.length; ++i) { - var body = this.m_bodies[i]; - body.c_position.c.setVec2(body.m_sweep.c); - body.c_position.a = body.m_sweep.a; - body.c_velocity.v.setVec2(body.m_linearVelocity); - body.c_velocity.w = body.m_angularVelocity; - } - for (var i = 0; i < this.m_contacts.length; ++i) { - var contact = this.m_contacts[i]; - contact.initConstraint(subStep); - } - // Solve position constraints. - for (var i = 0; i < subStep.positionIterations; ++i) { - var minSeparation = 0.0; - for (var j = 0; j < this.m_contacts.length; ++j) { - var contact = this.m_contacts[j]; - var separation = contact.solvePositionConstraintTOI(subStep, toiA, toiB); - minSeparation = math$1.min(minSeparation, separation); - } - // We can't expect minSpeparation >= -Settings.linearSlop because we don't - // push the separation above -Settings.linearSlop. - var contactsOkay = minSeparation >= -1.5 * Settings.linearSlop; - if (contactsOkay) { - break; - } - } - var i, c; - // Leap of faith to new safe state. - toiA.m_sweep.c0.setVec2(toiA.c_position.c); - toiA.m_sweep.a0 = toiA.c_position.a; - toiB.m_sweep.c0.setVec2(toiB.c_position.c); - toiB.m_sweep.a0 = toiB.c_position.a; - // No warm starting is needed for TOI events because warm - // starting impulses were applied in the discrete solver. - for (var i = 0; i < this.m_contacts.length; ++i) { - var contact = this.m_contacts[i]; - contact.initVelocityConstraint(subStep); - } - // Solve velocity constraints. - for (var i = 0; i < subStep.velocityIterations; ++i) { - for (var j = 0; j < this.m_contacts.length; ++j) { - var contact = this.m_contacts[j]; - contact.solveVelocityConstraint(subStep); - } - } - // Don't store the TOI contact forces for warm starting - // because they can be quite large. - var h = subStep.dt; - // Integrate positions - for (var i = 0; i < this.m_bodies.length; ++i) { - var body = this.m_bodies[i]; - var c = Vec2.clone(body.c_position.c); - var a = body.c_position.a; - var v = Vec2.clone(body.c_velocity.v); - var w = body.c_velocity.w; - // Check for large velocities - var translation = Vec2.mulNumVec2(h, v); - if (Vec2.dot(translation, translation) > Settings.maxTranslationSquared) { - var ratio = Settings.maxTranslation / translation.length(); - v.mul(ratio); - } - var rotation = h * w; - if (rotation * rotation > Settings.maxRotationSquared) { - var ratio = Settings.maxRotation / math$1.abs(rotation); - w *= ratio; - } - // Integrate - c.addMul(h, v); - a += h * w; - body.c_position.c = c; - body.c_position.a = a; - body.c_velocity.v = v; - body.c_velocity.w = w; - // Sync bodies - body.m_sweep.c = c; - body.m_sweep.a = a; - body.m_linearVelocity = v; - body.m_angularVelocity = w; - body.synchronizeTransform(); - } - this.postSolveIsland(); - }; - /** @internal */ - Solver.prototype.postSolveIsland = function () { - for (var c = 0; c < this.m_contacts.length; ++c) { - var contact = this.m_contacts[c]; - this.m_world.postSolve(contact, contact.m_impulse); - } - }; - return Solver; - }()); + * Warning: for performance reasons this is only called when the AABBs begin to + * overlap. + */ + Fixture.prototype.shouldCollide = function (that) { + if (that.m_filterGroupIndex === this.m_filterGroupIndex && that.m_filterGroupIndex !== 0) { + return that.m_filterGroupIndex > 0; + } + var collideA = (that.m_filterMaskBits & this.m_filterCategoryBits) !== 0; + var collideB = (that.m_filterCategoryBits & this.m_filterMaskBits) !== 0; + var collide = collideA && collideB; + return collide; + }; + return Fixture; +}()); - /* - * Planck.js - * The MIT License - * Copyright (c) 2021 Erin Catto, Ali Shakiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - var WorldDefDefault = { - gravity: Vec2.zero(), - allowSleep: true, - warmStarting: true, - continuousPhysics: true, - subStepping: false, - blockSolve: true, - velocityIterations: 8, - positionIterations: 3 - }; - var World = /** @class */ (function () { - /** - * @param def World definition or gravity vector. - */ - function World(def) { - var _this = this; - /** @internal */ - this.s_step = new TimeStep(); // reuse - /** - * @internal - * Callback for broad-phase. - */ - this.createContact = function (proxyA, proxyB) { - var fixtureA = proxyA.fixture; - var fixtureB = proxyB.fixture; - var indexA = proxyA.childIndex; - var indexB = proxyB.childIndex; - var bodyA = fixtureA.getBody(); - var bodyB = fixtureB.getBody(); - // Are the fixtures on the same body? - if (bodyA == bodyB) { - return; - } - // TODO_ERIN use a hash table to remove a potential bottleneck when both - // bodies have a lot of contacts. - // Does a contact already exist? - var edge = bodyB.getContactList(); // ContactEdge - while (edge) { - if (edge.other == bodyA) { - var fA = edge.contact.getFixtureA(); - var fB = edge.contact.getFixtureB(); - var iA = edge.contact.getChildIndexA(); - var iB = edge.contact.getChildIndexB(); - if (fA == fixtureA && fB == fixtureB && iA == indexA && iB == indexB) { - // A contact already exists. - return; - } - if (fA == fixtureB && fB == fixtureA && iA == indexB && iB == indexA) { - // A contact already exists. - return; - } - } - edge = edge.next; - } - if (bodyB.shouldCollide(bodyA) == false) { - return; - } - if (fixtureB.shouldCollide(fixtureA) == false) { - return; - } - // Call the factory. - var contact = Contact.create(fixtureA, indexA, fixtureB, indexB); - if (contact == null) { - return; - } - // Insert into the world. - contact.m_prev = null; - if (_this.m_contactList != null) { - contact.m_next = _this.m_contactList; - _this.m_contactList.m_prev = contact; - } - _this.m_contactList = contact; - ++_this.m_contactCount; - }; - if (!(this instanceof World)) { - return new World(def); - } - if (def && Vec2.isValid(def)) { - def = { gravity: def }; - } - def = options(def, WorldDefDefault); - this.m_solver = new Solver(this); - this.m_broadPhase = new BroadPhase(); - this.m_contactList = null; - this.m_contactCount = 0; - this.m_bodyList = null; - this.m_bodyCount = 0; - this.m_jointList = null; - this.m_jointCount = 0; - this.m_stepComplete = true; - this.m_allowSleep = def.allowSleep; - this.m_gravity = Vec2.clone(def.gravity); - this.m_clearForces = true; - this.m_newFixture = false; - this.m_locked = false; - // These are for debugging the solver. - this.m_warmStarting = def.warmStarting; - this.m_continuousPhysics = def.continuousPhysics; - this.m_subStepping = def.subStepping; - this.m_blockSolve = def.blockSolve; - this.m_velocityIterations = def.velocityIterations; - this.m_positionIterations = def.positionIterations; - this.m_t = 0; +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +var STATIC = 'static'; +var KINEMATIC = 'kinematic'; +var DYNAMIC = 'dynamic'; +var BodyDefDefault = { + type: STATIC, + position: Vec2.zero(), + angle: 0.0, + linearVelocity: Vec2.zero(), + angularVelocity: 0.0, + linearDamping: 0.0, + angularDamping: 0.0, + fixedRotation: false, + bullet: false, + gravityScale: 1.0, + allowSleep: true, + awake: true, + active: true, + userData: null +}; +/** + * MassData This holds the mass data computed for a shape. + */ +var MassData = /** @class */ (function () { + function MassData() { + /** The mass of the shape, usually in kilograms. */ + this.mass = 0; + /** The position of the shape's centroid relative to the shape's origin. */ + this.center = Vec2.zero(); + /** The rotational inertia of the shape about the local origin. */ + this.I = 0; + } + return MassData; +}()); +/** + * A rigid body composed of one or more fixtures. + * + * To create a new Body use {@link World.createBody}. + */ +var Body = /** @class */ (function () { + /** @internal */ + function Body(world, def) { + def = options(def, BodyDefDefault); + this.m_world = world; + this.m_awakeFlag = def.awake; + this.m_autoSleepFlag = def.allowSleep; + this.m_bulletFlag = def.bullet; + this.m_fixedRotationFlag = def.fixedRotation; + this.m_activeFlag = def.active; + this.m_islandFlag = false; + this.m_toiFlag = false; + this.m_userData = def.userData; + this.m_type = def.type; + if (this.m_type == DYNAMIC) { + this.m_mass = 1.0; + this.m_invMass = 1.0; } - /** @internal */ - World.prototype._serialize = function () { - var bodies = []; - var joints = []; - for (var b = this.getBodyList(); b; b = b.getNext()) { - bodies.push(b); - } - for (var j = this.getJointList(); j; j = j.getNext()) { - // @ts-ignore - if (typeof j._serialize === 'function') { - joints.push(j); - } - } - return { - gravity: this.m_gravity, - bodies: bodies, - joints: joints, - }; - }; - /** @internal */ - World._deserialize = function (data, context, restore) { - if (!data) { - return new World(); - } - var world = new World(data.gravity); - if (data.bodies) { - for (var i = data.bodies.length - 1; i >= 0; i -= 1) { - world._addBody(restore(Body, data.bodies[i], world)); - } - } - if (data.joints) { - for (var i = data.joints.length - 1; i >= 0; i--) { - world.createJoint(restore(Joint, data.joints[i], world)); - } - } - return world; - }; - /** - * Get the world body list. With the returned body, use Body.getNext to get the - * next body in the world list. A null body indicates the end of the list. - * - * @return the head of the world body list. - */ - World.prototype.getBodyList = function () { - return this.m_bodyList; - }; - /** - * Get the world joint list. With the returned joint, use Joint.getNext to get - * the next joint in the world list. A null joint indicates the end of the list. - * - * @return the head of the world joint list. - */ - World.prototype.getJointList = function () { - return this.m_jointList; - }; - /** - * Get the world contact list. With the returned contact, use Contact.getNext to - * get the next contact in the world list. A null contact indicates the end of - * the list. - * - * Warning: contacts are created and destroyed in the middle of a time step. - * Use ContactListener to avoid missing contacts. - * - * @return the head of the world contact list. - */ - World.prototype.getContactList = function () { - return this.m_contactList; - }; - World.prototype.getBodyCount = function () { - return this.m_bodyCount; - }; - World.prototype.getJointCount = function () { - return this.m_jointCount; - }; - /** - * Get the number of contacts (each may have 0 or more contact points). - */ - World.prototype.getContactCount = function () { - return this.m_contactCount; - }; - /** - * Change the global gravity vector. - */ - World.prototype.setGravity = function (gravity) { - this.m_gravity = gravity; - }; - /** - * Get the global gravity vector. - */ - World.prototype.getGravity = function () { - return this.m_gravity; - }; - /** - * Is the world locked (in the middle of a time step). - */ - World.prototype.isLocked = function () { - return this.m_locked; - }; - /** - * Enable/disable sleep. - */ - World.prototype.setAllowSleeping = function (flag) { - if (flag == this.m_allowSleep) { - return; - } - this.m_allowSleep = flag; - if (this.m_allowSleep == false) { - for (var b = this.m_bodyList; b; b = b.m_next) { - b.setAwake(true); - } - } - }; - World.prototype.getAllowSleeping = function () { - return this.m_allowSleep; - }; - /** - * Enable/disable warm starting. For testing. - */ - World.prototype.setWarmStarting = function (flag) { - this.m_warmStarting = flag; - }; - World.prototype.getWarmStarting = function () { - return this.m_warmStarting; - }; - /** - * Enable/disable continuous physics. For testing. - */ - World.prototype.setContinuousPhysics = function (flag) { - this.m_continuousPhysics = flag; - }; - World.prototype.getContinuousPhysics = function () { - return this.m_continuousPhysics; - }; - /** - * Enable/disable single stepped continuous physics. For testing. - */ - World.prototype.setSubStepping = function (flag) { - this.m_subStepping = flag; - }; - World.prototype.getSubStepping = function () { - return this.m_subStepping; - }; - /** - * Set flag to control automatic clearing of forces after each time step. - */ - World.prototype.setAutoClearForces = function (flag) { - this.m_clearForces = flag; - }; - /** - * Get the flag that controls automatic clearing of forces after each time step. - */ - World.prototype.getAutoClearForces = function () { - return this.m_clearForces; - }; - /** - * Manually clear the force buffer on all bodies. By default, forces are cleared - * automatically after each call to step. The default behavior is modified by - * calling setAutoClearForces. The purpose of this function is to support - * sub-stepping. Sub-stepping is often used to maintain a fixed sized time step - * under a variable frame-rate. When you perform sub-stepping you will disable - * auto clearing of forces and instead call clearForces after all sub-steps are - * complete in one pass of your game loop. - * - * See {@link World.setAutoClearForces} - */ - World.prototype.clearForces = function () { - for (var body = this.m_bodyList; body; body = body.getNext()) { - body.m_force.setZero(); - body.m_torque = 0.0; - } - }; - /** - * Query the world for all fixtures that potentially overlap the provided AABB. - * - * @param aabb The query box. - * @param callback Called for each fixture found in the query AABB. It may return `false` to terminate the query. - */ - World.prototype.queryAABB = function (aabb, callback) { - var broadPhase = this.m_broadPhase; - this.m_broadPhase.query(aabb, function (proxyId) { - var proxy = broadPhase.getUserData(proxyId); - return callback(proxy.fixture); - }); - }; - /** - * Ray-cast the world for all fixtures in the path of the ray. Your callback - * controls whether you get the closest point, any point, or n-points. The - * ray-cast ignores shapes that contain the starting point. - * - * @param point1 The ray starting point - * @param point2 The ray ending point - * @param callback A user implemented callback function. - */ - World.prototype.rayCast = function (point1, point2, callback) { - var broadPhase = this.m_broadPhase; - this.m_broadPhase.rayCast({ - maxFraction: 1.0, - p1: point1, - p2: point2 - }, function (input, proxyId) { - var proxy = broadPhase.getUserData(proxyId); - var fixture = proxy.fixture; - var index = proxy.childIndex; - // @ts-ignore - var output = {}; // TODO GC - var hit = fixture.rayCast(output, input, index); - if (hit) { - var fraction = output.fraction; - var point = Vec2.add(Vec2.mulNumVec2((1.0 - fraction), input.p1), Vec2.mulNumVec2(fraction, input.p2)); - return callback(fixture, point, output.normal, fraction); - } - return input.maxFraction; - }); - }; - /** - * Get the number of broad-phase proxies. - */ - World.prototype.getProxyCount = function () { - return this.m_broadPhase.getProxyCount(); - }; - /** - * Get the height of broad-phase dynamic tree. - */ - World.prototype.getTreeHeight = function () { - return this.m_broadPhase.getTreeHeight(); - }; - /** - * Get the balance of broad-phase dynamic tree. - */ - World.prototype.getTreeBalance = function () { - return this.m_broadPhase.getTreeBalance(); - }; - /** - * Get the quality metric of broad-phase dynamic tree. The smaller the better. - * The minimum is 1. - */ - World.prototype.getTreeQuality = function () { - return this.m_broadPhase.getTreeQuality(); - }; - /** - * Shift the world origin. Useful for large worlds. The body shift formula is: - * position -= newOrigin - * - * @param newOrigin The new origin with respect to the old origin - */ - World.prototype.shiftOrigin = function (newOrigin) { - if (this.m_locked) { - return; - } - for (var b = this.m_bodyList; b; b = b.m_next) { - b.m_xf.p.sub(newOrigin); - b.m_sweep.c0.sub(newOrigin); - b.m_sweep.c.sub(newOrigin); - } - for (var j = this.m_jointList; j; j = j.m_next) { - j.shiftOrigin(newOrigin); - } - this.m_broadPhase.shiftOrigin(newOrigin); - }; - /** - * @internal Used for deserialize. - */ - World.prototype._addBody = function (body) { - if (this.isLocked()) { - return; - } - // Add to world doubly linked list. - body.m_prev = null; - body.m_next = this.m_bodyList; - if (this.m_bodyList) { - this.m_bodyList.m_prev = body; - } - this.m_bodyList = body; - ++this.m_bodyCount; - }; - // tslint:disable-next-line:typedef - World.prototype.createBody = function (arg1, arg2) { - if (this.isLocked()) { - return null; - } - var def = {}; - if (!arg1) ; - else if (Vec2.isValid(arg1)) { - def = { position: arg1, angle: arg2 }; - } - else if (typeof arg1 === 'object') { - def = arg1; - } - var body = new Body(this, def); - this._addBody(body); - return body; - }; - // tslint:disable-next-line:typedef - World.prototype.createDynamicBody = function (arg1, arg2) { - var def = {}; - if (!arg1) ; - else if (Vec2.isValid(arg1)) { - def = { position: arg1, angle: arg2 }; - } - else if (typeof arg1 === 'object') { - def = arg1; - } - def.type = 'dynamic'; - return this.createBody(def); - }; - // tslint:disable-next-line:typedef - World.prototype.createKinematicBody = function (arg1, arg2) { - var def = {}; - if (!arg1) ; - else if (Vec2.isValid(arg1)) { - def = { position: arg1, angle: arg2 }; - } - else if (typeof arg1 === 'object') { - def = arg1; - } - def.type = 'kinematic'; - return this.createBody(def); - }; - /** - * Destroy a rigid body given a definition. No reference to the definition is - * retained. - * - * Warning: This automatically deletes all associated shapes and joints. - * - * Warning: This function is locked during callbacks. - */ - World.prototype.destroyBody = function (b) { - if (this.isLocked()) { - return; - } - if (b.m_destroyed) { - return false; - } - // Delete the attached joints. - var je = b.m_jointList; - while (je) { - var je0 = je; - je = je.next; - this.publish('remove-joint', je0.joint); - this.destroyJoint(je0.joint); - b.m_jointList = je; - } - b.m_jointList = null; - // Delete the attached contacts. - var ce = b.m_contactList; - while (ce) { - var ce0 = ce; - ce = ce.next; - this.destroyContact(ce0.contact); - b.m_contactList = ce; - } - b.m_contactList = null; - // Delete the attached fixtures. This destroys broad-phase proxies. - var f = b.m_fixtureList; - while (f) { - var f0 = f; - f = f.m_next; - this.publish('remove-fixture', f0); - f0.destroyProxies(this.m_broadPhase); - b.m_fixtureList = f; - } - b.m_fixtureList = null; - // Remove world body list. - if (b.m_prev) { - b.m_prev.m_next = b.m_next; - } - if (b.m_next) { - b.m_next.m_prev = b.m_prev; - } - if (b == this.m_bodyList) { - this.m_bodyList = b.m_next; - } - b.m_destroyed = true; - --this.m_bodyCount; - this.publish('remove-body', b); - return true; - }; - /** - * Create a joint to constrain bodies together. No reference to the definition - * is retained. This may cause the connected bodies to cease colliding. - * - * Warning: This function is locked during callbacks. - */ - World.prototype.createJoint = function (joint) { - if (this.isLocked()) { - return null; - } - // Connect to the world list. - joint.m_prev = null; - joint.m_next = this.m_jointList; - if (this.m_jointList) { - this.m_jointList.m_prev = joint; - } - this.m_jointList = joint; - ++this.m_jointCount; - // Connect to the bodies' doubly linked lists. - joint.m_edgeA.joint = joint; - joint.m_edgeA.other = joint.m_bodyB; - joint.m_edgeA.prev = null; - joint.m_edgeA.next = joint.m_bodyA.m_jointList; - if (joint.m_bodyA.m_jointList) - joint.m_bodyA.m_jointList.prev = joint.m_edgeA; - joint.m_bodyA.m_jointList = joint.m_edgeA; - joint.m_edgeB.joint = joint; - joint.m_edgeB.other = joint.m_bodyA; - joint.m_edgeB.prev = null; - joint.m_edgeB.next = joint.m_bodyB.m_jointList; - if (joint.m_bodyB.m_jointList) - joint.m_bodyB.m_jointList.prev = joint.m_edgeB; - joint.m_bodyB.m_jointList = joint.m_edgeB; - // If the joint prevents collisions, then flag any contacts for filtering. - if (joint.m_collideConnected == false) { - for (var edge = joint.m_bodyB.getContactList(); edge; edge = edge.next) { - if (edge.other == joint.m_bodyA) { - // Flag the contact for filtering at the next time step (where either - // body is awake). - edge.contact.flagForFiltering(); - } - } - } - // Note: creating a joint doesn't wake the bodies. - return joint; - }; - /** - * Destroy a joint. This may cause the connected bodies to begin colliding. - * Warning: This function is locked during callbacks. - */ - World.prototype.destroyJoint = function (joint) { - if (this.isLocked()) { - return; - } - // Remove from the doubly linked list. - if (joint.m_prev) { - joint.m_prev.m_next = joint.m_next; - } - if (joint.m_next) { - joint.m_next.m_prev = joint.m_prev; - } - if (joint == this.m_jointList) { - this.m_jointList = joint.m_next; - } - // Disconnect from bodies. - var bodyA = joint.m_bodyA; - var bodyB = joint.m_bodyB; - // Wake up connected bodies. - bodyA.setAwake(true); - bodyB.setAwake(true); - // Remove from body 1. - if (joint.m_edgeA.prev) { - joint.m_edgeA.prev.next = joint.m_edgeA.next; - } - if (joint.m_edgeA.next) { - joint.m_edgeA.next.prev = joint.m_edgeA.prev; - } - if (joint.m_edgeA == bodyA.m_jointList) { - bodyA.m_jointList = joint.m_edgeA.next; - } - joint.m_edgeA.prev = null; - joint.m_edgeA.next = null; - // Remove from body 2 - if (joint.m_edgeB.prev) { - joint.m_edgeB.prev.next = joint.m_edgeB.next; - } - if (joint.m_edgeB.next) { - joint.m_edgeB.next.prev = joint.m_edgeB.prev; - } - if (joint.m_edgeB == bodyB.m_jointList) { - bodyB.m_jointList = joint.m_edgeB.next; - } - joint.m_edgeB.prev = null; - joint.m_edgeB.next = null; - --this.m_jointCount; - // If the joint prevents collisions, then flag any contacts for filtering. - if (joint.m_collideConnected == false) { - var edge = bodyB.getContactList(); - while (edge) { - if (edge.other == bodyA) { - // Flag the contact for filtering at the next time step (where either - // body is awake). - edge.contact.flagForFiltering(); - } - edge = edge.next; - } - } - this.publish('remove-joint', joint); - }; - /** - * Take a time step. This performs collision detection, integration, and - * constraint solution. - * - * Broad-phase, narrow-phase, solve and solve time of impacts. - * - * @param timeStep Time step, this should not vary. - */ - World.prototype.step = function (timeStep, velocityIterations, positionIterations) { - this.publish('pre-step', timeStep); - if ((velocityIterations | 0) !== velocityIterations) { - // TODO: remove this in future - velocityIterations = 0; - } - velocityIterations = velocityIterations || this.m_velocityIterations; - positionIterations = positionIterations || this.m_positionIterations; - // If new fixtures were added, we need to find the new contacts. - if (this.m_newFixture) { - this.findNewContacts(); - this.m_newFixture = false; - } - this.m_locked = true; - this.s_step.reset(timeStep); - this.s_step.velocityIterations = velocityIterations; - this.s_step.positionIterations = positionIterations; - this.s_step.warmStarting = this.m_warmStarting; - this.s_step.blockSolve = this.m_blockSolve; - // Update contacts. This is where some contacts are destroyed. - this.updateContacts(); - // Integrate velocities, solve velocity constraints, and integrate positions. - if (this.m_stepComplete && timeStep > 0.0) { - this.m_solver.solveWorld(this.s_step); - // Synchronize fixtures, check for out of range bodies. - for (var b = this.m_bodyList; b; b = b.getNext()) { - // If a body was not in an island then it did not move. - if (b.m_islandFlag == false) { - continue; - } - if (b.isStatic()) { - continue; - } - // Update fixtures (for broad-phase). - b.synchronizeFixtures(); - } - // Look for new contacts. - this.findNewContacts(); - } - // Handle TOI events. - if (this.m_continuousPhysics && timeStep > 0.0) { - this.m_solver.solveWorldTOI(this.s_step); - } - if (this.m_clearForces) { - this.clearForces(); - } - this.m_locked = false; - this.publish('post-step', timeStep); - }; - /** - * @internal - * Call this method to find new contacts. - */ - World.prototype.findNewContacts = function () { - this.m_broadPhase.updatePairs(this.createContact); - }; - /** - * @internal - * Removes old non-overlapping contacts, applies filters and updates contacts. - */ - World.prototype.updateContacts = function () { - // Update awake contacts. - var c; - var next_c = this.m_contactList; - while (c = next_c) { - next_c = c.getNext(); - var fixtureA = c.getFixtureA(); - var fixtureB = c.getFixtureB(); - var indexA = c.getChildIndexA(); - var indexB = c.getChildIndexB(); - var bodyA = fixtureA.getBody(); - var bodyB = fixtureB.getBody(); - // Is this contact flagged for filtering? - if (c.m_filterFlag) { - if (bodyB.shouldCollide(bodyA) == false) { - this.destroyContact(c); - continue; - } - if (fixtureB.shouldCollide(fixtureA) == false) { - this.destroyContact(c); - continue; - } - // Clear the filtering flag. - c.m_filterFlag = false; - } - var activeA = bodyA.isAwake() && !bodyA.isStatic(); - var activeB = bodyB.isAwake() && !bodyB.isStatic(); - // At least one body must be awake and it must be dynamic or kinematic. - if (activeA == false && activeB == false) { - continue; - } - var proxyIdA = fixtureA.m_proxies[indexA].proxyId; - var proxyIdB = fixtureB.m_proxies[indexB].proxyId; - var overlap = this.m_broadPhase.testOverlap(proxyIdA, proxyIdB); - // Here we destroy contacts that cease to overlap in the broad-phase. - if (overlap == false) { - this.destroyContact(c); - continue; - } - // The contact persists. - c.update(this); - } - }; - /** - * @internal - */ - World.prototype.destroyContact = function (contact) { - Contact.destroy(contact, this); - // Remove from the world. - if (contact.m_prev) { - contact.m_prev.m_next = contact.m_next; - } - if (contact.m_next) { - contact.m_next.m_prev = contact.m_prev; - } - if (contact == this.m_contactList) { - this.m_contactList = contact.m_next; - } - --this.m_contactCount; - }; - /** - * Register an event listener. - */ - // tslint:disable-next-line:typedef - World.prototype.on = function (name, listener) { - if (typeof name !== 'string' || typeof listener !== 'function') { - return this; - } - if (!this._listeners) { - this._listeners = {}; - } - if (!this._listeners[name]) { - this._listeners[name] = []; - } - this._listeners[name].push(listener); - return this; - }; - /** - * Remove an event listener. - */ - // tslint:disable-next-line:typedef - World.prototype.off = function (name, listener) { - if (typeof name !== 'string' || typeof listener !== 'function') { - return this; - } - var listeners = this._listeners && this._listeners[name]; - if (!listeners || !listeners.length) { - return this; - } - var index = listeners.indexOf(listener); - if (index >= 0) { - listeners.splice(index, 1); - } - return this; + else { + this.m_mass = 0.0; + this.m_invMass = 0.0; + } + // Rotational inertia about the center of mass. + this.m_I = 0.0; + this.m_invI = 0.0; + // the body origin transform + this.m_xf = Transform.identity(); + this.m_xf.p = Vec2.clone(def.position); + this.m_xf.q.setAngle(def.angle); + // the swept motion for CCD + this.m_sweep = new Sweep(); + this.m_sweep.setTransform(this.m_xf); + // position and velocity correction + this.c_velocity = new Velocity(); + this.c_position = new Position(); + this.m_force = Vec2.zero(); + this.m_torque = 0.0; + this.m_linearVelocity = Vec2.clone(def.linearVelocity); + this.m_angularVelocity = def.angularVelocity; + this.m_linearDamping = def.linearDamping; + this.m_angularDamping = def.angularDamping; + this.m_gravityScale = def.gravityScale; + this.m_sleepTime = 0.0; + this.m_jointList = null; + this.m_contactList = null; + this.m_fixtureList = null; + this.m_prev = null; + this.m_next = null; + this.m_destroyed = false; + } + /** @internal */ + Body.prototype._serialize = function () { + var fixtures = []; + for (var f = this.m_fixtureList; f; f = f.m_next) { + fixtures.push(f); + } + return { + type: this.m_type, + bullet: this.m_bulletFlag, + position: this.m_xf.p, + angle: this.m_xf.q.getAngle(), + linearVelocity: this.m_linearVelocity, + angularVelocity: this.m_angularVelocity, + fixtures: fixtures, }; - World.prototype.publish = function (name, arg1, arg2, arg3) { - var listeners = this._listeners && this._listeners[name]; - if (!listeners || !listeners.length) { - return 0; + }; + /** @internal */ + Body._deserialize = function (data, world, restore) { + var body = new Body(world, data); + if (data.fixtures) { + for (var i = data.fixtures.length - 1; i >= 0; i--) { + var fixture = restore(Fixture, data.fixtures[i], body); + body._addFixture(fixture); } - for (var l = 0; l < listeners.length; l++) { - listeners[l].call(this, arg1, arg2, arg3); + } + return body; + }; + Body.prototype.isWorldLocked = function () { + return this.m_world && this.m_world.isLocked() ? true : false; + }; + Body.prototype.getWorld = function () { + return this.m_world; + }; + Body.prototype.getNext = function () { + return this.m_next; + }; + Body.prototype.setUserData = function (data) { + this.m_userData = data; + }; + Body.prototype.getUserData = function () { + return this.m_userData; + }; + Body.prototype.getFixtureList = function () { + return this.m_fixtureList; + }; + Body.prototype.getJointList = function () { + return this.m_jointList; + }; + /** + * Warning: this list changes during the time step and you may miss some + * collisions if you don't use ContactListener. + */ + Body.prototype.getContactList = function () { + return this.m_contactList; + }; + Body.prototype.isStatic = function () { + return this.m_type == STATIC; + }; + Body.prototype.isDynamic = function () { + return this.m_type == DYNAMIC; + }; + Body.prototype.isKinematic = function () { + return this.m_type == KINEMATIC; + }; + /** + * This will alter the mass and velocity. + */ + Body.prototype.setStatic = function () { + this.setType(STATIC); + return this; + }; + Body.prototype.setDynamic = function () { + this.setType(DYNAMIC); + return this; + }; + Body.prototype.setKinematic = function () { + this.setType(KINEMATIC); + return this; + }; + /** + * @internal + */ + Body.prototype.getType = function () { + return this.m_type; + }; + /** + * @internal + */ + Body.prototype.setType = function (type) { + if (this.isWorldLocked() == true) { + return; + } + if (this.m_type == type) { + return; + } + this.m_type = type; + this.resetMassData(); + if (this.m_type == STATIC) { + this.m_linearVelocity.setZero(); + this.m_angularVelocity = 0.0; + this.m_sweep.forward(); + this.synchronizeFixtures(); + } + this.setAwake(true); + this.m_force.setZero(); + this.m_torque = 0.0; + // Delete the attached contacts. + var ce = this.m_contactList; + while (ce) { + var ce0 = ce; + ce = ce.next; + this.m_world.destroyContact(ce0.contact); + } + this.m_contactList = null; + // Touch the proxies so that new contacts will be created (when appropriate) + var broadPhase = this.m_world.m_broadPhase; + for (var f = this.m_fixtureList; f; f = f.m_next) { + var proxyCount = f.m_proxyCount; + for (var i = 0; i < proxyCount; ++i) { + broadPhase.touchProxy(f.m_proxies[i].proxyId); } - return listeners.length; - }; - /** - * @internal - */ - World.prototype.beginContact = function (contact) { - this.publish('begin-contact', contact); - }; - /** - * @internal - */ - World.prototype.endContact = function (contact) { - this.publish('end-contact', contact); - }; - /** - * @internal - */ - World.prototype.preSolve = function (contact, oldManifold) { - this.publish('pre-solve', contact, oldManifold); - }; - /** - * @internal - */ - World.prototype.postSolve = function (contact, impulse) { - this.publish('post-solve', contact, impulse); - }; - return World; - }()); - - /* - * Planck.js - * The MIT License - * Copyright (c) 2021 Erin Catto, Ali Shakiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + } + }; + Body.prototype.isBullet = function () { + return this.m_bulletFlag; + }; + /** + * Should this body be treated like a bullet for continuous collision detection? + */ + Body.prototype.setBullet = function (flag) { + this.m_bulletFlag = !!flag; + }; + Body.prototype.isSleepingAllowed = function () { + return this.m_autoSleepFlag; + }; + Body.prototype.setSleepingAllowed = function (flag) { + this.m_autoSleepFlag = !!flag; + if (this.m_autoSleepFlag == false) { + this.setAwake(true); + } + }; + Body.prototype.isAwake = function () { + return this.m_awakeFlag; + }; + /** + * Set the sleep state of the body. A sleeping body has very low CPU cost. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - var Vec3 = /** @class */ (function () { - // tslint:disable-next-line:typedef - function Vec3(x, y, z) { - if (!(this instanceof Vec3)) { - return new Vec3(x, y, z); + * @param flag Set to true to wake the body, false to put it to sleep. + */ + Body.prototype.setAwake = function (flag) { + if (flag) { + if (this.m_awakeFlag == false) { + this.m_awakeFlag = true; + this.m_sleepTime = 0.0; } - if (typeof x === 'undefined') { - this.x = 0; - this.y = 0; - this.z = 0; + } + else { + this.m_awakeFlag = false; + this.m_sleepTime = 0.0; + this.m_linearVelocity.setZero(); + this.m_angularVelocity = 0.0; + this.m_force.setZero(); + this.m_torque = 0.0; + } + }; + Body.prototype.isActive = function () { + return this.m_activeFlag; + }; + /** + * Set the active state of the body. An inactive body is not simulated and + * cannot be collided with or woken up. If you pass a flag of true, all fixtures + * will be added to the broad-phase. If you pass a flag of false, all fixtures + * will be removed from the broad-phase and all contacts will be destroyed. + * Fixtures and joints are otherwise unaffected. + * + * You may continue to create/destroy fixtures and joints on inactive bodies. + * Fixtures on an inactive body are implicitly inactive and will not participate + * in collisions, ray-casts, or queries. Joints connected to an inactive body + * are implicitly inactive. An inactive body is still owned by a World object + * and remains + */ + Body.prototype.setActive = function (flag) { + if (flag == this.m_activeFlag) { + return; + } + this.m_activeFlag = !!flag; + if (this.m_activeFlag) { + // Create all proxies. + var broadPhase = this.m_world.m_broadPhase; + for (var f = this.m_fixtureList; f; f = f.m_next) { + f.createProxies(broadPhase, this.m_xf); } - else if (typeof x === 'object') { - this.x = x.x; - this.y = x.y; - this.z = x.z; + // Contacts are created the next time step. + } + else { + // Destroy all proxies. + var broadPhase = this.m_world.m_broadPhase; + for (var f = this.m_fixtureList; f; f = f.m_next) { + f.destroyProxies(broadPhase); } - else { - this.x = x; - this.y = y; - this.z = z; + // Destroy the attached contacts. + var ce = this.m_contactList; + while (ce) { + var ce0 = ce; + ce = ce.next; + this.m_world.destroyContact(ce0.contact); } + this.m_contactList = null; } - /** @internal */ - Vec3.prototype._serialize = function () { - return { - x: this.x, - y: this.y, - z: this.z - }; - }; - /** @internal */ - Vec3._deserialize = function (data) { - var obj = Object.create(Vec3.prototype); - obj.x = data.x; - obj.y = data.y; - obj.z = data.z; - return obj; - }; - /** @internal */ - Vec3.neo = function (x, y, z) { - var obj = Object.create(Vec3.prototype); - obj.x = x; - obj.y = y; - obj.z = z; - return obj; - }; - Vec3.zero = function () { - var obj = Object.create(Vec3.prototype); - obj.x = 0; - obj.y = 0; - obj.z = 0; - return obj; - }; - Vec3.clone = function (v) { - return Vec3.neo(v.x, v.y, v.z); - }; - /** @internal */ - Vec3.prototype.toString = function () { - return JSON.stringify(this); - }; - /** - * Does this vector contain finite coordinates? - */ - Vec3.isValid = function (obj) { - if (obj === null || typeof obj === 'undefined') { - return false; - } - return math$1.isFinite(obj.x) && math$1.isFinite(obj.y) && math$1.isFinite(obj.z); - }; - Vec3.assert = function (o) { + }; + Body.prototype.isFixedRotation = function () { + return this.m_fixedRotationFlag; + }; + /** + * Set this body to have fixed rotation. This causes the mass to be reset. + */ + Body.prototype.setFixedRotation = function (flag) { + if (this.m_fixedRotationFlag == flag) { return; - }; - Vec3.prototype.setZero = function () { - this.x = 0.0; - this.y = 0.0; - this.z = 0.0; - return this; - }; - Vec3.prototype.set = function (x, y, z) { - this.x = x; - this.y = y; - this.z = z; - return this; - }; - Vec3.prototype.add = function (w) { - this.x += w.x; - this.y += w.y; - this.z += w.z; - return this; - }; - Vec3.prototype.sub = function (w) { - this.x -= w.x; - this.y -= w.y; - this.z -= w.z; - return this; - }; - Vec3.prototype.mul = function (m) { - this.x *= m; - this.y *= m; - this.z *= m; - return this; - }; - Vec3.areEqual = function (v, w) { - return v === w || - typeof v === 'object' && v !== null && - typeof w === 'object' && w !== null && - v.x === w.x && v.y === w.y && v.z === w.z; - }; - /** - * Perform the dot product on two vectors. - */ - Vec3.dot = function (v, w) { - return v.x * w.x + v.y * w.y + v.z * w.z; - }; - /** - * Perform the cross product on two vectors. In 2D this produces a scalar. - */ - Vec3.cross = function (v, w) { - return new Vec3(v.y * w.z - v.z * w.y, v.z * w.x - v.x * w.z, v.x * w.y - v.y * w.x); - }; - Vec3.add = function (v, w) { - return new Vec3(v.x + w.x, v.y + w.y, v.z + w.z); - }; - Vec3.sub = function (v, w) { - return new Vec3(v.x - w.x, v.y - w.y, v.z - w.z); - }; - Vec3.mul = function (v, m) { - return new Vec3(m * v.x, m * v.y, m * v.z); - }; - Vec3.prototype.neg = function () { - this.x = -this.x; - this.y = -this.y; - this.z = -this.z; - return this; - }; - Vec3.neg = function (v) { - return new Vec3(-v.x, -v.y, -v.z); - }; - return Vec3; - }()); - - /* - * Planck.js - * The MIT License - * Copyright (c) 2021 Erin Catto, Ali Shakiba + } + this.m_fixedRotationFlag = !!flag; + this.m_angularVelocity = 0.0; + this.resetMassData(); + }; + /** + * Get the world transform for the body's origin. + */ + Body.prototype.getTransform = function () { + return this.m_xf; + }; + /** + * Set the position of the body's origin and rotation. Manipulating a body's + * transform may cause non-physical behavior. Note: contacts are updated on the + * next call to World.step. + * + * @param position The world position of the body's local origin. + * @param angle The world rotation in radians. + */ + Body.prototype.setTransform = function (position, angle) { + if (this.isWorldLocked() == true) { + return; + } + this.m_xf.setNum(position, angle); + this.m_sweep.setTransform(this.m_xf); + var broadPhase = this.m_world.m_broadPhase; + for (var f = this.m_fixtureList; f; f = f.m_next) { + f.synchronize(broadPhase, this.m_xf, this.m_xf); + } + }; + Body.prototype.synchronizeTransform = function () { + this.m_sweep.getTransform(this.m_xf, 1); + }; + /** + * Update fixtures in broad-phase. + */ + Body.prototype.synchronizeFixtures = function () { + var xf = Transform.identity(); + this.m_sweep.getTransform(xf, 0); + var broadPhase = this.m_world.m_broadPhase; + for (var f = this.m_fixtureList; f; f = f.m_next) { + f.synchronize(broadPhase, xf, this.m_xf); + } + }; + /** + * Used in TOI. + */ + Body.prototype.advance = function (alpha) { + // Advance to the new safe time. This doesn't sync the broad-phase. + this.m_sweep.advance(alpha); + this.m_sweep.c.setVec2(this.m_sweep.c0); + this.m_sweep.a = this.m_sweep.a0; + this.m_sweep.getTransform(this.m_xf, 1); + }; + /** + * Get the world position for the body's origin. + */ + Body.prototype.getPosition = function () { + return this.m_xf.p; + }; + Body.prototype.setPosition = function (p) { + this.setTransform(p, this.m_sweep.a); + }; + /** + * Get the current world rotation angle in radians. + */ + Body.prototype.getAngle = function () { + return this.m_sweep.a; + }; + Body.prototype.setAngle = function (angle) { + this.setTransform(this.m_xf.p, angle); + }; + /** + * Get the world position of the center of mass. + */ + Body.prototype.getWorldCenter = function () { + return this.m_sweep.c; + }; + /** + * Get the local position of the center of mass. + */ + Body.prototype.getLocalCenter = function () { + return this.m_sweep.localCenter; + }; + /** + * Get the linear velocity of the center of mass. * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * @return the linear velocity of the center of mass. + */ + Body.prototype.getLinearVelocity = function () { + return this.m_linearVelocity; + }; + /** + * Get the world linear velocity of a world point attached to this body. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * @param worldPoint A point in world coordinates. + */ + Body.prototype.getLinearVelocityFromWorldPoint = function (worldPoint) { + var localCenter = Vec2.sub(worldPoint, this.m_sweep.c); + return Vec2.add(this.m_linearVelocity, Vec2.crossNumVec2(this.m_angularVelocity, localCenter)); + }; + /** + * Get the world velocity of a local point. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - /** - * A line segment (edge) shape. These can be connected in chains or loops to - * other edge shapes. The connectivity information is used to ensure correct - * contact normals. - */ - var EdgeShape = /** @class */ (function (_super) { - __extends(EdgeShape, _super); - function EdgeShape(v1, v2) { - var _this = this; - // @ts-ignore - if (!(_this instanceof EdgeShape)) { - return new EdgeShape(v1, v2); - } - _this = _super.call(this) || this; - _this.m_type = EdgeShape.TYPE; - _this.m_radius = Settings.polygonRadius; - _this.m_vertex1 = v1 ? Vec2.clone(v1) : Vec2.zero(); - _this.m_vertex2 = v2 ? Vec2.clone(v2) : Vec2.zero(); - _this.m_vertex0 = Vec2.zero(); - _this.m_vertex3 = Vec2.zero(); - _this.m_hasVertex0 = false; - _this.m_hasVertex3 = false; - return _this; + * @param localPoint A point in local coordinates. + */ + Body.prototype.getLinearVelocityFromLocalPoint = function (localPoint) { + return this.getLinearVelocityFromWorldPoint(this.getWorldPoint(localPoint)); + }; + /** + * Set the linear velocity of the center of mass. + * + * @param v The new linear velocity of the center of mass. + */ + Body.prototype.setLinearVelocity = function (v) { + if (this.m_type == STATIC) { + return; } - /** @internal */ - EdgeShape.prototype._serialize = function () { - return { - type: this.m_type, - vertex1: this.m_vertex1, - vertex2: this.m_vertex2, - vertex0: this.m_vertex0, - vertex3: this.m_vertex3, - hasVertex0: this.m_hasVertex0, - hasVertex3: this.m_hasVertex3, - }; - }; - /** @internal */ - EdgeShape._deserialize = function (data) { - var shape = new EdgeShape(data.vertex1, data.vertex2); - if (shape.m_hasVertex0) { - shape.setPrevVertex(data.vertex0); - } - if (shape.m_hasVertex3) { - shape.setNextVertex(data.vertex3); - } - return shape; - }; - /** @internal @deprecated */ - EdgeShape.prototype.setNext = function (v) { - return this.setNextVertex(v); - }; - /** - * Optional next vertex, used for smooth collision. - */ - EdgeShape.prototype.setNextVertex = function (v) { - if (v) { - this.m_vertex3.setVec2(v); - this.m_hasVertex3 = true; - } - else { - this.m_vertex3.setZero(); - this.m_hasVertex3 = false; - } - return this; - }; - /** - * Optional next vertex, used for smooth collision. - */ - EdgeShape.prototype.getNextVertex = function () { - return this.m_vertex3; - }; - /** @internal @deprecated */ - EdgeShape.prototype.setPrev = function (v) { - return this.setPrevVertex(v); - }; - /** - * Optional prev vertex, used for smooth collision. - */ - EdgeShape.prototype.setPrevVertex = function (v) { - if (v) { - this.m_vertex0.setVec2(v); - this.m_hasVertex0 = true; - } - else { - this.m_vertex0.setZero(); - this.m_hasVertex0 = false; - } - return this; - }; - /** - * Optional prev vertex, used for smooth collision. - */ - EdgeShape.prototype.getPrevVertex = function () { - return this.m_vertex0; - }; - /** - * Set this as an isolated edge. - */ - EdgeShape.prototype._set = function (v1, v2) { - this.m_vertex1.setVec2(v1); - this.m_vertex2.setVec2(v2); - this.m_hasVertex0 = false; - this.m_hasVertex3 = false; - return this; - }; - /** - * @internal - * @deprecated Shapes should be treated as immutable. - * - * clone the concrete shape. - */ - EdgeShape.prototype._clone = function () { - var clone = new EdgeShape(); - clone.m_type = this.m_type; - clone.m_radius = this.m_radius; - clone.m_vertex1.setVec2(this.m_vertex1); - clone.m_vertex2.setVec2(this.m_vertex2); - clone.m_vertex0.setVec2(this.m_vertex0); - clone.m_vertex3.setVec2(this.m_vertex3); - clone.m_hasVertex0 = this.m_hasVertex0; - clone.m_hasVertex3 = this.m_hasVertex3; - return clone; - }; - /** - * Get the number of child primitives. - */ - EdgeShape.prototype.getChildCount = function () { - return 1; - }; - /** - * Test a point for containment in this shape. This only works for convex - * shapes. - * - * @param xf The shape world transform. - * @param p A point in world coordinates. - */ - EdgeShape.prototype.testPoint = function (xf, p) { - return false; - }; - /** - * Cast a ray against a child shape. - * - * @param output The ray-cast results. - * @param input The ray-cast input parameters. - * @param xf The transform to be applied to the shape. - * @param childIndex The child shape index - */ - EdgeShape.prototype.rayCast = function (output, input, xf, childIndex) { - // p = p1 + t * d - // v = v1 + s * e - // p1 + t * d = v1 + s * e - // s * e - t * d = p1 - v1 - // NOT_USED(childIndex); - // Put the ray into the edge's frame of reference. - var p1 = Rot.mulTVec2(xf.q, Vec2.sub(input.p1, xf.p)); - var p2 = Rot.mulTVec2(xf.q, Vec2.sub(input.p2, xf.p)); - var d = Vec2.sub(p2, p1); - var v1 = this.m_vertex1; - var v2 = this.m_vertex2; - var e = Vec2.sub(v2, v1); - var normal = Vec2.neo(e.y, -e.x); - normal.normalize(); - // q = p1 + t * d - // dot(normal, q - v1) = 0 - // dot(normal, p1 - v1) + t * dot(normal, d) = 0 - var numerator = Vec2.dot(normal, Vec2.sub(v1, p1)); - var denominator = Vec2.dot(normal, d); - if (denominator == 0.0) { - return false; - } - var t = numerator / denominator; - if (t < 0.0 || input.maxFraction < t) { - return false; - } - var q = Vec2.add(p1, Vec2.mulNumVec2(t, d)); - // q = v1 + s * r - // s = dot(q - v1, r) / dot(r, r) - var r = Vec2.sub(v2, v1); - var rr = Vec2.dot(r, r); - if (rr == 0.0) { - return false; - } - var s = Vec2.dot(Vec2.sub(q, v1), r) / rr; - if (s < 0.0 || 1.0 < s) { - return false; - } - output.fraction = t; - if (numerator > 0.0) { - output.normal = Rot.mulVec2(xf.q, normal).neg(); - } - else { - output.normal = Rot.mulVec2(xf.q, normal); - } - return true; - }; - /** - * Given a transform, compute the associated axis aligned bounding box for a - * child shape. - * - * @param aabb Returns the axis aligned box. - * @param xf The world transform of the shape. - * @param childIndex The child shape - */ - EdgeShape.prototype.computeAABB = function (aabb, xf, childIndex) { - var v1 = Transform.mulVec2(xf, this.m_vertex1); - var v2 = Transform.mulVec2(xf, this.m_vertex2); - aabb.combinePoints(v1, v2); - aabb.extend(this.m_radius); - }; - /** - * Compute the mass properties of this shape using its dimensions and density. - * The inertia tensor is computed about the local origin. - * - * @param massData Returns the mass data for this shape. - * @param density The density in kilograms per meter squared. - */ - EdgeShape.prototype.computeMass = function (massData, density) { - massData.mass = 0.0; - massData.center.setCombine(0.5, this.m_vertex1, 0.5, this.m_vertex2); - massData.I = 0.0; - }; - EdgeShape.prototype.computeDistanceProxy = function (proxy) { - proxy.m_vertices.push(this.m_vertex1); - proxy.m_vertices.push(this.m_vertex2); - proxy.m_count = 2; - proxy.m_radius = this.m_radius; - }; - EdgeShape.TYPE = 'edge'; - return EdgeShape; - }(Shape)); - - /* - * Planck.js - * The MIT License - * Copyright (c) 2021 Erin Catto, Ali Shakiba + if (Vec2.dot(v, v) > 0.0) { + this.setAwake(true); + } + this.m_linearVelocity.setVec2(v); + }; + /** + * Get the angular velocity. * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * @returns the angular velocity in radians/second. + */ + Body.prototype.getAngularVelocity = function () { + return this.m_angularVelocity; + }; + /** + * Set the angular velocity. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * @param omega The new angular velocity in radians/second. + */ + Body.prototype.setAngularVelocity = function (w) { + if (this.m_type == STATIC) { + return; + } + if (w * w > 0.0) { + this.setAwake(true); + } + this.m_angularVelocity = w; + }; + Body.prototype.getLinearDamping = function () { + return this.m_linearDamping; + }; + Body.prototype.setLinearDamping = function (linearDamping) { + this.m_linearDamping = linearDamping; + }; + Body.prototype.getAngularDamping = function () { + return this.m_angularDamping; + }; + Body.prototype.setAngularDamping = function (angularDamping) { + this.m_angularDamping = angularDamping; + }; + Body.prototype.getGravityScale = function () { + return this.m_gravityScale; + }; + /** + * Scale the gravity applied to this body. + */ + Body.prototype.setGravityScale = function (scale) { + this.m_gravityScale = scale; + }; + /** + * Get the total mass of the body. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - /** - * A chain shape is a free form sequence of line segments. The chain has - * two-sided collision, so you can use inside and outside collision. Therefore, - * you may use any winding order. Connectivity information is used to create - * smooth collisions. + * @returns The mass, usually in kilograms (kg). + */ + Body.prototype.getMass = function () { + return this.m_mass; + }; + /** + * Get the rotational inertia of the body about the local origin. * - * WARNING: The chain will not collide properly if there are self-intersections. + * @return the rotational inertia, usually in kg-m^2. */ - var ChainShape = /** @class */ (function (_super) { - __extends(ChainShape, _super); - function ChainShape(vertices, loop) { - var _this = this; - // @ts-ignore - if (!(_this instanceof ChainShape)) { - return new ChainShape(vertices, loop); - } - _this = _super.call(this) || this; - _this.m_type = ChainShape.TYPE; - _this.m_radius = Settings.polygonRadius; - _this.m_vertices = []; - _this.m_count = 0; - _this.m_prevVertex = null; - _this.m_nextVertex = null; - _this.m_hasPrevVertex = false; - _this.m_hasNextVertex = false; - _this.m_isLoop = !!loop; - if (vertices && vertices.length) { - if (loop) { - _this._createLoop(vertices); - } - else { - _this._createChain(vertices); - } - } - return _this; + Body.prototype.getInertia = function () { + return this.m_I + this.m_mass + * Vec2.dot(this.m_sweep.localCenter, this.m_sweep.localCenter); + }; + /** + * Copy the mass data of the body to data. + */ + Body.prototype.getMassData = function (data) { + data.mass = this.m_mass; + data.I = this.getInertia(); + data.center.setVec2(this.m_sweep.localCenter); + }; + /** + * This resets the mass properties to the sum of the mass properties of the + * fixtures. This normally does not need to be called unless you called + * SetMassData to override the mass and you later want to reset the mass. + */ + Body.prototype.resetMassData = function () { + // Compute mass data from shapes. Each shape has its own density. + this.m_mass = 0.0; + this.m_invMass = 0.0; + this.m_I = 0.0; + this.m_invI = 0.0; + this.m_sweep.localCenter.setZero(); + // Static and kinematic bodies have zero mass. + if (this.isStatic() || this.isKinematic()) { + this.m_sweep.c0.setVec2(this.m_xf.p); + this.m_sweep.c.setVec2(this.m_xf.p); + this.m_sweep.a0 = this.m_sweep.a; + return; } - /** @internal */ - ChainShape.prototype._serialize = function () { - var data = { - type: this.m_type, - vertices: this.m_vertices, - isLoop: this.m_isLoop, - hasPrevVertex: this.m_hasPrevVertex, - hasNextVertex: this.m_hasNextVertex, - prevVertex: null, - nextVertex: null, - }; - if (this.m_prevVertex) { - data.prevVertex = this.m_prevVertex; - } - if (this.m_nextVertex) { - data.nextVertex = this.m_nextVertex; - } - return data; - }; - /** @internal */ - ChainShape._deserialize = function (data, fixture, restore) { - var vertices = []; - if (data.vertices) { - for (var i = 0; i < data.vertices.length; i++) { - vertices.push(restore(Vec2, data.vertices[i])); - } - } - var shape = new ChainShape(vertices, data.isLoop); - if (data.prevVertex) { - shape.setPrevVertex(data.prevVertex); - } - if (data.nextVertex) { - shape.setNextVertex(data.nextVertex); - } - return shape; - }; - // clear() { - // this.m_vertices.length = 0; - // this.m_count = 0; - // } - /** - * @internal - * Create a loop. This automatically adjusts connectivity. - * - * @param vertices an array of vertices, these are copied - * @param count the vertex count - */ - ChainShape.prototype._createLoop = function (vertices) { - for (var i = 1; i < vertices.length; ++i) { - vertices[i - 1]; - vertices[i]; - } - this.m_vertices = []; - this.m_count = vertices.length + 1; - for (var i = 0; i < vertices.length; ++i) { - this.m_vertices[i] = Vec2.clone(vertices[i]); - } - this.m_vertices[vertices.length] = Vec2.clone(vertices[0]); - this.m_prevVertex = this.m_vertices[this.m_count - 2]; - this.m_nextVertex = this.m_vertices[1]; - this.m_hasPrevVertex = true; - this.m_hasNextVertex = true; - return this; - }; - /** - * @internal - * Create a chain with isolated end vertices. - * - * @param vertices an array of vertices, these are copied - * @param count the vertex count - */ - ChainShape.prototype._createChain = function (vertices) { - for (var i = 1; i < vertices.length; ++i) { - // If the code crashes here, it means your vertices are too close together. - vertices[i - 1]; - vertices[i]; - } - this.m_count = vertices.length; - for (var i = 0; i < vertices.length; ++i) { - this.m_vertices[i] = Vec2.clone(vertices[i]); - } - this.m_hasPrevVertex = false; - this.m_hasNextVertex = false; - this.m_prevVertex = null; - this.m_nextVertex = null; - return this; - }; - /** @internal */ - ChainShape.prototype._reset = function () { - if (this.m_isLoop) { - this._createLoop(this.m_vertices); - } - else { - this._createChain(this.m_vertices); - } - }; - /** - * Establish connectivity to a vertex that precedes the first vertex. Don't call - * this for loops. - */ - ChainShape.prototype.setPrevVertex = function (prevVertex) { - this.m_prevVertex = prevVertex; - this.m_hasPrevVertex = true; - }; - ChainShape.prototype.getPrevVertex = function () { - return this.m_prevVertex; - }; - /** - * Establish connectivity to a vertex that follows the last vertex. Don't call - * this for loops. - */ - ChainShape.prototype.setNextVertex = function (nextVertex) { - this.m_nextVertex = nextVertex; - this.m_hasNextVertex = true; - }; - ChainShape.prototype.getNextVertex = function () { - return this.m_nextVertex; - }; - /** - * @internal - * @deprecated Shapes should be treated as immutable. - * - * clone the concrete shape. - */ - ChainShape.prototype._clone = function () { - var clone = new ChainShape(); - clone._createChain(this.m_vertices); - clone.m_type = this.m_type; - clone.m_radius = this.m_radius; - clone.m_prevVertex = this.m_prevVertex; - clone.m_nextVertex = this.m_nextVertex; - clone.m_hasPrevVertex = this.m_hasPrevVertex; - clone.m_hasNextVertex = this.m_hasNextVertex; - return clone; - }; - /** - * Get the number of child primitives. - */ - ChainShape.prototype.getChildCount = function () { - // edge count = vertex count - 1 - return this.m_count - 1; - }; - // Get a child edge. - ChainShape.prototype.getChildEdge = function (edge, childIndex) { - edge.m_type = EdgeShape.TYPE; - edge.m_radius = this.m_radius; - edge.m_vertex1 = this.m_vertices[childIndex]; - edge.m_vertex2 = this.m_vertices[childIndex + 1]; - if (childIndex > 0) { - edge.m_vertex0 = this.m_vertices[childIndex - 1]; - edge.m_hasVertex0 = true; - } - else { - edge.m_vertex0 = this.m_prevVertex; - edge.m_hasVertex0 = this.m_hasPrevVertex; - } - if (childIndex < this.m_count - 2) { - edge.m_vertex3 = this.m_vertices[childIndex + 2]; - edge.m_hasVertex3 = true; - } - else { - edge.m_vertex3 = this.m_nextVertex; - edge.m_hasVertex3 = this.m_hasNextVertex; - } - }; - ChainShape.prototype.getVertex = function (index) { - if (index < this.m_count) { - return this.m_vertices[index]; - } - else { - return this.m_vertices[0]; - } - }; - ChainShape.prototype.isLoop = function () { - return this.m_isLoop; - }; - /** - * Test a point for containment in this shape. This only works for convex - * shapes. - * - * This always return false. - * - * @param xf The shape world transform. - * @param p A point in world coordinates. - */ - ChainShape.prototype.testPoint = function (xf, p) { - return false; - }; - /** - * Cast a ray against a child shape. - * - * @param output The ray-cast results. - * @param input The ray-cast input parameters. - * @param xf The transform to be applied to the shape. - * @param childIndex The child shape index - */ - ChainShape.prototype.rayCast = function (output, input, xf, childIndex) { - var edgeShape = new EdgeShape(this.getVertex(childIndex), this.getVertex(childIndex + 1)); - return edgeShape.rayCast(output, input, xf, 0); - }; - /** - * Given a transform, compute the associated axis aligned bounding box for a - * child shape. - * - * @param aabb Returns the axis aligned box. - * @param xf The world transform of the shape. - * @param childIndex The child shape - */ - ChainShape.prototype.computeAABB = function (aabb, xf, childIndex) { - var v1 = Transform.mulVec2(xf, this.getVertex(childIndex)); - var v2 = Transform.mulVec2(xf, this.getVertex(childIndex + 1)); - aabb.combinePoints(v1, v2); - }; - /** - * Compute the mass properties of this shape using its dimensions and density. - * The inertia tensor is computed about the local origin. - * - * Chains have zero mass. - * - * @param massData Returns the mass data for this shape. - * @param density The density in kilograms per meter squared. - */ - ChainShape.prototype.computeMass = function (massData, density) { - massData.mass = 0.0; - massData.center = Vec2.zero(); - massData.I = 0.0; - }; - ChainShape.prototype.computeDistanceProxy = function (proxy, childIndex) { - proxy.m_buffer[0] = this.getVertex(childIndex); - proxy.m_buffer[1] = this.getVertex(childIndex + 1); - proxy.m_vertices = proxy.m_buffer; - proxy.m_count = 2; - proxy.m_radius = this.m_radius; - }; - ChainShape.TYPE = 'chain'; - return ChainShape; - }(Shape)); - - /* - * Planck.js - * The MIT License - * Copyright (c) 2021 Erin Catto, Ali Shakiba + // Accumulate mass over all fixtures. + var localCenter = Vec2.zero(); + for (var f = this.m_fixtureList; f; f = f.m_next) { + if (f.m_density == 0.0) { + continue; + } + var massData = new MassData(); + f.getMassData(massData); + this.m_mass += massData.mass; + localCenter.addMul(massData.mass, massData.center); + this.m_I += massData.I; + } + // Compute center of mass. + if (this.m_mass > 0.0) { + this.m_invMass = 1.0 / this.m_mass; + localCenter.mul(this.m_invMass); + } + else { + // Force all dynamic bodies to have a positive mass. + this.m_mass = 1.0; + this.m_invMass = 1.0; + } + if (this.m_I > 0.0 && this.m_fixedRotationFlag == false) { + // Center the inertia about the center of mass. + this.m_I -= this.m_mass * Vec2.dot(localCenter, localCenter); + this.m_invI = 1.0 / this.m_I; + } + else { + this.m_I = 0.0; + this.m_invI = 0.0; + } + // Move center of mass. + var oldCenter = Vec2.clone(this.m_sweep.c); + this.m_sweep.setLocalCenter(localCenter, this.m_xf); + // Update center of mass velocity. + this.m_linearVelocity.add(Vec2.crossNumVec2(this.m_angularVelocity, Vec2.sub(this.m_sweep.c, oldCenter))); + }; + /** + * Set the mass properties to override the mass properties of the fixtures. Note + * that this changes the center of mass position. Note that creating or + * destroying fixtures can also alter the mass. This function has no effect if + * the body isn't dynamic. + * + * @param massData The mass properties. + */ + Body.prototype.setMassData = function (massData) { + if (this.isWorldLocked() == true) { + return; + } + if (this.m_type != DYNAMIC) { + return; + } + this.m_invMass = 0.0; + this.m_I = 0.0; + this.m_invI = 0.0; + this.m_mass = massData.mass; + if (this.m_mass <= 0.0) { + this.m_mass = 1.0; + } + this.m_invMass = 1.0 / this.m_mass; + if (massData.I > 0.0 && this.m_fixedRotationFlag == false) { + this.m_I = massData.I - this.m_mass + * Vec2.dot(massData.center, massData.center); + this.m_invI = 1.0 / this.m_I; + } + // Move center of mass. + var oldCenter = Vec2.clone(this.m_sweep.c); + this.m_sweep.setLocalCenter(massData.center, this.m_xf); + // Update center of mass velocity. + this.m_linearVelocity.add(Vec2.crossNumVec2(this.m_angularVelocity, Vec2.sub(this.m_sweep.c, oldCenter))); + }; + /** + * Apply a force at a world point. If the force is not applied at the center of + * mass, it will generate a torque and affect the angular velocity. This wakes + * up the body. * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * @param force The world force vector, usually in Newtons (N). + * @param point The world position of the point of application. + * @param wake Also wake up the body + */ + Body.prototype.applyForce = function (force, point, wake) { + if (wake === void 0) { wake = true; } + if (this.m_type != DYNAMIC) { + return; + } + if (wake && this.m_awakeFlag == false) { + this.setAwake(true); + } + // Don't accumulate a force if the body is sleeping. + if (this.m_awakeFlag) { + this.m_force.add(force); + this.m_torque += Vec2.crossVec2Vec2(Vec2.sub(point, this.m_sweep.c), force); + } + }; + /** + * Apply a force to the center of mass. This wakes up the body. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * @param force The world force vector, usually in Newtons (N). + * @param wake Also wake up the body + */ + Body.prototype.applyForceToCenter = function (force, wake) { + if (wake === void 0) { wake = true; } + if (this.m_type != DYNAMIC) { + return; + } + if (wake && this.m_awakeFlag == false) { + this.setAwake(true); + } + // Don't accumulate a force if the body is sleeping + if (this.m_awakeFlag) { + this.m_force.add(force); + } + }; + /** + * Apply a torque. This affects the angular velocity without affecting the + * linear velocity of the center of mass. This wakes up the body. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - /** - * A convex polygon. It is assumed that the interior of the polygon is to the - * left of each edge. Polygons have a maximum number of vertices equal to - * Settings.maxPolygonVertices. In most cases you should not need many vertices - * for a convex polygon. extends Shape - */ - var PolygonShape = /** @class */ (function (_super) { - __extends(PolygonShape, _super); - // @ts-ignore - function PolygonShape(vertices) { - var _this = this; - // @ts-ignore - if (!(_this instanceof PolygonShape)) { - return new PolygonShape(vertices); - } - _this = _super.call(this) || this; - _this.m_type = PolygonShape.TYPE; - _this.m_radius = Settings.polygonRadius; - _this.m_centroid = Vec2.zero(); - _this.m_vertices = []; - _this.m_normals = []; - _this.m_count = 0; - if (vertices && vertices.length) { - _this._set(vertices); + * @param torque About the z-axis (out of the screen), usually in N-m. + * @param wake Also wake up the body + */ + Body.prototype.applyTorque = function (torque, wake) { + if (wake === void 0) { wake = true; } + if (this.m_type != DYNAMIC) { + return; + } + if (wake && this.m_awakeFlag == false) { + this.setAwake(true); + } + // Don't accumulate a force if the body is sleeping + if (this.m_awakeFlag) { + this.m_torque += torque; + } + }; + /** + * Apply an impulse at a point. This immediately modifies the velocity. It also + * modifies the angular velocity if the point of application is not at the + * center of mass. This wakes up the body. + * + * @param impulse The world impulse vector, usually in N-seconds or kg-m/s. + * @param point The world position of the point of application. + * @param wake Also wake up the body + */ + Body.prototype.applyLinearImpulse = function (impulse, point, wake) { + if (wake === void 0) { wake = true; } + if (this.m_type != DYNAMIC) { + return; + } + if (wake && this.m_awakeFlag == false) { + this.setAwake(true); + } + // Don't accumulate velocity if the body is sleeping + if (this.m_awakeFlag) { + this.m_linearVelocity.addMul(this.m_invMass, impulse); + this.m_angularVelocity += this.m_invI * Vec2.crossVec2Vec2(Vec2.sub(point, this.m_sweep.c), impulse); + } + }; + /** + * Apply an angular impulse. + * + * @param impulse The angular impulse in units of kg*m*m/s + * @param wake Also wake up the body + */ + Body.prototype.applyAngularImpulse = function (impulse, wake) { + if (wake === void 0) { wake = true; } + if (this.m_type != DYNAMIC) { + return; + } + if (wake && this.m_awakeFlag == false) { + this.setAwake(true); + } + // Don't accumulate velocity if the body is sleeping + if (this.m_awakeFlag) { + this.m_angularVelocity += this.m_invI * impulse; + } + }; + /** + * This is used to prevent connected bodies (by joints) from colliding, + * depending on the joint's collideConnected flag. + */ + Body.prototype.shouldCollide = function (that) { + // At least one body should be dynamic. + if (this.m_type != DYNAMIC && that.m_type != DYNAMIC) { + return false; + } + // Does a joint prevent collision? + for (var jn = this.m_jointList; jn; jn = jn.next) { + if (jn.other == that) { + if (jn.joint.m_collideConnected == false) { + return false; + } } - return _this; } - /** @internal */ - PolygonShape.prototype._serialize = function () { - return { - type: this.m_type, - vertices: this.m_vertices, - }; - }; - /** @internal */ - PolygonShape._deserialize = function (data, fixture, restore) { - var vertices = []; - if (data.vertices) { - for (var i = 0; i < data.vertices.length; i++) { - vertices.push(restore(Vec2, data.vertices[i])); + return true; + }; + /** + * @internal Used for deserialize. + */ + Body.prototype._addFixture = function (fixture) { + if (this.isWorldLocked() == true) { + return null; + } + if (this.m_activeFlag) { + var broadPhase = this.m_world.m_broadPhase; + fixture.createProxies(broadPhase, this.m_xf); + } + fixture.m_next = this.m_fixtureList; + this.m_fixtureList = fixture; + // Adjust mass properties if needed. + if (fixture.m_density > 0.0) { + this.resetMassData(); + } + // Let the world know we have a new fixture. This will cause new contacts + // to be created at the beginning of the next time step. + this.m_world.m_newFixture = true; + return fixture; + }; + // tslint:disable-next-line:typedef + Body.prototype.createFixture = function (shape, fixdef) { + if (this.isWorldLocked() == true) { + return null; + } + var fixture = new Fixture(this, shape, fixdef); + this._addFixture(fixture); + return fixture; + }; + /** + * Destroy a fixture. This removes the fixture from the broad-phase and destroys + * all contacts associated with this fixture. This will automatically adjust the + * mass of the body if the body is dynamic and the fixture has positive density. + * All fixtures attached to a body are implicitly destroyed when the body is + * destroyed. + * + * Warning: This function is locked during callbacks. + * + * @param fixture The fixture to be removed. + */ + Body.prototype.destroyFixture = function (fixture) { + if (this.isWorldLocked() == true) { + return; + } + if (this.m_fixtureList === fixture) { + this.m_fixtureList = fixture.m_next; + } + else { + var node = this.m_fixtureList; + while (node != null) { + if (node.m_next === fixture) { + node.m_next = fixture.m_next; + break; } + node = node.m_next; } - var shape = new PolygonShape(vertices); - return shape; - }; - PolygonShape.prototype.getVertex = function (index) { - return this.m_vertices[index]; - }; + } + // Destroy any contacts associated with the fixture. + var edge = this.m_contactList; + while (edge) { + var c = edge.contact; + edge = edge.next; + var fixtureA = c.getFixtureA(); + var fixtureB = c.getFixtureB(); + if (fixture == fixtureA || fixture == fixtureB) { + // This destroys the contact and removes it from + // this body's contact list. + this.m_world.destroyContact(c); + } + } + if (this.m_activeFlag) { + var broadPhase = this.m_world.m_broadPhase; + fixture.destroyProxies(broadPhase); + } + fixture.m_body = null; + fixture.m_next = null; + this.m_world.publish('remove-fixture', fixture); + // Reset the mass data. + this.resetMassData(); + }; + /** + * Get the corresponding world point of a local point. + */ + Body.prototype.getWorldPoint = function (localPoint) { + return Transform.mulVec2(this.m_xf, localPoint); + }; + /** + * Get the corresponding world vector of a local vector. + */ + Body.prototype.getWorldVector = function (localVector) { + return Rot.mulVec2(this.m_xf.q, localVector); + }; + /** + * Gets the corresponding local point of a world point. + */ + Body.prototype.getLocalPoint = function (worldPoint) { + return Transform.mulTVec2(this.m_xf, worldPoint); + }; + /** + * Gets the corresponding local vector of a world vector. + */ + Body.prototype.getLocalVector = function (worldVector) { + return Rot.mulTVec2(this.m_xf.q, worldVector); + }; + /** + * A static body does not move under simulation and behaves as if it has infinite mass. + * Internally, zero is stored for the mass and the inverse mass. + * Static bodies can be moved manually by the user. + * A static body has zero velocity. + * Static bodies do not collide with other static or kinematic bodies. + */ + Body.STATIC = 'static'; + /** + * A kinematic body moves under simulation according to its velocity. + * Kinematic bodies do not respond to forces. + * They can be moved manually by the user, but normally a kinematic body is moved by setting its velocity. + * A kinematic body behaves as if it has infinite mass, however, zero is stored for the mass and the inverse mass. + * Kinematic bodies do not collide with other kinematic or static bodies. + */ + Body.KINEMATIC = 'kinematic'; + /** + * A dynamic body is fully simulated. + * They can be moved manually by the user, but normally they move according to forces. + * A dynamic body can collide with all body types. + * A dynamic body always has finite, non-zero mass. + * If you try to set the mass of a dynamic body to zero, it will automatically acquire a mass of one kilogram and it won't rotate. + */ + Body.DYNAMIC = 'dynamic'; + return Body; +}()); + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * A joint edge is used to connect bodies and joints together in a joint graph + * where each body is a node and each joint is an edge. A joint edge belongs to + * a doubly linked list maintained in each attached body. Each joint has two + * joint nodes, one for each attached body. + */ +var JointEdge = /** @class */ (function () { + function JointEdge() { /** - * @internal - * @deprecated Shapes should be treated as immutable. - * - * clone the concrete shape. + * provides quick access to the other body attached. */ - PolygonShape.prototype._clone = function () { - var clone = new PolygonShape(); - clone.m_type = this.m_type; - clone.m_radius = this.m_radius; - clone.m_count = this.m_count; - clone.m_centroid.setVec2(this.m_centroid); - for (var i = 0; i < this.m_count; i++) { - clone.m_vertices.push(this.m_vertices[i].clone()); - } - for (var i = 0; i < this.m_normals.length; i++) { - clone.m_normals.push(this.m_normals[i].clone()); - } - return clone; - }; + this.other = null; /** - * Get the number of child primitives. + * the joint */ - PolygonShape.prototype.getChildCount = function () { - return 1; - }; - /** @internal */ - PolygonShape.prototype._reset = function () { - this._set(this.m_vertices); - }; + this.joint = null; /** - * @internal - * - * Create a convex hull from the given array of local points. The count must be - * in the range [3, Settings.maxPolygonVertices]. - * - * Warning: the points may be re-ordered, even if they form a convex polygon - * Warning: collinear points are handled but not removed. Collinear points may - * lead to poor stacking behavior. + * prev the previous joint edge in the body's joint list */ - PolygonShape.prototype._set = function (vertices) { - if (vertices.length < 3) { - this._setAsBox(1.0, 1.0); - return; + this.prev = null; + /** + * the next joint edge in the body's joint list + */ + this.next = null; + } + return JointEdge; +}()); +/** + * The base joint class. Joints are used to constraint two bodies together in + * various fashions. Some joints also feature limits and motors. + */ +var Joint = /** @class */ (function () { + function Joint(def, bodyA, bodyB) { + /** @internal */ this.m_type = 'unknown-joint'; + /** @internal */ this.m_prev = null; + /** @internal */ this.m_next = null; + /** @internal */ this.m_edgeA = new JointEdge(); + /** @internal */ this.m_edgeB = new JointEdge(); + /** @internal */ this.m_islandFlag = false; + bodyA = 'bodyA' in def ? def.bodyA : bodyA; + bodyB = 'bodyB' in def ? def.bodyB : bodyB; + this.m_bodyA = bodyA; + this.m_bodyB = bodyB; + this.m_collideConnected = !!def.collideConnected; + this.m_userData = def.userData; + } + /** + * Short-cut function to determine if either body is inactive. + */ + Joint.prototype.isActive = function () { + return this.m_bodyA.isActive() && this.m_bodyB.isActive(); + }; + /** + * Get the type of the concrete joint. + */ + Joint.prototype.getType = function () { + return this.m_type; + }; + /** + * Get the first body attached to this joint. + */ + Joint.prototype.getBodyA = function () { + return this.m_bodyA; + }; + /** + * Get the second body attached to this joint. + */ + Joint.prototype.getBodyB = function () { + return this.m_bodyB; + }; + /** + * Get the next joint the world joint list. + */ + Joint.prototype.getNext = function () { + return this.m_next; + }; + Joint.prototype.getUserData = function () { + return this.m_userData; + }; + Joint.prototype.setUserData = function (data) { + this.m_userData = data; + }; + /** + * Get collide connected. Note: modifying the collide connect flag won't work + * correctly because the flag is only checked when fixture AABBs begin to + * overlap. + */ + Joint.prototype.getCollideConnected = function () { + return this.m_collideConnected; + }; + /** + * Shift the origin for any points stored in world coordinates. + */ + Joint.prototype.shiftOrigin = function (newOrigin) { }; + return Joint; +}()); + +var stats = { + gjkCalls: 0, + gjkIters: 0, + gjkMaxIters: 0, + toiTime: 0, + toiMaxTime: 0, + toiCalls: 0, + toiIters: 0, + toiMaxIters: 0, + toiRootIters: 0, + toiMaxRootIters: 0, + toString: function (newline) { + newline = typeof newline === 'string' ? newline : '\n'; + var string = ""; + // tslint:disable-next-line:no-for-in + for (var name_1 in this) { + if (typeof this[name_1] !== 'function' && typeof this[name_1] !== 'object') { + string += name_1 + ': ' + this[name_1] + newline; } - var n = math$1.min(vertices.length, Settings.maxPolygonVertices); - // Perform welding and copy vertices into local buffer. - var ps = []; // [Settings.maxPolygonVertices]; - for (var i = 0; i < n; ++i) { - var v = vertices[i]; - var unique = true; - for (var j = 0; j < ps.length; ++j) { - if (Vec2.distanceSquared(v, ps[j]) < 0.25 * Settings.linearSlopSquared) { - unique = false; - break; - } - } - if (unique) { - ps.push(v); - } + } + return string; + } +}; + +var now = function () { + return Date.now(); +}; +var diff = function (time) { + return Date.now() - time; +}; +var Timer = { + now: now, + diff: diff, +}; + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * GJK using Voronoi regions (Christer Ericson) and Barycentric coordinates. + */ +stats.gjkCalls = 0; +stats.gjkIters = 0; +stats.gjkMaxIters = 0; +/** + * Input for Distance. You have to option to use the shape radii in the + * computation. Even + */ +var DistanceInput = /** @class */ (function () { + function DistanceInput() { + this.proxyA = new DistanceProxy(); + this.proxyB = new DistanceProxy(); + this.transformA = null; + this.transformB = null; + this.useRadii = false; + } + return DistanceInput; +}()); +/** + * Output for Distance. + * + * @prop {Vec2} pointA closest point on shapeA + * @prop {Vec2} pointB closest point on shapeB + * @prop distance + * @prop iterations number of GJK iterations used + */ +var DistanceOutput = /** @class */ (function () { + function DistanceOutput() { + this.pointA = Vec2.zero(); + this.pointB = Vec2.zero(); + } + return DistanceOutput; +}()); +/** + * Used to warm start Distance. Set count to zero on first call. + * + * @prop {number} metric length or area + * @prop {array} indexA vertices on shape A + * @prop {array} indexB vertices on shape B + * @prop {number} count + */ +var SimplexCache = /** @class */ (function () { + function SimplexCache() { + this.metric = 0; + this.indexA = []; + this.indexB = []; + this.count = 0; + } + return SimplexCache; +}()); +/** + * Compute the closest points between two shapes. Supports any combination of: + * CircleShape, PolygonShape, EdgeShape. The simplex cache is input/output. On + * the first call set SimplexCache.count to zero. + */ +var Distance = function (output, cache, input) { + ++stats.gjkCalls; + var proxyA = input.proxyA; + var proxyB = input.proxyB; + var xfA = input.transformA; + var xfB = input.transformB; + // Initialize the simplex. + var simplex = new Simplex(); + simplex.readCache(cache, proxyA, xfA, proxyB, xfB); + // Get simplex vertices as an array. + var vertices = simplex.m_v; + var k_maxIters = Settings.maxDistnceIterations; + // These store the vertices of the last simplex so that we + // can check for duplicates and prevent cycling. + var saveA = []; + var saveB = []; // int[3] + var saveCount = 0; + // Main iteration loop. + var iter = 0; + while (iter < k_maxIters) { + // Copy simplex so we can identify duplicates. + saveCount = simplex.m_count; + for (var i = 0; i < saveCount; ++i) { + saveA[i] = vertices[i].indexA; + saveB[i] = vertices[i].indexB; + } + simplex.solve(); + // If we have 3 points, then the origin is in the corresponding triangle. + if (simplex.m_count === 3) { + break; + } + // Compute closest point. + var p = simplex.getClosestPoint(); + p.lengthSquared(); + // Get search direction. + var d = simplex.getSearchDirection(); + // Ensure the search direction is numerically fit. + if (d.lengthSquared() < math.EPSILON * math.EPSILON) { + // The origin is probably contained by a line segment + // or triangle. Thus the shapes are overlapped. + // We can't return zero here even though there may be overlap. + // In case the simplex is a point, segment, or triangle it is difficult + // to determine if the origin is contained in the CSO or very close to it. + break; + } + // Compute a tentative new simplex vertex using support points. + var vertex = vertices[simplex.m_count]; // SimplexVertex + vertex.indexA = proxyA.getSupport(Rot.mulTVec2(xfA.q, Vec2.neg(d))); + vertex.wA = Transform.mulVec2(xfA, proxyA.getVertex(vertex.indexA)); + vertex.indexB = proxyB.getSupport(Rot.mulTVec2(xfB.q, d)); + vertex.wB = Transform.mulVec2(xfB, proxyB.getVertex(vertex.indexB)); + vertex.w = Vec2.sub(vertex.wB, vertex.wA); + // Iteration count is equated to the number of support point calls. + ++iter; + ++stats.gjkIters; + // Check for duplicate support points. This is the main termination + // criteria. + var duplicate = false; + for (var i = 0; i < saveCount; ++i) { + if (vertex.indexA === saveA[i] && vertex.indexB === saveB[i]) { + duplicate = true; + break; } - n = ps.length; - if (n < 3) { - this._setAsBox(1.0, 1.0); - return; + } + // If we found a duplicate support point we must exit to avoid cycling. + if (duplicate) { + break; + } + // New vertex is ok and needed. + ++simplex.m_count; + } + stats.gjkMaxIters = math.max(stats.gjkMaxIters, iter); + // Prepare output. + simplex.getWitnessPoints(output.pointA, output.pointB); + output.distance = Vec2.distance(output.pointA, output.pointB); + output.iterations = iter; + // Cache the simplex. + simplex.writeCache(cache); + // Apply radii if requested. + if (input.useRadii) { + var rA = proxyA.m_radius; + var rB = proxyB.m_radius; + if (output.distance > rA + rB && output.distance > math.EPSILON) { + // Shapes are still no overlapped. + // Move the witness points to the outer surface. + output.distance -= rA + rB; + var normal = Vec2.sub(output.pointB, output.pointA); + normal.normalize(); + output.pointA.addMul(rA, normal); + output.pointB.subMul(rB, normal); + } + else { + // Shapes are overlapped when radii are considered. + // Move the witness points to the middle. + var p = Vec2.mid(output.pointA, output.pointB); + output.pointA.setVec2(p); + output.pointB.setVec2(p); + output.distance = 0.0; + } + } +}; +/** + * A distance proxy is used by the GJK algorithm. It encapsulates any shape. + */ +var DistanceProxy = /** @class */ (function () { + function DistanceProxy() { + this.m_buffer = []; // Vec2[2] + this.m_vertices = []; // Vec2[] + this.m_count = 0; + this.m_radius = 0; + } + /** + * Get the vertex count. + */ + DistanceProxy.prototype.getVertexCount = function () { + return this.m_count; + }; + /** + * Get a vertex by index. Used by Distance. + */ + DistanceProxy.prototype.getVertex = function (index) { + return this.m_vertices[index]; + }; + /** + * Get the supporting vertex index in the given direction. + */ + DistanceProxy.prototype.getSupport = function (d) { + var bestIndex = 0; + var bestValue = Vec2.dot(this.m_vertices[0], d); + for (var i = 0; i < this.m_count; ++i) { + var value = Vec2.dot(this.m_vertices[i], d); + if (value > bestValue) { + bestIndex = i; + bestValue = value; } - // Create the convex hull using the Gift wrapping algorithm - // http://en.wikipedia.org/wiki/Gift_wrapping_algorithm - // Find the right most point on the hull (in case of multiple points bottom most is used) - var i0 = 0; - var x0 = ps[0].x; - for (var i = 1; i < n; ++i) { - var x = ps[i].x; - if (x > x0 || (x === x0 && ps[i].y < ps[i0].y)) { - i0 = i; - x0 = x; - } + } + return bestIndex; + }; + /** + * Get the supporting vertex in the given direction. + */ + DistanceProxy.prototype.getSupportVertex = function (d) { + return this.m_vertices[this.getSupport(d)]; + }; + /** + * Initialize the proxy using the given shape. The shape must remain in scope + * while the proxy is in use. + */ + DistanceProxy.prototype.set = function (shape, index) { + shape.computeDistanceProxy(this, index); + }; + return DistanceProxy; +}()); +var SimplexVertex = /** @class */ (function () { + function SimplexVertex() { + /** support point in proxyA */ + this.wA = Vec2.zero(); + /** support point in proxyB */ + this.wB = Vec2.zero(); + /** wB - wA; */ + this.w = Vec2.zero(); + } + SimplexVertex.prototype.set = function (v) { + this.indexA = v.indexA; + this.indexB = v.indexB; + this.wA = Vec2.clone(v.wA); + this.wB = Vec2.clone(v.wB); + this.w = Vec2.clone(v.w); + this.a = v.a; + }; + return SimplexVertex; +}()); +var Simplex = /** @class */ (function () { + function Simplex() { + this.m_v1 = new SimplexVertex(); + this.m_v2 = new SimplexVertex(); + this.m_v3 = new SimplexVertex(); + this.m_v = [this.m_v1, this.m_v2, this.m_v3]; + this.m_count; + } + /** @internal */ + Simplex.prototype.toString = function () { + if (this.m_count === 3) { + return ["+" + this.m_count, + this.m_v1.a, this.m_v1.wA.x, this.m_v1.wA.y, this.m_v1.wB.x, this.m_v1.wB.y, + this.m_v2.a, this.m_v2.wA.x, this.m_v2.wA.y, this.m_v2.wB.x, this.m_v2.wB.y, + this.m_v3.a, this.m_v3.wA.x, this.m_v3.wA.y, this.m_v3.wB.x, this.m_v3.wB.y + ].toString(); + } + else if (this.m_count === 2) { + return ["+" + this.m_count, + this.m_v1.a, this.m_v1.wA.x, this.m_v1.wA.y, this.m_v1.wB.x, this.m_v1.wB.y, + this.m_v2.a, this.m_v2.wA.x, this.m_v2.wA.y, this.m_v2.wB.x, this.m_v2.wB.y + ].toString(); + } + else if (this.m_count === 1) { + return ["+" + this.m_count, + this.m_v1.a, this.m_v1.wA.x, this.m_v1.wA.y, this.m_v1.wB.x, this.m_v1.wB.y + ].toString(); + } + else { + return "+" + this.m_count; + } + }; + Simplex.prototype.readCache = function (cache, proxyA, transformA, proxyB, transformB) { + // Copy data from cache. + this.m_count = cache.count; + for (var i = 0; i < this.m_count; ++i) { + var v = this.m_v[i]; + v.indexA = cache.indexA[i]; + v.indexB = cache.indexB[i]; + var wALocal = proxyA.getVertex(v.indexA); + var wBLocal = proxyB.getVertex(v.indexB); + v.wA = Transform.mulVec2(transformA, wALocal); + v.wB = Transform.mulVec2(transformB, wBLocal); + v.w = Vec2.sub(v.wB, v.wA); + v.a = 0.0; + } + // Compute the new simplex metric, if it is substantially different than + // old metric then flush the simplex. + if (this.m_count > 1) { + var metric1 = cache.metric; + var metric2 = this.getMetric(); + if (metric2 < 0.5 * metric1 || 2.0 * metric1 < metric2 + || metric2 < math.EPSILON) { + // Reset the simplex. + this.m_count = 0; } - var hull = []; // [Settings.maxPolygonVertices]; - var m = 0; - var ih = i0; - while (true) { - hull[m] = ih; - var ie = 0; - for (var j = 1; j < n; ++j) { - if (ie === ih) { - ie = j; - continue; - } - var r = Vec2.sub(ps[ie], ps[hull[m]]); - var v = Vec2.sub(ps[j], ps[hull[m]]); - var c = Vec2.crossVec2Vec2(r, v); - // c < 0 means counter-clockwise wrapping, c > 0 means clockwise wrapping - if (c < 0.0) { - ie = j; - } - // Collinearity check - if (c === 0.0 && v.lengthSquared() > r.lengthSquared()) { - ie = j; - } + } + // If the cache is empty or invalid... + if (this.m_count === 0) { + var v = this.m_v[0]; + v.indexA = 0; + v.indexB = 0; + var wALocal = proxyA.getVertex(0); + var wBLocal = proxyB.getVertex(0); + v.wA = Transform.mulVec2(transformA, wALocal); + v.wB = Transform.mulVec2(transformB, wBLocal); + v.w = Vec2.sub(v.wB, v.wA); + v.a = 1.0; + this.m_count = 1; + } + }; + Simplex.prototype.writeCache = function (cache) { + cache.metric = this.getMetric(); + cache.count = this.m_count; + for (var i = 0; i < this.m_count; ++i) { + cache.indexA[i] = this.m_v[i].indexA; + cache.indexB[i] = this.m_v[i].indexB; + } + }; + Simplex.prototype.getSearchDirection = function () { + switch (this.m_count) { + case 1: + return Vec2.neg(this.m_v1.w); + case 2: { + var e12 = Vec2.sub(this.m_v2.w, this.m_v1.w); + var sgn = Vec2.crossVec2Vec2(e12, Vec2.neg(this.m_v1.w)); + if (sgn > 0.0) { + // Origin is left of e12. + return Vec2.crossNumVec2(1.0, e12); } - ++m; - ih = ie; - if (ie === i0) { - break; + else { + // Origin is right of e12. + return Vec2.crossVec2Num(e12, 1.0); } } - if (m < 3) { - this._setAsBox(1.0, 1.0); - return; + default: + return Vec2.zero(); + } + }; + Simplex.prototype.getClosestPoint = function () { + switch (this.m_count) { + case 0: + return Vec2.zero(); + case 1: + return Vec2.clone(this.m_v1.w); + case 2: + return Vec2.combine(this.m_v1.a, this.m_v1.w, this.m_v2.a, this.m_v2.w); + case 3: + return Vec2.zero(); + default: + return Vec2.zero(); + } + }; + Simplex.prototype.getWitnessPoints = function (pA, pB) { + switch (this.m_count) { + case 0: + break; + case 1: + pA.setVec2(this.m_v1.wA); + pB.setVec2(this.m_v1.wB); + break; + case 2: + pA.setCombine(this.m_v1.a, this.m_v1.wA, this.m_v2.a, this.m_v2.wA); + pB.setCombine(this.m_v1.a, this.m_v1.wB, this.m_v2.a, this.m_v2.wB); + break; + case 3: + pA.setCombine(this.m_v1.a, this.m_v1.wA, this.m_v2.a, this.m_v2.wA); + pA.addMul(this.m_v3.a, this.m_v3.wA); + pB.setVec2(pA); + break; + } + }; + Simplex.prototype.getMetric = function () { + switch (this.m_count) { + case 0: + return 0.0; + case 1: + return 0.0; + case 2: + return Vec2.distance(this.m_v1.w, this.m_v2.w); + case 3: + return Vec2.crossVec2Vec2(Vec2.sub(this.m_v2.w, this.m_v1.w), Vec2.sub(this.m_v3.w, this.m_v1.w)); + default: + return 0.0; + } + }; + Simplex.prototype.solve = function () { + switch (this.m_count) { + case 1: + break; + case 2: + this.solve2(); + break; + case 3: + this.solve3(); + break; + } + }; + // Solve a line segment using barycentric coordinates. + // + // p = a1 * w1 + a2 * w2 + // a1 + a2 = 1 + // + // The vector from the origin to the closest point on the line is + // perpendicular to the line. + // e12 = w2 - w1 + // dot(p, e) = 0 + // a1 * dot(w1, e) + a2 * dot(w2, e) = 0 + // + // 2-by-2 linear system + // [1 1 ][a1] = [1] + // [w1.e12 w2.e12][a2] = [0] + // + // Define + // d12_1 = dot(w2, e12) + // d12_2 = -dot(w1, e12) + // d12 = d12_1 + d12_2 + // + // Solution + // a1 = d12_1 / d12 + // a2 = d12_2 / d12 + Simplex.prototype.solve2 = function () { + var w1 = this.m_v1.w; + var w2 = this.m_v2.w; + var e12 = Vec2.sub(w2, w1); + // w1 region + var d12_2 = -Vec2.dot(w1, e12); + if (d12_2 <= 0.0) { + // a2 <= 0, so we clamp it to 0 + this.m_v1.a = 1.0; + this.m_count = 1; + return; + } + // w2 region + var d12_1 = Vec2.dot(w2, e12); + if (d12_1 <= 0.0) { + // a1 <= 0, so we clamp it to 0 + this.m_v2.a = 1.0; + this.m_count = 1; + this.m_v1.set(this.m_v2); + return; + } + // Must be in e12 region. + var inv_d12 = 1.0 / (d12_1 + d12_2); + this.m_v1.a = d12_1 * inv_d12; + this.m_v2.a = d12_2 * inv_d12; + this.m_count = 2; + }; + // Possible regions: + // - points[2] + // - edge points[0]-points[2] + // - edge points[1]-points[2] + // - inside the triangle + Simplex.prototype.solve3 = function () { + var w1 = this.m_v1.w; + var w2 = this.m_v2.w; + var w3 = this.m_v3.w; + // Edge12 + // [1 1 ][a1] = [1] + // [w1.e12 w2.e12][a2] = [0] + // a3 = 0 + var e12 = Vec2.sub(w2, w1); + var w1e12 = Vec2.dot(w1, e12); + var w2e12 = Vec2.dot(w2, e12); + var d12_1 = w2e12; + var d12_2 = -w1e12; + // Edge13 + // [1 1 ][a1] = [1] + // [w1.e13 w3.e13][a3] = [0] + // a2 = 0 + var e13 = Vec2.sub(w3, w1); + var w1e13 = Vec2.dot(w1, e13); + var w3e13 = Vec2.dot(w3, e13); + var d13_1 = w3e13; + var d13_2 = -w1e13; + // Edge23 + // [1 1 ][a2] = [1] + // [w2.e23 w3.e23][a3] = [0] + // a1 = 0 + var e23 = Vec2.sub(w3, w2); + var w2e23 = Vec2.dot(w2, e23); + var w3e23 = Vec2.dot(w3, e23); + var d23_1 = w3e23; + var d23_2 = -w2e23; + // Triangle123 + var n123 = Vec2.crossVec2Vec2(e12, e13); + var d123_1 = n123 * Vec2.crossVec2Vec2(w2, w3); + var d123_2 = n123 * Vec2.crossVec2Vec2(w3, w1); + var d123_3 = n123 * Vec2.crossVec2Vec2(w1, w2); + // w1 region + if (d12_2 <= 0.0 && d13_2 <= 0.0) { + this.m_v1.a = 1.0; + this.m_count = 1; + return; + } + // e12 + if (d12_1 > 0.0 && d12_2 > 0.0 && d123_3 <= 0.0) { + var inv_d12 = 1.0 / (d12_1 + d12_2); + this.m_v1.a = d12_1 * inv_d12; + this.m_v2.a = d12_2 * inv_d12; + this.m_count = 2; + return; + } + // e13 + if (d13_1 > 0.0 && d13_2 > 0.0 && d123_2 <= 0.0) { + var inv_d13 = 1.0 / (d13_1 + d13_2); + this.m_v1.a = d13_1 * inv_d13; + this.m_v3.a = d13_2 * inv_d13; + this.m_count = 2; + this.m_v2.set(this.m_v3); + return; + } + // w2 region + if (d12_1 <= 0.0 && d23_2 <= 0.0) { + this.m_v2.a = 1.0; + this.m_count = 1; + this.m_v1.set(this.m_v2); + return; + } + // w3 region + if (d13_1 <= 0.0 && d23_1 <= 0.0) { + this.m_v3.a = 1.0; + this.m_count = 1; + this.m_v1.set(this.m_v3); + return; + } + // e23 + if (d23_1 > 0.0 && d23_2 > 0.0 && d123_1 <= 0.0) { + var inv_d23 = 1.0 / (d23_1 + d23_2); + this.m_v2.a = d23_1 * inv_d23; + this.m_v3.a = d23_2 * inv_d23; + this.m_count = 2; + this.m_v1.set(this.m_v3); + return; + } + // Must be in triangle123 + var inv_d123 = 1.0 / (d123_1 + d123_2 + d123_3); + this.m_v1.a = d123_1 * inv_d123; + this.m_v2.a = d123_2 * inv_d123; + this.m_v3.a = d123_3 * inv_d123; + this.m_count = 3; + }; + return Simplex; +}()); +/** + * Determine if two generic shapes overlap. + */ +var testOverlap = function (shapeA, indexA, shapeB, indexB, xfA, xfB) { + var input = new DistanceInput(); + input.proxyA.set(shapeA, indexA); + input.proxyB.set(shapeB, indexB); + input.transformA = xfA; + input.transformB = xfB; + input.useRadii = true; + var cache = new SimplexCache(); + var output = new DistanceOutput(); + Distance(output, cache, input); + return output.distance < 10.0 * math.EPSILON; +}; +// legacy exports +Distance.testOverlap = testOverlap; +Distance.Input = DistanceInput; +Distance.Output = DistanceOutput; +Distance.Proxy = DistanceProxy; +Distance.Cache = SimplexCache; + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * Input parameters for TimeOfImpact. + */ +var TOIInput = /** @class */ (function () { + function TOIInput() { + this.proxyA = new DistanceProxy(); + this.proxyB = new DistanceProxy(); + this.sweepA = new Sweep(); + this.sweepB = new Sweep(); + } + return TOIInput; +}()); +var TOIOutputState; +(function (TOIOutputState) { + TOIOutputState[TOIOutputState["e_unknown"] = 0] = "e_unknown"; + TOIOutputState[TOIOutputState["e_failed"] = 1] = "e_failed"; + TOIOutputState[TOIOutputState["e_overlapped"] = 2] = "e_overlapped"; + TOIOutputState[TOIOutputState["e_touching"] = 3] = "e_touching"; + TOIOutputState[TOIOutputState["e_separated"] = 4] = "e_separated"; +})(TOIOutputState || (TOIOutputState = {})); +/** + * Output parameters for TimeOfImpact. + */ +var TOIOutput = /** @class */ (function () { + function TOIOutput() { + } + return TOIOutput; +}()); +stats.toiTime = 0; +stats.toiMaxTime = 0; +stats.toiCalls = 0; +stats.toiIters = 0; +stats.toiMaxIters = 0; +stats.toiRootIters = 0; +stats.toiMaxRootIters = 0; +/** + * Compute the upper bound on time before two shapes penetrate. Time is + * represented as a fraction between [0,tMax]. This uses a swept separating axis + * and may miss some intermediate, non-tunneling collision. If you change the + * time interval, you should call this function again. + * + * Note: use Distance to compute the contact point and normal at the time of + * impact. + * + * CCD via the local separating axis method. This seeks progression by computing + * the largest time at which separation is maintained. + */ +var TimeOfImpact = function (output, input) { + var timer = Timer.now(); + ++stats.toiCalls; + output.state = TOIOutputState.e_unknown; + output.t = input.tMax; + var proxyA = input.proxyA; // DistanceProxy + var proxyB = input.proxyB; // DistanceProxy + var sweepA = input.sweepA; // Sweep + var sweepB = input.sweepB; // Sweep + // Large rotations can make the root finder fail, so we normalize the + // sweep angles. + sweepA.normalize(); + sweepB.normalize(); + var tMax = input.tMax; + var totalRadius = proxyA.m_radius + proxyB.m_radius; + var target = math.max(Settings.linearSlop, totalRadius - 3.0 * Settings.linearSlop); + var tolerance = 0.25 * Settings.linearSlop; + var t1 = 0.0; + var k_maxIterations = Settings.maxTOIIterations; + var iter = 0; + // Prepare input for distance query. + var cache = new SimplexCache(); + var distanceInput = new DistanceInput(); + distanceInput.proxyA = input.proxyA; + distanceInput.proxyB = input.proxyB; + distanceInput.useRadii = false; + // The outer loop progressively attempts to compute new separating axes. + // This loop terminates when an axis is repeated (no progress is made). + while (true) { + var xfA = Transform.identity(); + var xfB = Transform.identity(); + sweepA.getTransform(xfA, t1); + sweepB.getTransform(xfB, t1); + // Get the distance between shapes. We can also use the results + // to get a separating axis. + distanceInput.transformA = xfA; + distanceInput.transformB = xfB; + var distanceOutput = new DistanceOutput(); + Distance(distanceOutput, cache, distanceInput); + // If the shapes are overlapped, we give up on continuous collision. + if (distanceOutput.distance <= 0.0) { + // Failure! + output.state = TOIOutputState.e_overlapped; + output.t = 0.0; + break; + } + if (distanceOutput.distance < target + tolerance) { + // Victory! + output.state = TOIOutputState.e_touching; + output.t = t1; + break; + } + // Initialize the separating axis. + var fcn = new SeparationFunction(); + fcn.initialize(cache, proxyA, sweepA, proxyB, sweepB, t1); + // if (false) { + // // Dump the curve seen by the root finder + // const N = 100; + // const dx = 1.0 / N; + // const xs = []; // [ N + 1 ]; + // const fs = []; // [ N + 1 ]; + // const x = 0.0; + // for (const i = 0; i <= N; ++i) { + // sweepA.getTransform(xfA, x); + // sweepB.getTransform(xfB, x); + // const f = fcn.evaluate(xfA, xfB) - target; + // printf("%g %g\n", x, f); + // xs[i] = x; + // fs[i] = f; + // x += dx; + // } + // } + // Compute the TOI on the separating axis. We do this by successively + // resolving the deepest point. This loop is bounded by the number of + // vertices. + var done = false; + var t2 = tMax; + var pushBackIter = 0; + while (true) { + // Find the deepest point at t2. Store the witness point indices. + var s2 = fcn.findMinSeparation(t2); + // const indexA = fcn.indexA; + // const indexB = fcn.indexB; + // Is the final configuration separated? + if (s2 > target + tolerance) { + // Victory! + output.state = TOIOutputState.e_separated; + output.t = tMax; + done = true; + break; } - this.m_count = m; - // Copy vertices. - this.m_vertices = []; - for (var i = 0; i < m; ++i) { - this.m_vertices[i] = ps[hull[i]]; + // Has the separation reached tolerance? + if (s2 > target - tolerance) { + // Advance the sweeps + t1 = t2; + break; } - // Compute normals. Ensure the edges have non-zero length. - for (var i = 0; i < m; ++i) { - var i1 = i; - var i2 = i + 1 < m ? i + 1 : 0; - var edge = Vec2.sub(this.m_vertices[i2], this.m_vertices[i1]); - this.m_normals[i] = Vec2.crossVec2Num(edge, 1.0); - this.m_normals[i].normalize(); + // Compute the initial separation of the witness points. + var s1 = fcn.evaluate(t1); + // const indexA = fcn.indexA; + // const indexB = fcn.indexB; + // Check for initial overlap. This might happen if the root finder + // runs out of iterations. + if (s1 < target - tolerance) { + output.state = TOIOutputState.e_failed; + output.t = t1; + done = true; + break; } - // Compute the polygon centroid. - this.m_centroid = ComputeCentroid(this.m_vertices, m); - }; - /** @internal */ - PolygonShape.prototype._setAsBox = function (hx, hy, center, angle) { - // start with right-bottom, counter-clockwise, as in Gift wrapping algorithm in PolygonShape._set() - this.m_vertices[0] = Vec2.neo(hx, -hy); - this.m_vertices[1] = Vec2.neo(hx, hy); - this.m_vertices[2] = Vec2.neo(-hx, hy); - this.m_vertices[3] = Vec2.neo(-hx, -hy); - this.m_normals[0] = Vec2.neo(1.0, 0.0); - this.m_normals[1] = Vec2.neo(0.0, 1.0); - this.m_normals[2] = Vec2.neo(-1.0, 0.0); - this.m_normals[3] = Vec2.neo(0.0, -1.0); - this.m_count = 4; - if (Vec2.isValid(center)) { - angle = angle || 0; - this.m_centroid.setVec2(center); - var xf = Transform.identity(); - xf.p.setVec2(center); - xf.q.setAngle(angle); - // Transform vertices and normals. - for (var i = 0; i < this.m_count; ++i) { - this.m_vertices[i] = Transform.mulVec2(xf, this.m_vertices[i]); - this.m_normals[i] = Rot.mulVec2(xf.q, this.m_normals[i]); - } + // Check for touching + if (s1 <= target + tolerance) { + // Victory! t1 should hold the TOI (could be 0.0). + output.state = TOIOutputState.e_touching; + output.t = t1; + done = true; + break; } - }; - /** - * Test a point for containment in this shape. This only works for convex - * shapes. - * - * @param xf The shape world transform. - * @param p A point in world coordinates. - */ - PolygonShape.prototype.testPoint = function (xf, p) { - var pLocal = Rot.mulTVec2(xf.q, Vec2.sub(p, xf.p)); - for (var i = 0; i < this.m_count; ++i) { - var dot = Vec2.dot(this.m_normals[i], Vec2.sub(pLocal, this.m_vertices[i])); - if (dot > 0.0) { - return false; + // Compute 1D root of: f(x) - target = 0 + var rootIterCount = 0; + var a1 = t1; + var a2 = t2; + while (true) { + // Use a mix of the secant rule and bisection. + var t = void 0; + if (rootIterCount & 1) { + // Secant rule to improve convergence. + t = a1 + (target - s1) * (a2 - a1) / (s2 - s1); } - } - return true; - }; - /** - * Cast a ray against a child shape. - * - * @param output The ray-cast results. - * @param input The ray-cast input parameters. - * @param xf The transform to be applied to the shape. - * @param childIndex The child shape index - */ - PolygonShape.prototype.rayCast = function (output, input, xf, childIndex) { - // Put the ray into the polygon's frame of reference. - var p1 = Rot.mulTVec2(xf.q, Vec2.sub(input.p1, xf.p)); - var p2 = Rot.mulTVec2(xf.q, Vec2.sub(input.p2, xf.p)); - var d = Vec2.sub(p2, p1); - var lower = 0.0; - var upper = input.maxFraction; - var index = -1; - for (var i = 0; i < this.m_count; ++i) { - // p = p1 + a * d - // dot(normal, p - v) = 0 - // dot(normal, p1 - v) + a * dot(normal, d) = 0 - var numerator = Vec2.dot(this.m_normals[i], Vec2.sub(this.m_vertices[i], p1)); - var denominator = Vec2.dot(this.m_normals[i], d); - if (denominator == 0.0) { - if (numerator < 0.0) { - return false; - } + else { + // Bisection to guarantee progress. + t = 0.5 * (a1 + a2); + } + ++rootIterCount; + ++stats.toiRootIters; + var s = fcn.evaluate(t); + fcn.indexA; + fcn.indexB; + if (math.abs(s - target) < tolerance) { + // t2 holds a tentative value for t1 + t2 = t; + break; + } + // Ensure we continue to bracket the root. + if (s > target) { + a1 = t; + s1 = s; } else { - // Note: we want this predicate without division: - // lower < numerator / denominator, where denominator < 0 - // Since denominator < 0, we have to flip the inequality: - // lower < numerator / denominator <==> denominator * lower > numerator. - if (denominator < 0.0 && numerator < lower * denominator) { - // Increase lower. - // The segment enters this half-space. - lower = numerator / denominator; - index = i; - } - else if (denominator > 0.0 && numerator < upper * denominator) { - // Decrease upper. - // The segment exits this half-space. - upper = numerator / denominator; - } + a2 = t; + s2 = s; } - // The use of epsilon here causes the assert on lower to trip - // in some cases. Apparently the use of epsilon was to make edge - // shapes work, but now those are handled separately. - // if (upper < lower - Math.EPSILON) - if (upper < lower) { - return false; + if (rootIterCount === 50) { + break; } } - if (index >= 0) { - output.fraction = lower; - output.normal = Rot.mulVec2(xf.q, this.m_normals[index]); - return true; - } - return false; - }; - /** - * Given a transform, compute the associated axis aligned bounding box for a - * child shape. - * - * @param aabb Returns the axis aligned box. - * @param xf The world transform of the shape. - * @param childIndex The child shape - */ - PolygonShape.prototype.computeAABB = function (aabb, xf, childIndex) { - var minX = Infinity; - var minY = Infinity; - var maxX = -Infinity; - var maxY = -Infinity; - for (var i = 0; i < this.m_count; ++i) { - var v = Transform.mulVec2(xf, this.m_vertices[i]); - minX = math$1.min(minX, v.x); - maxX = math$1.max(maxX, v.x); - minY = math$1.min(minY, v.y); - maxY = math$1.max(maxY, v.y); - } - aabb.lowerBound.setNum(minX, minY); - aabb.upperBound.setNum(maxX, maxY); - aabb.extend(this.m_radius); - }; - /** - * Compute the mass properties of this shape using its dimensions and density. - * The inertia tensor is computed about the local origin. - * - * @param massData Returns the mass data for this shape. - * @param density The density in kilograms per meter squared. - */ - PolygonShape.prototype.computeMass = function (massData, density) { - var center = Vec2.zero(); - var area = 0.0; - var I = 0.0; - // s is the reference point for forming triangles. - // It's location doesn't change the result (except for rounding error). - var s = Vec2.zero(); - // This code would put the reference point inside the polygon. - for (var i = 0; i < this.m_count; ++i) { - s.add(this.m_vertices[i]); - } - s.mul(1.0 / this.m_count); - var k_inv3 = 1.0 / 3.0; - for (var i = 0; i < this.m_count; ++i) { - // Triangle vertices. - var e1 = Vec2.sub(this.m_vertices[i], s); - var e2 = i + 1 < this.m_count ? Vec2.sub(this.m_vertices[i + 1], s) : Vec2.sub(this.m_vertices[0], s); - var D = Vec2.crossVec2Vec2(e1, e2); - var triangleArea = 0.5 * D; - area += triangleArea; - // Area weighted centroid - center.addCombine(triangleArea * k_inv3, e1, triangleArea * k_inv3, e2); - var ex1 = e1.x; - var ey1 = e1.y; - var ex2 = e2.x; - var ey2 = e2.y; - var intx2 = ex1 * ex1 + ex2 * ex1 + ex2 * ex2; - var inty2 = ey1 * ey1 + ey2 * ey1 + ey2 * ey2; - I += (0.25 * k_inv3 * D) * (intx2 + inty2); - } - // Total mass - massData.mass = density * area; - center.mul(1.0 / area); - massData.center.setCombine(1, center, 1, s); - // Inertia tensor relative to the local origin (point s). - massData.I = density * I; - // Shift to center of mass then to original body origin. - massData.I += massData.mass * (Vec2.dot(massData.center, massData.center) - Vec2.dot(center, center)); - }; - /** - * Validate convexity. This is a very time consuming operation. - * @returns true if valid - */ - PolygonShape.prototype.validate = function () { - for (var i = 0; i < this.m_count; ++i) { - var i1 = i; - var i2 = i < this.m_count - 1 ? i1 + 1 : 0; - var p = this.m_vertices[i1]; - var e = Vec2.sub(this.m_vertices[i2], p); - for (var j = 0; j < this.m_count; ++j) { - if (j == i1 || j == i2) { - continue; - } - var v = Vec2.sub(this.m_vertices[j], p); - var c = Vec2.crossVec2Vec2(e, v); - if (c < 0.0) { - return false; - } - } + stats.toiMaxRootIters = math.max(stats.toiMaxRootIters, rootIterCount); + ++pushBackIter; + if (pushBackIter === Settings.maxPolygonVertices) { + break; } - return true; - }; - PolygonShape.prototype.computeDistanceProxy = function (proxy) { - proxy.m_vertices = this.m_vertices; - proxy.m_count = this.m_count; - proxy.m_radius = this.m_radius; - }; - PolygonShape.TYPE = 'polygon'; - return PolygonShape; - }(Shape)); - function ComputeCentroid(vs, count) { - var c = Vec2.zero(); - var area = 0.0; - // pRef is the reference point for forming triangles. - // It's location doesn't change the result (except for rounding error). - var pRef = Vec2.zero(); - var i; - var inv3 = 1.0 / 3.0; - for (var i = 0; i < count; ++i) { - // Triangle vertices. - var p1 = pRef; - var p2 = vs[i]; - var p3 = i + 1 < count ? vs[i + 1] : vs[0]; - var e1 = Vec2.sub(p2, p1); - var e2 = Vec2.sub(p3, p1); - var D = Vec2.crossVec2Vec2(e1, e2); - var triangleArea = 0.5 * D; - area += triangleArea; - // Area weighted centroid - c.addMul(triangleArea * inv3, p1); - c.addMul(triangleArea * inv3, p2); - c.addMul(triangleArea * inv3, p3); } - c.mul(1.0 / area); - return c; + ++iter; + ++stats.toiIters; + if (done) { + break; + } + if (iter === k_maxIterations) { + // Root finder got stuck. Semi-victory. + output.state = TOIOutputState.e_failed; + output.t = t1; + break; + } } - - /* - * Planck.js - * The MIT License - * Copyright (c) 2021 Erin Catto, Ali Shakiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - /** - * A rectangle polygon which extend PolygonShape. - */ - var BoxShape = /** @class */ (function (_super) { - __extends(BoxShape, _super); - function BoxShape(hx, hy, center, angle) { - var _this = this; - // @ts-ignore - if (!(_this instanceof BoxShape)) { - return new BoxShape(hx, hy, center, angle); - } - _this = _super.call(this) || this; - _this._setAsBox(hx, hy, center, angle); - return _this; + stats.toiMaxIters = math.max(stats.toiMaxIters, iter); + var time = Timer.diff(timer); + stats.toiMaxTime = math.max(stats.toiMaxTime, time); + stats.toiTime += time; +}; +var SeparationFunctionType; +(function (SeparationFunctionType) { + SeparationFunctionType[SeparationFunctionType["e_points"] = 1] = "e_points"; + SeparationFunctionType[SeparationFunctionType["e_faceA"] = 2] = "e_faceA"; + SeparationFunctionType[SeparationFunctionType["e_faceB"] = 3] = "e_faceB"; +})(SeparationFunctionType || (SeparationFunctionType = {})); +var SeparationFunction = /** @class */ (function () { + function SeparationFunction() { + this.m_proxyA = new DistanceProxy(); + this.m_proxyB = new DistanceProxy(); + this.m_localPoint = Vec2.zero(); + this.m_axis = Vec2.zero(); + } + // TODO_ERIN might not need to return the separation + SeparationFunction.prototype.initialize = function (cache, proxyA, sweepA, proxyB, sweepB, t1) { + this.m_proxyA = proxyA; + this.m_proxyB = proxyB; + var count = cache.count; + this.m_sweepA = sweepA; + this.m_sweepB = sweepB; + var xfA = Transform.identity(); + var xfB = Transform.identity(); + this.m_sweepA.getTransform(xfA, t1); + this.m_sweepB.getTransform(xfB, t1); + if (count === 1) { + this.m_type = SeparationFunctionType.e_points; + var localPointA = this.m_proxyA.getVertex(cache.indexA[0]); + var localPointB = this.m_proxyB.getVertex(cache.indexB[0]); + var pointA = Transform.mulVec2(xfA, localPointA); + var pointB = Transform.mulVec2(xfB, localPointB); + this.m_axis.setCombine(1, pointB, -1, pointA); + var s = this.m_axis.normalize(); + return s; } - BoxShape.TYPE = 'polygon'; - return BoxShape; - }(PolygonShape)); - - /* - * Planck.js - * The MIT License - * Copyright (c) 2021 Erin Catto, Ali Shakiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - var CircleShape = /** @class */ (function (_super) { - __extends(CircleShape, _super); - // tslint:disable-next-line:typedef - function CircleShape(a, b) { - var _this = this; - // @ts-ignore - if (!(_this instanceof CircleShape)) { - return new CircleShape(a, b); + else if (cache.indexA[0] === cache.indexA[1]) { + // Two points on B and one on A. + this.m_type = SeparationFunctionType.e_faceB; + var localPointB1 = proxyB.getVertex(cache.indexB[0]); + var localPointB2 = proxyB.getVertex(cache.indexB[1]); + this.m_axis = Vec2.crossVec2Num(Vec2.sub(localPointB2, localPointB1), 1.0); + this.m_axis.normalize(); + var normal = Rot.mulVec2(xfB.q, this.m_axis); + this.m_localPoint = Vec2.mid(localPointB1, localPointB2); + var pointB = Transform.mulVec2(xfB, this.m_localPoint); + var localPointA = proxyA.getVertex(cache.indexA[0]); + var pointA = Transform.mulVec2(xfA, localPointA); + var s = Vec2.dot(pointA, normal) - Vec2.dot(pointB, normal); + if (s < 0.0) { + this.m_axis = Vec2.neg(this.m_axis); + s = -s; + } + return s; + } + else { + // Two points on A and one or two points on B. + this.m_type = SeparationFunctionType.e_faceA; + var localPointA1 = this.m_proxyA.getVertex(cache.indexA[0]); + var localPointA2 = this.m_proxyA.getVertex(cache.indexA[1]); + this.m_axis = Vec2.crossVec2Num(Vec2.sub(localPointA2, localPointA1), 1.0); + this.m_axis.normalize(); + var normal = Rot.mulVec2(xfA.q, this.m_axis); + this.m_localPoint = Vec2.mid(localPointA1, localPointA2); + var pointA = Transform.mulVec2(xfA, this.m_localPoint); + var localPointB = this.m_proxyB.getVertex(cache.indexB[0]); + var pointB = Transform.mulVec2(xfB, localPointB); + var s = Vec2.dot(pointB, normal) - Vec2.dot(pointA, normal); + if (s < 0.0) { + this.m_axis = Vec2.neg(this.m_axis); + s = -s; + } + return s; + } + }; + SeparationFunction.prototype.compute = function (find, t) { + // It was findMinSeparation and evaluate + var xfA = Transform.identity(); + var xfB = Transform.identity(); + this.m_sweepA.getTransform(xfA, t); + this.m_sweepB.getTransform(xfB, t); + switch (this.m_type) { + case SeparationFunctionType.e_points: { + if (find) { + var axisA = Rot.mulTVec2(xfA.q, this.m_axis); + var axisB = Rot.mulTVec2(xfB.q, Vec2.neg(this.m_axis)); + this.indexA = this.m_proxyA.getSupport(axisA); + this.indexB = this.m_proxyB.getSupport(axisB); + } + var localPointA = this.m_proxyA.getVertex(this.indexA); + var localPointB = this.m_proxyB.getVertex(this.indexB); + var pointA = Transform.mulVec2(xfA, localPointA); + var pointB = Transform.mulVec2(xfB, localPointB); + var sep = Vec2.dot(pointB, this.m_axis) - Vec2.dot(pointA, this.m_axis); + return sep; } - _this = _super.call(this) || this; - _this.m_type = CircleShape.TYPE; - _this.m_p = Vec2.zero(); - _this.m_radius = 1; - if (typeof a === 'object' && Vec2.isValid(a)) { - _this.m_p.setVec2(a); - if (typeof b === 'number') { - _this.m_radius = b; + case SeparationFunctionType.e_faceA: { + var normal = Rot.mulVec2(xfA.q, this.m_axis); + var pointA = Transform.mulVec2(xfA, this.m_localPoint); + if (find) { + var axisB = Rot.mulTVec2(xfB.q, Vec2.neg(normal)); + this.indexA = -1; + this.indexB = this.m_proxyB.getSupport(axisB); } + var localPointB = this.m_proxyB.getVertex(this.indexB); + var pointB = Transform.mulVec2(xfB, localPointB); + var sep = Vec2.dot(pointB, normal) - Vec2.dot(pointA, normal); + return sep; } - else if (typeof a === 'number') { - _this.m_radius = a; + case SeparationFunctionType.e_faceB: { + var normal = Rot.mulVec2(xfB.q, this.m_axis); + var pointB = Transform.mulVec2(xfB, this.m_localPoint); + if (find) { + var axisA = Rot.mulTVec2(xfA.q, Vec2.neg(normal)); + this.indexB = -1; + this.indexA = this.m_proxyA.getSupport(axisA); + } + var localPointA = this.m_proxyA.getVertex(this.indexA); + var pointA = Transform.mulVec2(xfA, localPointA); + var sep = Vec2.dot(pointA, normal) - Vec2.dot(pointB, normal); + return sep; } - return _this; + default: + if (find) { + this.indexA = -1; + this.indexB = -1; + } + return 0.0; } - /** @internal */ - CircleShape.prototype._serialize = function () { - return { - type: this.m_type, - p: this.m_p, - radius: this.m_radius, - }; - }; - /** @internal */ - CircleShape._deserialize = function (data) { - return new CircleShape(data.p, data.radius); - }; - // TODO: already defined in Shape - CircleShape.prototype.getRadius = function () { - return this.m_radius; - }; - CircleShape.prototype.getCenter = function () { - return this.m_p; - }; - CircleShape.prototype.getVertex = function (index) { - return this.m_p; - }; - /** - * @internal - * @deprecated Shapes should be treated as immutable. - * - * clone the concrete shape. - */ - CircleShape.prototype._clone = function () { - var clone = new CircleShape(); - clone.m_type = this.m_type; - clone.m_radius = this.m_radius; - clone.m_p = this.m_p.clone(); - return clone; - }; - /** - * Get the number of child primitives. - */ - CircleShape.prototype.getChildCount = function () { - return 1; - }; - /** - * Test a point for containment in this shape. This only works for convex - * shapes. - * - * @param xf The shape world transform. - * @param p A point in world coordinates. - */ - CircleShape.prototype.testPoint = function (xf, p) { - var center = Vec2.add(xf.p, Rot.mulVec2(xf.q, this.m_p)); - var d = Vec2.sub(p, center); - return Vec2.dot(d, d) <= this.m_radius * this.m_radius; - }; - /** - * Cast a ray against a child shape. - * - * @param output The ray-cast results. - * @param input The ray-cast input parameters. - * @param xf The transform to be applied to the shape. - * @param childIndex The child shape index - */ - CircleShape.prototype.rayCast = function (output, input, xf, childIndex) { - // Collision Detection in Interactive 3D Environments by Gino van den Bergen - // From Section 3.1.2 - // x = s + a * r - // norm(x) = radius - var position = Vec2.add(xf.p, Rot.mulVec2(xf.q, this.m_p)); - var s = Vec2.sub(input.p1, position); - var b = Vec2.dot(s, s) - this.m_radius * this.m_radius; - // Solve quadratic equation. - var r = Vec2.sub(input.p2, input.p1); - var c = Vec2.dot(s, r); - var rr = Vec2.dot(r, r); - var sigma = c * c - rr * b; - // Check for negative discriminant and short segment. - if (sigma < 0.0 || rr < math$1.EPSILON) { - return false; - } - // Find the point of intersection of the line with the circle. - var a = -(c + math$1.sqrt(sigma)); - // Is the intersection point on the segment? - if (0.0 <= a && a <= input.maxFraction * rr) { - a /= rr; - output.fraction = a; - output.normal = Vec2.add(s, Vec2.mulNumVec2(a, r)); - output.normal.normalize(); - return true; - } - return false; - }; - /** - * Given a transform, compute the associated axis aligned bounding box for a - * child shape. - * - * @param aabb Returns the axis aligned box. - * @param xf The world transform of the shape. - * @param childIndex The child shape - */ - CircleShape.prototype.computeAABB = function (aabb, xf, childIndex) { - var p = Vec2.add(xf.p, Rot.mulVec2(xf.q, this.m_p)); - aabb.lowerBound.setNum(p.x - this.m_radius, p.y - this.m_radius); - aabb.upperBound.setNum(p.x + this.m_radius, p.y + this.m_radius); - }; - /** - * Compute the mass properties of this shape using its dimensions and density. - * The inertia tensor is computed about the local origin. - * - * @param massData Returns the mass data for this shape. - * @param density The density in kilograms per meter squared. - */ - CircleShape.prototype.computeMass = function (massData, density) { - massData.mass = density * math$1.PI * this.m_radius * this.m_radius; - massData.center = this.m_p; - // inertia about the local origin - massData.I = massData.mass - * (0.5 * this.m_radius * this.m_radius + Vec2.dot(this.m_p, this.m_p)); - }; - CircleShape.prototype.computeDistanceProxy = function (proxy) { - proxy.m_vertices.push(this.m_p); - proxy.m_count = 1; - proxy.m_radius = this.m_radius; - }; - CircleShape.TYPE = 'circle'; - return CircleShape; - }(Shape)); - - /* - * Planck.js - * The MIT License - * Copyright (c) 2021 Erin Catto, Ali Shakiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - var DEFAULTS$a = { - frequencyHz: 0.0, - dampingRatio: 0.0 }; - /** - * A distance joint constrains two points on two bodies to remain at a fixed - * distance from each other. You can view this as a massless, rigid rod. - * - * @param anchorA Anchor A in global coordination. - * @param anchorB Anchor B in global coordination. - */ - var DistanceJoint = /** @class */ (function (_super) { - __extends(DistanceJoint, _super); - function DistanceJoint(def, bodyA, bodyB, anchorA, anchorB) { - var _this = this; - // @ts-ignore - if (!(_this instanceof DistanceJoint)) { - return new DistanceJoint(def, bodyA, bodyB, anchorA, anchorB); - } - // order of constructor arguments is changed in v0.2 - if (bodyB && anchorA && ('m_type' in anchorA) && ('x' in bodyB) && ('y' in bodyB)) { - var temp = bodyB; - bodyB = anchorA; - anchorA = temp; - } - def = options(def, DEFAULTS$a); - _this = _super.call(this, def, bodyA, bodyB) || this; - bodyA = _this.m_bodyA; - bodyB = _this.m_bodyB; - _this.m_type = DistanceJoint.TYPE; - // Solver shared - _this.m_localAnchorA = Vec2.clone(anchorA ? bodyA.getLocalPoint(anchorA) : def.localAnchorA || Vec2.zero()); - _this.m_localAnchorB = Vec2.clone(anchorB ? bodyB.getLocalPoint(anchorB) : def.localAnchorB || Vec2.zero()); - _this.m_length = math$1.isFinite(def.length) ? def.length : - Vec2.distance(bodyA.getWorldPoint(_this.m_localAnchorA), bodyB.getWorldPoint(_this.m_localAnchorB)); - _this.m_frequencyHz = def.frequencyHz; - _this.m_dampingRatio = def.dampingRatio; - _this.m_impulse = 0.0; - _this.m_gamma = 0.0; - _this.m_bias = 0.0; - return _this; - // 1-D constrained system - // m (v2 - v1) = lambda - // v2 + (beta/h) * x1 + gamma * lambda = 0, gamma has units of inverse mass. - // x2 = x1 + h * v2 - // 1-D mass-damper-spring system - // m (v2 - v1) + h * d * v2 + h * k * - // C = norm(p2 - p1) - L - // u = (p2 - p1) / norm(p2 - p1) - // Cdot = dot(u, v2 + cross(w2, r2) - v1 - cross(w1, r1)) - // J = [-u -cross(r1, u) u cross(r2, u)] - // K = J * invM * JT - // = invMass1 + invI1 * cross(r1, u)^2 + invMass2 + invI2 * cross(r2, u)^2 + SeparationFunction.prototype.findMinSeparation = function (t) { + return this.compute(true, t); + }; + SeparationFunction.prototype.evaluate = function (t) { + return this.compute(false, t); + }; + return SeparationFunction; +}()); +new SeparationFunction(); +// legacy exports +TimeOfImpact.Input = TOIInput; +TimeOfImpact.Output = TOIOutput; + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +var TimeStep = /** @class */ (function () { + function TimeStep() { + /** time step */ + this.dt = 0; + /** inverse time step (0 if dt == 0) */ + this.inv_dt = 0; + this.velocityIterations = 0; + this.positionIterations = 0; + this.warmStarting = false; + this.blockSolve = true; + /** timestep ratio for variable timestep */ + this.inv_dt0 = 0.0; + /** dt * inv_dt0 */ + this.dtRatio = 1; + } + TimeStep.prototype.reset = function (dt) { + if (this.dt > 0.0) { + this.inv_dt0 = this.inv_dt; } - /** @internal */ - DistanceJoint.prototype._serialize = function () { - return { - type: this.m_type, - bodyA: this.m_bodyA, - bodyB: this.m_bodyB, - collideConnected: this.m_collideConnected, - frequencyHz: this.m_frequencyHz, - dampingRatio: this.m_dampingRatio, - localAnchorA: this.m_localAnchorA, - localAnchorB: this.m_localAnchorB, - length: this.m_length, - impulse: this.m_impulse, - gamma: this.m_gamma, - bias: this.m_bias, - }; - }; - /** @internal */ - DistanceJoint._deserialize = function (data, world, restore) { - data = __assign({}, data); - data.bodyA = restore(Body, data.bodyA, world); - data.bodyB = restore(Body, data.bodyB, world); - var joint = new DistanceJoint(data); - return joint; - }; - /** @internal */ - DistanceJoint.prototype._setAnchors = function (def) { - if (def.anchorA) { - this.m_localAnchorA.setVec2(this.m_bodyA.getLocalPoint(def.anchorA)); - } - else if (def.localAnchorA) { - this.m_localAnchorA.setVec2(def.localAnchorA); - } - if (def.anchorB) { - this.m_localAnchorB.setVec2(this.m_bodyB.getLocalPoint(def.anchorB)); - } - else if (def.localAnchorB) { - this.m_localAnchorB.setVec2(def.localAnchorB); - } - if (def.length > 0) { - this.m_length = +def.length; - } - else if (def.length < 0) ; - else if (def.anchorA || def.anchorA || def.anchorA || def.anchorA) { - this.m_length = Vec2.distance(this.m_bodyA.getWorldPoint(this.m_localAnchorA), this.m_bodyB.getWorldPoint(this.m_localAnchorB)); - } - }; - /** - * The local anchor point relative to bodyA's origin. - */ - DistanceJoint.prototype.getLocalAnchorA = function () { - return this.m_localAnchorA; - }; - /** - * The local anchor point relative to bodyB's origin. - */ - DistanceJoint.prototype.getLocalAnchorB = function () { - return this.m_localAnchorB; - }; - /** - * Set the natural length. Manipulating the length can lead to non-physical - * behavior when the frequency is zero. - */ - DistanceJoint.prototype.setLength = function (length) { - this.m_length = length; - }; - /** - * Get the natural length. - */ - DistanceJoint.prototype.getLength = function () { - return this.m_length; - }; - DistanceJoint.prototype.setFrequency = function (hz) { - this.m_frequencyHz = hz; - }; - DistanceJoint.prototype.getFrequency = function () { - return this.m_frequencyHz; - }; - DistanceJoint.prototype.setDampingRatio = function (ratio) { - this.m_dampingRatio = ratio; - }; - DistanceJoint.prototype.getDampingRatio = function () { - return this.m_dampingRatio; - }; - /** - * Get the anchor point on bodyA in world coordinates. - */ - DistanceJoint.prototype.getAnchorA = function () { - return this.m_bodyA.getWorldPoint(this.m_localAnchorA); - }; - /** - * Get the anchor point on bodyB in world coordinates. - */ - DistanceJoint.prototype.getAnchorB = function () { - return this.m_bodyB.getWorldPoint(this.m_localAnchorB); - }; - /** - * Get the reaction force on bodyB at the joint anchor in Newtons. - */ - DistanceJoint.prototype.getReactionForce = function (inv_dt) { - return Vec2.mulNumVec2(this.m_impulse, this.m_u).mul(inv_dt); - }; - /** - * Get the reaction torque on bodyB in N*m. - */ - DistanceJoint.prototype.getReactionTorque = function (inv_dt) { - return 0.0; - }; - DistanceJoint.prototype.initVelocityConstraints = function (step) { - this.m_localCenterA = this.m_bodyA.m_sweep.localCenter; - this.m_localCenterB = this.m_bodyB.m_sweep.localCenter; - this.m_invMassA = this.m_bodyA.m_invMass; - this.m_invMassB = this.m_bodyB.m_invMass; - this.m_invIA = this.m_bodyA.m_invI; - this.m_invIB = this.m_bodyB.m_invI; - var cA = this.m_bodyA.c_position.c; - var aA = this.m_bodyA.c_position.a; - var vA = this.m_bodyA.c_velocity.v; - var wA = this.m_bodyA.c_velocity.w; - var cB = this.m_bodyB.c_position.c; - var aB = this.m_bodyB.c_position.a; - var vB = this.m_bodyB.c_velocity.v; - var wB = this.m_bodyB.c_velocity.w; - var qA = Rot.neo(aA); - var qB = Rot.neo(aB); - this.m_rA = Rot.mulVec2(qA, Vec2.sub(this.m_localAnchorA, this.m_localCenterA)); - this.m_rB = Rot.mulVec2(qB, Vec2.sub(this.m_localAnchorB, this.m_localCenterB)); - this.m_u = Vec2.sub(Vec2.add(cB, this.m_rB), Vec2.add(cA, this.m_rA)); - // Handle singularity. - var length = this.m_u.length(); - if (length > Settings.linearSlop) { - this.m_u.mul(1.0 / length); - } - else { - this.m_u.setNum(0.0, 0.0); - } - var crAu = Vec2.crossVec2Vec2(this.m_rA, this.m_u); - var crBu = Vec2.crossVec2Vec2(this.m_rB, this.m_u); - var invMass = this.m_invMassA + this.m_invIA * crAu * crAu + this.m_invMassB - + this.m_invIB * crBu * crBu; - // Compute the effective mass matrix. - this.m_mass = invMass != 0.0 ? 1.0 / invMass : 0.0; - if (this.m_frequencyHz > 0.0) { - var C = length - this.m_length; - // Frequency - var omega = 2.0 * math$1.PI * this.m_frequencyHz; - // Damping coefficient - var d = 2.0 * this.m_mass * this.m_dampingRatio * omega; - // Spring stiffness - var k = this.m_mass * omega * omega; - // magic formulas - var h = step.dt; - this.m_gamma = h * (d + h * k); - this.m_gamma = this.m_gamma != 0.0 ? 1.0 / this.m_gamma : 0.0; - this.m_bias = C * h * k * this.m_gamma; - invMass += this.m_gamma; - this.m_mass = invMass != 0.0 ? 1.0 / invMass : 0.0; - } - else { - this.m_gamma = 0.0; - this.m_bias = 0.0; - } - if (step.warmStarting) { - // Scale the impulse to support a variable time step. - this.m_impulse *= step.dtRatio; - var P = Vec2.mulNumVec2(this.m_impulse, this.m_u); - vA.subMul(this.m_invMassA, P); - wA -= this.m_invIA * Vec2.crossVec2Vec2(this.m_rA, P); - vB.addMul(this.m_invMassB, P); - wB += this.m_invIB * Vec2.crossVec2Vec2(this.m_rB, P); - } - else { - this.m_impulse = 0.0; - } - this.m_bodyA.c_velocity.v.setVec2(vA); - this.m_bodyA.c_velocity.w = wA; - this.m_bodyB.c_velocity.v.setVec2(vB); - this.m_bodyB.c_velocity.w = wB; - }; - DistanceJoint.prototype.solveVelocityConstraints = function (step) { - var vA = this.m_bodyA.c_velocity.v; - var wA = this.m_bodyA.c_velocity.w; - var vB = this.m_bodyB.c_velocity.v; - var wB = this.m_bodyB.c_velocity.w; - // Cdot = dot(u, v + cross(w, r)) - var vpA = Vec2.add(vA, Vec2.crossNumVec2(wA, this.m_rA)); - var vpB = Vec2.add(vB, Vec2.crossNumVec2(wB, this.m_rB)); - var Cdot = Vec2.dot(this.m_u, vpB) - Vec2.dot(this.m_u, vpA); - var impulse = -this.m_mass - * (Cdot + this.m_bias + this.m_gamma * this.m_impulse); - this.m_impulse += impulse; - var P = Vec2.mulNumVec2(impulse, this.m_u); - vA.subMul(this.m_invMassA, P); - wA -= this.m_invIA * Vec2.crossVec2Vec2(this.m_rA, P); - vB.addMul(this.m_invMassB, P); - wB += this.m_invIB * Vec2.crossVec2Vec2(this.m_rB, P); - this.m_bodyA.c_velocity.v.setVec2(vA); - this.m_bodyA.c_velocity.w = wA; - this.m_bodyB.c_velocity.v.setVec2(vB); - this.m_bodyB.c_velocity.w = wB; - }; - /** - * This returns true if the position errors are within tolerance. - */ - DistanceJoint.prototype.solvePositionConstraints = function (step) { - if (this.m_frequencyHz > 0.0) { - // There is no position correction for soft distance constraints. - return true; - } - var cA = this.m_bodyA.c_position.c; - var aA = this.m_bodyA.c_position.a; - var cB = this.m_bodyB.c_position.c; - var aB = this.m_bodyB.c_position.a; - var qA = Rot.neo(aA); - var qB = Rot.neo(aB); - var rA = Rot.mulSub(qA, this.m_localAnchorA, this.m_localCenterA); - var rB = Rot.mulSub(qB, this.m_localAnchorB, this.m_localCenterB); - var u = Vec2.sub(Vec2.add(cB, rB), Vec2.add(cA, rA)); - var length = u.normalize(); - var C = length - this.m_length; - C = math$1 - .clamp(C, -Settings.maxLinearCorrection, Settings.maxLinearCorrection); - var impulse = -this.m_mass * C; - var P = Vec2.mulNumVec2(impulse, u); - cA.subMul(this.m_invMassA, P); - aA -= this.m_invIA * Vec2.crossVec2Vec2(rA, P); - cB.addMul(this.m_invMassB, P); - aB += this.m_invIB * Vec2.crossVec2Vec2(rB, P); - this.m_bodyA.c_position.c.setVec2(cA); - this.m_bodyA.c_position.a = aA; - this.m_bodyB.c_position.c.setVec2(cB); - this.m_bodyB.c_position.a = aB; - return math$1.abs(C) < Settings.linearSlop; - }; - DistanceJoint.TYPE = 'distance-joint'; - return DistanceJoint; - }(Joint)); - - /* - * Planck.js - * The MIT License - * Copyright (c) 2021 Erin Catto, Ali Shakiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - var DEFAULTS$9 = { - maxForce: 0.0, - maxTorque: 0.0, + this.dt = dt; + this.inv_dt = dt == 0 ? 0 : 1 / dt; + this.dtRatio = dt * this.inv_dt0; }; - /** - * Friction joint. This is used for top-down friction. It provides 2D - * translational friction and angular friction. - * - * @param anchor Anchor in global coordination. - */ - var FrictionJoint = /** @class */ (function (_super) { - __extends(FrictionJoint, _super); - function FrictionJoint(def, bodyA, bodyB, anchor) { - var _this = this; - // @ts-ignore - if (!(_this instanceof FrictionJoint)) { - return new FrictionJoint(def, bodyA, bodyB, anchor); - } - def = options(def, DEFAULTS$9); - _this = _super.call(this, def, bodyA, bodyB) || this; - bodyA = _this.m_bodyA; - bodyB = _this.m_bodyB; - _this.m_type = FrictionJoint.TYPE; - _this.m_localAnchorA = Vec2.clone(anchor ? bodyA.getLocalPoint(anchor) : def.localAnchorA || Vec2.zero()); - _this.m_localAnchorB = Vec2.clone(anchor ? bodyB.getLocalPoint(anchor) : def.localAnchorB || Vec2.zero()); - // Solver shared - _this.m_linearImpulse = Vec2.zero(); - _this.m_angularImpulse = 0.0; - _this.m_maxForce = def.maxForce; - _this.m_maxTorque = def.maxTorque; - return _this; - // Point-to-point constraint - // Cdot = v2 - v1 - // = v2 + cross(w2, r2) - v1 - cross(w1, r1) - // J = [-I -r1_skew I r2_skew ] - // Identity used: - // w k % (rx i + ry j) = w * (-ry i + rx j) - // Angle constraint - // Cdot = w2 - w1 - // J = [0 0 -1 0 0 1] - // K = invI1 + invI2 + return TimeStep; +}()); +// reuse +var s_subStep = new TimeStep(); +/** + * Contact impulses for reporting. Impulses are used instead of forces because + * sub-step forces may approach infinity for rigid body collisions. These match + * up one-to-one with the contact points in Manifold. + */ +var ContactImpulse = /** @class */ (function () { + function ContactImpulse(contact) { + this.contact = contact; + this.normals = []; + this.tangents = []; + } + Object.defineProperty(ContactImpulse.prototype, "normalImpulses", { + get: function () { + var contact = this.contact; + var normals = this.normals; + normals.length = 0; + for (var p = 0; p < contact.v_points.length; ++p) { + normals.push(contact.v_points[p].normalImpulse); + } + return normals; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ContactImpulse.prototype, "tangentImpulses", { + get: function () { + var contact = this.contact; + var tangents = this.tangents; + tangents.length = 0; + for (var p = 0; p < contact.v_points.length; ++p) { + tangents.push(contact.v_points[p].tangentImpulse); + } + return tangents; + }, + enumerable: false, + configurable: true + }); + return ContactImpulse; +}()); +/** + * Finds and solves islands. An island is a connected subset of the world. + */ +var Solver = /** @class */ (function () { + function Solver(world) { + this.m_world = world; + this.m_stack = []; + this.m_bodies = []; + this.m_contacts = []; + this.m_joints = []; + } + Solver.prototype.clear = function () { + this.m_stack.length = 0; + this.m_bodies.length = 0; + this.m_contacts.length = 0; + this.m_joints.length = 0; + }; + Solver.prototype.addBody = function (body) { + this.m_bodies.push(body); + // why? + // body.c_position.c.setZero(); + // body.c_position.a = 0; + // body.c_velocity.v.setZero(); + // body.c_velocity.w = 0; + }; + Solver.prototype.addContact = function (contact) { + // false && console.assert(contact instanceof Contact, 'Not a Contact!', contact); + this.m_contacts.push(contact); + }; + Solver.prototype.addJoint = function (joint) { + this.m_joints.push(joint); + }; + Solver.prototype.solveWorld = function (step) { + var world = this.m_world; + // Clear all the island flags. + for (var b = world.m_bodyList; b; b = b.m_next) { + b.m_islandFlag = false; } - /** @internal */ - FrictionJoint.prototype._serialize = function () { - return { - type: this.m_type, - bodyA: this.m_bodyA, - bodyB: this.m_bodyB, - collideConnected: this.m_collideConnected, - maxForce: this.m_maxForce, - maxTorque: this.m_maxTorque, - localAnchorA: this.m_localAnchorA, - localAnchorB: this.m_localAnchorB, - }; - }; - /** @internal */ - FrictionJoint._deserialize = function (data, world, restore) { - data = __assign({}, data); - data.bodyA = restore(Body, data.bodyA, world); - data.bodyB = restore(Body, data.bodyB, world); - var joint = new FrictionJoint(data); - return joint; - }; - /** @internal */ - FrictionJoint.prototype._setAnchors = function (def) { - if (def.anchorA) { - this.m_localAnchorA.setVec2(this.m_bodyA.getLocalPoint(def.anchorA)); - } - else if (def.localAnchorA) { - this.m_localAnchorA.setVec2(def.localAnchorA); + for (var c = world.m_contactList; c; c = c.m_next) { + c.m_islandFlag = false; + } + for (var j = world.m_jointList; j; j = j.m_next) { + j.m_islandFlag = false; + } + // Build and simulate all awake islands. + var stack = this.m_stack; + for (var seed = world.m_bodyList; seed; seed = seed.m_next) { + if (seed.m_islandFlag) { + continue; + } + if (seed.isAwake() == false || seed.isActive() == false) { + continue; + } + // The seed can be dynamic or kinematic. + if (seed.isStatic()) { + continue; + } + // Reset island and stack. + this.clear(); + stack.push(seed); + seed.m_islandFlag = true; + // Perform a depth first search (DFS) on the constraint graph. + while (stack.length > 0) { + // Grab the next body off the stack and add it to the island. + var b = stack.pop(); + this.addBody(b); + // Make sure the body is awake. + b.setAwake(true); + // To keep islands as small as possible, we don't + // propagate islands across static bodies. + if (b.isStatic()) { + continue; + } + // Search all contacts connected to this body. + for (var ce = b.m_contactList; ce; ce = ce.next) { + var contact = ce.contact; + // Has this contact already been added to an island? + if (contact.m_islandFlag) { + continue; + } + // Is this contact solid and touching? + if (contact.isEnabled() == false || contact.isTouching() == false) { + continue; + } + // Skip sensors. + var sensorA = contact.m_fixtureA.m_isSensor; + var sensorB = contact.m_fixtureB.m_isSensor; + if (sensorA || sensorB) { + continue; + } + this.addContact(contact); + contact.m_islandFlag = true; + var other = ce.other; + // Was the other body already added to this island? + if (other.m_islandFlag) { + continue; + } + // false && console.assert(stack.length < world.m_bodyCount); + stack.push(other); + other.m_islandFlag = true; + } + // Search all joints connect to this body. + for (var je = b.m_jointList; je; je = je.next) { + if (je.joint.m_islandFlag == true) { + continue; + } + var other = je.other; + // Don't simulate joints connected to inactive bodies. + if (other.isActive() == false) { + continue; + } + this.addJoint(je.joint); + je.joint.m_islandFlag = true; + if (other.m_islandFlag) { + continue; + } + // false && console.assert(stack.length < world.m_bodyCount); + stack.push(other); + other.m_islandFlag = true; + } } - if (def.anchorB) { - this.m_localAnchorB.setVec2(this.m_bodyB.getLocalPoint(def.anchorB)); + this.solveIsland(step); + // Post solve cleanup. + for (var i = 0; i < this.m_bodies.length; ++i) { + // Allow static bodies to participate in other islands. + // TODO: are they added at all? + var b = this.m_bodies[i]; + if (b.isStatic()) { + b.m_islandFlag = false; + } } - else if (def.localAnchorB) { - this.m_localAnchorB.setVec2(def.localAnchorB); + } + }; + Solver.prototype.solveIsland = function (step) { + // B2: Island Solve + var world = this.m_world; + var gravity = world.m_gravity; + var allowSleep = world.m_allowSleep; + var h = step.dt; + // Integrate velocities and apply damping. Initialize the body state. + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + var c = Vec2.clone(body.m_sweep.c); + var a = body.m_sweep.a; + var v = Vec2.clone(body.m_linearVelocity); + var w = body.m_angularVelocity; + // Store positions for continuous collision. + body.m_sweep.c0.setVec2(body.m_sweep.c); + body.m_sweep.a0 = body.m_sweep.a; + if (body.isDynamic()) { + // Integrate velocities. + v.addMul(h * body.m_gravityScale, gravity); + v.addMul(h * body.m_invMass, body.m_force); + w += h * body.m_invI * body.m_torque; + /** + *
+ * Apply damping. + * ODE: dv/dt + c * v = 0 + * Solution: v(t) = v0 * exp(-c * t) + * Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt) + * v2 = exp(-c * dt) * v1 + * Pade approximation: + * v2 = v1 * 1 / (1 + c * dt) + *+ */ + v.mul(1.0 / (1.0 + h * body.m_linearDamping)); + w *= 1.0 / (1.0 + h * body.m_angularDamping); + } + body.c_position.c = c; + body.c_position.a = a; + body.c_velocity.v = v; + body.c_velocity.w = w; + } + for (var i = 0; i < this.m_contacts.length; ++i) { + var contact = this.m_contacts[i]; + contact.initConstraint(step); + } + for (var i = 0; i < this.m_contacts.length; ++i) { + var contact = this.m_contacts[i]; + contact.initVelocityConstraint(step); + } + if (step.warmStarting) { + // Warm start. + for (var i = 0; i < this.m_contacts.length; ++i) { + var contact = this.m_contacts[i]; + contact.warmStartConstraint(step); } - }; - /** - * The local anchor point relative to bodyA's origin. - */ - FrictionJoint.prototype.getLocalAnchorA = function () { - return this.m_localAnchorA; - }; - /** - * The local anchor point relative to bodyB's origin. - */ - FrictionJoint.prototype.getLocalAnchorB = function () { - return this.m_localAnchorB; - }; - /** - * Set the maximum friction force in N. - */ - FrictionJoint.prototype.setMaxForce = function (force) { - this.m_maxForce = force; - }; - /** - * Get the maximum friction force in N. - */ - FrictionJoint.prototype.getMaxForce = function () { - return this.m_maxForce; - }; - /** - * Set the maximum friction torque in N*m. - */ - FrictionJoint.prototype.setMaxTorque = function (torque) { - this.m_maxTorque = torque; - }; - /** - * Get the maximum friction torque in N*m. - */ - FrictionJoint.prototype.getMaxTorque = function () { - return this.m_maxTorque; - }; - /** - * Get the anchor point on bodyA in world coordinates. - */ - FrictionJoint.prototype.getAnchorA = function () { - return this.m_bodyA.getWorldPoint(this.m_localAnchorA); - }; - /** - * Get the anchor point on bodyB in world coordinates. - */ - FrictionJoint.prototype.getAnchorB = function () { - return this.m_bodyB.getWorldPoint(this.m_localAnchorB); - }; - /** - * Get the reaction force on bodyB at the joint anchor in Newtons. - */ - FrictionJoint.prototype.getReactionForce = function (inv_dt) { - return Vec2.mulNumVec2(inv_dt, this.m_linearImpulse); - }; - /** - * Get the reaction torque on bodyB in N*m. - */ - FrictionJoint.prototype.getReactionTorque = function (inv_dt) { - return inv_dt * this.m_angularImpulse; - }; - FrictionJoint.prototype.initVelocityConstraints = function (step) { - this.m_localCenterA = this.m_bodyA.m_sweep.localCenter; - this.m_localCenterB = this.m_bodyB.m_sweep.localCenter; - this.m_invMassA = this.m_bodyA.m_invMass; - this.m_invMassB = this.m_bodyB.m_invMass; - this.m_invIA = this.m_bodyA.m_invI; - this.m_invIB = this.m_bodyB.m_invI; - var aA = this.m_bodyA.c_position.a; - var vA = this.m_bodyA.c_velocity.v; - var wA = this.m_bodyA.c_velocity.w; - var aB = this.m_bodyB.c_position.a; - var vB = this.m_bodyB.c_velocity.v; - var wB = this.m_bodyB.c_velocity.w; - var qA = Rot.neo(aA); - var qB = Rot.neo(aB); - // Compute the effective mass matrix. - this.m_rA = Rot.mulVec2(qA, Vec2.sub(this.m_localAnchorA, this.m_localCenterA)); - this.m_rB = Rot.mulVec2(qB, Vec2.sub(this.m_localAnchorB, this.m_localCenterB)); - // J = [-I -r1_skew I r2_skew] - // [ 0 -1 0 1] - // r_skew = [-ry; rx] - // Matlab - // K = [ mA+r1y^2*iA+mB+r2y^2*iB, -r1y*iA*r1x-r2y*iB*r2x, -r1y*iA-r2y*iB] - // [ -r1y*iA*r1x-r2y*iB*r2x, mA+r1x^2*iA+mB+r2x^2*iB, r1x*iA+r2x*iB] - // [ -r1y*iA-r2y*iB, r1x*iA+r2x*iB, iA+iB] - var mA = this.m_invMassA; - var mB = this.m_invMassB; - var iA = this.m_invIA; - var iB = this.m_invIB; - var K = new Mat22(); - K.ex.x = mA + mB + iA * this.m_rA.y * this.m_rA.y + iB * this.m_rB.y - * this.m_rB.y; - K.ex.y = -iA * this.m_rA.x * this.m_rA.y - iB * this.m_rB.x * this.m_rB.y; - K.ey.x = K.ex.y; - K.ey.y = mA + mB + iA * this.m_rA.x * this.m_rA.x + iB * this.m_rB.x - * this.m_rB.x; - this.m_linearMass = K.getInverse(); - this.m_angularMass = iA + iB; - if (this.m_angularMass > 0.0) { - this.m_angularMass = 1.0 / this.m_angularMass; + } + for (var i = 0; i < this.m_joints.length; ++i) { + var joint = this.m_joints[i]; + joint.initVelocityConstraints(step); + } + // Solve velocity constraints + for (var i = 0; i < step.velocityIterations; ++i) { + for (var j = 0; j < this.m_joints.length; ++j) { + var joint = this.m_joints[j]; + joint.solveVelocityConstraints(step); } - if (step.warmStarting) { - // Scale impulses to support a variable time step. - this.m_linearImpulse.mul(step.dtRatio); - this.m_angularImpulse *= step.dtRatio; - var P = Vec2.neo(this.m_linearImpulse.x, this.m_linearImpulse.y); - vA.subMul(mA, P); - wA -= iA * (Vec2.crossVec2Vec2(this.m_rA, P) + this.m_angularImpulse); - vB.addMul(mB, P); - wB += iB * (Vec2.crossVec2Vec2(this.m_rB, P) + this.m_angularImpulse); + for (var j = 0; j < this.m_contacts.length; ++j) { + var contact = this.m_contacts[j]; + contact.solveVelocityConstraint(step); } - else { - this.m_linearImpulse.setZero(); - this.m_angularImpulse = 0.0; + } + // Store impulses for warm starting + for (var i = 0; i < this.m_contacts.length; ++i) { + var contact = this.m_contacts[i]; + contact.storeConstraintImpulses(step); + } + // Integrate positions + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + var c = Vec2.clone(body.c_position.c); + var a = body.c_position.a; + var v = Vec2.clone(body.c_velocity.v); + var w = body.c_velocity.w; + // Check for large velocities + var translation = Vec2.mulNumVec2(h, v); + if (Vec2.lengthSquared(translation) > Settings.maxTranslationSquared) { + var ratio = Settings.maxTranslation / translation.length(); + v.mul(ratio); + } + var rotation = h * w; + if (rotation * rotation > Settings.maxRotationSquared) { + var ratio = Settings.maxRotation / math.abs(rotation); + w *= ratio; + } + // Integrate + c.addMul(h, v); + a += h * w; + body.c_position.c.setVec2(c); + body.c_position.a = a; + body.c_velocity.v.setVec2(v); + body.c_velocity.w = w; + } + // Solve position constraints + var positionSolved = false; + for (var i = 0; i < step.positionIterations; ++i) { + var minSeparation = 0.0; + for (var j = 0; j < this.m_contacts.length; ++j) { + var contact = this.m_contacts[j]; + var separation = contact.solvePositionConstraint(step); + minSeparation = math.min(minSeparation, separation); + } + // We can't expect minSpeparation >= -Settings.linearSlop because we don't + // push the separation above -Settings.linearSlop. + var contactsOkay = minSeparation >= -3.0 * Settings.linearSlop; + var jointsOkay = true; + for (var j = 0; j < this.m_joints.length; ++j) { + var joint = this.m_joints[j]; + var jointOkay = joint.solvePositionConstraints(step); + jointsOkay = jointsOkay && jointOkay; + } + if (contactsOkay && jointsOkay) { + // Exit early if the position errors are small. + positionSolved = true; + break; } - this.m_bodyA.c_velocity.v = vA; - this.m_bodyA.c_velocity.w = wA; - this.m_bodyB.c_velocity.v = vB; - this.m_bodyB.c_velocity.w = wB; - }; - FrictionJoint.prototype.solveVelocityConstraints = function (step) { - var vA = this.m_bodyA.c_velocity.v; - var wA = this.m_bodyA.c_velocity.w; - var vB = this.m_bodyB.c_velocity.v; - var wB = this.m_bodyB.c_velocity.w; - var mA = this.m_invMassA; - var mB = this.m_invMassB; - var iA = this.m_invIA; - var iB = this.m_invIB; - var h = step.dt; // float - // Solve angular friction - { - var Cdot = wB - wA; // float - var impulse = -this.m_angularMass * Cdot; // float - var oldImpulse = this.m_angularImpulse; // float - var maxImpulse = h * this.m_maxTorque; // float - this.m_angularImpulse = math$1.clamp(this.m_angularImpulse + impulse, -maxImpulse, maxImpulse); - impulse = this.m_angularImpulse - oldImpulse; - wA -= iA * impulse; - wB += iB * impulse; + } + // Copy state buffers back to the bodies + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + body.m_sweep.c.setVec2(body.c_position.c); + body.m_sweep.a = body.c_position.a; + body.m_linearVelocity.setVec2(body.c_velocity.v); + body.m_angularVelocity = body.c_velocity.w; + body.synchronizeTransform(); + } + this.postSolveIsland(); + if (allowSleep) { + var minSleepTime = Infinity; + var linTolSqr = Settings.linearSleepToleranceSqr; + var angTolSqr = Settings.angularSleepToleranceSqr; + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + if (body.isStatic()) { + continue; + } + if ((body.m_autoSleepFlag == false) + || (body.m_angularVelocity * body.m_angularVelocity > angTolSqr) + || (Vec2.lengthSquared(body.m_linearVelocity) > linTolSqr)) { + body.m_sleepTime = 0.0; + minSleepTime = 0.0; + } + else { + body.m_sleepTime += h; + minSleepTime = math.min(minSleepTime, body.m_sleepTime); + } } - // Solve linear friction - { - var Cdot = Vec2.sub(Vec2.add(vB, Vec2.crossNumVec2(wB, this.m_rB)), Vec2.add(vA, Vec2.crossNumVec2(wA, this.m_rA))); // Vec2 - var impulse = Vec2.neg(Mat22.mulVec2(this.m_linearMass, Cdot)); // Vec2 - var oldImpulse = this.m_linearImpulse; // Vec2 - this.m_linearImpulse.add(impulse); - var maxImpulse = h * this.m_maxForce; // float - if (this.m_linearImpulse.lengthSquared() > maxImpulse * maxImpulse) { - this.m_linearImpulse.normalize(); - this.m_linearImpulse.mul(maxImpulse); + if (minSleepTime >= Settings.timeToSleep && positionSolved) { + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + body.setAwake(false); } - impulse = Vec2.sub(this.m_linearImpulse, oldImpulse); - vA.subMul(mA, impulse); - wA -= iA * Vec2.crossVec2Vec2(this.m_rA, impulse); - vB.addMul(mB, impulse); - wB += iB * Vec2.crossVec2Vec2(this.m_rB, impulse); } - this.m_bodyA.c_velocity.v = vA; - this.m_bodyA.c_velocity.w = wA; - this.m_bodyB.c_velocity.v = vB; - this.m_bodyB.c_velocity.w = wB; - }; - /** - * This returns true if the position errors are within tolerance. - */ - FrictionJoint.prototype.solvePositionConstraints = function (step) { - return true; - }; - FrictionJoint.TYPE = 'friction-joint'; - return FrictionJoint; - }(Joint)); - - /* - * Planck.js - * The MIT License - * Copyright (c) 2021 Erin Catto, Ali Shakiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - /** - * A 3-by-3 matrix. Stored in column-major order. - */ - var Mat33 = /** @class */ (function () { - function Mat33(a, b, c) { - if (typeof a === 'object' && a !== null) { - this.ex = Vec3.clone(a); - this.ey = Vec3.clone(b); - this.ez = Vec3.clone(c); + } + }; + /** + * Find TOI contacts and solve them. + */ + Solver.prototype.solveWorldTOI = function (step) { + var world = this.m_world; + if (world.m_stepComplete) { + for (var b = world.m_bodyList; b; b = b.m_next) { + b.m_islandFlag = false; + b.m_sweep.alpha0 = 0.0; } - else { - this.ex = Vec3.zero(); - this.ey = Vec3.zero(); - this.ez = Vec3.zero(); + for (var c = world.m_contactList; c; c = c.m_next) { + // Invalidate TOI + c.m_toiFlag = false; + c.m_islandFlag = false; + c.m_toiCount = 0; + c.m_toi = 1.0; } } - /** @internal */ - Mat33.prototype.toString = function () { - return JSON.stringify(this); - }; - Mat33.isValid = function (obj) { - if (obj === null || typeof obj === 'undefined') { - return false; + // Find TOI events and solve them. + while (true) { + // Find the first TOI. + var minContact = null; // Contact + var minAlpha = 1.0; + for (var c = world.m_contactList; c; c = c.m_next) { + // Is this contact disabled? + if (c.isEnabled() == false) { + continue; + } + // Prevent excessive sub-stepping. + if (c.m_toiCount > Settings.maxSubSteps) { + continue; + } + var alpha = 1.0; + if (c.m_toiFlag) { + // This contact has a valid cached TOI. + alpha = c.m_toi; + } + else { + var fA_1 = c.getFixtureA(); + var fB_1 = c.getFixtureB(); + // Is there a sensor? + if (fA_1.isSensor() || fB_1.isSensor()) { + continue; + } + var bA_1 = fA_1.getBody(); + var bB_1 = fB_1.getBody(); + var activeA = bA_1.isAwake() && !bA_1.isStatic(); + var activeB = bB_1.isAwake() && !bB_1.isStatic(); + // Is at least one body active (awake and dynamic or kinematic)? + if (activeA == false && activeB == false) { + continue; + } + var collideA = bA_1.isBullet() || !bA_1.isDynamic(); + var collideB = bB_1.isBullet() || !bB_1.isDynamic(); + // Are these two non-bullet dynamic bodies? + if (collideA == false && collideB == false) { + continue; + } + // Compute the TOI for this contact. + // Put the sweeps onto the same time interval. + var alpha0 = bA_1.m_sweep.alpha0; + if (bA_1.m_sweep.alpha0 < bB_1.m_sweep.alpha0) { + alpha0 = bB_1.m_sweep.alpha0; + bA_1.m_sweep.advance(alpha0); + } + else if (bB_1.m_sweep.alpha0 < bA_1.m_sweep.alpha0) { + alpha0 = bA_1.m_sweep.alpha0; + bB_1.m_sweep.advance(alpha0); + } + var indexA = c.getChildIndexA(); + var indexB = c.getChildIndexB(); + bA_1.m_sweep; + bB_1.m_sweep; + // Compute the time of impact in interval [0, minTOI] + var input = new TOIInput(); // TODO: reuse + input.proxyA.set(fA_1.getShape(), indexA); + input.proxyB.set(fB_1.getShape(), indexB); + input.sweepA.set(bA_1.m_sweep); + input.sweepB.set(bB_1.m_sweep); + input.tMax = 1.0; + var output = new TOIOutput(); // TODO: reuse + TimeOfImpact(output, input); + // Beta is the fraction of the remaining portion of the [time?]. + var beta = output.t; + if (output.state == TOIOutputState.e_touching) { + alpha = math.min(alpha0 + (1.0 - alpha0) * beta, 1.0); + } + else { + alpha = 1.0; + } + c.m_toi = alpha; + c.m_toiFlag = true; + } + if (alpha < minAlpha) { + // This is the minimum TOI found so far. + minContact = c; + minAlpha = alpha; + } } - return Vec3.isValid(obj.ex) && Vec3.isValid(obj.ey) && Vec3.isValid(obj.ez); - }; - Mat33.assert = function (o) { - return; - }; - /** - * Set this matrix to all zeros. - */ - Mat33.prototype.setZero = function () { - this.ex.setZero(); - this.ey.setZero(); - this.ez.setZero(); - return this; - }; - /** - * Solve A * x = b, where b is a column vector. This is more efficient than - * computing the inverse in one-shot cases. - */ - Mat33.prototype.solve33 = function (v) { - var det = Vec3.dot(this.ex, Vec3.cross(this.ey, this.ez)); - if (det !== 0.0) { - det = 1.0 / det; + if (minContact == null || 1.0 - 10.0 * math.EPSILON < minAlpha) { + // No more TOI events. Done! + world.m_stepComplete = true; + break; } - var r = new Vec3(); - r.x = det * Vec3.dot(v, Vec3.cross(this.ey, this.ez)); - r.y = det * Vec3.dot(this.ex, Vec3.cross(v, this.ez)); - r.z = det * Vec3.dot(this.ex, Vec3.cross(this.ey, v)); - return r; - }; - /** - * Solve A * x = b, where b is a column vector. This is more efficient than - * computing the inverse in one-shot cases. Solve only the upper 2-by-2 matrix - * equation. - */ - Mat33.prototype.solve22 = function (v) { - var a11 = this.ex.x; - var a12 = this.ey.x; - var a21 = this.ex.y; - var a22 = this.ey.y; - var det = a11 * a22 - a12 * a21; - if (det !== 0.0) { - det = 1.0 / det; + // Advance the bodies to the TOI. + var fA = minContact.getFixtureA(); + var fB = minContact.getFixtureB(); + var bA = fA.getBody(); + var bB = fB.getBody(); + var backup1 = bA.m_sweep.clone(); + var backup2 = bB.m_sweep.clone(); + bA.advance(minAlpha); + bB.advance(minAlpha); + // The TOI contact likely has some new contact points. + minContact.update(world); + minContact.m_toiFlag = false; + ++minContact.m_toiCount; + // Is the contact solid? + if (minContact.isEnabled() == false || minContact.isTouching() == false) { + // Restore the sweeps. + minContact.setEnabled(false); + bA.m_sweep.set(backup1); + bB.m_sweep.set(backup2); + bA.synchronizeTransform(); + bB.synchronizeTransform(); + continue; + } + bA.setAwake(true); + bB.setAwake(true); + // Build the island + this.clear(); + this.addBody(bA); + this.addBody(bB); + this.addContact(minContact); + bA.m_islandFlag = true; + bB.m_islandFlag = true; + minContact.m_islandFlag = true; + // Get contacts on bodyA and bodyB. + var bodies = [bA, bB]; + for (var i = 0; i < bodies.length; ++i) { + var body = bodies[i]; + if (body.isDynamic()) { + for (var ce = body.m_contactList; ce; ce = ce.next) { + // if (this.m_bodyCount == this.m_bodyCapacity) { break; } + // if (this.m_contactCount == this.m_contactCapacity) { break; } + var contact = ce.contact; + // Has this contact already been added to the island? + if (contact.m_islandFlag) { + continue; + } + // Only add if either is static, kinematic or bullet. + var other = ce.other; + if (other.isDynamic() && !body.isBullet() && !other.isBullet()) { + continue; + } + // Skip sensors. + var sensorA = contact.m_fixtureA.m_isSensor; + var sensorB = contact.m_fixtureB.m_isSensor; + if (sensorA || sensorB) { + continue; + } + // Tentatively advance the body to the TOI. + var backup = other.m_sweep.clone(); + if (other.m_islandFlag == false) { + other.advance(minAlpha); + } + // Update the contact points + contact.update(world); + // Was the contact disabled by the user? + // Are there contact points? + if (contact.isEnabled() == false || contact.isTouching() == false) { + other.m_sweep.set(backup); + other.synchronizeTransform(); + continue; + } + // Add the contact to the island + contact.m_islandFlag = true; + this.addContact(contact); + // Has the other body already been added to the island? + if (other.m_islandFlag) { + continue; + } + // Add the other body to the island. + other.m_islandFlag = true; + if (!other.isStatic()) { + other.setAwake(true); + } + this.addBody(other); + } + } } - var r = Vec2.zero(); - r.x = det * (a22 * v.x - a12 * v.y); - r.y = det * (a11 * v.y - a21 * v.x); - return r; - }; - /** - * Get the inverse of this matrix as a 2-by-2. Returns the zero matrix if - * singular. - */ - Mat33.prototype.getInverse22 = function (M) { - var a = this.ex.x; - var b = this.ey.x; - var c = this.ex.y; - var d = this.ey.y; - var det = a * d - b * c; - if (det !== 0.0) { - det = 1.0 / det; + s_subStep.reset((1.0 - minAlpha) * step.dt); + s_subStep.dtRatio = 1.0; + s_subStep.positionIterations = 20; + s_subStep.velocityIterations = step.velocityIterations; + s_subStep.warmStarting = false; + this.solveIslandTOI(s_subStep, bA, bB); + // Reset island flags and synchronize broad-phase proxies. + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + body.m_islandFlag = false; + if (!body.isDynamic()) { + continue; + } + body.synchronizeFixtures(); + // Invalidate all contact TOIs on this displaced body. + for (var ce = body.m_contactList; ce; ce = ce.next) { + ce.contact.m_toiFlag = false; + ce.contact.m_islandFlag = false; + } } - M.ex.x = det * d; - M.ey.x = -det * b; - M.ex.z = 0.0; - M.ex.y = -det * c; - M.ey.y = det * a; - M.ey.z = 0.0; - M.ez.x = 0.0; - M.ez.y = 0.0; - M.ez.z = 0.0; - }; - /** - * Get the symmetric inverse of this matrix as a 3-by-3. Returns the zero matrix - * if singular. - */ - Mat33.prototype.getSymInverse33 = function (M) { - var det = Vec3.dot(this.ex, Vec3.cross(this.ey, this.ez)); - if (det !== 0.0) { - det = 1.0 / det; + // Commit fixture proxy movements to the broad-phase so that new contacts + // are created. + // Also, some contacts can be destroyed. + world.findNewContacts(); + if (world.m_subStepping) { + world.m_stepComplete = false; + break; } - var a11 = this.ex.x; - var a12 = this.ey.x; - var a13 = this.ez.x; - var a22 = this.ey.y; - var a23 = this.ez.y; - var a33 = this.ez.z; - M.ex.x = det * (a22 * a33 - a23 * a23); - M.ex.y = det * (a13 * a23 - a12 * a33); - M.ex.z = det * (a12 * a23 - a13 * a22); - M.ey.x = M.ex.y; - M.ey.y = det * (a11 * a33 - a13 * a13); - M.ey.z = det * (a13 * a12 - a11 * a23); - M.ez.x = M.ex.z; - M.ez.y = M.ey.z; - M.ez.z = det * (a11 * a22 - a12 * a12); - }; - // tslint:disable-next-line:typedef - Mat33.mul = function (a, b) { - if (b && 'z' in b && 'y' in b && 'x' in b) { - var x = a.ex.x * b.x + a.ey.x * b.y + a.ez.x * b.z; - var y = a.ex.y * b.x + a.ey.y * b.y + a.ez.y * b.z; - var z = a.ex.z * b.x + a.ey.z * b.y + a.ez.z * b.z; - return new Vec3(x, y, z); + } + }; + Solver.prototype.solveIslandTOI = function (subStep, toiA, toiB) { + this.m_world; + // Initialize the body state. + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + body.c_position.c.setVec2(body.m_sweep.c); + body.c_position.a = body.m_sweep.a; + body.c_velocity.v.setVec2(body.m_linearVelocity); + body.c_velocity.w = body.m_angularVelocity; + } + for (var i = 0; i < this.m_contacts.length; ++i) { + var contact = this.m_contacts[i]; + contact.initConstraint(subStep); + } + // Solve position constraints. + for (var i = 0; i < subStep.positionIterations; ++i) { + var minSeparation = 0.0; + for (var j = 0; j < this.m_contacts.length; ++j) { + var contact = this.m_contacts[j]; + var separation = contact.solvePositionConstraintTOI(subStep, toiA, toiB); + minSeparation = math.min(minSeparation, separation); + } + // We can't expect minSpeparation >= -Settings.linearSlop because we don't + // push the separation above -Settings.linearSlop. + var contactsOkay = minSeparation >= -1.5 * Settings.linearSlop; + if (contactsOkay) { + break; } - else if (b && 'y' in b && 'x' in b) { - var x = a.ex.x * b.x + a.ey.x * b.y; - var y = a.ex.y * b.x + a.ey.y * b.y; - return Vec2.neo(x, y); + } + var i, c; + // Leap of faith to new safe state. + toiA.m_sweep.c0.setVec2(toiA.c_position.c); + toiA.m_sweep.a0 = toiA.c_position.a; + toiB.m_sweep.c0.setVec2(toiB.c_position.c); + toiB.m_sweep.a0 = toiB.c_position.a; + // No warm starting is needed for TOI events because warm + // starting impulses were applied in the discrete solver. + for (var i = 0; i < this.m_contacts.length; ++i) { + var contact = this.m_contacts[i]; + contact.initVelocityConstraint(subStep); + } + // Solve velocity constraints. + for (var i = 0; i < subStep.velocityIterations; ++i) { + for (var j = 0; j < this.m_contacts.length; ++j) { + var contact = this.m_contacts[j]; + contact.solveVelocityConstraint(subStep); } - }; - Mat33.mulVec3 = function (a, b) { - var x = a.ex.x * b.x + a.ey.x * b.y + a.ez.x * b.z; - var y = a.ex.y * b.x + a.ey.y * b.y + a.ez.y * b.z; - var z = a.ex.z * b.x + a.ey.z * b.y + a.ez.z * b.z; - return new Vec3(x, y, z); - }; - Mat33.mulVec2 = function (a, b) { - var x = a.ex.x * b.x + a.ey.x * b.y; - var y = a.ex.y * b.x + a.ey.y * b.y; + } + // Don't store the TOI contact forces for warm starting + // because they can be quite large. + var h = subStep.dt; + // Integrate positions + for (var i = 0; i < this.m_bodies.length; ++i) { + var body = this.m_bodies[i]; + var c = Vec2.clone(body.c_position.c); + var a = body.c_position.a; + var v = Vec2.clone(body.c_velocity.v); + var w = body.c_velocity.w; + // Check for large velocities + var translation = Vec2.mulNumVec2(h, v); + if (Vec2.dot(translation, translation) > Settings.maxTranslationSquared) { + var ratio = Settings.maxTranslation / translation.length(); + v.mul(ratio); + } + var rotation = h * w; + if (rotation * rotation > Settings.maxRotationSquared) { + var ratio = Settings.maxRotation / math.abs(rotation); + w *= ratio; + } + // Integrate + c.addMul(h, v); + a += h * w; + body.c_position.c = c; + body.c_position.a = a; + body.c_velocity.v = v; + body.c_velocity.w = w; + // Sync bodies + body.m_sweep.c = c; + body.m_sweep.a = a; + body.m_linearVelocity = v; + body.m_angularVelocity = w; + body.synchronizeTransform(); + } + this.postSolveIsland(); + }; + /** @internal */ + Solver.prototype.postSolveIsland = function () { + for (var c = 0; c < this.m_contacts.length; ++c) { + var contact = this.m_contacts[c]; + this.m_world.postSolve(contact, contact.m_impulse); + } + }; + return Solver; +}()); +// @ts-ignore +Solver.TimeStep = TimeStep; + +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * A 2-by-2 matrix. Stored in column-major order. + */ +var Mat22 = /** @class */ (function () { + // tslint:disable-next-line:typedef + function Mat22(a, b, c, d) { + if (typeof a === 'object' && a !== null) { + this.ex = Vec2.clone(a); + this.ey = Vec2.clone(b); + } + else if (typeof a === 'number') { + this.ex = Vec2.neo(a, c); + this.ey = Vec2.neo(b, d); + } + else { + this.ex = Vec2.zero(); + this.ey = Vec2.zero(); + } + } + /** @internal */ + Mat22.prototype.toString = function () { + return JSON.stringify(this); + }; + Mat22.isValid = function (obj) { + if (obj === null || typeof obj === 'undefined') { + return false; + } + return Vec2.isValid(obj.ex) && Vec2.isValid(obj.ey); + }; + Mat22.assert = function (o) { + }; + // tslint:disable-next-line:typedef + Mat22.prototype.set = function (a, b, c, d) { + if (typeof a === 'number' && typeof b === 'number' && typeof c === 'number' + && typeof d === 'number') { + this.ex.setNum(a, c); + this.ey.setNum(b, d); + } + else if (typeof a === 'object' && typeof b === 'object') { + this.ex.setVec2(a); + this.ey.setVec2(b); + } + else if (typeof a === 'object') { + this.ex.setVec2(a.ex); + this.ey.setVec2(a.ey); + } + else ; + }; + Mat22.prototype.setIdentity = function () { + this.ex.x = 1.0; + this.ey.x = 0.0; + this.ex.y = 0.0; + this.ey.y = 1.0; + }; + Mat22.prototype.setZero = function () { + this.ex.x = 0.0; + this.ey.x = 0.0; + this.ex.y = 0.0; + this.ey.y = 0.0; + }; + Mat22.prototype.getInverse = function () { + var a = this.ex.x; + var b = this.ey.x; + var c = this.ex.y; + var d = this.ey.y; + var det = a * d - b * c; + if (det !== 0.0) { + det = 1.0 / det; + } + var imx = new Mat22(); + imx.ex.x = det * d; + imx.ey.x = -det * b; + imx.ex.y = -det * c; + imx.ey.y = det * a; + return imx; + }; + /** + * Solve A * x = b, where b is a column vector. This is more efficient than + * computing the inverse in one-shot cases. + */ + Mat22.prototype.solve = function (v) { + var a = this.ex.x; + var b = this.ey.x; + var c = this.ex.y; + var d = this.ey.y; + var det = a * d - b * c; + if (det !== 0.0) { + det = 1.0 / det; + } + var w = Vec2.zero(); + w.x = det * (d * v.x - b * v.y); + w.y = det * (a * v.y - c * v.x); + return w; + }; + // tslint:disable-next-line:typedef + Mat22.mul = function (mx, v) { + if (v && 'x' in v && 'y' in v) { + var x = mx.ex.x * v.x + mx.ey.x * v.y; + var y = mx.ex.y * v.x + mx.ey.y * v.y; return Vec2.neo(x, y); - }; - Mat33.add = function (a, b) { - return new Mat33(Vec3.add(a.ex, b.ex), Vec3.add(a.ey, b.ey), Vec3.add(a.ez, b.ez)); - }; - return Mat33; - }()); + } + else if (v && 'ex' in v && 'ey' in v) { // Mat22 + // return new Mat22(Vec2.mul(mx, v.ex), Vec2.mul(mx, v.ey)); + var a = mx.ex.x * v.ex.x + mx.ey.x * v.ex.y; + var b = mx.ex.x * v.ey.x + mx.ey.x * v.ey.y; + var c = mx.ex.y * v.ex.x + mx.ey.y * v.ex.y; + var d = mx.ex.y * v.ey.x + mx.ey.y * v.ey.y; + return new Mat22(a, b, c, d); + } + }; + Mat22.mulVec2 = function (mx, v) { + var x = mx.ex.x * v.x + mx.ey.x * v.y; + var y = mx.ex.y * v.x + mx.ey.y * v.y; + return Vec2.neo(x, y); + }; + Mat22.mulMat22 = function (mx, v) { + // return new Mat22(Vec2.mul(mx, v.ex), Vec2.mul(mx, v.ey)); + var a = mx.ex.x * v.ex.x + mx.ey.x * v.ex.y; + var b = mx.ex.x * v.ey.x + mx.ey.x * v.ey.y; + var c = mx.ex.y * v.ex.x + mx.ey.y * v.ex.y; + var d = mx.ex.y * v.ey.x + mx.ey.y * v.ey.y; + return new Mat22(a, b, c, d); + }; + // tslint:disable-next-line:typedef + Mat22.mulT = function (mx, v) { + if (v && 'x' in v && 'y' in v) { // Vec2 + return Vec2.neo(Vec2.dot(v, mx.ex), Vec2.dot(v, mx.ey)); + } + else if (v && 'ex' in v && 'ey' in v) { // Mat22 + var c1 = Vec2.neo(Vec2.dot(mx.ex, v.ex), Vec2.dot(mx.ey, v.ex)); + var c2 = Vec2.neo(Vec2.dot(mx.ex, v.ey), Vec2.dot(mx.ey, v.ey)); + return new Mat22(c1, c2); + } + }; + Mat22.mulTVec2 = function (mx, v) { + return Vec2.neo(Vec2.dot(v, mx.ex), Vec2.dot(v, mx.ey)); + }; + Mat22.mulTMat22 = function (mx, v) { + var c1 = Vec2.neo(Vec2.dot(mx.ex, v.ex), Vec2.dot(mx.ey, v.ex)); + var c2 = Vec2.neo(Vec2.dot(mx.ex, v.ey), Vec2.dot(mx.ey, v.ey)); + return new Mat22(c1, c2); + }; + Mat22.abs = function (mx) { + return new Mat22(Vec2.abs(mx.ex), Vec2.abs(mx.ey)); + }; + Mat22.add = function (mx1, mx2) { + return new Mat22(Vec2.add(mx1.ex, mx2.ex), Vec2.add(mx1.ey, mx2.ey)); + }; + return Mat22; +}()); - /* - * Planck.js - * The MIT License - * Copyright (c) 2021 Erin Catto, Ali Shakiba - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - var inactiveLimit$2 = 0; - var atLowerLimit$1 = 1; - var atUpperLimit$2 = 2; - var equalLimits$1 = 3; - var DEFAULTS$8 = { - lowerAngle: 0.0, - upperAngle: 0.0, - maxMotorTorque: 0.0, - motorSpeed: 0.0, - enableLimit: false, - enableMotor: false - }; - /** - * A revolute joint constrains two bodies to share a common point while they are - * free to rotate about the point. The relative rotation about the shared point - * is the joint angle. You can limit the relative rotation with a joint limit - * that specifies a lower and upper angle. You can use a motor to drive the - * relative rotation about the shared point. A maximum motor torque is provided - * so that infinite forces are not generated. - */ - var RevoluteJoint = /** @class */ (function (_super) { - __extends(RevoluteJoint, _super); - // @ts-ignore - function RevoluteJoint(def, bodyA, bodyB, anchor) { - var _this = this; - // @ts-ignore - if (!(_this instanceof RevoluteJoint)) { - return new RevoluteJoint(def, bodyA, bodyB, anchor); - } - def = options(def, DEFAULTS$8); - _this = _super.call(this, def, bodyA, bodyB) || this; - // effective mass for point-to-point constraint. - /** @internal */ _this.m_mass = new Mat33(); - /** @internal */ _this.m_limitState = inactiveLimit$2; // TODO enum - bodyA = _this.m_bodyA; - bodyB = _this.m_bodyB; - _this.m_type = RevoluteJoint.TYPE; - _this.m_localAnchorA = Vec2.clone(anchor ? bodyA.getLocalPoint(anchor) : def.localAnchorA || Vec2.zero()); - _this.m_localAnchorB = Vec2.clone(anchor ? bodyB.getLocalPoint(anchor) : def.localAnchorB || Vec2.zero()); - _this.m_referenceAngle = math$1.isFinite(def.referenceAngle) ? def.referenceAngle : bodyB.getAngle() - bodyA.getAngle(); - _this.m_impulse = new Vec3(); - _this.m_motorImpulse = 0.0; - _this.m_lowerAngle = def.lowerAngle; - _this.m_upperAngle = def.upperAngle; - _this.m_maxMotorTorque = def.maxMotorTorque; - _this.m_motorSpeed = def.motorSpeed; - _this.m_enableLimit = def.enableLimit; - _this.m_enableMotor = def.enableMotor; - return _this; - // Point-to-point constraint - // C = p2 - p1 - // Cdot = v2 - v1 - // = v2 + cross(w2, r2) - v1 - cross(w1, r1) - // J = [-I -r1_skew I r2_skew ] - // Identity used: - // w k % (rx i + ry j) = w * (-ry i + rx j) - // Motor constraint - // Cdot = w2 - w1 - // J = [0 0 -1 0 0 1] - // K = invI1 + invI2 +/* + * Planck.js + * The MIT License + * Copyright (c) 2021 Erin Catto, Ali Shakiba + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +var ManifoldType; +(function (ManifoldType) { + ManifoldType[ManifoldType["e_circles"] = 0] = "e_circles"; + ManifoldType[ManifoldType["e_faceA"] = 1] = "e_faceA"; + ManifoldType[ManifoldType["e_faceB"] = 2] = "e_faceB"; +})(ManifoldType || (ManifoldType = {})); +var ContactFeatureType; +(function (ContactFeatureType) { + ContactFeatureType[ContactFeatureType["e_vertex"] = 0] = "e_vertex"; + ContactFeatureType[ContactFeatureType["e_face"] = 1] = "e_face"; +})(ContactFeatureType || (ContactFeatureType = {})); +/** + * This is used for determining the state of contact points. + */ +var PointState; +(function (PointState) { + /** Point does not exist */ + PointState[PointState["nullState"] = 0] = "nullState"; + /** Point was added in the update */ + PointState[PointState["addState"] = 1] = "addState"; + /** Point persisted across the update */ + PointState[PointState["persistState"] = 2] = "persistState"; + /** Point was removed in the update */ + PointState[PointState["removeState"] = 3] = "removeState"; +})(PointState || (PointState = {})); +/** + * Used for computing contact manifolds. + */ +var ClipVertex = /** @class */ (function () { + function ClipVertex() { + this.v = Vec2.zero(); + this.id = new ContactID(); + } + ClipVertex.prototype.set = function (o) { + this.v.setVec2(o.v); + this.id.set(o.id); + }; + return ClipVertex; +}()); +/** + * A manifold for two touching convex shapes. Manifolds are created in `evaluate` + * method of Contact subclasses. + * + * Supported manifold types are e_faceA or e_faceB for clip point versus plane + * with radius and e_circles point versus point with radius. + * + * We store contacts in this way so that position correction can account for + * movement, which is critical for continuous physics. All contact scenarios + * must be expressed in one of these types. This structure is stored across time + * steps, so we keep it small. + * + * @prop type e_circle, e_faceA, e_faceB + * @prop localPoint Usage depends on manifold type:
\n * Apply damping.\n * ODE: dv/dt + c * v = 0\n * Solution: v(t) = v0 * exp(-c * t)\n * Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt)\n * v2 = exp(-c * dt) * v1\n * Pade approximation:\n * v2 = v1 * 1 / (1 + c * dt)\n *\n */\n v.mul(1.0 / (1.0 + h * body.m_linearDamping));\n w *= 1.0 / (1.0 + h * body.m_angularDamping);\n }\n\n body.c_position.c = c;\n body.c_position.a = a;\n body.c_velocity.v = v;\n body.c_velocity.w = w;\n }\n\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.initConstraint(step);\n }\n\n _DEBUG && this.printBodies('M: ');\n\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.initVelocityConstraint(step);\n }\n\n _DEBUG && this.printBodies('R: ');\n\n if (step.warmStarting) {\n // Warm start.\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.warmStartConstraint(step);\n }\n }\n\n _DEBUG && this.printBodies('Q: ');\n\n for (let i = 0; i < this.m_joints.length; ++i) {\n const joint = this.m_joints[i];\n joint.initVelocityConstraints(step);\n }\n\n _DEBUG && this.printBodies('E: ');\n\n // Solve velocity constraints\n for (let i = 0; i < step.velocityIterations; ++i) {\n for (let j = 0; j < this.m_joints.length; ++j) {\n const joint = this.m_joints[j];\n joint.solveVelocityConstraints(step);\n }\n\n for (let j = 0; j < this.m_contacts.length; ++j) {\n const contact = this.m_contacts[j];\n contact.solveVelocityConstraint(step);\n }\n }\n\n _DEBUG && this.printBodies('D: ');\n\n // Store impulses for warm starting\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.storeConstraintImpulses(step);\n }\n\n _DEBUG && this.printBodies('C: ');\n\n // Integrate positions\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n\n const c = Vec2.clone(body.c_position.c);\n let a = body.c_position.a;\n const v = Vec2.clone(body.c_velocity.v);\n let w = body.c_velocity.w;\n\n // Check for large velocities\n const translation = Vec2.mulNumVec2(h, v);\n if (Vec2.lengthSquared(translation) > Settings.maxTranslationSquared) {\n const ratio = Settings.maxTranslation / translation.length();\n v.mul(ratio);\n }\n\n const rotation = h * w;\n if (rotation * rotation > Settings.maxRotationSquared) {\n const ratio = Settings.maxRotation / Math.abs(rotation);\n w *= ratio;\n }\n\n // Integrate\n c.addMul(h, v);\n a += h * w;\n\n body.c_position.c.setVec2(c);\n body.c_position.a = a;\n body.c_velocity.v.setVec2(v);\n body.c_velocity.w = w;\n }\n\n _DEBUG && this.printBodies('B: ');\n\n // Solve position constraints\n let positionSolved = false;\n for (let i = 0; i < step.positionIterations; ++i) {\n let minSeparation = 0.0;\n for (let j = 0; j < this.m_contacts.length; ++j) {\n const contact = this.m_contacts[j];\n const separation = contact.solvePositionConstraint(step);\n minSeparation = Math.min(minSeparation, separation);\n }\n // We can't expect minSpeparation >= -Settings.linearSlop because we don't\n // push the separation above -Settings.linearSlop.\n const contactsOkay = minSeparation >= -3.0 * Settings.linearSlop;\n\n let jointsOkay = true;\n for (let j = 0; j < this.m_joints.length; ++j) {\n const joint = this.m_joints[j];\n const jointOkay = joint.solvePositionConstraints(step);\n jointsOkay = jointsOkay && jointOkay;\n }\n\n if (contactsOkay && jointsOkay) {\n // Exit early if the position errors are small.\n positionSolved = true;\n break;\n }\n }\n\n _DEBUG && this.printBodies('L: ');\n\n // Copy state buffers back to the bodies\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n\n body.m_sweep.c.setVec2(body.c_position.c);\n body.m_sweep.a = body.c_position.a;\n body.m_linearVelocity.setVec2(body.c_velocity.v);\n body.m_angularVelocity = body.c_velocity.w;\n body.synchronizeTransform();\n }\n\n this.postSolveIsland();\n\n if (allowSleep) {\n let minSleepTime = Infinity;\n\n const linTolSqr = Settings.linearSleepToleranceSqr;\n const angTolSqr = Settings.angularSleepToleranceSqr;\n\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n if (body.isStatic()) {\n continue;\n }\n\n if ((body.m_autoSleepFlag == false)\n || (body.m_angularVelocity * body.m_angularVelocity > angTolSqr)\n || (Vec2.lengthSquared(body.m_linearVelocity) > linTolSqr)) {\n body.m_sleepTime = 0.0;\n minSleepTime = 0.0;\n } else {\n body.m_sleepTime += h;\n minSleepTime = Math.min(minSleepTime, body.m_sleepTime);\n }\n }\n\n if (minSleepTime >= Settings.timeToSleep && positionSolved) {\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n body.setAwake(false);\n }\n }\n }\n }\n\n /** @internal */\n printBodies(tag: string): void {\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const b = this.m_bodies[i];\n common.debug(tag, b.c_position.a, b.c_position.c.x, b.c_position.c.y, b.c_velocity.w, b.c_velocity.v.x, b.c_velocity.v.y);\n }\n }\n\n /**\n * Find TOI contacts and solve them.\n */\n solveWorldTOI(step: TimeStep): void {\n const world = this.m_world;\n\n if (world.m_stepComplete) {\n for (let b = world.m_bodyList; b; b = b.m_next) {\n b.m_islandFlag = false;\n b.m_sweep.alpha0 = 0.0;\n }\n\n for (let c = world.m_contactList; c; c = c.m_next) {\n // Invalidate TOI\n c.m_toiFlag = false;\n c.m_islandFlag = false;\n c.m_toiCount = 0;\n c.m_toi = 1.0;\n }\n }\n\n // Find TOI events and solve them.\n while (true) {\n // Find the first TOI.\n let minContact = null; // Contact\n let minAlpha = 1.0;\n\n for (let c = world.m_contactList; c; c = c.m_next) {\n // Is this contact disabled?\n if (c.isEnabled() == false) {\n continue;\n }\n\n // Prevent excessive sub-stepping.\n if (c.m_toiCount > Settings.maxSubSteps) {\n continue;\n }\n\n let alpha = 1.0;\n if (c.m_toiFlag) {\n // This contact has a valid cached TOI.\n alpha = c.m_toi;\n } else {\n const fA = c.getFixtureA();\n const fB = c.getFixtureB();\n\n // Is there a sensor?\n if (fA.isSensor() || fB.isSensor()) {\n continue;\n }\n\n const bA = fA.getBody();\n const bB = fB.getBody();\n\n _ASSERT && common.assert(bA.isDynamic() || bB.isDynamic());\n\n const activeA = bA.isAwake() && !bA.isStatic();\n const activeB = bB.isAwake() && !bB.isStatic();\n\n // Is at least one body active (awake and dynamic or kinematic)?\n if (activeA == false && activeB == false) {\n continue;\n }\n\n const collideA = bA.isBullet() || !bA.isDynamic();\n const collideB = bB.isBullet() || !bB.isDynamic();\n\n // Are these two non-bullet dynamic bodies?\n if (collideA == false && collideB == false) {\n continue;\n }\n\n // Compute the TOI for this contact.\n // Put the sweeps onto the same time interval.\n let alpha0 = bA.m_sweep.alpha0;\n\n if (bA.m_sweep.alpha0 < bB.m_sweep.alpha0) {\n alpha0 = bB.m_sweep.alpha0;\n bA.m_sweep.advance(alpha0);\n } else if (bB.m_sweep.alpha0 < bA.m_sweep.alpha0) {\n alpha0 = bA.m_sweep.alpha0;\n bB.m_sweep.advance(alpha0);\n }\n\n _ASSERT && common.assert(alpha0 < 1.0);\n\n const indexA = c.getChildIndexA();\n const indexB = c.getChildIndexB();\n\n const sweepA = bA.m_sweep;\n const sweepB = bB.m_sweep;\n\n // Compute the time of impact in interval [0, minTOI]\n const input = new TOIInput(); // TODO: reuse\n input.proxyA.set(fA.getShape(), indexA);\n input.proxyB.set(fB.getShape(), indexB);\n input.sweepA.set(bA.m_sweep);\n input.sweepB.set(bB.m_sweep);\n input.tMax = 1.0;\n\n const output = new TOIOutput(); // TODO: reuse\n TimeOfImpact(output, input);\n\n // Beta is the fraction of the remaining portion of the [time?].\n const beta = output.t;\n if (output.state == TOIOutputState.e_touching) {\n alpha = Math.min(alpha0 + (1.0 - alpha0) * beta, 1.0);\n } else {\n alpha = 1.0;\n }\n\n c.m_toi = alpha;\n c.m_toiFlag = true;\n }\n\n if (alpha < minAlpha) {\n // This is the minimum TOI found so far.\n minContact = c;\n minAlpha = alpha;\n }\n }\n\n if (minContact == null || 1.0 - 10.0 * Math.EPSILON < minAlpha) {\n // No more TOI events. Done!\n world.m_stepComplete = true;\n break;\n }\n\n // Advance the bodies to the TOI.\n const fA = minContact.getFixtureA();\n const fB = minContact.getFixtureB();\n const bA = fA.getBody();\n const bB = fB.getBody();\n\n const backup1 = bA.m_sweep.clone();\n const backup2 = bB.m_sweep.clone();\n\n bA.advance(minAlpha);\n bB.advance(minAlpha);\n\n // The TOI contact likely has some new contact points.\n minContact.update(world);\n minContact.m_toiFlag = false;\n ++minContact.m_toiCount;\n\n // Is the contact solid?\n if (minContact.isEnabled() == false || minContact.isTouching() == false) {\n // Restore the sweeps.\n minContact.setEnabled(false);\n bA.m_sweep.set(backup1);\n bB.m_sweep.set(backup2);\n bA.synchronizeTransform();\n bB.synchronizeTransform();\n continue;\n }\n\n bA.setAwake(true);\n bB.setAwake(true);\n\n // Build the island\n this.clear();\n this.addBody(bA);\n this.addBody(bB);\n this.addContact(minContact);\n\n bA.m_islandFlag = true;\n bB.m_islandFlag = true;\n minContact.m_islandFlag = true;\n\n // Get contacts on bodyA and bodyB.\n const bodies = [ bA, bB ];\n for (let i = 0; i < bodies.length; ++i) {\n const body = bodies[i];\n if (body.isDynamic()) {\n for (let ce = body.m_contactList; ce; ce = ce.next) {\n // if (this.m_bodyCount == this.m_bodyCapacity) { break; }\n // if (this.m_contactCount == this.m_contactCapacity) { break; }\n\n const contact = ce.contact;\n\n // Has this contact already been added to the island?\n if (contact.m_islandFlag) {\n continue;\n }\n\n // Only add if either is static, kinematic or bullet.\n const other = ce.other;\n if (other.isDynamic() && !body.isBullet() && !other.isBullet()) {\n continue;\n }\n\n // Skip sensors.\n const sensorA = contact.m_fixtureA.m_isSensor;\n const sensorB = contact.m_fixtureB.m_isSensor;\n if (sensorA || sensorB) {\n continue;\n }\n\n // Tentatively advance the body to the TOI.\n const backup = other.m_sweep.clone();\n if (other.m_islandFlag == false) {\n other.advance(minAlpha);\n }\n\n // Update the contact points\n contact.update(world);\n\n // Was the contact disabled by the user?\n // Are there contact points?\n if (contact.isEnabled() == false || contact.isTouching() == false) {\n other.m_sweep.set(backup);\n other.synchronizeTransform();\n continue;\n }\n\n // Add the contact to the island\n contact.m_islandFlag = true;\n this.addContact(contact);\n\n // Has the other body already been added to the island?\n if (other.m_islandFlag) {\n continue;\n }\n\n // Add the other body to the island.\n other.m_islandFlag = true;\n\n if (!other.isStatic()) {\n other.setAwake(true);\n }\n\n this.addBody(other);\n }\n }\n }\n\n s_subStep.reset((1.0 - minAlpha) * step.dt);\n s_subStep.dtRatio = 1.0;\n s_subStep.positionIterations = 20;\n s_subStep.velocityIterations = step.velocityIterations;\n s_subStep.warmStarting = false;\n\n this.solveIslandTOI(s_subStep, bA, bB);\n\n // Reset island flags and synchronize broad-phase proxies.\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n body.m_islandFlag = false;\n\n if (!body.isDynamic()) {\n continue;\n }\n\n body.synchronizeFixtures();\n\n // Invalidate all contact TOIs on this displaced body.\n for (let ce = body.m_contactList; ce; ce = ce.next) {\n ce.contact.m_toiFlag = false;\n ce.contact.m_islandFlag = false;\n }\n }\n\n // Commit fixture proxy movements to the broad-phase so that new contacts\n // are created.\n // Also, some contacts can be destroyed.\n world.findNewContacts();\n\n if (world.m_subStepping) {\n world.m_stepComplete = false;\n break;\n }\n }\n\n if (_DEBUG) for (let b = world.m_bodyList; b; b = b.m_next) {\n const c = b.m_sweep.c;\n const a = b.m_sweep.a;\n const v = b.m_linearVelocity;\n const w = b.m_angularVelocity;\n }\n }\n\n solveIslandTOI(subStep: TimeStep, toiA: Body, toiB: Body): void {\n const world = this.m_world;\n\n // Initialize the body state.\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n body.c_position.c.setVec2(body.m_sweep.c);\n body.c_position.a = body.m_sweep.a;\n body.c_velocity.v.setVec2(body.m_linearVelocity);\n body.c_velocity.w = body.m_angularVelocity;\n }\n\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.initConstraint(subStep);\n }\n\n // Solve position constraints.\n for (let i = 0; i < subStep.positionIterations; ++i) {\n let minSeparation = 0.0;\n for (let j = 0; j < this.m_contacts.length; ++j) {\n const contact = this.m_contacts[j];\n const separation = contact.solvePositionConstraintTOI(subStep, toiA, toiB);\n minSeparation = Math.min(minSeparation, separation);\n }\n // We can't expect minSpeparation >= -Settings.linearSlop because we don't\n // push the separation above -Settings.linearSlop.\n const contactsOkay = minSeparation >= -1.5 * Settings.linearSlop;\n if (contactsOkay) {\n break;\n }\n }\n\n if (false) {\n // Is the new position really safe?\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const c = this.m_contacts[i];\n const fA = c.getFixtureA();\n const fB = c.getFixtureB();\n\n const bA = fA.getBody();\n const bB = fB.getBody();\n\n const indexA = c.getChildIndexA();\n const indexB = c.getChildIndexB();\n\n const input = new DistanceInput();\n input.proxyA.set(fA.getShape(), indexA);\n input.proxyB.set(fB.getShape(), indexB);\n input.transformA = bA.getTransform();\n input.transformB = bB.getTransform();\n input.useRadii = false;\n\n const output = new DistanceOutput();\n const cache = new SimplexCache();\n Distance(output, cache, input);\n\n if (output.distance == 0 || cache.count == 3) {\n cache.count += 0;\n }\n }\n }\n\n // Leap of faith to new safe state.\n toiA.m_sweep.c0.setVec2(toiA.c_position.c);\n toiA.m_sweep.a0 = toiA.c_position.a;\n toiB.m_sweep.c0.setVec2(toiB.c_position.c);\n toiB.m_sweep.a0 = toiB.c_position.a;\n\n // No warm starting is needed for TOI events because warm\n // starting impulses were applied in the discrete solver.\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.initVelocityConstraint(subStep);\n }\n\n // Solve velocity constraints.\n for (let i = 0; i < subStep.velocityIterations; ++i) {\n for (let j = 0; j < this.m_contacts.length; ++j) {\n const contact = this.m_contacts[j];\n contact.solveVelocityConstraint(subStep);\n }\n }\n\n // Don't store the TOI contact forces for warm starting\n // because they can be quite large.\n\n const h = subStep.dt;\n\n // Integrate positions\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n\n const c = Vec2.clone(body.c_position.c);\n let a = body.c_position.a;\n const v = Vec2.clone(body.c_velocity.v);\n let w = body.c_velocity.w;\n\n // Check for large velocities\n const translation = Vec2.mulNumVec2(h, v);\n if (Vec2.dot(translation, translation) > Settings.maxTranslationSquared) {\n const ratio = Settings.maxTranslation / translation.length();\n v.mul(ratio);\n }\n\n const rotation = h * w;\n if (rotation * rotation > Settings.maxRotationSquared) {\n const ratio = Settings.maxRotation / Math.abs(rotation);\n w *= ratio;\n }\n\n // Integrate\n c.addMul(h, v);\n a += h * w;\n\n body.c_position.c = c;\n body.c_position.a = a;\n body.c_velocity.v = v;\n body.c_velocity.w = w;\n\n // Sync bodies\n body.m_sweep.c = c;\n body.m_sweep.a = a;\n body.m_linearVelocity = v;\n body.m_angularVelocity = w;\n body.synchronizeTransform();\n }\n\n this.postSolveIsland();\n }\n\n /** @internal */\n postSolveIsland(): void {\n for (let c = 0; c < this.m_contacts.length; ++c) {\n const contact = this.m_contacts[c];\n this.m_world.postSolve(contact, contact.m_impulse);\n }\n }\n}\n","/*\n * Planck.js\n * The MIT License\n * Copyright (c) 2021 Erin Catto, Ali Shakiba\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport options from '../util/options';\nimport common from '../util/common';\nimport Vec2 from '../common/Vec2';\nimport BroadPhase from '../collision/BroadPhase';\nimport Solver, { ContactImpulse, TimeStep } from './Solver';\nimport Body, { BodyDef } from './Body';\nimport Joint from './Joint';\nimport Contact from './Contact';\nimport AABB, { RayCastInput, RayCastOutput } from \"../collision/AABB\";\nimport Fixture, { FixtureProxy } from \"./Fixture\";\nimport Manifold from \"../collision/Manifold\";\n\n\nconst _ASSERT = typeof ASSERT === 'undefined' ? false : ASSERT;\n\n\n/**\n * @prop gravity [{ x : 0, y : 0}]\n * @prop allowSleep [true]\n * @prop warmStarting [true]\n * @prop continuousPhysics [true]\n * @prop subStepping [false]\n * @prop blockSolve [true]\n * @prop velocityIterations [8] For the velocity constraint solver.\n * @prop positionIterations [3] For the position constraint solver.\n */\nexport interface WorldDef {\n gravity?: Vec2;\n allowSleep?: boolean;\n warmStarting?: boolean;\n continuousPhysics?: boolean;\n subStepping?: boolean;\n blockSolve?: boolean;\n velocityIterations?: number;\n positionIterations?: number;\n}\n\nconst WorldDefDefault: WorldDef = {\n gravity : Vec2.zero(),\n allowSleep : true,\n warmStarting : true,\n continuousPhysics : true,\n subStepping : false,\n blockSolve : true,\n velocityIterations : 8,\n positionIterations : 3\n};\n\n/**\n * Callback function for ray casts, see {@link World.rayCast}.\n *\n * Called for each fixture found in the query. You control how the ray cast\n * proceeds by returning a float: return -1: ignore this fixture and continue\n * return 0: terminate the ray cast return fraction: clip the ray to this point\n * return 1: don't clip the ray and continue\n *\n * @param fixture The fixture hit by the ray\n * @param point The point of initial intersection\n * @param normal The normal vector at the point of intersection\n * @param fraction\n *\n * @return -1 to filter, 0 to terminate, fraction to clip the ray for closest hit, 1 to continue\n */\nexport type WorldRayCastCallback = (fixture: Fixture, point: Vec2, normal: Vec2, fraction: number) => number;\n\n/**\n * Called for each fixture found in the query AABB. It may return `false` to terminate the query.\n */\nexport type WorldAABBQueryCallback = (fixture: Fixture) => boolean;\n\nexport default class World {\n /** @internal */ m_solver: Solver;\n /** @internal */ m_broadPhase: BroadPhase;\n /** @internal */ m_contactList: Contact | null;\n /** @internal */ m_contactCount: number;\n /** @internal */ m_bodyList: Body | null;\n /** @internal */ m_bodyCount: number;\n /** @internal */ m_jointList: Joint | null;\n /** @internal */ m_jointCount: number;\n /** @internal */ m_stepComplete: boolean;\n /** @internal */ m_allowSleep: boolean;\n /** @internal */ m_gravity: Vec2;\n /** @internal */ m_clearForces: boolean;\n /** @internal */ m_newFixture: boolean;\n /** @internal */ m_locked: boolean;\n /** @internal */ m_warmStarting: boolean;\n /** @internal */ m_continuousPhysics: boolean;\n /** @internal */ m_subStepping: boolean;\n /** @internal */ m_blockSolve: boolean;\n /** @internal */ m_velocityIterations: number;\n /** @internal */ m_positionIterations: number;\n /** @internal */ m_t: number;\n\n // TODO\n /** @internal */ _listeners: {\n [key: string]: any[]\n };\n\n /**\n * @param def World definition or gravity vector.\n */\n constructor(def?: WorldDef | Vec2 | null) {\n if (!(this instanceof World)) {\n return new World(def);\n }\n\n if (def && Vec2.isValid(def)) {\n def = { gravity: def as Vec2 };\n }\n\n def = options(def, WorldDefDefault) as WorldDef;\n\n this.m_solver = new Solver(this);\n\n this.m_broadPhase = new BroadPhase();\n\n this.m_contactList = null;\n this.m_contactCount = 0;\n\n this.m_bodyList = null;\n this.m_bodyCount = 0;\n\n this.m_jointList = null;\n this.m_jointCount = 0;\n\n this.m_stepComplete = true;\n\n this.m_allowSleep = def.allowSleep;\n this.m_gravity = Vec2.clone(def.gravity);\n\n this.m_clearForces = true;\n this.m_newFixture = false;\n this.m_locked = false;\n\n // These are for debugging the solver.\n this.m_warmStarting = def.warmStarting;\n this.m_continuousPhysics = def.continuousPhysics;\n this.m_subStepping = def.subStepping;\n\n this.m_blockSolve = def.blockSolve;\n this.m_velocityIterations = def.velocityIterations;\n this.m_positionIterations = def.positionIterations;\n\n this.m_t = 0;\n }\n\n /** @internal */\n _serialize(): object {\n const bodies = [];\n const joints = [];\n\n for (let b = this.getBodyList(); b; b = b.getNext()) {\n bodies.push(b);\n }\n\n for (let j = this.getJointList(); j; j = j.getNext()) {\n // @ts-ignore\n if (typeof j._serialize === 'function') {\n joints.push(j);\n }\n }\n\n return {\n gravity: this.m_gravity,\n bodies,\n joints,\n };\n }\n\n /** @internal */\n static _deserialize(data: any, context: any, restore: any): World {\n if (!data) {\n return new World();\n }\n\n const world = new World(data.gravity);\n\n if (data.bodies) {\n for (let i = data.bodies.length - 1; i >= 0; i -= 1) {\n world._addBody(restore(Body, data.bodies[i], world));\n }\n }\n\n if (data.joints) {\n for (let i = data.joints.length - 1; i >= 0; i--) {\n world.createJoint(restore(Joint, data.joints[i], world));\n }\n }\n\n return world;\n }\n\n /**\n * Get the world body list. With the returned body, use Body.getNext to get the\n * next body in the world list. A null body indicates the end of the list.\n *\n * @return the head of the world body list.\n */\n getBodyList(): Body | null {\n return this.m_bodyList;\n }\n\n /**\n * Get the world joint list. With the returned joint, use Joint.getNext to get\n * the next joint in the world list. A null joint indicates the end of the list.\n *\n * @return the head of the world joint list.\n */\n getJointList(): Joint | null {\n return this.m_jointList;\n }\n\n /**\n * Get the world contact list. With the returned contact, use Contact.getNext to\n * get the next contact in the world list. A null contact indicates the end of\n * the list.\n *\n * Warning: contacts are created and destroyed in the middle of a time step.\n * Use ContactListener to avoid missing contacts.\n *\n * @return the head of the world contact list.\n */\n getContactList(): Contact | null {\n return this.m_contactList;\n }\n\n getBodyCount(): number {\n return this.m_bodyCount;\n }\n\n getJointCount(): number {\n return this.m_jointCount;\n }\n\n /**\n * Get the number of contacts (each may have 0 or more contact points).\n */\n getContactCount(): number {\n return this.m_contactCount;\n }\n\n /**\n * Change the global gravity vector.\n */\n setGravity(gravity: Vec2): void {\n this.m_gravity = gravity;\n }\n\n /**\n * Get the global gravity vector.\n */\n getGravity(): Vec2 {\n return this.m_gravity;\n }\n\n /**\n * Is the world locked (in the middle of a time step).\n */\n isLocked(): boolean {\n return this.m_locked;\n }\n\n /**\n * Enable/disable sleep.\n */\n setAllowSleeping(flag: boolean): void {\n if (flag == this.m_allowSleep) {\n return;\n }\n\n this.m_allowSleep = flag;\n if (this.m_allowSleep == false) {\n for (let b = this.m_bodyList; b; b = b.m_next) {\n b.setAwake(true);\n }\n }\n }\n\n getAllowSleeping(): boolean {\n return this.m_allowSleep;\n }\n\n /**\n * Enable/disable warm starting. For testing.\n */\n setWarmStarting(flag: boolean): void {\n this.m_warmStarting = flag;\n }\n\n getWarmStarting(): boolean {\n return this.m_warmStarting;\n }\n\n /**\n * Enable/disable continuous physics. For testing.\n */\n setContinuousPhysics(flag: boolean): void {\n this.m_continuousPhysics = flag;\n }\n\n getContinuousPhysics(): boolean {\n return this.m_continuousPhysics;\n }\n\n /**\n * Enable/disable single stepped continuous physics. For testing.\n */\n setSubStepping(flag: boolean): void {\n this.m_subStepping = flag;\n }\n\n getSubStepping(): boolean {\n return this.m_subStepping;\n }\n\n /**\n * Set flag to control automatic clearing of forces after each time step.\n */\n setAutoClearForces(flag: boolean): void {\n this.m_clearForces = flag;\n }\n\n /**\n * Get the flag that controls automatic clearing of forces after each time step.\n */\n getAutoClearForces(): boolean {\n return this.m_clearForces;\n }\n\n /**\n * Manually clear the force buffer on all bodies. By default, forces are cleared\n * automatically after each call to step. The default behavior is modified by\n * calling setAutoClearForces. The purpose of this function is to support\n * sub-stepping. Sub-stepping is often used to maintain a fixed sized time step\n * under a variable frame-rate. When you perform sub-stepping you will disable\n * auto clearing of forces and instead call clearForces after all sub-steps are\n * complete in one pass of your game loop.\n *\n * See {@link World.setAutoClearForces}\n */\n clearForces(): void {\n for (let body = this.m_bodyList; body; body = body.getNext()) {\n body.m_force.setZero();\n body.m_torque = 0.0;\n }\n }\n\n /**\n * Query the world for all fixtures that potentially overlap the provided AABB.\n *\n * @param aabb The query box.\n * @param callback Called for each fixture found in the query AABB. It may return `false` to terminate the query.\n */\n queryAABB(aabb: AABB, callback: WorldAABBQueryCallback): void {\n _ASSERT && common.assert(typeof callback === 'function');\n const broadPhase = this.m_broadPhase;\n this.m_broadPhase.query(aabb, function(proxyId: number): boolean { // TODO GC\n const proxy = broadPhase.getUserData(proxyId);\n return callback(proxy.fixture);\n });\n }\n\n /**\n * Ray-cast the world for all fixtures in the path of the ray. Your callback\n * controls whether you get the closest point, any point, or n-points. The\n * ray-cast ignores shapes that contain the starting point.\n *\n * @param point1 The ray starting point\n * @param point2 The ray ending point\n * @param callback A user implemented callback function.\n */\n rayCast(point1: Vec2, point2: Vec2, callback: WorldRayCastCallback): void {\n _ASSERT && common.assert(typeof callback === 'function');\n const broadPhase = this.m_broadPhase;\n\n this.m_broadPhase.rayCast({\n maxFraction : 1.0,\n p1 : point1,\n p2 : point2\n }, function(input: RayCastInput, proxyId: number): number { // TODO GC\n const proxy = broadPhase.getUserData(proxyId);\n const fixture = proxy.fixture;\n const index = proxy.childIndex;\n // @ts-ignore\n const output: RayCastOutput = {}; // TODO GC\n const hit = fixture.rayCast(output, input, index);\n if (hit) {\n const fraction = output.fraction;\n const point = Vec2.add(Vec2.mulNumVec2((1.0 - fraction), input.p1), Vec2.mulNumVec2(fraction, input.p2));\n return callback(fixture, point, output.normal, fraction);\n }\n return input.maxFraction;\n });\n }\n\n /**\n * Get the number of broad-phase proxies.\n */\n getProxyCount(): number {\n return this.m_broadPhase.getProxyCount();\n }\n\n /**\n * Get the height of broad-phase dynamic tree.\n */\n getTreeHeight(): number {\n return this.m_broadPhase.getTreeHeight();\n }\n\n /**\n * Get the balance of broad-phase dynamic tree.\n */\n getTreeBalance(): number {\n return this.m_broadPhase.getTreeBalance();\n }\n\n /**\n * Get the quality metric of broad-phase dynamic tree. The smaller the better.\n * The minimum is 1.\n */\n getTreeQuality(): number {\n return this.m_broadPhase.getTreeQuality();\n }\n\n /**\n * Shift the world origin. Useful for large worlds. The body shift formula is:\n * position -= newOrigin\n *\n * @param newOrigin The new origin with respect to the old origin\n */\n shiftOrigin(newOrigin: Vec2): void {\n _ASSERT && common.assert(this.m_locked == false);\n if (this.m_locked) {\n return;\n }\n\n for (let b = this.m_bodyList; b; b = b.m_next) {\n b.m_xf.p.sub(newOrigin);\n b.m_sweep.c0.sub(newOrigin);\n b.m_sweep.c.sub(newOrigin);\n }\n\n for (let j = this.m_jointList; j; j = j.m_next) {\n j.shiftOrigin(newOrigin);\n }\n\n this.m_broadPhase.shiftOrigin(newOrigin);\n }\n\n /**\n * @internal Used for deserialize.\n */\n _addBody(body: Body): void {\n _ASSERT && common.assert(this.isLocked() === false);\n if (this.isLocked()) {\n return;\n }\n\n // Add to world doubly linked list.\n body.m_prev = null;\n body.m_next = this.m_bodyList;\n if (this.m_bodyList) {\n this.m_bodyList.m_prev = body;\n }\n this.m_bodyList = body;\n ++this.m_bodyCount;\n }\n\n /**\n * Create a rigid body given a definition. No reference to the definition is\n * retained.\n *\n * Warning: This function is locked during callbacks.\n */\n createBody(def?: BodyDef): Body;\n createBody(position: Vec2, angle?: number): Body;\n // tslint:disable-next-line:typedef\n createBody(arg1?, arg2?) {\n _ASSERT && common.assert(this.isLocked() == false);\n if (this.isLocked()) {\n return null;\n }\n\n let def: BodyDef = {};\n if (!arg1) {\n } else if (Vec2.isValid(arg1)) {\n def = { position : arg1, angle: arg2 };\n } else if (typeof arg1 === 'object') {\n def = arg1;\n }\n\n const body = new Body(this, def);\n this._addBody(body);\n return body;\n }\n\n createDynamicBody(def?: BodyDef): Body;\n createDynamicBody(position: Vec2, angle?: number): Body;\n // tslint:disable-next-line:typedef\n createDynamicBody(arg1?, arg2?) {\n let def: BodyDef = {};\n if (!arg1) {\n } else if (Vec2.isValid(arg1)) {\n def = { position : arg1, angle: arg2 };\n } else if (typeof arg1 === 'object') {\n def = arg1;\n }\n def.type = 'dynamic';\n return this.createBody(def);\n }\n\n createKinematicBody(def?: BodyDef): Body;\n createKinematicBody(position: Vec2, angle?: number): Body;\n // tslint:disable-next-line:typedef\n createKinematicBody(arg1?, arg2?) {\n let def: BodyDef = {};\n if (!arg1) {\n } else if (Vec2.isValid(arg1)) {\n def = { position : arg1, angle: arg2 };\n } else if (typeof arg1 === 'object') {\n def = arg1;\n }\n def.type = 'kinematic';\n return this.createBody(def);\n }\n\n /**\n * Destroy a rigid body given a definition. No reference to the definition is\n * retained.\n *\n * Warning: This automatically deletes all associated shapes and joints.\n *\n * Warning: This function is locked during callbacks.\n */\n destroyBody(b: Body): boolean {\n _ASSERT && common.assert(this.m_bodyCount > 0);\n _ASSERT && common.assert(this.isLocked() == false);\n if (this.isLocked()) {\n return;\n }\n\n if (b.m_destroyed) {\n return false;\n }\n\n // Delete the attached joints.\n let je = b.m_jointList;\n while (je) {\n const je0 = je;\n je = je.next;\n\n this.publish('remove-joint', je0.joint);\n this.destroyJoint(je0.joint);\n\n b.m_jointList = je;\n }\n b.m_jointList = null;\n\n // Delete the attached contacts.\n let ce = b.m_contactList;\n while (ce) {\n const ce0 = ce;\n ce = ce.next;\n\n this.destroyContact(ce0.contact);\n\n b.m_contactList = ce;\n }\n b.m_contactList = null;\n\n // Delete the attached fixtures. This destroys broad-phase proxies.\n let f = b.m_fixtureList;\n while (f) {\n const f0 = f;\n f = f.m_next;\n\n this.publish('remove-fixture', f0);\n f0.destroyProxies(this.m_broadPhase);\n\n b.m_fixtureList = f;\n }\n b.m_fixtureList = null;\n\n // Remove world body list.\n if (b.m_prev) {\n b.m_prev.m_next = b.m_next;\n }\n\n if (b.m_next) {\n b.m_next.m_prev = b.m_prev;\n }\n\n if (b == this.m_bodyList) {\n this.m_bodyList = b.m_next;\n }\n\n b.m_destroyed = true;\n\n --this.m_bodyCount;\n\n this.publish('remove-body', b);\n\n return true;\n }\n\n /**\n * Create a joint to constrain bodies together. No reference to the definition\n * is retained. This may cause the connected bodies to cease colliding.\n *\n * Warning: This function is locked during callbacks.\n */\n createJoint
\n * Apply damping.\n * ODE: dv/dt + c * v = 0\n * Solution: v(t) = v0 * exp(-c * t)\n * Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt)\n * v2 = exp(-c * dt) * v1\n * Pade approximation:\n * v2 = v1 * 1 / (1 + c * dt)\n *\n */\n v.mul(1.0 / (1.0 + h * body.m_linearDamping));\n w *= 1.0 / (1.0 + h * body.m_angularDamping);\n }\n\n body.c_position.c = c;\n body.c_position.a = a;\n body.c_velocity.v = v;\n body.c_velocity.w = w;\n }\n\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.initConstraint(step);\n }\n\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.initVelocityConstraint(step);\n }\n\n if (step.warmStarting) {\n // Warm start.\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.warmStartConstraint(step);\n }\n }\n\n for (let i = 0; i < this.m_joints.length; ++i) {\n const joint = this.m_joints[i];\n joint.initVelocityConstraints(step);\n }\n\n // Solve velocity constraints\n for (let i = 0; i < step.velocityIterations; ++i) {\n for (let j = 0; j < this.m_joints.length; ++j) {\n const joint = this.m_joints[j];\n joint.solveVelocityConstraints(step);\n }\n\n for (let j = 0; j < this.m_contacts.length; ++j) {\n const contact = this.m_contacts[j];\n contact.solveVelocityConstraint(step);\n }\n }\n\n // Store impulses for warm starting\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.storeConstraintImpulses(step);\n }\n\n // Integrate positions\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n\n const c = Vec2.clone(body.c_position.c);\n let a = body.c_position.a;\n const v = Vec2.clone(body.c_velocity.v);\n let w = body.c_velocity.w;\n\n // Check for large velocities\n const translation = Vec2.mulNumVec2(h, v);\n if (Vec2.lengthSquared(translation) > Settings.maxTranslationSquared) {\n const ratio = Settings.maxTranslation / translation.length();\n v.mul(ratio);\n }\n\n const rotation = h * w;\n if (rotation * rotation > Settings.maxRotationSquared) {\n const ratio = Settings.maxRotation / Math.abs(rotation);\n w *= ratio;\n }\n\n // Integrate\n c.addMul(h, v);\n a += h * w;\n\n body.c_position.c.setVec2(c);\n body.c_position.a = a;\n body.c_velocity.v.setVec2(v);\n body.c_velocity.w = w;\n }\n\n // Solve position constraints\n let positionSolved = false;\n for (let i = 0; i < step.positionIterations; ++i) {\n let minSeparation = 0.0;\n for (let j = 0; j < this.m_contacts.length; ++j) {\n const contact = this.m_contacts[j];\n const separation = contact.solvePositionConstraint(step);\n minSeparation = Math.min(minSeparation, separation);\n }\n // We can't expect minSpeparation >= -Settings.linearSlop because we don't\n // push the separation above -Settings.linearSlop.\n const contactsOkay = minSeparation >= -3.0 * Settings.linearSlop;\n\n let jointsOkay = true;\n for (let j = 0; j < this.m_joints.length; ++j) {\n const joint = this.m_joints[j];\n const jointOkay = joint.solvePositionConstraints(step);\n jointsOkay = jointsOkay && jointOkay;\n }\n\n if (contactsOkay && jointsOkay) {\n // Exit early if the position errors are small.\n positionSolved = true;\n break;\n }\n }\n\n // Copy state buffers back to the bodies\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n\n body.m_sweep.c.setVec2(body.c_position.c);\n body.m_sweep.a = body.c_position.a;\n body.m_linearVelocity.setVec2(body.c_velocity.v);\n body.m_angularVelocity = body.c_velocity.w;\n body.synchronizeTransform();\n }\n\n this.postSolveIsland();\n\n if (allowSleep) {\n let minSleepTime = Infinity;\n\n const linTolSqr = Settings.linearSleepToleranceSqr;\n const angTolSqr = Settings.angularSleepToleranceSqr;\n\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n if (body.isStatic()) {\n continue;\n }\n\n if ((body.m_autoSleepFlag == false)\n || (body.m_angularVelocity * body.m_angularVelocity > angTolSqr)\n || (Vec2.lengthSquared(body.m_linearVelocity) > linTolSqr)) {\n body.m_sleepTime = 0.0;\n minSleepTime = 0.0;\n } else {\n body.m_sleepTime += h;\n minSleepTime = Math.min(minSleepTime, body.m_sleepTime);\n }\n }\n\n if (minSleepTime >= Settings.timeToSleep && positionSolved) {\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n body.setAwake(false);\n }\n }\n }\n }\n\n /**\n * Find TOI contacts and solve them.\n */\n solveWorldTOI(step: TimeStep): void {\n const world = this.m_world;\n\n if (world.m_stepComplete) {\n for (let b = world.m_bodyList; b; b = b.m_next) {\n b.m_islandFlag = false;\n b.m_sweep.alpha0 = 0.0;\n }\n\n for (let c = world.m_contactList; c; c = c.m_next) {\n // Invalidate TOI\n c.m_toiFlag = false;\n c.m_islandFlag = false;\n c.m_toiCount = 0;\n c.m_toi = 1.0;\n }\n }\n\n // Find TOI events and solve them.\n while (true) {\n // Find the first TOI.\n let minContact = null; // Contact\n let minAlpha = 1.0;\n\n for (let c = world.m_contactList; c; c = c.m_next) {\n // Is this contact disabled?\n if (c.isEnabled() == false) {\n continue;\n }\n\n // Prevent excessive sub-stepping.\n if (c.m_toiCount > Settings.maxSubSteps) {\n continue;\n }\n\n let alpha = 1.0;\n if (c.m_toiFlag) {\n // This contact has a valid cached TOI.\n alpha = c.m_toi;\n } else {\n const fA = c.getFixtureA();\n const fB = c.getFixtureB();\n\n // Is there a sensor?\n if (fA.isSensor() || fB.isSensor()) {\n continue;\n }\n\n const bA = fA.getBody();\n const bB = fB.getBody();\n\n _ASSERT && console.assert(bA.isDynamic() || bB.isDynamic());\n\n const activeA = bA.isAwake() && !bA.isStatic();\n const activeB = bB.isAwake() && !bB.isStatic();\n\n // Is at least one body active (awake and dynamic or kinematic)?\n if (activeA == false && activeB == false) {\n continue;\n }\n\n const collideA = bA.isBullet() || !bA.isDynamic();\n const collideB = bB.isBullet() || !bB.isDynamic();\n\n // Are these two non-bullet dynamic bodies?\n if (collideA == false && collideB == false) {\n continue;\n }\n\n // Compute the TOI for this contact.\n // Put the sweeps onto the same time interval.\n let alpha0 = bA.m_sweep.alpha0;\n\n if (bA.m_sweep.alpha0 < bB.m_sweep.alpha0) {\n alpha0 = bB.m_sweep.alpha0;\n bA.m_sweep.advance(alpha0);\n } else if (bB.m_sweep.alpha0 < bA.m_sweep.alpha0) {\n alpha0 = bA.m_sweep.alpha0;\n bB.m_sweep.advance(alpha0);\n }\n\n _ASSERT && console.assert(alpha0 < 1.0);\n\n const indexA = c.getChildIndexA();\n const indexB = c.getChildIndexB();\n\n const sweepA = bA.m_sweep;\n const sweepB = bB.m_sweep;\n\n // Compute the time of impact in interval [0, minTOI]\n const input = new TOIInput(); // TODO: reuse\n input.proxyA.set(fA.getShape(), indexA);\n input.proxyB.set(fB.getShape(), indexB);\n input.sweepA.set(bA.m_sweep);\n input.sweepB.set(bB.m_sweep);\n input.tMax = 1.0;\n\n const output = new TOIOutput(); // TODO: reuse\n TimeOfImpact(output, input);\n\n // Beta is the fraction of the remaining portion of the [time?].\n const beta = output.t;\n if (output.state == TOIOutputState.e_touching) {\n alpha = Math.min(alpha0 + (1.0 - alpha0) * beta, 1.0);\n } else {\n alpha = 1.0;\n }\n\n c.m_toi = alpha;\n c.m_toiFlag = true;\n }\n\n if (alpha < minAlpha) {\n // This is the minimum TOI found so far.\n minContact = c;\n minAlpha = alpha;\n }\n }\n\n if (minContact == null || 1.0 - 10.0 * Math.EPSILON < minAlpha) {\n // No more TOI events. Done!\n world.m_stepComplete = true;\n break;\n }\n\n // Advance the bodies to the TOI.\n const fA = minContact.getFixtureA();\n const fB = minContact.getFixtureB();\n const bA = fA.getBody();\n const bB = fB.getBody();\n\n const backup1 = bA.m_sweep.clone();\n const backup2 = bB.m_sweep.clone();\n\n bA.advance(minAlpha);\n bB.advance(minAlpha);\n\n // The TOI contact likely has some new contact points.\n minContact.update(world);\n minContact.m_toiFlag = false;\n ++minContact.m_toiCount;\n\n // Is the contact solid?\n if (minContact.isEnabled() == false || minContact.isTouching() == false) {\n // Restore the sweeps.\n minContact.setEnabled(false);\n bA.m_sweep.set(backup1);\n bB.m_sweep.set(backup2);\n bA.synchronizeTransform();\n bB.synchronizeTransform();\n continue;\n }\n\n bA.setAwake(true);\n bB.setAwake(true);\n\n // Build the island\n this.clear();\n this.addBody(bA);\n this.addBody(bB);\n this.addContact(minContact);\n\n bA.m_islandFlag = true;\n bB.m_islandFlag = true;\n minContact.m_islandFlag = true;\n\n // Get contacts on bodyA and bodyB.\n const bodies = [ bA, bB ];\n for (let i = 0; i < bodies.length; ++i) {\n const body = bodies[i];\n if (body.isDynamic()) {\n for (let ce = body.m_contactList; ce; ce = ce.next) {\n // if (this.m_bodyCount == this.m_bodyCapacity) { break; }\n // if (this.m_contactCount == this.m_contactCapacity) { break; }\n\n const contact = ce.contact;\n\n // Has this contact already been added to the island?\n if (contact.m_islandFlag) {\n continue;\n }\n\n // Only add if either is static, kinematic or bullet.\n const other = ce.other;\n if (other.isDynamic() && !body.isBullet() && !other.isBullet()) {\n continue;\n }\n\n // Skip sensors.\n const sensorA = contact.m_fixtureA.m_isSensor;\n const sensorB = contact.m_fixtureB.m_isSensor;\n if (sensorA || sensorB) {\n continue;\n }\n\n // Tentatively advance the body to the TOI.\n const backup = other.m_sweep.clone();\n if (other.m_islandFlag == false) {\n other.advance(minAlpha);\n }\n\n // Update the contact points\n contact.update(world);\n\n // Was the contact disabled by the user?\n // Are there contact points?\n if (contact.isEnabled() == false || contact.isTouching() == false) {\n other.m_sweep.set(backup);\n other.synchronizeTransform();\n continue;\n }\n\n // Add the contact to the island\n contact.m_islandFlag = true;\n this.addContact(contact);\n\n // Has the other body already been added to the island?\n if (other.m_islandFlag) {\n continue;\n }\n\n // Add the other body to the island.\n other.m_islandFlag = true;\n\n if (!other.isStatic()) {\n other.setAwake(true);\n }\n\n this.addBody(other);\n }\n }\n }\n\n s_subStep.reset((1.0 - minAlpha) * step.dt);\n s_subStep.dtRatio = 1.0;\n s_subStep.positionIterations = 20;\n s_subStep.velocityIterations = step.velocityIterations;\n s_subStep.warmStarting = false;\n\n this.solveIslandTOI(s_subStep, bA, bB);\n\n // Reset island flags and synchronize broad-phase proxies.\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n body.m_islandFlag = false;\n\n if (!body.isDynamic()) {\n continue;\n }\n\n body.synchronizeFixtures();\n\n // Invalidate all contact TOIs on this displaced body.\n for (let ce = body.m_contactList; ce; ce = ce.next) {\n ce.contact.m_toiFlag = false;\n ce.contact.m_islandFlag = false;\n }\n }\n\n // Commit fixture proxy movements to the broad-phase so that new contacts\n // are created.\n // Also, some contacts can be destroyed.\n world.findNewContacts();\n\n if (world.m_subStepping) {\n world.m_stepComplete = false;\n break;\n }\n }\n }\n\n solveIslandTOI(subStep: TimeStep, toiA: Body, toiB: Body): void {\n const world = this.m_world;\n\n // Initialize the body state.\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n body.c_position.c.setVec2(body.m_sweep.c);\n body.c_position.a = body.m_sweep.a;\n body.c_velocity.v.setVec2(body.m_linearVelocity);\n body.c_velocity.w = body.m_angularVelocity;\n }\n\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.initConstraint(subStep);\n }\n\n // Solve position constraints.\n for (let i = 0; i < subStep.positionIterations; ++i) {\n let minSeparation = 0.0;\n for (let j = 0; j < this.m_contacts.length; ++j) {\n const contact = this.m_contacts[j];\n const separation = contact.solvePositionConstraintTOI(subStep, toiA, toiB);\n minSeparation = Math.min(minSeparation, separation);\n }\n // We can't expect minSpeparation >= -Settings.linearSlop because we don't\n // push the separation above -Settings.linearSlop.\n const contactsOkay = minSeparation >= -1.5 * Settings.linearSlop;\n if (contactsOkay) {\n break;\n }\n }\n\n if (false) {\n // Is the new position really safe?\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const c = this.m_contacts[i];\n const fA = c.getFixtureA();\n const fB = c.getFixtureB();\n\n const bA = fA.getBody();\n const bB = fB.getBody();\n\n const indexA = c.getChildIndexA();\n const indexB = c.getChildIndexB();\n\n const input = new DistanceInput();\n input.proxyA.set(fA.getShape(), indexA);\n input.proxyB.set(fB.getShape(), indexB);\n input.transformA = bA.getTransform();\n input.transformB = bB.getTransform();\n input.useRadii = false;\n\n const output = new DistanceOutput();\n const cache = new SimplexCache();\n Distance(output, cache, input);\n\n if (output.distance == 0 || cache.count == 3) {\n cache.count += 0;\n }\n }\n }\n\n // Leap of faith to new safe state.\n toiA.m_sweep.c0.setVec2(toiA.c_position.c);\n toiA.m_sweep.a0 = toiA.c_position.a;\n toiB.m_sweep.c0.setVec2(toiB.c_position.c);\n toiB.m_sweep.a0 = toiB.c_position.a;\n\n // No warm starting is needed for TOI events because warm\n // starting impulses were applied in the discrete solver.\n for (let i = 0; i < this.m_contacts.length; ++i) {\n const contact = this.m_contacts[i];\n contact.initVelocityConstraint(subStep);\n }\n\n // Solve velocity constraints.\n for (let i = 0; i < subStep.velocityIterations; ++i) {\n for (let j = 0; j < this.m_contacts.length; ++j) {\n const contact = this.m_contacts[j];\n contact.solveVelocityConstraint(subStep);\n }\n }\n\n // Don't store the TOI contact forces for warm starting\n // because they can be quite large.\n\n const h = subStep.dt;\n\n // Integrate positions\n for (let i = 0; i < this.m_bodies.length; ++i) {\n const body = this.m_bodies[i];\n\n const c = Vec2.clone(body.c_position.c);\n let a = body.c_position.a;\n const v = Vec2.clone(body.c_velocity.v);\n let w = body.c_velocity.w;\n\n // Check for large velocities\n const translation = Vec2.mulNumVec2(h, v);\n if (Vec2.dot(translation, translation) > Settings.maxTranslationSquared) {\n const ratio = Settings.maxTranslation / translation.length();\n v.mul(ratio);\n }\n\n const rotation = h * w;\n if (rotation * rotation > Settings.maxRotationSquared) {\n const ratio = Settings.maxRotation / Math.abs(rotation);\n w *= ratio;\n }\n\n // Integrate\n c.addMul(h, v);\n a += h * w;\n\n body.c_position.c = c;\n body.c_position.a = a;\n body.c_velocity.v = v;\n body.c_velocity.w = w;\n\n // Sync bodies\n body.m_sweep.c = c;\n body.m_sweep.a = a;\n body.m_linearVelocity = v;\n body.m_angularVelocity = w;\n body.synchronizeTransform();\n }\n\n this.postSolveIsland();\n }\n\n /** @internal */\n postSolveIsland(): void {\n for (let c = 0; c < this.m_contacts.length; ++c) {\n const contact = this.m_contacts[c];\n this.m_world.postSolve(contact, contact.m_impulse);\n }\n }\n}\n\n// @ts-ignore\nSolver.TimeStep = TimeStep;\n","/*\n * Planck.js\n * The MIT License\n * Copyright (c) 2021 Erin Catto, Ali Shakiba\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { Vec2 } from './Vec2';\n\n\nconst _ASSERT = typeof ASSERT === 'undefined' ? false : ASSERT;\n\n\n/**\n * A 2-by-2 matrix. Stored in column-major order.\n */\nexport class Mat22 {\n ex: Vec2;\n ey: Vec2;\n\n constructor(a: number, b: number, c: number, d: number);\n constructor(a: { x: number; y: number }, b: { x: number; y: number });\n constructor();\n // tslint:disable-next-line:typedef\n constructor(a?, b?, c?, d?) {\n if (typeof a === 'object' && a !== null) {\n this.ex = Vec2.clone(a);\n this.ey = Vec2.clone(b);\n } else if (typeof a === 'number') {\n this.ex = Vec2.neo(a, c);\n this.ey = Vec2.neo(b, d);\n } else {\n this.ex = Vec2.zero();\n this.ey = Vec2.zero();\n }\n }\n\n /** @internal */\n toString(): string {\n return JSON.stringify(this);\n }\n\n static isValid(obj: any): boolean {\n if (obj === null || typeof obj === 'undefined') {\n return false;\n }\n return Vec2.isValid(obj.ex) && Vec2.isValid(obj.ey);\n }\n\n static assert(o: any): void {\n _ASSERT && console.assert(!Mat22.isValid(o), 'Invalid Mat22!', o);\n }\n\n set(a: Mat22): void;\n set(a: Vec2, b: Vec2): void;\n set(a: number, b: number, c: number, d: number): void;\n // tslint:disable-next-line:typedef\n set(a, b?, c?, d?): void {\n if (typeof a === 'number' && typeof b === 'number' && typeof c === 'number'\n && typeof d === 'number') {\n this.ex.setNum(a, c);\n this.ey.setNum(b, d);\n\n } else if (typeof a === 'object' && typeof b === 'object') {\n this.ex.setVec2(a);\n this.ey.setVec2(b);\n\n } else if (typeof a === 'object') {\n _ASSERT && Mat22.assert(a);\n this.ex.setVec2(a.ex);\n this.ey.setVec2(a.ey);\n\n } else {\n _ASSERT && console.assert(false);\n }\n }\n\n setIdentity(): void {\n this.ex.x = 1.0;\n this.ey.x = 0.0;\n this.ex.y = 0.0;\n this.ey.y = 1.0;\n }\n\n setZero(): void {\n this.ex.x = 0.0;\n this.ey.x = 0.0;\n this.ex.y = 0.0;\n this.ey.y = 0.0;\n }\n\n getInverse(): Mat22 {\n const a = this.ex.x;\n const b = this.ey.x;\n const c = this.ex.y;\n const d = this.ey.y;\n let det = a * d - b * c;\n if (det !== 0.0) {\n det = 1.0 / det;\n }\n const imx = new Mat22();\n imx.ex.x = det * d;\n imx.ey.x = -det * b;\n imx.ex.y = -det * c;\n imx.ey.y = det * a;\n return imx;\n }\n\n /**\n * Solve A * x = b, where b is a column vector. This is more efficient than\n * computing the inverse in one-shot cases.\n */\n solve(v: Vec2): Vec2 {\n _ASSERT && Vec2.assert(v);\n const a = this.ex.x;\n const b = this.ey.x;\n const c = this.ex.y;\n const d = this.ey.y;\n let det = a * d - b * c;\n if (det !== 0.0) {\n det = 1.0 / det;\n }\n const w = Vec2.zero();\n w.x = det * (d * v.x - b * v.y);\n w.y = det * (a * v.y - c * v.x);\n return w;\n }\n\n /**\n * Multiply a matrix times a vector. If a rotation matrix is provided, then this\n * transforms the vector from one frame to another.\n */\n static mul(mx: Mat22, my: Mat22): Mat22;\n static mul(mx: Mat22, v: Vec2): Vec2;\n // tslint:disable-next-line:typedef\n static mul(mx, v) {\n if (v && 'x' in v && 'y' in v) {\n _ASSERT && Vec2.assert(v);\n const x = mx.ex.x * v.x + mx.ey.x * v.y;\n const y = mx.ex.y * v.x + mx.ey.y * v.y;\n return Vec2.neo(x, y);\n\n } else if (v && 'ex' in v && 'ey' in v) { // Mat22\n _ASSERT && Mat22.assert(v);\n // return new Mat22(Vec2.mul(mx, v.ex), Vec2.mul(mx, v.ey));\n const a = mx.ex.x * v.ex.x + mx.ey.x * v.ex.y;\n const b = mx.ex.x * v.ey.x + mx.ey.x * v.ey.y;\n const c = mx.ex.y * v.ex.x + mx.ey.y * v.ex.y;\n const d = mx.ex.y * v.ey.x + mx.ey.y * v.ey.y;\n return new Mat22(a, b, c, d);\n }\n\n _ASSERT && console.assert(false);\n }\n\n static mulVec2(mx: Mat22, v: Vec2): Vec2 {\n _ASSERT && Vec2.assert(v);\n const x = mx.ex.x * v.x + mx.ey.x * v.y;\n const y = mx.ex.y * v.x + mx.ey.y * v.y;\n return Vec2.neo(x, y);\n }\n\n static mulMat22(mx: Mat22, v: Mat22): Mat22 {\n _ASSERT && Mat22.assert(v);\n // return new Mat22(Vec2.mul(mx, v.ex), Vec2.mul(mx, v.ey));\n const a = mx.ex.x * v.ex.x + mx.ey.x * v.ex.y;\n const b = mx.ex.x * v.ey.x + mx.ey.x * v.ey.y;\n const c = mx.ex.y * v.ex.x + mx.ey.y * v.ex.y;\n const d = mx.ex.y * v.ey.x + mx.ey.y * v.ey.y;\n return new Mat22(a, b, c, d);\n }\n\n /**\n * Multiply a matrix transpose times a vector. If a rotation matrix is provided,\n * then this transforms the vector from one frame to another (inverse\n * transform).\n */\n static mulT(mx: Mat22, my: Mat22): Mat22;\n static mulT(mx: Mat22, v: Vec2): Vec2;\n // tslint:disable-next-line:typedef\n static mulT(mx, v) {\n if (v && 'x' in v && 'y' in v) { // Vec2\n _ASSERT && Vec2.assert(v);\n return Vec2.neo(Vec2.dot(v, mx.ex), Vec2.dot(v, mx.ey));\n\n } else if (v && 'ex' in v && 'ey' in v) { // Mat22\n _ASSERT && Mat22.assert(v);\n const c1 = Vec2.neo(Vec2.dot(mx.ex, v.ex), Vec2.dot(mx.ey, v.ex));\n const c2 = Vec2.neo(Vec2.dot(mx.ex, v.ey), Vec2.dot(mx.ey, v.ey));\n return new Mat22(c1, c2);\n }\n\n _ASSERT && console.assert(false);\n }\n\n static mulTVec2(mx: Mat22, v: Vec2): Vec2 {\n _ASSERT && Mat22.assert(mx);\n _ASSERT && Vec2.assert(v);\n return Vec2.neo(Vec2.dot(v, mx.ex), Vec2.dot(v, mx.ey));\n }\n\n static mulTMat22(mx: Mat22, v: Mat22): Mat22 {\n _ASSERT && Mat22.assert(mx);\n _ASSERT && Mat22.assert(v);\n const c1 = Vec2.neo(Vec2.dot(mx.ex, v.ex), Vec2.dot(mx.ey, v.ex));\n const c2 = Vec2.neo(Vec2.dot(mx.ex, v.ey), Vec2.dot(mx.ey, v.ey));\n return new Mat22(c1, c2);\n }\n\n static abs(mx: Mat22): Mat22 {\n _ASSERT && Mat22.assert(mx);\n return new Mat22(Vec2.abs(mx.ex), Vec2.abs(mx.ey));\n }\n\n static add(mx1: Mat22, mx2: Mat22): Mat22 {\n _ASSERT && Mat22.assert(mx1);\n _ASSERT && Mat22.assert(mx2);\n return new Mat22(Vec2.add(mx1.ex, mx2.ex), Vec2.add(mx1.ey, mx2.ey));\n }\n}\n","/*\n * Planck.js\n * The MIT License\n * Copyright (c) 2021 Erin Catto, Ali Shakiba\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { Vec2 } from '../common/Vec2';\nimport { Transform } from '../common/Transform';\nimport { math as Math } from '../common/Math';\nimport { Rot } from '../common/Rot';\n\nexport enum ManifoldType {\n e_circles = 0,\n e_faceA = 1,\n e_faceB = 2\n}\n\nexport enum ContactFeatureType {\n e_vertex = 0,\n e_face = 1\n}\n\n/**\n * This is used for determining the state of contact points.\n */\n export enum PointState {\n /** Point does not exist */\n nullState = 0,\n /** Point was added in the update */\n addState = 1,\n /** Point persisted across the update */\n persistState = 2,\n /** Point was removed in the update */\n removeState = 3\n}\n\n/**\n * Used for computing contact manifolds.\n */\n export class ClipVertex {\n v: Vec2 = Vec2.zero();\n id: ContactID = new ContactID();\n\n set(o: ClipVertex): void {\n this.v.setVec2(o.v);\n this.id.set(o.id);\n }\n}\n\n/**\n * A manifold for two touching convex shapes. Manifolds are created in `evaluate`\n * method of Contact subclasses.\n *\n * Supported manifold types are e_faceA or e_faceB for clip point versus plane\n * with radius and e_circles point versus point with radius.\n *\n * We store contacts in this way so that position correction can account for\n * movement, which is critical for continuous physics. All contact scenarios\n * must be expressed in one of these types. This structure is stored across time\n * steps, so we keep it small.\n *\n * @prop type e_circle, e_faceA, e_faceB\n * @prop localPoint Usage depends on manifold type:
0)return!1}return!0},e.prototype.rayCast=function(t,e,i,o){for(var s=v.mulTVec2(i.q,m.sub(e.p1,i.p)),n=v.mulTVec2(i.q,m.sub(e.p2,i.p)),r=m.sub(n,s),a=0,h=e.maxFraction,c=-1,_=0;_ u*u)return;t.pointCount=1,t.type=a.e_faceA,t.localNormal.setCombine(1,c,-1,A),t.localNormal.normalize(),t.localPoint=A,t.points[0].localPoint.setVec2(o.m_p),t.points[0].id.cf.indexA=0,t.points[0].id.cf.typeA=h.e_vertex,t.points[0].id.cf.indexB=0,t.points[0].id.cf.typeB=h.e_vertex}else if(V<=0){if(m.distanceSquared(c,B)>u*u)return;t.pointCount=1,t.type=a.e_faceA,t.localNormal.setCombine(1,c,-1,B),t.localNormal.normalize(),t.localPoint.setVec2(B),t.points[0].localPoint.setVec2(o.m_p),t.points[0].id.cf.indexA=0,t.points[0].id.cf.typeA=h.e_vertex,t.points[0].id.cf.indexB=0,t.points[0].id.cf.typeB=h.e_vertex}else{var C=m.mid(A,B);if(m.dot(c,y[g])-m.dot(C,y[g])>u)return;t.pointCount=1,t.type=a.e_faceA,t.localNormal.setVec2(y[g]),t.localPoint.setVec2(C),t.points[0].localPoint.setVec2(o.m_p),t.points[0].id.cf.indexA=0,t.points[0].id.cf.typeA=h.e_vertex,t.points[0].id.cf.indexB=0,t.points[0].id.cf.typeB=h.e_vertex}}ot.addType(At.TYPE,wt.TYPE,(function(t,e,i,o,s,n,r){se(t,i.getShape(),e,n.getShape(),s)})),ot.addType(gt.TYPE,At.TYPE,(function(t,e,i,o,s,n,r){le(t,i.getShape(),e,n.getShape(),s)})),ot.addType(bt.TYPE,At.TYPE,(function(t,e,i,o,s,n,r){var a=i.getShape(),h=new gt;a.getChildEdge(h,o),le(t,h,e,n.getShape(),s)})),function(t){t[t.e_unknown=-1]="e_unknown",t[t.e_edgeA=1]="e_edgeA",t[t.e_edgeB=2]="e_edgeB"}(te||(te={})),function(t){t[t.e_isolated=0]="e_isolated",t[t.e_concave=1]="e_concave",t[t.e_convex=2]="e_convex"}(ee||(ee={}));var ne=function(){},re=function(){this.vertices=[],this.normals=[],this.count=0},ae=function(){this.normal=m.zero(),this.sideNormal1=m.zero(),this.sideNormal2=m.zero()},he=new ne,ce=new ne,me=new re,_e=new ae;function le(t,e,i,o,s){var n=x.mulTXf(i,s),c=x.mulVec2(n,o.m_centroid),_=e.m_vertex0,u=e.m_vertex1,p=e.m_vertex2,d=e.m_vertex3,y=e.m_hasVertex0,f=e.m_hasVertex3,g=m.sub(p,u);g.normalize();var b,A,B,w=m.neo(g.y,-g.x),V=m.dot(w,m.sub(c,u)),C=0,M=0,I=!1,S=!1;if(y){var P=m.sub(u,_);P.normalize(),b=m.neo(P.y,-P.x),I=m.crossVec2Vec2(P,g)>=0,C=m.dot(b,c)-m.dot(b,_)}if(f){var z=m.sub(d,p);z.normalize(),A=m.neo(z.y,-z.x),S=m.crossVec2Vec2(g,z)>0,M=m.dot(A,c)-m.dot(A,p)}var T=m.zero(),k=m.zero(),F=m.zero();y&&f?I&&S?(B=C>=0||V>=0||M>=0)?(T.setVec2(w),k.setVec2(b),F.setVec2(A)):(T.setMul(-1,w),k.setMul(-1,w),F.setMul(-1,w)):I?(B=C>=0||V>=0&&M>=0)?(T.setVec2(w),k.setVec2(b),F.setVec2(w)):(T.setMul(-1,w),k.setMul(-1,A),F.setMul(-1,w)):S?(B=M>=0||C>=0&&V>=0)?(T.setVec2(w),k.setVec2(w),F.setVec2(A)):(T.setMul(-1,w),k.setMul(-1,w),F.setMul(-1,b)):(B=C>=0&&V>=0&&M>=0)?(T.setVec2(w),k.setVec2(w),F.setVec2(w)):(T.setMul(-1,w),k.setMul(-1,A),F.setMul(-1,b)):y?I?(B=C>=0||V>=0)?(T.setVec2(w),k.setVec2(b),F.setMul(-1,w)):(T.setMul(-1,w),k.setVec2(w),F.setMul(-1,w)):(B=C>=0&&V>=0)?(T.setVec2(w),k.setVec2(w),F.setMul(-1,w)):(T.setMul(-1,w),k.setVec2(w),F.setMul(-1,b)):f?S?(B=V>=0||M>=0)?(T.setVec2(w),k.setMul(-1,w),F.setVec2(A)):(T.setMul(-1,w),k.setMul(-1,w),F.setVec2(w)):(B=V>=0&&M>=0)?(T.setVec2(w),k.setMul(-1,w),F.setVec2(w)):(T.setMul(-1,w),k.setMul(-1,A),F.setVec2(w)):(B=V>=0)?(T.setVec2(w),k.setMul(-1,w),F.setMul(-1,w)):(T.setMul(-1,w),k.setVec2(w),F.setVec2(w)),me.count=o.m_count;for(var q=0;qthis.MAX_ELAPSE&&(t=this.MAX_ELAPSE);var o=!1;if(null!==this._tickBefore)for(var s=0;s