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