ParticleFuse

The ParticleFuse is a particle kernel which fuses particles together if they get too close to each other. It duplicates the functionality of Nuke’s ParticleFuse node and demonstrates

random access to the particle images as well as how to retire particles.

Change the paFuseDistance parameter to set the direciton of the wind and the paDrag parameter to alter the air resistance.

// Copyright (c) 2024 The Foundry Visionmongers Ltd.  All Rights Reserved.

// A kernel which fuses particles which get too close to each other. It demonstrates
// random access to particles and expiring particles.
kernel ParticleFuse : ImageComputationKernel<ePixelWise>
{
  Image<eRead,eAccessRandom> p_position;
  Image<eReadWrite,eAccessRandom> p_life;
  Image<eReadWrite,eAccessRandom> p_size;
  Image<eReadWrite,eAccessRandom> p_mass;
  Image<eReadWrite,eAccessRandom> p_velocity;

  param:
    float _fuseDistance;
    float _dt;

  local:
    int _numParticles;

  void define() {
    defineParam(_fuseDistance, "paFuseDistance", 0.1f);
    defineParam(_dt, "_dt", 1.0f);
  }

  void init() {
    _numParticles = p_position.bounds.height();
  }

  void process(int2 pos) {
    const float kFuseDistanceSquared = _fuseDistance * _fuseDistance;

    // The kernel is called from multiple threads and there is no guarantee in which order
    // the particles are processed. For a kernel like this which is reading and
    // writing simultaneously, this means that the results re non-deterministic and
    // will vary from run to run. This is a very tacky way to avoid this: We process
    // all the particles when called for the last particle. This means that we lose the
    // benefits of multithreading, but keep the results deterministic. A future version
    // of ParticleBlinkScript may provide better mechanisms for dealing with this.
    if (pos.y == _numParticles - 1) {
      for (int i = 0; i < _numParticles; ++i) {
        if (p_life(0, i, 0) > 0) {
          float3 lposition = p_position(0, i);

          for (int j = 0; j < i; ++j) {
            if (p_life(0, j, 0) == 0.0f) {
              continue;
            }

            float3 gap = p_position(0, j) - lposition;
            float distanceSquared = dot(gap, gap);
            if (distanceSquared < kFuseDistanceSquared) {
              p_life(0, i, 0) = 0.0f;
              float mi = p_mass(0, i, 0);
              float mj = p_mass(0, j, 0);
              float3 vi = p_velocity(0, i);
              float3 vj = p_velocity(0, j);
              p_size(0, j) = p_size(0, j) + p_size(0, i);
              p_mass(0, j, 0) = mi + mj;
              p_velocity(0, j) = (mi * vi + mj * vj) / (mi + mj);
            }
          }
        }
      }
    }
  }
};