28d093368bdd2fb1cdcf4fdcc458abebc43d3c66
[platform/core/uifw/dali-toolkit.git] / dali-scene3d / internal / controls / scene-view / scene-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 <dali-scene3d/internal/controls/scene-view/scene-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/devel-api/visual-factory/visual-factory.h>
25 #include <dali-toolkit/internal/controls/control/control-data-impl.h>
26 #include <dali-toolkit/public-api/image-loader/image-url.h>
27 #include <dali-toolkit/public-api/image-loader/image.h>
28 #include <dali-toolkit/public-api/image-loader/sync-image-loader.h>
29 #include <dali/devel-api/actors/camera-actor-devel.h>
30 #include <dali/devel-api/adaptor-framework/window-devel.h>
31 #include <dali/devel-api/common/stage.h>
32 #include <dali/devel-api/rendering/frame-buffer-devel.h>
33 #include <dali/integration-api/debug.h>
34 #include <dali/public-api/math/math-utils.h>
35 #include <dali/public-api/object/type-registry-helper.h>
36 #include <dali/public-api/object/type-registry.h>
37 #include <string_view>
38
39 // INTERNAL INCLUDES
40 #include <dali-scene3d/internal/controls/model/model-impl.h>
41 #include <dali-scene3d/internal/graphics/builtin-shader-extern-gen.h>
42 #include <dali-scene3d/public-api/loader/cube-map-loader.h>
43
44 using namespace Dali;
45
46 namespace Dali
47 {
48 namespace Scene3D
49 {
50 namespace Internal
51 {
52 namespace
53 {
54 BaseHandle Create()
55 {
56   return Scene3D::SceneView::New();
57 }
58
59 // Setup properties, signals and actions using the type-registry.
60 DALI_TYPE_REGISTRATION_BEGIN(Scene3D::SceneView, Toolkit::Control, Create);
61 DALI_TYPE_REGISTRATION_END()
62
63 Property::Index   RENDERING_BUFFER    = Dali::Toolkit::Control::CONTROL_PROPERTY_END_INDEX + 1;
64 constexpr int32_t DEFAULT_ORIENTATION = 0;
65
66 constexpr uint8_t DEFAULT_FRAME_BUFFER_MULTI_SAMPLING_LEVEL = 4u;
67
68 static constexpr std::string_view SKYBOX_INTENSITY_STRING = "uIntensity";
69
70 Dali::Actor CreateSkybox(const std::string& skyboxUrl, Scene3D::SceneView::SkyboxType skyboxType)
71 {
72   struct Vertex
73   {
74     Vector3 aPosition;
75   };
76
77   Vertex skyboxVertices[] = {
78     // back
79     {Vector3(-1.0f, 1.0f, -1.0f)},
80     {Vector3(-1.0f, -1.0f, -1.0f)},
81     {Vector3(1.0f, -1.0f, -1.0f)},
82     {Vector3(1.0f, -1.0f, -1.0f)},
83     {Vector3(1.0f, 1.0f, -1.0f)},
84     {Vector3(-1.0f, 1.0f, -1.0f)},
85
86     // left
87     {Vector3(-1.0f, -1.0f, 1.0f)},
88     {Vector3(-1.0f, -1.0f, -1.0f)},
89     {Vector3(-1.0f, 1.0f, -1.0f)},
90     {Vector3(-1.0f, 1.0f, -1.0f)},
91     {Vector3(-1.0f, 1.0f, 1.0f)},
92     {Vector3(-1.0f, -1.0f, 1.0f)},
93
94     // right
95     {Vector3(1.0f, -1.0f, -1.0f)},
96     {Vector3(1.0f, -1.0f, 1.0f)},
97     {Vector3(1.0f, 1.0f, 1.0f)},
98     {Vector3(1.0f, 1.0f, 1.0f)},
99     {Vector3(1.0f, 1.0f, -1.0f)},
100     {Vector3(1.0f, -1.0f, -1.0f)},
101
102     // front
103     {Vector3(-1.0f, -1.0f, 1.0f)},
104     {Vector3(-1.0f, 1.0f, 1.0f)},
105     {Vector3(1.0f, 1.0f, 1.0f)},
106     {Vector3(1.0f, 1.0f, 1.0f)},
107     {Vector3(1.0f, -1.0f, 1.0f)},
108     {Vector3(-1.0f, -1.0f, 1.0f)},
109
110     // botton
111     {Vector3(-1.0f, 1.0f, -1.0f)},
112     {Vector3(1.0f, 1.0f, -1.0f)},
113     {Vector3(1.0f, 1.0f, 1.0f)},
114     {Vector3(1.0f, 1.0f, 1.0f)},
115     {Vector3(-1.0f, 1.0f, 1.0f)},
116     {Vector3(-1.0f, 1.0f, -1.0f)},
117
118     // top
119     {Vector3(-1.0f, -1.0f, -1.0f)},
120     {Vector3(-1.0f, -1.0f, 1.0f)},
121     {Vector3(1.0f, -1.0f, -1.0f)},
122     {Vector3(1.0f, -1.0f, -1.0f)},
123     {Vector3(-1.0f, -1.0f, 1.0f)},
124     {Vector3(1.0f, -1.0f, 1.0f)}};
125
126   Dali::VertexBuffer vertexBuffer = Dali::VertexBuffer::New(Property::Map().Add("aPosition", Property::VECTOR3));
127   vertexBuffer.SetData(skyboxVertices, sizeof(skyboxVertices) / sizeof(Vertex));
128
129   Dali::Geometry skyboxGeometry = Geometry::New();
130   skyboxGeometry.AddVertexBuffer(vertexBuffer);
131   skyboxGeometry.SetType(Geometry::TRIANGLES);
132
133   Dali::Texture  skyboxTexture;
134   Dali::Shader   shaderSkybox;
135   Dali::Renderer skyboxRenderer;
136
137   if(skyboxType == Scene3D::SceneView::SkyboxType::CUBEMAP)
138   {
139     skyboxTexture = Dali::Scene3D::Loader::LoadCubeMap(skyboxUrl);
140     shaderSkybox  = Shader::New(SHADER_SKYBOX_SHADER_VERT.data(), SHADER_SKYBOX_SHADER_FRAG.data());
141   }
142   else // Scene3D::SceneView::SkyboxType::EQUIRECTANGULAR
143   {
144     // Load image from file
145     PixelData pixels = Dali::Toolkit::SyncImageLoader::Load(skyboxUrl);
146
147     skyboxTexture = Texture::New(TextureType::TEXTURE_2D, pixels.GetPixelFormat(), pixels.GetWidth(), pixels.GetHeight());
148     skyboxTexture.Upload(pixels, 0, 0, 0, 0, pixels.GetWidth(), pixels.GetHeight());
149     shaderSkybox = Shader::New(SHADER_SKYBOX_SHADER_VERT.data(), SHADER_SKYBOX_EQUIRECTANGULAR_SHADER_FRAG.data());
150   }
151
152   Dali::TextureSet skyboxTextures = TextureSet::New();
153   skyboxTextures.SetTexture(0, skyboxTexture);
154
155   skyboxRenderer = Renderer::New(skyboxGeometry, shaderSkybox);
156   skyboxRenderer.SetTextures(skyboxTextures);
157   skyboxRenderer.SetProperty(Renderer::Property::DEPTH_INDEX, 2.0f);
158   // Enables the depth test.
159   skyboxRenderer.SetProperty(Renderer::Property::DEPTH_TEST_MODE, DepthTestMode::ON);
160   // The fragment shader will run only is those pixels that have the max depth value.
161   skyboxRenderer.SetProperty(Renderer::Property::DEPTH_FUNCTION, DepthFunction::LESS_EQUAL);
162
163   Dali::Actor skyboxActor = Actor::New();
164   skyboxActor.SetProperty(Dali::Actor::Property::NAME, "SkyBox");
165   skyboxActor.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
166   skyboxActor.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
167   skyboxActor.AddRenderer(skyboxRenderer);
168   return skyboxActor;
169 }
170
171 } // anonymous namespace
172
173 SceneView::SceneView()
174 : Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT)),
175   mWindowOrientation(DEFAULT_ORIENTATION),
176   mSkybox(),
177   mSkyboxOrientation(Quaternion()),
178   mSkyboxIntensity(1.0f)
179 {
180 }
181
182 SceneView::~SceneView() = default;
183
184 Dali::Scene3D::SceneView SceneView::New()
185 {
186   SceneView* impl = new SceneView();
187
188   Dali::Scene3D::SceneView handle = Dali::Scene3D::SceneView(*impl);
189
190   // Second-phase init of the implementation
191   // This can only be done after the CustomActor connection has been made...
192   impl->Initialize();
193
194   return handle;
195 }
196
197 void SceneView::AddCamera(CameraActor camera)
198 {
199   if(camera)
200   {
201     if(mCameras.empty())
202     {
203       UpdateCamera(camera);
204     }
205     mCameras.push_back(camera);
206   }
207 }
208
209 void SceneView::RemoveCamera(CameraActor camera)
210 {
211   if(camera == mDefaultCamera)
212   {
213     DALI_LOG_ERROR("Default Camera cannot removed.\n");
214     return;
215   }
216
217   if(camera)
218   {
219     for(uint32_t i = 0; i < mCameras.size(); ++i)
220     {
221       if(mCameras[i] == camera)
222       {
223         mCameras.erase(mCameras.begin() + i);
224         break;
225       }
226     }
227
228     if(mSelectedCamera == camera)
229     {
230       CameraActor newCurrentCamera = *mCameras.begin();
231       UpdateCamera(newCurrentCamera);
232     }
233   }
234 }
235
236 uint32_t SceneView::GetCameraCount() const
237 {
238   return mCameras.size();
239 }
240
241 CameraActor SceneView::GetSelectedCamera() const
242 {
243   return mSelectedCamera;
244 }
245
246 CameraActor SceneView::GetCamera(uint32_t index) const
247 {
248   if(index < mCameras.size())
249   {
250     return mCameras[index];
251   }
252   DALI_LOG_ERROR("Input index is out of bounds\n");
253   return CameraActor();
254 }
255
256 CameraActor SceneView::GetCamera(const std::string& name) const
257 {
258   CameraActor returnCamera;
259   for(auto&& camera : mCameras)
260   {
261     if(camera.GetProperty<std::string>(Actor::Property::NAME) == name)
262     {
263       returnCamera = camera;
264       break;
265     }
266   }
267   return returnCamera;
268 }
269
270 void SceneView::SelectCamera(uint32_t index)
271 {
272   UpdateCamera(GetCamera(index));
273 }
274
275 void SceneView::SelectCamera(const std::string& name)
276 {
277   UpdateCamera(GetCamera(name));
278 }
279
280 void SceneView::RegisterSceneItem(Scene3D::Internal::ImageBasedLightObserver* item)
281 {
282   if(item)
283   {
284     item->NotifyImageBasedLightTexture(mDiffuseTexture, mSpecularTexture, mIblScaleFactor);
285     mItems.push_back(item);
286   }
287 }
288
289 void SceneView::UnregisterSceneItem(Scene3D::Internal::ImageBasedLightObserver* item)
290 {
291   if(item)
292   {
293     for(uint32_t i = 0; i < mItems.size(); ++i)
294     {
295       if(mItems[i] == item)
296       {
297         mItems.erase(mItems.begin() + i);
298         break;
299       }
300     }
301   }
302 }
303
304 void SceneView::SetImageBasedLightSource(const std::string& diffuseUrl, const std::string& specularUrl, float scaleFactor)
305 {
306   mIBLResourceReady = false;
307
308   // If url is empty or invalid, reset IBL.
309   mDiffuseTexture  = (!diffuseUrl.empty()) ? Dali::Scene3D::Loader::LoadCubeMap(diffuseUrl) : Texture();
310   mSpecularTexture = (!specularUrl.empty()) ? Dali::Scene3D::Loader::LoadCubeMap(specularUrl) : Texture();
311
312   mIblScaleFactor = scaleFactor;
313
314   for(auto&& item : mItems)
315   {
316     if(item)
317     {
318       item->NotifyImageBasedLightTexture(mDiffuseTexture, mSpecularTexture, mIblScaleFactor);
319     }
320   }
321
322   mIBLResourceReady = true;
323   if(IsResourceReady())
324   {
325     Control::SetResourceReady(false);
326   }
327 }
328
329 void SceneView::SetImageBasedLightScaleFactor(float scaleFactor)
330 {
331   mIblScaleFactor = scaleFactor;
332   for(auto&& item : mItems)
333   {
334     if(item)
335     {
336       item->NotifyImageBasedLightScaleFactor(scaleFactor);
337     }
338   }
339 }
340
341 float SceneView::GetImageBasedLightScaleFactor() const
342 {
343   return mIblScaleFactor;
344 }
345
346 void SceneView::UseFramebuffer(bool useFramebuffer)
347 {
348   if(mUseFrameBuffer != useFramebuffer)
349   {
350     mUseFrameBuffer = useFramebuffer;
351     UpdateRenderTask();
352   }
353 }
354
355 bool SceneView::IsUsingFramebuffer() const
356 {
357   return mUseFrameBuffer;
358 }
359
360 void SceneView::SetSkybox(const std::string& skyboxUrl, Scene3D::SceneView::SkyboxType skyboxType)
361 {
362   mSkyboxResourceReady = false;
363   if(mSkybox)
364   {
365     mSkybox.Unparent();
366     mSkybox.Reset();
367   }
368   mSkybox = CreateSkybox(skyboxUrl, skyboxType);
369   SetSkyboxIntensity(mSkyboxIntensity);
370   SetSkyboxOrientation(mSkyboxOrientation);
371   if(mRootLayer)
372   {
373     mRootLayer.Add(mSkybox);
374   }
375
376   mSkyboxResourceReady = true;
377   if(IsResourceReady())
378   {
379     Control::SetResourceReady(false);
380   }
381 }
382
383 void SceneView::SetSkyboxIntensity(float intensity)
384 {
385   mSkyboxIntensity = intensity;
386   if(intensity < 0)
387   {
388     DALI_LOG_ERROR("Intensity should be greater than or equal to 0.\n");
389     mSkyboxIntensity = 0.0f;
390   }
391
392   if(mSkybox)
393   {
394     mSkybox.RegisterProperty(SKYBOX_INTENSITY_STRING.data(), mSkyboxIntensity);
395   }
396 }
397
398 float SceneView::GetSkyboxIntensity() const
399 {
400   return mSkyboxIntensity;
401 }
402
403 void SceneView::SetSkyboxOrientation(const Quaternion& orientation)
404 {
405   mSkyboxOrientation = orientation;
406   if(mSkybox)
407   {
408     mSkybox.SetProperty(Dali::Actor::Property::ORIENTATION, orientation);
409   }
410 }
411
412 Quaternion SceneView::GetSkyboxOrientation() const
413 {
414   return mSkyboxOrientation;
415 }
416
417 ///////////////////////////////////////////////////////////
418 //
419 // Private methods
420 //
421
422 void SceneView::OnSceneConnection(int depth)
423 {
424   Window window = DevelWindow::Get(Self());
425   if(window)
426   {
427     window.ResizeSignal().Connect(this, &SceneView::OnWindowResized);
428     RenderTaskList taskList = window.GetRenderTaskList();
429     mRenderTask             = taskList.CreateTask();
430     mRenderTask.SetSourceActor(mRootLayer);
431     mRenderTask.SetExclusive(true);
432     mRenderTask.SetInputEnabled(true);
433     mRenderTask.SetCullMode(false);
434     mRenderTask.SetScreenToFrameBufferMappingActor(Self());
435
436     UpdateRenderTask();
437     mWindow = window;
438   }
439
440   Control::OnSceneConnection(depth);
441 }
442
443 void SceneView::OnSceneDisconnection()
444 {
445   mItems.clear();
446
447   Window window = mWindow.GetHandle();
448   if(window)
449   {
450     window.ResizeSignal().Disconnect(this, &SceneView::OnWindowResized);
451     RenderTaskList taskList = window.GetRenderTaskList();
452     if(mRenderTask)
453     {
454       taskList.RemoveTask(mRenderTask);
455       mFrameBuffer.Reset();
456     }
457   }
458   mWindow.Reset();
459
460   Control::OnSceneDisconnection();
461 }
462
463 void SceneView::OnInitialize()
464 {
465   Actor self = Self();
466   mRootLayer = Layer::New();
467   mRootLayer.SetProperty(Layer::Property::BEHAVIOR, Layer::LAYER_3D);
468   mRootLayer.SetProperty(Layer::Property::DEPTH_TEST, true);
469   // The models in the SceneView should be have independent coordinate with DALi default coordinate.
470   mRootLayer.SetProperty(Dali::Actor::Property::INHERIT_POSITION, false);
471   mRootLayer.SetProperty(Dali::Actor::Property::INHERIT_ORIENTATION, false);
472   mRootLayer.SetProperty(Dali::Actor::Property::INHERIT_SCALE, false);
473   self.Add(mRootLayer);
474
475   mDefaultCamera = Dali::CameraActor::New();
476   mDefaultCamera.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
477   mDefaultCamera.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
478   mDefaultCamera.SetNearClippingPlane(1.0f);
479   AddCamera(mDefaultCamera);
480   UpdateCamera(mDefaultCamera);
481 }
482
483 void SceneView::OnChildAdd(Actor& child)
484 {
485   if(child != mRootLayer)
486   {
487     mRootLayer.Add(child);
488   }
489   Control::OnChildAdd(child);
490 }
491
492 void SceneView::OnChildRemove(Actor& child)
493 {
494   mRootLayer.Remove(child);
495   Control::OnChildRemove(child);
496 }
497
498 float SceneView::GetHeightForWidth(float width)
499 {
500   Extents padding;
501   padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
502   return Control::GetHeightForWidth(width) + padding.top + padding.bottom;
503 }
504
505 float SceneView::GetWidthForHeight(float height)
506 {
507   Extents padding;
508   padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
509   return Control::GetWidthForHeight(height) + padding.start + padding.end;
510 }
511
512 void SceneView::OnRelayout(const Vector2& size, RelayoutContainer& container)
513 {
514   Control::OnRelayout(size, container);
515   // Change canvas size of camera actor.
516   UpdateRenderTask();
517 }
518
519 bool SceneView::IsResourceReady() const
520 {
521   return mIBLResourceReady & mSkyboxResourceReady;
522 }
523
524 void SceneView::UpdateCamera(CameraActor camera)
525 {
526   if(camera)
527   {
528     if(mSelectedCamera && mSelectedCamera.GetParent())
529     {
530       mSelectedCamera.Unparent();
531     }
532     mRootLayer.Add(camera);
533   }
534
535   mSelectedCamera = camera;
536   UpdateRenderTask();
537 }
538
539 void SceneView::UpdateRenderTask()
540 {
541   if(mRenderTask)
542   {
543     if(mSelectedCamera != mRenderTask.GetCameraActor())
544     {
545       mRenderTask.SetCameraActor(mSelectedCamera);
546     }
547
548     Vector3     size        = Self().GetProperty<Vector3>(Dali::Actor::Property::SIZE);
549     const float aspectRatio = size.width / size.height;
550     mSelectedCamera.SetAspectRatio(aspectRatio);
551
552     if(mUseFrameBuffer)
553     {
554       Dali::FrameBuffer currentFrameBuffer = mRenderTask.GetFrameBuffer();
555       if(!currentFrameBuffer ||
556          !Dali::Equals(currentFrameBuffer.GetColorTexture().GetWidth(), size.width) ||
557          !Dali::Equals(currentFrameBuffer.GetColorTexture().GetHeight(), size.height))
558       {
559         mRootLayer.SetProperty(Dali::Actor::Property::COLOR_MODE, ColorMode::USE_OWN_COLOR);
560         mRenderTask.ResetViewportGuideActor();
561         mRenderTask.SetViewport(Dali::Viewport(Vector4::ZERO));
562
563         // create offscreen buffer of new size to render our child actors to
564         mTexture     = Dali::Texture::New(TextureType::TEXTURE_2D, Pixel::RGBA8888, unsigned(size.width), unsigned(size.height));
565         mFrameBuffer = FrameBuffer::New(size.width, size.height, FrameBuffer::Attachment::DEPTH_STENCIL);
566         mFrameBuffer.AttachColorTexture(mTexture);
567         DevelFrameBuffer::SetMultiSamplingLevel(mFrameBuffer, DEFAULT_FRAME_BUFFER_MULTI_SAMPLING_LEVEL);
568         Dali::Toolkit::ImageUrl imageUrl = Dali::Toolkit::Image::GenerateUrl(mFrameBuffer, 0u);
569
570         Property::Map imagePropertyMap;
571         imagePropertyMap.Insert(Toolkit::Visual::Property::TYPE, Toolkit::Visual::IMAGE);
572         imagePropertyMap.Insert(Toolkit::ImageVisual::Property::URL, imageUrl.GetUrl());
573         // To make sure this visual call LoadTexture API immediate.
574         imagePropertyMap.Insert(Toolkit::ImageVisual::Property::LOAD_POLICY, Toolkit::ImageVisual::LoadPolicy::IMMEDIATE);
575         imagePropertyMap.Insert(Toolkit::ImageVisual::Property::RELEASE_POLICY, Toolkit::ImageVisual::ReleasePolicy::DESTROYED);
576         // To flip rendered scene without CameraActor::SetInvertYAxis() to avoid backface culling.
577         imagePropertyMap.Insert(Toolkit::ImageVisual::Property::PIXEL_AREA, Vector4(0.0f, 1.0f, 1.0f, -1.0f));
578         mVisual = Toolkit::VisualFactory::Get().CreateVisual(imagePropertyMap);
579
580         // Use premultiplied alpha when we use FBO
581         if(mVisual)
582         {
583           Toolkit::GetImplementation(mVisual).EnablePreMultipliedAlpha(true);
584         }
585
586         Toolkit::DevelControl::RegisterVisual(*this, RENDERING_BUFFER, mVisual);
587
588         mRenderTask.SetFrameBuffer(mFrameBuffer);
589         mRenderTask.SetClearEnabled(true);
590         mRenderTask.SetClearColor(Color::TRANSPARENT);
591       }
592     }
593     else
594     {
595       mRenderTask.SetViewportGuideActor(Self());
596       if(mRenderTask.GetFrameBuffer())
597       {
598         mRootLayer.SetProperty(Dali::Actor::Property::COLOR_MODE, ColorMode::USE_OWN_MULTIPLY_PARENT_ALPHA);
599         FrameBuffer framebuffer;
600         mRenderTask.SetFrameBuffer(framebuffer);
601         mRenderTask.SetClearEnabled(false);
602
603         Toolkit::DevelControl::UnregisterVisual(*this, RENDERING_BUFFER);
604
605         mVisual.Reset();
606         mFrameBuffer.Reset();
607         mTexture.Reset();
608       }
609     }
610
611     RotateCamera();
612   }
613 }
614
615 void SceneView::OnWindowResized(Window window, Window::WindowSize size)
616 {
617   mWindowOrientation = DevelWindow::GetPhysicalOrientation(window);
618   RotateCamera();
619 }
620
621 void SceneView::RotateCamera()
622 {
623   if(mUseFrameBuffer)
624   {
625     DevelCameraActor::RotateProjection(mSelectedCamera, DEFAULT_ORIENTATION);
626   }
627   else
628   {
629     DevelCameraActor::RotateProjection(mSelectedCamera, mWindowOrientation);
630   }
631 }
632
633 } // namespace Internal
634 } // namespace Scene3D
635 } // namespace Dali