0032524: Data Exchange, RWGltf_CafWriter - avoid writing translucent metallic materials
[occt.git] / src / RWGltf / RWGltf_GltfMaterialMap.cxx
1 // Copyright (c) 2017-2019 OPEN CASCADE SAS
2 //
3 // This file is part of Open CASCADE Technology software library.
4 //
5 // This library is free software; you can redistribute it and/or modify it under
6 // the terms of the GNU Lesser General Public License version 2.1 as published
7 // by the Free Software Foundation, with special exception defined in the file
8 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
9 // distribution for complete text of the license and disclaimer of any warranty.
10 //
11 // Alternatively, this file may be used under the terms of Open CASCADE
12 // commercial license or contractual agreement.
13
14 #include <RWGltf_GltfMaterialMap.hxx>
15
16 #include <Message.hxx>
17 #include <NCollection_Array1.hxx>
18 #include <OSD_OpenFile.hxx>
19 #include <RWGltf_GltfRootElement.hxx>
20
21 #ifdef HAVE_RAPIDJSON
22   #include <RWGltf_GltfOStreamWriter.hxx>
23 #endif
24
25 IMPLEMENT_STANDARD_RTTIEXT(RWGltf_GltfMaterialMap, RWMesh_MaterialMap)
26
27 // =======================================================================
28 // function : baseColorTexture
29 // purpose  :
30 // =======================================================================
31 const Handle(Image_Texture)& RWGltf_GltfMaterialMap::baseColorTexture (const Handle(XCAFDoc_VisMaterial)& theMat)
32 {
33   static const Handle(Image_Texture) THE_NULL_TEXTURE;
34   if (theMat.IsNull())
35   {
36     return THE_NULL_TEXTURE;
37   }
38   else if (theMat->HasPbrMaterial()
39        && !theMat->PbrMaterial().BaseColorTexture.IsNull())
40   {
41     return theMat->PbrMaterial().BaseColorTexture;
42   }
43   else if (theMat->HasCommonMaterial()
44        && !theMat->CommonMaterial().DiffuseTexture.IsNull())
45   {
46     return theMat->CommonMaterial().DiffuseTexture;
47   }
48   return THE_NULL_TEXTURE;
49 }
50
51 // =======================================================================
52 // function : RWGltf_GltfMaterialMap
53 // purpose  :
54 // =======================================================================
55 RWGltf_GltfMaterialMap::RWGltf_GltfMaterialMap (const TCollection_AsciiString& theFile,
56                                                 const Standard_Integer theDefSamplerId)
57 : RWMesh_MaterialMap (theFile),
58   myWriter (NULL),
59   myDefSamplerId (theDefSamplerId)
60 {
61   myMatNameAsKey = false;
62 }
63
64 // =======================================================================
65 // function : ~RWGltf_GltfMaterialMap
66 // purpose  :
67 // =======================================================================
68 RWGltf_GltfMaterialMap::~RWGltf_GltfMaterialMap()
69 {
70   //
71 }
72
73 // =======================================================================
74 // function : AddImages
75 // purpose  :
76 // =======================================================================
77 void RWGltf_GltfMaterialMap::AddImages (RWGltf_GltfOStreamWriter* theWriter,
78                                         const XCAFPrs_Style& theStyle,
79                                         Standard_Boolean& theIsStarted)
80 {
81   if (theWriter == NULL
82    || theStyle.Material().IsNull()
83    || theStyle.Material()->IsEmpty())
84   {
85     return;
86   }
87
88   addImage (theWriter, baseColorTexture (theStyle.Material()), theIsStarted);
89   addImage (theWriter, theStyle.Material()->PbrMaterial().MetallicRoughnessTexture, theIsStarted);
90   addImage (theWriter, theStyle.Material()->PbrMaterial().NormalTexture,    theIsStarted);
91   addImage (theWriter, theStyle.Material()->PbrMaterial().EmissiveTexture,  theIsStarted);
92   addImage (theWriter, theStyle.Material()->PbrMaterial().OcclusionTexture, theIsStarted);
93 }
94
95 // =======================================================================
96 // function : AddGlbImages
97 // purpose  :
98 // =======================================================================
99 void RWGltf_GltfMaterialMap::AddGlbImages (std::ostream& theBinFile,
100                                            const XCAFPrs_Style& theStyle)
101 {
102   if (theStyle.Material().IsNull()
103    || theStyle.Material()->IsEmpty())
104   {
105     return;
106   }
107
108   addGlbImage (theBinFile, baseColorTexture (theStyle.Material()));
109   addGlbImage (theBinFile, theStyle.Material()->PbrMaterial().MetallicRoughnessTexture);
110   addGlbImage (theBinFile, theStyle.Material()->PbrMaterial().NormalTexture);
111   addGlbImage (theBinFile, theStyle.Material()->PbrMaterial().EmissiveTexture);
112   addGlbImage (theBinFile, theStyle.Material()->PbrMaterial().OcclusionTexture);
113 }
114
115 // =======================================================================
116 // function : addImage
117 // purpose  :
118 // =======================================================================
119 void RWGltf_GltfMaterialMap::addImage (RWGltf_GltfOStreamWriter* theWriter,
120                                        const Handle(Image_Texture)& theTexture,
121                                        Standard_Boolean& theIsStarted)
122 {
123 #ifdef HAVE_RAPIDJSON
124   if (theTexture.IsNull()
125    || myImageMap.Contains (theTexture)
126    || myImageFailMap.Contains (theTexture))
127   {
128     return;
129   }
130
131   const TCollection_AsciiString aGltfImgKey = myImageMap.Extent();
132   TCollection_AsciiString aTextureUri;
133   if (!CopyTexture (aTextureUri, theTexture, aGltfImgKey))
134   {
135     myImageFailMap.Add (theTexture);
136     return;
137   }
138   myImageMap.Add (theTexture, RWGltf_GltfBufferView());
139
140   if (!theIsStarted)
141   {
142     theWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Images));
143     theWriter->StartArray();
144     theIsStarted = true;
145   }
146
147   theWriter->StartObject();
148   {
149     theWriter->Key ("uri");
150     theWriter->String (aTextureUri.ToCString());
151   }
152   theWriter->EndObject();
153 #else
154   (void )theWriter;
155   (void )theTexture;
156   (void )theIsStarted;
157 #endif
158 }
159
160 // =======================================================================
161 // function : addGlbImage
162 // purpose  :
163 // =======================================================================
164 void RWGltf_GltfMaterialMap::addGlbImage (std::ostream& theBinFile,
165                                           const Handle(Image_Texture)& theTexture)
166 {
167   if (theTexture.IsNull()
168    || myImageMap.Contains (theTexture)
169    || myImageFailMap.Contains (theTexture))
170   {
171     return;
172   }
173
174   RWGltf_GltfBufferView aBuffImage;
175   aBuffImage.ByteOffset = theBinFile.tellp();
176   if (!theTexture->WriteImage (theBinFile, myFileName))
177   {
178     myImageFailMap.Add (theTexture);
179     return;
180   }
181
182   // alignment by 4 bytes
183   int64_t aContentLen64 = (int64_t)theBinFile.tellp();
184   while (aContentLen64 % 4 != 0)
185   {
186     theBinFile.write (" ", 1);
187     ++aContentLen64;
188   }
189
190   //aBuffImage.Id = myBuffViewImages.Size(); // id will be corrected later
191   aBuffImage.ByteLength = (int64_t)theBinFile.tellp() - aBuffImage.ByteOffset;
192   if (aBuffImage.ByteLength <= 0)
193   {
194     myImageFailMap.Add (theTexture);
195     return;
196   }
197
198   myImageMap.Add (theTexture, aBuffImage);
199 }
200
201 // =======================================================================
202 // function : FlushBufferViews
203 // purpose  :
204 // =======================================================================
205 void RWGltf_GltfMaterialMap::FlushGlbBufferViews (RWGltf_GltfOStreamWriter* theWriter,
206                                                   const Standard_Integer theBinDataBufferId,
207                                                   Standard_Integer& theBuffViewId)
208 {
209 #ifdef HAVE_RAPIDJSON
210   for (NCollection_IndexedDataMap<Handle(Image_Texture), RWGltf_GltfBufferView, Image_Texture>::Iterator aBufViewIter (myImageMap);
211        aBufViewIter.More(); aBufViewIter.Next())
212   {
213     RWGltf_GltfBufferView& aBuffView = aBufViewIter.ChangeValue();
214     if (aBuffView.ByteLength <= 0)
215     {
216       continue;
217     }
218
219     aBuffView.Id = theBuffViewId++;
220     theWriter->StartObject();
221     theWriter->Key ("buffer");
222     theWriter->Int (theBinDataBufferId);
223     theWriter->Key ("byteLength");
224     theWriter->Int64 (aBuffView.ByteLength);
225     theWriter->Key ("byteOffset");
226     theWriter->Int64 (aBuffView.ByteOffset);
227     theWriter->EndObject();
228   }
229 #else
230   (void )theWriter;
231   (void )theBinDataBufferId;
232   (void )theBuffViewId;
233 #endif
234 }
235
236 // =======================================================================
237 // function : FlushGlbImages
238 // purpose  :
239 // =======================================================================
240 void RWGltf_GltfMaterialMap::FlushGlbImages (RWGltf_GltfOStreamWriter* theWriter)
241 {
242 #ifdef HAVE_RAPIDJSON
243   bool isStarted = false;
244   for (NCollection_IndexedDataMap<Handle(Image_Texture), RWGltf_GltfBufferView, Image_Texture>::Iterator aBufViewIter (myImageMap);
245        aBufViewIter.More(); aBufViewIter.Next())
246   {
247     const Handle(Image_Texture)& aTexture  = aBufViewIter.Key();
248     const RWGltf_GltfBufferView& aBuffView = aBufViewIter.Value();
249     if (aBuffView.ByteLength <= 0)
250     {
251       continue;
252     }
253
254     if (!isStarted)
255     {
256       theWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Images));
257       theWriter->StartArray();
258       isStarted = true;
259     }
260
261     theWriter->StartObject();
262     {
263       const TCollection_AsciiString anImageFormat = aTexture->MimeType();
264       if (anImageFormat != "image/png"
265        && anImageFormat != "image/jpeg")
266       {
267         Message::SendWarning (TCollection_AsciiString ("Warning! Non-standard mime-type ")
268                               + anImageFormat + " (texture " + aTexture->TextureId()
269                               + ") within glTF file");
270       }
271       theWriter->Key ("mimeType");
272       theWriter->String (anImageFormat.ToCString());
273       theWriter->Key ("bufferView");
274       theWriter->Int (aBuffView.Id);
275     }
276     theWriter->EndObject();
277   }
278   if (isStarted)
279   {
280     theWriter->EndArray();
281   }
282 #else
283   (void )theWriter;
284 #endif
285 }
286
287 // =======================================================================
288 // function : AddMaterial
289 // purpose  :
290 // =======================================================================
291 void RWGltf_GltfMaterialMap::AddMaterial (RWGltf_GltfOStreamWriter* theWriter,
292                                           const XCAFPrs_Style& theStyle,
293                                           Standard_Boolean& theIsStarted)
294 {
295 #ifdef HAVE_RAPIDJSON
296   if (theWriter == NULL
297    || ((theStyle.Material().IsNull() || theStyle.Material()->IsEmpty())
298     && !theStyle.IsSetColorSurf()))
299   {
300     return;
301   }
302
303   if (!theIsStarted)
304   {
305     theWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Materials));
306     theWriter->StartArray();
307     theIsStarted = true;
308   }
309   myWriter = theWriter;
310   AddMaterial (theStyle);
311   myWriter = NULL;
312 #else
313   (void )theWriter;
314   (void )theStyle;
315   (void )theIsStarted;
316 #endif
317 }
318
319 // =======================================================================
320 // function : AddTextures
321 // purpose  :
322 // =======================================================================
323 void RWGltf_GltfMaterialMap::AddTextures (RWGltf_GltfOStreamWriter* theWriter,
324                                           const XCAFPrs_Style& theStyle,
325                                           Standard_Boolean& theIsStarted)
326 {
327   if (theWriter == NULL
328    || theStyle.Material().IsNull()
329    || theStyle.Material()->IsEmpty())
330   {
331     return;
332   }
333
334   addTexture (theWriter, baseColorTexture (theStyle.Material()), theIsStarted);
335   addTexture (theWriter, theStyle.Material()->PbrMaterial().MetallicRoughnessTexture, theIsStarted);
336   addTexture (theWriter, theStyle.Material()->PbrMaterial().NormalTexture,    theIsStarted);
337   addTexture (theWriter, theStyle.Material()->PbrMaterial().EmissiveTexture,  theIsStarted);
338   addTexture (theWriter, theStyle.Material()->PbrMaterial().OcclusionTexture, theIsStarted);
339 }
340
341 // =======================================================================
342 // function : addTexture
343 // purpose  :
344 // =======================================================================
345 void RWGltf_GltfMaterialMap::addTexture (RWGltf_GltfOStreamWriter* theWriter,
346                                          const Handle(Image_Texture)& theTexture,
347                                          Standard_Boolean& theIsStarted)
348 {
349 #ifdef HAVE_RAPIDJSON
350   if (theTexture.IsNull()
351   ||  myTextureMap.Contains (theTexture)
352   || !myImageMap  .Contains (theTexture))
353   {
354     return;
355   }
356
357   const Standard_Integer anImgKey = myImageMap.FindIndex (theTexture) - 1; // glTF indexation starts from 0
358   myTextureMap.Add (theTexture);
359
360   if (!theIsStarted)
361   {
362     theWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Textures));
363     theWriter->StartArray();
364     theIsStarted = true;
365   }
366
367   theWriter->StartObject();
368   {
369     theWriter->Key ("sampler");
370     theWriter->Int (myDefSamplerId); // mandatory field by specs
371     theWriter->Key ("source");
372     theWriter->Int (anImgKey);
373   }
374   theWriter->EndObject();
375 #else
376   (void )theWriter;
377   (void )theTexture;
378   (void )theIsStarted;
379 #endif
380 }
381
382 // =======================================================================
383 // function : AddMaterial
384 // purpose  :
385 // =======================================================================
386 TCollection_AsciiString RWGltf_GltfMaterialMap::AddMaterial (const XCAFPrs_Style& theStyle)
387 {
388   return RWMesh_MaterialMap::AddMaterial (theStyle);
389 }
390
391 // =======================================================================
392 // function : DefineMaterial
393 // purpose  :
394 // =======================================================================
395 void RWGltf_GltfMaterialMap::DefineMaterial (const XCAFPrs_Style& theStyle,
396                                              const TCollection_AsciiString& /*theKey*/,
397                                              const TCollection_AsciiString& theName)
398 {
399 #ifdef HAVE_RAPIDJSON
400   if (myWriter == NULL)
401   {
402     Standard_ProgramError::Raise ("RWGltf_GltfMaterialMap::DefineMaterial() should be called with JSON Writer");
403     return;
404   }
405
406   XCAFDoc_VisMaterialPBR aPbrMat;
407   const bool hasMaterial = !theStyle.Material().IsNull()
408                         && !theStyle.Material()->IsEmpty();
409   if (hasMaterial)
410   {
411     aPbrMat = theStyle.Material()->ConvertToPbrMaterial();
412   }
413   else if (!myDefaultStyle.Material().IsNull()
414          && myDefaultStyle.Material()->HasPbrMaterial())
415   {
416     aPbrMat = myDefaultStyle.Material()->PbrMaterial();
417   }
418   if (theStyle.IsSetColorSurf())
419   {
420     aPbrMat.BaseColor.SetRGB (theStyle.GetColorSurf());
421     if (theStyle.GetColorSurfRGBA().Alpha() < 1.0f)
422     {
423       aPbrMat.Metallic = 0.0f;
424       aPbrMat.BaseColor.SetAlpha (theStyle.GetColorSurfRGBA().Alpha());
425     }
426   }
427   myWriter->StartObject();
428   {
429     myWriter->Key ("name");
430     myWriter->String (theName.ToCString());
431
432     myWriter->Key ("pbrMetallicRoughness");
433     myWriter->StartObject();
434     {
435       myWriter->Key ("baseColorFactor");
436       myWriter->StartArray();
437       {
438         myWriter->Double (aPbrMat.BaseColor.GetRGB().Red());
439         myWriter->Double (aPbrMat.BaseColor.GetRGB().Green());
440         myWriter->Double (aPbrMat.BaseColor.GetRGB().Blue());
441         myWriter->Double (aPbrMat.BaseColor.Alpha());
442       }
443       myWriter->EndArray();
444
445       if (const Handle(Image_Texture)& aBaseTexture = baseColorTexture (theStyle.Material()))
446       {
447         const Standard_Integer aBaseImageIdx = myImageMap.FindIndex (aBaseTexture) - 1;
448         if (aBaseImageIdx != -1)
449         {
450           myWriter->Key ("baseColorTexture");
451           myWriter->StartObject();
452           {
453             myWriter->Key ("index");
454             myWriter->Int (aBaseImageIdx);
455           }
456           myWriter->EndObject();
457         }
458       }
459
460       if (hasMaterial
461        || aPbrMat.Metallic != 1.0f)
462       {
463         myWriter->Key ("metallicFactor");
464         myWriter->Double (aPbrMat.Metallic);
465       }
466
467       const Standard_Integer aMetRoughImageIdx = !aPbrMat.MetallicRoughnessTexture.IsNull()
468                                                ? myImageMap.FindIndex (aPbrMat.MetallicRoughnessTexture) - 1
469                                                : -1;
470       if (aMetRoughImageIdx != -1)
471       {
472         myWriter->Key ("metallicRoughnessTexture");
473         myWriter->StartObject();
474         {
475           myWriter->Key ("index");
476           myWriter->Int (aMetRoughImageIdx);
477         }
478         myWriter->EndObject();
479       }
480
481       if (hasMaterial
482        || aPbrMat.Roughness != 1.0f)
483       {
484         myWriter->Key ("roughnessFactor");
485         myWriter->Double (aPbrMat.Roughness);
486       }
487     }
488     myWriter->EndObject();
489
490     // export automatic culling as doubleSided material;
491     // extra preprocessing is necessary to automatically export
492     // Solids with singleSided material and Shells with doubleSided material,
493     // as both may share the same material having "auto" flag
494     if (theStyle.Material().IsNull()
495      || theStyle.Material()->FaceCulling() == Graphic3d_TypeOfBackfacingModel_Auto
496      || theStyle.Material()->FaceCulling() == Graphic3d_TypeOfBackfacingModel_DoubleSided)
497     {
498       myWriter->Key ("doubleSided");
499       myWriter->Bool (true);
500     }
501
502     const Graphic3d_AlphaMode anAlphaMode = !theStyle.Material().IsNull() ? theStyle.Material()->AlphaMode() : Graphic3d_AlphaMode_BlendAuto;
503     switch (anAlphaMode)
504     {
505       case Graphic3d_AlphaMode_BlendAuto:
506       {
507         if (aPbrMat.BaseColor.Alpha() < 1.0f)
508         {
509           myWriter->Key ("alphaMode");
510           myWriter->String ("BLEND");
511         }
512         break;
513       }
514       case Graphic3d_AlphaMode_Opaque:
515       {
516         break;
517       }
518       case Graphic3d_AlphaMode_Mask:
519       {
520         myWriter->Key ("alphaMode");
521         myWriter->String ("MASK");
522         break;
523       }
524       case Graphic3d_AlphaMode_Blend:
525       case Graphic3d_AlphaMode_MaskBlend:
526       {
527         myWriter->Key ("alphaMode");
528         myWriter->String ("BLEND");
529         break;
530       }
531     }
532     if (!theStyle.Material().IsNull()
533       && theStyle.Material()->AlphaCutOff() != 0.5f)
534     {
535       myWriter->Key ("alphaCutoff");
536       myWriter->Double (theStyle.Material()->AlphaCutOff());
537     }
538
539     if (aPbrMat.EmissiveFactor != Graphic3d_Vec3 (0.0f, 0.0f, 0.0f))
540     {
541       myWriter->Key ("emissiveFactor");
542       myWriter->StartArray();
543       {
544         myWriter->Double (aPbrMat.EmissiveFactor.r());
545         myWriter->Double (aPbrMat.EmissiveFactor.g());
546         myWriter->Double (aPbrMat.EmissiveFactor.b());
547       }
548       myWriter->EndArray();
549     }
550
551     const Standard_Integer anEmissImageIdx = !aPbrMat.EmissiveTexture.IsNull()
552                                            ? myImageMap.FindIndex (aPbrMat.EmissiveTexture) - 1
553                                            : -1;
554     if (anEmissImageIdx != -1)
555     {
556       myWriter->Key ("emissiveTexture");
557       myWriter->StartObject();
558       {
559         myWriter->Key ("index");
560         myWriter->Int (anEmissImageIdx);
561       }
562       myWriter->EndObject();
563     }
564
565     const Standard_Integer aNormImageIdx = !aPbrMat.NormalTexture.IsNull()
566                                          ? myImageMap.FindIndex (aPbrMat.NormalTexture) - 1
567                                          : -1;
568     if (aNormImageIdx != -1)
569     {
570       myWriter->Key ("normalTexture");
571       myWriter->StartObject();
572       {
573         myWriter->Key ("index");
574         myWriter->Int (aNormImageIdx);
575       }
576       myWriter->EndObject();
577     }
578
579     const Standard_Integer anOcclusImageIdx = !aPbrMat.OcclusionTexture.IsNull()
580                                             ? myImageMap.FindIndex (aPbrMat.OcclusionTexture) - 1
581                                             : -1;
582     if (anOcclusImageIdx != -1)
583     {
584       myWriter->Key ("occlusionTexture");
585       myWriter->StartObject();
586       {
587         myWriter->Key ("index");
588         myWriter->Int (anOcclusImageIdx);
589       }
590       myWriter->EndObject();
591     }
592   }
593   myWriter->EndObject();
594 #else
595   (void )theStyle;
596   (void )theName;
597 #endif
598 }