2 * Copyright (c) 2024 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 <dali-scene3d/internal/loader/dli-loader-impl.h>
22 #include <dali-toolkit/devel-api/builder/json-parser.h>
23 #include <dali/devel-api/common/map-wrapper.h>
24 #include <dali/integration-api/debug.h>
25 #include <dali/public-api/object/property-array.h>
35 #include <dali-scene3d/internal/loader/json-util.h>
36 #include <dali-scene3d/public-api/loader/alpha-function-helper.h>
37 #include <dali-scene3d/public-api/loader/animation-definition.h>
38 #include <dali-scene3d/public-api/loader/blend-shape-details.h>
39 #include <dali-scene3d/public-api/loader/camera-parameters.h>
40 #include <dali-scene3d/public-api/loader/ktx-loader.h>
41 #include <dali-scene3d/public-api/loader/light-parameters.h>
42 #include <dali-scene3d/public-api/loader/load-result.h>
43 #include <dali-scene3d/public-api/loader/parse-renderer-state.h>
44 #include <dali-scene3d/public-api/loader/scene-definition.h>
45 #include <dali-scene3d/public-api/loader/skinning-details.h>
46 #include <dali-scene3d/public-api/loader/utils.h>
48 #define DLI_0_1_COMPATIBILITY
52 using namespace Toolkit;
60 namespace rs = RendererState;
64 const char* NODES = "nodes";
65 const char* SCENES = "scenes";
66 const char* NODE = "node";
67 const char* URI = "uri";
68 const char* URL = "url";
69 const char* HINTS = "hints";
70 const char* NAME("name");
71 const char* BLEND_SHAPE_HEADER("blendShapeHeader");
72 const char* BLEND_SHAPES("blendShapes");
73 const char* BLEND_SHAPE_VERSION_1_0("1.0");
74 const char* BLEND_SHAPE_VERSION_2_0("2.0");
75 const char* VERSION("version");
77 const char* const SHADOW_MAP_SIZE = "shadowMapSize";
78 const char* const ORTHOGRAPHIC_SIZE = "orthographicSize";
79 const char* const PIXEL_UNITS = "px";
81 const char SLASH = '/';
83 void ReadModelTransform(const TreeNode* node, Quaternion& orientation, Vector3& translation, Vector3& scale)
85 float num[16u] = {.0f};
87 if(ReadVector(node->GetChild("matrix"), num, 16u))
90 mat.GetTransformComponents(translation, orientation, scale);
94 if(ReadVector(node->GetChild("angle"), num, 3u))
96 orientation = Quaternion(Radian(Degree(num[0u])), Radian(Degree(num[1u])), Radian(Degree(num[2u])));
99 if(ReadVector(node->GetChild("position"), num, 3u))
101 translation = Vector3(num);
106 bool ReadAttribBlob(const TreeNode* node, MeshDefinition::Blob& buffer)
108 return ReadBlob(node, buffer.mOffset, buffer.mLength);
111 bool ReadAttribAccessor(const TreeNode* node, MeshDefinition::Accessor& accessor)
113 return ReadBlob(node, accessor.mBlob.mOffset, accessor.mBlob.mLength);
116 bool ReadColorCode(const TreeNode* node, Vector4& color, DliInputParameter::ConvertColorCode convertColorCode)
118 if(!node || !convertColorCode)
123 color = convertColorCode(node->GetString());
128 bool ReadColorCodeOrColor(const TreeNode* node, Vector4& color, DliInputParameter::ConvertColorCode convertColorCode)
130 return ReadColorCode(node->GetChild("colorCode"), color, convertColorCode) ||
131 ReadColor(node->GetChild("color"), color);
134 RendererState::Type ReadRendererState(const TreeNode& tnRendererState)
136 if(tnRendererState.GetType() == TreeNode::INTEGER)
138 return static_cast<RendererState::Type>(tnRendererState.GetInteger());
140 else if(tnRendererState.GetType() == TreeNode::STRING)
142 return RendererState::Parse(tnRendererState.GetString());
150 ///@brief Reads arc properties.
151 void ReadArcField(const TreeNode* eArc, ArcRenderable& arc)
153 ReadBool(eArc->GetChild("antiAliasing"), arc.mAntiAliasing);
154 ReadInt(eArc->GetChild("arcCaps"), arc.mArcCaps);
155 ReadFloat(eArc->GetChild("radius"), arc.mRadius);
157 arc.mStartAngleDegrees = .0f;
158 ReadFloat(eArc->GetChild("startAngle"), arc.mStartAngleDegrees);
160 arc.mEndAngleDegrees = .0f;
161 ReadFloat(eArc->GetChild("endAngle"), arc.mEndAngleDegrees);
164 const TreeNode* GetNthChild(const TreeNode* node, uint32_t index)
167 for(TreeNode::ConstIterator it = (*node).CBegin(); it != (*node).CEnd(); ++it, ++i)
171 return &((*it).second);
177 const TreeNode* RequireChild(const TreeNode* node, const std::string& childName)
179 auto child = node->GetChild(childName);
182 ExceptionFlinger flinger(ASSERT_LOCATION);
183 flinger << "Failed to find child node '" << childName << "'";
184 if(auto nodeName = node->GetName())
186 flinger << " on '" << nodeName << "'";
193 void ParseProperties(const Toolkit::TreeNode& node, Property::Array& array);
195 void ParseProperties(const Toolkit::TreeNode& node, Property::Map& map)
197 DALI_ASSERT_DEBUG(node.GetType() == TreeNode::OBJECT);
198 for(auto i0 = node.CBegin(), i1 = node.CEnd(); i0 != i1; ++i0)
201 switch(kv.second.GetType())
203 case TreeNode::ARRAY:
205 Property::Array array;
206 ParseProperties(kv.second, array);
207 map.Insert(kv.first, array);
211 case TreeNode::OBJECT:
213 Property::Map innerMap;
214 ParseProperties(kv.second, innerMap);
215 map.Insert(kv.first, innerMap);
219 case TreeNode::STRING:
221 map.Insert(kv.first, kv.second.GetString());
225 case TreeNode::INTEGER:
227 map.Insert(kv.first, kv.second.GetInteger());
231 case TreeNode::BOOLEAN:
233 map.Insert(kv.first, kv.second.GetBoolean());
237 case TreeNode::FLOAT:
239 map.Insert(kv.first, kv.second.GetFloat());
243 case TreeNode::IS_NULL:
251 void ParseProperties(const Toolkit::TreeNode& node, Property::Array& array)
253 DALI_ASSERT_DEBUG(node.GetType() == TreeNode::ARRAY);
254 for(auto i0 = node.CBegin(), i1 = node.CEnd(); i0 != i1; ++i0)
257 switch(kv.second.GetType())
259 case TreeNode::ARRAY:
261 Property::Array innerArray;
262 ParseProperties(kv.second, innerArray);
263 array.PushBack(innerArray);
267 case TreeNode::OBJECT:
270 ParseProperties(kv.second, map);
275 case TreeNode::STRING:
277 array.PushBack(kv.second.GetString());
281 case TreeNode::INTEGER:
283 array.PushBack(kv.second.GetInteger());
287 case TreeNode::BOOLEAN:
289 array.PushBack(kv.second.GetBoolean());
293 case TreeNode::FLOAT:
295 array.PushBack(kv.second.GetFloat());
299 case TreeNode::IS_NULL:
309 struct DliLoaderImpl::Impl
311 StringCallback mOnError = DefaultErrorCallback;
312 Toolkit::JsonParser mParser;
314 void ParseScene(LoadParams& params);
317 std::map<Index, Matrix> mInverseBindMatrices;
320 * @brief Due to .dli nodes being processed in depth-first traversal with orphans being
321 * ignored, features that rely on node indices (which is more compact and closer to
322 * glTF) require a mapping from .dli node indices to those in the resulting SceneDefinition.
323 * The index mapper is responsible for maintaing this mapping, and resolving node IDs
324 * once the processing of the nodes has finished.
325 * @note The resolution requires the whole scene graph to finish parsing, therefore any
326 * node extensions relying on node IDs will see the dli ID in their processor.
331 * @brief Attempts to create a mapping from a node's @a dli index to its @a scene
333 * @return Whether the operation was successful.
335 virtual bool Map(Index iDli, Index iScene) = 0;
338 * @return The scene index for the node's @a dli index.
340 virtual Index Resolve(Index iDli) = 0;
344 * @brief Traverses the DOM tree created by LoadDocument() in an attempt to create
345 * an intermediate representation of resources and nodes.
347 void ParseSceneInternal(Index iScene, const Toolkit::TreeNode* tnScenes, const Toolkit::TreeNode* tnNodes, LoadParams& params);
349 void ParseSkeletons(const Toolkit::TreeNode* skeletons, Dali::Scene3D::Loader::SceneDefinition& scene, Dali::Scene3D::Loader::ResourceBundle& resources);
350 void ParseEnvironments(const Toolkit::TreeNode* environments, Dali::Scene3D::Loader::ResourceBundle& resources);
351 void ParseMaterials(const Toolkit::TreeNode* materials, DliInputParameter::ConvertColorCode convertColorCode, Dali::Scene3D::Loader::ResourceBundle& resources);
353 void ParseNodes(const Toolkit::TreeNode* nodes, Index index, LoadParams& params);
354 void ParseNodesInternal(const Toolkit::TreeNode* nodes, Index index, std::vector<Index>& inOutParentStack, LoadParams& params, IIndexMapper& indexMapper);
356 void ParseAnimations(const Toolkit::TreeNode* animations, LoadParams& params);
357 void ParseAnimationGroups(const Toolkit::TreeNode* animationGroups, LoadParams& params);
359 void ParseShaders(const Toolkit::TreeNode* shaders, Dali::Scene3D::Loader::ResourceBundle& resources);
360 void ParseMeshes(const Toolkit::TreeNode* meshes, Dali::Scene3D::Loader::ResourceBundle& resources);
362 void GetCameraParameters(std::vector<Dali::Scene3D::Loader::CameraParameters>& cameras) const;
363 void GetLightParameters(std::vector<Dali::Scene3D::Loader::LightParameters>& lights) const;
366 DliLoaderImpl::DliLoaderImpl()
371 DliLoaderImpl::~DliLoaderImpl() = default;
373 void DliLoaderImpl::SetErrorCallback(StringCallback onError)
375 mImpl->mOnError = onError;
378 bool DliLoaderImpl::LoadModel(const std::string& uri, Dali::Scene3D::Loader::LoadResult& result)
380 std::string daliBuffer = LoadTextFile(uri.c_str());
382 auto& parser = mImpl->mParser;
383 parser = JsonParser::New();
384 if(!parser.Parse(daliBuffer))
389 std::filesystem::path modelPath(uri);
390 Dali::Scene3D::Loader::DliInputParameter input;
391 LoadParams loadParams;
394 loadParams.input = static_cast<DliInputParameter*>(mInputParameter);
398 input.mAnimationsPath = std::string(modelPath.parent_path()) + "/";
399 loadParams.input = &input;
401 loadParams.output = &result;
403 mImpl->ParseScene(loadParams);
407 std::string DliLoaderImpl::GetParseError() const
409 std::stringstream stream;
411 auto& parser = mImpl->mParser;
412 if(parser.ParseError())
414 stream << "position: " << parser.GetErrorPosition() << ", line: " << parser.GetErrorLineNumber() << ", column: " << parser.GetErrorColumn() << ", description: " << parser.GetErrorDescription() << ".";
420 void DliLoaderImpl::Impl::ParseScene(LoadParams& params)
422 auto& input = *params.input;
423 auto& output = *params.output;
425 // get index of root node.
426 auto docRoot = mParser.GetRoot();
429 // Process resources first - these are shared
430 if(auto environments = docRoot->GetChild("environment"))
432 ParseEnvironments(environments, output.mResources); // NOTE: must precede parsing of materials
435 if(auto meshes = docRoot->GetChild("meshes"))
437 ParseMeshes(meshes, output.mResources);
440 if(auto shaders = docRoot->GetChild("shaders"))
442 ParseShaders(shaders, output.mResources);
445 if(auto materials = docRoot->GetChild("materials"))
447 ParseMaterials(materials, input.mConvertColorCode, output.mResources);
450 for(auto& c : input.mPreNodeCategoryProcessors)
452 if(auto node = docRoot->GetChild(c.first))
454 Property::Array array;
455 ParseProperties(*node, array);
456 c.second(std::move(array), mOnError);
461 Index iScene = 0; // default scene
462 ReadIndex(docRoot->GetChild("scene"), iScene);
464 auto tnScenes = RequireChild(docRoot, "scenes");
465 auto tnNodes = RequireChild(docRoot, "nodes");
466 ParseSceneInternal(iScene, tnScenes, tnNodes, params);
468 ParseSkeletons(docRoot->GetChild("skeletons"), output.mScene, output.mResources);
470 output.mScene.EnsureUniqueSkinningShaderInstances(output.mResources);
471 output.mScene.EnsureUniqueBlendShapeShaderInstances(output.mResources);
473 // Ger cameras and lights
474 GetCameraParameters(output.mCameraParameters);
475 GetLightParameters(output.mLightParameters);
477 // Post-node processors and animations last
478 for(auto& c : input.mPostNodeCategoryProcessors)
480 if(auto node = docRoot->GetChild(c.first))
482 Property::Array array;
483 ParseProperties(*node, array);
484 c.second(std::move(array), mOnError);
488 if(auto animations = docRoot->GetChild("animations"))
490 ParseAnimations(animations, params);
493 if(!output.mAnimationDefinitions.empty())
495 if(auto animationGroups = docRoot->GetChild("animationGroups"))
497 ParseAnimationGroups(animationGroups, params);
503 void DliLoaderImpl::Impl::ParseSceneInternal(Index iScene, const Toolkit::TreeNode* tnScenes, const Toolkit::TreeNode* tnNodes, LoadParams& params)
505 auto getSceneRootIdx = [tnScenes, tnNodes](Index iScene) {
506 auto tn = GetNthChild(tnScenes, iScene); // now a "scene" object
509 ExceptionFlinger(ASSERT_LOCATION) << iScene << " is out of bounds access into " << SCENES << ".";
512 tn = RequireChild(tn, NODES); // now a "nodes" array
513 if(tn->GetType() != TreeNode::ARRAY)
515 ExceptionFlinger(ASSERT_LOCATION) << SCENES << "[" << iScene << "]." << NODES << " has an invalid type; array required.";
520 ExceptionFlinger(ASSERT_LOCATION) << SCENES << "[" << iScene << "]." << NODES << " must define a node id.";
523 tn = GetNthChild(tn, 0); // now the first element of the array
525 if(!ReadIndex(tn, iRootNode))
527 ExceptionFlinger(ASSERT_LOCATION) << SCENES << "[" << iScene << "]." << NODES << " has an invalid value for root node index: '" << iRootNode << "'.";
530 if(iRootNode >= tnNodes->Size())
532 ExceptionFlinger(ASSERT_LOCATION) << "Root node index << " << iRootNode << " of scene " << iScene << " is out of bounds.";
535 tn = GetNthChild(tnNodes, iRootNode); // now a "node" object
536 if(tn->GetType() != TreeNode::OBJECT)
538 ExceptionFlinger(ASSERT_LOCATION) << "Root node of scene " << iScene << " is of invalid JSON type; object required";
544 Index iRootNode = getSceneRootIdx(iScene);
545 ParseNodes(tnNodes, iRootNode, params);
547 auto& scene = params.output->mScene;
548 scene.AddRootNode(0);
550 for(Index i = 0; i < iScene; ++i)
552 Index iRootNode = getSceneRootIdx(i);
553 const Index iRoot = scene.GetNodeCount();
554 ParseNodes(tnNodes, iRootNode, params);
555 scene.AddRootNode(iRoot);
558 auto numScenes = tnScenes->Size();
559 for(Index i = iScene + 1; i < numScenes; ++i)
561 Index iRootNode = getSceneRootIdx(i);
562 const Index iRoot = scene.GetNodeCount();
563 ParseNodes(tnNodes, iRootNode, params);
564 scene.AddRootNode(iRoot);
568 void DliLoaderImpl::Impl::ParseSkeletons(const TreeNode* skeletons, Dali::Scene3D::Loader::SceneDefinition& scene, Dali::Scene3D::Loader::ResourceBundle& resources)
572 auto iStart = skeletons->CBegin();
573 for(auto i0 = iStart, i1 = skeletons->CEnd(); i0 != i1; ++i0)
575 auto& node = (*i0).second;
576 std::string skeletonRootName;
577 if(ReadString(node.GetChild(NODE), skeletonRootName))
579 SkeletonDefinition skeleton;
580 if(!scene.FindNode(skeletonRootName, &skeleton.mRootNodeIdx))
582 ExceptionFlinger(ASSERT_LOCATION) << FormatString("Skeleton %d: node '%s' not defined.", resources.mSkeletons.size(), skeletonRootName.c_str());
585 uint32_t jointCount = 0;
586 std::function<void(Index)> visitFn;
587 auto& ibms = mInverseBindMatrices;
588 visitFn = [&](Index id) {
589 auto node = scene.GetNode(id);
590 jointCount += ibms.find(id) != ibms.end();
592 for(auto i : node->mChildren)
597 visitFn(skeleton.mRootNodeIdx);
599 if(jointCount > Skinning::MAX_JOINTS)
601 mOnError(FormatString("Skeleton %d: joint count exceeds supported limit.", resources.mSkeletons.size()));
602 jointCount = Skinning::MAX_JOINTS;
605 skeleton.mJoints.reserve(jointCount);
607 visitFn = [&](Index id) {
608 auto iFind = ibms.find(id);
609 if(iFind != ibms.end() && skeleton.mJoints.size() < Skinning::MAX_JOINTS)
611 skeleton.mJoints.push_back({id, iFind->second});
614 auto node = scene.GetNode(id);
615 for(auto i : node->mChildren)
620 visitFn(skeleton.mRootNodeIdx);
622 resources.mSkeletons.push_back(std::move(skeleton));
626 ExceptionFlinger(ASSERT_LOCATION) << "skeleton " << std::distance(iStart, i0) << ": Missing required attribute '" << NODE << "'.";
632 void DliLoaderImpl::Impl::ParseEnvironments(const TreeNode* environments, Dali::Scene3D::Loader::ResourceBundle& resources)
634 Matrix cubeOrientation(Matrix::IDENTITY);
636 for(auto i0 = environments->CBegin(), i1 = environments->CEnd(); i0 != i1; ++i0)
638 auto& node = (*i0).second;
640 EnvironmentDefinition envDef;
641 ReadString(node.GetChild("cubeSpecular"), envDef.mSpecularMapPath);
642 ReadString(node.GetChild("cubeDiffuse"), envDef.mDiffuseMapPath);
643 ToUnixFileSeparators(envDef.mSpecularMapPath);
644 ToUnixFileSeparators(envDef.mDiffuseMapPath);
645 envDef.mIblIntensity = 1.0f;
646 ReadFloat(node.GetChild("iblIntensity"), envDef.mIblIntensity);
647 if(ReadVector(node.GetChild("cubeInitialOrientation"), cubeOrientation.AsFloat(), 16u))
649 envDef.mCubeOrientation = Quaternion(cubeOrientation);
652 resources.mEnvironmentMaps.emplace_back(std::move(envDef), EnvironmentDefinition::Textures());
655 // NOTE: guarantees environmentMaps to have an empty environment.
656 if(resources.mEnvironmentMaps.empty())
658 resources.mEnvironmentMaps.emplace_back(EnvironmentDefinition(), EnvironmentDefinition::Textures());
662 void DliLoaderImpl::Impl::ParseShaders(const TreeNode* shaders, Dali::Scene3D::Loader::ResourceBundle& resources)
664 uint32_t iShader = 0;
665 for(auto i0 = shaders->CBegin(), i1 = shaders->CEnd(); i0 != i1; ++i0, ++iShader)
667 auto& node = (*i0).second;
668 ShaderDefinition shaderDef;
669 ReadStringVector(node.GetChild("defines"), shaderDef.mDefines);
670 auto sssIter = std::find_if(shaderDef.mDefines.begin(), shaderDef.mDefines.end(), [](std::string& item) { return (item == "SSS"); });
671 if(sssIter != shaderDef.mDefines.end())
673 shaderDef.mDefines.erase(sssIter);
676 // Read shader hints. Possible values are:
677 // Don't define for No hints.
678 // "OUTPUT_IS_TRANSPARENT" Might generate transparent alpha from opaque inputs.
679 // "MODIFIES_GEOMETRY" Might change position of vertices, this option disables any culling optimizations.
681 ReadStringVector(node.GetChild(HINTS), shaderDef.mHints);
683 if(ReadString(node.GetChild("vertex"), shaderDef.mVertexShaderPath) &&
684 ReadString(node.GetChild("fragment"), shaderDef.mFragmentShaderPath))
686 ToUnixFileSeparators(shaderDef.mVertexShaderPath);
687 ToUnixFileSeparators(shaderDef.mFragmentShaderPath);
689 for(TreeNode::ConstIterator j0 = node.CBegin(), j1 = node.CEnd(); j0 != j1; ++j0)
691 const TreeNode::KeyNodePair& keyValue = *j0;
692 const std::string& key = keyValue.first;
693 const TreeNode& value = keyValue.second;
695 Property::Value uniformValue;
696 if(key.compare("vertex") == 0 || key.compare("fragment") == 0 || key.compare("defines") == 0 || key.compare(HINTS) == 0)
700 else if(key.compare("rendererState") == 0)
702 shaderDef.mRendererState = ReadRendererState(keyValue.second);
704 else if(value.GetType() == TreeNode::INTEGER || value.GetType() == TreeNode::FLOAT)
707 ReadFloat(&value, f);
710 else if(value.GetType() == TreeNode::BOOLEAN)
712 DALI_LOG_WARNING("\"bool\" uniforms are handled as floats in shader");
714 if(ReadBool(&keyValue.second, value))
716 uniformValue = value ? 1.0f : 0.0f;
720 switch(auto size = GetNumericalArraySize(&value))
725 ReadVector(&value, m.AsFloat(), size);
733 ReadVector(&value, m.AsFloat(), size);
741 ReadVector(&value, v.AsFloat(), size);
749 ReadVector(&value, v.AsFloat(), size);
757 ReadVector(&value, v.AsFloat(), size);
763 mOnError(FormatString(
764 "shader %u: Ignoring uniform '%s': failed to infer type from %zu elements.",
771 if(Property::NONE != uniformValue.GetType())
773 shaderDef.mUniforms.Insert(key, uniformValue);
777 resources.mShaders.emplace_back(std::move(shaderDef), Shader());
781 ExceptionFlinger(ASSERT_LOCATION) << "shader " << iShader << ": Missing vertex / fragment shader definition.";
786 void DliLoaderImpl::Impl::ParseMeshes(const TreeNode* meshes, Dali::Scene3D::Loader::ResourceBundle& resources)
788 for(auto i0 = meshes->CBegin(), i1 = meshes->CEnd(); i0 != i1; ++i0)
790 auto& node = (*i0).second;
792 MeshDefinition meshDef;
793 if(!ReadString(node.GetChild(URI), meshDef.mUri))
795 ExceptionFlinger(ASSERT_LOCATION) << "mesh " << resources.mMeshes.size() << ": Missing required attribute '" << URI << "'.";
798 ToUnixFileSeparators(meshDef.mUri);
800 std::string primitive;
801 if(ReadString(node.GetChild("primitive"), primitive))
803 if(primitive == "LINES")
805 meshDef.mPrimitiveType = Geometry::LINES;
807 else if(primitive == "POINTS")
809 meshDef.mPrimitiveType = Geometry::POINTS;
811 else if(primitive != "TRIANGLES")
813 mOnError(FormatString(
814 "mesh %d: Using TRIANGLES instead of unsupported primitive type '%s'.",
815 resources.mMeshes.size(),
821 if(ReadInt(node.GetChild("attributes"), attributes))
823 if(MaskMatch(attributes, MeshDefinition::INDICES) &&
824 !ReadAttribAccessor(node.GetChild("indices"), meshDef.mIndices))
826 ExceptionFlinger(ASSERT_LOCATION) << FormatString("mesh %d: Failed to read %s.",
827 resources.mMeshes.size(),
831 if(MaskMatch(attributes, MeshDefinition::POSITIONS) &&
832 !ReadAttribAccessor(node.GetChild("positions"), meshDef.mPositions))
834 ExceptionFlinger(ASSERT_LOCATION) << FormatString("mesh %d: Failed to read %s.",
835 resources.mMeshes.size(),
839 if(MaskMatch(attributes, MeshDefinition::NORMALS) &&
840 !ReadAttribAccessor(node.GetChild("normals"), meshDef.mNormals))
842 mOnError(FormatString("mesh %d: Failed to read %s.", resources.mMeshes.size(), "normals"));
845 if(MaskMatch(attributes, MeshDefinition::TEX_COORDS))
847 meshDef.mTexCoords.emplace_back(MeshDefinition::Accessor{});
848 if(!ReadAttribAccessor(node.GetChild("textures"), meshDef.mTexCoords[0]))
850 mOnError(FormatString("mesh %d: Failed to read %s.", resources.mMeshes.size(), "textures"));
854 if(MaskMatch(attributes, MeshDefinition::TANGENTS) &&
855 !ReadAttribAccessor(node.GetChild("tangents"), meshDef.mTangents))
857 mOnError(FormatString("mesh %d: Failed to read %s.", resources.mMeshes.size(), "tangents"));
860 // NOTE: we're no longer reading bitangents as these are calculated in the shaders.
861 if(ReadIndex(node.GetChild("skeleton"), meshDef.mSkeletonIdx))
863 if(!MaskMatch(attributes, MeshDefinition::JOINTS_0) &&
864 !MaskMatch(attributes, MeshDefinition::WEIGHTS_0))
866 mOnError(FormatString("mesh %d: Expected joints0 / weights0 attribute(s) missing.",
867 resources.mMeshes.size()));
871 meshDef.mJoints.emplace_back(MeshDefinition::Accessor{});
872 meshDef.mWeights.emplace_back(MeshDefinition::Accessor{});
873 if(!ReadAttribAccessor(node.GetChild("joints0"), meshDef.mJoints[0]) ||
874 !ReadAttribAccessor(node.GetChild("weights0"), meshDef.mWeights[0]))
876 mOnError(FormatString("mesh %d: Failed to read skinning information.",
877 resources.mMeshes.size()));
882 if(auto blendshapeHeader = node.GetChild(BLEND_SHAPE_HEADER))
884 std::string blendShapeVersion;
885 ReadString(blendshapeHeader->GetChild(VERSION), blendShapeVersion);
887 if(0u == blendShapeVersion.compare(BLEND_SHAPE_VERSION_1_0))
889 meshDef.mBlendShapeVersion = BlendShapes::Version::VERSION_1_0;
891 else if(0u == blendShapeVersion.compare(BLEND_SHAPE_VERSION_2_0))
893 meshDef.mBlendShapeVersion = BlendShapes::Version::VERSION_2_0;
896 switch(meshDef.mBlendShapeVersion)
898 case BlendShapes::Version::VERSION_1_0:
899 case BlendShapes::Version::VERSION_2_0: // FALL THROUGH
901 ReadAttribBlob(blendshapeHeader, meshDef.mBlendShapeHeader);
912 if(auto blendShapes = node.GetChild(BLEND_SHAPES))
914 meshDef.mBlendShapes.resize(blendShapes->Size());
917 for(auto it = blendShapes->CBegin(), endIt = blendShapes->CEnd(); it != endIt; ++it, ++index)
919 // Each blend shape is stored as the difference with the original mesh.
921 auto& blendShapeNode = (*it).second;
923 auto& blendShape = meshDef.mBlendShapes[index];
924 ReadString(blendShapeNode.GetChild("name"), blendShape.name);
925 if(auto position = blendShapeNode.GetChild("positions"))
927 ReadAttribAccessor(position, blendShape.deltas);
929 if(auto normals = blendShapeNode.GetChild("normals"))
931 ReadAttribAccessor(normals, blendShape.normals);
933 if(auto tangents = blendShapeNode.GetChild("tangents"))
935 ReadAttribAccessor(tangents, blendShape.tangents);
937 ReadFloat(blendShapeNode.GetChild("weight"), blendShape.weight);
942 if(ReadBool(node.GetChild("flipV"), flipV))
944 meshDef.mFlags |= flipV * MeshDefinition::FLIP_UVS_VERTICAL;
947 resources.mMeshes.emplace_back(std::move(meshDef), MeshGeometry());
952 void DliLoaderImpl::Impl::ParseMaterials(const TreeNode* materials, DliInputParameter::ConvertColorCode convertColorCode, Dali::Scene3D::Loader::ResourceBundle& resources)
954 for(auto i0 = materials->CBegin(), i1 = materials->CEnd(); i0 != i1; ++i0)
956 auto& node = (*i0).second;
958 MaterialDefinition materialDef;
959 if(auto eEnvironment = node.GetChild("environment"))
961 ReadIndex(eEnvironment, materialDef.mEnvironmentIdx);
962 if(static_cast<unsigned int>(materialDef.mEnvironmentIdx) >= resources.mEnvironmentMaps.size())
964 ExceptionFlinger(ASSERT_LOCATION) << "material " << resources.mMaterials.size() << ": Environment index " << materialDef.mEnvironmentIdx << " out of bounds (" << resources.mEnvironmentMaps.size() << ").";
968 // TODO : need to consider AGIF
969 std::vector<std::string> texturePaths;
970 std::string texturePath;
971 if(ReadString(node.GetChild("albedoMap"), texturePath))
973 ToUnixFileSeparators(texturePath);
974 const auto semantic = MaterialDefinition::ALBEDO;
975 materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
976 materialDef.mFlags |= semantic | MaterialDefinition::TRANSPARENCY; // NOTE: only in dli does single / separate ALBEDO texture mean TRANSPARENCY.
978 if(ReadString(node.GetChild("albedoMetallicMap"), texturePath))
980 ToUnixFileSeparators(texturePath);
982 if(MaskMatch(materialDef.mFlags, MaterialDefinition::ALBEDO))
984 mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "albedo"));
987 const auto semantic = MaterialDefinition::ALBEDO | MaterialDefinition::METALLIC;
988 materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
989 materialDef.mFlags |= semantic;
992 if(ReadString(node.GetChild("metallicRoughnessMap"), texturePath))
994 ToUnixFileSeparators(texturePath);
996 if(MaskMatch(materialDef.mFlags, MaterialDefinition::METALLIC))
998 mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "metallic"));
1001 const auto semantic = MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS;
1002 materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
1003 materialDef.mFlags |= semantic |
1004 // We have a metallic-roughhness map and the first texture did not have albedo semantics - we're in the transparency workflow.
1005 (MaskMatch(materialDef.mFlags, MaterialDefinition::ALBEDO) * MaterialDefinition::TRANSPARENCY);
1008 if(ReadString(node.GetChild("normalMap"), texturePath))
1010 ToUnixFileSeparators(texturePath);
1012 const auto semantic = MaterialDefinition::NORMAL;
1013 materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
1014 materialDef.mFlags |= semantic |
1015 // We have a standalone normal map and the first texture did not have albedo semantics - we're in the transparency workflow.
1016 (MaskMatch(materialDef.mFlags, MaterialDefinition::ALBEDO) * MaterialDefinition::TRANSPARENCY);
1019 if(ReadString(node.GetChild("normalRoughnessMap"), texturePath))
1021 ToUnixFileSeparators(texturePath);
1023 if(MaskMatch(materialDef.mFlags, MaterialDefinition::NORMAL))
1025 mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "normal"));
1028 if(MaskMatch(materialDef.mFlags, MaterialDefinition::ROUGHNESS))
1030 mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "roughness"));
1033 if(MaskMatch(materialDef.mFlags, MaterialDefinition::TRANSPARENCY))
1035 mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "transparency"));
1038 const auto semantic = MaterialDefinition::NORMAL | MaterialDefinition::ROUGHNESS;
1039 materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
1040 materialDef.mFlags |= semantic;
1043 /// @TODO : Some dli shader don't implement this subsurfaceMp usage.
1044 /// To make visual test pass, Skip subsurfaceMap texture using
1045 /// until dli shaders are support it.
1046 // if(ReadString(node.GetChild("subsurfaceMap"), texturePath))
1048 // ToUnixFileSeparators(texturePath);
1050 // const auto semantic = MaterialDefinition::SUBSURFACE;
1051 // materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
1052 // materialDef.mFlags |= semantic;
1055 if(ReadString(node.GetChild("occlusionMap"), texturePath))
1057 ToUnixFileSeparators(texturePath);
1058 const auto semantic = MaterialDefinition::OCCLUSION;
1059 materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
1060 materialDef.mFlags |= semantic;
1063 if(ReadColorCodeOrColor(&node, materialDef.mColor, convertColorCode) &&
1064 materialDef.mColor.a < 1.0f)
1066 materialDef.mFlags |= MaterialDefinition::TRANSPARENCY;
1069 ReadFloat(node.GetChild("metallic"), materialDef.mMetallic);
1070 ReadFloat(node.GetChild("roughness"), materialDef.mRoughness);
1073 if(ReadBool(node.GetChild("mipmap"), mipmaps) && mipmaps)
1075 for(auto& ts : materialDef.mTextureStages)
1077 ts.mTexture.mSamplerFlags |= SamplerFlags::FILTER_MIPMAP_LINEAR;
1081 resources.mMaterials.emplace_back(std::move(materialDef), TextureSet());
1085 void DliLoaderImpl::Impl::ParseNodes(const TreeNode* const nodes, Index index, LoadParams& params)
1087 std::vector<Index> parents;
1090 struct IndexMapper : IIndexMapper
1092 IndexMapper(size_t numNodes)
1094 mIndices.reserve(numNodes);
1097 virtual bool Map(Index iDli, Index iScene) override
1099 Entry idx{iDli, iScene};
1100 auto iInsert = std::lower_bound(mIndices.begin(), mIndices.end(), idx);
1101 if(iInsert == mIndices.end() || iInsert->iDli != iDli)
1103 mIndices.insert(iInsert, idx);
1105 else if(iInsert->iScene != iScene)
1112 virtual unsigned int Resolve(Index iDli) override
1114 auto iFind = std::lower_bound(mIndices.begin(), mIndices.end(), iDli, [](const Entry& idx, Index iDli) { return idx.iDli < iDli; });
1115 DALI_ASSERT_ALWAYS(iFind != mIndices.end());
1116 return iFind->iScene;
1123 unsigned int iScene;
1125 bool operator<(const Entry& other) const
1127 return iDli < other.iDli;
1130 std::vector<Entry> mIndices;
1131 } mapper(nodes->Size());
1132 ParseNodesInternal(nodes, index, parents, params, mapper);
1134 auto& scene = params.output->mScene;
1135 for(size_t i0 = 0, i1 = scene.GetNodeCount(); i0 < i1; ++i0)
1137 for(auto& c : scene.GetNode(i0)->mConstraints)
1139 c.mSourceIdx = mapper.Resolve(c.mSourceIdx);
1144 void DliLoaderImpl::Impl::ParseNodesInternal(const TreeNode* const nodes, Index index, std::vector<Index>& inOutParentStack, LoadParams& params, IIndexMapper& mapper)
1146 // Properties that may be resolved from a JSON value with ReadInt() -- or default to 0.
1147 struct IndexProperty
1149 ResourceType::Value type;
1150 const TreeNode* source;
1153 std::vector<IndexProperty> resourceIds;
1154 resourceIds.reserve(4);
1156 if(auto node = GetNthChild(nodes, index))
1158 Dali::Scene3D::Loader::NodeDefinition nodeDef;
1159 nodeDef.mParentIdx = inOutParentStack.empty() ? INVALID_INDEX : inOutParentStack.back();
1162 ReadString(node->GetChild(NAME), nodeDef.mName);
1165 ReadModelTransform(node, nodeDef.mOrientation, nodeDef.mPosition, nodeDef.mScale);
1167 // Reads the size of the node.
1169 // * It can be given as 'size' or 'bounds'.
1170 // * The sdk saves the 'size' as a vector2 in some cases.
1171 // * To avoid size related issues the following code attemps
1172 // to read the 'size/bounds' as a vector3 first, if it's
1173 // not successful then reads it as a vector2.
1174 ReadVector(node->GetChild("size"), nodeDef.mSize.AsFloat(), 3) ||
1175 ReadVector(node->GetChild("size"), nodeDef.mSize.AsFloat(), 2) ||
1176 ReadVector(node->GetChild("bounds"), nodeDef.mSize.AsFloat(), 3) ||
1177 ReadVector(node->GetChild("bounds"), nodeDef.mSize.AsFloat(), 2);
1180 ReadBool(node->GetChild("visible"), nodeDef.mIsVisible);
1182 // type classification
1183 if(auto eCustomization = node->GetChild("customization")) // customization
1186 if(ReadString(eCustomization->GetChild("tag"), tag))
1188 nodeDef.mCustomization.reset(new Dali::Scene3D::Loader::NodeDefinition::CustomizationDefinition{tag});
1191 else // something renderable maybe
1193 std::unique_ptr<Dali::Scene3D::Loader::NodeDefinition::Renderable> renderable;
1194 ModelRenderable* modelRenderable = nullptr; // no ownership, aliasing renderable for the right type.
1196 const TreeNode* eRenderable = nullptr;
1197 if((eRenderable = node->GetChild("model")))
1199 // check for mesh before allocating - this can't be missing.
1200 auto eMesh = eRenderable->GetChild("mesh");
1203 ExceptionFlinger(ASSERT_LOCATION) << "node " << nodeDef.mName << ": Missing mesh definition.";
1206 modelRenderable = new ModelRenderable();
1207 renderable.reset(modelRenderable);
1209 resourceIds.push_back({ResourceType::Mesh, eMesh, modelRenderable->mMeshIdx});
1211 else if((eRenderable = node->GetChild("arc")))
1213 // check for mesh before allocating - this can't be missing.
1214 auto eMesh = eRenderable->GetChild("mesh");
1217 ExceptionFlinger(ASSERT_LOCATION) << "node " << nodeDef.mName << ": Missing mesh definition.";
1220 auto arcRenderable = new ArcRenderable;
1221 renderable.reset(arcRenderable);
1222 modelRenderable = arcRenderable;
1224 resourceIds.push_back({ResourceType::Mesh, eMesh, arcRenderable->mMeshIdx});
1226 ReadArcField(eRenderable, *arcRenderable);
1229 if(renderable && eRenderable != nullptr) // process common properties of all renderables + register payload
1232 renderable->mShaderIdx = 0;
1233 auto eShader = eRenderable->GetChild("shader");
1236 resourceIds.push_back({ResourceType::Shader, eShader, renderable->mShaderIdx});
1242 modelRenderable->mMaterialIdx = 0; // must offer default of 0
1243 auto eMaterial = eRenderable->GetChild("material");
1246 resourceIds.push_back({ResourceType::Material, eMaterial, modelRenderable->mMaterialIdx});
1249 if(!ReadColorCodeOrColor(eRenderable, modelRenderable->mColor, params.input->mConvertColorCode))
1251 ReadColorCodeOrColor(node, modelRenderable->mColor, params.input->mConvertColorCode);
1255 nodeDef.mRenderables.push_back(std::move(renderable));
1259 // Resolve ints - default to 0 if undefined
1260 auto& output = params.output;
1261 for(auto& idRes : resourceIds)
1266 case ResourceType::Shader:
1267 iCheck = output->mResources.mShaders.size();
1270 case ResourceType::Mesh:
1271 iCheck = output->mResources.mMeshes.size();
1274 case ResourceType::Material:
1275 iCheck = output->mResources.mMaterials.size();
1279 ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ": Invalid resource type: " << idRes.type << " (Programmer error)";
1286 else if(idRes.source->GetType() != TreeNode::INTEGER)
1288 ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ": Invalid " << GetResourceTypeName(idRes.type) << " index type.";
1292 idRes.target = idRes.source->GetInteger();
1295 if(idRes.target >= iCheck)
1297 ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ": " << GetResourceTypeName(idRes.type) << " index " << idRes.target << " out of bounds (" << iCheck << ").";
1300 resourceIds.clear();
1303 if(auto eExtras = node->GetChild("extras"))
1305 auto& extras = nodeDef.mExtras;
1306 extras.reserve(eExtras->Size());
1308 for(auto i0 = eExtras->CBegin(), i1 = eExtras->CEnd(); i0 != i1; ++i0)
1310 Dali::Scene3D::Loader::NodeDefinition::Extra e;
1313 e.mKey = eExtra.first;
1316 mOnError(FormatString("node %d: empty string is invalid for name of extra %d; ignored.",
1322 e.mValue = ReadPropertyValue(eExtra.second);
1323 if(e.mValue.GetType() == Property::Type::NONE)
1325 mOnError(FormatString("node %d: failed to interpret value of extra '%s' : %s; ignored.",
1328 eExtra.second.GetString()));
1332 auto iInsert = std::lower_bound(extras.begin(), extras.end(), e);
1333 if(iInsert != extras.end() && iInsert->mKey == e.mKey)
1335 mOnError(FormatString("node %d: extra '%s' already defined; overriding with %s.",
1338 eExtra.second.GetString()));
1339 *iInsert = std::move(e);
1343 extras.insert(iInsert, e);
1350 if(auto eConstraints = node->GetChild("constraints"))
1352 auto& constraints = nodeDef.mConstraints;
1353 constraints.reserve(eConstraints->Size());
1355 ConstraintDefinition cDef;
1356 for(auto i0 = eConstraints->CBegin(), i1 = eConstraints->CEnd(); i0 != i1; ++i0)
1358 auto eConstraint = *i0;
1359 if(!ReadIndex(&eConstraint.second, cDef.mSourceIdx))
1361 mOnError(FormatString("node %d: node ID %s for constraint %d is invalid; ignored.",
1363 eConstraint.second.GetString(),
1364 constraints.size()));
1368 cDef.mProperty = eConstraint.first;
1370 auto iInsert = std::lower_bound(constraints.begin(), constraints.end(), cDef);
1371 if(iInsert != constraints.end() && *iInsert == cDef)
1373 mOnError(FormatString("node %d: constraint %s@%d already defined; ignoring.",
1375 cDef.mProperty.c_str(),
1380 constraints.insert(iInsert, cDef);
1386 // Determine index for mapping
1387 const unsigned int myIndex = output->mScene.GetNodeCount();
1388 if(!mapper.Map(index, myIndex))
1390 mOnError(FormatString("node %d: error mapping dli index %d: node has multiple parents. Ignoring subtree.", index, myIndex));
1394 // if the node is a bone in a skeletal animation, it will have the inverse bind pose matrix.
1395 Matrix invBindMatrix{false};
1396 if(ReadVector(node->GetChild("inverseBindPoseMatrix"), invBindMatrix.AsFloat(), 16u)) // TODO: more robust error checking?
1398 mInverseBindMatrices[myIndex] = invBindMatrix;
1402 auto rawDef = output->mScene.AddNode(std::make_unique<Dali::Scene3D::Loader::NodeDefinition>(std::move(nodeDef)));
1403 if(rawDef) // NOTE: no ownership. Guaranteed to stay in scope.
1405 // ...And only then parse children.
1406 if(auto children = node->GetChild("children"))
1408 inOutParentStack.push_back(myIndex);
1410 rawDef->mChildren.reserve(children->Size());
1412 uint32_t iChild = 0;
1413 for(auto j0 = children->CBegin(), j1 = children->CEnd(); j0 != j1; ++j0, ++iChild)
1415 auto& child = (*j0).second;
1416 if(child.GetType() == TreeNode::INTEGER)
1418 ParseNodesInternal(nodes, child.GetInteger(), inOutParentStack, params, mapper); // child object is created in scene definition.
1422 ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ", child " << iChild << ": invalid index type.";
1426 inOutParentStack.pop_back();
1428 else if(rawDef->mCustomization)
1430 mOnError(FormatString("node %d: not an actual customization without children.", index));
1433 if(auto proc = params.input->mNodePropertyProcessor) // optional processing
1435 // WARNING: constraint IDs are not resolved at this point.
1436 Property::Map nodeData;
1437 ParseProperties(*node, nodeData);
1438 proc(*rawDef, std::move(nodeData), mOnError);
1443 ExceptionFlinger(ASSERT_LOCATION) << "Node " << index << ": name already used.";
1448 void DliLoaderImpl::Impl::ParseAnimations(const TreeNode* tnAnimations, LoadParams& params)
1450 auto& definitions = params.output->mAnimationDefinitions;
1451 definitions.reserve(definitions.size() + tnAnimations->Size());
1453 for(TreeNode::ConstIterator iAnim = tnAnimations->CBegin(), iAnimEnd = tnAnimations->CEnd();
1457 const TreeNode& tnAnim = (*iAnim).second;
1458 uint32_t animationPropertyIndex = 0;
1459 AnimationDefinition animDef;
1460 std::string animationName;
1461 ReadString(tnAnim.GetChild(NAME), animationName);
1462 animDef.SetName(animationName);
1464 auto iFind = std::lower_bound(definitions.begin(), definitions.end(), animDef, [](const AnimationDefinition& ad0, const AnimationDefinition& ad1) { return ad0.GetName() < ad1.GetName(); });
1465 const bool overwrite = iFind != definitions.end() && iFind->GetName() == animDef.GetName();
1468 mOnError(FormatString("Pre-existing animation with name '%s' is being overwritten.", animDef.GetName().c_str()));
1471 // Duration -- We need something that animated properties' delay / duration can
1472 // be expressed as a multiple of; 0 won't work. This is small enough (i.e. shorter
1473 // than our frame delay) to not be restrictive WRT replaying. If anything needs
1474 // to occur more frequently, then Animations are likely not your solution anyway.
1475 float animationDuration = 0.0f;
1476 if(!ReadFloat(tnAnim.GetChild("duration"), animationDuration))
1478 mOnError(FormatString("Animation '%s' fails to define '%s'.",
1479 animDef.GetName().c_str(),
1482 if(animationDuration < AnimationDefinition::MIN_DURATION_SECONDS)
1484 animationDuration = AnimationDefinition::MIN_DURATION_SECONDS;
1486 animDef.SetDuration(animationDuration);
1488 // Get loop count - # of playbacks. Default is once. 0 means repeat indefinitely.
1489 int32_t animationLoopCount = 1;
1490 if(ReadInt(tnAnim.GetChild("loopCount"), animationLoopCount) && animationLoopCount < 0)
1492 animationLoopCount = 0;
1494 animDef.SetLoopCount(animationLoopCount);
1496 std::string endAction;
1497 if(ReadString(tnAnim.GetChild("endAction"), endAction))
1499 if("BAKE" == endAction)
1501 animDef.SetEndAction(Animation::BAKE);
1503 else if("DISCARD" == endAction)
1505 animDef.SetEndAction(Animation::DISCARD);
1507 else if("BAKE_FINAL" == endAction)
1509 animDef.SetEndAction(Animation::BAKE_FINAL);
1513 if(ReadString(tnAnim.GetChild("disconnectAction"), endAction))
1515 if("BAKE" == endAction)
1517 animDef.SetDisconnectAction(Animation::BAKE);
1519 else if("DISCARD" == endAction)
1521 animDef.SetDisconnectAction(Animation::DISCARD);
1523 else if("BAKE_FINAL" == endAction)
1525 animDef.SetDisconnectAction(Animation::BAKE_FINAL);
1529 if(const TreeNode* tnProperties = tnAnim.GetChild("properties"))
1531 animDef.ReserveSize(tnProperties->Size());
1532 for(TreeNode::ConstIterator iProperty = tnProperties->CBegin(), iPropertyEnd = tnProperties->CEnd();
1533 iProperty != iPropertyEnd;
1536 const TreeNode& tnProperty = (*iProperty).second;
1538 AnimatedProperty animProp;
1539 if(!ReadString(tnProperty.GetChild("node"), animProp.mNodeName))
1541 mOnError(FormatString("Animation '%s': Failed to read the 'node' tag.", animDef.GetName().c_str()));
1545 if(!ReadString(tnProperty.GetChild("property"), animProp.mPropertyName))
1547 mOnError(FormatString("Animation '%s': Failed to read the 'property' tag", animDef.GetName().c_str()));
1551 // these are the defaults
1552 animProp.mTimePeriod.delaySeconds = 0.f;
1553 animProp.mTimePeriod.durationSeconds = animDef.GetDuration();
1554 if(!ReadTimePeriod(tnProperty.GetChild("timePeriod"), animProp.mTimePeriod))
1556 mOnError(FormatString("Animation '%s': timePeriod missing in Property #%d: defaulting to %f.",
1557 animDef.GetName().c_str(),
1558 animDef.GetPropertyCount(),
1559 animProp.mTimePeriod.durationSeconds));
1562 std::string alphaFunctionValue;
1563 if(ReadString(tnProperty.GetChild("alphaFunction"), alphaFunctionValue))
1565 animProp.mAlphaFunction = GetAlphaFunction(alphaFunctionValue);
1568 if(const TreeNode* tnKeyFramesBin = tnProperty.GetChild("keyFramesBin"))
1570 DALI_ASSERT_ALWAYS(!animProp.mPropertyName.empty() && "Animation must specify a property name");
1572 std::ifstream binAniFile;
1573 std::string animationFilename;
1574 if(ReadString(tnKeyFramesBin->GetChild(URL), animationFilename))
1576 std::string animationFullPath = params.input->mAnimationsPath + animationFilename;
1577 binAniFile.open(animationFullPath, std::ios::binary);
1578 if(binAniFile.fail())
1580 ExceptionFlinger(ASSERT_LOCATION) << "Failed to open animation data '" << animationFullPath << "'";
1585 ReadInt(tnKeyFramesBin->GetChild("byteOffset"), byteOffset);
1586 DALI_ASSERT_ALWAYS(byteOffset >= 0);
1588 binAniFile.seekg(static_cast<std::streamoff>(byteOffset), std::ios::beg);
1591 ReadInt(tnKeyFramesBin->GetChild("numKeys"), numKeys);
1592 DALI_ASSERT_ALWAYS(numKeys >= 0);
1594 animProp.mKeyFrames = KeyFrames::New();
1596 // In binary animation file only is saved the position, rotation, scale and blend shape weight keys.
1597 // so, if it is vector3 we assume is position or scale keys, if it is vector4 we assume is rotation,
1598 // otherwise are blend shape weight keys.
1599 // TODO support for binary header with size information
1600 Property::Type propType = Property::FLOAT; // assume blend shape weights
1601 if(animProp.mPropertyName == "orientation")
1603 propType = Property::VECTOR4;
1605 else if((animProp.mPropertyName == "position") || (animProp.mPropertyName == "scale"))
1607 propType = Property::VECTOR3;
1610 // alphafunction is reserved for future implementation
1611 // NOTE: right now we're just using AlphaFunction::LINEAR.
1612 unsigned char dummyAlphaFunction;
1615 Property::Value propValue;
1616 for(int key = 0; key < numKeys; key++)
1618 binAniFile.read(reinterpret_cast<char*>(&progress), sizeof(float));
1619 if(propType == Property::VECTOR3)
1622 binAniFile.read(reinterpret_cast<char*>(value.AsFloat()), sizeof(float) * 3);
1623 propValue = Property::Value(value);
1625 else if(propType == Property::VECTOR4)
1628 binAniFile.read(reinterpret_cast<char*>(value.AsFloat()), sizeof(float) * 4);
1629 propValue = Property::Value(Quaternion(value));
1634 binAniFile.read(reinterpret_cast<char*>(&value), sizeof(float));
1635 propValue = Property::Value(value);
1638 binAniFile.read(reinterpret_cast<char*>(&dummyAlphaFunction), sizeof(unsigned char));
1640 animProp.mKeyFrames.Add(progress, propValue, AlphaFunction::LINEAR);
1643 else if(const TreeNode* tnKeyFrames = tnProperty.GetChild("keyFrames"))
1645 DALI_ASSERT_ALWAYS(!animProp.mPropertyName.empty() && "Animation must specify a property name");
1646 animProp.mKeyFrames = KeyFrames::New();
1648 float progress = 0.0f;
1649 for(auto i0 = tnKeyFrames->CBegin(), i1 = tnKeyFrames->CEnd(); i1 != i0; ++i0)
1651 const TreeNode::KeyNodePair& kfKeyChild = *i0;
1652 bool readResult = ReadFloat(kfKeyChild.second.GetChild("progress"), progress);
1653 DALI_ASSERT_ALWAYS(readResult && "Key frame entry must have 'progress'");
1655 const TreeNode* tnValue = kfKeyChild.second.GetChild("value");
1656 DALI_ASSERT_ALWAYS(tnValue && "Key frame entry must have 'value'");
1658 // For the "orientation" property, convert from Vector4 -> Rotation value
1659 // This work-around is preferable to a null-pointer exception in the DALi update thread
1660 Property::Value propValue(ReadPropertyValue(*tnValue));
1661 if(propValue.GetType() == Property::VECTOR4 &&
1662 animProp.mPropertyName == "orientation")
1666 propValue = Property::Value(Quaternion(v.w, v.x, v.y, v.z));
1669 AlphaFunction kfAlphaFunction(AlphaFunction::DEFAULT);
1670 std::string alphaFuncStr;
1671 if(ReadString(kfKeyChild.second.GetChild("alphaFunction"), alphaFuncStr))
1673 kfAlphaFunction = GetAlphaFunction(alphaFuncStr);
1676 animProp.mKeyFrames.Add(progress, propValue, kfAlphaFunction);
1681 const TreeNode* tnValue = tnProperty.GetChild("value");
1684 animProp.mValue.reset(new AnimatedProperty::Value{ReadPropertyValue(*tnValue)});
1685 ReadBool(tnProperty.GetChild("relative"), animProp.mValue->mIsRelative);
1689 mOnError(FormatString("Property '%s' fails to define target value.",
1690 animProp.mPropertyName.c_str()));
1694 animDef.SetProperty(animationPropertyIndex++, std::move(animProp));
1698 if(auto proc = params.input->mAnimationPropertyProcessor) // optional processing
1701 ParseProperties(tnAnim, map);
1702 proc(animDef, std::move(map), mOnError);
1707 *iFind = std::move(animDef);
1711 iFind = definitions.insert(iFind, std::move(animDef));
1716 void DliLoaderImpl::Impl::ParseAnimationGroups(const Toolkit::TreeNode* tnAnimationGroups, LoadParams& params)
1718 auto& animGroups = params.output->mAnimationGroupDefinitions;
1721 for(auto iGroups = tnAnimationGroups->CBegin(), iGroupsEnd = tnAnimationGroups->CEnd();
1722 iGroups != iGroupsEnd;
1723 ++iGroups, ++numGroups)
1725 const auto& tnGroup = *iGroups;
1726 auto tnName = tnGroup.second.GetChild(NAME);
1727 std::string groupName;
1728 if(!tnName || !ReadString(tnName, groupName))
1730 mOnError(FormatString("Failed to get the name for the Animation group %d; ignoring.", numGroups));
1734 auto iFind = std::lower_bound(animGroups.begin(), animGroups.end(), groupName, [](const AnimationGroupDefinition& group, const std::string& name) { return group.mName < name; });
1735 if(iFind != animGroups.end() && iFind->mName == groupName)
1737 mOnError(FormatString("Animation group with name '%s' already exists; new entries will be merged.", groupName.c_str()));
1741 iFind = animGroups.insert(iFind, AnimationGroupDefinition{});
1744 iFind->mName = groupName;
1746 auto tnAnims = tnGroup.second.GetChild("animations");
1747 if(tnAnims && tnAnims->Size() > 0)
1749 auto& anims = iFind->mAnimations;
1750 anims.reserve(anims.size() + tnAnims->Size());
1751 for(auto iAnims = tnAnims->CBegin(), iAnimsEnd = tnAnims->CEnd(); iAnims != iAnimsEnd; ++iAnims)
1753 anims.push_back((*iAnims).second.GetString());
1759 void DliLoaderImpl::Impl::GetCameraParameters(std::vector<Dali::Scene3D::Loader::CameraParameters>& cameras) const
1761 if(mParser.GetRoot())
1763 if(const TreeNode* jsonCameras = mParser.GetRoot()->GetChild("cameras"))
1765 float dummyFloatArray[4];
1767 cameras.resize(jsonCameras->Size());
1768 auto iCamera = cameras.begin();
1769 for(auto i0 = jsonCameras->CBegin(), i1 = jsonCameras->CEnd(); i0 != i1; ++i0)
1771 auto& jsonCamera = (*i0).second;
1773 ReadFloat(jsonCamera.GetChild("fov"), iCamera->yFovDegree.degree);
1774 ReadFloat(jsonCamera.GetChild("near"), iCamera->zNear);
1775 ReadFloat(jsonCamera.GetChild("far"), iCamera->zFar);
1776 if(ReadVector(jsonCamera.GetChild("orthographic"), dummyFloatArray, 4u))
1778 iCamera->isPerspective = false;
1780 iCamera->orthographicSize = dummyFloatArray[2] * 0.5f;
1781 iCamera->aspectRatio = dummyFloatArray[1] / dummyFloatArray[2];
1784 if(auto jsonMatrix = jsonCamera.GetChild("matrix"))
1786 ReadVector(jsonMatrix, iCamera->matrix.AsFloat(), 16u);
1795 void DliLoaderImpl::Impl::GetLightParameters(std::vector<Dali::Scene3D::Loader::LightParameters>& lights) const
1797 if(mParser.GetRoot())
1799 if(const TreeNode* jsonLights = mParser.GetRoot()->GetChild("lights"))
1801 lights.resize(jsonLights->Size());
1802 auto iLight = lights.begin();
1803 for(auto i0 = jsonLights->CBegin(), i1 = jsonLights->CEnd(); i0 != i1; ++i0)
1805 auto& jsonLight = (*i0).second;
1806 if(!ReadVector(jsonLight.GetChild("matrix"), iLight->transform.AsFloat(), 16))
1809 FormatString("Failed to parse light %d - \"matrix\" child with 16 floats expected.\n",
1810 std::distance(jsonLights->CBegin(), i0)));
1814 int shadowMapSize = 0;
1815 if(ReadInt(jsonLight.GetChild(SHADOW_MAP_SIZE), shadowMapSize) && shadowMapSize < 0)
1818 FormatString("Failed to parse light %d - %s has an invalid value.",
1819 std::distance(jsonLights->CBegin(), i0),
1823 iLight->shadowMapSize = shadowMapSize;
1825 float orthoSize = 0.f;
1826 if(ReadFloat(jsonLight.GetChild(ORTHOGRAPHIC_SIZE), orthoSize) &&
1827 (orthoSize < .0f || std::isnan(orthoSize) || std::isinf(orthoSize)))
1830 FormatString("Failed to parse light %d - %s has an invalid value.",
1831 std::distance(jsonLights->CBegin(), i0),
1832 ORTHOGRAPHIC_SIZE));
1835 iLight->orthographicSize = orthoSize;
1837 if((iLight->shadowMapSize > 0) != (iLight->orthographicSize > .0f))
1839 mOnError(FormatString(
1840 "Light %d: Both shadow map size and orthographic size must be set for shadows to work.",
1841 std::distance(jsonLights->CBegin(), i0)));
1844 if(!ReadVector(jsonLight.GetChild("color"), iLight->color.AsFloat(), 3)) // color is optional
1846 iLight->color = Vector3::ONE; // default to white
1849 if(!ReadFloat(jsonLight.GetChild("intensity"), iLight->intensity)) // intensity is optional
1851 iLight->intensity = 1.0f; // default to 1.0
1854 if(!ReadFloat(jsonLight.GetChild("shadowIntensity"), iLight->shadowIntensity)) // intensity is optional
1856 iLight->shadowIntensity = 1.0f; // default to 1.0
1865 } // namespace Internal
1866 } // namespace Loader
1867 } // namespace Scene3D