]> git.mxchange.org Git - simgear.git/blob - simgear/io/sg_binobj.cxx
c6cfc63686eaa23a2a9181bb210a3a82f4cacd7b
[simgear.git] / simgear / io / sg_binobj.cxx
1 // sg_binobj.cxx -- routines to read and write low level flightgear 3d objects
2 //
3 // Written by Curtis Olson, started January 2000.
4 //
5 // Copyright (C) 2000  Curtis L. Olson  - curt@flightgear.org
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or
10 // (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 // GNU General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 //
21 // $Id$
22 //
23
24
25 #include <simgear/compiler.h>
26
27 #include <stdio.h>
28 #include <time.h>
29
30 #include <vector>
31 #include STL_STRING
32
33 #include <simgear/bucket/newbucket.hxx>
34
35 #include "lowlevel.hxx"
36 #include "sg_binobj.hxx"
37
38
39 SG_USING_STD( string );
40 SG_USING_STD( vector );
41
42 #if !defined (SG_HAVE_NATIVE_SGI_COMPILERS)
43 SG_USING_STD( cout );
44 SG_USING_STD( endl );
45 #endif
46
47
48 enum {
49     SG_BOUNDING_SPHERE = 0,
50
51     SG_VERTEX_LIST = 1,
52     SG_NORMAL_LIST = 2,
53     SG_TEXCOORD_LIST = 3,
54
55     SG_TRIANGLE_FACES = 10,
56     SG_TRIANGLE_STRIPS = 11,
57     SG_TRIANGLE_FANS = 12
58 } tgObjectTypes;
59
60 enum {
61     SG_MATERIAL = 0
62 } tgPropertyTypes;
63
64
65 class sgSimpleBuffer {
66
67 private:
68
69     char *ptr;
70     unsigned int size;
71
72 public:
73
74     inline sgSimpleBuffer( unsigned int s )
75     {
76         size = 1;
77         while ( size < s ) {
78             size *= 2;
79         }
80         cout << "Creating a new buffer of size = " << size << endl;
81         ptr = new char[size];
82     }
83
84     inline ~sgSimpleBuffer() {
85         delete [] ptr;
86     }
87
88     inline unsigned int get_size() const { return size; }
89     inline char *get_ptr() const { return ptr; }
90     inline void resize( unsigned int s ) {
91         if ( s > size ) {
92             if ( ptr != NULL ) {
93                 delete [] ptr;
94             }
95             while ( size < s ) {
96                 size *= 2;
97             }
98             cout << "resizing buffer to size = " << size << endl;
99             ptr = new char[size];
100         }
101     }
102 };
103
104
105 // calculate the center of a list of points, by taking the halfway
106 // point between the min and max points.
107 static Point3D calc_center( point_list& wgs84_nodes ) {
108     Point3D p, min, max;
109
110     if ( wgs84_nodes.size() ) {
111         min = max = wgs84_nodes[0];
112     } else {
113         min = max = Point3D( 0 );
114     }
115
116     for ( int i = 0; i < (int)wgs84_nodes.size(); ++i ) {
117         p = wgs84_nodes[i];
118
119         if ( p.x() < min.x() ) { min.setx( p.x() ); }
120         if ( p.y() < min.y() ) { min.sety( p.y() ); }
121         if ( p.z() < min.z() ) { min.setz( p.z() ); }
122
123         if ( p.x() > max.x() ) { max.setx( p.x() ); }
124         if ( p.y() > max.y() ) { max.sety( p.y() ); }
125         if ( p.z() > max.z() ) { max.setz( p.z() ); }
126     }
127
128     return ( min + max ) / 2.0;
129 }
130
131 // calculate the bounding sphere.  Center is the center of the
132 // tile and zero elevation
133 double sgCalcBoundingRadius( Point3D center, point_list& wgs84_nodes ) {
134     double dist_squared;
135     double radius_squared = 0;
136     
137     for ( int i = 0; i < (int)wgs84_nodes.size(); ++i ) {
138         dist_squared = center.distance3Dsquared( wgs84_nodes[i] );
139         if ( dist_squared > radius_squared ) {
140             radius_squared = dist_squared;
141         }
142     }
143
144     return sqrt(radius_squared);
145 }
146
147
148 // read a binary file and populate the provided structures.
149 bool SGBinObject::read_bin( const string& file ) {
150     Point3D p;
151     int i, j, k;
152     char material[256];
153     unsigned int nbytes;
154     static sgSimpleBuffer buf( 32768 );  // 32 Kb
155
156     // zero out structures
157     gbs_center = Point3D( 0 );
158     gbs_radius = 0.0;
159
160     wgs84_nodes.clear();
161     normals.clear();
162     texcoords.clear();
163
164     tris_v.clear();
165     tris_tc.clear();
166     tri_materials.clear();
167
168     strips_v.clear();
169     strips_tc.clear();
170     strip_materials.clear();
171
172     fans_v.clear();
173     fans_tc.clear();
174     fan_materials.clear();
175    
176     gzFile fp;
177     if ( (fp = gzopen( file.c_str(), "rb" )) == NULL ) {
178         string filegz = file + ".gz";
179         if ( (fp = gzopen( filegz.c_str(), "rb" )) == NULL ) {
180             // cout << "ERROR: opening " << file << " or " << filegz
181             //      << "for reading!" << endl;
182
183             return false;
184         }
185     }
186
187     sgClearReadError();
188
189     // read headers
190     unsigned int header;
191     sgReadUInt( fp, &header );
192     if ( ((header & 0xFF000000) >> 24) == 'S' &&
193          ((header & 0x00FF0000) >> 16) == 'G' ) {
194         // cout << "Good header" << endl;
195         // read file version
196         version = (header & 0x0000FFFF);
197         // cout << "File version = " << version << endl;
198     } else {
199         // close the file before we return
200         gzclose(fp);
201
202         return false;
203     }
204
205     // read creation time
206     time_t calendar_time;
207     sgReadLong( fp, &calendar_time );
208
209 #if 0
210     // The following code has a global effect on the host application
211     // and can screws up the time elsewhere.  It should be avoided
212     // unless you need this for debugging in which case you should
213     // disable it again once the debugging task is finished.
214     struct tm *local_tm;
215     local_tm = localtime( &calendar_time );
216     char time_str[256];
217     strftime( time_str, 256, "%a %b %d %H:%M:%S %Z %Y", local_tm);
218     cout << "File created on " << time_str << endl;
219 #endif
220
221     // read number of top level objects
222     short nobjects;
223     sgReadShort( fp, &nobjects );
224     // cout << "Total objects to read = " << nobjects << endl;
225
226     // read in objects
227     for ( i = 0; i < nobjects; ++i ) {
228         // read object header
229         char obj_type;
230         short nproperties, nelements;
231         sgReadChar( fp, &obj_type );
232         sgReadShort( fp, &nproperties );
233         sgReadShort( fp, &nelements );
234
235         // cout << "object " << i << " = " << (int)obj_type << " props = "
236         //      << nproperties << " elements = " << nelements << endl;
237             
238         if ( obj_type == SG_BOUNDING_SPHERE ) {
239             // read bounding sphere properties
240             for ( j = 0; j < nproperties; ++j ) {
241                 char prop_type;
242                 sgReadChar( fp, &prop_type );
243
244                 sgReadUInt( fp, &nbytes );
245                 // cout << "property size = " << nbytes << endl;
246                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
247                 char *ptr = buf.get_ptr();
248                 sgReadBytes( fp, nbytes, ptr );
249             }
250
251             // read bounding sphere elements
252             for ( j = 0; j < nelements; ++j ) {
253                 sgReadUInt( fp, &nbytes );
254                 // cout << "element size = " << nbytes << endl;
255                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
256                 char *ptr = buf.get_ptr();
257                 sgReadBytes( fp, nbytes, ptr );
258
259                 double *dptr = (double *)ptr;
260                 if ( sgIsBigEndian() ) {
261                     sgEndianSwap( (uint64 *)&(dptr[0]) );
262                     sgEndianSwap( (uint64 *)&(dptr[1]) );
263                     sgEndianSwap( (uint64 *)&(dptr[2]) );
264                 }
265                 gbs_center = Point3D( dptr[0], dptr[1], dptr[2] );
266                 // cout << "Center = " << gbs_center << endl;
267                 ptr += sizeof(double) * 3;
268                 
269                 float *fptr = (float *)ptr;
270                 if ( sgIsBigEndian() ) {
271                     sgEndianSwap( (unsigned int *)fptr );
272                 }
273                 gbs_radius = fptr[0];
274                 // cout << "Bounding radius = " << gbs_radius << endl;
275             }
276         } else if ( obj_type == SG_VERTEX_LIST ) {
277             // read vertex list properties
278             for ( j = 0; j < nproperties; ++j ) {
279                 char prop_type;
280                 sgReadChar( fp, &prop_type );
281
282                 sgReadUInt( fp, &nbytes );
283                 // cout << "property size = " << nbytes << endl;
284                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
285                 char *ptr = buf.get_ptr();
286                 sgReadBytes( fp, nbytes, ptr );
287             }
288
289             // read vertex list elements
290             for ( j = 0; j < nelements; ++j ) {
291                 sgReadUInt( fp, &nbytes );
292                 // cout << "element size = " << nbytes << endl;
293                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
294                 char *ptr = buf.get_ptr();
295                 sgReadBytes( fp, nbytes, ptr );
296                 int count = nbytes / (sizeof(float) * 3);
297                 float *fptr = (float *)ptr;
298                 for ( k = 0; k < count; ++k ) {
299                     if ( sgIsBigEndian() ) {
300                         sgEndianSwap( (unsigned int *)&(fptr[0]) );
301                         sgEndianSwap( (unsigned int *)&(fptr[1]) );
302                         sgEndianSwap( (unsigned int *)&(fptr[2]) );
303                     }
304                     p = Point3D( fptr[0], fptr[1], fptr[2] );
305                     // cout << "node = " << p << endl;
306                     wgs84_nodes.push_back( p );
307                     fptr += 3;
308                 }
309             }
310         } else if ( obj_type == SG_NORMAL_LIST ) {
311             // read normal list properties
312             for ( j = 0; j < nproperties; ++j ) {
313                 char prop_type;
314                 sgReadChar( fp, &prop_type );
315
316                 sgReadUInt( fp, &nbytes );
317                 // cout << "property size = " << nbytes << endl;
318                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
319                 char *ptr = buf.get_ptr();
320                 sgReadBytes( fp, nbytes, ptr );
321             }
322
323             // read normal list elements
324             for ( j = 0; j < nelements; ++j ) {
325                 sgReadUInt( fp, &nbytes );
326                 // cout << "element size = " << nbytes << endl;
327                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
328                 unsigned char *ptr = (unsigned char *)(buf.get_ptr());
329                 sgReadBytes( fp, nbytes, ptr );
330                 int count = nbytes / 3;
331                 for ( k = 0; k < count; ++k ) {
332                     sgdVec3 normal;
333                     sgdSetVec3( normal,
334                                (ptr[0]) / 127.5 - 1.0,
335                                (ptr[1]) / 127.5 - 1.0,
336                                (ptr[2]) / 127.5 - 1.0 );
337                     sgdNormalizeVec3( normal );
338
339                     p = Point3D( normal[0], normal[1], normal[2] );
340                     // cout << "normal = " << p << endl;
341                     normals.push_back( p );
342                     ptr += 3;
343                 }
344             }
345         } else if ( obj_type == SG_TEXCOORD_LIST ) {
346             // read texcoord list properties
347             for ( j = 0; j < nproperties; ++j ) {
348                 char prop_type;
349                 sgReadChar( fp, &prop_type );
350
351                 sgReadUInt( fp, &nbytes );
352                 // cout << "property size = " << nbytes << endl;
353                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
354                 char *ptr = buf.get_ptr();
355                 sgReadBytes( fp, nbytes, ptr );
356             }
357
358             // read texcoord list elements
359             for ( j = 0; j < nelements; ++j ) {
360                 sgReadUInt( fp, &nbytes );
361                 // cout << "element size = " << nbytes << endl;
362                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
363                 char *ptr = buf.get_ptr();
364                 sgReadBytes( fp, nbytes, ptr );
365                 int count = nbytes / (sizeof(float) * 2);
366                 float *fptr = (float *)ptr;
367                 for ( k = 0; k < count; ++k ) {
368                     if ( sgIsBigEndian() ) {
369                         sgEndianSwap( (unsigned int *)&(fptr[0]) );
370                         sgEndianSwap( (unsigned int *)&(fptr[1]) );
371                     }
372                     p = Point3D( fptr[0], fptr[1], 0 );
373                     // cout << "texcoord = " << p << endl;
374                     texcoords.push_back( p );
375                     fptr += 2;
376                 }
377             }
378         } else if ( obj_type == SG_TRIANGLE_FACES ) {
379             // read triangle face properties
380             for ( j = 0; j < nproperties; ++j ) {
381                 char prop_type;
382                 sgReadChar( fp, &prop_type );
383
384                 sgReadUInt( fp, &nbytes );
385                 // cout << "property size = " << nbytes << endl;
386                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
387                 char *ptr = buf.get_ptr();
388                 sgReadBytes( fp, nbytes, ptr );
389                 if ( prop_type == SG_MATERIAL ) {
390                     strncpy( material, ptr, nbytes );
391                     material[nbytes] = '\0';
392                     // cout << "material type = " << material << endl;
393                 }
394             }
395
396             // read triangle face elements
397             for ( j = 0; j < nelements; ++j ) {
398                 sgReadUInt( fp, &nbytes );
399                 // cout << "element size = " << nbytes << endl;
400                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
401                 char *ptr = buf.get_ptr();
402                 sgReadBytes( fp, nbytes, ptr );
403                 int count = nbytes / (sizeof(short) * 2);
404                 short *sptr = (short *)ptr;
405                 int_list vs, tcs;
406                 vs.clear(); tcs.clear();
407                 for ( k = 0; k < count; ++k ) {
408                     if ( sgIsBigEndian() ) {
409                         sgEndianSwap( (unsigned short *)&(sptr[0]) );
410                         sgEndianSwap( (unsigned short *)&(sptr[1]) );
411                     }
412                     vs.push_back( sptr[0] );
413                     tcs.push_back( sptr[1] );
414                     // cout << sptr[0] << "/" << sptr[1] << " ";
415                     sptr += 2;
416                 }
417                 // cout << endl;
418                 tris_v.push_back( vs );
419                 tris_tc.push_back( tcs );
420                 tri_materials.push_back( material );
421             }
422         } else if ( obj_type == SG_TRIANGLE_STRIPS ) {
423             // read triangle strip properties
424             for ( j = 0; j < nproperties; ++j ) {
425                 char prop_type;
426                 sgReadChar( fp, &prop_type );
427
428                 sgReadUInt( fp, &nbytes );
429                 // cout << "property size = " << nbytes << endl;
430                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
431                 char *ptr = buf.get_ptr();
432                 sgReadBytes( fp, nbytes, ptr );
433                 if ( prop_type == SG_MATERIAL ) {
434                     strncpy( material, ptr, nbytes );
435                     material[nbytes] = '\0';
436                     // cout << "material type = " << material << endl;
437                 }
438             }
439
440             // read triangle strip elements
441             for ( j = 0; j < nelements; ++j ) {
442                 sgReadUInt( fp, &nbytes );
443                 // cout << "element size = " << nbytes << endl;
444                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
445                 char *ptr = buf.get_ptr();
446                 sgReadBytes( fp, nbytes, ptr );
447                 int count = nbytes / (sizeof(short) * 2);
448                 short *sptr = (short *)ptr;
449                 int_list vs, tcs;
450                 vs.clear(); tcs.clear();
451                 for ( k = 0; k < count; ++k ) {
452                     if ( sgIsBigEndian() ) {
453                         sgEndianSwap( (unsigned short *)&(sptr[0]) );
454                         sgEndianSwap( (unsigned short *)&(sptr[1]) );
455                     }
456                     vs.push_back( sptr[0] );
457                     tcs.push_back( sptr[1] );
458                     // cout << sptr[0] << "/" << sptr[1] << " ";
459                     sptr += 2;
460                 }
461                 // cout << endl;
462                 strips_v.push_back( vs );
463                 strips_tc.push_back( tcs );
464                 strip_materials.push_back( material );
465             }
466         } else if ( obj_type == SG_TRIANGLE_FANS ) {
467             // read triangle fan properties
468             for ( j = 0; j < nproperties; ++j ) {
469                 char prop_type;
470                 sgReadChar( fp, &prop_type );
471
472                 sgReadUInt( fp, &nbytes );
473                 // cout << "property size = " << nbytes << endl;
474                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
475                 char *ptr = buf.get_ptr();
476                 sgReadBytes( fp, nbytes, ptr );
477                 if ( prop_type == SG_MATERIAL ) {
478                     strncpy( material, ptr, nbytes );
479                     material[nbytes] = '\0';
480                     // cout << "material type = " << material << endl;
481                 }
482             }
483
484             // read triangle fan elements
485             for ( j = 0; j < nelements; ++j ) {
486                 sgReadUInt( fp, &nbytes );
487                 // cout << "element size = " << nbytes << endl;
488                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
489                 char *ptr = buf.get_ptr();
490                 sgReadBytes( fp, nbytes, ptr );
491                 int count = nbytes / (sizeof(short) * 2);
492                 short *sptr = (short *)ptr;
493                 int_list vs, tcs;
494                 vs.clear(); tcs.clear();
495                 for ( k = 0; k < count; ++k ) {
496                     if ( sgIsBigEndian() ) {
497                         sgEndianSwap( (unsigned short *)&(sptr[0]) );
498                         sgEndianSwap( (unsigned short *)&(sptr[1]) );
499                     }
500                     vs.push_back( sptr[0] );
501                     tcs.push_back( sptr[1] );
502                     // cout << sptr[0] << "/" << sptr[1] << " ";
503                     sptr += 2;
504                 }
505                 // cout << endl;
506                 fans_v.push_back( vs );
507                 fans_tc.push_back( tcs );
508                 fan_materials.push_back( material );
509             }
510         } else {
511             // unknown object type, just skip
512
513             // read properties
514             for ( j = 0; j < nproperties; ++j ) {
515                 char prop_type;
516                 sgReadChar( fp, &prop_type );
517
518                 sgReadUInt( fp, &nbytes );
519                 // cout << "property size = " << nbytes << endl;
520                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
521                 char *ptr = buf.get_ptr();
522                 sgReadBytes( fp, nbytes, ptr );
523             }
524
525             // read elements
526             for ( j = 0; j < nelements; ++j ) {
527                 sgReadUInt( fp, &nbytes );
528                 // cout << "element size = " << nbytes << endl;
529                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
530                 char *ptr = buf.get_ptr();
531                 sgReadBytes( fp, nbytes, ptr );
532             }
533         }
534     }
535
536     // close the file
537     gzclose(fp);
538
539     if ( sgReadError() ) {
540         cout << "We detected an error while reading the file." << endl;
541         return false;
542     }
543
544     return true;
545 }
546
547
548 // write out the structures to a binary file.  We assume that the
549 // groups come to us sorted by material property.  If not, things
550 // don't break, but the result won't be as optimal.
551 bool SGBinObject::write_bin( const string& base, const string& name,
552                              const SGBucket& b )
553 {
554     Point3D p;
555     sgVec2 t;
556     sgVec3 pt;
557     int i, j;
558
559     string dir = base + "/" + b.gen_base_path();
560     string command = "mkdir -p " + dir;
561 #if defined(_MSC_VER) || defined(__MINGW32__)
562     system( (string("mkdir ") + dir).c_str() );
563 #else
564     system(command.c_str());
565 #endif
566
567     string file = dir + "/" + name + ".gz";
568     cout << "Output file = " << file << endl;
569
570     gzFile fp;
571     if ( (fp = gzopen( file.c_str(), "wb9" )) == NULL ) {
572         cout << "ERROR: opening " << file << " for writing!" << endl;
573         return false;
574     }
575
576     sgClearWriteError();
577
578     cout << "triangles size = " << tris_v.size() << "  tri_materials = " 
579          << tri_materials.size() << endl;
580     cout << "strips size = " << strips_v.size() << "  strip_materials = " 
581          << strip_materials.size() << endl;
582     cout << "fans size = " << fans_v.size() << "  fan_materials = " 
583          << fan_materials.size() << endl;
584
585     cout << "points = " << wgs84_nodes.size() << endl;
586     cout << "tex coords = " << texcoords.size() << endl;
587
588     // write header magic
589     sgWriteUInt( fp, SG_FILE_MAGIC_NUMBER );
590     time_t calendar_time = time(NULL);
591     sgWriteLong( fp, (long int)calendar_time );
592
593     // calculate and write number of top level objects
594     string material;
595     int start;
596     int end;
597     short nobjects = 0;
598     nobjects++;                 // for gbs
599     nobjects++;                 // for vertices
600     nobjects++;                 // for normals
601     nobjects++;                 // for texcoords
602
603     // tris
604     short ntris = 0;
605     start = 0; end = 1;
606     while ( start < (int)tri_materials.size() ) {
607         material = tri_materials[start];
608         while ( (end < (int)tri_materials.size()) &&
609                 (material == tri_materials[end]) ) {
610             end++;
611         }
612         ntris++;
613         start = end; end = start + 1;
614     }
615     nobjects += ntris;
616
617     // strips
618     short nstrips = 0;
619     start = 0; end = 1;
620     while ( start < (int)strip_materials.size() ) {
621         material = strip_materials[start];
622         while ( (end < (int)strip_materials.size()) &&
623                 (material == strip_materials[end]) ) {
624             end++;
625         }
626         nstrips++;
627         start = end; end = start + 1;
628     }
629     nobjects += nstrips;
630
631     // fans
632     short nfans = 0;
633     start = 0; end = 1;
634     while ( start < (int)fan_materials.size() ) {
635         material = fan_materials[start];
636         while ( (end < (int)fan_materials.size()) &&
637                 (material == fan_materials[end]) ) {
638             end++;
639         }
640         nfans++;
641         start = end; end = start + 1;
642     }
643     nobjects += nfans;
644
645     cout << "total top level objects = " << nobjects << endl;
646     sgWriteShort( fp, nobjects );
647
648     // write bounding sphere
649     sgWriteChar( fp, (char)SG_BOUNDING_SPHERE );        // type
650     sgWriteShort( fp, 0 );                              // nproperties
651     sgWriteShort( fp, 1 );                              // nelements
652
653     sgWriteUInt( fp, sizeof(double) * 3 + sizeof(float) ); // nbytes
654     sgdVec3 center;
655     sgdSetVec3( center, gbs_center.x(), gbs_center.y(), gbs_center.z() );
656     sgWritedVec3( fp, center );
657     sgWriteFloat( fp, gbs_radius );
658
659     // dump vertex list
660     sgWriteChar( fp, (char)SG_VERTEX_LIST );             // type
661     sgWriteShort( fp, 0 );                               // nproperties
662     sgWriteShort( fp, 1 );                               // nelements
663     sgWriteUInt( fp, wgs84_nodes.size() * sizeof(float) * 3 ); // nbytes
664     for ( i = 0; i < (int)wgs84_nodes.size(); ++i ) {
665         p = wgs84_nodes[i] - gbs_center;
666         sgSetVec3( pt, p.x(), p.y(), p.z() );
667         sgWriteVec3( fp, pt );
668     }
669
670     // dump vertex normal list
671     sgWriteChar( fp, (char)SG_NORMAL_LIST );            // type
672     sgWriteShort( fp, 0 );                              // nproperties
673     sgWriteShort( fp, 1 );                              // nelements
674     sgWriteUInt( fp, normals.size() * 3 );              // nbytes
675     char normal[3];
676     for ( i = 0; i < (int)normals.size(); ++i ) {
677         p = normals[i];
678         normal[0] = (unsigned char)((p.x() + 1.0) * 127.5);
679         normal[1] = (unsigned char)((p.y() + 1.0) * 127.5);
680         normal[2] = (unsigned char)((p.z() + 1.0) * 127.5);
681         sgWriteBytes( fp, 3, normal );
682     }
683
684     // dump texture coordinates
685     sgWriteChar( fp, (char)SG_TEXCOORD_LIST );          // type
686     sgWriteShort( fp, 0 );                              // nproperties
687     sgWriteShort( fp, 1 );                              // nelements
688     sgWriteUInt( fp, texcoords.size() * sizeof(float) * 2 ); // nbytes
689     for ( i = 0; i < (int)texcoords.size(); ++i ) {
690         p = texcoords[i];
691         sgSetVec2( t, p.x(), p.y() );
692         sgWriteVec2( fp, t );
693     }
694
695     // dump individual triangles if they exist
696     if ( tris_v.size() > 0 ) {
697         int start = 0;
698         int end = 1;
699         string material;
700         while ( start < (int)tri_materials.size() ) {
701             // find next group
702             material = tri_materials[start];
703             while ( (end < (int)tri_materials.size()) && 
704                     (material == tri_materials[end]) )
705             {
706                 // cout << "end = " << end << endl;
707                 end++;
708             }
709             // cout << "group = " << start << " to " << end - 1 << endl;
710
711             // write group headers
712             sgWriteChar( fp, (char)SG_TRIANGLE_FACES ); // type
713             sgWriteShort( fp, 1 );                      // nproperties
714             sgWriteShort( fp, 1 );                      // nelements
715
716             sgWriteChar( fp, (char)SG_MATERIAL );       // property
717             sgWriteUInt( fp, material.length() );        // nbytes
718             sgWriteBytes( fp, material.length(), material.c_str() );
719
720             sgWriteUInt( fp, (end - start) * 3 * 2 * sizeof(short) ); // nbytes
721
722             // write group
723             for ( i = start; i < end; ++i ) {
724                 for ( j = 0; j < 3; ++j ) {
725                     sgWriteShort( fp, (short)tris_v[i][j] );
726                     sgWriteShort( fp, (short)tris_tc[i][j] );
727                 }
728             }
729
730             start = end;
731             end = start + 1;
732         }
733     }
734
735     // dump triangle strips
736     if ( strips_v.size() > 0 ) {
737         int start = 0;
738         int end = 1;
739         string material;
740         while ( start < (int)strip_materials.size() ) {
741             // find next group
742             material = strip_materials[start];
743             while ( (end < (int)strip_materials.size()) && 
744                     (material == strip_materials[end]) )
745                 {
746                     // cout << "end = " << end << endl;
747                     end++;
748                 }
749             // cout << "group = " << start << " to " << end - 1 << endl;
750
751             // write group headers
752             sgWriteChar( fp, (char)SG_TRIANGLE_STRIPS ); // type
753             sgWriteShort( fp, 1 );                       // nproperties
754             sgWriteShort( fp, end - start );             // nelements
755
756             sgWriteChar( fp, (char)SG_MATERIAL );        // property
757             sgWriteUInt( fp, material.length() );        // nbytes
758             sgWriteBytes( fp, material.length(), material.c_str() );
759
760             // write strips
761             for ( i = start; i < end; ++i ) {
762                 // nbytes
763                 sgWriteUInt( fp, strips_v[i].size() * 2 * sizeof(short) );
764                 for ( j = 0; j < (int)strips_v[i].size(); ++j ) {
765                     sgWriteShort( fp, (short)strips_v[i][j] );
766                     sgWriteShort( fp, (short)strips_tc[i][j] );
767                 }
768             }
769             
770             start = end;
771             end = start + 1;
772         }
773     }
774
775     // dump triangle fans
776     if ( fans_v.size() > 0 ) {
777         int start = 0;
778         int end = 1;
779         string material;
780         while ( start < (int)fan_materials.size() ) {
781             // find next group
782             material = fan_materials[start];
783             while ( (end < (int)fan_materials.size()) && 
784                     (material == fan_materials[end]) )
785                 {
786                     // cout << "end = " << end << endl;
787                     end++;
788                 }
789             // cout << "group = " << start << " to " << end - 1 << endl;
790
791             // write group headers
792             sgWriteChar( fp, (char)SG_TRIANGLE_FANS );   // type
793             sgWriteShort( fp, 1 );                       // nproperties
794             sgWriteShort( fp, end - start );             // nelements
795
796             sgWriteChar( fp, (char)SG_MATERIAL );       // property
797             sgWriteUInt( fp, material.length() );        // nbytes
798             sgWriteBytes( fp, material.length(), material.c_str() );
799
800             // write fans
801             for ( i = start; i < end; ++i ) {
802                 // nbytes
803                 sgWriteUInt( fp, fans_v[i].size() * 2 * sizeof(short) );
804                 for ( j = 0; j < (int)fans_v[i].size(); ++j ) {
805                     sgWriteShort( fp, (short)fans_v[i][j] );
806                     sgWriteShort( fp, (short)fans_tc[i][j] );
807                 }
808             }
809             
810             start = end;
811             end = start + 1;
812         }
813     }
814
815     // close the file
816     gzclose(fp);
817
818     if ( sgWriteError() ) {
819         cout << "We detected an error while writing the file." << endl;
820         return false;
821     }
822
823     return true;
824 }
825
826
827 // write out the structures to an ASCII file.  We assume that the
828 // groups come to us sorted by material property.  If not, things
829 // don't break, but the result won't be as optimal.
830 bool SGBinObject::write_ascii( const string& base, const string& name,
831                                const SGBucket& b )
832 {
833     Point3D p;
834     int i, j;
835
836     string dir = base + "/" + b.gen_base_path();
837     string command = "mkdir -p " + dir;
838 #if defined(_MSC_VER) || defined(__MINGW32__)
839     system( (string("mkdir ") + dir).c_str() );
840 #else
841     system(command.c_str());
842 #endif
843
844     // string file = dir + "/" + b.gen_index_str();
845     string file = dir + "/" + name;
846     cout << "Output file = " << file << endl;
847
848     FILE *fp;
849     if ( (fp = fopen( file.c_str(), "w" )) == NULL ) {
850         cout << "ERROR: opening " << file << " for writing!" << endl;
851         return false;
852     }
853
854     cout << "triangles size = " << tris_v.size() << "  tri_materials = " 
855          << tri_materials.size() << endl;
856     cout << "strips size = " << strips_v.size() << "  strip_materials = " 
857          << strip_materials.size() << endl;
858     cout << "fans size = " << fans_v.size() << "  fan_materials = " 
859          << fan_materials.size() << endl;
860
861     cout << "points = " << wgs84_nodes.size() << endl;
862     cout << "tex coords = " << texcoords.size() << endl;
863     // write headers
864     fprintf(fp, "# FGFS Scenery\n");
865     fprintf(fp, "# Version %s\n", SG_SCENERY_FILE_FORMAT);
866
867     time_t calendar_time = time(NULL);
868     struct tm *local_tm;
869     local_tm = localtime( &calendar_time );
870     char time_str[256];
871     strftime( time_str, 256, "%a %b %d %H:%M:%S %Z %Y", local_tm);
872     fprintf(fp, "# Created %s\n", time_str );
873     fprintf(fp, "\n");
874
875     // write bounding sphere
876     fprintf(fp, "# gbs %.5f %.5f %.5f %.2f\n",
877             gbs_center.x(), gbs_center.y(), gbs_center.z(), gbs_radius);
878     fprintf(fp, "\n");
879
880     // dump vertex list
881     fprintf(fp, "# vertex list\n");
882     for ( i = 0; i < (int)wgs84_nodes.size(); ++i ) {
883         p = wgs84_nodes[i] - gbs_center;
884         
885         fprintf(fp,  "v %.5f %.5f %.5f\n", p.x(), p.y(), p.z() );
886     }
887     fprintf(fp, "\n");
888
889     fprintf(fp, "# vertex normal list\n");
890     for ( i = 0; i < (int)normals.size(); ++i ) {
891         p = normals[i];
892         fprintf(fp,  "vn %.5f %.5f %.5f\n", p.x(), p.y(), p.z() );
893     }
894     fprintf(fp, "\n");
895
896     // dump texture coordinates
897     fprintf(fp, "# texture coordinate list\n");
898     for ( i = 0; i < (int)texcoords.size(); ++i ) {
899         p = texcoords[i];
900         fprintf(fp,  "vt %.5f %.5f\n", p.x(), p.y() );
901     }
902     fprintf(fp, "\n");
903
904     // dump individual triangles if they exist
905     if ( tris_v.size() > 0 ) {
906         fprintf(fp, "# triangle groups\n");
907
908         int start = 0;
909         int end = 1;
910         string material;
911         while ( start < (int)tri_materials.size() ) {
912             // find next group
913             material = tri_materials[start];
914             while ( (end < (int)tri_materials.size()) && 
915                     (material == tri_materials[end]) )
916             {
917                 // cout << "end = " << end << endl;
918                 end++;
919             }
920             // cout << "group = " << start << " to " << end - 1 << endl;
921
922             // make a list of points for the group
923             point_list group_nodes;
924             group_nodes.clear();
925             Point3D bs_center;
926             double bs_radius = 0;
927             for ( i = start; i < end; ++i ) {
928                 for ( j = 0; j < (int)tris_v[i].size(); ++j ) {
929                     group_nodes.push_back( wgs84_nodes[ tris_v[i][j] ] );
930                     bs_center = calc_center( group_nodes );
931                     bs_radius = sgCalcBoundingRadius( bs_center, group_nodes );
932                 }
933             }
934
935             // write group headers
936             fprintf(fp, "\n");
937             fprintf(fp, "# usemtl %s\n", material.c_str());
938             fprintf(fp, "# bs %.4f %.4f %.4f %.2f\n",
939                     bs_center.x(), bs_center.y(), bs_center.z(), bs_radius);
940
941             // write groups
942             for ( i = start; i < end; ++i ) {
943                 fprintf(fp, "f");
944                 for ( j = 0; j < (int)tris_v[i].size(); ++j ) {
945                     fprintf(fp, " %d/%d", tris_v[i][j], tris_tc[i][j] );
946                 }
947                 fprintf(fp, "\n");
948             }
949
950             start = end;
951             end = start + 1;
952         }
953     }
954
955     // dump triangle groups
956     if ( strips_v.size() > 0 ) {
957         fprintf(fp, "# triangle strips\n");
958
959         int start = 0;
960         int end = 1;
961         string material;
962         while ( start < (int)strip_materials.size() ) {
963             // find next group
964             material = strip_materials[start];
965             while ( (end < (int)strip_materials.size()) && 
966                     (material == strip_materials[end]) )
967                 {
968                     // cout << "end = " << end << endl;
969                     end++;
970                 }
971             // cout << "group = " << start << " to " << end - 1 << endl;
972
973             // make a list of points for the group
974             point_list group_nodes;
975             group_nodes.clear();
976             Point3D bs_center;
977             double bs_radius = 0;
978             for ( i = start; i < end; ++i ) {
979                 for ( j = 0; j < (int)strips_v[i].size(); ++j ) {
980                     group_nodes.push_back( wgs84_nodes[ strips_v[i][j] ] );
981                     bs_center = calc_center( group_nodes );
982                     bs_radius = sgCalcBoundingRadius( bs_center, group_nodes );
983                 }
984             }
985
986             // write group headers
987             fprintf(fp, "\n");
988             fprintf(fp, "# usemtl %s\n", material.c_str());
989             fprintf(fp, "# bs %.4f %.4f %.4f %.2f\n",
990                     bs_center.x(), bs_center.y(), bs_center.z(), bs_radius);
991
992             // write groups
993             for ( i = start; i < end; ++i ) {
994                 fprintf(fp, "ts");
995                 for ( j = 0; j < (int)strips_v[i].size(); ++j ) {
996                     fprintf(fp, " %d/%d", strips_v[i][j], strips_tc[i][j] );
997                 }
998                 fprintf(fp, "\n");
999             }
1000             
1001             start = end;
1002             end = start + 1;
1003         }
1004     }
1005
1006     // close the file
1007     fclose(fp);
1008
1009     command = "gzip --force --best " + file;
1010     system(command.c_str());
1011
1012     return true;
1013 }