/* * Copyright (c) 2008-2011, Piccolo2D project, http://piccolo2d.org * Copyright (c) 1998-2008, University of Maryland * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions * and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions * and the following disclaimer in the documentation and/or other materials provided with the * distribution. * * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its * contributors may be used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package edu.umd.cs.piccolox.activities; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.util.ArrayList; import edu.umd.cs.piccolo.activities.PInterpolatingActivity; /** * <b>PPositionPathActivity</b> animates through a sequence of points. * * @version 1.0 * @author Jesse Grosjean */ public class PPositionPathActivity extends PPathActivity { /** Points that define the animation's path. */ protected Point2D[] positions; /** An abstract representation of the thing being positioned. */ protected Target target; /** * Interface that objects must conform to in order to have their position * animated. */ public interface Target { /** * Set's the target's position to the coordinate provided. * * @param x the x component of the new position * @param y the y component of the new position */ void setPosition(double x, double y); } /** * Constructs a position activity that acts on the given target for the * duration provided and will update it's position at the given stepRate. * * @param duration milliseconds of animation * @param stepRate milliseconds between successive position updates * @param target abstract representation of thing being animated */ public PPositionPathActivity(final long duration, final long stepRate, final Target target) { this(duration, stepRate, target, null, new Point2D[0]); } /** * Constructs a position activity that acts on the given target for the * duration provided and will update it's position at the given stepRate. It * will follow the path defined by the knots and positions arguments. * * @param duration milliseconds of animation * @param stepRate milliseconds between successive position updates * @param target abstract representation of thing being animated * @param knots timing to use when animating * @param positions points along the path */ public PPositionPathActivity(final long duration, final long stepRate, final Target target, final float[] knots, final Point2D[] positions) { this(duration, stepRate, 1, PInterpolatingActivity.SOURCE_TO_DESTINATION, target, knots, positions); } /** * Constructs a position activity that will repeat the number of times * specified. It will act on the given target for the duration provided and * will update it's position at the given stepRate. It will follow the path * defined by the knots and positions arguments. * * @param duration milliseconds of animation * @param stepRate milliseconds between successive position updates * @param loopCount number of times this activity should repeat * @param mode how easing is handled on this activity * @param target abstract representation of thing being animated * @param knots timing to use when animating * @param positions points along the path */ public PPositionPathActivity(final long duration, final long stepRate, final int loopCount, final int mode, final Target target, final float[] knots, final Point2D[] positions) { super(duration, stepRate, loopCount, mode, knots); this.target = target; this.positions = (Point2D[]) positions.clone(); } /** * Returns true since this activity modifies the view and so cause a * repaint. * * @return always true */ protected boolean isAnimation() { return true; } /** * Returns a copy of the path's points. * * @return array of points on the path */ public Point2D[] getPositions() { return (Point2D[]) positions.clone(); } /** * Returns the point at the given index. * * @param index desired position index * @return point at the given index */ public Point2D getPosition(final int index) { return positions[index]; } /** * Changes all positions that define where along the target is being * positioned during the animation. * * @param positions new animation positions */ public void setPositions(final Point2D[] positions) { this.positions = (Point2D[]) positions.clone(); } /** * Sets the position of the point at the given index. * * @param index index of the point to change * @param position point defining the new position */ public void setPosition(final int index, final Point2D position) { positions[index] = position; } /** * Extracts positions from a GeneralPath and uses them to define this * activity's animation points. * * @param path source of points */ public void setPositions(final GeneralPath path) { final PathIterator pi = path.getPathIterator(null, 1); final ArrayList points = new ArrayList(); final float[] point = new float[6]; float distanceSum = 0; float lastMoveToX = 0; float lastMoveToY = 0; while (!pi.isDone()) { final int type = pi.currentSegment(point); switch (type) { case PathIterator.SEG_MOVETO: points.add(new Point2D.Float(point[0], point[1])); lastMoveToX = point[0]; lastMoveToY = point[1]; break; case PathIterator.SEG_LINETO: points.add(new Point2D.Float(point[0], point[1])); break; case PathIterator.SEG_CLOSE: points.add(new Point2D.Float(lastMoveToX, lastMoveToY)); break; case PathIterator.SEG_QUADTO: case PathIterator.SEG_CUBICTO: throw new RuntimeException(); default: // ok to do nothing it'll just be skipped } if (points.size() > 1) { final Point2D last = (Point2D) points.get(points.size() - 2); final Point2D current = (Point2D) points.get(points.size() - 1); distanceSum += last.distance(current); } pi.next(); } final int size = points.size(); final Point2D[] newPositions = new Point2D[size]; final float[] newKnots = new float[size]; for (int i = 0; i < size; i++) { newPositions[i] = (Point2D) points.get(i); if (i > 0) { final float dist = (float) newPositions[i - 1].distance(newPositions[i]); newKnots[i] = newKnots[i - 1] + dist / distanceSum; } } setPositions(newPositions); setKnots(newKnots); } /** * Overridden to interpret position at correct point along animation. * * TODO: improve these comments * * @param zeroToOne how far along the activity we are * @param startKnot the index of the startKnot * @param endKnot the index of the endKnot */ public void setRelativeTargetValue(final float zeroToOne, final int startKnot, final int endKnot) { final Point2D start = getPosition(startKnot); final Point2D end = getPosition(endKnot); target.setPosition(start.getX() + zeroToOne * (end.getX() - start.getX()), start.getY() + zeroToOne * (end.getY() - start.getY())); } }