//
// $Id: LineSegmentPath.java,v 1.9 2001/09/13 19:10:26 mdb Exp $
package com.threerings.media.sprite;
import java.awt.Color;
import java.awt.Point;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Iterator;
import com.samskivert.util.StringUtil;
import com.threerings.media.util.MathUtil;
/**
* The LineSegmentPath class is used to cause a sprite to
* follow a path that is made up of a sequence of line segments. There
* must be at least two nodes in any worthwhile path. The direction of the
* first node in the path is meaningless since the sprite begins at that
* node and will therefore never be heading towards it.
*/
public class LineSegmentPath implements Path
{
/**
* Construct a LineSegmentPath object.
*/
public LineSegmentPath ()
{
_nodes = new ArrayList();
}
/**
* Construct a LineSegmentPath object with the specified
* starting node coordinates. An arbitrary direction will be assigned
* to the starting node.
*
* @param x the starting node x-position.
* @param y the starting node y-position.
*/
public LineSegmentPath (int x, int y)
{
_nodes = new ArrayList();
// add the starting node with an arbitrarily chosen direction
// since direction is meaningless here
addNode(x, y, Sprite.DIR_NORTH);
}
/**
* Add a node to the path with the specified destination point and
* facing direction.
*
* @param x the x-position.
* @param y the y-position.
* @param dir the facing direction.
*/
public void addNode (int x, int y, int dir)
{
_nodes.add(new PathNode(x, y, dir));
}
/**
* Add a node to the path with the specified destination point. An
* arbitrary direction will be assigned to the node.
*
* @param x the x-position.
* @param y the y-position.
*/
public void addNode (int x, int y)
{
_nodes.add(new PathNode(x, y, Sprite.DIR_NORTH));
}
/**
* Return the requested node index in the path, or null if no such
* index exists.
*
* @param idx the node index.
*
* @return the path node.
*/
public PathNode getNode (int idx)
{
return (PathNode)_nodes.get(idx);
}
/**
* Return the number of nodes in the path.
*/
public int size ()
{
return _nodes.size();
}
/**
* Sets the velocity of this sprite in pixels per millisecond. The
* velocity is measured as pixels traversed along the path that the
* sprite is traveling rather than in the x or y directions
* individually.
*
* @param velocity the sprite velocity in pixels per millisecond.
*/
public void setVelocity (float velocity)
{
_vel = velocity;
}
// documentation inherited
public void init (Sprite sprite, long timestamp)
{
// if we have only one node then let the sprite know that we're
// done straight away
if (size() < 2) {
// move the sprite to the location specified by the first node
// (assuming we have a first node)
if (size() == 1) {
PathNode node = (PathNode)_nodes.get(0);
sprite.setLocation(node.loc.x, node.loc.y);
}
// and let the sprite know that we're done
sprite.pathCompleted();
return;
}
// and an enumeration of the path nodes
_niter = _nodes.iterator();
// pretend like we were previously heading to our starting position
_dest = (PathNode)_niter.next();
// begin traversing the path
headToNextNode(sprite, timestamp, timestamp);
}
// documentation inherited
public boolean updatePosition (Sprite sprite, long timestamp)
{
// figure out how far along this segment we should be
long msecs = timestamp - _nodestamp;
float travpix = msecs * _vel;
float pctdone = travpix / _seglength;
// if we've moved beyond the end of the path, we need to adjust
// the timestamp to determine how much time we used getting to the
// end of this node, then move to the next one
if (pctdone >= 1.0) {
long used = (long)(_seglength / _vel);
return headToNextNode(sprite, _nodestamp + used, timestamp);
}
// otherwise we position the sprite along the path
int ox = sprite.getX();
int oy = sprite.getY();
int nx = _src.loc.x + (int)((_dest.loc.x - _src.loc.x) * pctdone);
int ny = _src.loc.y + (int)((_dest.loc.y - _src.loc.y) * pctdone);
// only update the sprite's location if it actually moved
if (ox != nx || oy != ny) {
sprite.setLocation(nx, ny);
return true;
}
return false;
}
protected boolean headToNextNode (Sprite sprite, long startstamp, long now)
{
// check to see if we've completed our path
if (!_niter.hasNext()) {
// move the sprite to the location of our last destination
sprite.setLocation(_dest.loc.x, _dest.loc.y);
sprite.pathCompleted();
return true;
}
// our previous destination is now our source
_src = _dest;
// pop the next node off the path
_dest = (PathNode)_niter.next();
// adjust the sprite's orientation
sprite.setOrientation(_dest.dir);
// make a note of when we started traversing this node
_nodestamp = startstamp;
// figure out the distance from source to destination
_seglength = MathUtil.distance(_src.loc.x, _src.loc.y,
_dest.loc.x, _dest.loc.y);
// if we're already there (the segment length is zero), we skip to
// the next segment
if (_seglength == 0) {
return headToNextNode(sprite, startstamp, now);
}
// now update the sprite's position based on our progress thus far
return updatePosition(sprite, now);
}
public void paint (Graphics2D gfx)
{
gfx.setColor(Color.red);
Point prev = null;
int size = size();
for (int ii = 0; ii < size; ii++) {
PathNode n = (PathNode)getNode(ii);
if (prev != null) {
gfx.drawLine(prev.x, prev.y, n.loc.x, n.loc.y);
}
prev = n.loc;
}
}
public String toString ()
{
return StringUtil.toString(_nodes.iterator());
}
/** The nodes that make up the path. */
protected ArrayList _nodes;
/** We use this when moving along this path. */
protected Iterator _niter;
/** When moving, the sprite's source path node. */
protected PathNode _src;
/** When moving, the sprite's destination path node. */
protected PathNode _dest;
/** The time at which we started traversing the current node. */
protected long _nodestamp;
/** The length in pixels of the current path segment. */
protected float _seglength;
/** The path velocity in pixels per millisecond. */
protected float _vel = DEFAULT_VELOCITY;
/** When moving, the sprite position including fractional pixels. */
protected float _movex, _movey;
/** When moving, the distance to move on each axis per tick. */
protected float _incx, _incy;
/** The distance to move on the straight path line per tick. */
protected float _fracx, _fracy;
/** Default sprite velocity. */
protected static final float DEFAULT_VELOCITY = 200f/1000f;
}