/** * @author Richard Davey * @copyright 2020 Photon Storm Ltd. * @license {@link https://opensource.org/licenses/MIT|MIT License} */ var Bounds = require('./lib/geometry/Bounds'); var Class = require('../../utils/Class'); var Composite = require('./lib/body/Composite'); var Constraint = require('./lib/constraint/Constraint'); var Detector = require('./lib/collision/Detector'); var Events = require('./events'); var InputEvents = require('../../input/events'); var Merge = require('../../utils/object/Merge'); var Sleeping = require('./lib/core/Sleeping'); var Vector2 = require('../../math/Vector2'); var Vertices = require('./lib/geometry/Vertices'); /** * @classdesc * A Pointer Constraint is a special type of constraint that allows you to click * and drag bodies in a Matter World. It monitors the active Pointers in a Scene, * and when one is pressed down it checks to see if that hit any part of any active * body in the world. If it did, and the body has input enabled, it will begin to * drag it until either released, or you stop it via the `stopDrag` method. * * You can adjust the stiffness, length and other properties of the constraint via * the `options` object on creation. * * @class PointerConstraint * @memberof Phaser.Physics.Matter * @constructor * @since 3.0.0 * * @param {Phaser.Scene} scene - A reference to the Scene to which this Pointer Constraint belongs. * @param {Phaser.Physics.Matter.World} world - A reference to the Matter World instance to which this Constraint belongs. * @param {object} [options] - A Constraint configuration object. */ var PointerConstraint = new Class({ initialize: function PointerConstraint (scene, world, options) { if (options === undefined) { options = {}; } // Defaults var defaults = { label: 'Pointer Constraint', pointA: { x: 0, y: 0 }, pointB: { x: 0, y: 0 }, length: 0.01, stiffness: 0.1, angularStiffness: 1, collisionFilter: { category: 0x0001, mask: 0xFFFFFFFF, group: 0 } }; /** * A reference to the Scene to which this Pointer Constraint belongs. * This is the same Scene as the Matter World instance. * * @name Phaser.Physics.Matter.PointerConstraint#scene * @type {Phaser.Scene} * @since 3.0.0 */ this.scene = scene; /** * A reference to the Matter World instance to which this Constraint belongs. * * @name Phaser.Physics.Matter.PointerConstraint#world * @type {Phaser.Physics.Matter.World} * @since 3.0.0 */ this.world = world; /** * The Camera the Pointer was interacting with when the input * down event was processed. * * @name Phaser.Physics.Matter.PointerConstraint#camera * @type {Phaser.Cameras.Scene2D.Camera} * @since 3.0.0 */ this.camera = null; /** * A reference to the Input Pointer that activated this Constraint. * This is set in the `onDown` handler. * * @name Phaser.Physics.Matter.PointerConstraint#pointer * @type {Phaser.Input.Pointer} * @default null * @since 3.0.0 */ this.pointer = null; /** * Is this Constraint active or not? * * An active constraint will be processed each update. An inactive one will be skipped. * Use this to toggle a Pointer Constraint on and off. * * @name Phaser.Physics.Matter.PointerConstraint#active * @type {boolean} * @default true * @since 3.0.0 */ this.active = true; /** * The internal transformed position. * * @name Phaser.Physics.Matter.PointerConstraint#position * @type {Phaser.Math.Vector2} * @since 3.0.0 */ this.position = new Vector2(); /** * The body that is currently being dragged, if any. * * @name Phaser.Physics.Matter.PointerConstraint#body * @type {?MatterJS.BodyType} * @since 3.16.2 */ this.body = null; /** * The part of the body that was clicked on to start the drag. * * @name Phaser.Physics.Matter.PointerConstraint#part * @type {?MatterJS.BodyType} * @since 3.16.2 */ this.part = null; /** * The native Matter Constraint that is used to attach to bodies. * * @name Phaser.Physics.Matter.PointerConstraint#constraint * @type {MatterJS.ConstraintType} * @since 3.0.0 */ this.constraint = Constraint.create(Merge(options, defaults)); this.world.on(Events.BEFORE_UPDATE, this.update, this); scene.sys.input.on(InputEvents.POINTER_DOWN, this.onDown, this); scene.sys.input.on(InputEvents.POINTER_UP, this.onUp, this); }, /** * A Pointer has been pressed down onto the Scene. * * If this Constraint doesn't have an active Pointer then a hit test is set to * run against all active bodies in the world during the _next_ call to `update`. * If a body is found, it is bound to this constraint and the drag begins. * * @method Phaser.Physics.Matter.PointerConstraint#onDown * @since 3.0.0 * * @param {Phaser.Input.Pointer} pointer - A reference to the Pointer that was pressed. */ onDown: function (pointer) { if (!this.pointer) { this.pointer = pointer; this.camera = pointer.camera; } }, /** * A Pointer has been released from the Scene. If it was the one this constraint was using, it's cleared. * * @method Phaser.Physics.Matter.PointerConstraint#onUp * @since 3.22.0 * * @param {Phaser.Input.Pointer} pointer - A reference to the Pointer that was pressed. */ onUp: function (pointer) { if (pointer === this.pointer) { this.pointer = null; } }, /** * Scans all active bodies in the current Matter World to see if any of them * are hit by the Pointer. The _first one_ found to hit is set as the active contraint * body. * * @method Phaser.Physics.Matter.PointerConstraint#getBody * @fires Phaser.Physics.Matter.Events#DRAG_START * @since 3.16.2 * * @return {boolean} `true` if a body was found and set, otherwise `false`. */ getBody: function (pointer) { var pos = this.position; var constraint = this.constraint; this.camera.getWorldPoint(pointer.x, pointer.y, pos); var bodies = Composite.allBodies(this.world.localWorld); for (var i = 0; i < bodies.length; i++) { var body = bodies[i]; if (!body.ignorePointer && Bounds.contains(body.bounds, pos) && Detector.canCollide(body.collisionFilter, constraint.collisionFilter)) { if (this.hitTestBody(body, pos)) { this.world.emit(Events.DRAG_START, body, this.part, this); return true; } } } return false; }, /** * Scans the current body to determine if a part of it was clicked on. * If a part is found the body is set as the `constraint.bodyB` property, * as well as the `body` property of this class. The part is also set. * * @method Phaser.Physics.Matter.PointerConstraint#hitTestBody * @since 3.16.2 * * @param {MatterJS.BodyType} body - The Matter Body to check. * @param {Phaser.Math.Vector2} position - A translated hit test position. * * @return {boolean} `true` if a part of the body was hit, otherwise `false`. */ hitTestBody: function (body, position) { var constraint = this.constraint; var partsLength = body.parts.length; var start = (partsLength > 1) ? 1 : 0; for (var i = start; i < partsLength; i++) { var part = body.parts[i]; if (Vertices.contains(part.vertices, position)) { constraint.pointA = position; constraint.pointB = { x: position.x - body.position.x, y: position.y - body.position.y }; constraint.bodyB = body; constraint.angleB = body.angle; Sleeping.set(body, false); this.part = part; this.body = body; return true; } } return false; }, /** * Internal update handler. Called in the Matter BEFORE_UPDATE step. * * @method Phaser.Physics.Matter.PointerConstraint#update * @fires Phaser.Physics.Matter.Events#DRAG * @since 3.0.0 */ update: function () { var pointer = this.pointer; var body = this.body; if (!this.active || !pointer) { if (body) { this.stopDrag(); } return; } if (!pointer.isDown && body) { this.stopDrag(); return; } else if (pointer.isDown) { if (!body && !this.getBody(pointer)) { return; } body = this.body; var pos = this.position; var constraint = this.constraint; this.camera.getWorldPoint(pointer.x, pointer.y, pos); // Drag update constraint.pointA.x = pos.x; constraint.pointA.y = pos.y; Sleeping.set(body, false); this.world.emit(Events.DRAG, body, this); } }, /** * Stops the Pointer Constraint from dragging the body any further. * * This is called automatically if the Pointer is released while actively * dragging a body. Or, you can call it manually to release a body from a * constraint without having to first release the pointer. * * @method Phaser.Physics.Matter.PointerConstraint#stopDrag * @fires Phaser.Physics.Matter.Events#DRAG_END * @since 3.16.2 */ stopDrag: function () { var body = this.body; var constraint = this.constraint; constraint.bodyB = null; constraint.pointB = null; this.pointer = null; this.body = null; this.part = null; if (body) { this.world.emit(Events.DRAG_END, body, this); } }, /** * Destroys this Pointer Constraint instance and all of its references. * * @method Phaser.Physics.Matter.PointerConstraint#destroy * @since 3.0.0 */ destroy: function () { this.world.removeConstraint(this.constraint); this.pointer = null; this.constraint = null; this.body = null; this.part = null; this.world.off(Events.BEFORE_UPDATE, this.update); this.scene.sys.input.off(InputEvents.POINTER_DOWN, this.onDown, this); this.scene.sys.input.off(InputEvents.POINTER_UP, this.onUp, this); } }); module.exports = PointerConstraint;