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);
        this.acc.add(f);
    }

    update() {
        const fr = p5.Vector.mult(this.vel, -1);
        fr.setMag(0.02);
        this.acc.add(fr)
        this.vel.add(this.acc);
        this.vel.add(this.fr);
        this.pos.add(this.vel);
        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 :wink:

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 :slight_smile:

@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
                    totalForce.add(force);
                }
                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
                    totalForce.add(force);
                }
            }
        }
        acc.add(totalForce); // if mass totalForce/this.mass
        this.vel.add(acc);
        this.pos.add(this.vel);
        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