Tutorials (back to the list of tutorials)

## Making A Custom Agent (Reproduction Base) (requires iGeo version 0.8.1.8 or higher)

### Reproduction Based Agent

This tutorial page shows a process to design a custom agent from scratch. There are two major types of agent as the following.
• Reproduction Based Agent
• Particle Based Agent
A reproduction based agent doesn't move by itself but it reproduces child agent(s) in adjacent locations. When one parent agent reproduces multiple child agents, it propagates and the algorithm is equivalent to a branching algorithm. An agent creates child agents only once in the first time frame. Most of agents on the tutorial codes in "Multi-Agents" section are reproduction based agents. Usually, remaining body geometries of agents becomes result geometries of the agent system.

On the other hand, a particle based agent moves by itself. The movement follows the physical rules in Newton's law as described on the tutorial page about particles. Particles move around following forces applied to them. The result of the system are created as trajectories or some geometries built on the course of particle movement and their interaction.

This page shows examples to build a custom reproduction based agent. As the starting point, the following code defines a simple reproduction based agent with two point properties with IVec variables. The update method creates one line with the two points as geometry representation of an agent. It also contains a rule to reproduce one child agent in the same direction at the end point of the parent agent.

```import igeo.*;
import processing.opengl.*;

void setup() {
size(480, 360, IG.GL);
new MyAgent(IG.v(), IG.v(0,1,0));
}

class MyAgent extends IAgent {
IVec pt1, pt2;

MyAgent(IVec pt, IVec dir) {
pt1 = pt;
pt2 = pt.cp(dir);
}

void update() {
if (time()==0) {
new ICurve(pt1, pt2).clr(0); //geometry representation
IVec dir = pt2.dif(pt1);
new MyAgent(pt2, dir); //reproduce
}
}
}
```

### Transformation Rule in Update Method

The next code adds a transformation rule in the update method to control reproduction process by changing the direction of a child agent. In the example it simply rotates the direction of child agents and slightly shrink the vector decreasing the length of an agent.

```import igeo.*;
import processing.opengl.*;

void setup() {
size(480, 360, IG.GL);
new MyAgent(IG.v(), IG.v(0,1,0));
}

class MyAgent extends IAgent {
IVec pt1, pt2;

MyAgent(IVec pt, IVec dir) {
pt1 = pt;
pt2 = pt.cp(dir);
}

void update() {
if (time()==0) {
new ICurve(pt1, pt2).clr(0);
IVec dir = pt2.dif(pt1);
dir.rot(PI/100); // transformation rule 1: rotate
dir.mul(0.999); // transformation rule 2: scale down
new MyAgent(pt2, dir);
}
}
}
```

### Branching Rule in Update Method

In the next step, it adds a branching rule. In the update method, one more agent is created in the different direction from the first child agent by rotating the direction on the opposite way.

```import igeo.*;
import processing.opengl.*;

void setup() {
size(480, 360, IG.GL);
new MyAgent(IG.v(), IG.v(0,1,0));
}

class MyAgent extends IAgent {
IVec pt1, pt2;

MyAgent(IVec pt, IVec dir) {
pt1 = pt;
pt2 = pt.cp(dir);
}

void update() {
if (time()==0) {
new ICurve(pt1, pt2).clr(0);
IVec dir = pt2.dif(pt1);
dir.rot(PI/100);
dir.mul(0.999);
new MyAgent(pt2, dir); // branch 1

IVec dir2 = pt2.dif(pt1);
dir2.rot(-PI/10);
new MyAgent(pt2, dir2); // branch 2
}
}
}
```

You can see that the result shows too many agents and if you run the code it gets very slow quickly because it generates exponential number of agents every time frame. This means creating two agents every time frame is too much. There are many ways to control number of child agents. The following code is one example to control branching by random number. It creates the second child agent only in 3% probability.

```import igeo.*;
import processing.opengl.*;

void setup() {
size(480, 360, IG.GL);
new MyAgent(IG.v(), IG.v(0,1,0));
}

class MyAgent extends IAgent {
IVec pt1, pt2;

MyAgent(IVec pt, IVec dir) {
pt1 = pt;
pt2 = pt.cp(dir);
}

void update() {
if (time()==0) {
new ICurve(pt1, pt2).clr(0);
IVec dir = pt2.dif(pt1);
dir.rot(PI/100);
dir.mul(0.999);
new MyAgent(pt2, dir); // branch 1

if( IRand.pct(3) ){ // 3% probability
IVec dir2 = pt2.dif(pt1);
dir2.rot(-PI/10);
new MyAgent(pt2, dir2); // branch 2
}
}
}
}
```

### Collision Detection Rule in Interact Method

The next rule to add is a collision detection rule. Collision detection is about checking relationship between one agent and another. When one agent needs to check other agent, the rule is described inside the interact method in a agent class. There are several ways to detect collisions but the following example calculates intersection of two line segments to determine if they are colliding or not. If two agents find an intersection, it means two lines of agents are colliding. One exception is that a child agent always touches with its parent agent. Because of this, you need to exclude the case when an intersection happens exactly at a start point (pt1) by adding the following boolean statement in the if-condition.

!intxn.eq(pt1) // intersection and pt1 are not at the same location

```import igeo.*;
import processing.opengl.*;

void setup() {
size(480, 360, IG.GL);
new MyAgent(IG.v(), IG.v(0,1,0));
}

class MyAgent extends IAgent {
IVec pt1, pt2;

MyAgent(IVec pt, IVec dir) {
pt1 = pt;
pt2 = pt.cp(dir);
}

void interact(ArrayList agents){
if(time()==0){
for(int i=0; i < agents.size() && alive(); i++){
if(agents.get(i) instanceof MyAgent){
MyAgent a = (MyAgent)agents.get(i);
if(a!=this){
IVec intxn = IVec.intersectSegment(a.pt1, a.pt2, pt1, pt2);
if(intxn!=null && !intxn.eq(pt1)){
del();
}
}
}
}
}
}

void update() {
if (time()==0) {
new ICurve(pt1, pt2).clr(0);
IVec dir = pt2.dif(pt1);
dir.rot(PI/100);
dir.mul(0.999);
new MyAgent(pt2, dir); // branch 1

if( IRand.pct(3) ){ // 3% probability
IVec dir2 = pt2.dif(pt1);
dir2.rot(-PI/10);
new MyAgent(pt2, dir2); // branch 2
}
}
}
}
```

### Adding A Property to Agent 1

To develop a custom behavior, you sometimes need to add a variable to an agent class as property of the class to have a certain state or to remember something. This state variable can be set and used only inside one agent or passed to child agents to propagate the state information. To pass information from a parent agent to child agents, you need to add an input argument of the constructor of the agent class to initialize the field. In the example code below, the agent class adds a boolean property isStraight to define a state to change agent's behavior in the update method. Declaration of a new property is done by the following line.

boolean isStraight;

The constructor of MyAgent class adds another input argument of boolean variable straight as the line below.

MyAgent(IVec pt, IVec dir, boolean straight){

Then the new agent instance property of isStraight is initialized by this input argument straight. In the update method, this property is used to differentiate the agent behavior. When isStraight is true, it doesn't apply the transformation rule to bend the direction and it only creates one child agent. If false, it applys the original behavior to the agent with the transformation rule and the branching rule. This property isStraight is passed through the child agents as well to keep the state. The update method contains another rule to toggle this boolean state randomly in low probability as the following line.

if(IRand.pct(0.8)){ isStraight = !isStraight; } // toggle boolean switch

Then isStraight is fed to child agents to pass the current state of the parent agent.

```import igeo.*;
import processing.opengl.*;

void setup() {
size(480, 360, IG.GL);
new MyAgent(IG.v(), IG.v(0,1,0), true);
}

class MyAgent extends IAgent {
IVec pt1, pt2;
boolean isStraight;

MyAgent(IVec pt, IVec dir, boolean straight) {
pt1 = pt;
pt2 = pt.cp(dir);
isStraight = straight;
}

void interact(ArrayList agents){
if(time()==0){
for(int i=0; i < agents.size() && alive(); i++){
if(agents.get(i) instanceof MyAgent){
MyAgent a = (MyAgent)agents.get(i);
if(a!=this){
IVec intxn = IVec.intersectSegment(a.pt1, a.pt2, pt1, pt2);
if(intxn!=null && !intxn.eq(pt1)){
del();
}
}
}
}
}
}

void update() {
if (time()==0) {
new ICurve(pt1, pt2).clr(0);
IVec dir = pt2.dif(pt1);
if(IRand.pct(0.8)){ isStraight = !isStraight; } //toggle boolean switch
if(isStraight){
new MyAgent(pt2, dir, isStraight);
}
else{
dir.rot(PI/100);
dir.mul(0.999);
new MyAgent(pt2, dir, isStraight);

if( IRand.pct(3) ){
IVec dir2 = pt2.dif(pt1);
dir2.rot(-PI/10);
new MyAgent(pt2, dir2, isStraight);
}
}
}
}
}
```

### Adding A Property to Agent 2

The following example code adds another property to the agent class. It gets a double precision floating point number for the agent's bending angle.

double angle;

The example codes above so far had a constant number for the bending angle. Now introducing the angle property makes it possible to change the bending angle gradiently through branching and propagation of agents. Inside the update method, the angle is sometimes randomly increased or decreased and also flipped to positive or negative number to flip the bending direction.

```import igeo.*;
import processing.opengl.*;

void setup() {
size(480, 360, IG.GL);
new MyAgent(IG.v(), IG.v(0,1,0), true, 0);
}

class MyAgent extends IAgent {
IVec pt1, pt2;
boolean isStraight;
double angle;

MyAgent(IVec pt, IVec dir, boolean straight, double ang) {
pt1 = pt;
pt2 = pt.cp(dir);
isStraight = straight;
angle = ang;
}

void interact(ArrayList agents){
if(time()==0){
for(int i=0; i < agents.size() && alive(); i++){
if(agents.get(i) instanceof MyAgent){
MyAgent a = (MyAgent)agents.get(i);
if(a!=this){
IVec intxn = IVec.intersectSegment(a.pt1, a.pt2, pt1, pt2);
if(intxn!=null && !intxn.eq(pt1)){
del();
}
}
}
}
}
}

void update() {
if (time()==0) {
new ICurve(pt1, pt2).clr(0);
IVec dir = pt2.dif(pt1);
if(IRand.pct(0.8)){ isStraight = !isStraight; }
if(isStraight){
new MyAgent(pt2, dir, isStraight, angle);
}
else{
dir.rot(angle);
dir.mul(0.999);
if(IRand.pct(1)){
angle += IRand.get(-0.05, 0.05);
angle = -angle; // flip bending direction
}
new MyAgent(pt2, dir, isStraight, angle);

if( IRand.pct(3) ){
IVec dir2 = pt2.dif(pt1);
dir2.rot(-PI/10);
new MyAgent(pt2, dir2, isStraight, angle);
}
}
}
}
}
```

### Other Agent Classes to Interact 1

The code below describes an example of a reproduction based agent interacting with another agent class. A new class Attractor is added to the code. Attractor class simply contains one position vector and MyAgent class checks all existing Attractor instances inside its interact method. It searches the closest Attractor and sets it to target property. Then inside update method, it switches the direction of rotation closer to the target in both cases of isStraight is true and false.

```import igeo.*;
import processing.opengl.*;

void setup() {
size(480, 360, IG.GL);
new MyAgent(IG.v(), IG.v(0,1,0), true, 0);
for(int i=0; i < 10; i++){
new Attractor(IRand.pt(-200,200,200,400)).clr(1.0,0,0);
}
}

class MyAgent extends IAgent {
IVec pt1, pt2;
boolean isStraight;
double angle;
Attractor target;

MyAgent(IVec pt, IVec dir, boolean straight, double ang) {
pt1 = pt;
pt2 = pt.cp(dir);
isStraight = straight;
angle = ang;
target = null;
}

void interact(ArrayList agents){
if(time()==0){
double minDist=-1;
for(int i=0; i < agents.size() && alive(); i++){
if(agents.get(i) instanceof MyAgent){
MyAgent a = (MyAgent)agents.get(i);
if(a!=this){
IVec intxn = IVec.intersectSegment(a.pt1, a.pt2, pt1, pt2);
if(intxn!=null && !intxn.eq(pt1)){
del();
}
}
}
else if (agents.get(i) instanceof Attractor) {
Attractor attr = (Attractor)agents.get(i);
// find the closest attractor in front
double dist = attr.pos.dist(pt2);
if(minDist < 0){ // first time to check Attractor
minDist = dist;
target = attr;
}
else if(dist < minDist){
minDist = dist;
target = attr;
}
}
}
}
}

void update() {
if (time()==0) {
new ICurve(pt1, pt2).clr(0);
IVec dir = pt2.dif(pt1);
if(IRand.pct(0.8)){ isStraight = !isStraight; } // switch
if(isStraight){
if(target!=null){
IVec targetDir = target.pos.dif(pt2);
if(dir.cp().rot(angle).angle(targetDir) >
dir.cp().rot(-angle).angle(targetDir)){
angle = -angle; // angle closer to target
}
dir.rot(angle); // rotate toward target
}
new MyAgent(pt2, dir, isStraight, angle); // branch 1
}
else{
if(IRand.pct(1)){
angle += IRand.get(-0.05, 0.05);
angle = -angle;
}
if(target!=null){
IVec targetDir = target.pos.dif(pt2);
if(dir.cp().rot(angle).angle(targetDir) >
dir.cp().rot(-angle).angle(targetDir)){
angle = -angle; // angle closer to target
}
dir.rot(angle); // rotate toward target
dir.mul(0.999);
new MyAgent(pt2, dir, isStraight, angle); // branch 1
}
else{
dir.rot(angle);
dir.mul(0.999);
if(IRand.pct(99)) new MyAgent(pt2, dir, isStraight, angle); // branch 1
}

if( IRand.pct(3) ){ // 3% probability
IVec dir2 = pt2.dif(pt1);
dir2.rot(-PI/10);
new MyAgent(pt2, dir2, isStraight, angle); // branch 2
}
}
}
}
}

class Attractor extends IAgent{
IVec pos;

Attractor(IVec p) {
pos = p;
}

void update(){
if(time()==0) new IPoint(pos).clr(clr());
}
}
```

### Other Agent Classes to Interact 2

The next tutorial code adds another class called BoundaryAgent. This new agent class contains one closed curve as a 2D boundary. This time, the property isStraight in MyAgent is controlled by BoundaryAgent instead of by itself. When an instance of MyAgent is inside BoundaryAgent, it turns isStraight false and otherwise true. As a result of this behavior, branching and bending happens only inside boundaries and when MyAgent is outside of the boundaries, it just go straight. The initial locations of agents, attractors and boundaries are set by an input file. The following is the sample input file used in the code.

```import igeo.*;
import processing.opengl.*;

void setup() {
size(480, 360, IG.GL);
IG.open("agent_lines1.3dm");  // input file to initialize agents
ICurve[] roots = IG.layer("root").curves();
ICurve[] bounds = IG.layer("boundary").curves();
IPoint[] attractors = IG.layer("attractor").points();

for(int i=0; i < roots.length; i++){
new MyAgent(roots[i].start(), roots[i].end().dif(roots[i].start()), 0);
}
for(int i=0; i < attractors.length; i++){
new Attractor(attractors[i].pos()).clr(IRand.clr());
attractors[i].del();
}
for(int i=0; i < bounds.length; i++){
new BoundaryAgent(bounds[i]);
}
IG.bg(0);
}

class MyAgent extends IAgent {
IVec pt1, pt2;
boolean isStraight=true;
double angle;
Attractor target;

MyAgent(IVec pt, IVec dir, double ang) {
pt1 = pt;
pt2 = pt.cp(dir);
angle = ang;
target = null;
}

void interact(ArrayList agents){
if(time()==0){
double minDist=-1;
for(int i=0; i < agents.size() && alive(); i++){
if(agents.get(i) instanceof MyAgent){
MyAgent a = (MyAgent)agents.get(i);
if(a!=this){
IVec intxn = IVec.intersectSegment(a.pt1, a.pt2, pt1, pt2);
if(intxn!=null && !intxn.eq(pt1)){
del();
}
}
}
else if (agents.get(i) instanceof Attractor) {
Attractor attr = (Attractor)agents.get(i);
// find the closest attractor in front
double dist = attr.pos.dist(pt2);
if(minDist < 0){ // first time to check Attractor
minDist = dist;
target = attr;
}
else if(dist < minDist){
minDist = dist;
target = attr;
}
}
}
}
}

void update() {
if (time()==0) {
new ICurve(pt1, pt2).clr(clr().cp());
IVec dir = pt2.dif(pt1);
if(isStraight){ // just go straight
if(target!=null){
clr().blend(target.clr(),0.02); // blend 2% color
}
new MyAgent(pt2, dir, angle).clr(clr().cp()); // branch 1
}
else{
if(IRand.pct(1)){
angle += IRand.get(-0.05, 0.05);
angle = -angle;
}
if(target!=null){
IVec targetDir = target.pos.dif(pt2);
if(dir.cp().rot(angle).angle(targetDir) >
dir.cp().rot(-angle).angle(targetDir)){
angle = -angle; // angle closer to target
}
dir.rot(angle); // rotate toward target
dir.mul(0.999);
clr().blend(target.clr(),0.02); // blend 2% color
new MyAgent(pt2, dir, angle).clr(clr().cp()); // branch 1
}
else{
dir.rot(angle);
dir.mul(0.999);
if(IRand.pct(100)) new MyAgent(pt2, dir, angle).clr(clr().cp()); // branch 1
}

if( IRand.pct(3) ){ // 3% probability
IVec dir2 = pt2.dif(pt1);
dir2.rot(-PI/10);
new MyAgent(pt2, dir2, angle).clr(clr().cp()); // branch 2
}
}
}
}
}

class Attractor extends IAgent{
IVec pos;
Attractor(IVec p) {
pos = p;
}
void update(){
if(time()==0) new IPoint(pos).clr(clr()).clr(clr());
}
}

class BoundaryAgent extends IAgent{
ICurve boundary;
BoundaryAgent(ICurve crv){
boundary = crv;
}
void interact(ArrayList < IDynamics > agents){
for(int i=0; i < agents.size(); i++){
if(agents.get(i) instanceof MyAgent){
MyAgent agent = (MyAgent)agents.get(i);
if(agent.time()==0){ // check only what's just created
if(boundary.isInside2d(agent.pt2)){ // check if it's inside
agent.isStraight=false;
}
}
}
}
}
}
```

(back to the list of tutorials)