0032107: Data Exchange, RWGltf_CafReader - reading glTF document back loses sharing
[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       RWGltf_StyledShape aStyledShape (aFaceIter.ExploredShape(), aDocNode.Style);
438       if (myBinDataMap.Contains (aStyledShape))
439       {
440         continue;
441       }
442
443       Handle(RWGltf_GltfFaceList) aGltfFaceList = new RWGltf_GltfFaceList();
444       myBinDataMap.Add (aStyledShape, aGltfFaceList);
445       for (; aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
446       {
447         if (toSkipFaceMesh (aFaceIter))
448         {
449           continue;
450         }
451
452         Handle(RWGltf_GltfFace) aGltfFace;
453         if (!aMergedFaces.Find (aFaceIter.FaceStyle(), aGltfFace))
454         {
455           aGltfFace = new RWGltf_GltfFace();
456           aGltfFaceList->Append (aGltfFace);
457           aGltfFace->Shape = aFaceIter.Face();
458           aGltfFace->Style = aFaceIter.FaceStyle();
459           aGltfFace->NbIndexedNodes = aFaceIter.NbNodes();
460           aMergedFaces.Bind (aFaceIter.FaceStyle(), aGltfFace);
461         }
462         else if (myToSplitIndices16
463              &&  aGltfFace->NbIndexedNodes < std::numeric_limits<uint16_t>::max()
464              && (aGltfFace->NbIndexedNodes + aFaceIter.NbNodes()) >= std::numeric_limits<uint16_t>::max())
465         {
466           aMergedFaces.UnBind (aFaceIter.FaceStyle());
467           aGltfFace = new RWGltf_GltfFace();
468           aGltfFaceList->Append (aGltfFace);
469           aGltfFace->Shape = aFaceIter.Face();
470           aGltfFace->Style = aFaceIter.FaceStyle();
471           aGltfFace->NbIndexedNodes = aFaceIter.NbNodes();
472           aMergedFaces.Bind (aFaceIter.FaceStyle(), aGltfFace);
473         }
474         else
475         {
476           if (aGltfFace->Shape.ShapeType() != TopAbs_COMPOUND)
477           {
478             TopoDS_Shape anOldShape = aGltfFace->Shape;
479             TopoDS_Compound aComp;
480             BRep_Builder().MakeCompound (aComp);
481             BRep_Builder().Add (aComp, anOldShape);
482             aGltfFace->Shape = aComp;
483           }
484           BRep_Builder().Add (aGltfFace->Shape, aFaceIter.Face());
485           aGltfFace->NbIndexedNodes += aFaceIter.NbNodes();
486         }
487       }
488     }
489     else
490     {
491       for (; aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
492       {
493         RWGltf_StyledShape aStyledShape (aFaceIter.Face(), aFaceIter.FaceStyle());
494         if (toSkipFaceMesh (aFaceIter)
495          || myBinDataMap.Contains (aStyledShape))
496         {
497           continue;
498         }
499
500         Handle(RWGltf_GltfFaceList) aGltfFaceList = new RWGltf_GltfFaceList();
501         Handle(RWGltf_GltfFace) aGltfFace = new RWGltf_GltfFace();
502         aGltfFace->Shape = aFaceIter.Face();
503         aGltfFace->Style = aFaceIter.FaceStyle();
504         aGltfFaceList->Append (aGltfFace);
505         myBinDataMap.Add (aStyledShape, aGltfFaceList);
506       }
507     }
508   }
509
510   Standard_Integer aNbAccessors = 0;
511   NCollection_Map<Handle(RWGltf_GltfFaceList)> aWrittenFaces;
512   NCollection_DataMap<TopoDS_Shape, Handle(RWGltf_GltfFace), TopTools_ShapeMapHasher> aWrittenPrimData;
513   for (Standard_Integer aTypeIter = 0; aTypeIter < 4; ++aTypeIter)
514   {
515     const RWGltf_GltfArrayType anArrType = (RWGltf_GltfArrayType )anArrTypes[aTypeIter];
516     RWGltf_GltfBufferView* aBuffView = NULL;
517     switch (anArrType)
518     {
519       case RWGltf_GltfArrayType_Position: aBuffView = &myBuffViewPos;  break;
520       case RWGltf_GltfArrayType_Normal:   aBuffView = &myBuffViewNorm; break;
521       case RWGltf_GltfArrayType_TCoord0:  aBuffView = &myBuffViewTextCoord; break;
522       case RWGltf_GltfArrayType_Indices:  aBuffView = &myBuffViewInd; break;
523       default: break;
524     }
525     aBuffView->ByteOffset = aBinFile->tellp();
526     aWrittenFaces.Clear (false);
527     aWrittenPrimData.Clear (false);
528     for (ShapeToGltfFaceMap::Iterator aBinDataIter (myBinDataMap); aBinDataIter.More() && aPSentryBin.More(); aBinDataIter.Next())
529     {
530       const Handle(RWGltf_GltfFaceList)& aGltfFaceList = aBinDataIter.Value();
531       if (!aWrittenFaces.Add (aGltfFaceList)) // skip repeating faces
532       {
533         continue;
534       }
535
536       for (RWGltf_GltfFaceList::Iterator aGltfFaceIter (*aGltfFaceList); aGltfFaceIter.More() && aPSentryBin.More(); aGltfFaceIter.Next())
537       {
538         const Handle(RWGltf_GltfFace)& aGltfFace = aGltfFaceIter.Value();
539
540         Handle(RWGltf_GltfFace) anOldGltfFace;
541         if (aWrittenPrimData.Find (aGltfFace->Shape, anOldGltfFace))
542         {
543           switch (anArrType)
544           {
545             case RWGltf_GltfArrayType_Position:
546             {
547               aGltfFace->NodePos = anOldGltfFace->NodePos;
548               break;
549             }
550             case RWGltf_GltfArrayType_Normal:
551             {
552               aGltfFace->NodeNorm = anOldGltfFace->NodeNorm;
553               break;
554             }
555             case RWGltf_GltfArrayType_TCoord0:
556             {
557               aGltfFace->NodeUV = anOldGltfFace->NodeUV;
558               break;
559             }
560             case RWGltf_GltfArrayType_Indices:
561             {
562               aGltfFace->Indices = anOldGltfFace->Indices;
563               break;
564             }
565             default:
566             {
567               break;
568             }
569           }
570           continue;
571         }
572         aWrittenPrimData.Bind (aGltfFace->Shape, aGltfFace);
573
574         for (RWMesh_FaceIterator aFaceIter (aGltfFace->Shape, aGltfFace->Style); aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
575         {
576           switch (anArrType)
577           {
578             case RWGltf_GltfArrayType_Position:
579             {
580               aGltfFace->NbIndexedNodes = 0; // reset to zero before RWGltf_GltfArrayType_Indices step
581               saveNodes (*aGltfFace, *aBinFile, aFaceIter, aNbAccessors);
582               break;
583             }
584             case RWGltf_GltfArrayType_Normal:
585             {
586               saveNormals (*aGltfFace, *aBinFile, aFaceIter, aNbAccessors);
587               break;
588             }
589             case RWGltf_GltfArrayType_TCoord0:
590             {
591               saveTextCoords (*aGltfFace, *aBinFile, aFaceIter, aNbAccessors);
592               break;
593             }
594             case RWGltf_GltfArrayType_Indices:
595             {
596               saveIndices (*aGltfFace, *aBinFile, aFaceIter, aNbAccessors);
597               break;
598             }
599             default:
600             {
601               break;
602             }
603           }
604
605           if (!aBinFile->good())
606           {
607             Message::SendFail (TCollection_AsciiString ("File '") + myBinFileNameFull + "' cannot be written");
608             return false;
609           }
610         }
611
612         // add alignment by 4 bytes (might happen on RWGltf_GltfAccessorCompType_UInt16 indices)
613         int64_t aContentLen64 = (int64_t)aBinFile->tellp();
614         while (aContentLen64 % 4 != 0)
615         {
616           aBinFile->write (" ", 1);
617           ++aContentLen64;
618         }
619       }
620     }
621
622     aBuffView->ByteLength = (int64_t )aBinFile->tellp() - aBuffView->ByteOffset;
623     if (!aPSentryBin.More())
624     {
625       return false;
626     }
627
628     aPSentryBin.Next();
629   }
630
631   if (myIsBinary
632    && myToEmbedTexturesInGlb)
633   {
634     // save unique image textures
635     for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
636          aDocExplorer.More() && aPSentryBin.More(); aDocExplorer.Next())
637     {
638       const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
639       if (theLabelFilter != NULL
640       && !theLabelFilter->Contains (aDocNode.Id))
641       {
642         continue;
643       }
644
645       for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style);
646            aFaceIter.More(); aFaceIter.Next())
647       {
648         if (toSkipFaceMesh (aFaceIter))
649         {
650           continue;
651         }
652
653         myMaterialMap->AddGlbImages (*aBinFile, aFaceIter.FaceStyle());
654       }
655     }
656   }
657
658   int aBuffViewId = 0;
659   if (myBuffViewPos.ByteLength > 0)
660   {
661     myBuffViewPos.Id = aBuffViewId++;
662   }
663   if (myBuffViewNorm.ByteLength > 0)
664   {
665     myBuffViewNorm.Id = aBuffViewId++;
666   }
667   if (myBuffViewTextCoord.ByteLength > 0)
668   {
669     myBuffViewTextCoord.Id = aBuffViewId++;
670   }
671   if (myBuffViewInd.ByteLength > 0)
672   {
673     myBuffViewInd.Id = aBuffViewId++;
674   }
675   // myMaterialMap->FlushGlbBufferViews() will put image bufferView's IDs at the end of list
676
677   myBinDataLen64 = aBinFile->tellp();
678   aBinFile->flush();
679   if (!aBinFile->good())
680   {
681     Message::SendFail (TCollection_AsciiString ("File '") + myBinFileNameFull + "' cannot be written");
682     return false;
683   }
684   aBinFile.reset();
685   return true;
686 }
687
688 //================================================================
689 // Function : writeJson
690 // Purpose  :
691 //================================================================
692 bool RWGltf_CafWriter::writeJson (const Handle(TDocStd_Document)&  theDocument,
693                                   const TDF_LabelSequence&         theRootLabels,
694                                   const TColStd_MapOfAsciiString*  theLabelFilter,
695                                   const TColStd_IndexedDataMapOfStringString& theFileInfo,
696                                   const Message_ProgressRange& theProgress)
697 {
698 #ifdef HAVE_RAPIDJSON
699   myWriter.reset();
700
701   // write vertex arrays
702   Message_ProgressScope aPSentryBin (theProgress, "Header data", 2);
703
704   const Standard_Integer aBinDataBufferId = 0;
705   const Standard_Integer aDefSceneId      = 0;
706
707   const TCollection_AsciiString aFileNameGltf = myFile;
708   const Handle(OSD_FileSystem)& aFileSystem = OSD_FileSystem::DefaultFileSystem();
709   opencascade::std::shared_ptr<std::ostream> aGltfContentFile = aFileSystem->OpenOStream (aFileNameGltf, std::ios::out | std::ios::binary);
710   if (aGltfContentFile.get() == NULL
711    || !aGltfContentFile->good())
712   {
713     Message::SendFail (TCollection_AsciiString ("File '") + aFileNameGltf + "' can not be created");
714     return false;
715   }
716   if (myIsBinary)
717   {
718     const char* aMagic = "glTF";
719     uint32_t aVersion       = 2;
720     uint32_t aLength        = 0;
721     uint32_t aContentLength = 0;
722     uint32_t aContentType   = 0x4E4F534A;
723
724     aGltfContentFile->write (aMagic, 4);
725     aGltfContentFile->write ((const char* )&aVersion,       sizeof(aVersion));
726     aGltfContentFile->write ((const char* )&aLength,        sizeof(aLength));
727     aGltfContentFile->write ((const char* )&aContentLength, sizeof(aContentLength));
728     aGltfContentFile->write ((const char* )&aContentType,   sizeof(aContentType));
729   }
730
731   // Prepare an indexed map of scene nodes (without assemblies) in correct order.
732   // Note: this is also order of meshes in glTF document array.
733   RWGltf_GltfSceneNodeMap aSceneNodeMap;
734   for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
735        aDocExplorer.More(); aDocExplorer.Next())
736   {
737     const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
738     if (theLabelFilter != NULL
739     && !theLabelFilter->Contains (aDocNode.Id))
740     {
741       continue;
742     }
743
744     bool hasMeshData = false;
745     if (!aDocNode.IsAssembly)
746     {
747       for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
748       {
749         if (!toSkipFaceMesh (aFaceIter))
750         {
751           hasMeshData = true;
752           break;
753         }
754       }
755     }
756     if (hasMeshData)
757     {
758       aSceneNodeMap.Add (aDocNode);
759     }
760     else
761     {
762       // glTF disallows empty meshes / primitive arrays
763       const TCollection_AsciiString aNodeName = formatName (RWMesh_NameFormat_ProductOrInstance, aDocNode.Label, aDocNode.RefLabel);
764       Message::SendWarning (TCollection_AsciiString("RWGltf_CafWriter skipped node '") + aNodeName + "' without triangulation data");
765     }
766   }
767
768   rapidjson::OStreamWrapper aFileStream (*aGltfContentFile);
769   myWriter.reset (new RWGltf_GltfOStreamWriter (aFileStream));
770
771   myWriter->StartObject();
772
773   writeAccessors (aSceneNodeMap);
774   writeAnimations();
775   writeAsset (theFileInfo);
776   writeBufferViews (aBinDataBufferId);
777   writeBuffers();
778   writeExtensions();
779
780   writeImages    (aSceneNodeMap);
781   writeMaterials (aSceneNodeMap);
782   writeMeshes    (aSceneNodeMap);
783
784   aPSentryBin.Next();
785   if (!aPSentryBin.More())
786   {
787     return false;
788   }
789
790   // root nodes indices starting from 0
791   NCollection_Sequence<Standard_Integer> aSceneRootNodeInds;
792   writeNodes (theDocument, theRootLabels, theLabelFilter, aSceneNodeMap, aSceneRootNodeInds);
793   writeSamplers();
794   writeScene (aDefSceneId);
795   writeScenes (aSceneRootNodeInds);
796   writeSkins();
797   writeTextures (aSceneNodeMap);
798
799   myWriter->EndObject();
800
801   if (!myIsBinary)
802   {
803     aGltfContentFile->flush();
804     if (!aGltfContentFile->good())
805     {
806       Message::SendFail (TCollection_AsciiString ("File '") + aFileNameGltf + "' can not be written");
807       return false;
808     }
809     aGltfContentFile.reset();
810     return true;
811   }
812
813   int64_t aContentLen64 = (int64_t )aGltfContentFile->tellp() - 20;
814   while (aContentLen64 % 4 != 0)
815   {
816     aGltfContentFile->write (" ", 1);
817     ++aContentLen64;
818   }
819
820   const uint32_t aBinLength = (uint32_t )myBinDataLen64;
821   const uint32_t aBinType   = 0x004E4942;
822   aGltfContentFile->write ((const char*)&aBinLength, 4);
823   aGltfContentFile->write ((const char*)&aBinType,   4);
824
825   const int64_t aFullLen64 = aContentLen64 + 20 + myBinDataLen64 + 8;
826   if (aFullLen64 < std::numeric_limits<uint32_t>::max())
827   {
828     {
829       opencascade::std::shared_ptr<std::istream> aBinFile = aFileSystem->OpenIStream (myBinFileNameFull, std::ios::in | std::ios::binary);
830       if (aBinFile.get() == NULL || !aBinFile->good())
831       {
832         Message::SendFail (TCollection_AsciiString ("File '") + myBinFileNameFull + "' cannot be opened");
833         return false;
834       }
835       char aBuffer[4096];
836       for (; aBinFile->good();)
837       {
838         aBinFile->read (aBuffer, 4096);
839         const Standard_Integer aReadLen = (Standard_Integer )aBinFile->gcount();
840         if (aReadLen == 0)
841         {
842           break;
843         }
844         aGltfContentFile->write (aBuffer, aReadLen);
845       }
846     }
847     OSD_Path aBinFilePath (myBinFileNameFull);
848     OSD_File (aBinFilePath).Remove();
849     if (OSD_File (aBinFilePath).Exists())
850     {
851       Message::SendFail (TCollection_AsciiString ("Unable to remove temporary glTF content file '") + myBinFileNameFull + "'");
852     }
853   }
854   else
855   {
856     Message::SendFail ("glTF file content is too big for binary format");
857     return false;
858   }
859
860   const uint32_t aLength        = (uint32_t )aFullLen64;
861   const uint32_t aContentLength = (uint32_t )aContentLen64;
862   aGltfContentFile->seekp (8);
863   aGltfContentFile->write ((const char* )&aLength,        4);
864   aGltfContentFile->write ((const char* )&aContentLength, 4);
865
866   aGltfContentFile->flush();
867   if (!aGltfContentFile->good())
868   {
869     Message::SendFail (TCollection_AsciiString ("File '") + aFileNameGltf + "' can not be written");
870     return false;
871   }
872   aGltfContentFile.reset();
873   myWriter.reset();
874   return true;
875 #else
876   (void )theDocument;
877   (void )theRootLabels;
878   (void )theLabelFilter;
879   (void )theFileInfo;
880   (void )theProgress;
881   Message::SendFail ("Error: glTF writer is unavailable - OCCT has been built without RapidJSON support [HAVE_RAPIDJSON undefined]");
882   return false;
883 #endif
884 }
885
886 // =======================================================================
887 // function : writeAccessors
888 // purpose  :
889 // =======================================================================
890 void RWGltf_CafWriter::writeAccessors (const RWGltf_GltfSceneNodeMap& )
891 {
892 #ifdef HAVE_RAPIDJSON
893   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeAccessors()");
894
895   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Accessors));
896   myWriter->StartArray();
897
898   const RWGltf_GltfArrayType anArrTypes[4] =
899   {
900     RWGltf_GltfArrayType_Position,
901     RWGltf_GltfArrayType_Normal,
902     RWGltf_GltfArrayType_TCoord0,
903     RWGltf_GltfArrayType_Indices
904   };
905   NCollection_Map<Handle(RWGltf_GltfFaceList)> aWrittenFaces;
906   NCollection_Map<int> aWrittenIds;
907   int aNbAccessors = 0;
908   for (Standard_Integer aTypeIter = 0; aTypeIter < 4; ++aTypeIter)
909   {
910     const RWGltf_GltfArrayType anArrType = (RWGltf_GltfArrayType )anArrTypes[aTypeIter];
911     aWrittenFaces.Clear (false);
912     for (ShapeToGltfFaceMap::Iterator aBinDataIter (myBinDataMap); aBinDataIter.More(); aBinDataIter.Next())
913     {
914       const Handle(RWGltf_GltfFaceList)& aGltfFaceList = aBinDataIter.Value();
915       if (!aWrittenFaces.Add (aGltfFaceList)) // skip repeating faces
916       {
917         continue;
918       }
919
920       for (RWGltf_GltfFaceList::Iterator aFaceIter (*aGltfFaceList); aFaceIter.More(); aFaceIter.Next())
921       {
922         const Handle(RWGltf_GltfFace)& aGltfFace = aFaceIter.Value();
923         switch (anArrType)
924         {
925           case RWGltf_GltfArrayType_Position:
926           {
927             const int anAccessorId = aGltfFace->NodePos.Id;
928             if (anAccessorId == RWGltf_GltfAccessor::INVALID_ID
929             || !aWrittenIds.Add (anAccessorId))
930             {
931               break;
932             }
933
934             if (anAccessorId != aNbAccessors)
935             {
936               throw Standard_ProgramError ("Internal error: RWGltf_CafWriter::writeAccessors()");
937             }
938             ++aNbAccessors;
939             writePositions (*aGltfFace);
940             break;
941           }
942           case RWGltf_GltfArrayType_Normal:
943           {
944             const int anAccessorId = aGltfFace->NodeNorm.Id;
945             if (anAccessorId == RWGltf_GltfAccessor::INVALID_ID
946             || !aWrittenIds.Add (anAccessorId))
947             {
948               break;
949             }
950
951             if (anAccessorId != aNbAccessors)
952             {
953               throw Standard_ProgramError ("Internal error: RWGltf_CafWriter::writeAccessors()");
954             }
955             ++aNbAccessors;
956             writeNormals (*aGltfFace);
957             break;
958           }
959           case RWGltf_GltfArrayType_TCoord0:
960           {
961             const int anAccessorId = aGltfFace->NodeUV.Id;
962             if (anAccessorId == RWGltf_GltfAccessor::INVALID_ID
963             || !aWrittenIds.Add (anAccessorId)
964              )
965             {
966               break;
967             }
968
969             if (anAccessorId != aNbAccessors)
970             {
971               throw Standard_ProgramError ("Internal error: RWGltf_CafWriter::writeAccessors()");
972             }
973             ++aNbAccessors;
974             writeTextCoords (*aGltfFace);
975             break;
976           }
977           case RWGltf_GltfArrayType_Indices:
978           {
979             const int anAccessorId = aGltfFace->Indices.Id;
980             if (anAccessorId == RWGltf_GltfAccessor::INVALID_ID
981             || !aWrittenIds.Add (anAccessorId)
982              )
983             {
984               break;
985             }
986
987             if (anAccessorId != aNbAccessors)
988             {
989               throw Standard_ProgramError ("Internal error: RWGltf_CafWriter::writeAccessors()");
990             }
991             ++aNbAccessors;
992             writeIndices (*aGltfFace);
993             break;
994           }
995           default:
996           {
997             break;
998           }
999         }
1000       }
1001     }
1002   }
1003
1004   myWriter->EndArray();
1005 #endif
1006 }
1007
1008 // =======================================================================
1009 // function : writePositions
1010 // purpose  :
1011 // =======================================================================
1012 void RWGltf_CafWriter::writePositions (const RWGltf_GltfFace& theGltfFace)
1013 {
1014 #ifdef HAVE_RAPIDJSON
1015   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writePositions()");
1016   if (theGltfFace.NodePos.Id == RWGltf_GltfAccessor::INVALID_ID)
1017   {
1018     return;
1019   }
1020
1021   myWriter->StartObject();
1022   myWriter->Key    ("bufferView");
1023   myWriter->Int    (myBuffViewPos.Id);
1024   myWriter->Key    ("byteOffset");
1025   myWriter->Int64  (theGltfFace.NodePos.ByteOffset);
1026   myWriter->Key    ("componentType");
1027   myWriter->Int    (theGltfFace.NodePos.ComponentType);
1028   myWriter->Key    ("count");
1029   myWriter->Int64  (theGltfFace.NodePos.Count);
1030
1031   if (theGltfFace.NodePos.BndBox.IsValid())
1032   {
1033     myWriter->Key ("max");
1034     myWriter->StartArray();
1035     myWriter->Double (theGltfFace.NodePos.BndBox.CornerMax().x());
1036     myWriter->Double (theGltfFace.NodePos.BndBox.CornerMax().y());
1037     myWriter->Double (theGltfFace.NodePos.BndBox.CornerMax().z());
1038     myWriter->EndArray();
1039
1040     myWriter->Key("min");
1041     myWriter->StartArray();
1042     myWriter->Double (theGltfFace.NodePos.BndBox.CornerMin().x());
1043     myWriter->Double (theGltfFace.NodePos.BndBox.CornerMin().y());
1044     myWriter->Double (theGltfFace.NodePos.BndBox.CornerMin().z());
1045     myWriter->EndArray();
1046   }
1047   myWriter->Key    ("type");
1048   myWriter->String ("VEC3");
1049
1050   myWriter->EndObject();
1051 #else
1052   (void )theGltfFace;
1053 #endif
1054 }
1055
1056 // =======================================================================
1057 // function : writeNormals
1058 // purpose  :
1059 // =======================================================================
1060 void RWGltf_CafWriter::writeNormals (const RWGltf_GltfFace& theGltfFace)
1061 {
1062 #ifdef HAVE_RAPIDJSON
1063   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeNormals()");
1064   if (theGltfFace.NodeNorm.Id == RWGltf_GltfAccessor::INVALID_ID)
1065   {
1066     return;
1067   }
1068
1069   myWriter->StartObject();
1070   myWriter->Key    ("bufferView");
1071   myWriter->Int    (myBuffViewNorm.Id);
1072   myWriter->Key    ("byteOffset");
1073   myWriter->Int64  (theGltfFace.NodeNorm.ByteOffset);
1074   myWriter->Key    ("componentType");
1075   myWriter->Int    (theGltfFace.NodeNorm.ComponentType);
1076   myWriter->Key    ("count");
1077   myWriter->Int64  (theGltfFace.NodeNorm.Count);
1078   // min/max values are optional, and not very useful for normals - skip them
1079   /*{
1080     myWriter->Key ("max");
1081     myWriter->StartArray();
1082     myWriter->Double (1.0);
1083     myWriter->Double (1.0);
1084     myWriter->Double (1.0);
1085     myWriter->EndArray();
1086   }
1087   {
1088     myWriter->Key ("min");
1089     myWriter->StartArray();
1090     myWriter->Double (0.0);
1091     myWriter->Double (0.0);
1092     myWriter->Double (0.0);
1093     myWriter->EndArray();
1094   }*/
1095   myWriter->Key    ("type");
1096   myWriter->String ("VEC3");
1097
1098   myWriter->EndObject();
1099 #else
1100   (void )theGltfFace;
1101 #endif
1102 }
1103
1104 // =======================================================================
1105 // function : writeTextCoords
1106 // purpose  :
1107 // =======================================================================
1108 void RWGltf_CafWriter::writeTextCoords (const RWGltf_GltfFace& theGltfFace)
1109 {
1110 #ifdef HAVE_RAPIDJSON
1111   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeTextCoords()");
1112   if (theGltfFace.NodeUV.Id == RWGltf_GltfAccessor::INVALID_ID)
1113   {
1114     return;
1115   }
1116
1117   myWriter->StartObject();
1118   myWriter->Key    ("bufferView");
1119   myWriter->Int    (myBuffViewTextCoord.Id);
1120   myWriter->Key    ("byteOffset");
1121   myWriter->Int64  (theGltfFace.NodeUV.ByteOffset);
1122   myWriter->Key    ("componentType");
1123   myWriter->Int    (theGltfFace.NodeUV.ComponentType);
1124   myWriter->Key    ("count");
1125   myWriter->Int64  (theGltfFace.NodeUV.Count);
1126   // min/max values are optional, and not very useful for UV coordinates - skip them
1127   /*{
1128     myWriter->Key ("max");
1129     myWriter->StartArray();
1130     myWriter->Double (1.0);
1131     myWriter->Double (1.0);
1132     myWriter->Double (1.0);
1133     myWriter->EndArray();
1134   }
1135   {
1136     myWriter->Key ("min");
1137     myWriter->StartArray();
1138     myWriter->Double (0.0);
1139     myWriter->Double (0.0);
1140     myWriter->Double (0.0);
1141     myWriter->EndArray();
1142   }*/
1143   myWriter->Key    ("type");
1144   myWriter->String ("VEC2");
1145
1146   myWriter->EndObject();
1147 #else
1148   (void )theGltfFace;
1149 #endif
1150 }
1151
1152 // =======================================================================
1153 // function : writeIndices
1154 // purpose  :
1155 // =======================================================================
1156 void RWGltf_CafWriter::writeIndices (const RWGltf_GltfFace& theGltfFace)
1157 {
1158 #ifdef HAVE_RAPIDJSON
1159   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeIndices()");
1160   if (theGltfFace.Indices.Id == RWGltf_GltfAccessor::INVALID_ID)
1161   {
1162     return;
1163   }
1164
1165   myWriter->StartObject();
1166   myWriter->Key    ("bufferView");
1167   myWriter->Int    (myBuffViewInd.Id);
1168   myWriter->Key    ("byteOffset");
1169   myWriter->Int64  (theGltfFace.Indices.ByteOffset);
1170   myWriter->Key    ("componentType");
1171   myWriter->Int    (theGltfFace.Indices.ComponentType);
1172   myWriter->Key    ("count");
1173   myWriter->Int64  (theGltfFace.Indices.Count);
1174
1175   myWriter->Key    ("type");
1176   myWriter->String ("SCALAR");
1177
1178   myWriter->EndObject();
1179 #else
1180   (void )theGltfFace;
1181 #endif
1182 }
1183
1184 // =======================================================================
1185 // function : writeAnimations
1186 // purpose  :
1187 // =======================================================================
1188 void RWGltf_CafWriter::writeAnimations()
1189 {
1190   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeAnimations()");
1191
1192   // This section should be skipped if it doesn't contain any information but not be empty
1193   //myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Animations));
1194   //myWriter->StartArray();
1195   //myWriter->EndArray();
1196 }
1197
1198 // =======================================================================
1199 // function : writeAsset
1200 // purpose  :
1201 // =======================================================================
1202 void RWGltf_CafWriter::writeAsset (const TColStd_IndexedDataMapOfStringString& theFileInfo)
1203 {
1204 #ifdef HAVE_RAPIDJSON
1205   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeAsset()");
1206
1207   myWriter->Key    (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Asset));
1208   myWriter->StartObject();
1209   myWriter->Key    ("generator");
1210   myWriter->String ("Open CASCADE Technology [dev.opencascade.org]");
1211   myWriter->Key    ("version");
1212   myWriter->String ("2.0"); // glTF format version
1213
1214   bool anIsStarted = false;
1215   for (TColStd_IndexedDataMapOfStringString::Iterator aKeyValueIter (theFileInfo); aKeyValueIter.More(); aKeyValueIter.Next())
1216   {
1217     if (!anIsStarted)
1218     {
1219       myWriter->Key ("extras");
1220       myWriter->StartObject();
1221       anIsStarted = true;
1222     }
1223     myWriter->Key (aKeyValueIter.Key().ToCString());
1224     myWriter->String (aKeyValueIter.Value().ToCString());
1225   }
1226   if (anIsStarted)
1227   {
1228     myWriter->EndObject();
1229   }
1230
1231   myWriter->EndObject();
1232 #else
1233   (void )theFileInfo;
1234 #endif
1235 }
1236
1237 // =======================================================================
1238 // function : writeBufferViews
1239 // purpose  :
1240 // =======================================================================
1241 void RWGltf_CafWriter::writeBufferViews (const Standard_Integer theBinDataBufferId)
1242 {
1243 #ifdef HAVE_RAPIDJSON
1244   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeBufferViews()");
1245
1246   int aBuffViewId = 0;
1247   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_BufferViews));
1248   myWriter->StartArray();
1249   if (myBuffViewPos.Id != RWGltf_GltfAccessor::INVALID_ID)
1250   {
1251     aBuffViewId++;
1252     myWriter->StartObject();
1253     myWriter->Key    ("buffer");
1254     myWriter->Int    (theBinDataBufferId);
1255     myWriter->Key    ("byteLength");
1256     myWriter->Int64  (myBuffViewPos.ByteLength);
1257     myWriter->Key    ("byteOffset");
1258     myWriter->Int64  (myBuffViewPos.ByteOffset);
1259     myWriter->Key    ("byteStride");
1260     myWriter->Int64  (myBuffViewPos.ByteStride);
1261     myWriter->Key    ("target");
1262     myWriter->Int    (myBuffViewPos.Target);
1263     myWriter->EndObject();
1264   }
1265   if (myBuffViewNorm.Id != RWGltf_GltfAccessor::INVALID_ID)
1266   {
1267     aBuffViewId++;
1268     myWriter->StartObject();
1269     myWriter->Key    ("buffer");
1270     myWriter->Int    (theBinDataBufferId);
1271     myWriter->Key    ("byteLength");
1272     myWriter->Int64  (myBuffViewNorm.ByteLength);
1273     myWriter->Key    ("byteOffset");
1274     myWriter->Int64  (myBuffViewNorm.ByteOffset);
1275     myWriter->Key    ("byteStride");
1276     myWriter->Int64  (myBuffViewNorm.ByteStride);
1277     myWriter->Key    ("target");
1278     myWriter->Int    (myBuffViewNorm.Target);
1279     myWriter->EndObject();
1280   }
1281   if (myBuffViewTextCoord.Id != RWGltf_GltfAccessor::INVALID_ID)
1282   {
1283     aBuffViewId++;
1284     myWriter->StartObject();
1285     myWriter->Key    ("buffer");
1286     myWriter->Int    (theBinDataBufferId);
1287     myWriter->Key    ("byteLength");
1288     myWriter->Int64  (myBuffViewTextCoord.ByteLength);
1289     myWriter->Key    ("byteOffset");
1290     myWriter->Int64  (myBuffViewTextCoord.ByteOffset);
1291     myWriter->Key    ("byteStride");
1292     myWriter->Int64  (myBuffViewTextCoord.ByteStride);
1293     myWriter->Key    ("target");
1294     myWriter->Int    (myBuffViewTextCoord.Target);
1295     myWriter->EndObject();
1296   }
1297   if (myBuffViewInd.Id != RWGltf_GltfAccessor::INVALID_ID)
1298   {
1299     aBuffViewId++;
1300     myWriter->StartObject();
1301     myWriter->Key    ("buffer");
1302     myWriter->Int    (theBinDataBufferId);
1303     myWriter->Key    ("byteLength");
1304     myWriter->Int64  (myBuffViewInd.ByteLength);
1305     myWriter->Key    ("byteOffset");
1306     myWriter->Int64  (myBuffViewInd.ByteOffset);
1307     myWriter->Key    ("target");
1308     myWriter->Int    (myBuffViewInd.Target);
1309     myWriter->EndObject();
1310   }
1311
1312   myMaterialMap->FlushGlbBufferViews (myWriter.get(), theBinDataBufferId, aBuffViewId);
1313
1314   myWriter->EndArray();
1315 #else
1316   (void )theBinDataBufferId;
1317 #endif
1318 }
1319
1320 // =======================================================================
1321 // function : writeBuffers
1322 // purpose  :
1323 // =======================================================================
1324 void RWGltf_CafWriter::writeBuffers()
1325 {
1326 #ifdef HAVE_RAPIDJSON
1327   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeBuffers()");
1328
1329   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Buffers));
1330   myWriter->StartArray();
1331   {
1332     myWriter->StartObject();
1333     {
1334       myWriter->Key   ("byteLength");
1335       myWriter->Int64 (myBinDataLen64);
1336       if (!myIsBinary)
1337       {
1338         myWriter->Key   ("uri");
1339         myWriter->String (myBinFileNameShort.ToCString());
1340       }
1341     }
1342     myWriter->EndObject();
1343   }
1344   myWriter->EndArray();
1345 #endif
1346 }
1347
1348 // =======================================================================
1349 // function : writeExtensions
1350 // purpose  :
1351 // =======================================================================
1352 void RWGltf_CafWriter::writeExtensions()
1353 {
1354   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeExtensions()");
1355 }
1356
1357 // =======================================================================
1358 // function : writeImages
1359 // purpose  :
1360 // =======================================================================
1361 void RWGltf_CafWriter::writeImages (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
1362 {
1363 #ifdef HAVE_RAPIDJSON
1364   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeImages()");
1365
1366   // empty RWGltf_GltfRootElement_Images section should NOT be written to avoid validator errors
1367   if (myIsBinary
1368    && myToEmbedTexturesInGlb)
1369   {
1370     myMaterialMap->FlushGlbImages (myWriter.get());
1371   }
1372   else
1373   {
1374     bool anIsStarted = false;
1375     for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter(theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
1376     {
1377       const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
1378       for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
1379       {
1380         myMaterialMap->AddImages (myWriter.get(), aFaceIter.FaceStyle(), anIsStarted);
1381       }
1382     }
1383     if (anIsStarted)
1384     {
1385       myWriter->EndArray();
1386     }
1387   }
1388 #else
1389   (void )theSceneNodeMap;
1390 #endif
1391 }
1392
1393 // =======================================================================
1394 // function : writeMaterials
1395 // purpose  :
1396 // =======================================================================
1397 void RWGltf_CafWriter::writeMaterials (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
1398 {
1399 #ifdef HAVE_RAPIDJSON
1400   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeMaterials()");
1401
1402   // empty RWGltf_GltfRootElement_Materials section should NOT be written to avoid validator errors
1403   bool anIsStarted = false;
1404   for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
1405   {
1406     const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
1407     for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
1408     {
1409       myMaterialMap->AddMaterial (myWriter.get(), aFaceIter.FaceStyle(), anIsStarted);
1410     }
1411   }
1412   if (anIsStarted)
1413   {
1414     myWriter->EndArray();
1415   }
1416 #else
1417   (void )theSceneNodeMap;
1418 #endif
1419 }
1420
1421 // =======================================================================
1422 // function : writePrimArray
1423 // purpose  :
1424 // =======================================================================
1425 void RWGltf_CafWriter::writePrimArray (const RWGltf_GltfFace& theGltfFace,
1426                                        const TCollection_AsciiString& theName,
1427                                        bool& theToStartPrims)
1428 {
1429 #ifdef HAVE_RAPIDJSON
1430   if (theToStartPrims)
1431   {
1432     theToStartPrims = false;
1433     myWriter->StartObject();
1434     if (!theName.IsEmpty())
1435     {
1436       myWriter->Key ("name");
1437       myWriter->String (theName.ToCString());
1438     }
1439     myWriter->Key ("primitives");
1440     myWriter->StartArray();
1441   }
1442
1443   const TCollection_AsciiString aMatId = myMaterialMap->FindMaterial (theGltfFace.Style);
1444   myWriter->StartObject();
1445   {
1446     myWriter->Key ("attributes");
1447     myWriter->StartObject();
1448     {
1449       if (theGltfFace.NodeNorm.Id != RWGltf_GltfAccessor::INVALID_ID)
1450       {
1451         myWriter->Key ("NORMAL");
1452         myWriter->Int (theGltfFace.NodeNorm.Id);
1453       }
1454       myWriter->Key ("POSITION");
1455       myWriter->Int (theGltfFace.NodePos.Id);
1456       if (theGltfFace.NodeUV.Id != RWGltf_GltfAccessor::INVALID_ID)
1457       {
1458         myWriter->Key ("TEXCOORD_0");
1459         myWriter->Int (theGltfFace.NodeUV.Id);
1460       }
1461     }
1462     myWriter->EndObject();
1463
1464     myWriter->Key ("indices");
1465     myWriter->Int (theGltfFace.Indices.Id);
1466     if (!aMatId.IsEmpty())
1467     {
1468       myWriter->Key ("material");
1469       myWriter->Int (aMatId.IntegerValue());
1470     }
1471     myWriter->Key ("mode");
1472     myWriter->Int (RWGltf_GltfPrimitiveMode_Triangles);
1473   }
1474   myWriter->EndObject();
1475 #else
1476   (void )theGltfFace;
1477   (void )theName;
1478   (void )theToStartPrims;
1479 #endif
1480 }
1481
1482 // =======================================================================
1483 // function : writeMeshes
1484 // purpose  :
1485 // =======================================================================
1486 void RWGltf_CafWriter::writeMeshes (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
1487 {
1488 #ifdef HAVE_RAPIDJSON
1489   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeMeshes()");
1490
1491   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Meshes));
1492   myWriter->StartArray();
1493
1494   NCollection_Map<Handle(RWGltf_GltfFaceList)> aWrittenFaces;
1495   for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
1496   {
1497     const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
1498     const TCollection_AsciiString aNodeName = formatName (myMeshNameFormat, aDocNode.Label, aDocNode.RefLabel);
1499
1500     bool toStartPrims = true;
1501     Standard_Integer aNbFacesInNode = 0;
1502     aWrittenFaces.Clear (false);
1503     if (myToMergeFaces)
1504     {
1505       TopoDS_Shape aShape;
1506       if (!XCAFDoc_ShapeTool::GetShape (aDocNode.RefLabel, aShape)
1507       ||  aShape.IsNull())
1508       {
1509         continue;
1510       }
1511
1512       Handle(RWGltf_GltfFaceList) aGltfFaceList;
1513       aShape.Location (TopLoc_Location());
1514       RWGltf_StyledShape aStyledShape (aShape, aDocNode.Style);
1515       myBinDataMap.FindFromKey (aStyledShape, aGltfFaceList);
1516       if (!aWrittenFaces.Add (aGltfFaceList))
1517       {
1518         continue;
1519       }
1520
1521       for (RWGltf_GltfFaceList::Iterator aFaceGroupIter (*aGltfFaceList); aFaceGroupIter.More(); aFaceGroupIter.Next())
1522       {
1523         const Handle(RWGltf_GltfFace)& aGltfFace = aFaceGroupIter.Value();
1524         writePrimArray (*aGltfFace, aNodeName, toStartPrims);
1525       }
1526     }
1527     else
1528     {
1529       for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next(), ++aNbFacesInNode)
1530       {
1531         if (toSkipFaceMesh (aFaceIter))
1532         {
1533           continue;
1534         }
1535
1536         RWGltf_StyledShape aStyledShape (aFaceIter.Face(), aFaceIter.FaceStyle());
1537         const Handle(RWGltf_GltfFaceList)& aGltfFaceList = myBinDataMap.FindFromKey (aStyledShape);
1538         if (!aWrittenFaces.Add (aGltfFaceList))
1539         {
1540           continue;
1541         }
1542
1543         const Handle(RWGltf_GltfFace)& aGltfFace = aGltfFaceList->First();
1544         writePrimArray (*aGltfFace, aNodeName, toStartPrims);
1545       }
1546     }
1547
1548     if (!toStartPrims)
1549     {
1550       myWriter->EndArray();
1551       myWriter->EndObject();
1552     }
1553   }
1554   myWriter->EndArray();
1555 #else
1556   (void )theSceneNodeMap;
1557 #endif
1558 }
1559
1560 // =======================================================================
1561 // function : writeNodes
1562 // purpose  :
1563 // =======================================================================
1564 void RWGltf_CafWriter::writeNodes (const Handle(TDocStd_Document)&  theDocument,
1565                                    const TDF_LabelSequence&         theRootLabels,
1566                                    const TColStd_MapOfAsciiString*  theLabelFilter,
1567                                    const RWGltf_GltfSceneNodeMap&   theSceneNodeMap,
1568                                    NCollection_Sequence<Standard_Integer>& theSceneRootNodeInds)
1569 {
1570 #ifdef HAVE_RAPIDJSON
1571   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeNodes()");
1572
1573   // Prepare full indexed map of scene nodes in correct order.
1574   RWGltf_GltfSceneNodeMap aSceneNodeMapWithChildren; // indexes starting from 1
1575   for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_None);
1576        aDocExplorer.More(); aDocExplorer.Next())
1577   {
1578     const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
1579     if (theLabelFilter != NULL
1580     && !theLabelFilter->Contains (aDocNode.Id))
1581     {
1582       continue;
1583     }
1584
1585     // keep empty nodes
1586     //RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), false);
1587     //if (!aFaceIter.More()) { continue; }
1588
1589     Standard_Integer aNodeIndex = aSceneNodeMapWithChildren.Add (aDocNode);
1590     if (aDocExplorer.CurrentDepth() == 0)
1591     {
1592       // save root node index (starting from 0 not 1)
1593       theSceneRootNodeInds.Append (aNodeIndex - 1);
1594     }
1595   }
1596
1597   // Write scene nodes using prepared map for correct order of array members
1598   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Nodes));
1599   myWriter->StartArray();
1600
1601   for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (aSceneNodeMapWithChildren); aSceneNodeIter.More(); aSceneNodeIter.Next())
1602   {
1603     const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
1604
1605     myWriter->StartObject();
1606     {
1607       if (aDocNode.IsAssembly)
1608       {
1609         myWriter->Key ("children");
1610         myWriter->StartArray();
1611         {
1612           for (TDF_ChildIterator aChildIter (aDocNode.RefLabel); aChildIter.More(); aChildIter.Next())
1613           {
1614             const TDF_Label& aChildLabel = aChildIter.Value();
1615             if (aChildLabel.IsNull())
1616             {
1617               continue;
1618             }
1619
1620             const TCollection_AsciiString aChildId = XCAFPrs_DocumentExplorer::DefineChildId (aChildLabel, aDocNode.Id);
1621             Standard_Integer aChildIdx = aSceneNodeMapWithChildren.FindIndex (aChildId);
1622             if (aChildIdx > 0)
1623             {
1624               myWriter->Int (aChildIdx - 1);
1625             }
1626           }
1627         }
1628         myWriter->EndArray();
1629       }
1630     }
1631     if (!aDocNode.LocalTrsf.IsIdentity())
1632     {
1633       gp_Trsf aTrsf = aDocNode.LocalTrsf.Transformation();
1634       if (aTrsf.Form() != gp_Identity)
1635       {
1636         myCSTrsf.TransformTransformation (aTrsf);
1637         const gp_Quaternion aQuaternion = aTrsf.GetRotation();
1638         const bool hasRotation = Abs (aQuaternion.X())       > gp::Resolution()
1639                               || Abs (aQuaternion.Y())       > gp::Resolution()
1640                               || Abs (aQuaternion.Z())       > gp::Resolution()
1641                               || Abs (aQuaternion.W() - 1.0) > gp::Resolution();
1642         const Standard_Real aScaleFactor = aTrsf.ScaleFactor();
1643         const bool hasScale = Abs (aScaleFactor - 1.0) > Precision::Confusion();
1644         const gp_XYZ& aTranslPart = aTrsf.TranslationPart();
1645         const bool hasTranslation = aTranslPart.SquareModulus() > gp::Resolution();
1646
1647         RWGltf_WriterTrsfFormat aTrsfFormat = myTrsfFormat;
1648         if (myTrsfFormat == RWGltf_WriterTrsfFormat_Compact)
1649         {
1650           aTrsfFormat = hasRotation && hasScale && hasTranslation
1651                       ? RWGltf_WriterTrsfFormat_Mat4
1652                       : RWGltf_WriterTrsfFormat_TRS;
1653         }
1654
1655         if (aTrsfFormat == RWGltf_WriterTrsfFormat_Mat4)
1656         {
1657           // write full matrix
1658           Graphic3d_Mat4 aMat4;
1659           aTrsf.GetMat4 (aMat4);
1660           if (!aMat4.IsIdentity())
1661           {
1662             myWriter->Key ("matrix");
1663             myWriter->StartArray();
1664             for (Standard_Integer aColIter = 0; aColIter < 4; ++aColIter)
1665             {
1666               for (Standard_Integer aRowIter = 0; aRowIter < 4; ++aRowIter)
1667               {
1668                 myWriter->Double (aMat4.GetValue (aRowIter, aColIter));
1669               }
1670             }
1671             myWriter->EndArray();
1672           }
1673         }
1674         else //if (aTrsfFormat == RWGltf_WriterTrsfFormat_TRS)
1675         {
1676           if (hasRotation)
1677           {
1678             myWriter->Key ("rotation");
1679             myWriter->StartArray();
1680             myWriter->Double (aQuaternion.X());
1681             myWriter->Double (aQuaternion.Y());
1682             myWriter->Double (aQuaternion.Z());
1683             myWriter->Double (aQuaternion.W());
1684             myWriter->EndArray();
1685           }
1686           if (hasScale)
1687           {
1688             myWriter->Key ("scale");
1689             myWriter->StartArray();
1690             myWriter->Double (aScaleFactor);
1691             myWriter->Double (aScaleFactor);
1692             myWriter->Double (aScaleFactor);
1693             myWriter->EndArray();
1694           }
1695           if (hasTranslation)
1696           {
1697             myWriter->Key ("translation");
1698             myWriter->StartArray();
1699             myWriter->Double (aTranslPart.X());
1700             myWriter->Double (aTranslPart.Y());
1701             myWriter->Double (aTranslPart.Z());
1702             myWriter->EndArray();
1703           }
1704         }
1705       }
1706     }
1707     if (!aDocNode.IsAssembly)
1708     {
1709       // Mesh order of current node is equal to order of this node in scene nodes map
1710       Standard_Integer aMeshIdx = theSceneNodeMap.FindIndex (aDocNode.Id);
1711       if (aMeshIdx > 0)
1712       {
1713         myWriter->Key ("mesh");
1714         myWriter->Int (aMeshIdx - 1);
1715       }
1716     }
1717     {
1718       const TCollection_AsciiString aNodeName = formatName (myNodeNameFormat, aDocNode.Label, aDocNode.RefLabel);
1719       if (!aNodeName.IsEmpty())
1720       {
1721         myWriter->Key ("name");
1722         myWriter->String (aNodeName.ToCString());
1723       }
1724     }
1725     myWriter->EndObject();
1726   }
1727   myWriter->EndArray();
1728 #else
1729   (void )theDocument;
1730   (void )theRootLabels;
1731   (void )theLabelFilter;
1732   (void )theSceneNodeMap;
1733   (void )theSceneRootNodeInds;
1734 #endif
1735 }
1736
1737 // =======================================================================
1738 // function : writeSamplers
1739 // purpose  :
1740 // =======================================================================
1741 void RWGltf_CafWriter::writeSamplers()
1742 {
1743 #ifdef HAVE_RAPIDJSON
1744   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeSamplers()");
1745   if (myMaterialMap->NbImages() == 0)
1746   {
1747     return;
1748   }
1749
1750   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Samplers));
1751   myWriter->StartArray();
1752   {
1753     myWriter->StartObject();
1754     {
1755       //myWriter->Key ("magFilter");
1756       //myWriter->Int (9729);
1757       //myWriter->Key ("minFilter");
1758       //myWriter->Int (9729);
1759     }
1760     myWriter->EndObject();
1761   }
1762   myWriter->EndArray();
1763 #endif
1764 }
1765
1766 // =======================================================================
1767 // function : writeScene
1768 // purpose  :
1769 // =======================================================================
1770 void RWGltf_CafWriter::writeScene (const Standard_Integer theDefSceneId)
1771 {
1772 #ifdef HAVE_RAPIDJSON
1773   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeScene()");
1774
1775   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Scene));
1776   myWriter->Int (theDefSceneId);
1777 #else
1778   (void )theDefSceneId;
1779 #endif
1780 }
1781
1782 // =======================================================================
1783 // function : writeScenes
1784 // purpose  :
1785 // =======================================================================
1786 void RWGltf_CafWriter::writeScenes (const NCollection_Sequence<Standard_Integer>& theSceneRootNodeInds)
1787 {
1788 #ifdef HAVE_RAPIDJSON
1789   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeScenes()");
1790
1791   myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Scenes));
1792   myWriter->StartArray();
1793   {
1794     myWriter->StartObject();
1795     myWriter->Key ("nodes");
1796     myWriter->StartArray();
1797     for (NCollection_Sequence<Standard_Integer>::Iterator aRootIter (theSceneRootNodeInds); aRootIter.More(); aRootIter.Next())
1798     {
1799       myWriter->Int (aRootIter.Value());
1800     }
1801     myWriter->EndArray();
1802     myWriter->EndObject();
1803   }
1804   myWriter->EndArray();
1805 #else
1806   (void )theSceneRootNodeInds;
1807 #endif
1808 }
1809
1810 // =======================================================================
1811 // function : writeSkins
1812 // purpose  :
1813 // =======================================================================
1814 void RWGltf_CafWriter::writeSkins()
1815 {
1816   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeSkins()");
1817
1818   // This section should be skipped if it doesn't contain any information but not be empty
1819   /*myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Skins));
1820   myWriter->StartArray();
1821   myWriter->EndArray();*/
1822 }
1823
1824 // =======================================================================
1825 // function : writeTextures
1826 // purpose  :
1827 // =======================================================================
1828 void RWGltf_CafWriter::writeTextures (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
1829 {
1830 #ifdef HAVE_RAPIDJSON
1831   Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeTextures()");
1832
1833   // empty RWGltf_GltfRootElement_Textures section should not be written to avoid validator errors
1834   bool anIsStarted = false;
1835   for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
1836   {
1837     const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
1838     for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
1839     {
1840       myMaterialMap->AddTextures (myWriter.get(), aFaceIter.FaceStyle(), anIsStarted);
1841     }
1842   }
1843   if (anIsStarted)
1844   {
1845     myWriter->EndArray();
1846   }
1847 #else
1848  (void )theSceneNodeMap;
1849 #endif
1850 }