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