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