mirror of
https://github.com/liabru/matter-js.git
synced 2024-11-23 09:26:51 -05:00
Merge branch 'master' into runner-2
* master: (22 commits) improve test comparison report update ci update ci preserve pair.contacts order optimised Resolver.solvePosition bump package lock improve test comparison report fixed compare tool layer order in demo testbed fixed compare tool layer order in demo testbed added multi example testing tool to demo added body removal to Example.remove changed Composte.removeComposite and Composte.removeBody to reset body.sleepCounter optimised Collision.collides fix collision events for sleeping pairs, closes #1077 added local pairs functions in Pairs.update removed pair.confirmedActive changed Pair.id format to use shorter ids optimised Resolver.solveVelocity optimised contacts and supports memory and gc use optimised pairs and collisions memory and gc use ...
This commit is contained in:
commit
2c91e7400f
20 changed files with 4850 additions and 5680 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [12.x, 14.x, 16.x]
|
node-version: [16.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
.matter-js-compare-build.matter-demo canvas {
|
.matter-js-compare-build.matter-demo canvas {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
|
z-index: 25 !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
97
demo/src/Multi.js
Normal file
97
demo/src/Multi.js
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
/**
|
||||||
|
* A Matter.js multi example testbed based on MatterTools.
|
||||||
|
*
|
||||||
|
* Tool to interactively test multiple examples at once.
|
||||||
|
*
|
||||||
|
* USAGE: [host]?multi#[example1,example2,example3...]
|
||||||
|
* e.g. http://localhost:8000/?multi#mixed
|
||||||
|
*
|
||||||
|
* @module Multi
|
||||||
|
*/
|
||||||
|
|
||||||
|
var MatterTools = require('matter-tools');
|
||||||
|
|
||||||
|
var multi = function(examples, isDev) {
|
||||||
|
var demo = MatterTools.Demo.create({
|
||||||
|
toolbar: {
|
||||||
|
title: 'matter-js ・ ' + (isDev ? 'dev' : '') + ' ・ multi',
|
||||||
|
url: 'https://github.com/liabru/matter-js',
|
||||||
|
reset: false,
|
||||||
|
source: false,
|
||||||
|
inspector: false,
|
||||||
|
tools: false,
|
||||||
|
fullscreen: false,
|
||||||
|
exampleSelect: false
|
||||||
|
},
|
||||||
|
tools: {
|
||||||
|
inspector: false,
|
||||||
|
gui: false
|
||||||
|
},
|
||||||
|
inline: false,
|
||||||
|
preventZoom: false,
|
||||||
|
resetOnOrientation: false,
|
||||||
|
routing: false,
|
||||||
|
startExample: false
|
||||||
|
});
|
||||||
|
|
||||||
|
var urlHash = window.location.hash,
|
||||||
|
allExampleIds = examples.map(function(example) { return example.id; }),
|
||||||
|
exampleIds = urlHash ? urlHash.slice(1).split(',') : allExampleIds.slice(0, 4),
|
||||||
|
exampleCount = Math.ceil(Math.sqrt(exampleIds.length));
|
||||||
|
|
||||||
|
var container = document.createElement('div');
|
||||||
|
container.style = 'display: grid; grid-template-columns: repeat(' + exampleCount + ', 1fr); grid-template-rows: repeat(' + exampleCount + ', 1fr); max-width: calc(100vmin * 1.25 - 40px); max-height: 100vmin;';
|
||||||
|
|
||||||
|
demo.dom.root.appendChild(container);
|
||||||
|
document.body.appendChild(demo.dom.root);
|
||||||
|
|
||||||
|
document.title = 'Matter.js Multi' + (isDev ? ' ・ Dev' : '');
|
||||||
|
console.info('Demo.Multi: matter-js@' + Matter.version);
|
||||||
|
|
||||||
|
// always show debug info
|
||||||
|
Matter.before('Render.create', function(renderOptions) {
|
||||||
|
renderOptions.options.showDebug = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
Matter.after('Runner.create', function() {
|
||||||
|
this.isFixed = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var runExamples = function(exampleIds) {
|
||||||
|
for (var i = 0; i < exampleIds.length; i += 1) {
|
||||||
|
var exampleId = exampleIds[i],
|
||||||
|
example = examples.find(function(example) { return example.id === exampleId; });
|
||||||
|
|
||||||
|
if (!example) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var canvas = example.init().render.canvas;
|
||||||
|
container.appendChild(canvas);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
runExamples(exampleIds);
|
||||||
|
|
||||||
|
// arrow key navigation of examples
|
||||||
|
document.addEventListener('keyup', function(event) {
|
||||||
|
var isBackKey = event.key === 'ArrowLeft' || event.key === 'ArrowUp',
|
||||||
|
isForwardKey = event.key === 'ArrowRight' || event.key === 'ArrowDown';
|
||||||
|
|
||||||
|
if (isBackKey || isForwardKey) {
|
||||||
|
var direction = isBackKey ? -1 : 1;
|
||||||
|
|
||||||
|
var currentExampleIndex = allExampleIds.findIndex(function(exampleId) {
|
||||||
|
return exampleId === exampleIds[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
var nextExampleId = (allExampleIds.length + currentExampleIndex + direction * exampleIds.length) % allExampleIds.length,
|
||||||
|
nextExamples = allExampleIds.slice(nextExampleId, (nextExampleId + exampleIds.length) % allExampleIds.length);
|
||||||
|
|
||||||
|
window.location.hash = nextExamples.join(',');
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { multi: multi };
|
|
@ -35,7 +35,6 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: 0;
|
border: 0;
|
||||||
z-index: 1;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,9 +42,15 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.matter-js.dev.comparing.matter-demo canvas {
|
||||||
|
background: transparent !important;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
.matter-js-compare-build.matter-demo canvas {
|
.matter-js-compare-build.matter-demo canvas {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
|
z-index: 15 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (min-width: 1300px) {
|
@media only screen and (min-width: 1300px) {
|
||||||
|
@ -54,6 +59,11 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.matter-js-compare-build.matter-demo canvas {
|
||||||
|
position: relative;
|
||||||
|
z-index: 15;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
var Matter = require('matter-js');
|
var Matter = require('matter-js');
|
||||||
var Examples = require('../../examples/index');
|
var Examples = require('../../examples/index');
|
||||||
var compare = require('./Compare').compare;
|
var compare = require('./Compare').compare;
|
||||||
|
var multi = require('./Multi').multi;
|
||||||
var demo = require('./Demo').demo;
|
var demo = require('./Demo').demo;
|
||||||
|
|
||||||
// browser globals
|
// browser globals
|
||||||
|
@ -31,9 +32,13 @@ var examples = Matter.Common.keys(Examples).map(function(id){
|
||||||
|
|
||||||
// start the requested tool
|
// start the requested tool
|
||||||
var isCompare = window.location.search.indexOf('compare') >= 0;
|
var isCompare = window.location.search.indexOf('compare') >= 0;
|
||||||
|
var isMulti = window.location.search.indexOf('multi') >= 0;
|
||||||
var isDev = __MATTER_IS_DEV__;
|
var isDev = __MATTER_IS_DEV__;
|
||||||
|
|
||||||
if (isCompare) {
|
if (isCompare) {
|
||||||
compare(examples, isDev);
|
compare(examples, isDev);
|
||||||
|
} else if (isMulti) {
|
||||||
|
multi(examples, isDev);
|
||||||
} else {
|
} else {
|
||||||
demo(examples, isDev);
|
demo(examples, isDev);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,11 @@ Example.remove = function() {
|
||||||
Events = Matter.Events;
|
Events = Matter.Events;
|
||||||
|
|
||||||
// create engine
|
// create engine
|
||||||
var engine = Engine.create(),
|
var engine = Engine.create({
|
||||||
world = engine.world;
|
enableSleeping: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var world = engine.world;
|
||||||
|
|
||||||
// create renderer
|
// create renderer
|
||||||
var render = Render.create({
|
var render = Render.create({
|
||||||
|
@ -24,6 +27,7 @@ Example.remove = function() {
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
showAngleIndicator: true,
|
showAngleIndicator: true,
|
||||||
|
showSleeping: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -33,9 +37,6 @@ Example.remove = function() {
|
||||||
var runner = Runner.create();
|
var runner = Runner.create();
|
||||||
Runner.run(runner, engine);
|
Runner.run(runner, engine);
|
||||||
|
|
||||||
var stack = null,
|
|
||||||
lastTimestamp = 0;
|
|
||||||
|
|
||||||
var createStack = function() {
|
var createStack = function() {
|
||||||
return Composites.stack(20, 20, 10, 5, 0, 0, function(x, y) {
|
return Composites.stack(20, 20, 10, 5, 0, 0, function(x, y) {
|
||||||
var sides = Math.round(Common.random(1, 8));
|
var sides = Math.round(Common.random(1, 8));
|
||||||
|
@ -61,15 +62,28 @@ Example.remove = function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// add and remove stacks every few updates
|
var stack = null,
|
||||||
|
bottomStack = createStack(),
|
||||||
|
lastTimestamp = 0;
|
||||||
|
|
||||||
|
// add and remove bodies and composites every few updates
|
||||||
Events.on(engine, 'afterUpdate', function(event) {
|
Events.on(engine, 'afterUpdate', function(event) {
|
||||||
// limit rate
|
// limit rate
|
||||||
if (stack && event.timestamp - lastTimestamp < 800) {
|
if (event.timestamp - lastTimestamp < 800) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTimestamp = event.timestamp;
|
lastTimestamp = event.timestamp;
|
||||||
|
|
||||||
|
// remove an old body
|
||||||
|
Composite.remove(bottomStack, bottomStack.bodies[0]);
|
||||||
|
|
||||||
|
// add a new body
|
||||||
|
Composite.add(
|
||||||
|
bottomStack,
|
||||||
|
Bodies.rectangle(Common.random(100, 500), 50, Common.random(25, 50), Common.random(25, 50))
|
||||||
|
);
|
||||||
|
|
||||||
// remove last stack
|
// remove last stack
|
||||||
if (stack) {
|
if (stack) {
|
||||||
Composite.remove(world, stack);
|
Composite.remove(world, stack);
|
||||||
|
@ -82,10 +96,9 @@ Example.remove = function() {
|
||||||
Composite.add(world, stack);
|
Composite.add(world, stack);
|
||||||
});
|
});
|
||||||
|
|
||||||
// add another stack that will not be removed
|
|
||||||
Composite.add(world, createStack());
|
|
||||||
|
|
||||||
Composite.add(world, [
|
Composite.add(world, [
|
||||||
|
bottomStack,
|
||||||
|
|
||||||
// walls
|
// walls
|
||||||
Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
|
Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
|
||||||
Bodies.rectangle(400, 600, 800, 50, { isStatic: true }),
|
Bodies.rectangle(400, 600, 800, 50, { isStatic: true }),
|
||||||
|
|
9648
package-lock.json
generated
9648
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -23,8 +23,8 @@
|
||||||
"conventional-changelog-cli": "^2.1.1",
|
"conventional-changelog-cli": "^2.1.1",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"html-webpack-plugin": "^4.5.1",
|
"html-webpack-plugin": "^4.5.1",
|
||||||
"jest": "^25.1.0",
|
"jest": "^29.7.0",
|
||||||
"jest-worker": "^24.9.0",
|
"jest-worker": "^29.7.0",
|
||||||
"json-stringify-pretty-compact": "^2.0.0",
|
"json-stringify-pretty-compact": "^2.0.0",
|
||||||
"matter-tools": "^0.14.0",
|
"matter-tools": "^0.14.0",
|
||||||
"matter-wrap": "^0.2.0",
|
"matter-wrap": "^0.2.0",
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
"lint": "eslint 'src/**/*.js' 'demo/src/**/*.js' 'examples/*.js' 'webpack.*.js'",
|
"lint": "eslint 'src/**/*.js' 'demo/src/**/*.js' 'examples/*.js' 'webpack.*.js'",
|
||||||
"doc": "yuidoc --config yuidoc.json --project-version $npm_package_version",
|
"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'",
|
"doc-watch": "nodemon --delay 3 --watch 'matter-doc-theme' --watch src -e 'js,html,css,handlebars' --exec 'npm run doc'",
|
||||||
"benchmark": "npm run test-node -- --examples=stress3,stress4 --updates=300 --repeats=3",
|
"benchmark": "npm run test-node -- --examples=stress3,stress4 --benchmark=true --updates=300 --repeats=3",
|
||||||
"test": "npm run test-node",
|
"test": "npm run test-node",
|
||||||
"test-node": "npm run build-dev && node --expose-gc node_modules/.bin/jest --force-exit --no-cache --runInBand ./test/Examples.spec.js",
|
"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-browser": "node --expose-gc node_modules/.bin/jest --force-exit --no-cache --runInBand ./test/Browser.spec.js",
|
||||||
|
|
|
@ -192,8 +192,15 @@ var Body = require('./Body');
|
||||||
*/
|
*/
|
||||||
Composite.removeComposite = function(compositeA, compositeB, deep) {
|
Composite.removeComposite = function(compositeA, compositeB, deep) {
|
||||||
var position = Common.indexOf(compositeA.composites, compositeB);
|
var position = Common.indexOf(compositeA.composites, compositeB);
|
||||||
|
|
||||||
if (position !== -1) {
|
if (position !== -1) {
|
||||||
|
var bodies = Composite.allBodies(compositeB);
|
||||||
|
|
||||||
Composite.removeCompositeAt(compositeA, position);
|
Composite.removeCompositeAt(compositeA, position);
|
||||||
|
|
||||||
|
for (var i = 0; i < bodies.length; i++) {
|
||||||
|
bodies[i].sleepCounter = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deep) {
|
if (deep) {
|
||||||
|
@ -244,8 +251,10 @@ var Body = require('./Body');
|
||||||
*/
|
*/
|
||||||
Composite.removeBody = function(composite, body, deep) {
|
Composite.removeBody = function(composite, body, deep) {
|
||||||
var position = Common.indexOf(composite.bodies, body);
|
var position = Common.indexOf(composite.bodies, body);
|
||||||
|
|
||||||
if (position !== -1) {
|
if (position !== -1) {
|
||||||
Composite.removeBodyAt(composite, position);
|
Composite.removeBodyAt(composite, position);
|
||||||
|
body.sleepCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deep) {
|
if (deep) {
|
||||||
|
@ -296,6 +305,7 @@ var Body = require('./Body');
|
||||||
*/
|
*/
|
||||||
Composite.removeConstraint = function(composite, constraint, deep) {
|
Composite.removeConstraint = function(composite, constraint, deep) {
|
||||||
var position = Common.indexOf(composite.constraints, constraint);
|
var position = Common.indexOf(composite.constraints, constraint);
|
||||||
|
|
||||||
if (position !== -1) {
|
if (position !== -1) {
|
||||||
Composite.removeConstraintAt(composite, position);
|
Composite.removeConstraintAt(composite, position);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,8 @@ var Pair = require('./Pair');
|
||||||
normal: { x: 0, y: 0 },
|
normal: { x: 0, y: 0 },
|
||||||
tangent: { x: 0, y: 0 },
|
tangent: { x: 0, y: 0 },
|
||||||
penetration: { x: 0, y: 0 },
|
penetration: { x: 0, y: 0 },
|
||||||
supports: []
|
supports: [null, null],
|
||||||
|
supportCount: 0
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -99,27 +100,32 @@ var Pair = require('./Pair');
|
||||||
}
|
}
|
||||||
|
|
||||||
var normal = collision.normal,
|
var normal = collision.normal,
|
||||||
|
tangent = collision.tangent,
|
||||||
|
penetration = collision.penetration,
|
||||||
supports = collision.supports,
|
supports = collision.supports,
|
||||||
|
depth = minOverlap.overlap,
|
||||||
minAxis = minOverlap.axis,
|
minAxis = minOverlap.axis,
|
||||||
minAxisX = minAxis.x,
|
normalX = minAxis.x,
|
||||||
minAxisY = minAxis.y;
|
normalY = minAxis.y,
|
||||||
|
deltaX = bodyB.position.x - bodyA.position.x,
|
||||||
|
deltaY = bodyB.position.y - bodyA.position.y;
|
||||||
|
|
||||||
// ensure normal is facing away from bodyA
|
// ensure normal is facing away from bodyA
|
||||||
if (minAxisX * (bodyB.position.x - bodyA.position.x) + minAxisY * (bodyB.position.y - bodyA.position.y) < 0) {
|
if (normalX * deltaX + normalY * deltaY >= 0) {
|
||||||
normal.x = minAxisX;
|
normalX = -normalX;
|
||||||
normal.y = minAxisY;
|
normalY = -normalY;
|
||||||
} else {
|
|
||||||
normal.x = -minAxisX;
|
|
||||||
normal.y = -minAxisY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normal.x = normalX;
|
||||||
|
normal.y = normalY;
|
||||||
|
|
||||||
collision.tangent.x = -normal.y;
|
tangent.x = -normalY;
|
||||||
collision.tangent.y = normal.x;
|
tangent.y = normalX;
|
||||||
|
|
||||||
collision.depth = minOverlap.overlap;
|
penetration.x = normalX * depth;
|
||||||
|
penetration.y = normalY * depth;
|
||||||
|
|
||||||
collision.penetration.x = normal.x * collision.depth;
|
collision.depth = depth;
|
||||||
collision.penetration.y = normal.y * collision.depth;
|
|
||||||
|
|
||||||
// find support points, there is always either exactly one or two
|
// find support points, there is always either exactly one or two
|
||||||
var supportsB = Collision._findSupports(bodyA, bodyB, normal, 1),
|
var supportsB = Collision._findSupports(bodyA, bodyB, normal, 1),
|
||||||
|
@ -152,8 +158,8 @@ var Pair = require('./Pair');
|
||||||
supports[supportCount++] = supportsB[0];
|
supports[supportCount++] = supportsB[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// update supports array size
|
// update support count
|
||||||
supports.length = supportCount;
|
collision.supportCount = supportCount;
|
||||||
|
|
||||||
return collision;
|
return collision;
|
||||||
};
|
};
|
||||||
|
@ -232,32 +238,6 @@ var Pair = require('./Pair');
|
||||||
result.overlap = overlapMin;
|
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.
|
* Finds supporting vertices given two bodies along a given direction using hill-climbing.
|
||||||
* @method _findSupports
|
* @method _findSupports
|
||||||
|
@ -275,15 +255,15 @@ var Pair = require('./Pair');
|
||||||
bodyAPositionY = bodyA.position.y,
|
bodyAPositionY = bodyA.position.y,
|
||||||
normalX = normal.x * direction,
|
normalX = normal.x * direction,
|
||||||
normalY = normal.y * direction,
|
normalY = normal.y * direction,
|
||||||
nearestDistance = Number.MAX_VALUE,
|
vertexA = vertices[0],
|
||||||
vertexA,
|
vertexB = vertexA,
|
||||||
vertexB,
|
nearestDistance = normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y),
|
||||||
vertexC,
|
vertexC,
|
||||||
distance,
|
distance,
|
||||||
j;
|
j;
|
||||||
|
|
||||||
// find deepest vertex relative to the axis
|
// find deepest vertex relative to the axis
|
||||||
for (j = 0; j < verticesLength; j += 1) {
|
for (j = 1; j < verticesLength; j += 1) {
|
||||||
vertexB = vertices[j];
|
vertexB = vertices[j];
|
||||||
distance = normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y);
|
distance = normalX * (bodyAPositionX - vertexB.x) + normalY * (bodyAPositionY - vertexB.y);
|
||||||
|
|
||||||
|
@ -398,6 +378,10 @@ var Pair = require('./Pair');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of body vertices that represent the support points in the collision.
|
* An array of body vertices that represent the support points in the collision.
|
||||||
|
*
|
||||||
|
* _Note:_ Only the first `collision.supportCount` items of `collision.supports` are active.
|
||||||
|
* Therefore use `collision.supportCount` instead of `collision.supports.length` when iterating the active supports.
|
||||||
|
*
|
||||||
* These are the deepest vertices (along the collision normal) of each body that are contained by the other body's vertices.
|
* These are the deepest vertices (along the collision normal) of each body that are contained by the other body's vertices.
|
||||||
*
|
*
|
||||||
* @property supports
|
* @property supports
|
||||||
|
@ -405,4 +389,15 @@ var Pair = require('./Pair');
|
||||||
* @default []
|
* @default []
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of active supports for this collision found in `collision.supports`.
|
||||||
|
*
|
||||||
|
* _Note:_ Only the first `collision.supportCount` items of `collision.supports` are active.
|
||||||
|
* Therefore use `collision.supportCount` instead of `collision.supports.length` when iterating the active supports.
|
||||||
|
*
|
||||||
|
* @property supportCount
|
||||||
|
* @type number
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -13,7 +13,7 @@ module.exports = Contact;
|
||||||
/**
|
/**
|
||||||
* Creates a new contact.
|
* Creates a new contact.
|
||||||
* @method create
|
* @method create
|
||||||
* @param {vertex} vertex
|
* @param {vertex} [vertex]
|
||||||
* @return {contact} A new contact
|
* @return {contact} A new contact
|
||||||
*/
|
*/
|
||||||
Contact.create = function(vertex) {
|
Contact.create = function(vertex) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ var Collision = require('./Collision');
|
||||||
Detector.create = function(options) {
|
Detector.create = function(options) {
|
||||||
var defaults = {
|
var defaults = {
|
||||||
bodies: [],
|
bodies: [],
|
||||||
|
collisions: [],
|
||||||
pairs: null
|
pairs: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,6 +46,7 @@ var Collision = require('./Collision');
|
||||||
*/
|
*/
|
||||||
Detector.clear = function(detector) {
|
Detector.clear = function(detector) {
|
||||||
detector.bodies = [];
|
detector.bodies = [];
|
||||||
|
detector.collisions = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,12 +59,13 @@ var Collision = require('./Collision');
|
||||||
* @return {collision[]} collisions
|
* @return {collision[]} collisions
|
||||||
*/
|
*/
|
||||||
Detector.collisions = function(detector) {
|
Detector.collisions = function(detector) {
|
||||||
var collisions = [],
|
var pairs = detector.pairs,
|
||||||
pairs = detector.pairs,
|
|
||||||
bodies = detector.bodies,
|
bodies = detector.bodies,
|
||||||
bodiesLength = bodies.length,
|
bodiesLength = bodies.length,
|
||||||
canCollide = Detector.canCollide,
|
canCollide = Detector.canCollide,
|
||||||
collides = Collision.collides,
|
collides = Collision.collides,
|
||||||
|
collisions = detector.collisions,
|
||||||
|
collisionIndex = 0,
|
||||||
i,
|
i,
|
||||||
j;
|
j;
|
||||||
|
|
||||||
|
@ -104,7 +107,7 @@ var Collision = require('./Collision');
|
||||||
var collision = collides(bodyA, bodyB, pairs);
|
var collision = collides(bodyA, bodyB, pairs);
|
||||||
|
|
||||||
if (collision) {
|
if (collision) {
|
||||||
collisions.push(collision);
|
collisions[collisionIndex++] = collision;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var partsAStart = partsALength > 1 ? 1 : 0,
|
var partsAStart = partsALength > 1 ? 1 : 0,
|
||||||
|
@ -126,7 +129,7 @@ var Collision = require('./Collision');
|
||||||
var collision = collides(partA, partB, pairs);
|
var collision = collides(partA, partB, pairs);
|
||||||
|
|
||||||
if (collision) {
|
if (collision) {
|
||||||
collisions.push(collision);
|
collisions[collisionIndex++] = collision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,6 +137,10 @@ var Collision = require('./Collision');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (collisions.length !== collisionIndex) {
|
||||||
|
collisions.length = collisionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
return collisions;
|
return collisions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -180,6 +187,13 @@ var Collision = require('./Collision');
|
||||||
* @default []
|
* @default []
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The array of `Matter.Collision` found in the last call to `Detector.collisions` on this detector.
|
||||||
|
* @property collisions
|
||||||
|
* @type collision[]
|
||||||
|
* @default []
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional. A `Matter.Pairs` object from which previous collision objects may be reused. Intended for internal `Matter.Engine` usage.
|
* Optional. A `Matter.Pairs` object from which previous collision objects may be reused. Intended for internal `Matter.Engine` usage.
|
||||||
* @property pairs
|
* @property pairs
|
||||||
|
|
|
@ -28,11 +28,10 @@ var Contact = require('./Contact');
|
||||||
bodyA: bodyA,
|
bodyA: bodyA,
|
||||||
bodyB: bodyB,
|
bodyB: bodyB,
|
||||||
collision: collision,
|
collision: collision,
|
||||||
contacts: [],
|
contacts: [Contact.create(), Contact.create()],
|
||||||
activeContacts: [],
|
contactCount: 0,
|
||||||
separation: 0,
|
separation: 0,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
confirmedActive: true,
|
|
||||||
isSensor: bodyA.isSensor || bodyB.isSensor,
|
isSensor: bodyA.isSensor || bodyB.isSensor,
|
||||||
timeCreated: timestamp,
|
timeCreated: timestamp,
|
||||||
timeUpdated: timestamp,
|
timeUpdated: timestamp,
|
||||||
|
@ -56,12 +55,11 @@ var Contact = require('./Contact');
|
||||||
* @param {number} timestamp
|
* @param {number} timestamp
|
||||||
*/
|
*/
|
||||||
Pair.update = function(pair, collision, timestamp) {
|
Pair.update = function(pair, collision, timestamp) {
|
||||||
var contacts = pair.contacts,
|
var supports = collision.supports,
|
||||||
supports = collision.supports,
|
supportCount = collision.supportCount,
|
||||||
activeContacts = pair.activeContacts,
|
contacts = pair.contacts,
|
||||||
parentA = collision.parentA,
|
parentA = collision.parentA,
|
||||||
parentB = collision.parentB,
|
parentB = collision.parentB;
|
||||||
parentAVerticesLength = parentA.vertices.length;
|
|
||||||
|
|
||||||
pair.isActive = true;
|
pair.isActive = true;
|
||||||
pair.timeUpdated = timestamp;
|
pair.timeUpdated = timestamp;
|
||||||
|
@ -73,20 +71,24 @@ var Contact = require('./Contact');
|
||||||
pair.restitution = parentA.restitution > parentB.restitution ? parentA.restitution : parentB.restitution;
|
pair.restitution = parentA.restitution > parentB.restitution ? parentA.restitution : parentB.restitution;
|
||||||
pair.slop = parentA.slop > parentB.slop ? parentA.slop : parentB.slop;
|
pair.slop = parentA.slop > parentB.slop ? parentA.slop : parentB.slop;
|
||||||
|
|
||||||
|
pair.contactCount = supportCount;
|
||||||
collision.pair = pair;
|
collision.pair = pair;
|
||||||
activeContacts.length = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < supports.length; i++) {
|
|
||||||
var support = supports[i],
|
|
||||||
contactId = support.body === parentA ? support.index : parentAVerticesLength + support.index,
|
|
||||||
contact = contacts[contactId];
|
|
||||||
|
|
||||||
if (contact) {
|
var supportA = supports[0],
|
||||||
activeContacts.push(contact);
|
contactA = contacts[0],
|
||||||
} else {
|
supportB = supports[1],
|
||||||
activeContacts.push(contacts[contactId] = Contact.create(support));
|
contactB = contacts[1];
|
||||||
}
|
|
||||||
|
// match contacts to supports
|
||||||
|
if (contactB.vertex === supportA || contactA.vertex === supportB) {
|
||||||
|
contacts[1] = contactA;
|
||||||
|
contacts[0] = contactA = contactB;
|
||||||
|
contactB = contacts[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update contacts
|
||||||
|
contactA.vertex = supportA;
|
||||||
|
contactB.vertex = supportB;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,7 +104,7 @@ var Contact = require('./Contact');
|
||||||
pair.timeUpdated = timestamp;
|
pair.timeUpdated = timestamp;
|
||||||
} else {
|
} else {
|
||||||
pair.isActive = false;
|
pair.isActive = false;
|
||||||
pair.activeContacts.length = 0;
|
pair.contactCount = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -114,11 +116,8 @@ var Contact = require('./Contact');
|
||||||
* @return {string} Unique pairId
|
* @return {string} Unique pairId
|
||||||
*/
|
*/
|
||||||
Pair.id = function(bodyA, bodyB) {
|
Pair.id = function(bodyA, bodyB) {
|
||||||
if (bodyA.id < bodyB.id) {
|
return bodyA.id < bodyB.id ? bodyA.id.toString(36) + ':' + bodyB.id.toString(36)
|
||||||
return 'A' + bodyA.id + 'B' + bodyB.id;
|
: bodyB.id.toString(36) + ':' + bodyA.id.toString(36);
|
||||||
} else {
|
|
||||||
return 'A' + bodyB.id + 'B' + bodyA.id;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -37,27 +37,24 @@ var Common = require('../core/Common');
|
||||||
* @param {number} timestamp
|
* @param {number} timestamp
|
||||||
*/
|
*/
|
||||||
Pairs.update = function(pairs, collisions, timestamp) {
|
Pairs.update = function(pairs, collisions, timestamp) {
|
||||||
var pairsList = pairs.list,
|
var pairUpdate = Pair.update,
|
||||||
pairsListLength = pairsList.length,
|
pairCreate = Pair.create,
|
||||||
|
pairSetActive = Pair.setActive,
|
||||||
pairsTable = pairs.table,
|
pairsTable = pairs.table,
|
||||||
collisionsLength = collisions.length,
|
pairsList = pairs.list,
|
||||||
|
pairsListLength = pairsList.length,
|
||||||
|
pairsListIndex = pairsListLength,
|
||||||
collisionStart = pairs.collisionStart,
|
collisionStart = pairs.collisionStart,
|
||||||
collisionEnd = pairs.collisionEnd,
|
collisionEnd = pairs.collisionEnd,
|
||||||
collisionActive = pairs.collisionActive,
|
collisionActive = pairs.collisionActive,
|
||||||
|
collisionsLength = collisions.length,
|
||||||
|
collisionStartIndex = 0,
|
||||||
|
collisionEndIndex = 0,
|
||||||
|
collisionActiveIndex = 0,
|
||||||
collision,
|
collision,
|
||||||
pairIndex,
|
|
||||||
pair,
|
pair,
|
||||||
i;
|
i;
|
||||||
|
|
||||||
// clear collision state arrays, but maintain old reference
|
|
||||||
collisionStart.length = 0;
|
|
||||||
collisionEnd.length = 0;
|
|
||||||
collisionActive.length = 0;
|
|
||||||
|
|
||||||
for (i = 0; i < pairsListLength; i++) {
|
|
||||||
pairsList[i].confirmedActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < collisionsLength; i++) {
|
for (i = 0; i < collisionsLength; i++) {
|
||||||
collision = collisions[i];
|
collision = collisions[i];
|
||||||
pair = collision.pair;
|
pair = collision.pair;
|
||||||
|
@ -66,49 +63,62 @@ var Common = require('../core/Common');
|
||||||
// pair already exists (but may or may not be active)
|
// pair already exists (but may or may not be active)
|
||||||
if (pair.isActive) {
|
if (pair.isActive) {
|
||||||
// pair exists and is active
|
// pair exists and is active
|
||||||
collisionActive.push(pair);
|
collisionActive[collisionActiveIndex++] = pair;
|
||||||
} else {
|
|
||||||
// pair exists but was inactive, so a collision has just started again
|
|
||||||
collisionStart.push(pair);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the pair
|
// update the pair
|
||||||
Pair.update(pair, collision, timestamp);
|
pairUpdate(pair, collision, timestamp);
|
||||||
pair.confirmedActive = true;
|
|
||||||
} else {
|
} else {
|
||||||
// pair did not exist, create a new pair
|
// pair did not exist, create a new pair
|
||||||
pair = Pair.create(collision, timestamp);
|
pair = pairCreate(collision, timestamp);
|
||||||
pairsTable[pair.id] = pair;
|
pairsTable[pair.id] = pair;
|
||||||
|
|
||||||
// push the new pair
|
// add the new pair
|
||||||
collisionStart.push(pair);
|
collisionStart[collisionStartIndex++] = pair;
|
||||||
pairsList.push(pair);
|
pairsList[pairsListIndex++] = pair;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// find pairs that are no longer active
|
// find pairs that are no longer active
|
||||||
var removePairIndex = [];
|
pairsListIndex = 0;
|
||||||
pairsListLength = pairsList.length;
|
pairsListLength = pairsList.length;
|
||||||
|
|
||||||
for (i = 0; i < pairsListLength; i++) {
|
for (i = 0; i < pairsListLength; i++) {
|
||||||
pair = pairsList[i];
|
pair = pairsList[i];
|
||||||
|
|
||||||
if (!pair.confirmedActive) {
|
// pair is active if updated this timestep
|
||||||
Pair.setActive(pair, false, timestamp);
|
if (pair.timeUpdated >= timestamp) {
|
||||||
collisionEnd.push(pair);
|
// keep active pairs
|
||||||
|
pairsList[pairsListIndex++] = pair;
|
||||||
|
} else {
|
||||||
|
pairSetActive(pair, false, timestamp);
|
||||||
|
|
||||||
if (!pair.collision.bodyA.isSleeping && !pair.collision.bodyB.isSleeping) {
|
// keep inactive pairs if both bodies may be sleeping
|
||||||
removePairIndex.push(i);
|
if (pair.collision.bodyA.sleepCounter > 0 && pair.collision.bodyB.sleepCounter > 0) {
|
||||||
|
pairsList[pairsListIndex++] = pair;
|
||||||
|
} else {
|
||||||
|
// remove inactive pairs if either body awake
|
||||||
|
collisionEnd[collisionEndIndex++] = pair;
|
||||||
|
delete pairsTable[pair.id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove inactive pairs
|
// update array lengths if changed
|
||||||
for (i = 0; i < removePairIndex.length; i++) {
|
if (pairsList.length !== pairsListIndex) {
|
||||||
pairIndex = removePairIndex[i] - i;
|
pairsList.length = pairsListIndex;
|
||||||
pair = pairsList[pairIndex];
|
}
|
||||||
pairsList.splice(pairIndex, 1);
|
|
||||||
delete pairsTable[pair.id];
|
if (collisionStart.length !== collisionStartIndex) {
|
||||||
|
collisionStart.length = collisionStartIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collisionEnd.length !== collisionEndIndex) {
|
||||||
|
collisionEnd.length = collisionEndIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collisionActive.length !== collisionActiveIndex) {
|
||||||
|
collisionActive.length = collisionActiveIndex;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ var Bounds = require('../geometry/Bounds');
|
||||||
Resolver.preSolvePosition = function(pairs) {
|
Resolver.preSolvePosition = function(pairs) {
|
||||||
var i,
|
var i,
|
||||||
pair,
|
pair,
|
||||||
activeCount,
|
contactCount,
|
||||||
pairsLength = pairs.length;
|
pairsLength = pairs.length;
|
||||||
|
|
||||||
// find total contacts on each body
|
// find total contacts on each body
|
||||||
|
@ -39,9 +39,9 @@ var Bounds = require('../geometry/Bounds');
|
||||||
if (!pair.isActive)
|
if (!pair.isActive)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
activeCount = pair.activeContacts.length;
|
contactCount = pair.contactCount;
|
||||||
pair.collision.parentA.totalContacts += activeCount;
|
pair.collision.parentA.totalContacts += contactCount;
|
||||||
pair.collision.parentB.totalContacts += activeCount;
|
pair.collision.parentB.totalContacts += contactCount;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,8 +79,8 @@ var Bounds = require('../geometry/Bounds');
|
||||||
|
|
||||||
// get current separation between body edges involved in collision
|
// get current separation between body edges involved in collision
|
||||||
pair.separation =
|
pair.separation =
|
||||||
normal.x * (bodyB.positionImpulse.x + collision.penetration.x - bodyA.positionImpulse.x)
|
collision.depth + normal.x * (bodyB.positionImpulse.x - bodyA.positionImpulse.x)
|
||||||
+ normal.y * (bodyB.positionImpulse.y + collision.penetration.y - bodyA.positionImpulse.y);
|
+ normal.y * (bodyB.positionImpulse.y - bodyA.positionImpulse.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < pairsLength; i++) {
|
for (i = 0; i < pairsLength; i++) {
|
||||||
|
@ -176,8 +176,8 @@ var Bounds = require('../geometry/Bounds');
|
||||||
if (!pair.isActive || pair.isSensor)
|
if (!pair.isActive || pair.isSensor)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var contacts = pair.activeContacts,
|
var contacts = pair.contacts,
|
||||||
contactsLength = contacts.length,
|
contactCount = pair.contactCount,
|
||||||
collision = pair.collision,
|
collision = pair.collision,
|
||||||
bodyA = collision.parentA,
|
bodyA = collision.parentA,
|
||||||
bodyB = collision.parentB,
|
bodyB = collision.parentB,
|
||||||
|
@ -185,7 +185,7 @@ var Bounds = require('../geometry/Bounds');
|
||||||
tangent = collision.tangent;
|
tangent = collision.tangent;
|
||||||
|
|
||||||
// resolve each contact
|
// resolve each contact
|
||||||
for (j = 0; j < contactsLength; j++) {
|
for (j = 0; j < contactCount; j++) {
|
||||||
var contact = contacts[j],
|
var contact = contacts[j],
|
||||||
contactVertex = contact.vertex,
|
contactVertex = contact.vertex,
|
||||||
normalImpulse = contact.normalImpulse,
|
normalImpulse = contact.normalImpulse,
|
||||||
|
@ -248,28 +248,26 @@ var Bounds = require('../geometry/Bounds');
|
||||||
var collision = pair.collision,
|
var collision = pair.collision,
|
||||||
bodyA = collision.parentA,
|
bodyA = collision.parentA,
|
||||||
bodyB = collision.parentB,
|
bodyB = collision.parentB,
|
||||||
bodyAVelocity = bodyA.velocity,
|
|
||||||
bodyBVelocity = bodyB.velocity,
|
|
||||||
normalX = collision.normal.x,
|
normalX = collision.normal.x,
|
||||||
normalY = collision.normal.y,
|
normalY = collision.normal.y,
|
||||||
tangentX = collision.tangent.x,
|
tangentX = collision.tangent.x,
|
||||||
tangentY = collision.tangent.y,
|
tangentY = collision.tangent.y,
|
||||||
contacts = pair.activeContacts,
|
inverseMassTotal = pair.inverseMass,
|
||||||
contactsLength = contacts.length,
|
friction = pair.friction * pair.frictionStatic * frictionNormalMultiplier,
|
||||||
contactShare = 1 / contactsLength,
|
contacts = pair.contacts,
|
||||||
inverseMassTotal = bodyA.inverseMass + bodyB.inverseMass,
|
contactCount = pair.contactCount,
|
||||||
friction = pair.friction * pair.frictionStatic * frictionNormalMultiplier;
|
contactShare = 1 / contactCount;
|
||||||
|
|
||||||
// update body velocities
|
// get body velocities
|
||||||
bodyAVelocity.x = bodyA.position.x - bodyA.positionPrev.x;
|
var bodyAVelocityX = bodyA.position.x - bodyA.positionPrev.x,
|
||||||
bodyAVelocity.y = bodyA.position.y - bodyA.positionPrev.y;
|
bodyAVelocityY = bodyA.position.y - bodyA.positionPrev.y,
|
||||||
bodyBVelocity.x = bodyB.position.x - bodyB.positionPrev.x;
|
bodyAAngularVelocity = bodyA.angle - bodyA.anglePrev,
|
||||||
bodyBVelocity.y = bodyB.position.y - bodyB.positionPrev.y;
|
bodyBVelocityX = bodyB.position.x - bodyB.positionPrev.x,
|
||||||
bodyA.angularVelocity = bodyA.angle - bodyA.anglePrev;
|
bodyBVelocityY = bodyB.position.y - bodyB.positionPrev.y,
|
||||||
bodyB.angularVelocity = bodyB.angle - bodyB.anglePrev;
|
bodyBAngularVelocity = bodyB.angle - bodyB.anglePrev;
|
||||||
|
|
||||||
// resolve each contact
|
// resolve each contact
|
||||||
for (j = 0; j < contactsLength; j++) {
|
for (j = 0; j < contactCount; j++) {
|
||||||
var contact = contacts[j],
|
var contact = contacts[j],
|
||||||
contactVertex = contact.vertex;
|
contactVertex = contact.vertex;
|
||||||
|
|
||||||
|
@ -278,10 +276,10 @@ var Bounds = require('../geometry/Bounds');
|
||||||
offsetBX = contactVertex.x - bodyB.position.x,
|
offsetBX = contactVertex.x - bodyB.position.x,
|
||||||
offsetBY = contactVertex.y - bodyB.position.y;
|
offsetBY = contactVertex.y - bodyB.position.y;
|
||||||
|
|
||||||
var velocityPointAX = bodyAVelocity.x - offsetAY * bodyA.angularVelocity,
|
var velocityPointAX = bodyAVelocityX - offsetAY * bodyAAngularVelocity,
|
||||||
velocityPointAY = bodyAVelocity.y + offsetAX * bodyA.angularVelocity,
|
velocityPointAY = bodyAVelocityY + offsetAX * bodyAAngularVelocity,
|
||||||
velocityPointBX = bodyBVelocity.x - offsetBY * bodyB.angularVelocity,
|
velocityPointBX = bodyBVelocityX - offsetBY * bodyBAngularVelocity,
|
||||||
velocityPointBY = bodyBVelocity.y + offsetBX * bodyB.angularVelocity;
|
velocityPointBY = bodyBVelocityY + offsetBX * bodyBAngularVelocity;
|
||||||
|
|
||||||
var relativeVelocityX = velocityPointAX - velocityPointBX,
|
var relativeVelocityX = velocityPointAX - velocityPointBX,
|
||||||
relativeVelocityY = velocityPointAY - velocityPointBY;
|
relativeVelocityY = velocityPointAY - velocityPointBY;
|
||||||
|
|
|
@ -63,6 +63,7 @@ var Body = require('../body/Body');
|
||||||
engine.world = options.world || Composite.create({ label: 'World' });
|
engine.world = options.world || Composite.create({ label: 'World' });
|
||||||
engine.pairs = options.pairs || Pairs.create();
|
engine.pairs = options.pairs || Pairs.create();
|
||||||
engine.detector = options.detector || Detector.create();
|
engine.detector = options.detector || Detector.create();
|
||||||
|
engine.detector.pairs = engine.pairs;
|
||||||
|
|
||||||
// for temporary back compatibility only
|
// for temporary back compatibility only
|
||||||
engine.grid = { buckets: [] };
|
engine.grid = { buckets: [] };
|
||||||
|
@ -148,7 +149,6 @@ var Body = require('../body/Body');
|
||||||
Constraint.postSolveAll(allBodies);
|
Constraint.postSolveAll(allBodies);
|
||||||
|
|
||||||
// find all collisions
|
// find all collisions
|
||||||
detector.pairs = engine.pairs;
|
|
||||||
var collisions = Detector.collisions(detector);
|
var collisions = Detector.collisions(detector);
|
||||||
|
|
||||||
// update collision pairs
|
// update collision pairs
|
||||||
|
|
|
@ -1222,8 +1222,8 @@ var Mouse = require('../core/Mouse');
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
collision = pair.collision;
|
collision = pair.collision;
|
||||||
for (j = 0; j < pair.activeContacts.length; j++) {
|
for (j = 0; j < pair.contactCount; j++) {
|
||||||
var contact = pair.activeContacts[j],
|
var contact = pair.contacts[j],
|
||||||
vertex = contact.vertex;
|
vertex = contact.vertex;
|
||||||
c.rect(vertex.x - 1.5, vertex.y - 1.5, 3.5, 3.5);
|
c.rect(vertex.x - 1.5, vertex.y - 1.5, 3.5, 3.5);
|
||||||
}
|
}
|
||||||
|
@ -1247,13 +1247,13 @@ var Mouse = require('../core/Mouse');
|
||||||
|
|
||||||
collision = pair.collision;
|
collision = pair.collision;
|
||||||
|
|
||||||
if (pair.activeContacts.length > 0) {
|
if (pair.contactCount > 0) {
|
||||||
var normalPosX = pair.activeContacts[0].vertex.x,
|
var normalPosX = pair.contacts[0].vertex.x,
|
||||||
normalPosY = pair.activeContacts[0].vertex.y;
|
normalPosY = pair.contacts[0].vertex.y;
|
||||||
|
|
||||||
if (pair.activeContacts.length === 2) {
|
if (pair.contactCount === 2) {
|
||||||
normalPosX = (pair.activeContacts[0].vertex.x + pair.activeContacts[1].vertex.x) / 2;
|
normalPosX = (pair.contacts[0].vertex.x + pair.contacts[1].vertex.x) / 2;
|
||||||
normalPosY = (pair.activeContacts[0].vertex.y + pair.activeContacts[1].vertex.y) / 2;
|
normalPosY = (pair.contacts[0].vertex.y + pair.contacts[1].vertex.y) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collision.bodyB === collision.supports[0].body || collision.bodyA.isStatic === true) {
|
if (collision.bodyB === collision.supports[0].body || collision.bodyA.isStatic === true) {
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const mock = require('mock-require');
|
const mock = require('mock-require');
|
||||||
const { requireUncached, serialize } = require('./TestTools');
|
const { requireUncached, serialize, smoothExp } = require('./TestTools');
|
||||||
const consoleOriginal = global.console;
|
const consoleOriginal = global.console;
|
||||||
|
const DateOriginal = global.Date;
|
||||||
|
|
||||||
const runExample = options => {
|
const runExample = options => {
|
||||||
const {
|
const {
|
||||||
|
@ -13,8 +14,8 @@ const runExample = options => {
|
||||||
frameCallbacks
|
frameCallbacks
|
||||||
} = prepareEnvironment(options);
|
} = prepareEnvironment(options);
|
||||||
|
|
||||||
let totalMemory = 0;
|
let memoryDeltaAverage = 0;
|
||||||
let totalDuration = 0;
|
let timeDeltaAverage = 0;
|
||||||
let overlapTotal = 0;
|
let overlapTotal = 0;
|
||||||
let overlapCount = 0;
|
let overlapCount = 0;
|
||||||
let i;
|
let i;
|
||||||
|
@ -24,6 +25,12 @@ const runExample = options => {
|
||||||
let runner;
|
let runner;
|
||||||
let engine;
|
let engine;
|
||||||
let render;
|
let render;
|
||||||
|
let extrinsicCapture;
|
||||||
|
|
||||||
|
const pairOverlap = (pair) => {
|
||||||
|
const collision = Matter.Collision.collides(pair.bodyA, pair.bodyB);
|
||||||
|
return collision ? Math.max(collision.depth - pair.slop, 0) : -1;
|
||||||
|
};
|
||||||
|
|
||||||
for (i = 0; i < options.repeats; i += 1) {
|
for (i = 0; i < options.repeats; i += 1) {
|
||||||
if (global.gc) {
|
if (global.gc) {
|
||||||
|
@ -36,35 +43,57 @@ const runExample = options => {
|
||||||
runner = example.runner;
|
runner = example.runner;
|
||||||
engine = example.engine;
|
engine = example.engine;
|
||||||
render = example.render;
|
render = example.render;
|
||||||
|
|
||||||
for (j = 0; j < options.updates; j += 1) {
|
for (j = 0; j < options.updates; j += 1) {
|
||||||
const time = j * runner.delta;
|
const time = j * runner.delta;
|
||||||
const callbackCount = frameCallbacks.length;
|
const callbackCount = frameCallbacks.length;
|
||||||
|
|
||||||
|
global.timeNow = time;
|
||||||
|
|
||||||
for (let p = 0; p < callbackCount; p += 1) {
|
for (let p = 0; p < callbackCount; p += 1) {
|
||||||
totalMemory += process.memoryUsage().heapUsed;
|
const frameCallback = frameCallbacks.shift();
|
||||||
const callback = frameCallbacks.shift();
|
const memoryBefore = process.memoryUsage().heapUsed;
|
||||||
const startTime = process.hrtime();
|
const timeBefore = process.hrtime();
|
||||||
|
|
||||||
callback(time);
|
frameCallback(time);
|
||||||
|
|
||||||
const duration = process.hrtime(startTime);
|
const timeDuration = process.hrtime(timeBefore);
|
||||||
totalMemory += process.memoryUsage().heapUsed;
|
const timeDelta = timeDuration[0] * 1e9 + timeDuration[1];
|
||||||
totalDuration += duration[0] * 1e9 + duration[1];
|
const memoryAfter = process.memoryUsage().heapUsed;
|
||||||
|
const memoryDelta = Math.max(memoryAfter - memoryBefore, 0);
|
||||||
|
|
||||||
|
memoryDeltaAverage = smoothExp(memoryDeltaAverage, memoryDelta);
|
||||||
|
timeDeltaAverage = smoothExp(timeDeltaAverage, timeDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let overlapTotalUpdate = 0;
|
||||||
|
let overlapCountUpdate = 0;
|
||||||
|
|
||||||
const pairsList = engine.pairs.list;
|
const pairsList = engine.pairs.list;
|
||||||
const pairsListLength = engine.pairs.list.length;
|
const pairsListLength = engine.pairs.list.length;
|
||||||
|
|
||||||
for (let p = 0; p < pairsListLength; p += 1) {
|
for (let p = 0; p < pairsListLength; p += 1) {
|
||||||
const pair = pairsList[p];
|
const pair = pairsList[p];
|
||||||
const separation = pair.separation - pair.slop;
|
|
||||||
|
|
||||||
if (pair.isActive && !pair.isSensor) {
|
if (pair.isActive && !pair.isSensor){
|
||||||
overlapTotal += separation > 0 ? separation : 0;
|
const overlap = pairOverlap(pair);
|
||||||
overlapCount += 1;
|
|
||||||
|
if (overlap >= 0) {
|
||||||
|
overlapTotalUpdate += overlap;
|
||||||
|
overlapCountUpdate += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (overlapCountUpdate > 0) {
|
||||||
|
overlapTotal += overlapTotalUpdate / overlapCountUpdate;
|
||||||
|
overlapCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extrinsicCapture && engine.timing.timestamp >= 1000) {
|
||||||
|
extrinsicCapture = captureExtrinsics(engine, Matter);
|
||||||
|
extrinsicCapture.updates = j;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,13 +101,13 @@ const runExample = options => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: options.name,
|
name: options.name,
|
||||||
duration: totalDuration,
|
duration: timeDeltaAverage,
|
||||||
|
memory: memoryDeltaAverage,
|
||||||
overlap: overlapTotal / (overlapCount || 1),
|
overlap: overlapTotal / (overlapCount || 1),
|
||||||
memory: totalMemory,
|
extrinsic: extrinsicCapture,
|
||||||
logs: logs,
|
|
||||||
extrinsic: captureExtrinsics(engine, Matter),
|
|
||||||
intrinsic: captureIntrinsics(engine, Matter),
|
intrinsic: captureIntrinsics(engine, Matter),
|
||||||
state: captureState(engine, runner, render)
|
state: captureState(engine, runner, render),
|
||||||
|
logs
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -144,6 +173,7 @@ const prepareEnvironment = options => {
|
||||||
const frameCallbacks = [];
|
const frameCallbacks = [];
|
||||||
|
|
||||||
global.document = global.window = {
|
global.document = global.window = {
|
||||||
|
performance: {},
|
||||||
addEventListener: () => {},
|
addEventListener: () => {},
|
||||||
requestAnimationFrame: callback => {
|
requestAnimationFrame: callback => {
|
||||||
frameCallbacks.push(callback);
|
frameCallbacks.push(callback);
|
||||||
|
@ -174,6 +204,21 @@ const prepareEnvironment = options => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
global.Math.random = () => {
|
||||||
|
throw new Error("Math.random was called during tests, output can not be compared.");
|
||||||
|
};
|
||||||
|
|
||||||
|
global.timeNow = 0;
|
||||||
|
|
||||||
|
global.window.performance.now = () => global.timeNow;
|
||||||
|
|
||||||
|
global.Date = function() {
|
||||||
|
this.toString = () => global.timeNow.toString();
|
||||||
|
this.valueOf = () => global.timeNow;
|
||||||
|
};
|
||||||
|
|
||||||
|
global.Date.now = () => global.timeNow;
|
||||||
|
|
||||||
const Matter = prepareMatter(options);
|
const Matter = prepareMatter(options);
|
||||||
mock('matter-js', Matter);
|
mock('matter-js', Matter);
|
||||||
global.Matter = Matter;
|
global.Matter = Matter;
|
||||||
|
@ -187,6 +232,7 @@ const prepareEnvironment = options => {
|
||||||
|
|
||||||
const resetEnvironment = () => {
|
const resetEnvironment = () => {
|
||||||
global.console = consoleOriginal;
|
global.console = consoleOriginal;
|
||||||
|
global.Date = DateOriginal;
|
||||||
global.window = undefined;
|
global.window = undefined;
|
||||||
global.document = undefined;
|
global.document = undefined;
|
||||||
global.Matter = undefined;
|
global.Matter = undefined;
|
||||||
|
@ -195,46 +241,22 @@ const resetEnvironment = () => {
|
||||||
|
|
||||||
const captureExtrinsics = ({ world }, Matter) => ({
|
const captureExtrinsics = ({ world }, Matter) => ({
|
||||||
bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => {
|
bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => {
|
||||||
bodies[body.id] = [
|
bodies[body.id] = {
|
||||||
body.position.x,
|
position: { x: body.position.x, y: body.position.y },
|
||||||
body.position.y,
|
vertices: body.vertices.map(vertex => ({ x: vertex.x, y: vertex.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;
|
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({
|
const captureIntrinsics = ({ world, timing }, Matter) => serialize({
|
||||||
|
engine: {
|
||||||
|
timing: {
|
||||||
|
timeScale: timing.timeScale,
|
||||||
|
timestamp: timing.timestamp
|
||||||
|
}
|
||||||
|
},
|
||||||
bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => {
|
bodies: Matter.Composite.allBodies(world).reduce((bodies, body) => {
|
||||||
bodies[body.id] = body;
|
bodies[body.id] = body;
|
||||||
return bodies;
|
return bodies;
|
||||||
|
@ -289,6 +311,9 @@ const extrinsicProperties = [
|
||||||
'velocity',
|
'velocity',
|
||||||
'position',
|
'position',
|
||||||
'positionPrev',
|
'positionPrev',
|
||||||
|
'motion',
|
||||||
|
'sleepCounter',
|
||||||
|
'positionImpulse'
|
||||||
];
|
];
|
||||||
|
|
||||||
const excludeStateProperties = [
|
const excludeStateProperties = [
|
||||||
|
|
|
@ -17,13 +17,14 @@ const {
|
||||||
const Example = requireUncached('../examples/index');
|
const Example = requireUncached('../examples/index');
|
||||||
const MatterBuild = requireUncached('../build/matter');
|
const MatterBuild = requireUncached('../build/matter');
|
||||||
const { versionSatisfies } = requireUncached('../src/core/Plugin');
|
const { versionSatisfies } = requireUncached('../src/core/Plugin');
|
||||||
const Worker = require('jest-worker').default;
|
const Worker = require('jest-worker').Worker;
|
||||||
|
|
||||||
const testComparison = getArg('compare', null) === 'true';
|
const testComparison = getArg('compare', null) === 'true';
|
||||||
const saveComparison = getArg('save', null) === 'true';
|
const saveComparison = getArg('save', null) === 'true';
|
||||||
const specificExamples = getArg('examples', null, (val) => val.split(','));
|
const specificExamples = getArg('examples', null, (val) => val.split(','));
|
||||||
const repeats = getArg('repeats', 1, parseFloat);
|
const repeats = getArg('repeats', 1, parseFloat);
|
||||||
const updates = getArg('updates', 150, parseFloat);
|
const updates = getArg('updates', 150, parseFloat);
|
||||||
|
const benchmark = getArg('benchmark', null) === 'true';
|
||||||
|
|
||||||
const excludeExamples = ['svg', 'terrain'];
|
const excludeExamples = ['svg', 'terrain'];
|
||||||
const excludeJitter = ['stack', 'circleStack', 'restitution', 'staticFriction', 'friction', 'newtonsCradle', 'catapult'];
|
const excludeJitter = ['stack', 'circleStack', 'restitution', 'staticFriction', 'friction', 'newtonsCradle', 'catapult'];
|
||||||
|
@ -37,68 +38,26 @@ const examples = (specificExamples || Object.keys(Example)).filter(key => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const captureExamples = async useDev => {
|
const captureExamples = async useDev => {
|
||||||
const multiThreadWorker = new Worker(require.resolve('./ExampleWorker'), {
|
const worker = new Worker(require.resolve('./ExampleWorker'), {
|
||||||
enableWorkerThreads: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const overlapRuns = await Promise.all(examples.map(name => multiThreadWorker.runExample({
|
|
||||||
name,
|
|
||||||
useDev,
|
|
||||||
updates: 2,
|
|
||||||
repeats: 1,
|
|
||||||
stableSort: true,
|
|
||||||
jitter: excludeJitter.includes(name) ? 0 : 1e-10
|
|
||||||
})));
|
|
||||||
|
|
||||||
const behaviourRuns = await Promise.all(examples.map(name => multiThreadWorker.runExample({
|
|
||||||
name,
|
|
||||||
useDev,
|
|
||||||
updates: 2,
|
|
||||||
repeats: 1,
|
|
||||||
stableSort: true,
|
|
||||||
jitter: excludeJitter.includes(name) ? 0 : 1e-10
|
|
||||||
})));
|
|
||||||
|
|
||||||
const similarityRuns = await Promise.all(examples.map(name => multiThreadWorker.runExample({
|
|
||||||
name,
|
|
||||||
useDev,
|
|
||||||
updates: 2,
|
|
||||||
repeats: 1,
|
|
||||||
stableSort: false,
|
|
||||||
jitter: excludeJitter.includes(name) ? 0 : 1e-10
|
|
||||||
})));
|
|
||||||
|
|
||||||
await multiThreadWorker.end();
|
|
||||||
|
|
||||||
const singleThreadWorker = new Worker(require.resolve('./ExampleWorker'), {
|
|
||||||
enableWorkerThreads: true,
|
enableWorkerThreads: true,
|
||||||
numWorkers: 1
|
numWorkers: benchmark ? 1 : undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
const completeRuns = await Promise.all(examples.map(name => singleThreadWorker.runExample({
|
const completeRuns = await Promise.all(examples.map(name => worker.runExample({
|
||||||
name,
|
name,
|
||||||
useDev,
|
useDev,
|
||||||
updates: updates,
|
updates: updates,
|
||||||
repeats: repeats,
|
repeats: benchmark ? Math.max(repeats, 3) : repeats,
|
||||||
stableSort: false,
|
stableSort: false,
|
||||||
jitter: excludeJitter.includes(name) ? 0 : 1e-10
|
jitter: excludeJitter.includes(name) ? 0 : 1e-10
|
||||||
})));
|
})));
|
||||||
|
|
||||||
await singleThreadWorker.end();
|
await worker.end();
|
||||||
|
|
||||||
const capture = {};
|
const capture = {};
|
||||||
|
|
||||||
for (const completeRun of completeRuns) {
|
for (const completeRun of completeRuns) {
|
||||||
const behaviourRun = behaviourRuns.find(({ name }) => name === completeRun.name);
|
capture[completeRun.name] = completeRun;
|
||||||
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;
|
return capture;
|
||||||
|
@ -119,7 +78,7 @@ afterAll(async () => {
|
||||||
'Examples ran against previous release and current build\n\n'
|
'Examples ran against previous release and current build\n\n'
|
||||||
+ logReport(build, `release`) + '\n'
|
+ logReport(build, `release`) + '\n'
|
||||||
+ logReport(dev, `current`) + '\n'
|
+ logReport(dev, `current`) + '\n'
|
||||||
+ comparisonReport(dev, build, devSize, buildSize, MatterBuild.version, saveComparison)
|
+ comparisonReport(dev, build, devSize, buildSize, MatterBuild.version, saveComparison, benchmark)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,25 +8,26 @@ const comparePath = './test/__compare__';
|
||||||
const compareCommand = 'open http://localhost:8000/?compare';
|
const compareCommand = 'open http://localhost:8000/?compare';
|
||||||
const diffSaveCommand = 'npm run test-save';
|
const diffSaveCommand = 'npm run test-save';
|
||||||
const diffCommand = 'code -n -d test/__compare__/examples-build.json test/__compare__/examples-dev.json';
|
const diffCommand = 'code -n -d test/__compare__/examples-build.json test/__compare__/examples-dev.json';
|
||||||
const equalityThreshold = 0.99999;
|
const equalityThreshold = 1;
|
||||||
const colors = { Red: 31, Green: 32, Yellow: 33, White: 37, BrightWhite: 90, BrightCyan: 36 };
|
const colors = { Red: 31, Green: 32, Yellow: 33, White: 37, BrightWhite: 90, BrightCyan: 36 };
|
||||||
|
|
||||||
const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildVersion, save) => {
|
const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildVersion, save, benchmark) => {
|
||||||
const performanceDev = capturePerformanceTotals(capturesDev);
|
const {
|
||||||
const performanceBuild = capturePerformanceTotals(capturesBuild);
|
durationChange,
|
||||||
|
memoryChange,
|
||||||
|
overlapChange
|
||||||
|
} = captureBenchmark(capturesDev, capturesBuild);
|
||||||
|
|
||||||
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;
|
const filesizeChange = (devSize / buildSize) - 1;
|
||||||
|
|
||||||
const behaviourSimilaritys = extrinsicSimilarity(capturesDev, capturesBuild, 'behaviourExtrinsic');
|
const firstCapture = Object.entries(capturesDev)[0][1];
|
||||||
const behaviourSimilarityAverage = extrinsicSimilarityAverage(behaviourSimilaritys);
|
const updates = firstCapture.extrinsic.updates;
|
||||||
const behaviourSimilarityEntries = Object.entries(behaviourSimilaritys);
|
|
||||||
behaviourSimilarityEntries.sort((a, b) => a[1] - b[1]);
|
|
||||||
|
|
||||||
const similaritys = extrinsicSimilarity(capturesDev, capturesBuild, 'similarityExtrinsic');
|
const similaritys = extrinsicSimilarity(capturesDev, capturesBuild);
|
||||||
const similarityAverage = extrinsicSimilarityAverage(similaritys);
|
const similarityAverage = extrinsicSimilarityAverage(similaritys);
|
||||||
|
const similarityAveragePerUpdate = Math.pow(1, -1 / updates) * Math.pow(similarityAverage, 1 / updates);
|
||||||
|
const similarityEntries = Object.entries(similaritys);
|
||||||
|
similarityEntries.sort((a, b) => a[1] - b[1]);
|
||||||
|
|
||||||
const devIntrinsicsChanged = {};
|
const devIntrinsicsChanged = {};
|
||||||
const buildIntrinsicsChanged = {};
|
const buildIntrinsicsChanged = {};
|
||||||
|
@ -50,32 +51,31 @@ const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildV
|
||||||
.sort((a, b) => a.name.localeCompare(b.name));
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
const report = (breakEvery, format) => [
|
const report = (breakEvery, format) => [
|
||||||
[`Output comparison of ${behaviourSimilarityEntries.length}`,
|
[`Output sample comparison estimates of ${similarityEntries.length} examples`,
|
||||||
`examples against previous release ${format('matter-js@' + buildVersion, colors.Yellow)}`
|
`against previous release ${format('matter-js@' + buildVersion, colors.Yellow)}:`
|
||||||
].join(' '),
|
].join(' '),
|
||||||
|
|
||||||
`\n\n${format('Behaviour ', colors.White)}`,
|
`\n\n${format(`Similarity`, colors.White)} `,
|
||||||
`${format(formatPercent(behaviourSimilarityAverage), behaviourSimilarityAverage === 1 ? colors.Green : colors.Yellow)}%`,
|
`${format(formatPercent(similarityAveragePerUpdate, false, true), formatColor(similarityAveragePerUpdate === 1))}% `,
|
||||||
|
|
||||||
` ${format('Similarity', colors.White)}`,
|
|
||||||
`${format(formatPercent(similarityAverage), similarityAverage === 1 ? colors.Green : colors.Yellow)}%`,
|
|
||||||
|
|
||||||
` ${format('Overlap', colors.White)}`,
|
` ${format('Overlap', colors.White)}`,
|
||||||
` ${format((overlapChange >= 0 ? '+' : '-') + formatPercent(overlapChange, true), overlapChange <= 0 ? colors.Green : colors.Yellow)}%`,
|
` ${format(formatPercent(overlapChange), formatColor(overlapChange <= 0))}%`,
|
||||||
|
|
||||||
`\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('Filesize', colors.White)}`,
|
||||||
`${format((filesizeChange >= 0 ? '+' : '-') + formatPercent(filesizeChange, true), filesizeChange <= 0 ? colors.Green : colors.Yellow)}%`,
|
`${format(formatPercent(filesizeChange), formatColor(filesizeChange <= 0))}%`,
|
||||||
`${format(`${(devSize / 1024).toPrecision(4)} KB`, colors.White)}`,
|
`${format(`${(devSize / 1024).toPrecision(4)} KB`, colors.White)}`,
|
||||||
|
|
||||||
|
...(benchmark ? [
|
||||||
|
`\n${format('Performance', colors.White)}`,
|
||||||
|
` ${format(formatPercent(durationChange), formatColor(durationChange >= 0))}%`,
|
||||||
|
|
||||||
|
` ${format('Memory', colors.White)} `,
|
||||||
|
` ${format(formatPercent(memoryChange), formatColor(memoryChange <= 0))}%`,
|
||||||
|
] : []),
|
||||||
|
|
||||||
captureSummary.reduce((output, p, i) => {
|
captureSummary.reduce((output, p, i) => {
|
||||||
output += `${p.name} `;
|
output += `${p.name} `;
|
||||||
output += `${similarityRatings(behaviourSimilaritys[p.name])} `;
|
output += `${similarityRatings(similaritys[p.name])} `;
|
||||||
output += `${changeRatings(capturesDev[p.name].changedIntrinsics)} `;
|
output += `${changeRatings(capturesDev[p.name].changedIntrinsics)} `;
|
||||||
if (i > 0 && i < captureSummary.length && breakEvery > 0 && i % breakEvery === 0) {
|
if (i > 0 && i < captureSummary.length && breakEvery > 0 && i % breakEvery === 0) {
|
||||||
output += '\n';
|
output += '\n';
|
||||||
|
@ -83,9 +83,9 @@ const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildV
|
||||||
return output;
|
return output;
|
||||||
}, '\n\n'),
|
}, '\n\n'),
|
||||||
|
|
||||||
`\n\nwhere · no change ● extrinsics changed ◆ intrinsics changed\n`,
|
`\n\nwhere for the sample · no change detected ● extrinsics changed ◆ intrinsics changed\n`,
|
||||||
|
|
||||||
behaviourSimilarityAverage < 1 ? `\n${format('▶', colors.White)} ${format(compareCommand + '=' + 150 + '#' + behaviourSimilarityEntries[0][0], colors.BrightCyan)}` : '',
|
similarityAverage < 1 ? `\n${format('▶', colors.White)} ${format(compareCommand + '=' + 150 + '#' + similarityEntries[0][0], colors.BrightCyan)}` : '',
|
||||||
intrinsicChangeCount > 0 ? `\n${format('▶', colors.White)} ${format((save ? diffCommand : diffSaveCommand), colors.BrightCyan)}` : ''
|
intrinsicChangeCount > 0 ? `\n${format('▶', colors.White)} ${format((save ? diffCommand : diffSaveCommand), colors.BrightCyan)}` : ''
|
||||||
].join(' ');
|
].join(' ');
|
||||||
|
|
||||||
|
@ -98,17 +98,29 @@ const comparisonReport = (capturesDev, capturesBuild, devSize, buildSize, buildV
|
||||||
return report(5, color);
|
return report(5, color);
|
||||||
};
|
};
|
||||||
|
|
||||||
const similarity = (a, b) => {
|
|
||||||
const distance = Math.sqrt(a.reduce(
|
|
||||||
(sum, _val, i) => sum + Math.pow((a[i] || 0) - (b[i] || 0), 2), 0)
|
|
||||||
);
|
|
||||||
return 1 / (1 + (distance / a.length));
|
|
||||||
};
|
|
||||||
|
|
||||||
const similarityRatings = similarity => similarity < equalityThreshold ? color('●', colors.Yellow) : '·';
|
const similarityRatings = similarity => similarity < equalityThreshold ? color('●', colors.Yellow) : '·';
|
||||||
|
|
||||||
const changeRatings = isChanged => isChanged ? color('◆', colors.White) : '·';
|
const changeRatings = isChanged => isChanged ? color('◆', colors.White) : '·';
|
||||||
|
|
||||||
const color = (text, number) => number ? `\x1b[${number}m${text}\x1b[0m` : text;
|
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 formatColor = isGreen => isGreen ? colors.Green : colors.Yellow;
|
||||||
|
|
||||||
|
const formatPercent = (val, showSign=true, showFractional=false, padStart=6) => {
|
||||||
|
let fractionalSign = '';
|
||||||
|
|
||||||
|
if (showFractional && val > 0.9999 && val < 1) {
|
||||||
|
val = 0.9999;
|
||||||
|
fractionalSign = '>';
|
||||||
|
} else if (showFractional && val > 0 && val < 0.0001) {
|
||||||
|
val = 0.0001;
|
||||||
|
fractionalSign = '<';
|
||||||
|
}
|
||||||
|
|
||||||
|
const percentFixed = Math.abs(100 * val).toFixed(2);
|
||||||
|
const sign = parseFloat((100 * val).toFixed(2)) >= 0 ? '+' : '-';
|
||||||
|
return ((showFractional ? fractionalSign : '') + (showSign ? sign : '') + percentFixed).padStart(padStart, ' ');
|
||||||
|
};
|
||||||
|
|
||||||
const noiseThreshold = (val, threshold) => {
|
const noiseThreshold = (val, threshold) => {
|
||||||
const sign = val < 0 ? -1 : 1;
|
const sign = val < 0 ? -1 : 1;
|
||||||
|
@ -116,6 +128,38 @@ const noiseThreshold = (val, threshold) => {
|
||||||
return sign * Math.max(0, magnitude - threshold) / (1 - threshold);
|
return sign * Math.max(0, magnitude - threshold) / (1 - threshold);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const median = (values, lower, upper) => {
|
||||||
|
const valuesSorted = values.slice(0).sort();
|
||||||
|
|
||||||
|
return mean(valuesSorted.slice(
|
||||||
|
Math.floor(valuesSorted.length * lower),
|
||||||
|
Math.floor(valuesSorted.length * upper)
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
const mean = (values) => {
|
||||||
|
const valuesLength = values.length;
|
||||||
|
let result = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < valuesLength; i += 1) {
|
||||||
|
result += values[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result / valuesLength) || 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const smoothExp = (last, current) => {
|
||||||
|
const delta = current - last;
|
||||||
|
const sign = delta < 0 ? -1 : 1;
|
||||||
|
const magnitude = Math.abs(delta);
|
||||||
|
|
||||||
|
if (magnitude < 1) {
|
||||||
|
return last + 0.01 * delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
return last + Math.sqrt(magnitude) * sign;
|
||||||
|
};
|
||||||
|
|
||||||
const equals = (a, b) => {
|
const equals = (a, b) => {
|
||||||
try {
|
try {
|
||||||
expect(a).toEqual(b);
|
expect(a).toEqual(b);
|
||||||
|
@ -125,41 +169,77 @@ const equals = (a, b) => {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const capturePerformanceTotals = (captures) => {
|
const captureBenchmark = (capturesDev, capturesBuild) => {
|
||||||
const totals = {
|
const overlapChanges = [];
|
||||||
duration: 0,
|
|
||||||
overlap: 0,
|
let durationDev = 0;
|
||||||
memory: 0
|
let durationBuild = 0;
|
||||||
|
let memoryDev = 0;
|
||||||
|
let memoryBuild = 0;
|
||||||
|
|
||||||
|
for (const name in capturesDev) {
|
||||||
|
durationDev += capturesDev[name].duration;
|
||||||
|
durationBuild += capturesBuild[name].duration;
|
||||||
|
|
||||||
|
memoryDev += capturesDev[name].memory;
|
||||||
|
memoryBuild += capturesBuild[name].memory;
|
||||||
|
|
||||||
|
if (capturesBuild[name].overlap > 0.1 && capturesDev[name].overlap > 0.1){
|
||||||
|
overlapChanges.push(capturesDev[name].overlap / capturesBuild[name].overlap);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [ name ] of Object.entries(captures)) {
|
const durationChange = 1 - noiseThreshold(durationDev / durationBuild, 0.02);
|
||||||
totals.duration += captures[name].duration;
|
const memoryChange = noiseThreshold(memoryDev / memoryBuild, 0.02) - 1;
|
||||||
totals.overlap += captures[name].overlap;
|
const overlapChange = noiseThreshold(median(overlapChanges, 0.45, 0.55), 0.001) - 1;
|
||||||
totals.memory += captures[name].memory;
|
|
||||||
};
|
|
||||||
|
|
||||||
return totals;
|
return {
|
||||||
|
durationChange,
|
||||||
|
memoryChange,
|
||||||
|
overlapChange
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const extrinsicSimilarity = (currentCaptures, referenceCaptures, key) => {
|
const extrinsicSimilarity = (currentCaptures, referenceCaptures, key='extrinsic') => {
|
||||||
const result = {};
|
const result = {};
|
||||||
|
const zeroVector = { x: 0, y: 0 };
|
||||||
|
|
||||||
Object.entries(currentCaptures).forEach(([name, current]) => {
|
for (const name in currentCaptures) {
|
||||||
const reference = referenceCaptures[name];
|
const currentExtrinsic = currentCaptures[name][key];
|
||||||
const worldVector = [];
|
const referenceExtrinsic = referenceCaptures[name][key];
|
||||||
const worldVectorRef = [];
|
|
||||||
const currentExtrinsic = current[key];
|
|
||||||
const referenceExtrinsic = reference[key];
|
|
||||||
|
|
||||||
Object.keys(currentExtrinsic).forEach(objectType => {
|
let totalCount = 0;
|
||||||
Object.keys(currentExtrinsic[objectType]).forEach(objectId => {
|
let totalSimilarity = 0;
|
||||||
worldVector.push(...currentExtrinsic[objectType][objectId]);
|
|
||||||
worldVectorRef.push(...referenceExtrinsic[objectType][objectId]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
result[name] = similarity(worldVector, worldVectorRef);
|
for (const objectType in currentExtrinsic) {
|
||||||
});
|
for (const objectId in currentExtrinsic[objectType]) {
|
||||||
|
const currentObject = currentExtrinsic[objectType][objectId];
|
||||||
|
const referenceObject = referenceExtrinsic[objectType][objectId];
|
||||||
|
|
||||||
|
for (let i = 0; i < currentObject.vertices.length; i += 1) {
|
||||||
|
const currentPosition = currentObject.position;
|
||||||
|
const currentVertex = currentObject.vertices[i];
|
||||||
|
const referenceVertex = referenceObject.vertices[i] ? referenceObject.vertices[i] : zeroVector;
|
||||||
|
|
||||||
|
const radius = Math.sqrt(
|
||||||
|
Math.pow(currentVertex.x - currentPosition.x, 2)
|
||||||
|
+ Math.pow(currentVertex.y - currentPosition.y, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
const distance = Math.sqrt(
|
||||||
|
Math.pow(currentVertex.x - referenceVertex.x, 2)
|
||||||
|
+ Math.pow(currentVertex.y - referenceVertex.y, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
totalSimilarity += Math.min(1, distance / (2 * radius)) / currentObject.vertices.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result[name] = 1 - (totalSimilarity / totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
@ -291,6 +371,6 @@ const toMatchIntrinsics = {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
requireUncached, comparisonReport, logReport, getArg,
|
requireUncached, comparisonReport, logReport, getArg, smoothExp,
|
||||||
serialize, toMatchExtrinsics, toMatchIntrinsics
|
serialize, toMatchExtrinsics, toMatchIntrinsics
|
||||||
};
|
};
|
Loading…
Reference in a new issue