diff --git a/Docs/phaser_rotate4.png b/Docs/phaser_rotate4.png new file mode 100644 index 00000000..fee589a0 Binary files /dev/null and b/Docs/phaser_rotate4.png differ 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;