Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ParticleSystem.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections;
5 using System.Collections.Generic;
6 using System.Collections.Specialized;
7 using System.Linq;
8 using System.Runtime.InteropServices;
9 using SiliconStudio.Paradox.Games;
10 using SiliconStudio.Core;
11 using SiliconStudio.Core.Collections;
12 using SiliconStudio.Core.Mathematics;
13 
14 namespace SiliconStudio.Paradox.Particles
15 {
16  /// <summary>
17  /// A particle system, containing particles and their updaters.
18  /// </summary>
19  public class ParticleSystem : IDisposable
20  {
21  private bool disposed = false;
22  private const int particleDefaultCapacity = 256;
23  private int particleCount;
24  private int particleCapacity;
25  private int particleSize;
26  private IntPtr particleData;
27 
28  // We could also store it at beginning of particleData?
29  private IntPtr particleDefaultValue;
30 
31  /// <value>
32  /// The particle fields.
33  /// </value>
34  private Dictionary<ParticleFieldDescription, ParticleField> fields = new Dictionary<ParticleFieldDescription, ParticleField>(8);
35 
36 
37  /// <summary>
38  /// Gets the particle system plugins.
39  /// </summary>
40  /// <value>
41  /// The particle system plugins.
42  /// </value>
43  public TrackingCollection<IParticlePlugin> Plugins { get; private set; }
44 
45  /// <summary>
46  /// Gets the position field accessor.
47  /// </summary>
48  /// <value>
49  /// The position field accessor.
50  /// </value>
51  public ParticleFieldAccessor<Vector3> Position { get; private set; }
52 
53  /// <summary>
54  /// Gets the angle field accessor.
55  /// </summary>
56  /// <value>
57  /// The angle field accessor.
58  /// </value>
59  public ParticleFieldAccessor<float> Angle { get; private set; }
60 
61  /// <summary>
62  /// Gets the particle count.
63  /// </summary>
64  /// <value>
65  /// The particle count.
66  /// </value>
67  public int ParticleCount
68  {
69  get { return particleCount; }
70  }
71 
72  /// <summary>
73  /// Initializes a new instance of the <see cref="ParticleSystem" /> class.
74  /// </summary>
75  public ParticleSystem()
76  {
77  // Initialize collections
78  Plugins = new TrackingCollection<IParticlePlugin>();
79 
80  // Add default position field
81  Position = new ParticleFieldAccessor<Vector3>(AddField(ParticleFields.Position));
82  Angle = new ParticleFieldAccessor<float>(AddField(ParticleFields.Angle));
83 
84  EnsureCapacity(particleDefaultCapacity);
85 
86  Plugins.CollectionChanged += Plugins_CollectionChanged;
87  }
88 
89  /// <summary>
90  /// Finalizes an instance of the <see cref="ParticleSystem" /> class.
91  /// </summary>
93  {
94  Dispose(false);
95  }
96 
97  /// <inheritdoc/>
98  public void Dispose()
99  {
100  Dispose(true);
101  GC.SuppressFinalize(this);
102  }
103 
104  /// <summary>
105  /// Releases unmanaged and - optionally - managed resources.
106  /// </summary>
107  /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
108  protected virtual void Dispose(bool disposing)
109  {
110  if (!disposed)
111  {
112  if (particleData != IntPtr.Zero)
113  {
114  Utilities.FreeMemory(particleData);
115  particleData = IntPtr.Zero;
116  }
117  if (particleDefaultValue != IntPtr.Zero)
118  {
119  Utilities.FreeMemory(particleDefaultValue);
120  particleDefaultValue = IntPtr.Zero;
121  }
122  disposed = true;
123  }
124  }
125 
126  /// <summary>
127  /// Gets the <see cref="Particle"/> enumerator.
128  /// </summary>
129  /// <returns>A <see cref="Particle"/> enumerator.</returns>
131  {
132  return new Enumerator(this);
133  }
134 
135  private void Plugins_CollectionChanged(object sender, TrackingCollectionChangedEventArgs e)
136  {
137  var particlePluginListener = e.Item as IParticlePluginListener;
138  switch (e.Action)
139  {
140  case NotifyCollectionChangedAction.Add:
141  if (particlePluginListener != null)
142  particlePluginListener.OnAddPlugin(this);
143  break;
144  case NotifyCollectionChangedAction.Remove:
145  if (particlePluginListener != null)
146  particlePluginListener.OnRemovePlugin(this);
147  break;
148  }
149  }
150 
151  /// <summary>
152  /// Gets the field accessor specified by the given <see cref="ParticleFieldDescription{T}"/>.
153  /// If the field doesn't exist in this <see cref="ParticleSystem"/>,
154  /// a <see cref="ParticleFieldAccessor{T}"/> is returned with its <see cref="ParticleFieldAccessor{T}.IsValid()"/> returning false.
155  /// </summary>
156  /// <typeparam name="T">The field type.</typeparam>
157  /// <param name="fieldDesc">The field description.</param>
158  /// <returns>A valid field accessor for the requested field if the field exists; otherwise a non-valid one.</returns>
159  public ParticleFieldAccessor<T> GetField<T>(ParticleFieldDescription<T> fieldDesc) where T : struct
160  {
161  ParticleField field;
162  if (!fields.TryGetValue(fieldDesc, out field))
163  {
164  return new ParticleFieldAccessor<T>(-1);
165  }
166  return new ParticleFieldAccessor<T>(field);
167  }
168 
169  /// <summary>
170  /// Gets the field accessor for the given <see cref="ParticleFieldDescription{T}"/>.
171  /// If it doesn't exist, a new field will be created in the <see cref="ParticleSystem"/>.
172  /// </summary>
173  /// <typeparam name="T">The field type.</typeparam>
174  /// <param name="fieldDesc">The field description.</param>
175  /// <returns>A valid field accessor for the requested field.</returns>
176  public ParticleFieldAccessor<T> GetOrCreateField<T>(ParticleFieldDescription<T> fieldDesc) where T : struct
177  {
178  ParticleField field;
179  if (!fields.TryGetValue(fieldDesc, out field))
180  {
181  field = AddField(fieldDesc);
182  }
183  return new ParticleFieldAccessor<T>(field);
184  }
185 
186  /// <summary>
187  /// Gets the field accessor for the given <see cref="ParticleFieldDescription{T}"/>.
188  /// If it doesn't exist, a new field will be created in the <see cref="ParticleSystem"/>.
189  /// Weither the field exists or not, its default value will be changed to the supplied one.
190  /// </summary>
191  /// <typeparam name="T">The field type.</typeparam>
192  /// <param name="fieldDesc">The field description.</param>
193  /// <param name="defaultValue">The new field default value.</param>
194  /// <returns>A valid field accessor for the requested field.</returns>
195  public ParticleFieldAccessor<T> GetOrCreateFieldWithDefault<T>(ParticleFieldDescription<T> fieldDesc, T defaultValue) where T : struct
196  {
197  ParticleField field;
198  if (!fields.TryGetValue(fieldDesc, out field))
199  {
200  field = AddField(fieldDesc);
201  }
202  SetDefaultValue(field, defaultValue);
203  return new ParticleFieldAccessor<T>(field);
204  }
205 
206  /// <summary>
207  /// Sets the default value for the specified field.
208  /// </summary>
209  /// <typeparam name="T">The field type.</typeparam>
210  /// <param name="particleField">The field.</param>
211  /// <param name="defaultValue">The new field default value.</param>
212  internal void SetDefaultValue<T>(ParticleField particleField, T defaultValue) where T : struct
213  {
214  if (particleDefaultValue == IntPtr.Zero)
215  throw new InvalidOperationException();
216 
217  // Write new default value for the field
218  Utilities.Write(particleDefaultValue + particleField.Offset, ref defaultValue);
219  }
220 
221  /// <summary>
222  /// Adds a new field to the particle system.
223  /// </summary>
224  /// <typeparam name="T">The field type.</typeparam>
225  /// <param name="fieldDesc">The field description.</param>
226  /// <returns>The field.</returns>
227  /// <exception cref="System.ArgumentException">Particle field size must be a multiple of 4;size</exception>
228  internal ParticleField AddField<T>(ParticleFieldDescription<T> fieldDesc) where T : struct
229  {
230  ParticleField existingField;
231  if (fields.TryGetValue(fieldDesc, out existingField))
232  return existingField;
233 
234  var size = Utilities.SizeOf<T>();
235 
236  // Only accept 4-byte multiples.
237  if (size % 4 != 0)
238  throw new ArgumentException("Particle field size must be a multiple of 4", "size");
239 
240  var newParticleSize = particleSize + size;
241 
242  // Update default value
243  var newParticleDefaultValue = Utilities.AllocateMemory(newParticleSize);
244  if (particleDefaultValue != IntPtr.Zero)
245  {
246  Utilities.CopyMemory(newParticleDefaultValue, particleDefaultValue, particleSize);
247  Utilities.FreeMemory(particleDefaultValue);
248  }
249 
250  particleDefaultValue = newParticleDefaultValue;
251 
252  // Write default value for new field
253  var defaultValue = fieldDesc.DefaultValue;
254  Utilities.Write(newParticleDefaultValue + particleSize, ref defaultValue);
255 
256 
257  // Check if there is enough space to do the conversion in-place
258  if (particleData != IntPtr.Zero && particleCapacity * particleSize < newParticleSize * particleCount)
259  {
260  // Copy in-place, particles from back to front (so that there is no memory overlap)
261  // i.e.
262  // AAABBBCCCDDD gets copied that way:
263  // --------------D-
264  // -------------DD-
265  // ------------DDD-
266  // ----------C-DDD-
267  // ................
268  // AAA-BBB-CCC-DDD-
269  var particleSizeInDword = particleSize / 4;
270  unsafe
271  {
272  var particleEnd = (int*)(particleData + particleCount * particleSize);
273  var newParticleEnd = (int*)(particleData + particleCount * newParticleSize - size);
274  for (int i = particleCount - 1; i >= 0; --i)
275  {
276  // Copy one particle, from back to end (no overlap)
277  for (int j = 0; j < particleSizeInDword; ++j)
278  {
279  *--newParticleEnd = *--particleEnd;
280  }
281  // Leave space for the newly added field.
282  newParticleEnd -= size;
283 
284  // Write default value for new field
285  Utilities.Write((IntPtr)newParticleEnd, ref defaultValue);
286  }
287  }
288 
289  // Update capacity
290  particleCapacity = particleCapacity * particleSize / newParticleSize;
291  }
292  else if (particleCapacity > 0)
293  {
294  var newParticleData = Utilities.AllocateMemory(particleCapacity * newParticleSize);
295 
296  // Copy previous data to the new buffer, interleaved with space for the new field
297  if (particleData != IntPtr.Zero)
298  {
299  var newParticleDataPtr = newParticleData;
300  var particleDataPtr = particleData;
301  for (int i = 0; i < particleCount; ++i)
302  {
303  // Copy particle values
304  Utilities.CopyMemory(newParticleDataPtr, particleDataPtr, particleSize);
305  // Write default value for new field
306  Utilities.Write(newParticleDataPtr + particleSize, ref defaultValue);
307  particleDataPtr += particleSize;
308  newParticleDataPtr += newParticleSize;
309  }
310  Utilities.FreeMemory(particleData);
311  }
312 
313  particleData = newParticleData;
314  }
315 
316  // Add the field
317  var field = new ParticleField { Offset = particleSize, Size = size };
318  fields.Add(fieldDesc, field);
319 
320  // Update particle size
321  particleSize = newParticleSize;
322  return field;
323  }
324 
325  /// <summary>
326  /// Updates this instance.
327  /// </summary>
328  public void Update(float dt)
329  {
330  foreach (var plugin in Plugins)
331  {
332  plugin.Update(this, dt);
333  }
334  }
335 
336  /// <summary>
337  /// Adds the particle.
338  /// </summary>
339  /// <returns>The index of the newly added particle.</returns>
341  {
342  // Ensure there is enough space
343  if (ParticleCount == particleCapacity)
344  EnsureCapacity(ParticleCount + 1);
345 
346  // New particle will be stored at the end
347  var particle = new Particle(particleData + particleSize * particleCount);
348 
349  // Initialize particle to default values
350  Utilities.CopyMemory(particle.Pointer, particleDefaultValue, particleSize);
351 
352  particleCount++;
353  return particle;
354  }
355 
356  /// <summary>
357  /// Removes the particle at the specified index.
358  /// </summary>
359  /// <param name="particleIndex">Index of the particle.</param>
360  public void RemoveParticleAt(int particleIndex)
361  {
362  // If not removing last one, copy last one over removed one so that it can be done in O(1).
363  var lastParticleIndex = ParticleCount - 1;
364  if (particleIndex != lastParticleIndex)
365  {
366  Utilities.CopyMemory(particleData + particleIndex * particleSize, particleData + lastParticleIndex * particleSize, particleSize);
367  }
368 
369  particleCount = ParticleCount - 1;
370  }
371 
372  private void EnsureCapacity(int min)
373  {
374  if (ParticleCount < min)
375  {
376  int newCapacity = (particleCapacity == 0) ? particleDefaultCapacity : (particleCapacity * 2);
377  if (newCapacity < min)
378  newCapacity = min;
379 
380  if (newCapacity != particleCapacity)
381  {
382  // Allocate new buffer
383  var newParticleData = Utilities.AllocateMemory(newCapacity * particleSize);
384 
385  if (particleData != IntPtr.Zero)
386  {
387  // Copy only real particles
388  Utilities.CopyMemory(newParticleData, particleData, particleSize * particleCount);
389 
390  // Free previous buffer
391  Utilities.FreeMemory(particleData);
392  }
393 
394  // Update with new array
395  particleData = newParticleData;
396  particleCapacity = newCapacity;
397  }
398  }
399  }
400 
401  /// <summary>
402  /// A <see cref="Particle"/> enumerator.
403  /// </summary>
404  public struct Enumerator : IEnumerator<Particle>
405  {
406  private ParticleSystem particleSystem;
407  private int index;
408  private int particleSize;
409  private IntPtr particleData;
410 
411  /// <summary>
412  /// Initializes a new instance of the <see cref="Enumerator" /> struct.
413  /// </summary>
414  /// <param name="particleSystem">The particle system.</param>
415  public Enumerator(ParticleSystem particleSystem)
416  {
417  this.particleSystem = particleSystem;
418  this.particleSize = particleSystem.particleSize;
419  this.index = -1;
420  this.particleData = IntPtr.Zero;
421  }
422 
423  /// <summary>
424  /// Gets the index of the current particle.
425  /// </summary>
426  /// <value>
427  /// The index of the current particle.
428  /// </value>
429  public int Index
430  {
431  get { return index; }
432  set
433  {
434  // Updates index
435  index = value;
436 
437  // Updates particle data pointer.
438  particleData = particleSystem.particleData + particleSize * index;
439  }
440  }
441 
442  /// <summary>
443  /// Removes the current particle from the particle system.
444  /// The iterator will be placed at the previous particle, so that
445  /// next iteration with MoveNext() will point to the right particle.
446  /// </summary>
447  public void RemoveParticle()
448  {
449  particleSystem.RemoveParticleAt(Index--);
450  }
451 
452  /// <inheritdoc/>
453  public bool MoveNext()
454  {
455  // Move to next (and update pointer)
456  // Returns true if success (not out of bound)
457  return ++Index < particleSystem.ParticleCount;
458  }
459 
460  /// <inheritdoc/>
461  public void Dispose()
462  {
463  }
464 
465  /// <inheritdoc/>
466  public void Reset()
467  {
468  index = -1;
469  particleData = IntPtr.Zero;
470  }
471 
472  /// <inheritdoc/>
473  object IEnumerator.Current
474  {
475  get { return Current; }
476  }
477 
478  /// <inheritdoc/>
479  public Particle Current
480  {
481  get { return new Particle(particleData); }
482  }
483  }
484  }
485 }
Describes a field for a particle, which can store specific data for every particle.
static readonly ParticleFieldDescription< float > Angle
A particle field description for the particle Orientation (for billboard only).
A particle system, containing particles and their updaters.
Enumerator(ParticleSystem particleSystem)
Initializes a new instance of the Enumerator struct.
Specifies how to access a ParticleFieldDescription in a given ParticleSystem instance.
static readonly ParticleFieldDescription< Vector3 > Position
A particle field description for the particle position.
virtual void Dispose(bool disposing)
Releases unmanaged and - optionally - managed resources.
void Update(float dt)
Updates this instance.
Enumerator GetEnumerator()
Gets the Particle enumerator.
Listeners that can react on addition or removal to a ParticleSystem.
NotifyCollectionChangedAction Action
Gets the type of action performed. Allowed values are NotifyCollectionChangedAction.Add and NotifyCollectionChangedAction.Remove.
A particle in the particle system.
Definition: Particle.cs:12
Specifies how to access a ParticleFieldDescription{T} in a given ParticleSystem instance.
Particle AddParticle()
Adds the particle.
void RemoveParticle()
Removes the current particle from the particle system. The iterator will be placed at the previous pa...
_In_ size_t _In_ size_t size
Definition: DirectXTexP.h:175
ParticleSystem()
Initializes a new instance of the ParticleSystem class.
void RemoveParticleAt(int particleIndex)
Removes the particle at the specified index.