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