Chaotic Motion  1.0
ChaoticParticles.cs
1 using UnityEngine;
2 using System.Collections;
3 using System.Collections.Generic; // Dictionary
4 
20 [RequireComponent(typeof(ParticleSystem))]
21 [RequireComponent(typeof(ParticleSystemRenderer))]
23 
24  private ParticleSystem particalSystem;
25 
26  // Initial position in world space
27  private Vector3 initialPosition;
28  private ChaosEqn chaosEqn;
29 
30  // Define these as class variables so they can
31  // be alloc-ed once in Awake and not in every Evolve()
32  // Internal physics position
33  private float[,] x;
34  // Runge-Kutta intermediate values - define globally to avoid alloc per update
35  private float[] x_in;
36  private float[] a_n;
37  private float[] b_n;
38  private float[] c_n;
39  private float[] d_n;
40  private float[] arg;
41 
42  private ParticleSystem chaosParticles;
43  private ParticleSystem.Particle[] particles;
44  private int particleCount;
45 
46  /*
47  Need to keep a parallel copy of the physics co-ordinates for each particle and handle
48  particle creation/extinction. (In cases where e.g. scale by 0 to project system to plane
49  there is no way to invert this back to a physics co-ordinate - hence the need to have a
50  parallel copy in 3D for each particle.)
51 
52  As particles come and go they can shuffle in the particles[] array, so it becomes necessary
53  to check their identities via their random seed and keep the parallel physics array in
54  alignment.
55  */
56  // per-particle activity
57  private bool[] inactive;
58  private bool playing;
59 
60  private bool oneTimeBurst;
61 
62  private uint[] seed; // tracking array for random seed attached to each particle
63  private Dictionary<uint,int> particleBySeed;
64  private int lastParticleCount;
65 
66  private const float OUT_OF_VIEW = 10000f;
67 
68  // debug flag to monitor/understand the code
69  private const bool debugLogs = false;
70  private int debugCnt = 0;
71 
72  void Awake() {
73 
74  chaosParticles = GetComponent<ParticleSystem>();
75 
76  ParticleSystemRenderer psr = GetComponent<ParticleSystemRenderer>();
77  if (psr.renderMode == ParticleSystemRenderMode.Stretch) {
78  Debug.LogError("Stretch render mode does not work for ChaoticParticles");
79  }
80 
81  // init here so do not re-alloc each frame. Do not otherwise need to persist.
82  x_in = new float[3];
83  a_n = new float[3];
84  b_n = new float[3];
85  c_n = new float[3];
86  d_n = new float[3];
87  arg = new float[3];
88 
89  Init();
90 
91  // determine if this is a one-time burst scenario
92  int burstCount = 0;
93  ParticleSystem.Burst[] bursts = new ParticleSystem.Burst[chaosParticles.emission.burstCount];
94  chaosParticles.emission.GetBursts(bursts);
95  foreach (ParticleSystem.Burst burst in bursts) {
96  burstCount += burst.maxCount;
97  }
98  if (burstCount == chaosParticles.main.maxParticles) {
99  Debug.Log("One time burst");
100  oneTimeBurst = true;
101  }
102  particleBySeed = new Dictionary<uint,int>();
103  InitParticleData();
104  }
105 
106  public override void Init() {
108  initialPosition = transform.position;
109  TimeInit();
110  }
111 
112  private void InitParticleData() {
113  if (chaosParticles == null) {
114  Debug.LogError("Must be attached to a particle system object");
115  return;
116  }
117  // create array to hold particles
118  particles = new ParticleSystem.Particle[chaosParticles.main.maxParticles];
119  // get particles from the system (this fills in particles[])
120  chaosParticles.GetParticles(particles);
121  int maxParticles = chaosParticles.main.maxParticles;
122  #pragma warning disable 162 // disable unreachable code warning
123  if (debugLogs) {
124  Debug.Log("Init numParticles=" + maxParticles);
125  }
126  #pragma warning restore 162
127  x = new float[maxParticles,3];
128  seed = new uint[maxParticles];
129  inactive = new bool[maxParticles];
130  }
131 
139  private void InitParticles(int fromP, int toP) {
140  for (int i=fromP; i < toP; i++) {
141  x[i,0] = chaosEqn.paramBundle.initialPosition.x + particles[i].position.x - transform.position.x;
142  x[i,1] = chaosEqn.paramBundle.initialPosition.y + particles[i].position.y - transform.position.y;
143  x[i,2] = chaosEqn.paramBundle.initialPosition.z + particles[i].position.z - transform.position.z;
144  inactive[i] = false;
145  particles[i].velocity = Vector3.zero;
146  }
147  #pragma warning disable 162 // disable unreachable code warning
148  if (debugLogs) {
149  Debug.Log(string.Format("InitParticles from={0} to={1} pp_pos={2} phy_pos=({3},{4},{5})",
150  fromP, toP, particles[fromP].position, x[fromP,0], x[fromP,1], x[fromP,2] ));
151  }
152  #pragma warning restore 162
153  }
154 
155  //
156  // Emmisive particle management
157  // - per cycle look for new particles or cases where particles expired and were shuffled
158  //
159 
160  void ParticleLifeCycleHandler()
161  {
162  // Particle life cycle management
163  // - need GetParticles() call to get the correct number of active particle (p.particleCount did not work)
164  // - IIUC this is a re-copy and it would be better to avoid this if possible
165  particleCount = chaosParticles.GetParticles (particles);
166  if (lastParticleCount < particleCount) {
167  // there are new particles
168  InitParticles(lastParticleCount, chaosParticles.particleCount);
169  for (int i = lastParticleCount; i < chaosParticles.particleCount; i++) {
170  inactive[i] = false;
171  seed[i] = particles[i].randomSeed;
172  particleBySeed[particles[i].randomSeed] = i;
173  }
174  lastParticleCount = chaosParticles.particleCount;
175  }
176  if (oneTimeBurst) {
177  // not doing life cycle for this particle system
178  return;
179  }
180  // Check if any existing particles were replaced.
181  // As particles expire, Unity will move particles from the end down into their slot and reduce
182  // the number of active particles. Need to detect this and move their physics data.
183  // This makes emmisive particle systems more CPU intensive.
184  for (int i = 0; i < chaosParticles.particleCount; i++) {
185  if (seed[i] != particles[i].randomSeed) {
186  #pragma warning disable 162 // disable unreachable code warning
187  if (debugLogs)
188  Debug.Log("Seed changed was:" + seed[i] + " now:" + particles[i].randomSeed);
189  #pragma warning restore 162
190  // particle has been replaced
191  particleBySeed.Remove (seed [i]);
192  // remove old seed from hash
193  if (particleBySeed.ContainsKey (particles[i].randomSeed)) {
194  // particle was moved - copy physical data down
195  int oldIndex = particleBySeed[particles[i].randomSeed];
196  x[i, 0] = x[oldIndex, 0];
197  x[i, 1] = x[oldIndex, 1];
198  x[i, 2] = x[oldIndex, 2];
199  particleBySeed [particles[i].randomSeed] = i;
200  #pragma warning disable 162 // disable unreachable code warning
201  if (debugLogs)
202  Debug.Log("Shuffling particle from " + oldIndex + " to " + i);
203  #pragma warning restore 162
204  }
205  else {
206  #pragma warning disable 162 // disable unreachable code warning
207  if (debugLogs)
208  Debug.Log("Reusing particle " + i + " vel=" + particles[i].velocity);
209  #pragma warning restore 162
210  InitParticles(i, i+1);
211  particleBySeed[particles[i].randomSeed] = i;
212  #pragma warning disable 162 // disable unreachable code warning
213  if (debugLogs)
214  Debug.Log("Post-Setup Reusing particle " + i);
215  #pragma warning restore 162
216  }
217  seed[i] = particles[i].randomSeed;
218  inactive[i] = false;
219  }
220  }
221  }
222 
223  // Evolve the dynamical system using a 4th order RK integrator
224  private void Evolve(float h) {
225  // do nothing if all inactive
226  if (inactive == null) {
227  return; // Particle system has not init-ed yet or is done
228  }
229  // (did not work in Start() -> Unity bug? Init sequencing?)
230  if (!playing) {
231  chaosParticles.Play();
232  playing = true;
233  }
234 
235  for (int j=0; j < particleCount; j++) {
236  if (!inactive[j]) {
237 
238  Vector3 xout = Vector3.zero;
239  x_in[0] = x[j,0];
240  x_in[1] = x[j,1];
241  x_in[2] = x[j,2];
242  chaosEqn.Function(ref x_in, ref a_n);
243  //Debug.Log("j=" + j + " x=" + x[j,0] + " arg0=" + arg[0] + " a_n0=" + a_n[0]);
244  float h_frac = h/2f;
245  arg[0] = x[j,0] + h_frac * a_n[0];
246  arg[1] = x[j,1] + h_frac * a_n[1];
247  arg[2] = x[j,2] + h_frac * a_n[2];
248  chaosEqn.Function(ref arg, ref b_n);
249  arg[0] = x[j,0] + h_frac * b_n[0];
250  arg[1] = x[j,1] + h_frac * b_n[1];
251  arg[2] = x[j,2] + h_frac * b_n[2];
252  chaosEqn.Function(ref arg, ref c_n);
253  h_frac = h;
254  arg[0] = x[j,0] + h_frac * c_n[0];
255  arg[1] = x[j,1] + h_frac * c_n[1];
256  arg[2] = x[j,2] + h_frac * c_n[2];
257  chaosEqn.Function(ref arg, ref d_n);
258  h_frac = h/6f;
259  x[j,0] = x[j,0] + h_frac*(a_n[0] + 2f * b_n[0] + 2f * c_n[0] + d_n[0]);
260  x[j,1] = x[j,1] + h_frac*(a_n[1] + 2f * b_n[1] + 2f * c_n[1] + d_n[1]);
261  x[j,2] = x[j,2] + h_frac*(a_n[2] + 2f * b_n[2] + 2f * c_n[2] + d_n[2]);
262  if (double.IsNaN(x[j,0]) || double.IsNaN(x[j,1]) || double.IsNaN(x[j,2])) {
263  inactive[j] = true;
264  }
265  }
266  }
267  }
268 
269  void FixedUpdate() {
270 
271  // need a new copy of particles every frame (they age out, shuffle etc.)
272  particles = new ParticleSystem.Particle[chaosParticles.main.maxParticles];
273  ParticleLifeCycleHandler();
274 
275  int numSteps = CalcNumSteps();
276 
277  for (int i=0; i < numSteps; i++) {
278  // position is in the scale of the dynamical system. Apply the
279  // transform scale
280  Evolve(DT);
281  }
282  UpdateParticles();
283  }
284 
285 
291  public void UpdateParticles() {
292  Vector3 phy_pos;
293  for (int i=0; i < lastParticleCount; i++) {
294  if (!inactive[i]) {
295  // particles are in physics space - map back to world space
296  phy_pos = new Vector3((float) x[i,0], (float) x[i,1],(float) x[i,2]);
297  phy_pos = (phy_pos + chaosEqn.paramBundle.offset) * chaosEqn.paramBundle.scale;
298  particles[i].position = Vector3.Scale(phy_pos, evolveScale) + initialPosition;
299  particles[i].position = transform.rotation * particles[i].position;
300  }
301  }
302  chaosParticles.SetParticles(particles, particleCount);
303  #pragma warning disable 162, 429 // disable unreachable code warning
304  if (debugLogs && debugCnt++ > 30) {
305  debugCnt = 0;
306  string log = "time = " + Time.time + " remaining=" + particleCount ;
307  log += " is Stopped " + chaosParticles.isStopped + " num=" + chaosParticles.particleCount + " pcount=" + particleCount + "\n";
308  int logTo = 1; //(chaosParticles.main.maxParticles < 10) ? chaosParticles.main.maxParticles : 10;
309  for (int i=0; i < logTo; i++) {
310  log += string.Format("{0} rand={1} life={2} inactive={3} ", i, particles[i].randomSeed, particles[i].remainingLifetime, inactive[i]);
311  log += " pos=" + particles[i].position ;
312  log += " phyPos= " + x[i,0] + " " + x[i,1] + " " + x[i,2];
313  log += "\n";
314  }
315  Debug.Log(log);
316  }
317  #pragma warning restore 162, 429
318  }
319 
320 }
ParamBundle paramBundle
The parameter bundle that is used to evolve the system.
Definition: ChaosEqn.cs:19
ParamBundle customParams
Custom parameter values for evolution (valid only if selectedParams exceeds number of params listed i...
static ChaosEqn Create(int index, int paramIndex, ParamBundle customParams)
Create the ChaosEqn implementation for the specified index in the eqnList, with the selected param bu...
Chaotic system. Base class for chaotic scripts. Holds the type of system selected, the parameter set or the custom parameters to be used.
float scale
scale to ensure that attractor fits in box of size 10
Definition: ParamBundle.cs:25
Chaotic particles.
int selectedEqn
Number of chaos system to evolve (w.r.t. ChaosFactory list)
void UpdateParticles()
Updates the particles positions in world space.
abstract void Function(ref float[] x_in, ref float[] x_dot)
Evaluate the first order evolution of the attractor, given the current position.
Vector3 evolveScale
Scale to apply to phyics evolution when mapping back to world space.
Vector3 initialPosition
Initial position for evolution in physics space.
Definition: ParamBundle.cs:28
Chaos eqn. Base class for all equations that define a 3D chaotic system.
Definition: ChaosEqn.cs:9
Vector3 offset
offset to center attractor at local origin
Definition: ParamBundle.cs:22
int selectedParams
Number of parameter bundle selected for evolution (per chaotic equation)