0030953: Data Exchange - implement export of mesh data into glTF 2.0 format
authorkgv <kgv@opencascade.com>
Mon, 18 Nov 2019 23:09:09 +0000 (02:09 +0300)
committerbugmaster <bugmaster@opencascade.com>
Sat, 23 Nov 2019 13:03:19 +0000 (16:03 +0300)
Added new class RWGltf_CafWriter for exporting XCAF document into glTF file
as well as Draw Harness command WriteGltf.

Added auxiliary method OSD_Path::FileNameAndExtension() splitting file name into Name and Extension.

26 files changed:
src/OSD/OSD_Path.cxx
src/OSD/OSD_Path.hxx
src/RWGltf/FILES
src/RWGltf/RWGltf_CafWriter.cxx [new file with mode: 0644]
src/RWGltf/RWGltf_CafWriter.hxx [new file with mode: 0644]
src/RWGltf/RWGltf_GltfJsonParser.cxx
src/RWGltf/RWGltf_GltfMaterialMap.cxx [new file with mode: 0644]
src/RWGltf/RWGltf_GltfMaterialMap.hxx [new file with mode: 0644]
src/RWGltf/RWGltf_GltfOStreamWriter.hxx [new file with mode: 0644]
src/RWGltf/RWGltf_GltfSceneNodeMap.hxx [new file with mode: 0644]
src/RWGltf/RWGltf_WriterTrsfFormat.hxx [new file with mode: 0644]
src/RWMesh/FILES
src/RWMesh/RWMesh_FaceIterator.cxx [new file with mode: 0644]
src/RWMesh/RWMesh_FaceIterator.hxx [new file with mode: 0644]
src/RWMesh/RWMesh_MaterialMap.cxx [new file with mode: 0644]
src/RWMesh/RWMesh_MaterialMap.hxx [new file with mode: 0644]
src/TKXSDRAW/EXTERNLIB
src/XCAFPrs/XCAFPrs_DocumentNode.hxx
src/XSDRAWSTLVRML/XSDRAWSTLVRML.cxx
tests/de_mesh/end
tests/de_mesh/gltf_write/ball [new file with mode: 0644]
tests/de_mesh/gltf_write/begin [new file with mode: 0644]
tests/de_mesh/gltf_write/end [new file with mode: 0644]
tests/de_mesh/gltf_write/helmet [new file with mode: 0644]
tests/de_mesh/grids.list
tests/v3d/glsl/pbr_spheres

index 5f53fa6..271a878 100644 (file)
@@ -1675,3 +1675,34 @@ void OSD_Path::FolderAndFileFromPath (const TCollection_AsciiString& theFilePath
     theFileName.Clear();
   }
 }
+
+// =======================================================================
+// function : FileNameAndExtension
+// purpose  :
+// =======================================================================
+void OSD_Path::FileNameAndExtension (const TCollection_AsciiString& theFilePath,
+                                     TCollection_AsciiString&       theName,
+                                     TCollection_AsciiString&       theExtension)
+{
+  const Standard_Integer THE_EXT_MAX_LEN = 20; // this method is supposed to be used with normal extension
+  const Standard_Integer aLen = theFilePath.Length();
+  for (Standard_Integer anExtLen = 1; anExtLen < aLen && anExtLen < THE_EXT_MAX_LEN; ++anExtLen)
+  {
+    if (theFilePath.Value (aLen - anExtLen) == '.')
+    {
+      const Standard_Integer aNameUpper = aLen - anExtLen - 1;
+      if (aNameUpper < 1)
+      {
+        break;
+      }
+
+      theName      = theFilePath.SubString (1, aNameUpper);
+      theExtension = theFilePath.SubString (aLen - anExtLen + 1, aLen);
+      theExtension.LowerCase();
+      return;
+    }
+  }
+
+  theName = theFilePath;
+  theExtension.Clear();
+}
index 397666b..011b2a0 100644 (file)
@@ -215,6 +215,18 @@ public:
                                                      TCollection_AsciiString&       theFolder,
                                                      TCollection_AsciiString&       theFileName);
 
+  //! Return file extension from the name in lower case.
+  //! Extension is expected to be within 20-symbols length, and determined as file name tail after last dot.
+  //! Example: IN  theFilePath ='Image.sbs.JPG'
+  //!          OUT theName     ='Image.sbs'
+  //!          OUT theFileName ='jpg'
+  //! @param theFilePath  [in]  file path
+  //! @param theName      [out] file name without extension
+  //! @param theExtension [out] file extension in lower case and without dot
+  Standard_EXPORT static void FileNameAndExtension (const TCollection_AsciiString& theFilePath,
+                                                    TCollection_AsciiString&       theName,
+                                                    TCollection_AsciiString&       theExtension);
+
   //! Detect absolute DOS-path also used in Windows.
   //! The total path length is limited to 256 characters.
   //! Sample path:
index d5b8199..bc32622 100644 (file)
@@ -15,9 +15,16 @@ RWGltf_MaterialCommon.hxx
 RWGltf_MaterialMetallicRoughness.hxx
 RWGltf_CafReader.cxx
 RWGltf_CafReader.hxx
+RWGltf_CafWriter.cxx
+RWGltf_CafWriter.hxx
+RWGltf_GltfMaterialMap.cxx
+RWGltf_GltfMaterialMap.hxx
 RWGltf_GltfJsonParser.cxx
 RWGltf_GltfJsonParser.pxx
+RWGltf_GltfOStreamWriter.hxx
+RWGltf_GltfSceneNodeMap.hxx
 RWGltf_PrimitiveArrayReader.cxx
 RWGltf_PrimitiveArrayReader.hxx
 RWGltf_TriangulationReader.cxx
 RWGltf_TriangulationReader.hxx
+RWGltf_WriterTrsfFormat.hxx
diff --git a/src/RWGltf/RWGltf_CafWriter.cxx b/src/RWGltf/RWGltf_CafWriter.cxx
new file mode 100644 (file)
index 0000000..80bd705
--- /dev/null
@@ -0,0 +1,1580 @@
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <RWGltf_CafWriter.hxx>
+
+#include <gp_Quaternion.hxx>
+#include <Message.hxx>
+#include <Message_Messenger.hxx>
+#include <Message_ProgressSentry.hxx>
+#include <NCollection_DataMap.hxx>
+#include <OSD_OpenFile.hxx>
+#include <OSD_File.hxx>
+#include <OSD_Path.hxx>
+#include <Poly_Triangulation.hxx>
+#include <RWGltf_GltfAccessorLayout.hxx>
+#include <RWGltf_GltfMaterialMap.hxx>
+#include <RWGltf_GltfPrimitiveMode.hxx>
+#include <RWGltf_GltfRootElement.hxx>
+#include <RWGltf_GltfSceneNodeMap.hxx>
+#include <RWMesh_FaceIterator.hxx>
+#include <TDataStd_Name.hxx>
+#include <TDF_Tool.hxx>
+#include <TDocStd_Document.hxx>
+#include <XCAFDoc_DocumentTool.hxx>
+#include <XCAFDoc_ShapeTool.hxx>
+#include <XCAFPrs_DocumentExplorer.hxx>
+
+#ifdef HAVE_RAPIDJSON
+  #include <RWGltf_GltfOStreamWriter.hxx>
+#endif
+
+IMPLEMENT_STANDARD_RTTIEXT(RWGltf_CafWriter, Standard_Transient)
+
+namespace
+{
+  //! Write three float values.
+  static void writeVec3 (std::ostream& theStream,
+                         const gp_XYZ& theVec3)
+  {
+    Graphic3d_Vec3 aVec3 (float(theVec3.X()), float(theVec3.Y()), float(theVec3.Z()));
+    theStream.write ((const char* )aVec3.GetData(), sizeof(aVec3));
+  }
+
+  //! Write three float values.
+  static void writeVec3 (std::ostream& theStream,
+                         const Graphic3d_Vec3& theVec3)
+  {
+    theStream.write ((const char* )theVec3.GetData(), sizeof(theVec3));
+  }
+
+  //! Write two float values.
+  static void writeVec2 (std::ostream& theStream,
+                         const gp_XY&  theVec2)
+  {
+    Graphic3d_Vec2 aVec2 (float(theVec2.X()), float(theVec2.Y()));
+    theStream.write ((const char* )aVec2.GetData(), sizeof(aVec2));
+  }
+
+  //! Write triangle indices.
+  static void writeTriangle32 (std::ostream& theStream,
+                               const Graphic3d_Vec3i& theTri)
+  {
+    theStream.write ((const char* )theTri.GetData(), sizeof(theTri));
+  }
+
+  //! Write triangle indices.
+  static void writeTriangle16 (std::ostream& theStream,
+                               const NCollection_Vec3<uint16_t>& theTri)
+  {
+    theStream.write ((const char* )theTri.GetData(), sizeof(theTri));
+  }
+
+#ifdef HAVE_RAPIDJSON
+  //! Read name attribute.
+  static TCollection_AsciiString readNameAttribute (const TDF_Label& theRefLabel)
+  {
+    Handle(TDataStd_Name) aNodeName;
+    if (!theRefLabel.FindAttribute (TDataStd_Name::GetID(), aNodeName))
+    {
+      return TCollection_AsciiString();
+    }
+    return TCollection_AsciiString (aNodeName->Get());
+  }
+#endif
+}
+
+//================================================================
+// Function : Constructor
+// Purpose  :
+//================================================================
+RWGltf_CafWriter::RWGltf_CafWriter (const TCollection_AsciiString& theFile,
+                                    Standard_Boolean theIsBinary)
+: myFile          (theFile),
+  myTrsfFormat    (RWGltf_WriterTrsfFormat_Compact),
+  myIsBinary      (theIsBinary),
+  myBinDataLen64  (0)
+{
+  myCSTrsf.SetOutputLengthUnit (1.0); // meters
+  myCSTrsf.SetOutputCoordinateSystem (RWMesh_CoordinateSystem_glTF);
+
+  TCollection_AsciiString aFolder, aFileName, aShortFileNameBase, aFileExt;
+  OSD_Path::FolderAndFileFromPath (theFile, aFolder, aFileName);
+  OSD_Path::FileNameAndExtension (aFileName, aShortFileNameBase, aFileExt);
+
+  myBinFileNameShort = aShortFileNameBase + ".bin" + (myIsBinary ? ".tmp" : "");
+  myBinFileNameFull = !aFolder.IsEmpty() ? aFolder + myBinFileNameShort : myBinFileNameShort;
+}
+
+//================================================================
+// Function : Destructor
+// Purpose  :
+//================================================================
+RWGltf_CafWriter::~RWGltf_CafWriter()
+{
+  myWriter.reset();
+}
+
+//================================================================
+// Function : toSkipFaceMesh
+// Purpose  :
+//================================================================
+Standard_Boolean RWGltf_CafWriter::toSkipFaceMesh (const RWMesh_FaceIterator& theFaceIter)
+{
+  return theFaceIter.IsEmptyMesh();
+}
+
+// =======================================================================
+// function : saveNodes
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::saveNodes (RWGltf_GltfFace& theGltfFace,
+                                  std::ostream& theBinFile,
+                                  const RWMesh_FaceIterator& theFaceIter,
+                                  Standard_Integer& theAccessorNb) const
+{
+  theGltfFace.NodePos.Id            = theAccessorNb++;
+  theGltfFace.NodePos.Count         = theFaceIter.NbNodes();
+  theGltfFace.NodePos.ByteOffset    = (int64_t )theBinFile.tellp() - myBuffViewPos.ByteOffset;
+  theGltfFace.NodePos.Type          = RWGltf_GltfAccessorLayout_Vec3;
+  theGltfFace.NodePos.ComponentType = RWGltf_GltfAccessorCompType_Float32;
+
+  const Standard_Integer aNodeUpper = theFaceIter.NodeUpper();
+  for (Standard_Integer aNodeIter = theFaceIter.NodeLower(); aNodeIter <= aNodeUpper; ++aNodeIter)
+  {
+    gp_XYZ aNode = theFaceIter.NodeTransformed (aNodeIter).XYZ();
+    myCSTrsf.TransformPosition (aNode);
+    theGltfFace.NodePos.BndBox.Add (Graphic3d_Vec3d(aNode.X(), aNode.Y(), aNode.Z()));
+    writeVec3 (theBinFile, aNode);
+  }
+}
+
+// =======================================================================
+// function : saveNormals
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::saveNormals (RWGltf_GltfFace& theGltfFace,
+                                    std::ostream& theBinFile,
+                                    RWMesh_FaceIterator& theFaceIter,
+                                    Standard_Integer& theAccessorNb) const
+{
+  if (!theFaceIter.HasNormals())
+  {
+    return;
+  }
+
+  theGltfFace.NodeNorm.Id            = theAccessorNb++;
+  theGltfFace.NodeNorm.Count         = theFaceIter.NbNodes();
+  theGltfFace.NodeNorm.ByteOffset    = (int64_t )theBinFile.tellp() - myBuffViewNorm.ByteOffset;
+  theGltfFace.NodeNorm.Type          = RWGltf_GltfAccessorLayout_Vec3;
+  theGltfFace.NodeNorm.ComponentType = RWGltf_GltfAccessorCompType_Float32;
+
+  const Standard_Integer aNodeUpper = theFaceIter.NodeUpper();
+  for (Standard_Integer aNodeIter = theFaceIter.NodeLower(); aNodeIter <= aNodeUpper; ++aNodeIter)
+  {
+    const gp_Dir aNormal = theFaceIter.NormalTransformed (aNodeIter);
+    Graphic3d_Vec3 aVecNormal ((float )aNormal.X(), (float )aNormal.Y(), (float )aNormal.Z());
+    myCSTrsf.TransformNormal (aVecNormal);
+    writeVec3 (theBinFile, aVecNormal);
+  }
+}
+
+// =======================================================================
+// function : saveTextCoords
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::saveTextCoords (RWGltf_GltfFace& theGltfFace,
+                                       std::ostream& theBinFile,
+                                       const RWMesh_FaceIterator& theFaceIter,
+                                       Standard_Integer& theAccessorNb) const
+{
+  if (!theFaceIter.HasTexCoords())
+  {
+    return;
+  }
+  if (!myIsForcedUVExport)
+  {
+    if (theFaceIter.FaceStyle().Material().IsNull())
+    {
+      return;
+    }
+
+    if (RWGltf_GltfMaterialMap::baseColorTexture (theFaceIter.FaceStyle().Material()).IsNull()
+     && theFaceIter.FaceStyle().Material()->PbrMaterial().MetallicRoughnessTexture.IsNull()
+     && theFaceIter.FaceStyle().Material()->PbrMaterial().EmissiveTexture.IsNull()
+     && theFaceIter.FaceStyle().Material()->PbrMaterial().OcclusionTexture.IsNull()
+     && theFaceIter.FaceStyle().Material()->PbrMaterial().NormalTexture.IsNull())
+    {
+      return;
+    }
+  }
+
+  theGltfFace.NodeUV.Id            = theAccessorNb++;
+  theGltfFace.NodeUV.Count         = theFaceIter.NbNodes();
+  theGltfFace.NodeUV.ByteOffset    = (int64_t )theBinFile.tellp() - myBuffViewTextCoord.ByteOffset;
+  theGltfFace.NodeUV.Type          = RWGltf_GltfAccessorLayout_Vec2;
+  theGltfFace.NodeUV.ComponentType = RWGltf_GltfAccessorCompType_Float32;
+  const Standard_Integer aNodeUpper = theFaceIter.NodeUpper();
+  for (Standard_Integer aNodeIter = theFaceIter.NodeLower(); aNodeIter <= aNodeUpper; ++aNodeIter)
+  {
+    gp_Pnt2d aTexCoord = theFaceIter.NodeTexCoord (aNodeIter);
+    aTexCoord.SetY (1.0 - aTexCoord.Y());
+    writeVec2 (theBinFile, aTexCoord.XY());
+  }
+}
+
+// =======================================================================
+// function : saveIndices
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::saveIndices (RWGltf_GltfFace& theGltfFace,
+                                    std::ostream& theBinFile,
+                                    const RWMesh_FaceIterator& theFaceIter,
+                                    Standard_Integer& theAccessorNb)
+{
+  theGltfFace.Indices.Id            = theAccessorNb++;
+  theGltfFace.Indices.Count         = theFaceIter.NbTriangles() * 3;
+  theGltfFace.Indices.ByteOffset    = (int64_t )theBinFile.tellp() - myBuffViewInd.ByteOffset;
+  theGltfFace.Indices.Type          = RWGltf_GltfAccessorLayout_Scalar;
+  theGltfFace.Indices.ComponentType = theGltfFace.NodePos.Count > std::numeric_limits<uint16_t>::max()
+                                    ? RWGltf_GltfAccessorCompType_UInt32
+                                    : RWGltf_GltfAccessorCompType_UInt16;
+
+  const Standard_Integer anElemLower = theFaceIter.ElemLower();
+  const Standard_Integer anElemUpper = theFaceIter.ElemUpper();
+  for (Standard_Integer anElemIter = anElemLower; anElemIter <= anElemUpper; ++anElemIter)
+  {
+    Poly_Triangle aTri = theFaceIter.TriangleOriented (anElemIter);
+    aTri(1) -= anElemLower;
+    aTri(2) -= anElemLower;
+    aTri(3) -= anElemLower;
+    if (theGltfFace.Indices.ComponentType == RWGltf_GltfAccessorCompType_UInt16)
+    {
+      writeTriangle16 (theBinFile, NCollection_Vec3<uint16_t>((uint16_t)aTri(1), (uint16_t)aTri(2), (uint16_t)aTri(3)));
+    }
+    else
+    {
+      writeTriangle32 (theBinFile, Graphic3d_Vec3i (aTri(1), aTri(2), aTri(3)));
+    }
+  }
+  if (theGltfFace.Indices.ComponentType == RWGltf_GltfAccessorCompType_UInt16)
+  {
+    // alignment by 4 bytes
+    int64_t aContentLen64 = (int64_t)theBinFile.tellp();
+    while (aContentLen64 % 4 != 0)
+    {
+      theBinFile.write (" ", 1);
+      ++aContentLen64;
+    }
+  }
+}
+
+// =======================================================================
+// function : Perform
+// purpose  :
+// =======================================================================
+bool RWGltf_CafWriter::Perform (const Handle(TDocStd_Document)& theDocument,
+                                const TColStd_IndexedDataMapOfStringString& theFileInfo,
+                                const Handle(Message_ProgressIndicator)& theProgress)
+{
+  TDF_LabelSequence aRoots;
+  Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool (theDocument->Main());
+  aShapeTool->GetFreeShapes (aRoots);
+  return Perform (theDocument, aRoots, NULL, theFileInfo, theProgress);
+}
+
+// =======================================================================
+// function : Perform
+// purpose  :
+// =======================================================================
+bool RWGltf_CafWriter::Perform (const Handle(TDocStd_Document)& theDocument,
+                                const TDF_LabelSequence& theRootLabels,
+                                const TColStd_MapOfAsciiString* theLabelFilter,
+                                const TColStd_IndexedDataMapOfStringString& theFileInfo,
+                                const Handle(Message_ProgressIndicator)& theProgress)
+{
+  Message_ProgressSentry aPSentry (theProgress, "Writing glTF file", 0, 2, 1);
+  if (!writeBinData (theDocument, theRootLabels, theLabelFilter, theProgress))
+  {
+    return false;
+  }
+
+  aPSentry.Next();
+  if (!aPSentry.More())
+  {
+    return false;
+  }
+
+  return writeJson (theDocument, theRootLabels, theLabelFilter, theFileInfo, theProgress);
+}
+
+// =======================================================================
+// function : writeBinData
+// purpose  :
+// =======================================================================
+bool RWGltf_CafWriter::writeBinData (const Handle(TDocStd_Document)& theDocument,
+                                     const TDF_LabelSequence& theRootLabels,
+                                     const TColStd_MapOfAsciiString* theLabelFilter,
+                                     const Handle(Message_ProgressIndicator)& theProgress)
+{
+  myBuffViewPos.ByteOffset       = 0;
+  myBuffViewPos.ByteLength       = 0;
+  myBuffViewPos.ByteStride       = 12;
+  myBuffViewPos.Target           = RWGltf_GltfBufferViewTarget_ARRAY_BUFFER;
+  myBuffViewNorm.ByteOffset      = 0;
+  myBuffViewNorm.ByteLength      = 0;
+  myBuffViewNorm.ByteStride      = 12;
+  myBuffViewNorm.Target          = RWGltf_GltfBufferViewTarget_ARRAY_BUFFER;
+  myBuffViewTextCoord.ByteOffset = 0;
+  myBuffViewTextCoord.ByteLength = 0;
+  myBuffViewTextCoord.ByteStride = 8;
+  myBuffViewTextCoord.Target     = RWGltf_GltfBufferViewTarget_ARRAY_BUFFER;
+  myBuffViewInd.ByteOffset       = 0;
+  myBuffViewInd.ByteLength       = 0;
+  myBuffViewInd.Target           = RWGltf_GltfBufferViewTarget_ELEMENT_ARRAY_BUFFER;
+
+  myBinDataMap.Clear();
+  myBinDataLen64 = 0;
+
+  std::ofstream aBinFile;
+  OSD_OpenStream (aBinFile, myBinFileNameFull.ToCString(), std::ios::out | std::ios::binary);
+  if (!aBinFile.is_open()
+   || !aBinFile.good())
+  {
+    Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + myBinFileNameFull + "' can not be created!", Message_Fail);
+    return false;
+  }
+
+  Message_ProgressSentry aPSentryBin (theProgress, "Binary data", 0, 4, 1);
+
+  Standard_Integer aNbAccessors = 0;
+
+  // write positions
+  myBuffViewPos.ByteOffset = aBinFile.tellp();
+  for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
+       aDocExplorer.More() && aPSentryBin.More(); aDocExplorer.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
+    if (theLabelFilter != NULL
+    && !theLabelFilter->Contains (aDocNode.Id))
+    {
+      continue;
+    }
+
+    // transformation will be stored at scene nodes
+    for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
+    {
+      if (myBinDataMap.IsBound (aFaceIter.Face())
+       || toSkipFaceMesh (aFaceIter))
+      {
+        continue;
+      }
+
+      RWGltf_GltfFace aGltfFace;
+      saveNodes (aGltfFace, aBinFile, aFaceIter, aNbAccessors);
+
+      if (!aBinFile.good())
+      {
+        Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + myBinFileNameFull + "' can not be written!", Message_Fail);
+        return false;
+      }
+
+      myBinDataMap.Bind (aFaceIter.Face(), aGltfFace);
+    }
+  }
+  myBuffViewPos.ByteLength = (int64_t )aBinFile.tellp() - myBuffViewPos.ByteOffset;
+  if (!aPSentryBin.More())
+  {
+    return false;
+  }
+  aPSentryBin.Next();
+
+  // write normals
+  myBuffViewNorm.ByteOffset = aBinFile.tellp();
+  for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
+       aDocExplorer.More() && aPSentryBin.More(); aDocExplorer.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
+    if (theLabelFilter != NULL
+    && !theLabelFilter->Contains (aDocNode.Id))
+    {
+      continue;
+    }
+    for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
+    {
+      if (toSkipFaceMesh (aFaceIter))
+      {
+        continue;
+      }
+
+      RWGltf_GltfFace& aGltfFace = myBinDataMap.ChangeFind (aFaceIter.Face());
+      if (aGltfFace.NodeNorm.Id != RWGltf_GltfAccessor::INVALID_ID)
+      {
+        continue;
+      }
+
+      saveNormals (aGltfFace, aBinFile, aFaceIter, aNbAccessors);
+
+      if (!aBinFile.good())
+      {
+        Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + myBinFileNameFull + "' can not be written!", Message_Fail);
+        return false;
+      }
+    }
+  }
+  myBuffViewNorm.ByteLength = (int64_t )aBinFile.tellp() - myBuffViewNorm.ByteOffset;
+  if (!aPSentryBin.More())
+  {
+    return false;
+  }
+  aPSentryBin.Next();
+
+  // write texture coordinates
+  myBuffViewTextCoord.ByteOffset = aBinFile.tellp();
+  for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
+       aDocExplorer.More() && aPSentryBin.More(); aDocExplorer.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
+    if (theLabelFilter != NULL
+    && !theLabelFilter->Contains (aDocNode.Id))
+    {
+      continue;
+    }
+
+    for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
+    {
+      if (toSkipFaceMesh (aFaceIter))
+      {
+        continue;
+      }
+
+      RWGltf_GltfFace& aGltfFace = myBinDataMap.ChangeFind (aFaceIter.Face());
+      if (aGltfFace.NodeUV.Id != RWGltf_GltfAccessor::INVALID_ID)
+      {
+        continue;
+      }
+
+      saveTextCoords (aGltfFace, aBinFile, aFaceIter, aNbAccessors);
+
+      if (!aBinFile.good())
+      {
+        Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + myBinFileNameFull + "' can not be written!", Message_Fail);
+        return false;
+      }
+    }
+  }
+  myBuffViewTextCoord.ByteLength = (int64_t )aBinFile.tellp() - myBuffViewTextCoord.ByteOffset;
+  if (!aPSentryBin.More())
+  {
+    return false;
+  }
+  aPSentryBin.Next();
+
+  // write indices
+  myBuffViewInd.ByteOffset = aBinFile.tellp();
+  for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
+       aDocExplorer.More() && aPSentryBin.More(); aDocExplorer.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
+    if (theLabelFilter != NULL
+    && !theLabelFilter->Contains (aDocNode.Id))
+    {
+      continue;
+    }
+
+    for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
+    {
+      if (toSkipFaceMesh (aFaceIter))
+      {
+        continue;
+      }
+
+      RWGltf_GltfFace& aGltfFace = myBinDataMap.ChangeFind (aFaceIter.Face());
+      if (aGltfFace.Indices.Id != RWGltf_GltfAccessor::INVALID_ID)
+      {
+        continue;
+      }
+
+      saveIndices (aGltfFace, aBinFile, aFaceIter, aNbAccessors);
+
+      if (!aBinFile.good())
+      {
+        Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + myBinFileNameFull + "' can not be written!", Message_Fail);
+        return false;
+      }
+    }
+  }
+  myBuffViewInd.ByteLength = (int64_t )aBinFile.tellp() - myBuffViewInd.ByteOffset;
+
+  int aBuffViewId = 0;
+  if (myBuffViewPos.ByteLength > 0)
+  {
+    myBuffViewPos.Id = aBuffViewId++;
+  }
+  if (myBuffViewNorm.ByteLength > 0)
+  {
+    myBuffViewNorm.Id = aBuffViewId++;
+  }
+  if (myBuffViewTextCoord.ByteLength > 0)
+  {
+    myBuffViewTextCoord.Id = aBuffViewId++;
+  }
+  if (myBuffViewInd.ByteLength > 0)
+  {
+    myBuffViewInd.Id = aBuffViewId++;
+  }
+
+  myBinDataLen64 = aBinFile.tellp();
+  aBinFile.close();
+  if (!aBinFile.good())
+  {
+    Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + myBinFileNameFull + "' can not be written!", Message_Fail);
+    return false;
+  }
+  return true;
+}
+
+//================================================================
+// Function : writeJson
+// Purpose  :
+//================================================================
+bool RWGltf_CafWriter::writeJson (const Handle(TDocStd_Document)&  theDocument,
+                                  const TDF_LabelSequence&         theRootLabels,
+                                  const TColStd_MapOfAsciiString*  theLabelFilter,
+                                  const TColStd_IndexedDataMapOfStringString& theFileInfo,
+                                  const Handle(Message_ProgressIndicator)& theProgress)
+{
+#ifdef HAVE_RAPIDJSON
+  myWriter.reset();
+
+  // write vertex arrays
+  Message_ProgressSentry aPSentryBin (theProgress, "Header data", 0, 2, 1);
+
+  const Standard_Integer aBinDataBufferId = 0;
+  const Standard_Integer aDefSamplerId    = 0;
+  const Standard_Integer aDefSceneId      = 0;
+
+  const TCollection_AsciiString aFileNameGltf = myFile;
+  std::ofstream aGltfContentFile;
+  OSD_OpenStream (aGltfContentFile, aFileNameGltf.ToCString(), std::ios::out | std::ios::binary);
+  if (!aGltfContentFile.is_open()
+   || !aGltfContentFile.good())
+  {
+    Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + aFileNameGltf + "' can not be created!", Message_Fail);
+    return false;
+  }
+  if (myIsBinary)
+  {
+    const char* aMagic = "glTF";
+    uint32_t aVersion       = 2;
+    uint32_t aLength        = 0;
+    uint32_t aContentLength = 0;
+    uint32_t aContentType   = 0x4E4F534A;
+
+    aGltfContentFile.write (aMagic, 4);
+    aGltfContentFile.write ((const char* )&aVersion,       sizeof(aVersion));
+    aGltfContentFile.write ((const char* )&aLength,        sizeof(aLength));
+    aGltfContentFile.write ((const char* )&aContentLength, sizeof(aContentLength));
+    aGltfContentFile.write ((const char* )&aContentType,   sizeof(aContentType));
+  }
+
+  // Prepare an indexed map of scene nodes (without assemblies) in correct order.
+  // Note: this is also order of meshes in glTF document array.
+  RWGltf_GltfSceneNodeMap aSceneNodeMap;
+  for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
+       aDocExplorer.More(); aDocExplorer.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
+    if (theLabelFilter != NULL
+    && !theLabelFilter->Contains (aDocNode.Id))
+    {
+      continue;
+    }
+    aSceneNodeMap.Add (aDocNode);
+  }
+
+  rapidjson::OStreamWrapper aFileStream (aGltfContentFile);
+  myWriter.reset (new RWGltf_GltfOStreamWriter (aFileStream));
+
+  myWriter->StartObject();
+
+  writeAccessors (aSceneNodeMap);
+  writeAnimations();
+  writeAsset (theFileInfo);
+  writeBufferViews (aBinDataBufferId);
+  writeBuffers();
+  writeExtensions ();
+
+  RWGltf_GltfMaterialMap aMaterialMap (myFile, aDefSamplerId);
+  aMaterialMap.SetDefaultStyle (myDefaultStyle);
+  writeImages    (aSceneNodeMap, aMaterialMap);
+  writeMaterials (aSceneNodeMap, aMaterialMap);
+  writeMeshes    (aSceneNodeMap, aMaterialMap);
+
+  aPSentryBin.Next();
+  if (!aPSentryBin.More())
+  {
+    return false;
+  }
+
+  // root nodes indices starting from 0
+  NCollection_Sequence<Standard_Integer> aSceneRootNodeInds;
+  writeNodes (theDocument, theRootLabels, theLabelFilter, aSceneNodeMap, aSceneRootNodeInds);
+  writeSamplers (aMaterialMap);
+  writeScene (aDefSceneId);
+  writeScenes (aSceneRootNodeInds);
+  writeSkins();
+  writeTextures (aSceneNodeMap, aMaterialMap);
+
+  myWriter->EndObject();
+
+  if (!myIsBinary)
+  {
+    aGltfContentFile.close();
+    if (!aGltfContentFile.good())
+    {
+      Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + aFileNameGltf + "' can not be written!", Message_Fail);
+      return false;
+    }
+    return true;
+  }
+
+  int64_t aContentLen64 = (int64_t )aGltfContentFile.tellp() - 20;
+  while (aContentLen64 % 4 != 0)
+  {
+    aGltfContentFile.write (" ", 1);
+    ++aContentLen64;
+  }
+
+  const uint32_t aBinLength = (uint32_t )myBinDataLen64;
+  const uint32_t aBinType   = 0x004E4942;
+  aGltfContentFile.write ((const char*)&aBinLength, 4);
+  aGltfContentFile.write ((const char*)&aBinType,   4);
+
+  const int64_t aFullLen64 = aContentLen64 + 20 + myBinDataLen64 + 8;
+  if (aFullLen64 < std::numeric_limits<uint32_t>::max())
+  {
+    {
+      std::ifstream aBinFile;
+      OSD_OpenStream (aBinFile, myBinFileNameFull.ToCString(), std::ios::in | std::ios::binary);
+      if (!aBinFile.is_open()
+       || !aBinFile.good())
+      {
+        Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + myBinFileNameFull + "' cannot be opened!", Message_Fail);
+        return false;
+      }
+      char aBuffer[4096];
+      for (; aBinFile.good();)
+      {
+        aBinFile.read (aBuffer, 4096);
+        const Standard_Integer aReadLen = (Standard_Integer )aBinFile.gcount();
+        if (aReadLen == 0)
+        {
+          break;
+        }
+        aGltfContentFile.write (aBuffer, aReadLen);
+      }
+    }
+    OSD_Path aBinFilePath (myBinFileNameFull);
+    OSD_File (aBinFilePath).Remove();
+    if (OSD_File (aBinFilePath).Exists())
+    {
+      Message::DefaultMessenger()->Send (TCollection_AsciiString ("Unable to remove temporary glTF content file '")
+                                         + myBinFileNameFull + "'!", Message_Fail);
+    }
+  }
+  else
+  {
+    Message::DefaultMessenger()->Send ("glTF file content is too big for binary format!", Message_Fail);
+    return false;
+  }
+
+  const uint32_t aLength        = (uint32_t )aFullLen64;
+  const uint32_t aContentLength = (uint32_t )aContentLen64;
+  aGltfContentFile.seekp (8);
+  aGltfContentFile.write ((const char* )&aLength,        4);
+  aGltfContentFile.write ((const char* )&aContentLength, 4);
+
+  aGltfContentFile.close();
+  if (!aGltfContentFile.good())
+  {
+    Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + aFileNameGltf + "' can not be written!", Message_Fail);
+    return false;
+  }
+
+  myWriter.reset();
+  return true;
+#else
+  (void )theDocument;
+  (void )theRootLabels;
+  (void )theLabelFilter;
+  (void )theFileInfo;
+  (void )theProgress;
+  Message::DefaultMessenger()->Send ("Error: glTF writer is unavailable - OCCT has been built without RapidJSON support [HAVE_RAPIDJSON undefined].", Message_Fail);
+  return false;
+#endif
+}
+
+// =======================================================================
+// function : writeAccessors
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeAccessors (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeAccessors()");
+
+  myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Accessors));
+  myWriter->StartArray();
+
+  NCollection_Map<TopoDS_Shape, TopTools_ShapeMapHasher> aWrittenFaces;
+  for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
+    for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
+    {
+      if (!aWrittenFaces.Add (aFaceIter.Face()) // skip repeating faces
+        || toSkipFaceMesh (aFaceIter))
+      {
+        continue;
+      }
+
+      const RWGltf_GltfFace& aGltfFace = myBinDataMap.Find (aFaceIter.Face());
+      writePositions (aGltfFace);
+    }
+  }
+  aWrittenFaces.Clear();
+  for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
+    for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
+    {
+      if (!aWrittenFaces.Add (aFaceIter.Face()) // skip repeating faces
+        || toSkipFaceMesh (aFaceIter))
+      {
+        continue;
+      }
+
+      const RWGltf_GltfFace& aGltfFace = myBinDataMap.Find (aFaceIter.Face());
+      writeNormals (aGltfFace);
+    }
+  }
+  aWrittenFaces.Clear();
+  for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
+    for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
+    {
+      if (!aWrittenFaces.Add (aFaceIter.Face()) // skip repeating faces
+        || toSkipFaceMesh (aFaceIter))
+      {
+        continue;
+      }
+
+      const RWGltf_GltfFace& aGltfFace = myBinDataMap.Find (aFaceIter.Face());
+      writeTextCoords (aGltfFace);
+    }
+  }
+  aWrittenFaces.Clear();
+  for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
+    for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
+    {
+      if (!aWrittenFaces.Add (aFaceIter.Face()) // skip repeating faces
+        || toSkipFaceMesh (aFaceIter))
+      {
+        continue;
+      }
+
+      const RWGltf_GltfFace& aGltfFace = myBinDataMap.Find (aFaceIter.Face());
+      writeIndices (aGltfFace);
+    }
+  }
+
+  myWriter->EndArray();
+#else
+  (void )theSceneNodeMap;
+#endif
+}
+
+// =======================================================================
+// function : writePositions
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writePositions (const RWGltf_GltfFace& theGltfFace)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writePositions()");
+  if (theGltfFace.NodePos.Id == RWGltf_GltfAccessor::INVALID_ID)
+  {
+    return;
+  }
+
+  myWriter->StartObject();
+  myWriter->Key    ("bufferView");
+  myWriter->Int    (myBuffViewPos.Id);
+  myWriter->Key    ("byteOffset");
+  myWriter->Int64  (theGltfFace.NodePos.ByteOffset);
+  myWriter->Key    ("componentType");
+  myWriter->Int    (theGltfFace.NodePos.ComponentType);
+  myWriter->Key    ("count");
+  myWriter->Int64  (theGltfFace.NodePos.Count);
+
+  if (theGltfFace.NodePos.BndBox.IsValid())
+  {
+    myWriter->Key ("max");
+    myWriter->StartArray();
+    myWriter->Double (theGltfFace.NodePos.BndBox.CornerMax().x());
+    myWriter->Double (theGltfFace.NodePos.BndBox.CornerMax().y());
+    myWriter->Double (theGltfFace.NodePos.BndBox.CornerMax().z());
+    myWriter->EndArray();
+
+    myWriter->Key("min");
+    myWriter->StartArray();
+    myWriter->Double (theGltfFace.NodePos.BndBox.CornerMin().x());
+    myWriter->Double (theGltfFace.NodePos.BndBox.CornerMin().y());
+    myWriter->Double (theGltfFace.NodePos.BndBox.CornerMin().z());
+    myWriter->EndArray();
+  }
+  myWriter->Key    ("type");
+  myWriter->String ("VEC3");
+
+  myWriter->EndObject();
+#else
+  (void )theGltfFace;
+#endif
+}
+
+// =======================================================================
+// function : writeNormals
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeNormals (const RWGltf_GltfFace& theGltfFace)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeNormals()");
+  if (theGltfFace.NodeNorm.Id == RWGltf_GltfAccessor::INVALID_ID)
+  {
+    return;
+  }
+
+  myWriter->StartObject();
+  myWriter->Key    ("bufferView");
+  myWriter->Int    (myBuffViewNorm.Id);
+  myWriter->Key    ("byteOffset");
+  myWriter->Int64  (theGltfFace.NodeNorm.ByteOffset);
+  myWriter->Key    ("componentType");
+  myWriter->Int    (theGltfFace.NodeNorm.ComponentType);
+  myWriter->Key    ("count");
+  myWriter->Int64  (theGltfFace.NodeNorm.Count);
+  // min/max values are optional, and not very useful for normals - skip them
+  /*{
+    myWriter->Key ("max");
+    myWriter->StartArray();
+    myWriter->Double (1.0);
+    myWriter->Double (1.0);
+    myWriter->Double (1.0);
+    myWriter->EndArray();
+  }
+  {
+    myWriter->Key ("min");
+    myWriter->StartArray();
+    myWriter->Double (0.0);
+    myWriter->Double (0.0);
+    myWriter->Double (0.0);
+    myWriter->EndArray();
+  }*/
+  myWriter->Key    ("type");
+  myWriter->String ("VEC3");
+
+  myWriter->EndObject();
+#else
+  (void )theGltfFace;
+#endif
+}
+
+// =======================================================================
+// function : writeTextCoords
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeTextCoords (const RWGltf_GltfFace& theGltfFace)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeTextCoords()");
+  if (theGltfFace.NodeUV.Id == RWGltf_GltfAccessor::INVALID_ID)
+  {
+    return;
+  }
+
+  myWriter->StartObject();
+  myWriter->Key    ("bufferView");
+  myWriter->Int    (myBuffViewTextCoord.Id);
+  myWriter->Key    ("byteOffset");
+  myWriter->Int64  (theGltfFace.NodeUV.ByteOffset);
+  myWriter->Key    ("componentType");
+  myWriter->Int    (theGltfFace.NodeUV.ComponentType);
+  myWriter->Key    ("count");
+  myWriter->Int64  (theGltfFace.NodeUV.Count);
+  // min/max values are optional, and not very useful for UV coordinates - skip them
+  /*{
+    myWriter->Key ("max");
+    myWriter->StartArray();
+    myWriter->Double (1.0);
+    myWriter->Double (1.0);
+    myWriter->Double (1.0);
+    myWriter->EndArray();
+  }
+  {
+    myWriter->Key ("min");
+    myWriter->StartArray();
+    myWriter->Double (0.0);
+    myWriter->Double (0.0);
+    myWriter->Double (0.0);
+    myWriter->EndArray();
+  }*/
+  myWriter->Key    ("type");
+  myWriter->String ("VEC2");
+
+  myWriter->EndObject();
+#else
+  (void )theGltfFace;
+#endif
+}
+
+// =======================================================================
+// function : writeIndices
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeIndices (const RWGltf_GltfFace& theGltfFace)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeIndices()");
+  if (theGltfFace.Indices.Id == RWGltf_GltfAccessor::INVALID_ID)
+  {
+    return;
+  }
+
+  myWriter->StartObject();
+  myWriter->Key    ("bufferView");
+  myWriter->Int    (myBuffViewInd.Id);
+  myWriter->Key    ("byteOffset");
+  myWriter->Int64  (theGltfFace.Indices.ByteOffset);
+  myWriter->Key    ("componentType");
+  myWriter->Int    (theGltfFace.Indices.ComponentType);
+  myWriter->Key    ("count");
+  myWriter->Int64  (theGltfFace.Indices.Count);
+
+  myWriter->Key    ("type");
+  myWriter->String ("SCALAR");
+
+  myWriter->EndObject();
+#else
+  (void )theGltfFace;
+#endif
+}
+
+// =======================================================================
+// function : writeAnimations
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeAnimations()
+{
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeAnimations()");
+
+  // This section should be skipped if it doesn't contain any information but not be empty
+  //myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Animations));
+  //myWriter->StartArray();
+  //myWriter->EndArray();
+}
+
+// =======================================================================
+// function : writeAsset
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeAsset (const TColStd_IndexedDataMapOfStringString& theFileInfo)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeAsset()");
+
+  myWriter->Key    (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Asset));
+  myWriter->StartObject();
+  myWriter->Key    ("generator");
+  myWriter->String ("Open CASCADE Technology [www.opencascade.com]");
+  myWriter->Key    ("version");
+  myWriter->String ("2.0"); // glTF format version
+
+  bool anIsStarted = false;
+  for (TColStd_IndexedDataMapOfStringString::Iterator aKeyValueIter (theFileInfo); aKeyValueIter.More(); aKeyValueIter.Next())
+  {
+    if (!anIsStarted)
+    {
+      myWriter->Key ("extras");
+      myWriter->StartObject();
+      anIsStarted = true;
+    }
+    myWriter->Key (aKeyValueIter.Key().ToCString());
+    myWriter->String (aKeyValueIter.Value().ToCString());
+  }
+  if (anIsStarted)
+  {
+    myWriter->EndObject();
+  }
+
+  myWriter->EndObject();
+#else
+  (void )theFileInfo;
+#endif
+}
+
+// =======================================================================
+// function : writeBufferViews
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeBufferViews (const Standard_Integer theBinDataBufferId)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeBufferViews()");
+
+  myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_BufferViews));
+  myWriter->StartArray();
+  if (myBuffViewPos.Id != RWGltf_GltfAccessor::INVALID_ID)
+  {
+    myWriter->StartObject();
+    myWriter->Key    ("buffer");
+    myWriter->Int    (theBinDataBufferId);
+    myWriter->Key    ("byteLength");
+    myWriter->Int64  (myBuffViewPos.ByteLength);
+    myWriter->Key    ("byteOffset");
+    myWriter->Int64  (myBuffViewPos.ByteOffset);
+    myWriter->Key    ("byteStride");
+    myWriter->Int64  (myBuffViewPos.ByteStride);
+    myWriter->Key    ("target");
+    myWriter->Int    (myBuffViewPos.Target);
+    myWriter->EndObject();
+  }
+  if (myBuffViewNorm.Id != RWGltf_GltfAccessor::INVALID_ID)
+  {
+    myWriter->StartObject();
+    myWriter->Key    ("buffer");
+    myWriter->Int    (theBinDataBufferId);
+    myWriter->Key    ("byteLength");
+    myWriter->Int64  (myBuffViewNorm.ByteLength);
+    myWriter->Key    ("byteOffset");
+    myWriter->Int64  (myBuffViewNorm.ByteOffset);
+    myWriter->Key    ("byteStride");
+    myWriter->Int64  (myBuffViewNorm.ByteStride);
+    myWriter->Key    ("target");
+    myWriter->Int    (myBuffViewNorm.Target);
+    myWriter->EndObject();
+  }
+  if (myBuffViewTextCoord.Id != RWGltf_GltfAccessor::INVALID_ID)
+  {
+    myWriter->StartObject();
+    myWriter->Key    ("buffer");
+    myWriter->Int    (theBinDataBufferId);
+    myWriter->Key    ("byteLength");
+    myWriter->Int64  (myBuffViewTextCoord.ByteLength);
+    myWriter->Key    ("byteOffset");
+    myWriter->Int64  (myBuffViewTextCoord.ByteOffset);
+    myWriter->Key    ("byteStride");
+    myWriter->Int64  (myBuffViewTextCoord.ByteStride);
+    myWriter->Key    ("target");
+    myWriter->Int    (myBuffViewTextCoord.Target);
+    myWriter->EndObject();
+  }
+  if (myBuffViewInd.Id != RWGltf_GltfAccessor::INVALID_ID)
+  {
+    myWriter->StartObject();
+    myWriter->Key    ("buffer");
+    myWriter->Int    (theBinDataBufferId);
+    myWriter->Key    ("byteLength");
+    myWriter->Int64  (myBuffViewInd.ByteLength);
+    myWriter->Key    ("byteOffset");
+    myWriter->Int64  (myBuffViewInd.ByteOffset);
+    myWriter->Key    ("target");
+    myWriter->Int    (myBuffViewInd.Target);
+    myWriter->EndObject();
+  }
+  myWriter->EndArray();
+#else
+  (void )theBinDataBufferId;
+#endif
+}
+
+// =======================================================================
+// function : writeBuffers
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeBuffers()
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeBuffers()");
+
+  myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Buffers));
+  myWriter->StartArray();
+  {
+    myWriter->StartObject();
+    {
+      myWriter->Key   ("byteLength");
+      myWriter->Int64 (myBuffViewPos.ByteLength + myBuffViewNorm.ByteLength +
+                       myBuffViewTextCoord.ByteLength + myBuffViewInd.ByteLength);
+      if (!myIsBinary)
+      {
+        myWriter->Key   ("uri");
+        myWriter->String (myBinFileNameShort.ToCString());
+      }
+    }
+    myWriter->EndObject();
+  }
+  myWriter->EndArray();
+#endif
+}
+
+// =======================================================================
+// function : writeExtensions
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeExtensions()
+{
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeExtensions()");
+}
+
+// =======================================================================
+// function : writeImages
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeImages (const RWGltf_GltfSceneNodeMap& theSceneNodeMap,
+                                    RWGltf_GltfMaterialMap& theMaterialMap)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeImages()");
+
+  // empty RWGltf_GltfRootElement_Images section should NOT be written to avoid validator errors
+  bool anIsStarted = false;
+  for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
+    for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
+    {
+      theMaterialMap.AddImages (myWriter.get(), aFaceIter.FaceStyle(), anIsStarted);
+    }
+  }
+  if (anIsStarted)
+  {
+    myWriter->EndArray();
+  }
+#else
+  (void )theSceneNodeMap;
+  (void )theMaterialMap;
+#endif
+}
+
+// =======================================================================
+// function : writeMaterials
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeMaterials (const RWGltf_GltfSceneNodeMap& theSceneNodeMap,
+                                       RWGltf_GltfMaterialMap& theMaterialMap)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeMaterials()");
+
+  // empty RWGltf_GltfRootElement_Materials section should NOT be written to avoid validator errors
+  bool anIsStarted = false;
+  for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
+    for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
+    {
+      theMaterialMap.AddMaterial (myWriter.get(), aFaceIter.FaceStyle(), anIsStarted);
+    }
+  }
+  if (anIsStarted)
+  {
+    myWriter->EndArray();
+  }
+#else
+  (void )theSceneNodeMap;
+  (void )theMaterialMap;
+#endif
+}
+
+// =======================================================================
+// function : writeMeshes
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeMeshes (const RWGltf_GltfSceneNodeMap& theSceneNodeMap,
+                                    const RWGltf_GltfMaterialMap&  theMaterialMap)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeMeshes()");
+
+  myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Meshes));
+  myWriter->StartArray();
+
+  for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
+    const TCollection_AsciiString aNodeName = readNameAttribute (aDocNode.RefLabel);
+
+    myWriter->StartObject();
+    myWriter->Key ("name");
+    myWriter->String (aNodeName.ToCString());
+    myWriter->Key ("primitives");
+    myWriter->StartArray();
+
+    Standard_Integer aNbFacesInNode = 0;
+    for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next(), ++aNbFacesInNode)
+    {
+      if (toSkipFaceMesh (aFaceIter))
+      {
+        continue;
+      }
+
+      const RWGltf_GltfFace& aGltfFace = myBinDataMap.Find (aFaceIter.Face());
+      const TCollection_AsciiString aMatId = theMaterialMap.FindMaterial (aFaceIter.FaceStyle());
+      myWriter->StartObject();
+      {
+        myWriter->Key ("attributes");
+        myWriter->StartObject();
+        {
+          if (aGltfFace.NodeNorm.Id != RWGltf_GltfAccessor::INVALID_ID)
+          {
+            myWriter->Key ("NORMAL");
+            myWriter->Int (aGltfFace.NodeNorm.Id);
+          }
+          myWriter->Key ("POSITION");
+          myWriter->Int (aGltfFace.NodePos.Id);
+          if (aGltfFace.NodeUV.Id != RWGltf_GltfAccessor::INVALID_ID)
+          {
+            myWriter->Key ("TEXCOORD_0");
+            myWriter->Int (aGltfFace.NodeUV.Id);
+          }
+        }
+        myWriter->EndObject();
+
+        myWriter->Key ("indices");
+        myWriter->Int (aGltfFace.Indices.Id);
+        if (!aMatId.IsEmpty())
+        {
+          myWriter->Key ("material");
+          myWriter->Int (aMatId.IntegerValue());
+        }
+        myWriter->Key ("mode");
+        myWriter->Int (RWGltf_GltfPrimitiveMode_Triangles);
+      }
+      myWriter->EndObject();
+    }
+    myWriter->EndArray();
+    myWriter->EndObject();
+  }
+  myWriter->EndArray();
+#else
+  (void )theSceneNodeMap;
+  (void )theMaterialMap;
+#endif
+}
+
+// =======================================================================
+// function : writeNodes
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeNodes (const Handle(TDocStd_Document)&  theDocument,
+                                   const TDF_LabelSequence&         theRootLabels,
+                                   const TColStd_MapOfAsciiString*  theLabelFilter,
+                                   const RWGltf_GltfSceneNodeMap&   theSceneNodeMap,
+                                   NCollection_Sequence<Standard_Integer>& theSceneRootNodeInds)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeNodes()");
+
+  // Prepare full indexed map of scene nodes in correct order.
+  RWGltf_GltfSceneNodeMap aSceneNodeMapWithChildren; // indexes starting from 1
+  for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_None);
+       aDocExplorer.More(); aDocExplorer.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
+    if (theLabelFilter != NULL
+    && !theLabelFilter->Contains (aDocNode.Id))
+    {
+      continue;
+    }
+
+    Standard_Integer aNodeIndex = aSceneNodeMapWithChildren.Add (aDocNode);
+    if (aDocExplorer.CurrentDepth() == 0)
+    {
+      // save root node index (starting from 0 not 1)
+      theSceneRootNodeInds.Append (aNodeIndex - 1);
+    }
+  }
+
+  // Write scene nodes using prepared map for correct order of array members
+  myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Nodes));
+  myWriter->StartArray();
+
+  for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (aSceneNodeMapWithChildren); aSceneNodeIter.More(); aSceneNodeIter.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
+
+    myWriter->StartObject();
+    {
+      if (aDocNode.IsAssembly)
+      {
+        myWriter->Key ("children");
+        myWriter->StartArray();
+        {
+          for (TDF_ChildIterator aChildIter (aDocNode.RefLabel); aChildIter.More(); aChildIter.Next())
+          {
+            const TDF_Label& aChildLabel = aChildIter.Value();
+            if (aChildLabel.IsNull())
+            {
+              continue;
+            }
+
+            const TCollection_AsciiString aChildId = XCAFPrs_DocumentExplorer::DefineChildId (aChildLabel, aDocNode.Id);
+            Standard_Integer aChildIdx = aSceneNodeMapWithChildren.FindIndex (aChildId);
+            if (aChildIdx > 0)
+            {
+              myWriter->Int (aChildIdx - 1);
+            }
+          }
+        }
+        myWriter->EndArray();
+      }
+    }
+    if (!aDocNode.LocalTrsf.IsIdentity())
+    {
+      gp_Trsf aTrsf = aDocNode.LocalTrsf.Transformation();
+      if (aTrsf.Form() != gp_Identity)
+      {
+        myCSTrsf.TransformTransformation (aTrsf);
+        const gp_Quaternion aQuaternion = aTrsf.GetRotation();
+        const bool hasRotation = Abs (aQuaternion.X())       > gp::Resolution()
+                              || Abs (aQuaternion.Y())       > gp::Resolution()
+                              || Abs (aQuaternion.Z())       > gp::Resolution()
+                              || Abs (aQuaternion.W() - 1.0) > gp::Resolution();
+        const Standard_Real aScaleFactor = aTrsf.ScaleFactor();
+        const bool hasScale = Abs (aScaleFactor - 1.0) > Precision::Confusion();
+        const gp_XYZ& aTranslPart = aTrsf.TranslationPart();
+        const bool hasTranslation = aTranslPart.SquareModulus() > gp::Resolution();
+
+        RWGltf_WriterTrsfFormat aTrsfFormat = myTrsfFormat;
+        if (myTrsfFormat == RWGltf_WriterTrsfFormat_Compact)
+        {
+          aTrsfFormat = hasRotation && hasScale && hasTranslation
+                      ? RWGltf_WriterTrsfFormat_Mat4
+                      : RWGltf_WriterTrsfFormat_TRS;
+        }
+
+        if (aTrsfFormat == RWGltf_WriterTrsfFormat_Mat4)
+        {
+          // write full matrix
+          Graphic3d_Mat4 aMat4;
+          aTrsf.GetMat4 (aMat4);
+          if (!aMat4.IsIdentity())
+          {
+            myWriter->Key ("matrix");
+            myWriter->StartArray();
+            for (Standard_Integer aColIter = 0; aColIter < 4; ++aColIter)
+            {
+              for (Standard_Integer aRowIter = 0; aRowIter < 4; ++aRowIter)
+              {
+                myWriter->Double (aMat4.GetValue (aRowIter, aColIter));
+              }
+            }
+            myWriter->EndArray();
+          }
+        }
+        else //if (aTrsfFormat == RWGltf_WriterTrsfFormat_TRS)
+        {
+          if (hasRotation)
+          {
+            myWriter->Key ("rotation");
+            myWriter->StartArray();
+            myWriter->Double (aQuaternion.X());
+            myWriter->Double (aQuaternion.Y());
+            myWriter->Double (aQuaternion.Z());
+            myWriter->Double (aQuaternion.W());
+            myWriter->EndArray();
+          }
+          if (hasScale)
+          {
+            myWriter->Key ("scale");
+            myWriter->StartArray();
+            myWriter->Double (aScaleFactor);
+            myWriter->Double (aScaleFactor);
+            myWriter->Double (aScaleFactor);
+            myWriter->EndArray();
+          }
+          if (hasTranslation)
+          {
+            myWriter->Key ("translation");
+            myWriter->StartArray();
+            myWriter->Double (aTranslPart.X());
+            myWriter->Double (aTranslPart.Y());
+            myWriter->Double (aTranslPart.Z());
+            myWriter->EndArray();
+          }
+        }
+      }
+    }
+    if (!aDocNode.IsAssembly)
+    {
+      myWriter->Key ("mesh");
+      // Mesh order of current node is equal to order of this node in scene nodes map
+      Standard_Integer aMeshIdx = theSceneNodeMap.FindIndex (aDocNode.Id);
+      if (aMeshIdx > 0)
+      {
+        myWriter->Int (aMeshIdx - 1);
+      }
+    }
+    {
+      TCollection_AsciiString aNodeName = readNameAttribute (aDocNode.Label);
+      if (aNodeName.IsEmpty())
+      {
+        aNodeName = readNameAttribute (aDocNode.RefLabel);
+      }
+      myWriter->Key ("name");
+      myWriter->String (aNodeName.ToCString());
+    }
+    myWriter->EndObject();
+  }
+  myWriter->EndArray();
+#else
+  (void )theDocument;
+  (void )theRootLabels;
+  (void )theLabelFilter;
+  (void )theSceneNodeMap;
+  (void )theSceneRootNodeInds;
+#endif
+}
+
+// =======================================================================
+// function : writeSamplers
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeSamplers (const RWGltf_GltfMaterialMap& theMaterialMap)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeSamplers()");
+  if (theMaterialMap.NbImages() == 0)
+  {
+    return;
+  }
+
+  myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Samplers));
+  myWriter->StartArray();
+  {
+    myWriter->StartObject();
+    {
+      //myWriter->Key ("magFilter");
+      //myWriter->Int (9729);
+      //myWriter->Key ("minFilter");
+      //myWriter->Int (9729);
+    }
+    myWriter->EndObject();
+  }
+  myWriter->EndArray();
+#else
+  (void )theMaterialMap;
+#endif
+}
+
+// =======================================================================
+// function : writeScene
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeScene (const Standard_Integer theDefSceneId)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeScene()");
+
+  myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Scene));
+  myWriter->Int (theDefSceneId);
+#else
+  (void )theDefSceneId;
+#endif
+}
+
+// =======================================================================
+// function : writeScenes
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeScenes (const NCollection_Sequence<Standard_Integer>& theSceneRootNodeInds)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeScenes()");
+
+  myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Scenes));
+  myWriter->StartArray();
+  {
+    myWriter->StartObject();
+    myWriter->Key ("nodes");
+    myWriter->StartArray();
+    for (NCollection_Sequence<Standard_Integer>::Iterator aRootIter (theSceneRootNodeInds); aRootIter.More(); aRootIter.Next())
+    {
+      myWriter->Int (aRootIter.Value());
+    }
+    myWriter->EndArray();
+    myWriter->EndObject();
+  }
+  myWriter->EndArray();
+#else
+  (void )theSceneRootNodeInds;
+#endif
+}
+
+// =======================================================================
+// function : writeSkins
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeSkins()
+{
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeSkins()");
+
+  // This section should be skipped if it doesn't contain any information but not be empty
+  /*myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Skins));
+  myWriter->StartArray();
+  myWriter->EndArray();*/
+}
+
+// =======================================================================
+// function : writeTextures
+// purpose  :
+// =======================================================================
+void RWGltf_CafWriter::writeTextures (const RWGltf_GltfSceneNodeMap& theSceneNodeMap,
+                                      RWGltf_GltfMaterialMap& theMaterialMap)
+{
+#ifdef HAVE_RAPIDJSON
+  Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeTextures()");
+
+  // empty RWGltf_GltfRootElement_Textures section should not be written to avoid validator errors
+  bool anIsStarted = false;
+  for (RWGltf_GltfSceneNodeMap::Iterator aSceneNodeIter (theSceneNodeMap); aSceneNodeIter.More(); aSceneNodeIter.Next())
+  {
+    const XCAFPrs_DocumentNode& aDocNode = aSceneNodeIter.Value();
+    for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
+    {
+      theMaterialMap.AddTextures (myWriter.get(), aFaceIter.FaceStyle(), anIsStarted);
+    }
+  }
+  if (anIsStarted)
+  {
+    myWriter->EndArray();
+  }
+#else
+ (void )theSceneNodeMap;
+ (void )theMaterialMap;
+#endif
+}
diff --git a/src/RWGltf/RWGltf_CafWriter.hxx b/src/RWGltf/RWGltf_CafWriter.hxx
new file mode 100644 (file)
index 0000000..686a27d
--- /dev/null
@@ -0,0 +1,294 @@
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWGltf_CafWriter_HeaderFiler
+#define _RWGltf_CafWriter_HeaderFiler
+
+#include <TColStd_IndexedDataMapOfStringString.hxx>
+#include <TColStd_MapOfAsciiString.hxx>
+#include <TDF_LabelSequence.hxx>
+#include <TopTools_ShapeMapHasher.hxx>
+#include <RWGltf_GltfBufferView.hxx>
+#include <RWGltf_GltfFace.hxx>
+#include <RWGltf_WriterTrsfFormat.hxx>
+#include <RWMesh_CoordinateSystemConverter.hxx>
+#include <XCAFPrs_Style.hxx>
+
+#include <memory>
+
+class Message_ProgressIndicator;
+class RWMesh_FaceIterator;
+class RWGltf_GltfOStreamWriter;
+class RWGltf_GltfMaterialMap;
+class RWGltf_GltfSceneNodeMap;
+class TDocStd_Document;
+
+//! glTF writer context from XCAF document.
+class RWGltf_CafWriter : public Standard_Transient
+{
+  DEFINE_STANDARD_RTTIEXT(RWGltf_CafWriter, Standard_Transient)
+public:
+
+  //! Main constructor.
+  //! @param theFile     [in] path to output glTF file
+  //! @param theIsBinary [in] flag to write into binary glTF format (.glb)
+  Standard_EXPORT RWGltf_CafWriter (const TCollection_AsciiString& theFile,
+                                    Standard_Boolean theIsBinary);
+
+  //! Destructor.
+  Standard_EXPORT virtual ~RWGltf_CafWriter();
+
+  //! Return transformation from OCCT to glTF coordinate system.
+  const RWMesh_CoordinateSystemConverter& CoordinateSystemConverter() const { return myCSTrsf; }
+
+  //! Return transformation from OCCT to glTF coordinate system.
+  RWMesh_CoordinateSystemConverter& ChangeCoordinateSystemConverter() { return myCSTrsf; }
+
+  //! Set transformation from OCCT to glTF coordinate system.
+  void SetCoordinateSystemConverter (const RWMesh_CoordinateSystemConverter& theConverter) { myCSTrsf = theConverter; }
+
+  //! Return flag to write into binary glTF format (.glb), specified within class constructor.
+  bool IsBinary() const { return myIsBinary; }
+
+  //! Return preferred transformation format for writing into glTF file; RWGltf_WriterTrsfFormat_Compact by default.
+  RWGltf_WriterTrsfFormat TransformationFormat() const { return myTrsfFormat; }
+
+  //! Set preferred transformation format for writing into glTF file.
+  void SetTransformationFormat (RWGltf_WriterTrsfFormat theFormat) { myTrsfFormat = theFormat; }
+
+  //! Return TRUE to export UV coordinates even if there are no mapped texture; FALSE by default.
+  bool IsForcedUVExport() const { return myIsForcedUVExport; }
+
+  //! Set flag to export UV coordinates even if there are no mapped texture; FALSE by default.
+  void SetForcedUVExport (bool theToForce) { myIsForcedUVExport = theToForce; }
+
+  //! Return default material definition to be used for nodes with only color defined.
+  const XCAFPrs_Style& DefaultStyle() const { return myDefaultStyle; }
+
+  //! Set default material definition to be used for nodes with only color defined.
+  void SetDefaultStyle (const XCAFPrs_Style& theStyle) { myDefaultStyle = theStyle; }
+
+  //! Write glTF file and associated binary file.
+  //! Triangulation data should be precomputed within shapes!
+  //! @param theDocument    [in] input document
+  //! @param theRootLabels  [in] list of root shapes to export
+  //! @param theLabelFilter [in] optional filter with document nodes to export,
+  //!                            with keys defined by XCAFPrs_DocumentExplorer::DefineChildId() and filled recursively
+  //!                            (leaves and parent assembly nodes at all levels);
+  //!                            when not NULL, all nodes not included into the map will be ignored
+  //! @param theFileInfo    [in] map with file metadata to put into glTF header section
+  //! @param theProgress    [in] optional progress indicator
+  //! @return FALSE on file writing failure
+  Standard_EXPORT virtual bool Perform (const Handle(TDocStd_Document)& theDocument,
+                                        const TDF_LabelSequence& theRootLabels,
+                                        const TColStd_MapOfAsciiString* theLabelFilter,
+                                        const TColStd_IndexedDataMapOfStringString& theFileInfo,
+                                        const Handle(Message_ProgressIndicator)& theProgress);
+
+  //! Write glTF file and associated binary file.
+  //! Triangulation data should be precomputed within shapes!
+  //! @param theDocument    [in] input document
+  //! @param theFileInfo    [in] map with file metadata to put into glTF header section
+  //! @param theProgress    [in] optional progress indicator
+  //! @return FALSE on file writing failure
+  Standard_EXPORT virtual bool Perform (const Handle(TDocStd_Document)& theDocument,
+                                        const TColStd_IndexedDataMapOfStringString& theFileInfo,
+                                        const Handle(Message_ProgressIndicator)& theProgress);
+
+protected:
+
+  //! Write binary data file with triangulation data.
+  //! Triangulation data should be precomputed within shapes!
+  //! @param theDocument    [in] input document
+  //! @param theRootLabels  [in] list of root shapes to export
+  //! @param theLabelFilter [in] optional filter with document nodes to export
+  //! @param theProgress    [in] optional progress indicator
+  //! @return FALSE on file writing failure
+  Standard_EXPORT virtual bool writeBinData (const Handle(TDocStd_Document)& theDocument,
+                                             const TDF_LabelSequence& theRootLabels,
+                                             const TColStd_MapOfAsciiString* theLabelFilter,
+                                             const Handle(Message_ProgressIndicator)& theProgress);
+
+  //! Write JSON file with glTF structure (should be called after writeBinData()).
+  //! @param theDocument    [in] input document
+  //! @param theRootLabels  [in] list of root shapes to export
+  //! @param theLabelFilter [in] optional filter with document nodes to export
+  //! @param theFileInfo    [in] map with file metadata to put into glTF header section
+  //! @param theProgress    [in] optional progress indicator
+  //! @return FALSE on file writing failure
+  Standard_EXPORT virtual bool writeJson (const Handle(TDocStd_Document)& theDocument,
+                                          const TDF_LabelSequence& theRootLabels,
+                                          const TColStd_MapOfAsciiString* theLabelFilter,
+                                          const TColStd_IndexedDataMapOfStringString& theFileInfo,
+                                          const Handle(Message_ProgressIndicator)& theProgress);
+
+protected:
+
+  //! Return TRUE if face mesh should be skipped (e.g. because it is invalid or empty).
+  Standard_EXPORT virtual Standard_Boolean toSkipFaceMesh (const RWMesh_FaceIterator& theFaceIter);
+
+  //! Write mesh nodes into binary file.
+  //! @param theGltfFace [out] glTF face definition
+  //! @param theBinFile  [out] output file to write into
+  //! @param theFaceIter [in]  current face to write
+  //! @param theAccessorNb [in] [out] last accessor index
+  Standard_EXPORT virtual void saveNodes (RWGltf_GltfFace& theGltfFace,
+                                          std::ostream& theBinFile,
+                                          const RWMesh_FaceIterator& theFaceIter,
+                                          Standard_Integer& theAccessorNb) const;
+
+  //! Write mesh normals into binary file.
+  //! @param theGltfFace [out] glTF face definition
+  //! @param theBinFile  [out] output file to write into
+  //! @param theFaceIter [in]  current face to write
+  //! @param theAccessorNb [in] [out] last accessor index
+  Standard_EXPORT virtual void saveNormals (RWGltf_GltfFace& theGltfFace,
+                                            std::ostream& theBinFile,
+                                            RWMesh_FaceIterator& theFaceIter,
+                                            Standard_Integer& theAccessorNb) const;
+
+  //! Write mesh texture UV coordinates into binary file.
+  //! @param theGltfFace [out] glTF face definition
+  //! @param theBinFile  [out] output file to write into
+  //! @param theFaceIter [in]  current face to write
+  //! @param theAccessorNb [in] [out] last accessor index
+  Standard_EXPORT virtual void saveTextCoords (RWGltf_GltfFace& theGltfFace,
+                                               std::ostream& theBinFile,
+                                               const RWMesh_FaceIterator& theFaceIter,
+                                               Standard_Integer& theAccessorNb) const;
+
+  //! Write mesh indexes into binary file.
+  //! @param theGltfFace [out] glTF face definition
+  //! @param theBinFile  [out] output file to write into
+  //! @param theFaceIter [in]  current face to write
+  //! @param theAccessorNb [in] [out] last accessor index
+  Standard_EXPORT virtual void saveIndices (RWGltf_GltfFace& theGltfFace,
+                                            std::ostream& theBinFile,
+                                            const RWMesh_FaceIterator& theFaceIter,
+                                            Standard_Integer& theAccessorNb);
+
+protected:
+
+  //! Write bufferView for vertex positions within RWGltf_GltfRootElement_Accessors section
+  //! @param theGltfFace [in] face definition to write
+  Standard_EXPORT virtual void writePositions (const RWGltf_GltfFace& theGltfFace);
+
+  //! Write bufferView for vertex normals within RWGltf_GltfRootElement_Accessors section
+  //! @param theGltfFace [in] face definition to write
+  Standard_EXPORT virtual void writeNormals (const RWGltf_GltfFace& theGltfFace);
+
+  //! Write bufferView for vertex texture coordinates within RWGltf_GltfRootElement_Accessors section
+  //! @param theGltfFace [in] face definition to write
+  Standard_EXPORT virtual void writeTextCoords (const RWGltf_GltfFace& theGltfFace);
+
+  //! Write bufferView for triangle indexes within RWGltf_GltfRootElement_Accessors section.
+  //! @param theGltfFace [in] face definition to write
+  Standard_EXPORT virtual void writeIndices (const RWGltf_GltfFace& theGltfFace);
+
+protected:
+
+  //! Write RWGltf_GltfRootElement_Accessors section.
+  //! @param theSceneNodeMap [in] ordered map of scene nodes
+  Standard_EXPORT virtual void writeAccessors (const RWGltf_GltfSceneNodeMap& theSceneNodeMap);
+
+  //! Write RWGltf_GltfRootElement_Animations section (reserved).
+  Standard_EXPORT virtual void writeAnimations();
+
+  //! Write RWGltf_GltfRootElement_Asset section.
+  //! @param theFileInfo [in] optional metadata to write into file header
+  Standard_EXPORT virtual void writeAsset (const TColStd_IndexedDataMapOfStringString& theFileInfo);
+
+  //! Write RWGltf_GltfRootElement_BufferViews section.
+  //! @param theBinDataBufferId [in] index of binary buffer with vertex data
+  Standard_EXPORT virtual void writeBufferViews (const Standard_Integer theBinDataBufferId);
+
+  //! Write RWGltf_GltfRootElement_Buffers section.
+  Standard_EXPORT virtual void writeBuffers();
+
+  //! Write RWGltf_GltfRootElement_ExtensionsUsed/RWGltf_GltfRootElement_ExtensionsRequired sections (reserved).
+  Standard_EXPORT virtual void writeExtensions();
+
+  //! Write RWGltf_GltfRootElement_Images section.
+  //! @param theSceneNodeMap [in] ordered map of scene nodes
+  //! @param theMaterialMap [out] map of materials, filled with image files used by textures
+  Standard_EXPORT virtual void writeImages (const RWGltf_GltfSceneNodeMap& theSceneNodeMap,
+                                            RWGltf_GltfMaterialMap& theMaterialMap);
+
+  //! Write RWGltf_GltfRootElement_Materials section.
+  //! @param theSceneNodeMap [in] ordered map of scene nodes
+  //! @param theMaterialMap [out] map of materials, filled with materials
+  Standard_EXPORT virtual void writeMaterials (const RWGltf_GltfSceneNodeMap& theSceneNodeMap,
+                                               RWGltf_GltfMaterialMap& theMaterialMap);
+
+  //! Write RWGltf_GltfRootElement_Meshes section.
+  //! @param theSceneNodeMap [in] ordered map of scene nodes
+  //! @param theMaterialMap  [in] map of materials
+  Standard_EXPORT virtual void writeMeshes (const RWGltf_GltfSceneNodeMap& theSceneNodeMap,
+                                            const RWGltf_GltfMaterialMap& theMaterialMap);
+
+  //! Write RWGltf_GltfRootElement_Nodes section.
+  //! @param theDocument     [in] input document
+  //! @param theRootLabels   [in] list of root shapes to export
+  //! @param theLabelFilter  [in] optional filter with document nodes to export
+  //! @param theSceneNodeMap [in] ordered map of scene nodes
+  //! @param theSceneRootNodeInds [out] sequence of scene nodes pointing to root shapes (to be used for writeScenes())
+  Standard_EXPORT virtual void writeNodes (const Handle(TDocStd_Document)&  theDocument,
+                                           const TDF_LabelSequence&         theRootLabels,
+                                           const TColStd_MapOfAsciiString*  theLabelFilter,
+                                           const RWGltf_GltfSceneNodeMap&   theSceneNodeMap,
+                                           NCollection_Sequence<Standard_Integer>& theSceneRootNodeInds);
+
+  //! Write RWGltf_GltfRootElement_Samplers section.
+  Standard_EXPORT virtual void writeSamplers (const RWGltf_GltfMaterialMap& theMaterialMap);
+
+  //! Write RWGltf_GltfRootElement_Scene section.
+  //! @param theDefSceneId [in] index of default scene (0)
+  Standard_EXPORT virtual void writeScene (const Standard_Integer theDefSceneId);
+
+  //! Write RWGltf_GltfRootElement_Scenes section.
+  //! @param theSceneRootNodeInds [in] sequence of scene nodes pointing to root shapes
+  Standard_EXPORT virtual void writeScenes (const NCollection_Sequence<Standard_Integer>& theSceneRootNodeInds);
+
+  //! Write RWGltf_GltfRootElement_Skins section (reserved).
+  Standard_EXPORT virtual void writeSkins();
+
+  //! Write RWGltf_GltfRootElement_Textures section.
+  //! @param theSceneNodeMap [in] ordered map of scene nodes
+  //! @param theMaterialMap [out] map of materials, filled with textures
+  Standard_EXPORT virtual void writeTextures (const RWGltf_GltfSceneNodeMap& theSceneNodeMap,
+                                              RWGltf_GltfMaterialMap& theMaterialMap);
+
+protected:
+
+  TCollection_AsciiString                       myFile;              //!< output glTF file
+  TCollection_AsciiString                       myBinFileNameFull;   //!< output file with binary data (full  path)
+  TCollection_AsciiString                       myBinFileNameShort;  //!< output file with binary data (short path)
+  RWGltf_WriterTrsfFormat                       myTrsfFormat;        //!< transformation format to write into glTF file
+  Standard_Boolean                              myIsBinary;          //!< flag to write into binary glTF format (.glb)
+  Standard_Boolean                              myIsForcedUVExport;  //!< export UV coordinates even if there are no mapped texture
+  RWMesh_CoordinateSystemConverter              myCSTrsf;            //!< transformation from OCCT to glTF coordinate system
+  XCAFPrs_Style                                 myDefaultStyle;      //!< default material definition to be used for nodes with only color defined
+
+  opencascade::std::shared_ptr<RWGltf_GltfOStreamWriter>
+                                                myWriter;            //!< JSON writer
+  RWGltf_GltfBufferView                         myBuffViewPos;       //!< current buffer view with nodes positions
+  RWGltf_GltfBufferView                         myBuffViewNorm;      //!< current buffer view with nodes normals
+  RWGltf_GltfBufferView                         myBuffViewTextCoord; //!< current buffer view with nodes UV coordinates
+  RWGltf_GltfBufferView                         myBuffViewInd;       //!< current buffer view with triangulation indexes
+  NCollection_DataMap<TopoDS_Shape, RWGltf_GltfFace,
+                      TopTools_ShapeMapHasher>  myBinDataMap;        //!< map for TopoDS_Face to glTF face (merging duplicates)
+  int64_t                                       myBinDataLen64;      //!< length of binary file
+
+};
+
+#endif // _RWGltf_CafWriter_HeaderFiler
index 0e772e9..a47d72f 100644 (file)
@@ -1780,7 +1780,7 @@ bool RWGltf_GltfJsonParser::Parse (const Handle(Message_ProgressIndicator)& theP
   }
   return true;
 #else
-  Message::DefaultMessenger()->Send ("Error: glTF reader is unavailable - OCCT has been built without RapidJSON support.", Message_Fail);
+  Message::DefaultMessenger()->Send ("Error: glTF reader is unavailable - OCCT has been built without RapidJSON support [HAVE_RAPIDJSON undefined].", Message_Fail);
   return false;
 #endif
 }
diff --git a/src/RWGltf/RWGltf_GltfMaterialMap.cxx b/src/RWGltf/RWGltf_GltfMaterialMap.cxx
new file mode 100644 (file)
index 0000000..763538d
--- /dev/null
@@ -0,0 +1,461 @@
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <RWGltf_GltfMaterialMap.hxx>
+
+#include <RWGltf_GltfRootElement.hxx>
+
+#ifdef HAVE_RAPIDJSON
+  #include <RWGltf_GltfOStreamWriter.hxx>
+#endif
+
+// =======================================================================
+// function : baseColorTexture
+// purpose  :
+// =======================================================================
+const Handle(Image_Texture)& RWGltf_GltfMaterialMap::baseColorTexture (const Handle(XCAFDoc_VisMaterial)& theMat)
+{
+  static const Handle(Image_Texture) THE_NULL_TEXTURE;
+  if (theMat.IsNull())
+  {
+    return THE_NULL_TEXTURE;
+  }
+  else if (theMat->HasPbrMaterial()
+       && !theMat->PbrMaterial().BaseColorTexture.IsNull())
+  {
+    return theMat->PbrMaterial().BaseColorTexture;
+  }
+  else if (theMat->HasCommonMaterial()
+       && !theMat->CommonMaterial().DiffuseTexture.IsNull())
+  {
+    return theMat->CommonMaterial().DiffuseTexture;
+  }
+  return THE_NULL_TEXTURE;
+}
+
+// =======================================================================
+// function : RWGltf_GltfMaterialMap
+// purpose  :
+// =======================================================================
+RWGltf_GltfMaterialMap::RWGltf_GltfMaterialMap (const TCollection_AsciiString& theFile,
+                                                const Standard_Integer theDefSamplerId)
+: RWMesh_MaterialMap (theFile),
+  myWriter (NULL),
+  myDefSamplerId (theDefSamplerId),
+  myNbImages (0)
+{
+  myMatNameAsKey = false;
+}
+
+// =======================================================================
+// function : ~RWGltf_GltfMaterialMap
+// purpose  :
+// =======================================================================
+RWGltf_GltfMaterialMap::~RWGltf_GltfMaterialMap()
+{
+  //
+}
+
+// =======================================================================
+// function : AddImages
+// purpose  :
+// =======================================================================
+void RWGltf_GltfMaterialMap::AddImages (RWGltf_GltfOStreamWriter* theWriter,
+                                        const XCAFPrs_Style& theStyle,
+                                        Standard_Boolean& theIsStarted)
+{
+  if (theWriter == NULL
+   || theStyle.Material().IsNull()
+   || theStyle.Material()->IsEmpty())
+  {
+    return;
+  }
+
+  addImage (theWriter, baseColorTexture (theStyle.Material()), theIsStarted);
+  addImage (theWriter, theStyle.Material()->PbrMaterial().MetallicRoughnessTexture, theIsStarted);
+  addImage (theWriter, theStyle.Material()->PbrMaterial().NormalTexture,    theIsStarted);
+  addImage (theWriter, theStyle.Material()->PbrMaterial().EmissiveTexture,  theIsStarted);
+  addImage (theWriter, theStyle.Material()->PbrMaterial().OcclusionTexture, theIsStarted);
+}
+
+// =======================================================================
+// function : addImage
+// purpose  :
+// =======================================================================
+void RWGltf_GltfMaterialMap::addImage (RWGltf_GltfOStreamWriter* theWriter,
+                                       const Handle(Image_Texture)& theTexture,
+                                       Standard_Boolean& theIsStarted)
+{
+#ifdef HAVE_RAPIDJSON
+  if (theTexture.IsNull()
+   || myImageMap.IsBound1 (theTexture)
+   || myImageFailMap.Contains (theTexture))
+  {
+    return;
+  }
+
+  TCollection_AsciiString aGltfImgKey = myNbImages;
+  ++myNbImages;
+  for (; myImageMap.IsBound2 (aGltfImgKey); ++myNbImages)
+  {
+    aGltfImgKey = myNbImages;
+  }
+
+  TCollection_AsciiString aTextureUri;
+  if (!CopyTexture (aTextureUri, theTexture, aGltfImgKey))
+  {
+    myImageFailMap.Add (theTexture);
+    return;
+  }
+
+  myImageMap.Bind (theTexture, aGltfImgKey);
+
+  if (!theIsStarted)
+  {
+    theWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Images));
+    theWriter->StartArray();
+    theIsStarted = true;
+  }
+
+  theWriter->StartObject();
+  {
+    theWriter->Key ("uri");
+    theWriter->String (aTextureUri.ToCString());
+  }
+  theWriter->EndObject();
+#else
+  (void )theWriter;
+  (void )theTexture;
+  (void )theIsStarted;
+#endif
+}
+
+// =======================================================================
+// function : AddMaterial
+// purpose  :
+// =======================================================================
+void RWGltf_GltfMaterialMap::AddMaterial (RWGltf_GltfOStreamWriter* theWriter,
+                                          const XCAFPrs_Style& theStyle,
+                                          Standard_Boolean& theIsStarted)
+{
+#ifdef HAVE_RAPIDJSON
+  if (theWriter == NULL
+   || ((theStyle.Material().IsNull() || theStyle.Material()->IsEmpty())
+    && !theStyle.IsSetColorSurf()))
+  {
+    return;
+  }
+
+  if (!theIsStarted)
+  {
+    theWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Materials));
+    theWriter->StartArray();
+    theIsStarted = true;
+  }
+  myWriter = theWriter;
+  AddMaterial (theStyle);
+  myWriter = NULL;
+#else
+  (void )theWriter;
+  (void )theStyle;
+  (void )theIsStarted;
+#endif
+}
+
+// =======================================================================
+// function : AddTextures
+// purpose  :
+// =======================================================================
+void RWGltf_GltfMaterialMap::AddTextures (RWGltf_GltfOStreamWriter* theWriter,
+                                          const XCAFPrs_Style& theStyle,
+                                          Standard_Boolean& theIsStarted)
+{
+  if (theWriter == NULL
+   || theStyle.Material().IsNull()
+   || theStyle.Material()->IsEmpty())
+  {
+    return;
+  }
+
+  addTexture (theWriter, baseColorTexture (theStyle.Material()), theIsStarted);
+  addTexture (theWriter, theStyle.Material()->PbrMaterial().MetallicRoughnessTexture, theIsStarted);
+  addTexture (theWriter, theStyle.Material()->PbrMaterial().NormalTexture,    theIsStarted);
+  addTexture (theWriter, theStyle.Material()->PbrMaterial().EmissiveTexture,  theIsStarted);
+  addTexture (theWriter, theStyle.Material()->PbrMaterial().OcclusionTexture, theIsStarted);
+}
+
+// =======================================================================
+// function : addTexture
+// purpose  :
+// =======================================================================
+void RWGltf_GltfMaterialMap::addTexture (RWGltf_GltfOStreamWriter* theWriter,
+                                         const Handle(Image_Texture)& theTexture,
+                                         Standard_Boolean& theIsStarted)
+{
+#ifdef HAVE_RAPIDJSON
+  if (theTexture.IsNull()
+  ||  myTextureMap.Contains (theTexture)
+  || !myImageMap  .IsBound1 (theTexture))
+  {
+    return;
+  }
+
+  const TCollection_AsciiString anImgKey = myImageMap.Find1 (theTexture);
+  myTextureMap.Add (theTexture);
+  if (anImgKey.IsEmpty())
+  {
+    return;
+  }
+
+  if (!theIsStarted)
+  {
+    theWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Textures));
+    theWriter->StartArray();
+    theIsStarted = true;
+  }
+
+  theWriter->StartObject();
+  {
+    theWriter->Key ("sampler");
+    theWriter->Int (myDefSamplerId); // mandatory field by specs
+    theWriter->Key ("source");
+    theWriter->Int (anImgKey.IntegerValue());
+  }
+  theWriter->EndObject();
+#else
+  (void )theWriter;
+  (void )theTexture;
+  (void )theIsStarted;
+#endif
+}
+
+// =======================================================================
+// function : AddMaterial
+// purpose  :
+// =======================================================================
+TCollection_AsciiString RWGltf_GltfMaterialMap::AddMaterial (const XCAFPrs_Style& theStyle)
+{
+  return RWMesh_MaterialMap::AddMaterial (theStyle);
+}
+
+// =======================================================================
+// function : DefineMaterial
+// purpose  :
+// =======================================================================
+void RWGltf_GltfMaterialMap::DefineMaterial (const XCAFPrs_Style& theStyle,
+                                             const TCollection_AsciiString& /*theKey*/,
+                                             const TCollection_AsciiString& theName)
+{
+#ifdef HAVE_RAPIDJSON
+  if (myWriter == NULL)
+  {
+    Standard_ProgramError::Raise ("RWGltf_GltfMaterialMap::DefineMaterial() should be called with JSON Writer");
+    return;
+  }
+
+  XCAFDoc_VisMaterialPBR aPbrMat;
+  const bool hasMaterial = !theStyle.Material().IsNull()
+                        && !theStyle.Material()->IsEmpty();
+  if (hasMaterial)
+  {
+    aPbrMat = theStyle.Material()->ConvertToPbrMaterial();
+  }
+  else if (!myDefaultStyle.Material().IsNull()
+         && myDefaultStyle.Material()->HasPbrMaterial())
+  {
+    aPbrMat = myDefaultStyle.Material()->PbrMaterial();
+  }
+  if (theStyle.IsSetColorSurf())
+  {
+    aPbrMat.BaseColor.SetRGB (theStyle.GetColorSurf());
+    if (theStyle.GetColorSurfRGBA().Alpha() < 1.0f)
+    {
+      aPbrMat.BaseColor.SetAlpha (theStyle.GetColorSurfRGBA().Alpha());
+    }
+  }
+  myWriter->StartObject();
+  {
+    myWriter->Key ("name");
+    myWriter->String (theName.ToCString());
+
+    myWriter->Key ("pbrMetallicRoughness");
+    myWriter->StartObject();
+    {
+      myWriter->Key ("baseColorFactor");
+      myWriter->StartArray();
+      {
+        myWriter->Double (aPbrMat.BaseColor.GetRGB().Red());
+        myWriter->Double (aPbrMat.BaseColor.GetRGB().Green());
+        myWriter->Double (aPbrMat.BaseColor.GetRGB().Blue());
+        myWriter->Double (aPbrMat.BaseColor.Alpha());
+      }
+      myWriter->EndArray();
+
+      if (const Handle(Image_Texture)& aBaseTexture = baseColorTexture (theStyle.Material()))
+      {
+        if (myImageMap.IsBound1 (aBaseTexture))
+        {
+          myWriter->Key ("baseColorTexture");
+          myWriter->StartObject();
+          {
+            myWriter->Key ("index");
+            const TCollection_AsciiString& anImageIdx = myImageMap.Find1 (aBaseTexture);
+            if (!anImageIdx.IsEmpty())
+            {
+              myWriter->Int (anImageIdx.IntegerValue());
+            }
+          }
+          myWriter->EndObject();
+        }
+      }
+
+      if (hasMaterial
+       || aPbrMat.Metallic != 1.0f)
+      {
+        myWriter->Key ("metallicFactor");
+        myWriter->Double (aPbrMat.Metallic);
+      }
+
+      if (!aPbrMat.MetallicRoughnessTexture.IsNull()
+        && myImageMap.IsBound1 (aPbrMat.MetallicRoughnessTexture))
+      {
+        myWriter->Key ("metallicRoughnessTexture");
+        myWriter->StartObject();
+        {
+          myWriter->Key ("index");
+          const TCollection_AsciiString& anImageIdx = myImageMap.Find1 (aPbrMat.MetallicRoughnessTexture);
+          if (!anImageIdx.IsEmpty())
+          {
+            myWriter->Int (anImageIdx.IntegerValue());
+          }
+        }
+        myWriter->EndObject();
+      }
+
+      if (hasMaterial
+       || aPbrMat.Roughness != 1.0f)
+      {
+        myWriter->Key ("roughnessFactor");
+        myWriter->Double (aPbrMat.Roughness);
+      }
+    }
+    myWriter->EndObject();
+
+    if (theStyle.Material().IsNull()
+     || theStyle.Material()->IsDoubleSided())
+    {
+      myWriter->Key ("doubleSided");
+      myWriter->Bool (true);
+    }
+
+    const Graphic3d_AlphaMode anAlphaMode = !theStyle.Material().IsNull() ? theStyle.Material()->AlphaMode() : Graphic3d_AlphaMode_BlendAuto;
+    switch (anAlphaMode)
+    {
+      case Graphic3d_AlphaMode_BlendAuto:
+      {
+        if (aPbrMat.BaseColor.Alpha() < 1.0f)
+        {
+          myWriter->Key ("alphaMode");
+          myWriter->String ("BLEND");
+        }
+        break;
+      }
+      case Graphic3d_AlphaMode_Opaque:
+      {
+        break;
+      }
+      case Graphic3d_AlphaMode_Mask:
+      {
+        myWriter->Key ("alphaMode");
+        myWriter->String ("MASK");
+        break;
+      }
+      case Graphic3d_AlphaMode_Blend:
+      {
+        myWriter->Key ("alphaMode");
+        myWriter->String ("BLEND");
+        break;
+      }
+    }
+    if (!theStyle.Material().IsNull()
+      && theStyle.Material()->AlphaCutOff() != 0.5f)
+    {
+      myWriter->Key ("alphaCutoff");
+      myWriter->Double (theStyle.Material()->AlphaCutOff());
+    }
+
+    if (aPbrMat.EmissiveFactor != Graphic3d_Vec3 (0.0f, 0.0f, 0.0f))
+    {
+      myWriter->Key ("emissiveFactor");
+      myWriter->StartArray();
+      {
+        myWriter->Double (aPbrMat.EmissiveFactor.r());
+        myWriter->Double (aPbrMat.EmissiveFactor.g());
+        myWriter->Double (aPbrMat.EmissiveFactor.b());
+      }
+      myWriter->EndArray();
+    }
+    if (!aPbrMat.EmissiveTexture.IsNull()
+      && myImageMap.IsBound1 (aPbrMat.EmissiveTexture))
+    {
+      myWriter->Key ("emissiveTexture");
+      myWriter->StartObject();
+      {
+        myWriter->Key ("index");
+        const TCollection_AsciiString& anImageIdx = myImageMap.Find1 (aPbrMat.EmissiveTexture);
+        if (!anImageIdx.IsEmpty())
+        {
+          myWriter->Int (anImageIdx.IntegerValue());
+        }
+      }
+      myWriter->EndObject();
+    }
+
+    if (!aPbrMat.NormalTexture.IsNull()
+      && myImageMap.IsBound1 (aPbrMat.NormalTexture))
+    {
+      myWriter->Key ("normalTexture");
+      myWriter->StartObject();
+      {
+        myWriter->Key ("index");
+        const TCollection_AsciiString& anImageIdx = myImageMap.Find1 (aPbrMat.NormalTexture);
+        if (!anImageIdx.IsEmpty())
+        {
+          myWriter->Int (anImageIdx.IntegerValue());
+        }
+      }
+      myWriter->EndObject();
+    }
+
+    if (!aPbrMat.OcclusionTexture.IsNull()
+      && myImageMap.IsBound1 (aPbrMat.OcclusionTexture))
+    {
+      myWriter->Key ("occlusionTexture");
+      myWriter->StartObject();
+      {
+        myWriter->Key ("index");
+        const TCollection_AsciiString& anImageIdx = myImageMap.Find1 (aPbrMat.OcclusionTexture);
+        if (!anImageIdx.IsEmpty())
+        {
+          myWriter->Int (anImageIdx.IntegerValue());
+        }
+      }
+      myWriter->EndObject();
+    }
+  }
+  myWriter->EndObject();
+#else
+  (void )theStyle;
+  (void )theName;
+#endif
+}
diff --git a/src/RWGltf/RWGltf_GltfMaterialMap.hxx b/src/RWGltf/RWGltf_GltfMaterialMap.hxx
new file mode 100644 (file)
index 0000000..8159991
--- /dev/null
@@ -0,0 +1,88 @@
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWGltf_GltfMaterialMap_HeaderFile
+#define _RWGltf_GltfMaterialMap_HeaderFile
+
+#include <RWMesh_MaterialMap.hxx>
+
+class RWGltf_GltfOStreamWriter;
+
+//! Material manager for exporting into glTF format.
+class RWGltf_GltfMaterialMap : public RWMesh_MaterialMap
+{
+public:
+
+  //! Main constructor.
+  Standard_EXPORT RWGltf_GltfMaterialMap (const TCollection_AsciiString& theFile,
+                                          const Standard_Integer theDefSamplerId);
+
+  //! Destructor.
+  Standard_EXPORT virtual ~RWGltf_GltfMaterialMap();
+
+  //! Add material images.
+  Standard_EXPORT void AddImages (RWGltf_GltfOStreamWriter* theWriter,
+                                  const XCAFPrs_Style& theStyle,
+                                  Standard_Boolean& theIsStarted);
+
+  //! Add material.
+  Standard_EXPORT void AddMaterial (RWGltf_GltfOStreamWriter* theWriter,
+                                    const XCAFPrs_Style& theStyle,
+                                    Standard_Boolean& theIsStarted);
+  //! Add material textures.
+  Standard_EXPORT void AddTextures (RWGltf_GltfOStreamWriter* theWriter,
+                                    const XCAFPrs_Style& theStyle,
+                                    Standard_Boolean& theIsStarted);
+
+  //! Return extent of images map.
+  Standard_Integer NbImages() const { return myImageMap.Extent(); }
+
+  //! Return extent of textures map.
+  Standard_Integer NbTextures() const { return myTextureMap.Extent(); }
+
+public:
+
+  //! Return base color texture.
+  Standard_EXPORT static const Handle(Image_Texture)& baseColorTexture (const Handle(XCAFDoc_VisMaterial)& theMat);
+
+protected:
+
+  //! Add texture image.
+  Standard_EXPORT void addImage (RWGltf_GltfOStreamWriter* theWriter,
+                                 const Handle(Image_Texture)& theTexture,
+                                 Standard_Boolean& theIsStarted);
+
+  //! Add texture.
+  Standard_EXPORT void addTexture (RWGltf_GltfOStreamWriter* theWriter,
+                                   const Handle(Image_Texture)& theTexture,
+                                   Standard_Boolean& theIsStarted);
+
+  //! Add material
+  Standard_EXPORT virtual TCollection_AsciiString AddMaterial (const XCAFPrs_Style& theStyle) Standard_OVERRIDE;
+
+  //! Virtual method actually defining the material (e.g. export to the file).
+  Standard_EXPORT virtual void DefineMaterial (const XCAFPrs_Style& theStyle,
+                                               const TCollection_AsciiString& theKey,
+                                               const TCollection_AsciiString& theName) Standard_OVERRIDE;
+
+protected:
+
+  RWGltf_GltfOStreamWriter* myWriter;
+  NCollection_DoubleMap<Handle(Image_Texture), TCollection_AsciiString, Image_Texture, TCollection_AsciiString> myImageMap;
+  NCollection_Map<Handle(Image_Texture), Image_Texture> myTextureMap;
+  Standard_Integer myDefSamplerId;
+  Standard_Integer myNbImages;
+
+};
+
+#endif // _RWGltf_GltfMaterialMap_HeaderFile
diff --git a/src/RWGltf/RWGltf_GltfOStreamWriter.hxx b/src/RWGltf/RWGltf_GltfOStreamWriter.hxx
new file mode 100644 (file)
index 0000000..1746997
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright (c) 2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWGltf_GltfOStreamWriter_HeaderFile
+#define _RWGltf_GltfOStreamWriter_HeaderFile
+
+#include <rapidjson/prettywriter.h>
+#include <rapidjson/ostreamwrapper.h>
+
+//! rapidjson::Writer wrapper for forward declaration.
+class RWGltf_GltfOStreamWriter : public rapidjson::Writer<rapidjson::OStreamWrapper>
+{
+public:
+  //! Main constructor.
+  RWGltf_GltfOStreamWriter (rapidjson::OStreamWrapper& theOStream)
+  : rapidjson::Writer<rapidjson::OStreamWrapper> (theOStream) {}
+};
+
+#endif // _RWGltf_GltfOStreamWriter_HeaderFile
diff --git a/src/RWGltf/RWGltf_GltfSceneNodeMap.hxx b/src/RWGltf/RWGltf_GltfSceneNodeMap.hxx
new file mode 100644 (file)
index 0000000..c6743fe
--- /dev/null
@@ -0,0 +1,48 @@
+// Copyright (c) 2018-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWGltf_GltfSceneNodeMap_HeaderFile
+#define _RWGltf_GltfSceneNodeMap_HeaderFile
+
+#include <NCollection_IndexedMap.hxx>
+#include <XCAFPrs_DocumentExplorer.hxx>
+
+//! Indexed map of scene nodes with custom search algorithm.
+class RWGltf_GltfSceneNodeMap : public NCollection_IndexedMap<XCAFPrs_DocumentNode, XCAFPrs_DocumentNode>
+{
+public:
+
+  //! Empty constructor.
+  RWGltf_GltfSceneNodeMap() {}
+
+  //! Find index from document node string identifier.
+  Standard_Integer FindIndex (const TCollection_AsciiString& theNodeId) const
+  {
+    if (IsEmpty())
+    {
+      return 0;
+    }
+
+    for (IndexedMapNode* aNode1Iter = (IndexedMapNode* )myData1[::HashCode (theNodeId, NbBuckets())]; aNode1Iter != NULL; aNode1Iter = (IndexedMapNode* )aNode1Iter->Next())
+    {
+      if (::IsEqual (aNode1Iter->Key1().Id, theNodeId))
+      {
+        return aNode1Iter->Index();
+      }
+    }
+    return 0;
+  }
+
+};
+
+#endif // _RWGltf_GltfSceneNodeMap_HeaderFile
diff --git a/src/RWGltf/RWGltf_WriterTrsfFormat.hxx b/src/RWGltf/RWGltf_WriterTrsfFormat.hxx
new file mode 100644 (file)
index 0000000..6ef51e5
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWGltf_WriterTrsfFormat_HeaderFile
+#define _RWGltf_WriterTrsfFormat_HeaderFile
+
+//! Transformation format.
+enum RWGltf_WriterTrsfFormat
+{
+  RWGltf_WriterTrsfFormat_Compact = 0, //!< automatically choose most compact representation between Mat4 and TRS
+  RWGltf_WriterTrsfFormat_Mat4    = 1, //!< 4x4 transformation Matrix
+  RWGltf_WriterTrsfFormat_TRS     = 2, //!< transformation decomposed into Translation vector, Rotation quaternion and Scale factor (T * R * S)
+};
+enum { RWGltf_WriterTrsfFormat_LOWER = 0, RWGltf_WriterTrsfFormat_UPPER = RWGltf_WriterTrsfFormat_TRS }; // aliases
+
+#endif // _RWGltf_WriterTrsfFormat_HeaderFile
index 902571c..1533fa8 100644 (file)
@@ -3,4 +3,8 @@ RWMesh_CoordinateSystemConverter.cxx
 RWMesh_CoordinateSystemConverter.hxx
 RWMesh_CafReader.cxx
 RWMesh_CafReader.hxx
+RWMesh_FaceIterator.cxx
+RWMesh_FaceIterator.hxx
+RWMesh_MaterialMap.cxx
+RWMesh_MaterialMap.hxx
 RWMesh_NodeAttributes.hxx
diff --git a/src/RWMesh/RWMesh_FaceIterator.cxx b/src/RWMesh/RWMesh_FaceIterator.cxx
new file mode 100644 (file)
index 0000000..51b95f7
--- /dev/null
@@ -0,0 +1,240 @@
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <RWMesh_FaceIterator.hxx>
+
+#include <BRepAdaptor_Surface.hxx>
+#include <BRep_Tool.hxx>
+#include <TopExp.hxx>
+#include <TopoDS.hxx>
+#include <XCAFDoc_ShapeTool.hxx>
+#include <XCAFPrs.hxx>
+
+// =======================================================================
+// function : RWMesh_FaceIterator
+// purpose  :
+// =======================================================================
+RWMesh_FaceIterator::RWMesh_FaceIterator (const TDF_Label&       theLabel,
+                                          const TopLoc_Location& theLocation,
+                                          const Standard_Boolean theToMapColors,
+                                          const XCAFPrs_Style&   theStyle)
+: myDefStyle (theStyle),
+  myToMapColors (theToMapColors),
+  mySLTool  (1, 1e-12),
+  myNodes   (NULL),
+  myNormals (NULL),
+  myNodeUVs (NULL),
+  myHasNormals (false),
+  myIsMirrored (false),
+  myHasFaceColor (false)
+{
+  TopoDS_Shape aShape;
+  if (!XCAFDoc_ShapeTool::GetShape (theLabel, aShape)
+   ||  aShape.IsNull())
+  {
+    return;
+  }
+
+  aShape.Location (theLocation);
+  myFaceIter.Init (aShape, TopAbs_FACE);
+
+  if (theToMapColors)
+  {
+    dispatchStyles (theLabel, theLocation, theStyle);
+  }
+
+  Next();
+}
+
+// =======================================================================
+// function : dispatchStyles
+// purpose  :
+// =======================================================================
+void RWMesh_FaceIterator::dispatchStyles (const TDF_Label&       theLabel,
+                                          const TopLoc_Location& theLocation,
+                                          const XCAFPrs_Style&   theStyle)
+{
+  TopLoc_Location aDummyLoc;
+  XCAFPrs_IndexedDataMapOfShapeStyle aStyles;
+  XCAFPrs::CollectStyleSettings (theLabel, aDummyLoc, aStyles);
+
+  Standard_Integer aNbTypes[TopAbs_SHAPE] = {};
+  for (Standard_Integer aTypeIter = TopAbs_FACE; aTypeIter >= TopAbs_COMPOUND; --aTypeIter)
+  {
+    if (aTypeIter != TopAbs_FACE
+     && aNbTypes[aTypeIter] == 0)
+    {
+      continue;
+    }
+
+    for (XCAFPrs_IndexedDataMapOfShapeStyle::Iterator aStyleIter (aStyles); aStyleIter.More(); aStyleIter.Next())
+    {
+      const TopoDS_Shape&    aKeyShape     = aStyleIter.Key();
+      const TopAbs_ShapeEnum aKeyShapeType = aKeyShape.ShapeType();
+      if (aTypeIter == TopAbs_FACE)
+      {
+        ++aNbTypes[aKeyShapeType];
+      }
+      if (aTypeIter != aKeyShapeType)
+      {
+        continue;
+      }
+
+      XCAFPrs_Style aCafStyle = aStyleIter.Value();
+      if (!aCafStyle.IsSetColorCurv()
+         && theStyle.IsSetColorCurv())
+      {
+        aCafStyle.SetColorCurv (theStyle.GetColorCurv());
+      }
+      if (!aCafStyle.IsSetColorSurf()
+         && theStyle.IsSetColorSurf())
+      {
+        aCafStyle.SetColorSurf (theStyle.GetColorSurfRGBA());
+      }
+      if (aCafStyle.Material().IsNull()
+       && !theStyle.Material().IsNull())
+      {
+        aCafStyle.SetMaterial (theStyle.Material());
+      }
+
+      TopoDS_Shape aKeyShapeLocated = aKeyShape.Located (theLocation);
+      if (aKeyShapeType == TopAbs_FACE)
+      {
+        myStyles.Bind (aKeyShapeLocated, aCafStyle);
+      }
+      else
+      {
+        for (TopExp_Explorer aFaceIter (aKeyShapeLocated, TopAbs_FACE); aFaceIter.More(); aFaceIter.Next())
+        {
+          if (!myStyles.IsBound (aFaceIter.Current()))
+          {
+            myStyles.Bind (aFaceIter.Current(), aCafStyle);
+          }
+        }
+      }
+    }
+  }
+}
+
+// =======================================================================
+// function : normal
+// purpose  :
+// =======================================================================
+gp_Dir RWMesh_FaceIterator::normal (Standard_Integer theNode)
+{
+  gp_Dir aNormal (gp::DZ());
+  if (myNormals != NULL)
+  {
+    const Standard_Integer aNodeIndex = theNode - myNodes->Lower();
+    const Graphic3d_Vec3 aNormVec3 (myNormals->Value (myNormals->Lower() + aNodeIndex * 3),
+                                    myNormals->Value (myNormals->Lower() + aNodeIndex * 3 + 1),
+                                    myNormals->Value (myNormals->Lower() + aNodeIndex * 3 + 2));
+    if (aNormVec3.Modulus() != 0.0f)
+    {
+      aNormal.SetCoord (aNormVec3.x(), aNormVec3.y(), aNormVec3.z());
+    }
+  }
+  else if (myHasNormals
+        && myNodeUVs != NULL)
+  {
+    const gp_XY& anUV = myNodeUVs->Value (theNode).XY();
+    mySLTool.SetParameters (anUV.X(), anUV.Y());
+    if (mySLTool.IsNormalDefined())
+    {
+      aNormal = mySLTool.Normal();
+    }
+  }
+  return aNormal;
+}
+
+// =======================================================================
+// function : Next
+// purpose  :
+// =======================================================================
+void RWMesh_FaceIterator::Next()
+{
+  for (; myFaceIter.More(); myFaceIter.Next())
+  {
+    myFace       = TopoDS::Face (myFaceIter.Current());
+    myPolyTriang = BRep_Tool::Triangulation (myFace, myFaceLocation);
+    myTrsf       = myFaceLocation.Transformation();
+    if (myPolyTriang.IsNull()
+     || myPolyTriang->Triangles().Length() == 0)
+    {
+      resetFace();
+      continue;
+    }
+
+    initFace();
+    myFaceIter.Next();
+    return;
+  }
+
+  resetFace();
+}
+
+// =======================================================================
+// function : initFace
+// purpose  :
+// =======================================================================
+void RWMesh_FaceIterator::initFace()
+{
+  myHasNormals   = false;
+  myHasFaceColor = false;
+  myIsMirrored   = myTrsf.VectorialPart().Determinant() < 0.0;
+  myNormals = NULL;
+  myNodeUVs = NULL;
+
+  myNodes = &myPolyTriang->Nodes();
+  if (myPolyTriang->HasNormals())
+  {
+    myNormals = &myPolyTriang->Normals();
+    myHasNormals = true;
+  }
+  if (myPolyTriang->HasUVNodes())
+  {
+    myNodeUVs = &myPolyTriang->UVNodes();
+    if (!myHasNormals)
+    {
+      TopoDS_Face aFaceFwd = TopoDS::Face (myFace.Oriented (TopAbs_FORWARD));
+      aFaceFwd.Location (TopLoc_Location());
+      TopLoc_Location aLoc;
+      if (!BRep_Tool::Surface (aFaceFwd, aLoc).IsNull())
+      {
+        myFaceAdaptor.Initialize (aFaceFwd, false);
+        mySLTool.SetSurface (myFaceAdaptor);
+        myHasNormals = true;
+      }
+    }
+  }
+  if (!myToMapColors)
+  {
+    return;
+  }
+
+  if (!myStyles.Find (myFace, myFaceStyle))
+  {
+    myFaceStyle = myDefStyle;
+  }
+
+  if (!myFaceStyle.Material().IsNull())
+  {
+    myHasFaceColor = true;
+    myFaceColor = myFaceStyle.Material()->BaseColor();
+  }
+  else if (myFaceStyle.IsSetColorSurf())
+  {
+    myHasFaceColor = true;
+    myFaceColor = myFaceStyle.GetColorSurfRGBA();
+  }
+}
diff --git a/src/RWMesh/RWMesh_FaceIterator.hxx b/src/RWMesh/RWMesh_FaceIterator.hxx
new file mode 100644 (file)
index 0000000..0e2f248
--- /dev/null
@@ -0,0 +1,205 @@
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWMesh_FaceIterator_HeaderFile
+#define _RWMesh_FaceIterator_HeaderFile
+
+#include <BRepLProp_SLProps.hxx>
+#include <gp_Trsf.hxx>
+#include <NCollection_DataMap.hxx>
+#include <Poly_Array1OfTriangle.hxx>
+#include <Poly_Triangulation.hxx>
+#include <TopExp_Explorer.hxx>
+#include <TopoDS_Face.hxx>
+#include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
+#include <XCAFPrs_Style.hxx>
+
+#include <algorithm>
+
+class TDF_Label;
+
+//! Auxiliary class to iterate through triangulated faces.
+class RWMesh_FaceIterator
+{
+public:
+
+  //! Main constructor.
+  Standard_EXPORT RWMesh_FaceIterator (const TDF_Label&       theLabel,
+                                       const TopLoc_Location& theLocation,
+                                       const Standard_Boolean theToMapColors = false,
+                                       const XCAFPrs_Style&   theStyle = XCAFPrs_Style());
+
+  //! Return true if iterator points to the valid triangulation.
+  bool More() const { return !myPolyTriang.IsNull(); }
+
+  //! Find next value.
+  Standard_EXPORT void Next();
+
+  //! Return current face.
+  const TopoDS_Face& Face() const { return myFace; }
+
+  //! Return current face triangulation.
+  const Handle(Poly_Triangulation)& Triangulation() const { return myPolyTriang; }
+
+  //! Return true if mesh data is defined.
+  bool IsEmptyMesh() const
+  {
+    return myPolyTriang.IsNull()
+       || (myPolyTriang->NbNodes() < 1 && myPolyTriang->NbTriangles() < 1);
+  }
+
+public:
+
+  //! Return face material.
+  const XCAFPrs_Style& FaceStyle() const { return myFaceStyle; }
+
+  //! Return TRUE if face color is set.
+  bool HasFaceColor() const { return myHasFaceColor; }
+
+  //! Return face color.
+  const Quantity_ColorRGBA& FaceColor() const { return myFaceColor; }
+
+public:
+
+  //! Return number of elements of specific type for the current face.
+  Standard_Integer NbTriangles() const { return myPolyTriang->NbTriangles(); }
+
+  //! Lower element index in current triangulation.
+  Standard_Integer ElemLower() const { return myPolyTriang->Triangles().Lower(); }
+
+  //! Upper element index in current triangulation.
+  Standard_Integer ElemUpper() const { return myPolyTriang->Triangles().Upper(); }
+
+  //! Return triangle with specified index with applied Face orientation.
+  Poly_Triangle TriangleOriented (Standard_Integer theElemIndex) const
+  {
+    Poly_Triangle aTri = triangle (theElemIndex);
+    if ((myFace.Orientation() == TopAbs_REVERSED) ^ myIsMirrored)
+    {
+      return Poly_Triangle (aTri.Value (1), aTri.Value (3), aTri.Value (2));
+    }
+    return aTri;
+  }
+
+public:
+
+  //! Return true if triangulation has defined normals.
+  bool HasNormals() const { return myHasNormals; }
+
+  //! Return true if triangulation has defined normals.
+  bool HasTexCoords() const { return myNodeUVs != NULL; }
+
+  //! Return normal at specified node index with face transformation applied and face orientation applied.
+  gp_Dir NormalTransformed (Standard_Integer theNode)
+  {
+    gp_Dir aNorm = normal (theNode);
+    if (myTrsf.Form() != gp_Identity)
+    {
+      aNorm.Transform (myTrsf);
+    }
+    if (myFace.Orientation() == TopAbs_REVERSED)
+    {
+      aNorm.Reverse();
+    }
+    return aNorm;
+  }
+
+  //! Return number of nodes for the current face.
+  Standard_Integer NbNodes() const
+  {
+    return !myPolyTriang.IsNull()
+          ? myPolyTriang->Nodes().Length()
+          : 0;
+  }
+
+  //! Lower node index in current triangulation.
+  Standard_Integer NodeLower() const { return myPolyTriang->Nodes().Lower(); }
+
+  //! Upper node index in current triangulation.
+  Standard_Integer NodeUpper() const { return myPolyTriang->Nodes().Upper(); }
+
+  //! Return the node with specified index with applied transformation.
+  gp_Pnt NodeTransformed (const Standard_Integer theNode) const
+  {
+    gp_Pnt aNode = node (theNode);
+    aNode.Transform (myTrsf);
+    return aNode;
+  }
+
+  //! Return texture coordinates for the node.
+  gp_Pnt2d NodeTexCoord (const Standard_Integer theNode) const
+  {
+    return myNodeUVs != NULL ? myNodeUVs->Value (theNode) : gp_Pnt2d();
+  }
+
+public:
+
+  //! Return the node with specified index with applied transformation.
+  gp_Pnt node (const Standard_Integer theNode) const { return myPolyTriang->Nodes().Value (theNode); }
+
+  //! Return normal at specified node index without face transformation applied.
+  Standard_EXPORT gp_Dir normal (Standard_Integer theNode);
+
+  //! Return triangle with specified index.
+  Poly_Triangle triangle (Standard_Integer theElemIndex) const { return myPolyTriang->Triangles().Value (theElemIndex); }
+
+private:
+
+  //! Dispatch face styles.
+  void dispatchStyles (const TDF_Label&       theLabel,
+                       const TopLoc_Location& theLocation,
+                       const XCAFPrs_Style&   theStyle);
+
+  //! Reset information for current face.
+  void resetFace()
+  {
+    myPolyTriang.Nullify();
+    myFace.Nullify();
+    myNodes   = NULL;
+    myNormals = NULL;
+    myNodeUVs = NULL;
+    myHasNormals = false;
+    myHasFaceColor = false;
+    myFaceColor = Quantity_ColorRGBA();
+    myFaceStyle = XCAFPrs_Style();
+  }
+
+  //! Initialize face properties.
+  void initFace();
+
+private:
+
+  NCollection_DataMap<TopoDS_Shape, XCAFPrs_Style, TopTools_ShapeMapHasher>
+                                  myStyles;       //!< Face -> Style map
+  XCAFPrs_Style                   myDefStyle;     //!< default style for faces without dedicated style
+  Standard_Boolean                myToMapColors;  //!< flag to dispatch styles
+
+  TopExp_Explorer                 myFaceIter;     //!< face explorer
+  TopoDS_Face                     myFace;         //!< current face
+  Handle(Poly_Triangulation)      myPolyTriang;   //!< triangulation of current face
+  TopLoc_Location                 myFaceLocation; //!< current face location
+  BRepLProp_SLProps               mySLTool;       //!< auxiliary tool for fetching normals from surface
+  BRepAdaptor_Surface             myFaceAdaptor;  //!< surface adaptor for fetching normals from surface
+  const TColgp_Array1OfPnt*       myNodes;        //!< node positions of current face
+  const TShort_Array1OfShortReal* myNormals;      //!< node normals of current face
+  const TColgp_Array1OfPnt2d*     myNodeUVs;      //!< node UV coordinates of current face
+  Standard_Boolean                myHasNormals;   //!< flag indicating that current face has normals
+  gp_Trsf                         myTrsf;         //!< current face transformation
+  Standard_Boolean                myIsMirrored;   //!< flag indicating that face triangles should be mirrored
+  XCAFPrs_Style                   myFaceStyle;    //!< current face style
+  Quantity_ColorRGBA              myFaceColor;    //!< current face color
+  Standard_Boolean                myHasFaceColor; //!< flag indicating that current face has assigned color
+
+};
+
+#endif // _RWMesh_FaceIterator_HeaderFile
diff --git a/src/RWMesh/RWMesh_MaterialMap.cxx b/src/RWMesh/RWMesh_MaterialMap.cxx
new file mode 100644 (file)
index 0000000..a25f1c8
--- /dev/null
@@ -0,0 +1,239 @@
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <RWMesh_MaterialMap.hxx>
+
+#include <Message.hxx>
+#include <Message_Messenger.hxx>
+#include <OSD_Directory.hxx>
+#include <OSD_File.hxx>
+#include <OSD_Path.hxx>
+#include <OSD_Protection.hxx>
+#include <TDataStd_Name.hxx>
+#include <TDF_Label.hxx>
+
+// =======================================================================
+// function : RWMesh_MaterialMap
+// purpose  :
+// =======================================================================
+RWMesh_MaterialMap::RWMesh_MaterialMap (const TCollection_AsciiString& theFile)
+: myFileName (theFile),
+  myKeyPrefix ("mat_"),
+  myNbMaterials (0),
+  myIsFailed (false),
+  myMatNameAsKey (true)
+{
+  TCollection_AsciiString aFileName, aFileExt;
+  OSD_Path::FolderAndFileFromPath (theFile, myFolder, aFileName);
+  OSD_Path::FileNameAndExtension (aFileName, myShortFileNameBase, aFileExt);
+}
+
+// =======================================================================
+// function : ~RWMesh_MaterialMap
+// purpose  :
+// =======================================================================
+RWMesh_MaterialMap::~RWMesh_MaterialMap()
+{
+  //
+}
+
+// =======================================================================
+// function : AddMaterial
+// purpose  :
+// =======================================================================
+TCollection_AsciiString RWMesh_MaterialMap::AddMaterial (const XCAFPrs_Style& theStyle)
+{
+  if (myStyles.IsBound1 (theStyle))
+  {
+    return myStyles.Find1 (theStyle);
+  }
+
+  TCollection_AsciiString aMatKey, aMatName, aMatNameSuffix;
+  int  aCounter    = 0;
+  int* aCounterPtr = &myNbMaterials;
+  if (myMatNameAsKey)
+  {
+    if (!theStyle.Material().IsNull()
+     && !theStyle.Material()->IsEmpty())
+    {
+      aCounterPtr = &aCounter;
+      Handle(TDataStd_Name) aNodeName;
+      if (!theStyle.Material()->Label().IsNull()
+       &&  theStyle.Material()->Label().FindAttribute (TDataStd_Name::GetID(), aNodeName))
+      {
+        aMatName = aNodeName->Get();
+      }
+      else
+      {
+        aMatName = "mat";
+      }
+      aMatNameSuffix = aMatName;
+    }
+    else
+    {
+      ++myNbMaterials;
+      aMatNameSuffix = myKeyPrefix;
+      aMatName = aMatNameSuffix + myNbMaterials;
+    }
+    aMatKey = aMatName;
+  }
+  else
+  {
+    aMatKey        = myNbMaterials++; // starts from 0
+    aMatNameSuffix = myKeyPrefix;
+    aMatName       = aMatNameSuffix + aMatKey;
+  }
+
+  for (;; ++(*aCounterPtr))
+  {
+    if (myStyles.IsBound2 (aMatKey))
+    {
+      if (myMatNameAsKey)
+      {
+        aMatName = aMatNameSuffix + (*aCounterPtr);
+        aMatKey  = aMatName;
+      }
+      else
+      {
+        aMatKey  = *aCounterPtr;
+        aMatName = aMatNameSuffix + aMatKey;
+      }
+      continue;
+    }
+    break;
+  }
+
+  myStyles.Bind (theStyle, aMatKey);
+  DefineMaterial (theStyle, aMatKey, aMatName);
+  return aMatKey;
+}
+
+// =======================================================================
+// function : copyFileTo
+// purpose  :
+// =======================================================================
+bool RWMesh_MaterialMap::copyFileTo (const TCollection_AsciiString& theFileSrc,
+                                     const TCollection_AsciiString& theFileDst)
+{
+  if (theFileSrc.IsEmpty()
+   || theFileDst.IsEmpty())
+  {
+    return false;
+  }
+  else if (theFileSrc == theFileDst)
+  {
+    return true;
+  }
+
+  try
+  {
+    OSD_Path aSrcPath (theFileSrc);
+    OSD_Path aDstPath (theFileDst);
+    OSD_File aFileSrc (aSrcPath);
+    if (!aFileSrc.Exists())
+    {
+      Message::DefaultMessenger()->Send (TCollection_AsciiString("Failed to copy file - source file '")
+                                       + theFileSrc + "' does not exist\n", Message_Fail);
+      return false;
+    }
+    aFileSrc.Copy (aDstPath);
+    return !aFileSrc.Failed();
+  }
+  catch (Standard_Failure const& theException)
+  {
+    Message::DefaultMessenger()->Send (TCollection_AsciiString("Failed to copy file\n") +
+                                       theException.GetMessageString(), Message_Fail);
+    return false;
+  }
+}
+
+// =======================================================================
+// function : CopyTexture
+// purpose  :
+// =======================================================================
+bool RWMesh_MaterialMap::CopyTexture (TCollection_AsciiString& theResTexture,
+                                      const Handle(Image_Texture)& theTexture,
+                                      const TCollection_AsciiString& theKey)
+{
+  CreateTextureFolder();
+
+  TCollection_AsciiString aTexFileName;
+  TCollection_AsciiString aTextureSrc = theTexture->FilePath();
+  if (!aTextureSrc.IsEmpty())
+  {
+    TCollection_AsciiString aSrcTexFolder;
+    OSD_Path::FolderAndFileFromPath (aTextureSrc, aSrcTexFolder, aTexFileName);
+    const TCollection_AsciiString aResTexFile = myTexFolder + aTexFileName;
+    theResTexture = myTexFolderShort + aTexFileName;
+    return copyFileTo (aTextureSrc, aResTexFile);
+  }
+
+  TCollection_AsciiString anExt = theTexture->ProbeImageFileFormat();
+  if (anExt.IsEmpty())
+  {
+    anExt = "bin";
+  }
+  aTexFileName = theKey + "." + anExt;
+
+  const TCollection_AsciiString aResTexFile = myTexFolder + aTexFileName;
+  theResTexture = myTexFolderShort + aTexFileName;
+  return theTexture->WriteImage (aResTexFile);
+}
+
+// =======================================================================
+// function : CreateTextureFolder
+// purpose  :
+// =======================================================================
+bool RWMesh_MaterialMap::CreateTextureFolder()
+{
+  if (!myTexFolder.IsEmpty())
+  {
+    return true;
+  }
+
+  myTexFolderShort = myShortFileNameBase + "_textures/";
+  myTexFolder      = myFolder + "/" + myTexFolderShort;
+  OSD_Path aTexFolderPath (myTexFolder);
+  OSD_Directory aTexDir (aTexFolderPath);
+  if (aTexDir.Exists())
+  {
+    return true;
+  }
+
+  OSD_Path aResFolderPath (myFolder);
+  OSD_Directory aResDir (aResFolderPath);
+  if (!aResDir.Exists())
+  {
+    return false;
+  }
+  const OSD_Protection aParentProt = aResDir.Protection();
+  OSD_Protection aProt = aParentProt;
+  if (aProt.User() == OSD_None)
+  {
+    aProt.SetUser (OSD_RWXD);
+  }
+  if (aProt.System() == OSD_None)
+  {
+    aProt.SetSystem (OSD_RWXD);
+  }
+
+  aTexDir.Build (aProt);
+  if (aTexDir.Failed())
+  {
+    // fallback to the same folder as output model file
+    myTexFolder = myFolder;
+    myTexFolderShort.Clear();
+    return true;
+  }
+  return true;
+}
diff --git a/src/RWMesh/RWMesh_MaterialMap.hxx b/src/RWMesh/RWMesh_MaterialMap.hxx
new file mode 100644 (file)
index 0000000..2e9034c
--- /dev/null
@@ -0,0 +1,101 @@
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWMesh_MaterialMap_HeaderFile
+#define _RWMesh_MaterialMap_HeaderFile
+
+#include <NCollection_DoubleMap.hxx>
+#include <NCollection_Map.hxx>
+#include <XCAFPrs_Style.hxx>
+
+//! Material manager.
+//! Provides an interface for collecting all materials within the document before writing it into file,
+//! and for copying associated image files (textures) into sub-folder near by exported model.
+class RWMesh_MaterialMap
+{
+public:
+
+  //! Main constructor.
+  Standard_EXPORT RWMesh_MaterialMap (const TCollection_AsciiString& theFile);
+
+  //! Destructor.
+  Standard_EXPORT virtual ~RWMesh_MaterialMap();
+
+  //! Return default material definition to be used for nodes with only color defined.
+  const XCAFPrs_Style& DefaultStyle() const { return myDefaultStyle; }
+
+  //! Set default material definition to be used for nodes with only color defined.
+  void SetDefaultStyle (const XCAFPrs_Style& theStyle) { myDefaultStyle = theStyle; }
+
+  //! Find already registered material
+  TCollection_AsciiString FindMaterial (const XCAFPrs_Style& theStyle) const
+  {
+    if (myStyles.IsBound1 (theStyle))
+    {
+      return myStyles.Find1 (theStyle);
+    }
+    return TCollection_AsciiString();
+  }
+
+  //! Register material and return its name identifier.
+  Standard_EXPORT virtual TCollection_AsciiString AddMaterial (const XCAFPrs_Style& theStyle);
+
+  //! Create texture folder "modelName/textures"; for example:
+  //! MODEL:  Path/ModelName.gltf
+  //! IMAGES: Path/ModelName/textures/
+  //! Warning! Output folder is NOT cleared.
+  Standard_EXPORT virtual bool CreateTextureFolder();
+
+  //! Copy and rename texture file to the new location.
+  //! @param theResTexture [out] result texture file path (relative to the model)
+  //! @param theTexture [in] original texture
+  //! @param theKey [in] material key
+  Standard_EXPORT virtual bool CopyTexture (TCollection_AsciiString& theResTexture,
+                                            const Handle(Image_Texture)& theTexture,
+                                            const TCollection_AsciiString& theKey);
+
+  //! Virtual method actually defining the material (e.g. export to the file).
+  virtual void DefineMaterial (const XCAFPrs_Style& theStyle,
+                               const TCollection_AsciiString& theKey,
+                               const TCollection_AsciiString& theName) = 0;
+
+  //! Return failed flag.
+  bool IsFailed() const { return myIsFailed; }
+
+protected:
+
+  //! Copy file to another place.
+  Standard_EXPORT static bool copyFileTo (const TCollection_AsciiString& theFileSrc,
+                                          const TCollection_AsciiString& theFileDst);
+
+protected:
+
+  TCollection_AsciiString myFolder;            //!< output folder for glTF file
+  TCollection_AsciiString myTexFolder;         //!< output folder for images (full  path)
+  TCollection_AsciiString myTexFolderShort;    //!< output folder for images (short path)
+  TCollection_AsciiString myFileName;          //!< output glTF file path
+  TCollection_AsciiString myShortFileNameBase; //!< output glTF file name without extension
+  TCollection_AsciiString myKeyPrefix;         //!< prefix for generated keys
+  NCollection_DoubleMap<XCAFPrs_Style, TCollection_AsciiString,
+                        XCAFPrs_Style, TCollection_AsciiString>
+                          myStyles;            //!< map of processed styles
+  NCollection_Map<Handle(Image_Texture), Image_Texture>
+                          myImageFailMap;      //!< map of images failed to be copied
+  XCAFPrs_Style           myDefaultStyle;      //!< default material definition to be used for nodes with only color defined
+  Standard_Integer        myNbMaterials;       //!< number of registered materials
+  Standard_Boolean        myIsFailed;          //!< flag indicating failure
+  Standard_Boolean        myMatNameAsKey;      //!< flag indicating usage of material name as key
+
+};
+
+#endif // _RWMesh_MaterialMap_HeaderFile
index f72b131..1947db4 100755 (executable)
@@ -20,4 +20,5 @@ TKSTL
 TKVRML
 TKLCAF
 TKDCAF
+TKXCAF
 TKRWMesh
index 9126a40..68bf1df 100644 (file)
@@ -34,6 +34,23 @@ struct XCAFPrs_DocumentNode
   Standard_Boolean        IsAssembly; //!< flag indicating that this label is assembly
 
   XCAFPrs_DocumentNode() : IsAssembly (Standard_False) {}
+
+public: // Methods for hash map
+
+  //! Return hash code based on node string identifier.
+  static Standard_Integer HashCode (const XCAFPrs_DocumentNode& theNode,
+                                    const Standard_Integer theN)
+  {
+    return ::HashCode (theNode.Id, theN);
+  }
+
+  //! Return TRUE if two document nodes has the same string identifier.
+  static Standard_Boolean IsEqual (const XCAFPrs_DocumentNode& theNode1,
+                                   const XCAFPrs_DocumentNode& theNode2)
+  {
+    return theNode1.Id == theNode2.Id;
+  }
+
 };
 
 #endif // _XCAFPrs_DocumentNode_HeaderFile
index cf86dc0..5c3630a 100644 (file)
@@ -43,6 +43,7 @@
 #include <Quantity_HArray1OfColor.hxx>
 #include <Quantity_NameOfColor.hxx>
 #include <RWGltf_CafReader.hxx>
+#include <RWGltf_CafWriter.hxx>
 #include <RWStl.hxx>
 #include <RWObj.hxx>
 #include <RWObj_CafReader.hxx>
@@ -69,6 +70,8 @@
 #include <VrmlData_DataMapOfShapeAppearance.hxx>
 #include <VrmlData_Scene.hxx>
 #include <VrmlData_ShapeConvert.hxx>
+#include <XCAFDoc_DocumentTool.hxx>
+#include <XCAFDoc_ShapeTool.hxx>
 #include <XSDRAW.hxx>
 #include <XSDRAWIGES.hxx>
 #include <XSDRAWSTEP.hxx>
@@ -205,6 +208,117 @@ static Standard_Integer ReadGltf (Draw_Interpretor& theDI,
   return 0;
 }
 
+//=============================================================================
+//function : WriteGltf
+//purpose  : Writes glTF file
+//=============================================================================
+static Standard_Integer WriteGltf (Draw_Interpretor& theDI,
+                                   Standard_Integer theNbArgs,
+                                   const char** theArgVec)
+{
+  TCollection_AsciiString aGltfFilePath;
+  Handle(TDocStd_Document) aDoc;
+  Handle(TDocStd_Application) anApp = DDocStd::GetApplication();
+  TColStd_IndexedDataMapOfStringString aFileInfo;
+  RWGltf_WriterTrsfFormat aTrsfFormat = RWGltf_WriterTrsfFormat_Compact;
+  bool toForceUVExport = false;
+  for (Standard_Integer anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
+  {
+    TCollection_AsciiString anArgCase (theArgVec[anArgIter]);
+    anArgCase.LowerCase();
+    if (anArgCase == "-comments"
+     && anArgIter + 1 < theNbArgs)
+    {
+      aFileInfo.Add ("Comments", theArgVec[++anArgIter]);
+    }
+    else if (anArgCase == "-author"
+          && anArgIter + 1 < theNbArgs)
+    {
+      aFileInfo.Add ("Author", theArgVec[++anArgIter]);
+    }
+    else if (anArgCase == "-forceuvexport"
+          || anArgCase == "-forceuv")
+    {
+      toForceUVExport = true;
+      if (anArgIter + 1 < theNbArgs
+       && ViewerTest::ParseOnOff (theArgVec[anArgIter + 1], toForceUVExport))
+      {
+        ++anArgIter;
+      }
+    }
+    else if (anArgCase == "-trsfformat"
+          && anArgIter + 1 < theNbArgs)
+    {
+      TCollection_AsciiString aTrsfStr (theArgVec[++anArgIter]);
+      aTrsfStr.LowerCase();
+      if (aTrsfStr == "compact")
+      {
+        aTrsfFormat = RWGltf_WriterTrsfFormat_Compact;
+      }
+      else if (aTrsfStr == "mat4")
+      {
+        aTrsfFormat = RWGltf_WriterTrsfFormat_Mat4;
+      }
+      else if (aTrsfStr == "trs")
+      {
+        aTrsfFormat = RWGltf_WriterTrsfFormat_TRS;
+      }
+      else
+      {
+        std::cout << "Syntax error at '" << anArgCase << "'\n";
+        return 1;
+      }
+    }
+    else if (aDoc.IsNull())
+    {
+      Standard_CString aNameVar = theArgVec[anArgIter];
+      DDocStd::GetDocument (aNameVar, aDoc, false);
+      if (aDoc.IsNull())
+      {
+        TopoDS_Shape aShape = DBRep::Get (aNameVar);
+        if (aShape.IsNull())
+        {
+          std::cout << "Syntax error: '" << aNameVar << "' is not a shape nor document\n";
+          return 1;
+        }
+
+        anApp->NewDocument (TCollection_ExtendedString ("BinXCAF"), aDoc);
+        Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool (aDoc->Main());
+        aShapeTool->AddShape (aShape);
+      }
+    }
+    else if (aGltfFilePath.IsEmpty())
+    {
+      aGltfFilePath = theArgVec[anArgIter];
+    }
+    else
+    {
+      std::cout << "Syntax error at '" << theArgVec[anArgIter] << "'\n";
+      return 1;
+    }
+  }
+  if (aGltfFilePath.IsEmpty())
+  {
+    std::cout << "Syntax error: wrong number of arguments\n";
+    return 1;
+  }
+
+  Handle(Draw_ProgressIndicator) aProgress = new Draw_ProgressIndicator (theDI, 1);
+
+  TCollection_AsciiString anExt = aGltfFilePath;
+  anExt.LowerCase();
+
+  const Standard_Real aSystemUnitFactor = UnitsMethods::GetCasCadeLengthUnit() * 0.001;
+
+  RWGltf_CafWriter aWriter (aGltfFilePath, anExt.EndsWith (".glb"));
+  aWriter.SetTransformationFormat (aTrsfFormat);
+  aWriter.SetForcedUVExport (toForceUVExport);
+  aWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit (aSystemUnitFactor);
+  aWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem (RWMesh_CoordinateSystem_Zup);
+  aWriter.Perform (aDoc, aFileInfo, aProgress);
+  return 0;
+}
+
 static Standard_Integer writestl
 (Draw_Interpretor& di, Standard_Integer argc, const char** argv)
 {
@@ -1604,6 +1718,15 @@ void  XSDRAWSTLVRML::InitCommands (Draw_Interpretor& theCommands)
                    "readgltf shape file"
                    "\n\t\t: Same as ReadGltf but reads glTF file into a shape instead of a document.",
                    __FILE__, ReadGltf, g);
+  theCommands.Add ("WriteGltf",
+                   "WriteGltf Doc file [-trsfFormat {compact|TRS|mat4}=compact] [-comments Text] [-author Name] [-forceUVExport]"
+                   "\n\t\t: Write XDE document into glTF file."
+                   "\n\t\t:   -trsfFormat preferred transformation format"
+                   "\n\t\t:   -forceUVExport always export UV coordinates",
+                   __FILE__, WriteGltf, g);
+  theCommands.Add ("writegltf",
+                   "writegltf shape file",
+                   __FILE__, WriteGltf, g);
   theCommands.Add ("writevrml", "shape file [version VRML#1.0/VRML#2.0 (1/2): 2 by default] [representation shaded/wireframe/both (0/1/2): 1 by default]",__FILE__,writevrml,g);
   theCommands.Add ("writestl",  "shape file [ascii/binary (0/1) : 1 by default] [InParallel (0/1) : 0 by default]",__FILE__,writestl,g);
   theCommands.Add ("readstl",
index 8525cdc..5985c3d 100644 (file)
@@ -1,3 +1,13 @@
+if { [info exists occ_tmp_files] } {
+  foreach aTmpFileIter $occ_tmp_files {
+    if {[file exists "$aTmpFileIter"] == 1} {
+      puts "Deleting temporary file $aTmpFileIter"
+      file delete -force "$aTmpFileIter"
+    }
+  }
+  set occ_tmp_files ""
+}
+
 puts ""
 puts "TEST COMPLETED" 
 puts ""
diff --git a/tests/de_mesh/gltf_write/ball b/tests/de_mesh/gltf_write/ball
new file mode 100644 (file)
index 0000000..9439bc6
--- /dev/null
@@ -0,0 +1,13 @@
+puts "========"
+puts "0030953: Data Exchange - implement export of mesh data into glTF 2.0 format"
+puts "Test case exporting BRep model into glb (binary glTF) file."
+puts "========"
+
+restore [locate_data_file Ball.brep] b
+incmesh b 0.1
+set aTmpGltf "${imagedir}/${casename}_tmp.glb"
+writegltf b "$aTmpGltf"
+
+ReadGltf D "$aTmpGltf"
+XGetOneShape s D
+checknbshapes s -face 17 -compound 3
diff --git a/tests/de_mesh/gltf_write/begin b/tests/de_mesh/gltf_write/begin
new file mode 100644 (file)
index 0000000..a6de429
--- /dev/null
@@ -0,0 +1,2 @@
+pload XDE OCAF MODELING VISUALIZATION
+catch { Close D }
diff --git a/tests/de_mesh/gltf_write/end b/tests/de_mesh/gltf_write/end
new file mode 100644 (file)
index 0000000..71be807
--- /dev/null
@@ -0,0 +1,20 @@
+vclear
+vinit View1
+XDisplay -dispMode 1 D
+vaxo
+vfit
+
+vrenderparams -shadingModel PHONG
+vlight -change 0 -intensity 2.5
+vlight -change 1 -intensity 0.3
+vcamera -orthographic
+vdump ${imagedir}/${casename}.png
+vdump ${imagedir}/${casename}_ortho_phong.png
+vcamera -persp
+vdump ${imagedir}/${casename}_persp_phong.png
+
+vrenderparams -shadingModel PBR
+vcamera -orthographic
+vdump ${imagedir}/${casename}_ortho_pbr.png
+vcamera -persp
+vdump ${imagedir}/${casename}_persp_pbr.png
diff --git a/tests/de_mesh/gltf_write/helmet b/tests/de_mesh/gltf_write/helmet
new file mode 100644 (file)
index 0000000..a974f7c
--- /dev/null
@@ -0,0 +1,20 @@
+puts "========"
+puts "0030953: Data Exchange - implement export of mesh data into glTF 2.0 format"
+puts "Test case exporting glTF model into glTF file."
+puts "========"
+
+catch { Close D1 }
+ReadGltf D1 [locate_data_file bug30691_DamagedHelmet.gltf]
+
+set aTmpGltfBase "${imagedir}/${casename}_tmp"
+set aTmpGltf "${aTmpGltfBase}.gltf"
+lappend occ_tmp_files $aTmpGltf
+lappend occ_tmp_files "${aTmpGltfBase}.bin"
+lappend occ_tmp_files "${aTmpGltfBase}_textures"
+
+WriteGltf D1 "$aTmpGltf"
+
+ReadGltf D "$aTmpGltf"
+XGetOneShape s D
+checknbshapes s -face 1 -compound 0
+checktrinfo s -tri 15452 -nod 14556
index 9ed1a89..60189db 100644 (file)
@@ -2,3 +2,4 @@
 002 shape_write_stl
 003 gltf_read
 004 obj_read
+005 gltf_write
index 5d96c80..752c07e 100644 (file)
@@ -18,18 +18,47 @@ foreach i [list 0 3] {
     set aPrefix "g_";
     set aColor "CCB11D"
   }
+  set aColShapes {}
   for { set m 0 } { $m <= $THE_UPPER } { incr m } {
+    set aRowShapes {}
     for { set r 0 } { $r <= $THE_UPPER } { incr r } {
       set aName ${aPrefix}m${m}r${r}
       copy s $aName
+      lappend aRowShapes $aName
       ttranslate $aName ${r} ${i} ${m}
-      set aLab [XAddShape D $aName]
-      SetName D $aLab $aName
+    }
+    set aName ${aPrefix}m${m}
+    compound {*}$aRowShapes $aName
+    lappend aColShapes $aName
+  }
+  set aName ${aPrefix}spheres
+  compound {*}$aColShapes $aName
+  set aLabName "Gray Spheres"
+  if { $i != 0 } { set aLabName "Golden Spheres" }
+  set aLabComp [XAddShape D $aName 0]
+  SetName D $aLabComp $aLabName
+
+  for { set m 0 } { $m <= $THE_UPPER } { incr m } {
+    set aMet [expr 100 * ${m}/$THE_UPPER]
+    set aName ${aPrefix}m${m}
+    XAddComponent D $aLabComp $aName
+    set aLabCompCol [XFindShape D $aName]
+    SetName D $aLabCompCol "${aPrefix}m${aMet}%"
+    SetName D {*}[XFindComponent D $aName] "${aPrefix}m${aMet}%"
+    for { set r 0 } { $r <= $THE_UPPER } { incr r } {
+      set aRoug [expr 100 * ${r}/$THE_UPPER]
+      set aName ${aPrefix}m${m}r${r}
+      XAddComponent D $aLabCompCol $aName
+      set aLab [XFindComponent D $aName]
+      SetName D {*}$aLab "${aPrefix}m${aMet}%_r${aRoug}%"
       XAddVisMaterial D $aName -baseColor $aColor -metallic ${m}/$THE_UPPER -roughness ${r}/$THE_UPPER
-      XSetVisMaterial D $aLab $aName
+      XSetVisMaterial D {*}$aLab $aName
     }
   }
 }
+set aLab [XFindShape D s]
+SetName D {*}$aLab "Sphere"
+
 XGetAllVisMaterials D
 
 # labels
@@ -37,14 +66,21 @@ text2brep tm  "Metal"     -plane 0 -1 0 0 0 -1 -height 0.5 -pos -0.5 0  6.5 -hal
 text2brep tnm "Non-metal" -plane 0 -1 0 0 0 -1 -height 0.5 -pos -0.5 0 -0.5 -halign right -valign top -font monospace
 text2brep ts  "Smooth"    -plane 0 -1 0 1 0  0 -height 0.5 -pos -0.5 0 -0.5 -halign left  -valign top -font monospace
 text2brep tr  "Rough"     -plane 0 -1 0 1 0  0 -height 0.5 -pos  6.5 0 -0.5 -halign right -valign top -font monospace
-set aLab [XAddShape D tm]
-SetName D $aLab "Metal"
-set aLab [XAddShape D tnm]
-SetName D $aLab "Non-metal"
-set aLab [XAddShape D ts]
-SetName D $aLab "Smooth"
-set aLab [XAddShape D tr]
-SetName D $aLab "Rough"
+compound tm tnm ts tr labs
+set aLab [XAddShape D labs 0]
+SetName D $aLab "Labels"
+XAddComponent D $aLab tm
+XAddComponent D $aLab tnm
+XAddComponent D $aLab ts
+XAddComponent D $aLab tr
+SetName D {*}[XFindComponent D tm]  "Metal"
+SetName D    [XFindShape     D tm]  "Metal"
+SetName D {*}[XFindComponent D tnm] "Non-metal"
+SetName D    [XFindShape     D tnm] "Non-metal"
+SetName D {*}[XFindComponent D ts]  "Smooth"
+SetName D    [XFindShape     D ts]  "Smooth"
+SetName D {*}[XFindComponent D tr]  "Rough"
+SetName D    [XFindShape     D tr]  "Rough"
 
 vclear
 vinit View1 -width 768 -height 768