如何平滑 p5js 模拟中的粒子轨迹

How to smooth out the trails of the particles in a p5js simulation

我想在 this simulation

上转动粒子中这个停止的、不连续的轨迹

here 中这个美丽的田野中值得一看的东西(不是我的作品,但我不记得我从哪里得到的)。

我已经在已完成的字段流中尝试了不同的代码排列,但没有得到任何与我想要的过渡平滑度相差甚远的东西。我怀疑我对更新或黑色矩形的放置处理不当,这似乎避免了对黑色背景的需要,这会消除粒子的尾迹。

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();

        }
    }
}

作为后续行动,我深入了解了基础知识,以便能够将下面链接的模拟中的代码与初始代码更恰当地融合在一起,并将这些想法纳入已接受的答案中。我减少了 this.maxspeed = 3(仅减少 1)。我摆脱了 class Particle 中不必要的(并且可能冲突的 show()update(),只留下 update(),如 OP 中提供的示例。

这里是为了比较:

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();

        }
    }
}

我最终选择了几个插图,其中向量场对粒子施加了不同的拉力:this.steerStrength = 0.1;(松散粒子)与 this.steerStrength = 30;(紧贴向量):

松拉(更浮华):

代码here.

更紧凑或更忠实于引导向量:

使用代码 here

您可以通过多种方式获得试用。您提到的草图通过使用“填充(0、10)”功能向背景添加不透明度来创建轨迹。

如果您想了解更多关于 p5 函数的信息,您可以随时在此处查找它们:https://p5js.org/reference/。 fill() 页面显示第一个参数是颜色(0 表示黑色),第二个参数是不透明度(255 中的 10)。

在你提到的草图中,在 draw() 中,他们写道:

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

它在 canvas 上绘制了一个半透明的黑色矩形,但您也可以使用:

background( 0, 10 );

在粒子class中你可以画出你想要的颜色的粒子,例如:

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

它仍然不如将 maxSpeed 设置为 4 的草图平滑,例如,如果将其降低到 2,它看起来已经更好了。

我看到您的代码中还有一个 prevPos。这是另一种绘制轨迹的方法;用一条线连接 this.prevPos 和 this.pos。通常有一系列以前的位置以这种方式连接。

只有一个问题,当粒子离开屏幕并位于另一侧时,有一条线从 canvas 的一侧延伸到另一侧。您可以解决此问题,但透明背景更容易。

您可以使用:

background(255, 10)

其中: 255是颜色的数字; 10 是 alpha 颜色(不透明度);