diff --git a/src/java/com/threerings/cast/ActionFrames.java b/src/java/com/threerings/cast/ActionFrames.java index 3898f875..0c6f334b 100644 --- a/src/java/com/threerings/cast/ActionFrames.java +++ b/src/java/com/threerings/cast/ActionFrames.java @@ -64,4 +64,10 @@ public interface ActionFrames * colorizations applied to the frame images. */ public ActionFrames cloneColorized (Colorization[] zations); + + /** + * Creates a clone of these action frames which will have the supplied + * translation applied to the frame images. + */ + public ActionFrames cloneTranslated (int dx, int dy); } diff --git a/src/java/com/threerings/cast/CharacterDescriptor.java b/src/java/com/threerings/cast/CharacterDescriptor.java index 4353d9af..1d6ae41f 100644 --- a/src/java/com/threerings/cast/CharacterDescriptor.java +++ b/src/java/com/threerings/cast/CharacterDescriptor.java @@ -21,6 +21,7 @@ package com.threerings.cast; +import java.awt.Point; import java.util.Arrays; import com.samskivert.util.StringUtil; import com.threerings.media.image.Colorization; @@ -75,6 +76,23 @@ public class CharacterDescriptor _zations = zations; } + /** + * Returns the array of translations to be applied to the components + * when compositing action images. + */ + public Point[] getTranslations () + { + return _xlations; + } + + /** + * Updates the translations to be used by this character descriptor. + */ + public void setTranslations (Point[] xlations) + { + _xlations = xlations; + } + /** * Compute a sensible hashcode for this object. */ @@ -124,7 +142,7 @@ public class CharacterDescriptor } } - return true; + return Arrays.equals(_xlations, odesc._xlations); } /** @@ -141,4 +159,7 @@ public class CharacterDescriptor /** The colorizations to apply when compositing this character. */ protected Colorization[][] _zations; + + /** The translations to apply when compositing this character. */ + protected Point[] _xlations; } diff --git a/src/java/com/threerings/cast/CharacterManager.java b/src/java/com/threerings/cast/CharacterManager.java index c6aec5ac..52c2a4d1 100644 --- a/src/java/com/threerings/cast/CharacterManager.java +++ b/src/java/com/threerings/cast/CharacterManager.java @@ -21,6 +21,8 @@ package com.threerings.cast; +import java.awt.Point; + import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -94,7 +96,7 @@ public class CharacterManager { // sanity check if (!CharacterSprite.class.isAssignableFrom(charClass)) { - String errmsg = "Requested to use character sprite class that " + + String errmsg = "Requested to use character sprite class that " + "does not derive from CharacterSprite " + "[class=" + charClass.getName() + "]."; throw new IllegalArgumentException(errmsg); @@ -250,25 +252,25 @@ public class CharacterManager int[] cids = descrip.getComponentIds(); int ccount = cids.length; Colorization[][] zations = descrip.getColorizations(); + Point[] xlations = descrip.getTranslations(); Log.debug("Compositing action [action=" + action + ", descrip=" + descrip + "]."); // this will be used to construct any shadow layers - HashMap shadows = null; - + HashMap> shadows = null; + // maps components by class name for masks - HashMap ccomps = new HashMap(); - + HashMap> ccomps = + new HashMap>(); + // create colorized versions of all of the source action frames - ArrayList sources = new ArrayList(ccount); + ArrayList sources = new ArrayList(ccount); for (int ii = 0; ii < ccount; ii++) { ComponentFrames cframes = new ComponentFrames(); sources.add(cframes); - CharacterComponent ccomp = - (cframes.ccomp = _crepo.getComponent(cids[ii])); - ccomps.put(ccomp.componentClass.name, ccomp); - + CharacterComponent ccomp = (cframes.ccomp = _crepo.getComponent(cids[ii])); + // load up the main component images ActionFrames source = ccomp.getFrames(action, null); if (source == null) { @@ -277,32 +279,39 @@ public class CharacterManager ", desc=" + descrip + ", comp=" + ccomp + "]"; throw new RuntimeException(errmsg); } - cframes.frames = (zations == null || zations[ii] == null) ? + source = (zations == null || zations[ii] == null) ? source : source.cloneColorized(zations[ii]); - + Point xlation = (xlations == null) ? null : xlations[ii]; + cframes.frames = (xlation == null) ? + source : source.cloneTranslated(xlation.x, xlation.y); + + // store the component with its translation under its class for masking + TranslatedComponent tcomp = new TranslatedComponent(ccomp, xlation); + ArrayList tcomps = ccomps.get(ccomp.componentClass.name); + if (tcomps == null) { + ccomps.put(ccomp.componentClass.name, + tcomps = new ArrayList()); + } + tcomps.add(tcomp); + // if this component has a shadow, make a note of it if (ccomp.componentClass.isShadowed()) { if (shadows == null) { - shadows = new HashMap(); + shadows = new HashMap>(); } - ArrayList shadlist = (ArrayList) - shadows.get(ccomp.componentClass.shadow); + ArrayList shadlist = shadows.get(ccomp.componentClass.shadow); if (shadlist == null) { shadows.put(ccomp.componentClass.shadow, - shadlist = new ArrayList()); + shadlist = new ArrayList()); } - shadlist.add(ccomp); + shadlist.add(tcomp); } } // now create any necessary shadow layers if (shadows != null) { - Iterator iter = shadows.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry entry = (Map.Entry)iter.next(); - String sclass = (String)entry.getKey(); - ArrayList scomps = (ArrayList)entry.getValue(); - ComponentFrames scf = compositeShadow(action, sclass, scomps); + for (Map.Entry> entry : shadows.entrySet()) { + ComponentFrames scf = compositeShadow(action, entry.getKey(), entry.getValue()); if (scf != null) { sources.add(scf); } @@ -310,46 +319,21 @@ public class CharacterManager } // add any necessary masks - for (int ii = 0, nn = sources.size(); ii < nn; ii++) { - ComponentFrames cframes = (ComponentFrames)sources.get(ii); - CharacterComponent mcomp = (CharacterComponent)ccomps.get( - cframes.ccomp.componentClass.mask); - if (mcomp != null) { - cframes.frames = compositeMask(action, cframes.ccomp, - cframes.frames, mcomp); + for (ComponentFrames cframes : sources) { + ArrayList mcomps = ccomps.get(cframes.ccomp.componentClass.mask); + if (mcomps != null) { + cframes.frames = compositeMask(action, cframes.ccomp, cframes.frames, mcomps); } } - // use those to create an entity that will lazily composite things // together as they are needed - ComponentFrames[] cfvec = (ComponentFrames[])sources.toArray( - new ComponentFrames[sources.size()]); + ComponentFrames[] cfvec = sources.toArray(new ComponentFrames[sources.size()]); return new CompositedActionFrames(_imgr, _frameCache, action, cfvec); } - protected ActionFrames compositeMask ( - String action, CharacterComponent ccomp, ActionFrames cframes, - CharacterComponent mcomp) - { - ActionFrames mframes = mcomp.getFrames(action, - StandardActions.CROP_TYPE); - if (mframes == null) { - return cframes; - } - return new CompositedActionFrames( - _imgr, _frameCache, action, new ComponentFrames[] { - new ComponentFrames(ccomp, cframes), - new ComponentFrames(mcomp, mframes) }) { - protected CompositedMultiFrameImage createFrames (int orient) { - return new CompositedMaskedImage( - _imgr, _sources, _action, orient); - } - }; - } - protected ComponentFrames compositeShadow ( - String action, String sclass, ArrayList scomps) + String action, String sclass, ArrayList scomps) { final ComponentClass cclass = _crepo.getComponentClass(sclass); if (cclass == null) { @@ -363,12 +347,11 @@ public class CharacterManager // create a fake component for the shadow layer cframes.ccomp = new CharacterComponent(-1, "shadow", cclass, null); - ArrayList sources = new ArrayList(); - for (int ii = 0, ll = scomps.size(); ii < ll; ii++) { + ArrayList sources = new ArrayList(); + for (TranslatedComponent scomp : scomps) { ComponentFrames source = new ComponentFrames(); - source.ccomp = (CharacterComponent)scomps.get(ii); - source.frames = source.ccomp.getFrames( - action, StandardActions.SHADOW_TYPE); + source.ccomp = scomp.ccomp; + source.frames = scomp.getFrames(action, StandardActions.SHADOW_TYPE); if (source.frames == null) { // skip this shadow component continue; @@ -383,10 +366,8 @@ public class CharacterManager // create custom action frames that use a special compositing // multi-frame image that does the necessary shadow magic - ComponentFrames[] svec = (ComponentFrames[]) - sources.toArray(new ComponentFrames[sources.size()]); - cframes.frames = new CompositedActionFrames( - _imgr, _frameCache, action, svec) { + ComponentFrames[] svec = sources.toArray(new ComponentFrames[sources.size()]); + cframes.frames = new CompositedActionFrames(_imgr, _frameCache, action, svec) { protected CompositedMultiFrameImage createFrames (int orient) { return new CompositedShadowImage( _imgr, _sources, _action, orient, cclass.shadowAlpha); @@ -396,6 +377,49 @@ public class CharacterManager return cframes; } + protected ActionFrames compositeMask ( + String action, CharacterComponent ccomp, ActionFrames cframes, + ArrayList mcomps) + { + ArrayList sources = new ArrayList(); + sources.add(new ComponentFrames(ccomp, cframes)); + for (TranslatedComponent mcomp : mcomps) { + ActionFrames mframes = mcomp.getFrames(action, StandardActions.CROP_TYPE); + if (mframes != null) { + sources.add(new ComponentFrames(mcomp.ccomp, mframes)); + } + } + if (sources.size() == 1) { + return cframes; + } + ComponentFrames[] mvec = sources.toArray(new ComponentFrames[sources.size()]); + return new CompositedActionFrames(_imgr, _frameCache, action, mvec) { + protected CompositedMultiFrameImage createFrames (int orient) { + return new CompositedMaskedImage(_imgr, _sources, _action, orient); + } + }; + } + + /** Combines a component with an optional translation for shadowing or masking. */ + protected static class TranslatedComponent + { + public CharacterComponent ccomp; + public Point xlation; + + public TranslatedComponent (CharacterComponent ccomp, Point xlation) + { + this.ccomp = ccomp; + this.xlation = xlation; + } + + public ActionFrames getFrames (String action, String type) + { + ActionFrames frames = ccomp.getFrames(action, type); + return (frames == null || xlation == null) ? + frames : frames.cloneTranslated(xlation.x, xlation.y); + } + } + /** The image manager with whom we interact. */ protected ImageManager _imgr; diff --git a/src/java/com/threerings/cast/ComponentClass.java b/src/java/com/threerings/cast/ComponentClass.java index 49f178ce..1aa38fcd 100644 --- a/src/java/com/threerings/cast/ComponentClass.java +++ b/src/java/com/threerings/cast/ComponentClass.java @@ -129,6 +129,9 @@ public class ComponentClass implements Serializable * shadow for the special "shadow" component class. */ public float shadowAlpha = 1.0f; + /** Whether or not components of this class will have translations applied. */ + public boolean translate; + /** * Creates an uninitialized instance suitable for unserialization or * population during XML parsing. @@ -237,5 +240,5 @@ public class ComponentClass implements Serializable /** Increase this value when object's serialized state is impacted by * a class change (modification of fields, inheritance). */ - private static final long serialVersionUID = 3; + private static final long serialVersionUID = 4; } diff --git a/src/java/com/threerings/cast/CompositedActionFrames.java b/src/java/com/threerings/cast/CompositedActionFrames.java index 7bf659d0..4010b4b6 100644 --- a/src/java/com/threerings/cast/CompositedActionFrames.java +++ b/src/java/com/threerings/cast/CompositedActionFrames.java @@ -48,13 +48,13 @@ public class CompositedActionFrames public ComponentFrames () { } - + public ComponentFrames ( CharacterComponent ccomp, ActionFrames frames) { this.ccomp = ccomp; this.frames = frames; } - + public String toString () { return ccomp + ":" + frames; } @@ -127,6 +127,17 @@ public class CompositedActionFrames throw new RuntimeException("What you talkin' about Willis?"); } + // documentation inherited from interface + public ActionFrames cloneTranslated (int dx, int dy) + { + ComponentFrames[] tsources = new ComponentFrames[_sources.length]; + for (int ii = 0; ii < _sources.length; ii++) { + tsources[ii] = new ComponentFrames( + _sources[ii].ccomp, _sources[ii].frames.cloneTranslated(dx, dy)); + } + return new CompositedActionFrames(_imgr, _frameCache, _action, tsources); + } + /** * Creates our underlying multi-frame image for a particular orientation. */ diff --git a/src/java/com/threerings/cast/CompositedMaskedImage.java b/src/java/com/threerings/cast/CompositedMaskedImage.java index b6a90f46..71c663d8 100644 --- a/src/java/com/threerings/cast/CompositedMaskedImage.java +++ b/src/java/com/threerings/cast/CompositedMaskedImage.java @@ -45,7 +45,7 @@ public class CompositedMaskedImage extends CompositedMultiFrameImage { super(imgr, sources, action, orient); } - + // documentation inherited from interface public int getWidth (int index) { return _sources[0].frames.getFrames(_orient).getWidth(index); @@ -63,7 +63,7 @@ public class CompositedMaskedImage extends CompositedMultiFrameImage public int getYOrigin (int index) { return _sources[0].frames.getYOrigin(_orient, index); } - + // documentation inherited from interface public void paintFrame (Graphics2D g, int index, int x, int y) { _images[index].paint(g, x + _images[index].getX(), @@ -85,18 +85,18 @@ public class CompositedMaskedImage extends CompositedMultiFrameImage // documentation inherited protected CompositedMirage createCompositedMirage (int index) { - return new MaskedMirage(index); + return new MaskedMirage(index); } - + /** - * Combines the image in the first source with the mask in the second. */ + * Combines the image in the first source with the masks in the rest. */ protected class MaskedMirage extends CompositedMirage { public MaskedMirage (int index) { super(index); } - + // documentation inherited protected Rectangle combineBounds (Rectangle bounds, Rectangle tbounds) { @@ -107,19 +107,20 @@ public class CompositedMaskedImage extends CompositedMultiFrameImage } return bounds; } - + // documentation inherited protected void refreshVolatileImage () { Graphics2D g = (Graphics2D)_image.getGraphics(); try { - TrimmedMultiFrameImage source = - _sources[0].frames.getFrames(_orient), - mask = _sources[1].frames.getFrames(_orient); + TrimmedMultiFrameImage source = _sources[0].frames.getFrames(_orient); source.paintFrame(g, _index, -_bounds.x, -_bounds.y); g.setComposite(AlphaComposite.DstIn); - mask.paintFrame(g, _index, -_bounds.x, -_bounds.y); - + for (int ii = 1; ii < _sources.length; ii++) { + TrimmedMultiFrameImage mask = _sources[ii].frames.getFrames(_orient); + mask.paintFrame(g, _index, -_bounds.x, -_bounds.y); + } + } finally { // clean up after ourselves if (g != null) { diff --git a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java index 2a11b603..4b68bd03 100644 --- a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java +++ b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java @@ -345,9 +345,9 @@ public class BundledComponentRepository // if that failed too, we're hosed if (aset == null) { - // if this is a shadow or crop image, no need to freak out + // if this is a shadow or crop image, no need to freak out // as they are optional - if (!StandardActions.CROP_TYPE.equals(type) && + if (!StandardActions.CROP_TYPE.equals(type) && !StandardActions.SHADOW_TYPE.equals(type)) { Log.warning("Unable to locate tileset for action '" + imgpath + "' " + component + "."); @@ -388,9 +388,20 @@ public class BundledComponentRepository * for the specified orientation. */ public TileSetFrameImage (TileSet set, ActionSequence actseq) + { + this(set, actseq, 0, 0); + } + + /** + * Constructs a tileset frame image with the specified tileset and + * for the specified orientation, with an optional translation. + */ + public TileSetFrameImage (TileSet set, ActionSequence actseq, int dx, int dy) { _set = set; _actseq = actseq; + _dx = dx; + _dy = dy; // compute these now to avoid pointless recomputation later _ocount = actseq.orients.length; @@ -438,7 +449,7 @@ public class BundledComponentRepository { Tile tile = getTile(orient, index); if (tile != null) { - tile.paint(g, x, y); + tile.paint(g, x + _dx, y + _dy); } } @@ -446,7 +457,7 @@ public class BundledComponentRepository public boolean hitTest (int index, int x, int y) { Tile tile = getTile(orient, index); - return (tile != null) ? tile.hitTest(x, y) : false; + return (tile != null) ? tile.hitTest(x + _dx, y + _dy) : false; } // documentation inherited from interface @@ -459,6 +470,7 @@ public class BundledComponentRepository bounds.setBounds( 0, 0, tile.getWidth(), tile.getHeight()); } + bounds.translate(_dx, _dy); } }; } @@ -481,6 +493,12 @@ public class BundledComponentRepository return new TileSetFrameImage(_set.clone(zations), _actseq); } + // documentation inherited from interface + public ActionFrames cloneTranslated (int dx, int dy) + { + return new TileSetFrameImage(_set, _actseq, dx, dy); + } + /** * Fetches the requested tile. */ @@ -496,6 +514,9 @@ public class BundledComponentRepository /** The action sequence for which we're providing frame images. */ protected ActionSequence _actseq; + /** A translation to apply to the images. */ + protected int _dx, _dy; + /** Frame and orientation counts. */ protected int _fcount, _ocount;