Hiroki Sayama's Swarm Chemistry is a model I've tinkered with and blogged about for years. Swarm chemistry simulates systems of heterogeneous particles each with genomes that describe simple kinetic rules such as separation and cohesion. As the simulation progresses, emergent phenomena such as mitosis and morphogenesis can be observed.
All of my previous implementations of swarm chemistry have been in two dimensions, but as part of my run-before-you-can-walk Houdini education, I thought I'd implement the maths in VEX and attempt a three dimensional Houdini version.
The results, I think, are pretty impressive. Rather than rendering plain particles, the system is rendered as metaballs and, combined with the additional dimension, it looks very organic.
Let's dive into my swarm chemistry node to see how it's all put together:
- The first node is a Point Generate which, as the name suggests, generate points. In my project, I create 7,500 points.
- I use a Color node to add a color attribute.
- The generated points are, by default, all at {0, 0, 0}, this Attribute Wrangle node places them randomly in a cube using:
- vector randomPosition = random(@ptnum);
- @P = -10 + (randomPosition * 20.0);
- The swarm chemistry code requires that each point has velocity and velocity2 attributes which are added in this Attribute Create node.
- The Solver node runs the swarm chemistry VEX which I'll describe later in this post.
- A Metaball node supplies the metaball geometry for the system.
- A Copy node copies a metaball to each point.
- Finally, an Attribute Transfer node transfers the point colors to the metaballs.
My first step is to figure out which of the three genomes will be used for the current point. This is done by using the modulo operator on the current point number. Using that value, I define the values for cohesion, alignment, separation, etc:
int index = @ptnum % 3;
float c1_cohesion = 0;
float c2_alignment = 0;
float c3_seperation = 0;
float c4_steering = 0;
float c5_paceKeeping = 0;
float radius = 0;
float normalSpeed = 0;
if (index == 0)My next step is to iterate over each point's closest neighbors and start summing an accumulator with the distances of those neighbors multiplied by the genome's separation value:
{
@Cd = {1, 0, 0};
c1_cohesion = 73.63;
c2_alignment = 0.95;
c3_seperation = 3.2;
c4_steering = 0.3;
c5_paceKeeping = 0.35;
radius = 20;
normalSpeed = 0.8; }
else if (index == 1)
{
@Cd = {0, 1, 0};
c1_cohesion = 53.63;
c2_alignment = 0.75;
c3_seperation = 2.9;
c4_steering = 0.5;
c5_paceKeeping = 0.3;
radius = 10;
normalSpeed = 0.8;
}
else // index == 2
{
@Cd = {0, 0, 1};
c1_cohesion = 63.63;
c2_alignment = 0.65;
c3_seperation = 2.7;
c4_steering = 0.2;
c5_paceKeeping = 0.25;
radius = 25;
normalSpeed = 2.8;
}
int pointCloud = pcopen(0, "P", @P, radius, 1000);The average position and velocities of the neighboring points is calculated with pcfilter:
vector temp = {0, 0, 0};
while(pciterate(pointCloud)){ vector tmpP; pcimport(pointCloud, "P", tmpP); float dist = distance(@P, tmpP); if (dist < 1) { dist = 1; } temp += (@P - tmpP) / (dist * c3_seperation); }
vector localCentre = pcfilter(pointCloud, "P"); vector localVelocity = pcfilter(pointCloud, "velocity");A random steering value is added to the accumulator:
float r = random(@ptnum + @Time);if (r > c4_steering){ vector randSteer = (random(@ptnum * @Time) - 0.5) * 3; temp += randSteer; }The average position and velocity is also added to the accumulator, multiplied by the cohesion and alignment values of the genome:
temp += (localCentre - @P) * c1_cohesion;Finally, the velocity is calculated based on the accumulator and the point's position is updated. Interestingly, I have to tell Houdini that my velocity and velocity2 attributes are vectors by prefixing them with a v (e.g. v@velocity):
temp += (localVelocity - v@velocity) * c2_alignment;
v@velocity2 += temp * 0.1;Phew!
float d = length(v@velocity2);
if (d < 0.1) { d = 0.1; }
float accelerateMultiplier = 0; accelerateMultiplier = (normalSpeed - d) / d * c5_paceKeeping;
v@velocity2 += v@velocity2 * accelerateMultiplier ;
v@velocity = v@velocity2;
@P += v@velocity * 0.1;
Considering the work involved, Houdini renders the points pretty quickly - even in the UI of the app.
I hope you find this post useful, keep an eye out for more experiments in Houdini!
thanks for this - i managed to drop down a network following your advice - i just copy pasted your code tho sorry :/ I did try typing it but it was making me go cross eyed plus i had errors. I'm just trying to suss out what the various values do really!
ReplyDelete