Writing new behaviour ops
=========================

NukeX 6.3 has a particle system.  It is possible to hook into this and write your own
plugins that operate on particles.

The base class of interest is DD::Image::ParticleBehaviour in <DDImage/ParticleOp.h>.
Subclasses should implement the following function:

.. code-block:: c

  virtual void applyBehaviour( const ParticleContext& context, ParticleSystem* ps)

This should apply the operation to the particles in ParticleSystem.  

A simple example is as follows:

.. code-block:: c

  virtual void applyBehaviour( const ParticleContext& context, ParticleSystem* ps)
  {
     for (unsigned i = 0; i < ps->numParticles(); i++) {
        if ( conditionsApply( ps, i ) ) { 
          applyAcceleration(ps, i, context, Vector3(0, -0.1, 0));
        }
     }
  }

This applies a consistent downwards acceleration to all particles.  The conditionsApply function is a 
checks to see whether the given particle should be operated upon.  It is important to call this, as 
the ParticleMerge node relies on it.  Apart from checking which branch the particle was emitted 
on for ParticleMerge, it also checks whether or not the particle matches various criteria that can 
be set on knobs.  These are the conditionKnobs (which allow execution to be made conditional on
min/max age, channels, etc) and the domainKnobs (which allow execution to be confined to a certain
region).  To add these knobs call the addDomainKnobs and addConditionKnobs functions from your
knobs() call, like so:

.. code-block:: c

  void knobs(Knob_Callback f)
  {
    addConditionsKnobs(f);
    addDomainKnobs(f);
 }

It is possible to access properties of particles from within the applyBehaviour function by functions
on the ParticleBehaviour.  For example, the mass of the particle can be got with 

.. code-block:: c

  const float& particleMass(unsigned idx) const;

and can be set by 

.. code-block:: c

  float& particleMass(unsigned idx);

The properties that exist are as follows:
  
  =================== =====================  ========================================================================
  Type                Name                   Description
  =================== =====================  ========================================================================
  Vector3             initialPosition        position that the particle was created at
  Vector3             position               position that the particle is at now
  Vector3             velocity               velocity of the particle, expressed in units/frame
  Vector3             size                   size of the particle, expressed in units
  Quarternion         orientation
  Vector3             rotationAxis
  float               rotationAngle
  float               rotationVelocity
  float               mass                   mass of the particle
  float               life                   maximum lifetime of the particle (in frames)
  float               expirationChance       chance that this particle will die each frame (for halflife)
  float               t                      number of frames (possibly fractional) this particle has been alive
  float               startTime              time this particle was emitted
  int                 id                     a unique ID for the particle
  ParticleChannelSet  channels               the sets the particle is in
  int                 pathMask               the pathMasks the particle is active for (internal)
  bool                active                 whether the particle currently exists
  Op*                 representation         The Iop or GeoOp to use to as the particle for rendering
  =================== =====================  ========================================================================


Any of these could be set by an applyBehaviour implementation, although it would not make sense to set
initialPosition/t/dtOffset/startTime/id/pathMask/active/representation.

applyBehaviour is given a ParticleContext which tells it how long to run the effect for.  For example,
a force applied for 0.5 frames ought to only result in half the acceleration that it would if it were
run for 1 frame.  This is used to implement sub-frame stepping.  A couple of helper functions
are provided on ParticleBehaviour

.. code-block:: c

    void applyAcceleration(ParticleSystem* ps, unsigned idx, const ParticleContext& ctx, Vector3 accel);
    void applyForce(ParticleSystem* ps, unsigned idx, const ParticleContext& ctx, Vector3 force);

These take into account the ctx.dt(), and also whether or not the particle was only just emitted in the last frame
and if so whether only part of that force should be applied.  applyForce() takes into account the mass, dividing
the force by the mass to get the acceleration.  If you writing a plugin that, for example, reduces the particle's
mass slightly each frame, then you will need to take ctx.dt() into account too.  If, on the other hand, you are writing
a plugin that sets the particle's colour to a different value depending upon its value t, there is no need to take
into account ctx.dt().
