0004aac7ca8e14f2e3f52a9b493a62abc6b0a43d
[platform/core/uifw/dali-toolkit.git] / dali-scene-loader / internal / controls / model-view / model-view-impl.cpp
1 /*
2  * Copyright (c) 2022 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include "model-view-impl.h"
20
21 // EXTERNAL INCLUDES
22 #include <dali/devel-api/adaptor-framework/image-loading.h>
23 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
24 #include <dali-toolkit/dali-toolkit.h>
25 #include <dali-toolkit/devel-api/controls/control-devel.h>
26 #include <dali-toolkit/internal/controls/control/control-data-impl.h>
27 #include <dali-toolkit/internal/graphics/builtin-shader-extern-gen.h>
28 #include <dali/public-api/object/type-registry-helper.h>
29 #include <dali/public-api/object/type-registry.h>
30 #include <dali/integration-api/debug.h>
31 #include <filesystem>
32
33 // INTERNAL INCLUDES
34 #include <dali-scene-loader/public-api/animation-definition.h>
35 #include <dali-scene-loader/public-api/camera-parameters.h>
36 #include <dali-scene-loader/public-api/dli-loader.h>
37 #include <dali-scene-loader/public-api/gltf2-loader.h>
38 #include <dali-scene-loader/public-api/light-parameters.h>
39 #include <dali-scene-loader/public-api/load-result.h>
40 #include <dali-scene-loader/public-api/node-definition.h>
41 #include <dali-scene-loader/public-api/scene-definition.h>
42 #include <dali-scene-loader/public-api/shader-definition-factory.h>
43 #include <dali-scene-loader/public-api/controls/model-view/model-view.h>
44
45 using namespace Dali;
46
47 namespace Dali
48 {
49 namespace Scene3D
50 {
51 namespace Internal
52 {
53 namespace
54 {
55 BaseHandle Create()
56 {
57   return Scene3D::ModelView::New(std::string());
58 }
59
60 // Setup properties, signals and actions using the type-registry.
61 DALI_TYPE_REGISTRATION_BEGIN(Scene3D::ModelView, Toolkit::Control, Create);
62 DALI_TYPE_REGISTRATION_END()
63
64 static constexpr uint32_t OFFSET_FOR_DIFFUSE_CUBE_TEXTURE  = 2u;
65 static constexpr uint32_t OFFSET_FOR_SPECULAR_CUBE_TEXTURE = 1u;
66
67 static constexpr Vector3 Y_DIRECTION(1.0f, -1.0f, 1.0f);
68
69 static constexpr std::string_view KTX_EXTENSION  = ".ktx";
70 static constexpr std::string_view OBJ_EXTENSION  = ".obj";
71 static constexpr std::string_view GLTF_EXTENSION = ".gltf";
72 static constexpr std::string_view DLI_EXTENSION  = ".dli";
73
74 struct BoundingVolume
75 {
76   void Init()
77   {
78     pointMin = Vector3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
79     pointMax = Vector3(std::numeric_limits<float>::min(), std::numeric_limits<float>::min(), std::numeric_limits<float>::min());
80   }
81
82   void ConsiderNewPointInVolume(const Vector3& position)
83   {
84     pointMin.x = std::min(position.x, pointMin.x);
85     pointMin.y = std::min(position.y, pointMin.y);
86     pointMin.z = std::min(position.z, pointMin.z);
87
88     pointMax.x = std::max(position.x, pointMax.x);
89     pointMax.y = std::max(position.y, pointMax.y);
90     pointMax.z = std::max(position.z, pointMax.z);
91   }
92
93   Vector3 CalculateSize()
94   {
95     return pointMax - pointMin;
96   }
97
98   Vector3 CalculatePivot()
99   {
100     Vector3 pivot = pointMin / (pointMin - pointMax);
101     for(uint32_t i = 0; i < 3; ++i)
102     {
103       // To avoid divid by zero
104       if(pointMin[i] == pointMax[i])
105       {
106         pivot[i] = 0.5f;
107       }
108     }
109     return pivot;
110   }
111
112   Vector3 pointMin;
113   Vector3 pointMax;
114 };
115
116 Texture LoadCubeMap(const std::string& cubeMapPath)
117 {
118   std::filesystem::path modelPath(cubeMapPath);
119   std::string           extension = modelPath.extension();
120   std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
121
122   Texture cubeTexture;
123   if(extension == KTX_EXTENSION)
124   {
125     SceneLoader::CubeData cubeData;
126     if(SceneLoader::LoadCubeMapData(cubeMapPath, cubeData))
127     {
128       cubeTexture = cubeData.CreateTexture();
129     }
130     else
131     {
132       DALI_LOG_ERROR("Fail to load cube map, %s\n", cubeMapPath.c_str());
133     }
134   }
135
136   return cubeTexture;
137 }
138
139 void ConfigureBlendShapeShaders(
140   SceneLoader::ResourceBundle& resources, const SceneLoader::SceneDefinition& scene, Actor root, std::vector<SceneLoader::BlendshapeShaderConfigurationRequest>&& requests)
141 {
142   std::vector<std::string> errors;
143   auto                     onError = [&errors](const std::string& msg) { errors.push_back(msg); };
144   if(!scene.ConfigureBlendshapeShaders(resources, root, std::move(requests), onError))
145   {
146     SceneLoader::ExceptionFlinger flinger(ASSERT_LOCATION);
147     for(auto& msg : errors)
148     {
149       flinger << msg << '\n';
150     }
151   }
152 }
153
154 void AddModelTreeToAABB(BoundingVolume& AABB, const SceneLoader::SceneDefinition& scene, const Dali::SceneLoader::Customization::Choices& choices, Dali::SceneLoader::Index iNode, Dali::SceneLoader::NodeDefinition::CreateParams& nodeParams, Matrix parentMatrix)
155 {
156   static constexpr uint32_t BOX_POINT_COUNT             = 8;
157   static uint32_t           BBIndex[BOX_POINT_COUNT][3] = {{0, 0, 0}, {0, 1, 0}, {1, 0, 0}, {1, 1, 0}, {0, 0, 1}, {0, 1, 1}, {1, 0, 1}, {1, 1, 1}};
158
159   Matrix nodeMatrix;
160   const SceneLoader::NodeDefinition* node        = scene.GetNode(iNode);
161   Matrix                             localMatrix = node->GetLocalSpace();
162   Matrix::Multiply(nodeMatrix, localMatrix, parentMatrix);
163
164   Vector3 volume[2];
165   if(node->GetExtents(nodeParams.mResources, volume[0], volume[1]))
166   {
167     for(uint32_t i = 0; i < BOX_POINT_COUNT; ++i)
168     {
169       Vector4 position       = Vector4(volume[BBIndex[i][0]].x, volume[BBIndex[i][1]].y, volume[BBIndex[i][2]].z, 1.0f);
170       Vector4 objectPosition = nodeMatrix * position;
171       objectPosition /= objectPosition.w;
172
173       AABB.ConsiderNewPointInVolume(Vector3(objectPosition));
174     }
175   }
176
177   if(node->mCustomization)
178   {
179     if(!node->mChildren.empty())
180     {
181       auto                     choice = choices.Get(node->mCustomization->mTag);
182       Dali::SceneLoader::Index i      = std::min(choice != Dali::SceneLoader::Customization::NONE ? choice : 0, static_cast<Dali::SceneLoader::Index>(node->mChildren.size() - 1));
183
184       AddModelTreeToAABB(AABB, scene, choices, node->mChildren[i], nodeParams, nodeMatrix);
185     }
186   }
187   else
188   {
189     for(auto i : node->mChildren)
190     {
191       AddModelTreeToAABB(AABB, scene, choices, i, nodeParams, nodeMatrix);
192     }
193   }
194 }
195
196 } // anonymous namespace
197
198 ModelView::ModelView(const std::string& modelPath, const std::string& resourcePath)
199 : Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT)),
200   mModelPath(modelPath),
201   mResourcePath(resourcePath),
202   mModelLayer(),
203   mModelRoot(),
204   mNaturalSize(Vector3::ZERO),
205   mModelPivot(AnchorPoint::CENTER),
206   mIblScaleFactor(1.0f),
207   mFitSize(false),
208   mFitCenter(false)
209 {
210 }
211
212 ModelView::~ModelView()
213 {
214 }
215
216 Dali::Scene3D::ModelView ModelView::New(const std::string& modelPath, const std::string& resourcePath)
217 {
218   ModelView* impl = new ModelView(modelPath, resourcePath);
219
220   Dali::Scene3D::ModelView handle = Dali::Scene3D::ModelView(*impl);
221
222   // Second-phase init of the implementation
223   // This can only be done after the CustomActor connection has been made...
224   impl->Initialize();
225
226   return handle;
227 }
228
229 const Actor ModelView::GetModelRoot()
230 {
231   return mModelRoot;
232 }
233
234 void ModelView::FitSize(bool fit)
235 {
236   mFitSize = fit;
237   ScaleModel();
238 }
239
240 void ModelView::FitCenter(bool fit)
241 {
242   mFitCenter = fit;
243   FitModelPosition();
244 }
245
246 void ModelView::SetImageBasedLightSource(const std::string& diffuse, const std::string& specular, float scaleFactor)
247 {
248   Texture diffuseTexture = LoadCubeMap(diffuse);
249   if(diffuseTexture)
250   {
251     Texture specularTexture = LoadCubeMap(specular);
252     if(specularTexture)
253     {
254       mDiffuseTexture  = diffuseTexture;
255       mSpecularTexture = specularTexture;
256       mIblScaleFactor  = scaleFactor;
257
258       SetImageBasedLight(mModelRoot);
259     }
260   }
261 }
262
263 uint32_t ModelView::GetAnimationCount()
264 {
265   return mAnimations.size();
266 }
267
268 Dali::Animation ModelView::GetAnimation(uint32_t index)
269 {
270   Dali::Animation animation;
271   if(mAnimations.size() > index)
272   {
273     animation = mAnimations[index].second;
274   }
275   return animation;
276 }
277
278 Dali::Animation ModelView::GetAnimation(const std::string& name)
279 {
280   Dali::Animation animation;
281   if(!name.empty())
282   {
283     for(auto&& animationData : mAnimations)
284     {
285       if(animationData.first == name)
286       {
287         animation = animationData.second;
288         break;
289       }
290     }
291   }
292   return animation;
293 }
294
295 ///////////////////////////////////////////////////////////
296 //
297 // Private methods
298 //
299
300 void ModelView::OnSceneConnection(int depth)
301 {
302   if(!mModelRoot)
303   {
304     LoadModel();
305   }
306
307   Control::OnSceneConnection(depth);
308 }
309
310 void ModelView::OnInitialize()
311 {
312   Actor self  = Self();
313   mModelLayer = Layer::New();
314   mModelLayer.SetProperty(Layer::Property::BEHAVIOR, Layer::LAYER_3D);
315   mModelLayer.SetProperty(Layer::Property::DEPTH_TEST, true);
316   mModelLayer.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
317   mModelLayer.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
318   mModelLayer.SetResizePolicy(ResizePolicy::FILL_TO_PARENT,
319                               Dimension::ALL_DIMENSIONS);
320
321   // Models in glTF and dli are defined as right hand coordinate system.
322   // DALi uses left hand coordinate system. Scaling negative is for change winding order.
323   mModelLayer.SetProperty(Dali::Actor::Property::SCALE_Y, -1.0f);
324   self.Add(mModelLayer);
325 }
326
327 Vector3 ModelView::GetNaturalSize()
328 {
329   if(!mModelRoot)
330   {
331     LoadModel();
332   }
333
334   return mNaturalSize;
335 }
336
337 float ModelView::GetHeightForWidth(float width)
338 {
339   Extents padding;
340   padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
341   return Control::GetHeightForWidth(width) + padding.top + padding.bottom;
342 }
343
344 float ModelView::GetWidthForHeight(float height)
345 {
346   Extents padding;
347   padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
348   return Control::GetWidthForHeight(height) + padding.start + padding.end;
349 }
350
351 void ModelView::OnRelayout(const Vector2& size, RelayoutContainer& container)
352 {
353   Control::OnRelayout(size, container);
354   ScaleModel();
355 }
356
357 void ModelView::LoadModel()
358 {
359   std::filesystem::path modelPath(mModelPath);
360   if(mResourcePath.empty())
361   {
362     mResourcePath = std::string(modelPath.parent_path()) + "/";
363   }
364   std::string extension = modelPath.extension();
365   std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
366
367   SceneLoader::ResourceBundle::PathProvider pathProvider = [&](SceneLoader::ResourceType::Value type) {
368     return mResourcePath;
369   };
370
371   SceneLoader::ResourceBundle                        resources;
372   SceneLoader::SceneDefinition                       scene;
373   std::vector<SceneLoader::AnimationGroupDefinition> animGroups;
374   std::vector<SceneLoader::CameraParameters>         cameraParameters;
375   std::vector<SceneLoader::LightParameters>          lights;
376
377   std::vector<Dali::SceneLoader::AnimationDefinition> animations;
378   animations.clear();
379
380   SceneLoader::LoadResult output{resources, scene, animations, animGroups, cameraParameters, lights};
381
382   if(extension == DLI_EXTENSION)
383   {
384     SceneLoader::DliLoader              loader;
385     SceneLoader::DliLoader::InputParams input{
386       pathProvider(SceneLoader::ResourceType::Mesh),
387       nullptr,
388       {},
389       {},
390       nullptr,
391       {}};
392     SceneLoader::DliLoader::LoadParams loadParams{input, output};
393     if(!loader.LoadScene(mModelPath, loadParams))
394     {
395       SceneLoader::ExceptionFlinger(ASSERT_LOCATION) << "Failed to load scene from '" << mModelPath << "': " << loader.GetParseError();
396     }
397   }
398   else if(extension == GLTF_EXTENSION)
399   {
400     SceneLoader::ShaderDefinitionFactory sdf;
401     sdf.SetResources(resources);
402     SceneLoader::LoadGltfScene(mModelPath, sdf, output);
403
404     resources.mEnvironmentMaps.push_back({});
405   }
406   else
407   {
408     DALI_LOG_ERROR("Unsupported model type.\n");
409   }
410
411   SceneLoader::Transforms                   xforms{SceneLoader::MatrixStack{}, SceneLoader::ViewProjection{}};
412   SceneLoader::NodeDefinition::CreateParams nodeParams{resources, xforms, {}, {}, {}};
413   SceneLoader::Customization::Choices       choices;
414
415   mModelRoot = Actor::New();
416
417   BoundingVolume AABB;
418   for(auto iRoot : scene.GetRoots())
419   {
420     auto resourceRefs = resources.CreateRefCounter();
421     scene.CountResourceRefs(iRoot, choices, resourceRefs);
422     resources.CountEnvironmentReferences(resourceRefs);
423
424     resources.LoadResources(resourceRefs, pathProvider);
425
426     // glTF Mesh is defined in right hand coordinate system, with positive Y for Up direction.
427     // Because DALi uses left hand system, Y direciton will be flipped for environment map sampling.
428     for(auto&& env : resources.mEnvironmentMaps)
429     {
430       env.first.mYDirection = Y_DIRECTION;
431     }
432
433     if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
434     {
435       scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
436       scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
437       ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));
438
439       scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
440
441       mModelRoot.Add(actor);
442     }
443
444     AddModelTreeToAABB(AABB, scene, choices, iRoot, nodeParams, Matrix::IDENTITY);
445   }
446
447   if(!animations.empty())
448   {
449     auto getActor = [&](const std::string& name) {
450       return mModelRoot.FindChildByName(name);
451     };
452
453     mAnimations.clear();
454     for(auto&& animation : animations)
455     {
456       Dali::Animation anim = animation.ReAnimate(getActor);
457
458       mAnimations.push_back({animation.mName, anim});
459     }
460   }
461
462   SetImageBasedLight(mModelRoot);
463
464   mNaturalSize = AABB.CalculateSize();
465   mModelPivot = AABB.CalculatePivot();
466   mModelRoot.SetProperty(Dali::Actor::Property::SIZE, mNaturalSize);
467
468   FitModelPosition();
469   ScaleModel();
470
471   mModelLayer.Add(mModelRoot);
472 }
473
474 void ModelView::ScaleModel()
475 {
476   if(mModelRoot)
477   {
478     if(mFitSize)
479     {
480       Vector3 size = Self().GetProperty<Vector3>(Dali::Actor::Property::SIZE);
481       if(size.x > 0.0f && size.y > 0.0f)
482       {
483         float scaleFactor = MAXFLOAT;
484         scaleFactor       = std::min(size.x / mNaturalSize.x, scaleFactor);
485         scaleFactor       = std::min(size.y / mNaturalSize.y, scaleFactor);
486         mModelRoot.SetProperty(Dali::Actor::Property::SCALE, scaleFactor);
487       }
488       else
489       {
490         DALI_LOG_ERROR("ModelView size is wrong.");
491       }
492     }
493     else
494     {
495       mModelRoot.SetProperty(Dali::Actor::Property::SCALE, 1.0f);
496     }
497   }
498 }
499
500 void ModelView::FitModelPosition()
501 {
502   if(mModelRoot)
503   {
504     if(mFitCenter)
505     {
506       // Loaded model pivot is not the model center.
507       mModelRoot.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
508       mModelRoot.SetProperty(Dali::Actor::Property::ANCHOR_POINT, Vector3::ONE - mModelPivot);
509     }
510     else
511     {
512       mModelRoot.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
513       mModelRoot.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
514     }
515   }
516 }
517
518 void ModelView::SetImageBasedLight(Actor node)
519 {
520   if(!mDiffuseTexture || !mSpecularTexture || !node)
521   {
522     return;
523   }
524
525   uint32_t rendererCount = node.GetRendererCount();
526   if(rendererCount)
527   {
528     node.RegisterProperty(SceneLoader::NodeDefinition::GetIblScaleFactorUniformName().data(), mIblScaleFactor);
529   }
530
531   for(uint32_t i = 0; i < rendererCount; ++i)
532   {
533     Dali::Renderer renderer = node.GetRendererAt(i);
534     if(renderer)
535     {
536       Dali::TextureSet textures = renderer.GetTextures();
537       if(textures)
538       {
539         uint32_t textureCount = textures.GetTextureCount();
540         // EnvMap requires at least 2 texture, diffuse and specular
541         if(textureCount > 2u)
542         {
543           textures.SetTexture(textureCount - OFFSET_FOR_DIFFUSE_CUBE_TEXTURE, mDiffuseTexture);
544           textures.SetTexture(textureCount - OFFSET_FOR_SPECULAR_CUBE_TEXTURE, mSpecularTexture);
545         }
546       }
547     }
548   }
549
550   uint32_t childrenCount = node.GetChildCount();
551   for(uint32_t i = 0; i < childrenCount; ++i)
552   {
553     SetImageBasedLight(node.GetChildAt(i));
554   }
555 }
556
557 } // namespace Internal
558 } // namespace Scene3D
559 } // namespace Dali