0029303: Data Exchange - add RWObj_CafWriter tool for wavefront OBJ file
[occt.git] / src / RWObj / RWObj_ObjWriterContext.cxx
1 // Copyright (c) 2015-2021 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 <RWObj_ObjWriterContext.hxx>
15
16 #include <Message.hxx>
17 #include <NCollection_IndexedMap.hxx>
18 #include <OSD_OpenFile.hxx>
19
20 // =======================================================================
21 // function : splitLines
22 // purpose  :
23 // =======================================================================
24 static void splitLines (const TCollection_AsciiString& theString,
25                         NCollection_IndexedMap<TCollection_AsciiString>& theLines)
26 {
27   if (theString.IsEmpty())
28   {
29     return;
30   }
31
32   Standard_Integer aLineFrom = 1;
33   for (Standard_Integer aCharIter = 1;; ++aCharIter)
34   {
35     const char aChar = theString.Value (aCharIter);
36     if (aChar != '\r'
37         && aChar != '\n'
38         && aCharIter != theString.Length())
39     {
40       continue;
41     }
42
43     if (aLineFrom != aCharIter)
44     {
45       TCollection_AsciiString aLine = theString.SubString (aLineFrom, aCharIter);
46       aLine.RightAdjust();
47       theLines.Add (aLine);
48     }
49
50     if (aCharIter == theString.Length())
51     {
52       break;
53     }
54     else if (aChar == '\r'
55              && theString.Value (aCharIter + 1) == '\n')
56     {
57       // CRLF
58       ++aCharIter;
59     }
60     aLineFrom = aCharIter + 1;
61   }
62 }
63
64 // ================================================================
65 // Function : RWObj_ObjWriterContext
66 // Purpose  :
67 // ================================================================
68 RWObj_ObjWriterContext::RWObj_ObjWriterContext (const TCollection_AsciiString& theName)
69 : NbFaces (0),
70   myFile (OSD_OpenFile (theName.ToCString(), "wb")),
71   myName (theName),
72   myElemPosFirst (1, 1, 1, 1),
73   myElemNormFirst(1, 1, 1, 1),
74   myElemUVFirst  (1, 1, 1, 1),
75   myHasNormals   (false),
76   myHasTexCoords (false)
77 {
78   if (myFile == NULL)
79   {
80     Message::SendFail (TCollection_AsciiString ("File cannot be created\n") + theName);
81     return;
82   }
83 }
84
85 // ================================================================
86 // Function : ~RWObj_ObjWriterContext
87 // Purpose  :
88 // ================================================================
89 RWObj_ObjWriterContext::~RWObj_ObjWriterContext()
90 {
91   if (myFile != NULL)
92   {
93     ::fclose (myFile);
94     Message::SendFail (TCollection_AsciiString ("File cannot be written\n") + myName);
95   }
96 }
97
98 // ================================================================
99 // Function : Close
100 // Purpose  :
101 // ================================================================
102 bool RWObj_ObjWriterContext::Close()
103 {
104   bool isOk = ::fclose (myFile) == 0;
105   myFile = NULL;
106   return isOk;
107 }
108
109 // ================================================================
110 // Function : WriteHeader
111 // Purpose  :
112 // ================================================================
113 bool RWObj_ObjWriterContext::WriteHeader (const Standard_Integer theNbNodes,
114                                           const Standard_Integer theNbElems,
115                                           const TCollection_AsciiString& theMatLib,
116                                           const TColStd_IndexedDataMapOfStringString& theFileInfo)
117 {
118   bool isOk = ::Fprintf (myFile, "# Exported by Open CASCADE Technology [dev.opencascade.org]\n"
119                                  "#  Vertices: %d\n"
120                                  "#     Faces: %d\n", theNbNodes, theNbElems) != 0;
121   for (TColStd_IndexedDataMapOfStringString::Iterator aKeyValueIter (theFileInfo); aKeyValueIter.More(); aKeyValueIter.Next())
122   {
123     NCollection_IndexedMap<TCollection_AsciiString> aKeyLines, aValLines;
124     splitLines (aKeyValueIter.Key(),   aKeyLines);
125     splitLines (aKeyValueIter.Value(), aValLines);
126     for (Standard_Integer aLineIter = 1; aLineIter <= aKeyLines.Extent(); ++aLineIter)
127     {
128       const TCollection_AsciiString& aLine = aKeyLines.FindKey (aLineIter);
129       isOk = isOk
130         && ::Fprintf (myFile,
131                       aLineIter > 1 ? "\n# %s" : "# %s",
132                       aLine.ToCString()) != 0;
133     }
134     isOk = isOk
135       && ::Fprintf (myFile, !aKeyLines.IsEmpty() ? ":" : "# ") != 0;
136     for (Standard_Integer aLineIter = 1; aLineIter <= aValLines.Extent(); ++aLineIter)
137     {
138       const TCollection_AsciiString& aLine = aValLines.FindKey (aLineIter);
139       isOk = isOk
140         && ::Fprintf (myFile,
141                       aLineIter > 1 ? "\n# %s" : " %s",
142                       aLine.ToCString()) != 0;
143     }
144     isOk = isOk
145       && ::Fprintf (myFile, "\n") != 0;
146   }
147
148   if (!theMatLib.IsEmpty())
149   {
150     isOk = isOk
151         && ::Fprintf (myFile, "mtllib %s\n", theMatLib.ToCString()) != 0;
152   }
153   return isOk;
154 }
155
156 // ================================================================
157 // Function : WriteActiveMaterial
158 // Purpose  :
159 // ================================================================
160 bool RWObj_ObjWriterContext::WriteActiveMaterial (const TCollection_AsciiString& theMaterial)
161 {
162   myActiveMaterial = theMaterial;
163   return !theMaterial.IsEmpty()
164         ? Fprintf (myFile, "usemtl %s\n", theMaterial.ToCString()) != 0
165         : Fprintf (myFile, "usemtl\n") != 0;
166 }
167
168 // ================================================================
169 // Function : WriteTriangle
170 // Purpose  :
171 // ================================================================
172 bool RWObj_ObjWriterContext::WriteTriangle (const Graphic3d_Vec3i& theTri)
173 {
174   const Graphic3d_Vec3i aTriPos = theTri + myElemPosFirst.xyz();
175   if (myHasNormals)
176   {
177     const Graphic3d_Vec3i aTriNorm = theTri + myElemNormFirst.xyz();
178     if (myHasTexCoords)
179     {
180       const Graphic3d_Vec3i aTriUv = theTri + myElemUVFirst.xyz();
181       return Fprintf (myFile, "f %d/%d/%d %d/%d/%d %d/%d/%d\n",
182                       aTriPos[0], aTriUv[0], aTriNorm[0],
183                       aTriPos[1], aTriUv[1], aTriNorm[1],
184                       aTriPos[2], aTriUv[2], aTriNorm[2]) != 0;
185     }
186     else
187     {
188       return Fprintf (myFile, "f %d//%d %d//%d %d//%d\n",
189                       aTriPos[0], aTriNorm[0],
190                       aTriPos[1], aTriNorm[1],
191                       aTriPos[2], aTriNorm[2]) != 0;
192     }
193   }
194   if (myHasTexCoords)
195   {
196     const Graphic3d_Vec3i aTriUv = theTri + myElemUVFirst.xyz();
197     return Fprintf (myFile, "f %d/%d %d/%d %d/%d\n",
198                     aTriPos[0], aTriUv[0],
199                     aTriPos[1], aTriUv[1],
200                     aTriPos[2], aTriUv[2]) != 0;
201   }
202   else
203   {
204     return Fprintf (myFile, "f %d %d %d\n", aTriPos[0], aTriPos[1], aTriPos[2]) != 0;
205   }
206 }
207
208 // ================================================================
209 // Function : WriteQuad
210 // Purpose  :
211 // ================================================================
212 bool RWObj_ObjWriterContext::WriteQuad (const Graphic3d_Vec4i& theQuad)
213 {
214   const Graphic3d_Vec4i aQPos = theQuad + myElemPosFirst;
215   if (myHasNormals)
216   {
217     const Graphic3d_Vec4i aQNorm = theQuad + myElemNormFirst;
218     if (myHasTexCoords)
219     {
220       const Graphic3d_Vec4i aQTex = theQuad + myElemUVFirst;
221       return Fprintf (myFile, "f %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d\n",
222                       aQPos[0], aQTex[0], aQNorm[0],
223                       aQPos[1], aQTex[1], aQNorm[1],
224                       aQPos[2], aQTex[2], aQNorm[2],
225                       aQPos[3], aQTex[3], aQNorm[3]) != 0;
226     }
227     else
228     {
229       return Fprintf (myFile, "f %d//%d %d//%d %d//%d %d//%d\n",
230                       aQPos[0], aQNorm[0],
231                       aQPos[1], aQNorm[1],
232                       aQPos[2], aQNorm[2],
233                       aQPos[3], aQNorm[3]) != 0;
234     }
235   }
236   if (myHasTexCoords)
237   {
238     const Graphic3d_Vec4i aQTex = theQuad + myElemUVFirst;
239     return Fprintf (myFile, "f %d/%d %d/%d %d/%d %d/%d\n",
240                     aQPos[0], aQTex[0],
241                     aQPos[1], aQTex[1],
242                     aQPos[2], aQTex[2],
243                     aQPos[3], aQTex[3]) != 0;
244   }
245   else
246   {
247     return Fprintf (myFile, "f %d %d %d %d\n", aQPos[0], aQPos[1], aQPos[2], aQPos[3]) != 0;
248   }
249 }
250
251 // ================================================================
252 // Function : WriteVertex
253 // Purpose  :
254 // ================================================================
255 bool RWObj_ObjWriterContext::WriteVertex (const Graphic3d_Vec3& theValue)
256 {
257   return Fprintf (myFile, "v %f %f %f\n",  theValue.x(), theValue.y(), theValue.z()) != 0;
258 }
259
260 // ================================================================
261 // Function : WriteNormal
262 // Purpose  :
263 // ================================================================
264 bool RWObj_ObjWriterContext::WriteNormal (const Graphic3d_Vec3& theValue)
265 {
266   return Fprintf (myFile, "vn %f %f %f\n", theValue.x(), theValue.y(), theValue.z()) != 0;
267 }
268
269 // ================================================================
270 // Function : WriteTexCoord
271 // Purpose  :
272 // ================================================================
273 bool RWObj_ObjWriterContext::WriteTexCoord (const Graphic3d_Vec2& theValue)
274 {
275   return Fprintf (myFile, "vt %f %f\n", theValue.x(), theValue.y()) != 0;
276 }
277
278 // ================================================================
279 // Function : WriteGroup
280 // Purpose  :
281 // ================================================================
282 bool RWObj_ObjWriterContext::WriteGroup (const TCollection_AsciiString& theValue)
283 {
284   return !theValue.IsEmpty()
285         ? Fprintf (myFile, "g %s\n", theValue.ToCString()) != 0
286         : Fprintf (myFile, "g\n") != 0;
287 }
288
289 // ================================================================
290 // Function : FlushFace
291 // Purpose  :
292 // ================================================================
293 void RWObj_ObjWriterContext::FlushFace (Standard_Integer theNbNodes)
294 {
295   Graphic3d_Vec4i aShift (theNbNodes, theNbNodes, theNbNodes, theNbNodes);
296   myElemPosFirst += aShift;
297   if (myHasNormals)
298   {
299     myElemNormFirst += aShift;
300   }
301   if (myHasTexCoords)
302   {
303     myElemUVFirst += aShift;
304   }
305 }