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