2 * Copyright (c) 2018 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include "particle-view.h"
20 #include "float-rand.h"
21 #include "particle-field.h"
23 #include "dali/devel-api/adaptor-framework/tilt-sensor.h"
24 #include "dali/public-api/adaptor-framework/application.h"
25 #include "dali/public-api/adaptor-framework/key.h"
26 #include "dali/public-api/actors/camera-actor.h"
27 #include "dali/public-api/actors/layer.h"
28 #include "dali/public-api/events/tap-gesture-detector.h"
29 #include "dali/public-api/events/pan-gesture-detector.h"
30 #include "dali/public-api/events/touch-event.h"
31 #include "dali/public-api/render-tasks/render-task-list.h"
32 #include "dali/public-api/render-tasks/render-task.h"
33 #include "dali/public-api/object/property-index-ranges.h"
46 const float PARTICLE_ALPHA = .75;
47 const float ALPHA_TEST_REF_VALUE = .0625;
49 const float NEAR_FADE = 0.04; // normalized depth
50 const float FAR_FADE = 0.8; // normalized depth
52 const ParticleField PARTICLE_FIELD = {
53 200.f, // particle size
54 Vector3(2500., 2500., 7800.), // box size - depth needs to be >= camera depth range
55 Vector3(6., 6., 12.), // number of particles
56 .333, // size variance
60 15., // motion cycle length
61 6., // twinkle frequency
62 .11, // twinkle size scale
63 .333 // twinkle opacity weight
66 const float WORLD_ANGULAR_VELOCITY = .08; // radians per seconds
67 const float WORLD_LINEAR_VELOCITY = -500; // units along z
69 const float SCATTER_RADIUS = 450.0f;
70 const float SCATTER_AMOUNT = 180.0f;
71 const float SCATTER_DURATION_OUT = .8f;
72 const float SCATTER_DURATION_IN = 1.5f;
74 const float FADE_DURATION = 1.2f;
75 const float FADEOUT_SPEED_MULTIPLIER = 4.f; // speed multiplier upon fading out.
77 const float FOCAL_LENGTH = 0.5f; // normalized depth value where particles appear sharp.
78 const float APERTURE = 2.2f; // distance scale - the higher it is, the quicker the particles get blurred out moving away from the focal length.
80 const ColorRange DEFAULT_COLOR_RANGE { Vector3(0., 48. / 255., 1.), Vector3(0., 216. / 255., 1.) };
82 const float TILT_SCALE = 0.2;
83 const float TILT_RANGE_DEGREES = 30.f;
92 std::fill(mTiltSamples, mTiltSamples + FILTER_SIZE, Vector2(.0f, .0f));
95 void Add(Dali::Vector2 tilt)
97 mTiltSamples[mIdxNextSample] = tilt;
98 mIdxNextSample = (mIdxNextSample + 1) % FILTER_SIZE;
101 Dali::Vector2 Filter() const
103 return std::accumulate(mTiltSamples, mTiltSamples + FILTER_SIZE, Vector2(.0f, .0f)) / FILTER_SIZE;
107 enum { FILTER_SIZE = 8u };
109 Dali::Vector2 mTiltSamples[FILTER_SIZE];
110 size_t mIdxNextSample = 0;
113 class ParticlesExample : public ConnectionTracker
116 ParticlesExample( Application& app )
119 mApp.InitSignal().Connect(this, &ParticlesExample::OnInit);
120 mApp.TerminateSignal().Connect(this, &ParticlesExample::OnTerminate);
123 ~ParticlesExample() = default;
131 Vector2 mAngularPosition;
134 std::unique_ptr<ParticleView> mParticles;
135 std::unique_ptr<ParticleView> mExpiringParticles;
137 TapGestureDetector mDoubleTapGesture;
139 TiltSensor mTiltSensor;
140 TiltFilter mTiltFilter;
142 PanGestureDetector mPanGesture;
144 void OnInit( Application& application )
146 Window window = application.GetWindow();
147 auto rootLayer = window.GetRootLayer();
148 rootLayer.SetProperty(Layer::Property::BEHAVIOR, Layer::Behavior::LAYER_3D);
150 window.KeyEventSignal().Connect( this, &ParticlesExample::OnKeyEvent );
151 window.GetRootLayer().TouchedSignal().Connect( this, &ParticlesExample::OnTouched );
153 auto tiltSensor = TiltSensor::Get();
154 if ( tiltSensor.Start() )
156 // Get notifications when the device is tilted
157 tiltSensor.TiltedSignal().Connect( this, &ParticlesExample::OnTilted );
161 mPanGesture = PanGestureDetector::New();
162 mPanGesture.Attach(rootLayer);
163 mPanGesture.DetectedSignal().Connect(this, &ParticlesExample::OnPan);
167 RenderTaskList tasks = window.GetRenderTaskList();
168 RenderTask mainPass = tasks.GetTask(0);
169 CameraActor camera = mainPass.GetCameraActor();
172 // Create world - particles and clock are added to it; this is what we apply tilt to.
173 auto world = CreateActor();
174 world.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
179 TriggerColorTransition(DEFAULT_COLOR_RANGE);
181 // Setup double tap detector for color change
182 mDoubleTapGesture = TapGestureDetector::New(2);
183 mDoubleTapGesture.Attach(rootLayer);
184 mDoubleTapGesture.DetectedSignal().Connect(this, &ParticlesExample::OnDoubleTap);
187 void OnTerminate(Application& app)
189 UnparentAndReset(mWorld);
191 mDoubleTapGesture.Reset();
196 void OnPause(Application& app)
201 void OnResume(Application& app)
206 void OnKeyEvent(const KeyEvent& event)
208 if ( event.GetState() == KeyEvent::UP) // single keystrokes
210 if( IsKey( event, DALI_KEY_ESCAPE ) || IsKey( event, DALI_KEY_BACK ) )
217 bool OnTouched( Actor a, const TouchEvent& event )
219 if (event.GetPointCount() > 0)
221 auto screenPos = event.GetScreenPosition(0);
222 switch (event.GetState(0))
224 case PointState::STARTED:
226 mParticles->Scatter(SCATTER_RADIUS, SCATTER_AMOUNT, SCATTER_DURATION_OUT, SCATTER_DURATION_IN);
228 Vector3 ray = GetViewRay(screenPos);
229 mParticles->SetScatterRay(ray);
241 void OnDoubleTap(Actor /*actor*/, const TapGesture& /*gesture*/)
243 if (!mExpiringParticles)
245 mColors.rgb0 = Vector3::ONE - mColors.rgb1;
246 mColors.rgb1 = FromHueSaturationLightness(Vector3(sFloatRand() * 360.f, sFloatRand() * .5f + .5f, sFloatRand() * .25 + .75f));
248 TriggerColorTransition(mColors);
252 void OnPan(Actor actor, const PanGesture& gesture)
254 auto tilt = gesture.GetDisplacement() / Vector2(mApp.GetWindow().GetSize()) * TILT_SCALE;
255 Quaternion q(Radian(-tilt.y), Radian(tilt.x), Radian(0.f));
256 Quaternion q0 = mWorld.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();
257 mWorld.SetProperty(Actor::Property::ORIENTATION, q * q0);
260 void OnTilted( const TiltSensor& sensor)
262 mTiltFilter.Add(Vector2(sensor.GetPitch(), sensor.GetRoll()));
263 Vector2 tilt = mTiltFilter.Filter() * TILT_RANGE_DEGREES;
264 Quaternion q(Radian(Degree(tilt.x)), Radian(Degree(tilt.y)), Radian(0.f));
265 mWorld.SetProperty(Actor::Property::ORIENTATION, q);
268 Vector3 GetViewRay(const Vector2& screenPos)
270 Vector2 screenSize = mApp.GetWindow().GetSize();
271 Vector2 normScreenPos = (screenPos / screenSize) * 2.f - Vector2::ONE;
273 const float fov = mCamera.GetProperty(CameraActor::Property::FIELD_OF_VIEW).Get<float>();
274 const float tanFov = std::tan(fov);
276 const float zNear = mCamera.GetProperty(CameraActor::Property::NEAR_PLANE_DISTANCE).Get<float>();
277 const float hProj = zNear * tanFov;
279 const float aspectRatio = mCamera.GetProperty(CameraActor::Property::ASPECT_RATIO).Get<float>();
280 const float wProj = hProj * aspectRatio;
282 // Get camera orientation for view space ray casting. Assume:
283 // - this to be world space, i.e. no parent transforms;
285 Quaternion cameraOrientation = mCamera.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();
288 worldCamera.SetTransformComponents(Vector3::ONE, cameraOrientation, Vector3::ZERO);
290 float* data = worldCamera.AsFloat();
291 Vector3 xWorldCamera(data[0], data[4], data[8]);
292 xWorldCamera *= wProj * normScreenPos.x / xWorldCamera.Length();
294 Vector3 yWorldCamera(data[1], data[5], data[9]);
295 yWorldCamera *= hProj * normScreenPos.y / yWorldCamera.Length();
297 Vector3 zWorldCamera(data[2], data[6], data[10]);
298 Vector3 ray = xWorldCamera + yWorldCamera + zWorldCamera * -zNear;
304 void TriggerColorTransition(const ColorRange& range)
308 mExpiringParticles = std::move(mParticles);
310 // NOTE: this will break the perfect looping, but we can get away with it because we're fading out.
311 mExpiringParticles->SetLinearVelocity(WORLD_LINEAR_VELOCITY * FADEOUT_SPEED_MULTIPLIER);
313 auto& p = mExpiringParticles;
314 mExpiringParticles->Fade(FADE_DURATION, 0.f, AlphaFunction::EASE_IN, [&p](Animation&) {
319 mParticles.reset(new ParticleView(PARTICLE_FIELD, mWorld, mCamera));
320 mParticles->SetColorRange(range);
321 mParticles->SetFocalLength(FOCAL_LENGTH);
322 mParticles->SetAperture(APERTURE);
323 mParticles->SetAlphaTestRefValue(ALPHA_TEST_REF_VALUE);
324 mParticles->SetFadeRange(NEAR_FADE, FAR_FADE);
325 mParticles->SetAngularVelocity(WORLD_ANGULAR_VELOCITY);
326 mParticles->SetLinearVelocity(WORLD_LINEAR_VELOCITY);
327 mParticles->Fade(FADE_DURATION, PARTICLE_ALPHA, 0.f, AlphaFunction::EASE_OUT);
333 int DALI_EXPORT_API main( int argc, char **argv )
335 Application application = Application::New( &argc, &argv, DEMO_THEME_PATH );
336 ParticlesExample example( application);
337 application.MainLoop();