We are about to switch to a new forum software. Until then we have removed the registration on this forum.
It's a simple steering algorithm I picked up from "The Nature of Code" and I don't understand why it isn't working. It doesn't seem to be steering, so much as pulling the entity. What's wrong here?
ArrayList<Entity> en;
boolean centerEmitter;
float radiusFromEmitter;
float restitution;
float mass;
float senseTypeProbability;
float geneticHit;
float birthSpeed;
float speedTypeSpeed;
float birthForce;
float birthSenseRange;
float senseTypeRange;
float birthSize;
color birthColor;
color strokeColor;
float femaleProbability;
void setup() {
size( 900, 700 );
background( 0 );
en = new ArrayList<Entity>();
centerEmitter = true;
radiusFromEmitter = 100;
restitution = 0.6;
mass = 0;
senseTypeProbability = 0.7;
geneticHit = 0.05;
birthSpeed = 2;
speedTypeSpeed = 4;
birthForce = 1;
birthSenseRange = 60;
senseTypeRange = 100;
strokeColor = 0;
birthSize = 50;
femaleProbability = 0.33;
}
void draw() {
background( 0 );
for(Entity ens: en ) {
ens.steer( new PVector( mouseX, mouseY ) );
//ens.deFlock( en, 17 );
ens.update();
ens.display();
if( mouseX > ens.location.x - ens.size * 0.3 && mouseX < ens.location.x + ens.size * 0.3 && mouseY > ens.location.y - ens.size * 0.3 && mouseY < ens.location.y + ens.size * 0.3 ) {
ens.displayRange();
}
}
}
void addSperm() {
PVector location;
float bSR;
float bS;
if( centerEmitter ) {
float rotation = random( 0, TWO_PI );
float x = width/2 + radiusFromEmitter * cos( rotation );
float y = height/2 + radiusFromEmitter * sin( rotation );
location = new PVector( x, y );
}
else {
int x = (int) ( Math.floor( (int) random( 2 ) ) * width + radiusFromEmitter );
int y = (int) ( Math.floor( (int) random( 2 ) ) * height + radiusFromEmitter );
location = new PVector( x, y );
}
birthColor = color( random( 255 ), random( 255 ), random( 255 ) );
if( random( 1 ) < senseTypeProbability ) {
bSR = senseTypeRange;// * random( 1 - geneticHit );
bS = birthSpeed;
}
else {
bSR = birthSenseRange;// * random( 1 - geneticHit );
bS = speedTypeSpeed;
}
float bSZ = map( bS * bSR, 0, speedTypeSpeed * senseTypeRange, 0, birthSize );
boolean gender;
if( random( 1 ) < femaleProbability ) gender = true;
else gender = false;
Entity ent = new Entity( location, bSZ, birthColor, strokeColor, mass, restitution, bS, birthForce, bSR, gender );
en.add( ent );
}
void mousePressed() {
if( en.size() < 100 && mouseButton == RIGHT ) {
addSperm();
}
if( mouseButton == LEFT ){
}
}
class Entity {
PVector location;
PVector velocity;
PVector acceleration;
float size;
color shade;
color strocke;
float mass;
float restitution;
float maxSpeed;
float maxForce;
float senseRange;
boolean gender;
float tailSwipe;
Entity( PVector location, float size, color shade, color strocke, float mass, float restitution, float maxSpeed, float maxForce, float senseRange, boolean gender ) {
this.location = location;
this.size = size;
this.shade = shade;
this.strocke = strocke;
this.mass = mass;
this.restitution = restitution;
this.maxSpeed = maxSpeed;
this.maxForce = maxForce;
this.senseRange = senseRange;
this.gender = gender;
velocity = new PVector();
acceleration = new PVector();
tailSwipe = 0;
}
void applyForce( PVector force ) {
acceleration.add( force );
}
void update() {
velocity.add( acceleration );
location.add( velocity );
edgeCheck();
acceleration.mult( 0 );
}
void steer( PVector target ) {
PVector desired = PVector.sub( target, location );
float desMag = desired.mag();
desired.normalize();
if( desMag < 100 ) {
float limiter = map( desMag, 0, 100, 0, maxSpeed );
desired.mult( limiter );
}
else {
desired.mult( maxSpeed );
}
PVector steeringForce = PVector.sub( desired, velocity );
steeringForce.limit(maxForce);
applyForce( steeringForce );
}
void edgeCheck() {
if ( location.x + size/2 > width) {
location.x = width - size/2;
velocity.mult( -( 1 - restitution) );
}
if ( location.y + size/2 > height) {
location.y = height - size/2;
velocity.mult( -( 1 - restitution) );
}
if( location.x - size/2 < 0 ) {
location.x = 0 + size/2;
velocity.mult( -( 1 - restitution) );
}
if( location.y - size/2 < 0 ) {
location.y = 0 + size/2;
velocity.mult( -( 1 - restitution) );
}
}
void deFlock( ArrayList<Entity> alEnt, float afLimit ) {
int count = 0;
PVector away = new PVector( );
for( Entity ent: alEnt ) {
if( PVector.dist( location, ent.location ) > 0 && PVector.dist( location, ent.location ) < size*1.1 ) {
PVector awayForce = PVector.sub( location, ent.location );
awayForce.normalize();
away.add( awayForce );
count ++ ;
}
}
if( count > 0 ) {
away.div( count );
away.setMag( maxSpeed );
PVector steerF = PVector.sub( away, velocity );
steerF.limit( afLimit );
applyForce( steerF );
}
}
void display() {
pushMatrix();
translate( location.x, location.y );
rotate( velocity.heading() + PI/2 );
fill( shade );
tail( new PVector( 0, 0 ), size * 0.7, PI/4 );
if( gender ) {
strokeWeight( 4 );
line( 0, 0, 0, -size * 0.7 );
}
strokeWeight( 1 );
ellipse( 0, 0, size*0.8, size );
if( senseRange > maxSpeed ) {
fill( 0 );
ellipse( 0, 0, size*0.1, size*0.1 );
}
popMatrix();
}
void displayRange() {
noStroke();
fill( 200, 50 );
pushMatrix();
translate( location.x, location.y );
ellipse( 0, 0, senseRange * 2, senseRange *2 );
popMatrix();
}
void tail( PVector location, float len, float deg) {
float vMag = velocity.mag();
float firstC = sin( tailSwipe ) * vMag * 2 ;
float secondC = sin( tailSwipe + PI ) *vMag * 1;
tailSwipe += 0.5;
pushMatrix();
translate( location.x, location.y );
stroke( 255 );
strokeWeight( 3 );
line( 0, 0, firstC, size );
translate( 0, size );
strokeWeight( 2 );
line( firstC, 0, secondC, size/2 );
translate( 0, size/2 );
strokeWeight( 1 );
line( secondC, 0, firstC, size/2 );
popMatrix();
}
}
Answers
Difficult to see what you mean because we don't have all the code to run your program.
Not very clear what you mean. If I was pulling an entity, I am controlling the direction it travels i.e. steering it?
It appears to be calculating a separation force to prevent entities from occupying the same space :-? perhaps you could explain what the steering force is expected to do.
It's the difference between a car having to make a turn to start going in the opposite direction and suddenly turning about its axis and then moving. As in, if a car is going west and it needs to go north, it is going to make a turn following a curved path, instead of suddenly turning 90 degrees on it's axis and then moving.
Also, I've added the main body of the code.
Still can't help because the code above doe not compile.
Line 64 fails because there is no displayRange() method. Line 106 fails because the parameters do not match the constructor
There maybe other errors but I stopped there.
Oops...sorry. I made some mods in a few places that didn't work with the original class. I'll show you the whole thing AS IS, just now...
Okay, updated the code. The whole thing should work or I'll eat my stinky shorts!
OK the program now works and I can see what you mean (so forget eating the shorts).
The solution is difficult to implement but easy to understand.
The first thing is to separate the entity's velocity and the direction it is facing. Start by adding a new PVector attribute called
facing
to the Entity class. At each update you calculate the new velocity vector in the same way as before but as it is most unlikely to match facing you ease the facing vector towards the new velocity vector finally normalising the facing vector to ensure it is of unit length.A small improvement can be made to calculating the new velocity vector. If the entity's velocity is below some threshold then use the facing vector instead of the velocity vector when calculating the new velocity.
When drawing the entity you use the facing vector for the rotation.
If you are seriously thinking about creating a large application using autonomous agents and steering behaviours you might consider learning to use the really excellent O:-) AI for 2D Games library which you can install through Processing's IDE, some of the examples require the G4P library to be installed as well.
HTH
Hey, thanks for this. While I won't need to implement the facing vector because I figured out what's wrong with the program, I still appreciate the help. It turns out my algorithm was actually error-free. The error lay in the maxForce I was allowing the entities. The turning force was too great and so the entitites turned too fast. I reduced the maxForce to really small values (starting with PI/180 ) and it worked.
But thanks, anyway :)
I will have a look at the library and get back to you with my thoughts.
Seriously, thank you for taking the time to help me out.