diff --git a/README.md b/README.md index 645089d1..bf92713b 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ New features: * Math.normalizeAngle - normalises an angle, now in radians only. * Math.normalizeLatitude - Normalizes a latitude to the [-90,90] range. * Math.normalizeLongitude - Normalizes a longitude to the [-180,180] range. +* Phaser.Line added to the group of geometry classes, with full point on line/segment and intersection tests (see new examples) New Examples: diff --git a/build/config.php b/build/config.php index 32790143..015c4cff 100644 --- a/build/config.php +++ b/build/config.php @@ -104,6 +104,7 @@ + diff --git a/examples/geometry/line intersection.js b/examples/geometry/line intersection.js new file mode 100644 index 00000000..f6134f80 --- /dev/null +++ b/examples/geometry/line intersection.js @@ -0,0 +1,80 @@ + +var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render }); + +function preload() { + + game.load.spritesheet('balls', 'assets/sprites/balls.png', 17, 17); + +} + +var handle1; +var handle2; +var handle3; +var handle4; + +var line1; +var line2; + +function create() { + + game.stage.backgroundColor = '#124184'; + + handle1 = game.add.sprite(100, 200, 'balls', 0); + handle1.inputEnabled = true; + handle1.input.enableDrag(true); + + handle2 = game.add.sprite(400, 300, 'balls', 0); + handle2.inputEnabled = true; + handle2.input.enableDrag(true); + + handle3 = game.add.sprite(200, 400, 'balls', 1); + handle3.inputEnabled = true; + handle3.input.enableDrag(true); + + handle4 = game.add.sprite(500, 500, 'balls', 1); + handle4.inputEnabled = true; + handle4.input.enableDrag(true); + + line1 = new Phaser.Line(handle1.x, handle1.y, handle2.x, handle2.y); + line2 = new Phaser.Line(handle3.x, handle3.y, handle4.x, handle4.y); + +} + +var c = 'rgb(255,255,255)'; +var p = new Phaser.Point(); + +function update() { + + line1.fromSprite(handle1, handle2, true); + line2.fromSprite(handle3, handle4, true); + + p = line1.intersects(line2, true); + + if (p) + { + c = 'rgb(0,255,0)'; + } + else + { + c = 'rgb(255,255,255)'; + } + +} + +function render() { + + game.debug.renderLine(line1, c); + game.debug.renderLine(line2, c); + + game.debug.renderLineInfo(line1, 32, 32); + game.debug.renderLineInfo(line2, 32, 100); + + if (p) + { + game.context.fillStyle = 'rgb(255,0,255)'; + game.context.fillRect(p.x - 2, p.y - 2, 5, 5); + } + + game.debug.renderText("Drag the handles", 32, 550); + +} \ No newline at end of file diff --git a/examples/geometry/line.js b/examples/geometry/line.js index 61b3f502..70a71706 100644 --- a/examples/geometry/line.js +++ b/examples/geometry/line.js @@ -1,17 +1,44 @@ -var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { create: create }); +var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render }); + +function preload() { + + game.load.spritesheet('balls', 'assets/sprites/balls.png', 17, 17); + +} + +var handle1; +var handle2; + +var line1; function create() { - var graphics = game.add.graphics(50,50); + game.stage.backgroundColor = '#124184'; - // set a fill and line style - graphics.beginFill(0xFF0000); - graphics.lineStyle(10, 0xFF0000, 1); - - // draw a shape - graphics.moveTo(50,50); - graphics.lineTo(250, 50); - graphics.endFill(); + handle1 = game.add.sprite(100, 200, 'balls', 0); + handle1.inputEnabled = true; + handle1.input.enableDrag(true); + + handle2 = game.add.sprite(400, 300, 'balls', 0); + handle2.inputEnabled = true; + handle2.input.enableDrag(true); + + line1 = new Phaser.Line(handle1.x, handle1.y, handle2.x, handle2.y); } + +function update() { + + line1.fromSprite(handle1, handle2, true); + +} + +function render() { + + game.debug.renderLine(line1); + game.debug.renderLineInfo(line1, 32, 32); + + game.debug.renderText("Drag the handles", 32, 550); + +} \ No newline at end of file diff --git a/examples/wip/line intersection.js b/examples/wip/line intersection.js index b2278478..5efe8f0c 100644 --- a/examples/wip/line intersection.js +++ b/examples/wip/line intersection.js @@ -7,37 +7,39 @@ function preload() { } -var lineA1; -var lineA2; -var lineB1; -var lineB2; +var handle1; +var handle2; +var handle3; +var handle4; +var line1; +var line2; function create() { game.stage.backgroundColor = '#124184'; - lineA1 = game.add.sprite(100, 100, 'balls', 0); - lineA1.inputEnabled = true; - lineA1.input.enableDrag(true); + handle1 = game.add.sprite(100, 200, 'balls', 0); + handle1.inputEnabled = true; + handle1.input.enableDrag(true); - lineA2 = game.add.sprite(400, 300, 'balls', 0); - lineA2.inputEnabled = true; - lineA2.input.enableDrag(true); + handle2 = game.add.sprite(400, 300, 'balls', 0); + handle2.inputEnabled = true; + handle2.input.enableDrag(true); - lineB1 = game.add.sprite(300, 300, 'balls', 1); - lineB1.inputEnabled = true; - lineB1.input.enableDrag(true); + handle3 = game.add.sprite(200, 400, 'balls', 1); + handle3.inputEnabled = true; + handle3.input.enableDrag(true); - lineB2 = game.add.sprite(500, 500, 'balls', 1); - lineB2.inputEnabled = true; - lineB2.input.enableDrag(true); + handle4 = game.add.sprite(500, 500, 'balls', 1); + handle4.inputEnabled = true; + handle4.input.enableDrag(true); + line1 = new Phaser.Line(handle1.x, handle1.y, handle2.x, handle2.y); + line2 = new Phaser.Line(handle3.x, handle3.y, handle4.x, handle4.y); } - - /*--------------------------------------------------------------------------- Returns an Object with the following properties: intersects -Boolean indicating if an intersection exists. @@ -113,78 +115,15 @@ function lineIntersectPoly(A, B, pArry) { } */ -//--------------------------------------------------------------- -//Checks for intersection of Segment if as_seg is true. -//Checks for intersection of Line if as_seg is false. -//Return intersection of Segment AB and Segment EF as a Point -//Return null if there is no intersection -//--------------------------------------------------------------- - - -// a, b, e, f = point -// as_seg = bool (true) -// returns point - -// Adapted from code by Keith Hair -function lineIntersectLine(A, B, E, F, as_seg) { - - var ip = { x: 0, y: 0 }; - var a1 = B.y - A.y; - var a2 = F.y - E.y; - var b1 = A.x - B.x; - var b2 = E.x - F.x; - var c1 = (B.x * A.y) - (A.x * B.y); - var c2 = (F.x * E.y) - (E.x * F.y); - var denom = (a1 * b2) - (a2 * b1); - - if (denom === 0) - { - return null; - } - - ip.x = ((b1 * c2) - (b2 * c1)) / denom; - ip.y = ((a2 * c1) - (a1 * c2)) / denom; - - //--------------------------------------------------- - //Do checks to see if intersection to endpoints - //distance is longer than actual Segments. - //Return null if it is with any. - //--------------------------------------------------- - if (as_seg) - { - if (Math.pow((ip.x - B.x) + (ip.y - B.y), 2) > Math.pow((A.x - B.x) + (A.y - B.y), 2)) - { - return null; - } - - if (Math.pow((ip.x - A.x) + (ip.y - A.y), 2) > Math.pow((A.x - B.x) + (A.y - B.y), 2)) - { - return null; - } - - if (Math.pow((ip.x - F.x) + (ip.y - F.y), 2) > Math.pow((E.x - F.x) + (E.y - F.y), 2)) - { - return null; - } - - if (Math.pow((ip.x - E.x) + (ip.y - E.y), 2) > Math.pow((E.x - F.x) + (E.y - F.y), 2)) - { - return null; - } - } - - return ip; - -} - - - var c = 'rgb(255,255,255)'; var p = new Phaser.Point(); function update() { - p = lineIntersectLine(lineA1.center, lineA2.center, lineB1.center, lineB2.center, true); + line1.fromSprite(handle1, handle2, true); + line2.fromSprite(handle3, handle4, true); + + p = line1.intersects(line2, true); if (p) { @@ -199,27 +138,16 @@ function update() { function render() { - game.context.strokeStyle = c; + game.debug.renderLine(line1, c); + game.debug.renderLine(line2, c); - game.context.beginPath(); - game.context.moveTo(lineA1.center.x, lineA1.center.y); - game.context.lineTo(lineA2.center.x, lineA2.center.y); - game.context.closePath(); - game.context.stroke(); - - game.context.beginPath(); - game.context.moveTo(lineB1.center.x, lineB1.center.y); - game.context.lineTo(lineB2.center.x, lineB2.center.y); - game.context.closePath(); - game.context.stroke(); + game.debug.renderLineInfo(line1, 32, 32); + game.debug.renderLineInfo(line2, 32, 100); if (p) { game.context.fillStyle = 'rgb(255,0,255)'; game.context.fillRect(p.x - 2, p.y - 2, 5, 5); - game.debug.renderPointInfo(p, 32, 32); } - - } \ No newline at end of file diff --git a/src/geom/Circle.js b/src/geom/Circle.js index 3e65334b..7b906c7f 100644 --- a/src/geom/Circle.js +++ b/src/geom/Circle.js @@ -9,9 +9,9 @@ * @class Circle * @classdesc Phaser - Circle * @constructor -* @param {number} [x] The x coordinate of the center of the circle. -* @param {number} [y] The y coordinate of the center of the circle. -* @param {number} [diameter] The diameter of the circle. +* @param {number} [x=0] - The x coordinate of the center of the circle. +* @param {number} [y=0] - The y coordinate of the center of the circle. +* @param {number} [diameter=0] - The diameter of the circle. * @return {Phaser.Circle} This circle object */ Phaser.Circle = function (x, y, diameter) { diff --git a/src/geom/Line.js b/src/geom/Line.js new file mode 100644 index 00000000..93b325fc --- /dev/null +++ b/src/geom/Line.js @@ -0,0 +1,249 @@ +/** +* @author Richard Davey +* @copyright 2013 Photon Storm Ltd. +* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} +*/ + +/** +* Creates a new Line object with a start and an end point. +* @class Line +* @classdesc Phaser - Line +* @constructor +* @param {number} [x1=0] - The x coordinate of the start of the line. +* @param {number} [y1=0] - The y coordinate of the start of the line. +* @param {number} [x2=0] - The x coordinate of the end of the line. +* @param {number} [y2=0] - The y coordinate of the end of the line. +* @return {Phaser.Line} This line object +*/ +Phaser.Line = function (x1, y1, x2, y2) { + + x1 = x1 || 0; + y1 = y1 || 0; + x2 = x2 || 0; + y2 = y2 || 0; + + /** + * @property {Phaser.Point} start - The start point of the line. + */ + this.start = new Phaser.Point(x1, y1); + + /** + * @property {Phaser.Point} end - The end point of the line. + */ + this.end = new Phaser.Point(x2, y2); + +}; + +Phaser.Line.prototype = { + + /** + * Sets the components of the Line to the specified values. + * @method Phaser.Line#setTo + * @param {number} [x1=0] - The x coordinate of the start of the line. + * @param {number} [y1=0] - The y coordinate of the start of the line. + * @param {number} [x2=0] - The x coordinate of the end of the line. + * @param {number} [y2=0] - The y coordinate of the end of the line. + * @return {Phaser.Line} This line object + */ + setTo: function (x1, y1, x2, y2) { + + this.start.setTo(x1, y1); + this.end.setTo(x2, y2); + + return this; + + }, + + fromSprite: function (startSprite, endSprite, useCenter) { + + if (typeof useCenter === 'undefined') { useCenter = true; } + + if (useCenter) + { + this.setTo(startSprite.center.x, startSprite.center.y, endSprite.center.x, endSprite.center.y); + } + else + { + this.setTo(startSprite.x, startSprite.y, endSprite.x, endSprite.y); + } + + }, + + /** + * Checks for intersection between two lines. + * If asSegment is true it will check for segment intersection. + * If asSegment is false it will check for line intersection. + * Returns the intersection segment of AB and EF as a Point, or null if there is no intersection. + * Adapted from code by Keith Hair + * + * @method Phaser.Line#intersects + * @param {Phaser.Line} line - The line to check against this one. + * @param {boolean} [asSegment=true] - If true it will check for segment intersection, otherwise full line intersection. + * @param {Phaser.Point} [result] - A Point object to store the result in, if not given a new one will be created. + * @return {Phaser.Point} The intersection segment of the two lines as a Point, or null if there is no intersection. + */ + intersects: function (line, asSegment, result) { + + return Phaser.Line.intersects(this, line, asSegment, result); + + }, + + /** + * Tests if the given coordinates fall on this line. See pointOnSegment to test against just the line segment. + * @method Phaser.Line#pointOnLine + * @param {number} x - The line to check against this one. + * @param {number} y - The line to check against this one. + * @return {boolean} True if the point is on the line, false if not. + */ + pointOnLine: function (x, y) { + + return ((x - this.start.x) * (this.end.y - this.end.y) === (this.end.x - this.start.x) * (y - this.end.y)); + + }, + + /** + * Tests if the given coordinates fall on this line and within the segment. See pointOnLine to test against just the line. + * @method Phaser.Line#pointOnSegment + * @param {number} x - The line to check against this one. + * @param {number} y - The line to check against this one. + * @return {boolean} True if the point is on the line and segment, false if not. + */ + pointOnSegment: function (x, y) { + + var xMin = Math.min(this.start.x, this.end.x); + var xMax = Math.max(this.start.x, this.end.x); + var yMin = Math.min(this.start.y, this.end.y); + var yMax = Math.max(this.start.y, this.end.y); + + return (this.pointOnLine(x, y) && (x >= xMin && x <= xMax) && (y >= yMin && y <= yMax)); + + } + +}; + +/** +* @name Phaser.Line#length +* @property {number} length - Gets the length of the line segment. +* @readonly +*/ +Object.defineProperty(Phaser.Line.prototype, "length", { + + get: function () { + return Math.sqrt((this.end.x - this.start.x) * (this.end.x - this.start.x) + (this.end.y - this.start.y) * (this.end.y - this.start.y)); + } + +}); + +/** +* @name Phaser.Line#angle +* @property {number} angle - Gets the angle of the line. +* @readonly +*/ +Object.defineProperty(Phaser.Line.prototype, "angle", { + + get: function () { + return Math.atan2(this.end.x - this.start.x, this.end.y - this.start.y); + } + +}); + +/** +* @name Phaser.Line#slope +* @property {number} slope - Gets the slope of the line (y/x). +* @readonly +*/ +Object.defineProperty(Phaser.Line.prototype, "slope", { + + get: function () { + return (this.end.y - this.start.y) / (this.end.x - this.start.x); + } + +}); + +/** +* @name Phaser.Line#perpSlope +* @property {number} perpSlope - Gets the perpendicular slope of the line (x/y). +* @readonly +*/ +Object.defineProperty(Phaser.Line.prototype, "perpSlope", { + + get: function () { + return -((this.end.x - this.start.x) / (this.end.y - this.start.y)); + } + +}); + +/** +* Checks for intersection between two lines. +* If asSegment is true it will check for segment intersection. +* If asSegment is false it will check for line intersection. +* Returns the intersection segment of AB and EF as a Point, or null if there is no intersection. +* Adapted from code by Keith Hair +* +* @method Phaser.Line.intersects +* @param {Phaser.Line} a - The first Line to be checked. +* @param {Phaser.Line} b - The second Line to be checked. +* @param {boolean} [asSegment=true] - If true it will check for segment intersection, otherwise full line intersection. +* @param {Phaser.Point} [result] - A Point object to store the result in, if not given a new one will be created. +* @return {Phaser.Point} The intersection segment of the two lines as a Point, or null if there is no intersection. +*/ +Phaser.Line.intersects = function (a, b, asSegment, result) { + + if (typeof asSegment === 'undefined') { asSegment = true; } + if (typeof result === 'undefined') { result = new Phaser.Point(); } + + // var a1 = B.y - A.y; + // var a2 = F.y - E.y; + // var b1 = A.x - B.x; + // var b2 = E.x - F.x; + // var c1 = (B.x * A.y) - (A.x * B.y); + // var c2 = (F.x * E.y) - (E.x * F.y); + // var denom = (a1 * b2) - (a2 * b1); + + // A = a.start + // B = a.end + // E = b.start + // F = b.end + + var a1 = a.end.y - a.start.y; + var a2 = b.end.y - b.start.y; + var b1 = a.start.x - a.end.x; + var b2 = b.start.x - b.end.x; + var c1 = (a.end.x * a.start.y) - (a.start.x * a.end.y); + var c2 = (b.end.x * b.start.y) - (b.start.x * b.end.y); + var denom = (a1 * b2) - (a2 * b1); + + if (denom === 0) + { + return null; + } + + result.x = ((b1 * c2) - (b2 * c1)) / denom; + result.y = ((a2 * c1) - (a1 * c2)) / denom; + + if (asSegment) + { + if (Math.pow((result.x - a.end.x) + (result.y - a.end.y), 2) > Math.pow((a.start.x - a.end.x) + (a.start.y - a.end.y), 2)) + { + return null; + } + + if (Math.pow((result.x - a.start.x) + (result.y - a.start.y), 2) > Math.pow((a.start.x - a.end.x) + (a.start.y - a.end.y), 2)) + { + return null; + } + + if (Math.pow((result.x - b.end.x) + (result.y - b.end.y), 2) > Math.pow((b.start.x - b.end.x) + (b.start.y - b.end.y), 2)) + { + return null; + } + + if (Math.pow((result.x - b.start.x) + (result.y - b.start.y), 2) > Math.pow((b.start.x - b.end.x) + (b.start.y - b.end.y), 2)) + { + return null; + } + } + + return result; + +}; diff --git a/src/physics/arcade/Body.js b/src/physics/arcade/Body.js index b1754ecd..8ff28872 100644 --- a/src/physics/arcade/Body.js +++ b/src/physics/arcade/Body.js @@ -4,6 +4,27 @@ * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ +/* + +The SKPhysicsBody class defines properties that determine how the physics body is simulated. These properties affect how the body reacts to forces, what forces it generates on itself (to simulate friction), and how it reacts to collisions in the scene. In most cases, the properties are used to simulate physical effects. + +Each individual body also has its own property values that determine exactly how it reacts to forces and collisions in the scene. Here are the most important properties: + +The mass property determines how forces affect the body, as well as how much momentum the body has when it is involved in a collision. +The friction property determines the roughness of the body’s surface. It is used to calculate the frictional force that a body applies to other bodies moving along its surface. +The linearDamping and angularDamping properties are used to calculate friction on the body as it moves through the world. For example, this might be used to simulate air or water friction. +The restitution property determines how much energy a body maintains during a collision—its bounciness. +Other properties are used to determine how the simulation is performed on the body itself: + +The dynamic property determines whether the body is simulated by the physics subsystem. +The affectedByGravity property determines whether the simulation exerts a gravitational force on the body. For more information on the physics world, see “Configuring the Physics World.” +The allowsRotation property determines whether forces can impart angular velocity on the body. + + +*/ + + + /** * The Physics Body is linked to a single Sprite. All physics operations should be performed against the body rather than * the Sprite itself. For example you can set the velocity, acceleration, bounce values etc all on the Body. diff --git a/src/utils/Debug.js b/src/utils/Debug.js index 9e476e5d..3f017756 100644 --- a/src/utils/Debug.js +++ b/src/utils/Debug.js @@ -95,6 +95,7 @@ Phaser.Utils.Debug.prototype = { this.context.save(); this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.strokeStyle = color; this.context.fillStyle = color; this.context.font = this.font; this.context.globalAlpha = 1; @@ -630,6 +631,56 @@ Phaser.Utils.Debug.prototype = { }, + /** + * Renders a Line object in the given color. + * @method Phaser.Utils.Debug#renderLine + * @param {Phaser.Line} line - The Line to render. + * @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string). + */ + renderLine: function (line, color) { + + if (this.context == null) + { + return; + } + + color = color || 'rgb(255, 255, 255)'; + + this.start(0, 0, color); + this.context.beginPath(); + this.context.moveTo(line.start.x, line.start.y); + this.context.lineTo(line.end.x, line.end.y); + this.context.closePath(); + this.context.stroke(); + this.stop(); + + }, + + /** + * Renders Line information in the given color. + * @method Phaser.Utils.Debug#renderLineInfo + * @param {Phaser.Line} line - The Line to render. + * @param {number} x - X position of the debug info to be rendered. + * @param {number} y - Y position of the debug info to be rendered. + * @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string). + */ + renderLineInfo: function (line, x, y, color) { + + if (this.context == null) + { + return; + } + + color = color || 'rgb(255, 255, 255)'; + + this.start(x, y, color, 80); + this.splitline('start.x:', line.start.x.toFixed(2), 'start.y:', line.start.y.toFixed(2)); + this.splitline('end.x:', line.end.x.toFixed(2), 'end.y:', line.end.y.toFixed(2)); + this.splitline('length:', line.length.toFixed(2), 'angle:', line.angle); + this.stop(); + + }, + /** * Renders Point coordinates in the given color. * @method Phaser.Utils.Debug#renderPointInfo