0
0
Fork 0
mirror of https://github.com/liabru/matter-js.git synced 2025-01-13 16:18:50 -05:00

Merge branch 'master' into timing-improve

* master: (32 commits)
  fix lint
  update dependencies
  Revert "Merge branch 'pr/526'"
  Revert "Merge branch 'pr/527'"
  changed alpha build configuration
  add window global, stub require and handle bad values in test tools
  added overlap metric to test tools
  fix path to build in test worker
  implemented threaded comparison testing
  fixed plugins in compare
  Added build comparison tools and tests
  Added config and test files to lint
  Set loose build version on dev server
  Added watch content base to dev server
  added timing to engine snapshot
  updated readme
  added tag push to release task
  updated readme
  removed yuidocjs dev dependency
  removed unused gulp release tasks
  ...

# Conflicts:
#	src/collision/Resolver.js
#	src/core/Engine.js
This commit is contained in:
liabru 2020-03-11 00:42:44 +00:00
commit 8cfc234b5b
80 changed files with 15644 additions and 1799 deletions

View file

@ -65,7 +65,12 @@
"Runner": false,
"Svg": false,
"Metrics": false,
"Example": false
"Example": false,
"__MATTER_VERSION__": false,
"jest": false,
"test": false,
"expect": false,
"describe": false
},
"extends": "eslint:recommended"
}

3
.gitignore vendored
View file

@ -7,8 +7,11 @@ build/matter-dev.js
build/matter-dev.min.js
demo/js/lib/matter-dev.js
demo/js/Examples.js
demo/js/Examples.min.js
examples/build
test/browser/diffs
test/browser/refs
test/node/diffs
test/node/refs
__snapshots__
__compare__

View file

@ -2,10 +2,9 @@ language: node_js
sudo: false
node_js:
- "node"
before_install:
- npm install -g gulp
- mkdir travis-phantomjs
- wget https://s3.amazonaws.com/travis-phantomjs/phantomjs-2.0.0-ubuntu-12.04.tar.bz2 -O $PWD/travis-phantomjs/phantomjs-2.0.0-ubuntu-12.04.tar.bz2
- tar -xvf $PWD/travis-phantomjs/phantomjs-2.0.0-ubuntu-12.04.tar.bz2 -C $PWD/travis-phantomjs
- export PATH=$PWD/travis-phantomjs:$PATH
install: npm install
install:
- npm install
script:
- npm run lint
- npm run test
- npm run build

View file

@ -2,22 +2,39 @@
## License Agreement
When providing any contributions, you must agree and be legally entitled to provide them for use and distribution in the project under the same terms as the [license](https://github.com/liabru/matter-js/blob/master/LICENSE), otherwise they can not be accepted.
## Building
To build you must first install [node.js](http://nodejs.org/) and [gulp](http://gulpjs.com/), then run
npm install
This will install the required build dependencies, then run
gulp dev
which is a task that builds the `matter-dev.js` file, spawns a development server and opens `http://localhost:8000/demo/index.html` in your browser. Any changes you make to the source will automatically rebuild `matter-dev.js` and reload your browser.
By providing any kind of contribution to this project, **you must agree and be legally entitled** to provide them for use and distribution as a part of this project **wholly under the same terms as in the original included [license](https://github.com/liabru/matter-js/blob/master/LICENSE)**.
## Contributions
Contributions by pull request are welcome! Please ensure they follow the same style and architecture as the rest of the code. You should run `gulp test` and ensure there are no reported errors. Please do not include any changes to the files in the `build` directory. All contributors must agree to the license agreement described at the beginning of this document.
Contributions by pull request or issues are welcome. Please ensure they follow the same style and architecture as the rest of the code. Use `npm run lint` before submitting. Please **do not include** any changes to the files in the `build` directory.
If you'd like to contribute but not sure what to work on, feel free to get in touch.
Before contributing please read the license agreement described at the beginning of this document.
## Building
To build you must first install [node.js](http://nodejs.org), then run
npm install
which will install the required build dependencies, then run
npm run dev
which will run the development server and opens `http://localhost:8000/` in your browser. Any changes you make to the source will automatically rebuild and reload the page.
## Commands
The following development commands can be run at the terminal
- **npm run dev**
runs development server
- **npm run build**
creates a release build
- **npm run lint**
runs the linter
- **npm run test**
runs the tests
- **npm run compare**
compares the output of examples for current source against release build
- **npm run doc**
builds the documentation

View file

@ -1,55 +1,83 @@
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var rename = require('gulp-rename');
var header = require('gulp-header');
var eslint = require('gulp-eslint');
var bump = require('gulp-bump');
var changelog = require('gulp-conventional-changelog');
var tag = require('gulp-tag-version');
var release = require('gulp-github-release');
var sequence = require('run-sequence');
var gutil = require('gulp-util');
var replace = require('gulp-replace');
var webserver = require('gulp-webserver');
var concat = require('gulp-concat');
var preprocess = require('gulp-preprocess');
var browserify = require('browserify');
var derequire = require('gulp-derequire');
var transform = require('vinyl-transform');
var through2 = require('through2');
var pkg = require('./package.json');
var clone = require('gulp-clone');
var livereload = require('connect-livereload');
var es = require('event-stream');
var path = require('path');
var fs = require('fs');
var watchify = require('watchify');
var extend = require('util')._extend;
var exec = require('child_process').exec;
var buildDirectory = 'build';
var server;
/* eslint-env es6 */
"use strict";
gulp.task('default', ['build:dev']);
const gulp = require('gulp');
const bump = require('gulp-bump');
const changelog = require('gulp-conventional-changelog');
const tag = require('gulp-tag-version');
const sequence = require('run-sequence');
const gutil = require('gulp-util');
const pkg = require('./package.json');
const exec = require('child_process').exec;
gulp.task('dev', function(callback) {
sequence('watch', 'serve', callback);
const shellExec = (command, callback) => {
const args = process.argv.slice(3).join(' '),
proc = exec(command + ' ' + args, (err, stdout, stderr) => {
callback(err, stdout, stderr, proc);
});
proc.stdout.on('data', data => process.stdout.write(data));
proc.stderr.on('data', data => process.stderr.write(data));
};
const shell = command => (callback => { shellExec(command, callback); });
const hint = command => (callback => {
gutil.log(gutil.colors.red('Error'), 'use', gutil.colors.yellow(command), 'instead.');
callback();
});
gulp.task('release', function(callback) {
shell('git status --porcelain', function(err, stdout) {
gulp.task('default', hint('npm run build'));
gulp.task('dev', hint('npm run dev'));
gulp.task('build', hint('npm run build'));
gulp.task('test', hint('npm run test'));
gulp.task('lint', hint('npm run lint'));
gulp.task('doc', callback => {
shellExec(`yuidoc --config yuidoc.json --project-version ${pkg.version}`, callback);
});
gulp.task('bump', () => {
return gulp.src(['package.json', 'bower.json'])
.pipe(bump({ type: process.argv[4] || 'minor' }))
.pipe(gulp.dest('.'));
});
gulp.task('tag', () => {
return gulp.src('package.json')
.pipe(tag({ prefix: '' }));
});
gulp.task('changelog', () => {
return gulp.src('CHANGELOG.md')
.pipe(changelog())
.pipe(gulp.dest('.'));
});
gulp.task('release', callback => {
shellExec('git status --porcelain', (err, stdout) => {
if (stdout && stdout.trim()) {
throw new gutil.PluginError({
plugin: 'release',
message: 'cannot build release as there are uncomitted changes'
});
} else {
sequence('test', 'bump', 'reload', 'build:release', 'doc', 'changelog', callback);
sequence(
'release:lint', 'bump', 'release:build', 'release:test',
'doc', 'changelog', callback
);
}
});
});
gulp.task('release:push', function(callback) {
shell('git status --porcelain', function(err, stdout) {
gulp.task('release:lint', shell('npm run lint'));
gulp.task('release:build', shell('npm run build'));
gulp.task('release:test', shell('TEST_BUILD=true npm run test'));
gulp.task('release:push:git', shell('git push origin && git push origin --tags'));
gulp.task('release:push:npm', shell('npm publish'));
gulp.task('release:push', callback => {
shellExec('git status --porcelain', (err, stdout) => {
if (stdout && stdout.trim()) {
throw new gutil.PluginError({
plugin: 'release',
@ -60,254 +88,3 @@ gulp.task('release:push', function(callback) {
}
});
});
gulp.task('release:push:github', function(callback) {
return gulp.src([
'CHANGELOG.md',
'LICENSE',
buildDirectory + '/matter.min.js',
buildDirectory + '/matter.js'
]).pipe(release({
owner: 'liabru',
repo: pkg.name,
tag: pkg.version,
name: 'Matter.js ' + pkg.version
}));
});
gulp.task('release:push:git', function(callback) {
shell('git push', callback);
});
gulp.task('release:push:npm', function(callback) {
shell('npm publish', callback);
});
gulp.task('build:dev', function() {
return build(extend(extend({}, pkg), { version: pkg.version + '-dev' }));
});
gulp.task('build:edge', function() {
return build(extend(extend({}, pkg), { version: pkg.version + '-alpha' }));
});
gulp.task('build:release', function() {
return build(extend(extend({}, pkg), { version: pkg.version }));
});
gulp.task('build:examples', function() {
var options = extend(extend({}, pkg), { version: pkg.version + '-dev' });
options.name = options.name + '-examples';
options.date = options.date || new Date().toISOString().slice(0, 10);
options.author = '@liabru';
return gulp.src('examples/**/*.js')
.pipe(concat('examples.js'))
.pipe(header(banner, { context: options }))
.pipe(gulp.dest('examples/build'));
});
gulp.task('watch', function() {
var b = browserify({
entries: ['src/module/main.js'],
standalone: 'Matter',
plugin: [watchify]
});
var bundle = function() {
gutil.log('Updated bundle build/matter-dev.js');
b.bundle()
.on('error', function(err) {
gutil.log('ERROR', err.message);
this.emit('end');
})
.pipe(through2({ objectMode: true }, function(chunk, encoding, callback) {
return callback(
null,
chunk.toString().replace(/@@VERSION@@/g, pkg.version + '-dev')
);
}))
.pipe(fs.createWriteStream('build/matter-dev.js'));
};
b.on('update', bundle);
bundle();
});
gulp.task('bump', function() {
return gulp.src(['package.json', 'bower.json'])
.pipe(bump({ type: process.argv[4] || 'minor' }))
.pipe(gulp.dest('.'));
});
gulp.task('reload', function(callback) {
delete require.cache[require.resolve('./package.json')];
pkg = require('./package.json');
callback();
});
gulp.task('tag', function() {
return gulp.src('package.json')
.pipe(tag({ prefix: '' }));
});
gulp.task('changelog', function () {
return gulp.src('CHANGELOG.md')
.pipe(changelog())
.pipe(gulp.dest('.'));
});
gulp.task('serve', function() {
serve(false);
});
gulp.task('serve:test', function() {
serve(true);
});
gulp.task('serve:stop', function() {
if (server) {
try {
server.emit('kill');
} catch (e) {} // eslint-disable-line no-empty
gutil.log('Web server stopped');
}
});
gulp.task('test', function(callback) {
// TODO: fix tests by switching to nightmare instead of phantom
// sequence('serve:test', 'lint', 'build:dev', 'test:browser', 'test:node', 'serve:stop', callback);
sequence('lint', callback);
});
gulp.task('test:browser', function(callback) {
shell('phantomjs test/browser/TestDemo.js', callback);
});
gulp.task('test:node', function(callback) {
shell('node test/node/TestDemo.js', callback);
});
gulp.task('lint', function() {
return gulp.src([
'src/**/*.js',
'demo/js/*.js',
'examples/*.js',
'test/browser/TestDemo.js',
'test/node/TestDemo.js',
'Gulpfile.js'
])
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError());
});
gulp.task('doc', function(callback) {
var options = {
paths: ['src'],
themedir: 'matter-doc-theme',
outdir: 'doc/build',
linkNatives: true,
project: {
name: pkg.name + ' ' + pkg.version + ' Physics Engine API Docs',
description: pkg.description,
version: pkg.version,
url: pkg.homepage
}
};
var Y = require('yuidocjs');
var json = new Y.YUIDoc(options).run();
json.project = options.project;
var builder = new Y.DocBuilder(options, json);
builder.compile(callback);
});
var serve = function(isTest) {
process.on('uncaughtException', function(err) {
if (err.errno === 'EADDRINUSE') {
gutil.log('Server already running (or port is otherwise in use)');
}
});
server = gulp.src('.')
.pipe(webserver({
host: '0.0.0.0',
livereload: {
enable: !isTest,
filter: function(filename) {
return filename.match(/build|demo/);
}
},
middleware: livereload(),
open: isTest ? false : 'http://localhost:8000/demo/index.html',
directoryListing: true
}));
};
var build = function(options) {
var isDev = options.version.indexOf('-dev') !== -1,
filename = buildDirectory + (isDev ? '/matter-dev' : '/matter'),
dest = filename + '.js',
destMin = filename + '.min.js';
options.date = options.date || new Date().toISOString().slice(0, 10);
options.author = '@liabru';
gutil.log('Building', filename, options.date);
var compiled = gulp.src(['src/module/main.js'])
.pipe(through2.obj(function(file, enc, next){
browserify(file.path, { standalone: 'Matter' })
.bundle(function(err, res){
file.contents = res;
next(null, file);
});
}))
.pipe(derequire())
.pipe(replace('@@VERSION@@', options.version));
if (!isDev) {
compiled.pipe(preprocess({ context: { DEBUG: false } }));
}
var build = compiled.pipe(clone())
.pipe(header(banner + '\n' + license + '\n\n', { context: options }))
.pipe(rename(dest))
.pipe(gulp.dest('.'));
var buildMin = compiled.pipe(clone())
.pipe(uglify({ output: { max_line_len: 1000 } }))
.pipe(header(banner, { context: options }))
.pipe(rename(destMin))
.pipe(gulp.dest('.'));
return es.merge(build, buildMin);
};
var shell = function(command, callback) {
var args = process.argv.slice(3).join(' '),
proc = exec(command + ' ' + args, function(err, stdout, stderr) {
callback(err, stdout, stderr, proc);
});
proc.stdout.on('data', function(data) {
process.stdout.write(data);
});
proc.stderr.on('data', function(data) {
process.stderr.write(data);
});
};
var license = fs.readFileSync('src/module/license.js');
var banner = [
'/**',
'* <%= context.name %> <%= context.version %> by <%= context.author %> <%= context.date %>',
'* <%= context.homepage %>',
'* License <%= context.license %>',
'*/',
''
].join('\n');

View file

@ -111,15 +111,14 @@ See how others are using matter.js physics
### Install
Download the [edge build (master)](https://github.com/liabru/matter-js/blob/master/build/matter.js) or get a [stable release](https://github.com/liabru/matter-js/releases) and include the script in your web page:
You can install using package managers [npm](https://www.npmjs.org/package/matter-js) and [Yarn](https://yarnpkg.com/) using:
npm install matter-js
Alternatively you can download a [stable release](https://github.com/liabru/matter-js/tags) or try the latest experimental [alpha build](https://github.com/liabru/matter-js/tree/master/build) (master) and include the script in your web page:
<script src="matter.js" type="text/javascript"></script>
You can also install using the package managers [Bower](http://bower.io/search/?q=matter-js) and [NPM](https://www.npmjs.org/package/matter-js).
bower install matter-js
npm install matter-js
### 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.
@ -149,13 +148,13 @@ See the [API Documentation](http://brm.io/matter-js/docs/) and the [wiki](https:
### Building and Contributing
To build you must first install [node.js](http://nodejs.org/) and [gulp](http://gulpjs.com/), then run
To build you must first install [node.js](http://nodejs.org/), then run
npm install
This will install the required build dependencies, then run
gulp dev
npm run dev
to spawn a development server. For information on contributing see [CONTRIBUTING.md](https://github.com/liabru/matter-js/blob/master/CONTRIBUTING.md).

View file

@ -12,21 +12,21 @@
<title>Matter.js Demo</title>
<!-- libs -->
<script type="text/javascript" src="/demo/lib/decomp.js"></script>
<script type="text/javascript" src="/demo/lib/pathseg.js"></script>
<!-- Libs -->
<script type="text/javascript" src="./lib/decomp.js"></script>
<script type="text/javascript" src="./lib/pathseg.js"></script>
<!-- Matter -->
<script src="../build/matter-dev.js"></script>
<script src="../build/matter.js"></script>
<!-- MatterTools -->
<script src="//code.jquery.com/jquery-3.1.1.js"></script>
<script src="/demo/lib/matter-tools.gui.js"></script>
<script src="/demo/lib/matter-tools.inspector.js"></script>
<script src="/demo/lib/matter-tools.demo.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.js"></script>
<script src="./lib/matter-tools.gui.js"></script>
<script src="./lib/matter-tools.inspector.js"></script>
<script src="./lib/matter-tools.demo.js"></script>
<!-- Plugins -->
<script src="/demo/lib/matter-wrap.js"></script>
<script src="./lib/matter-wrap.js"></script>
<!-- Examples -->
<script src="../examples/airFriction.js"></script>
@ -82,9 +82,30 @@
margin: 0;
padding: 0;
}
.matter-js-compare-build {
position: absolute;
background: none;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
z-index: 1;
pointer-events: none;
}
.matter-js-compare-build .matter-header-outer {
display: none;
}
.matter-js-compare-build canvas {
opacity: 0.5;
background: transparent !important;
}
</style>
</head>
<body>
<script src="/demo/js/Demo.js"></script>
<script src="./js/Demo.js"></script>
</body>
</html>

179
demo/js/Compare.js Normal file
View file

@ -0,0 +1,179 @@
/**
* A Matter.js build comparison testbed.
*
* Tool for Matter.js maintainers to compare results of
* the current source build with the release build in the browser.
*
* USAGE: open http://localhost:8000/?compare=120#mixed
*
* NOTE: For the actual example code, refer to the source files in `/examples/`.
*
* @class Compare
*/
(function() {
// maintain reference to dev version of Matter already loaded
var MatterDev = window.Matter;
// load the build version of Matter
var matterBuildScript = document.createElement('script');
matterBuildScript.src = '../build/matter.min.js';
// wait for load
matterBuildScript.addEventListener('load', function() {
var examples = window.MatterDemo.examples;
// maintain reference of build version and set dev version as main
var MatterBuild = window.Matter;
window.Matter = MatterDev;
var demo = MatterTools.Demo.create({
toolbar: {
title: 'matter-js',
url: 'https://github.com/liabru/matter-js',
reset: true,
source: true,
inspector: true,
tools: true,
fullscreen: true,
exampleSelect: true
},
tools: {
inspector: true,
gui: true
},
inline: false,
preventZoom: true,
resetOnOrientation: true,
routing: true,
startExample: 'mixed',
examples: examples
});
var demoBuild = MatterTools.Demo.create({
toolbar: {
title: 'matter-js-compare-build',
reset: false,
source: false,
inspector: false,
tools: false,
fullscreen: false,
exampleSelect: false
},
tools: {
inspector: false,
gui: false
},
inline: false,
preventZoom: true,
resetOnOrientation: true,
routing: false,
startExample: 'mixed',
examples: examples.map(function(example) {
return Matter.Common.extend({}, example);
})
});
/**
* NOTE: For the actual example code, refer to the source files in `/examples/`.
* The code below is tooling for Matter.js maintainers to compare versions of Matter.
*/
// build version should not run itself
MatterBuild.Runner.run = function() {};
MatterBuild.Render.run = function() {};
// maintain original references to patched methods
MatterDev.Runner._tick = MatterDev.Runner.tick;
MatterDev.Render._world = MatterDev.Render.world;
MatterBuild.Mouse._setElement = MatterBuild.Mouse.setElement;
// patch MatterTools to control both demo versions simultaneously
MatterTools.Demo._setExample = MatterTools.Demo.setExample;
MatterTools.Demo.setExample = function(_demo, example) {
MatterBuild.Common._nextId = MatterBuild.Common._seed = 0;
MatterDev.Common._nextId = MatterDev.Common._seed = 0;
MatterBuild.Plugin._registry = MatterDev.Plugin._registry;
MatterBuild.use.apply(null, MatterDev.used);
window.Matter = MatterDev;
MatterTools.Demo._setExample(
demo, demo.examples.find(function(e) { return e.name === example.name; })
);
var maxTicks = parseFloat(window.location.search.split('=')[1]);
var ticks = 0;
MatterDev.Runner.tick = function(runner, engine, time) {
if (ticks === -1) {
return;
}
if (ticks >= maxTicks) {
console.info(
'Demo.Compare: ran ' + ticks + ' ticks, timestamp is now '
+ engine.timing.timestamp.toFixed(2)
);
ticks = -1;
return;
}
ticks += 1;
var demoBuildInstance = demoBuild.example.instance;
runner.isFixed = demoBuildInstance.runner.isFixed = true;
runner.delta = demoBuildInstance.runner.delta = 1000 / 60;
window.Matter = MatterBuild;
MatterBuild.Runner.tick(demoBuildInstance.runner, demoBuildInstance.engine, time);
window.Matter = MatterDev;
return MatterDev.Runner._tick(runner, engine, time);
};
MatterDev.Render.world = function(render) {
window.Matter = MatterBuild;
MatterBuild.Render.world(demoBuild.example.instance.render);
window.Matter = MatterDev;
return MatterDev.Render._world(render);
};
MatterBuild.Mouse.setElement = function(mouse) {
return MatterBuild.Mouse._setElement(mouse, demo.example.instance.render.canvas);
};
window.Matter = MatterBuild;
MatterTools.Demo._setExample(
demoBuild, demoBuild.examples.find(function(e) { return e.name === example.name; })
);
window.Matter = MatterDev;
};
// reset both engine versions simultaneously
MatterTools.Demo._reset = MatterTools.Demo.reset;
MatterTools.Demo.reset = function(_demo) {
MatterBuild.Common._nextId = MatterBuild.Common._seed = 0;
MatterDev.Common._nextId = MatterDev.Common._seed = 0;
window.Matter = MatterBuild;
MatterTools.Demo._reset(demoBuild);
window.Matter = MatterDev;
MatterTools.Demo._reset(demo);
};
document.body.appendChild(demo.dom.root);
document.body.appendChild(demoBuild.dom.root);
MatterTools.Demo.start(demo);
console.info(
'Demo.Compare: comparing matter-js@' + MatterDev.version + ' with matter-js@' + MatterBuild.version
);
});
document.body.append(matterBuildScript);
})();

View file

@ -1,5 +1,8 @@
/**
* The Matter.js demo page controller and example runner.
* The Matter.js development demo and testing tool.
*
* This demo uses MatterTools, you can see the wiki for a simple example instead:
* https://github.com/liabru/matter-js/wiki/Getting-started
*
* NOTE: For the actual example code, refer to the source files in `/examples/`.
*
@ -7,8 +10,72 @@
*/
(function() {
var examples = [
{ name: 'Air Friction', id: 'airFriction' },
{ name: 'Avalanche', id: 'avalanche' },
{ name: 'Ball Pool', id: 'ballPool' },
{ name: 'Bridge', id: 'bridge' },
{ name: 'Broadphase', id: 'broadphase' },
{ name: 'Car', id: 'car' },
{ name: 'Catapult', id: 'catapult' },
{ name: 'Chains', id: 'chains' },
{ name: 'Circle Stack', id: 'circleStack' },
{ name: 'Cloth', id: 'cloth' },
{ name: 'Collision Filtering', id: 'collisionFiltering' },
{ name: 'Composite Manipulation', id: 'compositeManipulation' },
{ name: 'Compound Bodies', id: 'compound' },
{ name: 'Compound Stack', id: 'compoundStack' },
{ name: 'Concave', id: 'concave' },
{ name: 'Constraints', id: 'constraints' },
{ name: 'Double Pendulum', id: 'doublePendulum' },
{ name: 'Events', id: 'events' },
{ name: 'Friction', id: 'friction' },
{ name: 'Reverse Gravity', id: 'gravity' },
{ name: 'Gyroscope', id: 'gyro' },
{ name: 'Manipulation', id: 'manipulation' },
{ name: 'Mixed Shapes', id: 'mixed' },
{ name: 'Newton\'s Cradle', id: 'newtonsCradle' },
{ name: 'Ragdoll', id: 'ragdoll' },
{ name: 'Pyramid', id: 'pyramid' },
{ name: 'Raycasting', id: 'raycasting' },
{ name: 'Restitution', id: 'restitution' },
{ name: 'Rounded Corners (Chamfering)', id: 'rounded' },
{ name: 'Sensors', id: 'sensors' },
{ name: 'Sleeping', id: 'sleeping' },
{ name: 'Slingshot', id: 'slingshot' },
{ name: 'Soft Body', id: 'softBody' },
{ name: 'Sprites', id: 'sprites' },
{ name: 'Stack', id: 'stack' },
{ name: 'Static Friction', id: 'staticFriction' },
{ name: 'Stress', id: 'stress' },
{ name: 'Stress 2', id: 'stress2' },
{ name: 'Concave SVG Paths', id: 'svg' },
{ name: 'Terrain', id: 'terrain' },
{ name: 'Time Scaling', id: 'timescale' },
{ name: 'Views', id: 'views' },
{ name: 'Wrecking Ball', id: 'wreckingBall' }
];
var sourceLinkRoot = 'https://github.com/liabru/matter-js/blob/master/examples';
for (var i = 0; i < examples.length; i += 1) {
var example = examples[i];
example.sourceLink = sourceLinkRoot + '/' + example.id + '.js';
example.init = window.Example[example.id];
if (!example.init) {
console.warn('Example not loaded:', example.id);
}
}
if (window.location.search.indexOf('compare') >= 0) {
var compareScript = document.createElement('script');
compareScript.src = '../js/Compare.js';
window.MatterDemo = { examples: examples };
document.body.append(compareScript);
return;
}
var demo = MatterTools.Demo.create({
toolbar: {
title: 'matter-js',
@ -29,266 +96,7 @@
resetOnOrientation: true,
routing: true,
startExample: 'mixed',
examples: [
{
name: 'Air Friction',
id: 'airFriction',
init: Example.airFriction,
sourceLink: sourceLinkRoot + '/airFriction.js'
},
{
name: 'Avalanche',
id: 'avalanche',
init: Example.avalanche,
sourceLink: sourceLinkRoot + '/avalanche.js'
},
{
name: 'Ball Pool',
id: 'ballPool',
init: Example.ballPool,
sourceLink: sourceLinkRoot + '/ballPool.js'
},
{
name: 'Bridge',
id: 'bridge',
init: Example.bridge,
sourceLink: sourceLinkRoot + '/bridge.js'
},
{
name: 'Broadphase',
id: 'broadphase',
init: Example.broadphase,
sourceLink: sourceLinkRoot + '/broadphase.js'
},
{
name: 'Car',
id: 'car',
init: Example.car,
sourceLink: sourceLinkRoot + '/car.js'
},
{
name: 'Catapult',
id: 'catapult',
init: Example.catapult,
sourceLink: sourceLinkRoot + '/catapult.js'
},
{
name: 'Chains',
id: 'chains',
init: Example.chains,
sourceLink: sourceLinkRoot + '/chains.js'
},
{
name: 'Circle Stack',
id: 'circleStack',
init: Example.circleStack,
sourceLink: sourceLinkRoot + '/circleStack.js'
},
{
name: 'Cloth',
id: 'cloth',
init: Example.cloth,
sourceLink: sourceLinkRoot + '/cloth.js'
},
{
name: 'Collision Filtering',
id: 'collisionFiltering',
init: Example.collisionFiltering,
sourceLink: sourceLinkRoot + '/collisionFiltering.js'
},
{
name: 'Composite Manipulation',
id: 'compositeManipulation',
init: Example.compositeManipulation,
sourceLink: sourceLinkRoot + '/compositeManipulation.js'
},
{
name: 'Compound Bodies',
id: 'compound',
init: Example.compound,
sourceLink: sourceLinkRoot + '/compound.js'
},
{
name: 'Compound Stack',
id: 'compoundStack',
init: Example.compoundStack,
sourceLink: sourceLinkRoot + '/compoundStack.js'
},
{
name: 'Concave',
id: 'concave',
init: Example.concave,
sourceLink: sourceLinkRoot + '/concave.js'
},
{
name: 'Constraints',
id: 'constraints',
init: Example.constraints,
sourceLink: sourceLinkRoot + '/constraints.js'
},
{
name: 'Double Pendulum',
id: 'doublePendulum',
init: Example.doublePendulum,
sourceLink: sourceLinkRoot + '/doublePendulum.js'
},
{
name: 'Events',
id: 'events',
init: Example.events,
sourceLink: sourceLinkRoot + '/events.js'
},
{
name: 'Friction',
id: 'friction',
init: Example.friction,
sourceLink: sourceLinkRoot + '/friction.js'
},
{
name: 'Reverse Gravity',
id: 'gravity',
init: Example.gravity,
sourceLink: sourceLinkRoot + '/gravity.js'
},
{
name: 'Gyroscope',
id: 'gyro',
init: Example.gyro,
sourceLink: sourceLinkRoot + '/gyro.js'
},
{
name: 'Manipulation',
id: 'manipulation',
init: Example.manipulation,
sourceLink: sourceLinkRoot + '/manipulation.js'
},
{
name: 'Mixed Shapes',
id: 'mixed',
init: Example.mixed,
sourceLink: sourceLinkRoot + '/mixed.js'
},
{
name: 'Newton\'s Cradle',
id: 'newtonsCradle',
init: Example.newtonsCradle,
sourceLink: sourceLinkRoot + '/newtonsCradle.js'
},
{
name: 'Ragdoll',
id: 'ragdoll',
init: Example.ragdoll,
sourceLink: sourceLinkRoot + '/ragdoll.js'
},
{
name: 'Pyramid',
id: 'pyramid',
init: Example.pyramid,
sourceLink: sourceLinkRoot + '/pyramid.js'
},
{
name: 'Raycasting',
id: 'raycasting',
init: Example.raycasting,
sourceLink: sourceLinkRoot + '/raycasting.js'
},
{
name: 'Restitution',
id: 'restitution',
init: Example.restitution,
sourceLink: sourceLinkRoot + '/restitution.js'
},
{
name: 'Rounded Corners (Chamfering)',
id: 'rounded',
init: Example.rounded,
sourceLink: sourceLinkRoot + '/rounded.js'
},
{
name: 'Sensors',
id: 'sensors',
init: Example.sensors,
sourceLink: sourceLinkRoot + '/sensors.js'
},
{
name: 'Sleeping',
id: 'sleeping',
init: Example.sleeping,
sourceLink: sourceLinkRoot + '/sleeping.js'
},
{
name: 'Slingshot',
id: 'slingshot',
init: Example.slingshot,
sourceLink: sourceLinkRoot + '/slingshot.js'
},
{
name: 'Soft Body',
id: 'softBody',
init: Example.softBody,
sourceLink: sourceLinkRoot + '/softBody.js'
},
{
name: 'Sprites',
id: 'sprites',
init: Example.sprites,
sourceLink: sourceLinkRoot + '/sprites.js'
},
{
name: 'Stack',
id: 'stack',
init: Example.stack,
sourceLink: sourceLinkRoot + '/stack.js'
},
{
name: 'Static Friction',
id: 'staticFriction',
init: Example.staticFriction,
sourceLink: sourceLinkRoot + '/staticFriction.js'
},
{
name: 'Stress',
id: 'stress',
init: Example.stress,
sourceLink: sourceLinkRoot + '/stress.js'
},
{
name: 'Stress 2',
id: 'stress2',
init: Example.stress2,
sourceLink: sourceLinkRoot + '/stress2.js'
},
{
name: 'Concave SVG Paths',
id: 'svg',
init: Example.svg,
sourceLink: sourceLinkRoot + '/svg.js'
},
{
name: 'Terrain',
id: 'terrain',
init: Example.terrain,
sourceLink: sourceLinkRoot + '/terrain.js'
},
{
name: 'Time Scaling',
id: 'timescale',
init: Example.timescale,
sourceLink: sourceLinkRoot + '/timescale.js'
},
{
name: 'Views',
id: 'views',
init: Example.views,
sourceLink: sourceLinkRoot + '/views.js'
},
{
name: 'Wrecking Ball',
id: 'wreckingBall',
init: Example.wreckingBall,
sourceLink: sourceLinkRoot + '/wreckingBall.js'
}
]
examples: examples
});
document.body.appendChild(demo.dom.root);

View file

@ -79,3 +79,7 @@ Example.airFriction = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -1,10 +1,10 @@
var Example = Example || {};
Matter.use(
'matter-wrap'
);
Example.avalanche = function() {
Matter.use(
'matter-wrap'
);
var Engine = Matter.Engine,
Render = Matter.Render,
Runner = Matter.Runner,
@ -90,3 +90,7 @@ Example.avalanche = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -1,10 +1,10 @@
var Example = Example || {};
Matter.use(
'matter-wrap'
);
Example.ballPool = function() {
Matter.use(
'matter-wrap'
);
var Engine = Matter.Engine,
Render = Matter.Render,
Runner = Matter.Runner,
@ -98,3 +98,7 @@ Example.ballPool = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -123,3 +123,7 @@ Example.bridge = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -94,3 +94,7 @@ Example.broadphase = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -88,3 +88,7 @@ Example.car = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -95,3 +95,7 @@ Example.catapult = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -121,3 +121,7 @@ Example.chains = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -80,3 +80,7 @@ Example.circleStack = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -83,3 +83,7 @@ Example.cloth = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -154,3 +154,7 @@ Example.collisionFiltering = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -107,3 +107,7 @@ Example.compositeManipulation = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -105,3 +105,7 @@ Example.compound = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -88,3 +88,7 @@ Example.compoundStack = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -94,3 +94,7 @@ Example.concave = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -178,3 +178,7 @@ Example.constraints = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -151,3 +151,7 @@ Example.doublePendulum = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -185,3 +185,7 @@ Example.events = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -89,3 +89,7 @@ Example.friction = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -95,3 +95,7 @@ Example.gravity = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -68,26 +68,28 @@ Example.gyro = function() {
]);
// add gyro control
var updateGravity = function(event) {
var orientation = typeof window.orientation !== 'undefined' ? window.orientation : 0,
gravity = engine.world.gravity;
if (typeof window !== 'undefined') {
var updateGravity = function(event) {
var orientation = typeof window.orientation !== 'undefined' ? window.orientation : 0,
gravity = engine.world.gravity;
if (orientation === 0) {
gravity.x = Common.clamp(event.gamma, -90, 90) / 90;
gravity.y = Common.clamp(event.beta, -90, 90) / 90;
} else if (orientation === 180) {
gravity.x = Common.clamp(event.gamma, -90, 90) / 90;
gravity.y = Common.clamp(-event.beta, -90, 90) / 90;
} else if (orientation === 90) {
gravity.x = Common.clamp(event.beta, -90, 90) / 90;
gravity.y = Common.clamp(-event.gamma, -90, 90) / 90;
} else if (orientation === -90) {
gravity.x = Common.clamp(-event.beta, -90, 90) / 90;
gravity.y = Common.clamp(event.gamma, -90, 90) / 90;
}
};
if (orientation === 0) {
gravity.x = Common.clamp(event.gamma, -90, 90) / 90;
gravity.y = Common.clamp(event.beta, -90, 90) / 90;
} else if (orientation === 180) {
gravity.x = Common.clamp(event.gamma, -90, 90) / 90;
gravity.y = Common.clamp(-event.beta, -90, 90) / 90;
} else if (orientation === 90) {
gravity.x = Common.clamp(event.beta, -90, 90) / 90;
gravity.y = Common.clamp(-event.gamma, -90, 90) / 90;
} else if (orientation === -90) {
gravity.x = Common.clamp(-event.beta, -90, 90) / 90;
gravity.y = Common.clamp(event.gamma, -90, 90) / 90;
}
};
window.addEventListener('deviceorientation', updateGravity);
window.addEventListener('deviceorientation', updateGravity);
}
// add mouse control
var mouse = Mouse.create(render.canvas),
@ -121,7 +123,13 @@ Example.gyro = function() {
stop: function() {
Matter.Render.stop(render);
Matter.Runner.stop(runner);
window.removeEventListener('deviceorientation', updateGravity);
if (typeof window !== 'undefined') {
window.removeEventListener('deviceorientation', updateGravity);
}
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

45
examples/index.js Normal file
View file

@ -0,0 +1,45 @@
module.exports = {
airFriction: require('./airFriction.js'),
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'),
circleStack: require('./circleStack.js'),
cloth: require('./cloth.js'),
collisionFiltering: require('./collisionFiltering.js'),
compositeManipulation: require('./compositeManipulation.js'),
compound: require('./compound.js'),
compoundStack: require('./compoundStack.js'),
concave: require('./concave.js'),
constraints: require('./constraints.js'),
doublePendulum: require('./doublePendulum.js'),
events: require('./events.js'),
friction: require('./friction.js'),
gravity: require('./gravity.js'),
gyro: require('./gyro.js'),
manipulation: require('./manipulation.js'),
mixed: require('./mixed.js'),
newtonsCradle: require('./newtonsCradle.js'),
ragdoll: require('./ragdoll.js'),
pyramid: require('./pyramid.js'),
raycasting: require('./raycasting.js'),
restitution: require('./restitution.js'),
rounded: require('./rounded.js'),
sensors: require('./sensors.js'),
sleeping: require('./sleeping.js'),
slingshot: require('./slingshot.js'),
softBody: require('./softBody.js'),
sprites: require('./sprites.js'),
stack: require('./stack.js'),
staticFriction: require('./staticFriction.js'),
stress: require('./stress.js'),
stress2: require('./stress2.js'),
svg: require('./svg.js'),
terrain: require('./terrain.js'),
timescale: require('./timescale.js'),
views: require('./views.js'),
wreckingBall: require('./wreckingBall.js')
};

View file

@ -138,3 +138,7 @@ Example.manipulation = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -104,3 +104,7 @@ Example.mixed = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -75,3 +75,7 @@ Example.newtonsCradle = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -80,3 +80,7 @@ Example.pyramid = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -494,3 +494,7 @@ Example.ragdoll.ragdoll = function(x, y, scale, options) {
return person;
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -134,3 +134,7 @@ Example.raycasting = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -84,3 +84,7 @@ Example.restitution = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -108,3 +108,7 @@ Example.rounded = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -130,3 +130,7 @@ Example.sensors = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -103,3 +103,7 @@ Example.sleeping = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -106,3 +106,7 @@ Example.slingshot = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -84,3 +84,7 @@ Example.softBody = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -112,3 +112,7 @@ Example.sprites = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -80,3 +80,7 @@ Example.stack = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -100,3 +100,7 @@ Example.staticFriction = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -78,3 +78,7 @@ Example.stress = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -78,3 +78,7 @@ Example.stress2 = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -40,44 +40,46 @@ Example.svg = function() {
'iconmonstr-user-icon'
];
for (var i = 0; i < svgs.length; i += 1) {
(function(i) {
$.get('./svg/' + svgs[i] + '.svg').done(function(data) {
var vertexSets = [],
color = Common.choose(['#556270', '#4ECDC4', '#C7F464', '#FF6B6B', '#C44D58']);
if (typeof $ !== 'undefined') {
for (var i = 0; i < svgs.length; i += 1) {
(function(i) {
$.get('./svg/' + svgs[i] + '.svg').done(function(data) {
var vertexSets = [],
color = Common.choose(['#556270', '#4ECDC4', '#C7F464', '#FF6B6B', '#C44D58']);
$(data).find('path').each(function(i, path) {
var points = Svg.pathToVertices(path, 30);
vertexSets.push(Vertices.scale(points, 0.4, 0.4));
$(data).find('path').each(function(i, path) {
var points = Svg.pathToVertices(path, 30);
vertexSets.push(Vertices.scale(points, 0.4, 0.4));
});
World.add(world, Bodies.fromVertices(100 + i * 150, 200 + i * 50, vertexSets, {
render: {
fillStyle: color,
strokeStyle: color,
lineWidth: 1
}
}, true));
});
})(i);
}
World.add(world, Bodies.fromVertices(100 + i * 150, 200 + i * 50, vertexSets, {
render: {
fillStyle: color,
strokeStyle: color,
lineWidth: 1
}
}, true));
$.get('./svg/svg.svg').done(function(data) {
var vertexSets = [],
color = Common.choose(['#556270', '#4ECDC4', '#C7F464', '#FF6B6B', '#C44D58']);
$(data).find('path').each(function(i, path) {
vertexSets.push(Svg.pathToVertices(path, 30));
});
})(i);
}
$.get('./svg/svg.svg').done(function(data) {
var vertexSets = [],
color = Common.choose(['#556270', '#4ECDC4', '#C7F464', '#FF6B6B', '#C44D58']);
$(data).find('path').each(function(i, path) {
vertexSets.push(Svg.pathToVertices(path, 30));
World.add(world, Bodies.fromVertices(400, 80, vertexSets, {
render: {
fillStyle: color,
strokeStyle: color,
lineWidth: 1
}
}, true));
});
World.add(world, Bodies.fromVertices(400, 80, vertexSets, {
render: {
fillStyle: color,
strokeStyle: color,
lineWidth: 1
}
}, true));
});
}
World.add(world, [
Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
@ -121,3 +123,7 @@ Example.svg = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -36,36 +36,38 @@ Example.terrain = function() {
// add bodies
var terrain;
$.get('./svg/terrain.svg').done(function(data) {
var vertexSets = [];
if (typeof $ !== 'undefined') {
$.get('./svg/terrain.svg').done(function(data) {
var vertexSets = [];
$(data).find('path').each(function(i, path) {
vertexSets.push(Svg.pathToVertices(path, 30));
$(data).find('path').each(function(i, path) {
vertexSets.push(Svg.pathToVertices(path, 30));
});
terrain = Bodies.fromVertices(400, 350, vertexSets, {
isStatic: true,
render: {
fillStyle: '#2e2b44',
strokeStyle: '#2e2b44',
lineWidth: 1
}
}, true);
World.add(world, terrain);
var bodyOptions = {
frictionAir: 0,
friction: 0.0001,
restitution: 0.6
};
World.add(world, Composites.stack(80, 100, 20, 20, 10, 10, function(x, y) {
if (Query.point([terrain], { x: x, y: y }).length === 0) {
return Bodies.polygon(x, y, 5, 12, bodyOptions);
}
}));
});
terrain = Bodies.fromVertices(400, 350, vertexSets, {
isStatic: true,
render: {
fillStyle: '#2e2b44',
strokeStyle: '#2e2b44',
lineWidth: 1
}
}, true);
World.add(world, terrain);
var bodyOptions = {
frictionAir: 0,
friction: 0.0001,
restitution: 0.6
};
World.add(world, Composites.stack(80, 100, 20, 20, 10, 10, function(x, y) {
if (Query.point([terrain], { x: x, y: y }).length === 0) {
return Bodies.polygon(x, y, 5, 12, bodyOptions);
}
}));
});
}
// add mouse control
var mouse = Mouse.create(render.canvas),
@ -102,3 +104,7 @@ Example.terrain = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example;
}

View file

@ -149,3 +149,7 @@ Example.timescale = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -181,3 +181,7 @@ Example.views = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

View file

@ -92,3 +92,7 @@ Example.wreckingBall = function() {
}
};
};
if (typeof module !== 'undefined') {
module.exports = Example[Object.keys(Example)[0]];
}

14132
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -20,38 +20,30 @@
"rigid body physics"
],
"devDependencies": {
"browserify": "^14.0.0",
"cheerio": "^0.22.0",
"connect-livereload": "^0.6.0",
"event-stream": "^3.3.2",
"fast-json-patch": "^1.1.4",
"gulp": "^3.9.0",
"eslint": "^6.8.0",
"gulp": "^4.0.2",
"gulp-bump": "^2.6.1",
"gulp-clone": "^1.0.0",
"gulp-concat": "^2.6.0",
"gulp-conventional-changelog": "^1.1.0",
"gulp-derequire": "^2.1.0",
"gulp-eslint": "^3.0.1",
"gulp-github-release": "^1.2.1",
"gulp-header": "^1.7.1",
"gulp-preprocess": "^2.0.0",
"gulp-rename": "^1.2.2",
"gulp-replace": "^0.5.4",
"gulp-tag-version": "^1.3.0",
"gulp-uglify": "^2.0.1",
"gulp-util": "^3.0.8",
"gulp-webdriver": "^2.0.3",
"gulp-webserver": "^0.9.1",
"mkdirp": "^0.5.1",
"rimraf": "^2.4.2",
"jest": "^25.1.0",
"jest-worker": "^24.9.0",
"json-stringify-pretty-compact": "^2.0.0",
"run-sequence": "^1.1.4",
"through2": "^2.0.3",
"vinyl-transform": "^1.0.0",
"watchify": "^3.9.0",
"yuidocjs": "^0.10.2"
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
},
"scripts": {
"test": "gulp build:dev && gulp lint"
"dev": "webpack-dev-server --watch-content-base",
"build": "webpack --mode=production & webpack --mode=production --env.MINIMIZE",
"build-alpha": "webpack --mode=production --env.ALPHA & webpack --mode=production --env.MINIMIZE --env.ALPHA",
"build-examples": "webpack --config webpack.examples.config.js --mode=production --env.ALPHA & webpack --config webpack.examples.config.js --mode=production --env.MINIMIZE --env.ALPHA",
"lint": "eslint 'src/**/*.js' 'demo/js/Demo.js' 'demo/js/Compare.js' 'examples/*.js' 'webpack.*.js' 'Gulpfile.js'",
"doc": "gulp doc",
"test": "jest",
"test-save": "SAVE=true jest",
"test-watch": "jest --watch"
},
"dependencies": {},
"files": [

View file

@ -49,7 +49,6 @@ var Axes = require('../geometry/Axes');
force: { x: 0, y: 0 },
torque: 0,
positionImpulse: { x: 0, y: 0 },
previousPositionImpulse: { x: 0, y: 0 },
constraintImpulse: { x: 0, y: 0, angle: 0 },
totalContacts: 0,
speed: 0,
@ -193,8 +192,7 @@ var Axes = require('../geometry/Axes');
}
for (property in settings) {
if (!settings.hasOwnProperty(property))
if (!Object.prototype.hasOwnProperty.call(settings, property))
continue;
value = settings[property];
@ -1264,7 +1262,7 @@ var Axes = require('../geometry/Axes');
* @default 1
*/
/**
/**
* A `Number` that defines the offset in the x-axis for the sprite (normalised by texture width).
*
* @property render.sprite.xOffset
@ -1272,7 +1270,7 @@ var Axes = require('../geometry/Axes');
* @default 0
*/
/**
/**
* A `Number` that defines the offset in the y-axis for the sprite (normalised by texture height).
*
* @property render.sprite.yOffset

View file

@ -440,8 +440,8 @@ var Body = require('./Body');
*/
Composite.rebase = function(composite) {
var objects = Composite.allBodies(composite)
.concat(Composite.allConstraints(composite))
.concat(Composite.allComposites(composite));
.concat(Composite.allConstraints(composite))
.concat(Composite.allComposites(composite));
for (var i = 0; i < objects.length; i++) {
objects[i].id = Common.nextId();

View file

@ -128,7 +128,7 @@ var Common = require('../core/Common');
* @return {world} The original world with the objects from composite added
*/
/**
/**
* An alias for Composite.addBody
* @method addBody
* @param {world} world
@ -136,7 +136,7 @@ var Common = require('../core/Common');
* @return {world} The original world with the body added
*/
/**
/**
* An alias for Composite.addConstraint
* @method addConstraint
* @param {world} world

38
src/collision/Contact.js Normal file
View file

@ -0,0 +1,38 @@
/**
* The `Matter.Contact` module contains methods for creating and manipulating collision contacts.
*
* @class Contact
*/
var Contact = {};
module.exports = Contact;
(function() {
/**
* Creates a new contact.
* @method create
* @param {vertex} vertex
* @return {contact} A new 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;
};
})();

View file

@ -8,6 +8,8 @@ var Pair = {};
module.exports = Pair;
var Contact = require('./Contact');
(function() {
/**
@ -19,12 +21,15 @@ module.exports = Pair;
*/
Pair.create = function(collision, timestamp) {
var bodyA = collision.bodyA,
bodyB = collision.bodyB;
bodyB = collision.bodyB,
parentA = collision.parentA,
parentB = collision.parentB;
var pair = {
id: Pair.id(bodyA, bodyB),
bodyA: bodyA,
bodyB: bodyB,
contacts: {},
activeContacts: [],
separation: 0,
isActive: true,
@ -32,12 +37,11 @@ module.exports = Pair;
isSensor: bodyA.isSensor || bodyB.isSensor,
timeCreated: timestamp,
timeUpdated: timestamp,
collision: null,
inverseMass: 0,
friction: 0,
frictionStatic: 0,
restitution: 0,
slop: 0
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)
};
Pair.update(pair, collision, timestamp);
@ -53,28 +57,31 @@ module.exports = Pair;
* @param {number} timestamp
*/
Pair.update = function(pair, collision, timestamp) {
var contacts = pair.contacts,
supports = collision.supports,
activeContacts = pair.activeContacts,
parentA = collision.parentA,
parentB = collision.parentB;
pair.collision = collision;
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);
activeContacts.length = 0;
if (collision.collided) {
var supports = collision.supports,
activeContacts = pair.activeContacts,
parentA = collision.parentA,
parentB = collision.parentB;
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);
for (var i = 0; i < supports.length; i++) {
activeContacts[i] = supports[i].contact;
}
var support = supports[i],
contactId = Contact.id(support),
contact = contacts[contactId];
// optimise array size
var supportCount = supports.length;
if (supportCount < activeContacts.length) {
activeContacts.length = supportCount;
if (contact) {
activeContacts.push(contact);
} else {
activeContacts.push(contacts[contactId] = Contact.create(support));
}
}
pair.separation = collision.depth;

View file

@ -48,34 +48,24 @@ var Bounds = require('../geometry/Bounds');
* Find a solution for pair positions.
* @method solvePosition
* @param {pair[]} pairs
* @param {body[]} bodies
* @param {number} delta
*/
Resolver.solvePosition = function(pairs, bodies, delta) {
Resolver.solvePosition = function(pairs, delta) {
var i,
normalX,
normalY,
pair,
collision,
bodyA,
bodyB,
normal,
separation,
penetration,
positionImpulseA,
positionImpulseB,
bodyBtoA,
contactShare,
bodyBtoAX,
bodyBtoAY,
positionImpulse,
timeScale = delta / Common._timeUnit,
damping = Common.clamp(Resolver._positionDampen * timeScale, 0, 1);
for (i = 0; i < bodies.length; i++) {
var body = bodies[i];
body.previousPositionImpulse.x = body.positionImpulse.x;
body.previousPositionImpulse.y = body.positionImpulse.y;
}
damping = Common.clamp(Resolver._positionDampen * timeScale, 0, 1),
tempA = Vector._temp[0],
tempB = Vector._temp[1],
tempC = Vector._temp[2],
tempD = Vector._temp[3];
// find impulses required to resolve penetration
for (i = 0; i < pairs.length; i++) {
@ -89,35 +79,39 @@ var Bounds = require('../geometry/Bounds');
bodyB = collision.parentB;
normal = collision.normal;
positionImpulseA = bodyA.previousPositionImpulse;
positionImpulseB = bodyB.previousPositionImpulse;
// 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);
penetration = collision.penetration;
pair.separation = Vector.dot(normal, bodyBtoA);
}
bodyBtoAX = positionImpulseB.x - positionImpulseA.x + penetration.x;
bodyBtoAY = positionImpulseB.y - positionImpulseA.y + penetration.y;
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
normalX = normal.x;
normalY = normal.y;
if (!pair.isActive || pair.isSensor)
continue;
separation = normalX * bodyBtoAX + normalY * bodyBtoAY;
pair.separation = separation;
positionImpulse = (separation - pair.slop) * damping;
collision = pair.collision;
bodyA = collision.parentA;
bodyB = collision.parentB;
normal = collision.normal;
positionImpulse = (pair.separation - pair.slop) * damping;
if (bodyA.isStatic || bodyB.isStatic)
positionImpulse *= 2;
if (!(bodyA.isStatic || bodyA.isSleeping)) {
contactShare = positionImpulse / bodyA.totalContacts;
bodyA.positionImpulse.x += normalX * contactShare;
bodyA.positionImpulse.y += normalY * contactShare;
contactShare = Resolver._positionDampen / bodyA.totalContacts;
bodyA.positionImpulse.x += normal.x * positionImpulse * contactShare;
bodyA.positionImpulse.y += normal.y * positionImpulse * contactShare;
}
if (!(bodyB.isStatic || bodyB.isSleeping)) {
contactShare = positionImpulse / bodyB.totalContacts;
bodyB.positionImpulse.x -= normalX * contactShare;
bodyB.positionImpulse.y -= normalY * contactShare;
contactShare = Resolver._positionDampen / bodyB.totalContacts;
bodyB.positionImpulse.x -= normal.x * positionImpulse * contactShare;
bodyB.positionImpulse.y -= normal.y * positionImpulse * contactShare;
}
}
};

View file

@ -304,6 +304,32 @@ var Common = require('../core/Common');
}
};
/**
* Returns the world-space position of `constraint.pointA`, accounting for `constraint.bodyA`.
* @method pointAWorld
* @param {constraint} constraint
* @returns {vector} the world-space position
*/
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
};
};
/**
* Returns the world-space position of `constraint.pointB`, accounting for `constraint.bodyB`.
* @method pointBWorld
* @param {constraint} constraint
* @returns {vector} the world-space position
*/
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
};
};
/*
*
* Properties Documentation

View file

@ -258,7 +258,7 @@ module.exports = Common;
* @return {number} the current timestamp
*/
Common.now = function() {
if (window.performance) {
if (typeof window !== 'undefined' && window.performance) {
if (window.performance.now) {
return window.performance.now();
} else if (window.performance.webkitNow) {
@ -536,19 +536,4 @@ module.exports = Common;
func
));
};
/**
* Used to require external libraries outside of the bundle.
* It first looks for the `globalName` on the environment's global namespace.
* If the global is not found, it will fall back to using the standard `require` using the `moduleName`.
* @private
* @method _requireGlobal
* @param {string} globalName The global module name
* @param {string} moduleName The fallback CommonJS module name
* @return {} The loaded module
*/
Common._requireGlobal = function(globalName, moduleName) {
var obj = (typeof window !== 'undefined' ? window[globalName] : typeof global !== 'undefined' ? global[globalName] : null);
return obj || require(moduleName);
};
})();

View file

@ -191,7 +191,7 @@ var Body = require('../body/Body');
// iteratively resolve position between collisions
Resolver.preSolvePosition(pairs.list);
for (i = 0; i < engine.positionIterations; i++) {
Resolver.solvePosition(pairs.list, allBodies, delta);
Resolver.solvePosition(pairs.list, delta);
}
Resolver.postSolvePosition(allBodies);

View file

@ -27,7 +27,7 @@ var Common = require('./Common');
* @readOnly
* @type {String}
*/
Matter.version = '@@VERSION@@';
Matter.version = typeof __MATTER_VERSION__ !== 'undefined' ? __MATTER_VERSION__ : '*';
/**
* A list of plugin dependencies to be installed. These are normally set and installed through `Matter.use`.

View file

@ -18,7 +18,6 @@ var Common = require('../core/Common');
var Body = require('../body/Body');
var Bounds = require('../geometry/Bounds');
var Vector = require('../geometry/Vector');
var decomp;
(function() {
@ -46,7 +45,7 @@ var decomp;
if (options.chamfer) {
var chamfer = options.chamfer;
rectangle.vertices = Vertices.chamfer(rectangle.vertices, chamfer.radius,
chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
delete options.chamfer;
}
@ -92,7 +91,7 @@ var decomp;
if (options.chamfer) {
var chamfer = options.chamfer;
trapezoid.vertices = Vertices.chamfer(trapezoid.vertices, chamfer.radius,
chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
delete options.chamfer;
}
@ -169,7 +168,7 @@ var decomp;
if (options.chamfer) {
var chamfer = options.chamfer;
polygon.vertices = Vertices.chamfer(polygon.vertices, chamfer.radius,
chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
delete options.chamfer;
}
@ -197,11 +196,8 @@ var decomp;
* @return {body}
*/
Bodies.fromVertices = function(x, y, vertexSets, options, flagInternal, removeCollinear, minimumArea) {
if (!decomp) {
decomp = Common._requireGlobal('decomp', 'poly-decomp');
}
var body,
var decomp = global.decomp || require('poly-decomp'),
body,
parts,
isConvex,
vertices,

View file

@ -217,7 +217,7 @@ var Bodies = require('./Bodies');
for (var i = 0; i < number; i++) {
var separation = 1.9,
circle = Bodies.circle(xx + i * (size * separation), yy + length, size,
{ inertia: Infinity, restitution: 1, friction: 0, frictionAir: 0.0001, slop: 1 }),
{ inertia: Infinity, restitution: 1, friction: 0, frictionAir: 0.0001, slop: 1 }),
constraint = Constraint.create({ pointA: { x: xx + i * (size * separation), y: yy }, bodyB: circle });
Composite.addBody(newtonsCradle, circle);

View file

@ -44,16 +44,9 @@ var Common = require('../core/Common');
y: point.y,
index: i,
body: body,
isInternal: false,
contact: null
isInternal: false
};
vertex.contact = {
vertex: vertex,
normalImpulse: 0,
tangentImpulse: 0
};
vertices.push(vertex);
}
@ -70,7 +63,7 @@ var Common = require('../core/Common');
* @return {vertices} vertices
*/
Vertices.fromPath = function(path, body) {
var pathPattern = /L?\s*([\-\d\.e]+)[\s,]*([\-\d\.e]+)*/ig,
var pathPattern = /L?\s*([-\d.e]+)[\s,]*([-\d.e]+)*/ig,
points = [];
path.replace(pathPattern, function(match, x, y) {

View file

@ -4,6 +4,7 @@ Matter.Body = require('../body/Body');
Matter.Composite = require('../body/Composite');
Matter.World = require('../body/World');
Matter.Contact = require('../collision/Contact');
Matter.Detector = require('../collision/Detector');
Matter.Grid = require('../collision/Grid');
Matter.Pairs = require('../collision/Pairs');

View file

@ -927,7 +927,7 @@ var Mouse = require('../core/Mouse');
// render a single axis indicator
c.moveTo(part.position.x, part.position.y);
c.lineTo((part.vertices[0].x + part.vertices[part.vertices.length-1].x) / 2,
(part.vertices[0].y + part.vertices[part.vertices.length-1].y) / 2);
(part.vertices[0].y + part.vertices[part.vertices.length-1].y) / 2);
}
}
}
@ -1227,9 +1227,9 @@ var Mouse = require('../core/Mouse');
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);
0.5 + parseInt(region[2], 10) * grid.bucketHeight,
grid.bucketWidth,
grid.bucketHeight);
}
c.lineWidth = 1;
@ -1276,7 +1276,7 @@ var Mouse = require('../core/Mouse');
bounds = item.bounds;
context.beginPath();
context.rect(Math.floor(bounds.min.x - 3), Math.floor(bounds.min.y - 3),
Math.floor(bounds.max.x - bounds.min.x + 6), Math.floor(bounds.max.y - bounds.min.y + 6));
Math.floor(bounds.max.x - bounds.min.x + 6), Math.floor(bounds.max.y - bounds.min.y + 6));
context.closePath();
context.stroke();
@ -1310,7 +1310,7 @@ var Mouse = require('../core/Mouse');
bounds = inspector.selectBounds;
context.beginPath();
context.rect(Math.floor(bounds.min.x), Math.floor(bounds.min.y),
Math.floor(bounds.max.x - bounds.min.x), Math.floor(bounds.max.y - bounds.min.y));
Math.floor(bounds.max.x - bounds.min.x), Math.floor(bounds.max.y - bounds.min.y));
context.closePath();
context.stroke();
context.fill();

View file

@ -485,7 +485,7 @@ var Vector = require('../geometry/Vector');
primitive.moveTo(part.position.x - body.position.x, part.position.y - body.position.y);
primitive.lineTo(((part.vertices[0].x + part.vertices[part.vertices.length-1].x) / 2 - body.position.x),
((part.vertices[0].y + part.vertices[part.vertices.length-1].y) / 2 - body.position.y));
((part.vertices[0].y + part.vertices[part.vertices.length-1].y) / 2 - body.position.y));
primitive.endFill();
}

93
test/ExampleWorker.js Normal file
View file

@ -0,0 +1,93 @@
/* eslint-env es6 */
/* eslint no-global-assign: 0 */
"use strict";
const stubBrowserFeatures = M => {
const noop = () => ({ collisionFilter: {}, mouse: {} });
M.Common._requireGlobal = name => global[name];
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.log = M.Common.info = M.Common.warn = noop;
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 { engineCapture } = require('./TestTools');
const MatterDev = stubBrowserFeatures(require('../src/module/main'));
const MatterBuild = stubBrowserFeatures(require('../build/matter'));
const Example = require('../examples/index');
const decomp = require('../demo/lib/decomp');
const runExample = options => {
const Matter = options.useDev ? MatterDev : MatterBuild;
const consoleOriginal = global.console;
global.console = { log: () => {} };
global.document = global.window = { addEventListener: () => {} };
global.decomp = decomp;
global.Matter = Matter;
reset(Matter);
const example = Example[options.name]();
const engine = example.engine;
let totalDuration = 0;
let overlapTotal = 0;
let overlapCount = 0;
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
});
}
}
for (let i = 0; i < options.totalUpdates; i += 1) {
const startTime = process.hrtime();
Matter.Engine.update(engine, 1000 / 60);
const duration = process.hrtime(startTime);
totalDuration += duration[0] * 1e9 + duration[1];
for (let p = 0; p < engine.pairs.list.length; p += 1) {
const pair = engine.pairs.list[p];
const separation = pair.separation - pair.slop;
if (pair.isActive && !pair.isSensor) {
overlapTotal += separation > 0 ? separation : 0;
overlapCount += 1;
}
}
}
global.console = consoleOriginal;
global.window = undefined;
global.document = undefined;
global.decomp = undefined;
global.Matter = undefined;
return {
name: options.name,
duration: totalDuration,
overlap: overlapTotal / (overlapCount || 1),
...engineCapture(engine)
};
};
module.exports = { runExample };

74
test/Examples.spec.js Normal file
View file

@ -0,0 +1,74 @@
/* eslint-env es6 */
"use strict";
jest.setTimeout(30 * 1000);
const { comparisonReport, toMatchExtrinsics, toMatchIntrinsics } = require('./TestTools');
const Example = require('../examples/index');
const MatterBuild = require('../build/matter');
const Worker = require('jest-worker').default;
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 => !excludeExamples.includes(key));
const runExamples = async useDev => {
const worker = new Worker(require.resolve('./ExampleWorker'), {
enableWorkerThreads: true
});
const result = await Promise.all(examples.map(name => worker.runExample({
name,
useDev,
totalUpdates: 120,
jitter: excludeJitter.includes(name) ? 0 : 1e-10
})));
await worker.end();
return result.reduce((out, capture) => (out[capture.name] = capture, out), {});
};
const capturesDev = runExamples(true);
const capturesBuild = runExamples(false);
afterAll(async () => {
// Report experimental capture comparison.
const dev = await capturesDev;
const build = await capturesBuild;
console.log(comparisonReport(dev, build, MatterBuild.version, saveComparison));
});
describe(`Integration checks (${examples.length})`, () => {
test(`Examples run without throwing`, async () => {
const dev = await capturesDev;
const build = await capturesBuild;
expect(Object.keys(dev)).toEqual(examples);
expect(Object.keys(build)).toEqual(examples);
});
});
// Experimental regression comparison checks.
if (testComparison) {
describe(`Regression checks (${examples.length})`, () => {
expect.extend(toMatchExtrinsics);
expect.extend(toMatchIntrinsics);
test(`Examples match intrinsic properties with release build`, async () => {
const dev = await capturesDev;
const build = await capturesBuild;
// compare mass, inertia, friction etc.
expect(dev).toMatchIntrinsics(build);
});
test(`Examples match extrinsic positions and velocities with release build`, async () => {
const dev = await capturesDev;
const build = await capturesBuild;
// compare position, linear and angular velocity
expect(dev).toMatchExtrinsics(build);
});
});
}

278
test/TestTools.js Normal file
View file

@ -0,0 +1,278 @@
/* eslint-env es6 */
"use strict";
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 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), [])
];
return bodies;
}, {}),
constraints: Composite.allConstraints(world).reduce((constraints, constraint) => {
const positionA = Constraint.pointAWorld(constraint);
const positionB = Constraint.pointBWorld(constraint);
constraints[constraint.id] = [
positionA.x,
positionA.y,
positionB.x,
positionB.y
];
return constraints;
}, {})
});
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 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;
}
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;
}
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), {});
};
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 captureSimilarityExtrinsic = (currentCaptures, referenceCaptures) => {
const result = {};
Object.entries(currentCaptures).forEach(([name, current]) => {
const reference = referenceCaptures[name];
const worldVector = [];
const worldVectorRef = [];
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]);
});
});
result[name] = similarity(worldVector, worldVectorRef);
});
return result;
};
const writeCaptures = (name, obj) => {
try {
fs.mkdirSync(comparePath, { recursive: true });
} catch (err) {
if (err.code !== 'EEXIST') throw err;
}
fs.writeFileSync(`${comparePath}/${name}.json`, compactStringify(obj, { maxLength: 100 }), 'utf8');
};
const toMatchExtrinsics = {
toMatchExtrinsics(received, value) {
const similaritys = captureSimilarityExtrinsic(received, value);
const pass = Object.values(similaritys).every(similarity => similarity >= equalityThreshold);
return {
message: () => 'Expected positions and velocities to match between builds.',
pass
};
}
};
const toMatchIntrinsics = {
toMatchIntrinsics(currentCaptures, referenceCaptures) {
const entries = Object.entries(currentCaptures);
let changed = false;
entries.forEach(([name, current]) => {
const reference = referenceCaptures[name];
if (!this.equals(current.intrinsic, reference.intrinsic)) {
changed = true;
}
});
return {
message: () => 'Expected intrinsic properties to match between builds.',
pass: !changed
};
}
};
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 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);
perfChange = perfChange < -0.05 || perfChange > 0.05 ? perfChange : 0;
let similarityAvg = 0;
similarityEntries.forEach(([_, similarity]) => {
similarityAvg += similarity;
});
similarityAvg /= similarityEntries.length;
const overlapChange = (totalOverlapDev / (totalOverlapBuild || 1)) - 1;
if (save) {
writeCaptures('examples-dev', devIntrinsicsChanged);
writeCaptures('examples-build', buildIntrinsicsChanged);
}
return [
[`Output comparison of ${similarityEntries.length}`,
`examples against ${color('matter-js@' + buildVersion, colors.Yellow)} build on last run`
].join(' '),
`\n\n${color('Similarity', colors.White)}`,
`${color(toPercent(similarityAvg), similarityAvg === 1 ? colors.Green : colors.Yellow)}%`,
`${color('Performance', colors.White)}`,
`${color((perfChange >= 0 ? '+' : '') + toPercent(perfChange), perfChange >= 0 ? colors.Green : colors.Red)}%`,
`${color('Overlap', colors.White)}`,
`${color((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 && i % 5 === 0) {
output += '\n';
}
return output;
}, '\n\n'),
`\nwhere · no change ● extrinsics changed ◆ intrinsics changed\n`,
similarityAvg < 1 ? `\n${color('▶', colors.White)} ${color(compareCommand + '=' + 120 + '#' + similarityEntries[0][0], colors.BrightCyan)}` : '',
intrinsicChangeCount > 0 ? `\n${color('▶', colors.White)} ${color((save ? diffCommand : diffSaveCommand), colors.BrightCyan)}` : ''
].join(' ');
};
module.exports = {
engineCapture, comparisonReport,
toMatchExtrinsics, toMatchIntrinsics
};

View file

@ -1,206 +0,0 @@
var page = require('webpage').create();
var fs = require('fs');
var Resurrect = require('../lib/resurrect');
var compare = require('fast-json-patch').compare;
var system = require('system');
var demo,
frames = 10,
testUrl = 'http://localhost:8000/demo/index.html',
refsPath = 'test/browser/refs',
diffsPath = 'test/browser/diffs';
var update = arg('--update'),
updateAll = typeof arg('--updateAll') !== 'undefined',
diff = arg('--diff');
var resurrect = new Resurrect({ cleanup: true, revive: false }),
created = [],
changed = [];
var test = function(status) {
if (status === 'fail') {
console.log('failed to load', testUrl);
console.log('check dev server is running!');
console.log('use `grunt dev`');
phantom.exit(1);
return;
}
var demos = page.evaluate(function() {
var demoSelect = document.getElementById('demo-select'),
options = Array.prototype.slice.call(demoSelect);
return options.map(function(o) { return o.value; });
});
fs.removeTree(diffsPath);
if (diff) {
fs.makeDirectory(diffsPath);
}
for (var i = 0; i < demos.length; i += 1) {
demo = demos[i];
var hasChanged = false,
hasCreated = false,
forceUpdate = update === demo || updateAll,
worldStartPath = refsPath + '/' + demo + '/' + demo + '-0.json',
worldEndPath = refsPath + '/' + demo + '/' + demo + '-' + frames + '.json',
worldStartDiffPath = diffsPath + '/' + demo + '/' + demo + '-0.json',
worldEndDiffPath = diffsPath + '/' + demo + '/' + demo + '-' + frames + '.json';
var worldStart = page.evaluate(function(demo) {
if (!(demo in Matter.Example)) {
throw '\'' + demo + '\' is not defined in Matter.Demo';
}
var _demo = Matter.Demo.create(),
engine = Matter.Example.engine(_demo),
runner = Matter.Runner.create();
Matter.Demo._demo = _demo;
_demo.engine = engine;
_demo.engine.render = {};
_demo.engine.render.options = {};
_demo.runner = runner;
_demo.render = { options: {} };
_demo.mouseConstraint = Matter.MouseConstraint.create(engine);
Matter.Demo.reset(_demo);
Matter.Example[demo](_demo);
return engine.world;
}, demo);
var worldEnd = page.evaluate(function(demo, frames) {
var engine = Matter.Demo._demo.engine,
runner = Matter.Demo._demo.runner;
for (var j = 0; j <= frames; j += 1) {
Matter.Runner.tick(runner, engine, j * runner.delta);
}
return engine.world;
}, demo, frames);
worldEnd = JSON.parse(resurrect.stringify(worldEnd, precisionLimiter));
worldStart = JSON.parse(resurrect.stringify(worldStart, precisionLimiter));
if (fs.exists(worldStartPath)) {
var worldStartRef = JSON.parse(fs.read(worldStartPath));
var worldStartDiff = compare(worldStartRef, worldStart);
if (worldStartDiff.length !== 0) {
if (diff) {
fs.write(worldStartDiffPath, JSON.stringify(worldStartDiff, precisionLimiter, 2), 'w');
}
if (forceUpdate) {
hasCreated = true;
fs.write(worldStartPath, JSON.stringify(worldStart, precisionLimiter, 2), 'w');
} else {
hasChanged = true;
}
}
} else {
hasCreated = true;
fs.write(worldStartPath, JSON.stringify(worldStart, precisionLimiter, 2), 'w');
}
if (fs.exists(worldEndPath)) {
var worldEndRef = JSON.parse(fs.read(worldEndPath));
var worldEndDiff = compare(worldEndRef, worldEnd);
if (worldEndDiff.length !== 0) {
if (diff) {
fs.write(worldEndDiffPath, JSON.stringify(worldEndDiff, precisionLimiter, 2), 'w');
}
if (forceUpdate) {
hasCreated = true;
fs.write(worldEndPath, JSON.stringify(worldEnd, precisionLimiter, 2), 'w');
} else {
hasChanged = true;
}
}
} else {
hasCreated = true;
fs.write(worldEndPath, JSON.stringify(worldEnd, precisionLimiter, 2), 'w');
}
if (hasChanged) {
changed.push("'" + demo + "'");
system.stdout.write('x');
} else if (hasCreated) {
created.push("'" + demo + "'");
system.stdout.write('+');
} else {
system.stdout.write('.');
}
}
if (created.length > 0) {
console.log('\nupdated', created.join(', '));
}
var isOk = changed.length === 0 ? 1 : 0;
console.log('');
if (isOk) {
console.log('ok');
} else {
console.log('\nchanges detected on:');
console.log(changed.join(', '));
console.log('\nreview, then --update [name] or --updateAll');
console.log('use --diff for diff log');
}
phantom.exit(!isOk);
};
var precisionLimiter = function(key, value) {
if (typeof value === 'number') {
return parseFloat(value.toFixed(5));
}
return value;
};
function arg(name) {
var index = system.args.indexOf(name);
if (index >= 0) {
return system.args[index + 1] || true;
}
return undefined;
}
page.onError = function(msg, trace) {
setTimeout(function() {
var msgStack = ['testing \'' + demo + '\'', msg];
if (trace && trace.length) {
trace.forEach(function(t) {
msgStack.push(' at ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (fn: ' + t.function +')' : ''));
});
}
console.log(msgStack.join('\n'));
phantom.exit(1);
}, 0);
};
page.onResourceReceived = function(res) {
setTimeout(function() {
if (res.stage === 'end'
&& (res.status !== 304 && res.status !== 200 && res.status !== null)) {
console.log('error', res.status, res.url);
phantom.exit(1);
}
}, 0);
};
phantom.onError = page.onError;
page.open(testUrl, test);

View file

@ -1,542 +0,0 @@
/**
* # ResurrectJS
* @version 1.0.3
* @license Public Domain
*
* ResurrectJS preserves object behavior (prototypes) and reference
* circularity with a special JSON encoding. Unlike regular JSON,
* Date, RegExp, DOM objects, and `undefined` are also properly
* preserved.
*
* ## Examples
*
* function Foo() {}
* Foo.prototype.greet = function() { return "hello"; };
*
* // Behavior is preserved:
* var necromancer = new Resurrect();
* var json = necromancer.stringify(new Foo());
* var foo = necromancer.resurrect(json);
* foo.greet(); // => "hello"
*
* // References to the same object are preserved:
* json = necromancer.stringify([foo, foo]);
* var array = necromancer.resurrect(json);
* array[0] === array[1]; // => true
* array[1].greet(); // => "hello"
*
* // Dates are restored properly
* json = necromancer.stringify(new Date());
* var date = necromancer.resurrect(json);
* Object.prototype.toString.call(date); // => "[object Date]"
*
* ## Options
*
* Options are provided to the constructor as an object with these
* properties:
*
* prefix ('#'): A prefix string used for temporary properties added
* to objects during serialization and deserialization. It is
* important that you don't use any properties beginning with this
* string. This option must be consistent between both
* serialization and deserialization.
*
* cleanup (false): Perform full property cleanup after both
* serialization and deserialization using the `delete`
* operator. This may cause performance penalties (breaking hidden
* classes in V8) on objects that ResurrectJS touches, so enable
* with care.
*
* revive (true): Restore behavior (__proto__) to objects that have
* been resurrected. If this is set to false during serialization,
* resurrection information will not be encoded. You still get
* circularity and Date support.
*
* resolver (Resurrect.NamespaceResolver(window)): Converts between
* a name and a prototype. Create a custom resolver if your
* constructors are not stored in global variables. The resolver
* has two methods: getName(object) and getPrototype(string).
*
* For example,
*
* var necromancer = new Resurrect({
* prefix: '__#',
* cleanup: true
* });
*
* ## Caveats
*
* * With the default resolver, all constructors must be named and
* stored in the global variable under that name. This is required
* so that the prototypes can be looked up and reconnected at
* resurrection time.
*
* * The wrapper objects Boolean, String, and Number will be
* unwrapped. This means extra properties added to these objects
* will not be preserved.
*
* * Functions cannot ever be serialized. Resurrect will throw an
* error if a function is found when traversing a data structure.
*
* @see http://nullprogram.com/blog/2013/03/28/
*/
/**
* @param {Object} [options] See options documentation.
* @namespace
* @constructor
*/
function Resurrect(options) {
this.table = null;
this.prefix = '#';
this.cleanup = false;
this.revive = true;
for (var option in options) {
if (options.hasOwnProperty(option)) {
this[option] = options[option];
}
}
this.refcode = this.prefix + 'id';
this.origcode = this.prefix + 'original';
this.buildcode = this.prefix + '.';
this.valuecode = this.prefix + 'v';
}
if (module)
module.exports = Resurrect;
/**
* Portable access to the global object (window, global).
* Uses indirect eval.
* @constant
*/
Resurrect.GLOBAL = (0, eval)('this');
/**
* Escape special regular expression characters in a string.
* @param {string} string
* @returns {string} The string escaped for exact matches.
* @see http://stackoverflow.com/a/6969486
*/
Resurrect.escapeRegExp = function (string) {
return string.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
};
/* Helper Objects */
/**
* @param {string} [message]
* @constructor
*/
Resurrect.prototype.Error = function ResurrectError(message) {
this.message = message || '';
this.stack = new Error().stack;
};
Resurrect.prototype.Error.prototype = Object.create(Error.prototype);
Resurrect.prototype.Error.prototype.name = 'ResurrectError';
/**
* Resolves prototypes through the properties on an object and
* constructor names.
* @param {Object} scope
* @constructor
*/
Resurrect.NamespaceResolver = function(scope) {
this.scope = scope;
};
/**
* Gets the prototype of the given property name from an object. If
* not found, it throws an error.
* @param {string} name
* @returns {Object}
* @method
*/
Resurrect.NamespaceResolver.prototype.getPrototype = function(name) {
var constructor = this.scope[name];
if (constructor) {
return constructor.prototype;
} else {
throw new Resurrect.prototype.Error('Unknown constructor: ' + name);
}
};
/**
* Get the prototype name for an object, to be fetched later with getPrototype.
* @param {Object} object
* @returns {?string} Null if the constructor is Object.
* @method
*/
Resurrect.NamespaceResolver.prototype.getName = function(object) {
var constructor = object.constructor.name;
if (constructor == null) { // IE
var funcPattern = /^\s*function\s*([A-Za-z0-9_$]*)/;
constructor = funcPattern.exec(object.constructor)[1];
}
if (constructor === '') {
var msg = "Can't serialize objects with anonymous constructors.";
throw new Resurrect.prototype.Error(msg);
} else if (constructor === 'Object' || constructor === 'Array') {
return null;
} else {
return constructor;
}
};
/* Set the default resolver searches the global object. */
Resurrect.prototype.resolver =
new Resurrect.NamespaceResolver(Resurrect.GLOBAL);
/**
* Create a DOM node from HTML source; behaves like a constructor.
* @param {string} html
* @constructor
*/
Resurrect.Node = function(html) {
var div = document.createElement('div');
div.innerHTML = html;
return div.firstChild;
};
/* Type Tests */
/**
* @param {string} type
* @returns {Function} A function that tests for type.
*/
Resurrect.is = function(type) {
var string = '[object ' + type + ']';
return function(object) {
return Object.prototype.toString.call(object) === string;
};
};
Resurrect.isArray = Resurrect.is('Array');
Resurrect.isString = Resurrect.is('String');
Resurrect.isBoolean = Resurrect.is('Boolean');
Resurrect.isNumber = Resurrect.is('Number');
Resurrect.isFunction = Resurrect.is('Function');
Resurrect.isDate = Resurrect.is('Date');
Resurrect.isRegExp = Resurrect.is('RegExp');
Resurrect.isObject = Resurrect.is('Object');
Resurrect.isAtom = function(object) {
return !Resurrect.isObject(object) && !Resurrect.isArray(object);
};
/**
* @param {*} object
* @returns {boolean} True if object is a primitive or a primitive wrapper.
*/
Resurrect.isPrimitive = function(object) {
return object == null ||
Resurrect.isNumber(object) ||
Resurrect.isString(object) ||
Resurrect.isBoolean(object);
};
/* Methods */
/**
* Create a reference (encoding) to an object.
* @param {(Object|undefined)} object
* @returns {Object}
* @method
*/
Resurrect.prototype.ref = function(object) {
var ref = {};
if (object === undefined) {
ref[this.prefix] = -1;
} else {
ref[this.prefix] = object[this.refcode];
}
return ref;
};
/**
* Lookup an object in the table by reference object.
* @param {Object} ref
* @returns {(Object|undefined)}
* @method
*/
Resurrect.prototype.deref = function(ref) {
return this.table[ref[this.prefix]];
};
/**
* Put a temporary identifier on an object and store it in the table.
* @param {Object} object
* @returns {number} The unique identifier number.
* @method
*/
Resurrect.prototype.tag = function(object) {
if (this.revive) {
var constructor = this.resolver.getName(object);
if (constructor) {
var proto = Object.getPrototypeOf(object);
if (this.resolver.getPrototype(constructor) !== proto) {
throw new this.Error('Constructor mismatch!');
} else {
object[this.prefix] = constructor;
}
}
}
object[this.refcode] = this.table.length;
this.table.push(object);
return object[this.refcode];
};
/**
* Create a builder object (encoding) for serialization.
* @param {string} name The name of the constructor.
* @param value The value to pass to the constructor.
* @returns {Object}
* @method
*/
Resurrect.prototype.builder = function(name, value) {
var builder = {};
builder[this.buildcode] = name;
builder[this.valuecode] = value;
return builder;
};
/**
* Build a value from a deserialized builder.
* @param {Object} ref
* @returns {Object}
* @method
* @see http://stackoverflow.com/a/14378462
* @see http://nullprogram.com/blog/2013/03/24/
*/
Resurrect.prototype.build = function(ref) {
var type = ref[this.buildcode].split(/\./).reduce(function(object, name) {
return object[name];
}, Resurrect.GLOBAL);
/* Brilliant hack by kybernetikos: */
var args = [null].concat(ref[this.valuecode]);
var factory = type.bind.apply(type, args);
var result = new factory();
if (Resurrect.isPrimitive(result)) {
return result.valueOf(); // unwrap
} else {
return result;
}
};
/**
* Dereference or build an object or value from an encoding.
* @param {Object} ref
* @returns {(Object|undefined)}
* @method
*/
Resurrect.prototype.decode = function(ref) {
if (this.prefix in ref) {
return this.deref(ref);
} else if (this.buildcode in ref) {
return this.build(ref);
} else {
throw new this.Error('Unknown encoding.');
}
};
/**
* @param {Object} object
* @returns {boolean} True if the provided object is tagged for serialization.
* @method
*/
Resurrect.prototype.isTagged = function(object) {
return (this.refcode in object) && (object[this.refcode] != null);
};
/**
* Visit root and all its ancestors, visiting atoms with f.
* @param {*} root
* @param {Function} f
* @param {Function} replacer
* @returns {*} A fresh copy of root to be serialized.
* @method
*/
Resurrect.prototype.visit = function(root, f, replacer) {
if (Resurrect.isAtom(root)) {
return f(root);
} else if (!this.isTagged(root)) {
var copy = null;
if (Resurrect.isArray(root)) {
copy = [];
root[this.refcode] = this.tag(copy);
for (var i = 0; i < root.length; i++) {
copy.push(this.visit(root[i], f, replacer));
}
} else { /* Object */
copy = Object.create(Object.getPrototypeOf(root));
root[this.refcode] = this.tag(copy);
for (var key in root) {
var value = root[key];
if (root.hasOwnProperty(key)) {
if (replacer && value !== undefined) {
// Call replacer like JSON.stringify's replacer
value = replacer.call(root, key, root[key]);
if (value === undefined) {
continue; // Omit from result
}
}
copy[key] = this.visit(value, f, replacer);
}
}
}
copy[this.origcode] = root;
return this.ref(copy);
} else {
return this.ref(root);
}
};
/**
* Manage special atom values, possibly returning an encoding.
* @param {*} atom
* @returns {*}
* @method
*/
Resurrect.prototype.handleAtom = function(atom) {
var Node = Resurrect.GLOBAL.Node || function() {};
if (Resurrect.isFunction(atom)) {
throw new this.Error("Can't serialize functions.");
} else if (atom instanceof Node) {
var xmls = new XMLSerializer();
return this.builder('Resurrect.Node', [xmls.serializeToString(atom)]);
} else if (Resurrect.isDate(atom)) {
return this.builder('Date', [atom.toISOString()]);
} else if (Resurrect.isRegExp(atom)) {
var args = atom.toString().match(/\/(.+)\/([gimy]*)/).slice(1);
return this.builder('RegExp', args);
} else if (atom === undefined) {
return this.ref(undefined);
} else if (Resurrect.isNumber(atom) && (isNaN(atom) || !isFinite(atom))) {
return this.builder('Number', [atom.toString()]);
} else {
return atom;
}
};
/**
* Hides intrusive keys from a user-supplied replacer.
* @param {Function} replacer function of two arguments (key, value)
* @returns {Function} A function that skips the replacer for intrusive keys.
* @method
*/
Resurrect.prototype.replacerWrapper = function(replacer) {
var skip = new RegExp('^' + Resurrect.escapeRegExp(this.prefix));
return function(k, v) {
if (skip.test(k)) {
return v;
} else {
return replacer(k, v);
}
};
};
/**
* Serialize an arbitrary JavaScript object, carefully preserving it.
* @param {*} object
* @param {(Function|Array)} replacer
* @param {string} space
* @method
*/
Resurrect.prototype.stringify = function(object, replacer, space) {
if (Resurrect.isFunction(replacer)) {
replacer = this.replacerWrapper(replacer);
} else if (Resurrect.isArray(replacer)) {
var acceptKeys = replacer;
replacer = function(k, v) {
return acceptKeys.indexOf(k) >= 0 ? v : undefined;
};
}
if (Resurrect.isAtom(object)) {
return JSON.stringify(this.handleAtom(object), replacer, space);
} else {
this.table = [];
this.visit(object, this.handleAtom.bind(this), replacer);
for (var i = 0; i < this.table.length; i++) {
if (this.cleanup) {
delete this.table[i][this.origcode][this.refcode];
} else {
this.table[i][this.origcode][this.refcode] = null;
}
delete this.table[i][this.refcode];
delete this.table[i][this.origcode];
}
var table = this.table;
this.table = null;
return JSON.stringify(table, null, space);
}
};
/**
* Restore the __proto__ of the given object to the proper value.
* @param {Object} object
* @returns {Object} Its argument, or a copy, with the prototype restored.
* @method
*/
Resurrect.prototype.fixPrototype = function(object) {
if (this.prefix in object) {
var name = object[this.prefix];
var prototype = this.resolver.getPrototype(name);
if ('__proto__' in object) {
object.__proto__ = prototype;
if (this.cleanup) {
delete object[this.prefix];
}
return object;
} else { // IE
var copy = Object.create(prototype);
for (var key in object) {
if (object.hasOwnProperty(key) && key !== this.prefix) {
copy[key] = object[key];
}
}
return copy;
}
} else {
return object;
}
};
/**
* Deserialize an encoded object, restoring circularity and behavior.
* @param {string} string
* @returns {*} The decoded object or value.
* @method
*/
Resurrect.prototype.resurrect = function(string) {
var result = null;
var data = JSON.parse(string);
if (Resurrect.isArray(data)) {
this.table = data;
/* Restore __proto__. */
if (this.revive) {
for (var i = 0; i < this.table.length; i++) {
this.table[i] = this.fixPrototype(this.table[i]);
}
}
/* Re-establish object references and construct atoms. */
for (i = 0; i < this.table.length; i++) {
var object = this.table[i];
for (var key in object) {
if (object.hasOwnProperty(key)) {
if (!(Resurrect.isAtom(object[key]))) {
object[key] = this.decode(object[key]);
}
}
}
}
result = this.table[0];
} else if (Resurrect.isObject(data)) {
this.table = [];
result = this.decode(data);
} else {
result = data;
}
this.table = null;
return result;
};

View file

@ -1,185 +0,0 @@
var fs = require('fs');
var mkdirp = require('mkdirp').sync;
var removeDir = require('rimraf').sync;
var Resurrect = require('../lib/resurrect');
var compare = require('fast-json-patch').compare;
var path = require('path');
var $ = require('cheerio');
var Matter = require('../../build/matter-dev.js');
Matter.Example = require('../../demo/js/Examples.js');
Matter.Demo = require('../../demo/js/Demo.js');
var demo,
frames = 10,
refsPath = 'test/node/refs',
diffsPath = 'test/node/diffs';
var update = arg('--update'),
updateAll = typeof arg('--updateAll') !== 'undefined',
diff = arg('--diff');
var resurrect = new Resurrect({ cleanup: true, revive: false }),
created = [],
changed = [];
var test = function() {
var demos = getDemoNames();
removeDir(diffsPath);
if (diff) {
mkdirp(diffsPath);
}
for (var i = 0; i < demos.length; i += 1) {
demo = demos[i];
var hasChanged = false,
hasCreated = false,
forceUpdate = update === demo || updateAll,
worldStartPath = refsPath + '/' + demo + '/' + demo + '-0.json',
worldEndPath = refsPath + '/' + demo + '/' + demo + '-' + frames + '.json',
worldStartDiffPath = diffsPath + '/' + demo + '/' + demo + '-0.json',
worldEndDiffPath = diffsPath + '/' + demo + '/' + demo + '-' + frames + '.json';
var _demo = Matter.Demo.create(),
engine = Matter.Example.engine(_demo),
runner = Matter.Runner.create();
_demo.engine = engine;
_demo.engine.render = {};
_demo.engine.render.options = {};
_demo.runner = runner;
_demo.render = { options: {} };
if (!(demo in Matter.Example)) {
throw '\'' + demo + '\' is not defined in Matter.Example';
}
Matter.Demo.reset(_demo);
Matter.Example[demo](_demo);
var worldStart = JSON.parse(resurrect.stringify(engine.world, precisionLimiter));
for (var j = 0; j <= frames; j += 1) {
Matter.Runner.tick(runner, engine, j * runner.delta);
}
var worldEnd = JSON.parse(resurrect.stringify(engine.world, precisionLimiter));
if (fs.existsSync(worldStartPath)) {
var worldStartRef = JSON.parse(fs.readFileSync(worldStartPath));
var worldStartDiff = compare(worldStartRef, worldStart);
if (worldStartDiff.length !== 0) {
if (diff) {
writeFile(worldStartDiffPath, JSON.stringify(worldStartDiff, precisionLimiter, 2));
}
if (forceUpdate) {
hasCreated = true;
writeFile(worldStartPath, JSON.stringify(worldStart, precisionLimiter, 2));
} else {
hasChanged = true;
}
}
} else {
hasCreated = true;
writeFile(worldStartPath, JSON.stringify(worldStart, precisionLimiter, 2));
}
if (fs.existsSync(worldEndPath)) {
var worldEndRef = JSON.parse(fs.readFileSync(worldEndPath));
var worldEndDiff = compare(worldEndRef, worldEnd);
if (worldEndDiff.length !== 0) {
if (diff) {
writeFile(worldEndDiffPath, JSON.stringify(worldEndDiff, precisionLimiter, 2));
}
if (forceUpdate) {
hasCreated = true;
writeFile(worldEndPath, JSON.stringify(worldEnd, precisionLimiter, 2));
} else {
hasChanged = true;
}
}
} else {
hasCreated = true;
writeFile(worldEndPath, JSON.stringify(worldEnd, precisionLimiter, 2));
}
if (hasChanged) {
changed.push("'" + demo + "'");
process.stdout.write('x');
} else if (hasCreated) {
created.push("'" + demo + "'");
process.stdout.write('+');
} else {
process.stdout.write('.');
}
}
if (created.length > 0) {
console.log('\nupdated', created.join(', '));
}
var isOk = changed.length === 0 ? 1 : 0;
console.log('');
if (isOk) {
console.log('ok');
} else {
console.log('\nchanges detected on:');
console.log(changed.join(', '));
console.log('\nreview, then --update [name] or --updateAll');
console.log('use --diff for diff log');
}
setTimeout(function() {
process.exit(!isOk);
}, 100);
};
var precisionLimiter = function(key, value) {
if (typeof value === 'number') {
return parseFloat(value.toFixed(5));
}
return value;
};
function arg(name) {
var index = process.argv.indexOf(name);
if (index >= 0) {
return process.argv[index + 1] || true;
}
return undefined;
}
var getDemoNames = function() {
var demos = [],
skip = [
'terrain', 'svg', 'concave',
'slingshot', 'views', 'raycasting',
'events', 'collisionFiltering', 'sleeping',
'attractors'
];
$('#demo-select option', fs.readFileSync('demo/index.html').toString())
.each(function() {
var name = $(this).val();
if (skip.indexOf(name) === -1) {
demos.push(name);
}
});
return demos;
};
var writeFile = function(filePath, string) {
mkdirp(path.dirname(filePath));
fs.writeFileSync(filePath, string);
};
test();

77
webpack.config.js Normal file
View file

@ -0,0 +1,77 @@
/* eslint-env es6 */
"use strict";
const webpack = require('webpack');
const path = require('path');
const pkg = require('./package.json');
const execSync = require('child_process').execSync;
module.exports = (env = {}) => {
const minimize = env.MINIMIZE || false;
const alpha = env.ALPHA || false;
const maxSize = minimize ? 100 * 1024 : 512 * 1024;
const isDevServer = process.env.WEBPACK_DEV_SERVER;
const commitHash = execSync('git rev-parse --short HEAD').toString().trim();
const version = !alpha ? pkg.version : `${pkg.version}-alpha+${commitHash}`;
const date = new Date().toISOString().slice(0, 10);
const name = 'matter';
const alphaInfo = 'Experimental pre-release build.\n ';
const banner =
` ${pkg.name} ${version} by @liabru (c) ${date}
${alpha ? alphaInfo : ''}${pkg.homepage}
License ${pkg.license}`;
return {
entry: { [name]: './src/module/main.js' },
output: {
library: 'Matter',
libraryTarget: 'umd',
umdNamedDefine: true,
globalObject: 'this',
path: path.resolve(__dirname, './build'),
filename: `[name]${alpha ? '.alpha' : ''}${minimize ? '.min' : ''}.js`
},
node: false,
optimization: { minimize },
performance: {
maxEntrypointSize: maxSize,
maxAssetSize: maxSize
},
plugins: [
new webpack.BannerPlugin(banner),
new webpack.DefinePlugin({
__MATTER_VERSION__: JSON.stringify(!isDevServer ? version : '*'),
})
],
externals: {
'poly-decomp': {
commonjs: 'poly-decomp',
commonjs2: 'poly-decomp',
amd: 'poly-decomp',
root: 'decomp'
}
},
devServer: {
contentBase: [
path.resolve(__dirname, './demo'),
path.resolve(__dirname, './examples'),
path.resolve(__dirname, './build')
],
open: true,
openPage: '',
compress: true,
port: 8000,
proxy: {
'/build': {
target: 'http://localhost:8000/',
pathRewrite: { '^/build' : '/' }
},
'/examples': {
target: 'http://localhost:8000/',
pathRewrite: { '^/examples' : '/' }
}
}
}
};
};

View file

@ -0,0 +1,50 @@
/* eslint-env es6 */
"use strict";
const webpack = require('webpack');
const path = require('path');
const pkg = require('./package.json');
const execSync = require('child_process').execSync;
module.exports = (env = {}) => {
const minimize = env.MINIMIZE || false;
const edge = env.EDGE || false;
const maxSize = minimize ? 100 * 1024 : 512 * 1024;
const commitHash = execSync('git rev-parse --short HEAD').toString().trim();
const version = !edge ? pkg.version : `${pkg.version}-alpha-${commitHash}`;
const date = new Date().toISOString().slice(0, 10);
const name = 'matter-examples';
const banner = `${name} ${version} by @liabru ${date}
${pkg.homepage}
License ${pkg.license}`;
return {
entry: './examples/index.js',
output: {
library: 'Example',
libraryTarget: 'umd',
umdNamedDefine: true,
globalObject: 'this',
path: path.resolve(__dirname, './demo/js'),
filename: `Examples${minimize ? '.min' : ''}.js`
},
node: false,
optimization: { minimize },
performance: {
maxEntrypointSize: maxSize,
maxAssetSize: maxSize
},
plugins: [
new webpack.BannerPlugin(banner)
],
externals: {
'matter-js': {
commonjs: 'matter-js',
commonjs2: 'matter-js',
amd: 'matter-js',
root: 'Matter'
}
}
};
};

11
yuidoc.json Normal file
View file

@ -0,0 +1,11 @@
{
"name": "Matter.js Physics Engine API Docs",
"description": "a 2D rigid body physics engine for the web",
"url": "http://brm.io/matter-js/",
"options": {
"linkNatives": true,
"outdir": "doc/build",
"themedir": "matter-doc-theme",
"paths": "src"
}
}