[NUI] Particle System binding and implementation of C# side
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / ParticleSystem / ParticleEmitter.cs
1 /*
2  * Copyright(c) 2023 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using System.Collections.Generic;
20 using System.Runtime.InteropServices;
21 using System.ComponentModel;
22
23 namespace Tizen.NUI.ParticleSystem
24 {
25     using Tizen.NUI.BaseComponents;
26
27     /// <summary>
28     /// Enum defining blending options when rendering the particles.
29     /// </summary>
30     public enum ParticleBlendingMode
31     {
32         Additive = 0,
33         Screen = 1,
34         Default = Additive
35     }
36     
37     /// <summary>
38     /// Internal class defining data types stored in the data streams
39     /// </summary>
40     internal enum StreamType
41     {
42         Float = 0,
43         FloatVector2 = 1,
44         FloatVector3 = 2,
45         FloatVector4 = 3,
46         Integer = 4,
47         IntVector2 = 5,
48         IntVector3 = 6,
49         IntVector4 = 7,
50     }
51     
52     /// <summary>
53     /// Class ParticleEmitter creates a single emitter attached to a specified
54     /// View. ParticleEmitter is responsible for spawning and updating particles.
55     ///
56     /// Emitter must contain:
57     /// ParticleSource - responsible for spawning new particles
58     /// ParticleModifier(s) - responsible for updating particles in the system
59     ///
60     /// ParticleSource and ParticleModifier callback interfaces should not be accessing
61     /// Event side (NUI) objects. Both callbacks are executed on Update thread.
62     /// </summary>
63     public class ParticleEmitter : BaseHandle
64     {
65         internal ParticleEmitter(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
66         {
67         }
68
69         /// <summary>
70         /// Create an initialized ParticleEmitter.
71         /// </summary>
72         /// <param name="view">View to attach the particle emitter.</param>
73         [EditorBrowsable(EditorBrowsableState.Never)]
74         public ParticleEmitter(View view) : this(Interop.ParticleEmitter.New(view.SwigCPtr), true)
75         {
76             mProxy = new ParticleEmitterProxy(this);
77             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
78         }
79         
80         /// <summary>
81         /// Copy constructor.
82         /// </summary>
83         /// <param name="particleEmitter">Source object to copy.</param>
84         [EditorBrowsable(EditorBrowsableState.Never)]
85         public ParticleEmitter( ParticleEmitter particleEmitter) : this(Interop. ParticleEmitter.New( ParticleEmitter.getCPtr(particleEmitter)), true)
86         {
87             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
88         }
89
90         /// <summary>
91         /// Assignment operator.
92         /// </summary>
93         /// <param name="particleEmitter">Source object to be assigned.</param>
94         /// <returns>Reference to this.</returns>
95         internal ParticleEmitter Assign( ParticleEmitter particleEmitter)
96         {
97             ParticleEmitter ret = new ParticleEmitter(Interop.ParticleEmitter.Assign(SwigCPtr, ParticleEmitter.getCPtr(particleEmitter)), false);
98             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
99             return ret;
100         }
101         
102         /// <summary>
103         /// Raises the window to the top of the window stack.
104         /// </summary>
105         /// <param name="particleEmitter">Source object to copy.</param>
106         [EditorBrowsable(EditorBrowsableState.Never)]
107         public void SetSource<T>(ParticleSource<T> source) where T : ParticleSourceInterface, new()
108         {
109             // update interface
110             source.SetEmitter(this);
111             
112             // Set native source
113             Interop.ParticleEmitter.SetSource(SwigCPtr, source.SwigCPtr);
114             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
115         }
116         
117         /// <summary>
118         /// Maximum particle count
119         /// </summary>
120         [EditorBrowsable(EditorBrowsableState.Never)]
121         public uint ParticleCount
122         {
123             get
124             {
125                 var value = Interop.ParticleEmitter.GetParticleCount(SwigCPtr);
126                 if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
127
128                 return value;
129             }
130             set
131             {
132                 Interop.ParticleEmitter.SetParticleCount(SwigCPtr, value);
133                 if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
134             }
135         }
136
137         /// <summary>
138         /// Rate of emission per second
139         /// </summary>
140         /// <remarks>
141         /// EmissionRate defines number of particles emitted per second.
142         /// </remarks>
143         [EditorBrowsable(EditorBrowsableState.Never)]
144         public uint EmissionRate
145         {
146             get
147             {
148                 var value = Interop.ParticleEmitter.GetEmissionRate(SwigCPtr);
149                 if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
150                 return value;
151             }
152             set
153             {
154                 Interop.ParticleEmitter.SetEmissionRate(SwigCPtr, value);
155                 if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
156             }
157         }
158         
159         /// <summary>
160         /// Initial particle count
161         /// </summary>
162         /// <remarks>
163         /// Initial number of particles to be emitted immediately after emitter starts. It allows
164         /// initial burst emission. By default it's set to 0.
165         /// </remarks>
166         [EditorBrowsable(EditorBrowsableState.Never)]
167         public uint InitialParticleCount
168         {
169             get
170             {
171                 var value = Interop.ParticleEmitter.GetInitialParticleCount(SwigCPtr);
172                 if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
173                 return value;
174             }
175             set 
176             {
177                 Interop.ParticleEmitter.SetInitialParticleCount(SwigCPtr, value);
178                 if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve(); 
179             }
180         }
181         
182         /// <summary>
183         /// Limit of active particles in the system
184         /// </summary>
185         /// <remarks>
186         /// Active particles in the system can be limited without changing <see cref="ParticleCount"/>.
187         /// </remarks>
188         [EditorBrowsable(EditorBrowsableState.Never)]
189         public uint ActiveParticleLimit
190         {
191             get{
192                 var value = Interop.ParticleEmitter.GetActiveParticlesLimit(SwigCPtr);
193                 if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
194                 return value;
195             }
196             set 
197             {
198                 Interop.ParticleEmitter.SetActiveParticlesLimit(SwigCPtr, value);
199                 if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
200             }
201             
202         }
203
204         /// <summary>
205         /// Gets/sets blending mode for particle renderer
206         /// </summary>
207         /// <remarks>
208         /// Currently two blending modes are supported: Additive and Screen (advanced blending mode).
209         /// <see cref="ParticleBlendingMode"/>
210         /// </remarks>
211         [EditorBrowsable(EditorBrowsableState.Never)]
212         public ParticleBlendingMode RendererBlendingMode
213         {
214             get
215             {                
216                 var value = Interop.ParticleEmitter.GetBlendingMode(SwigCPtr);
217                 if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
218                 return (ParticleBlendingMode)value;
219             }
220             set
221             {
222                 Interop.ParticleEmitter.SetBlendingMode(SwigCPtr, value);
223                 if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
224             }
225         }
226         
227         /// <summary>
228         /// Gets/sets texture to be used by the renderer
229         /// </summary>
230         [EditorBrowsable(EditorBrowsableState.Never)]
231         public Texture RendererTexture
232         {
233             set
234             {
235                 Interop.ParticleEmitter.SetTexture(SwigCPtr, value.SwigCPtr);
236                 if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
237             }
238         }
239         
240         /// <summary>
241         /// Adds ParticleModifier to the stack
242         /// </summary>
243         /// <remarks>
244         /// ParticleEmitter implements a stack of modifiers which are responsible for
245         /// updating particles in the system. The stack is processed such as result of
246         /// previous modifier is an input for next modifier.
247         /// </remarks>
248         /// <param name="modifier">Valid modifier object</param>
249         [EditorBrowsable(EditorBrowsableState.Never)]
250         public void AddModifier<T>(ParticleModifier<T> modifier) where T : ParticleModifierInterface, new()
251         {
252             // update interface
253             modifier.SetEmitter(this);
254             
255             Interop.ParticleEmitter.AddModifier(SwigCPtr, modifier.SwigCPtr);
256             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
257         }
258
259         /// <summary>
260         /// Returns associated ParticleSource object
261         /// </summary>
262         /// <returns>Valid ParticleSource object or null</returns>
263         [EditorBrowsable(EditorBrowsableState.Never)]
264         public ParticleSource<T> GetSource<T>() where T : ParticleSourceInterface, new()
265         {
266             IntPtr cPtr = Interop.ParticleEmitter.GetSource(SwigCPtr);
267             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
268             ParticleSource<T> ret = (cPtr == IntPtr.Zero) ? null : Registry.GetManagedBaseHandleFromNativePtr(cPtr) as ParticleSource<T>;
269             return ret;
270         }
271
272         /// <summary>
273         /// Returns modifier at specified index
274         /// </summary>
275         /// <param name="index">Index within modifier stack</param>
276         /// <returns>Valid ParticleModifier object or null</returns>
277         [EditorBrowsable(EditorBrowsableState.Never)]
278         public ParticleModifier<ParticleModifierInterface> GetModifierAt(uint index)
279         {
280             IntPtr cPtr = Interop.ParticleEmitter.GetModifierAt(SwigCPtr, index);
281             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
282             ParticleModifier<ParticleModifierInterface> ret = (cPtr == IntPtr.Zero) ? null : Registry.GetManagedBaseHandleFromNativePtr(cPtr) as ParticleModifier<ParticleModifierInterface>;
283             return ret;
284         }
285
286         /// <summary>
287         /// Starts emission of particles.
288         /// </summary>
289         [EditorBrowsable(EditorBrowsableState.Never)]
290         public void Start()
291         {
292             Interop.ParticleEmitter.Start(SwigCPtr);
293             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
294         }
295
296         /// <summary>
297         /// Stops emission of particles.
298         /// </summary>
299         [EditorBrowsable(EditorBrowsableState.Never)]
300         public void Stop()
301         {
302             Interop.ParticleEmitter.Stop(SwigCPtr);
303             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
304         }
305         
306         /// <summary>
307         /// Adds local (not used by shader) data stream to the particle emitter
308         /// </summary>
309         /// <remarks>
310         /// Adds new stream of float type.
311         /// </remarks>
312         /// <param name="defaultValue">Default value to fill the stream with</param>
313         /// <returns>Index of newly created data stream</returns>
314         [EditorBrowsable(EditorBrowsableState.Never)]
315         public unsafe uint AddLocalStreamFloat(float defaultValue)
316         {
317             var result = Interop.ParticleEmitter.AddLocalStream(SwigCPtr, (uint)StreamType.Float, &defaultValue, sizeof(float));
318             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
319             return result;
320         }
321         
322         /// <summary>
323         /// Adds local (not used by shader) data stream to the particle emitter
324         /// </summary>
325         /// <remarks>
326         /// Adds new stream of Vector2 type.
327         /// </remarks>
328         /// <param name="defaultValue">Default value to fill the stream with</param>
329         /// <returns>Index of newly created data stream</returns>
330         [EditorBrowsable(EditorBrowsableState.Never)]
331         public unsafe uint AddLocalStreamVector2(Vector2 defaultValue)
332         {
333             var result = Interop.ParticleEmitter.AddLocalStream(SwigCPtr, (uint)StreamType.FloatVector2, (void*)defaultValue.SwigCPtr.Handle, sizeof(float)*2);
334             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
335             return result;
336         }
337         
338         /// <summary>
339         /// Adds local (not used by shader) data stream to the particle emitter
340         /// </summary>
341         /// <remarks>
342         /// Adds new stream of Vector3 type.
343         /// </remarks>
344         /// <param name="defaultValue">Default value to fill the stream with</param>
345         /// <returns>Index of newly created data stream</returns>
346         [EditorBrowsable(EditorBrowsableState.Never)]
347         public unsafe uint AddLocalStreamVector3(Vector3 defaultValue)
348         {
349             var result = Interop.ParticleEmitter.AddLocalStream(SwigCPtr, (uint)StreamType.FloatVector3, (void*)defaultValue.SwigCPtr.Handle, sizeof(float)*3);
350             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
351             return result;
352         }
353         
354         /// <summary>
355         /// Adds local (not used by shader) data stream to the particle emitter
356         /// </summary>
357         /// <remarks>
358         /// Adds new stream of Vector4 type.
359         /// </remarks>
360         /// <param name="defaultValue">Default value to fill the stream with</param>
361         /// <returns>Index of newly created data stream</returns>
362         [EditorBrowsable(EditorBrowsableState.Never)]
363         public unsafe uint AddLocalStreamVector4(Vector4 defaultValue)
364         {
365             var result = Interop.ParticleEmitter.AddLocalStream(SwigCPtr, (uint)StreamType.FloatVector4, (void*)defaultValue.SwigCPtr.Handle, sizeof(float)*4);
366             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
367             return result;
368         }
369
370         // Internal proxy object to be used on the update thread
371         internal ParticleEmitterProxy EmitterProxy => mProxy;
372         private ParticleEmitterProxy mProxy = null;
373     }
374     
375     /// <summary>
376     /// This class provides functionality that can be used inside the Source/Modifier callbacks.
377     /// </summary>
378     [EditorBrowsable(EditorBrowsableState.Never)]
379     public class ParticleEmitterProxy
380     {
381         internal ParticleEmitterProxy(ParticleEmitter emitter)
382         {
383             mEmitterBasePtr = emitter.SwigCPtr.Handle;
384             mEmitter = emitter;
385         }
386         
387         /// <summary>
388         /// Creates new particle
389         /// </summary>
390         /// <remarks>
391         /// Function may fail and return null if current number of particles exceeds limits of emitter.
392         /// Particle is valid only inside the callback and must not be stored and used anywhere else. Otherwise
393         /// the behaviour is undefined.
394         /// </remarks>
395         /// <param name="lifetime">Lifetime of the particle in seconds</param>
396         /// <returns>New Particle object or null</returns>
397         [EditorBrowsable(EditorBrowsableState.Never)]
398         public Particle NewParticle( float lifetime )
399         {
400             var result = Interop.ParticleEmitter.NewParticle(mEmitterBasePtr, lifetime);
401             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
402             if (result >= 0)
403             {
404                 // TODO: new particles should be coming form cached queue
405                 return new Particle(mEmitter.SwigCPtr, (uint)result);
406             }
407             return null;
408         }
409         
410         /// <summary>
411         /// Acquires list of Particles of specified length
412         /// </summary>
413         /// <remarks>
414         /// The function should be use internally only. Native side passes list of indices of particles (int[]).
415         /// Before calling the callback the indices must be marshalled and converted into the Particle objects.
416         ///
417         /// Internal Particle cache is used to speed up acquiring new Particle.
418         /// </remarks>
419         /// <param name="nativePtr">Native pointer to the list of indices (int32)</param>
420         /// <param name="count">Number of elements</param>
421         /// <returns>List of Particle objects</returns>
422         [EditorBrowsable(EditorBrowsableState.Never)]
423         internal List<Particle> AcquireParticleList(IntPtr nativePtr, uint count)
424         {
425             // Populate enough particles into cache
426             while(mParticleCache.Count < count)
427             {
428                 mParticleCache.Push(new Particle(mEmitter.SwigCPtr, 0));
429             }
430             
431             List<Particle> retval = new List<Particle>();
432             for (var i = 0; i < count; ++i)
433             {
434                 var particleIndex = Marshal.ReadInt32(nativePtr, i * 4);
435                 Particle p = mParticleCache.Pop();
436                 p.Index = (uint)particleIndex;
437                 retval.Add(p);
438             }
439
440             return retval;
441         }
442
443         /// <summary>
444         /// Releases list of Particles back into the pool
445         /// </summary>
446         /// <remarks>
447         /// Acquired particles come from internal pool and must be returned so then they can
448         /// be recycled.
449         /// </remarks>
450         /// <param name="particles">List of particles to be returned</param>
451         [EditorBrowsable(EditorBrowsableState.Never)]
452         internal void ReleaseParticleList(List<Particle> particles)
453         {
454             // return particles back into the pull
455             for(var i = 0; i < particles.Count; ++i)
456             {
457                 mParticleCache.Push(particles[i]);
458             }
459             
460             // clear the list (probably not needed?)
461             particles.Clear();
462         }
463         
464         /// <summary>
465         /// Adds local particle data stream of float values
466         /// </summary>
467         /// <param name="defaultValue">Default value</param>
468         /// <returns>Index of new stream</returns>
469         [EditorBrowsable(EditorBrowsableState.Never)]
470         public uint AddLocalStreamFloat(float defaultValue)
471         {
472             return mEmitter.AddLocalStreamFloat(defaultValue);
473         }
474         
475         /// <summary>
476         /// Adds local particle data stream of Vector2 values
477         /// </summary>
478         /// <param name="defaultValue">Default value</param>
479         /// <returns>Index of new stream</returns>
480         [EditorBrowsable(EditorBrowsableState.Never)]
481         public uint AddLocalStreamVector2(Vector2 defaultValue)
482         {
483             return mEmitter.AddLocalStreamVector2(defaultValue);
484         }
485         
486         /// <summary>
487         /// Adds local particle data stream of Vector3 values
488         /// </summary>
489         /// <param name="defaultValue">Default value</param>
490         /// <returns>Index of new stream</returns>
491         [EditorBrowsable(EditorBrowsableState.Never)]
492         public uint AddLocalStreamVector3(Vector3 defaultValue)
493         {
494             return mEmitter.AddLocalStreamVector3(defaultValue);
495         }
496         
497         /// <summary>
498         /// Adds local particle data stream of Vector4 values
499         /// </summary>
500         /// <param name="defaultValue">Default value</param>
501         /// <returns>Index of new stream</returns>
502         [EditorBrowsable(EditorBrowsableState.Never)]
503         public uint AddLocalStreamVector4(Vector4 defaultValue)
504         {
505             return mEmitter.AddLocalStreamVector4(defaultValue);
506         }
507         
508         // Stack of cached particles
509         private Stack<Particle> mParticleCache = new Stack<Particle>();
510         
511         private IntPtr mEmitterBasePtr;
512         private ParticleEmitter mEmitter;
513
514     }
515
516     /// <summary>
517     /// Register binding Source/Modifier interface to the pointer of a native counterpart 
518     /// </summary>
519     /// <typeparam name="T">Class type of objects to be stored in the register</typeparam>
520     internal class ParticleInterfaceRegister<T> where T : class
521     {
522         internal void Register(IntPtr cPtr, T iface)
523         {
524             lock (mBasePtr)
525             {
526                 mBasePtr.Add(cPtr);
527                 mInterfaces.Add(iface);
528             }
529         }
530
531         internal bool Remove(IntPtr cPtr)
532         {
533             lock (mBasePtr)
534             {
535                 var result = mBasePtr.FindIndex(0, x => x == cPtr);
536                 if (result >= 0)
537                 {
538                     mBasePtr.RemoveAt(result);
539                     mInterfaces.RemoveAt(result);
540                 }
541
542                 return result >= 0;
543             }
544         }
545
546         internal bool Remove(T iface)
547         {
548             lock (mBasePtr)
549             {
550                 var result = mInterfaces.FindIndex(0, x => x.Equals(iface));
551                 if (result >= 0)
552                 {
553                     mBasePtr.RemoveAt(result);
554                     mInterfaces.RemoveAt(result);
555                 }
556
557                 return result >= 0;
558             }
559         }
560         
561         internal T Get(IntPtr cPtr)
562         {
563             var result = mBasePtr.FindIndex(0, x => x == cPtr );
564             if (result >= 0)
565             {
566                 return mInterfaces[result];
567             }
568
569             return null;
570         }
571         
572         private List<IntPtr> mBasePtr = new List<IntPtr>();
573         private List<T> mInterfaces = new List<T>();
574     }
575 }