// // Nenya library - tools for developing networked games // Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved // https://github.com/threerings/nenya // // 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.media.animation; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Shape; import com.samskivert.util.RandomUtil; import com.samskivert.util.StringUtil; import com.threerings.media.image.Mirage; /** * An animation that displays an object exploding into chunks, fading out as they fly apart. The * animation ends when all chunks have exited the animation bounds, or when the given delay time * (if any is specified) has elapsed. */ public class ExplodeAnimation extends Animation { /** * A class that describes an explosion's attributes. */ public static class ExplodeInfo { /** The bounds within which to animate. */ public Rectangle bounds; /** The number of image chunks on each axis. */ public int xchunk, ychunk; /** The maximum chunk velocity on each axis in pixels per millisecond. */ public float xvel, yvel; /** The y-axis chunk acceleration in pixels per millisecond. */ public float yacc; /** The chunk rotational velocity in rotations per millisecond. */ public float rvel; /** The animation length in milliseconds, or -1 if the animation should continue until * all pieces are outside the bounds. */ public long delay; @Override public String toString () { return StringUtil.fieldsToString(this); } } /** * Constructs an explode animation with the chunks represented as filled rectangles of the * specified color. * * @param color the color to render the chunks in. * @param info the explode info object. * @param x the x-position of the object. * @param y the y-position of the object. * @param width the width of the object. * @param height the height of the object. */ public ExplodeAnimation (Color color, ExplodeInfo info, int x, int y, int width, int height) { super(info.bounds); _color = color; init(info, x, y, width, height); } /** * Constructs an explode animation with the chunks represented as portions of the actual * image. * * @param image the image to animate. * @param info the explode info object. * @param x the x-position of the object. * @param y the y-position of the object. * @param width the width of the object. * @param height the height of the object. */ public ExplodeAnimation (Mirage image, ExplodeInfo info, int x, int y, int width, int height) { super(info.bounds); _image = image; init(info, x, y, width, height); } /** * Initializes the animation with the attributes of the given explode info object. */ protected void init (ExplodeInfo info, int x, int y, int width, int height) { _info = info; _ox = x; _oy = y; _owid = width; _ohei = height; _info.rvel = (float)((2.0f * Math.PI) * _info.rvel); _chunkcount = (_info.xchunk * _info.ychunk); _cxpos = new int[_chunkcount]; _cypos = new int[_chunkcount]; _sxvel = new float[_chunkcount]; _syvel = new float[_chunkcount]; // determine chunk dimensions _cwid = _owid / _info.xchunk; _chei = _ohei / _info.ychunk; _hcwid = _cwid / 2; _hchei = _chei / 2; // initialize all chunks for (int ii = 0; ii < _chunkcount; ii++) { // initialize chunk position int xpos = ii % _info.xchunk; int ypos = ii / _info.xchunk; _cxpos[ii] = _ox + (xpos * _cwid); _cypos[ii] = _oy + (ypos * _chei); // initialize chunk velocity _sxvel[ii] = RandomUtil.getFloat(_info.xvel) * ((xpos < (_info.xchunk / 2)) ? -1.0f : 1.0f); _syvel[ii] = -(RandomUtil.getFloat(_info.yvel)); } // initialize the chunk rotation angle _angle = 0.0f; } @Override public void fastForward (long timeDelta) { if (_start > 0) { _start += timeDelta; _end += timeDelta; } } @Override public void tick (long timestamp) { if (_start == 0) { // initialize our starting time _start = timestamp; if (_info.delay != -1) { _end = _start + _info.delay; } } // figure out the distance the chunks have traveled long msecs = timestamp - _start; if (_info.delay != -1) { // calculate the alpha level with which to render the chunks float pctdone = msecs / (float)_info.delay; _alpha = Math.max(0.1f, Math.min(1.0f, 1.0f - pctdone)); } // move all chunks and check whether any remain to be animated int inside = 0; for (int ii = 0; ii < _chunkcount; ii++) { // determine the chunk travel distance int xtrav = (int)(_sxvel[ii] * msecs); int ytrav = (int) ((_syvel[ii] * msecs) + (0.5f * _info.yacc * (msecs * msecs))); // determine the chunk movement direction int xpos = ii % _info.xchunk; int ypos = ii / _info.xchunk; // update the chunk position _cxpos[ii] = _ox + (xpos * _cwid) + xtrav; _cypos[ii] = _oy + (ypos * _chei) + ytrav; // note whether this chunk is still within our bounds _wrect.setBounds(_cxpos[ii], _cypos[ii], _cwid, _chei); if (_bounds.intersects(_wrect)) { inside++; } } // increment the rotation angle _angle += _info.rvel; // note whether we're finished _finished = (inside == 0) || (_info.delay != -1 && timestamp >= _end); // dirty ourselves invalidate(); } @Override public void paint (Graphics2D gfx) { Shape oclip = gfx.getClip(); Composite ocomp = null; if (_info.delay != -1) { // set the alpha composite to reflect the current fade-out ocomp = gfx.getComposite(); gfx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, _alpha)); } for (int ii = 0; ii < _chunkcount; ii++) { // get the chunk position within the image int xpos = ii % _info.xchunk; int ypos = ii / _info.xchunk; // calculate image chunk offset int xoff = -(xpos * _cwid); int yoff = -(ypos * _chei); // translate the origin to center on the chunk int tx = _cxpos[ii] + _hcwid, ty = _cypos[ii] + _hchei; gfx.translate(tx, ty); // set up the desired rotation gfx.rotate(_angle); if (_image != null) { // draw the image chunk gfx.clipRect(-_hcwid, -_hchei, _cwid, _chei); _image.paint(gfx, -_hcwid + xoff, -_hchei + yoff); } else { // draw the color chunk gfx.setColor(_color); gfx.fillRect(-_hcwid, -_hchei, _cwid, _chei); } // restore the original transform and clip gfx.rotate(-_angle); gfx.translate(-tx, -ty); gfx.setClip(oclip); } if (_info.delay != -1) { // restore the original composite gfx.setComposite(ocomp); } } @Override protected void toString (StringBuilder buf) { super.toString(buf); buf.append(", ox=").append(_ox); buf.append(", oy=").append(_oy); buf.append(", cwid=").append(_cwid); buf.append(", chei=").append(_chei); buf.append(", hcwid=").append(_hcwid); buf.append(", hchei=").append(_hchei); buf.append(", chunkcount=").append(_chunkcount); buf.append(", info=").append(_info); } /** The current chunk rotation. */ protected float _angle; /** The starting x-axis velocity of each chunk. */ protected float[] _sxvel; /** The starting y-axis velocity of each chunk. */ protected float[] _syvel; /** The current x-axis position of each chunk. */ protected int[] _cxpos; /** The current y-axis position of each chunk. */ protected int[] _cypos; /** The individual chunk dimensions in pixels. */ protected int _cwid, _chei; /** The individual chunk dimensions in pixels, halved for handy use in repeated calculations. */ protected int _hcwid, _hchei; /** The total number of image chunks. */ protected int _chunkcount; /** The explode info. */ protected ExplodeInfo _info; /** The exploding object position and dimensions. */ protected int _ox, _oy, _owid, _ohei; /** The color to render the object chunks in if we're using a color. */ protected Color _color; /** The image to animate if we're using an image. */ protected Mirage _image; /** The starting animation time. */ protected long _start; /** The ending animation time. */ protected long _end; /** The percent alpha with which to render the chunks. */ protected float _alpha; /** A reusable working rectangle. */ protected Rectangle _wrect = new Rectangle(); }