Merge "Change visual animation code" into devel/master
[platform/core/uifw/dali-demo.git] / examples / scene-loader / scene-loader-example.cpp
1 /*\r
2  * Copyright (c) 2020 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-scene-loader/public-api/gltf2-loader.h"\r
19 #include "dali-scene-loader/public-api/shader-definition-factory.h"\r
20 #include "dali-scene-loader/public-api/light-parameters.h"\r
21 #include "dali-scene-loader/public-api/dli-loader.h"\r
22 #include "dali-scene-loader/public-api/load-result.h"\r
23 #include "dali/public-api/adaptor-framework/key.h"\r
24 #include "dali/public-api/events/key-event.h"\r
25 #include "dali/public-api/actors/layer.h"\r
26 #include "dali/public-api/render-tasks/render-task-list.h"\r
27 #include "dali/public-api/object/property-array.h"\r
28 #include "dali-toolkit/public-api/controls/scrollable/item-view/item-factory.h"\r
29 #include "dali-toolkit/public-api/controls/scrollable/item-view/default-item-layout.h"\r
30 #include "dali-toolkit/public-api/controls/text-controls/text-label.h"\r
31 #include "dali-toolkit/public-api/visuals/gradient-visual-properties.h"\r
32 #include <string_view>\r
33 #include <cstring>\r
34 #include <dirent.h>\r
35 \r
36 using namespace Dali;\r
37 using namespace Dali::Toolkit;\r
38 using namespace Dali::SceneLoader;\r
39 \r
40 namespace\r
41 {\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(const std::string& path, bool(*predicate)(const char*) = [](const char*) { return true; })\r
62 {\r
63   StringVector results;\r
64 \r
65   if (auto dirp = opendir(path.c_str()))\r
66   {\r
67     std::unique_ptr<DIR, int(*)(DIR*)> dir(dirp, closedir);\r
68 \r
69     struct dirent* ent;\r
70     while ((ent = readdir(dir.get())) != nullptr)\r
71     {\r
72       if (ent->d_type == DT_REG && predicate(ent->d_name))\r
73       {\r
74         results.push_back(ent->d_name);\r
75       }\r
76     }\r
77   }\r
78   return results;\r
79 }\r
80 \r
81 TextLabel MakeLabel(std::string msg)\r
82 {\r
83   TextLabel label = TextLabel::New(msg);\r
84   label.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);\r
85   label.SetProperty(TextLabel::Property::TEXT_COLOR, Color::WHITE);\r
86   label.SetProperty(TextLabel::Property::PIXEL_SIZE, ITEM_HEIGHT * 4 / 7);\r
87   SetActorCentered(label);\r
88   return label;\r
89 }\r
90 \r
91 struct ItemFactoryImpl : Dali::Toolkit::ItemFactory\r
92 {\r
93   const std::vector<std::string>& mSceneNames;\r
94   TapGestureDetector mTapDetector;\r
95 \r
96   ItemFactoryImpl(const std::vector<std::string>& sceneNames, TapGestureDetector tapDetector)\r
97   : mSceneNames(sceneNames),\r
98     mTapDetector(tapDetector)\r
99   {}\r
100 \r
101   unsigned int GetNumberOfItems() override\r
102   {\r
103     return mSceneNames.size();\r
104   }\r
105 \r
106   Actor NewItem(unsigned int itemId) override\r
107   {\r
108     auto label = MakeLabel(mSceneNames[itemId]);\r
109     mTapDetector.Attach(label);\r
110     return label;\r
111   }\r
112 };\r
113 \r
114 Actor CreateErrorMessage(std::string msg)\r
115 {\r
116   auto label = MakeLabel(msg);\r
117   label.SetProperty(TextLabel::Property::MULTI_LINE, true);\r
118   label.SetProperty(TextLabel::Property::HORIZONTAL_ALIGNMENT, HorizontalAlignment::LEFT);\r
119   label.SetProperty(TextLabel::Property::VERTICAL_ALIGNMENT, VerticalAlignment::TOP);\r
120   return label;\r
121 }\r
122 \r
123 void ConfigureCamera(const CameraParameters& params, CameraActor camera)\r
124 {\r
125   if (params.isPerspective)\r
126   {\r
127     camera.SetProjectionMode(Camera::PERSPECTIVE_PROJECTION);\r
128     camera.SetNearClippingPlane(params.zNear);\r
129     camera.SetFarClippingPlane(params.zFar);\r
130     camera.SetFieldOfView(Radian(Degree(params.yFov)));\r
131   }\r
132   else\r
133   {\r
134     camera.SetProjectionMode(Camera::ORTHOGRAPHIC_PROJECTION);\r
135     camera.SetOrthographicProjection(params.orthographicSize.x,\r
136       params.orthographicSize.y,\r
137       params.orthographicSize.z,\r
138       params.orthographicSize.w,\r
139       params.zNear,\r
140       params.zFar);\r
141   }\r
142 \r
143   // model\r
144   Vector3 camTranslation;\r
145   Vector3 camScale;\r
146   Quaternion camOrientation;\r
147   params.CalculateTransformComponents(camTranslation, camOrientation, camScale);\r
148 \r
149   SetActorCentered(camera);\r
150   camera.SetInvertYAxis(true);\r
151   camera.SetProperty(Actor::Property::POSITION, camTranslation);\r
152   camera.SetProperty(Actor::Property::ORIENTATION, camOrientation);\r
153   camera.SetProperty(Actor::Property::SCALE, camScale);\r
154 \r
155   camOrientation.Conjugate();\r
156 }\r
157 \r
158 void ConfigureBlendShapeShaders(ResourceBundle& resources, const SceneDefinition& scene, Actor root,\r
159   std::vector<BlendshapeShaderConfigurationRequest>&& requests)\r
160 {\r
161   std::vector<std::string> errors;\r
162   auto onError = [&errors](const std::string& msg) {\r
163     errors.push_back(msg);\r
164   };\r
165   if (!scene.ConfigureBlendshapeShaders(resources, root, std::move(requests), onError))\r
166   {\r
167     ExceptionFlinger flinger(ASSERT_LOCATION);\r
168     for (auto& msg : errors)\r
169     {\r
170       flinger << msg << '\n';\r
171     }\r
172   }\r
173 }\r
174 \r
175 Actor LoadScene(std::string sceneName, CameraActor camera)\r
176 {\r
177   ResourceBundle::PathProvider pathProvider = [](ResourceType::Value type) {\r
178     return Application::GetResourcePath() + RESOURCE_TYPE_DIRS[type];\r
179   };\r
180 \r
181   auto path = pathProvider(ResourceType::Mesh) + sceneName;\r
182 \r
183   ResourceBundle resources;\r
184   SceneDefinition scene;\r
185   std::vector<AnimationGroupDefinition> animGroups;\r
186   std::vector<CameraParameters> cameraParameters;\r
187   std::vector<LightParameters> lights;\r
188   std::vector<AnimationDefinition> animations;\r
189 \r
190   LoadResult output{\r
191     resources,\r
192     scene,\r
193     animations,\r
194     animGroups,\r
195     cameraParameters,\r
196     lights\r
197   };\r
198 \r
199   if (sceneName.rfind(DLI_EXTENSION) == sceneName.size() - DLI_EXTENSION.size())\r
200   {\r
201     DliLoader loader;\r
202     DliLoader::InputParams input{\r
203       pathProvider(ResourceType::Mesh),\r
204       nullptr,\r
205       {},\r
206       {},\r
207       nullptr,\r
208     };\r
209     DliLoader::LoadParams loadParams{ input, output };\r
210     if (!loader.LoadScene(path, loadParams))\r
211     {\r
212       ExceptionFlinger(ASSERT_LOCATION) << "Failed to load scene from '" << path << "': " << loader.GetParseError();\r
213     }\r
214   }\r
215   else\r
216   {\r
217     ShaderDefinitionFactory sdf;\r
218     sdf.SetResources(resources);\r
219     LoadGltfScene(path, sdf, output);\r
220 \r
221     resources.mEnvironmentMaps.push_back({});\r
222   }\r
223 \r
224   if (cameraParameters.empty())\r
225   {\r
226     cameraParameters.push_back(CameraParameters());\r
227     cameraParameters[0].matrix.SetTranslation(CAMERA_DEFAULT_POSITION);\r
228   }\r
229   ConfigureCamera(cameraParameters[0], camera);\r
230 \r
231   ViewProjection viewProjection = cameraParameters[0].GetViewProjection();\r
232   Transforms xforms{\r
233     MatrixStack{},\r
234     viewProjection\r
235   };\r
236   NodeDefinition::CreateParams nodeParams{\r
237     resources,\r
238     xforms\r
239   };\r
240   Customization::Choices choices;\r
241 \r
242   Actor root = Actor::New();\r
243   SetActorCentered(root);\r
244 \r
245   for (auto iRoot : scene.GetRoots())\r
246   {\r
247     auto resourceRefs = resources.CreateRefCounter();\r
248     scene.CountResourceRefs(iRoot, choices, resourceRefs);\r
249     resources.CountEnvironmentReferences(resourceRefs);\r
250 \r
251     resources.LoadResources(resourceRefs, pathProvider);\r
252 \r
253     if (auto actor = scene.CreateNodes(iRoot, choices, nodeParams))\r
254     {\r
255       scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);\r
256       scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));\r
257       ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));\r
258 \r
259       scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));\r
260 \r
261       root.Add(actor);\r
262     }\r
263   }\r
264 \r
265   if (!animations.empty())\r
266   {\r
267     auto getActor = [&root](const std::string& name) {\r
268       return root.FindChildByName(name);\r
269     };\r
270 \r
271     animations[0].ReAnimate(getActor).Play();\r
272   }\r
273 \r
274   return root;\r
275 }\r
276 \r
277 } // nonamespace\r
278 \r
279 SceneLoaderExample::SceneLoaderExample(Dali::Application& app)\r
280 : mApp(app)\r
281 {\r
282   if (!std::getenv("DALI_APPLICATION_PACKAGE"))\r
283   {\r
284     if (auto desktopPrefix = std::getenv("DESKTOP_PREFIX"))\r
285     {\r
286       std::stringstream sstr;\r
287       sstr << desktopPrefix << "/share/com.samsung.dali-demo/res/";\r
288 \r
289       auto daliApplicationPackage = sstr.str();\r
290       setenv("DALI_APPLICATION_PACKAGE", daliApplicationPackage.c_str(), 0);\r
291     }\r
292   }\r
293 \r
294   app.InitSignal().Connect(this, &SceneLoaderExample::OnInit);\r
295   app.TerminateSignal().Connect(this, &SceneLoaderExample::OnTerminate);\r
296 }\r
297 \r
298 void SceneLoaderExample::OnInit(Application& app)\r
299 {\r
300   // get scenes\r
301   auto resPath = Application::GetResourcePath();\r
302   auto scenePath = resPath + RESOURCE_TYPE_DIRS[ResourceType::Mesh];\r
303   auto sceneNames = ListFiles(scenePath, [](const char* name) {\r
304     auto len = strlen(name);\r
305     return (len > DLI_EXTENSION.size() && DLI_EXTENSION.compare(name + (len - DLI_EXTENSION.size())) == 0) ||\r
306       (len > GLTF_EXTENSION.size() && GLTF_EXTENSION.compare(name + (len - GLTF_EXTENSION.size())) == 0);\r
307   });\r
308   mSceneNames = sceneNames;\r
309 \r
310   // create Dali objects\r
311   auto window = app.GetWindow();\r
312 \r
313   // navigation view\r
314   auto navigationView = NavigationView::New();\r
315   navigationView.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);\r
316   SetActorCentered(navigationView);\r
317 \r
318   // Set up the background gradient.\r
319   Property::Array stopOffsets;\r
320   stopOffsets.PushBack(0.0f);\r
321   stopOffsets.PushBack(1.0f);\r
322   Property::Array stopColors;\r
323   stopColors.PushBack(Color::BLACK);\r
324   stopColors.PushBack(Vector4(0.45f, 0.7f, 0.8f, 1.f)); // Medium bright, pastel blue\r
325   const float percentageWindowHeight = window.GetSize().GetHeight() * 0.6f;\r
326 \r
327   navigationView.SetProperty(Toolkit::Control::Property::BACKGROUND, Dali::Property::Map().\r
328     Add(Toolkit::Visual::Property::TYPE, Dali::Toolkit::Visual::GRADIENT).\r
329     Add(Toolkit::GradientVisual::Property::STOP_OFFSET, stopOffsets).\r
330     Add(Toolkit::GradientVisual::Property::STOP_COLOR, stopColors).\r
331     Add(Toolkit::GradientVisual::Property::START_POSITION, Vector2(0.f, -percentageWindowHeight)).\r
332     Add(Toolkit::GradientVisual::Property::END_POSITION, Vector2(0.f, percentageWindowHeight)).\r
333     Add(Toolkit::GradientVisual::Property::UNITS, Toolkit::GradientVisual::Units::USER_SPACE));\r
334   window.Add(navigationView);\r
335   mNavigationView = navigationView;\r
336 \r
337   // item view\r
338   auto tapDetector = TapGestureDetector::New();\r
339   mItemFactory.reset(new ::ItemFactoryImpl(mSceneNames, tapDetector));\r
340 \r
341   auto items = ItemView::New(*mItemFactory);\r
342   SetActorCentered(items);\r
343   items.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);\r
344   items.SetProperty(Actor::Property::KEYBOARD_FOCUSABLE, true);\r
345 \r
346   Vector3 windowSize(window.GetSize());\r
347   auto itemLayout = DefaultItemLayout::New(DefaultItemLayout::LIST);\r
348   itemLayout->SetItemSize(Vector3(windowSize.x, ITEM_HEIGHT, 1.f));\r
349   items.AddLayout(*itemLayout);\r
350   navigationView.Push(items);\r
351 \r
352   mItemLayout = itemLayout;\r
353   mItemView = items;\r
354 \r
355   // camera\r
356   auto camera = CameraActor::New();\r
357   camera.SetInvertYAxis(true);\r
358   window.Add(camera);\r
359   mSceneCamera = camera;\r
360 \r
361   // event handling\r
362   window.KeyEventSignal().Connect(this, &SceneLoaderExample::OnKey);\r
363 \r
364   tapDetector.DetectedSignal().Connect(this, &SceneLoaderExample::OnTap);\r
365   mTapDetector = tapDetector;\r
366 \r
367   // activate layout\r
368   mItemView.ActivateLayout(0, windowSize, 1.f);\r
369 }\r
370 \r
371 void SceneLoaderExample::OnTerminate(Application& app)\r
372 {\r
373   mTapDetector.Reset();\r
374   mPanDetector.Reset();\r
375 \r
376   auto window = app.GetWindow();\r
377   auto renderTasks = window.GetRenderTaskList();\r
378   renderTasks.RemoveTask(mSceneRender);\r
379   mSceneRender.Reset();\r
380 \r
381   UnparentAndReset(mNavigationView);\r
382   UnparentAndReset(mSceneCamera);\r
383 \r
384   mItemFactory.reset();\r
385 }\r
386 \r
387 void SceneLoaderExample::OnKey(const KeyEvent& e)\r
388 {\r
389   if (e.GetState() == KeyEvent::UP)\r
390   {\r
391     if (IsKey(e, DALI_KEY_ESCAPE) || IsKey(e, DALI_KEY_BACK))\r
392     {\r
393       if (mScene)\r
394       {\r
395         mPanDetector.Reset();\r
396 \r
397         mNavigationView.Pop();\r
398         mScene.Reset();\r
399       }\r
400       else\r
401       {\r
402         mApp.Quit();\r
403       }\r
404     }\r
405   }\r
406 }\r
407 \r
408 void SceneLoaderExample::OnPan(Actor actor, const PanGesture& pan)\r
409 {\r
410   auto windowSize = mApp.GetWindow().GetSize();\r
411   Vector2 size{ float(windowSize.GetWidth()), float(windowSize.GetHeight()) };\r
412   float aspect = size.y / size.x;\r
413 \r
414   size /= ROTATION_SCALE;\r
415 \r
416   Vector2 rotation{ pan.GetDisplacement().x / size.x, pan.GetDisplacement().y / size.y * aspect };\r
417 \r
418   Quaternion q = Quaternion(Radian(Degree(rotation.y)), Radian(Degree(rotation.x)), Radian(0.f));\r
419   Quaternion q0 = mScene.GetProperty(Actor::Property::ORIENTATION).Get<Quaternion>();\r
420 \r
421   mScene.SetProperty(Actor::Property::ORIENTATION, q * q0);\r
422 }\r
423 \r
424 void SceneLoaderExample::OnTap(Dali::Actor actor, const Dali::TapGesture& tap)\r
425 {\r
426   auto id = mItemView.GetItemId(actor);\r
427 \r
428   try\r
429   {\r
430     auto window = mApp.GetWindow();\r
431     auto renderTasks = window.GetRenderTaskList();\r
432     renderTasks.RemoveTask(mSceneRender);\r
433 \r
434     auto scene = LoadScene(mSceneNames[id], mSceneCamera);\r
435 \r
436     auto sceneRender = renderTasks.CreateTask();\r
437     sceneRender.SetCameraActor(mSceneCamera);\r
438     sceneRender.SetSourceActor(scene);\r
439     sceneRender.SetExclusive(true);\r
440 \r
441     mScene = scene;\r
442     mSceneRender = sceneRender;\r
443 \r
444     mPanDetector = PanGestureDetector::New();\r
445     mPanDetector.DetectedSignal().Connect(this, &SceneLoaderExample::OnPan);\r
446     mPanDetector.Attach(mNavigationView);\r
447   }\r
448   catch (const DaliException& e)\r
449   {\r
450     mScene = CreateErrorMessage(e.condition);\r
451   }\r
452 \r
453   mNavigationView.Push(mScene);\r
454 }\r