diff --git a/src/java/com/threerings/cast/CharacterManager.java b/src/java/com/threerings/cast/CharacterManager.java index 1b967bb1d..1ecd78e50 100644 --- a/src/java/com/threerings/cast/CharacterManager.java +++ b/src/java/com/threerings/cast/CharacterManager.java @@ -256,7 +256,10 @@ public class CharacterManager // this will be used to construct any shadow layers HashMap shadows = null; - + + // maps components by class name for masks + HashMap ccomps = new HashMap(); + // create colorized versions of all of the source action frames ArrayList sources = new ArrayList(ccount); for (int ii = 0; ii < ccount; ii++) { @@ -264,7 +267,8 @@ public class CharacterManager sources.add(cframes); CharacterComponent ccomp = (cframes.ccomp = _crepo.getComponent(cids[ii])); - + ccomps.put(ccomp.componentClass.name, ccomp); + // load up the main component images ActionFrames source = ccomp.getFrames(action, null); if (source == null) { @@ -275,7 +279,7 @@ public class CharacterManager } cframes.frames = (zations == null || zations[ii] == null) ? source : source.cloneColorized(zations[ii]); - + // if this component has a shadow, make a note of it if (ccomp.componentClass.isShadowed()) { if (shadows == null) { @@ -291,6 +295,17 @@ public class CharacterManager } } + // add any necessary masks + for (int ii = 0; ii < ccount; 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); + } + } + // now create any necessary shadow layers if (shadows != null) { Iterator iter = shadows.entrySet().iterator(); @@ -312,6 +327,26 @@ public class CharacterManager 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) { diff --git a/src/java/com/threerings/cast/ComponentClass.java b/src/java/com/threerings/cast/ComponentClass.java index d951a7478..2b2eb21a1 100644 --- a/src/java/com/threerings/cast/ComponentClass.java +++ b/src/java/com/threerings/cast/ComponentClass.java @@ -117,6 +117,10 @@ public class ComponentClass implements Serializable * be null if a system does not use recolorable components. */ public String[] colors; + /** The class name of the layer from which this component class obtains a + * mask to limit rendering to certain areas. */ + public String mask; + /** Indicates the class name of the shadow layer to which this component * class contributes a shadow. */ public String shadow; @@ -217,6 +221,9 @@ public class ComponentClass implements Serializable if (colors != null) { buf.append(", colors=").append(StringUtil.toString(colors)); } + if (mask != null) { + buf.append(", mask=").append(mask); + } if (shadowAlpha != 1.0f) { buf.append(", shadow=").append(shadowAlpha); } else if (shadow != null) { diff --git a/src/java/com/threerings/cast/CompositedActionFrames.java b/src/java/com/threerings/cast/CompositedActionFrames.java index 3d219efaa..17bb23f22 100644 --- a/src/java/com/threerings/cast/CompositedActionFrames.java +++ b/src/java/com/threerings/cast/CompositedActionFrames.java @@ -46,6 +46,15 @@ public class CompositedActionFrames public ActionFrames frames; + public ComponentFrames () { + } + + public ComponentFrames ( + CharacterComponent ccomp, ActionFrames frames) { + this.ccomp = ccomp; + this.frames = frames; + } + public String toString () { return ccomp + ":" + frames; } diff --git a/src/java/com/threerings/cast/CompositedMaskedImage.java b/src/java/com/threerings/cast/CompositedMaskedImage.java new file mode 100644 index 000000000..21d31e895 --- /dev/null +++ b/src/java/com/threerings/cast/CompositedMaskedImage.java @@ -0,0 +1,131 @@ +// +// $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.Color; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.Transparency; + +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 action frames with mask frames. + */ +public class CompositedMaskedImage extends CompositedMultiFrameImage +{ + public CompositedMaskedImage ( + ImageManager imgr, ComponentFrames[] sources, String action, + int orient) + { + super(imgr, sources, action, orient); + } + + // 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) { + _images[index].paint(g, x + _images[index].getX(), + y + _images[index].getY()); + } + + // 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()); + } + + // documentation inherited + protected CompositedMirage createCompositedMirage (int index) + { + return new MaskedMirage(index); + } + + /** + * Combines the image in the first source with the mask in the second. */ + protected class MaskedMirage extends CompositedMirage + { + public MaskedMirage (int index) + { + super(index); + } + + // documentation inherited + protected Rectangle combineBounds (Rectangle bounds, Rectangle tbounds) + { + if (bounds.width == 0 && bounds.height == 0) { + bounds.setBounds(tbounds); + } else { + bounds = bounds.intersection(tbounds); + } + 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); + source.paintFrame(g, _index, -_bounds.x, -_bounds.y); + g.setComposite(AlphaComposite.DstIn); + mask.paintFrame(g, _index, -_bounds.x, -_bounds.y); + + } finally { + // clean up after ourselves + if (g != null) { + g.dispose(); + } + } + } + } +}; diff --git a/src/java/com/threerings/cast/CompositedMultiFrameImage.java b/src/java/com/threerings/cast/CompositedMultiFrameImage.java index d8271b298..b30bd19e0 100644 --- a/src/java/com/threerings/cast/CompositedMultiFrameImage.java +++ b/src/java/com/threerings/cast/CompositedMultiFrameImage.java @@ -56,10 +56,10 @@ public class CompositedMultiFrameImage int fcount = sources[0].frames.getFrames(orient).getFrameCount(); _images = new CompositedMirage[fcount]; for (int ii = 0; ii < fcount; ii++) { - _images[ii] = new CompositedMirage(ii); + _images[ii] = createCompositedMirage(ii); } } - + // documentation inherited public int getFrameCount () { return _images.length; @@ -110,6 +110,14 @@ public class CompositedMultiFrameImage return size; } + /** + * Creates a composited image for the specified frame. + */ + protected CompositedMirage createCompositedMirage (int index) + { + return new CompositedMirage(index); + } + // documentation inherited protected Mirage getFrame (int orient, int index) { @@ -153,13 +161,7 @@ public class CompositedMultiFrameImage 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) { - _bounds.setBounds(tbounds); - } else { - _bounds.add(tbounds); - } + _bounds = combineBounds(_bounds, tbounds); } // compute our new origin @@ -172,7 +174,7 @@ public class CompositedMultiFrameImage // render our volatile image for the first time createVolatileImage(); } - + public int getXOrigin () { return _origin.x; @@ -194,6 +196,20 @@ public class CompositedMultiFrameImage _action, _orient)); } + /** + * Combines the working bounds with a new set of bounds. + */ + protected Rectangle combineBounds (Rectangle bounds, Rectangle tbounds) + { + // the first one defines our initial bounds + if (bounds.width == 0 && bounds.height == 0) { + bounds.setBounds(tbounds); + } else { + bounds.add(tbounds); + } + return bounds; + } + // documentation inherited protected int getTransparency () {