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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user