Added constraint fields to TrimmedObjectTileSet, fixed constraint parsing, added basic constraint handler class, added image manager to StageServer to fix NPE in tile retrieval.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3607 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Andrzej Kapolka
2005-06-20 21:18:55 +00:00
parent d7fd2f5f67
commit bd017c0237
6 changed files with 522 additions and 8 deletions
@@ -184,6 +184,14 @@ public class ObjectTile extends Tile
ListUtil.contains(_constraints, constraint);
}
/**
* Configures this object's constraints.
*/
public void setConstraints (String[] constraints)
{
_constraints = constraints;
}
// documentation inherited
public void toString (StringBuffer buf)
{
@@ -249,6 +249,9 @@ public class ObjectTileSet extends SwissArmyTileSet
otile.setSpot(_xspots[tileIndex], _yspots[tileIndex],
_sorients[tileIndex]);
}
if (_constraints != null) {
otile.setConstraints(_constraints[tileIndex]);
}
}
/** The width (in tile units) of our object tiles. */
@@ -283,5 +286,5 @@ public class ObjectTileSet extends SwissArmyTileSet
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
private static final long serialVersionUID = 2;
}
@@ -1,5 +1,5 @@
//
// $Id: TrimmedObjectTileSet.java,v 1.16 2004/08/30 22:09:29 ray Exp $
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
@@ -26,6 +26,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import com.samskivert.util.ListUtil;
import com.samskivert.util.StringUtil;
import com.threerings.media.image.Colorization;
@@ -75,6 +76,24 @@ public class TrimmedObjectTileSet extends TileSet
return (_bits == null) ? -1 : _bits[tileIdx].sorient;
}
/**
* Returns the constraints associated with the specified tile index, or
* <code>null</code> if the object has no associated constraints.
*/
public String[] getConstraints (int tileIdx)
{
return (_bits == null) ? null : _bits[tileIdx].constraints;
}
/**
* Checks whether the tile at the specified index has the given constraint.
*/
public boolean hasConstraint (int tileIdx, String constraint)
{
return (_bits == null || _bits[tileIdx].constraints == null) ? false :
ListUtil.contains(_bits[tileIdx].constraints, constraint);
}
// documentation inherited from interface RecolorableTileSet
public String[] getColorizations ()
{
@@ -136,6 +155,7 @@ public class TrimmedObjectTileSet extends TileSet
if (bits.sorient != -1) {
otile.setSpot(bits.xspot, bits.yspot, bits.sorient);
}
otile.setConstraints(bits.constraints);
}
}
@@ -174,7 +194,8 @@ public class TrimmedObjectTileSet extends TileSet
// create our bits if needed
if (source._priorities != null ||
source._xspots != null) {
source._xspots != null ||
source._constraints != null) {
tset._bits = new Bits[tcount];
}
@@ -205,6 +226,9 @@ public class TrimmedObjectTileSet extends TileSet
tset._bits[ii].yspot = source._yspots[ii];
tset._bits[ii].sorient = source._sorients[ii];
}
if (source._constraints != null) {
tset._bits[ii].constraints = source._constraints[ii];
}
}
// create the trimmed tileset image
@@ -243,6 +267,9 @@ public class TrimmedObjectTileSet extends TileSet
/** The orientation of the "spot" associated with this object. */
public byte sorient = -1;
/** The constraints associated with this object. */
public String[] constraints;
/** Generates a string representation of this instance. */
public String toString ()
{
@@ -251,7 +278,7 @@ public class TrimmedObjectTileSet extends TileSet
/** Increase this value when object's serialized state is impacted
* by a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
private static final long serialVersionUID = 2;
}
/** Contains the width and height of each object tile and the offset
@@ -173,7 +173,7 @@ public class ObjectTileSetRuleSet extends SwissArmyTileSetRuleSet
bodyText);
String[][] constraints = new String[constrs.length][];
for (int ii = 0; ii < constrs.length; ii++) {
constraints[ii] = constrs[ii].split("\\s*|\\s*");
constraints[ii] = constrs[ii].split("\\s*\\|\\s*");
}
((ObjectTileSet)target).setConstraints(constraints);
}
@@ -21,8 +21,11 @@
package com.threerings.stage.server;
import java.awt.Container;
import com.threerings.resource.ResourceManager;
import com.threerings.media.image.ImageManager;
import com.threerings.media.tile.TileManager;
import com.threerings.media.tile.bundle.BundledTileSetRepository;
@@ -42,6 +45,9 @@ public abstract class StageServer extends WhirledServer
* the server and client). */
public ResourceManager rsrcmgr;
/** Provides access to image resources. */
public static ImageManager imagemgr;
/** Provides access to our tile repository. */
public static TileManager tilemgr;
@@ -56,10 +62,11 @@ public abstract class StageServer extends WhirledServer
rsrcmgr = new ResourceManager("rsrc");
rsrcmgr.initBundles(null, "config/resource/manager.properties", null);
// create our tile manager and repository
tilemgr = new TileManager(null);
// create our image manager, tile manager and repository
imagemgr = new ImageManager(rsrcmgr, null);
tilemgr = new TileManager(imagemgr);
tilemgr.setTileSetRepository(
new BundledTileSetRepository(rsrcmgr, null,
new BundledTileSetRepository(rsrcmgr, imagemgr,
StageCodes.TILESET_RSRC_SET));
Log.info("Stage server initialized.");
@@ -0,0 +1,469 @@
//
// $Id: YoSceneUtil.java 19769 2005-03-17 07:38:31Z mdb $
package com.threerings.stage.util;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import com.samskivert.util.ListUtil;
import com.samskivert.util.StringUtil;
import com.threerings.util.DirectionCodes;
import com.threerings.util.MessageBundle;
import com.threerings.media.tile.ObjectTile;
import com.threerings.media.tile.ObjectTileSet;
import com.threerings.media.tile.TileManager;
import com.threerings.miso.data.ObjectInfo;
import com.threerings.stage.Log;
import com.threerings.stage.data.StageCodes;
import com.threerings.stage.data.StageMisoSceneModel;
import com.threerings.stage.data.StageScene;
/**
* Maintains extra information on objects in a scene and checks proposed
* placement operations for constraint violations. When the constraints
* object is in use, all placement operations (object additions and removals)
* must go through the constraints object so that the object's internal state
* remains consistent.
*/
public class PlacementConstraints
implements DirectionCodes, StageCodes
{
/**
* Default constructor.
*/
public PlacementConstraints (TileManager tilemgr, StageScene scene)
{
_tilemgr = tilemgr;
_scene = scene;
_mmodel = StageMisoSceneModel.getSceneModel(scene.getSceneModel());
// add all the objects in the scene
StageMisoSceneModel.ObjectVisitor visitor =
new StageMisoSceneModel.ObjectVisitor() {
public void visit (ObjectInfo info) {
ObjectData data = createObjectData(info);
if (data != null) {
_objectData.put(info, data);
}
}
};
_mmodel.visitObjects(visitor);
}
/**
* Determines whether the constraints allow the specified object to be
* added to the scene.
*
* @return <code>null</code> if the constraints allow the operation,
* otherwise a translatable string explaining why the object can't be
* added
*/
public String allowAddObject (ObjectInfo info)
{
return allowModifyObjects(new ObjectInfo[] { info },
new ObjectInfo[0]);
}
/**
* Adds the specified object through the constraints.
*/
public void addObject (ObjectInfo info)
{
ObjectData data = createObjectData(info);
if (data != null) {
_scene.addObject(info);
_objectData.put(info, data);
}
}
/**
* Determines whether the constraints allow the specified object to be
* removed from the scene.
*
* @return <code>null</code> if the constraints allow the operation,
* otherwise a translatable string explaining why the object can't be
* removed
*/
public String allowRemoveObject (ObjectInfo info)
{
return allowModifyObjects(new ObjectInfo[0],
new ObjectInfo[] { info });
}
/**
* Removes the specified object through the constraints.
*/
public void removeObject (ObjectInfo info)
{
_scene.removeObject(info);
_objectData.remove(info);
}
/**
* Determines whether the constraints allow the specified objects to be
* added and removed simultaneously.
*
* @return <code>null</code> if the constraints allow the operation,
* otherwise a translatable string explaining why the objects can't be
* modified
*/
public String allowModifyObjects (ObjectInfo[] added,
ObjectInfo[] removed)
{
ObjectData[] addedData = new ObjectData[added.length];
for (int i = 0; i < added.length; i++) {
addedData[i] = createObjectData(added[i]);
}
ObjectData[] removedData = new ObjectData[removed.length];
for (int i = 0; i < removed.length; i++) {
removedData[i] = (ObjectData)_objectData.get(removed[i]);
}
return allowModifyObjects(addedData, removedData);
}
/**
* Determines whether the constraints allow the specified objects to be
* added and removed simultaneously. Subclasses that wish to define
* additional constraints should override this method.
*
* @return <code>null</code> if the constraints allow the operation,
* otherwise a qualified translatable string explaining why the objects
* can't be modified
*/
protected String allowModifyObjects (ObjectData[] added,
ObjectData[] removed)
{
for (int i = 0; i < added.length; i++) {
if (added[i].tile.hasConstraint(ObjectTileSet.ON_SURFACE) &&
!isOnSurface(added[i], added, removed)) {
return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
"m.not_on_surface");
} else if (added[i].tile.hasConstraint(ObjectTileSet.ON_WALL) &&
!isOnWall(added[i], added, removed)) {
return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
"m.not_on_wall");
}
int dir = getConstraintDirection(added[i], "ATTACH");
if (dir != NONE && !isAttached(added[i], added, removed, dir)) {
return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
"m.not_attached");
}
dir = getConstraintDirection(added[i], "SPACE");
if (dir != NONE && !hasSpace(added[i], added, removed, dir)) {
return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
"m.no_space");
}
if (!checkAdjacentSpace(added[i], added, removed)) {
return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
"m.no_space_adj");
}
}
for (int i = 0; i < removed.length; i++) {
if (removed[i].tile.hasConstraint(ObjectTileSet.SURFACE) &&
hasOnSurface(removed[i], added, removed)) {
return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
"m.has_on_surface");
} else if (removed[i].tile.hasConstraint(ObjectTileSet.WALL) &&
hasOnWall(removed[i], added, removed)) {
return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
"m.has_on_wall");
} else if (!checkAdjacentAttached(removed[i], added, removed)) {
return MessageBundle.qualify(STAGE_MESSAGE_BUNDLE,
"m.has_attached");
}
}
return null;
}
/**
* Determines whether the specified object is on a surface.
*/
protected boolean isOnSurface (ObjectData data, ObjectData[] added,
ObjectData[] removed)
{
return isCovered(data.bounds, added, removed, ObjectTileSet.SURFACE);
}
/**
* Determines whether the specified object is on a wall.
*/
protected boolean isOnWall (ObjectData data, ObjectData[] added,
ObjectData[] removed)
{
return isCovered(data.bounds, added, removed, ObjectTileSet.WALL);
}
/**
* Verifies that the objects adjacent to the given object will still have
* their attachment constraints met if the object is removed.
*/
protected boolean checkAdjacentAttached (ObjectData data,
ObjectData[] added, ObjectData[] removed)
{
Rectangle rect = new Rectangle(data.bounds);
rect.grow(1, 1);
ArrayList objects = getObjectData(rect, added, removed);
for (int i = 0, size = objects.size(); i < size; i++) {
ObjectData odata = (ObjectData)objects.get(i);
int dir = getConstraintDirection(odata, "ATTACH");
if (dir != NONE && !isAttached(odata, added, removed, dir)) {
return false;
}
}
return true;
}
/**
* Determines whether the specified object is attached to another object in
* the specified direction.
*/
protected boolean isAttached (ObjectData data, ObjectData[] added,
ObjectData[] removed, int dir)
{
return isCovered(getAdjacentEdge(data.bounds, dir), added, removed,
ObjectTileSet.WALL);
}
/**
* Given a rectangle, determines whether all of the tiles within
* the rectangle intersect an object. If the constraint parameter is
* non-null, the intersected objects must have that constraint.
*/
protected boolean isCovered (Rectangle rect, ObjectData[] added,
ObjectData[] removed, String constraint)
{
ArrayList objects = getObjectData(rect, added, removed);
for (int y = rect.y, ymax = rect.y + rect.height; y < ymax; y++) {
for (int x = rect.x, xmax = rect.x + rect.width; x < xmax; x++) {
boolean covered = false;
for (int i = 0, size = objects.size(); i < size; i++) {
ObjectData data = (ObjectData)objects.get(i);
if (data.bounds.contains(x, y) && (constraint == null ||
data.tile.hasConstraint(constraint))) {
covered = true;
break;
}
}
if (!covered) {
return false;
}
}
}
return true;
}
/**
* Verifies that that objects adjacent to the given object will still have
* their space constraints met if the object is added.
*/
protected boolean checkAdjacentSpace (ObjectData data, ObjectData[] added,
ObjectData[] removed)
{
Rectangle rect = new Rectangle(data.bounds);
rect.grow(1, 1);
ArrayList objects = getObjectData(rect, added, removed);
for (int i = 0, size = objects.size(); i < size; i++) {
ObjectData odata = (ObjectData)objects.get(i);
int dir = getConstraintDirection(odata, "SPACE");
if (dir != NONE && !hasSpace(odata, added, removed, dir)) {
return false;
}
}
return true;
}
/**
* Determines whether the specified object has empty space in the specified
* direction.
*/
protected boolean hasSpace (ObjectData data, ObjectData[] added,
ObjectData[] removed, int dir)
{
return getObjectData(getAdjacentEdge(data.bounds, dir), added,
removed).size() == 0;
}
/**
* Creates and returns a rectangle that covers the specified rectangle's
* adjacent edge (the squares one tile beyond the bounds) in the specified
* direction.
*/
protected Rectangle getAdjacentEdge (Rectangle rect, int dir)
{
switch (dir) {
case NORTH:
return new Rectangle(rect.x - 1, rect.y, 1, rect.height);
case EAST:
return new Rectangle(rect.x, rect.y - 1, rect.width, 1);
case SOUTH:
return new Rectangle(rect.x + rect.width, rect.y, 1,
rect.height);
case WEST:
return new Rectangle(rect.x, rect.y + rect.height,
rect.width, 1);
default:
return null;
}
}
/**
* Determines whether the specified surface has anything on it.
*/
protected boolean hasOnSurface (ObjectData data, ObjectData[] added,
ObjectData[] removed)
{
return hasConstrained(data.bounds, added, removed,
ObjectTileSet.ON_SURFACE);
}
/**
* Determines whether the specified wall has anything on it.
*/
protected boolean hasOnWall (ObjectData data, ObjectData[] added,
ObjectData[] removed)
{
return hasConstrained(data.bounds, added, removed,
ObjectTileSet.ON_WALL);
}
/**
* Determines whether the given rectangle overlaps any objects with the
* given constraint.
*/
protected boolean hasConstrained (Rectangle rect, ObjectData[] added,
ObjectData[] removed, String constraint)
{
ArrayList objects = getObjectData(rect, added, removed);
for (int i = 0, size = objects.size(); i < size; i++) {
ObjectData data = (ObjectData)objects.get(i);
if (data.tile.hasConstraint(constraint)) {
return true;
}
}
return false;
}
/**
* Returns the direction in which the specified object is constrained by
* appending "_[NESW]" to the given constraint prefix. Returns
* <code>NONE</code> if there is no such directional constraint.
*/
protected int getConstraintDirection (ObjectData data, String prefix)
{
if (data.tile.hasConstraint(prefix + "_N")) {
return NORTH;
} else if (data.tile.hasConstraint(prefix + "_E")) {
return EAST;
} else if (data.tile.hasConstraint(prefix + "_S")) {
return SOUTH;
} else if (data.tile.hasConstraint(prefix + "_W")) {
return WEST;
} else {
return NONE;
}
}
/**
* Finds all objects whose bounds intersect the given rectangle and
* returns a list containing their {@link ObjectData} elements.
*
* @param added an array of objects to add to the search
* @param removed an array of objects to exclude from the search
*/
protected ArrayList getObjectData (Rectangle rect, ObjectData[] added,
ObjectData[] removed)
{
ArrayList list = new ArrayList();
for (Iterator it = _objectData.values().iterator(); it.hasNext(); ) {
ObjectData data = (ObjectData)it.next();
if (rect.intersects(data.bounds) && !ListUtil.contains(removed,
data)) {
list.add(data);
}
}
for (int i = 0; i < added.length; i++) {
if (rect.intersects(added[i].bounds)) {
list.add(added[i]);
}
}
return list;
}
/**
* Using the tile manager, computes and returns the specified object's
* data.
*/
protected ObjectData createObjectData (ObjectInfo info)
{
try {
ObjectTile tile = (ObjectTile)_tilemgr.getTile(info.tileId);
Rectangle bounds = new Rectangle(info.x, info.y, tile.getBaseWidth(),
tile.getBaseHeight());
bounds.translate(1 - bounds.width, 1 - bounds.height);
return new ObjectData(bounds, tile);
} catch (Exception e) {
Log.warning("Error retrieving tile for object [info=" +
info + ", error=" + e + "].");
Log.logStackTrace(e);
return null;
}
}
/** Contains information about an object used in checking constraints. */
protected class ObjectData
{
public Rectangle bounds;
public ObjectTile tile;
public ObjectData (Rectangle bounds, ObjectTile tile)
{
this.bounds = bounds;
this.tile = tile;
}
}
/** The tile manager to use for object dimensions and constraints. */
protected TileManager _tilemgr;
/** The scene being checked for constraints. */
protected StageScene _scene;
/** The Miso scene model. */
protected StageMisoSceneModel _mmodel;
/** For all objects in the scene, maps {@link ObjectInfo}s to
* {@link ObjectData}s. */
protected HashMap _objectData = new HashMap();
}