4

I want to turn this halting, discontinuous trails in the particle on this simulation

enter image description here

to something worth staring at as in this beautiful field flow in here (not my work, but I don't remember where I got it from).

I have tried different permutations of the code in the accomplished field flow without getting anything remotely close to the smoothness in the transitions that I was aiming for. I suspect I am mishandling the updates or the placement of the black rectangle that seems to circumvent the need for a black background, which would erase the wake of the particles.

const scl = 45;
var cols, rows;
var particles = [];
var flowfield;

function setup() {

    createCanvas(750, 750);
    cols = ceil( width / scl );
    rows = ceil( height / scl );


    flowfield = new Array( cols * rows );

    for (var i = 0; i < 1000; i ++ ) {
        particles[i] = new Particle();
    }
}

function draw() {
    
    translate(height / 2, height / 2); //moves the origin to center
    scale( 1, - 1 ); //flips the y values so y increases "up"

    rect(-width,-height,2*width,2*height);
    for ( var y = 0; y < rows; y ++ ) { 
        for ( var x = 0; x < cols; x ++ ) { 
      
      var index = x + y * cols;

      let vX = x * 2 - cols;
      let vY = y * 2 - rows;
                
     
      var v = createVector( vY, -vX );
      v.normalize();
          
      flowfield[index] = v;
      
      // The following push() / pull() affects only the arrows     
      push();
      translate(x*scl-width/2,y*scl-height/2);

      fill(255);
      stroke(255);
      rotate(v.heading());
      line(0,0,0.5*scl,0);
      let arrowSize = 7;
      translate(0.5*scl - arrowSize, 0);
      triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
      pop();
// The preceding push() / pull() affects only the arrows     
    }// Closes inner loop
  }// Closes outer loop to create vectors and index.
  
//This next loop actually creates the desired particles:
    for (var i = 0; i < particles.length; i++) {
    particles[i].follow(flowfield);
    particles[i].update();
    particles[i].edges();
    particles[i].show();
  }
} // End of the function draw

class Particle {

    constructor() {


        // changed startpostion. Since the origin is in the center of the canvas,
        // the x goes from -width/2 to width/2
        // the y goes from -height/2 to height/2
        // i also changed this in this.edges().

        this.pos = createVector( random( - width / 2, width / 2 ),
            random( - height / 2, height / 2 ) );
        this.vel = createVector( 0, 0 );
        this.acc = createVector( 0, 0 );
        this.maxspeed = 4;
        this.steerStrength = 30;
        this.prevPos = this.pos.copy();
        this.size = 2;

    }

    update() {

        this.vel.add( this.acc );
        this.vel.limit( this.maxspeed );
        this.pos.add( this.vel );
        this.acc.mult( 0 );
        fill(255)
        circle( this.pos.x, this.pos.y, this.size );
      

    }

    follow( vectors ) {

        var x = floor( map( this.pos.x, - width / 2, width / 2, 0, cols - 1, true ) );
        var y = floor( map( this.pos.y, - height / 2, height / 2, 0, rows - 1, true ) );
        var index = ( y * cols ) + x;

        var force = vectors[ index ].copy();
        force.mult( this.steerStrength );
        this.applyForce( force );

    }

    applyForce( force ) {

        this.acc.add( force );

    }

    show() {


        noStroke();
        fill(0,5)
        // you can just draw on the position.


        this.updatePrev();


    }

    updatePrev() {

        this.prevPos.x = this.pos.x;
        this.prevPos.y = this.pos.y;

    }

    edges() {

        //clamp between -width/2 and width/2. -height/2 and height/2
        if ( this.pos.x > width / 2 ) {

            this.pos.x = - width / 2;
            this.updatePrev();

        }
        if ( this.pos.x < - width / 2 ) {

            this.pos.x = width / 2;
            this.updatePrev();

        }
        if ( this.pos.y > height / 2 ) {

            this.pos.y = - height / 2;
            this.updatePrev();

        }
        if ( this.pos.y < - height / 2 ) {

            this.pos.y = height / 2;
            this.updatePrev();

        }
    }
}

As a follow-up, I went down to the basics to be able to blend more properly the code in the simulation linked below with the initial code, incorporating the ideas in the accepted answer. I reduced the this.maxspeed = 3 (just by 1). I got rid of the unnecessary (and probably conflicting show() and update() within the class Particle, leaving only update() as in the example provided in the OP.

Here it is for comparison:

const scl = 45
var cols, rows;
var particles = [];
var flowfield;

function setup() {

    createCanvas(750, 750);
    cols = ceil( width / scl );
    rows = ceil( height / scl );


    flowfield = new Array( cols * rows );

    for (var i = 0; i < 1000; i ++ ) {
        particles[i] = new Particle();
    }
}

function draw() {
    
    translate(height / 2, height / 2); //moves the origin to center
    scale(1, -1); //flips the y values so y increases "up"
  
  
      fill(0, 10);
      rect(-width, -height, 2*width, 2*height );


    for ( var y = 0; y < rows; y ++ ) { 
        for ( var x = 0; x < cols; x ++ ) { 
      
      var index = x + y * cols;

      let vX = x * 2 - cols;
      let vY = y * 2 - rows;

     
      var v = createVector( vY, -vX );
      v.normalize();
          
      flowfield[index] = v;
      
      // The following push() / pull() affects only the arrows     
      push();
      translate(x*scl-width/2,y*scl-height/2);

      fill(255);
      stroke(255);
      rotate(v.heading());
      line(0,0,0.5*scl,0);
      let arrowSize = 7;
      translate(0.5*scl - arrowSize, 0);
      triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
      pop();
  // The preceding push() / pull() affects only the arrows     
    }// Closes inner loop
  }// Closes outer loop to create vectors and index.
  
//This next loop actually creates the desired particles:
    for (var i = 0; i < particles.length; i++) {
    particles[i].follow(flowfield);
    particles[i].update();
    particles[i].edges();
  }
} // End of the function draw

class Particle {

    constructor() {

        this.pos = createVector( random( - width / 2, width / 2 ),
            random( - height / 2, height / 2 ) );
        this.vel = createVector( 0, 0 );
        this.acc = createVector( 0, 0 );
        this.maxspeed = 3;
        this.steerStrength = 30;
        this.prevPos = this.pos.copy();
        this.size = 4;

    }

    update() {

        this.vel.add( this.acc );
        this.vel.limit( this.maxspeed );
        this.pos.add( this.vel );
        this.acc.mult( 0 );
        noStroke();
        fill(255)
        circle(this.pos.x, this.pos.y, this.size); 
    }

    follow(vectors) {
        var x = floor( map( this.pos.x, - width / 2, width / 2, 0, cols - 1, true ) );
        var y = floor( map( this.pos.y, - height / 2, height / 2, 0, rows - 1, true ) );
        var index = (y * cols) + x;
        var force = vectors[index].copy();
        force.mult(this.steerStrength);
        this.applyForce(force);
    }

    applyForce( force ) {
        this.acc.add(force);
    }


    updatePrev() {
        this.prevPos.x = this.pos.x;
        this.prevPos.y = this.pos.y;
    }

    edges() {
        //clamp between -width/2 and width/2. -height/2 and height/2
        if ( this.pos.x > width / 2 ) {

            this.pos.x = - width / 2;
            this.updatePrev();

        }
        if ( this.pos.x < - width / 2 ) {

            this.pos.x = width / 2;
            this.updatePrev();

        }
        if ( this.pos.y > height / 2 ) {

            this.pos.y = - height / 2;
            this.updatePrev();

        }
        if ( this.pos.y < - height / 2 ) {

            this.pos.y = height / 2;
            this.updatePrev();

        }
    }
}

I finally settled for a couple of illustrations with different pull exerted by the vector field on the particles: this.steerStrength = 0.1; (loose particles) versus this.steerStrength = 30; (clinging to vectors):

Loose pull (more flashy):

enter image description here

Code here.

More compact or faithful to the guiding vectors:

enter image description here

with code here.

JAP
  • 405
  • 2
  • 11

2 Answers2

1

You can get trials in multiple ways. The sketch you mentioned creates the trails by adding opacity to the background with the "fill( 0, 10 )" function.

If you want to know more about p5 functions you can always look them up here: https://p5js.org/reference/. The fill() page shows that the first argument is the color ( 0 for black ) and the second argument is the opacity ( 10 out of 255 ).

In the sketch you mentioned, in draw(), they wrote:

fill( 0, 10 );
noStroke();
rect( 0, 0, width, height );

It draws a semi transparent black rectangle over the canvas, but you can also use:

background( 0, 10 );

In the particle class you can draw the particle with the color you want, for example:

fill( 255 ); //white color
circle( this.pos.x, this.pos.y, this.size ); 

It's still not as smooth as the sketch with the maxSpeed set to 4, if you lower that to 2 for example it will look better already.

I see there's also a prevPos in your code. That's another way to draw trails; connect the this.prevPos and this.pos with a line. it's common to have an array of previous positions to connect that way.

There's only one catch, when the particles go offscreen and are places on the other side, there's a line going from one side of the canvas to the other. You can work around this, but the transparent background is easier.

Ethan Hermsey
  • 930
  • 4
  • 11
  • Thank you Ethan! I think that to some extent I have already shamelessly copied and pasted the chunks of code you suggest from the pretty simulation I link to in the original post. My problem is that I don't understand how the different parts and functions interact with each other, and I wonder if the way I used them in the code pasted in the OP is the same way you are suggesting. – JAP Jan 11 '22 at 18:05
  • Are you sure you updated the sketch? The particle.show function is empty and I'm not seeing the fill(0, 10) or background(0, 10) anywhere... – Ethan Hermsey Jan 11 '22 at 18:46
  • You can just show the particles as you did before, without transparency. In the draw function, replace the background() you had before with the background(0, 10). – Ethan Hermsey Jan 11 '22 at 19:03
  • I did use these lines of code initially, copying from the referenced flow field, but they seemed to conflict or just not render the intended simulation - the issue I have is that I don't understand the hierarchical order of these commands. – JAP Jan 11 '22 at 20:54
  • For instance, this would be an less commendable rendition than my attempt in the OP when I try and use your suggestions (obviously not inserting them on the right spots - my fault; not yours): https://editor.p5js.org/Mathcurious/sketches/TaiNWHrAI – JAP Jan 11 '22 at 21:04
  • Can you show me what the code in the OP would look like with your suggested modifications implemented correctly? – JAP Jan 11 '22 at 22:10
  • Yes ofcourse. I always try to let the other party puzzle it out for a bit, but if it's really not working out then I can show you :) https://editor.p5js.org/EthanHermsey/sketches/t9LESc7A2 Check the beginning of the draw function and inside the particle.show :) – Ethan Hermsey Jan 11 '22 at 22:41
  • Well, your technique certainly worked :-), forcing me to learn the basics of plotting basic geometrical shapes on p5.js - the very basics. And here is what I was about to post using more properly your tips, and blending more seamlessly the code in the OP and that of the nice simulation of flow fields: https://editor.p5js.org/Mathcurious/sketches/TaiNWHrAI – JAP Jan 11 '22 at 22:48
0

You can use:

background(255, 10)

which: 255 is the number for color; 10 is the alpha color (opacity);

Oscar Nguyen
  • 43
  • 1
  • 9