diff --git a/src/as/com/threerings/media/tile/TileUtil.as b/src/as/com/threerings/media/tile/TileUtil.as
new file mode 100644
index 00000000..7b3aac24
--- /dev/null
+++ b/src/as/com/threerings/media/tile/TileUtil.as
@@ -0,0 +1,67 @@
+// 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.media.tile {
+
+public class TileUtil
+{
+ /**
+ * Generates a fully-qualified tile id given the supplied tileset id
+ * and tile index.
+ */
+ public static function getFQTileId (tileSetId :int, tileIndex :int) :int
+ {
+ return (tileSetId << 16) | tileIndex;
+ }
+
+ /**
+ * Extracts the tile set id from the supplied fully qualified tile id.
+ */
+ public static function getTileSetId (fqTileId :int) :int
+ {
+ return (fqTileId >> 16);
+ }
+
+ /**
+ * Extracts the tile index from the supplied fully qualified tile id.
+ */
+ public static function getTileIndex (fqTileId :int) :int
+ {
+ return (fqTileId & 0xFFFF);
+ }
+
+ /**
+ * Compute some hash value for "randomizing" tileset picks
+ * based on x and y coordinates.
+ * NOTE: Because actionscript doesn't handle longs well, this does NOT match the implementation
+ * of the java version of getTileHash()
+ *
+ * @return a positive, seemingly random number based on x and y.
+ */
+ public static function getTileHash (x :int, y :int) :int
+ {
+ var seed :int = ((x ^ y) ^ MULTIPLIER) & MASK;
+ var hash :int = (seed * MULTIPLIER + ADDEND) & MASK;
+ return hash >>> 10;
+ }
+
+ protected static const MULTIPLIER :int = 0x5E66D;
+ protected static const ADDEND :int = 0xB;
+ protected static const MASK :int = (1 << 16) - 1;
+}
+}
diff --git a/src/as/com/threerings/miso/data/MisoSceneModel.as b/src/as/com/threerings/miso/data/MisoSceneModel.as
new file mode 100644
index 00000000..54879f26
--- /dev/null
+++ b/src/as/com/threerings/miso/data/MisoSceneModel.as
@@ -0,0 +1,101 @@
+//
+// $Id: MisoSceneModel.java 872 2010-01-13 18:28:28Z ray $
+//
+// 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.data {
+
+import flash.geom.Rectangle;
+
+import com.threerings.miso.util.ObjectSet;
+import com.threerings.io.SimpleStreamableObject;
+import com.threerings.miso.data.MisoSceneModel;
+import com.threerings.io.ObjectInputStream;
+import com.threerings.miso.data.ObjectInfo;
+import com.threerings.util.ClassUtil;
+import com.threerings.util.Cloneable;
+import com.threerings.io.ObjectOutputStream;
+
+/**
+ * Contains basic information for a miso scene model that is shared among
+ * the specialized model implementations.
+ */
+public /*abstract*/ class MisoSceneModel extends SimpleStreamableObject
+ implements Cloneable
+{
+ public function clone () :Object
+ {
+ return (ClassUtil.newInstance(this) as MisoSceneModel);
+ }
+
+ /**
+ * Returns the fully qualified tile id of the base tile at the
+ * specified coordinates. -1 will be returned if there is
+ * no tile at the specified coordinate.
+ */
+ public function getBaseTileId (arg1 :int, arg2 :int) :int
+ {
+ throw new Error("abstract");
+ }
+
+ public function setBaseTile (arg1 :int, arg2 :int, arg3 :int) :Boolean
+ {
+ throw new Error("abstract");
+ }
+
+ public function setDefaultBaseTileSet (arg1 :int) :void
+ {
+ // nothing doing
+ }
+
+ /**
+ * Scene models can return a default tileset to be used when no base
+ * tile data exists for a particular tile.
+ */
+ public function getDefaultBaseTileSet () :int
+ {
+ return 0;
+ }
+
+ /**
+ * Populates the supplied object set with info on all objects whose
+ * origin falls in the requested region.
+ */
+ public function getObjects (region :Rectangle, set :ObjectSet) :void
+ {
+ throw new Error("abstract");
+ }
+
+ public function addObject (info :ObjectInfo) :Boolean
+ {
+ throw new Error("abstract");
+ }
+
+ public function updateObject (info :ObjectInfo) :void
+ {
+ throw new Error("abstract");
+ }
+
+ public function removeObject (info :ObjectInfo) :Boolean
+ {
+ throw new Error("abstract");
+ }
+
+}
+}
diff --git a/src/as/com/threerings/miso/data/ObjectInfo.as b/src/as/com/threerings/miso/data/ObjectInfo.as
new file mode 100644
index 00000000..a5ec526b
--- /dev/null
+++ b/src/as/com/threerings/miso/data/ObjectInfo.as
@@ -0,0 +1,201 @@
+//
+// $Id: ObjectInfo.java 872 2010-01-13 18:28:28Z ray $
+//
+// 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.data {
+
+import com.threerings.util.Hashable;
+import com.threerings.util.ClassUtil;
+import com.threerings.util.Cloneable;
+import com.threerings.util.StringUtil;
+
+import com.threerings.io.SimpleStreamableObject;
+import com.threerings.io.ObjectInputStream;
+import com.threerings.io.ObjectOutputStream;
+
+import com.threerings.miso.data.ObjectInfo;
+import com.threerings.media.tile.TileUtil;
+
+/**
+ * Contains information about an object in a Miso scene.
+ */
+public class ObjectInfo extends SimpleStreamableObject
+ implements Cloneable, Hashable
+{
+ /** The fully qualified object tile id. */
+ public var tileId :int;
+
+ /** The x and y tile coordinates of the object. */
+ public var x :int;
+ public var y :int;
+
+ /** Don't access this directly unless you are serializing this
+ * instance. Use {@link #getPriority} instead. */
+ public var priority :int = 0;
+
+ /** The action associated with this object or null if it has no
+ * action. */
+ public var action :String;
+
+ /** A "spot" associated with this object (specified as an offset from
+ * the fine coordinates of the object's origin tile). */
+ public var sx :int;
+ public var sy :int;
+
+ /** The orientation of the "spot" associated with this object. */
+ public var sorient :int;
+
+ /** Up to two colorization assignments for this object. */
+ public var zations :int;
+
+ public function ObjectInfo (tileId :int = 0, x :int = 0, y :int = 0)
+ {
+ this.tileId = tileId;
+ this.x = x;
+ this.y = y;
+ }
+
+ public function hashCode () :int
+ {
+ return x ^ y ^ tileId;
+ }
+
+ public function clone () :Object
+ {
+ var info :ObjectInfo = (ClassUtil.newInstance(this) as ObjectInfo);
+ info.tileId = tileId;
+ info.x = x;
+ info.y = y;
+ info.priority = priority;
+ info.action = action;
+ info.sx = sx;
+ info.sy = sy;
+ info.sorient = sorient;
+ info.zations = zations;
+ return info;
+ }
+
+ public function equals (other :Object) :Boolean
+ {
+ if (other is ObjectInfo) {
+ var ooi :ObjectInfo = ObjectInfo(other);
+ return (x == ooi.x && y == ooi.y && tileId == ooi.tileId);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the render priority of this object tile.
+ */
+ public function getPriority () :int
+ {
+ return priority;
+ }
+
+ /**
+ * Returns the primary colorization assignment.
+ */
+ public function getPrimaryZation () :int
+ {
+ return (zations & 0xFF);
+ }
+
+ /**
+ * Returns the secondary colorization assignment.
+ */
+ public function getSecondaryZation () :int
+ {
+ return ((zations >> 16) & 0xFF);
+ }
+
+ /**
+ * Returns the tertiary colorization assignment.
+ */
+ public function getTertiaryZation () :int
+ {
+ return ((zations >> 24) & 0xFF);
+ }
+
+ /**
+ * Returns the quaternary colorization assignment.
+ */
+ public function getQuaternaryZation () :int
+ {
+ return ((zations >> 8) & 0xFF);
+ }
+
+ /**
+ * Sets the primary and secondary colorization assignments.
+ */
+ public function setZations (primary :int, secondary :int, tertiary :int, quaternary :int) :void
+ {
+ zations = (primary | (secondary << 16) | (tertiary << 24) | (quaternary << 8));
+ }
+
+ /**
+ * Returns true if this object info contains non-default data for
+ * anything other than the tile id and coordinates.
+ */
+ public function isInteresting () :Boolean
+ {
+ return (!StringUtil.isBlank(action) || priority != 0 ||
+ sx != 0 || sy != 0 || zations != 0);
+ }
+
+ /** Enhances our {@link SimpleStreamableObject#toString} output. */
+ public function tileIdToString () :String
+ {
+ return (TileUtil.getTileSetId(tileId) + ":" +
+ TileUtil.getTileIndex(tileId));
+ }
+
+ // from interface Streamable
+ override public function readObject (ins :ObjectInputStream) :void
+ {
+ super.readObject(ins);
+ tileId = ins.readInt();
+ x = ins.readInt();
+ y = ins.readInt();
+ priority = ins.readByte();
+ action = ins.readField(String);
+ sx = ins.readByte();
+ sy = ins.readByte();
+ sorient = ins.readByte();
+ zations = ins.readInt();
+ }
+
+ // from interface Streamable
+ override public function writeObject (out :ObjectOutputStream) :void
+ {
+ super.writeObject(out);
+ out.writeInt(tileId);
+ out.writeInt(x);
+ out.writeInt(y);
+ out.writeByte(priority);
+ out.writeField(action);
+ out.writeByte(sx);
+ out.writeByte(sy);
+ out.writeByte(sorient);
+ out.writeInt(zations);
+ }
+
+}
+}
diff --git a/src/as/com/threerings/miso/data/SparseMisoSceneModel.as b/src/as/com/threerings/miso/data/SparseMisoSceneModel.as
new file mode 100644
index 00000000..400d9add
--- /dev/null
+++ b/src/as/com/threerings/miso/data/SparseMisoSceneModel.as
@@ -0,0 +1,248 @@
+//
+// $Id: SparseMisoSceneModel.java 901 2010-03-11 00:51:30Z mthomas $
+//
+// 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.data {
+
+import flash.geom.Rectangle;
+
+import com.threerings.io.ObjectInputStream;
+import com.threerings.io.SimpleStreamableObject;
+import com.threerings.io.ObjectOutputStream;
+
+import com.threerings.util.ArrayIterator;
+import com.threerings.util.ClassUtil;
+import com.threerings.util.Iterator;
+import com.threerings.util.Joiner;
+import com.threerings.util.MathUtil;
+import com.threerings.util.StreamableHashMap;
+import com.threerings.util.StringUtil;
+
+import com.threerings.miso.util.ObjectSet;
+import com.threerings.miso.data.MisoSceneModel;
+import com.threerings.miso.data.SparseMisoSceneModel;
+import com.threerings.miso.data.ObjectInfo;
+import com.threerings.miso.data.SparseMisoSceneModel_ObjectVisitor;
+import com.threerings.miso.data.SparseMisoSceneModel_Section;
+
+/**
+ * Contains miso scene data that is broken up into NxN tile sections.
+ */
+public class SparseMisoSceneModel extends MisoSceneModel
+{
+ public var swidth :int;
+ public var sheight :int;
+ /** The dimensions of a section of our scene. */
+
+ /** The tileset to use when we have no tile data. */
+ public var defTileSet :int = 0;
+
+ override public function clone () :Object
+ {
+ var model :SparseMisoSceneModel = (ClassUtil.newInstance(this) as SparseMisoSceneModel);
+ model._sections = new StreamableHashMap();
+ for (var iter :Iterator = getSections(); iter.hasNext(); ) {
+ var sect :SparseMisoSceneModel_Section = SparseMisoSceneModel_Section(iter.next());
+ model.setSection(SparseMisoSceneModel_Section(sect.clone()));
+ }
+ return model;
+ }
+
+ override public function getBaseTileId (col :int, row :int) :int
+ {
+ var sec :SparseMisoSceneModel_Section = getSection(col, row, false);
+ return (sec == null) ? -1 : sec.getBaseTileId(col, row);
+ }
+
+ override public function setBaseTile (fqBaseTileId :int, col :int, row :int) :Boolean
+ {
+ getSection(col, row, true).setBaseTile(col, row, fqBaseTileId);
+ return true;
+ }
+
+ override public function setDefaultBaseTileSet (tileSetId :int) :void
+ {
+ defTileSet = tileSetId;
+ }
+
+ override public function getDefaultBaseTileSet () :int
+ {
+ return defTileSet;
+ }
+
+ override public function getObjects (region :Rectangle, set :ObjectSet) :void
+ {
+ var minx :int = MathUtil.floorDiv(region.x, swidth)*swidth;
+ var maxx :int = MathUtil.floorDiv(region.x+region.width-1, swidth)*swidth;
+ var miny :int = MathUtil.floorDiv(region.y, sheight)*sheight;
+ var maxy :int = MathUtil.floorDiv(region.y+region.height-1, sheight)*sheight;
+ for (var yy :int = miny; yy <= maxy; yy += sheight) {
+ for (var xx :int = minx; xx <= maxx; xx += swidth) {
+ var sec :SparseMisoSceneModel_Section = getSection(xx, yy, false);
+ if (sec != null) {
+ sec.getObjects(region, set);
+ }
+ }
+ }
+ }
+
+ override public function addObject (info :ObjectInfo) :Boolean
+ {
+ return getSection(info.x, info.y, true).addObject(info);
+ }
+
+ override public function updateObject (info :ObjectInfo) :void
+ {
+ // not efficient, but this is only done in editing situations
+ removeObject(info);
+ addObject(info);
+ }
+
+ override public function removeObject (info :ObjectInfo) :Boolean
+ {
+ var sec :SparseMisoSceneModel_Section = getSection(info.x, info.y, false);
+ if (sec != null) {
+ return sec.removeObject(info);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Adds all interesting {@link ObjectInfo} records in this scene to
+ * the supplied list.
+ */
+ public function getInterestingObjects (list :Array) :void
+ {
+ for (var iter :Iterator = getSections(); iter.hasNext(); ) {
+ var sect :SparseMisoSceneModel_Section = SparseMisoSceneModel_Section(iter.next());
+ for each (var element :ObjectInfo in sect.objectInfo) {
+ list.add(element);
+ }
+ }
+ }
+
+ /**
+ * Don't call this method! This is only public so that the scene
+ * writer can generate XML from the raw scene data.
+ */
+ public function getSections () :Iterator
+ {
+ return new ArrayIterator(_sections.values());
+ }
+
+ /**
+ * Adds all {@link ObjectInfo} records in this scene to the supplied list.
+ */
+ public function getAllObjects (list :Array) :void
+ {
+ for (var iter :Iterator = getSections(); iter.hasNext(); ) {
+ iter.next().getAllObjects(list);
+ }
+ }
+
+ /**
+ * Informs the supplied visitor of each object in this scene.
+ *
+ * @param interestingOnly if true, only the interesting objects will
+ * be visited.
+ */
+ public function visitObjects (visitor :SparseMisoSceneModel_ObjectVisitor,
+ interestingOnly :Boolean = false) :void
+ {
+ for (var iter :Iterator = getSections(); iter.hasNext(); ) {
+ var sect :SparseMisoSceneModel_Section = SparseMisoSceneModel_Section(iter.next());
+ for each (var oinfo :ObjectInfo in sect.objectInfo) {
+ visitor.visit(oinfo);
+ }
+ if (!interestingOnly) {
+ for (var oo :int = 0; oo < sect.objectTileIds.length; oo++) {
+ var info :ObjectInfo = new ObjectInfo(sect.objectTileIds[oo],
+ sect.objectXs[oo], sect.objectYs[oo]);
+ visitor.visit(info);
+ }
+ }
+ }
+ }
+
+ /**
+ * Don't call this method! This is only public so that the scene
+ * parser can construct a scene from raw data. If only Java supported
+ * class friendship.
+ */
+ public function setSection (section :SparseMisoSceneModel_Section) :void
+ {
+ _sections.put(key(section.x, section.y), section);
+ }
+
+ // from interface Streamable
+ override public function readObject (ins :ObjectInputStream) :void
+ {
+ super.readObject(ins);
+ swidth = ins.readShort();
+ sheight = ins.readShort();
+ defTileSet = ins.readInt();
+ _sections = ins.readObject(StreamableHashMap);
+ }
+
+ // from interface Streamable
+ override public function writeObject (out :ObjectOutputStream) :void
+ {
+ super.writeObject(out);
+ out.writeShort(swidth);
+ out.writeShort(sheight);
+ out.writeInt(defTileSet);
+ out.writeObject(_sections);
+ }
+
+ override protected function toStringJoiner (j :Joiner) :void
+ {
+ super.toStringJoiner(j);
+ j.add("sections", StringUtil.toString(_sections.values()));
+ }
+
+ /**
+ * Returns the key for the specified section.
+ */
+ protected function key (x :int, y :int) :int
+ {
+ var sx :int = MathUtil.floorDiv(x, swidth);
+ var sy :int = MathUtil.floorDiv(y, sheight);
+ return (sx << 16) | (sy & 0xFFFF);
+ }
+
+ /** Returns the section for the specified tile coordinate. */
+ protected function getSection (x :int, y :int, create :Boolean) :SparseMisoSceneModel_Section
+ {
+ var key :int = key(x, y);
+ var sect :SparseMisoSceneModel_Section = _sections.get(key);
+ if (sect == null && create) {
+ var sx :int = MathUtil.floorDiv(x, swidth)*swidth;
+ var sy :int = MathUtil.floorDiv(y, sheight)*sheight;
+ _sections.put(key, sect = new SparseMisoSceneModel_Section(sx, sy, swidth, sheight));
+ }
+ return sect;
+ }
+
+ /** Contains our sections in row major order. */
+ protected var _sections :StreamableHashMap = new StreamableHashMap();
+
+}
+}
diff --git a/src/as/com/threerings/miso/data/SparseMisoSceneModel_ObjectVisitor.as b/src/as/com/threerings/miso/data/SparseMisoSceneModel_ObjectVisitor.as
new file mode 100644
index 00000000..f847b299
--- /dev/null
+++ b/src/as/com/threerings/miso/data/SparseMisoSceneModel_ObjectVisitor.as
@@ -0,0 +1,26 @@
+// 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.data {
+
+public interface SparseMisoSceneModel_ObjectVisitor
+{
+ /** Called for each object in the scene, interesting and not. */
+ function visit (info :ObjectInfo) :void;
+}
+}
diff --git a/src/as/com/threerings/miso/data/SparseMisoSceneModel_Section.as b/src/as/com/threerings/miso/data/SparseMisoSceneModel_Section.as
new file mode 100644
index 00000000..6bf22acc
--- /dev/null
+++ b/src/as/com/threerings/miso/data/SparseMisoSceneModel_Section.as
@@ -0,0 +1,265 @@
+//
+// $Id: SparseMisoSceneModel_Section.java 901 2010-03-11 00:51:30Z mthomas $
+//
+// 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.data {
+
+import flash.geom.Rectangle;
+
+import com.threerings.io.ObjectOutputStream;
+import com.threerings.io.ObjectInputStream;
+import com.threerings.io.SimpleStreamableObject;
+import com.threerings.io.TypedArray;
+import com.threerings.miso.util.ObjectSet;
+import com.threerings.util.ArrayUtil;
+import com.threerings.util.ClassUtil;
+import com.threerings.util.Cloneable;
+import com.threerings.util.Log;
+import com.threerings.util.StringUtil;
+
+public class SparseMisoSceneModel_Section extends SimpleStreamableObject
+ implements Cloneable
+{
+ public static const log :Log = Log.getLog(SparseMisoSceneModel_Section);
+
+ /** The tile coordinate of our upper leftmost tile. */
+ public var x :int;
+ public var y :int;
+
+ /** The width of this section in tiles. */
+ public var width :int;
+
+ /** The combined tile ids (tile set id and tile id) for our
+ * section (in row major order). */
+ public var baseTileIds :TypedArray;
+
+ /** The combined tile ids (tile set id and tile id) of the
+ * "uninteresting" tiles in the object layer. */
+ public var objectTileIds :TypedArray = TypedArray.create(int);
+
+ /** The x coordinate of the "uninteresting" tiles in the object
+ * layer. */
+ public var objectXs :TypedArray = TypedArray.createShort();
+
+ /** The y coordinate of the "uninteresting" tiles in the object
+ * layer. */
+ public var objectYs :TypedArray = TypedArray.createShort();
+
+ /** Information records for the "interesting" objects in the
+ * object layer. */
+ public var objectInfo :TypedArray = TypedArray.create(ObjectInfo);
+
+ /**
+ * Creates a new scene section with the specified dimensions.
+ */
+ public function SparseMisoSceneModel_Section (
+ x :int = 0, y :int = 0, width :int = 0, height :int = 0)
+ {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ baseTileIds = TypedArray.create(int, ArrayUtil.create(width*height));
+ }
+
+ public function getBaseTileId (col :int, row :int) :int
+ {
+ if (col < x || col >= (x+width) || row < y || row >= (y+width)) {
+ log.warning("Requested bogus tile +" + col + "+" + row +
+ " from " + this + ".");
+ return -1;
+ } else {
+ return baseTileIds[(row-y)*width+(col-x)];
+ }
+ }
+
+ public function setBaseTile (col :int, row :int, fqBaseTileId :int) :void
+ {
+ baseTileIds[(row-y)*width+(col-x)] = fqBaseTileId;
+ }
+
+ public function addObject (info :ObjectInfo) :Boolean
+ {
+ // sanity check: see if there is already an object of this
+ // type at these coordinates
+ var dupidx :int;
+ if ((dupidx = ArrayUtil.indexOf(objectInfo, info)) != -1) {
+ log.warning("Refusing to add duplicate object [ninfo=" + info +
+ ", oinfo=" + objectInfo[dupidx] + "].");
+ return false;
+ }
+ if ((dupidx = indexOfUn(info)) != -1) {
+ log.warning("Refusing to add duplicate object " +
+ "[info=" + info + "].");
+ return false;
+ }
+
+ if (info.isInteresting()) {
+ objectInfo = TypedArray(objectInfo.concat(info));
+ } else {
+ objectTileIds = TypedArray(objectTileIds.concat(info.tileId));
+ objectXs = TypedArray(objectXs.concat(info.x));
+ objectYs = TypedArray(objectYs.concat(info.y));
+ }
+ return true;
+ }
+
+ public function removeObject (info :ObjectInfo) :Boolean
+ {
+ // look for it in the interesting info array
+ var oidx :int = ArrayUtil.indexOf(objectInfo, info);
+ if (oidx != -1) {
+ objectInfo = TypedArray(ArrayUtil.splice(objectInfo, oidx, 1));
+ return true;
+ }
+
+ // look for it in the uninteresting arrays
+ oidx = indexOfUn(info);
+ if (oidx != -1) {
+ objectTileIds = TypedArray(ArrayUtil.splice(objectTileIds, oidx, 1));
+ objectXs = TypedArray(ArrayUtil.splice(objectXs, oidx, 1));
+ objectYs = TypedArray(ArrayUtil.splice(objectYs, oidx, 1));
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the index of the specified object in the uninteresting
+ * arrays or -1 if it is not in this section as an uninteresting
+ * object.
+ */
+ protected function indexOfUn (info :ObjectInfo) :int
+ {
+ for (var ii :int = 0; ii < objectTileIds.length; ii++) {
+ if (objectTileIds[ii] == info.tileId &&
+ objectXs[ii] == info.x && objectYs[ii] == info.y) {
+ return ii;
+ }
+ }
+ return -1;
+ }
+
+ public function getAllObjects (list :Array) :void
+ {
+ for each (var info :ObjectInfo in objectInfo) {
+ list.add(info);
+ }
+ for (var ii :int= 0; ii < objectTileIds.length; ii++) {
+ var x :int = objectXs[ii];
+ var y :int = objectYs[ii];
+ list.add(new ObjectInfo(objectTileIds[ii], x, y));
+ }
+ }
+
+ public function getObjects (region :Rectangle, set :ObjectSet) :void
+ {
+ // first look for intersecting interesting objects
+ for each (var info :ObjectInfo in objectInfo) {
+ if (region.contains(info.x, info.y)) {
+ set.insert(info);
+ }
+ }
+
+ // now look for intersecting non-interesting objects
+ for (var ii :int = 0; ii < objectTileIds.length; ii++) {
+ var x :int = objectXs[ii];
+ var y :int = objectYs[ii];
+ if (region.contains(x, y)) {
+ set.insert(new ObjectInfo(objectTileIds[ii], x, y));
+ }
+ }
+ }
+
+ /**
+ * Returns true if this section contains no data beyond the default.
+ * Used when saving a sparse scene: we omit blank sections.
+ */
+ public function isBlank () :Boolean
+ {
+ if ((objectTileIds.length != 0) || (objectInfo.length != 0)) {
+ return false;
+ }
+ for each (var baseTileId :int in baseTileIds) {
+ if (baseTileId != 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public function clone () :Object
+ {
+ var section :SparseMisoSceneModel_Section =
+ (ClassUtil.newInstance(this) as SparseMisoSceneModel_Section);
+ section.x = x;
+ section.y = y;
+ section.width = width;
+ section.baseTileIds = TypedArray(ArrayUtil.copyOf(baseTileIds));
+ section.objectTileIds = TypedArray(ArrayUtil.copyOf(objectTileIds));
+ section.objectXs = TypedArray(ArrayUtil.copyOf(objectXs));
+ section.objectYs = TypedArray(ArrayUtil.copyOf(objectYs));
+ section.objectInfo = TypedArray.create(ObjectInfo);
+ for (var ii :int = 0; ii < objectInfo.length; ii++) {
+ section.objectInfo[ii] = objectInfo[ii].clone();
+ }
+ return section;
+ }
+
+ override public function toString () :String
+ {
+ if (width == 0 || baseTileIds == null) {
+ return "";
+ } else {
+ return width + "x" + (baseTileIds.length / width) + "+" + x + ":" + y + ":" +
+ objectInfo.length;
+ }
+ }
+
+ // from interface Streamable
+ override public function readObject (ins :ObjectInputStream) :void
+ {
+ super.readObject(ins);
+ x = ins.readShort();
+ y = ins.readShort();
+ width = ins.readInt();
+ baseTileIds = ins.readField(TypedArray.getJavaType(int));
+ objectTileIds = ins.readField(TypedArray.getJavaType(int));
+ objectXs = ins.readField(TypedArray.getJavaShortType());
+ objectYs = ins.readField(TypedArray.getJavaShortType());
+ objectInfo = TypedArray(ins.readObject());
+ }
+
+ // from interface Streamable
+ override public function writeObject (out :ObjectOutputStream) :void
+ {
+ super.writeObject(out);
+ out.writeShort(x);
+ out.writeShort(y);
+ out.writeInt(width);
+ out.writeField(baseTileIds);
+ out.writeField(objectTileIds);
+ out.writeField(objectXs);
+ out.writeField(objectYs);
+ out.writeField(objectInfo);
+ }
+}
+}
diff --git a/src/as/com/threerings/miso/data/VirtualMisoSceneModel.as b/src/as/com/threerings/miso/data/VirtualMisoSceneModel.as
new file mode 100644
index 00000000..6534ff83
--- /dev/null
+++ b/src/as/com/threerings/miso/data/VirtualMisoSceneModel.as
@@ -0,0 +1,56 @@
+//
+// $Id: VirtualMisoSceneModel.java 868 2010-01-04 21:47:34Z dhoover $
+//
+// 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.data {
+
+import com.threerings.io.ObjectInputStream;
+import com.threerings.miso.data.ObjectInfo;
+import com.threerings.io.ObjectOutputStream;
+
+/**
+ * A convenient base class for "virtual" scenes which do not allow editing
+ * and compute the base and object tiles rather than obtain them from some
+ * data structure.
+ */
+public /*abstract*/ class VirtualMisoSceneModel extends MisoSceneModel
+{
+ override public function setBaseTile (arg1 :int, arg2 :int, arg3 :int) :Boolean
+ {
+ throw new Error("Unsupported");
+ }
+
+ override public function addObject (arg1 :ObjectInfo) :Boolean
+ {
+ throw new Error("Unsupported");
+ }
+
+ override public function updateObject (arg1 :ObjectInfo) :void
+ {
+ throw new Error("Unsupported");
+ }
+
+ override public function removeObject (arg1 :ObjectInfo) :Boolean
+ {
+ throw new Error("Unsupported");
+ }
+
+}
+}
diff --git a/src/as/com/threerings/miso/util/MisoContext.as b/src/as/com/threerings/miso/util/MisoContext.as
new file mode 100644
index 00000000..22a30609
--- /dev/null
+++ b/src/as/com/threerings/miso/util/MisoContext.as
@@ -0,0 +1,28 @@
+// 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.util {
+
+/**
+ * Provides Miso code with access to the managers that it needs to do its
+ * thing. For now it is just a stub.
+ */
+public interface MisoContext
+{
+}
+}
diff --git a/src/as/com/threerings/miso/util/ObjectSet.as b/src/as/com/threerings/miso/util/ObjectSet.as
new file mode 100644
index 00000000..f6faeac3
--- /dev/null
+++ b/src/as/com/threerings/miso/util/ObjectSet.as
@@ -0,0 +1,160 @@
+// 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.util {
+
+import com.threerings.miso.data.ObjectInfo;
+import com.threerings.util.ArrayUtil;
+import com.threerings.util.Log;
+
+/**
+ * Used to store an (arbitrarily) ordered, low-impact iteratable (doesn't
+ * require object creation), set of {@link ObjectInfo} instances.
+ */
+public class ObjectSet
+{
+ public static const log :Log = Log.getLog(ObjectSet);
+
+ /**
+ * Inserts the supplied object into the set.
+ *
+ * @return true if it was inserted, false if the object was already in
+ * the set.
+ */
+ public function insert (info :ObjectInfo) :Boolean
+ {
+ // bail if it's already in the set
+ var ipos :int = indexOf(info);
+ if (ipos >= 0) {
+ // log a warning because the caller shouldn't be doing this
+ log.warning("Requested to add an object to a set that already " +
+ "contains such an object [ninfo=" + info +
+ ", oinfo=" + _objs[ipos] + "].", new Error());
+ return false;
+ }
+
+ // otherwise insert it
+ ipos = -(ipos+1);
+ _objs = _objs.splice(ipos, 0, info);
+ return true;
+ }
+
+ /**
+ * Returns true if the specified object is in the set, false if it is
+ * not.
+ */
+ public function contains (info :ObjectInfo) :Boolean
+ {
+ return (indexOf(info) >= 0);
+ }
+
+ /**
+ * Returns the number of objects in this set.
+ */
+ public function size () :int
+ {
+ return _objs.length;
+ }
+
+ /**
+ * Returns the object with the specified index. The index must & be
+ * between 0 and {@link #size}-1.
+ */
+ public function get (index :int) :ObjectInfo
+ {
+ return ObjectInfo(_objs[index]);
+ }
+
+ /**
+ * Removes the object at the specified index.
+ */
+ public function removeAt (index :int) :void
+ {
+ ArrayUtil.removeFirst(_objs, index);
+ }
+
+ /**
+ * Removes the specified object from the set.
+ *
+ * @return true if it was removed, false if it was not in the set.
+ */
+ public function remove (info :ObjectInfo) :Boolean
+ {
+ var opos :int = indexOf(info);
+ if (opos >= 0) {
+ removeAt(opos);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Clears out the contents of this set.
+ */
+ public function clear () :void
+ {
+ _objs.length = 0;
+ }
+
+ /**
+ * Converts the contents of this object set to an array.
+ */
+ public function toArray () :Array
+ {
+ return ArrayUtil.copyOf(_objs);
+ }
+
+ public function toString () :String
+ {
+ var result :String = "[";
+ for (var ii :int = 0; ii < _objs.length; ii++) {
+ if (ii > 0) {
+ result += ", ";
+ }
+ result += _objs[ii];
+ }
+ return result += "]";
+ }
+
+ /** We simply sort the objects in order of their hash code. We don't
+ * care about their order, it exists only to support binary search. */
+ protected function searchCompare (o1 :Object, o2 :Object) :int
+ {
+ var do1 :ObjectInfo = ObjectInfo(o1);
+ var do2 :ObjectInfo = ObjectInfo(o2);
+ if (do1.tileId == do2.tileId) {
+ return ((do1.x << 16) + do1.y) - ((do2.x << 16) + do2.y);
+ } else {
+ return do1.tileId - do2.tileId;
+ }
+ };
+
+ /**
+ * Returns the index of the object or it's insertion index if it is
+ * not in the set.
+ */
+ protected function indexOf (info :ObjectInfo) :int
+ {
+ return ArrayUtil.binarySearch(_objs, 0, _objs.length, info, searchCompare);
+ }
+
+ /** Our sorted array of objects. */
+ protected var _objs :Array = new Array();
+}
+}
diff --git a/src/as/com/threerings/util/DirectionCodes.as b/src/as/com/threerings/util/DirectionCodes.as
new file mode 100644
index 00000000..c0e66301
--- /dev/null
+++ b/src/as/com/threerings/util/DirectionCodes.as
@@ -0,0 +1,111 @@
+// 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.util {
+
+/**
+ * A single, top-level location for the definition of compass direction
+ * constants, which are used by a variety of Narya services.
+ */
+public class DirectionCodes
+{
+ /** A direction code indicating no direction. */
+ public static const NONE :int = -1;
+
+ /** A direction code indicating moving left. */
+ public static const LEFT :int = 0;
+
+ /** A direction code indicating moving right. */
+ public static const RIGHT :int = 1;
+
+ /** A direction code indicating moving up. */
+ public static const UP :int = 2;
+
+ /** A direction code indicating moving down. */
+ public static const DOWN :int = 3;
+
+ /** A direction code indicating a counter-clockwise rotation. */
+ public static const CCW :int = 0;
+
+ /** A direction code indicating a clockwise rotation. */
+ public static const CW :int = 1;
+
+ /** A direction code indicating horizontal movement. */
+ public static const HORIZONTAL :int = 0;
+
+ /** A direction code indicating vertical movement. */
+ public static const VERTICAL :int = 1;
+
+ /** A direction code indicating southwest. */
+ public static const SOUTHWEST :int = 0;
+
+ /** A direction code indicating west. */
+ public static const WEST :int = 1;
+
+ /** A direction code indicating northwest. */
+ public static const NORTHWEST :int = 2;
+
+ /** A direction code indicating north. */
+ public static const NORTH :int = 3;
+
+ /** A direction code indicating northeast. */
+ public static const NORTHEAST :int = 4;
+
+ /** A direction code indicating east. */
+ public static const EAST :int = 5;
+
+ /** A direction code indicating southeast. */
+ public static const SOUTHEAST :int = 6;
+
+ /** A direction code indicating south. */
+ public static const SOUTH :int = 7;
+
+ /** The number of basic compass directions. */
+ public static const DIRECTION_COUNT :int = 8;
+
+ /** A direction code indicating west by southwest. */
+ public static const WESTSOUTHWEST :int = 8;
+
+ /** A direction code indicating west by northwest. */
+ public static const WESTNORTHWEST :int = 9;
+
+ /** A direction code indicating north by northwest. */
+ public static const NORTHNORTHWEST :int = 10;
+
+ /** A direction code indicating north by northeast. */
+ public static const NORTHNORTHEAST :int = 11;
+
+ /** A direction code indicating east by northeast. */
+ public static const EASTNORTHEAST :int = 12;
+
+ /** A direction code indicating east by southeast. */
+ public static const EASTSOUTHEAST :int = 13;
+
+ /** A direction code indicating south by southeast. */
+ public static const SOUTHSOUTHEAST :int = 14;
+
+ /** A direction code indicating south by southwest. */
+ public static const SOUTHSOUTHWEST :int = 15;
+
+ /** The number of fine compass directions. */
+ public static const FINE_DIRECTION_COUNT :int = 16;
+
+ /** The four points of the compass. */
+ public static const CARDINAL_DIRECTIONS :Array = [ NORTH, EAST, SOUTH, WEST ];
+}
+}
diff --git a/src/as/com/threerings/util/DirectionUtil.as b/src/as/com/threerings/util/DirectionUtil.as
new file mode 100644
index 00000000..c0dbd018
--- /dev/null
+++ b/src/as/com/threerings/util/DirectionUtil.as
@@ -0,0 +1,320 @@
+// 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.util {
+
+import flash.geom.Point;
+
+import com.threerings.util.ArrayUtil;
+
+public class DirectionUtil extends DirectionCodes
+{
+ /**
+ * Returns an array of names corresponding to each direction constant.
+ */
+ public static function getDirectionNames () :Array
+ {
+ return DIR_STRINGS;
+ }
+
+ /**
+ * Returns a string representation of the supplied direction code.
+ */
+ public static function toString (direction :int) :String
+ {
+ return ((direction >= 0) && (direction < FINE_DIRECTION_COUNT)) ?
+ DIR_STRINGS[direction] : "INVALID";
+ }
+
+ /**
+ * Returns an abbreviated string representation of the supplied
+ * direction code.
+ */
+ public static function toShortString (direction :int) :String
+ {
+ return ((direction >= 0) && (direction < FINE_DIRECTION_COUNT)) ?
+ SHORT_DIR_STRINGS[direction] : "?";
+ }
+
+ /**
+ * Returns the direction code that corresponds to the supplied string
+ * or {@link #NONE} if the string does not correspond to a known
+ * direction code.
+ */
+ public static function fromString (dirstr :String) :int
+ {
+ for (var ii :int = 0; ii < FINE_DIRECTION_COUNT; ii++) {
+ if (DIR_STRINGS[ii].equals(dirstr)) {
+ return ii;
+ }
+ }
+ return NONE;
+ }
+
+ /**
+ * Returns the direction code that corresponds to the supplied short
+ * string or {@link #NONE} if the string does not correspond to a
+ * known direction code.
+ */
+ public static function fromShortString (dirstr :String) :int
+ {
+ for (var ii :int = 0; ii < FINE_DIRECTION_COUNT; ii++) {
+ if (SHORT_DIR_STRINGS[ii].equals(dirstr)) {
+ return ii;
+ }
+ }
+ return NONE;
+ }
+
+ /**
+ * Returns a string representation of an array of direction codes. The
+ * directions are represented by the abbreviated names.
+ */
+ public static function dirsToString (directions :Array) :String
+ {
+ var result :String = "{";
+ for (var ii :int = 0; ii < directions.length; ii++) {
+ if (ii > 0) {
+ result += ", ";
+ }
+ result += toShortString(directions[ii]);
+ }
+ result += "}";
+ return result;
+ }
+
+ /**
+ * Rotates the requested fine direction constant clockwise by
+ * the requested number of ticks.
+ */
+ public static function rotateCW (direction :int, ticks :int) :int
+ {
+ for (var ii :int = 0; ii < ticks; ii++) {
+ direction = FINE_CW_ROTATE[direction];
+ }
+ return direction;
+ }
+
+ /**
+ * Rotates the requested fine direction constant
+ * counter-clockwise by the requested number of ticks.
+ */
+ public static function rotateCCW (direction :int, ticks :int) :int
+ {
+ for (var ii :int = 0; ii < ticks; ii++) {
+ direction = FINE_CCW_ROTATE[direction];
+ }
+ return direction;
+ }
+
+ /**
+ * Returns the opposite of the specified direction.
+ */
+ public static function getOpposite (direction :int) :int
+ {
+ return rotateCW(direction, FINE_CW_ROTATE.length/2);
+ }
+
+ /**
+ * Get the cardinal direction closest to the specified direction (preferring a clockwise match).
+ */
+ public static function getClosestCardinal (direction :int) :int
+ {
+ return getClosest(direction, CARDINAL_DIRECTIONS, true);
+ }
+
+ /**
+ * Get the direction closest to the specified direction, out of
+ * the directions in the possible list.
+ *
+ * @param preferCW whether to prefer a clockwise match or a
+ * counter-clockwise match.
+ */
+ public static function getClosest (direction :int, possible :Array,
+ preferCW :Boolean = true) :int
+ {
+ // rotate a tick at a time, looking for matches
+ var first :int = direction;
+ var second :int = direction;
+ for (var ii :int = 0; ii <= FINE_DIRECTION_COUNT / 2; ii++) {
+ if (ArrayUtil.contains(possible, first)) {
+ return first;
+ }
+
+ if (ii != 0 && ArrayUtil.contains(possible, second)) {
+ return second;
+ }
+
+ first = preferCW ? rotateCW(first, 1) : rotateCCW(first, 1);
+ second = preferCW ? rotateCCW(second, 1) : rotateCW(second, 1);
+ }
+
+ return NONE;
+ }
+
+ /**
+ * Returns which of the eight compass directions that point
+ * b lies in from point a as one of the
+ * {@link DirectionCodes} direction constants. Note: that the
+ * coordinates supplied are assumed to be logical (screen) rather than
+ * cartesian coordinates and NORTH is considered to point
+ * toward the top of the screen.
+ */
+ public static function getDirectionForPts (a :Point, b :Point) :int
+ {
+ return getDirection(a.x, a.y, b.x, b.y);
+ }
+
+ /**
+ * Returns which of the eight compass directions that point
+ * b lies in from point a as one of the
+ * {@link DirectionCodes} direction constants. Note: that the
+ * coordinates supplied are assumed to be logical (screen) rather than
+ * cartesian coordinates and NORTH is considered to point
+ * toward the top of the screen.
+ */
+ public static function getDirection (ax :Number, ay :Number, bx :Number, by :Number) :int
+ {
+ return getDirectionForAngle(Math.atan2(by-ay, bx-ax));
+ }
+
+ /**
+ * Returns which of the eight compass directions is associated with
+ * the specified angle theta. Note: that the angle supplied
+ * is assumed to increase clockwise around the origin (which screen
+ * angles do) rather than counter-clockwise around the origin (which
+ * cartesian angles do) and NORTH is considered to point
+ * toward the top of the screen.
+ */
+ public static function getDirectionForAngle (theta :Number) :int
+ {
+ theta = ((theta + Math.PI) * 4) / Math.PI;
+ return (int)(Math.round(theta) + WEST) % 8;
+ }
+
+ /**
+ * Returns which of the sixteen compass directions that point
+ * b lies in from point a as one of the
+ * {@link DirectionCodes} direction constants. Note: that the
+ * coordinates supplied are assumed to be logical (screen) rather than
+ * cartesian coordinates and NORTH is considered to point
+ * toward the top of the screen.
+ */
+ public static function getFineDirectionForPts (a :Point, b :Point) :int
+ {
+ return getFineDirection(a.x, a.y, b.x, b.y);
+ }
+
+ /**
+ * Returns which of the sixteen compass directions that point
+ * b lies in from point a as one of the
+ * {@link DirectionCodes} direction constants. Note: that the
+ * coordinates supplied are assumed to be logical (screen) rather than
+ * cartesian coordinates and NORTH is considered to point
+ * toward the top of the screen.
+ */
+ public static function getFineDirection (ax :Number, ay :Number, bx :Number, by :Number) :int
+ {
+ return getFineDirectionForAngle(Math.atan2(by-ay, bx-ax));
+ }
+
+ /**
+ * Returns which of the sixteen compass directions is associated with
+ * the specified angle theta. Note: that the angle supplied
+ * is assumed to increase clockwise around the origin (which screen
+ * angles do) rather than counter-clockwise around the origin (which
+ * cartesian angles do) and NORTH is considered to point
+ * toward the top of the screen.
+ */
+ public static function getFineDirectionForAngle (theta :Number) :int
+ {
+ theta = ((theta + Math.PI) * 8) / Math.PI;
+ return ANGLE_MAP[int(Math.round(theta)) % FINE_DIRECTION_COUNT];
+ }
+
+ /**
+ * Move the specified point in the specified screen direction,
+ * adjusting by the specified adjustments. Fine directions are
+ * not supported.
+ */
+ public static function moveDirection (p :Point, direction :int, dx :int, dy :int) :void
+ {
+ if (direction >= DIRECTION_COUNT) {
+ throw new ArgumentError(
+ "Fine coordinates not supported.");
+ }
+
+ switch (direction) {
+ case NORTH: case NORTHWEST: case NORTHEAST: p.y -= dy;
+ }
+ switch (direction) {
+ case SOUTH: case SOUTHWEST: case SOUTHEAST: p.y += dy;
+ }
+ switch (direction) {
+ case WEST: case SOUTHWEST: case NORTHWEST: p.x -= dx;
+ }
+ switch (direction) {
+ case EAST: case SOUTHEAST: case NORTHEAST: p.x += dx;
+ }
+ }
+
+ /** Direction constant string names. */
+ protected static const DIR_STRINGS :Array = [
+ "SOUTHWEST", "WEST", "NORTHWEST", "NORTH",
+ "NORTHEAST", "EAST", "SOUTHEAST", "SOUTH",
+ "WESTSOUTHWEST", "WESTNORTHWEST", "NORTHNORTHWEST", "NORTHNORTHEAST",
+ "EASTNORTHEAST", "EASTSOUTHEAST", "SOUTHSOUTHEAST", "SOUTHSOUTHWEST",
+ ];
+
+ /** Abbreviated direction constant string names. */
+ protected static const SHORT_DIR_STRINGS :Array = [
+ "SW", "W", "NW", "N", "NE", "E", "SE", "S",
+ "WSW", "WNW", "NNW", "NNE", "ENE", "ESE", "SSE", "SSW",
+ ];
+
+ /** Used to rotate a fine compass direction clockwise. */
+ protected static const FINE_CW_ROTATE :Array = [
+ /* SW -> */ WESTSOUTHWEST, /* W -> */ WESTNORTHWEST,
+ /* NW -> */ NORTHNORTHWEST, /* N -> */ NORTHNORTHEAST,
+ /* NE -> */ EASTNORTHEAST, /* E -> */ EASTSOUTHEAST,
+ /* SE -> */ SOUTHSOUTHEAST, /* S -> */ SOUTHSOUTHWEST,
+ /* WSW -> */ WEST, /* WNW -> */ NORTHWEST,
+ /* NNW -> */ NORTH, /* NNE -> */ NORTHEAST,
+ /* ENE -> */ EAST, /* ESE -> */ SOUTHEAST,
+ /* SSE -> */ SOUTH, /* SSW -> */ SOUTHWEST
+ ];
+
+ /** Used to rotate a fine compass direction counter-clockwise. */
+ protected static const FINE_CCW_ROTATE :Array = [
+ /* SW -> */ SOUTHSOUTHWEST, /* W -> */ WESTSOUTHWEST,
+ /* NW -> */ WESTNORTHWEST, /* N -> */ NORTHNORTHWEST,
+ /* NE -> */ NORTHNORTHEAST, /* E -> */ EASTNORTHEAST,
+ /* SE -> */ EASTSOUTHEAST, /* S -> */ SOUTHSOUTHEAST,
+ /* WSW -> */ SOUTHWEST, /* WNW -> */ WEST,
+ /* NNW -> */ NORTHWEST, /* NNE -> */ NORTH,
+ /* ENE -> */ NORTHEAST, /* ESE -> */ EAST,
+ /* SSE -> */ SOUTHEAST, /* SSW -> */ SOUTH
+ ];
+
+ /** Used to map an angle to a fine compass direction. */
+ protected static const ANGLE_MAP :Array = [
+ WEST, WESTNORTHWEST, NORTHWEST, NORTHNORTHWEST, NORTH, NORTHNORTHEAST,
+ NORTHEAST, EASTNORTHEAST, EAST, EASTSOUTHEAST, SOUTHEAST,
+ SOUTHSOUTHEAST, SOUTH, SOUTHSOUTHWEST, SOUTHWEST, WESTSOUTHWEST ];
+}
+}