Commit 12fdc83b authored by Ant01n3's avatar Ant01n3

Merge branch 'master' of github.com:graphstream/gs-boids

parents 44f63c2c fbe65e39
......@@ -189,13 +189,13 @@
<dependency>
<groupId>org.graphstream</groupId>
<artifactId>gs-core</artifactId>
<version>1.1.1</version>
<version>1.2-git</version>
<optional>false</optional>
</dependency>
<dependency>
<groupId>org.graphstream</groupId>
<artifactId>gs-algo</artifactId>
<version>1.1</version>
<version>1.2-git</version>
<optional>false</optional>
</dependency>
</dependencies>
......
......@@ -33,9 +33,7 @@ import java.util.LinkedList;
import org.graphstream.graph.implementations.AbstractGraph;
import org.graphstream.graph.implementations.AdjacencyListNode;
import org.miv.pherd.Particle;
import org.miv.pherd.geom.Point3;
import org.miv.pherd.geom.Vector3;
/**
* Represents a single bird-oid object.
......@@ -62,7 +60,6 @@ import org.miv.pherd.geom.Vector3;
public class Boid extends AdjacencyListNode {
protected final BoidSpecies species;
protected BoidParticle particle;
/** Parameters of this group of boids. */
......@@ -80,23 +77,22 @@ public class Boid extends AdjacencyListNode {
public Boid(AbstractGraph graph, BoidSpecies species, String id) {
super(graph, id);
this.particle = new BoidParticle((BoidGraph) graph);
this.species = species;
this.forces = getDefaultForces();
this.forces = null;
}
/**
* Force the position of the boid in space.
*/
public void setPosition(double x, double y, double z) {
particle.setPosition(x, y, z);
forces.setPosition(x, y, z);
}
/**
* Actual position of the boid in space.
*/
public Point3 getPosition() {
return particle.getPosition();
return forces.getPosition();
}
/**
......@@ -106,23 +102,16 @@ public class Boid extends AdjacencyListNode {
return species;
}
/**
* The underlying particle of the force system this boids is linked to.
*/
public BoidParticle getParticle() {
return particle;
public void setForces(BoidForces forces) {
this.forces = forces;
}
/**
* The forces acting on the boids, this is a set of vectors and parameters
* computed at each time step.
*/
public BoidForces getDefaultForces() {
return new BoidForces.BasicForces();
public BoidForces getForces() {
return forces;
}
protected void checkNeighborhood(BoidParticle... particles) {
if (particles != null) {
public void checkNeighborhood(Boid... boids) {
if (boids != null) {
Iterator<Boid> it = getNeighborNodeIterator();
LinkedList<Boid> toRemove = null;
......@@ -130,14 +119,14 @@ public class Boid extends AdjacencyListNode {
boolean found = false;
Boid b = it.next();
for (BoidParticle p : particles) {
if (p.getId().equals(b.getParticle().getId())) {
for (Boid b2 : boids) {
if (b == b2) {
found = true;
break;
}
}
if (!found && !forces.isVisible(b.particle, this.getPosition())) {
if (!found && !forces.isVisible(b, this.getPosition())) {
if (toRemove == null)
toRemove = new LinkedList<Boid>();
......@@ -152,12 +141,14 @@ public class Boid extends AdjacencyListNode {
toRemove.clear();
toRemove = null;
}
for (BoidParticle p : particles) {
if (getEdgeBetween(p.getBoid().getId()) == null) {
getGraph().addEdge(getEdgeId(this, p.getBoid()), getId(),
p.getBoid().getId());
}
for (Boid b2 : boids) {
if (getEdgeBetween(b2) == null)
getGraph().addEdge(getEdgeId(this, b2), this, b2);
}
} else {
while (getDegree() > 0)
getGraph().removeEdge(getEdge(0));
}
}
......@@ -176,140 +167,4 @@ public class Boid extends AdjacencyListNode {
return String.format("%s--%s", b1.getId(), b2.getId());
}
/**
* Internal representation of the boid position, and direction in the forces
* system.
*
* @author Guilhelm Savin
* @author Antoine Dutot
*/
class BoidParticle extends Particle {
/**
* Direction of the boid.
*/
protected Vector3 dir;
/**
* Set of global parameters.
*/
protected BoidGraph ctx;
/**
* Number of boids in view at each step.
*/
protected int contacts = 0;
/**
* Number of boids of my group in view at each step.
*/
protected int mySpeciesContacts = 0;
/**
* New particle.
*
* @param ctx
* The set of global parameters.
*/
public BoidParticle(BoidGraph ctx) {
super(Boid.this.getId(), ctx.random.nextDouble() * (ctx.area * 2)
- ctx.area, ctx.random.nextDouble() * (ctx.area * 2)
- ctx.area, 0);
this.dir = new Vector3(ctx.random.nextDouble(), ctx.random
.nextDouble(), 0);
this.ctx = ctx;
}
@Override
public void move(int time) {
contacts = 0;
mySpeciesContacts = 0;
forces.compute(Boid.this, cell.getTree().getRootCell());
forces.direction.scalarMult(species.directionFactor);
forces.attraction.scalarMult(species.attractionFactor);
forces.repulsion.scalarMult(species.repulsionFactor);
dir.scalarMult(species.inertia);
dir.add(forces.direction);
dir.add(forces.attraction);
dir.add(forces.repulsion);
if (ctx.normalizeMode) {
double len = dir.normalize();
if (len <= species.minSpeed)
len = species.minSpeed;
else if (len >= species.maxSpeed)
len = species.maxSpeed;
dir.scalarMult(species.speedFactor * len);
} else {
dir.scalarMult(species.speedFactor);
}
if (ctx.storeForcesAttributes)
forces.store(this);
checkWalls();
nextPos.move(dir);
Boid.this.setAttribute("xyz", pos.x, pos.y, pos.z);
moved = true;
}
@Override
public void inserted() {
}
@Override
public void removed() {
}
public Boid getBoid() {
return Boid.this;
}
public void setPosition(double x, double y, double z) {
initPos(x, y, z);
}
/**
* Check the boid does not go out of the space walls.
*/
protected void checkWalls() {
// /float area = ctx.area;
float aarea = 0.000001f;
if (nextPos.x + dir.data[0] <= ctx.getSpace().getLoAnchor().x
+ aarea) {
nextPos.x = ctx.getSpace().getLoAnchor().x + aarea;
dir.data[0] = -dir.data[0];
} else if (nextPos.x + dir.data[0] >= ctx.getSpace().getHiAnchor().x
- aarea) {
nextPos.x = ctx.getSpace().getHiAnchor().x - aarea;
dir.data[0] = -dir.data[0];
}
if (nextPos.y + dir.data[1] <= ctx.getSpace().getLoAnchor().y
+ aarea) {
nextPos.y = ctx.getSpace().getLoAnchor().y + aarea;
dir.data[1] = -dir.data[1];
} else if (nextPos.y + dir.data[1] >= ctx.getSpace().getHiAnchor().y
- aarea) {
nextPos.y = ctx.getSpace().getHiAnchor().y - aarea;
dir.data[1] = -dir.data[1];
}
if (nextPos.z + dir.data[2] <= ctx.getSpace().getLoAnchor().z
+ aarea) {
nextPos.z = ctx.getSpace().getLoAnchor().z + aarea;
dir.data[2] = -dir.data[2];
} else if (nextPos.z + dir.data[2] >= ctx.getSpace().getHiAnchor().z
- aarea) {
nextPos.z = ctx.getSpace().getHiAnchor().z - aarea;
dir.data[2] = -dir.data[2];
}
}
}
}
\ No newline at end of file
......@@ -28,15 +28,10 @@
*/
package org.graphstream.boids;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Collection;
import org.graphstream.boids.Boid.BoidParticle;
import org.miv.pherd.Particle;
import org.miv.pherd.geom.Point3;
import org.miv.pherd.geom.Vector3;
import org.miv.pherd.ntree.Cell;
/**
* Models the forces applied to a boid at each step.
......@@ -84,16 +79,26 @@ public abstract class BoidForces {
*/
public int countRep;
protected Boid boid;
/**
* Direction of the boid.
*/
protected Vector3 dir;
/**
* Forces all set at zero.
*/
public BoidForces() {
public BoidForces(Boid b) {
barycenter = new Point3();
direction = new Vector3();
attraction = new Vector3();
repulsion = new Vector3();
countAtt = 0;
countRep = 0;
boid = b;
dir = new Vector3(((BoidGraph) b.getGraph()).getRandom().nextDouble(),
((BoidGraph) b.getGraph()).getRandom().nextDouble(), 0);
}
/**
......@@ -106,22 +111,66 @@ public abstract class BoidForces {
* @param startCell
* The start cell (usually the root cell of the n-tree).
*/
public abstract void compute(Boid source, Cell startCell);
public void compute() {
Collection<Boid> neigh;
BoidSpecies species = boid.getSpecies();
Vector3 dir = getDirection();
Vector3 rep = new Vector3();
Point3 nextPos = getNextPosition();
barycenter.set(0, 0, 0);
direction.fill(0);
attraction.fill(0);
repulsion.fill(0);
countAtt = 0;
countRep = 0;
/**
* Store each force vector as an attribute in the boid. This allows all
* listeners to retrieve the force vectors. This is particularly useful for
* the viewer for example. Each force is stored with the attribute name
* "force&lt;number&gt;" where &lt;number&gt; is a number that starts at
* zero.
*
* @param boid
* The boid to modify.
*/
protected void store(BoidParticle boid) {
boid.setAttribute("force0", direction);
boid.setAttribute("force1", attraction);
boid.setAttribute("force2", repulsion);
neigh = getNeighborhood();
for (Boid b : neigh) {
actionWithNeighboor(b, rep);
}
boid.checkNeighborhood(neigh.toArray(new Boid[neigh.size()]));
if (countAtt > 0) {
barycenter.scale(1f / countAtt, 1f / countAtt, 1f / countAtt);
direction.scalarDiv(countAtt);
attraction
.set(barycenter.x - boid.getPosition().x, barycenter.y
- boid.getPosition().y, barycenter.z
- boid.getPosition().z);
}
if (countRep > 0) {
repulsion.scalarDiv(countRep);
}
direction.scalarMult(species.getDirectionFactor());
attraction.scalarMult(species.getAttractionFactor());
repulsion.scalarMult(species.getRepulsionFactor());
dir.scalarMult(species.getInertia());
dir.add(direction);
dir.add(attraction);
dir.add(repulsion);
if (((BoidGraph) boid.getGraph()).isNormalizeMode()) {
double len = dir.normalize();
if (len <= species.getMinSpeed())
len = species.getMinSpeed();
else if (len >= species.getMaxSpeed())
len = species.getMaxSpeed();
dir.scalarMult(species.getSpeedFactor() * len);
} else {
dir.scalarMult(species.getSpeedFactor());
}
checkWalls();
nextPos.move(dir);
boid.setAttribute("xyz", nextPos.x, nextPos.y, nextPos.z);
}
/**
......@@ -156,264 +205,151 @@ public abstract class BoidForces {
}
/**
* Is the another point is space in the field of view?
* A boid particle p2 that is visible by p1 as been found, integrate it in
* the forces that apply to the boid p1.
*
* @param p1
* The source boid.
* @param b
* the boid visible by p1.
* @param rep
* The repulsion to compute.
*/
public abstract boolean isVisible(BoidParticle boid, Point3 other);
protected void actionWithNeighboor(Boid b, Vector3 rep) {
Point3 p1 = boid.getPosition();
Point3 p2 = b.getPosition();
BoidSpecies p1Species = boid.getSpecies();
BoidSpecies p2Species = b.getSpecies();
double v = boid.getSpecies().getViewZone();
rep.set(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z);
double len = rep.length();
if (len != 0) {
if (p1Species != p2Species)
rep.scalarMult(1 / (len * len) * p2Species.getFearFactor());
else
rep.scalarMult(1 / (len * len));
}
double a = Math.log(Math.min(len, v)) / Math.log(v);
rep.scalarMult(a);
repulsion.add(rep);
countRep++;
if (p1Species == p2Species) {
barycenter.move(p2);
direction.add(b.getForces().getDirection());
countAtt++;
}
}
/**
* A basic definition of forces for a boid.
* Check the boid does not go out of the space walls.
*/
protected void checkWalls() {
float aarea = 0.000001f;
Point3 lo = ((BoidGraph) boid.getGraph()).getLowAnchor();
Point3 hi = ((BoidGraph) boid.getGraph()).getHighAnchor();
Point3 nextPos = getNextPosition();
if (nextPos.x + dir.data[0] <= lo.x + aarea) {
nextPos.x = lo.x + aarea;
dir.data[0] = -dir.data[0];
} else if (nextPos.x + dir.data[0] >= hi.x - aarea) {
nextPos.x = hi.x - aarea;
dir.data[0] = -dir.data[0];
}
if (nextPos.y + dir.data[1] <= lo.y + aarea) {
nextPos.y = lo.y + aarea;
dir.data[1] = -dir.data[1];
} else if (nextPos.y + dir.data[1] >= hi.y - aarea) {
nextPos.y = hi.y - aarea;
dir.data[1] = -dir.data[1];
}
if (nextPos.z + dir.data[2] <= lo.z + aarea) {
nextPos.z = lo.z + aarea;
dir.data[2] = -dir.data[2];
} else if (nextPos.z + dir.data[2] >= hi.z - aarea) {
nextPos.z = hi.z - aarea;
dir.data[2] = -dir.data[2];
}
}
/**
* True if the given position is visible by the boid.
*
* <p>
* The kind of forces exercising on a boid can be changed to either use a
* n-tree or not, or to account for other kind of forces or another force
* model. This is the default force system that matches the basic boid
* definition as defined by Craig Reynolds.
* This method first check if the given point is under the max distance of
* view. If so, it checks if the point is in the angle of view. The angle of
* view is specified as the cosine of the angle between the boid direction
* vector and the vector between the boid and the given point. This means
* that -1 is equal to a 360 degree of vision (the angle of view test is
* deactivated in this case), 0 means 180 degree angle, and 0.5 a 90 degree
* angle for example.
* </p>
*
* @author Guilhelm Savin
* @author Antoine Dutot
* @param source
* The source boid.
* @param point
* The point to consider.
*
* @return True if point is visible by source.
*/
public static class BasicForces extends BoidForces {
@Override
public void compute(Boid source, Cell startCell) {
barycenter.set(0, 0, 0);
direction.fill(0);
attraction.fill(0);
repulsion.fill(0);
countAtt = 0;
countRep = 0;
Set<BoidParticle> contacts = new HashSet<BoidParticle>();
exploreTree(source, startCell, contacts);
source.checkNeighborhood(contacts.toArray(new BoidParticle[contacts
.size()]));
if (countAtt > 0) {
barycenter.scale(1f / countAtt, 1f / countAtt, 1f / countAtt);
direction.scalarDiv(countAtt);
attraction.set(barycenter.x - source.getPosition().x,
barycenter.y - source.getPosition().y, barycenter.z
- source.getPosition().z);
}
public boolean isVisible(Boid boid, Point3 point) {
//
// Check both the distance and angle of view according to the
// direction
// of the source.
//
BoidSpecies species = boid.getSpecies();
Point3 pos = boid.getPosition();
double d = pos.distance(point);
// At good distance.
if (d <= species.getViewZone()) {
//
// If there is an angle of view.
//
if (species.getAngleOfView() > -1) {
double angle;
Vector3 dir = new Vector3(boid.getForces().getDirection());
Vector3 light = new Vector3(point.x - pos.x, point.y - pos.y,
point.z - pos.z);
if (countRep > 0) {
repulsion.scalarDiv(countRep);
}
}
dir.normalize();
light.normalize();
/**
* Recursively explore the n-tree to search for intersection cells, and
* the visible boids.
*
* @param source
* The boid the forces are computed on.
* @param cell
* The cell to explore recursively.
*/
protected void exploreTree(Boid source, Cell cell,
Set<BoidParticle> contacts) {
if (intersection(source, cell)) {
if (cell.isLeaf()) {
forcesFromCell(source.getParticle(), cell, contacts);
} else {
int n = cell.getSpace().getDivisions();
for (int i = 0; i < n; ++i)
exploreTree(source, cell.getSub(i), contacts);
}
}
}
angle = dir.dotProduct(light);
/**
* A leaf cell has been found that is in intersection with the boid
* area, computes the forces from this cell.
*
* @param source
* The boid the forces are computed on.
* @param cell
* The cell.
*/
protected void forcesFromCell(BoidParticle source, Cell cell,
Set<BoidParticle> contacts) {
// BoidCellData data = (BoidCellData) cell.getData();
Iterator<? extends Particle> particles = cell.getParticles();
Vector3 rep = new Vector3();
// LinkedList<BoidParticle> contacts = null;
while (particles.hasNext()) {
Particle particle = particles.next();
if (particle instanceof BoidParticle) {
// if (contacts == null)
// contacts = new LinkedList<BoidParticle>();
if (source != particle
&& isVisible(source, particle.getPosition())) {
contacts.add((BoidParticle) particle);
actionWithNeighboor(source, (BoidParticle) particle,
rep);
}
}
//
// In the field of view.
//
if (angle > species.getAngleOfView())
return true;
} else {
return true;
}
// barycenter.move( data.getCenter() );
// direction.add( data.getDirection() );
}
/**
* A rectangular intersection function, is the boid view area
* intersecting the given cell?. This provides a quick lookup function
* to test if a cell must be explored or not. Later, a better test
* according to a spherical view zone will be done.
*
* @param cell
* The cell to test for intersection with the boid