0
0
Fork 0
mirror of https://github.com/liabru/matter-js.git synced 2024-11-23 09:26:51 -05:00
liabru-matter-js/build/matter.js

9010 lines
284 KiB
JavaScript
Raw Normal View History

2014-02-19 09:15:05 -05:00
/**
2015-08-12 19:38:20 -04:00
* matter.js edge-master 2015-08-13
2014-02-19 09:15:05 -05:00
* http://brm.io/matter-js/
* License: MIT
*/
/**
* The MIT License (MIT)
*
* Copyright (c) 2014 Liam Brummitt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
(function() {
var Matter = {};
// Begin Matter namespace closure
// All Matter modules are included below during build
// Outro.js then closes at the end of the file
// Begin src/body/Body.js
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* The `Matter.Body` module contains methods for creating and manipulating body models.
* A `Matter.Body` is a rigid body that can be simulated by a `Matter.Engine`.
* Factories for commonly used body configurations (such as rectangles, circles and other polygons) can be found in the module `Matter.Bodies`.
*
2014-02-28 20:10:08 -05:00
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
2014-06-09 14:40:24 -04:00
2014-02-28 20:10:08 -05:00
* @class Body
*/
2014-02-19 09:15:05 -05:00
var Body = {};
(function() {
2014-06-09 14:40:24 -04:00
Body._inertiaScale = 4;
2014-07-29 11:26:49 -04:00
var _nextCollidingGroupId = 1,
2015-01-01 18:10:10 -05:00
_nextNonCollidingGroupId = -1,
_nextCategory = 0x0001;
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Creates a new rigid body model. The options parameter is an object that specifies any properties you wish to override the defaults.
* All properties have default values, and many are pre-calculated automatically based on other properties.
2015-06-29 15:58:24 -04:00
* See the properties section below for detailed information on what you can pass via the `options` object.
2014-02-28 20:10:08 -05:00
* @method create
* @param {} options
* @return {body} body
*/
2014-02-19 09:15:05 -05:00
Body.create = function(options) {
var defaults = {
2014-05-01 09:09:06 -04:00
id: Common.nextId(),
2014-03-30 14:45:30 -04:00
type: 'body',
2014-05-01 09:09:06 -04:00
label: 'Body',
2015-05-20 15:38:41 -04:00
parts: [],
2014-02-19 09:15:05 -05:00
angle: 0,
2014-05-05 14:32:51 -04:00
vertices: Vertices.fromPath('L 0 0 L 40 0 L 40 40 L 0 40'),
2014-02-19 09:15:05 -05:00
position: { x: 0, y: 0 },
force: { x: 0, y: 0 },
torque: 0,
positionImpulse: { x: 0, y: 0 },
2014-03-30 14:45:30 -04:00
constraintImpulse: { x: 0, y: 0, angle: 0 },
2015-05-20 15:38:41 -04:00
totalContacts: 0,
2014-02-19 09:15:05 -05:00
speed: 0,
angularSpeed: 0,
velocity: { x: 0, y: 0 },
angularVelocity: 0,
isStatic: false,
isSleeping: false,
motion: 0,
sleepThreshold: 60,
density: 0.001,
restitution: 0,
friction: 0.1,
2015-05-20 15:38:41 -04:00
frictionStatic: 0.5,
2014-02-19 09:15:05 -05:00
frictionAir: 0.01,
2014-07-29 11:26:49 -04:00
collisionFilter: {
category: 0x0001,
mask: 0xFFFFFFFF,
group: 0
},
slop: 0.05,
2014-05-01 09:09:06 -04:00
timeScale: 1,
render: {
visible: true,
2014-03-30 14:45:30 -04:00
sprite: {
xScale: 1,
yScale: 1
},
lineWidth: 1.5
}
2014-02-19 09:15:05 -05:00
};
var body = Common.extend(defaults, options);
2014-06-09 14:40:24 -04:00
_initProperties(body, options);
2014-02-19 09:15:05 -05:00
return body;
};
2014-02-28 20:10:08 -05:00
/**
2014-07-29 11:26:49 -04:00
* Returns the next unique group index for which bodies will collide.
* If `isNonColliding` is `true`, returns the next unique group index for which bodies will _not_ collide.
* See `body.collisionFilter` for more information.
* @method nextGroup
* @param {bool} [isNonColliding=false]
* @return {Number} Unique group index
2014-02-28 20:10:08 -05:00
*/
2014-07-29 11:26:49 -04:00
Body.nextGroup = function(isNonColliding) {
if (isNonColliding)
return _nextNonCollidingGroupId--;
return _nextCollidingGroupId++;
2014-02-19 09:15:05 -05:00
};
2015-01-01 18:10:10 -05:00
/**
* Returns the next unique category bitfield (starting after the initial default category `0x0001`).
* There are 32 available. See `body.collisionFilter` for more information.
* @method nextCategory
* @return {Number} Unique category bitfield
*/
Body.nextCategory = function() {
_nextCategory = _nextCategory << 1;
return _nextCategory;
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Initialises body properties.
2014-05-01 09:09:06 -04:00
* @method _initProperties
* @private
2014-02-28 20:10:08 -05:00
* @param {body} body
2014-06-09 14:40:24 -04:00
* @param {} options
2014-02-28 20:10:08 -05:00
*/
2014-06-09 14:40:24 -04:00
var _initProperties = function(body, options) {
2015-05-20 15:38:41 -04:00
// init required properties (order is important)
2014-12-28 13:37:43 -05:00
Body.set(body, {
bounds: body.bounds || Bounds.create(body.vertices),
positionPrev: body.positionPrev || Vector.clone(body.position),
anglePrev: body.anglePrev || body.angle,
vertices: body.vertices,
2015-05-20 15:38:41 -04:00
parts: body.parts || [body],
2014-12-28 13:37:43 -05:00
isStatic: body.isStatic,
2015-05-20 15:38:41 -04:00
isSleeping: body.isSleeping,
parent: body.parent || body
2014-12-28 13:37:43 -05:00
});
2014-02-19 09:15:05 -05:00
Vertices.rotate(body.vertices, body.angle, body.position);
Axes.rotate(body.axes, body.angle);
2014-07-29 11:26:49 -04:00
Bounds.update(body.bounds, body.vertices, body.velocity);
2014-02-19 09:15:05 -05:00
2014-06-09 14:40:24 -04:00
// allow options to override the automatically calculated properties
2014-12-28 13:37:43 -05:00
Body.set(body, {
axes: options.axes || body.axes,
area: options.area || body.area,
mass: options.mass || body.mass,
inertia: options.inertia || body.inertia
});
2014-06-09 14:40:24 -04:00
// render properties
var defaultFillStyle = (body.isStatic ? '#eeeeee' : Common.choose(['#556270', '#4ECDC4', '#C7F464', '#FF6B6B', '#C44D58'])),
defaultStrokeStyle = Common.shadeColor(defaultFillStyle, -20);
body.render.fillStyle = body.render.fillStyle || defaultFillStyle;
body.render.strokeStyle = body.render.strokeStyle || defaultStrokeStyle;
2014-05-01 09:09:06 -04:00
};
2014-12-28 13:37:43 -05:00
/**
* Given a property and a value (or map of), sets the property(s) on the body, using the appropriate setter functions if they exist.
* Prefer to use the actual setter functions in performance critical situations.
* @method set
* @param {body} body
* @param {} settings A property name (or map of properties and values) to set on the body.
* @param {} value The value to set if `settings` is a single property name.
*/
Body.set = function(body, settings, value) {
var property;
if (typeof settings === 'string') {
property = settings;
settings = {};
settings[property] = value;
}
for (property in settings) {
value = settings[property];
if (!settings.hasOwnProperty(property))
continue;
switch (property) {
case 'isStatic':
Body.setStatic(body, value);
break;
case 'isSleeping':
Sleeping.set(body, value);
break;
case 'mass':
Body.setMass(body, value);
break;
case 'density':
Body.setDensity(body, value);
break;
case 'inertia':
Body.setInertia(body, value);
break;
case 'vertices':
Body.setVertices(body, value);
break;
case 'position':
Body.setPosition(body, value);
break;
case 'angle':
Body.setAngle(body, value);
break;
case 'velocity':
Body.setVelocity(body, value);
break;
case 'angularVelocity':
Body.setAngularVelocity(body, value);
break;
2015-05-20 15:38:41 -04:00
case 'parts':
Body.setParts(body, value);
break;
2014-12-28 13:37:43 -05:00
default:
body[property] = value;
}
}
};
2014-05-01 09:09:06 -04:00
/**
2014-06-09 14:40:24 -04:00
* Sets the body as static, including isStatic flag and setting mass and inertia to Infinity.
2014-05-01 09:09:06 -04:00
* @method setStatic
2014-06-09 14:40:24 -04:00
* @param {body} body
2014-05-01 09:09:06 -04:00
* @param {bool} isStatic
*/
Body.setStatic = function(body, isStatic) {
2015-05-20 15:38:41 -04:00
for (var i = 0; i < body.parts.length; i++) {
var part = body.parts[i];
part.isStatic = isStatic;
if (isStatic) {
part.restitution = 0;
part.friction = 1;
part.mass = part.inertia = part.density = Infinity;
part.inverseMass = part.inverseInertia = 0;
part.positionPrev.x = part.position.x;
part.positionPrev.y = part.position.y;
part.anglePrev = part.angle;
part.angularVelocity = 0;
part.speed = 0;
part.angularSpeed = 0;
part.motion = 0;
}
2014-05-01 09:09:06 -04:00
}
2014-02-19 09:15:05 -05:00
};
2014-07-29 11:26:49 -04:00
/**
* Sets the mass of the body. Inverse mass and density are automatically updated to reflect the change.
* @method setMass
* @param {body} body
* @param {number} mass
*/
Body.setMass = function(body, mass) {
body.mass = mass;
body.inverseMass = 1 / body.mass;
body.density = body.mass / body.area;
};
/**
* Sets the density of the body. Mass is automatically updated to reflect the change.
* @method setDensity
* @param {body} body
* @param {number} density
*/
Body.setDensity = function(body, density) {
Body.setMass(body, density * body.area);
body.density = density;
};
/**
* Sets the moment of inertia (i.e. second moment of area) of the body of the body.
* Inverse inertia is automatically updated to reflect the change. Mass is not changed.
* @method setInertia
* @param {body} body
* @param {number} inertia
*/
Body.setInertia = function(body, inertia) {
body.inertia = inertia;
body.inverseInertia = 1 / body.inertia;
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Sets the body's vertices and updates body properties accordingly, including inertia, area and mass (with respect to `body.density`).
* Vertices will be automatically transformed to be orientated around their centre of mass as the origin.
* They are then automatically translated to world space based on `body.position`.
*
* The `vertices` argument should be passed as an array of `Matter.Vector` points (or a `Matter.Vertices` array).
* Vertices must form a convex hull, concave hulls are not supported.
*
* @method setVertices
* @param {body} body
* @param {vector[]} vertices
*/
Body.setVertices = function(body, vertices) {
// change vertices
if (vertices[0].body === body) {
body.vertices = vertices;
} else {
body.vertices = Vertices.create(vertices, body);
}
// update properties
body.axes = Axes.fromVertices(body.vertices);
body.area = Vertices.area(body.vertices);
2014-07-29 11:26:49 -04:00
Body.setMass(body, body.density * body.area);
2014-06-09 14:40:24 -04:00
// orient vertices around the centre of mass at origin (0, 0)
var centre = Vertices.centre(body.vertices);
Vertices.translate(body.vertices, centre, -1);
// update inertia while vertices are at origin (0, 0)
2014-07-29 11:26:49 -04:00
Body.setInertia(body, Body._inertiaScale * Vertices.inertia(body.vertices, body.mass));
2014-06-09 14:40:24 -04:00
// update geometry
Vertices.translate(body.vertices, body.position);
Bounds.update(body.bounds, body.vertices, body.velocity);
};
2015-05-20 15:38:41 -04:00
/**
* Sets the parts of the `body` and updates mass, inertia and centroid.
* Each part will have its parent set to `body`.
* By default the convex hull will be automatically computed and set on `body`, unless `autoHull` is set to `false.`
* Note that this method will ensure that the first part in `body.parts` will always be the `body`.
* @method setParts
* @param {body} body
* @param [body] parts
* @param {bool} [autoHull=true]
*/
Body.setParts = function(body, parts, autoHull) {
var i;
// add all the parts, ensuring that the first part is always the parent body
parts = parts.slice(0);
body.parts.length = 0;
body.parts.push(body);
body.parent = body;
for (i = 0; i < parts.length; i++) {
var part = parts[i];
if (part !== body) {
part.parent = body;
body.parts.push(part);
}
}
if (body.parts.length === 1)
return;
autoHull = typeof autoHull !== 'undefined' ? autoHull : true;
// find the convex hull of all parts to set on the parent body
if (autoHull) {
var vertices = [];
for (i = 0; i < parts.length; i++) {
vertices = vertices.concat(parts[i].vertices);
}
Vertices.clockwiseSort(vertices);
var hull = Vertices.hull(vertices),
hullCentre = Vertices.centre(hull);
Body.setVertices(body, hull);
Vertices.translate(body.vertices, hullCentre);
}
// sum the properties of all compound parts of the parent body
var total = _totalProperties(body);
body.area = total.area;
body.parent = body;
body.position.x = total.centre.x;
body.position.y = total.centre.y;
body.positionPrev.x = total.centre.x;
body.positionPrev.y = total.centre.y;
Body.setMass(body, total.mass);
Body.setInertia(body, total.inertia);
Body.setPosition(body, total.centre);
};
2014-06-09 14:40:24 -04:00
/**
* Sets the position of the body instantly. Velocity, angle, force etc. are unchanged.
* @method setPosition
* @param {body} body
* @param {vector} position
*/
Body.setPosition = function(body, position) {
var delta = Vector.sub(position, body.position);
body.positionPrev.x += delta.x;
body.positionPrev.y += delta.y;
2015-05-20 15:38:41 -04:00
for (var i = 0; i < body.parts.length; i++) {
var part = body.parts[i];
part.position.x += delta.x;
part.position.y += delta.y;
Vertices.translate(part.vertices, delta);
Bounds.update(part.bounds, part.vertices, body.velocity);
}
2014-06-09 14:40:24 -04:00
};
/**
* Sets the angle of the body instantly. Angular velocity, position, force etc. are unchanged.
* @method setAngle
* @param {body} body
* @param {number} angle
*/
Body.setAngle = function(body, angle) {
var delta = angle - body.angle;
body.anglePrev += delta;
2015-05-20 15:38:41 -04:00
for (var i = 0; i < body.parts.length; i++) {
var part = body.parts[i];
part.angle += delta;
Vertices.rotate(part.vertices, delta, body.position);
Axes.rotate(part.axes, delta);
Bounds.update(part.bounds, part.vertices, body.velocity);
if (i > 0) {
Vector.rotateAbout(part.position, delta, body.position, part.position);
}
}
2014-06-09 14:40:24 -04:00
};
/**
* Sets the linear velocity of the body instantly. Position, angle, force etc. are unchanged. See also `Body.applyForce`.
* @method setVelocity
* @param {body} body
* @param {vector} velocity
*/
Body.setVelocity = function(body, velocity) {
body.positionPrev.x = body.position.x - velocity.x;
body.positionPrev.y = body.position.y - velocity.y;
body.velocity.x = velocity.x;
body.velocity.y = velocity.y;
body.speed = Vector.magnitude(body.velocity);
};
/**
* Sets the angular velocity of the body instantly. Position, angle, force etc. are unchanged. See also `Body.applyForce`.
* @method setAngularVelocity
* @param {body} body
* @param {number} velocity
*/
Body.setAngularVelocity = function(body, velocity) {
body.anglePrev = body.angle - velocity;
body.angularVelocity = velocity;
body.angularSpeed = Math.abs(body.angularVelocity);
};
/**
* Moves a body by a given vector relative to its current position, without imparting any velocity.
* @method translate
* @param {body} body
* @param {vector} translation
*/
Body.translate = function(body, translation) {
Body.setPosition(body, Vector.add(body.position, translation));
};
/**
* Rotates a body by a given angle relative to its current angle, without imparting any angular velocity.
* @method rotate
* @param {body} body
* @param {number} rotation
*/
Body.rotate = function(body, rotation) {
2014-07-29 11:26:49 -04:00
Body.setAngle(body, body.angle + rotation);
2014-06-09 14:40:24 -04:00
};
/**
* Scales the body, including updating physical properties (mass, area, axes, inertia), from a world-space point (default is body centre).
* @method scale
* @param {body} body
* @param {number} scaleX
* @param {number} scaleY
* @param {vector} [point]
*/
Body.scale = function(body, scaleX, scaleY, point) {
2015-05-20 15:38:41 -04:00
for (var i = 0; i < body.parts.length; i++) {
var part = body.parts[i];
2014-06-09 14:40:24 -04:00
2015-05-20 15:38:41 -04:00
// scale vertices
Vertices.scale(part.vertices, scaleX, scaleY, body.position);
2014-06-09 14:40:24 -04:00
2015-05-20 15:38:41 -04:00
// update properties
part.axes = Axes.fromVertices(part.vertices);
2014-06-09 14:40:24 -04:00
2015-05-20 15:38:41 -04:00
if (!body.isStatic) {
part.area = Vertices.area(part.vertices);
Body.setMass(part, body.density * part.area);
// update inertia (requires vertices to be at origin)
Vertices.translate(part.vertices, { x: -part.position.x, y: -part.position.y });
Body.setInertia(part, Vertices.inertia(part.vertices, part.mass));
Vertices.translate(part.vertices, { x: part.position.x, y: part.position.y });
}
// update bounds
Bounds.update(part.bounds, part.vertices, body.velocity);
}
if (!body.isStatic) {
var total = _totalProperties(body);
body.area = total.area;
Body.setMass(body, total.mass);
Body.setInertia(body, total.inertia);
}
2014-06-09 14:40:24 -04:00
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Performs a simulation step for the given `body`, including updating position and angle using Verlet integration.
2014-02-28 20:10:08 -05:00
* @method update
* @param {body} body
* @param {number} deltaTime
2014-05-05 14:32:51 -04:00
* @param {number} timeScale
2014-02-28 20:10:08 -05:00
* @param {number} correction
*/
2014-05-05 14:32:51 -04:00
Body.update = function(body, deltaTime, timeScale, correction) {
var deltaTimeSquared = Math.pow(deltaTime * timeScale * body.timeScale, 2);
2014-02-19 09:15:05 -05:00
// from the previous step
2014-05-05 14:32:51 -04:00
var frictionAir = 1 - body.frictionAir * timeScale * body.timeScale,
2014-02-19 09:15:05 -05:00
velocityPrevX = body.position.x - body.positionPrev.x,
velocityPrevY = body.position.y - body.positionPrev.y;
2015-06-29 15:58:24 -04:00
// update velocity with Verlet integration
2014-02-19 09:15:05 -05:00
body.velocity.x = (velocityPrevX * frictionAir * correction) + (body.force.x / body.mass) * deltaTimeSquared;
body.velocity.y = (velocityPrevY * frictionAir * correction) + (body.force.y / body.mass) * deltaTimeSquared;
body.positionPrev.x = body.position.x;
body.positionPrev.y = body.position.y;
body.position.x += body.velocity.x;
body.position.y += body.velocity.y;
2015-06-29 15:58:24 -04:00
// update angular velocity with Verlet integration
2014-02-19 09:15:05 -05:00
body.angularVelocity = ((body.angle - body.anglePrev) * frictionAir * correction) + (body.torque / body.inertia) * deltaTimeSquared;
body.anglePrev = body.angle;
body.angle += body.angularVelocity;
// track speed and acceleration
body.speed = Vector.magnitude(body.velocity);
body.angularSpeed = Math.abs(body.angularVelocity);
// transform the body geometry
2015-05-20 15:38:41 -04:00
for (var i = 0; i < body.parts.length; i++) {
var part = body.parts[i];
Vertices.translate(part.vertices, body.velocity);
if (i > 0) {
part.position.x += body.velocity.x;
part.position.y += body.velocity.y;
}
if (body.angularVelocity !== 0) {
Vertices.rotate(part.vertices, body.angularVelocity, body.position);
Axes.rotate(part.axes, body.angularVelocity);
if (i > 0) {
Vector.rotateAbout(part.position, body.angularVelocity, body.position, part.position);
}
}
Bounds.update(part.bounds, part.vertices, body.velocity);
2014-04-01 08:47:17 -04:00
}
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Applies a force to a body from a given world-space position, including resulting torque.
2014-02-28 20:10:08 -05:00
* @method applyForce
* @param {body} body
* @param {vector} position
* @param {vector} force
*/
2014-02-19 09:15:05 -05:00
Body.applyForce = function(body, position, force) {
body.force.x += force.x;
body.force.y += force.y;
var offset = { x: position.x - body.position.x, y: position.y - body.position.y };
body.torque += (offset.x * force.y - offset.y * force.x) * body.inverseInertia;
};
2015-05-20 15:38:41 -04:00
/**
* Returns the sums of the properties of all compound parts of the parent body.
* @method _totalProperties
* @private
* @param {body} body
* @return {}
*/
var _totalProperties = function(body) {
// https://ecourses.ou.edu/cgi-bin/ebook.cgi?doc=&topic=st&chap_sec=07.2&page=theory
// http://output.to/sideway/default.asp?qno=121100087
var properties = {
mass: 0,
area: 0,
inertia: 0,
centre: { x: 0, y: 0 }
};
// sum the properties of all compound parts of the parent body
for (var i = body.parts.length === 1 ? 0 : 1; i < body.parts.length; i++) {
var part = body.parts[i];
properties.mass += part.mass;
properties.area += part.area;
properties.inertia += part.inertia;
properties.centre = Vector.add(properties.centre,
Vector.mult(part.position, part.mass !== Infinity ? part.mass : 1));
}
properties.centre = Vector.div(properties.centre,
properties.mass !== Infinity ? properties.mass : body.parts.length);
return properties;
};
2015-06-29 15:58:24 -04:00
/*
*
* Events Documentation
*
*/
/**
* Fired when a body starts sleeping (where `this` is the body).
*
* @event sleepStart
* @this {body} The body that has started sleeping
* @param {} event An event object
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired when a body ends sleeping (where `this` is the body).
*
* @event sleepEnd
* @this {body} The body that has ended sleeping
* @param {} event An event object
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
2014-06-09 14:40:24 -04:00
/*
*
* Properties Documentation
*
*/
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* An integer `Number` uniquely identifying number generated in `Body.create` by `Common.nextId`.
*
* @property id
* @type number
2014-02-28 20:10:08 -05:00
*/
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* A `String` denoting the type of object.
*
* @property type
* @type string
* @default "body"
2014-02-28 20:10:08 -05:00
*/
2014-02-19 09:15:05 -05:00
2014-05-01 09:09:06 -04:00
/**
2014-06-09 14:40:24 -04:00
* An arbitrary `String` name to help the user identify and manage bodies.
*
* @property label
* @type string
* @default "Body"
2014-05-01 09:09:06 -04:00
*/
2015-05-20 15:38:41 -04:00
/**
* An array of bodies that make up this body.
* The first body in the array must always be a self reference to the current body instance.
* All bodies in the `parts` array together form a single rigid compound body.
* Parts are allowed to overlap, have gaps or holes or even form concave bodies.
* Parts themselves should never be added to a `World`, only the parent body should be.
* Use `Body.setParts` when setting parts to ensure correct updates of all properties.
*
* @property parts
* @type body[]
*/
/**
* A self reference if the body is _not_ a part of another body.
* Otherwise this is a reference to the body that this is a part of.
* See `body.parts`.
*
* @property parent
* @type body
*/
2014-06-09 14:40:24 -04:00
/**
* A `Number` specifying the angle of the body, in radians.
*
* @property angle
* @type number
* @default 0
*/
2014-05-01 09:09:06 -04:00
2014-06-09 14:40:24 -04:00
/**
* An array of `Vector` objects that specify the convex hull of the rigid body.
* These should be provided about the origin `(0, 0)`. E.g.
*
* [{ x: 0, y: 0 }, { x: 25, y: 50 }, { x: 50, y: 0 }]
*
2015-06-29 15:58:24 -04:00
* When passed via `Body.create`, the vertices are translated relative to `body.position` (i.e. world-space, and constantly updated by `Body.update` during simulation).
2014-06-09 14:40:24 -04:00
* The `Vector` objects are also augmented with additional properties required for efficient collision detection.
*
* Other properties such as `inertia` and `bounds` are automatically calculated from the passed vertices (unless provided via `options`).
* Concave hulls are not currently supported. The module `Matter.Vertices` contains useful methods for working with vertices.
*
* @property vertices
* @type vector[]
*/
2014-05-01 09:09:06 -04:00
2014-06-09 14:40:24 -04:00
/**
* A `Vector` that specifies the current world-space position of the body.
*
* @property position
* @type vector
* @default { x: 0, y: 0 }
*/
/**
* A `Vector` that specifies the force to apply in the current step. It is zeroed after every `Body.update`. See also `Body.applyForce`.
*
* @property force
* @type vector
* @default { x: 0, y: 0 }
*/
/**
* A `Number` that specifies the torque (turning force) to apply in the current step. It is zeroed after every `Body.update`.
*
* @property torque
* @type number
* @default 0
*/
/**
* A `Number` that _measures_ the current speed of the body after the last `Body.update`. It is read-only and always positive (it's the magnitude of `body.velocity`).
*
* @readOnly
* @property speed
* @type number
* @default 0
*/
/**
* A `Number` that _measures_ the current angular speed of the body after the last `Body.update`. It is read-only and always positive (it's the magnitude of `body.angularVelocity`).
*
* @readOnly
* @property angularSpeed
* @type number
* @default 0
*/
/**
* A `Vector` that _measures_ the current velocity of the body after the last `Body.update`. It is read-only.
* If you need to modify a body's velocity directly, you should either apply a force or simply change the body's `position` (as the engine uses position-Verlet integration).
*
* @readOnly
* @property velocity
* @type vector
* @default { x: 0, y: 0 }
*/
/**
* A `Number` that _measures_ the current angular velocity of the body after the last `Body.update`. It is read-only.
* If you need to modify a body's angular velocity directly, you should apply a torque or simply change the body's `angle` (as the engine uses position-Verlet integration).
*
* @readOnly
* @property angularVelocity
* @type number
* @default 0
*/
/**
* A flag that indicates whether a body is considered static. A static body can never change position or angle and is completely fixed.
* If you need to set a body as static after its creation, you should use `Body.setStatic` as this requires more than just setting this flag.
*
* @property isStatic
* @type boolean
* @default false
*/
/**
* A flag that indicates whether the body is considered sleeping. A sleeping body acts similar to a static body, except it is only temporary and can be awoken.
* If you need to set a body as sleeping, you should use `Sleeping.set` as this requires more than just setting this flag.
*
* @property isSleeping
* @type boolean
* @default false
*/
/**
* A `Number` that _measures_ the amount of movement a body currently has (a combination of `speed` and `angularSpeed`). It is read-only and always positive.
* It is used and updated by the `Matter.Sleeping` module during simulation to decide if a body has come to rest.
*
* @readOnly
* @property motion
* @type number
* @default 0
*/
/**
* A `Number` that defines the number of updates in which this body must have near-zero velocity before it is set as sleeping by the `Matter.Sleeping` module (if sleeping is enabled by the engine).
*
* @property sleepThreshold
* @type number
* @default 60
*/
/**
* A `Number` that defines the density of the body, that is its mass per unit area.
* If you pass the density via `Body.create` the `mass` property is automatically calculated for you based on the size (area) of the object.
* This is generally preferable to simply setting mass and allows for more intuitive definition of materials (e.g. rock has a higher density than wood).
*
* @property density
* @type number
* @default 0.001
*/
/**
* A `Number` that defines the mass of the body, although it may be more appropriate to specify the `density` property instead.
* If you modify this value, you must also modify the `body.inverseMass` property (`1 / mass`).
*
* @property mass
* @type number
*/
/**
* A `Number` that defines the inverse mass of the body (`1 / mass`).
* If you modify this value, you must also modify the `body.mass` property.
*
* @property inverseMass
* @type number
*/
/**
* A `Number` that defines the moment of inertia (i.e. second moment of area) of the body.
* It is automatically calculated from the given convex hull (`vertices` array) and density in `Body.create`.
* If you modify this value, you must also modify the `body.inverseInertia` property (`1 / inertia`).
*
* @property inertia
* @type number
*/
/**
* A `Number` that defines the inverse moment of inertia of the body (`1 / inertia`).
* If you modify this value, you must also modify the `body.inertia` property.
*
* @property inverseInertia
* @type number
*/
/**
* A `Number` that defines the restitution (elasticity) of the body. The value is always positive and is in the range `(0, 1)`.
* A value of `0` means collisions may be perfectly inelastic and no bouncing may occur.
* A value of `0.8` means the body may bounce back with approximately 80% of its kinetic energy.
* Note that collision response is based on _pairs_ of bodies, and that `restitution` values are _combined_ with the following formula:
*
* Math.max(bodyA.restitution, bodyB.restitution)
*
* @property restitution
* @type number
* @default 0
*/
/**
* A `Number` that defines the friction of the body. The value is always positive and is in the range `(0, 1)`.
* A value of `0` means that the body may slide indefinitely.
* A value of `1` means the body may come to a stop almost instantly after a force is applied.
*
* The effects of the value may be non-linear.
* High values may be unstable depending on the body.
* The engine uses a Coulomb friction model including static and kinetic friction.
* Note that collision response is based on _pairs_ of bodies, and that `friction` values are _combined_ with the following formula:
*
* Math.min(bodyA.friction, bodyB.friction)
*
* @property friction
* @type number
* @default 0.1
*/
2015-05-20 15:38:41 -04:00
/**
* A `Number` that defines the static friction of the body (in the Coulomb friction model).
* A value of `0` means the body will never 'stick' when it is nearly stationary and only dynamic `friction` is used.
* The higher the value (e.g. `10`), the more force it will take to initially get the body moving when nearly stationary.
* This value is multiplied with the `friction` property to make it easier to change `friction` and maintain an appropriate amount of static friction.
*
* @property frictionStatic
* @type number
* @default 0.5
*/
2014-06-09 14:40:24 -04:00
/**
* A `Number` that defines the air friction of the body (air resistance).
* A value of `0` means the body will never slow as it moves through space.
* The higher the value, the faster a body slows when moving through space.
* The effects of the value are non-linear.
*
* @property frictionAir
* @type number
* @default 0.01
*/
/**
2014-07-29 11:26:49 -04:00
* An `Object` that specifies the collision filtering properties of this body.
2014-06-09 14:40:24 -04:00
*
2014-07-29 11:26:49 -04:00
* Collisions between two bodies will obey the following rules:
* - If the two bodies have the same non-zero value of `collisionFilter.group`,
* they will always collide if the value is positive, and they will never collide
* if the value is negative.
* - If the two bodies have different values of `collisionFilter.group` or if one
* (or both) of the bodies has a value of 0, then the category/mask rules apply as follows:
*
* Each body belongs to a collision category, given by `collisionFilter.category`. This
* value is used as a bit field and the category should have only one bit set, meaning that
* the value of this property is a power of two in the range [1, 2^31]. Thus, there are 32
* different collision categories available.
*
* Each body also defines a collision bitmask, given by `collisionFilter.mask` which specifies
* the categories it collides with (the value is the bitwise AND value of all these categories).
*
* Using the category/mask rules, two bodies `A` and `B` collide if each includes the other's
* category in its mask, i.e. `(categoryA & maskB) !== 0` and `(categoryB & maskA) !== 0`
* are both true.
*
* @property collisionFilter
* @type object
*/
/**
* An Integer `Number`, that specifies the collision group this body belongs to.
* See `body.collisionFilter` for more information.
*
* @property collisionFilter.group
* @type object
2014-06-09 14:40:24 -04:00
* @default 0
*/
/**
2014-07-29 11:26:49 -04:00
* A bit field that specifies the collision category this body belongs to.
* The category value should have only one bit set, for example `0x0001`.
* This means there are up to 32 unique collision categories available.
* See `body.collisionFilter` for more information.
*
* @property collisionFilter.category
* @type object
* @default 1
*/
/**
* A bit mask that specifies the collision categories this body may collide with.
* See `body.collisionFilter` for more information.
*
* @property collisionFilter.mask
* @type object
* @default -1
*/
/**
* A `Number` that specifies a tolerance on how far a body is allowed to 'sink' or rotate into other bodies.
2014-06-09 14:40:24 -04:00
* Avoid changing this value unless you understand the purpose of `slop` in physics engines.
* The default should generally suffice, although very large bodies may require larger values for stable stacking.
*
* @property slop
* @type number
* @default 0.05
*/
/**
* A `Number` that allows per-body time scaling, e.g. a force-field where bodies inside are in slow-motion, while others are at full speed.
*
* @property timeScale
* @type number
* @default 1
*/
/**
* An `Object` that defines the rendering properties to be consumed by the module `Matter.Render`.
*
* @property render
* @type object
*/
/**
* A flag that indicates if the body should be rendered.
*
* @property render.visible
* @type boolean
* @default true
*/
/**
* An `Object` that defines the sprite properties to use when rendering, if any.
*
* @property render.sprite
* @type object
*/
/**
* An `String` that defines the path to the image to use as the sprite texture, if any.
*
* @property render.sprite.texture
* @type string
*/
/**
* A `Number` that defines the scaling in the x-axis for the sprite, if any.
*
* @property render.sprite.xScale
* @type number
* @default 1
*/
/**
* A `Number` that defines the scaling in the y-axis for the sprite, if any.
*
* @property render.sprite.yScale
* @type number
* @default 1
*/
/**
* A `Number` that defines the line width to use when rendering the body outline (if a sprite is not defined).
* A value of `0` means no outline will be rendered.
*
* @property render.lineWidth
* @type number
* @default 1.5
*/
/**
* A `String` that defines the fill style to use when rendering the body (if a sprite is not defined).
* It is the same as when using a canvas, so it accepts CSS style property values.
*
* @property render.fillStyle
* @type string
* @default a random colour
*/
/**
* A `String` that defines the stroke style to use when rendering the body outline (if a sprite is not defined).
* It is the same as when using a canvas, so it accepts CSS style property values.
*
* @property render.strokeStyle
* @type string
* @default a random colour
*/
/**
* An array of unique axis vectors (edge normals) used for collision detection.
* These are automatically calculated from the given convex hull (`vertices` array) in `Body.create`.
* They are constantly updated by `Body.update` during the simulation.
*
* @property axes
* @type vector[]
*/
/**
* A `Number` that _measures_ the area of the body's convex hull, calculated at creation by `Body.create`.
*
* @property area
* @type string
* @default
*/
/**
* A `Bounds` object that defines the AABB region for the body.
* It is automatically calculated from the given convex hull (`vertices` array) in `Body.create` and constantly updated by `Body.update` during simulation.
*
* @property bounds
* @type bounds
*/
2014-05-01 09:09:06 -04:00
2014-02-19 09:15:05 -05:00
})();
2014-07-29 11:26:49 -04:00
2014-02-19 09:15:05 -05:00
; // End src/body/Body.js
// Begin src/body/Composite.js
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* The `Matter.Composite` module contains methods for creating and manipulating composite bodies.
* A composite body is a collection of `Matter.Body`, `Matter.Constraint` and other `Matter.Composite`, therefore composites form a tree structure.
* It is important to use the functions in this module to modify composites, rather than directly modifying their properties.
* Note that the `Matter.World` object is also a type of `Matter.Composite` and as such all composite methods here can also operate on a `Matter.World`.
*
2014-02-28 20:10:08 -05:00
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class Composite
*/
2014-02-19 09:15:05 -05:00
var Composite = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Creates a new composite. The options parameter is an object that specifies any properties you wish to override the defaults.
* See the properites section below for detailed information on what you can pass via the `options` object.
2014-02-28 20:10:08 -05:00
* @method create
2014-06-09 14:40:24 -04:00
* @param {} [options]
2014-02-28 20:10:08 -05:00
* @return {composite} A new composite
*/
2014-02-19 09:15:05 -05:00
Composite.create = function(options) {
2014-03-24 16:11:42 -04:00
return Common.extend({
2014-05-01 09:09:06 -04:00
id: Common.nextId(),
2014-03-30 14:45:30 -04:00
type: 'composite',
2014-03-24 16:11:42 -04:00
parent: null,
isModified: false,
bodies: [],
constraints: [],
2014-05-01 09:09:06 -04:00
composites: [],
label: 'Composite'
2014-03-24 16:11:42 -04:00
}, options);
};
/**
* Sets the composite's `isModified` flag.
* If `updateParents` is true, all parents will be set (default: false).
* If `updateChildren` is true, all children will be set (default: false).
* @method setModified
* @param {composite} composite
* @param {boolean} isModified
2014-06-09 14:40:24 -04:00
* @param {boolean} [updateParents=false]
* @param {boolean} [updateChildren=false]
2014-03-24 16:11:42 -04:00
*/
Composite.setModified = function(composite, isModified, updateParents, updateChildren) {
composite.isModified = isModified;
if (updateParents && composite.parent) {
Composite.setModified(composite.parent, isModified, updateParents, updateChildren);
}
if (updateChildren) {
for(var i = 0; i < composite.composites.length; i++) {
var childComposite = composite.composites[i];
Composite.setModified(childComposite, isModified, updateParents, updateChildren);
}
}
2014-02-19 09:15:05 -05:00
};
2014-03-30 14:45:30 -04:00
/**
* Generic add function. Adds one or many body(s), constraint(s) or a composite(s) to the given composite.
2014-07-29 11:26:49 -04:00
* Triggers `beforeAdd` and `afterAdd` events on the `composite`.
2014-03-30 14:45:30 -04:00
* @method add
* @param {composite} composite
* @param {} object
* @return {composite} The original composite with the objects added
*/
Composite.add = function(composite, object) {
var objects = [].concat(object);
2014-07-29 11:26:49 -04:00
Events.trigger(composite, 'beforeAdd', { object: object });
2014-03-30 14:45:30 -04:00
for (var i = 0; i < objects.length; i++) {
var obj = objects[i];
switch (obj.type) {
case 'body':
2015-05-20 15:38:41 -04:00
// skip adding compound parts
if (obj.parent !== obj) {
Common.log('Composite.add: skipped adding a compound body part (you must add its parent instead)', 'warn');
break;
}
2014-03-30 14:45:30 -04:00
Composite.addBody(composite, obj);
break;
case 'constraint':
Composite.addConstraint(composite, obj);
break;
case 'composite':
Composite.addComposite(composite, obj);
break;
case 'mouseConstraint':
Composite.addConstraint(composite, obj.constraint);
break;
}
}
2014-07-29 11:26:49 -04:00
Events.trigger(composite, 'afterAdd', { object: object });
2014-03-30 14:45:30 -04:00
return composite;
};
/**
* Generic remove function. Removes one or many body(s), constraint(s) or a composite(s) to the given composite.
* Optionally searching its children recursively.
2014-07-29 11:26:49 -04:00
* Triggers `beforeRemove` and `afterRemove` events on the `composite`.
2014-03-30 14:45:30 -04:00
* @method remove
* @param {composite} composite
* @param {} object
2014-06-09 14:40:24 -04:00
* @param {boolean} [deep=false]
2014-03-30 14:45:30 -04:00
* @return {composite} The original composite with the objects removed
*/
Composite.remove = function(composite, object, deep) {
var objects = [].concat(object);
2014-07-29 11:26:49 -04:00
Events.trigger(composite, 'beforeRemove', { object: object });
2014-03-30 14:45:30 -04:00
for (var i = 0; i < objects.length; i++) {
var obj = objects[i];
switch (obj.type) {
case 'body':
Composite.removeBody(composite, obj, deep);
break;
case 'constraint':
Composite.removeConstraint(composite, obj, deep);
break;
case 'composite':
Composite.removeComposite(composite, obj, deep);
break;
case 'mouseConstraint':
Composite.removeConstraint(composite, obj.constraint);
break;
}
}
2014-07-29 11:26:49 -04:00
Events.trigger(composite, 'afterRemove', { object: object });
2014-03-30 14:45:30 -04:00
return composite;
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Adds a composite to the given composite
2014-07-29 11:26:49 -04:00
* @private
2014-03-24 16:11:42 -04:00
* @method addComposite
2014-02-28 20:10:08 -05:00
* @param {composite} compositeA
* @param {composite} compositeB
* @return {composite} The original compositeA with the objects from compositeB added
*/
2014-03-24 16:11:42 -04:00
Composite.addComposite = function(compositeA, compositeB) {
compositeA.composites.push(compositeB);
compositeB.parent = compositeA;
Composite.setModified(compositeA, true, true, false);
2014-02-19 09:15:05 -05:00
return compositeA;
};
2014-03-30 14:45:30 -04:00
/**
* Removes a composite from the given composite, and optionally searching its children recursively
2014-07-29 11:26:49 -04:00
* @private
2014-03-30 14:45:30 -04:00
* @method removeComposite
* @param {composite} compositeA
* @param {composite} compositeB
2014-06-09 14:40:24 -04:00
* @param {boolean} [deep=false]
2014-03-30 14:45:30 -04:00
* @return {composite} The original compositeA with the composite removed
*/
Composite.removeComposite = function(compositeA, compositeB, deep) {
2014-07-29 11:26:49 -04:00
var position = Common.indexOf(compositeA.composites, compositeB);
2014-03-30 14:45:30 -04:00
if (position !== -1) {
Composite.removeCompositeAt(compositeA, position);
Composite.setModified(compositeA, true, true, false);
}
if (deep) {
for (var i = 0; i < compositeA.composites.length; i++){
Composite.removeComposite(compositeA.composites[i], compositeB, true);
}
}
return compositeA;
};
/**
* Removes a composite from the given composite
2014-07-29 11:26:49 -04:00
* @private
2014-03-30 14:45:30 -04:00
* @method removeCompositeAt
* @param {composite} composite
* @param {number} position
* @return {composite} The original composite with the composite removed
*/
Composite.removeCompositeAt = function(composite, position) {
composite.composites.splice(position, 1);
Composite.setModified(composite, true, true, false);
return composite;
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Adds a body to the given composite
2014-07-29 11:26:49 -04:00
* @private
2014-02-28 20:10:08 -05:00
* @method addBody
* @param {composite} composite
* @param {body} body
* @return {composite} The original composite with the body added
*/
2014-02-19 09:15:05 -05:00
Composite.addBody = function(composite, body) {
composite.bodies.push(body);
2014-03-24 16:11:42 -04:00
Composite.setModified(composite, true, true, false);
return composite;
};
/**
* Removes a body from the given composite, and optionally searching its children recursively
2014-07-29 11:26:49 -04:00
* @private
2014-03-24 16:11:42 -04:00
* @method removeBody
* @param {composite} composite
* @param {body} body
2014-06-09 14:40:24 -04:00
* @param {boolean} [deep=false]
2014-03-24 16:11:42 -04:00
* @return {composite} The original composite with the body removed
*/
Composite.removeBody = function(composite, body, deep) {
2014-07-29 11:26:49 -04:00
var position = Common.indexOf(composite.bodies, body);
2014-03-24 16:11:42 -04:00
if (position !== -1) {
Composite.removeBodyAt(composite, position);
Composite.setModified(composite, true, true, false);
}
if (deep) {
for (var i = 0; i < composite.composites.length; i++){
Composite.removeBody(composite.composites[i], body, true);
}
}
return composite;
};
/**
* Removes a body from the given composite
2014-07-29 11:26:49 -04:00
* @private
2014-03-24 16:11:42 -04:00
* @method removeBodyAt
* @param {composite} composite
* @param {number} position
* @return {composite} The original composite with the body removed
*/
Composite.removeBodyAt = function(composite, position) {
composite.bodies.splice(position, 1);
Composite.setModified(composite, true, true, false);
2014-02-19 09:15:05 -05:00
return composite;
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Adds a constraint to the given composite
2014-07-29 11:26:49 -04:00
* @private
2014-02-28 20:10:08 -05:00
* @method addConstraint
* @param {composite} composite
* @param {constraint} constraint
* @return {composite} The original composite with the constraint added
*/
2014-02-19 09:15:05 -05:00
Composite.addConstraint = function(composite, constraint) {
composite.constraints.push(constraint);
2014-03-24 16:11:42 -04:00
Composite.setModified(composite, true, true, false);
2014-02-19 09:15:05 -05:00
return composite;
};
2014-03-24 16:11:42 -04:00
/**
* Removes a constraint from the given composite, and optionally searching its children recursively
2014-07-29 11:26:49 -04:00
* @private
2014-03-24 16:11:42 -04:00
* @method removeConstraint
* @param {composite} composite
* @param {constraint} constraint
2014-06-09 14:40:24 -04:00
* @param {boolean} [deep=false]
2014-03-24 16:11:42 -04:00
* @return {composite} The original composite with the constraint removed
*/
Composite.removeConstraint = function(composite, constraint, deep) {
2014-07-29 11:26:49 -04:00
var position = Common.indexOf(composite.constraints, constraint);
2014-03-24 16:11:42 -04:00
if (position !== -1) {
Composite.removeConstraintAt(composite, position);
}
if (deep) {
for (var i = 0; i < composite.composites.length; i++){
Composite.removeConstraint(composite.composites[i], constraint, true);
}
}
return composite;
};
/**
* Removes a body from the given composite
2014-07-29 11:26:49 -04:00
* @private
2014-03-24 16:11:42 -04:00
* @method removeConstraintAt
* @param {composite} composite
* @param {number} position
* @return {composite} The original composite with the constraint removed
*/
Composite.removeConstraintAt = function(composite, position) {
composite.constraints.splice(position, 1);
Composite.setModified(composite, true, true, false);
return composite;
};
/**
* Removes all bodies, constraints and composites from the given composite
* Optionally clearing its children recursively
* @method clear
2015-06-29 15:58:24 -04:00
* @param {composite} composite
2014-03-24 16:11:42 -04:00
* @param {boolean} keepStatic
2014-06-09 14:40:24 -04:00
* @param {boolean} [deep=false]
2014-03-24 16:11:42 -04:00
*/
Composite.clear = function(composite, keepStatic, deep) {
if (deep) {
for (var i = 0; i < composite.composites.length; i++){
Composite.clear(composite.composites[i], keepStatic, true);
}
}
if (keepStatic) {
composite.bodies = composite.bodies.filter(function(body) { return body.isStatic; });
} else {
composite.bodies.length = 0;
}
composite.constraints.length = 0;
composite.composites.length = 0;
Composite.setModified(composite, true, true, false);
return composite;
};
/**
* Returns all bodies in the given composite, including all bodies in its children, recursively
* @method allBodies
* @param {composite} composite
* @return {body[]} All the bodies
*/
Composite.allBodies = function(composite) {
var bodies = [].concat(composite.bodies);
for (var i = 0; i < composite.composites.length; i++)
bodies = bodies.concat(Composite.allBodies(composite.composites[i]));
return bodies;
};
/**
* Returns all constraints in the given composite, including all constraints in its children, recursively
* @method allConstraints
* @param {composite} composite
* @return {constraint[]} All the constraints
*/
Composite.allConstraints = function(composite) {
var constraints = [].concat(composite.constraints);
for (var i = 0; i < composite.composites.length; i++)
constraints = constraints.concat(Composite.allConstraints(composite.composites[i]));
return constraints;
};
2014-03-30 14:45:30 -04:00
/**
* Returns all composites in the given composite, including all composites in its children, recursively
* @method allComposites
* @param {composite} composite
* @return {composite[]} All the composites
*/
Composite.allComposites = function(composite) {
var composites = [].concat(composite.composites);
for (var i = 0; i < composite.composites.length; i++)
composites = composites.concat(Composite.allComposites(composite.composites[i]));
return composites;
};
2014-05-01 09:09:06 -04:00
/**
* Searches the composite recursively for an object matching the type and id supplied, null if not found
* @method get
* @param {composite} composite
* @param {number} id
* @param {string} type
* @return {object} The requested object, if found
*/
Composite.get = function(composite, id, type) {
var objects,
object;
switch (type) {
case 'body':
objects = Composite.allBodies(composite);
break;
case 'constraint':
objects = Composite.allConstraints(composite);
break;
case 'composite':
objects = Composite.allComposites(composite).concat(composite);
break;
}
if (!objects)
return null;
object = objects.filter(function(object) {
return object.id.toString() === id.toString();
});
return object.length === 0 ? null : object[0];
};
/**
* Moves the given object(s) from compositeA to compositeB (equal to a remove followed by an add)
* @method move
* @param {compositeA} compositeA
* @param {object[]} objects
* @param {compositeB} compositeB
* @return {composite} Returns compositeA
*/
Composite.move = function(compositeA, objects, compositeB) {
Composite.remove(compositeA, objects);
Composite.add(compositeB, objects);
return compositeA;
};
/**
2014-06-09 14:40:24 -04:00
* Assigns new ids for all objects in the composite, recursively
* @method rebase
* @param {composite} composite
* @return {composite} Returns composite
*/
Composite.rebase = function(composite) {
var objects = Composite.allBodies(composite)
.concat(Composite.allConstraints(composite))
.concat(Composite.allComposites(composite));
for (var i = 0; i < objects.length; i++) {
objects[i].id = Common.nextId();
}
Composite.setModified(composite, true, true, false);
return composite;
};
2014-07-30 12:29:21 -04:00
/**
* Translates all children in the composite by a given vector relative to their current positions,
* without imparting any velocity.
* @method translate
* @param {composite} composite
* @param {vector} translation
* @param {bool} [recursive=true]
*/
Composite.translate = function(composite, translation, recursive) {
var bodies = recursive ? Composite.allBodies(composite) : composite.bodies;
for (var i = 0; i < bodies.length; i++) {
Body.translate(bodies[i], translation);
}
Composite.setModified(composite, true, true, false);
return composite;
};
/**
* Rotates all children in the composite by a given angle about the given point, without imparting any angular velocity.
* @method rotate
* @param {composite} composite
* @param {number} rotation
* @param {vector} point
* @param {bool} [recursive=true]
*/
Composite.rotate = function(composite, rotation, point, recursive) {
var cos = Math.cos(rotation),
sin = Math.sin(rotation),
bodies = recursive ? Composite.allBodies(composite) : composite.bodies;
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i],
dx = body.position.x - point.x,
dy = body.position.y - point.y;
Body.setPosition(body, {
x: point.x + (dx * cos - dy * sin),
y: point.y + (dx * sin + dy * cos)
});
Body.rotate(body, rotation);
}
Composite.setModified(composite, true, true, false);
return composite;
};
/**
* Scales all children in the composite, including updating physical properties (mass, area, axes, inertia), from a world-space point.
* @method scale
* @param {composite} composite
* @param {number} scaleX
* @param {number} scaleY
* @param {vector} point
* @param {bool} [recursive=true]
*/
Composite.scale = function(composite, scaleX, scaleY, point, recursive) {
var bodies = recursive ? Composite.allBodies(composite) : composite.bodies;
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i],
dx = body.position.x - point.x,
dy = body.position.y - point.y;
Body.setPosition(body, {
x: point.x + dx * scaleX,
y: point.y + dy * scaleY
});
Body.scale(body, scaleX, scaleY);
}
Composite.setModified(composite, true, true, false);
return composite;
};
2014-07-29 11:26:49 -04:00
/*
*
* Events Documentation
*
*/
/**
* Fired when a call to `Composite.add` is made, before objects have been added.
*
* @event beforeAdd
* @param {} event An event object
* @param {} event.object The object(s) to be added (may be a single body, constraint, composite or a mixed array of these)
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired when a call to `Composite.add` is made, after objects have been added.
*
* @event afterAdd
* @param {} event An event object
* @param {} event.object The object(s) that have been added (may be a single body, constraint, composite or a mixed array of these)
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired when a call to `Composite.remove` is made, before objects have been removed.
*
* @event beforeRemove
* @param {} event An event object
* @param {} event.object The object(s) to be removed (may be a single body, constraint, composite or a mixed array of these)
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired when a call to `Composite.remove` is made, after objects have been removed.
*
* @event afterRemove
* @param {} event An event object
* @param {} event.object The object(s) that have been removed (may be a single body, constraint, composite or a mixed array of these)
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
2014-06-09 14:40:24 -04:00
/*
*
* Properties Documentation
*
*/
/**
* An integer `Number` uniquely identifying number generated in `Composite.create` by `Common.nextId`.
*
* @property id
* @type number
*/
/**
* A `String` denoting the type of object.
*
* @property type
* @type string
* @default "composite"
*/
/**
* An arbitrary `String` name to help the user identify and manage composites.
*
* @property label
* @type string
* @default "Composite"
*/
/**
* A flag that specifies whether the composite has been modified during the current step.
* Most `Matter.Composite` methods will automatically set this flag to `true` to inform the engine of changes to be handled.
* If you need to change it manually, you should use the `Composite.setModified` method.
*
* @property isModified
* @type boolean
* @default false
2014-05-01 09:09:06 -04:00
*/
2014-06-09 14:40:24 -04:00
/**
* The `Composite` that is the parent of this composite. It is automatically managed by the `Matter.Composite` methods.
*
* @property parent
* @type composite
* @default null
*/
2014-05-01 09:09:06 -04:00
2014-06-09 14:40:24 -04:00
/**
* An array of `Body` that are _direct_ children of this composite.
* To add or remove bodies you should use `Composite.add` and `Composite.remove` methods rather than directly modifying this property.
* If you wish to recursively find all descendants, you should use the `Composite.allBodies` method.
*
* @property bodies
* @type body[]
* @default []
*/
2014-05-01 09:09:06 -04:00
2014-06-09 14:40:24 -04:00
/**
* An array of `Constraint` that are _direct_ children of this composite.
* To add or remove constraints you should use `Composite.add` and `Composite.remove` methods rather than directly modifying this property.
* If you wish to recursively find all descendants, you should use the `Composite.allConstraints` method.
*
* @property constraints
* @type constraint[]
* @default []
*/
/**
* An array of `Composite` that are _direct_ children of this composite.
* To add or remove composites you should use `Composite.add` and `Composite.remove` methods rather than directly modifying this property.
* If you wish to recursively find all descendants, you should use the `Composite.allComposites` method.
*
* @property composites
* @type composite[]
* @default []
*/
2014-05-01 09:09:06 -04:00
2014-02-19 09:15:05 -05:00
})();
2015-06-29 15:58:24 -04:00
2014-02-19 09:15:05 -05:00
; // End src/body/Composite.js
// Begin src/body/World.js
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* The `Matter.World` module contains methods for creating and manipulating the world composite.
* A `Matter.World` is a `Matter.Composite` body, which is a collection of `Matter.Body`, `Matter.Constraint` and other `Matter.Composite`.
* A `Matter.World` has a few additional properties including `gravity` and `bounds`.
* It is important to use the functions in the `Matter.Composite` module to modify the world composite, rather than directly modifying its properties.
* There are also a few methods here that alias those in `Matter.Composite` for easier readability.
*
2014-02-28 20:10:08 -05:00
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class World
2015-05-20 15:38:41 -04:00
* @extends Composite
2014-02-28 20:10:08 -05:00
*/
2014-02-19 09:15:05 -05:00
var World = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Creates a new world composite. The options parameter is an object that specifies any properties you wish to override the defaults.
2015-06-29 15:58:24 -04:00
* See the properties section below for detailed information on what you can pass via the `options` object.
2014-02-28 20:10:08 -05:00
* @method create
* @constructor
* @param {} options
* @return {world} A new world
*/
2014-02-19 09:15:05 -05:00
World.create = function(options) {
2014-03-24 16:11:42 -04:00
var composite = Composite.create();
2014-02-19 09:15:05 -05:00
var defaults = {
2014-05-01 09:09:06 -04:00
label: 'World',
2014-02-19 09:15:05 -05:00
gravity: { x: 0, y: 1 },
bounds: {
2015-05-21 19:33:26 -04:00
min: { x: -Infinity, y: -Infinity },
max: { x: Infinity, y: Infinity }
2014-02-19 09:15:05 -05:00
}
};
2014-03-24 16:11:42 -04:00
return Common.extend(composite, defaults, options);
2014-02-19 09:15:05 -05:00
};
2014-03-24 16:11:42 -04:00
// World is a Composite body
// see src/module/Outro.js for these aliases:
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* An alias for Composite.clear since World is also a Composite
2014-02-28 20:10:08 -05:00
* @method clear
* @param {world} world
* @param {boolean} keepStatic
*/
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* An alias for Composite.add since World is also a Composite
2014-02-28 20:10:08 -05:00
* @method addComposite
* @param {world} world
* @param {composite} composite
* @return {world} The original world with the objects from composite added
*/
/**
2014-06-09 14:40:24 -04:00
* An alias for Composite.addBody since World is also a Composite
2014-02-28 20:10:08 -05:00
* @method addBody
* @param {world} world
* @param {body} body
* @return {world} The original world with the body added
*/
/**
2014-06-09 14:40:24 -04:00
* An alias for Composite.addConstraint since World is also a Composite
2014-02-28 20:10:08 -05:00
* @method addConstraint
* @param {world} world
* @param {constraint} constraint
* @return {world} The original world with the constraint added
*/
2014-02-19 09:15:05 -05:00
})();
2015-06-29 15:58:24 -04:00
2014-02-19 09:15:05 -05:00
; // End src/body/World.js
// Begin src/collision/Contact.js
2014-02-28 20:10:08 -05:00
/**
* _Internal Class_, not generally used outside of the engine's internals.
*
* @class Contact
*/
2014-02-19 09:15:05 -05:00
var Contact = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
* Description
* @method create
* @param {vertex} vertex
* @return {contact} A new contact
*/
2014-02-19 09:15:05 -05:00
Contact.create = function(vertex) {
return {
id: Contact.id(vertex),
vertex: vertex,
normalImpulse: 0,
tangentImpulse: 0
};
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method id
* @param {vertex} vertex
2015-01-01 18:10:10 -05:00
* @return {string} Unique contactID
2014-02-28 20:10:08 -05:00
*/
2014-02-19 09:15:05 -05:00
Contact.id = function(vertex) {
return vertex.body.id + '_' + vertex.index;
};
})();
2015-01-01 18:10:10 -05:00
2014-02-19 09:15:05 -05:00
; // End src/collision/Contact.js
// Begin src/collision/Detector.js
2014-02-28 20:10:08 -05:00
/**
* _Internal Class_, not generally used outside of the engine's internals.
*
* @class Detector
*/
2014-02-19 09:15:05 -05:00
// TODO: speculative contacts
var Detector = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
* Description
* @method collisions
* @param {pair[]} broadphasePairs
* @param {engine} engine
2014-02-28 20:10:08 -05:00
* @return {array} collisions
*/
Detector.collisions = function(broadphasePairs, engine) {
var collisions = [],
pairsTable = engine.pairs.table;
2014-02-19 09:15:05 -05:00
2015-05-20 15:38:41 -04:00
for (var i = 0; i < broadphasePairs.length; i++) {
var bodyA = broadphasePairs[i][0],
bodyB = broadphasePairs[i][1];
2014-02-19 09:15:05 -05:00
if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping))
continue;
2014-07-29 11:26:49 -04:00
if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter))
continue;
2014-02-19 09:15:05 -05:00
// mid phase
if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) {
2015-05-20 15:38:41 -04:00
for (var j = bodyA.parts.length > 1 ? 1 : 0; j < bodyA.parts.length; j++) {
var partA = bodyA.parts[j];
for (var k = bodyB.parts.length > 1 ? 1 : 0; k < bodyB.parts.length; k++) {
var partB = bodyB.parts[k];
if ((partA === bodyA && partB === bodyB) || Bounds.overlaps(partA.bounds, partB.bounds)) {
// find a previous collision we could reuse
var pairId = Pair.id(partA, partB),
pair = pairsTable[pairId],
previousCollision;
if (pair && pair.isActive) {
previousCollision = pair.collision;
} else {
previousCollision = null;
}
2015-05-20 15:38:41 -04:00
// narrow phase
var collision = SAT.collides(partA, partB, previousCollision);
2015-05-20 15:38:41 -04:00
if (collision.collided) {
collisions.push(collision);
}
}
}
2014-02-19 09:15:05 -05:00
}
}
}
return collisions;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method bruteForce
* @param {body[]} bodies
* @param {engine} engine
2014-02-28 20:10:08 -05:00
* @return {array} collisions
*/
Detector.bruteForce = function(bodies, engine) {
var collisions = [],
pairsTable = engine.pairs.table;
2014-02-19 09:15:05 -05:00
for (var i = 0; i < bodies.length; i++) {
for (var j = i + 1; j < bodies.length; j++) {
var bodyA = bodies[i],
bodyB = bodies[j];
// NOTE: could share a function for the below, but may drop performance?
if ((bodyA.isStatic || bodyA.isSleeping) && (bodyB.isStatic || bodyB.isSleeping))
continue;
2014-07-29 11:26:49 -04:00
if (!Detector.canCollide(bodyA.collisionFilter, bodyB.collisionFilter))
continue;
2014-02-19 09:15:05 -05:00
// mid phase
if (Bounds.overlaps(bodyA.bounds, bodyB.bounds)) {
// find a previous collision we could reuse
var pairId = Pair.id(bodyA, bodyB),
2014-03-30 14:45:30 -04:00
pair = pairsTable[pairId],
previousCollision;
if (pair && pair.isActive) {
previousCollision = pair.collision;
} else {
previousCollision = null;
}
2014-02-19 09:15:05 -05:00
// narrow phase
var collision = SAT.collides(bodyA, bodyB, previousCollision);
2014-02-19 09:15:05 -05:00
if (collision.collided) {
collisions.push(collision);
}
}
}
}
return collisions;
};
2014-07-29 11:26:49 -04:00
/**
* Returns `true` if both supplied collision filters will allow a collision to occur.
* See `body.collisionFilter` for more information.
* @method canCollide
* @param {} filterA
* @param {} filterB
* @return {bool} `true` if collision can occur
*/
Detector.canCollide = function(filterA, filterB) {
if (filterA.group === filterB.group && filterA.group !== 0)
return filterA.group > 0;
return (filterA.mask & filterB.category) !== 0 && (filterB.mask & filterA.category) !== 0;
};
2014-02-19 09:15:05 -05:00
})();
2014-07-29 11:26:49 -04:00
2014-02-19 09:15:05 -05:00
; // End src/collision/Detector.js
// Begin src/collision/Grid.js
2014-02-28 20:10:08 -05:00
/**
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class Grid
*/
2014-02-19 09:15:05 -05:00
var Grid = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
* Description
* @method create
2014-07-29 11:26:49 -04:00
* @param {} options
2014-02-28 20:10:08 -05:00
* @return {grid} A new grid
*/
2014-07-29 11:26:49 -04:00
Grid.create = function(options) {
var defaults = {
controller: Grid,
detector: Detector.collisions,
2014-02-19 09:15:05 -05:00
buckets: {},
pairs: {},
pairsList: [],
2014-07-29 11:26:49 -04:00
bucketWidth: 48,
bucketHeight: 48
2014-02-19 09:15:05 -05:00
};
2014-07-29 11:26:49 -04:00
return Common.extend(defaults, options);
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method update
* @param {grid} grid
* @param {body[]} bodies
* @param {engine} engine
* @param {boolean} forceUpdate
*/
2014-02-19 09:15:05 -05:00
Grid.update = function(grid, bodies, engine, forceUpdate) {
var i, col, row,
world = engine.world,
buckets = grid.buckets,
bucket,
bucketId,
gridChanged = false;
for (i = 0; i < bodies.length; i++) {
var body = bodies[i];
2014-03-24 16:11:42 -04:00
if (body.isSleeping && !forceUpdate)
2014-02-19 09:15:05 -05:00
continue;
// don't update out of world bodies
if (body.bounds.max.x < 0 || body.bounds.min.x > world.bounds.width
|| body.bounds.max.y < 0 || body.bounds.min.y > world.bounds.height)
continue;
var newRegion = _getRegion(grid, body);
// if the body has changed grid region
if (!body.region || newRegion.id !== body.region.id || forceUpdate) {
if (!body.region || forceUpdate)
body.region = newRegion;
var union = _regionUnion(newRegion, body.region);
// update grid buckets affected by region change
// iterate over the union of both regions
for (col = union.startCol; col <= union.endCol; col++) {
for (row = union.startRow; row <= union.endRow; row++) {
bucketId = _getBucketId(col, row);
bucket = buckets[bucketId];
var isInsideNewRegion = (col >= newRegion.startCol && col <= newRegion.endCol
&& row >= newRegion.startRow && row <= newRegion.endRow);
var isInsideOldRegion = (col >= body.region.startCol && col <= body.region.endCol
&& row >= body.region.startRow && row <= body.region.endRow);
// remove from old region buckets
if (!isInsideNewRegion && isInsideOldRegion) {
if (isInsideOldRegion) {
if (bucket)
_bucketRemoveBody(grid, bucket, body);
}
}
// add to new region buckets
if (body.region === newRegion || (isInsideNewRegion && !isInsideOldRegion) || forceUpdate) {
if (!bucket)
bucket = _createBucket(buckets, bucketId);
_bucketAddBody(grid, bucket, body);
}
}
}
// set the new region
body.region = newRegion;
// flag changes so we can update pairs
gridChanged = true;
}
}
// update pairs list only if pairs changed (i.e. a body changed region)
if (gridChanged)
grid.pairsList = _createActivePairsList(grid);
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method clear
* @param {grid} grid
*/
2014-02-19 09:15:05 -05:00
Grid.clear = function(grid) {
grid.buckets = {};
grid.pairs = {};
grid.pairsList = [];
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _regionUnion
* @private
* @param {} regionA
* @param {} regionB
* @return CallExpression
*/
2014-02-19 09:15:05 -05:00
var _regionUnion = function(regionA, regionB) {
var startCol = Math.min(regionA.startCol, regionB.startCol),
endCol = Math.max(regionA.endCol, regionB.endCol),
startRow = Math.min(regionA.startRow, regionB.startRow),
endRow = Math.max(regionA.endRow, regionB.endRow);
return _createRegion(startCol, endCol, startRow, endRow);
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _getRegion
* @private
* @param {} grid
* @param {} body
* @return CallExpression
*/
2014-02-19 09:15:05 -05:00
var _getRegion = function(grid, body) {
var bounds = body.bounds,
startCol = Math.floor(bounds.min.x / grid.bucketWidth),
endCol = Math.floor(bounds.max.x / grid.bucketWidth),
startRow = Math.floor(bounds.min.y / grid.bucketHeight),
endRow = Math.floor(bounds.max.y / grid.bucketHeight);
return _createRegion(startCol, endCol, startRow, endRow);
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _createRegion
* @private
* @param {} startCol
* @param {} endCol
* @param {} startRow
* @param {} endRow
* @return ObjectExpression
*/
2014-02-19 09:15:05 -05:00
var _createRegion = function(startCol, endCol, startRow, endRow) {
return {
id: startCol + ',' + endCol + ',' + startRow + ',' + endRow,
startCol: startCol,
endCol: endCol,
startRow: startRow,
endRow: endRow
};
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _getBucketId
* @private
* @param {} column
* @param {} row
* @return BinaryExpression
*/
2014-02-19 09:15:05 -05:00
var _getBucketId = function(column, row) {
return column + ',' + row;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _createBucket
* @private
* @param {} buckets
* @param {} bucketId
* @return bucket
*/
2014-02-19 09:15:05 -05:00
var _createBucket = function(buckets, bucketId) {
var bucket = buckets[bucketId] = [];
return bucket;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _bucketAddBody
* @private
* @param {} grid
* @param {} bucket
* @param {} body
*/
2014-02-19 09:15:05 -05:00
var _bucketAddBody = function(grid, bucket, body) {
// add new pairs
for (var i = 0; i < bucket.length; i++) {
var bodyB = bucket[i];
if (body.id === bodyB.id || (body.isStatic && bodyB.isStatic))
continue;
// keep track of the number of buckets the pair exists in
// important for Grid.update to work
var pairId = Pair.id(body, bodyB),
pair = grid.pairs[pairId];
if (pair) {
pair[2] += 1;
2014-02-19 09:15:05 -05:00
} else {
grid.pairs[pairId] = [body, bodyB, 1];
}
}
// add to bodies (after pairs, otherwise pairs with self)
bucket.push(body);
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _bucketRemoveBody
* @private
* @param {} grid
* @param {} bucket
* @param {} body
*/
2014-02-19 09:15:05 -05:00
var _bucketRemoveBody = function(grid, bucket, body) {
// remove from bucket
2014-07-29 11:26:49 -04:00
bucket.splice(Common.indexOf(bucket, body), 1);
2014-02-19 09:15:05 -05:00
// update pair counts
for (var i = 0; i < bucket.length; i++) {
// keep track of the number of buckets the pair exists in
// important for _createActivePairsList to work
var bodyB = bucket[i],
pairId = Pair.id(body, bodyB),
pair = grid.pairs[pairId];
2014-02-19 09:15:05 -05:00
if (pair)
pair[2] -= 1;
2014-02-19 09:15:05 -05:00
}
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _createActivePairsList
* @private
* @param {} grid
* @return pairs
*/
2014-02-19 09:15:05 -05:00
var _createActivePairsList = function(grid) {
var pairKeys,
pair,
pairs = [];
// grid.pairs is used as a hashmap
pairKeys = Common.keys(grid.pairs);
// iterate over grid.pairs
for (var k = 0; k < pairKeys.length; k++) {
pair = grid.pairs[pairKeys[k]];
// if pair exists in at least one bucket
// it is a pair that needs further collision testing so push it
if (pair[2] > 0) {
2014-02-19 09:15:05 -05:00
pairs.push(pair);
} else {
delete grid.pairs[pairKeys[k]];
}
2014-02-19 09:15:05 -05:00
}
return pairs;
};
})();
; // End src/collision/Grid.js
2014-03-24 16:11:42 -04:00
// Begin src/collision/Pair.js
/**
* _Internal Class_, not generally used outside of the engine's internals.
*
* @class Pair
*/
var Pair = {};
(function() {
/**
* Description
* @method create
* @param {collision} collision
2015-06-29 15:58:24 -04:00
* @param {number} timestamp
2014-03-24 16:11:42 -04:00
* @return {pair} A new pair
*/
Pair.create = function(collision, timestamp) {
var bodyA = collision.bodyA,
2015-05-20 15:38:41 -04:00
bodyB = collision.bodyB,
parentA = collision.parentA,
parentB = collision.parentB;
2014-03-24 16:11:42 -04:00
var pair = {
id: Pair.id(bodyA, bodyB),
bodyA: bodyA,
bodyB: bodyB,
contacts: {},
activeContacts: [],
separation: 0,
isActive: true,
timeCreated: timestamp,
timeUpdated: timestamp,
2015-05-20 15:38:41 -04:00
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)
2014-03-24 16:11:42 -04:00
};
Pair.update(pair, collision, timestamp);
return pair;
};
/**
* Description
* @method update
* @param {pair} pair
* @param {collision} collision
2015-06-29 15:58:24 -04:00
* @param {number} timestamp
2014-03-24 16:11:42 -04:00
*/
Pair.update = function(pair, collision, timestamp) {
var contacts = pair.contacts,
supports = collision.supports,
2015-05-20 15:38:41 -04:00
activeContacts = pair.activeContacts,
parentA = collision.parentA,
parentB = collision.parentB;
2014-03-24 16:11:42 -04:00
pair.collision = collision;
2015-05-20 15:38:41 -04:00
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);
2014-03-24 16:11:42 -04:00
activeContacts.length = 0;
if (collision.collided) {
for (var i = 0; i < supports.length; i++) {
var support = supports[i],
2014-03-30 14:45:30 -04:00
contactId = Contact.id(support),
contact = contacts[contactId];
2014-03-24 16:11:42 -04:00
2014-03-30 14:45:30 -04:00
if (contact) {
activeContacts.push(contact);
2014-03-24 16:11:42 -04:00
} else {
activeContacts.push(contacts[contactId] = Contact.create(support));
}
}
pair.separation = collision.depth;
Pair.setActive(pair, true, timestamp);
} else {
if (pair.isActive === true)
Pair.setActive(pair, false, timestamp);
}
};
/**
* Description
* @method setActive
* @param {pair} pair
* @param {bool} isActive
2015-06-29 15:58:24 -04:00
* @param {number} timestamp
2014-03-24 16:11:42 -04:00
*/
Pair.setActive = function(pair, isActive, timestamp) {
if (isActive) {
pair.isActive = true;
pair.timeUpdated = timestamp;
} else {
pair.isActive = false;
pair.activeContacts.length = 0;
}
};
/**
* Description
* @method id
* @param {body} bodyA
* @param {body} bodyB
2015-01-01 18:10:10 -05:00
* @return {string} Unique pairId
2014-03-24 16:11:42 -04:00
*/
Pair.id = function(bodyA, bodyB) {
if (bodyA.id < bodyB.id) {
return bodyA.id + '_' + bodyB.id;
} else {
return bodyB.id + '_' + bodyA.id;
}
};
})();
2015-01-01 18:10:10 -05:00
2014-03-24 16:11:42 -04:00
; // End src/collision/Pair.js
// Begin src/collision/Pairs.js
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
* _Internal Class_, not generally used outside of the engine's internals.
*
2014-03-24 16:11:42 -04:00
* @class Pairs
2014-02-28 20:10:08 -05:00
*/
2014-03-24 16:11:42 -04:00
var Pairs = {};
2014-02-19 09:15:05 -05:00
(function() {
var _pairMaxIdleLife = 1000;
2014-02-19 09:15:05 -05:00
2014-03-24 16:11:42 -04:00
/**
* Creates a new pairs structure
* @method create
* @param {object} options
* @return {pairs} A new pairs structure
*/
Pairs.create = function(options) {
return Common.extend({
table: {},
list: [],
collisionStart: [],
collisionActive: [],
collisionEnd: []
}, options);
};
2014-02-28 20:10:08 -05:00
/**
* Description
2014-03-24 16:11:42 -04:00
* @method update
2014-02-28 20:10:08 -05:00
* @param {object} pairs
* @param {collision[]} collisions
2015-06-29 15:58:24 -04:00
* @param {number} timestamp
2014-02-28 20:10:08 -05:00
*/
2014-03-24 16:11:42 -04:00
Pairs.update = function(pairs, collisions, timestamp) {
var pairsList = pairs.list,
pairsTable = pairs.table,
collisionStart = pairs.collisionStart,
collisionEnd = pairs.collisionEnd,
collisionActive = pairs.collisionActive,
activePairIds = [],
collision,
pairId,
pair,
i;
2014-02-19 09:15:05 -05:00
// clear collision state arrays, but maintain old reference
collisionStart.length = 0;
collisionEnd.length = 0;
collisionActive.length = 0;
2014-02-19 09:15:05 -05:00
for (i = 0; i < collisions.length; i++) {
collision = collisions[i];
if (collision.collided) {
2014-02-19 09:15:05 -05:00
pairId = Pair.id(collision.bodyA, collision.bodyB);
activePairIds.push(pairId);
2014-03-30 14:45:30 -04:00
pair = pairsTable[pairId];
2014-03-30 14:45:30 -04:00
if (pair) {
// pair already exists (but may or may not be active)
if (pair.isActive) {
// pair exists and is active
collisionActive.push(pair);
} else {
// pair exists but was inactive, so a collision has just started again
collisionStart.push(pair);
}
// update the pair
Pair.update(pair, collision, timestamp);
} else {
// pair did not exist, create a new pair
pair = Pair.create(collision, timestamp);
pairsTable[pairId] = pair;
// push the new pair
collisionStart.push(pair);
pairsList.push(pair);
}
}
}
// deactivate previously active pairs that are now inactive
for (i = 0; i < pairsList.length; i++) {
pair = pairsList[i];
2014-07-29 11:26:49 -04:00
if (pair.isActive && Common.indexOf(activePairIds, pair.id) === -1) {
Pair.setActive(pair, false, timestamp);
collisionEnd.push(pair);
2014-02-19 09:15:05 -05:00
}
}
};
2014-02-28 20:10:08 -05:00
/**
* Description
2014-03-24 16:11:42 -04:00
* @method removeOld
2014-02-28 20:10:08 -05:00
* @param {object} pairs
2015-06-29 15:58:24 -04:00
* @param {number} timestamp
2014-02-28 20:10:08 -05:00
*/
2014-03-24 16:11:42 -04:00
Pairs.removeOld = function(pairs, timestamp) {
var pairsList = pairs.list,
pairsTable = pairs.table,
indexesToRemove = [],
pair,
collision,
pairIndex,
2014-02-19 09:15:05 -05:00
i;
2014-02-19 09:15:05 -05:00
for (i = 0; i < pairsList.length; i++) {
pair = pairsList[i];
collision = pair.collision;
2014-02-19 09:15:05 -05:00
// never remove sleeping pairs
if (collision.bodyA.isSleeping || collision.bodyB.isSleeping) {
pair.timeUpdated = timestamp;
2014-02-19 09:15:05 -05:00
continue;
}
// if pair is inactive for too long, mark it to be removed
if (timestamp - pair.timeUpdated > _pairMaxIdleLife) {
indexesToRemove.push(i);
2014-02-19 09:15:05 -05:00
}
}
// remove marked pairs
for (i = 0; i < indexesToRemove.length; i++) {
pairIndex = indexesToRemove[i] - i;
pair = pairsList[pairIndex];
delete pairsTable[pair.id];
pairsList.splice(pairIndex, 1);
}
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
2014-03-24 16:11:42 -04:00
* Clears the given pairs structure
2015-06-29 15:58:24 -04:00
* @method clear
2014-03-24 16:11:42 -04:00
* @param {pairs} pairs
2015-06-29 15:58:24 -04:00
* @return {pairs} pairs
2014-03-24 16:11:42 -04:00
*/
Pairs.clear = function(pairs) {
pairs.table = {};
pairs.list.length = 0;
pairs.collisionStart.length = 0;
pairs.collisionActive.length = 0;
pairs.collisionEnd.length = 0;
return pairs;
2014-02-19 09:15:05 -05:00
};
})();
2015-06-29 15:58:24 -04:00
2014-03-24 16:11:42 -04:00
; // End src/collision/Pairs.js
2014-02-19 09:15:05 -05:00
2014-05-01 09:09:06 -04:00
// Begin src/collision/Query.js
/**
2014-06-09 14:40:24 -04:00
* The `Matter.Query` module contains methods for performing collision queries.
2014-05-01 09:09:06 -04:00
*
* @class Query
*/
var Query = {};
(function() {
/**
* Casts a ray segment against a set of bodies and returns all collisions, ray width is optional. Intersection points are not provided.
* @method ray
* @param {body[]} bodies
* @param {vector} startPoint
* @param {vector} endPoint
2014-06-09 14:40:24 -04:00
* @param {number} [rayWidth]
2014-05-01 09:09:06 -04:00
* @return {object[]} Collisions
*/
Query.ray = function(bodies, startPoint, endPoint, rayWidth) {
2015-05-20 15:38:41 -04:00
rayWidth = rayWidth || 1e-100;
2014-05-01 09:09:06 -04:00
var rayAngle = Vector.angle(startPoint, endPoint),
rayLength = Vector.magnitude(Vector.sub(startPoint, endPoint)),
rayX = (endPoint.x + startPoint.x) * 0.5,
rayY = (endPoint.y + startPoint.y) * 0.5,
ray = Bodies.rectangle(rayX, rayY, rayLength, rayWidth, { angle: rayAngle }),
collisions = [];
for (var i = 0; i < bodies.length; i++) {
var bodyA = bodies[i];
2015-05-20 15:38:41 -04:00
2014-05-01 09:09:06 -04:00
if (Bounds.overlaps(bodyA.bounds, ray.bounds)) {
2015-05-20 15:38:41 -04:00
for (var j = bodyA.parts.length === 1 ? 0 : 1; j < bodyA.parts.length; j++) {
var part = bodyA.parts[j];
if (Bounds.overlaps(part.bounds, ray.bounds)) {
var collision = SAT.collides(part, ray);
if (collision.collided) {
collision.body = collision.bodyA = collision.bodyB = bodyA;
collisions.push(collision);
break;
}
}
2014-05-01 09:09:06 -04:00
}
}
}
return collisions;
};
/**
2014-06-09 14:40:24 -04:00
* Returns all bodies whose bounds are inside (or outside if set) the given set of bounds, from the given set of bodies.
2014-05-01 09:09:06 -04:00
* @method region
* @param {body[]} bodies
* @param {bounds} bounds
2014-06-09 14:40:24 -04:00
* @param {bool} [outside=false]
2014-05-01 09:09:06 -04:00
* @return {body[]} The bodies matching the query
*/
Query.region = function(bodies, bounds, outside) {
var result = [];
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i],
overlaps = Bounds.overlaps(body.bounds, bounds);
if ((overlaps && !outside) || (!overlaps && outside))
result.push(body);
}
return result;
};
2015-05-20 15:38:41 -04:00
/**
* Returns all bodies whose vertices contain the given point, from the given set of bodies.
* @method point
* @param {body[]} bodies
* @param {vector} point
* @return {body[]} The bodies matching the query
*/
Query.point = function(bodies, point) {
var result = [];
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
if (Bounds.contains(body.bounds, point)) {
for (var j = body.parts.length === 1 ? 0 : 1; j < body.parts.length; j++) {
var part = body.parts[j];
if (Bounds.contains(part.bounds, point)
&& Vertices.contains(part.vertices, point)) {
result.push(body);
break;
}
}
}
}
return result;
};
2014-05-01 09:09:06 -04:00
})();
; // End src/collision/Query.js
2014-02-19 09:15:05 -05:00
// Begin src/collision/Resolver.js
2014-02-28 20:10:08 -05:00
/**
* _Internal Class_, not generally used outside of the engine's internals.
*
* @class Resolver
*/
2014-02-19 09:15:05 -05:00
var Resolver = {};
(function() {
2015-05-20 15:38:41 -04:00
Resolver._restingThresh = 4;
Resolver._positionDampen = 0.9;
Resolver._positionWarming = 0.8;
Resolver._frictionNormalMultiplier = 5;
/**
* Description
* @method preSolvePosition
* @param {pair[]} pairs
*/
Resolver.preSolvePosition = function(pairs) {
var i,
pair,
activeCount;
// find total contacts on each body
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
if (!pair.isActive)
continue;
activeCount = pair.activeContacts.length;
pair.collision.parentA.totalContacts += activeCount;
pair.collision.parentB.totalContacts += activeCount;
}
};
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
* Description
* @method solvePosition
* @param {pair[]} pairs
2014-05-01 09:09:06 -04:00
* @param {number} timeScale
2014-02-28 20:10:08 -05:00
*/
2014-05-01 09:09:06 -04:00
Resolver.solvePosition = function(pairs, timeScale) {
2014-02-19 09:15:05 -05:00
var i,
pair,
collision,
bodyA,
bodyB,
normal,
2015-05-20 15:38:41 -04:00
bodyBtoA,
contactShare,
2015-07-02 15:17:03 -04:00
positionImpulse,
2015-05-20 15:38:41 -04:00
contactCount = {},
tempA = Vector._temp[0],
tempB = Vector._temp[1],
tempC = Vector._temp[2],
tempD = Vector._temp[3];
2014-02-19 09:15:05 -05:00
// find impulses required to resolve penetration
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
if (!pair.isActive)
continue;
collision = pair.collision;
2015-05-20 15:38:41 -04:00
bodyA = collision.parentA;
bodyB = collision.parentB;
2014-02-19 09:15:05 -05:00
normal = collision.normal;
// get current separation between body edges involved in collision
2015-05-20 15:38:41 -04:00
bodyBtoA = Vector.sub(Vector.add(bodyB.positionImpulse, bodyB.position, tempA),
Vector.add(bodyA.positionImpulse,
Vector.sub(bodyB.position, collision.penetration, tempB), tempC), tempD);
2014-02-19 09:15:05 -05:00
pair.separation = Vector.dot(normal, bodyBtoA);
}
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
2015-05-20 15:38:41 -04:00
if (!pair.isActive || pair.separation < 0)
2014-02-19 09:15:05 -05:00
continue;
collision = pair.collision;
2015-05-20 15:38:41 -04:00
bodyA = collision.parentA;
bodyB = collision.parentB;
2014-02-19 09:15:05 -05:00
normal = collision.normal;
2015-05-20 15:38:41 -04:00
positionImpulse = (pair.separation - pair.slop) * timeScale;
2014-02-19 09:15:05 -05:00
if (bodyA.isStatic || bodyB.isStatic)
positionImpulse *= 2;
if (!(bodyA.isStatic || bodyA.isSleeping)) {
2015-05-20 15:38:41 -04:00
contactShare = Resolver._positionDampen / bodyA.totalContacts;
bodyA.positionImpulse.x += normal.x * positionImpulse * contactShare;
bodyA.positionImpulse.y += normal.y * positionImpulse * contactShare;
2014-02-19 09:15:05 -05:00
}
if (!(bodyB.isStatic || bodyB.isSleeping)) {
2015-05-20 15:38:41 -04:00
contactShare = Resolver._positionDampen / bodyB.totalContacts;
bodyB.positionImpulse.x -= normal.x * positionImpulse * contactShare;
bodyB.positionImpulse.y -= normal.y * positionImpulse * contactShare;
2014-02-19 09:15:05 -05:00
}
}
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method postSolvePosition
* @param {body[]} bodies
*/
2014-02-19 09:15:05 -05:00
Resolver.postSolvePosition = function(bodies) {
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
2015-05-20 15:38:41 -04:00
// reset contact count
body.totalContacts = 0;
2014-02-19 09:15:05 -05:00
if (body.positionImpulse.x !== 0 || body.positionImpulse.y !== 0) {
2015-05-20 15:38:41 -04:00
// update body geometry
for (var j = 0; j < body.parts.length; j++) {
var part = body.parts[j];
Vertices.translate(part.vertices, body.positionImpulse);
Bounds.update(part.bounds, part.vertices, body.velocity);
part.position.x += body.positionImpulse.x;
part.position.y += body.positionImpulse.y;
}
2014-02-19 09:15:05 -05:00
// move the body without changing velocity
body.positionPrev.x += body.positionImpulse.x;
body.positionPrev.y += body.positionImpulse.y;
2015-05-20 15:38:41 -04:00
if (Vector.dot(body.positionImpulse, body.velocity) < 0) {
// reset cached impulse if the body has velocity along it
body.positionImpulse.x = 0;
body.positionImpulse.y = 0;
} else {
// warm the next iteration
body.positionImpulse.x *= Resolver._positionWarming;
body.positionImpulse.y *= Resolver._positionWarming;
}
2014-02-19 09:15:05 -05:00
}
}
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method preSolveVelocity
* @param {pair[]} pairs
*/
2014-02-19 09:15:05 -05:00
Resolver.preSolveVelocity = function(pairs) {
2015-05-20 15:38:41 -04:00
var i,
2014-02-19 09:15:05 -05:00
j,
pair,
contacts,
collision,
bodyA,
bodyB,
normal,
tangent,
contact,
contactVertex,
normalImpulse,
tangentImpulse,
2015-05-20 15:38:41 -04:00
offset,
impulse = Vector._temp[0],
tempA = Vector._temp[1];
2014-02-19 09:15:05 -05:00
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
if (!pair.isActive)
continue;
contacts = pair.activeContacts;
collision = pair.collision;
2015-05-20 15:38:41 -04:00
bodyA = collision.parentA;
bodyB = collision.parentB;
2014-02-19 09:15:05 -05:00
normal = collision.normal;
tangent = collision.tangent;
// resolve each contact
for (j = 0; j < contacts.length; j++) {
contact = contacts[j];
contactVertex = contact.vertex;
normalImpulse = contact.normalImpulse;
tangentImpulse = contact.tangentImpulse;
2015-05-20 15:38:41 -04:00
if (normalImpulse !== 0 || tangentImpulse !== 0) {
// total impulse from contact
impulse.x = (normal.x * normalImpulse) + (tangent.x * tangentImpulse);
impulse.y = (normal.y * normalImpulse) + (tangent.y * tangentImpulse);
// apply impulse from contact
if (!(bodyA.isStatic || bodyA.isSleeping)) {
offset = Vector.sub(contactVertex, bodyA.position, tempA);
bodyA.positionPrev.x += impulse.x * bodyA.inverseMass;
bodyA.positionPrev.y += impulse.y * bodyA.inverseMass;
bodyA.anglePrev += Vector.cross(offset, impulse) * bodyA.inverseInertia;
}
if (!(bodyB.isStatic || bodyB.isSleeping)) {
offset = Vector.sub(contactVertex, bodyB.position, tempA);
bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass;
bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass;
bodyB.anglePrev -= Vector.cross(offset, impulse) * bodyB.inverseInertia;
}
2014-02-19 09:15:05 -05:00
}
}
}
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method solveVelocity
* @param {pair[]} pairs
2015-06-29 15:58:24 -04:00
* @param {number} timeScale
2014-02-28 20:10:08 -05:00
*/
2014-05-05 14:32:51 -04:00
Resolver.solveVelocity = function(pairs, timeScale) {
2015-05-20 15:38:41 -04:00
var timeScaleSquared = timeScale * timeScale,
impulse = Vector._temp[0],
tempA = Vector._temp[1],
tempB = Vector._temp[2],
tempC = Vector._temp[3],
tempD = Vector._temp[4],
tempE = Vector._temp[5];
2014-02-19 09:15:05 -05:00
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i];
if (!pair.isActive)
continue;
var collision = pair.collision,
2015-05-20 15:38:41 -04:00
bodyA = collision.parentA,
bodyB = collision.parentB,
2014-02-19 09:15:05 -05:00
normal = collision.normal,
tangent = collision.tangent,
contacts = pair.activeContacts,
contactShare = 1 / contacts.length;
// update body velocities
bodyA.velocity.x = bodyA.position.x - bodyA.positionPrev.x;
bodyA.velocity.y = bodyA.position.y - bodyA.positionPrev.y;
bodyB.velocity.x = bodyB.position.x - bodyB.positionPrev.x;
bodyB.velocity.y = bodyB.position.y - bodyB.positionPrev.y;
bodyA.angularVelocity = bodyA.angle - bodyA.anglePrev;
bodyB.angularVelocity = bodyB.angle - bodyB.anglePrev;
// resolve each contact
for (var j = 0; j < contacts.length; j++) {
var contact = contacts[j],
contactVertex = contact.vertex,
2015-05-20 15:38:41 -04:00
offsetA = Vector.sub(contactVertex, bodyA.position, tempA),
offsetB = Vector.sub(contactVertex, bodyB.position, tempB),
velocityPointA = Vector.add(bodyA.velocity, Vector.mult(Vector.perp(offsetA), bodyA.angularVelocity), tempC),
velocityPointB = Vector.add(bodyB.velocity, Vector.mult(Vector.perp(offsetB), bodyB.angularVelocity), tempD),
relativeVelocity = Vector.sub(velocityPointA, velocityPointB, tempE),
2014-02-19 09:15:05 -05:00
normalVelocity = Vector.dot(normal, relativeVelocity);
var tangentVelocity = Vector.dot(tangent, relativeVelocity),
tangentSpeed = Math.abs(tangentVelocity),
tangentVelocityDirection = Common.sign(tangentVelocity);
// raw impulses
var normalImpulse = (1 + pair.restitution) * normalVelocity,
2015-05-20 15:38:41 -04:00
normalForce = Common.clamp(pair.separation + normalVelocity, 0, 1) * Resolver._frictionNormalMultiplier;
2014-02-19 09:15:05 -05:00
// coulomb friction
2015-05-20 15:38:41 -04:00
var tangentImpulse = tangentVelocity,
maxFriction = Infinity;
if (tangentSpeed > pair.friction * pair.frictionStatic * normalForce * timeScaleSquared) {
tangentImpulse = pair.friction * tangentVelocityDirection * timeScaleSquared;
maxFriction = tangentSpeed;
}
2014-02-19 09:15:05 -05:00
// modify impulses accounting for mass, inertia and offset
var oAcN = Vector.cross(offsetA, normal),
oBcN = Vector.cross(offsetB, normal),
2015-05-20 15:38:41 -04:00
denom = bodyA.inverseMass + bodyB.inverseMass + bodyA.inverseInertia * oAcN * oAcN + bodyB.inverseInertia * oBcN * oBcN;
normalImpulse *= contactShare / denom;
tangentImpulse *= contactShare / (1 + denom);
2014-02-19 09:15:05 -05:00
// handle high velocity and resting collisions separately
2015-05-20 15:38:41 -04:00
if (normalVelocity < 0 && normalVelocity * normalVelocity > Resolver._restingThresh * timeScaleSquared) {
2014-02-19 09:15:05 -05:00
// high velocity so clear cached contact impulse
contact.normalImpulse = 0;
contact.tangentImpulse = 0;
} else {
// solve resting collision constraints using Erin Catto's method (GDC08)
// impulse constraint, tends to 0
var contactNormalImpulse = contact.normalImpulse;
contact.normalImpulse = Math.min(contact.normalImpulse + normalImpulse, 0);
normalImpulse = contact.normalImpulse - contactNormalImpulse;
// tangent impulse, tends to -maxFriction or maxFriction
var contactTangentImpulse = contact.tangentImpulse;
2015-05-20 15:38:41 -04:00
contact.tangentImpulse = Common.clamp(contact.tangentImpulse + tangentImpulse, -maxFriction, maxFriction);
2014-02-19 09:15:05 -05:00
tangentImpulse = contact.tangentImpulse - contactTangentImpulse;
}
// total impulse from contact
impulse.x = (normal.x * normalImpulse) + (tangent.x * tangentImpulse);
impulse.y = (normal.y * normalImpulse) + (tangent.y * tangentImpulse);
// apply impulse from contact
if (!(bodyA.isStatic || bodyA.isSleeping)) {
bodyA.positionPrev.x += impulse.x * bodyA.inverseMass;
bodyA.positionPrev.y += impulse.y * bodyA.inverseMass;
bodyA.anglePrev += Vector.cross(offsetA, impulse) * bodyA.inverseInertia;
}
if (!(bodyB.isStatic || bodyB.isSleeping)) {
bodyB.positionPrev.x -= impulse.x * bodyB.inverseMass;
bodyB.positionPrev.y -= impulse.y * bodyB.inverseMass;
bodyB.anglePrev -= Vector.cross(offsetB, impulse) * bodyB.inverseInertia;
}
}
}
};
})();
2015-06-29 15:58:24 -04:00
2014-02-19 09:15:05 -05:00
; // End src/collision/Resolver.js
// Begin src/collision/SAT.js
2014-02-28 20:10:08 -05:00
/**
* _Internal Class_, not generally used outside of the engine's internals.
*
* @class SAT
*/
2014-02-19 09:15:05 -05:00
// TODO: true circles and curves
var SAT = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
* Description
* @method collides
* @param {body} bodyA
* @param {body} bodyB
* @param {collision} previousCollision
2014-02-28 20:10:08 -05:00
* @return {collision} collision
*/
SAT.collides = function(bodyA, bodyB, previousCollision) {
2014-02-19 09:15:05 -05:00
var overlapAB,
overlapBA,
minOverlap,
collision,
prevCol = previousCollision,
canReusePrevCol = false;
if (prevCol) {
// estimate total motion
2015-05-20 15:38:41 -04:00
var parentA = bodyA.parent,
parentB = bodyB.parent,
motion = parentA.speed * parentA.speed + parentA.angularSpeed * parentA.angularSpeed
+ parentB.speed * parentB.speed + parentB.angularSpeed * parentB.angularSpeed;
2014-02-19 09:15:05 -05:00
// we may be able to (partially) reuse collision result
// but only safe if collision was resting
canReusePrevCol = prevCol && prevCol.collided && motion < 0.2;
2014-02-19 09:15:05 -05:00
// reuse collision object
collision = prevCol;
} else {
collision = { collided: false, bodyA: bodyA, bodyB: bodyB };
}
2014-02-19 09:15:05 -05:00
if (prevCol && canReusePrevCol) {
// if we can reuse the collision result
// we only need to test the previously found axis
2015-05-20 15:38:41 -04:00
var axisBodyA = collision.axisBody,
axisBodyB = axisBodyA === bodyA ? bodyB : bodyA,
axes = [axisBodyA.axes[prevCol.axisNumber]];
2014-02-19 09:15:05 -05:00
2015-05-20 15:38:41 -04:00
minOverlap = _overlapAxes(axisBodyA.vertices, axisBodyB.vertices, axes);
collision.reused = true;
2014-02-19 09:15:05 -05:00
if (minOverlap.overlap <= 0) {
collision.collided = false;
return collision;
}
2014-02-19 09:15:05 -05:00
} else {
// if we can't reuse a result, perform a full SAT test
overlapAB = _overlapAxes(bodyA.vertices, bodyB.vertices, bodyA.axes);
if (overlapAB.overlap <= 0) {
collision.collided = false;
return collision;
}
overlapBA = _overlapAxes(bodyB.vertices, bodyA.vertices, bodyB.axes);
if (overlapBA.overlap <= 0) {
collision.collided = false;
return collision;
}
if (overlapAB.overlap < overlapBA.overlap) {
minOverlap = overlapAB;
2015-05-20 15:38:41 -04:00
collision.axisBody = bodyA;
} else {
minOverlap = overlapBA;
2015-05-20 15:38:41 -04:00
collision.axisBody = bodyB;
}
// important for reuse later
collision.axisNumber = minOverlap.axisNumber;
2014-02-19 09:15:05 -05:00
}
2015-05-20 15:38:41 -04:00
collision.bodyA = bodyA.id < bodyB.id ? bodyA : bodyB;
collision.bodyB = bodyA.id < bodyB.id ? bodyB : bodyA;
2014-02-19 09:15:05 -05:00
collision.collided = true;
collision.normal = minOverlap.axis;
collision.depth = minOverlap.overlap;
2015-05-20 15:38:41 -04:00
collision.parentA = collision.bodyA.parent;
collision.parentB = collision.bodyB.parent;
2014-02-19 09:15:05 -05:00
bodyA = collision.bodyA;
bodyB = collision.bodyB;
// ensure normal is facing away from bodyA
if (Vector.dot(collision.normal, Vector.sub(bodyB.position, bodyA.position)) > 0)
collision.normal = Vector.neg(collision.normal);
collision.tangent = Vector.perp(collision.normal);
collision.penetration = {
x: collision.normal.x * collision.depth,
y: collision.normal.y * collision.depth
};
// find support points, there is always either exactly one or two
var verticesB = _findSupports(bodyA, bodyB, collision.normal),
2014-07-29 11:26:49 -04:00
supports = collision.supports || [];
supports.length = 0;
// find the supports from bodyB that are inside bodyA
if (Vertices.contains(bodyA.vertices, verticesB[0]))
supports.push(verticesB[0]);
if (Vertices.contains(bodyA.vertices, verticesB[1]))
2014-02-19 09:15:05 -05:00
supports.push(verticesB[1]);
2014-07-29 11:26:49 -04:00
// find the supports from bodyA that are inside bodyB
if (supports.length < 2) {
2014-02-19 09:15:05 -05:00
var verticesA = _findSupports(bodyB, bodyA, Vector.neg(collision.normal));
2014-07-29 11:26:49 -04:00
if (Vertices.contains(bodyB.vertices, verticesA[0]))
2014-02-19 09:15:05 -05:00
supports.push(verticesA[0]);
2014-07-29 11:26:49 -04:00
if (supports.length < 2 && Vertices.contains(bodyB.vertices, verticesA[1]))
2014-02-19 09:15:05 -05:00
supports.push(verticesA[1]);
}
2014-07-29 11:26:49 -04:00
// account for the edge case of overlapping but no vertex containment
2015-05-20 15:38:41 -04:00
if (supports.length < 1)
2014-07-29 11:26:49 -04:00
supports = [verticesB[0]];
2014-02-19 09:15:05 -05:00
collision.supports = supports;
return collision;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _overlapAxes
* @private
* @param {} verticesA
* @param {} verticesB
* @param {} axes
* @return result
*/
2014-02-19 09:15:05 -05:00
var _overlapAxes = function(verticesA, verticesB, axes) {
2015-05-20 15:38:41 -04:00
var projectionA = Vector._temp[0],
projectionB = Vector._temp[1],
2014-02-19 09:15:05 -05:00
result = { overlap: Number.MAX_VALUE },
overlap,
axis;
for (var i = 0; i < axes.length; i++) {
axis = axes[i];
_projectToAxis(projectionA, verticesA, axis);
_projectToAxis(projectionB, verticesB, axis);
2015-05-20 15:38:41 -04:00
overlap = Math.min(projectionA.max - projectionB.min, projectionB.max - projectionA.min);
2014-02-19 09:15:05 -05:00
if (overlap <= 0) {
result.overlap = overlap;
return result;
}
2014-02-19 09:15:05 -05:00
if (overlap < result.overlap) {
result.overlap = overlap;
result.axis = axis;
result.axisNumber = i;
2014-02-19 09:15:05 -05:00
}
}
return result;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _projectToAxis
* @private
* @param {} projection
* @param {} vertices
* @param {} axis
*/
2014-02-19 09:15:05 -05:00
var _projectToAxis = function(projection, vertices, axis) {
var min = Vector.dot(vertices[0], axis),
max = min;
for (var i = 1; i < vertices.length; i += 1) {
var dot = Vector.dot(vertices[i], axis);
if (dot > max) {
max = dot;
} else if (dot < min) {
min = dot;
}
}
projection.min = min;
projection.max = max;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _findSupports
* @private
* @param {} bodyA
* @param {} bodyB
* @param {} normal
* @return ArrayExpression
*/
2014-02-19 09:15:05 -05:00
var _findSupports = function(bodyA, bodyB, normal) {
var nearestDistance = Number.MAX_VALUE,
2015-05-20 15:38:41 -04:00
vertexToBody = Vector._temp[0],
2014-02-19 09:15:05 -05:00
vertices = bodyB.vertices,
bodyAPosition = bodyA.position,
distance,
vertex,
2015-05-20 15:38:41 -04:00
vertexA,
vertexB;
2014-02-19 09:15:05 -05:00
// find closest vertex on bodyB
for (var i = 0; i < vertices.length; i++) {
vertex = vertices[i];
vertexToBody.x = vertex.x - bodyAPosition.x;
vertexToBody.y = vertex.y - bodyAPosition.y;
distance = -Vector.dot(normal, vertexToBody);
if (distance < nearestDistance) {
nearestDistance = distance;
vertexA = vertex;
}
}
// find next closest vertex using the two connected to it
var prevIndex = vertexA.index - 1 >= 0 ? vertexA.index - 1 : vertices.length - 1;
vertex = vertices[prevIndex];
vertexToBody.x = vertex.x - bodyAPosition.x;
vertexToBody.y = vertex.y - bodyAPosition.y;
nearestDistance = -Vector.dot(normal, vertexToBody);
vertexB = vertex;
var nextIndex = (vertexA.index + 1) % vertices.length;
vertex = vertices[nextIndex];
vertexToBody.x = vertex.x - bodyAPosition.x;
vertexToBody.y = vertex.y - bodyAPosition.y;
distance = -Vector.dot(normal, vertexToBody);
if (distance < nearestDistance) {
vertexB = vertex;
}
return [vertexA, vertexB];
};
})();
2015-01-01 18:10:10 -05:00
2014-02-19 09:15:05 -05:00
; // End src/collision/SAT.js
// Begin src/constraint/Constraint.js
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* The `Matter.Constraint` module contains methods for creating and manipulating constraints.
* Constraints are used for specifying that a fixed distance must be maintained between two bodies (or a body and a fixed world-space position).
* The stiffness of constraints can be modified to create springs or elastic.
*
2014-02-28 20:10:08 -05:00
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class Constraint
*/
2015-06-29 15:58:24 -04:00
// TODO: fix instability issues with torque
2014-02-19 09:15:05 -05:00
// TODO: linked constraints
// TODO: breakable constraints
2015-06-29 15:58:24 -04:00
// TODO: collision constraints
2014-02-19 09:15:05 -05:00
// TODO: allow constrained bodies to sleep
// TODO: handle 0 length constraints properly
// TODO: impulse caching and warming
var Constraint = {};
(function() {
var _minLength = 0.000001,
2014-05-01 09:09:06 -04:00
_minDifference = 0.001;
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Creates a new constraint.
* All properties have default values, and many are pre-calculated automatically based on other properties.
2015-06-29 15:58:24 -04:00
* See the properties section below for detailed information on what you can pass via the `options` object.
2014-02-28 20:10:08 -05:00
* @method create
* @param {} options
* @return {constraint} constraint
*/
2014-02-19 09:15:05 -05:00
Constraint.create = function(options) {
var constraint = options;
// if bodies defined but no points, use body centre
if (constraint.bodyA && !constraint.pointA)
constraint.pointA = { x: 0, y: 0 };
if (constraint.bodyB && !constraint.pointB)
constraint.pointB = { x: 0, y: 0 };
// calculate static length using initial world space points
var initialPointA = constraint.bodyA ? Vector.add(constraint.bodyA.position, constraint.pointA) : constraint.pointA,
initialPointB = constraint.bodyB ? Vector.add(constraint.bodyB.position, constraint.pointB) : constraint.pointB,
length = Vector.magnitude(Vector.sub(initialPointA, initialPointB));
constraint.length = constraint.length || length || _minLength;
// render
var render = {
visible: true,
lineWidth: 2,
strokeStyle: '#666'
};
constraint.render = Common.extend(render, constraint.render);
2014-02-19 09:15:05 -05:00
// option defaults
2014-05-01 09:09:06 -04:00
constraint.id = constraint.id || Common.nextId();
constraint.label = constraint.label || 'Constraint';
2014-03-30 14:45:30 -04:00
constraint.type = 'constraint';
2014-02-19 09:15:05 -05:00
constraint.stiffness = constraint.stiffness || 1;
constraint.angularStiffness = constraint.angularStiffness || 0;
constraint.angleA = constraint.bodyA ? constraint.bodyA.angle : constraint.angleA;
constraint.angleB = constraint.bodyB ? constraint.bodyB.angle : constraint.angleB;
return constraint;
};
2014-02-28 20:10:08 -05:00
/**
* Description
2014-06-09 14:40:24 -04:00
* @private
2014-03-30 14:45:30 -04:00
* @method solveAll
2014-02-28 20:10:08 -05:00
* @param {constraint[]} constraints
2014-05-01 09:09:06 -04:00
* @param {number} timeScale
2014-02-28 20:10:08 -05:00
*/
2014-05-01 09:09:06 -04:00
Constraint.solveAll = function(constraints, timeScale) {
2014-02-19 09:15:05 -05:00
for (var i = 0; i < constraints.length; i++) {
2014-05-01 09:09:06 -04:00
Constraint.solve(constraints[i], timeScale);
2014-02-19 09:15:05 -05:00
}
};
2014-02-28 20:10:08 -05:00
/**
* Description
2014-06-09 14:40:24 -04:00
* @private
2014-03-30 14:45:30 -04:00
* @method solve
2014-02-28 20:10:08 -05:00
* @param {constraint} constraint
2014-05-01 09:09:06 -04:00
* @param {number} timeScale
2014-02-28 20:10:08 -05:00
*/
2014-05-01 09:09:06 -04:00
Constraint.solve = function(constraint, timeScale) {
2014-02-19 09:15:05 -05:00
var bodyA = constraint.bodyA,
bodyB = constraint.bodyB,
pointA = constraint.pointA,
pointB = constraint.pointB;
// update reference angle
if (bodyA && !bodyA.isStatic) {
constraint.pointA = Vector.rotate(pointA, bodyA.angle - constraint.angleA);
constraint.angleA = bodyA.angle;
}
// update reference angle
if (bodyB && !bodyB.isStatic) {
constraint.pointB = Vector.rotate(pointB, bodyB.angle - constraint.angleB);
constraint.angleB = bodyB.angle;
}
var pointAWorld = pointA,
pointBWorld = pointB;
if (bodyA) pointAWorld = Vector.add(bodyA.position, pointA);
if (bodyB) pointBWorld = Vector.add(bodyB.position, pointB);
if (!pointAWorld || !pointBWorld)
return;
var delta = Vector.sub(pointAWorld, pointBWorld),
currentLength = Vector.magnitude(delta);
// prevent singularity
if (currentLength === 0)
currentLength = _minLength;
// solve distance constraint with Gauss-Siedel method
var difference = (currentLength - constraint.length) / currentLength,
normal = Vector.div(delta, currentLength),
2014-05-01 09:09:06 -04:00
force = Vector.mult(delta, difference * 0.5 * constraint.stiffness * timeScale * timeScale);
2014-04-01 08:47:17 -04:00
// if difference is very small, we can skip
2014-05-01 09:09:06 -04:00
if (Math.abs(1 - (currentLength / constraint.length)) < _minDifference * timeScale)
2014-04-01 08:47:17 -04:00
return;
2014-02-19 09:15:05 -05:00
var velocityPointA,
velocityPointB,
offsetA,
offsetB,
oAn,
oBn,
bodyADenom,
bodyBDenom;
if (bodyA && !bodyA.isStatic) {
// point body offset
offsetA = {
x: pointAWorld.x - bodyA.position.x + force.x,
y: pointAWorld.y - bodyA.position.y + force.y
};
// update velocity
bodyA.velocity.x = bodyA.position.x - bodyA.positionPrev.x;
bodyA.velocity.y = bodyA.position.y - bodyA.positionPrev.y;
bodyA.angularVelocity = bodyA.angle - bodyA.anglePrev;
// find point velocity and body mass
velocityPointA = Vector.add(bodyA.velocity, Vector.mult(Vector.perp(offsetA), bodyA.angularVelocity));
oAn = Vector.dot(offsetA, normal);
bodyADenom = bodyA.inverseMass + bodyA.inverseInertia * oAn * oAn;
} else {
velocityPointA = { x: 0, y: 0 };
bodyADenom = bodyA ? bodyA.inverseMass : 0;
}
if (bodyB && !bodyB.isStatic) {
// point body offset
offsetB = {
x: pointBWorld.x - bodyB.position.x - force.x,
y: pointBWorld.y - bodyB.position.y - force.y
};
// update velocity
bodyB.velocity.x = bodyB.position.x - bodyB.positionPrev.x;
bodyB.velocity.y = bodyB.position.y - bodyB.positionPrev.y;
bodyB.angularVelocity = bodyB.angle - bodyB.anglePrev;
// find point velocity and body mass
velocityPointB = Vector.add(bodyB.velocity, Vector.mult(Vector.perp(offsetB), bodyB.angularVelocity));
oBn = Vector.dot(offsetB, normal);
bodyBDenom = bodyB.inverseMass + bodyB.inverseInertia * oBn * oBn;
} else {
velocityPointB = { x: 0, y: 0 };
bodyBDenom = bodyB ? bodyB.inverseMass : 0;
}
var relativeVelocity = Vector.sub(velocityPointB, velocityPointA),
normalImpulse = Vector.dot(normal, relativeVelocity) / (bodyADenom + bodyBDenom);
if (normalImpulse > 0) normalImpulse = 0;
var normalVelocity = {
x: normal.x * normalImpulse,
y: normal.y * normalImpulse
};
var torque;
if (bodyA && !bodyA.isStatic) {
torque = Vector.cross(offsetA, normalVelocity) * bodyA.inverseInertia * (1 - constraint.angularStiffness);
Sleeping.set(bodyA, false);
2015-06-29 15:58:24 -04:00
// clamp to prevent instability
// TODO: solve this properly
2014-02-19 09:15:05 -05:00
torque = Common.clamp(torque, -0.01, 0.01);
2014-03-30 14:45:30 -04:00
// keep track of applied impulses for post solving
bodyA.constraintImpulse.x -= force.x;
bodyA.constraintImpulse.y -= force.y;
bodyA.constraintImpulse.angle += torque;
2014-02-19 09:15:05 -05:00
// apply forces
bodyA.position.x -= force.x;
bodyA.position.y -= force.y;
bodyA.angle += torque;
}
if (bodyB && !bodyB.isStatic) {
torque = Vector.cross(offsetB, normalVelocity) * bodyB.inverseInertia * (1 - constraint.angularStiffness);
Sleeping.set(bodyB, false);
2015-06-29 15:58:24 -04:00
// clamp to prevent instability
// TODO: solve this properly
2014-02-19 09:15:05 -05:00
torque = Common.clamp(torque, -0.01, 0.01);
2014-03-30 14:45:30 -04:00
// keep track of applied impulses for post solving
bodyB.constraintImpulse.x += force.x;
bodyB.constraintImpulse.y += force.y;
bodyB.constraintImpulse.angle -= torque;
2014-02-19 09:15:05 -05:00
// apply forces
bodyB.position.x += force.x;
bodyB.position.y += force.y;
bodyB.angle -= torque;
}
};
2014-03-30 14:45:30 -04:00
/**
* Performs body updates required after solving constraints
2014-06-09 14:40:24 -04:00
* @private
2014-03-30 14:45:30 -04:00
* @method postSolveAll
* @param {body[]} bodies
*/
Constraint.postSolveAll = function(bodies) {
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i],
impulse = body.constraintImpulse;
2014-04-01 08:47:17 -04:00
// update geometry and reset
2015-05-20 15:38:41 -04:00
for (var j = 0; j < body.parts.length; j++) {
var part = body.parts[j];
Vertices.translate(part.vertices, impulse);
2014-04-01 08:47:17 -04:00
2015-05-20 15:38:41 -04:00
if (j > 0) {
part.position.x += impulse.x;
part.position.y += impulse.y;
}
if (impulse.angle !== 0) {
Vertices.rotate(part.vertices, impulse.angle, body.position);
Axes.rotate(part.axes, impulse.angle);
if (j > 0) {
Vector.rotateAbout(part.position, impulse.angle, body.position, part.position);
}
}
2014-04-01 08:47:17 -04:00
2015-05-20 15:38:41 -04:00
Bounds.update(part.bounds, part.vertices);
}
2014-04-01 08:47:17 -04:00
2015-05-20 15:38:41 -04:00
impulse.angle = 0;
2014-04-01 08:47:17 -04:00
impulse.x = 0;
impulse.y = 0;
2014-03-30 14:45:30 -04:00
}
};
2014-06-09 14:40:24 -04:00
/*
*
* Properties Documentation
*
*/
/**
* An integer `Number` uniquely identifying number generated in `Composite.create` by `Common.nextId`.
*
* @property id
* @type number
*/
/**
* A `String` denoting the type of object.
*
* @property type
* @type string
* @default "constraint"
*/
/**
* An arbitrary `String` name to help the user identify and manage bodies.
*
* @property label
* @type string
* @default "Constraint"
*/
/**
* An `Object` that defines the rendering properties to be consumed by the module `Matter.Render`.
*
* @property render
* @type object
*/
/**
* A flag that indicates if the constraint should be rendered.
*
* @property render.visible
* @type boolean
* @default true
*/
/**
* A `Number` that defines the line width to use when rendering the constraint outline.
* A value of `0` means no outline will be rendered.
*
* @property render.lineWidth
* @type number
* @default 2
*/
/**
* A `String` that defines the stroke style to use when rendering the constraint outline.
* It is the same as when using a canvas, so it accepts CSS style property values.
*
* @property render.strokeStyle
* @type string
* @default a random colour
*/
/**
* The first possible `Body` that this constraint is attached to.
*
* @property bodyA
* @type body
* @default null
*/
/**
* The second possible `Body` that this constraint is attached to.
*
* @property bodyB
* @type body
* @default null
*/
/**
* A `Vector` that specifies the offset of the constraint from center of the `constraint.bodyA` if defined, otherwise a world-space position.
*
* @property pointA
* @type vector
* @default { x: 0, y: 0 }
*/
/**
* A `Vector` that specifies the offset of the constraint from center of the `constraint.bodyA` if defined, otherwise a world-space position.
*
* @property pointB
* @type vector
* @default { x: 0, y: 0 }
*/
/**
* A `Number` that specifies the stiffness of the constraint, i.e. the rate at which it returns to its resting `constraint.length`.
* A value of `1` means the constraint should be very stiff.
* A value of `0.2` means the constraint acts like a soft spring.
*
* @property stiffness
* @type number
* @default 1
*/
/**
* A `Number` that specifies the target resting length of the constraint.
2015-06-29 15:58:24 -04:00
* It is calculated automatically in `Constraint.create` from initial positions of the `constraint.bodyA` and `constraint.bodyB`.
2014-06-09 14:40:24 -04:00
*
* @property length
* @type number
*/
2014-02-19 09:15:05 -05:00
})();
2015-06-29 15:58:24 -04:00
2014-02-19 09:15:05 -05:00
; // End src/constraint/Constraint.js
// Begin src/constraint/MouseConstraint.js
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* The `Matter.MouseConstraint` module contains methods for creating mouse constraints.
* Mouse constraints are used for allowing user interaction, providing the ability to move bodies via the mouse or touch.
*
2014-04-01 08:47:17 -04:00
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
2014-02-28 20:10:08 -05:00
*
* @class MouseConstraint
*/
2014-02-19 09:15:05 -05:00
var MouseConstraint = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Creates a new mouse constraint.
* All properties have default values, and many are pre-calculated automatically based on other properties.
2015-06-29 15:58:24 -04:00
* See the properties section below for detailed information on what you can pass via the `options` object.
2014-02-28 20:10:08 -05:00
* @method create
2014-03-30 14:45:30 -04:00
* @param {engine} engine
* @param {} options
2014-02-28 20:10:08 -05:00
* @return {MouseConstraint} A new MouseConstraint
*/
2014-03-30 14:45:30 -04:00
MouseConstraint.create = function(engine, options) {
2015-01-20 19:15:04 -05:00
var mouse = (engine ? engine.mouse : null) || (options ? options.mouse : null);
if (!mouse && engine && engine.render && engine.render.canvas) {
mouse = Mouse.create(engine.render.canvas);
} else {
mouse = Mouse.create();
Common.log('MouseConstraint.create: options.mouse was undefined, engine.render.canvas was undefined, may not function as expected', 'warn');
}
2014-03-30 14:45:30 -04:00
2014-02-19 09:15:05 -05:00
var constraint = Constraint.create({
2014-05-01 09:09:06 -04:00
label: 'Mouse Constraint',
2014-02-19 09:15:05 -05:00
pointA: mouse.position,
pointB: { x: 0, y: 0 },
length: 0.01,
stiffness: 0.1,
angularStiffness: 1,
render: {
strokeStyle: '#90EE90',
lineWidth: 3
}
2014-02-19 09:15:05 -05:00
});
2014-03-30 14:45:30 -04:00
var defaults = {
type: 'mouseConstraint',
2014-02-19 09:15:05 -05:00
mouse: mouse,
2014-12-02 16:40:34 -05:00
body: null,
2014-07-29 11:26:49 -04:00
constraint: constraint,
collisionFilter: {
category: 0x0001,
mask: 0xFFFFFFFF,
group: 0
}
2014-02-19 09:15:05 -05:00
};
2014-03-30 14:45:30 -04:00
var mouseConstraint = Common.extend(defaults, options);
2015-01-01 18:10:10 -05:00
Events.on(engine, 'tick', function() {
2014-03-30 14:45:30 -04:00
var allBodies = Composite.allBodies(engine.world);
MouseConstraint.update(mouseConstraint, allBodies);
2014-07-29 11:26:49 -04:00
_triggerEvents(mouseConstraint);
2014-03-30 14:45:30 -04:00
});
return mouseConstraint;
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Updates the given mouse constraint.
* @private
2014-02-28 20:10:08 -05:00
* @method update
* @param {MouseConstraint} mouseConstraint
* @param {body[]} bodies
*/
2014-02-19 09:15:05 -05:00
MouseConstraint.update = function(mouseConstraint, bodies) {
var mouse = mouseConstraint.mouse,
2014-12-02 16:40:34 -05:00
constraint = mouseConstraint.constraint,
body = mouseConstraint.body;
2014-02-19 09:15:05 -05:00
2014-03-24 16:11:42 -04:00
if (mouse.button === 0) {
2014-02-19 09:15:05 -05:00
if (!constraint.bodyB) {
for (var i = 0; i < bodies.length; i++) {
2014-12-02 16:40:34 -05:00
body = bodies[i];
2014-02-19 09:15:05 -05:00
if (Bounds.contains(body.bounds, mouse.position)
2014-07-29 11:26:49 -04:00
&& Detector.canCollide(body.collisionFilter, mouseConstraint.collisionFilter)) {
2015-05-20 15:38:41 -04:00
for (var j = body.parts.length > 1 ? 1 : 0; j < body.parts.length; j++) {
var part = body.parts[j];
if (Vertices.contains(part.vertices, mouse.position)) {
constraint.pointA = mouse.position;
constraint.bodyB = mouseConstraint.body = body;
constraint.pointB = { x: mouse.position.x - body.position.x, y: mouse.position.y - body.position.y };
constraint.angleB = body.angle;
Sleeping.set(body, false);
Events.trigger(mouseConstraint, 'startdrag', { mouse: mouse, body: body });
break;
}
}
2014-02-19 09:15:05 -05:00
}
}
2014-12-02 16:40:34 -05:00
} else {
Sleeping.set(constraint.bodyB, false);
constraint.pointA = mouse.position;
2014-02-19 09:15:05 -05:00
}
} else {
2014-12-02 16:40:34 -05:00
constraint.bodyB = mouseConstraint.body = null;
2014-02-19 09:15:05 -05:00
constraint.pointB = null;
2014-12-02 16:40:34 -05:00
if (body)
Events.trigger(mouseConstraint, 'enddrag', { mouse: mouse, body: body });
2014-02-19 09:15:05 -05:00
}
};
2014-07-29 11:26:49 -04:00
/**
* Triggers mouse constraint events
* @method _triggerEvents
* @private
2015-06-29 15:58:24 -04:00
* @param {mouse} mouseConstraint
2014-07-29 11:26:49 -04:00
*/
var _triggerEvents = function(mouseConstraint) {
var mouse = mouseConstraint.mouse,
mouseEvents = mouse.sourceEvents;
if (mouseEvents.mousemove)
Events.trigger(mouseConstraint, 'mousemove', { mouse: mouse });
if (mouseEvents.mousedown)
Events.trigger(mouseConstraint, 'mousedown', { mouse: mouse });
if (mouseEvents.mouseup)
Events.trigger(mouseConstraint, 'mouseup', { mouse: mouse });
// reset the mouse state ready for the next step
Mouse.clearSourceEvents(mouse);
};
/*
*
* Events Documentation
*
*/
/**
* Fired when the mouse has moved (or a touch moves) during the last step
*
* @event mousemove
* @param {} event An event object
* @param {mouse} event.mouse The engine's mouse instance
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired when the mouse is down (or a touch has started) during the last step
*
* @event mousedown
* @param {} event An event object
* @param {mouse} event.mouse The engine's mouse instance
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired when the mouse is up (or a touch has ended) during the last step
*
* @event mouseup
* @param {} event An event object
* @param {mouse} event.mouse The engine's mouse instance
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
2014-12-02 16:40:34 -05:00
/**
* Fired when the user starts dragging a body
*
* @event startdrag
* @param {} event An event object
* @param {mouse} event.mouse The engine's mouse instance
* @param {body} event.body The body being dragged
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired when the user ends dragging a body
*
* @event enddrag
* @param {} event An event object
* @param {mouse} event.mouse The engine's mouse instance
* @param {body} event.body The body that has stopped being dragged
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
2014-06-09 14:40:24 -04:00
/*
*
* Properties Documentation
*
*/
/**
* A `String` denoting the type of object.
*
* @property type
* @type string
* @default "constraint"
*/
/**
2014-07-29 11:26:49 -04:00
* The `Mouse` instance in use. If not supplied in `MouseConstraint.create`, one will be created.
2014-06-09 14:40:24 -04:00
*
* @property mouse
* @type mouse
2014-07-29 11:26:49 -04:00
* @default mouse
2014-06-09 14:40:24 -04:00
*/
/**
* The `Body` that is currently being moved by the user, or `null` if no body.
*
2014-12-02 16:40:34 -05:00
* @property body
2014-06-09 14:40:24 -04:00
* @type body
* @default null
*/
/**
* The `Constraint` object that is used to move the body during interaction.
*
* @property constraint
* @type constraint
*/
2014-07-29 11:26:49 -04:00
/**
* An `Object` that specifies the collision filter properties.
* The collision filter allows the user to define which types of body this mouse constraint can interact with.
* See `body.collisionFilter` for more information.
*
* @property collisionFilter
* @type object
*/
2014-02-19 09:15:05 -05:00
})();
2015-01-01 18:10:10 -05:00
2014-02-19 09:15:05 -05:00
; // End src/constraint/MouseConstraint.js
// Begin src/core/Common.js
2014-02-28 20:10:08 -05:00
/**
* _Internal Class_, not generally used outside of the engine's internals.
*
* @class Common
*/
2014-02-19 09:15:05 -05:00
var Common = {};
(function() {
2014-05-01 09:09:06 -04:00
Common._nextId = 0;
2014-06-09 14:40:24 -04:00
Common._seed = 0;
2014-05-01 09:09:06 -04:00
2014-02-28 20:10:08 -05:00
/**
* Description
* @method extend
* @param {} obj
* @param {boolean} deep
2014-02-28 20:10:08 -05:00
* @return {} obj extended
*/
Common.extend = function(obj, deep) {
var argsStart,
args,
deepClone;
if (typeof deep === 'boolean') {
argsStart = 2;
deepClone = deep;
} else {
argsStart = 1;
deepClone = true;
}
args = Array.prototype.slice.call(arguments, argsStart);
2014-02-19 09:15:05 -05:00
for (var i = 0; i < args.length; i++) {
var source = args[i];
if (source) {
for (var prop in source) {
if (deepClone && source[prop] && source[prop].constructor === Object) {
2014-02-19 09:15:05 -05:00
if (!obj[prop] || obj[prop].constructor === Object) {
obj[prop] = obj[prop] || {};
Common.extend(obj[prop], deepClone, source[prop]);
2014-02-19 09:15:05 -05:00
} else {
obj[prop] = source[prop];
}
} else {
obj[prop] = source[prop];
}
}
}
}
return obj;
};
/**
* Creates a new clone of the object, if deep is true references will also be cloned
* @method clone
* @param {} obj
* @param {bool} deep
* @return {} obj cloned
*/
Common.clone = function(obj, deep) {
return Common.extend({}, deep, obj);
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method keys
* @param {} obj
* @return {string[]} keys
*/
2014-02-19 09:15:05 -05:00
Common.keys = function(obj) {
if (Object.keys)
return Object.keys(obj);
// avoid hasOwnProperty for performance
var keys = [];
for (var key in obj)
keys.push(key);
return keys;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method values
* @param {} obj
* @return {array} Array of the objects property values
*/
2014-02-19 09:15:05 -05:00
Common.values = function(obj) {
var values = [];
if (Object.keys) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
values.push(obj[keys[i]]);
}
return values;
}
// avoid hasOwnProperty for performance
for (var key in obj)
values.push(obj[key]);
return values;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method shadeColor
* @param {string} color
* @param {number} percent
* @return {string} A hex colour string made by lightening or darkening color by percent
*/
2014-02-19 09:15:05 -05:00
Common.shadeColor = function(color, percent) {
// http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color
var colorInteger = parseInt(color.slice(1),16),
amount = Math.round(2.55 * percent),
R = (colorInteger >> 16) + amount,
B = (colorInteger >> 8 & 0x00FF) + amount,
G = (colorInteger & 0x0000FF) + amount;
return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R :255) * 0x10000
+ (B < 255 ? B < 1 ? 0 : B : 255) * 0x100
+ (G < 255 ? G < 1 ? 0 : G : 255)).toString(16).slice(1);
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method shuffle
* @param {array} array
* @return {array} array shuffled randomly
*/
2014-02-19 09:15:05 -05:00
Common.shuffle = function(array) {
for (var i = array.length - 1; i > 0; i--) {
2014-06-09 14:40:24 -04:00
var j = Math.floor(Common.random() * (i + 1));
2014-02-19 09:15:05 -05:00
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method choose
* @param {array} choices
* @return {object} A random choice object from the array
*/
2014-02-19 09:15:05 -05:00
Common.choose = function(choices) {
2014-06-09 14:40:24 -04:00
return choices[Math.floor(Common.random() * choices.length)];
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method isElement
* @param {object} obj
* @return {boolean} True if the object is a HTMLElement, otherwise false
*/
2014-02-19 09:15:05 -05:00
Common.isElement = function(obj) {
// http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object
try {
return obj instanceof HTMLElement;
}
catch(e){
return (typeof obj==="object") &&
(obj.nodeType===1) && (typeof obj.style === "object") &&
(typeof obj.ownerDocument ==="object");
}
};
2015-05-20 15:38:41 -04:00
/**
* Description
* @method isArray
* @param {object} obj
* @return {boolean} True if the object is an array, otherwise false
*/
Common.isArray = function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
* Description
* @method clamp
* @param {number} value
* @param {number} min
* @param {number} max
* @return {number} The value clamped between min and max inclusive
*/
2014-02-19 09:15:05 -05:00
Common.clamp = function(value, min, max) {
if (value < min)
return min;
if (value > max)
return max;
return value;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method sign
* @param {number} value
* @return {number} -1 if negative, +1 if 0 or positive
*/
2014-02-19 09:15:05 -05:00
Common.sign = function(value) {
return value < 0 ? -1 : 1;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method now
2015-06-29 15:58:24 -04:00
* @return {number} the current timestamp (high-res if available)
2014-02-28 20:10:08 -05:00
*/
Common.now = function() {
2014-02-19 09:15:05 -05:00
// http://stackoverflow.com/questions/221294/how-do-you-get-a-timestamp-in-javascript
// https://gist.github.com/davidwaterston/2982531
2015-05-21 19:33:26 -04:00
var performance = window.performance || {};
performance.now = (function() {
return performance.now ||
performance.webkitNow ||
performance.msNow ||
performance.oNow ||
performance.mozNow ||
function() { return +(new Date()); };
})();
return performance.now();
2014-02-19 09:15:05 -05:00
};
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
* Description
* @method random
* @param {number} min
* @param {number} max
* @return {number} A random number between min and max inclusive
*/
2014-02-19 09:15:05 -05:00
Common.random = function(min, max) {
2014-06-09 14:40:24 -04:00
min = (typeof min !== "undefined") ? min : 0;
max = (typeof max !== "undefined") ? max : 1;
return min + _seededRandom() * (max - min);
2014-02-19 09:15:05 -05:00
};
/**
* Converts a CSS hex colour string into an integer
* @method colorToNumber
* @param {string} colorString
* @return {number} An integer representing the CSS hex string
*/
Common.colorToNumber = function(colorString) {
colorString = colorString.replace('#','');
if (colorString.length == 3) {
colorString = colorString.charAt(0) + colorString.charAt(0)
+ colorString.charAt(1) + colorString.charAt(1)
+ colorString.charAt(2) + colorString.charAt(2);
}
return parseInt(colorString, 16);
};
/**
* A wrapper for console.log, for providing errors and warnings
* @method log
* @param {string} message
* @param {string} type
*/
Common.log = function(message, type) {
2015-01-20 19:15:04 -05:00
if (!console || !console.log || !console.warn)
return;
switch (type) {
case 'warn':
2015-01-20 19:15:04 -05:00
console.warn('Matter.js:', message);
break;
case 'error':
2015-01-20 19:15:04 -05:00
console.log('Matter.js:', message);
break;
}
};
2014-05-01 09:09:06 -04:00
/**
* Returns the next unique sequential ID
* @method nextId
* @return {Number} Unique sequential ID
*/
Common.nextId = function() {
return Common._nextId++;
};
2014-07-29 11:26:49 -04:00
/**
* A cross browser compatible indexOf implementation
* @method indexOf
* @param {array} haystack
* @param {object} needle
*/
Common.indexOf = function(haystack, needle) {
if (haystack.indexOf)
return haystack.indexOf(needle);
for (var i = 0; i < haystack.length; i++) {
if (haystack[i] === needle)
return i;
}
return -1;
};
2014-06-09 14:40:24 -04:00
var _seededRandom = function() {
// https://gist.github.com/ngryman/3830489
Common._seed = (Common._seed * 9301 + 49297) % 233280;
return Common._seed / 233280;
};
2014-02-19 09:15:05 -05:00
})();
2015-06-29 15:58:24 -04:00
2014-02-19 09:15:05 -05:00
; // End src/core/Common.js
// Begin src/core/Engine.js
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* The `Matter.Engine` module contains methods for creating and manipulating engines.
2015-08-12 19:38:20 -04:00
* An engine is a controller that manages updating the simulation of the world.
2014-07-29 11:26:49 -04:00
* See `Matter.Runner` for an optional game loop utility.
2014-06-09 14:40:24 -04:00
*
2014-02-28 20:10:08 -05:00
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class Engine
*/
2014-02-19 09:15:05 -05:00
var Engine = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Creates a new engine. The options parameter is an object that specifies any properties you wish to override the defaults.
* All properties have default values, and many are pre-calculated automatically based on other properties.
2015-06-29 15:58:24 -04:00
* See the properties section below for detailed information on what you can pass via the `options` object.
2014-02-28 20:10:08 -05:00
* @method create
* @param {HTMLElement} element
2014-06-09 14:40:24 -04:00
* @param {object} [options]
2014-02-28 20:10:08 -05:00
* @return {engine} engine
*/
2014-02-19 09:15:05 -05:00
Engine.create = function(element, options) {
// options may be passed as the first (and only) argument
options = Common.isElement(element) ? options : element;
element = Common.isElement(element) ? element : null;
2014-02-19 09:15:05 -05:00
var defaults = {
positionIterations: 6,
velocityIterations: 4,
2014-03-30 14:45:30 -04:00
constraintIterations: 2,
2014-02-19 09:15:05 -05:00
enableSleeping: false,
events: [],
2014-02-19 09:15:05 -05:00
timing: {
timestamp: 0,
2015-08-12 19:38:20 -04:00
timeScale: 1
},
2014-07-29 11:26:49 -04:00
broadphase: {
controller: Grid
2014-02-19 09:15:05 -05:00
}
};
2015-07-05 10:57:31 -04:00
2014-02-19 09:15:05 -05:00
var engine = Common.extend(defaults, options);
2015-07-05 10:57:31 -04:00
if (element || engine.render) {
var renderDefaults = {
2015-06-29 15:58:24 -04:00
element: element,
controller: Render
};
2015-07-05 10:57:31 -04:00
engine.render = Common.extend(renderDefaults, engine.render);
2015-06-29 15:58:24 -04:00
}
if (engine.render && engine.render.controller) {
engine.render = engine.render.controller.create(engine.render);
}
2014-02-19 09:15:05 -05:00
engine.world = World.create(engine.world);
2014-03-24 16:11:42 -04:00
engine.pairs = Pairs.create();
2014-07-29 11:26:49 -04:00
engine.broadphase = engine.broadphase.controller.create(engine.broadphase);
2015-05-20 15:38:41 -04:00
engine.metrics = engine.metrics || { extended: false };
2014-02-19 09:15:05 -05:00
return engine;
};
2014-02-28 20:10:08 -05:00
/**
2015-08-12 19:38:20 -04:00
* Moves the simulation forward in time by `delta` ms.
* The `correction` argument is an optional `Number` that specifies the time correction factor to apply to the update.
* This can help improve the accuracy of the simulation in cases where `delta` is changing between updates.
* The value of `correction` is defined as `delta / lastDelta`, i.e. the percentage change of `delta` over the last step.
* Therefore the value is always `1` (no correction) when `delta` constant (or when no correction is desired, which is the default).
* See the paper on <a href="http://lonesock.net/article/verlet.html">Time Corrected Verlet</a> for more information.
*
2014-07-29 11:26:49 -04:00
* Triggers `beforeUpdate` and `afterUpdate` events.
* Triggers `collisionStart`, `collisionActive` and `collisionEnd` events.
* @method update
2014-02-28 20:10:08 -05:00
* @param {engine} engine
2014-07-29 11:26:49 -04:00
* @param {number} delta
* @param {number} [correction]
2014-02-28 20:10:08 -05:00
*/
2014-07-29 11:26:49 -04:00
Engine.update = function(engine, delta, correction) {
correction = (typeof correction !== 'undefined') ? correction : 1;
2014-02-19 09:15:05 -05:00
2014-07-29 11:26:49 -04:00
var world = engine.world,
timing = engine.timing,
broadphase = engine.broadphase,
broadphasePairs = [],
i;
2014-02-19 09:15:05 -05:00
2014-07-29 11:26:49 -04:00
// increment timestamp
timing.timestamp += delta * timing.timeScale;
2014-02-19 09:15:05 -05:00
2014-07-29 11:26:49 -04:00
// create an event object
var event = {
2015-08-12 19:38:20 -04:00
timestamp: timing.timestamp
2014-07-29 11:26:49 -04:00
};
2014-07-29 11:26:49 -04:00
Events.trigger(engine, 'beforeUpdate', event);
2014-07-29 11:26:49 -04:00
// get lists of all bodies and constraints, no matter what composites they are in
var allBodies = Composite.allBodies(world),
allConstraints = Composite.allConstraints(world);
2014-03-24 16:11:42 -04:00
// if sleeping enabled, call the sleeping controller
if (engine.enableSleeping)
2014-07-29 11:26:49 -04:00
Sleeping.update(allBodies, timing.timeScale);
2014-03-24 16:11:42 -04:00
// applies gravity to all bodies
2015-01-20 19:15:04 -05:00
_bodiesApplyGravity(allBodies, world.gravity);
2014-03-24 16:11:42 -04:00
// update all body position and rotation by integration
2015-01-20 19:15:04 -05:00
_bodiesUpdate(allBodies, delta, timing.timeScale, correction, world.bounds);
2014-02-19 09:15:05 -05:00
// update all constraints
2014-02-19 09:15:05 -05:00
for (i = 0; i < engine.constraintIterations; i++) {
2014-05-01 09:09:06 -04:00
Constraint.solveAll(allConstraints, timing.timeScale);
2014-02-19 09:15:05 -05:00
}
2014-03-30 14:45:30 -04:00
Constraint.postSolveAll(allBodies);
2014-02-19 09:15:05 -05:00
// broadphase pass: find potential collision pairs
2014-02-19 09:15:05 -05:00
if (broadphase.controller) {
2014-03-24 16:11:42 -04:00
// if world is dirty, we must flush the whole grid
if (world.isModified)
2014-07-29 11:26:49 -04:00
broadphase.controller.clear(broadphase);
2014-03-24 16:11:42 -04:00
// update the grid buckets based on current bodies
2014-07-29 11:26:49 -04:00
broadphase.controller.update(broadphase, allBodies, engine, world.isModified);
broadphasePairs = broadphase.pairsList;
2014-02-19 09:15:05 -05:00
} else {
2014-03-24 16:11:42 -04:00
// if no broadphase set, we just pass all bodies
broadphasePairs = allBodies;
2014-02-19 09:15:05 -05:00
}
// narrowphase pass: find actual collisions, then create or update collision pairs
var collisions = broadphase.detector(broadphasePairs, engine);
2014-03-24 16:11:42 -04:00
// update collision pairs
var pairs = engine.pairs,
2014-05-01 09:09:06 -04:00
timestamp = timing.timestamp;
2014-03-24 16:11:42 -04:00
Pairs.update(pairs, collisions, timestamp);
Pairs.removeOld(pairs, timestamp);
2014-02-19 09:15:05 -05:00
// wake up bodies involved in collisions
if (engine.enableSleeping)
2014-07-29 11:26:49 -04:00
Sleeping.afterCollisions(pairs.list, timing.timeScale);
// trigger collision events
if (pairs.collisionStart.length > 0)
Events.trigger(engine, 'collisionStart', { pairs: pairs.collisionStart });
2014-02-19 09:15:05 -05:00
// iteratively resolve position between collisions
2015-05-20 15:38:41 -04:00
Resolver.preSolvePosition(pairs.list);
2014-02-19 09:15:05 -05:00
for (i = 0; i < engine.positionIterations; i++) {
2014-05-01 09:09:06 -04:00
Resolver.solvePosition(pairs.list, timing.timeScale);
2014-02-19 09:15:05 -05:00
}
2014-03-24 16:11:42 -04:00
Resolver.postSolvePosition(allBodies);
2014-02-19 09:15:05 -05:00
2015-05-20 15:38:41 -04:00
// iteratively resolve velocity between collisions
Resolver.preSolveVelocity(pairs.list);
for (i = 0; i < engine.velocityIterations; i++) {
Resolver.solveVelocity(pairs.list, timing.timeScale);
}
2014-07-29 11:26:49 -04:00
// trigger collision events
if (pairs.collisionActive.length > 0)
Events.trigger(engine, 'collisionActive', { pairs: pairs.collisionActive });
if (pairs.collisionEnd.length > 0)
Events.trigger(engine, 'collisionEnd', { pairs: pairs.collisionEnd });
// clear force buffers
2015-01-20 19:15:04 -05:00
_bodiesClearForces(allBodies);
2014-03-24 16:11:42 -04:00
// clear all composite modified flags
if (world.isModified)
Composite.setModified(world, false, false, true);
2014-05-05 14:32:51 -04:00
Events.trigger(engine, 'afterUpdate', event);
2014-02-19 09:15:05 -05:00
return engine;
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Merges two engines by keeping the configuration of `engineA` but replacing the world with the one from `engineB`.
2014-02-28 20:10:08 -05:00
* @method merge
* @param {engine} engineA
* @param {engine} engineB
*/
2014-02-19 09:15:05 -05:00
Engine.merge = function(engineA, engineB) {
Common.extend(engineA, engineB);
if (engineB.world) {
engineA.world = engineB.world;
Engine.clear(engineA);
2014-03-24 16:11:42 -04:00
var bodies = Composite.allBodies(engineA.world);
2014-02-19 09:15:05 -05:00
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
Sleeping.set(body, false);
2014-05-01 09:09:06 -04:00
body.id = Common.nextId();
2014-02-19 09:15:05 -05:00
}
}
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Clears the engine including the world, pairs and broadphase.
2014-02-28 20:10:08 -05:00
* @method clear
* @param {engine} engine
*/
2014-02-19 09:15:05 -05:00
Engine.clear = function(engine) {
var world = engine.world;
2014-03-24 16:11:42 -04:00
Pairs.clear(engine.pairs);
2014-02-19 09:15:05 -05:00
2014-07-29 11:26:49 -04:00
var broadphase = engine.broadphase;
2014-02-19 09:15:05 -05:00
if (broadphase.controller) {
2014-03-24 16:11:42 -04:00
var bodies = Composite.allBodies(world);
2014-07-29 11:26:49 -04:00
broadphase.controller.clear(broadphase);
broadphase.controller.update(broadphase, bodies, engine, true);
2014-02-19 09:15:05 -05:00
}
};
2015-01-20 19:15:04 -05:00
/**
* Zeroes the `body.force` and `body.torque` force buffers.
* @method bodiesClearForces
* @private
* @param {body[]} bodies
*/
var _bodiesClearForces = function(bodies) {
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
// reset force buffers
body.force.x = 0;
body.force.y = 0;
body.torque = 0;
}
};
/**
* Applys a mass dependant force to all given bodies.
* @method bodiesApplyGravity
* @private
* @param {body[]} bodies
* @param {vector} gravity
*/
var _bodiesApplyGravity = function(bodies, gravity) {
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
if (body.isStatic || body.isSleeping)
continue;
// apply gravity
body.force.y += body.mass * gravity.y * 0.001;
body.force.x += body.mass * gravity.x * 0.001;
}
};
/**
* Applys `Body.update` to all given `bodies`.
* @method updateAll
* @private
* @param {body[]} bodies
* @param {number} deltaTime
* The amount of time elapsed between updates
* @param {number} timeScale
* @param {number} correction
* The Verlet correction factor (deltaTime / lastDeltaTime)
* @param {bounds} worldBounds
*/
var _bodiesUpdate = function(bodies, deltaTime, timeScale, correction, worldBounds) {
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
if (body.isStatic || body.isSleeping)
continue;
Body.update(body, deltaTime, timeScale, correction);
}
};
/**
2014-07-29 11:26:49 -04:00
* An alias for `Runner.run`, see `Matter.Runner` for more information.
* @method run
* @param {engine} engine
*/
2014-05-01 09:09:06 -04:00
/**
* Fired just before an update
*
* @event beforeUpdate
* @param {} event An event object
2015-08-12 19:38:20 -04:00
* @param {number} event.timestamp The engine.timing.timestamp of the event
2014-05-01 09:09:06 -04:00
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired after engine update and all collision events
*
* @event afterUpdate
* @param {} event An event object
2015-08-12 19:38:20 -04:00
* @param {number} event.timestamp The engine.timing.timestamp of the event
2014-05-01 09:09:06 -04:00
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired after engine update, provides a list of all pairs that have started to collide in the current tick (if any)
*
* @event collisionStart
* @param {} event An event object
* @param {} event.pairs List of affected pairs
2015-08-12 19:38:20 -04:00
* @param {number} event.timestamp The engine.timing.timestamp of the event
2014-05-01 09:09:06 -04:00
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired after engine update, provides a list of all pairs that are colliding in the current tick (if any)
*
* @event collisionActive
* @param {} event An event object
* @param {} event.pairs List of affected pairs
2015-08-12 19:38:20 -04:00
* @param {number} event.timestamp The engine.timing.timestamp of the event
2014-05-01 09:09:06 -04:00
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired after engine update, provides a list of all pairs that have ended collision in the current tick (if any)
*
* @event collisionEnd
* @param {} event An event object
* @param {} event.pairs List of affected pairs
2015-08-12 19:38:20 -04:00
* @param {number} event.timestamp The engine.timing.timestamp of the event
2014-05-01 09:09:06 -04:00
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
2014-06-09 14:40:24 -04:00
/*
*
* Properties Documentation
*
*/
/**
* An integer `Number` that specifies the number of position iterations to perform each update.
* The higher the value, the higher quality the simulation will be at the expense of performance.
*
* @property positionIterations
* @type number
* @default 6
*/
/**
* An integer `Number` that specifies the number of velocity iterations to perform each update.
* The higher the value, the higher quality the simulation will be at the expense of performance.
*
* @property velocityIterations
* @type number
* @default 4
*/
/**
* An integer `Number` that specifies the number of constraint iterations to perform each update.
* The higher the value, the higher quality the simulation will be at the expense of performance.
* The default value of `2` is usually very adequate.
*
* @property constraintIterations
* @type number
* @default 2
*/
/**
* A flag that specifies whether the engine should allow sleeping via the `Matter.Sleeping` module.
* Sleeping can improve stability and performance, but often at the expense of accuracy.
*
* @property enableSleeping
* @type boolean
* @default false
*/
/**
* An `Object` containing properties regarding the timing systems of the engine.
*
* @property timing
* @type object
*/
/**
* A `Number` that specifies the global scaling factor of time for all bodies.
* A value of `0` freezes the simulation.
* A value of `0.1` gives a slow-motion effect.
* A value of `1.2` gives a speed-up effect.
*
* @property timing.timeScale
* @type number
* @default 1
*/
/**
* A `Number` that specifies the current simulation-time in milliseconds starting from `0`.
2015-08-12 19:38:20 -04:00
* It is incremented on every `Engine.update` by the given `delta` argument.
2014-06-09 14:40:24 -04:00
*
* @property timing.timestamp
* @type number
* @default 0
*/
/**
* An instance of a `Render` controller. The default value is a `Matter.Render` instance created by `Engine.create`.
* One may also develop a custom renderer module based on `Matter.Render` and pass an instance of it to `Engine.create` via `options.render`.
*
* A minimal custom renderer object must define at least three functions: `create`, `clear` and `world` (see `Matter.Render`).
* It is also possible to instead pass the _module_ reference via `options.render.controller` and `Engine.create` will instantiate one for you.
*
* @property render
* @type render
* @default a Matter.Render instance
*/
2014-07-29 11:26:49 -04:00
/**
* An instance of a broadphase controller. The default value is a `Matter.Grid` instance created by `Engine.create`.
*
* @property broadphase
* @type grid
* @default a Matter.Grid instance
*/
2014-06-09 14:40:24 -04:00
/**
* A `World` composite object that will contain all simulated bodies and constraints.
*
* @property world
* @type world
* @default a Matter.World instance
*/
2014-02-19 09:15:05 -05:00
})();
2015-06-29 15:58:24 -04:00
2014-02-19 09:15:05 -05:00
; // End src/core/Engine.js
// Begin src/core/Events.js
/**
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class Events
*/
var Events = {};
(function() {
/**
2014-06-09 14:40:24 -04:00
* Subscribes a callback function to the given object's `eventName`.
* @method on
* @param {} object
* @param {string} eventNames
* @param {function} callback
*/
Events.on = function(object, eventNames, callback) {
var names = eventNames.split(' '),
name;
for (var i = 0; i < names.length; i++) {
name = names[i];
object.events = object.events || {};
object.events[name] = object.events[name] || [];
object.events[name].push(callback);
}
2014-05-01 09:09:06 -04:00
return callback;
};
2014-03-24 16:11:42 -04:00
/**
2014-06-09 14:40:24 -04:00
* Removes the given event callback. If no callback, clears all callbacks in `eventNames`. If no `eventNames`, clears all events.
2014-03-24 16:11:42 -04:00
* @method off
* @param {} object
* @param {string} eventNames
2014-05-01 09:09:06 -04:00
* @param {function} callback
2014-03-24 16:11:42 -04:00
*/
2014-05-01 09:09:06 -04:00
Events.off = function(object, eventNames, callback) {
2014-03-24 16:11:42 -04:00
if (!eventNames) {
object.events = {};
return;
}
2014-05-01 09:09:06 -04:00
// handle Events.off(object, callback)
if (typeof eventNames === 'function') {
callback = eventNames;
eventNames = Common.keys(object.events).join(' ');
}
2014-03-24 16:11:42 -04:00
var names = eventNames.split(' ');
2014-05-01 09:09:06 -04:00
2014-03-24 16:11:42 -04:00
for (var i = 0; i < names.length; i++) {
2014-05-01 09:09:06 -04:00
var callbacks = object.events[names[i]],
newCallbacks = [];
if (callback) {
for (var j = 0; j < callbacks.length; j++) {
if (callbacks[j] !== callback)
newCallbacks.push(callbacks[j]);
}
}
object.events[names[i]] = newCallbacks;
2014-03-24 16:11:42 -04:00
}
};
/**
2014-06-09 14:40:24 -04:00
* Fires all the callbacks subscribed to the given object's `eventName`, in the order they subscribed, if any.
2014-03-24 16:11:42 -04:00
* @method trigger
* @param {} object
* @param {string} eventNames
* @param {} event
*/
Events.trigger = function(object, eventNames, event) {
var names,
name,
callbacks,
eventClone;
if (object.events) {
if (!event)
event = {};
names = eventNames.split(' ');
for (var i = 0; i < names.length; i++) {
name = names[i];
2014-03-30 14:45:30 -04:00
callbacks = object.events[name];
2014-03-30 14:45:30 -04:00
if (callbacks) {
eventClone = Common.clone(event, false);
eventClone.name = name;
eventClone.source = object;
for (var j = 0; j < callbacks.length; j++) {
callbacks[j].apply(object, [eventClone]);
}
}
}
}
};
})();
; // End src/core/Events.js
2014-02-19 09:15:05 -05:00
// Begin src/core/Metrics.js
; // End src/core/Metrics.js
// Begin src/core/Mouse.js
2014-02-28 20:10:08 -05:00
/**
* _Internal Class_, not generally used outside of the engine's internals.
*
* @class Mouse
*/
2014-07-29 11:26:49 -04:00
var Mouse = {};
2014-02-19 09:15:05 -05:00
(function() {
2014-07-29 11:26:49 -04:00
2014-02-28 20:10:08 -05:00
/**
* Description
2014-07-29 11:26:49 -04:00
* @method create
2014-02-28 20:10:08 -05:00
* @param {HTMLElement} element
2014-07-29 11:26:49 -04:00
* @return {mouse} A new mouse
2014-02-28 20:10:08 -05:00
*/
2014-07-29 11:26:49 -04:00
Mouse.create = function(element) {
var mouse = {};
2015-01-20 19:15:04 -05:00
if (!element) {
Common.log('Mouse.create: element was undefined, defaulting to document.body', 'warn');
}
2014-02-19 09:15:05 -05:00
2014-07-29 11:26:49 -04:00
mouse.element = element || document.body;
mouse.absolute = { x: 0, y: 0 };
mouse.position = { x: 0, y: 0 };
mouse.mousedownPosition = { x: 0, y: 0 };
mouse.mouseupPosition = { x: 0, y: 0 };
mouse.offset = { x: 0, y: 0 };
mouse.scale = { x: 1, y: 1 };
mouse.wheelDelta = 0;
mouse.button = -1;
2015-01-20 19:15:04 -05:00
mouse.pixelRatio = mouse.element.getAttribute('data-pixel-ratio') || 1;
2014-07-29 11:26:49 -04:00
mouse.sourceEvents = {
mousemove: null,
mousedown: null,
2014-05-05 14:32:51 -04:00
mouseup: null,
mousewheel: null
};
2014-02-19 09:15:05 -05:00
2014-07-29 11:26:49 -04:00
mouse.mousemove = function(event) {
2014-12-28 13:37:43 -05:00
var position = _getRelativeMousePosition(event, mouse.element, mouse.pixelRatio),
2014-02-19 09:15:05 -05:00
touches = event.changedTouches;
if (touches) {
mouse.button = 0;
event.preventDefault();
}
2014-05-05 14:32:51 -04:00
mouse.absolute.x = position.x;
mouse.absolute.y = position.y;
mouse.position.x = mouse.absolute.x * mouse.scale.x + mouse.offset.x;
mouse.position.y = mouse.absolute.y * mouse.scale.y + mouse.offset.y;
mouse.sourceEvents.mousemove = event;
2014-02-19 09:15:05 -05:00
};
2014-07-29 11:26:49 -04:00
mouse.mousedown = function(event) {
2014-12-28 13:37:43 -05:00
var position = _getRelativeMousePosition(event, mouse.element, mouse.pixelRatio),
2014-02-19 09:15:05 -05:00
touches = event.changedTouches;
if (touches) {
mouse.button = 0;
event.preventDefault();
} else {
mouse.button = event.button;
}
2014-05-05 14:32:51 -04:00
mouse.absolute.x = position.x;
mouse.absolute.y = position.y;
mouse.position.x = mouse.absolute.x * mouse.scale.x + mouse.offset.x;
mouse.position.y = mouse.absolute.y * mouse.scale.y + mouse.offset.y;
mouse.mousedownPosition.x = mouse.position.x;
mouse.mousedownPosition.y = mouse.position.y;
mouse.sourceEvents.mousedown = event;
2014-02-19 09:15:05 -05:00
};
2014-07-29 11:26:49 -04:00
mouse.mouseup = function(event) {
2014-12-28 13:37:43 -05:00
var position = _getRelativeMousePosition(event, mouse.element, mouse.pixelRatio),
2014-02-19 09:15:05 -05:00
touches = event.changedTouches;
if (touches) {
event.preventDefault();
}
mouse.button = -1;
2014-05-05 14:32:51 -04:00
mouse.absolute.x = position.x;
mouse.absolute.y = position.y;
mouse.position.x = mouse.absolute.x * mouse.scale.x + mouse.offset.x;
mouse.position.y = mouse.absolute.y * mouse.scale.y + mouse.offset.y;
mouse.mouseupPosition.x = mouse.position.x;
mouse.mouseupPosition.y = mouse.position.y;
mouse.sourceEvents.mouseup = event;
2014-02-19 09:15:05 -05:00
};
2014-03-30 14:45:30 -04:00
2014-07-29 11:26:49 -04:00
mouse.mousewheel = function(event) {
2014-05-05 14:32:51 -04:00
mouse.wheelDelta = Math.max(-1, Math.min(1, event.wheelDelta || -event.detail));
event.preventDefault();
};
2014-03-30 14:45:30 -04:00
Mouse.setElement(mouse, mouse.element);
2014-02-19 09:15:05 -05:00
2014-07-29 11:26:49 -04:00
return mouse;
2014-02-19 09:15:05 -05:00
};
2014-03-30 14:45:30 -04:00
/**
* Sets the element the mouse is bound to (and relative to)
* @method setElement
* @param {mouse} mouse
* @param {HTMLElement} element
*/
Mouse.setElement = function(mouse, element) {
mouse.element = element;
element.addEventListener('mousemove', mouse.mousemove);
element.addEventListener('mousedown', mouse.mousedown);
element.addEventListener('mouseup', mouse.mouseup);
2014-05-05 14:32:51 -04:00
element.addEventListener("mousewheel", mouse.mousewheel);
element.addEventListener("DOMMouseScroll", mouse.mousewheel);
2014-03-30 14:45:30 -04:00
element.addEventListener('touchmove', mouse.mousemove);
element.addEventListener('touchstart', mouse.mousedown);
element.addEventListener('touchend', mouse.mouseup);
};
/**
* Clears all captured source events
2014-04-01 08:47:17 -04:00
* @method clearSourceEvents
* @param {mouse} mouse
*/
Mouse.clearSourceEvents = function(mouse) {
mouse.sourceEvents.mousemove = null;
mouse.sourceEvents.mousedown = null;
mouse.sourceEvents.mouseup = null;
2014-05-05 14:32:51 -04:00
mouse.sourceEvents.mousewheel = null;
mouse.wheelDelta = 0;
};
/**
* Sets the offset
* @method setOffset
* @param {mouse} mouse
2015-06-29 15:58:24 -04:00
* @param {vector} offset
2014-05-05 14:32:51 -04:00
*/
Mouse.setOffset = function(mouse, offset) {
mouse.offset.x = offset.x;
mouse.offset.y = offset.y;
mouse.position.x = mouse.absolute.x * mouse.scale.x + mouse.offset.x;
mouse.position.y = mouse.absolute.y * mouse.scale.y + mouse.offset.y;
};
/**
* Sets the scale
* @method setScale
* @param {mouse} mouse
2015-06-29 15:58:24 -04:00
* @param {vector} scale
2014-05-05 14:32:51 -04:00
*/
Mouse.setScale = function(mouse, scale) {
mouse.scale.x = scale.x;
mouse.scale.y = scale.y;
mouse.position.x = mouse.absolute.x * mouse.scale.x + mouse.offset.x;
mouse.position.y = mouse.absolute.y * mouse.scale.y + mouse.offset.y;
};
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _getRelativeMousePosition
* @private
* @param {} event
* @param {} element
2014-12-28 13:37:43 -05:00
* @param {number} pixelRatio
* @return {}
2014-02-28 20:10:08 -05:00
*/
2014-12-28 13:37:43 -05:00
var _getRelativeMousePosition = function(event, element, pixelRatio) {
2014-02-19 09:15:05 -05:00
var elementBounds = element.getBoundingClientRect(),
2014-05-05 14:32:51 -04:00
rootNode = (document.documentElement || document.body.parentNode || document.body),
scrollX = (window.pageXOffset !== undefined) ? window.pageXOffset : rootNode.scrollLeft,
scrollY = (window.pageYOffset !== undefined) ? window.pageYOffset : rootNode.scrollTop,
2014-02-19 09:15:05 -05:00
touches = event.changedTouches,
x, y;
if (touches) {
x = touches[0].pageX - elementBounds.left - scrollX;
y = touches[0].pageY - elementBounds.top - scrollY;
} else {
x = event.pageX - elementBounds.left - scrollX;
y = event.pageY - elementBounds.top - scrollY;
}
2014-12-28 13:37:43 -05:00
2014-02-19 09:15:05 -05:00
return {
2014-12-28 13:37:43 -05:00
x: x / (element.clientWidth / element.width * pixelRatio),
y: y / (element.clientHeight / element.height * pixelRatio)
2014-02-19 09:15:05 -05:00
};
};
})();
2014-12-28 13:37:43 -05:00
2014-02-19 09:15:05 -05:00
; // End src/core/Mouse.js
2014-07-29 11:26:49 -04:00
// Begin src/core/Runner.js
/**
* The `Matter.Runner` module is an optional utility which provides a game loop,
* that handles updating and rendering a `Matter.Engine` for you within a browser.
2015-08-12 19:38:20 -04:00
* It is intended for demo and testing purposes, but may be adequate for simple games.
* If you are using your own game loop instead, then you do not need the `Matter.Runner` module.
* Instead just call `Engine.update(engine, delta)` in your own loop.
2014-07-29 11:26:49 -04:00
* Note that the method `Engine.run` is an alias for `Runner.run`.
*
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class Runner
*/
var Runner = {};
(function() {
2015-06-29 15:58:24 -04:00
if (typeof window === 'undefined') {
// TODO: support Runner on non-browser environments.
return;
}
2014-07-29 11:26:49 -04:00
var _requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame || window.msRequestAnimationFrame
2015-08-12 19:38:20 -04:00
|| function(callback){ window.setTimeout(function() { callback(Common.now()); }, 1000 / 60); };
2014-07-29 11:26:49 -04:00
var _cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame
|| window.webkitCancelAnimationFrame || window.msCancelAnimationFrame;
/**
2015-08-12 19:38:20 -04:00
* Creates a new Runner. The options parameter is an object that specifies any properties you wish to override the defaults.
* @method create
* @param {} options
*/
Runner.create = function(options) {
var defaults = {
fps: 60,
correction: 1,
deltaSampleSize: 60,
counterTimestamp: 0,
frameCounter: 0,
deltaHistory: [],
timePrev: null,
timeScalePrev: 1,
frameRequestId: null,
isFixed: false,
enabled: true
};
var runner = Common.extend(defaults, options);
runner.delta = runner.delta || 1000 / runner.fps;
runner.deltaMin = runner.deltaMin || 1000 / runner.fps;
runner.deltaMax = runner.deltaMax || 1000 / (runner.fps * 0.5);
runner.fps = 1000 / runner.delta;
return runner;
};
/**
* Continuously ticks a `Matter.Engine` by calling `Runner.tick` on the `requestAnimationFrame` event.
2014-07-29 11:26:49 -04:00
* @method run
* @param {engine} engine
*/
2015-08-12 19:38:20 -04:00
Runner.run = function(runner, engine) {
// create runner if engine is first argument
if (typeof runner.positionIterations !== 'undefined') {
engine = runner;
runner = Runner.create();
}
2014-07-29 11:26:49 -04:00
(function render(time){
2015-08-12 19:38:20 -04:00
runner.frameRequestId = _requestAnimationFrame(render);
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
if (time && runner.enabled) {
Runner.tick(runner, engine, time);
}
})();
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
return runner;
};
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
/**
* A game loop utility that updates the engine and renderer by one step (a 'tick').
* Features delta smoothing, time correction and fixed or dynamic timing.
* Triggers `beforeTick`, `tick` and `afterTick` events on the engine.
* Consider just `Engine.update(engine, delta)` if you're using your own loop.
* @method tick
* @param {runner} runner
* @param {engine} engine
* @param {number} time
*/
Runner.tick = function(runner, engine, time) {
var timing = engine.timing,
correction = 1,
delta;
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
// create an event object
var event = {
timestamp: timing.timestamp
};
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
Events.trigger(runner, 'beforeTick', event);
Events.trigger(engine, 'beforeTick', event); // @deprecated
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
if (runner.isFixed) {
// fixed timestep
delta = runner.delta;
} else {
// dynamic timestep based on wall clock between calls
delta = (time - runner.timePrev) || runner.delta;
runner.timePrev = time;
// optimistically filter delta over a few frames, to improve stability
runner.deltaHistory.push(delta);
runner.deltaHistory = runner.deltaHistory.slice(-runner.deltaSampleSize);
delta = Math.min.apply(null, runner.deltaHistory);
// limit delta
delta = delta < runner.deltaMin ? runner.deltaMin : delta;
delta = delta > runner.deltaMax ? runner.deltaMax : delta;
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
// correction for delta
correction = delta / runner.delta;
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
// update engine timing object
runner.delta = delta;
}
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
// time correction for time scaling
if (runner.timeScalePrev !== 0)
correction *= timing.timeScale / runner.timeScalePrev;
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
if (timing.timeScale === 0)
correction = 0;
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
runner.timeScalePrev = timing.timeScale;
runner.correction = correction;
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
// fps counter
runner.frameCounter += 1;
if (time - runner.counterTimestamp >= 1000) {
runner.fps = runner.frameCounter * ((time - runner.counterTimestamp) / 1000);
runner.counterTimestamp = time;
runner.frameCounter = 0;
}
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
Events.trigger(runner, 'tick', event);
Events.trigger(engine, 'tick', event); // @deprecated
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
// if world has been modified, clear the render scene graph
if (engine.world.isModified
&& engine.render
&& engine.render.controller
&& engine.render.controller.clear) {
engine.render.controller.clear(engine.render);
}
2014-07-29 11:26:49 -04:00
2015-08-12 19:38:20 -04:00
// update
Events.trigger(runner, 'beforeUpdate', event);
Engine.update(engine, delta, correction);
Events.trigger(runner, 'afterUpdate', event);
// render
if (engine.render) {
Events.trigger(runner, 'beforeRender', event);
Events.trigger(engine, 'beforeRender', event); // @deprecated
engine.render.controller.world(engine);
Events.trigger(runner, 'afterRender', event);
Events.trigger(engine, 'afterRender', event); // @deprecated
}
Events.trigger(runner, 'afterTick', event);
Events.trigger(engine, 'afterTick', event); // @deprecated
2014-07-29 11:26:49 -04:00
};
/**
2015-08-12 19:38:20 -04:00
* Ends execution of `Runner.run` on the given `runner`, by canceling the animation frame request event loop.
2014-07-29 11:26:49 -04:00
* If you wish to only temporarily pause the engine, see `engine.enabled` instead.
* @method stop
2015-08-12 19:38:20 -04:00
* @param {runner} runner
2014-07-29 11:26:49 -04:00
*/
2015-08-12 19:38:20 -04:00
Runner.stop = function(runner) {
_cancelAnimationFrame(runner.frameRequestId);
2014-07-29 11:26:49 -04:00
};
2015-08-12 19:38:20 -04:00
/*
*
* Events Documentation
*
*/
/**
* Fired at the start of a tick, before any updates to the engine or timing
*
* @event beforeTick
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired after engine timing updated, but just before update
*
* @event tick
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired at the end of a tick, after engine update and after rendering
*
* @event afterTick
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired before update
*
* @event beforeUpdate
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired after update
*
* @event afterUpdate
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired before rendering
*
* @event beforeRender
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired after rendering
*
* @event afterRender
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/*
*
* Properties Documentation
*
*/
/**
* A flag that specifies whether the runner is running or not.
*
* @property enabled
* @type boolean
* @default true
*/
/**
* A `Boolean` that specifies if the runner should use a fixed timestep (otherwise it is variable).
* If timing is fixed, then the apparent simulation speed will change depending on the frame rate (but behaviour will be deterministic).
* If the timing is variable, then the apparent simulation speed will be constant (approximately, but at the cost of determininism).
*
* @property isFixed
* @type boolean
* @default false
*/
/**
* A `Number` that specifies the time step between updates in milliseconds.
* If `engine.timing.isFixed` is set to `true`, then `delta` is fixed.
* If it is `false`, then `delta` can dynamically change to maintain the correct apparent simulation speed.
*
* @property delta
* @type number
* @default 1000 / 60
*/
2014-07-29 11:26:49 -04:00
})();
2015-01-01 18:10:10 -05:00
2014-07-29 11:26:49 -04:00
; // End src/core/Runner.js
2014-02-19 09:15:05 -05:00
// Begin src/core/Sleeping.js
2014-02-28 20:10:08 -05:00
/**
* _Internal Class_, not generally used outside of the engine's internals.
*
* @class Sleeping
*/
2014-02-19 09:15:05 -05:00
var Sleeping = {};
(function() {
2014-07-29 11:26:49 -04:00
Sleeping._motionWakeThreshold = 0.18;
Sleeping._motionSleepThreshold = 0.08;
Sleeping._minBias = 0.9;
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
2014-07-29 11:26:49 -04:00
* Puts bodies to sleep or wakes them up depending on their motion.
2014-02-28 20:10:08 -05:00
* @method update
* @param {body[]} bodies
2014-07-29 11:26:49 -04:00
* @param {number} timeScale
2014-02-28 20:10:08 -05:00
*/
2014-07-29 11:26:49 -04:00
Sleeping.update = function(bodies, timeScale) {
var timeFactor = timeScale * timeScale * timeScale;
2014-02-19 09:15:05 -05:00
// update bodies sleeping status
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i],
motion = body.speed * body.speed + body.angularSpeed * body.angularSpeed;
// wake up bodies if they have a force applied
2015-05-21 19:33:26 -04:00
if (body.force.x !== 0 || body.force.y !== 0) {
Sleeping.set(body, false);
continue;
}
2014-02-19 09:15:05 -05:00
var minMotion = Math.min(body.motion, motion),
maxMotion = Math.max(body.motion, motion);
// biased average motion estimation between frames
2014-07-29 11:26:49 -04:00
body.motion = Sleeping._minBias * minMotion + (1 - Sleeping._minBias) * maxMotion;
2014-02-19 09:15:05 -05:00
2014-07-29 11:26:49 -04:00
if (body.sleepThreshold > 0 && body.motion < Sleeping._motionSleepThreshold * timeFactor) {
2014-02-19 09:15:05 -05:00
body.sleepCounter += 1;
if (body.sleepCounter >= body.sleepThreshold)
Sleeping.set(body, true);
} else if (body.sleepCounter > 0) {
body.sleepCounter -= 1;
}
}
};
2014-02-28 20:10:08 -05:00
/**
2014-07-29 11:26:49 -04:00
* Given a set of colliding pairs, wakes the sleeping bodies involved.
2014-02-28 20:10:08 -05:00
* @method afterCollisions
* @param {pair[]} pairs
2014-07-29 11:26:49 -04:00
* @param {number} timeScale
2014-02-28 20:10:08 -05:00
*/
2014-07-29 11:26:49 -04:00
Sleeping.afterCollisions = function(pairs, timeScale) {
var timeFactor = timeScale * timeScale * timeScale;
2014-02-19 09:15:05 -05:00
// wake up bodies involved in collisions
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i];
// don't wake inactive pairs
if (!pair.isActive)
continue;
var collision = pair.collision,
2015-05-20 15:38:41 -04:00
bodyA = collision.bodyA.parent,
bodyB = collision.bodyB.parent;
2014-02-19 09:15:05 -05:00
// don't wake if at least one body is static
2014-02-19 09:15:05 -05:00
if ((bodyA.isSleeping && bodyB.isSleeping) || bodyA.isStatic || bodyB.isStatic)
continue;
if (bodyA.isSleeping || bodyB.isSleeping) {
var sleepingBody = (bodyA.isSleeping && !bodyA.isStatic) ? bodyA : bodyB,
movingBody = sleepingBody === bodyA ? bodyB : bodyA;
2014-07-29 11:26:49 -04:00
if (!sleepingBody.isStatic && movingBody.motion > Sleeping._motionWakeThreshold * timeFactor) {
2014-02-19 09:15:05 -05:00
Sleeping.set(sleepingBody, false);
}
}
}
};
2015-05-21 19:33:26 -04:00
2014-02-28 20:10:08 -05:00
/**
* Description
* @method set
* @param {body} body
* @param {boolean} isSleeping
*/
2014-02-19 09:15:05 -05:00
Sleeping.set = function(body, isSleeping) {
2015-06-29 15:58:24 -04:00
var wasSleeping = body.isSleeping;
2014-02-19 09:15:05 -05:00
if (isSleeping) {
body.isSleeping = true;
body.sleepCounter = body.sleepThreshold;
body.positionImpulse.x = 0;
body.positionImpulse.y = 0;
body.positionPrev.x = body.position.x;
body.positionPrev.y = body.position.y;
body.anglePrev = body.angle;
body.speed = 0;
body.angularSpeed = 0;
body.motion = 0;
2015-06-29 15:58:24 -04:00
if (!wasSleeping) {
Events.trigger(body, 'sleepStart');
}
2014-02-19 09:15:05 -05:00
} else {
body.isSleeping = false;
body.sleepCounter = 0;
2015-06-29 15:58:24 -04:00
if (wasSleeping) {
Events.trigger(body, 'sleepEnd');
}
2014-02-19 09:15:05 -05:00
}
};
})();
; // End src/core/Sleeping.js
// Begin src/factory/Bodies.js
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* The `Matter.Bodies` module contains factory methods for creating rigid body models
* with commonly used body configurations (such as rectangles, circles and other polygons).
*
2014-02-28 20:10:08 -05:00
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class Bodies
*/
2014-02-19 09:15:05 -05:00
// TODO: true circle bodies
var Bodies = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Creates a new rigid body model with a rectangle hull.
* The options parameter is an object that specifies any properties you wish to override the defaults.
2015-01-01 18:10:10 -05:00
* See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
2014-02-28 20:10:08 -05:00
* @method rectangle
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
2014-06-09 14:40:24 -04:00
* @param {object} [options]
2014-02-28 20:10:08 -05:00
* @return {body} A new rectangle body
*/
2014-02-19 09:15:05 -05:00
Bodies.rectangle = function(x, y, width, height, options) {
options = options || {};
var rectangle = {
2014-05-01 09:09:06 -04:00
label: 'Rectangle Body',
position: { x: x, y: y },
2014-05-05 14:32:51 -04:00
vertices: Vertices.fromPath('L 0 0 L ' + width + ' 0 L ' + width + ' ' + height + ' L 0 ' + height)
};
2014-02-19 09:15:05 -05:00
2014-05-05 14:32:51 -04:00
if (options.chamfer) {
var chamfer = options.chamfer;
rectangle.vertices = Vertices.chamfer(rectangle.vertices, chamfer.radius,
chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
delete options.chamfer;
}
2014-02-19 09:15:05 -05:00
return Body.create(Common.extend({}, rectangle, options));
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Creates a new rigid body model with a trapezoid hull.
* The options parameter is an object that specifies any properties you wish to override the defaults.
2015-01-01 18:10:10 -05:00
* See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
2014-02-28 20:10:08 -05:00
* @method trapezoid
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
* @param {number} slope
2014-06-09 14:40:24 -04:00
* @param {object} [options]
2014-02-28 20:10:08 -05:00
* @return {body} A new trapezoid body
*/
2014-02-19 09:15:05 -05:00
Bodies.trapezoid = function(x, y, width, height, slope, options) {
options = options || {};
slope *= 0.5;
var roof = (1 - (slope * 2)) * width;
var x1 = width * slope,
x2 = x1 + roof,
x3 = x2 + x1;
var trapezoid = {
2014-05-01 09:09:06 -04:00
label: 'Trapezoid Body',
position: { x: x, y: y },
2014-05-05 14:32:51 -04:00
vertices: Vertices.fromPath('L 0 0 L ' + x1 + ' ' + (-height) + ' L ' + x2 + ' ' + (-height) + ' L ' + x3 + ' 0')
};
2014-02-19 09:15:05 -05:00
2014-05-05 14:32:51 -04:00
if (options.chamfer) {
var chamfer = options.chamfer;
trapezoid.vertices = Vertices.chamfer(trapezoid.vertices, chamfer.radius,
chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
delete options.chamfer;
}
2014-02-19 09:15:05 -05:00
return Body.create(Common.extend({}, trapezoid, options));
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Creates a new rigid body model with a circle hull.
* The options parameter is an object that specifies any properties you wish to override the defaults.
2015-01-01 18:10:10 -05:00
* See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
2014-02-28 20:10:08 -05:00
* @method circle
* @param {number} x
* @param {number} y
* @param {number} radius
2014-06-09 14:40:24 -04:00
* @param {object} [options]
2015-05-20 15:38:41 -04:00
* @param {number} [maxSides]
2014-02-28 20:10:08 -05:00
* @return {body} A new circle body
*/
2014-02-19 09:15:05 -05:00
Bodies.circle = function(x, y, radius, options, maxSides) {
options = options || {};
2014-05-01 09:09:06 -04:00
options.label = 'Circle Body';
2014-02-19 09:15:05 -05:00
// approximate circles with polygons until true circles implemented in SAT
maxSides = maxSides || 25;
var sides = Math.ceil(Math.max(10, Math.min(maxSides, radius)));
// optimisation: always use even number of sides (half the number of unique axes)
if (sides % 2 === 1)
sides += 1;
// flag for better rendering
options.circleRadius = radius;
return Bodies.polygon(x, y, sides, radius, options);
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Creates a new rigid body model with a regular polygon hull with the given number of sides.
* The options parameter is an object that specifies any properties you wish to override the defaults.
2015-01-01 18:10:10 -05:00
* See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
2014-02-28 20:10:08 -05:00
* @method polygon
* @param {number} x
* @param {number} y
* @param {number} sides
* @param {number} radius
2014-06-09 14:40:24 -04:00
* @param {object} [options]
2014-02-28 20:10:08 -05:00
* @return {body} A new regular polygon body
*/
2014-02-19 09:15:05 -05:00
Bodies.polygon = function(x, y, sides, radius, options) {
options = options || {};
if (sides < 3)
return Bodies.circle(x, y, radius, options);
var theta = 2 * Math.PI / sides,
path = '',
offset = theta * 0.5;
for (var i = 0; i < sides; i += 1) {
var angle = offset + (i * theta),
xx = Math.cos(angle) * radius,
yy = Math.sin(angle) * radius;
path += 'L ' + xx.toFixed(3) + ' ' + yy.toFixed(3) + ' ';
}
var polygon = {
2014-05-01 09:09:06 -04:00
label: 'Polygon Body',
position: { x: x, y: y },
2014-05-05 14:32:51 -04:00
vertices: Vertices.fromPath(path)
};
2014-02-19 09:15:05 -05:00
2014-05-05 14:32:51 -04:00
if (options.chamfer) {
var chamfer = options.chamfer;
polygon.vertices = Vertices.chamfer(polygon.vertices, chamfer.radius,
chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
delete options.chamfer;
}
2014-02-19 09:15:05 -05:00
return Body.create(Common.extend({}, polygon, options));
};
2015-05-20 15:38:41 -04:00
/**
* Creates a body using the supplied vertices (or an array containing multiple sets of vertices).
* If the vertices are convex, they will pass through as supplied.
* Otherwise if the vertices are concave, they will be decomposed if [poly-decomp.js](https://github.com/schteppe/poly-decomp.js) is available.
* Note that this process is not guaranteed to support complex sets of vertices (e.g. those with holes may fail).
* By default the decomposition will discard collinear edges (to improve performance).
* It can also optionally discard any parts that have an area less than `minimumArea`.
* If the vertices can not be decomposed, the result will fall back to using the convex hull.
* The options parameter is an object that specifies any `Matter.Body` properties you wish to override the defaults.
* See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
* @method fromVertices
* @param {number} x
* @param {number} y
* @param [[vector]] vertexSets
* @param {object} [options]
* @param {bool} [flagInternal=false]
* @param {number} [removeCollinear=0.01]
* @param {number} [minimumArea=10]
* @return {body}
*/
Bodies.fromVertices = function(x, y, vertexSets, options, flagInternal, removeCollinear, minimumArea) {
var body,
parts,
isConvex,
vertices,
i,
j,
k,
v,
z;
options = options || {};
parts = [];
flagInternal = typeof flagInternal !== 'undefined' ? flagInternal : false;
removeCollinear = typeof removeCollinear !== 'undefined' ? removeCollinear : 0.01;
minimumArea = typeof minimumArea !== 'undefined' ? minimumArea : 10;
if (!window.decomp) {
Common.log('Bodies.fromVertices: poly-decomp.js required. Could not decompose vertices. Fallback to convex hull.', 'warn');
}
// ensure vertexSets is an array of arrays
if (!Common.isArray(vertexSets[0])) {
vertexSets = [vertexSets];
}
for (v = 0; v < vertexSets.length; v += 1) {
vertices = vertexSets[v];
isConvex = Vertices.isConvex(vertices);
if (isConvex || !window.decomp) {
if (isConvex) {
vertices = Vertices.clockwiseSort(vertices);
} else {
// fallback to convex hull when decomposition is not possible
vertices = Vertices.hull(vertices);
}
parts.push({
position: { x: x, y: y },
vertices: vertices
});
} else {
// initialise a decomposition
var concave = new decomp.Polygon();
for (i = 0; i < vertices.length; i++) {
concave.vertices.push([vertices[i].x, vertices[i].y]);
}
// vertices are concave and simple, we can decompose into parts
concave.makeCCW();
if (removeCollinear !== false)
concave.removeCollinearPoints(removeCollinear);
// use the quick decomposition algorithm (Bayazit)
var decomposed = concave.quickDecomp();
// for each decomposed chunk
for (i = 0; i < decomposed.length; i++) {
var chunk = decomposed[i],
chunkVertices = [];
// convert vertices into the correct structure
for (j = 0; j < chunk.vertices.length; j++) {
chunkVertices.push({ x: chunk.vertices[j][0], y: chunk.vertices[j][1] });
}
// skip small chunks
if (minimumArea > 0 && Vertices.area(chunkVertices) < minimumArea)
continue;
// create a compound part
parts.push({
position: Vertices.centre(chunkVertices),
vertices: chunkVertices
});
}
}
}
// create body parts
for (i = 0; i < parts.length; i++) {
parts[i] = Body.create(Common.extend(parts[i], options));
}
// flag internal edges (coincident part edges)
if (flagInternal) {
var coincident_max_dist = 5;
for (i = 0; i < parts.length; i++) {
var partA = parts[i];
for (j = i + 1; j < parts.length; j++) {
var partB = parts[j];
if (Bounds.overlaps(partA.bounds, partB.bounds)) {
var pav = partA.vertices,
pbv = partB.vertices;
// iterate vertices of both parts
for (k = 0; k < partA.vertices.length; k++) {
for (z = 0; z < partB.vertices.length; z++) {
// find distances between the vertices
var da = Vector.magnitudeSquared(Vector.sub(pav[(k + 1) % pav.length], pbv[z])),
db = Vector.magnitudeSquared(Vector.sub(pav[k], pbv[(z + 1) % pbv.length]));
// if both vertices are very close, consider the edge concident (internal)
if (da < coincident_max_dist && db < coincident_max_dist) {
pav[k].isInternal = true;
pbv[z].isInternal = true;
}
}
}
}
}
}
}
if (parts.length > 1) {
// create the parent body to be returned, that contains generated compound parts
body = Body.create(Common.extend({ parts: parts.slice(0) }, options));
Body.setPosition(body, { x: x, y: y });
return body;
} else {
return parts[0];
}
};
})();
; // End src/factory/Bodies.js
2014-02-19 09:15:05 -05:00
// Begin src/factory/Composites.js
2014-02-28 20:10:08 -05:00
/**
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class Composites
*/
2014-02-19 09:15:05 -05:00
var Composites = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
* Description
* @method stack
* @param {number} xx
* @param {number} yy
* @param {number} columns
* @param {number} rows
* @param {number} columnGap
* @param {number} rowGap
* @param {function} callback
* @return {composite} A new composite containing objects created in the callback
*/
2014-02-19 09:15:05 -05:00
Composites.stack = function(xx, yy, columns, rows, columnGap, rowGap, callback) {
2014-05-01 09:09:06 -04:00
var stack = Composite.create({ label: 'Stack' }),
2014-02-19 09:15:05 -05:00
x = xx,
y = yy,
lastBody,
i = 0;
for (var row = 0; row < rows; row++) {
var maxHeight = 0;
for (var column = 0; column < columns; column++) {
var body = callback(x, y, column, row, lastBody, i);
if (body) {
var bodyHeight = body.bounds.max.y - body.bounds.min.y,
bodyWidth = body.bounds.max.x - body.bounds.min.x;
if (bodyHeight > maxHeight)
maxHeight = bodyHeight;
Body.translate(body, { x: bodyWidth * 0.5, y: bodyHeight * 0.5 });
x = body.bounds.max.x + columnGap;
Composite.addBody(stack, body);
lastBody = body;
i += 1;
2015-05-20 15:38:41 -04:00
} else {
x += columnGap;
2014-02-19 09:15:05 -05:00
}
}
y += maxHeight + rowGap;
x = xx;
}
return stack;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method chain
* @param {composite} composite
* @param {number} xOffsetA
* @param {number} yOffsetA
* @param {number} xOffsetB
* @param {number} yOffsetB
* @param {object} options
* @return {composite} A new composite containing objects chained together with constraints
*/
2014-02-19 09:15:05 -05:00
Composites.chain = function(composite, xOffsetA, yOffsetA, xOffsetB, yOffsetB, options) {
var bodies = composite.bodies;
for (var i = 1; i < bodies.length; i++) {
var bodyA = bodies[i - 1],
bodyB = bodies[i],
bodyAHeight = bodyA.bounds.max.y - bodyA.bounds.min.y,
bodyAWidth = bodyA.bounds.max.x - bodyA.bounds.min.x,
bodyBHeight = bodyB.bounds.max.y - bodyB.bounds.min.y,
bodyBWidth = bodyB.bounds.max.x - bodyB.bounds.min.x;
var defaults = {
bodyA: bodyA,
pointA: { x: bodyAWidth * xOffsetA, y: bodyAHeight * yOffsetA },
bodyB: bodyB,
pointB: { x: bodyBWidth * xOffsetB, y: bodyBHeight * yOffsetB }
};
var constraint = Common.extend(defaults, options);
Composite.addConstraint(composite, Constraint.create(constraint));
}
2014-05-01 09:09:06 -04:00
composite.label += ' Chain';
2014-02-19 09:15:05 -05:00
return composite;
};
2014-03-30 14:45:30 -04:00
/**
* Connects bodies in the composite with constraints in a grid pattern, with optional cross braces
* @method mesh
* @param {composite} composite
* @param {number} columns
* @param {number} rows
* @param {boolean} crossBrace
* @param {object} options
* @return {composite} The composite containing objects meshed together with constraints
*/
Composites.mesh = function(composite, columns, rows, crossBrace, options) {
var bodies = composite.bodies,
row,
col,
bodyA,
bodyB,
bodyC;
for (row = 0; row < rows; row++) {
2015-05-21 19:33:26 -04:00
for (col = 1; col < columns; col++) {
bodyA = bodies[(col - 1) + (row * columns)];
bodyB = bodies[col + (row * columns)];
Composite.addConstraint(composite, Constraint.create(Common.extend({ bodyA: bodyA, bodyB: bodyB }, options)));
2014-03-30 14:45:30 -04:00
}
2015-05-21 19:33:26 -04:00
if (row > 0) {
for (col = 0; col < columns; col++) {
2014-03-30 14:45:30 -04:00
bodyA = bodies[col + ((row - 1) * columns)];
bodyB = bodies[col + (row * columns)];
Composite.addConstraint(composite, Constraint.create(Common.extend({ bodyA: bodyA, bodyB: bodyB }, options)));
if (crossBrace && col > 0) {
bodyC = bodies[(col - 1) + ((row - 1) * columns)];
Composite.addConstraint(composite, Constraint.create(Common.extend({ bodyA: bodyC, bodyB: bodyB }, options)));
}
if (crossBrace && col < columns - 1) {
bodyC = bodies[(col + 1) + ((row - 1) * columns)];
Composite.addConstraint(composite, Constraint.create(Common.extend({ bodyA: bodyC, bodyB: bodyB }, options)));
}
}
}
}
2014-05-01 09:09:06 -04:00
composite.label += ' Mesh';
2014-03-30 14:45:30 -04:00
return composite;
};
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
* Description
* @method pyramid
* @param {number} xx
* @param {number} yy
* @param {number} columns
* @param {number} rows
* @param {number} columnGap
* @param {number} rowGap
* @param {function} callback
* @return {composite} A new composite containing objects created in the callback
*/
2014-02-19 09:15:05 -05:00
Composites.pyramid = function(xx, yy, columns, rows, columnGap, rowGap, callback) {
return Composites.stack(xx, yy, columns, rows, columnGap, rowGap, function(x, y, column, row, lastBody, i) {
var actualRows = Math.min(rows, Math.ceil(columns / 2)),
lastBodyWidth = lastBody ? lastBody.bounds.max.x - lastBody.bounds.min.x : 0;
if (row > actualRows)
return;
// reverse row order
row = actualRows - row;
var start = row,
end = columns - 1 - row;
if (column < start || column > end)
return;
// retroactively fix the first body's position, since width was unknown
if (i === 1) {
Body.translate(lastBody, { x: (column + (columns % 2 === 1 ? 1 : -1)) * lastBodyWidth, y: 0 });
}
var xOffset = lastBody ? column * lastBodyWidth : 0;
return callback(xx + xOffset + column * columnGap, y, column, row, lastBody, i);
});
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method newtonsCradle
* @param {number} xx
* @param {number} yy
* @param {number} number
* @param {number} size
* @param {number} length
* @return {composite} A new composite newtonsCradle body
*/
2014-02-19 09:15:05 -05:00
Composites.newtonsCradle = function(xx, yy, number, size, length) {
2014-05-01 09:09:06 -04:00
var newtonsCradle = Composite.create({ label: 'Newtons Cradle' });
2014-02-19 09:15:05 -05:00
for (var i = 0; i < number; i++) {
var separation = 1.9,
circle = Bodies.circle(xx + i * (size * separation), yy + length, size,
2014-03-30 14:45:30 -04:00
{ inertia: 99999, restitution: 1, friction: 0, frictionAir: 0.0001, slop: 0.01 }),
2014-02-19 09:15:05 -05:00
constraint = Constraint.create({ pointA: { x: xx + i * (size * separation), y: yy }, bodyB: circle });
Composite.addBody(newtonsCradle, circle);
Composite.addConstraint(newtonsCradle, constraint);
}
return newtonsCradle;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method car
* @param {number} xx
* @param {number} yy
* @param {number} width
* @param {number} height
* @param {number} wheelSize
* @return {composite} A new composite car body
*/
2014-02-19 09:15:05 -05:00
Composites.car = function(xx, yy, width, height, wheelSize) {
2014-07-29 11:26:49 -04:00
var group = Body.nextGroup(true),
2014-02-19 09:15:05 -05:00
wheelBase = -20,
wheelAOffset = -width * 0.5 + wheelBase,
wheelBOffset = width * 0.5 - wheelBase,
wheelYOffset = 0;
2014-05-01 09:09:06 -04:00
var car = Composite.create({ label: 'Car' }),
2014-05-05 14:32:51 -04:00
body = Bodies.trapezoid(xx, yy, width, height, 0.3, {
2014-07-29 11:26:49 -04:00
collisionFilter: {
group: group
},
2014-05-05 14:32:51 -04:00
friction: 0.01,
chamfer: {
radius: 10
}
});
2014-02-19 09:15:05 -05:00
var wheelA = Bodies.circle(xx + wheelAOffset, yy + wheelYOffset, wheelSize, {
2014-07-29 11:26:49 -04:00
collisionFilter: {
group: group
},
2014-02-19 09:15:05 -05:00
restitution: 0.5,
friction: 0.9,
2015-05-20 15:38:41 -04:00
frictionStatic: 10,
slop: 0.5,
2014-02-19 09:15:05 -05:00
density: 0.01
});
var wheelB = Bodies.circle(xx + wheelBOffset, yy + wheelYOffset, wheelSize, {
2014-07-29 11:26:49 -04:00
collisionFilter: {
group: group
},
2014-02-19 09:15:05 -05:00
restitution: 0.5,
friction: 0.9,
2015-05-20 15:38:41 -04:00
frictionStatic: 10,
slop: 0.5,
2014-02-19 09:15:05 -05:00
density: 0.01
});
var axelA = Constraint.create({
bodyA: body,
pointA: { x: wheelAOffset, y: wheelYOffset },
bodyB: wheelA,
stiffness: 0.5
});
var axelB = Constraint.create({
bodyA: body,
pointA: { x: wheelBOffset, y: wheelYOffset },
bodyB: wheelB,
stiffness: 0.5
});
Composite.addBody(car, body);
Composite.addBody(car, wheelA);
Composite.addBody(car, wheelB);
Composite.addConstraint(car, axelA);
Composite.addConstraint(car, axelB);
return car;
};
2014-03-30 14:45:30 -04:00
/**
* Creates a simple soft body like object
* @method softBody
* @param {number} xx
* @param {number} yy
* @param {number} columns
* @param {number} rows
* @param {number} columnGap
* @param {number} rowGap
* @param {boolean} crossBrace
* @param {number} particleRadius
* @param {} particleOptions
* @param {} constraintOptions
* @return {composite} A new composite softBody
*/
Composites.softBody = function(xx, yy, columns, rows, columnGap, rowGap, crossBrace, particleRadius, particleOptions, constraintOptions) {
particleOptions = Common.extend({ inertia: Infinity }, particleOptions);
constraintOptions = Common.extend({ stiffness: 0.4 }, constraintOptions);
2015-01-01 18:10:10 -05:00
var softBody = Composites.stack(xx, yy, columns, rows, columnGap, rowGap, function(x, y) {
2014-03-30 14:45:30 -04:00
return Bodies.circle(x, y, particleRadius, particleOptions);
});
Composites.mesh(softBody, columns, rows, crossBrace, constraintOptions);
2014-05-01 09:09:06 -04:00
softBody.label = 'Soft Body';
2014-03-30 14:45:30 -04:00
return softBody;
};
2014-02-19 09:15:05 -05:00
})();
2014-07-29 11:26:49 -04:00
2014-02-19 09:15:05 -05:00
; // End src/factory/Composites.js
// Begin src/geometry/Axes.js
2014-02-28 20:10:08 -05:00
/**
* _Internal Class_, not generally used outside of the engine's internals.
*
* @class Axes
*/
2014-02-19 09:15:05 -05:00
var Axes = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
* Description
* @method fromVertices
* @param {vertices} vertices
* @return {axes} A new axes from the given vertices
*/
2014-02-19 09:15:05 -05:00
Axes.fromVertices = function(vertices) {
var axes = {};
// find the unique axes, using edge normal gradients
for (var i = 0; i < vertices.length; i++) {
var j = (i + 1) % vertices.length,
normal = Vector.normalise({
x: vertices[j].y - vertices[i].y,
y: vertices[i].x - vertices[j].x
}),
gradient = (normal.y === 0) ? Infinity : (normal.x / normal.y);
// limit precision
gradient = gradient.toFixed(3).toString();
axes[gradient] = normal;
}
return Common.values(axes);
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method rotate
* @param {axes} axes
* @param {number} angle
*/
2014-02-19 09:15:05 -05:00
Axes.rotate = function(axes, angle) {
if (angle === 0)
return;
var cos = Math.cos(angle),
sin = Math.sin(angle);
for (var i = 0; i < axes.length; i++) {
var axis = axes[i],
xx;
xx = axis.x * cos - axis.y * sin;
axis.y = axis.x * sin + axis.y * cos;
axis.x = xx;
}
};
})();
; // End src/geometry/Axes.js
// Begin src/geometry/Bounds.js
2014-02-28 20:10:08 -05:00
/**
* _Internal Class_, not generally used outside of the engine's internals.
*
* @class Bounds
*/
2014-02-19 09:15:05 -05:00
var Bounds = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
* Description
* @method create
* @param {vertices} vertices
* @return {bounds} A new bounds object
*/
2014-02-19 09:15:05 -05:00
Bounds.create = function(vertices) {
var bounds = {
min: { x: 0, y: 0 },
max: { x: 0, y: 0 }
};
2014-05-01 09:09:06 -04:00
if (vertices)
Bounds.update(bounds, vertices);
2014-02-19 09:15:05 -05:00
return bounds;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method update
* @param {bounds} bounds
* @param {vertices} vertices
* @param {vector} velocity
*/
2014-02-19 09:15:05 -05:00
Bounds.update = function(bounds, vertices, velocity) {
bounds.min.x = Number.MAX_VALUE;
bounds.max.x = Number.MIN_VALUE;
bounds.min.y = Number.MAX_VALUE;
bounds.max.y = Number.MIN_VALUE;
for (var i = 0; i < vertices.length; i++) {
var vertex = vertices[i];
if (vertex.x > bounds.max.x) bounds.max.x = vertex.x;
if (vertex.x < bounds.min.x) bounds.min.x = vertex.x;
if (vertex.y > bounds.max.y) bounds.max.y = vertex.y;
if (vertex.y < bounds.min.y) bounds.min.y = vertex.y;
}
if (velocity) {
if (velocity.x > 0) {
bounds.max.x += velocity.x;
} else {
bounds.min.x += velocity.x;
}
if (velocity.y > 0) {
bounds.max.y += velocity.y;
} else {
bounds.min.y += velocity.y;
}
}
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method contains
* @param {bounds} bounds
* @param {vector} point
* @return {boolean} True if the bounds contain the point, otherwise false
*/
2014-02-19 09:15:05 -05:00
Bounds.contains = function(bounds, point) {
return point.x >= bounds.min.x && point.x <= bounds.max.x
&& point.y >= bounds.min.y && point.y <= bounds.max.y;
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method overlaps
* @param {bounds} boundsA
* @param {bounds} boundsB
* @return {boolean} True if the bounds overlap, otherwise false
*/
2014-02-19 09:15:05 -05:00
Bounds.overlaps = function(boundsA, boundsB) {
return (boundsA.min.x <= boundsB.max.x && boundsA.max.x >= boundsB.min.x
&& boundsA.max.y >= boundsB.min.y && boundsA.min.y <= boundsB.max.y);
};
2014-05-01 09:09:06 -04:00
/**
* Translates the bounds by the given vector
* @method translate
* @param {bounds} bounds
* @param {vector} vector
*/
Bounds.translate = function(bounds, vector) {
bounds.min.x += vector.x;
bounds.max.x += vector.x;
bounds.min.y += vector.y;
bounds.max.y += vector.y;
};
/**
* Shifts the bounds to the given position
* @method shift
* @param {bounds} bounds
* @param {vector} position
*/
Bounds.shift = function(bounds, position) {
var deltaX = bounds.max.x - bounds.min.x,
deltaY = bounds.max.y - bounds.min.y;
bounds.min.x = position.x;
bounds.max.x = position.x + deltaX;
bounds.min.y = position.y;
bounds.max.y = position.y + deltaY;
};
2014-02-19 09:15:05 -05:00
})();
; // End src/geometry/Bounds.js
2015-05-20 15:38:41 -04:00
// Begin src/geometry/Svg.js
/**
* The `Matter.Svg` module contains methods for converting SVG images into an array of vector points.
*
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class Svg
*/
var Svg = {};
(function() {
/**
* Converts an SVG path into an array of vector points.
* If the input path forms a concave shape, you must decompose the result into convex parts before use.
* See `Bodies.fromVertices` which provides support for this.
* Note that this function is not guaranteed to support complex paths (such as those with holes).
* @method pathToVertices
* @param {SVGPathElement} path
* @param {Number} [sampleLength=15]
* @return {Vector[]} points
*/
Svg.pathToVertices = function(path, sampleLength) {
// https://github.com/wout/svg.topoly.js/blob/master/svg.topoly.js
var i, il, total, point, segment, segments,
segmentsQueue, lastSegment,
lastPoint, segmentIndex, points = [],
2015-07-02 15:17:03 -04:00
lx, ly, length = 0, x = 0, y = 0;
2015-05-20 15:38:41 -04:00
sampleLength = sampleLength || 15;
var addPoint = function(px, py, pathSegType) {
// all odd-numbered path types are relative except PATHSEG_CLOSEPATH (1)
var isRelative = pathSegType % 2 === 1 && pathSegType > 1;
// when the last point doesn't equal the current point add the current point
if (!lastPoint || px != lastPoint.x || py != lastPoint.y) {
if (lastPoint && isRelative) {
lx = lastPoint.x;
ly = lastPoint.y;
} else {
lx = 0;
ly = 0;
}
var point = {
x: lx + px,
y: ly + py
};
// set last point
if (isRelative || !lastPoint) {
lastPoint = point;
}
points.push(point);
x = lx + px;
y = ly + py;
}
};
var addSegmentPoint = function(segment) {
var segType = segment.pathSegTypeAsLetter.toUpperCase();
// skip path ends
if (segType === 'Z')
return;
// map segment to x and y
switch (segType) {
case 'M':
case 'L':
case 'T':
case 'C':
case 'S':
case 'Q':
x = segment.x;
y = segment.y;
break;
case 'H':
x = segment.x;
break;
case 'V':
y = segment.y;
break;
}
addPoint(x, y, segment.pathSegType);
};
// ensure path is absolute
_svgPathToAbsolute(path);
// get total length
total = path.getTotalLength();
// queue segments
segments = [];
for (i = 0; i < path.pathSegList.numberOfItems; i += 1)
segments.push(path.pathSegList.getItem(i));
segmentsQueue = segments.concat();
// sample through path
while (length < total) {
// get segment at position
segmentIndex = path.getPathSegAtLength(length);
segment = segments[segmentIndex];
// new segment
if (segment != lastSegment) {
while (segmentsQueue.length && segmentsQueue[0] != segment)
addSegmentPoint(segmentsQueue.shift());
lastSegment = segment;
}
// add points in between when curving
// TODO: adaptive sampling
switch (segment.pathSegTypeAsLetter.toUpperCase()) {
case 'C':
case 'T':
case 'S':
case 'Q':
case 'A':
point = path.getPointAtLength(length);
addPoint(point.x, point.y, 0);
break;
}
// increment by sample value
length += sampleLength;
}
// add remaining segments not passed by sampling
for (i = 0, il = segmentsQueue.length; i < il; ++i)
addSegmentPoint(segmentsQueue[i]);
return points;
};
var _svgPathToAbsolute = function(path) {
// http://phrogz.net/convert-svg-path-to-all-absolute-commands
var x0, y0, x1, y1, x2, y2, segs = path.pathSegList,
x = 0, y = 0, len = segs.numberOfItems;
for (var i = 0; i < len; ++i) {
var seg = segs.getItem(i),
segType = seg.pathSegTypeAsLetter;
if (/[MLHVCSQTA]/.test(segType)) {
if ('x' in seg) x = seg.x;
if ('y' in seg) y = seg.y;
} else {
if ('x1' in seg) x1 = x + seg.x1;
if ('x2' in seg) x2 = x + seg.x2;
if ('y1' in seg) y1 = y + seg.y1;
if ('y2' in seg) y2 = y + seg.y2;
if ('x' in seg) x += seg.x;
if ('y' in seg) y += seg.y;
switch (segType) {
case 'm':
segs.replaceItem(path.createSVGPathSegMovetoAbs(x, y), i);
break;
case 'l':
segs.replaceItem(path.createSVGPathSegLinetoAbs(x, y), i);
break;
case 'h':
segs.replaceItem(path.createSVGPathSegLinetoHorizontalAbs(x), i);
break;
case 'v':
segs.replaceItem(path.createSVGPathSegLinetoVerticalAbs(y), i);
break;
case 'c':
segs.replaceItem(path.createSVGPathSegCurvetoCubicAbs(x, y, x1, y1, x2, y2), i);
break;
case 's':
segs.replaceItem(path.createSVGPathSegCurvetoCubicSmoothAbs(x, y, x2, y2), i);
break;
case 'q':
segs.replaceItem(path.createSVGPathSegCurvetoQuadraticAbs(x, y, x1, y1), i);
break;
case 't':
segs.replaceItem(path.createSVGPathSegCurvetoQuadraticSmoothAbs(x, y), i);
break;
case 'a':
segs.replaceItem(path.createSVGPathSegArcAbs(x, y, seg.r1, seg.r2, seg.angle, seg.largeArcFlag, seg.sweepFlag), i);
break;
case 'z':
case 'Z':
x = x0;
y = y0;
break;
}
}
if (segType == 'M' || segType == 'm') {
x0 = x;
y0 = y;
}
}
};
})();
; // End src/geometry/Svg.js
2014-02-19 09:15:05 -05:00
// Begin src/geometry/Vector.js
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* The `Matter.Vector` module contains methods for creating and manipulating vectors.
* Vectors are the basis of all the geometry related operations in the engine.
* A `Matter.Vector` object is of the form `{ x: 0, y: 0 }`.
*
2014-02-28 20:10:08 -05:00
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class Vector
*/
2014-02-19 09:15:05 -05:00
// TODO: consider params for reusing vector objects
var Vector = {};
(function() {
2015-05-20 15:38:41 -04:00
/**
* Creates a new vector.
* @method create
* @param {number} x
* @param {number} y
* @return {vector} A new vector
*/
Vector.create = function(x, y) {
return { x: x || 0, y: y || 0 };
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Returns a new vector with `x` and `y` copied from the given `vector`.
* @method clone
* @param {vector} vector
* @return {vector} A new cloned vector
*/
Vector.clone = function(vector) {
return { x: vector.x, y: vector.y };
};
/**
* Returns the magnitude (length) of a vector.
2014-02-28 20:10:08 -05:00
* @method magnitude
* @param {vector} vector
* @return {number} The magnitude of the vector
*/
2014-02-19 09:15:05 -05:00
Vector.magnitude = function(vector) {
return Math.sqrt((vector.x * vector.x) + (vector.y * vector.y));
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Returns the magnitude (length) of a vector (therefore saving a `sqrt` operation).
2014-02-28 20:10:08 -05:00
* @method magnitudeSquared
* @param {vector} vector
* @return {number} The squared magnitude of the vector
*/
2014-02-19 09:15:05 -05:00
Vector.magnitudeSquared = function(vector) {
return (vector.x * vector.x) + (vector.y * vector.y);
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Rotates the vector about (0, 0) by specified angle.
2014-02-28 20:10:08 -05:00
* @method rotate
* @param {vector} vector
* @param {number} angle
2014-06-09 14:40:24 -04:00
* @return {vector} A new vector rotated about (0, 0)
2014-02-28 20:10:08 -05:00
*/
2014-02-19 09:15:05 -05:00
Vector.rotate = function(vector, angle) {
var cos = Math.cos(angle), sin = Math.sin(angle);
return {
x: vector.x * cos - vector.y * sin,
y: vector.x * sin + vector.y * cos
};
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Rotates the vector about a specified point by specified angle.
2014-02-28 20:10:08 -05:00
* @method rotateAbout
* @param {vector} vector
* @param {number} angle
* @param {vector} point
2015-05-20 15:38:41 -04:00
* @param {vector} [output]
2014-02-28 20:10:08 -05:00
* @return {vector} A new vector rotated about the point
*/
2015-05-20 15:38:41 -04:00
Vector.rotateAbout = function(vector, angle, point, output) {
2014-02-19 09:15:05 -05:00
var cos = Math.cos(angle), sin = Math.sin(angle);
2015-05-20 15:38:41 -04:00
if (!output) output = {};
var x = point.x + ((vector.x - point.x) * cos - (vector.y - point.y) * sin);
output.y = point.y + ((vector.x - point.x) * sin + (vector.y - point.y) * cos);
output.x = x;
return output;
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Normalises a vector (such that its magnitude is `1`).
2014-02-28 20:10:08 -05:00
* @method normalise
* @param {vector} vector
* @return {vector} A new vector normalised
*/
2014-02-19 09:15:05 -05:00
Vector.normalise = function(vector) {
var magnitude = Vector.magnitude(vector);
if (magnitude === 0)
return { x: 0, y: 0 };
return { x: vector.x / magnitude, y: vector.y / magnitude };
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Returns the dot-product of two vectors.
2014-02-28 20:10:08 -05:00
* @method dot
* @param {vector} vectorA
* @param {vector} vectorB
* @return {number} The dot product of the two vectors
*/
2014-02-19 09:15:05 -05:00
Vector.dot = function(vectorA, vectorB) {
return (vectorA.x * vectorB.x) + (vectorA.y * vectorB.y);
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Returns the cross-product of two vectors.
2014-02-28 20:10:08 -05:00
* @method cross
* @param {vector} vectorA
* @param {vector} vectorB
* @return {number} The cross product of the two vectors
*/
2014-02-19 09:15:05 -05:00
Vector.cross = function(vectorA, vectorB) {
return (vectorA.x * vectorB.y) - (vectorA.y * vectorB.x);
};
2015-05-20 15:38:41 -04:00
/**
* Returns the cross-product of three vectors.
* @method cross3
* @param {vector} vectorA
* @param {vector} vectorB
* @param {vector} vectorC
* @return {number} The cross product of the three vectors
*/
Vector.cross3 = function(vectorA, vectorB, vectorC) {
return (vectorB.x - vectorA.x) * (vectorC.y - vectorA.y) - (vectorB.y - vectorA.y) * (vectorC.x - vectorA.x);
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Adds the two vectors.
2014-02-28 20:10:08 -05:00
* @method add
* @param {vector} vectorA
* @param {vector} vectorB
2015-05-20 15:38:41 -04:00
* @param {vector} [output]
2014-06-09 14:40:24 -04:00
* @return {vector} A new vector of vectorA and vectorB added
2014-02-28 20:10:08 -05:00
*/
2015-05-20 15:38:41 -04:00
Vector.add = function(vectorA, vectorB, output) {
if (!output) output = {};
output.x = vectorA.x + vectorB.x;
output.y = vectorA.y + vectorB.y;
return output;
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Subtracts the two vectors.
2014-02-28 20:10:08 -05:00
* @method sub
* @param {vector} vectorA
* @param {vector} vectorB
2015-05-20 15:38:41 -04:00
* @param {vector} [output]
2014-06-09 14:40:24 -04:00
* @return {vector} A new vector of vectorA and vectorB subtracted
2014-02-28 20:10:08 -05:00
*/
2015-05-20 15:38:41 -04:00
Vector.sub = function(vectorA, vectorB, output) {
if (!output) output = {};
output.x = vectorA.x - vectorB.x;
output.y = vectorA.y - vectorB.y;
return output;
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Multiplies a vector and a scalar.
2014-02-28 20:10:08 -05:00
* @method mult
* @param {vector} vector
* @param {number} scalar
* @return {vector} A new vector multiplied by scalar
*/
2014-02-19 09:15:05 -05:00
Vector.mult = function(vector, scalar) {
return { x: vector.x * scalar, y: vector.y * scalar };
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Divides a vector and a scalar.
2014-02-28 20:10:08 -05:00
* @method div
* @param {vector} vector
* @param {number} scalar
* @return {vector} A new vector divided by scalar
*/
2014-02-19 09:15:05 -05:00
Vector.div = function(vector, scalar) {
return { x: vector.x / scalar, y: vector.y / scalar };
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Returns the perpendicular vector. Set `negate` to true for the perpendicular in the opposite direction.
2014-02-28 20:10:08 -05:00
* @method perp
* @param {vector} vector
2014-06-09 14:40:24 -04:00
* @param {bool} [negate=false]
2014-02-28 20:10:08 -05:00
* @return {vector} The perpendicular vector
*/
2014-02-19 09:15:05 -05:00
Vector.perp = function(vector, negate) {
negate = negate === true ? -1 : 1;
return { x: negate * -vector.y, y: negate * vector.x };
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Negates both components of a vector such that it points in the opposite direction.
2014-02-28 20:10:08 -05:00
* @method neg
* @param {vector} vector
* @return {vector} The negated vector
*/
2014-02-19 09:15:05 -05:00
Vector.neg = function(vector) {
return { x: -vector.x, y: -vector.y };
};
2014-05-01 09:09:06 -04:00
/**
2014-06-09 14:40:24 -04:00
* Returns the angle in radians between the two vectors relative to the x-axis.
2014-05-01 09:09:06 -04:00
* @method angle
* @param {vector} vectorA
* @param {vector} vectorB
* @return {number} The angle in radians
*/
Vector.angle = function(vectorA, vectorB) {
return Math.atan2(vectorB.y - vectorA.y, vectorB.x - vectorA.x);
};
2015-05-20 15:38:41 -04:00
/**
* Temporary vector pool (not thread-safe).
* @property _temp
* @type {vector[]}
* @private
*/
Vector._temp = [Vector.create(), Vector.create(),
Vector.create(), Vector.create(),
Vector.create(), Vector.create()];
2014-02-19 09:15:05 -05:00
})();
; // End src/geometry/Vector.js
// Begin src/geometry/Vertices.js
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* The `Matter.Vertices` module contains methods for creating and manipulating sets of vertices.
* A set of vertices is an array of `Matter.Vector` with additional indexing properties inserted by `Vertices.create`.
* A `Matter.Body` maintains a set of vertices to represent the shape of the object (its convex hull).
*
2014-02-28 20:10:08 -05:00
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class Vertices
*/
2014-02-19 09:15:05 -05:00
var Vertices = {};
(function() {
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Creates a new set of `Matter.Body` compatible vertices.
* The `points` argument accepts an array of `Matter.Vector` points orientated around the origin `(0, 0)`, for example:
*
* [{ x: 0, y: 0 }, { x: 25, y: 50 }, { x: 50, y: 0 }]
*
* The `Vertices.create` method returns a new array of vertices, which are similar to Matter.Vector objects,
* but with some additional references required for efficient collision detection routines.
*
* Note that the `body` argument is not optional, a `Matter.Body` reference must be provided.
*
2014-02-28 20:10:08 -05:00
* @method create
2014-06-09 14:40:24 -04:00
* @param {vector[]} points
2014-02-28 20:10:08 -05:00
* @param {body} body
*/
2014-06-09 14:40:24 -04:00
Vertices.create = function(points, body) {
var vertices = [];
for (var i = 0; i < points.length; i++) {
var point = points[i],
2015-05-20 15:38:41 -04:00
vertex = {
x: point.x,
y: point.y,
index: i,
body: body,
isInternal: false
};
2014-06-09 14:40:24 -04:00
vertices.push(vertex);
2014-02-19 09:15:05 -05:00
}
2014-06-09 14:40:24 -04:00
return vertices;
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
2015-05-20 15:38:41 -04:00
* Parses a string containing ordered x y pairs separated by spaces (and optionally commas),
* into a `Matter.Vertices` object for the given `Matter.Body`.
* For parsing SVG paths, see `Svg.pathToVertices`.
2014-02-28 20:10:08 -05:00
* @method fromPath
* @param {string} path
2014-06-09 14:40:24 -04:00
* @param {body} body
2014-02-28 20:10:08 -05:00
* @return {vertices} vertices
*/
2014-06-09 14:40:24 -04:00
Vertices.fromPath = function(path, body) {
2015-05-20 15:38:41 -04:00
var pathPattern = /L?\s*([\-\d\.e]+)[\s,]*([\-\d\.e]+)*/ig,
2014-06-09 14:40:24 -04:00
points = [];
2014-02-19 09:15:05 -05:00
path.replace(pathPattern, function(match, x, y) {
2015-01-01 18:10:10 -05:00
points.push({ x: parseFloat(x), y: parseFloat(y) });
2014-02-19 09:15:05 -05:00
});
2014-06-09 14:40:24 -04:00
return Vertices.create(points, body);
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Returns the centre (centroid) of the set of vertices.
2014-02-28 20:10:08 -05:00
* @method centre
* @param {vertices} vertices
* @return {vector} The centre point
*/
2014-02-19 09:15:05 -05:00
Vertices.centre = function(vertices) {
2014-05-05 14:32:51 -04:00
var area = Vertices.area(vertices, true),
centre = { x: 0, y: 0 },
cross,
temp,
j;
2014-02-19 09:15:05 -05:00
for (var i = 0; i < vertices.length; i++) {
2014-05-05 14:32:51 -04:00
j = (i + 1) % vertices.length;
cross = Vector.cross(vertices[i], vertices[j]);
temp = Vector.mult(Vector.add(vertices[i], vertices[j]), cross);
centre = Vector.add(centre, temp);
2014-02-19 09:15:05 -05:00
}
2014-05-05 14:32:51 -04:00
return Vector.div(centre, 6 * area);
2014-02-19 09:15:05 -05:00
};
2015-05-20 15:38:41 -04:00
/**
* Returns the average (mean) of the set of vertices.
* @method mean
* @param {vertices} vertices
* @return {vector} The average point
*/
Vertices.mean = function(vertices) {
var average = { x: 0, y: 0 };
for (var i = 0; i < vertices.length; i++) {
average.x += vertices[i].x;
average.y += vertices[i].y;
}
return Vector.div(average, vertices.length);
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Returns the area of the set of vertices.
2014-02-28 20:10:08 -05:00
* @method area
* @param {vertices} vertices
2014-05-05 14:32:51 -04:00
* @param {bool} signed
2014-02-28 20:10:08 -05:00
* @return {number} The area
*/
2014-05-05 14:32:51 -04:00
Vertices.area = function(vertices, signed) {
2014-02-19 09:15:05 -05:00
var area = 0,
j = vertices.length - 1;
for (var i = 0; i < vertices.length; i++) {
area += (vertices[j].x - vertices[i].x) * (vertices[j].y + vertices[i].y);
j = i;
}
2014-05-05 14:32:51 -04:00
if (signed)
return area / 2;
2014-02-19 09:15:05 -05:00
return Math.abs(area) / 2;
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Returns the moment of inertia (second moment of area) of the set of vertices given the total mass.
2014-02-28 20:10:08 -05:00
* @method inertia
* @param {vertices} vertices
* @param {number} mass
2014-06-09 14:40:24 -04:00
* @return {number} The polygon's moment of inertia
2014-02-28 20:10:08 -05:00
*/
2014-02-19 09:15:05 -05:00
Vertices.inertia = function(vertices, mass) {
var numerator = 0,
denominator = 0,
v = vertices,
cross,
j;
// find the polygon's moment of inertia, using second moment of area
// http://www.physicsforums.com/showthread.php?t=25293
for (var n = 0; n < v.length; n++) {
j = (n + 1) % v.length;
cross = Math.abs(Vector.cross(v[j], v[n]));
numerator += cross * (Vector.dot(v[j], v[j]) + Vector.dot(v[j], v[n]) + Vector.dot(v[n], v[n]));
denominator += cross;
}
return (mass / 6) * (numerator / denominator);
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Translates the set of vertices in-place.
2014-02-28 20:10:08 -05:00
* @method translate
* @param {vertices} vertices
* @param {vector} vector
* @param {number} scalar
*/
2014-02-19 09:15:05 -05:00
Vertices.translate = function(vertices, vector, scalar) {
var i;
if (scalar) {
for (i = 0; i < vertices.length; i++) {
vertices[i].x += vector.x * scalar;
vertices[i].y += vector.y * scalar;
}
} else {
for (i = 0; i < vertices.length; i++) {
vertices[i].x += vector.x;
vertices[i].y += vector.y;
}
2014-06-09 14:40:24 -04:00
}
return vertices;
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Rotates the set of vertices in-place.
2014-02-28 20:10:08 -05:00
* @method rotate
* @param {vertices} vertices
* @param {number} angle
* @param {vector} point
*/
2014-02-19 09:15:05 -05:00
Vertices.rotate = function(vertices, angle, point) {
if (angle === 0)
return;
var cos = Math.cos(angle),
sin = Math.sin(angle);
for (var i = 0; i < vertices.length; i++) {
var vertice = vertices[i],
dx = vertice.x - point.x,
dy = vertice.y - point.y;
vertice.x = point.x + (dx * cos - dy * sin);
vertice.y = point.y + (dx * sin + dy * cos);
}
2014-06-09 14:40:24 -04:00
return vertices;
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Returns `true` if the `point` is inside the set of `vertices`.
2014-02-28 20:10:08 -05:00
* @method contains
* @param {vertices} vertices
* @param {vector} point
* @return {boolean} True if the vertices contains point, otherwise false
*/
2014-02-19 09:15:05 -05:00
Vertices.contains = function(vertices, point) {
for (var i = 0; i < vertices.length; i++) {
var vertice = vertices[i],
nextVertice = vertices[(i + 1) % vertices.length];
if ((point.x - vertice.x) * (nextVertice.y - vertice.y) + (point.y - vertice.y) * (vertice.x - nextVertice.x) > 0) {
return false;
}
}
return true;
};
2014-05-01 09:09:06 -04:00
/**
2014-06-09 14:40:24 -04:00
* Scales the vertices from a point (default is centre) in-place.
2014-05-01 09:09:06 -04:00
* @method scale
* @param {vertices} vertices
* @param {number} scaleX
* @param {number} scaleY
* @param {vector} point
*/
Vertices.scale = function(vertices, scaleX, scaleY, point) {
if (scaleX === 1 && scaleY === 1)
return vertices;
point = point || Vertices.centre(vertices);
var vertex,
delta;
for (var i = 0; i < vertices.length; i++) {
vertex = vertices[i];
delta = Vector.sub(vertex, point);
vertices[i].x = point.x + delta.x * scaleX;
vertices[i].y = point.y + delta.y * scaleY;
}
return vertices;
};
2014-05-05 14:32:51 -04:00
/**
* Chamfers a set of vertices by giving them rounded corners, returns a new set of vertices.
* The radius parameter is a single number or an array to specify the radius for each vertex.
* @method chamfer
* @param {vertices} vertices
* @param {number[]} radius
* @param {number} quality
* @param {number} qualityMin
* @param {number} qualityMax
*/
Vertices.chamfer = function(vertices, radius, quality, qualityMin, qualityMax) {
radius = radius || [8];
if (!radius.length)
radius = [radius];
// quality defaults to -1, which is auto
quality = (typeof quality !== 'undefined') ? quality : -1;
qualityMin = qualityMin || 2;
qualityMax = qualityMax || 14;
2015-01-01 18:10:10 -05:00
var newVertices = [];
2014-05-05 14:32:51 -04:00
for (var i = 0; i < vertices.length; i++) {
var prevVertex = vertices[i - 1 >= 0 ? i - 1 : vertices.length - 1],
vertex = vertices[i],
nextVertex = vertices[(i + 1) % vertices.length],
currentRadius = radius[i < radius.length ? i : radius.length - 1];
if (currentRadius === 0) {
newVertices.push(vertex);
continue;
}
var prevNormal = Vector.normalise({
x: vertex.y - prevVertex.y,
y: prevVertex.x - vertex.x
});
var nextNormal = Vector.normalise({
x: nextVertex.y - vertex.y,
y: vertex.x - nextVertex.x
});
var diagonalRadius = Math.sqrt(2 * Math.pow(currentRadius, 2)),
radiusVector = Vector.mult(Common.clone(prevNormal), currentRadius),
midNormal = Vector.normalise(Vector.mult(Vector.add(prevNormal, nextNormal), 0.5)),
scaledVertex = Vector.sub(vertex, Vector.mult(midNormal, diagonalRadius));
var precision = quality;
if (quality === -1) {
// automatically decide precision
precision = Math.pow(currentRadius, 0.32) * 1.75;
}
precision = Common.clamp(precision, qualityMin, qualityMax);
// use an even value for precision, more likely to reduce axes by using symmetry
if (precision % 2 === 1)
precision += 1;
var alpha = Math.acos(Vector.dot(prevNormal, nextNormal)),
theta = alpha / precision;
for (var j = 0; j < precision; j++) {
newVertices.push(Vector.add(Vector.rotate(radiusVector, theta * j), scaledVertex));
}
}
return newVertices;
};
2015-05-20 15:38:41 -04:00
/**
* Sorts the input vertices into clockwise order in place.
* @method clockwiseSort
* @param {vertices} vertices
* @return {vertices} vertices
*/
Vertices.clockwiseSort = function(vertices) {
var centre = Vertices.mean(vertices);
vertices.sort(function(vertexA, vertexB) {
return Vector.angle(centre, vertexA) - Vector.angle(centre, vertexB);
});
return vertices;
};
/**
* Returns true if the vertices form a convex shape (vertices must be in clockwise order).
* @method isConvex
* @param {vertices} vertices
* @return {bool} `true` if the `vertices` are convex, `false` if not (or `null` if not computable).
*/
Vertices.isConvex = function(vertices) {
// http://paulbourke.net/geometry/polygonmesh/
var flag = 0,
n = vertices.length,
i,
j,
k,
z;
if (n < 3)
return null;
for (i = 0; i < n; i++) {
j = (i + 1) % n;
k = (i + 2) % n;
z = (vertices[j].x - vertices[i].x) * (vertices[k].y - vertices[j].y);
z -= (vertices[j].y - vertices[i].y) * (vertices[k].x - vertices[j].x);
if (z < 0) {
flag |= 1;
} else if (z > 0) {
flag |= 2;
}
if (flag === 3) {
return false;
}
}
if (flag !== 0){
return true;
} else {
return null;
}
};
/**
* Returns the convex hull of the input vertices as a new array of points.
* @method hull
* @param {vertices} vertices
* @return [vertex] vertices
*/
Vertices.hull = function(vertices) {
// http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
var upper = [],
lower = [],
vertex,
i;
// sort vertices on x-axis (y-axis for ties)
vertices = vertices.slice(0);
vertices.sort(function(vertexA, vertexB) {
var dx = vertexA.x - vertexB.x;
return dx !== 0 ? dx : vertexA.y - vertexB.y;
});
// build lower hull
for (i = 0; i < vertices.length; i++) {
vertex = vertices[i];
while (lower.length >= 2
&& Vector.cross3(lower[lower.length - 2], lower[lower.length - 1], vertex) <= 0) {
lower.pop();
}
lower.push(vertex);
}
// build upper hull
for (i = vertices.length - 1; i >= 0; i--) {
vertex = vertices[i];
while (upper.length >= 2
&& Vector.cross3(upper[upper.length - 2], upper[upper.length - 1], vertex) <= 0) {
upper.pop();
}
upper.push(vertex);
}
// concatenation of the lower and upper hulls gives the convex hull
// omit last points because they are repeated at the beginning of the other list
upper.pop();
lower.pop();
return upper.concat(lower);
};
2014-02-19 09:15:05 -05:00
})();
2015-01-01 18:10:10 -05:00
2014-02-19 09:15:05 -05:00
; // End src/geometry/Vertices.js
2014-05-01 09:09:06 -04:00
// Begin src/render/Render.js
2014-02-19 09:15:05 -05:00
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* The `Matter.Render` module is the default `render.controller` used by a `Matter.Engine`.
* This renderer is HTML5 canvas based and supports a number of drawing options including sprites and viewports.
*
* It is possible develop a custom renderer module based on `Matter.Render` and pass an instance of it to `Engine.create` via `options.render`.
* A minimal custom renderer object must define at least three functions: `create`, `clear` and `world` (see `Matter.Render`).
*
* See also `Matter.RenderPixi` for an alternate WebGL, scene-graph based renderer.
2014-02-28 20:10:08 -05:00
*
2014-05-01 09:09:06 -04:00
* @class Render
2014-02-28 20:10:08 -05:00
*/
2014-05-01 09:09:06 -04:00
var Render = {};
2014-02-19 09:15:05 -05:00
2014-05-01 09:09:06 -04:00
(function() {
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Creates a new renderer. The options parameter is an object that specifies any properties you wish to override the defaults.
* All properties have default values, and many are pre-calculated automatically based on other properties.
2015-06-29 15:58:24 -04:00
* See the properties section below for detailed information on what you can pass via the `options` object.
2014-02-28 20:10:08 -05:00
* @method create
2014-06-09 14:40:24 -04:00
* @param {object} [options]
2014-05-01 09:09:06 -04:00
* @return {render} A new renderer
2014-02-28 20:10:08 -05:00
*/
2014-05-01 09:09:06 -04:00
Render.create = function(options) {
var defaults = {
controller: Render,
element: null,
canvas: null,
options: {
width: 800,
height: 600,
2014-12-28 13:37:43 -05:00
pixelRatio: 1,
2014-05-01 09:09:06 -04:00
background: '#fafafa',
wireframeBackground: '#222',
hasBounds: false,
enabled: true,
wireframes: true,
showSleeping: true,
showDebug: false,
showBroadphase: false,
showBounds: false,
showVelocity: false,
showCollisions: false,
2015-05-20 15:38:41 -04:00
showSeparations: false,
2014-05-01 09:09:06 -04:00
showAxes: false,
showPositions: false,
showAngleIndicator: false,
showIds: false,
2015-05-20 15:38:41 -04:00
showShadows: false,
showVertexNumbers: false,
showConvexHulls: false,
showInternalEdges: false
2014-05-01 09:09:06 -04:00
}
2014-02-19 09:15:05 -05:00
};
2014-05-01 09:09:06 -04:00
var render = Common.extend(defaults, options);
2014-02-19 09:15:05 -05:00
2014-05-01 09:09:06 -04:00
render.canvas = render.canvas || _createCanvas(render.options.width, render.options.height);
render.context = render.canvas.getContext('2d');
render.textures = {};
2014-02-19 09:15:05 -05:00
2014-05-01 09:09:06 -04:00
render.bounds = render.bounds || {
min: {
x: 0,
y: 0
},
max: {
x: render.options.width,
y: render.options.height
}
};
2014-03-30 14:45:30 -04:00
2014-12-28 13:37:43 -05:00
if (render.options.pixelRatio !== 1) {
Render.setPixelRatio(render, render.options.pixelRatio);
}
if (Common.isElement(render.element)) {
render.element.appendChild(render.canvas);
} else {
2015-01-20 19:15:04 -05:00
Common.log('Render.create: options.element was undefined, render.canvas was created but not appended', 'warn');
}
2014-02-19 09:15:05 -05:00
return render;
};
2014-12-28 13:37:43 -05:00
/**
* Sets the pixel ratio of the renderer and updates the canvas.
* To automatically detect the correct ratio, pass the string `'auto'` for `pixelRatio`.
* @method setPixelRatio
* @param {render} render
* @param {number} pixelRatio
*/
Render.setPixelRatio = function(render, pixelRatio) {
var options = render.options,
canvas = render.canvas;
if (pixelRatio === 'auto') {
pixelRatio = _getPixelRatio(canvas);
}
options.pixelRatio = pixelRatio;
canvas.setAttribute('data-pixel-ratio', pixelRatio);
canvas.width = options.width * pixelRatio;
canvas.height = options.height * pixelRatio;
canvas.style.width = options.width + 'px';
canvas.style.height = options.height + 'px';
render.context.scale(pixelRatio, pixelRatio);
};
2014-02-28 20:10:08 -05:00
/**
2014-06-09 14:40:24 -04:00
* Renders the given `engine`'s `Matter.World` object.
* This is the entry point for all rendering and should be called every time the scene changes.
2014-02-28 20:10:08 -05:00
* @method world
* @param {engine} engine
*/
2014-02-19 09:15:05 -05:00
Render.world = function(engine) {
var render = engine.render,
world = engine.world,
canvas = render.canvas,
context = render.context,
options = render.options,
2014-05-01 09:09:06 -04:00
allBodies = Composite.allBodies(world),
allConstraints = Composite.allConstraints(world),
2015-01-20 19:15:04 -05:00
background = options.wireframes ? options.wireframeBackground : options.background,
2014-05-01 09:09:06 -04:00
bodies = [],
constraints = [],
2014-02-19 09:15:05 -05:00
i;
2015-08-12 19:38:20 -04:00
var event = {
timestamp: engine.timing.timestamp
};
Events.trigger(render, 'beforeRender', event);
2015-01-20 19:15:04 -05:00
// apply background if it has changed
if (render.currentBackground !== background)
_applyBackground(render, background);
// clear the canvas with a transparent fill, to allow the canvas background to show
context.globalCompositeOperation = 'source-in';
context.fillStyle = "transparent";
2014-02-19 09:15:05 -05:00
context.fillRect(0, 0, canvas.width, canvas.height);
context.globalCompositeOperation = 'source-over';
2014-02-19 09:15:05 -05:00
2014-05-01 09:09:06 -04:00
// handle bounds
if (options.hasBounds) {
2014-12-28 13:37:43 -05:00
var boundsWidth = render.bounds.max.x - render.bounds.min.x,
boundsHeight = render.bounds.max.y - render.bounds.min.y,
boundsScaleX = boundsWidth / options.width,
boundsScaleY = boundsHeight / options.height;
2014-05-01 09:09:06 -04:00
// filter out bodies that are not in view
for (i = 0; i < allBodies.length; i++) {
var body = allBodies[i];
if (Bounds.overlaps(body.bounds, render.bounds))
bodies.push(body);
}
// filter out constraints that are not in view
for (i = 0; i < allConstraints.length; i++) {
var constraint = allConstraints[i],
bodyA = constraint.bodyA,
bodyB = constraint.bodyB,
pointAWorld = constraint.pointA,
pointBWorld = constraint.pointB;
if (bodyA) pointAWorld = Vector.add(bodyA.position, constraint.pointA);
if (bodyB) pointBWorld = Vector.add(bodyB.position, constraint.pointB);
if (!pointAWorld || !pointBWorld)
continue;
if (Bounds.contains(render.bounds, pointAWorld) || Bounds.contains(render.bounds, pointBWorld))
constraints.push(constraint);
}
2014-05-05 14:32:51 -04:00
// transform the view
context.scale(1 / boundsScaleX, 1 / boundsScaleY);
2014-05-01 09:09:06 -04:00
context.translate(-render.bounds.min.x, -render.bounds.min.y);
} else {
constraints = allConstraints;
bodies = allBodies;
}
2014-02-19 09:15:05 -05:00
if (!options.wireframes || (engine.enableSleeping && options.showSleeping)) {
// fully featured rendering of bodies
2014-03-24 16:11:42 -04:00
Render.bodies(engine, bodies, context);
} else {
2015-05-20 15:38:41 -04:00
if (options.showConvexHulls)
Render.bodyConvexHulls(engine, bodies, context);
// optimised method for wireframes only
2014-03-24 16:11:42 -04:00
Render.bodyWireframes(engine, bodies, context);
}
2014-02-19 09:15:05 -05:00
if (options.showBounds)
2014-03-24 16:11:42 -04:00
Render.bodyBounds(engine, bodies, context);
if (options.showAxes || options.showAngleIndicator)
2014-03-24 16:11:42 -04:00
Render.bodyAxes(engine, bodies, context);
if (options.showPositions)
2014-03-24 16:11:42 -04:00
Render.bodyPositions(engine, bodies, context);
if (options.showVelocity)
2014-03-24 16:11:42 -04:00
Render.bodyVelocity(engine, bodies, context);
if (options.showIds)
2014-03-24 16:11:42 -04:00
Render.bodyIds(engine, bodies, context);
2014-02-19 09:15:05 -05:00
2015-05-20 15:38:41 -04:00
if (options.showSeparations)
Render.separations(engine, engine.pairs.list, context);
2014-02-19 09:15:05 -05:00
if (options.showCollisions)
Render.collisions(engine, engine.pairs.list, context);
2015-05-20 15:38:41 -04:00
if (options.showVertexNumbers)
Render.vertexNumbers(engine, bodies, context);
2014-03-24 16:11:42 -04:00
Render.constraints(constraints, context);
2014-02-19 09:15:05 -05:00
2014-07-29 11:26:49 -04:00
if (options.showBroadphase && engine.broadphase.controller === Grid)
Render.grid(engine, engine.broadphase, context);
2014-02-19 09:15:05 -05:00
if (options.showDebug)
Render.debug(engine, context);
2014-05-01 09:09:06 -04:00
2014-05-05 14:32:51 -04:00
if (options.hasBounds) {
// revert view transforms
2014-12-28 13:37:43 -05:00
context.setTransform(options.pixelRatio, 0, 0, options.pixelRatio, 0, 0);
2014-05-05 14:32:51 -04:00
}
2015-08-12 19:38:20 -04:00
Events.trigger(render, 'afterRender', event);
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
* Description
2014-06-09 14:40:24 -04:00
* @private
2014-02-28 20:10:08 -05:00
* @method debug
* @param {engine} engine
* @param {RenderingContext} context
*/
2014-02-19 09:15:05 -05:00
Render.debug = function(engine, context) {
var c = context,
world = engine.world,
render = engine.render,
options = render.options,
2014-03-24 16:11:42 -04:00
bodies = Composite.allBodies(world),
2014-02-19 09:15:05 -05:00
space = " ";
if (engine.timing.timestamp - (render.debugTimestamp || 0) >= 500) {
var text = "";
text += "fps: " + Math.round(engine.timing.fps) + space;
render.debugString = text;
render.debugTimestamp = engine.timing.timestamp;
}
if (render.debugString) {
c.font = "12px Arial";
if (options.wireframes) {
c.fillStyle = 'rgba(255,255,255,0.5)';
} else {
c.fillStyle = 'rgba(0,0,0,0.5)';
}
var split = render.debugString.split('\n');
for (var i = 0; i < split.length; i++) {
c.fillText(split[i], 50, 50 + i * 18);
}
}
};
2014-02-28 20:10:08 -05:00
/**
* Description
2014-06-09 14:40:24 -04:00
* @private
* @method constraints
* @param {constraint[]} constraints
2014-02-28 20:10:08 -05:00
* @param {RenderingContext} context
*/
Render.constraints = function(constraints, context) {
var c = context;
2014-02-19 09:15:05 -05:00
for (var i = 0; i < constraints.length; i++) {
var constraint = constraints[i];
2014-02-19 09:15:05 -05:00
if (!constraint.render.visible || !constraint.pointA || !constraint.pointB)
continue;
2014-02-19 09:15:05 -05:00
var bodyA = constraint.bodyA,
bodyB = constraint.bodyB;
2014-02-19 09:15:05 -05:00
if (bodyA) {
c.beginPath();
c.moveTo(bodyA.position.x + constraint.pointA.x, bodyA.position.y + constraint.pointA.y);
} else {
c.beginPath();
c.moveTo(constraint.pointA.x, constraint.pointA.y);
}
if (bodyB) {
c.lineTo(bodyB.position.x + constraint.pointB.x, bodyB.position.y + constraint.pointB.y);
} else {
c.lineTo(constraint.pointB.x, constraint.pointB.y);
}
c.lineWidth = constraint.render.lineWidth;
c.strokeStyle = constraint.render.strokeStyle;
c.stroke();
}
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
* Description
2014-06-09 14:40:24 -04:00
* @private
* @method bodyShadows
2014-02-28 20:10:08 -05:00
* @param {engine} engine
* @param {body[]} bodies
2014-02-28 20:10:08 -05:00
* @param {RenderingContext} context
*/
Render.bodyShadows = function(engine, bodies, context) {
2014-02-19 09:15:05 -05:00
var c = context,
2015-01-01 18:10:10 -05:00
render = engine.render;
2014-02-19 09:15:05 -05:00
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
if (!body.render.visible)
continue;
if (body.circleRadius) {
c.beginPath();
c.arc(body.position.x, body.position.y, body.circleRadius, 0, 2 * Math.PI);
c.closePath();
} else {
c.beginPath();
c.moveTo(body.vertices[0].x, body.vertices[0].y);
for (var j = 1; j < body.vertices.length; j++) {
c.lineTo(body.vertices[j].x, body.vertices[j].y);
}
c.closePath();
}
2014-02-19 09:15:05 -05:00
var distanceX = body.position.x - render.options.width * 0.5,
distanceY = body.position.y - render.options.height * 0.2,
distance = Math.abs(distanceX) + Math.abs(distanceY);
2014-02-19 09:15:05 -05:00
c.shadowColor = 'rgba(0,0,0,0.15)';
c.shadowOffsetX = 0.05 * distanceX;
c.shadowOffsetY = 0.05 * distanceY;
c.shadowBlur = 1 + 12 * Math.min(1, distance / 1000);
2014-02-19 09:15:05 -05:00
c.fill();
2014-02-19 09:15:05 -05:00
c.shadowColor = null;
c.shadowOffsetX = null;
c.shadowOffsetY = null;
c.shadowBlur = null;
}
2014-02-19 09:15:05 -05:00
};
2014-02-28 20:10:08 -05:00
/**
* Description
2014-06-09 14:40:24 -04:00
* @private
* @method bodies
2014-02-28 20:10:08 -05:00
* @param {engine} engine
* @param {body[]} bodies
2014-02-28 20:10:08 -05:00
* @param {RenderingContext} context
*/
Render.bodies = function(engine, bodies, context) {
2014-02-19 09:15:05 -05:00
var c = context,
render = engine.render,
options = render.options,
2015-05-20 15:38:41 -04:00
body,
part,
2015-07-02 15:17:03 -04:00
i,
k;
2014-02-19 09:15:05 -05:00
for (i = 0; i < bodies.length; i++) {
2015-05-20 15:38:41 -04:00
body = bodies[i];
if (!body.render.visible)
continue;
2015-05-20 15:38:41 -04:00
// handle compound parts
for (k = body.parts.length > 1 ? 1 : 0; k < body.parts.length; k++) {
part = body.parts[k];
2015-05-20 15:38:41 -04:00
if (part.render.sprite && part.render.sprite.texture && !options.wireframes) {
// part sprite
var sprite = part.render.sprite,
texture = _getTexture(render, sprite.texture);
2015-05-20 15:38:41 -04:00
if (options.showSleeping && body.isSleeping)
c.globalAlpha = 0.5;
2015-05-20 15:38:41 -04:00
c.translate(part.position.x, part.position.y);
c.rotate(part.angle);
2015-05-20 15:38:41 -04:00
c.drawImage(texture, texture.width * -0.5 * sprite.xScale, texture.height * -0.5 * sprite.yScale,
texture.width * sprite.xScale, texture.height * sprite.yScale);
2015-05-20 15:38:41 -04:00
// revert translation, hopefully faster than save / restore
c.rotate(-part.angle);
c.translate(-part.position.x, -part.position.y);
if (options.showSleeping && body.isSleeping)
c.globalAlpha = 1;
} else {
2015-05-20 15:38:41 -04:00
// part polygon
if (part.circleRadius) {
c.beginPath();
c.arc(part.position.x, part.position.y, part.circleRadius, 0, 2 * Math.PI);
} else {
c.beginPath();
c.moveTo(part.vertices[0].x, part.vertices[0].y);
for (var j = 1; j < part.vertices.length; j++) {
c.lineTo(part.vertices[j].x, part.vertices[j].y);
}
c.closePath();
}
2015-05-20 15:38:41 -04:00
if (!options.wireframes) {
if (options.showSleeping && body.isSleeping) {
c.fillStyle = Common.shadeColor(part.render.fillStyle, 50);
} else {
c.fillStyle = part.render.fillStyle;
}
c.lineWidth = part.render.lineWidth;
c.strokeStyle = part.render.strokeStyle;
c.fill();
c.stroke();
} else {
2015-05-20 15:38:41 -04:00
c.lineWidth = 1;
c.strokeStyle = '#bbb';
if (options.showSleeping && body.isSleeping)
c.strokeStyle = 'rgba(255,255,255,0.2)';
c.stroke();
}
}
2014-02-19 09:15:05 -05:00
}
}
};
/**
* Optimised method for drawing body wireframes in one pass
2014-06-09 14:40:24 -04:00
* @private
* @method bodyWireframes
* @param {engine} engine
* @param {body[]} bodies
* @param {RenderingContext} context
*/
Render.bodyWireframes = function(engine, bodies, context) {
var c = context,
2015-05-20 15:38:41 -04:00
showInternalEdges = engine.render.options.showInternalEdges,
body,
part,
i,
2015-05-20 15:38:41 -04:00
j,
k;
c.beginPath();
2015-05-20 15:38:41 -04:00
// render all bodies
for (i = 0; i < bodies.length; i++) {
2015-05-20 15:38:41 -04:00
body = bodies[i];
if (!body.render.visible)
continue;
2015-05-20 15:38:41 -04:00
// handle compound parts
for (k = body.parts.length > 1 ? 1 : 0; k < body.parts.length; k++) {
part = body.parts[k];
c.moveTo(part.vertices[0].x, part.vertices[0].y);
for (j = 1; j < part.vertices.length; j++) {
if (!part.vertices[j - 1].isInternal || showInternalEdges) {
c.lineTo(part.vertices[j].x, part.vertices[j].y);
} else {
c.moveTo(part.vertices[j].x, part.vertices[j].y);
}
if (part.vertices[j].isInternal && !showInternalEdges) {
c.moveTo(part.vertices[(j + 1) % part.vertices.length].x, part.vertices[(j + 1) % part.vertices.length].y);
}
}
c.lineTo(part.vertices[0].x, part.vertices[0].y);
}
}
c.lineWidth = 1;
c.strokeStyle = '#bbb';
c.stroke();
};
/**
* Optimised method for drawing body convex hull wireframes in one pass
* @private
* @method bodyConvexHulls
* @param {engine} engine
* @param {body[]} bodies
* @param {RenderingContext} context
*/
Render.bodyConvexHulls = function(engine, bodies, context) {
var c = context,
body,
part,
i,
j,
k;
c.beginPath();
// render convex hulls
for (i = 0; i < bodies.length; i++) {
body = bodies[i];
if (!body.render.visible || body.parts.length === 1)
continue;
2014-02-19 09:15:05 -05:00
c.moveTo(body.vertices[0].x, body.vertices[0].y);
for (j = 1; j < body.vertices.length; j++) {
2014-02-19 09:15:05 -05:00
c.lineTo(body.vertices[j].x, body.vertices[j].y);
}
c.lineTo(body.vertices[0].x, body.vertices[0].y);
2014-02-19 09:15:05 -05:00
}
c.lineWidth = 1;
2015-05-20 15:38:41 -04:00
c.strokeStyle = 'rgba(255,255,255,0.2)';
c.stroke();
};
2015-05-20 15:38:41 -04:00
/**
* Renders body vertex numbers.
* @private
* @method vertexNumbers
* @param {engine} engine
* @param {body[]} bodies
* @param {RenderingContext} context
*/
Render.vertexNumbers = function(engine, bodies, context) {
var c = context,
i,
j,
k;
for (i = 0; i < bodies.length; i++) {
var parts = bodies[i].parts;
for (k = parts.length > 1 ? 1 : 0; k < parts.length; k++) {
var part = parts[k];
for (j = 0; j < part.vertices.length; j++) {
c.fillStyle = 'rgba(255,255,255,0.2)';
c.fillText(i + '_' + j, part.position.x + (part.vertices[j].x - part.position.x) * 0.8, part.position.y + (part.vertices[j].y - part.position.y) * 0.8);
}
}
}
};
/**
* Draws body bounds
2014-06-09 14:40:24 -04:00
* @private
* @method bodyBounds
* @param {engine} engine
* @param {body[]} bodies
* @param {RenderingContext} context
*/
Render.bodyBounds = function(engine, bodies, context) {
var c = context,
render = engine.render,
options = render.options;
c.beginPath();
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
2015-05-20 15:38:41 -04:00
if (body.render.visible) {
var parts = bodies[i].parts;
for (var j = parts.length > 1 ? 1 : 0; j < parts.length; j++) {
var part = parts[j];
c.rect(part.bounds.min.x, part.bounds.min.y, part.bounds.max.x - part.bounds.min.x, part.bounds.max.y - part.bounds.min.y);
}
}
}
if (options.wireframes) {
c.strokeStyle = 'rgba(255,255,255,0.08)';
2014-02-19 09:15:05 -05:00
} else {
c.strokeStyle = 'rgba(0,0,0,0.1)';
2014-02-19 09:15:05 -05:00
}
c.lineWidth = 1;
c.stroke();
};
/**
* Draws body angle indicators and axes
2014-06-09 14:40:24 -04:00
* @private
* @method bodyAxes
* @param {engine} engine
* @param {body[]} bodies
* @param {RenderingContext} context
*/
Render.bodyAxes = function(engine, bodies, context) {
var c = context,
render = engine.render,
options = render.options,
2015-05-20 15:38:41 -04:00
part,
i,
2015-05-20 15:38:41 -04:00
j,
k;
c.beginPath();
for (i = 0; i < bodies.length; i++) {
2015-05-20 15:38:41 -04:00
var body = bodies[i],
parts = body.parts;
if (!body.render.visible)
continue;
if (options.showAxes) {
// render all axes
2015-05-20 15:38:41 -04:00
for (j = parts.length > 1 ? 1 : 0; j < parts.length; j++) {
part = parts[j];
for (k = 0; k < part.axes.length; k++) {
var axis = part.axes[k];
c.moveTo(part.position.x, part.position.y);
c.lineTo(part.position.x + axis.x * 20, part.position.y + axis.y * 20);
}
}
2014-02-19 09:15:05 -05:00
} else {
2015-05-20 15:38:41 -04:00
for (j = parts.length > 1 ? 1 : 0; j < parts.length; j++) {
part = parts[j];
for (k = 0; k < part.axes.length; k++) {
// 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);
}
}
2014-02-19 09:15:05 -05:00
}
}
if (options.wireframes) {
c.strokeStyle = 'indianred';
} else {
c.strokeStyle = 'rgba(0,0,0,0.3)';
}
c.lineWidth = 1;
c.stroke();
};
/**
* Draws body positions
2014-06-09 14:40:24 -04:00
* @private
* @method bodyPositions
* @param {engine} engine
* @param {body[]} bodies
* @param {RenderingContext} context
*/
Render.bodyPositions = function(engine, bodies, context) {
var c = context,
render = engine.render,
options = render.options,
body,
2015-05-20 15:38:41 -04:00
part,
2015-07-02 15:17:03 -04:00
i,
k;
c.beginPath();
// render current positions
for (i = 0; i < bodies.length; i++) {
body = bodies[i];
2015-05-20 15:38:41 -04:00
if (!body.render.visible)
continue;
// handle compound parts
for (k = 0; k < body.parts.length; k++) {
part = body.parts[k];
c.arc(part.position.x, part.position.y, 3, 0, 2 * Math.PI, false);
c.closePath();
2014-02-19 09:15:05 -05:00
}
}
if (options.wireframes) {
c.fillStyle = 'indianred';
} else {
c.fillStyle = 'rgba(0,0,0,0.5)';
}
c.fill();
c.beginPath();
// render previous positions
for (i = 0; i < bodies.length; i++) {
body = bodies[i];
if (body.render.visible) {
c.arc(body.positionPrev.x, body.positionPrev.y, 2, 0, 2 * Math.PI, false);
c.closePath();
2014-02-19 09:15:05 -05:00
}
}
c.fillStyle = 'rgba(255,165,0,0.8)';
c.fill();
};
/**
* Draws body velocity
2014-06-09 14:40:24 -04:00
* @private
* @method bodyVelocity
* @param {engine} engine
* @param {body[]} bodies
* @param {RenderingContext} context
*/
Render.bodyVelocity = function(engine, bodies, context) {
2015-01-01 18:10:10 -05:00
var c = context;
c.beginPath();
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
if (!body.render.visible)
continue;
2014-02-19 09:15:05 -05:00
c.moveTo(body.position.x, body.position.y);
c.lineTo(body.position.x + (body.position.x - body.positionPrev.x) * 2, body.position.y + (body.position.y - body.positionPrev.y) * 2);
}
c.lineWidth = 3;
c.strokeStyle = 'cornflowerblue';
c.stroke();
};
/**
* Draws body ids
2014-06-09 14:40:24 -04:00
* @private
* @method bodyIds
* @param {engine} engine
* @param {body[]} bodies
* @param {RenderingContext} context
*/
Render.bodyIds = function(engine, bodies, context) {
2015-05-20 15:38:41 -04:00
var c = context,
i,
j;
2015-05-20 15:38:41 -04:00
for (i = 0; i < bodies.length; i++) {
if (!bodies[i].render.visible)
continue;
2015-05-20 15:38:41 -04:00
var parts = bodies[i].parts;
for (j = parts.length > 1 ? 1 : 0; j < parts.length; j++) {
var part = parts[j];
c.font = "12px Arial";
c.fillStyle = 'rgba(255,255,255,0.5)';
c.fillText(part.id, part.position.x + 10, part.position.y - 10);
}
2014-02-19 09:15:05 -05:00
}
};
2014-02-28 20:10:08 -05:00
/**
* Description
2014-06-09 14:40:24 -04:00
* @private
* @method collisions
2014-02-28 20:10:08 -05:00
* @param {engine} engine
* @param {pair[]} pairs
2014-02-28 20:10:08 -05:00
* @param {RenderingContext} context
*/
Render.collisions = function(engine, pairs, context) {
2014-02-19 09:15:05 -05:00
var c = context,
options = engine.render.options,
pair,
collision,
2015-05-20 15:38:41 -04:00
corrected,
bodyA,
bodyB,
i,
j;
2014-02-19 09:15:05 -05:00
c.beginPath();
// render collision positions
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
2015-05-20 15:38:41 -04:00
if (!pair.isActive)
continue;
collision = pair.collision;
for (j = 0; j < pair.activeContacts.length; j++) {
var contact = pair.activeContacts[j],
vertex = contact.vertex;
c.rect(vertex.x - 1.5, vertex.y - 1.5, 3.5, 3.5);
2014-02-19 09:15:05 -05:00
}
}
if (options.wireframes) {
c.fillStyle = 'rgba(255,255,255,0.7)';
} else {
c.fillStyle = 'orange';
}
c.fill();
c.beginPath();
2014-02-19 09:15:05 -05:00
// render collision normals
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
2015-05-20 15:38:41 -04:00
if (!pair.isActive)
continue;
collision = pair.collision;
if (pair.activeContacts.length > 0) {
var normalPosX = pair.activeContacts[0].vertex.x,
normalPosY = pair.activeContacts[0].vertex.y;
if (pair.activeContacts.length === 2) {
normalPosX = (pair.activeContacts[0].vertex.x + pair.activeContacts[1].vertex.x) / 2;
normalPosY = (pair.activeContacts[0].vertex.y + pair.activeContacts[1].vertex.y) / 2;
}
2015-05-20 15:38:41 -04:00
if (collision.bodyB === collision.supports[0].body || collision.bodyA.isStatic === true) {
c.moveTo(normalPosX - collision.normal.x * 8, normalPosY - collision.normal.y * 8);
} else {
c.moveTo(normalPosX + collision.normal.x * 8, normalPosY + collision.normal.y * 8);
}
c.lineTo(normalPosX, normalPosY);
2014-02-19 09:15:05 -05:00
}
}
if (options.wireframes) {
c.strokeStyle = 'rgba(255,165,0,0.7)';
} else {
c.strokeStyle = 'orange';
}
c.lineWidth = 1;
c.stroke();
2014-02-19 09:15:05 -05:00
};
2015-05-20 15:38:41 -04:00
/**
* Description
* @private
* @method separations
* @param {engine} engine
* @param {pair[]} pairs
* @param {RenderingContext} context
*/
Render.separations = function(engine, pairs, context) {
var c = context,
options = engine.render.options,
pair,
collision,
corrected,
bodyA,
bodyB,
i,
j;
c.beginPath();
// render separations
for (i = 0; i < pairs.length; i++) {
pair = pairs[i];
if (!pair.isActive)
continue;
collision = pair.collision;
bodyA = collision.bodyA;
bodyB = collision.bodyB;
var k = 1;
if (!bodyB.isStatic && !bodyA.isStatic) k = 0.5;
if (bodyB.isStatic) k = 0;
c.moveTo(bodyB.position.x, bodyB.position.y);
c.lineTo(bodyB.position.x - collision.penetration.x * k, bodyB.position.y - collision.penetration.y * k);
k = 1;
if (!bodyB.isStatic && !bodyA.isStatic) k = 0.5;
if (bodyA.isStatic) k = 0;
c.moveTo(bodyA.position.x, bodyA.position.y);
c.lineTo(bodyA.position.x + collision.penetration.x * k, bodyA.position.y + collision.penetration.y * k);
}
if (options.wireframes) {
c.strokeStyle = 'rgba(255,165,0,0.5)';
} else {
c.strokeStyle = 'orange';
}
c.stroke();
};
2014-02-28 20:10:08 -05:00
/**
* Description
2014-06-09 14:40:24 -04:00
* @private
2014-02-28 20:10:08 -05:00
* @method grid
* @param {engine} engine
* @param {grid} grid
* @param {RenderingContext} context
*/
2014-02-19 09:15:05 -05:00
Render.grid = function(engine, grid, context) {
var c = context,
options = engine.render.options;
if (options.wireframes) {
c.strokeStyle = 'rgba(255,180,0,0.1)';
} else {
c.strokeStyle = 'rgba(255,180,0,0.5)';
}
c.beginPath();
2014-02-19 09:15:05 -05:00
var bucketKeys = Common.keys(grid.buckets);
for (var i = 0; i < bucketKeys.length; i++) {
var bucketId = bucketKeys[i];
if (grid.buckets[bucketId].length < 2)
continue;
var region = bucketId.split(',');
c.rect(0.5 + parseInt(region[0], 10) * grid.bucketWidth,
0.5 + parseInt(region[1], 10) * grid.bucketHeight,
grid.bucketWidth,
grid.bucketHeight);
}
c.lineWidth = 1;
c.stroke();
2014-02-19 09:15:05 -05:00
};
2014-05-01 09:09:06 -04:00
/**
* Description
2014-06-09 14:40:24 -04:00
* @private
2014-05-01 09:09:06 -04:00
* @method inspector
* @param {inspector} inspector
* @param {RenderingContext} context
*/
Render.inspector = function(inspector, context) {
var engine = inspector.engine,
selected = inspector.selected,
render = engine.render,
options = render.options,
bounds;
2014-05-05 14:32:51 -04:00
if (options.hasBounds) {
var boundsWidth = render.bounds.max.x - render.bounds.min.x,
boundsHeight = render.bounds.max.y - render.bounds.min.y,
boundsScaleX = boundsWidth / render.options.width,
boundsScaleY = boundsHeight / render.options.height;
context.scale(1 / boundsScaleX, 1 / boundsScaleY);
2014-05-01 09:09:06 -04:00
context.translate(-render.bounds.min.x, -render.bounds.min.y);
2014-05-05 14:32:51 -04:00
}
2014-05-01 09:09:06 -04:00
for (var i = 0; i < selected.length; i++) {
var item = selected[i].data;
context.translate(0.5, 0.5);
context.lineWidth = 1;
context.strokeStyle = 'rgba(255,165,0,0.9)';
context.setLineDash([1,2]);
switch (item.type) {
case 'body':
// render body selections
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));
context.closePath();
context.stroke();
break;
case 'constraint':
// render constraint selections
var point = item.pointA;
if (item.bodyA)
point = item.pointB;
context.beginPath();
context.arc(point.x, point.y, 10, 0, 2 * Math.PI);
context.closePath();
context.stroke();
break;
}
2015-08-12 19:38:20 -04:00
context.setLineDash([]);
2014-05-01 09:09:06 -04:00
context.translate(-0.5, -0.5);
}
// render selection region
if (inspector.selectStart !== null) {
context.translate(0.5, 0.5);
context.lineWidth = 1;
context.strokeStyle = 'rgba(255,165,0,0.6)';
context.fillStyle = 'rgba(255,165,0,0.1)';
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));
context.closePath();
context.stroke();
context.fill();
context.translate(-0.5, -0.5);
}
if (options.hasBounds)
2014-05-05 14:32:51 -04:00
context.setTransform(1, 0, 0, 1, 0, 0);
2014-05-01 09:09:06 -04:00
};
2014-02-28 20:10:08 -05:00
/**
* Description
* @method _createCanvas
* @private
* @param {} width
* @param {} height
* @return canvas
*/
2014-02-19 09:15:05 -05:00
var _createCanvas = function(width, height) {
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.oncontextmenu = function() { return false; };
canvas.onselectstart = function() { return false; };
return canvas;
};
2014-12-28 13:37:43 -05:00
/**
* Gets the pixel ratio of the canvas.
* @method _getPixelRatio
* @private
* @param {HTMLElement} canvas
* @return {Number} pixel ratio
*/
var _getPixelRatio = function(canvas) {
var context = canvas.getContext('2d'),
devicePixelRatio = window.devicePixelRatio || 1,
backingStorePixelRatio = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio
|| context.msBackingStorePixelRatio || context.oBackingStorePixelRatio
|| context.backingStorePixelRatio || 1;
return devicePixelRatio / backingStorePixelRatio;
};
/**
* Gets the requested texture (an Image) via its path
* @method _getTexture
* @private
* @param {render} render
* @param {string} imagePath
* @return {Image} texture
*/
var _getTexture = function(render, imagePath) {
var image = render.textures[imagePath];
if (image)
return image;
image = render.textures[imagePath] = new Image();
image.src = imagePath;
return image;
};
2015-01-20 19:15:04 -05:00
/**
* Applies the background to the canvas using CSS.
* @method applyBackground
* @private
* @param {render} render
* @param {string} background
*/
var _applyBackground = function(render, background) {
var cssBackground = background;
if (/(jpg|gif|png)$/.test(background))
cssBackground = 'url(' + background + ')';
render.canvas.style.background = cssBackground;
render.canvas.style.backgroundSize = "contain";
render.currentBackground = background;
};
2015-08-12 19:38:20 -04:00
/*
*
* Events Documentation
*
*/
/**
* Fired before rendering
*
* @event beforeRender
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
/**
* Fired after rendering
*
* @event afterRender
* @param {} event An event object
* @param {number} event.timestamp The engine.timing.timestamp of the event
* @param {} event.source The source object of the event
* @param {} event.name The name of the event
*/
2014-06-09 14:40:24 -04:00
/*
*
* Properties Documentation
*
*/
/**
* A back-reference to the `Matter.Render` module.
*
* @property controller
* @type render
*/
/**
* A reference to the element where the canvas is to be inserted (if `render.canvas` has not been specified)
*
* @property element
* @type HTMLElement
* @default null
*/
/**
* The canvas element to render to. If not specified, one will be created if `render.element` has been specified.
*
* @property canvas
* @type HTMLCanvasElement
* @default null
*/
/**
* The configuration options of the renderer.
*
* @property options
* @type {}
*/
/**
* The target width in pixels of the `render.canvas` to be created.
*
* @property options.width
* @type number
* @default 800
*/
/**
* The target height in pixels of the `render.canvas` to be created.
*
* @property options.height
* @type number
* @default 600
*/
/**
* A flag that specifies if `render.bounds` should be used when rendering.
*
* @property options.hasBounds
* @type boolean
* @default false
*/
/**
* A `Bounds` object that specifies the drawing view region.
* Rendering will be automatically transformed and scaled to fit within the canvas size (`render.options.width` and `render.options.height`).
* This allows for creating views that can pan or zoom around the scene.
* You must also set `render.options.hasBounds` to `true` to enable bounded rendering.
*
* @property bounds
* @type bounds
*/
/**
* The 2d rendering context from the `render.canvas` element.
*
* @property context
* @type CanvasRenderingContext2D
*/
/**
* The sprite texture cache.
*
* @property textures
* @type {}
*/
2014-02-19 09:15:05 -05:00
})();
2014-12-28 13:37:43 -05:00
2014-02-19 09:15:05 -05:00
; // End src/render/Render.js
// Begin src/render/RenderPixi.js
/**
* See [Demo.js](https://github.com/liabru/matter-js/blob/master/demo/js/Demo.js)
* and [DemoMobile.js](https://github.com/liabru/matter-js/blob/master/demo/js/DemoMobile.js) for usage examples.
*
* @class RenderPixi
*/
var RenderPixi = {};
(function() {
/**
* Creates a new Pixi.js WebGL renderer
* @method create
* @param {object} options
* @return {RenderPixi} A new renderer
*/
RenderPixi.create = function(options) {
var defaults = {
controller: RenderPixi,
element: null,
canvas: null,
options: {
width: 800,
height: 600,
background: '#fafafa',
wireframeBackground: '#222',
2014-07-29 11:26:49 -04:00
hasBounds: false,
enabled: true,
wireframes: true,
showSleeping: true,
showDebug: false,
showBroadphase: false,
showBounds: false,
showVelocity: false,
showCollisions: false,
showAxes: false,
showPositions: false,
showAngleIndicator: false,
showIds: false,
showShadows: false
}
};
2014-07-29 11:26:49 -04:00
var render = Common.extend(defaults, options),
transparent = !render.options.wireframes && render.options.background === 'transparent';
// init pixi
2015-05-21 19:33:26 -04:00
render.context = new PIXI.WebGLRenderer(render.options.width, render.options.height, {
view: render.canvas,
transparent: transparent,
antialias: true,
backgroundColor: options.background
});
render.canvas = render.context.view;
2015-05-21 19:33:26 -04:00
render.container = new PIXI.Container();
2014-07-29 11:26:49 -04:00
render.bounds = render.bounds || {
min: {
x: 0,
y: 0
},
max: {
x: render.options.width,
y: render.options.height
}
};
// caches
render.textures = {};
render.sprites = {};
render.primitives = {};
// use a sprite batch for performance
2015-05-21 19:33:26 -04:00
render.spriteContainer = new PIXI.Container();
render.container.addChild(render.spriteContainer);
// insert canvas
if (Common.isElement(render.element)) {
render.element.appendChild(render.canvas);
} else {
Common.log('No "render.element" passed, "render.canvas" was not inserted into document.', 'warn');
}
2014-03-24 16:11:42 -04:00
// prevent menus on canvas
render.canvas.oncontextmenu = function() { return false; };
render.canvas.onselectstart = function() { return false; };
return render;
};
/**
* Clears the scene graph
* @method clear
* @param {RenderPixi} render
*/
RenderPixi.clear = function(render) {
2014-07-29 11:26:49 -04:00
var container = render.container,
2015-05-21 19:33:26 -04:00
spriteContainer = render.spriteContainer;
2014-07-29 11:26:49 -04:00
// clear stage container
while (container.children[0]) {
container.removeChild(container.children[0]);
}
// clear sprite batch
2015-05-21 19:33:26 -04:00
while (spriteContainer.children[0]) {
spriteContainer.removeChild(spriteContainer.children[0]);
}
var bgSprite = render.sprites['bg-0'];
// clear caches
render.textures = {};
render.sprites = {};
render.primitives = {};
// set background sprite
render.sprites['bg-0'] = bgSprite;
if (bgSprite)
2015-05-21 19:33:26 -04:00
container.addChildAt(bgSprite, 0);
2014-07-29 11:26:49 -04:00
// add sprite batch back into container
2015-05-21 19:33:26 -04:00
render.container.addChild(render.spriteContainer);
// reset background state
render.currentBackground = null;
2014-07-29 11:26:49 -04:00
// reset bounds transforms
container.scale.set(1, 1);
container.position.set(0, 0);
};
/**
* Sets the background of the canvas
* @method setBackground
* @param {RenderPixi} render
* @param {string} background
*/
RenderPixi.setBackground = function(render, background) {
if (render.currentBackground !== background) {
var isColor = background.indexOf && background.indexOf('#') !== -1,
bgSprite = render.sprites['bg-0'];
if (isColor) {
// if solid background color
var color = Common.colorToNumber(background);
2015-05-21 19:33:26 -04:00
render.context.backgroundColor = color;
// remove background sprite if existing
if (bgSprite)
2015-05-21 19:33:26 -04:00
render.container.removeChild(bgSprite);
} else {
// initialise background sprite if needed
if (!bgSprite) {
var texture = _getTexture(render, background);
bgSprite = render.sprites['bg-0'] = new PIXI.Sprite(texture);
bgSprite.position.x = 0;
bgSprite.position.y = 0;
2015-05-21 19:33:26 -04:00
render.container.addChildAt(bgSprite, 0);
}
}
render.currentBackground = background;
}
};
/**
* Description
* @method world
* @param {engine} engine
*/
RenderPixi.world = function(engine) {
var render = engine.render,
world = engine.world,
context = render.context,
2014-07-29 11:26:49 -04:00
container = render.container,
options = render.options,
2014-03-24 16:11:42 -04:00
bodies = Composite.allBodies(world),
2014-07-29 11:26:49 -04:00
allConstraints = Composite.allConstraints(world),
constraints = [],
i;
if (options.wireframes) {
RenderPixi.setBackground(render, options.wireframeBackground);
} else {
RenderPixi.setBackground(render, options.background);
}
2014-07-29 11:26:49 -04:00
// handle bounds
var boundsWidth = render.bounds.max.x - render.bounds.min.x,
boundsHeight = render.bounds.max.y - render.bounds.min.y,
boundsScaleX = boundsWidth / render.options.width,
boundsScaleY = boundsHeight / render.options.height;
if (options.hasBounds) {
// Hide bodies that are not in view
for (i = 0; i < bodies.length; i++) {
var body = bodies[i];
body.render.sprite.visible = Bounds.overlaps(body.bounds, render.bounds);
}
// filter out constraints that are not in view
for (i = 0; i < allConstraints.length; i++) {
var constraint = allConstraints[i],
bodyA = constraint.bodyA,
bodyB = constraint.bodyB,
pointAWorld = constraint.pointA,
pointBWorld = constraint.pointB;
if (bodyA) pointAWorld = Vector.add(bodyA.position, constraint.pointA);
if (bodyB) pointBWorld = Vector.add(bodyB.position, constraint.pointB);
if (!pointAWorld || !pointBWorld)
continue;
if (Bounds.contains(render.bounds, pointAWorld) || Bounds.contains(render.bounds, pointBWorld))
constraints.push(constraint);
}
// transform the view
container.scale.set(1 / boundsScaleX, 1 / boundsScaleY);
container.position.set(-render.bounds.min.x * (1 / boundsScaleX), -render.bounds.min.y * (1 / boundsScaleY));
} else {
constraints = allConstraints;
}
2014-03-24 16:11:42 -04:00
for (i = 0; i < bodies.length; i++)
RenderPixi.body(engine, bodies[i]);
2014-03-24 16:11:42 -04:00
for (i = 0; i < constraints.length; i++)
RenderPixi.constraint(engine, constraints[i]);
2015-05-21 19:33:26 -04:00
context.render(container);
};
/**
* Description
* @method constraint
* @param {engine} engine
* @param {constraint} constraint
*/
RenderPixi.constraint = function(engine, constraint) {
var render = engine.render,
bodyA = constraint.bodyA,
bodyB = constraint.bodyB,
pointA = constraint.pointA,
pointB = constraint.pointB,
2014-07-29 11:26:49 -04:00
container = render.container,
constraintRender = constraint.render,
primitiveId = 'c-' + constraint.id,
primitive = render.primitives[primitiveId];
// initialise constraint primitive if not existing
if (!primitive)
primitive = render.primitives[primitiveId] = new PIXI.Graphics();
// don't render if constraint does not have two end points
if (!constraintRender.visible || !constraint.pointA || !constraint.pointB) {
primitive.clear();
return;
}
// add to scene graph if not already there
2014-07-29 11:26:49 -04:00
if (Common.indexOf(container.children, primitive) === -1)
container.addChild(primitive);
// render the constraint on every update, since they can change dynamically
primitive.clear();
primitive.beginFill(0, 0);
primitive.lineStyle(constraintRender.lineWidth, Common.colorToNumber(constraintRender.strokeStyle), 1);
if (bodyA) {
primitive.moveTo(bodyA.position.x + pointA.x, bodyA.position.y + pointA.y);
} else {
primitive.moveTo(pointA.x, pointA.y);
}
if (bodyB) {
primitive.lineTo(bodyB.position.x + pointB.x, bodyB.position.y + pointB.y);
} else {
primitive.lineTo(pointB.x, pointB.y);
}
primitive.endFill();
};
/**
* Description
* @method body
* @param {engine} engine
* @param {body} body
*/
RenderPixi.body = function(engine, body) {
var render = engine.render,
bodyRender = body.render;
if (!bodyRender.visible)
return;
2014-03-30 14:45:30 -04:00
if (bodyRender.sprite && bodyRender.sprite.texture) {
var spriteId = 'b-' + body.id,
sprite = render.sprites[spriteId],
2015-05-21 19:33:26 -04:00
spriteContainer = render.spriteContainer;
// initialise body sprite if not existing
if (!sprite)
sprite = render.sprites[spriteId] = _createBodySprite(render, body);
// add to scene graph if not already there
2015-05-21 19:33:26 -04:00
if (Common.indexOf(spriteContainer.children, sprite) === -1)
spriteContainer.addChild(sprite);
// update body sprite
sprite.position.x = body.position.x;
sprite.position.y = body.position.y;
sprite.rotation = body.angle;
2014-12-28 13:37:43 -05:00
sprite.scale.x = bodyRender.sprite.xScale || 1;
sprite.scale.y = bodyRender.sprite.yScale || 1;
} else {
var primitiveId = 'b-' + body.id,
primitive = render.primitives[primitiveId],
2014-07-29 11:26:49 -04:00
container = render.container;
// initialise body primitive if not existing
if (!primitive) {
primitive = render.primitives[primitiveId] = _createBodyPrimitive(render, body);
primitive.initialAngle = body.angle;
}
// add to scene graph if not already there
2014-07-29 11:26:49 -04:00
if (Common.indexOf(container.children, primitive) === -1)
container.addChild(primitive);
// update body primitive
primitive.position.x = body.position.x;
primitive.position.y = body.position.y;
primitive.rotation = body.angle - primitive.initialAngle;
}
};
/**
* Creates a body sprite
* @method _createBodySprite
* @private
* @param {RenderPixi} render
* @param {body} body
* @return {PIXI.Sprite} sprite
*/
var _createBodySprite = function(render, body) {
var bodyRender = body.render,
texturePath = bodyRender.sprite.texture,
texture = _getTexture(render, texturePath),
sprite = new PIXI.Sprite(texture);
sprite.anchor.x = 0.5;
sprite.anchor.y = 0.5;
return sprite;
};
/**
* Creates a body primitive
* @method _createBodyPrimitive
* @private
* @param {RenderPixi} render
* @param {body} body
* @return {PIXI.Graphics} graphics
*/
var _createBodyPrimitive = function(render, body) {
var bodyRender = body.render,
options = render.options,
2015-05-20 15:38:41 -04:00
primitive = new PIXI.Graphics(),
fillStyle = Common.colorToNumber(bodyRender.fillStyle),
strokeStyle = Common.colorToNumber(bodyRender.strokeStyle),
strokeStyleIndicator = Common.colorToNumber(bodyRender.strokeStyle),
strokeStyleWireframe = Common.colorToNumber('#bbb'),
strokeStyleWireframeIndicator = Common.colorToNumber('#CD5C5C'),
part;
primitive.clear();
2015-05-20 15:38:41 -04:00
// handle compound parts
for (var k = body.parts.length > 1 ? 1 : 0; k < body.parts.length; k++) {
part = body.parts[k];
if (!options.wireframes) {
primitive.beginFill(fillStyle, 1);
primitive.lineStyle(bodyRender.lineWidth, strokeStyle, 1);
} else {
primitive.beginFill(0, 0);
primitive.lineStyle(1, strokeStyleWireframe, 1);
}
2015-05-20 15:38:41 -04:00
primitive.moveTo(part.vertices[0].x - body.position.x, part.vertices[0].y - body.position.y);
2015-05-20 15:38:41 -04:00
for (var j = 1; j < part.vertices.length; j++) {
primitive.lineTo(part.vertices[j].x - body.position.x, part.vertices[j].y - body.position.y);
}
2015-05-20 15:38:41 -04:00
primitive.lineTo(part.vertices[0].x - body.position.x, part.vertices[0].y - body.position.y);
2015-05-20 15:38:41 -04:00
primitive.endFill();
2015-05-20 15:38:41 -04:00
// angle indicator
if (options.showAngleIndicator || options.showAxes) {
primitive.beginFill(0, 0);
2015-05-20 15:38:41 -04:00
if (options.wireframes) {
primitive.lineStyle(1, strokeStyleWireframeIndicator, 1);
} else {
primitive.lineStyle(1, strokeStyleIndicator);
}
2015-05-20 15:38:41 -04:00
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));
2015-05-20 15:38:41 -04:00
primitive.endFill();
}
}
return primitive;
};
/**
* Gets the requested texture (a PIXI.Texture) via its path
* @method _getTexture
* @private
* @param {RenderPixi} render
* @param {string} imagePath
* @return {PIXI.Texture} texture
*/
var _getTexture = function(render, imagePath) {
var texture = render.textures[imagePath];
if (!texture)
texture = render.textures[imagePath] = PIXI.Texture.fromImage(imagePath);
return texture;
};
})();
2014-12-28 13:37:43 -05:00
; // End src/render/RenderPixi.js
2014-02-19 09:15:05 -05:00
// aliases
2014-03-30 14:45:30 -04:00
World.add = Composite.add;
World.remove = Composite.remove;
2014-03-24 16:11:42 -04:00
World.addComposite = Composite.addComposite;
2014-02-19 09:15:05 -05:00
World.addBody = Composite.addBody;
World.addConstraint = Composite.addConstraint;
2014-03-24 16:11:42 -04:00
World.clear = Composite.clear;
2014-02-19 09:15:05 -05:00
2014-07-29 11:26:49 -04:00
Engine.run = Runner.run;
2014-02-19 09:15:05 -05:00
// exports
Matter.Body = Body;
Matter.Composite = Composite;
Matter.World = World;
Matter.Contact = Contact;
Matter.Detector = Detector;
Matter.Grid = Grid;
2014-03-24 16:11:42 -04:00
Matter.Pairs = Pairs;
2014-02-19 09:15:05 -05:00
Matter.Pair = Pair;
Matter.Resolver = Resolver;
Matter.SAT = SAT;
Matter.Constraint = Constraint;
Matter.MouseConstraint = MouseConstraint;
Matter.Common = Common;
Matter.Engine = Engine;
Matter.Mouse = Mouse;
Matter.Sleeping = Sleeping;
Matter.Bodies = Bodies;
Matter.Composites = Composites;
Matter.Axes = Axes;
Matter.Bounds = Bounds;
Matter.Vector = Vector;
Matter.Vertices = Vertices;
Matter.Render = Render;
Matter.RenderPixi = RenderPixi;
Matter.Events = Events;
2014-05-01 09:09:06 -04:00
Matter.Query = Query;
2014-07-29 11:26:49 -04:00
Matter.Runner = Runner;
2015-05-20 15:38:41 -04:00
Matter.Svg = Svg;
2014-02-19 09:15:05 -05:00
// CommonJS module
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = Matter;
}
exports.Matter = Matter;
}
// AMD module
if (typeof define === 'function' && define.amd) {
define('Matter', [], function () {
return Matter;
});
}
// browser
if (typeof window === 'object' && typeof window.document === 'object') {
window.Matter = Matter;
}
// End Matter namespace closure
})();