PageRenderTime 5538ms CodeModel.GetById 24ms RepoModel.GetById 2ms app.codeStats 0ms

/src/com/pblabs/rendering2D/DisplayObjectScene.as

http://github.com/PushButtonLabs/PushButtonEngine
ActionScript | 588 lines | 368 code | 113 blank | 107 comment | 61 complexity | eed062d9855cdc362980073992dc4645 MD5 | raw file
  1. /*******************************************************************************
  2. * PushButton Engine
  3. * Copyright (C) 2009 PushButton Labs, LLC
  4. * For more information see http://www.pushbuttonengine.com
  5. *
  6. * This file is licensed under the terms of the MIT license, which is included
  7. * in the License.html file at the root directory of this SDK.
  8. ******************************************************************************/
  9. package com.pblabs.rendering2D
  10. {
  11. import com.pblabs.engine.PBE;
  12. import com.pblabs.engine.PBUtil;
  13. import com.pblabs.engine.components.AnimatedComponent;
  14. import com.pblabs.engine.core.ObjectType;
  15. import com.pblabs.engine.debug.Logger;
  16. import com.pblabs.rendering2D.ui.IUITarget;
  17. import flash.display.DisplayObject;
  18. import flash.display.Sprite;
  19. import flash.events.Event;
  20. import flash.geom.*;
  21. import flash.utils.Dictionary;
  22. /**
  23. * Basic Rendering2D scene; it is given a SceneView and some
  24. * DisplayObjectRenderers, and makes sure that they are drawn. Extensible
  25. * for more complex rendering scenarios. Enforces sorting order, too.
  26. */
  27. public class DisplayObjectScene extends AnimatedComponent implements IScene2D
  28. {
  29. /**
  30. * Minimum allowed zoom level.
  31. *
  32. * @see zoom
  33. */
  34. public var minZoom:Number = .01;
  35. /**
  36. * Maximum allowed zoom level.
  37. *
  38. * @see zoom
  39. */
  40. public var maxZoom:Number = 1;
  41. /**
  42. * How the scene is aligned relative to its position property.
  43. *
  44. * @see SceneAlignment
  45. * @see position
  46. */
  47. public function set sceneAlignment(value:SceneAlignment):void
  48. {
  49. if (value != _sceneAlignment)
  50. {
  51. _sceneAlignment = value;
  52. _transformDirty = true;
  53. updateTransform();
  54. }
  55. }
  56. /**
  57. * @private
  58. */
  59. public function get sceneAlignment():SceneAlignment
  60. {
  61. return _sceneAlignment;
  62. }
  63. /**
  64. * Holds DisplayObjectSceneLayer instances to use for various layers.
  65. * That is, if index 3 of layers[] holds an instance of DisplayObjectSceneLayer
  66. * or a subclass, then that instance will be used for layer #3.
  67. *
  68. * Note this is only considered at layer setup time. Use getLayer() to
  69. * get a layer that is being actively used.
  70. */
  71. public var layers:Array = [];
  72. /**
  73. * If set, every frame, trackObject's position is read and assigned
  74. * to the scene's position, so that the scene follows the trackObject.
  75. */
  76. public var trackObject:DisplayObjectRenderer;
  77. /**
  78. * An x/y offset for adjusting the camera's focus around the tracked
  79. * object.
  80. *
  81. * Only applies if trackObject is set.
  82. */
  83. public var trackOffset:Point = new Point(0,0);
  84. protected var _sceneView:IUITarget;
  85. protected var _sceneViewName:String = null;
  86. protected var _rootSprite:Sprite;
  87. protected var _layers:Array = [];
  88. protected var _renderers:Dictionary = new Dictionary(true);
  89. protected var _zoom:Number = 1;
  90. protected var _rootPosition:Point = new Point();
  91. protected var _rootRotation:Number = 0;
  92. protected var _rootTransform:Matrix = new Matrix();
  93. protected var _transformDirty:Boolean = true;
  94. protected var _currentWorldCenter:Point = new Point();
  95. protected var _sceneViewBoundsCache:Rectangle = new Rectangle();
  96. protected var _tempPoint:Point = new Point();
  97. protected var _trackLimitRectangle:Rectangle = null;
  98. protected var _sceneAlignment:SceneAlignment = SceneAlignment.DEFAULT_ALIGNMENT;
  99. public function DisplayObjectScene()
  100. {
  101. // Get ticked after all the renderers.
  102. updatePriority = -10;
  103. _rootSprite = generateRootSprite();
  104. }
  105. protected override function onAdd() : void
  106. {
  107. super.onAdd();
  108. // Make sure we start with a correct transform.
  109. _transformDirty = true;
  110. updateTransform();
  111. }
  112. protected override function onRemove() : void
  113. {
  114. super.onRemove();
  115. // Make sure we don't leave any lingering content.
  116. if(_sceneView)
  117. _sceneView.removeDisplayObject(_rootSprite);
  118. }
  119. public function get layerCount():int
  120. {
  121. return _layers.length;
  122. }
  123. public function getLayer(index:int, allocateIfAbsent:Boolean = false):DisplayObjectSceneLayer
  124. {
  125. // Maybe it already exists.
  126. if(_layers[index])
  127. return _layers[index];
  128. if(allocateIfAbsent == false)
  129. return null;
  130. // Allocate the layer.
  131. _layers[index] = generateLayer(index);
  132. // Order the layers. This is suboptimal but we are probably not going
  133. // to be adding a lot of layers all the time.
  134. while(_rootSprite.numChildren)
  135. _rootSprite.removeChildAt(_rootSprite.numChildren-1);
  136. for(var i:int=0; i<layerCount; i++)
  137. {
  138. if (_layers[i])
  139. _rootSprite.addChild(_layers[i]);
  140. }
  141. // Return new layer.
  142. return _layers[index];
  143. }
  144. public function invalidate(dirtyRenderer:DisplayObjectRenderer):void
  145. {
  146. var layerToDirty:DisplayObjectSceneLayer = getLayer(dirtyRenderer.layerIndex);
  147. if(!layerToDirty)
  148. return;
  149. if(layerToDirty is ICachingLayer)
  150. ICachingLayer(layerToDirty).invalidate(dirtyRenderer);
  151. }
  152. public function invalidateRectangle(dirty:Rectangle):void
  153. {
  154. for each(var l:DisplayObjectSceneLayer in _layers)
  155. {
  156. if(l is ICachingLayer)
  157. (l as ICachingLayer).invalidateRectangle(dirty);
  158. }
  159. }
  160. /**
  161. * Convenience function for subclasses to create a custom root sprite.
  162. */
  163. protected function generateRootSprite():Sprite
  164. {
  165. var s:Sprite = new Sprite();
  166. //TODO: set any properties we want for our root host sprite
  167. s.name = "DisplayObjectSceneRoot";
  168. return s;
  169. }
  170. /**
  171. * Convenience funtion for subclasses to control what class of layer
  172. * they are using.
  173. */
  174. protected function generateLayer(layerIndex:int):DisplayObjectSceneLayer
  175. {
  176. var outLayer:DisplayObjectSceneLayer;
  177. // Do we have that layer already specified?
  178. if (layers && layers[layerIndex])
  179. outLayer = layers[layerIndex] as DisplayObjectSceneLayer;
  180. // Go with default.
  181. if (!outLayer)
  182. outLayer = new DisplayObjectSceneLayer();
  183. //TODO: set any properties we want for our layer.
  184. outLayer.name = "Layer" + layerIndex;
  185. return outLayer;
  186. }
  187. public function get sceneView():IUITarget
  188. {
  189. if(!_sceneView && _sceneViewName)
  190. sceneView = PBE.findChild(_sceneViewName) as IUITarget;
  191. return _sceneView;
  192. }
  193. /**
  194. * The IUITarget to which we will be displaying the scene. A scene can
  195. * only draw to on IUITarget at a time.
  196. */
  197. [EditorData(ignore="true")]
  198. public function set sceneView(value:IUITarget):void
  199. {
  200. if(_sceneView)
  201. {
  202. _sceneView.removeDisplayObject(_rootSprite);
  203. }
  204. _sceneView = value;
  205. if(_sceneView)
  206. {
  207. _sceneView.addDisplayObject(_rootSprite);
  208. }
  209. }
  210. public function get sceneViewName():String
  211. {
  212. return _sceneViewName;
  213. }
  214. public function set sceneViewName(value:String):void
  215. {
  216. _sceneViewName = value;
  217. }
  218. public function get sceneViewBounds():Rectangle
  219. {
  220. if(!sceneView)
  221. return null;
  222. // Make sure we are up to date with latest track.
  223. if(trackObject)
  224. {
  225. position = new Point(-(trackObject.position.x + trackOffset.x),
  226. -(trackObject.position.y + trackOffset.y));
  227. }
  228. if(trackLimitRectangle != null)
  229. {
  230. var centeredLimitBounds:Rectangle = new Rectangle( trackLimitRectangle.x + (sceneView.width * 0.5) / zoom, trackLimitRectangle.y + (sceneView.height * 0.5) / zoom,
  231. trackLimitRectangle.width - (sceneView.width / zoom) , trackLimitRectangle.height - (sceneView.height/zoom) );
  232. position = new Point(PBUtil.clamp(position.x, -centeredLimitBounds.right, -centeredLimitBounds.left ),
  233. PBUtil.clamp(position.y, -centeredLimitBounds.bottom, -centeredLimitBounds.top) );
  234. }
  235. updateTransform();
  236. // What region of the scene are we currently viewing?
  237. SceneAlignment.calculate(_tempPoint, sceneAlignment, sceneView.width / zoom, sceneView.height / zoom);
  238. _sceneViewBoundsCache.x = -position.x - _tempPoint.x;
  239. _sceneViewBoundsCache.y = -position.y - _tempPoint.y;
  240. _sceneViewBoundsCache.width = sceneView.width / zoom;
  241. _sceneViewBoundsCache.height = sceneView.height / zoom;
  242. return _sceneViewBoundsCache;
  243. }
  244. protected function sceneViewResized(event:Event) : void
  245. {
  246. _transformDirty = true;
  247. }
  248. /**
  249. * @inheritDoc
  250. */
  251. public function get trackLimitRectangle():Rectangle
  252. {
  253. return _trackLimitRectangle;
  254. }
  255. /**
  256. * @inheritDoc
  257. */
  258. public function set trackLimitRectangle(value:Rectangle):void
  259. {
  260. _trackLimitRectangle = value;
  261. }
  262. public function add(dor:DisplayObjectRenderer):void
  263. {
  264. // Add to the appropriate layer.
  265. var layer:DisplayObjectSceneLayer = getLayer(dor.layerIndex, true);
  266. layer.add(dor);
  267. if (dor.displayObject)
  268. _renderers[dor.displayObject] = dor;
  269. }
  270. public function remove(dor:DisplayObjectRenderer):void
  271. {
  272. var layer:DisplayObjectSceneLayer = getLayer(dor.layerIndex, false);
  273. if(!layer)
  274. return;
  275. layer.remove(dor);
  276. if (dor.displayObject)
  277. delete _renderers[dor.displayObject];
  278. }
  279. public function transformWorldToScene(inPos:Point):Point
  280. {
  281. return inPos;
  282. }
  283. public function transformSceneToWorld(inPos:Point):Point
  284. {
  285. return inPos;
  286. }
  287. public function transformSceneToScreen(inPos:Point):Point
  288. {
  289. updateTransform();
  290. return _rootSprite.localToGlobal(inPos);
  291. }
  292. public function transformScreenToScene(inPos:Point):Point
  293. {
  294. updateTransform();
  295. return _rootSprite.globalToLocal(inPos);
  296. }
  297. public function transformWorldToScreen(inPos:Point):Point
  298. {
  299. updateTransform();
  300. return _rootSprite.localToGlobal(inPos);
  301. }
  302. public function transformScreenToWorld(inPos:Point):Point
  303. {
  304. updateTransform();
  305. return _rootSprite.globalToLocal(inPos);
  306. }
  307. public function getRenderersUnderPoint(screenPosition:Point, results:Array, mask:ObjectType = null):Boolean
  308. {
  309. // Query normal DO hierarchy.
  310. var unfilteredResults:Array = _rootSprite.getObjectsUnderPoint(screenPosition);
  311. var scenePosition:Point = transformScreenToScene(screenPosition);
  312. for each (var o:* in unfilteredResults)
  313. {
  314. var renderer:DisplayObjectRenderer = getRendererForDisplayObject(o);
  315. if(!renderer)
  316. continue;
  317. if(!renderer.owner)
  318. continue;
  319. if(mask && !PBE.objectTypeManager.doTypesOverlap(mask, renderer.objectMask))
  320. continue;
  321. if(!renderer.pointOccupied(scenePosition, mask))
  322. continue;
  323. results.push(renderer);
  324. }
  325. // Also give layers opportunity to return renderers.
  326. scenePosition = transformScreenToScene(screenPosition);
  327. for each(var l:DisplayObjectSceneLayer in _layers)
  328. {
  329. // Skip them if they don't use the interface.
  330. if(!(l is ILayerMouseHandler))
  331. continue;
  332. (l as ILayerMouseHandler).getRenderersUnderPoint(scenePosition, mask, results);
  333. }
  334. return results.length > 0 ? true : false;
  335. }
  336. public function getRendererForDisplayObject(displayObject:DisplayObject):DisplayObjectRenderer
  337. {
  338. var current:DisplayObject = displayObject;
  339. // Walk up the display tree looking for a DO we know about.
  340. while (current)
  341. {
  342. // See if it's a DOR.
  343. var renderer:DisplayObjectRenderer = _renderers[current] as DisplayObjectRenderer;
  344. if (renderer)
  345. return renderer;
  346. // If we get to a layer, we know we're done.
  347. if(renderer is DisplayObjectSceneLayer)
  348. return null;
  349. // Go up the tree..
  350. current = current.parent;
  351. }
  352. // No match!
  353. return null;
  354. }
  355. public function updateTransform():void
  356. {
  357. if(!sceneView)
  358. return;
  359. if(_transformDirty == false)
  360. return;
  361. _transformDirty = false;
  362. // Update our transform, if required
  363. _rootTransform.identity();
  364. _rootTransform.translate(_rootPosition.x, _rootPosition.y);
  365. _rootTransform.scale(zoom, zoom);
  366. // Apply rotation.
  367. _rootTransform.rotate(_rootRotation);
  368. // Center it appropriately.
  369. SceneAlignment.calculate(_tempPoint, sceneAlignment, sceneView.width, sceneView.height);
  370. _rootTransform.translate(_tempPoint.x, _tempPoint.y);
  371. // Apply the transform.
  372. _rootSprite.transform.matrix = _rootTransform;
  373. }
  374. public override function onFrame(elapsed:Number) : void
  375. {
  376. if(!sceneView)
  377. {
  378. Logger.warn(this, "updateTransform", "sceneView is null, so we aren't rendering.");
  379. return;
  380. }
  381. // Update our state based on the tracked object, if any.
  382. if(trackObject)
  383. {
  384. position = new Point(-(trackObject.position.x + trackOffset.x),
  385. -(trackObject.position.y + trackOffset.y));
  386. }
  387. // Apply limit to camera movement.
  388. if(trackLimitRectangle != null)
  389. {
  390. var centeredLimitBounds:Rectangle = new Rectangle( trackLimitRectangle.x + sceneView.width * 0.5, trackLimitRectangle.y + sceneView.height * 0.5,
  391. trackLimitRectangle.width - sceneView.width , trackLimitRectangle.height - sceneView.height );
  392. position = new Point(PBUtil.clamp(position.x, -centeredLimitBounds.right, -centeredLimitBounds.left ),
  393. PBUtil.clamp(position.y, -centeredLimitBounds.bottom, -centeredLimitBounds.top) );
  394. }
  395. // Make sure transforms are up to date.
  396. updateTransform();
  397. // This is disabled, because it causes everything in the screen
  398. // to invalidate and redraw.
  399. //PBE.pushStageQuality(StageQuality.LOW);
  400. // Give layers a chance to sort and update.
  401. for each(var l:DisplayObjectSceneLayer in _layers)
  402. l.onRender();
  403. //PBE.pushStageQuality(StageQuality.HIGH);
  404. }
  405. public function setWorldCenter(pos:Point):void
  406. {
  407. if (!sceneView)
  408. throw new Error("sceneView not yet set. can't center the world.");
  409. position = transformWorldToScreen(pos);
  410. }
  411. public function screenPan(deltaX:int, deltaY:int):void
  412. {
  413. if(deltaX == 0 && deltaY == 0)
  414. return;
  415. // TODO: Take into account rotation so it's correct even when
  416. // rotating.
  417. _rootPosition.x -= int(deltaX / _zoom);
  418. _rootPosition.y -= int(deltaY / _zoom);
  419. _transformDirty = true;
  420. }
  421. public function get rotation():Number
  422. {
  423. return _rootRotation;
  424. }
  425. public function set rotation(value:Number):void
  426. {
  427. if (_rootRotation != value)
  428. {
  429. _rootRotation = value;
  430. _transformDirty = true;
  431. }
  432. }
  433. public function get position():Point
  434. {
  435. return _rootPosition.clone();
  436. }
  437. public function set position(value:Point) : void
  438. {
  439. if (!value)
  440. return;
  441. var newX:int = int(value.x);
  442. var newY:int = int(value.y);
  443. if (_rootPosition.x == newX && _rootPosition.y == newY)
  444. return;
  445. _rootPosition.x = newX;
  446. _rootPosition.y = newY;
  447. // Apply limit to camera movement.
  448. if(trackLimitRectangle != null)
  449. {
  450. var centeredLimitBounds:Rectangle = new Rectangle( trackLimitRectangle.x + (sceneView.width * 0.5) / zoom, trackLimitRectangle.y + (sceneView.height * 0.5) / zoom,
  451. trackLimitRectangle.width - (sceneView.width / zoom) , trackLimitRectangle.height - (sceneView.height/zoom) );
  452. _rootPosition.x = PBUtil.clamp(_rootPosition.x, -centeredLimitBounds.right, -centeredLimitBounds.left );
  453. _rootPosition.y = PBUtil.clamp(_rootPosition.y, -centeredLimitBounds.bottom, -centeredLimitBounds.top);
  454. }
  455. _transformDirty = true;
  456. }
  457. public function get zoom():Number
  458. {
  459. return _zoom;
  460. }
  461. public function set zoom(value:Number):void
  462. {
  463. // Make sure our zoom level stays within the desired bounds
  464. value = PBUtil.clamp(value, minZoom, maxZoom);
  465. if (_zoom == value)
  466. return;
  467. _zoom = value;
  468. _transformDirty = true;
  469. }
  470. public function sortSpatials(array:Array):void
  471. {
  472. // Subclasses can set how things are sorted.
  473. }
  474. }
  475. }