Finished up custom render order support.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@2070 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2002-12-16 03:08:39 +00:00
parent 3807953a55
commit 6b273dbf0a
4 changed files with 186 additions and 85 deletions
@@ -1,21 +1,21 @@
//
// $Id: CharacterManager.java,v 1.26 2002/12/07 02:04:31 shaper Exp $
// $Id: CharacterManager.java,v 1.27 2002/12/16 03:08:39 mdb Exp $
package com.threerings.cast;
import java.util.Arrays;
import java.util.Iterator;
import java.util.HashMap;
import com.samskivert.util.LRUHashMap;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Throttle;
import com.samskivert.util.Tuple;
import com.threerings.media.util.Colorization;
import com.threerings.util.DirectionCodes;
import com.threerings.cast.CompositedActionFrames.ComponentFrames;
import com.threerings.cast.Log;
import com.samskivert.util.Throttle;
/**
* The character manager provides facilities for constructing sprites that
@@ -229,52 +229,19 @@ public class CharacterManager
Log.debug("Compositing action [action=" + action +
", descrip=" + descrip + "].");
// obtain the necessary components
ColorizedComponent[] components = new ColorizedComponent[ccount];
for (int i = 0; i < ccount; i++) {
ColorizedComponent cc = new ColorizedComponent();
cc.component = _crepo.getComponent(cids[i]);
if (zations != null) {
cc.zations = zations[i];
}
components[i] = cc;
}
// sort them into the proper rendering order
Arrays.sort(components);
// create colorized versions of all of the source action frames
ActionFrames[] sources = new ActionFrames[ccount];
ComponentFrames[] sources = new ComponentFrames[ccount];
for (int ii = 0; ii < ccount; ii++) {
ActionFrames source = components[ii].component.getFrames(action);
if (zations != null) {
sources[ii] = source.cloneColorized(components[ii].zations);
} else {
sources[ii] = source;
}
sources[ii] = new ComponentFrames();
sources[ii].ccomp = _crepo.getComponent(cids[ii]);
ActionFrames source = sources[ii].ccomp.getFrames(action);
sources[ii].frames = (zations == null || zations[ii] == null) ?
source : source.cloneColorized(zations[ii]);
}
// use those to create an entity that will lazily composite things
// together as they are needed
return new CompositedActionFrames(sources);
}
/** Used when compositing component frame images. */
protected static final class ColorizedComponent implements Comparable
{
/** The component to be colorized. */
public CharacterComponent component;
/** The colorizations to apply. */
public Colorization[] zations;
/** Sorts by render order. */
public int compareTo (Object o)
{
ColorizedComponent co = (ColorizedComponent)o;
return (component.componentClass.renderPriority -
co.component.componentClass.renderPriority);
}
return new CompositedActionFrames(action, sources);
}
/** The component repository. */
+103 -30
View File
@@ -1,24 +1,35 @@
//
// $Id: ComponentClass.java,v 1.6 2002/11/20 02:21:10 mdb Exp $
// $Id: ComponentClass.java,v 1.7 2002/12/16 03:08:39 mdb Exp $
package com.threerings.cast;
import java.io.Serializable;
import java.util.Comparator;
import com.samskivert.util.ArrayIntSet;
import com.samskivert.util.SortableArrayList;
import com.threerings.util.DirectionUtil;
/**
* Denotes a class of components to which {@link CharacterComponent}
* objects belong. Examples include "Hat", "Head", and "Feet". A component
* class dictates a component's rendering priority so that components can
* be rendered in an order that causes them to overlap properly.
*
* <p> Components support render priority overrides for particular
* actions, orientations or combinations of actions and orientations. The
* system is currently structured with the expectation that the overrides
* will be relatively few (less than fifteen, say) for any given component
* class. A system that relied on many overrides for its components would
* want to implement a more scalable algorithm for determining which, if
* any, override matches a particular action and orientation combination.
*/
public class ComponentClass implements Serializable
{
/** Used to effect custom render orders for particular actions,
* orientations, etc. */
public static class PriorityOverride
implements Comparable, Serializable
{
/** The overridden render priority value. */
public int renderPriority;
@@ -29,25 +40,106 @@ public class ComponentClass implements Serializable
/** The orientations, if any, for which this override is
* appropriate. */
public ArrayIntSet orients;
}
/** The comparator used to sort component class objects in render
* priority order. */
public static final Comparator RENDER_COMP = new RenderComparator();
/**
* Determines whether this priority override matches the specified
* action and orientation combination.
*/
public boolean matches (String action, int orient)
{
return (((orients == null) || orients.contains(orient)) &&
((this.action == null) || this.action.equals(action)));
}
// documentation inherited from interface
public int compareTo (Object other)
{
// overrides with both an action and an orientation should
// come first in the list
PriorityOverride po = (PriorityOverride)other;
int pri = priority(), opri = po.priority();
if (pri == opri) {
return hashCode() - po.hashCode();
} else {
return pri - opri;
}
}
protected int priority ()
{
int priority = 0;
if (action != null) {
priority++;
}
if (orients != null) {
priority++;
}
return priority;
}
/** Generates a string representation of this instance. */
public String toString ()
{
return "[pri=" + renderPriority + ", action=" + action +
", orients=" + orients + "]";
}
}
/** The component class name. */
public String name;
/** The render priority. */
/** The default render priority. */
public int renderPriority;
/**
* Creates a component class with the specified name and default
* render priority.
*/
public ComponentClass (String name, int defaultRenderPriority)
{
this.name = name;
this.renderPriority = defaultRenderPriority;
}
/**
* Creates an uninitialized instance suitable for unserialization.
*/
public ComponentClass ()
{
}
/**
* Returns the render priority appropriate for the specified action
* and orientation.
*/
public int getRenderPriority (String action, int orientation)
{
return -1;
// because we expect there to be relatively few priority
// overrides, we simply search linearly through the list for the
// closest match
int ocount = (_overrides != null) ? _overrides.size() : 0;
for (int ii = 0; ii < ocount; ii++) {
PriorityOverride over = (PriorityOverride)_overrides.get(ii);
// based on the way the overrides are sorted, the first match
// is the most specific and the one we want
if (over.matches(action, orientation)) {
return over.renderPriority;
}
}
return renderPriority;
}
/**
* Adds the supplied render priority override record to this component
* class.
*/
public void addPriorityOverride (PriorityOverride override)
{
if (_overrides == null) {
_overrides = new SortableArrayList();
}
_overrides.insertSorted(override);
}
/**
@@ -78,29 +170,10 @@ public class ComponentClass implements Serializable
return "[name=" + name + ", renderPriority=" + renderPriority + "]";
}
/**
* The comparator used to sort {@link CharacterComponent} instances in
* render priority order so that compositing components into a single
* character image can be done in the proper order.
*/
protected static class RenderComparator implements Comparator
{
// documentation inherited
public int compare (Object a, Object b)
{
if (!(a instanceof CharacterComponent) ||
!(b instanceof CharacterComponent)) {
return -1;
}
CharacterComponent ca = (CharacterComponent)a;
CharacterComponent cb = (CharacterComponent)b;
return (ca.componentClass.renderPriority -
cb.componentClass.renderPriority);
}
}
/** A list of render priority overrides. */
protected SortableArrayList _overrides;
/** 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,11 +1,13 @@
//
// $Id: CompositedActionFrames.java,v 1.9 2002/12/07 02:04:31 shaper Exp $
// $Id: CompositedActionFrames.java,v 1.10 2002/12/16 03:08:39 mdb Exp $
package com.threerings.cast;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.util.Arrays;
import java.util.Comparator;
import com.samskivert.util.StringUtil;
@@ -13,6 +15,7 @@ import com.threerings.media.util.Colorization;
import com.threerings.media.util.ImageUtil;
import com.threerings.media.util.MultiFrameImage;
import com.threerings.cast.CharacterComponent;
import com.threerings.util.DirectionCodes;
/**
@@ -20,14 +23,27 @@ import com.threerings.util.DirectionCodes;
* to lazily create composited character frames when they are requested.
*/
public class CompositedActionFrames
implements ActionFrames, DirectionCodes
implements ActionFrames, DirectionCodes, Comparator
{
/** Used to associate a {@link CharacterComponent} with its {@link
* ActionFrames} for a particular action. */
public static class ComponentFrames
{
public CharacterComponent ccomp;
public ActionFrames frames;
public String toString () {
return ccomp + ":" + frames;
}
}
/**
* Constructs a set of composited action frames with the supplied
* source frames and colorization configuration. The actual component
* frame images will not be composited until they are requested.
*/
public CompositedActionFrames (ActionFrames[] sources)
public CompositedActionFrames (String action, ComponentFrames[] sources)
{
// sanity check
if (sources == null || sources.length == 0) {
@@ -36,12 +52,13 @@ public class CompositedActionFrames
throw new RuntimeException(errmsg);
}
_sources = sources;
_action = action;
// the sources must all have the same orientation count, and each
// orientation must also have the same frame count, so we just use
// the counts from the first source and orientation
_orientCount = _sources[0].getOrientationCount();
_frameCount = _sources[0].getFrames(NORTH).getFrameCount();
_orientCount = _sources[0].frames.getOrientationCount();
_frameCount = _sources[0].frames.getFrames(NORTH).getFrameCount();
_images = new Image[_orientCount][_frameCount];
_bounds = new Rectangle[_orientCount][_frameCount];
}
@@ -129,6 +146,14 @@ public class CompositedActionFrames
return size;
}
// documentation inherited from interface
public int compare (Object o1, Object o2)
{
ComponentFrames cf1 = (ComponentFrames)o1, cf2 = (ComponentFrames)o2;
return (cf1.ccomp.componentClass.getRenderPriority(_action, _sorient) -
cf2.ccomp.componentClass.getRenderPriority(_action, _sorient));
}
// documentation inherited
protected Image getFrame (int orient, int index)
{
@@ -163,12 +188,17 @@ public class CompositedActionFrames
// // DEBUG
// int width = 0, height = 0;
// sort the sources appropriately for this orientation
_sorient = orient;
Arrays.sort(_sources, this);
// first we need to determine the bounds of the rectangle that
// will enclose all of our various components
Rectangle tbounds = new Rectangle();
Rectangle bounds = _bounds[orient][index] = new Rectangle(0, 0, 0, 0);
for (int ii = 0; ii < scount; ii++) {
TrimmedMultiFrameImage source = _sources[ii].getFrames(orient);
TrimmedMultiFrameImage source =
_sources[ii].frames.getFrames(orient);
source.getTrimmedBounds(index, tbounds);
// the first one defines our initial bounds
if (bounds.width == 0 && bounds.height == 0) {
@@ -190,7 +220,8 @@ public class CompositedActionFrames
// now render each of the components into a composited frame
for (int ii = 0; ii < scount; ii++) {
TrimmedMultiFrameImage source = _sources[ii].getFrames(orient);
TrimmedMultiFrameImage source =
_sources[ii].frames.getFrames(orient);
source.getTrimmedBounds(index, tbounds);
// render this frame for this particular action for this
@@ -208,8 +239,8 @@ public class CompositedActionFrames
// ", width=" + width + ", height=" + height + "].");
// keep track of our new origin
bounds.x = (_sources[0].getXOrigin(orient, index) - bounds.x);
bounds.y = (_sources[0].getYOrigin(orient, index) - bounds.y);
bounds.x = (_sources[0].frames.getXOrigin(orient, index) - bounds.x);
bounds.y = (_sources[0].frames.getYOrigin(orient, index) - bounds.y);
// Log.info("New origin [x=" + bounds.x + ", y=" + bounds.y + "].");
@@ -220,14 +251,20 @@ public class CompositedActionFrames
return dest;
}
/** The action for which we're compositing frames. */
protected String _action;
/** The orientation for which we're currently sorting. */
protected int _sorient;
/** The number of orientations. */
protected int _orientCount;
/** The number of frames in each orientation. */
protected int _frameCount;
/** Our source action frames. */
protected ActionFrames[] _sources;
/** Our source components and action frames. */
protected ComponentFrames[] _sources;
/** The frame images. */
protected Image[][] _images;
@@ -1,14 +1,19 @@
//
// $Id: ClassRuleSet.java,v 1.1 2001/11/27 08:09:35 mdb Exp $
// $Id: ClassRuleSet.java,v 1.2 2002/12/16 03:08:39 mdb Exp $
package com.threerings.cast.tools.xml;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.RuleSetBase;
import com.samskivert.util.ArrayIntSet;
import com.samskivert.util.StringUtil;
import com.samskivert.xml.SetPropertyFieldsRule;
import com.samskivert.xml.SetPropertyFieldsRule.FieldParser;
import com.threerings.cast.ComponentClass.PriorityOverride;
import com.threerings.cast.ComponentClass;
import com.threerings.util.DirectionUtil;
/**
* The class rule set is used to parse the attributes of a component class
@@ -53,6 +58,25 @@ public class ClassRuleSet extends RuleSetBase
// grab the attributes from the <class> tag
digester.addRule(_prefix + CLASS_PATH,
new SetPropertyFieldsRule(digester));
// parse render priority overrides
String opath = _prefix + CLASS_PATH + "/override";
digester.addObjectCreate(opath, PriorityOverride.class.getName());
SetPropertyFieldsRule rule = new SetPropertyFieldsRule(digester);
rule.addFieldParser("orients", new FieldParser() {
public Object parse (String text) {
String[] orients = StringUtil.parseStringArray(text);
ArrayIntSet oset = new ArrayIntSet();
for (int ii = 0; ii < orients.length; ii++) {
oset.add(DirectionUtil.fromShortString(orients[ii]));
}
return oset;
}
});
digester.addRule(opath, rule);
digester.addSetNext(opath, "addPriorityOverride",
PriorityOverride.class.getName());
}
/** The prefix at which me match our component classes. */