Particle text animations in p5js

I want to make my text consisting of smaller particles to make it look like the words are formed by a shoal of fish (like in the movie Finding Nemo if you remember that scene). So far, I have managed to use steering behavior to make the particles move and form shapes. But I need help with a few things:

  1. How can I change the shape of a point (which is also a vehicle) to another shape say, a square. Or how can i replace the shape with an image?

  2. Once the points have reached their target positions, how can i keep them moving in place as if they are alive?

  3. currently, the points only sit on the outside of the word. How do i make the points fill it up?

  4. How do I transition from one word to another?

1 Like

It would be a lot easier to answer, if we knew your Code. As for how that would be done :

  1. I don‘t know what you mean by shape of a point…

  2. You can change their position by using something like this :

position.set(position.x + noise(frameCount/speed,0) * offset, position.y + noise(0,frameCount/speed) * offset);
// speed = inverse of the velocity (200 is slower than 10)
// offset = Max distance to the actual position they should be in
  1. I don‘t know your Code, so i can‘t help with that… might want to distribute additional points within the shape?

  2. Again, i don’t know how your Code is even forming the word in the first place. (How the particles decide in what direction to steer)

1 Like

Hi Lexyth, thank you for your reply.
In the code below, I have created the text by splitting the font into points using the font.textToPoints() function. The points appear as circles on the borders of the word. I wanted to know how to make the points look like something else, say as triangles or if I want to make those points coincide with images.


var font;
var vehicles= [];

function preload(){
 font = loadFont ('AvenirNextLTPro-Demi.otf');

function setup(){
 createCanvas(900, 900);
  // textSize(192);
  // fill(255);
  // noStroke();
  // text('SWIMMY',100,250);
  var points = font.textToPoints('SWIMMY',100,250,192);
  for (var i = 0; i<points.length;i++){
    var pt = points[i];
    // stroke(255);
    // strokeWeight(10);
    // point(pt.x,pt.y);
    var vehicle = new Vehicle(pt.x , pt.y);
  translate(pt.x, pt.y);
  rect(pt.x, pt.y, 82, 3);
  rotate(frameCount / 200.0); 


function draw(){
  for (var i = 0; i< vehicles.length; i++){ //noprotect
  var v = vehicles[i];

tab 2:

function Vehicle(x,y){ //cunstroctor function to make vehicle ojects
 this.pos = createVector(random(width),random(height)); = createVector(x,y);
 //this.vel = createVector();
 this.vel = p5.Vector.random2D();
 this.acc = createVector();
  //each vehicles obj needs a position, a target position, a velocity and an acceleration
  this.maxspeed = 10; 
  this.maxforce = 1;

Vehicle.prototype.behaviours = function(){ 
 var arrive = this.arrive(; 
 var mouse = createVector(mouseX,mouseY);
  var flee = this.flee(mouse);
  //fleep force is much more forcefull than the arrive force

Vehicle.prototype.applyForce = function(f){
this.acc.add(f); //so if multiple forces are at play, we could add all of them in the acceleration and hence all the animations must start by 0

Vehicle.prototype.update = function(){ //way of attaching functions to objects
  //basic physics idea of acc changing velocity and velocity changing acceleration
  this.acc.mult(0); //this multiplies everything by 0 so that everything starts from o for the applyForce function to work
} = function() {
   point(this.pos.x,this.pos.y); //linking the vehicle to particular points and their characteristics  

Vehicle.prototype.arrive = function(target){
 var desired = p5.Vector.sub (target, this.pos); 
  var d = desired.mag();
  var speed = this.maxspeed;
  if (d<100){
  speed = map(d, 0, 100, 0, this.maxspeed);
  var steer = p5.Vector.sub(desired, this.vel);
  return steer; 
} = function(target){
 var desired = p5.Vector.sub (target, this.pos); //this line is subtracting the vector points from position to target, you subtract the two vectors 
  //desired vel is always its aximum speed
  var steer = p5.Vector.sub(desired, this.vel);
  return steer; //the idea is to calculate that force and return it to be applied in seek

Vehicle.prototype.flee = function(target){
 var desired = p5.Vector.sub (target, this.pos); 
  var d = desired.mag();
  if (d<50){
  var steer = p5.Vector.sub(desired, this.vel);
  return steer; //the idea is to calculate that force and return it to be applied in seek
  return createVector(0,0);
1 Like

To change the shape of the points, you can simply go to the function and change the point function to another one… like rect() one maybe triangle() and just set the calculation for the respective vertices correctly.

point(x, y);
rect(x-5, y-5, 10, 10); //so it‘s centered on x and y
triangle(x-5, y+5, x+5, y+5, x, y-5);// bottom left, bottom right and top center (you might need to change the order)

As for the problem with points sitting on the border, that has to do with how textToPoints() gives out the points… i don‘t know if there‘s a function/way to give out points within too… else you might have to calculate them Manualy, which might be quite a hassle…

To change from one word to another, just set a second target for your particles that you got from the textToPoint function of another word. Like :

vehicles[i].target = createVector(newX, newY);
//newX and newY are the points of the new word.
// Also, you need to check if the nes point[] is larger than vehicles. 
// If so, then add more vehicles, else, make the target of the remaining vehicles... maybe on positions on an ellipse around the word?
1 Like

Thank you very much again Lexyth :sunny:
I will definitely give this a try. I hope you have a great day.

1 Like

The mathematical way to sample whether a point falls within your shape is hard, because letters are concave and have cutouts. The collision detection math is unpleasant.

An easier way is to use a picking buffer. Draw your shape (“E”) onto a black and white buffer. Then walk across the buffer pixels and sample them at regular intervals (e.g. if you want your fish spaced 10x10 pixels, sample every 10th line and check every 10th pixel. If a pixel is a hit, add a point to your array of fish.

1 Like