Update Scene3D demo after Scene3D Loader API change
[platform/core/uifw/dali-demo.git] / examples / scene3d / scene3d-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 "scene3d-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-scene3d/public-api/loader/dli-loader.h"\r
23 #include "dali-scene3d/public-api/loader/gltf2-loader.h"\r
24 #include "dali-scene3d/public-api/loader/light-parameters.h"\r
25 #include "dali-scene3d/public-api/loader/load-result.h"\r
26 #include "dali-scene3d/public-api/loader/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 "scene3d-extension.h"\r
37 \r
38 using namespace Dali;\r
39 using namespace Dali::Toolkit;\r
40 using namespace Dali::Scene3D::Loader;\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   SceneMetadata                         metaData;\r
190   std::vector<AnimationGroupDefinition> animGroups;\r
191   std::vector<CameraParameters>         cameraParameters;\r
192   std::vector<LightParameters>          lights;\r
193 \r
194   animations->clear();\r
195 \r
196   LoadResult output{\r
197     resources,\r
198     scene,\r
199     metaData,\r
200     *animations,\r
201     animGroups,\r
202     cameraParameters,\r
203     lights};\r
204 \r
205   if(sceneName.rfind(DLI_EXTENSION) == sceneName.size() - DLI_EXTENSION.size())\r
206   {\r
207     DliLoader              loader;\r
208     DliLoader::InputParams input{\r
209       pathProvider(ResourceType::Mesh),\r
210       nullptr,\r
211       {},\r
212       {},\r
213       nullptr,\r
214       {}};\r
215     DliLoader::LoadParams loadParams{input, output};\r
216     if(!loader.LoadScene(path, loadParams))\r
217     {\r
218       ExceptionFlinger(ASSERT_LOCATION) << "Failed to load scene from '" << path << "': " << loader.GetParseError();\r
219     }\r
220   }\r
221   else\r
222   {\r
223     ShaderDefinitionFactory sdf;\r
224     sdf.SetResources(resources);\r
225     LoadGltfScene(path, sdf, output);\r
226 \r
227     resources.mEnvironmentMaps.push_back({});\r
228   }\r
229 \r
230   if(cameraParameters.empty())\r
231   {\r
232     cameraParameters.push_back(CameraParameters());\r
233     cameraParameters[0].matrix.SetTranslation(CAMERA_DEFAULT_POSITION);\r
234   }\r
235   ConfigureCamera(cameraParameters[0], camera);\r
236 \r
237   ViewProjection viewProjection = cameraParameters[0].GetViewProjection();\r
238   Transforms     xforms{\r
239     MatrixStack{},\r
240     viewProjection};\r
241   NodeDefinition::CreateParams nodeParams{\r
242     resources,\r
243     xforms,\r
244     {},\r
245     {},\r
246     {}};\r
247   Customization::Choices choices;\r
248 \r
249   Actor root = Actor::New();\r
250   SetActorCentered(root);\r
251 \r
252   for(auto iRoot : scene.GetRoots())\r
253   {\r
254     auto resourceRefs = resources.CreateRefCounter();\r
255     scene.CountResourceRefs(iRoot, choices, resourceRefs);\r
256     resources.CountEnvironmentReferences(resourceRefs);\r
257 \r
258     resources.LoadResources(resourceRefs, pathProvider);\r
259 \r
260     if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))\r
261     {\r
262       scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);\r
263       scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));\r
264       ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));\r
265 \r
266       scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));\r
267 \r
268       root.Add(actor);\r
269     }\r
270   }\r
271 \r
272   if(!animations->empty())\r
273   {\r
274     auto getActor = [&root](const std::string& name) {\r
275       return root.FindChildByName(name);\r
276     };\r
277 \r
278     animation = (*animations)[0].ReAnimate(getActor);\r
279     animation.Play();\r
280   }\r
281 \r
282   return root;\r
283 }\r
284 \r
285 } // namespace\r
286 \r
287 Scene3DExample::Scene3DExample(Dali::Application& app)\r
288 : mApp(app),\r
289   mScene3DExtension(new Scene3DExtension())\r
290 {\r
291   if(!std::getenv("DALI_APPLICATION_PACKAGE"))\r
292   {\r
293     if(auto desktopPrefix = std::getenv("DESKTOP_PREFIX"))\r
294     {\r
295       std::stringstream sstr;\r
296       sstr << desktopPrefix << "/share/com.samsung.dali-demo/res/";\r
297 \r
298       auto daliApplicationPackage = sstr.str();\r
299       setenv("DALI_APPLICATION_PACKAGE", daliApplicationPackage.c_str(), 0);\r
300     }\r
301   }\r
302 \r
303   app.InitSignal().Connect(this, &Scene3DExample::OnInit);\r
304   app.TerminateSignal().Connect(this, &Scene3DExample::OnTerminate);\r
305 }\r
306 \r
307 Scene3DExample::~Scene3DExample() = default;\r
308 \r
309 void Scene3DExample::OnInit(Application& app)\r
310 {\r
311   // get scenes\r
312   auto resPath    = Application::GetResourcePath();\r
313   auto scenePath  = resPath + RESOURCE_TYPE_DIRS[ResourceType::Mesh];\r
314   auto sceneNames = ListFiles(scenePath, [](const char* name) {\r
315     auto len = strlen(name);\r
316     return (len > DLI_EXTENSION.size() && DLI_EXTENSION.compare(name + (len - DLI_EXTENSION.size())) == 0) ||\r
317            (len > GLTF_EXTENSION.size() && GLTF_EXTENSION.compare(name + (len - GLTF_EXTENSION.size())) == 0);\r
318   });\r
319   mSceneNames     = sceneNames;\r
320 \r
321   // create Dali objects\r
322   auto window = app.GetWindow();\r
323 \r
324   // navigation view\r
325   auto navigationView = NavigationView::New();\r
326   navigationView.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);\r
327   SetActorCentered(navigationView);\r
328 \r
329   // Set up the background gradient.\r
330   Property::Array stopOffsets;\r
331   stopOffsets.PushBack(0.0f);\r
332   stopOffsets.PushBack(1.0f);\r
333   Property::Array stopColors;\r
334   stopColors.PushBack(Color::BLACK);\r
335   stopColors.PushBack(Vector4(0.45f, 0.7f, 0.8f, 1.f)); // Medium bright, pastel blue\r
336   const float percentageWindowHeight = window.GetSize().GetHeight() * 0.6f;\r
337 \r
338   navigationView.SetProperty(Toolkit::Control::Property::BACKGROUND,\r
339                              Dali::Property::Map()\r
340                                .Add(Toolkit::Visual::Property::TYPE, Dali::Toolkit::Visual::GRADIENT)\r
341                                .Add(Toolkit::GradientVisual::Property::STOP_OFFSET, stopOffsets)\r
342                                .Add(Toolkit::GradientVisual::Property::STOP_COLOR, stopColors)\r
343                                .Add(Toolkit::GradientVisual::Property::START_POSITION, Vector2(0.f, -percentageWindowHeight))\r
344                                .Add(Toolkit::GradientVisual::Property::END_POSITION, Vector2(0.f, percentageWindowHeight))\r
345                                .Add(Toolkit::GradientVisual::Property::UNITS, Toolkit::GradientVisual::Units::USER_SPACE));\r
346   window.Add(navigationView);\r
347   mNavigationView = navigationView;\r
348 \r
349   // item view\r
350   auto tapDetector = TapGestureDetector::New();\r
351   mItemFactory.reset(new ::ItemFactoryImpl(mSceneNames, tapDetector));\r
352 \r
353   auto items = ItemView::New(*mItemFactory);\r
354   SetActorCentered(items);\r
355   items.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);\r
356   items.SetProperty(Actor::Property::KEYBOARD_FOCUSABLE, true);\r
357 \r
358   Vector3 windowSize(window.GetSize());\r
359   auto    itemLayout = DefaultItemLayout::New(DefaultItemLayout::LIST);\r
360   itemLayout->SetItemSize(Vector3(windowSize.x * 0.9f, ITEM_HEIGHT, 1.f));\r
361   items.AddLayout(*itemLayout);\r
362   navigationView.Push(items);\r
363 \r
364   mItemLayout = itemLayout;\r
365   mItemView   = items;\r
366 \r
367   mItemView.SetProperty(Actor::Property::KEYBOARD_FOCUSABLE, true);\r
368   KeyboardFocusManager::Get().PreFocusChangeSignal().Connect(this, &Scene3DExample::OnKeyboardPreFocusChange);\r
369   KeyboardFocusManager::Get().FocusedActorEnterKeySignal().Connect(this, &Scene3DExample::OnKeyboardFocusedActorActivated);\r
370   KeyboardFocusManager::Get().FocusChangedSignal().Connect(this, &Scene3DExample::OnKeyboardFocusChanged);\r
371 \r
372   SetActorCentered(KeyboardFocusManager::Get().GetFocusIndicatorActor());\r
373 \r
374   // camera\r
375   auto camera = CameraActor::New();\r
376   camera.SetInvertYAxis(true);\r
377   window.Add(camera);\r
378   mSceneCamera = camera;\r
379 \r
380   // event handling\r
381   window.KeyEventSignal().Connect(this, &Scene3DExample::OnKey);\r
382 \r
383   tapDetector.DetectedSignal().Connect(this, &Scene3DExample::OnTap);\r
384   mTapDetector = tapDetector;\r
385 \r
386   // activate layout\r
387   mItemView.ActivateLayout(0, windowSize, 0.f);\r
388 \r
389   mScene3DExtension->SetSceneLoader(this);\r
390 }\r
391 \r
392 Actor Scene3DExample::OnKeyboardPreFocusChange(Actor current, Actor proposed, Control::KeyboardFocus::Direction direction)\r
393 {\r
394   if(!current && !proposed)\r
395   {\r
396     return mItemView;\r
397   }\r
398 \r
399   return proposed;\r
400 }\r
401 \r
402 void Scene3DExample::OnKeyboardFocusedActorActivated(Actor activatedActor)\r
403 {\r
404   if(activatedActor)\r
405   {\r
406     OnTap(activatedActor, Dali::TapGesture());\r
407   }\r
408 }\r
409 \r
410 void Scene3DExample::OnKeyboardFocusChanged(Actor originalFocusedActor, Actor currentFocusedActor)\r
411 {\r
412   if(currentFocusedActor)\r
413   {\r
414     auto itemId = mItemView.GetItemId(currentFocusedActor);\r
415     mItemView.ScrollToItem(itemId, 0.1f);\r
416   }\r
417 }\r
418 \r
419 void Scene3DExample::OnTerminate(Application& app)\r
420 {\r
421   mTapDetector.Reset();\r
422   mPanDetector.Reset();\r
423 \r
424   auto window      = app.GetWindow();\r
425   auto renderTasks = window.GetRenderTaskList();\r
426   renderTasks.RemoveTask(mSceneRender);\r
427   mSceneRender.Reset();\r
428 \r
429   UnparentAndReset(mNavigationView);\r
430   UnparentAndReset(mSceneCamera);\r
431 \r
432   mItemFactory.reset();\r
433 }\r
434 \r
435 void Scene3DExample::OnKey(const KeyEvent& e)\r
436 {\r
437   if(e.GetState() == KeyEvent::UP)\r
438   {\r
439     if(IsKey(e, DALI_KEY_ESCAPE) || IsKey(e, DALI_KEY_BACK))\r
440     {\r
441       if(mScene)\r
442       {\r
443         mPanDetector.Reset();\r
444 \r
445         mNavigationView.Pop();\r
446         mScene.Reset();\r
447 \r
448         if(mActivatedActor)\r
449         {\r
450           KeyboardFocusManager::Get().SetCurrentFocusActor(mActivatedActor);\r
451         }\r
452       }\r
453       else\r
454       {\r
455         mApp.Quit();\r
456       }\r
457     }\r
458     else\r
459     {\r
460       mScene3DExtension->OnKey(e);\r
461     }\r
462   }\r
463 }\r
464 \r
465 void Scene3DExample::OnPan(Actor actor, const PanGesture& pan)\r
466 {\r
467   auto    windowSize = mApp.GetWindow().GetSize();\r
468   Vector2 size{float(windowSize.GetWidth()), float(windowSize.GetHeight())};\r
469   float   aspect = size.y / size.x;\r
470 \r
471   size /= ROTATION_SCALE;\r
472 \r
473   Vector2 rotation{pan.GetDisplacement().x / size.x, pan.GetDisplacement().y / size.y * aspect};\r
474 \r
475   Quaternion q  = Quaternion(Radian(Degree(rotation.y)), Radian(Degree(rotation.x)), Radian(0.f));\r
476   Quaternion q0 = mScene.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();\r
477 \r
478   mScene.SetProperty(Actor::Property::ORIENTATION, q * q0);\r
479 }\r
480 \r
481 void Scene3DExample::OnTap(Dali::Actor actor, const Dali::TapGesture& tap)\r
482 {\r
483   mActivatedActor = actor;\r
484 \r
485   auto id = mItemView.GetItemId(actor);\r
486 \r
487   try\r
488   {\r
489     auto window      = mApp.GetWindow();\r
490     auto renderTasks = window.GetRenderTaskList();\r
491     renderTasks.RemoveTask(mSceneRender);\r
492 \r
493     auto scene = LoadScene(mSceneNames[id], mSceneCamera, &mSceneAnimations, mCurrentAnimation);\r
494 \r
495     auto sceneRender = renderTasks.CreateTask();\r
496     sceneRender.SetCameraActor(mSceneCamera);\r
497     sceneRender.SetSourceActor(scene);\r
498     sceneRender.SetExclusive(true);\r
499 \r
500     mScene       = scene;\r
501     mSceneRender = sceneRender;\r
502 \r
503     mPanDetector = PanGestureDetector::New();\r
504     mPanDetector.DetectedSignal().Connect(this, &Scene3DExample::OnPan);\r
505     mPanDetector.Attach(mNavigationView);\r
506   }\r
507   catch(const DaliException& e)\r
508   {\r
509     mScene = CreateErrorMessage(e.condition);\r
510   }\r
511 \r
512   mNavigationView.Push(mScene);\r
513 \r
514   mScene3DExtension->ConnectTouchSignals();\r
515 }\r