From 80dc05a92baeba9dd0651eccf0b1b12203ba6e55 Mon Sep 17 00:00:00 2001 From: genxium Date: Mon, 26 Sep 2022 11:16:18 +0800 Subject: [PATCH] Fixed collision detection and camera tracking. --- frontend/assets/scenes/login.fire | 2 +- frontend/assets/scripts/CameraTracker.js | 2 +- frontend/assets/scripts/Map.js | 28 +-- frontend/assets/scripts/TouchEventsManager.js | 71 ++----- frontend/assets/scripts/modules/Collisions.js | 198 +++++++++++++++++- 5 files changed, 234 insertions(+), 67 deletions(-) diff --git a/frontend/assets/scenes/login.fire b/frontend/assets/scenes/login.fire index 31ccba3..020e9a8 100644 --- a/frontend/assets/scenes/login.fire +++ b/frontend/assets/scenes/login.fire @@ -440,7 +440,7 @@ "array": [ 0, 0, - 209.73151519075364, + 216.05530045313827, 0, 0, 0, diff --git a/frontend/assets/scripts/CameraTracker.js b/frontend/assets/scripts/CameraTracker.js index 37ab4c9..d822237 100644 --- a/frontend/assets/scripts/CameraTracker.js +++ b/frontend/assets/scripts/CameraTracker.js @@ -22,7 +22,7 @@ cc.Class({ if (!self.mapScriptIns) return; if (!self.mapScriptIns.selfPlayerInfo) return; if (!self.mapScriptIns.playerRichInfoDict) return; - const selfPlayerRichInfo = self.mapScriptIns.playerRichInfoDict[self.mapScriptIns.selfPlayerInfo.id]; + const selfPlayerRichInfo = self.mapScriptIns.playerRichInfoDict.get(self.mapScriptIns.selfPlayerInfo.id); if (!selfPlayerRichInfo) return; const selfPlayerNode = selfPlayerRichInfo.node; if (!selfPlayerNode) return; diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index f8e7c9f..b92a56f 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -126,7 +126,7 @@ cc.Class({ }, maxChasingRenderFramesPerUpdate: { type: cc.Integer, - default: 5 + default: 10 }, }, @@ -565,7 +565,9 @@ cc.Class({ self.onBattleStarted(rdf); return; case window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.PLAYER_READDED_AND_ACKED: - self.lastAllConfirmedRenderFrameId = frameId; + // [WARNING] The "frameId" from server could be quite fast-forwarding, don't assign it in other cases. + self.renderFrameId = frameId; + self.lastAllConfirmedRenderFrameId = frameId; self.onBattleReadyToStart(rdf.playerMetas, true); self.onBattleStarted(rdf); return; @@ -763,8 +765,8 @@ cc.Class({ newPlayerNode.getComponent("SelfPlayer").mapNode = self.node; const currentSelfColliderCircle = newPlayerNode.getComponent(cc.CircleCollider); - const newPlayerColliderLatest = self.latestCollisionSys.createCircle(x, y, currentSelfColliderCircle); - const newPlayerColliderChaser = self.chaserCollisionSys.createCircle(x, y, currentSelfColliderCircle); + const newPlayerColliderLatest = self.latestCollisionSys.createCircle(x, y, currentSelfColliderCircle.radius); + const newPlayerColliderChaser = self.chaserCollisionSys.createCircle(x, y, currentSelfColliderCircle.radius); const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; self.latestCollisionSysMap.set(collisionPlayerIndex, newPlayerColliderLatest); self.chaserCollisionSysMap.set(collisionPlayerIndex, newPlayerColliderChaser); @@ -819,11 +821,6 @@ cc.Class({ } catch (err) { console.error("Error during Map.update", err); } finally { - // Update camera to track selfPlayer. - if (null != self.ctrl) { - self.ctrl.justifyMapNodePosAndScale(self.ctrl.linearSpeedBase, self.ctrl.zoomingSpeedBase); - } - // Update countdown if (null != self.countdownNanos) { self.countdownNanos -= self.rollbackEstimatedDt*1000000000; @@ -1050,7 +1047,7 @@ cc.Class({ const player = renderFrame.players[playerId]; const encodedInput = inputList[joinIndex-1]; const decodedInput = self.ctrl.decodeDirection(encodedInput); - const baseChange = player.speed*self.rollbackEstimatedDt; + const baseChange = player.speed*self.rollbackEstimatedDt*decodedInput.speedFactor; playerCollider.x += baseChange*decodedInput.dx; playerCollider.y += baseChange*decodedInput.dy; /* @@ -1063,8 +1060,9 @@ cc.Class({ collisionSys.update(); const result = collisionSys.createResult(); // Can I reuse a "self.latestCollisionSysResult" object throughout the whole battle? - self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { - const joinIndex = playerRichInfo.joinIndex; + // [WARNING] Traverse in the order of joinIndices to guarantee determinism. + for (let i in self.playerRichInfoArr) { + const joinIndex = parseInt(i) + 1; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const playerCollider = collisionSysMap.get(collisionPlayerIndex); const potentials = playerCollider.potentials(); @@ -1075,7 +1073,7 @@ cc.Class({ playerCollider.x -= result.overlap * result.overlap_x; playerCollider.y -= result.overlap * result.overlap_y; } - }); + } } return self._createRoomDownsyncFrameLocally(renderFrameIdEd, collisionSys, collisionSysMap); @@ -1101,6 +1099,10 @@ cc.Class({ nodeAndScriptIns[1].showArrowTipNode(); } } + self.playerRichInfoArr = new Array(self.playerRichInfoDict.size); + self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { + self.playerRichInfoArr[playerRichInfo.joinIndex-1] = playerRichInfo; + }); }, _stringifyRecentInputCache(usefullOutput) { diff --git a/frontend/assets/scripts/TouchEventsManager.js b/frontend/assets/scripts/TouchEventsManager.js index 22c1554..4a1e459 100644 --- a/frontend/assets/scripts/TouchEventsManager.js +++ b/frontend/assets/scripts/TouchEventsManager.js @@ -1,19 +1,24 @@ window.DIRECTION_DECODER = [ - [0, 0], - [0, +1], - [0, -1], - [+2, 0], - [-2, 0], - [+2, +1], - [-2, -1], - [+2, -1], - [-2, +1], - [+2, 0], - [-2, 0], - [0, +1], - [0, -1], + [0, 0, null], + [0, +1, null], + [0, -1, null], + [+2, 0, null], + [-2, 0, null], + [+2, +1, null], + [-2, -1, null], + [+2, -1, null], + [-2, +1, null], + [+2, 0, null], + [-2, 0, null], + [0, +1, null], + [0, -1, null], ]; +for (let k in window.DIRECTION_DECODER) { + const length = Math.sqrt(window.DIRECTION_DECODER[k][0]*window.DIRECTION_DECODER[k][0] + window.DIRECTION_DECODER[k][1]*window.DIRECTION_DECODER[k][1]); + window.DIRECTION_DECODER[k][2] = (0 == length ? 0 : (1.0/length)); +} + cc.Class({ extends: cc.Component, properties: { @@ -114,43 +119,6 @@ cc.Class({ this.initialized = true; }, - justifyMapNodePosAndScale(linearSpeedBase, zoomingSpeedBase) { - const self = this; - if (false == self.mapScriptIns._inputControlEnabled) return; - if (null != self._cachedMapNodePosTarget) { - while (self.maxMovingBufferLength < self._cachedMapNodePosTarget.length) { - self._cachedMapNodePosTarget.shift(); - } - if (0 < self._cachedMapNodePosTarget.length && 0 == self.mapNode.getNumberOfRunningActions()) { - const nextMapNodePosTarget = self._cachedMapNodePosTarget.shift(); - const linearSpeed = linearSpeedBase; - const finalDiffVec = nextMapNodePosTarget.pos.sub(self.mapNode.position); - const finalDiffVecMag = finalDiffVec.mag(); - if (self.linearMovingEps > finalDiffVecMag) { - // Jittering. - // cc.log("Map node moving by finalDiffVecMag == %s is just jittering.", finalDiffVecMag); - return; - } - const durationSeconds = finalDiffVecMag / linearSpeed; - cc.log("Map node moving to %o in %s/%s == %s seconds.", nextMapNodePosTarget.pos, finalDiffVecMag, linearSpeed, durationSeconds); - const bufferedTargetPos = cc.v2(nextMapNodePosTarget.pos.x, nextMapNodePosTarget.pos.y); - self.mapNode.runAction(cc.sequence( - cc.moveTo(durationSeconds, bufferedTargetPos), - cc.callFunc(() => { - if (self._isMapOverMoved(self.mapNode.position)) { - self.mapNode.setPosition(bufferedTargetPos); - } - }, self) - )); - } - } - if (null != self._cachedZoomRawTarget && false == self._cachedZoomRawTarget.processed) { - cc.log(`Processing self._cachedZoomRawTarget == ${self._cachedZoomRawTarget}`); - self._cachedZoomRawTarget.processed = true; - self.mapNode.setScale(self._cachedZoomRawTarget.scale); - } - }, - _initTouchEvent() { const self = this; const translationListenerNode = (self.translationListenerNode ? self.translationListenerNode : self.mapNode); @@ -377,7 +345,8 @@ cc.Class({ } return { dx: mapped[0], - dy: mapped[1] + dy: mapped[1], + speedFactor: mapped[2], } }, diff --git a/frontend/assets/scripts/modules/Collisions.js b/frontend/assets/scripts/modules/Collisions.js index a53753d..6805e83 100644 --- a/frontend/assets/scripts/modules/Collisions.js +++ b/frontend/assets/scripts/modules/Collisions.js @@ -1 +1,197 @@ -!function(_){var t={};function s(n){if(t[n])return t[n].exports;var e=t[n]={i:n,l:!1,exports:{}};return _[n].call(e.exports,e,e.exports,s),e.l=!0,e.exports}s.m=_,s.c=t,s.d=function(_,t,n){s.o(_,t)||Object.defineProperty(_,t,{enumerable:!0,get:n})},s.r=function(_){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(_,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(_,"__esModule",{value:!0})},s.t=function(_,t){if(1&t&&(_=s(_)),8&t)return _;if(4&t&&"object"==typeof _&&_&&_.__esModule)return _;var n=Object.create(null);if(s.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:_}),2&t&&"string"!=typeof _)for(var e in _)s.d(n,e,function(t){return _[t]}.bind(null,e));return n},s.n=function(_){var t=_&&_.__esModule?function(){return _.default}:function(){return _};return s.d(t,"a",t),t},s.o=function(_,t){return Object.prototype.hasOwnProperty.call(_,t)},s.p="",s(s.s=0)}([function(_,t,s){"use strict";s.r(t);const n=[];class e{constructor(){this._bvh_parent=null,this._bvh_branch=!0,this._bvh_left=null,this._bvh_right=null,this._bvh_sort=0,this._bvh_min_x=0,this._bvh_min_y=0,this._bvh_max_x=0,this._bvh_max_y=0}static getBranch(){return n.length?n.pop():new e}static releaseBranch(_){n.push(_)}static sortBranches(_,t){return _.sort>t.sort?-1:1}}class i{constructor(){this._hierarchy=null,this._bodies=[],this._dirty_branches=[]}insert(_,t=!1){if(!t){const t=_._bvh;if(t&&t!==this)throw new Error("Body belongs to another collision system");_._bvh=this,this._bodies.push(_)}const s=_._polygon,n=_.x,i=_.y;s&&(_._dirty_coords||_.x!==_._x||_.y!==_._y||_.angle!==_._angle||_.scale_x!==_._scale_x||_.scale_y!==_._scale_y)&&_._calculateCoords();const r=_._bvh_padding,h=s?0:_.radius*_.scale,o=(s?_._min_x:n-h)-r,l=(s?_._min_y:i-h)-r,a=(s?_._max_x:n+h)+r,c=(s?_._max_y:i+h)+r;_._bvh_min_x=o,_._bvh_min_y=l,_._bvh_max_x=a,_._bvh_max_y=c;let b=this._hierarchy,v=0;if(b)for(;;){if(!b._bvh_branch){const t=b._bvh_parent,s=b._bvh_min_x,n=b._bvh_min_y,i=b._bvh_max_x,r=b._bvh_max_y,h=b._bvh_parent=_._bvh_parent=e.getBranch();h._bvh_parent=t,h._bvh_left=b,h._bvh_right=_,h._bvh_sort=v++,h._bvh_min_x=oi?a:i,h._bvh_max_y=c>r?c:r,t?t._bvh_left===b?t._bvh_left=h:t._bvh_right=h:this._hierarchy=h;break}{const _=b._bvh_left,t=_._bvh_min_y,s=_._bvh_max_x,n=_._bvh_max_y,e=o<_._bvh_min_x?o:_._bvh_min_x,i=ls?a:s,h=c>n?c:n,u=(r-e)*(h-i)-(s-_._bvh_min_x)*(n-t),y=b._bvh_right,x=y._bvh_min_x,m=y._bvh_min_y,d=y._bvh_max_x,f=y._bvh_max_y,p=od?a:d,P=c>f?c:f,M=(w-p)*(P-g)-(d-x)*(f-m);b._bvh_sort=v++,b._bvh_min_x=ew?r:w,b._bvh_max_y=h>P?h:P,b=u<=M?_:y}}else this._hierarchy=_}remove(_,t=!1){if(!t){const t=_._bvh;if(t&&t!==this)throw new Error("Body belongs to another collision system");_._bvh=null,this._bodies.splice(this._bodies.indexOf(_),1)}if(this._hierarchy===_)return void(this._hierarchy=null);const s=_._bvh_parent,n=s._bvh_parent,i=s._bvh_left,r=i===_?s._bvh_right:i;if(r._bvh_parent=n,r._bvh_branch&&(r._bvh_sort=s._bvh_sort),n){n._bvh_left===s?n._bvh_left=r:n._bvh_right=r;let _=n;for(;_;){const t=_._bvh_left,s=t._bvh_min_x,n=t._bvh_min_y,e=t._bvh_max_x,i=t._bvh_max_y,r=_._bvh_right,h=r._bvh_min_x,o=r._bvh_min_y,l=r._bvh_max_x,a=r._bvh_max_y;_._bvh_min_x=sl?e:l,_._bvh_max_y=i>a?i:a,_=_._bvh_parent}}else this._hierarchy=r;e.releaseBranch(s)}update(){const _=this._bodies,t=_.length;for(let s=0;st._bvh_max_x||l>t._bvh_max_y}n&&(this.remove(t,!0),this.insert(t,!0))}}potentials(_){const t=[],s=_._bvh_min_x,n=_._bvh_min_y,e=_._bvh_max_x,i=_._bvh_max_y;let r=this._hierarchy,h=!0;if(!r||!r._bvh_branch)return t;for(;r;){if(h){h=!1;let _=r._bvh_branch?r._bvh_left:null;for(;_&&_._bvh_max_x>=s&&_._bvh_max_y>=n&&_._bvh_min_x<=e&&_._bvh_min_y<=i;)r=_,_=r._bvh_branch?r._bvh_left:null}const o=r._bvh_branch,l=o?r._bvh_right:null;if(l&&l._bvh_max_x>s&&l._bvh_max_y>n&&l._bvh_min_xu&&l>y}(_,t)||(e&&_._dirty_normals&&_._calculateNormals(),i&&t._dirty_normals&&t._calculateNormals(),r=e&&i?function(_,t,s=null){const n=_._coords.length,e=t._coords.length;if(2===n&&2===e){const n=_._coords,e=t._coords;return s&&(s.overlap=0),n[0]===e[0]&&n[1]===e[1]}const i=_._coords,r=t._coords,h=_._normals,o=t._normals;if(n>2)for(let _=0,t=1;_2)for(let _=0,t=1;_h*h)return!1;if(s){const _=Math.sqrt(o);s.a_in_b=n<=e&&_<=e-n,s.b_in_a=e<=n&&_<=n-e,s.overlap=h-_,s.overlap_x=i/_,s.overlap_y=r/_}return!0}(_,t,s)),s&&(s.collision=r),r}function o(_,t,s=null,n=!1){const e=_._coords,i=_._edges,r=_._normals,h=t.x,o=t.y,l=t.radius*t.scale,a=2*l,c=l*l,b=e.length;let v=!0,u=!0,y=null,x=0,m=0;if(2===b){const _=h-e[0],t=o-e[1],n=_*_+t*t;if(n>c)return!1;if(s){const s=Math.sqrt(n);y=l-s,x=_/s,m=t/s,u=!1}}else for(let _=0,t=1;_f*f+p*p?1:0;let P=!1,M=0,T=0,B=0;if(s&&v&&n*n+d*d>c&&(v=!1),w){const t=-1===w,r=t?0===_?b-2:_-2:_===b-2?0:_+2,a=r+1,v=h-e[r],y=o-e[a],x=i[r],m=i[a],f=v*x+y*m;if((f<0?-1:f>x*x+m*m?1:0)===-w){const _=t?n:v,e=t?d:y,i=_*_+e*e;if(i>c)return!1;if(s){const t=Math.sqrt(i);P=!0,M=l-t,T=_/t,B=e/t,u=!1}}}else{const e=r[_],i=r[t],h=n*e+d*i,o=h<0?-h:h;if(h>0&&o>l)return!1;s&&(P=!0,M=l-h,T=e,B=i,(u&&h>=0||MM)&&(y=M,x=T,m=B)}return s&&(s.a_in_b=n?u:v,s.b_in_a=n?v:u,s.overlap=y,s.overlap_x=n?-x:x,s.overlap_y=n?-m:m),!0}function l(_,t,s,n,e=null){const i=_.length,r=t.length;if(!i||!r)return!0;let h=null,o=null,l=null,a=null;for(let t=0,e=1;ti)&&(h=i),(null===o||oi)&&(l=i),(null===a||aa||oa)_=h-a,e.a_in_b=!1;else{const t=o-l,s=a-h;_=ti){const t=_<0?-1:1;e.overlap=i,e.overlap_x=s*t,e.overlap_y=n*t}}return!1}class a{constructor(_=0,t=0,s=0){this.x=_,this.y=t,this.padding=s,this._circle=!1,this._polygon=!1,this._point=!1,this._bvh=null,this._bvh_parent=null,this._bvh_branch=!1,this._bvh_padding=s,this._bvh_min_x=0,this._bvh_min_y=0,this._bvh_max_x=0,this._bvh_max_y=0}collides(_,t=null,s=!0){return h(this,_,t,s)}potentials(){const _=this._bvh;if(null===_)throw new Error("Body does not belong to a collision system");return _.potentials(this)}remove(){const _=this._bvh;_&&_.remove(this,!1)}createResult(){return new r}static createResult(){return new r}}class c extends a{constructor(_=0,t=0,s=0,n=1,e=0){super(_,t,e),this.radius=s,this.scale=n}draw(_){const t=this.x,s=this.y,n=this.radius*this.scale;_.moveTo(t+n,s),_.arc(t,s,n,0,2*Math.PI)}}class b extends a{constructor(_=0,t=0,s=[],n=0,e=1,i=1,r=0){super(_,t,r),this.angle=n,this.scale_x=e,this.scale_y=i,this._polygon=!0,this._x=_,this._y=t,this._angle=n,this._scale_x=e,this._scale_y=i,this._min_x=0,this._min_y=0,this._max_x=0,this._max_y=0,this._points=null,this._coords=null,this._edges=null,this._normals=null,this._dirty_coords=!0,this._dirty_normals=!0,b.prototype.setPoints.call(this,s)}draw(_){(this._dirty_coords||this.x!==this._x||this.y!==this._y||this.angle!==this._angle||this.scale_x!==this._scale_x||this.scale_y!==this._scale_y)&&this._calculateCoords();const t=this._coords;if(2===t.length)_.moveTo(t[0],t[1]),_.arc(t[0],t[1],1,0,2*Math.PI);else{_.moveTo(t[0],t[1]);for(let s=2;s4&&_.lineTo(t[0],t[1])}}setPoints(_){const t=_.length;this._points=new Float64Array(2*t),this._coords=new Float64Array(2*t),this._edges=new Float64Array(2*t),this._normals=new Float64Array(2*t);const s=this._points;for(let n=0,e=0,i=1;nl&&(l=h),uc&&(c=u))}this._x=_,this._y=t,this._angle=s,this._scale_x=n,this._scale_y=e,this._min_x=o,this._min_y=a,this._max_x=l,this._max_y=c,this._dirty_coords=!1,this._dirty_normals=!0}_calculateNormals(){const _=this._coords,t=this._edges,s=this._normals,n=_.length;for(let e=0,i=1;e} [points = []] An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]\r\n\t * @param {Number} [angle = 0] The starting rotation in radians\r\n\t * @param {Number} [scale_x = 1] The starting scale along the X axis\r\n\t * @param {Number} [scale_y = 1] The starting scale long the Y axis\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t * @returns {Polygon}\r\n\t */\r\n\tcreatePolygon(x = 0, y = 0, points = [[0, 0]], angle = 0, scale_x = 1, scale_y = 1, padding = 0) {\r\n\t\tconst body = new _modules_Polygon_mjs__WEBPACK_IMPORTED_MODULE_2__[\"default\"](x, y, points, angle, scale_x, scale_y, padding);\r\n\r\n\t\tthis._bvh.insert(body);\r\n\r\n\t\treturn body;\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a {@link Point} and inserts it into the collision system\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t * @returns {Point}\r\n\t */\r\n\tcreatePoint(x = 0, y = 0, padding = 0) {\r\n\t\tconst body = new _modules_Point_mjs__WEBPACK_IMPORTED_MODULE_3__[\"default\"](x, y, padding);\r\n\r\n\t\tthis._bvh.insert(body);\r\n\r\n\t\treturn body;\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a {@link Result} used to collect the detailed results of a collision test\r\n\t */\r\n\tcreateResult() {\r\n\t\treturn new _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__[\"default\"]();\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a Result used to collect the detailed results of a collision test\r\n\t */\r\n\tstatic createResult() {\r\n\t\treturn new _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__[\"default\"]();\r\n\t}\r\n\r\n\t/**\r\n\t * Inserts bodies into the collision system\r\n\t * @param {...Circle|...Polygon|...Point} bodies\r\n\t */\r\n\tinsert(...bodies) {\r\n\t\tfor(const body of bodies) {\r\n\t\t\tthis._bvh.insert(body, false);\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * Removes bodies from the collision system\r\n\t * @param {...Circle|...Polygon|...Point} bodies\r\n\t */\r\n\tremove(...bodies) {\r\n\t\tfor(const body of bodies) {\r\n\t\t\tthis._bvh.remove(body, false);\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * Updates the collision system. This should be called before any collisions are tested.\r\n\t */\r\n\tupdate() {\r\n\t\tthis._bvh.update();\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the bodies within the system to a CanvasRenderingContext2D's current path\r\n\t * @param {CanvasRenderingContext2D} context The context to draw to\r\n\t */\r\n\tdraw(context) {\r\n\t\treturn this._bvh.draw(context);\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the system's BVH to a CanvasRenderingContext2D's current path. This is useful for testing out different padding values for bodies.\r\n\t * @param {CanvasRenderingContext2D} context The context to draw to\r\n\t */\r\n\tdrawBVH(context) {\r\n\t\treturn this._bvh.drawBVH(context);\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a list of potential collisions for a body\r\n\t * @param {Circle|Polygon|Point} body The body to test for potential collisions against\r\n\t * @returns {Array}\r\n\t */\r\n\tpotentials(body) {\r\n\t\treturn this._bvh.potentials(body);\r\n\t}\r\n\r\n\t/**\r\n\t * Determines if two bodies are colliding\r\n\t * @param {Circle|Polygon|Point} target The target body to test against\r\n\t * @param {Result} [result = null] A Result object on which to store information about the collision\r\n\t * @param {Boolean} [aabb = true] Set to false to skip the AABB test (useful if you use your own potential collision heuristic)\r\n\t * @returns {Boolean}\r\n\t */\r\n\tcollides(source, target, result = null, aabb = true) {\r\n\t\treturn Object(_modules_SAT_mjs__WEBPACK_IMPORTED_MODULE_5__[\"default\"])(source, target, result, aabb);\r\n\t}\r\n};\r\n\r\nconst toExport = {\r\n\tCollisions,\r\n\tResult: _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\r\n\tCircle: _modules_Circle_mjs__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\r\n\tPolygon: _modules_Polygon_mjs__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\r\n\tPoint: _modules_Point_mjs__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\r\n};\r\n\r\nmodule.exports = toExport; \r\n\n\n//# sourceURL=webpack:///./src/Collisions.mjs?"); + +/***/ }), + +/***/ "./src/modules/BVH.mjs": +/*!*****************************!*\ + !*** ./src/modules/BVH.mjs ***! + \*****************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return BVH; });\n/* harmony import */ var _BVHBranch_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./BVHBranch.mjs */ \"./src/modules/BVHBranch.mjs\");\n\r\n\r\n/**\r\n * A Bounding Volume Hierarchy (BVH) used to find potential collisions quickly\r\n * @class\r\n * @private\r\n */\r\nclass BVH {\r\n\t/**\r\n\t * @constructor\r\n\t */\r\n\tconstructor() {\r\n\t\t/** @private */\r\n\t\tthis._hierarchy = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bodies = [];\r\n\r\n\t\t/** @private */\r\n\t\tthis._dirty_branches = [];\r\n\t}\r\n\r\n\t/**\r\n\t * Inserts a body into the BVH\r\n\t * @param {Circle|Polygon|Point} body The body to insert\r\n\t * @param {Boolean} [updating = false] Set to true if the body already exists in the BVH (used internally when updating the body's position)\r\n\t */\r\n\tinsert(body, updating = false) {\r\n\t\tif(!updating) {\r\n\t\t\tconst bvh = body._bvh;\r\n\r\n\t\t\tif(bvh && bvh !== this) {\r\n\t\t\t\tthrow new Error('Body belongs to another collision system');\r\n\t\t\t}\r\n\r\n\t\t\tbody._bvh = this;\r\n\t\t\tthis._bodies.push(body);\r\n\t\t}\r\n\r\n\t\tconst polygon = body._polygon;\r\n\t\tconst body_x = body.x;\r\n\t\tconst body_y = body.y;\r\n\r\n\t\tif(polygon) {\r\n\t\t\tif(\r\n\t\t\t\tbody._dirty_coords ||\r\n\t\t\t\tbody.x !== body._x ||\r\n\t\t\t\tbody.y !== body._y ||\r\n\t\t\t\tbody.angle !== body._angle ||\r\n\t\t\t\tbody.scale_x !== body._scale_x ||\r\n\t\t\t\tbody.scale_y !== body._scale_y\r\n\t\t\t) {\r\n\t\t\t\tbody._calculateCoords();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst padding = body._bvh_padding;\r\n\t\tconst radius = polygon ? 0 : body.radius * body.scale;\r\n\t\tconst body_min_x = (polygon ? body._min_x : body_x - radius) - padding;\r\n\t\tconst body_min_y = (polygon ? body._min_y : body_y - radius) - padding;\r\n\t\tconst body_max_x = (polygon ? body._max_x : body_x + radius) + padding;\r\n\t\tconst body_max_y = (polygon ? body._max_y : body_y + radius) + padding;\r\n\r\n\t\tbody._bvh_min_x = body_min_x;\r\n\t\tbody._bvh_min_y = body_min_y;\r\n\t\tbody._bvh_max_x = body_max_x;\r\n\t\tbody._bvh_max_y = body_max_y;\r\n\r\n\t\tlet current = this._hierarchy;\r\n\t\tlet sort = 0;\r\n\r\n\t\tif(!current) {\r\n\t\t\tthis._hierarchy = body;\r\n\t\t}\r\n\t\telse {\r\n\t\t\twhile(true) {\r\n\t\t\t\t// Branch\r\n\t\t\t\tif(current._bvh_branch) {\r\n\t\t\t\t\tconst left = current._bvh_left;\r\n\t\t\t\t\tconst left_min_y = left._bvh_min_y;\r\n\t\t\t\t\tconst left_max_x = left._bvh_max_x;\r\n\t\t\t\t\tconst left_max_y = left._bvh_max_y;\r\n\t\t\t\t\tconst left_new_min_x = body_min_x < left._bvh_min_x ? body_min_x : left._bvh_min_x;\r\n\t\t\t\t\tconst left_new_min_y = body_min_y < left_min_y ? body_min_y : left_min_y;\r\n\t\t\t\t\tconst left_new_max_x = body_max_x > left_max_x ? body_max_x : left_max_x;\r\n\t\t\t\t\tconst left_new_max_y = body_max_y > left_max_y ? body_max_y : left_max_y;\r\n\t\t\t\t\tconst left_volume = (left_max_x - left._bvh_min_x) * (left_max_y - left_min_y);\r\n\t\t\t\t\tconst left_new_volume = (left_new_max_x - left_new_min_x) * (left_new_max_y - left_new_min_y);\r\n\t\t\t\t\tconst left_difference = left_new_volume - left_volume;\r\n\r\n\t\t\t\t\tconst right = current._bvh_right;\r\n\t\t\t\t\tconst right_min_x = right._bvh_min_x;\r\n\t\t\t\t\tconst right_min_y = right._bvh_min_y;\r\n\t\t\t\t\tconst right_max_x = right._bvh_max_x;\r\n\t\t\t\t\tconst right_max_y = right._bvh_max_y;\r\n\t\t\t\t\tconst right_new_min_x = body_min_x < right_min_x ? body_min_x : right_min_x;\r\n\t\t\t\t\tconst right_new_min_y = body_min_y < right_min_y ? body_min_y : right_min_y;\r\n\t\t\t\t\tconst right_new_max_x = body_max_x > right_max_x ? body_max_x : right_max_x;\r\n\t\t\t\t\tconst right_new_max_y = body_max_y > right_max_y ? body_max_y : right_max_y;\r\n\t\t\t\t\tconst right_volume = (right_max_x - right_min_x) * (right_max_y - right_min_y);\r\n\t\t\t\t\tconst right_new_volume = (right_new_max_x - right_new_min_x) * (right_new_max_y - right_new_min_y);\r\n\t\t\t\t\tconst right_difference = right_new_volume - right_volume;\r\n\r\n\t\t\t\t\tcurrent._bvh_sort = sort++;\r\n\t\t\t\t\tcurrent._bvh_min_x = left_new_min_x < right_new_min_x ? left_new_min_x : right_new_min_x;\r\n\t\t\t\t\tcurrent._bvh_min_y = left_new_min_y < right_new_min_y ? left_new_min_y : right_new_min_y;\r\n\t\t\t\t\tcurrent._bvh_max_x = left_new_max_x > right_new_max_x ? left_new_max_x : right_new_max_x;\r\n\t\t\t\t\tcurrent._bvh_max_y = left_new_max_y > right_new_max_y ? left_new_max_y : right_new_max_y;\r\n\r\n\t\t\t\t\tcurrent = left_difference <= right_difference ? left : right;\r\n\t\t\t\t}\r\n\t\t\t\t// Leaf\r\n\t\t\t\telse {\r\n\t\t\t\t\tconst grandparent = current._bvh_parent;\r\n\t\t\t\t\tconst parent_min_x = current._bvh_min_x;\r\n\t\t\t\t\tconst parent_min_y = current._bvh_min_y;\r\n\t\t\t\t\tconst parent_max_x = current._bvh_max_x;\r\n\t\t\t\t\tconst parent_max_y = current._bvh_max_y;\r\n\t\t\t\t\tconst new_parent = current._bvh_parent = body._bvh_parent = _BVHBranch_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"].getBranch();\r\n\r\n\t\t\t\t\tnew_parent._bvh_parent = grandparent;\r\n\t\t\t\t\tnew_parent._bvh_left = current;\r\n\t\t\t\t\tnew_parent._bvh_right = body;\r\n\t\t\t\t\tnew_parent._bvh_sort = sort++;\r\n\t\t\t\t\tnew_parent._bvh_min_x = body_min_x < parent_min_x ? body_min_x : parent_min_x;\r\n\t\t\t\t\tnew_parent._bvh_min_y = body_min_y < parent_min_y ? body_min_y : parent_min_y;\r\n\t\t\t\t\tnew_parent._bvh_max_x = body_max_x > parent_max_x ? body_max_x : parent_max_x;\r\n\t\t\t\t\tnew_parent._bvh_max_y = body_max_y > parent_max_y ? body_max_y : parent_max_y;\r\n\r\n\t\t\t\t\tif(!grandparent) {\r\n\t\t\t\t\t\tthis._hierarchy = new_parent;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if(grandparent._bvh_left === current) {\r\n\t\t\t\t\t\tgrandparent._bvh_left = new_parent;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tgrandparent._bvh_right = new_parent;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Removes a body from the BVH\r\n\t * @param {Circle|Polygon|Point} body The body to remove\r\n\t * @param {Boolean} [updating = false] Set to true if this is a temporary removal (used internally when updating the body's position)\r\n\t */\r\n\tremove(body, updating = false) {\r\n\t\tif(!updating) {\r\n\t\t\tconst bvh = body._bvh;\r\n\r\n\t\t\tif(bvh && bvh !== this) {\r\n\t\t\t\tthrow new Error('Body belongs to another collision system');\r\n\t\t\t}\r\n\r\n\t\t\tbody._bvh = null;\r\n\t\t\tthis._bodies.splice(this._bodies.indexOf(body), 1);\r\n\t\t}\r\n\r\n\t\tif(this._hierarchy === body) {\r\n\t\t\tthis._hierarchy = null;\r\n\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst parent = body._bvh_parent;\r\n\t\tconst grandparent = parent._bvh_parent;\r\n\t\tconst parent_left = parent._bvh_left;\r\n\t\tconst sibling = parent_left === body ? parent._bvh_right : parent_left;\r\n\r\n\t\tsibling._bvh_parent = grandparent;\r\n\r\n\t\tif(sibling._bvh_branch) {\r\n\t\t\tsibling._bvh_sort = parent._bvh_sort;\r\n\t\t}\r\n\r\n\t\tif(grandparent) {\r\n\t\t\tif(grandparent._bvh_left === parent) {\r\n\t\t\t\tgrandparent._bvh_left = sibling;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tgrandparent._bvh_right = sibling;\r\n\t\t\t}\r\n\r\n\t\t\tlet branch = grandparent;\r\n\r\n\t\t\twhile(branch) {\r\n\t\t\t\tconst left = branch._bvh_left;\r\n\t\t\t\tconst left_min_x = left._bvh_min_x;\r\n\t\t\t\tconst left_min_y = left._bvh_min_y;\r\n\t\t\t\tconst left_max_x = left._bvh_max_x;\r\n\t\t\t\tconst left_max_y = left._bvh_max_y;\r\n\r\n\t\t\t\tconst right = branch._bvh_right;\r\n\t\t\t\tconst right_min_x = right._bvh_min_x;\r\n\t\t\t\tconst right_min_y = right._bvh_min_y;\r\n\t\t\t\tconst right_max_x = right._bvh_max_x;\r\n\t\t\t\tconst right_max_y = right._bvh_max_y;\r\n\r\n\t\t\t\tbranch._bvh_min_x = left_min_x < right_min_x ? left_min_x : right_min_x;\r\n\t\t\t\tbranch._bvh_min_y = left_min_y < right_min_y ? left_min_y : right_min_y;\r\n\t\t\t\tbranch._bvh_max_x = left_max_x > right_max_x ? left_max_x : right_max_x;\r\n\t\t\t\tbranch._bvh_max_y = left_max_y > right_max_y ? left_max_y : right_max_y;\r\n\r\n\t\t\t\tbranch = branch._bvh_parent;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse {\r\n\t\t\tthis._hierarchy = sibling;\r\n\t\t}\r\n\r\n\t\t_BVHBranch_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"].releaseBranch(parent);\r\n\t}\r\n\r\n\t/**\r\n\t * Updates the BVH. Moved bodies are removed/inserted.\r\n\t */\r\n\tupdate() {\r\n\t\tconst bodies = this._bodies;\r\n\t\tconst count = bodies.length;\r\n\r\n\t\tfor(let i = 0; i < count; ++i) {\r\n\t\t\tconst body = bodies[i];\r\n\r\n\t\t\tlet update = false;\r\n\r\n\t\t\tif(!update && body.padding !== body._bvh_padding) {\r\n\t\t\t\tbody._bvh_padding = body.padding;\r\n\t\t\t\tupdate = true;\r\n\t\t\t}\r\n\r\n\t\t\tif(!update) {\r\n\t\t\t\tconst polygon = body._polygon;\r\n\r\n\t\t\t\tif(polygon) {\r\n\t\t\t\t\tif(\r\n\t\t\t\t\t\tbody._dirty_coords ||\r\n\t\t\t\t\t\tbody.x !== body._x ||\r\n\t\t\t\t\t\tbody.y !== body._y ||\r\n\t\t\t\t\t\tbody.angle !== body._angle ||\r\n\t\t\t\t\t\tbody.scale_x !== body._scale_x ||\r\n\t\t\t\t\t\tbody.scale_y !== body._scale_y\r\n\t\t\t\t\t) {\r\n\t\t\t\t\t\tbody._calculateCoords();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tconst x = body.x;\r\n\t\t\t\tconst y = body.y;\r\n\t\t\t\tconst radius = polygon ? 0 : body.radius * body.scale;\r\n\t\t\t\tconst min_x = polygon ? body._min_x : x - radius;\r\n\t\t\t\tconst min_y = polygon ? body._min_y : y - radius;\r\n\t\t\t\tconst max_x = polygon ? body._max_x : x + radius;\r\n\t\t\t\tconst max_y = polygon ? body._max_y : y + radius;\r\n\r\n\t\t\t\tupdate = min_x < body._bvh_min_x || min_y < body._bvh_min_y || max_x > body._bvh_max_x || max_y > body._bvh_max_y;\r\n\t\t\t}\r\n\r\n\t\t\tif(update) {\r\n\t\t\t\tthis.remove(body, true);\r\n\t\t\t\tthis.insert(body, true);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a list of potential collisions for a body\r\n\t * @param {Circle|Polygon|Point} body The body to test\r\n\t * @returns {Array}\r\n\t */\r\n\tpotentials(body) {\r\n\t\tconst results = [];\r\n\t\tconst min_x = body._bvh_min_x;\r\n\t\tconst min_y = body._bvh_min_y;\r\n\t\tconst max_x = body._bvh_max_x;\r\n\t\tconst max_y = body._bvh_max_y;\r\n\r\n\t\tlet current = this._hierarchy;\r\n\t\tlet traverse_left = true;\r\n\r\n\t\tif(!current || !current._bvh_branch) {\r\n\t\t\treturn results;\r\n\t\t}\r\n\r\n\t\twhile(current) {\r\n\t\t\tif(traverse_left) {\r\n\t\t\t\ttraverse_left = false;\r\n\r\n\t\t\t\tlet left = current._bvh_branch ? current._bvh_left : null;\r\n\r\n\t\t\t\twhile(\r\n\t\t\t\t\tleft &&\r\n\t\t\t\t\tleft._bvh_max_x >= min_x &&\r\n\t\t\t\t\tleft._bvh_max_y >= min_y &&\r\n\t\t\t\t\tleft._bvh_min_x <= max_x &&\r\n\t\t\t\t\tleft._bvh_min_y <= max_y\r\n\t\t\t\t) {\r\n\t\t\t\t\tcurrent = left;\r\n\t\t\t\t\tleft = current._bvh_branch ? current._bvh_left : null;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tconst branch = current._bvh_branch;\r\n\t\t\tconst right = branch ? current._bvh_right : null;\r\n\r\n\t\t\tif(\r\n\t\t\t\tright &&\r\n\t\t\t\tright._bvh_max_x > min_x &&\r\n\t\t\t\tright._bvh_max_y > min_y &&\r\n\t\t\t\tright._bvh_min_x < max_x &&\r\n\t\t\t\tright._bvh_min_y < max_y\r\n\t\t\t) {\r\n\t\t\t\tcurrent = right;\r\n\t\t\t\ttraverse_left = true;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tif(!branch && current !== body) {\r\n\t\t\t\t\tresults.push(current);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tlet parent = current._bvh_parent;\r\n\r\n\t\t\t\tif(parent) {\r\n\t\t\t\t\twhile(parent && parent._bvh_right === current) {\r\n\t\t\t\t\t\tcurrent = parent;\r\n\t\t\t\t\t\tparent = current._bvh_parent;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tcurrent = parent;\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn results;\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the bodies within the BVH to a CanvasRenderingContext2D's current path\r\n\t * @param {CanvasRenderingContext2D} context The context to draw to\r\n\t */\r\n\tdraw(context) {\r\n\t\tconst bodies = this._bodies;\r\n\t\tconst count = bodies.length;\r\n\r\n\t\tfor(let i = 0; i < count; ++i) {\r\n\t\t\tbodies[i].draw(context);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the BVH to a CanvasRenderingContext2D's current path. This is useful for testing out different padding values for bodies.\r\n\t * @param {CanvasRenderingContext2D} context The context to draw to\r\n\t */\r\n\tdrawBVH(context) {\r\n\t\tlet current = this._hierarchy;\r\n\t\tlet traverse_left = true;\r\n\r\n\t\twhile(current) {\r\n\t\t\tif(traverse_left) {\r\n\t\t\t\ttraverse_left = false;\r\n\r\n\t\t\t\tlet left = current._bvh_branch ? current._bvh_left : null;\r\n\r\n\t\t\t\twhile(left) {\r\n\t\t\t\t\tcurrent = left;\r\n\t\t\t\t\tleft = current._bvh_branch ? current._bvh_left : null;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tconst branch = current._bvh_branch;\r\n\t\t\tconst min_x = current._bvh_min_x;\r\n\t\t\tconst min_y = current._bvh_min_y;\r\n\t\t\tconst max_x = current._bvh_max_x;\r\n\t\t\tconst max_y = current._bvh_max_y;\r\n\t\t\tconst right = branch ? current._bvh_right : null;\r\n\r\n\t\t\tcontext.moveTo(min_x, min_y);\r\n\t\t\tcontext.lineTo(max_x, min_y);\r\n\t\t\tcontext.lineTo(max_x, max_y);\r\n\t\t\tcontext.lineTo(min_x, max_y);\r\n\t\t\tcontext.lineTo(min_x, min_y);\r\n\r\n\t\t\tif(right) {\r\n\t\t\t\tcurrent = right;\r\n\t\t\t\ttraverse_left = true;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tlet parent = current._bvh_parent;\r\n\r\n\t\t\t\tif(parent) {\r\n\t\t\t\t\twhile(parent && parent._bvh_right === current) {\r\n\t\t\t\t\t\tcurrent = parent;\r\n\t\t\t\t\t\tparent = current._bvh_parent;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tcurrent = parent;\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/BVH.mjs?"); + +/***/ }), + +/***/ "./src/modules/BVHBranch.mjs": +/*!***********************************!*\ + !*** ./src/modules/BVHBranch.mjs ***! + \***********************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return BVHBranch; });\n/**\r\n * @private\r\n */\r\nconst branch_pool = [];\r\n\r\n/**\r\n * A branch within a BVH\r\n * @class\r\n * @private\r\n */\r\nclass BVHBranch {\r\n\t/**\r\n\t * @constructor\r\n\t */\r\n\tconstructor() {\r\n\t\t/** @private */\r\n\t\tthis._bvh_parent = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_branch = true;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_left = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_right = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_sort = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_min_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_min_y = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_max_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_max_y = 0;\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a branch from the branch pool or creates a new branch\r\n\t * @returns {BVHBranch}\r\n\t */\r\n\tstatic getBranch() {\r\n\t\tif(branch_pool.length) {\r\n\t\t\treturn branch_pool.pop();\r\n\t\t}\r\n\r\n\t\treturn new BVHBranch();\r\n\t}\r\n\r\n\t/**\r\n\t * Releases a branch back into the branch pool\r\n\t * @param {BVHBranch} branch The branch to release\r\n\t */\r\n\tstatic releaseBranch(branch) {\r\n\t\tbranch_pool.push(branch);\r\n\t}\r\n\r\n\t/**\r\n\t * Sorting callback used to sort branches by deepest first\r\n\t * @param {BVHBranch} a The first branch\r\n\t * @param {BVHBranch} b The second branch\r\n\t * @returns {Number}\r\n\t */\r\n\tstatic sortBranches(a, b) {\r\n\t\treturn a.sort > b.sort ? -1 : 1;\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/BVHBranch.mjs?"); + +/***/ }), + +/***/ "./src/modules/Body.mjs": +/*!******************************!*\ + !*** ./src/modules/Body.mjs ***! + \******************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Body; });\n/* harmony import */ var _Result_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Result.mjs */ \"./src/modules/Result.mjs\");\n/* harmony import */ var _SAT_mjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./SAT.mjs */ \"./src/modules/SAT.mjs\");\n\r\n\r\n\r\n/**\r\n * The base class for bodies used to detect collisions\r\n * @class\r\n * @protected\r\n */\r\nclass Body {\r\n\t/**\r\n\t * @constructor\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t */\r\n\tconstructor(x = 0, y = 0, padding = 0) {\r\n\t\t/**\r\n\t\t * @desc The X coordinate of the body\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.x = x;\r\n\r\n\t\t/**\r\n\t\t * @desc The Y coordinate of the body\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.y = y;\r\n\r\n\t\t/**\r\n\t\t * @desc The amount to pad the bounding volume when testing for potential collisions\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.padding = padding;\r\n\r\n\t\t/** @private */\r\n\t\tthis._circle = false;\r\n\r\n\t\t/** @private */\r\n\t\tthis._polygon = false;\r\n\r\n\t\t/** @private */\r\n\t\tthis._point = false;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_parent = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_branch = false;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_padding = padding;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_min_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_min_y = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_max_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_max_y = 0;\r\n\t}\r\n\r\n\t/**\r\n\t * Determines if the body is colliding with another body\r\n\t * @param {Circle|Polygon|Point} target The target body to test against\r\n\t * @param {Result} [result = null] A Result object on which to store information about the collision\r\n\t * @param {Boolean} [aabb = true] Set to false to skip the AABB test (useful if you use your own potential collision heuristic)\r\n\t * @returns {Boolean}\r\n\t */\r\n\tcollides(target, result = null, aabb = true) {\r\n\t\treturn Object(_SAT_mjs__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(this, target, result, aabb);\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a list of potential collisions\r\n\t * @returns {Array}\r\n\t */\r\n\tpotentials() {\r\n\t\tconst bvh = this._bvh;\r\n\r\n\t\tif(bvh === null) {\r\n\t\t\tthrow new Error('Body does not belong to a collision system');\r\n\t\t}\r\n\r\n\t\treturn bvh.potentials(this);\r\n\t}\r\n\r\n\t/**\r\n\t * Removes the body from its current collision system\r\n\t */\r\n\tremove() {\r\n\t\tconst bvh = this._bvh;\r\n\r\n\t\tif(bvh) {\r\n\t\t\tbvh.remove(this, false);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a {@link Result} used to collect the detailed results of a collision test\r\n\t */\r\n\tcreateResult() {\r\n\t\treturn new _Result_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a Result used to collect the detailed results of a collision test\r\n\t */\r\n\tstatic createResult() {\r\n\t\treturn new _Result_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/Body.mjs?"); + +/***/ }), + +/***/ "./src/modules/Circle.mjs": +/*!********************************!*\ + !*** ./src/modules/Circle.mjs ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Circle; });\n/* harmony import */ var _Body_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Body.mjs */ \"./src/modules/Body.mjs\");\n\r\n\r\n/**\r\n * A circle used to detect collisions\r\n * @class\r\n */\r\nclass Circle extends _Body_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n\t/**\r\n\t * @constructor\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [radius = 0] The radius\r\n\t * @param {Number} [scale = 1] The scale\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t */\r\n\tconstructor(x = 0, y = 0, radius = 0, scale = 1, padding = 0) {\r\n\t\tsuper(x, y, padding);\r\n\r\n\t\t/**\r\n\t\t * @desc\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.radius = radius;\r\n\r\n\t\t/**\r\n\t\t * @desc\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.scale = scale;\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the circle to a CanvasRenderingContext2D's current path\r\n\t * @param {CanvasRenderingContext2D} context The context to add the arc to\r\n\t */\r\n\tdraw(context) {\r\n\t\tconst x = this.x;\r\n\t\tconst y = this.y;\r\n\t\tconst radius = this.radius * this.scale;\r\n\r\n\t\tcontext.moveTo(x + radius, y);\r\n\t\tcontext.arc(x, y, radius, 0, Math.PI * 2);\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/Circle.mjs?"); + +/***/ }), + +/***/ "./src/modules/Point.mjs": +/*!*******************************!*\ + !*** ./src/modules/Point.mjs ***! + \*******************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Point; });\n/* harmony import */ var _Polygon_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Polygon.mjs */ \"./src/modules/Polygon.mjs\");\n\r\n\r\n/**\r\n * A point used to detect collisions\r\n * @class\r\n */\r\nclass Point extends _Polygon_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n\t/**\r\n\t * @constructor\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t */\r\n\tconstructor(x = 0, y = 0, padding = 0) {\r\n\t\tsuper(x, y, [[0, 0]], 0, 1, 1, padding);\r\n\r\n\t\t/** @private */\r\n\t\tthis._point = true;\r\n\t}\r\n};\r\n\r\nPoint.prototype.setPoints = undefined;\r\n\n\n//# sourceURL=webpack:///./src/modules/Point.mjs?"); + +/***/ }), + +/***/ "./src/modules/Polygon.mjs": +/*!*********************************!*\ + !*** ./src/modules/Polygon.mjs ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Polygon; });\n/* harmony import */ var _Body_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Body.mjs */ \"./src/modules/Body.mjs\");\n\r\n\r\n/**\r\n * A polygon used to detect collisions\r\n * @class\r\n */\r\nclass Polygon extends _Body_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n\t/**\r\n\t * @constructor\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Array} [points = []] An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]\r\n\t * @param {Number} [angle = 0] The starting rotation in radians\r\n\t * @param {Number} [scale_x = 1] The starting scale along the X axis\r\n\t * @param {Number} [scale_y = 1] The starting scale long the Y axis\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t */\r\n\tconstructor(x = 0, y = 0, points = [], angle = 0, scale_x = 1, scale_y = 1, padding = 0) {\r\n\t\tsuper(x, y, padding);\r\n\r\n\t\t/**\r\n\t\t * @desc The angle of the body in radians\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.angle = angle;\r\n\r\n\t\t/**\r\n\t\t * @desc The scale of the body along the X axis\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.scale_x = scale_x;\r\n\r\n\t\t/**\r\n\t\t * @desc The scale of the body along the Y axis\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.scale_y = scale_y;\r\n\r\n\r\n\t\t/** @private */\r\n\t\tthis._polygon = true;\r\n\r\n\t\t/** @private */\r\n\t\tthis._x = x;\r\n\r\n\t\t/** @private */\r\n\t\tthis._y = y;\r\n\r\n\t\t/** @private */\r\n\t\tthis._angle = angle;\r\n\r\n\t\t/** @private */\r\n\t\tthis._scale_x = scale_x;\r\n\r\n\t\t/** @private */\r\n\t\tthis._scale_y = scale_y;\r\n\r\n\t\t/** @private */\r\n\t\tthis._min_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._min_y = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._max_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._max_y = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._points = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._coords = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._edges = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._normals = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._dirty_coords = true;\r\n\r\n\t\t/** @private */\r\n\t\tthis._dirty_normals = true;\r\n\r\n\t\tPolygon.prototype.setPoints.call(this, points);\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the polygon to a CanvasRenderingContext2D's current path\r\n\t * @param {CanvasRenderingContext2D} context The context to add the shape to\r\n\t */\r\n\tdraw(context) {\r\n\t\tif(\r\n\t\t\tthis._dirty_coords ||\r\n\t\t\tthis.x !== this._x ||\r\n\t\t\tthis.y !== this._y ||\r\n\t\t\tthis.angle !== this._angle ||\r\n\t\t\tthis.scale_x !== this._scale_x ||\r\n\t\t\tthis.scale_y !== this._scale_y\r\n\t\t) {\r\n\t\t\tthis._calculateCoords();\r\n\t\t}\r\n\r\n\t\tconst coords = this._coords;\r\n\r\n\t\tif(coords.length === 2) {\r\n\t\t\tcontext.moveTo(coords[0], coords[1]);\r\n\t\t\tcontext.arc(coords[0], coords[1], 1, 0, Math.PI * 2);\r\n\t\t}\r\n\t\telse {\r\n\t\t\tcontext.moveTo(coords[0], coords[1]);\r\n\r\n\t\t\tfor(let i = 2; i < coords.length; i += 2) {\r\n\t\t\t\tcontext.lineTo(coords[i], coords[i + 1]);\r\n\t\t\t}\r\n\r\n\t\t\tif(coords.length > 4) {\r\n\t\t\t\tcontext.lineTo(coords[0], coords[1]);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Sets the points making up the polygon. It's important to use this function when changing the polygon's shape to ensure internal data is also updated.\r\n\t * @param {Array} new_points An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]\r\n\t */\r\n\tsetPoints(new_points) {\r\n\t\tconst count = new_points.length;\r\n\r\n\t\tthis._points = new Float64Array(count * 2);\r\n\t\tthis._coords = new Float64Array(count * 2);\r\n\t\tthis._edges = new Float64Array(count * 2);\r\n\t\tthis._normals = new Float64Array(count * 2);\r\n\r\n\t\tconst points = this._points;\r\n\r\n\t\tfor(let i = 0, ix = 0, iy = 1; i < count; ++i, ix += 2, iy += 2) {\r\n\t\t\tconst new_point = new_points[i];\r\n\r\n\t\t\tpoints[ix] = new_point[0];\r\n\t\t\tpoints[iy] = new_point[1];\r\n\t\t}\r\n\r\n\t\tthis._dirty_coords = true;\r\n\t}\r\n\r\n\t/**\r\n\t * Calculates and caches the polygon's world coordinates based on its points, angle, and scale\r\n\t */\r\n\t_calculateCoords() {\r\n\t\tconst x = this.x;\r\n\t\tconst y = this.y;\r\n\t\tconst angle = this.angle;\r\n\t\tconst scale_x = this.scale_x;\r\n\t\tconst scale_y = this.scale_y;\r\n\t\tconst points = this._points;\r\n\t\tconst coords = this._coords;\r\n\t\tconst count = points.length;\r\n\r\n\t\tlet min_x;\r\n\t\tlet max_x;\r\n\t\tlet min_y;\r\n\t\tlet max_y;\r\n\r\n\t\tfor(let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {\r\n\t\t\tlet coord_x = points[ix] * scale_x;\r\n\t\t\tlet coord_y = points[iy] * scale_y;\r\n\r\n\t\t\tif(angle) {\r\n\t\t\t\tconst cos = Math.cos(angle);\r\n\t\t\t\tconst sin = Math.sin(angle);\r\n\t\t\t\tconst tmp_x = coord_x;\r\n\t\t\t\tconst tmp_y = coord_y;\r\n\r\n\t\t\t\tcoord_x = tmp_x * cos - tmp_y * sin;\r\n\t\t\t\tcoord_y = tmp_x * sin + tmp_y * cos;\r\n\t\t\t}\r\n\r\n\t\t\tcoord_x += x;\r\n\t\t\tcoord_y += y;\r\n\r\n\t\t\tcoords[ix] = coord_x;\r\n\t\t\tcoords[iy] = coord_y;\r\n\r\n\t\t\tif(ix === 0) {\r\n\t\t\t\tmin_x = max_x = coord_x;\r\n\t\t\t\tmin_y = max_y = coord_y;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tif(coord_x < min_x) {\r\n\t\t\t\t\tmin_x = coord_x;\r\n\t\t\t\t}\r\n\t\t\t\telse if(coord_x > max_x) {\r\n\t\t\t\t\tmax_x = coord_x;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif(coord_y < min_y) {\r\n\t\t\t\t\tmin_y = coord_y;\r\n\t\t\t\t}\r\n\t\t\t\telse if(coord_y > max_y) {\r\n\t\t\t\t\tmax_y = coord_y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis._x = x;\r\n\t\tthis._y = y;\r\n\t\tthis._angle = angle;\r\n\t\tthis._scale_x = scale_x;\r\n\t\tthis._scale_y = scale_y;\r\n\t\tthis._min_x = min_x;\r\n\t\tthis._min_y = min_y;\r\n\t\tthis._max_x = max_x;\r\n\t\tthis._max_y = max_y;\r\n\t\tthis._dirty_coords = false;\r\n\t\tthis._dirty_normals = true;\r\n\t}\r\n\r\n\t/**\r\n\t * Calculates the normals and edges of the polygon's sides\r\n\t */\r\n\t_calculateNormals() {\r\n\t\tconst coords = this._coords;\r\n\t\tconst edges = this._edges;\r\n\t\tconst normals = this._normals;\r\n\t\tconst count = coords.length;\r\n\r\n\t\tfor(let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {\r\n\t\t\tconst next = ix + 2 < count ? ix + 2 : 0;\r\n\t\t\tconst x = coords[next] - coords[ix];\r\n\t\t\tconst y = coords[next + 1] - coords[iy];\r\n\t\t\tconst length = x || y ? Math.sqrt(x * x + y * y) : 0;\r\n\r\n\t\t\tedges[ix] = x;\r\n\t\t\tedges[iy] = y;\r\n\t\t\tnormals[ix] = length ? y / length : 0;\r\n\t\t\tnormals[iy] = length ? -x / length : 0;\r\n\t\t}\r\n\r\n\t\tthis._dirty_normals = false;\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/Polygon.mjs?"); + +/***/ }), + +/***/ "./src/modules/Result.mjs": +/*!********************************!*\ + !*** ./src/modules/Result.mjs ***! + \********************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Result; });\n/**\r\n * An object used to collect the detailed results of a collision test\r\n *\r\n * > **Note:** It is highly recommended you recycle the same Result object if possible in order to avoid wasting memory\r\n * @class\r\n */\r\nclass Result {\r\n\t/**\r\n\t * @constructor\r\n\t */\r\n\tconstructor() {\r\n\t\t/**\r\n\t\t * @desc True if a collision was detected\r\n\t\t * @type {Boolean}\r\n\t\t */\r\n\t\tthis.collision = false;\r\n\r\n\t\t/**\r\n\t\t * @desc The source body tested\r\n\t\t * @type {Circle|Polygon|Point}\r\n\t\t */\r\n\t\tthis.a = null;\r\n\r\n\t\t/**\r\n\t\t * @desc The target body tested against\r\n\t\t * @type {Circle|Polygon|Point}\r\n\t\t */\r\n\t\tthis.b = null;\r\n\r\n\t\t/**\r\n\t\t * @desc True if A is completely contained within B\r\n\t\t * @type {Boolean}\r\n\t\t */\r\n\t\tthis.a_in_b = false;\r\n\r\n\t\t/**\r\n\t\t * @desc True if B is completely contained within A\r\n\t\t * @type {Boolean}\r\n\t\t */\r\n\t\tthis.b_in_a = false;\r\n\r\n\t\t/**\r\n\t\t * @desc The magnitude of the shortest axis of overlap\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.overlap = 0;\r\n\r\n\t\t/**\r\n\t\t * @desc The X direction of the shortest axis of overlap\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.overlap_x = 0;\r\n\r\n\t\t/**\r\n\t\t * @desc The Y direction of the shortest axis of overlap\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.overlap_y = 0;\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/Result.mjs?"); + +/***/ }), + +/***/ "./src/modules/SAT.mjs": +/*!*****************************!*\ + !*** ./src/modules/SAT.mjs ***! + \*****************************/ +/*! exports provided: default */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return SAT; });\n/**\r\n * Determines if two bodies are colliding using the Separating Axis Theorem\r\n * @private\r\n * @param {Circle|Polygon|Point} a The source body to test\r\n * @param {Circle|Polygon|Point} b The target body to test against\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @param {Boolean} [aabb = true] Set to false to skip the AABB test (useful if you use your own collision heuristic)\r\n * @returns {Boolean}\r\n */\r\nfunction SAT(a, b, result = null, aabb = true) {\r\n\tconst a_polygon = a._polygon;\r\n\tconst b_polygon = b._polygon;\r\n\r\n\tlet collision = false;\r\n\r\n\tif(result) {\r\n\t\tresult.a = a;\r\n\t\tresult.b = b;\r\n\t\tresult.a_in_b = true;\r\n\t\tresult.b_in_a = true;\r\n\t\tresult.overlap = null;\r\n\t\tresult.overlap_x = 0;\r\n\t\tresult.overlap_y = 0;\r\n\t}\r\n\r\n\tif(a_polygon) {\r\n\t\tif(\r\n\t\t\ta._dirty_coords ||\r\n\t\t\ta.x !== a._x ||\r\n\t\t\ta.y !== a._y ||\r\n\t\t\ta.angle !== a._angle ||\r\n\t\t\ta.scale_x !== a._scale_x ||\r\n\t\t\ta.scale_y !== a._scale_y\r\n\t\t) {\r\n\t\t\ta._calculateCoords();\r\n\t\t}\r\n\t}\r\n\r\n\tif(b_polygon) {\r\n\t\tif(\r\n\t\t\tb._dirty_coords ||\r\n\t\t\tb.x !== b._x ||\r\n\t\t\tb.y !== b._y ||\r\n\t\t\tb.angle !== b._angle ||\r\n\t\t\tb.scale_x !== b._scale_x ||\r\n\t\t\tb.scale_y !== b._scale_y\r\n\t\t) {\r\n\t\t\tb._calculateCoords();\r\n\t\t}\r\n\t}\r\n\r\n\tif(!aabb || aabbAABB(a, b)) {\r\n\t\tif(a_polygon && a._dirty_normals) {\r\n\t\t\ta._calculateNormals();\r\n\t\t}\r\n\r\n\t\tif(b_polygon && b._dirty_normals) {\r\n\t\t\tb._calculateNormals();\r\n\t\t}\r\n\r\n\t\tcollision = (\r\n\t\t\ta_polygon && b_polygon ? polygonPolygon(a, b, result) :\r\n\t\t\ta_polygon ? polygonCircle(a, b, result, false) :\r\n\t\t\tb_polygon ? polygonCircle(b, a, result, true) :\r\n\t\t\tcircleCircle(a, b, result)\r\n\t\t);\r\n\t}\r\n\r\n\tif(result) {\r\n\t\tresult.collision = collision;\r\n\t}\r\n\r\n\treturn collision;\r\n};\r\n\r\n/**\r\n * Determines if two bodies' axis aligned bounding boxes are colliding\r\n * @param {Circle|Polygon|Point} a The source body to test\r\n * @param {Circle|Polygon|Point} b The target body to test against\r\n */\r\nfunction aabbAABB(a, b) {\r\n\tconst a_polygon = a._polygon;\r\n\tconst a_x = a_polygon ? 0 : a.x;\r\n\tconst a_y = a_polygon ? 0 : a.y;\r\n\tconst a_radius = a_polygon ? 0 : a.radius * a.scale;\r\n\tconst a_min_x = a_polygon ? a._min_x : a_x - a_radius;\r\n\tconst a_min_y = a_polygon ? a._min_y : a_y - a_radius;\r\n\tconst a_max_x = a_polygon ? a._max_x : a_x + a_radius;\r\n\tconst a_max_y = a_polygon ? a._max_y : a_y + a_radius;\r\n\r\n\tconst b_polygon = b._polygon;\r\n\tconst b_x = b_polygon ? 0 : b.x;\r\n\tconst b_y = b_polygon ? 0 : b.y;\r\n\tconst b_radius = b_polygon ? 0 : b.radius * b.scale;\r\n\tconst b_min_x = b_polygon ? b._min_x : b_x - b_radius;\r\n\tconst b_min_y = b_polygon ? b._min_y : b_y - b_radius;\r\n\tconst b_max_x = b_polygon ? b._max_x : b_x + b_radius;\r\n\tconst b_max_y = b_polygon ? b._max_y : b_y + b_radius;\r\n\r\n\treturn a_min_x < b_max_x && a_min_y < b_max_y && a_max_x > b_min_x && a_max_y > b_min_y;\r\n}\r\n\r\n/**\r\n * Determines if two polygons are colliding\r\n * @param {Polygon} a The source polygon to test\r\n * @param {Polygon} b The target polygon to test against\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @returns {Boolean}\r\n */\r\nfunction polygonPolygon(a, b, result = null) {\r\n\tconst a_count = a._coords.length;\r\n\tconst b_count = b._coords.length;\r\n\r\n\t// Handle points specially\r\n\tif(a_count === 2 && b_count === 2) {\r\n\t\tconst a_coords = a._coords;\r\n\t\tconst b_coords = b._coords;\r\n\r\n\t\tif(result) {\r\n\t\t\tresult.overlap = 0;\r\n\t\t}\r\n\r\n\t\treturn a_coords[0] === b_coords[0] && a_coords[1] === b_coords[1];\r\n\t}\r\n\r\n\tconst a_coords = a._coords;\r\n\tconst b_coords = b._coords;\r\n\tconst a_normals = a._normals;\r\n\tconst b_normals = b._normals;\r\n\r\n\tif(a_count > 2) {\r\n\t\tfor(let ix = 0, iy = 1; ix < a_count; ix += 2, iy += 2) {\r\n\t\t\tif(separatingAxis(a_coords, b_coords, a_normals[ix], a_normals[iy], result)) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif(b_count > 2) {\r\n\t\tfor(let ix = 0, iy = 1; ix < b_count; ix += 2, iy += 2) {\r\n\t\t\tif(separatingAxis(a_coords, b_coords, b_normals[ix], b_normals[iy], result)) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\n/**\r\n * Determines if a polygon and a circle are colliding\r\n * @param {Polygon} a The source polygon to test\r\n * @param {Circle} b The target circle to test against\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @param {Boolean} [reverse = false] Set to true to reverse a and b in the result parameter when testing circle->polygon instead of polygon->circle\r\n * @returns {Boolean}\r\n */\r\nfunction polygonCircle(a, b, result = null, reverse = false) {\r\n\tconst a_coords = a._coords;\r\n\tconst a_edges = a._edges;\r\n\tconst a_normals = a._normals;\r\n\tconst b_x = b.x;\r\n\tconst b_y = b.y;\r\n\tconst b_radius = b.radius * b.scale;\r\n\tconst b_radius2 = b_radius * 2;\r\n\tconst radius_squared = b_radius * b_radius;\r\n\tconst count = a_coords.length;\r\n\r\n\tlet a_in_b = true;\r\n\tlet b_in_a = true;\r\n\tlet overlap = null;\r\n\tlet overlap_x = 0;\r\n\tlet overlap_y = 0;\r\n\r\n\t// Handle points specially\r\n\tif(count === 2) {\r\n\t\tconst coord_x = b_x - a_coords[0];\r\n\t\tconst coord_y = b_y - a_coords[1];\r\n\t\tconst length_squared = coord_x * coord_x + coord_y * coord_y;\r\n\r\n\t\tif(length_squared > radius_squared) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tif(result) {\r\n\t\t\tconst length = Math.sqrt(length_squared);\r\n\r\n\t\t\toverlap = b_radius - length;\r\n\t\t\toverlap_x = coord_x / length;\r\n\t\t\toverlap_y = coord_y / length;\r\n\t\t\tb_in_a = false;\r\n\t\t}\r\n\t}\r\n\telse {\r\n\t\tfor(let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {\r\n\t\t\tconst coord_x = b_x - a_coords[ix];\r\n\t\t\tconst coord_y = b_y - a_coords[iy];\r\n\t\t\tconst edge_x = a_edges[ix];\r\n\t\t\tconst edge_y = a_edges[iy];\r\n\t\t\tconst dot = coord_x * edge_x + coord_y * edge_y;\r\n\t\t\tconst region = dot < 0 ? -1 : dot > edge_x * edge_x + edge_y * edge_y ? 1 : 0;\r\n\r\n\t\t\tlet tmp_overlapping = false;\r\n\t\t\tlet tmp_overlap = 0;\r\n\t\t\tlet tmp_overlap_x = 0;\r\n\t\t\tlet tmp_overlap_y = 0;\r\n\r\n\t\t\tif(result && a_in_b && coord_x * coord_x + coord_y * coord_y > radius_squared) {\r\n\t\t\t\ta_in_b = false;\r\n\t\t\t}\r\n\r\n\t\t\tif(region) {\r\n\t\t\t\tconst left = region === -1;\r\n\t\t\t\tconst other_x = left ? (ix === 0 ? count - 2 : ix - 2) : (ix === count - 2 ? 0 : ix + 2);\r\n\t\t\t\tconst other_y = other_x + 1;\r\n\t\t\t\tconst coord2_x = b_x - a_coords[other_x];\r\n\t\t\t\tconst coord2_y = b_y - a_coords[other_y];\r\n\t\t\t\tconst edge2_x = a_edges[other_x];\r\n\t\t\t\tconst edge2_y = a_edges[other_y];\r\n\t\t\t\tconst dot2 = coord2_x * edge2_x + coord2_y * edge2_y;\r\n\t\t\t\tconst region2 = dot2 < 0 ? -1 : dot2 > edge2_x * edge2_x + edge2_y * edge2_y ? 1 : 0;\r\n\r\n\t\t\t\tif(region2 === -region) {\r\n\t\t\t\t\tconst target_x = left ? coord_x : coord2_x;\r\n\t\t\t\t\tconst target_y = left ? coord_y : coord2_y;\r\n\t\t\t\t\tconst length_squared = target_x * target_x + target_y * target_y;\r\n\r\n\t\t\t\t\tif(length_squared > radius_squared) {\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif(result) {\r\n\t\t\t\t\t\tconst length = Math.sqrt(length_squared);\r\n\r\n\t\t\t\t\t\ttmp_overlapping = true;\r\n\t\t\t\t\t\ttmp_overlap = b_radius - length;\r\n\t\t\t\t\t\ttmp_overlap_x = target_x / length;\r\n\t\t\t\t\t\ttmp_overlap_y = target_y / length;\r\n\t\t\t\t\t\tb_in_a = false;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tconst normal_x = a_normals[ix];\r\n\t\t\t\tconst normal_y = a_normals[iy];\r\n\t\t\t\tconst length = coord_x * normal_x + coord_y * normal_y;\r\n\t\t\t\tconst absolute_length = length < 0 ? -length : length;\r\n\r\n\t\t\t\tif(length > 0 && absolute_length > b_radius) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif(result) {\r\n\t\t\t\t\ttmp_overlapping = true;\r\n\t\t\t\t\ttmp_overlap = b_radius - length;\r\n\t\t\t\t\ttmp_overlap_x = normal_x;\r\n\t\t\t\t\ttmp_overlap_y = normal_y;\r\n\r\n\t\t\t\t\tif(b_in_a && length >= 0 || tmp_overlap < b_radius2) {\r\n\t\t\t\t\t\tb_in_a = false;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif(tmp_overlapping && (overlap === null || overlap > tmp_overlap)) {\r\n\t\t\t\toverlap = tmp_overlap;\r\n\t\t\t\toverlap_x = tmp_overlap_x;\r\n\t\t\t\toverlap_y = tmp_overlap_y;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif(result) {\r\n\t\tresult.a_in_b = reverse ? b_in_a : a_in_b;\r\n\t\tresult.b_in_a = reverse ? a_in_b : b_in_a;\r\n\t\tresult.overlap = overlap;\r\n\t\tresult.overlap_x = reverse ? -overlap_x : overlap_x;\r\n\t\tresult.overlap_y = reverse ? -overlap_y : overlap_y;\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\n/**\r\n * Determines if two circles are colliding\r\n * @param {Circle} a The source circle to test\r\n * @param {Circle} b The target circle to test against\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @returns {Boolean}\r\n */\r\nfunction circleCircle(a, b, result = null) {\r\n\tconst a_radius = a.radius * a.scale;\r\n\tconst b_radius = b.radius * b.scale;\r\n\tconst difference_x = b.x - a.x;\r\n\tconst difference_y = b.y - a.y;\r\n\tconst radius_sum = a_radius + b_radius;\r\n\tconst length_squared = difference_x * difference_x + difference_y * difference_y;\r\n\r\n\tif(length_squared > radius_sum * radius_sum) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tif(result) {\r\n\t\tconst length = Math.sqrt(length_squared);\r\n\r\n\t\tresult.a_in_b = a_radius <= b_radius && length <= b_radius - a_radius;\r\n\t\tresult.b_in_a = b_radius <= a_radius && length <= a_radius - b_radius;\r\n\t\tresult.overlap = radius_sum - length;\r\n\t\tresult.overlap_x = difference_x / length;\r\n\t\tresult.overlap_y = difference_y / length;\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\n/**\r\n * Determines if two polygons are separated by an axis\r\n * @param {Array} a_coords The coordinates of the polygon to test\r\n * @param {Array} b_coords The coordinates of the polygon to test against\r\n * @param {Number} x The X direction of the axis\r\n * @param {Number} y The Y direction of the axis\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @returns {Boolean}\r\n */\r\nfunction separatingAxis(a_coords, b_coords, x, y, result = null) {\r\n\tconst a_count = a_coords.length;\r\n\tconst b_count = b_coords.length;\r\n\r\n\tif(!a_count || !b_count) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\tlet a_start = null;\r\n\tlet a_end = null;\r\n\tlet b_start = null;\r\n\tlet b_end = null;\r\n\r\n\tfor(let ix = 0, iy = 1; ix < a_count; ix += 2, iy += 2) {\r\n\t\tconst dot = a_coords[ix] * x + a_coords[iy] * y;\r\n\r\n\t\tif(a_start === null || a_start > dot) {\r\n\t\t\ta_start = dot;\r\n\t\t}\r\n\r\n\t\tif(a_end === null || a_end < dot) {\r\n\t\t\ta_end = dot;\r\n\t\t}\r\n\t}\r\n\r\n\tfor(let ix = 0, iy = 1; ix < b_count; ix += 2, iy += 2) {\r\n\t\tconst dot = b_coords[ix] * x + b_coords[iy] * y;\r\n\r\n\t\tif(b_start === null || b_start > dot) {\r\n\t\t\tb_start = dot;\r\n\t\t}\r\n\r\n\t\tif(b_end === null || b_end < dot) {\r\n\t\t\tb_end = dot;\r\n\t\t}\r\n\t}\r\n\r\n\tif(a_start > b_end || a_end < b_start) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\tif(result) {\r\n\t\tlet overlap = 0;\r\n\r\n\t\tif(a_start < b_start) {\r\n\t\t\tresult.a_in_b = false;\r\n\r\n\t\t\tif(a_end < b_end) {\r\n\t\t\t\toverlap = a_end - b_start;\r\n\t\t\t\tresult.b_in_a = false;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tconst option1 = a_end - b_start;\r\n\t\t\t\tconst option2 = b_end - a_start;\r\n\r\n\t\t\t\toverlap = option1 < option2 ? option1 : -option2;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse {\r\n\t\t\tresult.b_in_a = false;\r\n\r\n\t\t\tif(a_end > b_end) {\r\n\t\t\t\toverlap = a_start - b_end;\r\n\t\t\t\tresult.a_in_b = false;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tconst option1 = a_end - b_start;\r\n\t\t\t\tconst option2 = b_end - a_start;\r\n\r\n\t\t\t\toverlap = option1 < option2 ? option1 : -option2;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst current_overlap = result.overlap;\r\n\t\tconst absolute_overlap = overlap < 0 ? -overlap : overlap;\r\n\r\n\t\tif(current_overlap === null || current_overlap > absolute_overlap) {\r\n\t\t\tconst sign = overlap < 0 ? -1 : 1;\r\n\r\n\t\t\tresult.overlap = absolute_overlap;\r\n\t\t\tresult.overlap_x = x * sign;\r\n\t\t\tresult.overlap_y = y * sign;\r\n\t\t}\r\n\t}\r\n\r\n\treturn false;\r\n}\r\n\n\n//# sourceURL=webpack:///./src/modules/SAT.mjs?"); + +/***/ }) + +/******/ }); \ No newline at end of file