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