return anImage;
}
+// ================================================================
+// Function : MimeType
+// Purpose :
+// ================================================================
+TCollection_AsciiString Image_Texture::MimeType() const
+{
+ const TCollection_AsciiString aType = ProbeImageFileFormat();
+ if (aType == "jpg")
+ {
+ return "image/jpeg";
+ }
+ else if (aType == "png"
+ || aType == "bmp"
+ || aType == "webp"
+ || aType == "gif"
+ || aType == "tiff")
+ {
+ return TCollection_AsciiString ("image/") + aType;
+ }
+ else if (aType == "dds")
+ {
+ return "image/vnd-ms.dds";
+ }
+ else if (!aType.IsEmpty())
+ {
+ return TCollection_AsciiString ("image/x-") + aType;
+ }
+ return TCollection_AsciiString();
+}
+
// ================================================================
// Function : ProbeImageFileFormat
// Purpose :
// ================================================================
Standard_Boolean Image_Texture::WriteImage (const TCollection_AsciiString& theFile)
{
- Handle(NCollection_Buffer) aBuffer = myBuffer;
- if (myBuffer.IsNull())
+ std::ofstream aFileOut;
+ OSD_OpenStream (aFileOut, theFile.ToCString(), std::ios::out | std::ios::binary | std::ios::trunc);
+ if (!aFileOut)
{
- std::ifstream aFileIn;
- OSD_OpenStream (aFileIn, myImagePath.ToCString(), std::ios::in | std::ios::binary);
- if (!aFileIn)
+ Message::SendFail (TCollection_AsciiString ("Error: Unable to create file '") + theFile + "'");
+ return false;
+ }
+
+ if (!WriteImage (aFileOut, theFile))
+ {
+ return false;
+ }
+
+ aFileOut.close();
+ if (!aFileOut.good())
+ {
+ Message::SendFail (TCollection_AsciiString ("Error: Unable to write file '") + theFile + "'");
+ return false;
+ }
+ return true;
+}
+
+// ================================================================
+// Function : WriteImage
+// Purpose :
+// ================================================================
+Standard_Boolean Image_Texture::WriteImage (std::ostream& theStream,
+ const TCollection_AsciiString& theFile)
+{
+ if (!myBuffer.IsNull())
+ {
+ theStream.write ((const char* )myBuffer->Data(), myBuffer->Size());
+ if (!theStream.good())
{
- Message::SendFail (TCollection_AsciiString ("Error: Unable to open file ") + myImagePath + "!");
- return Standard_False;
+ Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' cannot be written");
+ return false;
}
+ return true;
+ }
- Standard_Size aLen = (Standard_Size )myLength;
- if (myOffset >= 0)
+ std::ifstream aFileIn;
+ OSD_OpenStream (aFileIn, myImagePath.ToCString(), std::ios::in | std::ios::binary);
+ if (!aFileIn)
+ {
+ Message::SendFail (TCollection_AsciiString ("Error: Unable to open file ") + myImagePath + "!");
+ return false;
+ }
+
+ int64_t aLen = myLength;
+ if (myOffset >= 0)
+ {
+ aFileIn.seekg ((std::streamoff )myOffset, std::ios_base::beg);
+ if (!aFileIn.good())
{
- aFileIn.seekg ((std::streamoff )myOffset, std::ios_base::beg);
- if (!aFileIn.good())
- {
- Message::SendFail (TCollection_AsciiString ("Error: Image is defined with invalid file offset '") + myImagePath + "'");
- return Standard_False;
- }
+ Message::SendFail (TCollection_AsciiString ("Error: Image is defined with invalid file offset '") + myImagePath + "'");
+ return false;
}
- else
+ }
+ else
+ {
+ aFileIn.seekg (0, std::ios_base::end);
+ aLen = (int64_t )aFileIn.tellg();
+ aFileIn.seekg (0, std::ios_base::beg);
+ }
+
+ Standard_Integer aChunkSize = 4096;
+ NCollection_Array1<Standard_Byte> aBuffer (0, aChunkSize - 1);
+ for (int64_t aChunkIter = 0; aChunkIter < aLen; aChunkIter += aChunkSize)
+ {
+ if (aChunkIter + aChunkSize >= aLen)
{
- aFileIn.seekg (0, std::ios_base::end);
- aLen = (Standard_Size )aFileIn.tellg();
- aFileIn.seekg (0, std::ios_base::beg);
+ aChunkSize = Standard_Integer(aLen - aChunkIter);
}
-
- aBuffer = new NCollection_Buffer (NCollection_BaseAllocator::CommonBaseAllocator(), aLen);
- if (!aFileIn.read ((char* )aBuffer->ChangeData(), aBuffer->Size()))
+ if (!aFileIn.read ((char* )&aBuffer.ChangeFirst(), aChunkSize))
{
Message::SendFail (TCollection_AsciiString ("Error: unable to read image file '") + myImagePath + "'");
- return Standard_False;
+ return false;
}
+ theStream.write ((const char* )&aBuffer.First(), aChunkSize);
}
-
- std::ofstream aFileOut;
- OSD_OpenStream (aFileOut, theFile.ToCString(), std::ios::out | std::ios::binary | std::ios::trunc);
- if (!aFileOut)
- {
- Message::SendFail (TCollection_AsciiString ("Error: Unable to create file '") + theFile + "'");
- return Standard_False;
- }
-
- aFileOut.write ((const char* )aBuffer->Data(), aBuffer->Size());
- aFileOut.close();
- if (!aFileOut.good())
+ if (!theStream.good())
{
- Message::SendFail (TCollection_AsciiString ("Error: Unable to write file '") + theFile + "'");
- return Standard_False;
+ Message::SendFail (TCollection_AsciiString ("File '") + theFile + "' can not be written");
+ return false;
}
- return Standard_True;
+ return true;
}
//=======================================================================
//! Return buffer holding encoded image content.
const Handle(NCollection_Buffer)& DataBuffer() const { return myBuffer; }
+ //! Return mime-type of image file based on ProbeImageFileFormat().
+ Standard_EXPORT TCollection_AsciiString MimeType() const;
+
//! Return image file format.
Standard_EXPORT TCollection_AsciiString ProbeImageFileFormat() const;
//! Write image to specified file without decoding data.
Standard_EXPORT virtual Standard_Boolean WriteImage (const TCollection_AsciiString& theFile);
+ //! Write image to specified stream without decoding data.
+ Standard_EXPORT virtual Standard_Boolean WriteImage (std::ostream& theStream,
+ const TCollection_AsciiString& theFile);
+
public: //! @name hasher interface
//! Hash value, for Map interface.
: myFile (theFile),
myTrsfFormat (RWGltf_WriterTrsfFormat_Compact),
myIsBinary (theIsBinary),
+ myIsForcedUVExport (false),
+ myToEmbedTexturesInGlb (true),
myBinDataLen64 (0)
{
myCSTrsf.SetOutputLengthUnit (1.0); // meters
const TColStd_IndexedDataMapOfStringString& theFileInfo,
const Message_ProgressRange& theProgress)
{
+ const Standard_Integer aDefSamplerId = 0;
+ myMaterialMap = new RWGltf_GltfMaterialMap (myFile, aDefSamplerId);
+ myMaterialMap->SetDefaultStyle (myDefaultStyle);
+
Message_ProgressScope aPSentry (theProgress, "Writing glTF file", 2);
if (!writeBinData (theDocument, theRootLabels, theLabelFilter, aPSentry.Next()))
{
const TColStd_MapOfAsciiString* theLabelFilter,
const Message_ProgressRange& theProgress)
{
+ myBuffViewPos.Id = RWGltf_GltfAccessor::INVALID_ID;
myBuffViewPos.ByteOffset = 0;
myBuffViewPos.ByteLength = 0;
myBuffViewPos.ByteStride = 12;
myBuffViewPos.Target = RWGltf_GltfBufferViewTarget_ARRAY_BUFFER;
+
+ myBuffViewNorm.Id = RWGltf_GltfAccessor::INVALID_ID;
myBuffViewNorm.ByteOffset = 0;
myBuffViewNorm.ByteLength = 0;
myBuffViewNorm.ByteStride = 12;
myBuffViewNorm.Target = RWGltf_GltfBufferViewTarget_ARRAY_BUFFER;
+
+ myBuffViewTextCoord.Id = RWGltf_GltfAccessor::INVALID_ID;
myBuffViewTextCoord.ByteOffset = 0;
myBuffViewTextCoord.ByteLength = 0;
myBuffViewTextCoord.ByteStride = 8;
myBuffViewTextCoord.Target = RWGltf_GltfBufferViewTarget_ARRAY_BUFFER;
+
+ myBuffViewInd.Id = RWGltf_GltfAccessor::INVALID_ID;
myBuffViewInd.ByteOffset = 0;
myBuffViewInd.ByteLength = 0;
myBuffViewInd.Target = RWGltf_GltfBufferViewTarget_ELEMENT_ARRAY_BUFFER;
}
myBuffViewInd.ByteLength = (int64_t )aBinFile.tellp() - myBuffViewInd.ByteOffset;
+ if (myIsBinary
+ && myToEmbedTexturesInGlb)
+ {
+ // save unique image textures
+ 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(); aFaceIter.Next())
+ {
+ if (toSkipFaceMesh (aFaceIter))
+ {
+ continue;
+ }
+
+ myMaterialMap->AddGlbImages (aBinFile, aFaceIter.FaceStyle());
+ }
+ }
+ }
+
int aBuffViewId = 0;
if (myBuffViewPos.ByteLength > 0)
{
{
myBuffViewInd.Id = aBuffViewId++;
}
+ // myMaterialMap->FlushGlbBufferViews() will put image bufferView's IDs at the end of list
myBinDataLen64 = aBinFile.tellp();
aBinFile.close();
Message_ProgressScope aPSentryBin (theProgress, "Header data", 2);
const Standard_Integer aBinDataBufferId = 0;
- const Standard_Integer aDefSamplerId = 0;
const Standard_Integer aDefSceneId = 0;
const TCollection_AsciiString aFileNameGltf = myFile;
writeAsset (theFileInfo);
writeBufferViews (aBinDataBufferId);
writeBuffers();
- writeExtensions ();
+ writeExtensions();
- RWGltf_GltfMaterialMap aMaterialMap (myFile, aDefSamplerId);
- aMaterialMap.SetDefaultStyle (myDefaultStyle);
- writeImages (aSceneNodeMap, aMaterialMap);
- writeMaterials (aSceneNodeMap, aMaterialMap);
- writeMeshes (aSceneNodeMap, aMaterialMap);
+ writeImages (aSceneNodeMap);
+ writeMaterials (aSceneNodeMap);
+ writeMeshes (aSceneNodeMap);
aPSentryBin.Next();
if (!aPSentryBin.More())
// root nodes indices starting from 0
NCollection_Sequence<Standard_Integer> aSceneRootNodeInds;
writeNodes (theDocument, theRootLabels, theLabelFilter, aSceneNodeMap, aSceneRootNodeInds);
- writeSamplers (aMaterialMap);
+ writeSamplers();
writeScene (aDefSceneId);
writeScenes (aSceneRootNodeInds);
writeSkins();
- writeTextures (aSceneNodeMap, aMaterialMap);
+ writeTextures (aSceneNodeMap);
myWriter->EndObject();
#ifdef HAVE_RAPIDJSON
Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeBufferViews()");
+ int aBuffViewId = 0;
myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_BufferViews));
myWriter->StartArray();
if (myBuffViewPos.Id != RWGltf_GltfAccessor::INVALID_ID)
{
+ aBuffViewId++;
myWriter->StartObject();
myWriter->Key ("buffer");
myWriter->Int (theBinDataBufferId);
}
if (myBuffViewNorm.Id != RWGltf_GltfAccessor::INVALID_ID)
{
+ aBuffViewId++;
myWriter->StartObject();
myWriter->Key ("buffer");
myWriter->Int (theBinDataBufferId);
}
if (myBuffViewTextCoord.Id != RWGltf_GltfAccessor::INVALID_ID)
{
+ aBuffViewId++;
myWriter->StartObject();
myWriter->Key ("buffer");
myWriter->Int (theBinDataBufferId);
}
if (myBuffViewInd.Id != RWGltf_GltfAccessor::INVALID_ID)
{
+ aBuffViewId++;
myWriter->StartObject();
myWriter->Key ("buffer");
myWriter->Int (theBinDataBufferId);
myWriter->Int (myBuffViewInd.Target);
myWriter->EndObject();
}
+
+ myMaterialMap->FlushGlbBufferViews (myWriter.get(), theBinDataBufferId, aBuffViewId);
+
myWriter->EndArray();
#else
(void )theBinDataBufferId;
myWriter->StartObject();
{
myWriter->Key ("byteLength");
- myWriter->Int64 (myBuffViewPos.ByteLength + myBuffViewNorm.ByteLength +
- myBuffViewTextCoord.ByteLength + myBuffViewInd.ByteLength);
+ myWriter->Int64 (myBinDataLen64);
if (!myIsBinary)
{
myWriter->Key ("uri");
// function : writeImages
// purpose :
// =======================================================================
-void RWGltf_CafWriter::writeImages (const RWGltf_GltfSceneNodeMap& theSceneNodeMap,
- RWGltf_GltfMaterialMap& theMaterialMap)
+void RWGltf_CafWriter::writeImages (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
{
#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())
+ if (myIsBinary
+ && myToEmbedTexturesInGlb)
{
- 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);
- }
+ myMaterialMap->FlushGlbImages (myWriter.get());
}
- if (anIsStarted)
+ else
{
- myWriter->EndArray();
+ 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())
+ {
+ myMaterialMap->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)
+void RWGltf_CafWriter::writeMaterials (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
{
#ifdef HAVE_RAPIDJSON
Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeMaterials()");
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);
+ myMaterialMap->AddMaterial (myWriter.get(), aFaceIter.FaceStyle(), anIsStarted);
}
}
if (anIsStarted)
}
#else
(void )theSceneNodeMap;
- (void )theMaterialMap;
#endif
}
// function : writeMeshes
// purpose :
// =======================================================================
-void RWGltf_CafWriter::writeMeshes (const RWGltf_GltfSceneNodeMap& theSceneNodeMap,
- const RWGltf_GltfMaterialMap& theMaterialMap)
+void RWGltf_CafWriter::writeMeshes (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
{
#ifdef HAVE_RAPIDJSON
Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeMeshes()");
}
const RWGltf_GltfFace& aGltfFace = myBinDataMap.Find (aFaceIter.Face());
- const TCollection_AsciiString aMatId = theMaterialMap.FindMaterial (aFaceIter.FaceStyle());
+ const TCollection_AsciiString aMatId = myMaterialMap->FindMaterial (aFaceIter.FaceStyle());
myWriter->StartObject();
{
myWriter->Key ("attributes");
myWriter->EndArray();
#else
(void )theSceneNodeMap;
- (void )theMaterialMap;
#endif
}
// function : writeSamplers
// purpose :
// =======================================================================
-void RWGltf_CafWriter::writeSamplers (const RWGltf_GltfMaterialMap& theMaterialMap)
+void RWGltf_CafWriter::writeSamplers()
{
#ifdef HAVE_RAPIDJSON
Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeSamplers()");
- if (theMaterialMap.NbImages() == 0)
+ if (myMaterialMap->NbImages() == 0)
{
return;
}
myWriter->EndObject();
}
myWriter->EndArray();
-#else
- (void )theMaterialMap;
#endif
}
// function : writeTextures
// purpose :
// =======================================================================
-void RWGltf_CafWriter::writeTextures (const RWGltf_GltfSceneNodeMap& theSceneNodeMap,
- RWGltf_GltfMaterialMap& theMaterialMap)
+void RWGltf_CafWriter::writeTextures (const RWGltf_GltfSceneNodeMap& theSceneNodeMap)
{
#ifdef HAVE_RAPIDJSON
Standard_ProgramError_Raise_if (myWriter.get() == NULL, "Internal error: RWGltf_CafWriter::writeTextures()");
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);
+ myMaterialMap->AddTextures (myWriter.get(), aFaceIter.FaceStyle(), anIsStarted);
}
}
if (anIsStarted)
}
#else
(void )theSceneNodeMap;
- (void )theMaterialMap;
#endif
}
//! Set default material definition to be used for nodes with only color defined.
void SetDefaultStyle (const XCAFPrs_Style& theStyle) { myDefaultStyle = theStyle; }
+ //! Return flag to write image textures into GLB file (binary gltf export); TRUE by default.
+ //! When set to FALSE, texture images will be written as separate files.
+ //! Has no effect on writing into non-binary format.
+ Standard_Boolean ToEmbedTexturesInGlb() { return myToEmbedTexturesInGlb; }
+
+ //! Set flag to write image textures into GLB file (binary gltf export).
+ void SetToEmbedTexturesInGlb (Standard_Boolean theToEmbedTexturesInGlb) { myToEmbedTexturesInGlb = theToEmbedTexturesInGlb; }
+
//! Write glTF file and associated binary file.
//! Triangulation data should be precomputed within shapes!
//! @param theDocument [in] input document
//! 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);
+ Standard_EXPORT virtual void writeImages (const RWGltf_GltfSceneNodeMap& theSceneNodeMap);
//! 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);
+ Standard_EXPORT virtual void writeMaterials (const RWGltf_GltfSceneNodeMap& theSceneNodeMap);
//! 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);
+ Standard_EXPORT virtual void writeMeshes (const RWGltf_GltfSceneNodeMap& theSceneNodeMap);
//! Write RWGltf_GltfRootElement_Nodes section.
//! @param theDocument [in] input document
NCollection_Sequence<Standard_Integer>& theSceneRootNodeInds);
//! Write RWGltf_GltfRootElement_Samplers section.
- Standard_EXPORT virtual void writeSamplers (const RWGltf_GltfMaterialMap& theMaterialMap);
+ Standard_EXPORT virtual void writeSamplers();
//! Write RWGltf_GltfRootElement_Scene section.
//! @param theDefSceneId [in] index of default scene (0)
//! 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);
+ Standard_EXPORT virtual void writeTextures (const RWGltf_GltfSceneNodeMap& theSceneNodeMap);
protected:
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
+ Standard_Boolean myToEmbedTexturesInGlb; //!< flag to write image textures into GLB file
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
+ Handle(RWGltf_GltfMaterialMap) myMaterialMap; //!< map of defined materials
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
static const int INVALID_ID = -1;
public:
- int Id;
- int64_t ByteOffset;
- int64_t ByteLength;
+ int Id; //!< index of bufferView in the array of bufferViews
+ int64_t ByteOffset; //!< offset to the beginning of the data in buffer
+ int64_t ByteLength; //!< length of the data
int32_t ByteStride; //!< [0, 255]
RWGltf_GltfBufferViewTarget Target;
#include <RWGltf_GltfMaterialMap.hxx>
+#include <Message.hxx>
+#include <NCollection_Array1.hxx>
+#include <OSD_OpenFile.hxx>
#include <RWGltf_GltfRootElement.hxx>
#ifdef HAVE_RAPIDJSON
#include <RWGltf_GltfOStreamWriter.hxx>
#endif
+IMPLEMENT_STANDARD_RTTIEXT(RWGltf_GltfMaterialMap, RWMesh_MaterialMap)
+
// =======================================================================
// function : baseColorTexture
// purpose :
const Standard_Integer theDefSamplerId)
: RWMesh_MaterialMap (theFile),
myWriter (NULL),
- myDefSamplerId (theDefSamplerId),
- myNbImages (0)
+ myDefSamplerId (theDefSamplerId)
{
myMatNameAsKey = false;
}
addImage (theWriter, theStyle.Material()->PbrMaterial().OcclusionTexture, theIsStarted);
}
+// =======================================================================
+// function : AddGlbImages
+// purpose :
+// =======================================================================
+void RWGltf_GltfMaterialMap::AddGlbImages (std::ostream& theBinFile,
+ const XCAFPrs_Style& theStyle)
+{
+ if (theStyle.Material().IsNull()
+ || theStyle.Material()->IsEmpty())
+ {
+ return;
+ }
+
+ addGlbImage (theBinFile, baseColorTexture (theStyle.Material()));
+ addGlbImage (theBinFile, theStyle.Material()->PbrMaterial().MetallicRoughnessTexture);
+ addGlbImage (theBinFile, theStyle.Material()->PbrMaterial().NormalTexture);
+ addGlbImage (theBinFile, theStyle.Material()->PbrMaterial().EmissiveTexture);
+ addGlbImage (theBinFile, theStyle.Material()->PbrMaterial().OcclusionTexture);
+}
+
// =======================================================================
// function : addImage
// purpose :
{
#ifdef HAVE_RAPIDJSON
if (theTexture.IsNull()
- || myImageMap.IsBound1 (theTexture)
+ || myImageMap.Contains (theTexture)
|| myImageFailMap.Contains (theTexture))
{
return;
}
- TCollection_AsciiString aGltfImgKey = myNbImages;
- ++myNbImages;
- for (; myImageMap.IsBound2 (aGltfImgKey); ++myNbImages)
- {
- aGltfImgKey = myNbImages;
- }
-
+ const TCollection_AsciiString aGltfImgKey = myImageMap.Extent();
TCollection_AsciiString aTextureUri;
if (!CopyTexture (aTextureUri, theTexture, aGltfImgKey))
{
myImageFailMap.Add (theTexture);
return;
}
-
- myImageMap.Bind (theTexture, aGltfImgKey);
+ myImageMap.Add (theTexture, RWGltf_GltfBufferView());
if (!theIsStarted)
{
#endif
}
+// =======================================================================
+// function : addGlbImage
+// purpose :
+// =======================================================================
+void RWGltf_GltfMaterialMap::addGlbImage (std::ostream& theBinFile,
+ const Handle(Image_Texture)& theTexture)
+{
+ if (theTexture.IsNull()
+ || myImageMap.Contains (theTexture)
+ || myImageFailMap.Contains (theTexture))
+ {
+ return;
+ }
+
+ RWGltf_GltfBufferView aBuffImage;
+ aBuffImage.ByteOffset = theBinFile.tellp();
+ if (!theTexture->WriteImage (theBinFile, myFileName))
+ {
+ myImageFailMap.Add (theTexture);
+ return;
+ }
+
+ // alignment by 4 bytes
+ int64_t aContentLen64 = (int64_t)theBinFile.tellp();
+ while (aContentLen64 % 4 != 0)
+ {
+ theBinFile.write (" ", 1);
+ ++aContentLen64;
+ }
+
+ //aBuffImage.Id = myBuffViewImages.Size(); // id will be corrected later
+ aBuffImage.ByteLength = (int64_t)theBinFile.tellp() - aBuffImage.ByteOffset;
+ if (aBuffImage.ByteLength <= 0)
+ {
+ myImageFailMap.Add (theTexture);
+ return;
+ }
+
+ myImageMap.Add (theTexture, aBuffImage);
+}
+
+// =======================================================================
+// function : FlushBufferViews
+// purpose :
+// =======================================================================
+void RWGltf_GltfMaterialMap::FlushGlbBufferViews (RWGltf_GltfOStreamWriter* theWriter,
+ const Standard_Integer theBinDataBufferId,
+ Standard_Integer& theBuffViewId)
+{
+#ifdef HAVE_RAPIDJSON
+ for (NCollection_IndexedDataMap<Handle(Image_Texture), RWGltf_GltfBufferView, Image_Texture>::Iterator aBufViewIter (myImageMap);
+ aBufViewIter.More(); aBufViewIter.Next())
+ {
+ RWGltf_GltfBufferView& aBuffView = aBufViewIter.ChangeValue();
+ if (aBuffView.ByteLength <= 0)
+ {
+ continue;
+ }
+
+ aBuffView.Id = theBuffViewId++;
+ theWriter->StartObject();
+ theWriter->Key ("buffer");
+ theWriter->Int (theBinDataBufferId);
+ theWriter->Key ("byteLength");
+ theWriter->Int64 (aBuffView.ByteLength);
+ theWriter->Key ("byteOffset");
+ theWriter->Int64 (aBuffView.ByteOffset);
+ theWriter->EndObject();
+ }
+#else
+ (void )theWriter;
+ (void )theBinDataBufferId;
+ (void )theBuffViewId;
+#endif
+}
+
+// =======================================================================
+// function : FlushGlbImages
+// purpose :
+// =======================================================================
+void RWGltf_GltfMaterialMap::FlushGlbImages (RWGltf_GltfOStreamWriter* theWriter)
+{
+#ifdef HAVE_RAPIDJSON
+ bool isStarted = false;
+ for (NCollection_IndexedDataMap<Handle(Image_Texture), RWGltf_GltfBufferView, Image_Texture>::Iterator aBufViewIter (myImageMap);
+ aBufViewIter.More(); aBufViewIter.Next())
+ {
+ const Handle(Image_Texture)& aTexture = aBufViewIter.Key();
+ const RWGltf_GltfBufferView& aBuffView = aBufViewIter.Value();
+ if (aBuffView.ByteLength <= 0)
+ {
+ continue;
+ }
+
+ if (!isStarted)
+ {
+ theWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Images));
+ theWriter->StartArray();
+ isStarted = true;
+ }
+
+ theWriter->StartObject();
+ {
+ const TCollection_AsciiString anImageFormat = aTexture->MimeType();
+ if (anImageFormat != "image/png"
+ && anImageFormat != "image/jpeg")
+ {
+ Message::SendWarning (TCollection_AsciiString ("Warning! Non-standard mime-type ")
+ + anImageFormat + " (texture " + aTexture->TextureId()
+ + ") within glTF file");
+ }
+ theWriter->Key ("mimeType");
+ theWriter->String (anImageFormat.ToCString());
+ theWriter->Key ("bufferView");
+ theWriter->Int (aBuffView.Id);
+ }
+ theWriter->EndObject();
+ }
+ if (isStarted)
+ {
+ theWriter->EndArray();
+ }
+#else
+ (void )theWriter;
+#endif
+}
+
// =======================================================================
// function : AddMaterial
// purpose :
#ifdef HAVE_RAPIDJSON
if (theTexture.IsNull()
|| myTextureMap.Contains (theTexture)
- || !myImageMap .IsBound1 (theTexture))
+ || !myImageMap .Contains (theTexture))
{
return;
}
- const TCollection_AsciiString anImgKey = myImageMap.Find1 (theTexture);
+ const Standard_Integer anImgKey = myImageMap.FindIndex (theTexture) - 1; // glTF indexation starts from 0
myTextureMap.Add (theTexture);
- if (anImgKey.IsEmpty())
- {
- return;
- }
if (!theIsStarted)
{
theWriter->Key ("sampler");
theWriter->Int (myDefSamplerId); // mandatory field by specs
theWriter->Key ("source");
- theWriter->Int (anImgKey.IntegerValue());
+ theWriter->Int (anImgKey);
}
theWriter->EndObject();
#else
if (const Handle(Image_Texture)& aBaseTexture = baseColorTexture (theStyle.Material()))
{
- if (myImageMap.IsBound1 (aBaseTexture))
+ const Standard_Integer aBaseImageIdx = myImageMap.FindIndex (aBaseTexture) - 1;
+ if (aBaseImageIdx != -1)
{
myWriter->Key ("baseColorTexture");
myWriter->StartObject();
{
myWriter->Key ("index");
- const TCollection_AsciiString& anImageIdx = myImageMap.Find1 (aBaseTexture);
- if (!anImageIdx.IsEmpty())
- {
- myWriter->Int (anImageIdx.IntegerValue());
- }
+ myWriter->Int (aBaseImageIdx);
}
myWriter->EndObject();
}
myWriter->Double (aPbrMat.Metallic);
}
- if (!aPbrMat.MetallicRoughnessTexture.IsNull()
- && myImageMap.IsBound1 (aPbrMat.MetallicRoughnessTexture))
+ const Standard_Integer aMetRoughImageIdx = !aPbrMat.MetallicRoughnessTexture.IsNull()
+ ? myImageMap.FindIndex (aPbrMat.MetallicRoughnessTexture) - 1
+ : -1;
+ if (aMetRoughImageIdx != -1)
{
myWriter->Key ("metallicRoughnessTexture");
myWriter->StartObject();
{
myWriter->Key ("index");
- const TCollection_AsciiString& anImageIdx = myImageMap.Find1 (aPbrMat.MetallicRoughnessTexture);
- if (!anImageIdx.IsEmpty())
- {
- myWriter->Int (anImageIdx.IntegerValue());
- }
+ myWriter->Int (aMetRoughImageIdx);
}
myWriter->EndObject();
}
}
myWriter->EndArray();
}
- if (!aPbrMat.EmissiveTexture.IsNull()
- && myImageMap.IsBound1 (aPbrMat.EmissiveTexture))
+
+ const Standard_Integer anEmissImageIdx = !aPbrMat.EmissiveTexture.IsNull()
+ ? myImageMap.FindIndex (aPbrMat.EmissiveTexture) - 1
+ : -1;
+ if (anEmissImageIdx != -1)
{
myWriter->Key ("emissiveTexture");
myWriter->StartObject();
{
myWriter->Key ("index");
- const TCollection_AsciiString& anImageIdx = myImageMap.Find1 (aPbrMat.EmissiveTexture);
- if (!anImageIdx.IsEmpty())
- {
- myWriter->Int (anImageIdx.IntegerValue());
- }
+ myWriter->Int (anEmissImageIdx);
}
myWriter->EndObject();
}
- if (!aPbrMat.NormalTexture.IsNull()
- && myImageMap.IsBound1 (aPbrMat.NormalTexture))
+ const Standard_Integer aNormImageIdx = !aPbrMat.NormalTexture.IsNull()
+ ? myImageMap.FindIndex (aPbrMat.NormalTexture) - 1
+ : -1;
+ if (aNormImageIdx != -1)
{
myWriter->Key ("normalTexture");
myWriter->StartObject();
{
myWriter->Key ("index");
- const TCollection_AsciiString& anImageIdx = myImageMap.Find1 (aPbrMat.NormalTexture);
- if (!anImageIdx.IsEmpty())
- {
- myWriter->Int (anImageIdx.IntegerValue());
- }
+ myWriter->Int (aNormImageIdx);
}
myWriter->EndObject();
}
- if (!aPbrMat.OcclusionTexture.IsNull()
- && myImageMap.IsBound1 (aPbrMat.OcclusionTexture))
+ const Standard_Integer anOcclusImageIdx = !aPbrMat.OcclusionTexture.IsNull()
+ ? myImageMap.FindIndex (aPbrMat.OcclusionTexture) - 1
+ : -1;
+ if (anOcclusImageIdx != -1)
{
myWriter->Key ("occlusionTexture");
myWriter->StartObject();
{
myWriter->Key ("index");
- const TCollection_AsciiString& anImageIdx = myImageMap.Find1 (aPbrMat.OcclusionTexture);
- if (!anImageIdx.IsEmpty())
- {
- myWriter->Int (anImageIdx.IntegerValue());
- }
+ myWriter->Int (anOcclusImageIdx);
}
myWriter->EndObject();
}
#define _RWGltf_GltfMaterialMap_HeaderFile
#include <RWMesh_MaterialMap.hxx>
+#include <RWGltf_GltfBufferView.hxx>
class RWGltf_GltfOStreamWriter;
//! Material manager for exporting into glTF format.
class RWGltf_GltfMaterialMap : public RWMesh_MaterialMap
{
+ DEFINE_STANDARD_RTTIEXT(RWGltf_GltfMaterialMap, RWMesh_MaterialMap)
public:
//! Main constructor.
//! Destructor.
Standard_EXPORT virtual ~RWGltf_GltfMaterialMap();
- //! Add material images.
+public:
+
+ //! Add material images into GLB stream.
+ //! @param theBinFile [in] [out] output file stream
+ //! @param theStyle [in] material images to add
+ Standard_EXPORT void AddGlbImages (std::ostream& theBinFile,
+ const XCAFPrs_Style& theStyle);
+
+ //! Add bufferView's into RWGltf_GltfRootElement_BufferViews section with images collected by AddImagesToGlb().
+ Standard_EXPORT void FlushGlbBufferViews (RWGltf_GltfOStreamWriter* theWriter,
+ const Standard_Integer theBinDataBufferId,
+ Standard_Integer& theBuffViewId);
+
+ //! Write RWGltf_GltfRootElement_Images section with images collected by AddImagesToGlb().
+ Standard_EXPORT void FlushGlbImages (RWGltf_GltfOStreamWriter* theWriter);
+
+public:
+
+ //! Add material images in case of non-GLB file
+ //! (an alternative to AddImagesToGlb() + FlushBufferViews() + FlushImagesGlb()).
Standard_EXPORT void AddImages (RWGltf_GltfOStreamWriter* theWriter,
const XCAFPrs_Style& theStyle,
Standard_Boolean& theIsStarted);
const Handle(Image_Texture)& theTexture,
Standard_Boolean& theIsStarted);
+ //! Add texture image into GLB stream.
+ //! @param theBinFile [in] [out] output file stream
+ //! @param theTexture [in] texture image to add
+ Standard_EXPORT void addGlbImage (std::ostream& theBinFile,
+ const Handle(Image_Texture)& theTexture);
+
//! Add texture.
Standard_EXPORT void addTexture (RWGltf_GltfOStreamWriter* theWriter,
const Handle(Image_Texture)& theTexture,
protected:
RWGltf_GltfOStreamWriter* myWriter;
- NCollection_DoubleMap<Handle(Image_Texture), TCollection_AsciiString, Image_Texture, TCollection_AsciiString> myImageMap;
+ NCollection_IndexedDataMap<Handle(Image_Texture), RWGltf_GltfBufferView, Image_Texture> myImageMap;
NCollection_Map<Handle(Image_Texture), Image_Texture> myTextureMap;
+
Standard_Integer myDefSamplerId;
- Standard_Integer myNbImages;
};
#include <TDataStd_Name.hxx>
#include <TDF_Label.hxx>
+IMPLEMENT_STANDARD_RTTIEXT(RWMesh_MaterialMap, Standard_Transient)
+
// =======================================================================
// function : RWMesh_MaterialMap
// purpose :
//! 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
+class RWMesh_MaterialMap : public Standard_Transient
{
+ DEFINE_STANDARD_RTTIEXT(RWMesh_MaterialMap, Standard_Transient)
public:
//! Main constructor.
Handle(TDocStd_Application) anApp = DDocStd::GetApplication();
TColStd_IndexedDataMapOfStringString aFileInfo;
RWGltf_WriterTrsfFormat aTrsfFormat = RWGltf_WriterTrsfFormat_Compact;
- bool toForceUVExport = false;
+ bool toForceUVExport = false, toEmbedTexturesInGlb = true;
for (Standard_Integer anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
{
TCollection_AsciiString anArgCase (theArgVec[anArgIter]);
{
aGltfFilePath = theArgVec[anArgIter];
}
+ else if (anArgCase == "-texturesSeparate")
+ {
+ toEmbedTexturesInGlb = false;
+ }
else
{
Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
RWGltf_CafWriter aWriter (aGltfFilePath, anExt.EndsWith (".glb"));
aWriter.SetTransformationFormat (aTrsfFormat);
aWriter.SetForcedUVExport (toForceUVExport);
+ aWriter.SetToEmbedTexturesInGlb (toEmbedTexturesInGlb);
aWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit (aSystemUnitFactor);
aWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem (RWMesh_CoordinateSystem_Zup);
aWriter.Perform (aDoc, aFileInfo, aProgress->Start());
"\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]"
+ "WriteGltf Doc file [-trsfFormat {compact|TRS|mat4}=compact] [-comments Text] [-author Name] [-forceUVExport] [-texturesSeparate]"
"\n\t\t: Write XDE document into glTF file."
"\n\t\t: -trsfFormat preferred transformation format"
- "\n\t\t: -forceUVExport always export UV coordinates",
+ "\n\t\t: -forceUVExport always export UV coordinates"
+ "\n\t\t: -texturesSeparate write textures to separate files",
__FILE__, WriteGltf, g);
theCommands.Add ("writegltf",
"writegltf shape file",
--- /dev/null
+puts "========"
+puts "0031703: Data Exchange, RWGltf_CafWriter - add option putting textures inside GLB file as alternative to external references"
+puts "Test case exporting glTF model into GLB file."
+puts "========"
+
+catch { Close D1 }
+ReadGltf D1 [locate_data_file bug30691_DamagedHelmet.gltf]
+
+set aTmpGltfBase "${imagedir}/${casename}_tmp"
+set aTmpGltf "${aTmpGltfBase}.glb"
+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
--- /dev/null
+puts "========"
+puts "0031703: Data Exchange, RWGltf_CafWriter - add option putting textures inside GLB file as alternative to external references"
+puts "Test case exporting glTF model into GLB file."
+puts "========"
+
+catch { Close D1 }
+ReadGltf D1 [locate_data_file bug30691_Lantern.glb]
+
+set aTmpGltfBase "${imagedir}/${casename}_tmp"
+set aTmpGltf "${aTmpGltfBase}.glb"
+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 3 -compound 1
+checktrinfo s -tri 5394 -nod 4145