Simulating Custom Physics: Attraction and Repulsion with p5.Vector

article title

In this post, we’ll explore how to define forces between particles as vectors and animate their interactions using p5.js. Specifically, we'll create a system where a particle responds to the pull and push of a moving reference point.

 

[Japanese version / 日本語版はこちら]

 

 

Exploring Abstract Motion

Here is the result of the simulation.

red and black particles moving animation

The red particle moves in a uniform circular motion. The black particle reacts to it: if it’s far away, it feels an attractive force; if it gets too close, it’s repelled.

This isn't gravity. It's an imaginary physical law. There is no friction, inertia, or acceleration here. Furthermore, the interaction is one-way—the red particle is completely unaffected by the black one. It’s a purely mathematical playground where real-world physics don't 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;


 

Calculating the Forces

Using p5.Vector.dist(), we calculate the distance between the two particles.

If the distance is less than our threshold (baseDist), we apply a repulsive force (pushFix). If it's greater, we apply an attractive force (pullFix).



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


The direction of the force is determined by the vector pointing from the node to the mover.

p5.Vector.sub(mover, node) returns a vector representing the direction of the mover as seen from the node. By calling .heading(), we extract the angle required to move the node toward (or away from) the reference point.

 

A Non-standard Use of the Z-Axis

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


While p5.Vector is designed for 3D coordinates (x, y, z), I’ve used the z attribute here to store the size of the particle.

 

In the code, circle(node.x, node.y, node.z) looks like a 3D operation, but it's simply a shortcut for a 2D circle with a dynamic radius.



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


It’s a bit of a "hacky" implementation—misleading, perhaps, but effective for this experiment!

 

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 dot's movement follows a consistent pattern.
In the video below, white dots are used for filming reasons.

The black dot's movement follows a consistent pattern.

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

10 arms putting out from the center

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

4 arms putting out from the center

 

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 lost its visual appeal.

4 particles animation

As the number of nodes increased, it became obvious that they were just moving the same distance from the red dot, and he results became predictable.

Over 10 particles animation

 

Conclusion: Becoming a Creator of Laws

Designing fictitious physical laws is one of the joys of creative coding. It allows you to visualize behaviors that couldn't exist in reality.

p5.Vector is an essential tool for this, providing all the methods necessary to translate abstract logic into fluid motion. Why settle for real-world physics when you can invent your own?

 

Next Post Previous Post
No Comment
Add Comment
comment url