I recently posted Chaos in Swift! Visualizing the Lorenz Attractor with Metal, which was inspired by a Houdini tweet from Erwin Santacruz. It appears that Erwin's solution creates geometry, so I thought I'd look at this from a different perspective to see if I could create a particle based visualization of strange attractors in Houdini.

So, it turns out to be a lot simpler than I expected! 

The secret sauce is the POP Wrangle node. This node runs a VEX expression against every particle and allows Houdini control over each particle's properties such as size, position, and velocity. The number of solves per-frame is controlled by the POP Solver's subsets attributes.

To give my attractors some variance, my projects use very small spheres as the particle source. Once Houdini had created a DOP Network, I've removed gravity, added the POP Wrangle and an instance geometry node for fine control over particle size:



The VEX inside the POP Wrangle nodes isn't a million miles away from my Metal code. The main difference is that for each invocation, it's getting and setting the x, y and z attributes of each particle's position (@P). So, the Hadley attractor POP Wrangle expression is:


float alpha = 0.2;
float beta = 4.0;
float zeta = 8;
float d = 1.0;     
float deltaX = -@P.y * @P.y - @P.z * @P.z - alpha * @P.x + alpha * zeta; 
float deltaY = @P.x * @P.y - beta * @P.x * @P.z - @P.y * d;
float deltaZ = beta * @P.x * @P.y + @P.x * @P.z - @P.z;
@P.x += deltaX / 300.0;
@P.y += deltaY / 300.0; 
@P.z += deltaZ / 300.0;

...and the Lorenz Mod 2 attractor POP Wrangle expression is:


float a = 0.9;
float b = 5;
float c = 9.9;
float d = 1;  
float deltaX = -a * @P.x + @P.y * @P.y - @P.z * @P.z + a * c; 
float deltaY = @P.x * (@P.y - b * @P.z) + d;
float deltaZ = -@P.z + @P.x * (b * @P.y + @P.z);
@P.x += deltaX / 1000.0;
@P.y += deltaY / 1000.0;
@P.z += deltaZ / 1000.0;
The final renders have some depth-of-field and motion-blur added with some nausea inducing camera moves (I'm no cinematographer!). Enjoy!

Addendum: Adding a Fluid Surface


By adding a Particle Fluid Surface node to the particle import location node, Houdini can add a mesh to the particle system which gives a great mercurial effect.


The video above shows an implementation of the Genesio-Tesi Attractor, using the following VEX code in the POP Wrangle node:

float alpha = 0.44;
float beta = 1.1; 
float delta = 1.0;
float deltaX = @P.y;
float deltaY = @P.z;
float deltaZ = - (@P.x * delta) - (beta * @P.y) - (alpha * @P.z) + (@P.x * @P.x);
@P.x += deltaX / 100.0; 
@P.y += deltaY / 100.0; 
@P.z += deltaZ / 100.0;
If you enjoyed this, take a look at VEX in Houdini: Strange Attractors over at Entagma.
0

Add a comment


It's been a fairly busy few months at my "proper" job, so my recreational Houdini tinkering has taken a bit of a back seat. However, when I saw my Swarm Chemistry hero, Hiroki Sayama tweeting a link to How a life-like system emerges from a simple particle motion law, I thought I'd dust off Houdini to see if I could implement this model in VEX.

The paper discusses a simple particle system, named Primordial Particle Systems (PPS), that leads to life-like structures through morphogenesis. Each particle in the system is defined by its position and heading and, with each step in the simulation, alters its heading based on the PPS rule and moves forward at a defined speed. The heading is updated based on the number of neighbors to the particle's left and right. 

The project set up is super simple: 



Inside a geometry node, I create a grid, and randomly scatter 19,000 points across it. An attribute wrangle node assigns a random value to @angle:
@angle = $PI * 2 * rand(@ptnum); 
The real magic happens inside another attribute wrangle inside the solver.

In a nutshell, my VEX code iterates over each point's neighbors and sums the neighbor count to its left and right. To figure out the chirality, I use some simple trigonometry to rotate the vector defined by the current particle and the neighbor by the current particle's angle, then calculate the angle of the rotated vector. 
while(pciterate(pointCloud)) {

    vector otherPosition;
    pcimport(pointCloud, "P", otherPosition);

    vector2 offsetPosition = set(otherPosition.x - @P.x, otherPosition.z - @P.z);
    float xx = offsetPosition.x * cos(-@angle) - offsetPosition.y * sin(-@angle);
    float yy = offsetPosition.x * sin(-@angle) + offsetPosition.y * cos(-@angle);
    
    float otherAngle = atan2(yy, xx); 

    if (otherAngle >= 0) {
        L++;
    } 
    else {
        R++;
    }   
}
After iterating over the nearby particles, I update the angle based on the PPS rule:
float N = float(L + R);
@angle += alpha + beta * N * sign(R - L);
...and, finally, I can update the particle's position based on its angle and speed:
vector velocity = set(cos(@angle) * @speed, 0.0, sin(@angle) * @speed);  
@P += velocity ;
Not quite finally, because to make things pretty, I update the color using the number of neighbors to control hue:
@Cd = hsvtorgb(N / maxParticles, 1.0, 1.0); 
Easy!

Solitons Emerging from Tweaked Model



I couldn't help tinkering with the published PPS math by making the speed a function of the number of local neighbors:
@speed = 1.5 * (N / maxParticles);
In the video above, alpha is 182° and beta is -13°.

References

Schmickl, T. et al. How a life-like system emerges from a simple particle motion law. Sci. Rep. 6, 37969; doi: 10.1038/srep37969 (2016).


5

View comments

  1. ok. I've got to finish current job, then crash course in programming, and ... this is very inspirational!

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
About Me
About Me
Labels
Labels
Blog Archive
Loading