| 1 |
package com.modestmaps.core |
|---|
| 2 |
{ |
|---|
| 3 |
import com.modestmaps.events.MapEvent; |
|---|
| 4 |
import com.modestmaps.mapproviders.IMapProvider; |
|---|
| 5 |
|
|---|
| 6 |
import flash.display.Bitmap; |
|---|
| 7 |
import flash.display.DisplayObject; |
|---|
| 8 |
import flash.display.Loader; |
|---|
| 9 |
import flash.display.LoaderInfo; |
|---|
| 10 |
import flash.display.Sprite; |
|---|
| 11 |
import flash.events.Event; |
|---|
| 12 |
import flash.events.IOErrorEvent; |
|---|
| 13 |
import flash.events.MouseEvent; |
|---|
| 14 |
import flash.events.ProgressEvent; |
|---|
| 15 |
import flash.events.TimerEvent; |
|---|
| 16 |
import flash.geom.Matrix; |
|---|
| 17 |
import flash.geom.Point; |
|---|
| 18 |
import flash.geom.Rectangle; |
|---|
| 19 |
import flash.net.URLRequest; |
|---|
| 20 |
import flash.system.LoaderContext; |
|---|
| 21 |
import flash.system.System; |
|---|
| 22 |
import flash.text.TextField; |
|---|
| 23 |
import flash.text.TextFormat; |
|---|
| 24 |
import flash.utils.Dictionary; |
|---|
| 25 |
import flash.utils.Timer; |
|---|
| 26 |
import flash.utils.getTimer; |
|---|
| 27 |
|
|---|
| 28 |
public class TileGrid extends Sprite |
|---|
| 29 |
{ |
|---|
| 30 |
// OPTIONS |
|---|
| 31 |
/////////////////////////////// |
|---|
| 32 |
|
|---|
| 33 |
// TODO: split these out into a TileGridOptions class and allow mass setting/getting? |
|---|
| 34 |
|
|---|
| 35 |
protected static const DEFAULT_MAX_PARENT_SEARCH:int = 5; |
|---|
| 36 |
protected static const DEFAULT_MAX_PARENT_LOAD:int = 0; // enable this to load lower zoom tiles first |
|---|
| 37 |
protected static const DEFAULT_MAX_CHILD_SEARCH:int = 1; |
|---|
| 38 |
protected static const DEFAULT_MAX_TILES_TO_KEEP:int = 256; // 256*256*4bytes = 0.25MB ... so 256 tiles is 64MB of memory, minimum! |
|---|
| 39 |
protected static const DEFAULT_TILE_BUFFER:int = 0; |
|---|
| 40 |
protected static const DEFAULT_ENFORCE_BOUNDS:Boolean = true; |
|---|
| 41 |
protected static const DEFAULT_MAX_OPEN_REQUESTS:int = 4; // TODO: should this be split into max-new-requests-per-frame, too? |
|---|
| 42 |
protected static const DEFAULT_ROUND_POSITIONS:Boolean = true; |
|---|
| 43 |
protected static const DEFAULT_ROUND_SCALES:Boolean = true; |
|---|
| 44 |
protected static const DEFAULT_CACHE_LOADERS:Boolean = false; // !!! only enable this if you have crossdomain permissions to access Loader content |
|---|
| 45 |
protected static const DEFAULT_SMOOTH_CONTENT:Boolean = false; // !!! only enable this if you have crossdomain permissions to access Loader content |
|---|
| 46 |
protected static const DEFAULT_MAX_LOADER_CACHE_SIZE:int = 0; // !!! suggest 256 or so |
|---|
| 47 |
|
|---|
| 48 |
/** if we don't have a tile at currentZoom, onRender will look for tiles up to 5 levels out. |
|---|
| 49 |
* set this to 0 if you only want the current zoom level's tiles |
|---|
| 50 |
* WARNING: tiles will get scaled up A LOT for this, but maybe it beats blank tiles? */ |
|---|
| 51 |
public var maxParentSearch:int = DEFAULT_MAX_PARENT_SEARCH; |
|---|
| 52 |
/** if we don't have a tile at currentZoom, onRender will look for tiles up to one levels further in. |
|---|
| 53 |
* set this to 0 if you only want the current zoom level's tiles |
|---|
| 54 |
* WARNING: bad, bad nasty recursion possibilities really soon if you go much above 1 |
|---|
| 55 |
* - it works, but you probably don't want to change this number :) */ |
|---|
| 56 |
public var maxChildSearch:int = DEFAULT_MAX_CHILD_SEARCH; |
|---|
| 57 |
|
|---|
| 58 |
/** if maxParentSearch is enabled, setting maxParentLoad to between 1 and maxParentSearch |
|---|
| 59 |
* will make requests for lower zoom levels first */ |
|---|
| 60 |
public var maxParentLoad:int = DEFAULT_MAX_PARENT_LOAD; |
|---|
| 61 |
|
|---|
| 62 |
/** this is the maximum size of tileCache (visible tiles will also be kept in the cache) */ |
|---|
| 63 |
public var maxTilesToKeep:int = DEFAULT_MAX_TILES_TO_KEEP; |
|---|
| 64 |
|
|---|
| 65 |
// 0 or 1, really: 2 will load *lots* of extra tiles |
|---|
| 66 |
public var tileBuffer:int = DEFAULT_TILE_BUFFER; |
|---|
| 67 |
|
|---|
| 68 |
// how many Loaders are allowed to be open at once? |
|---|
| 69 |
public var maxOpenRequests:int = DEFAULT_MAX_OPEN_REQUESTS; |
|---|
| 70 |
|
|---|
| 71 |
/** set this to true to enable enforcing of map bounds from the map provider's limits */ |
|---|
| 72 |
public var enforceBoundsEnabled:Boolean = DEFAULT_ENFORCE_BOUNDS; |
|---|
| 73 |
|
|---|
| 74 |
/** set this to false, along with roundScalesEnabled, if you need a map to stay 'fixed' in place as it changes size */ |
|---|
| 75 |
public var roundPositionsEnabled:Boolean = DEFAULT_ROUND_POSITIONS; |
|---|
| 76 |
|
|---|
| 77 |
/** set this to false, along with roundPositionsEnabled, if you need a map to stay 'fixed' in place as it changes size */ |
|---|
| 78 |
public var roundScalesEnabled:Boolean = DEFAULT_ROUND_SCALES; |
|---|
| 79 |
|
|---|
| 80 |
/** set this to true to enable bitmap smoothing on tiles - requires crossdomain.xml permissions so won't work online with most providers */ |
|---|
| 81 |
public var smoothContent:Boolean = DEFAULT_SMOOTH_CONTENT; |
|---|
| 82 |
|
|---|
| 83 |
/** with tile providers that you have crossdomain.xml support for, |
|---|
| 84 |
* it's possible to avoid extra requests by reusing bitmapdata. enable cacheLoaders to try and do that */ |
|---|
| 85 |
public static var cacheLoaders:Boolean = DEFAULT_CACHE_LOADERS; |
|---|
| 86 |
public static var maxLoaderCacheSize:int = DEFAULT_MAX_LOADER_CACHE_SIZE; |
|---|
| 87 |
protected static var loaderCache:Object = {}; |
|---|
| 88 |
protected static var cachedUrls:Array = []; |
|---|
| 89 |
|
|---|
| 90 |
/////////////////////////////// |
|---|
| 91 |
// END OPTIONS |
|---|
| 92 |
|
|---|
| 93 |
// TILE_WIDTH and TILE_HEIGHT are now tileWidth and tileHeight |
|---|
| 94 |
// this was needed for the NASA DailyPlanetProvider which has 512x512px tiles |
|---|
| 95 |
// public static const TILE_WIDTH:Number = 256; |
|---|
| 96 |
// public static const TILE_HEIGHT:Number = 256; |
|---|
| 97 |
|
|---|
| 98 |
// read-only, kept up to date by calculateBounds() |
|---|
| 99 |
protected var _minZoom:Number; |
|---|
| 100 |
protected var _maxZoom:Number; |
|---|
| 101 |
|
|---|
| 102 |
protected var minTx:Number, maxTx:Number, minTy:Number, maxTy:Number; |
|---|
| 103 |
|
|---|
| 104 |
// read-only, convenience for tileWidth/Height |
|---|
| 105 |
protected var _tileWidth:Number; |
|---|
| 106 |
protected var _tileHeight:Number; |
|---|
| 107 |
|
|---|
| 108 |
// pan and zoom etc are stored in here |
|---|
| 109 |
// NB: this matrix is never applied to a DisplayObject's transform |
|---|
| 110 |
// because it would require scaling tile positions to compensate. |
|---|
| 111 |
// Instead, we adapt its values such that the current zoom level |
|---|
| 112 |
// is approximately scale 1, and positions make sense in screen pixels |
|---|
| 113 |
protected var worldMatrix:Matrix; |
|---|
| 114 |
|
|---|
| 115 |
// this turns screen points into coordinates |
|---|
| 116 |
protected var _invertedMatrix:Matrix; // use lazy getter for this |
|---|
| 117 |
|
|---|
| 118 |
// the corners and center of the screen, in map coordinates |
|---|
| 119 |
// (these also have lazy getters) |
|---|
| 120 |
protected var _topLeftCoordinate:Coordinate; |
|---|
| 121 |
protected var _bottomRightCoordinate:Coordinate; |
|---|
| 122 |
protected var _centerCoordinate:Coordinate; |
|---|
| 123 |
|
|---|
| 124 |
// where the tiles live: |
|---|
| 125 |
protected var well:Sprite; |
|---|
| 126 |
|
|---|
| 127 |
protected var provider:IMapProvider; |
|---|
| 128 |
|
|---|
| 129 |
protected var tileQueue:TileQueue; |
|---|
| 130 |
|
|---|
| 131 |
protected var tileCache:TileCache; |
|---|
| 132 |
|
|---|
| 133 |
protected var tilePool:TilePool; |
|---|
| 134 |
|
|---|
| 135 |
// per-tile, the array of images we're going to load, which can be empty |
|---|
| 136 |
// TODO: document this in IMapProvider, so that provider implementers know |
|---|
| 137 |
// they are free to check the bounds of their overlays and don't have to serve |
|---|
| 138 |
// millions of 404s |
|---|
| 139 |
protected var layersNeeded:Object = {}; |
|---|
| 140 |
|
|---|
| 141 |
// keys we've recently seen |
|---|
| 142 |
protected var recentlySeen:Array = []; |
|---|
| 143 |
|
|---|
| 144 |
// open requests |
|---|
| 145 |
protected var openRequests:Array = []; |
|---|
| 146 |
|
|---|
| 147 |
// keeping track for dispatching MapEvent.ALL_TILES_LOADED and MapEvent.BEGIN_TILE_LOADING |
|---|
| 148 |
protected var previousOpenRequests:int = 0; |
|---|
| 149 |
|
|---|
| 150 |
// currently visible tiles |
|---|
| 151 |
protected var visibleTiles:Array = []; |
|---|
| 152 |
|
|---|
| 153 |
// number of tiles we're failing to show |
|---|
| 154 |
protected var blankCount:int = 0; |
|---|
| 155 |
|
|---|
| 156 |
// a textfield with lots of stats |
|---|
| 157 |
public var debugField:TextField; |
|---|
| 158 |
|
|---|
| 159 |
// for stats: |
|---|
| 160 |
protected var lastFrameTime:Number; |
|---|
| 161 |
protected var fps:Number = 30; |
|---|
| 162 |
|
|---|
| 163 |
// what zoom level of tiles is 'correct'? |
|---|
| 164 |
protected var _currentTileZoom:int; |
|---|
| 165 |
// so we know if we're going in or out |
|---|
| 166 |
protected var previousTileZoom:int; |
|---|
| 167 |
|
|---|
| 168 |
// for sorting the queue: |
|---|
| 169 |
protected var centerRow:Number; |
|---|
| 170 |
protected var centerColumn:Number; |
|---|
| 171 |
|
|---|
| 172 |
// for pan events |
|---|
| 173 |
protected var startPan:Coordinate; |
|---|
| 174 |
public var panning:Boolean; |
|---|
| 175 |
|
|---|
| 176 |
// previous mouse position when dragging |
|---|
| 177 |
protected var pmouse:Point; |
|---|
| 178 |
|
|---|
| 179 |
// for zoom events |
|---|
| 180 |
protected var startZoom:Number = -1; |
|---|
| 181 |
public var zooming:Boolean; |
|---|
| 182 |
|
|---|
| 183 |
protected var mapWidth:Number; |
|---|
| 184 |
protected var mapHeight:Number; |
|---|
| 185 |
|
|---|
| 186 |
protected var draggable:Boolean; |
|---|
| 187 |
|
|---|
| 188 |
// setting this.dirty = true will request an Event.RENDER |
|---|
| 189 |
protected var _dirty:Boolean; |
|---|
| 190 |
|
|---|
| 191 |
// setting to true will dispatch a CHANGE event which Map will convert to an EXTENT_CHANGED for us |
|---|
| 192 |
protected var matrixChanged:Boolean = false; |
|---|
| 193 |
|
|---|
| 194 |
protected var queueTimer:Timer; |
|---|
| 195 |
|
|---|
| 196 |
public function TileGrid(w:Number, h:Number, draggable:Boolean, provider:IMapProvider) |
|---|
| 197 |
{ |
|---|
| 198 |
doubleClickEnabled = true; |
|---|
| 199 |
|
|---|
| 200 |
//this.map = map; |
|---|
| 201 |
this.draggable = draggable; |
|---|
| 202 |
|
|---|
| 203 |
// don't call set map provider here, because it triggers a redraw and we're not ready for that |
|---|
| 204 |
this.provider = provider; |
|---|
| 205 |
|
|---|
| 206 |
// but do grab tile dimensions: |
|---|
| 207 |
_tileWidth = provider.tileWidth; |
|---|
| 208 |
_tileHeight = provider.tileHeight; |
|---|
| 209 |
|
|---|
| 210 |
// and calculate bounds from provider |
|---|
| 211 |
calculateBounds(); |
|---|
| 212 |
|
|---|
| 213 |
this.tilePool = new TilePool(Tile); |
|---|
| 214 |
this.tileQueue = new TileQueue(); |
|---|
| 215 |
this.tileCache = new TileCache(tilePool); |
|---|
| 216 |
|
|---|
| 217 |
this.mapWidth = w; |
|---|
| 218 |
this.mapHeight = h; |
|---|
| 219 |
|
|---|
| 220 |
debugField = new TextField(); |
|---|
| 221 |
debugField.defaultTextFormat = new TextFormat(null, 12, 0x000000, false); |
|---|
| 222 |
debugField.backgroundColor = 0xffffff; |
|---|
| 223 |
debugField.background = true; |
|---|
| 224 |
debugField.text = "messages"; |
|---|
| 225 |
debugField.x = mapWidth - debugField.width - 15; |
|---|
| 226 |
debugField.y = mapHeight - debugField.height - 15; |
|---|
| 227 |
debugField.name = 'text'; |
|---|
| 228 |
debugField.mouseEnabled = false; |
|---|
| 229 |
debugField.selectable = false; |
|---|
| 230 |
debugField.multiline = true; |
|---|
| 231 |
debugField.wordWrap = false; |
|---|
| 232 |
|
|---|
| 233 |
lastFrameTime = getTimer(); |
|---|
| 234 |
|
|---|
| 235 |
well = new Sprite(); |
|---|
| 236 |
well.name = 'well'; |
|---|
| 237 |
well.doubleClickEnabled = true; |
|---|
| 238 |
well.mouseEnabled = true; |
|---|
| 239 |
well.mouseChildren = false; |
|---|
| 240 |
addChild(well); |
|---|
| 241 |
|
|---|
| 242 |
worldMatrix = new Matrix(); |
|---|
| 243 |
|
|---|
| 244 |
addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); |
|---|
| 245 |
|
|---|
| 246 |
queueTimer = new Timer(200); |
|---|
| 247 |
queueTimer.addEventListener(TimerEvent.TIMER, processQueue); |
|---|
| 248 |
} |
|---|
| 249 |
|
|---|
| 250 |
/** |
|---|
| 251 |
* Get the Tile instance that corresponds to a given coordinate. |
|---|
| 252 |
*/ |
|---|
| 253 |
public function getCoordTile(coord:Coordinate):Tile |
|---|
| 254 |
{ |
|---|
| 255 |
// these get floored when they're cast as ints in tileKey() |
|---|
| 256 |
var key:String = tileKey(coord.column, coord.row, coord.zoom); |
|---|
| 257 |
return well.getChildByName(key) as Tile; |
|---|
| 258 |
} |
|---|
| 259 |
|
|---|
| 260 |
private function onAddedToStage(event:Event):void |
|---|
| 261 |
{ |
|---|
| 262 |
if (draggable) { |
|---|
| 263 |
addEventListener(MouseEvent.MOUSE_DOWN, mousePressed, true); |
|---|
| 264 |
} |
|---|
| 265 |
addEventListener(Event.RENDER, onRender); |
|---|
| 266 |
addEventListener(Event.ENTER_FRAME, onEnterFrame); |
|---|
| 267 |
queueTimer.start(); |
|---|
| 268 |
addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); |
|---|
| 269 |
removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); |
|---|
| 270 |
dirty = true; |
|---|
| 271 |
// force an on-render in case we were added in a render handler |
|---|
| 272 |
onRender(); |
|---|
| 273 |
} |
|---|
| 274 |
|
|---|
| 275 |
private function onRemovedFromStage(event:Event):void |
|---|
| 276 |
{ |
|---|
| 277 |
if (hasEventListener(MouseEvent.MOUSE_DOWN)) { |
|---|
| 278 |
removeEventListener(MouseEvent.MOUSE_DOWN, mousePressed, true); |
|---|
| 279 |
} |
|---|
| 280 |
removeEventListener(Event.RENDER, onRender); |
|---|
| 281 |
removeEventListener(Event.ENTER_FRAME, onEnterFrame); |
|---|
| 282 |
queueTimer.stop(); |
|---|
| 283 |
removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage); |
|---|
| 284 |
addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); |
|---|
| 285 |
} |
|---|
| 286 |
|
|---|
| 287 |
/** The classes themselves serve as factories! |
|---|
| 288 |
* |
|---|
| 289 |
* @param tileClass e.g. Tile, TweenTile, etc. |
|---|
| 290 |
* |
|---|
| 291 |
* @see http://norvig.com/design-patterns/img013.gif |
|---|
| 292 |
*/ |
|---|
| 293 |
public function setTileClass(tileClass:Class):void |
|---|
| 294 |
{ |
|---|
| 295 |
tilePool.setTileClass(tileClass); |
|---|
| 296 |
clearEverything(); |
|---|
| 297 |
} |
|---|
| 298 |
|
|---|
| 299 |
/** processes the tileQueue and optionally outputs stats into debugField */ |
|---|
| 300 |
protected function onEnterFrame(event:Event=null):void |
|---|
| 301 |
{ |
|---|
| 302 |
if (debugField.parent) { |
|---|
| 303 |
// for stats... |
|---|
| 304 |
var frameDuration:Number = getTimer() - lastFrameTime; |
|---|
| 305 |
lastFrameTime = getTimer(); |
|---|
| 306 |
|
|---|
| 307 |
fps = (0.9 * fps) + (0.1 * (1000.0/frameDuration)); |
|---|
| 308 |
|
|---|
| 309 |
// report stats: |
|---|
| 310 |
var tileChildren:int = 0; |
|---|
| 311 |
for (var i:int = 0; i < well.numChildren; i++) { |
|---|
| 312 |
tileChildren += Tile(well.getChildAt(i)).numChildren; |
|---|
| 313 |
} |
|---|
| 314 |
/* debugField.text = "fps: " + fps.toFixed(0) |
|---|
| 315 |
+ "\nmemory: " + (System.totalMemory/1048576).toFixed(1) + "MB"; */ |
|---|
| 316 |
|
|---|
| 317 |
debugField.text = "tx: " + tx.toFixed(3) |
|---|
| 318 |
+ "\nty: " + tx.toFixed(3) |
|---|
| 319 |
+ "\nsc: " + scale.toFixed(4) |
|---|
| 320 |
+ "\nfps: " + fps.toFixed(0) |
|---|
| 321 |
+ "\ncurrent child count: " + well.numChildren |
|---|
| 322 |
+ "\ncurrent child of tile count: " + tileChildren |
|---|
| 323 |
+ "\nvisible tile count: " + visibleTiles.length |
|---|
| 324 |
+ "\nqueue length: " + tileQueue.length |
|---|
| 325 |
+ "\nblank count: " + blankCount |
|---|
| 326 |
+ "\nrequests: " + openRequests.length |
|---|
| 327 |
+ "\nfinished (cached) tiles: " + tileCache.size |
|---|
| 328 |
+ "\nrecently used tiles: " + recentlySeen.length |
|---|
| 329 |
+ "\ncachedLoaders: " + cachedUrls.length |
|---|
| 330 |
+ "\nTiles created: " + Tile.count |
|---|
| 331 |
+ "\nmemory: " + (System.totalMemory/1048576).toFixed(1) + "MB"; |
|---|
| 332 |
debugField.width = debugField.textWidth+8; |
|---|
| 333 |
debugField.height = debugField.textHeight+4; |
|---|
| 334 |
debugField.x = mapWidth - debugField.width - 15; |
|---|
| 335 |
debugField.y = mapHeight - debugField.height - 15; |
|---|
| 336 |
} |
|---|
| 337 |
|
|---|
| 338 |
//processQueue(); |
|---|
| 339 |
} |
|---|
| 340 |
|
|---|
| 341 |
protected function onRendered():void |
|---|
| 342 |
{ |
|---|
| 343 |
// listen out for this if you want to be sure map is in its final state before reprojecting markers etc. |
|---|
| 344 |
dispatchEvent(new MapEvent(MapEvent.RENDERED)); |
|---|
| 345 |
} |
|---|
| 346 |
|
|---|
| 347 |
protected function onPanned():void |
|---|
| 348 |
{ |
|---|
| 349 |
var pt:Point = coordinatePoint(startPan); |
|---|
| 350 |
dispatchEvent(new MapEvent(MapEvent.PANNED, pt.subtract(new Point(mapWidth/2, mapHeight/2)))); |
|---|
| 351 |
} |
|---|
| 352 |
|
|---|
| 353 |
protected function onZoomed():void |
|---|
| 354 |
{ |
|---|
| 355 |
var zoomEvent:MapEvent = new MapEvent(MapEvent.ZOOMED_BY, zoomLevel-startZoom); |
|---|
| 356 |
// this might also be useful |
|---|
| 357 |
zoomEvent.zoomLevel = zoomLevel; |
|---|
| 358 |
dispatchEvent(zoomEvent); |
|---|
| 359 |
} |
|---|
| 360 |
|
|---|
| 361 |
protected function onChanged():void |
|---|
| 362 |
{ |
|---|
| 363 |
// doesn't bubble, unlike MapEvent |
|---|
| 364 |
// Map will pick this up and dispatch MapEvent.EXTENT_CHANGED for us |
|---|
| 365 |
dispatchEvent(new Event(Event.CHANGE, false, false)); |
|---|
| 366 |
} |
|---|
| 367 |
|
|---|
| 368 |
protected function onBeginTileLoading():void |
|---|
| 369 |
{ |
|---|
| 370 |
dispatchEvent(new MapEvent(MapEvent.BEGIN_TILE_LOADING)); |
|---|
| 371 |
} |
|---|
| 372 |
|
|---|
| 373 |
protected function onProgress():void |
|---|
| 374 |
{ |
|---|
| 375 |
// dispatch tile load progress |
|---|
| 376 |
dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS, false, false, previousOpenRequests - openRequests.length, previousOpenRequests)); |
|---|
| 377 |
} |
|---|
| 378 |
|
|---|
| 379 |
protected function onAllTilesLoaded():void |
|---|
| 380 |
{ |
|---|
| 381 |
dispatchEvent(new MapEvent(MapEvent.ALL_TILES_LOADED)); |
|---|
| 382 |
} |
|---|
| 383 |
|
|---|
| 384 |
/** |
|---|
| 385 |
* figures out from worldMatrix which tiles we should be showing, adds them to the stage, adds them to the tileQueue if needed, etc. |
|---|
| 386 |
* |
|---|
| 387 |
* from my recent testing, TileGrid.onRender takes < 5ms most of the time, and rarely >10ms |
|---|
| 388 |
* (Flash Player 9, Firefox, Macbook Pro) |
|---|
| 389 |
* |
|---|
| 390 |
*/ |
|---|
| 391 |
protected function onRender(event:Event=null):void |
|---|
| 392 |
{ |
|---|
| 393 |
if (!dirty || !stage) { |
|---|
| 394 |
onRendered(); |
|---|
| 395 |
return; |
|---|
| 396 |
} |
|---|
| 397 |
|
|---|
| 398 |
var boundsEnforced:Boolean = enforceBounds(); |
|---|
| 399 |
|
|---|
| 400 |
if (zooming || panning) { |
|---|
| 401 |
if (panning) { |
|---|
| 402 |
onPanned(); |
|---|
| 403 |
} |
|---|
| 404 |
if (zooming) { |
|---|
| 405 |
onZoomed(); |
|---|
| 406 |
} |
|---|
| 407 |
} |
|---|
| 408 |
else if (boundsEnforced) { |
|---|
| 409 |
onChanged(); |
|---|
| 410 |
} |
|---|
| 411 |
else if (matrixChanged) { |
|---|
| 412 |
matrixChanged = false; |
|---|
| 413 |
onChanged(); |
|---|
| 414 |
} |
|---|
| 415 |
|
|---|
| 416 |
// what zoom level of tiles should we be loading, taking into account min/max zoom? |
|---|
| 417 |
// (0 when scale == 1, 1 when scale == 2, 2 when scale == 4, etc.) |
|---|
| 418 |
var newZoom:int = Math.min(maxZoom, Math.max(minZoom, Math.round(zoomLevel))); |
|---|
| 419 |
|
|---|
| 420 |
// see if the newZoom is different to currentZoom |
|---|
| 421 |
// so we know which way we're zooming, if any: |
|---|
| 422 |
if (currentTileZoom != newZoom) { |
|---|
| 423 |
previousTileZoom = currentTileZoom; |
|---|
| 424 |
} |
|---|
| 425 |
|
|---|
| 426 |
// this is the level of tiles we'll be loading: |
|---|
| 427 |
_currentTileZoom = newZoom; |
|---|
| 428 |
|
|---|
| 429 |
// find start and end columns for the visible tiles, at current tile zoom |
|---|
| 430 |
// TODO: take account of potential rotation in worldMatrix (ask Tom about this if you need it) |
|---|
| 431 |
var tlC:Coordinate = topLeftCoordinate.zoomTo(currentTileZoom); |
|---|
| 432 |
var brC:Coordinate = bottomRightCoordinate.zoomTo(currentTileZoom); |
|---|
| 433 |
|
|---|
| 434 |
// optionally pad it out a little bit more with a tile buffer |
|---|
| 435 |
// TODO: investigate giving a directional bias to TILE_BUFFER when panning quickly |
|---|
| 436 |
// NB:- I'm pretty sure these calculations are accurate enough that using |
|---|
| 437 |
// Math.ceil for the maxCols will load one column too many -- Tom |
|---|
| 438 |
var minCol:int = Math.floor(tlC.column) - tileBuffer; |
|---|
| 439 |
var maxCol:int = Math.floor(brC.column) + tileBuffer; |
|---|
| 440 |
var minRow:int = Math.floor(tlC.row) - tileBuffer; |
|---|
| 441 |
var maxRow:int = Math.floor(brC.row) + tileBuffer; |
|---|
| 442 |
|
|---|
| 443 |
// loop over all tiles and find parent or child tiles from cache to compensate for unloaded tiles: |
|---|
| 444 |
repopulateVisibleTiles(minCol, maxCol, minRow, maxRow); |
|---|
| 445 |
|
|---|
| 446 |
// move visible tiles to the end of recentlySeen if we're done loading them |
|---|
| 447 |
// the 'least recently seen' tiles will be removed from the tileCache below |
|---|
| 448 |
for each (var visibleTile:Tile in visibleTiles) { |
|---|
| 449 |
if (!layersNeeded[visibleTile.name]) { |
|---|
| 450 |
var ri:int = recentlySeen.indexOf(visibleTile.name); |
|---|
| 451 |
if (ri >= 0) { |
|---|
| 452 |
recentlySeen.splice(ri, 1); |
|---|
| 453 |
} |
|---|
| 454 |
recentlySeen.push(visibleTile.name); |
|---|
| 455 |
} |
|---|
| 456 |
} |
|---|
| 457 |
|
|---|
| 458 |
// prune tiles from the well if they shouldn't be there (not currently in visibleTiles) |
|---|
| 459 |
// TODO: unless they're fading in or out? |
|---|
| 460 |
// (loop backwards so removal doesn't change i) |
|---|
| 461 |
for (var i:int = well.numChildren-1; i >= 0; i--) { |
|---|
| 462 |
var wellTile:Tile = well.getChildAt(i) as Tile; |
|---|
| 463 |
if (visibleTiles.indexOf(wellTile) < 0) { |
|---|
| 464 |
well.removeChild(wellTile); |
|---|
| 465 |
wellTile.hide(); |
|---|
| 466 |
if (!tileCache.containsKey(wellTile.name)) { |
|---|
| 467 |
//trace("destroying tile that was in the well but never cached"); |
|---|
| 468 |
delete layersNeeded[wellTile.name]; |
|---|
| 469 |
if (tileQueue.contains(wellTile)) { |
|---|
| 470 |
tileQueue.remove(wellTile); |
|---|
| 471 |
} |
|---|
| 472 |
tilePool.returnTile(wellTile); |
|---|
| 473 |
} |
|---|
| 474 |
} |
|---|
| 475 |
} |
|---|
| 476 |
|
|---|
| 477 |
// position tiles such that currentZoom is approximately scale 1 |
|---|
| 478 |
// and x and y make sense in pixels relative to tlC.column and tlC.row (topleft) |
|---|
| 479 |
positionTiles(tlC.column, tlC.row); |
|---|
| 480 |
|
|---|
| 481 |
// all the visible tiles will be at the end of recentlySeen |
|---|
| 482 |
// let's make sure we keep them around: |
|---|
| 483 |
var maxRecentlySeen:int = Math.max(visibleTiles.length, maxTilesToKeep); |
|---|
| 484 |
|
|---|
| 485 |
// prune cache of already seen tiles if it's getting too big: |
|---|
| 486 |
if (recentlySeen.length > maxRecentlySeen) { |
|---|
| 487 |
|
|---|
| 488 |
// can we sort so that biggest zoom levels get removed first, without removing currently visible tiles? |
|---|
| 489 |
/* var visibleKeys:Array = recentlySeen.slice(recentlySeen.length - visibleTiles.length, recentlySeen.length); |
|---|
| 490 |
|
|---|
| 491 |
// take a look at everything else |
|---|
| 492 |
recentlySeen = recentlySeen.slice(0, recentlySeen.length - visibleTiles.length); |
|---|
| 493 |
recentlySeen = recentlySeen.sort(Array.DESCENDING); |
|---|
| 494 |
recentlySeen = recentlySeen.concat(visibleKeys); */ |
|---|
| 495 |
|
|---|
| 496 |
// throw away keys at the beginning of recentlySeen |
|---|
| 497 |
recentlySeen = recentlySeen.slice(recentlySeen.length - maxRecentlySeen, recentlySeen.length); |
|---|
| 498 |
|
|---|
| 499 |
// loop over our internal tile cache |
|---|
| 500 |
// and throw out tiles not in recentlySeen |
|---|
| 501 |
tileCache.retainKeys(recentlySeen); |
|---|
| 502 |
} |
|---|
| 503 |
|
|---|
| 504 |
// update centerRow and centerCol for sorting the tileQueue in processQueue() |
|---|
| 505 |
var center:Coordinate = centerCoordinate.zoomTo(currentTileZoom); |
|---|
| 506 |
centerRow = center.row; |
|---|
| 507 |
centerColumn = center.column; |
|---|
| 508 |
|
|---|
| 509 |
onRendered(); |
|---|
| 510 |
|
|---|
| 511 |
dirty = false; |
|---|
| 512 |
} |
|---|
| 513 |
|
|---|
| 514 |
/** |
|---|
| 515 |
* loops over given cols and rows and adds tiles to visibleTiles array and the well |
|---|
| 516 |
* using child or parent tiles to compensate for tiles not yet available in the tileCache |
|---|
| 517 |
*/ |
|---|
| 518 |
private function repopulateVisibleTiles(minCol:int, maxCol:int, minRow:int, maxRow:int):void |
|---|
| 519 |
{ |
|---|
| 520 |
visibleTiles = []; |
|---|
| 521 |
|
|---|
| 522 |
blankCount = 0; // keep count of how many tiles we missed? |
|---|
| 523 |
|
|---|
| 524 |
// for use in loops etc. |
|---|
| 525 |
var coord:Coordinate = new Coordinate(0,0,0); |
|---|
| 526 |
|
|---|
| 527 |
var searchedParentKeys:Object = {}; |
|---|
| 528 |
|
|---|
| 529 |
// loop over currently visible tiles |
|---|
| 530 |
for (var col:int = minCol; col <= maxCol; col++) { |
|---|
| 531 |
for (var row:int = minRow; row <= maxRow; row++) { |
|---|
| 532 |
|
|---|
| 533 |
// create a string key for this tile |
|---|
| 534 |
var key:String = tileKey(col, row, currentTileZoom); |
|---|
| 535 |
|
|---|
| 536 |
// see if we already have this tile |
|---|
| 537 |
var tile:Tile = well.getChildByName(key) as Tile; |
|---|
| 538 |
|
|---|
| 539 |
// create it if not, and add it to the load queue |
|---|
| 540 |
if (!tile) { |
|---|
| 541 |
tile = tileCache.getTile(key); |
|---|
| 542 |
if (!tile) { |
|---|
| 543 |
tile = tilePool.getTile(col, row, currentTileZoom); |
|---|
| 544 |
tile.name = key; |
|---|
| 545 |
coord.row = tile.row; |
|---|
| 546 |
coord.column = tile.column; |
|---|
| 547 |
coord.zoom = tile.zoom; |
|---|
| 548 |
var urls:Array = provider.getTileUrls(coord); |
|---|
| 549 |
if (urls && urls.length > 0) { |
|---|
| 550 |
// keep a local copy of the URLs so we don't have to call this twice: |
|---|
| 551 |
layersNeeded[tile.name] = urls; |
|---|
| 552 |
tileQueue.push(tile); |
|---|
| 553 |
} |
|---|
| 554 |
else { |
|---|
| 555 |
tile.show(); |
|---|
| 556 |
} |
|---|
| 557 |
} |
|---|
| 558 |
else { |
|---|
| 559 |
tile.show(); |
|---|
| 560 |
} |
|---|
| 561 |
well.addChild(tile); |
|---|
| 562 |
} |
|---|
| 563 |
|
|---|
| 564 |
visibleTiles.push(tile); |
|---|
| 565 |
|
|---|
| 566 |
var tileReady:Boolean = tile.isShowing() && (layersNeeded[tile.name] == null); |
|---|
| 567 |
|
|---|
| 568 |
// |
|---|
| 569 |
// if the tile isn't ready yet, we're going to reuse a parent tile |
|---|
| 570 |
// if there isn't a parent tile, and we're zooming out, we'll reuse child tiles |
|---|
| 571 |
// if we don't get all 4 child tiles, we'll look at more parent levels |
|---|
| 572 |
// |
|---|
| 573 |
// yes, this is quite involved, but it should be fast enough because most of the loops |
|---|
| 574 |
// don't get hit most of the time |
|---|
| 575 |
// |
|---|
| 576 |
|
|---|
| 577 |
if (!tileReady) { |
|---|
| 578 |
|
|---|
| 579 |
var foundParent:Boolean = false; |
|---|
| 580 |
var foundChildren:int = 0; |
|---|
| 581 |
|
|---|
| 582 |
if (currentTileZoom > previousTileZoom) { |
|---|
| 583 |
|
|---|
| 584 |
// if it still doesn't have enough images yet, or it's fading in, try a double size parent instead |
|---|
| 585 |
if (maxParentSearch > 0 && currentTileZoom > minZoom) { |
|---|
| 586 |
var firstParentKey:String = parentKey(col, row, currentTileZoom, currentTileZoom-1); |
|---|
| 587 |
if (!searchedParentKeys[firstParentKey]) { |
|---|
| 588 |
searchedParentKeys[firstParentKey] = true; |
|---|
| 589 |
if (ensureVisible(firstParentKey)) { |
|---|
| 590 |
foundParent = true; |
|---|
| 591 |
} |
|---|
| 592 |
if (!foundParent && (currentTileZoom - 1 < maxParentLoad)) { |
|---|
| 593 |
//trace("requesting parent tile at zoom", pzoom); |
|---|
| 594 |
var firstParentCoord:Array = parentCoord(col, row, currentTileZoom, currentTileZoom-1); |
|---|
| 595 |
visibleTiles.push(requestLoad(firstParentCoord[0], firstParentCoord[1], currentTileZoom-1)); |
|---|
| 596 |
} |
|---|
| 597 |
} |
|---|
| 598 |
} |
|---|
| 599 |
|
|---|
| 600 |
} |
|---|
| 601 |
else { |
|---|
| 602 |
|
|---|
| 603 |
// currentZoom <= previousZoom, so we're zooming out |
|---|
| 604 |
// and therefore we might want to reuse 'smaller' tiles |
|---|
| 605 |
|
|---|
| 606 |
// if it doesn't have an image yet, see if we can make it from smaller images |
|---|
| 607 |
if (!foundParent && maxChildSearch > 0 && currentTileZoom < maxZoom) { |
|---|
| 608 |
for (var czoom:int = currentTileZoom+1; czoom <= Math.min(maxZoom, currentTileZoom+maxChildSearch); czoom++) { |
|---|
| 609 |
var ckeys:Array = childKeys(col, row, currentTileZoom, czoom); |
|---|
| 610 |
for each (var ckey:String in ckeys) { |
|---|
| 611 |
if (ensureVisible(ckey)) { |
|---|
| 612 |
foundChildren++; |
|---|
| 613 |
} |
|---|
| 614 |
} // ckeys |
|---|
| 615 |
if (foundChildren == ckeys.length) { |
|---|
| 616 |
break; |
|---|
| 617 |
} |
|---|
| 618 |
} // czoom |
|---|
| 619 |
} |
|---|
| 620 |
} |
|---|
| 621 |
|
|---|
| 622 |
var stillNeedsAnImage:Boolean = !foundParent && foundChildren < 4; |
|---|
| 623 |
|
|---|
| 624 |
// if it still doesn't have an image yet, try more parent zooms |
|---|
| 625 |
if (stillNeedsAnImage && maxParentSearch > 1 && currentTileZoom > minZoom) { |
|---|
| 626 |
|
|---|
| 627 |
var startZoomSearch:int = currentTileZoom - 1; |
|---|
| 628 |
|
|---|
| 629 |
if (currentTileZoom > previousTileZoom) { |
|---|
| 630 |
// we already looked for parent level 1, and didn't find it, so: |
|---|
| 631 |
startZoomSearch -= 1; |
|---|
| 632 |
} |
|---|
| 633 |
|
|---|
| 634 |
var endZoomSearch:int = Math.max(minZoom, currentTileZoom-maxParentSearch); |
|---|
| 635 |
|
|---|
| 636 |
for (var pzoom:int = startZoomSearch; pzoom >= endZoomSearch; pzoom--) { |
|---|
| 637 |
var pkey:String = parentKey(col, row, currentTileZoom, pzoom); |
|---|
| 638 |
if (!searchedParentKeys[pkey]) { |
|---|
| 639 |
searchedParentKeys[pkey] = true; |
|---|
| 640 |
if (ensureVisible(pkey)) { |
|---|
| 641 |
stillNeedsAnImage = false; |
|---|
| 642 |
break; |
|---|
| 643 |
} |
|---|
| 644 |
if (currentTileZoom - pzoom < maxParentLoad) { |
|---|
| 645 |
//trace("requesting parent tile at zoom", pzoom); |
|---|
| 646 |
var pcoord:Array = parentCoord(col, row, currentTileZoom, pzoom); |
|---|
| 647 |
visibleTiles.push(requestLoad(pcoord[0], pcoord[1], pzoom)); |
|---|
| 648 |
} |
|---|
| 649 |
} |
|---|
| 650 |
else { |
|---|
| 651 |
break; |
|---|
| 652 |
} |
|---|
| 653 |
} |
|---|
| 654 |
|
|---|
| 655 |
} |
|---|
| 656 |
|
|---|
| 657 |
if (stillNeedsAnImage) { |
|---|
| 658 |
blankCount++; |
|---|
| 659 |
} |
|---|
| 660 |
|
|---|
| 661 |
} // if !tileReady |
|---|
| 662 |
|
|---|
| 663 |
} // for row |
|---|
| 664 |
} // for col |
|---|
| 665 |
|
|---|
| 666 |
// trace("zoomLevel", zoomLevel, "currentTileZoom", currentTileZoom, "blankCount", blankCount); |
|---|
| 667 |
|
|---|
| 668 |
} // repopulateVisibleTiles |
|---|
| 669 |
|
|---|
| 670 |
private function positionTiles(realMinCol:Number, realMinRow:Number):void |
|---|
| 671 |
{ |
|---|
| 672 |
// sort children by difference from current zoom level |
|---|
| 673 |
// this means current is on top, +1 and -1 are next, then +2 and -2, etc. |
|---|
| 674 |
visibleTiles.sort(distanceFromCurrentZoomCompare, Array.DESCENDING); |
|---|
| 675 |
|
|---|
| 676 |
/* var zooms:Array = visibleTiles.map(function(tile:Tile, ...rest):int { |
|---|
| 677 |
return tile.zoom; |
|---|
| 678 |
}); |
|---|
| 679 |
trace("currentTileZoom", currentTileZoom); |
|---|
| 680 |
trace("tile zooms:", zooms); */ |
|---|
| 681 |
|
|---|
| 682 |
// for fixing positions when we're between zoom levels: |
|---|
| 683 |
var positionScaleCompensation:Number = Math.pow(2, zoomLevel-currentTileZoom); |
|---|
| 684 |
|
|---|
| 685 |
// for positioning tile according to current transform, based on current tile zoom |
|---|
| 686 |
var scaleFactors:Array = new Array(maxZoom+1); |
|---|
| 687 |
// scales to compensate for zoom differences between current grid zoom level |
|---|
| 688 |
var tileScales:Array = new Array(maxZoom+1); |
|---|
| 689 |
for (var z:int = 0; z <= maxZoom; z++) { |
|---|
| 690 |
scaleFactors[z] = Math.pow(2.0, currentTileZoom-z) |
|---|
| 691 |
|
|---|
| 692 |
// round up to the nearest pixel to avoid seams between zoom levels |
|---|
| 693 |
if (roundScalesEnabled) { |
|---|
| 694 |
tileScales[z] = Math.ceil(Math.pow(2, zoomLevel-z) * tileWidth) / tileWidth; |
|---|
| 695 |
} |
|---|
| 696 |
else { |
|---|
| 697 |
tileScales[z] = Math.pow(2, zoomLevel-z); |
|---|
| 698 |
} |
|---|
| 699 |
} |
|---|
| 700 |
|
|---|
| 701 |
//trace(); |
|---|
| 702 |
//trace("tile.zoom, tile.alpha, tile.numChildren ? tile.getChildAt(0).alpha : '', tile.isShowing() && (layersNeeded[tile.name] == null), tileCache.containsKey(tile.name)"); |
|---|
| 703 |
|
|---|
| 704 |
// apply the sorted depths, position all the tiles and also keep recentlySeen updated: |
|---|
| 705 |
for each (var tile:Tile in visibleTiles) { |
|---|
| 706 |
|
|---|
| 707 |
// if we set them all to numChildren-1, descending, they should end up correctly sorted |
|---|
| 708 |
well.setChildIndex(tile, well.numChildren-1); |
|---|
| 709 |
|
|---|
| 710 |
var positionCol:Number = (scaleFactors[tile.zoom]*tile.column) - realMinCol; |
|---|
| 711 |
var positionRow:Number = (scaleFactors[tile.zoom]*tile.row) - realMinRow; |
|---|
| 712 |
|
|---|
| 713 |
tile.scaleX = tile.scaleY = tileScales[tile.zoom]; |
|---|
| 714 |
|
|---|
| 715 |
if (!zooming && roundPositionsEnabled) { |
|---|
| 716 |
// this also helps the rare seams not fixed by rounding the tile scale, |
|---|
| 717 |
// but makes slow zooming uglier: |
|---|
| 718 |
// round, not floor, because the latter causes artifacts at lower zoom levels :( |
|---|
| 719 |
tile.x = Math.round(positionCol*tileWidth*positionScaleCompensation); |
|---|
| 720 |
tile.y = Math.round(positionRow*tileHeight*positionScaleCompensation); |
|---|
| 721 |
} |
|---|
| 722 |
else { |
|---|
| 723 |
tile.x = positionCol*tileWidth*positionScaleCompensation; |
|---|
| 724 |
tile.y = positionRow*tileHeight*positionScaleCompensation; |
|---|
| 725 |
} |
|---|
| 726 |
|
|---|
| 727 |
} |
|---|
| 728 |
} |
|---|
| 729 |
|
|---|
| 730 |
/** called by the onEnterFrame handler to manage the tileQueue |
|---|
| 731 |
* usual operation is extremely quick, ~1ms or so */ |
|---|
| 732 |
private function processQueue(event:TimerEvent=null):void |
|---|
| 733 |
{ |
|---|
| 734 |
if (openRequests.length < maxOpenRequests && tileQueue.length > 0) { |
|---|
| 735 |
|
|---|
| 736 |
// prune queue for tiles that aren't visible |
|---|
| 737 |
var removedTiles:Array = tileQueue.retainAll(visibleTiles); |
|---|
| 738 |
|
|---|
| 739 |
// keep layersNeeded tidy: |
|---|
| 740 |
for each (var removedTile:Tile in removedTiles) { |
|---|
| 741 |
delete layersNeeded[removedTile.name]; |
|---|
| 742 |
} |
|---|
| 743 |
|
|---|
| 744 |
// note that queue is not the same as visible tiles, because things |
|---|
| 745 |
// that have already been loaded are also in visible tiles. if we |
|---|
| 746 |
// reuse visible tiles for the queue we'll be loading the same things over and over |
|---|
| 747 |
|
|---|
| 748 |
if (maxParentLoad == 0) { |
|---|
| 749 |
// sort queue by distance from 'center' |
|---|
| 750 |
tileQueue.sortTiles(centerDistanceCompare); |
|---|
| 751 |
} |
|---|
| 752 |
else { |
|---|
| 753 |
tileQueue.sortTiles(zoomThenCenterCompare); |
|---|
| 754 |
} |
|---|
| 755 |
|
|---|
| 756 |
// process the queue |
|---|
| 757 |
while (openRequests.length < maxOpenRequests && tileQueue.length > 0) { |
|---|
| 758 |
var tile:Tile = tileQueue.shift(); |
|---|
| 759 |
// if it's still on the stage: |
|---|
| 760 |
if (tile.parent) { |
|---|
| 761 |
loadNextURLForTile(tile); |
|---|
| 762 |
} |
|---|
| 763 |
} |
|---|
| 764 |
} |
|---|
| 765 |
|
|---|
| 766 |
// you might want to wait for tiles to load before displaying other data, interface elements, etc. |
|---|
| 767 |
// these events take care of that for you... |
|---|
| 768 |
if (previousOpenRequests == 0 && openRequests.length > 0) { |
|---|
| 769 |
onBeginTileLoading(); |
|---|
| 770 |
} |
|---|
| 771 |
else if (previousOpenRequests > 0) |
|---|
| 772 |
{ |
|---|
| 773 |
// TODO: a custom event for load progress rather than overloading bytesloaded? |
|---|
| 774 |
onProgress(); |
|---|
| 775 |
|
|---|
| 776 |
// if we're finished... |
|---|
| 777 |
if (openRequests.length == 0) |
|---|
| 778 |
{ |
|---|
| 779 |
onAllTilesLoaded(); |
|---|
| 780 |
// request redraw to take parent and child tiles off the stage if we haven't already |
|---|
| 781 |
dirty = true; |
|---|
| 782 |
} |
|---|
| 783 |
} |
|---|
| 784 |
|
|---|
| 785 |
previousOpenRequests = openRequests.length; |
|---|
| 786 |
} |
|---|
| 787 |
|
|---|
| 788 |
private function loadNextURLForTile(tile:Tile):void |
|---|
| 789 |
{ |
|---|
| 790 |
// TODO: add urls to Tile? |
|---|
| 791 |
var urls:Array = layersNeeded[tile.name] as Array; |
|---|
| 792 |
if (urls && urls.length > 0) { |
|---|
| 793 |
var url:* = urls.shift(); |
|---|
| 794 |
if (cacheLoaders && (url is String) && loaderCache[url]) { |
|---|
| 795 |
var original:Bitmap = loaderCache[url] as Bitmap; |
|---|
| 796 |
var bitmap:Bitmap = new Bitmap(original.bitmapData); |
|---|
| 797 |
tile.addChild(bitmap); |
|---|
| 798 |
loadNextURLForTile(tile); |
|---|
| 799 |
} |
|---|
| 800 |
else { |
|---|
| 801 |
var tileLoader:Loader = new Loader(); |
|---|
| 802 |
tileLoader.name = tile.name; |
|---|
| 803 |
try { |
|---|
| 804 |
if (cacheLoaders || smoothContent) { |
|---|
| 805 |
// check crossdomain permissions on tiles if we plan to access their bitmap content |
|---|
| 806 |
tileLoader.load((url is URLRequest) ? url : new URLRequest(url), new LoaderContext(true)); |
|---|
| 807 |
} |
|---|
| 808 |
else { |
|---|
| 809 |
tileLoader.load((url is URLRequest) ? url : new URLRequest(url)); |
|---|
| 810 |
} |
|---|
| 811 |
tileLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadEnd, false, 0, true); |
|---|
| 812 |
tileLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError, false, 0, true); |
|---|
| 813 |
openRequests.push(tileLoader); |
|---|
| 814 |
} |
|---|
| 815 |
catch(error:Error) { |
|---|
| 816 |
tile.paintError(); |
|---|
| 817 |
} |
|---|
| 818 |
} |
|---|
| 819 |
} |
|---|
| 820 |
else if (urls && urls.length == 0) { |
|---|
| 821 |
if (currentTileZoom-tile.zoom <= maxParentLoad) { |
|---|
| 822 |
tile.show(); |
|---|
| 823 |
} |
|---|
| 824 |
else { |
|---|
| 825 |
tile.showNow(); |
|---|
| 826 |
} |
|---|
| 827 |
tileCache.putTile(tile); |
|---|
| 828 |
delete layersNeeded[tile.name]; |
|---|
| 829 |
} |
|---|
| 830 |
} |
|---|
| 831 |
|
|---|
| 832 |
private function zoomThenCenterCompare(t1:Tile, t2:Tile):int |
|---|
| 833 |
{ |
|---|
| 834 |
if (t1.zoom == t2.zoom) { |
|---|
| 835 |
return centerDistanceCompare(t1, t2); |
|---|
| 836 |
} |
|---|
| 837 |
return t1.zoom < t2.zoom ? -1 : t1.zoom > t2.zoom ? 1 : 0; |
|---|
| 838 |
} |
|---|
| 839 |
|
|---|
| 840 |
// for sorting arrays of tiles by distance from center Coordinate |
|---|
| 841 |
private function centerDistanceCompare(t1:Tile, t2:Tile):int |
|---|
| 842 |
{ |
|---|
| 843 |
if (t1.zoom == t2.zoom && t1.zoom == currentTileZoom && t2.zoom == currentTileZoom) { |
|---|
| 844 |
var d1:int = Math.pow(t1.row+0.5-centerRow,2) + Math.pow(t1.column+0.5-centerColumn,2); |
|---|
| 845 |
var d2:int = Math.pow(t2.row+0.5-centerRow,2) + Math.pow(t2.column+0.5-centerColumn,2); |
|---|
| 846 |
return d1 < d2 ? -1 : d1 > d2 ? 1 : 0; |
|---|
| 847 |
} |
|---|
| 848 |
return Math.abs(t1.zoom-currentTileZoom) < Math.abs(t2.zoom-currentTileZoom) ? -1 : 1; |
|---|
| 849 |
} |
|---|
| 850 |
|
|---|
| 851 |
// for sorting arrays of tiles by distance from currentZoom |
|---|
| 852 |
private function distanceFromCurrentZoomCompare(t1:Tile, t2:Tile):int |
|---|
| 853 |
{ |
|---|
| 854 |
var d1:int = Math.abs(t1.zoom-currentTileZoom); |
|---|
| 855 |
var d2:int = Math.abs(t2.zoom-currentTileZoom); |
|---|
| 856 |
return d1 < d2 ? -1 : d1 > d2 ? 1 : zoomCompare(t2, t1); // t2, t1 so that big tiles are on top of small |
|---|
| 857 |
} |
|---|
| 858 |
|
|---|
| 859 |
// for when tiles have same difference in zoom in distanceFromCurrentZoomCompare |
|---|
| 860 |
private static function zoomCompare(t1:Tile, t2:Tile):int |
|---|
| 861 |
{ |
|---|
| 862 |
return t1.zoom == t2.zoom ? 0 : t1.zoom > t2.zoom ? 1 : -1; |
|---|
| 863 |
} |
|---|
| 864 |
|
|---|
| 865 |
// makes sure that if a tile with the given key exists in the cache that it is added to the well and added to visibleTiles |
|---|
| 866 |
// returns null if tile does not exist in cache |
|---|
| 867 |
private function ensureVisible(key:String):Tile |
|---|
| 868 |
{ |
|---|
| 869 |
if (tileCache.containsKey(key)) { |
|---|
| 870 |
var tile:Tile = well.getChildByName(key) as Tile; |
|---|
| 871 |
if (!tile) { |
|---|
| 872 |
tile = tileCache.getTile(key); |
|---|
| 873 |
well.addChildAt(tile,0); |
|---|
| 874 |
if (currentTileZoom-tile.zoom <= maxParentLoad) { |
|---|
| 875 |
tile.show(); |
|---|
| 876 |
} |
|---|
| 877 |
else { |
|---|
| 878 |
tile.showNow(); |
|---|
| 879 |
} |
|---|
| 880 |
} |
|---|
| 881 |
if (visibleTiles.indexOf(tile) < 0) { |
|---|
| 882 |
visibleTiles.push(tile); // don't get rid of it yet! |
|---|
| 883 |
} |
|---|
| 884 |
return tile; |
|---|
| 885 |
} |
|---|
| 886 |
return null; |
|---|
| 887 |
} |
|---|
| 888 |
|
|---|
| 889 |
// for use in requestLoad |
|---|
| 890 |
private var tempCoord:Coordinate = new Coordinate(0,0,0); |
|---|
| 891 |
|
|---|
| 892 |
/** create a tile and add it to the queue - WARNING: this is buggy for the current zoom level, it's only used for parent zooms when maxParentLoad is > 0 */ |
|---|
| 893 |
private function requestLoad(col:int, row:int, zoom:int):Tile |
|---|
| 894 |
{ |
|---|
| 895 |
var key:String = tileKey(col, row, zoom); |
|---|
| 896 |
if (tileCache.containsKey(key)) throw new Error("requested load for an already cached tile"); |
|---|
| 897 |
var tile:Tile = well.getChildByName(key) as Tile; |
|---|
| 898 |
if (!tile) { |
|---|
| 899 |
tile = tilePool.getTile(col, row, zoom); |
|---|
| 900 |
tile.name = key; |
|---|
| 901 |
tempCoord.row = row; |
|---|
| 902 |
tempCoord.column = col; |
|---|
| 903 |
tempCoord.zoom = zoom; |
|---|
| 904 |
var urls:Array = provider.getTileUrls(tempCoord); |
|---|
| 905 |
if (urls && urls.length > 0) { |
|---|
| 906 |
// keep a local copy of the URLs so we don't have to call this twice: |
|---|
| 907 |
layersNeeded[tile.name] = urls; |
|---|
| 908 |
tileQueue.push(tile); |
|---|
| 909 |
} |
|---|
| 910 |
else { |
|---|
| 911 |
// trace("no urls needed for that tile", tempCoord); |
|---|
| 912 |
tile.show(); |
|---|
| 913 |
} |
|---|
| 914 |
well.addChild(tile); |
|---|
| 915 |
} |
|---|
| 916 |
return tile; |
|---|
| 917 |
} |
|---|
| 918 |
|
|---|
| 919 |
private static const zoomLetter:Array = "abcdefghijklmnopqrstuvwxyz".split(''); |
|---|
| 920 |
|
|---|
| 921 |
/** zoom is translated into a letter so that keys can easily be sorted (alphanumerically) by zoom level */ |
|---|
| 922 |
private function tileKey(col:int, row:int, zoom:int):String |
|---|
| 923 |
{ |
|---|
| 924 |
return zoomLetter[zoom]+":"+col+":"+row; |
|---|
| 925 |
} |
|---|
| 926 |
|
|---|
| 927 |
// TODO: check that this does the right thing with negative row/col? |
|---|
| 928 |
private function parentKey(col:int, row:int, zoom:int, parentZoom:int):String |
|---|
| 929 |
{ |
|---|
| 930 |
var scaleFactor:Number = Math.pow(2.0, zoom-parentZoom); |
|---|
| 931 |
var pcol:int = Math.floor(Number(col) / scaleFactor); |
|---|
| 932 |
var prow:int = Math.floor(Number(row) / scaleFactor); |
|---|
| 933 |
return tileKey(pcol,prow,parentZoom); |
|---|
| 934 |
} |
|---|
| 935 |
|
|---|
| 936 |
// used when maxParentLoad is > 0 |
|---|
| 937 |
// TODO: check that this does the right thing with negative row/col? |
|---|
| 938 |
private function parentCoord(col:int, row:int, zoom:int, parentZoom:int):Array |
|---|
| 939 |
{ |
|---|
| 940 |
var scaleFactor:Number = Math.pow(2.0, zoom-parentZoom); |
|---|
| 941 |
var pcol:int = Math.floor(Number(col) / scaleFactor); |
|---|
| 942 |
var prow:int = Math.floor(Number(row) / scaleFactor); |
|---|
| 943 |
return [ pcol, prow ]; |
|---|
| 944 |
} |
|---|
| 945 |
|
|---|
| 946 |
// TODO: check that this does the right thing with negative row/col? |
|---|
| 947 |
private function childKeys(col:int, row:int, zoom:int, childZoom:int):Array |
|---|
| 948 |
{ |
|---|
| 949 |
var scaleFactor:Number = Math.pow(2, zoom-childZoom); // one zoom in = 0.5 |
|---|
| 950 |
var rowColSpan:int = Math.pow(2, childZoom-zoom); // one zoom in = 2, two = 4 |
|---|
| 951 |
var keys:Array = []; |
|---|
| 952 |
for (var ccol:int = col/scaleFactor; ccol < (col/scaleFactor)+rowColSpan; ccol++) { |
|---|
| 953 |
for (var crow:int = row/scaleFactor; crow < (row/scaleFactor)+rowColSpan; crow++) { |
|---|
| 954 |
keys.push(tileKey(ccol, crow, childZoom)); |
|---|
| 955 |
} |
|---|
| 956 |
} |
|---|
| 957 |
return keys; |
|---|
| 958 |
} |
|---|
| 959 |
|
|---|
| 960 |
private function onLoadEnd(event:Event):void |
|---|
| 961 |
{ |
|---|
| 962 |
var loader:Loader = (event.target as LoaderInfo).loader; |
|---|
| 963 |
|
|---|
| 964 |
if (cacheLoaders && !loaderCache[loader.contentLoaderInfo.url]) { |
|---|
| 965 |
//trace('caching content for', loader.contentLoaderInfo.url); |
|---|
| 966 |
try { |
|---|
| 967 |
var content:Bitmap = loader.content as Bitmap; |
|---|
| 968 |
loaderCache[loader.contentLoaderInfo.url] = content; |
|---|
| 969 |
cachedUrls.push(loader.contentLoaderInfo.url); |
|---|
| 970 |
if (cachedUrls.length > maxLoaderCacheSize) { |
|---|
| 971 |
delete loaderCache[cachedUrls.shift()]; |
|---|
| 972 |
} |
|---|
| 973 |
} |
|---|
| 974 |
catch (error:Error) { |
|---|
| 975 |
// ??? |
|---|
| 976 |
} |
|---|
| 977 |
} |
|---|
| 978 |
|
|---|
| 979 |
if (smoothContent) { |
|---|
| 980 |
try { |
|---|
| 981 |
var smoothContent:Bitmap = loader.content as Bitmap; |
|---|
| 982 |
smoothContent.smoothing = true; |
|---|
| 983 |
} |
|---|
| 984 |
catch (error:Error) { |
|---|
| 985 |
// ??? |
|---|
| 986 |
} |
|---|
| 987 |
} |
|---|
| 988 |
|
|---|
| 989 |
// tidy up the request monitor |
|---|
| 990 |
var index:int = openRequests.indexOf(loader); |
|---|
| 991 |
if (index >= 0) { |
|---|
| 992 |
openRequests.splice(index,1); |
|---|
| 993 |
} |
|---|
| 994 |
|
|---|
| 995 |
var tile:Tile = well.getChildByName(loader.name) as Tile; |
|---|
| 996 |
if (tile) { |
|---|
| 997 |
tile.addChild(loader); |
|---|
| 998 |
loadNextURLForTile(tile); |
|---|
| 999 |
} |
|---|
| 1000 |
else { |
|---|
| 1001 |
// we've loaded an image, but its parent tile has been removed |
|---|
| 1002 |
// so we'll have to throw it away |
|---|
| 1003 |
} |
|---|
| 1004 |
} |
|---|
| 1005 |
|
|---|
| 1006 |
private function onLoadError(event:IOErrorEvent):void |
|---|
| 1007 |
{ |
|---|
| 1008 |
var loaderInfo:LoaderInfo = event.target as LoaderInfo; |
|---|
| 1009 |
for (var i:int = openRequests.length-1; i >= 0; i--) { |
|---|
| 1010 |
var loader:Loader = openRequests[i] as Loader; |
|---|
| 1011 |
if (loader.contentLoaderInfo == loaderInfo) { |
|---|
| 1012 |
openRequests.splice(i,1); |
|---|
| 1013 |
var tile:Tile = well.getChildByName(loader.name) as Tile; |
|---|
| 1014 |
if (tile) { |
|---|
| 1015 |
delete layersNeeded[tile.name]; |
|---|
| 1016 |
tile.paintError(tileWidth, tileHeight); |
|---|
| 1017 |
if (currentTileZoom-tile.zoom <= maxParentLoad) { |
|---|
| 1018 |
tile.show(); |
|---|
| 1019 |
} |
|---|
| 1020 |
else { |
|---|
| 1021 |
tile.showNow(); |
|---|
| 1022 |
} |
|---|
| 1023 |
} |
|---|
| 1024 |
break; |
|---|
| 1025 |
} |
|---|
| 1026 |
} |
|---|
| 1027 |
} |
|---|
| 1028 |
|
|---|
| 1029 |
public function mousePressed(event:MouseEvent):void |
|---|
| 1030 |
{ |
|---|
| 1031 |
prepareForPanning(true); |
|---|
| 1032 |
pmouse = new Point(event.stageX, event.stageY); |
|---|
| 1033 |
stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseDragged); |
|---|
| 1034 |
stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased); |
|---|
| 1035 |
stage.addEventListener(Event.MOUSE_LEAVE, mouseReleased); |
|---|
| 1036 |
} |
|---|
| 1037 |
|
|---|
| 1038 |
public function mouseReleased(event:Event):void |
|---|
| 1039 |
{ |
|---|
| 1040 |
stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseDragged); |
|---|
| 1041 |
stage.removeEventListener(MouseEvent.MOUSE_UP, mouseReleased); |
|---|
| 1042 |
stage.removeEventListener(Event.MOUSE_LEAVE, mouseReleased); |
|---|
| 1043 |
donePanning(); |
|---|
| 1044 |
dirty = true; |
|---|
| 1045 |
if (event is MouseEvent) { |
|---|
| 1046 |
MouseEvent(event).updateAfterEvent(); |
|---|
| 1047 |
} |
|---|
| 1048 |
else if (event.type == Event.MOUSE_LEAVE) { |
|---|
| 1049 |
onRender(); |
|---|
| 1050 |
} |
|---|
| 1051 |
} |
|---|
| 1052 |
|
|---|
| 1053 |
public function mouseDragged(event:MouseEvent):void |
|---|
| 1054 |
{ |
|---|
| 1055 |
var mousePoint:Point = new Point(event.stageX, event.stageY); |
|---|
| 1056 |
tx += mousePoint.x - pmouse.x; |
|---|
| 1057 |
ty += mousePoint.y - pmouse.y; |
|---|
| 1058 |
pmouse = mousePoint; |
|---|
| 1059 |
dirty = true; |
|---|
| 1060 |
event.updateAfterEvent(); |
|---|
| 1061 |
} |
|---|
| 1062 |
|
|---|
| 1063 |
// today is all about lazy evaluation |
|---|
| 1064 |
// this gets set to null by 'dirty = true' |
|---|
| 1065 |
// and only calculated again if you need it |
|---|
| 1066 |
protected function get invertedMatrix():Matrix |
|---|
| 1067 |
{ |
|---|
| 1068 |
if (!_invertedMatrix) { |
|---|
| 1069 |
_invertedMatrix = worldMatrix.clone(); |
|---|
| 1070 |
_invertedMatrix.invert(); |
|---|
| 1071 |
_invertedMatrix.scale(scale/tileWidth, scale/tileHeight); |
|---|
| 1072 |
} |
|---|
| 1073 |
return _invertedMatrix; |
|---|
| 1074 |
} |
|---|
| 1075 |
|
|---|
| 1076 |
/** derived from map provider by calculateBounds(), read-only here for convenience */ |
|---|
| 1077 |
public function get minZoom():Number |
|---|
| 1078 |
{ |
|---|
| 1079 |
return _minZoom; |
|---|
| 1080 |
} |
|---|
| 1081 |
/** derived from map provider by calculateBounds(), read-only here for convenience */ |
|---|
| 1082 |
public function get maxZoom():Number |
|---|
| 1083 |
{ |
|---|
| 1084 |
return _maxZoom; |
|---|
| 1085 |
} |
|---|
| 1086 |
|
|---|
| 1087 |
/** convenience method for tileWidth */ |
|---|
| 1088 |
public function get tileWidth():Number |
|---|
| 1089 |
{ |
|---|
| 1090 |
return _tileWidth; |
|---|
| 1091 |
} |
|---|
| 1092 |
/** convenience method for tileHeight */ |
|---|
| 1093 |
public function get tileHeight():Number |
|---|
| 1094 |
{ |
|---|
| 1095 |
return _tileHeight; |
|---|
| 1096 |
} |
|---|
| 1097 |
|
|---|
| 1098 |
/** read-only, this is the level of tiles we'll be loading first */ |
|---|
| 1099 |
public function get currentTileZoom():Number |
|---|
| 1100 |
{ |
|---|
| 1101 |
return _currentTileZoom; |
|---|
| 1102 |
} |
|---|
| 1103 |
|
|---|
| 1104 |
|
|---|
| 1105 |
public function get topLeftCoordinate():Coordinate |
|---|
| 1106 |
{ |
|---|
| 1107 |
if (!_topLeftCoordinate) { |
|---|
| 1108 |
var tl:Point = invertedMatrix.transformPoint(new Point()); |
|---|
| 1109 |
_topLeftCoordinate = new Coordinate(tl.y, tl.x, zoomLevel); |
|---|
| 1110 |
} |
|---|
| 1111 |
return _topLeftCoordinate; |
|---|
| 1112 |
} |
|---|
| 1113 |
|
|---|
| 1114 |
public function get bottomRightCoordinate():Coordinate |
|---|
| 1115 |
{ |
|---|
| 1116 |
if (!_bottomRightCoordinate) { |
|---|
| 1117 |
var br:Point = invertedMatrix.transformPoint(new Point(mapWidth, mapHeight)); |
|---|
| 1118 |
_bottomRightCoordinate = new Coordinate(br.y, br.x, zoomLevel); |
|---|
| 1119 |
} |
|---|
| 1120 |
return _bottomRightCoordinate; |
|---|
| 1121 |
} |
|---|
| 1122 |
|
|---|
| 1123 |
public function get centerCoordinate():Coordinate |
|---|
| 1124 |
{ |
|---|
| 1125 |
if (!_centerCoordinate) { |
|---|
| 1126 |
var c:Point = invertedMatrix.transformPoint(new Point(mapWidth/2, mapHeight/2)); |
|---|
| 1127 |
_centerCoordinate = new Coordinate(c.y, c.x, zoomLevel); |
|---|
| 1128 |
} |
|---|
| 1129 |
return _centerCoordinate; |
|---|
| 1130 |
} |
|---|
| 1131 |
|
|---|
| 1132 |
public function coordinatePoint(coord:Coordinate, context:DisplayObject=null):Point |
|---|
| 1133 |
{ |
|---|
| 1134 |
// this is the same as coord.zoomTo, but doesn't make a new Coordinate: |
|---|
| 1135 |
var zoomFactor:Number = Math.pow(2, zoomLevel - coord.zoom); |
|---|
| 1136 |
//zoomFactor *= tileWidth/scale; |
|---|
| 1137 |
var zoomedColumn:Number = coord.column * zoomFactor; |
|---|
| 1138 |
var zoomedRow:Number = coord.row * zoomFactor; |
|---|
| 1139 |
|
|---|
| 1140 |
var tl:Coordinate = topLeftCoordinate; |
|---|
| 1141 |
var br:Coordinate = bottomRightCoordinate; |
|---|
| 1142 |
|
|---|
| 1143 |
var cols:Number = br.column - tl.column; |
|---|
| 1144 |
var rows:Number = br.row - tl.row; |
|---|
| 1145 |
|
|---|
| 1146 |
var screenPoint:Point = new Point(mapWidth * (zoomedColumn-tl.column) / cols, mapHeight * (zoomedRow-tl.row) / rows); |
|---|
| 1147 |
|
|---|
| 1148 |
//var screenPoint:Point = worldMatrix.transformPoint(new Point(zoomedColumn, zoomedRow)); |
|---|
| 1149 |
|
|---|
| 1150 |
if (context && context != this) |
|---|
| 1151 |
{ |
|---|
| 1152 |
screenPoint = this.parent.localToGlobal(screenPoint); |
|---|
| 1153 |
screenPoint = context.globalToLocal(screenPoint); |
|---|
| 1154 |
} |
|---|
| 1155 |
|
|---|
| 1156 |
return screenPoint; |
|---|
| 1157 |
} |
|---|
| 1158 |
public function pointCoordinate(point:Point, context:DisplayObject=null):Coordinate |
|---|
| 1159 |
{ |
|---|
| 1160 |
if (context && context != this) |
|---|
| 1161 |
{ |
|---|
| 1162 |
point = context.localToGlobal(point); |
|---|
| 1163 |
point = this.globalToLocal(point); |
|---|
| 1164 |
} |
|---|
| 1165 |
|
|---|
| 1166 |
var p:Point = invertedMatrix.transformPoint(point); |
|---|
| 1167 |
return new Coordinate(p.y, p.x, zoomLevel); |
|---|
| 1168 |
} |
|---|
| 1169 |
|
|---|
| 1170 |
public function prepareForPanning(dragging:Boolean=false):void |
|---|
| 1171 |
{ |
|---|
| 1172 |
if (panning) { |
|---|
| 1173 |
donePanning(); |
|---|
| 1174 |
} |
|---|
| 1175 |
if (!dragging && draggable) { |
|---|
| 1176 |
if (hasEventListener(MouseEvent.MOUSE_DOWN)) { |
|---|
| 1177 |
removeEventListener(MouseEvent.MOUSE_DOWN, mousePressed, true); |
|---|
| 1178 |
} |
|---|
| 1179 |
} |
|---|
| 1180 |
startPan = centerCoordinate.copy(); |
|---|
| 1181 |
panning = true; |
|---|
| 1182 |
onStartPanning(); |
|---|
| 1183 |
} |
|---|
| 1184 |
|
|---|
| 1185 |
protected function onStartPanning():void |
|---|
| 1186 |
{ |
|---|
| 1187 |
dispatchEvent(new MapEvent(MapEvent.START_PANNING)); |
|---|
| 1188 |
} |
|---|
| 1189 |
|
|---|
| 1190 |
public function donePanning():void |
|---|
| 1191 |
{ |
|---|
| 1192 |
if (draggable) { |
|---|
| 1193 |
if (!hasEventListener(MouseEvent.MOUSE_DOWN)) { |
|---|
| 1194 |
addEventListener(MouseEvent.MOUSE_DOWN, mousePressed, true); |
|---|
| 1195 |
} |
|---|
| 1196 |
} |
|---|
| 1197 |
startPan = null; |
|---|
| 1198 |
panning = false; |
|---|
| 1199 |
onStopPanning(); |
|---|
| 1200 |
} |
|---|
| 1201 |
|
|---|
| 1202 |
protected function onStopPanning():void |
|---|
| 1203 |
{ |
|---|
| 1204 |
dispatchEvent(new MapEvent(MapEvent.STOP_PANNING)); |
|---|
| 1205 |
} |
|---|
| 1206 |
|
|---|
| 1207 |
public function prepareForZooming():void |
|---|
| 1208 |
{ |
|---|
| 1209 |
if (startZoom >= 0) { |
|---|
| 1210 |
doneZooming(); |
|---|
| 1211 |
} |
|---|
| 1212 |
|
|---|
| 1213 |
startZoom = zoomLevel; |
|---|
| 1214 |
zooming = true; |
|---|
| 1215 |
onStartZooming(); |
|---|
| 1216 |
} |
|---|
| 1217 |
|
|---|
| 1218 |
protected function onStartZooming():void |
|---|
| 1219 |
{ |
|---|
| 1220 |
dispatchEvent(new MapEvent(MapEvent.START_ZOOMING, startZoom)); |
|---|
| 1221 |
} |
|---|
| 1222 |
|
|---|
| 1223 |
public function doneZooming():void |
|---|
| 1224 |
{ |
|---|
| 1225 |
onStopZooming(); |
|---|
| 1226 |
startZoom = -1; |
|---|
| 1227 |
zooming = false; |
|---|
| 1228 |
} |
|---|
| 1229 |
|
|---|
| 1230 |
protected function onStopZooming():void |
|---|
| 1231 |
{ |
|---|
| 1232 |
var event:MapEvent = new MapEvent(MapEvent.STOP_ZOOMING, zoomLevel); |
|---|
| 1233 |
event.zoomDelta = zoomLevel - startZoom; |
|---|
| 1234 |
dispatchEvent(event); |
|---|
| 1235 |
} |
|---|
| 1236 |
|
|---|
| 1237 |
public function resetTiles(coord:Coordinate):void |
|---|
| 1238 |
{ |
|---|
| 1239 |
var sc:Number = Math.pow(2, coord.zoom); |
|---|
| 1240 |
|
|---|
| 1241 |
worldMatrix.identity(); |
|---|
| 1242 |
worldMatrix.scale(sc, sc); |
|---|
| 1243 |
worldMatrix.translate(mapWidth/2, mapHeight/2 ); |
|---|
| 1244 |
worldMatrix.translate(-tileWidth*coord.column, -tileHeight*coord.row); |
|---|
| 1245 |
|
|---|
| 1246 |
// reset the inverted matrix, request a redraw, etc. |
|---|
| 1247 |
dirty = true; |
|---|
| 1248 |
} |
|---|
| 1249 |
|
|---|
| 1250 |
public function get zoomLevel():Number |
|---|
| 1251 |
{ |
|---|
| 1252 |
return Math.log(scale) / Math.LN2; |
|---|
| 1253 |
} |
|---|
| 1254 |
|
|---|
| 1255 |
public function set zoomLevel(n:Number):void |
|---|
| 1256 |
{ |
|---|
| 1257 |
if (zoomLevel != n) |
|---|
| 1258 |
{ |
|---|
| 1259 |
scale = Math.pow(2, n); |
|---|
| 1260 |
} |
|---|
| 1261 |
} |
|---|
| 1262 |
|
|---|
| 1263 |
public function get scale():Number |
|---|
| 1264 |
{ |
|---|
| 1265 |
return Math.sqrt(worldMatrix.a * worldMatrix.a + worldMatrix.b * worldMatrix.b); |
|---|
| 1266 |
} |
|---|
| 1267 |
|
|---|
| 1268 |
public function set scale(n:Number):void |
|---|
| 1269 |
{ |
|---|
| 1270 |
if (scale != n) |
|---|
| 1271 |
{ |
|---|
| 1272 |
var needsStop:Boolean = false; |
|---|
| 1273 |
if (!zooming) { |
|---|
| 1274 |
prepareForZooming(); |
|---|
| 1275 |
needsStop = true; |
|---|
| 1276 |
} |
|---|
| 1277 |
|
|---|
| 1278 |
var sc:Number = n / scale; |
|---|
| 1279 |
worldMatrix.translate(-mapWidth/2, -mapHeight/2); |
|---|
| 1280 |
worldMatrix.scale(sc, sc); |
|---|
| 1281 |
worldMatrix.translate(mapWidth/2, mapHeight/2); |
|---|
| 1282 |
|
|---|
| 1283 |
dirty = true; |
|---|
| 1284 |
|
|---|
| 1285 |
if (needsStop) { |
|---|
| 1286 |
doneZooming(); |
|---|
| 1287 |
} |
|---|
| 1288 |
} |
|---|
| 1289 |
} |
|---|
| 1290 |
|
|---|
| 1291 |
public function resizeTo(p:Point):void |
|---|
| 1292 |
{ |
|---|
| 1293 |
if (mapWidth != p.x || mapHeight != p.y) |
|---|
| 1294 |
{ |
|---|
| 1295 |
var dx:Number = p.x - mapWidth; |
|---|
| 1296 |
var dy:Number = p.y - mapHeight; |
|---|
| 1297 |
|
|---|
| 1298 |
// maintain the center point: |
|---|
| 1299 |
tx += dx/2; |
|---|
| 1300 |
ty += dy/2; |
|---|
| 1301 |
|
|---|
| 1302 |
mapWidth = p.x; |
|---|
| 1303 |
mapHeight = p.y; |
|---|
| 1304 |
scrollRect = new Rectangle(0, 0, mapWidth, mapHeight); |
|---|
| 1305 |
|
|---|
| 1306 |
debugField.x = mapWidth - debugField.width - 15; |
|---|
| 1307 |
debugField.y = mapHeight - debugField.height - 15; |
|---|
| 1308 |
|
|---|
| 1309 |
dirty = true; |
|---|
| 1310 |
|
|---|
| 1311 |
// force this but only for onResize |
|---|
| 1312 |
onRender(); |
|---|
| 1313 |
} |
|---|
| 1314 |
|
|---|
| 1315 |
// this makes sure the well is clickable even without tiles |
|---|
| 1316 |
well.graphics.clear(); |
|---|
| 1317 |
well.graphics.beginFill(0x000000, 0); |
|---|
| 1318 |
well.graphics.drawRect(0, 0, mapWidth, mapHeight); |
|---|
| 1319 |
well.graphics.endFill(); |
|---|
| 1320 |
} |
|---|
| 1321 |
|
|---|
| 1322 |
public function setMapProvider(provider:IMapProvider):void |
|---|
| 1323 |
{ |
|---|
| 1324 |
this.provider = provider; |
|---|
| 1325 |
|
|---|
| 1326 |
_tileWidth = provider.tileWidth; |
|---|
| 1327 |
_tileHeight = provider.tileHeight; |
|---|
| 1328 |
|
|---|
| 1329 |
calculateBounds(); |
|---|
| 1330 |
|
|---|
| 1331 |
clearEverything(); |
|---|
| 1332 |
} |
|---|
| 1333 |
|
|---|
| 1334 |
protected function clearEverything():void |
|---|
| 1335 |
{ |
|---|
| 1336 |
while (well.numChildren > 0) { |
|---|
| 1337 |
var tile:Tile = well.removeChildAt(0) as Tile; |
|---|
| 1338 |
if (!tileCache.containsKey(tile.name)) { |
|---|
| 1339 |
delete layersNeeded[tile.name]; |
|---|
| 1340 |
tilePool.returnTile(tile); |
|---|
| 1341 |
} |
|---|
| 1342 |
} |
|---|
| 1343 |
|
|---|
| 1344 |
for each (var loader:Loader in openRequests) { |
|---|
| 1345 |
try { |
|---|
| 1346 |
// la la I can't hear you |
|---|
| 1347 |
loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onLoadEnd); |
|---|
| 1348 |
loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, onLoadError); |
|---|
| 1349 |
loader.close(); |
|---|
| 1350 |
} |
|---|
| 1351 |
catch (error:Error) { |
|---|
| 1352 |
// close often doesn't work, no biggie |
|---|
| 1353 |
} |
|---|
| 1354 |
} |
|---|
| 1355 |
openRequests = []; |
|---|
| 1356 |
|
|---|
| 1357 |
for (var key:String in layersNeeded) { |
|---|
| 1358 |
delete layersNeeded[key]; |
|---|
| 1359 |
} |
|---|
| 1360 |
layersNeeded = {}; |
|---|
| 1361 |
|
|---|
| 1362 |
recentlySeen = []; |
|---|
| 1363 |
|
|---|
| 1364 |
tileQueue.clear(); |
|---|
| 1365 |
tileCache.clear(); |
|---|
| 1366 |
|
|---|
| 1367 |
dirty = true; |
|---|
| 1368 |
} |
|---|
| 1369 |
|
|---|
| 1370 |
protected function calculateBounds():void |
|---|
| 1371 |
{ |
|---|
| 1372 |
var limits:Array = provider.outerLimits(); |
|---|
| 1373 |
var tl:Coordinate = limits[0] as Coordinate; |
|---|
| 1374 |
var br:Coordinate = limits[1] as Coordinate; |
|---|
| 1375 |
|
|---|
| 1376 |
_maxZoom = Math.max(tl.zoom, br.zoom); |
|---|
| 1377 |
_minZoom = Math.min(tl.zoom, br.zoom); |
|---|
| 1378 |
|
|---|
| 1379 |
tl = tl.zoomTo(0); |
|---|
| 1380 |
br = br.zoomTo(0); |
|---|
| 1381 |
|
|---|
| 1382 |
minTx = tl.column * tileWidth; |
|---|
| 1383 |
maxTx = br.column * tileWidth; |
|---|
| 1384 |
minTy = tl.row * tileHeight; |
|---|
| 1385 |
maxTy = br.row * tileHeight; |
|---|
| 1386 |
} |
|---|
| 1387 |
|
|---|
| 1388 |
/** called inside of onRender before events are fired |
|---|
| 1389 |
* don't use setters inside of here to correct values otherwise we'll get stuck in a loop! */ |
|---|
| 1390 |
protected function enforceBounds():Boolean |
|---|
| 1391 |
{ |
|---|
| 1392 |
if (!enforceBoundsEnabled) { |
|---|
| 1393 |
return false; |
|---|
| 1394 |
} |
|---|
| 1395 |
|
|---|
| 1396 |
// TODO: should this modify things directly and return true? |
|---|
| 1397 |
if (zoomLevel < minZoom) { |
|---|
| 1398 |
scale = Math.pow(2, minZoom); |
|---|
| 1399 |
} |
|---|
| 1400 |
else if (zoomLevel > maxZoom) { |
|---|
| 1401 |
scale = Math.pow(2, maxZoom); |
|---|
| 1402 |
} |
|---|
| 1403 |
|
|---|
| 1404 |
var touched:Boolean = false; |
|---|
| 1405 |
|
|---|
| 1406 |
/* this is potentially the way to wrap the x position |
|---|
| 1407 |
but all the tiles flash and the values aren't quite right |
|---|
| 1408 |
so wrapping the matrix needs more work :( |
|---|
| 1409 |
|
|---|
| 1410 |
var wrapTx:Number = 256 * scale; |
|---|
| 1411 |
|
|---|
| 1412 |
if (worldMatrix.tx > 0) { |
|---|
| 1413 |
worldMatrix.tx = worldMatrix.tx - wrapTx; |
|---|
| 1414 |
} |
|---|
| 1415 |
else if (worldMatrix.tx < -wrapTx) { |
|---|
| 1416 |
worldMatrix.tx += wrapTx; |
|---|
| 1417 |
} */ |
|---|
| 1418 |
|
|---|
| 1419 |
// to make sure we haven't gone too far |
|---|
| 1420 |
// zoom topLeft and bottomRight coords to 0 |
|---|
| 1421 |
// so that they can be compared against minTx etc. |
|---|
| 1422 |
|
|---|
| 1423 |
var tl:Coordinate = topLeftCoordinate.zoomTo(0); |
|---|
| 1424 |
var br:Coordinate = bottomRightCoordinate.zoomTo(0); |
|---|
| 1425 |
|
|---|
| 1426 |
var leftX:Number = tl.column * tileWidth; |
|---|
| 1427 |
var rightX:Number = br.column * tileHeight; |
|---|
| 1428 |
|
|---|
| 1429 |
if (rightX-leftX > maxTx-minTx) { |
|---|
| 1430 |
worldMatrix.tx = (mapWidth-(minTx+maxTx)*scale)/2; |
|---|
| 1431 |
touched = true; |
|---|
| 1432 |
} |
|---|
| 1433 |
else if (leftX < minTx) { |
|---|
| 1434 |
worldMatrix.tx += (leftX-minTx)*scale; |
|---|
| 1435 |
touched = true; |
|---|
| 1436 |
} |
|---|
| 1437 |
else if (rightX > maxTx) { |
|---|
| 1438 |
worldMatrix.tx += (rightX-maxTx)*scale; |
|---|
| 1439 |
touched = true; |
|---|
| 1440 |
} |
|---|
| 1441 |
|
|---|
| 1442 |
var upY:Number = tl.row * tileHeight; |
|---|
| 1443 |
var downY:Number = br.row * tileWidth; |
|---|
| 1444 |
|
|---|
| 1445 |
if (downY-upY > maxTy-minTy) { |
|---|
| 1446 |
worldMatrix.ty = (mapHeight-(minTy+maxTy)*scale)/2; |
|---|
| 1447 |
touched = true; |
|---|
| 1448 |
} |
|---|
| 1449 |
else if (upY < minTy) { |
|---|
| 1450 |
worldMatrix.ty += (upY-minTy)*scale; |
|---|
| 1451 |
touched = true; |
|---|
| 1452 |
} |
|---|
| 1453 |
else if (downY > maxTy) { |
|---|
| 1454 |
worldMatrix.ty += (downY-maxTy)*scale; |
|---|
| 1455 |
touched = true; |
|---|
| 1456 |
} |
|---|
| 1457 |
|
|---|
| 1458 |
if (touched) { |
|---|
| 1459 |
_invertedMatrix = null; |
|---|
| 1460 |
_topLeftCoordinate = null; |
|---|
| 1461 |
_bottomRightCoordinate = null; |
|---|
| 1462 |
_centerCoordinate = null; |
|---|
| 1463 |
} |
|---|
| 1464 |
|
|---|
| 1465 |
return touched; |
|---|
| 1466 |
} |
|---|
| 1467 |
|
|---|
| 1468 |
protected function set dirty(d:Boolean):void |
|---|
| 1469 |
{ |
|---|
| 1470 |
_dirty = d; |
|---|
| 1471 |
if (d) { |
|---|
| 1472 |
if (stage) stage.invalidate(); |
|---|
| 1473 |
|
|---|
| 1474 |
_invertedMatrix = null; |
|---|
| 1475 |
_topLeftCoordinate = null; |
|---|
| 1476 |
_bottomRightCoordinate = null; |
|---|
| 1477 |
_centerCoordinate = null; |
|---|
| 1478 |
} |
|---|
| 1479 |
} |
|---|
| 1480 |
|
|---|
| 1481 |
protected function get dirty():Boolean |
|---|
| 1482 |
{ |
|---|
| 1483 |
return _dirty; |
|---|
| 1484 |
} |
|---|
| 1485 |
|
|---|
| 1486 |
public function getMatrix():Matrix |
|---|
| 1487 |
{ |
|---|
| 1488 |
return worldMatrix.clone(); |
|---|
| 1489 |
} |
|---|
| 1490 |
|
|---|
| 1491 |
public function setMatrix(m:Matrix):void |
|---|
| 1492 |
{ |
|---|
| 1493 |
worldMatrix = m; |
|---|
| 1494 |
matrixChanged = true; |
|---|
| 1495 |
dirty = true; |
|---|
| 1496 |
} |
|---|
| 1497 |
|
|---|
| 1498 |
public function get a():Number |
|---|
| 1499 |
{ |
|---|
| 1500 |
return worldMatrix.a; |
|---|
| 1501 |
} |
|---|
| 1502 |
public function get b():Number |
|---|
| 1503 |
{ |
|---|
| 1504 |
return worldMatrix.b; |
|---|
| 1505 |
} |
|---|
| 1506 |
public function get c():Number |
|---|
| 1507 |
{ |
|---|
| 1508 |
return worldMatrix.c; |
|---|
| 1509 |
} |
|---|
| 1510 |
public function get d():Number |
|---|
| 1511 |
{ |
|---|
| 1512 |
return worldMatrix.d; |
|---|
| 1513 |
} |
|---|
| 1514 |
public function get tx():Number |
|---|
| 1515 |
{ |
|---|
| 1516 |
return worldMatrix.tx; |
|---|
| 1517 |
} |
|---|
| 1518 |
public function get ty():Number |
|---|
| 1519 |
{ |
|---|
| 1520 |
return worldMatrix.ty; |
|---|
| 1521 |
} |
|---|
| 1522 |
|
|---|
| 1523 |
public function set a(n:Number):void |
|---|
| 1524 |
{ |
|---|
| 1525 |
worldMatrix.a = n; |
|---|
| 1526 |
dirty = true; |
|---|
| 1527 |
} |
|---|
| 1528 |
public function set b(n:Number):void |
|---|
| 1529 |
{ |
|---|
| 1530 |
worldMatrix.b = n; |
|---|
| 1531 |
dirty = true; |
|---|
| 1532 |
} |
|---|
| 1533 |
public function set c(n:Number):void |
|---|
| 1534 |
{ |
|---|
| 1535 |
worldMatrix.c = n; |
|---|
| 1536 |
dirty = true; |
|---|
| 1537 |
} |
|---|
| 1538 |
public function set d(n:Number):void |
|---|
| 1539 |
{ |
|---|
| 1540 |
worldMatrix.d = n; |
|---|
| 1541 |
dirty = true; |
|---|
| 1542 |
} |
|---|
| 1543 |
public function set tx(n:Number):void |
|---|
| 1544 |
{ |
|---|
| 1545 |
worldMatrix.tx = n; |
|---|
| 1546 |
dirty = true; |
|---|
| 1547 |
} |
|---|
| 1548 |
public function set ty(n:Number):void |
|---|
| 1549 |
{ |
|---|
| 1550 |
worldMatrix.ty = n; |
|---|
| 1551 |
dirty = true; |
|---|
| 1552 |
} |
|---|
| 1553 |
|
|---|
| 1554 |
} |
|---|
| 1555 |
|
|---|
| 1556 |
} |
|---|
| 1557 |
|
|---|
| 1558 |
import com.modestmaps.core.Tile; |
|---|
| 1559 |
import flash.utils.Dictionary; |
|---|
| 1560 |
import com.modestmaps.Map; |
|---|
| 1561 |
|
|---|
| 1562 |
class TileQueue |
|---|
| 1563 |
{ |
|---|
| 1564 |
// Tiles we want to load: |
|---|
| 1565 |
protected var queue:Array; |
|---|
| 1566 |
|
|---|
| 1567 |
public function TileQueue() |
|---|
| 1568 |
{ |
|---|
| 1569 |
queue = []; |
|---|
| 1570 |
} |
|---|
| 1571 |
|
|---|
| 1572 |
public function get length():Number |
|---|
| 1573 |
{ |
|---|
| 1574 |
return queue.length; |
|---|
| 1575 |
} |
|---|
| 1576 |
|
|---|
| 1577 |
public function contains(tile:Tile):Boolean |
|---|
| 1578 |
{ |
|---|
| 1579 |
return queue.indexOf(tile) >= 0; |
|---|
| 1580 |
} |
|---|
| 1581 |
|
|---|
| 1582 |
public function remove(tile:Tile):void |
|---|
| 1583 |
{ |
|---|
| 1584 |
var index:int = queue.indexOf(tile); |
|---|
| 1585 |
if (index >= 0) { |
|---|
| 1586 |
queue.splice(index, 1); |
|---|
| 1587 |
} |
|---|
| 1588 |
} |
|---|
| 1589 |
|
|---|
| 1590 |
public function push(tile:Tile):void |
|---|
| 1591 |
{ |
|---|
| 1592 |
if (contains(tile)) { |
|---|
| 1593 |
throw new Error("that tile is already on the queue!"); |
|---|
| 1594 |
} |
|---|
| 1595 |
queue.push(tile); |
|---|
| 1596 |
} |
|---|
| 1597 |
|
|---|
| 1598 |
public function shift():Tile |
|---|
| 1599 |
{ |
|---|
| 1600 |
return queue.shift() as Tile; |
|---|
| 1601 |
} |
|---|
| 1602 |
|
|---|
| 1603 |
public function sortTiles(callback:Function):void |
|---|
| 1604 |
{ |
|---|
| 1605 |
queue = queue.sort(callback); |
|---|
| 1606 |
} |
|---|
| 1607 |
|
|---|
| 1608 |
public function retainAll(tiles:Array):Array |
|---|
| 1609 |
{ |
|---|
| 1610 |
var removed:Array = []; |
|---|
| 1611 |
for (var i:int = queue.length-1; i >= 0; i--) { |
|---|
| 1612 |
var tile:Tile = queue[i] as Tile; |
|---|
| 1613 |
if (tiles.indexOf(tile) < 0) { |
|---|
| 1614 |
removed.push(tile); |
|---|
| 1615 |
queue.splice(i,1); |
|---|
| 1616 |
} |
|---|
| 1617 |
} |
|---|
| 1618 |
return removed; |
|---|
| 1619 |
} |
|---|
| 1620 |
|
|---|
| 1621 |
public function clear():void |
|---|
| 1622 |
{ |
|---|
| 1623 |
queue = []; |
|---|
| 1624 |
} |
|---|
| 1625 |
|
|---|
| 1626 |
} |
|---|
| 1627 |
|
|---|
| 1628 |
/** the alreadySeen Dictionary here will contain up to grid.maxTilesToKeep Tiles */ |
|---|
| 1629 |
class TileCache |
|---|
| 1630 |
{ |
|---|
| 1631 |
// Tiles we've already seen and fully loaded, by key (.name) |
|---|
| 1632 |
protected var alreadySeen:Dictionary; |
|---|
| 1633 |
protected var tilePool:TilePool; // for handing tiles back! |
|---|
| 1634 |
|
|---|
| 1635 |
public function TileCache(tilePool:TilePool) |
|---|
| 1636 |
{ |
|---|
| 1637 |
this.tilePool = tilePool; |
|---|
| 1638 |
alreadySeen = new Dictionary(); |
|---|
| 1639 |
} |
|---|
| 1640 |
|
|---|
| 1641 |
public function get size():int |
|---|
| 1642 |
{ |
|---|
| 1643 |
var alreadySeenCount:int = 0; |
|---|
| 1644 |
for (var key:* in alreadySeen) { |
|---|
| 1645 |
alreadySeenCount++; |
|---|
| 1646 |
} |
|---|
| 1647 |
return alreadySeenCount; |
|---|
| 1648 |
} |
|---|
| 1649 |
|
|---|
| 1650 |
public function putTile(tile:Tile):void |
|---|
| 1651 |
{ |
|---|
| 1652 |
if (alreadySeen[tile.name]) { |
|---|
| 1653 |
throw new Error("caching a tile that's already cached"); |
|---|
| 1654 |
} |
|---|
| 1655 |
if (tile.numChildren == 0) { |
|---|
| 1656 |
throw new Error("tile added to cache has no children!"); |
|---|
| 1657 |
} |
|---|
| 1658 |
alreadySeen[tile.name] = tile; |
|---|
| 1659 |
} |
|---|
| 1660 |
|
|---|
| 1661 |
public function getTile(key:String):Tile |
|---|
| 1662 |
{ |
|---|
| 1663 |
return alreadySeen[key] as Tile; |
|---|
| 1664 |
} |
|---|
| 1665 |
|
|---|
| 1666 |
public function containsKey(key:String):Boolean |
|---|
| 1667 |
{ |
|---|
| 1668 |
return alreadySeen[key] is Tile; |
|---|
| 1669 |
} |
|---|
| 1670 |
|
|---|
| 1671 |
public function retainKeys(keys:Array):void |
|---|
| 1672 |
{ |
|---|
| 1673 |
for (var key:String in alreadySeen) { |
|---|
| 1674 |
if (keys.indexOf(key) < 0) { |
|---|
| 1675 |
tilePool.returnTile(alreadySeen[key] as Tile); |
|---|
| 1676 |
delete alreadySeen[key]; |
|---|
| 1677 |
} |
|---|
| 1678 |
} |
|---|
| 1679 |
} |
|---|
| 1680 |
|
|---|
| 1681 |
public function clear():void |
|---|
| 1682 |
{ |
|---|
| 1683 |
for (var key:String in alreadySeen) { |
|---|
| 1684 |
tilePool.returnTile(alreadySeen[key] as Tile); |
|---|
| 1685 |
delete alreadySeen[key]; |
|---|
| 1686 |
} |
|---|
| 1687 |
alreadySeen = new Dictionary(); |
|---|
| 1688 |
} |
|---|
| 1689 |
} |
|---|
| 1690 |
|
|---|
| 1691 |
/** |
|---|
| 1692 |
* This post http://lab.polygonal.de/2008/06/18/using-object-pools/ |
|---|
| 1693 |
* suggests that using Object pools, especially for complex classes like Sprite |
|---|
| 1694 |
* is a lot faster than calling new Object(). The suggested implementation |
|---|
| 1695 |
* uses a linked list, but to get started with it here I'm using an Array. |
|---|
| 1696 |
* |
|---|
| 1697 |
* If anyone wants to try it with a linked list and compare the times, |
|---|
| 1698 |
* it seems like it could be worth it :) |
|---|
| 1699 |
*/ |
|---|
| 1700 |
class TilePool |
|---|
| 1701 |
{ |
|---|
| 1702 |
protected static const MIN_POOL_SIZE:int = 256; |
|---|
| 1703 |
protected static const MAX_NEW_TILES:int = 256; |
|---|
| 1704 |
|
|---|
| 1705 |
protected var pool:Array = []; |
|---|
| 1706 |
protected var tileClass:Class; |
|---|
| 1707 |
|
|---|
| 1708 |
public function TilePool(tileClass:Class) |
|---|
| 1709 |
{ |
|---|
| 1710 |
this.tileClass = tileClass; |
|---|
| 1711 |
} |
|---|
| 1712 |
|
|---|
| 1713 |
public function setTileClass(tileClass:Class):void |
|---|
| 1714 |
{ |
|---|
| 1715 |
this.tileClass = tileClass; |
|---|
| 1716 |
pool = []; |
|---|
| 1717 |
} |
|---|
| 1718 |
|
|---|
| 1719 |
public function getTile(column:int, row:int, zoom:int):Tile |
|---|
| 1720 |
{ |
|---|
| 1721 |
if (pool.length < MIN_POOL_SIZE) { |
|---|
| 1722 |
while (pool.length < MAX_NEW_TILES) { |
|---|
| 1723 |
pool.push(new tileClass(0,0,0)); |
|---|
| 1724 |
} |
|---|
| 1725 |
} |
|---|
| 1726 |
var tile:Tile = pool.pop() as Tile; |
|---|
| 1727 |
tile.init(column, row, zoom); |
|---|
| 1728 |
return tile; |
|---|
| 1729 |
} |
|---|
| 1730 |
|
|---|
| 1731 |
public function returnTile(tile:Tile):void |
|---|
| 1732 |
{ |
|---|
| 1733 |
tile.destroy(); |
|---|
| 1734 |
pool.push(tile); |
|---|
| 1735 |
} |
|---|
| 1736 |
|
|---|
| 1737 |
} |
|---|