toxiclibs

simutils-0001: Diffusion limited aggregation

A few days ago I published some updates to all five existing main packages and also bundled up some other classes from existing projects into a new package, called simutils. As the name indicates, these new classes deal with the simulation of some fairly basic processes inspired by nature, but each of them exhibits some emergent behaviours which can be (and have been) used for a wide variety of design tasks from 3D printed sculptures to generative music. To help you get started, the package also contains two or three Processing demos for each process showing basic usage patterns. The three processes implemented so far are explained in more detail in this & the next few upcoming posts. Let’s start with…

brownian motion

Diffusion limited aggregation

Diffusion limited aggregation, DLA in short, is a theoretical process describing the clustering of particles through a process of diffusion, like a random walk/Brownian motion. Inspired by the work of Andy Lomas with this process, in 2006 I set out to implement DLA myself from scratch, in order to apply it to structures and literally grow objects not made out of triangles/polygons. At the same time I’d also just discovered the possibilities of rendering huge point clouds with Sunflow, which seemed the perfect combination to explore this all further. By March ’07 I had a system in place which allowed me to grow type and have used it for a commission to produce the “New Shoots” title sequence for Channel4.

The basic premise of a DLA simulation is the slow formation of complex structures through the (random) movement of single particles. Once a particle comes close to an existing one, it attaches itself and a new particle is spawned in a sphere around a previous particle. The process then repeats, slowly filling up the simulation space. If the random walk ever takes a particle passed a certain escape radius, the search is restarted with a different initial direction until another particle is hit. Because particles are usually very tiny, a large number of them and even more iterations are required for even very simple structures to emerge. For example, the final frame of the New Shoots sequence consisted of 4.5 million particles, whereas the structure for the image below has a “mere” 645,000…

Finding the needle in the haystack

Searching for particle collisions in sets of such size linearly is super slow & cumbersome, so to make the process more efficient, my version of DLA is using the PointOctree class from the core package. This class is recursively (and on-demand) subdividing the simulation space into smaller, nested cubes, which are stored as tree structure. When new particles are added by the DLA process they’re also being attached to the leaf nodes of the octree. When the next particle is searching for collisions with existing particles, these can then be found very quickly by querying the octree based on local search criteria using these 2 methods:

PointOctree octree = new PointOctree(new Vec3D(-50,-50,50),100);
List<Vec3D> points; // in Java
java.util.List points; // in Processing, no support for generics!

// search within a sphere (i.e. all points within a certain distance)
Sphere boundingSphere = new Sphere(new Vec3D(),1);
points = octree.getPointsWithinSphere(boundingSphere); // or
points = octree.getPointsWithinSphere(origin, radius);

// search within an axis-aligned bounding box
AABB boundingBox = AABB.fromMinMax(new Vec3D(-1, -1, -1), new Vec3D(1, 1, 1));
points = octree.getPointsWithinBox(boundingBox);

For more usage information about the octree also see the DLASpiral demo (the screenshot below also shows the adaptive structure of the octree) and the OctreeDemo of the core package…

In the future I’ll probably refactor this current dependency on the PointOctree class and have the DLA merely depend on a more generic SpatialTree interface, which then can also be implemented by other tree types, e.g. a kD tree. To compensate, the DLA class so far has an extension point of sorts to allow custom PointOctree sub-classes to be used. In this case you’ll simply have to also sub-class DLA and overwrite the createOctree method to return an instance of your custom PointOctree.

Guided growth

In the above screenshot you can also see the blue spiral shape guideline directing the growth of the particles. These guidelines can either be individual line segments or points lists, e.g. sourced from a Spline3D instance or computed otherwise. When passing in a list of points, they’re connected into successive line segments internally. At least one line segment is required for the DLA process to function and you’d combine the 2 classes like this:

// define a spline
Spline3D spline = new Spline3D();
spline.add(new Vec3D()).add(new Vec3D(0,-50,0)).add(new Vec3D(50,0,50)).add(new Vec3D(0,50,0));
DLAGuideLines guides = new DLAGuideLines();
guides.addCurveStrip(spline.computeVertices(8));

// setup DLA in a cubic space of 100 units edge length (always centred around (0,0,0))
DLA dla = new DLA(100);
// assign guideline(s)
dla.setGuideLines(guides);

The moment the guidelines are attached, each line segment is sampled at a high resolution and the computed points added to a second octree kept by the DLA class. It’s these points which are shown in the above screenshot (although only 10% of them are displayed)…

Order of growth

By default the growth of the entire particle system will be in the same order as the guideline segments have been added, however you’re free to change this by means of custom Compatators. The DLAGuideLines class is storing DLASegment instances in a sorted TreeSet. The order of this set can be defined by defining a custom comparator implementation when creating the guidelines and so any possible growth order can be designed/defined. The example below defines one which enforces a bottom-up growth (assuming positive Y is up):

// define comparator
// (this how to do in Processing, which doesn't support generics...
// in plain Java you'd compare Line3D instance directly
// without the need for casting from Object)
class BottomUpOrder implements Comparator {
  public int compare(Object a, Object b) {
    float ya=((Line3D)a).getMidPoint().y;
    float yb=((Line3D)b).getMidPoint().y;
    if (ya<yb) return -1;
    if (ya>yb) return 1;
    return 0;
  }
}

// now use the comparator for the guidelines
DLAGuideLines guides = new DLAGuideLines(new BottomUpOrder());

The next release of this package will contain a few more of those…

Parameters of growth

The final requirement for the whole process to start is a set of configuration parameters. Of these there are many and so they’re encapsulated in their own class. To use them they’re simply passed to the DLA like:

// use default configuration
dla.setConfig(new DLAConfiguration());

The default configuration has the following values, also briefly described below:

// threshold distance below which a particle attaches itself to an older one
float snapDistance = 1.8f;
// threshold distance to attach to a curve/guideline particle
float curveAttachDistance = 2;

// max. radius of the sphere in which a new particle is spawned around a previous one
float spawnRadius = 12;
// max. radius of the sphere when the particle's random walk is interrupted/restarted
float escapeRadius = 36;
// actual size of the particles (currently assumed to be uniform for entire system)
float particleRadius = 0.25f;
// stickiness factor, only 10% of all attachments will be successful
float stickiness = 0.1f;
// using guidelines the growth can be forced into a certain direction
// this parameter defines the impact strength of this direction
// if 0.0 particles grow in all directions, 1.0 = actual segment direction is used
float curveAlign = 0.74f;

// percentage amount to progress along segments after each new particle
float curveSpeed = 0.00045f;
// velocity of the random particle walk
float searchSpeed = snapDistance * 0.66f;
// turn speed when particle changes direction
float particleSpeed = 0.001f;

// progress speed when scanning guidelines to build octree
float guideLineDensity = 0.1;

// chance for existing/already processed segments to continue growing
// evaluated before spawning each new particle
float continousGrowthRatio = 0.1f;

Why a library?

Whereas this is setup creates very interesting structures (especially when rendered in Sunflow), my idea for this library was not necessarily to have everyone replicate this specific look over & over again, but provide the means to focus more on exploring the actual growth process, to customize it and use it as driver for creating new structures in which the actual particles are only present indirectly/secondary. One key feature for that matter are events emitted by the DLA class at key moments of the process. The DLAEventListener interface defines the following 3 event hooks:

public interface DLAEventListener {

    void dlaAllSegmentsProcessed(DLA dla);

    void dlaNewParticleAdded(DLA dla, Vec3D p);

    void dlaSegmentSwitched(DLA dla, DLASegment s);
}

You can subscribe to these events by either creating your own class implementing this interface or sub-classing the also supplied DLAEventAdapter (in case you don’t want to react to all of them):

class MyDLAListener extends DLAEventAdapter {

  // only get notified when growth is complete
  public  void dlaAllSegmentsProcessed(DLA dla) {
     System.out.println("done");
  }
}

DLA dla=new DLA(100);
// attach listener
dla.addListener(new MyDLAListener());

Alternatively, listening to “new particle” events allows you to construct elaborate structures not just from particles, but use particle positions to deposit small meshes or use volumeutils to draw with a VolumetricBrush at these positions… There’re endless possibilities for further exploration and I hope some of you are excited enough to explore them! If so have fun & share it!

Some more of my own earlier explorations into this field are on flickr.

Leave a Reply