DALi Version 2.1.31
[platform/core/uifw/dali-demo.git] / examples / scene-loader / scene-loader-example.cpp
1 /*\r
2  * Copyright (c) 2022 Samsung Electronics Co., Ltd.\r
3  *\r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  * http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  *\r
16  */\r
17 #include "scene-loader-example.h"\r
18 #include <dali-toolkit/dali-toolkit.h>\r
19 #include <dirent.h>\r
20 #include <cstring>\r
21 #include <string_view>\r
22 #include "dali-scene-loader/public-api/dli-loader.h"\r
23 #include "dali-scene-loader/public-api/gltf2-loader.h"\r
24 #include "dali-scene-loader/public-api/light-parameters.h"\r
25 #include "dali-scene-loader/public-api/load-result.h"\r
26 #include "dali-scene-loader/public-api/shader-definition-factory.h"\r
27 #include "dali-toolkit/public-api/controls/scrollable/item-view/default-item-layout.h"\r
28 #include "dali-toolkit/public-api/controls/scrollable/item-view/item-factory.h"\r
29 #include "dali-toolkit/public-api/controls/text-controls/text-label.h"\r
30 #include "dali-toolkit/public-api/visuals/gradient-visual-properties.h"\r
31 #include "dali/public-api/actors/layer.h"\r
32 #include "dali/public-api/adaptor-framework/key.h"\r
33 #include "dali/public-api/events/key-event.h"\r
34 #include "dali/public-api/object/property-array.h"\r
35 #include "dali/public-api/render-tasks/render-task-list.h"\r
36 #include "scene-loader-extension.h"\r
37 \r
38 using namespace Dali;\r
39 using namespace Dali::Toolkit;\r
40 using namespace Dali::SceneLoader;\r
41 \r
42 namespace\r
43 {\r
44 const float ROTATION_SCALE = 180.f; // the amount of rotation that a swipe whose length is the width of the screen, causes, in degrees.\r
45 \r
46 const float ITEM_HEIGHT = 50.f;\r
47 \r
48 const Vector3 CAMERA_DEFAULT_POSITION(0.0f, 0.0f, 3.5f);\r
49 \r
50 const std::string_view DLI_EXTENSION  = ".dli";\r
51 const std::string_view GLTF_EXTENSION = ".gltf";\r
52 \r
53 const std::string RESOURCE_TYPE_DIRS[]{\r
54   "images/",\r
55   "shaders/",\r
56   "models/",\r
57   "images/",\r
58 };\r
59 \r
60 using StringVector = std::vector<std::string>;\r
61 \r
62 StringVector ListFiles(\r
63   const std::string& path, bool (*predicate)(const char*) = [](const char*) { return true; })\r
64 {\r
65   StringVector results;\r
66 \r
67   if(auto dirp = opendir(path.c_str()))\r
68   {\r
69     std::unique_ptr<DIR, int (*)(DIR*)> dir(dirp, closedir);\r
70 \r
71     struct dirent* ent;\r
72     while((ent = readdir(dir.get())) != nullptr)\r
73     {\r
74       if(ent->d_type == DT_REG && predicate(ent->d_name))\r
75       {\r
76         results.push_back(ent->d_name);\r
77       }\r
78     }\r
79   }\r
80   return results;\r
81 }\r
82 \r
83 TextLabel MakeLabel(std::string msg)\r
84 {\r
85   TextLabel label = TextLabel::New("  " + msg);\r
86   label.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);\r
87   label.SetProperty(TextLabel::Property::TEXT_COLOR, Color::WHITE);\r
88   label.SetProperty(TextLabel::Property::PIXEL_SIZE, ITEM_HEIGHT * 4 / 7);\r
89   label.SetProperty(TextLabel::Property::VERTICAL_ALIGNMENT, "CENTER");\r
90   SetActorCentered(label);\r
91   return label;\r
92 }\r
93 \r
94 struct ItemFactoryImpl : Dali::Toolkit::ItemFactory\r
95 {\r
96   const std::vector<std::string>& mSceneNames;\r
97   TapGestureDetector              mTapDetector;\r
98 \r
99   ItemFactoryImpl(const std::vector<std::string>& sceneNames, TapGestureDetector tapDetector)\r
100   : mSceneNames(sceneNames),\r
101     mTapDetector(tapDetector)\r
102   {\r
103   }\r
104 \r
105   unsigned int GetNumberOfItems() override\r
106   {\r
107     return mSceneNames.size();\r
108   }\r
109 \r
110   Actor NewItem(unsigned int itemId) override\r
111   {\r
112     auto label = MakeLabel(mSceneNames[itemId]);\r
113     mTapDetector.Attach(label);\r
114     label.SetProperty(Actor::Property::KEYBOARD_FOCUSABLE, true);\r
115     return label;\r
116   }\r
117 };\r
118 \r
119 Actor CreateErrorMessage(std::string msg)\r
120 {\r
121   auto label = MakeLabel(msg);\r
122   label.SetProperty(TextLabel::Property::MULTI_LINE, true);\r
123   label.SetProperty(TextLabel::Property::HORIZONTAL_ALIGNMENT, HorizontalAlignment::LEFT);\r
124   label.SetProperty(TextLabel::Property::VERTICAL_ALIGNMENT, VerticalAlignment::TOP);\r
125   return label;\r
126 }\r
127 \r
128 void ConfigureCamera(const CameraParameters& params, CameraActor camera)\r
129 {\r
130   if(params.isPerspective)\r
131   {\r
132     camera.SetProjectionMode(Camera::PERSPECTIVE_PROJECTION);\r
133     camera.SetNearClippingPlane(params.zNear);\r
134     camera.SetFarClippingPlane(params.zFar);\r
135     camera.SetFieldOfView(Radian(Degree(params.yFov)));\r
136   }\r
137   else\r
138   {\r
139     camera.SetProjectionMode(Camera::ORTHOGRAPHIC_PROJECTION);\r
140     camera.SetOrthographicProjection(params.orthographicSize.x,\r
141                                      params.orthographicSize.y,\r
142                                      params.orthographicSize.z,\r
143                                      params.orthographicSize.w,\r
144                                      params.zNear,\r
145                                      params.zFar);\r
146   }\r
147 \r
148   // model\r
149   Vector3    camTranslation;\r
150   Vector3    camScale;\r
151   Quaternion camOrientation;\r
152   params.CalculateTransformComponents(camTranslation, camOrientation, camScale);\r
153 \r
154   SetActorCentered(camera);\r
155   camera.SetInvertYAxis(true);\r
156   camera.SetProperty(Actor::Property::POSITION, camTranslation);\r
157   camera.SetProperty(Actor::Property::ORIENTATION, camOrientation);\r
158   camera.SetProperty(Actor::Property::SCALE, camScale);\r
159 \r
160   camOrientation.Conjugate();\r
161 }\r
162 \r
163 void ConfigureBlendShapeShaders(ResourceBundle& resources, const SceneDefinition& scene, Actor root, std::vector<BlendshapeShaderConfigurationRequest>&& requests)\r
164 {\r
165   std::vector<std::string> errors;\r
166   auto                     onError = [&errors](const std::string& msg) {\r
167     errors.push_back(msg);\r
168   };\r
169   if(!scene.ConfigureBlendshapeShaders(resources, root, std::move(requests), onError))\r
170   {\r
171     ExceptionFlinger flinger(ASSERT_LOCATION);\r
172     for(auto& msg : errors)\r
173     {\r
174       flinger << msg << '\n';\r
175     }\r
176   }\r
177 }\r
178 \r
179 Actor LoadScene(std::string sceneName, CameraActor camera, std::vector<AnimationDefinition>* animations, Animation& animation)\r
180 {\r
181   ResourceBundle::PathProvider pathProvider = [](ResourceType::Value type) {\r
182     return Application::GetResourcePath() + RESOURCE_TYPE_DIRS[type];\r
183   };\r
184 \r
185   auto path = pathProvider(ResourceType::Mesh) + sceneName;\r
186 \r
187   ResourceBundle                        resources;\r
188   SceneDefinition                       scene;\r
189   std::vector<AnimationGroupDefinition> animGroups;\r
190   std::vector<CameraParameters>         cameraParameters;\r
191   std::vector<LightParameters>          lights;\r
192 \r
193   animations->clear();\r
194 \r
195   LoadResult output{\r
196     resources,\r
197     scene,\r
198     *animations,\r
199     animGroups,\r
200     cameraParameters,\r
201     lights};\r
202 \r
203   if(sceneName.rfind(DLI_EXTENSION) == sceneName.size() - DLI_EXTENSION.size())\r
204   {\r
205     DliLoader              loader;\r
206     DliLoader::InputParams input{\r
207       pathProvider(ResourceType::Mesh),\r
208       nullptr,\r
209       {},\r
210       {},\r
211       nullptr,\r
212       {}};\r
213     DliLoader::LoadParams loadParams{input, output};\r
214     if(!loader.LoadScene(path, loadParams))\r
215     {\r
216       ExceptionFlinger(ASSERT_LOCATION) << "Failed to load scene from '" << path << "': " << loader.GetParseError();\r
217     }\r
218   }\r
219   else\r
220   {\r
221     ShaderDefinitionFactory sdf;\r
222     sdf.SetResources(resources);\r
223     LoadGltfScene(path, sdf, output);\r
224 \r
225     resources.mEnvironmentMaps.push_back({});\r
226   }\r
227 \r
228   if(cameraParameters.empty())\r
229   {\r
230     cameraParameters.push_back(CameraParameters());\r
231     cameraParameters[0].matrix.SetTranslation(CAMERA_DEFAULT_POSITION);\r
232   }\r
233   ConfigureCamera(cameraParameters[0], camera);\r
234 \r
235   ViewProjection viewProjection = cameraParameters[0].GetViewProjection();\r
236   Transforms     xforms{\r
237     MatrixStack{},\r
238     viewProjection};\r
239   NodeDefinition::CreateParams nodeParams{\r
240     resources,\r
241     xforms,\r
242     {},\r
243     {},\r
244     {}};\r
245   Customization::Choices choices;\r
246 \r
247   Actor root = Actor::New();\r
248   SetActorCentered(root);\r
249 \r
250   for(auto iRoot : scene.GetRoots())\r
251   {\r
252     auto resourceRefs = resources.CreateRefCounter();\r
253     scene.CountResourceRefs(iRoot, choices, resourceRefs);\r
254     resources.CountEnvironmentReferences(resourceRefs);\r
255 \r
256     resources.LoadResources(resourceRefs, pathProvider);\r
257 \r
258     if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))\r
259     {\r
260       scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);\r
261       scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));\r
262       ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));\r
263 \r
264       scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));\r
265 \r
266       root.Add(actor);\r
267     }\r
268   }\r
269 \r
270   if(!animations->empty())\r
271   {\r
272     auto getActor = [&root](const std::string& name) {\r
273       return root.FindChildByName(name);\r
274     };\r
275 \r
276     animation = (*animations)[0].ReAnimate(getActor);\r
277     animation.Play();\r
278   }\r
279 \r
280   return root;\r
281 }\r
282 \r
283 } // namespace\r
284 \r
285 SceneLoaderExample::SceneLoaderExample(Dali::Application& app)\r
286 : mApp(app),\r
287   mSceneLoaderExtension(new SceneLoaderExtension())\r
288 {\r
289   if(!std::getenv("DALI_APPLICATION_PACKAGE"))\r
290   {\r
291     if(auto desktopPrefix = std::getenv("DESKTOP_PREFIX"))\r
292     {\r
293       std::stringstream sstr;\r
294       sstr << desktopPrefix << "/share/com.samsung.dali-demo/res/";\r
295 \r
296       auto daliApplicationPackage = sstr.str();\r
297       setenv("DALI_APPLICATION_PACKAGE", daliApplicationPackage.c_str(), 0);\r
298     }\r
299   }\r
300 \r
301   app.InitSignal().Connect(this, &SceneLoaderExample::OnInit);\r
302   app.TerminateSignal().Connect(this, &SceneLoaderExample::OnTerminate);\r
303 }\r
304 \r
305 SceneLoaderExample::~SceneLoaderExample() = default;\r
306 \r
307 void SceneLoaderExample::OnInit(Application& app)\r
308 {\r
309   // get scenes\r
310   auto resPath    = Application::GetResourcePath();\r
311   auto scenePath  = resPath + RESOURCE_TYPE_DIRS[ResourceType::Mesh];\r
312   auto sceneNames = ListFiles(scenePath, [](const char* name) {\r
313     auto len = strlen(name);\r
314     return (len > DLI_EXTENSION.size() && DLI_EXTENSION.compare(name + (len - DLI_EXTENSION.size())) == 0) ||\r
315            (len > GLTF_EXTENSION.size() && GLTF_EXTENSION.compare(name + (len - GLTF_EXTENSION.size())) == 0);\r
316   });\r
317   mSceneNames     = sceneNames;\r
318 \r
319   // create Dali objects\r
320   auto window = app.GetWindow();\r
321 \r
322   // navigation view\r
323   auto navigationView = NavigationView::New();\r
324   navigationView.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);\r
325   SetActorCentered(navigationView);\r
326 \r
327   // Set up the background gradient.\r
328   Property::Array stopOffsets;\r
329   stopOffsets.PushBack(0.0f);\r
330   stopOffsets.PushBack(1.0f);\r
331   Property::Array stopColors;\r
332   stopColors.PushBack(Color::BLACK);\r
333   stopColors.PushBack(Vector4(0.45f, 0.7f, 0.8f, 1.f)); // Medium bright, pastel blue\r
334   const float percentageWindowHeight = window.GetSize().GetHeight() * 0.6f;\r
335 \r
336   navigationView.SetProperty(Toolkit::Control::Property::BACKGROUND,\r
337                              Dali::Property::Map()\r
338                                .Add(Toolkit::Visual::Property::TYPE, Dali::Toolkit::Visual::GRADIENT)\r
339                                .Add(Toolkit::GradientVisual::Property::STOP_OFFSET, stopOffsets)\r
340                                .Add(Toolkit::GradientVisual::Property::STOP_COLOR, stopColors)\r
341                                .Add(Toolkit::GradientVisual::Property::START_POSITION, Vector2(0.f, -percentageWindowHeight))\r
342                                .Add(Toolkit::GradientVisual::Property::END_POSITION, Vector2(0.f, percentageWindowHeight))\r
343                                .Add(Toolkit::GradientVisual::Property::UNITS, Toolkit::GradientVisual::Units::USER_SPACE));\r
344   window.Add(navigationView);\r
345   mNavigationView = navigationView;\r
346 \r
347   // item view\r
348   auto tapDetector = TapGestureDetector::New();\r
349   mItemFactory.reset(new ::ItemFactoryImpl(mSceneNames, tapDetector));\r
350 \r
351   auto items = ItemView::New(*mItemFactory);\r
352   SetActorCentered(items);\r
353   items.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);\r
354   items.SetProperty(Actor::Property::KEYBOARD_FOCUSABLE, true);\r
355 \r
356   Vector3 windowSize(window.GetSize());\r
357   auto    itemLayout = DefaultItemLayout::New(DefaultItemLayout::LIST);\r
358   itemLayout->SetItemSize(Vector3(windowSize.x * 0.9f, ITEM_HEIGHT, 1.f));\r
359   items.AddLayout(*itemLayout);\r
360   navigationView.Push(items);\r
361 \r
362   mItemLayout = itemLayout;\r
363   mItemView   = items;\r
364 \r
365   mItemView.SetProperty(Actor::Property::KEYBOARD_FOCUSABLE, true);\r
366   KeyboardFocusManager::Get().PreFocusChangeSignal().Connect(this, &SceneLoaderExample::OnKeyboardPreFocusChange);\r
367   KeyboardFocusManager::Get().FocusedActorEnterKeySignal().Connect(this, &SceneLoaderExample::OnKeyboardFocusedActorActivated);\r
368   KeyboardFocusManager::Get().FocusChangedSignal().Connect(this, &SceneLoaderExample::OnKeyboardFocusChanged);\r
369 \r
370   SetActorCentered(KeyboardFocusManager::Get().GetFocusIndicatorActor());\r
371 \r
372   // camera\r
373   auto camera = CameraActor::New();\r
374   camera.SetInvertYAxis(true);\r
375   window.Add(camera);\r
376   mSceneCamera = camera;\r
377 \r
378   // event handling\r
379   window.KeyEventSignal().Connect(this, &SceneLoaderExample::OnKey);\r
380 \r
381   tapDetector.DetectedSignal().Connect(this, &SceneLoaderExample::OnTap);\r
382   mTapDetector = tapDetector;\r
383 \r
384   // activate layout\r
385   mItemView.ActivateLayout(0, windowSize, 0.f);\r
386 \r
387   mSceneLoaderExtension->SetSceneLoader(this);\r
388 }\r
389 \r
390 Actor SceneLoaderExample::OnKeyboardPreFocusChange(Actor current, Actor proposed, Control::KeyboardFocus::Direction direction)\r
391 {\r
392   if(!current && !proposed)\r
393   {\r
394     return mItemView;\r
395   }\r
396 \r
397   return proposed;\r
398 }\r
399 \r
400 void SceneLoaderExample::OnKeyboardFocusedActorActivated(Actor activatedActor)\r
401 {\r
402   if(activatedActor)\r
403   {\r
404     OnTap(activatedActor, Dali::TapGesture());\r
405   }\r
406 }\r
407 \r
408 void SceneLoaderExample::OnKeyboardFocusChanged(Actor originalFocusedActor, Actor currentFocusedActor)\r
409 {\r
410   if(currentFocusedActor)\r
411   {\r
412     auto itemId = mItemView.GetItemId(currentFocusedActor);\r
413     mItemView.ScrollToItem(itemId, 0.1f);\r
414   }\r
415 }\r
416 \r
417 void SceneLoaderExample::OnTerminate(Application& app)\r
418 {\r
419   mTapDetector.Reset();\r
420   mPanDetector.Reset();\r
421 \r
422   auto window      = app.GetWindow();\r
423   auto renderTasks = window.GetRenderTaskList();\r
424   renderTasks.RemoveTask(mSceneRender);\r
425   mSceneRender.Reset();\r
426 \r
427   UnparentAndReset(mNavigationView);\r
428   UnparentAndReset(mSceneCamera);\r
429 \r
430   mItemFactory.reset();\r
431 }\r
432 \r
433 void SceneLoaderExample::OnKey(const KeyEvent& e)\r
434 {\r
435   if(e.GetState() == KeyEvent::UP)\r
436   {\r
437     if(IsKey(e, DALI_KEY_ESCAPE) || IsKey(e, DALI_KEY_BACK))\r
438     {\r
439       if(mScene)\r
440       {\r
441         mPanDetector.Reset();\r
442 \r
443         mNavigationView.Pop();\r
444         mScene.Reset();\r
445 \r
446         if(mActivatedActor)\r
447         {\r
448           KeyboardFocusManager::Get().SetCurrentFocusActor(mActivatedActor);\r
449         }\r
450       }\r
451       else\r
452       {\r
453         mApp.Quit();\r
454       }\r
455     }\r
456     else\r
457     {\r
458       mSceneLoaderExtension->OnKey(e);\r
459     }\r
460   }\r
461 }\r
462 \r
463 void SceneLoaderExample::OnPan(Actor actor, const PanGesture& pan)\r
464 {\r
465   auto    windowSize = mApp.GetWindow().GetSize();\r
466   Vector2 size{float(windowSize.GetWidth()), float(windowSize.GetHeight())};\r
467   float   aspect = size.y / size.x;\r
468 \r
469   size /= ROTATION_SCALE;\r
470 \r
471   Vector2 rotation{pan.GetDisplacement().x / size.x, pan.GetDisplacement().y / size.y * aspect};\r
472 \r
473   Quaternion q  = Quaternion(Radian(Degree(rotation.y)), Radian(Degree(rotation.x)), Radian(0.f));\r
474   Quaternion q0 = mScene.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();\r
475 \r
476   mScene.SetProperty(Actor::Property::ORIENTATION, q * q0);\r
477 }\r
478 \r
479 void SceneLoaderExample::OnTap(Dali::Actor actor, const Dali::TapGesture& tap)\r
480 {\r
481   mActivatedActor = actor;\r
482 \r
483   auto id = mItemView.GetItemId(actor);\r
484 \r
485   try\r
486   {\r
487     auto window      = mApp.GetWindow();\r
488     auto renderTasks = window.GetRenderTaskList();\r
489     renderTasks.RemoveTask(mSceneRender);\r
490 \r
491     auto scene = LoadScene(mSceneNames[id], mSceneCamera, &mSceneAnimations, mCurrentAnimation);\r
492 \r
493     auto sceneRender = renderTasks.CreateTask();\r
494     sceneRender.SetCameraActor(mSceneCamera);\r
495     sceneRender.SetSourceActor(scene);\r
496     sceneRender.SetExclusive(true);\r
497 \r
498     mScene       = scene;\r
499     mSceneRender = sceneRender;\r
500 \r
501     mPanDetector = PanGestureDetector::New();\r
502     mPanDetector.DetectedSignal().Connect(this, &SceneLoaderExample::OnPan);\r
503     mPanDetector.Attach(mNavigationView);\r
504   }\r
505   catch(const DaliException& e)\r
506   {\r
507     mScene = CreateErrorMessage(e.condition);\r
508   }\r
509 \r
510   mNavigationView.Push(mScene);\r
511 \r
512   mSceneLoaderExtension->ConnectTouchSignals();\r
513 }\r