Skip to content

Commit 2389c6c

Browse files
committed
Added ability to detect if a given point is within a sprite or not, taking rotation and scaling into account.
1 parent c2533d1 commit 2389c6c

16 files changed

Lines changed: 447 additions & 380 deletions

Phaser/components/Transform.ts

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ module Phaser.Components {
1010
export class Transform {
1111

1212
/**
13-
* Creates a new Sprite Transform component
14-
* @param parent The Sprite using this transform
13+
* Creates a new Transform component
14+
* @param parent The game object using this transform
1515
*/
1616
constructor(parent) {
1717

@@ -57,14 +57,39 @@ module Phaser.Components {
5757
private _distance: number;
5858
private _prevRotation: number;
5959

60+
/**
61+
* The center of the Sprite in world coordinates, after taking scaling and rotation into consideration
62+
*/
6063
public center: Phaser.Point;
64+
65+
/**
66+
* The upper-left corner of the Sprite in world coordinates, after taking scaling and rotation into consideration
67+
*/
6168
public upperLeft: Phaser.Point;
69+
70+
/**
71+
* The upper-right corner of the Sprite in world coordinates, after taking scaling and rotation into consideration
72+
*/
6273
public upperRight: Phaser.Point;
74+
75+
/**
76+
* The bottom-left corner of the Sprite in world coordinates, after taking scaling and rotation into consideration
77+
*/
6378
public bottomLeft: Phaser.Point;
79+
80+
/**
81+
* The bottom-right corner of the Sprite in world coordinates, after taking scaling and rotation into consideration
82+
*/
6483
public bottomRight: Phaser.Point;
6584

85+
/**
86+
* The local transform matrix
87+
*/
6688
public local: Mat3;
6789

90+
/**
91+
* Populates the transform cache. Called by the parent object on creation.
92+
*/
6893
public setCache() {
6994

7095
this._pos.x = this.parent.x;
@@ -96,14 +121,22 @@ module Phaser.Components {
96121
this._sc.y = 1;
97122
}
98123

99-
this.center.setTo(this.center.x, this.center.y);
124+
this.center.x = this.parent.x + this._distance * this._scA.y;
125+
this.center.y = this.parent.y + this._distance * this._scA.x;
126+
100127
this.upperLeft.setTo(this.center.x - this._halfSize.x * this._sc.y + this._halfSize.y * this._sc.x, this.center.y - this._halfSize.y * this._sc.y - this._halfSize.x * this._sc.x);
101128
this.upperRight.setTo(this.center.x + this._halfSize.x * this._sc.y + this._halfSize.y * this._sc.x, this.center.y - this._halfSize.y * this._sc.y + this._halfSize.x * this._sc.x);
102129
this.bottomLeft.setTo(this.center.x - this._halfSize.x * this._sc.y - this._halfSize.y * this._sc.x, this.center.y + this._halfSize.y * this._sc.y - this._halfSize.x * this._sc.x);
103130
this.bottomRight.setTo(this.center.x + this._halfSize.x * this._sc.y - this._halfSize.y * this._sc.x, this.center.y + this._halfSize.y * this._sc.y + this._halfSize.x * this._sc.x);
104131

132+
this._pos.x = this.parent.x;
133+
this._pos.y = this.parent.y;
134+
105135
}
106136

137+
/**
138+
* Updates the local transform matrix and the cache values if anything has changed in the parent.
139+
*/
107140
public update() {
108141

109142
// Check cache
@@ -156,8 +189,6 @@ module Phaser.Components {
156189
this.center.x = this.parent.x + this._distance * this._scA.y;
157190
this.center.y = this.parent.y + this._distance * this._scA.x;
158191

159-
this.center.setTo(this.center.x, this.center.y);
160-
161192
this.upperLeft.setTo(this.center.x - this._halfSize.x * this._sc.y + this._halfSize.y * this._sc.x, this.center.y - this._halfSize.y * this._sc.y - this._halfSize.x * this._sc.x);
162193
this.upperRight.setTo(this.center.x + this._halfSize.x * this._sc.y + this._halfSize.y * this._sc.x, this.center.y - this._halfSize.y * this._sc.y + this._halfSize.x * this._sc.x);
163194
this.bottomLeft.setTo(this.center.x - this._halfSize.x * this._sc.y - this._halfSize.y * this._sc.x, this.center.y + this._halfSize.y * this._sc.y - this._halfSize.x * this._sc.x);
@@ -282,23 +313,15 @@ module Phaser.Components {
282313
}
283314

284315
/**
285-
* The center of the Sprite in world coordinates, after taking scaling and rotation into consideration
286-
*/
287-
//public get centerX(): number {
288-
// return this.center.x;
289-
//}
290-
291-
/**
292-
* The center of the Sprite in world coordinates, after taking scaling and rotation into consideration
316+
* The equivalent of Math.sin(rotation + rotationOffset)
293317
*/
294-
//public get centerY(): number {
295-
// return this.center.y;
296-
//}
297-
298318
public get sin(): number {
299319
return this._sc.x;
300320
}
301321

322+
/**
323+
* The equivalent of Math.cos(rotation + rotationOffset)
324+
*/
302325
public get cos(): number {
303326
return this._sc.y;
304327
}

Phaser/renderers/CanvasRenderer.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,7 @@ module Phaser {
221221
return true;
222222
}
223223

224-
return true;
225-
//return RectangleUtils.intersects(sprite.cameraView, camera.screenView);
224+
return RectangleUtils.intersects(sprite.cameraView, camera.screenView);
226225

227226
}
228227

Phaser/utils/SpriteUtils.ts

Lines changed: 33 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -60,42 +60,11 @@ module Phaser {
6060
}
6161
else
6262
{
63-
//var left:Number = Math.min(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x);
64-
//var top:Number = Math.min(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y);
65-
//var right:Number = Math.max(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x);
66-
//var bottom:Number = Math.max(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y);
67-
//return new Rectangle(left, top, right - left, bottom - top);
68-
69-
var minX: number = Math.min(sprite.transform.upperLeft.x, sprite.transform.upperRight.x, sprite.transform.bottomLeft.x, sprite.transform.bottomRight.x);
70-
var minY: number = Math.min(sprite.transform.upperLeft.y, sprite.transform.upperRight.y, sprite.transform.bottomLeft.y, sprite.transform.bottomRight.y);
71-
var maxX: number = Math.max(sprite.transform.upperLeft.x, sprite.transform.upperRight.x, sprite.transform.bottomLeft.x, sprite.transform.bottomRight.x);
72-
var maxY: number = Math.max(sprite.transform.upperLeft.y, sprite.transform.upperRight.y, sprite.transform.bottomLeft.y, sprite.transform.bottomRight.y);
73-
74-
// (min_x,min_y), (min_x,max_y), (max_x,max_y), (max_x,min_y)
75-
76-
sprite.cameraView.x = minX;
77-
sprite.cameraView.y = minY;
78-
sprite.cameraView.width = maxX - minX;
79-
sprite.cameraView.height = maxY - minY;
80-
81-
/*
82-
// Useful to get the maximum AABB size of any given rect
83-
84-
If you want a single box that covers all angles, just take the half-diagonal of your existing box as the radius of a circle.
85-
The new box has to contain this circle, so it should be a square with side-length equal to twice the radius
86-
(equiv. the diagonal of the original AABB) and with the same center as the original.
87-
*/
88-
63+
sprite.cameraView.x = Math.min(sprite.transform.upperLeft.x, sprite.transform.upperRight.x, sprite.transform.bottomLeft.x, sprite.transform.bottomRight.x);
64+
sprite.cameraView.y = Math.min(sprite.transform.upperLeft.y, sprite.transform.upperRight.y, sprite.transform.bottomLeft.y, sprite.transform.bottomRight.y);
65+
sprite.cameraView.width = Math.max(sprite.transform.upperLeft.x, sprite.transform.upperRight.x, sprite.transform.bottomLeft.x, sprite.transform.bottomRight.x) - sprite.cameraView.x;
66+
sprite.cameraView.height = Math.max(sprite.transform.upperLeft.y, sprite.transform.upperRight.y, sprite.transform.bottomLeft.y, sprite.transform.bottomRight.y) - sprite.cameraView.y;
8967
}
90-
91-
}
92-
93-
if (sprite.animations.currentFrame !== null && sprite.animations.currentFrame.trimmed)
94-
{
95-
//sprite.cameraView.x += sprite.animations.currentFrame.spriteSourceSizeX;
96-
//sprite.cameraView.y += sprite.animations.currentFrame.spriteSourceSizeY;
97-
//this._dw = sprite.animations.currentFrame.spriteSourceSizeW;
98-
//this._dh = sprite.animations.currentFrame.spriteSourceSizeH;
9968
}
10069

10170
return sprite.cameraView;
@@ -174,92 +143,60 @@ module Phaser {
174143
}
175144
*/
176145

146+
177147
/**
178-
* Checks to see if this <code>GameObject</code> were located at the given position, would it overlap the <code>GameObject</code> or <code>Group</code>?
179-
* This is distinct from overlapsPoint(), which just checks that point, rather than taking the object's size numbero account.
180-
* WARNING: Currently tilemaps do NOT support screen space overlap checks!
148+
* Checks to see if the given x and y coordinates overlaps this <code>Sprite</code>, taking scaling and rotation into account.
149+
* The coordinates must be given in world space, not local or camera space.
181150
*
182-
* @param X {number} The X position you want to check. Pretends this object (the caller, not the parameter) is located here.
183-
* @param Y {number} The Y position you want to check. Pretends this object (the caller, not the parameter) is located here.
184-
* @param objectOrGroup {object} The object or group being tested.
185-
* @param inScreenSpace {boolean} Whether to take scroll factors numbero account when checking for overlap. Default is false, or "only compare in world space."
186-
* @param camera {Camera} Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
151+
* @param sprite {Sprite} The Sprite to check. It will take scaling and rotation into account.
152+
* @param x {Number} The x coordinate in world space.
153+
* @param y {Number} The y coordinate in world space.
187154
*
188-
* @return {boolean} Whether or not the two objects overlap.
155+
* @return Whether or not the point overlaps this object.
189156
*/
190-
/*
191-
static overlapsAt(X: number, Y: number, objectOrGroup, inScreenSpace: bool = false, camera: Camera = null): bool {
157+
static overlapsXY(sprite: Phaser.Sprite, x: number, y: number): bool {
192158

193-
if (objectOrGroup.isGroup)
159+
// if rotation == 0 then just do a rect check instead!
160+
if (sprite.transform.rotation == 0)
194161
{
195-
var results: bool = false;
196-
var basic;
197-
var i: number = 0;
198-
var members = objectOrGroup.members;
199-
200-
while (i < length)
201-
{
202-
if (this.overlapsAt(X, Y, members[i++], inScreenSpace, camera))
203-
{
204-
results = true;
205-
}
206-
}
162+
return Phaser.RectangleUtils.contains(sprite.cameraView, x, y);
163+
}
207164

208-
return results;
165+
if ((x - sprite.transform.upperLeft.x) * (sprite.transform.upperRight.x - sprite.transform.upperLeft.x) + (y - sprite.transform.upperLeft.y) * (sprite.transform.upperRight.y - sprite.transform.upperLeft.y) < 0)
166+
{
167+
return false;
209168
}
210169

211-
if (!inScreenSpace)
170+
if ((x - sprite.transform.upperRight.x) * (sprite.transform.upperRight.x - sprite.transform.upperLeft.x) + (y - sprite.transform.upperRight.y) * (sprite.transform.upperRight.y - sprite.transform.upperLeft.y) > 0)
212171
{
213-
return (objectOrGroup.x + objectOrGroup.width > X) && (objectOrGroup.x < X + this.width) &&
214-
(objectOrGroup.y + objectOrGroup.height > Y) && (objectOrGroup.y < Y + this.height);
172+
return false;
215173
}
216174

217-
if (camera == null)
175+
if ((x - sprite.transform.upperLeft.x) * (sprite.transform.bottomLeft.x - sprite.transform.upperLeft.x) + (y - sprite.transform.upperLeft.y) * (sprite.transform.bottomLeft.y - sprite.transform.upperLeft.y) < 0)
218176
{
219-
camera = this._game.camera;
177+
return false;
220178
}
221179

222-
var objectScreenPos: Point = objectOrGroup.getScreenXY(null, Camera);
180+
if ((x - sprite.transform.bottomLeft.x) * (sprite.transform.bottomLeft.x - sprite.transform.upperLeft.x) + (y - sprite.transform.bottomLeft.y) * (sprite.transform.bottomLeft.y - sprite.transform.upperLeft.y) > 0)
181+
{
182+
return false;
183+
}
223184

224-
this._point.x = X - camera.scroll.x * this.transform.scrollFactor.x; //copied from getScreenXY()
225-
this._point.y = Y - camera.scroll.y * this.transform.scrollFactor.y;
226-
this._point.x += (this._point.x > 0) ? 0.0000001 : -0.0000001;
227-
this._point.y += (this._point.y > 0) ? 0.0000001 : -0.0000001;
185+
return true;
228186

229-
return (objectScreenPos.x + objectOrGroup.width > this._point.x) && (objectScreenPos.x < this._point.x + this.width) &&
230-
(objectScreenPos.y + objectOrGroup.height > this._point.y) && (objectScreenPos.y < this._point.y + this.height);
231187
}
232-
*/
233188

234189
/**
235-
* Checks to see if a point in 2D world space overlaps this <code>GameObject</code>.
190+
* Checks to see if the given point overlaps this <code>Sprite</code>, taking scaling and rotation into account.
191+
* The point must be given in world space, not local or camera space.
236192
*
193+
* @param sprite {Sprite} The Sprite to check. It will take scaling and rotation into account.
237194
* @param point {Point} The point in world space you want to check.
238-
* @param inScreenSpace {boolean} Whether to take scroll factors into account when checking for overlap.
239-
* @param camera {Camera} Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
240195
*
241196
* @return Whether or not the point overlaps this object.
242197
*/
243-
static overlapsPoint(sprite: Sprite, point: Point, inScreenSpace: bool = false, camera: Camera = null): bool {
244-
245-
if (!inScreenSpace)
246-
{
247-
return Phaser.RectangleUtils.containsPoint(sprite.body.bounds, point);
248-
//return (point.x > sprite.x) && (point.x < sprite.x + sprite.width) && (point.y > sprite.y) && (point.y < sprite.y + sprite.height);
249-
}
250-
251-
if (camera == null)
252-
{
253-
camera = sprite.game.camera;
254-
}
255-
256-
//var x: number = point.x - camera.scroll.x;
257-
//var y: number = point.y - camera.scroll.y;
258-
259-
//this.getScreenXY(this._point, camera);
260-
261-
//return (x > this._point.x) && (X < this._point.x + this.width) && (Y > this._point.y) && (Y < this._point.y + this.height);
262-
198+
static overlapsPoint(sprite: Sprite, point: Point): bool {
199+
return overlapsXY(sprite, point.x, point.y);
263200
}
264201

265202
/**
@@ -346,19 +283,6 @@ module Phaser {
346283

347284
}
348285

349-
static setOriginToCenter(sprite: Sprite, fromFrameBounds: bool = true, fromBody?: bool = false) {
350-
351-
if (fromFrameBounds)
352-
{
353-
sprite.transform.origin.setTo(sprite.width / 2, sprite.height / 2);
354-
}
355-
else if (fromBody)
356-
{
357-
sprite.transform.origin.setTo(sprite.body.bounds.halfWidth, sprite.body.bounds.halfHeight);
358-
}
359-
360-
}
361-
362286
/**
363287
* Set the world bounds that this GameObject can exist within. By default a GameObject can exist anywhere
364288
* in the world. But by setting the bounds (which are given in world dimensions, not screen dimensions)

README.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,28 @@ TODO:
2626
* Investigate why tweens don't restart after the game pauses
2727
* Fix bug in Tween yoyo + loop combo
2828
* Apply Sprite scaling to Body.bounds
29-
* When you modify the sprite x/y directly the body position doesn't update, which leads to weird results. Need to work out who controls who.
3029
* Check that tween pausing works with the new performance.now
3130
* Game.Time should monitor pause duration
3231
* Investigate bug re: tilemap collision and animation frames
3332
* Update tests that use arrow keys and include touch/mouse support (FlxControlHandler style)
34-
* Polygon geom primitive
3533
* If the Camera is larger than the Stage size then the rotation offset isn't correct
3634
* Texture Repeat doesn't scroll, because it's part of the camera not the world, need to think about this more
3735
* Bug: Sprite x/y gets shifted if dynamic from the original value
3836
* Input CSS cursor those little 4-way arrows on drag?
3937
* Stage CSS3 transforms!!! Color tints, sepia, greyscale, all of those cool things :)
40-
* Cameras should have option to be input disabled + Pointers should check which camera they are over before doing Sprite selection
41-
* Added JSON Texture Atlas object support.
42-
* RenderOrderID won't work across cameras - but then neither do Pointers yet anyway
38+
* Add JSON Texture Atlas object support.
4339
* Swap to using time based motion (like the tweens) rather than delta timer - it just doesn't work well on slow phones
4440
* Pointer.getWorldX(camera) needs to take camera scale into consideration
4541
* Add a 'click to bring to top' demo (+ Group feature?)
4642
* If stage.clear set to false and game pauses, when it unpauses you still see the pause arrow - resolve this
47-
4843
* Add clip support + shape options to Texture Component.
44+
* Make sure I'm using Point and not Vec2 when it's not a directional vector I need
45+
* Bug with setting scale or anything on a Sprite inside a Group, or maybe group.addNewSprite issue
4946

50-
Make sure I'm using Point and not Vec2 when it's not a directional vector I need
51-
52-
47+
* Make input check use the rotated sprite check
48+
* Sprite collision events
49+
* See which functions in the input component can move elsewhere (utils)
50+
* Move all of the renderDebugInfo methods to the DebugUtils class
5351

5452
V1.0.0
5553

@@ -98,7 +96,7 @@ V1.0.0
9896
* Added optional frame parameter to Phaser.Sprite (and game.add.sprite) so you can set a frame ID or frame name on construction.
9997
* Fixed bug where passing a texture atlas string would incorrectly skip the frames array.
10098
* Added AnimationManager.autoUpdateBounds to control if a new frame should change the physics bounds of a sprite body or not.
101-
* Added StageScaleMode.pageAlignHorizontally and pageAlignVertically booleans. When on Phaser will set the margin-left and top of the canvas element so that it is positioned in the middle of the page (based on window.innerWidth).
99+
* Added StageScaleMode.pageAlignHorizontally and pageAlignVertically booleans. When true Phaser will set the margin-left and top of the canvas element so that it is positioned in the middle of the page (based only on window.innerWidth).
102100
* Added support for globalCompositeOperation, opaque and backgroundColor to the Sprite.Texture and Camera.Texture components.
103101
* Added ability for a Camera to skew and rotate around an origin.
104102
* Moved the Camera rendering into CanvasRenderer to keep things consistent.
@@ -109,8 +107,12 @@ V1.0.0
109107
* Added CameraManager.swap and CameraManager.sort methods and added a z-index property to Camera to control render order.
110108
* Added World.postUpdate loop + Group and Camera postUpdate methods.
111109
* Fixed issue stopping Pointer from working in world coordinates and fixed the world drag example.
112-
* For consistency renamed input.scaledX/Y = input.scale.
110+
* For consistency renamed input.scaledX/Y to input.scale.
113111
* Added input.activePointer which contains a reference to the most recently active pointer.
112+
* Sprite.Transform now has upperLeft, upperRight, bottomLeft and bottomRight Point properties and lots of useful coordinate related methods.
113+
* Camera.inCamera check now uses the Sprite.worldView which finally accurately updates regardless of scale, rotation or rotation origin.
114+
* Added Math.Mat3 for Matrix3 operations (which Sprite.Transform uses) and Math.Mat3Utils for lots of use Mat3 related methods.
115+
* Added SpriteUtils.overlapsXY and overlapsPoint to check if a point is within a sprite, taking scale and rotation into account.
114116

115117

116118

0 commit comments

Comments
 (0)