UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.

760 lines (650 loc) 25.8 kB
/** * 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`. * * 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: {}, wrapBounds: null, 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). * @method setModified * @param {composite} composite * @param {boolean} isModified * @param {boolean} [updateParents=false] * @param {boolean} [updateChildren=false] */ Composite.setModified = function(composite, isModified, updateParents, updateChildren) { Events.trigger(composite, 'compositeModified', composite); 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); }; /** * Wraps the `composite` position such that it always stays within the given bounds. * Upon crossing a boundary the composite will appear on the opposite side of the bounds, * while maintaining its velocity. * This is called automatically by the plugin. * @function wrap * @param {composite} composite The composite to wrap. * @param {bounds} bounds The bounds to wrap the composite inside. * @returns {?Matter.Vector} The translation vector that was applied (only if wrapping was required). */ Composite.wrap = function(composite, bounds) { var translation = Bounds.wrap( Composite.bounds(composite), bounds ); if (translation) { Composite.translate(composite, translation); } return translation; }; /* * * 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. * 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 */ /** * 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 storing axis-aligned bounding boxes (AABB). * * @property wrapBounds * @type {} */ /** * An object used for storing cached results for performance reasons. * This is used internally only and is automatically managed. * * @private * @property cache * @type {} */ })();