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

added Matter.Plugin initial implementation

This commit is contained in:
liabru 2016-07-31 18:32:03 +01:00
parent 098f224cad
commit e84c537d29
7 changed files with 990 additions and 431 deletions

View file

@ -1,375 +1,381 @@
/**
* The Matter.js demo page controller and example runner.
*
* NOTE: For the actual example code, refer to the source files in `/examples/`.
*
* @class Demo
*/
(function() {
var _isBrowser = typeof window !== 'undefined' && window.location,
_useInspector = _isBrowser && window.location.hash.indexOf('-inspect') !== -1,
_isMobile = _isBrowser && /(ipad|iphone|ipod|android)/gi.test(navigator.userAgent),
_isAutomatedTest = !_isBrowser || window._phantom;
var Matter = _isBrowser ? window.Matter : require('../../build/matter-dev.js');
var Demo = {};
Matter.Demo = Demo;
if (!_isBrowser) {
module.exports = Demo;
window = {};
}
// Matter aliases
var Body = Matter.Body,
Example = Matter.Example,
Engine = Matter.Engine,
World = Matter.World,
Common = Matter.Common,
Bodies = Matter.Bodies,
Events = Matter.Events,
Mouse = Matter.Mouse,
MouseConstraint = Matter.MouseConstraint,
Runner = Matter.Runner,
Render = Matter.Render;
// MatterTools aliases
if (window.MatterTools) {
var Gui = MatterTools.Gui,
Inspector = MatterTools.Inspector;
}
Demo.create = function(options) {
var defaults = {
isManual: false,
sceneName: 'mixed',
sceneEvents: []
};
return Common.extend(defaults, options);
};
Demo.init = function() {
var demo = Demo.create();
Matter.Demo._demo = demo;
// get container element for the canvas
demo.container = document.getElementById('canvas-container');
// create an example engine (see /examples/engine.js)
demo.engine = Example.engine(demo);
// run the engine
demo.runner = Engine.run(demo.engine);
// create a debug renderer
demo.render = Render.create({
element: demo.container,
engine: demo.engine
});
// run the renderer
Render.run(demo.render);
// add a mouse controlled constraint
demo.mouseConstraint = MouseConstraint.create(demo.engine, {
element: demo.render.canvas
});
World.add(demo.engine.world, demo.mouseConstraint);
// pass mouse to renderer to enable showMousePosition
demo.render.mouse = demo.mouseConstraint.mouse;
// get the scene function name from hash
if (window.location.hash.length !== 0)
demo.sceneName = window.location.hash.replace('#', '').replace('-inspect', '');
// set up a scene with bodies
Demo.reset(demo);
Demo.setScene(demo, demo.sceneName);
// set up demo interface (see end of this file)
Demo.initControls(demo);
// pass through runner as timing for debug rendering
demo.engine.metrics.timing = demo.runner;
return demo;
};
// call init when the page has loaded fully
if (!_isAutomatedTest) {
if (window.addEventListener) {
window.addEventListener('load', Demo.init);
} else if (window.attachEvent) {
window.attachEvent('load', Demo.init);
}
}
Demo.setScene = function(demo, sceneName) {
Example[sceneName](demo);
};
// the functions for the demo interface and controls below
Demo.initControls = function(demo) {
var demoSelect = document.getElementById('demo-select'),
demoReset = document.getElementById('demo-reset');
// create a Matter.Gui
if (!_isMobile && Gui) {
demo.gui = Gui.create(demo.engine, demo.runner, demo.render);
// need to add mouse constraint back in after gui clear or load is pressed
Events.on(demo.gui, 'clear load', function() {
demo.mouseConstraint = MouseConstraint.create(demo.engine, {
element: demo.render.canvas
});
World.add(demo.engine.world, demo.mouseConstraint);
});
}
// create a Matter.Inspector
if (!_isMobile && Inspector && _useInspector) {
demo.inspector = Inspector.create(demo.engine, demo.runner, demo.render);
Events.on(demo.inspector, 'import', function() {
demo.mouseConstraint = MouseConstraint.create(demo.engine);
World.add(demo.engine.world, demo.mouseConstraint);
});
Events.on(demo.inspector, 'play', function() {
demo.mouseConstraint = MouseConstraint.create(demo.engine);
World.add(demo.engine.world, demo.mouseConstraint);
});
Events.on(demo.inspector, 'selectStart', function() {
demo.mouseConstraint.constraint.render.visible = false;
});
Events.on(demo.inspector, 'selectEnd', function() {
demo.mouseConstraint.constraint.render.visible = true;
});
}
// go fullscreen when using a mobile device
if (_isMobile) {
var body = document.body;
body.className += ' is-mobile';
demo.render.canvas.addEventListener('touchstart', Demo.fullscreen);
var fullscreenChange = function() {
var fullscreenEnabled = document.fullscreenEnabled || document.mozFullScreenEnabled || document.webkitFullscreenEnabled;
// delay fullscreen styles until fullscreen has finished changing
setTimeout(function() {
if (fullscreenEnabled) {
body.className += ' is-fullscreen';
} else {
body.className = body.className.replace('is-fullscreen', '');
}
}, 2000);
};
document.addEventListener('webkitfullscreenchange', fullscreenChange);
document.addEventListener('mozfullscreenchange', fullscreenChange);
document.addEventListener('fullscreenchange', fullscreenChange);
}
// keyboard controls
document.onkeypress = function(keys) {
// shift + a = toggle manual
if (keys.shiftKey && keys.keyCode === 65) {
Demo.setManualControl(demo, !demo.isManual);
}
// shift + q = step
if (keys.shiftKey && keys.keyCode === 81) {
if (!demo.isManual) {
Demo.setManualControl(demo, true);
}
Runner.tick(demo.runner, demo.engine);
}
};
// initialise demo selector
demoSelect.value = demo.sceneName;
Demo.setUpdateSourceLink(demo.sceneName);
demoSelect.addEventListener('change', function(e) {
Demo.reset(demo);
Demo.setScene(demo,demo.sceneName = e.target.value);
if (demo.gui) {
Gui.update(demo.gui);
}
var scrollY = window.scrollY;
window.location.hash = demo.sceneName;
window.scrollY = scrollY;
Demo.setUpdateSourceLink(demo.sceneName);
});
demoReset.addEventListener('click', function(e) {
Demo.reset(demo);
Demo.setScene(demo, demo.sceneName);
if (demo.gui) {
Gui.update(demo.gui);
}
Demo.setUpdateSourceLink(demo.sceneName);
});
};
Demo.setUpdateSourceLink = function(sceneName) {
var demoViewSource = document.getElementById('demo-view-source'),
sourceUrl = 'https://github.com/liabru/matter-js/blob/master/examples';
demoViewSource.setAttribute('href', sourceUrl + '/' + sceneName + '.js');
};
Demo.setManualControl = function(demo, isManual) {
var engine = demo.engine,
world = engine.world,
runner = demo.runner;
demo.isManual = isManual;
if (demo.isManual) {
Runner.stop(runner);
// continue rendering but not updating
(function render(time){
runner.frameRequestId = window.requestAnimationFrame(render);
Events.trigger(engine, 'beforeUpdate');
Events.trigger(engine, 'tick');
engine.render.controller.world(engine);
Events.trigger(engine, 'afterUpdate');
})();
} else {
Runner.stop(runner);
Runner.start(runner, engine);
}
};
Demo.fullscreen = function(demo) {
var _fullscreenElement = demo.render.canvas;
if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement) {
if (_fullscreenElement.requestFullscreen) {
_fullscreenElement.requestFullscreen();
} else if (_fullscreenElement.mozRequestFullScreen) {
_fullscreenElement.mozRequestFullScreen();
} else if (_fullscreenElement.webkitRequestFullscreen) {
_fullscreenElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
}
}
};
Demo.reset = function(demo) {
var world = demo.engine.world,
i;
World.clear(world);
Engine.clear(demo.engine);
// clear scene graph (if defined in controller)
if (demo.render) {
var renderController = demo.render.controller;
if (renderController && renderController.clear)
renderController.clear(demo.render);
}
// clear all scene events
if (demo.engine.events) {
for (i = 0; i < demo.sceneEvents.length; i++)
Events.off(demo.engine, demo.sceneEvents[i]);
}
if (demo.mouseConstraint && demo.mouseConstraint.events) {
for (i = 0; i < demo.sceneEvents.length; i++)
Events.off(demo.mouseConstraint, demo.sceneEvents[i]);
}
if (world.events) {
for (i = 0; i < demo.sceneEvents.length; i++)
Events.off(world, demo.sceneEvents[i]);
}
if (demo.runner && demo.runner.events) {
for (i = 0; i < demo.sceneEvents.length; i++)
Events.off(demo.runner, demo.sceneEvents[i]);
}
if (demo.render && demo.render.events) {
for (i = 0; i < demo.sceneEvents.length; i++)
Events.off(demo.render, demo.sceneEvents[i]);
}
demo.sceneEvents = [];
// reset id pool
Body._nextCollidingGroupId = 1;
Body._nextNonCollidingGroupId = -1;
Body._nextCategory = 0x0001;
Common._nextId = 0;
// reset random seed
Common._seed = 0;
// reset mouse offset and scale (only required for Demo.views)
if (demo.mouseConstraint) {
Mouse.setScale(demo.mouseConstraint.mouse, { x: 1, y: 1 });
Mouse.setOffset(demo.mouseConstraint.mouse, { x: 0, y: 0 });
}
demo.engine.enableSleeping = false;
demo.engine.world.gravity.y = 1;
demo.engine.world.gravity.x = 0;
demo.engine.timing.timeScale = 1;
var offset = 5;
World.add(world, [
Bodies.rectangle(400, -offset, 800.5 + 2 * offset, 50.5, { isStatic: true }),
Bodies.rectangle(400, 600 + offset, 800.5 + 2 * offset, 50.5, { isStatic: true }),
Bodies.rectangle(800 + offset, 300, 50.5, 600.5 + 2 * offset, { isStatic: true }),
Bodies.rectangle(-offset, 300, 50.5, 600.5 + 2 * offset, { isStatic: true })
]);
if (demo.mouseConstraint) {
World.add(world, demo.mouseConstraint);
}
if (demo.render) {
var renderOptions = demo.render.options;
renderOptions.wireframes = true;
renderOptions.hasBounds = false;
renderOptions.showDebug = false;
renderOptions.showBroadphase = false;
renderOptions.showBounds = false;
renderOptions.showVelocity = false;
renderOptions.showCollisions = false;
renderOptions.showAxes = false;
renderOptions.showPositions = false;
renderOptions.showAngleIndicator = true;
renderOptions.showIds = false;
renderOptions.showShadows = false;
renderOptions.showVertexNumbers = false;
renderOptions.showConvexHulls = false;
renderOptions.showInternalEdges = false;
renderOptions.showSeparations = false;
renderOptions.background = '#fff';
if (_isMobile) {
renderOptions.showDebug = true;
}
}
};
})();
/**
* The Matter.js demo page controller and example runner.
*
* NOTE: For the actual example code, refer to the source files in `/examples/`.
*
* @class Demo
*/
(function() {
var _isBrowser = typeof window !== 'undefined' && window.location,
_useInspector = _isBrowser && window.location.hash.indexOf('-inspect') !== -1,
_isMobile = _isBrowser && /(ipad|iphone|ipod|android)/gi.test(navigator.userAgent),
_isAutomatedTest = !_isBrowser || window._phantom;
var Matter = _isBrowser ? window.Matter : require('../../build/matter-dev.js');
var Demo = {};
Matter.Demo = Demo;
if (!_isBrowser) {
module.exports = Demo;
window = {};
}
// Matter aliases
var Body = Matter.Body,
Example = Matter.Example,
Engine = Matter.Engine,
World = Matter.World,
Common = Matter.Common,
Bodies = Matter.Bodies,
Events = Matter.Events,
Mouse = Matter.Mouse,
MouseConstraint = Matter.MouseConstraint,
Runner = Matter.Runner,
Render = Matter.Render;
// MatterTools aliases
if (window.MatterTools) {
var Gui = MatterTools.Gui,
Inspector = MatterTools.Inspector;
}
Demo.create = function(options) {
var defaults = {
isManual: false,
sceneName: 'mixed',
sceneEvents: []
};
return Common.extend(defaults, options);
};
Demo.init = function() {
var demo = Demo.create();
Matter.Demo._demo = demo;
Matter.use(
'matter-plugin-fake',
'matter-plugin-2',
window.MatterPlugin
);
// get container element for the canvas
demo.container = document.getElementById('canvas-container');
// create an example engine (see /examples/engine.js)
demo.engine = Example.engine(demo);
// run the engine
demo.runner = Engine.run(demo.engine);
// create a debug renderer
demo.render = Render.create({
element: demo.container,
engine: demo.engine
});
// run the renderer
Render.run(demo.render);
// add a mouse controlled constraint
demo.mouseConstraint = MouseConstraint.create(demo.engine, {
element: demo.render.canvas
});
World.add(demo.engine.world, demo.mouseConstraint);
// pass mouse to renderer to enable showMousePosition
demo.render.mouse = demo.mouseConstraint.mouse;
// get the scene function name from hash
if (window.location.hash.length !== 0)
demo.sceneName = window.location.hash.replace('#', '').replace('-inspect', '');
// set up a scene with bodies
Demo.reset(demo);
Demo.setScene(demo, demo.sceneName);
// set up demo interface (see end of this file)
Demo.initControls(demo);
// pass through runner as timing for debug rendering
demo.engine.metrics.timing = demo.runner;
return demo;
};
// call init when the page has loaded fully
if (!_isAutomatedTest) {
if (window.addEventListener) {
window.addEventListener('load', Demo.init);
} else if (window.attachEvent) {
window.attachEvent('load', Demo.init);
}
}
Demo.setScene = function(demo, sceneName) {
Example[sceneName](demo);
};
// the functions for the demo interface and controls below
Demo.initControls = function(demo) {
var demoSelect = document.getElementById('demo-select'),
demoReset = document.getElementById('demo-reset');
// create a Matter.Gui
if (!_isMobile && Gui) {
demo.gui = Gui.create(demo.engine, demo.runner, demo.render);
// need to add mouse constraint back in after gui clear or load is pressed
Events.on(demo.gui, 'clear load', function() {
demo.mouseConstraint = MouseConstraint.create(demo.engine, {
element: demo.render.canvas
});
World.add(demo.engine.world, demo.mouseConstraint);
});
}
// create a Matter.Inspector
if (!_isMobile && Inspector && _useInspector) {
demo.inspector = Inspector.create(demo.engine, demo.runner, demo.render);
Events.on(demo.inspector, 'import', function() {
demo.mouseConstraint = MouseConstraint.create(demo.engine);
World.add(demo.engine.world, demo.mouseConstraint);
});
Events.on(demo.inspector, 'play', function() {
demo.mouseConstraint = MouseConstraint.create(demo.engine);
World.add(demo.engine.world, demo.mouseConstraint);
});
Events.on(demo.inspector, 'selectStart', function() {
demo.mouseConstraint.constraint.render.visible = false;
});
Events.on(demo.inspector, 'selectEnd', function() {
demo.mouseConstraint.constraint.render.visible = true;
});
}
// go fullscreen when using a mobile device
if (_isMobile) {
var body = document.body;
body.className += ' is-mobile';
demo.render.canvas.addEventListener('touchstart', Demo.fullscreen);
var fullscreenChange = function() {
var fullscreenEnabled = document.fullscreenEnabled || document.mozFullScreenEnabled || document.webkitFullscreenEnabled;
// delay fullscreen styles until fullscreen has finished changing
setTimeout(function() {
if (fullscreenEnabled) {
body.className += ' is-fullscreen';
} else {
body.className = body.className.replace('is-fullscreen', '');
}
}, 2000);
};
document.addEventListener('webkitfullscreenchange', fullscreenChange);
document.addEventListener('mozfullscreenchange', fullscreenChange);
document.addEventListener('fullscreenchange', fullscreenChange);
}
// keyboard controls
document.onkeypress = function(keys) {
// shift + a = toggle manual
if (keys.shiftKey && keys.keyCode === 65) {
Demo.setManualControl(demo, !demo.isManual);
}
// shift + q = step
if (keys.shiftKey && keys.keyCode === 81) {
if (!demo.isManual) {
Demo.setManualControl(demo, true);
}
Runner.tick(demo.runner, demo.engine);
}
};
// initialise demo selector
demoSelect.value = demo.sceneName;
Demo.setUpdateSourceLink(demo.sceneName);
demoSelect.addEventListener('change', function(e) {
Demo.reset(demo);
Demo.setScene(demo,demo.sceneName = e.target.value);
if (demo.gui) {
Gui.update(demo.gui);
}
var scrollY = window.scrollY;
window.location.hash = demo.sceneName;
window.scrollY = scrollY;
Demo.setUpdateSourceLink(demo.sceneName);
});
demoReset.addEventListener('click', function(e) {
Demo.reset(demo);
Demo.setScene(demo, demo.sceneName);
if (demo.gui) {
Gui.update(demo.gui);
}
Demo.setUpdateSourceLink(demo.sceneName);
});
};
Demo.setUpdateSourceLink = function(sceneName) {
var demoViewSource = document.getElementById('demo-view-source'),
sourceUrl = 'https://github.com/liabru/matter-js/blob/master/examples';
demoViewSource.setAttribute('href', sourceUrl + '/' + sceneName + '.js');
};
Demo.setManualControl = function(demo, isManual) {
var engine = demo.engine,
world = engine.world,
runner = demo.runner;
demo.isManual = isManual;
if (demo.isManual) {
Runner.stop(runner);
// continue rendering but not updating
(function render(time){
runner.frameRequestId = window.requestAnimationFrame(render);
Events.trigger(engine, 'beforeUpdate');
Events.trigger(engine, 'tick');
engine.render.controller.world(engine);
Events.trigger(engine, 'afterUpdate');
})();
} else {
Runner.stop(runner);
Runner.start(runner, engine);
}
};
Demo.fullscreen = function(demo) {
var _fullscreenElement = demo.render.canvas;
if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement) {
if (_fullscreenElement.requestFullscreen) {
_fullscreenElement.requestFullscreen();
} else if (_fullscreenElement.mozRequestFullScreen) {
_fullscreenElement.mozRequestFullScreen();
} else if (_fullscreenElement.webkitRequestFullscreen) {
_fullscreenElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
}
}
};
Demo.reset = function(demo) {
var world = demo.engine.world,
i;
World.clear(world);
Engine.clear(demo.engine);
// clear scene graph (if defined in controller)
if (demo.render) {
var renderController = demo.render.controller;
if (renderController && renderController.clear)
renderController.clear(demo.render);
}
// clear all scene events
if (demo.engine.events) {
for (i = 0; i < demo.sceneEvents.length; i++)
Events.off(demo.engine, demo.sceneEvents[i]);
}
if (demo.mouseConstraint && demo.mouseConstraint.events) {
for (i = 0; i < demo.sceneEvents.length; i++)
Events.off(demo.mouseConstraint, demo.sceneEvents[i]);
}
if (world.events) {
for (i = 0; i < demo.sceneEvents.length; i++)
Events.off(world, demo.sceneEvents[i]);
}
if (demo.runner && demo.runner.events) {
for (i = 0; i < demo.sceneEvents.length; i++)
Events.off(demo.runner, demo.sceneEvents[i]);
}
if (demo.render && demo.render.events) {
for (i = 0; i < demo.sceneEvents.length; i++)
Events.off(demo.render, demo.sceneEvents[i]);
}
demo.sceneEvents = [];
// reset id pool
Body._nextCollidingGroupId = 1;
Body._nextNonCollidingGroupId = -1;
Body._nextCategory = 0x0001;
Common._nextId = 0;
// reset random seed
Common._seed = 0;
// reset mouse offset and scale (only required for Demo.views)
if (demo.mouseConstraint) {
Mouse.setScale(demo.mouseConstraint.mouse, { x: 1, y: 1 });
Mouse.setOffset(demo.mouseConstraint.mouse, { x: 0, y: 0 });
}
demo.engine.enableSleeping = false;
demo.engine.world.gravity.y = 1;
demo.engine.world.gravity.x = 0;
demo.engine.timing.timeScale = 1;
var offset = 5;
World.add(world, [
Bodies.rectangle(400, -offset, 800.5 + 2 * offset, 50.5, { isStatic: true }),
Bodies.rectangle(400, 600 + offset, 800.5 + 2 * offset, 50.5, { isStatic: true }),
Bodies.rectangle(800 + offset, 300, 50.5, 600.5 + 2 * offset, { isStatic: true }),
Bodies.rectangle(-offset, 300, 50.5, 600.5 + 2 * offset, { isStatic: true })
]);
if (demo.mouseConstraint) {
World.add(world, demo.mouseConstraint);
}
if (demo.render) {
var renderOptions = demo.render.options;
renderOptions.wireframes = true;
renderOptions.hasBounds = false;
renderOptions.showDebug = false;
renderOptions.showBroadphase = false;
renderOptions.showBounds = false;
renderOptions.showVelocity = false;
renderOptions.showCollisions = false;
renderOptions.showAxes = false;
renderOptions.showPositions = false;
renderOptions.showAngleIndicator = true;
renderOptions.showIds = false;
renderOptions.showShadows = false;
renderOptions.showVertexNumbers = false;
renderOptions.showConvexHulls = false;
renderOptions.showInternalEdges = false;
renderOptions.showSeparations = false;
renderOptions.background = '#fff';
if (_isMobile) {
renderOptions.showDebug = true;
}
}
};
})();

54
examples/plugin.js Normal file
View file

@ -0,0 +1,54 @@
(function() {
var chain = Matter.Common.chain,
last = Matter.Common.last;
var MatterPlugin = {
name: 'matter-plugin',
version: '0.2.0',
for: 'matter-js@^0.10.0',
uses: [
{
plugin: 'matter-plugin-2@^0.0.1',
options: {
message: 'hello'
}
},
'matter-plugin-3@^0.10.0'
],
options: {
thing: 1
},
install: function(base) {
base.Engine.create = chain(
Matter.Engine.create,
MatterPlugin.engineCreate
);
base.Body.create = chain(
MatterPlugin.bodyCreate,
Matter.Body.create
);
},
engineCreate: function(element, options, engine) {
engine = last(arguments);
console.log('2nd patched engine create!', engine);
},
bodyCreate: function(options) {
console.log('patched body create!', arguments);
}
};
Matter.Plugin.exports(MatterPlugin);
window.MatterPlugin = MatterPlugin;
})();

34
examples/plugin2.js Normal file
View file

@ -0,0 +1,34 @@
(function() {
var chain = Matter.Common.chain;
var MatterPlugin2 = {
name: 'matter-plugin-2',
version: '0.1.0',
for: 'matter-js@^0.10.0',
uses: ['matter-plugin'],
options: {
thing: 1
},
install: function(matter) {
matter.Engine.create = chain(
matter.Engine.create,
MatterPlugin2.engineCreate
);
},
engineCreate: function(element, options, engine) {
console.log('patched engine create!', arguments);
}
};
Matter.Plugin.exports(MatterPlugin2);
window.MatterPlugin2 = MatterPlugin2;
})();

View file

@ -182,6 +182,26 @@ module.exports = Common;
Common.isArray = function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
/**
* Returns true if the object is a function.
* @method isFunction
* @param {object} obj
* @return {boolean} True if the object is a function, otherwise false
*/
Common.isFunction = function(obj) {
return typeof obj === "function";
};
/**
* Returns true if the object is a plain object.
* @method isPlainObject
* @param {object} obj
* @return {boolean} True if the object is a plain object, otherwise false
*/
Common.isPlainObject = function(obj) {
return typeof obj === 'object' && obj.constructor === Object;
};
/**
* Returns the given value clamped between a minimum and maximum value.
@ -231,7 +251,6 @@ module.exports = Common;
return performance.now();
};
/**
* Returns a random value between a minimum and a maximum value inclusive.
@ -247,6 +266,12 @@ module.exports = Common;
return min + _seededRandom() * (max - min);
};
var _seededRandom = function() {
// https://gist.github.com/ngryman/3830489
Common._seed = (Common._seed * 9301 + 49297) % 233280;
return Common._seed / 233280;
};
/**
* Converts a CSS hex colour string into an integer.
* @method colorToNumber
@ -301,6 +326,7 @@ module.exports = Common;
* @method indexOf
* @param {array} haystack
* @param {object} needle
* @return {number} The position of needle in haystack, otherwise -1.
*/
Common.indexOf = function(haystack, needle) {
if (haystack.indexOf)
@ -314,10 +340,109 @@ module.exports = Common;
return -1;
};
var _seededRandom = function() {
// https://gist.github.com/ngryman/3830489
Common._seed = (Common._seed * 9301 + 49297) % 233280;
return Common._seed / 233280;
/**
* A cross browser compatible array map implementation.
* @method map
* @param {array} list
* @param {function} func
* @return {array} Values from list transformed by func.
*/
Common.map = function(list, func) {
if (list.map) {
return list.map(func);
}
var mapped = [];
for (var i = 0; i < list.length; i += 1) {
mapped.push(func(list[i]));
}
return mapped;
};
/**
* Returns the last value in an array.
* @method last
* @param {array} list
* @return {} The last value in list.
*/
Common.last = function(list) {
return list[list.length - 1];
};
/**
* Takes a directed graph and returns the partially ordered set of vertices in topological order.
* Circular dependencies are allowed.
* @method topologicalSort
* @param {object} graph
* @return {array} Partially ordered set of vertices in topological order.
*/
Common.topologicalSort = function(graph) {
// https://mgechev.github.io/javascript-algorithms/graphs_others_topological-sort.js.html
var result = [],
visited = [],
temp = [];
for (var node in graph) {
if (!visited[node] && !temp[node]) {
_topologicalSort(node, visited, temp, graph, result);
}
}
return result;
};
var _topologicalSort = function(node, visited, temp, graph, result) {
var neighbors = graph[node] || [];
temp[node] = true;
for (var i = 0; i < neighbors.length; i += 1) {
var neighbor = neighbors[i];
if (temp[neighbor]) {
// skip circular dependencies
continue;
}
if (!visited[neighbor]) {
_topologicalSort(neighbor, visited, temp, graph, result);
}
}
temp[node] = false;
visited[node] = true;
result.push(node);
};
/**
* Takes _n_ functions as arguments and returns a new function that calls them in order.
* The arguments and `this` value applied when calling the new function will be applied to every function passed.
* An additional final argument is passed to provide access to the last value returned (that was not `undefined`).
* Therefore if a passed function does not return a value, the previously returned value is passed as the final argument.
* After all passed functions have been called the new function returns the final value (if any).
* @method chain
* @param ...funcs {function} The functions to chain.
* @return {function} A new function that calls the passed functions in order.
*/
Common.chain = function() {
var funcs = Array.prototype.slice.call(arguments);
return function() {
var args = Array.prototype.slice.call(arguments),
lastResult;
for (var i = 0; i < funcs.length; i += 1) {
var result = funcs[i].apply(this, lastResult ? args.concat(lastResult) : args);
if (typeof result !== 'undefined') {
lastResult = result;
}
}
return lastResult;
};
};
})();

60
src/core/Matter.js Normal file
View file

@ -0,0 +1,60 @@
/**
* The `Matter` module is the top level namespace and includes functions for extending other modules.
*
* @class Matter
*/
var Matter = {};
module.exports = Matter;
var Plugin = require('./Plugin');
(function() {
/**
* The library name.
* @property Matter.name
* @type {String}
*/
Matter.name = 'matter-js';
/**
* The library version.
* @property Matter.version
* @type {String}
*/
Matter.version = 'master';
/**
* The plugins that have been _installed_ through `Matter.Plugin.install`. Read only.
* @property Matter.used
* @readOnly
* @type {Array}
*/
Matter.used = [];
/**
* A list of plugin dependencies to be installed. These are normally set and installed through `Matter.use`.
* Alternatively set them and install manually through `Plugin.installDependencies`.
* @property Matter.used
* @readOnly
* @type {Array}
*/
Matter.uses = [];
/**
* Installs plugins on the `Matter` namespace.
* Populates `Matter.used` with an array of the plugins in the order they were applied after dependencies were resolved.
* See `Common.use` in `Matter.Common` for more information.
* TODO: add link to wiki
* @method use
* @param ...plugins {Function} The plugins to install on `base`.
*/
Matter.use = function() {
Matter.uses = Array.prototype.slice.call(arguments);
Plugin.installDependencies(Matter);
};
})();

280
src/core/Plugin.js Normal file
View file

@ -0,0 +1,280 @@
/**
* The `Matter.Plugin` module contains utility functions that are Plugin to all modules.
*
* @class Plugin
*/
var Plugin = {};
module.exports = Plugin;
var Common = require('./Common');
(function() {
//Plugin._anonymousName = 0;
Plugin._registry = {};
Plugin.exports = function(options) {
var plugin = options;
/*plugin.uses = plugin.uses || [];
plugin.for = plugin.for || 'matter-js@*';
plugin.name = plugin.name || Plugin.anonymousName();
plugin.version = plugin.version || '0.0.0';*/
//plugin.id = plugin.name + '@' + plugin.version;
if (!Plugin.isPlugin(plugin)) {
Common.log('Plugin.exports: ' + plugin.name + ' does not implement all required fields.', 'warn');
}
if (plugin.name in Plugin._registry) {
var registered = Plugin._registry[plugin.name];
if (Plugin.versionGte(plugin.version, registered.version)) {
Plugin._registry[plugin.name] = plugin;
}
} else {
Plugin._registry[plugin.name] = plugin;
}
return plugin;
};
/**
* Returns a unique identifier for anonymous plugins.
* @method anonymousName
* @return {Number} Unique identifier name
*/
/*Plugin.anonymousName = function() {
return 'anonymous-' + Plugin._nextId++;
};*/
Plugin.isPlugin = function(obj) {
return obj && obj.name && Common.isFunction(obj.install);
};
Plugin.isUsed = function(base, name) {
return base.used.indexOf(name) > -1;
//return (',' + base.used.join(',')).indexOf(',' + name + '@') > -1;
};
Plugin.isFor = function(plugin, base) {
var parsed = Plugin.versionParse(plugin.for);
return base.name === parsed.name && Plugin.versionSatisfies(base.version, parsed.version);
};
/**
* Installs plugins on an object and ensures all dependencies are loaded.
* TODO: add link to wiki
* @method installDependencies
* @param base {} The object to install the `plugins` on.
* @return {Array} An array of the plugins in the order they were applied after dependencies were resolved.
*/
Plugin.installDependencies = function(base) {
if (!base.uses || base.uses.length === 0) {
Common.log('Plugin.installDependencies: ' + base.name + ' does not specify any dependencies to install.', 'warn');
return;
}
if (base.used && base.used.length > 0) {
Common.log('Plugin.installDependencies: ' + base.name + ' has already installed its dependencies.', 'warn');
return;
}
var dependencies = Plugin.dependencies(base),
sortedDependencies = Common.topologicalSort(dependencies),
warnings = 0;
console.log(dependencies, sortedDependencies);
for (var i = 0; i < sortedDependencies.length; i += 1) {
var plugin = Plugin.resolve(sortedDependencies[i]);
if (sortedDependencies[i] === base.name) {
continue;
}
if (!plugin) {
Common.log('Plugin.installDependencies: ' + sortedDependencies[i] + ' could not be resolved.', 'warn');
warnings += 1;
continue;
}
if (Plugin.isUsed(base, plugin.name)) {
continue;
}
if (!Plugin.isFor(plugin, base)) {
Common.log('Plugin.installDependencies: ' + plugin.name + '@' + plugin.version + ' is for ' + plugin.for + ' but used on ' + base.name + '@' + base.version + '.', 'warn');
warnings += 1;
}
var options = Common.isPlainObject(sortedDependencies[i]) ? sortedDependencies[i].options : null;
if (plugin.install) {
plugin.install(base, options);
}
base.used.push(plugin.name);
}
if (warnings > 0) {
Common.log('Plugin.installDependencies: Some dependencies may not function as expected, see above warnings.', 'warn');
}
};
Plugin.dependencies = function(base, _dependencies) {
base = Plugin.resolve(base) || base;
_dependencies = _dependencies || {};
var name = Plugin.versionParse(Plugin.dependencyName(base)).name;
if (name in _dependencies) {
return;
}
_dependencies[name] = Common.map(base.uses || [], function(dependency) {
return Plugin.versionParse(Plugin.dependencyName(dependency)).name;
});
for (var i = 0; i < _dependencies[name].length; i += 1) {
Plugin.dependencies(_dependencies[name][i], _dependencies);
}
return _dependencies;
};
Plugin.dependencyName = function(dependency) {
return (dependency.plugin && (dependency.plugin.name || dependency.plugin)) || dependency.name || dependency;
};
Plugin.resolve = function(dependency) {
if (Plugin.isPlugin(dependency)) {
return dependency;
}
var plugin = dependency.plugin && Plugin.resolve(dependency.plugin);
if (plugin) {
return plugin;
}
var parsed = Plugin.versionParse(Plugin.dependencyName(dependency));
plugin = Plugin._registry[parsed.name];
if (!plugin) {
return null;
}
if (Plugin.versionSatisfies(plugin.version, parsed.version)) {
return plugin;
}
};
Plugin.versionParse = function(name) {
return {
name: name.split('@')[0],
pattern: name.split('@')[1] || '*'
};
};
Plugin.semverParse = function(pattern) {
var parsed = {};
parsed.version = pattern;
if (+parsed.version[0] === NaN) {
parsed.operator = parsed.version[0];
parsed.version = parsed.version.substr(1);
}
parsed.parts = Common.map(parsed.version.split('.'), function(part) {
return +part;
});
return parsed;
};
Plugin.versionNumber = function(version) {
var parts = Plugin.semverParse(version).parts;
return parts[0] * 1e8 + parts[1] * 1e4 + parts[2];
};
Plugin.versionLt = function(versionA, versionB) {
return Plugin.versionNumber(versionA) < Plugin.versionNumber(versionB);
};
Plugin.versionLte = function(versionA, versionB) {
return Plugin.versionNumber(versionA) <= Plugin.versionNumber(versionB);
};
Plugin.versionGt = function(versionA, versionB) {
return Plugin.versionNumber(versionA) > Plugin.versionNumber(versionB);
};
Plugin.versionGte = function(versionA, versionB) {
return Plugin.versionNumber(versionA) >= Plugin.versionNumber(versionB);
};
Plugin.versionSatisfies = function(version, pattern) {
// https://docs.npmjs.com/misc/semver#advanced-range-syntax
var operator;
pattern = pattern || '*';
if (isNaN(+pattern[0])) {
operator = pattern[0];
pattern = pattern.substr(1);
}
var parts = Common.map(version.split('.'), function(part) {
return +part;
});
var patternParts = Common.map(pattern.split('.'), function(part) {
return +part;
});
/*var parsed = Plugin.semverParse(pattern);
if (parsed.operator === '*') {
return true;
}
if (parsed.operator === '~') {
return Plugin.versionGte(version, parsed.pattern) +
Plugin.versionLte(version, parts[0] + '.' + (+parts[1] + 1) + '.0');
}
if (parsed.operator === '^') {
return Plugin.versionGte(version, pattern) +
Plugin.versionLte(version, parts[0] + '.' + (+parts[1] + 1) + '.0');
}*/
if (operator === '*') {
return true;
}
if (operator === '~') {
return parts[0] === patternParts[0] && parts[1] === patternParts[1] && parts[2] >= patternParts[2];
}
if (operator === '^') {
if (patternParts[0] > 0) {
return parts[0] === patternParts[0] && Plugin.versionGte(version, pattern);
}
if (patternParts[1] > 0) {
return parts[2] >= patternParts[2];
}
//return parts[0] === patternParts[0] && (parts[1] >= patternParts[1] || parts[2] >= patternParts[2]);
//return '^' + parts[0] === patternParts[0] && +parts[1] >= +patternParts[1] || +parts[2] >= +patternParts[2];
}
return version === pattern;
};
})();

View file

@ -1,51 +1,51 @@
var Matter = module.exports = {};
Matter.version = 'master';
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');
Matter.Pair = require('../collision/Pair');
Matter.Query = require('../collision/Query');
Matter.Resolver = require('../collision/Resolver');
Matter.SAT = require('../collision/SAT');
Matter.Constraint = require('../constraint/Constraint');
Matter.MouseConstraint = require('../constraint/MouseConstraint');
Matter.Common = require('../core/Common');
Matter.Engine = require('../core/Engine');
Matter.Events = require('../core/Events');
Matter.Mouse = require('../core/Mouse');
Matter.Runner = require('../core/Runner');
Matter.Sleeping = require('../core/Sleeping');
// @if DEBUG
Matter.Metrics = require('../core/Metrics');
// @endif
Matter.Bodies = require('../factory/Bodies');
Matter.Composites = require('../factory/Composites');
Matter.Axes = require('../geometry/Axes');
Matter.Bounds = require('../geometry/Bounds');
Matter.Svg = require('../geometry/Svg');
Matter.Vector = require('../geometry/Vector');
Matter.Vertices = require('../geometry/Vertices');
Matter.Render = require('../render/Render');
Matter.RenderPixi = require('../render/RenderPixi');
// aliases
Matter.World.add = Matter.Composite.add;
Matter.World.remove = Matter.Composite.remove;
Matter.World.addComposite = Matter.Composite.addComposite;
Matter.World.addBody = Matter.Composite.addBody;
Matter.World.addConstraint = Matter.Composite.addConstraint;
Matter.World.clear = Matter.Composite.clear;
Matter.Engine.run = Matter.Runner.run;
var Matter = module.exports = require('../core/Matter');
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');
Matter.Pair = require('../collision/Pair');
Matter.Query = require('../collision/Query');
Matter.Resolver = require('../collision/Resolver');
Matter.SAT = require('../collision/SAT');
Matter.Constraint = require('../constraint/Constraint');
Matter.MouseConstraint = require('../constraint/MouseConstraint');
Matter.Common = require('../core/Common');
Matter.Engine = require('../core/Engine');
Matter.Events = require('../core/Events');
Matter.Mouse = require('../core/Mouse');
Matter.Runner = require('../core/Runner');
Matter.Sleeping = require('../core/Sleeping');
Matter.Plugin = require('../core/Plugin');
// @if DEBUG
Matter.Metrics = require('../core/Metrics');
// @endif
Matter.Bodies = require('../factory/Bodies');
Matter.Composites = require('../factory/Composites');
Matter.Axes = require('../geometry/Axes');
Matter.Bounds = require('../geometry/Bounds');
Matter.Svg = require('../geometry/Svg');
Matter.Vector = require('../geometry/Vector');
Matter.Vertices = require('../geometry/Vertices');
Matter.Render = require('../render/Render');
Matter.RenderPixi = require('../render/RenderPixi');
// aliases
Matter.World.add = Matter.Composite.add;
Matter.World.remove = Matter.Composite.remove;
Matter.World.addComposite = Matter.Composite.addComposite;
Matter.World.addBody = Matter.Composite.addBody;
Matter.World.addConstraint = Matter.Composite.addConstraint;
Matter.World.clear = Matter.Composite.clear;
Matter.Engine.run = Matter.Runner.run;