Fix coverity issue (no POSITION attribute + use after move)
[platform/core/uifw/dali-toolkit.git] / dali-scene3d / internal / loader / dli-loader-impl.cpp
1 /*
2  * Copyright (c) 2023 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/loader/dli-loader-impl.h>
20
21 // EXTERNAL INCLUDES
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>
26
27 #include <algorithm>
28 #include <cmath>
29 #include <filesystem>
30 #include <fstream>
31 #include <limits>
32 #include <memory>
33
34 // INTERNAL INCLUDES
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>
47
48 #define DLI_0_1_COMPATIBILITY
49
50 namespace Dali
51 {
52 using namespace Toolkit;
53
54 namespace Scene3D
55 {
56 namespace Loader
57 {
58 namespace Internal
59 {
60 namespace rs = RendererState;
61
62 namespace
63 {
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");
76
77 const char* const SHADOW_MAP_SIZE   = "shadowMapSize";
78 const char* const ORTHOGRAPHIC_SIZE = "orthographicSize";
79 const char* const PIXEL_UNITS       = "px";
80
81 const char SLASH = '/';
82
83 void ReadModelTransform(const TreeNode* node, Quaternion& orientation, Vector3& translation, Vector3& scale)
84 {
85   float num[16u] = {.0f};
86
87   if(ReadVector(node->GetChild("matrix"), num, 16u))
88   {
89     Matrix mat(num);
90     mat.GetTransformComponents(translation, orientation, scale);
91   }
92   else
93   {
94     if(ReadVector(node->GetChild("angle"), num, 3u))
95     {
96       orientation = Quaternion(Radian(Degree(num[0u])), Radian(Degree(num[1u])), Radian(Degree(num[2u])));
97     }
98
99     if(ReadVector(node->GetChild("position"), num, 3u))
100     {
101       translation = Vector3(num);
102     }
103   }
104 }
105
106 bool ReadAttribBlob(const TreeNode* node, MeshDefinition::Blob& buffer)
107 {
108   return ReadBlob(node, buffer.mOffset, buffer.mLength);
109 }
110
111 bool ReadAttribAccessor(const TreeNode* node, MeshDefinition::Accessor& accessor)
112 {
113   return ReadBlob(node, accessor.mBlob.mOffset, accessor.mBlob.mLength);
114 }
115
116 bool ReadColorCode(const TreeNode* node, Vector4& color, DliInputParameter::ConvertColorCode convertColorCode)
117 {
118   if(!node || !convertColorCode)
119   {
120     return false;
121   }
122
123   color = convertColorCode(node->GetString());
124
125   return true;
126 }
127
128 bool ReadColorCodeOrColor(const TreeNode* node, Vector4& color, DliInputParameter::ConvertColorCode convertColorCode)
129 {
130   return ReadColorCode(node->GetChild("colorCode"), color, convertColorCode) ||
131          ReadColor(node->GetChild("color"), color);
132 }
133
134 RendererState::Type ReadRendererState(const TreeNode& tnRendererState)
135 {
136   if(tnRendererState.GetType() == TreeNode::INTEGER)
137   {
138     return static_cast<RendererState::Type>(tnRendererState.GetInteger());
139   }
140   else if(tnRendererState.GetType() == TreeNode::STRING)
141   {
142     return RendererState::Parse(tnRendererState.GetString());
143   }
144   else
145   {
146     return -1;
147   }
148 }
149
150 ///@brief Reads arc properties.
151 void ReadArcField(const TreeNode* eArc, ArcRenderable& arc)
152 {
153   ReadBool(eArc->GetChild("antiAliasing"), arc.mAntiAliasing);
154   ReadInt(eArc->GetChild("arcCaps"), arc.mArcCaps);
155   ReadFloat(eArc->GetChild("radius"), arc.mRadius);
156
157   arc.mStartAngleDegrees = .0f;
158   ReadFloat(eArc->GetChild("startAngle"), arc.mStartAngleDegrees);
159
160   arc.mEndAngleDegrees = .0f;
161   ReadFloat(eArc->GetChild("endAngle"), arc.mEndAngleDegrees);
162 }
163
164 const TreeNode* GetNthChild(const TreeNode* node, uint32_t index)
165 {
166   uint32_t i = 0;
167   for(TreeNode::ConstIterator it = (*node).CBegin(); it != (*node).CEnd(); ++it, ++i)
168   {
169     if(i == index)
170     {
171       return &((*it).second);
172     }
173   }
174   return NULL;
175 }
176
177 const TreeNode* RequireChild(const TreeNode* node, const std::string& childName)
178 {
179   auto child = node->GetChild(childName);
180   if(!child)
181   {
182     ExceptionFlinger flinger(ASSERT_LOCATION);
183     flinger << "Failed to find child node '" << childName << "'";
184     if(auto nodeName = node->GetName())
185     {
186       flinger << " on '" << nodeName << "'";
187     }
188     flinger << ".";
189   }
190   return child;
191 }
192
193 void ParseProperties(const Toolkit::TreeNode& node, Property::Array& array);
194
195 void ParseProperties(const Toolkit::TreeNode& node, Property::Map& map)
196 {
197   DALI_ASSERT_DEBUG(node.GetType() == TreeNode::OBJECT);
198   for(auto i0 = node.CBegin(), i1 = node.CEnd(); i0 != i1; ++i0)
199   {
200     auto kv = *i0;
201     switch(kv.second.GetType())
202     {
203       case TreeNode::ARRAY:
204       {
205         Property::Array array;
206         ParseProperties(kv.second, array);
207         map.Insert(kv.first, array);
208         break;
209       }
210
211       case TreeNode::OBJECT:
212       {
213         Property::Map innerMap;
214         ParseProperties(kv.second, innerMap);
215         map.Insert(kv.first, innerMap);
216         break;
217       }
218
219       case TreeNode::STRING:
220       {
221         map.Insert(kv.first, kv.second.GetString());
222         break;
223       }
224
225       case TreeNode::INTEGER:
226       {
227         map.Insert(kv.first, kv.second.GetInteger());
228         break;
229       }
230
231       case TreeNode::BOOLEAN:
232       {
233         map.Insert(kv.first, kv.second.GetBoolean());
234         break;
235       }
236
237       case TreeNode::FLOAT:
238       {
239         map.Insert(kv.first, kv.second.GetFloat());
240         break;
241       }
242
243       case TreeNode::IS_NULL:
244       {
245         break;
246       }
247     }
248   }
249 }
250
251 void ParseProperties(const Toolkit::TreeNode& node, Property::Array& array)
252 {
253   DALI_ASSERT_DEBUG(node.GetType() == TreeNode::ARRAY);
254   for(auto i0 = node.CBegin(), i1 = node.CEnd(); i0 != i1; ++i0)
255   {
256     auto kv = *i0;
257     switch(kv.second.GetType())
258     {
259       case TreeNode::ARRAY:
260       {
261         Property::Array innerArray;
262         ParseProperties(kv.second, innerArray);
263         array.PushBack(innerArray);
264         break;
265       }
266
267       case TreeNode::OBJECT:
268       {
269         Property::Map map;
270         ParseProperties(kv.second, map);
271         array.PushBack(map);
272         break;
273       }
274
275       case TreeNode::STRING:
276       {
277         array.PushBack(kv.second.GetString());
278         break;
279       }
280
281       case TreeNode::INTEGER:
282       {
283         array.PushBack(kv.second.GetInteger());
284         break;
285       }
286
287       case TreeNode::BOOLEAN:
288       {
289         array.PushBack(kv.second.GetBoolean());
290         break;
291       }
292
293       case TreeNode::FLOAT:
294       {
295         array.PushBack(kv.second.GetFloat());
296         break;
297       }
298
299       case TreeNode::IS_NULL:
300       {
301         break;
302       }
303     }
304   }
305 }
306
307 } // namespace
308
309 struct DliLoaderImpl::Impl
310 {
311   StringCallback      mOnError = DefaultErrorCallback;
312   Toolkit::JsonParser mParser;
313
314   void ParseScene(LoadParams& params);
315
316 private:
317   std::map<Index, Matrix> mInverseBindMatrices;
318
319   /**
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.
327    */
328   struct IIndexMapper
329   {
330     /**
331      * @brief Attempts to create a mapping from a node's @a dli index to its @a scene
332      *  index.
333      * @return Whether the operation was successful.
334      */
335     virtual bool Map(Index iDli, Index iScene) = 0;
336
337     /**
338      * @return The scene index for the node's @a dli index.
339      */
340     virtual Index Resolve(Index iDli) = 0;
341   };
342
343   /**
344    * @brief Traverses the DOM tree created by LoadDocument() in an attempt to create
345    *  an intermediate representation of resources and nodes.
346    */
347   void ParseSceneInternal(Index iScene, const Toolkit::TreeNode* tnScenes, const Toolkit::TreeNode* tnNodes, LoadParams& params);
348
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);
352
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);
355
356   void ParseAnimations(const Toolkit::TreeNode* animations, LoadParams& params);
357   void ParseAnimationGroups(const Toolkit::TreeNode* animationGroups, LoadParams& params);
358
359   void ParseShaders(const Toolkit::TreeNode* shaders, Dali::Scene3D::Loader::ResourceBundle& resources);
360   void ParseMeshes(const Toolkit::TreeNode* meshes, Dali::Scene3D::Loader::ResourceBundle& resources);
361
362   void GetCameraParameters(std::vector<Dali::Scene3D::Loader::CameraParameters>& cameras) const;
363   void GetLightParameters(std::vector<Dali::Scene3D::Loader::LightParameters>& lights) const;
364 };
365
366 DliLoaderImpl::DliLoaderImpl()
367 : mImpl{new Impl}
368 {
369 }
370
371 DliLoaderImpl::~DliLoaderImpl() = default;
372
373 void DliLoaderImpl::SetErrorCallback(StringCallback onError)
374 {
375   mImpl->mOnError = onError;
376 }
377
378 bool DliLoaderImpl::LoadModel(const std::string& uri, Dali::Scene3D::Loader::LoadResult& result)
379 {
380   std::string daliBuffer = LoadTextFile(uri.c_str());
381
382   auto& parser = mImpl->mParser;
383   parser       = JsonParser::New();
384   if(!parser.Parse(daliBuffer))
385   {
386     return false;
387   }
388
389   std::filesystem::path                    modelPath(uri);
390   Dali::Scene3D::Loader::DliInputParameter input;
391   LoadParams                               loadParams;
392   if(mInputParameter)
393   {
394     loadParams.input = static_cast<DliInputParameter*>(mInputParameter);
395   }
396   else
397   {
398     input.mAnimationsPath = std::string(modelPath.parent_path()) + "/";
399     loadParams.input      = &input;
400   }
401   loadParams.output = &result;
402
403   mImpl->ParseScene(loadParams);
404   return true;
405 }
406
407 std::string DliLoaderImpl::GetParseError() const
408 {
409   std::stringstream stream;
410
411   auto& parser = mImpl->mParser;
412   if(parser.ParseError())
413   {
414     stream << "position: " << parser.GetErrorPosition() << ", line: " << parser.GetErrorLineNumber() << ", column: " << parser.GetErrorColumn() << ", description: " << parser.GetErrorDescription() << ".";
415   }
416
417   return stream.str();
418 }
419
420 void DliLoaderImpl::Impl::ParseScene(LoadParams& params)
421 {
422   auto& input  = *params.input;
423   auto& output = *params.output;
424
425   // get index of root node.
426   auto docRoot = mParser.GetRoot();
427   if(docRoot)
428   {
429     // Process resources first - these are shared
430     if(auto environments = docRoot->GetChild("environment"))
431     {
432       ParseEnvironments(environments, output.mResources); // NOTE: must precede parsing of materials
433     }
434
435     if(auto meshes = docRoot->GetChild("meshes"))
436     {
437       ParseMeshes(meshes, output.mResources);
438     }
439
440     if(auto shaders = docRoot->GetChild("shaders"))
441     {
442       ParseShaders(shaders, output.mResources);
443     }
444
445     if(auto materials = docRoot->GetChild("materials"))
446     {
447       ParseMaterials(materials, input.mConvertColorCode, output.mResources);
448     }
449
450     for(auto& c : input.mPreNodeCategoryProcessors)
451     {
452       if(auto node = docRoot->GetChild(c.first))
453       {
454         Property::Array array;
455         ParseProperties(*node, array);
456         c.second(std::move(array), mOnError);
457       }
458     }
459
460     // Process scenes
461     Index iScene = 0; // default scene
462     ReadIndex(docRoot->GetChild("scene"), iScene);
463
464     auto tnScenes = RequireChild(docRoot, "scenes");
465     auto tnNodes  = RequireChild(docRoot, "nodes");
466     ParseSceneInternal(iScene, tnScenes, tnNodes, params);
467
468     ParseSkeletons(docRoot->GetChild("skeletons"), output.mScene, output.mResources);
469
470     output.mScene.EnsureUniqueSkinningShaderInstances(output.mResources);
471     output.mScene.EnsureUniqueBlendShapeShaderInstances(output.mResources);
472
473     // Ger cameras and lights
474     GetCameraParameters(output.mCameraParameters);
475     GetLightParameters(output.mLightParameters);
476
477     // Post-node processors and animations last
478     for(auto& c : input.mPostNodeCategoryProcessors)
479     {
480       if(auto node = docRoot->GetChild(c.first))
481       {
482         Property::Array array;
483         ParseProperties(*node, array);
484         c.second(std::move(array), mOnError);
485       }
486     }
487
488     if(auto animations = docRoot->GetChild("animations"))
489     {
490       ParseAnimations(animations, params);
491     }
492
493     if(!output.mAnimationDefinitions.empty())
494     {
495       if(auto animationGroups = docRoot->GetChild("animationGroups"))
496       {
497         ParseAnimationGroups(animationGroups, params);
498       }
499     }
500   }
501 }
502
503 void DliLoaderImpl::Impl::ParseSceneInternal(Index iScene, const Toolkit::TreeNode* tnScenes, const Toolkit::TreeNode* tnNodes, LoadParams& params)
504 {
505   auto getSceneRootIdx = [tnScenes, tnNodes](Index iScene) {
506     auto tn = GetNthChild(tnScenes, iScene); // now a "scene" object
507     if(!tn)
508     {
509       ExceptionFlinger(ASSERT_LOCATION) << iScene << " is out of bounds access into " << SCENES << ".";
510     }
511
512     tn = RequireChild(tn, NODES); // now a "nodes" array
513     if(tn->GetType() != TreeNode::ARRAY)
514     {
515       ExceptionFlinger(ASSERT_LOCATION) << SCENES << "[" << iScene << "]." << NODES << " has an invalid type; array required.";
516     }
517
518     if(tn->Size() < 1)
519     {
520       ExceptionFlinger(ASSERT_LOCATION) << SCENES << "[" << iScene << "]." << NODES << " must define a node id.";
521     }
522
523     tn = GetNthChild(tn, 0); // now the first element of the array
524     Index iRootNode;
525     if(!ReadIndex(tn, iRootNode))
526     {
527       ExceptionFlinger(ASSERT_LOCATION) << SCENES << "[" << iScene << "]." << NODES << " has an invalid value for root node index: '" << iRootNode << "'.";
528     }
529
530     if(iRootNode >= tnNodes->Size())
531     {
532       ExceptionFlinger(ASSERT_LOCATION) << "Root node index << " << iRootNode << " of scene " << iScene << " is out of bounds.";
533     }
534
535     tn = GetNthChild(tnNodes, iRootNode); // now a "node" object
536     if(tn->GetType() != TreeNode::OBJECT)
537     {
538       ExceptionFlinger(ASSERT_LOCATION) << "Root node of scene " << iScene << " is of invalid JSON type; object required";
539     }
540
541     return iRootNode;
542   };
543
544   Index iRootNode = getSceneRootIdx(iScene);
545   ParseNodes(tnNodes, iRootNode, params);
546
547   auto& scene = params.output->mScene;
548   scene.AddRootNode(0);
549
550   for(Index i = 0; i < iScene; ++i)
551   {
552     Index       iRootNode = getSceneRootIdx(i);
553     const Index iRoot     = scene.GetNodeCount();
554     ParseNodes(tnNodes, iRootNode, params);
555     scene.AddRootNode(iRoot);
556   }
557
558   auto numScenes = tnScenes->Size();
559   for(Index i = iScene + 1; i < numScenes; ++i)
560   {
561     Index       iRootNode = getSceneRootIdx(i);
562     const Index iRoot     = scene.GetNodeCount();
563     ParseNodes(tnNodes, iRootNode, params);
564     scene.AddRootNode(iRoot);
565   }
566 }
567
568 void DliLoaderImpl::Impl::ParseSkeletons(const TreeNode* skeletons, Dali::Scene3D::Loader::SceneDefinition& scene, Dali::Scene3D::Loader::ResourceBundle& resources)
569 {
570   if(skeletons)
571   {
572     auto iStart = skeletons->CBegin();
573     for(auto i0 = iStart, i1 = skeletons->CEnd(); i0 != i1; ++i0)
574     {
575       auto&       node = (*i0).second;
576       std::string skeletonRootName;
577       if(ReadString(node.GetChild(NODE), skeletonRootName))
578       {
579         SkeletonDefinition skeleton;
580         if(!scene.FindNode(skeletonRootName, &skeleton.mRootNodeIdx))
581         {
582           ExceptionFlinger(ASSERT_LOCATION) << FormatString("Skeleton %d: node '%s' not defined.", resources.mSkeletons.size(), skeletonRootName.c_str());
583         }
584
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();
591
592           for(auto i : node->mChildren)
593           {
594             visitFn(i);
595           }
596         };
597         visitFn(skeleton.mRootNodeIdx);
598
599         if(jointCount > Skinning::MAX_JOINTS)
600         {
601           mOnError(FormatString("Skeleton %d: joint count exceeds supported limit.", resources.mSkeletons.size()));
602           jointCount = Skinning::MAX_JOINTS;
603         }
604
605         skeleton.mJoints.reserve(jointCount);
606
607         visitFn = [&](Index id) {
608           auto iFind = ibms.find(id);
609           if(iFind != ibms.end() && skeleton.mJoints.size() < Skinning::MAX_JOINTS)
610           {
611             skeleton.mJoints.push_back({id, iFind->second});
612           }
613
614           auto node = scene.GetNode(id);
615           for(auto i : node->mChildren)
616           {
617             visitFn(i);
618           }
619         };
620         visitFn(skeleton.mRootNodeIdx);
621
622         resources.mSkeletons.push_back(std::move(skeleton));
623       }
624       else
625       {
626         ExceptionFlinger(ASSERT_LOCATION) << "skeleton " << std::distance(iStart, i0) << ": Missing required attribute '" << NODE << "'.";
627       }
628     }
629   }
630 }
631
632 void DliLoaderImpl::Impl::ParseEnvironments(const TreeNode* environments, Dali::Scene3D::Loader::ResourceBundle& resources)
633 {
634   Matrix cubeOrientation(Matrix::IDENTITY);
635
636   for(auto i0 = environments->CBegin(), i1 = environments->CEnd(); i0 != i1; ++i0)
637   {
638     auto& node = (*i0).second;
639
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))
648     {
649       envDef.mCubeOrientation = Quaternion(cubeOrientation);
650     }
651
652     resources.mEnvironmentMaps.emplace_back(std::move(envDef), EnvironmentDefinition::Textures());
653   }
654
655   // NOTE: guarantees environmentMaps to have an empty environment.
656   if(resources.mEnvironmentMaps.empty())
657   {
658     resources.mEnvironmentMaps.emplace_back(EnvironmentDefinition(), EnvironmentDefinition::Textures());
659   }
660 }
661
662 void DliLoaderImpl::Impl::ParseShaders(const TreeNode* shaders, Dali::Scene3D::Loader::ResourceBundle& resources)
663 {
664   uint32_t iShader = 0;
665   for(auto i0 = shaders->CBegin(), i1 = shaders->CEnd(); i0 != i1; ++i0, ++iShader)
666   {
667     auto&            node = (*i0).second;
668     ShaderDefinition shaderDef;
669     ReadStringVector(node.GetChild("defines"), shaderDef.mDefines);
670
671     // Read shader hints. Possible values are:
672     //                         Don't define for No hints.
673     // "OUTPUT_IS_TRANSPARENT" Might generate transparent alpha from opaque inputs.
674     //     "MODIFIES_GEOMETRY" Might change position of vertices, this option disables any culling optimizations.
675
676     ReadStringVector(node.GetChild(HINTS), shaderDef.mHints);
677
678     if(ReadString(node.GetChild("vertex"), shaderDef.mVertexShaderPath) &&
679        ReadString(node.GetChild("fragment"), shaderDef.mFragmentShaderPath))
680     {
681       ToUnixFileSeparators(shaderDef.mVertexShaderPath);
682       ToUnixFileSeparators(shaderDef.mFragmentShaderPath);
683
684       for(TreeNode::ConstIterator j0 = node.CBegin(), j1 = node.CEnd(); j0 != j1; ++j0)
685       {
686         const TreeNode::KeyNodePair& keyValue = *j0;
687         const std::string&           key      = keyValue.first;
688         const TreeNode&              value    = keyValue.second;
689
690         Property::Value uniformValue;
691         if(key.compare("vertex") == 0 || key.compare("fragment") == 0 || key.compare("defines") == 0 || key.compare(HINTS) == 0)
692         {
693           continue;
694         }
695         else if(key.compare("rendererState") == 0)
696         {
697           shaderDef.mRendererState = ReadRendererState(keyValue.second);
698         }
699         else if(value.GetType() == TreeNode::INTEGER || value.GetType() == TreeNode::FLOAT)
700         {
701           float f = 0.f;
702           ReadFloat(&value, f);
703           uniformValue = f;
704         }
705         else if(value.GetType() == TreeNode::BOOLEAN)
706         {
707           DALI_LOG_WARNING("\"bool\" uniforms are handled as floats in shader");
708           bool value = false;
709           if(ReadBool(&keyValue.second, value))
710           {
711             uniformValue = value ? 1.0f : 0.0f;
712           }
713         }
714         else
715           switch(auto size = GetNumericalArraySize(&value))
716           {
717             case 16:
718             {
719               Matrix m;
720               ReadVector(&value, m.AsFloat(), size);
721               uniformValue = m;
722               break;
723             }
724
725             case 9:
726             {
727               Matrix3 m;
728               ReadVector(&value, m.AsFloat(), size);
729               uniformValue = m;
730               break;
731             }
732
733             case 4:
734             {
735               Vector4 v;
736               ReadVector(&value, v.AsFloat(), size);
737               uniformValue = v;
738               break;
739             }
740
741             case 3:
742             {
743               Vector3 v;
744               ReadVector(&value, v.AsFloat(), size);
745               uniformValue = v;
746               break;
747             }
748
749             case 2:
750             {
751               Vector2 v;
752               ReadVector(&value, v.AsFloat(), size);
753               uniformValue = v;
754               break;
755             }
756
757             default:
758               mOnError(FormatString(
759                 "shader %u: Ignoring uniform '%s': failed to infer type from %zu elements.",
760                 iShader,
761                 key.c_str(),
762                 size));
763               break;
764           }
765
766         if(Property::NONE != uniformValue.GetType())
767         {
768           shaderDef.mUniforms.Insert(key, uniformValue);
769         }
770       }
771
772       resources.mShaders.emplace_back(std::move(shaderDef), Shader());
773     }
774     else
775     {
776       ExceptionFlinger(ASSERT_LOCATION) << "shader " << iShader << ": Missing vertex / fragment shader definition.";
777     }
778   }
779 }
780
781 void DliLoaderImpl::Impl::ParseMeshes(const TreeNode* meshes, Dali::Scene3D::Loader::ResourceBundle& resources)
782 {
783   for(auto i0 = meshes->CBegin(), i1 = meshes->CEnd(); i0 != i1; ++i0)
784   {
785     auto& node = (*i0).second;
786
787     MeshDefinition meshDef;
788     if(!ReadString(node.GetChild(URI), meshDef.mUri))
789     {
790       ExceptionFlinger(ASSERT_LOCATION) << "mesh " << resources.mMeshes.size() << ": Missing required attribute '" << URI << "'.";
791     }
792
793     ToUnixFileSeparators(meshDef.mUri);
794
795     std::string primitive;
796     if(ReadString(node.GetChild("primitive"), primitive))
797     {
798       if(primitive == "LINES")
799       {
800         meshDef.mPrimitiveType = Geometry::LINES;
801       }
802       else if(primitive == "POINTS")
803       {
804         meshDef.mPrimitiveType = Geometry::POINTS;
805       }
806       else if(primitive != "TRIANGLES")
807       {
808         mOnError(FormatString(
809           "mesh %d: Using TRIANGLES instead of unsupported primitive type '%s'.",
810           resources.mMeshes.size(),
811           primitive.c_str()));
812       }
813     }
814
815     int attributes;
816     if(ReadInt(node.GetChild("attributes"), attributes))
817     {
818       if(MaskMatch(attributes, MeshDefinition::INDICES) &&
819          !ReadAttribAccessor(node.GetChild("indices"), meshDef.mIndices))
820       {
821         ExceptionFlinger(ASSERT_LOCATION) << FormatString("mesh %d: Failed to read %s.",
822                                                           resources.mMeshes.size(),
823                                                           "indices");
824       }
825
826       if(MaskMatch(attributes, MeshDefinition::POSITIONS) &&
827          !ReadAttribAccessor(node.GetChild("positions"), meshDef.mPositions))
828       {
829         ExceptionFlinger(ASSERT_LOCATION) << FormatString("mesh %d: Failed to read %s.",
830                                                           resources.mMeshes.size(),
831                                                           "positions");
832       }
833
834       if(MaskMatch(attributes, MeshDefinition::NORMALS) &&
835          !ReadAttribAccessor(node.GetChild("normals"), meshDef.mNormals))
836       {
837         mOnError(FormatString("mesh %d: Failed to read %s.", resources.mMeshes.size(), "normals"));
838       }
839
840       if(MaskMatch(attributes, MeshDefinition::TEX_COORDS) &&
841          !ReadAttribAccessor(node.GetChild("textures"), meshDef.mTexCoords))
842       {
843         mOnError(FormatString("mesh %d: Failed to read %s.", resources.mMeshes.size(), "textures"));
844       }
845
846       if(MaskMatch(attributes, MeshDefinition::TANGENTS) &&
847          !ReadAttribAccessor(node.GetChild("tangents"), meshDef.mTangents))
848       {
849         mOnError(FormatString("mesh %d: Failed to read %s.", resources.mMeshes.size(), "tangents"));
850       }
851
852       // NOTE: we're no longer reading bitangents as these are calculated in the shaders.
853       if(ReadIndex(node.GetChild("skeleton"), meshDef.mSkeletonIdx))
854       {
855         if(!MaskMatch(attributes, MeshDefinition::JOINTS_0) &&
856            !MaskMatch(attributes, MeshDefinition::WEIGHTS_0))
857         {
858           mOnError(FormatString("mesh %d: Expected joints0 / weights0 attribute(s) missing.",
859                                 resources.mMeshes.size()));
860         }
861         else if(!ReadAttribAccessor(node.GetChild("joints0"), meshDef.mJoints0) ||
862                 !ReadAttribAccessor(node.GetChild("weights0"), meshDef.mWeights0))
863         {
864           mOnError(FormatString("mesh %d: Failed to read skinning information.",
865                                 resources.mMeshes.size()));
866         }
867       }
868
869       if(auto blendshapeHeader = node.GetChild(BLEND_SHAPE_HEADER))
870       {
871         std::string blendShapeVersion;
872         ReadString(blendshapeHeader->GetChild(VERSION), blendShapeVersion);
873
874         if(0u == blendShapeVersion.compare(BLEND_SHAPE_VERSION_1_0))
875         {
876           meshDef.mBlendShapeVersion = BlendShapes::Version::VERSION_1_0;
877         }
878         else if(0u == blendShapeVersion.compare(BLEND_SHAPE_VERSION_2_0))
879         {
880           meshDef.mBlendShapeVersion = BlendShapes::Version::VERSION_2_0;
881         }
882
883         switch(meshDef.mBlendShapeVersion)
884         {
885           case BlendShapes::Version::VERSION_1_0:
886           case BlendShapes::Version::VERSION_2_0: // FALL THROUGH
887           {
888             ReadAttribBlob(blendshapeHeader, meshDef.mBlendShapeHeader);
889             break;
890           }
891           default:
892           {
893             // nothing to do
894             break;
895           }
896         }
897       }
898
899       if(auto blendShapes = node.GetChild(BLEND_SHAPES))
900       {
901         meshDef.mBlendShapes.resize(blendShapes->Size());
902
903         auto index = 0u;
904         for(auto it = blendShapes->CBegin(), endIt = blendShapes->CEnd(); it != endIt; ++it, ++index)
905         {
906           // Each blend shape is stored as the difference with the original mesh.
907
908           auto& blendShapeNode = (*it).second;
909
910           auto& blendShape = meshDef.mBlendShapes[index];
911           ReadString(blendShapeNode.GetChild("name"), blendShape.name);
912           if(auto position = blendShapeNode.GetChild("positions"))
913           {
914             ReadAttribAccessor(position, blendShape.deltas);
915           }
916           if(auto normals = blendShapeNode.GetChild("normals"))
917           {
918             ReadAttribAccessor(normals, blendShape.normals);
919           }
920           if(auto tangents = blendShapeNode.GetChild("tangents"))
921           {
922             ReadAttribAccessor(tangents, blendShape.tangents);
923           }
924           ReadFloat(blendShapeNode.GetChild("weight"), blendShape.weight);
925         }
926       }
927
928       bool flipV;
929       if(ReadBool(node.GetChild("flipV"), flipV))
930       {
931         meshDef.mFlags |= flipV * MeshDefinition::FLIP_UVS_VERTICAL;
932       }
933
934       resources.mMeshes.emplace_back(std::move(meshDef), MeshGeometry());
935     }
936   }
937 }
938
939 void DliLoaderImpl::Impl::ParseMaterials(const TreeNode* materials, DliInputParameter::ConvertColorCode convertColorCode, Dali::Scene3D::Loader::ResourceBundle& resources)
940 {
941   for(auto i0 = materials->CBegin(), i1 = materials->CEnd(); i0 != i1; ++i0)
942   {
943     auto& node = (*i0).second;
944
945     MaterialDefinition materialDef;
946     if(auto eEnvironment = node.GetChild("environment"))
947     {
948       ReadIndex(eEnvironment, materialDef.mEnvironmentIdx);
949       if(static_cast<unsigned int>(materialDef.mEnvironmentIdx) >= resources.mEnvironmentMaps.size())
950       {
951         ExceptionFlinger(ASSERT_LOCATION) << "material " << resources.mMaterials.size() << ": Environment index " << materialDef.mEnvironmentIdx << " out of bounds (" << resources.mEnvironmentMaps.size() << ").";
952       }
953     }
954
955     // TODO : need to consider AGIF
956     std::vector<std::string> texturePaths;
957     std::string              texturePath;
958     if(ReadString(node.GetChild("albedoMap"), texturePath))
959     {
960       ToUnixFileSeparators(texturePath);
961       const auto semantic = MaterialDefinition::ALBEDO;
962       materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
963       materialDef.mFlags |= semantic | MaterialDefinition::TRANSPARENCY; // NOTE: only in dli does single / separate ALBEDO texture mean TRANSPARENCY.
964     }
965     if(ReadString(node.GetChild("albedoMetallicMap"), texturePath))
966     {
967       ToUnixFileSeparators(texturePath);
968
969       if(MaskMatch(materialDef.mFlags, MaterialDefinition::ALBEDO))
970       {
971         mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "albedo"));
972       }
973
974       const auto semantic = MaterialDefinition::ALBEDO | MaterialDefinition::METALLIC;
975       materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
976       materialDef.mFlags |= semantic;
977     }
978
979     if(ReadString(node.GetChild("metallicRoughnessMap"), texturePath))
980     {
981       ToUnixFileSeparators(texturePath);
982
983       if(MaskMatch(materialDef.mFlags, MaterialDefinition::METALLIC))
984       {
985         mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "metallic"));
986       }
987
988       const auto semantic = MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS;
989       materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
990       materialDef.mFlags |= semantic |
991                             // We have a metallic-roughhness map and the first texture did not have albedo semantics - we're in the transparency workflow.
992                             (MaskMatch(materialDef.mFlags, MaterialDefinition::ALBEDO) * MaterialDefinition::TRANSPARENCY);
993     }
994
995     if(ReadString(node.GetChild("normalMap"), texturePath))
996     {
997       ToUnixFileSeparators(texturePath);
998
999       const auto semantic = MaterialDefinition::NORMAL;
1000       materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
1001       materialDef.mFlags |= semantic |
1002                             // We have a standalone normal map and the first texture did not have albedo semantics - we're in the transparency workflow.
1003                             (MaskMatch(materialDef.mFlags, MaterialDefinition::ALBEDO) * MaterialDefinition::TRANSPARENCY);
1004     }
1005
1006     if(ReadString(node.GetChild("normalRoughnessMap"), texturePath))
1007     {
1008       ToUnixFileSeparators(texturePath);
1009
1010       if(MaskMatch(materialDef.mFlags, MaterialDefinition::NORMAL))
1011       {
1012         mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "normal"));
1013       }
1014
1015       if(MaskMatch(materialDef.mFlags, MaterialDefinition::ROUGHNESS))
1016       {
1017         mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "roughness"));
1018       }
1019
1020       if(MaskMatch(materialDef.mFlags, MaterialDefinition::TRANSPARENCY))
1021       {
1022         mOnError(FormatString("material %d: conflicting semantics; already set %s.", resources.mMaterials.size(), "transparency"));
1023       }
1024
1025       const auto semantic = MaterialDefinition::NORMAL | MaterialDefinition::ROUGHNESS;
1026       materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
1027       materialDef.mFlags |= semantic;
1028     }
1029
1030     if(ReadString(node.GetChild("subsurfaceMap"), texturePath))
1031     {
1032       ToUnixFileSeparators(texturePath);
1033
1034       const auto semantic = MaterialDefinition::SUBSURFACE;
1035       materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
1036       materialDef.mFlags |= semantic;
1037     }
1038
1039     if(ReadString(node.GetChild("occlusionMap"), texturePath))
1040     {
1041       ToUnixFileSeparators(texturePath);
1042       const auto semantic = MaterialDefinition::OCCLUSION;
1043       materialDef.mTextureStages.push_back({semantic, TextureDefinition{std::move(texturePath)}});
1044       materialDef.mFlags |= semantic;
1045     }
1046
1047     if(ReadColorCodeOrColor(&node, materialDef.mColor, convertColorCode) &&
1048        materialDef.mColor.a < 1.0f)
1049     {
1050       materialDef.mFlags |= MaterialDefinition::TRANSPARENCY;
1051     }
1052
1053     ReadFloat(node.GetChild("metallic"), materialDef.mMetallic);
1054     ReadFloat(node.GetChild("roughness"), materialDef.mRoughness);
1055
1056     bool mipmaps;
1057     if(ReadBool(node.GetChild("mipmap"), mipmaps) && mipmaps)
1058     {
1059       for(auto& ts : materialDef.mTextureStages)
1060       {
1061         ts.mTexture.mSamplerFlags |= SamplerFlags::FILTER_MIPMAP_LINEAR;
1062       }
1063     }
1064
1065     resources.mMaterials.emplace_back(std::move(materialDef), TextureSet());
1066   }
1067 }
1068
1069 void DliLoaderImpl::Impl::ParseNodes(const TreeNode* const nodes, Index index, LoadParams& params)
1070 {
1071   std::vector<Index> parents;
1072   parents.reserve(8);
1073
1074   struct IndexMapper : IIndexMapper
1075   {
1076     IndexMapper(size_t numNodes)
1077     {
1078       mIndices.reserve(numNodes);
1079     }
1080
1081     virtual bool Map(Index iDli, Index iScene) override
1082     {
1083       Entry idx{iDli, iScene};
1084       auto  iInsert = std::lower_bound(mIndices.begin(), mIndices.end(), idx);
1085       if(iInsert == mIndices.end() || iInsert->iDli != iDli)
1086       {
1087         mIndices.insert(iInsert, idx);
1088       }
1089       else if(iInsert->iScene != iScene)
1090       {
1091         return false;
1092       }
1093       return true;
1094     }
1095
1096     virtual unsigned int Resolve(Index iDli) override
1097     {
1098       auto iFind = std::lower_bound(mIndices.begin(), mIndices.end(), iDli, [](const Entry& idx, Index iDli) { return idx.iDli < iDli; });
1099       DALI_ASSERT_ALWAYS(iFind != mIndices.end());
1100       return iFind->iScene;
1101     }
1102
1103   private:
1104     struct Entry
1105     {
1106       unsigned int iDli;
1107       unsigned int iScene;
1108
1109       bool operator<(const Entry& other) const
1110       {
1111         return iDli < other.iDli;
1112       }
1113     };
1114     std::vector<Entry> mIndices;
1115   } mapper(nodes->Size());
1116   ParseNodesInternal(nodes, index, parents, params, mapper);
1117
1118   auto& scene = params.output->mScene;
1119   for(size_t i0 = 0, i1 = scene.GetNodeCount(); i0 < i1; ++i0)
1120   {
1121     for(auto& c : scene.GetNode(i0)->mConstraints)
1122     {
1123       c.mSourceIdx = mapper.Resolve(c.mSourceIdx);
1124     }
1125   }
1126 }
1127
1128 void DliLoaderImpl::Impl::ParseNodesInternal(const TreeNode* const nodes, Index index, std::vector<Index>& inOutParentStack, LoadParams& params, IIndexMapper& mapper)
1129 {
1130   // Properties that may be resolved from a JSON value with ReadInt() -- or default to 0.
1131   struct IndexProperty
1132   {
1133     ResourceType::Value type;
1134     const TreeNode*     source;
1135     Index&              target;
1136   };
1137   std::vector<IndexProperty> resourceIds;
1138   resourceIds.reserve(4);
1139
1140   if(auto node = GetNthChild(nodes, index))
1141   {
1142     Dali::Scene3D::Loader::NodeDefinition nodeDef;
1143     nodeDef.mParentIdx = inOutParentStack.empty() ? INVALID_INDEX : inOutParentStack.back();
1144
1145     // name
1146     ReadString(node->GetChild(NAME), nodeDef.mName);
1147
1148     // transform
1149     ReadModelTransform(node, nodeDef.mOrientation, nodeDef.mPosition, nodeDef.mScale);
1150
1151     // Reads the size of the node.
1152     //
1153     // * It can be given as 'size' or 'bounds'.
1154     // * The sdk saves the 'size' as a vector2 in some cases.
1155     // * To avoid size related issues the following code attemps
1156     //   to read the 'size/bounds' as a vector3 first, if it's
1157     //   not successful then reads it as a vector2.
1158     ReadVector(node->GetChild("size"), nodeDef.mSize.AsFloat(), 3) ||
1159       ReadVector(node->GetChild("size"), nodeDef.mSize.AsFloat(), 2) ||
1160       ReadVector(node->GetChild("bounds"), nodeDef.mSize.AsFloat(), 3) ||
1161       ReadVector(node->GetChild("bounds"), nodeDef.mSize.AsFloat(), 2);
1162
1163     // visibility
1164     ReadBool(node->GetChild("visible"), nodeDef.mIsVisible);
1165
1166     // type classification
1167     if(auto eCustomization = node->GetChild("customization")) // customization
1168     {
1169       std::string tag;
1170       if(ReadString(eCustomization->GetChild("tag"), tag))
1171       {
1172         nodeDef.mCustomization.reset(new Dali::Scene3D::Loader::NodeDefinition::CustomizationDefinition{tag});
1173       }
1174     }
1175     else // something renderable maybe
1176     {
1177       std::unique_ptr<Dali::Scene3D::Loader::NodeDefinition::Renderable> renderable;
1178       ModelRenderable*                                                   modelRenderable = nullptr; // no ownership, aliasing renderable for the right type.
1179
1180       const TreeNode* eRenderable = nullptr;
1181       if((eRenderable = node->GetChild("model")))
1182       {
1183         // check for mesh before allocating - this can't be missing.
1184         auto eMesh = eRenderable->GetChild("mesh");
1185         if(!eMesh)
1186         {
1187           ExceptionFlinger(ASSERT_LOCATION) << "node " << nodeDef.mName << ": Missing mesh definition.";
1188         }
1189
1190         modelRenderable = new ModelRenderable();
1191         renderable.reset(modelRenderable);
1192
1193         resourceIds.push_back({ResourceType::Mesh, eMesh, modelRenderable->mMeshIdx});
1194       }
1195       else if((eRenderable = node->GetChild("arc")))
1196       {
1197         // check for mesh before allocating - this can't be missing.
1198         auto eMesh = eRenderable->GetChild("mesh");
1199         if(!eMesh)
1200         {
1201           ExceptionFlinger(ASSERT_LOCATION) << "node " << nodeDef.mName << ": Missing mesh definition.";
1202         }
1203
1204         auto arcRenderable = new ArcRenderable;
1205         renderable.reset(arcRenderable);
1206         modelRenderable = arcRenderable;
1207
1208         resourceIds.push_back({ResourceType::Mesh, eMesh, arcRenderable->mMeshIdx});
1209
1210         ReadArcField(eRenderable, *arcRenderable);
1211       }
1212
1213       if(renderable && eRenderable != nullptr) // process common properties of all renderables + register payload
1214       {
1215         // shader
1216         renderable->mShaderIdx = 0;
1217         auto eShader           = eRenderable->GetChild("shader");
1218         if(eShader)
1219         {
1220           resourceIds.push_back({ResourceType::Shader, eShader, renderable->mShaderIdx});
1221         }
1222
1223         // color
1224         if(modelRenderable)
1225         {
1226           modelRenderable->mMaterialIdx = 0; // must offer default of 0
1227           auto eMaterial                = eRenderable->GetChild("material");
1228           if(eMaterial)
1229           {
1230             resourceIds.push_back({ResourceType::Material, eMaterial, modelRenderable->mMaterialIdx});
1231           }
1232
1233           if(!ReadColorCodeOrColor(eRenderable, modelRenderable->mColor, params.input->mConvertColorCode))
1234           {
1235             ReadColorCodeOrColor(node, modelRenderable->mColor, params.input->mConvertColorCode);
1236           }
1237         }
1238
1239         nodeDef.mRenderables.push_back(std::move(renderable));
1240       }
1241     }
1242
1243     // Resolve ints - default to 0 if undefined
1244     auto& output = params.output;
1245     for(auto& idRes : resourceIds)
1246     {
1247       Index iCheck = 0;
1248       switch(idRes.type)
1249       {
1250         case ResourceType::Shader:
1251           iCheck = output->mResources.mShaders.size();
1252           break;
1253
1254         case ResourceType::Mesh:
1255           iCheck = output->mResources.mMeshes.size();
1256           break;
1257
1258         case ResourceType::Material:
1259           iCheck = output->mResources.mMaterials.size();
1260           break;
1261
1262         default:
1263           ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ": Invalid resource type: " << idRes.type << " (Programmer error)";
1264       }
1265
1266       if(!idRes.source)
1267       {
1268         idRes.target = 0;
1269       }
1270       else if(idRes.source->GetType() != TreeNode::INTEGER)
1271       {
1272         ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ": Invalid " << GetResourceTypeName(idRes.type) << " index type.";
1273       }
1274       else
1275       {
1276         idRes.target = idRes.source->GetInteger();
1277       }
1278
1279       if(idRes.target >= iCheck)
1280       {
1281         ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ": " << GetResourceTypeName(idRes.type) << " index " << idRes.target << " out of bounds (" << iCheck << ").";
1282       }
1283     }
1284     resourceIds.clear();
1285
1286     // Extra properties
1287     if(auto eExtras = node->GetChild("extras"))
1288     {
1289       auto& extras = nodeDef.mExtras;
1290       extras.reserve(eExtras->Size());
1291
1292       for(auto i0 = eExtras->CBegin(), i1 = eExtras->CEnd(); i0 != i1; ++i0)
1293       {
1294         Dali::Scene3D::Loader::NodeDefinition::Extra e;
1295
1296         auto eExtra = *i0;
1297         e.mKey      = eExtra.first;
1298         if(e.mKey.empty())
1299         {
1300           mOnError(FormatString("node %d: empty string is invalid for name of extra %d; ignored.",
1301                                 index,
1302                                 extras.size()));
1303           continue;
1304         }
1305
1306         e.mValue = ReadPropertyValue(eExtra.second);
1307         if(e.mValue.GetType() == Property::Type::NONE)
1308         {
1309           mOnError(FormatString("node %d: failed to interpret value of extra '%s' : %s; ignored.",
1310                                 index,
1311                                 e.mKey.c_str(),
1312                                 eExtra.second.GetString()));
1313         }
1314         else
1315         {
1316           auto iInsert = std::lower_bound(extras.begin(), extras.end(), e);
1317           if(iInsert != extras.end() && iInsert->mKey == e.mKey)
1318           {
1319             mOnError(FormatString("node %d: extra '%s' already defined; overriding with %s.",
1320                                   index,
1321                                   e.mKey.c_str(),
1322                                   eExtra.second.GetString()));
1323             *iInsert = std::move(e);
1324           }
1325           else
1326           {
1327             extras.insert(iInsert, e);
1328           }
1329         }
1330       }
1331     }
1332
1333     // Constraints
1334     if(auto eConstraints = node->GetChild("constraints"))
1335     {
1336       auto& constraints = nodeDef.mConstraints;
1337       constraints.reserve(eConstraints->Size());
1338
1339       ConstraintDefinition cDef;
1340       for(auto i0 = eConstraints->CBegin(), i1 = eConstraints->CEnd(); i0 != i1; ++i0)
1341       {
1342         auto eConstraint = *i0;
1343         if(!ReadIndex(&eConstraint.second, cDef.mSourceIdx))
1344         {
1345           mOnError(FormatString("node %d: node ID %s for constraint %d is invalid; ignored.",
1346                                 index,
1347                                 eConstraint.second.GetString(),
1348                                 constraints.size()));
1349         }
1350         else
1351         {
1352           cDef.mProperty = eConstraint.first;
1353
1354           auto iInsert = std::lower_bound(constraints.begin(), constraints.end(), cDef);
1355           if(iInsert != constraints.end() && *iInsert == cDef)
1356           {
1357             mOnError(FormatString("node %d: constraint %s@%d already defined; ignoring.",
1358                                   index,
1359                                   cDef.mProperty.c_str(),
1360                                   cDef.mSourceIdx));
1361           }
1362           else
1363           {
1364             constraints.insert(iInsert, cDef);
1365           }
1366         }
1367       }
1368     }
1369
1370     // Determine index for mapping
1371     const unsigned int myIndex = output->mScene.GetNodeCount();
1372     if(!mapper.Map(index, myIndex))
1373     {
1374       mOnError(FormatString("node %d: error mapping dli index %d: node has multiple parents. Ignoring subtree.", index, myIndex));
1375       return;
1376     }
1377
1378     // if the node is a bone in a skeletal animation, it will have the inverse bind pose matrix.
1379     Matrix invBindMatrix{false};
1380     if(ReadVector(node->GetChild("inverseBindPoseMatrix"), invBindMatrix.AsFloat(), 16u)) // TODO: more robust error checking?
1381     {
1382       mInverseBindMatrices[myIndex] = invBindMatrix;
1383     }
1384
1385     // Register nodeDef
1386     auto rawDef = output->mScene.AddNode(std::make_unique<Dali::Scene3D::Loader::NodeDefinition>(std::move(nodeDef)));
1387     if(rawDef) // NOTE: no ownership. Guaranteed to stay in scope.
1388     {
1389       // ...And only then parse children.
1390       if(auto children = node->GetChild("children"))
1391       {
1392         inOutParentStack.push_back(myIndex);
1393
1394         rawDef->mChildren.reserve(children->Size());
1395
1396         uint32_t iChild = 0;
1397         for(auto j0 = children->CBegin(), j1 = children->CEnd(); j0 != j1; ++j0, ++iChild)
1398         {
1399           auto& child = (*j0).second;
1400           if(child.GetType() == TreeNode::INTEGER)
1401           {
1402             ParseNodesInternal(nodes, child.GetInteger(), inOutParentStack, params, mapper); // child object is created in scene definition.
1403           }
1404           else
1405           {
1406             ExceptionFlinger(ASSERT_LOCATION) << "node " << index << ", child " << iChild << ": invalid index type.";
1407           }
1408         }
1409
1410         inOutParentStack.pop_back();
1411       }
1412       else if(rawDef->mCustomization)
1413       {
1414         mOnError(FormatString("node %d: not an actual customization without children.", index));
1415       }
1416
1417       if(auto proc = params.input->mNodePropertyProcessor) // optional processing
1418       {
1419         // WARNING: constraint IDs are not resolved at this point.
1420         Property::Map nodeData;
1421         ParseProperties(*node, nodeData);
1422         proc(*rawDef, std::move(nodeData), mOnError);
1423       }
1424     }
1425     else
1426     {
1427       ExceptionFlinger(ASSERT_LOCATION) << "Node " << index << ": name already used.";
1428     }
1429   }
1430 }
1431
1432 void DliLoaderImpl::Impl::ParseAnimations(const TreeNode* tnAnimations, LoadParams& params)
1433 {
1434   auto& definitions = params.output->mAnimationDefinitions;
1435   definitions.reserve(definitions.size() + tnAnimations->Size());
1436
1437   for(TreeNode::ConstIterator iAnim = tnAnimations->CBegin(), iAnimEnd = tnAnimations->CEnd();
1438       iAnim != iAnimEnd;
1439       ++iAnim)
1440   {
1441     const TreeNode&     tnAnim = (*iAnim).second;
1442     AnimationDefinition animDef;
1443     ReadString(tnAnim.GetChild(NAME), animDef.mName);
1444
1445     auto       iFind     = std::lower_bound(definitions.begin(), definitions.end(), animDef, [](const AnimationDefinition& ad0, const AnimationDefinition& ad1) { return ad0.mName < ad1.mName; });
1446     const bool overwrite = iFind != definitions.end() && iFind->mName == animDef.mName;
1447     if(overwrite)
1448     {
1449       mOnError(FormatString("Pre-existing animation with name '%s' is being overwritten.", animDef.mName.c_str()));
1450     }
1451
1452     // Duration -- We need something that animated properties' delay / duration can
1453     // be expressed as a multiple of; 0 won't work. This is small enough (i.e. shorter
1454     // than our frame delay) to not be restrictive WRT replaying. If anything needs
1455     // to occur more frequently, then Animations are likely not your solution anyway.
1456     animDef.mDuration = AnimationDefinition::MIN_DURATION_SECONDS;
1457     if(!ReadFloat(tnAnim.GetChild("duration"), animDef.mDuration))
1458     {
1459       mOnError(FormatString("Animation '%s' fails to define '%s', defaulting to %f.",
1460                             animDef.mName.c_str(),
1461                             "duration",
1462                             animDef.mDuration));
1463     }
1464
1465     // Get loop count - # of playbacks. Default is once. 0 means repeat indefinitely.
1466     animDef.mLoopCount = 1;
1467     if(ReadInt(tnAnim.GetChild("loopCount"), animDef.mLoopCount) &&
1468        animDef.mLoopCount < 0)
1469     {
1470       animDef.mLoopCount = 0;
1471     }
1472
1473     std::string endAction;
1474     if(ReadString(tnAnim.GetChild("endAction"), endAction))
1475     {
1476       if("BAKE" == endAction)
1477       {
1478         animDef.mEndAction = Animation::BAKE;
1479       }
1480       else if("DISCARD" == endAction)
1481       {
1482         animDef.mEndAction = Animation::DISCARD;
1483       }
1484       else if("BAKE_FINAL" == endAction)
1485       {
1486         animDef.mEndAction = Animation::BAKE_FINAL;
1487       }
1488     }
1489
1490     if(ReadString(tnAnim.GetChild("disconnectAction"), endAction))
1491     {
1492       if("BAKE" == endAction)
1493       {
1494         animDef.mDisconnectAction = Animation::BAKE;
1495       }
1496       else if("DISCARD" == endAction)
1497       {
1498         animDef.mDisconnectAction = Animation::DISCARD;
1499       }
1500       else if("BAKE_FINAL" == endAction)
1501       {
1502         animDef.mDisconnectAction = Animation::BAKE_FINAL;
1503       }
1504     }
1505
1506     if(const TreeNode* tnProperties = tnAnim.GetChild("properties"))
1507     {
1508       animDef.mProperties.reserve(tnProperties->Size());
1509       for(TreeNode::ConstIterator iProperty = tnProperties->CBegin(), iPropertyEnd = tnProperties->CEnd();
1510           iProperty != iPropertyEnd;
1511           ++iProperty)
1512       {
1513         const TreeNode& tnProperty = (*iProperty).second;
1514
1515         AnimatedProperty animProp;
1516         if(!ReadString(tnProperty.GetChild("node"), animProp.mNodeName))
1517         {
1518           mOnError(FormatString("Animation '%s': Failed to read the 'node' tag.", animDef.mName.c_str()));
1519           continue;
1520         }
1521
1522         if(!ReadString(tnProperty.GetChild("property"), animProp.mPropertyName))
1523         {
1524           mOnError(FormatString("Animation '%s': Failed to read the 'property' tag", animDef.mName.c_str()));
1525           continue;
1526         }
1527
1528         // these are the defaults
1529         animProp.mTimePeriod.delaySeconds    = 0.f;
1530         animProp.mTimePeriod.durationSeconds = animDef.mDuration;
1531         if(!ReadTimePeriod(tnProperty.GetChild("timePeriod"), animProp.mTimePeriod))
1532         {
1533           mOnError(FormatString("Animation '%s': timePeriod missing in Property #%d: defaulting to %f.",
1534                                 animDef.mName.c_str(),
1535                                 animDef.mProperties.size(),
1536                                 animProp.mTimePeriod.durationSeconds));
1537         }
1538
1539         std::string alphaFunctionValue;
1540         if(ReadString(tnProperty.GetChild("alphaFunction"), alphaFunctionValue))
1541         {
1542           animProp.mAlphaFunction = GetAlphaFunction(alphaFunctionValue);
1543         }
1544
1545         if(const TreeNode* tnKeyFramesBin = tnProperty.GetChild("keyFramesBin"))
1546         {
1547           DALI_ASSERT_ALWAYS(!animProp.mPropertyName.empty() && "Animation must specify a property name");
1548
1549           std::ifstream binAniFile;
1550           std::string   animationFilename;
1551           if(ReadString(tnKeyFramesBin->GetChild(URL), animationFilename))
1552           {
1553             std::string animationFullPath = params.input->mAnimationsPath + animationFilename;
1554             binAniFile.open(animationFullPath, std::ios::binary);
1555             if(binAniFile.fail())
1556             {
1557               ExceptionFlinger(ASSERT_LOCATION) << "Failed to open animation data '" << animationFullPath << "'";
1558             }
1559           }
1560
1561           int byteOffset = 0;
1562           ReadInt(tnKeyFramesBin->GetChild("byteOffset"), byteOffset);
1563           DALI_ASSERT_ALWAYS(byteOffset >= 0);
1564
1565           binAniFile.seekg(byteOffset, std::ios::beg);
1566
1567           int numKeys = 0;
1568           ReadInt(tnKeyFramesBin->GetChild("numKeys"), numKeys);
1569           DALI_ASSERT_ALWAYS(numKeys >= 0);
1570
1571           animProp.mKeyFrames = KeyFrames::New();
1572
1573           // In binary animation file only is saved the position, rotation, scale and blend shape weight keys.
1574           // so, if it is vector3 we assume is position or scale keys, if it is vector4 we assume is rotation,
1575           //  otherwise are blend shape weight keys.
1576           //  TODO support for binary header with size information
1577           Property::Type propType = Property::FLOAT; // assume blend shape weights
1578           if(animProp.mPropertyName == "orientation")
1579           {
1580             propType = Property::VECTOR4;
1581           }
1582           else if((animProp.mPropertyName == "position") || (animProp.mPropertyName == "scale"))
1583           {
1584             propType = Property::VECTOR3;
1585           }
1586
1587           // alphafunction is reserved for future implementation
1588           //  NOTE: right now we're just using AlphaFunction::LINEAR.
1589           unsigned char dummyAlphaFunction;
1590
1591           float           progress;
1592           Property::Value propValue;
1593           for(int key = 0; key < numKeys; key++)
1594           {
1595             binAniFile.read(reinterpret_cast<char*>(&progress), sizeof(float));
1596             if(propType == Property::VECTOR3)
1597             {
1598               Vector3 value;
1599               binAniFile.read(reinterpret_cast<char*>(value.AsFloat()), sizeof(float) * 3);
1600               propValue = Property::Value(value);
1601             }
1602             else if(propType == Property::VECTOR4)
1603             {
1604               Vector4 value;
1605               binAniFile.read(reinterpret_cast<char*>(value.AsFloat()), sizeof(float) * 4);
1606               propValue = Property::Value(Quaternion(value));
1607             }
1608             else
1609             {
1610               float value;
1611               binAniFile.read(reinterpret_cast<char*>(&value), sizeof(float));
1612               propValue = Property::Value(value);
1613             }
1614
1615             binAniFile.read(reinterpret_cast<char*>(&dummyAlphaFunction), sizeof(unsigned char));
1616
1617             animProp.mKeyFrames.Add(progress, propValue, AlphaFunction::LINEAR);
1618           }
1619         }
1620         else if(const TreeNode* tnKeyFrames = tnProperty.GetChild("keyFrames"))
1621         {
1622           DALI_ASSERT_ALWAYS(!animProp.mPropertyName.empty() && "Animation must specify a property name");
1623           animProp.mKeyFrames = KeyFrames::New();
1624
1625           float progress = 0.0f;
1626           for(auto i0 = tnKeyFrames->CBegin(), i1 = tnKeyFrames->CEnd(); i1 != i0; ++i0)
1627           {
1628             const TreeNode::KeyNodePair& kfKeyChild = *i0;
1629             bool                         readResult = ReadFloat(kfKeyChild.second.GetChild("progress"), progress);
1630             DALI_ASSERT_ALWAYS(readResult && "Key frame entry must have 'progress'");
1631
1632             const TreeNode* tnValue = kfKeyChild.second.GetChild("value");
1633             DALI_ASSERT_ALWAYS(tnValue && "Key frame entry must have 'value'");
1634
1635             // For the "orientation" property, convert from Vector4 -> Rotation value
1636             // This work-around is preferable to a null-pointer exception in the DALi update thread
1637             Property::Value propValue(ReadPropertyValue(*tnValue));
1638             if(propValue.GetType() == Property::VECTOR4 &&
1639                animProp.mPropertyName == "orientation")
1640             {
1641               Vector4 v;
1642               propValue.Get(v);
1643               propValue = Property::Value(Quaternion(v.w, v.x, v.y, v.z));
1644             }
1645
1646             AlphaFunction kfAlphaFunction(AlphaFunction::DEFAULT);
1647             std::string   alphaFuncStr;
1648             if(ReadString(kfKeyChild.second.GetChild("alphaFunction"), alphaFuncStr))
1649             {
1650               kfAlphaFunction = GetAlphaFunction(alphaFuncStr);
1651             }
1652
1653             animProp.mKeyFrames.Add(progress, propValue, kfAlphaFunction);
1654           }
1655         }
1656         else
1657         {
1658           const TreeNode* tnValue = tnProperty.GetChild("value");
1659           if(tnValue)
1660           {
1661             animProp.mValue.reset(new AnimatedProperty::Value{ReadPropertyValue(*tnValue)});
1662             ReadBool(tnProperty.GetChild("relative"), animProp.mValue->mIsRelative);
1663           }
1664           else
1665           {
1666             mOnError(FormatString("Property '%s' fails to define target value.",
1667                                   animProp.mPropertyName.c_str()));
1668           }
1669         }
1670
1671         animDef.mProperties.push_back(std::move(animProp));
1672       }
1673     }
1674
1675     if(auto proc = params.input->mAnimationPropertyProcessor) // optional processing
1676     {
1677       Property::Map map;
1678       ParseProperties(tnAnim, map);
1679       proc(animDef, std::move(map), mOnError);
1680     }
1681
1682     if(overwrite)
1683     {
1684       *iFind = std::move(animDef);
1685     }
1686     else
1687     {
1688       iFind = definitions.insert(iFind, std::move(animDef));
1689     }
1690   }
1691 }
1692
1693 void DliLoaderImpl::Impl::ParseAnimationGroups(const Toolkit::TreeNode* tnAnimationGroups, LoadParams& params)
1694 {
1695   auto& animGroups = params.output->mAnimationGroupDefinitions;
1696
1697   int numGroups = 0;
1698   for(auto iGroups = tnAnimationGroups->CBegin(), iGroupsEnd = tnAnimationGroups->CEnd();
1699       iGroups != iGroupsEnd;
1700       ++iGroups, ++numGroups)
1701   {
1702     const auto& tnGroup = *iGroups;
1703     auto        tnName  = tnGroup.second.GetChild(NAME);
1704     std::string groupName;
1705     if(!tnName || !ReadString(tnName, groupName))
1706     {
1707       mOnError(FormatString("Failed to get the name for the Animation group %d; ignoring.", numGroups));
1708       continue;
1709     }
1710
1711     auto iFind = std::lower_bound(animGroups.begin(), animGroups.end(), groupName, [](const AnimationGroupDefinition& group, const std::string& name) { return group.mName < name; });
1712     if(iFind != animGroups.end() && iFind->mName == groupName)
1713     {
1714       mOnError(FormatString("Animation group with name '%s' already exists; new entries will be merged.", groupName.c_str()));
1715     }
1716     else
1717     {
1718       iFind = animGroups.insert(iFind, AnimationGroupDefinition{});
1719     }
1720
1721     iFind->mName = groupName;
1722
1723     auto tnAnims = tnGroup.second.GetChild("animations");
1724     if(tnAnims && tnAnims->Size() > 0)
1725     {
1726       auto& anims = iFind->mAnimations;
1727       anims.reserve(anims.size() + tnAnims->Size());
1728       for(auto iAnims = tnAnims->CBegin(), iAnimsEnd = tnAnims->CEnd(); iAnims != iAnimsEnd; ++iAnims)
1729       {
1730         anims.push_back((*iAnims).second.GetString());
1731       }
1732     }
1733   }
1734 }
1735
1736 void DliLoaderImpl::Impl::GetCameraParameters(std::vector<Dali::Scene3D::Loader::CameraParameters>& cameras) const
1737 {
1738   if(mParser.GetRoot())
1739   {
1740     if(const TreeNode* jsonCameras = mParser.GetRoot()->GetChild("cameras"))
1741     {
1742       float dummyFloatArray[4];
1743
1744       cameras.resize(jsonCameras->Size());
1745       auto iCamera = cameras.begin();
1746       for(auto i0 = jsonCameras->CBegin(), i1 = jsonCameras->CEnd(); i0 != i1; ++i0)
1747       {
1748         auto& jsonCamera = (*i0).second;
1749
1750         ReadFloat(jsonCamera.GetChild("fov"), iCamera->yFovDegree.degree);
1751         ReadFloat(jsonCamera.GetChild("near"), iCamera->zNear);
1752         ReadFloat(jsonCamera.GetChild("far"), iCamera->zFar);
1753         if(ReadVector(jsonCamera.GetChild("orthographic"), dummyFloatArray, 4u))
1754         {
1755           iCamera->isPerspective = false;
1756
1757           iCamera->orthographicSize = dummyFloatArray[2] * 0.5f;
1758           iCamera->aspectRatio      = dummyFloatArray[1] / dummyFloatArray[2];
1759         }
1760
1761         if(auto jsonMatrix = jsonCamera.GetChild("matrix"))
1762         {
1763           ReadVector(jsonMatrix, iCamera->matrix.AsFloat(), 16u);
1764         }
1765
1766         ++iCamera;
1767       }
1768     }
1769   }
1770 }
1771
1772 void DliLoaderImpl::Impl::GetLightParameters(std::vector<Dali::Scene3D::Loader::LightParameters>& lights) const
1773 {
1774   if(mParser.GetRoot())
1775   {
1776     if(const TreeNode* jsonLights = mParser.GetRoot()->GetChild("lights"))
1777     {
1778       lights.resize(jsonLights->Size());
1779       auto iLight = lights.begin();
1780       for(auto i0 = jsonLights->CBegin(), i1 = jsonLights->CEnd(); i0 != i1; ++i0)
1781       {
1782         auto& jsonLight = (*i0).second;
1783         if(!ReadVector(jsonLight.GetChild("matrix"), iLight->transform.AsFloat(), 16))
1784         {
1785           mOnError(
1786             FormatString("Failed to parse light %d - \"matrix\" child with 16 floats expected.\n",
1787                          std::distance(jsonLights->CBegin(), i0)));
1788           continue;
1789         }
1790
1791         int shadowMapSize = 0;
1792         if(ReadInt(jsonLight.GetChild(SHADOW_MAP_SIZE), shadowMapSize) && shadowMapSize < 0)
1793         {
1794           mOnError(
1795             FormatString("Failed to parse light %d - %s has an invalid value.",
1796                          std::distance(jsonLights->CBegin(), i0),
1797                          SHADOW_MAP_SIZE));
1798           continue;
1799         }
1800         iLight->shadowMapSize = shadowMapSize;
1801
1802         float orthoSize = 0.f;
1803         if(ReadFloat(jsonLight.GetChild(ORTHOGRAPHIC_SIZE), orthoSize) &&
1804            (orthoSize < .0f || std::isnan(orthoSize) || std::isinf(orthoSize)))
1805         {
1806           mOnError(
1807             FormatString("Failed to parse light %d - %s has an invalid value.",
1808                          std::distance(jsonLights->CBegin(), i0),
1809                          ORTHOGRAPHIC_SIZE));
1810           continue;
1811         }
1812         iLight->orthographicSize = orthoSize;
1813
1814         if((iLight->shadowMapSize > 0) != (iLight->orthographicSize > .0f))
1815         {
1816           mOnError(FormatString(
1817             "Light %d: Both shadow map size and orthographic size must be set for shadows to work.",
1818             std::distance(jsonLights->CBegin(), i0)));
1819         }
1820
1821         if(!ReadVector(jsonLight.GetChild("color"), iLight->color.AsFloat(), 3)) // color is optional
1822         {
1823           iLight->color = Vector3::ONE; // default to white
1824         }
1825
1826         if(!ReadFloat(jsonLight.GetChild("intensity"), iLight->intensity)) // intensity is optional
1827         {
1828           iLight->intensity = 1.0f; // default to 1.0
1829         }
1830
1831         if(!ReadFloat(jsonLight.GetChild("shadowIntensity"), iLight->shadowIntensity)) // intensity is optional
1832         {
1833           iLight->shadowIntensity = 1.0f; // default to 1.0
1834         }
1835
1836         ++iLight;
1837       }
1838     }
1839   }
1840 }
1841
1842 } // namespace Internal
1843 } // namespace Loader
1844 } // namespace Scene3D
1845 } // namespace Dali