36db679d0fb919efb7cfa7b18924cef9765312ce
[platform/core/uifw/dali-demo.git] / examples / particles / particle-view.cpp
1 /*
2  * Copyright (c) 2020 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 #include "particle-view.h"
18 #include "utils.h"
19 #include "dali/public-api/animation/constraints.h"
20
21 //#define ENABLE_DEBUG_VOLUME
22
23 #define USE_GLSL_VERSION(version) "#version " #version "\n"
24
25 using namespace Dali;
26
27 namespace
28 {
29
30 const uint32_t POPULATION_GRANULARITY = 128;
31
32 ///@brief Shader for billboarded particles, where the vertices of the particles
33 /// are supplied as vec3 position (particle position) + vec2 sub-position.
34 const char* const PARTICLES_VSH = USE_GLSL_VERSION(300 es)
35 DALI_COMPOSE_SHADER(
36   precision lowp float;
37   uniform mat4 uModelView; // DALi
38   uniform mat4 uProjection; // DALi
39   uniform vec3 uSize; // DALi
40   uniform vec4 uColor; // DALi
41
42   uniform vec3 uSecondaryColor;
43   uniform vec2 uDepthRange; // x is zNear, y is 1.f / (zFar - zNear)
44   uniform float uTwinkleFrequency;
45   uniform float uTwinkleSizeScale;
46   uniform float uTwinkleOpacityWeight;
47   uniform float uTime;
48   uniform float uFocalLength;
49   uniform float uAperture;
50   uniform float uPopulation;
51
52   struct Scatter
53   {
54     float radiusSqr;
55     float amount;
56     vec3 ray;
57   };
58
59   const int SCATTER_VARS = 6; // Must match ParticleView::mScatterProps' size.
60   uniform Scatter uScatter[SCATTER_VARS];
61
62   const int POPULATION_GRANULARITY = 128;
63   uniform float uOrderLookUp[POPULATION_GRANULARITY];
64
65   in vec3 aPosition;
66   in float aSeed;
67   in vec4 aPath;
68   in vec2 aSubPosition;
69   in float aSize;
70
71   flat out float vDepth;
72   flat out float vFocalDistance;
73   out vec2 vUvUnit;
74   flat out float vOpacity;
75   flat out vec3 vColor; // ignore alpha
76
77   float bezier(vec3 control, float alpha)
78   {
79     return mix(mix(control.x, control.y, alpha), mix(control.y, control.z, alpha), alpha);
80   }
81
82   void main() {
83     // Get random order from the look-up table, based on particle ID.
84     int particleId = gl_VertexID / 6;
85     float order = uOrderLookUp[particleId & (POPULATION_GRANULARITY - 1)];
86
87     // Get twinkle scalar
88     float twinkle = sin(uTime * floor(uTwinkleFrequency * aSeed) + fract(aSeed * 1.17137));
89
90     // Add Motion
91     float s = sin(uTime + aSeed) * .5f + .5f;   // different phase for all
92     // NOTE: you'd think that taking the bezier() calls apart would save 4 mix() calls, since
93     // the mix()es (of xy / yz / zw / wx) are all calculated twice. It turns out that the MALI
94     // compiler is already doing this; leaving it as is for readability.
95     float bx0 = bezier(aPath.xyz, s);
96     float bx1 = bezier(aPath.zwx, s);
97     float by0 = bezier(aPath.yzw, s);
98     float by1 = bezier(aPath.wxy, s);
99     vec3 motion = vec3(mix(bx0, bx1, s), mix(by0, by1, s), 0.f);
100
101     // Model to view position
102     vec3 position3 = aPosition * uSize + motion;
103
104     vec4 position = uModelView * vec4(position3, 1.f);
105
106     // Add scatter - calculated in view space, using view ray
107     vec3 normalizedPos = position.xyz / uSize;
108     for (int i = 0; i < SCATTER_VARS; ++i)
109     {
110       vec2 scatterDist = (normalizedPos - uScatter[i].ray * dot(uScatter[i].ray, normalizedPos)).xy;
111
112       // NOTE: replacing the division with a multiplication (by inverse) oddly results in more instructions (MALI).
113       float scatter = max(0.f, uScatter[i].radiusSqr - dot(scatterDist, scatterDist)) *
114         uScatter[i].amount / aSize;
115       position.xy += scatter * normalize(scatterDist) * uSize.xy;
116     }
117
118     // Calculate normalised depth and distance from focal plane
119     float depth = (position.z - uDepthRange.x) * uDepthRange.y;
120     vDepth = depth;
121
122     float focalDist = (uFocalLength - depth) * uAperture;
123     focalDist *= focalDist;
124     vFocalDistance = max(focalDist, 1e-6f);     // NOTE: was clamp(..., 1.f); side effect: out of focus particles get squashed at higher aperture values.
125
126     // Calculate expiring scale - for size and opacity.
127     float expiringScale = smoothstep(order + 1.f, order, uPopulation);
128
129     // Calculate billboard position and size
130     vec2 subPosition = aSubPosition * aSize *
131       (1.f + twinkle * aSeed * uTwinkleSizeScale) *
132       expiringScale;
133
134     // Insist on hacking the size? Do it here...
135     float sizeHack = depth + .5f;
136     // NOTE: sizeHack *= sizeHack looked slightly better.
137     subPosition *= sizeHack;
138
139     vec3 subPositionView = vec3(subPosition, 0.);
140
141     // Add billboards to view position.
142     position += vec4(subPositionView, 0.f);
143
144     // subPosition doubles as normalized (-1..1) UV.
145     vUvUnit = aSubPosition;
146
147     // Vary opacity (actor alpha) by time as well as expiring scale.
148     vOpacity = uColor.a * expiringScale *
149       (1.0f + aSeed + twinkle * uTwinkleOpacityWeight) / (2.0f + uTwinkleOpacityWeight);
150
151     // Randomize RGB using seed.
152     vec3 mixColor = vec3(fract(aSeed), fract(aSeed * 16.f), fract(aSeed * 256.f));
153     vColor = mix(uColor.rgb, uSecondaryColor, mixColor);
154
155     gl_Position = uProjection * position;
156   });
157
158 ///@brief Fragment shader for particles, which simulates depth of field
159 /// using a combination of procedural texturing, alpha testing and alpha
160 /// blending.
161 const char* const PARTICLES_FSH = USE_GLSL_VERSION(300 es)
162 DALI_COMPOSE_SHADER(
163   precision lowp float;
164   uniform float uAlphaTestRefValue;
165   uniform vec2 uFadeRange; // near, far
166   in vec2 vUvUnit;
167   flat in float vDepth;
168   flat in float vFocalDistance;
169   flat in float vOpacity;
170   flat in vec3 vColor;
171   out vec4 oFragColor;
172
173   const float REF_VALUE_THRESHOLD = 1. / 64.;
174
175   void main() {
176     // Softened disc pattern from normalized UVs
177     float value = 1.f - dot(vUvUnit, vUvUnit);
178
179     // Decrease area of particles 'in-focus'.
180     float refValue = (1.f - vFocalDistance) * .5f;
181     float threshold = REF_VALUE_THRESHOLD * (1.f + vDepth);
182     float alpha = pow(value, vFocalDistance) * smoothstep(refValue - threshold, refValue + threshold, value);
183     if (alpha < uAlphaTestRefValue)
184     {
185       discard;
186     }
187
188     // Apply opacity
189     alpha *= vOpacity;
190     alpha *= alpha;
191
192     // Fade particles out as they get close to the near and far clipping planes
193     alpha *= smoothstep(.0f, uFadeRange.x, vDepth) * smoothstep(1.f, uFadeRange.y, vDepth);
194
195     oFragColor = vec4(vColor, alpha);
196   });
197
198 ///@brief Shader for simple textured geometry.
199 const char* const SIMPLE_VSH = USE_GLSL_VERSION(300 es)
200 DALI_COMPOSE_SHADER(
201   precision mediump float;
202   uniform mat4 uMvpMatrix;//by DALi
203   uniform vec3 uSize;  // by DALi
204   in vec3 aPosition;
205   void main() {
206     gl_Position = uMvpMatrix * vec4(aPosition * uSize, 1.f);
207   });
208
209 ///@brief Shader for an unlit, unfogged, textured mesh.
210 const char* const SIMPLE_FSH = USE_GLSL_VERSION(300 es)
211 DALI_COMPOSE_SHADER(
212   precision mediump float;
213   uniform vec4 uColor;
214   out vec4 oFragColor;
215
216   void main() {
217     oFragColor = uColor;
218   });
219
220
221 uint32_t GetSkipValue(uint32_t count, uint32_t prime)
222 {
223   uint32_t skip = 0;
224   do
225   {
226     skip = (rand() % prime) * count * count + (rand() % prime) * count + (rand() % prime);
227   }
228   while (skip % prime == 0);
229   return skip;
230 }
231
232 }
233
234 ParticleView::ParticleView(const ParticleField& field, Dali::Actor world, Dali::CameraActor camera,
235   Dali::Geometry particleGeom)
236 : mWorld(world),
237   mParticleBoxSize(field.mBoxSize)
238 {
239   if (!particleGeom)
240   {
241     // create particles
242     particleGeom = field.MakeGeometry();
243   }
244
245   // create shader
246   Shader particleShader = Shader::New(PARTICLES_VSH, PARTICLES_FSH, Shader::Hint::MODIFIES_GEOMETRY);
247
248   float zNear = camera.GetNearClippingPlane();
249   float zFar = camera.GetFarClippingPlane();
250   const Vector2 depthRange(zNear, 1.f / (zFar - zNear));
251   particleShader.RegisterProperty("uDepthRange", depthRange);
252
253   particleShader.RegisterProperty("uTwinkleFrequency", field.mTwinkleFrequency);
254   particleShader.RegisterProperty("uTwinkleSizeScale", field.mTwinkleSizeScale);
255   particleShader.RegisterProperty("uTwinkleOpacityWeight", field.mTwinkleOpacityWeight);
256
257   mPropPopulation = particleShader.RegisterProperty("uPopulation", 1.f);
258   mPropFocalLength = particleShader.RegisterProperty("uFocalLength", .5f);
259   mPropAperture = particleShader.RegisterProperty("uAperture", 8.f);
260   mPropAlphaTestRefValue = particleShader.RegisterProperty("uAlphaTestRefValue", 0.f);
261   mPropFadeRange = particleShader.RegisterProperty("uFadeRange", Vector2(0.f, 1.f));
262
263   // scatter variables
264   char nameBuffer[64];
265   char* writep = nameBuffer + snprintf(nameBuffer, sizeof(nameBuffer), "uScatter[");
266   for (uint32_t i = 0; i < std::extent<decltype(mScatterProps)>::value; ++i)
267   {
268     char* writep2 = writep + snprintf(writep, sizeof(nameBuffer) - std::distance(nameBuffer, writep), "%d].", i);
269
270     snprintf(writep2, sizeof(nameBuffer) - std::distance(nameBuffer, writep2), "radiusSqr");
271     mScatterProps[i].mPropRadius = particleShader.RegisterProperty(nameBuffer, 0.f);
272
273     snprintf(writep2, sizeof(nameBuffer) - std::distance(nameBuffer, writep2), "amount");
274     mScatterProps[i].mPropAmount = particleShader.RegisterProperty(nameBuffer, 0.f);
275
276     snprintf(writep2, sizeof(nameBuffer) - std::distance(nameBuffer, writep2), "ray");
277     mScatterProps[i].mPropRay = particleShader.RegisterProperty(nameBuffer, Vector3::ZERO);
278   }
279
280   // Create a look-up table for pseudo-random traversal of particles.
281   // Our particle mesh is sorted in Z; changing the population should remove
282   // particles "randomly", not from one end.
283   // Algorithm described in Mike McShaffry & al: Game Coding Complete.
284   const uint32_t prime = 131;   // next prime after POPULATION_GRANULARITY
285   const uint32_t skip = GetSkipValue(POPULATION_GRANULARITY, prime);
286   uint32_t next = 0;
287
288   writep = nameBuffer + snprintf(nameBuffer, sizeof(nameBuffer), "uOrderLookUp[");
289   for (uint32_t i = 0; i < POPULATION_GRANULARITY; ++i)
290   {
291     do {
292       next += skip;
293       next %= prime;
294     }
295     while (next == 0 || next > POPULATION_GRANULARITY);
296
297     snprintf(writep, sizeof(nameBuffer) - std::distance(nameBuffer, writep), "%d]", i);
298     particleShader.RegisterProperty(nameBuffer, float(next - 1));
299   }
300
301   // create animation for time in shader
302   auto propTime = particleShader.RegisterProperty("uTime", 0.f);
303
304   Animation animTime = Animation::New(field.mMotionCycleLength);
305   animTime.AnimateTo(Property(particleShader, propTime), static_cast<float>(M_PI * 2.f));
306   animTime.SetLoopCount(0);
307   animTime.Play();
308
309   mParticleShader = particleShader;
310
311   auto renderer = CreateRenderer(TextureSet::New(), particleGeom, particleShader, OPTION_BLEND);
312   auto masterParticles = CreateActor();
313   masterParticles.SetProperty(Actor::Property::SIZE, field.mBoxSize);
314   masterParticles.SetProperty(Actor::Property::VISIBLE, true);
315   masterParticles.AddRenderer(renderer);
316
317   mPropSecondaryColor = masterParticles.RegisterProperty("uSecondaryColor", Vector3::XAXIS);
318
319 #ifdef ENABLE_DEBUG_VOLUME
320   Geometry cubeGeom = CreateCuboidWireframeGeometry();
321   renderer = CreateRenderer(renderer.GetTextures(), cubeGeom, Shader::New(SIMPLE_VSH, SIMPLE_FSH));
322   masterParticles.AddRenderer(renderer);
323 #endif
324
325   world.Add(masterParticles);
326   mMasterParticles = masterParticles;
327 }
328
329 ParticleView::~ParticleView()
330 {
331   UnparentAndReset(mMasterParticles);
332   UnparentAndReset(mSlaveParticles);
333
334   for (auto anim: { mAngularAnim, mLinearAnim })
335   {
336     if (anim)
337     {
338       anim.Stop();
339       anim.Reset();
340     }
341   }
342
343   for (auto& s: mScatterProps)
344   {
345     auto& anim = s.mAnim;
346     if (anim)
347     {
348       anim.Stop();
349       anim.Reset();
350     }
351   }
352 }
353
354 void ParticleView::SetColorRange(const ColorRange& range)
355 {
356   mMasterParticles.SetProperty(Actor::Property::COLOR_RED, range.rgb0.r);
357   mMasterParticles.SetProperty(Actor::Property::COLOR_GREEN, range.rgb0.g);
358   mMasterParticles.SetProperty(Actor::Property::COLOR_BLUE, range.rgb0.b);
359
360   mMasterParticles.SetProperty(mPropSecondaryColor, range.rgb1);
361 }
362
363 void ParticleView::SetPopulation(float percentage)
364 {
365   percentage = 1.f - std::min(1.f, std::max(0.f, percentage));
366   mParticleShader.SetProperty(mPropPopulation, POPULATION_GRANULARITY * percentage);
367 }
368
369 void ParticleView::SetFocalLength(float f)
370 {
371   mParticleShader.SetProperty(mPropFocalLength, f);
372 }
373
374 void ParticleView::SetAperture(float a)
375 {
376   mParticleShader.SetProperty(mPropAperture, a);
377 }
378
379 void ParticleView::SetAlphaTestRefValue(float rv)
380 {
381   mParticleShader.SetProperty(mPropAlphaTestRefValue, rv);
382 }
383
384 void ParticleView::SetFadeRange(float near, float far)
385 {
386   mParticleShader.SetProperty(mPropFadeRange, Vector2(near, far));
387 }
388
389 void ParticleView::SetAngularVelocity(float v)
390 {
391   if (mAngularAnim)
392   {
393     mAngularAnim.Stop();
394     mAngularAnim.Clear();
395     mAngularAnim.Reset();
396   }
397
398   if (v * v > .0f)
399   {
400     float sign = Sign(v);
401     auto anim = Animation::New(std::abs(2. * M_PI / v));
402     anim.AnimateTo(Property(mMasterParticles, Actor::Property::ORIENTATION),
403       Quaternion(Radian(Degree(120. * sign)), Vector3::ZAXIS), TimePeriod(0., anim.GetDuration() / 3.));
404     anim.AnimateTo(Property(mMasterParticles, Actor::Property::ORIENTATION),
405       Quaternion(Radian(Degree(240. * sign)), Vector3::ZAXIS), TimePeriod(anim.GetDuration() / 3., anim.GetDuration() / 3.));
406     anim.AnimateTo(Property(mMasterParticles, Actor::Property::ORIENTATION),
407       Quaternion(Radian(Degree(360. * sign)), Vector3::ZAXIS), TimePeriod(2. * anim.GetDuration() / 3., anim.GetDuration() / 3.));
408     anim.SetLoopCount(0);
409     anim.Play();
410
411     mAngularAnim = anim;
412   }
413 }
414
415 void ParticleView::SetLinearVelocity(float v)
416 {
417   if (mLinearAnim)
418   {
419     mLinearAnim.Stop();
420     mLinearAnim.Clear();
421     mLinearAnim.Reset();
422   }
423   UnparentAndReset(mSlaveParticles);
424
425   if (v * v > .0f)
426   {
427     float sign = Sign(v);
428     float directedSize = sign * mParticleBoxSize.z;
429
430     Actor slaveParticles = CloneActor(mMasterParticles);
431     Vector3 position = mMasterParticles.GetCurrentProperty(Actor::Property::POSITION).Get<Vector3>();
432     slaveParticles.SetProperty(Actor::Property::POSITION, position + Vector3(0., 0., directedSize));
433
434     auto propSecondaryColor = slaveParticles.RegisterProperty("uSecondaryColor", Vector3::XAXIS);
435
436     Actor world = mWorld.GetHandle();
437     world.Add(slaveParticles);
438
439     if (sign < 0.)      // fix draw order
440     {
441       world.Remove(mMasterParticles);
442       world.Add(mMasterParticles);
443     }
444
445     Constraint constraint = Constraint::New<Vector4>(slaveParticles, Actor::Property::COLOR,
446       EqualToConstraint());
447     constraint.AddSource(Source(mMasterParticles, Actor::Property::COLOR));
448     constraint.Apply();
449
450     constraint = Constraint::New<Vector3>(slaveParticles, propSecondaryColor,
451       EqualToConstraint());
452     constraint.AddSource(Source(mMasterParticles, mPropSecondaryColor));
453     constraint.Apply();
454
455     constraint = Constraint::New<Quaternion>(slaveParticles, Actor::Property::ORIENTATION,
456       EqualToConstraint());
457     constraint.AddSource(Source(mMasterParticles, Actor::Property::ORIENTATION));
458     constraint.Apply();
459
460     auto anim = Animation::New(std::abs(directedSize / v));
461     anim.AnimateTo(Property(mMasterParticles, Actor::Property::POSITION_Z), position.z - directedSize);
462     anim.AnimateTo(Property(slaveParticles, Actor::Property::POSITION_Z), position.z);
463     anim.SetLoopCount(0);
464     anim.Play();
465
466     mLinearAnim = anim;
467     mSlaveParticles = slaveParticles;
468   }
469 }
470
471 void ParticleView::Scatter(float radius, float amount, float durationOut, float durationIn)
472 {
473   mActiveScatter = (mActiveScatter + 1) % std::extent<decltype(mScatterProps)>::value;
474
475   auto& scatter = mScatterProps[mActiveScatter];
476   if (scatter.mAnim)
477   {
478     scatter.mAnim.Stop();
479   }
480
481   radius /= mParticleBoxSize.y;
482   radius *= radius;
483   mParticleShader.SetProperty(scatter.mPropRadius, radius);
484
485   Animation anim = Animation::New(durationOut + durationIn);
486   auto scatterAmount = Property(mParticleShader, scatter.mPropAmount);
487   anim.AnimateTo(scatterAmount, amount, AlphaFunction::EASE_OUT,
488     TimePeriod(0.f, durationOut));
489   anim.AnimateTo(scatterAmount, 0.f, AlphaFunction::EASE_IN_OUT_SINE,
490     TimePeriod(durationOut, durationIn));
491   anim.Play();
492
493   scatter.mAnim = anim;
494 }
495
496 void ParticleView::SetScatterRay(Dali::Vector3 rayDir)
497 {
498   auto& scatter = mScatterProps[mActiveScatter];
499   mParticleShader.SetProperty(scatter.mPropRay, rayDir);;
500 }
501
502 void ParticleView::Fade(float duration, float target, AlphaFunction alphaFn,
503   std::function<void(Dali::Animation&)> onFinished)
504 {
505   if (mFadeAnim)
506   {
507     mFadeAnim.Stop();
508   }
509
510   Animation anim = Animation::New(duration);
511   anim.AnimateTo(Property(mMasterParticles, Actor::Property::COLOR_ALPHA), target, alphaFn);
512   if (mSlaveParticles)
513   {
514     anim.AnimateTo(Property(mSlaveParticles, Actor::Property::COLOR_ALPHA), target, alphaFn);
515   }
516
517   if (onFinished)
518   {
519     anim.FinishedSignal().Connect(this, onFinished);
520   }
521   anim.Play();
522
523   mFadeAnim = anim;
524 }
525
526 void ParticleView::Fade(float duration, float target, float from, AlphaFunction alphaFn,
527   std::function<void(Dali::Animation&)> onFinished)
528 {
529   mMasterParticles.SetProperty(Actor::Property::COLOR_ALPHA, from);
530   if (mSlaveParticles)
531   {
532     mSlaveParticles.SetProperty(Actor::Property::COLOR_ALPHA, from);
533   }
534
535   Fade(duration, target, alphaFn, onFinished);
536 }