0031382: Data Exchange - BinXCAF should preserve length unit information
[occt.git] / src / RWGltf / RWGltf_CafWriter.cxx
1 // Copyright (c) 2017-2019 OPEN CASCADE SAS
2 //
3 // This file is part of Open CASCADE Technology software library.
4 //
5 // This library is free software; you can redistribute it and/or modify it under
6 // the terms of the GNU Lesser General Public License version 2.1 as published
7 // by the Free Software Foundation, with special exception defined in the file
8 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
9 // distribution for complete text of the license and disclaimer of any warranty.
10 //
11 // Alternatively, this file may be used under the terms of Open CASCADE
12 // commercial license or contractual agreement.
13
14 #include <RWGltf_CafWriter.hxx>
15
16 #include <BRep_Builder.hxx>
17 #include <gp_Quaternion.hxx>
18 #include <Message.hxx>
19 #include <Message_Messenger.hxx>
20 #include <Message_ProgressScope.hxx>
21 #include <NCollection_DataMap.hxx>
22 #include <OSD_FileSystem.hxx>
23 #include <OSD_File.hxx>
24 #include <OSD_Path.hxx>
25 #include <Poly_Triangulation.hxx>
26 #include <RWGltf_GltfAccessorLayout.hxx>
27 #include <RWGltf_GltfArrayType.hxx>
28 #include <RWGltf_GltfMaterialMap.hxx>
29 #include <RWGltf_GltfPrimitiveMode.hxx>
30 #include <RWGltf_GltfRootElement.hxx>
31 #include <RWGltf_GltfSceneNodeMap.hxx>
32 #include <RWMesh.hxx>
33 #include <RWMesh_FaceIterator.hxx>
34 #include <TDataStd_Name.hxx>
35 #include <TDF_Tool.hxx>
36 #include <TDocStd_Document.hxx>
37 #include <TopoDS_Compound.hxx>
38 #include <XCAFDoc_DocumentTool.hxx>
39 #include <XCAFDoc_ShapeTool.hxx>
40 #include <XCAFPrs_DocumentExplorer.hxx>
41
42 #ifdef HAVE_RAPIDJSON
43   #include <RWGltf_GltfOStreamWriter.hxx>
44 #endif
45
46 IMPLEMENT_STANDARD_RTTIEXT(RWGltf_CafWriter, Standard_Transient)
47
48 namespace
49 {
50   //! Write three float values.
51   static void writeVec3 (std::ostream& theStream,
52                          const gp_XYZ& theVec3)
53   {
54     Graphic3d_Vec3 aVec3 (float(theVec3.X()), float(theVec3.Y()), float(theVec3.Z()));
55     theStream.write ((const char* )aVec3.GetData(), sizeof(aVec3));
56   }
57
58   //! Write three float values.
59   static void writeVec3 (std::ostream& theStream,
60                          const Graphic3d_Vec3& theVec3)
61   {
62     theStream.write ((const char* )theVec3.GetData(), sizeof(theVec3));
63   }
64
65   //! Write two float values.
66   static void writeVec2 (std::ostream& theStream,
67                          const gp_XY&  theVec2)
68   {
69     Graphic3d_Vec2 aVec2 (float(theVec2.X()), float(theVec2.Y()));
70     theStream.write ((const char* )aVec2.GetData(), sizeof(aVec2));
71   }
72
73   //! Write triangle indices.
74   static void writeTriangle32 (std::ostream& theStream,
75                                const Graphic3d_Vec3i& theTri)
76   {
77     theStream.write ((const char* )theTri.GetData(), sizeof(theTri));
78   }
79
80   //! Write triangle indices.
81   static void writeTriangle16 (std::ostream& theStream,
82                                const NCollection_Vec3<uint16_t>& theTri)
83   {
84     theStream.write ((const char* )theTri.GetData(), sizeof(theTri));
85   }
86 }
87
88 //================================================================
89 // Function : Constructor
90 // Purpose  :
91 //================================================================
92 RWGltf_CafWriter::RWGltf_CafWriter (const TCollection_AsciiString& theFile,
93                                     Standard_Boolean theIsBinary)
94 : myFile          (theFile),
95   myTrsfFormat    (RWGltf_WriterTrsfFormat_Compact),
96   myNodeNameFormat(RWMesh_NameFormat_InstanceOrProduct),
97   myMeshNameFormat(RWMesh_NameFormat_Product),
98   myIsBinary      (theIsBinary),
99   myIsForcedUVExport (false),
100   myToEmbedTexturesInGlb (true),
101   myToMergeFaces (false),
102   myToSplitIndices16 (false),
103   myBinDataLen64  (0)
104 {
105   myCSTrsf.SetOutputLengthUnit (1.0); // meters
106   myCSTrsf.SetOutputCoordinateSystem (RWMesh_CoordinateSystem_glTF);
107
108   TCollection_AsciiString aFolder, aFileName, aShortFileNameBase, aFileExt;
109   OSD_Path::FolderAndFileFromPath (theFile, aFolder, aFileName);
110   OSD_Path::FileNameAndExtension (aFileName, aShortFileNameBase, aFileExt);
111
112   myBinFileNameShort = aShortFileNameBase + ".bin" + (myIsBinary ? ".tmp" : "");
113   myBinFileNameFull = !aFolder.IsEmpty() ? aFolder + myBinFileNameShort : myBinFileNameShort;
114 }
115
116 //================================================================
117 // Function : Destructor
118 // Purpose  :
119 //================================================================
120 RWGltf_CafWriter::~RWGltf_CafWriter()
121 {
122   myWriter.reset();
123 }
124
125 //================================================================
126 // Function : formatName
127 // Purpose  :
128 //================================================================
129 TCollection_AsciiString RWGltf_CafWriter::formatName (RWMesh_NameFormat theFormat,
130                                                       const TDF_Label& theLabel,
131                                                       const TDF_Label& theRefLabel) const
132 {
133   return RWMesh::FormatName (theFormat, theLabel, theRefLabel);
134 }
135
136 //================================================================
137 // Function : toSkipFaceMesh
138 // Purpose  :
139 //================================================================
140 Standard_Boolean RWGltf_CafWriter::toSkipFaceMesh (const RWMesh_FaceIterator& theFaceIter)
141 {
142   return theFaceIter.IsEmptyMesh();
143 }
144
145 // =======================================================================
146 // function : saveNodes
147 // purpose  :
148 // =======================================================================
149 void RWGltf_CafWriter::saveNodes (RWGltf_GltfFace& theGltfFace,
150                                   std::ostream& theBinFile,
151                                   const RWMesh_FaceIterator& theFaceIter,
152                                   Standard_Integer& theAccessorNb) const
153 {
154   if (theGltfFace.NodePos.Id == RWGltf_GltfAccessor::INVALID_ID)
155   {
156     theGltfFace.NodePos.Id            = theAccessorNb++;
157     theGltfFace.NodePos.ByteOffset    = (int64_t )theBinFile.tellp() - myBuffViewPos.ByteOffset;
158     theGltfFace.NodePos.Type          = RWGltf_GltfAccessorLayout_Vec3;
159     theGltfFace.NodePos.ComponentType = RWGltf_GltfAccessorCompType_Float32;
160   }
161   else
162   {
163     const int64_t aPos = theGltfFace.NodePos.ByteOffset + myBuffViewPos.ByteOffset + theGltfFace.NodePos.Count * sizeof(Graphic3d_Vec3);
164     Standard_ASSERT_RAISE (aPos == (int64_t )theBinFile.tellp(), "wrong offset");
165   }
166   theGltfFace.NodePos.Count += theFaceIter.NbNodes();
167
168   const Standard_Integer aNodeUpper = theFaceIter.NodeUpper();
169   for (Standard_Integer aNodeIter = theFaceIter.NodeLower(); aNodeIter <= aNodeUpper; ++aNodeIter)
170   {
171     gp_XYZ aNode = theFaceIter.NodeTransformed (aNodeIter).XYZ();
172     myCSTrsf.TransformPosition (aNode);
173     theGltfFace.NodePos.BndBox.Add (Graphic3d_Vec3d(aNode.X(), aNode.Y(), aNode.Z()));
174     writeVec3 (theBinFile, aNode);
175   }
176 }
177
178 // =======================================================================
179 // function : saveNormals
180 // purpose  :
181 // =======================================================================
182 void RWGltf_CafWriter::saveNormals (RWGltf_GltfFace& theGltfFace,
183                                     std::ostream& theBinFile,
184                                     RWMesh_FaceIterator& theFaceIter,
185                                     Standard_Integer& theAccessorNb) const
186 {
187   if (!theFaceIter.HasNormals())
188   {
189     return;
190   }
191
192   if (theGltfFace.NodeNorm.Id == RWGltf_GltfAccessor::INVALID_ID)
193   {
194     theGltfFace.NodeNorm.Id            = theAccessorNb++;
195     theGltfFace.NodeNorm.ByteOffset    = (int64_t )theBinFile.tellp() - myBuffViewNorm.ByteOffset;
196     theGltfFace.NodeNorm.Type          = RWGltf_GltfAccessorLayout_Vec3;
197     theGltfFace.NodeNorm.ComponentType = RWGltf_GltfAccessorCompType_Float32;
198   }
199   else
200   {
201     const int64_t aPos = theGltfFace.NodeNorm.ByteOffset + myBuffViewNorm.ByteOffset + theGltfFace.NodeNorm.Count * sizeof(Graphic3d_Vec3);
202     Standard_ASSERT_RAISE (aPos == (int64_t )theBinFile.tellp(), "wrong offset");
203   }
204   theGltfFace.NodeNorm.Count += theFaceIter.NbNodes();
205
206   const Standard_Integer aNodeUpper = theFaceIter.NodeUpper();
207   for (Standard_Integer aNodeIter = theFaceIter.NodeLower(); aNodeIter <= aNodeUpper; ++aNodeIter)
208   {
209     const gp_Dir aNormal = theFaceIter.NormalTransformed (aNodeIter);
210     Graphic3d_Vec3 aVecNormal ((float )aNormal.X(), (float )aNormal.Y(), (float )aNormal.Z());
211     myCSTrsf.TransformNormal (aVecNormal);
212     writeVec3 (theBinFile, aVecNormal);
213   }
214 }
215
216 // =======================================================================
217 // function : saveTextCoords
218 // purpose  :
219 // =======================================================================
220 void RWGltf_CafWriter::saveTextCoords (RWGltf_GltfFace& theGltfFace,
221                                        std::ostream& theBinFile,
222                                        const RWMesh_FaceIterator& theFaceIter,
223                                        Standard_Integer& theAccessorNb) const
224 {
225   if (!theFaceIter.HasTexCoords())
226   {
227     return;
228   }
229   if (!myIsForcedUVExport)
230   {
231     if (theFaceIter.FaceStyle().Material().IsNull())
232     {
233       return;
234     }
235
236     if (RWGltf_GltfMaterialMap::baseColorTexture (theFaceIter.FaceStyle().Material()).IsNull()
237      && theFaceIter.FaceStyle().Material()->PbrMaterial().MetallicRoughnessTexture.IsNull()
238      && theFaceIter.FaceStyle().Material()->PbrMaterial().EmissiveTexture.IsNull()
239      && theFaceIter.FaceStyle().Material()->PbrMaterial().OcclusionTexture.IsNull()
240      && theFaceIter.FaceStyle().Material()->PbrMaterial().NormalTexture.IsNull())
241     {
242       return;
243     }
244   }
245
246   if (theGltfFace.NodeUV.Id == RWGltf_GltfAccessor::INVALID_ID)
247   {
248     theGltfFace.NodeUV.Id            = theAccessorNb++;
249     theGltfFace.NodeUV.ByteOffset    = (int64_t )theBinFile.tellp() - myBuffViewTextCoord.ByteOffset;
250     theGltfFace.NodeUV.Type          = RWGltf_GltfAccessorLayout_Vec2;
251     theGltfFace.NodeUV.ComponentType = RWGltf_GltfAccessorCompType_Float32;
252   }
253   else
254   {
255     const int64_t aPos = theGltfFace.NodeUV.ByteOffset + myBuffViewTextCoord.ByteOffset + theGltfFace.NodeUV.Count * sizeof(Graphic3d_Vec2);
256     Standard_ASSERT_RAISE (aPos == (int64_t )theBinFile.tellp(), "wrong offset");
257   }
258   theGltfFace.NodeUV.Count += theFaceIter.NbNodes();
259
260   const Standard_Integer aNodeUpper = theFaceIter.NodeUpper();
261   for (Standard_Integer aNodeIter = theFaceIter.NodeLower(); aNodeIter <= aNodeUpper; ++aNodeIter)
262   {
263     gp_Pnt2d aTexCoord = theFaceIter.NodeTexCoord (aNodeIter);
264     aTexCoord.SetY (1.0 - aTexCoord.Y());
265     writeVec2 (theBinFile, aTexCoord.XY());
266   }
267 }
268
269 // =======================================================================
270 // function : saveIndices
271 // purpose  :
272 // =======================================================================
273 void RWGltf_CafWriter::saveIndices (RWGltf_GltfFace& theGltfFace,
274                                     std::ostream& theBinFile,
275                                     const RWMesh_FaceIterator& theFaceIter,
276                                     Standard_Integer& theAccessorNb)
277 {
278   if (theGltfFace.Indices.Id == RWGltf_GltfAccessor::INVALID_ID)
279   {
280     theGltfFace.Indices.Id            = theAccessorNb++;
281     theGltfFace.Indices.ByteOffset    = (int64_t )theBinFile.tellp() - myBuffViewInd.ByteOffset;
282     theGltfFace.Indices.Type          = RWGltf_GltfAccessorLayout_Scalar;
283     theGltfFace.Indices.ComponentType = theGltfFace.NodePos.Count > std::numeric_limits<uint16_t>::max()
284                                       ? RWGltf_GltfAccessorCompType_UInt32
285                                       : RWGltf_GltfAccessorCompType_UInt16;
286   }
287   else
288   {
289     const int64_t aRefPos = (int64_t )theBinFile.tellp();
290     const int64_t aPos = theGltfFace.Indices.ByteOffset
291                        + myBuffViewInd.ByteOffset
292                        + theGltfFace.Indices.Count * (theGltfFace.Indices.ComponentType == RWGltf_GltfAccessorCompType_UInt32 ? sizeof(uint32_t) : sizeof(uint16_t));
293     Standard_ASSERT_RAISE (aPos == aRefPos, "wrong offset");
294   }
295
296   const Standard_Integer aNodeFirst = theGltfFace.NbIndexedNodes - theFaceIter.ElemLower();
297   theGltfFace.NbIndexedNodes += theFaceIter.NbNodes();
298   theGltfFace.Indices.Count += theFaceIter.NbTriangles() * 3;
299
300   const Standard_Integer anElemLower = theFaceIter.ElemLower();
301   const Standard_Integer anElemUpper = theFaceIter.ElemUpper();
302   for (Standard_Integer anElemIter = anElemLower; anElemIter <= anElemUpper; ++anElemIter)
303   {
304     Poly_Triangle aTri = theFaceIter.TriangleOriented (anElemIter);
305     aTri(1) += aNodeFirst;
306     aTri(2) += aNodeFirst;
307     aTri(3) += aNodeFirst;
308     if (theGltfFace.Indices.ComponentType == RWGltf_GltfAccessorCompType_UInt16)
309     {
310       writeTriangle16 (theBinFile, NCollection_Vec3<uint16_t>((uint16_t)aTri(1), (uint16_t)aTri(2), (uint16_t)aTri(3)));
311     }
312     else
313     {
314       writeTriangle32 (theBinFile, Graphic3d_Vec3i (aTri(1), aTri(2), aTri(3)));
315     }
316   }
317 }
318
319 // =======================================================================
320 // function : Perform
321 // purpose  :
322 // =======================================================================
323 bool RWGltf_CafWriter::Perform (const Handle(TDocStd_Document)& theDocument,
324                                 const TColStd_IndexedDataMapOfStringString& theFileInfo,
325                                 const Message_ProgressRange& theProgress)
326 {
327   TDF_LabelSequence aRoots;
328   Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool (theDocument->Main());
329   aShapeTool->GetFreeShapes (aRoots);
330   return Perform (theDocument, aRoots, NULL, theFileInfo, theProgress);
331 }
332
333 // =======================================================================
334 // function : Perform
335 // purpose  :
336 // =======================================================================
337 bool RWGltf_CafWriter::Perform (const Handle(TDocStd_Document)& theDocument,
338                                 const TDF_LabelSequence& theRootLabels,
339                                 const TColStd_MapOfAsciiString* theLabelFilter,
340                                 const TColStd_IndexedDataMapOfStringString& theFileInfo,
341                                 const Message_ProgressRange& theProgress)
342 {
343   Standard_Real aLengthUnit = 1.;
344   if (XCAFDoc_DocumentTool::GetLengthUnit(theDocument, aLengthUnit))
345   {
346     myCSTrsf.SetInputLengthUnit(aLengthUnit);
347   }
348   const Standard_Integer aDefSamplerId = 0;
349   myMaterialMap = new RWGltf_GltfMaterialMap (myFile, aDefSamplerId);
350   myMaterialMap->SetDefaultStyle (myDefaultStyle);
351
352   Message_ProgressScope aPSentry (theProgress, "Writing glTF file", 2);
353   if (!writeBinData (theDocument, theRootLabels, theLabelFilter, aPSentry.Next()))
354   {
355     return false;
356   }
357
358   if (!aPSentry.More())
359   {
360     return false;
361   }
362
363   return writeJson (theDocument, theRootLabels, theLabelFilter, theFileInfo, aPSentry.Next());
364 }
365
366 // =======================================================================
367 // function : writeBinData
368 // purpose  :
369 // =======================================================================
370 bool RWGltf_CafWriter::writeBinData (const Handle(TDocStd_Document)& theDocument,
371                                      const TDF_LabelSequence& theRootLabels,
372                                      const TColStd_MapOfAsciiString* theLabelFilter,
373                                      const Message_ProgressRange& theProgress)
374 {
375   myBuffViewPos.Id               = RWGltf_GltfAccessor::INVALID_ID;
376   myBuffViewPos.ByteOffset       = 0;
377   myBuffViewPos.ByteLength       = 0;
378   myBuffViewPos.ByteStride       = 12;
379   myBuffViewPos.Target           = RWGltf_GltfBufferViewTarget_ARRAY_BUFFER;
380
381   myBuffViewNorm.Id              = RWGltf_GltfAccessor::INVALID_ID;
382   myBuffViewNorm.ByteOffset      = 0;
383   myBuffViewNorm.ByteLength      = 0;
384   myBuffViewNorm.ByteStride      = 12;
385   myBuffViewNorm.Target          = RWGltf_GltfBufferViewTarget_ARRAY_BUFFER;
386
387   myBuffViewTextCoord.Id         = RWGltf_GltfAccessor::INVALID_ID;
388   myBuffViewTextCoord.ByteOffset = 0;
389   myBuffViewTextCoord.ByteLength = 0;
390   myBuffViewTextCoord.ByteStride = 8;
391   myBuffViewTextCoord.Target     = RWGltf_GltfBufferViewTarget_ARRAY_BUFFER;
392
393   myBuffViewInd.Id               = RWGltf_GltfAccessor::INVALID_ID;
394   myBuffViewInd.ByteOffset       = 0;
395   myBuffViewInd.ByteLength       = 0;
396   myBuffViewInd.Target           = RWGltf_GltfBufferViewTarget_ELEMENT_ARRAY_BUFFER;
397
398   myBinDataMap.Clear();
399   myBinDataLen64 = 0;
400
401   const Handle(OSD_FileSystem)& aFileSystem = OSD_FileSystem::DefaultFileSystem();
402   opencascade::std::shared_ptr<std::ostream> aBinFile = aFileSystem->OpenOStream (myBinFileNameFull, std::ios::out | std::ios::binary);
403   if (aBinFile.get() == NULL
404    || !aBinFile->good())
405   {
406     Message::SendFail (TCollection_AsciiString ("File '") + myBinFileNameFull + "' can not be created");
407     return false;
408   }
409
410   Message_ProgressScope aPSentryBin (theProgress, "Binary data", 4);
411   const RWGltf_GltfArrayType anArrTypes[4] =
412   {
413     RWGltf_GltfArrayType_Position,
414     RWGltf_GltfArrayType_Normal,
415     RWGltf_GltfArrayType_TCoord0,
416     RWGltf_GltfArrayType_Indices
417   };
418
419   // dispatch faces
420   NCollection_DataMap<XCAFPrs_Style, Handle(RWGltf_GltfFace), XCAFPrs_Style> aMergedFaces;
421   for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
422        aDocExplorer.More() && aPSentryBin.More(); aDocExplorer.Next())
423   {
424     const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
425     if (theLabelFilter != NULL
426     && !theLabelFilter->Contains (aDocNode.Id))
427     {
428       continue;
429     }
430
431     // transformation will be stored at scene nodes
432     aMergedFaces.Clear (false);
433
434     RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style);
435     if (myToMergeFaces)
436     {
437       if (myBinDataMap.Contains (aFaceIter.ExploredShape()))
438       {
439         continue;
440       }
441
442       Handle(RWGltf_GltfFaceList) aGltfFaceList = new RWGltf_GltfFaceList();
443       myBinDataMap.Add (aFaceIter.ExploredShape(), aGltfFaceList);
444       for (; aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
445       {
446         if (toSkipFaceMesh (aFaceIter))
447         {
448           continue;
449         }
450
451         Handle(RWGltf_GltfFace) aGltfFace;
452         if (!aMergedFaces.Find (aFaceIter.FaceStyle(), aGltfFace))
453         {
454           aGltfFace = new RWGltf_GltfFace();
455           aGltfFaceList->Append (aGltfFace);
456           aGltfFace->Shape = aFaceIter.Face();
457           aGltfFace->Style = aFaceIter.FaceStyle();
458           aGltfFace->NbIndexedNodes = aFaceIter.NbNodes();
459           aMergedFaces.Bind (aFaceIter.FaceStyle(), aGltfFace);
460         }
461         else if (myToSplitIndices16
462              &&  aGltfFace->NbIndexedNodes < std::numeric_limits<uint16_t>::max()
463              && (aGltfFace->NbIndexedNodes + aFaceIter.NbNodes()) >= std::numeric_limits<uint16_t>::max())
464         {
465           aMergedFaces.UnBind (aFaceIter.FaceStyle());
466           aGltfFace = new RWGltf_GltfFace();
467           aGltfFaceList->Append (aGltfFace);
468           aGltfFace->Shape = aFaceIter.Face();
469           aGltfFace->Style = aFaceIter.FaceStyle();
470           aGltfFace->NbIndexedNodes = aFaceIter.NbNodes();
471           aMergedFaces.Bind (aFaceIter.FaceStyle(), aGltfFace);
472         }
473         else
474         {
475           if (aGltfFace->Shape.ShapeType() != TopAbs_COMPOUND)
476           {
477             TopoDS_Shape anOldShape = aGltfFace->Shape;
478             TopoDS_Compound aComp;
479             BRep_Builder().MakeCompound (aComp);
480             BRep_Builder().Add (aComp, anOldShape);
481             aGltfFace->Shape = aComp;
482           }
483           BRep_Builder().Add (aGltfFace->Shape, aFaceIter.Face());
484           aGltfFace->NbIndexedNodes += aFaceIter.NbNodes();
485         }
486       }
487     }
488     else
489     {
490       for (; aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
491       {
492         if (toSkipFaceMesh (aFaceIter)
493          || myBinDataMap.Contains (aFaceIter.Face()))
494         {
495           continue;
496         }
497
498         Handle(RWGltf_GltfFaceList) aGltfFaceList = new RWGltf_GltfFaceList();
499         Handle(RWGltf_GltfFace) aGltfFace = new RWGltf_GltfFace();
500         aGltfFace->Shape = aFaceIter.Face();
501         aGltfFace->Style = aFaceIter.FaceStyle();
502         aGltfFaceList->Append (aGltfFace);
503         myBinDataMap.Add (aFaceIter.Face(), aGltfFaceList);
504       }
505     }
506   }
507
508   Standard_Integer aNbAccessors = 0;
509   NCollection_Map<Handle(RWGltf_GltfFaceList)> aWrittenFaces;
510   for (Standard_Integer aTypeIter = 0; aTypeIter < 4; ++aTypeIter)
511   {
512     const RWGltf_GltfArrayType anArrType = (RWGltf_GltfArrayType )anArrTypes[aTypeIter];
513     RWGltf_GltfBufferView* aBuffView = NULL;
514     switch (anArrType)
515     {
516       case RWGltf_GltfArrayType_Position: aBuffView = &myBuffViewPos;  break;
517       case RWGltf_GltfArrayType_Normal:   aBuffView = &myBuffViewNorm; break;
518       case RWGltf_GltfArrayType_TCoord0:  aBuffView = &myBuffViewTextCoord; break;
519       case RWGltf_GltfArrayType_Indices:  aBuffView = &myBuffViewInd; break;
520       default: break;
521     }
522     aBuffView->ByteOffset = aBinFile->tellp();
523     aWrittenFaces.Clear (false);
524     for (ShapeToGltfFaceMap::Iterator aBinDataIter (myBinDataMap); aBinDataIter.More() && aPSentryBin.More(); aBinDataIter.Next())
525     {
526       const Handle(RWGltf_GltfFaceList)& aGltfFaceList = aBinDataIter.Value();
527       if (!aWrittenFaces.Add (aGltfFaceList)) // skip repeating faces
528       {
529         continue;
530       }
531
532       for (RWGltf_GltfFaceList::Iterator aGltfFaceIter (*aGltfFaceList); aGltfFaceIter.More() && aPSentryBin.More(); aGltfFaceIter.Next())
533       {
534         const Handle(RWGltf_GltfFace)& aGltfFace = aGltfFaceIter.Value();
535         for (RWMesh_FaceIterator aFaceIter (aGltfFace->Shape, aGltfFace->Style); aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
536         {
537           switch (anArrType)
538           {
539             case RWGltf_GltfArrayType_Position:
540             {
541               aGltfFace->NbIndexedNodes = 0; // reset to zero before RWGltf_GltfArrayType_Indices step
542               saveNodes (*aGltfFace, *aBinFile, aFaceIter, aNbAccessors);
543               break;
544             }
545             case RWGltf_GltfArrayType_Normal:
546             {
547               saveNormals (*aGltfFace, *aBinFile, aFaceIter, aNbAccessors);
548               break;
549             }
550             case RWGltf_GltfArrayType_TCoord0:
551             {
552               saveTextCoords (*aGltfFace, *aBinFile, aFaceIter, aNbAccessors);
553               break;
554             }
555             case RWGltf_GltfArrayType_Indices:
556             {
557               saveIndices (*aGltfFace, *aBinFile, aFaceIter, aNbAccessors);
558               break;
559             }
560             default:
561             {
562               break;
563             }
564           }
565
566           if (!aBinFile->good())
567           {
568             Message::SendFail (TCollection_AsciiString ("File '") + myBinFileNameFull + "' cannot be written");
569             return false;
570           }
571         }
572
573         // add alignment by 4 bytes (might happen on RWGltf_GltfAccessorCompType_UInt16 indices)
574         int64_t aContentLen64 = (int64_t)aBinFile->tellp();
575         while (aContentLen64 % 4 != 0)
576         {
577           aBinFile->write (" ", 1);
578           ++aContentLen64;
579         }
580       }
581     }
582
583     aBuffView->ByteLength = (int64_t )aBinFile->tellp() - aBuffView->ByteOffset;
584     if (!aPSentryBin.More())
585     {
586       return false;
587     }
588
589     aPSentryBin.Next();
590   }
591
592   if (myIsBinary
593    && myToEmbedTexturesInGlb)
594   {
595     // save unique image textures
596     for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
597          aDocExplorer.More() && aPSentryBin.More(); aDocExplorer.Next())
598     {
599       const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
600       if (theLabelFilter != NULL
601       && !theLabelFilter->Contains (aDocNode.Id))
602       {
603         continue;
604       }
605
606       for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style);
607            aFaceIter.More(); aFaceIter.Next())
608       {
609         if (toSkipFaceMesh (aFaceIter))
610         {
611           continue;
612         }
613
614         myMaterialMap->AddGlbImages (*aBinFile, aFaceIter.FaceStyle());
615       }
616     }
617   }
618
619   int aBuffViewId = 0;
620   if (myBuffViewPos.ByteLength > 0)
621   {
622     myBuffViewPos.Id = aBuffViewId++;
623   }
624   if (myBuffViewNorm.ByteLength > 0)
625   {
626     myBuffViewNorm.Id = aBuffViewId++;
627   }
628   if (myBuffViewTextCoord.ByteLength > 0)
629   {
630     myBuffViewTextCoord.Id = aBuffViewId++;
631   }
632   if (myBuffViewInd.ByteLength > 0)
633   {
634     myBuffViewInd.Id = aBuffViewId++;
635   }
636   // myMaterialMap->FlushGlbBufferViews() will put image bufferView's IDs at the end of list
637
638   myBinDataLen64 = aBinFile->tellp();
639   aBinFile->flush();
640   if (!aBinFile->good())
641   {
642     Message::SendFail (TCollection_AsciiString ("File '") + myBinFileNameFull + "' cannot be written");
643     return false;
644   }
645   aBinFile.reset();
646   return true;
647 }
648
649 //================================================================
650 // Function : writeJson
651 // Purpose  :
652 //================================================================
653 bool RWGltf_CafWriter::writeJson (const Handle(TDocStd_Document)&  theDocument,
654                                   const TDF_LabelSequence&         theRootLabels,
655                                   const TColStd_MapOfAsciiString*  theLabelFilter,
656                                   const TColStd_IndexedDataMapOfStringString& theFileInfo,
657                                   const Message_ProgressRange& theProgress)
658 {
659 #ifdef HAVE_RAPIDJSON
660   myWriter.reset();
661
662   // write vertex arrays
663   Message_ProgressScope aPSentryBin (theProgress, "Header data", 2);
664
665   const Standard_Integer aBinDataBufferId = 0;
666   const Standard_Integer aDefSceneId      = 0;
667
668   const TCollection_AsciiString aFileNameGltf = myFile;
669   const Handle(OSD_FileSystem)& aFileSystem = OSD_FileSystem::DefaultFileSystem();
670   opencascade::std::shared_ptr<std::ostream> aGltfContentFile = aFileSystem->OpenOStream (aFileNameGltf, std::ios::out | std::ios::binary);
671   if (aGltfContentFile.get() == NULL
672    || !aGltfContentFile->good())
673   {
674     Message::SendFail (TCollection_AsciiString ("File '") + aFileNameGltf + "' can not be created");
675     return false;
676   }
677   if (myIsBinary)
678   {
679     const char* aMagic = "glTF";
680     uint32_t aVersion       = 2;
681     uint32_t aLength        = 0;
682     uint32_t aContentLength = 0;
683     uint32_t aContentType   = 0x4E4F534A;
684
685     aGltfContentFile->write (aMagic, 4);
686     aGltfContentFile->write ((const char* )&aVersion,       sizeof(aVersion));
687     aGltfContentFile->write ((const char* )&aLength,        sizeof(aLength));
688     aGltfContentFile->write ((const char* )&aContentLength, sizeof(aContentLength));
689     aGltfContentFile->write ((const char* )&aContentType,   sizeof(aContentType));
690   }
691
692   // Prepare an indexed map of scene nodes (without assemblies) in correct order.
693   // Note: this is also order of meshes in glTF document array.
694   RWGltf_GltfSceneNodeMap aSceneNodeMap;
695   for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
696        aDocExplorer.More(); aDocExplorer.Next())
697   {
698     const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
699     if (theLabelFilter != NULL
700     && !theLabelFilter->Contains (aDocNode.Id))
701     {
702       continue;
703     }
704
705     bool hasMeshData = false;
706     if (!aDocNode.IsAssembly)
707     {
708       for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
709       {
710         if (!toSkipFaceMesh (aFaceIter))
711         {
712           hasMeshData = true;
713           break;
714         }
715       }
716     }
717     if (hasMeshData)
718     {
719       aSceneNodeMap.Add (aDocNode);
720     }
721     else
722     {
723       // glTF disallows empty meshes / primitive arrays
724       const TCollection_AsciiString aNodeName = formatName (RWMesh_NameFormat_ProductOrInstance, aDocNode.Label, aDocNode.RefLabel);
725       Message::SendWarning (TCollection_AsciiString("RWGltf_CafWriter skipped node '") + aNodeName + "' without triangulation data");
726     }
727   }
728
729   rapidjson::OStreamWrapper aFileStream (*aGltfContentFile);
730   myWriter.reset (new RWGltf_GltfOStreamWriter (aFileStream));
731
732   myWriter->StartObject();
733
734   writeAccessors (aSceneNodeMap);
735   writeAnimations();
736   writeAsset (theFileInfo);
737   writeBufferViews (aBinDataBufferId);
738   writeBuffers();
739   writeExtensions();
740
741   writeImages    (aSceneNodeMap);
742   writeMaterials (aSceneNodeMap);
743   writeMeshes    (aSceneNodeMap);
744
745   aPSentryBin.Next();
746   if (!aPSentryBin.More())
747   {
748     return false;
749   }
750
751   // root nodes indices starting from 0
752   NCollection_Sequence<Standard_Integer> aSceneRootNodeInds;
753   writeNodes (theDocument, theRootLabels, theLabelFilter, aSceneNodeMap, aSceneRootNodeInds);
754   writeSamplers();
755   writeScene (aDefSceneId);
756   writeScenes (aSceneRootNodeInds);
757   writeSkins();
758   writeTextures (aSceneNodeMap);
759
760   myWriter->EndObject();
761
762   if (!myIsBinary)
763   {
764     aGltfContentFile->flush();
765     if (!aGltfContentFile->good())
766     {
767       Message::SendFail (TCollection_AsciiString ("File '") + aFileNameGltf + "' can not be written");
768       return false;
769     }
770     aGltfContentFile.reset();
771     return true;
772   }
773
774   int64_t aContentLen64 = (int64_t )aGltfContentFile->tellp() - 20;
775   while (aContentLen64 % 4 != 0)
776   {
777     aGltfContentFile->write (" ", 1);
778     ++aContentLen64;
779   }
780
781   const uint32_t aBinLength = (uint32_t )myBinDataLen64;
782   const uint32_t aBinType   = 0x004E4942;
783   aGltfContentFile->write ((const char*)&aBinLength, 4);
784   aGltfContentFile->write ((const char*)&aBinType,   4);
785
786   const int64_t aFullLen64 = aContentLen64 + 20 + myBinDataLen64 + 8;
787   if (aFullLen64 < std::numeric_limits<uint32_t>::max())
788   {
789     {
790       opencascade::std::shared_ptr<std::istream> aBinFile = aFileSystem->OpenIStream (myBinFileNameFull, std::ios::in | std::ios::binary);
791       if (aBinFile.get() == NULL || !aBinFile->good())
792       {
793         Message::SendFail (TCollection_AsciiString ("File '") + myBinFileNameFull + "' cannot be opened");
794         return false;
795       }
796       char aBuffer[4096];
797       for (; aBinFile->good();)
798       {
799         aBinFile->read (aBuffer, 4096);
800         const Standard_Integer aReadLen = (Standard_Integer )aBinFile->gcount();
801         if (aReadLen == 0)
802         {
803           break;
804         }
805         aGltfContentFile->write (aBuffer, aReadLen);
806       }
807     }
808     OSD_Path aBinFilePath (myBinFileNameFull);
809     OSD_File (aBinFilePath).Remove();
810     if (OSD_File (aBinFilePath).Exists())
811     {
812       Message::SendFail (TCollection_AsciiString ("Unable to remove temporary glTF content file '") + myBinFileNameFull + "'");
813     }
814   }
815   else
816   {
817     Message::SendFail ("glTF file content is too big for binary format");
818     return false;
819   }
820
821   const uint32_t aLength        = (uint32_t )aFullLen64;
822   const uint32_t aContentLength = (uint32_t )aContentLen64;
823   aGltfContentFile->seekp (8);
824   aGltfContentFile->write ((const char* )&aLength,        4);
825   aGltfContentFile->write ((const char* )&aContentLength, 4);
826
827   aGltfContentFile->flush();
828   if (!aGltfContentFile->good())
829   {
830     Message::SendFail (TCollection_AsciiString ("File '") + aFileNameGltf + "' can not be written");
831     return false;
832   }
833   aGltfContentFile.reset();
834   myWriter.reset();
835   return true;
836 #else
837   (void )theDocument;
838   (void )theRootLabels;
839   (void )theLabelFilter;
840   (void )theFileInfo;
841   (void )theProgress;
842   Message::SendFail ("Error: glTF writer is unavailable - OCCT has been built without RapidJSON support [HAVE_RAPIDJSON undefined]");
843   return false;
844 #endif
845 }
846
847 // =======================================================================
848 // function : writeAccessors
849 // purpose  :
850 // =======================================================================
851 void RWGltf_CafWriter::writeAccessors (const RWGltf_GltfSceneNodeMap& )
852 {
853 #ifdef HAVE_RAPIDJSON
854   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeAccessors()");
855
856   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Accessors));
857   myWriter->StartArray();
858
859   const RWGltf_GltfArrayType anArrTypes[4] =
860   {
861     RWGltf_GltfArrayType_Position,
862     RWGltf_GltfArrayType_Normal,
863     RWGltf_GltfArrayType_TCoord0,
864     RWGltf_GltfArrayType_Indices
865   };
866   NCollection_Map<Handle(RWGltf_GltfFaceList)> aWrittenFaces;
867   for (Standard_Integer aTypeIter = 0; aTypeIter < 4; ++aTypeIter)
868   {
869     const RWGltf_GltfArrayType anArrType = (RWGltf_GltfArrayType )anArrTypes[aTypeIter];
870     aWrittenFaces.Clear (false);
871     for (ShapeToGltfFaceMap::Iterator aBinDataIter (myBinDataMap); aBinDataIter.More(); aBinDataIter.Next())
872     {
873       const Handle(RWGltf_GltfFaceList)& aGltfFaceList = aBinDataIter.Value();
874       if (!aWrittenFaces.Add (aGltfFaceList)) // skip repeating faces
875       {
876         continue;
877       }
878
879       for (RWGltf_GltfFaceList::Iterator aFaceIter (*aGltfFaceList); aFaceIter.More(); aFaceIter.Next())
880       {
881         const Handle(RWGltf_GltfFace)& aGltfFace = aFaceIter.Value();
882         switch (anArrType)
883         {
884           case RWGltf_GltfArrayType_Position:
885           {
886             writePositions (*aGltfFace);
887             break;
888           }
889           case RWGltf_GltfArrayType_Normal:
890           {
891             writeNormals (*aGltfFace);
892             break;
893           }
894           case RWGltf_GltfArrayType_TCoord0:
895           {
896             writeTextCoords (*aGltfFace);
897             break;
898           }
899           case RWGltf_GltfArrayType_Indices:
900           {
901             writeIndices (*aGltfFace);
902             break;
903           }
904           default:
905           {
906             break;
907           }
908         }
909       }
910     }
911   }
912
913   myWriter->EndArray();
914 #endif
915 }
916
917 // =======================================================================
918 // function : writePositions
919 // purpose  :
920 // =======================================================================
921 void RWGltf_CafWriter::writePositions (const RWGltf_GltfFace& theGltfFace)
922 {
923 #ifdef HAVE_RAPIDJSON
924   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writePositions()");
925   if (theGltfFace.NodePos.Id == RWGltf_GltfAccessor::INVALID_ID)
926   {
927     return;
928   }
929
930   myWriter->StartObject();
931   myWriter->Key    ("bufferView");
932   myWriter->Int    (myBuffViewPos.Id);
933   myWriter->Key    ("byteOffset");
934   myWriter->Int64  (theGltfFace.NodePos.ByteOffset);
935   myWriter->Key    ("componentType");
936   myWriter->Int    (theGltfFace.NodePos.ComponentType);
937   myWriter->Key    ("count");
938   myWriter->Int64  (theGltfFace.NodePos.Count);
939
940   if (theGltfFace.NodePos.BndBox.IsValid())
941   {
942     myWriter->Key ("max");
943     myWriter->StartArray();
944     myWriter->Double (theGltfFace.NodePos.BndBox.CornerMax().x());
945     myWriter->Double (theGltfFace.NodePos.BndBox.CornerMax().y());
946     myWriter->Double (theGltfFace.NodePos.BndBox.CornerMax().z());
947     myWriter->EndArray();
948
949     myWriter->Key("min");
950     myWriter->StartArray();
951     myWriter->Double (theGltfFace.NodePos.BndBox.CornerMin().x());
952     myWriter->Double (theGltfFace.NodePos.BndBox.CornerMin().y());
953     myWriter->Double (theGltfFace.NodePos.BndBox.CornerMin().z());
954     myWriter->EndArray();
955   }
956   myWriter->Key    ("type");
957   myWriter->String ("VEC3");
958
959   myWriter->EndObject();
960 #else
961   (void )theGltfFace;
962 #endif
963 }
964
965 // =======================================================================
966 // function : writeNormals
967 // purpose  :
968 // =======================================================================
969 void RWGltf_CafWriter::writeNormals (const RWGltf_GltfFace& theGltfFace)
970 {
971 #ifdef HAVE_RAPIDJSON
972   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeNormals()");
973   if (theGltfFace.NodeNorm.Id == RWGltf_GltfAccessor::INVALID_ID)
974   {
975     return;
976   }
977
978   myWriter->StartObject();
979   myWriter->Key    ("bufferView");
980   myWriter->Int    (myBuffViewNorm.Id);
981   myWriter->Key    ("byteOffset");
982   myWriter->Int64  (theGltfFace.NodeNorm.ByteOffset);
983   myWriter->Key    ("componentType");
984   myWriter->Int    (theGltfFace.NodeNorm.ComponentType);
985   myWriter->Key    ("count");
986   myWriter->Int64  (theGltfFace.NodeNorm.Count);
987   // min/max values are optional, and not very useful for normals - skip them
988   /*{
989     myWriter->Key ("max");
990     myWriter->StartArray();
991     myWriter->Double (1.0);
992     myWriter->Double (1.0);
993     myWriter->Double (1.0);
994     myWriter->EndArray();
995   }
996   {
997     myWriter->Key ("min");
998     myWriter->StartArray();
999     myWriter->Double (0.0);
1000     myWriter->Double (0.0);
1001     myWriter->Double (0.0);
1002     myWriter->EndArray();
1003   }*/
1004   myWriter->Key    ("type");
1005   myWriter->String ("VEC3");
1006
1007   myWriter->EndObject();
1008 #else
1009   (void )theGltfFace;
1010 #endif
1011 }
1012
1013 // =======================================================================
1014 // function : writeTextCoords
1015 // purpose  :
1016 // =======================================================================
1017 void RWGltf_CafWriter::writeTextCoords (const RWGltf_GltfFace& theGltfFace)
1018 {
1019 #ifdef HAVE_RAPIDJSON
1020   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeTextCoords()");
1021   if (theGltfFace.NodeUV.Id == RWGltf_GltfAccessor::INVALID_ID)
1022   {
1023     return;
1024   }
1025
1026   myWriter->StartObject();
1027   myWriter->Key    ("bufferView");
1028   myWriter->Int    (myBuffViewTextCoord.Id);
1029   myWriter->Key    ("byteOffset");
1030   myWriter->Int64  (theGltfFace.NodeUV.ByteOffset);
1031   myWriter->Key    ("componentType");
1032   myWriter->Int    (theGltfFace.NodeUV.ComponentType);
1033   myWriter->Key    ("count");
1034   myWriter->Int64  (theGltfFace.NodeUV.Count);
1035   // min/max values are optional, and not very useful for UV coordinates - skip them
1036   /*{
1037     myWriter->Key ("max");
1038     myWriter->StartArray();
1039     myWriter->Double (1.0);
1040     myWriter->Double (1.0);
1041     myWriter->Double (1.0);
1042     myWriter->EndArray();
1043   }
1044   {
1045     myWriter->Key ("min");
1046     myWriter->StartArray();
1047     myWriter->Double (0.0);
1048     myWriter->Double (0.0);
1049     myWriter->Double (0.0);
1050     myWriter->EndArray();
1051   }*/
1052   myWriter->Key    ("type");
1053   myWriter->String ("VEC2");
1054
1055   myWriter->EndObject();
1056 #else
1057   (void )theGltfFace;
1058 #endif
1059 }
1060
1061 // =======================================================================
1062 // function : writeIndices
1063 // purpose  :
1064 // =======================================================================
1065 void RWGltf_CafWriter::writeIndices (const RWGltf_GltfFace& theGltfFace)
1066 {
1067 #ifdef HAVE_RAPIDJSON
1068   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeIndices()");
1069   if (theGltfFace.Indices.Id == RWGltf_GltfAccessor::INVALID_ID)
1070   {
1071     return;
1072   }
1073
1074   myWriter->StartObject();
1075   myWriter->Key    ("bufferView");
1076   myWriter->Int    (myBuffViewInd.Id);
1077   myWriter->Key    ("byteOffset");
1078   myWriter->Int64  (theGltfFace.Indices.ByteOffset);
1079   myWriter->Key    ("componentType");
1080   myWriter->Int    (theGltfFace.Indices.ComponentType);
1081   myWriter->Key    ("count");
1082   myWriter->Int64  (theGltfFace.Indices.Count);
1083
1084   myWriter->Key    ("type");
1085   myWriter->String ("SCALAR");
1086
1087   myWriter->EndObject();
1088 #else
1089   (void )theGltfFace;
1090 #endif
1091 }
1092
1093 // =======================================================================
1094 // function : writeAnimations
1095 // purpose  :
1096 // =======================================================================
1097 void RWGltf_CafWriter::writeAnimations()
1098 {
1099   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeAnimations()");
1100
1101   // This section should be skipped if it doesn't contain any information but not be empty
1102   //myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Animations));
1103   //myWriter->StartArray();
1104   //myWriter->EndArray();
1105 }
1106
1107 // =======================================================================
1108 // function : writeAsset
1109 // purpose  :
1110 // =======================================================================
1111 void RWGltf_CafWriter::writeAsset (const TColStd_IndexedDataMapOfStringString& theFileInfo)
1112 {
1113 #ifdef HAVE_RAPIDJSON
1114   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeAsset()");
1115
1116   myWriter->Key    (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Asset));
1117   myWriter->StartObject();
1118   myWriter->Key    ("generator");
1119   myWriter->String ("Open CASCADE Technology [dev.opencascade.org]");
1120   myWriter->Key    ("version");
1121   myWriter->String ("2.0"); // glTF format version
1122
1123   bool anIsStarted = false;
1124   for (TColStd_IndexedDataMapOfStringString::Iterator aKeyValueIter (theFileInfo); aKeyValueIter.More(); aKeyValueIter.Next())
1125   {
1126     if (!anIsStarted)
1127     {
1128       myWriter->Key ("extras");
1129       myWriter->StartObject();
1130       anIsStarted = true;
1131     }
1132     myWriter->Key (aKeyValueIter.Key().ToCString());
1133     myWriter->String (aKeyValueIter.Value().ToCString());
1134   }
1135   if (anIsStarted)
1136   {
1137     myWriter->EndObject();
1138   }
1139
1140   myWriter->EndObject();
1141 #else
1142   (void )theFileInfo;
1143 #endif
1144 }
1145
1146 // =======================================================================
1147 // function : writeBufferViews
1148 // purpose  :
1149 // =======================================================================
1150 void RWGltf_CafWriter::writeBufferViews (const Standard_Integer theBinDataBufferId)
1151 {
1152 #ifdef HAVE_RAPIDJSON
1153   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeBufferViews()");
1154
1155   int aBuffViewId = 0;
1156   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_BufferViews));
1157   myWriter->StartArray();
1158   if (myBuffViewPos.Id != RWGltf_GltfAccessor::INVALID_ID)
1159   {
1160     aBuffViewId++;
1161     myWriter->StartObject();
1162     myWriter->Key    ("buffer");
1163     myWriter->Int    (theBinDataBufferId);
1164     myWriter->Key    ("byteLength");
1165     myWriter->Int64  (myBuffViewPos.ByteLength);
1166     myWriter->Key    ("byteOffset");
1167     myWriter->Int64  (myBuffViewPos.ByteOffset);
1168     myWriter->Key    ("byteStride");
1169     myWriter->Int64  (myBuffViewPos.ByteStride);
1170     myWriter->Key    ("target");
1171     myWriter->Int    (myBuffViewPos.Target);
1172     myWriter->EndObject();
1173   }
1174   if (myBuffViewNorm.Id != RWGltf_GltfAccessor::INVALID_ID)
1175   {
1176     aBuffViewId++;
1177     myWriter->StartObject();
1178     myWriter->Key    ("buffer");
1179     myWriter->Int    (theBinDataBufferId);
1180     myWriter->Key    ("byteLength");
1181     myWriter->Int64  (myBuffViewNorm.ByteLength);
1182     myWriter->Key    ("byteOffset");
1183     myWriter->Int64  (myBuffViewNorm.ByteOffset);
1184     myWriter->Key    ("byteStride");
1185     myWriter->Int64  (myBuffViewNorm.ByteStride);
1186     myWriter->Key    ("target");
1187     myWriter->Int    (myBuffViewNorm.Target);
1188     myWriter->EndObject();
1189   }
1190   if (myBuffViewTextCoord.Id != RWGltf_GltfAccessor::INVALID_ID)
1191   {
1192     aBuffViewId++;
1193     myWriter->StartObject();
1194     myWriter->Key    ("buffer");
1195     myWriter->Int    (theBinDataBufferId);
1196     myWriter->Key    ("byteLength");
1197     myWriter->Int64  (myBuffViewTextCoord.ByteLength);
1198     myWriter->Key    ("byteOffset");
1199     myWriter->Int64  (myBuffViewTextCoord.ByteOffset);
1200     myWriter->Key    ("byteStride");
1201     myWriter->Int64  (myBuffViewTextCoord.ByteStride);
1202     myWriter->Key    ("target");
1203     myWriter->Int    (myBuffViewTextCoord.Target);
1204     myWriter->EndObject();
1205   }
1206   if (myBuffViewInd.Id != RWGltf_GltfAccessor::INVALID_ID)
1207   {
1208     aBuffViewId++;
1209     myWriter->StartObject();
1210     myWriter->Key    ("buffer");
1211     myWriter->Int    (theBinDataBufferId);
1212     myWriter->Key    ("byteLength");
1213     myWriter->Int64  (myBuffViewInd.ByteLength);
1214     myWriter->Key    ("byteOffset");
1215     myWriter->Int64  (myBuffViewInd.ByteOffset);
1216     myWriter->Key    ("target");
1217     myWriter->Int    (myBuffViewInd.Target);
1218     myWriter->EndObject();
1219   }
1220
1221   myMaterialMap->FlushGlbBufferViews (myWriter.get(), theBinDataBufferId, aBuffViewId);
1222
1223   myWriter->EndArray();
1224 #else
1225   (void )theBinDataBufferId;
1226 #endif
1227 }
1228
1229 // =======================================================================
1230 // function : writeBuffers
1231 // purpose  :
1232 // =======================================================================
1233 void RWGltf_CafWriter::writeBuffers()
1234 {
1235 #ifdef HAVE_RAPIDJSON
1236   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeBuffers()");
1237
1238   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Buffers));
1239   myWriter->StartArray();
1240   {
1241     myWriter->StartObject();
1242     {
1243       myWriter->Key   ("byteLength");
1244       myWriter->Int64 (myBinDataLen64);
1245       if (!myIsBinary)
1246       {
1247         myWriter->Key   ("uri");
1248         myWriter->String (myBinFileNameShort.ToCString());
1249       }
1250     }
1251     myWriter->EndObject();
1252   }
1253   myWriter->EndArray();
1254 #endif
1255 }
1256
1257 // =======================================================================
1258 // function : writeExtensions
1259 // purpose  :
1260 // =======================================================================
1261 void RWGltf_CafWriter::writeExtensions()
1262 {
1263   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeExtensions()");
1264 }
1265
1266 // =======================================================================
1267 // function : writeImages
1268 // purpose  :
1269 // =======================================================================
1270 void RWGltf_CafWriter::writeImages (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
1271 {
1272 #ifdef HAVE_RAPIDJSON
1273   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeImages()");
1274
1275   // empty RWGltf_GltfRootElement_Images section should NOT be written to avoid validator errors
1276   if (myIsBinary
1277    && myToEmbedTexturesInGlb)
1278   {
1279     myMaterialMap->FlushGlbImages (myWriter.get());
1280   }
1281   else
1282   {
1283     bool anIsStarted = false;
1284     for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter(theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
1285     {
1286       const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
1287       for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
1288       {
1289         myMaterialMap->AddImages (myWriter.get(), aFaceIter.FaceStyle(), anIsStarted);
1290       }
1291     }
1292     if (anIsStarted)
1293     {
1294       myWriter->EndArray();
1295     }
1296   }
1297 #else
1298   (void )theSceneNodeMap;
1299 #endif
1300 }
1301
1302 // =======================================================================
1303 // function : writeMaterials
1304 // purpose  :
1305 // =======================================================================
1306 void RWGltf_CafWriter::writeMaterials (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
1307 {
1308 #ifdef HAVE_RAPIDJSON
1309   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeMaterials()");
1310
1311   // empty RWGltf_GltfRootElement_Materials section should NOT be written to avoid validator errors
1312   bool anIsStarted = false;
1313   for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
1314   {
1315     const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
1316     for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
1317     {
1318       myMaterialMap->AddMaterial (myWriter.get(), aFaceIter.FaceStyle(), anIsStarted);
1319     }
1320   }
1321   if (anIsStarted)
1322   {
1323     myWriter->EndArray();
1324   }
1325 #else
1326   (void )theSceneNodeMap;
1327 #endif
1328 }
1329
1330 // =======================================================================
1331 // function : writePrimArray
1332 // purpose  :
1333 // =======================================================================
1334 void RWGltf_CafWriter::writePrimArray (const RWGltf_GltfFace& theGltfFace,
1335                                        const TCollection_AsciiString& theName,
1336                                        bool& theToStartPrims)
1337 {
1338 #ifdef HAVE_RAPIDJSON
1339   if (theToStartPrims)
1340   {
1341     theToStartPrims = false;
1342     myWriter->StartObject();
1343     if (!theName.IsEmpty())
1344     {
1345       myWriter->Key ("name");
1346       myWriter->String (theName.ToCString());
1347     }
1348     myWriter->Key ("primitives");
1349     myWriter->StartArray();
1350   }
1351
1352   const TCollection_AsciiString aMatId = myMaterialMap->FindMaterial (theGltfFace.Style);
1353   myWriter->StartObject();
1354   {
1355     myWriter->Key ("attributes");
1356     myWriter->StartObject();
1357     {
1358       if (theGltfFace.NodeNorm.Id != RWGltf_GltfAccessor::INVALID_ID)
1359       {
1360         myWriter->Key ("NORMAL");
1361         myWriter->Int (theGltfFace.NodeNorm.Id);
1362       }
1363       myWriter->Key ("POSITION");
1364       myWriter->Int (theGltfFace.NodePos.Id);
1365       if (theGltfFace.NodeUV.Id != RWGltf_GltfAccessor::INVALID_ID)
1366       {
1367         myWriter->Key ("TEXCOORD_0");
1368         myWriter->Int (theGltfFace.NodeUV.Id);
1369       }
1370     }
1371     myWriter->EndObject();
1372
1373     myWriter->Key ("indices");
1374     myWriter->Int (theGltfFace.Indices.Id);
1375     if (!aMatId.IsEmpty())
1376     {
1377       myWriter->Key ("material");
1378       myWriter->Int (aMatId.IntegerValue());
1379     }
1380     myWriter->Key ("mode");
1381     myWriter->Int (RWGltf_GltfPrimitiveMode_Triangles);
1382   }
1383   myWriter->EndObject();
1384 #else
1385   (void )theGltfFace;
1386   (void )theName;
1387   (void )theToStartPrims;
1388 #endif
1389 }
1390
1391 // =======================================================================
1392 // function : writeMeshes
1393 // purpose  :
1394 // =======================================================================
1395 void RWGltf_CafWriter::writeMeshes (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
1396 {
1397 #ifdef HAVE_RAPIDJSON
1398   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeMeshes()");
1399
1400   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Meshes));
1401   myWriter->StartArray();
1402
1403   NCollection_Map<Handle(RWGltf_GltfFaceList)> aWrittenFaces;
1404   for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
1405   {
1406     const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
1407     const TCollection_AsciiString aNodeName = formatName (myMeshNameFormat, aDocNode.Label, aDocNode.RefLabel);
1408
1409     bool toStartPrims = true;
1410     Standard_Integer aNbFacesInNode = 0;
1411     aWrittenFaces.Clear (false);
1412     if (myToMergeFaces)
1413     {
1414       TopoDS_Shape aShape;
1415       if (!XCAFDoc_ShapeTool::GetShape (aDocNode.RefLabel, aShape)
1416       ||  aShape.IsNull())
1417       {
1418         continue;
1419       }
1420
1421       Handle(RWGltf_GltfFaceList) aGltfFaceList;
1422       aShape.Location (TopLoc_Location());
1423       myBinDataMap.FindFromKey (aShape, aGltfFaceList);
1424       if (!aWrittenFaces.Add (aGltfFaceList))
1425       {
1426         continue;
1427       }
1428
1429       for (RWGltf_GltfFaceList::Iterator aFaceGroupIter (*aGltfFaceList); aFaceGroupIter.More(); aFaceGroupIter.Next())
1430       {
1431         const Handle(RWGltf_GltfFace)& aGltfFace = aFaceGroupIter.Value();
1432         writePrimArray (*aGltfFace, aNodeName, toStartPrims);
1433       }
1434     }
1435     else
1436     {
1437       for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next(), ++aNbFacesInNode)
1438       {
1439         if (toSkipFaceMesh (aFaceIter))
1440         {
1441           continue;
1442         }
1443
1444         const Handle(RWGltf_GltfFaceList)& aGltfFaceList = myBinDataMap.FindFromKey (aFaceIter.Face());
1445         if (!aWrittenFaces.Add (aGltfFaceList))
1446         {
1447           continue;
1448         }
1449
1450         const Handle(RWGltf_GltfFace)& aGltfFace = aGltfFaceList->First();
1451         writePrimArray (*aGltfFace, aNodeName, toStartPrims);
1452       }
1453     }
1454
1455     if (!toStartPrims)
1456     {
1457       myWriter->EndArray();
1458       myWriter->EndObject();
1459     }
1460   }
1461   myWriter->EndArray();
1462 #else
1463   (void )theSceneNodeMap;
1464 #endif
1465 }
1466
1467 // =======================================================================
1468 // function : writeNodes
1469 // purpose  :
1470 // =======================================================================
1471 void RWGltf_CafWriter::writeNodes (const Handle(TDocStd_Document)&  theDocument,
1472                                    const TDF_LabelSequence&         theRootLabels,
1473                                    const TColStd_MapOfAsciiString*  theLabelFilter,
1474                                    const RWGltf_GltfSceneNodeMap&   theSceneNodeMap,
1475                                    NCollection_Sequence<Standard_Integer>& theSceneRootNodeInds)
1476 {
1477 #ifdef HAVE_RAPIDJSON
1478   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeNodes()");
1479
1480   // Prepare full indexed map of scene nodes in correct order.
1481   RWGltf_GltfSceneNodeMap aSceneNodeMapWithChildren; // indexes starting from 1
1482   for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_None);
1483        aDocExplorer.More(); aDocExplorer.Next())
1484   {
1485     const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
1486     if (theLabelFilter != NULL
1487     && !theLabelFilter->Contains (aDocNode.Id))
1488     {
1489       continue;
1490     }
1491
1492     // keep empty nodes
1493     //RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), false);
1494     //if (!aFaceIter.More()) { continue; }
1495
1496     Standard_Integer aNodeIndex = aSceneNodeMapWithChildren.Add (aDocNode);
1497     if (aDocExplorer.CurrentDepth() == 0)
1498     {
1499       // save root node index (starting from 0 not 1)
1500       theSceneRootNodeInds.Append (aNodeIndex - 1);
1501     }
1502   }
1503
1504   // Write scene nodes using prepared map for correct order of array members
1505   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Nodes));
1506   myWriter->StartArray();
1507
1508   for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (aSceneNodeMapWithChildren); aSceneNodeIter.More(); aSceneNodeIter.Next())
1509   {
1510     const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
1511
1512     myWriter->StartObject();
1513     {
1514       if (aDocNode.IsAssembly)
1515       {
1516         myWriter->Key ("children");
1517         myWriter->StartArray();
1518         {
1519           for (TDF_ChildIterator aChildIter (aDocNode.RefLabel); aChildIter.More(); aChildIter.Next())
1520           {
1521             const TDF_Label& aChildLabel = aChildIter.Value();
1522             if (aChildLabel.IsNull())
1523             {
1524               continue;
1525             }
1526
1527             const TCollection_AsciiString aChildId = XCAFPrs_DocumentExplorer::DefineChildId (aChildLabel, aDocNode.Id);
1528             Standard_Integer aChildIdx = aSceneNodeMapWithChildren.FindIndex (aChildId);
1529             if (aChildIdx > 0)
1530             {
1531               myWriter->Int (aChildIdx - 1);
1532             }
1533           }
1534         }
1535         myWriter->EndArray();
1536       }
1537     }
1538     if (!aDocNode.LocalTrsf.IsIdentity())
1539     {
1540       gp_Trsf aTrsf = aDocNode.LocalTrsf.Transformation();
1541       if (aTrsf.Form() != gp_Identity)
1542       {
1543         myCSTrsf.TransformTransformation (aTrsf);
1544         const gp_Quaternion aQuaternion = aTrsf.GetRotation();
1545         const bool hasRotation = Abs (aQuaternion.X())       > gp::Resolution()
1546                               || Abs (aQuaternion.Y())       > gp::Resolution()
1547                               || Abs (aQuaternion.Z())       > gp::Resolution()
1548                               || Abs (aQuaternion.W() - 1.0) > gp::Resolution();
1549         const Standard_Real aScaleFactor = aTrsf.ScaleFactor();
1550         const bool hasScale = Abs (aScaleFactor - 1.0) > Precision::Confusion();
1551         const gp_XYZ& aTranslPart = aTrsf.TranslationPart();
1552         const bool hasTranslation = aTranslPart.SquareModulus() > gp::Resolution();
1553
1554         RWGltf_WriterTrsfFormat aTrsfFormat = myTrsfFormat;
1555         if (myTrsfFormat == RWGltf_WriterTrsfFormat_Compact)
1556         {
1557           aTrsfFormat = hasRotation && hasScale && hasTranslation
1558                       ? RWGltf_WriterTrsfFormat_Mat4
1559                       : RWGltf_WriterTrsfFormat_TRS;
1560         }
1561
1562         if (aTrsfFormat == RWGltf_WriterTrsfFormat_Mat4)
1563         {
1564           // write full matrix
1565           Graphic3d_Mat4 aMat4;
1566           aTrsf.GetMat4 (aMat4);
1567           if (!aMat4.IsIdentity())
1568           {
1569             myWriter->Key ("matrix");
1570             myWriter->StartArray();
1571             for (Standard_Integer aColIter = 0; aColIter < 4; ++aColIter)
1572             {
1573               for (Standard_Integer aRowIter = 0; aRowIter < 4; ++aRowIter)
1574               {
1575                 myWriter->Double (aMat4.GetValue (aRowIter, aColIter));
1576               }
1577             }
1578             myWriter->EndArray();
1579           }
1580         }
1581         else //if (aTrsfFormat == RWGltf_WriterTrsfFormat_TRS)
1582         {
1583           if (hasRotation)
1584           {
1585             myWriter->Key ("rotation");
1586             myWriter->StartArray();
1587             myWriter->Double (aQuaternion.X());
1588             myWriter->Double (aQuaternion.Y());
1589             myWriter->Double (aQuaternion.Z());
1590             myWriter->Double (aQuaternion.W());
1591             myWriter->EndArray();
1592           }
1593           if (hasScale)
1594           {
1595             myWriter->Key ("scale");
1596             myWriter->StartArray();
1597             myWriter->Double (aScaleFactor);
1598             myWriter->Double (aScaleFactor);
1599             myWriter->Double (aScaleFactor);
1600             myWriter->EndArray();
1601           }
1602           if (hasTranslation)
1603           {
1604             myWriter->Key ("translation");
1605             myWriter->StartArray();
1606             myWriter->Double (aTranslPart.X());
1607             myWriter->Double (aTranslPart.Y());
1608             myWriter->Double (aTranslPart.Z());
1609             myWriter->EndArray();
1610           }
1611         }
1612       }
1613     }
1614     if (!aDocNode.IsAssembly)
1615     {
1616       // Mesh order of current node is equal to order of this node in scene nodes map
1617       Standard_Integer aMeshIdx = theSceneNodeMap.FindIndex (aDocNode.Id);
1618       if (aMeshIdx > 0)
1619       {
1620         myWriter->Key ("mesh");
1621         myWriter->Int (aMeshIdx - 1);
1622       }
1623     }
1624     {
1625       const TCollection_AsciiString aNodeName = formatName (myNodeNameFormat, aDocNode.Label, aDocNode.RefLabel);
1626       if (!aNodeName.IsEmpty())
1627       {
1628         myWriter->Key ("name");
1629         myWriter->String (aNodeName.ToCString());
1630       }
1631     }
1632     myWriter->EndObject();
1633   }
1634   myWriter->EndArray();
1635 #else
1636   (void )theDocument;
1637   (void )theRootLabels;
1638   (void )theLabelFilter;
1639   (void )theSceneNodeMap;
1640   (void )theSceneRootNodeInds;
1641 #endif
1642 }
1643
1644 // =======================================================================
1645 // function : writeSamplers
1646 // purpose  :
1647 // =======================================================================
1648 void RWGltf_CafWriter::writeSamplers()
1649 {
1650 #ifdef HAVE_RAPIDJSON
1651   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeSamplers()");
1652   if (myMaterialMap->NbImages() == 0)
1653   {
1654     return;
1655   }
1656
1657   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Samplers));
1658   myWriter->StartArray();
1659   {
1660     myWriter->StartObject();
1661     {
1662       //myWriter->Key ("magFilter");
1663       //myWriter->Int (9729);
1664       //myWriter->Key ("minFilter");
1665       //myWriter->Int (9729);
1666     }
1667     myWriter->EndObject();
1668   }
1669   myWriter->EndArray();
1670 #endif
1671 }
1672
1673 // =======================================================================
1674 // function : writeScene
1675 // purpose  :
1676 // =======================================================================
1677 void RWGltf_CafWriter::writeScene (const Standard_Integer theDefSceneId)
1678 {
1679 #ifdef HAVE_RAPIDJSON
1680   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeScene()");
1681
1682   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Scene));
1683   myWriter->Int (theDefSceneId);
1684 #else
1685   (void )theDefSceneId;
1686 #endif
1687 }
1688
1689 // =======================================================================
1690 // function : writeScenes
1691 // purpose  :
1692 // =======================================================================
1693 void RWGltf_CafWriter::writeScenes (const NCollection_Sequence<Standard_Integer>& theSceneRootNodeInds)
1694 {
1695 #ifdef HAVE_RAPIDJSON
1696   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeScenes()");
1697
1698   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Scenes));
1699   myWriter->StartArray();
1700   {
1701     myWriter->StartObject();
1702     myWriter->Key ("nodes");
1703     myWriter->StartArray();
1704     for (NCollection_Sequence<Standard_Integer>::Iterator aRootIter (theSceneRootNodeInds); aRootIter.More(); aRootIter.Next())
1705     {
1706       myWriter->Int (aRootIter.Value());
1707     }
1708     myWriter->EndArray();
1709     myWriter->EndObject();
1710   }
1711   myWriter->EndArray();
1712 #else
1713   (void )theSceneRootNodeInds;
1714 #endif
1715 }
1716
1717 // =======================================================================
1718 // function : writeSkins
1719 // purpose  :
1720 // =======================================================================
1721 void RWGltf_CafWriter::writeSkins()
1722 {
1723   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeSkins()");
1724
1725   // This section should be skipped if it doesn't contain any information but not be empty
1726   /*myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Skins));
1727   myWriter->StartArray();
1728   myWriter->EndArray();*/
1729 }
1730
1731 // =======================================================================
1732 // function : writeTextures
1733 // purpose  :
1734 // =======================================================================
1735 void RWGltf_CafWriter::writeTextures (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
1736 {
1737 #ifdef HAVE_RAPIDJSON
1738   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeTextures()");
1739
1740   // empty RWGltf_GltfRootElement_Textures section should not be written to avoid validator errors
1741   bool anIsStarted = false;
1742   for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
1743   {
1744     const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
1745     for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
1746     {
1747       myMaterialMap->AddTextures (myWriter.get(), aFaceIter.FaceStyle(), anIsStarted);
1748     }
1749   }
1750   if (anIsStarted)
1751   {
1752     myWriter->EndArray();
1753   }
1754 #else
1755  (void )theSceneNodeMap;
1756 #endif
1757 }