]> git.mxchange.org Git - simgear.git/blob - simgear/io/sg_binobj.cxx
73403d3734362a2230bd8a99bd5155ce2d00b513
[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_COLOR_LIST = 4,
53     SG_NORMAL_LIST = 2,
54     SG_TEXCOORD_LIST = 3,
55
56     SG_POINTS = 9,
57
58     SG_TRIANGLE_FACES = 10,
59     SG_TRIANGLE_STRIPS = 11,
60     SG_TRIANGLE_FANS = 12
61 } sgObjectTypes;
62
63 enum {
64     SG_IDX_VERTICES =  0x0001,
65     SG_IDX_NORMALS =   0x0010,
66     SG_IDX_COLORS =    0x0100,
67     SG_IDX_TEXCOORDS = 0x1000
68 } sgIndexTypes;
69
70 enum {
71     SG_MATERIAL = 0,
72     SG_INDEX_TYPES = 1
73 } sgPropertyTypes;
74
75
76
77 class sgSimpleBuffer {
78
79 private:
80
81     char *ptr;
82     unsigned int size;
83
84 public:
85
86     inline sgSimpleBuffer( unsigned int s )
87     {
88         size = 1;
89         while ( size < s ) {
90             size *= 2;
91         }
92         cout << "Creating a new buffer of size = " << size << endl;
93         ptr = new char[size];
94     }
95
96     inline ~sgSimpleBuffer() {
97         delete [] ptr;
98     }
99
100     inline unsigned int get_size() const { return size; }
101     inline char *get_ptr() const { return ptr; }
102     inline void resize( unsigned int s ) {
103         if ( s > size ) {
104             if ( ptr != NULL ) {
105                 delete [] ptr;
106             }
107             while ( size < s ) {
108                 size *= 2;
109             }
110             cout << "resizing buffer to size = " << size << endl;
111             ptr = new char[size];
112         }
113     }
114 };
115
116
117 // calculate the center of a list of points, by taking the halfway
118 // point between the min and max points.
119 static Point3D calc_center( point_list& wgs84_nodes ) {
120     Point3D p, min, max;
121
122     if ( wgs84_nodes.size() ) {
123         min = max = wgs84_nodes[0];
124     } else {
125         min = max = Point3D( 0 );
126     }
127
128     for ( int i = 0; i < (int)wgs84_nodes.size(); ++i ) {
129         p = wgs84_nodes[i];
130
131         if ( p.x() < min.x() ) { min.setx( p.x() ); }
132         if ( p.y() < min.y() ) { min.sety( p.y() ); }
133         if ( p.z() < min.z() ) { min.setz( p.z() ); }
134
135         if ( p.x() > max.x() ) { max.setx( p.x() ); }
136         if ( p.y() > max.y() ) { max.sety( p.y() ); }
137         if ( p.z() > max.z() ) { max.setz( p.z() ); }
138     }
139
140     return ( min + max ) / 2.0;
141 }
142
143 // calculate the bounding sphere.  Center is the center of the
144 // tile and zero elevation
145 double sgCalcBoundingRadius( Point3D center, point_list& wgs84_nodes ) {
146     double dist_squared;
147     double radius_squared = 0;
148     
149     for ( int i = 0; i < (int)wgs84_nodes.size(); ++i ) {
150         dist_squared = center.distance3Dsquared( wgs84_nodes[i] );
151         if ( dist_squared > radius_squared ) {
152             radius_squared = dist_squared;
153         }
154     }
155
156     return sqrt(radius_squared);
157 }
158
159
160
161 // read object properties
162 static void read_object( gzFile fp,
163                          int obj_type,
164                          int nproperties,
165                          int nelements,
166                          group_list *vertices,
167                          group_list *normals,
168                          group_list *colors,
169                          group_list *texcoords,
170                          string_list *materials )
171 {
172     unsigned int nbytes;
173     char idx_mask;
174     int idx_size;
175     bool do_vertices, do_normals, do_colors, do_texcoords;
176     int j, k, idx;
177     static sgSimpleBuffer buf( 32768 );  // 32 Kb
178     char material[256];
179
180     // default values
181     if ( obj_type == SG_POINTS ) {
182         idx_size = 1;
183         idx_mask = SG_IDX_VERTICES;
184         do_vertices = true;
185         do_normals = false;
186         do_colors = false;
187         do_texcoords = false;
188     } else {
189         idx_size = 2;
190         idx_mask = (char)(SG_IDX_VERTICES | SG_IDX_TEXCOORDS);
191         do_vertices = true;
192         do_normals = false;
193         do_colors = false;
194         do_texcoords = true;
195     }
196
197     for ( j = 0; j < nproperties; ++j ) {
198         char prop_type;
199         sgReadChar( fp, &prop_type );
200
201         sgReadUInt( fp, &nbytes );
202         // cout << "property size = " << nbytes << endl;
203         if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
204         char *ptr = buf.get_ptr();
205         sgReadBytes( fp, nbytes, ptr );
206         if ( prop_type == SG_MATERIAL ) {
207             strncpy( material, ptr, nbytes );
208             material[nbytes] = '\0';
209             // cout << "material type = " << material << endl;
210         } else if ( prop_type == SG_INDEX_TYPES ) {
211             idx_mask = ptr[0];
212             idx_size = 0;
213             do_vertices = false;
214             do_normals = false;
215             do_colors = false;
216             do_texcoords = false;
217             if ( idx_mask & SG_IDX_VERTICES ) {
218                 do_vertices = true;
219                 ++idx_size;
220             }
221             if ( idx_mask & SG_IDX_NORMALS ) {
222                 do_normals = true;
223                 ++idx_size;
224             }
225             if ( idx_mask & SG_IDX_COLORS ) {
226                 do_colors = true;
227                 ++idx_size;
228             }
229             if ( idx_mask & SG_IDX_TEXCOORDS ) {
230                 do_texcoords = true;
231                 ++idx_size;
232             }
233         }
234     }
235
236     for ( j = 0; j < nelements; ++j ) {
237         sgReadUInt( fp, &nbytes );
238         // cout << "element size = " << nbytes << endl;
239         if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
240         char *ptr = buf.get_ptr();
241         sgReadBytes( fp, nbytes, ptr );
242         int count = nbytes / (idx_size * sizeof(short));
243         short *sptr = (short *)ptr;
244         int_list vs; vs.clear();
245         int_list ns; ns.clear();
246         int_list cs; cs.clear();
247         int_list tcs; tcs.clear();
248         for ( k = 0; k < count; ++k ) {
249             if ( sgIsBigEndian() ) {
250                 for ( idx = 0; idx < idx_size; ++idx ) {
251                     sgEndianSwap( (unsigned short *)&(sptr[idx]) );
252                 }
253             }
254             idx = 0;
255             if ( do_vertices ) {
256                 vs.push_back( sptr[idx++] );
257             }
258             if ( do_normals ) {
259                 ns.push_back( sptr[idx++] );
260                     }
261             if ( do_colors ) {
262                 cs.push_back( sptr[idx++] );
263             }
264             if ( do_texcoords ) {
265                 tcs.push_back( sptr[idx++] );
266             }
267             // cout << sptr[0] << " ";
268             sptr += idx_size;
269         }
270         // cout << endl;
271         vertices->push_back( vs );
272         normals->push_back( ns );
273         colors->push_back( cs );
274         texcoords->push_back( tcs );
275         materials->push_back( material );
276     }
277 }
278
279
280 // read a binary file and populate the provided structures.
281 bool SGBinObject::read_bin( const string& file ) {
282     Point3D p;
283     int i, j, k;
284     unsigned int nbytes;
285     static sgSimpleBuffer buf( 32768 );  // 32 Kb
286
287     // zero out structures
288     gbs_center = Point3D( 0 );
289     gbs_radius = 0.0;
290
291     wgs84_nodes.clear();
292     normals.clear();
293     texcoords.clear();
294
295     pts_v.clear();
296     pts_n.clear();
297     pts_c.clear();
298     pts_tc.clear();
299     pt_materials.clear();
300
301     tris_v.clear();
302     tris_n.clear();
303     tris_c.clear();
304     tris_tc.clear();
305     tri_materials.clear();
306
307     strips_v.clear();
308     strips_n.clear();
309     strips_c.clear();
310     strips_tc.clear();
311     strip_materials.clear();
312
313     fans_v.clear();
314     fans_n.clear();
315     fans_c.clear();
316     fans_tc.clear();
317     fan_materials.clear();
318    
319     gzFile fp;
320     if ( (fp = gzopen( file.c_str(), "rb" )) == NULL ) {
321         string filegz = file + ".gz";
322         if ( (fp = gzopen( filegz.c_str(), "rb" )) == NULL ) {
323             cout << "ERROR: opening " << file << " or " << filegz
324                  << "for reading!" << endl;
325
326             return false;
327         }
328     }
329
330     sgClearReadError();
331
332     // read headers
333     unsigned int header;
334     sgReadUInt( fp, &header );
335     if ( ((header & 0xFF000000) >> 24) == 'S' &&
336          ((header & 0x00FF0000) >> 16) == 'G' ) {
337         // cout << "Good header" << endl;
338         // read file version
339         version = (header & 0x0000FFFF);
340         // cout << "File version = " << version << endl;
341     } else {
342         // close the file before we return
343         gzclose(fp);
344
345         return false;
346     }
347
348     // read creation time
349     time_t calendar_time;
350     sgReadLong( fp, &calendar_time );
351
352 #if 0
353     // The following code has a global effect on the host application
354     // and can screws up the time elsewhere.  It should be avoided
355     // unless you need this for debugging in which case you should
356     // disable it again once the debugging task is finished.
357     struct tm *local_tm;
358     local_tm = localtime( &calendar_time );
359     char time_str[256];
360     strftime( time_str, 256, "%a %b %d %H:%M:%S %Z %Y", local_tm);
361     cout << "File created on " << time_str << endl;
362 #endif
363
364     // read number of top level objects
365     short nobjects;
366     sgReadShort( fp, &nobjects );
367     // cout << "Total objects to read = " << nobjects << endl;
368
369     // read in objects
370     for ( i = 0; i < nobjects; ++i ) {
371         // read object header
372         char obj_type;
373         short nproperties, nelements;
374         sgReadChar( fp, &obj_type );
375         sgReadShort( fp, &nproperties );
376         sgReadShort( fp, &nelements );
377
378         // cout << "object " << i << " = " << (int)obj_type << " props = "
379         //      << nproperties << " elements = " << nelements << endl;
380             
381         if ( obj_type == SG_BOUNDING_SPHERE ) {
382             // read bounding sphere properties
383             for ( j = 0; j < nproperties; ++j ) {
384                 char prop_type;
385                 sgReadChar( fp, &prop_type );
386
387                 sgReadUInt( fp, &nbytes );
388                 // cout << "property size = " << nbytes << endl;
389                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
390                 char *ptr = buf.get_ptr();
391                 sgReadBytes( fp, nbytes, ptr );
392             }
393
394             // read bounding sphere elements
395             for ( j = 0; j < nelements; ++j ) {
396                 sgReadUInt( fp, &nbytes );
397                 // cout << "element size = " << nbytes << endl;
398                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
399                 char *ptr = buf.get_ptr();
400                 sgReadBytes( fp, nbytes, ptr );
401
402                 double *dptr = (double *)ptr;
403                 if ( sgIsBigEndian() ) {
404                     sgEndianSwap( (uint64 *)&(dptr[0]) );
405                     sgEndianSwap( (uint64 *)&(dptr[1]) );
406                     sgEndianSwap( (uint64 *)&(dptr[2]) );
407                 }
408                 gbs_center = Point3D( dptr[0], dptr[1], dptr[2] );
409                 // cout << "Center = " << gbs_center << endl;
410                 ptr += sizeof(double) * 3;
411                 
412                 float *fptr = (float *)ptr;
413                 if ( sgIsBigEndian() ) {
414                     sgEndianSwap( (unsigned int *)fptr );
415                 }
416                 gbs_radius = fptr[0];
417                 // cout << "Bounding radius = " << gbs_radius << endl;
418             }
419         } else if ( obj_type == SG_VERTEX_LIST ) {
420             // read vertex list properties
421             for ( j = 0; j < nproperties; ++j ) {
422                 char prop_type;
423                 sgReadChar( fp, &prop_type );
424
425                 sgReadUInt( fp, &nbytes );
426                 // cout << "property size = " << nbytes << endl;
427                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
428                 char *ptr = buf.get_ptr();
429                 sgReadBytes( fp, nbytes, ptr );
430             }
431
432             // read vertex list elements
433             for ( j = 0; j < nelements; ++j ) {
434                 sgReadUInt( fp, &nbytes );
435                 // cout << "element size = " << nbytes << endl;
436                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
437                 char *ptr = buf.get_ptr();
438                 sgReadBytes( fp, nbytes, ptr );
439                 int count = nbytes / (sizeof(float) * 3);
440                 float *fptr = (float *)ptr;
441                 for ( k = 0; k < count; ++k ) {
442                     if ( sgIsBigEndian() ) {
443                         sgEndianSwap( (unsigned int *)&(fptr[0]) );
444                         sgEndianSwap( (unsigned int *)&(fptr[1]) );
445                         sgEndianSwap( (unsigned int *)&(fptr[2]) );
446                     }
447                     p = Point3D( fptr[0], fptr[1], fptr[2] );
448                     // cout << "node = " << p << endl;
449                     wgs84_nodes.push_back( p );
450                     fptr += 3;
451                 }
452             }
453         } else if ( obj_type == SG_COLOR_LIST ) {
454             // read color list properties
455             for ( j = 0; j < nproperties; ++j ) {
456                 char prop_type;
457                 sgReadChar( fp, &prop_type );
458
459                 sgReadUInt( fp, &nbytes );
460                 // cout << "property size = " << nbytes << endl;
461                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
462                 char *ptr = buf.get_ptr();
463                 sgReadBytes( fp, nbytes, ptr );
464             }
465
466             // read color list elements
467             for ( j = 0; j < nelements; ++j ) {
468                 sgReadUInt( fp, &nbytes );
469                 // cout << "element size = " << nbytes << endl;
470                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
471                 char *ptr = buf.get_ptr();
472                 sgReadBytes( fp, nbytes, ptr );
473                 int count = nbytes / (sizeof(float) * 4);
474                 float *fptr = (float *)ptr;
475                 for ( k = 0; k < count; ++k ) {
476                     if ( sgIsBigEndian() ) {
477                         sgEndianSwap( (unsigned int *)&(fptr[0]) );
478                         sgEndianSwap( (unsigned int *)&(fptr[1]) );
479                         sgEndianSwap( (unsigned int *)&(fptr[2]) );
480                         sgEndianSwap( (unsigned int *)&(fptr[3]) );
481                     }
482                     p = Point3D( fptr[0], fptr[1], fptr[2] );
483                     // cout << "node = " << p << endl;
484                     colors.push_back( p );
485                     fptr += 4;
486                 }
487             }
488         } else if ( obj_type == SG_NORMAL_LIST ) {
489             // read normal list properties
490             for ( j = 0; j < nproperties; ++j ) {
491                 char prop_type;
492                 sgReadChar( fp, &prop_type );
493
494                 sgReadUInt( fp, &nbytes );
495                 // cout << "property size = " << nbytes << endl;
496                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
497                 char *ptr = buf.get_ptr();
498                 sgReadBytes( fp, nbytes, ptr );
499             }
500
501             // read normal list elements
502             for ( j = 0; j < nelements; ++j ) {
503                 sgReadUInt( fp, &nbytes );
504                 // cout << "element size = " << nbytes << endl;
505                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
506                 unsigned char *ptr = (unsigned char *)(buf.get_ptr());
507                 sgReadBytes( fp, nbytes, ptr );
508                 int count = nbytes / 3;
509                 for ( k = 0; k < count; ++k ) {
510                     sgdVec3 normal;
511                     sgdSetVec3( normal,
512                                (ptr[0]) / 127.5 - 1.0,
513                                (ptr[1]) / 127.5 - 1.0,
514                                (ptr[2]) / 127.5 - 1.0 );
515                     sgdNormalizeVec3( normal );
516
517                     p = Point3D( normal[0], normal[1], normal[2] );
518                     // cout << "normal = " << p << endl;
519                     normals.push_back( p );
520                     ptr += 3;
521                 }
522             }
523         } else if ( obj_type == SG_TEXCOORD_LIST ) {
524             // read texcoord list properties
525             for ( j = 0; j < nproperties; ++j ) {
526                 char prop_type;
527                 sgReadChar( fp, &prop_type );
528
529                 sgReadUInt( fp, &nbytes );
530                 // cout << "property size = " << nbytes << endl;
531                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
532                 char *ptr = buf.get_ptr();
533                 sgReadBytes( fp, nbytes, ptr );
534             }
535
536             // read texcoord list elements
537             for ( j = 0; j < nelements; ++j ) {
538                 sgReadUInt( fp, &nbytes );
539                 // cout << "element size = " << nbytes << endl;
540                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
541                 char *ptr = buf.get_ptr();
542                 sgReadBytes( fp, nbytes, ptr );
543                 int count = nbytes / (sizeof(float) * 2);
544                 float *fptr = (float *)ptr;
545                 for ( k = 0; k < count; ++k ) {
546                     if ( sgIsBigEndian() ) {
547                         sgEndianSwap( (unsigned int *)&(fptr[0]) );
548                         sgEndianSwap( (unsigned int *)&(fptr[1]) );
549                     }
550                     p = Point3D( fptr[0], fptr[1], 0 );
551                     // cout << "texcoord = " << p << endl;
552                     texcoords.push_back( p );
553                     fptr += 2;
554                 }
555             }
556         } else if ( obj_type == SG_POINTS ) {
557             // read point elements
558             read_object( fp, SG_POINTS, nproperties, nelements,
559                          &pts_v, &pts_n, &pts_c, &pts_tc, &pt_materials );
560         } else if ( obj_type == SG_TRIANGLE_FACES ) {
561             // read triangle face properties
562             read_object( fp, SG_TRIANGLE_FACES, nproperties, nelements,
563                          &tris_v, &tris_n, &tris_c, &tris_tc, &tri_materials );
564         } else if ( obj_type == SG_TRIANGLE_STRIPS ) {
565             // read triangle strip properties
566             read_object( fp, SG_TRIANGLE_STRIPS, nproperties, nelements,
567                          &strips_v, &strips_n, &strips_c, &strips_tc,
568                          &strip_materials );
569 #if 0
570             // default values
571             idx_size = 2;
572             idx_mask = (char)(SG_IDX_VERTICES | SG_IDX_TEXCOORDS);
573             do_vertices = true;
574             do_normals = false;
575             do_colors = false;
576             do_texcoords = true;
577
578             for ( j = 0; j < nproperties; ++j ) {
579                 char prop_type;
580                 sgReadChar( fp, &prop_type );
581
582                 sgReadUInt( fp, &nbytes );
583                 // cout << "property size = " << nbytes << endl;
584                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
585                 char *ptr = buf.get_ptr();
586                 sgReadBytes( fp, nbytes, ptr );
587                 if ( prop_type == SG_MATERIAL ) {
588                     strncpy( material, ptr, nbytes );
589                     material[nbytes] = '\0';
590                     // cout << "material type = " << material << endl;
591                 }
592             }
593
594             // read triangle strip elements
595             for ( j = 0; j < nelements; ++j ) {
596                 sgReadUInt( fp, &nbytes );
597                 // cout << "element size = " << nbytes << endl;
598                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
599                 char *ptr = buf.get_ptr();
600                 sgReadBytes( fp, nbytes, ptr );
601                 int count = nbytes / (sizeof(short) * 2);
602                 short *sptr = (short *)ptr;
603                 int_list vs, tcs;
604                 vs.clear(); tcs.clear();
605                 for ( k = 0; k < count; ++k ) {
606                     if ( sgIsBigEndian() ) {
607                         sgEndianSwap( (unsigned short *)&(sptr[0]) );
608                         sgEndianSwap( (unsigned short *)&(sptr[1]) );
609                     }
610                     vs.push_back( sptr[0] );
611                     tcs.push_back( sptr[1] );
612                     // cout << sptr[0] << "/" << sptr[1] << " ";
613                     sptr += idx_size;
614                 }
615                 // cout << endl;
616                 strips_v.push_back( vs );
617                 strips_tc.push_back( tcs );
618                 strip_materials.push_back( material );
619             }
620 #endif
621         } else if ( obj_type == SG_TRIANGLE_FANS ) {
622             // read triangle fan properties
623             read_object( fp, SG_TRIANGLE_FANS, nproperties, nelements,
624                          &fans_v, &fans_n, &fans_c, &fans_tc, &fan_materials );
625 #if 0
626             // default values
627             idx_size = 2;
628             idx_mask = (char)(SG_IDX_VERTICES | SG_IDX_TEXCOORDS);
629             do_vertices = true;
630             do_normals = false;
631             do_colors = false;
632             do_texcoords = true;
633
634             for ( j = 0; j < nproperties; ++j ) {
635                 char prop_type;
636                 sgReadChar( fp, &prop_type );
637
638                 sgReadUInt( fp, &nbytes );
639                 // cout << "property size = " << nbytes << endl;
640                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
641                 char *ptr = buf.get_ptr();
642                 sgReadBytes( fp, nbytes, ptr );
643                 if ( prop_type == SG_MATERIAL ) {
644                     strncpy( material, ptr, nbytes );
645                     material[nbytes] = '\0';
646                     // cout << "material type = " << material << endl;
647                 }
648             }
649
650             // read triangle fan elements
651             for ( j = 0; j < nelements; ++j ) {
652                 sgReadUInt( fp, &nbytes );
653                 // cout << "element size = " << nbytes << endl;
654                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
655                 char *ptr = buf.get_ptr();
656                 sgReadBytes( fp, nbytes, ptr );
657                 int count = nbytes / (sizeof(short) * 2);
658                 short *sptr = (short *)ptr;
659                 int_list vs, tcs;
660                 vs.clear(); tcs.clear();
661                 for ( k = 0; k < count; ++k ) {
662                     if ( sgIsBigEndian() ) {
663                         sgEndianSwap( (unsigned short *)&(sptr[0]) );
664                         sgEndianSwap( (unsigned short *)&(sptr[1]) );
665                     }
666                     vs.push_back( sptr[0] );
667                     tcs.push_back( sptr[1] );
668                     // cout << sptr[0] << "/" << sptr[1] << " ";
669                     sptr += idx_size;
670                 }
671                 // cout << endl;
672                 fans_v.push_back( vs );
673                 fans_tc.push_back( tcs );
674                 fan_materials.push_back( material );
675             }
676 #endif
677         } else {
678             // unknown object type, just skip
679
680             // read properties
681             for ( j = 0; j < nproperties; ++j ) {
682                 char prop_type;
683                 sgReadChar( fp, &prop_type );
684
685                 sgReadUInt( fp, &nbytes );
686                 // cout << "property size = " << nbytes << endl;
687                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
688                 char *ptr = buf.get_ptr();
689                 sgReadBytes( fp, nbytes, ptr );
690             }
691
692             // read elements
693             for ( j = 0; j < nelements; ++j ) {
694                 sgReadUInt( fp, &nbytes );
695                 // cout << "element size = " << nbytes << endl;
696                 if ( nbytes > buf.get_size() ) { buf.resize( nbytes ); }
697                 char *ptr = buf.get_ptr();
698                 sgReadBytes( fp, nbytes, ptr );
699             }
700         }
701     }
702
703     // close the file
704     gzclose(fp);
705
706     if ( sgReadError() ) {
707         cout << "We detected an error while reading the file." << endl;
708         return false;
709     }
710
711     return true;
712 }
713
714
715 // write out the structures to a binary file.  We assume that the
716 // groups come to us sorted by material property.  If not, things
717 // don't break, but the result won't be as optimal.
718 bool SGBinObject::write_bin( const string& base, const string& name,
719                              const SGBucket& b )
720 {
721     Point3D p;
722     sgVec2 t;
723     sgVec3 pt;
724     sgVec4 color;
725     int i, j;
726     char idx_mask;
727     int idx_size;
728
729     string dir = base + "/" + b.gen_base_path();
730     string command = "mkdir -p " + dir;
731 #if defined(_MSC_VER) || defined(__MINGW32__)
732     system( (string("mkdir ") + dir).c_str() );
733 #else
734     system(command.c_str());
735 #endif
736
737     string file = dir + "/" + name + ".gz";
738     cout << "Output file = " << file << endl;
739
740     gzFile fp;
741     if ( (fp = gzopen( file.c_str(), "wb9" )) == NULL ) {
742         cout << "ERROR: opening " << file << " for writing!" << endl;
743         return false;
744     }
745
746     sgClearWriteError();
747
748     cout << "points size = " << pts_v.size() << "  pt_materials = " 
749          << pt_materials.size() << endl;
750     cout << "triangles size = " << tris_v.size() << "  tri_materials = " 
751          << tri_materials.size() << endl;
752     cout << "strips size = " << strips_v.size() << "  strip_materials = " 
753          << strip_materials.size() << endl;
754     cout << "fans size = " << fans_v.size() << "  fan_materials = " 
755          << fan_materials.size() << endl;
756
757     cout << "nodes = " << wgs84_nodes.size() << endl;
758     cout << "colors = " << colors.size() << endl;
759     cout << "normals = " << normals.size() << endl;
760     cout << "tex coords = " << texcoords.size() << endl;
761
762     // write header magic
763     sgWriteUInt( fp, SG_FILE_MAGIC_NUMBER );
764     time_t calendar_time = time(NULL);
765     sgWriteLong( fp, (long int)calendar_time );
766
767     // calculate and write number of top level objects
768     string material;
769     int start;
770     int end;
771     short nobjects = 0;
772     nobjects++;                 // for gbs
773     nobjects++;                 // for vertices
774     nobjects++;                 // for colors
775     nobjects++;                 // for normals
776     nobjects++;                 // for texcoords
777
778     // points
779     short npts = 0;
780     start = 0; end = 1;
781     while ( start < (int)pt_materials.size() ) {
782         material = pt_materials[start];
783         while ( (end < (int)pt_materials.size()) &&
784                 (material == pt_materials[end]) ) {
785             end++;
786         }
787         npts++;
788         start = end; end = start + 1;
789     }
790     nobjects += npts;
791
792     // tris
793     short ntris = 0;
794     start = 0; end = 1;
795     while ( start < (int)tri_materials.size() ) {
796         material = tri_materials[start];
797         while ( (end < (int)tri_materials.size()) &&
798                 (material == tri_materials[end]) ) {
799             end++;
800         }
801         ntris++;
802         start = end; end = start + 1;
803     }
804     nobjects += ntris;
805
806     // strips
807     short nstrips = 0;
808     start = 0; end = 1;
809     while ( start < (int)strip_materials.size() ) {
810         material = strip_materials[start];
811         while ( (end < (int)strip_materials.size()) &&
812                 (material == strip_materials[end]) ) {
813             end++;
814         }
815         nstrips++;
816         start = end; end = start + 1;
817     }
818     nobjects += nstrips;
819
820     // fans
821     short nfans = 0;
822     start = 0; end = 1;
823     while ( start < (int)fan_materials.size() ) {
824         material = fan_materials[start];
825         while ( (end < (int)fan_materials.size()) &&
826                 (material == fan_materials[end]) ) {
827             end++;
828         }
829         nfans++;
830         start = end; end = start + 1;
831     }
832     nobjects += nfans;
833
834     cout << "total top level objects = " << nobjects << endl;
835     sgWriteShort( fp, nobjects );
836
837     // write bounding sphere
838     sgWriteChar( fp, (char)SG_BOUNDING_SPHERE );        // type
839     sgWriteShort( fp, 0 );                              // nproperties
840     sgWriteShort( fp, 1 );                              // nelements
841
842     sgWriteUInt( fp, sizeof(double) * 3 + sizeof(float) ); // nbytes
843     sgdVec3 center;
844     sgdSetVec3( center, gbs_center.x(), gbs_center.y(), gbs_center.z() );
845     sgWritedVec3( fp, center );
846     sgWriteFloat( fp, gbs_radius );
847
848     // dump vertex list
849     sgWriteChar( fp, (char)SG_VERTEX_LIST );             // type
850     sgWriteShort( fp, 0 );                               // nproperties
851     sgWriteShort( fp, 1 );                               // nelements
852     sgWriteUInt( fp, wgs84_nodes.size() * sizeof(float) * 3 ); // nbytes
853     for ( i = 0; i < (int)wgs84_nodes.size(); ++i ) {
854         p = wgs84_nodes[i] - gbs_center;
855         sgSetVec3( pt, p.x(), p.y(), p.z() );
856         sgWriteVec3( fp, pt );
857     }
858
859     // dump vertex color list
860     sgWriteChar( fp, (char)SG_COLOR_LIST );             // type
861     sgWriteShort( fp, 0 );                               // nproperties
862     sgWriteShort( fp, 1 );                               // nelements
863     sgWriteUInt( fp, colors.size() * sizeof(float) * 4 ); // nbytes
864     for ( i = 0; i < (int)colors.size(); ++i ) {
865         p = colors[i];
866         // Right now we have a place holder for color alpha but we
867         // need to update the interface so the calling program can
868         // provide the info.
869         sgSetVec4( color, p.x(), p.y(), p.z(), 1.0 );
870         sgWriteVec4( fp, color );
871     }
872
873     // dump vertex normal list
874     sgWriteChar( fp, (char)SG_NORMAL_LIST );            // type
875     sgWriteShort( fp, 0 );                              // nproperties
876     sgWriteShort( fp, 1 );                              // nelements
877     sgWriteUInt( fp, normals.size() * 3 );              // nbytes
878     char normal[3];
879     for ( i = 0; i < (int)normals.size(); ++i ) {
880         p = normals[i];
881         normal[0] = (unsigned char)((p.x() + 1.0) * 127.5);
882         normal[1] = (unsigned char)((p.y() + 1.0) * 127.5);
883         normal[2] = (unsigned char)((p.z() + 1.0) * 127.5);
884         sgWriteBytes( fp, 3, normal );
885     }
886
887     // dump texture coordinates
888     sgWriteChar( fp, (char)SG_TEXCOORD_LIST );          // type
889     sgWriteShort( fp, 0 );                              // nproperties
890     sgWriteShort( fp, 1 );                              // nelements
891     sgWriteUInt( fp, texcoords.size() * sizeof(float) * 2 ); // nbytes
892     for ( i = 0; i < (int)texcoords.size(); ++i ) {
893         p = texcoords[i];
894         sgSetVec2( t, p.x(), p.y() );
895         sgWriteVec2( fp, t );
896     }
897
898     // dump point groups if they exist
899     if ( pts_v.size() > 0 ) {
900         int start = 0;
901         int end = 1;
902         string material;
903         while ( start < (int)pt_materials.size() ) {
904             // find next group
905             material = pt_materials[start];
906             while ( (end < (int)pt_materials.size()) && 
907                     (material == pt_materials[end]) )
908                 {
909                     // cout << "end = " << end << endl;
910                     end++;
911                 }
912             // cout << "group = " << start << " to " << end - 1 << endl;
913
914             // write group headers
915             sgWriteChar( fp, (char)SG_POINTS );          // type
916             sgWriteShort( fp, 2 );                       // nproperties
917             sgWriteShort( fp, end - start );             // nelements
918
919             sgWriteChar( fp, (char)SG_MATERIAL );        // property
920             sgWriteUInt( fp, material.length() );        // nbytes
921             sgWriteBytes( fp, material.length(), material.c_str() );
922
923             idx_mask = 0;
924             idx_size = 0;
925             if ( pts_v.size() ) { idx_mask |= SG_IDX_VERTICES; ++idx_size; }
926             if ( pts_n.size() ) { idx_mask |= SG_IDX_NORMALS; ++idx_size; }
927             if ( pts_c.size() ) { idx_mask |= SG_IDX_COLORS; ++idx_size; }
928             if ( pts_tc.size() ) { idx_mask |= SG_IDX_TEXCOORDS; ++idx_size; }
929             sgWriteChar( fp, (char)SG_INDEX_TYPES );     // property
930             sgWriteUInt( fp, 1 );                        // nbytes
931             sgWriteChar( fp, idx_mask );
932
933             // write strips
934             for ( i = start; i < end; ++i ) {
935                 // nbytes
936                 sgWriteUInt( fp, pts_v[i].size() * idx_size * sizeof(short) );
937                 for ( j = 0; j < (int)pts_v[i].size(); ++j ) {
938                     if ( pts_v.size() ) { 
939                         sgWriteShort( fp, (short)pts_v[i][j] );
940                     }
941                     if ( pts_n.size() ) { 
942                         sgWriteShort( fp, (short)pts_n[i][j] );
943                     }
944                     if ( pts_c.size() ) { 
945                         sgWriteShort( fp, (short)pts_c[i][j] );
946                     }
947                     if ( pts_tc.size() ) { 
948                         sgWriteShort( fp, (short)pts_tc[i][j] );
949                     }
950                 }
951             }
952             
953             start = end;
954             end = start + 1;
955         }
956     }
957
958     // dump individual triangles if they exist
959     if ( tris_v.size() > 0 ) {
960         int start = 0;
961         int end = 1;
962         string material;
963         while ( start < (int)tri_materials.size() ) {
964             // find next group
965             material = tri_materials[start];
966             while ( (end < (int)tri_materials.size()) && 
967                     (material == tri_materials[end]) )
968             {
969                 // cout << "end = " << end << endl;
970                 end++;
971             }
972             // cout << "group = " << start << " to " << end - 1 << endl;
973
974             // write group headers
975             sgWriteChar( fp, (char)SG_TRIANGLE_FACES ); // type
976             sgWriteShort( fp, 2 );                      // nproperties
977             sgWriteShort( fp, 1 );                      // nelements
978
979             sgWriteChar( fp, (char)SG_MATERIAL );       // property
980             sgWriteUInt( fp, material.length() );        // nbytes
981             sgWriteBytes( fp, material.length(), material.c_str() );
982
983             idx_mask = 0;
984             idx_size = 0;
985             if ( tris_v.size() ) { idx_mask |= SG_IDX_VERTICES; ++idx_size; }
986             if ( tris_n.size() ) { idx_mask |= SG_IDX_NORMALS; ++idx_size; }
987             if ( tris_c.size() ) { idx_mask |= SG_IDX_COLORS; ++idx_size; }
988             if ( tris_tc.size() ) { idx_mask |= SG_IDX_TEXCOORDS; ++idx_size; }
989             sgWriteChar( fp, (char)SG_INDEX_TYPES );     // property
990             sgWriteUInt( fp, 1 );                        // nbytes
991             sgWriteChar( fp, idx_mask );
992
993             // nbytes
994             sgWriteUInt( fp, (end - start) * 3 * idx_size * sizeof(short) );
995
996             // write group
997             for ( i = start; i < end; ++i ) {
998                 for ( j = 0; j < 3; ++j ) {
999                     if ( tris_v.size() ) {
1000                         sgWriteShort( fp, (short)tris_v[i][j] );
1001                     }
1002                     if ( tris_n.size() ) {
1003                         sgWriteShort( fp, (short)tris_n[i][j] );
1004                     }
1005                     if ( tris_c.size() ) {
1006                         sgWriteShort( fp, (short)tris_c[i][j] );
1007                     }
1008                     if ( tris_tc.size() ) {
1009                         sgWriteShort( fp, (short)tris_tc[i][j] );
1010                     }
1011                 }
1012             }
1013
1014             start = end;
1015             end = start + 1;
1016         }
1017     }
1018
1019     // dump triangle strips
1020     if ( strips_v.size() > 0 ) {
1021         int start = 0;
1022         int end = 1;
1023         string material;
1024         while ( start < (int)strip_materials.size() ) {
1025             // find next group
1026             material = strip_materials[start];
1027             while ( (end < (int)strip_materials.size()) && 
1028                     (material == strip_materials[end]) )
1029                 {
1030                     // cout << "end = " << end << endl;
1031                     end++;
1032                 }
1033             // cout << "group = " << start << " to " << end - 1 << endl;
1034
1035             // write group headers
1036             sgWriteChar( fp, (char)SG_TRIANGLE_STRIPS ); // type
1037             sgWriteShort( fp, 2 );                       // nproperties
1038             sgWriteShort( fp, end - start );             // nelements
1039
1040             sgWriteChar( fp, (char)SG_MATERIAL );        // property
1041             sgWriteUInt( fp, material.length() );        // nbytes
1042             sgWriteBytes( fp, material.length(), material.c_str() );
1043
1044             idx_mask = 0;
1045             idx_size = 0;
1046             if ( strips_v.size() ) { idx_mask |= SG_IDX_VERTICES; ++idx_size; }
1047             if ( strips_n.size() ) { idx_mask |= SG_IDX_NORMALS; ++idx_size; }
1048             if ( strips_c.size() ) { idx_mask |= SG_IDX_COLORS; ++idx_size; }
1049             if ( strips_tc.size() ) { idx_mask |= SG_IDX_TEXCOORDS; ++idx_size;}
1050             sgWriteChar( fp, (char)SG_INDEX_TYPES );     // property
1051             sgWriteUInt( fp, 1 );                        // nbytes
1052             sgWriteChar( fp, idx_mask );
1053
1054             // write strips
1055             for ( i = start; i < end; ++i ) {
1056                 // nbytes
1057                 sgWriteUInt( fp, strips_v[i].size() * idx_size * sizeof(short));
1058                 for ( j = 0; j < (int)strips_v[i].size(); ++j ) {
1059                     if ( strips_v.size() ) { 
1060                         sgWriteShort( fp, (short)strips_v[i][j] );
1061                     }
1062                     if ( strips_n.size() ) { 
1063                         sgWriteShort( fp, (short)strips_n[i][j] );
1064                     }
1065                     if ( strips_c.size() ) { 
1066                         sgWriteShort( fp, (short)strips_c[i][j] );
1067                     }
1068                     if ( strips_tc.size() ) { 
1069                         sgWriteShort( fp, (short)strips_tc[i][j] );
1070                     }
1071                 }
1072             }
1073             
1074             start = end;
1075             end = start + 1;
1076         }
1077     }
1078
1079     // dump triangle fans
1080     if ( fans_v.size() > 0 ) {
1081         int start = 0;
1082         int end = 1;
1083         string material;
1084         while ( start < (int)fan_materials.size() ) {
1085             // find next group
1086             material = fan_materials[start];
1087             while ( (end < (int)fan_materials.size()) && 
1088                     (material == fan_materials[end]) )
1089                 {
1090                     // cout << "end = " << end << endl;
1091                     end++;
1092                 }
1093             // cout << "group = " << start << " to " << end - 1 << endl;
1094
1095             // write group headers
1096             sgWriteChar( fp, (char)SG_TRIANGLE_FANS );   // type
1097             sgWriteShort( fp, 2 );                       // nproperties
1098             sgWriteShort( fp, end - start );             // nelements
1099
1100             sgWriteChar( fp, (char)SG_MATERIAL );       // property
1101             sgWriteUInt( fp, material.length() );        // nbytes
1102             sgWriteBytes( fp, material.length(), material.c_str() );
1103
1104             idx_mask = 0;
1105             idx_size = 0;
1106             if ( fans_v.size() ) { idx_mask |= SG_IDX_VERTICES; ++idx_size; }
1107             if ( fans_n.size() ) { idx_mask |= SG_IDX_NORMALS; ++idx_size; }
1108             if ( fans_c.size() ) { idx_mask |= SG_IDX_COLORS; ++idx_size; }
1109             if ( fans_tc.size() ) { idx_mask |= SG_IDX_TEXCOORDS; ++idx_size; }
1110             sgWriteChar( fp, (char)SG_INDEX_TYPES );     // property
1111             sgWriteUInt( fp, 1 );                        // nbytes
1112             sgWriteChar( fp, idx_mask );
1113
1114             // write fans
1115             for ( i = start; i < end; ++i ) {
1116                 // nbytes
1117                 sgWriteUInt( fp, fans_v[i].size() * idx_size * sizeof(short) );
1118                 for ( j = 0; j < (int)fans_v[i].size(); ++j ) {
1119                     if ( fans_v.size() ) {
1120                         sgWriteShort( fp, (short)fans_v[i][j] );
1121                     }
1122                     if ( fans_n.size() ) {
1123                         sgWriteShort( fp, (short)fans_n[i][j] );
1124                     }
1125                     if ( fans_c.size() ) {
1126                         sgWriteShort( fp, (short)fans_c[i][j] );
1127                     }
1128                     if ( fans_tc.size() ) {
1129                         sgWriteShort( fp, (short)fans_tc[i][j] );
1130                     }
1131                 }
1132             }
1133             
1134             start = end;
1135             end = start + 1;
1136         }
1137     }
1138
1139     // close the file
1140     gzclose(fp);
1141
1142     if ( sgWriteError() ) {
1143         cout << "We detected an error while writing the file." << endl;
1144         return false;
1145     }
1146
1147     return true;
1148 }
1149
1150
1151 // write out the structures to an ASCII file.  We assume that the
1152 // groups come to us sorted by material property.  If not, things
1153 // don't break, but the result won't be as optimal.
1154 bool SGBinObject::write_ascii( const string& base, const string& name,
1155                                const SGBucket& b )
1156 {
1157     Point3D p;
1158     int i, j;
1159
1160     string dir = base + "/" + b.gen_base_path();
1161     string command = "mkdir -p " + dir;
1162 #if defined(_MSC_VER) || defined(__MINGW32__)
1163     system( (string("mkdir ") + dir).c_str() );
1164 #else
1165     system(command.c_str());
1166 #endif
1167
1168     // string file = dir + "/" + b.gen_index_str();
1169     string file = dir + "/" + name;
1170     cout << "Output file = " << file << endl;
1171
1172     FILE *fp;
1173     if ( (fp = fopen( file.c_str(), "w" )) == NULL ) {
1174         cout << "ERROR: opening " << file << " for writing!" << endl;
1175         return false;
1176     }
1177
1178     cout << "triangles size = " << tris_v.size() << "  tri_materials = " 
1179          << tri_materials.size() << endl;
1180     cout << "strips size = " << strips_v.size() << "  strip_materials = " 
1181          << strip_materials.size() << endl;
1182     cout << "fans size = " << fans_v.size() << "  fan_materials = " 
1183          << fan_materials.size() << endl;
1184
1185     cout << "points = " << wgs84_nodes.size() << endl;
1186     cout << "tex coords = " << texcoords.size() << endl;
1187     // write headers
1188     fprintf(fp, "# FGFS Scenery\n");
1189     fprintf(fp, "# Version %s\n", SG_SCENERY_FILE_FORMAT);
1190
1191     time_t calendar_time = time(NULL);
1192     struct tm *local_tm;
1193     local_tm = localtime( &calendar_time );
1194     char time_str[256];
1195     strftime( time_str, 256, "%a %b %d %H:%M:%S %Z %Y", local_tm);
1196     fprintf(fp, "# Created %s\n", time_str );
1197     fprintf(fp, "\n");
1198
1199     // write bounding sphere
1200     fprintf(fp, "# gbs %.5f %.5f %.5f %.2f\n",
1201             gbs_center.x(), gbs_center.y(), gbs_center.z(), gbs_radius);
1202     fprintf(fp, "\n");
1203
1204     // dump vertex list
1205     fprintf(fp, "# vertex list\n");
1206     for ( i = 0; i < (int)wgs84_nodes.size(); ++i ) {
1207         p = wgs84_nodes[i] - gbs_center;
1208         
1209         fprintf(fp,  "v %.5f %.5f %.5f\n", p.x(), p.y(), p.z() );
1210     }
1211     fprintf(fp, "\n");
1212
1213     fprintf(fp, "# vertex normal list\n");
1214     for ( i = 0; i < (int)normals.size(); ++i ) {
1215         p = normals[i];
1216         fprintf(fp,  "vn %.5f %.5f %.5f\n", p.x(), p.y(), p.z() );
1217     }
1218     fprintf(fp, "\n");
1219
1220     // dump texture coordinates
1221     fprintf(fp, "# texture coordinate list\n");
1222     for ( i = 0; i < (int)texcoords.size(); ++i ) {
1223         p = texcoords[i];
1224         fprintf(fp,  "vt %.5f %.5f\n", p.x(), p.y() );
1225     }
1226     fprintf(fp, "\n");
1227
1228     // dump individual triangles if they exist
1229     if ( tris_v.size() > 0 ) {
1230         fprintf(fp, "# triangle groups\n");
1231
1232         int start = 0;
1233         int end = 1;
1234         string material;
1235         while ( start < (int)tri_materials.size() ) {
1236             // find next group
1237             material = tri_materials[start];
1238             while ( (end < (int)tri_materials.size()) && 
1239                     (material == tri_materials[end]) )
1240             {
1241                 // cout << "end = " << end << endl;
1242                 end++;
1243             }
1244             // cout << "group = " << start << " to " << end - 1 << endl;
1245
1246             // make a list of points for the group
1247             point_list group_nodes;
1248             group_nodes.clear();
1249             Point3D bs_center;
1250             double bs_radius = 0;
1251             for ( i = start; i < end; ++i ) {
1252                 for ( j = 0; j < (int)tris_v[i].size(); ++j ) {
1253                     group_nodes.push_back( wgs84_nodes[ tris_v[i][j] ] );
1254                     bs_center = calc_center( group_nodes );
1255                     bs_radius = sgCalcBoundingRadius( bs_center, group_nodes );
1256                 }
1257             }
1258
1259             // write group headers
1260             fprintf(fp, "\n");
1261             fprintf(fp, "# usemtl %s\n", material.c_str());
1262             fprintf(fp, "# bs %.4f %.4f %.4f %.2f\n",
1263                     bs_center.x(), bs_center.y(), bs_center.z(), bs_radius);
1264
1265             // write groups
1266             for ( i = start; i < end; ++i ) {
1267                 fprintf(fp, "f");
1268                 for ( j = 0; j < (int)tris_v[i].size(); ++j ) {
1269                     fprintf(fp, " %d/%d", tris_v[i][j], tris_tc[i][j] );
1270                 }
1271                 fprintf(fp, "\n");
1272             }
1273
1274             start = end;
1275             end = start + 1;
1276         }
1277     }
1278
1279     // dump triangle groups
1280     if ( strips_v.size() > 0 ) {
1281         fprintf(fp, "# triangle strips\n");
1282
1283         int start = 0;
1284         int end = 1;
1285         string material;
1286         while ( start < (int)strip_materials.size() ) {
1287             // find next group
1288             material = strip_materials[start];
1289             while ( (end < (int)strip_materials.size()) && 
1290                     (material == strip_materials[end]) )
1291                 {
1292                     // cout << "end = " << end << endl;
1293                     end++;
1294                 }
1295             // cout << "group = " << start << " to " << end - 1 << endl;
1296
1297             // make a list of points for the group
1298             point_list group_nodes;
1299             group_nodes.clear();
1300             Point3D bs_center;
1301             double bs_radius = 0;
1302             for ( i = start; i < end; ++i ) {
1303                 for ( j = 0; j < (int)strips_v[i].size(); ++j ) {
1304                     group_nodes.push_back( wgs84_nodes[ strips_v[i][j] ] );
1305                     bs_center = calc_center( group_nodes );
1306                     bs_radius = sgCalcBoundingRadius( bs_center, group_nodes );
1307                 }
1308             }
1309
1310             // write group headers
1311             fprintf(fp, "\n");
1312             fprintf(fp, "# usemtl %s\n", material.c_str());
1313             fprintf(fp, "# bs %.4f %.4f %.4f %.2f\n",
1314                     bs_center.x(), bs_center.y(), bs_center.z(), bs_radius);
1315
1316             // write groups
1317             for ( i = start; i < end; ++i ) {
1318                 fprintf(fp, "ts");
1319                 for ( j = 0; j < (int)strips_v[i].size(); ++j ) {
1320                     fprintf(fp, " %d/%d", strips_v[i][j], strips_tc[i][j] );
1321                 }
1322                 fprintf(fp, "\n");
1323             }
1324             
1325             start = end;
1326             end = start + 1;
1327         }
1328     }
1329
1330     // close the file
1331     fclose(fp);
1332
1333     command = "gzip --force --best " + file;
1334     system(command.c_str());
1335
1336     return true;
1337 }