Behold, Nenya, Ring of Water and repository for our media and animation related

goodies, both Java 2D and LWJGL/JME 3D.


git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@1 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Michael Bayne
2006-06-23 18:07:28 +00:00
commit c2117ee86d
570 changed files with 61913 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
//
// $Id: Log.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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;
/**
* A placeholder class that contains a reference to the log object used by
* the miso package.
*/
public class Log
{
public static com.samskivert.util.Log log =
new com.samskivert.util.Log("miso");
/** Convenience function. */
public static void debug (String message)
{
log.debug(message);
}
/** Convenience function. */
public static void info (String message)
{
log.info(message);
}
/** Convenience function. */
public static void warning (String message)
{
log.warning(message);
}
/** Convenience function. */
public static void logStackTrace (Throwable t)
{
log.logStackTrace(com.samskivert.util.Log.WARNING, t);
}
}
@@ -0,0 +1,61 @@
//
// $Id: MisoConfig.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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;
import com.samskivert.util.Config;
import com.threerings.miso.util.MisoSceneMetrics;
/**
* Provides access to the Miso configuration.
*/
public class MisoConfig
{
/** Provides access to configuration data for this package. */
public static Config config = new Config("rsrc/config/miso/miso");
/**
* Creates scene metrics with information obtained from the deployed
* config file.
*/
public static MisoSceneMetrics getSceneMetrics ()
{
return new MisoSceneMetrics(
config.getValue(TILE_WIDTH_KEY, DEF_TILE_WIDTH),
config.getValue(TILE_HEIGHT_KEY, DEF_TILE_HEIGHT),
config.getValue(FINE_GRAN_KEY, DEF_FINE_GRAN));
}
/** The config key for tile width in pixels. */
protected static final String TILE_WIDTH_KEY = "tile_width";
/** The config key for tile height in pixels. */
protected static final String TILE_HEIGHT_KEY = "tile_height";
/** The config key for tile fine coordinate granularity. */
protected static final String FINE_GRAN_KEY = "fine_granularity";
/** Default scene view parameters. */
protected static final int DEF_TILE_WIDTH = 64;
protected static final int DEF_TILE_HEIGHT = 48;
protected static final int DEF_FINE_GRAN = 4;
}
@@ -0,0 +1,35 @@
//
// $Id: MisoPrefs.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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;
import com.samskivert.util.Config;
/**
* Provides access to runtime configuration parameters for the miso
* package and its subpackages.
*/
public class MisoPrefs
{
/** Used to load our preferences from a properties file and map them
* to the persistent Java preferences repository. */
public static Config config = new Config("rsrc/config/miso");
}
@@ -0,0 +1,680 @@
//
// $Id: DirtyItemList.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Comparator;
import com.samskivert.util.SortableArrayList;
import com.threerings.media.Log;
import com.threerings.media.sprite.Sprite;
import com.threerings.media.tile.ObjectTile;
/**
* The dirty item list keeps track of dirty sprites and object tiles
* in a scene.
*/
public class DirtyItemList
{
/**
* Creates a dirt item list that will handle dirty items for the
* specified view.
*/
public DirtyItemList ()
{
}
/**
* Appends the dirty sprite at the given coordinates to the dirty item
* list.
*
* @param sprite the dirty sprite itself.
* @param tx the sprite's x tile position.
* @param ty the sprite's y tile position.
*/
public void appendDirtySprite (Sprite sprite, int tx, int ty)
{
DirtyItem item = getDirtyItem();
item.init(sprite, tx, ty);
_items.add(item);
}
/**
* Appends the dirty object tile at the given coordinates to the dirty
* item list.
*
* @param scobj the scene object that is dirty.
*/
public void appendDirtyObject (SceneObject scobj)
{
DirtyItem item = getDirtyItem();
item.init(scobj, scobj.info.x, scobj.info.y);
_items.add(item);
}
/**
* Returns the dirty item at the given index in the list.
*/
public DirtyItem get (int idx)
{
return (DirtyItem)_items.get(idx);
}
/**
* Returns an array of the {@link DirtyItem} objects in the list
* sorted in proper rendering order.
*/
public void sort ()
{
int size = size();
if (DEBUG_SORT) {
Log.info("Sorting dirty item list [size=" + size + "].");
}
// if we've only got one item, we need to do no sorting
if (size > 1) {
// get items sorted by increasing origin x-coordinate
_xitems.addAll(_items);
_xitems.sort(ORIGIN_X_COMP);
if (DEBUG_SORT) {
Log.info("Sorted by x-origin " +
"[items=" + toString(_xitems) + "].");
}
// get items sorted by increasing origin y-coordinate
_yitems.addAll(_items);
_yitems.sort(ORIGIN_Y_COMP);
if (DEBUG_SORT) {
Log.info("Sorted by y-origin " +
"[items=" + toString(_yitems) + "].");
}
// sort the items according to the depth of the rear-most tile
_ditems.addAll(_items);
_ditems.sort(REAR_DEPTH_COMP);
// now insertion sort the items from back to front into the
// render-sorted array
_items.clear();
POS_LOOP:
for (int ii = 0; ii < size; ii++) {
DirtyItem item = (DirtyItem)_ditems.get(ii);
for (int rr = _items.size()-1; rr >= 0; rr--) {
DirtyItem pitem = (DirtyItem)_items.get(rr);
// if we render in front of this item, insert
// ourselves immediately following it
if (_rcomp.compare(item, pitem) > 0) {
_items.add(rr+1, item);
continue POS_LOOP;
}
}
// we don't render in front of anyone, so we go at the
// front of the list
_items.add(0, item);
}
// clear out our temporary arrays
_xitems.clear();
_yitems.clear();
_ditems.clear();
}
if (DEBUG_SORT) {
Log.info("Sorted for render [items=" + toString(_items) + "].");
for (int ii = 0, ll = _items.size()-1; ii < ll; ii++) {
DirtyItem a = (DirtyItem)_items.get(ii);
DirtyItem b = (DirtyItem)_items.get(ii+1);
if (_rcomp.compare(a, b) > 0) {
Log.warning("Invalid ordering [a=" + a + ", b=" + b + "].");
}
}
}
}
/**
* Paints all the dirty items in this list using the supplied graphics
* context. The items are removed from the dirty list after being
* painted and the dirty list ends up empty.
*/
public void paintAndClear (Graphics2D gfx)
{
int icount = _items.size();
for (int ii = 0; ii < icount; ii++) {
DirtyItem item = (DirtyItem)_items.get(ii);
item.paint(gfx);
item.clear();
_freelist.add(item);
}
_items.clear();
}
/**
* Clears out any items that were in this list.
*/
public void clear ()
{
for (int icount = _items.size(); icount > 0; icount--) {
DirtyItem item = (DirtyItem)_items.remove(0);
item.clear();
_freelist.add(item);
}
}
/**
* Returns the number of items in the dirty item list.
*/
public int size ()
{
return _items.size();
}
/**
* Obtains a new dirty item instance, reusing an old one if possible
* or creating a new one otherwise.
*/
protected DirtyItem getDirtyItem ()
{
if (_freelist.size() > 0) {
return (DirtyItem)_freelist.remove(0);
} else {
return new DirtyItem();
}
}
/**
* Returns an abbreviated string representation of the given dirty
* item describing only its origin coordinates and render priority.
* Intended for debugging purposes.
*/
protected static String toString (DirtyItem a)
{
StringBuilder buf = new StringBuilder("[");
toString(buf, a);
return buf.append("]").toString();
}
/**
* Returns an abbreviated string representation of the two given dirty
* items. See {@link #toString(DirtyItem)}.
*/
protected static String toString (DirtyItem a, DirtyItem b)
{
StringBuilder buf = new StringBuilder("[");
toString(buf, a);
toString(buf, b);
return buf.append("]").toString();
}
/**
* Returns an abbreviated string representation of the given dirty
* items. See {@link #toString(DirtyItem)}.
*/
protected static String toString (SortableArrayList items)
{
StringBuilder buf = new StringBuilder();
buf.append("[");
for (int ii = 0; ii < items.size(); ii++) {
DirtyItem item = (DirtyItem)items.get(ii);
toString(buf, item);
if (ii < (items.size() - 1)) {
buf.append(", ");
}
}
return buf.append("]").toString();
}
/** Helper function for {@link #toString(DirtyItem)}. */
protected static void toString (StringBuilder buf, DirtyItem item)
{
buf.append("(o:+").append(item.ox).append("+").append(item.oy);
buf.append(" p:").append(item.getRenderPriority()).append(")");
}
/**
* A class to hold the items inserted in the dirty list along with
* all of the information necessary to render their dirty regions
* to the target graphics context when the time comes to do so.
*/
public class DirtyItem
{
/** The dirtied object; one of either a sprite or an object tile. */
public Object obj;
/** The origin tile coordinates. */
public int ox, oy;
/** The leftmost tile coordinates. */
public int lx, ly;
/** The rightmost tile coordinates. */
public int rx, ry;
/**
* Initializes a dirty item.
*/
public void init (Object obj, int x, int y)
{
this.obj = obj;
this.ox = x;
this.oy = y;
// calculate the item's leftmost and rightmost tiles; note
// that sprites occupy only a single tile, so leftmost and
// rightmost tiles are equivalent
lx = rx = ox;
ly = ry = oy;
if (obj instanceof SceneObject) {
ObjectTile tile = ((SceneObject)obj).tile;
lx -= (tile.getBaseWidth() - 1);
ry -= (tile.getBaseHeight() - 1);
}
}
/**
* Paints the dirty item to the given graphics context. Only
* the portion of the item that falls within the given dirty
* rectangle is actually drawn.
*/
public void paint (Graphics2D gfx)
{
if (obj instanceof Sprite) {
((Sprite)obj).paint(gfx);
} else {
((SceneObject)obj).paint(gfx);
}
}
/**
* Returns the "depth" of our rear-most tile.
*/
public int getRearDepth ()
{
return ry + lx;
}
/**
* Returns the render priority for this dirty item. It will be
* zero unless this is a display object which may have a custom
* render priority.
*/
public int getRenderPriority ()
{
if (obj instanceof SceneObject) {
return ((SceneObject)obj).getPriority();
} else {
return 0;
}
}
/**
* Releases all references held by this dirty item so that it
* doesn't inadvertently hold on to any objects while waiting to
* be reused.
*/
public void clear ()
{
obj = null;
}
// documentation inherited
public boolean equals (Object other)
{
// we're never equal to something that's not our kind
if (!(other instanceof DirtyItem)) {
return false;
}
// sprites are equivalent if they're the same sprite
DirtyItem b = (DirtyItem)other;
return obj.equals(b.obj);
}
// documentation inherited
public int hashCode ()
{
return obj.hashCode();
}
/**
* Returns a string representation of the dirty item.
*/
public String toString ()
{
StringBuilder buf = new StringBuilder();
buf.append("[obj=").append(obj);
buf.append(", ox=").append(ox);
buf.append(", oy=").append(oy);
buf.append(", lx=").append(lx);
buf.append(", ly=").append(ly);
buf.append(", rx=").append(rx);
buf.append(", ry=").append(ry);
return buf.append("]").toString();
}
}
/**
* A comparator class for use in sorting dirty items in ascending
* origin x- or y-axis coordinate order.
*/
protected static class OriginComparator implements Comparator
{
/**
* Constructs an origin comparator that sorts dirty items in
* ascending order based on their origin coordinate on the given
* axis.
*/
public OriginComparator (int axis)
{
_axis = axis;
}
// documentation inherited
public int compare (Object a, Object b)
{
DirtyItem da = (DirtyItem)a;
DirtyItem db = (DirtyItem)b;
// if they don't overlap, sort them normally
if (_axis == X_AXIS) {
if (da.ox != db.ox) {
return da.ox - db.ox;
}
} else {
if (da.oy != db.oy) {
return da.oy - db.oy;
}
}
// if they do overlap, incorporate render priority; assume
// non-display objects have a render priority of zero
return da.getRenderPriority() - db.getRenderPriority();
}
/** The axis this comparator sorts on. */
protected int _axis;
}
/**
* A comparator class for use in sorting the dirty sprites and
* objects in a scene in ascending x- and y-coordinate order
* suitable for rendering in the isometric view with proper visual
* results.
*/
protected class RenderComparator implements Comparator
{
// documentation inherited
public int compare (Object a, Object b)
{
DirtyItem da = (DirtyItem)a;
DirtyItem db = (DirtyItem)b;
// if the two objects are scene objects and they overlap, we
// compare them solely based on their human assigned priority
if ((da.obj instanceof SceneObject) &&
(db.obj instanceof SceneObject)) {
SceneObject soa = (SceneObject)da.obj;
SceneObject sob = (SceneObject)db.obj;
if (soa.objectFootprintOverlaps(sob)) {
int result = soa.getPriority() - sob.getPriority();
if (DEBUG_COMPARE) {
String items = DirtyItemList.toString(da, db);
Log.info("compare: overlapping [result=" + result +
", items=" + items + "].");
}
return result;
}
}
// check for partitioning objects on the y-axis
int result = comparePartitioned(Y_AXIS, da, db);
if (result != 0) {
if (DEBUG_COMPARE) {
String items = DirtyItemList.toString(da, db);
Log.info("compare: Y-partitioned " +
"[result=" + result + ", items=" + items + "].");
}
return result;
}
// check for partitioning objects on the x-axis
result = comparePartitioned(X_AXIS, da, db);
if (result != 0) {
if (DEBUG_COMPARE) {
String items = DirtyItemList.toString(da, db);
Log.info("compare: X-partitioned " +
"[result=" + result + ", items=" + items + "].");
}
return result;
}
// use normal iso-ordering check
result = compareNonPartitioned(da, db);
if (DEBUG_COMPARE) {
String items = DirtyItemList.toString(da, db);
Log.info("compare: non-partitioned " +
"[result=" + result + ", items=" + items + "].");
}
return result;
}
/**
* Returns whether two dirty items have a partitioning object
* between them on the given axis.
*/
protected int comparePartitioned (
int axis, DirtyItem da, DirtyItem db)
{
// prepare for the partitioning check
SortableArrayList sitems;
Comparator comp;
boolean swapped = false;
switch (axis) {
case X_AXIS:
if (da.ox == db.ox) {
// can't be partitioned if there's no space between
return 0;
}
// order items for proper comparison
if (da.ox > db.ox) {
DirtyItem temp = da;
da = db;
db = temp;
swapped = true;
}
// use the axis-specific sorted array
sitems = _xitems;
comp = ORIGIN_X_COMP;
break;
case Y_AXIS:
default:
if (da.oy == db.oy) {
// can't be partitioned if there's no space between
return 0;
}
// order items for proper comparison
if (da.oy > db.oy) {
DirtyItem temp = da;
da = db;
db = temp;
swapped = true;
}
// use the axis-specific sorted array
sitems = _yitems;
comp = ORIGIN_Y_COMP;
break;
}
// get the bounding item indices and the number of
// potentially-partitioning dirty items
int aidx = sitems.binarySearch(da, comp);
int bidx = sitems.binarySearch(db, comp);
int size = bidx - aidx - 1;
// check each potentially partitioning item
int startidx = aidx + 1, endidx = startidx + size;
for (int pidx = startidx; pidx < endidx; pidx++) {
DirtyItem dp = (DirtyItem)sitems.get(pidx);
if (dp.obj instanceof Sprite) {
// sprites can't partition things
continue;
} else if ((dp.obj == da.obj) ||
(dp.obj == db.obj)) {
// can't be partitioned by ourselves
continue;
}
// perform the actual partition check for this object
switch (axis) {
case X_AXIS:
if (dp.ly >= da.ry &&
dp.ry <= db.ly &&
dp.lx >= da.rx &&
dp.rx <= db.lx) {
return (swapped) ? 1 : -1;
}
case Y_AXIS:
default:
if (dp.lx <= db.ox &&
dp.rx >= da.lx &&
dp.ry >= da.oy &&
dp.oy <= db.ry) {
return (swapped) ? 1 : -1;
}
}
}
// no partitioning object found
return 0;
}
/**
* Compares the two dirty items assuming there are no partitioning
* objects between them.
*/
protected int compareNonPartitioned (DirtyItem da, DirtyItem db)
{
if (da.ox == db.ox &&
da.oy == db.oy) {
if (da.equals(db)) {
// render level is equal if we're the same sprite
// or an object at the same location
return 0;
}
boolean aIsSprite = (da.obj instanceof Sprite);
boolean bIsSprite = (db.obj instanceof Sprite);
if (aIsSprite && bIsSprite) {
Sprite as = (Sprite)da.obj, bs = (Sprite)db.obj;
// we're comparing two sprites co-existing on the same
// tile, first check their render order
int rocomp = as.getRenderOrder() - bs.getRenderOrder();
if (rocomp != 0) {
return rocomp;
}
// next sort them by y-position
int ydiff = as.getY() - bs.getY();
if (ydiff != 0) {
return ydiff;
}
// if they're at the same height, just use hashCode()
// to establish a consistent arbitrary ordering
return (as.hashCode() - bs.hashCode());
// otherwise, always put a sprite on top of a non-sprite
} else if (aIsSprite) {
return 1;
} else if (bIsSprite) {
return -1;
}
}
// otherwise use a consistent ordering for non-overlappers;
// see narya/docs/miso/render_sort_diagram.png for more info
if (db.lx <= da.ox && db.ry <= da.oy) {
return 1;
} else if (db.rx >= da.lx && db.ly >= da.ry) {
return -1;
} else {
return da.oy - db.oy;
}
}
}
/** The list of dirty items. */
protected SortableArrayList _items = new SortableArrayList();
/** The list of dirty items sorted by x-position. */
protected SortableArrayList _xitems = new SortableArrayList();
/** The list of dirty items sorted by y-position. */
protected SortableArrayList _yitems = new SortableArrayList();
/** The list of dirty items sorted by rear-depth. */
protected SortableArrayList _ditems = new SortableArrayList();
/** The render comparator we'll use for our final, magical sort. */
protected Comparator _rcomp = new RenderComparator();
/** Unused dirty items. */
protected ArrayList _freelist = new ArrayList();
/** Whether to log debug info when comparing pairs of dirty items. */
protected static final boolean DEBUG_COMPARE = false;
/** Whether to log debug info for the main dirty item sorting algorithm. */
protected static final boolean DEBUG_SORT = false;
/** Constants used to denote axis sorting constraints. */
protected static final int X_AXIS = 0;
protected static final int Y_AXIS = 1;
/** The comparator used to sort dirty items in ascending origin
* x-coordinate order. */
protected static final Comparator ORIGIN_X_COMP =
new OriginComparator(X_AXIS);
/** The comparator used to sort dirty items in ascending origin
* y-coordinate order. */
protected static final Comparator ORIGIN_Y_COMP =
new OriginComparator(Y_AXIS);
/** The comparator used to sort dirty items in ascending "rear-depth"
* order. */
protected static final Comparator REAR_DEPTH_COMP = new Comparator() {
public int compare (Object o1, Object o2) {
return (((DirtyItem)o1).getRearDepth() -
((DirtyItem)o2).getRearDepth());
}
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,151 @@
//
// $Id: ObjectActionHandler.java 3917 2006-03-06 23:45:12Z mthomas $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.event.ActionEvent;
import java.util.HashMap;
import javax.swing.Icon;
import com.samskivert.swing.RadialMenu;
import com.samskivert.util.StringUtil;
import com.threerings.miso.Log;
import com.threerings.miso.client.SceneObject;
/**
* Objects in scenes can be configured to generate action events. Those
* events are grouped into types and an object action handler can be
* registered to handle all actions of a particular type.
*/
public class ObjectActionHandler
{
/**
* Returns true if we should allow this object action, false if we
* should not.
*/
public boolean actionAllowed (String action)
{
return true;
}
/**
* Returns true if we should display the text for the action. By default
* this returns whether the action is allowed or not, but can be
* overridden by subclasses. This is used to completely hide actions that
* should not be visible without the proper privileges.
*/
public boolean isVisible (String action)
{
return actionAllowed(action);
}
/**
* Get the human readable object tip for the specified action.
*/
public String getTipText (String action)
{
return action;
}
/**
* Returns the tooltip icon for the specified action or null if the
* action has no tooltip icon.
*/
public Icon getTipIcon (String action)
{
return null;
}
/**
* Return a {@link RadialMenu} or null if no menu needed.
*/
public RadialMenu handlePressed (SceneObject sourceObject)
{
return null;
}
/**
* Called when an action is generated for an object.
*/
public void handleAction (SceneObject scobj, ActionEvent event)
{
Log.warning("Unknown object action [scobj=" + scobj +
", action=" + event + "].");
}
/**
* Returns the type associated with this action command (which is
* mapped to a registered object action handler) or the empty string
* if it has no type.
*/
public static String getType (String command)
{
int cidx = StringUtil.isBlank(command) ? -1 : command.indexOf(':');
return (cidx == -1) ? "" : command.substring(0, cidx);
}
/**
* Returns the unqualified object action (minus the type, see {@link
* #getType}).
*/
public static String getAction (String command)
{
int cidx = StringUtil.isBlank(command) ? -1 : command.indexOf(':');
return (cidx == -1) ? command : command.substring(cidx+1);
}
/**
* Looks up the object action handler associated with the specified
* command.
*/
public static ObjectActionHandler lookup (String command)
{
return (ObjectActionHandler)_oahandlers.get(getType(command));
}
/**
* Registers an object action handler which will be called when a user
* clicks on an object in a scene that has an associated action.
*/
public static void register (String prefix, ObjectActionHandler handler)
{
// make sure we know about potential funny business
if (_oahandlers.containsKey(prefix)) {
Log.warning("Warning! Overwriting previous object action " +
"handler registration, all hell could shortly " +
"break loose [prefix=" + prefix +
", handler=" + handler + "].");
}
_oahandlers.put(prefix, handler);
}
/**
* Removes an object action handler registration.
*/
public static void unregister (String prefix)
{
_oahandlers.remove(prefix);
}
/** Our registered object action handlers. */
protected static HashMap _oahandlers = new HashMap();
}
@@ -0,0 +1,164 @@
//
// $Id: ResolutionView.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import javax.swing.JPanel;
import java.util.HashMap;
import java.util.Iterator;
import com.samskivert.util.IntTuple;
import com.threerings.media.util.MathUtil;
import com.threerings.miso.util.MisoSceneMetrics;
import com.threerings.miso.util.MisoUtil;
/**
* Used to debug scene block resolution visually.
*/
public class ResolutionView extends JPanel
{
public ResolutionView (MisoScenePanel panel)
{
_panel = panel;
_metrics = panel.getSceneMetrics();
}
public Dimension getPreferredSize ()
{
return new Dimension(TILE_SIZE*MAX_WIDTH, TILE_SIZE*MAX_HEIGHT);
}
public synchronized void queuedBlock (SceneBlock block)
{
assignColor(block, Color.yellow);
repaint();
}
public synchronized void resolvingBlock (SceneBlock block)
{
IntTuple key = blockKey(block);
if (_blocks.containsKey(key)) {
assignColor(block, Color.red);
repaint();
}
}
public synchronized void resolvedBlock (SceneBlock block)
{
IntTuple key = blockKey(block);
if (_blocks.containsKey(key)) {
assignColor(block, Color.green);
repaint();
}
}
public synchronized void blockCleared (SceneBlock block)
{
_blocks.remove(blockKey(block));
repaint();
}
public synchronized void newScene ()
{
_blocks.clear();
repaint();
}
protected void assignColor (SceneBlock block, Color color)
{
IntTuple key = blockKey(block);
BlockGlyph glyph = (BlockGlyph)_blocks.get(key);
if (glyph == null) {
glyph = new BlockGlyph(_metrics, key.left, key.right);
_blocks.put(key, glyph);
}
glyph.color = color;
}
public synchronized void paintComponent (Graphics g)
{
super.paintComponent(g);
Graphics2D gfx = (Graphics2D)g;
Rectangle vbounds = _panel.getViewBounds();
gfx.translate((getWidth()-vbounds.width/16)/2 - vbounds.x/16,
(getHeight()-vbounds.height/16)/2 - vbounds.y/16);
AffineTransform xform = gfx.getTransform();
gfx.scale(0.25, 0.25);
// draw our block glyphs
for (Iterator iter = _blocks.values().iterator(); iter.hasNext(); ) {
((BlockGlyph)iter.next()).paint(gfx);
}
// draw the view bounds
gfx.scale(0.25, 0.25);
gfx.draw(vbounds);
gfx.setColor(Color.red);
gfx.draw(_panel.getInfluentialBounds());
gfx.setTransform(xform);
}
protected final IntTuple blockKey (SceneBlock block)
{
Rectangle bounds = block.getBounds();
return new IntTuple(MathUtil.floorDiv(bounds.x, bounds.width),
MathUtil.floorDiv(bounds.y, bounds.height));
}
protected static class BlockGlyph
{
public Color color;
public BlockGlyph (MisoSceneMetrics metrics, int bx, int by)
{
_bpoly = MisoUtil.getTilePolygon(metrics, bx, by);
}
public void paint (Graphics2D gfx)
{
gfx.setColor(color);
gfx.fill(_bpoly);
gfx.setColor(Color.black);
gfx.draw(_bpoly);
}
protected Polygon _bpoly;
}
protected MisoScenePanel _panel;
protected MisoSceneMetrics _metrics;
protected HashMap _blocks = new HashMap();
protected static final int TILE_SIZE = 10;
protected static final int MAX_WIDTH = 30;
protected static final int MAX_HEIGHT = 30;
}
@@ -0,0 +1,596 @@
//
// $Id: SceneBlock.java 3628 2005-06-28 17:42:55Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.Polygon;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
import com.threerings.geom.GeomUtil;
import com.threerings.media.tile.NoSuchTileSetException;
import com.threerings.media.tile.ObjectTile;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TileUtil;
import com.threerings.media.util.MathUtil;
import com.threerings.miso.Log;
import com.threerings.miso.data.MisoSceneModel;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.tile.BaseTile;
import com.threerings.miso.util.MisoUtil;
import com.threerings.miso.util.ObjectSet;
/**
* Contains the base and object tile information on a particular
* rectangular region of a scene.
*/
public class SceneBlock
{
/**
* Creates a scene block and resolves the base and object tiles that
* reside therein.
*/
public SceneBlock (
MisoScenePanel panel, int tx, int ty, int width, int height)
{
_panel = panel;
_bounds = new Rectangle(tx, ty, width, height);
_base = new BaseTile[width*height];
_fringe = new BaseTile[width*height];
_covered = new boolean[width*height];
// compute our screen-coordinate footprint polygon
_footprint = MisoUtil.getFootprintPolygon(
panel.getSceneMetrics(), tx, ty, width, height);
// the rest of our resolution will happen in resolve()
}
/**
* Makes a note that this block was considered to be visible at the
* time it was created. This is purely for debugging purposes.
*/
public void setVisiBlock (boolean visi)
{
_visi = visi;
}
/**
* This method is called by the {@link SceneBlockResolver} on the
* block resolution thread to allow us to load up our image data
* without blocking the AWT thread.
*/
protected boolean resolve ()
{
// if we got canned before we were resolved, go ahead and bail now
if (_panel.getBlock(_bounds.x, _bounds.y) != this) {
// Log.info("Not resolving abandoned block " + this + ".");
_panel.blockAbandoned(this);
return false;
}
_panel.blockResolving(this);
// start with the bounds of the footprint polygon
Rectangle sbounds = new Rectangle(_footprint.getBounds());
Rectangle obounds = null;
// resolve our base tiles
long now = System.currentTimeMillis();
int baseCount = 0, fringeCount = 0;
MisoSceneModel model = _panel.getSceneModel();
for (int yy = 0; yy < _bounds.height; yy++) {
for (int xx = 0; xx < _bounds.width; xx++) {
int x = _bounds.x + xx, y = _bounds.y + yy;
int fqTileId = model.getBaseTileId(x, y);
if (fqTileId <= 0) {
continue;
}
// load up this base tile
updateBaseTile(fqTileId, x, y);
baseCount++;
// if there's no tile here, we don't need no fringe
int tidx = index(x, y);
if (_base[tidx] == null) {
continue;
}
// compute the fringe for this tile
_fringe[tidx] = _panel.computeFringeTile(x, y);
fringeCount++;
}
}
// DEBUG: check for long resolution times
long stamp = System.currentTimeMillis();
long elapsed = stamp - now;
if (elapsed > 500L) {
Log.warning("Base and fringe resolution took long time " +
"[block=" + this + ", baseCount=" + baseCount +
", fringeCount=" + fringeCount +
", elapsed=" + elapsed + "].");
}
// resolve our objects
ObjectSet set = new ObjectSet();
model.getObjects(_bounds, set);
ArrayList scobjs = new ArrayList();
now = System.currentTimeMillis();
for (int ii = 0, ll = set.size(); ii < ll; ii++) {
SceneObject scobj = new SceneObject(_panel, set.get(ii));
// ignore this object if it failed to resolve
if (scobj.bounds == null) {
continue;
}
sbounds.add(scobj.bounds);
obounds = GeomUtil.grow(obounds, scobj.bounds);
scobjs.add(scobj);
// DEBUG: check for long resolution times
stamp = System.currentTimeMillis();
elapsed = stamp - now;
now = stamp;
if (elapsed > 250L) {
Log.warning("Scene object took look time to resolve " +
"[block=" + this + ", scobj=" + scobj +
", elapsed=" + elapsed + "].");
}
}
_objects = (SceneObject[])scobjs.toArray(
new SceneObject[scobjs.size()]);
// resolve our default tileset
int bsetid = model.getDefaultBaseTileSet();
try {
if (bsetid > 0) {
_defset = _panel.getTileManager().getTileSet(bsetid);
}
} catch (Exception e) {
Log.warning("Unable to fetch default base tileset [tsid=" + bsetid +
", error=" + e + "].");
}
// this both marks us as resolved and makes all our other updated
// fields visible
synchronized (this) {
_obounds = obounds;
_sbounds = sbounds;
}
return true;
}
/**
* This is called by the {@link SceneBlockResolver} on the AWT thread
* when our resolution has completed. We inform our containing panel.
*/
protected void wasResolved ()
{
_panel.blockResolved(this);
}
/**
* Returns true if this block has been resolved, false if not.
*/
protected synchronized boolean isResolved ()
{
return _sbounds != null;
}
/**
* Returns the bounds of this block, in tile coordinates.
*/
public Rectangle getBounds ()
{
return _bounds;
}
/**
* Returns the bounds of the screen coordinate rectangle that contains
* all pixels that are drawn on by all tiles and objects in this
* block.
*/
public Rectangle getScreenBounds ()
{
return _sbounds;
}
/**
* Returns the bounds of the screen coordinate rectangle that contains
* all pixels that are drawn on by all objects (but not base tiles) in
* this block. <em>Note:</em> this will return <code>null</code> if
* the block has no objects.
*/
public Rectangle getObjectBounds ()
{
return _obounds;
}
/**
* Returns the screen-coordinate polygon bounding the footprint of
* this block.
*/
public Polygon getFootprint ()
{
return _footprint;
}
/**
* Returns an array of all resolved scene objects in this block.
*/
public SceneObject[] getObjects ()
{
return _objects;
}
/**
* Returns the base tile at the specified coordinates or null if
* there's no tile at said coordinates.
*/
public BaseTile getBaseTile (int tx, int ty)
{
BaseTile tile = _base[index(tx, ty)];
if (tile == null && _defset != null) {
tile = (BaseTile)_defset.getTile(
TileUtil.getTileHash(tx, ty) % _defset.getTileCount());
}
return tile;
}
/**
* Returns the fringe tile at the specified coordinates or null if
* there's no tile at said coordinates.
*/
public BaseTile getFringeTile (int tx, int ty)
{
return _fringe[index(tx, ty)];
}
/**
* Informs this scene block that the specified base tile has been
* changed.
*/
public void updateBaseTile (int fqTileId, int tx, int ty)
{
String errmsg = null;
int tidx = index(tx, ty);
// this is a bit magical: we pass the fully qualified tile id to
// the tile manager which loads up from the configured tileset
// repository the appropriate tileset (which should be a
// BaseTileSet) and then extracts the appropriate base tile (the
// index of which is also in the fqTileId)
try {
if (fqTileId <= 0) {
_base[tidx] = null;
} else {
_base[tidx] = (BaseTile)
_panel.getTileManager().getTile(fqTileId);
}
// clear out the fringe (it must be recomputed by the caller)
_fringe[tidx] = null;
} catch (ClassCastException cce) {
errmsg = "Scene contains non-base tile in base layer";
} catch (NoSuchTileSetException nste) {
errmsg = "Scene contains non-existent tileset";
}
if (errmsg != null) {
Log.warning(errmsg + " [fqtid=" + fqTileId +
", x=" + tx + ", y=" + ty + "].");
}
}
/**
* Instructs this block to recompute its fringe at the specified
* location.
*/
public void updateFringe (int tx, int ty)
{
int tidx = index(tx, ty);
if (_base[tidx] != null) {
_fringe[tidx] = _panel.computeFringeTile(tx, ty);
}
}
/**
* Adds the supplied object to this block. Coverage is not computed
* for the added object, a subsequent call to {@link #update} will be
* needed.
*
* @return true if the object was added, false if it was not because
* another object of the same type already occupies that location.
*/
public boolean addObject (ObjectInfo info)
{
// make sure we don't already have this same object at these
// coordinates
for (int ii = 0; ii < _objects.length; ii++) {
if (_objects[ii].info.equals(info)) {
return false;
}
}
_objects = (SceneObject[])
ArrayUtil.append(_objects, new SceneObject(_panel, info));
// clear out our neighbors array so that the subsequent update
// causes us to recompute our coverage
Arrays.fill(_neighbors, null);
return true;
}
/**
* Removes the specified object from this block. Coverage is not
* recomputed, so a subsequent call to {@link #update} will be needed.
*
* @return true if the object was deleted, false if it was not found
* in our object list.
*/
public boolean deleteObject (ObjectInfo info)
{
int oidx = -1;
for (int ii = 0; ii < _objects.length; ii++) {
if (_objects[ii].info.equals(info)) {
oidx = ii;
break;
}
}
if (oidx == -1) {
return false;
}
_objects = (SceneObject[])ArrayUtil.splice(_objects, oidx, 1);
// clear out our neighbors array so that the subsequent update
// causes us to recompute our coverage
Arrays.fill(_neighbors, null);
return true;
}
/**
* Returns true if the specified traverser can traverse the specified
* tile (which is assumed to be in the bounds of this scene block).
*/
public boolean canTraverse (Object traverser, int tx, int ty)
{
if (_covered[index(tx, ty)]) {
return false;
}
// null base or impassable base kills traversal
BaseTile base = getBaseTile(tx, ty);
if ((base == null) || !base.isPassable()) {
return false;
}
// fringe can only kill traversal if it is present
BaseTile fringe = getFringeTile(tx, ty);
return (fringe == null) || fringe.isPassable();
}
/**
* Computes the memory usage of the base and object tiles in this
* scene block; registering counted tiles in the hash map so that
* other blocks can be sure not to double count them. Base tile usage
* is placed into the zeroth array element, fringe tile usage into the
* first and object tile usage into the second.
*/
public void computeMemoryUsage (
HashMap bases, HashSet fringes, HashMap objects, long[] usage)
{
// account for our base tiles
MisoSceneModel model = _panel.getSceneModel();
for (int yy = 0; yy < _bounds.height; yy++) {
for (int xx = 0; xx < _bounds.width; xx++) {
int x = _bounds.x + xx, y = _bounds.y + yy;
int tidx = index(x, y);
BaseTile base = _base[tidx];
if (base == null) {
continue;
}
BaseTile sbase = (BaseTile)bases.get(base.key);
if (sbase == null) {
bases.put(base.key, base);
usage[0] += base.getEstimatedMemoryUsage();
} else if (base != _base[tidx]) {
Log.warning("Multiple instances of same base tile " +
"[base=" + base +
", x=" + xx + ", y=" + yy + "].");
usage[0] += base.getEstimatedMemoryUsage();
}
// now account for the fringe
if (_fringe[tidx] == null) {
continue;
} else if (!fringes.contains(_fringe[tidx])) {
fringes.add(_fringe[tidx]);
usage[1] += _fringe[tidx].getEstimatedMemoryUsage();
}
}
}
// now get the object tiles
int ocount = (_objects == null) ? 0 : _objects.length;
for (int ii = 0; ii < ocount; ii++) {
SceneObject scobj = _objects[ii];
ObjectTile tile = (ObjectTile)objects.get(scobj.tile.key);
if (tile == null) {
objects.put(scobj.tile.key, scobj.tile);
usage[2] += scobj.tile.getEstimatedMemoryUsage();
} else if (tile != scobj.tile) {
Log.warning("Multiple instances of same object tile: " +
scobj.info + ".");
usage[2] += scobj.tile.getEstimatedMemoryUsage();
}
}
}
/**
* Returns a string representation of this instance.
*/
public String toString ()
{
int bx = MathUtil.floorDiv(_bounds.x, _bounds.width);
int by = MathUtil.floorDiv(_bounds.y, _bounds.height);
return StringUtil.coordsToString(bx, by) + ":" +
StringUtil.toString(_bounds) + ":" +
((_objects == null) ? 0 : _objects.length) +
(_visi ? ":v" : ":i");
}
/**
* Returns the index into our arrays of the specified tile.
*/
protected final int index (int tx, int ty)
{
// if (!_bounds.contains(tx, ty)) {
// String errmsg = "Coordinates out of bounds: +" + tx + "+" + ty +
// " not in " + StringUtil.toString(_bounds);
// throw new IllegalArgumentException(errmsg);
// }
return (ty-_bounds.y)*_bounds.width + (tx-_bounds.x);
}
/**
* Links this block to its neighbors; informs neighboring blocks of
* object coverage.
*/
protected void update (HashIntMap blocks)
{
boolean recover = false;
// link up to our neighbors
for (int ii = 0; ii < DX.length; ii++) {
SceneBlock neigh = (SceneBlock)
blocks.get(neighborKey(DX[ii], DY[ii]));
if (neigh != _neighbors[ii]) {
_neighbors[ii] = neigh;
// if we're linking up to a neighbor for the first time;
// we need to recalculate our coverage
recover = recover || (neigh != null);
// Log.info(this + " was introduced to " + neigh + ".");
}
}
// if we need to regenerate the set of tiles covered by our
// objects, do so
if (recover) {
for (int ii = 0; ii < _objects.length; ii++) {
setCovered(blocks, _objects[ii]);
}
}
}
/** Computes the key of our neighbor. */
protected final int neighborKey (int dx, int dy)
{
int nx = MathUtil.floorDiv(_bounds.x, _bounds.width)+dx;
int ny = MathUtil.floorDiv(_bounds.y, _bounds.height)+dy;
return MisoScenePanel.compose(nx, ny);
}
/** Computes the key for the block that holds the specified tile. */
protected final int blockKey (int tx, int ty)
{
int bx = MathUtil.floorDiv(tx, _bounds.width);
int by = MathUtil.floorDiv(ty, _bounds.height);
return MisoScenePanel.compose(bx, by);
}
/**
* Sets the footprint of this object tile
*/
protected void setCovered (HashIntMap blocks, SceneObject scobj)
{
int endx = scobj.info.x - scobj.tile.getBaseWidth() + 1;
int endy = scobj.info.y - scobj.tile.getBaseHeight() + 1;
for (int xx = scobj.info.x; xx >= endx; xx--) {
for (int yy = scobj.info.y; yy >= endy; yy--) {
SceneBlock block = (SceneBlock)blocks.get(blockKey(xx, yy));
if (block != null) {
block.setCovered(xx, yy);
}
}
}
// Log.info("Updated coverage " + scobj.info + ".");
}
/**
* Indicates that this tile is covered by an object footprint.
*/
protected void setCovered (int tx, int ty)
{
_covered[index(tx, ty)] = true;
}
/** The panel for which we contain a block. */
protected MisoScenePanel _panel;
/** The bounds of (in tile coordinates) of this block. */
protected Rectangle _bounds;
/** The bounds (in screen coords) of all images rendered by this block. */
protected Rectangle _sbounds;
/** The bounds (in screen coords) of all objects rendered by this block. */
protected Rectangle _obounds;
/** A polygon bounding the footprint of this block. */
protected Polygon _footprint;
/** Used to return a tile where we have none. */
protected TileSet _defset;
/** Our base tiles. */
protected BaseTile[] _base;
/** Our fringe tiles. */
protected BaseTile[] _fringe;
/** Indicates whether our tiles are covered by an object. */
protected boolean[] _covered;
/** Info on our objects. */
protected SceneObject[] _objects;
/** Our neighbors in the eight cardinal directions. */
protected SceneBlock[] _neighbors = new SceneBlock[DX.length];
/** A debug flag indicating whether we were visible at creation. */
protected boolean _visi;
// used to link up to our neighbors
protected static final int[] DX = { -1, -1, 0, 1, 1, 1, 0, -1 };
protected static final int[] DY = { 0, -1, -1, -1, 0, 1, 1, 1 };
}
@@ -0,0 +1,138 @@
//
// $Id: SceneBlockResolver.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.EventQueue;
import com.samskivert.util.Histogram;
import com.samskivert.util.LoopingThread;
import com.samskivert.util.Queue;
import com.threerings.miso.Log;
/**
* A separate thread for resolving miso scene blocks.
*/
public class SceneBlockResolver extends LoopingThread
{
/**
* Queues up a scene block for resolution.
*/
public void resolveBlock (SceneBlock block, boolean hipri)
{
Log.debug("Queueing block for resolution " + block +
" (" + hipri + ").");
if (hipri) {
_queue.prepend(block);
} else {
_queue.append(block);
}
}
/**
* Temporarily suspends the scene block resolution thread.
*/
public synchronized void suspendResolution ()
{
_resolving = false;
}
/**
* Restores the operation of the scene block resolution thread after a
* previous call to {@link #suspendResolution}.
*/
public synchronized void restoreResolution ()
{
_resolving = true;
notify();
}
/**
* Returns the number of scene blocks on the resolution queue.
*/
public int queueSize ()
{
return _queue.size();
}
// documentation inherited
public void iterate ()
{
final SceneBlock block = (SceneBlock)_queue.get();
while (!_resolving) {
synchronized (this) {
try {
wait();
} catch (InterruptedException ie) {
Log.info("Resolver interrupted.");
}
}
}
try {
long start = System.currentTimeMillis();
Log.debug("Resolving block " + block + ".");
if (block.resolve()) {
Log.debug("Resolved block " + block + ".");
}
long elapsed = System.currentTimeMillis() - start;
_histo.addValue((int)elapsed);
// warn if a block takes a long time to resolve
if (elapsed > LONG_RESOLVE_TIME) {
Log.warning("Block took long time to resolve [block=" + block +
", elapsed=" + elapsed + "ms].");
}
// queue it up on the AWT thread to complete its resolution
final boolean report = (_queue.size() == 0);
EventQueue.invokeLater(new Runnable() {
public void run () {
// let the block's panel know that it is resolved
block.wasResolved();
// report statistics
// if (report) {
// Log.info("Resolution histogram " +
// _histo.summarize() + ".");
// }
}
});
} catch (Exception e) {
Log.warning("Block failed during resolution " + block + ".");
Log.logStackTrace(e);
}
}
/** The invoker's queue of units to be executed. */
protected Queue _queue = new Queue();
/** Indicates whether or not we are resolving or suspended. */
protected boolean _resolving = true;
/** Used to time block loading. */
protected Histogram _histo = new Histogram(0, 25, 100);
/** Blocks shouldn't take too long to resolve. */
protected static final long LONG_RESOLVE_TIME = 500L;
}
@@ -0,0 +1,358 @@
//
// $Id: SceneObject.java 4007 2006-04-10 08:59:30Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import com.samskivert.swing.RuntimeAdjust;
import com.samskivert.util.StringUtil;
import com.threerings.media.tile.NoSuchTileSetException;
import com.threerings.media.tile.ObjectTile;
import com.threerings.media.tile.TileUtil;
import com.threerings.miso.Log;
import com.threerings.miso.MisoPrefs;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.util.MisoSceneMetrics;
import com.threerings.miso.util.MisoUtil;
/**
* Contains resolved information on an object in a scene.
*/
public class SceneObject
{
/** The object's info record. */
public ObjectInfo info;
/** The object tile used to display this object. */
public ObjectTile tile;
/** The screen coordinate bounds of our object tile given its position
* in the scene. */
public Rectangle bounds;
/**
* Creates a scene object for display by the specified panel. The
* appropriate object tile is resolved and the object's in-situ bounds
* are computed.
*/
public SceneObject (MisoScenePanel panel, ObjectInfo info)
{
this.info = info;
// resolve our object tile
refreshObjectTile(panel);
}
/**
* Creates a scene object for display by the specified panel.
*/
public SceneObject (MisoScenePanel panel, ObjectInfo info, ObjectTile tile)
{
this(panel.getSceneMetrics(), info, tile);
}
/**
* Creates a scene object for display according to the supplied metrics.
*/
public SceneObject (
MisoSceneMetrics metrics, ObjectInfo info, ObjectTile tile)
{
this.info = info;
this.tile = tile;
computeInfo(metrics);
}
/**
* Used to flag overlapping scene objects that have no resolving
* object priorities.
*/
public void setWarning (boolean warning)
{
_warning = warning;
}
/**
* Requests that this scene object render itself.
*/
public void paint (Graphics2D gfx)
{
if (_hideObjects.getValue()) {
return;
}
// if we're rendering footprints, paint that
boolean footpaint = _fprintDebug.getValue();
if (footpaint) {
gfx.setColor(Color.black);
gfx.draw(_footprint);
}
// if we have a warning, render an alpha'd red rectangle over our
// bounds
if (_warning) {
Composite ocomp = gfx.getComposite();
gfx.setComposite(ALPHA_WARN);
gfx.fill(bounds);
gfx.setComposite(ocomp);
}
// paint our tile
tile.paint(gfx, bounds.x, bounds.y);
// and possibly paint the object's spot
if (footpaint && _sspot != null) {
// translate the origin to center on the portal
gfx.translate(_sspot.x, _sspot.y);
// rotate to reflect the spot orientation
double rot = (Math.PI / 4.0f) * tile.getSpotOrient();
gfx.rotate(rot);
// draw the spot triangle
gfx.setColor(Color.green);
gfx.fill(_spotTri);
// outline the triangle in black
gfx.setColor(Color.black);
gfx.draw(_spotTri);
// restore the original transform
gfx.rotate(-rot);
gfx.translate(-_sspot.x, -_sspot.y);
}
}
/**
* Returns the location associated with this object's "spot" in fine
* coordinates or null if it has no spot.
*/
public Point getObjectSpot ()
{
return _fspot;
}
/**
* Returns the location associated with this object's "spot" in screen
* coordinates or null if it has no spot.
*/
public Point getObjectScreenSpot ()
{
return _sspot;
}
/**
* Returns true if this object's footprint overlaps that of the
* specified other object.
*/
public boolean objectFootprintOverlaps (SceneObject so)
{
return _frect.intersects(so._frect);
}
/**
* Returns true if this object's footprint overlaps the supplied tile
* coordinate rectangle.
*/
public boolean objectFootprintOverlaps (Rectangle rect)
{
return _frect.intersects(rect);
}
/**
* Returns a polygon bounding all footprint tiles of this scene
* object.
*
* @return the bounding polygon.
*/
public Polygon getObjectFootprint ()
{
return _footprint;
}
/**
* Returns the render priority of this scene object.
*/
public int getPriority ()
{
// if we have no overridden priority, return our object tile's
// default priority
return (info.priority == 0 && tile != null) ?
tile.getPriority() : info.priority;
}
/**
* Overrides the render priority of this object.
*/
public void setPriority (byte priority)
{
info.priority = (byte)Math.max(
Byte.MIN_VALUE, Math.min(Byte.MAX_VALUE, priority));
}
/**
* Informs this scene object that the mouse is now hovering over it.
* Custom objects may wish to adjust some internal state and return
* true from this method indicating that they should be repainted.
*/
public boolean setHovered (boolean hovered)
{
return false;
}
/**
* Updates this object's origin tile coordinate. It's bounds and other
* cached screen coordinate information are updated.
*/
public void relocateObject (MisoSceneMetrics metrics, int tx, int ty)
{
// Log.info("Relocating object " + this + " to " +
// StringUtil.coordsToString(tx, ty));
info.x = tx;
info.y = ty;
computeInfo(metrics);
}
/**
* Reloads and recolorizes our object tile. It is not intended for the
* actual object tile used by a scene object to change in its
* lifetime, only attributes of that object like its colorizations. So
* don't do anything crazy like change our {@link ObjectInfo}'s
* <code>tileId</code> and call this method or things might break.
*/
public void refreshObjectTile (MisoScenePanel panel)
{
int tsid = TileUtil.getTileSetId(info.tileId);
int tidx = TileUtil.getTileIndex(info.tileId);
try {
tile = (ObjectTile)panel.getTileManager().getTile(
tsid, tidx, panel.getColorizer(info));
computeInfo(panel.getSceneMetrics());
} catch (NoSuchTileSetException te) {
Log.warning("Scene contains non-existent object tileset " +
"[info=" + info + "].");
}
}
/**
* Computes our screen bounds, tile footprint and other useful cached
* metrics.
*/
protected void computeInfo (MisoSceneMetrics metrics)
{
// start with the screen coordinates of our origin tile
Point tpos = MisoUtil.tileToScreen(
metrics, info.x, info.y, new Point());
// if the tile has an origin coordinate, use that, otherwise
// compute it from the tile footprint
int tox = tile.getOriginX(), toy = tile.getOriginY();
if (tox == Integer.MIN_VALUE) {
tox = tile.getBaseWidth() * metrics.tilehwid;
}
if (toy == Integer.MIN_VALUE) {
toy = tile.getHeight();
}
bounds = new Rectangle(tpos.x + metrics.tilehwid - tox,
tpos.y + metrics.tilehei - toy,
tile.getWidth(), tile.getHeight());
// compute our object footprint as well
_frect = new Rectangle(info.x - tile.getBaseWidth() + 1,
info.y - tile.getBaseHeight() + 1,
tile.getBaseWidth(), tile.getBaseHeight());
_footprint = MisoUtil.getFootprintPolygon(
metrics, _frect.x, _frect.y, _frect.width, _frect.height);
// compute our object spot if we've got one
if (tile.hasSpot()) {
_fspot = MisoUtil.tilePlusFineToFull(
metrics, info.x, info.y, tile.getSpotX(), tile.getSpotY(),
new Point());
_sspot = MisoUtil.fullToScreen(
metrics, _fspot.x, _fspot.y, new Point());
}
// Log.info("Computed object metrics " +
// "[tpos=" + StringUtil.coordsToString(tx, ty) +
// ", info=" + info +
// ", sbounds=" + StringUtil.toString(bounds) + "].");
}
/**
* Returns a string representation of this instance.
*/
public String toString ()
{
return info + "[" + StringUtil.toString(bounds) + "]";
}
/** Our object as a tile coordinate rectangle. */
protected Rectangle _frect;
/** Our object footprint as a polygon. */
protected Polygon _footprint;
/** The full-coordinates of our object spot; or null if we have none. */
protected Point _fspot;
/** The screen-coordinates of our object spot; or null if we have none. */
protected Point _sspot;
/** Used to mark objects with errors. */
protected boolean _warning;
/** A debug hook that toggles rendering of objects. */
protected static RuntimeAdjust.BooleanAdjust _hideObjects =
new RuntimeAdjust.BooleanAdjust(
"Toggles rendering of objects in the scene view.",
"narya.miso.hide_objects", MisoPrefs.config, false);
/** A debug hook that toggles rendering of object footprints. */
protected static RuntimeAdjust.BooleanAdjust _fprintDebug =
new RuntimeAdjust.BooleanAdjust(
"Toggles rendering of object footprints in the scene view.",
"narya.miso.iso_fprint_debug_render", MisoPrefs.config, false);
/** The triangle used to render an object's spot. */
protected static Polygon _spotTri;
static {
_spotTri = new Polygon();
_spotTri.addPoint(-3, -3);
_spotTri.addPoint(3, -3);
_spotTri.addPoint(0, 3);
};
/** The alpha used to fill our bounds for warning purposes. */
protected static final Composite ALPHA_WARN =
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);
}
@@ -0,0 +1,48 @@
//
// $Id: SceneObjectActionEvent.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.event.ActionEvent;
/**
* An {@link ActionEvent} derivation that is fired when a scene object is
* clicked or menu item selected.
*/
public class SceneObjectActionEvent extends ActionEvent
{
public SceneObjectActionEvent (Object source, int id, String action,
int modifiers, SceneObject scobj)
{
super(source, id, action, modifiers);
_scobj = scobj;
}
/**
* Returns the scene object that was the source of this action.
*/
public SceneObject getSceneObject ()
{
return _scobj;
}
protected SceneObject _scobj;
}
@@ -0,0 +1,206 @@
//
// $Id: SceneObjectTip.java 4007 2006-04-10 08:59:30Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.Collection;
import javax.swing.Icon;
import javax.swing.UIManager;
import com.samskivert.swing.Label;
import com.samskivert.swing.LabelSausage;
import com.samskivert.util.ComparableArrayList;
import com.samskivert.util.StringUtil;
/**
* A lightweight tooltip used by the {@link MisoScenePanel}. The tip
* foreground and background are controlled by the following {@link
* UIManager} properties:
*
* <pre>
* SceneObjectTip.background
* SceneObjectTip.foreground
* SceneObjectTip.font (falls back to Label.font)
* </pre>
*/
public class SceneObjectTip extends LabelSausage
{
/**
* Used to position a scene tip in relation to the object with which
* it is associated.
*/
public static interface TipLayout
{
/**
* Position the supplied tip relative to the supplied scene object.
*/
public void layout (Graphics2D gfx, Rectangle boundary,
SceneObject tipFor, SceneObjectTip tip);
}
/** The bounding box of this tip, or null prior to layout(). */
public Rectangle bounds;
/**
* Construct a SceneObjectTip.
*/
public SceneObjectTip (String text, Icon icon)
{
super(new Label(text, _foreground, _font), icon);
}
/**
* Called to initialize the tip so that it can be painted.
*
* @param tipFor the scene object that we're a tip for.
* @param boundary the boundary of all displayable space.
* @param othertips other tip boundaries that we should avoid.
*/
public void layout (Graphics2D gfx, SceneObject tipFor, Rectangle boundary,
Collection othertips)
{
layout(gfx, ICON_PAD, EXTRA_PAD);
bounds = new Rectangle(_size);
// locate the most appropriate tip layout
for (int ii = 0, ll = _layouts.size(); ii < ll; ii++) {
LayoutReg reg = (LayoutReg)_layouts.get(ii);
String act = tipFor.info.action == null ? "" : tipFor.info.action;
if (act.startsWith(reg.prefix)) {
reg.layout.layout(gfx, boundary, tipFor, this);
break;
}
}
}
/**
* Paint this tip at it's location.
*/
public void paint (Graphics2D gfx)
{
paint(gfx, bounds.x, bounds.y, _background, null);
}
/**
* Generates a string representation of this instance.
*/
public String toString ()
{
return _label.getText() + "[" + StringUtil.toString(bounds) + "]";
}
/**
* It may be desirable to layout object tips specially depending on
* what sort of actions they represent, so we allow different tip
* layout algorithms to be registered for particular object prefixes.
* The registration is simply a list searched from longest string to
* shortest string for the first match to an object's action.
*/
public static void registerTipLayout (String prefix, TipLayout layout)
{
LayoutReg reg = new LayoutReg();
reg.prefix = prefix;
reg.layout = layout;
_layouts.insertSorted(reg);
}
// documentation inherited
protected void drawBase (Graphics2D gfx, int x, int y)
{
Composite ocomp = gfx.getComposite();
gfx.setComposite(ALPHA);
super.drawBase(gfx, x, y);
gfx.setComposite(ocomp);
}
/** The alpha we use for our base. */
protected static final Composite ALPHA = AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, .75f);
/** Colors to use when rendering the tip. */
protected static Color _background, _foreground;
/** The font to use when rendering the tip. */
protected static Font _font;
// initialize resources shared by all tips
static {
_background = UIManager.getColor("SceneObjectTip.background");
_foreground = UIManager.getColor("SceneObjectTip.foreground");
_font = UIManager.getFont("SceneObjectTip.font");
if (_font == null) {
_font = UIManager.getFont("Label.font");
}
}
/** Used to store {@link TipLayout} registrations. */
protected static class LayoutReg implements Comparable
{
/** The prefix that defines our applicability. */
public String prefix;
/** The layout to use for objects matching our prefix. */
public TipLayout layout;
// documentation inherited from interface
public int compareTo (Object o) {
LayoutReg or = (LayoutReg)o;
if (or.prefix.length() == prefix.length()) {
return or.prefix.compareTo(prefix);
} else {
return or.prefix.length() - prefix.length();
}
}
}
/** Our default tip layout algorithm which centers the tip in the
* bounds of the object in question. */
protected static class DefaultLayout implements TipLayout
{
public void layout (Graphics2D gfx, Rectangle boundary,
SceneObject tipFor, SceneObjectTip tip) {
tip.bounds.setLocation(
tipFor.bounds.x + (tipFor.bounds.width-tip.bounds.width) / 2,
tipFor.bounds.y + (tipFor.bounds.height-tip.bounds.height) / 2);
}
}
/** Contains a sorted list of layout registrations. */
protected static ComparableArrayList _layouts = new ComparableArrayList();
/** The number of pixels to pad around the icon. */
protected static final int ICON_PAD = 4;
/** The number of pixels to pad between the icon and text. */
protected static final int EXTRA_PAD = 2;
static {
registerTipLayout("", new DefaultLayout());
}
}
@@ -0,0 +1,144 @@
//
// $Id: TilePath.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.Point;
import java.util.List;
import com.threerings.util.DirectionCodes;
import com.threerings.media.sprite.Sprite;
import com.threerings.media.util.LineSegmentPath;
import com.threerings.media.util.MathUtil;
import com.threerings.miso.util.MisoSceneMetrics;
import com.threerings.miso.util.MisoUtil;
/**
* The tile path represents a path of tiles through a scene. The path is
* traversed by treating each pair of connected tiles as a line segment.
* Only ambulatory sprites can follow a tile path, and their tile
* coordinates are updated as the path is traversed.
*/
public class TilePath extends LineSegmentPath
implements DirectionCodes
{
/**
* Constructs a tile path.
*
* @param metrics the metrics for the scene the with which the path is
* associated.
* @param sprite the sprite to follow the path.
* @param tiles the tiles to be traversed during the path.
* @param destx the destination x-position in screen pixel
* coordinates.
* @param desty the destination y-position in screen pixel
* coordinates.
*/
public TilePath (MisoSceneMetrics metrics, Sprite sprite,
List tiles, int destx, int desty)
{
// constrain destination pixels to fine coordinates
Point fpos = new Point();
MisoUtil.screenToFull(metrics, destx, desty, fpos);
// add the starting path node
int sx = sprite.getX(), sy = sprite.getY();
Point ipos = MisoUtil.screenToTile(metrics, sx, sy, new Point());
addNode(sx, sy, NORTH);
// TODO: make more visually appealing path segments from start to
// second tile, and penultimate to ultimate tile
// add all remaining path nodes excepting the last one
Point prev = new Point(ipos.x, ipos.y);
Point spos = new Point();
int size = tiles.size();
for (int ii = 1; ii < size - 1; ii++) {
Point next = (Point)tiles.get(ii);
// determine the direction from previous to next node
int dir = MisoUtil.getIsoDirection(prev.x, prev.y, next.x, next.y);
// determine the node's position in screen pixel coordinates
MisoUtil.tileToScreen(metrics, next.x, next.y, spos);
// add the node to the path, wandering through the middle
// of each tile in the path for now
int dsx = spos.x + metrics.tilehwid;
int dsy = spos.y + metrics.tilehhei;
addNode(dsx, dsy, dir);
prev = next;
}
// get the final destination point's screen coordinates
// constrained to the closest full coordinate
MisoUtil.fullToScreen(metrics, fpos.x, fpos.y, spos);
// get the tile coordinates for the final destination tile
int tdestx = MisoUtil.fullToTile(fpos.x);
int tdesty = MisoUtil.fullToTile(fpos.y);
// get the facing direction for the final node
int dir;
if (prev.x == ipos.x && prev.y == ipos.y) {
// if destination is within starting tile, direction is
// determined by studying the fine coordinates
dir = MisoUtil.getDirection(metrics, sx, sy, spos.x, spos.y);
} else {
// else it's based on the last tile we traversed
dir = MisoUtil.getIsoDirection(prev.x, prev.y, tdestx, tdesty);
}
// add the final destination path node
addNode(spos.x, spos.y, dir);
}
/**
* Returns the estimated number of millis that we'll be traveling
* along this path.
*/
public long getEstimTravelTime ()
{
return (long)(_estimPixels / _vel);
}
// documentation inherited
public void addNode (int x, int y, int dir)
{
super.addNode(x, y, dir);
if (_last == null) {
_last = new Point();
} else {
_estimPixels += MathUtil.distance(_last.x, _last.y, x, y);
}
_last.setLocation(x, y);
}
/** Used to compute estimated travel time. */
protected Point _last;
/** Estimated pixels traveled. */
protected int _estimPixels;
}
@@ -0,0 +1,129 @@
//
// $Id: MisoSceneModel.java 3726 2005-10-11 19:17:43Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.Rectangle;
import java.util.Random;
import com.threerings.io.SimpleStreamableObject;
import com.threerings.miso.util.ObjectSet;
/**
* Contains basic information for a miso scene model that is shared among
* the specialized model implementations.
*/
public abstract class MisoSceneModel extends SimpleStreamableObject
implements Cloneable
{
/**
* Creates a completely uninitialized model suitable for little more
* than unserialization.
*/
public MisoSceneModel ()
{
}
/**
* Returns the fully qualified tile id of the base tile at the
* specified coordinates. <code>-1</code> will be returned if there is
* no tile at the specified coordinate.
*/
public abstract int getBaseTileId (int x, int y);
/**
* Updates the tile at the specified location in the base layer.
*
* <p> Note that if there are fringe tiles associated with this scene,
* calling this method may result in the surrounding fringe tiles
* being cleared and subsequently recalculated. This should not be
* called on a displaying scene unless you know what you are doing.
*
* @param fqTileId the fully-qualified tile id (@see
* TileUtil#getFQTileId}) of the tile to set.
* @param x the x-coordinate of the tile to set.
* @param y the y-coordinate of the tile to set.
*
* @return false if the specified tile coordinates are outside of the
* scene and the tile was not saved, true otherwise.
*/
public abstract boolean setBaseTile (int fqTileId, int x, int y);
/**
* Updates the default base tileset id for this scene.
*/
public void setDefaultBaseTileSet (int tileSetId)
{
// nothing doing
}
/**
* Scene models can return a default tileset to be used when no base
* tile data exists for a particular tile.
*/
public int getDefaultBaseTileSet ()
{
return 0;
}
/**
* Populates the supplied object set with info on all objects whose
* origin falls in the requested region.
*/
public abstract void getObjects (Rectangle region, ObjectSet set);
/**
* Adds an object to this scene.
*
* @return true if the object was added, false if the add was rejected
* due to being a duplicate.
*/
public abstract boolean addObject (ObjectInfo info);
/**
* Updates an object in this scene.
*/
public abstract void updateObject (ObjectInfo info);
/**
* Removes the specified object from the scene.
*
* @return true if it was removed, false if the object was not in the
* scene.
*/
public abstract boolean removeObject (ObjectInfo info);
/**
* Creates a copy of this scene model.
*/
public Object clone ()
{
try {
return (MisoSceneModel)super.clone();
} catch (CloneNotSupportedException cnse) {
throw new RuntimeException("MisoSceneModel.clone: " + cnse);
}
}
/** A random number generator for filling random base tiles. */
protected transient Random _rando = new Random();
}
@@ -0,0 +1,168 @@
//
// $Id: ObjectInfo.java 3749 2005-11-09 04:00:16Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.samskivert.util.StringUtil;
import com.threerings.io.SimpleStreamableObject;
import com.threerings.media.tile.TileUtil;
/**
* Contains information about an object in a Miso scene.
*/
public class ObjectInfo extends SimpleStreamableObject
implements Cloneable
{
/** The fully qualified object tile id. */
public int tileId;
/** The x and y tile coordinates of the object. */
public int x, y;
/** Don't access this directly unless you are serializing this
* instance. Use {@link #getPriority} instead. */
public byte priority = 0;
/** The action associated with this object or null if it has no
* action. */
public String action;
/** A "spot" associated with this object (specified as an offset from
* the fine coordinates of the object's origin tile). */
public byte sx, sy;
/** The orientation of the "spot" associated with this object. */
public byte sorient;
/** Up to two colorization assignments for this object. */
public int zations;
/**
* Convenience constructor.
*/
public ObjectInfo (int tileId, int x, int y)
{
this.tileId = tileId;
this.x = x;
this.y = y;
}
/**
* Creates an object info that is a copy of the supplied info.
*/
public ObjectInfo (ObjectInfo other)
{
this.tileId = other.tileId;
this.x = other.x;
this.y = other.y;
this.priority = other.priority;
this.action = other.action;
this.sx = other.sx;
this.sy = other.sy;
this.sorient = other.sorient;
this.zations = other.zations;
}
/**
* Zero argument constructor needed for unserialization.
*/
public ObjectInfo ()
{
}
/**
* Returns the render priority of this object tile.
*/
public int getPriority ()
{
return priority;
}
/**
* Returns the primary colorization assignment.
*/
public int getPrimaryZation ()
{
return (zations & 0xFFFF);
}
/**
* Returns the secondary colorization assignment.
*/
public int getSecondaryZation ()
{
return ((zations >> 16) & 0xFFFF);
}
/**
* Sets the primary and secondary colorization assignments.
*/
public void setZations (short primary, short secondary)
{
zations = ((secondary << 16) | primary);
}
/**
* Returns true if this object info contains non-default data for
* anything other than the tile id and coordinates.
*/
public boolean isInteresting ()
{
return (!StringUtil.isBlank(action) || priority != 0 ||
sx != 0 || sy != 0 || zations != 0);
}
// documentation inherited
public boolean equals (Object other)
{
if (other instanceof ObjectInfo) {
ObjectInfo ooi = (ObjectInfo)other;
return (x == ooi.x && y == ooi.y && tileId == ooi.tileId);
} else {
return false;
}
}
// documentation inherited
public int hashCode ()
{
return x ^ y ^ tileId;
}
// documentation inherited
public Object clone ()
{
try {
return super.clone();
} catch (CloneNotSupportedException cnse) {
// notgunnahappen.
return null;
}
}
/** Enhances our {@link SimpleStreamableObject#toString} output. */
public String tileIdToString ()
{
return (TileUtil.getTileSetId(tileId) + ":" +
TileUtil.getTileIndex(tileId));
}
}
@@ -0,0 +1,282 @@
//
// $Id: SimpleMisoSceneModel.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.Rectangle;
import java.util.ArrayList;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.IntListUtil;
import com.samskivert.util.ListUtil;
import com.threerings.media.util.MathUtil;
import com.threerings.miso.util.ObjectSet;
/**
* Contains miso scene data for a scene that is assumed to be reasonably
* simple and small, such that all base tile data for the entire scene can
* be stored in a single contiguous array.
*
* <p> Additionally, it makes the assumption that the single model will be
* used to display a scene on a rectangular screen (dimensions defined by
* {@link #vwidth} and {@link #vheight}) and further optimizes the base
* tile array to obviate the need to store tile data for things that fall
* outside the bounds of the screen.
*/
public class SimpleMisoSceneModel extends MisoSceneModel
{
/** The width of this scene in tiles. */
public short width;
/** The height of this scene in tiles. */
public short height;
/** The viewport width in tiles. */
public int vwidth;
/** The viewport height in tiles. */
public int vheight;
/** The combined tile ids (tile set id and tile id) in compressed
* format. Don't go poking around in here, use the accessor
* methods. */
public int[] baseTileIds;
/** The combined tile ids (tile set id and tile id) of the
* "uninteresting" tiles in the object layer. */
public int[] objectTileIds;
/** The x coordinate of the "uninteresting" tiles in the object
* layer. */
public short[] objectXs;
/** The y coordinate of the "uninteresting" tiles in the object
* layer. */
public short[] objectYs;
/** Information records for the "interesting" objects in the object
* layer. */
public ObjectInfo[] objectInfo;
/**
* Creates a completely uninitialized model suitable for little more
* than unserialization.
*/
public SimpleMisoSceneModel ()
{
}
/**
* Creates a blank scene model with the specified dimensions.
*/
public SimpleMisoSceneModel (int width, int height, int vwidth, int vheight)
{
this.width = (short)MathUtil.bound(
Short.MIN_VALUE, width, Short.MAX_VALUE);
this.height = (short)MathUtil.bound(
Short.MIN_VALUE, height, Short.MAX_VALUE);
this.vwidth = vwidth;
this.vheight = vheight;
allocateBaseTileArray();
// start with zero-length object arrays
objectTileIds = new int[0];
objectXs = new short[0];
objectYs = new short[0];
objectInfo = new ObjectInfo[0];
}
// documentation inherited
public int getBaseTileId (int col, int row)
{
int index = getIndex(col, row);
return (index == -1) ? 0 : baseTileIds[index];
}
// documentation inherited
public boolean setBaseTile (int fqBaseTileId, int col, int row)
{
int index = getIndex(col, row);
if (index == -1) {
return false;
}
baseTileIds[index] = fqBaseTileId;
return true;
}
// documentation inherited
public void getObjects (Rectangle region, ObjectSet set)
{
// first look for intersecting interesting objects
for (int ii = 0; ii < objectInfo.length; ii++) {
ObjectInfo info = objectInfo[ii];
if (region.contains(info.x, info.y)) {
set.insert(info);
}
}
// now look for intersecting non-interesting objects
for (int ii = 0; ii < objectTileIds.length; ii++) {
int x = objectXs[ii], y = objectYs[ii];
if (region.contains(x, y)) {
set.insert(new ObjectInfo(objectTileIds[ii], x, y));
}
}
}
// documentation inherited
public boolean addObject (ObjectInfo info)
{
if (info.isInteresting()) {
objectInfo = (ObjectInfo[])ArrayUtil.append(objectInfo, info);
} else {
objectTileIds = ArrayUtil.append(objectTileIds, info.tileId);
objectXs = ArrayUtil.append(objectXs, (short)info.x);
objectYs = ArrayUtil.append(objectYs, (short)info.y);
}
return true;
}
// documentation inherited
public void updateObject (ObjectInfo info)
{
// not efficient, but this is only done in editing situations
removeObject(info);
addObject(info);
}
// documentation inherited
public boolean removeObject (ObjectInfo info)
{
// look for it in the interesting info array
int oidx = ListUtil.indexOf(objectInfo, info);
if (oidx != -1) {
objectInfo = (ObjectInfo[])ArrayUtil.splice(objectInfo, oidx, 1);
return true;
}
// look for it in the uninteresting arrays
oidx = IntListUtil.indexOf(objectTileIds, info.tileId);
if (oidx != -1) {
objectTileIds = ArrayUtil.splice(objectTileIds, oidx, 1);
objectXs = ArrayUtil.splice(objectXs, oidx, 1);
objectYs = ArrayUtil.splice(objectYs, oidx, 1);
return true;
}
return false;
}
// documentation inherited
public Object clone ()
{
SimpleMisoSceneModel model = (SimpleMisoSceneModel)super.clone();
model.baseTileIds = (int[])baseTileIds.clone();
model.objectTileIds = (int[])objectTileIds.clone();
model.objectXs = (short[])objectXs.clone();
model.objectYs = (short[])objectYs.clone();
model.objectInfo = (ObjectInfo[])objectInfo.clone();
return model;
}
/**
* Get the index into the baseTileIds[] for the specified
* x and y coordinates, or return -1 if the specified coordinates
* are outside of the viewable area.
*
* Assumption: The viewable area is centered and aligned as far
* to the top of the isometric scene as possible, such that
* the upper-left corner is at the point where the tiles
* (0, vwid) and (0, vwid-1) touch. The upper-right corner
* is at the point where the tiles (vwid-1, 0) and (vwid, 0)
* touch.
*
* The viewable area is made up of "fat" rows and "thin" rows. The
* fat rows display one more tile than the thin rows because their
* first and last tiles are halfway off the viewable area. The thin
* rows are fully contained within the viewable area except for the
* first and last thin rows, which display only their bottom and top
* halves, respectively. Note that #fatrows == #thinrows - 1;
*/
protected int getIndex (int x, int y)
{
// check to see if the index lies in one of the "fat" rows
if (((x + y) & 1) == (vwidth & 1)) {
int col = (vwidth + x - y) >> 1;
int row = x - col;
if ((col < 0) || (col > vwidth) ||
(row < 0) || (row >= vheight)) {
return -1; // out of view
}
return (vwidth + 1) * row + col;
} else {
// the index must be in a "thin" row
int col = (vwidth + x - y - 1) >> 1;
int row = x - col;
if ((col < 0) || (col >= vwidth) ||
(row < 0) || (row > vheight)) {
return -1; // out of view
}
// we store the all the fat rows first, then all the thin
// rows, the '(vwidth + 1) * vheight' is the size of all
// the fat rows.
return row * vwidth + col + (vwidth + 1) * vheight;
}
}
/**
* Allocate the base tile array.
*/
protected void allocateBaseTileArray ()
{
baseTileIds = new int[vwidth + vheight + ((vwidth * vheight) << 1)];
}
/**
* Populates the interesting and uninteresting parts of a miso scene
* model given lists of {@link ObjectInfo} records for each.
*/
public static void populateObjects (SimpleMisoSceneModel model,
ArrayList ilist, ArrayList ulist)
{
// set up the uninteresting arrays
int ucount = ulist.size();
model.objectTileIds = new int[ucount];
model.objectXs = new short[ucount];
model.objectYs = new short[ucount];
for (int ii = 0; ii < ucount; ii++) {
ObjectInfo info = (ObjectInfo)ulist.get(ii);
model.objectTileIds[ii] = info.tileId;
model.objectXs[ii] = (short)info.x;
model.objectYs[ii] = (short)info.y;
}
// set up the interesting array
int icount = ilist.size();
model.objectInfo = new ObjectInfo[icount];
ilist.toArray(model.objectInfo);
}
}
@@ -0,0 +1,445 @@
//
// $Id: SparseMisoSceneModel.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Iterator;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.ListUtil;
import com.samskivert.util.StringUtil;
import com.threerings.io.SimpleStreamableObject;
import com.threerings.media.util.MathUtil;
import com.threerings.util.StreamableHashIntMap;
import com.threerings.miso.Log;
import com.threerings.miso.util.ObjectSet;
/**
* Contains miso scene data that is broken up into NxN tile sections.
*/
public class SparseMisoSceneModel extends MisoSceneModel
{
/** An interface that allows external entities to "visit" and inspect
* every object in this scene. */
public static interface ObjectVisitor
{
/** Called for each object in the scene, interesting and not. */
public void visit (ObjectInfo info);
}
/** Contains information on a section of this scene. This is only
* public so that the scene model parser can do its job, so don't go
* poking around in here. */
public static class Section extends SimpleStreamableObject
implements Cloneable
{
/** The tile coordinate of our upper leftmost tile. */
public short x, y;
/** The width of this section in tiles. */
public int width;
/** The combined tile ids (tile set id and tile id) for our
* section (in row major order). */
public int[] baseTileIds;
/** The combined tile ids (tile set id and tile id) of the
* "uninteresting" tiles in the object layer. */
public int[] objectTileIds = new int[0];
/** The x coordinate of the "uninteresting" tiles in the object
* layer. */
public short[] objectXs = new short[0];
/** The y coordinate of the "uninteresting" tiles in the object
* layer. */
public short[] objectYs = new short[0];
/** Information records for the "interesting" objects in the
* object layer. */
public ObjectInfo[] objectInfo = new ObjectInfo[0];
/**
* Creates a blank section instance, suitable for unserialization
* or configuration by the XML scene parser.
*/
public Section ()
{
}
/**
* Creates a new scene section with the specified dimensions.
*/
public Section (short x, short y, short width, short height)
{
this.x = x;
this.y = y;
this.width = width;
baseTileIds = new int[width*height];
}
public int getBaseTileId (int col, int row) {
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 void setBaseTile (int col, int row, int fqBaseTileId) {
baseTileIds[(row-y)*width+(col-x)] = fqBaseTileId;
}
public boolean addObject (ObjectInfo info) {
// sanity check: see if there is already an object of this
// type at these coordinates
int dupidx;
if ((dupidx = ListUtil.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 = (ObjectInfo[])ArrayUtil.append(objectInfo, info);
} else {
objectTileIds = ArrayUtil.append(objectTileIds, info.tileId);
objectXs = ArrayUtil.append(objectXs, (short)info.x);
objectYs = ArrayUtil.append(objectYs, (short)info.y);
}
return true;
}
public boolean removeObject (ObjectInfo info) {
// look for it in the interesting info array
int oidx = ListUtil.indexOf(objectInfo, info);
if (oidx != -1) {
objectInfo = (ObjectInfo[])
ArrayUtil.splice(objectInfo, oidx, 1);
return true;
}
// look for it in the uninteresting arrays
oidx = indexOfUn(info);
if (oidx != -1) {
objectTileIds = ArrayUtil.splice(objectTileIds, oidx, 1);
objectXs = ArrayUtil.splice(objectXs, oidx, 1);
objectYs = 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 int indexOfUn (ObjectInfo info)
{
for (int ii = 0; ii < objectTileIds.length; ii++) {
if (objectTileIds[ii] == info.tileId &&
objectXs[ii] == info.x && objectYs[ii] == info.y) {
return ii;
}
}
return -1;
}
public void getObjects (Rectangle region, ObjectSet set) {
// first look for intersecting interesting objects
for (int ii = 0; ii < objectInfo.length; ii++) {
ObjectInfo info = objectInfo[ii];
if (region.contains(info.x, info.y)) {
set.insert(info);
}
}
// now look for intersecting non-interesting objects
for (int ii = 0; ii < objectTileIds.length; ii++) {
int x = objectXs[ii], y = 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 boolean isBlank ()
{
if ((objectTileIds.length != 0) || (objectInfo.length != 0)) {
return false;
}
for (int ii=0, nn=baseTileIds.length; ii < nn; ii++) {
if (baseTileIds[ii] != 0) {
return false;
}
}
return true;
}
public Object clone () {
try {
Section section = (Section)super.clone();
section.baseTileIds = (int[])baseTileIds.clone();
section.objectTileIds = (int[])objectTileIds.clone();
section.objectXs = (short[])objectXs.clone();
section.objectYs = (short[])objectYs.clone();
section.objectInfo = (ObjectInfo[])objectInfo.clone();
return section;
} catch (CloneNotSupportedException cnse) {
throw new RuntimeException(
"SparseMisoSceneModel.Section.clone: " + cnse);
}
}
public String toString () {
return ((width == 0) ? "<no bounds>" :
(width + "x" + (baseTileIds.length/width))) +
"+" + x + "+" + y +
":" + objectInfo.length + ":" + objectTileIds.length;
}
}
/** The dimensions of a section of our scene. */
public short swidth, sheight;
/** The tileset to use when we have no tile data. */
public int defTileSet = 0;
/**
* Creates a scene model with the specified bounds.
*
* @param swidth the width of a single section (in tiles).
* @param sheight the height of a single section (in tiles).
*/
public SparseMisoSceneModel (int swidth, int sheight)
{
this.swidth = (short)swidth;
this.sheight = (short)sheight;
}
/**
* Creates a blank model suitable for unserialization.
*/
public SparseMisoSceneModel ()
{
}
/**
* Adds all interesting {@link ObjectInfo} records in this scene to
* the supplied list.
*/
public void getInterestingObjects (ArrayList list)
{
for (Iterator iter = getSections(); iter.hasNext(); ) {
Section sect = (Section)iter.next();
for (int oo = 0; oo < sect.objectInfo.length; oo++) {
list.add(sect.objectInfo[oo]);
}
}
}
/**
* Informs the supplied visitor of each object in this scene.
*/
public void visitObjects (ObjectVisitor visitor)
{
visitObjects(visitor, false);
}
/**
* Informs the supplied visitor of each object in this scene.
*
* @param interestingOnly if true, only the interesting objects will
* be visited.
*/
public void visitObjects (ObjectVisitor visitor, boolean interestingOnly)
{
for (Iterator iter = getSections(); iter.hasNext(); ) {
Section sect = (Section)iter.next();
for (int oo = 0; oo < sect.objectInfo.length; oo++) {
ObjectInfo oinfo = sect.objectInfo[oo];
visitor.visit(oinfo);
}
if (!interestingOnly) {
ObjectInfo info = new ObjectInfo();
for (int oo = 0; oo < sect.objectTileIds.length; oo++) {
info.tileId = sect.objectTileIds[oo];
info.x = sect.objectXs[oo];
info.y = sect.objectYs[oo];
visitor.visit(info);
}
}
}
}
// documentation inherited
public int getBaseTileId (int col, int row)
{
Section sec = getSection(col, row, false);
return (sec == null) ? -1 : sec.getBaseTileId(col, row);
}
// documentation inherited
public boolean setBaseTile (int fqBaseTileId, int col, int row)
{
getSection(col, row, true).setBaseTile(col, row, fqBaseTileId);
return true;
}
// documentation inherited
public void setDefaultBaseTileSet (int tileSetId)
{
defTileSet = tileSetId;
}
// documentation inherited
public int getDefaultBaseTileSet ()
{
return defTileSet;
}
// documentation inherited
public void getObjects (Rectangle region, ObjectSet set)
{
int minx = MathUtil.floorDiv(region.x, swidth)*swidth;
int maxx = MathUtil.floorDiv(region.x+region.width-1, swidth)*swidth;
int miny = MathUtil.floorDiv(region.y, sheight)*sheight;
int maxy = MathUtil.floorDiv(region.y+region.height-1, sheight)*sheight;
for (int yy = miny; yy <= maxy; yy += sheight) {
for (int xx = minx; xx <= maxx; xx += swidth) {
Section sec = getSection(xx, yy, false);
if (sec != null) {
sec.getObjects(region, set);
}
}
}
}
// documentation inherited
public boolean addObject (ObjectInfo info)
{
return getSection(info.x, info.y, true).addObject(info);
}
// documentation inherited
public void updateObject (ObjectInfo info)
{
// not efficient, but this is only done in editing situations
removeObject(info);
addObject(info);
}
// documentation inherited
public boolean removeObject (ObjectInfo info)
{
Section sec = getSection(info.x, info.y, false);
if (sec != null) {
return sec.removeObject(info);
} else {
return false;
}
}
/**
* 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 void setSection (Section section)
{
_sections.put(key(section.x, section.y), section);
}
/**
* Don't call this method! This is only public so that the scene
* writer can generate XML from the raw scene data.
*/
public Iterator getSections ()
{
return _sections.values().iterator();
}
// documentation inherited
protected void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", sections=" +
StringUtil.toString(_sections.values().iterator()));
}
/**
* Returns the key for the specified section.
*/
protected final int key (int x, int y)
{
int sx = MathUtil.floorDiv(x, swidth);
int sy = MathUtil.floorDiv(y, sheight);
return (sx << 16) | (sy & 0xFFFF);
}
/** Returns the section for the specified tile coordinate. */
protected final Section getSection (int x, int y, boolean create)
{
int key = key(x, y);
Section sect = (Section)_sections.get(key);
if (sect == null && create) {
short sx = (short)(MathUtil.floorDiv(x, swidth)*swidth);
short sy = (short)(MathUtil.floorDiv(y, sheight)*sheight);
_sections.put(key, sect = new Section(sx, sy, swidth, sheight));
// Log.info("Created new section " + sect + ".");
}
return sect;
}
// documentation inherited
public Object clone ()
{
SparseMisoSceneModel model = (SparseMisoSceneModel)super.clone();
model._sections = new StreamableHashIntMap();
for (Iterator iter = getSections(); iter.hasNext(); ) {
Section sect = (Section)iter.next();
model.setSection((Section)sect.clone());
}
return model;
}
/** Contains our sections in row major order. */
protected StreamableHashIntMap _sections = new StreamableHashIntMap();
}
@@ -0,0 +1,58 @@
//
// $Id: VirtualMisoSceneModel.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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;
/**
* 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
{
public VirtualMisoSceneModel ()
{
}
// documentation inherited from interface
public boolean setBaseTile (int fqTileId, int x, int y)
{
throw new UnsupportedOperationException();
}
// documentation inherited from interface
public boolean addObject (ObjectInfo info)
{
throw new UnsupportedOperationException();
}
// documentation inherited from interface
public void updateObject (ObjectInfo info)
{
throw new UnsupportedOperationException();
}
// documentation inherited from interface
public boolean removeObject (ObjectInfo info)
{
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,390 @@
//
// $Id: AutoFringer.java 4145 2006-05-24 01:24:24Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tile;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import com.samskivert.util.CheapIntMap;
import com.samskivert.util.QuickSort;
import com.threerings.media.image.BufferedMirage;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.ImageUtil;
import com.threerings.media.tile.NoSuchTileSetException;
import com.threerings.media.tile.Tile;
import com.threerings.media.tile.TileManager;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TileUtil;
import com.threerings.miso.Log;
import com.threerings.miso.data.MisoSceneModel;
/**
* Automatically fringes a scene according to the rules in the supplied
* fringe configuration.
*/
public class AutoFringer
{
/**
* Constructs an instance that will fringe according to the rules in
* the supplied fringe configuration.
*/
public AutoFringer (FringeConfiguration fringeconf, ImageManager imgr,
TileManager tmgr)
{
_fringeconf = fringeconf;
_imgr = imgr;
_tmgr = tmgr;
}
/**
* Compute and return the fringe tile to be inserted at the specified
* location.
*/
public BaseTile getFringeTile (MisoSceneModel scene, int col, int row,
HashMap masks)
{
// get the tileset id of the base tile we are considering
int underset = scene.getBaseTileId(col, row) >> 16;
// start with a clean temporary fringer map
_fringers.clear();
boolean passable = true;
// walk through our influence tiles
for (int y = row - 1, maxy = row + 2; y < maxy; y++) {
for (int x = col - 1, maxx = col + 2; x < maxx; x++) {
// we sensibly do not consider ourselves
if ((x == col) && (y == row)) {
continue;
}
// determine the tileset for this tile
int btid = scene.getBaseTileId(x, y);
int baseset = (btid <= 0) ?
scene.getDefaultBaseTileSet() : (btid >> 16);
// determine if it fringes on our tile
int pri = _fringeconf.fringesOn(baseset, underset);
if (pri == -1) {
continue;
}
FringerRec fringer = (FringerRec)_fringers.get(baseset);
if (fringer == null) {
fringer = new FringerRec(baseset, pri);
_fringers.put(baseset, fringer);
}
// now turn on the appropriate fringebits
fringer.bits |= FLAGMATRIX[y - row + 1][x - col + 1];
// See if a tile that fringes on us kills our passability,
// but don't count the default base tile against us, as
// we allow users to splash in the water.
if (passable && (btid > 0)) {
try {
BaseTile bt = (BaseTile) _tmgr.getTile(btid);
passable = bt.isPassable();
} catch (NoSuchTileSetException nstse) {
Log.warning("Autofringer couldn't find a base " +
"set while attempting to figure passability " +
"[error=" + nstse + "].");
}
}
}
}
// if nothing fringed, we're done
int numfringers = _fringers.size();
if (numfringers == 0) {
return null;
}
// otherwise compose a FringeTile from the specified fringes
FringerRec[] frecs = new FringerRec[numfringers];
for (int ii = 0, pp = 0; ii < 16; ii++) {
FringerRec rec = (FringerRec)_fringers.getValue(ii);
if (rec != null) {
frecs[pp++] = rec;
}
}
BaseTile frTile = new BaseTile();
frTile.setPassable(passable);
composeFringeTile(frTile, frecs, masks, TileUtil.getTileHash(col, row));
return frTile;
}
/**
* Compose a FringeTile out of the various fringe images needed.
*/
protected void composeFringeTile (
Tile frTile, FringerRec[] fringers, HashMap masks, int hashValue)
{
// sort the array so that higher priority fringers get drawn first
QuickSort.sort(fringers);
BufferedImage ftimg = null;
for (int ii = 0; ii < fringers.length; ii++) {
int[] indexes = getFringeIndexes(fringers[ii].bits);
for (int jj = 0; jj < indexes.length; jj++) {
try {
ftimg = getTileImage(ftimg, fringers[ii].baseset,
indexes[jj], masks, hashValue);
} catch (NoSuchTileSetException nstse) {
Log.warning("Autofringer couldn't find a needed tileset " +
"[error=" + nstse + "].");
}
}
}
frTile.setImage(new BufferedMirage(ftimg));
}
/**
* Retrieve or compose an image for the specified fringe.
*/
protected BufferedImage getTileImage (
BufferedImage ftimg, int baseset, int index,
HashMap masks, int hashValue)
throws NoSuchTileSetException
{
FringeConfiguration.FringeTileSetRecord tsr =
_fringeconf.getFringe(baseset, hashValue);
int fringeset = tsr.fringe_tsid;
TileSet fset = _tmgr.getTileSet(fringeset);
if (!tsr.mask) {
// oh good, this is easy
Tile stamp = fset.getTile(index);
return stampTileImage(stamp, ftimg, stamp.getWidth(),
stamp.getHeight());
}
// otherwise, it's a mask.. look for it in the cache..
Long maskkey = Long.valueOf((((long) baseset) << 32) +
(fringeset << 16) + index);
BufferedImage img = (BufferedImage)masks.get(maskkey);
if (img == null) {
BufferedImage fsrc = fset.getRawTileImage(index);
BufferedImage bsrc = _tmgr.getTileSet(baseset).getRawTileImage(0);
img = ImageUtil.composeMaskedImage(_imgr, fsrc, bsrc);
masks.put(maskkey, img);
}
ftimg = stampTileImage(img, ftimg, img.getWidth(null),
img.getHeight(null));
return ftimg;
}
/** Helper function for {@link #getTileImage}. */
protected BufferedImage stampTileImage (Object stamp, BufferedImage ftimg,
int width, int height)
{
// create the target image if necessary
if (ftimg == null) {
ftimg = _imgr.createImage(width, height, Transparency.BITMASK);
}
Graphics2D gfx = (Graphics2D)ftimg.getGraphics();
try {
if (stamp instanceof Tile) {
((Tile)stamp).paint(gfx, 0, 0);
} else {
gfx.drawImage((BufferedImage)stamp, 0, 0, null);
}
} finally {
gfx.dispose();
}
return ftimg;
}
/**
* Get the fringe index specified by the fringebits. If no index
* is available, try breaking down the bits into contiguous regions of
* bits and look for indexes for those.
*/
protected int[] getFringeIndexes (int bits)
{
int index = BITS_TO_INDEX[bits];
if (index != -1) {
int[] ret = new int[1];
ret[0] = index;
return ret;
}
// otherwise, split the bits into contiguous components
// look for a zero and start our first split
int start = 0;
while ((((1 << start) & bits) != 0) && (start < NUM_FRINGEBITS)) {
start++;
}
if (start == NUM_FRINGEBITS) {
// we never found an empty fringebit, and since index (above)
// was already -1, we have no fringe tile for these bits.. sad.
return new int[0];
}
ArrayList indexes = new ArrayList();
int weebits = 0;
for (int ii=(start + 1) % NUM_FRINGEBITS; ii != start;
ii = (ii + 1) % NUM_FRINGEBITS) {
if (((1 << ii) & bits) != 0) {
weebits |= (1 << ii);
} else if (weebits != 0) {
index = BITS_TO_INDEX[weebits];
if (index != -1) {
indexes.add(Integer.valueOf(index));
}
weebits = 0;
}
}
if (weebits != 0) {
index = BITS_TO_INDEX[weebits];
if (index != -1) {
indexes.add(Integer.valueOf(index));
}
}
int[] ret = new int[indexes.size()];
for (int ii=0; ii < ret.length; ii++) {
ret[ii] = ((Integer) indexes.get(ii)).intValue();
}
return ret;
}
/**
* A record for holding information about a particular fringe as we're
* computing what it will look like.
*/
static protected class FringerRec implements Comparable
{
int baseset;
int priority;
int bits;
public FringerRec (int base, int pri)
{
baseset = base;
priority = pri;
}
public int compareTo (Object o)
{
return priority - ((FringerRec) o).priority;
}
public String toString ()
{
return "[base=" + baseset + ", pri=" + priority +
", bits=" + Integer.toString(bits, 16) + "]";
}
}
// fringe bits
// see docs/miso/fringebits.png
//
protected static final int NORTH = 1 << 0;
protected static final int NORTHEAST = 1 << 1;
protected static final int EAST = 1 << 2;
protected static final int SOUTHEAST = 1 << 3;
protected static final int SOUTH = 1 << 4;
protected static final int SOUTHWEST = 1 << 5;
protected static final int WEST = 1 << 6;
protected static final int NORTHWEST = 1 << 7;
protected static final int NUM_FRINGEBITS = 8;
// A matrix mapping adjacent tiles to which fringe bits
// they affect.
// (x and y are offset by +1, since we can't have -1 as an array index)
// again, see docs/miso/fringebits.png
//
protected static final int[][] FLAGMATRIX = {
{ NORTHEAST, (NORTHEAST | EAST | SOUTHEAST), SOUTHEAST },
{ (NORTHWEST | NORTH | NORTHEAST), 0, (SOUTHEAST | SOUTH | SOUTHWEST) },
{ NORTHWEST, (NORTHWEST | WEST | SOUTHWEST), SOUTHWEST }
};
/**
* The fringe tiles we use. These are the 17 possible tiles made
* up of continuous fringebits sections.
* Huh? see docs/miso/fringebits.png
*/
protected static final int[] FRINGETILES = {
SOUTHEAST,
SOUTHWEST | SOUTH | SOUTHEAST,
SOUTHWEST,
NORTHEAST | EAST | SOUTHEAST,
NORTHWEST | WEST | SOUTHWEST,
NORTHEAST,
NORTHWEST | NORTH | NORTHEAST,
NORTHWEST,
SOUTHWEST | WEST | NORTHWEST | NORTH | NORTHEAST,
NORTHWEST | NORTH | NORTHEAST | EAST | SOUTHEAST,
NORTHWEST | WEST | SOUTHWEST | SOUTH | SOUTHEAST,
SOUTHWEST | SOUTH | SOUTHEAST | EAST | NORTHEAST,
NORTHEAST | NORTH | NORTHWEST | WEST | SOUTHWEST | SOUTH | SOUTHEAST,
SOUTHEAST | EAST | NORTHEAST | NORTH | NORTHWEST | WEST | SOUTHWEST,
SOUTHWEST | SOUTH | SOUTHEAST | EAST | NORTHEAST | NORTH | NORTHWEST,
NORTHWEST | WEST | SOUTHWEST | SOUTH | SOUTHEAST | EAST | NORTHEAST,
// all the directions!
NORTH | NORTHEAST | EAST | SOUTHEAST |
SOUTH | SOUTHWEST | WEST | NORTHWEST
};
// A reverse map of the above array, for quickly looking up which tile
// we want.
protected static final int[] BITS_TO_INDEX;
// Construct the BITS_TO_INDEX array.
static {
int num = (1 << NUM_FRINGEBITS);
BITS_TO_INDEX = new int[num];
// first clear everything to -1 (meaning there is no tile defined)
for (int ii=0; ii < num; ii++) {
BITS_TO_INDEX[ii] = -1;
}
// then fill in with the defined tiles.
for (int ii=0; ii < FRINGETILES.length; ii++) {
BITS_TO_INDEX[FRINGETILES[ii]] = ii;
}
}
protected ImageManager _imgr;
protected TileManager _tmgr;
protected FringeConfiguration _fringeconf;
protected CheapIntMap _fringers = new CheapIntMap(16);
}
@@ -0,0 +1,52 @@
//
// $Id: BaseTile.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tile;
import com.threerings.media.tile.Tile;
/**
* Extends the tile class to add support for tile passability.
*
* @see BaseTileSet
*/
public class BaseTile extends Tile
{
/**
* Returns whether or not this tile can be walked upon by character
* sprites.
*/
public boolean isPassable ()
{
return _passable;
}
/**
* Configures this base tile as passable or impassable.
*/
public void setPassable (boolean passable)
{
_passable = passable;
}
/** Whether the tile is passable. */
protected boolean _passable = true;
}
@@ -0,0 +1,81 @@
//
// $Id: BaseTileSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tile;
import com.samskivert.util.StringUtil;
import com.threerings.media.image.Colorization;
import com.threerings.media.tile.SwissArmyTileSet;
import com.threerings.media.tile.Tile;
/**
* The base tileset extends the swiss army tileset to add support for tile
* passability. Passability is used to determine whether traverser objects
* (generally sprites made to "walk" around the scene) can traverse a
* particular tile in a scene.
*/
public class BaseTileSet extends SwissArmyTileSet
{
/**
* Sets the passability information for the tiles in this tileset.
* Each entry in the array corresponds to the tile at that tile index.
*/
public void setPassability (boolean[] passable)
{
_passable = passable;
}
/**
* Returns the passability information for the tiles in this tileset.
*/
public boolean[] getPassability ()
{
return _passable;
}
// documentation inherited
protected Tile createTile ()
{
return new BaseTile();
}
// documentation inherited
protected void initTile (Tile tile, int tileIndex, Colorization[] zations)
{
super.initTile(tile, tileIndex, zations);
((BaseTile)tile).setPassable(_passable[tileIndex]);
}
// documentation inherited
protected void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", passable=").append(StringUtil.toString(_passable));
}
/** Whether each tile is passable. */
protected boolean[] _passable;
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,158 @@
//
// $Id: FringeConfiguration.java 3403 2005-03-14 23:58:02Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tile;
import java.io.Serializable;
import java.util.ArrayList;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
/**
* Used to manage data about which base tilesets fringe on which others
* and how they fringe.
*/
public class FringeConfiguration implements Serializable
{
/**
* The path (relative to the resource directory) at which the fringe
* configuration should be loaded and stored.
*/
public static final String CONFIG_PATH = "config/miso/tile/fringeconf.dat";
public static class FringeRecord implements Serializable
{
/** The tileset id of the base tileset to which this applies. */
public int base_tsid;
/** The fringe priority of this base tileset. */
public int priority;
/** A list of the possible tilesets that can be used for fringing. */
public ArrayList tilesets = new ArrayList();
/** Used when parsing the tilesets definitions. */
public void addTileset (FringeTileSetRecord record)
{
tilesets.add(record);
}
/** Did everything parse well? */
public boolean isValid ()
{
return ((base_tsid != 0) && (priority > 0));
}
/** Generates a string representation of this instance. */
public String toString ()
{
return "[base_tsid=" + base_tsid + ", priority=" + priority +
", tilesets=" + StringUtil.toString(tilesets) + "]";
}
/** Increase this value when object's serialized state is impacted
* by a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
/**
* Used to parse the tileset fringe definitions.
*/
public static class FringeTileSetRecord implements Serializable
{
/** The tileset id of the fringe tileset. */
public int fringe_tsid;
/** Is this a mask? */
public boolean mask;
/** Did everything parse well? */
public boolean isValid ()
{
return (fringe_tsid != 0);
}
/** Generates a string representation of this instance. */
public String toString ()
{
return "[fringe_tsid=" + fringe_tsid + ", mask=" + mask + "]";
}
/** Increase this value when object's serialized state is impacted
* by a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
/**
* Adds a parsed FringeRecord to this instance. This is used when parsing
* the fringerecords from xml.
*/
public void addFringeRecord (FringeRecord frec)
{
_frecs.put(frec.base_tsid, frec);
}
/**
* If the first base tileset fringes upon the second, return the
* fringe priority of the first base tileset, otherwise return -1.
*/
public int fringesOn (int first, int second)
{
FringeRecord f1 = (FringeRecord) _frecs.get(first);
// we better have a fringe record for the first
if (null != f1) {
// it had better have some tilesets defined
if (f1.tilesets.size() > 0) {
FringeRecord f2 = (FringeRecord) _frecs.get(second);
// and we only fringe if second doesn't exist or has a lower
// priority
if ((null == f2) || (f1.priority > f2.priority)) {
return f1.priority;
}
}
}
return -1;
}
/**
* Get a random FringeTileSetRecord from amongst the ones
* listed for the specified base tileset.
*/
public FringeTileSetRecord getFringe (int baseset, int hashValue)
{
FringeRecord f = (FringeRecord) _frecs.get(baseset);
return (FringeTileSetRecord) f.tilesets.get(
hashValue % f.tilesets.size());
}
/** The mapping from base tileset id to fringerecord. */
protected HashIntMap _frecs = new HashIntMap();
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,92 @@
//
// $Id: MisoTileManager.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tile;
import java.io.IOException;
import java.io.InputStream;
import com.samskivert.io.StreamUtil;
import com.threerings.resource.ResourceManager;
import com.threerings.util.CompiledConfig;
import com.threerings.media.image.ImageManager;
import com.threerings.media.tile.TileManager;
import com.threerings.miso.Log;
/**
* Extends the basic tile manager and provides support for automatically
* generating fringes in between different types of base tiles in a scene.
*/
public class MisoTileManager extends TileManager
{
/**
* Creates a tile manager and provides it with a reference to the
* image manager from which it will load tileset images.
*
* @param imgr the image manager via which the tile manager will
* decode and cache images.
*/
public MisoTileManager (ResourceManager rmgr, ImageManager imgr)
{
super(imgr);
// look for a fringe configuration in the appropriate place
InputStream in = null;
try {
in = rmgr.getResource(FRINGE_CONFIG_PATH);
FringeConfiguration config = (FringeConfiguration)
CompiledConfig.loadConfig(in);
// if we've found it, create our auto fringer with it
_fringer = new AutoFringer(config, imgr, this);
} catch (IOException ioe) {
Log.warning("Unable to load fringe configuration " +
"[path=" + FRINGE_CONFIG_PATH +
", error=" + ioe + "].");
} finally {
StreamUtil.close(in);
}
}
/**
* Returns the auto fringer that has been configured for use by this
* tile manager. This will only be valid if this tile manager has been
* provided with a miso tileset repository via {@link
* #setTileSetRepository}.
*/
public AutoFringer getAutoFringer ()
{
return _fringer;
}
/** The entity that performs the automatic fringe layer generation. */
protected AutoFringer _fringer;
/** The path (in the classpath) to the serialized fringe
* configuration. */
protected static final String FRINGE_CONFIG_PATH =
"config/miso/tile/fringeconf.dat";
}
@@ -0,0 +1,96 @@
//
// $Id: CompileFringeConfigurationTask.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tile.tools;
import java.io.File;
import java.io.Serializable;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import com.samskivert.io.PersistenceException;
import com.threerings.util.CompiledConfig;
import com.threerings.media.tile.tools.MapFileTileSetIDBroker;
import com.threerings.miso.tile.tools.xml.FringeConfigurationParser;
/**
* Compile fringe configuration.
*/
public class CompileFringeConfigurationTask extends Task
{
public void setTileSetMap (File tsetmap)
{
_tsetmap = tsetmap;
}
public void setFringeDef (File fringedef)
{
_fringedef = fringedef;
}
public void setTarget (File target)
{
_target = target;
}
public void execute () throws BuildException
{
// make sure the source file exists
if (!_fringedef.exists()) {
throw new BuildException("Fringe definition file not found " +
"[path=" + _fringedef.getPath() + "].");
}
// set up the tileid broker
MapFileTileSetIDBroker broker;
try {
broker = new MapFileTileSetIDBroker(_tsetmap);
} catch (PersistenceException pe) {
throw new BuildException("Couldn't set up tileset mapping " +
"[path=" + _tsetmap.getPath() +
", error=" + pe.getCause() + "].");
}
FringeConfigurationParser parser = new FringeConfigurationParser(
broker);
Serializable config;
try {
config = parser.parseConfig(_fringedef);
} catch (Exception e) {
throw new BuildException("Failure parsing config definition", e);
}
try {
// and write it on out
CompiledConfig.saveConfig(_target, config);
} catch (Exception e) {
throw new BuildException("Failure writing serialized config", e);
}
}
protected File _tsetmap;
protected File _fringedef;
protected File _target;
}
@@ -0,0 +1,84 @@
//
// $Id: BaseTileSetRuleSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tile.tools.xml;
import org.apache.commons.digester.Digester;
import com.samskivert.util.StringUtil;
import com.samskivert.xml.CallMethodSpecialRule;
import com.threerings.media.tile.tools.xml.SwissArmyTileSetRuleSet;
import com.threerings.miso.Log;
import com.threerings.miso.tile.BaseTileSet;
/**
* Parses {@link BaseTileSet} instances from a tileset description. Base
* tilesets extend swiss army tilesets with the addition of a passability
* flag for each tile.
*
* @see SwissArmyTileSetRuleSet
*/
public class BaseTileSetRuleSet extends SwissArmyTileSetRuleSet
{
// documentation inherited
public void addRuleInstances (Digester digester)
{
super.addRuleInstances(digester);
digester.addRule(
_prefix + TILESET_PATH + "/passable", new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
{
int[] values = StringUtil.parseIntArray(bodyText);
boolean[] passable = new boolean[values.length];
for (int i = 0; i < values.length; i++) {
passable[i] = (values[i] != 0);
}
BaseTileSet starget = (BaseTileSet)target;
starget.setPassability(passable);
}
});
}
// documentation inherited
public boolean isValid (Object target)
{
BaseTileSet set = (BaseTileSet)target;
boolean valid = super.isValid(target);
// check for a <passable> element
if (set.getPassability() == null) {
Log.warning("Tile set definition missing valid <passable> " +
"element [set=" + set + "].");
valid = false;
}
return valid;
}
// documentation inherited
protected Class getTileSetClass ()
{
return BaseTileSet.class;
}
}
@@ -0,0 +1,168 @@
//
// $Id: FringeConfigurationParser.java 3749 2005-11-09 04:00:16Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tile.tools.xml;
import java.io.Serializable;
import org.xml.sax.Attributes;
import org.apache.commons.digester.Digester;
import com.samskivert.util.StringUtil;
import com.samskivert.xml.SetPropertyFieldsRule;
import com.samskivert.xml.ValidatedSetNextRule;
import com.threerings.tools.xml.CompiledConfigParser;
import com.threerings.media.tile.TileSetIDBroker;
import com.threerings.miso.Log;
import com.threerings.miso.tile.FringeConfiguration.FringeRecord;
import com.threerings.miso.tile.FringeConfiguration.FringeTileSetRecord;
import com.threerings.miso.tile.FringeConfiguration;
/**
* Parses fringe config definitions.
*/
public class FringeConfigurationParser extends CompiledConfigParser
{
public FringeConfigurationParser (TileSetIDBroker broker)
{
_idBroker = broker;
}
// documentation inherited
protected Serializable createConfigObject ()
{
return new FringeConfiguration();
}
// documentation inherited
protected void addRules (Digester digest)
{
// configure top-level constraints
String prefix = "fringe";
digest.addRule(prefix, new SetPropertyFieldsRule());
// create and configure fringe config instances
prefix += "/base";
digest.addObjectCreate(prefix, FringeRecord.class.getName());
ValidatedSetNextRule.Validator val;
val = new ValidatedSetNextRule.Validator() {
public boolean isValid (Object target) {
if (((FringeRecord) target).isValid()) {
return true;
} else {
Log.warning("A FringeRecord was not added because it was " +
"improperly specified [rec=" + target + "].");
return false;
}
}
};
ValidatedSetNextRule vrule;
vrule = new ValidatedSetNextRule("addFringeRecord", val) {
// parse the fringe record, converting tileset names to
// tileset ids
public void begin (String namespace, String lname, Attributes attrs)
throws Exception
{
FringeRecord frec = (FringeRecord) digester.peek();
for (int ii=0; ii < attrs.getLength(); ii++) {
String name = attrs.getLocalName(ii);
if (StringUtil.isBlank(name)) {
name = attrs.getQName(ii);
}
String value = attrs.getValue(ii);
if ("name".equals(name)) {
if (_idBroker.tileSetMapped(value)) {
frec.base_tsid = _idBroker.getTileSetID(value);
} else {
Log.warning("Skipping unknown base " +
"tileset [name=" + value + "].");
}
} else if ("priority".equals(name)) {
frec.priority = Integer.parseInt(value);
} else {
Log.warning("Skipping unknown attribute " +
"[name=" + name + "].");
}
}
}
};
digest.addRule(prefix, vrule);
// create the tileset records in each fringe record
prefix += "/tileset";
digest.addObjectCreate(prefix, FringeTileSetRecord.class.getName());
val = new ValidatedSetNextRule.Validator() {
public boolean isValid (Object target) {
if (((FringeTileSetRecord) target).isValid()) {
return true;
} else {
Log.warning("A FringeTileSetRecord was not added because " +
"it was improperly specified " +
"[rec=" + target + "].");
return false;
}
}
};
vrule = new ValidatedSetNextRule("addTileset", val) {
// parse the fringe tilesetrecord, converting tileset names to ids
public void begin (String namespace, String lname, Attributes attrs)
throws Exception
{
FringeTileSetRecord f = (FringeTileSetRecord) digester.peek();
for (int ii=0; ii < attrs.getLength(); ii++) {
String name = attrs.getLocalName(ii);
if (StringUtil.isBlank(name)) {
name = attrs.getQName(ii);
}
String value = attrs.getValue(ii);
if ("name".equals(name)) {
if (_idBroker.tileSetMapped(value)) {
f.fringe_tsid = _idBroker.getTileSetID(value);
} else {
Log.warning("Skipping unknown fringe " +
"tileset [name=" + value + "].");
}
} else if ("mask".equals(name)) {
f.mask = Boolean.valueOf(value).booleanValue();
} else {
Log.warning("Skipping unknown attribute " +
"[name=" + name + "].");
}
}
}
};
digest.addRule(prefix, vrule);
}
protected TileSetIDBroker _idBroker;
}
@@ -0,0 +1,90 @@
//
// $Id: SimpleMisoSceneParser.java 3749 2005-11-09 04:00:16Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tools.xml;
import java.io.IOException;
import java.io.FileInputStream;
import org.xml.sax.SAXException;
import org.apache.commons.digester.Digester;
import com.samskivert.util.StringUtil;
import com.threerings.miso.data.SimpleMisoSceneModel;
/**
* A simple class for parsing simple miso scene models.
*/
public class SimpleMisoSceneParser
{
/**
* Constructs a scene parser that parses scenes with the specified XML
* path prefix.
*/
public SimpleMisoSceneParser (String prefix)
{
// create and configure our digester
_digester = new Digester();
// create our scene rule set
SimpleMisoSceneRuleSet set = new SimpleMisoSceneRuleSet();
// configure our top-level path prefix
if (StringUtil.isBlank(prefix)) {
_prefix = set.getOuterElement();
} else {
_prefix = prefix + "/" + set.getOuterElement();
}
// add the scene rules
set.addRuleInstances(_prefix, _digester);
// add a rule to grab the finished scene model
_digester.addSetNext(
_prefix, "setScene", SimpleMisoSceneModel.class.getName());
}
/**
* Parses the XML file at the specified path into a scene model
* instance.
*/
public SimpleMisoSceneModel parseScene (String path)
throws IOException, SAXException
{
_model = null;
_digester.push(this);
_digester.parse(new FileInputStream(path));
return _model;
}
/**
* Called by the parser once the scene is parsed.
*/
public void setScene (SimpleMisoSceneModel model)
{
_model = model;
}
protected String _prefix;
protected Digester _digester;
protected SimpleMisoSceneModel _model;
}
@@ -0,0 +1,108 @@
//
// $Id: SimpleMisoSceneRuleSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tools.xml;
import java.util.ArrayList;
import org.xml.sax.Attributes;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.Rule;
import com.samskivert.xml.CallMethodSpecialRule;
import com.samskivert.xml.SetFieldRule;
import com.samskivert.xml.SetPropertyFieldsRule;
import com.threerings.tools.xml.NestableRuleSet;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.data.SimpleMisoSceneModel;
/**
* Used to parse a {@link SimpleMisoSceneModel} from XML.
*/
public class SimpleMisoSceneRuleSet implements NestableRuleSet
{
// documentation inherited from interface
public String getOuterElement ()
{
return SimpleMisoSceneWriter.OUTER_ELEMENT;
}
// documentation inherited from interface
public void addRuleInstances (String prefix, Digester dig)
{
// this creates the appropriate instance when we encounter our
// prefix tag
dig.addRule(prefix, new Rule() {
public void begin (String namespace, String name,
Attributes attributes) throws Exception {
digester.push(createMisoSceneModel());
}
public void end (String namespace, String name) throws Exception {
digester.pop();
}
});
// set up rules to parse and set our fields
dig.addRule(prefix + "/width", new SetFieldRule("width"));
dig.addRule(prefix + "/height", new SetFieldRule("height"));
dig.addRule(prefix + "/viewwidth", new SetFieldRule("vwidth"));
dig.addRule(prefix + "/viewheight", new SetFieldRule("vheight"));
dig.addRule(prefix + "/base", new SetFieldRule("baseTileIds"));
dig.addObjectCreate(prefix + "/objects", ArrayList.class.getName());
dig.addObjectCreate(prefix + "/objects/object",
ObjectInfo.class.getName());
dig.addSetNext(prefix + "/objects/object", "add",
Object.class.getName());
dig.addRule(prefix + "/objects/object", new SetPropertyFieldsRule());
dig.addRule(prefix + "/objects", new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
throws Exception
{
ArrayList ilist = (ArrayList)target;
ArrayList ulist = new ArrayList();
SimpleMisoSceneModel model = (SimpleMisoSceneModel)
digester.peek(1);
// filter interesting and uninteresting into two lists
for (int ii = 0; ii < ilist.size(); ii++) {
ObjectInfo info = (ObjectInfo)ilist.get(ii);
if (!info.isInteresting()) {
ilist.remove(ii--);
ulist.add(info);
}
}
// now populate the model
SimpleMisoSceneModel.populateObjects(model, ilist, ulist);
}
});
}
protected SimpleMisoSceneModel createMisoSceneModel ()
{
return new SimpleMisoSceneModel(0, 0, 0, 0);
}
}
@@ -0,0 +1,116 @@
//
// $Id: SimpleMisoSceneWriter.java 3749 2005-11-09 04:00:16Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tools.xml;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import com.megginson.sax.DataWriter;
import com.samskivert.util.StringUtil;
import com.threerings.tools.xml.NestableWriter;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.data.SimpleMisoSceneModel;
/**
* Generates an XML representation of a {@link SimpleMisoSceneModel}.
*/
public class SimpleMisoSceneWriter implements NestableWriter
{
/** The element used to enclose scene models written with this
* writer. */
public static final String OUTER_ELEMENT = "miso";
// documentation inherited from interface
public void write (Object object, DataWriter writer)
throws SAXException
{
SimpleMisoSceneModel model = (SimpleMisoSceneModel)object;
writer.startElement(OUTER_ELEMENT);
writeSceneData(model, writer);
writer.endElement(OUTER_ELEMENT);
}
/**
* Writes just the scene data which is handy for derived classes which
* may wish to add their own scene data to the scene output.
*/
protected void writeSceneData (SimpleMisoSceneModel model,
DataWriter writer)
throws SAXException
{
writer.dataElement("width", Integer.toString(model.width));
writer.dataElement("height", Integer.toString(model.height));
writer.dataElement("viewwidth", Integer.toString(model.vwidth));
writer.dataElement("viewheight", Integer.toString(model.vheight));
writer.dataElement("base",
StringUtil.toString(model.baseTileIds, "", ""));
// write our uninteresting object tile information
writer.startElement("objects");
for (int ii = 0; ii < model.objectTileIds.length; ii++) {
AttributesImpl attrs = new AttributesImpl();
attrs.addAttribute("", "tileId", "", "",
String.valueOf(model.objectTileIds[ii]));
attrs.addAttribute("", "x", "", "",
String.valueOf(model.objectXs[ii]));
attrs.addAttribute("", "y", "", "",
String.valueOf(model.objectYs[ii]));
writer.emptyElement("", "object", "", attrs);
}
// write our uninteresting object tile information
for (int ii = 0; ii < model.objectInfo.length; ii++) {
ObjectInfo info = model.objectInfo[ii];
AttributesImpl attrs = new AttributesImpl();
attrs.addAttribute("", "tileId", "", "",
String.valueOf(info.tileId));
attrs.addAttribute("", "x", "", "", String.valueOf(info.x));
attrs.addAttribute("", "y", "", "", String.valueOf(info.y));
if (!StringUtil.isBlank(info.action)) {
attrs.addAttribute("", "action", "", "", info.action);
}
if (info.priority != 0) {
attrs.addAttribute("", "priority", "", "",
String.valueOf(info.priority));
}
if (info.sx != 0 || info.sy != 0) {
attrs.addAttribute("", "sx", "", "",
String.valueOf(info.sx));
attrs.addAttribute("", "sy", "", "",
String.valueOf(info.sy));
attrs.addAttribute("", "sorient", "", "",
String.valueOf(info.sorient));
}
if (info.zations != 0) {
attrs.addAttribute("", "zations", "", "",
String.valueOf(info.zations));
}
writer.emptyElement("", "object", "", attrs);
}
writer.endElement("objects");
}
}
@@ -0,0 +1,97 @@
//
// $Id: SparseMisoSceneParser.java 3749 2005-11-09 04:00:16Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tools.xml;
import java.io.IOException;
import java.io.FileInputStream;
import org.xml.sax.SAXException;
import org.apache.commons.digester.Digester;
import com.samskivert.io.StreamUtil;
import com.samskivert.util.StringUtil;
import com.threerings.miso.data.SparseMisoSceneModel;
/**
* A simple class for parsing simple miso scene models.
*/
public class SparseMisoSceneParser
{
/**
* Constructs a scene parser that parses scenes with the specified XML
* path prefix.
*/
public SparseMisoSceneParser (String prefix)
{
// create and configure our digester
_digester = new Digester();
// create our scene rule set
SparseMisoSceneRuleSet set = new SparseMisoSceneRuleSet();
// configure our top-level path prefix
if (StringUtil.isBlank(prefix)) {
_prefix = set.getOuterElement();
} else {
_prefix = prefix + "/" + set.getOuterElement();
}
// add the scene rules
set.addRuleInstances(_prefix, _digester);
// add a rule to grab the finished scene model
_digester.addSetNext(
_prefix, "setScene", SparseMisoSceneModel.class.getName());
}
/**
* Parses the XML file at the specified path into a scene model
* instance.
*/
public SparseMisoSceneModel parseScene (String path)
throws IOException, SAXException
{
_model = null;
_digester.push(this);
FileInputStream stream = null;
try {
stream = new FileInputStream(path);
_digester.parse(stream);
} finally {
StreamUtil.close(stream);
}
return _model;
}
/**
* Called by the parser once the scene is parsed.
*/
public void setScene (SparseMisoSceneModel model)
{
_model = model;
}
protected String _prefix;
protected Digester _digester;
protected SparseMisoSceneModel _model;
}
@@ -0,0 +1,85 @@
//
// $Id: SparseMisoSceneRuleSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tools.xml;
import org.xml.sax.Attributes;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.Rule;
import com.samskivert.xml.SetFieldRule;
import com.samskivert.xml.SetPropertyFieldsRule;
import com.threerings.tools.xml.NestableRuleSet;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.data.SparseMisoSceneModel.Section;
import com.threerings.miso.data.SparseMisoSceneModel;
/**
* Used to parse a {@link SparseMisoSceneModel} from XML.
*/
public class SparseMisoSceneRuleSet implements NestableRuleSet
{
// documentation inherited from interface
public String getOuterElement ()
{
return SparseMisoSceneWriter.OUTER_ELEMENT;
}
// documentation inherited from interface
public void addRuleInstances (String prefix, Digester dig)
{
// this creates the appropriate instance when we encounter our
// prefix tag
dig.addRule(prefix, new Rule() {
public void begin (String namespace, String name,
Attributes attributes) throws Exception {
digester.push(createMisoSceneModel());
}
public void end (String namespace, String name) throws Exception {
digester.pop();
}
});
// set up rules to parse and set our fields
dig.addRule(prefix + "/swidth", new SetFieldRule("swidth"));
dig.addRule(prefix + "/sheight", new SetFieldRule("sheight"));
dig.addRule(prefix + "/defTileSet", new SetFieldRule("defTileSet"));
String sprefix = prefix + "/sections/section";
dig.addObjectCreate(sprefix, Section.class.getName());
dig.addRule(sprefix, new SetPropertyFieldsRule());
dig.addRule(sprefix + "/base", new SetFieldRule("baseTileIds"));
dig.addObjectCreate(sprefix + "/objects/object",
ObjectInfo.class.getName());
dig.addRule(sprefix + "/objects/object", new SetPropertyFieldsRule());
dig.addSetNext(sprefix + "/objects/object", "addObject",
ObjectInfo.class.getName());
dig.addSetNext(sprefix, "setSection", Section.class.getName());
}
protected SparseMisoSceneModel createMisoSceneModel ()
{
return new SparseMisoSceneModel();
}
}
@@ -0,0 +1,131 @@
//
// $Id: SparseMisoSceneWriter.java 3749 2005-11-09 04:00:16Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.tools.xml;
import java.util.Iterator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import com.megginson.sax.DataWriter;
import com.samskivert.util.StringUtil;
import com.threerings.tools.xml.NestableWriter;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.miso.data.SparseMisoSceneModel.Section;
import com.threerings.miso.data.SparseMisoSceneModel;
/**
* Generates an XML representation of a {@link SparseMisoSceneModel}.
*/
public class SparseMisoSceneWriter implements NestableWriter
{
/** The element used to enclose scene models written with this
* writer. */
public static final String OUTER_ELEMENT = "miso";
// documentation inherited from interface
public void write (Object object, DataWriter writer)
throws SAXException
{
SparseMisoSceneModel model = (SparseMisoSceneModel)object;
writer.startElement(OUTER_ELEMENT);
writeSceneData(model, writer);
writer.endElement(OUTER_ELEMENT);
}
/**
* Writes just the scene data which is handy for derived classes which
* may wish to add their own scene data to the scene output.
*/
protected void writeSceneData (SparseMisoSceneModel model,
DataWriter writer)
throws SAXException
{
writer.dataElement("swidth", Integer.toString(model.swidth));
writer.dataElement("sheight", Integer.toString(model.sheight));
writer.dataElement("defTileSet", Integer.toString(model.defTileSet));
writer.startElement("sections");
for (Iterator iter = model.getSections(); iter.hasNext(); ) {
Section sect = (Section)iter.next();
if (sect.isBlank()) {
continue;
}
AttributesImpl attrs = new AttributesImpl();
attrs.addAttribute("", "x", "", "", String.valueOf(sect.x));
attrs.addAttribute("", "y", "", "", String.valueOf(sect.y));
attrs.addAttribute("", "width", "", "", String.valueOf(sect.width));
writer.startElement("", "section", "", attrs);
writer.dataElement(
"base", StringUtil.toString(sect.baseTileIds, "", ""));
// write our uninteresting object tile information
writer.startElement("objects");
for (int ii = 0; ii < sect.objectTileIds.length; ii++) {
attrs = new AttributesImpl();
attrs.addAttribute("", "tileId", "", "",
String.valueOf(sect.objectTileIds[ii]));
attrs.addAttribute("", "x", "", "",
String.valueOf(sect.objectXs[ii]));
attrs.addAttribute("", "y", "", "",
String.valueOf(sect.objectYs[ii]));
writer.emptyElement("", "object", "", attrs);
}
// write our interesting object tile information
for (int ii = 0; ii < sect.objectInfo.length; ii++) {
ObjectInfo info = sect.objectInfo[ii];
attrs = new AttributesImpl();
attrs.addAttribute("", "tileId", "", "",
String.valueOf(info.tileId));
attrs.addAttribute("", "x", "", "", String.valueOf(info.x));
attrs.addAttribute("", "y", "", "", String.valueOf(info.y));
if (!StringUtil.isBlank(info.action)) {
attrs.addAttribute("", "action", "", "", info.action);
}
if (info.priority != 0) {
attrs.addAttribute("", "priority", "", "",
String.valueOf(info.priority));
}
if (info.sx != 0 || info.sy != 0) {
attrs.addAttribute("", "sx", "", "",
String.valueOf(info.sx));
attrs.addAttribute("", "sy", "", "",
String.valueOf(info.sy));
attrs.addAttribute("", "sorient", "", "",
String.valueOf(info.sorient));
}
if (info.zations != 0) {
attrs.addAttribute("", "zations", "", "",
String.valueOf(info.zations));
}
writer.emptyElement("", "object", "", attrs);
}
writer.endElement("objects");
writer.endElement("section");
}
writer.endElement("sections");
}
}
@@ -0,0 +1,43 @@
//
// $Id: MisoContext.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.media.FrameManager;
import com.threerings.miso.tile.MisoTileManager;
/**
* Provides Miso code with access to the managers that it needs to do its
* thing.
*/
public interface MisoContext
{
/**
* Returns the frame manager that our scene panel will interact with.
*/
public FrameManager getFrameManager ();
/**
* Returns a reference to the tile manager. This reference is valid
* for the lifetime of the application.
*/
public MisoTileManager getTileManager ();
}
@@ -0,0 +1,101 @@
//
// $Id: MisoSceneMetrics.java 3310 2005-01-24 23:08:21Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.client.MisoScenePanel;
/**
* Contains information on the configuration of a particular isometric
* view. The member data are public to facilitate convenient referencing
* by the {@link MisoScenePanel} class, the values should not be modified
* once the metrics are constructed.
*/
public class MisoSceneMetrics
{
/** Tile dimensions and half-dimensions in the view. */
public int tilewid, tilehei, tilehwid, tilehhei;
/** Fine coordinate dimensions. */
public int finehwid, finehhei;
/** Number of fine coordinates on each axis within a tile. */
public int finegran;
/** Dimensions of our scene blocks in tile count. */
public short blockwid = 4, blockhei = 4;
/** The length of a tile edge in pixels. */
public float tilelen;
/** The slope of the x- and y-axis lines. */
public float slopeX, slopeY;
/** The length between fine coordinates in pixels. */
public float finelen;
/** The y-intercept of the x-axis line within a tile. */
public float fineBX;
/** The slope of the x- and y-axis lines within a tile. */
public float fineSlopeX, fineSlopeY;
/**
* Constructs scene metrics by directly specifying the desired config
* parameters.
*
* @param tilewid the width in pixels of the tiles.
* @param tilehei the height in pixels of the tiles.
* @param finegran the number of sub-tile divisions to use for fine
* coordinates.
*/
public MisoSceneMetrics (int tilewid, int tilehei, int finegran)
{
// keep track of this stuff
this.tilewid = tilewid;
this.tilehei = tilehei;
this.finegran = finegran;
// halve the dimensions
tilehwid = (tilewid / 2);
tilehhei = (tilehei / 2);
// calculate the length of a tile edge in pixels
tilelen = (float) Math.sqrt(
(tilehwid * tilehwid) + (tilehhei * tilehhei));
// calculate the slope of the x- and y-axis lines
slopeX = (float)tilehei / (float)tilewid;
slopeY = -slopeX;
// calculate the edge length separating each fine coordinate
finelen = tilelen / (float)finegran;
// calculate the fine-coordinate x-axis line
fineSlopeX = (float)tilehei / (float)tilewid;
fineBX = -(fineSlopeX * (float)tilehwid);
fineSlopeY = -fineSlopeX;
// calculate the fine coordinate dimensions
finehwid = (int)((float)tilehwid / (float)finegran);
finehhei = (int)((float)tilehhei / (float)finegran);
}
}
@@ -0,0 +1,518 @@
//
// $Id: MisoUtil.java 3628 2005-06-28 17:42:55Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.awt.Point;
import java.awt.Polygon;
import com.samskivert.swing.SmartPolygon;
import com.threerings.util.DirectionCodes;
import com.threerings.util.DirectionUtil;
import com.threerings.media.util.MathUtil;
/**
* Miscellaneous isometric-display-related utility routines.
*/
public class MisoUtil
implements DirectionCodes
{
/**
* Given two points in screen pixel coordinates, return the
* compass direction that point B lies in from point A from an
* isometric perspective.
*
* @param ax the x-position of point A.
* @param ay the y-position of point A.
* @param bx the x-position of point B.
* @param by the y-position of point B.
*
* @return the direction specified as one of the <code>Sprite</code>
* class's direction constants.
*/
public static int getDirection (
MisoSceneMetrics metrics, int ax, int ay, int bx, int by)
{
Point afpos = new Point(), bfpos = new Point();
// convert screen coordinates to full coordinates to get both
// tile coordinates and fine coordinates
screenToFull(metrics, ax, ay, afpos);
screenToFull(metrics, bx, by, bfpos);
// pull out the tile coordinates for each point
int tax = fullToTile(afpos.x);
int tay = fullToTile(afpos.y);
int tbx = fullToTile(bfpos.x);
int tby = fullToTile(bfpos.y);
// compare tile coordinates to determine direction
int dir = getIsoDirection(tax, tay, tbx, tby);
if (dir != DirectionCodes.NONE) {
return dir;
}
// destination point is in the same tile as the
// origination point, so consider fine coordinates
// pull out the fine coordinates for each point
int fax = afpos.x - (tax * FULL_TILE_FACTOR);
int fay = afpos.y - (tay * FULL_TILE_FACTOR);
int fbx = bfpos.x - (tbx * FULL_TILE_FACTOR);
int fby = bfpos.y - (tby * FULL_TILE_FACTOR);
// compare fine coordinates to determine direction
dir = getIsoDirection(fax, fay, fbx, fby);
// arbitrarily return southwest if fine coords were also equivalent
return (dir == -1) ? SOUTHWEST : dir;
}
/**
* Given two points in an isometric coordinate system (in which {@link
* #NORTH} is in the direction of the negative x-axis and {@link
* #WEST} in the direction of the negative y-axis), return the compass
* direction that point B lies in from point A. This method is used
* to determine direction for both tile coordinates and fine
* coordinates within a tile, since the coordinate systems are the
* same.
*
* @param ax the x-position of point A.
* @param ay the y-position of point A.
* @param bx the x-position of point B.
* @param by the y-position of point B.
*
* @return the direction specified as one of the <code>Sprite</code>
* class's direction constants, or <code>DirectionCodes.NONE</code> if
* point B is equivalent to point A.
*/
public static int getIsoDirection (int ax, int ay, int bx, int by)
{
// head off a div by 0 at the pass..
if (bx == ax) {
if (by == ay) {
return DirectionCodes.NONE;
}
return (by < ay) ? EAST : WEST;
}
// figure direction base on the slope of the line
float slope = ((float) (ay - by)) / ((float) Math.abs(ax - bx));
if (slope > 2f) {
return EAST;
}
if (slope > .5f) {
return (bx < ax) ? NORTHEAST : SOUTHEAST;
}
if (slope > -.5f) {
return (bx < ax) ? NORTH : SOUTH;
}
if (slope > -2f) {
return (bx < ax) ? NORTHWEST : SOUTHWEST;
}
return WEST;
}
/**
* Given two points in screen coordinates, return the isometrically
* projected compass direction that point B lies in from point A.
*
* @param ax the x-position of point A.
* @param ay the y-position of point A.
* @param bx the x-position of point B.
* @param by the y-position of point B.
*
* @return the direction specified as one of the <code>Sprite</code>
* class's direction constants, or <code>DirectionCodes.NONE</code> if
* point B is equivalent to point A.
*/
public static int getProjectedIsoDirection (int ax, int ay, int bx, int by)
{
return toIsoDirection(DirectionUtil.getDirection(ax, ay, bx, by));
}
/**
* Converts a non-isometric orientation (where north points toward the
* top of the screen) to an isometric orientation where north points
* toward the upper-left corner of the screen.
*/
public static int toIsoDirection (int dir)
{
if (dir != DirectionCodes.NONE) {
// rotate the direction clockwise (ie. change SOUTHEAST to
// SOUTH)
dir = DirectionUtil.rotateCW(dir, 2);
}
return dir;
}
/**
* Returns the tile coordinate of the given full coordinate.
*/
public static int fullToTile (int val)
{
return MathUtil.floorDiv(val, FULL_TILE_FACTOR);
}
/**
* Returns the fine coordinate of the given full coordinate.
*/
public static int fullToFine (int val)
{
return (val - (fullToTile(val) * FULL_TILE_FACTOR));
}
/**
* Convert the given screen-based pixel coordinates to their
* corresponding tile-based coordinates. Converted coordinates
* are placed in the given point object.
*
* @param sx the screen x-position pixel coordinate.
* @param sy the screen y-position pixel coordinate.
* @param tpos the point object to place coordinates in.
*
* @return the point instance supplied via the <code>tpos</code>
* parameter.
*/
public static Point screenToTile (
MisoSceneMetrics metrics, int sx, int sy, Point tpos)
{
// determine the upper-left of the quadrant that contains our
// point
int zx = (int)Math.floor((float)sx / metrics.tilewid);
int zy = (int)Math.floor((float)sy / metrics.tilehei);
// these are the screen coordinates of the tile's top
int ox = (zx * metrics.tilewid), oy = (zy * metrics.tilehei);
// these are the tile coordinates
tpos.x = zy + zx; tpos.y = zy - zx;
// now determine which of the four tiles our point occupies
int dx = sx - ox, dy = sy - oy;
if (Math.round(metrics.slopeY * dx + metrics.tilehei) <= dy) {
tpos.x += 1;
}
if (Math.round(metrics.slopeX * dx) > dy) {
tpos.y -= 1;
}
// Log.info("Converted [sx=" + sx + ", sy=" + sy +
// ", zx=" + zx + ", zy=" + zy +
// ", ox=" + ox + ", oy=" + oy +
// ", dx=" + dx + ", dy=" + dy +
// ", tpos.x=" + tpos.x + ", tpos.y=" + tpos.y + "].");
return tpos;
}
/**
* Convert the given tile-based coordinates to their corresponding
* screen-based pixel coordinates. The screen coordinate for a tile is
* the upper-left coordinate of the rectangle that bounds the tile
* polygon. Converted coordinates are placed in the given point
* object.
*
* @param x the tile x-position coordinate.
* @param y the tile y-position coordinate.
* @param spos the point object to place coordinates in.
*
* @return the point instance supplied via the <code>spos</code>
* parameter.
*/
public static Point tileToScreen (
MisoSceneMetrics metrics, int x, int y, Point spos)
{
spos.x = (x - y - 1) * metrics.tilehwid;
spos.y = (x + y) * metrics.tilehhei;
return spos;
}
/**
* Convert the given fine coordinates to pixel coordinates within
* the containing tile. Converted coordinates are placed in the
* given point object.
*
* @param x the x-position fine coordinate.
* @param y the y-position fine coordinate.
* @param ppos the point object to place coordinates in.
*/
public static void fineToPixel (
MisoSceneMetrics metrics, int x, int y, Point ppos)
{
ppos.x = metrics.tilehwid + ((x - y) * metrics.finehwid);
ppos.y = (x + y) * metrics.finehhei;
}
/**
* Convert the given pixel coordinates, whose origin is at the
* top-left of a tile's containing rectangle, to fine coordinates
* within that tile. Converted coordinates are placed in the
* given point object.
*
* @param x the x-position pixel coordinate.
* @param y the y-position pixel coordinate.
* @param fpos the point object to place coordinates in.
*/
public static void pixelToFine (
MisoSceneMetrics metrics, int x, int y, Point fpos)
{
// calculate line parallel to the y-axis (from the given
// x/y-pos to the x-axis)
float bY = y - (metrics.fineSlopeY * x);
// determine intersection of x- and y-axis lines
int crossx = (int)((bY - metrics.fineBX) /
(metrics.fineSlopeX - metrics.fineSlopeY));
int crossy = (int)((metrics.fineSlopeY * crossx) + bY);
// TODO: final position should check distance between our
// position and the surrounding fine coords and return the
// actual closest fine coord, rather than just dividing.
// determine distance along the x-axis
float xdist = MathUtil.distance(metrics.tilehwid, 0, crossx, crossy);
fpos.x = (int)(xdist / metrics.finelen);
// determine distance along the y-axis
float ydist = MathUtil.distance(x, y, crossx, crossy);
fpos.y = (int)(ydist / metrics.finelen);
// Log.info("Pixel to fine " + StringUtil.coordsToString(x, y) +
// " -> " + StringUtil.toString(fpos) + ".");
}
/**
* Convert the given screen-based pixel coordinates to full
* scene-based coordinates that include both the tile coordinates
* and the fine coordinates in each dimension. Converted
* coordinates are placed in the given point object.
*
* @param sx the screen x-position pixel coordinate.
* @param sy the screen y-position pixel coordinate.
* @param fpos the point object to place coordinates in.
*
* @return the point passed in to receive the coordinates.
*/
public static Point screenToFull (
MisoSceneMetrics metrics, int sx, int sy, Point fpos)
{
// get the tile coordinates
Point tpos = new Point();
screenToTile(metrics, sx, sy, tpos);
// get the screen coordinates for the containing tile
Point spos = tileToScreen(metrics, tpos.x, tpos.y, new Point());
// Log.info("Screen to full " +
// "[screen=" + StringUtil.coordsToString(sx, sy) +
// ", tpos=" + StringUtil.toString(tpos) +
// ", spos=" + StringUtil.toString(spos) +
// ", fpix=" + StringUtil.coordsToString(
// sx-spos.x, sy-spos.y) + "].");
// get the fine coordinates within the containing tile
pixelToFine(metrics, sx - spos.x, sy - spos.y, fpos);
// toss in the tile coordinates for good measure
fpos.x += (tpos.x * FULL_TILE_FACTOR);
fpos.y += (tpos.y * FULL_TILE_FACTOR);
return fpos;
}
/**
* Convert the given full coordinates to screen-based pixel
* coordinates. Converted coordinates are placed in the given
* point object.
*
* @param x the x-position full coordinate.
* @param y the y-position full coordinate.
* @param spos the point object to place coordinates in.
*
* @return the point passed in to receive the coordinates.
*/
public static Point fullToScreen (
MisoSceneMetrics metrics, int x, int y, Point spos)
{
// get the tile screen position
int tx = fullToTile(x), ty = fullToTile(y);
Point tspos = tileToScreen(metrics, tx, ty, new Point());
// get the pixel position of the fine coords within the tile
Point ppos = new Point();
int fx = x - (tx * FULL_TILE_FACTOR), fy = y - (ty * FULL_TILE_FACTOR);
fineToPixel(metrics, fx, fy, ppos);
// final position is tile position offset by fine position
spos.x = tspos.x + ppos.x;
spos.y = tspos.y + ppos.y;
return spos;
}
/**
* Converts the given fine coordinate to a full coordinate (a tile
* coordinate plus a fine coordinate remainder). The fine coordinate
* is assumed to be relative to tile <code>(0, 0)</code>.
*/
public static int fineToFull (MisoSceneMetrics metrics, int fine)
{
return toFull(fine / metrics.finegran, fine % metrics.finegran);
}
/**
* Composes the supplied tile coordinate and fine coordinate offset
* into a full coordinate.
*/
public static int toFull (int tile, int fine)
{
return tile * FULL_TILE_FACTOR + fine;
}
/**
* Return a polygon framing the specified tile.
*
* @param x the tile x-position coordinate.
* @param y the tile y-position coordinate.
*/
public static Polygon getTilePolygon (
MisoSceneMetrics metrics, int x, int y)
{
return getFootprintPolygon(metrics, x, y, 1, 1);
}
/**
* Return a screen-coordinates polygon framing the two specified
* tile-coordinate points.
*/
public static Polygon getMultiTilePolygon (MisoSceneMetrics metrics,
Point sp1, Point sp2)
{
int x = Math.min(sp1.x, sp2.x), y = Math.min(sp1.y, sp2.y);
int width = Math.abs(sp1.x-sp2.x)+1, height = Math.abs(sp1.y-sp2.y)+1;
return getFootprintPolygon(metrics, x, y, width, height);
}
/**
* Returns a polygon framing the specified scene footprint.
*
* @param x the x tile coordinate of the "upper-left" of the footprint.
* @param y the y tile coordinate of the "upper-left" of the footprint.
* @param width the width in tiles of the footprint.
* @param height the height in tiles of the footprint.
*/
public static Polygon getFootprintPolygon (
MisoSceneMetrics metrics, int x, int y, int width, int height)
{
SmartPolygon footprint = new SmartPolygon();
Point tpos = MisoUtil.tileToScreen(metrics, x, y, new Point());
// start with top-center point
int rx = tpos.x + metrics.tilehwid, ry = tpos.y;
footprint.addPoint(rx, ry);
// right point
rx += width * metrics.tilehwid;
ry += width * metrics.tilehhei;
footprint.addPoint(rx, ry);
// bottom-center point
rx -= height * metrics.tilehwid;
ry += height * metrics.tilehhei;
footprint.addPoint(rx, ry);
// left point
rx -= width * metrics.tilehwid;
ry -= width * metrics.tilehhei;
footprint.addPoint(rx, ry);
// end with top-center point
rx += height * metrics.tilehwid;
ry -= height * metrics.tilehhei;
footprint.addPoint(rx, ry);
return footprint;
}
/**
* Adds the supplied fine coordinates to the supplied tile coordinates
* to compute full coordinates.
*
* @return the point object supplied as <code>full</code>.
*/
public static Point tilePlusFineToFull (MisoSceneMetrics metrics,
int tileX, int tileY,
int fineX, int fineY,
Point full)
{
int dtx = fineX / metrics.finegran;
int dty = fineY / metrics.finegran;
int fx = fineX - dtx * metrics.finegran;
if (fx < 0) {
dtx--;
fx += metrics.finegran;
}
int fy = fineY - dty * metrics.finegran;
if (fy < 0) {
dty--;
fy += metrics.finegran;
}
full.x = toFull(tileX + dtx, fx);
full.y = toFull(tileY + dty, fy);
return full;
}
/**
* Turns x and y scene coordinates into an integer key.
*
* @return the hash key, given x and y.
*/
public static final int coordsToKey (int x, int y)
{
return ((y << 16) & (0xFFFF0000)) | (x & 0xFFFF);
}
/**
* Gets the x coordinate from an integer hash key.
*
* @return the x coordinate.
*/
public static final int xCoordFromKey (int key)
{
return (key & 0xFFFF);
}
/**
* Gets the y coordinate from an integer hash key.
*
* @return the y coordinate from the hash key.
*/
public static final int yCoordFromKey (int key)
{
return ((key >> 16) & 0xFFFF);
}
/** Multiplication factor to embed tile coords in full coords. */
protected static final int FULL_TILE_FACTOR = 100;
}
@@ -0,0 +1,182 @@
//
// $Id: ObjectSet.java 4191 2006-06-13 22:42:20Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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 java.util.Arrays;
import java.util.Comparator;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.ListUtil;
import com.threerings.miso.Log;
import com.threerings.miso.data.ObjectInfo;
/**
* Used to store an (arbitrarily) ordered, low-impact iteratable (doesn't
* require object creation), set of {@link ObjectInfo} instances.
*/
public class ObjectSet
{
/**
* Inserts the supplied object into the set.
*
* @return true if it was inserted, false if the object was already in
* the set.
*/
public boolean insert (ObjectInfo info)
{
// bail if it's already in the set
int ipos = 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] + "].");
Thread.dumpStack();
return false;
}
// otherwise insert it
ipos = -(ipos+1);
_objs = ListUtil.insert(_objs, ipos, info);
_size++;
return true;
}
/**
* Returns true if the specified object is in the set, false if it is
* not.
*/
public boolean contains (ObjectInfo info)
{
return (indexOf(info) >= 0);
}
/**
* Returns the number of objects in this set.
*/
public int size ()
{
return _size;
}
/**
* Returns the object with the specified index. The index must & be
* between <code>0</code> and {@link #size}<code>-1</code>.
*/
public ObjectInfo get (int index)
{
return (ObjectInfo)_objs[index];
}
/**
* Removes the object at the specified index.
*/
public void remove (int index)
{
ListUtil.remove(_objs, index);
_size--;
}
/**
* Removes the specified object from the set.
*
* @return true if it was removed, false if it was not in the set.
*/
public boolean remove (ObjectInfo info)
{
int opos = indexOf(info);
if (opos >= 0) {
remove(opos);
return true;
} else {
return false;
}
}
/**
* Clears out the contents of this set.
*/
public void clear ()
{
_size = 0;
Arrays.fill(_objs, null);
}
/**
* Converts the contents of this object set to an array.
*/
public ObjectInfo[] toArray ()
{
ObjectInfo[] info = new ObjectInfo[_size];
System.arraycopy(_objs, 0, info, 0, _size);
return info;
}
/**
* Returns a string representation of this instance.
*/
public String toString ()
{
StringBuilder buf = new StringBuilder("[");
for (int ii = 0; ii < _size; ii++) {
if (ii > 0) {
buf.append(", ");
}
buf.append(_objs[ii]);
}
return buf.append("]").toString();
}
/**
* Returns the index of the object or it's insertion index if it is
* not in the set.
*/
protected final int indexOf (ObjectInfo info)
{
return ArrayUtil.binarySearch(_objs, 0, _size, info, INFO_COMP);
}
/** Our sorted array of objects. */
protected Object[] _objs = new Object[DEFAULT_SIZE];
/** The number of objects in the set. */
protected int _size;
/** 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 static final Comparator INFO_COMP = new Comparator() {
public int compare (Object o1, Object o2) {
ObjectInfo do1 = (ObjectInfo)o1;
ObjectInfo do2 = (ObjectInfo)o2;
if (do1.tileId == do2.tileId) {
return ((do1.x << 16) + do1.y) - ((do2.x << 16) + do2.y);
} else {
return do1.tileId - do2.tileId;
}
}
};
/** We start big because we know these will in general contain at
* least in the tens of objects. */
protected static final int DEFAULT_SIZE = 16;
}