From 4dba269585aab0b6e675bc3fe406d1192ab0b6a9 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Thu, 20 Oct 2005 22:12:45 +0000 Subject: [PATCH] Finished support for shadows. This is all some seriously complicated shit, but the shadows sure look cool. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3740 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../threerings/cast/CharacterComponent.java | 12 ++- .../com/threerings/cast/CharacterManager.java | 101 ++++++++++++++---- .../com/threerings/cast/ComponentClass.java | 13 ++- .../cast/CompositedActionFrames.java | 11 +- .../cast/CompositedShadowImage.java | 92 ++++++++++++++++ .../com/threerings/cast/FrameProvider.java | 4 +- .../com/threerings/cast/StandardActions.java | 8 +- .../bundle/BundledComponentRepository.java | 20 ++-- .../bundle/tools/ComponentBundlerTask.java | 3 +- .../media/image/VolatileMirage.java | 22 +++- 10 files changed, 239 insertions(+), 47 deletions(-) create mode 100644 src/java/com/threerings/cast/CompositedShadowImage.java diff --git a/src/java/com/threerings/cast/CharacterComponent.java b/src/java/com/threerings/cast/CharacterComponent.java index b4b456764..8e2df3d2f 100644 --- a/src/java/com/threerings/cast/CharacterComponent.java +++ b/src/java/com/threerings/cast/CharacterComponent.java @@ -59,13 +59,15 @@ public class CharacterComponent implements Serializable } /** - * Returns the image frames for the specified action animation or null - * if no animation for the specified action is available for this - * component. + * Returns the image frames for the specified action animation or null if + * no animation for the specified action is available for this component. + * + * @param type null for the normal action frames or one of the custom + * action sub-types: {@link StandardActions#SHADOW_TYPE}, etc. */ - public ActionFrames getFrames (String action) + public ActionFrames getFrames (String action, String type) { - return _frameProvider.getFrames(this, action); + return _frameProvider.getFrames(this, action, type); } /** diff --git a/src/java/com/threerings/cast/CharacterManager.java b/src/java/com/threerings/cast/CharacterManager.java index 37f551ba5..3e5bf6c9e 100644 --- a/src/java/com/threerings/cast/CharacterManager.java +++ b/src/java/com/threerings/cast/CharacterManager.java @@ -21,11 +21,14 @@ package com.threerings.cast; -import java.util.Iterator; +import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; import com.samskivert.util.LRUHashMap; import com.samskivert.util.RuntimeAdjust; +import com.samskivert.util.StringUtil; import com.samskivert.util.Throttle; import com.samskivert.util.Tuple; @@ -255,37 +258,99 @@ public class CharacterManager HashMap shadows = null; // create colorized versions of all of the source action frames - ComponentFrames[] sources = new ComponentFrames[ccount]; + ArrayList sources = new ArrayList(ccount); for (int ii = 0; ii < ccount; ii++) { - sources[ii] = new ComponentFrames(); - sources[ii].ccomp = _crepo.getComponent(cids[ii]); + ComponentFrames cframes = new ComponentFrames(); + sources.add(cframes); + CharacterComponent ccomp = + (cframes.ccomp = _crepo.getComponent(cids[ii])); // load up the main component images - ActionFrames source = sources[ii].ccomp.getFrames(action); + ActionFrames source = ccomp.getFrames(action, null); if (source == null) { String errmsg = "Cannot composite action frames; no such " + "action for component [action=" + action + - ", desc=" + descrip + ", comp=" + sources[ii].ccomp + "]"; + ", desc=" + descrip + ", comp=" + ccomp + "]"; throw new RuntimeException(errmsg); } - sources[ii].frames = (zations == null || zations[ii] == null) ? + cframes.frames = (zations == null || zations[ii] == null) ? source : source.cloneColorized(zations[ii]); -// // load up the shadow images if they are needed -// if (sources[ii].ccomp.componentClass.isShadowed()) { -// sources[ii].shadowFrames = sources[ii].ccomp.getFrames( -// action + StandardActions.SHADOW_SUFFIX); -// if (sources[ii].shadowFrames == null) { -// Log.warning("Missing shadow frames for action " + -// "[action=" + action + -// ", comp=" + sources[ii].ccomp + "]."); -// } -// } + // if this component has a shadow, make a note of it + if (ccomp.componentClass.isShadowed()) { + if (shadows == null) { + shadows = new HashMap(); + } + ArrayList shadlist = (ArrayList) + shadows.get(ccomp.componentClass.shadow); + if (shadlist == null) { + shadows.put(ccomp.componentClass.shadow, + shadlist = new ArrayList()); + } + shadlist.add(ccomp); + } + } + + // 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); + if (scf != null) { + sources.add(scf); + } + } } // use those to create an entity that will lazily composite things // together as they are needed - return new CompositedActionFrames(_imgr, _frameCache, action, sources); + ComponentFrames[] cfvec = (ComponentFrames[])sources.toArray( + new ComponentFrames[sources.size()]); + return new CompositedActionFrames(_imgr, _frameCache, action, cfvec); + } + + protected ComponentFrames compositeShadow ( + String action, String sclass, ArrayList scomps) + { + final ComponentClass cclass = _crepo.getComponentClass(sclass); + if (cclass == null) { + Log.warning("Components reference non-existent shadow layer class " + + "[sclass=" + sclass + + ", scomps=" + StringUtil.toString(scomps) + "]."); + return null; + } + + ComponentFrames cframes = new ComponentFrames(); + // create a fake component for the shadow layer + cframes.ccomp = new CharacterComponent(-1, "shadow", cclass, null); + + ComponentFrames[] sources = new ComponentFrames[scomps.size()]; + for (int ii = 0, ll = scomps.size(); ii < ll; ii++) { + sources[ii] = new ComponentFrames(); + sources[ii].ccomp = (CharacterComponent)scomps.get(ii); + sources[ii].frames = sources[ii].ccomp.getFrames( + action, StandardActions.SHADOW_TYPE); + if (sources[ii].frames == null) { + Log.warning("Missing shadow frames for action " + + "[action=" + action + + ", comp=" + sources[ii].ccomp + "]."); + } + } + + // create custom action frames that use a special compositing + // multi-frame image that does the necessary shadow magic + cframes.frames = new CompositedActionFrames( + _imgr, _frameCache, action, sources) { + protected CompositedMultiFrameImage createFrames (int orient) { + return new CompositedShadowImage( + _imgr, _sources, _action, orient, cclass.shadowAlpha); + } + }; + + return cframes; } /** The image manager with whom we interact. */ diff --git a/src/java/com/threerings/cast/ComponentClass.java b/src/java/com/threerings/cast/ComponentClass.java index c92ade1eb..d951a7478 100644 --- a/src/java/com/threerings/cast/ComponentClass.java +++ b/src/java/com/threerings/cast/ComponentClass.java @@ -121,9 +121,9 @@ public class ComponentClass implements Serializable * class contributes a shadow. */ public String shadow; - /** Null for a normal component, the color of the pre-composited shadow for - * the special "shadow" component class. */ - public Color shadowColor; + /** 1.0 for a normal component, the alpha value of the pre-composited + * shadow for the special "shadow" component class. */ + public float shadowAlpha = 1.0f; /** * Creates an uninitialized instance suitable for unserialization or @@ -183,7 +183,7 @@ public class ComponentClass implements Serializable */ public boolean isShadow () { - return (shadowColor != null); + return (shadowAlpha != 1.0f); } /** @@ -217,9 +217,8 @@ public class ComponentClass implements Serializable if (colors != null) { buf.append(", colors=").append(StringUtil.toString(colors)); } - if (shadowColor != null) { - buf.append(", shadow="); - buf.append(StringUtil.toString(shadowColor.getComponents(null))); + if (shadowAlpha != 1.0f) { + buf.append(", shadow=").append(shadowAlpha); } else if (shadow != null) { buf.append(", shadow=").append(shadow); } diff --git a/src/java/com/threerings/cast/CompositedActionFrames.java b/src/java/com/threerings/cast/CompositedActionFrames.java index 8f85c3613..691174daa 100644 --- a/src/java/com/threerings/cast/CompositedActionFrames.java +++ b/src/java/com/threerings/cast/CompositedActionFrames.java @@ -89,8 +89,7 @@ public class CompositedActionFrames CompositedMultiFrameImage cmfi = (CompositedMultiFrameImage)_frameCache.get(_key); if (cmfi == null) { - cmfi = new CompositedMultiFrameImage( - _imgr, _sources, _action, orient); + cmfi = createFrames(orient); _frameCache.put(new CompositedFramesKey(orient), cmfi); } return cmfi; @@ -118,6 +117,14 @@ public class CompositedActionFrames throw new RuntimeException("What you talkin' about Willis?"); } + /** + * Creates our underlying multi-frame image for a particular orientation. + */ + protected CompositedMultiFrameImage createFrames (int orient) + { + return new CompositedMultiFrameImage(_imgr, _sources, _action, orient); + } + /** Used to cache composited frames for a particular action and * orientation. */ protected class CompositedFramesKey diff --git a/src/java/com/threerings/cast/CompositedShadowImage.java b/src/java/com/threerings/cast/CompositedShadowImage.java new file mode 100644 index 000000000..3c75d8c3b --- /dev/null +++ b/src/java/com/threerings/cast/CompositedShadowImage.java @@ -0,0 +1,92 @@ +// +// $Id: CompositedMultiFrameImage.java 3310 2005-01-24 23:08:21Z 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.cast; + +import java.awt.AlphaComposite; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.Rectangle; + +import com.threerings.media.image.ImageManager; +import com.threerings.media.image.Mirage; +import com.threerings.media.image.VolatileMirage; + +import com.threerings.cast.CompositedActionFrames.ComponentFrames; + +/** + * Used to composite the special shadow action frames for a particular + * orientation of a {@link CompositedActionFrames}. + */ +public class CompositedShadowImage extends CompositedMultiFrameImage +{ + public CompositedShadowImage (ImageManager imgr, ComponentFrames[] sources, + String action, int orient, float shadowAlpha) + { + super(imgr, sources, action, orient); + + // create the appropriate alpha composite for rendering the shadow + _shadowAlpha = AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, shadowAlpha); + } + + // documentation inherited from interface + public int getWidth (int index) { + return _sources[0].frames.getFrames(_orient).getWidth(index); + } + + // documentation inherited from interface + public int getHeight (int index) { + return _sources[0].frames.getFrames(_orient).getHeight(index); + } + + public int getXOrigin (int index) { + return _sources[0].frames.getXOrigin(_orient, index); + } + + 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) { + Composite ocomp = g.getComposite(); + g.setComposite(_shadowAlpha); + _images[index].paint(g, x + _images[index].getX(), + y + _images[index].getY()); + g.setComposite(ocomp); + } + + // documentation inherited from interface + public boolean hitTest (int index, int x, int y) { + return _images[index].hitTest(x + _images[index].getX(), + y + _images[index].getY()); + } + + // documentation inherited from interface TrimmedMultiFrameImage + public void getTrimmedBounds (int index, Rectangle bounds) { + bounds.setBounds(_images[index].getX(), _images[index].getY(), + _images[index].getWidth(), _images[index].getHeight()); + } + + /** The alpha value at which we render our shadow. */ + protected AlphaComposite _shadowAlpha; +}; diff --git a/src/java/com/threerings/cast/FrameProvider.java b/src/java/com/threerings/cast/FrameProvider.java index 3b31c95c9..02885de3d 100644 --- a/src/java/com/threerings/cast/FrameProvider.java +++ b/src/java/com/threerings/cast/FrameProvider.java @@ -1,5 +1,5 @@ // -// $Id: FrameProvider.java,v 1.3 2004/08/27 02:12:25 mdb Exp $ +// $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved @@ -33,5 +33,5 @@ public interface FrameProvider * the specified action does not exist for the specified component. */ public ActionFrames getFrames ( - CharacterComponent component, String action); + CharacterComponent component, String action, String type); } diff --git a/src/java/com/threerings/cast/StandardActions.java b/src/java/com/threerings/cast/StandardActions.java index 2ea916cad..322aaf867 100644 --- a/src/java/com/threerings/cast/StandardActions.java +++ b/src/java/com/threerings/cast/StandardActions.java @@ -34,9 +34,9 @@ public interface StandardActions /** The name of the standard walking action. */ public static final String WALKING = "walking"; - /** The suffix appended to an action to obtain its shadow image. */ - public static final String SHADOW_SUFFIX = "_shadow"; + /** A special action sub-type for shadow imagery. */ + public static final String SHADOW_TYPE = "shadow"; - /** The suffix appended to an action to obtain its crop image. */ - public static final String CROP_SUFFIX = "_crop"; + /** A special action sub-type for crop imagery. */ + public static final String CROP_TYPE = "crop"; } diff --git a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java index a2b2f2d2a..406bf7d02 100644 --- a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java +++ b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java @@ -1,5 +1,5 @@ // -// $Id: BundledComponentRepository.java,v 1.36 2004/10/28 17:49:01 mdb Exp $ +// $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved @@ -297,7 +297,7 @@ public class BundledComponentRepository // documentation inherited public ActionFrames getFrames ( - CharacterComponent component, String action) + CharacterComponent component, String action, String type) { // obtain the action sequence definition for this action ActionSequence actseq = (ActionSequence)_actions.get(action); @@ -308,11 +308,17 @@ public class BundledComponentRepository return null; } + // determine our image path name + String imgpath = action, dimgpath = ActionSequence.DEFAULT_SEQUENCE; + if (type != null) { + imgpath += "_" + type; + dimgpath += "_" + type; + } + String root = component.componentClass.name + "/" + component.name + "/"; - String cpath = root + action + BundleUtil.TILESET_EXTENSION; - String dpath = root + ActionSequence.DEFAULT_SEQUENCE + - BundleUtil.TILESET_EXTENSION; + String cpath = root + imgpath + BundleUtil.TILESET_EXTENSION; + String dpath = root + dimgpath + BundleUtil.TILESET_EXTENSION; // look to see if this tileset is already cached (as the // custom action or the default action) @@ -342,7 +348,7 @@ public class BundledComponentRepository // if that failed too, we're hosed if (aset == null) { Log.warning("Unable to locate tileset for action '" + - action + "' " + component + "."); + imgpath + "' " + component + "."); if (_wipeOnFailure) { _bundle.wipeBundle(false); } @@ -354,7 +360,7 @@ public class BundledComponentRepository return new TileSetFrameImage(aset, actseq); } catch (Exception e) { - Log.warning("Error loading tileset for action '" + action + + Log.warning("Error loading tileset for action '" + imgpath + "' " + component + "."); Log.logStackTrace(e); return null; diff --git a/src/java/com/threerings/cast/bundle/tools/ComponentBundlerTask.java b/src/java/com/threerings/cast/bundle/tools/ComponentBundlerTask.java index 908da2350..392436f26 100644 --- a/src/java/com/threerings/cast/bundle/tools/ComponentBundlerTask.java +++ b/src/java/com/threerings/cast/bundle/tools/ComponentBundlerTask.java @@ -619,5 +619,6 @@ public class ComponentBundlerTask extends Task /** Used to process auxilliary tilesets. */ protected static final String[] AUX_EXTS = { - StandardActions.SHADOW_SUFFIX, StandardActions.CROP_SUFFIX }; + "_" + StandardActions.SHADOW_TYPE, + "_" + StandardActions.CROP_TYPE }; } diff --git a/src/java/com/threerings/media/image/VolatileMirage.java b/src/java/com/threerings/media/image/VolatileMirage.java index b840b4bff..17ab6320e 100644 --- a/src/java/com/threerings/media/image/VolatileMirage.java +++ b/src/java/com/threerings/media/image/VolatileMirage.java @@ -1,5 +1,5 @@ // -// $Id: VolatileMirage.java,v 1.6 2004/08/27 02:12:38 mdb Exp $ +// $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved @@ -83,6 +83,26 @@ public abstract class VolatileMirage implements Mirage // TODO: note number of attempted renders for performance } + /** + * Returns the x offset into our source image, which is generally zero but + * may be non-zero for a mirage that obtains its data from a region of its + * source image. + */ + public int getX () + { + return _bounds.x; + } + + /** + * Returns the y offset into our source image, which is generally zero but + * may be non-zero for a mirage that obtains its data from a region of its + * source image. + */ + public int getY () + { + return _bounds.y; + } + // documentation inherited from interface public int getWidth () {