forked from: Playing with GSkinner ProximityTest use case
forked from Playing with GSkinner ProximityTest use case (diff: 1)
ActionScript3 source code
/**
* Copyright WindsorFarmer ( http://wonderfl.net/user/WindsorFarmer )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/pG05
*/
// forked from jpauclair's Playing with GSkinner ProximityTest use case
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Rectangle;
import flash.utils.getTimer;
[SWF(frameRate="20", backgroundColor="#FFFFFF", width="550", height="550", pageTitle="Untitled")]
public class ProximityTest extends Sprite {
protected var proximityManager:ProximityManager;
protected var itemCount:uint=0;
protected var bases:Vector.<Shape>;
protected var items:Vector.<Ship>;
protected var canvas:Shape;
protected var resultVector:Vector.<Ship>;
private var mBackgroundData:BitmapData = null;
private var mBackground:Bitmap = null;
private var mCurrentFrame:int = 0;
public function ProximityTest() {
// we'll reuse this Vector for all of our getNeighbor calls:
resultVector = new Vector.<Ship>();
// offsetting the bounds a little bit to put the bases near the center of a grid position:
proximityManager = new ProximityManager(25,new Rectangle(-12,-12,570,570));
mBackgroundData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xFF000000);
mBackground = new Bitmap(mBackgroundData);
addChild(mBackground);
// create "link" canvas:
canvas = new Shape();
addChild(canvas);
// create our initial set of items:
items = new Vector.<Ship>();
var l:uint=5000;
for (var i:uint=0; i<l; i++) {
var item:Ship = new Ship();
resetItem(item);
item.x = Math.random()*550;
item.y = Math.random()*550;
items.push(item);
}
proximityManager.items = items;
// create bases:
bases = new Vector.<Shape>();
addBase(100,100);
addBase(450,450);
addBase(100,450);
addBase(450,100);
addEventListener(Event.ENTER_FRAME, OnEnterFrame);
stage.frameRate = 999;
}
protected function addBase(x:Number, y:Number):void {
var base:Shape = new Shape();
base.x = x;
base.y = y;
base.graphics.beginFill(0xFFFFFF,1);
base.graphics.lineStyle(2,0xFF0000,1);
base.graphics.drawCircle(0,0,7);
addChild(base);
bases.push(base);
}
protected function resetItem(item:Ship):void {
item.x = (Math.random()>0.5) ? 275 : 0;
item.y = (Math.random()>0.5) ? 275 : 0;
item.velX = (Math.random()*4+0.1)*(Math.random()>0.5?1:-1)*10;
item.velY = (Math.random()*4+0.1)*(Math.random()>0.5?1:-1)*10;
item.alpha = 1;
}
private function OnEnterFrame(e:Event) : void
{
Update();
mCurrentFrame++;
if (mCurrentFrame > 3500)
{
stage.frameRate = 10;
}
if (mCurrentFrame % 50 == 0)
{
RenderCanvas();
trace(mCurrentFrame);
}
}
private final function RenderCanvas() : void
{
mBackgroundData.fillRect(mBackgroundData.rect, 0xFFFFFFFF);
var l:int = items.length;
for (var i:int=0; i<l; i++) {
var item:Ship = items[i] as Ship;
mBackgroundData.setPixel32(item.x, item.y,(255*(item.alpha))<<24);
}
}
protected final function Update():void
{
var g:Graphics = canvas.graphics;
g.clear();
g.lineStyle(1, 0xFF0000);
var l:int = items.length;
for (var i:int=0; i<l; i++) {
var item:Ship = items[i] as Ship;
item.x = (item.x+item.velX+550)%550;
item.y = (item.y+item.velY+550)%550;
}
proximityManager.update();
l = bases.length;
for (i=0; i<l; i++) {
var base:Shape = bases[i];
resultVector.length = 0;
proximityManager.getNeighbors(base,1,resultVector);
var jl:int = resultVector.length;
for (var j:int=0; j<jl; j++) {
item = resultVector[j] as Ship;
item.alpha-=0.1;
if (true || item.alpha<=0) {
resetItem(item);
} else {
g.moveTo(base.x, base.y);
g.lineTo(item.x,item.y);
}
}
}
}
}
}
internal class Ship {
public var x:int = 0;
public var y:int = 0;
public var velX:Number;
public var velY:Number;
public var alpha:Number = 0;
}
import flash.display.DisplayObject;
import flash.geom.Rectangle;
import flash.utils.Dictionary;
/**
* <b>ProximityManager ©2009 Grant Skinner, gskinner.com. Visit www.gskinner.com/blog/ for documentation, updates and more free code. Licensed under the MIT license - see the source file header for more information.</b>
* <hr/>
* Uses grid based proximity to quickly return coarse neighbors of a display object in systems with large numbers of items. It has a linear growth pattern for processing time,
* versus an exponential growth pattern for simpler compare all to all approaches. For example, in a system with 5000 items, it would require nearly 12.5 million iterations to
* compare each item's distance directly. With this library it only requires 5000 iterations. While the cost per iteration is higher with grid based proximity, that cost is quickly
* made insignificant as you add more items.
* <br/><br/>
* This class also offers optional item list management methods. These are significant in that they support the removal and addition of items between updates. An item removed with removeItem() after update() was called
* will not be returned in subsequent getNeighbor calls.
**/
internal class ProximityManager {
// Public properties:
/**
* If true, items will be included in getNeighbor calls immediately when they are added.
* If false, they will not be included until the next time update() is called.
**/
public var addItemsImmediately:Boolean=false;
// Protected properties:
/** @private **/
protected var _items:Vector.<Ship> = new Vector.<Ship>();
/** @private **/
protected var grid:Vector.<Vector.<Ship>>;
/** @private **/
protected var deadItems:Dictionary = new Dictionary(true);
/** @private **/
protected var checkDead:Boolean=false;
/** @private **/
protected var liveItems:Dictionary = new Dictionary(true);
/** @private **/
protected var gridSize:Number;
/** @private **/
protected var bounds:Rectangle;
/** @private **/
protected var w:int;
/** @private **/
protected var h:int;
/** @private **/
protected var offX:Number=0;
/** @private **/
protected var offY:Number=0;
/** @private **/
protected var length:int;
/** @private **/
protected var lengths:Vector.<int>;
/** @private **/
protected var m:Number;
// Constructor:
/** Constructs a new ProximityManager instance **/
public function ProximityManager(gridSize:Number,bounds:Rectangle) {
this.gridSize = gridSize;
this.bounds = bounds;
init();
}
// Public getter / setters:
/**
* The list of items currently being tracked in the system. You can use this to quickly pass in a list of
* existing items, or retrieve the current list. It is recommended to use the addItem and removeItem methods
* for minor modifications to the list.
**/
public function get items():Vector.<Ship> {
return _items;
}
public function set items(value:Vector.<Ship>):void {
_items = value;
for (var o:Object in deadItems) { delete(deadItems[o]); }
var l:int = _items.length;
for (var i:int=0; i<l; i++) {
liveItems[_items[i]] = true;
}
}
// Public methods:
/**
* Adds an item to track. If addItemsImmediately is true, it will immediately
* be inserted into the active grid, if not, it will be inserted the next time update() is called.
* <b>Note that removing and adding an item within the same update with addItemsImmediately can cause problems with duplicate returns.</b>
**/
public function addItem(item:Ship):void {
if (!(liveItems[item] || deadItems[item])) {
_items.push(item);
}
liveItems[item] = true;
delete(deadItems[item]);
if (addItemsImmediately) {
var pos:int = ((item.x+offX)*m|0)*h+(item.y+offY)*m;
grid[pos][lengths[pos]++] = item;
}
}
/**
* Removes an item from the system. It will not be returned in any subsequent getNeighbors() calls.
**/
public function removeItem(item:Ship):void {
if (!liveItems[item]) { return; }
deadItems[item] = true;
delete(liveItems[item]);
checkDead = true;
}
/**
* Updates the positions of all items on the grid. Call this when items have moved, but not after *each* item moves.
* For example, if you have a number of sprites moving around on screen each frame, move them all, then call update() once per frame.
**/
public function update():void {
// clear grid:
lengths.length = 0;
lengths.length = length;
for (var i:int=0; i<length; ++i) {
grid[i].length = 0;
}
// populate grid:
var l:int = _items.length;
for (i=l; i-->0; ) {
var item:Ship = _items[i];
if (checkDead && deadItems[item]) {
_items.splice(i,1);
delete(deadItems[item]);
continue;
}
var pos:int = ((item.x+offX)*m|0)*h+(item.y+offY)*m;
grid[pos][lengths[pos]++] = item;
}
checkDead = false;
}
/**
* Returns the list of neighbors for the specified item. Neighbours are items in grid positions within radius positions away from the item.
* For example, a radius of 0 returns only items in the same position. A radius of 1 returns 9 positions (the center, + the 8 positions 1 position away).
* A radius of 2 returns 25 positions. It is generally recommended to only use a radius of 1, but there are occasional use cases that may benefit from using
* a radius of 2.
* <br/><br/>
* It is important to note that this is a coarse set of neighbors. Their distance from the target item varies depending on their location within their grid position.
* This is allows you to exclude items that are too far away, then use more accurate comparisions (like pythagoram distance calculations or hit tests) on the
* smaller set of items.
* <br/><br/>
* You can specify a resultVector to avoid the need for ProximityManager to instantiate a new Vector each time you call getNeighbors. Results will be appended
* to the end of the vector. You may want to clear the vector with myVector.length = 0 before reusing it.
**/
public function getNeighbors(item:DisplayObject,radius:int=1,resultVector:Vector.<Ship>=null):Vector.<Ship> {
var itemX:int = (item.x+offX)/gridSize|0;
var itemY:int = (item.y+offY)/gridSize|0;
var minX:int = itemX-radius;
if (minX < 0) { minX = 0; }
var minY:int = itemY-radius;
if (minY < 0) { minY = 0; }
var maxX:int = itemX+radius;
if (maxX > w) { maxX = w; }
var maxY:int = itemY+radius;
if (maxY > h) { maxY = h; }
var results:Vector.<Ship> = resultVector ? resultVector : new Vector.<Ship>();
var count:int= resultVector ? resultVector.length : 0;
for (var x:int=minX; x<=maxX; x++) {
var adjX:int = x*h;
for (var y:int=minY; y<=maxY; y++) {
var itemList:Vector.<Ship> = grid[adjX+y];
var l:int = itemList.length;
for (var i:int=0; i<l; i++) {
var item2:Ship = itemList[i];
if (!checkDead || !deadItems[item2]) { results[count++] = itemList[i]; }
}
}
}
return results;
}
// Protected methods:
/** @private **/
protected function init():void {
w = Math.ceil(bounds.width/gridSize)+1;
h = Math.ceil(bounds.height/gridSize)+1;
length = w*h;
offX = -bounds.x;
offY = -bounds.y;
m = 1/gridSize;
lengths = new Vector.<int>();
grid = new Vector.<Vector.<Ship>>(length,true);
for (var i:int=0; i<length; i++) {
grid[i] = new Vector.<Ship>();
}
}
}