/*
Copyright 2006 by Sean Luke and George Mason University
Licensed under the Academic Free License version 3.0
See the file "LICENSE" for more information
*/
package sim.app.woims;
import sim.util.*;
import sim.engine.*;
import java.awt.*;
import sim.portrayal.*;
import java.awt.geom.*;
public /*strictfp*/ class Woim extends SimplePortrayal2D implements Steppable
{
private static final long serialVersionUID = 1;
public static final double CENTROID_DISTANCE = 20 * WoimsDemo.DIAMETER;
public static final double AVOID_DISTANCE = 16 * WoimsDemo.DIAMETER;
public static final double COPY_SPEED_DISTANCE = 40 * WoimsDemo.DIAMETER;
public static final double OBSTACLE_AVOID_COEF = 1.05;
public static final double OBSTACLE_FAST_AVOID_COEF = 1.5;
public static final double MAX_DISTANCE = /*Strict*/Math.max( CENTROID_DISTANCE,
/*Strict*/Math.max( AVOID_DISTANCE,
COPY_SPEED_DISTANCE ) );
public static final double ADJUSTMENT_RATE = 0.025;
public static final double MIN_VELOCITY = 0.25;
public static final double MAX_VELOCITY = 0.75;
// initialize the woim
public Woim()
{
ond = /*Strict*/Math.random()*2*Math.PI;
ondSpeed = 0.05 + /*Strict*/Math.random()*0.15;
setNumberOfLinks( numLinks );
}
// squared distance between two points
public final double distanceSquared( final Vector2D loc1, final Vector2D loc2 )
{
return( (loc1.x-loc2.x)*(loc1.x-loc2.x)+(loc1.y-loc2.y)*(loc1.y-loc2.y) );
}
// squared distance between two points
public final double distanceSquared( final Vector2D loc1, final Double2D loc2 )
{
return( (loc1.x-loc2.x)*(loc1.x-loc2.x)+(loc1.y-loc2.y)*(loc1.y-loc2.y) );
}
// squared distance between two points
public final double distanceSquared( final double x1, final double y1, final double x2, final double y2 )
{
return ((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
}
// dot product between two vectors
public final double dotproduct( final Vector2D loc1, final Vector2D loc2 )
{
return loc1.x*loc2.x+loc1.y*loc2.y;
}
// initializes distances to closeby woims. it should be called a single time in the step function at each timestep.
Bag nearbyWoims;
double[] distSqrTo;
void preprocessWoims( final WoimsDemo state, Double2D pos, double distance )
{
nearbyWoims = state.woimsEnvironment.getNeighborsWithinDistance( pos, distance );
/*
if( nearbyWoims == null )
{
return;
}
*/
distSqrTo = new double[nearbyWoims.numObjs];
for( int i = 0 ; i < nearbyWoims.numObjs ; i++ )
{
Woim p = (Woim)(nearbyWoims.objs[i]);
distSqrTo[i] = distanceSquared(
pos.x,pos.y,p.x,p.y);
}
}
// returns a vector towards the center of the flock
public Vector2D towardsFlockCenterOfMass( final WoimsDemo state )
{
if( nearbyWoims == null )
return new Vector2D( 0, 0 );
Vector2D mean = new Vector2D( 0, 0 );
int n = 0;
for( int i = 0 ; i < nearbyWoims.numObjs ; i++ )
{
if( nearbyWoims.objs[i] != this &&
distSqrTo[i] <= CENTROID_DISTANCE * CENTROID_DISTANCE &&
distSqrTo[i] > AVOID_DISTANCE * AVOID_DISTANCE )
{
Woim p = (Woim)(nearbyWoims.objs[i]);
mean = mean.add(new Double2D(p.x,p.y));
n++;
}
}
if( n == 0 )
return new Vector2D( 0, 0 );
else
{
mean = mean.amplify( 1.0 / n );
mean = mean.subtract( woimPosition );
return mean.normalize();
}
}
// returns a vector away from woims that are too close
public Vector2D awayFromCloseBys( final WoimsDemo state )
{
if( nearbyWoims == null )
return new Vector2D( 0, 0 );
Vector2D away = new Vector2D( 0, 0 );
for( int i = 0 ; i < nearbyWoims.numObjs ; i++ )
{
if( nearbyWoims.objs[i] != this &&
distSqrTo[i] <= AVOID_DISTANCE * AVOID_DISTANCE )
{
Woim p = (Woim)(nearbyWoims.objs[i]);
Vector2D temp = woimPosition.subtract(new Double2D(p.x,p.y));
temp = temp.normalize();
away = away.add( temp );
}
}
return away.normalize();
}
// returns the mean speed of the nearby woims
public Vector2D matchFlockSpeed( final SimState state )
{
if( nearbyWoims == null )
return new Vector2D( 0, 0 );
Vector2D mean = new Vector2D( 0, 0 );
int n = 0;
for( int i = 0 ; i < nearbyWoims.numObjs ; i++ )
{
if( nearbyWoims.objs[i] != this &&
distSqrTo[i] <= COPY_SPEED_DISTANCE * COPY_SPEED_DISTANCE &&
distSqrTo[i] > AVOID_DISTANCE * AVOID_DISTANCE )
{
mean = mean.add( ((Woim)(nearbyWoims.objs[i])).velocity );
n++;
}
}
if( n == 0 )
return new Vector2D( 0, 0 );
else
{
mean = mean.amplify( 1.0 / n );
return mean.normalize();
}
}
// returns a random directions
public Vector2D randomDirection( final SimState state )
{
Vector2D temp = new Vector2D( 1.0 - 2.0 * state.random.nextDouble(),
1.0 - 2.0 * state.random.nextDouble() );
return temp.setLength( MIN_VELOCITY + state.random.nextDouble()*(MAX_VELOCITY-MIN_VELOCITY) );
}
// returns the oscilation vector
double ond;
double ondSpeed;
public Vector2D niceUndulation( final SimState state )
{
ond += ondSpeed;
if( ond > 7 )
ond -= 2*Math.PI;
double angle = /*Strict*/Math.cos( ond );
Vector2D temp = velocity;
double velA = /*Strict*/Math.atan2( temp.y, temp.x );
velA = velA + (Math.PI/2)*angle;
return new Vector2D( /*Strict*/Math.cos(velA), /*Strict*/Math.sin(velA) );
}
// returns a direction away from obstacles
public Vector2D avoidObstacles( final SimState state )
{
double[][] info = WoimsDemo.obstInfo;
if( info == null || info.length == 0 )
return new Vector2D( 0, 0 );
Vector2D away = new Vector2D( 0, 0 );
for( int i = 0 ; i < info.length ; i++ )
{
double dist = /*Strict*/Math.sqrt( (woimPosition.x-info[i][1])*(woimPosition.x-info[i][1]) +
(woimPosition.y-info[i][2])*(woimPosition.y-info[i][2]) );
if( dist <= info[i][0]+AVOID_DISTANCE )
{
Vector2D temp = woimPosition.subtract( new Vector2D( info[i][1], info[i][2] ) );
temp = temp.normalize();
away = away.add( temp );
}
}
return away.normalize();
}
protected Vector2D woimPosition = new Vector2D( 0, 0 );
public void step( final SimState state )
{
WoimsDemo bd = (WoimsDemo)state;
{
Double2D temp = new Double2D(x,y); //bd.environment.getObjectLocation( this );
woimPosition.x = x;
woimPosition.y = y;
preprocessWoims( bd, temp, MAX_DISTANCE );
}
Vector2D vel = new Vector2D( 0, 0 );
vel = vel.add( avoidObstacles(bd).amplify( 1.5 ) );
vel = vel.add( towardsFlockCenterOfMass(bd).amplify(0.5) );
vel = vel.add( matchFlockSpeed(bd).amplify(0.5) );
vel = vel.add( awayFromCloseBys(bd).amplify(1.5) );
if( vel.length() <= 1.0 )
{
vel = vel.add( niceUndulation(bd).amplify(0.5) );
vel = vel.add( randomDirection(bd).amplify(0.25) );
}
double vl = vel.length();
if( vl < MIN_VELOCITY )
vel = vel.setLength( MIN_VELOCITY );
else if( vl > MAX_VELOCITY )
vel = vel.setLength( MAX_VELOCITY );
vel = new Vector2D( (1-ADJUSTMENT_RATE)*velocity.x + ADJUSTMENT_RATE*vel.x,
(1-ADJUSTMENT_RATE)*velocity.y + ADJUSTMENT_RATE*vel.y );
velocity = vel;
Double2D desiredPosition = new Double2D( woimPosition.x+vel.x*WoimsDemo.TIMESTEP,
woimPosition.y+vel.y*WoimsDemo.TIMESTEP );
bd.setObjectLocation( this, desiredPosition );
updateLinkPosition();
}
public double x;
public double y;
Vector2D[] lastPos;
Color[] colors;
int numLinks = 7;
public int getNumberOfLinks() { return numLinks; }
public void setNumberOfLinks( int n )
{
if( numLinks == n && colors != null )
return;
if( n <= 0 )
return;
if( n > WoimsDemo.MAX_LINKS )
n = WoimsDemo.MAX_LINKS;
numLinks = n;
lastPos = new Vector2D[numLinks];
colors = new Color[numLinks];
for( int i = 0 ; i < colors.length ; i++ )
colors[i] = new Color( (int) (63+(192.0*(colors.length-i))/colors.length), 0, 0 );
updateLinkPosition();
}
protected double orientation;
protected Vector2D velocity = new Vector2D( 0, 0 );
protected Vector2D acceleration = new Vector2D( 0, 0 );
// drawing graphics
void drawLink( final Graphics2D graphics, double x, double y, double rx, double ry, final Color color)
{
graphics.setColor( color );
graphics.fillOval( (int)(x-rx/2.0), (int)(y-ry/2.0), (int)(rx), (int)(ry));
}
public void updateLinkPosition()
{
double centerx, centery;
// the head!
centerx = x;
centery = y;
lastPos[0] = new Vector2D( centerx, centery );
for( int i = 1 ; i < numLinks ; i++ )
{
if( lastPos[i] == null )
{
Vector2D temp = velocity.normalize().amplify(-1.0);
centerx = lastPos[i-1].x+1.0*temp.x;
centery = lastPos[i-1].y+1.0*temp.y;
lastPos[i] = new Vector2D( centerx, centery );
}
else
{
Vector2D temp = lastPos[i-1].subtract( lastPos[i] );
temp = temp.setLength( 1.0 );
temp = lastPos[i-1].subtract( temp );
lastPos[i] = temp;
}
}
}
public final void draw(Object object, final Graphics2D graphics, final DrawInfo2D info)
{
if( lastPos == null )
return;
for( int i = 0 ; i < numLinks ; i++ )
if( lastPos[i] != null )
drawLink( graphics,
info.draw.x+info.draw.width*(lastPos[i].x-lastPos[0].x),
info.draw.y+info.draw.height*(lastPos[i].y-lastPos[0].y),
info.draw.width,
info.draw.height,
colors[i] );
}
/** If drawing area intersects selected area, add last portrayed object to the bag */
public boolean hitObject(Object object, DrawInfo2D info)
{
if( lastPos == null )
return false;
for( int i = 0 ; i < numLinks ; i++ )
if( lastPos[i] != null )
{
Ellipse2D.Double ellipse = new Ellipse2D.Double(
info.draw.x+info.draw.width*(lastPos[i].x-lastPos[0].x),
info.draw.y+info.draw.height*(lastPos[i].y-lastPos[0].y),
info.draw.width,
info.draw.height );
if( ellipse.intersects( info.clip.x, info.clip.y, info.clip.width, info.clip.height ) )
{
return true;
}
}
return false;
}
}