Refactoring of SceneView and Model.
[platform/core/uifw/dali-toolkit.git] / dali-scene3d / internal / controls / model / model-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 <dali-scene3d/internal/controls/model/model-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali-toolkit/dali-toolkit.h>
23 #include <dali-toolkit/internal/controls/control/control-data-impl.h>
24 #include <dali-toolkit/internal/graphics/builtin-shader-extern-gen.h>
25 #include <dali/devel-api/actors/actor-devel.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/internal/controls/scene-view/scene-view-impl.h>
33 #include <dali-scene3d/public-api/controls/model/model.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::Model::New(std::string());
58 }
59
60 // Setup properties, signals and actions using the type-registry.
61 DALI_TYPE_REGISTRATION_BEGIN(Scene3D::Model, 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 bool DEFAULT_MODEL_CHILDREN_SENSITIVE = false;
70
71 static constexpr std::string_view KTX_EXTENSION  = ".ktx";
72 static constexpr std::string_view OBJ_EXTENSION  = ".obj";
73 static constexpr std::string_view GLTF_EXTENSION = ".gltf";
74 static constexpr std::string_view DLI_EXTENSION  = ".dli";
75
76 struct BoundingVolume
77 {
78   void Init()
79   {
80     pointMin = Vector3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
81     pointMax = Vector3(std::numeric_limits<float>::min(), std::numeric_limits<float>::min(), std::numeric_limits<float>::min());
82   }
83
84   void ConsiderNewPointInVolume(const Vector3& position)
85   {
86     pointMin.x = std::min(position.x, pointMin.x);
87     pointMin.y = std::min(position.y, pointMin.y);
88     pointMin.z = std::min(position.z, pointMin.z);
89
90     pointMax.x = std::max(position.x, pointMax.x);
91     pointMax.y = std::max(position.y, pointMax.y);
92     pointMax.z = std::max(position.z, pointMax.z);
93   }
94
95   Vector3 CalculateSize()
96   {
97     return pointMax - pointMin;
98   }
99
100   Vector3 CalculatePivot()
101   {
102     Vector3 pivot = pointMin / (pointMin - pointMax);
103     for(uint32_t i = 0; i < 3; ++i)
104     {
105       // To avoid divid by zero
106       if(pointMin[i] == pointMax[i])
107       {
108         pivot[i] = 0.5f;
109       }
110     }
111     return pivot;
112   }
113
114   Vector3 pointMin;
115   Vector3 pointMax;
116 };
117
118 void ConfigureBlendShapeShaders(
119   Dali::Scene3D::Loader::ResourceBundle& resources, const Dali::Scene3D::Loader::SceneDefinition& scene, Actor root, std::vector<Dali::Scene3D::Loader::BlendshapeShaderConfigurationRequest>&& requests)
120 {
121   std::vector<std::string> errors;
122   auto                     onError = [&errors](const std::string& msg) { errors.push_back(msg); };
123   if(!scene.ConfigureBlendshapeShaders(resources, root, std::move(requests), onError))
124   {
125     Dali::Scene3D::Loader::ExceptionFlinger flinger(ASSERT_LOCATION);
126     for(auto& msg : errors)
127     {
128       flinger << msg << '\n';
129     }
130   }
131 }
132
133 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)
134 {
135   static constexpr uint32_t BOX_POINT_COUNT             = 8;
136   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}};
137
138   Matrix                                       nodeMatrix;
139   const Dali::Scene3D::Loader::NodeDefinition* node        = scene.GetNode(iNode);
140   Matrix                                       localMatrix = node->GetLocalSpace();
141   Matrix::Multiply(nodeMatrix, localMatrix, parentMatrix);
142
143   Vector3 volume[2];
144   if(node->GetExtents(nodeParams.mResources, volume[0], volume[1]))
145   {
146     for(uint32_t i = 0; i < BOX_POINT_COUNT; ++i)
147     {
148       Vector4 position       = Vector4(volume[BBIndex[i][0]].x, volume[BBIndex[i][1]].y, volume[BBIndex[i][2]].z, 1.0f);
149       Vector4 objectPosition = nodeMatrix * position;
150       objectPosition /= objectPosition.w;
151
152       AABB.ConsiderNewPointInVolume(Vector3(objectPosition));
153     }
154   }
155
156   if(node->mCustomization)
157   {
158     if(!node->mChildren.empty())
159     {
160       auto                         choice = choices.Get(node->mCustomization->mTag);
161       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));
162
163       AddModelTreeToAABB(AABB, scene, choices, node->mChildren[i], nodeParams, nodeMatrix);
164     }
165   }
166   else
167   {
168     for(auto i : node->mChildren)
169     {
170       AddModelTreeToAABB(AABB, scene, choices, i, nodeParams, nodeMatrix);
171     }
172   }
173 }
174
175 } // anonymous namespace
176
177 Model::Model(const std::string& modelUrl, const std::string& resourceDirectoryUrl)
178 : Control(ControlBehaviour(DISABLE_SIZE_NEGOTIATION | DISABLE_STYLE_CHANGE_SIGNALS)),
179   mModelUrl(modelUrl),
180   mResourceDirectoryUrl(resourceDirectoryUrl),
181   mModelRoot(),
182   mNaturalSize(Vector3::ZERO),
183   mModelPivot(AnchorPoint::CENTER),
184   mSceneIblScaleFactor(1.0f),
185   mIblScaleFactor(1.0f),
186   mModelChildrenSensitive(DEFAULT_MODEL_CHILDREN_SENSITIVE),
187   mModelResourceReady(false),
188   mIBLResourceReady(true)
189 {
190 }
191
192 Model::~Model()
193 {
194 }
195
196 Dali::Scene3D::Model Model::New(const std::string& modelUrl, const std::string& resourceDirectoryUrl)
197 {
198   Model* impl = new Model(modelUrl, resourceDirectoryUrl);
199
200   Dali::Scene3D::Model handle = Dali::Scene3D::Model(*impl);
201
202   // Second-phase init of the implementation
203   // This can only be done after the CustomActor connection has been made...
204   impl->Initialize();
205
206   return handle;
207 }
208
209 const Actor Model::GetModelRoot() const
210 {
211   return mModelRoot;
212 }
213
214 void Model::SetChildrenSensitive(bool enable)
215 {
216   if(mModelChildrenSensitive != enable)
217   {
218     mModelChildrenSensitive = enable;
219     if(mModelRoot)
220     {
221       mModelRoot.SetProperty(Dali::Actor::Property::SENSITIVE, mModelChildrenSensitive);
222     }
223   }
224 }
225
226 bool Model::GetChildrenSensitive() const
227 {
228   return mModelChildrenSensitive;
229 }
230
231 void Model::SetImageBasedLightSource(const std::string& diffuseUrl, const std::string& specularUrl, float scaleFactor)
232 {
233   mIBLResourceReady       = false;
234   Texture diffuseTexture  = (!diffuseUrl.empty()) ? Dali::Scene3D::Loader::LoadCubeMap(diffuseUrl) : Texture();
235   Texture specularTexture = (!specularUrl.empty()) ? Dali::Scene3D::Loader::LoadCubeMap(specularUrl) : Texture();
236   SetImageBasedLightTexture(diffuseTexture, specularTexture, scaleFactor);
237   mIBLResourceReady = true;
238
239   // If Model resource is already ready, then set resource ready.
240   // If Model resource is still not ready, wait for model resource ready.
241   if(IsResourceReady())
242   {
243     SetResourceReady(false);
244   }
245 }
246
247 void Model::SetImageBasedLightTexture(Dali::Texture diffuseTexture, Dali::Texture specularTexture, float scaleFactor)
248 {
249   // If input texture is wrong, Model is rendered with SceneView's IBL.
250   if(mDiffuseTexture != diffuseTexture || mSpecularTexture != specularTexture)
251   {
252     mDiffuseTexture  = diffuseTexture;
253     mSpecularTexture = specularTexture;
254     mIblScaleFactor  = scaleFactor;
255     UpdateImageBasedLightTexture();
256   }
257 }
258
259 void Model::SetImageBasedLightScaleFactor(float scaleFactor)
260 {
261   mIblScaleFactor = scaleFactor;
262   if(mDiffuseTexture && mSpecularTexture)
263   {
264     UpdateImageBasedLightScaleFactor();
265   }
266 }
267
268 float Model::GetImageBasedLightScaleFactor() const
269 {
270   return mIblScaleFactor;
271 }
272
273 uint32_t Model::GetAnimationCount() const
274 {
275   return mAnimations.size();
276 }
277
278 Dali::Animation Model::GetAnimation(uint32_t index) const
279 {
280   Dali::Animation animation;
281   if(mAnimations.size() > index)
282   {
283     animation = mAnimations[index].second;
284   }
285   return animation;
286 }
287
288 Dali::Animation Model::GetAnimation(const std::string& name) const
289 {
290   Dali::Animation animation;
291   if(!name.empty())
292   {
293     for(auto&& animationData : mAnimations)
294     {
295       if(animationData.first == name)
296       {
297         animation = animationData.second;
298         break;
299       }
300     }
301   }
302   return animation;
303 }
304
305 ///////////////////////////////////////////////////////////
306 //
307 // Private methods
308 //
309
310 void Model::OnSceneConnection(int depth)
311 {
312   if(!mModelRoot)
313   {
314     LoadModel();
315   }
316
317   Actor parent = Self().GetParent();
318   while(parent)
319   {
320     Scene3D::SceneView sceneView = Scene3D::SceneView::DownCast(parent);
321     if(sceneView)
322     {
323       GetImpl(sceneView).RegisterSceneItem(this);
324       mParentSceneView = sceneView;
325       break;
326     }
327     parent = parent.GetParent();
328   }
329
330   Control::OnSceneConnection(depth);
331 }
332
333 void Model::OnSceneDisconnection()
334 {
335   Scene3D::SceneView sceneView = mParentSceneView.GetHandle();
336   if(sceneView)
337   {
338     GetImpl(sceneView).UnregisterSceneItem(this);
339     mParentSceneView.Reset();
340   }
341   Control::OnSceneDisconnection();
342 }
343
344 Vector3 Model::GetNaturalSize()
345 {
346   if(!mModelRoot)
347   {
348     LoadModel();
349   }
350
351   return mNaturalSize;
352 }
353
354 float Model::GetHeightForWidth(float width)
355 {
356   Extents padding;
357   padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
358   return Control::GetHeightForWidth(width) + padding.top + padding.bottom;
359 }
360
361 float Model::GetWidthForHeight(float height)
362 {
363   Extents padding;
364   padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
365   return Control::GetWidthForHeight(height) + padding.start + padding.end;
366 }
367
368 void Model::OnRelayout(const Vector2& size, RelayoutContainer& container)
369 {
370   Control::OnRelayout(size, container);
371   ScaleModel();
372 }
373
374 bool Model::IsResourceReady() const
375 {
376   return mModelResourceReady && mIBLResourceReady;
377 }
378
379 void Model::LoadModel()
380 {
381   std::filesystem::path modelUrl(mModelUrl);
382   if(mResourceDirectoryUrl.empty())
383   {
384     mResourceDirectoryUrl = std::string(modelUrl.parent_path()) + "/";
385   }
386   std::string extension = modelUrl.extension();
387   std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
388
389   Dali::Scene3D::Loader::ResourceBundle::PathProvider pathProvider = [&](Dali::Scene3D::Loader::ResourceType::Value type) {
390     return mResourceDirectoryUrl;
391   };
392
393   Dali::Scene3D::Loader::ResourceBundle                        resources;
394   Dali::Scene3D::Loader::SceneDefinition                       scene;
395   std::vector<Dali::Scene3D::Loader::AnimationGroupDefinition> animGroups;
396   std::vector<Dali::Scene3D::Loader::CameraParameters>         cameraParameters;
397   std::vector<Dali::Scene3D::Loader::LightParameters>          lights;
398
399   std::vector<Dali::Scene3D::Loader::AnimationDefinition> animations;
400   animations.clear();
401
402   Dali::Scene3D::Loader::LoadResult output{resources, scene, animations, animGroups, cameraParameters, lights};
403
404   if(extension == DLI_EXTENSION)
405   {
406     Dali::Scene3D::Loader::DliLoader              loader;
407     Dali::Scene3D::Loader::DliLoader::InputParams input{
408       pathProvider(Dali::Scene3D::Loader::ResourceType::Mesh),
409       nullptr,
410       {},
411       {},
412       nullptr,
413       {}};
414     Dali::Scene3D::Loader::DliLoader::LoadParams loadParams{input, output};
415     if(!loader.LoadScene(mModelUrl, loadParams))
416     {
417       Dali::Scene3D::Loader::ExceptionFlinger(ASSERT_LOCATION) << "Failed to load scene from '" << mModelUrl << "': " << loader.GetParseError();
418     }
419   }
420   else if(extension == GLTF_EXTENSION)
421   {
422     Dali::Scene3D::Loader::ShaderDefinitionFactory sdf;
423     sdf.SetResources(resources);
424     Dali::Scene3D::Loader::LoadGltfScene(mModelUrl, sdf, output);
425
426     resources.mEnvironmentMaps.push_back({});
427   }
428   else
429   {
430     DALI_LOG_ERROR("Unsupported model type.\n");
431   }
432
433   Dali::Scene3D::Loader::Transforms                   xforms{Dali::Scene3D::Loader::MatrixStack{}, Dali::Scene3D::Loader::ViewProjection{}};
434   Dali::Scene3D::Loader::NodeDefinition::CreateParams nodeParams{resources, xforms, {}, {}, {}};
435   Dali::Scene3D::Loader::Customization::Choices       choices;
436
437   mModelRoot = Actor::New();
438
439   BoundingVolume AABB;
440   for(auto iRoot : scene.GetRoots())
441   {
442     auto resourceRefs = resources.CreateRefCounter();
443     scene.CountResourceRefs(iRoot, choices, resourceRefs);
444     resources.CountEnvironmentReferences(resourceRefs);
445
446     resources.LoadResources(resourceRefs, pathProvider);
447
448     // glTF Mesh is defined in right hand coordinate system, with positive Y for Up direction.
449     // Because DALi uses left hand system, Y direciton will be flipped for environment map sampling.
450     for(auto&& env : resources.mEnvironmentMaps)
451     {
452       env.first.mYDirection = Y_DIRECTION;
453     }
454
455     if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
456     {
457       scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
458       scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
459       ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));
460
461       scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
462
463       mModelRoot.Add(actor);
464     }
465
466     AddModelTreeToAABB(AABB, scene, choices, iRoot, nodeParams, Matrix::IDENTITY);
467   }
468
469   if(!resources.mEnvironmentMaps.empty())
470   {
471     mDefaultDiffuseTexture  = resources.mEnvironmentMaps.front().second.mDiffuse;
472     mDefaultSpecularTexture = resources.mEnvironmentMaps.front().second.mSpecular;
473   }
474
475   if(!animations.empty())
476   {
477     auto getActor = [&](const std::string& name) {
478       return mModelRoot.FindChildByName(name);
479     };
480
481     mAnimations.clear();
482     for(auto&& animation : animations)
483     {
484       Dali::Animation anim = animation.ReAnimate(getActor);
485
486       mAnimations.push_back({animation.mName, anim});
487     }
488   }
489
490   mRenderableActors.clear();
491   CollectRenderableActor(mModelRoot);
492   UpdateImageBasedLightTexture();
493   UpdateImageBasedLightScaleFactor();
494
495   mNaturalSize = AABB.CalculateSize();
496   mModelPivot  = AABB.CalculatePivot();
497   mModelRoot.SetProperty(Dali::Actor::Property::SIZE, mNaturalSize);
498   Vector3 controlSize = Self().GetProperty<Vector3>(Dali::Actor::Property::SIZE);
499   if(controlSize.x == 0.0f || controlSize.y == 0.0f)
500   {
501     Self().SetProperty(Dali::Actor::Property::SIZE, mNaturalSize);
502   }
503
504   FitModelPosition();
505   ScaleModel();
506
507   mModelRoot.SetProperty(Dali::Actor::Property::SENSITIVE, mModelChildrenSensitive);
508
509   Self().Add(mModelRoot);
510
511   Self().SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
512   Self().SetProperty(Dali::Actor::Property::ANCHOR_POINT, Vector3(mModelPivot.x, 1.0f - mModelPivot.y, mModelPivot.z));
513
514   mModelResourceReady = true;
515
516   Control::SetResourceReady(false);
517 }
518
519 void Model::ScaleModel()
520 {
521   if(mModelRoot)
522   {
523     float   scale = 1.0f;
524     Vector3 size  = Self().GetProperty<Vector3>(Dali::Actor::Property::SIZE);
525     if(size.x > 0.0f && size.y > 0.0f)
526     {
527       scale = MAXFLOAT;
528       scale = std::min(size.x / mNaturalSize.x, scale);
529       scale = std::min(size.y / mNaturalSize.y, scale);
530     }
531     // Models in glTF and dli are defined as right hand coordinate system.
532     // DALi uses left hand coordinate system. Scaling negative is for change winding order.
533     mModelRoot.SetProperty(Dali::Actor::Property::SCALE, Y_DIRECTION * scale);
534   }
535 }
536
537 void Model::FitModelPosition()
538 {
539   if(mModelRoot)
540   {
541     // Loaded model pivot is not the model center.
542     mModelRoot.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
543     mModelRoot.SetProperty(Dali::Actor::Property::ANCHOR_POINT, Vector3::ONE - mModelPivot);
544   }
545 }
546
547 void Model::CollectRenderableActor(Actor actor)
548 {
549   uint32_t rendererCount = actor.GetRendererCount();
550   if(rendererCount)
551   {
552     mRenderableActors.push_back(actor);
553   }
554
555   uint32_t childrenCount = actor.GetChildCount();
556   for(uint32_t i = 0; i < childrenCount; ++i)
557   {
558     CollectRenderableActor(actor.GetChildAt(i));
559   }
560 }
561
562 void Model::UpdateImageBasedLightTexture()
563 {
564   Dali::Texture currentDiffuseTexture  = (mDiffuseTexture) ? mDiffuseTexture : mSceneDiffuseTexture;
565   Dali::Texture currentSpecularTexture = (mSpecularTexture) ? mSpecularTexture : mSceneSpecularTexture;
566   float         currentIBLScaleFactor  = (mDiffuseTexture && mSpecularTexture) ? mIblScaleFactor : mSceneIblScaleFactor;
567   if(!currentDiffuseTexture || !currentSpecularTexture)
568   {
569     currentDiffuseTexture  = mDefaultDiffuseTexture;
570     currentSpecularTexture = mDefaultSpecularTexture;
571     currentIBLScaleFactor  = Dali::Scene3D::Loader::EnvironmentDefinition::GetDefaultIntensity();
572   }
573
574   for(auto&& actor : mRenderableActors)
575   {
576     Actor renderableActor = actor.GetHandle();
577     if(renderableActor)
578     {
579       uint32_t rendererCount = renderableActor.GetRendererCount();
580       for(uint32_t i = 0; i < rendererCount; ++i)
581       {
582         Dali::Renderer renderer = renderableActor.GetRendererAt(i);
583         if(renderer)
584         {
585           Dali::TextureSet textures = renderer.GetTextures();
586           if(textures)
587           {
588             uint32_t textureCount = textures.GetTextureCount();
589             // EnvMap requires at least 2 texture, diffuse and specular
590             if(textureCount > 2u)
591             {
592               textures.SetTexture(textureCount - OFFSET_FOR_DIFFUSE_CUBE_TEXTURE, currentDiffuseTexture);
593               textures.SetTexture(textureCount - OFFSET_FOR_SPECULAR_CUBE_TEXTURE, currentSpecularTexture);
594             }
595           }
596         }
597       }
598       renderableActor.RegisterProperty(Dali::Scene3D::Loader::NodeDefinition::GetIblScaleFactorUniformName().data(), currentIBLScaleFactor);
599     }
600   }
601 }
602
603 void Model::UpdateImageBasedLightScaleFactor()
604 {
605   if((!mDiffuseTexture || !mSpecularTexture) &&
606      (!mSceneDiffuseTexture || !mSceneSpecularTexture))
607   {
608     return;
609   }
610
611   float currentIBLScaleFactor = (mDiffuseTexture && mSpecularTexture) ? mIblScaleFactor : mSceneIblScaleFactor;
612   for(auto&& actor : mRenderableActors)
613   {
614     Actor renderableActor = actor.GetHandle();
615     if(renderableActor)
616     {
617       renderableActor.RegisterProperty(Dali::Scene3D::Loader::NodeDefinition::GetIblScaleFactorUniformName().data(), currentIBLScaleFactor);
618     }
619   }
620 }
621
622 void Model::NotifyImageBasedLightTexture(Dali::Texture diffuseTexture, Dali::Texture specularTexture, float scaleFactor)
623 {
624   if(mSceneDiffuseTexture != diffuseTexture || mSceneSpecularTexture != specularTexture)
625   {
626     mSceneDiffuseTexture  = diffuseTexture;
627     mSceneSpecularTexture = specularTexture;
628     mSceneIblScaleFactor  = scaleFactor;
629     UpdateImageBasedLightTexture();
630   }
631 }
632
633 void Model::NotifyImageBasedLightScaleFactor(float scaleFactor)
634 {
635   mSceneIblScaleFactor = scaleFactor;
636   if(mSceneDiffuseTexture && mSceneSpecularTexture)
637   {
638     UpdateImageBasedLightScaleFactor();
639   }
640 }
641
642 } // namespace Internal
643 } // namespace Scene3D
644 } // namespace Dali