From 06966e12a3d78c353109005f08b97ae08b3438dd Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Sat, 30 Nov 2013 00:15:57 +0100 Subject: [PATCH 01/15] Circle mask added and partially tested, Polygon mask initial work, had to add some things to other mask too --- net/flashpunk/Mask.as | 36 +++ net/flashpunk/masks/Circle.as | 199 ++++++++++++ net/flashpunk/masks/Grid.as | 2 + net/flashpunk/masks/Hitbox.as | 7 + net/flashpunk/masks/Polygon.as | 572 +++++++++++++++++++++++++++++++++ 5 files changed, 816 insertions(+) create mode 100644 net/flashpunk/masks/Circle.as create mode 100644 net/flashpunk/masks/Polygon.as diff --git a/net/flashpunk/Mask.as b/net/flashpunk/Mask.as index fbb9693..4bc6d30 100644 --- a/net/flashpunk/Mask.as +++ b/net/flashpunk/Mask.as @@ -1,6 +1,7 @@ package net.flashpunk { import flash.display.Graphics; + import flash.geom.Point; import flash.utils.Dictionary; import flash.utils.getDefinitionByName; import flash.utils.getQualifiedClassName; @@ -78,6 +79,41 @@ } + /** @private */ + public function project(axis:Point, projection:Object):void + { + var cur:Number, + max:Number = -9999999999.0, + min:Number = 9999999999.0; + + cur = -parent.originX * axis.x - parent.originY * axis.y; + if (cur < min) + min = cur; + if (cur > max) + max = cur; + + cur = (-parent.originX + parent.width) * axis.x - parent.originY * axis.y; + if (cur < min) + min = cur; + if (cur > max) + max = cur; + + cur = -parent.originX * axis.x + (-parent.originY + parent.height) * axis.y; + if (cur < min) + min = cur; + if (cur > max) + max = cur; + + cur = (-parent.originX + parent.width) * axis.x + (-parent.originY + parent.height)* axis.y; + if (cur < min) + min = cur; + if (cur > max) + max = cur; + + projection.min = min; + projection.max = max; + } + // Mask information. /** @private */ private var _class:Class; /** @private */ protected var _check:Dictionary = new Dictionary; diff --git a/net/flashpunk/masks/Circle.as b/net/flashpunk/masks/Circle.as new file mode 100644 index 0000000..1fb6f15 --- /dev/null +++ b/net/flashpunk/masks/Circle.as @@ -0,0 +1,199 @@ +package net.flashpunk.masks +{ + + import net.flashpunk.FP; + import net.flashpunk.Graphic; + import net.flashpunk.Mask; + import net.flashpunk.masks.Grid; + import flash.display.Graphics; + import flash.geom.Point; + + /** + * Uses circular area to determine collision. + */ + + public class Circle extends Hitbox + { + /** + * Constructor. + * @param radius Radius of the circle. + * @param x X offset of the circle. + * @param y Y offset of the circle. + */ + public function Circle(radius:int, x:int = 0, y:int = 0) + { + this.radius = radius; + _x = x + radius; + _y = y + radius; + + _check[Mask] = collideMask; + _check[Hitbox] = collideHitbox; + _check[Grid] = collideGrid; + _check[Circle] = collideCircle; + } + + /** @private Collides against an Entity. */ + private function collideMask(other:Mask):Boolean + { + var _otherHalfWidth:Number = other.parent.width * 0.5; + var _otherHalfHeight:Number = other.parent.height * 0.5; + + var distanceX:Number = Math.abs(parent.x + _x - other.parent.x - _otherHalfWidth), + distanceY:Number = Math.abs(parent.y + _y - other.parent.y - _otherHalfHeight); + + if (distanceX > _otherHalfWidth + radius || distanceY > _otherHalfHeight + radius) + { + return false; //The hitbox is to far away so return false + } + if (distanceX <= _otherHalfWidth || distanceY <= _otherHalfHeight) + { + return true; + } + var distanceToCorner:Number = (distanceX - _otherHalfWidth) * (distanceX - _otherHalfWidth) + + (distanceY - _otherHalfHeight) * (distanceY - _otherHalfHeight); + + return distanceToCorner <= _squaredRadius; + } + + /** @private Collides against a Hitbox. */ + private function collideHitbox(other:Hitbox):Boolean + { + var _otherHalfWidth:Number = other._width * 0.5; + var _otherHalfHeight:Number = other._height * 0.5; + + var distanceX:Number = Math.abs(parent.x + _x - other.parent.x - other._x - _otherHalfWidth), + distanceY:Number = Math.abs(parent.y + _y - other.parent.y - other._y - _otherHalfHeight); + + if (distanceX > _otherHalfWidth + radius || distanceY > _otherHalfHeight + radius) + { + return false; //The hitbox is to far away so return false + } + if (distanceX <= _otherHalfWidth || distanceY <= _otherHalfHeight) + { + return true; + } + var distanceToCorner:Number = (distanceX - _otherHalfWidth) * (distanceX - _otherHalfWidth) + + (distanceY - _otherHalfHeight) * (distanceY - _otherHalfHeight); + + return distanceToCorner <= _squaredRadius; + } + + /** @private Collides against a Grid. */ + private function collideGrid(other:Grid):Boolean + { + var thisX:Number = parent.x + _x, + thisY:Number = parent.y + _y, + otherX:Number = other.parent.x + other.x, + otherY:Number = other.parent.y + other.y, + entityDistX:Number = thisX - otherX, + entityDistY:Number = thisY - otherY; + + var minx:int = Math.floor((entityDistX - radius) / other.tileWidth), + miny:int = Math.floor((entityDistY - radius) / other.tileHeight), + maxx:int = Math.ceil((entityDistX + radius) / other.tileWidth), + maxy:int = Math.ceil((entityDistY + radius) / other.tileHeight); + + if (minx < 0) minx = 0; + if (miny < 0) miny = 0; + if (maxx > other.columns) maxx = other.columns; + if (maxy > other.rows) maxy = other.rows; + + var hTileWidth:Number = other.tileWidth * 0.5, + hTileHeight:Number = other.tileHeight * 0.5, + dx:Number, dy:Number; + + for (var xx:int = minx; xx < maxx; xx++) + { + for (var yy:int = miny; yy < maxy; yy++) + { + if (other.getTile(xx, yy)) + { + var mx:Number = otherX + xx*other.tileWidth + hTileWidth, + my:Number = otherY + yy*other.tileHeight + hTileHeight; + + dx = Math.abs(thisX - mx); + + if (dx > hTileWidth + radius) + continue; + + dy = Math.abs(thisY - my); + + if (dy > hTileHeight + radius) + continue; + + if (dx <= hTileWidth || dy <= hTileHeight) + return true; + + var xCornerDist:Number = dx - hTileWidth; + var yCornerDist:Number = dy - hTileHeight; + + if (xCornerDist * xCornerDist + yCornerDist * yCornerDist <= _squaredRadius) + return true; + } + } + } + + return false; + } + + /** @private Collides against a Circle. */ + private function collideCircle(other:Circle):Boolean + { + var dx:Number = (parent.x + _x) - (other.parent.x + other._x); + var dy:Number = (parent.y + _y) - (other.parent.y + other._y); + return (dx * dx + dy * dy) < Math.pow(_radius + other._radius, 2); + } + + /** @private */ + override public function project(axis:Point, projection:Object):void + { + projection.min = -_radius; + projection.max = _radius; + } + + override public function renderDebug(graphics:Graphics):void + { + var sx:Number = FP.screen.scaleX * FP.screen.scale; + var sy:Number = FP.screen.scaleY * FP.screen.scale; + + graphics.drawCircle((parent.x + _x - FP.camera.x) * sx, (parent.y + _y - FP.camera.y) * sy, radius * sx); + } + + override public function get x():int { return _x - _radius; } + override public function get y():int { return _y - _radius; } + + /** + * Radius. + */ + public function get radius():int { return _radius; } + public function set radius(value:int):void + { + if (_radius == value) return; + _radius = value; + _squaredRadius = value * value; + height = width = _radius + _radius; + if (list != null) list.update(); + else if (parent != null) update(); + } + + /** Updates the parent's bounds for this mask. */ + override public function update():void + { + if (parent != null) + { + //update entity bounds + parent.originX = -_x + radius; + parent.originY = -_y + radius; + parent.height = parent.width = radius + radius; + + // update parent list + if (list != null) + list.update(); + } + } + + // Hitbox information. + protected var _radius:int; + protected var _squaredRadius:int; //Set automatically through the setter for radius + } +} \ No newline at end of file diff --git a/net/flashpunk/masks/Grid.as b/net/flashpunk/masks/Grid.as index f473e74..ed3b7f5 100644 --- a/net/flashpunk/masks/Grid.as +++ b/net/flashpunk/masks/Grid.as @@ -349,6 +349,7 @@ var x:int, y:int; + g.beginFill(0xFFFFFF, .15); g.lineStyle(1, 0xFFFFFF, 0.25); for (y = 0; y < _rows; y ++) @@ -361,6 +362,7 @@ } } } + g.endFill(); } // Grid information. diff --git a/net/flashpunk/masks/Hitbox.as b/net/flashpunk/masks/Hitbox.as index 36f804b..9180f2c 100644 --- a/net/flashpunk/masks/Hitbox.as +++ b/net/flashpunk/masks/Hitbox.as @@ -1,5 +1,6 @@ package net.flashpunk.masks { + import flash.geom.Point; import net.flashpunk.*; /** @@ -110,6 +111,12 @@ } } + /** @private */ + override public function project(axis:Point, projection:Object):void + { + super.project(axis, projection); + } + // Hitbox information. /** @private */ internal var _width:uint; /** @private */ internal var _height:uint; diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as new file mode 100644 index 0000000..802fb43 --- /dev/null +++ b/net/flashpunk/masks/Polygon.as @@ -0,0 +1,572 @@ +package net.flashpunk.masks +{ + import flash.display.Graphics; + import flash.geom.Point; + import net.flashpunk.FP; + import net.flashpunk.Mask; + + + public class Polygon extends Hitbox + { + /** + * The polygon rotates around this point when the angle is set. + */ + public var origin:Point; + + /** + * Constructor. + * @param points an array of coordinates that define the polygon (must have at least 3) + * @param origin origin point of the polygon + */ + public function Polygon(points:Vector., origin:Point = null) + { + _points = points; + + _check[Mask] = collideMask; + _check[Hitbox] = collideHitbox; + _check[Grid] = collideGrid; + _check[Circle] = collideCircle; + _check[Polygon] = collidePolygon; + + this.origin = origin != null ? origin : new Point(); + _angle = 0; + + updateAxes(); + } + + /** + * Checks for collisions with a Entity. + */ + private function collideMask(other:Mask):Boolean + { + var offset:Number, + offsetX:Number = parent.x - other.parent.x, + offsetY:Number = parent.y - other.parent.y; + + project(vertical, firstProj);//Project on the horizontal axis of the hitbox + other.project(vertical, secondProj); + + firstProj.min += offsetY; + firstProj.max += offsetY; + + // if firstProj overlaps secondProj + if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) + { + return false; + } + + project(horizontal, firstProj);//Project on the vertical axis of the hitbox + other.project(horizontal, secondProj); + + firstProj.min += offsetX; + firstProj.max += offsetX; + + // if firstProj overlaps secondProj + if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) + { + return false; + } + + var a:Point; + + for (var i:int = 0; i < _axes.length; i++) + { + a = _axes[i]; + project(a, firstProj); + other.project(a, secondProj); + + offset = offsetX * a.x + offsetY * a.y; + firstProj.min += offset; + firstProj.max += offset; + + // if firstProj overlaps secondProj + if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) + { + return false; + } + } + return true; + } + + /** + * Checks for collisions with a hitbox. + */ + public function collideHitbox(hitbox:Hitbox):Boolean + { + var offset:Number, + offsetX:Number = parent.x - hitbox.parent.x, + offsetY:Number = parent.y - hitbox.parent.y; + + project(vertical, firstProj);//Project on the horizontal axis of the hitbox + hitbox.project(vertical, secondProj); + + firstProj.min += offsetY; + firstProj.max += offsetY; + + // if firstProj overlaps secondProj + if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) + { + return false; + } + + project(horizontal, firstProj);//Project on the vertical axis of the hitbox + hitbox.project(horizontal, secondProj); + + firstProj.min += offsetX; + firstProj.max += offsetX; + + // if firstProj overlaps secondProj + if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) + { + return false; + } + + var a:Point; + + for (var i:int = 0; i < _axes.length; i++) + { + a = _axes[i]; + project(a, firstProj); + hitbox.project(a, secondProj); + + offset = offsetX * a.x + offsetY * a.y; + firstProj.min += offset; + firstProj.max += offset; + + // if firstProj overlaps secondProj + if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) + { + return false; + } + } + return true; + } + + /** + * Checks for collisions along the edges of the polygon. + * May be very slow, mainly added for completeness sake. + */ + public function collideGrid(grid:Grid):Boolean + { + var p1X:Number, p1Y:Number, + p2X:Number, p2Y:Number, + k:Number, m:Number, + x:Number, y:Number, + min:Number, max:Number; + + for (var ii:int = 0; ii < _points.length - 1; ii++) + { + p1X = (parent.x + _points[ii].x) / grid.tileWidth; + p1Y = (parent.y + _points[ii].y) / grid.tileHeight; + p2X = (parent.x + _points[ii + 1].x) / grid.tileWidth; + p2Y = (parent.y + _points[ii + 1].y) / grid.tileHeight; + + k = (p2Y - p1Y) / (p2X - p1X); + m = p1Y - k * p1X; + + if (p2X > p1X) { min = p1X; max = p2X; } + else { max = p1X; min = p2X; } + + x = min; + while (x < max) + { + y = int(k * x + m); + if (grid.getTile(int(x), y)) + return true; + + x++; + } + } + + //Check the last point -> first point + p1X = (parent.x + _points[_points.length - 1].x) / grid.tileWidth; + p1Y = (parent.y + _points[_points.length - 1].y) / grid.tileHeight; + p2X = (parent.x + _points[0].x) / grid.tileWidth; + p2Y = (parent.y + _points[0].y) / grid.tileHeight; + + k = (p2Y - p1Y) / (p2X - p1X); + m = p1Y - k * p1X; + + if (p2X > p1X) { min = p1X; max = p2X; } + else { max = p1X; min = p2X; } + + x = min; + while (x < max) + { + y = int(k * x + m); + if (grid.getTile(int(x), y)) + return true; + + x++; + } + + return false; + } + + /** + * Checks for collision with a circle. + */ + public function collideCircle(circle:Circle):Boolean + { + var offset:Number; + + //First find the point closest to the circle + var distanceSquared:Number = int.MAX_VALUE; + var closestPoint:Point = null; + var p:Point; + + for (var i:int = 0; i < _points.length; i++) + { + p = _points[i]; + var dx:Number = parent.x + p.x - circle.parent.x - circle.radius; + var dy:Number = parent.y + p.y - circle.parent.y - circle.radius; + var tempDistance:Number = dx * dx + dy * dy; + + if (tempDistance < distanceSquared) + { + distanceSquared = tempDistance; + closestPoint = p; + } + } + + var offsetX:Number = parent.x - circle.parent.x - circle.radius; + var offsetY:Number = parent.y - circle.parent.y - circle.radius; + + //Get the vector between the closest point and the circle + //and get the normal of it + _axis.x = circle.parent.y - parent.y + closestPoint.y; + _axis.y = parent.x + closestPoint.x - circle.parent.x; + _axis.normalize(1); + + project(_axis, firstProj); + circle.project(_axis, secondProj); + + offset = offsetX * _axis.x + offsetY * _axis.y; + firstProj.min += offset; + firstProj.max += offset; + + // if firstProj overlaps secondProj + if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) + { + return false; + } + + var a:Point; + + for (var j:int = 0; j < _axes.length; j++) + { + a = _axes[j]; + project(a, firstProj); + circle.project(a, secondProj); + + offset = offsetX * a.x + offsetY * a.y; + firstProj.min += offset; + firstProj.max += offset; + + // if firstProj overlaps secondProj + if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) + { + return false; + } + } + + return true; + } + + /** + * Checks for collision with a polygon. + */ + public function collidePolygon(other:Polygon):Boolean + { + var offsetX:Number = parent.x - other.parent.x; + var offsetY:Number = parent.y - other.parent.y; + var a:Point; + + for (var i:int = 0; i < _axes.length; i++) + { + a = _axes[i]; + project(a, firstProj); + other.project(a, secondProj); + + //Shift the first info with the offset + var offset:Number = offsetX * a.x + offsetY * a.y; + firstProj.min += offset; + firstProj.max += offset; + + // if firstProj overlaps secondProj + if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) + { + return false; + } + } + + for (var j:int = 0; j < other._axes.length; j++) + { + a = _axes[j]; + project(a, firstProj); + other.project(a, secondProj); + + //Shift the first info with the offset + offset = offsetX * a.x + offsetY * a.y; + firstProj.min += offset; + firstProj.max += offset; + + // if firstProj overlaps secondProj + if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) + { + return false; + } + } + return true; + } + + /** @private */ + override public function project(axis:Point, projection:Object):void + { + var p:Point = _points[0]; + + var min:Number = axis.x * p.x + axis.y * p.y, // dot product + max:Number = min; + + for (var i:int = 1; i < _points.length; i++) + { + p = _points[i]; + var cur:Number = axis.x * p.x + axis.y * p.y; // dot product + + if (cur < min) + { + min = cur; + } + else if (cur > max) + { + max = cur; + } + } + projection.min = min; + projection.max = max; + } + + private function rotate(angle:Number):void + { + angle *= FP.RAD; + + var p:Point; + + for (var i:int = 0; i < _points.length; i++) + { + p = _points[i]; + var dx:Number = p.x - origin.x; + var dy:Number = p.y - origin.y; + + var pointAngle:Number = Math.atan2(dy, dx); + var length:Number = Math.sqrt(dx * dx + dy * dy); + + p.x = Math.cos(pointAngle + angle) * length + origin.x; + p.y = Math.sin(pointAngle + angle) * length + origin.y; + } + var a:Point; + + for (var j:int = 0; j < _axes.length; j++) + { + a = _axes[j]; + + var axisAngle:Number = Math.atan2(a.y, a.x); + + a.x = Math.cos(axisAngle + angle); + a.y = Math.sin(axisAngle + angle); + } + _angle += angle; + } + + override public function renderDebug(graphics:Graphics):void + { + if (parent != null) + { + var offsetX:Number = parent.x - FP.camera.x, + offsetY:Number = parent.y - FP.camera.y; + + var sx:Number = FP.screen.scaleX * FP.screen.scale; + var sy:Number = FP.screen.scaleY * FP.screen.scale; + + graphics.beginFill(0xFFFFFF, .15); + graphics.lineStyle(1, 0xFFFFFF, 0.25); + + graphics.moveTo((points[_points.length - 1].x + offsetX) * sx , (_points[_points.length - 1].y + offsetY) * sy); + for (var ii:int = 0; ii < _points.length; ii++) + { + graphics.lineTo((_points[ii].x + offsetX) * sx, (_points[ii].y + offsetY) * sy); + } + } + } + + /** + * Angle in degress that the polygon is rotated. + */ + public function get angle():Number { return _angle; } + public function set angle(value:Number):void + { + if (value == _angle) return; + rotate(_angle - value); + if (list != null || parent != null) update(); + } + + /** + * The points representing the polygon. + * If you need to set a point yourself instead of passing in a new Array you need to call update() to makes sure the axes update as well. + */ + public function get points():Vector. { return _points; } + public function set points(value:Vector.):void + { + if (_points == value) return; + _points = value; + + if (list != null || parent != null) updateAxes(); + } + + /** Updates the parent's bounds for this mask. */ + override public function update():void + { + project(horizontal, firstProj); //width + _x = Math.ceil(firstProj.min); + _width = Math.ceil(firstProj.max - firstProj.min); + project(vertical, secondProj); //height + _y = Math.ceil(secondProj.min); + _height = Math.ceil(secondProj.max - secondProj.min); + + if (parent != null) + { + //update entity bounds + parent.width = _width; + parent.height = _height; + + //Since the collisioninfos haven't changed we can use them to calculate hitbox placement + parent.originX = int((_width - firstProj.max - firstProj.min)/2); + parent.originY = int((_height - secondProj.max - secondProj.min )/2); + } + + // update parent list + if (list != null) list.update(); + } + + /** + * Creates a regular polygon. + * @param sides The number of sides in the polygon + * @param radius The distance that the corners are at + * @param angle How much the polygon is rotated + * @return The polygon + */ + public static function createPolygon(sides:int = 3, radius:Number = 100, angle:Number = 0):Polygon + { + if (sides < 3) throw "The polygon needs at least 3 sides"; + // create a return polygon + // figure out the angles required + var rotationAngle:Number = (Math.PI * 2) / sides; + + // loop through and generate each point + var points:Vector. = new Vector.(); + + for (var ii:int = 0; ii < sides; ii++) + { + var tempAngle:Number = ii * rotationAngle; + var p:Point = new Point(); + p.x = Math.cos(tempAngle) * radius; + p.y = Math.sin(tempAngle) * radius; + points.push(p); + } + // return the point + var poly:Polygon = new Polygon(points); + poly.angle = angle; + return poly; + } + + /** + * Creates a polygon from an array were even numbers are x and odd are y + * @param points Array containing the polygon's points. + * + * @return The polygon + */ + public static function createFromVector(points:Vector.):Polygon + { + var p:Vector. = new Vector.(); + + var ii:int = 0; + while (ii < points.length) + { + p.push(new Point(points[ii++], points[ii++])); + } + return new Polygon(p); + } + + private function generateAxes():void + { + _axes = new Vector.(); + var store:Number; + var numberOfPoints:int = _points.length - 1; + var edge:Point; + + for (var i:int = 0; i < numberOfPoints; i++) + { + edge = new Point(); + edge.x = _points[i].x - _points[i + 1].x; + edge.y = _points[i].y - _points[i + 1].y; + + //Get the axis which is perpendicular to the edge + store = edge.y; + edge.y = -edge.x; + edge.x = store; + edge.normalize(1); + + _axes.push(edge); + } + edge = new Point(); + //Add the last edge + edge.x = _points[numberOfPoints].x - _points[0].x; + edge.y = _points[numberOfPoints].y - _points[0].y; + store = edge.y; + edge.y = -edge.x; + edge.x = store; + edge.normalize(1); + + _axes.push(edge); + } + + private function removeDuplicateAxes():void + { + for (var ii:int = 0; ii < _axes.length; ii++ ) + { + for (var jj:int = 0; jj < _axes.length; jj++ ) + { + if (ii == jj || Math.max(ii, jj) >= _axes.length) continue; + // if the first vector is equal or similar to the second vector, + // remove it from the list. (for example, [1, 1] and [-1, -1] + // share the same relative path) + if ((_axes[ii].x == _axes[jj].x && _axes[ii].y == _axes[jj].y) + || ( -_axes[ii].x == _axes[jj].x && -_axes[ii].y == _axes[jj].y))//First axis inverted + { + _axes.splice(jj, 1); + } + } + } + } + + private function updateAxes():void + { + generateAxes(); + removeDuplicateAxes(); + update(); + } + + // Hitbox information. + private var _angle:Number; + private var _points:Vector.; + private var _axes:Vector.; + private var _projection:* = { min: 0.0, max:0.0 }; + + private static var _axis:Point = new Point(); + private static var firstProj:* = { min: 0.0, max:0.0 }; + private static var secondProj:* = { min: 0.0, max:0.0 }; + + public static var vertical:Point = new Point(0, 1); + public static var horizontal:Point = new Point(1, 0); + } +} \ No newline at end of file From deddbe1f6c615cf03b144c1bd5d6e2f93c4a290b Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Sat, 30 Nov 2013 03:01:18 +0100 Subject: [PATCH 02/15] fixes for Polygon vs Grid collision --- net/flashpunk/masks/Grid.as | 1 + net/flashpunk/masks/Polygon.as | 153 +++++++++++++++------------------ 2 files changed, 68 insertions(+), 86 deletions(-) diff --git a/net/flashpunk/masks/Grid.as b/net/flashpunk/masks/Grid.as index ed3b7f5..f6457dc 100644 --- a/net/flashpunk/masks/Grid.as +++ b/net/flashpunk/masks/Grid.as @@ -362,6 +362,7 @@ } } } + g.endFill(); } diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index 802fb43..b23434c 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -1,7 +1,9 @@ package net.flashpunk.masks { + import flash.display.BitmapData; import flash.display.Graphics; import flash.geom.Point; + import net.flashpunk.Entity; import net.flashpunk.FP; import net.flashpunk.Mask; @@ -21,6 +23,9 @@ package net.flashpunk.masks public function Polygon(points:Vector., origin:Point = null) { _points = points; + _fakeEntity = new Entity(); + _fakeTileHitbox = new Hitbox(); + _fakePixelmask = new Pixelmask(new BitmapData(1, 1)); _check[Mask] = collideMask; _check[Hitbox] = collideHitbox; @@ -91,7 +96,7 @@ package net.flashpunk.masks /** * Checks for collisions with a hitbox. */ - public function collideHitbox(hitbox:Hitbox):Boolean + private function collideHitbox(hitbox:Hitbox):Boolean { var offset:Number, offsetX:Number = parent.x - hitbox.parent.x, @@ -146,67 +151,37 @@ package net.flashpunk.masks * Checks for collisions along the edges of the polygon. * May be very slow, mainly added for completeness sake. */ - public function collideGrid(grid:Grid):Boolean + private function collideGrid(grid:Grid):Boolean { - var p1X:Number, p1Y:Number, - p2X:Number, p2Y:Number, - k:Number, m:Number, - x:Number, y:Number, - min:Number, max:Number; - - for (var ii:int = 0; ii < _points.length - 1; ii++) - { - p1X = (parent.x + _points[ii].x) / grid.tileWidth; - p1Y = (parent.y + _points[ii].y) / grid.tileHeight; - p2X = (parent.x + _points[ii + 1].x) / grid.tileWidth; - p2Y = (parent.y + _points[ii + 1].y) / grid.tileHeight; - - k = (p2Y - p1Y) / (p2X - p1X); - m = p1Y - k * p1X; - - if (p2X > p1X) { min = p1X; max = p2X; } - else { max = p1X; min = p2X; } - - x = min; - while (x < max) - { - y = int(k * x + m); - if (grid.getTile(int(x), y)) - return true; - - x++; - } - } + var tileW:uint = grid.tileWidth; + var tileH:uint = grid.tileHeight; + var solidTile:Boolean; + + _fakeEntity.width = tileW; + _fakeEntity.height = tileH; + _fakeEntity.originX = grid.parent.originX; + _fakeEntity.originY = grid.parent.originY; - //Check the last point -> first point - p1X = (parent.x + _points[_points.length - 1].x) / grid.tileWidth; - p1Y = (parent.y + _points[_points.length - 1].y) / grid.tileHeight; - p2X = (parent.x + _points[0].x) / grid.tileWidth; - p2Y = (parent.y + _points[0].y) / grid.tileHeight; - - k = (p2Y - p1Y) / (p2X - p1X); - m = p1Y - k * p1X; - - if (p2X > p1X) { min = p1X; max = p2X; } - else { max = p1X; min = p2X; } - - x = min; - while (x < max) - { - y = int(k * x + m); - if (grid.getTile(int(x), y)) - return true; - - x++; + _fakeTileHitbox._width = tileW; + _fakeTileHitbox._height = tileH; + _fakeTileHitbox.parent = _fakeEntity; + + for (var r:int = 0; r < grid.rows; r++ ) { + for (var c:int = 0; c < grid.columns; c++) { + _fakeEntity.x = grid.parent.x + grid._x + c * tileW; + _fakeEntity.y = grid.parent.y + grid._y + r * tileH; + solidTile = grid.getTile(c, r); + + if (solidTile && collideHitbox(_fakeTileHitbox)) return true; + } } - return false; } /** * Checks for collision with a circle. */ - public function collideCircle(circle:Circle):Boolean + private function collideCircle(circle:Circle):Boolean { var offset:Number; @@ -276,7 +251,7 @@ package net.flashpunk.masks /** * Checks for collision with a polygon. */ - public function collidePolygon(other:Polygon):Boolean + private function collidePolygon(other:Polygon):Boolean { var offsetX:Number = parent.x - other.parent.x; var offsetY:Number = parent.y - other.parent.y; @@ -346,38 +321,6 @@ package net.flashpunk.masks projection.max = max; } - private function rotate(angle:Number):void - { - angle *= FP.RAD; - - var p:Point; - - for (var i:int = 0; i < _points.length; i++) - { - p = _points[i]; - var dx:Number = p.x - origin.x; - var dy:Number = p.y - origin.y; - - var pointAngle:Number = Math.atan2(dy, dx); - var length:Number = Math.sqrt(dx * dx + dy * dy); - - p.x = Math.cos(pointAngle + angle) * length + origin.x; - p.y = Math.sin(pointAngle + angle) * length + origin.y; - } - var a:Point; - - for (var j:int = 0; j < _axes.length; j++) - { - a = _axes[j]; - - var axisAngle:Number = Math.atan2(a.y, a.x); - - a.x = Math.cos(axisAngle + angle); - a.y = Math.sin(axisAngle + angle); - } - _angle += angle; - } - override public function renderDebug(graphics:Graphics):void { if (parent != null) @@ -396,6 +339,8 @@ package net.flashpunk.masks { graphics.lineTo((_points[ii].x + offsetX) * sx, (_points[ii].y + offsetY) * sy); } + + graphics.endFill(); } } @@ -497,6 +442,38 @@ package net.flashpunk.masks return new Polygon(p); } + private function rotate(angle:Number):void + { + angle *= FP.RAD; + + var p:Point; + + for (var i:int = 0; i < _points.length; i++) + { + p = _points[i]; + var dx:Number = p.x - origin.x; + var dy:Number = p.y - origin.y; + + var pointAngle:Number = Math.atan2(dy, dx); + var length:Number = Math.sqrt(dx * dx + dy * dy); + + p.x = Math.cos(pointAngle + angle) * length + origin.x; + p.y = Math.sin(pointAngle + angle) * length + origin.y; + } + var a:Point; + + for (var j:int = 0; j < _axes.length; j++) + { + a = _axes[j]; + + var axisAngle:Number = Math.atan2(a.y, a.x); + + a.x = Math.cos(axisAngle + angle); + a.y = Math.sin(axisAngle + angle); + } + _angle += angle; + } + private function generateAxes():void { _axes = new Vector.(); @@ -561,6 +538,10 @@ package net.flashpunk.masks private var _points:Vector.; private var _axes:Vector.; private var _projection:* = { min: 0.0, max:0.0 }; + + private var _fakeEntity:Entity; // used for Grid collision + private var _fakeTileHitbox:Hitbox; // used for Grid collision + private var _fakePixelmask:Pixelmask; // used for Pixelmask collision private static var _axis:Point = new Point(); private static var firstProj:* = { min: 0.0, max:0.0 }; From 2cccd5271a70314e14ba4d1e4973eb5e1620eb46 Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Sat, 30 Nov 2013 18:39:27 +0100 Subject: [PATCH 03/15] Polygon vs Circle (now works but not if rotated) --- net/flashpunk/masks/Polygon.as | 132 ++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 58 deletions(-) diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index b23434c..eef70b6 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -6,6 +6,7 @@ package net.flashpunk.masks import net.flashpunk.Entity; import net.flashpunk.FP; import net.flashpunk.Mask; + import net.flashpunk.utils.Draw; public class Polygon extends Hitbox @@ -182,70 +183,85 @@ package net.flashpunk.masks * Checks for collision with a circle. */ private function collideCircle(circle:Circle):Boolean - { - var offset:Number; - - //First find the point closest to the circle - var distanceSquared:Number = int.MAX_VALUE; - var closestPoint:Point = null; - var p:Point; + { + var edgesCrossed:int = 0; + var p1:Point, p2:Point; + var i:int, j:int; + var nPoints:int = _points.length; - for (var i:int = 0; i < _points.length; i++) - { - p = _points[i]; - var dx:Number = parent.x + p.x - circle.parent.x - circle.radius; - var dy:Number = parent.y + p.y - circle.parent.y - circle.radius; - var tempDistance:Number = dx * dx + dy * dy; - - if (tempDistance < distanceSquared) - { - distanceSquared = tempDistance; - closestPoint = p; + // check if circle center is inside the polygon + for (i = 0, j = nPoints - 1; i < _points.length; j = i, i++) { + p1 = _points[i]; + p2 = _points[j]; + + var distFromCenter:Number = (p2.x - p1.x) * (circle._y + circle.parent.y - p1.y - parent.y - _y) / (p2.y - p1.y) + p1.x + parent.x + _x; + + if (((p1.y + parent.y + _y > circle._y + circle.parent.y) != (p2.y + parent.y + _y > circle._y + circle.parent.y))) { + if ((circle._x + circle.parent.x < distFromCenter)) + { + edgesCrossed++; + } } + + Draw.enqueueCall(function ():void + { + Draw.dot(FP.halfWidth - 10 * i, p1.y + parent.y + _y); + Draw.dot(FP.halfWidth - 10 * i, p2.y + parent.y + _y); + Draw.dot(FP.halfWidth - 10 * i, circle._y + circle.parent.y, 0xffff00); + Draw.dot(distFromCenter, FP.halfHeight - 10 * i, 0xFF0000); + }); } - - var offsetX:Number = parent.x - circle.parent.x - circle.radius; - var offsetY:Number = parent.y - circle.parent.y - circle.radius; - - //Get the vector between the closest point and the circle - //and get the normal of it - _axis.x = circle.parent.y - parent.y + closestPoint.y; - _axis.y = parent.x + closestPoint.x - circle.parent.x; - _axis.normalize(1); - - project(_axis, firstProj); - circle.project(_axis, secondProj); - - offset = offsetX * _axis.x + offsetY * _axis.y; - firstProj.min += offset; - firstProj.max += offset; - - // if firstProj overlaps secondProj - if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) - { - return false; + + if (edgesCrossed & 1) { + trace("pnpoly"); + return true; } - - var a:Point; + + + // check if minimum distance from circle center to each polygon side is less than radius + var radiusSqr:Number = circle.radius * circle.radius; + var cx:Number = circle._x + circle.parent.x; + var cy:Number = circle._y + circle.parent.y; + var offsetX:Number = parent.x + _x; + var offsetY:Number = parent.y + _y; + var minDistanceSqr:Number = 0; + var closestX:Number; + var closestY:Number; - for (var j:int = 0; j < _axes.length; j++) - { - a = _axes[j]; - project(a, firstProj); - circle.project(a, secondProj); + for (i = 0, j = nPoints - 1; i < _points.length; j = i, i++) { + p1 = _points[i]; + p2 = _points[j]; - offset = offsetX * a.x + offsetY * a.y; - firstProj.min += offset; - firstProj.max += offset; - - // if firstProj overlaps secondProj - if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) - { - return false; + var segmentLenSqr:Number = (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y); + + // find projection of center onto line (extended segment) + var t:Number = ((cx - p1.x - offsetX) * (p2.x - p1.x) + (cy - p1.y - offsetY) * (p2.y - p1.y)) / segmentLenSqr; + + if (t < 0) { + closestX = p1.x; + closestY = p1.y; + } else if (t > 1) { + closestX = p2.x; + closestY = p2.y; + } else { + closestX = p1.x + t * (p2.x - p1.x); + closestY = p1.y + t * (p2.y - p1.y); } + closestX += offsetX; + closestY += offsetY; + + minDistanceSqr = (cx - closestX) * (cx - closestX) + (cy - closestY) * (cy - closestY); + + Draw.enqueueCall(function ():void + { + Draw.dot(closestX, closestY); + Draw.text("" + t, 10, 20+10*i); + }); + + if (minDistanceSqr <= radiusSqr) return true; } - return true; + return false; } /** @@ -277,7 +293,7 @@ package net.flashpunk.masks for (var j:int = 0; j < other._axes.length; j++) { - a = _axes[j]; + a = other._axes[j]; project(a, firstProj); other.project(a, secondProj); @@ -295,7 +311,7 @@ package net.flashpunk.masks return true; } - /** @private */ + /** @private Projects polygon points on axis and returns min and max values in projection object. */ override public function project(axis:Point, projection:Object):void { var p:Point = _points[0]; @@ -384,7 +400,7 @@ package net.flashpunk.masks parent.width = _width; parent.height = _height; - //Since the collisioninfos haven't changed we can use them to calculate hitbox placement + //Since the collision infos haven't changed we can use them to calculate hitbox placement parent.originX = int((_width - firstProj.max - firstProj.min)/2); parent.originY = int((_height - secondProj.max - secondProj.min )/2); } From 6a82adc760faa5eaa8ebcd33f7ee5211062a624d Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Sat, 30 Nov 2013 21:58:13 +0100 Subject: [PATCH 04/15] Polygon vs Pixelmask (let's say that it seems to work), added a TestWorld and a bunch of other things to make debugging easier (will purge once finished) --- Main.as | 31 +++ TestWorld.as | 160 ++++++++++++++++ net/flashpunk/World.as | 2 + net/flashpunk/masks/Circle.as | 5 +- net/flashpunk/masks/Polygon.as | 75 ++++++-- net/flashpunk/utils/Draw.as | 339 +++++++++++++++++++++++++++++++++ 6 files changed, 599 insertions(+), 13 deletions(-) create mode 100644 Main.as create mode 100644 TestWorld.as diff --git a/Main.as b/Main.as new file mode 100644 index 0000000..75a2f96 --- /dev/null +++ b/Main.as @@ -0,0 +1,31 @@ +package { + import net.flashpunk.Engine; + import net.flashpunk.FP; + + + /** + * ... + * @author azrafe7 + */ + [SWF(width="640", height="400", backgroundColor="#000000")] + public class Main extends Engine + { + + public function Main() { + super(320, 200, 60, false); + } + + override public function init():void { + super.init(); + FP.screen.scale = 2; + FP.console.enable(); + + FP.world = new TestWorld; + } + + public static function main():void { + new Main(); + } + + } +} \ No newline at end of file diff --git a/TestWorld.as b/TestWorld.as new file mode 100644 index 0000000..2a64ef9 --- /dev/null +++ b/TestWorld.as @@ -0,0 +1,160 @@ +package +{ + import flash.geom.Point; + import flash.system.System; + import flash.text.AntiAliasType; + import flash.text.TextField; + import flash.text.TextFormat; + import net.flashpunk.Entity; + import net.flashpunk.FP; + import net.flashpunk.graphics.Text; + import net.flashpunk.masks.Circle; + import net.flashpunk.masks.Grid; + import net.flashpunk.masks.Hitbox; + import net.flashpunk.masks.Pixelmask; + import net.flashpunk.Tween; + import net.flashpunk.tweens.misc.NumTween; + import net.flashpunk.utils.Draw; + import net.flashpunk.utils.Input; + import net.flashpunk.utils.Key; + import net.flashpunk.World; + import net.flashpunk.masks.Polygon; + + /** + * ... + * @author azrafe7 + */ + public class TestWorld extends World + { + + [Embed(source="../assets/skeleton.png")] + private var SKELETON:Class; + + private var eActive:Entity; + private var ePoly:Entity; + private var eCircle:Entity; + private var circle:Circle; + private var polygon:Polygon; + + public function TestWorld() + { + + } + + override public function begin():void { + + // interactive CIRCLE + eCircle = addMask(circle = new Circle(20, 20,20), "circle"); + eCircle.x = FP.halfWidth; + eCircle.y = FP.halfHeight + 20; + + // interactive POLYGON + var points:Vector. = new Vector.(); + points.push(new Point(0, 0)); + points.push(new Point(30, 0)); + points.push(new Point(30, 30)); + ePoly = addMask(polygon = new Polygon(points), "polygon"); + //ePoly = addMask(polygon = Polygon.createPolygon(5, 20, 360/10), "polygon"); + ePoly.x = FP.halfWidth; + ePoly.y = FP.halfHeight; + ePoly.centerOrigin(); + polygon.x = ePoly.originX; + polygon.y = ePoly.originY; + polygon.origin.x = ePoly.originX; + polygon.origin.y = ePoly.originY; + + + // other MASKS + + + // Mask/Entity + var e1:Entity = new Entity(200, 30); + e1.type = "mask"; + e1.width = 40; + e1.height = 50; + add(e1); + + // Hitbox + var hitbox:Hitbox = new Hitbox(30, 30, 20); + var e2:Entity = addMask(hitbox, "hitbox"); + e2.x = 20; + e2.y = 20; + + // Circle + var e3:Entity = addMask(new Circle(30, 0, 0), "circle"); + e3.x = 250; + e3.y = 110; + + // Grid + var gridMask:Grid = new Grid(140, 80, 20, 20); + var gridStr:String = + "1,0,0,1,1,1,0\n" + + "0,0,0,1,0,1,1\n" + + "1,0,0,0,0,0,1\n" + + "0,0,0,0,0,0,1\n"; + gridMask.loadFromString(gridStr); + var e4:Entity = addMask(gridMask, "grid", 5, 120); + + // Polygon + var polyMask:Polygon = Polygon.createPolygon(5, 20); + var e5:Entity = addMask(polyMask, "polygon"); + polyMask.origin.x = polyMask.parent.width/2; + polyMask.origin.y = polyMask.parent.height / 2; + polyMask.angle = 45; + e5.x = 130; + e5.y = 40; + polyMask.update(); + + // Pixelmask + var pixelmask:Pixelmask = new Pixelmask(SKELETON); + var e6:Entity = addMask(pixelmask, "pixelmask"); + e6.x = 260; + e6.y = 20; + + FP.log("~: enable Console | ARROWS: move Circle | SHIFT+ARROWS: move Polygon"); + } + + override public function update():void + { + super.update(); + + // ESC to exit + if (Input.pressed(Key.ESCAPE)) { + System.exit(1); + } + + if (Input.pressed(Key.SPACE)) { + polygon.angle += 90; + } + + if (!Input.check(Key.SHIFT)) { + ePoly.x += Input.check(Key.LEFT) ? -1 : Input.check(Key.RIGHT) ? 1 : 0; + ePoly.y += Input.check(Key.UP) ? -1 : Input.check(Key.DOWN) ? 1 : 0; + eActive = ePoly; + } else { + eCircle.x += Input.check(Key.LEFT) ? -1 : Input.check(Key.RIGHT) ? 1 : 0; + eCircle.y += Input.check(Key.UP) ? -1 : Input.check(Key.DOWN) ? 1 : 0; + eActive = eCircle; + } + + var hitEntities:Array = []; + eActive.collideTypesInto(["hitbox", "mask", "circle", "grid", "polygon", "pixelmask"], eActive.x, eActive.y, hitEntities); + + for (var i:int = 0; i < hitEntities.length; i++) { + var hitEntity:Entity = Entity(hitEntities[i]); + trace("hit " + hitEntity.type); + } + + } + + override public function render():void + { + super.render(); + + Draw.dot(FP.halfWidth, FP.halfHeight, 0x0000FF); + trace(polygon.x, ePoly.originX, polygon.origin.x, ePoly.width, polygon.width); + trace(polygon.y, ePoly.originY, polygon.origin.y, ePoly.height, polygon.height, "\n"); + } + } + +} \ No newline at end of file diff --git a/net/flashpunk/World.as b/net/flashpunk/World.as index aa9163b..f931e89 100644 --- a/net/flashpunk/World.as +++ b/net/flashpunk/World.as @@ -2,6 +2,7 @@ { import flash.geom.Point; import flash.utils.Dictionary; + import net.flashpunk.utils.Draw; /** * Updated by Engine, main game container that holds all currently active Entities. @@ -90,6 +91,7 @@ e = e._renderPrev; } } + Draw.renderCallQueue(); } /** diff --git a/net/flashpunk/masks/Circle.as b/net/flashpunk/masks/Circle.as index 1fb6f15..9bc3e77 100644 --- a/net/flashpunk/masks/Circle.as +++ b/net/flashpunk/masks/Circle.as @@ -1,6 +1,7 @@ package net.flashpunk.masks { + import flash.display.BitmapData; import net.flashpunk.FP; import net.flashpunk.Graphic; import net.flashpunk.Mask; @@ -25,6 +26,7 @@ package net.flashpunk.masks this.radius = radius; _x = x + radius; _y = y + radius; + _fakePixelmask = new Pixelmask(new BitmapData(1, 1)); _check[Mask] = collideMask; _check[Hitbox] = collideHitbox; @@ -194,6 +196,7 @@ package net.flashpunk.masks // Hitbox information. protected var _radius:int; - protected var _squaredRadius:int; //Set automatically through the setter for radius + protected var _squaredRadius:int; //Set automatically through the setter for radius + private var _fakePixelmask:Pixelmask; // used for Pixelmask collision } } \ No newline at end of file diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index eef70b6..e63b34e 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -31,6 +31,7 @@ package net.flashpunk.masks _check[Mask] = collideMask; _check[Hitbox] = collideHitbox; _check[Grid] = collideGrid; + _check[Pixelmask] = collidePixelmask; _check[Circle] = collideCircle; _check[Polygon] = collidePolygon; @@ -179,6 +180,54 @@ package net.flashpunk.masks return false; } + /** + * Checks for collision with a Pixelmask. + * May be very slow, mainly added for completeness sake. + */ + private function collidePixelmask(pixelmask:Pixelmask):Boolean + { + var data:BitmapData = _fakePixelmask._data; + + _fakePixelmask._x = _x; + _fakePixelmask._y = _y; + _fakePixelmask.parent = parent; + + if (data == null || (data.width < _width || data.height < _height)) { + data = new BitmapData(_width, height, true, 0); + } else { + data.fillRect(data.rect, 0); + } + + var graphics:Graphics = FP.sprite.graphics; + graphics.clear(); + + graphics.beginFill(0xFFFFFF, 1); + graphics.lineStyle(1, 0xFFFFFF, 1); + + var offsetX:Number = _x + parent.originX * 2; + var offsetY:Number = _y + parent.originY * 2; + + graphics.moveTo(points[_points.length - 1].x + offsetX, _points[_points.length - 1].y + offsetY); + for (var ii:int = 0; ii < _points.length; ii++) + { + graphics.lineTo(_points[ii].x + offsetX, _points[ii].y + offsetY); + } + + graphics.endFill(); + + data.draw(FP.sprite); + + Draw.enqueueCall(function ():void + { + FP.buffer.copyPixels(_fakePixelmask.data, _fakePixelmask.data.rect, new Point(50, 70)); + Draw.rectPlus(50, 70, data.width, data.height, 0xFF0000, .5, false); + }); + + _fakePixelmask.data = data; + + return pixelmask.collide(_fakePixelmask); + } + /** * Checks for collision with a circle. */ @@ -188,15 +237,18 @@ package net.flashpunk.masks var p1:Point, p2:Point; var i:int, j:int; var nPoints:int = _points.length; + var offsetX:Number = parent.x + (_x >= 0 ? _x : parent.originX); + var offsetY:Number = parent.y + (_y >= 0 ? _y : parent.originY); + // check if circle center is inside the polygon for (i = 0, j = nPoints - 1; i < _points.length; j = i, i++) { p1 = _points[i]; p2 = _points[j]; - var distFromCenter:Number = (p2.x - p1.x) * (circle._y + circle.parent.y - p1.y - parent.y - _y) / (p2.y - p1.y) + p1.x + parent.x + _x; + var distFromCenter:Number = (p2.x - p1.x) * (circle._y + circle.parent.y - p1.y - offsetY) / (p2.y - p1.y) + p1.x + offsetX; - if (((p1.y + parent.y + _y > circle._y + circle.parent.y) != (p2.y + parent.y + _y > circle._y + circle.parent.y))) { + if (((p1.y + offsetY > circle._y + circle.parent.y) != (p2.y + offsetY > circle._y + circle.parent.y))) { if ((circle._x + circle.parent.x < distFromCenter)) { edgesCrossed++; @@ -208,7 +260,7 @@ package net.flashpunk.masks Draw.dot(FP.halfWidth - 10 * i, p1.y + parent.y + _y); Draw.dot(FP.halfWidth - 10 * i, p2.y + parent.y + _y); Draw.dot(FP.halfWidth - 10 * i, circle._y + circle.parent.y, 0xffff00); - Draw.dot(distFromCenter, FP.halfHeight - 10 * i, 0xFF0000); + Draw.dot(FP.halfWidth + distFromCenter - parent.x - _x, FP.halfHeight, 0xFF0000); }); } @@ -222,8 +274,6 @@ package net.flashpunk.masks var radiusSqr:Number = circle.radius * circle.radius; var cx:Number = circle._x + circle.parent.x; var cy:Number = circle._y + circle.parent.y; - var offsetX:Number = parent.x + _x; - var offsetY:Number = parent.y + _y; var minDistanceSqr:Number = 0; var closestX:Number; var closestY:Number; @@ -458,9 +508,11 @@ package net.flashpunk.masks return new Polygon(p); } - private function rotate(angle:Number):void + private function rotate(angleDelta:Number):void { - angle *= FP.RAD; + _angle += angleDelta; + + angleDelta *= FP.RAD; var p:Point; @@ -473,8 +525,8 @@ package net.flashpunk.masks var pointAngle:Number = Math.atan2(dy, dx); var length:Number = Math.sqrt(dx * dx + dy * dy); - p.x = Math.cos(pointAngle + angle) * length + origin.x; - p.y = Math.sin(pointAngle + angle) * length + origin.y; + p.x = Math.cos(pointAngle + angleDelta) * length + origin.x; + p.y = Math.sin(pointAngle + angleDelta) * length + origin.y; } var a:Point; @@ -484,10 +536,9 @@ package net.flashpunk.masks var axisAngle:Number = Math.atan2(a.y, a.x); - a.x = Math.cos(axisAngle + angle); - a.y = Math.sin(axisAngle + angle); + a.x = Math.cos(axisAngle + angleDelta); + a.y = Math.sin(axisAngle + angleDelta); } - _angle += angle; } private function generateAxes():void diff --git a/net/flashpunk/utils/Draw.as b/net/flashpunk/utils/Draw.as index c095d68..8d4d08c 100644 --- a/net/flashpunk/utils/Draw.as +++ b/net/flashpunk/utils/Draw.as @@ -444,10 +444,349 @@ textGfx.render(_target, FP.zero, _camera); } + /** + * Draws a tiny rectangle centered at x, y. + * @param x The point's x. + * @param y The point's y. + * @param color Color of the rectangle. + * @param alpha Alpha of the rectangle. + * @param size Size of the rectangle. + */ + public static function dot(x:Number, y:Number, color:uint = 0xFFFFFF, alpha:Number = 1, size:Number = 3):void + { + x -= _camera.x; + y -= _camera.y; + + var halfSize:Number = size / 2; + Draw.rectPlus(x - halfSize + _camera.x, y - halfSize + _camera.y, size, size, color, alpha, false); + } + + /** + * Draws a smooth, antialiased line with an arrow head at the ending point. + * @param x1 Starting x position. + * @param y1 Starting y position. + * @param x2 Ending x position. + * @param y2 Ending y position. + * @param color Color of the line. + * @param alpha Alpha of the line. + */ + public static function arrow(x1:Number, y1:Number, x2:Number, y2:Number, color:uint = 0xFFFFFF, alpha:Number = 1):void + { + x1 -= _camera.x; + y1 -= _camera.y; + x2 -= _camera.x; + y2 -= _camera.y; + + // temporarily set camera to zero, otherwise it will be reapplied in called functions + var _savedCamera:Point = _camera; + _camera = FP.zero; + + var lineAngleRad:Number = FP.angle(x1, y1, x2, y2) * FP.RAD; + var dx:Number = x2 - x1; + var dy:Number = y2 - y1; + var len:Number = Math.sqrt(dx * dx + dy * dy); + if (len == 0) return; + + var arrowStartX:Number = (len-5) * Math.cos(lineAngleRad); + var arrowStartY:Number = (len-5) * Math.sin(lineAngleRad); + FP.point.x = -dy; + FP.point.y = dx; + FP.point.normalize(1); + + Draw.linePlus(x1, y1, x2, y2, color, alpha); + Draw.linePlus(x1 + arrowStartX + FP.point.x * 3, y1 + arrowStartY + FP.point.y * 3, x2, y2, color, alpha); + Draw.linePlus(x1 + arrowStartX - FP.point.x * 3, y1 + arrowStartY - FP.point.y * 3, x2, y2, color, alpha); + + // restore camera + _camera = _savedCamera; + } + + /** + * Draws a smooth, antialiased line with optional arrow heads at the start and end point. + * @param x1 Starting x position. + * @param y1 Starting y position. + * @param x2 Ending x position. + * @param y2 Ending y position. + * @param color Color of the line. + * @param alpha Alpha of the line. + * @param thick Thickness of the line. + * @param arrowAngle Angle (in degrees) between the line and the arm of the arrow heads (defaults to 30). + * @param arrowLength Pixel length of each arm of the arrow heads. + * @param arrowAtStart Whether or not to draw and arrow head over the starting point. + * @param arrowAtEnd Whether or not to draw and arrow head over the ending point. + */ + public static function arrowPlus(x1:Number, y1:Number, x2:Number, y2:Number, color:uint = 0xFFFFFF, alpha:Number = 1, thick:Number = 1, arrowAngle:Number=30, arrowLength:Number=6, arrowAtStart:Boolean = false, arrowAtEnd:Boolean = true):void + { + x1 -= _camera.x; + y1 -= _camera.y; + x2 -= _camera.x; + y2 -= _camera.y; + + // temporarily set camera to zero, otherwise it will be reapplied in called functions + var _savedCamera:Point = _camera; + _camera = FP.zero; + + if (color > 0xFFFFFF) color = 0xFFFFFF & color; + _graphics.clear(); + + _graphics.lineStyle(thick, color, alpha, false, LineScaleMode.NORMAL, null, JointStyle.MITER); + + linePlus(x1, y1, x2, y2, color, alpha, thick); + + var arrowAngleRad:Number = arrowAngle * FP.RAD; + var dir:Point = FP.point; + var normal:Point = FP.point2; + + dir.x = x2 - x1; + dir.y = y2 - y1; + normal.x = -dir.y; + normal.y = dir.x; + dir.normalize(1); + normal.normalize(1); + + var orthoLen:Number = arrowLength * Math.sin(arrowAngleRad); + var paralLen:Number = arrowLength * Math.cos(arrowAngleRad); + + if (arrowAtStart) { + linePlus(x1 + paralLen * dir.x + orthoLen * normal.x, y1 + paralLen * dir.y + orthoLen * normal.y, x1, y1, color, alpha, thick); + linePlus(x1 + paralLen * dir.x - orthoLen * normal.x, y1 + paralLen * dir.y - orthoLen * normal.y, x1, y1, color, alpha, thick); + } + + if (arrowAtEnd) { + linePlus(x2 - paralLen * dir.x + orthoLen * normal.x, y2 - paralLen * dir.y + orthoLen * normal.y, x2, y2, color, alpha, thick); + linePlus(x2 - paralLen * dir.x - orthoLen * normal.x, y2 - paralLen * dir.y - orthoLen * normal.y, x2, y2, color, alpha, thick); + } + + // restore camera + _camera = _savedCamera; + } + + /** + * Draws a circular arc (using lines) with an optional arrow head at the end point. + * @param centerX Center x of the arc. + * @param centerY Center y of the arc. + * @param radius Radius of the arc. + * @param startAngle Starting angle (in degrees) of the arc. + * @param spanAngle Angular span (in degrees) of the arc. + * @param color Color of the arc. + * @param alpha Alpha of the arc. + * @param drawArrow Whether or not to draw an arrow head over the ending point. + */ + public static function arc(centerX:Number, centerY:Number, radius:Number, startAngle:Number, spanAngle:Number, color:uint = 0xFFFFFF, alpha:Number = 1, drawArrow:Boolean = false):void + { + centerX -= _camera.x; + centerY -= _camera.y; + + // temporarily set camera to zero, otherwise it will be reapplied in called functions + var _savedCamera:Point = _camera; + _camera = FP.zero; + + var startAngleRad:Number = startAngle * FP.RAD; + var spanAngleRad:Number; + + // adjust angles if |span| > 360 + if (Math.abs(spanAngle) > 360) { + startAngleRad += (spanAngle % 360) * FP.RAD; + spanAngleRad = -FP.sign(spanAngle) * Math.PI * 2; + } else { + spanAngleRad = spanAngle * FP.RAD; + } + + var steps:int = Math.abs(spanAngleRad) * 10; + steps = steps > 0 ? steps : 1; + var angleStep:Number = spanAngleRad / steps; + + var x1:Number = centerX + Math.cos(startAngleRad) * radius; + var y1:Number = centerY + Math.sin(startAngleRad) * radius; + var x2:Number; + var y2:Number; + + for (var i:int = 0; i < steps; i++) { + var angle:Number = startAngleRad + (i + 1) * angleStep; + x2 = centerX + Math.cos(angle) * radius; + y2 = centerY + Math.sin(angle) * radius; + if (i == (steps - 1) && drawArrow) + arrow(x1, y1, x2, y2, color, alpha); + else + Draw.linePlus(x1, y1, x2, y2, color, alpha); + x1 = x2; + y1 = y2; + } + + // restore camera + _camera = _savedCamera; + } + + /** + * Draws a circular arc (using bezier curves) with an optional arrow head on the end point and other optional values. + * @param centerX Center x of the arc. + * @param centerY Center y of the arc. + * @param radius Radius of the arc. + * @param startAngle Starting angle (in degrees) of the arc. + * @param spanAngle Angular span (in degrees) of the arc. + * @param color Color of the arc. + * @param alpha Alpha of the arc. + * @param fill If the arc should be filled with the color (true) or just an outline (false). + * @param thick Thickness of the outline (only applicable when fill = false). + * @param drawArrow Whether or not to draw an arrow head over the ending point. + */ + public static function arcPlus(centerX:Number, centerY:Number, radius:Number, startAngle:Number, spanAngle:Number, color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1, drawArrow:Boolean = false):void + { + centerX -= _camera.x; + centerY -= _camera.y; + + // temporarily set camera to zero, otherwise it will be reapplied in called functions + var _savedCamera:Point = _camera; + _camera = FP.zero; + + if (color > 0xFFFFFF) color = 0xFFFFFF & color; + _graphics.clear(); + + var startAngleRad:Number = startAngle * FP.RAD; + var spanAngleRad:Number; + + // adjust angles if |span| > 360 + if (Math.abs(spanAngle) > 360) { + startAngleRad += (spanAngle % 360) * FP.RAD; + spanAngleRad = -FP.sign(spanAngle) * Math.PI * 2; + } else { + spanAngleRad = spanAngle * FP.RAD; + } + + var steps:int = Math.floor(Math.abs(spanAngleRad / (Math.PI / 4))) + 1; + var angleStep:Number = spanAngleRad / (2 * steps); + var controlRadius:Number = radius / Math.cos(angleStep); + + var startX:Number = centerX + Math.cos(startAngleRad) * radius; + var startY:Number = centerY + Math.sin(startAngleRad) * radius; + + if (fill) { + _graphics.beginFill(color, alpha); + _graphics.moveTo(centerX, centerY); + _graphics.lineTo(startX, startY); + } else { + _graphics.lineStyle(thick, color, alpha, false, LineScaleMode.NORMAL, null, JointStyle.MITER); + _graphics.moveTo(startX, startY); + } + + var endAngleRad:Number = 0; + var controlPoint:Point = FP.point; + var anchorPoint:Point = FP.point2; + + for (var i:int = 0; i < steps; i++) + { + endAngleRad = startAngleRad + angleStep; + startAngleRad = endAngleRad + angleStep; + + controlPoint.x = centerX + Math.cos(endAngleRad) * controlRadius; + controlPoint.y = centerY + Math.sin(endAngleRad) * controlRadius; + + anchorPoint.x = centerX + Math.cos(startAngleRad) * radius; + anchorPoint.y = centerY + Math.sin(startAngleRad) * radius; + + _graphics.curveTo(controlPoint.x, controlPoint.y, anchorPoint.x, anchorPoint.y); + } + + if (fill) _graphics.lineTo(centerX, centerY); + + FP.matrix.identity(); + FP.matrix.translate(-_camera.x, -_camera.y); + _target.draw(FP.sprite, FP.matrix, null, blend); + + if (drawArrow) { + FP.point.x = anchorPoint.x - centerX; + FP.point.y = anchorPoint.y - centerY; + FP.point.normalize(1); + Draw.arrowPlus(anchorPoint.x + FP.sign(angleStep) * FP.point.y, anchorPoint.y - FP.sign(angleStep) * FP.point.x, anchorPoint.x, anchorPoint.y, color, alpha, thick); + } + + // restore camera + _camera = _savedCamera; + } + + /** + * Draws a rotated rectangle (with optional pivot point). + * @param x X position of the rectangle. + * @param y Y position of the rectangle. + * @param width Width of the rectangle. + * @param height Height of the rectangle. + * @param color Color of the rectangle. + * @param alpha Alpha of the rectangle. + * @param fill If the rectangle should be filled with the color (true) or just an outline (false). + * @param thick How thick the outline should be (only applicable when fill = false). + * @param radius Round rectangle corners by this amount. + * @param angle Rotation of the rectangle (in degrees). + * @param pivotX X position around which the rotation should be performed (defaults to 0). + * @param pivotY Y position around which the rotation should be performed (defaults to 0). + */ + public static function rotatedRect(x:Number, y:Number, width:Number, height:Number, color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1, radius:Number = 0, angle:Number=0, pivotX:Number=0, pivotY:Number=0):void + { + x -= _camera.x; + y -= _camera.y; + + if (color > 0xFFFFFF) color = 0xFFFFFF & color; + _graphics.clear(); + + if (fill) { + _graphics.beginFill(color, alpha); + } else { + _graphics.lineStyle(thick, color, alpha, false, LineScaleMode.NORMAL, null, JointStyle.MITER); + } + + if (radius <= 0) { + _graphics.drawRect(0, 0, width, height); + } else { + _graphics.drawRoundRect(0, 0, width, height, radius); + } + + var angleRad:Number = angle * FP.RAD; + FP.matrix.identity(); + FP.matrix.translate(-pivotX, -pivotY); + FP.matrix.rotate(angleRad); + FP.matrix.tx += x; + FP.matrix.ty += y; + + _target.draw(FP.sprite, FP.matrix, null, blend); + } + + /** + * Enqueues a call to a function, to be executed at the end of the next render step. + * Useful to debug draw directly from the update step, rather than having to override the render method. + * + * Ex.: + * Draw.enqueueCall(function():Void { + * Draw.line(player.x, player.y, enemy.x, enemy.y); + * }); + * + * @param method The function to be enqueued. + */ + public static function enqueueCall(method:Function):void + { + if (method != null) + _callQueue.push(method); + else + throw new Error("[method] must be a non-null Function."); + } + + /** + * Executes all the functions enqueued with Draw.enqueueCall(), and clears the queue. (called from World.render()). + */ + public static function renderCallQueue():void + { + if (_callQueue.length <= 0) return; + + var len:int = _callQueue.length; + for (var i:int = 0; i < len; i++) { + _callQueue[i](); + } + _callQueue.length = 0; + } + // Drawing information. /** @private */ private static var _target:BitmapData; /** @private */ private static var _camera:Point; /** @private */ private static var _graphics:Graphics = FP.sprite.graphics; /** @private */ private static var _rect:Rectangle = FP.rect; + /** @private */ private static var _callQueue:Vector. = new Vector.(); } } From 98953b2fc78437bd4f07f8c5de3afc86cfb92271 Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Sat, 30 Nov 2013 23:00:04 +0100 Subject: [PATCH 05/15] Circle vs Pixelmask, Polygon vs Circle, refactored TestWorld as a demo --- TestWorld.as | 52 ++++++++++++++++++++++++++-------- net/flashpunk/masks/Circle.as | 45 +++++++++++++++++++++++++++++ net/flashpunk/masks/Polygon.as | 43 +++++++++------------------- 3 files changed, 99 insertions(+), 41 deletions(-) diff --git a/TestWorld.as b/TestWorld.as index 2a64ef9..a942d78 100644 --- a/TestWorld.as +++ b/TestWorld.as @@ -1,5 +1,6 @@ package { + import flash.display.BlendMode; import flash.geom.Point; import flash.system.System; import flash.text.AntiAliasType; @@ -35,6 +36,9 @@ package private var eCircle:Entity; private var circle:Circle; private var polygon:Polygon; + private var text:Text; + private var messages:Vector.; + private const MAX_MESSAGES:int = 7; public function TestWorld() { @@ -53,16 +57,16 @@ package points.push(new Point(0, 0)); points.push(new Point(30, 0)); points.push(new Point(30, 30)); - ePoly = addMask(polygon = new Polygon(points), "polygon"); - //ePoly = addMask(polygon = Polygon.createPolygon(5, 20, 360/10), "polygon"); + //ePoly = addMask(polygon = new Polygon(points), "polygon"); + ePoly = addMask(polygon = Polygon.createPolygon(5, 20, 360/10), "polygon"); ePoly.x = FP.halfWidth; ePoly.y = FP.halfHeight; - ePoly.centerOrigin(); + /*ePoly.centerOrigin(); polygon.x = ePoly.originX; polygon.y = ePoly.originY; polygon.origin.x = ePoly.originX; polygon.origin.y = ePoly.originY; - + */ // other MASKS @@ -96,14 +100,10 @@ package var e4:Entity = addMask(gridMask, "grid", 5, 120); // Polygon - var polyMask:Polygon = Polygon.createPolygon(5, 20); + var polyMask:Polygon = Polygon.createPolygon(8, 20); var e5:Entity = addMask(polyMask, "polygon"); - polyMask.origin.x = polyMask.parent.width/2; - polyMask.origin.y = polyMask.parent.height / 2; - polyMask.angle = 45; e5.x = 130; e5.y = 40; - polyMask.update(); // Pixelmask var pixelmask:Pixelmask = new Pixelmask(SKELETON); @@ -111,7 +111,23 @@ package e6.x = 260; e6.y = 20; - FP.log("~: enable Console | ARROWS: move Circle | SHIFT+ARROWS: move Polygon"); + text = new Text("puppa!"); + var textEntity:Entity = new Entity(5, 55, text); + text.blend = BlendMode.OVERLAY; + text.scrollX = 0; + text.scrollY = 0; + text.scale = .5; + text.setTextProperty("multiline", true); + add(textEntity); + + messages = new Vector.(); + messages.push("Enable the console..."); + messages.push("hit the play button on top...") + messages.push("then use keys described below"); + + text.text = messages.join("\n"); + + FP.log("~: enable Console | SHIFT+ARROWS: move Circle | ARROWS: move Polygon"); } override public function update():void @@ -123,6 +139,11 @@ package System.exit(1); } + // R to reset the world + if (Input.pressed(Key.R)) { + FP.world = new TestWorld(); + } + if (Input.pressed(Key.SPACE)) { polygon.angle += 90; } @@ -143,6 +164,15 @@ package for (var i:int = 0; i < hitEntities.length; i++) { var hitEntity:Entity = Entity(hitEntities[i]); trace("hit " + hitEntity.type); + messages.push("hit " + hitEntity.type); + if (messages.length > MAX_MESSAGES) messages.splice(0, 1); + text.text = messages.join("\n"); + + FP.alarm(.2, function ():void + { + messages.splice(0, 1); + text.text = messages.join("\n"); + }); } } @@ -152,8 +182,6 @@ package super.render(); Draw.dot(FP.halfWidth, FP.halfHeight, 0x0000FF); - trace(polygon.x, ePoly.originX, polygon.origin.x, ePoly.width, polygon.width); - trace(polygon.y, ePoly.originY, polygon.origin.y, ePoly.height, polygon.height, "\n"); } } diff --git a/net/flashpunk/masks/Circle.as b/net/flashpunk/masks/Circle.as index 9bc3e77..22b2b78 100644 --- a/net/flashpunk/masks/Circle.as +++ b/net/flashpunk/masks/Circle.as @@ -8,6 +8,7 @@ package net.flashpunk.masks import net.flashpunk.masks.Grid; import flash.display.Graphics; import flash.geom.Point; + import net.flashpunk.utils.Draw; /** * Uses circular area to determine collision. @@ -31,6 +32,7 @@ package net.flashpunk.masks _check[Mask] = collideMask; _check[Hitbox] = collideHitbox; _check[Grid] = collideGrid; + _check[Pixelmask] = collidePixelmask; _check[Circle] = collideCircle; } @@ -138,6 +140,49 @@ package net.flashpunk.masks return false; } + /** + * Checks for collision with a Pixelmask. + * May be slow (especially with big polygons), mainly added for completeness sake. + */ + private function collidePixelmask(pixelmask:Pixelmask):Boolean + { + var data:BitmapData = _fakePixelmask._data; + + _fakePixelmask._x = _x - _radius; + _fakePixelmask._y = _y - _radius; + _fakePixelmask.parent = parent; + + _width = _height = _radius * 2; + + if (data == null || (data.width < _width || data.height < _height)) { + data = new BitmapData(_width, height, true, 0); + } else { + data.fillRect(data.rect, 0); + } + + var graphics:Graphics = FP.sprite.graphics; + graphics.clear(); + + graphics.beginFill(0xFFFFFF, 1); + graphics.lineStyle(1, 0xFFFFFF, 1); + + graphics.drawCircle(_x - _radius, _y - _radius, _radius); + + graphics.endFill(); + + data.draw(FP.sprite); + + Draw.enqueueCall(function ():void + { + FP.buffer.copyPixels(_fakePixelmask.data, _fakePixelmask.data.rect, new Point(50, 70)); + Draw.rectPlus(50, 70, data.width, data.height, 0xFF0000, .5, false); + }); + + _fakePixelmask.data = data; + + return pixelmask.collide(_fakePixelmask); + } + /** @private Collides against a Circle. */ private function collideCircle(other:Circle):Boolean { diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index e63b34e..d6a2812 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -1,5 +1,6 @@ package net.flashpunk.masks { + import flash.display.BitmapData; import flash.display.Graphics; import flash.geom.Point; @@ -9,6 +10,9 @@ package net.flashpunk.masks import net.flashpunk.utils.Draw; + /** + * Uses polygon edges to check for collisions. + */ public class Polygon extends Hitbox { /** @@ -151,7 +155,7 @@ package net.flashpunk.masks /** * Checks for collisions along the edges of the polygon. - * May be very slow, mainly added for completeness sake. + * May be slow, mainly added for completeness sake. */ private function collideGrid(grid:Grid):Boolean { @@ -182,7 +186,7 @@ package net.flashpunk.masks /** * Checks for collision with a Pixelmask. - * May be very slow, mainly added for completeness sake. + * May be slow (especially with big polygons), mainly added for completeness sake. */ private function collidePixelmask(pixelmask:Pixelmask):Boolean { @@ -237,8 +241,8 @@ package net.flashpunk.masks var p1:Point, p2:Point; var i:int, j:int; var nPoints:int = _points.length; - var offsetX:Number = parent.x + (_x >= 0 ? _x : parent.originX); - var offsetY:Number = parent.y + (_y >= 0 ? _y : parent.originY); + var offsetX:Number = parent.x + _x + parent.originX; + var offsetY:Number = parent.y + _y + parent.originY; // check if circle center is inside the polygon @@ -248,28 +252,15 @@ package net.flashpunk.masks var distFromCenter:Number = (p2.x - p1.x) * (circle._y + circle.parent.y - p1.y - offsetY) / (p2.y - p1.y) + p1.x + offsetX; - if (((p1.y + offsetY > circle._y + circle.parent.y) != (p2.y + offsetY > circle._y + circle.parent.y))) { - if ((circle._x + circle.parent.x < distFromCenter)) - { - edgesCrossed++; - } - } - - Draw.enqueueCall(function ():void + if ((p1.y + offsetY > circle._y + circle.parent.y) != (p2.y + offsetY > circle._y + circle.parent.y) + && (circle._x + circle.parent.x < distFromCenter)) { - Draw.dot(FP.halfWidth - 10 * i, p1.y + parent.y + _y); - Draw.dot(FP.halfWidth - 10 * i, p2.y + parent.y + _y); - Draw.dot(FP.halfWidth - 10 * i, circle._y + circle.parent.y, 0xffff00); - Draw.dot(FP.halfWidth + distFromCenter - parent.x - _x, FP.halfHeight, 0xFF0000); - }); + edgesCrossed++; + } } - if (edgesCrossed & 1) { - trace("pnpoly"); - return true; - } - - + if (edgesCrossed & 1) return true; + // check if minimum distance from circle center to each polygon side is less than radius var radiusSqr:Number = circle.radius * circle.radius; var cx:Number = circle._x + circle.parent.x; @@ -302,12 +293,6 @@ package net.flashpunk.masks minDistanceSqr = (cx - closestX) * (cx - closestX) + (cy - closestY) * (cy - closestY); - Draw.enqueueCall(function ():void - { - Draw.dot(closestX, closestY); - Draw.text("" + t, 10, 20+10*i); - }); - if (minDistanceSqr <= radiusSqr) return true; } From 64c2c7f2c51e51c9c7158e596b8c53baf09edbe0 Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Sun, 1 Dec 2013 22:09:27 +0100 Subject: [PATCH 06/15] cleaned up, removed test code, better documentation/comments, minor optimizations --- Main.as | 31 --- TestWorld.as | 188 ------------------ net/flashpunk/Mask.as | 30 ++- net/flashpunk/World.as | 2 - net/flashpunk/masks/Circle.as | 19 +- net/flashpunk/masks/Polygon.as | 177 ++++++++--------- net/flashpunk/utils/Draw.as | 339 --------------------------------- 7 files changed, 109 insertions(+), 677 deletions(-) delete mode 100644 Main.as delete mode 100644 TestWorld.as diff --git a/Main.as b/Main.as deleted file mode 100644 index 75a2f96..0000000 --- a/Main.as +++ /dev/null @@ -1,31 +0,0 @@ -package { - import net.flashpunk.Engine; - import net.flashpunk.FP; - - - /** - * ... - * @author azrafe7 - */ - [SWF(width="640", height="400", backgroundColor="#000000")] - public class Main extends Engine - { - - public function Main() { - super(320, 200, 60, false); - } - - override public function init():void { - super.init(); - FP.screen.scale = 2; - FP.console.enable(); - - FP.world = new TestWorld; - } - - public static function main():void { - new Main(); - } - - } -} \ No newline at end of file diff --git a/TestWorld.as b/TestWorld.as deleted file mode 100644 index a942d78..0000000 --- a/TestWorld.as +++ /dev/null @@ -1,188 +0,0 @@ -package -{ - import flash.display.BlendMode; - import flash.geom.Point; - import flash.system.System; - import flash.text.AntiAliasType; - import flash.text.TextField; - import flash.text.TextFormat; - import net.flashpunk.Entity; - import net.flashpunk.FP; - import net.flashpunk.graphics.Text; - import net.flashpunk.masks.Circle; - import net.flashpunk.masks.Grid; - import net.flashpunk.masks.Hitbox; - import net.flashpunk.masks.Pixelmask; - import net.flashpunk.Tween; - import net.flashpunk.tweens.misc.NumTween; - import net.flashpunk.utils.Draw; - import net.flashpunk.utils.Input; - import net.flashpunk.utils.Key; - import net.flashpunk.World; - import net.flashpunk.masks.Polygon; - - /** - * ... - * @author azrafe7 - */ - public class TestWorld extends World - { - - [Embed(source="../assets/skeleton.png")] - private var SKELETON:Class; - - private var eActive:Entity; - private var ePoly:Entity; - private var eCircle:Entity; - private var circle:Circle; - private var polygon:Polygon; - private var text:Text; - private var messages:Vector.; - private const MAX_MESSAGES:int = 7; - - public function TestWorld() - { - - } - - override public function begin():void { - - // interactive CIRCLE - eCircle = addMask(circle = new Circle(20, 20,20), "circle"); - eCircle.x = FP.halfWidth; - eCircle.y = FP.halfHeight + 20; - - // interactive POLYGON - var points:Vector. = new Vector.(); - points.push(new Point(0, 0)); - points.push(new Point(30, 0)); - points.push(new Point(30, 30)); - //ePoly = addMask(polygon = new Polygon(points), "polygon"); - ePoly = addMask(polygon = Polygon.createPolygon(5, 20, 360/10), "polygon"); - ePoly.x = FP.halfWidth; - ePoly.y = FP.halfHeight; - /*ePoly.centerOrigin(); - polygon.x = ePoly.originX; - polygon.y = ePoly.originY; - polygon.origin.x = ePoly.originX; - polygon.origin.y = ePoly.originY; - */ - - // other MASKS - - - // Mask/Entity - var e1:Entity = new Entity(200, 30); - e1.type = "mask"; - e1.width = 40; - e1.height = 50; - add(e1); - - // Hitbox - var hitbox:Hitbox = new Hitbox(30, 30, 20); - var e2:Entity = addMask(hitbox, "hitbox"); - e2.x = 20; - e2.y = 20; - - // Circle - var e3:Entity = addMask(new Circle(30, 0, 0), "circle"); - e3.x = 250; - e3.y = 110; - - // Grid - var gridMask:Grid = new Grid(140, 80, 20, 20); - var gridStr:String = - "1,0,0,1,1,1,0\n" + - "0,0,0,1,0,1,1\n" + - "1,0,0,0,0,0,1\n" + - "0,0,0,0,0,0,1\n"; - gridMask.loadFromString(gridStr); - var e4:Entity = addMask(gridMask, "grid", 5, 120); - - // Polygon - var polyMask:Polygon = Polygon.createPolygon(8, 20); - var e5:Entity = addMask(polyMask, "polygon"); - e5.x = 130; - e5.y = 40; - - // Pixelmask - var pixelmask:Pixelmask = new Pixelmask(SKELETON); - var e6:Entity = addMask(pixelmask, "pixelmask"); - e6.x = 260; - e6.y = 20; - - text = new Text("puppa!"); - var textEntity:Entity = new Entity(5, 55, text); - text.blend = BlendMode.OVERLAY; - text.scrollX = 0; - text.scrollY = 0; - text.scale = .5; - text.setTextProperty("multiline", true); - add(textEntity); - - messages = new Vector.(); - messages.push("Enable the console..."); - messages.push("hit the play button on top...") - messages.push("then use keys described below"); - - text.text = messages.join("\n"); - - FP.log("~: enable Console | SHIFT+ARROWS: move Circle | ARROWS: move Polygon"); - } - - override public function update():void - { - super.update(); - - // ESC to exit - if (Input.pressed(Key.ESCAPE)) { - System.exit(1); - } - - // R to reset the world - if (Input.pressed(Key.R)) { - FP.world = new TestWorld(); - } - - if (Input.pressed(Key.SPACE)) { - polygon.angle += 90; - } - - if (!Input.check(Key.SHIFT)) { - ePoly.x += Input.check(Key.LEFT) ? -1 : Input.check(Key.RIGHT) ? 1 : 0; - ePoly.y += Input.check(Key.UP) ? -1 : Input.check(Key.DOWN) ? 1 : 0; - eActive = ePoly; - } else { - eCircle.x += Input.check(Key.LEFT) ? -1 : Input.check(Key.RIGHT) ? 1 : 0; - eCircle.y += Input.check(Key.UP) ? -1 : Input.check(Key.DOWN) ? 1 : 0; - eActive = eCircle; - } - - var hitEntities:Array = []; - eActive.collideTypesInto(["hitbox", "mask", "circle", "grid", "polygon", "pixelmask"], eActive.x, eActive.y, hitEntities); - - for (var i:int = 0; i < hitEntities.length; i++) { - var hitEntity:Entity = Entity(hitEntities[i]); - trace("hit " + hitEntity.type); - messages.push("hit " + hitEntity.type); - if (messages.length > MAX_MESSAGES) messages.splice(0, 1); - text.text = messages.join("\n"); - - FP.alarm(.2, function ():void - { - messages.splice(0, 1); - text.text = messages.join("\n"); - }); - } - - } - - override public function render():void - { - super.render(); - - Draw.dot(FP.halfWidth, FP.halfHeight, 0x0000FF); - } - } - -} \ No newline at end of file diff --git a/net/flashpunk/Mask.as b/net/flashpunk/Mask.as index 4bc6d30..895989c 100644 --- a/net/flashpunk/Mask.as +++ b/net/flashpunk/Mask.as @@ -79,36 +79,28 @@ } - /** @private */ + /** @private Projects this mask points on axis and returns min and max values in projection object. */ public function project(axis:Point, projection:Object):void { var cur:Number, - max:Number = -9999999999.0, - min:Number = 9999999999.0; + max:Number = Number.NEGATIVE_INFINITY, + min:Number = Number.POSITIVE_INFINITY; cur = -parent.originX * axis.x - parent.originY * axis.y; - if (cur < min) - min = cur; - if (cur > max) - max = cur; + if (cur < min) min = cur; + if (cur > max) max = cur; cur = (-parent.originX + parent.width) * axis.x - parent.originY * axis.y; - if (cur < min) - min = cur; - if (cur > max) - max = cur; + if (cur < min) min = cur; + if (cur > max) max = cur; cur = -parent.originX * axis.x + (-parent.originY + parent.height) * axis.y; - if (cur < min) - min = cur; - if (cur > max) - max = cur; + if (cur < min) min = cur; + if (cur > max) max = cur; cur = (-parent.originX + parent.width) * axis.x + (-parent.originY + parent.height)* axis.y; - if (cur < min) - min = cur; - if (cur > max) - max = cur; + if (cur < min) min = cur; + if (cur > max) max = cur; projection.min = min; projection.max = max; diff --git a/net/flashpunk/World.as b/net/flashpunk/World.as index f931e89..aa9163b 100644 --- a/net/flashpunk/World.as +++ b/net/flashpunk/World.as @@ -2,7 +2,6 @@ { import flash.geom.Point; import flash.utils.Dictionary; - import net.flashpunk.utils.Draw; /** * Updated by Engine, main game container that holds all currently active Entities. @@ -91,7 +90,6 @@ e = e._renderPrev; } } - Draw.renderCallQueue(); } /** diff --git a/net/flashpunk/masks/Circle.as b/net/flashpunk/masks/Circle.as index 22b2b78..16528d7 100644 --- a/net/flashpunk/masks/Circle.as +++ b/net/flashpunk/masks/Circle.as @@ -13,7 +13,6 @@ package net.flashpunk.masks /** * Uses circular area to determine collision. */ - public class Circle extends Hitbox { /** @@ -47,7 +46,7 @@ package net.flashpunk.masks if (distanceX > _otherHalfWidth + radius || distanceY > _otherHalfHeight + radius) { - return false; //The hitbox is to far away so return false + return false; // the hitbox/mask is too far away so return false } if (distanceX <= _otherHalfWidth || distanceY <= _otherHalfHeight) { @@ -70,7 +69,7 @@ package net.flashpunk.masks if (distanceX > _otherHalfWidth + radius || distanceY > _otherHalfHeight + radius) { - return false; //The hitbox is to far away so return false + return false; // the hitbox is too far away so return false } if (distanceX <= _otherHalfWidth || distanceY <= _otherHalfHeight) { @@ -142,7 +141,9 @@ package net.flashpunk.masks /** * Checks for collision with a Pixelmask. - * May be slow (especially with big polygons), mainly added for completeness sake. + * May be slow (especially with big polygons), added for completeness sake. + * + * Internally sets up a Pixelmask and uses that for collision check. */ private function collidePixelmask(pixelmask:Pixelmask):Boolean { @@ -172,12 +173,6 @@ package net.flashpunk.masks data.draw(FP.sprite); - Draw.enqueueCall(function ():void - { - FP.buffer.copyPixels(_fakePixelmask.data, _fakePixelmask.data.rect, new Point(50, 70)); - Draw.rectPlus(50, 70, data.width, data.height, 0xFF0000, .5, false); - }); - _fakePixelmask.data = data; return pixelmask.collide(_fakePixelmask); @@ -228,7 +223,7 @@ package net.flashpunk.masks { if (parent != null) { - //update entity bounds + // update entity bounds parent.originX = -_x + radius; parent.originY = -_y + radius; parent.height = parent.width = radius + radius; @@ -241,7 +236,7 @@ package net.flashpunk.masks // Hitbox information. protected var _radius:int; - protected var _squaredRadius:int; //Set automatically through the setter for radius + protected var _squaredRadius:int; // set automatically through the setter for radius private var _fakePixelmask:Pixelmask; // used for Pixelmask collision } } \ No newline at end of file diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index d6a2812..5ca35ab 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -11,7 +11,7 @@ package net.flashpunk.masks /** - * Uses polygon edges to check for collisions. + * Uses polygonal structure to check for collisions. */ public class Polygon extends Hitbox { @@ -22,11 +22,12 @@ package net.flashpunk.masks /** * Constructor. - * @param points an array of coordinates that define the polygon (must have at least 3) + * @param points a vector of coordinates that define the polygon (must have at least 3) * @param origin origin point of the polygon */ public function Polygon(points:Vector., origin:Point = null) { + if (points.length < 3) throw "The polygon needs at least 3 sides"; _points = points; _fakeEntity = new Entity(); _fakeTileHitbox = new Hitbox(); @@ -46,7 +47,7 @@ package net.flashpunk.masks } /** - * Checks for collisions with a Entity. + * Checks for collisions with an Entity. */ private function collideMask(other:Mask):Boolean { @@ -54,25 +55,27 @@ package net.flashpunk.masks offsetX:Number = parent.x - other.parent.x, offsetY:Number = parent.y - other.parent.y; - project(vertical, firstProj);//Project on the horizontal axis of the hitbox - other.project(vertical, secondProj); + // project on the vertical axis of the hitbox/mask + project(verticalAxis, firstProj); + other.project(verticalAxis, secondProj); firstProj.min += offsetY; firstProj.max += offsetY; - // if firstProj overlaps secondProj + // if firstProj not overlaps secondProj if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) { return false; } - - project(horizontal, firstProj);//Project on the vertical axis of the hitbox - other.project(horizontal, secondProj); + + // project on the horizontal axis of the hitbox/mask + project(horizontalAxis, firstProj); + other.project(horizontalAxis, secondProj); firstProj.min += offsetX; firstProj.max += offsetX; - // if firstProj overlaps secondProj + // if firstProj not overlaps secondProj if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) { return false; @@ -80,6 +83,8 @@ package net.flashpunk.masks var a:Point; + // project hitbox/mask on polygon axes + // for a collision to be present all projections must overlap for (var i:int = 0; i < _axes.length; i++) { a = _axes[i]; @@ -90,7 +95,7 @@ package net.flashpunk.masks firstProj.min += offset; firstProj.max += offset; - // if firstProj overlaps secondProj + // if firstProj not overlaps secondProj if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) { return false; @@ -100,7 +105,7 @@ package net.flashpunk.masks } /** - * Checks for collisions with a hitbox. + * Checks for collisions with a Hitbox. */ private function collideHitbox(hitbox:Hitbox):Boolean { @@ -108,25 +113,27 @@ package net.flashpunk.masks offsetX:Number = parent.x - hitbox.parent.x, offsetY:Number = parent.y - hitbox.parent.y; - project(vertical, firstProj);//Project on the horizontal axis of the hitbox - hitbox.project(vertical, secondProj); + // project on the vertical axis of the hitbox + project(verticalAxis, firstProj); + hitbox.project(verticalAxis, secondProj); firstProj.min += offsetY; firstProj.max += offsetY; - // if firstProj overlaps secondProj + // if firstProj not overlaps secondProj if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) { return false; } - project(horizontal, firstProj);//Project on the vertical axis of the hitbox - hitbox.project(horizontal, secondProj); + // project on the horizontal axis of the hitbox + project(horizontalAxis, firstProj); + hitbox.project(horizontalAxis, secondProj); firstProj.min += offsetX; firstProj.max += offsetX; - // if firstProj overlaps secondProj + // if firstProj not overlaps secondProj if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) { return false; @@ -134,6 +141,8 @@ package net.flashpunk.masks var a:Point; + // project hitbox on polygon axes + // for a collision to be present all projections must overlap for (var i:int = 0; i < _axes.length; i++) { a = _axes[i]; @@ -144,7 +153,7 @@ package net.flashpunk.masks firstProj.min += offset; firstProj.max += offset; - // if firstProj overlaps secondProj + // if firstProj not overlaps secondProj if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) { return false; @@ -154,19 +163,21 @@ package net.flashpunk.masks } /** - * Checks for collisions along the edges of the polygon. - * May be slow, mainly added for completeness sake. + * Checks for collisions with a Grid. + * May be slow, added for completeness sake. + * + * Internally sets up an Hitbox out of each solid Grid tile and uses that for collision check. */ private function collideGrid(grid:Grid):Boolean { var tileW:uint = grid.tileWidth; var tileH:uint = grid.tileHeight; var solidTile:Boolean; - + _fakeEntity.width = tileW; _fakeEntity.height = tileH; - _fakeEntity.originX = grid.parent.originX; - _fakeEntity.originY = grid.parent.originY; + _fakeEntity.originX = grid.parent.originX + grid._x; + _fakeEntity.originY = grid.parent.originY + grid._y; _fakeTileHitbox._width = tileW; _fakeTileHitbox._height = tileH; @@ -186,7 +197,9 @@ package net.flashpunk.masks /** * Checks for collision with a Pixelmask. - * May be slow (especially with big polygons), mainly added for completeness sake. + * May be slow (especially with big polygons), added for completeness sake. + * + * Internally sets up a Pixelmask using the polygon representation and uses that for collision check. */ private function collidePixelmask(pixelmask:Pixelmask):Boolean { @@ -212,21 +225,15 @@ package net.flashpunk.masks var offsetY:Number = _y + parent.originY * 2; graphics.moveTo(points[_points.length - 1].x + offsetX, _points[_points.length - 1].y + offsetY); - for (var ii:int = 0; ii < _points.length; ii++) + for (var i:int = 0; i < _points.length; i++) { - graphics.lineTo(_points[ii].x + offsetX, _points[ii].y + offsetY); + graphics.lineTo(_points[i].x + offsetX, _points[i].y + offsetY); } graphics.endFill(); data.draw(FP.sprite); - Draw.enqueueCall(function ():void - { - FP.buffer.copyPixels(_fakePixelmask.data, _fakePixelmask.data.rect, new Point(50, 70)); - Draw.rectPlus(50, 70, data.width, data.height, 0xFF0000, .5, false); - }); - _fakePixelmask.data = data; return pixelmask.collide(_fakePixelmask); @@ -246,7 +253,7 @@ package net.flashpunk.masks // check if circle center is inside the polygon - for (i = 0, j = nPoints - 1; i < _points.length; j = i, i++) { + for (i = 0, j = nPoints - 1; i < nPoints; j = i, i++) { p1 = _points[i]; p2 = _points[j]; @@ -269,7 +276,7 @@ package net.flashpunk.masks var closestX:Number; var closestY:Number; - for (i = 0, j = nPoints - 1; i < _points.length; j = i, i++) { + for (i = 0, j = nPoints - 1; i < nPoints; j = i, i++) { p1 = _points[i]; p2 = _points[j]; @@ -308,36 +315,40 @@ package net.flashpunk.masks var offsetY:Number = parent.y - other.parent.y; var a:Point; + // project other on this polygon axes + // for a collision to be present all projections must overlap for (var i:int = 0; i < _axes.length; i++) { a = _axes[i]; project(a, firstProj); other.project(a, secondProj); - //Shift the first info with the offset + // shift the first info with the offset var offset:Number = offsetX * a.x + offsetY * a.y; firstProj.min += offset; firstProj.max += offset; - // if firstProj overlaps secondProj + // if firstProj not overlaps secondProj if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) { return false; } } + // project this polygon on other polygon axes + // for a collision to be present all projections must overlap for (var j:int = 0; j < other._axes.length; j++) { a = other._axes[j]; project(a, firstProj); other.project(a, secondProj); - //Shift the first info with the offset + // shift the first info with the offset offset = offsetX * a.x + offsetY * a.y; firstProj.min += offset; firstProj.max += offset; - // if firstProj overlaps secondProj + // if firstProj not overlaps secondProj if (firstProj.min > secondProj.max || firstProj.max < secondProj.min) { return false; @@ -346,7 +357,7 @@ package net.flashpunk.masks return true; } - /** @private Projects polygon points on axis and returns min and max values in projection object. */ + /** @private Projects this polygon points on axis and returns min and max values in projection object. */ override public function project(axis:Point, projection:Object):void { var p:Point = _points[0]; @@ -386,9 +397,9 @@ package net.flashpunk.masks graphics.lineStyle(1, 0xFFFFFF, 0.25); graphics.moveTo((points[_points.length - 1].x + offsetX) * sx , (_points[_points.length - 1].y + offsetY) * sy); - for (var ii:int = 0; ii < _points.length; ii++) + for (var i:int = 0; i < _points.length; i++) { - graphics.lineTo((_points[ii].x + offsetX) * sx, (_points[ii].y + offsetY) * sy); + graphics.lineTo((_points[i].x + offsetX) * sx, (_points[i].y + offsetY) * sy); } graphics.endFill(); @@ -396,7 +407,7 @@ package net.flashpunk.masks } /** - * Angle in degress that the polygon is rotated. + * Rotation angle (in degress) of the polygon (rotates around origin point). */ public function get angle():Number { return _angle; } public function set angle(value:Number):void @@ -408,7 +419,9 @@ package net.flashpunk.masks /** * The points representing the polygon. - * If you need to set a point yourself instead of passing in a new Array you need to call update() to makes sure the axes update as well. + * + * If you need to set a point yourself instead of passing in a new Array you need to call update() + * to makes sure the axes update as well. */ public function get points():Vector. { return _points; } public function set points(value:Vector.):void @@ -422,20 +435,20 @@ package net.flashpunk.masks /** Updates the parent's bounds for this mask. */ override public function update():void { - project(horizontal, firstProj); //width + project(horizontalAxis, firstProj); // width _x = Math.ceil(firstProj.min); _width = Math.ceil(firstProj.max - firstProj.min); - project(vertical, secondProj); //height + project(verticalAxis, secondProj); // height _y = Math.ceil(secondProj.min); _height = Math.ceil(secondProj.max - secondProj.min); if (parent != null) { - //update entity bounds + // update entity bounds parent.width = _width; parent.height = _height; - //Since the collision infos haven't changed we can use them to calculate hitbox placement + // since the collision infos haven't changed we can use them to calculate hitbox placement parent.originX = int((_width - firstProj.max - firstProj.min)/2); parent.originY = int((_height - secondProj.max - secondProj.min )/2); } @@ -445,7 +458,7 @@ package net.flashpunk.masks } /** - * Creates a regular polygon. + * Creates a regular polygon (edges of same length). * @param sides The number of sides in the polygon * @param radius The distance that the corners are at * @param angle How much the polygon is rotated @@ -454,22 +467,23 @@ package net.flashpunk.masks public static function createPolygon(sides:int = 3, radius:Number = 100, angle:Number = 0):Polygon { if (sides < 3) throw "The polygon needs at least 3 sides"; - // create a return polygon - // figure out the angles required + + // figure out the angle required for each step var rotationAngle:Number = (Math.PI * 2) / sides; // loop through and generate each point var points:Vector. = new Vector.(); - for (var ii:int = 0; ii < sides; ii++) + for (var i:int = 0; i < sides; i++) { - var tempAngle:Number = ii * rotationAngle; + var tempAngle:Number = i * rotationAngle; var p:Point = new Point(); p.x = Math.cos(tempAngle) * radius; p.y = Math.sin(tempAngle) * radius; points.push(p); } - // return the point + + // return the polygon var poly:Polygon = new Polygon(points); poly.angle = angle; return poly; @@ -477,7 +491,7 @@ package net.flashpunk.masks /** * Creates a polygon from an array were even numbers are x and odd are y - * @param points Array containing the polygon's points. + * @param points Vector containing the polygon's points. * * @return The polygon */ @@ -485,10 +499,10 @@ package net.flashpunk.masks { var p:Vector. = new Vector.(); - var ii:int = 0; - while (ii < points.length) + var i:int = 0; + while (i < points.length) { - p.push(new Point(points[ii++], points[ii++])); + p.push(new Point(points[i++], points[i++])); } return new Polygon(p); } @@ -529,50 +543,41 @@ package net.flashpunk.masks private function generateAxes():void { _axes = new Vector.(); - var store:Number; - var numberOfPoints:int = _points.length - 1; + var temp:Number; + var nPoints:int = _points.length - 1; var edge:Point; + var i:int, j:int; - for (var i:int = 0; i < numberOfPoints; i++) - { + for (i = 0, j = nPoints - 1; i < nPoints; j = i, i++) { edge = new Point(); - edge.x = _points[i].x - _points[i + 1].x; - edge.y = _points[i].y - _points[i + 1].y; + edge.x = _points[i].x - _points[j].x; + edge.y = _points[i].y - _points[j].y; - //Get the axis which is perpendicular to the edge - store = edge.y; + // get the axis which is perpendicular to the edge + temp = edge.y; edge.y = -edge.x; - edge.x = store; + edge.x = temp; edge.normalize(1); _axes.push(edge); } - edge = new Point(); - //Add the last edge - edge.x = _points[numberOfPoints].x - _points[0].x; - edge.y = _points[numberOfPoints].y - _points[0].y; - store = edge.y; - edge.y = -edge.x; - edge.x = store; - edge.normalize(1); - - _axes.push(edge); } private function removeDuplicateAxes():void { - for (var ii:int = 0; ii < _axes.length; ii++ ) + for (var i:int = 0; i < _axes.length; i++ ) { - for (var jj:int = 0; jj < _axes.length; jj++ ) + for (var j:int = 0; j < _axes.length; j++ ) { - if (ii == jj || Math.max(ii, jj) >= _axes.length) continue; + if (i == j || Math.max(i, j) >= _axes.length) continue; + // if the first vector is equal or similar to the second vector, // remove it from the list. (for example, [1, 1] and [-1, -1] - // share the same relative path) - if ((_axes[ii].x == _axes[jj].x && _axes[ii].y == _axes[jj].y) - || ( -_axes[ii].x == _axes[jj].x && -_axes[ii].y == _axes[jj].y))//First axis inverted + // represent the same axis) + if ((_axes[i].x == _axes[j].x && _axes[i].y == _axes[j].y) + || ( -_axes[i].x == _axes[j].x && -_axes[i].y == _axes[j].y)) // first axis inverted { - _axes.splice(jj, 1); + _axes.splice(j, 1); } } } @@ -599,7 +604,7 @@ package net.flashpunk.masks private static var firstProj:* = { min: 0.0, max:0.0 }; private static var secondProj:* = { min: 0.0, max:0.0 }; - public static var vertical:Point = new Point(0, 1); - public static var horizontal:Point = new Point(1, 0); + public static const verticalAxis:Point = new Point(0, 1); + public static const horizontalAxis:Point = new Point(1, 0); } } \ No newline at end of file diff --git a/net/flashpunk/utils/Draw.as b/net/flashpunk/utils/Draw.as index 8d4d08c..c095d68 100644 --- a/net/flashpunk/utils/Draw.as +++ b/net/flashpunk/utils/Draw.as @@ -444,349 +444,10 @@ textGfx.render(_target, FP.zero, _camera); } - /** - * Draws a tiny rectangle centered at x, y. - * @param x The point's x. - * @param y The point's y. - * @param color Color of the rectangle. - * @param alpha Alpha of the rectangle. - * @param size Size of the rectangle. - */ - public static function dot(x:Number, y:Number, color:uint = 0xFFFFFF, alpha:Number = 1, size:Number = 3):void - { - x -= _camera.x; - y -= _camera.y; - - var halfSize:Number = size / 2; - Draw.rectPlus(x - halfSize + _camera.x, y - halfSize + _camera.y, size, size, color, alpha, false); - } - - /** - * Draws a smooth, antialiased line with an arrow head at the ending point. - * @param x1 Starting x position. - * @param y1 Starting y position. - * @param x2 Ending x position. - * @param y2 Ending y position. - * @param color Color of the line. - * @param alpha Alpha of the line. - */ - public static function arrow(x1:Number, y1:Number, x2:Number, y2:Number, color:uint = 0xFFFFFF, alpha:Number = 1):void - { - x1 -= _camera.x; - y1 -= _camera.y; - x2 -= _camera.x; - y2 -= _camera.y; - - // temporarily set camera to zero, otherwise it will be reapplied in called functions - var _savedCamera:Point = _camera; - _camera = FP.zero; - - var lineAngleRad:Number = FP.angle(x1, y1, x2, y2) * FP.RAD; - var dx:Number = x2 - x1; - var dy:Number = y2 - y1; - var len:Number = Math.sqrt(dx * dx + dy * dy); - if (len == 0) return; - - var arrowStartX:Number = (len-5) * Math.cos(lineAngleRad); - var arrowStartY:Number = (len-5) * Math.sin(lineAngleRad); - FP.point.x = -dy; - FP.point.y = dx; - FP.point.normalize(1); - - Draw.linePlus(x1, y1, x2, y2, color, alpha); - Draw.linePlus(x1 + arrowStartX + FP.point.x * 3, y1 + arrowStartY + FP.point.y * 3, x2, y2, color, alpha); - Draw.linePlus(x1 + arrowStartX - FP.point.x * 3, y1 + arrowStartY - FP.point.y * 3, x2, y2, color, alpha); - - // restore camera - _camera = _savedCamera; - } - - /** - * Draws a smooth, antialiased line with optional arrow heads at the start and end point. - * @param x1 Starting x position. - * @param y1 Starting y position. - * @param x2 Ending x position. - * @param y2 Ending y position. - * @param color Color of the line. - * @param alpha Alpha of the line. - * @param thick Thickness of the line. - * @param arrowAngle Angle (in degrees) between the line and the arm of the arrow heads (defaults to 30). - * @param arrowLength Pixel length of each arm of the arrow heads. - * @param arrowAtStart Whether or not to draw and arrow head over the starting point. - * @param arrowAtEnd Whether or not to draw and arrow head over the ending point. - */ - public static function arrowPlus(x1:Number, y1:Number, x2:Number, y2:Number, color:uint = 0xFFFFFF, alpha:Number = 1, thick:Number = 1, arrowAngle:Number=30, arrowLength:Number=6, arrowAtStart:Boolean = false, arrowAtEnd:Boolean = true):void - { - x1 -= _camera.x; - y1 -= _camera.y; - x2 -= _camera.x; - y2 -= _camera.y; - - // temporarily set camera to zero, otherwise it will be reapplied in called functions - var _savedCamera:Point = _camera; - _camera = FP.zero; - - if (color > 0xFFFFFF) color = 0xFFFFFF & color; - _graphics.clear(); - - _graphics.lineStyle(thick, color, alpha, false, LineScaleMode.NORMAL, null, JointStyle.MITER); - - linePlus(x1, y1, x2, y2, color, alpha, thick); - - var arrowAngleRad:Number = arrowAngle * FP.RAD; - var dir:Point = FP.point; - var normal:Point = FP.point2; - - dir.x = x2 - x1; - dir.y = y2 - y1; - normal.x = -dir.y; - normal.y = dir.x; - dir.normalize(1); - normal.normalize(1); - - var orthoLen:Number = arrowLength * Math.sin(arrowAngleRad); - var paralLen:Number = arrowLength * Math.cos(arrowAngleRad); - - if (arrowAtStart) { - linePlus(x1 + paralLen * dir.x + orthoLen * normal.x, y1 + paralLen * dir.y + orthoLen * normal.y, x1, y1, color, alpha, thick); - linePlus(x1 + paralLen * dir.x - orthoLen * normal.x, y1 + paralLen * dir.y - orthoLen * normal.y, x1, y1, color, alpha, thick); - } - - if (arrowAtEnd) { - linePlus(x2 - paralLen * dir.x + orthoLen * normal.x, y2 - paralLen * dir.y + orthoLen * normal.y, x2, y2, color, alpha, thick); - linePlus(x2 - paralLen * dir.x - orthoLen * normal.x, y2 - paralLen * dir.y - orthoLen * normal.y, x2, y2, color, alpha, thick); - } - - // restore camera - _camera = _savedCamera; - } - - /** - * Draws a circular arc (using lines) with an optional arrow head at the end point. - * @param centerX Center x of the arc. - * @param centerY Center y of the arc. - * @param radius Radius of the arc. - * @param startAngle Starting angle (in degrees) of the arc. - * @param spanAngle Angular span (in degrees) of the arc. - * @param color Color of the arc. - * @param alpha Alpha of the arc. - * @param drawArrow Whether or not to draw an arrow head over the ending point. - */ - public static function arc(centerX:Number, centerY:Number, radius:Number, startAngle:Number, spanAngle:Number, color:uint = 0xFFFFFF, alpha:Number = 1, drawArrow:Boolean = false):void - { - centerX -= _camera.x; - centerY -= _camera.y; - - // temporarily set camera to zero, otherwise it will be reapplied in called functions - var _savedCamera:Point = _camera; - _camera = FP.zero; - - var startAngleRad:Number = startAngle * FP.RAD; - var spanAngleRad:Number; - - // adjust angles if |span| > 360 - if (Math.abs(spanAngle) > 360) { - startAngleRad += (spanAngle % 360) * FP.RAD; - spanAngleRad = -FP.sign(spanAngle) * Math.PI * 2; - } else { - spanAngleRad = spanAngle * FP.RAD; - } - - var steps:int = Math.abs(spanAngleRad) * 10; - steps = steps > 0 ? steps : 1; - var angleStep:Number = spanAngleRad / steps; - - var x1:Number = centerX + Math.cos(startAngleRad) * radius; - var y1:Number = centerY + Math.sin(startAngleRad) * radius; - var x2:Number; - var y2:Number; - - for (var i:int = 0; i < steps; i++) { - var angle:Number = startAngleRad + (i + 1) * angleStep; - x2 = centerX + Math.cos(angle) * radius; - y2 = centerY + Math.sin(angle) * radius; - if (i == (steps - 1) && drawArrow) - arrow(x1, y1, x2, y2, color, alpha); - else - Draw.linePlus(x1, y1, x2, y2, color, alpha); - x1 = x2; - y1 = y2; - } - - // restore camera - _camera = _savedCamera; - } - - /** - * Draws a circular arc (using bezier curves) with an optional arrow head on the end point and other optional values. - * @param centerX Center x of the arc. - * @param centerY Center y of the arc. - * @param radius Radius of the arc. - * @param startAngle Starting angle (in degrees) of the arc. - * @param spanAngle Angular span (in degrees) of the arc. - * @param color Color of the arc. - * @param alpha Alpha of the arc. - * @param fill If the arc should be filled with the color (true) or just an outline (false). - * @param thick Thickness of the outline (only applicable when fill = false). - * @param drawArrow Whether or not to draw an arrow head over the ending point. - */ - public static function arcPlus(centerX:Number, centerY:Number, radius:Number, startAngle:Number, spanAngle:Number, color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1, drawArrow:Boolean = false):void - { - centerX -= _camera.x; - centerY -= _camera.y; - - // temporarily set camera to zero, otherwise it will be reapplied in called functions - var _savedCamera:Point = _camera; - _camera = FP.zero; - - if (color > 0xFFFFFF) color = 0xFFFFFF & color; - _graphics.clear(); - - var startAngleRad:Number = startAngle * FP.RAD; - var spanAngleRad:Number; - - // adjust angles if |span| > 360 - if (Math.abs(spanAngle) > 360) { - startAngleRad += (spanAngle % 360) * FP.RAD; - spanAngleRad = -FP.sign(spanAngle) * Math.PI * 2; - } else { - spanAngleRad = spanAngle * FP.RAD; - } - - var steps:int = Math.floor(Math.abs(spanAngleRad / (Math.PI / 4))) + 1; - var angleStep:Number = spanAngleRad / (2 * steps); - var controlRadius:Number = radius / Math.cos(angleStep); - - var startX:Number = centerX + Math.cos(startAngleRad) * radius; - var startY:Number = centerY + Math.sin(startAngleRad) * radius; - - if (fill) { - _graphics.beginFill(color, alpha); - _graphics.moveTo(centerX, centerY); - _graphics.lineTo(startX, startY); - } else { - _graphics.lineStyle(thick, color, alpha, false, LineScaleMode.NORMAL, null, JointStyle.MITER); - _graphics.moveTo(startX, startY); - } - - var endAngleRad:Number = 0; - var controlPoint:Point = FP.point; - var anchorPoint:Point = FP.point2; - - for (var i:int = 0; i < steps; i++) - { - endAngleRad = startAngleRad + angleStep; - startAngleRad = endAngleRad + angleStep; - - controlPoint.x = centerX + Math.cos(endAngleRad) * controlRadius; - controlPoint.y = centerY + Math.sin(endAngleRad) * controlRadius; - - anchorPoint.x = centerX + Math.cos(startAngleRad) * radius; - anchorPoint.y = centerY + Math.sin(startAngleRad) * radius; - - _graphics.curveTo(controlPoint.x, controlPoint.y, anchorPoint.x, anchorPoint.y); - } - - if (fill) _graphics.lineTo(centerX, centerY); - - FP.matrix.identity(); - FP.matrix.translate(-_camera.x, -_camera.y); - _target.draw(FP.sprite, FP.matrix, null, blend); - - if (drawArrow) { - FP.point.x = anchorPoint.x - centerX; - FP.point.y = anchorPoint.y - centerY; - FP.point.normalize(1); - Draw.arrowPlus(anchorPoint.x + FP.sign(angleStep) * FP.point.y, anchorPoint.y - FP.sign(angleStep) * FP.point.x, anchorPoint.x, anchorPoint.y, color, alpha, thick); - } - - // restore camera - _camera = _savedCamera; - } - - /** - * Draws a rotated rectangle (with optional pivot point). - * @param x X position of the rectangle. - * @param y Y position of the rectangle. - * @param width Width of the rectangle. - * @param height Height of the rectangle. - * @param color Color of the rectangle. - * @param alpha Alpha of the rectangle. - * @param fill If the rectangle should be filled with the color (true) or just an outline (false). - * @param thick How thick the outline should be (only applicable when fill = false). - * @param radius Round rectangle corners by this amount. - * @param angle Rotation of the rectangle (in degrees). - * @param pivotX X position around which the rotation should be performed (defaults to 0). - * @param pivotY Y position around which the rotation should be performed (defaults to 0). - */ - public static function rotatedRect(x:Number, y:Number, width:Number, height:Number, color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1, radius:Number = 0, angle:Number=0, pivotX:Number=0, pivotY:Number=0):void - { - x -= _camera.x; - y -= _camera.y; - - if (color > 0xFFFFFF) color = 0xFFFFFF & color; - _graphics.clear(); - - if (fill) { - _graphics.beginFill(color, alpha); - } else { - _graphics.lineStyle(thick, color, alpha, false, LineScaleMode.NORMAL, null, JointStyle.MITER); - } - - if (radius <= 0) { - _graphics.drawRect(0, 0, width, height); - } else { - _graphics.drawRoundRect(0, 0, width, height, radius); - } - - var angleRad:Number = angle * FP.RAD; - FP.matrix.identity(); - FP.matrix.translate(-pivotX, -pivotY); - FP.matrix.rotate(angleRad); - FP.matrix.tx += x; - FP.matrix.ty += y; - - _target.draw(FP.sprite, FP.matrix, null, blend); - } - - /** - * Enqueues a call to a function, to be executed at the end of the next render step. - * Useful to debug draw directly from the update step, rather than having to override the render method. - * - * Ex.: - * Draw.enqueueCall(function():Void { - * Draw.line(player.x, player.y, enemy.x, enemy.y); - * }); - * - * @param method The function to be enqueued. - */ - public static function enqueueCall(method:Function):void - { - if (method != null) - _callQueue.push(method); - else - throw new Error("[method] must be a non-null Function."); - } - - /** - * Executes all the functions enqueued with Draw.enqueueCall(), and clears the queue. (called from World.render()). - */ - public static function renderCallQueue():void - { - if (_callQueue.length <= 0) return; - - var len:int = _callQueue.length; - for (var i:int = 0; i < len; i++) { - _callQueue[i](); - } - _callQueue.length = 0; - } - // Drawing information. /** @private */ private static var _target:BitmapData; /** @private */ private static var _camera:Point; /** @private */ private static var _graphics:Graphics = FP.sprite.graphics; /** @private */ private static var _rect:Rectangle = FP.rect; - /** @private */ private static var _callQueue:Vector. = new Vector.(); } } From cdbfebe1b658afc4b55a46088fe00d6c39ecbdc8 Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Tue, 3 Dec 2013 19:13:50 +0100 Subject: [PATCH 07/15] fixed regression with Polygon generateAxes --- net/flashpunk/masks/Circle.as | 1 - net/flashpunk/masks/Polygon.as | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/net/flashpunk/masks/Circle.as b/net/flashpunk/masks/Circle.as index 16528d7..d3c497d 100644 --- a/net/flashpunk/masks/Circle.as +++ b/net/flashpunk/masks/Circle.as @@ -8,7 +8,6 @@ package net.flashpunk.masks import net.flashpunk.masks.Grid; import flash.display.Graphics; import flash.geom.Point; - import net.flashpunk.utils.Draw; /** * Uses circular area to determine collision. diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index 5ca35ab..bfe3d88 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -7,7 +7,6 @@ package net.flashpunk.masks import net.flashpunk.Entity; import net.flashpunk.FP; import net.flashpunk.Mask; - import net.flashpunk.utils.Draw; /** @@ -311,6 +310,7 @@ package net.flashpunk.masks */ private function collidePolygon(other:Polygon):Boolean { + var offset:Number; var offsetX:Number = parent.x - other.parent.x; var offsetY:Number = parent.y - other.parent.y; var a:Point; @@ -324,7 +324,7 @@ package net.flashpunk.masks other.project(a, secondProj); // shift the first info with the offset - var offset:Number = offsetX * a.x + offsetY * a.y; + offset = offsetX * a.x + offsetY * a.y; firstProj.min += offset; firstProj.max += offset; @@ -544,7 +544,7 @@ package net.flashpunk.masks { _axes = new Vector.(); var temp:Number; - var nPoints:int = _points.length - 1; + var nPoints:int = _points.length; var edge:Point; var i:int, j:int; From 696febf216e66b7e7208e768dacd790b24e921aa Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Tue, 3 Dec 2013 20:33:19 +0100 Subject: [PATCH 08/15] origin fix for Circle vs Pixelmask collision --- net/flashpunk/masks/Circle.as | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/flashpunk/masks/Circle.as b/net/flashpunk/masks/Circle.as index d3c497d..4ffbbce 100644 --- a/net/flashpunk/masks/Circle.as +++ b/net/flashpunk/masks/Circle.as @@ -166,7 +166,7 @@ package net.flashpunk.masks graphics.beginFill(0xFFFFFF, 1); graphics.lineStyle(1, 0xFFFFFF, 1); - graphics.drawCircle(_x - _radius, _y - _radius, _radius); + graphics.drawCircle(_x + parent.originX, _y + parent.originY, _radius); graphics.endFill(); From c8a4b27f915280df4493355140b262902ea2de01 Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Sun, 23 Mar 2014 21:58:53 +0100 Subject: [PATCH 09/15] Made all collisions between different masks seemingly work (Polygon probably needs some more love), changed console update() position in Engine (now shouldn't lag behind), updated project() func, need to work out Image->Polygon sync --- net/flashpunk/Engine.as | 6 +- net/flashpunk/Mask.as | 2 +- net/flashpunk/graphics/Image.as | 123 +++++++++++++++++++++++++++---- net/flashpunk/masks/Circle.as | 8 +- net/flashpunk/masks/Grid.as | 4 +- net/flashpunk/masks/Hitbox.as | 29 +++++++- net/flashpunk/masks/Pixelmask.as | 4 +- net/flashpunk/masks/Polygon.as | 92 +++++++++++++---------- 8 files changed, 202 insertions(+), 66 deletions(-) diff --git a/net/flashpunk/Engine.as b/net/flashpunk/Engine.as index 1d6c900..7be8b1a 100644 --- a/net/flashpunk/Engine.as +++ b/net/flashpunk/Engine.as @@ -204,12 +204,12 @@ FP.elapsed *= FP.rate; _last = _time; - // update console - if (FP._console) FP._console.update(); - // update loop if (!paused) update(); + // update console + if (FP._console) FP._console.update(); + // update input Input.update(); diff --git a/net/flashpunk/Mask.as b/net/flashpunk/Mask.as index 895989c..dfd59ec 100644 --- a/net/flashpunk/Mask.as +++ b/net/flashpunk/Mask.as @@ -46,7 +46,7 @@ } /** @private Collide against an Entity. */ - private function collideMask(other:Mask):Boolean + protected function collideMask(other:Mask):Boolean { return parent.x - parent.originX + parent.width > other.parent.x - other.parent.originX && parent.y - parent.originY + parent.height > other.parent.y - other.parent.originY diff --git a/net/flashpunk/graphics/Image.as b/net/flashpunk/graphics/Image.as index 67eab97..45b34d1 100644 --- a/net/flashpunk/graphics/Image.as +++ b/net/flashpunk/graphics/Image.as @@ -2,6 +2,7 @@ package net.flashpunk.graphics { import flash.display.*; import flash.geom.*; + import net.flashpunk.masks.Polygon; import net.flashpunk.*; @@ -137,17 +138,38 @@ package net.flashpunk.graphics * @param width Width of the rectangle. * @param height Height of the rectangle. * @param color Color of the rectangle. + * @param alpha Alpha of the rectangle. + * @param fill If the rectangle should be filled with the color (true) or just an outline (false). + * @param thick How thick the outline should be (only applicable when fill = false). + * @param radius Round rectangle corners by this amount. * @return A new Image object. */ - public static function createRect(width:uint, height:uint, color:uint = 0xFFFFFF, alpha:Number = 1):Image + public static function createRect(width:uint, height:uint, color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1, radius:Number = 0):Image { - var source:BitmapData = new BitmapData(width, height, true, 0xFFFFFFFF); + var graphics:Graphics = FP.sprite.graphics; - var image:Image = new Image(source); + if (color > 0xFFFFFF) color = 0xFFFFFF & color; + graphics.clear(); - image.color = color; - image.alpha = alpha; + var thickOffset:Number = 0; + if (fill) { + graphics.beginFill(color, alpha); + } else { + thickOffset = thick * .5; + graphics.lineStyle(thick, color, alpha, false, LineScaleMode.NORMAL, null, JointStyle.MITER); + } + + if (radius <= 0) { + graphics.drawRect(0 + thickOffset, 0 + thickOffset, width - thickOffset * 2, height - thickOffset * 2); + } else { + graphics.drawRoundRect(0 + thickOffset, 0 + thickOffset, width - thickOffset * 2, height - thickOffset * 2, radius); + } + graphics.endFill(); + + var data:BitmapData = new BitmapData(width, height, true, 0); + data.draw(FP.sprite); + var image:Image = new Image(data); return image; } @@ -156,21 +178,27 @@ package net.flashpunk.graphics * @param radius Radius of the circle. * @param color Color of the circle. * @param alpha Alpha of the circle. + * @param fill If the circle should be filled with the color (true) or just an outline (false). + * @param thick How thick the outline should be (only applicable when fill = false). * @return A new Image object. */ - public static function createCircle(radius:uint, color:uint = 0xFFFFFF, alpha:Number = 1):Image + public static function createCircle(radius:uint, color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1):Image { - FP.sprite.graphics.clear(); - FP.sprite.graphics.beginFill(0xFFFFFF); - FP.sprite.graphics.drawCircle(radius, radius, radius); + var graphics:Graphics = FP.sprite.graphics; + + graphics.clear(); + if (fill) { + graphics.beginFill(color & 0xFFFFFF, alpha); + graphics.drawCircle(radius, radius, radius); + graphics.endFill(); + } else { + graphics.lineStyle(thick, color & 0xFFFFFF, alpha); + graphics.drawCircle(radius, radius, radius - thick * .5); + } var data:BitmapData = new BitmapData(radius * 2, radius * 2, true, 0); data.draw(FP.sprite); var image:Image = new Image(data); - - image.color = color; - image.alpha = alpha; - return image; } @@ -227,6 +255,63 @@ package net.flashpunk.graphics return new Image(bitmap); } + /** + * Creates a new polygon Image from an array of points. + * @param points Array containing the polygon's points. + * @param color Color of the polygon. + * @param alpha Alpha of the polygon. + * @param fill If the polygon should be filled with the color (true) or just an outline (false). + * @param thick How thick the outline should be (only applicable when fill = false). + * @return A new Image object. + */ + public static function createPolygonFromPoints(points:Vector., color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1):Image + { + var graphics:Graphics = FP.sprite.graphics; + var minX:Number, maxX:Number; + var minY:Number, maxY:Number; + var p:Point; + + minX = minY = Number.POSITIVE_INFINITY; + maxX = maxY = Number.NEGATIVE_INFINITY; + + // find polygon bounds + for (var i:int = 0; i < points.length; i++) { + p = points[i]; + if (p.x < minX) minX = p.x; + if (p.x > maxX) maxX = p.x; + if (p.y < minY) minY = p.y; + if (p.y > maxY) maxY = p.y; + } + var w:int = Math.ceil(maxX - minX); + var h:int = Math.ceil(maxY - minY); + + if (color > 0xFFFFFF) color = 0xFFFFFF & color; + graphics.clear(); + + if (fill) { + graphics.beginFill(color, alpha); + } else { + graphics.lineStyle(thick, color, alpha, false, LineScaleMode.NORMAL, null, JointStyle.MITER); + } + + graphics.moveTo(points[points.length - 1].x, points[points.length - 1].y); + for (var j:int = 0; j < points.length; j++) { + p = points[j]; + graphics.lineTo(p.x, p.y); + } + graphics.endFill(); + + var matrix:Matrix = FP.matrix; + matrix.identity(); + matrix.translate(-minX, -minY); + + var data:BitmapData = new BitmapData(w, h, true, 0); + data.draw(FP.sprite, matrix); + + var image:Image = new Image(data); + return image; + } + /** * Updates the image buffer. */ @@ -331,6 +416,18 @@ package net.flashpunk.graphics updateBuffer(); } + /** + * Sync the image with the specified polygon mask. + */ + public function syncWithPolygon(poly:Polygon):void + { + originX = poly.originX; + originY = poly.originY; + angle = poly.angle; + x = poly.x + poly.originX; + y = poly.y + poly.originY; + } + /** * If you want to draw the Image horizontally flipped. This is * faster than setting scaleX to -1 if your image isn't transformed. diff --git a/net/flashpunk/masks/Circle.as b/net/flashpunk/masks/Circle.as index 4ffbbce..5cdbec9 100644 --- a/net/flashpunk/masks/Circle.as +++ b/net/flashpunk/masks/Circle.as @@ -35,7 +35,7 @@ package net.flashpunk.masks } /** @private Collides against an Entity. */ - private function collideMask(other:Mask):Boolean + override protected function collideMask(other:Mask):Boolean { var _otherHalfWidth:Number = other.parent.width * 0.5; var _otherHalfHeight:Number = other.parent.height * 0.5; @@ -58,7 +58,7 @@ package net.flashpunk.masks } /** @private Collides against a Hitbox. */ - private function collideHitbox(other:Hitbox):Boolean + override protected function collideHitbox(other:Hitbox):Boolean { var _otherHalfWidth:Number = other._width * 0.5; var _otherHalfHeight:Number = other._height * 0.5; @@ -144,7 +144,7 @@ package net.flashpunk.masks * * Internally sets up a Pixelmask and uses that for collision check. */ - private function collidePixelmask(pixelmask:Pixelmask):Boolean + private function collidePixelmask(other:Pixelmask):Boolean { var data:BitmapData = _fakePixelmask._data; @@ -174,7 +174,7 @@ package net.flashpunk.masks _fakePixelmask.data = data; - return pixelmask.collide(_fakePixelmask); + return other.collide(_fakePixelmask); } /** @private Collides against a Circle. */ diff --git a/net/flashpunk/masks/Grid.as b/net/flashpunk/masks/Grid.as index f6457dc..e54d86a 100644 --- a/net/flashpunk/masks/Grid.as +++ b/net/flashpunk/masks/Grid.as @@ -196,7 +196,7 @@ public function get data():BitmapData { return _data; } /** @private Collides against an Entity. */ - private function collideMask(other:Mask):Boolean + override protected function collideMask(other:Mask):Boolean { _rect.x = other.parent.x - other.parent.originX - parent.x + parent.originX; _rect.y = other.parent.y - other.parent.originY - parent.y + parent.originY; @@ -210,7 +210,7 @@ } /** @private Collides against a Hitbox. */ - private function collideHitbox(other:Hitbox):Boolean + override protected function collideHitbox(other:Hitbox):Boolean { _rect.x = other.parent.x + other._x - parent.x - _x; _rect.y = other.parent.y + other._y - parent.y - _y; diff --git a/net/flashpunk/masks/Hitbox.as b/net/flashpunk/masks/Hitbox.as index 9180f2c..0098472 100644 --- a/net/flashpunk/masks/Hitbox.as +++ b/net/flashpunk/masks/Hitbox.as @@ -28,7 +28,7 @@ } /** @private Collides against an Entity. */ - private function collideMask(other:Mask):Boolean + override protected function collideMask(other:Mask):Boolean { return parent.x + _x + _width > other.parent.x - other.parent.originX && parent.y + _y + _height > other.parent.y - other.parent.originY @@ -37,7 +37,7 @@ } /** @private Collides against a Hitbox. */ - private function collideHitbox(other:Hitbox):Boolean + protected function collideHitbox(other:Hitbox):Boolean { return parent.x + _x + _width > other.parent.x + other._x && parent.y + _y + _height > other.parent.y + other._y @@ -114,7 +114,30 @@ /** @private */ override public function project(axis:Point, projection:Object):void { - super.project(axis, projection); + var px:Number = _x, + py:Number = _y, + cur:Number, + max:Number = Number.NEGATIVE_INFINITY, + min:Number = Number.POSITIVE_INFINITY; + + cur = px * axis.x + py * axis.y; + if (cur < min) min = cur; + if (cur > max) max = cur; + + cur = (px + _width) * axis.x + py * axis.y; + if (cur < min) min = cur; + if (cur > max) max = cur; + + cur = px * axis.x + (py + _height) * axis.y; + if (cur < min) min = cur; + if (cur > max) max = cur; + + cur = (px + _width) * axis.x + (py + _height) * axis.y; + if (cur < min) min = cur; + if (cur > max) max = cur; + + projection.min = min; + projection.max = max; } // Hitbox information. diff --git a/net/flashpunk/masks/Pixelmask.as b/net/flashpunk/masks/Pixelmask.as index 4c572a9..de59e7a 100644 --- a/net/flashpunk/masks/Pixelmask.as +++ b/net/flashpunk/masks/Pixelmask.as @@ -40,7 +40,7 @@ } /** @private Collide against an Entity. */ - private function collideMask(other:Mask):Boolean + override protected function collideMask(other:Mask):Boolean { _point.x = parent.x + _x; _point.y = parent.y + _y; @@ -52,7 +52,7 @@ } /** @private Collide against a Hitbox. */ - private function collideHitbox(other:Hitbox):Boolean + override protected function collideHitbox(other:Hitbox):Boolean { _point.x = parent.x + _x; _point.y = parent.y + _y; diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index bfe3d88..f08d049 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -14,17 +14,29 @@ package net.flashpunk.masks */ public class Polygon extends Hitbox { + + /** + * X coord to use for rotations. + * Defaults to top-left corner. + */ + public var originX:Number = 0; + /** - * The polygon rotates around this point when the angle is set. + * Y coord to use for rotations. + * Defaults to top-left corner. */ - public var origin:Point; + public var originY:Number = 0; + /** * Constructor. - * @param points a vector of coordinates that define the polygon (must have at least 3) - * @param origin origin point of the polygon + * @param points An array of coordinates that define the polygon (must have at least 3). + * @param x X offset of the polygon. + * @param y Y offset of the polygon. + * @param originX X pivot for rotations. + * @param originY Y pivot for rotations. */ - public function Polygon(points:Vector., origin:Point = null) + public function Polygon(points:Vector., x:int = 0, y:int = 0, originX:Number = 0, originY:Number = 0) { if (points.length < 3) throw "The polygon needs at least 3 sides"; _points = points; @@ -39,7 +51,11 @@ package net.flashpunk.masks _check[Circle] = collideCircle; _check[Polygon] = collidePolygon; - this.origin = origin != null ? origin : new Point(); + _x = x; + _y = y; + + this.originX = originX; + this.originY = originY; _angle = 0; updateAxes(); @@ -48,7 +64,7 @@ package net.flashpunk.masks /** * Checks for collisions with an Entity. */ - private function collideMask(other:Mask):Boolean + override protected function collideMask(other:Mask):Boolean { var offset:Number, offsetX:Number = parent.x - other.parent.x, @@ -106,15 +122,15 @@ package net.flashpunk.masks /** * Checks for collisions with a Hitbox. */ - private function collideHitbox(hitbox:Hitbox):Boolean + override protected function collideHitbox(other:Hitbox):Boolean { var offset:Number, - offsetX:Number = parent.x - hitbox.parent.x, - offsetY:Number = parent.y - hitbox.parent.y; + offsetX:Number = parent.x - other.parent.x, + offsetY:Number = parent.y - other.parent.y; // project on the vertical axis of the hitbox project(verticalAxis, firstProj); - hitbox.project(verticalAxis, secondProj); + other.project(verticalAxis, secondProj); firstProj.min += offsetY; firstProj.max += offsetY; @@ -127,7 +143,7 @@ package net.flashpunk.masks // project on the horizontal axis of the hitbox project(horizontalAxis, firstProj); - hitbox.project(horizontalAxis, secondProj); + other.project(horizontalAxis, secondProj); firstProj.min += offsetX; firstProj.max += offsetX; @@ -146,7 +162,7 @@ package net.flashpunk.masks { a = _axes[i]; project(a, firstProj); - hitbox.project(a, secondProj); + other.project(a, secondProj); offset = offsetX * a.x + offsetY * a.y; firstProj.min += offset; @@ -167,26 +183,26 @@ package net.flashpunk.masks * * Internally sets up an Hitbox out of each solid Grid tile and uses that for collision check. */ - private function collideGrid(grid:Grid):Boolean + protected function collideGrid(other:Grid):Boolean { - var tileW:uint = grid.tileWidth; - var tileH:uint = grid.tileHeight; + var tileW:uint = other.tileWidth; + var tileH:uint = other.tileHeight; var solidTile:Boolean; _fakeEntity.width = tileW; _fakeEntity.height = tileH; - _fakeEntity.originX = grid.parent.originX + grid._x; - _fakeEntity.originY = grid.parent.originY + grid._y; + _fakeEntity.originX = other.parent.originX + other._x; + _fakeEntity.originY = other.parent.originY + other._y; _fakeTileHitbox._width = tileW; _fakeTileHitbox._height = tileH; _fakeTileHitbox.parent = _fakeEntity; - for (var r:int = 0; r < grid.rows; r++ ) { - for (var c:int = 0; c < grid.columns; c++) { - _fakeEntity.x = grid.parent.x + grid._x + c * tileW; - _fakeEntity.y = grid.parent.y + grid._y + r * tileH; - solidTile = grid.getTile(c, r); + for (var r:int = 0; r < other.rows; r++ ) { + for (var c:int = 0; c < other.columns; c++) { + _fakeEntity.x = other.parent.x + other._x + c * tileW; + _fakeEntity.y = other.parent.y + other._y + r * tileH; + solidTile = other.getTile(c, r); if (solidTile && collideHitbox(_fakeTileHitbox)) return true; } @@ -200,7 +216,7 @@ package net.flashpunk.masks * * Internally sets up a Pixelmask using the polygon representation and uses that for collision check. */ - private function collidePixelmask(pixelmask:Pixelmask):Boolean + protected function collidePixelmask(other:Pixelmask):Boolean { var data:BitmapData = _fakePixelmask._data; @@ -235,13 +251,13 @@ package net.flashpunk.masks _fakePixelmask.data = data; - return pixelmask.collide(_fakePixelmask); + return other.collide(_fakePixelmask); } /** * Checks for collision with a circle. */ - private function collideCircle(circle:Circle):Boolean + protected function collideCircle(other:Circle):Boolean { var edgesCrossed:int = 0; var p1:Point, p2:Point; @@ -256,10 +272,10 @@ package net.flashpunk.masks p1 = _points[i]; p2 = _points[j]; - var distFromCenter:Number = (p2.x - p1.x) * (circle._y + circle.parent.y - p1.y - offsetY) / (p2.y - p1.y) + p1.x + offsetX; + var distFromCenter:Number = (p2.x - p1.x) * (other._y + other.parent.y - p1.y - offsetY) / (p2.y - p1.y) + p1.x + offsetX; - if ((p1.y + offsetY > circle._y + circle.parent.y) != (p2.y + offsetY > circle._y + circle.parent.y) - && (circle._x + circle.parent.x < distFromCenter)) + if ((p1.y + offsetY > other._y + other.parent.y) != (p2.y + offsetY > other._y + other.parent.y) + && (other._x + other.parent.x < distFromCenter)) { edgesCrossed++; } @@ -268,9 +284,9 @@ package net.flashpunk.masks if (edgesCrossed & 1) return true; // check if minimum distance from circle center to each polygon side is less than radius - var radiusSqr:Number = circle.radius * circle.radius; - var cx:Number = circle._x + circle.parent.x; - var cy:Number = circle._y + circle.parent.y; + var radiusSqr:Number = other.radius * other.radius; + var cx:Number = other._x + other.parent.x; + var cy:Number = other._y + other.parent.y; var minDistanceSqr:Number = 0; var closestX:Number; var closestY:Number; @@ -308,7 +324,7 @@ package net.flashpunk.masks /** * Checks for collision with a polygon. */ - private function collidePolygon(other:Polygon):Boolean + protected function collidePolygon(other:Polygon):Boolean { var offset:Number; var offsetX:Number = parent.x - other.parent.x; @@ -464,7 +480,7 @@ package net.flashpunk.masks * @param angle How much the polygon is rotated * @return The polygon */ - public static function createPolygon(sides:int = 3, radius:Number = 100, angle:Number = 0):Polygon + public static function createRegular(sides:int = 3, radius:Number = 100, angle:Number = 0):Polygon { if (sides < 3) throw "The polygon needs at least 3 sides"; @@ -518,14 +534,14 @@ package net.flashpunk.masks for (var i:int = 0; i < _points.length; i++) { p = _points[i]; - var dx:Number = p.x - origin.x; - var dy:Number = p.y - origin.y; + var dx:Number = p.x - originX; + var dy:Number = p.y - originY; var pointAngle:Number = Math.atan2(dy, dx); var length:Number = Math.sqrt(dx * dx + dy * dy); - p.x = Math.cos(pointAngle + angleDelta) * length + origin.x; - p.y = Math.sin(pointAngle + angleDelta) * length + origin.y; + p.x = Math.cos(pointAngle + angleDelta) * length + originX; + p.y = Math.sin(pointAngle + angleDelta) * length + originY; } var a:Point; From 30f823bad31ffae4bce8eb5f36c730efc9e44155 Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Sun, 23 Mar 2014 23:25:53 +0100 Subject: [PATCH 10/15] Draw pivot for Polygon, fixed drawing for Hitboxes in Masklist --- net/flashpunk/masks/Hitbox.as | 13 +++++++++++++ net/flashpunk/masks/Masklist.as | 2 ++ net/flashpunk/masks/Polygon.as | 3 +++ 3 files changed, 18 insertions(+) diff --git a/net/flashpunk/masks/Hitbox.as b/net/flashpunk/masks/Hitbox.as index 0098472..428b53f 100644 --- a/net/flashpunk/masks/Hitbox.as +++ b/net/flashpunk/masks/Hitbox.as @@ -1,5 +1,6 @@ package net.flashpunk.masks { + import flash.display.Graphics; import flash.geom.Point; import net.flashpunk.*; @@ -111,6 +112,18 @@ } } + override public function renderDebug(g:Graphics):void + { + // draw only if we're part of a Masklist + if (!list || list.count <= 1) return; + + var sx:Number = FP.screen.scaleX * FP.screen.scale; + var sy:Number = FP.screen.scaleY * FP.screen.scale; + + g.lineStyle(1, 0xFFFFFF, 0.25); + g.drawRect((parent.x - FP.camera.x + x) * sx, (parent.y - FP.camera.y + y) * sy, width * sx, height * sy); + } + /** @private */ override public function project(axis:Point, projection:Object):void { diff --git a/net/flashpunk/masks/Masklist.as b/net/flashpunk/masks/Masklist.as index b536fdd..b962cd6 100644 --- a/net/flashpunk/masks/Masklist.as +++ b/net/flashpunk/masks/Masklist.as @@ -135,6 +135,8 @@ { // find bounds of the contained masks var t:int, l:int, r:int, b:int, h:Hitbox, i:int = _count; + l = t = int.MAX_VALUE; + r = b = int.MIN_VALUE; while (i --) { if ((h = _masks[i] as Hitbox)) diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index f08d049..c368b30 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -419,6 +419,9 @@ package net.flashpunk.masks } graphics.endFill(); + + // draw pivot + graphics.drawCircle((offsetX + originX) * sx, (offsetY + originY) * sy, 2); } } From dbf734e7ae91cf27232cf6f525f3597bbda93f37 Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Tue, 25 Mar 2014 00:06:17 +0100 Subject: [PATCH 11/15] Jaysis, bugs all the way down, damn offsets! Fixed Polygon collisions (esp. w/Pixelmask), improved Masklist (and its drawing in console), syncing between Image->Polygon seems to be a hard task - we'll see... --- net/flashpunk/graphics/Image.as | 8 +- net/flashpunk/masks/Circle.as | 1 + net/flashpunk/masks/Hitbox.as | 12 +-- net/flashpunk/masks/Masklist.as | 33 +++++--- net/flashpunk/masks/Polygon.as | 139 +++++++++++++++++++------------- 5 files changed, 113 insertions(+), 80 deletions(-) diff --git a/net/flashpunk/graphics/Image.as b/net/flashpunk/graphics/Image.as index 45b34d1..8e8780c 100644 --- a/net/flashpunk/graphics/Image.as +++ b/net/flashpunk/graphics/Image.as @@ -216,7 +216,7 @@ package net.flashpunk.graphics * @param toAlpha Alpha at end of gradient. * @return A new Image object. */ - public static function createGradient (width:uint, height:uint, fromX:Number, fromY:Number, toX:Number, toY:Number, fromColor:uint, toColor:uint, fromAlpha:Number = 1, toAlpha:Number = 1):Image + public static function createGradient(width:uint, height:uint, fromX:Number, fromY:Number, toX:Number, toY:Number, fromColor:uint, toColor:uint, fromAlpha:Number = 1, toAlpha:Number = 1):Image { var bitmap:BitmapData = new BitmapData(width, height, true, 0x0); @@ -264,7 +264,7 @@ package net.flashpunk.graphics * @param thick How thick the outline should be (only applicable when fill = false). * @return A new Image object. */ - public static function createPolygonFromPoints(points:Vector., color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1):Image + public static function createPolygon(points:Vector., color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1):Image { var graphics:Graphics = FP.sprite.graphics; var minX:Number, maxX:Number; @@ -424,8 +424,8 @@ package net.flashpunk.graphics originX = poly.originX; originY = poly.originY; angle = poly.angle; - x = poly.x + poly.originX; - y = poly.y + poly.originY; + x = poly.minX// + poly.originX; + y = poly.minY// + poly.originY; } /** diff --git a/net/flashpunk/masks/Circle.as b/net/flashpunk/masks/Circle.as index 5cdbec9..5f6a95c 100644 --- a/net/flashpunk/masks/Circle.as +++ b/net/flashpunk/masks/Circle.as @@ -197,6 +197,7 @@ package net.flashpunk.masks var sx:Number = FP.screen.scaleX * FP.screen.scale; var sy:Number = FP.screen.scaleY * FP.screen.scale; + graphics.lineStyle(1, 0xFFFFFF, 0.25); graphics.drawCircle((parent.x + _x - FP.camera.x) * sx, (parent.y + _y - FP.camera.y) * sy, radius * sx); } diff --git a/net/flashpunk/masks/Hitbox.as b/net/flashpunk/masks/Hitbox.as index 428b53f..a36c7bd 100644 --- a/net/flashpunk/masks/Hitbox.as +++ b/net/flashpunk/masks/Hitbox.as @@ -54,8 +54,7 @@ { if (_x == value) return; _x = value; - if (list) list.update(); - else if (parent) update(); + update(); } /** @@ -66,8 +65,7 @@ { if (_y == value) return; _y = value; - if (list) list.update(); - else if (parent) update(); + update(); } /** @@ -78,8 +76,7 @@ { if (_width == value) return; _width = value; - if (list) list.update(); - else if (parent) update(); + update(); } /** @@ -90,8 +87,7 @@ { if (_height == value) return; _height = value; - if (list) list.update(); - else if (parent) update(); + update(); } /** @public Updates the parent's bounds for this mask. */ diff --git a/net/flashpunk/masks/Masklist.as b/net/flashpunk/masks/Masklist.as index b962cd6..737e6a6 100644 --- a/net/flashpunk/masks/Masklist.as +++ b/net/flashpunk/masks/Masklist.as @@ -48,7 +48,7 @@ */ public function add(mask:Mask):Mask { - _masks[_count ++] = mask; + _masks[_count++] = mask; mask.list = this; mask.parent = parent; update(); @@ -70,7 +70,7 @@ { mask.list = null; mask.parent = null; - _count --; + _count--; update(); } else _temp[_temp.length] = m; @@ -90,12 +90,12 @@ _temp.length = 0; var i:int = _masks.length; index %= i; - while (i --) + while (i--) { if (i == index) { _masks[index].list = null; - _count --; + _count--; update(); } else _temp[_temp.length] = _masks[index]; @@ -134,17 +134,28 @@ override public function update():void { // find bounds of the contained masks - var t:int, l:int, r:int, b:int, h:Hitbox, i:int = _count; + var t:int, l:int, r:int, b:int, i:int = _count; l = t = int.MAX_VALUE; r = b = int.MIN_VALUE; - while (i --) + + var hitbox:Hitbox; + var poly:Polygon; + + while (i--) { - if ((h = _masks[i] as Hitbox)) + if ((poly = _masks[i] as Polygon)) + { + if (poly.minX < l) l = poly.minX; + if (poly.minY < t) t = poly.minY; + if (poly.maxX > r) r = poly.maxX; + if (poly.maxY > b) b = poly.maxY; + } + else if ((hitbox = _masks[i] as Hitbox)) { - if (h._x < l) l = h._x; - if (h._y < t) t = h._y; - if (h._x + h._width > r) r = h._x + h._width; - if (h._y + h._height > b) b = h._y + h._height; + if (hitbox._x < l) l = hitbox._x; + if (hitbox._y < t) t = hitbox._y; + if (hitbox._x + hitbox._width > r) r = hitbox._x + hitbox._width; + if (hitbox._y + hitbox._height > b) b = hitbox._y + hitbox._height; } } diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index c368b30..fb0dde4 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -15,19 +15,25 @@ package net.flashpunk.masks public class Polygon extends Hitbox { - /** - * X coord to use for rotations. - * Defaults to top-left corner. - */ + /** X coord to use for rotations. Defaults to top-left corner. */ public var originX:Number = 0; - /** - * Y coord to use for rotations. - * Defaults to top-left corner. - */ + /** Y coord to use for rotations. Defaults to top-left corner. */ public var originY:Number = 0; + /** Leftmost X coord of the polygon. */ + public function get minX():int { return _minX; } + + /** Rightmost X coord of the polygon. */ + public function get maxX():int { return _maxX; } + + /** Topmost Y coord of the polygon. */ + public function get minY():int { return _minY; } + + /** Bottommost Y coord of the polygon. */ + public function get maxY():int { return _maxY; } + /** * Constructor. * @param points An array of coordinates that define the polygon (must have at least 3). @@ -67,8 +73,8 @@ package net.flashpunk.masks override protected function collideMask(other:Mask):Boolean { var offset:Number, - offsetX:Number = parent.x - other.parent.x, - offsetY:Number = parent.y - other.parent.y; + offsetX:Number = parent.x + _x - other.parent.x, + offsetY:Number = parent.y + _y - other.parent.y; // project on the vertical axis of the hitbox/mask project(verticalAxis, firstProj); @@ -125,8 +131,8 @@ package net.flashpunk.masks override protected function collideHitbox(other:Hitbox):Boolean { var offset:Number, - offsetX:Number = parent.x - other.parent.x, - offsetY:Number = parent.y - other.parent.y; + offsetX:Number = parent.x + _x - other.parent.x, + offsetY:Number = parent.y + _y - other.parent.y; // project on the vertical axis of the hitbox project(verticalAxis, firstProj); @@ -191,6 +197,8 @@ package net.flashpunk.masks _fakeEntity.width = tileW; _fakeEntity.height = tileH; + _fakeEntity.x = parent.x; + _fakeEntity.y = parent.y; _fakeEntity.originX = other.parent.originX + other._x; _fakeEntity.originY = other.parent.originY + other._y; @@ -220,12 +228,19 @@ package net.flashpunk.masks { var data:BitmapData = _fakePixelmask._data; - _fakePixelmask._x = _x; - _fakePixelmask._y = _y; - _fakePixelmask.parent = parent; + _fakeEntity.width = parent.width; + _fakeEntity.height = parent.height; + _fakeEntity.x = parent.x - _x; + _fakeEntity.y = parent.y - _y; + _fakeEntity.originX = parent.originX; + _fakeEntity.originY = parent.originY; + + _fakePixelmask._x = _x - parent.originX; + _fakePixelmask._y = _y - parent.originY; + _fakePixelmask.parent = _fakeEntity; - if (data == null || (data.width < _width || data.height < _height)) { - data = new BitmapData(_width, height, true, 0); + if (data == null || (data.width < parent.width || data.height < parent.height)) { + data = new BitmapData(parent.width, parent.height, true, 0); } else { data.fillRect(data.rect, 0); } @@ -236,8 +251,8 @@ package net.flashpunk.masks graphics.beginFill(0xFFFFFF, 1); graphics.lineStyle(1, 0xFFFFFF, 1); - var offsetX:Number = _x + parent.originX * 2; - var offsetY:Number = _y + parent.originY * 2; + var offsetX:Number = _x + parent.originX; + var offsetY:Number = _y + parent.originY; graphics.moveTo(points[_points.length - 1].x + offsetX, _points[_points.length - 1].y + offsetY); for (var i:int = 0; i < _points.length; i++) @@ -263,8 +278,8 @@ package net.flashpunk.masks var p1:Point, p2:Point; var i:int, j:int; var nPoints:int = _points.length; - var offsetX:Number = parent.x + _x + parent.originX; - var offsetY:Number = parent.y + _y + parent.originY; + var offsetX:Number = parent.x + _x; + var offsetY:Number = parent.y + _y; // check if circle center is inside the polygon @@ -327,8 +342,8 @@ package net.flashpunk.masks protected function collidePolygon(other:Polygon):Boolean { var offset:Number; - var offsetX:Number = parent.x - other.parent.x; - var offsetY:Number = parent.y - other.parent.y; + var offsetX:Number = parent.x + _x - other.parent.x - other.x; + var offsetY:Number = parent.y + _y - other.parent.y - other.y; var a:Point; // project other on this polygon axes @@ -386,14 +401,8 @@ package net.flashpunk.masks p = _points[i]; var cur:Number = axis.x * p.x + axis.y * p.y; // dot product - if (cur < min) - { - min = cur; - } - else if (cur > max) - { - max = cur; - } + if (cur < min) min = cur; + else if (cur > max) max = cur; } projection.min = min; projection.max = max; @@ -403,8 +412,8 @@ package net.flashpunk.masks { if (parent != null) { - var offsetX:Number = parent.x - FP.camera.x, - offsetY:Number = parent.y - FP.camera.y; + var offsetX:Number = parent.x +_x - FP.camera.x, + offsetY:Number = parent.y +_y - FP.camera.y; var sx:Number = FP.screen.scaleX * FP.screen.scale; var sy:Number = FP.screen.scaleY * FP.screen.scale; @@ -421,7 +430,7 @@ package net.flashpunk.masks graphics.endFill(); // draw pivot - graphics.drawCircle((offsetX + originX) * sx, (offsetY + originY) * sy, 2); + graphics.drawCircle((offsetX + originX) * sx + .5, (offsetY + originY) * sy + .5, 2); } } @@ -432,7 +441,7 @@ package net.flashpunk.masks public function set angle(value:Number):void { if (value == _angle) return; - rotate(_angle - value); + rotate(value - _angle); if (list != null || parent != null) update(); } @@ -455,25 +464,29 @@ package net.flashpunk.masks override public function update():void { project(horizontalAxis, firstProj); // width - _x = Math.ceil(firstProj.min); - _width = Math.ceil(firstProj.max - firstProj.min); + var projX:int = Math.round(firstProj.min); + _width = Math.round(firstProj.max - firstProj.min); project(verticalAxis, secondProj); // height - _y = Math.ceil(secondProj.min); - _height = Math.ceil(secondProj.max - secondProj.min); + var projY:int = Math.round(secondProj.min); + _height = Math.round(secondProj.max - secondProj.min); - if (parent != null) + _minX = _x + projX; + _minY = _y + projY; + _maxX = Math.round(minX + _width); + _maxY = Math.round(minY + _height); + + if (list != null) + { + // update parent list + list.update(); + } + else if (parent != null) { - // update entity bounds + parent.originX = -_x - projX; + parent.originY = -_y - projY; parent.width = _width; parent.height = _height; - - // since the collision infos haven't changed we can use them to calculate hitbox placement - parent.originX = int((_width - firstProj.max - firstProj.min)/2); - parent.originY = int((_height - secondProj.max - secondProj.min )/2); } - - // update parent list - if (list != null) list.update(); } /** @@ -485,7 +498,7 @@ package net.flashpunk.masks */ public static function createRegular(sides:int = 3, radius:Number = 100, angle:Number = 0):Polygon { - if (sides < 3) throw "The polygon needs at least 3 sides"; + if (sides < 3) throw "The polygon needs at least 3 sides."; // figure out the angle required for each step var rotationAngle:Number = (Math.PI * 2) / sides; @@ -493,13 +506,14 @@ package net.flashpunk.masks // loop through and generate each point var points:Vector. = new Vector.(); + var startAngle:Number = 0; for (var i:int = 0; i < sides; i++) { - var tempAngle:Number = i * rotationAngle; var p:Point = new Point(); - p.x = Math.cos(tempAngle) * radius; - p.y = Math.sin(tempAngle) * radius; + p.x = Math.cos(startAngle) * radius; + p.y = Math.sin(startAngle) * radius; points.push(p); + startAngle += rotationAngle; } // return the polygon @@ -514,7 +528,7 @@ package net.flashpunk.masks * * @return The polygon */ - public static function createFromVector(points:Vector.):Polygon + public static function createFromFlatVector(points:Vector.):Polygon { var p:Vector. = new Vector.(); @@ -584,6 +598,7 @@ package net.flashpunk.masks private function removeDuplicateAxes():void { + _indicesToRemove.length = 0; for (var i:int = 0; i < _axes.length; i++ ) { for (var j:int = 0; j < _axes.length; j++ ) @@ -593,13 +608,15 @@ package net.flashpunk.masks // if the first vector is equal or similar to the second vector, // remove it from the list. (for example, [1, 1] and [-1, -1] // represent the same axis) - if ((_axes[i].x == _axes[j].x && _axes[i].y == _axes[j].y) - || ( -_axes[i].x == _axes[j].x && -_axes[i].y == _axes[j].y)) // first axis inverted + if ((_axes[i].x == _axes[j].x && _axes[i].y == _axes[j].y) || + ( -_axes[i].x == _axes[j].x && -_axes[i].y == _axes[j].y)) // first axis inverted { - _axes.splice(j, 1); + _indicesToRemove.push(j); } } } + // remove duplicate axes + for (var k:int = 0; k < _indicesToRemove.length; k++) _axes.splice(_indicesToRemove[k], 1); } private function updateAxes():void @@ -615,10 +632,18 @@ package net.flashpunk.masks private var _axes:Vector.; private var _projection:* = { min: 0.0, max:0.0 }; - private var _fakeEntity:Entity; // used for Grid collision + // Polygon bounding box. + private var _minX:int = 0; + private var _minY:int = 0; + private var _maxX:int = 0; + private var _maxY:int = 0; + + private var _fakeEntity:Entity; // used for Grid and Pixelmask collision private var _fakeTileHitbox:Hitbox; // used for Grid collision private var _fakePixelmask:Pixelmask; // used for Pixelmask collision + private var _indicesToRemove:Vector. = new Vector.(); // used in removeDuplicateAxes() + private static var _axis:Point = new Point(); private static var firstProj:* = { min: 0.0, max:0.0 }; private static var secondProj:* = { min: 0.0, max:0.0 }; From 475c4c4fb11a3554261ba871f64f16c5ddd53693 Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Sat, 29 Mar 2014 21:25:26 +0100 Subject: [PATCH 12/15] Image.createPolygon now syncs with the passed polygon --- net/flashpunk/graphics/Image.as | 29 +++++++++++++++-------------- net/flashpunk/masks/Polygon.as | 1 + 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/net/flashpunk/graphics/Image.as b/net/flashpunk/graphics/Image.as index 8e8780c..b4a19ee 100644 --- a/net/flashpunk/graphics/Image.as +++ b/net/flashpunk/graphics/Image.as @@ -257,19 +257,23 @@ package net.flashpunk.graphics /** * Creates a new polygon Image from an array of points. - * @param points Array containing the polygon's points. + * @param polygon A Polygon object to create the Image from. * @param color Color of the polygon. * @param alpha Alpha of the polygon. * @param fill If the polygon should be filled with the color (true) or just an outline (false). * @param thick How thick the outline should be (only applicable when fill = false). * @return A new Image object. */ - public static function createPolygon(points:Vector., color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1):Image + public static function createPolygon(polygon:Polygon, color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1):Image { var graphics:Graphics = FP.sprite.graphics; + var points:Vector. = polygon.points; var minX:Number, maxX:Number; var minY:Number, maxY:Number; var p:Point; + var originalAngle:Number = polygon.angle; + + polygon.angle = 0; // set temporarily angle to 0 so we can sync with image angle later minX = minY = Number.POSITIVE_INFINITY; maxX = maxY = Number.NEGATIVE_INFINITY; @@ -309,6 +313,15 @@ package net.flashpunk.graphics data.draw(FP.sprite, matrix); var image:Image = new Image(data); + + // adjust position, origin and angle + image.x = polygon.x + polygon.originX; + image.y = polygon.y + polygon.originY; + image.originX = image.x - polygon.minX; + image.originY = image.y - polygon.minY; + image.angle = originalAngle; + polygon.angle = originalAngle; + return image; } @@ -416,18 +429,6 @@ package net.flashpunk.graphics updateBuffer(); } - /** - * Sync the image with the specified polygon mask. - */ - public function syncWithPolygon(poly:Polygon):void - { - originX = poly.originX; - originY = poly.originY; - angle = poly.angle; - x = poly.minX// + poly.originX; - y = poly.minY// + poly.originY; - } - /** * If you want to draw the Image horizontally flipped. This is * faster than setting scaleX to -1 if your image isn't transformed. diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index fb0dde4..831ed7a 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -36,6 +36,7 @@ package net.flashpunk.masks /** * Constructor. + * * @param points An array of coordinates that define the polygon (must have at least 3). * @param x X offset of the polygon. * @param y Y offset of the polygon. From b9fd2a267e0e6b5044651d325c1ea4a7c919baea Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Fri, 2 May 2014 16:49:46 +0200 Subject: [PATCH 13/15] Polygon: renamed origin to pivot, removed x/y from constructor, added angle to createFromFlatVector --- net/flashpunk/graphics/Image.as | 6 ++--- net/flashpunk/masks/Polygon.as | 40 ++++++++++++++++----------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/net/flashpunk/graphics/Image.as b/net/flashpunk/graphics/Image.as index b4a19ee..be22e26 100644 --- a/net/flashpunk/graphics/Image.as +++ b/net/flashpunk/graphics/Image.as @@ -256,7 +256,7 @@ package net.flashpunk.graphics } /** - * Creates a new polygon Image from an array of points. + * Creates a new polygon Image from a Polygon. * @param polygon A Polygon object to create the Image from. * @param color Color of the polygon. * @param alpha Alpha of the polygon. @@ -315,8 +315,8 @@ package net.flashpunk.graphics var image:Image = new Image(data); // adjust position, origin and angle - image.x = polygon.x + polygon.originX; - image.y = polygon.y + polygon.originY; + image.x = polygon.x + polygon.pivotX; + image.y = polygon.y + polygon.pivotY; image.originX = image.x - polygon.minX; image.originY = image.y - polygon.minY; image.angle = originalAngle; diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index 831ed7a..2cfc2b3 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -16,10 +16,10 @@ package net.flashpunk.masks { /** X coord to use for rotations. Defaults to top-left corner. */ - public var originX:Number = 0; + public var pivotX:Number = 0; /** Y coord to use for rotations. Defaults to top-left corner. */ - public var originY:Number = 0; + public var pivotY:Number = 0; /** Leftmost X coord of the polygon. */ public function get minX():int { return _minX; } @@ -38,12 +38,10 @@ package net.flashpunk.masks * Constructor. * * @param points An array of coordinates that define the polygon (must have at least 3). - * @param x X offset of the polygon. - * @param y Y offset of the polygon. - * @param originX X pivot for rotations. - * @param originY Y pivot for rotations. + * @param pivotX X pivot for rotations. + * @param pivotY Y pivot for rotations. */ - public function Polygon(points:Vector., x:int = 0, y:int = 0, originX:Number = 0, originY:Number = 0) + public function Polygon(points:Vector., pivotX:Number = 0, pivotY:Number = 0) { if (points.length < 3) throw "The polygon needs at least 3 sides"; _points = points; @@ -58,11 +56,8 @@ package net.flashpunk.masks _check[Circle] = collideCircle; _check[Polygon] = collidePolygon; - _x = x; - _y = y; - - this.originX = originX; - this.originY = originY; + this.pivotX = pivotX; + this.pivotY = pivotY; _angle = 0; updateAxes(); @@ -431,12 +426,12 @@ package net.flashpunk.masks graphics.endFill(); // draw pivot - graphics.drawCircle((offsetX + originX) * sx + .5, (offsetY + originY) * sy + .5, 2); + graphics.drawCircle((offsetX + pivotX) * sx + .5, (offsetY + pivotY) * sy + .5, 2); } } /** - * Rotation angle (in degress) of the polygon (rotates around origin point). + * Rotation angle (in degress) of the polygon (rotates around pivot point). */ public function get angle():Number { return _angle; } public function set angle(value:Number):void @@ -497,7 +492,7 @@ package net.flashpunk.masks * @param angle How much the polygon is rotated * @return The polygon */ - public static function createRegular(sides:int = 3, radius:Number = 100, angle:Number = 0):Polygon + public static function createRegular(sides:int, radius:Number, angle:Number = 0):Polygon { if (sides < 3) throw "The polygon needs at least 3 sides."; @@ -526,10 +521,11 @@ package net.flashpunk.masks /** * Creates a polygon from an array were even numbers are x and odd are y * @param points Vector containing the polygon's points. + * @param angle How much the polygon is rotated * * @return The polygon */ - public static function createFromFlatVector(points:Vector.):Polygon + public static function createFromFlatVector(points:Vector., angle:Number = 0):Polygon { var p:Vector. = new Vector.(); @@ -538,7 +534,9 @@ package net.flashpunk.masks { p.push(new Point(points[i++], points[i++])); } - return new Polygon(p); + var poly:Polygon = new Polygon(p); + poly.angle = angle; + return poly; } private function rotate(angleDelta:Number):void @@ -552,14 +550,14 @@ package net.flashpunk.masks for (var i:int = 0; i < _points.length; i++) { p = _points[i]; - var dx:Number = p.x - originX; - var dy:Number = p.y - originY; + var dx:Number = p.x - pivotX; + var dy:Number = p.y - pivotY; var pointAngle:Number = Math.atan2(dy, dx); var length:Number = Math.sqrt(dx * dx + dy * dy); - p.x = Math.cos(pointAngle + angleDelta) * length + originX; - p.y = Math.sin(pointAngle + angleDelta) * length + originY; + p.x = Math.cos(pointAngle + angleDelta) * length + pivotX; + p.y = Math.sin(pointAngle + angleDelta) * length + pivotY; } var a:Point; From af4862ac45a8e0c442ad7c4ba9d06d4a35db629b Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Sat, 3 May 2014 17:39:07 +0200 Subject: [PATCH 14/15] Polygon: make calculations on transformedPoints instead of original ones, pivot getters/setters, specified to work with convex polygons Image.createPolygon() now takes an array of points --- net/flashpunk/graphics/Image.as | 42 ++------- net/flashpunk/masks/Polygon.as | 148 +++++++++++++++++++------------- 2 files changed, 97 insertions(+), 93 deletions(-) diff --git a/net/flashpunk/graphics/Image.as b/net/flashpunk/graphics/Image.as index be22e26..0ac363e 100644 --- a/net/flashpunk/graphics/Image.as +++ b/net/flashpunk/graphics/Image.as @@ -256,38 +256,28 @@ package net.flashpunk.graphics } /** - * Creates a new polygon Image from a Polygon. - * @param polygon A Polygon object to create the Image from. + * Creates a new polygon Image from an array of points. + * @param points An array of coordinates (must be positive) that define the polygon. * @param color Color of the polygon. * @param alpha Alpha of the polygon. * @param fill If the polygon should be filled with the color (true) or just an outline (false). * @param thick How thick the outline should be (only applicable when fill = false). * @return A new Image object. */ - public static function createPolygon(polygon:Polygon, color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1):Image + public static function createPolygon(points:Vector., color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:Number = 1):Image { var graphics:Graphics = FP.sprite.graphics; - var points:Vector. = polygon.points; - var minX:Number, maxX:Number; - var minY:Number, maxY:Number; var p:Point; - var originalAngle:Number = polygon.angle; - polygon.angle = 0; // set temporarily angle to 0 so we can sync with image angle later + var maxX:Number = Number.NEGATIVE_INFINITY; + var maxY:Number = Number.NEGATIVE_INFINITY; - minX = minY = Number.POSITIVE_INFINITY; - maxX = maxY = Number.NEGATIVE_INFINITY; - - // find polygon bounds + // find max x and y coords for (var i:int = 0; i < points.length; i++) { p = points[i]; - if (p.x < minX) minX = p.x; if (p.x > maxX) maxX = p.x; - if (p.y < minY) minY = p.y; if (p.y > maxY) maxY = p.y; } - var w:int = Math.ceil(maxX - minX); - var h:int = Math.ceil(maxY - minY); if (color > 0xFFFFFF) color = 0xFFFFFF & color; graphics.clear(); @@ -305,24 +295,10 @@ package net.flashpunk.graphics } graphics.endFill(); - var matrix:Matrix = FP.matrix; - matrix.identity(); - matrix.translate(-minX, -minY); - - var data:BitmapData = new BitmapData(w, h, true, 0); - data.draw(FP.sprite, matrix); - - var image:Image = new Image(data); - - // adjust position, origin and angle - image.x = polygon.x + polygon.pivotX; - image.y = polygon.y + polygon.pivotY; - image.originX = image.x - polygon.minX; - image.originY = image.y - polygon.minY; - image.angle = originalAngle; - polygon.angle = originalAngle; + var data:BitmapData = new BitmapData(maxX, maxY, true, 0); + data.draw(FP.sprite); - return image; + return new Image(data); } /** diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index 2cfc2b3..a577cca 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -10,34 +10,14 @@ package net.flashpunk.masks /** - * Uses polygonal structure to check for collisions. + * Uses polygon edges to check for collisions (only works with convex polygons). */ public class Polygon extends Hitbox { - - /** X coord to use for rotations. Defaults to top-left corner. */ - public var pivotX:Number = 0; - - /** Y coord to use for rotations. Defaults to top-left corner. */ - public var pivotY:Number = 0; - - /** Leftmost X coord of the polygon. */ - public function get minX():int { return _minX; } - - /** Rightmost X coord of the polygon. */ - public function get maxX():int { return _maxX; } - - /** Topmost Y coord of the polygon. */ - public function get minY():int { return _minY; } - - /** Bottommost Y coord of the polygon. */ - public function get maxY():int { return _maxY; } - - /** * Constructor. * - * @param points An array of coordinates that define the polygon (must have at least 3). + * @param points An array of coordinates that define the convex polygon (must have at least 3). * @param pivotX X pivot for rotations. * @param pivotY Y pivot for rotations. */ @@ -45,6 +25,9 @@ package net.flashpunk.masks { if (points.length < 3) throw "The polygon needs at least 3 sides"; _points = points; + _transformedPoints = new Vector.(); + for (var i:int = 0; i < points.length; i++) _transformedPoints[i] = points[i].clone(); + _fakeEntity = new Entity(); _fakeTileHitbox = new Hitbox(); _fakePixelmask = new Pixelmask(new BitmapData(1, 1)); @@ -250,10 +233,10 @@ package net.flashpunk.masks var offsetX:Number = _x + parent.originX; var offsetY:Number = _y + parent.originY; - graphics.moveTo(points[_points.length - 1].x + offsetX, _points[_points.length - 1].y + offsetY); - for (var i:int = 0; i < _points.length; i++) + graphics.moveTo(points[_transformedPoints.length - 1].x + offsetX, _transformedPoints[_transformedPoints.length - 1].y + offsetY); + for (var i:int = 0; i < _transformedPoints.length; i++) { - graphics.lineTo(_points[i].x + offsetX, _points[i].y + offsetY); + graphics.lineTo(_transformedPoints[i].x + offsetX, _transformedPoints[i].y + offsetY); } graphics.endFill(); @@ -273,15 +256,15 @@ package net.flashpunk.masks var edgesCrossed:int = 0; var p1:Point, p2:Point; var i:int, j:int; - var nPoints:int = _points.length; + var nPoints:int = _transformedPoints.length; var offsetX:Number = parent.x + _x; var offsetY:Number = parent.y + _y; // check if circle center is inside the polygon for (i = 0, j = nPoints - 1; i < nPoints; j = i, i++) { - p1 = _points[i]; - p2 = _points[j]; + p1 = _transformedPoints[i]; + p2 = _transformedPoints[j]; var distFromCenter:Number = (p2.x - p1.x) * (other._y + other.parent.y - p1.y - offsetY) / (p2.y - p1.y) + p1.x + offsetX; @@ -303,8 +286,8 @@ package net.flashpunk.masks var closestY:Number; for (i = 0, j = nPoints - 1; i < nPoints; j = i, i++) { - p1 = _points[i]; - p2 = _points[j]; + p1 = _transformedPoints[i]; + p2 = _transformedPoints[j]; var segmentLenSqr:Number = (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y); @@ -387,14 +370,14 @@ package net.flashpunk.masks /** @private Projects this polygon points on axis and returns min and max values in projection object. */ override public function project(axis:Point, projection:Object):void { - var p:Point = _points[0]; + var p:Point = _transformedPoints[0]; var min:Number = axis.x * p.x + axis.y * p.y, // dot product max:Number = min; - for (var i:int = 1; i < _points.length; i++) + for (var i:int = 1; i < _transformedPoints.length; i++) { - p = _points[i]; + p = _transformedPoints[i]; var cur:Number = axis.x * p.x + axis.y * p.y; // dot product if (cur < min) min = cur; @@ -417,10 +400,10 @@ package net.flashpunk.masks graphics.beginFill(0xFFFFFF, .15); graphics.lineStyle(1, 0xFFFFFF, 0.25); - graphics.moveTo((points[_points.length - 1].x + offsetX) * sx , (_points[_points.length - 1].y + offsetY) * sy); - for (var i:int = 0; i < _points.length; i++) + graphics.moveTo((points[_transformedPoints.length - 1].x + offsetX) * sx , (_transformedPoints[_transformedPoints.length - 1].y + offsetY) * sy); + for (var i:int = 0; i < _transformedPoints.length; i++) { - graphics.lineTo((_points[i].x + offsetX) * sx, (_points[i].y + offsetY) * sy); + graphics.lineTo((_transformedPoints[i].x + offsetX) * sx, (_transformedPoints[i].y + offsetY) * sy); } graphics.endFill(); @@ -431,31 +414,72 @@ package net.flashpunk.masks } /** - * Rotation angle (in degress) of the polygon (rotates around pivot point). + * Rotation angle (in degrees) of the polygon (rotates around pivot point). */ public function get angle():Number { return _angle; } public function set angle(value:Number):void { if (value == _angle) return; - rotate(value - _angle); + _angle = value; + transformPoints(); + + if (list != null || parent != null) update(); + } + + /** X coord to use for rotations. Defaults to top-left corner. */ + public function get pivotX():Number { return _pivotX; } + public function set pivotX(value:Number):void + { + if (_pivotX == value) return; + _pivotX = value; + transformPoints(); + + if (list != null || parent != null) update(); + } + + /** Y coord to use for rotations. Defaults to top-left corner. */ + public function get pivotY():Number { return _pivotY; } + public function set pivotY(value:Number):void + { + if (_pivotY == value) return; + _pivotY = value; + transformPoints(); + if (list != null || parent != null) update(); } + + /** Leftmost X coord of the polygon. */ + public function get minX():int { return _minX; } + + /** Rightmost X coord of the polygon. */ + public function get maxX():int { return _maxX; } + + /** Topmost Y coord of the polygon. */ + public function get minY():int { return _minY; } + + /** Bottommost Y coord of the polygon. */ + public function get maxY():int { return _maxY; } /** - * The points representing the polygon. + * The original points (non transformed/rotated) representing the polygon. * - * If you need to set a point yourself instead of passing in a new Array you need to call update() + * If you need to set a point yourself instead of passing in a new Vector. you need to call update() * to makes sure the axes update as well. */ - public function get points():Vector. { return _points; } + public function get points():Vector. { return _transformedPoints; } public function set points(value:Vector.):void { - if (_points == value) return; - _points = value; + if (_transformedPoints == value) return; + _transformedPoints = value; if (list != null || parent != null) updateAxes(); } + /** + * The transformed/rotated points representing the polygon. + */ + public function get transformedPoints():Vector. { return _transformedPoints; } + /** Updates the parent's bounds for this mask. */ override public function update():void { @@ -506,8 +530,8 @@ package net.flashpunk.masks for (var i:int = 0; i < sides; i++) { var p:Point = new Point(); - p.x = Math.cos(startAngle) * radius; - p.y = Math.sin(startAngle) * radius; + p.x = Math.cos(startAngle) * radius + radius; + p.y = Math.sin(startAngle) * radius + radius; points.push(p); startAngle += rotationAngle; } @@ -539,25 +563,24 @@ package net.flashpunk.masks return poly; } - private function rotate(angleDelta:Number):void + private function transformPoints():void { - _angle += angleDelta; - - angleDelta *= FP.RAD; - var p:Point; + var tp:Point; + var angleRad:Number = _angle * FP.RAD; for (var i:int = 0; i < _points.length; i++) { p = _points[i]; - var dx:Number = p.x - pivotX; - var dy:Number = p.y - pivotY; + tp = _transformedPoints[i]; + var dx:Number = p.x - _pivotX; + var dy:Number = p.y - _pivotY; var pointAngle:Number = Math.atan2(dy, dx); var length:Number = Math.sqrt(dx * dx + dy * dy); - p.x = Math.cos(pointAngle + angleDelta) * length + pivotX; - p.y = Math.sin(pointAngle + angleDelta) * length + pivotY; + tp.x = Math.cos(pointAngle + angleRad) * length + _pivotX; + tp.y = Math.sin(pointAngle + angleRad) * length + _pivotY; } var a:Point; @@ -567,8 +590,8 @@ package net.flashpunk.masks var axisAngle:Number = Math.atan2(a.y, a.x); - a.x = Math.cos(axisAngle + angleDelta); - a.y = Math.sin(axisAngle + angleDelta); + a.x = Math.cos(axisAngle + angleRad); + a.y = Math.sin(axisAngle + angleRad); } } @@ -576,14 +599,14 @@ package net.flashpunk.masks { _axes = new Vector.(); var temp:Number; - var nPoints:int = _points.length; + var nPoints:int = _transformedPoints.length; var edge:Point; var i:int, j:int; for (i = 0, j = nPoints - 1; i < nPoints; j = i, i++) { edge = new Point(); - edge.x = _points[i].x - _points[j].x; - edge.y = _points[i].y - _points[j].y; + edge.x = _transformedPoints[i].x - _transformedPoints[j].x; + edge.y = _transformedPoints[i].y - _transformedPoints[j].y; // get the axis which is perpendicular to the edge temp = edge.y; @@ -627,10 +650,15 @@ package net.flashpunk.masks // Hitbox information. private var _angle:Number; - private var _points:Vector.; + private var _points:Vector.; // original points (non transformed/rotated) as passed in the constructor + private var _transformedPoints:Vector.; // transformed/rotated points private var _axes:Vector.; private var _projection:* = { min: 0.0, max:0.0 }; + // Polygon pivot point. + private var _pivotX:Number = 0; + private var _pivotY:Number = 0; + // Polygon bounding box. private var _minX:int = 0; private var _minY:int = 0; From 5a9d8574077153a42a80ed5506f17a6c26af8f99 Mon Sep 17 00:00:00 2001 From: azrafe7 Date: Wed, 21 May 2014 21:20:20 +0200 Subject: [PATCH 15/15] Polygon: Fixing/optimizing axes generation/removal/rotation --- net/flashpunk/masks/Polygon.as | 42 +++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/net/flashpunk/masks/Polygon.as b/net/flashpunk/masks/Polygon.as index a577cca..acba9d8 100644 --- a/net/flashpunk/masks/Polygon.as +++ b/net/flashpunk/masks/Polygon.as @@ -42,6 +42,7 @@ package net.flashpunk.masks this.pivotX = pivotX; this.pivotY = pivotY; _angle = 0; + _lastAngle = 0; updateAxes(); } @@ -420,6 +421,7 @@ package net.flashpunk.masks public function set angle(value:Number):void { if (value == _angle) return; + _lastAngle = _angle; _angle = value; transformPoints(); @@ -568,6 +570,7 @@ package net.flashpunk.masks var p:Point; var tp:Point; var angleRad:Number = _angle * FP.RAD; + var lastAngleRad:Number = _lastAngle * FP.RAD; for (var i:int = 0; i < _points.length; i++) { @@ -590,8 +593,8 @@ package net.flashpunk.masks var axisAngle:Number = Math.atan2(a.y, a.x); - a.x = Math.cos(axisAngle + angleRad); - a.y = Math.sin(axisAngle + angleRad); + a.x = Math.cos(axisAngle + angleRad - lastAngleRad); + a.y = Math.sin(axisAngle + angleRad - lastAngleRad); } } @@ -620,25 +623,27 @@ package net.flashpunk.masks private function removeDuplicateAxes():void { - _indicesToRemove.length = 0; - for (var i:int = 0; i < _axes.length; i++ ) + var i:int = _axes.length - 1; + var j:int = i - 1; + while (i > 0) { - for (var j:int = 0; j < _axes.length; j++ ) + // if the first vector is equal or similar to the second vector, + // remove it from the list. (for example, [1, 1] and [-1, -1] + // represent the same axis) + if ((Math.abs(_axes[i].x - _axes[j].x) < EPSILON && Math.abs(_axes[i].y - _axes[j].y) < EPSILON) + || (Math.abs(_axes[j].x + _axes[i].x) < EPSILON && Math.abs(_axes[i].y + _axes[j].y) < EPSILON)) // first axis inverted { - if (i == j || Math.max(i, j) >= _axes.length) continue; - - // if the first vector is equal or similar to the second vector, - // remove it from the list. (for example, [1, 1] and [-1, -1] - // represent the same axis) - if ((_axes[i].x == _axes[j].x && _axes[i].y == _axes[j].y) || - ( -_axes[i].x == _axes[j].x && -_axes[i].y == _axes[j].y)) // first axis inverted - { - _indicesToRemove.push(j); - } + _axes.splice(i, 1); + i--; + } + + j--; + if (j < 0) + { + i--; + j = i - 1; } } - // remove duplicate axes - for (var k:int = 0; k < _indicesToRemove.length; k++) _axes.splice(_indicesToRemove[k], 1); } private function updateAxes():void @@ -650,6 +655,7 @@ package net.flashpunk.masks // Hitbox information. private var _angle:Number; + private var _lastAngle:Number; private var _points:Vector.; // original points (non transformed/rotated) as passed in the constructor private var _transformedPoints:Vector.; // transformed/rotated points private var _axes:Vector.; @@ -669,7 +675,7 @@ package net.flashpunk.masks private var _fakeTileHitbox:Hitbox; // used for Grid collision private var _fakePixelmask:Pixelmask; // used for Pixelmask collision - private var _indicesToRemove:Vector. = new Vector.(); // used in removeDuplicateAxes() + private static var EPSILON:Number = 0.000000001; // used for axes comparison in removeDuplicateAxes private static var _axis:Point = new Point(); private static var firstProj:* = { min: 0.0, max:0.0 };