I got half-way through what I wanted in the representation of physics vector fields in 2D with p5js here. The other half is to get random particles to dynamically follow the forces of the vector field, and I am having a lot of problems with it. I have tried multiple things to take into account the wrap-around of the particles, as well as the fact that I am translating the origin of the plot to the center of the canvas. However, the particles seem minimally affected by the individual vectors in the field, and ultimately march along the x axis with slight bumpiness.
The fact that I am completely new at JS doesn't help splice all these elements from several presentations available online, and I would appreciate any advise as to what may be going wrong, and where I should focus on.
Here is what I have so far: a file sketch.js
corresponding to my own answer quoted above:
scl = 35;
var cols,rows;
var fr;
var particles = [];
var flowfield;
function setup() {
createCanvas(windowWidth, windowHeight);
cols = floor(width/scl);
rows = floor(height/scl);
fr = createP("");
flowfield = new Array(cols * rows);
for (var i = 0; i < 1000; i++) {
particles[i] = new Particle();
}
background(51);
}
function draw() {
translate(height/2, height/2); //moves the origin to bottom left
scale(1, -1); //flips the y values so y increases "up"
background(255);
loadPixels();
for (var y = -rows; y < rows; y++) {
for (var x = - cols; x < cols; x++) {
var index = x + y * cols;
//var v = createVector(sin(x)+cos(y),sin(x)*cos(y));
var v = createVector(y,-x);
flowfield[index] = v;
fill('blue');
stroke('blue');
push();
translate(x*scl,y*scl);
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();
}
}
for (var i = 0; i < particles.length; i++) {
particles[i].follow(flowfield);
particles[i].update();
particles[i].edges();
particles[i].show();
}
}
and a second file called particle.js
:
class Particle {
constructor() {
this.pos = createVector(random(-width,width),
random(-height,height));
this.vel = createVector(0, 0);
this.acc = createVector(0, 0);
this.maxspeed = 4;
this.prevPos = this.pos.copy();
this.size = 8;
}
update() {
this.vel.add(this.acc);
this.vel.limit(this.maxspeed);
this.pos.add(this.vel);
this.acc.mult(0);
}
follow(vectors) {
var x = floor(this.pos.x / scl);
var y = floor(this.pos.y / scl);
var index = x + y * cols;
var force = vectors[index];
this.applyForce(force);
}
applyForce(force) {
this.acc.add(force);
}
show() {
noStroke();
fill('rgba(100,0,255,.5)');
circle(-(this.pos.x+width/2), -(this.pos.y-height/2), this.size);
this.updatePrev();
}
updatePrev() {
this.prevPos.x = this.pos.x;
this.prevPos.y = this.pos.y;
}
edges() {
if (this.pos.x > width) {
this.pos.x = -width;
this.updatePrev();
}
if (this.pos.x < -width) {
this.pos.x = width;
this.updatePrev();
}
if (this.pos.y > height) {
this.pos.y = -height;
this.updatePrev();
}
if (this.pos.y == -height) {
this.pos.y = height;
this.updatePrev();
}
}
}
The beginning of the simulation with the updated code on this edit is not bad:
but soon enough all particles align with the last row along the x axis. So I guess I need some help understanding flow fields or scaling down the effect of the vectors at the bottom.
Ethan Hermsey did solve this plotting problem for me perfectly. At this point, and undoubtfully due to either some glitch in the code, or some miscommunication, the code in the accepted answer happens to actually result in a different output to that desired in asking the question, and the code that Ethan himself solved for me. So just for reference, this is the effect intended:
Generated as follows:
const scl = 35;
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"
background( 255 );
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();
fill( 'red' );
stroke( 'red' );
translate(x*scl-width/2,y*scl-height/2);
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 = 15;
this.prevPos = this.pos.copy();
this.size = 8;
}
update() {
this.vel.add( this.acc );
this.vel.limit( this.maxspeed );
this.pos.add( this.vel );
this.acc.mult( 0 );
}
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( 'rgba(100,0,255,.5)' );
// you can just draw on the position.
circle( this.pos.x, this.pos.y, this.size );
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();
}
}
}