Processing Code Remix: The Joy of Modifying Existing Sketches

title image

This is a modified version of a published Processing sketch where particles appear to autonomously sort themselves into color groups. Below, I’ll share the source code and the key insights from my creative process.

 

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


Original Processing Code for Modification

This example was created by modifying the code that ひさだん(@hisadan) san had published on Twitter.


I was captivated by its organic, autonomous movement, which inspired me to create my own version.

 

Key Points of Reading Source Code in Creative Coding Way

First, I refactored the minified source code from the original tweet to make it more readable.



/**
 * Ref. https://x.com/hisadan/status/1913976946687422638
 */

int i, s, n = 999;
float a[] = new float[n * 2];
float x, y, d, r, e, f;

void setup() {
  size(800,800);
  colorMode(HSB);
  background(0);

  // locate the particles randomly
  // a[i] as x axis, a[i + n] as y axis
  for (i = 0; i < n; i++) {
    d = random(250);
    r = random(TAU);
    a[i] = d * sin(r);
    a[i + n] = d * cos(r);
  }
}

void draw() {
  // translucent for afterimage
  fill(0,2);
  square(0, 0, 800);
  
  // calculation for particle move
  for(i = 0; i < n; i++) {
    for(s = 0; s < n; s++) {
      x = 0;
      y = 0;
      d = dist(a[i], a[i + n], a[s], a[s + n]);
      e = (a[s] - a[i]) / d / d;
      f = (a[s + n] - a[i + n]) / d / d;

      if (i != s) {
        if (i % 2 == s % 2) {
          x += d > 100 ? -e : e;
          y += d > 100 ? -f : f;
        } else {
          x += d > 50 ? e : -e;
          y += d > 50 ? f : -f;
        }
      }

      a[i] += x;
      a[i + n] += y;

    }

    // color with distance from center of sketch
    stroke(mag(a[i], a[i + n]), 255, 255);
    point(a[i] + 400, a[i + n] + 400);
  }
}  

	    

I've written some comments on source code.

A basic grasp of the logic is enough to get started. You’ll naturally understand the finer details as you begin to experiment and tweak the values.

 

Key Points for Creative Coding Style Modifications

Reading code all day can be tedious, so let’s dive straight into the 'creative coding' way—start tweaking things first, and let the understanding follow through experimentation.

When making modifications, if you start with simple changes such as changing initial values, you will smoothly understand how the program works. By the time you move on to more difficult modifications such as changing movements, you will be able to see what the key parts of the code are and where the appeal is created.

 

Try to Change the Number of Particles


int i, s, n = 999;
	    

Try changing the number of particles from 999 to something extreme, like 10,000 or 10, and see how the behavior shifts.

 

Try to Change the Way to Draw


  // translucent for afterimage
  fill(0,2);
  square(0, 0, 800);
	    

Try opaque or transparent.


    // color with distance from center of sketch
    stroke(mag(a[i], a[i + n]), 255, 255);
    point(a[i] + 400, a[i + n] + 400);
	    

For example.

 

Try to Change the Moving of Particles


      d = dist(a[i], a[i + n], a[s], a[s + n]);
      e = (a[s] - a[i]) / d / d;
      f = (a[s + n] - a[i + n]) / d / d;
	    

This is one of the most important points because it is the part that determines the amount of change in position. It's both a rewarding and challenging aspect of the modification.


        if (i % 2 == s % 2) {
          x += d > 100 ? -e : e;
          y += d > 100 ? -f : f;
        } else {
          x += d > 50 ? e : -e;
          y += d > 50 ? f : -f;
        }

	    

This is a section where the logic is clear, yet the resulting behavior is wonderfully unpredictable.

So, I guess it must be a key point of this work!

Example modification of this part.

 

Processing Code of Modified Work

Here is the Processing code of my modified work.


/**
 * Processing Example Code: particles seemingly autonomously sort themselves into different color clusters.
 * Ref. https://x.com/hisadan/status/1913976946687422638
 *
 * @author @deconbatch
 * @version 0.1
 * @license CC0 1.0 https://creativecommons.org/publicdomain/zero/1.0/deed.ja
 * Processing 4.3.3
 * 2025.05.22
 */

void setup() {

  int   frmMax   = 24 * 10; // 24fps x 10sec
  int   pNumMax  = 1800;
  int   pClass   = 6;
  float pIniRad  = 80.0;
  float pDistMax = 220.0;
  float distMag  = 12.0;
  float baseHue  = random(120.0, 300.0);
  
  PVector curP[] = new PVector[pNumMax];
  PVector prvP[] = new PVector[pNumMax];

  size(720, 720);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);

  for(int pCnt = 0; pCnt < pNumMax; pCnt++){
    float r = random(pIniRad);
    float t = random(TAU);
    curP[pCnt] = new PVector(r * cos(t), r * sin(t));
    prvP[pCnt] = new PVector(curP[pCnt].x, curP[pCnt].y);
  }
  
  translate(width * 0.5, height * 0.5);
  rectMode(CENTER);
  blendMode(BLEND);
  background(baseHue, 40.0, 10.0, 100.0);

  // iterate for the number of animation frames
  for (int frm = 0; frm < frmMax; frm++) {

    // for afterimage
    blendMode(BLEND);
    fill(baseHue, 40.0, 10.0, 20.0);
    noStroke();
    rect(0.0, 0.0, width, height);

    // calc position of points
    for (int mCnt = 0; mCnt < pNumMax; mCnt++) {
      for (int oCnt = 0; oCnt < pNumMax; oCnt++) {
        if (mCnt != oCnt) {

          float pDist = 1.0 + PVector.dist(prvP[mCnt], prvP[oCnt]);
          PVector pDiv = PVector.div(PVector.mult(PVector.sub(prvP[mCnt], prvP[oCnt]), distMag), pow(pDist, 2));

          if (mCnt % pClass == oCnt % pClass) {
            curP[mCnt].add(pDiv.mult((pDistMax - pDist) / pDistMax));
          } else {
            if (pDist > pDistMax) {
              curP[mCnt].sub(pDiv);
            } else {
              curP[mCnt].add(pDiv);
            }
          }
          
        }
      }
    }

    // calc min/max distance
    float drMin = 99.0;
    float drMax = -drMin;
    for (int mCnt = 0; mCnt < pNumMax; mCnt++) {
      float dR = PVector.dist(curP[mCnt], prvP[mCnt]) / pDistMax;
      drMax = max(drMax, dR);
      drMin = min(drMin, dR);
    }

    // draw lines
    blendMode(SCREEN);
    noFill();
    for (int mCnt = 0; mCnt < pNumMax; mCnt++) {
      float distRatio = map(PVector.dist(curP[mCnt], prvP[mCnt]) / pDistMax, drMin, drMax, 0.0, 1.0);
      float hueRatio = (mCnt % pClass) * 1.0 / pClass;
      float satRatio = floor(0.5 + mCnt / (pNumMax * 0.3)) / 3.0;
      float briRatio = floor(0.5 + mCnt / (pNumMax * 0.2)) / 5.0;

      stroke((baseHue + 90.0 + hueRatio * 120.0 + distRatio * 90.0) % 360.0,
             10.0 + satRatio * (1.0 - distRatio) * 80.0,
             20.0 + briRatio * 30.0 + distRatio * 30.0,
             100.0
             );
      strokeWeight(constrain(6.0 * (1.0 - distRatio), 1.0, 6.0));
      line(prvP[mCnt].x, prvP[mCnt].y, curP[mCnt].x, curP[mCnt].y);
    }

    // carry over
    cur2prv(curP, prvP);

    // make image files for animation frames
    saveFrame("frames/" + String.format("%04d", frm) + ".png");
  }

  exit();
}

/**
 * deep copy from c to p
 */
void cur2prv(PVector[] c, PVector[] p){
  for(int pCnt = 0; pCnt < p.length; pCnt++){
    p[pCnt].x = c[pCnt].x;
    p[pCnt].y = c[pCnt].y;
  }
}
	    

 

Points of Modification

I decided to manage the particles with PVector.


    curP[pCnt] = new PVector(r * cos(t), r * sin(t));
	    

I separated arrays for position calculations and for the results of previous calculations, so that the results of one particle's calculations do not affect the position calculations of other particles.


  PVector curP[] = new PVector[pNumMax]; // current
  PVector prvP[] = new PVector[pNumMax]; // previous
	    

I implemented a change in the particle's position calculation formula, using the condition '(mCnt % pClass == oCnt % pClass)'.


          float pDist = 1.0 + PVector.dist(prvP[mCnt], prvP[oCnt]);
          PVector pDiv = PVector.div(PVector.mult(PVector.sub(prvP[mCnt], prvP[oCnt]), distMag), pow(pDist, 2));

          if (mCnt % pClass == oCnt % pClass) {
            curP[mCnt].add(pDiv.mult((pDistMax - pDist) / pDistMax));
          } else {
            if (pDist > pDistMax) {
              curP[mCnt].sub(pDiv);
            } else {
              curP[mCnt].add(pDiv);
            }
          }
	    

Because of the long computation time required, I did not make real-time animation with draw() function. Instead, I've decided to use a method that writes out a still image for each frame to generate a movie.

 

Additional Ideas for Modification

Even with the code as it is, you can get various results by changing the initial values of the variables.


  int   pNumMax  = 1800;
  int   pClass   = 6;
  float pIniRad  = 80.0;
  float pDistMax = 220.0;
  float distMag  = 12.0;
	    

It would be interesting to get interesting results if these variables were not fixed values, but rather values that change as the frame progresses.

 

Conclusion

My version ended up feeling more like a simulation than a traditional art piece.

It looks as if the particles by color move and gather autonomously, but in reality it is better to say that the color is changed by the group of particles that gather.


      float hueRatio = (mCnt % pClass) * 1.0 / pClass;
	    

To tell the truth, I have no idea why both the original code and the modified code work like this.

This shows that you don't need a deep technical mastery to start creating. By experimenting with existing code, you can craft something unique—and that’s the true essence of creative coding.

Without the underlying code this time, this work would not have been created.
Thank you, ひさだん(@hisadan) san for publishing this interesting example code.

 

Next Post Previous Post
No Comment
Add Comment
comment url