The Poor Man's DLA: Reimagining Diffusion-Limited Aggregation

A result of poor man's Diffusion-limited aggregation.

Creative coding work made with Processing.

The Origin Story: Deconstructing the DLA

Wrestling with Brownian Motion

I initially set out to implement a standard Diffusion-Limited Aggregation (DLA) algorithm. For those unfamiliar, it’s a fascinating—if tongue-twisting—concept.


Traditional DLA is built on Brownian motion. For this experiment, however, I started by substituting it with a simple random walk.

Reference. Diffusion-limited aggregation | Wikipedia

Next, I experimented with a "downward" flow, where particles fall from the top of the canvas.


When I visualized the paths of these random walks, the results looked strikingly like lightning strikes. I briefly considered calling it "Chain Lightning Aggregation," but I decided to stick to my quest for simplification.


 

A Moment of Clarity

Then, a realization hit me: Does it actually need a random walk? I decided to try "firing" cells in a straight line from random angles toward the seed.


The result? It worked surprisingly well. To my surprise, simply dropping cells straight down from random positions yielded a very similar aesthetic, without any complex physics.


 

The Power of Randomness vs. Regularity

I pushed the idea further by shooting cells toward the center from random directions.


By slightly offsetting the target from the dead center, I could even create spiral galaxy-like structures.


These look remarkably like "real" DLA. Purists might scoff since there's no diffusion involved, but for creative purposes, the visual outcome is what matters.

 

Finding Beauty in Regularity

Finally, I wondered: What if I replaced randomness with total regularity?


By tuning the rhythm and angles of the "shots," I discovered I could generate complex, organic-looking forms—some even resembling microscopic life.


As the patterns grew more biological, I couldn't help but feel a sense of divine creation—in a very tiny scale: I have become the god of water fleas.🤣


 

Implementation: The "Poor Man's DLA" in Processing

In this final iteration, I achieved complex structural growth using purely rhythmic shooting—no random() calls required.



 

The following script generates a series of high-resolution frames directly to disk. It is designed for offline rendering and does not display an image window during execution. These frames are intended to be compiled into a final animation.

This code is provided under the GPL license. Feel free to experiment with it—I would be honored to see any works inspired by this approach.


/**
 * Poor man's DLA
 * 
 * @author @deconbatch
 * @version 0.1
 * Processing 3.2.1
 * created 2019.10.01
 * 
 */

/**
 * Point : Hold a point information.
 */
private class Point {
  public float x, y, h; // informations are writable purposely

  /**
   * @param  _x   : x-coordinate value of the point.
   * @param  _y   : y-coordinate value of the point.
   * @param  _hue : hue value of the point.
   */
  Point(float _x, float _y, float _hue) {
    x = _x;
    y = _y;
    h = _hue % 360.0;
  }
}

void setup(){
  size(720, 720);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noLoop();
}
 
void draw(){

  int   plotMax = 7200;
  int   frmMax  = 24 * 1;
  int   walkMax = height * 2;
  float pSize   = 4.0;
  float pHue    = random(360.0);
  float initDiv = random(0.1, 0.4);

  ArrayList<Point> cluster = new ArrayList<Point>();

  for (float i = 0.0; i < 1.0; i += initDiv) {
    cluster.add(new Point(
                          0.2 * width * cos(TWO_PI * i),
                          0.2 * height * sin(TWO_PI * i),
                          pHue));
  }

  translate(width * 0.5, height * 0.5);
  rotate(random(TWO_PI));

  float entryR = 1.0;  // entryRadius
  for (int plotCnt = 0; plotCnt < plotMax; plotCnt++) {

    // complex rhythm
    entryR += entryR;
    entryR = entryR % TWO_PI;
    
    Point p = new Point(
                        width * cos(entryR),
                        height * sin(entryR),
                        pHue);
    for (int walkCnt = 0; walkCnt < walkMax; walkCnt++) {

      // walk to the center
      p.x -= cos(entryR);
      p.y -= sin(entryR);
      
      if (checkCollision(cluster, p, pSize)) {
        cluster.add(p);
        pHue += 0.015;
        break;
      }
      
    }

    if (plotCnt % floor(plotMax / frmMax) == 0) {
      background(0.0, 0.0, 90.0, 100.0);
      drawCluster(cluster, pSize);
      saveFrame("frames/0." + String.format("%05d", plotCnt) + ".png");
    }    

  }

  // draw the final frame
  background(0.0, 0.0, 90.0, 100.0);
  drawCluster(cluster, pSize);
  saveFrame("frames/1.00000.png");
  exit();

}

/**
 * drawCluster : draw the points cluster 
 * @param  _cluster : ArrayList of the Point class.
 * @param  _size    : draw point size.
 */
private void drawCluster(ArrayList<Point> _cluster, float _size) {
  float eSat = 40.0;
  float eBri = 60.0;
  noStroke();
  for (Point p : _cluster) {
    eSat += 0.009;
    eBri -= 0.003;

    
    fill(p.h, eSat, eBri, 100.0);
    ellipse(p.x, p.y, _size, _size);
  }
}

/**
 * checkCollision : check collision between a point and the cluster.
 * @return boolean  : true = detect collision.
 * @param  _cluster : ArrayList of the Point class.
 * @param  _p       : a point
 * @param  _size    : size of the point.
 */
private boolean checkCollision(ArrayList<Point> _cluster, Point _p, float _size) {
  for (Point p : _cluster) {
    if (dist(p.x, p.y, _p.x, _p.y) < _size * 1.0) {
      return false;
    }
  }

  for (Point p : _cluster) {
    if (dist(p.x, p.y, _p.x, _p.y) < _size * 1.2) {
      return true;
    }
  }  
  return false;
}


/*
Copyright (C) 2019- deconbatch

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/




 

Visual Variations

It's a reminder that complexity doesn't always require randomness; sometimes, pure rhythm is enough.

Processing code examples above made this image.

You can make this with Processing code examples here.

Please see Processing code examples in this article.

 

Next Post Previous Post
2 Comments
  • Unknown
    Unknown Wednesday, April 15, 2020

    I'm absolutely blown away by this! I intend to use this very soon, and I'll be sure to tag you when I do. I'm always so grateful for people like yourself that share your knowledge in an accessible way.

    One note: when I copied this into Processing, I had to slightly modify the argument declarations. Everywhere that you have `ArrayList _cluster`, I had to change to `ArrayList<Point> _cluster` for it to compile. I'm using Processing 3.5.4, so slightly different version, just wanted to let you (and any future readers) know

    • deconbatch
      deconbatch Thursday, April 16, 2020

      Thank you for pointing out! I appreciate it!🙂
      It was my mistake and I fixed it now. This time I copied code from this article and ran it for confirmation.👍

Add Comment
comment url