/*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
*/
-// EXTERNAL
-#include "dali/devel-api/common/map-wrapper.h"
-#include "dali/public-api/animation/constraints.h"
+// CLASS HEADER
+#include <dali-scene3d/public-api/loader/scene-definition.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/common/map-wrapper.h>
+#include <dali/public-api/animation/constraints.h>
// INTERNAL
-#include "dali-scene3d/internal/graphics/builtin-shader-extern-gen.h"
-#include "dali-scene3d/public-api/loader/blend-shape-details.h"
-#include "dali-scene3d/public-api/loader/scene-definition.h"
-#include "dali-scene3d/public-api/loader/skinning-details.h"
-#include "dali-scene3d/public-api/loader/utils.h"
+#include <dali-scene3d/internal/graphics/builtin-shader-extern-gen.h>
+#include <dali-scene3d/internal/model-components/model-node-impl.h>
+#include <dali-scene3d/public-api/loader/blend-shape-details.h>
+#include <dali-scene3d/public-api/loader/skinning-details.h>
+#include <dali-scene3d/public-api/loader/utils.h>
-//#define DEBUG_SCENE_DEFINITION
-//#define DEBUG_JOINTS
+// #define DEBUG_SCENE_DEFINITION
+// #define DEBUG_JOINTS
#if defined(DEBUG_SCENE_DEFINITION) || defined(DEBUG_JOINTS)
#define DEBUG_ONLY(x) x
#define LOGD(x) DEBUG_ONLY(printf x; printf("\n"); fflush(stdout))
-namespace Dali
-{
-namespace Scene3D
-{
-namespace Loader
+namespace Dali::Scene3D::Loader
{
namespace
{
-const std::string JOINT_MATRIX{"jointMatrix"};
-
-const std::map<Property::Type, Constraint (*)(Actor&, Property::Index)> sConstraintFactory = {
- {Property::Type::BOOLEAN,
- [](Actor& a, Property::Index i) {
- return Constraint::New<bool>(a, i, [](bool& current, const PropertyInputContainer& inputs) {
- current = inputs[0]->GetBoolean();
- });
- }},
- {Property::Type::INTEGER,
- [](Actor& a, Property::Index i) {
- return Constraint::New<int>(a, i, [](int& current, const PropertyInputContainer& inputs) {
- current = inputs[0]->GetInteger();
- });
- }},
- {Property::Type::FLOAT,
- [](Actor& a, Property::Index i) {
- return Constraint::New<float>(a, i, EqualToConstraint());
- }},
- {Property::Type::VECTOR2,
- [](Actor& a, Property::Index i) {
- return Constraint::New<Vector2>(a, i, EqualToConstraint());
- }},
- {Property::Type::VECTOR3,
- [](Actor& a, Property::Index i) {
- return Constraint::New<Vector3>(a, i, EqualToConstraint());
- }},
- {Property::Type::VECTOR4,
- [](Actor& a, Property::Index i) {
- return Constraint::New<Vector4>(a, i, EqualToConstraint());
- }},
- {Property::Type::MATRIX,
- [](Actor& a, Property::Index i) {
- return Constraint::New<Matrix>(a, i, EqualToConstraint());
- }},
- {Property::Type::MATRIX3,
- [](Actor& a, Property::Index i) {
- return Constraint::New<Matrix3>(a, i, EqualToConstraint());
- }},
- {Property::Type::ROTATION,
- [](Actor& a, Property::Index i) {
- return Constraint::New<Quaternion>(a, i, EqualToConstraint());
- }},
-};
+const std::map<Property::Type, Constraint (*)(Actor&, Property::Index)>& GetConstraintFactory()
+{
+ static const std::map<Property::Type, Constraint (*)(Actor&, Property::Index)> sConstraintFactory = {
+ {Property::Type::BOOLEAN,
+ [](Actor& a, Property::Index i) {
+ return Constraint::New<bool>(a, i, [](bool& current, const PropertyInputContainer& inputs) { current = inputs[0]->GetBoolean(); });
+ }},
+ {Property::Type::INTEGER,
+ [](Actor& a, Property::Index i) {
+ return Constraint::New<int>(a, i, [](int& current, const PropertyInputContainer& inputs) { current = inputs[0]->GetInteger(); });
+ }},
+ {Property::Type::FLOAT,
+ [](Actor& a, Property::Index i) {
+ return Constraint::New<float>(a, i, EqualToConstraint());
+ }},
+ {Property::Type::VECTOR2,
+ [](Actor& a, Property::Index i) {
+ return Constraint::New<Vector2>(a, i, EqualToConstraint());
+ }},
+ {Property::Type::VECTOR3,
+ [](Actor& a, Property::Index i) {
+ return Constraint::New<Vector3>(a, i, EqualToConstraint());
+ }},
+ {Property::Type::VECTOR4,
+ [](Actor& a, Property::Index i) {
+ return Constraint::New<Vector4>(a, i, EqualToConstraint());
+ }},
+ {Property::Type::MATRIX,
+ [](Actor& a, Property::Index i) {
+ return Constraint::New<Matrix>(a, i, EqualToConstraint());
+ }},
+ {Property::Type::MATRIX3,
+ [](Actor& a, Property::Index i) {
+ return Constraint::New<Matrix3>(a, i, EqualToConstraint());
+ }},
+ {Property::Type::ROTATION,
+ [](Actor& a, Property::Index i) {
+ return Constraint::New<Quaternion>(a, i, EqualToConstraint());
+ }},
+ };
+ return sConstraintFactory;
+}
struct ResourceReflector : IResourceReflector
{
}
#endif //DEBUG_JOINTS
-class ActorCreatorVisitor : public NodeDefinition::IConstVisitor
+class ActorCreatorVisitor : public NodeDefinition::IVisitor
{
public:
ActorCreatorVisitor(NodeDefinition::CreateParams& params)
{
}
- void Start(const NodeDefinition& n)
+ void Start(NodeDefinition& n)
{
mCreationContext.mXforms.modelStack.Push(n.GetLocalSpace());
- Actor a = n.CreateActor(mCreationContext);
+ ModelNode a = n.CreateModelNode(mCreationContext);
if(!mActorStack.empty())
{
mActorStack.back().Add(a);
mActorStack.push_back(a);
}
- void Finish(const NodeDefinition& n)
+ void Finish(NodeDefinition& n)
{
mActorStack.pop_back();
mCreationContext.mXforms.modelStack.Pop();
}
- Actor GetRoot() const
+ ModelNode GetRoot() const
{
return mRoot;
}
private:
NodeDefinition::CreateParams& mCreationContext;
- std::vector<Actor> mActorStack;
- Actor mRoot;
+ std::vector<ModelNode> mActorStack;
+ ModelNode mRoot;
};
-bool IsAncestor(const SceneDefinition& scene, Index ancestor, Index node, Index rootHint = INVALID_INDEX)
-{
- bool isAncestor = false;
- while(node != rootHint && !isAncestor)
- {
- node = scene.GetNode(node)->mParentIdx;
- isAncestor = ancestor == node;
- }
- return isAncestor;
-}
-
-void InsertUniqueSorted(std::vector<Index>& data, Index value)
-{
- auto iInsert = std::lower_bound(data.begin(), data.end(), value);
- if(iInsert == data.end() || *iInsert != value)
- {
- data.insert(iInsert, value);
- }
-}
-
-void RemoveFromSorted(std::vector<Index>& data, Index value)
+template<typename RequestType>
+void SortAndDeduplicateRequests(std::vector<RequestType>& requests)
{
- auto iRemove = std::lower_bound(data.begin(), data.end(), value);
- if(iRemove != data.end() && *iRemove == value)
- {
- data.erase(iRemove);
- }
-}
-
-Property::Index ConfigureJointMatrix(Actor actor, Actor ancestor, Property::Index propJointMatrix)
-{
- Actor parent = actor.GetParent();
- if(parent != ancestor)
- {
- propJointMatrix = ConfigureJointMatrix(parent, ancestor, propJointMatrix);
- }
-
- auto myPropJointMatrix = actor.GetPropertyIndex(JOINT_MATRIX);
- if(myPropJointMatrix == Property::INVALID_INDEX)
- {
- myPropJointMatrix = actor.RegisterProperty(JOINT_MATRIX, Matrix{false});
- Constraint constraint = Constraint::New<Matrix>(actor, propJointMatrix, [](Matrix& output, const PropertyInputContainer& inputs) {
- Matrix jointMatrix{false};
- jointMatrix.SetTransformComponents(Vector3::ONE, inputs[0]->GetQuaternion(), inputs[1]->GetVector3());
-
- Matrix::Multiply(output, jointMatrix, inputs[2]->GetMatrix());
- });
- constraint.AddSource(Source{actor, Actor::Property::ORIENTATION});
- constraint.AddSource(Source{actor, Actor::Property::POSITION});
- constraint.AddSource(Source{parent, propJointMatrix});
- constraint.Apply();
- }
-
- return myPropJointMatrix;
-}
-
-void SortAndDeduplicateSkinningRequests(std::vector<SkinningShaderConfigurationRequest>& requests)
-{
- // Sort requests by shaders.
+ // Sort requests by shaders and primitives.
std::sort(requests.begin(), requests.end());
// Remove duplicates.
- auto i = requests.begin();
- auto iEnd = requests.end();
- Shader s = i->mShader;
- Index skeletonIdx = i->mSkeletonIdx;
- ++i;
+ auto iter = requests.begin();
+ auto iterEnd = requests.end();
+
+ Shader shader = iter->mShader;
+ ModelPrimitive modelPrimitive = iter->mPrimitive;
+ ++iter;
do
{
- // Multiple identical shader instances are removed.
- while(i != iEnd && i->mShader == s)
+ // Multiple identical shader and primitive instances are removed.
+ while(iter != iterEnd && iter->mShader == shader && iter->mPrimitive == modelPrimitive)
{
- // Cannot have multiple skeletons input to the same shader.
- // NOTE: DliModel now makes sure this doesn't happen.
- DALI_ASSERT_ALWAYS(i->mSkeletonIdx == skeletonIdx &&
- "Skinning shader must not be shared between different skeletons.");
-
- i->mShader = Shader();
- ++i;
+ // Mark as removed
+ iter->mShader = Shader();
+ ++iter;
}
- if(i == iEnd)
+ if(iter == iterEnd)
{
break;
}
- s = i->mShader;
- skeletonIdx = i->mSkeletonIdx;
- ++i;
+ shader = iter->mShader;
+ modelPrimitive = iter->mPrimitive;
+ ++iter;
} while(true);
- requests.erase(std::remove_if(requests.begin(), requests.end(), [](const SkinningShaderConfigurationRequest& sscr) {
- return !sscr.mShader;
- }),
+ requests.erase(std::remove_if(requests.begin(), requests.end(), [](const RequestType& sscr) { return !sscr.mShader; }),
requests.end());
}
-void ConfigureBoneMatrix(const Matrix& ibm, Actor joint, Shader& shader, Index& boneIdx)
+void ConfigureBoneMatrix(const Matrix& ibm, ModelNode joint, ModelPrimitive primitive, Index& boneIdx)
{
// Register bone transform on shader.
- char propertyNameBuffer[32];
- snprintf(propertyNameBuffer, sizeof(propertyNameBuffer), "%s[%d]", Skinning::BONE_UNIFORM_NAME.c_str(), boneIdx);
- DALI_ASSERT_DEBUG(shader.GetPropertyIndex(propertyNameBuffer) == Property::INVALID_INDEX);
- auto propBoneXform = shader.RegisterProperty(propertyNameBuffer, Matrix{false});
-
- // Constrain bone matrix to joint transform.
- Constraint constraint = Constraint::New<Matrix>(shader, propBoneXform, [ibm](Matrix& output, const PropertyInputContainer& inputs) {
- Matrix::Multiply(output, ibm, inputs[0]->GetMatrix());
- });
-
- auto propJointMatrix = joint.GetPropertyIndex(JOINT_MATRIX);
- constraint.AddSource(Source{joint, propJointMatrix});
- constraint.Apply();
-
+ Internal::GetImplementation(joint).SetBoneMatrix(ibm, primitive, boneIdx);
++boneIdx;
}
NodeDefinition* SceneDefinition::GetNode(Index iNode)
{
- return mNodes[iNode].get();
+ if(iNode != Scene3D::Loader::INVALID_INDEX && iNode < mNodes.size())
+ {
+ return mNodes[iNode].get();
+ }
+ return nullptr;
}
void SceneDefinition::Visit(Index iNode, const Customization::Choices& choices, NodeDefinition::IVisitor& v)
Visit(iNode, choices, refCounterVisitor);
}
-Actor SceneDefinition::CreateNodes(Index iNode, const Customization::Choices& choices, NodeDefinition::CreateParams& params) const
+ModelNode SceneDefinition::CreateNodes(Index iNode, const Customization::Choices& choices, NodeDefinition::CreateParams& params)
{
ActorCreatorVisitor actorCreatorVisitor(params);
NodeDefinition* SceneDefinition::AddNode(std::unique_ptr<NodeDefinition>&& nodeDef)
{
- if(!nodeDef->mName.empty() && FindNode(nodeDef->mName))
- {
- return nullptr;
- }
-
// add next index (to which we're about to push) as a child to the designated parent, if any.
if(nodeDef->mParentIdx != INVALID_INDEX)
{
{
auto iBegin = mNodes.begin();
auto iEnd = mNodes.end();
- auto iFind = std::find_if(iBegin, iEnd, [&name](const std::unique_ptr<NodeDefinition>& nd) {
- return nd->mName == name;
- });
+ auto iFind = std::find_if(iBegin, iEnd, [&name](const std::unique_ptr<NodeDefinition>& nd) { return nd->mName == name; });
auto result = iFind != iEnd ? iFind->get() : nullptr;
if(result && outIndex)
{
auto iBegin = mNodes.begin();
auto iEnd = mNodes.end();
- auto iFind = std::find_if(iBegin, iEnd, [&name](const std::unique_ptr<NodeDefinition>& nd) {
- return nd->mName == name;
- });
+ auto iFind = std::find_if(iBegin, iEnd, [&name](const std::unique_ptr<NodeDefinition>& nd) { return nd->mName == name; });
auto result = iFind != iEnd ? iFind->get() : nullptr;
if(result && outIndex)
{
auto iBegin = mNodes.begin();
auto iEnd = mNodes.end();
- auto iFind = std::find_if(iBegin, iEnd, [&node](const std::unique_ptr<NodeDefinition>& n) {
- return n.get() == &node;
- });
+ auto iFind = std::find_if(iBegin, iEnd, [&node](const std::unique_ptr<NodeDefinition>& n) { return n.get() == &node; });
return iFind != iEnd ? std::distance(iBegin, iFind) : INVALID_INDEX;
}
if(iTarget != Property::INVALID_INDEX)
{
auto propertyType = cr.mTarget.GetPropertyType(iTarget);
- auto iFind = sConstraintFactory.find(propertyType);
- if(iFind == sConstraintFactory.end())
+ auto iFind = GetConstraintFactory().find(propertyType);
+ if(iFind == GetConstraintFactory().end())
{
onError(FormatString("node '%s': Property '%s' has unsupported type '%s'; ignored.",
sourceName,
}
}
-void SceneDefinition::ConfigureSkeletonJoints(uint32_t iRoot, const SkeletonDefinition::Vector& skeletons, Actor root) const
-{
- // 1, For each skeleton, for each joint, walk upwards until we reach mNodes[iRoot]. If we do, record +1
- // to the refcount of each node we have visited, in our temporary registry. Those with refcount 1
- // are the leaves, while the most descendant node with the highest refcount is the root of the skeleton.
- std::map<Index, std::vector<Index>> rootsJoints;
- std::vector<Index> path;
- path.reserve(16);
- for(auto& s : skeletons)
- {
- std::map<uint32_t, uint32_t> jointRefs;
- for(auto& j : s.mJoints)
- {
- auto nodeIdx = j.mNodeIdx;
- do // Traverse upwards and record each node we have visited until we reach the scene root.
- {
- path.push_back(nodeIdx);
- if(nodeIdx == iRoot)
- {
- break;
- }
- auto node = GetNode(nodeIdx);
- nodeIdx = node->mParentIdx;
- } while(nodeIdx != INVALID_INDEX);
-
- if(nodeIdx == iRoot) // If the joint is in the correct scene, increment the reference count for all visited nodes.
- {
- for(auto i : path)
- {
- ++jointRefs[i];
- }
- }
-
- path.clear();
- }
-
- // Only record the skeleton if we have encountered the root of the current scene.
- if(jointRefs.empty())
- {
- continue;
- }
-
- Index root = s.mRootNodeIdx;
- uint32_t maxRef = 0;
- auto iFind = jointRefs.find(root);
- if(iFind != jointRefs.end())
- {
- maxRef = iFind->second;
- }
-
- std::vector<Index> joints;
- for(auto& j : jointRefs) // NOTE: jointRefs are sorted, so joints will also be.
- {
- // The most descendant node with the highest ref count is the root of the skeleton.
- if(j.second > maxRef || (j.second == maxRef && IsAncestor(*this, root, j.first, iRoot)))
- {
- maxRef = j.second;
-
- RemoveFromSorted(joints, root);
- root = j.first;
- }
- else if(j.second == 1) // This one's a leaf.
- {
- InsertUniqueSorted(joints, j.first);
- }
- }
-
- // Merge skeletons that share the same root.
- auto& finalJoints = rootsJoints[root];
- for(auto j : joints)
- {
- if(std::find_if(finalJoints.begin(), finalJoints.end(), [this, j, root](Index jj) {
- return IsAncestor(*this, j, jj, root);
- }) != finalJoints.end())
- {
- continue; // if the joint is found to be an ancestor of another joint already registered, move on.
- }
-
- auto i = j;
- while(i != root) // See if the current joint is a better leaf, i.e. descended from another leaf - which we'll then remove.
- {
- auto node = GetNode(i);
- i = node->mParentIdx;
-
- RemoveFromSorted(finalJoints, i);
- }
-
- InsertUniqueSorted(finalJoints, j);
- }
- }
-
- // 2, Merge records where one root joint is descendant of another. Handle leaf node changes - remove previous
- // leaf nodes that now have descendants, and add new ones.
- auto iRoots = rootsJoints.begin();
- auto iRootsEnd = rootsJoints.end();
- while(iRoots != iRootsEnd)
- {
- auto i = iRoots->first;
- bool merged = false;
- while(i != iRoot) // Starting with the root joint of the skeleton, traverse upwards.
- {
- auto node = GetNode(i);
- i = node->mParentIdx;
-
- auto iFind = rootsJoints.find(i);
- if(iFind != rootsJoints.end()) // Check if we've reached the root of another skeleton.
- {
- // Now find out which leaf of iFind is an ancestor, if any.
- auto iFindLeaf = std::find_if(iFind->second.begin(), iFind->second.end(), [this, iRoots, iFind](Index j) {
- return IsAncestor(*this, j, iRoots->first, iFind->first);
- });
- if(iFindLeaf != iFind->second.end())
- {
- iFind->second.erase(iFindLeaf); // Will no longer be a leaf -- remove it.
- }
-
- // Merge iRoots with iFind
- auto& targetJoints = iFind->second;
- if(iRoots->second.empty()) // The root is a leaf.
- {
- InsertUniqueSorted(targetJoints, iRoots->first);
- }
- else
- for(auto j : iRoots->second)
- {
- InsertUniqueSorted(targetJoints, j);
- }
-
- merged = true;
- break; // Traverse no more
- }
- }
-
- iRoots = merged ? rootsJoints.erase(iRoots) : std::next(iRoots);
- }
-
- // 3, For each root, register joint matrices and constraints
- for(auto r : rootsJoints)
- {
- auto node = GetNode(r.first);
- auto rootJoint = root.FindChildByName(node->mName);
- DALI_ASSERT_ALWAYS(!!rootJoint);
-
- DALI_ASSERT_DEBUG(rootJoint.GetPropertyIndex(JOINT_MATRIX) == Property::INVALID_INDEX);
- auto propJointMatrix = rootJoint.RegisterProperty(JOINT_MATRIX, Matrix{false});
- Constraint constraint = Constraint::New<Matrix>(rootJoint, propJointMatrix, [](Matrix& output, const PropertyInputContainer& inputs) {
- output.SetTransformComponents(Vector3::ONE, inputs[0]->GetQuaternion(), inputs[1]->GetVector3());
- });
- constraint.AddSource(Source(rootJoint, Actor::Property::ORIENTATION));
- constraint.AddSource(Source(rootJoint, Actor::Property::POSITION));
- constraint.Apply();
-
- for(auto j : r.second)
- {
- node = GetNode(j);
- auto joint = rootJoint.FindChildByName(node->mName);
- ConfigureJointMatrix(joint, rootJoint, propJointMatrix);
- }
- }
-}
-
void SceneDefinition::EnsureUniqueSkinningShaderInstances(ResourceBundle& resources) const
{
std::map<Index, std::map<Index, std::vector<Index*>>> skinningShaderUsers;
return;
}
- SortAndDeduplicateSkinningRequests(requests);
+ SortAndDeduplicateRequests(requests);
for(auto& request : requests)
{
Index boneIdx = 0;
for(auto& joint : skeleton.mJoints)
{
- auto node = GetNode(joint.mNodeIdx);
- Actor actor = rootActor.FindChildByName(node->mName);
- ConfigureBoneMatrix(joint.mInverseBindMatrix, actor, request.mShader, boneIdx);
+ auto node = GetNode(joint.mNodeIdx);
+ ModelNode modelNode = ModelNode::DownCast(rootActor.FindChildByName(node->mName));
+ if(!modelNode)
+ {
+ continue;
+ }
+ ConfigureBoneMatrix(joint.mInverseBindMatrix, modelNode, request.mPrimitive, boneIdx);
}
}
}
return true;
}
- // Sort requests by shaders.
- std::sort(requests.begin(), requests.end());
-
- // Remove duplicates.
- auto i = requests.begin();
- auto iEnd = requests.end();
- Shader s = i->mShader;
- ++i;
- do
- {
- // Multiple identical shader instances are removed.
- while(i != iEnd && i->mShader == s)
- {
- i->mShader = Shader();
- ++i;
- }
-
- if(i == iEnd)
- {
- break;
- }
- s = i->mShader;
- ++i;
- } while(true);
+ SortAndDeduplicateRequests(requests);
// Configure the rest.
bool ok = true;
- for(auto& i : requests)
+ for(auto& request : requests)
{
Index iNode;
- if(FindNode(i.mNodeName, &iNode))
+ if(FindNode(request.mNodeName, &iNode))
{
const auto& node = GetNode(iNode);
- const auto& mesh = resources.mMeshes[i.mMeshIdx];
+ const auto& mesh = resources.mMeshes[request.mMeshIdx];
if(mesh.first.HasBlendShapes())
{
- Actor actor = rootActor.FindChildByName(node->mName);
-
- // Sets the property to be animated.
- BlendShapes::ConfigureProperties(mesh, i.mShader, actor);
+ Actor actor = rootActor.FindChildByName(node->mName);
+ Scene3D::ModelNode node = Scene3D::ModelNode::DownCast(actor);
+ if(!node)
+ {
+ continue;
+ }
+ BlendShapes::BlendShapeData data;
+ data.components = 0x0;
+ for(auto&& blendShape : mesh.first.mBlendShapes)
+ {
+ data.weights.push_back(blendShape.weight);
+ data.components |= (blendShape.deltas.IsDefined() * BlendShapes::Component::POSITIONS) |
+ (blendShape.normals.IsDefined() * BlendShapes::Component::NORMALS) | (blendShape.tangents.IsDefined() * BlendShapes::Component::TANGENTS);
+ }
+ for(auto&& factor : mesh.second.blendShapeUnnormalizeFactor)
+ {
+ data.unnormalizeFactors.push_back(factor);
+ }
+ data.version = mesh.first.mBlendShapeVersion;
+ data.bufferOffset = mesh.second.blendShapeBufferOffset;
+ data.mActor = actor;
+ Internal::GetImplementation(node).SetBlendShapeData(data, request.mPrimitive);
}
}
}
// We're searching from the end assuming a higher probability of operations targeting
// recently added nodes. (conf.: root, which is immovable, cannot be removed, and was
// the first to be added, is index 0.)
- auto iFind = std::find_if(mNodes.rbegin(), mNodes.rend(), [&name](const std::unique_ptr<NodeDefinition>& nd) {
- return nd->mName == name;
- }).base();
+ auto iFind = std::find_if(mNodes.rbegin(), mNodes.rend(), [&name](const std::unique_ptr<NodeDefinition>& nd) { return nd->mName == name; })
+ .base();
const bool success = iFind != mNodes.begin();
if(success && result)
return success;
}
-} // namespace Loader
-} // namespace Scene3D
-} // namespace Dali
+} // namespace Dali::Scene3D::Loader