/**
* A composite is a collection of `Matter.Body`, `Matter.Constraint` and other `Matter.Composite` objects.
*
* They are a container that can represent complex objects made of multiple parts, even if they are not physically connected.
* A composite could contain anything from a single body all the way up to a whole world.
*
* When making any changes to composites, use the included functions rather than changing their properties directly.
*
* See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples).
*
* @class Composite
*/
var Composite = {};
module.exports = Composite;
var Events = require('../core/Events');
var Common = require('../core/Common');
var Bounds = require('../geometry/Bounds');
var Body = require('./Body');
(function() {
/**
* 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.
* @method create
* @param {} [options]
* @return {composite} A new composite
*/
Composite.create = function(options) {
return Common.extend({
id: Common.nextId(),
type: 'composite',
parent: null,
isModified: false,
bodies: [],
constraints: [],
composites: [],
label: 'Composite',
plugin: {},
cache: {
allBodies: null,
allConstraints: null,
allComposites: null
}
}, 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).
* @private
* @method setModified
* @param {composite} composite
* @param {boolean} isModified
* @param {boolean} [updateParents=false]
* @param {boolean} [updateChildren=false]
*/
Composite.setModified = function(composite, isModified, updateParents, updateChildren) {
composite.isModified = isModified;
if (isModified && composite.cache) {
composite.cache.allBodies = null;
composite.cache.allConstraints = null;
composite.cache.allComposites = null;
}
if (updateParents && composite.parent) {
Composite.setModified(composite.parent, isModified, updateParents, updateChildren);
}
if (updateChildren) {
for (var i = 0; i < composite.composites.length; i++) {
var childComposite = composite.composites[i];
Composite.setModified(childComposite, isModified, updateParents, updateChildren);
}
}
};
/**
* Generic single or multi-add function. Adds a single or an array of body(s), constraint(s) or composite(s) to the given composite.
* Triggers `beforeAdd` and `afterAdd` events on the `composite`.
* @method add
* @param {composite} composite
* @param {object|array} object A single or an array of body(s), constraint(s) or composite(s)
* @return {composite} The original composite with the objects added
*/
Composite.add = function(composite, object) {
var objects = [].concat(object);
Events.trigger(composite, 'beforeAdd', { object: object });
for (var i = 0; i < objects.length; i++) {
var obj = objects[i];
switch (obj.type) {
case 'body':
// skip adding compound parts
if (obj.parent !== obj) {
Common.warn('Composite.add: skipped adding a compound body part (you must add its parent instead)');
break;
}
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;
}
}
Events.trigger(composite, 'afterAdd', { object: object });
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.
* Triggers `beforeRemove` and `afterRemove` events on the `composite`.
* @method remove
* @param {composite} composite
* @param {object|array} object
* @param {boolean} [deep=false]
* @return {composite} The original composite with the objects removed
*/
Composite.remove = function(composite, object, deep) {
var objects = [].concat(object);
Events.trigger(composite, 'beforeRemove', { object: object });
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;
}
}
Events.trigger(composite, 'afterRemove', { object: object });
return composite;
};
/**
* Adds a composite to the given composite.
* @private
* @method addComposite
* @param {composite} compositeA
* @param {composite} compositeB
* @return {composite} The original compositeA with the objects from compositeB added
*/
Composite.addComposite = function(compositeA, compositeB) {
compositeA.composites.push(compositeB);
compositeB.parent = compositeA;
Composite.setModified(compositeA, true, true, false);
return compositeA;
};
/**
* Removes a composite from the given composite, and optionally searching its children recursively.
* @private
* @method removeComposite
* @param {composite} compositeA
* @param {composite} compositeB
* @param {boolean} [deep=false]
* @return {composite} The original compositeA with the composite removed
*/
Composite.removeComposite = function(compositeA, compositeB, deep) {
var position = Common.indexOf(compositeA.composites, compositeB);
if (position !== -1) {
var bodies = Composite.allBodies(compositeB);
Composite.removeCompositeAt(compositeA, position);
for (var i = 0; i < bodies.length; i++) {
bodies[i].sleepCounter = 0;
}
}
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.
* @private
* @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;
};
/**
* Adds a body to the given composite.
* @private
* @method addBody
* @param {composite} composite
* @param {body} body
* @return {composite} The original composite with the body added
*/
Composite.addBody = function(composite, body) {
composite.bodies.push(body);
Composite.setModified(composite, true, true, false);
return composite;
};
/**
* Removes a body from the given composite, and optionally searching its children recursively.
* @private
* @method removeBody
* @param {composite} composite
* @param {body} body
* @param {boolean} [deep=false]
* @return {composite} The original composite with the body removed
*/
Composite.removeBody = function(composite, body, deep) {
var position = Common.indexOf(composite.bodies, body);
if (position !== -1) {
Composite.removeBodyAt(composite, position);
body.sleepCounter = 0;
}
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.
* @private
* @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);
return composite;
};
/**
* Adds a constraint to the given composite.
* @private
* @method addConstraint
* @param {composite} composite
* @param {constraint} constraint
* @return {composite} The original composite with the constraint added
*/
Composite.addConstraint = function(composite, constraint) {
composite.constraints.push(constraint);
Composite.setModified(composite, true, true, false);
return composite;
};
/**
* Removes a constraint from the given composite, and optionally searching its children recursively.
* @private
* @method removeConstraint
* @param {composite} composite
* @param {constraint} constraint
* @param {boolean} [deep=false]
* @return {composite} The original composite with the constraint removed
*/
Composite.removeConstraint = function(composite, constraint, deep) {
var position = Common.indexOf(composite.constraints, constraint);
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.
* @private
* @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
* @param {composite} composite
* @param {boolean} keepStatic
* @param {boolean} [deep=false]
*/
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) {
if (composite.cache && composite.cache.allBodies) {
return composite.cache.allBodies;
}
var bodies = [].concat(composite.bodies);
for (var i = 0; i < composite.composites.length; i++)
bodies = bodies.concat(Composite.allBodies(composite.composites[i]));
if (composite.cache) {
composite.cache.allBodies = bodies;
}
return bodies;
};
/**
* 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) {
if (composite.cache && composite.cache.allConstraints) {
return composite.cache.allConstraints;
}
var constraints = [].concat(composite.constraints);
for (var i = 0; i < composite.composites.length; i++)
constraints = constraints.concat(Composite.allConstraints(composite.composites[i]));
if (composite.cache) {
composite.cache.allConstraints = constraints;
}
return constraints;
};
/**
* 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) {
if (composite.cache && composite.cache.allComposites) {
return composite.cache.allComposites;
}
var composites = [].concat(composite.composites);
for (var i = 0; i < composite.composites.length; i++)
composites = composites.concat(Composite.allComposites(composite.composites[i]));
if (composite.cache) {
composite.cache.allComposites = composites;
}
return composites;
};
/**
* 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;
};
/**
* 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();
}
return composite;
};
/**
* 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);
}
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);
}
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);
}
return composite;
};
/**
* Returns the union of the bounds of all of the composite's bodies.
* @method bounds
* @param {composite} composite The composite.
* @returns {bounds} The composite bounds.
*/
Composite.bounds = function(composite) {
var bodies = Composite.allBodies(composite),
vertices = [];
for (var i = 0; i < bodies.length; i += 1) {
var body = bodies[i];
vertices.push(body.bounds.min, body.bounds.max);
}
return Bounds.create(vertices);
};
/*
*
* 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
*/
/*
*
* 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"
* @readOnly
*/
/**
* 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.
* This is automatically managed when bodies, constraints or composites are added or removed.
*
* @property isModified
* @type boolean
* @default false
*/
/**
* The `Composite` that is the parent of this composite. It is automatically managed by the `Matter.Composite` methods.
*
* @property parent
* @type composite
* @default null
*/
/**
* 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 []
*/
/**
* 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 []
*/
/**
* An object reserved for storing plugin-specific properties.
*
* @property plugin
* @type {}
*/
/**
* An object used for storing cached results for performance reasons.
* This is used internally only and is automatically managed.
*
* @private
* @property cache
* @type {}
*/
})();