2 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include "model-view-impl.h"
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/integration-api/debug.h>
26 #include <dali/public-api/object/type-registry-helper.h>
27 #include <dali/public-api/object/type-registry.h>
31 #include <dali-scene3d/internal/controls/scene-view/scene-view-impl.h>
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-map-loader.h>
36 #include <dali-scene3d/public-api/loader/dli-loader.h>
37 #include <dali-scene3d/public-api/loader/gltf2-loader.h>
38 #include <dali-scene3d/public-api/loader/light-parameters.h>
39 #include <dali-scene3d/public-api/loader/load-result.h>
40 #include <dali-scene3d/public-api/loader/node-definition.h>
41 #include <dali-scene3d/public-api/loader/scene-definition.h>
42 #include <dali-scene3d/public-api/loader/shader-definition-factory.h>
56 return Scene3D::ModelView::New(std::string());
59 // Setup properties, signals and actions using the type-registry.
60 DALI_TYPE_REGISTRATION_BEGIN(Scene3D::ModelView, Toolkit::Control, Create);
61 DALI_TYPE_REGISTRATION_END()
63 static constexpr uint32_t OFFSET_FOR_DIFFUSE_CUBE_TEXTURE = 2u;
64 static constexpr uint32_t OFFSET_FOR_SPECULAR_CUBE_TEXTURE = 1u;
66 static constexpr Vector3 Y_DIRECTION(1.0f, -1.0f, 1.0f);
68 static constexpr std::string_view KTX_EXTENSION = ".ktx";
69 static constexpr std::string_view OBJ_EXTENSION = ".obj";
70 static constexpr std::string_view GLTF_EXTENSION = ".gltf";
71 static constexpr std::string_view DLI_EXTENSION = ".dli";
77 pointMin = Vector3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
78 pointMax = Vector3(std::numeric_limits<float>::min(), std::numeric_limits<float>::min(), std::numeric_limits<float>::min());
81 void ConsiderNewPointInVolume(const Vector3& position)
83 pointMin.x = std::min(position.x, pointMin.x);
84 pointMin.y = std::min(position.y, pointMin.y);
85 pointMin.z = std::min(position.z, pointMin.z);
87 pointMax.x = std::max(position.x, pointMax.x);
88 pointMax.y = std::max(position.y, pointMax.y);
89 pointMax.z = std::max(position.z, pointMax.z);
92 Vector3 CalculateSize()
94 return pointMax - pointMin;
97 Vector3 CalculatePivot()
99 Vector3 pivot = pointMin / (pointMin - pointMax);
100 for(uint32_t i = 0; i < 3; ++i)
102 // To avoid divid by zero
103 if(pointMin[i] == pointMax[i])
115 void ConfigureBlendShapeShaders(
116 Dali::Scene3D::Loader::ResourceBundle& resources, const Dali::Scene3D::Loader::SceneDefinition& scene, Actor root, std::vector<Dali::Scene3D::Loader::BlendshapeShaderConfigurationRequest>&& requests)
118 std::vector<std::string> errors;
119 auto onError = [&errors](const std::string& msg) { errors.push_back(msg); };
120 if(!scene.ConfigureBlendshapeShaders(resources, root, std::move(requests), onError))
122 Dali::Scene3D::Loader::ExceptionFlinger flinger(ASSERT_LOCATION);
123 for(auto& msg : errors)
125 flinger << msg << '\n';
130 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 static constexpr uint32_t BOX_POINT_COUNT = 8;
133 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}};
136 const Dali::Scene3D::Loader::NodeDefinition* node = scene.GetNode(iNode);
137 Matrix localMatrix = node->GetLocalSpace();
138 Matrix::Multiply(nodeMatrix, localMatrix, parentMatrix);
141 if(node->GetExtents(nodeParams.mResources, volume[0], volume[1]))
143 for(uint32_t i = 0; i < BOX_POINT_COUNT; ++i)
145 Vector4 position = Vector4(volume[BBIndex[i][0]].x, volume[BBIndex[i][1]].y, volume[BBIndex[i][2]].z, 1.0f);
146 Vector4 objectPosition = nodeMatrix * position;
147 objectPosition /= objectPosition.w;
149 AABB.ConsiderNewPointInVolume(Vector3(objectPosition));
153 if(node->mCustomization)
155 if(!node->mChildren.empty())
157 auto choice = choices.Get(node->mCustomization->mTag);
158 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 AddModelTreeToAABB(AABB, scene, choices, node->mChildren[i], nodeParams, nodeMatrix);
165 for(auto i : node->mChildren)
167 AddModelTreeToAABB(AABB, scene, choices, i, nodeParams, nodeMatrix);
172 } // anonymous namespace
174 ModelView::ModelView(const std::string& modelPath, const std::string& resourcePath)
175 : Control(ControlBehaviour(DISABLE_SIZE_NEGOTIATION | DISABLE_STYLE_CHANGE_SIGNALS)),
176 mModelPath(modelPath),
177 mResourcePath(resourcePath),
179 mNaturalSize(Vector3::ZERO),
180 mModelPivot(AnchorPoint::CENTER),
181 mIblScaleFactor(1.0f),
184 mModelResourceReady(false),
185 mIBLResourceReady(true)
189 ModelView::~ModelView()
193 Dali::Scene3D::ModelView ModelView::New(const std::string& modelPath, const std::string& resourcePath)
195 ModelView* impl = new ModelView(modelPath, resourcePath);
197 Dali::Scene3D::ModelView handle = Dali::Scene3D::ModelView(*impl);
199 // Second-phase init of the implementation
200 // This can only be done after the CustomActor connection has been made...
206 const Actor ModelView::GetModelRoot() const
211 void ModelView::FitSize(bool fit)
217 void ModelView::FitCenter(bool fit)
223 void ModelView::SetImageBasedLightSource(const std::string& diffuse, const std::string& specular, float scaleFactor)
225 mIBLResourceReady = false;
226 Texture diffuseTexture = Dali::Scene3D::Loader::LoadCubeMap(diffuse);
227 Texture specularTexture = Dali::Scene3D::Loader::LoadCubeMap(specular);
228 SetImageBasedLightTexture(diffuseTexture, specularTexture, scaleFactor);
229 mIBLResourceReady = true;
231 // If Model resource is already ready, then set resource ready.
232 // If Model resource is still not ready, wait for model resource ready.
233 if(IsResourceReady())
235 SetResourceReady(false);
239 void ModelView::SetImageBasedLightTexture(Dali::Texture diffuse, Dali::Texture specular, float scaleFactor)
241 if(diffuse && specular)
243 if(mDiffuseTexture != diffuse || mSpecularTexture != specular)
245 mDiffuseTexture = diffuse;
246 mSpecularTexture = specular;
247 UpdateImageBasedLightTexture();
249 if(mIblScaleFactor != scaleFactor)
251 mIblScaleFactor = scaleFactor;
252 UpdateImageBasedLightScaleFactor();
258 void ModelView::SetImageBasedLightScaleFactor(float scaleFactor)
260 mIblScaleFactor = scaleFactor;
261 if(mDiffuseTexture && mSpecularTexture)
263 UpdateImageBasedLightScaleFactor();
267 float ModelView::GetImageBasedLightScaleFactor() const
269 return mIblScaleFactor;
272 uint32_t ModelView::GetAnimationCount() const
274 return mAnimations.size();
277 Dali::Animation ModelView::GetAnimation(uint32_t index) const
279 Dali::Animation animation;
280 if(mAnimations.size() > index)
282 animation = mAnimations[index].second;
287 Dali::Animation ModelView::GetAnimation(const std::string& name) const
289 Dali::Animation animation;
292 for(auto&& animationData : mAnimations)
294 if(animationData.first == name)
296 animation = animationData.second;
304 ///////////////////////////////////////////////////////////
309 void ModelView::OnSceneConnection(int depth)
316 Actor parent = Self().GetParent();
319 Scene3D::SceneView sceneView = Scene3D::SceneView::DownCast(parent);
322 GetImpl(sceneView).RegisterModelView(Scene3D::ModelView::DownCast(Self()));
323 mParentSceneView = sceneView;
326 parent = parent.GetParent();
329 Control::OnSceneConnection(depth);
332 void ModelView::OnSceneDisconnection()
334 Scene3D::SceneView sceneView = mParentSceneView.GetHandle();
337 GetImpl(sceneView).UnregisterModelView(Scene3D::ModelView::DownCast(Self()));
338 mParentSceneView.Reset();
340 Control::OnSceneDisconnection();
343 Vector3 ModelView::GetNaturalSize()
353 float ModelView::GetHeightForWidth(float width)
356 padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
357 return Control::GetHeightForWidth(width) + padding.top + padding.bottom;
360 float ModelView::GetWidthForHeight(float height)
363 padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
364 return Control::GetWidthForHeight(height) + padding.start + padding.end;
367 void ModelView::OnRelayout(const Vector2& size, RelayoutContainer& container)
369 Control::OnRelayout(size, container);
373 bool ModelView::IsResourceReady() const
375 return mModelResourceReady && mIBLResourceReady;
378 void ModelView::LoadModel()
380 std::filesystem::path modelPath(mModelPath);
381 if(mResourcePath.empty())
383 mResourcePath = std::string(modelPath.parent_path()) + "/";
385 std::string extension = modelPath.extension();
386 std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
388 Dali::Scene3D::Loader::ResourceBundle::PathProvider pathProvider = [&](Dali::Scene3D::Loader::ResourceType::Value type) {
389 return mResourcePath;
392 Dali::Scene3D::Loader::ResourceBundle resources;
393 Dali::Scene3D::Loader::SceneDefinition scene;
394 std::vector<Dali::Scene3D::Loader::AnimationGroupDefinition> animGroups;
395 std::vector<Dali::Scene3D::Loader::CameraParameters> cameraParameters;
396 std::vector<Dali::Scene3D::Loader::LightParameters> lights;
398 std::vector<Dali::Scene3D::Loader::AnimationDefinition> animations;
401 Dali::Scene3D::Loader::LoadResult output{resources, scene, animations, animGroups, cameraParameters, lights};
403 if(extension == DLI_EXTENSION)
405 Dali::Scene3D::Loader::DliLoader loader;
406 Dali::Scene3D::Loader::DliLoader::InputParams input{
407 pathProvider(Dali::Scene3D::Loader::ResourceType::Mesh),
413 Dali::Scene3D::Loader::DliLoader::LoadParams loadParams{input, output};
414 if(!loader.LoadScene(mModelPath, loadParams))
416 Dali::Scene3D::Loader::ExceptionFlinger(ASSERT_LOCATION) << "Failed to load scene from '" << mModelPath << "': " << loader.GetParseError();
419 else if(extension == GLTF_EXTENSION)
421 Dali::Scene3D::Loader::ShaderDefinitionFactory sdf;
422 sdf.SetResources(resources);
423 Dali::Scene3D::Loader::LoadGltfScene(mModelPath, sdf, output);
425 resources.mEnvironmentMaps.push_back({});
429 DALI_LOG_ERROR("Unsupported model type.\n");
432 Dali::Scene3D::Loader::Transforms xforms{Dali::Scene3D::Loader::MatrixStack{}, Dali::Scene3D::Loader::ViewProjection{}};
433 Dali::Scene3D::Loader::NodeDefinition::CreateParams nodeParams{resources, xforms, {}, {}, {}};
434 Dali::Scene3D::Loader::Customization::Choices choices;
436 mModelRoot = Actor::New();
439 for(auto iRoot : scene.GetRoots())
441 auto resourceRefs = resources.CreateRefCounter();
442 scene.CountResourceRefs(iRoot, choices, resourceRefs);
443 resources.CountEnvironmentReferences(resourceRefs);
445 resources.LoadResources(resourceRefs, pathProvider);
447 // glTF Mesh is defined in right hand coordinate system, with positive Y for Up direction.
448 // Because DALi uses left hand system, Y direciton will be flipped for environment map sampling.
449 for(auto&& env : resources.mEnvironmentMaps)
451 env.first.mYDirection = Y_DIRECTION;
454 if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
456 scene.ConfigureSkeletonJoints(iRoot, resources.mSkeletons, actor);
457 scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
458 ConfigureBlendShapeShaders(resources, scene, actor, std::move(nodeParams.mBlendshapeRequests));
460 scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
462 mModelRoot.Add(actor);
465 AddModelTreeToAABB(AABB, scene, choices, iRoot, nodeParams, Matrix::IDENTITY);
468 if(!animations.empty())
470 auto getActor = [&](const std::string& name) {
471 return mModelRoot.FindChildByName(name);
475 for(auto&& animation : animations)
477 Dali::Animation anim = animation.ReAnimate(getActor);
479 mAnimations.push_back({animation.mName, anim});
483 mRenderableActors.clear();
484 CollectRenderableActor(mModelRoot);
485 UpdateImageBasedLightTexture();
486 UpdateImageBasedLightScaleFactor();
488 mNaturalSize = AABB.CalculateSize();
489 mModelPivot = AABB.CalculatePivot();
490 mModelRoot.SetProperty(Dali::Actor::Property::SIZE, mNaturalSize);
491 Vector3 controlSize = Self().GetProperty<Vector3>(Dali::Actor::Property::SIZE);
492 if(controlSize.x == 0.0f || controlSize.y == 0.0f)
494 Self().SetProperty(Dali::Actor::Property::SIZE, mNaturalSize);
500 Self().Add(mModelRoot);
502 Self().SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
503 Self().SetProperty(Dali::Actor::Property::ANCHOR_POINT, Vector3(mModelPivot.x, 1.0f - mModelPivot.y, mModelPivot.z));
505 mModelResourceReady = true;
507 Control::SetResourceReady(false);
510 void ModelView::ScaleModel()
514 Vector3 size = Self().GetProperty<Vector3>(Dali::Actor::Property::SIZE);
515 if(mFitSize && size.x > 0.0f && size.y > 0.0f)
517 float scaleFactor = MAXFLOAT;
518 scaleFactor = std::min(size.x / mNaturalSize.x, scaleFactor);
519 scaleFactor = std::min(size.y / mNaturalSize.y, scaleFactor);
520 // Models in glTF and dli are defined as right hand coordinate system.
521 // DALi uses left hand coordinate system. Scaling negative is for change winding order.
522 mModelRoot.SetProperty(Dali::Actor::Property::SCALE, Y_DIRECTION * scaleFactor);
526 // Models in glTF and dli are defined as right hand coordinate system.
527 // DALi uses left hand coordinate system. Scaling negative is for change winding order.
528 mModelRoot.SetProperty(Dali::Actor::Property::SCALE, Y_DIRECTION);
533 void ModelView::FitModelPosition()
539 // Loaded model pivot is not the model center.
540 mModelRoot.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
541 mModelRoot.SetProperty(Dali::Actor::Property::ANCHOR_POINT, Vector3::ONE - mModelPivot);
545 mModelRoot.SetProperty(Dali::Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
546 mModelRoot.SetProperty(Dali::Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
551 void ModelView::CollectRenderableActor(Actor actor)
553 uint32_t rendererCount = actor.GetRendererCount();
556 mRenderableActors.push_back(actor);
559 uint32_t childrenCount = actor.GetChildCount();
560 for(uint32_t i = 0; i < childrenCount; ++i)
562 CollectRenderableActor(actor.GetChildAt(i));
566 void ModelView::UpdateImageBasedLightTexture()
568 if(!mDiffuseTexture || !mSpecularTexture)
573 for(auto&& actor : mRenderableActors)
575 Actor renderableActor = actor.GetHandle();
578 uint32_t rendererCount = renderableActor.GetRendererCount();
579 for(uint32_t i = 0; i < rendererCount; ++i)
581 Dali::Renderer renderer = renderableActor.GetRendererAt(i);
584 Dali::TextureSet textures = renderer.GetTextures();
587 uint32_t textureCount = textures.GetTextureCount();
588 // EnvMap requires at least 2 texture, diffuse and specular
589 if(textureCount > 2u)
591 textures.SetTexture(textureCount - OFFSET_FOR_DIFFUSE_CUBE_TEXTURE, mDiffuseTexture);
592 textures.SetTexture(textureCount - OFFSET_FOR_SPECULAR_CUBE_TEXTURE, mSpecularTexture);
601 void ModelView::UpdateImageBasedLightScaleFactor()
603 if(!mDiffuseTexture || !mSpecularTexture)
607 for(auto&& actor : mRenderableActors)
609 Actor renderableActor = actor.GetHandle();
612 renderableActor.RegisterProperty(Dali::Scene3D::Loader::NodeDefinition::GetIblScaleFactorUniformName().data(), mIblScaleFactor);
617 } // namespace Internal
618 } // namespace Scene3D