a1639aac5406b603dfa2677f9e29fa9b93ac569a
[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/model-loader.h"\r
23 #include "dali-scene3d/public-api/loader/light-parameters.h"\r
24 #include "dali-scene3d/public-api/loader/load-result.h"\r
25 #include "dali-scene3d/public-api/loader/shader-definition-factory.h"\r
26 #include "dali-toolkit/public-api/controls/scrollable/item-view/default-item-layout.h"\r
27 #include "dali-toolkit/public-api/controls/scrollable/item-view/item-factory.h"\r
28 #include "dali-toolkit/public-api/controls/text-controls/text-label.h"\r
29 #include "dali-toolkit/public-api/visuals/gradient-visual-properties.h"\r
30 #include "dali/public-api/actors/layer.h"\r
31 #include "dali/public-api/adaptor-framework/key.h"\r
32 #include "dali/public-api/events/key-event.h"\r
33 #include "dali/public-api/object/property-array.h"\r
34 #include "dali/public-api/render-tasks/render-task-list.h"\r
35 #include "scene3d-extension.h"\r
36 \r
37 using namespace Dali;\r
38 using namespace Dali::Toolkit;\r
39 using namespace Dali::Scene3D::Loader;\r
40 \r
41 namespace\r
42 {\r
43 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
44 \r
45 const float ITEM_HEIGHT = 50.f;\r
46 \r
47 const Vector3 CAMERA_DEFAULT_POSITION(0.0f, 0.0f, 3.5f);\r
48 \r
49 const std::string_view DLI_EXTENSION  = ".dli";\r
50 const std::string_view GLTF_EXTENSION = ".gltf";\r
51 \r
52 const std::string RESOURCE_TYPE_DIRS[]{\r
53   "images/",\r
54   "shaders/",\r
55   "models/",\r
56   "images/",\r
57 };\r
58 \r
59 using StringVector = std::vector<std::string>;\r
60 \r
61 StringVector ListFiles(\r
62   const std::string& path, bool (*predicate)(const char*) = [](const char*) { return true; })\r
63 {\r
64   StringVector results;\r
65 \r
66   if(auto dirp = opendir(path.c_str()))\r
67   {\r
68     std::unique_ptr<DIR, int (*)(DIR*)> dir(dirp, closedir);\r
69 \r
70     struct dirent* ent;\r
71     while((ent = readdir(dir.get())) != nullptr)\r
72     {\r
73       if(ent->d_type == DT_REG && predicate(ent->d_name))\r
74       {\r
75         results.push_back(ent->d_name);\r
76       }\r
77     }\r
78   }\r
79   return results;\r
80 }\r
81 \r
82 TextLabel MakeLabel(std::string msg)\r
83 {\r
84   TextLabel label = TextLabel::New("  " + msg);\r
85   label.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);\r
86   label.SetProperty(TextLabel::Property::TEXT_COLOR, Color::WHITE);\r
87   label.SetProperty(TextLabel::Property::PIXEL_SIZE, ITEM_HEIGHT * 4 / 7);\r
88   label.SetProperty(TextLabel::Property::VERTICAL_ALIGNMENT, "CENTER");\r
89   SetActorCentered(label);\r
90   return label;\r
91 }\r
92 \r
93 struct ItemFactoryImpl : Dali::Toolkit::ItemFactory\r
94 {\r
95   const std::vector<std::string>& mSceneNames;\r
96   TapGestureDetector              mTapDetector;\r
97 \r
98   ItemFactoryImpl(const std::vector<std::string>& sceneNames, TapGestureDetector tapDetector)\r
99   : mSceneNames(sceneNames),\r
100     mTapDetector(tapDetector)\r
101   {\r
102   }\r
103 \r
104   unsigned int GetNumberOfItems() override\r
105   {\r
106     return mSceneNames.size();\r
107   }\r
108 \r
109   Actor NewItem(unsigned int itemId) override\r
110   {\r
111     auto label = MakeLabel(mSceneNames[itemId]);\r
112     mTapDetector.Attach(label);\r
113     label.SetProperty(Actor::Property::KEYBOARD_FOCUSABLE, true);\r
114     return label;\r
115   }\r
116 };\r
117 \r
118 Actor CreateErrorMessage(std::string msg)\r
119 {\r
120   auto label = MakeLabel(msg);\r
121   label.SetProperty(TextLabel::Property::MULTI_LINE, true);\r
122   label.SetProperty(TextLabel::Property::HORIZONTAL_ALIGNMENT, HorizontalAlignment::LEFT);\r
123   label.SetProperty(TextLabel::Property::VERTICAL_ALIGNMENT, VerticalAlignment::TOP);\r
124   return label;\r
125 }\r
126 \r
127 void ConfigureBlendShapeShaders(ResourceBundle& resources, const SceneDefinition& scene, Actor root, std::vector<BlendshapeShaderConfigurationRequest>&& requests)\r
128 {\r
129   std::vector<std::string> errors;\r
130   auto                     onError = [&errors](const std::string& msg) {\r
131     errors.push_back(msg);\r
132   };\r
133   if(!scene.ConfigureBlendshapeShaders(resources, root, std::move(requests), onError))\r
134   {\r
135     ExceptionFlinger flinger(ASSERT_LOCATION);\r
136     for(auto& msg : errors)\r
137     {\r
138       flinger << msg << '\n';\r
139     }\r
140   }\r
141 }\r
142 \r
143 Actor LoadScene(std::string sceneName, CameraActor camera, std::vector<Dali::Animation>& generatedAnimations, Animation& animation)\r
144 {\r
145   ResourceBundle::PathProvider pathProvider = [](ResourceType::Value type) {\r
146     return Application::GetResourcePath() + RESOURCE_TYPE_DIRS[type];\r
147   };\r
148 \r
149   auto path = pathProvider(ResourceType::Mesh) + sceneName;\r
150 \r
151   ResourceBundle                                          resources;\r
152   SceneDefinition                                         scene;\r
153   SceneMetadata                                           metaData;\r
154   std::vector<Dali::Scene3D::Loader::AnimationDefinition> animations;\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   Dali::Scene3D::Loader::ModelLoader modelLoader(path, pathProvider(ResourceType::Mesh) + "/", output);\r
171   modelLoader.LoadModel(pathProvider);\r
172 \r
173   if(cameraParameters.empty())\r
174   {\r
175     cameraParameters.push_back(CameraParameters());\r
176     cameraParameters[0].matrix.SetTranslation(CAMERA_DEFAULT_POSITION);\r
177   }\r
178   cameraParameters[0].ConfigureCamera(camera);\r
179   SetActorCentered(camera);\r
180 \r
181   ViewProjection viewProjection = cameraParameters[0].GetViewProjection();\r
182   Transforms     xforms{\r
183     MatrixStack{},\r
184     viewProjection};\r
185   NodeDefinition::CreateParams nodeParams{\r
186     resources,\r
187     xforms,\r
188     {},\r
189     {},\r
190     {}};\r
191 \r
192   Actor root = Actor::New();\r
193   SetActorCentered(root);\r
194 \r
195   auto& resourceChoices = modelLoader.GetResourceChoices();\r
196   for(auto iRoot : scene.GetRoots())\r
197   {\r
198     if(auto actor = scene.CreateNodes(iRoot, resourceChoices, nodeParams))\r
199     {\r
200       scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));\r
201       ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));\r
202 \r
203       scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));\r
204 \r
205       root.Add(actor);\r
206     }\r
207   }\r
208 \r
209   generatedAnimations.clear();\r
210   if(!animations.empty())\r
211   {\r
212     generatedAnimations.reserve(animations.size());\r
213     auto getActor = [&](const Scene3D::Loader::AnimatedProperty& property)\r
214     {\r
215       Dali::Actor actor;\r
216       if(property.mNodeIndex != Scene3D::Loader::INVALID_INDEX)\r
217       {\r
218         auto* node = scene.GetNode(property.mNodeIndex);\r
219         if(node != nullptr)\r
220         {\r
221           actor = root.FindChildById(node->mNodeId);\r
222         }\r
223       }\r
224       else\r
225       {\r
226         actor = root.FindChildByName(property.mNodeName);\r
227       }\r
228       return actor;\r
229     };\r
230 \r
231     for(auto& animationDefinition : animations)\r
232     {\r
233       generatedAnimations.push_back(animationDefinition.ReAnimate(getActor));\r
234     }\r
235     generatedAnimations[0].Play();\r
236   }\r
237 \r
238   return root;\r
239 }\r
240 \r
241 } // namespace\r
242 \r
243 Scene3DExample::Scene3DExample(Dali::Application& app)\r
244 : mApp(app),\r
245   mScene3DExtension(new Scene3DExtension())\r
246 {\r
247   if(!std::getenv("DALI_APPLICATION_PACKAGE"))\r
248   {\r
249     if(auto desktopPrefix = std::getenv("DESKTOP_PREFIX"))\r
250     {\r
251       std::stringstream sstr;\r
252       sstr << desktopPrefix << "/share/com.samsung.dali-demo/res/";\r
253 \r
254       auto daliApplicationPackage = sstr.str();\r
255       setenv("DALI_APPLICATION_PACKAGE", daliApplicationPackage.c_str(), 0);\r
256     }\r
257   }\r
258 \r
259   app.InitSignal().Connect(this, &Scene3DExample::OnInit);\r
260   app.TerminateSignal().Connect(this, &Scene3DExample::OnTerminate);\r
261 }\r
262 \r
263 Scene3DExample::~Scene3DExample() = default;\r
264 \r
265 void Scene3DExample::OnInit(Application& app)\r
266 {\r
267   // get scenes\r
268   auto resPath    = Application::GetResourcePath();\r
269   auto scenePath  = resPath + RESOURCE_TYPE_DIRS[ResourceType::Mesh];\r
270   auto sceneNames = ListFiles(scenePath, [](const char* name) {\r
271     auto len = strlen(name);\r
272     return (len > DLI_EXTENSION.size() && DLI_EXTENSION.compare(name + (len - DLI_EXTENSION.size())) == 0) ||\r
273            (len > GLTF_EXTENSION.size() && GLTF_EXTENSION.compare(name + (len - GLTF_EXTENSION.size())) == 0);\r
274   });\r
275   mSceneNames     = sceneNames;\r
276 \r
277   // create Dali objects\r
278   auto window = app.GetWindow();\r
279 \r
280   // navigation view\r
281   auto navigationView = NavigationView::New();\r
282   navigationView.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);\r
283   SetActorCentered(navigationView);\r
284 \r
285   // Set up the background gradient.\r
286   Property::Array stopOffsets;\r
287   stopOffsets.PushBack(0.0f);\r
288   stopOffsets.PushBack(1.0f);\r
289   Property::Array stopColors;\r
290   stopColors.PushBack(Color::BLACK);\r
291   stopColors.PushBack(Vector4(0.45f, 0.7f, 0.8f, 1.f)); // Medium bright, pastel blue\r
292   const float percentageWindowHeight = window.GetSize().GetHeight() * 0.6f;\r
293 \r
294   navigationView.SetProperty(Toolkit::Control::Property::BACKGROUND,\r
295                              Dali::Property::Map()\r
296                                .Add(Toolkit::Visual::Property::TYPE, Dali::Toolkit::Visual::GRADIENT)\r
297                                .Add(Toolkit::GradientVisual::Property::STOP_OFFSET, stopOffsets)\r
298                                .Add(Toolkit::GradientVisual::Property::STOP_COLOR, stopColors)\r
299                                .Add(Toolkit::GradientVisual::Property::START_POSITION, Vector2(0.f, -percentageWindowHeight))\r
300                                .Add(Toolkit::GradientVisual::Property::END_POSITION, Vector2(0.f, percentageWindowHeight))\r
301                                .Add(Toolkit::GradientVisual::Property::UNITS, Toolkit::GradientVisual::Units::USER_SPACE));\r
302   window.Add(navigationView);\r
303   mNavigationView = navigationView;\r
304 \r
305   // item view\r
306   auto tapDetector = TapGestureDetector::New();\r
307   mItemFactory.reset(new ::ItemFactoryImpl(mSceneNames, tapDetector));\r
308 \r
309   auto items = ItemView::New(*mItemFactory);\r
310   SetActorCentered(items);\r
311   items.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);\r
312   items.SetProperty(Actor::Property::KEYBOARD_FOCUSABLE, true);\r
313 \r
314   Vector3 windowSize(window.GetSize());\r
315   auto    itemLayout = DefaultItemLayout::New(DefaultItemLayout::LIST);\r
316   itemLayout->SetItemSize(Vector3(windowSize.x * 0.9f, ITEM_HEIGHT, 1.f));\r
317   items.AddLayout(*itemLayout);\r
318   navigationView.Push(items);\r
319 \r
320   mItemLayout = itemLayout;\r
321   mItemView   = items;\r
322 \r
323   mItemView.SetProperty(Actor::Property::KEYBOARD_FOCUSABLE, true);\r
324   KeyboardFocusManager::Get().PreFocusChangeSignal().Connect(this, &Scene3DExample::OnKeyboardPreFocusChange);\r
325   KeyboardFocusManager::Get().FocusedActorEnterKeySignal().Connect(this, &Scene3DExample::OnKeyboardFocusedActorActivated);\r
326   KeyboardFocusManager::Get().FocusChangedSignal().Connect(this, &Scene3DExample::OnKeyboardFocusChanged);\r
327 \r
328   SetActorCentered(KeyboardFocusManager::Get().GetFocusIndicatorActor());\r
329 \r
330   // camera\r
331   auto camera = CameraActor::New();\r
332   camera.SetInvertYAxis(true);\r
333   window.Add(camera);\r
334   mSceneCamera = camera;\r
335 \r
336   // event handling\r
337   window.KeyEventSignal().Connect(this, &Scene3DExample::OnKey);\r
338 \r
339   tapDetector.DetectedSignal().Connect(this, &Scene3DExample::OnTap);\r
340   mTapDetector = tapDetector;\r
341 \r
342   // activate layout\r
343   mItemView.ActivateLayout(0, windowSize, 0.f);\r
344 \r
345   mScene3DExtension->SetSceneLoader(this);\r
346 }\r
347 \r
348 Actor Scene3DExample::OnKeyboardPreFocusChange(Actor current, Actor proposed, Control::KeyboardFocus::Direction direction)\r
349 {\r
350   if(!current && !proposed)\r
351   {\r
352     return mItemView;\r
353   }\r
354 \r
355   return proposed;\r
356 }\r
357 \r
358 void Scene3DExample::OnKeyboardFocusedActorActivated(Actor activatedActor)\r
359 {\r
360   if(activatedActor)\r
361   {\r
362     OnTap(activatedActor, Dali::TapGesture());\r
363   }\r
364 }\r
365 \r
366 void Scene3DExample::OnKeyboardFocusChanged(Actor originalFocusedActor, Actor currentFocusedActor)\r
367 {\r
368   if(currentFocusedActor)\r
369   {\r
370     auto itemId = mItemView.GetItemId(currentFocusedActor);\r
371     mItemView.ScrollToItem(itemId, 0.1f);\r
372   }\r
373 }\r
374 \r
375 void Scene3DExample::OnTerminate(Application& app)\r
376 {\r
377   mTapDetector.Reset();\r
378   mPanDetector.Reset();\r
379 \r
380   auto window      = app.GetWindow();\r
381   auto renderTasks = window.GetRenderTaskList();\r
382   renderTasks.RemoveTask(mSceneRender);\r
383   mSceneRender.Reset();\r
384 \r
385   UnparentAndReset(mNavigationView);\r
386   UnparentAndReset(mSceneCamera);\r
387 \r
388   mItemFactory.reset();\r
389 }\r
390 \r
391 void Scene3DExample::OnKey(const KeyEvent& e)\r
392 {\r
393   if(e.GetState() == KeyEvent::UP)\r
394   {\r
395     if(IsKey(e, DALI_KEY_ESCAPE) || IsKey(e, DALI_KEY_BACK))\r
396     {\r
397       if(mScene)\r
398       {\r
399         mPanDetector.Reset();\r
400 \r
401         mNavigationView.Pop();\r
402         mScene.Reset();\r
403 \r
404         if(mActivatedActor)\r
405         {\r
406           KeyboardFocusManager::Get().SetCurrentFocusActor(mActivatedActor);\r
407         }\r
408         auto window = mApp.GetWindow();\r
409         window.GetRootLayer().SetProperty(Layer::Property::BEHAVIOR, Layer::LAYER_UI);\r
410       }\r
411       else\r
412       {\r
413         mApp.Quit();\r
414       }\r
415     }\r
416     else\r
417     {\r
418       mScene3DExtension->OnKey(e);\r
419     }\r
420   }\r
421 }\r
422 \r
423 void Scene3DExample::OnPan(Actor actor, const PanGesture& pan)\r
424 {\r
425   auto    windowSize = mApp.GetWindow().GetSize();\r
426   Vector2 size{float(windowSize.GetWidth()), float(windowSize.GetHeight())};\r
427   float   aspect = size.y / size.x;\r
428 \r
429   size /= ROTATION_SCALE;\r
430 \r
431   Vector2 rotation{pan.GetDisplacement().x / size.x, pan.GetDisplacement().y / size.y * aspect};\r
432 \r
433   Quaternion q  = Quaternion(Radian(Degree(rotation.y)), Radian(Degree(rotation.x)), Radian(0.f));\r
434   Quaternion q0 = mScene.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();\r
435 \r
436   mScene.SetProperty(Actor::Property::ORIENTATION, q * q0);\r
437 }\r
438 \r
439 void Scene3DExample::OnTap(Dali::Actor actor, const Dali::TapGesture& tap)\r
440 {\r
441   mActivatedActor = actor;\r
442 \r
443   auto id = mItemView.GetItemId(actor);\r
444 \r
445   try\r
446   {\r
447     auto window      = mApp.GetWindow();\r
448     window.GetRootLayer().SetProperty(Layer::Property::BEHAVIOR, Layer::LAYER_3D);\r
449     auto renderTasks = window.GetRenderTaskList();\r
450     renderTasks.RemoveTask(mSceneRender);\r
451 \r
452     auto scene = LoadScene(mSceneNames[id], mSceneCamera, mSceneAnimations, mCurrentAnimation);\r
453 \r
454     auto sceneRender = renderTasks.CreateTask();\r
455     sceneRender.SetCameraActor(mSceneCamera);\r
456     sceneRender.SetSourceActor(scene);\r
457     sceneRender.SetExclusive(true);\r
458 \r
459     mScene       = scene;\r
460     mSceneRender = sceneRender;\r
461 \r
462     mPanDetector = PanGestureDetector::New();\r
463     mPanDetector.DetectedSignal().Connect(this, &Scene3DExample::OnPan);\r
464     mPanDetector.Attach(mNavigationView);\r
465   }\r
466   catch(const DaliException& e)\r
467   {\r
468     mScene = CreateErrorMessage(e.condition);\r
469   }\r
470 \r
471   mNavigationView.Push(mScene);\r
472 \r
473   mScene3DExtension->ConnectTouchSignals();\r
474 }\r