1 /*
2      PLIB - A Suite of Portable Game Libraries
3      Copyright (C) 1998,2002  Steve Baker
4 
5      This library is free software; you can redistribute it and/or
6      modify it under the terms of the GNU Library General Public
7      License as published by the Free Software Foundation; either
8      version 2 of the License, or (at your option) any later version.
9 
10      This library is distributed in the hope that it will be useful,
11      but WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      Library General Public License for more details.
14 
15      You should have received a copy of the GNU Library General Public
16      License along with this library; if not, write to the Free
17      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 
19      For further information visit http://plib.sourceforge.net
20 
21      $Id: ssgSaveVRML1.cxx 1568 2002-09-02 06:05:49Z sjbaker $
22 
23      --------------------------------------------------------------------
24 
25      This save routine was written by Warren Wilbur to support a sub-set
26      of the standard Virtual Reality Modelling Language v1.0 (i.e. VRML1.0).
27      The Version 1.0 Specification, 9-Nov-95 was used and should be available
28      at www.vrml.org
29 
30      When reporting bugs/difficulties please mention 'VRML' in the subject
31      of your email and post to the plib developers mailing list.
32 */
33 
34 #include <stdio.h>
35 #include "ssgLocal.h"
36 #include "ssgLoaderWriterStuff.h"
37 
38 /* Function name:	SaveVRML1MaterialNode
39  *
40  * Limitations:		Before calling this function you must verify that
41  *             		at least one material exists whose textureName
42  *             		matches the one passed in to this function,
43  *             		otherwise you might save a empty Material node!
44  *
45  * Notes:		Saving empty Material notes will create an
46  *       		unnecessary choking hazard for VRML loaders :(
47  */
48 
SaveVRML1MaterialNode(FILE * fd,ssgIndexArray * materials_ptr,ssgSimpleStateArray * ssa_ptr,char * textureName,bool saveDiffuse,bool saveAmbient,bool saveEmission,bool saveSpecular)49 static void SaveVRML1MaterialNode(FILE *fd, ssgIndexArray *materials_ptr,
50                            ssgSimpleStateArray *ssa_ptr,
51                            char *textureName, bool saveDiffuse,
52                            bool saveAmbient,  bool saveEmission,
53                            bool saveSpecular)
54 {
55   ssgSimpleState *ss_ptr;
56   int            i;
57 
58   /* Tell VRML loaders that we are providing a single color for each
59    * face in the array of indices written below. */
60 
61   fprintf(fd, "    MaterialBinding { value PER_FACE }\n");
62   fprintf(fd, "    Material {\n");
63 
64   if (saveDiffuse)
65   {
66     fprintf(fd, "        diffuseColor [\n");
67     for (i = 0; i < materials_ptr->getNum(); i++)
68     {
69       ss_ptr = ssa_ptr->get(*(materials_ptr->get(i)));
70 
71            /* If we are trying to save all untextured materials then check
72             * if either ptr is NULL */
73 
74       if ( ( (textureName == NULL)&&
75              ((ss_ptr == NULL)||(ss_ptr->getTextureFilename() == NULL)) )||
76 
77            /* If we are trying to save all materials that are textured by
78             * a specific texture then check if the texture filename matches */
79 
80            ( (textureName != NULL)&&(ss_ptr != NULL)&&
81              (ss_ptr->getTextureFilename() != NULL)&&
82              (!strcmp(textureName, ss_ptr->getTextureFilename())) ) )
83       {
84         float diffuse0, diffuse1, diffuse2;
85 
86         diffuse0 = ss_ptr->diffuse_colour[0];
87         diffuse1 = ss_ptr->diffuse_colour[1];
88         diffuse2 = ss_ptr->diffuse_colour[2];
89 
90 #ifdef EXPERIMENTAL_ADD_AMBIENT_TO_DIFFUSE
91         diffuse0 += ss_ptr->ambient_colour[0];
92         diffuse1 += ss_ptr->ambient_colour[1];
93         diffuse2 += ss_ptr->ambient_colour[2];
94 #endif //EXPERIMENTAL_ADD_AMBIENT_TO_DIFFUSE
95 
96 #ifdef EXPERIMENTAL_ADD_EMISSION_TO_DIFFUSE
97         diffuse0 += ss_ptr->emission_colour[0];
98         diffuse1 += ss_ptr->emission_colour[1];
99         diffuse2 += ss_ptr->emission_colour[2];
100 #endif //EXPERIMENTAL_ADD_EMISSION_TO_DIFFUSE
101 
102 #ifdef EXPERIMENTAL_ADD_SPECULAR_TO_DIFFUSE
103         diffuse0 += ss_ptr->specular_colour[0];
104         diffuse1 += ss_ptr->specular_colour[1];
105         diffuse2 += ss_ptr->specular_colour[2];
106 #endif //EXPERIMENTAL_ADD_SPECULAR_TO_DIFFUSE
107 
108         /* OpenGL caps the maximum RGB value for a colour to 1.0 when it
109          * calculates colours in a scene. If we don't cap the value OpenGL
110          * will do it for us. */
111 
112         fprintf(fd, "            %f %f %f,\n", diffuse0 > 1.0 ? 1.0:diffuse0,
113                 diffuse1 > 1.0 ? 1.0:diffuse1, diffuse2 > 1.0 ? 1.0:diffuse2);
114       }
115     }
116     fprintf(fd, "        ]\n"); //close diffuseColor array
117   }
118 
119   if (saveAmbient)
120   {
121     fprintf(fd, "        ambientColor [\n");
122     for (i = 0; i < materials_ptr->getNum(); i++)
123     {
124       ss_ptr = ssa_ptr->get(*(materials_ptr->get(i)));
125 
126        /* If we are trying to save all untextured materials then check
127         * if either ptr is NULL */
128 
129       if ( ( (textureName == NULL)&&
130              ((ss_ptr == NULL)||(ss_ptr->getTextureFilename() == NULL)) )||
131 
132            /* If we are trying to save all materials that are textured by
133             * a specific texture then check if the texture filename matches */
134 
135            ( (textureName != NULL)&&(ss_ptr != NULL)&&
136              (ss_ptr->getTextureFilename() != NULL)&&
137              (!strcmp(textureName, ss_ptr->getTextureFilename())) ) )
138       {
139         fprintf(fd, "            %f %f %f,\n", ss_ptr->ambient_colour[0],
140                 ss_ptr->ambient_colour[1], ss_ptr->ambient_colour[2]);
141       }
142       fprintf(fd, "        ]\n"); //close ambientColor array
143     }
144   }
145 
146   if(saveEmission)
147   {
148     fprintf(fd, "        emissiveColor [\n");
149     for (i = 0; i < materials_ptr->getNum(); i++)
150     {
151       ss_ptr = ssa_ptr->get(*(materials_ptr->get(i)));
152 
153          /* If we are trying to save all untextured materials then check
154           * if either ptr is NULL */
155 
156       if ( ( (textureName == NULL)&&
157              ((ss_ptr == NULL)||(ss_ptr->getTextureFilename() == NULL)) )||
158 
159            /* If we are trying to save all materials that are textured by
160             * a specific texture then check if the texture filename matches */
161 
162            ( (textureName != NULL)&&(ss_ptr != NULL)&&
163              (ss_ptr->getTextureFilename() != NULL)&&
164              (!strcmp(textureName, ss_ptr->getTextureFilename())) ) )
165       {
166         fprintf(fd, "            %f %f %f,\n", ss_ptr->emission_colour[0],
167                 ss_ptr->emission_colour[1], ss_ptr->emission_colour[2]);
168       }
169       fprintf(fd, "        ]\n"); //close emissionColor array
170     }
171   }
172 
173   if(saveSpecular)
174   {
175     fprintf(fd, "        specularColor [\n");
176     for (i = 0; i < materials_ptr->getNum(); i++)
177     {
178       ss_ptr = ssa_ptr->get(*(materials_ptr->get(i)));
179 
180            /* If we are trying to save all untextured materials then check
181             * if either ptr is NULL */
182 
183       if ( ( (textureName == NULL)&&
184              ((ss_ptr == NULL)||(ss_ptr->getTextureFilename() == NULL)) )||
185 
186            /* If we are trying to save all materials that are textured by
187             * a specific texture then check if the texture filename matches */
188 
189            ( (textureName != NULL)&&(ss_ptr != NULL)&&
190              (ss_ptr->getTextureFilename() != NULL)&&
191              (!strcmp(textureName, ss_ptr->getTextureFilename())) ) )
192       {
193         fprintf(fd, "            %f %f %f,\n", ss_ptr->specular_colour[0],
194                 ss_ptr->specular_colour[1], ss_ptr->specular_colour[2]);
195       }
196       fprintf(fd, "        ]\n"); //close specularColor array
197     }
198   }
199 
200   fprintf(fd, "    }\n"); //close Material node
201   return;
202 }
203 
204 /* The 'main' entry point for saving a model in VRML1.0 */
205 
ssgSaveVRML1(const char * fname,ssgEntity * ent)206 int ssgSaveVRML1( const char* fname, ssgEntity *ent ) {
207   ssgVertexArray        *vertices_ptr;
208   ssgIndexArray         *indices_ptr;
209   FILE                  *fd;
210   ssgSimpleStateArray   ssa;
211   ssgTexCoordArray      *texcoord_ptr;
212   ssgIndexArray         *materials_ptr;
213   bool                  textured_faces_found, untextured_faces_found,
214                         textureFacesAlreadySaved;
215   int                   i, j, index1, index2, index3;
216   ssgSimpleState        *ss_ptr, *ss_ptr2;
217 
218   fd = fopen ( fname, "w" ) ;
219   if ( fd == NULL )
220   {
221     ulSetError ( UL_WARNING, "ssgSaveVRML1: Failed to open '%s' for writing",
222 		 fname );
223     return FALSE ;
224   }
225 
226   vertices_ptr  = new ssgVertexArray();
227   indices_ptr   = new ssgIndexArray();
228   materials_ptr = new ssgIndexArray();
229   texcoord_ptr  = new ssgTexCoordArray();
230 
231   sgMat4 ident;
232   sgMakeIdentMat4( ident );
233   ssgAccumVerticesAndFaces( ent, ident, vertices_ptr, indices_ptr, -1.0f,
234                             &ssa, materials_ptr, texcoord_ptr);
235 
236   /* The spec requires every file to begin with these characters */
237 
238   fprintf(fd, "#VRML V1.0 ascii\n\n");
239 
240   /* Since a VRML file contains only one parent node we must use a
241    * node type that can have several child 'nodes' so we can save
242    * the materials, texture coordinates, vertices, and indices each
243    * as (seperate) child nodes. */
244 
245   fprintf(fd, "Separator {\n");
246 
247     /* Save all the individual vertices used in the model. It doesn't
248      * matter if there are duplicates... */
249 
250     fprintf(fd, "    Coordinate3 {\n        point [\n");
251 
252       for (i = 0; i < vertices_ptr->getNum(); i++)
253       {
254         fprintf(fd, "            %f %f %f,\n", vertices_ptr->get(i)[0],
255                 vertices_ptr->get(i)[1], vertices_ptr->get(i)[2]);
256       }
257 
258     fprintf(fd, "        ]\n    }\n"); //close point array and
259                                                //Coordinate3
260 
261     /* Chcck if the model is textured at all. This test will help us parse
262      * out how to save the model since it may be totally textured, partially
263      * textured, or not textured at all. */
264 
265     textured_faces_found = false;
266     untextured_faces_found = false;
267     for (i = 0; i < materials_ptr->getNum(); i++)
268     {
269       ss_ptr = ssa.get(*(materials_ptr->get(i)));
270       if ( (ss_ptr != NULL)&&(ss_ptr->getTextureFilename() != NULL) )
271       {
272         textured_faces_found = true;
273       }
274       else
275       {
276         untextured_faces_found = true;
277       }
278     }
279 
280     if (untextured_faces_found)
281     {
282       /* Save all the material node fields which VRML supports. Note that the
283        * VRML spec discourages complicated uses of the Material Node. We
284        * cannot expect VRML implementations to support the full syntax
285        * of the Material Node including ambient, diffuse, specular, emissive,
286        * shininess, and transparency. We should be always be okay if we just
287        * use diffuse. */
288 
289       SaveVRML1MaterialNode(fd, materials_ptr, &ssa, NULL,
290                            true, false, false, false);
291 
292       /* Save all faces that are not textured in a single IndexedFaceSet node */
293 
294       fprintf(fd, "    IndexedFaceSet {\n        coordIndex [\n");
295       for (i = 0; i < indices_ptr->getNum(); i+=3)
296       {
297         ss_ptr = ssa.get(*(materials_ptr->get(i/3)));
298 
299         /* Make sure this face doesn't have a texture associated with it */
300 
301         if ( (ss_ptr == NULL)||(ss_ptr->getTextureFilename() == NULL) )
302 	{
303           index1 = *indices_ptr->get(i);
304           index2 = *indices_ptr->get(i+1);
305           index3 = *indices_ptr->get(i+2);
306 
307 	  /* Check for index overflow since PLIB stores it as a short */
308 
309 	  if ( (index1 < 0)||(index2 < 0)||(index3 < 0) )
310 	  {
311             ulSetError(UL_WARNING, "ssgSaveVRML1: Save error: index overflow, "
312                        "value won't fit in 16bits.");
313 	  }
314 	  else
315 	  {
316             fprintf(fd, "            %d, %d, %d, -1,\n", index1, index2,
317                                                          index3);
318 	  }
319         }
320       }
321       fprintf(fd, "        ]\n    }\n"); //close coordIndex array and
322                                          //IndexedFaceSet
323     }
324 
325     if (textured_faces_found)
326     {
327       /* Save all texture coordinates (per-vertex) for all the textures in
328        * the model. It doesn't matter if there is one texture or more than
329        * one since we will specify which texture to use with the coordinates
330        * before saving the portion of the indexed face set which uses that
331        * texture. */
332 
333       fprintf(fd, "    TextureCoordinate2 {\n        point [\n");
334       for (i = 0; i < texcoord_ptr->getNum(); i++)
335       {
336 
337 /* In VrmlView Pro 3.0 (Linux) textured models appear correct Left-Right
338  * but the texture is reversed Top-Bottom. Enabling the INVERSE_REPEAT
339  * macro fixes the problem for VrmlView. I don't want to enable this until
340  * I figure out where the problem really is!? */
341 
342 //#define INVERSE_REPEAT(a) (a > 0.0 ? 1.0 - a:a + 1.0)
343 #define INVERSE_REPEAT(a) a
344 
345         fprintf(fd, "            %f %f,\n", texcoord_ptr->get(i)[0],
346                 INVERSE_REPEAT(texcoord_ptr->get(i)[1]));
347       }
348       fprintf(fd, "        ]\n    }\n");
349 
350       /* Now save separate Texture2 and IndexedFaceSet node pairs for each
351        * texture used in the model. Each of the IndexedFaceSet(s) will
352        * reference back to the initial vertices, materials, and texture
353        * coordinates (due to the lack of Seperator nodes in between).
354        * Find the first textured face starting at the i'th face. In this
355        * manner we will find the next texture used in the model and save
356        * all faces that are textured with it. */
357 
358       for (i = 0; i < indices_ptr->getNum(); i+=3)
359       {
360         ss_ptr = ssa.get(*(materials_ptr->get(i/3)));
361         if ( (ss_ptr != NULL)&&(ss_ptr->getTextureFilename() != NULL) )
362         {
363           /* We've found the next textured face. Since we save all
364            * faces using a texture when we find the first face using
365            * that texture we must check if the faces for this texture
366            * have already been saved. If we can find a face using
367            * this texture earlier in the list of faces then we know
368            * that it has already been saved. */
369 
370           textureFacesAlreadySaved = false;
371           for (j = 0; j < i; j+=3)
372           {
373             ss_ptr2 = ssa.get(*(materials_ptr->get(j/3)));
374             if ( (ss_ptr2 != NULL)&&
375                  (ss_ptr2->getTextureFilename() != NULL)&&
376                  (!strcmp(ss_ptr->getTextureFilename(),
377                           ss_ptr2->getTextureFilename())) )
378             {
379               textureFacesAlreadySaved = true;
380               break;
381             }
382           }
383 
384           if (!textureFacesAlreadySaved)
385           {
386             fprintf(fd, "    Texture2 {\n");
387             fprintf(fd, "        filename %s\n", ss_ptr->getTextureFilename());
388 //TODO: support CLAMP mode as well.
389             fprintf(fd, "        wrapS    REPEAT\n");
390             fprintf(fd, "        wrapT    REPEAT\n");
391             fprintf(fd, "    }\n");
392 
393 	    /* Save all the materials needed by this following indexed face
394 	     * set. This will save all materials that have the same texture
395 	     * filename specified below. */
396 
397             SaveVRML1MaterialNode(fd, materials_ptr, &ssa,
398                                  ss_ptr->getTextureFilename(),
399                                  true, false, false, false);
400 
401             fprintf(fd, "    IndexedFaceSet {\n        coordIndex [\n");
402             for (j = i; j < indices_ptr->getNum(); j+=3)
403             {
404               /* Save each face which is textured by the Texture2 node defined
405                * above. */
406 
407               ss_ptr2 = ssa.get(*(materials_ptr->get(j/3)));
408               if ( (ss_ptr2 != NULL)&&
409                    (ss_ptr2->getTextureFilename() != NULL)&&
410                    (!strcmp(ss_ptr->getTextureFilename(),
411                             ss_ptr2->getTextureFilename())) )
412               {
413                 index1 = *indices_ptr->get(j);
414                 index2 = *indices_ptr->get(j+1);
415                 index3 = *indices_ptr->get(j+2);
416 
417                 /* Check for index overflow since PLIB stores it as a
418                  * short */
419 
420                 if ( (index1 < 0)||(index2 < 0)||(index3 < 0) )
421                 {
422                   ulSetError(UL_WARNING, "ssgSaveVRML1: Save error: index "
423                              "overflow, value won't fit in 16bits.");
424                 }
425                 else
426                 {
427                   fprintf(fd, "            %d, %d, %d, -1,\n",
428                           index1, index2, index3);
429                 }
430               }
431             }
432             fprintf(fd, "        ]\n    }\n"); //close coordIndex array and
433                                                //IndexedFaceSet
434           }
435         }
436       }
437     }
438 
439   fprintf(fd, "}\n"); //close Seperator
440   fclose( fd ) ;
441 
442   delete vertices_ptr;
443   delete indices_ptr;
444   delete materials_ptr;
445   delete texcoord_ptr;
446   return TRUE;
447 }
448 
449 
450