From e2141c91a6359672ac2ef22afe3f046089ffac4a Mon Sep 17 00:00:00 2001 From: Richard Davey Date: Tue, 21 May 2013 04:12:54 +0100 Subject: [PATCH] Added VerletManager and lots of new tests --- Docs/phaser_rotate4.png | Bin 0 -> 87529 bytes Phaser/Game.ts | 10 + Phaser/GameMath.ts | 39 ++ Phaser/Group.ts | 104 +++- Phaser/Phaser.csproj | 28 ++ Phaser/Stage.ts | 1 + Phaser/VerletManager.ts | 401 +++++++++++++++ Phaser/gameobjects/GameObject.ts | 1 - Phaser/geom/Point.ts | 26 + Phaser/geom/Quad.ts | 2 +- Phaser/geom/Vector2.ts | 151 ++++++ Phaser/verlet/AngleConstraint.ts | 74 +++ Phaser/verlet/Composite.ts | 78 +++ Phaser/verlet/DistanceConstraint.ts | 69 +++ Phaser/verlet/Particle.ts | 44 ++ Phaser/verlet/PinConstraint.ts | 48 ++ README.md | 5 + Tests/Tests.csproj | 28 ++ Tests/geometry/multi rotate.js | 31 ++ Tests/geometry/multi rotate.ts | 49 ++ Tests/geometry/rotate point 1.js | 21 + Tests/geometry/rotate point 1.ts | 36 ++ Tests/geometry/rotate point 2.js | 43 ++ Tests/geometry/rotate point 2.ts | 62 +++ Tests/geometry/rotate point 3.js | 71 +++ Tests/geometry/rotate point 3.ts | 95 ++++ Tests/geometry/rotate point 4.js | 54 ++ Tests/geometry/rotate point 4.ts | 82 +++ Tests/geometry/verlet 1.js | 30 ++ Tests/geometry/verlet 1.ts | 39 ++ Tests/geometry/verlet sprites.js | 49 ++ Tests/geometry/verlet sprites.ts | 75 +++ Tests/phaser.js | 742 +++++++++++++++++++++++++++- Tests/scrollzones/blasteroids.js | 3 + Tests/scrollzones/blasteroids.ts | 6 + build/phaser.d.ts | 302 ++++++++++- build/phaser.js | 742 +++++++++++++++++++++++++++- 37 files changed, 3612 insertions(+), 29 deletions(-) create mode 100644 Docs/phaser_rotate4.png create mode 100644 Phaser/VerletManager.ts create mode 100644 Phaser/geom/Vector2.ts create mode 100644 Phaser/verlet/AngleConstraint.ts create mode 100644 Phaser/verlet/Composite.ts create mode 100644 Phaser/verlet/DistanceConstraint.ts create mode 100644 Phaser/verlet/Particle.ts create mode 100644 Phaser/verlet/PinConstraint.ts create mode 100644 Tests/geometry/multi rotate.js create mode 100644 Tests/geometry/multi rotate.ts create mode 100644 Tests/geometry/rotate point 1.js create mode 100644 Tests/geometry/rotate point 1.ts create mode 100644 Tests/geometry/rotate point 2.js create mode 100644 Tests/geometry/rotate point 2.ts create mode 100644 Tests/geometry/rotate point 3.js create mode 100644 Tests/geometry/rotate point 3.ts create mode 100644 Tests/geometry/rotate point 4.js create mode 100644 Tests/geometry/rotate point 4.ts create mode 100644 Tests/geometry/verlet 1.js create mode 100644 Tests/geometry/verlet 1.ts create mode 100644 Tests/geometry/verlet sprites.js create mode 100644 Tests/geometry/verlet sprites.ts diff --git a/Docs/phaser_rotate4.png b/Docs/phaser_rotate4.png new file mode 100644 index 0000000000000000000000000000000000000000..fee589a0408e44dc2b6719cd16e198520c041bd3 GIT binary patch literal 87529 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 literal 0 HcmV?d00001 diff --git a/Phaser/Game.ts b/Phaser/Game.ts index 81bb3eba..76c1ec1d 100644 --- a/Phaser/Game.ts +++ b/Phaser/Game.ts @@ -15,7 +15,9 @@ /// /// /// +/// /// +/// /// /// /// @@ -230,6 +232,12 @@ module Phaser { */ public tweens: TweenManager; + /** + * Reference to the verlet manager. + * @type {VerletManager} + */ + public verlet: Phaser.Verlet.VerletManager; + /** * Reference to the world. * @type {World} @@ -292,6 +300,7 @@ module Phaser { this.tweens = new TweenManager(this); this.input = new Input(this); this.rnd = new RandomDataGenerator([(Date.now() * Math.random()).toString()]); + this.verlet = new Phaser.Verlet.VerletManager(this, width, height); this.framerate = 60; this.isBooted = true; @@ -368,6 +377,7 @@ module Phaser { this.tweens.update(); this.input.update(); this.stage.update(); + this.verlet.update(); this._accumulator += this.time.delta; diff --git a/Phaser/GameMath.ts b/Phaser/GameMath.ts index 3a20f2d9..8138252b 100644 --- a/Phaser/GameMath.ts +++ b/Phaser/GameMath.ts @@ -1022,6 +1022,45 @@ module Phaser { } + /** + * Returns the distance from this Point object to the given Point object. + * @method distanceFrom + * @param {Point} target - The destination Point object. + * @param {Boolean} round - Round the distance to the nearest integer (default false) + * @return {Number} The distance between this Point object and the destination Point object. + **/ + public static distanceBetween(x1: number, y1: number, x2: number, y2: number): number { + + var dx = x1 - x2; + var dy = y1 - y2; + + return Math.sqrt(dx * dx + dy * dy); + + } + + /** + * Rotates a point around the x/y coordinates given to the desired angle + * @param x {number} The x coordinate of the anchor point + * @param y {number} The y coordinate of the anchor point + * @param angle {number} The angle of the rotation in radians + * @param point {Point} The point object to perform the rotation on + * @return The modified point object + */ + public rotatePoint(x: number, y: number, angle: number, point) { + + var s: number = Math.sin(angle); + var c: number = Math.cos(angle); + + point.x -= x; + point.y -= y; + + var newX: number = point.x * c - point.y * s; + var newY: number = point.x * s - point.y * c; + + return point.setTo(newX + x, newY + y); + + } + } } \ No newline at end of file diff --git a/Phaser/Group.ts b/Phaser/Group.ts index ce76aca9..f830cbd4 100644 --- a/Phaser/Group.ts +++ b/Phaser/Group.ts @@ -21,9 +21,31 @@ module Phaser { this._maxSize = MaxSize; this._marker = 0; this._sortIndex = null; + this.cameraBlacklist = []; } + /** + * Internal tracker for the maximum capacity of the group. + * Default is 0, or no max capacity. + */ + private _maxSize: number; + + /** + * Internal helper variable for recycling objects a la Emitter. + */ + private _marker: number; + + /** + * Helper for sort. + */ + private _sortIndex: string; + + /** + * Helper for sort. + */ + private _sortOrder: number; + /** * Use with sort() to sort in ascending order. */ @@ -47,25 +69,62 @@ module Phaser { public length: number; /** - * Internal tracker for the maximum capacity of the group. - * Default is 0, or no max capacity. + * You can set a globalCompositeOperation that will be applied before the render method is called on this Groups children. + * This is useful if you wish to apply an effect like 'lighten' to a whole group of children as it saves doing it one-by-one. + * If this value is set it will call a canvas context save and restore before and after the render pass. + * Set to null to disable. */ - private _maxSize: number; + public globalCompositeOperation: string = null; /** - * Internal helper variable for recycling objects a la Emitter. + * You can set an alpha value on this Group that will be applied before the render method is called on this Groups children. + * This is useful if you wish to alpha a whole group of children as it saves doing it one-by-one. + * Set to 0 to disable. */ - private _marker: number; + public alpha: number = 0; /** - * Helper for sort. + * An Array of Cameras to which this Group, or any of its children, won't render + * @type {Array} */ - private _sortIndex: string; + public cameraBlacklist: number[]; /** - * Helper for sort. + * If you do not wish this object to be visible to a specific camera, pass the camera here. + * + * @param camera {Camera} The specific camera. + */ + public hideFromCamera(camera: Camera) { + + if (this.cameraBlacklist.indexOf(camera.ID) == -1) + { + this.cameraBlacklist.push(camera.ID); + } + + } + + /** + * Make this object only visible to a specific camera. + * + * @param camera {Camera} The camera you wish it to be visible. + */ + public showToCamera(camera: Camera) { + + if (this.cameraBlacklist.indexOf(camera.ID) !== -1) + { + this.cameraBlacklist.slice(this.cameraBlacklist.indexOf(camera.ID), 1); + } + + } + + /** + * This clears the camera black list, making the GameObject visible to all cameras. */ - private _sortOrder: number; + public clearCameraList() { + + this.cameraBlacklist.length = 0; + + } /** * Override this function to handle any deleting or "shutdown" type operations you might need, @@ -126,11 +185,28 @@ module Phaser { */ public render(camera: Camera, cameraOffsetX: number, cameraOffsetY: number, forceRender?: bool = false) { + if (this.cameraBlacklist.indexOf(camera.ID) !== -1) + { + return; + } + if (this.ignoreGlobalRender && forceRender == false) { return; } + if (this.globalCompositeOperation) + { + this._game.stage.context.save(); + this._game.stage.context.globalCompositeOperation = this.globalCompositeOperation; + } + + if (this.alpha > 0) + { + var prevAlpha: number = this._game.stage.context.globalAlpha; + this._game.stage.context.globalAlpha = this.alpha; + } + var basic: Basic; var i: number = 0; @@ -143,6 +219,16 @@ module Phaser { basic.render(camera, cameraOffsetX, cameraOffsetY, forceRender); } } + + if (this.alpha > 0) + { + this._game.stage.context.globalAlpha = prevAlpha; + } + + if (this.globalCompositeOperation) + { + this._game.stage.context.restore(); + } } /** diff --git a/Phaser/Phaser.csproj b/Phaser/Phaser.csproj index d627d288..4bce4c50 100644 --- a/Phaser/Phaser.csproj +++ b/Phaser/Phaser.csproj @@ -105,9 +105,25 @@ Tilemap.ts + + + + VerletManager.ts + + + AngleConstraint.ts + Circle.ts + + + + Composite.ts + + + DistanceConstraint.ts + IntersectResult.ts @@ -118,6 +134,14 @@ MicroPoint.ts + + + + Particle.ts + + + PinConstraint.ts + Point.ts @@ -128,6 +152,10 @@ Rectangle.ts + + + Vector2.ts + SoundManager.ts diff --git a/Phaser/Stage.ts b/Phaser/Stage.ts index d2e57b8a..24bb9b3e 100644 --- a/Phaser/Stage.ts +++ b/Phaser/Stage.ts @@ -47,6 +47,7 @@ module Phaser { this.canvas.style['ms-touch-action'] = 'none'; this.canvas.style['touch-action'] = 'none'; this.canvas.style.backgroundColor = 'rgb(0,0,0)'; + this.canvas.oncontextmenu = function(event) { event.preventDefault(); }; this.context = this.canvas.getContext('2d'); diff --git a/Phaser/VerletManager.ts b/Phaser/VerletManager.ts new file mode 100644 index 00000000..c9a60b69 --- /dev/null +++ b/Phaser/VerletManager.ts @@ -0,0 +1,401 @@ +/// +/// +/// +/// +/// +/// +/// + +/** +* Phaser - Verlet +* +* Based on verlet-js by Sub Protocol released under MIT +*/ + +module Phaser.Verlet { + + export class VerletManager { + + /** + * Creates a new Vector2 object. + * @class Vector2 + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Vector2} This object + **/ + constructor(game: Game, width: number, height: number) { + + this._game = game; + this.width = width; + this.height = height; + this.gravity = new Vector2(0, 0.2); + this.friction = 0.99; + this.groundFriction = 0.8; + + this.canvas = game.stage.canvas; + this.context = game.stage.context; + + this._game.input.onDown.add(this.mouseDownHandler, this); + this._game.input.onUp.add(this.mouseUpHandler, this); + + } + + private _game: Game; + + public composites = []; + + public width: number; + public height: number; + public step: number = 32; + public gravity: Vector2; + public friction: number; + public groundFriction: number; + public selectionRadius: number = 20; + public draggedEntity = null; + public highlightColor = '#4f545c'; + + + /** + * This class is actually a wrapper of canvas. + * @type {HTMLCanvasElement} + */ + public canvas: HTMLCanvasElement; + + /** + * Canvas context of this object. + * @type {CanvasRenderingContext2D} + */ + public context: CanvasRenderingContext2D; + + /** + * Computes time of intersection of a particle with a wall + * + * @param {Vec2} line wall's root position + * @param {Vec2} p particle's position + * @param {Vec2} dir walls's direction + * @param {Vec2} v particle's velocity + */ + public intersectionTime(wall, p, dir, v) { + + if (dir.x != 0) + { + var denominator = v.y - dir.y * v.x / dir.x; + if (denominator == 0) return undefined; // Movement is parallel to wall + var numerator = wall.y + dir.y * (p.x - wall.x) / dir.x - p.y; + return numerator / denominator; + } + else + { + if (v.x == 0) return undefined; // parallel again + var denominator = v.x; + var numerator = wall.x - p.x; + return numerator / denominator; + } + + } + + public intersectionPoint(wall, p, dir, v) { + var t = this.intersectionTime(wall, p, dir, v); + return new Phaser.Vector2(p.x + v.x * t, p.y + v.y * t); + } + + private v = new Phaser.Vector2(); + + public bounds(particle: Phaser.Verlet.Particle) { + + this.v.mutableSet(particle.pos); + this.v.mutableSub(particle.lastPos); + + if (particle.pos.y > this.height - 1) + { + particle.pos.mutableSet( + this.intersectionPoint(new Phaser.Vector2(0, this.height - 1), particle.lastPos, new Phaser.Vector2(1, 0), this.v)); + } + + if (particle.pos.x < 0) + { + particle.pos.mutableSet( + this.intersectionPoint(new Phaser.Vector2(0, 0), particle.pos, new Phaser.Vector2(0, 1), this.v)); + } + + if (particle.pos.x > this.width - 1) + { + particle.pos.mutableSet( + this.intersectionPoint(new Phaser.Vector2(this.width - 1, 0), particle.pos, new Phaser.Vector2(0, 1), this.v)); + } + } + + public OLDbounds(particle: Phaser.Verlet.Particle) { + + if (particle.pos.y > this.height - 1) + particle.pos.y = this.height - 1; + + if (particle.pos.x < 0) + { + var vx = particle.pos.x - particle.lastPos.x; + var vy = particle.pos.y - particle.lastPos.y; + + if (vx == 0) + { + particle.pos.x = 0; + } + else + { + var t = -particle.lastPos.x / vx; + particle.pos.x = particle.lastPos.x + t * vx; + particle.pos.y = particle.lastPos.y + t * vy; + } + } + + if (particle.pos.x > this.width - 1) + particle.pos.x = this.width - 1; + + } + + public createPoint(pos: Vector2) { + + var composite = new Phaser.Verlet.Composite(this._game); + composite.particles.push(new Phaser.Verlet.Particle(pos)); + this.composites.push(composite); + return composite; + + } + + public createLineSegments(vertices, stiffness) { + + var i; + var composite = new Phaser.Verlet.Composite(this._game); + + for (i in vertices) + { + composite.particles.push(new Phaser.Verlet.Particle(vertices[i])); + if (i > 0) + composite.constraints.push(new Phaser.Verlet.DistanceConstraint(composite.particles[i], composite.particles[i - 1], stiffness)); + } + + this.composites.push(composite); + return composite; + + } + + public createCloth(origin, width, height, segments, pinMod, stiffness) { + + var composite = new Phaser.Verlet.Composite(this._game); + + var xStride = width / segments; + var yStride = height / segments; + + var x, y; + for (y = 0; y < segments; ++y) + { + for (x = 0; x < segments; ++x) + { + var px = origin.x + x * xStride - width / 2 + xStride / 2; + var py = origin.y + y * yStride - height / 2 + yStride / 2; + composite.particles.push(new Phaser.Verlet.Particle(new Vector2(px, py))); + + if (x > 0) + composite.constraints.push(new Phaser.Verlet.DistanceConstraint(composite.particles[y * segments + x], composite.particles[y * segments + x - 1], stiffness)); + + if (y > 0) + composite.constraints.push(new Phaser.Verlet.DistanceConstraint(composite.particles[y * segments + x], composite.particles[(y - 1) * segments + x], stiffness)); + } + } + + for (x = 0; x < segments; ++x) + { + if (x % pinMod == 0) + composite.pin(x); + } + + this.composites.push(composite); + return composite; + + } + + public createTire(origin, radius, segments, spokeStiffness, treadStiffness) { + + var stride = (2 * Math.PI) / segments; + var i; + + var composite = new Phaser.Verlet.Composite(this._game); + + // particles + for (i = 0; i < segments; ++i) + { + var theta = i * stride; + composite.particles.push(new Particle(new Vector2(origin.x + Math.cos(theta) * radius, origin.y + Math.sin(theta) * radius))); + } + + var center = new Particle(origin); + composite.particles.push(center); + + // constraints + for (i = 0; i < segments; ++i) + { + composite.constraints.push(new DistanceConstraint(composite.particles[i], composite.particles[(i + 1) % segments], treadStiffness)); + composite.constraints.push(new DistanceConstraint(composite.particles[i], center, spokeStiffness)) + composite.constraints.push(new DistanceConstraint(composite.particles[i], composite.particles[(i + 5) % segments], treadStiffness)); + } + + this.composites.push(composite); + + return composite; + } + + public update() { + + if (this.composites.length == 0) + { + return; + } + + var i, j, c; + + for (c in this.composites) + { + for (i in this.composites[c].particles) + { + var particles = this.composites[c].particles; + + // calculate velocity + var velocity = particles[i].pos.sub(particles[i].lastPos).scale(this.friction); + + // ground friction + if (particles[i].pos.y >= this.height - 1 && velocity.length2() > 0.000001) + { + var m = velocity.length(); + velocity.x /= m; + velocity.y /= m; + velocity.mutableScale(m * this.groundFriction); + } + + // save last good state + particles[i].lastPos.mutableSet(particles[i].pos); + + // gravity + particles[i].pos.mutableAdd(this.gravity); + + // inertia + particles[i].pos.mutableAdd(velocity); + } + } + + // handle dragging of entities + if (this.draggedEntity) + this.draggedEntity.pos.mutableSet(new Vector2(this._game.input.x, this._game.input.y)); + + // relax + var stepCoef = 1 / this.step; + + for (c in this.composites) + { + var constraints = this.composites[c].constraints; + for (i = 0; i < this.step; ++i) + for (j in constraints) + constraints[j].relax(stepCoef); + } + + // bounds checking + for (c in this.composites) + { + var particles = this.composites[c].particles; + for (i in particles) + this.bounds(particles[i]); + } + + } + + private mouseDownHandler() { + + var nearest = this.nearestEntity(); + + if (nearest) + { + this.draggedEntity = nearest; + } + } + + private mouseUpHandler() { + this.draggedEntity = null; + } + + public nearestEntity() { + + var c, i; + var d2Nearest = 0; + var entity = null; + var constraintsNearest = null; + + // find nearest point + for (c in this.composites) + { + var particles = this.composites[c].particles; + + for (i in particles) + { + var d2 = particles[i].pos.dist2(new Vector2(this._game.input.x, this._game.input.y)); + + if (d2 <= this.selectionRadius * this.selectionRadius && (entity == null || d2 < d2Nearest)) + { + entity = particles[i]; + constraintsNearest = this.composites[c].constraints; + d2Nearest = d2; + } + } + } + + // search for pinned constraints for this entity + for (i in constraintsNearest) + if (constraintsNearest[i] instanceof PinConstraint && constraintsNearest[i].a == entity) + entity = constraintsNearest[i]; + + return entity; + + } + + public render() { + + var i, c; + + for (c in this.composites) + { + // draw constraints + if (this.composites[c].drawConstraints) + { + this.composites[c].drawConstraints(this.context, this.composites[c]); + } else + { + var constraints = this.composites[c].constraints; + for (i in constraints) + constraints[i].render(this.context); + } + + // draw particles + if (this.composites[c].drawParticles) + { + this.composites[c].drawParticles(this.context, this.composites[c]); + } else + { + var particles = this.composites[c].particles; + for (i in particles) + particles[i].render(this.context); + } + } + + // highlight nearest / dragged entity + var nearest = this.draggedEntity || this.nearestEntity(); + + if (nearest) + { + this.context.beginPath(); + this.context.arc(nearest.pos.x, nearest.pos.y, 8, 0, 2 * Math.PI); + this.context.strokeStyle = this.highlightColor; + this.context.stroke(); + } + } + + } + +} diff --git a/Phaser/gameobjects/GameObject.ts b/Phaser/gameobjects/GameObject.ts index c1d211a8..9ddf6559 100644 --- a/Phaser/gameobjects/GameObject.ts +++ b/Phaser/gameobjects/GameObject.ts @@ -41,7 +41,6 @@ module Phaser { this.scale = new MicroPoint(1, 1); this.last = new MicroPoint(x, y); - //this.origin = new MicroPoint(this.frameBounds.halfWidth, this.frameBounds.halfHeight); this.align = GameObject.ALIGN_TOP_LEFT; this.mass = 1; this.elasticity = 0; diff --git a/Phaser/geom/Point.ts b/Phaser/geom/Point.ts index e697b369..68b12cd7 100644 --- a/Phaser/geom/Point.ts +++ b/Phaser/geom/Point.ts @@ -296,6 +296,32 @@ module Phaser { } + /** + * Rotates the point around the x/y coordinates given to the desired angle + * @param x {number} The x coordinate of the anchor point + * @param y {number} The y coordinate of the anchor point + * @param {Number} angle The angle in radians (unless asDegrees is true) to return the point from. + * @param {Boolean} asDegrees Is the given angle in radians (false) or degrees (true)? + * @param {Number} distance An optional distance constraint between the point and the anchor + * @return The modified point object + */ + public rotate(cx: number, cy: number, angle: number, asDegrees: bool = false, distance?:number = null) { + + if (asDegrees) + { + angle = angle * GameMath.DEG_TO_RAD; + } + + // Get distance from origin (cx/cy) to this point + if (distance === null) + { + distance = Math.sqrt(((cx - this.x) * (cx - this.x)) + ((cy - this.y) * (cy - this.y))); + } + + return this.setTo(cx + distance * Math.cos(angle), cy + distance * Math.sin(angle)); + + } + /** * Sets the x and y values of this Point object to the given coordinates. * @method setTo diff --git a/Phaser/geom/Quad.ts b/Phaser/geom/Quad.ts index a920f238..d6cb7aa0 100644 --- a/Phaser/geom/Quad.ts +++ b/Phaser/geom/Quad.ts @@ -19,7 +19,7 @@ module Phaser { * @param {Number} y The y coordinate of the top-left corner of the quad. * @param {Number} width The width of the quad. * @param {Number} height The height of the quad. - * @return {Quad } This object + * @return {Quad} This object **/ constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) { diff --git a/Phaser/geom/Vector2.ts b/Phaser/geom/Vector2.ts new file mode 100644 index 00000000..7ea3d62e --- /dev/null +++ b/Phaser/geom/Vector2.ts @@ -0,0 +1,151 @@ +/// + +/** +* Phaser - Vector2 +* +* A simple 2-dimensional vector class. Based on the one included with verlet-js by Sub Protocol released under MIT +*/ + +module Phaser { + + export class Vector2 { + + /** + * Creates a new Vector2 object. + * @class Vector2 + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Vector2} This object + **/ + constructor(x: number = 0, y: number = 0) { + + this.x = x; + this.y = y; + + } + + public x: number; + public y: number; + + public setTo(x: number, y: number): Vector2 { + this.x = x; + this.y = y; + return this; + } + + public add(v: Vector2, output?:Vector2 = new Vector2): Vector2 { + return output.setTo(this.x + v.x, this.y + v.y); + } + + public sub(v: Vector2, output?:Vector2 = new Vector2): Vector2 { + return output.setTo(this.x - v.x, this.y - v.y); + } + + public mul(v: Vector2, output?:Vector2 = new Vector2): Vector2 { + return output.setTo(this.x * v.x, this.y * v.y); + } + + public div(v: Vector2, output?:Vector2 = new Vector2): Vector2 { + return output.setTo(this.x / v.x, this.y / v.y); + } + + public scale(coef: number, output?:Vector2 = new Vector2): Vector2 { + return output.setTo(this.x * coef, this.y * coef); + } + + public mutableSet(v: Vector2): Vector2 { + this.x = v.x; + this.y = v.y; + return this; + } + + public mutableAdd(v: Vector2): Vector2 { + this.x += v.x; + this.y += v.y; + return this; + } + + public mutableSub(v: Vector2): Vector2 { + this.x -= v.x; + this.y -= v.y; + return this; + } + + public mutableMul(v: Vector2): Vector2 { + this.x *= v.x; + this.y *= v.y; + return this; + } + + public mutableDiv(v: Vector2): Vector2 { + this.x /= v.x; + this.y /= v.y; + return this; + } + + public mutableScale(coef: number): Vector2 { + this.x *= coef; + this.y *= coef; + return this; + } + + public equals(v: Vector2): bool { + return this.x == v.x && this.y == v.y; + } + + public epsilonEquals(v: Vector2, epsilon:number): bool { + return Math.abs(this.x - v.x) <= epsilon && Math.abs(this.y - v.y) <= epsilon; + } + + public length(): number { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + + public length2(): number { + return this.x * this.x + this.y * this.y; + } + + public dist(v: Vector2): number { + return Math.sqrt(this.dist2(v)); + } + + public dist2(v: Vector2): number { + return ((v.x - this.x) * (v.x - this.x)) + ((v.y - this.y) * (v.y - this.y)); + } + + public normal(output?: Vector2 = new Vector2) { + var m = Math.sqrt(this.x * this.x + this.y * this.y); + return output.setTo(this.x / m, this.y / m); + } + + public dot(v: Vector2): number { + return this.x * v.x + this.y * v.y; + } + + public angle(v: Vector2): number { + return Math.atan2(this.x * v.y - this.y * v.x, this.x * v.x + this.y * v.y); + } + + public angle2(vLeft: Vector2, vRight: Vector2): number { + return vLeft.sub(this).angle(vRight.sub(this)); + } + + public rotate(origin, theta, output?: Vector2 = new Vector2): Vector2 { + var x = this.x - origin.x; + var y = this.y - origin.y; + return output.setTo(x * Math.cos(theta) - y * Math.sin(theta) + origin.x, x * Math.sin(theta) + y * Math.cos(theta) + origin.y); + } + + /** + * Returns a string representation of this object. + * @method toString + * @return {string} a string representation of the object. + **/ + public toString(): string { + return "[{Vector2 (x=" + this.x + " y=" + this.y + ")}]"; + } + + } + +} \ No newline at end of file diff --git a/Phaser/verlet/AngleConstraint.ts b/Phaser/verlet/AngleConstraint.ts new file mode 100644 index 00000000..3b090b56 --- /dev/null +++ b/Phaser/verlet/AngleConstraint.ts @@ -0,0 +1,74 @@ +/// +/// +/// + +/** +* Phaser - AngleConstraint +* +* constrains 3 particles to an angle +*/ + +module Phaser.Verlet { + + export class AngleConstraint { + + /** + * Creates a new AngleConstraint object. + * @class AngleConstraint + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {AngleConstraint} This object + **/ + constructor(a: Phaser.Verlet.Particle, b: Phaser.Verlet.Particle, c: Phaser.Verlet.Particle, stiffness: number) { + + this.a = a; + this.b = b; + this.c = c; + this.angle = this.b.pos.angle2(this.a.pos, this.c.pos); + this.stiffness = stiffness; + + } + + public a: Phaser.Verlet.Particle; + public b: Phaser.Verlet.Particle; + public c: Phaser.Verlet.Particle; + public angle: number; + public stiffness: number; + + public relax(stepCoef: number) { + + var angle = this.b.pos.angle2(this.a.pos, this.c.pos); + var diff = angle - this.angle; + + if (diff <= -Math.PI) + diff += 2 * Math.PI; + else if (diff >= Math.PI) + diff -= 2 * Math.PI; + + diff *= stepCoef * this.stiffness; + + this.a.pos = this.a.pos.rotate(this.b.pos, diff); + this.c.pos = this.c.pos.rotate(this.b.pos, -diff); + this.b.pos = this.b.pos.rotate(this.a.pos, diff); + this.b.pos = this.b.pos.rotate(this.c.pos, -diff); + + } + + public render(ctx) { + + ctx.beginPath(); + ctx.moveTo(this.a.pos.x, this.a.pos.y); + ctx.lineTo(this.b.pos.x, this.b.pos.y); + ctx.lineTo(this.c.pos.x, this.c.pos.y); + var tmp = ctx.lineWidth; + ctx.lineWidth = 5; + ctx.strokeStyle = "rgba(255,255,0,0.2)"; + ctx.stroke(); + ctx.lineWidth = tmp; + + } + + } + +} \ No newline at end of file diff --git a/Phaser/verlet/Composite.ts b/Phaser/verlet/Composite.ts new file mode 100644 index 00000000..19055cfa --- /dev/null +++ b/Phaser/verlet/Composite.ts @@ -0,0 +1,78 @@ +/// +/// +/// +/// + +/** +* Phaser - Verlet - Composite +* +* +*/ + +module Phaser.Verlet { + + export class Composite { + + /** + * Creates a new Composite object. + * @class Composite + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Composite} This object + **/ + constructor(game: Game) { + + this._game = game; + + this.particles = []; + this.constraints = []; + + } + + private _game: Game; + + public particles: Phaser.Verlet.Particle[]; + public constraints; + public drawParticles = null; + public drawConstraints = null; + + // Map sprites to particles + + public createDistanceConstraint(a: Phaser.Verlet.Particle, b: Phaser.Verlet.Particle, stiffness: number, distance?: number = null): Phaser.Verlet.DistanceConstraint { + + this.constraints.push(new Phaser.Verlet.DistanceConstraint(a, b, stiffness, distance)); + return this.constraints[this.constraints.length - 1]; + + } + + public createAngleConstraint(a: Phaser.Verlet.Particle, b: Phaser.Verlet.Particle, c: Phaser.Verlet.Particle, stiffness: number): Phaser.Verlet.AngleConstraint { + + this.constraints.push(new Phaser.Verlet.AngleConstraint(a, b, c, stiffness)); + return this.constraints[this.constraints.length - 1]; + + } + + public createPinConstraint(a: Phaser.Verlet.Particle, pos: Vector2): Phaser.Verlet.PinConstraint { + + this.constraints.push(new Phaser.Verlet.PinConstraint(a, pos)); + return this.constraints[this.constraints.length - 1]; + + } + + public pin(index, pos?=null) { + + if (pos == null) + { + pos = this.particles[index].pos; + } + + var pc = new Phaser.Verlet.PinConstraint(this.particles[index], pos); + this.constraints.push(pc); + return pc; + + } + + } + +} diff --git a/Phaser/verlet/DistanceConstraint.ts b/Phaser/verlet/DistanceConstraint.ts new file mode 100644 index 00000000..f2aa0eec --- /dev/null +++ b/Phaser/verlet/DistanceConstraint.ts @@ -0,0 +1,69 @@ +/// +/// +/// + +/** +* Phaser - DistanceConstraint +* +* Constrains to initial distance +*/ + +module Phaser.Verlet { + + export class DistanceConstraint { + + /** + * Creates a new DistanceConstraint object. + * @class DistanceConstraint + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {DistanceConstraint} This object + **/ + constructor(a: Phaser.Verlet.Particle, b: Phaser.Verlet.Particle, stiffness: number, distance?:number = null) { + + this.a = a; + this.b = b; + + if (distance === null) + { + this.distance = a.pos.sub(b.pos).length(); + } + else + { + this.distance = distance; + } + + this.stiffness = stiffness; + + } + + public a: Phaser.Verlet.Particle; + public b: Phaser.Verlet.Particle; + public distance: number; + public stiffness: number; + + public relax(stepCoef: number) { + + var normal = this.a.pos.sub(this.b.pos); + + var m = normal.length2(); + + normal.mutableScale(((this.distance * this.distance - m) / m) * this.stiffness * stepCoef); + + this.a.pos.mutableAdd(normal); + this.b.pos.mutableSub(normal); + + } + + public render(ctx) { + ctx.beginPath(); + ctx.moveTo(this.a.pos.x, this.a.pos.y); + ctx.lineTo(this.b.pos.x, this.b.pos.y); + ctx.strokeStyle = "#d8dde2"; + ctx.stroke(); + } + + } + +} \ No newline at end of file diff --git a/Phaser/verlet/Particle.ts b/Phaser/verlet/Particle.ts new file mode 100644 index 00000000..a4accdf3 --- /dev/null +++ b/Phaser/verlet/Particle.ts @@ -0,0 +1,44 @@ +/// +/// + +/** +* Phaser - Verlet - Particle +* +* +*/ + +module Phaser.Verlet { + + export class Particle { + + /** + * Creates a new Particle object. + * @class Particle + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Particle} This object + **/ + constructor(pos: Vector2) { + + this.pos = (new Vector2()).mutableSet(pos); + this.lastPos = (new Vector2()).mutableSet(pos); + + + } + + public pos: Vector2; + public lastPos: Vector2; + + public render(ctx) { + + ctx.beginPath(); + ctx.arc(this.pos.x, this.pos.y, 2, 0, 2*Math.PI); + ctx.fillStyle = "#2dad8f"; + ctx.fill(); + + } + + } + +} diff --git a/Phaser/verlet/PinConstraint.ts b/Phaser/verlet/PinConstraint.ts new file mode 100644 index 00000000..191df71f --- /dev/null +++ b/Phaser/verlet/PinConstraint.ts @@ -0,0 +1,48 @@ +/// +/// +/// + +/** +* Phaser - PinConstraint +* +* Constrains to static / fixed point +*/ + +module Phaser.Verlet { + + export class PinConstraint { + + /** + * Creates a new PinConstraint object. + * @class PinConstraint + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {PinConstraint} This object + **/ + constructor(a: Phaser.Verlet.Particle, pos: Vector2) { + + this.a = a; + this.pos = (new Vector2()).mutableSet(pos); + + } + + public a: Phaser.Verlet.Particle; + public pos: Vector2; + + public relax() { + this.a.pos.mutableSet(this.pos); + } + + public render(ctx) { + + ctx.beginPath(); + ctx.arc(this.pos.x, this.pos.y, 6, 0, 2*Math.PI); + ctx.fillStyle = "rgba(0,153,255,0.1)"; + ctx.fill(); + + } + + } + +} \ No newline at end of file diff --git a/README.md b/README.md index 4e4536c8..ba4131d6 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,11 @@ V0.9.6 * Updated QuadTree to use the new CollisionMask values and significantly optimised and reduced overall class size * Updated Collision.seperate to use the new CollisionMask * Added a callback context parameter to Game.collide, Collision.overlap and the QuadTree class +* Stage.canvas now calls preventDefault() when the context menu is activated (oncontextmenu) +* Added Point.rotate to allow you to rotate a point around another point, with optional distance clamping. Also created test cases. +* Added Group.alpha to apply a globalAlpha before the groups children are rendered. Useful to save on alpha calls. +* Added Group.globalCompositeOperation to apply a composite operation before all of the groups children are rendered. +* Added Camera black list support to Group along with Group.showToCamera, Group.hideFromCamera and Group.clearCameraList diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 781e5b73..c6dd907f 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -125,6 +125,34 @@ mask test 2.ts + + + multi rotate.ts + + + + + rotate point 1.ts + + + rotate point 2.ts + + + + rotate point 3.ts + + + + + rotate point 4.ts + + + verlet 1.ts + + + + verlet sprites.ts + display order.ts diff --git a/Tests/geometry/multi rotate.js b/Tests/geometry/multi rotate.js new file mode 100644 index 00000000..f00d76c4 --- /dev/null +++ b/Tests/geometry/multi rotate.js @@ -0,0 +1,31 @@ +/// +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, null, create, update, render); + var p1; + var p2; + var p3; + var p4; + var d = 0; + function create() { + p1 = new Phaser.Point(myGame.stage.centerX, myGame.stage.centerY); + p2 = new Phaser.Point(p1.x - 50, p1.y - 50); + p3 = new Phaser.Point(p1.x - 100, p1.y - 100); + p4 = new Phaser.Point(p1.x - 150, p1.y - 150); + } + function update() { + p2.rotate(p1.x, p1.y, myGame.math.wrapAngle(d), true); + p3.rotate(p1.x, p1.y, myGame.math.wrapAngle(d), true); + p4.rotate(p1.x, p1.y, myGame.math.wrapAngle(d), true); + d++; + } + function render() { + myGame.stage.context.fillStyle = 'rgb(255,255,0)'; + myGame.stage.context.fillRect(p1.x, p1.y, 4, 4); + myGame.stage.context.fillStyle = 'rgb(255,0,0)'; + myGame.stage.context.fillRect(p2.x, p2.y, 4, 4); + myGame.stage.context.fillStyle = 'rgb(0,255,0)'; + myGame.stage.context.fillRect(p3.x, p3.y, 4, 4); + myGame.stage.context.fillStyle = 'rgb(255,0,255)'; + myGame.stage.context.fillRect(p4.x, p4.y, 4, 4); + } +})(); diff --git a/Tests/geometry/multi rotate.ts b/Tests/geometry/multi rotate.ts new file mode 100644 index 00000000..075f82ca --- /dev/null +++ b/Tests/geometry/multi rotate.ts @@ -0,0 +1,49 @@ +/// + +(function () { + + var myGame = new Phaser.Game(this, 'game', 800, 600, null, create, update, render); + + var p1:Phaser.Point; + var p2:Phaser.Point; + var p3:Phaser.Point; + var p4:Phaser.Point; + + var d: number = 0; + + function create() { + + p1 = new Phaser.Point(myGame.stage.centerX, myGame.stage.centerY); + p2 = new Phaser.Point(p1.x - 50, p1.y - 50); + p3 = new Phaser.Point(p1.x - 100, p1.y - 100); + p4 = new Phaser.Point(p1.x - 150, p1.y - 150); + + } + + function update() { + + p2.rotate(p1.x, p1.y, myGame.math.wrapAngle(d), true); + p3.rotate(p1.x, p1.y, myGame.math.wrapAngle(d), true); + p4.rotate(p1.x, p1.y, myGame.math.wrapAngle(d), true); + + d++; + + } + + function render() { + + myGame.stage.context.fillStyle = 'rgb(255,255,0)'; + myGame.stage.context.fillRect(p1.x, p1.y, 4, 4); + + myGame.stage.context.fillStyle = 'rgb(255,0,0)'; + myGame.stage.context.fillRect(p2.x, p2.y, 4, 4); + + myGame.stage.context.fillStyle = 'rgb(0,255,0)'; + myGame.stage.context.fillRect(p3.x, p3.y, 4, 4); + + myGame.stage.context.fillStyle = 'rgb(255,0,255)'; + myGame.stage.context.fillRect(p4.x, p4.y, 4, 4); + + } + +})(); diff --git a/Tests/geometry/rotate point 1.js b/Tests/geometry/rotate point 1.js new file mode 100644 index 00000000..229ec21f --- /dev/null +++ b/Tests/geometry/rotate point 1.js @@ -0,0 +1,21 @@ +/// +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, null, create, update, render); + var p1; + var p2; + var d = 0; + function create() { + p1 = new Phaser.Point(200, 300); + p2 = new Phaser.Point(300, 300); + } + function update() { + p1.rotate(p2.x, p2.y, myGame.math.wrapAngle(d), true); + d++; + } + function render() { + myGame.stage.context.fillStyle = 'rgb(255,255,0)'; + myGame.stage.context.fillRect(p1.x, p1.y, 4, 4); + myGame.stage.context.fillStyle = 'rgb(255,0,0)'; + myGame.stage.context.fillRect(p2.x, p2.y, 4, 4); + } +})(); diff --git a/Tests/geometry/rotate point 1.ts b/Tests/geometry/rotate point 1.ts new file mode 100644 index 00000000..c97610f8 --- /dev/null +++ b/Tests/geometry/rotate point 1.ts @@ -0,0 +1,36 @@ +/// + +(function () { + + var myGame = new Phaser.Game(this, 'game', 800, 600, null, create, update, render); + + var p1:Phaser.Point; + var p2:Phaser.Point; + var d: number = 0; + + function create() { + + p1 = new Phaser.Point(200, 300); + p2 = new Phaser.Point(300, 300); + + } + + function update() { + + p1.rotate(p2.x, p2.y, myGame.math.wrapAngle(d), true); + + d++; + + } + + function render() { + + myGame.stage.context.fillStyle = 'rgb(255,255,0)'; + myGame.stage.context.fillRect(p1.x, p1.y, 4, 4); + + myGame.stage.context.fillStyle = 'rgb(255,0,0)'; + myGame.stage.context.fillRect(p2.x, p2.y, 4, 4); + + } + +})(); diff --git a/Tests/geometry/rotate point 2.js b/Tests/geometry/rotate point 2.js new file mode 100644 index 00000000..7e1c1688 --- /dev/null +++ b/Tests/geometry/rotate point 2.js @@ -0,0 +1,43 @@ +/// +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, null, create, update, render); + var p1; + var p2; + var p3; + var p4; + var d2 = 0; + var d3 = 0; + var d4 = 0; + function create() { + p1 = new Phaser.Point(myGame.stage.centerX, myGame.stage.centerY); + p2 = new Phaser.Point(p1.x - 50, p1.y - 50); + p3 = new Phaser.Point(p2.x - 50, p2.y - 50); + p4 = new Phaser.Point(p3.x - 50, p3.y - 50); + } + function update() { + p2.rotate(p1.x, p1.y, myGame.math.wrapAngle(d2), true, 150); + p3.rotate(p2.x, p2.y, myGame.math.wrapAngle(d3), true, 50); + p4.rotate(p3.x, p3.y, myGame.math.wrapAngle(d4), true, 100); + d2 += 1; + d3 += 4; + d4 += 6; + } + function render() { + myGame.stage.context.strokeStyle = 'rgb(0,255,255)'; + myGame.stage.context.beginPath(); + myGame.stage.context.moveTo(p1.x, p1.y); + myGame.stage.context.lineTo(p2.x, p2.y); + myGame.stage.context.lineTo(p3.x, p3.y); + myGame.stage.context.lineTo(p4.x, p4.y); + myGame.stage.context.stroke(); + myGame.stage.context.closePath(); + myGame.stage.context.fillStyle = 'rgb(255,255,0)'; + myGame.stage.context.fillRect(p1.x, p1.y, 4, 4); + myGame.stage.context.fillStyle = 'rgb(255,0,0)'; + myGame.stage.context.fillRect(p2.x, p2.y, 4, 4); + myGame.stage.context.fillStyle = 'rgb(0,255,0)'; + myGame.stage.context.fillRect(p3.x, p3.y, 4, 4); + myGame.stage.context.fillStyle = 'rgb(255,0,255)'; + myGame.stage.context.fillRect(p4.x, p4.y, 4, 4); + } +})(); diff --git a/Tests/geometry/rotate point 2.ts b/Tests/geometry/rotate point 2.ts new file mode 100644 index 00000000..92ce8034 --- /dev/null +++ b/Tests/geometry/rotate point 2.ts @@ -0,0 +1,62 @@ +/// + +(function () { + + var myGame = new Phaser.Game(this, 'game', 800, 600, null, create, update, render); + + var p1:Phaser.Point; + var p2:Phaser.Point; + var p3:Phaser.Point; + var p4:Phaser.Point; + + var d2: number = 0; + var d3: number = 0; + var d4: number = 0; + + function create() { + + p1 = new Phaser.Point(myGame.stage.centerX, myGame.stage.centerY); + p2 = new Phaser.Point(p1.x - 50, p1.y - 50); + p3 = new Phaser.Point(p2.x - 50, p2.y - 50); + p4 = new Phaser.Point(p3.x - 50, p3.y - 50); + + } + + function update() { + + p2.rotate(p1.x, p1.y, myGame.math.wrapAngle(d2), true, 150); + p3.rotate(p2.x, p2.y, myGame.math.wrapAngle(d3), true, 50); + p4.rotate(p3.x, p3.y, myGame.math.wrapAngle(d4), true, 100); + + d2 += 1; + d3 += 4; + d4 += 6; + + } + + function render() { + + myGame.stage.context.strokeStyle = 'rgb(0,255,255)'; + myGame.stage.context.beginPath(); + myGame.stage.context.moveTo(p1.x, p1.y); + myGame.stage.context.lineTo(p2.x, p2.y); + myGame.stage.context.lineTo(p3.x, p3.y); + myGame.stage.context.lineTo(p4.x, p4.y); + myGame.stage.context.stroke(); + myGame.stage.context.closePath(); + + myGame.stage.context.fillStyle = 'rgb(255,255,0)'; + myGame.stage.context.fillRect(p1.x, p1.y, 4, 4); + + myGame.stage.context.fillStyle = 'rgb(255,0,0)'; + myGame.stage.context.fillRect(p2.x, p2.y, 4, 4); + + myGame.stage.context.fillStyle = 'rgb(0,255,0)'; + myGame.stage.context.fillRect(p3.x, p3.y, 4, 4); + + myGame.stage.context.fillStyle = 'rgb(255,0,255)'; + myGame.stage.context.fillRect(p4.x, p4.y, 4, 4); + + } + +})(); diff --git a/Tests/geometry/rotate point 3.js b/Tests/geometry/rotate point 3.js new file mode 100644 index 00000000..13169cb2 --- /dev/null +++ b/Tests/geometry/rotate point 3.js @@ -0,0 +1,71 @@ +/// +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, null, create, update, render); + var origin; + var p1; + var p2; + var p3; + var p4; + var d = 0; + function create() { + // This creates a box made up of 4 edge-points and rotates it around the origin + origin = new Phaser.Point(400, 300); + p1 = new Phaser.Point()// top left + ; + p2 = new Phaser.Point()// top right + ; + p3 = new Phaser.Point()// bottom right + ; + p4 = new Phaser.Point()// bottom left + ; + } + function update() { + // top left (red) + p1.rotate(origin.x, origin.y, myGame.math.wrapAngle(d), true, 200); + // top right (yellow) + p2.rotate(origin.x, origin.y, myGame.math.wrapAngle(d + 90), true, 200); + // bottom right (aqua) + p3.rotate(origin.x, origin.y, myGame.math.wrapAngle(d + 180), true, 200); + // bottom left (blue) + p4.rotate(origin.x, origin.y, myGame.math.wrapAngle(d + 270), true, 200); + d++; + } + function render() { + // Render the shape + myGame.stage.context.beginPath(); + myGame.stage.context.fillStyle = 'rgba(0,255,0,0.2)'; + myGame.stage.context.strokeStyle = 'rgb(0,255,0)'; + myGame.stage.context.lineWidth = 1; + myGame.stage.context.moveTo(p1.x, p1.y); + myGame.stage.context.lineTo(p2.x, p2.y); + myGame.stage.context.lineTo(p3.x, p3.y); + myGame.stage.context.lineTo(p4.x, p4.y); + myGame.stage.context.lineTo(p1.x, p1.y); + myGame.stage.context.fill(); + myGame.stage.context.stroke(); + myGame.stage.context.closePath(); + // Render the points + myGame.stage.context.fillStyle = 'rgb(255,255,255)'; + myGame.stage.context.fillRect(origin.x, origin.y, 4, 4); + myGame.stage.context.beginPath(); + myGame.stage.context.fillStyle = 'rgb(255,0,0)'; + myGame.stage.context.arc(p1.x, p1.y, 4, 0, Math.PI * 2); + myGame.stage.context.fill(); + myGame.stage.context.closePath(); + myGame.stage.context.beginPath(); + myGame.stage.context.fillStyle = 'rgb(255,255,0)'; + myGame.stage.context.arc(p2.x, p2.y, 4, 0, Math.PI * 2); + myGame.stage.context.fill(); + myGame.stage.context.closePath(); + myGame.stage.context.beginPath(); + myGame.stage.context.fillStyle = 'rgb(0,255,255)'; + myGame.stage.context.arc(p3.x, p3.y, 4, 0, Math.PI * 2); + myGame.stage.context.fill(); + myGame.stage.context.closePath(); + myGame.stage.context.beginPath(); + myGame.stage.context.fillStyle = 'rgb(0,0,255)'; + myGame.stage.context.arc(p4.x, p4.y, 4, 0, Math.PI * 2); + myGame.stage.context.fill(); + myGame.stage.context.closePath(); + } +})(); diff --git a/Tests/geometry/rotate point 3.ts b/Tests/geometry/rotate point 3.ts new file mode 100644 index 00000000..ad0c8d76 --- /dev/null +++ b/Tests/geometry/rotate point 3.ts @@ -0,0 +1,95 @@ +/// + +(function () { + + var myGame = new Phaser.Game(this, 'game', 800, 600, null, create, update, render); + + var origin:Phaser.Point; + + var p1:Phaser.Point; + var p2:Phaser.Point; + var p3:Phaser.Point; + var p4:Phaser.Point; + + var d: number = 0; + + function create() { + + // This creates a box made up of 4 edge-points and rotates it around the origin + + origin = new Phaser.Point(400, 300); + + p1 = new Phaser.Point(); // top left + p2 = new Phaser.Point(); // top right + p3 = new Phaser.Point(); // bottom right + p4 = new Phaser.Point(); // bottom left + + } + + function update() { + + // top left (red) + p1.rotate(origin.x, origin.y, myGame.math.wrapAngle(d), true, 200); + + // top right (yellow) + p2.rotate(origin.x, origin.y, myGame.math.wrapAngle(d + 90), true, 200); + + // bottom right (aqua) + p3.rotate(origin.x, origin.y, myGame.math.wrapAngle(d + 180), true, 200); + + // bottom left (blue) + p4.rotate(origin.x, origin.y, myGame.math.wrapAngle(d + 270), true, 200); + + d++; + + } + + function render() { + + // Render the shape + + myGame.stage.context.beginPath(); + myGame.stage.context.fillStyle = 'rgba(0,255,0,0.2)'; + myGame.stage.context.strokeStyle = 'rgb(0,255,0)'; + myGame.stage.context.lineWidth = 1; + myGame.stage.context.moveTo(p1.x, p1.y); + myGame.stage.context.lineTo(p2.x, p2.y); + myGame.stage.context.lineTo(p3.x, p3.y); + myGame.stage.context.lineTo(p4.x, p4.y); + myGame.stage.context.lineTo(p1.x, p1.y); + myGame.stage.context.fill(); + myGame.stage.context.stroke(); + myGame.stage.context.closePath(); + + // Render the points + + myGame.stage.context.fillStyle = 'rgb(255,255,255)'; + myGame.stage.context.fillRect(origin.x, origin.y, 4, 4); + + myGame.stage.context.beginPath(); + myGame.stage.context.fillStyle = 'rgb(255,0,0)'; + myGame.stage.context.arc(p1.x, p1.y, 4, 0, Math.PI * 2); + myGame.stage.context.fill(); + myGame.stage.context.closePath(); + + myGame.stage.context.beginPath(); + myGame.stage.context.fillStyle = 'rgb(255,255,0)'; + myGame.stage.context.arc(p2.x, p2.y, 4, 0, Math.PI * 2); + myGame.stage.context.fill(); + myGame.stage.context.closePath(); + + myGame.stage.context.beginPath(); + myGame.stage.context.fillStyle = 'rgb(0,255,255)'; + myGame.stage.context.arc(p3.x, p3.y, 4, 0, Math.PI * 2); + myGame.stage.context.fill(); + myGame.stage.context.closePath(); + + myGame.stage.context.beginPath(); + myGame.stage.context.fillStyle = 'rgb(0,0,255)'; + myGame.stage.context.arc(p4.x, p4.y, 4, 0, Math.PI * 2); + myGame.stage.context.fill(); + myGame.stage.context.closePath(); + + } + +})(); diff --git a/Tests/geometry/rotate point 4.js b/Tests/geometry/rotate point 4.js new file mode 100644 index 00000000..ea9ed35c --- /dev/null +++ b/Tests/geometry/rotate point 4.js @@ -0,0 +1,54 @@ +/// +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, null, create, update, render); + var origin; + var origin2; + var points = []; + var points2 = []; + var d = 0; + var d2 = 0; + var m = 64; + function create() { + // Let's have some fun :) + origin = new Phaser.Point(300, 200); + origin2 = new Phaser.Point(600, 350); + for(var i = 0; i < m; i++) { + points.push(new Phaser.Point()); + points2.push(new Phaser.Point()); + } + } + function update() { + for(var i = 0; i < m; i++) { + points[i].rotate(origin.x, origin.y, myGame.math.wrapAngle(d + (i * (360 / m))), true, i * 5); + //points2[i].rotate(origin2.x, origin2.y, myGame.math.wrapAngle(d2 + (i * (360/m))), true, i * 10); + //points[i].rotate(origin.x, origin.y, myGame.math.wrapAngle(d + (i * (360/m))), true, 200); + points2[i].rotate(origin2.x, origin2.y, myGame.math.wrapAngle(d2 + (i * (360 / m))), true, 200); + } + d -= 2; + d2 += 2; + } + function render() { + // Render the shape + myGame.stage.context.save(); + //myGame.stage.context.globalCompositeOperation = 'xor'; + myGame.stage.context.globalCompositeOperation = 'lighter'; + myGame.stage.context.lineWidth = 20; + for(var i = 0; i < m; i++) { + myGame.stage.context.beginPath(); + myGame.stage.context.strokeStyle = 'rgba(255,' + Math.round(i * (255 / m)).toString() + ',0,1)'; + myGame.stage.context.moveTo(origin.x, origin.y); + myGame.stage.context.lineTo(points[i].x, points[i].y); + myGame.stage.context.stroke(); + myGame.stage.context.closePath(); + } + for(var i = 0; i < m; i++) { + myGame.stage.context.beginPath(); + myGame.stage.context.strokeStyle = 'rgba(0,' + Math.round(i * (255 / m)).toString() + ',255,1)'; + myGame.stage.context.moveTo(origin2.x, origin2.y); + myGame.stage.context.lineTo(points2[i].x, points2[i].y); + myGame.stage.context.stroke(); + myGame.stage.context.closePath(); + } + myGame.stage.context.restore(); + } +})(); diff --git a/Tests/geometry/rotate point 4.ts b/Tests/geometry/rotate point 4.ts new file mode 100644 index 00000000..020c268c --- /dev/null +++ b/Tests/geometry/rotate point 4.ts @@ -0,0 +1,82 @@ +/// + +(function () { + + var myGame = new Phaser.Game(this, 'game', 800, 600, null, create, update, render); + + var origin:Phaser.Point; + var origin2:Phaser.Point; + + var points:Phaser.Point[] = []; + var points2:Phaser.Point[] = []; + + var d: number = 0; + var d2: number = 0; + var m: number = 64; + + function create() { + + // Let's have some fun :) + + origin = new Phaser.Point(300, 200); + origin2 = new Phaser.Point(600, 350); + + for (var i = 0; i < m; i++) + { + points.push(new Phaser.Point()); + points2.push(new Phaser.Point()); + } + + } + + function update() { + + for (var i = 0; i < m; i++) + { + points[i].rotate(origin.x, origin.y, myGame.math.wrapAngle(d + (i * (360/m))), true, i * 5); + //points2[i].rotate(origin2.x, origin2.y, myGame.math.wrapAngle(d2 + (i * (360/m))), true, i * 10); + + //points[i].rotate(origin.x, origin.y, myGame.math.wrapAngle(d + (i * (360/m))), true, 200); + points2[i].rotate(origin2.x, origin2.y, myGame.math.wrapAngle(d2 + (i * (360/m))), true, 200); + } + + d -= 2; + d2 += 2; + + } + + function render() { + + // Render the shape + + myGame.stage.context.save(); + //myGame.stage.context.globalCompositeOperation = 'xor'; + myGame.stage.context.globalCompositeOperation = 'lighter'; + myGame.stage.context.lineWidth = 20; + + for (var i = 0; i < m; i++) + { + myGame.stage.context.beginPath(); + myGame.stage.context.strokeStyle = 'rgba(255,' + Math.round(i * (255/m)).toString() + ',0,1)'; + myGame.stage.context.moveTo(origin.x, origin.y); + myGame.stage.context.lineTo(points[i].x, points[i].y); + myGame.stage.context.stroke(); + myGame.stage.context.closePath(); + } + + + for (var i = 0; i < m; i++) + { + myGame.stage.context.beginPath(); + myGame.stage.context.strokeStyle = 'rgba(0,' + Math.round(i * (255/m)).toString() + ',255,1)'; + myGame.stage.context.moveTo(origin2.x, origin2.y); + myGame.stage.context.lineTo(points2[i].x, points2[i].y); + myGame.stage.context.stroke(); + myGame.stage.context.closePath(); + } + + myGame.stage.context.restore(); + + } + +})(); diff --git a/Tests/geometry/verlet 1.js b/Tests/geometry/verlet 1.js new file mode 100644 index 00000000..c951b1b7 --- /dev/null +++ b/Tests/geometry/verlet 1.js @@ -0,0 +1,30 @@ +/// +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, null, create, update, render); + var segment; + function create() { + myGame.verlet.friction = 1; + segment = myGame.verlet.createLineSegments([ + new Phaser.Vector2(20, 10), + new Phaser.Vector2(40, 10), + new Phaser.Vector2(60, 10), + new Phaser.Vector2(80, 10), + new Phaser.Vector2(100, 10) + ], 0.02); + segment.pin(0); + segment.pin(4); + var wheel = myGame.verlet.createTire(new Phaser.Vector2(200, 50), 100, 30, 0.3, 0.9); + var tire2 = myGame.verlet.createTire(new Phaser.Vector2(400, 50), 70, 7, 0.1, 0.2); + var cube = myGame.verlet.createTire(new Phaser.Vector2(600, 50), 70, 4, 1, 1); + var tri = myGame.verlet.createTire(new Phaser.Vector2(700, 50), 100, 3, 1, 1); + } + function update() { + } + function render() { + myGame.verlet.render(); + //myGame.stage.context.fillStyle = 'rgb(255,255,0)'; + //myGame.stage.context.fillRect(p1.x, p1.y, 4, 4); + //myGame.stage.context.fillStyle = 'rgb(255,0,0)'; + //myGame.stage.context.fillRect(p2.x, p2.y, 4, 4); + } +})(); diff --git a/Tests/geometry/verlet 1.ts b/Tests/geometry/verlet 1.ts new file mode 100644 index 00000000..455ad974 --- /dev/null +++ b/Tests/geometry/verlet 1.ts @@ -0,0 +1,39 @@ +/// + +(function () { + + var myGame = new Phaser.Game(this, 'game', 800, 600, null, create, update, render); + + var segment: Phaser.Verlet.Composite; + + function create() { + + myGame.verlet.friction = 1; + + segment = myGame.verlet.createLineSegments([new Phaser.Vector2(20, 10), new Phaser.Vector2(40, 10), new Phaser.Vector2(60, 10), new Phaser.Vector2(80, 10), new Phaser.Vector2(100, 10)], 0.02); + segment.pin(0); + segment.pin(4); + + var wheel = myGame.verlet.createTire(new Phaser.Vector2(200,50), 100, 30, 0.3, 0.9); + var tire2 = myGame.verlet.createTire(new Phaser.Vector2(400,50), 70, 7, 0.1, 0.2); + var cube = myGame.verlet.createTire(new Phaser.Vector2(600,50), 70, 4, 1, 1); + var tri = myGame.verlet.createTire(new Phaser.Vector2(700,50), 100, 3, 1, 1); + + } + + function update() { + } + + function render() { + + myGame.verlet.render(); + + //myGame.stage.context.fillStyle = 'rgb(255,255,0)'; + //myGame.stage.context.fillRect(p1.x, p1.y, 4, 4); + + //myGame.stage.context.fillStyle = 'rgb(255,0,0)'; + //myGame.stage.context.fillRect(p2.x, p2.y, 4, 4); + + } + +})(); diff --git a/Tests/geometry/verlet sprites.js b/Tests/geometry/verlet sprites.js new file mode 100644 index 00000000..93dbc2c1 --- /dev/null +++ b/Tests/geometry/verlet sprites.js @@ -0,0 +1,49 @@ +/// +(function () { + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + var cube; + var b1; + var b2; + var b3; + var b4; + function init() { + myGame.loader.addImageFile('ball0', 'assets/sprites/yellow_ball.png'); + myGame.loader.addImageFile('ball1', 'assets/sprites/aqua_ball.png'); + myGame.loader.addImageFile('ball2', 'assets/sprites/blue_ball.png'); + myGame.loader.addImageFile('ball3', 'assets/sprites/green_ball.png'); + myGame.loader.addImageFile('ball4', 'assets/sprites/red_ball.png'); + myGame.loader.addImageFile('ball5', 'assets/sprites/purple_ball.png'); + myGame.loader.load(); + } + function create() { + myGame.verlet.friction = 1; + myGame.verlet.step = 32; + //var wheel = myGame.verlet.createTire(new Phaser.Vector2(200,50), 100, 30, 0.3, 0.9); + //var tire2 = myGame.verlet.createTire(new Phaser.Vector2(400,50), 70, 7, 0.1, 0.2); + cube = myGame.verlet.createTire(new Phaser.Vector2(300, 50), 100, 4, 1, 1); + //var tri = myGame.verlet.createTire(new Phaser.Vector2(700,50), 100, 3, 1, 1); + var dc = new Phaser.Verlet.DistanceConstraint(cube.particles[0], cube.particles[1], 1); + cube.constraints.push(dc); + var dc2 = new Phaser.Verlet.DistanceConstraint(cube.particles[1], cube.particles[2], 1); + cube.constraints.push(dc2); + var dc3 = new Phaser.Verlet.DistanceConstraint(cube.particles[2], cube.particles[3], 1); + cube.constraints.push(dc3); + b1 = myGame.createSprite(cube.particles[0].pos.x, cube.particles[0].pos.y, 'ball0'); + b2 = myGame.createSprite(cube.particles[1].pos.x, cube.particles[1].pos.y, 'ball1'); + b3 = myGame.createSprite(cube.particles[2].pos.x, cube.particles[2].pos.y, 'ball2'); + b4 = myGame.createSprite(cube.particles[3].pos.x, cube.particles[3].pos.y, 'ball3'); + } + function update() { + b1.x = cube.particles[0].pos.x - 8; + b1.y = cube.particles[0].pos.y - 8; + b2.x = cube.particles[1].pos.x - 8; + b2.y = cube.particles[1].pos.y - 8; + b3.x = cube.particles[2].pos.x - 8; + b3.y = cube.particles[2].pos.y - 8; + b4.x = cube.particles[3].pos.x - 8; + b4.y = cube.particles[3].pos.y - 8; + } + function render() { + myGame.verlet.render(); + } +})(); diff --git a/Tests/geometry/verlet sprites.ts b/Tests/geometry/verlet sprites.ts new file mode 100644 index 00000000..30c5dc85 --- /dev/null +++ b/Tests/geometry/verlet sprites.ts @@ -0,0 +1,75 @@ +/// + +(function () { + + var myGame = new Phaser.Game(this, 'game', 800, 600, init, create, update, render); + + var cube: Phaser.Verlet.Composite; + + var b1: Phaser.Sprite; + var b2: Phaser.Sprite; + var b3: Phaser.Sprite; + var b4: Phaser.Sprite; + + function init() { + + myGame.loader.addImageFile('ball0', 'assets/sprites/yellow_ball.png'); + myGame.loader.addImageFile('ball1', 'assets/sprites/aqua_ball.png'); + myGame.loader.addImageFile('ball2', 'assets/sprites/blue_ball.png'); + myGame.loader.addImageFile('ball3', 'assets/sprites/green_ball.png'); + myGame.loader.addImageFile('ball4', 'assets/sprites/red_ball.png'); + myGame.loader.addImageFile('ball5', 'assets/sprites/purple_ball.png'); + + myGame.loader.load(); + + } + + function create() { + + myGame.verlet.friction = 1; + myGame.verlet.step = 32; + + //var wheel = myGame.verlet.createTire(new Phaser.Vector2(200,50), 100, 30, 0.3, 0.9); + //var tire2 = myGame.verlet.createTire(new Phaser.Vector2(400,50), 70, 7, 0.1, 0.2); + cube = myGame.verlet.createTire(new Phaser.Vector2(300, 50), 100, 4, 1, 1); + //var tri = myGame.verlet.createTire(new Phaser.Vector2(700,50), 100, 3, 1, 1); + + var dc: Phaser.Verlet.DistanceConstraint = new Phaser.Verlet.DistanceConstraint(cube.particles[0], cube.particles[1], 1); + cube.constraints.push(dc); + + var dc2: Phaser.Verlet.DistanceConstraint = new Phaser.Verlet.DistanceConstraint(cube.particles[1], cube.particles[2], 1); + cube.constraints.push(dc2); + + var dc3: Phaser.Verlet.DistanceConstraint = new Phaser.Verlet.DistanceConstraint(cube.particles[2], cube.particles[3], 1); + cube.constraints.push(dc3); + + b1 = myGame.createSprite(cube.particles[0].pos.x, cube.particles[0].pos.y, 'ball0'); + b2 = myGame.createSprite(cube.particles[1].pos.x, cube.particles[1].pos.y, 'ball1'); + b3 = myGame.createSprite(cube.particles[2].pos.x, cube.particles[2].pos.y, 'ball2'); + b4 = myGame.createSprite(cube.particles[3].pos.x, cube.particles[3].pos.y, 'ball3'); + + } + + function update() { + + b1.x = cube.particles[0].pos.x - 8; + b1.y = cube.particles[0].pos.y - 8; + + b2.x = cube.particles[1].pos.x - 8; + b2.y = cube.particles[1].pos.y - 8; + + b3.x = cube.particles[2].pos.x - 8; + b3.y = cube.particles[2].pos.y - 8; + + b4.x = cube.particles[3].pos.x - 8; + b4.y = cube.particles[3].pos.y - 8; + + } + + function render() { + + myGame.verlet.render(); + + } + +})(); diff --git a/Tests/phaser.js b/Tests/phaser.js index 54aa3d08..1d5e6f54 100644 --- a/Tests/phaser.js +++ b/Tests/phaser.js @@ -3286,6 +3286,27 @@ var Phaser; **/ function (length, angle) { }; + Point.prototype.rotate = /** + * Rotates the point around the x/y coordinates given to the desired angle + * @param x {number} The x coordinate of the anchor point + * @param y {number} The y coordinate of the anchor point + * @param {Number} angle The angle in radians (unless asDegrees is true) to return the point from. + * @param {Boolean} asDegrees Is the given angle in radians (false) or degrees (true)? + * @param {Number} distance An optional distance constraint between the point and the anchor + * @return The modified point object + */ + function (cx, cy, angle, asDegrees, distance) { + if (typeof asDegrees === "undefined") { asDegrees = false; } + if (typeof distance === "undefined") { distance = null; } + if(asDegrees) { + angle = angle * Phaser.GameMath.DEG_TO_RAD; + } + // Get distance from origin (cx/cy) to this point + if(distance === null) { + distance = Math.sqrt(((cx - this.x) * (cx - this.x)) + ((cy - this.y) * (cy - this.y))); + } + return this.setTo(cx + distance * Math.cos(angle), cy + distance * Math.sin(angle)); + }; Point.prototype.setTo = /** * Sets the x and y values of this Point object to the given coordinates. * @method setTo @@ -4081,7 +4102,7 @@ var Phaser; * @param {Number} y The y coordinate of the top-left corner of the quad. * @param {Number} width The width of the quad. * @param {Number} height The height of the quad. - * @return {Quad } This object + * @return {Quad} This object **/ function Quad(x, y, width, height) { if (typeof x === "undefined") { x = 0; } @@ -5290,7 +5311,6 @@ var Phaser; continue; } if(QuadTree._object.collisionMask.checkHullIntersection(checkObject.collisionMask)) { - console.log('quad hull'); //Execute callback functions if they exist if((QuadTree._processingCallback == null) || QuadTree._processingCallback(QuadTree._object, checkObject)) { overlapProcessed = true; @@ -7312,6 +7332,35 @@ var Phaser; } return array; }; + GameMath.distanceBetween = /** + * Returns the distance from this Point object to the given Point object. + * @method distanceFrom + * @param {Point} target - The destination Point object. + * @param {Boolean} round - Round the distance to the nearest integer (default false) + * @return {Number} The distance between this Point object and the destination Point object. + **/ + function distanceBetween(x1, y1, x2, y2) { + var dx = x1 - x2; + var dy = y1 - y2; + return Math.sqrt(dx * dx + dy * dy); + }; + GameMath.prototype.rotatePoint = /** + * Rotates a point around the x/y coordinates given to the desired angle + * @param x {number} The x coordinate of the anchor point + * @param y {number} The y coordinate of the anchor point + * @param angle {number} The angle of the rotation in radians + * @param point {Point} The point object to perform the rotation on + * @return The modified point object + */ + function (x, y, angle, point) { + var s = Math.sin(angle); + var c = Math.cos(angle); + point.x -= x; + point.y -= y; + var newX = point.x * c - point.y * s; + var newY = point.x * s - point.y * c; + return point.setTo(newX + x, newY + y); + }; return GameMath; })(); Phaser.GameMath = GameMath; @@ -7330,6 +7379,19 @@ var Phaser; function Group(game, MaxSize) { if (typeof MaxSize === "undefined") { MaxSize = 0; } _super.call(this, game); + /** + * You can set a globalCompositeOperation that will be applied before the render method is called on this Groups children. + * This is useful if you wish to apply an effect like 'lighten' to a whole group of children as it saves doing it one-by-one. + * If this value is set it will call a canvas context save and restore before and after the render pass. + * Set to null to disable. + */ + this.globalCompositeOperation = null; + /** + * You can set an alpha value on this Group that will be applied before the render method is called on this Groups children. + * This is useful if you wish to alpha a whole group of children as it saves doing it one-by-one. + * Set to 0 to disable. + */ + this.alpha = 0; this.isGroup = true; this.members = []; this.length = 0; @@ -7384,6 +7446,14 @@ var Phaser; if(this.ignoreGlobalRender && forceRender == false) { return; } + if(this.globalCompositeOperation) { + this._game.stage.context.save(); + this._game.stage.context.globalCompositeOperation = this.globalCompositeOperation; + } + if(this.alpha > 0) { + var prevAlpha = this._game.stage.context.globalAlpha; + this._game.stage.context.globalAlpha = this.alpha; + } var basic; var i = 0; while(i < this.length) { @@ -7392,6 +7462,12 @@ var Phaser; basic.render(camera, cameraOffsetX, cameraOffsetY, forceRender); } } + if(this.alpha > 0) { + this._game.stage.context.globalAlpha = prevAlpha; + } + if(this.globalCompositeOperation) { + this._game.stage.context.restore(); + } }; Object.defineProperty(Group.prototype, "maxSize", { get: /** @@ -9286,6 +9362,9 @@ var Phaser; this.canvas.style['ms-touch-action'] = 'none'; this.canvas.style['touch-action'] = 'none'; this.canvas.style.backgroundColor = 'rgb(0,0,0)'; + this.canvas.oncontextmenu = function (event) { + event.preventDefault(); + }; this.context = this.canvas.getContext('2d'); this.offset = this.getOffset(this.canvas); this.bounds = new Phaser.Quad(this.offset.x, this.offset.y, width, height); @@ -10278,6 +10357,661 @@ var Phaser; })(); Phaser.TweenManager = TweenManager; })(Phaser || (Phaser = {})); +/// +/** +* Phaser - Vector2 +* +* A simple 2-dimensional vector class. Based on the one included with verlet-js by Sub Protocol released under MIT +*/ +var Phaser; +(function (Phaser) { + var Vector2 = (function () { + /** + * Creates a new Vector2 object. + * @class Vector2 + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Vector2} This object + **/ + function Vector2(x, y) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + this.x = x; + this.y = y; + } + Vector2.prototype.setTo = function (x, y) { + this.x = x; + this.y = y; + return this; + }; + Vector2.prototype.add = function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x + v.x, this.y + v.y); + }; + Vector2.prototype.sub = function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x - v.x, this.y - v.y); + }; + Vector2.prototype.mul = function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x * v.x, this.y * v.y); + }; + Vector2.prototype.div = function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x / v.x, this.y / v.y); + }; + Vector2.prototype.scale = function (coef, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x * coef, this.y * coef); + }; + Vector2.prototype.mutableSet = function (v) { + this.x = v.x; + this.y = v.y; + return this; + }; + Vector2.prototype.mutableAdd = function (v) { + this.x += v.x; + this.y += v.y; + return this; + }; + Vector2.prototype.mutableSub = function (v) { + this.x -= v.x; + this.y -= v.y; + return this; + }; + Vector2.prototype.mutableMul = function (v) { + this.x *= v.x; + this.y *= v.y; + return this; + }; + Vector2.prototype.mutableDiv = function (v) { + this.x /= v.x; + this.y /= v.y; + return this; + }; + Vector2.prototype.mutableScale = function (coef) { + this.x *= coef; + this.y *= coef; + return this; + }; + Vector2.prototype.equals = function (v) { + return this.x == v.x && this.y == v.y; + }; + Vector2.prototype.epsilonEquals = function (v, epsilon) { + return Math.abs(this.x - v.x) <= epsilon && Math.abs(this.y - v.y) <= epsilon; + }; + Vector2.prototype.length = function () { + return Math.sqrt(this.x * this.x + this.y * this.y); + }; + Vector2.prototype.length2 = function () { + return this.x * this.x + this.y * this.y; + }; + Vector2.prototype.dist = function (v) { + return Math.sqrt(this.dist2(v)); + }; + Vector2.prototype.dist2 = function (v) { + return ((v.x - this.x) * (v.x - this.x)) + ((v.y - this.y) * (v.y - this.y)); + }; + Vector2.prototype.normal = function (output) { + if (typeof output === "undefined") { output = new Vector2(); } + var m = Math.sqrt(this.x * this.x + this.y * this.y); + return output.setTo(this.x / m, this.y / m); + }; + Vector2.prototype.dot = function (v) { + return this.x * v.x + this.y * v.y; + }; + Vector2.prototype.angle = function (v) { + return Math.atan2(this.x * v.y - this.y * v.x, this.x * v.x + this.y * v.y); + }; + Vector2.prototype.angle2 = function (vLeft, vRight) { + return vLeft.sub(this).angle(vRight.sub(this)); + }; + Vector2.prototype.rotate = function (origin, theta, output) { + if (typeof output === "undefined") { output = new Vector2(); } + var x = this.x - origin.x; + var y = this.y - origin.y; + return output.setTo(x * Math.cos(theta) - y * Math.sin(theta) + origin.x, x * Math.sin(theta) + y * Math.cos(theta) + origin.y); + }; + Vector2.prototype.toString = /** + * Returns a string representation of this object. + * @method toString + * @return {string} a string representation of the object. + **/ + function () { + return "[{Vector2 (x=" + this.x + " y=" + this.y + ")}]"; + }; + return Vector2; + })(); + Phaser.Vector2 = Vector2; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /** + * Phaser - Verlet - Particle + * + * + */ + (function (Verlet) { + var Particle = (function () { + /** + * Creates a new Particle object. + * @class Particle + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Particle} This object + **/ + function Particle(pos) { + this.pos = (new Phaser.Vector2()).mutableSet(pos); + this.lastPos = (new Phaser.Vector2()).mutableSet(pos); + } + Particle.prototype.render = function (ctx) { + ctx.beginPath(); + ctx.arc(this.pos.x, this.pos.y, 2, 0, 2 * Math.PI); + ctx.fillStyle = "#2dad8f"; + ctx.fill(); + }; + return Particle; + })(); + Verlet.Particle = Particle; + })(Phaser.Verlet || (Phaser.Verlet = {})); + var Verlet = Phaser.Verlet; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /// + /** + * Phaser - PinConstraint + * + * Constrains to static / fixed point + */ + (function (Verlet) { + var PinConstraint = (function () { + /** + * Creates a new PinConstraint object. + * @class PinConstraint + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {PinConstraint} This object + **/ + function PinConstraint(a, pos) { + this.a = a; + this.pos = (new Phaser.Vector2()).mutableSet(pos); + } + PinConstraint.prototype.relax = function () { + this.a.pos.mutableSet(this.pos); + }; + PinConstraint.prototype.render = function (ctx) { + ctx.beginPath(); + ctx.arc(this.pos.x, this.pos.y, 6, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,153,255,0.1)"; + ctx.fill(); + }; + return PinConstraint; + })(); + Verlet.PinConstraint = PinConstraint; + })(Phaser.Verlet || (Phaser.Verlet = {})); + var Verlet = Phaser.Verlet; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /// + /// + /** + * Phaser - Verlet - Composite + * + * + */ + (function (Verlet) { + var Composite = (function () { + /** + * Creates a new Composite object. + * @class Composite + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Composite} This object + **/ + function Composite(game) { + this.drawParticles = null; + this.drawConstraints = null; + this._game = game; + this.particles = []; + this.constraints = []; + } + Composite.prototype.createDistanceConstraint = // Map sprites to particles + function (a, b, stiffness, distance) { + if (typeof distance === "undefined") { distance = null; } + this.constraints.push(new Phaser.Verlet.DistanceConstraint(a, b, stiffness, distance)); + return this.constraints[this.constraints.length - 1]; + }; + Composite.prototype.createAngleConstraint = function (a, b, c, stiffness) { + this.constraints.push(new Phaser.Verlet.AngleConstraint(a, b, c, stiffness)); + return this.constraints[this.constraints.length - 1]; + }; + Composite.prototype.createPinConstraint = function (a, pos) { + this.constraints.push(new Phaser.Verlet.PinConstraint(a, pos)); + return this.constraints[this.constraints.length - 1]; + }; + Composite.prototype.pin = function (index, pos) { + if (typeof pos === "undefined") { pos = null; } + if(pos == null) { + pos = this.particles[index].pos; + } + var pc = new Phaser.Verlet.PinConstraint(this.particles[index], pos); + this.constraints.push(pc); + return pc; + }; + return Composite; + })(); + Verlet.Composite = Composite; + })(Phaser.Verlet || (Phaser.Verlet = {})); + var Verlet = Phaser.Verlet; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /// + /** + * Phaser - DistanceConstraint + * + * Constrains to initial distance + */ + (function (Verlet) { + var DistanceConstraint = (function () { + /** + * Creates a new DistanceConstraint object. + * @class DistanceConstraint + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {DistanceConstraint} This object + **/ + function DistanceConstraint(a, b, stiffness, distance) { + if (typeof distance === "undefined") { distance = null; } + this.a = a; + this.b = b; + if(distance === null) { + this.distance = a.pos.sub(b.pos).length(); + } else { + this.distance = distance; + } + this.stiffness = stiffness; + } + DistanceConstraint.prototype.relax = function (stepCoef) { + var normal = this.a.pos.sub(this.b.pos); + var m = normal.length2(); + normal.mutableScale(((this.distance * this.distance - m) / m) * this.stiffness * stepCoef); + this.a.pos.mutableAdd(normal); + this.b.pos.mutableSub(normal); + }; + DistanceConstraint.prototype.render = function (ctx) { + ctx.beginPath(); + ctx.moveTo(this.a.pos.x, this.a.pos.y); + ctx.lineTo(this.b.pos.x, this.b.pos.y); + ctx.strokeStyle = "#d8dde2"; + ctx.stroke(); + }; + return DistanceConstraint; + })(); + Verlet.DistanceConstraint = DistanceConstraint; + })(Phaser.Verlet || (Phaser.Verlet = {})); + var Verlet = Phaser.Verlet; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /// + /** + * Phaser - AngleConstraint + * + * constrains 3 particles to an angle + */ + (function (Verlet) { + var AngleConstraint = (function () { + /** + * Creates a new AngleConstraint object. + * @class AngleConstraint + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {AngleConstraint} This object + **/ + function AngleConstraint(a, b, c, stiffness) { + this.a = a; + this.b = b; + this.c = c; + this.angle = this.b.pos.angle2(this.a.pos, this.c.pos); + this.stiffness = stiffness; + } + AngleConstraint.prototype.relax = function (stepCoef) { + var angle = this.b.pos.angle2(this.a.pos, this.c.pos); + var diff = angle - this.angle; + if(diff <= -Math.PI) { + diff += 2 * Math.PI; + } else if(diff >= Math.PI) { + diff -= 2 * Math.PI; + } + diff *= stepCoef * this.stiffness; + this.a.pos = this.a.pos.rotate(this.b.pos, diff); + this.c.pos = this.c.pos.rotate(this.b.pos, -diff); + this.b.pos = this.b.pos.rotate(this.a.pos, diff); + this.b.pos = this.b.pos.rotate(this.c.pos, -diff); + }; + AngleConstraint.prototype.render = function (ctx) { + ctx.beginPath(); + ctx.moveTo(this.a.pos.x, this.a.pos.y); + ctx.lineTo(this.b.pos.x, this.b.pos.y); + ctx.lineTo(this.c.pos.x, this.c.pos.y); + var tmp = ctx.lineWidth; + ctx.lineWidth = 5; + ctx.strokeStyle = "rgba(255,255,0,0.2)"; + ctx.stroke(); + ctx.lineWidth = tmp; + }; + return AngleConstraint; + })(); + Verlet.AngleConstraint = AngleConstraint; + })(Phaser.Verlet || (Phaser.Verlet = {})); + var Verlet = Phaser.Verlet; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /// + /// + /// + /// + /// + /** + * Phaser - Verlet + * + * Based on verlet-js by Sub Protocol released under MIT + */ + (function (Verlet) { + var VerletManager = (function () { + /** + * Creates a new Vector2 object. + * @class Vector2 + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Vector2} This object + **/ + function VerletManager(game, width, height) { + this.composites = []; + this.step = 32; + this.selectionRadius = 20; + this.draggedEntity = null; + this.highlightColor = '#4f545c'; + this.v = new Phaser.Vector2(); + this._game = game; + this.width = width; + this.height = height; + this.gravity = new Phaser.Vector2(0, 0.2); + this.friction = 0.99; + this.groundFriction = 0.8; + this.canvas = game.stage.canvas; + this.context = game.stage.context; + this._game.input.onDown.add(this.mouseDownHandler, this); + this._game.input.onUp.add(this.mouseUpHandler, this); + } + VerletManager.prototype.intersectionTime = /** + * Computes time of intersection of a particle with a wall + * + * @param {Vec2} line wall's root position + * @param {Vec2} p particle's position + * @param {Vec2} dir walls's direction + * @param {Vec2} v particle's velocity + */ + function (wall, p, dir, v) { + if(dir.x != 0) { + var denominator = v.y - dir.y * v.x / dir.x; + if(denominator == 0) { + return undefined; + }// Movement is parallel to wall + + var numerator = wall.y + dir.y * (p.x - wall.x) / dir.x - p.y; + return numerator / denominator; + } else { + if(v.x == 0) { + return undefined; + }// parallel again + + var denominator = v.x; + var numerator = wall.x - p.x; + return numerator / denominator; + } + }; + VerletManager.prototype.intersectionPoint = function (wall, p, dir, v) { + var t = this.intersectionTime(wall, p, dir, v); + return new Phaser.Vector2(p.x + v.x * t, p.y + v.y * t); + }; + VerletManager.prototype.bounds = function (particle) { + this.v.mutableSet(particle.pos); + this.v.mutableSub(particle.lastPos); + if(particle.pos.y > this.height - 1) { + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, this.height - 1), particle.lastPos, new Phaser.Vector2(1, 0), this.v)); + } + if(particle.pos.x < 0) { + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, 0), particle.pos, new Phaser.Vector2(0, 1), this.v)); + } + if(particle.pos.x > this.width - 1) { + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(this.width - 1, 0), particle.pos, new Phaser.Vector2(0, 1), this.v)); + } + }; + VerletManager.prototype.OLDbounds = function (particle) { + if(particle.pos.y > this.height - 1) { + particle.pos.y = this.height - 1; + } + if(particle.pos.x < 0) { + var vx = particle.pos.x - particle.lastPos.x; + var vy = particle.pos.y - particle.lastPos.y; + if(vx == 0) { + particle.pos.x = 0; + } else { + var t = -particle.lastPos.x / vx; + particle.pos.x = particle.lastPos.x + t * vx; + particle.pos.y = particle.lastPos.y + t * vy; + } + } + if(particle.pos.x > this.width - 1) { + particle.pos.x = this.width - 1; + } + }; + VerletManager.prototype.createPoint = function (pos) { + var composite = new Phaser.Verlet.Composite(this._game); + composite.particles.push(new Phaser.Verlet.Particle(pos)); + this.composites.push(composite); + return composite; + }; + VerletManager.prototype.createLineSegments = function (vertices, stiffness) { + var i; + var composite = new Phaser.Verlet.Composite(this._game); + for(i in vertices) { + composite.particles.push(new Phaser.Verlet.Particle(vertices[i])); + if(i > 0) { + composite.constraints.push(new Phaser.Verlet.DistanceConstraint(composite.particles[i], composite.particles[i - 1], stiffness)); + } + } + this.composites.push(composite); + return composite; + }; + VerletManager.prototype.createCloth = function (origin, width, height, segments, pinMod, stiffness) { + var composite = new Phaser.Verlet.Composite(this._game); + var xStride = width / segments; + var yStride = height / segments; + var x, y; + for(y = 0; y < segments; ++y) { + for(x = 0; x < segments; ++x) { + var px = origin.x + x * xStride - width / 2 + xStride / 2; + var py = origin.y + y * yStride - height / 2 + yStride / 2; + composite.particles.push(new Phaser.Verlet.Particle(new Phaser.Vector2(px, py))); + if(x > 0) { + composite.constraints.push(new Phaser.Verlet.DistanceConstraint(composite.particles[y * segments + x], composite.particles[y * segments + x - 1], stiffness)); + } + if(y > 0) { + composite.constraints.push(new Phaser.Verlet.DistanceConstraint(composite.particles[y * segments + x], composite.particles[(y - 1) * segments + x], stiffness)); + } + } + } + for(x = 0; x < segments; ++x) { + if(x % pinMod == 0) { + composite.pin(x); + } + } + this.composites.push(composite); + return composite; + }; + VerletManager.prototype.createTire = function (origin, radius, segments, spokeStiffness, treadStiffness) { + var stride = (2 * Math.PI) / segments; + var i; + var composite = new Phaser.Verlet.Composite(this._game); + // particles + for(i = 0; i < segments; ++i) { + var theta = i * stride; + composite.particles.push(new Verlet.Particle(new Phaser.Vector2(origin.x + Math.cos(theta) * radius, origin.y + Math.sin(theta) * radius))); + } + var center = new Verlet.Particle(origin); + composite.particles.push(center); + // constraints + for(i = 0; i < segments; ++i) { + composite.constraints.push(new Verlet.DistanceConstraint(composite.particles[i], composite.particles[(i + 1) % segments], treadStiffness)); + composite.constraints.push(new Verlet.DistanceConstraint(composite.particles[i], center, spokeStiffness)); + composite.constraints.push(new Verlet.DistanceConstraint(composite.particles[i], composite.particles[(i + 5) % segments], treadStiffness)); + } + this.composites.push(composite); + return composite; + }; + VerletManager.prototype.update = function () { + if(this.composites.length == 0) { + return; + } + var i, j, c; + for(c in this.composites) { + for(i in this.composites[c].particles) { + var particles = this.composites[c].particles; + // calculate velocity + var velocity = particles[i].pos.sub(particles[i].lastPos).scale(this.friction); + // ground friction + if(particles[i].pos.y >= this.height - 1 && velocity.length2() > 0.000001) { + var m = velocity.length(); + velocity.x /= m; + velocity.y /= m; + velocity.mutableScale(m * this.groundFriction); + } + // save last good state + particles[i].lastPos.mutableSet(particles[i].pos); + // gravity + particles[i].pos.mutableAdd(this.gravity); + // inertia + particles[i].pos.mutableAdd(velocity); + } + } + // handle dragging of entities + if(this.draggedEntity) { + this.draggedEntity.pos.mutableSet(new Phaser.Vector2(this._game.input.x, this._game.input.y)); + } + // relax + var stepCoef = 1 / this.step; + for(c in this.composites) { + var constraints = this.composites[c].constraints; + for(i = 0; i < this.step; ++i) { + for(j in constraints) { + constraints[j].relax(stepCoef); + } + } + } + // bounds checking + for(c in this.composites) { + var particles = this.composites[c].particles; + for(i in particles) { + this.bounds(particles[i]); + } + } + }; + VerletManager.prototype.mouseDownHandler = function () { + var nearest = this.nearestEntity(); + if(nearest) { + this.draggedEntity = nearest; + } + }; + VerletManager.prototype.mouseUpHandler = function () { + this.draggedEntity = null; + }; + VerletManager.prototype.nearestEntity = function () { + var c, i; + var d2Nearest = 0; + var entity = null; + var constraintsNearest = null; + // find nearest point + for(c in this.composites) { + var particles = this.composites[c].particles; + for(i in particles) { + var d2 = particles[i].pos.dist2(new Phaser.Vector2(this._game.input.x, this._game.input.y)); + if(d2 <= this.selectionRadius * this.selectionRadius && (entity == null || d2 < d2Nearest)) { + entity = particles[i]; + constraintsNearest = this.composites[c].constraints; + d2Nearest = d2; + } + } + } + // search for pinned constraints for this entity + for(i in constraintsNearest) { + if(constraintsNearest[i] instanceof Verlet.PinConstraint && constraintsNearest[i].a == entity) { + entity = constraintsNearest[i]; + } + } + return entity; + }; + VerletManager.prototype.render = function () { + var i, c; + for(c in this.composites) { + // draw constraints + if(this.composites[c].drawConstraints) { + this.composites[c].drawConstraints(this.context, this.composites[c]); + } else { + var constraints = this.composites[c].constraints; + for(i in constraints) { + constraints[i].render(this.context); + } + } + // draw particles + if(this.composites[c].drawParticles) { + this.composites[c].drawParticles(this.context, this.composites[c]); + } else { + var particles = this.composites[c].particles; + for(i in particles) { + particles[i].render(this.context); + } + } + } + // highlight nearest / dragged entity + var nearest = this.draggedEntity || this.nearestEntity(); + if(nearest) { + this.context.beginPath(); + this.context.arc(nearest.pos.x, nearest.pos.y, 8, 0, 2 * Math.PI); + this.context.strokeStyle = this.highlightColor; + this.context.stroke(); + } + }; + return VerletManager; + })(); + Verlet.VerletManager = VerletManager; + })(Phaser.Verlet || (Phaser.Verlet = {})); + var Verlet = Phaser.Verlet; +})(Phaser || (Phaser = {})); /// /** * Phaser - World @@ -14847,7 +15581,9 @@ var Phaser; /// /// /// +/// /// +/// /// /// /// @@ -15025,6 +15761,7 @@ var Phaser; this.rnd = new Phaser.RandomDataGenerator([ (Date.now() * Math.random()).toString() ]); + this.verlet = new Phaser.Verlet.VerletManager(this, width, height); this.framerate = 60; this.isBooted = true; this.input.start(); @@ -15075,6 +15812,7 @@ var Phaser; this.tweens.update(); this.input.update(); this.stage.update(); + this.verlet.update(); this._accumulator += this.time.delta; if(this._accumulator > this._maxAccumulation) { this._accumulator = this._maxAccumulation; diff --git a/Tests/scrollzones/blasteroids.js b/Tests/scrollzones/blasteroids.js index 3fd1963e..f484ec2d 100644 --- a/Tests/scrollzones/blasteroids.js +++ b/Tests/scrollzones/blasteroids.js @@ -21,6 +21,9 @@ emitter = myGame.createEmitter(myGame.stage.centerX + 16, myGame.stage.centerY + 12); emitter.makeParticles('jet', 250, false, 0); emitter.setRotation(0, 0); + // Looks like a smoke trail! + //emitter.globalCompositeOperation = 'xor'; + emitter.globalCompositeOperation = 'lighter'; bullets = myGame.createGroup(50); // Create our bullet pool for(var i = 0; i < 50; i++) { diff --git a/Tests/scrollzones/blasteroids.ts b/Tests/scrollzones/blasteroids.ts index 45080fd2..af366a33 100644 --- a/Tests/scrollzones/blasteroids.ts +++ b/Tests/scrollzones/blasteroids.ts @@ -33,6 +33,12 @@ emitter.makeParticles('jet', 250, false, 0); emitter.setRotation(0, 0); + // Looks like a smoke trail! + //emitter.globalCompositeOperation = 'xor'; + + // Looks way cool :) + emitter.globalCompositeOperation = 'lighter'; + bullets = myGame.createGroup(50); // Create our bullet pool diff --git a/build/phaser.d.ts b/build/phaser.d.ts index ab96e09d..6fcb1ad4 100644 --- a/build/phaser.d.ts +++ b/build/phaser.d.ts @@ -2071,6 +2071,16 @@ module Phaser { **/ public polar(length, angle): void; /** + * Rotates the point around the x/y coordinates given to the desired angle + * @param x {number} The x coordinate of the anchor point + * @param y {number} The y coordinate of the anchor point + * @param {Number} angle The angle in radians (unless asDegrees is true) to return the point from. + * @param {Boolean} asDegrees Is the given angle in radians (false) or degrees (true)? + * @param {Number} distance An optional distance constraint between the point and the anchor + * @return The modified point object + */ + public rotate(cx: number, cy: number, angle: number, asDegrees?: bool, distance?: number): Point; + /** * Sets the x and y values of this Point object to the given coordinates. * @method setTo * @param {Number} x - The horizontal position of this point. @@ -2578,7 +2588,7 @@ module Phaser { * @param {Number} y The y coordinate of the top-left corner of the quad. * @param {Number} width The width of the quad. * @param {Number} height The height of the quad. - * @return {Quad } This object + * @return {Quad} This object **/ constructor(x?: number, y?: number, width?: number, height?: number); public x: number; @@ -4250,6 +4260,23 @@ module Phaser { * @return The array */ public shuffleArray(array); + /** + * Returns the distance from this Point object to the given Point object. + * @method distanceFrom + * @param {Point} target - The destination Point object. + * @param {Boolean} round - Round the distance to the nearest integer (default false) + * @return {Number} The distance between this Point object and the destination Point object. + **/ + static distanceBetween(x1: number, y1: number, x2: number, y2: number): number; + /** + * Rotates a point around the x/y coordinates given to the desired angle + * @param x {number} The x coordinate of the anchor point + * @param y {number} The y coordinate of the anchor point + * @param angle {number} The angle of the rotation in radians + * @param point {Point} The point object to perform the rotation on + * @return The modified point object + */ + public rotatePoint(x: number, y: number, angle: number, point); } } /** @@ -4261,6 +4288,23 @@ module Phaser { class Group extends Basic { constructor(game: Game, MaxSize?: number); /** + * Internal tracker for the maximum capacity of the group. + * Default is 0, or no max capacity. + */ + private _maxSize; + /** + * Internal helper variable for recycling objects a la Emitter. + */ + private _marker; + /** + * Helper for sort. + */ + private _sortIndex; + /** + * Helper for sort. + */ + private _sortOrder; + /** * Use with sort() to sort in ascending order. */ static ASCENDING: number; @@ -4279,22 +4323,18 @@ module Phaser { */ public length: number; /** - * Internal tracker for the maximum capacity of the group. - * Default is 0, or no max capacity. + * You can set a globalCompositeOperation that will be applied before the render method is called on this Groups children. + * This is useful if you wish to apply an effect like 'lighten' to a whole group of children as it saves doing it one-by-one. + * If this value is set it will call a canvas context save and restore before and after the render pass. + * Set to null to disable. */ - private _maxSize; + public globalCompositeOperation: string; /** - * Internal helper variable for recycling objects a la Emitter. + * You can set an alpha value on this Group that will be applied before the render method is called on this Groups children. + * This is useful if you wish to alpha a whole group of children as it saves doing it one-by-one. + * Set to 0 to disable. */ - private _marker; - /** - * Helper for sort. - */ - private _sortIndex; - /** - * Helper for sort. - */ - private _sortOrder; + public alpha: number; /** * Override this function to handle any deleting or "shutdown" type operations you might need, * such as removing traditional Flash children like Basic objects. @@ -5731,6 +5771,235 @@ module Phaser { } } /** +* Phaser - Vector2 +* +* A simple 2-dimensional vector class. Based on the one included with verlet-js by Sub Protocol released under MIT +*/ +module Phaser { + class Vector2 { + /** + * Creates a new Vector2 object. + * @class Vector2 + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Vector2} This object + **/ + constructor(x?: number, y?: number); + public x: number; + public y: number; + public setTo(x: number, y: number): Vector2; + public add(v: Vector2, output?: Vector2): Vector2; + public sub(v: Vector2, output?: Vector2): Vector2; + public mul(v: Vector2, output?: Vector2): Vector2; + public div(v: Vector2, output?: Vector2): Vector2; + public scale(coef: number, output?: Vector2): Vector2; + public mutableSet(v: Vector2): Vector2; + public mutableAdd(v: Vector2): Vector2; + public mutableSub(v: Vector2): Vector2; + public mutableMul(v: Vector2): Vector2; + public mutableDiv(v: Vector2): Vector2; + public mutableScale(coef: number): Vector2; + public equals(v: Vector2): bool; + public epsilonEquals(v: Vector2, epsilon: number): bool; + public length(): number; + public length2(): number; + public dist(v: Vector2): number; + public dist2(v: Vector2): number; + public normal(output?: Vector2): Vector2; + public dot(v: Vector2): number; + public angle(v: Vector2): number; + public angle2(vLeft: Vector2, vRight: Vector2): number; + public rotate(origin, theta, output?: Vector2): Vector2; + /** + * Returns a string representation of this object. + * @method toString + * @return {string} a string representation of the object. + **/ + public toString(): string; + } +} +/** +* Phaser - Verlet - Particle +* +* +*/ +module Phaser.Verlet { + class Particle { + /** + * Creates a new Particle object. + * @class Particle + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Particle} This object + **/ + constructor(pos: Vector2); + public pos: Vector2; + public lastPos: Vector2; + public render(ctx): void; + } +} +/** +* Phaser - PinConstraint +* +* Constrains to static / fixed point +*/ +module Phaser.Verlet { + class PinConstraint { + /** + * Creates a new PinConstraint object. + * @class PinConstraint + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {PinConstraint} This object + **/ + constructor(a: Particle, pos: Vector2); + public a: Particle; + public pos: Vector2; + public relax(): void; + public render(ctx): void; + } +} +/** +* Phaser - Verlet - Composite +* +* +*/ +module Phaser.Verlet { + class Composite { + /** + * Creates a new Composite object. + * @class Composite + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Composite} This object + **/ + constructor(game: Game); + private _game; + public particles: Particle[]; + public constraints; + public drawParticles; + public drawConstraints; + public createDistanceConstraint(a: Particle, b: Particle, stiffness: number, distance?: number): DistanceConstraint; + public createAngleConstraint(a: Particle, b: Particle, c: Particle, stiffness: number): AngleConstraint; + public createPinConstraint(a: Particle, pos: Vector2): PinConstraint; + public pin(index, pos?): PinConstraint; + } +} +/** +* Phaser - DistanceConstraint +* +* Constrains to initial distance +*/ +module Phaser.Verlet { + class DistanceConstraint { + /** + * Creates a new DistanceConstraint object. + * @class DistanceConstraint + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {DistanceConstraint} This object + **/ + constructor(a: Particle, b: Particle, stiffness: number, distance?: number); + public a: Particle; + public b: Particle; + public distance: number; + public stiffness: number; + public relax(stepCoef: number): void; + public render(ctx): void; + } +} +/** +* Phaser - AngleConstraint +* +* constrains 3 particles to an angle +*/ +module Phaser.Verlet { + class AngleConstraint { + /** + * Creates a new AngleConstraint object. + * @class AngleConstraint + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {AngleConstraint} This object + **/ + constructor(a: Particle, b: Particle, c: Particle, stiffness: number); + public a: Particle; + public b: Particle; + public c: Particle; + public angle: number; + public stiffness: number; + public relax(stepCoef: number): void; + public render(ctx): void; + } +} +/** +* Phaser - Verlet +* +* Based on verlet-js by Sub Protocol released under MIT +*/ +module Phaser.Verlet { + class VerletManager { + /** + * Creates a new Vector2 object. + * @class Vector2 + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Vector2} This object + **/ + constructor(game: Game, width: number, height: number); + private _game; + public composites: any[]; + public width: number; + public height: number; + public step: number; + public gravity: Vector2; + public friction: number; + public groundFriction: number; + public selectionRadius: number; + public draggedEntity; + public highlightColor: string; + /** + * This class is actually a wrapper of canvas. + * @type {HTMLCanvasElement} + */ + public canvas: HTMLCanvasElement; + /** + * Canvas context of this object. + * @type {CanvasRenderingContext2D} + */ + public context: CanvasRenderingContext2D; + /** + * Computes time of intersection of a particle with a wall + * + * @param {Vec2} line wall's root position + * @param {Vec2} p particle's position + * @param {Vec2} dir walls's direction + * @param {Vec2} v particle's velocity + */ + public intersectionTime(wall, p, dir, v): number; + public intersectionPoint(wall, p, dir, v): Vector2; + private v; + public bounds(particle: Particle): void; + public OLDbounds(particle: Particle): void; + public createPoint(pos: Vector2): Composite; + public createLineSegments(vertices, stiffness): Composite; + public createCloth(origin, width, height, segments, pinMod, stiffness): Composite; + public createTire(origin, radius, segments, spokeStiffness, treadStiffness): Composite; + public update(): void; + private mouseDownHandler(); + private mouseUpHandler(); + public nearestEntity(); + public render(): void; + } +} +/** * Phaser - World * * "This world is but a canvas to our imagination." - Henry David Thoreau @@ -8590,6 +8859,11 @@ module Phaser { */ public tweens: TweenManager; /** + * Reference to the verlet manager. + * @type {VerletManager} + */ + public verlet: Verlet.VerletManager; + /** * Reference to the world. * @type {World} */ diff --git a/build/phaser.js b/build/phaser.js index 54aa3d08..1d5e6f54 100644 --- a/build/phaser.js +++ b/build/phaser.js @@ -3286,6 +3286,27 @@ var Phaser; **/ function (length, angle) { }; + Point.prototype.rotate = /** + * Rotates the point around the x/y coordinates given to the desired angle + * @param x {number} The x coordinate of the anchor point + * @param y {number} The y coordinate of the anchor point + * @param {Number} angle The angle in radians (unless asDegrees is true) to return the point from. + * @param {Boolean} asDegrees Is the given angle in radians (false) or degrees (true)? + * @param {Number} distance An optional distance constraint between the point and the anchor + * @return The modified point object + */ + function (cx, cy, angle, asDegrees, distance) { + if (typeof asDegrees === "undefined") { asDegrees = false; } + if (typeof distance === "undefined") { distance = null; } + if(asDegrees) { + angle = angle * Phaser.GameMath.DEG_TO_RAD; + } + // Get distance from origin (cx/cy) to this point + if(distance === null) { + distance = Math.sqrt(((cx - this.x) * (cx - this.x)) + ((cy - this.y) * (cy - this.y))); + } + return this.setTo(cx + distance * Math.cos(angle), cy + distance * Math.sin(angle)); + }; Point.prototype.setTo = /** * Sets the x and y values of this Point object to the given coordinates. * @method setTo @@ -4081,7 +4102,7 @@ var Phaser; * @param {Number} y The y coordinate of the top-left corner of the quad. * @param {Number} width The width of the quad. * @param {Number} height The height of the quad. - * @return {Quad } This object + * @return {Quad} This object **/ function Quad(x, y, width, height) { if (typeof x === "undefined") { x = 0; } @@ -5290,7 +5311,6 @@ var Phaser; continue; } if(QuadTree._object.collisionMask.checkHullIntersection(checkObject.collisionMask)) { - console.log('quad hull'); //Execute callback functions if they exist if((QuadTree._processingCallback == null) || QuadTree._processingCallback(QuadTree._object, checkObject)) { overlapProcessed = true; @@ -7312,6 +7332,35 @@ var Phaser; } return array; }; + GameMath.distanceBetween = /** + * Returns the distance from this Point object to the given Point object. + * @method distanceFrom + * @param {Point} target - The destination Point object. + * @param {Boolean} round - Round the distance to the nearest integer (default false) + * @return {Number} The distance between this Point object and the destination Point object. + **/ + function distanceBetween(x1, y1, x2, y2) { + var dx = x1 - x2; + var dy = y1 - y2; + return Math.sqrt(dx * dx + dy * dy); + }; + GameMath.prototype.rotatePoint = /** + * Rotates a point around the x/y coordinates given to the desired angle + * @param x {number} The x coordinate of the anchor point + * @param y {number} The y coordinate of the anchor point + * @param angle {number} The angle of the rotation in radians + * @param point {Point} The point object to perform the rotation on + * @return The modified point object + */ + function (x, y, angle, point) { + var s = Math.sin(angle); + var c = Math.cos(angle); + point.x -= x; + point.y -= y; + var newX = point.x * c - point.y * s; + var newY = point.x * s - point.y * c; + return point.setTo(newX + x, newY + y); + }; return GameMath; })(); Phaser.GameMath = GameMath; @@ -7330,6 +7379,19 @@ var Phaser; function Group(game, MaxSize) { if (typeof MaxSize === "undefined") { MaxSize = 0; } _super.call(this, game); + /** + * You can set a globalCompositeOperation that will be applied before the render method is called on this Groups children. + * This is useful if you wish to apply an effect like 'lighten' to a whole group of children as it saves doing it one-by-one. + * If this value is set it will call a canvas context save and restore before and after the render pass. + * Set to null to disable. + */ + this.globalCompositeOperation = null; + /** + * You can set an alpha value on this Group that will be applied before the render method is called on this Groups children. + * This is useful if you wish to alpha a whole group of children as it saves doing it one-by-one. + * Set to 0 to disable. + */ + this.alpha = 0; this.isGroup = true; this.members = []; this.length = 0; @@ -7384,6 +7446,14 @@ var Phaser; if(this.ignoreGlobalRender && forceRender == false) { return; } + if(this.globalCompositeOperation) { + this._game.stage.context.save(); + this._game.stage.context.globalCompositeOperation = this.globalCompositeOperation; + } + if(this.alpha > 0) { + var prevAlpha = this._game.stage.context.globalAlpha; + this._game.stage.context.globalAlpha = this.alpha; + } var basic; var i = 0; while(i < this.length) { @@ -7392,6 +7462,12 @@ var Phaser; basic.render(camera, cameraOffsetX, cameraOffsetY, forceRender); } } + if(this.alpha > 0) { + this._game.stage.context.globalAlpha = prevAlpha; + } + if(this.globalCompositeOperation) { + this._game.stage.context.restore(); + } }; Object.defineProperty(Group.prototype, "maxSize", { get: /** @@ -9286,6 +9362,9 @@ var Phaser; this.canvas.style['ms-touch-action'] = 'none'; this.canvas.style['touch-action'] = 'none'; this.canvas.style.backgroundColor = 'rgb(0,0,0)'; + this.canvas.oncontextmenu = function (event) { + event.preventDefault(); + }; this.context = this.canvas.getContext('2d'); this.offset = this.getOffset(this.canvas); this.bounds = new Phaser.Quad(this.offset.x, this.offset.y, width, height); @@ -10278,6 +10357,661 @@ var Phaser; })(); Phaser.TweenManager = TweenManager; })(Phaser || (Phaser = {})); +/// +/** +* Phaser - Vector2 +* +* A simple 2-dimensional vector class. Based on the one included with verlet-js by Sub Protocol released under MIT +*/ +var Phaser; +(function (Phaser) { + var Vector2 = (function () { + /** + * Creates a new Vector2 object. + * @class Vector2 + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Vector2} This object + **/ + function Vector2(x, y) { + if (typeof x === "undefined") { x = 0; } + if (typeof y === "undefined") { y = 0; } + this.x = x; + this.y = y; + } + Vector2.prototype.setTo = function (x, y) { + this.x = x; + this.y = y; + return this; + }; + Vector2.prototype.add = function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x + v.x, this.y + v.y); + }; + Vector2.prototype.sub = function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x - v.x, this.y - v.y); + }; + Vector2.prototype.mul = function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x * v.x, this.y * v.y); + }; + Vector2.prototype.div = function (v, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x / v.x, this.y / v.y); + }; + Vector2.prototype.scale = function (coef, output) { + if (typeof output === "undefined") { output = new Vector2(); } + return output.setTo(this.x * coef, this.y * coef); + }; + Vector2.prototype.mutableSet = function (v) { + this.x = v.x; + this.y = v.y; + return this; + }; + Vector2.prototype.mutableAdd = function (v) { + this.x += v.x; + this.y += v.y; + return this; + }; + Vector2.prototype.mutableSub = function (v) { + this.x -= v.x; + this.y -= v.y; + return this; + }; + Vector2.prototype.mutableMul = function (v) { + this.x *= v.x; + this.y *= v.y; + return this; + }; + Vector2.prototype.mutableDiv = function (v) { + this.x /= v.x; + this.y /= v.y; + return this; + }; + Vector2.prototype.mutableScale = function (coef) { + this.x *= coef; + this.y *= coef; + return this; + }; + Vector2.prototype.equals = function (v) { + return this.x == v.x && this.y == v.y; + }; + Vector2.prototype.epsilonEquals = function (v, epsilon) { + return Math.abs(this.x - v.x) <= epsilon && Math.abs(this.y - v.y) <= epsilon; + }; + Vector2.prototype.length = function () { + return Math.sqrt(this.x * this.x + this.y * this.y); + }; + Vector2.prototype.length2 = function () { + return this.x * this.x + this.y * this.y; + }; + Vector2.prototype.dist = function (v) { + return Math.sqrt(this.dist2(v)); + }; + Vector2.prototype.dist2 = function (v) { + return ((v.x - this.x) * (v.x - this.x)) + ((v.y - this.y) * (v.y - this.y)); + }; + Vector2.prototype.normal = function (output) { + if (typeof output === "undefined") { output = new Vector2(); } + var m = Math.sqrt(this.x * this.x + this.y * this.y); + return output.setTo(this.x / m, this.y / m); + }; + Vector2.prototype.dot = function (v) { + return this.x * v.x + this.y * v.y; + }; + Vector2.prototype.angle = function (v) { + return Math.atan2(this.x * v.y - this.y * v.x, this.x * v.x + this.y * v.y); + }; + Vector2.prototype.angle2 = function (vLeft, vRight) { + return vLeft.sub(this).angle(vRight.sub(this)); + }; + Vector2.prototype.rotate = function (origin, theta, output) { + if (typeof output === "undefined") { output = new Vector2(); } + var x = this.x - origin.x; + var y = this.y - origin.y; + return output.setTo(x * Math.cos(theta) - y * Math.sin(theta) + origin.x, x * Math.sin(theta) + y * Math.cos(theta) + origin.y); + }; + Vector2.prototype.toString = /** + * Returns a string representation of this object. + * @method toString + * @return {string} a string representation of the object. + **/ + function () { + return "[{Vector2 (x=" + this.x + " y=" + this.y + ")}]"; + }; + return Vector2; + })(); + Phaser.Vector2 = Vector2; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /** + * Phaser - Verlet - Particle + * + * + */ + (function (Verlet) { + var Particle = (function () { + /** + * Creates a new Particle object. + * @class Particle + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Particle} This object + **/ + function Particle(pos) { + this.pos = (new Phaser.Vector2()).mutableSet(pos); + this.lastPos = (new Phaser.Vector2()).mutableSet(pos); + } + Particle.prototype.render = function (ctx) { + ctx.beginPath(); + ctx.arc(this.pos.x, this.pos.y, 2, 0, 2 * Math.PI); + ctx.fillStyle = "#2dad8f"; + ctx.fill(); + }; + return Particle; + })(); + Verlet.Particle = Particle; + })(Phaser.Verlet || (Phaser.Verlet = {})); + var Verlet = Phaser.Verlet; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /// + /** + * Phaser - PinConstraint + * + * Constrains to static / fixed point + */ + (function (Verlet) { + var PinConstraint = (function () { + /** + * Creates a new PinConstraint object. + * @class PinConstraint + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {PinConstraint} This object + **/ + function PinConstraint(a, pos) { + this.a = a; + this.pos = (new Phaser.Vector2()).mutableSet(pos); + } + PinConstraint.prototype.relax = function () { + this.a.pos.mutableSet(this.pos); + }; + PinConstraint.prototype.render = function (ctx) { + ctx.beginPath(); + ctx.arc(this.pos.x, this.pos.y, 6, 0, 2 * Math.PI); + ctx.fillStyle = "rgba(0,153,255,0.1)"; + ctx.fill(); + }; + return PinConstraint; + })(); + Verlet.PinConstraint = PinConstraint; + })(Phaser.Verlet || (Phaser.Verlet = {})); + var Verlet = Phaser.Verlet; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /// + /// + /** + * Phaser - Verlet - Composite + * + * + */ + (function (Verlet) { + var Composite = (function () { + /** + * Creates a new Composite object. + * @class Composite + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Composite} This object + **/ + function Composite(game) { + this.drawParticles = null; + this.drawConstraints = null; + this._game = game; + this.particles = []; + this.constraints = []; + } + Composite.prototype.createDistanceConstraint = // Map sprites to particles + function (a, b, stiffness, distance) { + if (typeof distance === "undefined") { distance = null; } + this.constraints.push(new Phaser.Verlet.DistanceConstraint(a, b, stiffness, distance)); + return this.constraints[this.constraints.length - 1]; + }; + Composite.prototype.createAngleConstraint = function (a, b, c, stiffness) { + this.constraints.push(new Phaser.Verlet.AngleConstraint(a, b, c, stiffness)); + return this.constraints[this.constraints.length - 1]; + }; + Composite.prototype.createPinConstraint = function (a, pos) { + this.constraints.push(new Phaser.Verlet.PinConstraint(a, pos)); + return this.constraints[this.constraints.length - 1]; + }; + Composite.prototype.pin = function (index, pos) { + if (typeof pos === "undefined") { pos = null; } + if(pos == null) { + pos = this.particles[index].pos; + } + var pc = new Phaser.Verlet.PinConstraint(this.particles[index], pos); + this.constraints.push(pc); + return pc; + }; + return Composite; + })(); + Verlet.Composite = Composite; + })(Phaser.Verlet || (Phaser.Verlet = {})); + var Verlet = Phaser.Verlet; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /// + /** + * Phaser - DistanceConstraint + * + * Constrains to initial distance + */ + (function (Verlet) { + var DistanceConstraint = (function () { + /** + * Creates a new DistanceConstraint object. + * @class DistanceConstraint + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {DistanceConstraint} This object + **/ + function DistanceConstraint(a, b, stiffness, distance) { + if (typeof distance === "undefined") { distance = null; } + this.a = a; + this.b = b; + if(distance === null) { + this.distance = a.pos.sub(b.pos).length(); + } else { + this.distance = distance; + } + this.stiffness = stiffness; + } + DistanceConstraint.prototype.relax = function (stepCoef) { + var normal = this.a.pos.sub(this.b.pos); + var m = normal.length2(); + normal.mutableScale(((this.distance * this.distance - m) / m) * this.stiffness * stepCoef); + this.a.pos.mutableAdd(normal); + this.b.pos.mutableSub(normal); + }; + DistanceConstraint.prototype.render = function (ctx) { + ctx.beginPath(); + ctx.moveTo(this.a.pos.x, this.a.pos.y); + ctx.lineTo(this.b.pos.x, this.b.pos.y); + ctx.strokeStyle = "#d8dde2"; + ctx.stroke(); + }; + return DistanceConstraint; + })(); + Verlet.DistanceConstraint = DistanceConstraint; + })(Phaser.Verlet || (Phaser.Verlet = {})); + var Verlet = Phaser.Verlet; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /// + /** + * Phaser - AngleConstraint + * + * constrains 3 particles to an angle + */ + (function (Verlet) { + var AngleConstraint = (function () { + /** + * Creates a new AngleConstraint object. + * @class AngleConstraint + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {AngleConstraint} This object + **/ + function AngleConstraint(a, b, c, stiffness) { + this.a = a; + this.b = b; + this.c = c; + this.angle = this.b.pos.angle2(this.a.pos, this.c.pos); + this.stiffness = stiffness; + } + AngleConstraint.prototype.relax = function (stepCoef) { + var angle = this.b.pos.angle2(this.a.pos, this.c.pos); + var diff = angle - this.angle; + if(diff <= -Math.PI) { + diff += 2 * Math.PI; + } else if(diff >= Math.PI) { + diff -= 2 * Math.PI; + } + diff *= stepCoef * this.stiffness; + this.a.pos = this.a.pos.rotate(this.b.pos, diff); + this.c.pos = this.c.pos.rotate(this.b.pos, -diff); + this.b.pos = this.b.pos.rotate(this.a.pos, diff); + this.b.pos = this.b.pos.rotate(this.c.pos, -diff); + }; + AngleConstraint.prototype.render = function (ctx) { + ctx.beginPath(); + ctx.moveTo(this.a.pos.x, this.a.pos.y); + ctx.lineTo(this.b.pos.x, this.b.pos.y); + ctx.lineTo(this.c.pos.x, this.c.pos.y); + var tmp = ctx.lineWidth; + ctx.lineWidth = 5; + ctx.strokeStyle = "rgba(255,255,0,0.2)"; + ctx.stroke(); + ctx.lineWidth = tmp; + }; + return AngleConstraint; + })(); + Verlet.AngleConstraint = AngleConstraint; + })(Phaser.Verlet || (Phaser.Verlet = {})); + var Verlet = Phaser.Verlet; +})(Phaser || (Phaser = {})); +var Phaser; +(function (Phaser) { + /// + /// + /// + /// + /// + /// + /// + /** + * Phaser - Verlet + * + * Based on verlet-js by Sub Protocol released under MIT + */ + (function (Verlet) { + var VerletManager = (function () { + /** + * Creates a new Vector2 object. + * @class Vector2 + * @constructor + * @param {Number} x The x coordinate of vector2 + * @param {Number} y The y coordinate of vector2 + * @return {Vector2} This object + **/ + function VerletManager(game, width, height) { + this.composites = []; + this.step = 32; + this.selectionRadius = 20; + this.draggedEntity = null; + this.highlightColor = '#4f545c'; + this.v = new Phaser.Vector2(); + this._game = game; + this.width = width; + this.height = height; + this.gravity = new Phaser.Vector2(0, 0.2); + this.friction = 0.99; + this.groundFriction = 0.8; + this.canvas = game.stage.canvas; + this.context = game.stage.context; + this._game.input.onDown.add(this.mouseDownHandler, this); + this._game.input.onUp.add(this.mouseUpHandler, this); + } + VerletManager.prototype.intersectionTime = /** + * Computes time of intersection of a particle with a wall + * + * @param {Vec2} line wall's root position + * @param {Vec2} p particle's position + * @param {Vec2} dir walls's direction + * @param {Vec2} v particle's velocity + */ + function (wall, p, dir, v) { + if(dir.x != 0) { + var denominator = v.y - dir.y * v.x / dir.x; + if(denominator == 0) { + return undefined; + }// Movement is parallel to wall + + var numerator = wall.y + dir.y * (p.x - wall.x) / dir.x - p.y; + return numerator / denominator; + } else { + if(v.x == 0) { + return undefined; + }// parallel again + + var denominator = v.x; + var numerator = wall.x - p.x; + return numerator / denominator; + } + }; + VerletManager.prototype.intersectionPoint = function (wall, p, dir, v) { + var t = this.intersectionTime(wall, p, dir, v); + return new Phaser.Vector2(p.x + v.x * t, p.y + v.y * t); + }; + VerletManager.prototype.bounds = function (particle) { + this.v.mutableSet(particle.pos); + this.v.mutableSub(particle.lastPos); + if(particle.pos.y > this.height - 1) { + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, this.height - 1), particle.lastPos, new Phaser.Vector2(1, 0), this.v)); + } + if(particle.pos.x < 0) { + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(0, 0), particle.pos, new Phaser.Vector2(0, 1), this.v)); + } + if(particle.pos.x > this.width - 1) { + particle.pos.mutableSet(this.intersectionPoint(new Phaser.Vector2(this.width - 1, 0), particle.pos, new Phaser.Vector2(0, 1), this.v)); + } + }; + VerletManager.prototype.OLDbounds = function (particle) { + if(particle.pos.y > this.height - 1) { + particle.pos.y = this.height - 1; + } + if(particle.pos.x < 0) { + var vx = particle.pos.x - particle.lastPos.x; + var vy = particle.pos.y - particle.lastPos.y; + if(vx == 0) { + particle.pos.x = 0; + } else { + var t = -particle.lastPos.x / vx; + particle.pos.x = particle.lastPos.x + t * vx; + particle.pos.y = particle.lastPos.y + t * vy; + } + } + if(particle.pos.x > this.width - 1) { + particle.pos.x = this.width - 1; + } + }; + VerletManager.prototype.createPoint = function (pos) { + var composite = new Phaser.Verlet.Composite(this._game); + composite.particles.push(new Phaser.Verlet.Particle(pos)); + this.composites.push(composite); + return composite; + }; + VerletManager.prototype.createLineSegments = function (vertices, stiffness) { + var i; + var composite = new Phaser.Verlet.Composite(this._game); + for(i in vertices) { + composite.particles.push(new Phaser.Verlet.Particle(vertices[i])); + if(i > 0) { + composite.constraints.push(new Phaser.Verlet.DistanceConstraint(composite.particles[i], composite.particles[i - 1], stiffness)); + } + } + this.composites.push(composite); + return composite; + }; + VerletManager.prototype.createCloth = function (origin, width, height, segments, pinMod, stiffness) { + var composite = new Phaser.Verlet.Composite(this._game); + var xStride = width / segments; + var yStride = height / segments; + var x, y; + for(y = 0; y < segments; ++y) { + for(x = 0; x < segments; ++x) { + var px = origin.x + x * xStride - width / 2 + xStride / 2; + var py = origin.y + y * yStride - height / 2 + yStride / 2; + composite.particles.push(new Phaser.Verlet.Particle(new Phaser.Vector2(px, py))); + if(x > 0) { + composite.constraints.push(new Phaser.Verlet.DistanceConstraint(composite.particles[y * segments + x], composite.particles[y * segments + x - 1], stiffness)); + } + if(y > 0) { + composite.constraints.push(new Phaser.Verlet.DistanceConstraint(composite.particles[y * segments + x], composite.particles[(y - 1) * segments + x], stiffness)); + } + } + } + for(x = 0; x < segments; ++x) { + if(x % pinMod == 0) { + composite.pin(x); + } + } + this.composites.push(composite); + return composite; + }; + VerletManager.prototype.createTire = function (origin, radius, segments, spokeStiffness, treadStiffness) { + var stride = (2 * Math.PI) / segments; + var i; + var composite = new Phaser.Verlet.Composite(this._game); + // particles + for(i = 0; i < segments; ++i) { + var theta = i * stride; + composite.particles.push(new Verlet.Particle(new Phaser.Vector2(origin.x + Math.cos(theta) * radius, origin.y + Math.sin(theta) * radius))); + } + var center = new Verlet.Particle(origin); + composite.particles.push(center); + // constraints + for(i = 0; i < segments; ++i) { + composite.constraints.push(new Verlet.DistanceConstraint(composite.particles[i], composite.particles[(i + 1) % segments], treadStiffness)); + composite.constraints.push(new Verlet.DistanceConstraint(composite.particles[i], center, spokeStiffness)); + composite.constraints.push(new Verlet.DistanceConstraint(composite.particles[i], composite.particles[(i + 5) % segments], treadStiffness)); + } + this.composites.push(composite); + return composite; + }; + VerletManager.prototype.update = function () { + if(this.composites.length == 0) { + return; + } + var i, j, c; + for(c in this.composites) { + for(i in this.composites[c].particles) { + var particles = this.composites[c].particles; + // calculate velocity + var velocity = particles[i].pos.sub(particles[i].lastPos).scale(this.friction); + // ground friction + if(particles[i].pos.y >= this.height - 1 && velocity.length2() > 0.000001) { + var m = velocity.length(); + velocity.x /= m; + velocity.y /= m; + velocity.mutableScale(m * this.groundFriction); + } + // save last good state + particles[i].lastPos.mutableSet(particles[i].pos); + // gravity + particles[i].pos.mutableAdd(this.gravity); + // inertia + particles[i].pos.mutableAdd(velocity); + } + } + // handle dragging of entities + if(this.draggedEntity) { + this.draggedEntity.pos.mutableSet(new Phaser.Vector2(this._game.input.x, this._game.input.y)); + } + // relax + var stepCoef = 1 / this.step; + for(c in this.composites) { + var constraints = this.composites[c].constraints; + for(i = 0; i < this.step; ++i) { + for(j in constraints) { + constraints[j].relax(stepCoef); + } + } + } + // bounds checking + for(c in this.composites) { + var particles = this.composites[c].particles; + for(i in particles) { + this.bounds(particles[i]); + } + } + }; + VerletManager.prototype.mouseDownHandler = function () { + var nearest = this.nearestEntity(); + if(nearest) { + this.draggedEntity = nearest; + } + }; + VerletManager.prototype.mouseUpHandler = function () { + this.draggedEntity = null; + }; + VerletManager.prototype.nearestEntity = function () { + var c, i; + var d2Nearest = 0; + var entity = null; + var constraintsNearest = null; + // find nearest point + for(c in this.composites) { + var particles = this.composites[c].particles; + for(i in particles) { + var d2 = particles[i].pos.dist2(new Phaser.Vector2(this._game.input.x, this._game.input.y)); + if(d2 <= this.selectionRadius * this.selectionRadius && (entity == null || d2 < d2Nearest)) { + entity = particles[i]; + constraintsNearest = this.composites[c].constraints; + d2Nearest = d2; + } + } + } + // search for pinned constraints for this entity + for(i in constraintsNearest) { + if(constraintsNearest[i] instanceof Verlet.PinConstraint && constraintsNearest[i].a == entity) { + entity = constraintsNearest[i]; + } + } + return entity; + }; + VerletManager.prototype.render = function () { + var i, c; + for(c in this.composites) { + // draw constraints + if(this.composites[c].drawConstraints) { + this.composites[c].drawConstraints(this.context, this.composites[c]); + } else { + var constraints = this.composites[c].constraints; + for(i in constraints) { + constraints[i].render(this.context); + } + } + // draw particles + if(this.composites[c].drawParticles) { + this.composites[c].drawParticles(this.context, this.composites[c]); + } else { + var particles = this.composites[c].particles; + for(i in particles) { + particles[i].render(this.context); + } + } + } + // highlight nearest / dragged entity + var nearest = this.draggedEntity || this.nearestEntity(); + if(nearest) { + this.context.beginPath(); + this.context.arc(nearest.pos.x, nearest.pos.y, 8, 0, 2 * Math.PI); + this.context.strokeStyle = this.highlightColor; + this.context.stroke(); + } + }; + return VerletManager; + })(); + Verlet.VerletManager = VerletManager; + })(Phaser.Verlet || (Phaser.Verlet = {})); + var Verlet = Phaser.Verlet; +})(Phaser || (Phaser = {})); /// /** * Phaser - World @@ -14847,7 +15581,9 @@ var Phaser; /// /// /// +/// /// +/// /// /// /// @@ -15025,6 +15761,7 @@ var Phaser; this.rnd = new Phaser.RandomDataGenerator([ (Date.now() * Math.random()).toString() ]); + this.verlet = new Phaser.Verlet.VerletManager(this, width, height); this.framerate = 60; this.isBooted = true; this.input.start(); @@ -15075,6 +15812,7 @@ var Phaser; this.tweens.update(); this.input.update(); this.stage.update(); + this.verlet.update(); this._accumulator += this.time.delta; if(this._accumulator > this._maxAccumulation) { this._accumulator = this._maxAccumulation;