Behold, Nenya, Ring of Water and repository for our media and animation related

goodies, both Java 2D and LWJGL/JME 3D.


git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@1 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Michael Bayne
2006-06-23 18:07:28 +00:00
commit c2117ee86d
570 changed files with 61913 additions and 0 deletions
@@ -0,0 +1,169 @@
//
// $Id: Animation.java 3822 2006-01-27 03:28:04Z ray $
//
// 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.media.animation;
import java.awt.Rectangle;
import com.samskivert.util.ObserverList;
import com.threerings.media.AbstractMedia;
/**
* The animation class is an abstract class that should be extended to
* provide animation functionality. It is generally used in conjunction
* with an {@link AnimationManager}.
*/
public abstract class Animation extends AbstractMedia
{
/**
* Constructs an animation.
*
* @param bounds the animation rendering bounds.
*/
public Animation (Rectangle bounds)
{
super(bounds);
}
/**
* Returns true if the animation has finished all of its business,
* false if not.
*/
public boolean isFinished ()
{
return _finished;
}
/**
* If this animation has run to completion, it can be reset to prepare
* it for another go.
*/
public void reset ()
{
_finished = false;
}
// documentation inherited
public void setLocation (int x, int y)
{
if (_bounds.x == x && _bounds.y == y) {
return; // no-op
}
// start with our current bounds
Rectangle dirty = new Rectangle(_bounds);
// move ourselves
super.setLocation(x, y);
// grow the dirty rectangle to incorporate our new bounds and pass
// the dirty region to our region manager
if (_mgr != null) {
// if our new bounds intersect our old bounds, grow a single
// dirty rectangle to incorporate them both
if (_bounds.intersects(dirty)) {
dirty.add(_bounds);
} else {
// otherwise invalidate our new bounds separately
_mgr.getRegionManager().invalidateRegion(_bounds);
}
_mgr.getRegionManager().addDirtyRegion(dirty);
}
}
// documentation inherited
protected void willStart (long tickStamp)
{
super.willStart(tickStamp);
queueNotification(new AnimStartedOp(this, tickStamp));
}
/**
* Called when the animation is finished and the animation manager is
* about to remove it from service.
*/
protected void willFinish (long tickStamp)
{
queueNotification(new AnimCompletedOp(this, tickStamp));
}
/**
* Called when the animation is finished and the animation manager has
* removed it from service.
*/
protected void didFinish (long tickStamp)
{
}
/**
* Adds an animation observer to this animation's list of observers.
*/
public void addAnimationObserver (AnimationObserver obs)
{
addObserver(obs);
}
/**
* Removes an animation observer from this animation's list of observers.
*/
public void removeAnimationObserver (AnimationObserver obs)
{
removeObserver(obs);
}
/** Whether the animation is finished. */
protected boolean _finished = false;
/** Used to dispatch {@link AnimationObserver#animationStarted}. */
protected static class AnimStartedOp implements ObserverList.ObserverOp
{
public AnimStartedOp (Animation anim, long when) {
_anim = anim;
_when = when;
}
public boolean apply (Object observer) {
((AnimationObserver)observer).animationStarted(_anim, _when);
return true;
}
protected Animation _anim;
protected long _when;
}
/** Used to dispatch {@link AnimationObserver#animationCompleted}. */
protected static class AnimCompletedOp implements ObserverList.ObserverOp
{
public AnimCompletedOp (Animation anim, long when) {
_anim = anim;
_when = when;
}
public boolean apply (Object observer) {
((AnimationObserver)observer).animationCompleted(_anim, _when);
return true;
}
protected Animation _anim;
protected long _when;
}
}
@@ -0,0 +1,38 @@
//
// $Id: AnimationAdapter.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.media.animation;
/**
* An adapter class for {@link AnimationObserver}.
*/
public class AnimationAdapter implements AnimationObserver
{
// documentation inherited from interface
public void animationStarted (Animation anim, long when)
{
}
// documentation inherited from interface
public void animationCompleted (Animation anim, long when)
{
}
}
@@ -0,0 +1,68 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 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.media.animation;
import java.awt.Rectangle;
import java.util.ArrayList;
import com.samskivert.swing.util.SwingUtil;
import com.threerings.media.Log;
/**
* A utility class for positioning animations such that they don't overlap,
* as best as possible.
*/
public class AnimationArranger
{
/**
* Position the specified animation so that it is not overlapping
* any still-running animations previously passed to this method.
*/
public void positionAvoidAnimation (Animation anim, Rectangle viewBounds)
{
Rectangle abounds = new Rectangle(anim.getBounds());
ArrayList avoidables = (ArrayList) _avoidAnims.clone();
// if we are able to place it somewhere, do so
if (SwingUtil.positionRect(abounds, viewBounds, avoidables)) {
anim.setLocation(abounds.x, abounds.y);
}
// add the animation to the list of avoidables
_avoidAnims.add(anim);
// keep an eye on it so that we can remove it when it's finished
anim.addAnimationObserver(_avoidAnimObs);
}
/** The animations that other animations may wish to avoid. */
protected ArrayList _avoidAnims = new ArrayList();
/** Automatically removes avoid animations when they're done. */
protected AnimationAdapter _avoidAnimObs = new AnimationAdapter() {
public void animationCompleted (Animation anim, long when) {
if (!_avoidAnims.remove(anim)) {
Log.warning("Couldn't remove avoid animation?! " + anim + ".");
}
}
};
}
@@ -0,0 +1,260 @@
//
// $Id: AnimationFrameSequencer.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.media.animation;
import com.samskivert.util.ObserverList;
import com.threerings.media.util.FrameSequencer;
import com.threerings.media.util.MultiFrameImage;
/**
* Used to control animation timing when displaying an animation.
*/
public interface AnimationFrameSequencer extends FrameSequencer
{
/**
* Called after init to set the animation.
*/
public void setAnimation (Animation anim);
/**
* A sequencer that can step through a series of frames in any order
* and speed and notify (via {@link SequencedAnimationObserver}) when
* specific frames are reached.
*/
public static class MultiFunction implements AnimationFrameSequencer
{
/**
* Creates the simplest multifunction frame sequencer.
*
* @param sequence the ordering to display the frames.
* @param msPerFrame the number of ms to display each frame.
* @param loop if false, the sequencer will report the end of
* the animation after progressing through all of the frames once,
* otherwise it will loop indefinitely.
*/
public MultiFunction (int[] sequence, long msPerFrame, boolean loop)
{
_length = sequence.length;
_sequence = sequence;
setDelay(msPerFrame);
_marks = new boolean[_length];
_loop = loop;
}
/**
* Creates a fully-specified multifunction frame sequencer.
*
* @param sequence the ordering to display the frames.
* @param msPerFrame the number of ms to display each frame.
* @param marks if true for a frame, a FrameReachedEvent will
* be generated when that frame is reached.
* @param loop if false, the sequencer will report the end of
* the animation after progressing through all of the frames once,
* otherwise it will loop indefinitely.
*/
public MultiFunction (
int[] sequence, long[] msPerFrame, boolean[] marks, boolean loop)
{
_length = sequence.length;
if ((_length != msPerFrame.length) || (_length != marks.length)) {
throw new IllegalArgumentException(
"All MultiFunction FrameSequencer arrays must be " +
"the same length.");
}
_sequence = sequence;
_delays = msPerFrame;
_marks = marks;
_loop = loop;
}
/**
* Set the delay to use.
*/
public void setDelay (long msPerFrame)
{
_delays = null;
_delay = msPerFrame;
}
/**
* Set the length of the sequence we are to use, or 0 means set
* it to the length of the sequence array that we were constructed with.
*/
public void setLength (int len)
{
_length = (len == 0) ? _sequence.length : len;
if (_curIdx >= _length) {
_curIdx = 0;
}
}
/**
* Do we reset the animation on init? Default is true.
*/
public void setResetOnInit (boolean resets)
{
_resets = resets;
}
// documentation inherited from interface
public void init (MultiFrameImage source)
{
int framecount = source.getFrameCount();
// let's make sure our frames are valid
for (int ii=0; ii < _length; ii++) {
if (_sequence[ii] >= framecount) {
throw new IllegalArgumentException(
"MultiFunction FrameSequencer was initialized " +
"with a MultiFrameImage with not enough frames " +
"to match the sequence it was constructed with.");
}
}
if (_resets) {
_lastStamp = 0L;
_curIdx = 0;
}
}
// documentation inherited from interface
public void setAnimation (Animation anim)
{
_animation = anim;
}
// documentation inherited from interface
public int tick (long tickStamp)
{
// obtain our starting timestamp if we don't already have one
if (_lastStamp == 0L) {
_lastStamp = tickStamp;
// we might need to notify on the first frame
checkNotify(tickStamp);
}
// we may have rushed through more than one frame since the last
// tick, but we want to always notify even if we never displayed
long curdelay = getDelay(_curIdx);
while (tickStamp >= (_lastStamp + curdelay)) {
_lastStamp += curdelay;
_curIdx++;
if (_curIdx == _length) {
if (_loop) {
_curIdx = 0;
} else {
return -1;
}
}
checkNotify(tickStamp);
// get the delay for checking the next frame
curdelay = getDelay(_curIdx);
}
// return the right frame
return _sequence[_curIdx];
}
// documentation inherited from interface
public void fastForward (long timeDelta)
{
// this method should be called "unpause"
_lastStamp += timeDelta;
}
/**
* Get the delay to use for the specified frame.
*/
protected final long getDelay (int index)
{
return (_delays == null) ? _delay : _delays[index];
}
/**
* Check to see if we need to notify that we've reached a marked frame.
*/
protected void checkNotify (long tickStamp)
{
if (_marks[_curIdx]) {
_animation.queueNotification(
new FrameReachedOp(_animation, tickStamp,
_sequence[_curIdx], _curIdx));
}
}
/** The current sequence index. */
protected int _curIdx = 0;
/** The length of our animation sequence. */
protected int _length;
/** Do we reset on init? */
protected boolean _resets = true;
/** The sequence of frames to display. */
protected int[] _sequence;
/** The corresponding delay for each frame, in ms. */
protected long[] _delays;
/** Or a single delay for all frames. */
protected long _delay;
/** Whether to send a FrameReachedEvent on a sequence index. */
protected boolean[] _marks;
/** Does the animation loop? */
protected boolean _loop;
/** The time at which we were last ticked. */
protected long _lastStamp;
/** The animation that we're sequencing for. */
protected Animation _animation;
/** Used to dispatch {@link SequencedAnimationObserver#frameReached}. */
protected static class FrameReachedOp implements ObserverList.ObserverOp
{
public FrameReachedOp (Animation anim, long when,
int frameIdx, int frameSeq) {
_anim = anim;
_when = when;
_frameIdx = frameIdx;
_frameSeq = frameSeq;
}
public boolean apply (Object observer) {
if (observer instanceof SequencedAnimationObserver) {
((SequencedAnimationObserver)observer).frameReached(
_anim, _when, _frameIdx, _frameSeq);
}
return true;
}
protected Animation _anim;
protected long _when;
protected int _frameIdx, _frameSeq;
}
}
}
@@ -0,0 +1,81 @@
//
// $Id: AnimationManager.java 3347 2005-02-14 03:00:58Z 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.media.animation;
import com.threerings.media.AbstractMediaManager;
import com.threerings.media.MediaPanel;
/**
* Manages a collection of animations, ticking them when the animation
* manager itself is ticked and generating events when animations finish
* and suchlike.
*/
public class AnimationManager extends AbstractMediaManager
{
/**
* Construct and initialize the animation manager which readies itself
* to manage animations.
*/
public AnimationManager (MediaPanel panel)
{
super(panel);
}
/**
* Registers the given {@link Animation} with the animation manager
* for ticking and painting.
*/
public void registerAnimation (Animation anim)
{
insertMedia(anim);
}
/**
* Un-registers the given {@link Animation} from the animation
* manager. The bounds of the animation will automatically be
* invalidated so that they are properly rerendered in the absence of
* the animation.
*/
public void unregisterAnimation (Animation anim)
{
removeMedia(anim);
}
// documentation inherited
protected void tickAllMedia (long tickStamp)
{
super.tickAllMedia(tickStamp);
for (int ii = _media.size() - 1; ii >= 0; ii--) {
Animation anim = (Animation)_media.get(ii);
if (!anim.isFinished()) {
continue;
}
// as the anim is finished, remove it and notify observers
anim.willFinish(tickStamp);
unregisterAnimation(anim);
anim.didFinish(tickStamp);
// Log.info("Removed finished animation " + anim + ".");
}
}
}
@@ -0,0 +1,39 @@
//
// $Id: AnimationObserver.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.media.animation;
/**
* An interface to be implemented by classes that would like to observe an
* {@link Animation} and be notified of interesting events relating to it.
*/
public interface AnimationObserver
{
/**
* Called the first time this animation is ticked.
*/
public void animationStarted (Animation anim, long when);
/**
* Called when the observed animation has completed.
*/
public void animationCompleted (Animation anim, long when);
}
@@ -0,0 +1,295 @@
//
// $Id: AnimationSequencer.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.media.animation;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import com.samskivert.util.StringUtil;
import com.threerings.media.Log;
/**
* An animation that provides facilities for adding a sequence of
* animations that are fired after a fixed time interval has elapsed or
* after previous animations in the sequence have completed. Facilities
* are also provided for running code upon the completion of animations in
* the sequence.
*/
public class AnimationSequencer extends Animation
{
/**
* Constructs an animation sequencer with the expectation that
* animations will be added via subsequent calls to {@link
* #addAnimation}.
*
* @param animmgr the animation manager to which to add our animations
* when they are ready to start.
*/
public AnimationSequencer (AnimationManager animmgr)
{
super(new Rectangle());
_animmgr = animmgr;
}
/**
* Adds the supplied animation to the sequence with the given
* parameters. Note that care should be taken if this is called after
* the animation sequence has begun firing animations. Do not add new
* animations after the final animation in the sequence has been
* started or you run the risk of attempting to add an animation to
* the sequence after it thinks that it has finished (in which case
* this method will fail).
*
* @param anim the animation to be sequenced, or null if the
* completion action should be run immediately when this "animation"
* is ready to fired.
* @param delta the number of milliseconds following the
* <em>start</em> of the previous animation in the queue that this
* animation should be started; 0 if it should be started
* simultaneously with its predecessor int the queue; -1 if it should
* be started when its predecessor has completed.
* @param completionAction a runnable to be executed when this
* animation completes.
*/
public void addAnimation (
Animation anim, long delta, Runnable completionAction)
{
// sanity check
if (_finished) {
throw new IllegalStateException(
"Animation added to finished sequencer");
}
// if this guy is triggering on a previous animation, grab that
// good fellow here
AnimRecord trigger = null;
if (delta == -1) {
if (_queued.size() > 0) {
// if there are queued animations we want the most
// recently queued animation
trigger = (AnimRecord)_queued.get(_queued.size()-1);
} else if (_running.size() > 0) {
// otherwise, if there are running animations, we want the
// last one in that list
trigger = (AnimRecord)_running.get(_running.size()-1);
}
// otherwise we have no trigger, we'll just start ASAP
}
AnimRecord arec = new AnimRecord(
anim, delta, trigger, completionAction);
// Log.info("Queued " + arec + ".");
_queued.add(arec);
}
/**
* Clears out the animations being managed by this sequencer.
*/
public void clear ()
{
_queued.clear();
_lastStamp = 0;
}
// documentation inherited
public void tick (long tickStamp)
{
if (_lastStamp == 0) {
_lastStamp = tickStamp;
}
// add all animations whose time has come
while (_queued.size() > 0) {
AnimRecord arec = (AnimRecord)_queued.get(0);
if (!arec.readyToFire(tickStamp, _lastStamp)) {
// if it's not time to add this animation, all subsequent
// animations must surely wait as well
break;
}
// remove it from queued and put it on the running list
_queued.remove(0);
_running.add(arec);
// note that we've advanced to the next animation
_lastStamp = tickStamp;
// fire in the hole!
arec.fire(tickStamp);
}
// we're done when both lists are empty
// boolean finished = _finished;
_finished = ((_queued.size() + _running.size()) == 0);
// if (!finished && _finished) {
// Log.info("Finishing sequence at " + (tickStamp%10000) + ".");
// }
}
// documentation inherited
public void paint (Graphics2D gfx)
{
// don't care
}
// documentation inherited
public void fastForward (long timeDelta)
{
_lastStamp += timeDelta;
}
// documentation inherited
public void viewLocationDidChange (int dx, int dy)
{
super.viewLocationDidChange(dx, dy);
// track cumulative view location changes so that we can adjust our
// animations before we start them
_vdx += dx;
_vdy += dy;
}
/**
* Called when the time comes to start an animation. Derived classes
* may override this method and pass the animation on to their
* animation manager and do whatever else they need to do with
* operating animations. The default implementation simply adds them
* to the media panel supplied when we were constructed.
*
* @param anim the animation to be displayed.
* @param tickStamp the timestamp at which this animation was fired.
*/
protected void startAnimation (Animation anim, long tickStamp)
{
// account for any view scrolling that happened before this animation
// was actually added to the view
if (_vdx != 0 || _vdy != 0) {
anim.viewLocationDidChange(_vdx, _vdy);
}
_animmgr.registerAnimation(anim);
}
protected class AnimRecord extends AnimationAdapter
{
public AnimRecord (Animation anim, long delta, AnimRecord trigger,
Runnable completionAction) {
_anim = anim;
_delta = delta;
_trigger = trigger;
_completionAction = completionAction;
}
public boolean readyToFire (long now, long lastStamp)
{
if (_delta == -1) {
// if we have no trigger, that means we should start
// immediately, otherwise we wait until our trigger is no
// longer running (they are guaranteed not to be still
// queued at this point because readyToFire is only called
// on an animation after all animations previous in the
// queue have been started)
return (_trigger == null) ?
true : !_running.contains(_trigger);
} else {
return (lastStamp + _delta <= now);
}
}
public void fire (long when)
{
// Log.info("Firing " + this + " at " + (when%10000) + ".");
// if we have an animation, start it up and await its
// completion
if (_anim != null) {
startAnimation(_anim, when);
_anim.addAnimationObserver(this);
} else {
// since there's no animation, we need to fire our
// completion routine immediately
fireCompletion(when);
}
}
public void fireCompletion (long when)
{
// Log.info("Completing " + this + " at " + (when%10000) + ".");
// call the completion action, if there is one
if (_completionAction != null) {
try {
_completionAction.run();
} catch (Throwable t) {
Log.logStackTrace(t);
}
}
// make a note that this animation is complete
_running.remove(this);
// kids, don't try this at home; we call tick() immediately so
// that any animations triggered on the completion of previous
// animations can trigger on the completion of this animation
// rather than having to wait until the next tick to do so
tick(when);
}
public void animationCompleted (Animation anim, long when)
{
fireCompletion(when);
}
public String toString ()
{
return "[anim=" + StringUtil.shortClassName(_anim) +
((_anim == null) ? "" : ("/" + _anim.hashCode())) +
", action=" + _completionAction +
", delta=" + _delta + ", trig=" + (_trigger != null) + "]";
}
protected Animation _anim;
protected Runnable _completionAction;
protected long _delta;
protected AnimRecord _trigger;
}
/** The animation manager in which we run animations. */
protected AnimationManager _animmgr;
/** Animations that have not been fired. */
protected ArrayList _queued = new ArrayList();
/** Animations that are currently running. */
protected ArrayList _running = new ArrayList();
/** The timestamp at which we fired the last animation. */
protected long _lastStamp;
/** Used to track view scrolling while animations are in limbo. */
protected int _vdx, _vdy;
}
@@ -0,0 +1,95 @@
//
// $Id: AnimationWaiter.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.media.animation;
/**
* An abstract class that simplifies a common animation usage case in
* which a number of animations are created and the creator would like to
* be able to perform specific actions when each animation has completed
* (see {@link #animationDidFinish} and/or when all animations are
* finished (see {@link #allAnimationsFinished}.)
*/
public abstract class AnimationWaiter
implements AnimationObserver
{
/**
* Adds an animation to the animation waiter for observation.
*/
public void addAnimation (Animation anim)
{
anim.addAnimationObserver(this);
_animCount++;
}
/**
* Adds the supplied animations to the animation waiter for
* observation.
*/
public void addAnimations (Animation[] anims)
{
int acount = anims.length;
for (int ii = 0; ii < acount; ii++) {
addAnimation(anims[ii]);
}
}
// documentation inherited from interface
public void animationStarted (Animation anim, long when)
{
}
// documentation inherited from interface
public void animationCompleted (Animation anim, long when)
{
// note that the animation is finished
animationDidFinish(anim);
_animCount--;
// let derived classes know when all is done
if (_animCount == 0) {
allAnimationsFinished();
}
}
/**
* Called when an animation being observed by the waiter has
* completed its business. Derived classes may wish to override
* this method to engage in their unique antics.
*/
protected void animationDidFinish (Animation anim)
{
// nothing for now
}
/**
* Called when all animations being observed by the waiter have
* completed their business. Derived classes may wish to override
* this method to engage in their unique antics.
*/
protected void allAnimationsFinished ()
{
// nothing for now
}
/** The number of animations. */
protected int _animCount;
}
@@ -0,0 +1,67 @@
//
// $Id: BlankAnimation.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.media.animation;
import java.awt.Graphics2D;
import java.awt.Rectangle;
/**
* Displays nothing, but does so for a specified amount of time. Useful
* when you want to get an animation completed event in some period of
* time but don't actually need to display anything.
*/
public class BlankAnimation extends Animation
{
public BlankAnimation (long duration)
{
super(new Rectangle(0, 0, 0, 0));
_duration = duration;
}
// documentation inherited
public void tick (long timestamp)
{
if (_start == 0) {
// initialize our starting time
_start = timestamp;
}
// check whether we're done
_finished = (timestamp - _start >= _duration);
}
// documentation inherited
public void fastForward (long timeDelta)
{
if (_start > 0) {
_start += timeDelta;
}
}
// documentation inherited
public void paint (Graphics2D gfx)
{
// nothing doing
}
protected long _duration, _start;
}
@@ -0,0 +1,87 @@
//
// $Id: BlendAnimation.java 4191 2006-06-13 22:42:20Z ray $
package com.threerings.media.animation;
import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.threerings.media.image.Mirage;
import com.threerings.media.util.LinearTimeFunction;
import com.threerings.media.util.TimeFunction;
/**
* Blends between a series of images using alpha.
*/
public class BlendAnimation extends Animation
{
/**
* Blends from the starting image through each successive image in the
* specified amount of time (blending between each image takes place
* in <code>delay</code> milliseconds).
*/
public BlendAnimation (int x, int y, Mirage[] images, int delay)
{
super(new Rectangle(x, y, images[0].getWidth(), images[0].getHeight()));
_images = images;
int fades = images.length-1;
_tfunc = new LinearTimeFunction(0, 100 * fades, delay * fades);
}
// documentation inherited
public void tick (long timestamp)
{
// check to see if our blend level has changed
int level = _tfunc.getValue(timestamp);
if (level == _level) {
return;
}
// stop if we reach the end
if (level == 100*(_images.length-1)) {
_finished = true;
}
_level = level;
invalidate();
}
// documentation inherited
public void fastForward (long timeDelta)
{
_tfunc.fastForward(timeDelta);
}
// documentation inherited
public void paint (Graphics2D gfx)
{
int index = _level / 100;
float alpha = 1f - (_level % 100) / 100f;
Composite ocomp = gfx.getComposite();
gfx.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, alpha));
_images[index].paint(gfx, _bounds.x, _bounds.y);
if (index < _images.length-1) {
gfx.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, 1f-alpha));
_images[index+1].paint(gfx, _bounds.x, _bounds.y);
}
gfx.setComposite(ocomp);
}
/** The images between which we are blending. */
protected Mirage[] _images;
/** The time function we're using to time our blends. */
protected TimeFunction _tfunc;
/** Our current blend level and image index all wrapped into one. */
protected int _level;
/** The alpha composite used to render our current image. */
protected AlphaComposite _currentComp;
/** The alpha composite used to render our "next" image. */
protected AlphaComposite _nextComp;
}
@@ -0,0 +1,120 @@
//
// $Id: BobbleAnimation.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.media.animation;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.samskivert.util.RandomUtil;
import com.threerings.media.image.Mirage;
/**
* An animation that bobbles an image around within a specific horizontal
* and vertical pixel range for a given period of time.
*/
public class BobbleAnimation extends Animation
{
/**
* Constructs a bobble animation.
*
* @param image the image to animate.
* @param sx the starting x-position.
* @param sy the starting y-position.
* @param rx the horizontal bobble range.
* @param ry the vertical bobble range.
* @param duration the time to animate in milliseconds.
*/
public BobbleAnimation (
Mirage image, int sx, int sy, int rx, int ry, int duration)
{
super(new Rectangle(sx - rx, sy - ry, sx + image.getWidth() + rx,
sy + image.getHeight() + ry));
// save things off
_image = image;
_sx = sx;
_sy = sy;
_rx = rx;
_ry = ry;
// calculate animation ending time
_duration = duration;
}
// documentation inherited
public void tick (long tickStamp)
{
// grab our ending time on our first tick
if (_end == 0L) {
_end = tickStamp + _duration;
}
_finished = (tickStamp >= _end);
invalidate();
// calculate the latest position
int dx = RandomUtil.getInt(_rx);
int dy = RandomUtil.getInt(_ry);
_x = (_sx + dx) * ((RandomUtil.getInt(2) == 0) ? -1 : 1);
_y = (_sy + dy) * ((RandomUtil.getInt(2) == 0) ? -1 : 1);
}
// documentation inherited
public void fastForward (long timeDelta)
{
_end += timeDelta;
}
// documentation inherited
public void paint (Graphics2D gfx)
{
_image.paint(gfx, _x, _y);
}
// documentation inherited
protected void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", x=").append(_x);
buf.append(", y=").append(_y);
buf.append(", rx=").append(_rx);
buf.append(", ry=").append(_ry);
buf.append(", sx=").append(_sx);
buf.append(", sy=").append(_sy);
}
/** The current position of the image. */
protected int _x, _y;
/** The horizontal and vertical bobbling range. */
protected int _rx, _ry;
/** The starting position. */
protected int _sx, _sy;
/** The image to animate. */
protected Mirage _image;
/** Animation ending timing information. */
protected long _duration, _end;
}
@@ -0,0 +1,336 @@
//
// $Id: ExplodeAnimation.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.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;
/** Returns a string representation of this instance. */
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;
}
// documentation inherited
public void fastForward (long timeDelta)
{
if (_start > 0) {
_start += timeDelta;
_end += timeDelta;
}
}
// documentation inherited
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 travelled
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();
}
// documentation inherited
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);
}
}
// documentation inherited
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();
}
@@ -0,0 +1,85 @@
//
// $Id: FadeAnimation.java 3427 2005-03-24 04:50:52Z 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.media.animation;
import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.threerings.media.Log;
import com.threerings.media.effects.FadeEffect;
/**
* An animation that displays an image fading from one alpha level to
* another in specified increments over time. The animation is finished
* when the specified target alpha is reached.
*/
public abstract class FadeAnimation extends Animation
{
/**
* Constructs a fade animation.
*
* @param bounds our bounds.
* @param alpha the starting alpha.
* @param step the alpha amount to step by each millisecond.
* @param target the target alpha level.
*/
protected FadeAnimation (
Rectangle bounds, float alpha, float step, float target)
{
super(bounds);
_effect = new FadeEffect(alpha, step, target);
}
// documentation inherited
public void tick (long timestamp)
{
if (_effect.tick(timestamp)) {
_finished = _effect.finished();
invalidate();
}
}
// documentation inherited
public void paint (Graphics2D gfx)
{
_effect.beforePaint(gfx);
paintAnimation(gfx);
_effect.afterPaint(gfx);
}
// documentation inherited
protected void willStart (long tickStamp)
{
super.willStart(tickStamp);
_effect.init(tickStamp);
}
/**
* Here is where derived animations actually render their image.
*/
protected abstract void paintAnimation (Graphics2D gfx);
/** This handles the main work of fading. */
protected FadeEffect _effect;
}
@@ -0,0 +1,52 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 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.media.animation;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.threerings.media.image.Mirage;
/**
* Fades an image in or out by varying the alpha level during rendering.
*/
public class FadeImageAnimation extends FadeAnimation
{
/**
* Creates an image fading animation.
*/
public FadeImageAnimation (Mirage image, int x, int y,
float alpha, float step, float target)
{
super(new Rectangle(x, y, image.getWidth(), image.getHeight()),
alpha, step, target);
_image = image;
}
// documentation inherited
protected void paintAnimation (Graphics2D gfx)
{
_image.paint(gfx, _bounds.x, _bounds.y);
}
protected Mirage _image;
}
@@ -0,0 +1,99 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 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.media.animation;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import com.samskivert.swing.Label;
import com.samskivert.swing.util.SwingUtil;
import com.threerings.media.Log;
/**
* Does something extraordinary.
*/
public class FadeLabelAnimation extends FadeAnimation
{
/**
* Creates a label fading animation.
*/
public FadeLabelAnimation (Label label, int x, int y,
float alpha, float step, float target)
{
super(new Rectangle(x, y, 0, 0), alpha, step, target);
_label = label;
}
/**
* Indicates that our label should be rendered with antialiased text.
*/
public void setAntiAliased (boolean antiAliased)
{
_antiAliased = antiAliased;
}
// documentation inherited
protected void init ()
{
super.init();
// if our label is not yet laid out, do the deed
if (!_label.isLaidOut()) {
Graphics2D gfx = (Graphics2D)_mgr.getMediaPanel().getGraphics();
if (gfx != null) {
gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
_antiAliased ?
RenderingHints.VALUE_ANTIALIAS_ON :
RenderingHints.VALUE_ANTIALIAS_OFF);
_label.layout(gfx);
gfx.dispose();
}
}
// size the bounds to fit our label
Dimension size = _label.getSize();
_bounds.width = size.width;
_bounds.height = size.height;
}
// documentation inherited
protected void paintAnimation (Graphics2D gfx)
{
Object ohints = null;
if (_antiAliased) {
ohints = SwingUtil.activateAntiAliasing(gfx);
}
_label.render(gfx, _bounds.x, _bounds.y);
if (_antiAliased) {
SwingUtil.restoreAntiAliasing(gfx, ohints);
}
}
/** The label we are rendering. */
protected Label _label;
/** Whether or not to use anti-aliased rendering. */
protected boolean _antiAliased;
}
@@ -0,0 +1,248 @@
//
// $Id: FloatingTextAnimation.java 3212 2004-11-12 00:33:38Z 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.media.animation;
import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.samskivert.swing.Label;
public class FloatingTextAnimation extends Animation
{
/**
* Constructs an animation for the given text centered at the given
* coordinates.
*/
public FloatingTextAnimation (Label label, int x, int y)
{
this(label, x, y, DEFAULT_FLOAT_PERIOD);
}
/**
* Constructs an animation for the given text centered at the given
* coordinates. The animation will float up the screen for 30 pixels.
*/
public FloatingTextAnimation (Label label, int x, int y, long floatPeriod)
{
this(label, x, y, x, y - DELTA_Y, floatPeriod);
}
/**
* Constructs an animation for the given text starting at the given
* coordinates and floating toward the specified coordinates.
*/
public FloatingTextAnimation (Label label, int sx, int sy,
int destx, int desty, long floatPeriod)
{
super(new Rectangle(sx, sy, label.getSize().width,
label.getSize().height));
// save things off
_label = label;
_startX = _x = sx;
_startY = _y = sy;
_destx = destx;
_desty = desty;
_floatPeriod = floatPeriod;
// calculate our deltas
_dx = (destx - sx);
_dy = (desty - sy);
// initialize our starting alpha
_alpha = 1.0f;
}
/**
* Called to change the direction 180 degrees.
*/
public void flipDirection ()
{
_dx = -_dx;
_dy = -_dy;
}
/**
* Returns the label used to render this score animation.
*/
public Label getLabel ()
{
return _label;
}
// documentation inherited
public void setLocation (int x, int y)
{
super.setLocation(x, y);
// update our destination coordinates
_destx += (x - _startX);
_desty += (y - _startY);
// update our initial coordinates
_startX = _x = x;
_startY = _y = y;
// recalculate our deltas
_dx = (_destx - x);
_dy = (_desty - y);
}
/**
* Sets the duration of this score animation to the specified time in
* milliseconds. This should be called before the animation is added
* to the animation manager.
*/
public void setFloatPeriod (long floatPeriod)
{
_floatPeriod = floatPeriod;
}
// documentation inherited
public void tick (long timestamp)
{
boolean invalid = false;
if (_start == 0) {
// initialize our starting time
_start = timestamp;
// we need to make sure to invalidate ourselves initially
invalid = true;
}
long fadeDelay = _floatPeriod/2;
long fadePeriod = _floatPeriod - fadeDelay;
// figure out the current alpha
long msecs = timestamp - _start;
float oalpha = _alpha;
if (msecs > fadeDelay) {
long rmsecs = msecs - fadeDelay;
_alpha = Math.max(1.0f - (rmsecs / (float)fadePeriod), 0.0f);
_alpha = Math.min(_alpha, 1.0f);
}
// get the alpha composite
_comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, _alpha);
// determine the new y-position of the score
float pctdone = (float)msecs / _floatPeriod;
int ox = _x, oy = _y;
_x = _startX + (int)(_dx * pctdone);
_y = _startY + (int)(_dy * pctdone);
// only update our location and dirty ourselves if we actually
// moved or our alpha changed
if (ox != _x || oy != _y) {
// dirty our old location
invalidate();
_bounds.setLocation(_x, _y);
invalid = true;
} else if (oalpha != _alpha) {
invalid = true;
}
if (invalid) {
// dirty our current location
invalidate();
}
// note whether we're done
_finished = (msecs >= _floatPeriod);
}
// documentation inherited
public void fastForward (long timeDelta)
{
if (_start > 0) {
_start += timeDelta;
}
}
// documentation inherited
public void paint (Graphics2D gfx)
{
Composite ocomp = gfx.getComposite();
if (_comp != null) {
gfx.setComposite(_comp);
}
paintLabels(gfx, _x, _y);
gfx.setComposite(ocomp);
}
/**
* Derived classes may wish to extend score animation and render more than
* just the standard single label.
*
* @param x the upper left coordinate of the animation.
* @param y the upper left coordinate of the animation.
*/
protected void paintLabels (Graphics2D gfx, int x, int y)
{
_label.render(gfx, x, y);
}
// documentation inherited
protected void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", x=").append(_x);
buf.append(", y=").append(_y);
buf.append(", alpha=").append(_alpha);
}
/** The starting animation time. */
protected long _start;
/** The label we're animating. */
protected Label _label;
/** The starting coordinates of the score. */
protected int _startX, _startY;
/** The current coordinates of the score. */
protected int _x, _y;
/** The destination coordinates towards which the animation travels. */
protected int _destx, _desty;
/** The distance to be traveled by the score animation. */
protected int _dx, _dy;
/** The duration for which we float up the screen. */
protected long _floatPeriod;
/** The current alpha level used to render the score. */
protected float _alpha;
/** The composite used to render the score. */
protected Composite _comp;
/** The time in milliseconds during which the score is visible. */
protected static final long DEFAULT_FLOAT_PERIOD = 1500L;
/** The total vertical distance the score travels. */
protected static final int DELTA_Y = 30;
}
@@ -0,0 +1,150 @@
//
// $Id: GleamAnimation.java 4191 2006-06-13 22:42:20Z ray $
package com.threerings.media.animation;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Transparency;
import com.threerings.media.sprite.Sprite;
import com.threerings.media.sprite.SpriteManager;
import com.threerings.media.util.LinearTimeFunction;
import com.threerings.media.util.TimeFunction;
/**
* Washes all non-transparent pixels in a sprite with a particular color
* (by compositing them with the solid color with progressively higher
* alpha values) and then back again.
*/
public class GleamAnimation extends Animation
{
/**
* Creates a gleam animation with the supplied sprite. The sprite will
* be faded to the specified color and then back again. The sprite may
* be already added to the supplied sprite manager or not, but when
* the animation is complete, it will have been added.
*
* @param fadeIn if true, the sprite itself will be faded in as we
* fade up to the gleam color and the gleam color will fade out,
* leaving just the sprite imagery.
*/
public GleamAnimation (SpriteManager spmgr, Sprite sprite, Color color,
int upmillis, int downmillis, boolean fadeIn)
{
super(sprite.getBounds());
_spmgr = spmgr;
_sprite = sprite;
_color = color;
_upfunc = new LinearTimeFunction(0, 750, upmillis);
_downfunc = new LinearTimeFunction(750, 0, downmillis);
_fadeIn = fadeIn;
}
// documentation inherited
public void tick (long timestamp)
{
int alpha;
if (_upfunc != null) {
if ((alpha = _upfunc.getValue(timestamp)) == 750) {
_upfunc = null;
}
} else if (_downfunc != null) {
if ((alpha = _downfunc.getValue(timestamp)) == 0) {
_downfunc = null;
}
} else {
_finished = true;
_spmgr.addSprite(_sprite);
return;
}
if (_alpha != alpha) {
_alpha = alpha;
invalidate();
}
}
// documentation inherited
public void fastForward (long timeDelta)
{
if (_upfunc != null) {
_upfunc.fastForward(timeDelta);
} else if (_downfunc != null) {
_downfunc.fastForward(timeDelta);
}
}
// documentation inherited
public void paint (Graphics2D gfx)
{
// TODO: recreate our off image if the sprite bounds changed; we
// also need to change the bounds of our animation which might
// require some jockeying (especially if we shrink)
if (_offimg == null) {
_offimg = gfx.getDeviceConfiguration().createCompatibleImage(
_bounds.width, _bounds.height, Transparency.TRANSLUCENT);
}
// create a mask image with our sprite and the appropriate color
Graphics2D ogfx = (Graphics2D)_offimg.getGraphics();
try {
ogfx.setColor(_color);
ogfx.fillRect(0, 0, _bounds.width, _bounds.height);
ogfx.setComposite(AlphaComposite.DstAtop);
ogfx.translate(-_sprite.getX(), -_sprite.getY());
_sprite.paint(ogfx);
} finally {
ogfx.dispose();
}
Composite ocomp = null;
Composite ncomp = AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, _alpha/1000f);
// if we're fading the sprite in on the way up, set our alpha
// composite before we render the sprite
if (_fadeIn && _upfunc != null) {
ocomp = gfx.getComposite();
gfx.setComposite(ncomp);
}
// next render the sprite
_sprite.paint(gfx);
// if we're not fading in, we still need to alpha the white bits
if (ocomp == null) {
ocomp = gfx.getComposite();
gfx.setComposite(ncomp);
}
// now alpha composite our mask atop the sprite
gfx.drawImage(_offimg, _sprite.getX(), _sprite.getY(), null);
gfx.setComposite(ocomp);
}
// documentation inherited
protected void willStart (long tickStamp)
{
super.willStart(tickStamp);
// remove the sprite we're fiddling with from the manager; we'll
// add it back when we're done
if (_spmgr.isManaged(_sprite)) {
_spmgr.removeSprite(_sprite);
}
}
protected SpriteManager _spmgr;
protected Sprite _sprite;
protected Color _color;
protected Image _offimg;
protected boolean _fadeIn;
protected TimeFunction _upfunc;
protected TimeFunction _downfunc;
protected int _alpha = -1;
}
@@ -0,0 +1,134 @@
//
// $Id: MultiFrameAnimation.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.media.animation;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.threerings.media.util.FrameSequencer;
import com.threerings.media.util.MultiFrameImage;
/**
* Animates a sequence of image frames in place with a particular frame
* rate.
*/
public class MultiFrameAnimation extends Animation
{
/**
* Creates a multi-frame animation with the specified source image
* frames and the specified target frame rate (in frames per second).
*
* @param frames the source frames of the animation.
* @param fps the target number of frames per second.
* @param loop whether the animation should loop indefinitely or
* finish after one shot.
*/
public MultiFrameAnimation (
MultiFrameImage frames, double fps, boolean loop)
{
this(frames, new FrameSequencer.ConstantRate(fps, loop));
}
/**
* Creates a multi-frame animation with the specified source image
* frames and the specified target frame rate (in frames per second).
*/
public MultiFrameAnimation (MultiFrameImage frames, FrameSequencer seeker)
{
// we'll set up our bounds via setLocation() and in reset()
super(new Rectangle());
_frames = frames;
_seeker = seeker;
// reset ourselves to start things off
reset();
}
// documentation inherited
public Rectangle getBounds ()
{
// fill in the bounds with our current animation frame's bounds
return _bounds;
}
/**
* If this animation has run to completion, it can be reset to prepare
* it for another go.
*/
public void reset ()
{
super.reset();
// set the frame number to -1 so that we don't ignore the
// transition to frame zero on the first call to tick()
_fidx = -1;
// reset our frame sequencer
_seeker.init(_frames);
if (_seeker instanceof AnimationFrameSequencer) {
((AnimationFrameSequencer) _seeker).setAnimation(this);
}
}
// documentation inherited
public void tick (long tickStamp)
{
int fidx = _seeker.tick(tickStamp);
if (fidx == -1) {
_finished = true;
} else if (fidx != _fidx) {
// update our frame index and bounds
setFrameIndex(fidx);
// and have ourselves repainted
invalidate();
}
}
/**
* Sets the frame index and updates our dimensions.
*/
protected void setFrameIndex (int fidx)
{
_fidx = fidx;
_bounds.width = _frames.getWidth(_fidx);
_bounds.height = _frames.getHeight(_fidx);
}
// documentation inherited
public void paint (Graphics2D gfx)
{
_frames.paintFrame(gfx, _fidx, _bounds.x, _bounds.y);
}
// documentation inherited
public void fastForward (long timeDelta)
{
_seeker.fastForward(timeDelta);
}
protected MultiFrameImage _frames;
protected FrameSequencer _seeker;
protected int _fidx;
}
@@ -0,0 +1,134 @@
//
// $Id: RainAnimation.java 4188 2006-06-13 18:03:48Z 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.media.animation;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.samskivert.util.RandomUtil;
/**
* An animation that displays raindrops spattering across an image.
*/
public class RainAnimation extends Animation
{
/**
* Constructs a rain animation with reasonable defaults for the number
* of raindrops and their dimensions.
*
* @param bounds the bounding rectangle for the animation.
* @param duration the number of seconds the animation should last.
*/
public RainAnimation (Rectangle bounds, long duration)
{
super(bounds);
init(duration, DEFAULT_COUNT, DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
/**
* Constructs a rain animation.
*
* @param bounds the bounding rectangle for the animation.
* @param duration the number of seconds the animation should last.
* @param count the number of raindrops to render in each frame.
* @param wid the width of a raindrop's bounding rectangle.
* @param hei the height of a raindrop's bounding rectangle.
*/
public RainAnimation (
Rectangle bounds, long duration, int count, int wid, int hei)
{
super(bounds);
init(duration, count, wid, hei);
}
protected void init (long duration, int count, int wid, int hei)
{
// save things off
_count = count;
_wid = wid;
_hei = hei;
// create the raindrop array
_drops = new int[count];
// calculate ending time
_duration = duration;
}
// documentation inherited
public void tick (long tickStamp)
{
// grab our ending time on our first tick
if (_end == 0L) {
_end = tickStamp + _duration;
}
_finished = (tickStamp >= _end);
// calculate the latest raindrop locations
for (int ii = 0; ii < _count; ii++) {
int x = RandomUtil.getInt(_bounds.width);
int y = RandomUtil.getInt(_bounds.height);
_drops[ii] = (x << 16 | y);
}
invalidate();
}
// documentation inherited
public void fastForward (long timeDelta)
{
_end += timeDelta;
}
// documentation inherited
public void paint (Graphics2D gfx)
{
gfx.setColor(Color.white);
for (int ii = 0; ii < _count; ii++) {
int x = _drops[ii] >> 16;
int y = _drops[ii] & 0xFFFF;
gfx.drawLine(x, y, x + _wid, y + _hei);
}
}
/** The number of raindrops. */
protected static final int DEFAULT_COUNT = 300;
/** The raindrop streak dimensions. */
protected static final int DEFAULT_WIDTH = 13;
protected static final int DEFAULT_HEIGHT = 10;
/** The number of raindrops. */
protected int _count;
/** The dimensions of each raindrop's bounding rectangle. */
protected int _wid, _hei;
/** The raindrop locations. */
protected int[] _drops;
/** Animation ending timing information. */
protected long _duration, _end;
}
@@ -0,0 +1,192 @@
//
// $Id: ScaleAnimation.java 3123 2004-09-18 22:58:39Z mdb $
package com.threerings.media.animation;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Image;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import com.threerings.media.image.Mirage;
import com.threerings.media.sprite.Sprite;
import com.threerings.media.sprite.SpriteManager;
import com.threerings.media.util.LinearTimeFunction;
import com.threerings.media.util.TimeFunction;
/**
* Animates an image changing size about its center point.
*/
public class ScaleAnimation extends Animation
{
/**
* Creates a scale animation with the supplied image. If the image's
* size would ever be 0 or less, it is not drawn.
*
* @param image The image to paint.
*
* @param center The screen coordinates of the pixel upon which the
* image's center should always be rendered.
*
* @param startScale The amount to scale the image when it is rendered
* at time 0.
*
* @param endScale The amount to scale the image at the final frame
* of animation.
*
* @param duration The time in milliseconds the anim takes to complete.
*/
public ScaleAnimation (Mirage image, Point center,
float startScale, float endScale, int duration)
{
super(getBounds(Math.max(startScale, endScale), center, image));
// Save inputted variables
_image = image;
_bufferedImage = _image.getSnapshot();
_center = new Point(center);
_startScale = startScale;
_endScale = endScale;
_duration = duration;
// Hack the LinearTimeFunction to use fixed point rationals
//
// FIXME: This class doesn't seem to be saving me a lot of
// work, since I have to repackage the outputs into floats
// anyway. Find some way to make the LinearTimeFunction do
// more of this work for us, or write a new class that does.
// Maybe IntLinearTimeFunction and FloatLinearTimeFunction
// classes would be useful.
_scaleFunc = new LinearTimeFunction(0, 10000, duration);
}
/**
* Java wants the first call in a constructor to be super()
* if it exists at all, so we have to trick it with this function.
*
* Oh, and this function computes how big the bounding box needs
* to be to bound the inputted image scaled to the inputted size
* centered around the inputted center poitn.
*/
public static Rectangle getBounds (float scale, Point center, Mirage image)
{
Point size = getSize(scale, image);
Point corner = getCorner(center, size);
return new Rectangle(corner.x, corner.y, size.x, size.y);
}
/**
* Compute the bounds to use to render the animation's image
* right now.
*/
public Rectangle getBounds ()
{
return getBounds(_scale, _center, _image);
}
/** Computes the width and height to which an image should be scaled. */
public static Point getSize (float scale, Mirage image)
{
int width = Math.max(0, Math.round(image.getWidth() * scale));
int height = Math.max(0, Math.round(image.getHeight() * scale));
return new Point(width, height);
}
/**
* Computes the upper left corner where the image should be drawn,
* given the center and dimensions to which the image should be scaled.
*/
public static Point getCorner (Point center, Point size)
{
return new Point(center.x - size.x/2, center.y - size.y/2);
}
// documentation inherited
public void tick (long tickStamp)
{
// Compute the new scaling value
float weight = _scaleFunc.getValue(tickStamp) / 10000.0f;
float scale = ((1.0f - weight) * _startScale) +
(( weight) * _endScale);
// Update the animation if the scaling changes
if (_scale != scale)
{
_scale = scale;
invalidate();
}
// Check if the animation completed
if (weight >= 1.0f) {
_finished = true;
}
}
// documentation inherited
public void fastForward (long timeDelta)
{
_scaleFunc.fastForward(timeDelta);
}
// documentation inherited
public void paint (Graphics2D gfx)
{
// Compute the bounding box to render this image
Rectangle bounds = getBounds();
// Paint nothing if the image was scaled to nothing
if (bounds.width <= 0 || bounds.height <= 0) {
return;
}
// Smooth out the image scaling
//
// FIXME: Should this be turned off when the painting is done?
gfx.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// Paint the image scaled to this location
gfx.drawImage(_bufferedImage,
bounds.x,
bounds.y,
bounds.x + bounds.width,
bounds.y + bounds.height,
0,
0,
_bufferedImage.getWidth(),
_bufferedImage.getHeight(),
null);
}
/** The image to scale. */
protected Mirage _image;
/** The image converted to a format Graphics2D likes, and cached. */
protected BufferedImage _bufferedImage;
/** The center pixel to render the image around. */
protected Point _center;
/** The amount of time the animation should last. */
//XXX Is this needed?
protected long _duration;
/** The amount to scale the image at the start of the animation. */
protected float _startScale;
/** The amount to scale the image at the end of the animation. */
protected float _endScale;
/** The current amount of scaling to render. */
protected float _scale;
/** Computes the image scaling to use at the specified time. */
protected TimeFunction _scaleFunc;
}
@@ -0,0 +1,35 @@
//
// $Id: SequencedAnimationObserver.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.media.animation;
/**
* Extends the animation observer interface with extra goodies.
*/
public interface SequencedAnimationObserver extends AnimationObserver
{
/**
* Called when the observed animation -- previously configured with an
* {@link AnimationFrameSequencer} -- reached the specified frame.
*/
public void frameReached (Animation anim, long when,
int frameIdx, int frameSeq);
}
@@ -0,0 +1,257 @@
//
// $Id: SparkAnimation.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.media.animation;
import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import com.samskivert.util.RandomUtil;
import com.threerings.media.animation.Animation;
import com.threerings.media.image.Mirage;
import com.threerings.media.Log;
/**
* Displays a set of spark images originating from a specified position
* and flying outward in random directions, fading out as they go, for a
* specified period of time.
*/
public class SparkAnimation extends Animation
{
/**
* Constructs a spark animation with the supplied parameters.
*
* @param bounds the bounding rectangle for the animation.
* @param x the starting x-position for the sparks.
* @param y the starting y-position for the sparks.
* @param xjog the maximum X distance by which to "jog" the initial spark
* positions, or 0 if no jogging is desired.
* @param yjog the maximum Y distance by which to "jog" the initial spark
* positions, or 0 if no jogging is desired.
* @param minxvel the minimum starting x-velocity of the sparks.
* @param minyvel the minimum starting y-velocity of the sparks.
* @param maxxvel the maximum x-velocity of the sparks.
* @param maxyvel the maximum y-velocity of the sparks.
* @param xacc the x axis acceleration, or 0 if none is desired.
* @param yacc the y axis acceleration, or 0 if none is desired.
* @param images the spark images to be animated.
* @param delay the duration of the animation in milliseconds.
* @param fade do the fade thing
*/
public SparkAnimation (Rectangle bounds, int x, int y, int xjog, int yjog,
float minxvel, float minyvel,
float maxxvel, float maxyvel,
float xacc, float yacc,
Mirage[] images, long delay, boolean fade)
{
super(bounds);
// save things off
_xacc = xacc;
_yacc = yacc;
_images = images;
_delay = delay;
_fade = fade;
// initialize various things
_icount = images.length;
_ox = new int[_icount];
_oy = new int[_icount];
_xpos = new int[_icount];
_ypos = new int[_icount];
_sxvel = new float[_icount];
_syvel = new float[_icount];
for (int ii = 0; ii < _icount; ii++) {
// initialize spark position
_ox[ii] = x +
((xjog == 0) ? 0 : RandomUtil.getInt(xjog) * randomDirection());
_oy[ii] = y +
((yjog == 0) ? 0 : RandomUtil.getInt(yjog) * randomDirection());
// Choose random X and Y axis velocities between the inputted
// bounds
_sxvel[ii] = minxvel + RandomUtil.getFloat(1) * (maxxvel - minxvel);
_syvel[ii] = minyvel + RandomUtil.getFloat(1) * (maxyvel - minyvel);
// If accelerationes were given, make the starting velocities
// move against that acceleration; otherwise pick directions
// at random
if (_xacc > 0) {
_sxvel[ii] = -_sxvel[ii];
} else if (_xacc == 0) {
_sxvel[ii] *= randomDirection();
}
if (_yacc > 0) {
_syvel[ii] = -_syvel[ii];
} else if (_yacc == 0) {
_syvel[ii] *= randomDirection();
}
}
if (_fade) {
_alpha = 1.0f;
_comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, _alpha);
}
}
/**
* Returns at random -1 for negative direction or +1 for positive.
*/
protected int randomDirection ()
{
return (RandomUtil.getInt(2) == 0) ? -1 : 1;
}
// documentation inherited
protected void willStart (long stamp)
{
super.willStart(stamp);
_start = stamp;
}
// documentation inherited
public void fastForward (long timeDelta)
{
_start += timeDelta;
}
// documentation inherited
public void tick (long timestamp)
{
// figure out the distance the chunks have travelled
long msecs = Math.max(timestamp - _start, 0);
long msecsSq = msecs * msecs;
// calculate the alpha level with which to render the chunks
if (_fade) {
float pctdone = msecs / (float)_delay;
_alpha = Math.max(0.1f, Math.min(1.0f, 1.0f - pctdone));
_comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, _alpha);
}
// assume all sparks have moved outside the bounds
boolean allOutside = true;
// move all sparks and check whether any remain to be animated
for (int ii = 0; ii < _icount; ii++) {
// determine the travel distance
int xtrav = (int)
((_sxvel[ii] * msecs) + (0.5f * _xacc * msecsSq));
int ytrav = (int)
((_syvel[ii] * msecs) + (0.5f * _yacc * msecsSq));
// update the position
_xpos[ii] = _ox[ii] + xtrav;
_ypos[ii] = _oy[ii] + ytrav;
// check to see if any are still in. Stop looking
// when we find one
if (allOutside && _bounds.intersects(_xpos[ii], _ypos[ii],
_images[ii].getWidth(), _images[ii].getHeight())) {
allOutside = false;
}
}
// note whether we're finished
_finished = allOutside || (msecs >= _delay);
// dirty ourselves
// TODO: only do this if at least one spark actually moved
invalidate();
}
// documentation inherited
public void paint (Graphics2D gfx)
{
Shape oclip = gfx.getClip();
gfx.clip(_bounds);
Composite ocomp = gfx.getComposite();
if (_fade) {
// set the alpha composite to reflect the current fade-out
gfx.setComposite(_comp);
}
// draw all sparks
for (int ii = 0; ii < _icount; ii++) {
_images[ii].paint(gfx, _xpos[ii], _ypos[ii]);
}
// restore the original gfx settings
gfx.setComposite(ocomp);
gfx.setClip(oclip);
}
// documentation inherited
protected void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", ox=").append(_ox);
buf.append(", oy=").append(_oy);
buf.append(", alpha=").append(_alpha);
}
/** The spark images we're animating. */
protected Mirage[] _images;
/** The number of images we're animating. */
protected int _icount;
/** The x axis acceleration in pixels per millisecond. */
protected float _xacc;
/** The y axis acceleration in pixels per millisecond. */
protected float _yacc;
/** The starting x-axis velocity of each chunk. */
protected float[] _sxvel;
/** The starting y-axis velocity of each chunk. */
protected float[] _syvel;
/** The starting 'jog' positions for each spark. */
protected int[] _ox, _oy;
/** The current positions of each spark. */
protected int[] _xpos, _ypos;
/** The starting animation time. */
protected long _start;
/** Whether or not we should fade the sparks out. */
protected boolean _fade;
/** The percent alpha with which to render the images. */
protected float _alpha;
/** The alpha composite with which to render the images. */
protected AlphaComposite _comp;
/** The duration of the spark animation in milliseconds. */
protected long _delay;
}
@@ -0,0 +1,102 @@
//
// $Id: SpriteAnimation.java 4191 2006-06-13 22:42:20Z ray $
//
// 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.media.animation;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.threerings.media.sprite.Sprite;
import com.threerings.media.sprite.SpriteManager;
import com.threerings.media.sprite.PathObserver;
import com.threerings.media.util.Path;
public class SpriteAnimation extends Animation
implements PathObserver
{
/**
* Constructs a sprite animation for the given sprite. The first time
* the animation is ticked, the sprite will be added to the given
* sprite manager and started along the supplied path.
*/
public SpriteAnimation (SpriteManager spritemgr, Sprite sprite, Path path)
{
super(new Rectangle());
// save things off
_spritemgr = spritemgr;
_sprite = sprite;
_path = path;
// set up our sprite business
_sprite.addSpriteObserver(this);
}
/**
* Derived classes can override tick if they want to end the animation
* in some other way than the sprite completing a path.
*/
public void tick (long timestamp)
{
// start the sprite moving on its path on our first tick
if (_path != null) {
_spritemgr.addSprite(_sprite);
_sprite.move(_path);
_path = null;
}
}
// documentation inherited
public void paint (Graphics2D gfx)
{
// nothing for now
}
// documentation inherited from interface
public void pathCancelled (Sprite sprite, Path path)
{
_finished = true;
}
// documentation inherited from interface
public void pathCompleted (Sprite sprite, Path path, long when)
{
_finished = true;
}
// documentation inherited
protected void didFinish (long tickStamp)
{
super.didFinish(tickStamp);
_spritemgr.removeSprite(_sprite);
_sprite = null;
}
/** The sprite associated with this animation. */
protected Sprite _sprite;
/** The sprite manager managing our sprite. */
protected SpriteManager _spritemgr;
/** The path along which we'll move our sprite. */
protected Path _path;
}