# Help thinking out an attraction behavior

Hello.
I’m wondering how, or if, I could make this idea happens. I’m have some code already, it’s based on @shiffman’s wonderful Nature of Code vectors and attractions chapters. Thanks Daniel, you are amazing! But not posting code, cause I’m after the logic yet.

Anyway I would like to have an ‘attractor’ that attracts some ‘movers’, but then, when they get close they stop organizing around the atractor. They should not overlap, instead (perhaps they repel each other) they kind of organize around the attractor. See the picture.

Is this insanely complicated? Don’t need code, just the thinking of how would this be acomplished. If this is to much for my league (a jr one : ) i can just set the spots for the movers to go to, and that will also work, but I’d like to have the representation I’m making based on forces… (I’m making - trying to make - a representation of brazilian congress - attractors would be parties and movers deputies)
any thoughts?

thanks

*around - sorry

Ok. Here goes some code:
This is getting the attracted to the same place of the attractor (in this pic it still moving veeeery slowly), but that last mile where they arrange them selfs…

live in p5 editor

The code.

``````let m,a;
let c;
let force;

function setup() {
createCanvas(900, 500);
c = color(200, 200, 170);
m = new Mover(0, height / 2, 150, c);
a = new Attractor(width / 2, height /2, 80);
force = createVector(0, 0, 1);
}

function draw() {
background(255, 155, 255);
m.log(20, 450)
const f = a.attract(m);
m.applyForce(f);
m.update();
m.display();

a.display();

}

class Mover {
constructor(x, y, m, c) {
this.pos = createVector(x, y, 1);
this.mass = m;
this.vel = createVector(0, 0);
this.acc = createVector(0, 0);
this.col = c || color(0, 150);
this.lastAcc = createVector(0, 0);

}

applyForce(force) {
// a copy so force vector is not changed and can be further used in other instances
const f = force.copy();

f.div(this.mass);
}

update() {
const fr = p5.Vector.mult(this.vel, -1);
fr.setMag(0.02);
this.lastAcc.set(this.acc);
//clear acc each frame
this.acc.mult(0);
}

display() {
push();
fill(110);
noStroke()
circle(this.pos.x, this.pos.y, this.mass);
pop();
}

log(x, y) {
const data = `pos= .x_\${nf(this.pos.x, 0, 2)}, .y_\${nf(this.pos.y, 0, 2)}\nvel=  .x_\${nf(this.vel.x, 0, 2)}, .y_\${nf(this.vel.y, 0, 2)}\nacc= .x_\${nf(this.lastAcc.x, 0, 2)}, .y_\${nf(this.lastAcc.y, 0, 2)}\n`
textSize(17);
text(data, x, y)
this.lastAcc.mult(0);
}

} // <== eof mover class

class Attractor {
constructor(x, y, m, c) {
this.pos = createVector(x, y, 1);
this.mass = m;
}

attract(mover){
let force = p5.Vector.sub(this.pos, mover.pos);
const dist = force.mag();
const strength = dist/10;
force.setMag(strength);
return (force);
}

display() {
push();
fill(110,50,60);
noStroke()
circle(this.pos.x, this.pos.y, this.mass);
pop();
}

} //<== eof attractor
``````

This video https://www.youtube.com/watch?v=xiUpAeos168 (not mine) gives a quite nice algorithm for a more general artificial life simulation with a mix of attraction and repulsion behaviors. You could hard-code some of the parameters to get the behavior you are looking for.

2 Likes

tanks, gonna look. ; )

Great link, watching now. Thanks again

I’m wondering if you could help me with this particular (no pun : ) aspect of the code. His `update()` code directly accsses the global `ArrayList`, `swarm`, inside one of it’s methods. I thought that would not be a good practice, but indeed it makes things easier. Should I worry about this approach?

As I gonna have some unique particles, I’ll need to have an array (i’m in `js`) holding only one? Well not really sure how i will do things yet, but this makes a worse modularity? Right? (not a rethorical ‘right?’ : )

Given that the swarm interactions is the whole point of the app, having it as a global variable makes perfect sense and simplifies the rest of the code. If you are bothered by the reference to a global, just pass in the swarm as a parameter to the particle `update()` and then it’s no longer a global. It’s “cleaner” but slower so unless you really have some reason to care, I wouldn’t bother.

I made a similar sketch many years ago where I also used a global list of entities and a 2-D grid for quicker neighbor detection. https://codepen.io/scudly/pen/NmoQWq The rules in that youtube video are closer to the behaviors you described, though.

2 Likes

Ok, got it. Thanks again. Very nice sketch

@scudly Thanks a lot. The particles of life nailed it totally.

I’m goint to integrate this in the rest of my code.
I’ll leave here an example of what I cooked so far. It’s a fun stuff to play with, xhanging forces and limits cool stuff… I made a very controlled one.

live in p5.editor

it’s a little oppressesd in the half a page of the editor though…

also the code is not very clean yet.

``````let bodies = []
let cnv;
let force;
const K = 0.5;
const friction = 0.5;
let bodiesNumber = 200;
let pressedX, pressedY;
let c;

let forces = [
[0, 0],
[5, -9]
]

let minDist = [
[-1, -1],
[100, 10]
]
let maxDist = [
[-1, 0],
[2000, 100]
]

function setup() {
cnv = createCanvas(900, 900);
C = color(100, 40, 60);

for (var i = 0; i < bodiesNumber; i++) {
// bodies.push(new Body(random(180,220), random(180,220), 30, c));
bodies.push(new Body(random(200, 700), random(100, 700), 30, c));
}
bodies[0].mass = 80;
bodies[0].type = 0;
}

function draw() {
background(185, 175, 185);
// translate(width/2 , height/2 );
// scale(scrollOff/200);
for (const b of bodies) {
b.display();
b.update();
}
}

function mouseDragged() {
bodies[0].pos.x = mouseX;
bodies[0].pos.y = mouseY;
}

class Body {

constructor(x, y, m, c) {
this.pos = createVector(x, y, 1);
this.mass = m;
this.vel = createVector(0, 0);
this.strength = 0.2;
this.minDist = this.mass * 1.5; // <===  experiences
this.maxDist = 100;
this.type = 1;
this.color1 = color(110, 50, 60, 50)
this.color2 = color(210, 150, 160, 50)
this.color3 = color(11, 5, 6, 50)
this.dispColor = this.color1;

}

update() {
let dir = createVector(0, 0);
let totalForce = createVector(0, 0);
let acc = createVector(0, 0);
let dist = 0;

for (const body of bodies) {
if (body !== this) {
this.maxDist = maxDist[this.type][body.type];
this.minDist = minDist[this.type][body.type]
// console.log(body)
//clear for this particle
dir.mult(0);

//copy to keep from messing origina value
dir = body.pos.copy();

// get dir to other
dir.sub(this.pos);

//store distance before normalizing
dist = dir.mag();

//normalize
dir.normalize();

//repel based on dist
if (dist < minDist[this.type][body.type]) {
this.dispColor = this.color2
// don't mess with dir
const force = dir.copy();

// an arbitrary value - in the example we had a table with a unique
// value for each combination. Let's see what i'll need...
force.mult(forces[this.type][body.type] * -10); // negative => repel

//map dist to positive 0~1 and multiply
const mappedD = abs(map(dist, 0, this.minDist, 1, 0)); // <== note 1 e 0  not 0 e 1
force.mult(mappedD);

// a constant to scale down the forces 0.5 in the example
force.mult(K)

//accumulate all the forces of all other particles interacting with this one
}
if (dist < maxDist[this.type][body.type]) {
this.dispColor = this.color3;
// don't mess with dir
const force = dir.copy();

// an arbitrary value - in the example we had a table with a unique
// value for each combination. Let's see what i'll need...
force.mult(forces[this.type][body.type]);

//map dist to positive 0~1 and multiply
const mappedD = abs(map(dist, 0, maxDist[this.type][body.type], 1, 0)); // <== note 1 e 0  not 0 e 1
force.mult(mappedD);

// a constant to scale down the forces 0.5 in the example
force.mult(K)

//accumulate all the forces of all other particles interacting with this one
}
}
}
acc.add(totalForce); // if mass totalForce/this.mass
this.vel.mult(friction);
}

display() {
push();
noStroke()
fill(255, 30);
circle(this.pos.x, this.pos.y, this.maxDist)
stroke(255, 0, 0, 50);
fill(255, 250, 250, 20);
circle(this.pos.x, this.pos.y, this.minDist)
fill(this.dispColor);
fill(this.dispColor);
circle(this.pos.x, this.pos.y, this.mass);

// if (this.type === 1) {
//     image(img, this.pos.x, this.pos.y);
//     textSize(7)
//     text('Nome Filhadaputa', this.pos.x -25, this.pos.y +30);
// } else {
//     fill(this.dispColor);
//     noStroke()
//     circle(this.pos.x, this.pos.y, this.mass);
// }
pop();
}

} //<== eof attractor

``````

1 Like