// // Nenya library - tools for developing networked games // Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved // https://github.com/threerings/nenya // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package com.threerings.media.util; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Point; import com.samskivert.util.StringUtil; import com.threerings.util.DirectionUtil; /** * The line path is used to cause a pathable to go from point A to point B * along the arc of an ellipse in a certain number of milliseconds. */ public class ArcPath extends TimedPath { /** An orientation constant indicating that the normal (eight) * directions should be used to orient the pathable being made to * follow this path. */ public static final int NORMAL = 0; /** An orientation constant indicating that the fine (sixteen) * directions should be used to orient the pathable being made to * follow this path. */ public static final int FINE = 1; /** An orientation indicating that the pathable should not be oriented * as it moves along the path. */ public static final int NONE = 2; /** * Creates an arc path that will animate a pathable from the specified * starting position along an ellipse defined by the supplied * parameters. The pathable will travel the specified number of * radians along the arc of that ellipse. A positive number of radians * indicates counter-clockwise travel along the circle, a negative * number, clockwise rotation. * * @param start the starting point for the pathable. * @param xradius the length of the x radius. * @param yradius the length of the y radius. * @param sangle the starting angle. * @param delta the angle through which the pathable should be moved. * @param duration the number of milliseconds during which to effect * the animation. * @param orient an orientation code indicating how the pathable * should be oriented when following the path, either {@link #NORMAL}, * or {@link #FINE}. */ public ArcPath (Point start, double xradius, double yradius, double sangle, double delta, long duration, int orient) { super(duration); _xradius = xradius; _yradius = yradius; _sangle = sangle; _delta = delta; _orient = orient; // compute the center of the ellipse _center = new Point( (int)(start.x - Math.round(Math.cos(sangle) * xradius)), (int)(start.y - Math.round(Math.sin(sangle) * yradius))); } /** * Return a copy of the path, translated by the specified amounts. */ public Path getTranslatedInstance (int x, int y) { int startx = (int)(_center.x + Math.round(Math.cos(_sangle) * _xradius)); int starty = (int)(_center.y + Math.round(Math.sin(_sangle) * _yradius)); return new ArcPath(new Point(startx + x, starty + y), _xradius, _yradius, _sangle, _delta, _duration, _orient); } /** * Sets the offset that is applied to the pathable whenever it is oriented. This offset is in * clockwise units whose granularity is specified by the {@link #NORMAL} or {@link #FINE} * setting supplied to the path at construct time. The intent here is to allow arc paths to be * applied that don't orient the pathable in the direction they are traveling but instead in * some fixed offset from that direction. */ public void setOrientOffset (int offset) { _orientOffset = offset; } /** * Returns the position of the end of the path. */ public Point getEndPos () { return new Point( (int)(_center.x + Math.round(Math.cos(_sangle + _delta) * _xradius)), (int)(_center.y + Math.round(Math.sin(_sangle + _delta) * _yradius))); } // documentation inherited public boolean tick (Pathable pable, long timestamp) { double angle; boolean modified = false; // if we've blown past our arrival time... if (timestamp >= _startStamp + _duration) { // ...force the angle to the destination angle angle = _sangle + _delta; } else { // otherwise, compute the angle at which we should place the pathable based on the // elapsed time long elapsed = timestamp - _startStamp; angle = _sangle + _delta * elapsed / _duration; } // determine where we should be along the path computePosition(_center, _xradius, _yradius, angle, _tpos); // Skip this if we are not reorienting as we follow the path. if (_orient != NONE) { // compute the pathable's new orientation double theta = angle + ((_delta > 0) ? Math.PI/2 : -Math.PI/2); int orient; switch (_orient) { default: case NORMAL: orient = DirectionUtil.getDirection(theta); // adjust it appropriately orient = DirectionUtil.rotateCW(orient, 2*_orientOffset); break; case FINE: orient = DirectionUtil.getFineDirection(theta); // adjust it appropriately orient = DirectionUtil.rotateCW(orient, _orientOffset); break; } // update the pathable's orientation if it changed if (pable.getOrientation() != orient) { pable.setOrientation(orient); modified = true; } } // update the pathable's location if it moved if (pable.getX() != _tpos.x || pable.getY() != _tpos.y) { pable.setLocation(_tpos.x, _tpos.y); modified = true; } // if we completed our path, let the sprite know if (angle == _sangle + _delta) { pable.pathCompleted(timestamp); } return modified; } // documentation inherited public void paint (Graphics2D gfx) { int x = (int)(_center.x - _xradius), y = (int)(_center.y - _yradius); int width = (int)(2*_xradius), height = (int)(2*_yradius); int sangle = (int)(Math.round(180 * _sangle / Math.PI)), delta = (int)(Math.round(180 * _delta / Math.PI)); gfx.setColor(Color.blue); gfx.drawRect(x, y, width-1, height-1); gfx.setColor(Color.yellow); gfx.drawArc(x, y, width-1, height-1, 0, 360); gfx.setColor(Color.red); gfx.drawArc(x, y, width-1, height-1, 360-sangle, -delta); } @Override protected void toString (StringBuilder buf) { super.toString(buf); buf.append(", center=").append(StringUtil.toString(_center)); buf.append(", sangle=").append(_sangle); buf.append(", delta=").append(_delta); buf.append(", radii=").append(_xradius).append("/").append(_yradius); } /** * Computes the position of an entity along the path defined by the supplied parameters * assuming that it must finish the path in the specified duration (in millis) and has been * traveling the path for the specified number of elapsed milliseconds. */ public static void computePosition ( Point center, double xradius, double yradius, double angle, Point pos) { pos.x = (int)Math.round(center.x + xradius * Math.cos(angle)); pos.y = (int)Math.round(center.y + yradius * Math.sin(angle)); } /** The center of our ellipse. */ protected Point _center; /** Our ellipse radii. */ protected double _xradius, _yradius; /** Our starting and delta angles. */ protected double _sangle, _delta; /** The method to be used to orient the pathable. */ protected int _orient; /** An orientation offset used when orienting our pathable. */ protected int _orientOffset = 0; /** A temporary point used when computing our position along the path. */ protected Point _tpos = new Point(); }