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
@@ -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;
}