Diffusion-Limited Aggregation is a process that can be used to create fractal patterns similar to those found in crystals. The workflow is that you have a particle moves in a random walk and then freezes as soon as it hits the other aggregate particles. Then it becomes part of the aggregate and you repeat the process with a new particle. This recursive nature is what creates the interesting fractal shapes. I wanted to do this project so that I could get more comfortable with VOPs as well as because I was really inspired by the artwork of Andy Lomas.
Instead of doing this a point at a time, I decided to scatter a lot of points initially and then move them all at once, stopping any points that get within a certain threshold of the aggregate points. While this isn't technically the same process, I feel as though I have been able to produce comparible results. I start off with two groups of points and create a point attribute called "static." The first group will be the initial cluster and will have a value of 1 for "static," and the other group will be the animated points and will have a value of 0. In the example, I use a single point for the first group and then scatter points inside the teapot using an Iso Offset node going into a Scatter node.
Now that I have the points set up, I merge them together and then pass it to a Solver SOP. I originally did this project using a POP Net but wanted to try out the Solver SOP and so I was able to switch it over relatively quickly. Diving into the Solver, I plug the Prev_Frame into a VOP SOP. Each frame, the Solver SOP will take the geometry from the previous frame and update it based on the operations in the VOP SOP. This is how we can get the points to remain static once they hit the cluster. One weird thing that caught me off guard about the Solver SOP is that it won't work unless you go back up to the geometry level. After the Solver, I simply deleted all the points with a static value equal to 0, this way the aggregate would look like it was growing. Finally, I plugged this into a Particle Fluid Surface node to skin all the points together.
In the VOP SOP, I check to see if the current point has a "static" value of 0. If it does, then I use a PointCloudOpen VOP passing it the total number of points for its Number of Points parameter and a search radius parameter to get all of the points within a certain distance. To get the points passed to the PCOpen, I promoted the Point Cloud Texture parameter and passed it the expression "op:`opinputpath(".",0)". The value of this distance will be our threshold to determine whether or not a point has hit the cluster, so it is inversely related to the density of your animated points. Using a PointCloudIterate VOP and a while loop, I add the value of the current point's static value to a constant. If the value of the constant is still equal to 0 then the point we just checked wasn't part of the cluster and so I iterate to the next point. I do this by passing the result of the PCIterate VOP and the result of comparing the constant to 0 to an And VOP and then passing that to the Condition Value. This way it will kick out of the loop as soon as it finds a point with a static value of 1. Outside of the while loop, I check to see if the constant's value is greater than 1. If it is, then the reason we kicked out of the loop was because the point hit the cluster and not because we exhausted all of the points and so the point will get added to the cluster.
Originally, I had used a while loop to check this distance of every static point to the current point and as a result it was ridiculously slow. By using the Point Cloud vops (thanks Steve Bevins for the suggestion!), I was able to speed up the sim times drastically. It was originally taking about 20 minutes a frame for less than 1 million points using the brute force approach, whereas the Point Cloud method was able to do 10 million points in under a minute a frame. Both times don't include skinning the points.
Outside of the if VOP, I set the point's static attribute equal to the maximum of the point's original static value and the output of the if. This way, if the point was originally static, it won't get overridden by the default value of 0 from the if VOP and thus remain part of the cluster. The complement of this is then multiplied by some curl noise which is in turn added to the position of the current point. This way, if a point has a static value of 0, the curl noise will be multiplied by 1 and so it will move. I also used the search radius parameter from the PCOpen for the amplitude of the Curl Noise VOP so that points would be more likely to hit the outer-most points. This can be changed to make the aggregate more "hairy." I also added three other parameters that I would mod the new position by so that if the points got too far out, they would loop around to the other side of the bounding box. Note that these values reflect the greatest distance from a particular axis, so a BBX value of 5 would keep the points within 5 and -5 of the x-axis.
To get the cluster to eventually form a shape, I added a third group of points, this time scattering tons of points only on the surface of the teapot, so that as soon as the cluster hit them, it would very quickly spread over the whole surface. The problem now was that I wanted these new points to have a static value of 0 but have them remain stationary. To solve this, I simply added another point attribute called "move" and set its value to 1 for the points scattered inside the teapot and 0 for the points on the surface. Then back in the VOP SOP, I multiply the Curl Noise by the result of an And VOP with the values of move and the maximum of the static. This way the position would only be changed if both the point was not static and if it had a move attribute equal to 1.
Below is a flipbook of the full animation, as well as the scene file.