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