diff --git a/src/as/com/threerings/miso/client/BaseTileIsoSprite.as b/src/as/com/threerings/miso/client/BaseTileIsoSprite.as index 4f60a305..37e36526 100644 --- a/src/as/com/threerings/miso/client/BaseTileIsoSprite.as +++ b/src/as/com/threerings/miso/client/BaseTileIsoSprite.as @@ -27,7 +27,7 @@ public class BaseTileIsoSprite extends TileIsoSprite public function BaseTileIsoSprite (x :int, y :int, tileId :int, tile :Tile, metrics :MisoSceneMetrics) { - super(x, y, tileId, tile, metrics); + super(x, y, tileId, tile, 0, metrics); } public override function layout (x :int, y :int, tile :Tile) :void diff --git a/src/as/com/threerings/miso/client/MisoScenePanel.as b/src/as/com/threerings/miso/client/MisoScenePanel.as index db6ba766..635230a1 100644 --- a/src/as/com/threerings/miso/client/MisoScenePanel.as +++ b/src/as/com/threerings/miso/client/MisoScenePanel.as @@ -30,18 +30,7 @@ import flash.geom.Rectangle; import flash.events.MouseEvent; -import com.threerings.crowd.client.PlaceView; -import com.threerings.crowd.data.PlaceObject; -import com.threerings.media.tile.Colorizer; -import com.threerings.media.tile.NoSuchTileSetError; -import com.threerings.media.tile.TileSet; -import com.threerings.media.tile.TileUtil; -import com.threerings.miso.client.MisoMetricsTransformation; -import com.threerings.miso.data.MisoSceneModel; -import com.threerings.miso.data.ObjectInfo; -import com.threerings.miso.util.MisoContext; -import com.threerings.miso.util.ObjectSet; -import com.threerings.miso.util.MisoSceneMetrics; +import mx.core.ClassFactory; import as3isolib.display.primitive.IsoBox; import as3isolib.geom.Pt; @@ -50,9 +39,26 @@ import as3isolib.display.scene.IsoGrid; import as3isolib.display.scene.IsoScene; import as3isolib.display.IsoView; +import com.threerings.crowd.client.PlaceView; +import com.threerings.crowd.data.PlaceObject; +import com.threerings.util.Log; +import com.threerings.media.tile.Colorizer; +import com.threerings.media.tile.NoSuchTileSetError; +import com.threerings.media.tile.TileSet; +import com.threerings.media.tile.TileUtil; +import com.threerings.miso.client.MisoMetricsTransformation; +import com.threerings.miso.client.PrioritizedSceneLayoutRenderer; +import com.threerings.miso.data.MisoSceneModel; +import com.threerings.miso.data.ObjectInfo; +import com.threerings.miso.util.MisoContext; +import com.threerings.miso.util.ObjectSet; +import com.threerings.miso.util.MisoSceneMetrics; + public class MisoScenePanel extends Sprite implements PlaceView { + private var log :Log = Log.getLog(MisoScenePanel); + public function MisoScenePanel (ctx :MisoContext, metrics :MisoSceneMetrics) { _ctx = ctx; @@ -97,7 +103,7 @@ public class MisoScenePanel extends Sprite _isoView.removeAllScenes(); var scene :IsoScene = new IsoScene(); - + scene.layoutRenderer = new ClassFactory(PrioritizedSceneLayoutRenderer); var time :int = getTimer(); var baseArr :Array = []; @@ -130,7 +136,7 @@ public class MisoScenePanel extends Sprite } if (tileSet == null) { - trace("TileManager returned null tilset: " + + log.warning("TileManager returned null tilset: " + TileUtil.getTileSetId(tileId)); continue; } @@ -161,14 +167,15 @@ public class MisoScenePanel extends Sprite } if (objTileSet == null) { - trace("TileManager returned null TileSet: " + TileUtil.getTileSetId(objTileId)); + log.warning("TileManager returned null TileSet: " + + TileUtil.getTileSetId(objTileId)); continue; } scene.addChild( new ObjectTileIsoSprite(objInfo.x, objInfo.y, objTileId, objTileSet.getTile(TileUtil.getTileIndex(objTileId), getColorizer(objInfo)), - _metrics)); + objInfo.priority, _metrics)); } time = getTimer(); diff --git a/src/as/com/threerings/miso/client/ObjectTileIsoSprite.as b/src/as/com/threerings/miso/client/ObjectTileIsoSprite.as index cc9216a2..5bc4ef8f 100644 --- a/src/as/com/threerings/miso/client/ObjectTileIsoSprite.as +++ b/src/as/com/threerings/miso/client/ObjectTileIsoSprite.as @@ -19,15 +19,17 @@ package com.threerings.miso.client { +import com.threerings.media.tile.ObjectTile; import com.threerings.media.tile.Tile; import com.threerings.miso.util.MisoSceneMetrics; public class ObjectTileIsoSprite extends TileIsoSprite { public function ObjectTileIsoSprite (x :int, y :int, tileId :int, tile :Tile, - metrics :MisoSceneMetrics) + priority :int, metrics :MisoSceneMetrics) { - super(x, y, tileId, tile, metrics); + super(x, y, tileId, tile, ObjectTile(tile).getPriority() == 0 ? + priority : ObjectTile(tile).getPriority(), metrics); } public override function layout (x :int, y :int, tile :Tile) :void diff --git a/src/as/com/threerings/miso/client/PrioritizedSceneLayoutRenderer.as b/src/as/com/threerings/miso/client/PrioritizedSceneLayoutRenderer.as new file mode 100644 index 00000000..9259d349 --- /dev/null +++ b/src/as/com/threerings/miso/client/PrioritizedSceneLayoutRenderer.as @@ -0,0 +1,176 @@ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.miso.client { + +import flash.utils.Dictionary; +import flash.utils.getTimer; + +import as3isolib.display.renderers.ISceneLayoutRenderer; +import as3isolib.bounds.IBounds; +import as3isolib.core.as3isolib_internal; +import as3isolib.core.IsoDisplayObject; +import as3isolib.display.scene.IIsoScene; + +/** + * Adapted from the as3isolib DefaultSceneLayoutRenderer, but thanks to its private vars, I cannot + * simply extend that class. + */ +public class PrioritizedSceneLayoutRenderer + implements ISceneLayoutRenderer +{ + /** + * Places all of our children in their appropriate rendering order based on their iso bounds + * and as necessary, by priority. Priority only comes into play if the iso bounds overlap. + */ + public function renderScene (scene:IIsoScene):void + { + // This is a little brute-force. Some suggestions from DefaultSceneLayoutRenderer: + // - cache dependencies between frames, only adjust invalidated objects, keeping old + // ordering as best as possible + // - screen space subdivision to limit dependency scan + // - set the invalidated children first, then do a rescan to make sure everything else is + // where it needs to be, too? probably need to order the invalidated children sets from + // low to high index + + // Reset everything for this rendering pass. + _scene = scene; + _dependencies = new Dictionary(); + _depth = 0; + _visited = new Dictionary(); + + var startTime:uint = getTimer(); + + // Use the non-rearranging display list so that the dependency sort will tend to + // create similar output each pass + var children:Array = _scene.displayListChildren; + + // Full naive cartesian scan, see what objects are behind/in front of child[ii] + var max:uint = children.length; + for (var ii :uint = 0; ii < max; ii++) + { + var objA:IsoDisplayObject = children[ii]; + + var rightA:Number = objA.x + objA.width; + var frontA:Number = objA.y + objA.length; + var topA:Number = objA.z + objA.height; + + var prioA :int = objA is PriorityIsoDisplayObject ? + PriorityIsoDisplayObject(objA).getPriority() : 0; + + for (var jj :uint = ii + 1; jj < max; jj++) + { + var objB:IsoDisplayObject = children[jj]; + + var prioB :int = objB is PriorityIsoDisplayObject ? + PriorityIsoDisplayObject(objB).getPriority() : 0; + + var rightB:Number = objB.x + objB.width; + var frontB:Number = objB.y + objB.length; + var topB:Number = objB.z + objB.height; + + // See if B should go behind A and vice-versa. + var bBehindA :Boolean = ((objB.x < rightA) && + (objB.y < frontA) && + (objB.z < topA)); + var aBehindB :Boolean = ((objA.x < rightB) && + (objA.y < frontB) && + (objA.z < topB)); + + if ((bBehindA && aBehindB)) { + // Overlap means we need to use the priority... + if (prioA > prioB) { + addDependency(objA, objB); + } else { + addDependency(objB, objA); + } + } else if (bBehindA) { + addDependency(objA, objB); + } else if (aBehindB) { + addDependency(objB, objA); + } + // Note - if we find that neither is behind the other, we don't add a dependency + // ordering. If they actually do visually overlap due to sticking outside their + // bounds, this can sometimes cause inconsistent visuals. + } + } + + trace("dependency scan time", getTimer() - startTime, "ms"); + + // Set the childrens' depth, using dependency ordering + for each (var obj:IsoDisplayObject in children) { + if (!_visited[obj]) { + place(obj); + } + } + + // Clear these out so we're not retaining memory between calls + _visited = null; + _dependencies = null; + + trace("scene layout render time", getTimer() - startTime, "ms (manual sort)"); + } + + /** + * Adds the front-to-back dependency to our map of all such dependencies. + */ + protected function addDependency (front :IsoDisplayObject, back :IsoDisplayObject) :void + { + var deps :Array = _dependencies[front]; + if (deps == null) { + deps = []; + _dependencies[front] = deps; + } + deps.push(back); + } + + /** + * Dependency-ordered depth placement of the given objects and its dependencies. + */ + protected function place (obj:IsoDisplayObject):void + { + _visited[obj] = true; + + for each (var inner:IsoDisplayObject in _dependencies[obj]) { + if (!_visited[inner]) { + place(inner); + } + } + + if (_depth != obj.depth) + { + _scene.setChildIndex(obj, _depth); + } + _depth++; + }; + + /** The depth we're currently placing children at. */ + protected var _depth :uint; + + /** Any objects we've already placed. */ + protected var _visited :Dictionary; + + /** The scene on which we're operating. */ + protected var _scene :IIsoScene; + + /** All the front-back dependencies we know about. This maps an object to a list of all + * the objects that should be rendered behind (and therefore before) it. */ + protected var _dependencies :Dictionary; +} +} \ No newline at end of file diff --git a/src/as/com/threerings/miso/client/PriorityIsoDisplayObject.as b/src/as/com/threerings/miso/client/PriorityIsoDisplayObject.as new file mode 100644 index 00000000..f6309d97 --- /dev/null +++ b/src/as/com/threerings/miso/client/PriorityIsoDisplayObject.as @@ -0,0 +1,32 @@ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.miso.client { + +/** + * Any Iso object wishing to specify a render priority should do so by implementing this interface. + */ +public interface PriorityIsoDisplayObject +{ + /** + * Returns the render priority for the display object. + */ + function getPriority () :int; +} +} \ No newline at end of file diff --git a/src/as/com/threerings/miso/client/TileIsoSprite.as b/src/as/com/threerings/miso/client/TileIsoSprite.as index e4df3bd1..4c19fb7b 100644 --- a/src/as/com/threerings/miso/client/TileIsoSprite.as +++ b/src/as/com/threerings/miso/client/TileIsoSprite.as @@ -33,16 +33,19 @@ import com.threerings.util.Log; import com.threerings.miso.util.MisoSceneMetrics; public class TileIsoSprite extends IsoDisplayObject + implements PriorityIsoDisplayObject { private static var log :Log = Log.getLog(TileIsoSprite); - public function TileIsoSprite (x :int, y :int, tileId :int, tile :Tile, + public function TileIsoSprite (x :int, y :int, tileId :int, tile :Tile, priority :int, metrics :MisoSceneMetrics) { _tileId = tileId; _metrics = metrics; + _priority = priority; + moveTo(x, y, 0); layout(x, y, tile); @@ -93,8 +96,15 @@ public class TileIsoSprite extends IsoDisplayObject render(); } + public function getPriority () :int + { + return _priority; + } + protected var _tileId :int; + protected var _priority :int; + protected var _metrics :MisoSceneMetrics; } } \ No newline at end of file