diff --git a/src/java/com/threerings/cast/ActionFrames.java b/src/java/com/threerings/cast/ActionFrames.java index 3e43560da..a03736e20 100644 --- a/src/java/com/threerings/cast/ActionFrames.java +++ b/src/java/com/threerings/cast/ActionFrames.java @@ -1,5 +1,5 @@ // -// $Id: ActionFrames.java,v 1.3 2002/05/15 23:54:04 mdb Exp $ +// $Id: ActionFrames.java,v 1.4 2002/06/19 23:31:57 mdb Exp $ package com.threerings.cast; @@ -21,7 +21,23 @@ public interface ActionFrames * Returns the multi-frame image that comprises the frames for the * specified orientation. */ - public MultiFrameImage getFrames (int orient); + public TrimmedMultiFrameImage getFrames (int orient); + + /** + * Returns the x offset from the upper left of the image to the + * "origin" for this character frame. A sprite with location (x, y) + * will be rendered such that its origin is coincident with that + * location. + */ + public int getXOrigin (int orient, int frameIdx); + + /** + * Returns the y offset from the upper left of the image to the + * "origin" for this character frame. A sprite with location (x, y) + * will be rendered such that its origin is coincident with that + * location. + */ + public int getYOrigin (int orient, int frameIdx); /** * Creates a clone of these action frames which will have the supplied diff --git a/src/java/com/threerings/cast/CharacterManager.java b/src/java/com/threerings/cast/CharacterManager.java index 8e18d9be8..9c1f42eff 100644 --- a/src/java/com/threerings/cast/CharacterManager.java +++ b/src/java/com/threerings/cast/CharacterManager.java @@ -1,5 +1,5 @@ // -// $Id: CharacterManager.java,v 1.21 2002/06/14 21:15:31 shaper Exp $ +// $Id: CharacterManager.java,v 1.22 2002/06/19 23:31:57 mdb Exp $ package com.threerings.cast; @@ -199,6 +199,9 @@ public class CharacterManager int ccount = cids.length; Colorization[][] zations = descrip.getColorizations(); + Log.debug("Compositing action [action=" + action + + ", descrip=" + descrip + "]."); + // obtain the necessary components ColorizedComponent[] components = new ColorizedComponent[ccount]; for (int i = 0; i < ccount; i++) { diff --git a/src/java/com/threerings/cast/CharacterSprite.java b/src/java/com/threerings/cast/CharacterSprite.java index c6580d115..0f5414112 100644 --- a/src/java/com/threerings/cast/CharacterSprite.java +++ b/src/java/com/threerings/cast/CharacterSprite.java @@ -1,8 +1,10 @@ // -// $Id: CharacterSprite.java,v 1.33 2002/06/12 07:00:49 ray Exp $ +// $Id: CharacterSprite.java,v 1.34 2002/06/19 23:31:57 mdb Exp $ package com.threerings.cast; +import java.awt.Graphics2D; + import com.threerings.media.sprite.ImageSprite; import com.threerings.media.util.Path; @@ -113,7 +115,6 @@ public class CharacterSprite extends ImageSprite _aframes = _charmgr.getActionFrames(_descrip, action); // update the sprite render attributes - setOrigin(actseq.origin.x, actseq.origin.y); setFrameRate(actseq.framesPerSecond); setFrames(_aframes.getFrames(_orient)); @@ -134,30 +135,24 @@ public class CharacterSprite extends ImageSprite } } - /** - * Sets the origin coordinates representing the "base" of the - * sprite, which in most cases corresponds to the center of the - * bottom of the sprite image. - */ - public void setOrigin (int x, int y) - { - _xorigin = x; - _yorigin = y; +// // documentation inherited +// public void paint (Graphics2D gfx) +// { +// // DEBUG: draw our origin +// gfx.setColor(java.awt.Color.white); +// gfx.drawOval(_x - 3, _y - 3, 6, 6); - updateRenderOffset(); - updateRenderOrigin(); - } +// super.paint(gfx); +// } // documentation inherited - protected void updateRenderOffset () + protected void accomodateFrame (int width, int height) { - super.updateRenderOffset(); + super.accomodateFrame(width, height); - if (_frames != null) { - // our location is based on the character origin coordinates - _rxoff = -_xorigin; - _ryoff = -_yorigin; - } + // we need to update the render offset for this frame + _rxoff = -_aframes.getXOrigin(_orient, _frameIdx); + _ryoff = -_aframes.getYOrigin(_orient, _frameIdx); } // documentation inherited @@ -221,7 +216,4 @@ public class CharacterSprite extends ImageSprite /** The animation frames for the active action sequence in each * orientation. */ protected ActionFrames _aframes; - - /** The origin of the sprite. */ - protected int _xorigin, _yorigin; } diff --git a/src/java/com/threerings/cast/CompositedActionFrames.java b/src/java/com/threerings/cast/CompositedActionFrames.java index f2fe0784f..ff584ab17 100644 --- a/src/java/com/threerings/cast/CompositedActionFrames.java +++ b/src/java/com/threerings/cast/CompositedActionFrames.java @@ -1,10 +1,13 @@ // -// $Id: CompositedActionFrames.java,v 1.4 2002/05/15 23:54:04 mdb Exp $ +// $Id: CompositedActionFrames.java,v 1.5 2002/06/19 23:31:57 mdb Exp $ package com.threerings.cast; import java.awt.Graphics; import java.awt.Image; +import java.awt.Rectangle; + +import com.samskivert.util.StringUtil; import com.threerings.media.sprite.MultiFrameImage; import com.threerings.media.util.Colorization; @@ -27,50 +30,70 @@ public class CompositedActionFrames public CompositedActionFrames (ActionFrames[] sources) { _sources = sources; - // the sources must all have the same frame count, so we just use - // the count from the first one + // the sources must all have the same frame count, and each + // orientation must also have the same frame count, so we just use + // the count from the first source and first orientation _frameCount = _sources[0].getFrames(NORTH).getFrameCount(); _images = new Image[DIRECTION_COUNT][_frameCount]; + _bounds = new Rectangle[DIRECTION_COUNT][_frameCount]; } // documentation inherited from interface - public MultiFrameImage getFrames (final int orient) + public TrimmedMultiFrameImage getFrames (final int orient) { - return new MultiFrameImage() { + return new TrimmedMultiFrameImage() { // documentation inherited - public int getFrameCount () - { - return _proto.getFrameCount(); + public int getFrameCount () { + return _frameCount; } // documentation inherited from interface - public int getWidth (int index) - { - return _proto.getWidth(index); + public int getWidth (int index) { + // composite the frame if necessary + if (_bounds[orient][index] == null) { + getFrame(orient, index); + } + return _bounds[orient][index].width; } // documentation inherited from interface - public int getHeight (int index) - { - return _proto.getHeight(index); + public int getHeight (int index) { + // composite the frame if necessary + if (_bounds[orient][index] == null) { + getFrame(orient, index); + } + return _bounds[orient][index].height; } // documentation inherited from interface - public void paintFrame (Graphics g, int index, int x, int y) - { + public void paintFrame (Graphics g, int index, int x, int y) { g.drawImage(getFrame(orient, index), x, y, null); } // documentation inherited from interface - public boolean hitTest (int index, int x, int y) - { + public boolean hitTest (int index, int x, int y) { return ImageUtil.hitTest(getFrame(orient, index), x, y); } - protected MultiFrameImage _proto = _sources[0].getFrames(orient); + // documentation inherited from interface + public void getTrimmedBounds (int index, Rectangle bounds) { + bounds.setBounds(0, 0, getWidth(index), getHeight(index)); + } }; } + // documentation inherited from interface + public int getXOrigin (int orient, int frameIdx) + { + return _bounds[orient][frameIdx].x; + } + + // documentation inherited from interface + public int getYOrigin (int orient, int frameIdx) + { + return _bounds[orient][frameIdx].y; + } + // documentation inherited from interface public ActionFrames cloneColorized (Colorization[] zations) { @@ -80,15 +103,19 @@ public class CompositedActionFrames // documentation inherited protected Image getFrame (int orient, int index) { - // create the frame if we don't already have it + // create the arrays for this orientation if we haven't yet if (_images[orient] == null) { _images[orient] = new Image[_frameCount]; + _bounds[orient] = new Rectangle[_frameCount]; } + + // create the frame if we don't already have it if (_images[orient][index] == null) { // Log.info("Compositing [orient=" + orient + // ", index=" + index + "]."); _images[orient][index] = compositeFrames(orient, index); } + return _images[orient][index]; } @@ -101,22 +128,45 @@ public class CompositedActionFrames */ protected Image compositeFrames (int orient, int index) { - Image dest = null; - Graphics g = null; + int scount = _sources.length; // long start = System.currentTimeMillis(); - for (int ii = 0; ii < _sources.length; ii++) { - MultiFrameImage source = _sources[ii].getFrames(orient); - // create the image now that we know how big it should be - if (dest == null) { - dest = ImageUtil.createImage(source.getWidth(index), - source.getHeight(index)); - g = dest.getGraphics(); +// // DEBUG +// int width = 0, height = 0; + + // 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); + 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); } - // render the particular action from this component into the - // target image - source.paintFrame(g, index, 0, 0); +// // DEBUG +// for (int ff = 0; ff < _frameCount; ff++) { +// width = Math.max(width, source.getWidth(ff)); +// height = Math.max(height, source.getHeight(ff)); +// } + } + + // create the image now that we know how big it should be + Image dest = ImageUtil.createImage(bounds.width, bounds.height); + Graphics g = dest.getGraphics(); + + // now render each of the components into a composited frame + for (int ii = 0; ii < scount; ii++) { + TrimmedMultiFrameImage source = _sources[ii].getFrames(orient); + source.getTrimmedBounds(index, tbounds); + + // render this frame for this particular action for this + // component into the target image + source.paintFrame(g, index, -bounds.x, -bounds.y); } // clean up after ourselves @@ -124,6 +174,16 @@ public class CompositedActionFrames g.dispose(); } +// Log.info("Composited [orient=" + orient + ", index=" + index + +// ", tbounds=" + StringUtil.toString(bounds) + +// ", 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); + +// Log.info("New origin [x=" + bounds.x + ", y=" + bounds.y + "]."); + // long now = System.currentTimeMillis(); // Log.info("Composited " + sources.length + " frames in " + // (now-start) + " millis."); @@ -139,4 +199,7 @@ public class CompositedActionFrames /** The frame images. */ protected Image[][] _images; + + /** Used to track our trimmed frame bounds. */ + protected Rectangle[][] _bounds; } diff --git a/src/java/com/threerings/cast/TrimmedMultiFrameImage.java b/src/java/com/threerings/cast/TrimmedMultiFrameImage.java new file mode 100644 index 000000000..1d33617d6 --- /dev/null +++ b/src/java/com/threerings/cast/TrimmedMultiFrameImage.java @@ -0,0 +1,23 @@ +// +// $Id: TrimmedMultiFrameImage.java,v 1.1 2002/06/19 23:31:57 mdb Exp $ + +package com.threerings.cast; + +import java.awt.Rectangle; + +import com.threerings.media.sprite.MultiFrameImage; + +/** + * Used to generate more memory efficient composited images in + * circumstances where we have trimmed underlying component images. + */ +public interface TrimmedMultiFrameImage extends MultiFrameImage +{ + /** + * Fills in the minimum bounding rectangle for this image that + * contains all non-transparent pixels. If this information is + * unavailable, the bounds of the entire image may be returned in + * exchange for improved performance. + */ + public void getTrimmedBounds (int index, Rectangle bounds); +} diff --git a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java index cf8aad89b..4d6ba2d6b 100644 --- a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java +++ b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java @@ -1,10 +1,11 @@ // -// $Id: BundledComponentRepository.java,v 1.13 2002/06/19 08:33:14 mdb Exp $ +// $Id: BundledComponentRepository.java,v 1.14 2002/06/19 23:31:57 mdb Exp $ package com.threerings.cast.bundle; import java.awt.Graphics; import java.awt.Image; +import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; @@ -36,16 +37,19 @@ import com.threerings.media.tile.ImageProvider; import com.threerings.media.tile.NoSuchTileException; import com.threerings.media.tile.Tile; import com.threerings.media.tile.TileSet; +import com.threerings.media.tile.TrimmedTile; import com.threerings.util.DirectionCodes; import com.threerings.cast.ActionFrames; +import com.threerings.cast.ActionSequence; import com.threerings.cast.CharacterComponent; import com.threerings.cast.ComponentClass; import com.threerings.cast.ComponentRepository; import com.threerings.cast.FrameProvider; import com.threerings.cast.Log; import com.threerings.cast.NoSuchComponentException; +import com.threerings.cast.TrimmedMultiFrameImage; /** * A component repository implementation that obtains information from @@ -262,10 +266,20 @@ public class BundledComponentRepository String path = component.componentClass.name + "/" + component.name + "/" + action + BundleUtil.TILESET_EXTENSION; + + // obtain the action sequence definition for this action + ActionSequence actseq = (ActionSequence)_actions.get(action); + if (actseq == null) { + Log.warning("Missing action sequence definition " + + "[action=" + action + + ", component=" + component + "]."); + return null; + } + try { TileSet aset = (TileSet)BundleUtil.loadObject(_bundle, path); aset.setImageProvider(this); - return new TileSetFrameImage(aset); + return new TileSetFrameImage(aset, actseq); } catch (Exception e) { Log.warning("Error loading tileset for action " + @@ -289,15 +303,16 @@ public class BundledComponentRepository * Constructs a tileset frame image with the specified tileset and * for the specified orientation. */ - public TileSetFrameImage (TileSet set) + public TileSetFrameImage (TileSet set, ActionSequence actseq) { _set = set; + _actseq = actseq; } // documentation inherited from interface - public MultiFrameImage getFrames (final int orient) + public TrimmedMultiFrameImage getFrames (final int orient) { - return new MultiFrameImage() { + return new TrimmedMultiFrameImage() { // documentation inherited public int getFrameCount () { @@ -333,13 +348,39 @@ public class BundledComponentRepository Tile tile = getTile(orient, index); return (tile != null) ? tile.hitTest(x, y) : false; } + + // documentation inherited from interface + public void getTrimmedBounds (int index, Rectangle bounds) + { + Tile tile = getTile(orient, index); + if (tile instanceof TrimmedTile) { + TrimmedTile ttile = (TrimmedTile)tile; + bounds.setBounds(ttile.getTrimmedBounds()); + } else { + bounds.setBounds( + 0, 0, tile.getWidth(), tile.getHeight()); + } + } }; } + // documentation inherited from interface + public int getXOrigin (int orient, int index) + { + return _actseq.origin.x; + } + + // documentation inherited from interface + public int getYOrigin (int orient, int index) + { + return _actseq.origin.y; + } + // documentation inherited from interface public ActionFrames cloneColorized (Colorization[] zations) { - return new TileSetFrameImage(_set.cloneColorized(zations)); + return new TileSetFrameImage( + _set.cloneColorized(zations), _actseq); } /** @@ -360,6 +401,9 @@ public class BundledComponentRepository /** The tileset from which we obtain our frame images. */ protected TileSet _set; + + /** The action sequence for which we're providing frame images. */ + protected ActionSequence _actseq; } /** We use the image manager to decode and cache images. */