2 * Copyright (c) 2021 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.
25 #include "dali/devel-api/adaptor-framework/tilt-sensor.h"
26 #include "dali/public-api/actors/camera-actor.h"
27 #include "dali/public-api/actors/layer.h"
28 #include "dali/public-api/adaptor-framework/application.h"
29 #include "dali/public-api/adaptor-framework/key.h"
30 #include "dali/public-api/events/pan-gesture-detector.h"
31 #include "dali/public-api/events/tap-gesture-detector.h"
32 #include "dali/public-api/events/touch-event.h"
33 #include "dali/public-api/object/property-index-ranges.h"
34 #include "dali/public-api/render-tasks/render-task-list.h"
35 #include "dali/public-api/render-tasks/render-task.h"
36 #include "float-rand.h"
37 #include "particle-field.h"
38 #include "particle-view.h"
45 const float PARTICLE_ALPHA = .75;
46 const float ALPHA_TEST_REF_VALUE = .0625;
48 const float NEAR_FADE = 0.04; // normalized depth
49 const float FAR_FADE = 0.8; // normalized depth
51 const ParticleField PARTICLE_FIELD = {
52 200.f, // particle size
53 Vector3(2500., 2500., 7800.), // box size - depth needs to be >= camera depth range
54 Vector3(6., 6., 12.), // number of particles
55 .333, // size variance
59 15., // motion cycle length
60 6., // twinkle frequency
61 .11, // twinkle size scale
62 .333 // twinkle opacity weight
65 const float WORLD_ANGULAR_VELOCITY = .08; // radians per seconds
66 const float WORLD_LINEAR_VELOCITY = -500; // units along z
68 const float SCATTER_RADIUS = 450.0f;
69 const float SCATTER_AMOUNT = 180.0f;
70 const float SCATTER_DURATION_OUT = .8f;
71 const float SCATTER_DURATION_IN = 1.5f;
73 const float FADE_DURATION = 1.2f;
74 const float FADEOUT_SPEED_MULTIPLIER = 4.f; // speed multiplier upon fading out.
76 const float FOCAL_LENGTH = 0.5f; // normalized depth value where particles appear sharp.
77 const float APERTURE = 2.2f; // distance scale - the higher it is, the quicker the particles get blurred out moving away from the focal length.
79 const ColorRange DEFAULT_COLOR_RANGE{Vector3(0., 48. / 255., 1.), Vector3(0., 216. / 255., 1.)};
81 const float TILT_SCALE = 0.2;
82 const float TILT_RANGE_DEGREES = 30.f;
91 std::fill(mTiltSamples, mTiltSamples + FILTER_SIZE, Vector2(.0f, .0f));
94 void Add(Dali::Vector2 tilt)
96 mTiltSamples[mIdxNextSample] = tilt;
97 mIdxNextSample = (mIdxNextSample + 1) % FILTER_SIZE;
100 Dali::Vector2 Filter() const
102 return std::accumulate(mTiltSamples, mTiltSamples + FILTER_SIZE, Vector2(.0f, .0f)) / FILTER_SIZE;
111 Dali::Vector2 mTiltSamples[FILTER_SIZE];
112 size_t mIdxNextSample = 0;
115 class ParticlesExample : public ConnectionTracker
118 ParticlesExample(Application& app)
121 mApp.InitSignal().Connect(this, &ParticlesExample::OnInit);
122 mApp.TerminateSignal().Connect(this, &ParticlesExample::OnTerminate);
125 ~ParticlesExample() = default;
133 Vector2 mAngularPosition;
136 std::unique_ptr<ParticleView> mParticles;
137 std::unique_ptr<ParticleView> mExpiringParticles;
139 TapGestureDetector mDoubleTapGesture;
141 TiltSensor mTiltSensor;
142 TiltFilter mTiltFilter;
144 PanGestureDetector mPanGesture;
146 void OnInit(Application& application)
148 Window window = application.GetWindow();
149 auto rootLayer = window.GetRootLayer();
150 rootLayer.SetProperty(Layer::Property::BEHAVIOR, Layer::Behavior::LAYER_3D);
152 window.KeyEventSignal().Connect(this, &ParticlesExample::OnKeyEvent);
153 window.GetRootLayer().TouchedSignal().Connect(this, &ParticlesExample::OnTouched);
155 auto tiltSensor = TiltSensor::Get();
156 if(tiltSensor.Start())
158 // Get notifications when the device is tilted
159 tiltSensor.TiltedSignal().Connect(this, &ParticlesExample::OnTilted);
163 mPanGesture = PanGestureDetector::New();
164 mPanGesture.Attach(rootLayer);
165 mPanGesture.DetectedSignal().Connect(this, &ParticlesExample::OnPan);
169 RenderTaskList tasks = window.GetRenderTaskList();
170 RenderTask mainPass = tasks.GetTask(0);
171 CameraActor camera = mainPass.GetCameraActor();
174 // Create world - particles and clock are added to it; this is what we apply tilt to.
175 auto world = CreateActor();
176 world.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
181 TriggerColorTransition(DEFAULT_COLOR_RANGE);
183 // Setup double tap detector for color change
184 mDoubleTapGesture = TapGestureDetector::New(2);
185 mDoubleTapGesture.Attach(rootLayer);
186 mDoubleTapGesture.DetectedSignal().Connect(this, &ParticlesExample::OnDoubleTap);
189 void OnTerminate(Application& app)
191 UnparentAndReset(mWorld);
193 mDoubleTapGesture.Reset();
198 void OnPause(Application& app)
203 void OnResume(Application& app)
208 void OnKeyEvent(const KeyEvent& event)
210 if(event.GetState() == KeyEvent::UP) // single keystrokes
212 if(IsKey(event, DALI_KEY_ESCAPE) || IsKey(event, DALI_KEY_BACK))
219 bool OnTouched(Actor a, const TouchEvent& event)
221 if(event.GetPointCount() > 0)
223 auto screenPos = event.GetScreenPosition(0);
224 switch(event.GetState(0))
226 case PointState::STARTED:
228 mParticles->Scatter(SCATTER_RADIUS, SCATTER_AMOUNT, SCATTER_DURATION_OUT, SCATTER_DURATION_IN);
230 Vector3 ray = GetViewRay(screenPos);
231 mParticles->SetScatterRay(ray);
243 void OnDoubleTap(Actor /*actor*/, const TapGesture& /*gesture*/)
245 if(!mExpiringParticles)
247 mColors.rgb0 = Vector3::ONE - mColors.rgb1;
248 mColors.rgb1 = FromHueSaturationLightness(Vector3(sFloatRand() * 360.f, sFloatRand() * .5f + .5f, sFloatRand() * .25 + .75f));
250 TriggerColorTransition(mColors);
254 void OnPan(Actor actor, const PanGesture& gesture)
256 auto tilt = gesture.GetDisplacement() / Vector2(mApp.GetWindow().GetSize()) * TILT_SCALE;
257 Quaternion q(Radian(-tilt.y), Radian(tilt.x), Radian(0.f));
258 Quaternion q0 = mWorld.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();
259 mWorld.SetProperty(Actor::Property::ORIENTATION, q * q0);
262 void OnTilted(const TiltSensor& sensor)
264 mTiltFilter.Add(Vector2(sensor.GetPitch(), sensor.GetRoll()));
265 Vector2 tilt = mTiltFilter.Filter() * TILT_RANGE_DEGREES;
266 Quaternion q(Radian(Degree(tilt.x)), Radian(Degree(tilt.y)), Radian(0.f));
267 mWorld.SetProperty(Actor::Property::ORIENTATION, q);
270 Vector3 GetViewRay(const Vector2& screenPos)
272 Vector2 screenSize = mApp.GetWindow().GetSize();
273 Vector2 normScreenPos = (screenPos / screenSize) * 2.f - Vector2::ONE;
275 const float fov = mCamera.GetProperty(CameraActor::Property::FIELD_OF_VIEW).Get<float>();
276 const float tanFov = std::tan(fov);
278 const float zNear = mCamera.GetProperty(CameraActor::Property::NEAR_PLANE_DISTANCE).Get<float>();
279 const float hProj = zNear * tanFov;
281 const float aspectRatio = mCamera.GetProperty(CameraActor::Property::ASPECT_RATIO).Get<float>();
282 const float wProj = hProj * aspectRatio;
284 // Get camera orientation for view space ray casting. Assume:
285 // - this to be world space, i.e. no parent transforms;
287 Quaternion cameraOrientation = mCamera.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();
290 worldCamera.SetTransformComponents(Vector3::ONE, cameraOrientation, Vector3::ZERO);
292 float* data = worldCamera.AsFloat();
293 Vector3 xWorldCamera(data[0], data[4], data[8]);
294 xWorldCamera *= wProj * normScreenPos.x / xWorldCamera.Length();
296 Vector3 yWorldCamera(data[1], data[5], data[9]);
297 yWorldCamera *= hProj * normScreenPos.y / yWorldCamera.Length();
299 Vector3 zWorldCamera(data[2], data[6], data[10]);
300 Vector3 ray = xWorldCamera + yWorldCamera + zWorldCamera * -zNear;
306 void TriggerColorTransition(const ColorRange& range)
310 mExpiringParticles = std::move(mParticles);
312 // NOTE: this will break the perfect looping, but we can get away with it because we're fading out.
313 mExpiringParticles->SetLinearVelocity(WORLD_LINEAR_VELOCITY * FADEOUT_SPEED_MULTIPLIER);
315 auto& p = mExpiringParticles;
316 mExpiringParticles->Fade(FADE_DURATION, 0.f, AlphaFunction::EASE_IN, [&p](Animation&) {
321 mParticles.reset(new ParticleView(PARTICLE_FIELD, mWorld, mCamera));
322 mParticles->SetColorRange(range);
323 mParticles->SetFocalLength(FOCAL_LENGTH);
324 mParticles->SetAperture(APERTURE);
325 mParticles->SetAlphaTestRefValue(ALPHA_TEST_REF_VALUE);
326 mParticles->SetFadeRange(NEAR_FADE, FAR_FADE);
327 mParticles->SetAngularVelocity(WORLD_ANGULAR_VELOCITY);
328 mParticles->SetLinearVelocity(WORLD_LINEAR_VELOCITY);
329 mParticles->Fade(FADE_DURATION, PARTICLE_ALPHA, 0.f, AlphaFunction::EASE_OUT);
335 int DALI_EXPORT_API main(int argc, char** argv)
337 Application application = Application::New(&argc, &argv, DEMO_THEME_PATH);
338 ParticlesExample example(application);
339 application.MainLoop();