mirror of
https://github.com/liabru/matter-js.git
synced 2025-01-15 16:38:43 -05:00
commit
79ab1622c1
47 changed files with 3243 additions and 2222 deletions
30
.github/workflows/ci.yml
vendored
Normal file
30
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm run benchmark
|
||||
- run: npm run test
|
||||
- run: npm run build
|
||||
- run: npm run build-demo
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,10 +1,13 @@
|
|||
.idea
|
||||
*.map
|
||||
node_modules
|
||||
npm-debug.log
|
||||
docs
|
||||
matter-doc-theme
|
||||
build/matter-dev.js
|
||||
build/matter-dev.min.js
|
||||
build/matter.dev.js
|
||||
build/matter.dev.min.js
|
||||
demo/js/lib/matter-dev.js
|
||||
demo/js/Examples.min.js
|
||||
examples/build
|
||||
|
|
11
.travis.yml
11
.travis.yml
|
@ -1,11 +0,0 @@
|
|||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- "node"
|
||||
install:
|
||||
- npm ci
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run test
|
||||
- npm run build
|
||||
- npm run build-demo
|
41
CHANGELOG.md
41
CHANGELOG.md
|
@ -1,3 +1,44 @@
|
|||
## 0.18.0 (2021-12-15)
|
||||
|
||||
* added test capture sort to improve comparison ([ea3c11b](https://github.com/liabru/matter-js/commit/ea3c11b))
|
||||
* added triangles to mixed bodies example ([b116f64](https://github.com/liabru/matter-js/commit/b116f64))
|
||||
* added behaviour metric to tests and refactor tests ([8125966](https://github.com/liabru/matter-js/commit/8125966))
|
||||
* added benchmark test command ([7f34c45](https://github.com/liabru/matter-js/commit/7f34c45))
|
||||
* added broadphase to Matter.Detector ([a6b5e7d](https://github.com/liabru/matter-js/commit/a6b5e7d))
|
||||
* added cache checks to Matter.Composite ([32fd285](https://github.com/liabru/matter-js/commit/32fd285))
|
||||
* added example for Composite.remove ([bc07f56](https://github.com/liabru/matter-js/commit/bc07f56))
|
||||
* added example stress 3 ([d0ee246](https://github.com/liabru/matter-js/commit/d0ee246))
|
||||
* added filesize to test comparison report ([b3a8aa3](https://github.com/liabru/matter-js/commit/b3a8aa3))
|
||||
* added Matter.Collision ([9037f36](https://github.com/liabru/matter-js/commit/9037f36))
|
||||
* added memory comparison to tests ([bedf84c](https://github.com/liabru/matter-js/commit/bedf84c))
|
||||
* added note about webpack performance to readme ([80cf76b](https://github.com/liabru/matter-js/commit/80cf76b))
|
||||
* added stable sorting to test worker and refactor ([81dd2fb](https://github.com/liabru/matter-js/commit/81dd2fb))
|
||||
* added support for build metadata in Plugin.versionParse ([8bfaff0](https://github.com/liabru/matter-js/commit/8bfaff0))
|
||||
* changed raycasting example events to enable use in tests ([10afaea](https://github.com/liabru/matter-js/commit/10afaea))
|
||||
* changed build config to 'source-map' devtool ([e909b04](https://github.com/liabru/matter-js/commit/e909b04))
|
||||
* changed tests to use a production build rather than source ([55feb89](https://github.com/liabru/matter-js/commit/55feb89))
|
||||
* deprecated Matter.Grid ([e366d0e](https://github.com/liabru/matter-js/commit/e366d0e))
|
||||
* fixed sync issues on demo compare tool ([826ed46](https://github.com/liabru/matter-js/commit/826ed46))
|
||||
* improved performance measurement accuracy in tests ([cd289ec](https://github.com/liabru/matter-js/commit/cd289ec))
|
||||
* improved test comparison report ([de04c00](https://github.com/liabru/matter-js/commit/de04c00))
|
||||
* optimised Matter.Detector ([c7cec16](https://github.com/liabru/matter-js/commit/c7cec16)), ([fd1a70e](https://github.com/liabru/matter-js/commit/fd1a70e)), ([caeb07e](https://github.com/liabru/matter-js/commit/caeb07e)), ([efede6e](https://github.com/liabru/matter-js/commit/efede6e))
|
||||
* optimised Matter.Composite ([52e7977](https://github.com/liabru/matter-js/commit/52e7977))
|
||||
* optimised Matter.Pair ([d8a6380](https://github.com/liabru/matter-js/commit/d8a6380)), ([48673db](https://github.com/liabru/matter-js/commit/48673db)), ([1073dde](https://github.com/liabru/matter-js/commit/1073dde))
|
||||
* optimised Matter.Pairs ([a30707f](https://github.com/liabru/matter-js/commit/a30707f)), ([a882a74](https://github.com/liabru/matter-js/commit/a882a74))
|
||||
* optimised Matter.Resolver ([fceb0ca](https://github.com/liabru/matter-js/commit/fceb0ca)), ([49fbfba](https://github.com/liabru/matter-js/commit/49fbfba)), ([0b07a31](https://github.com/liabru/matter-js/commit/0b07a31)), ([f847f4c](https://github.com/liabru/matter-js/commit/f847f4c)), ([3cf65e8](https://github.com/liabru/matter-js/commit/3cf65e8)), ([30b899c](https://github.com/liabru/matter-js/commit/30b899c)), ([e4b35d3](https://github.com/liabru/matter-js/commit/e4b35d3))
|
||||
* optimised Matter.SAT ([0d90a17](https://github.com/liabru/matter-js/commit/0d90a17)), ([2096961](https://github.com/liabru/matter-js/commit/2096961)), ([0af144c](https://github.com/liabru/matter-js/commit/0af144c))
|
||||
* optimised Matter.Vertices ([c198878](https://github.com/liabru/matter-js/commit/c198878)), ([6883d0d](https://github.com/liabru/matter-js/commit/6883d0d)), ([792ae2e](https://github.com/liabru/matter-js/commit/792ae2e))
|
||||
* refactor test worker and prevent test cache ([ca2fe75](https://github.com/liabru/matter-js/commit/ca2fe75)), ([bcc3168](https://github.com/liabru/matter-js/commit/bcc3168))
|
||||
* replaced Matter.SAT with Matter.Collision ([b9e7d9d](https://github.com/liabru/matter-js/commit/b9e7d9d))
|
||||
* show debug stats in dev demo ([2f14ec5](https://github.com/liabru/matter-js/commit/2f14ec5))
|
||||
* updated dev dependencies ([c5028d5](https://github.com/liabru/matter-js/commit/c5028d5))
|
||||
* updated examples ([c80ed5c](https://github.com/liabru/matter-js/commit/c80ed5c))
|
||||
* updated test scripts ([afa467a](https://github.com/liabru/matter-js/commit/afa467a))
|
||||
* use force exit in tests ([8adf810](https://github.com/liabru/matter-js/commit/8adf810))
|
||||
* use Matter.Runner in test worker ([2581595](https://github.com/liabru/matter-js/commit/2581595))
|
||||
|
||||
|
||||
|
||||
## <small>0.17.1 (2021-04-14)</small>
|
||||
|
||||
* deprecate Engine.run alias replaced by Runner.run ([5817046](https://github.com/liabru/matter-js/commit/5817046))
|
||||
|
|
|
@ -58,7 +58,6 @@
|
|||
<li><a href="https://brm.io/matter-js/demo/#airFriction">Air Friction</a></li>
|
||||
<li><a href="https://brm.io/matter-js/demo/#staticFriction">Static Friction</a></li>
|
||||
<li><a href="https://brm.io/matter-js/demo/#sleeping">Sleeping</a></li>
|
||||
<li><a href="https://brm.io/matter-js/demo/#broadphase">Grid Broadphase</a></li>
|
||||
<li><a href="https://brm.io/matter-js/demo/#beachBalls">Beach Balls</a></li>
|
||||
<li><a href="https://brm.io/matter-js/demo/#stress">Stress 1</a></li>
|
||||
<li><a href="https://brm.io/matter-js/demo/#stress2">Stress 2</a></li>
|
||||
|
@ -125,6 +124,10 @@ Alternatively you can download a [stable release](https://github.com/liabru/matt
|
|||
|
||||
<script src="matter.js" type="text/javascript"></script>
|
||||
|
||||
### Webpack
|
||||
|
||||
Some [webpack](https://webpack.js.org/) configs including the default may impact your project's performance during development, for a solution see [issue](https://github.com/liabru/matter-js/issues/1001).
|
||||
|
||||
### Usage
|
||||
|
||||
Visit the [Getting started](https://github.com/liabru/matter-js/wiki/Getting-started) wiki page for a minimal usage example which should work in both browsers and Node.js.
|
||||
|
|
58
RELEASE.md
58
RELEASE.md
|
@ -1,3 +1,61 @@
|
|||
## ▲.● matter.js `0.18.0`
|
||||
|
||||
Release notes for `0.18.0`. See the release [readme](https://github.com/liabru/matter-js/blob/0.18.0/README.md) for further information.
|
||||
|
||||
### Highlights ✺
|
||||
|
||||
- **Up to ~40% performance improvement (on average measured over all examples, in Node on a Mac Air M1)**
|
||||
- Replaces `Matter.Grid` with a faster and more efficient broadphase in `Matter.Detector`
|
||||
- Reduced memory usage and garbage collection
|
||||
- Resolves issues in `Matter.SAT` related to collision reuse
|
||||
- Removes performance issues from `Matter.Grid`
|
||||
- Improved collision accuracy
|
||||
- Improved API and docs for collision modules
|
||||
- Improved [documentation](https://brm.io/matter-js/docs/) pages
|
||||
- Added note about avoiding Webpack [performance problems](https://github.com/liabru/matter-js/blob/master/README.md#install)
|
||||
|
||||
### Changes ✲
|
||||
|
||||
See the release [compare page](https://github.com/liabru/matter-js/compare/0.17.1...0.18.0) and the [changelog](https://github.com/liabru/matter-js/blob/0.18.0/CHANGELOG.md) for a detailed list of changes.
|
||||
|
||||
### Migration ⌲
|
||||
|
||||
- Behaviour and similarity is in practice the same (a fractional difference from improved accuracy)
|
||||
- API is the same other than:
|
||||
- [Matter.Detector](https://brm.io/matter-js/docs/classes/Detector.html) replaces [Matter.Grid](https://brm.io/matter-js/docs/classes/Grid.html) (which is now deprecated but will remain for a short term)
|
||||
- [render.options.showBroadphase](https://brm.io/matter-js/docs/classes/Render.html#property_options.showBroadphase) is deprecated (no longer implemented)
|
||||
- [Matter.SAT](https://brm.io/matter-js/docs/classes/SAT.html) is renamed [Matter.Collision](https://brm.io/matter-js/docs/classes/Collision.html)
|
||||
- [Matter.SAT.collides](https://brm.io/matter-js/docs/classes/SAT.html#method_collides) is now [Matter.Collision.collides](https://brm.io/matter-js/docs/classes/Collision.html#method_collides) with a slightly different set of arguments
|
||||
|
||||
### Comparison ⎄
|
||||
|
||||
Differences in behaviour, quality and performance against the previous release `0.17.1`. For more information see [comparison method](https://github.com/liabru/matter-js/pull/794).
|
||||
|
||||
```ocaml
|
||||
Output comparison of 43 examples against previous release matter-js@0.17.1
|
||||
|
||||
Behaviour 99.99% Similarity 99.98% Overlap -0.00%
|
||||
Performance +40.62% Memory -6.18% Filesize -0.16% 77.73 KB
|
||||
|
||||
airFriction · · avalanche ● · ballPool · · bridge · · car · · catapult · ·
|
||||
chains · · circleStack · · cloth · · collisionFiltering · · compositeManipulation ● ·
|
||||
compound · · compoundStack · · concave · · constraints ● · doublePendulum · ·
|
||||
events · · friction · · gravity · · gyro · · manipulation · ◆
|
||||
mixed · · newtonsCradle · · pyramid · · ragdoll · · raycasting · ·
|
||||
remove · · restitution · · rounded · · sensors · · sleeping · ◆
|
||||
slingshot · · softBody · · sprites · · stack · · staticFriction · ·
|
||||
stats · · stress · · stress2 · · stress3 · · timescale · ·
|
||||
views · · wreckingBall · ·
|
||||
|
||||
where · no change ● extrinsics changed ◆ intrinsics changed
|
||||
```
|
||||
|
||||
### Contributors ♥︎
|
||||
|
||||
Many thanks to the [contributors](https://github.com/liabru/matter-js/compare/0.17.1...0.18.0) of this release, [past contributors](https://github.com/liabru/matter-js/graphs/contributors) as well those involved in the [community](https://github.com/liabru/matter-js/issues) for your input and support.
|
||||
|
||||
---
|
||||
|
||||
## ▲.● matter.js `0.17.0`
|
||||
|
||||
Release notes for `0.17.0`. See the release [readme](https://github.com/liabru/matter-js/blob/0.17.0/README.md) for further information.
|
||||
|
|
2533
build/matter.js
2533
build/matter.js
File diff suppressed because it is too large
Load diff
4
build/matter.min.js
vendored
4
build/matter.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -50,11 +50,11 @@
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script src="./js/matter-demo.main.5a504f.min.js"></script>
|
||||
<script src="./js/matter-demo.matter-tools.5e580e.min.js"></script>
|
||||
<script src="./js/matter-demo.matter-wrap.b3a896.min.js"></script>
|
||||
<script src="./js/matter-demo.pathseg.067c95.min.js"></script>
|
||||
<script src="./js/matter-demo.poly-decomp.59954b.min.js"></script>
|
||||
<script src="./js/matter-demo.e7da73.min.js"></script>
|
||||
<script src="./js/matter-demo.main.5754e1.min.js"></script>
|
||||
<script src="./js/matter-demo.matter-tools.97f38a.min.js"></script>
|
||||
<script src="./js/matter-demo.matter-wrap.dbda1f.min.js"></script>
|
||||
<script src="./js/matter-demo.pathseg.cf21c2.min.js"></script>
|
||||
<script src="./js/matter-demo.poly-decomp.c3d015.min.js"></script>
|
||||
<script src="./js/matter-demo.a280d3.min.js"></script>
|
||||
</body>
|
||||
</html>
|
6
demo/js/matter-demo.a280d3.min.js
vendored
Normal file
6
demo/js/matter-demo.a280d3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
demo/js/matter-demo.e7da73.min.js
vendored
6
demo/js/matter-demo.e7da73.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* matter-demo bundle 0.17.1 by @liabru
|
||||
* matter-demo bundle 0.18.0 by @liabru
|
||||
* http://brm.io/matter-js/
|
||||
* License MIT
|
||||
*/!function(e){function t(t){for(var n,l,a=t[0],f=t[1],i=t[2],c=0,s=[];c<a.length;c++)l=a[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(p&&p(t);s.length;)s.shift()();return u.push.apply(u,i||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,a=1;a<r.length;a++){var f=r[a];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=l(l.s=r[0]))}return e}var n={},o={1:0},u=[];function l(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,l),r.l=!0,r.exports}l.m=e,l.c=n,l.d=function(e,t,r){l.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,t){if(1&t&&(e=l(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(l.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)l.d(r,n,function(t){return e[t]}.bind(null,n));return r},l.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(t,"a",t),t},l.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},l.p="./js";var a=this.webpackJsonpMatterDemo=this.webpackJsonpMatterDemo||[],f=a.push.bind(a);a.push=t,a=a.slice();for(var i=0;i<a.length;i++)t(a[i]);var p=f;r()}([]);
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* matter-demo bundle 0.17.1 by @liabru
|
||||
* matter-demo bundle 0.18.0 by @liabru
|
||||
* http://brm.io/matter-js/
|
||||
* License MIT
|
||||
*/
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* matter-demo bundle 0.17.1 by @liabru
|
||||
* matter-demo bundle 0.18.0 by @liabru
|
||||
* http://brm.io/matter-js/
|
||||
* License MIT
|
||||
*/
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* matter-demo bundle 0.17.1 by @liabru
|
||||
* matter-demo bundle 0.18.0 by @liabru
|
||||
* http://brm.io/matter-js/
|
||||
* License MIT
|
||||
*/
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* matter-demo bundle 0.17.1 by @liabru
|
||||
* matter-demo bundle 0.18.0 by @liabru
|
||||
* http://brm.io/matter-js/
|
||||
* License MIT
|
||||
*/
|
|
@ -36,9 +36,6 @@ Example.gyro = function() {
|
|||
var stack = Composites.stack(20, 20, 10, 5, 0, 0, function(x, y) {
|
||||
var sides = Math.round(Common.random(1, 8));
|
||||
|
||||
// triangles can be a little unstable, so avoid until fixed
|
||||
sides = (sides === 3) ? 4 : sides;
|
||||
|
||||
// round the edges of some bodies
|
||||
var chamfer = null;
|
||||
if (sides > 2 && Common.random() > 0.7) {
|
||||
|
|
|
@ -3,7 +3,6 @@ module.exports = {
|
|||
avalanche: require('./avalanche.js'),
|
||||
ballPool: require('./ballPool.js'),
|
||||
bridge: require('./bridge.js'),
|
||||
broadphase: require('./broadphase.js'),
|
||||
car: require('./car.js'),
|
||||
catapult: require('./catapult.js'),
|
||||
chains: require('./chains.js'),
|
||||
|
@ -28,6 +27,7 @@ module.exports = {
|
|||
raycasting: require('./raycasting.js'),
|
||||
restitution: require('./restitution.js'),
|
||||
rounded: require('./rounded.js'),
|
||||
remove: require('./remove.js'),
|
||||
sensors: require('./sensors.js'),
|
||||
sleeping: require('./sleeping.js'),
|
||||
slingshot: require('./slingshot.js'),
|
||||
|
|
|
@ -62,7 +62,7 @@ Example.manipulation = function() {
|
|||
var counter = 0,
|
||||
scaleFactor = 1.01;
|
||||
|
||||
Events.on(engine, 'beforeUpdate', function(event) {
|
||||
Events.on(runner, 'afterTick', function(event) {
|
||||
counter += 1;
|
||||
|
||||
if (counter === 40)
|
||||
|
|
|
@ -36,9 +36,6 @@ Example.mixed = function() {
|
|||
var stack = Composites.stack(20, 20, 10, 5, 0, 0, function(x, y) {
|
||||
var sides = Math.round(Common.random(1, 8));
|
||||
|
||||
// triangles can be a little unstable, so avoid until fixed
|
||||
sides = (sides === 3) ? 4 : sides;
|
||||
|
||||
// round the edges of some bodies
|
||||
var chamfer = null;
|
||||
if (sides > 2 && Common.random() > 0.7) {
|
||||
|
|
|
@ -68,14 +68,21 @@ Example.raycasting = function() {
|
|||
Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
|
||||
]);
|
||||
|
||||
var collisions = [],
|
||||
startPoint = { x: 400, y: 100 };
|
||||
|
||||
Events.on(engine, 'afterUpdate', function() {
|
||||
var mouse = mouseConstraint.mouse,
|
||||
bodies = Composite.allBodies(engine.world),
|
||||
endPoint = mouse.position || { x: 100, y: 600 };
|
||||
|
||||
collisions = Query.ray(bodies, startPoint, endPoint);
|
||||
});
|
||||
|
||||
Events.on(render, 'afterRender', function() {
|
||||
var mouse = mouseConstraint.mouse,
|
||||
context = render.context,
|
||||
bodies = Composite.allBodies(engine.world),
|
||||
startPoint = { x: 400, y: 100 },
|
||||
endPoint = mouse.position;
|
||||
|
||||
var collisions = Query.ray(bodies, startPoint, endPoint);
|
||||
endPoint = mouse.position || { x: 100, y: 600 };
|
||||
|
||||
Render.startViewTransform(render);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
var Example = Example || {};
|
||||
|
||||
Example.broadphase = function() {
|
||||
Example.remove = function() {
|
||||
var Engine = Matter.Engine,
|
||||
Render = Matter.Render,
|
||||
Runner = Matter.Runner,
|
||||
|
@ -9,7 +9,8 @@ Example.broadphase = function() {
|
|||
MouseConstraint = Matter.MouseConstraint,
|
||||
Mouse = Matter.Mouse,
|
||||
Composite = Matter.Composite,
|
||||
Bodies = Matter.Bodies;
|
||||
Bodies = Matter.Bodies,
|
||||
Events = Matter.Events;
|
||||
|
||||
// create engine
|
||||
var engine = Engine.create(),
|
||||
|
@ -23,7 +24,6 @@ Example.broadphase = function() {
|
|||
width: 800,
|
||||
height: 600,
|
||||
showAngleIndicator: true,
|
||||
showBroadphase: true
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -33,7 +33,59 @@ Example.broadphase = function() {
|
|||
var runner = Runner.create();
|
||||
Runner.run(runner, engine);
|
||||
|
||||
// add bodies
|
||||
var stack = null,
|
||||
updateCount = 0;
|
||||
|
||||
var createStack = function() {
|
||||
return Composites.stack(20, 20, 10, 5, 0, 0, function(x, y) {
|
||||
var sides = Math.round(Common.random(1, 8));
|
||||
|
||||
// round the edges of some bodies
|
||||
var chamfer = null;
|
||||
if (sides > 2 && Common.random() > 0.7) {
|
||||
chamfer = {
|
||||
radius: 10
|
||||
};
|
||||
}
|
||||
|
||||
switch (Math.round(Common.random(0, 1))) {
|
||||
case 0:
|
||||
if (Common.random() < 0.8) {
|
||||
return Bodies.rectangle(x, y, Common.random(25, 50), Common.random(25, 50), { chamfer: chamfer });
|
||||
} else {
|
||||
return Bodies.rectangle(x, y, Common.random(80, 120), Common.random(25, 30), { chamfer: chamfer });
|
||||
}
|
||||
case 1:
|
||||
return Bodies.polygon(x, y, sides, Common.random(25, 50), { chamfer: chamfer });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// add and remove stacks every few updates
|
||||
Events.on(engine, 'afterUpdate', function() {
|
||||
// limit rate
|
||||
if (stack && updateCount <= 50) {
|
||||
updateCount += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
updateCount = 0;
|
||||
|
||||
// remove last stack
|
||||
if (stack) {
|
||||
Composite.remove(world, stack);
|
||||
}
|
||||
|
||||
// create a new stack
|
||||
stack = createStack();
|
||||
|
||||
// add the new stack
|
||||
Composite.add(world, stack);
|
||||
});
|
||||
|
||||
// add another stack that will not be removed
|
||||
Composite.add(world, createStack());
|
||||
|
||||
Composite.add(world, [
|
||||
// walls
|
||||
Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
|
||||
|
@ -42,23 +94,6 @@ Example.broadphase = function() {
|
|||
Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
|
||||
]);
|
||||
|
||||
var stack = Composites.stack(20, 20, 12, 5, 0, 0, function(x, y) {
|
||||
switch (Math.round(Common.random(0, 1))) {
|
||||
|
||||
case 0:
|
||||
if (Common.random() < 0.8) {
|
||||
return Bodies.rectangle(x, y, Common.random(20, 50), Common.random(20, 50));
|
||||
} else {
|
||||
return Bodies.rectangle(x, y, Common.random(80, 120), Common.random(20, 30));
|
||||
}
|
||||
case 1:
|
||||
return Bodies.polygon(x, y, Math.round(Common.random(1, 8)), Common.random(20, 50));
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
Composite.add(world, stack);
|
||||
|
||||
// add mouse control
|
||||
var mouse = Mouse.create(render.canvas),
|
||||
mouseConstraint = MouseConstraint.create(engine, {
|
||||
|
@ -95,9 +130,9 @@ Example.broadphase = function() {
|
|||
};
|
||||
};
|
||||
|
||||
Example.broadphase.title = 'Broadphase';
|
||||
Example.broadphase.for = '>=0.14.2';
|
||||
Example.remove.title = 'Composite Remove';
|
||||
Example.remove.for = '>=0.14.2';
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = Example.broadphase;
|
||||
module.exports = Example.remove;
|
||||
}
|
|
@ -61,12 +61,15 @@ Example.sleeping = function() {
|
|||
|
||||
Composite.add(world, stack);
|
||||
|
||||
/*
|
||||
// sleep events
|
||||
for (var i = 0; i < stack.bodies.length; i++) {
|
||||
Events.on(stack.bodies[i], 'sleepStart sleepEnd', function(event) {
|
||||
var body = this;
|
||||
console.log('body id', body.id, 'sleeping:', body.isSleeping);
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
// add mouse control
|
||||
var mouse = Mouse.create(render.canvas),
|
||||
|
|
|
@ -12,8 +12,12 @@ Example.stress3 = function() {
|
|||
Bodies = Matter.Bodies;
|
||||
|
||||
// create engine
|
||||
var engine = Engine.create(),
|
||||
world = engine.world;
|
||||
var engine = Engine.create({
|
||||
positionIterations: 10,
|
||||
velocityIterations: 10
|
||||
});
|
||||
|
||||
var world = engine.world;
|
||||
|
||||
// create renderer
|
||||
var render = Render.create({
|
||||
|
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "matter-js",
|
||||
"version": "0.17.1",
|
||||
"version": "0.18.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "matter-js",
|
||||
"version": "0.17.1",
|
||||
"version": "0.18.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"conventional-changelog-cli": "^2.1.1",
|
||||
|
|
12
package.json
12
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "matter-js",
|
||||
"version": "0.17.1",
|
||||
"version": "0.18.0",
|
||||
"license": "MIT",
|
||||
"homepage": "http://brm.io/matter-js/",
|
||||
"author": "Liam Brummitt <liam@brm.io> (http://brm.io/)",
|
||||
|
@ -44,17 +44,19 @@
|
|||
"serve": "webpack-dev-server --no-cache --mode development --config webpack.demo.config.js",
|
||||
"watch": "nodemon --watch webpack.demo.config.js --exec \"npm run serve\"",
|
||||
"build": "webpack --mode=production --no-hot --no-watch & webpack --mode=production --no-hot --no-watch --env.MINIMIZE",
|
||||
"build-alpha": "webpack --mode=production --env.ALPHA & webpack --mode=production --env.MINIMIZE --env.ALPHA",
|
||||
"build-alpha": "webpack --mode=production --no-hot --no-watch --env.KIND=alpha & webpack --mode=production --no-hot --no-watch --env.MINIMIZE --env.KIND=alpha",
|
||||
"build-dev": "webpack --mode=production --no-hot --no-watch --env.KIND=dev & webpack --mode=production --no-hot --no-watch --env.MINIMIZE --env.KIND=dev",
|
||||
"build-demo": "rm -rf ./demo/js && webpack --no-hot --no-watch --config webpack.demo.config.js --mode=production && webpack --no-hot --no-watch --config webpack.demo.config.js --mode=production --env.MINIMIZE",
|
||||
"lint": "eslint 'src/**/*.js' 'demo/src/**/*.js' 'examples/*.js' 'webpack.*.js'",
|
||||
"doc": "yuidoc --config yuidoc.json --project-version $npm_package_version",
|
||||
"doc-watch": "nodemon --delay 3 --watch 'matter-doc-theme' --watch src -e 'js,html,css,handlebars' --exec 'npm run doc'",
|
||||
"benchmark": "EXAMPLES=stress3 npm run test-node",
|
||||
"test": "npm run test-node",
|
||||
"test-all": "jest --no-cache",
|
||||
"test-node": "npm run build-dev && node --expose-gc node_modules/.bin/jest --force-exit --no-cache --runInBand ./test/Examples.spec.js",
|
||||
"test-browser": "node --expose-gc node_modules/.bin/jest --force-exit --no-cache --runInBand ./test/Browser.spec.js",
|
||||
"test-all": "npm run test-node && npm run test-browser",
|
||||
"test-save": "SAVE=true npm run test-node",
|
||||
"test-watch": "npm run test-node -- --watch",
|
||||
"test-node": "jest --no-cache ./test/Examples.spec.js",
|
||||
"test-browser": "jest --no-cache ./test/Browser.spec.js",
|
||||
"changelog": "conventional-changelog -i CHANGELOG.md -s -r",
|
||||
"release": "npm version --no-git-tag-version",
|
||||
"preversion": "git checkout master && npm run lint && SAVE=true npm run test-all",
|
||||
|
|
|
@ -39,7 +39,12 @@ var Body = require('./Body');
|
|||
constraints: [],
|
||||
composites: [],
|
||||
label: 'Composite',
|
||||
plugin: {}
|
||||
plugin: {},
|
||||
cache: {
|
||||
allBodies: null,
|
||||
allConstraints: null,
|
||||
allComposites: null
|
||||
}
|
||||
}, options);
|
||||
};
|
||||
|
||||
|
@ -47,6 +52,7 @@ var Body = require('./Body');
|
|||
* Sets the composite's `isModified` flag.
|
||||
* If `updateParents` is true, all parents will be set (default: false).
|
||||
* If `updateChildren` is true, all children will be set (default: false).
|
||||
* @private
|
||||
* @method setModified
|
||||
* @param {composite} composite
|
||||
* @param {boolean} isModified
|
||||
|
@ -56,6 +62,12 @@ var Body = require('./Body');
|
|||
Composite.setModified = function(composite, isModified, updateParents, updateChildren) {
|
||||
composite.isModified = isModified;
|
||||
|
||||
if (isModified && composite.cache) {
|
||||
composite.cache.allBodies = null;
|
||||
composite.cache.allConstraints = null;
|
||||
composite.cache.allComposites = null;
|
||||
}
|
||||
|
||||
if (updateParents && composite.parent) {
|
||||
Composite.setModified(composite.parent, isModified, updateParents, updateChildren);
|
||||
}
|
||||
|
@ -182,7 +194,6 @@ var Body = require('./Body');
|
|||
var position = Common.indexOf(compositeA.composites, compositeB);
|
||||
if (position !== -1) {
|
||||
Composite.removeCompositeAt(compositeA, position);
|
||||
Composite.setModified(compositeA, true, true, false);
|
||||
}
|
||||
|
||||
if (deep) {
|
||||
|
@ -235,7 +246,6 @@ var Body = require('./Body');
|
|||
var position = Common.indexOf(composite.bodies, body);
|
||||
if (position !== -1) {
|
||||
Composite.removeBodyAt(composite, position);
|
||||
Composite.setModified(composite, true, true, false);
|
||||
}
|
||||
|
||||
if (deep) {
|
||||
|
@ -336,6 +346,7 @@ var Body = require('./Body');
|
|||
|
||||
composite.constraints.length = 0;
|
||||
composite.composites.length = 0;
|
||||
|
||||
Composite.setModified(composite, true, true, false);
|
||||
|
||||
return composite;
|
||||
|
@ -348,11 +359,19 @@ var Body = require('./Body');
|
|||
* @return {body[]} All the bodies
|
||||
*/
|
||||
Composite.allBodies = function(composite) {
|
||||
if (composite.cache && composite.cache.allBodies) {
|
||||
return composite.cache.allBodies;
|
||||
}
|
||||
|
||||
var bodies = [].concat(composite.bodies);
|
||||
|
||||
for (var i = 0; i < composite.composites.length; i++)
|
||||
bodies = bodies.concat(Composite.allBodies(composite.composites[i]));
|
||||
|
||||
if (composite.cache) {
|
||||
composite.cache.allBodies = bodies;
|
||||
}
|
||||
|
||||
return bodies;
|
||||
};
|
||||
|
||||
|
@ -363,11 +382,19 @@ var Body = require('./Body');
|
|||
* @return {constraint[]} All the constraints
|
||||
*/
|
||||
Composite.allConstraints = function(composite) {
|
||||
if (composite.cache && composite.cache.allConstraints) {
|
||||
return composite.cache.allConstraints;
|
||||
}
|
||||
|
||||
var constraints = [].concat(composite.constraints);
|
||||
|
||||
for (var i = 0; i < composite.composites.length; i++)
|
||||
constraints = constraints.concat(Composite.allConstraints(composite.composites[i]));
|
||||
|
||||
if (composite.cache) {
|
||||
composite.cache.allConstraints = constraints;
|
||||
}
|
||||
|
||||
return constraints;
|
||||
};
|
||||
|
||||
|
@ -378,11 +405,19 @@ var Body = require('./Body');
|
|||
* @return {composite[]} All the composites
|
||||
*/
|
||||
Composite.allComposites = function(composite) {
|
||||
if (composite.cache && composite.cache.allComposites) {
|
||||
return composite.cache.allComposites;
|
||||
}
|
||||
|
||||
var composites = [].concat(composite.composites);
|
||||
|
||||
for (var i = 0; i < composite.composites.length; i++)
|
||||
composites = composites.concat(Composite.allComposites(composite.composites[i]));
|
||||
|
||||
if (composite.cache) {
|
||||
composite.cache.allComposites = composites;
|
||||
}
|
||||
|
||||
return composites;
|
||||
};
|
||||
|
||||
|
@ -449,8 +484,6 @@ var Body = require('./Body');
|
|||
objects[i].id = Common.nextId();
|
||||
}
|
||||
|
||||
Composite.setModified(composite, true, true, false);
|
||||
|
||||
return composite;
|
||||
};
|
||||
|
||||
|
@ -469,8 +502,6 @@ var Body = require('./Body');
|
|||
Body.translate(bodies[i], translation);
|
||||
}
|
||||
|
||||
Composite.setModified(composite, true, true, false);
|
||||
|
||||
return composite;
|
||||
};
|
||||
|
||||
|
@ -500,8 +531,6 @@ var Body = require('./Body');
|
|||
Body.rotate(body, rotation);
|
||||
}
|
||||
|
||||
Composite.setModified(composite, true, true, false);
|
||||
|
||||
return composite;
|
||||
};
|
||||
|
||||
|
@ -530,8 +559,6 @@ var Body = require('./Body');
|
|||
Body.scale(body, scaleX, scaleY);
|
||||
}
|
||||
|
||||
Composite.setModified(composite, true, true, false);
|
||||
|
||||
return composite;
|
||||
};
|
||||
|
||||
|
@ -631,8 +658,7 @@ var Body = require('./Body');
|
|||
|
||||
/**
|
||||
* A flag that specifies whether the composite has been modified during the current step.
|
||||
* Most `Matter.Composite` methods will automatically set this flag to `true` to inform the engine of changes to be handled.
|
||||
* If you need to change it manually, you should use the `Composite.setModified` method.
|
||||
* This is automatically managed when bodies, constraints or composites are added or removed.
|
||||
*
|
||||
* @property isModified
|
||||
* @type boolean
|
||||
|
@ -684,4 +710,13 @@ var Body = require('./Body');
|
|||
* @type {}
|
||||
*/
|
||||
|
||||
/**
|
||||
* An object used for storing cached results for performance reasons.
|
||||
* This is used internally only and is automatically managed.
|
||||
*
|
||||
* @private
|
||||
* @property cache
|
||||
* @type {}
|
||||
*/
|
||||
|
||||
})();
|
||||
|
|
408
src/collision/Collision.js
Normal file
408
src/collision/Collision.js
Normal file
|
@ -0,0 +1,408 @@
|
|||
/**
|
||||
* The `Matter.Collision` module contains methods for detecting collisions between a given pair of bodies.
|
||||
*
|
||||
* For efficient detection between a list of bodies, see `Matter.Detector` and `Matter.Query`.
|
||||
*
|
||||
* See `Matter.Engine` for collision events.
|
||||
*
|
||||
* @class Collision
|
||||
*/
|
||||
|
||||
var Collision = {};
|
||||
|
||||
module.exports = Collision;
|
||||
|
||||
var Vertices = require('../geometry/Vertices');
|
||||
var Pair = require('./Pair');
|
||||
|
||||
(function() {
|
||||
var _supports = [];
|
||||
|
||||
var _overlapAB = {
|
||||
overlap: 0,
|
||||
axis: null
|
||||
};
|
||||
|
||||
var _overlapBA = {
|
||||
overlap: 0,
|
||||
axis: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new collision record.
|
||||
* @method create
|
||||
* @param {body} bodyA The first body part represented by the collision record
|
||||
* @param {body} bodyB The second body part represented by the collision record
|
||||
* @return {collision} A new collision record
|
||||
*/
|
||||
Collision.create = function(bodyA, bodyB) {
|
||||
return {
|
||||
pair: null,
|
||||
collided: false,
|
||||
bodyA: bodyA,
|
||||
bodyB: bodyB,
|
||||
parentA: bodyA.parent,
|
||||
parentB: bodyB.parent,
|
||||
depth: 0,
|
||||
normal: { x: 0, y: 0 },
|
||||
tangent: { x: 0, y: 0 },
|
||||
penetration: { x: 0, y: 0 },
|
||||
supports: []
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect collision between two bodies.
|
||||
* @method collides
|
||||
* @param {body} bodyA
|
||||
* @param {body} bodyB
|
||||
* @param {pairs} [pairs] Optionally reuse collision records from existing pairs.
|
||||
* @return {collision|null} A collision record if detected, otherwise null
|
||||
*/
|
||||
Collision.collides = function(bodyA, bodyB, pairs) {
|
||||
Collision._overlapAxes(_overlapAB, bodyA.vertices, bodyB.vertices, bodyA.axes);
|
||||
|
||||
if (_overlapAB.overlap <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Collision._overlapAxes(_overlapBA, bodyB.vertices, bodyA.vertices, bodyB.axes);
|
||||
|
||||
if (_overlapBA.overlap <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// reuse collision records for gc efficiency
|
||||
var pair = pairs && pairs.table[Pair.id(bodyA, bodyB)],
|
||||
collision;
|
||||
|
||||
if (!pair) {
|
||||
collision = Collision.create(bodyA, bodyB);
|
||||
collision.collided = true;
|
||||
collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB;
|
||||
collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA;
|
||||
collision.parentA = collision.bodyA.parent;
|
||||
collision.parentB = collision.bodyB.parent;
|
||||
} else {
|
||||
collision = pair.collision;
|
||||
}
|
||||
|
||||
bodyA = collision.bodyA;
|
||||
bodyB = collision.bodyB;
|
||||
|
||||
var minOverlap;
|
||||
|
||||
if (_overlapAB.overlap < _overlapBA.overlap) {
|
||||
minOverlap = _overlapAB;
|
||||
} else {
|
||||
minOverlap = _overlapBA;
|
||||
}
|
||||
|
||||
var normal = collision.normal,
|
||||
supports = collision.supports,
|
||||
minAxis = minOverlap.axis,
|
||||
minAxisX = minAxis.x,
|
||||
minAxisY = minAxis.y;
|
||||
|
||||
// ensure normal is facing away from bodyA
|
||||
if (minAxisX * (bodyB.position.x - bodyA.position.x) + minAxisY * (bodyB.position.y - bodyA.position.y) < 0) {
|
||||
normal.x = minAxisX;
|
||||
normal.y = minAxisY;
|
||||
} else {
|
||||
normal.x = -minAxisX;
|
||||
normal.y = -minAxisY;
|
||||
}
|
||||
|
||||
collision.tangent.x = -normal.y;
|
||||
collision.tangent.y = normal.x;
|
||||
|
||||
collision.depth = minOverlap.overlap;
|
||||
|
||||
collision.penetration.x = normal.x * collision.depth;
|
||||
collision.penetration.y = normal.y * collision.depth;
|
||||
|
||||
// find support points, there is always either exactly one or two
|
||||
var supportsB = Collision._findSupports(bodyA, bodyB, normal, 1),
|
||||
supportCount = 0;
|
||||
|
||||
// find the supports from bodyB that are inside bodyA
|
||||
if (Vertices.contains(bodyA.vertices, supportsB[0])) {
|
||||
supports[supportCount++] = supportsB[0];
|
||||
}
|
||||
|
||||
if (Vertices.contains(bodyA.vertices, supportsB[1])) {
|
||||
supports[supportCount++] = supportsB[1];
|
||||
}
|
||||
|
||||
// find the supports from bodyA that are inside bodyB
|
||||
if (supportCount < 2) {
|
||||
var supportsA = Collision._findSupports(bodyB, bodyA, normal, -1);
|
||||
|
||||
if (Vertices.contains(bodyB.vertices, supportsA[0])) {
|
||||
supports[supportCount++] = supportsA[0];
|
||||
}
|
||||
|
||||
if (supportCount < 2 && Vertices.contains(bodyB.vertices, supportsA[1])) {
|
||||
supports[supportCount++] = supportsA[1];
|
||||
}
|
||||
}
|
||||
|
||||
// account for the edge case of overlapping but no vertex containment
|
||||
if (supportCount === 0) {
|
||||
supports[supportCount++] = supportsB[0];
|
||||
}
|
||||
|
||||
// update supports array size
|
||||
supports.length = supportCount;
|
||||
|
||||
return collision;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the overlap between two sets of vertices.
|
||||
* @method _overlapAxes
|
||||
* @private
|
||||
* @param {object} result
|
||||
* @param {vertices} verticesA
|
||||
* @param {vertices} verticesB
|
||||
* @param {axes} axes
|
||||
*/
|
||||
Collision._overlapAxes = function(result, verticesA, verticesB, axes) {
|
||||
var verticesALength = verticesA.length,
|
||||
verticesBLength = verticesB.length,
|
||||
verticesAX = verticesA[0].x,
|
||||
verticesAY = verticesA[0].y,
|
||||
verticesBX = verticesB[0].x,
|
||||
verticesBY = verticesB[0].y,
|
||||
axesLength = axes.length,
|
||||
overlapMin = Number.MAX_VALUE,
|
||||
overlapAxisNumber = 0,
|
||||
overlap,
|
||||
overlapAB,
|
||||
overlapBA,
|
||||
dot,
|
||||
i,
|
||||
j;
|
||||
|
||||
for (i = 0; i < axesLength; i++) {
|
||||
var axis = axes[i],
|
||||
axisX = axis.x,
|
||||
axisY = axis.y,
|
||||
minA = verticesAX * axisX + verticesAY * axisY,
|
||||
minB = verticesBX * axisX + verticesBY * axisY,
|
||||
maxA = minA,
|
||||
maxB = minB;
|
||||
|
||||
for (j = 1; j < verticesALength; j += 1) {
|
||||
dot = verticesA[j].x * axisX + verticesA[j].y * axisY;
|
||||
|
||||
if (dot > maxA) {
|
||||
maxA = dot;
|
||||
} else if (dot < minA) {
|
||||
minA = dot;
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 1; j < verticesBLength; j += 1) {
|
||||
dot = verticesB[j].x * axisX + verticesB[j].y * axisY;
|
||||
|
||||
if (dot > maxB) {
|
||||
maxB = dot;
|
||||
} else if (dot < minB) {
|
||||
minB = dot;
|
||||
}
|
||||
}
|
||||
|
||||
overlapAB = maxA - minB;
|
||||
overlapBA = maxB - minA;
|
||||
overlap = overlapAB < overlapBA ? overlapAB : overlapBA;
|
||||
|
||||
if (overlap < overlapMin) {
|
||||
overlapMin = overlap;
|
||||
overlapAxisNumber = i;
|
||||
|
||||
if (overlap <= 0) {
|
||||
// can not be intersecting
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.axis = axes[overlapAxisNumber];
|
||||
result.overlap = overlapMin;
|
||||
};
|
||||
|
||||
/**
|
||||
* Projects vertices on an axis and returns an interval.
|
||||
* @method _projectToAxis
|
||||
* @private
|
||||
* @param {} projection
|
||||
* @param {} vertices
|
||||
* @param {} axis
|
||||
*/
|
||||
Collision._projectToAxis = function(projection, vertices, axis) {
|
||||
var min = vertices[0].x * axis.x + vertices[0].y * axis.y,
|
||||
max = min;
|
||||
|
||||
for (var i = 1; i < vertices.length; i += 1) {
|
||||
var dot = vertices[i].x * axis.x + vertices[i].y * axis.y;
|
||||
|
||||
if (dot > max) {
|
||||
max = dot;
|
||||
} else if (dot < min) {
|
||||
min = dot;
|
||||
}
|
||||
}
|
||||
|
||||
projection.min = min;
|
||||
projection.max = max;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds supporting vertices given two bodies along a given direction using hill-climbing.
|
||||
* @method _findSupports
|
||||
* @private
|
||||
* @param {body} bodyA
|
||||
* @param {body} bodyB
|
||||
* @param {vector} normal
|
||||
* @param {number} direction
|
||||
* @return [vector]
|
||||
*/
|
||||
Collision._findSupports = function(bodyA, bodyB, normal, direction) {
|
||||
var vertices = bodyB.vertices,
|
||||
verticesLength = vertices.length,
|
||||
bodyAPositionX = bodyA.position.x,
|
||||
bodyAPositionY = bodyA.position.y,
|
||||
normalX = normal.x * direction,
|
||||
normalY = normal.y * direction,
|
||||
nearestDistance = Number.MAX_VALUE,
|
||||
vertexA,
|
||||
vertexB,
|
||||
vertexC,
|
||||
distance,
|
||||
j;
|
||||
|
||||
// find deepest vertex relative to the axis
|
||||
for (j = 0; j < verticesLength; j += 1) {
|
||||
vertexB = vertices[j];
|
||||
distance = normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y);
|
||||
|
||||
// convex hill-climbing
|
||||
if (distance < nearestDistance) {
|
||||
nearestDistance = distance;
|
||||
vertexA = vertexB;
|
||||
}
|
||||
}
|
||||
|
||||
// measure next vertex
|
||||
vertexC = vertices[(verticesLength + vertexA.index - 1) % verticesLength];
|
||||
nearestDistance = normalX * (bodyAPositionX - vertexC.x) + normalY * (bodyAPositionY - vertexC.y);
|
||||
|
||||
// compare with previous vertex
|
||||
vertexB = vertices[(vertexA.index + 1) % verticesLength];
|
||||
if (normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y) < nearestDistance) {
|
||||
_supports[0] = vertexA;
|
||||
_supports[1] = vertexB;
|
||||
|
||||
return _supports;
|
||||
}
|
||||
|
||||
_supports[0] = vertexA;
|
||||
_supports[1] = vertexC;
|
||||
|
||||
return _supports;
|
||||
};
|
||||
|
||||
/*
|
||||
*
|
||||
* Properties Documentation
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* A reference to the pair using this collision record, if there is one.
|
||||
*
|
||||
* @property pair
|
||||
* @type {pair|null}
|
||||
* @default null
|
||||
*/
|
||||
|
||||
/**
|
||||
* A flag that indicates if the bodies were colliding when the collision was last updated.
|
||||
*
|
||||
* @property collided
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
|
||||
/**
|
||||
* The first body part represented by the collision (see also `collision.parentA`).
|
||||
*
|
||||
* @property bodyA
|
||||
* @type body
|
||||
*/
|
||||
|
||||
/**
|
||||
* The second body part represented by the collision (see also `collision.parentB`).
|
||||
*
|
||||
* @property bodyB
|
||||
* @type body
|
||||
*/
|
||||
|
||||
/**
|
||||
* The first body represented by the collision (i.e. `collision.bodyA.parent`).
|
||||
*
|
||||
* @property parentA
|
||||
* @type body
|
||||
*/
|
||||
|
||||
/**
|
||||
* The second body represented by the collision (i.e. `collision.bodyB.parent`).
|
||||
*
|
||||
* @property parentB
|
||||
* @type body
|
||||
*/
|
||||
|
||||
/**
|
||||
* A `Number` that represents the minimum separating distance between the bodies along the collision normal.
|
||||
*
|
||||
* @readOnly
|
||||
* @property depth
|
||||
* @type number
|
||||
* @default 0
|
||||
*/
|
||||
|
||||
/**
|
||||
* A normalised `Vector` that represents the direction between the bodies that provides the minimum separating distance.
|
||||
*
|
||||
* @property normal
|
||||
* @type vector
|
||||
* @default { x: 0, y: 0 }
|
||||
*/
|
||||
|
||||
/**
|
||||
* A normalised `Vector` that is the tangent direction to the collision normal.
|
||||
*
|
||||
* @property tangent
|
||||
* @type vector
|
||||
* @default { x: 0, y: 0 }
|
||||
*/
|
||||
|
||||
/**
|
||||
* A `Vector` that represents the direction and depth of the collision.
|
||||
*
|
||||
* @property penetration
|
||||
* @type vector
|
||||
* @default { x: 0, y: 0 }
|
||||
*/
|
||||
|
||||
/**
|
||||
* An array of body vertices that represent the support points in the collision.
|
||||
* These are the deepest vertices (along the collision normal) of each body that are contained by the other body's vertices.
|
||||
*
|
||||
* @property supports
|
||||
* @type vector[]
|
||||
* @default []
|
||||
*/
|
||||
|
||||
})();
|
|
@ -18,21 +18,10 @@ module.exports = Contact;
|
|||
*/
|
||||
Contact.create = function(vertex) {
|
||||
return {
|
||||
id: Contact.id(vertex),
|
||||
vertex: vertex,
|
||||
normalImpulse: 0,
|
||||
tangentImpulse: 0
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a contact id.
|
||||
* @method id
|
||||
* @param {vertex} vertex
|
||||
* @return {string} Unique contactID
|
||||
*/
|
||||
Contact.id = function(vertex) {
|
||||
return vertex.body.id + '_' + vertex.index;
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
|
@ -1,66 +1,131 @@
|
|||
/**
|
||||
* The `Matter.Detector` module contains methods for detecting collisions given a set of pairs.
|
||||
* The `Matter.Detector` module contains methods for efficiently detecting collisions between a list of bodies using a broadphase algorithm.
|
||||
*
|
||||
* @class Detector
|
||||
*/
|
||||
|
||||
// TODO: speculative contacts
|
||||
|
||||
var Detector = {};
|
||||
|
||||
module.exports = Detector;
|
||||
|
||||
var SAT = require('./SAT');
|
||||
var Pair = require('./Pair');
|
||||
var Bounds = require('../geometry/Bounds');
|
||||
var Common = require('../core/Common');
|
||||
var Collision = require('./Collision');
|
||||
|
||||
(function() {
|
||||
|
||||
/**
|
||||
* Finds all collisions given a list of pairs.
|
||||
* @method collisions
|
||||
* @param {pair[]} broadphasePairs
|
||||
* @param {engine} engine
|
||||
* @return {array} collisions
|
||||
* Creates a new collision detector.
|
||||
* @method create
|
||||
* @param {} options
|
||||
* @return {detector} A new collision detector
|
||||
*/
|
||||
Detector.collisions = function(broadphasePairs, engine) {
|
||||
Detector.create = function(options) {
|
||||
var defaults = {
|
||||
bodies: [],
|
||||
pairs: null
|
||||
};
|
||||
|
||||
return Common.extend(defaults, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the list of bodies in the detector.
|
||||
* @method setBodies
|
||||
* @param {detector} detector
|
||||
* @param {body[]} bodies
|
||||
*/
|
||||
Detector.setBodies = function(detector, bodies) {
|
||||
detector.bodies = bodies.slice(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the detector including its list of bodies.
|
||||
* @method clear
|
||||
* @param {detector} detector
|
||||
*/
|
||||
Detector.clear = function(detector) {
|
||||
detector.bodies = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Efficiently finds all collisions among all the bodies in `detector.bodies` using a broadphase algorithm.
|
||||
*
|
||||
* _Note:_ The specific ordering of collisions returned is not guaranteed between releases and may change for performance reasons.
|
||||
* If a specific ordering is required then apply a sort to the resulting array.
|
||||
* @method collisions
|
||||
* @param {detector} detector
|
||||
* @return {collision[]} collisions
|
||||
*/
|
||||
Detector.collisions = function(detector) {
|
||||
var collisions = [],
|
||||
pairsTable = engine.pairs.table;
|
||||
pairs = detector.pairs,
|
||||
bodies = detector.bodies,
|
||||
bodiesLength = bodies.length,
|
||||
canCollide = Detector.canCollide,
|
||||
collides = Collision.collides,
|
||||
i,
|
||||
j;
|
||||
|
||||
for (var i = 0; i < broadphasePairs.length; i++) {
|
||||
var bodyA = broadphasePairs[i][0],
|
||||
bodyB = broadphasePairs[i][1];
|
||||
bodies.sort(Detector._compareBoundsX);
|
||||
|
||||
if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping))
|
||||
continue;
|
||||
for (i = 0; i < bodiesLength; i++) {
|
||||
var bodyA = bodies[i],
|
||||
boundsA = bodyA.bounds,
|
||||
boundXMax = bodyA.bounds.max.x,
|
||||
boundYMax = bodyA.bounds.max.y,
|
||||
boundYMin = bodyA.bounds.min.y,
|
||||
bodyAStatic = bodyA.isStatic || bodyA.isSleeping,
|
||||
partsALength = bodyA.parts.length,
|
||||
partsASingle = partsALength === 1;
|
||||
|
||||
if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter))
|
||||
continue;
|
||||
for (j = i + 1; j < bodiesLength; j++) {
|
||||
var bodyB = bodies[j],
|
||||
boundsB = bodyB.bounds;
|
||||
|
||||
// mid phase
|
||||
if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) {
|
||||
for (var j = bodyA.parts.length > 1 ? 1 : 0; j < bodyA.parts.length; j++) {
|
||||
var partA = bodyA.parts[j];
|
||||
|
||||
for (var k = bodyB.parts.length > 1 ? 1 : 0; k < bodyB.parts.length; k++) {
|
||||
var partB = bodyB.parts[k];
|
||||
|
||||
if ((partA === bodyA && partB === bodyB) || Bounds.overlaps(partA.bounds, partB.bounds)) {
|
||||
// find a previous collision we could reuse
|
||||
var pairId = Pair.id(partA, partB),
|
||||
pair = pairsTable[pairId],
|
||||
previousCollision;
|
||||
|
||||
if (pair && pair.isActive) {
|
||||
previousCollision = pair.collision;
|
||||
} else {
|
||||
previousCollision = null;
|
||||
if (boundsB.min.x > boundXMax) {
|
||||
break;
|
||||
}
|
||||
|
||||
// narrow phase
|
||||
var collision = SAT.collides(partA, partB, previousCollision);
|
||||
if (boundYMax < boundsB.min.y || boundYMin > boundsB.max.y) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collision.collided) {
|
||||
if (bodyAStatic && (bodyB.isStatic || bodyB.isSleeping)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!canCollide(bodyA.collisionFilter, bodyB.collisionFilter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var partsBLength = bodyB.parts.length;
|
||||
|
||||
if (partsASingle && partsBLength === 1) {
|
||||
var collision = collides(bodyA, bodyB, pairs);
|
||||
|
||||
if (collision) {
|
||||
collisions.push(collision);
|
||||
}
|
||||
} else {
|
||||
var partsAStart = partsALength > 1 ? 1 : 0,
|
||||
partsBStart = partsBLength > 1 ? 1 : 0;
|
||||
|
||||
for (var k = partsAStart; k < partsALength; k++) {
|
||||
var partA = bodyA.parts[k],
|
||||
boundsA = partA.bounds;
|
||||
|
||||
for (var z = partsBStart; z < partsBLength; z++) {
|
||||
var partB = bodyB.parts[z],
|
||||
boundsB = partB.bounds;
|
||||
|
||||
if (boundsA.min.x > boundsB.max.x || boundsA.max.x < boundsB.min.x
|
||||
|| boundsA.max.y < boundsB.min.y || boundsA.min.y > boundsB.max.y) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var collision = collides(partA, partB, pairs);
|
||||
|
||||
if (collision) {
|
||||
collisions.push(collision);
|
||||
}
|
||||
}
|
||||
|
@ -87,4 +152,39 @@ var Bounds = require('../geometry/Bounds');
|
|||
return (filterA.mask & filterB.category) !== 0 && (filterB.mask & filterA.category) !== 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* The comparison function used in the broadphase algorithm.
|
||||
* Returns the signed delta of the bodies bounds on the x-axis.
|
||||
* @private
|
||||
* @method _sortCompare
|
||||
* @param {body} bodyA
|
||||
* @param {body} bodyB
|
||||
* @return {number} The signed delta used for sorting
|
||||
*/
|
||||
Detector._compareBoundsX = function(bodyA, bodyB) {
|
||||
return bodyA.bounds.min.x - bodyB.bounds.min.x;
|
||||
};
|
||||
|
||||
/*
|
||||
*
|
||||
* Properties Documentation
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* The array of `Matter.Body` between which the detector finds collisions.
|
||||
*
|
||||
* _Note:_ The order of bodies in this array _is not fixed_ and will be continually managed by the detector.
|
||||
* @property bodies
|
||||
* @type body[]
|
||||
* @default []
|
||||
*/
|
||||
|
||||
/**
|
||||
* Optional. A `Matter.Pairs` object from which previous collision objects may be reused. Intended for internal `Matter.Engine` usage.
|
||||
* @property pairs
|
||||
* @type {pairs|null}
|
||||
* @default null
|
||||
*/
|
||||
|
||||
})();
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
/**
|
||||
* This module has now been replaced by `Matter.Detector`.
|
||||
*
|
||||
* All usage should be migrated to `Matter.Detector` or another alternative.
|
||||
* For back-compatibility purposes this module will remain for a short term and then later removed in a future release.
|
||||
*
|
||||
* The `Matter.Grid` module contains methods for creating and manipulating collision broadphase grid structures.
|
||||
*
|
||||
* @class Grid
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
var Grid = {};
|
||||
|
@ -10,11 +16,13 @@ module.exports = Grid;
|
|||
|
||||
var Pair = require('./Pair');
|
||||
var Common = require('../core/Common');
|
||||
var deprecated = Common.deprecated;
|
||||
|
||||
(function() {
|
||||
|
||||
/**
|
||||
* Creates a new grid.
|
||||
* @deprecated replaced by Matter.Detector
|
||||
* @method create
|
||||
* @param {} options
|
||||
* @return {grid} A new grid
|
||||
|
@ -49,6 +57,7 @@ var Common = require('../core/Common');
|
|||
|
||||
/**
|
||||
* Updates the grid.
|
||||
* @deprecated replaced by Matter.Detector
|
||||
* @method update
|
||||
* @param {grid} grid
|
||||
* @param {body[]} bodies
|
||||
|
@ -127,8 +136,11 @@ var Common = require('../core/Common');
|
|||
grid.pairsList = Grid._createActivePairsList(grid);
|
||||
};
|
||||
|
||||
deprecated(Grid, 'update', 'Grid.update ➤ replaced by Matter.Detector');
|
||||
|
||||
/**
|
||||
* Clears the grid.
|
||||
* @deprecated replaced by Matter.Detector
|
||||
* @method clear
|
||||
* @param {grid} grid
|
||||
*/
|
||||
|
@ -138,9 +150,12 @@ var Common = require('../core/Common');
|
|||
grid.pairsList = [];
|
||||
};
|
||||
|
||||
deprecated(Grid, 'clear', 'Grid.clear ➤ replaced by Matter.Detector');
|
||||
|
||||
/**
|
||||
* Finds the union of two regions.
|
||||
* @method _regionUnion
|
||||
* @deprecated replaced by Matter.Detector
|
||||
* @private
|
||||
* @param {} regionA
|
||||
* @param {} regionB
|
||||
|
@ -158,6 +173,7 @@ var Common = require('../core/Common');
|
|||
/**
|
||||
* Gets the region a given body falls in for a given grid.
|
||||
* @method _getRegion
|
||||
* @deprecated replaced by Matter.Detector
|
||||
* @private
|
||||
* @param {} grid
|
||||
* @param {} body
|
||||
|
@ -176,6 +192,7 @@ var Common = require('../core/Common');
|
|||
/**
|
||||
* Creates a region.
|
||||
* @method _createRegion
|
||||
* @deprecated replaced by Matter.Detector
|
||||
* @private
|
||||
* @param {} startCol
|
||||
* @param {} endCol
|
||||
|
@ -196,6 +213,7 @@ var Common = require('../core/Common');
|
|||
/**
|
||||
* Gets the bucket id at the given position.
|
||||
* @method _getBucketId
|
||||
* @deprecated replaced by Matter.Detector
|
||||
* @private
|
||||
* @param {} column
|
||||
* @param {} row
|
||||
|
@ -208,6 +226,7 @@ var Common = require('../core/Common');
|
|||
/**
|
||||
* Creates a bucket.
|
||||
* @method _createBucket
|
||||
* @deprecated replaced by Matter.Detector
|
||||
* @private
|
||||
* @param {} buckets
|
||||
* @param {} bucketId
|
||||
|
@ -221,14 +240,20 @@ var Common = require('../core/Common');
|
|||
/**
|
||||
* Adds a body to a bucket.
|
||||
* @method _bucketAddBody
|
||||
* @deprecated replaced by Matter.Detector
|
||||
* @private
|
||||
* @param {} grid
|
||||
* @param {} bucket
|
||||
* @param {} body
|
||||
*/
|
||||
Grid._bucketAddBody = function(grid, bucket, body) {
|
||||
var gridPairs = grid.pairs,
|
||||
pairId = Pair.id,
|
||||
bucketLength = bucket.length,
|
||||
i;
|
||||
|
||||
// add new pairs
|
||||
for (var i = 0; i < bucket.length; i++) {
|
||||
for (i = 0; i < bucketLength; i++) {
|
||||
var bodyB = bucket[i];
|
||||
|
||||
if (body.id === bodyB.id || (body.isStatic && bodyB.isStatic))
|
||||
|
@ -236,13 +261,13 @@ var Common = require('../core/Common');
|
|||
|
||||
// keep track of the number of buckets the pair exists in
|
||||
// important for Grid.update to work
|
||||
var pairId = Pair.id(body, bodyB),
|
||||
pair = grid.pairs[pairId];
|
||||
var id = pairId(body, bodyB),
|
||||
pair = gridPairs[id];
|
||||
|
||||
if (pair) {
|
||||
pair[2] += 1;
|
||||
} else {
|
||||
grid.pairs[pairId] = [body, bodyB, 1];
|
||||
gridPairs[id] = [body, bodyB, 1];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,22 +278,27 @@ var Common = require('../core/Common');
|
|||
/**
|
||||
* Removes a body from a bucket.
|
||||
* @method _bucketRemoveBody
|
||||
* @deprecated replaced by Matter.Detector
|
||||
* @private
|
||||
* @param {} grid
|
||||
* @param {} bucket
|
||||
* @param {} body
|
||||
*/
|
||||
Grid._bucketRemoveBody = function(grid, bucket, body) {
|
||||
var gridPairs = grid.pairs,
|
||||
pairId = Pair.id,
|
||||
i;
|
||||
|
||||
// remove from bucket
|
||||
bucket.splice(Common.indexOf(bucket, body), 1);
|
||||
|
||||
var bucketLength = bucket.length;
|
||||
|
||||
// update pair counts
|
||||
for (var i = 0; i < bucket.length; i++) {
|
||||
for (i = 0; i < bucketLength; i++) {
|
||||
// keep track of the number of buckets the pair exists in
|
||||
// important for _createActivePairsList to work
|
||||
var bodyB = bucket[i],
|
||||
pairId = Pair.id(body, bodyB),
|
||||
pair = grid.pairs[pairId];
|
||||
var pair = gridPairs[pairId(body, bucket[i])];
|
||||
|
||||
if (pair)
|
||||
pair[2] -= 1;
|
||||
|
@ -278,28 +308,29 @@ var Common = require('../core/Common');
|
|||
/**
|
||||
* Generates a list of the active pairs in the grid.
|
||||
* @method _createActivePairsList
|
||||
* @deprecated replaced by Matter.Detector
|
||||
* @private
|
||||
* @param {} grid
|
||||
* @return [] pairs
|
||||
*/
|
||||
Grid._createActivePairsList = function(grid) {
|
||||
var pairKeys,
|
||||
pair,
|
||||
pairs = [];
|
||||
|
||||
// grid.pairs is used as a hashmap
|
||||
pairKeys = Common.keys(grid.pairs);
|
||||
var pair,
|
||||
gridPairs = grid.pairs,
|
||||
pairKeys = Common.keys(gridPairs),
|
||||
pairKeysLength = pairKeys.length,
|
||||
pairs = [],
|
||||
k;
|
||||
|
||||
// iterate over grid.pairs
|
||||
for (var k = 0; k < pairKeys.length; k++) {
|
||||
pair = grid.pairs[pairKeys[k]];
|
||||
for (k = 0; k < pairKeysLength; k++) {
|
||||
pair = gridPairs[pairKeys[k]];
|
||||
|
||||
// if pair exists in at least one bucket
|
||||
// it is a pair that needs further collision testing so push it
|
||||
if (pair[2] > 0) {
|
||||
pairs.push(pair);
|
||||
} else {
|
||||
delete grid.pairs[pairKeys[k]];
|
||||
delete gridPairs[pairKeys[k]];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,15 +21,14 @@ var Contact = require('./Contact');
|
|||
*/
|
||||
Pair.create = function(collision, timestamp) {
|
||||
var bodyA = collision.bodyA,
|
||||
bodyB = collision.bodyB,
|
||||
parentA = collision.parentA,
|
||||
parentB = collision.parentB;
|
||||
bodyB = collision.bodyB;
|
||||
|
||||
var pair = {
|
||||
id: Pair.id(bodyA, bodyB),
|
||||
bodyA: bodyA,
|
||||
bodyB: bodyB,
|
||||
contacts: {},
|
||||
collision: collision,
|
||||
contacts: [],
|
||||
activeContacts: [],
|
||||
separation: 0,
|
||||
isActive: true,
|
||||
|
@ -37,11 +36,11 @@ var Contact = require('./Contact');
|
|||
isSensor: bodyA.isSensor || bodyB.isSensor,
|
||||
timeCreated: timestamp,
|
||||
timeUpdated: timestamp,
|
||||
inverseMass: parentA.inverseMass + parentB.inverseMass,
|
||||
friction: Math.min(parentA.friction, parentB.friction),
|
||||
frictionStatic: Math.max(parentA.frictionStatic, parentB.frictionStatic),
|
||||
restitution: Math.max(parentA.restitution, parentB.restitution),
|
||||
slop: Math.max(parentA.slop, parentB.slop)
|
||||
inverseMass: 0,
|
||||
friction: 0,
|
||||
frictionStatic: 0,
|
||||
restitution: 0,
|
||||
slop: 0
|
||||
};
|
||||
|
||||
Pair.update(pair, collision, timestamp);
|
||||
|
@ -61,20 +60,25 @@ var Contact = require('./Contact');
|
|||
supports = collision.supports,
|
||||
activeContacts = pair.activeContacts,
|
||||
parentA = collision.parentA,
|
||||
parentB = collision.parentB;
|
||||
parentB = collision.parentB,
|
||||
parentAVerticesLength = parentA.vertices.length;
|
||||
|
||||
pair.isActive = true;
|
||||
pair.timeUpdated = timestamp;
|
||||
pair.collision = collision;
|
||||
pair.separation = collision.depth;
|
||||
pair.inverseMass = parentA.inverseMass + parentB.inverseMass;
|
||||
pair.friction = Math.min(parentA.friction, parentB.friction);
|
||||
pair.frictionStatic = Math.max(parentA.frictionStatic, parentB.frictionStatic);
|
||||
pair.restitution = Math.max(parentA.restitution, parentB.restitution);
|
||||
pair.slop = Math.max(parentA.slop, parentB.slop);
|
||||
pair.friction = parentA.friction < parentB.friction ? parentA.friction : parentB.friction;
|
||||
pair.frictionStatic = parentA.frictionStatic > parentB.frictionStatic ? parentA.frictionStatic : parentB.frictionStatic;
|
||||
pair.restitution = parentA.restitution > parentB.restitution ? parentA.restitution : parentB.restitution;
|
||||
pair.slop = parentA.slop > parentB.slop ? parentA.slop : parentB.slop;
|
||||
|
||||
collision.pair = pair;
|
||||
activeContacts.length = 0;
|
||||
|
||||
if (collision.collided) {
|
||||
for (var i = 0; i < supports.length; i++) {
|
||||
var support = supports[i],
|
||||
contactId = Contact.id(support),
|
||||
contactId = support.body === parentA ? support.index : parentAVerticesLength + support.index,
|
||||
contact = contacts[contactId];
|
||||
|
||||
if (contact) {
|
||||
|
@ -83,13 +87,6 @@ var Contact = require('./Contact');
|
|||
activeContacts.push(contacts[contactId] = Contact.create(support));
|
||||
}
|
||||
}
|
||||
|
||||
pair.separation = collision.depth;
|
||||
Pair.setActive(pair, true, timestamp);
|
||||
} else {
|
||||
if (pair.isActive === true)
|
||||
Pair.setActive(pair, false, timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,8 +13,6 @@ var Common = require('../core/Common');
|
|||
|
||||
(function() {
|
||||
|
||||
Pairs._pairMaxIdleLife = 1000;
|
||||
|
||||
/**
|
||||
* Creates a new pairs structure.
|
||||
* @method create
|
||||
|
@ -40,12 +38,14 @@ var Common = require('../core/Common');
|
|||
*/
|
||||
Pairs.update = function(pairs, collisions, timestamp) {
|
||||
var pairsList = pairs.list,
|
||||
pairsListLength = pairsList.length,
|
||||
pairsTable = pairs.table,
|
||||
collisionsLength = collisions.length,
|
||||
collisionStart = pairs.collisionStart,
|
||||
collisionEnd = pairs.collisionEnd,
|
||||
collisionActive = pairs.collisionActive,
|
||||
collision,
|
||||
pairId,
|
||||
pairIndex,
|
||||
pair,
|
||||
i;
|
||||
|
||||
|
@ -54,17 +54,13 @@ var Common = require('../core/Common');
|
|||
collisionEnd.length = 0;
|
||||
collisionActive.length = 0;
|
||||
|
||||
for (i = 0; i < pairsList.length; i++) {
|
||||
for (i = 0; i < pairsListLength; i++) {
|
||||
pairsList[i].confirmedActive = false;
|
||||
}
|
||||
|
||||
for (i = 0; i < collisions.length; i++) {
|
||||
for (i = 0; i < collisionsLength; i++) {
|
||||
collision = collisions[i];
|
||||
|
||||
if (collision.collided) {
|
||||
pairId = Pair.id(collision.bodyA, collision.bodyB);
|
||||
|
||||
pair = pairsTable[pairId];
|
||||
pair = collision.pair;
|
||||
|
||||
if (pair) {
|
||||
// pair already exists (but may or may not be active)
|
||||
|
@ -82,62 +78,37 @@ var Common = require('../core/Common');
|
|||
} else {
|
||||
// pair did not exist, create a new pair
|
||||
pair = Pair.create(collision, timestamp);
|
||||
pairsTable[pairId] = pair;
|
||||
pairsTable[pair.id] = pair;
|
||||
|
||||
// push the new pair
|
||||
collisionStart.push(pair);
|
||||
pairsList.push(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deactivate previously active pairs that are now inactive
|
||||
for (i = 0; i < pairsList.length; i++) {
|
||||
// find pairs that are no longer active
|
||||
var removePairIndex = [];
|
||||
pairsListLength = pairsList.length;
|
||||
|
||||
for (i = 0; i < pairsListLength; i++) {
|
||||
pair = pairsList[i];
|
||||
if (pair.isActive && !pair.confirmedActive) {
|
||||
|
||||
if (!pair.confirmedActive) {
|
||||
Pair.setActive(pair, false, timestamp);
|
||||
collisionEnd.push(pair);
|
||||
|
||||
if (!pair.collision.bodyA.isSleeping && !pair.collision.bodyB.isSleeping) {
|
||||
removePairIndex.push(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds and removes pairs that have been inactive for a set amount of time.
|
||||
* @method removeOld
|
||||
* @param {object} pairs
|
||||
* @param {number} timestamp
|
||||
*/
|
||||
Pairs.removeOld = function(pairs, timestamp) {
|
||||
var pairsList = pairs.list,
|
||||
pairsTable = pairs.table,
|
||||
indexesToRemove = [],
|
||||
pair,
|
||||
collision,
|
||||
pairIndex,
|
||||
i;
|
||||
|
||||
for (i = 0; i < pairsList.length; i++) {
|
||||
pair = pairsList[i];
|
||||
collision = pair.collision;
|
||||
|
||||
// never remove sleeping pairs
|
||||
if (collision.bodyA.isSleeping || collision.bodyB.isSleeping) {
|
||||
pair.timeUpdated = timestamp;
|
||||
continue;
|
||||
}
|
||||
|
||||
// if pair is inactive for too long, mark it to be removed
|
||||
if (timestamp - pair.timeUpdated > Pairs._pairMaxIdleLife) {
|
||||
indexesToRemove.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
// remove marked pairs
|
||||
for (i = 0; i < indexesToRemove.length; i++) {
|
||||
pairIndex = indexesToRemove[i] - i;
|
||||
// remove inactive pairs
|
||||
for (i = 0; i < removePairIndex.length; i++) {
|
||||
pairIndex = removePairIndex[i] - i;
|
||||
pair = pairsList[pairIndex];
|
||||
delete pairsTable[pair.id];
|
||||
pairsList.splice(pairIndex, 1);
|
||||
delete pairsTable[pair.id];
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ var Query = {};
|
|||
module.exports = Query;
|
||||
|
||||
var Vector = require('../geometry/Vector');
|
||||
var SAT = require('./SAT');
|
||||
var Collision = require('./Collision');
|
||||
var Bounds = require('../geometry/Bounds');
|
||||
var Bodies = require('../factory/Bodies');
|
||||
var Vertices = require('../geometry/Vertices');
|
||||
|
@ -23,22 +23,28 @@ var Vertices = require('../geometry/Vertices');
|
|||
* @method collides
|
||||
* @param {body} body
|
||||
* @param {body[]} bodies
|
||||
* @return {object[]} Collisions
|
||||
* @return {collision[]} Collisions
|
||||
*/
|
||||
Query.collides = function(body, bodies) {
|
||||
var collisions = [];
|
||||
var collisions = [],
|
||||
bodiesLength = bodies.length,
|
||||
bounds = body.bounds,
|
||||
collides = Collision.collides,
|
||||
overlaps = Bounds.overlaps;
|
||||
|
||||
for (var i = 0; i < bodies.length; i++) {
|
||||
var bodyA = bodies[i];
|
||||
for (var i = 0; i < bodiesLength; i++) {
|
||||
var bodyA = bodies[i],
|
||||
partsALength = bodyA.parts.length,
|
||||
partsAStart = partsALength === 1 ? 0 : 1;
|
||||
|
||||
if (Bounds.overlaps(bodyA.bounds, body.bounds)) {
|
||||
for (var j = bodyA.parts.length === 1 ? 0 : 1; j < bodyA.parts.length; j++) {
|
||||
if (overlaps(bodyA.bounds, bounds)) {
|
||||
for (var j = partsAStart; j < partsALength; j++) {
|
||||
var part = bodyA.parts[j];
|
||||
|
||||
if (Bounds.overlaps(part.bounds, body.bounds)) {
|
||||
var collision = SAT.collides(part, body);
|
||||
if (overlaps(part.bounds, bounds)) {
|
||||
var collision = collides(part, body);
|
||||
|
||||
if (collision.collided) {
|
||||
if (collision) {
|
||||
collisions.push(collision);
|
||||
break;
|
||||
}
|
||||
|
@ -57,7 +63,7 @@ var Vertices = require('../geometry/Vertices');
|
|||
* @param {vector} startPoint
|
||||
* @param {vector} endPoint
|
||||
* @param {number} [rayWidth]
|
||||
* @return {object[]} Collisions
|
||||
* @return {collision[]} Collisions
|
||||
*/
|
||||
Query.ray = function(bodies, startPoint, endPoint, rayWidth) {
|
||||
rayWidth = rayWidth || 1e-100;
|
||||
|
|
|
@ -9,8 +9,6 @@ var Resolver = {};
|
|||
module.exports = Resolver;
|
||||
|
||||
var Vertices = require('../geometry/Vertices');
|
||||
var Vector = require('../geometry/Vector');
|
||||
var Common = require('../core/Common');
|
||||
var Bounds = require('../geometry/Bounds');
|
||||
|
||||
(function() {
|
||||
|
@ -29,10 +27,11 @@ var Bounds = require('../geometry/Bounds');
|
|||
Resolver.preSolvePosition = function(pairs) {
|
||||
var i,
|
||||
pair,
|
||||
activeCount;
|
||||
activeCount,
|
||||
pairsLength = pairs.length;
|
||||
|
||||
// find total contacts on each body
|
||||
for (i = 0; i < pairs.length; i++) {
|
||||
for (i = 0; i < pairsLength; i++) {
|
||||
pair = pairs[i];
|
||||
|
||||
if (!pair.isActive)
|
||||
|
@ -57,17 +56,13 @@ var Bounds = require('../geometry/Bounds');
|
|||
bodyA,
|
||||
bodyB,
|
||||
normal,
|
||||
bodyBtoA,
|
||||
contactShare,
|
||||
positionImpulse,
|
||||
contactCount = {},
|
||||
tempA = Vector._temp[0],
|
||||
tempB = Vector._temp[1],
|
||||
tempC = Vector._temp[2],
|
||||
tempD = Vector._temp[3];
|
||||
positionDampen = Resolver._positionDampen,
|
||||
pairsLength = pairs.length;
|
||||
|
||||
// find impulses required to resolve penetration
|
||||
for (i = 0; i < pairs.length; i++) {
|
||||
for (i = 0; i < pairsLength; i++) {
|
||||
pair = pairs[i];
|
||||
|
||||
if (!pair.isActive || pair.isSensor)
|
||||
|
@ -79,14 +74,12 @@ var Bounds = require('../geometry/Bounds');
|
|||
normal = collision.normal;
|
||||
|
||||
// get current separation between body edges involved in collision
|
||||
bodyBtoA = Vector.sub(Vector.add(bodyB.positionImpulse, bodyB.position, tempA),
|
||||
Vector.add(bodyA.positionImpulse,
|
||||
Vector.sub(bodyB.position, collision.penetration, tempB), tempC), tempD);
|
||||
|
||||
pair.separation = Vector.dot(normal, bodyBtoA);
|
||||
pair.separation =
|
||||
normal.x * (bodyB.positionImpulse.x + collision.penetration.x - bodyA.positionImpulse.x)
|
||||
+ normal.y * (bodyB.positionImpulse.y + collision.penetration.y - bodyA.positionImpulse.y);
|
||||
}
|
||||
|
||||
for (i = 0; i < pairs.length; i++) {
|
||||
for (i = 0; i < pairsLength; i++) {
|
||||
pair = pairs[i];
|
||||
|
||||
if (!pair.isActive || pair.isSensor)
|
||||
|
@ -102,13 +95,13 @@ var Bounds = require('../geometry/Bounds');
|
|||
positionImpulse *= 2;
|
||||
|
||||
if (!(bodyA.isStatic || bodyA.isSleeping)) {
|
||||
contactShare = Resolver._positionDampen / bodyA.totalContacts;
|
||||
contactShare = positionDampen / bodyA.totalContacts;
|
||||
bodyA.positionImpulse.x += normal.x * positionImpulse * contactShare;
|
||||
bodyA.positionImpulse.y += normal.y * positionImpulse * contactShare;
|
||||
}
|
||||
|
||||
if (!(bodyB.isStatic || bodyB.isSleeping)) {
|
||||
contactShare = Resolver._positionDampen / bodyB.totalContacts;
|
||||
contactShare = positionDampen / bodyB.totalContacts;
|
||||
bodyB.positionImpulse.x -= normal.x * positionImpulse * contactShare;
|
||||
bodyB.positionImpulse.y -= normal.y * positionImpulse * contactShare;
|
||||
}
|
||||
|
@ -121,34 +114,43 @@ var Bounds = require('../geometry/Bounds');
|
|||
* @param {body[]} bodies
|
||||
*/
|
||||
Resolver.postSolvePosition = function(bodies) {
|
||||
for (var i = 0; i < bodies.length; i++) {
|
||||
var body = bodies[i];
|
||||
var positionWarming = Resolver._positionWarming,
|
||||
bodiesLength = bodies.length,
|
||||
verticesTranslate = Vertices.translate,
|
||||
boundsUpdate = Bounds.update;
|
||||
|
||||
for (var i = 0; i < bodiesLength; i++) {
|
||||
var body = bodies[i],
|
||||
positionImpulse = body.positionImpulse,
|
||||
positionImpulseX = positionImpulse.x,
|
||||
positionImpulseY = positionImpulse.y,
|
||||
velocity = body.velocity;
|
||||
|
||||
// reset contact count
|
||||
body.totalContacts = 0;
|
||||
|
||||
if (body.positionImpulse.x !== 0 || body.positionImpulse.y !== 0) {
|
||||
if (positionImpulseX !== 0 || positionImpulseY !== 0) {
|
||||
// update body geometry
|
||||
for (var j = 0; j < body.parts.length; j++) {
|
||||
var part = body.parts[j];
|
||||
Vertices.translate(part.vertices, body.positionImpulse);
|
||||
Bounds.update(part.bounds, part.vertices, body.velocity);
|
||||
part.position.x += body.positionImpulse.x;
|
||||
part.position.y += body.positionImpulse.y;
|
||||
verticesTranslate(part.vertices, positionImpulse);
|
||||
boundsUpdate(part.bounds, part.vertices, velocity);
|
||||
part.position.x += positionImpulseX;
|
||||
part.position.y += positionImpulseY;
|
||||
}
|
||||
|
||||
// move the body without changing velocity
|
||||
body.positionPrev.x += body.positionImpulse.x;
|
||||
body.positionPrev.y += body.positionImpulse.y;
|
||||
body.positionPrev.x += positionImpulseX;
|
||||
body.positionPrev.y += positionImpulseY;
|
||||
|
||||
if (Vector.dot(body.positionImpulse, body.velocity) < 0) {
|
||||
if (positionImpulseX * velocity.x + positionImpulseY * velocity.y < 0) {
|
||||
// reset cached impulse if the body has velocity along it
|
||||
body.positionImpulse.x = 0;
|
||||
body.positionImpulse.y = 0;
|
||||
positionImpulse.x = 0;
|
||||
positionImpulse.y = 0;
|
||||
} else {
|
||||
// warm the next iteration
|
||||
body.positionImpulse.x *= Resolver._positionWarming;
|
||||
body.positionImpulse.y *= Resolver._positionWarming;
|
||||
positionImpulse.x *= positionWarming;
|
||||
positionImpulse.y *= positionWarming;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,61 +162,53 @@ var Bounds = require('../geometry/Bounds');
|
|||
* @param {pair[]} pairs
|
||||
*/
|
||||
Resolver.preSolveVelocity = function(pairs) {
|
||||
var i,
|
||||
j,
|
||||
pair,
|
||||
contacts,
|
||||
collision,
|
||||
bodyA,
|
||||
bodyB,
|
||||
normal,
|
||||
tangent,
|
||||
contact,
|
||||
contactVertex,
|
||||
normalImpulse,
|
||||
tangentImpulse,
|
||||
offset,
|
||||
impulse = Vector._temp[0],
|
||||
tempA = Vector._temp[1];
|
||||
var pairsLength = pairs.length,
|
||||
i,
|
||||
j;
|
||||
|
||||
for (i = 0; i < pairs.length; i++) {
|
||||
pair = pairs[i];
|
||||
for (i = 0; i < pairsLength; i++) {
|
||||
var pair = pairs[i];
|
||||
|
||||
if (!pair.isActive || pair.isSensor)
|
||||
continue;
|
||||
|
||||
contacts = pair.activeContacts;
|
||||
collision = pair.collision;
|
||||
bodyA = collision.parentA;
|
||||
bodyB = collision.parentB;
|
||||
normal = collision.normal;
|
||||
var contacts = pair.activeContacts,
|
||||
contactsLength = contacts.length,
|
||||
collision = pair.collision,
|
||||
bodyA = collision.parentA,
|
||||
bodyB = collision.parentB,
|
||||
normal = collision.normal,
|
||||
tangent = collision.tangent;
|
||||
|
||||
// resolve each contact
|
||||
for (j = 0; j < contacts.length; j++) {
|
||||
contact = contacts[j];
|
||||
contactVertex = contact.vertex;
|
||||
normalImpulse = contact.normalImpulse;
|
||||
for (j = 0; j < contactsLength; j++) {
|
||||
var contact = contacts[j],
|
||||
contactVertex = contact.vertex,
|
||||
normalImpulse = contact.normalImpulse,
|
||||
tangentImpulse = contact.tangentImpulse;
|
||||
|
||||
if (normalImpulse !== 0 || tangentImpulse !== 0) {
|
||||
// total impulse from contact
|
||||
impulse.x = (normal.x * normalImpulse) + (tangent.x * tangentImpulse);
|
||||
impulse.y = (normal.y * normalImpulse) + (tangent.y * tangentImpulse);
|
||||
var impulseX = normal.x * normalImpulse + tangent.x * tangentImpulse,
|
||||
impulseY = normal.y * normalImpulse + tangent.y * tangentImpulse;
|
||||
|
||||
// apply impulse from contact
|
||||
if (!(bodyA.isStatic || bodyA.isSleeping)) {
|
||||
offset = Vector.sub(contactVertex, bodyA.position, tempA);
|
||||
bodyA.positionPrev.x += impulse.x * bodyA.inverseMass;
|
||||
bodyA.positionPrev.y += impulse.y * bodyA.inverseMass;
|
||||
bodyA.anglePrev += Vector.cross(offset, impulse) * bodyA.inverseInertia;
|
||||
bodyA.positionPrev.x += impulseX * bodyA.inverseMass;
|
||||
bodyA.positionPrev.y += impulseY * bodyA.inverseMass;
|
||||
bodyA.anglePrev += bodyA.inverseInertia * (
|
||||
(contactVertex.x - bodyA.position.x) * impulseY
|
||||
- (contactVertex.y - bodyA.position.y) * impulseX
|
||||
);
|
||||
}
|
||||
|
||||
if (!(bodyB.isStatic || bodyB.isSleeping)) {
|
||||
offset = Vector.sub(contactVertex, bodyB.position, tempA);
|
||||
bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass;
|
||||
bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass;
|
||||
bodyB.anglePrev -= Vector.cross(offset, impulse) * bodyB.inverseInertia;
|
||||
bodyB.positionPrev.x -= impulseX * bodyB.inverseMass;
|
||||
bodyB.positionPrev.y -= impulseY * bodyB.inverseMass;
|
||||
bodyB.anglePrev -= bodyB.inverseInertia * (
|
||||
(contactVertex.x - bodyB.position.x) * impulseY
|
||||
- (contactVertex.y - bodyB.position.y) * impulseX
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -229,14 +223,17 @@ var Bounds = require('../geometry/Bounds');
|
|||
*/
|
||||
Resolver.solveVelocity = function(pairs, timeScale) {
|
||||
var timeScaleSquared = timeScale * timeScale,
|
||||
impulse = Vector._temp[0],
|
||||
tempA = Vector._temp[1],
|
||||
tempB = Vector._temp[2],
|
||||
tempC = Vector._temp[3],
|
||||
tempD = Vector._temp[4],
|
||||
tempE = Vector._temp[5];
|
||||
restingThresh = Resolver._restingThresh * timeScaleSquared,
|
||||
frictionNormalMultiplier = Resolver._frictionNormalMultiplier,
|
||||
restingThreshTangent = Resolver._restingThreshTangent * timeScaleSquared,
|
||||
NumberMaxValue = Number.MAX_VALUE,
|
||||
pairsLength = pairs.length,
|
||||
tangentImpulse,
|
||||
maxFriction,
|
||||
i,
|
||||
j;
|
||||
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
for (i = 0; i < pairsLength; i++) {
|
||||
var pair = pairs[i];
|
||||
|
||||
if (!pair.isActive || pair.isSensor)
|
||||
|
@ -245,97 +242,119 @@ var Bounds = require('../geometry/Bounds');
|
|||
var collision = pair.collision,
|
||||
bodyA = collision.parentA,
|
||||
bodyB = collision.parentB,
|
||||
normal = collision.normal,
|
||||
tangent = collision.tangent,
|
||||
bodyAVelocity = bodyA.velocity,
|
||||
bodyBVelocity = bodyB.velocity,
|
||||
normalX = collision.normal.x,
|
||||
normalY = collision.normal.y,
|
||||
tangentX = collision.tangent.x,
|
||||
tangentY = collision.tangent.y,
|
||||
contacts = pair.activeContacts,
|
||||
contactShare = 1 / contacts.length;
|
||||
contactsLength = contacts.length,
|
||||
contactShare = 1 / contactsLength,
|
||||
inverseMassTotal = bodyA.inverseMass + bodyB.inverseMass,
|
||||
friction = pair.friction * pair.frictionStatic * frictionNormalMultiplier * timeScaleSquared;
|
||||
|
||||
// update body velocities
|
||||
bodyA.velocity.x = bodyA.position.x - bodyA.positionPrev.x;
|
||||
bodyA.velocity.y = bodyA.position.y - bodyA.positionPrev.y;
|
||||
bodyB.velocity.x = bodyB.position.x - bodyB.positionPrev.x;
|
||||
bodyB.velocity.y = bodyB.position.y - bodyB.positionPrev.y;
|
||||
bodyAVelocity.x = bodyA.position.x - bodyA.positionPrev.x;
|
||||
bodyAVelocity.y = bodyA.position.y - bodyA.positionPrev.y;
|
||||
bodyBVelocity.x = bodyB.position.x - bodyB.positionPrev.x;
|
||||
bodyBVelocity.y = bodyB.position.y - bodyB.positionPrev.y;
|
||||
bodyA.angularVelocity = bodyA.angle - bodyA.anglePrev;
|
||||
bodyB.angularVelocity = bodyB.angle - bodyB.anglePrev;
|
||||
|
||||
// resolve each contact
|
||||
for (var j = 0; j < contacts.length; j++) {
|
||||
for (j = 0; j < contactsLength; j++) {
|
||||
var contact = contacts[j],
|
||||
contactVertex = contact.vertex,
|
||||
offsetA = Vector.sub(contactVertex, bodyA.position, tempA),
|
||||
offsetB = Vector.sub(contactVertex, bodyB.position, tempB),
|
||||
velocityPointA = Vector.add(bodyA.velocity, Vector.mult(Vector.perp(offsetA), bodyA.angularVelocity), tempC),
|
||||
velocityPointB = Vector.add(bodyB.velocity, Vector.mult(Vector.perp(offsetB), bodyB.angularVelocity), tempD),
|
||||
relativeVelocity = Vector.sub(velocityPointA, velocityPointB, tempE),
|
||||
normalVelocity = Vector.dot(normal, relativeVelocity);
|
||||
contactVertex = contact.vertex;
|
||||
|
||||
var tangentVelocity = Vector.dot(tangent, relativeVelocity),
|
||||
tangentSpeed = Math.abs(tangentVelocity),
|
||||
tangentVelocityDirection = Common.sign(tangentVelocity);
|
||||
var offsetAX = contactVertex.x - bodyA.position.x,
|
||||
offsetAY = contactVertex.y - bodyA.position.y,
|
||||
offsetBX = contactVertex.x - bodyB.position.x,
|
||||
offsetBY = contactVertex.y - bodyB.position.y;
|
||||
|
||||
// raw impulses
|
||||
var normalImpulse = (1 + pair.restitution) * normalVelocity,
|
||||
normalForce = Common.clamp(pair.separation + normalVelocity, 0, 1) * Resolver._frictionNormalMultiplier;
|
||||
var velocityPointAX = bodyAVelocity.x - offsetAY * bodyA.angularVelocity,
|
||||
velocityPointAY = bodyAVelocity.y + offsetAX * bodyA.angularVelocity,
|
||||
velocityPointBX = bodyBVelocity.x - offsetBY * bodyB.angularVelocity,
|
||||
velocityPointBY = bodyBVelocity.y + offsetBX * bodyB.angularVelocity;
|
||||
|
||||
var relativeVelocityX = velocityPointAX - velocityPointBX,
|
||||
relativeVelocityY = velocityPointAY - velocityPointBY;
|
||||
|
||||
var normalVelocity = normalX * relativeVelocityX + normalY * relativeVelocityY,
|
||||
tangentVelocity = tangentX * relativeVelocityX + tangentY * relativeVelocityY;
|
||||
|
||||
// coulomb friction
|
||||
var tangentImpulse = tangentVelocity,
|
||||
maxFriction = Infinity;
|
||||
var normalOverlap = pair.separation + normalVelocity;
|
||||
var normalForce = Math.min(normalOverlap, 1);
|
||||
normalForce = normalOverlap < 0 ? 0 : normalForce;
|
||||
|
||||
if (tangentSpeed > pair.friction * pair.frictionStatic * normalForce * timeScaleSquared) {
|
||||
maxFriction = tangentSpeed;
|
||||
tangentImpulse = Common.clamp(
|
||||
pair.friction * tangentVelocityDirection * timeScaleSquared,
|
||||
-maxFriction, maxFriction
|
||||
);
|
||||
var frictionLimit = normalForce * friction;
|
||||
|
||||
if (tangentVelocity > frictionLimit || -tangentVelocity > frictionLimit) {
|
||||
maxFriction = tangentVelocity > 0 ? tangentVelocity : -tangentVelocity;
|
||||
tangentImpulse = pair.friction * (tangentVelocity > 0 ? 1 : -1) * timeScaleSquared;
|
||||
|
||||
if (tangentImpulse < -maxFriction) {
|
||||
tangentImpulse = -maxFriction;
|
||||
} else if (tangentImpulse > maxFriction) {
|
||||
tangentImpulse = maxFriction;
|
||||
}
|
||||
} else {
|
||||
tangentImpulse = tangentVelocity;
|
||||
maxFriction = NumberMaxValue;
|
||||
}
|
||||
|
||||
// modify impulses accounting for mass, inertia and offset
|
||||
var oAcN = Vector.cross(offsetA, normal),
|
||||
oBcN = Vector.cross(offsetB, normal),
|
||||
share = contactShare / (bodyA.inverseMass + bodyB.inverseMass + bodyA.inverseInertia * oAcN * oAcN + bodyB.inverseInertia * oBcN * oBcN);
|
||||
// account for mass, inertia and contact offset
|
||||
var oAcN = offsetAX * normalY - offsetAY * normalX,
|
||||
oBcN = offsetBX * normalY - offsetBY * normalX,
|
||||
share = contactShare / (inverseMassTotal + bodyA.inverseInertia * oAcN * oAcN + bodyB.inverseInertia * oBcN * oBcN);
|
||||
|
||||
normalImpulse *= share;
|
||||
// raw impulses
|
||||
var normalImpulse = (1 + pair.restitution) * normalVelocity * share;
|
||||
tangentImpulse *= share;
|
||||
|
||||
// handle high velocity and resting collisions separately
|
||||
if (normalVelocity < 0 && normalVelocity * normalVelocity > Resolver._restingThresh * timeScaleSquared) {
|
||||
if (normalVelocity * normalVelocity > restingThresh && normalVelocity < 0) {
|
||||
// high normal velocity so clear cached contact normal impulse
|
||||
contact.normalImpulse = 0;
|
||||
} else {
|
||||
// solve resting collision constraints using Erin Catto's method (GDC08)
|
||||
// impulse constraint tends to 0
|
||||
var contactNormalImpulse = contact.normalImpulse;
|
||||
contact.normalImpulse = Math.min(contact.normalImpulse + normalImpulse, 0);
|
||||
contact.normalImpulse += normalImpulse;
|
||||
contact.normalImpulse = Math.min(contact.normalImpulse, 0);
|
||||
normalImpulse = contact.normalImpulse - contactNormalImpulse;
|
||||
}
|
||||
|
||||
// handle high velocity and resting collisions separately
|
||||
if (tangentVelocity * tangentVelocity > Resolver._restingThreshTangent * timeScaleSquared) {
|
||||
if (tangentVelocity * tangentVelocity > restingThreshTangent) {
|
||||
// high tangent velocity so clear cached contact tangent impulse
|
||||
contact.tangentImpulse = 0;
|
||||
} else {
|
||||
// solve resting collision constraints using Erin Catto's method (GDC08)
|
||||
// tangent impulse tends to -tangentSpeed or +tangentSpeed
|
||||
var contactTangentImpulse = contact.tangentImpulse;
|
||||
contact.tangentImpulse = Common.clamp(contact.tangentImpulse + tangentImpulse, -maxFriction, maxFriction);
|
||||
contact.tangentImpulse += tangentImpulse;
|
||||
if (contact.tangentImpulse < -maxFriction) contact.tangentImpulse = -maxFriction;
|
||||
if (contact.tangentImpulse > maxFriction) contact.tangentImpulse = maxFriction;
|
||||
tangentImpulse = contact.tangentImpulse - contactTangentImpulse;
|
||||
}
|
||||
|
||||
// total impulse from contact
|
||||
impulse.x = (normal.x * normalImpulse) + (tangent.x * tangentImpulse);
|
||||
impulse.y = (normal.y * normalImpulse) + (tangent.y * tangentImpulse);
|
||||
var impulseX = normalX * normalImpulse + tangentX * tangentImpulse,
|
||||
impulseY = normalY * normalImpulse + tangentY * tangentImpulse;
|
||||
|
||||
// apply impulse from contact
|
||||
if (!(bodyA.isStatic || bodyA.isSleeping)) {
|
||||
bodyA.positionPrev.x += impulse.x * bodyA.inverseMass;
|
||||
bodyA.positionPrev.y += impulse.y * bodyA.inverseMass;
|
||||
bodyA.anglePrev += Vector.cross(offsetA, impulse) * bodyA.inverseInertia;
|
||||
bodyA.positionPrev.x += impulseX * bodyA.inverseMass;
|
||||
bodyA.positionPrev.y += impulseY * bodyA.inverseMass;
|
||||
bodyA.anglePrev += (offsetAX * impulseY - offsetAY * impulseX) * bodyA.inverseInertia;
|
||||
}
|
||||
|
||||
if (!(bodyB.isStatic || bodyB.isSleeping)) {
|
||||
bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass;
|
||||
bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass;
|
||||
bodyB.anglePrev -= Vector.cross(offsetB, impulse) * bodyB.inverseInertia;
|
||||
bodyB.positionPrev.x -= impulseX * bodyB.inverseMass;
|
||||
bodyB.positionPrev.y -= impulseY * bodyB.inverseMass;
|
||||
bodyB.anglePrev -= (offsetBX * impulseY - offsetBY * impulseX) * bodyB.inverseInertia;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,270 +1,37 @@
|
|||
/**
|
||||
* This module has now been replaced by `Matter.Collision`.
|
||||
*
|
||||
* All usage should be migrated to `Matter.Collision`.
|
||||
* For back-compatibility purposes this module will remain for a short term and then later removed in a future release.
|
||||
*
|
||||
* The `Matter.SAT` module contains methods for detecting collisions using the Separating Axis Theorem.
|
||||
*
|
||||
* @class SAT
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
// TODO: true circles and curves
|
||||
|
||||
var SAT = {};
|
||||
|
||||
module.exports = SAT;
|
||||
|
||||
var Vertices = require('../geometry/Vertices');
|
||||
var Vector = require('../geometry/Vector');
|
||||
var Collision = require('./Collision');
|
||||
var Common = require('../core/Common');
|
||||
var deprecated = Common.deprecated;
|
||||
|
||||
(function() {
|
||||
|
||||
/**
|
||||
* Detect collision between two bodies using the Separating Axis Theorem.
|
||||
* @deprecated replaced by Collision.collides
|
||||
* @method collides
|
||||
* @param {body} bodyA
|
||||
* @param {body} bodyB
|
||||
* @param {collision} previousCollision
|
||||
* @return {collision} collision
|
||||
*/
|
||||
SAT.collides = function(bodyA, bodyB, previousCollision) {
|
||||
var overlapAB,
|
||||
overlapBA,
|
||||
minOverlap,
|
||||
collision,
|
||||
canReusePrevCol = false;
|
||||
|
||||
if (previousCollision) {
|
||||
// estimate total motion
|
||||
var parentA = bodyA.parent,
|
||||
parentB = bodyB.parent,
|
||||
motion = parentA.speed * parentA.speed + parentA.angularSpeed * parentA.angularSpeed
|
||||
+ parentB.speed * parentB.speed + parentB.angularSpeed * parentB.angularSpeed;
|
||||
|
||||
// we may be able to (partially) reuse collision result
|
||||
// but only safe if collision was resting
|
||||
canReusePrevCol = previousCollision && previousCollision.collided && motion < 0.2;
|
||||
|
||||
// reuse collision object
|
||||
collision = previousCollision;
|
||||
} else {
|
||||
collision = { collided: false, bodyA: bodyA, bodyB: bodyB };
|
||||
}
|
||||
|
||||
if (previousCollision && canReusePrevCol) {
|
||||
// if we can reuse the collision result
|
||||
// we only need to test the previously found axis
|
||||
var axisBodyA = collision.axisBody,
|
||||
axisBodyB = axisBodyA === bodyA ? bodyB : bodyA,
|
||||
axes = [axisBodyA.axes[previousCollision.axisNumber]];
|
||||
|
||||
minOverlap = SAT._overlapAxes(axisBodyA.vertices, axisBodyB.vertices, axes);
|
||||
collision.reused = true;
|
||||
|
||||
if (minOverlap.overlap <= 0) {
|
||||
collision.collided = false;
|
||||
return collision;
|
||||
}
|
||||
} else {
|
||||
// if we can't reuse a result, perform a full SAT test
|
||||
|
||||
overlapAB = SAT._overlapAxes(bodyA.vertices, bodyB.vertices, bodyA.axes);
|
||||
|
||||
if (overlapAB.overlap <= 0) {
|
||||
collision.collided = false;
|
||||
return collision;
|
||||
}
|
||||
|
||||
overlapBA = SAT._overlapAxes(bodyB.vertices, bodyA.vertices, bodyB.axes);
|
||||
|
||||
if (overlapBA.overlap <= 0) {
|
||||
collision.collided = false;
|
||||
return collision;
|
||||
}
|
||||
|
||||
if (overlapAB.overlap < overlapBA.overlap) {
|
||||
minOverlap = overlapAB;
|
||||
collision.axisBody = bodyA;
|
||||
} else {
|
||||
minOverlap = overlapBA;
|
||||
collision.axisBody = bodyB;
|
||||
}
|
||||
|
||||
// important for reuse later
|
||||
collision.axisNumber = minOverlap.axisNumber;
|
||||
}
|
||||
|
||||
collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB;
|
||||
collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA;
|
||||
collision.collided = true;
|
||||
collision.depth = minOverlap.overlap;
|
||||
collision.parentA = collision.bodyA.parent;
|
||||
collision.parentB = collision.bodyB.parent;
|
||||
|
||||
bodyA = collision.bodyA;
|
||||
bodyB = collision.bodyB;
|
||||
|
||||
// ensure normal is facing away from bodyA
|
||||
if (Vector.dot(minOverlap.axis, Vector.sub(bodyB.position, bodyA.position)) < 0) {
|
||||
collision.normal = {
|
||||
x: minOverlap.axis.x,
|
||||
y: minOverlap.axis.y
|
||||
};
|
||||
} else {
|
||||
collision.normal = {
|
||||
x: -minOverlap.axis.x,
|
||||
y: -minOverlap.axis.y
|
||||
};
|
||||
}
|
||||
|
||||
collision.tangent = Vector.perp(collision.normal);
|
||||
|
||||
collision.penetration = collision.penetration || {};
|
||||
collision.penetration.x = collision.normal.x * collision.depth;
|
||||
collision.penetration.y = collision.normal.y * collision.depth;
|
||||
|
||||
// find support points, there is always either exactly one or two
|
||||
var verticesB = SAT._findSupports(bodyA, bodyB, collision.normal),
|
||||
supports = [];
|
||||
|
||||
// find the supports from bodyB that are inside bodyA
|
||||
if (Vertices.contains(bodyA.vertices, verticesB[0]))
|
||||
supports.push(verticesB[0]);
|
||||
|
||||
if (Vertices.contains(bodyA.vertices, verticesB[1]))
|
||||
supports.push(verticesB[1]);
|
||||
|
||||
// find the supports from bodyA that are inside bodyB
|
||||
if (supports.length < 2) {
|
||||
var verticesA = SAT._findSupports(bodyB, bodyA, Vector.neg(collision.normal));
|
||||
|
||||
if (Vertices.contains(bodyB.vertices, verticesA[0]))
|
||||
supports.push(verticesA[0]);
|
||||
|
||||
if (supports.length < 2 && Vertices.contains(bodyB.vertices, verticesA[1]))
|
||||
supports.push(verticesA[1]);
|
||||
}
|
||||
|
||||
// account for the edge case of overlapping but no vertex containment
|
||||
if (supports.length < 1)
|
||||
supports = [verticesB[0]];
|
||||
|
||||
collision.supports = supports;
|
||||
|
||||
return collision;
|
||||
SAT.collides = function(bodyA, bodyB) {
|
||||
return Collision.collides(bodyA, bodyB);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the overlap between two sets of vertices.
|
||||
* @method _overlapAxes
|
||||
* @private
|
||||
* @param {} verticesA
|
||||
* @param {} verticesB
|
||||
* @param {} axes
|
||||
* @return result
|
||||
*/
|
||||
SAT._overlapAxes = function(verticesA, verticesB, axes) {
|
||||
var projectionA = Vector._temp[0],
|
||||
projectionB = Vector._temp[1],
|
||||
result = { overlap: Number.MAX_VALUE },
|
||||
overlap,
|
||||
axis;
|
||||
|
||||
for (var i = 0; i < axes.length; i++) {
|
||||
axis = axes[i];
|
||||
|
||||
SAT._projectToAxis(projectionA, verticesA, axis);
|
||||
SAT._projectToAxis(projectionB, verticesB, axis);
|
||||
|
||||
overlap = Math.min(projectionA.max - projectionB.min, projectionB.max - projectionA.min);
|
||||
|
||||
if (overlap <= 0) {
|
||||
result.overlap = overlap;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (overlap < result.overlap) {
|
||||
result.overlap = overlap;
|
||||
result.axis = axis;
|
||||
result.axisNumber = i;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Projects vertices on an axis and returns an interval.
|
||||
* @method _projectToAxis
|
||||
* @private
|
||||
* @param {} projection
|
||||
* @param {} vertices
|
||||
* @param {} axis
|
||||
*/
|
||||
SAT._projectToAxis = function(projection, vertices, axis) {
|
||||
var min = Vector.dot(vertices[0], axis),
|
||||
max = min;
|
||||
|
||||
for (var i = 1; i < vertices.length; i += 1) {
|
||||
var dot = Vector.dot(vertices[i], axis);
|
||||
|
||||
if (dot > max) {
|
||||
max = dot;
|
||||
} else if (dot < min) {
|
||||
min = dot;
|
||||
}
|
||||
}
|
||||
|
||||
projection.min = min;
|
||||
projection.max = max;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds supporting vertices given two bodies along a given direction using hill-climbing.
|
||||
* @method _findSupports
|
||||
* @private
|
||||
* @param {} bodyA
|
||||
* @param {} bodyB
|
||||
* @param {} normal
|
||||
* @return [vector]
|
||||
*/
|
||||
SAT._findSupports = function(bodyA, bodyB, normal) {
|
||||
var nearestDistance = Number.MAX_VALUE,
|
||||
vertexToBody = Vector._temp[0],
|
||||
vertices = bodyB.vertices,
|
||||
bodyAPosition = bodyA.position,
|
||||
distance,
|
||||
vertex,
|
||||
vertexA,
|
||||
vertexB;
|
||||
|
||||
// find closest vertex on bodyB
|
||||
for (var i = 0; i < vertices.length; i++) {
|
||||
vertex = vertices[i];
|
||||
vertexToBody.x = vertex.x - bodyAPosition.x;
|
||||
vertexToBody.y = vertex.y - bodyAPosition.y;
|
||||
distance = -Vector.dot(normal, vertexToBody);
|
||||
|
||||
if (distance < nearestDistance) {
|
||||
nearestDistance = distance;
|
||||
vertexA = vertex;
|
||||
}
|
||||
}
|
||||
|
||||
// find next closest vertex using the two connected to it
|
||||
var prevIndex = vertexA.index - 1 >= 0 ? vertexA.index - 1 : vertices.length - 1;
|
||||
vertex = vertices[prevIndex];
|
||||
vertexToBody.x = vertex.x - bodyAPosition.x;
|
||||
vertexToBody.y = vertex.y - bodyAPosition.y;
|
||||
nearestDistance = -Vector.dot(normal, vertexToBody);
|
||||
vertexB = vertex;
|
||||
|
||||
var nextIndex = (vertexA.index + 1) % vertices.length;
|
||||
vertex = vertices[nextIndex];
|
||||
vertexToBody.x = vertex.x - bodyAPosition.x;
|
||||
vertexToBody.y = vertex.y - bodyAPosition.y;
|
||||
distance = -Vector.dot(normal, vertexToBody);
|
||||
if (distance < nearestDistance) {
|
||||
vertexB = vertex;
|
||||
}
|
||||
|
||||
return [vertexA, vertexB];
|
||||
};
|
||||
deprecated(SAT, 'collides', 'SAT.collides ➤ replaced by Collision.collides');
|
||||
|
||||
})();
|
||||
|
|
|
@ -308,8 +308,10 @@ var Common = require('../core/Common');
|
|||
*/
|
||||
Constraint.pointAWorld = function(constraint) {
|
||||
return {
|
||||
x: (constraint.bodyA ? constraint.bodyA.position.x : 0) + constraint.pointA.x,
|
||||
y: (constraint.bodyA ? constraint.bodyA.position.y : 0) + constraint.pointA.y
|
||||
x: (constraint.bodyA ? constraint.bodyA.position.x : 0)
|
||||
+ (constraint.pointA ? constraint.pointA.x : 0),
|
||||
y: (constraint.bodyA ? constraint.bodyA.position.y : 0)
|
||||
+ (constraint.pointA ? constraint.pointA.y : 0)
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -321,8 +323,10 @@ var Common = require('../core/Common');
|
|||
*/
|
||||
Constraint.pointBWorld = function(constraint) {
|
||||
return {
|
||||
x: (constraint.bodyB ? constraint.bodyB.position.x : 0) + constraint.pointB.x,
|
||||
y: (constraint.bodyB ? constraint.bodyB.position.y : 0) + constraint.pointB.y
|
||||
x: (constraint.bodyB ? constraint.bodyB.position.x : 0)
|
||||
+ (constraint.pointB ? constraint.pointB.x : 0),
|
||||
y: (constraint.bodyB ? constraint.bodyB.position.y : 0)
|
||||
+ (constraint.pointB ? constraint.pointB.y : 0)
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ var Sleeping = require('./Sleeping');
|
|||
var Resolver = require('../collision/Resolver');
|
||||
var Detector = require('../collision/Detector');
|
||||
var Pairs = require('../collision/Pairs');
|
||||
var Grid = require('../collision/Grid');
|
||||
var Events = require('./Events');
|
||||
var Composite = require('../body/Composite');
|
||||
var Constraint = require('../constraint/Constraint');
|
||||
|
@ -43,7 +42,6 @@ var Body = require('../body/Body');
|
|||
enableSleeping: false,
|
||||
events: [],
|
||||
plugin: {},
|
||||
grid: null,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: 1,
|
||||
|
@ -60,10 +58,11 @@ var Body = require('../body/Body');
|
|||
var engine = Common.extend(defaults, options);
|
||||
|
||||
engine.world = options.world || Composite.create({ label: 'World' });
|
||||
engine.grid = Grid.create(options.grid || options.broadphase);
|
||||
engine.pairs = Pairs.create();
|
||||
engine.pairs = options.pairs || Pairs.create();
|
||||
engine.detector = options.detector || Detector.create();
|
||||
|
||||
// temporary back compatibility
|
||||
// for temporary back compatibility only
|
||||
engine.grid = { buckets: [] };
|
||||
engine.world.gravity = engine.gravity;
|
||||
engine.broadphase = engine.grid;
|
||||
engine.metrics = {};
|
||||
|
@ -93,9 +92,10 @@ var Body = require('../body/Body');
|
|||
correction = correction || 1;
|
||||
|
||||
var world = engine.world,
|
||||
detector = engine.detector,
|
||||
pairs = engine.pairs,
|
||||
timing = engine.timing,
|
||||
grid = engine.grid,
|
||||
gridPairs = [],
|
||||
timestamp = timing.timestamp,
|
||||
i;
|
||||
|
||||
// increment timestamp
|
||||
|
@ -109,15 +109,25 @@ var Body = require('../body/Body');
|
|||
|
||||
Events.trigger(engine, 'beforeUpdate', event);
|
||||
|
||||
// get lists of all bodies and constraints, no matter what composites they are in
|
||||
// get all bodies and all constraints in the world
|
||||
var allBodies = Composite.allBodies(world),
|
||||
allConstraints = Composite.allConstraints(world);
|
||||
|
||||
// if sleeping enabled, call the sleeping controller
|
||||
// update the detector bodies if they have changed
|
||||
if (world.isModified) {
|
||||
Detector.setBodies(detector, allBodies);
|
||||
}
|
||||
|
||||
// reset all composite modified flags
|
||||
if (world.isModified) {
|
||||
Composite.setModified(world, false, false, true);
|
||||
}
|
||||
|
||||
// update sleeping if enabled
|
||||
if (engine.enableSleeping)
|
||||
Sleeping.update(allBodies, timing.timeScale);
|
||||
|
||||
// applies gravity to all bodies
|
||||
// apply gravity to all bodies
|
||||
Engine._bodiesApplyGravity(allBodies, engine.gravity);
|
||||
|
||||
// update all body position and rotation by integration
|
||||
|
@ -130,29 +140,12 @@ var Body = require('../body/Body');
|
|||
}
|
||||
Constraint.postSolveAll(allBodies);
|
||||
|
||||
// broadphase pass: find potential collision pairs
|
||||
|
||||
// if world is dirty, we must flush the whole grid
|
||||
if (world.isModified)
|
||||
Grid.clear(grid);
|
||||
|
||||
// update the grid buckets based on current bodies
|
||||
Grid.update(grid, allBodies, engine, world.isModified);
|
||||
gridPairs = grid.pairsList;
|
||||
|
||||
// clear all composite modified flags
|
||||
if (world.isModified) {
|
||||
Composite.setModified(world, false, false, true);
|
||||
}
|
||||
|
||||
// narrowphase pass: find actual collisions, then create or update collision pairs
|
||||
var collisions = Detector.collisions(gridPairs, engine);
|
||||
// find all collisions
|
||||
detector.pairs = engine.pairs;
|
||||
var collisions = Detector.collisions(detector);
|
||||
|
||||
// update collision pairs
|
||||
var pairs = engine.pairs,
|
||||
timestamp = timing.timestamp;
|
||||
Pairs.update(pairs, collisions, timestamp);
|
||||
Pairs.removeOld(pairs, timestamp);
|
||||
|
||||
// wake up bodies involved in collisions
|
||||
if (engine.enableSleeping)
|
||||
|
@ -225,17 +218,13 @@ var Body = require('../body/Body');
|
|||
};
|
||||
|
||||
/**
|
||||
* Clears the engine including the world, pairs and broadphase.
|
||||
* Clears the engine pairs and detector.
|
||||
* @method clear
|
||||
* @param {engine} engine
|
||||
*/
|
||||
Engine.clear = function(engine) {
|
||||
var world = engine.world,
|
||||
bodies = Composite.allBodies(world);
|
||||
|
||||
Pairs.clear(engine.pairs);
|
||||
Grid.clear(engine.grid);
|
||||
Grid.update(engine.grid, bodies, engine, true);
|
||||
Detector.clear(engine.detector);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -315,53 +304,53 @@ var Body = require('../body/Body');
|
|||
* Fired just before an update
|
||||
*
|
||||
* @event beforeUpdate
|
||||
* @param {} event An event object
|
||||
* @param {object} event An event object
|
||||
* @param {number} event.timestamp The engine.timing.timestamp of the event
|
||||
* @param {} event.source The source object of the event
|
||||
* @param {} event.name The name of the event
|
||||
* @param {engine} event.source The source object of the event
|
||||
* @param {string} event.name The name of the event
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired after engine update and all collision events
|
||||
*
|
||||
* @event afterUpdate
|
||||
* @param {} event An event object
|
||||
* @param {object} event An event object
|
||||
* @param {number} event.timestamp The engine.timing.timestamp of the event
|
||||
* @param {} event.source The source object of the event
|
||||
* @param {} event.name The name of the event
|
||||
* @param {engine} event.source The source object of the event
|
||||
* @param {string} event.name The name of the event
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired after engine update, provides a list of all pairs that have started to collide in the current tick (if any)
|
||||
*
|
||||
* @event collisionStart
|
||||
* @param {} event An event object
|
||||
* @param {} event.pairs List of affected pairs
|
||||
* @param {object} event An event object
|
||||
* @param {pair[]} event.pairs List of affected pairs
|
||||
* @param {number} event.timestamp The engine.timing.timestamp of the event
|
||||
* @param {} event.source The source object of the event
|
||||
* @param {} event.name The name of the event
|
||||
* @param {engine} event.source The source object of the event
|
||||
* @param {string} event.name The name of the event
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired after engine update, provides a list of all pairs that are colliding in the current tick (if any)
|
||||
*
|
||||
* @event collisionActive
|
||||
* @param {} event An event object
|
||||
* @param {} event.pairs List of affected pairs
|
||||
* @param {object} event An event object
|
||||
* @param {pair[]} event.pairs List of affected pairs
|
||||
* @param {number} event.timestamp The engine.timing.timestamp of the event
|
||||
* @param {} event.source The source object of the event
|
||||
* @param {} event.name The name of the event
|
||||
* @param {engine} event.source The source object of the event
|
||||
* @param {string} event.name The name of the event
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired after engine update, provides a list of all pairs that have ended collision in the current tick (if any)
|
||||
*
|
||||
* @event collisionEnd
|
||||
* @param {} event An event object
|
||||
* @param {} event.pairs List of affected pairs
|
||||
* @param {object} event An event object
|
||||
* @param {pair[]} event.pairs List of affected pairs
|
||||
* @param {number} event.timestamp The engine.timing.timestamp of the event
|
||||
* @param {} event.source The source object of the event
|
||||
* @param {} event.name The name of the event
|
||||
* @param {engine} event.source The source object of the event
|
||||
* @param {string} event.name The name of the event
|
||||
*/
|
||||
|
||||
/*
|
||||
|
@ -453,9 +442,18 @@ var Body = require('../body/Body');
|
|||
* @default 0
|
||||
*/
|
||||
|
||||
/**
|
||||
* A `Matter.Detector` instance.
|
||||
*
|
||||
* @property detector
|
||||
* @type detector
|
||||
* @default a Matter.Detector instance
|
||||
*/
|
||||
|
||||
/**
|
||||
* A `Matter.Grid` instance.
|
||||
*
|
||||
* @deprecated replaced by `engine.detector`
|
||||
* @property grid
|
||||
* @type grid
|
||||
* @default a Matter.Grid instance
|
||||
|
@ -464,7 +462,7 @@ var Body = require('../body/Body');
|
|||
/**
|
||||
* Replaced by and now alias for `engine.grid`.
|
||||
*
|
||||
* @deprecated use `engine.grid`
|
||||
* @deprecated replaced by `engine.detector`
|
||||
* @property broadphase
|
||||
* @type grid
|
||||
* @default a Matter.Grid instance
|
||||
|
|
|
@ -240,7 +240,7 @@ var Common = require('./Common');
|
|||
*/
|
||||
Plugin.dependencyParse = function(dependency) {
|
||||
if (Common.isString(dependency)) {
|
||||
var pattern = /^[\w-]+(@(\*|[\^~]?\d+\.\d+\.\d+(-[0-9A-Za-z-]+)?))?$/;
|
||||
var pattern = /^[\w-]+(@(\*|[\^~]?\d+\.\d+\.\d+(-[0-9A-Za-z-+]+)?))?$/;
|
||||
|
||||
if (!pattern.test(dependency)) {
|
||||
Common.warn('Plugin.dependencyParse:', dependency, 'is not a valid dependency string.');
|
||||
|
@ -275,7 +275,7 @@ var Common = require('./Common');
|
|||
* @return {object} The version range parsed into its components.
|
||||
*/
|
||||
Plugin.versionParse = function(range) {
|
||||
var pattern = /^(\*)|(\^|~|>=|>)?\s*((\d+)\.(\d+)\.(\d+))(-[0-9A-Za-z-]+)?$/;
|
||||
var pattern = /^(\*)|(\^|~|>=|>)?\s*((\d+)\.(\d+)\.(\d+))(-[0-9A-Za-z-+]+)?$/;
|
||||
|
||||
if (!pattern.test(range)) {
|
||||
Common.warn('Plugin.versionParse:', range, 'is not a valid version or range.');
|
||||
|
|
|
@ -169,17 +169,16 @@ var Common = require('../core/Common');
|
|||
* @param {number} scalar
|
||||
*/
|
||||
Vertices.translate = function(vertices, vector, scalar) {
|
||||
var i;
|
||||
if (scalar) {
|
||||
for (i = 0; i < vertices.length; i++) {
|
||||
vertices[i].x += vector.x * scalar;
|
||||
vertices[i].y += vector.y * scalar;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < vertices.length; i++) {
|
||||
vertices[i].x += vector.x;
|
||||
vertices[i].y += vector.y;
|
||||
}
|
||||
scalar = typeof scalar !== 'undefined' ? scalar : 1;
|
||||
|
||||
var verticesLength = vertices.length,
|
||||
translateX = vector.x * scalar,
|
||||
translateY = vector.y * scalar,
|
||||
i;
|
||||
|
||||
for (i = 0; i < verticesLength; i++) {
|
||||
vertices[i].x += translateX;
|
||||
vertices[i].y += translateY;
|
||||
}
|
||||
|
||||
return vertices;
|
||||
|
@ -197,15 +196,21 @@ var Common = require('../core/Common');
|
|||
return;
|
||||
|
||||
var cos = Math.cos(angle),
|
||||
sin = Math.sin(angle);
|
||||
sin = Math.sin(angle),
|
||||
pointX = point.x,
|
||||
pointY = point.y,
|
||||
verticesLength = vertices.length,
|
||||
vertex,
|
||||
dx,
|
||||
dy,
|
||||
i;
|
||||
|
||||
for (var i = 0; i < vertices.length; i++) {
|
||||
var vertice = vertices[i],
|
||||
dx = vertice.x - point.x,
|
||||
dy = vertice.y - point.y;
|
||||
|
||||
vertice.x = point.x + (dx * cos - dy * sin);
|
||||
vertice.y = point.y + (dx * sin + dy * cos);
|
||||
for (i = 0; i < verticesLength; i++) {
|
||||
vertex = vertices[i];
|
||||
dx = vertex.x - pointX;
|
||||
dy = vertex.y - pointY;
|
||||
vertex.x = pointX + (dx * cos - dy * sin);
|
||||
vertex.y = pointY + (dx * sin + dy * cos);
|
||||
}
|
||||
|
||||
return vertices;
|
||||
|
@ -219,12 +224,21 @@ var Common = require('../core/Common');
|
|||
* @return {boolean} True if the vertices contains point, otherwise false
|
||||
*/
|
||||
Vertices.contains = function(vertices, point) {
|
||||
for (var i = 0; i < vertices.length; i++) {
|
||||
var vertice = vertices[i],
|
||||
nextVertice = vertices[(i + 1) % vertices.length];
|
||||
if ((point.x - vertice.x) * (nextVertice.y - vertice.y) + (point.y - vertice.y) * (vertice.x - nextVertice.x) > 0) {
|
||||
var pointX = point.x,
|
||||
pointY = point.y,
|
||||
verticesLength = vertices.length,
|
||||
vertex = vertices[verticesLength - 1],
|
||||
nextVertex;
|
||||
|
||||
for (var i = 0; i < verticesLength; i++) {
|
||||
nextVertex = vertices[i];
|
||||
|
||||
if ((pointX - vertex.x) * (nextVertex.y - vertex.y)
|
||||
+ (pointY - vertex.y) * (vertex.x - nextVertex.x) > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vertex = nextVertex;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -4,6 +4,7 @@ Matter.Axes = require('../geometry/Axes');
|
|||
Matter.Bodies = require('../factory/Bodies');
|
||||
Matter.Body = require('../body/Body');
|
||||
Matter.Bounds = require('../geometry/Bounds');
|
||||
Matter.Collision = require('../collision/Collision');
|
||||
Matter.Common = require('../core/Common');
|
||||
Matter.Composite = require('../body/Composite');
|
||||
Matter.Composites = require('../factory/Composites');
|
||||
|
|
|
@ -44,7 +44,6 @@ var Mouse = require('../core/Mouse');
|
|||
*/
|
||||
Render.create = function(options) {
|
||||
var defaults = {
|
||||
controller: Render,
|
||||
engine: null,
|
||||
element: null,
|
||||
canvas: null,
|
||||
|
@ -76,7 +75,6 @@ var Mouse = require('../core/Mouse');
|
|||
showDebug: false,
|
||||
showStats: false,
|
||||
showPerformance: false,
|
||||
showBroadphase: false,
|
||||
showBounds: false,
|
||||
showVelocity: false,
|
||||
showCollisions: false,
|
||||
|
@ -116,14 +114,16 @@ var Mouse = require('../core/Mouse');
|
|||
}
|
||||
};
|
||||
|
||||
// for temporary back compatibility only
|
||||
render.controller = Render;
|
||||
render.options.showBroadphase = false;
|
||||
|
||||
if (render.options.pixelRatio !== 1) {
|
||||
Render.setPixelRatio(render, render.options.pixelRatio);
|
||||
}
|
||||
|
||||
if (Common.isElement(render.element)) {
|
||||
render.element.appendChild(render.canvas);
|
||||
} else if (!render.canvas.parentNode) {
|
||||
Common.log('Render.create: options.element was undefined, render.canvas was created but not appended', 'warn');
|
||||
}
|
||||
|
||||
return render;
|
||||
|
@ -438,9 +438,6 @@ var Mouse = require('../core/Mouse');
|
|||
|
||||
Render.constraints(constraints, context);
|
||||
|
||||
if (options.showBroadphase)
|
||||
Render.grid(render, engine.grid, context);
|
||||
|
||||
if (options.hasBounds) {
|
||||
// revert view transforms
|
||||
Render.endViewTransform(render);
|
||||
|
@ -1287,45 +1284,6 @@ var Mouse = require('../core/Mouse');
|
|||
c.stroke();
|
||||
};
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @private
|
||||
* @method grid
|
||||
* @param {render} render
|
||||
* @param {grid} grid
|
||||
* @param {RenderingContext} context
|
||||
*/
|
||||
Render.grid = function(render, grid, context) {
|
||||
var c = context,
|
||||
options = render.options;
|
||||
|
||||
if (options.wireframes) {
|
||||
c.strokeStyle = 'rgba(255,180,0,0.1)';
|
||||
} else {
|
||||
c.strokeStyle = 'rgba(255,180,0,0.5)';
|
||||
}
|
||||
|
||||
c.beginPath();
|
||||
|
||||
var bucketKeys = Common.keys(grid.buckets);
|
||||
|
||||
for (var i = 0; i < bucketKeys.length; i++) {
|
||||
var bucketId = bucketKeys[i];
|
||||
|
||||
if (grid.buckets[bucketId].length < 2)
|
||||
continue;
|
||||
|
||||
var region = bucketId.split(/C|R/);
|
||||
c.rect(0.5 + parseInt(region[1], 10) * grid.bucketWidth,
|
||||
0.5 + parseInt(region[2], 10) * grid.bucketHeight,
|
||||
grid.bucketWidth,
|
||||
grid.bucketHeight);
|
||||
}
|
||||
|
||||
c.lineWidth = 1;
|
||||
c.stroke();
|
||||
};
|
||||
|
||||
/**
|
||||
* Description
|
||||
* @private
|
||||
|
@ -1577,6 +1535,7 @@ var Mouse = require('../core/Mouse');
|
|||
/**
|
||||
* A back-reference to the `Matter.Render` module.
|
||||
*
|
||||
* @deprecated
|
||||
* @property controller
|
||||
* @type render
|
||||
*/
|
||||
|
@ -1777,6 +1736,7 @@ var Mouse = require('../core/Mouse');
|
|||
/**
|
||||
* A flag to enable or disable the collision broadphase debug overlay.
|
||||
*
|
||||
* @deprecated no longer implemented
|
||||
* @property options.showBroadphase
|
||||
* @type boolean
|
||||
* @default false
|
||||
|
|
|
@ -2,76 +2,56 @@
|
|||
/* eslint no-global-assign: 0 */
|
||||
"use strict";
|
||||
|
||||
const stubBrowserFeatures = M => {
|
||||
const noop = () => ({ collisionFilter: {}, mouse: {} });
|
||||
M.Render.create = () => ({ options: {}, bounds: { min: { x: 0, y: 0 }, max: { x: 800, y: 600 }}});
|
||||
M.Render.run = M.Render.lookAt = noop;
|
||||
M.Runner.create = M.Runner.run = noop;
|
||||
M.MouseConstraint.create = M.Mouse.create = noop;
|
||||
M.Common.info = M.Common.warn = M.Common.log;
|
||||
return M;
|
||||
};
|
||||
|
||||
const reset = M => {
|
||||
M.Common._nextId = M.Common._seed = 0;
|
||||
M.Body._nextCollidingGroupId = 1;
|
||||
M.Body._nextNonCollidingGroupId = -1;
|
||||
M.Body._nextCategory = 0x0001;
|
||||
};
|
||||
|
||||
const mock = require('mock-require');
|
||||
const { engineCapture } = require('./TestTools');
|
||||
const MatterDev = stubBrowserFeatures(require('../src/module/main'));
|
||||
const MatterBuild = stubBrowserFeatures(require('../build/matter'));
|
||||
const Example = require('../examples/index');
|
||||
const { requireUncached, serialize } = require('./TestTools');
|
||||
const consoleOriginal = global.console;
|
||||
|
||||
const runExample = options => {
|
||||
const Matter = options.useDev ? MatterDev : MatterBuild;
|
||||
const consoleOriginal = global.console;
|
||||
const logs = [];
|
||||
const {
|
||||
Matter,
|
||||
logs,
|
||||
frameCallbacks
|
||||
} = prepareEnvironment(options);
|
||||
|
||||
mock('matter-js', Matter);
|
||||
global.Matter = Matter;
|
||||
const Examples = requireUncached('../examples/index');
|
||||
const example = Examples[options.name]();
|
||||
|
||||
global.document = global.window = { addEventListener: () => {} };
|
||||
global.console = {
|
||||
log: (...args) => {
|
||||
logs.push(args.join(' '));
|
||||
}
|
||||
};
|
||||
|
||||
reset(Matter);
|
||||
|
||||
const example = Example[options.name]();
|
||||
const engine = example.engine;
|
||||
const runner = example.runner;
|
||||
const render = example.render;
|
||||
|
||||
let totalMemory = 0;
|
||||
let totalDuration = 0;
|
||||
let overlapTotal = 0;
|
||||
let overlapCount = 0;
|
||||
let i;
|
||||
|
||||
const bodies = Matter.Composite.allBodies(engine.world);
|
||||
|
||||
if (options.jitter) {
|
||||
for (let i = 0; i < bodies.length; i += 1) {
|
||||
const body = bodies[i];
|
||||
|
||||
Matter.Body.applyForce(body, body.position, {
|
||||
x: Math.cos(i * i) * options.jitter * body.mass,
|
||||
y: Math.sin(i * i) * options.jitter * body.mass
|
||||
});
|
||||
}
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
|
||||
for (let i = 0; i < options.totalUpdates; i += 1) {
|
||||
try {
|
||||
for (i = 0; i < options.updates; i += 1) {
|
||||
const time = i * runner.delta;
|
||||
const callbackCount = frameCallbacks.length;
|
||||
|
||||
for (let p = 0; p < callbackCount; p += 1) {
|
||||
totalMemory += process.memoryUsage().heapUsed;
|
||||
const callback = frameCallbacks.shift();
|
||||
const startTime = process.hrtime();
|
||||
|
||||
Matter.Engine.update(engine, 1000 / 60);
|
||||
callback(time);
|
||||
|
||||
const duration = process.hrtime(startTime);
|
||||
totalMemory += process.memoryUsage().heapUsed;
|
||||
totalDuration += duration[0] * 1e9 + duration[1];
|
||||
}
|
||||
|
||||
for (let p = 0; p < engine.pairs.list.length; p += 1) {
|
||||
const pair = engine.pairs.list[p];
|
||||
const pairsList = engine.pairs.list;
|
||||
const pairsListLength = engine.pairs.list.length;
|
||||
|
||||
for (let p = 0; p < pairsListLength; p += 1) {
|
||||
const pair = pairsList[p];
|
||||
const separation = pair.separation - pair.slop;
|
||||
|
||||
if (pair.isActive && !pair.isSensor) {
|
||||
|
@ -81,19 +61,251 @@ const runExample = options => {
|
|||
}
|
||||
}
|
||||
|
||||
global.console = consoleOriginal;
|
||||
global.window = undefined;
|
||||
global.document = undefined;
|
||||
global.Matter = undefined;
|
||||
mock.stopAll();
|
||||
resetEnvironment();
|
||||
|
||||
return {
|
||||
name: options.name,
|
||||
duration: totalDuration,
|
||||
overlap: overlapTotal / (overlapCount || 1),
|
||||
memory: totalMemory,
|
||||
logs: logs,
|
||||
extrinsic: captureExtrinsics(engine, Matter),
|
||||
intrinsic: captureIntrinsics(engine, Matter),
|
||||
state: captureState(engine, runner, render)
|
||||
};
|
||||
|
||||
} catch (err) {
|
||||
err.message = `On example '${options.name}' update ${i}:\n\n ${err.message}`;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const prepareMatter = (options) => {
|
||||
const Matter = requireUncached(options.useDev ? '../build/matter.dev' : '../build/matter');
|
||||
|
||||
if (Matter.Common._nextId !== 0) {
|
||||
throw 'Matter instance has already been used.';
|
||||
}
|
||||
|
||||
Matter.Common.info = Matter.Common.warn = Matter.Common.log;
|
||||
|
||||
if (options.stableSort) {
|
||||
if (Matter.Collision) {
|
||||
const MatterCollisionCollides = Matter.Collision.collides;
|
||||
Matter.Collision.collides = function(bodyA, bodyB, pairs) {
|
||||
const _bodyA = bodyA.id < bodyB.id ? bodyA : bodyB;
|
||||
const _bodyB = bodyA.id < bodyB.id ? bodyB : bodyA;
|
||||
return MatterCollisionCollides(_bodyA, _bodyB, pairs);
|
||||
};
|
||||
} else {
|
||||
const MatterSATCollides = Matter.SAT.collides;
|
||||
Matter.SAT.collides = function(bodyA, bodyB, previousCollision, pairActive) {
|
||||
const _bodyA = bodyA.id < bodyB.id ? bodyA : bodyB;
|
||||
const _bodyB = bodyA.id < bodyB.id ? bodyB : bodyA;
|
||||
return MatterSATCollides(_bodyA, _bodyB, previousCollision, pairActive);
|
||||
};
|
||||
}
|
||||
|
||||
Matter.after('Detector.collisions', function() { this.sort(collisionCompareId); });
|
||||
Matter.after('Composite.allBodies', function() { sortById(this); });
|
||||
Matter.after('Composite.allConstraints', function() { sortById(this); });
|
||||
Matter.after('Composite.allComposites', function() { sortById(this); });
|
||||
|
||||
Matter.before('Pairs.update', function(pairs) {
|
||||
pairs.list.sort((pairA, pairB) => collisionCompareId(pairA.collision, pairB.collision));
|
||||
});
|
||||
|
||||
Matter.after('Pairs.update', function(pairs) {
|
||||
pairs.list.sort((pairA, pairB) => collisionCompareId(pairA.collision, pairB.collision));
|
||||
});
|
||||
}
|
||||
|
||||
if (options.jitter) {
|
||||
Matter.after('Body.create', function() {
|
||||
Matter.Body.applyForce(this, this.position, {
|
||||
x: Math.cos(this.id * this.id) * options.jitter * this.mass,
|
||||
y: Math.sin(this.id * this.id) * options.jitter * this.mass
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Matter;
|
||||
};
|
||||
|
||||
const prepareEnvironment = options => {
|
||||
const logs = [];
|
||||
const frameCallbacks = [];
|
||||
|
||||
global.document = global.window = {
|
||||
addEventListener: () => {},
|
||||
requestAnimationFrame: callback => {
|
||||
frameCallbacks.push(callback);
|
||||
return frameCallbacks.length;
|
||||
},
|
||||
createElement: () => ({
|
||||
parentNode: {},
|
||||
width: 800,
|
||||
height: 600,
|
||||
style: {},
|
||||
addEventListener: () => {},
|
||||
getAttribute: name => ({
|
||||
'data-pixel-ratio': '1'
|
||||
}[name]),
|
||||
getContext: () => new Proxy({}, {
|
||||
get() { return () => {}; }
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
global.document.body = global.document.createElement();
|
||||
|
||||
global.Image = function Image() { };
|
||||
|
||||
global.console = {
|
||||
log: (...args) => {
|
||||
logs.push(args.join(' '));
|
||||
}
|
||||
};
|
||||
|
||||
const Matter = prepareMatter(options);
|
||||
mock('matter-js', Matter);
|
||||
global.Matter = Matter;
|
||||
|
||||
return {
|
||||
Matter,
|
||||
logs,
|
||||
...engineCapture(engine)
|
||||
frameCallbacks
|
||||
};
|
||||
};
|
||||
|
||||
const resetEnvironment = () => {
|
||||
global.console = consoleOriginal;
|
||||
global.window = undefined;
|
||||
global.document = undefined;
|
||||
global.Matter = undefined;
|
||||
mock.stopAll();
|
||||
};
|
||||
|
||||
const captureExtrinsics = ({ world }, Matter) => ({
|
||||
bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => {
|
||||
bodies[body.id] = [
|
||||
body.position.x,
|
||||
body.position.y,
|
||||
body.positionPrev.x,
|
||||
body.positionPrev.y,
|
||||
body.angle,
|
||||
body.anglePrev,
|
||||
...body.vertices.reduce((flat, vertex) => (flat.push(vertex.x, vertex.y), flat), [])
|
||||
];
|
||||
|
||||
return bodies;
|
||||
}, {}),
|
||||
constraints: Matter.Composite.allConstraints(world).reduce((constraints, constraint) => {
|
||||
let positionA;
|
||||
let positionB;
|
||||
|
||||
try {
|
||||
positionA = Matter.Constraint.pointAWorld(constraint);
|
||||
} catch (err) {
|
||||
positionA = { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
try {
|
||||
positionB = Matter.Constraint.pointBWorld(constraint);
|
||||
} catch (err) {
|
||||
positionB = { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
constraints[constraint.id] = [
|
||||
positionA.x,
|
||||
positionA.y,
|
||||
positionB.x,
|
||||
positionB.y
|
||||
];
|
||||
|
||||
return constraints;
|
||||
}, {})
|
||||
});
|
||||
|
||||
const captureIntrinsics = ({ world }, Matter) => serialize({
|
||||
bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => {
|
||||
bodies[body.id] = body;
|
||||
return bodies;
|
||||
}, {}),
|
||||
constraints: Matter.Composite.allConstraints(world).reduce((constraints, constraint) => {
|
||||
constraints[constraint.id] = constraint;
|
||||
return constraints;
|
||||
}, {}),
|
||||
composites: Matter.Composite.allComposites(world).reduce((composites, composite) => {
|
||||
composites[composite.id] = {
|
||||
bodies: Matter.Composite.allBodies(composite).map(body => body.id),
|
||||
constraints: Matter.Composite.allConstraints(composite).map(constraint => constraint.id),
|
||||
composites: Matter.Composite.allComposites(composite).map(composite => composite.id)
|
||||
};
|
||||
return composites;
|
||||
}, {})
|
||||
}, (key) => !Number.isInteger(parseInt(key)) && !intrinsicProperties.includes(key));
|
||||
|
||||
const captureState = (engine, runner, render, excludeKeys=excludeStateProperties) => (
|
||||
serialize({ engine, runner, render }, (key) => excludeKeys.includes(key))
|
||||
);
|
||||
|
||||
const intrinsicProperties = [
|
||||
// Composite
|
||||
'bodies', 'constraints', 'composites',
|
||||
|
||||
// Common
|
||||
'id', 'label',
|
||||
|
||||
// Constraint
|
||||
'angularStiffness', 'bodyA', 'bodyB', 'damping', 'length', 'stiffness',
|
||||
|
||||
// Body
|
||||
'area', 'collisionFilter', 'category', 'mask', 'group', 'density', 'friction',
|
||||
'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass',
|
||||
'isSensor', 'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution',
|
||||
'sleepThreshold', 'slop', 'timeScale',
|
||||
|
||||
// Composite
|
||||
'bodies', 'constraints', 'composites'
|
||||
];
|
||||
|
||||
const extrinsicProperties = [
|
||||
'axes',
|
||||
'vertices',
|
||||
'bounds',
|
||||
'angle',
|
||||
'anglePrev',
|
||||
'angularVelocity',
|
||||
'angularSpeed',
|
||||
'speed',
|
||||
'velocity',
|
||||
'position',
|
||||
'positionPrev',
|
||||
];
|
||||
|
||||
const excludeStateProperties = [
|
||||
'cache',
|
||||
'grid',
|
||||
'context',
|
||||
'broadphase',
|
||||
'metrics',
|
||||
'controller',
|
||||
'detector',
|
||||
'pairs',
|
||||
'lastElapsed',
|
||||
'deltaHistory',
|
||||
'elapsedHistory',
|
||||
'engineDeltaHistory',
|
||||
'engineElapsedHistory',
|
||||
'timestampElapsedHistory',
|
||||
].concat(extrinsicProperties);
|
||||
|
||||
const collisionId = (collision) =>
|
||||
Math.min(collision.bodyA.id, collision.bodyB.id) + Math.max(collision.bodyA.id, collision.bodyB.id) * 10000;
|
||||
|
||||
const collisionCompareId = (collisionA, collisionB) => collisionId(collisionA) - collisionId(collisionB);
|
||||
|
||||
const sortById = (objs) => objs.sort((objA, objB) => objA.id - objB.id);
|
||||
|
||||
module.exports = { runExample };
|
|
@ -1,26 +1,31 @@
|
|||
/* eslint-env es6 */
|
||||
"use strict";
|
||||
|
||||
jest.setTimeout(30 * 1000);
|
||||
jest.setTimeout(2 * 60 * 1000);
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const {
|
||||
requireUncached,
|
||||
comparisonReport,
|
||||
logReport,
|
||||
toMatchExtrinsics,
|
||||
toMatchIntrinsics
|
||||
} = require('./TestTools');
|
||||
|
||||
const Example = require('../examples/index');
|
||||
const MatterBuild = require('../build/matter');
|
||||
const { versionSatisfies } = require('../src/core/Plugin');
|
||||
const Example = requireUncached('../examples/index');
|
||||
const MatterBuild = requireUncached('../build/matter');
|
||||
const { versionSatisfies } = requireUncached('../src/core/Plugin');
|
||||
const Worker = require('jest-worker').default;
|
||||
|
||||
const specificExamples = process.env.EXAMPLES ? process.env.EXAMPLES.split(' ') : null;
|
||||
const testComparison = process.env.COMPARE === 'true';
|
||||
const saveComparison = process.env.SAVE === 'true';
|
||||
|
||||
const excludeExamples = ['svg', 'terrain'];
|
||||
const excludeJitter = ['stack', 'circleStack', 'restitution', 'staticFriction', 'friction', 'newtonsCradle', 'catapult'];
|
||||
|
||||
const examples = Object.keys(Example).filter(key => {
|
||||
const examples = (specificExamples || Object.keys(Example)).filter(key => {
|
||||
const excluded = excludeExamples.includes(key);
|
||||
const buildVersion = MatterBuild.version;
|
||||
const exampleFor = Example[key].for;
|
||||
|
@ -28,36 +33,86 @@ const examples = Object.keys(Example).filter(key => {
|
|||
return !excluded && supported;
|
||||
});
|
||||
|
||||
const runExamples = async useDev => {
|
||||
const worker = new Worker(require.resolve('./ExampleWorker'), {
|
||||
const captureExamples = async useDev => {
|
||||
const multiThreadWorker = new Worker(require.resolve('./ExampleWorker'), {
|
||||
enableWorkerThreads: true
|
||||
});
|
||||
|
||||
const result = await Promise.all(examples.map(name => worker.runExample({
|
||||
const overlapRuns = await Promise.all(examples.map(name => multiThreadWorker.runExample({
|
||||
name,
|
||||
useDev,
|
||||
totalUpdates: 120,
|
||||
updates: 1,
|
||||
stableSort: true,
|
||||
jitter: excludeJitter.includes(name) ? 0 : 1e-10
|
||||
})));
|
||||
|
||||
await worker.end();
|
||||
const behaviourRuns = await Promise.all(examples.map(name => multiThreadWorker.runExample({
|
||||
name,
|
||||
useDev,
|
||||
updates: 2,
|
||||
stableSort: true,
|
||||
jitter: excludeJitter.includes(name) ? 0 : 1e-10
|
||||
})));
|
||||
|
||||
return result.reduce((out, capture) => (out[capture.name] = capture, out), {});
|
||||
const similarityRuns = await Promise.all(examples.map(name => multiThreadWorker.runExample({
|
||||
name,
|
||||
useDev,
|
||||
updates: 2,
|
||||
stableSort: false,
|
||||
jitter: excludeJitter.includes(name) ? 0 : 1e-10
|
||||
})));
|
||||
|
||||
await multiThreadWorker.end();
|
||||
|
||||
const singleThreadWorker = new Worker(require.resolve('./ExampleWorker'), {
|
||||
enableWorkerThreads: true,
|
||||
numWorkers: 1
|
||||
});
|
||||
|
||||
const completeRuns = await Promise.all(examples.map(name => singleThreadWorker.runExample({
|
||||
name,
|
||||
useDev,
|
||||
updates: 150,
|
||||
stableSort: false,
|
||||
jitter: excludeJitter.includes(name) ? 0 : 1e-10
|
||||
})));
|
||||
|
||||
await singleThreadWorker.end();
|
||||
|
||||
const capture = {};
|
||||
|
||||
for (const completeRun of completeRuns) {
|
||||
const behaviourRun = behaviourRuns.find(({ name }) => name === completeRun.name);
|
||||
const similarityRun = similarityRuns.find(({ name }) => name === completeRun.name);
|
||||
const overlapRun = overlapRuns.find(({ name }) => name === completeRun.name);
|
||||
|
||||
capture[overlapRun.name] = {
|
||||
...completeRun,
|
||||
behaviourExtrinsic: behaviourRun.extrinsic,
|
||||
similarityExtrinsic: similarityRun.extrinsic,
|
||||
overlap: overlapRun.overlap
|
||||
};
|
||||
}
|
||||
|
||||
return capture;
|
||||
};
|
||||
|
||||
const capturesDev = runExamples(true);
|
||||
const capturesBuild = runExamples(false);
|
||||
const capturesDev = captureExamples(true);
|
||||
const capturesBuild = captureExamples(false);
|
||||
|
||||
afterAll(async () => {
|
||||
// Report experimental capture comparison.
|
||||
const dev = await capturesDev;
|
||||
const build = await capturesBuild;
|
||||
|
||||
const buildSize = fs.statSync('./build/matter.min.js').size;
|
||||
const devSize = fs.statSync('./build/matter.dev.min.js').size;
|
||||
|
||||
console.log(
|
||||
'Examples ran against previous release and current version\n\n'
|
||||
'Examples ran against previous release and current build\n\n'
|
||||
+ logReport(build, `release`) + '\n'
|
||||
+ logReport(dev, `current`) + '\n'
|
||||
+ comparisonReport(dev, build, MatterBuild.version, saveComparison)
|
||||
+ comparisonReport(dev, build, devSize, buildSize, MatterBuild.version, saveComparison)
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -3,118 +3,99 @@
|
|||
|
||||
const fs = require('fs');
|
||||
const compactStringify = require('json-stringify-pretty-compact');
|
||||
const { Composite, Constraint } = require('../src/module/main');
|
||||
|
||||
const comparePath = './test/__compare__';
|
||||
const compareCommand = 'open http://localhost:8000/?compare';
|
||||
const diffSaveCommand = 'npm run test-save';
|
||||
const diffCommand = 'code -n -d test/__compare__/examples-build.json test/__compare__/examples-dev.json';
|
||||
const equalityThreshold = 0.99999;
|
||||
|
||||
const intrinsicProps = [
|
||||
// Common
|
||||
'id', 'label',
|
||||
|
||||
// Constraint
|
||||
'angularStiffness', 'bodyA', 'bodyB', 'damping', 'length', 'stiffness',
|
||||
|
||||
// Body
|
||||
'area', 'axes', 'collisionFilter', 'category', 'mask',
|
||||
'group', 'density', 'friction', 'frictionAir', 'frictionStatic', 'inertia', 'inverseInertia', 'inverseMass', 'isSensor',
|
||||
'isSleeping', 'isStatic', 'mass', 'parent', 'parts', 'restitution', 'sleepThreshold', 'slop',
|
||||
'timeScale', 'vertices',
|
||||
|
||||
// Composite
|
||||
'bodies', 'constraints', 'composites'
|
||||
];
|
||||
|
||||
const colors = { Red: 31, Green: 32, Yellow: 33, White: 37, BrightWhite: 90, BrightCyan: 36 };
|
||||
const color = (text, number) => number ? `\x1b[${number}m${text}\x1b[0m` : text;
|
||||
const limit = (val, precision=3) => parseFloat(val.toPrecision(precision));
|
||||
const toPercent = val => (100 * val).toPrecision(3);
|
||||
|
||||
const engineCapture = (engine) => ({
|
||||
timestamp: limit(engine.timing.timestamp),
|
||||
extrinsic: worldCaptureExtrinsic(engine.world),
|
||||
intrinsic: worldCaptureIntrinsic(engine.world)
|
||||
});
|
||||
const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildVersion, save) => {
|
||||
const performanceDev = capturePerformanceTotals(capturesDev);
|
||||
const performanceBuild = capturePerformanceTotals(capturesBuild);
|
||||
|
||||
const worldCaptureExtrinsic = world => ({
|
||||
bodies: Composite.allBodies(world).reduce((bodies, body) => {
|
||||
bodies[body.id] = [
|
||||
body.position.x,
|
||||
body.position.y,
|
||||
body.positionPrev.x,
|
||||
body.positionPrev.y,
|
||||
body.angle,
|
||||
body.anglePrev,
|
||||
...body.vertices.reduce((flat, vertex) => (flat.push(vertex.x, vertex.y), flat), [])
|
||||
];
|
||||
const perfChange = noiseThreshold(1 - (performanceDev.duration / performanceBuild.duration), 0.01);
|
||||
const memoryChange = noiseThreshold((performanceDev.memory / performanceBuild.memory) - 1, 0.01);
|
||||
const overlapChange = (performanceDev.overlap / (performanceBuild.overlap || 1)) - 1;
|
||||
const filesizeChange = (devSize / buildSize) - 1;
|
||||
|
||||
return bodies;
|
||||
}, {}),
|
||||
constraints: Composite.allConstraints(world).reduce((constraints, constraint) => {
|
||||
const positionA = Constraint.pointAWorld(constraint);
|
||||
const positionB = Constraint.pointBWorld(constraint);
|
||||
const behaviourSimilaritys = extrinsicSimilarity(capturesDev, capturesBuild, 'behaviourExtrinsic');
|
||||
const behaviourSimilarityAverage = extrinsicSimilarityAverage(behaviourSimilaritys);
|
||||
const behaviourSimilarityEntries = Object.entries(behaviourSimilaritys);
|
||||
behaviourSimilarityEntries.sort((a, b) => a[1] - b[1]);
|
||||
|
||||
constraints[constraint.id] = [
|
||||
positionA.x,
|
||||
positionA.y,
|
||||
positionB.x,
|
||||
positionB.y
|
||||
];
|
||||
const similaritys = extrinsicSimilarity(capturesDev, capturesBuild, 'similarityExtrinsic');
|
||||
const similarityAverage = extrinsicSimilarityAverage(similaritys);
|
||||
|
||||
return constraints;
|
||||
}, {})
|
||||
});
|
||||
const devIntrinsicsChanged = {};
|
||||
const buildIntrinsicsChanged = {};
|
||||
let intrinsicChangeCount = 0;
|
||||
|
||||
const worldCaptureIntrinsic = world => worldCaptureIntrinsicBase({
|
||||
bodies: Composite.allBodies(world).reduce((bodies, body) => {
|
||||
bodies[body.id] = body;
|
||||
return bodies;
|
||||
}, {}),
|
||||
constraints: Composite.allConstraints(world).reduce((constraints, constraint) => {
|
||||
constraints[constraint.id] = constraint;
|
||||
return constraints;
|
||||
}, {}),
|
||||
composites: Composite.allComposites(world).reduce((composites, composite) => {
|
||||
composites[composite.id] = {
|
||||
bodies: Composite.allBodies(composite).map(body => body.id),
|
||||
constraints: Composite.allConstraints(composite).map(constraint => constraint.id),
|
||||
composites: Composite.allComposites(composite).map(composite => composite.id)
|
||||
};
|
||||
return composites;
|
||||
}, {})
|
||||
});
|
||||
const captureSummary = Object.entries(capturesDev)
|
||||
.map(([name]) => {
|
||||
const changedIntrinsics = !equals(capturesDev[name].intrinsic, capturesBuild[name].intrinsic);
|
||||
|
||||
const worldCaptureIntrinsicBase = (obj, depth=0) => {
|
||||
if (obj === Infinity) {
|
||||
return 'Infinity';
|
||||
} else if (typeof obj === 'number') {
|
||||
return limit(obj);
|
||||
} else if (Array.isArray(obj)) {
|
||||
return obj.map(item => worldCaptureIntrinsicBase(item, depth + 1));
|
||||
} else if (typeof obj !== 'object') {
|
||||
return obj;
|
||||
if (changedIntrinsics) {
|
||||
capturesDev[name].changedIntrinsics = true;
|
||||
if (intrinsicChangeCount < 1) {
|
||||
devIntrinsicsChanged[name] = capturesDev[name].state;
|
||||
buildIntrinsicsChanged[name] = capturesBuild[name].state;
|
||||
intrinsicChangeCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const result = Object.entries(obj)
|
||||
.filter(([key]) => depth <= 1 || intrinsicProps.includes(key))
|
||||
.reduce((cleaned, [key, val]) => {
|
||||
if (val && val.id && String(val.id) !== key) {
|
||||
val = val.id;
|
||||
return { name };
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const report = (breakEvery, format) => [
|
||||
[`Output comparison of ${behaviourSimilarityEntries.length}`,
|
||||
`examples against previous release ${format('matter-js@' + buildVersion, colors.Yellow)}`
|
||||
].join(' '),
|
||||
|
||||
`\n\n${format('Behaviour ', colors.White)}`,
|
||||
`${format(formatPercent(behaviourSimilarityAverage), behaviourSimilarityAverage === 1 ? colors.Green : colors.Yellow)}%`,
|
||||
|
||||
` ${format('Similarity', colors.White)}`,
|
||||
`${format(formatPercent(similarityAverage), similarityAverage === 1 ? colors.Green : colors.Yellow)}%`,
|
||||
|
||||
` ${format('Overlap', colors.White)}`,
|
||||
` ${format((overlapChange >= 0 ? '+' : '-') + formatPercent(overlapChange, true), overlapChange <= 0 ? colors.Green : colors.Yellow)}%`,
|
||||
|
||||
`\n${format('Performance', colors.White)}`,
|
||||
`${format((perfChange >= 0 ? '+' : '-') + formatPercent(perfChange, true), perfChange >= 0 ? colors.Green : colors.Yellow)}%`,
|
||||
|
||||
` ${format('Memory', colors.White)}`,
|
||||
` ${format((memoryChange >= 0 ? '+' : '-') + formatPercent(memoryChange, true), memoryChange <= 0 ? colors.Green : colors.Yellow)}%`,
|
||||
|
||||
` ${format('Filesize', colors.White)}`,
|
||||
`${format((filesizeChange >= 0 ? '+' : '-') + formatPercent(filesizeChange, true), filesizeChange <= 0 ? colors.Green : colors.Yellow)}%`,
|
||||
`${format(`${(devSize / 1024).toPrecision(4)} KB`, colors.White)}`,
|
||||
|
||||
captureSummary.reduce((output, p, i) => {
|
||||
output += `${p.name} `;
|
||||
output += `${similarityRatings(behaviourSimilaritys[p.name])} `;
|
||||
output += `${changeRatings(capturesDev[p.name].changedIntrinsics)} `;
|
||||
if (i > 0 && i < captureSummary.length && breakEvery > 0 && i % breakEvery === 0) {
|
||||
output += '\n';
|
||||
}
|
||||
return output;
|
||||
}, '\n\n'),
|
||||
|
||||
`\n\nwhere · no change ● extrinsics changed ◆ intrinsics changed\n`,
|
||||
|
||||
behaviourSimilarityAverage < 1 ? `\n${format('▶', colors.White)} ${format(compareCommand + '=' + 150 + '#' + behaviourSimilarityEntries[0][0], colors.BrightCyan)}` : '',
|
||||
intrinsicChangeCount > 0 ? `\n${format('▶', colors.White)} ${format((save ? diffCommand : diffSaveCommand), colors.BrightCyan)}` : ''
|
||||
].join(' ');
|
||||
|
||||
if (save) {
|
||||
writeResult('examples-dev', devIntrinsicsChanged);
|
||||
writeResult('examples-build', buildIntrinsicsChanged);
|
||||
writeResult('examples-report', report(5, s => s));
|
||||
}
|
||||
|
||||
if (Array.isArray(val) && !['composites', 'constraints', 'bodies'].includes(key)) {
|
||||
val = `[${val.length}]`;
|
||||
}
|
||||
|
||||
cleaned[key] = worldCaptureIntrinsicBase(val, depth + 1);
|
||||
return cleaned;
|
||||
}, {});
|
||||
|
||||
return Object.keys(result).sort()
|
||||
.reduce((sorted, key) => (sorted[key] = result[key], sorted), {});
|
||||
return report(5, color);
|
||||
};
|
||||
|
||||
const similarity = (a, b) => {
|
||||
|
@ -124,18 +105,56 @@ const similarity = (a, b) => {
|
|||
return 1 / (1 + (distance / a.length));
|
||||
};
|
||||
|
||||
const captureSimilarityExtrinsic = (currentCaptures, referenceCaptures) => {
|
||||
const similarityRatings = similarity => similarity < equalityThreshold ? color('●', colors.Yellow) : '·';
|
||||
const changeRatings = isChanged => isChanged ? color('◆', colors.White) : '·';
|
||||
const color = (text, number) => number ? `\x1b[${number}m${text}\x1b[0m` : text;
|
||||
const formatPercent = (val, abs) => (100 * (abs ? Math.abs(val) : val)).toFixed(2);
|
||||
|
||||
const noiseThreshold = (val, threshold) => {
|
||||
const sign = val < 0 ? -1 : 1;
|
||||
const magnitude = Math.abs(val);
|
||||
return sign * Math.max(0, magnitude - threshold) / (1 - threshold);
|
||||
};
|
||||
|
||||
const equals = (a, b) => {
|
||||
try {
|
||||
expect(a).toEqual(b);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const capturePerformanceTotals = (captures) => {
|
||||
const totals = {
|
||||
duration: 0,
|
||||
overlap: 0,
|
||||
memory: 0
|
||||
};
|
||||
|
||||
for (const [ name ] of Object.entries(captures)) {
|
||||
totals.duration += captures[name].duration;
|
||||
totals.overlap += captures[name].overlap;
|
||||
totals.memory += captures[name].memory;
|
||||
};
|
||||
|
||||
return totals;
|
||||
};
|
||||
|
||||
const extrinsicSimilarity = (currentCaptures, referenceCaptures, key) => {
|
||||
const result = {};
|
||||
|
||||
Object.entries(currentCaptures).forEach(([name, current]) => {
|
||||
const reference = referenceCaptures[name];
|
||||
const worldVector = [];
|
||||
const worldVectorRef = [];
|
||||
const currentExtrinsic = current[key];
|
||||
const referenceExtrinsic = reference[key];
|
||||
|
||||
Object.keys(current.extrinsic).forEach(objectType => {
|
||||
Object.keys(current.extrinsic[objectType]).forEach(objectId => {
|
||||
worldVector.push(...current.extrinsic[objectType][objectId]);
|
||||
worldVectorRef.push(...reference.extrinsic[objectType][objectId]);
|
||||
Object.keys(currentExtrinsic).forEach(objectType => {
|
||||
Object.keys(currentExtrinsic[objectType]).forEach(objectId => {
|
||||
worldVector.push(...currentExtrinsic[objectType][objectId]);
|
||||
worldVectorRef.push(...referenceExtrinsic[objectType][objectId]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -145,6 +164,56 @@ const captureSimilarityExtrinsic = (currentCaptures, referenceCaptures) => {
|
|||
return result;
|
||||
};
|
||||
|
||||
const extrinsicSimilarityAverage = (similaritys) => {
|
||||
const entries = Object.entries(similaritys);
|
||||
let average = 0;
|
||||
|
||||
entries.forEach(([_, similarity]) => average += similarity);
|
||||
|
||||
return average /= entries.length;
|
||||
};
|
||||
|
||||
const serialize = (obj, exclude=()=>false, precision=4, path='$', visited=[], paths=[]) => {
|
||||
if (typeof obj === 'number') {
|
||||
return parseFloat(obj.toPrecision(precision));
|
||||
} else if (typeof obj === 'string' || typeof obj === 'boolean') {
|
||||
return obj;
|
||||
} else if (obj === null) {
|
||||
return 'null';
|
||||
} else if (typeof obj === 'undefined') {
|
||||
return 'undefined';
|
||||
} else if (obj === Infinity) {
|
||||
return 'Infinity';
|
||||
} else if (obj === -Infinity) {
|
||||
return '-Infinity';
|
||||
} else if (typeof obj === 'function') {
|
||||
return 'function';
|
||||
} else if (Array.isArray(obj)) {
|
||||
return obj.map(
|
||||
(item, index) => serialize(item, exclude, precision, path + '.' + index, visited, paths)
|
||||
);
|
||||
}
|
||||
|
||||
const visitedIndex = visited.indexOf(obj);
|
||||
|
||||
if (visitedIndex !== -1) {
|
||||
return paths[visitedIndex];
|
||||
}
|
||||
|
||||
visited.push(obj);
|
||||
paths.push(path);
|
||||
|
||||
const result = {};
|
||||
|
||||
for (const key of Object.keys(obj).sort()) {
|
||||
if (!exclude(key, obj[key], path + '.' + key)) {
|
||||
result[key] = serialize(obj[key], exclude, precision, path + '.' + key, visited, paths);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const writeResult = (name, obj) => {
|
||||
try {
|
||||
fs.mkdirSync(comparePath, { recursive: true });
|
||||
|
@ -159,9 +228,35 @@ const writeResult = (name, obj) => {
|
|||
}
|
||||
};
|
||||
|
||||
const logReport = (captures, version) => {
|
||||
let report = '';
|
||||
|
||||
for (const capture of Object.values(captures)) {
|
||||
if (!capture.logs.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
report += ` ${capture.name}\n`;
|
||||
|
||||
for (const log of capture.logs) {
|
||||
report += ` ${log}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
return `Output logs from ${color(version, colors.Yellow)} build on last run\n\n`
|
||||
+ (report ? report : ' None\n');
|
||||
};
|
||||
|
||||
const requireUncached = path => {
|
||||
delete require.cache[require.resolve(path)];
|
||||
const module = require(path);
|
||||
delete require.cache[require.resolve(path)];
|
||||
return module;
|
||||
};
|
||||
|
||||
const toMatchExtrinsics = {
|
||||
toMatchExtrinsics(received, value) {
|
||||
const similaritys = captureSimilarityExtrinsic(received, value);
|
||||
const similaritys = extrinsicSimilarity(received, value, 'extrinsic');
|
||||
const pass = Object.values(similaritys).every(similarity => similarity >= equalityThreshold);
|
||||
|
||||
return {
|
||||
|
@ -190,119 +285,7 @@ const toMatchIntrinsics = {
|
|||
}
|
||||
};
|
||||
|
||||
const similarityRatings = similarity => similarity < equalityThreshold ? color('●', colors.Yellow) : '·';
|
||||
const changeRatings = isChanged => isChanged ? color('◆', colors.White) : '·';
|
||||
|
||||
const equals = (a, b) => {
|
||||
try {
|
||||
expect(a).toEqual(b);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const logReport = (captures, version) => {
|
||||
let report = '';
|
||||
|
||||
for (const capture of Object.values(captures)) {
|
||||
if (!capture.logs.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
report += ` ${capture.name}\n`;
|
||||
|
||||
for (const log of capture.logs) {
|
||||
report += ` ${log}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
return `Output logs from ${color(version, colors.Yellow)} version on last run\n\n`
|
||||
+ (report ? report : ' None\n');
|
||||
};
|
||||
|
||||
const comparisonReport = (capturesDev, capturesBuild, buildVersion, save) => {
|
||||
const similaritys = captureSimilarityExtrinsic(capturesDev, capturesBuild);
|
||||
const similarityEntries = Object.entries(similaritys);
|
||||
const devIntrinsicsChanged = {};
|
||||
const buildIntrinsicsChanged = {};
|
||||
let intrinsicChangeCount = 0;
|
||||
let totalTimeBuild = 0;
|
||||
let totalTimeDev = 0;
|
||||
let totalOverlapBuild = 0;
|
||||
let totalOverlapDev = 0;
|
||||
|
||||
const capturePerformance = Object.entries(capturesDev).map(([name]) => {
|
||||
totalTimeBuild += capturesBuild[name].duration;
|
||||
totalTimeDev += capturesDev[name].duration;
|
||||
totalOverlapBuild += capturesBuild[name].overlap;
|
||||
totalOverlapDev += capturesDev[name].overlap;
|
||||
|
||||
const changedIntrinsics = !equals(capturesDev[name].intrinsic, capturesBuild[name].intrinsic);
|
||||
if (changedIntrinsics) {
|
||||
capturesDev[name].changedIntrinsics = true;
|
||||
if (intrinsicChangeCount < 2) {
|
||||
devIntrinsicsChanged[name] = capturesDev[name].intrinsic;
|
||||
buildIntrinsicsChanged[name] = capturesBuild[name].intrinsic;
|
||||
intrinsicChangeCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return { name };
|
||||
});
|
||||
|
||||
capturePerformance.sort((a, b) => a.name.localeCompare(b.name));
|
||||
similarityEntries.sort((a, b) => a[1] - b[1]);
|
||||
|
||||
let perfChange = 1 - (totalTimeDev / totalTimeBuild);
|
||||
|
||||
const perfChangeThreshold = 0.075;
|
||||
const perfChangeLarge = Math.abs(perfChange) > perfChangeThreshold;
|
||||
perfChange = perfChangeLarge ? perfChange : 0;
|
||||
|
||||
let similarityAvg = 0;
|
||||
similarityEntries.forEach(([_, similarity]) => {
|
||||
similarityAvg += similarity;
|
||||
});
|
||||
|
||||
similarityAvg /= similarityEntries.length;
|
||||
|
||||
const overlapChange = (totalOverlapDev / (totalOverlapBuild || 1)) - 1;
|
||||
|
||||
const report = (breakEvery, format) => [
|
||||
[`Output comparison of ${similarityEntries.length}`,
|
||||
`examples against previous release ${format('matter-js@' + buildVersion, colors.Yellow)}`
|
||||
].join(' '),
|
||||
`\n\n${format('Similarity', colors.White)}`,
|
||||
`${format(toPercent(similarityAvg), similarityAvg === 1 ? colors.Green : colors.Yellow)}%`,
|
||||
`${format('Performance', colors.White)}`,
|
||||
`${format((perfChange >= 0 ? '+' : '') + toPercent(perfChange), perfChange >= 0 ? colors.Green : colors.Red)}%`,
|
||||
`${format('Overlap', colors.White)}`,
|
||||
`${format((overlapChange >= 0 ? '+' : '') + toPercent(overlapChange), overlapChange > 0 ? colors.Red : colors.Green)}%`,
|
||||
capturePerformance.reduce((output, p, i) => {
|
||||
output += `${p.name} `;
|
||||
output += `${similarityRatings(similaritys[p.name])} `;
|
||||
output += `${changeRatings(capturesDev[p.name].changedIntrinsics)} `;
|
||||
if (i > 0 && i < capturePerformance.length && breakEvery > 0 && i % breakEvery === 0) {
|
||||
output += '\n';
|
||||
}
|
||||
return output;
|
||||
}, '\n\n'),
|
||||
`\nwhere · no change ● extrinsics changed ◆ intrinsics changed\n`,
|
||||
similarityAvg < 1 ? `\n${format('▶', colors.White)} ${format(compareCommand + '=' + 120 + '#' + similarityEntries[0][0], colors.BrightCyan)}` : '',
|
||||
intrinsicChangeCount > 0 ? `\n${format('▶', colors.White)} ${format((save ? diffCommand : diffSaveCommand), colors.BrightCyan)}` : ''
|
||||
].join(' ');
|
||||
|
||||
if (save) {
|
||||
writeResult('examples-dev', devIntrinsicsChanged);
|
||||
writeResult('examples-build', buildIntrinsicsChanged);
|
||||
writeResult('examples-report', report(5, s => s));
|
||||
}
|
||||
|
||||
return report(5, color);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
engineCapture, comparisonReport, logReport,
|
||||
toMatchExtrinsics, toMatchIntrinsics
|
||||
requireUncached, comparisonReport, logReport,
|
||||
serialize, toMatchExtrinsics, toMatchIntrinsics
|
||||
};
|
|
@ -9,18 +9,17 @@ const execSync = require('child_process').execSync;
|
|||
|
||||
module.exports = (env = {}) => {
|
||||
const minimize = env.MINIMIZE || false;
|
||||
const alpha = env.ALPHA || false;
|
||||
const kind = env.KIND || null;
|
||||
const sizeThreshold = minimize ? 100 * 1024 : 512 * 1024;
|
||||
|
||||
const commitHash = execSync('git rev-parse --short HEAD').toString().trim();
|
||||
const version = !alpha ? pkg.version : `${pkg.version}-alpha+${commitHash}`;
|
||||
const version = !kind ? pkg.version : `${pkg.version}-${kind}+${commitHash}`;
|
||||
const license = fs.readFileSync('LICENSE', 'utf8');
|
||||
const resolve = relativePath => path.resolve(__dirname, relativePath);
|
||||
|
||||
const alphaInfo = 'Experimental pre-release build.\n ';
|
||||
const banner =
|
||||
`${pkg.name} ${version} by @liabru
|
||||
${alpha ? alphaInfo : ''}${pkg.homepage}
|
||||
${kind ? 'Experimental pre-release build.\n ' : ''}${pkg.homepage}
|
||||
License ${pkg.license}${!minimize ? '\n\n' + license : ''}`;
|
||||
|
||||
return {
|
||||
|
@ -32,7 +31,7 @@ License ${pkg.license}${!minimize ? '\n\n' + license : ''}`;
|
|||
umdNamedDefine: true,
|
||||
globalObject: 'this',
|
||||
path: resolve('./build'),
|
||||
filename: `[name]${alpha ? '.alpha' : ''}${minimize ? '.min' : ''}.js`
|
||||
filename: `[name]${kind ? '.' + kind : ''}${minimize ? '.min' : ''}.js`
|
||||
},
|
||||
optimization: { minimize },
|
||||
performance: {
|
||||
|
|
|
@ -29,7 +29,7 @@ License ${pkg.license}`;
|
|||
return {
|
||||
entry: { [name]: './demo/src/index.js' },
|
||||
node: false,
|
||||
devtool: 'source-map',
|
||||
devtool: devServer ? 'source-map' : 'none',
|
||||
output: {
|
||||
library: 'MatterDemo',
|
||||
libraryTarget: 'umd',
|
||||
|
|
Loading…
Reference in a new issue