Animation of a point moving under attraction and repulsion using 'p5.Vector'


I would like to define the force acting between two points as a vector and write an animation that moves in response to that force using p5.js.

 

👉 この記事の日本語版がこちらにあります。

 

Animation using 'p5.Vector'

So, here is what I made.

The red dot is in a uniform circular motion. If the black dot is far away from the red dot, it will receive an attractive force, and if it is close, it will receive a repulsive force.

This is not gravity. What I am defining here is an imaginary physical law. There is no friction, no inertia, and no acceleration. Furthermore, the red dot does not receive any force from the black dot. This is an unimaginable, otherworldly movement in which the laws of the real world physics do not apply.

The values of attraction and repulsion are the magnitude of the force, including negative values. The direction in which the force acts is the direction of the line connecting the red dot and the black dot. Attraction is from the black dot to the red dot, and repulsion is the opposite.

Vectors have not only the magnitude of force but also the direction, so you can use 'p5.Vector' to neatly translate this physics law into code.

 

Explanation of example code

Here is the p5.js code that draws the animation above.



/** 
 * Animation of a point moving under attraction and repulsion using 'p5.Vector'
 * When only one point receives force
 * 
 * @author @deconbatch
 * @version 0.1
 * @license GPL3
 * p5.js 1.6.0
 * created 2023.02.15
 */

const w = 480;
const h = w;
const pullFix = 20;
const pushFix = 30;
const baseDist = w * 0.25;
const moverR = w * 0.2;
const moverT = 0.1;

let node;

function setup() {
  createCanvas(w, h);
  frameRate(24);
  initial();
}

function draw() {
  // Calculating the position of a moving reference point
  const mover = createVector(
    w * 0.5 + moverR * cos(moverT * frameCount),
    h * 0.5 + moverR * sin(moverT * frameCount)
  );

  // Calculating the position of the point receiving the force
  let d = (p5.Vector.dist(mover, node) - baseDist) / baseDist;
  let g = (d < 0) ? d * pushFix : d * pullFix;
  let t = p5.Vector.sub(mover, node).heading();
  node.add(
    g * cos(t),
    g * sin(t),
    -node.z + (d + 0.5) * w * 0.1
  );

  // draw points
  background(192);
  fill(192, 0, 0);
  circle(mover.x, mover.y, 10);

  fill(16);
  circle(node.x, node.y, node.z);

}

// initialize
function initial() {
  frameCount = 0;
  node = createVector(
    random(w),
    random(h),
    0.0
  );
  noStroke();
}

// Redo with mouse click
function mouseClicked() {
  initial();
}
  


Explanation of variables

The variable 'mover' represents a red dot moving in a uniform circle, which is the basis for attraction and repulsion.



 const mover = createVector(
   w * 0.5 + moverR * cos(moverT * frameCount),
   h * 0.5 + moverR * sin(moverT * frameCount)
 );
  


The following two constants determine the radius of the mover's circular motion and its rotation speed.



 const moverR = w * 0.2;
 const moverT = 0.1;
  


The variable 'node' represents a black dot that moves under attraction and repulsion.



 const pullFix = 20;
 const pushFix = 30;
 const baseDist = w * 0.25;


Please think of the following three constants as physical constants in the world of this code. Changing the value will change the behavior of the black dot.



 const pullFix = 20;
 const pushFix = 30;
 const baseDist = w * 0.25;


 

Calculation of the force

'p5.Vector.dist()' is a method of 'p5.Vector' that calculates the distance between two p5.Vectors.

In the formula below, if the distance between the 'mover' and 'node' is closer than the reference distance (baseDist), the repulsive force (d * pushFix) is set to variable 'g', and if it is farther, the attractive force (d * pullFix) is set to variable 'g'.



 let d = (p5.Vector.dist(mover, node) - baseDist) / baseDist;
 let g = (d < 0) ? d * pushFix : d * pullFix;



I named it 'g' even though it's not gravity...

 

The formula below is the part that calculates the direction in which the force acts.



 let t = p5.Vector.sub(mover, node).heading();


'p5.Vector.sub()' is a method that subtracts two p5.Vectors, and in the case of the above expression, it returns a new vector obtained by subtracting 'node' from 'mover'. The value being return is a vector, which means it has a direction. Its direction is the direction of the 'mover' as seen from the 'node'.

'p5.Vector.heading()' is a method that returns the direction of the vector, and the above formula sets the angle of the mover direction as seen from the node to the variable 't'.

 

Drawing

Using the formula below, add the calculated force magnitude and direction to the node position x, y.



 node.add(
   g * cos(t),
   g * sin(t),



However, what is the process for the z part of the node in the code below?



   -node.z + (d + 0.5) * w * 0.1


p5.Vector can represent a three-dimensional vector, so it has x, y, and z attributes, but this time, I treated it as a two-dimensional vector, and z held the value of the size of the black dot.

 

The code below looks like a three-dimensional circle, but node.x and node.y represent a normal circle with coordinates and node.z the size.



 circle(node.x, node.y, node.z);


I'm not impressed with this usage. It's misleading to the person reading the code. ;-P

 

Play with the code

In the example code, 'background(192)' in 'draw()' is used to fill in each time. If we move this to 'initial()', we can see that the black dots are actually regular movements.
In the video below, white dots are used for filming reasons.


In the example below, we can draw an interesting shape by leaving a trail.


By changing the constant values, the example code will draw a shape like this.


 

I thought it would be interesting to have multiple nodes instead of just one. However, it was fine when I tried 2 or 3 nodes, but when I increased it to 10 or 20, it wasn't fun at all.


As the number of nodes increased, it became obvious that they were just moving the same distance from the red dot, and I became less interested.

 

Conclusion

It's fun to set up fictitious physical laws and imagine what kind of behavior those laws will produce, and it's also fun to write code based on those laws and try to make them work.

'p5.Vector' is a useful class for describing vectors suitable for representing motion and force. It includes a number of methods needed to calculate vectors, so you can easily translate the laws of physics into code.

Why not create a world with your own physics laws using p5.Vector? You can be a creator!

 

Previous Post
No Comment
Add Comment
comment url