Click or drag to resize
DigitalRuneHow To: Create a Start Value Effector

This section explains how to create a start value effector (an effector that initializes particle parameters for newly created particles).

What is an start value effector?

Most particle systems have varying particle parameters. These are values (such as "Position", "Size", etc.) that are stored per particle. When new particles are created, someone must initialize the values of varying parameters. This is the job of the start value effector.

The other type of particle parameters are uniform parameters. Uniform parameters are shared by the particle system and all particles. They are usually initialized when the particle system is created or reset. Start value effectors can also be used to initialize uniform parameters, but in most cases it is easier to set them directly.

Start value effectors are derived from ParticleEffector. Whenever new particles are emitted, a start value effector sets the initial values of one or more varying particle parameters. This article shows how to implement a start value effector that initializes a single configurable particle parameter. (Please note: DigitalRune Particles contains a StartValueEffectorT that provides the same functionality.)

Creating a start value effector

First, we create a new class that inherits from ParticleEffector. Particle effectors can be added to a particle system, and they are called whenever the particle system is initialized or updated. Here is the empty class and the namespaces that we will need:

C#
using DigitalRune.Mathematics.Statistics;
using DigitalRune.Particles;

namespace MyNamespace
{
  public class MyStartValueEffector<T> : ParticleEffector
  {
    // TODO: Add code.
  }
}

The class is a generic where the type parameter T is the type of the particle parameter. Particle parameters are identified by their name - a string that must be unique within a particle system. Let's add a property to the particle effector that lets the user set the particle parameter that should be initialized:

C#
[ParticleParameter(ParticleParameterUsage.Out)]
public string Parameter { get; set; }

The ParticleParameterAttribute provides meta-information for particle editors and validation.

How do we determine the start values for new particles? Usually each particle should get a random value (e.g. a random position in sphere, a random color, a random mass, etc.). DigitalRune Mathematics provides random value distributions (see class DistributionT and derived types). A DistributionT returns random values within a defined range that follow a certain probability distribution.

Let's add a property to allow the user to set the DistributionT that should be used. As a fallback, in case the user does not want to use random values, let's also add a property "DefaultValue". This value will be applied to all particles if no random value distribution is specified.

C#
public Distribution<T> Distribution { get; set; }

public T DefaultValue { get; set; }

Particle effectors should be cloneable. The following two methods must be overridden to support cloning: CreateInstanceCore must return a new instance of the particle emitter and CloneCore must copy all important properties:

C#
protected override ParticleEffector CreateInstanceCore()
{
  return new MyStartValueEffector<T>();
}

protected override void CloneCore(ParticleEffector source)
{
  base.CloneCore(source);

  var sourceTyped = (MyStartValueEffector<T>)source;
  Parameter = sourceTyped.Parameter;
  Distribution = sourceTyped.Distribution;
  DefaultValue = sourceTyped.DefaultValue;
}

The base class ParticleEffector provides several virtual methods that can be overridden in derived classes. These methods will be automatically called by the particle system to which the effector is added. In OnRequeryParameters the particle effector can query the required particle parameters and cache the references. This method is called before the particle system is updated the first time, and then every time the particle parameters change and need to be requeried:

C#
private IParticleParameter<T> _parameter;

protected override void OnRequeryParameters()
{
  _parameter = ParticleSystem.Parameters.Get<T>(Parameter);      
}

OnInitialize is called after OnRequeryParameters when the particle system is updated for the first time, and whenever the particle system is reset. In this method the internal state of the particle effector should be reset and uniform particle parameters should be set to their start values.

C#
protected override void OnInitialize()
{
  if (_parameter != null && _parameter.IsUniform)
  {
    // Initialize uniform parameter.
    var distribution = Distribution;
    if (distribution != null)
      _parameter.DefaultValue = distribution.Next(ParticleSystem.Random);
    else
      _parameter.DefaultValue = DefaultValue;
  }
}

OnUninitialize is called when an effector is removed from a particle system. In this method all resources and references to external objects should be released.

C#
protected override void OnUninitialize()
{
  _parameter = null;
}

The base class ParticleEffector also provides a virtual method that is called whenever new particles were added to the system: OnInitializeParticles. In this method the start values of varying particle parameters should be set.

C#
protected override void OnInitializeParticles(int startIndex, int count, object emitter)
{
  if (_parameter == null)
    return;

  T[] values = _parameter.Values;
  if (values == null)
  {
    // Parameter is uniform. Uniform parameters are handled in OnInitialize().
    return;
  }

  // Parameter is a varying parameter.
  var distribution = Distribution;
  if (distribution != null)
  {
    var random = ParticleSystem.Random;
    for (int i = startIndex; i < startIndex + count; i++)
      values[i] = distribution.Next(random);
  }
  else
  {
    var startValue = DefaultValue;
    for (int i = startIndex; i < startIndex + count; i++)
      values[i] = startValue;
  }
}

The code above is all that is needed for a start value effector. It is important to note that the effector can be used to initialize both, uniform and varying, particle parameters. Uniform particle parameters are initialized in OnInitialize whenever the particle system is reset. Varying particle parameters are initialized in OnInitializeParticles whenever new particles are created.

The final start value effector code

Here is the full source code of the start value effector.

C#
using DigitalRune.Mathematics.Statistics;
using DigitalRune.Particles;


namespace MyNamespace
{
  public class MyStartValueEffector<T> : ParticleEffector
  {
    private IParticleParameter<T> _parameter;


    [ParticleParameter(ParticleParameterUsage.Out)]
    public string Parameter { get; set; }

    public Distribution<T> Distribution { get; set; }

    public T DefaultValue { get; set; }


    protected override ParticleEffector CreateInstanceCore()
    {
      return new MyStartValueEffector<T>();
    }

    protected override void CloneCore(ParticleEffector source)
    {
      base.CloneCore(source);

      var sourceTyped = (MyStartValueEffector<T>)source;
      Parameter = sourceTyped.Parameter;
      Distribution = sourceTyped.Distribution;
      DefaultValue = sourceTyped.DefaultValue;
    }

    protected override void OnRequeryParameters()
    {
      _parameter = ParticleSystem.Parameters.Get<T>(Parameter);
    }

    protected override void OnInitialize()
    {
      if (_parameter != null && _parameter.Values == null)
      {
        // Initialize uniform parameter.
        var distribution = Distribution;
        if (distribution != null)
          _parameter.DefaultValue = distribution.Next(ParticleSystem.Random);
        else
          _parameter.DefaultValue = DefaultValue;
      }
    }

    protected override void OnUninitialize()
    {
      _parameter = null;
    }

    protected override void OnInitializeParticles(int startIndex, int count, object emitter)
    {
      if (_parameter == null)
        return;

      T[] values = _parameter.Values;
      if (values == null)
      {
        // Parameter is a uniform. Uniform parameters are handled in OnInitialize().
        return;
      }

      // Parameter is a varying parameter.
      var distribution = Distribution;
      if (distribution != null)
      {
        var random = ParticleSystem.Random;
        for (int i = startIndex; i < startIndex + count; i++)
          values[i] = distribution.Next(random);
      }
      else
      {
        var startValue = DefaultValue;
        for (int i = startIndex; i < startIndex + count; i++)
          values[i] = startValue;
      }
    }
  }
}