[NUI.Samples] Add Semi-Particle system
authorEunki Hong <eunkiki.hong@samsung.com>
Tue, 20 May 2025 19:34:42 +0000 (04:34 +0900)
committerWoochan <48237284+lwc0917@users.noreply.github.com>
Thu, 22 May 2025 05:52:09 +0000 (14:52 +0900)
Add simple particle system demo only by using `View`.

Note : This demo use deprecated class - `Renderer`. We should remove it in future.

Signed-off-by: Eunki Hong <eunkiki.hong@samsung.com>
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/SemiParticleSystem.cs [new file with mode: 0644]

diff --git a/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/SemiParticleSystem.cs b/test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/SemiParticleSystem.cs
new file mode 100644 (file)
index 0000000..6534ae5
--- /dev/null
@@ -0,0 +1,384 @@
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using global::System;
+using global::System.Collections.Generic;
+using global::System.ComponentModel;
+using global::System.Reflection;
+
+namespace Tizen.NUI.Samples
+{
+    public class SemiParticleSystem : IExample
+    {
+        public class ParticleSystem : IDisposable
+        {
+            private class Particle : IDisposable
+            {
+                private static readonly Random s_random = new Random();
+
+                private bool _disposed;
+                private View _rootView;
+                private View _view;
+                private PropertyNotification _culledNotification;
+
+                private Vector2 _initialPosition;
+                private Vector2 _initialSize;
+                private float _finalOpacity;
+                private float _blurRadius;
+                private Animation _animation;
+
+                private uint _totalDurationMs;
+                private float _rootLayerWidth;
+
+                public Particle(View rootView, float rootLayerWidth, uint totalDurationMs)
+                {
+                    _rootView = rootView;
+                    _rootLayerWidth = rootLayerWidth;
+                    _totalDurationMs = totalDurationMs;
+
+                    // Generate random position of view
+                    GenerateRandomValues();
+                    GenerateView();
+                    GenerateAnimation();
+                }
+
+                ~Particle() => Dispose(false);
+
+                public void Dispose()
+                {
+                    Dispose(true);
+                }
+
+                protected virtual void Dispose(bool disposing)
+                {
+                    if(_disposed)
+                    {
+                        return;
+                    }
+                    if(disposing)
+                    {
+                        _view?.Dispose();
+                        _view = null;
+
+                        if (_animation != null)
+                        {
+                            //_animation.Finished -= OnFinished;
+                            _animation.Clear();
+                            _animation.Dispose();
+                            _animation = null;
+                        }
+
+                        _initialPosition?.Dispose();
+                        _initialSize?.Dispose();
+
+                        _rootView = null;
+                    }
+                    _disposed = true;
+                }
+
+                private void OnFinished(object o, EventArgs e)
+                {
+                    // Play again
+                    if(_view != null)
+                    {
+                        GenerateRandomValues();
+                        GenerateView();
+                        GenerateAnimation();
+                        _animation.Play();
+                    }
+                }
+
+                private void GenerateAnimation()
+                {
+                    if(_animation == null)
+                    {
+                        _animation = new Animation((int)_totalDurationMs);
+                        _animation.Finished += OnFinished;
+                    }
+                    else
+                    {
+                        _animation.Clear();
+                    }
+
+                    _animation.AnimateTo(_view, "PositionX", _initialPosition.X * 10.0f);
+                    _animation.AnimateTo(_view, "PositionY", _initialPosition.Y * 10.0f);
+                    _animation.AnimateTo(_view, "SizeWidth", _initialSize.X * 2.0f, 0, (int)_totalDurationMs / 10, new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOut));
+                    _animation.AnimateTo(_view, "SizeWidth", 0.0f, 0, (int)_totalDurationMs);
+                    _animation.AnimateTo(_view, "SizeHeight", _initialSize.X * 2.0f, 0, (int)_totalDurationMs / 10, new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOut));
+                    _animation.AnimateTo(_view, "SizeHeight", 0.0f, 0, (int)_totalDurationMs);
+                    _animation.AnimateTo(_view, "Opacity", _finalOpacity, 0, (int)_totalDurationMs / 10);
+
+                    _animation.Play();
+                }
+                private void GenerateView()
+                {
+                    if(_view == null)
+                    {
+                        _view = new View()
+                        {
+                            PositionX = _initialPosition.X,
+                            PositionY = _initialPosition.Y,
+                            SizeWidth = _initialSize.X,
+                            SizeHeight = _initialSize.Y,
+                            BoxShadow = new Shadow(_blurRadius, Color.White),
+                            Opacity = 0.0f,
+                            CornerRadius = 0.5f,
+                            CornerRadiusPolicy = VisualTransformPolicyType.Relative,
+                        };
+                    }
+                    else
+                    {
+                        _view.PositionX = _initialPosition.X;
+                        _view.PositionY = _initialPosition.Y;
+                        _view.SizeWidth = _initialSize.X;
+                        _view.SizeHeight = _initialSize.Y;
+                        _view.Opacity = 0.0f;
+                    }
+
+                    _rootView?.Add(_view);
+
+                    if(_culledNotification == null)
+                    {
+                        _culledNotification = _view.AddPropertyNotification("Culled", PropertyCondition.GreaterThan(0.5f));
+                        _culledNotification.SetNotifyMode(PropertyNotification.NotifyMode.NotifyOnTrue);
+                        _culledNotification.Notified += (o, e) => {
+                            // Automatically unparent for reduce rendering process.
+                            //_view?.Unparent();
+                            _animation?.Stop();
+                        };
+
+                        //Tizen.Log.Error("NUI", $"{_view.GetRendererCount()}");
+
+                        // Deprecated API used!
+                        var renderer = _view.GetRendererAt(0u);
+                        renderer.BlendFactorSrcRgb = (int)BlendFactorType.SrcAlpha;
+                        renderer.BlendFactorSrcAlpha = (int)BlendFactorType.SrcAlpha;
+                        renderer.BlendFactorDestRgb = (int)BlendFactorType.One;
+                        renderer.BlendFactorDestAlpha = (int)BlendFactorType.OneMinusSrcAlpha;
+                    }
+                }
+
+                private void GenerateRandomValues()
+                {
+                    double positoinFactor = (double)_rootLayerWidth * 0.1;
+                    double sizeFactor = (double)_rootLayerWidth * 0.03;
+
+                    double radius = s_random.NextDouble() * positoinFactor + positoinFactor;
+                    double angle = s_random.NextDouble() * 2.0 * Math.PI;
+                    double size = s_random.NextDouble() * sizeFactor;
+
+                    _initialPosition = new Vector2((float)(radius * Math.Cos(angle)), (float)(radius * Math.Sin(angle)));
+                    _initialSize =  new Vector2((float)size, (float)size);
+
+                    _blurRadius = (float)(size * s_random.NextDouble()) * 0.9f + 0.2f;
+                    _finalOpacity = (float)(s_random.NextDouble() * 0.5 + 0.5);
+                }
+            };
+
+            private static Lazy<ParticleSystem> _instance = new();
+            private bool _disposed;
+            private Window _window;
+            private Layer _rootLayer;
+            private View _root;
+
+            private List<Particle> _particleList;
+            private Timer _timer;
+            private uint _particleCount;
+            private uint _emitIntervalMs;
+            private float _rootLayerWidth;
+
+            private Animation _rootRotateAnimation;
+
+            public uint ParticleCount
+            {
+                get
+                {
+                    return _particleCount;
+                }
+                set
+                {
+                    _particleCount = value;
+                    if (_particleList != null)
+                    {
+                        while (_particleList.Count > _particleCount)
+                        {
+                            Particle particle = _particleList[_particleList.Count - 1];
+                            particle?.Dispose();
+                            _particleList.RemoveAt(_particleList.Count - 1);
+                        }
+                    }
+                    if (_timer != null)
+                    {
+                        if (_particleList.Count < _particleCount)
+                        {
+                            _timer.Start();
+                        }
+                    }
+                }
+            }
+
+            public uint ParticleEmitDurationMs
+            {
+                get
+                {
+                    return _emitIntervalMs;
+                }
+                set
+                {
+                    _emitIntervalMs = value;
+                    if (_timer  != null)
+                    {
+                        _timer.Interval = _emitIntervalMs;
+                    }
+                }
+            }
+
+            public static ParticleSystem Instance => _instance.Value;
+
+            public ParticleSystem()
+            {
+                ParticleCount = 100;
+                ParticleEmitDurationMs = 100;
+
+                _root = new View()
+                {
+                    PivotPoint = PivotPoint.Center,
+                    ParentOrigin = ParentOrigin.Center,
+                };
+            }
+
+            ~ParticleSystem() => Dispose(false);
+
+            public void SetRootWindow(Window window)
+            {
+                _window = window;
+                _rootLayer = window.GetDefaultLayer();
+
+                _rootLayer.Add(_root);
+
+                _rootLayerWidth = window.WindowSize.Width;
+
+                _window.Resized += OnWindowResized;
+            }
+
+            public void Start()
+            {
+                if (_particleList == null)
+                {
+                    _particleList = new();
+                }
+                if (_timer == null)
+                {
+                    _timer = new Timer(_emitIntervalMs);
+                    _timer.Tick += OnTick;
+                }
+                if (_rootRotateAnimation == null)
+                {
+                    _rootRotateAnimation = new Animation((int)((_emitIntervalMs * _particleCount) * 7.3f));
+                    _rootRotateAnimation.AnimateBy(_root, "Orientation", new Rotation(new Radian(new Degree(360.0f)), Vector3.ZAxis), new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseInOut));
+                    _rootRotateAnimation.LoopingMode = Animation.LoopingModes.AutoReverse;
+                    _rootRotateAnimation.Looping = true;
+                }
+
+                _timer.Start();
+                _rootRotateAnimation.Play();
+            }
+
+            public void Stop()
+            {
+                _timer?.Stop();
+                _rootRotateAnimation?.Stop();
+            }
+
+            public void Dispose()
+            {
+                Dispose(true);
+            }
+
+            protected virtual void Dispose(bool disposing)
+            {
+                if(_disposed)
+                {
+                    return;
+                }
+                if(disposing)
+                {
+                    Stop();
+
+                    _root?.Dispose();
+                    
+                    if (_timer != null)
+                    {
+                        _timer.Tick -= OnTick;
+                        _timer.Dispose();
+                    }
+                    if (_particleList != null)
+                    {
+                        foreach (Particle particle in _particleList)
+                        {
+                            particle?.Dispose();
+                        };
+                        _particleList = null;
+                    }
+
+                    if (_window != null)
+                    {
+                        _window.Resized -= OnWindowResized;
+                    }
+
+                    _window = null;
+                    _rootLayer = null;
+                }
+                _disposed = true;
+                _instance = new();
+            }
+
+            private void OnWindowResized(object o, Window.ResizedEventArgs e)
+            {
+                _rootLayerWidth = (float)e.WindowSize.Width;
+            }
+
+            private bool OnTick(object o, EventArgs e)
+            {
+                if (_particleList.Count < _particleCount)
+                {
+                    Particle particle = new Particle(_root, _rootLayerWidth, _particleCount * _emitIntervalMs);
+                    _particleList.Add(particle);
+                }
+                return _particleList.Count < _particleCount;
+            }
+        }
+        private View root;
+
+        public void Activate()
+        {
+            Window window = NUIApplication.GetDefaultWindow();
+            window.WindowSize = new Size2D(1920, 1080);
+
+            root = new View()
+            {
+                BackgroundColor = new Color(0.3f, 0.6f, 0.8f, 1.0f),
+                ParentOrigin = ParentOrigin.Center,
+                PivotPoint = PivotPoint.Center,
+                PositionUsesPivotPoint = true,
+                WidthResizePolicy = ResizePolicyType.FillToParent,
+                HeightResizePolicy = ResizePolicyType.FillToParent,
+
+                InnerShadow = new InnerShadow(new UIExtents(-100.0f, -100.0f, -100.0f, 300.0f), 300.0f, new Color(0.1f, 0.3f, 0.5f, 1.0f)),
+            };
+            window.Add(root);
+
+            ParticleSystem.Instance.SetRootWindow(window);
+            ParticleSystem.Instance.Start();
+        }
+
+        public void Deactivate()
+        {
+            if (root != null)
+            {
+                root.Dispose();
+            }
+            ParticleSystem.Instance.Dispose();
+        }
+    }
+}