diff --git a/src/java/com/threerings/cast/CharacterManager.java b/src/java/com/threerings/cast/CharacterManager.java index fb37edcb7..1eb65545d 100644 --- a/src/java/com/threerings/cast/CharacterManager.java +++ b/src/java/com/threerings/cast/CharacterManager.java @@ -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. */ diff --git a/src/java/com/threerings/cast/ComponentClass.java b/src/java/com/threerings/cast/ComponentClass.java index 65735337a..c026ef238 100644 --- a/src/java/com/threerings/cast/ComponentClass.java +++ b/src/java/com/threerings/cast/ComponentClass.java @@ -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. + * + *

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; } diff --git a/src/java/com/threerings/cast/CompositedActionFrames.java b/src/java/com/threerings/cast/CompositedActionFrames.java index 24cff36cd..3124c12e0 100644 --- a/src/java/com/threerings/cast/CompositedActionFrames.java +++ b/src/java/com/threerings/cast/CompositedActionFrames.java @@ -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; diff --git a/src/java/com/threerings/cast/tools/xml/ClassRuleSet.java b/src/java/com/threerings/cast/tools/xml/ClassRuleSet.java index bf6ff0a2c..c5b59b254 100644 --- a/src/java/com/threerings/cast/tools/xml/ClassRuleSet.java +++ b/src/java/com/threerings/cast/tools/xml/ClassRuleSet.java @@ -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 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. */