]> git.mxchange.org Git - flightgear.git/blob - src/Objects/obj.cxx
Misc clean ups.
[flightgear.git] / src / Objects / obj.cxx
1 // obj.cxx -- routines to handle "sorta" WaveFront .obj format files.
2 //
3 // Written by Curtis Olson, started October 1997.
4 //
5 // Copyright (C) 1997  Curtis L. Olson  - curt@infoplane.com
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // 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 #ifdef HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27
28 #ifdef SG_MATH_EXCEPTION_CLASH
29 #  include <math.h>
30 #endif
31
32 #include <stdio.h>
33 #include <string.h>
34
35 #include <simgear/compiler.h>
36 #include <simgear/sg_inlines.h>
37 #include <simgear/io/sg_binobj.hxx>
38
39 #include STL_STRING
40 #include <map>                  // STL
41 #include <vector>               // STL
42 #include <ctype.h>              // isdigit()
43
44 #include <simgear/constants.h>
45 #include <simgear/debug/logstream.hxx>
46 #include <simgear/math/point3d.hxx>
47 #include <simgear/math/polar3d.hxx>
48 #include <simgear/math/sg_geodesy.hxx>
49 #include <simgear/math/sg_random.h>
50 #include <simgear/math/vector.hxx>
51 #include <simgear/misc/sgstream.hxx>
52 #include <simgear/misc/stopwatch.hxx>
53 #include <simgear/misc/texcoord.hxx>
54
55 #include <Main/globals.hxx>
56 #include <Main/fg_props.hxx>
57 #include <Time/light.hxx>
58 #include <Scenery/tileentry.hxx>
59
60 #include "newmat.hxx"
61 #include "matlib.hxx"
62 #include "pt_lights.hxx"
63 #include "obj.hxx"
64
65 SG_USING_STD(string);
66 SG_USING_STD(vector);
67
68
69 typedef vector < int > int_list;
70 typedef int_list::iterator int_list_iterator;
71 typedef int_list::const_iterator int_point_list_iterator;
72
73
74 static double normals[FG_MAX_NODES][3];
75 static double tex_coords[FG_MAX_NODES*3][3];
76
77 static int
78 runway_lights_predraw (ssgEntity * e)
79 {
80                                 // Turn on lights only at night
81     float sun_angle = cur_light_params.sun_angle * SGD_RADIANS_TO_DEGREES;
82     return int((sun_angle > 90.0) ||
83                (fgGetDouble("/environment/visibility-m") < 5000.0));
84 }
85
86
87 #define FG_TEX_CONSTANT 69.0
88
89 // Calculate texture coordinates for a given point.
90 static Point3D local_calc_tex_coords(const Point3D& node, const Point3D& ref) {
91     Point3D cp;
92     Point3D pp;
93     // double tmplon, tmplat;
94
95     // cout << "-> " << node[0] << " " << node[1] << " " << node[2] << endl;
96     // cout << "-> " << ref.x() << " " << ref.y() << " " << ref.z() << endl;
97
98     cp = Point3D( node[0] + ref.x(),
99                   node[1] + ref.y(),
100                   node[2] + ref.z() );
101
102     pp = sgCartToPolar3d(cp);
103
104     // tmplon = pp.lon() * SGD_RADIANS_TO_DEGREES;
105     // tmplat = pp.lat() * SGD_RADIANS_TO_DEGREES;
106     // cout << tmplon << " " << tmplat << endl;
107
108     pp.setx( fmod(SGD_RADIANS_TO_DEGREES * FG_TEX_CONSTANT * pp.x(), 11.0) );
109     pp.sety( fmod(SGD_RADIANS_TO_DEGREES * FG_TEX_CONSTANT * pp.y(), 11.0) );
110
111     if ( pp.x() < 0.0 ) {
112         pp.setx( pp.x() + 11.0 );
113     }
114
115     if ( pp.y() < 0.0 ) {
116         pp.sety( pp.y() + 11.0 );
117     }
118
119     // cout << pp << endl;
120
121     return(pp);
122 }
123
124
125 // Generate an ocean tile
126 bool fgGenTile( const string& path, SGBucket b,
127                       Point3D *center,
128                       double *bounding_radius,
129                       ssgBranch* geometry )
130 {
131     FGNewMat *newmat;
132
133     ssgSimpleState *state = NULL;
134
135     geometry -> setName ( (char *)path.c_str() ) ;
136
137     double tex_width = 1000.0;
138     // double tex_height;
139
140     // find Ocean material in the properties list
141     newmat = material_lib.find( "Ocean" );
142     if ( newmat != NULL ) {
143         // set the texture width and height values for this
144         // material
145         tex_width = newmat->get_xsize();
146         // tex_height = newmat->get_ysize();
147         
148         // set ssgState
149         state = newmat->get_state();
150     } else {
151         SG_LOG( SG_TERRAIN, SG_ALERT, 
152                 "Ack! unknown usemtl name = " << "Ocean" 
153                 << " in " << path );
154     }
155
156     // Calculate center point
157     double clon = b.get_center_lon();
158     double clat = b.get_center_lat();
159     double height = b.get_height();
160     double width = b.get_width();
161
162     *center = sgGeodToCart( Point3D(clon*SGD_DEGREES_TO_RADIANS,
163                                     clat*SGD_DEGREES_TO_RADIANS,
164                                     0.0) );
165     // cout << "center = " << center << endl;;
166     
167     // Caculate corner vertices
168     Point3D geod[4];
169     geod[0] = Point3D( clon - width/2.0, clat - height/2.0, 0.0 );
170     geod[1] = Point3D( clon + width/2.0, clat - height/2.0, 0.0 );
171     geod[2] = Point3D( clon + width/2.0, clat + height/2.0, 0.0 );
172     geod[3] = Point3D( clon - width/2.0, clat + height/2.0, 0.0 );
173
174     Point3D rad[4];
175     int i;
176     for ( i = 0; i < 4; ++i ) {
177         rad[i] = Point3D( geod[i].x() * SGD_DEGREES_TO_RADIANS,
178                           geod[i].y() * SGD_DEGREES_TO_RADIANS,
179                           geod[i].z() );
180     }
181
182     Point3D cart[4], rel[4];
183     for ( i = 0; i < 4; ++i ) {
184         cart[i] = sgGeodToCart(rad[i]);
185         rel[i] = cart[i] - *center;
186         // cout << "corner " << i << " = " << cart[i] << endl;
187     }
188
189     // Calculate bounding radius
190     *bounding_radius = center->distance3D( cart[0] );
191     // cout << "bounding radius = " << t->bounding_radius << endl;
192
193     // Calculate normals
194     Point3D normals[4];
195     for ( i = 0; i < 4; ++i ) {
196         double length = cart[i].distance3D( Point3D(0.0) );
197         normals[i] = cart[i] / length;
198         // cout << "normal = " << normals[i] << endl;
199     }
200
201     // Calculate texture coordinates
202     point_list geod_nodes;
203     geod_nodes.clear();
204     geod_nodes.reserve(4);
205     int_list rectangle;
206     rectangle.clear();
207     rectangle.reserve(4);
208     for ( i = 0; i < 4; ++i ) {
209         geod_nodes.push_back( geod[i] );
210         rectangle.push_back( i );
211     }
212     point_list texs = calc_tex_coords( b, geod_nodes, rectangle, 
213                                        1000.0 / tex_width );
214
215     // Allocate ssg structure
216     ssgVertexArray   *vl = new ssgVertexArray( 4 );
217     ssgNormalArray   *nl = new ssgNormalArray( 4 );
218     ssgTexCoordArray *tl = new ssgTexCoordArray( 4 );
219     ssgColourArray   *cl = new ssgColourArray( 1 );
220
221     sgVec4 color;
222     sgSetVec4( color, 1.0, 1.0, 1.0, 1.0 );
223     cl->add( color );
224
225     // sgVec3 *vtlist = new sgVec3 [ 4 ];
226     // t->vec3_ptrs.push_back( vtlist );
227     // sgVec3 *vnlist = new sgVec3 [ 4 ];
228     // t->vec3_ptrs.push_back( vnlist );
229     // sgVec2 *tclist = new sgVec2 [ 4 ];
230     // t->vec2_ptrs.push_back( tclist );
231
232     sgVec2 tmp2;
233     sgVec3 tmp3;
234     for ( i = 0; i < 4; ++i ) {
235         sgSetVec3( tmp3, 
236                    rel[i].x(), rel[i].y(), rel[i].z() );
237         vl->add( tmp3 );
238
239         sgSetVec3( tmp3, 
240                    normals[i].x(), normals[i].y(), normals[i].z() );
241         nl->add( tmp3 );
242
243         sgSetVec2( tmp2, texs[i].x(), texs[i].y());
244         tl->add( tmp2 );
245     }
246     
247     ssgLeaf *leaf = 
248         new ssgVtxTable ( GL_TRIANGLE_FAN, vl, nl, tl, cl );
249
250     leaf->setState( state );
251
252     geometry->addKid( leaf );
253
254     return true;
255 }
256
257
258 static void random_pt_inside_tri( float *res,
259                                   float *n1, float *n2, float *n3 )
260 {
261     double a = sg_random();
262     double b = sg_random();
263     if ( a + b > 1.0 ) {
264         a = 1.0 - a;
265         b = 1.0 - b;
266     }
267     double c = 1 - a - b;
268
269     res[0] = n1[0]*a + n2[0]*b + n3[0]*c;
270     res[1] = n1[1]*a + n2[1]*b + n3[1]*c;
271     res[2] = n1[2]*a + n2[2]*b + n3[2]*c;
272 }
273
274
275 static void gen_random_surface_points( ssgLeaf *leaf, ssgVertexArray *lights,
276                                        double factor ) {
277     int num = leaf->getNumTriangles();
278     if ( num > 0 ) {
279         short int n1, n2, n3;
280         float *p1, *p2, *p3;
281         sgVec3 result;
282
283         // generate a repeatable random seed
284         p1 = leaf->getVertex( 0 );
285         unsigned int seed = (unsigned int)(fabs(p1[0]*100));
286         sg_srandom( seed );
287
288         for ( int i = 0; i < num; ++i ) {
289             leaf->getTriangle( i, &n1, &n2, &n3 );
290             p1 = leaf->getVertex(n1);
291             p2 = leaf->getVertex(n2);
292             p3 = leaf->getVertex(n3);
293             double area = sgTriArea( p1, p2, p3 );
294             double num = area / factor;
295
296             // generate a light point for each unit of area
297             while ( num > 1.0 ) {
298                 random_pt_inside_tri( result, p1, p2, p3 );
299                 lights->add( result );
300                 num -= 1.0;
301             }
302             // for partial units of area, use a zombie door method to
303             // create the proper random chance of a light being created
304             // for this triangle
305             if ( num > 0.0 ) {
306                 if ( sg_random() <= num ) {
307                     // a zombie made it through our door
308                     random_pt_inside_tri( result, p1, p2, p3 );
309                     lights->add( result );
310                 }
311             }
312         }
313     }
314 }
315
316
317 /**
318  * User data for populating leaves when they come in range.
319  */
320 class LeafUserData : public ssgBase
321 {
322 public:
323     bool is_filled_in;
324     ssgLeaf * leaf;
325     FGNewMat * mat;
326     ssgBranch * branch;
327     float sin_lat;
328     float cos_lat;
329     float sin_lon;
330     float cos_lon;
331
332     void setup_triangle( int i );
333 };
334
335
336 /**
337  * User data for populating triangles when they come in range.
338  */
339 class TriUserData : public ssgBase
340 {
341 public:
342   bool is_filled_in;
343   float * p1;
344   float * p2;
345   float * p3;
346     sgVec3 center;
347     double area;
348   FGNewMat::ObjectGroup * object_group;
349   ssgBranch * branch;
350     LeafUserData * leafData;
351   unsigned int seed;
352
353     void fill_in_triangle();
354     void add_object_to_triangle(FGNewMat::Object * object);
355     void makeWorldMatrix (sgMat4 ROT, double hdg_deg );
356 };
357
358
359 /**
360  * Fill in a triangle with randomly-placed objects.
361  *
362  * This method is invoked by a callback when the triangle is in range
363  * but not yet populated.
364  *
365  */
366
367 void TriUserData::fill_in_triangle ()
368 {
369                                 // generate a repeatable random seed
370     sg_srandom(seed);
371
372     int nObjects = object_group->get_object_count();
373
374     for (int i = 0; i < nObjects; i++) {
375       FGNewMat::Object * object = object_group->get_object(i);
376       double num = area / object->get_coverage_m2();
377
378       // place an object each unit of area
379       while ( num > 1.0 ) {
380             add_object_to_triangle(object);
381         num -= 1.0;
382       }
383       // for partial units of area, use a zombie door method to
384       // create the proper random chance of an object being created
385       // for this triangle
386       if ( num > 0.0 ) {
387         if ( sg_random() <= num ) {
388           // a zombie made it through our door
389                 add_object_to_triangle(object);
390         }
391       }
392     }
393 }
394
395 void TriUserData::add_object_to_triangle (FGNewMat::Object * object)
396 {
397     // Set up the random heading if required.
398     double hdg_deg = 0;
399     if (object->get_heading_type() == FGNewMat::Object::HEADING_RANDOM)
400         hdg_deg = sg_random() * 360;
401
402     sgMat4 mat;
403     makeWorldMatrix(mat, hdg_deg);
404
405     ssgTransform * pos = new ssgTransform;
406     pos->setTransform(mat);
407     pos->addKid(object->get_random_model());
408     branch->addKid(pos);
409 }
410
411 void TriUserData::makeWorldMatrix (sgMat4 mat, double hdg_deg )
412 {
413     if (hdg_deg == 0) {
414         mat[0][0] =  leafData->sin_lat * leafData->cos_lon;
415         mat[0][1] =  leafData->sin_lat * leafData->sin_lon;
416         mat[0][2] = -leafData->cos_lat;
417         mat[0][3] =  SG_ZERO;
418
419         mat[1][0] =  -leafData->sin_lon;
420         mat[1][1] =  leafData->cos_lon;
421         mat[1][2] =  SG_ZERO;
422         mat[1][3] =  SG_ZERO;
423     } else {
424         float sin_hdg = sin( hdg_deg * SGD_DEGREES_TO_RADIANS ) ;
425         float cos_hdg = cos( hdg_deg * SGD_DEGREES_TO_RADIANS ) ;
426         mat[0][0] =  cos_hdg * leafData->sin_lat * leafData->cos_lon - sin_hdg * leafData->sin_lon;
427         mat[0][1] =  cos_hdg * leafData->sin_lat * leafData->sin_lon + sin_hdg * leafData->cos_lon;
428         mat[0][2] = -cos_hdg * leafData->cos_lat;
429         mat[0][3] =  SG_ZERO;
430
431         mat[1][0] = -sin_hdg * leafData->sin_lat * leafData->cos_lon - cos_hdg * leafData->sin_lon;
432         mat[1][1] = -sin_hdg * leafData->sin_lat * leafData->sin_lon + cos_hdg * leafData->cos_lon;
433         mat[1][2] =  sin_hdg * leafData->cos_lat;
434         mat[1][3] =  SG_ZERO;
435     }
436
437     mat[2][0] = leafData->cos_lat * leafData->cos_lon;
438     mat[2][1] = leafData->cos_lat * leafData->sin_lon;
439     mat[2][2] = leafData->sin_lat;
440     mat[2][3] = SG_ZERO;
441
442     // translate to random point in triangle
443     sgVec3 result;
444     random_pt_inside_tri(result, p1, p2, p3);
445     sgSubVec3(mat[3], result, center);
446
447     mat[3][3] = SG_ONE ;
448 }
449
450 /**
451  * SSG callback for an in-range triangle of randomly-placed objects.
452  *
453  * This pretraversal callback is attached to a branch that is traversed
454  * only when a triangle is in range.  If the triangle is not currently
455  * populated with randomly-placed objects, this callback will populate
456  * it.
457  *
458  * @param entity The entity to which the callback is attached (not used).
459  * @param mask The entity's traversal mask (not used).
460  * @return Always 1, to allow traversal and culling to continue.
461  */
462 static int
463 tri_in_range_callback (ssgEntity * entity, int mask)
464 {
465   TriUserData * data = (TriUserData *)entity->getUserData();
466   if (!data->is_filled_in) {
467         data->fill_in_triangle();
468     data->is_filled_in = true;
469   }
470   return 1;
471 }
472
473
474 /**
475  * SSG callback for an out-of-range triangle of randomly-placed objects.
476  *
477  * This pretraversal callback is attached to a branch that is traversed
478  * only when a triangle is out of range.  If the triangle is currently
479  * populated with randomly-placed objects, the objects will be removed.
480  *
481  *
482  * @param entity The entity to which the callback is attached (not used).
483  * @param mask The entity's traversal mask (not used).
484  * @return Always 0, to prevent any further traversal or culling.
485  */
486 static int
487 tri_out_of_range_callback (ssgEntity * entity, int mask)
488 {
489   TriUserData * data = (TriUserData *)entity->getUserData();
490   if (data->is_filled_in) {
491     data->branch->removeAllKids();
492     data->is_filled_in = false;
493   }
494   return 0;
495 }
496
497
498 /**
499  * ssgEntity with a dummy bounding sphere, to fool culling.
500  *
501  * This forces the in-range and out-of-range branches to be visited
502  * when appropriate, even if they have no children.  It's ugly, but
503  * it works and seems fairly efficient (since branches can still
504  * be culled when they're out of the view frustum).
505  */
506 class DummyBSphereEntity : public ssgEntity
507 {
508 public:
509   DummyBSphereEntity (float radius)
510   {
511     bsphere.setCenter(0, 0, 0);
512     bsphere.setRadius(radius);
513   }
514   virtual ~DummyBSphereEntity () {}
515   virtual void recalcBSphere () { bsphere_is_invalid = false; }
516   virtual void cull (sgFrustum *f, sgMat4 m, int test_needed) {}
517   virtual void isect (sgSphere *s, sgMat4 m, int test_needed) {}
518   virtual void hot (sgVec3 s, sgMat4 m, int test_needed) {}
519   virtual void los (sgVec3 s, sgMat4 m, int test_needed) {}
520 };
521
522
523 /**
524  * Calculate the bounding radius of a triangle from its center.
525  *
526  * @param center The triangle center.
527  * @param p1 The first point in the triangle.
528  * @param p2 The second point in the triangle.
529  * @param p3 The third point in the triangle.
530  * @return The greatest distance any point lies from the center.
531  */
532 static inline float
533 get_bounding_radius( sgVec3 center, float *p1, float *p2, float *p3)
534 {
535    return sqrt( SG_MAX3( sgDistanceSquaredVec3(center, p1),
536                          sgDistanceSquaredVec3(center, p2),
537                          sgDistanceSquaredVec3(center, p3) ) );
538 }
539
540
541 /**
542  * Set up a triangle for randomly-placed objects.
543  *
544  * No objects will be added unless the triangle comes into range.
545  *
546  */
547
548 void LeafUserData::setup_triangle (int i )
549 {
550     short n1, n2, n3;
551     leaf->getTriangle(i, &n1, &n2, &n3);
552
553     float * p1 = leaf->getVertex(n1);
554     float * p2 = leaf->getVertex(n2);
555     float * p3 = leaf->getVertex(n3);
556
557                                 // Set up a single center point for LOD
558     sgVec3 center;
559     sgSetVec3(center,
560               (p1[0] + p2[0] + p3[0]) / 3.0,
561               (p1[1] + p2[1] + p3[1]) / 3.0,
562               (p1[2] + p2[2] + p3[2]) / 3.0);
563     double area = sgTriArea(p1, p2, p3);
564       
565                                 // maximum radius of an object from center.
566     double bounding_radius = get_bounding_radius(center, p1, p2, p3);
567
568                                 // Set up a transformation to the center
569                                 // point, so that everything else can
570                                 // be specified relative to it.
571     ssgTransform * location = new ssgTransform;
572     sgMat4 TRANS;
573     sgMakeTransMat4(TRANS, center);
574     location->setTransform(TRANS);
575     branch->addKid(location);
576
577                                 // Iterate through all the object types.
578     int num_groups = mat->get_object_group_count();
579     for (int j = 0; j < num_groups; j++) {
580                                 // Look up the random object.
581         FGNewMat::ObjectGroup * group = mat->get_object_group(j);
582
583                                 // Set up the range selector for the entire
584                                 // triangle; note that we use the object
585                                 // range plus the bounding radius here, to
586                                 // allow for objects far from the center.
587         float ranges[] = { 0,
588                           group->get_range_m() + bounding_radius,
589                 SG_MAX };
590         ssgRangeSelector * lod = new ssgRangeSelector;
591         lod->setRanges(ranges, 3);
592         location->addKid(lod);
593
594                                 // Create the in-range and out-of-range
595                                 // branches.
596         ssgBranch * in_range = new ssgBranch;
597         ssgBranch * out_of_range = new ssgBranch;
598
599                                 // Set up the user data for if/when
600                                 // the random objects in this triangle
601                                 // are filled in.
602         TriUserData * data = new TriUserData;
603         data->is_filled_in = false;
604         data->p1 = p1;
605         data->p2 = p2;
606         data->p3 = p3;
607         sgCopyVec3 (data->center, center);
608         data->area = area;
609         data->object_group = group;
610         data->branch = in_range;
611         data->leafData = this;
612         data->seed = (unsigned int)(p1[0] * j);
613
614                                 // Set up the in-range node.
615         in_range->setUserData(data);
616         in_range->setTravCallback(SSG_CALLBACK_PRETRAV,
617                                  tri_in_range_callback);
618         lod->addKid(in_range);
619
620                                 // Set up the out-of-range node.
621         out_of_range->setUserData(data);
622         out_of_range->setTravCallback(SSG_CALLBACK_PRETRAV,
623                                       tri_out_of_range_callback);
624         out_of_range->addKid(new DummyBSphereEntity(bounding_radius));
625         lod->addKid(out_of_range);
626     }
627 }
628
629 /**
630  * SSG callback for an in-range leaf of randomly-placed objects.
631  *
632  * This pretraversal callback is attached to a branch that is
633  * traversed only when a leaf is in range.  If the leaf is not
634  * currently prepared to be populated with randomly-placed objects,
635  * this callback will prepare it (actual population is handled by
636  * the tri_in_range_callback for individual triangles).
637  *
638  * @param entity The entity to which the callback is attached (not used).
639  * @param mask The entity's traversal mask (not used).
640  * @return Always 1, to allow traversal and culling to continue.
641  */
642 static int
643 leaf_in_range_callback (ssgEntity * entity, int mask)
644 {
645   LeafUserData * data = (LeafUserData *)entity->getUserData();
646
647   if (!data->is_filled_in) {
648                                 // Iterate through all the triangles
649                                 // and populate them.
650     int num_tris = data->leaf->getNumTriangles();
651     for ( int i = 0; i < num_tris; ++i ) {
652             data->setup_triangle(i);
653     }
654     data->is_filled_in = true;
655   }
656   return 1;
657 }
658
659
660 /**
661  * SSG callback for an out-of-range leaf of randomly-placed objects.
662  *
663  * This pretraversal callback is attached to a branch that is
664  * traversed only when a leaf is out of range.  If the leaf is
665  * currently prepared to be populated with randomly-placed objects (or
666  * is actually populated), the objects will be removed.
667  *
668  * @param entity The entity to which the callback is attached (not used).
669  * @param mask The entity's traversal mask (not used).
670  * @return Always 0, to prevent any further traversal or culling.
671  */
672 static int
673 leaf_out_of_range_callback (ssgEntity * entity, int mask)
674 {
675   LeafUserData * data = (LeafUserData *)entity->getUserData();
676   if (data->is_filled_in) {
677     data->branch->removeAllKids();
678     data->is_filled_in = false;
679   }
680   return 0;
681 }
682
683
684 /**
685  * Randomly place objects on a surface.
686  *
687  * The leaf node provides the geometry of the surface, while the
688  * material provides the objects and placement density.  Latitude
689  * and longitude are required so that the objects can be rotated
690  * to the world-up vector.  This function does not actually add
691  * any objects; instead, it attaches an ssgRangeSelector to the
692  * branch with callbacks to generate the objects when needed.
693  *
694  * @param leaf The surface where the objects should be placed.
695  * @param branch The branch that will hold the randomly-placed objects.
696  * @param center The center of the leaf in FlightGear coordinates.
697  * @param material_name The name of the surface's material.
698  */
699 static void
700 gen_random_surface_objects (ssgLeaf *leaf,
701                             ssgBranch *branch,
702                             Point3D * center,
703                             const string &material_name)
704 {
705                                 // If the surface has no triangles, return
706                                 // now.
707     int num_tris = leaf->getNumTriangles();
708     if (num_tris < 1)
709       return;
710
711                                 // Get the material for this surface.
712     FGNewMat * mat = material_lib.find(material_name);
713     if (mat == 0) {
714       SG_LOG(SG_INPUT, SG_ALERT, "Unknown material " << material_name);
715       return;
716     }
717
718                                 // If the material has no randomly-placed
719                                 // objects, return now.
720     if (mat->get_object_group_count() < 1)
721       return;
722
723                                 // Calculate the geodetic centre of
724                                 // the tile, for aligning automatic
725                                 // objects.
726     double lon_deg, lat_rad, lat_deg, alt_m, sl_radius_m;
727     Point3D geoc = sgCartToPolar3d(*center);
728     lon_deg = geoc.lon() * SGD_RADIANS_TO_DEGREES;
729     sgGeocToGeod(geoc.lat(), geoc.radius(),
730                  &lat_rad, &alt_m, &sl_radius_m);
731     lat_deg = lat_rad * SGD_RADIANS_TO_DEGREES;
732
733                                 // LOD for the leaf
734                                 // max random object range: 20000m
735     float ranges[] = { 0, 20000, 1000000 };
736     ssgRangeSelector * lod = new ssgRangeSelector;
737     lod->setRanges(ranges, 3);
738     branch->addKid(lod);
739
740                                 // Create the in-range and out-of-range
741                                 // branches.
742     ssgBranch * in_range = new ssgBranch;
743     ssgBranch * out_of_range = new ssgBranch;
744     lod->addKid(in_range);
745     lod->addKid(out_of_range);
746
747     LeafUserData * data = new LeafUserData;
748     data->is_filled_in = false;
749     data->leaf = leaf;
750     data->mat = mat;
751     data->branch = in_range;
752     data->sin_lat = sin(lat_deg * SGD_DEGREES_TO_RADIANS);
753     data->cos_lat = cos(lat_deg * SGD_DEGREES_TO_RADIANS);
754     data->sin_lon = sin(lon_deg * SGD_DEGREES_TO_RADIANS);
755     data->cos_lon = cos(lon_deg * SGD_DEGREES_TO_RADIANS);
756
757     in_range->setUserData(data);
758     in_range->setTravCallback(SSG_CALLBACK_PRETRAV, leaf_in_range_callback);
759     out_of_range->setUserData(data);
760     out_of_range->setTravCallback(SSG_CALLBACK_PRETRAV,
761                                    leaf_out_of_range_callback);
762     out_of_range
763       ->addKid(new DummyBSphereEntity(leaf->getBSphere()->getRadius()));
764 }
765
766
767 \f
768 ////////////////////////////////////////////////////////////////////////
769 // Scenery loaders.
770 ////////////////////////////////////////////////////////////////////////
771
772
773 // Load an Ascii obj file
774 ssgBranch *fgAsciiObjLoad( const string& path, FGTileEntry *t,
775                            ssgVertexArray *lights, const bool is_base)
776 {
777     FGNewMat *newmat = NULL;
778     string material;
779     float coverage = -1;
780     Point3D pp;
781     // sgVec3 approx_normal;
782     // double normal[3], scale = 0.0;
783     // double x, y, z, xmax, xmin, ymax, ymin, zmax, zmin;
784     // GLfloat sgenparams[] = { 1.0, 0.0, 0.0, 0.0 };
785     // GLint display_list = 0;
786     int shading;
787     bool in_faces = false;
788     int vncount, vtcount;
789     int n1 = 0, n2 = 0, n3 = 0;
790     int tex;
791     // int last1 = 0, last2 = 0;
792     bool odd = false;
793     point_list nodes;
794     Point3D node;
795     Point3D center;
796     double scenery_version = 0.0;
797     double tex_width = 1000.0, tex_height = 1000.0;
798     bool shared_done = false;
799     int_list fan_vertices;
800     int_list fan_tex_coords;
801     int i;
802     ssgSimpleState *state = NULL;
803     sgVec3 *vtlist, *vnlist;
804     sgVec2 *tclist;
805
806     ssgBranch *tile = new ssgBranch () ;
807
808     tile -> setName ( (char *)path.c_str() ) ;
809
810     // Attempt to open "path.gz" or "path"
811     sg_gzifstream in( path );
812     if ( ! in.is_open() ) {
813         SG_LOG( SG_TERRAIN, SG_DEBUG, "Cannot open file: " << path );
814         SG_LOG( SG_TERRAIN, SG_DEBUG, "default to ocean tile: " << path );
815
816         delete tile;
817
818         return NULL;
819     }
820
821     shading = fgGetBool("/sim/rendering/shading");
822
823     if ( is_base ) {
824         t->ncount = 0;
825     }
826     vncount = 0;
827     vtcount = 0;
828     if ( is_base ) {
829         t->bounding_radius = 0.0;
830     }
831     center = t->center;
832
833     // StopWatch stopwatch;
834     // stopwatch.start();
835
836     // ignore initial comments and blank lines. (priming the pump)
837     // in >> skipcomment;
838     // string line;
839
840     string token;
841     char c;
842
843 #ifdef __MWERKS__
844     while ( in.get(c) && c  != '\0' ) {
845         in.putback(c);
846 #else
847     while ( ! in.eof() ) {
848 #endif
849         in >> ::skipws;
850
851         if ( in.get( c ) && c == '#' ) {
852             // process a comment line
853
854             // getline( in, line );
855             // cout << "comment = " << line << endl;
856
857             in >> token;
858
859             if ( token == "Version" ) {
860                 // read scenery versions number
861                 in >> scenery_version;
862                 // cout << "scenery_version = " << scenery_version << endl;
863                 if ( scenery_version > 0.4 ) {
864                     SG_LOG( SG_TERRAIN, SG_ALERT, 
865                             "\nYou are attempting to load a tile format that\n"
866                             << "is newer than this version of flightgear can\n"
867                             << "handle.  You should upgrade your copy of\n"
868                             << "FlightGear to the newest version.  For\n"
869                             << "details, please see:\n"
870                             << "\n    http://www.flightgear.org\n" );
871                     exit(-1);
872                 }
873             } else if ( token == "gbs" ) {
874                 // reference point (center offset)
875                 if ( is_base ) {
876                     in >> t->center >> t->bounding_radius;
877                 } else {
878                     Point3D junk1;
879                     double junk2;
880                     in >> junk1 >> junk2;
881                 }
882                 center = t->center;
883                 // cout << "center = " << center 
884                 //      << " radius = " << t->bounding_radius << endl;
885             } else if ( token == "bs" ) {
886                 // reference point (center offset)
887                 // (skip past this)
888                 Point3D junk1;
889                 double junk2;
890                 in >> junk1 >> junk2;
891             } else if ( token == "usemtl" ) {
892                 // material property specification
893
894                 // if first usemtl with shared_done = false, then set
895                 // shared_done true and build the ssg shared lists
896                 if ( ! shared_done ) {
897                     // sanity check
898                     if ( (int)nodes.size() != vncount ) {
899                         SG_LOG( SG_TERRAIN, SG_ALERT, 
900                                 "Tile has mismatched nodes = " << nodes.size()
901                                 << " and normals = " << vncount << " : " 
902                                 << path );
903                         // exit(-1);
904                     }
905                     shared_done = true;
906
907                     vtlist = new sgVec3 [ nodes.size() ];
908                     t->vec3_ptrs.push_back( vtlist );
909                     vnlist = new sgVec3 [ vncount ];
910                     t->vec3_ptrs.push_back( vnlist );
911                     tclist = new sgVec2 [ vtcount ];
912                     t->vec2_ptrs.push_back( tclist );
913
914                     for ( i = 0; i < (int)nodes.size(); ++i ) {
915                         sgSetVec3( vtlist[i], 
916                                    nodes[i][0], nodes[i][1], nodes[i][2] );
917                     }
918                     for ( i = 0; i < vncount; ++i ) {
919                         sgSetVec3( vnlist[i], 
920                                    normals[i][0], 
921                                    normals[i][1],
922                                    normals[i][2] );
923                     }
924                     for ( i = 0; i < vtcount; ++i ) {
925                         sgSetVec2( tclist[i],
926                                    tex_coords[i][0],
927                                    tex_coords[i][1] );
928                     }
929                 }
930
931                 // display_list = xglGenLists(1);
932                 // xglNewList(display_list, GL_COMPILE);
933                 // printf("xglGenLists(); xglNewList();\n");
934                 in_faces = false;
935
936                 // scan the material line
937                 in >> material;
938                 
939                 // find this material in the properties list
940
941                 newmat = material_lib.find( material );
942                 if ( newmat == NULL ) {
943                     // see if this is an on the fly texture
944                     string file = path;
945                     int pos = file.rfind( "/" );
946                     file = file.substr( 0, pos );
947                     // cout << "current file = " << file << endl;
948                     file += "/";
949                     file += material;
950                     // cout << "current file = " << file << endl;
951                     if ( ! material_lib.add_item( file ) ) {
952                         SG_LOG( SG_TERRAIN, SG_ALERT, 
953                                 "Ack! unknown usemtl name = " << material 
954                                 << " in " << path );
955                     } else {
956                         // locate our newly created material
957                         newmat = material_lib.find( material );
958                         if ( newmat == NULL ) {
959                             SG_LOG( SG_TERRAIN, SG_ALERT, 
960                                     "Ack! bad on the fly materia create = "
961                                     << material << " in " << path );
962                         }
963                     }
964                 }
965
966                 if ( newmat != NULL ) {
967                     // set the texture width and height values for this
968                     // material
969                     tex_width = newmat->get_xsize();
970                     tex_height = newmat->get_ysize();
971                     state = newmat->get_state();
972                     coverage = newmat->get_light_coverage();
973                     // cout << "(w) = " << tex_width << " (h) = "
974                     //      << tex_width << endl;
975                 } else {
976                     coverage = -1;
977                 }
978             } else {
979                 // unknown comment, just gobble the input until the
980                 // end of line
981
982                 in >> skipeol;
983             }
984         } else {
985             in.putback( c );
986         
987             in >> token;
988
989             // cout << "token = " << token << endl;
990
991             if ( token == "vn" ) {
992                 // vertex normal
993                 if ( vncount < FG_MAX_NODES ) {
994                     in >> normals[vncount][0]
995                        >> normals[vncount][1]
996                        >> normals[vncount][2];
997                     vncount++;
998                 } else {
999                     SG_LOG( SG_TERRAIN, SG_ALERT, 
1000                             "Read too many vertex normals in " << path 
1001                             << " ... dying :-(" );
1002                     exit(-1);
1003                 }
1004             } else if ( token == "vt" ) {
1005                 // vertex texture coordinate
1006                 if ( vtcount < FG_MAX_NODES*3 ) {
1007                     in >> tex_coords[vtcount][0]
1008                        >> tex_coords[vtcount][1];
1009                     vtcount++;
1010                 } else {
1011                     SG_LOG( SG_TERRAIN, SG_ALERT, 
1012                             "Read too many vertex texture coords in " << path
1013                             << " ... dying :-("
1014                             );
1015                     exit(-1);
1016                 }
1017             } else if ( token == "v" ) {
1018                 // node (vertex)
1019                 if ( t->ncount < FG_MAX_NODES ) {
1020                     /* in >> nodes[t->ncount][0]
1021                        >> nodes[t->ncount][1]
1022                        >> nodes[t->ncount][2]; */
1023                     in >> node;
1024                     nodes.push_back(node);
1025                     if ( is_base ) {
1026                         t->ncount++;
1027                     }
1028                 } else {
1029                     SG_LOG( SG_TERRAIN, SG_ALERT, 
1030                             "Read too many nodes in " << path 
1031                             << " ... dying :-(");
1032                     exit(-1);
1033                 }
1034             } else if ( (token == "tf") || (token == "ts") || (token == "f") ) {
1035                 // triangle fan, strip, or individual face
1036                 // SG_LOG( SG_TERRAIN, SG_INFO, "new fan or strip");
1037
1038                 fan_vertices.clear();
1039                 fan_tex_coords.clear();
1040                 odd = true;
1041
1042                 // xglBegin(GL_TRIANGLE_FAN);
1043
1044                 in >> n1;
1045                 fan_vertices.push_back( n1 );
1046                 // xglNormal3dv(normals[n1]);
1047                 if ( in.get( c ) && c == '/' ) {
1048                     in >> tex;
1049                     fan_tex_coords.push_back( tex );
1050                     if ( scenery_version >= 0.4 ) {
1051                         if ( tex_width > 0 ) {
1052                             tclist[tex][0] *= (1000.0 / tex_width);
1053                         }
1054                         if ( tex_height > 0 ) {
1055                             tclist[tex][1] *= (1000.0 / tex_height);
1056                         }
1057                     }
1058                     pp.setx( tex_coords[tex][0] * (1000.0 / tex_width) );
1059                     pp.sety( tex_coords[tex][1] * (1000.0 / tex_height) );
1060                 } else {
1061                     in.putback( c );
1062                     pp = local_calc_tex_coords(nodes[n1], center);
1063                 }
1064                 // xglTexCoord2f(pp.x(), pp.y());
1065                 // xglVertex3dv(nodes[n1].get_n());
1066
1067                 in >> n2;
1068                 fan_vertices.push_back( n2 );
1069                 // xglNormal3dv(normals[n2]);
1070                 if ( in.get( c ) && c == '/' ) {
1071                     in >> tex;
1072                     fan_tex_coords.push_back( tex );
1073                     if ( scenery_version >= 0.4 ) {
1074                         if ( tex_width > 0 ) {
1075                             tclist[tex][0] *= (1000.0 / tex_width);
1076                         }
1077                         if ( tex_height > 0 ) {
1078                             tclist[tex][1] *= (1000.0 / tex_height);
1079                         }
1080                     }
1081                     pp.setx( tex_coords[tex][0] * (1000.0 / tex_width) );
1082                     pp.sety( tex_coords[tex][1] * (1000.0 / tex_height) );
1083                 } else {
1084                     in.putback( c );
1085                     pp = local_calc_tex_coords(nodes[n2], center);
1086                 }
1087                 // xglTexCoord2f(pp.x(), pp.y());
1088                 // xglVertex3dv(nodes[n2].get_n());
1089                 
1090                 // read all subsequent numbers until next thing isn't a number
1091                 while ( true ) {
1092                     in >> ::skipws;
1093
1094                     char c;
1095                     in.get(c);
1096                     in.putback(c);
1097                     if ( ! isdigit(c) || in.eof() ) {
1098                         break;
1099                     }
1100
1101                     in >> n3;
1102                     fan_vertices.push_back( n3 );
1103                     // cout << "  triangle = "
1104                     //      << n1 << "," << n2 << "," << n3
1105                     //      << endl;
1106                     // xglNormal3dv(normals[n3]);
1107                     if ( in.get( c ) && c == '/' ) {
1108                         in >> tex;
1109                         fan_tex_coords.push_back( tex );
1110                         if ( scenery_version >= 0.4 ) {
1111                             if ( tex_width > 0 ) {
1112                                 tclist[tex][0] *= (1000.0 / tex_width);
1113                             }
1114                             if ( tex_height > 0 ) {
1115                                 tclist[tex][1] *= (1000.0 / tex_height);
1116                             }
1117                         }
1118                         pp.setx( tex_coords[tex][0] * (1000.0 / tex_width) );
1119                         pp.sety( tex_coords[tex][1] * (1000.0 / tex_height) );
1120                     } else {
1121                         in.putback( c );
1122                         pp = local_calc_tex_coords(nodes[n3], center);
1123                     }
1124                     // xglTexCoord2f(pp.x(), pp.y());
1125                     // xglVertex3dv(nodes[n3].get_n());
1126
1127                     if ( (token == "tf") || (token == "f") ) {
1128                         // triangle fan
1129                         n2 = n3;
1130                     } else {
1131                         // triangle strip
1132                         odd = !odd;
1133                         n1 = n2;
1134                         n2 = n3;
1135                     }
1136                 }
1137
1138                 // xglEnd();
1139
1140                 // build the ssg entity
1141                 int size = (int)fan_vertices.size();
1142                 ssgVertexArray   *vl = new ssgVertexArray( size );
1143                 ssgNormalArray   *nl = new ssgNormalArray( size );
1144                 ssgTexCoordArray *tl = new ssgTexCoordArray( size );
1145                 ssgColourArray   *cl = new ssgColourArray( 1 );
1146
1147                 sgVec4 color;
1148                 sgSetVec4( color, 1.0, 1.0, 1.0, 1.0 );
1149                 cl->add( color );
1150
1151                 sgVec2 tmp2;
1152                 sgVec3 tmp3;
1153                 for ( i = 0; i < size; ++i ) {
1154                     sgCopyVec3( tmp3, vtlist[ fan_vertices[i] ] );
1155                     vl -> add( tmp3 );
1156
1157                     sgCopyVec3( tmp3, vnlist[ fan_vertices[i] ] );
1158                     nl -> add( tmp3 );
1159
1160                     sgCopyVec2( tmp2, tclist[ fan_tex_coords[i] ] );
1161                     tl -> add( tmp2 );
1162                 }
1163
1164                 ssgLeaf *leaf = NULL;
1165                 if ( token == "tf" ) {
1166                     // triangle fan
1167                     leaf = 
1168                         new ssgVtxTable ( GL_TRIANGLE_FAN, vl, nl, tl, cl );
1169                 } else if ( token == "ts" ) {
1170                     // triangle strip
1171                     leaf = 
1172                         new ssgVtxTable ( GL_TRIANGLE_STRIP, vl, nl, tl, cl );
1173                 } else if ( token == "f" ) {
1174                     // triangle
1175                     leaf = 
1176                         new ssgVtxTable ( GL_TRIANGLES, vl, nl, tl, cl );
1177                 }
1178                 // leaf->makeDList();
1179                 leaf->setState( state );
1180
1181                 tile->addKid( leaf );
1182
1183                 if ( is_base ) {
1184                     if ( coverage > 0.0 ) {
1185                         if ( coverage < 10000.0 ) {
1186                             SG_LOG(SG_INPUT, SG_ALERT, "Light coverage is "
1187                                    << coverage << ", pushing up to 10000");
1188                             coverage = 10000;
1189                         }
1190                         gen_random_surface_points(leaf, lights, coverage);
1191                     }
1192                 }
1193             } else {
1194                 SG_LOG( SG_TERRAIN, SG_WARN, "Unknown token in " 
1195                         << path << " = " << token );
1196             }
1197
1198             // eat white space before start of while loop so if we are
1199             // done with useful input it is noticed before hand.
1200             in >> ::skipws;
1201         }
1202     }
1203
1204     if ( is_base ) {
1205         t->nodes = nodes;
1206     }
1207
1208     // stopwatch.stop();
1209     // SG_LOG( SG_TERRAIN, SG_DEBUG, 
1210     //     "Loaded " << path << " in " 
1211     //     << stopwatch.elapsedSeconds() << " seconds" );
1212
1213     return tile;
1214 }
1215
1216
1217 ssgLeaf *gen_leaf( const string& path,
1218                    const GLenum ty, const string& material,
1219                    const point_list& nodes, const point_list& normals,
1220                    const point_list& texcoords,
1221                    const int_list& node_index,
1222                    const int_list& normal_index,
1223                    const int_list& tex_index,
1224                    const bool calc_lights, ssgVertexArray *lights )
1225 {
1226     double tex_width = 1000.0, tex_height = 1000.0;
1227     ssgSimpleState *state = NULL;
1228     float coverage = -1;
1229
1230     FGNewMat *newmat = material_lib.find( material );
1231     if ( newmat == NULL ) {
1232         // see if this is an on the fly texture
1233         string file = path;
1234         string::size_type pos = file.rfind( "/" );
1235         file = file.substr( 0, pos );
1236         // cout << "current file = " << file << endl;
1237         file += "/";
1238         file += material;
1239         // cout << "current file = " << file << endl;
1240         if ( ! material_lib.add_item( file ) ) {
1241             SG_LOG( SG_TERRAIN, SG_ALERT, 
1242                     "Ack! unknown usemtl name = " << material 
1243                     << " in " << path );
1244         } else {
1245             // locate our newly created material
1246             newmat = material_lib.find( material );
1247             if ( newmat == NULL ) {
1248                 SG_LOG( SG_TERRAIN, SG_ALERT, 
1249                         "Ack! bad on the fly material create = "
1250                         << material << " in " << path );
1251             }
1252         }
1253     }
1254
1255     if ( newmat != NULL ) {
1256         // set the texture width and height values for this
1257         // material
1258         tex_width = newmat->get_xsize();
1259         tex_height = newmat->get_ysize();
1260         state = newmat->get_state();
1261         coverage = newmat->get_light_coverage();
1262         // cout << "(w) = " << tex_width << " (h) = "
1263         //      << tex_width << endl;
1264     } else {
1265         coverage = -1;
1266     }
1267
1268     sgVec2 tmp2;
1269     sgVec3 tmp3;
1270     sgVec4 tmp4;
1271     int i;
1272
1273     // vertices
1274     int size = node_index.size();
1275     if ( size < 1 ) {
1276         SG_LOG( SG_TERRAIN, SG_ALERT, "Woh! node list size < 1" );
1277         exit(-1);
1278     }
1279     ssgVertexArray *vl = new ssgVertexArray( size );
1280     Point3D node;
1281     for ( i = 0; i < size; ++i ) {
1282         node = nodes[ node_index[i] ];
1283         sgSetVec3( tmp3, node[0], node[1], node[2] );
1284         vl -> add( tmp3 );
1285     }
1286
1287     // normals
1288     Point3D normal;
1289     ssgNormalArray *nl = new ssgNormalArray( size );
1290     if ( normal_index.size() ) {
1291         // object file specifies normal indices (i.e. normal indices
1292         // aren't 'implied'
1293         for ( i = 0; i < size; ++i ) {
1294             normal = normals[ normal_index[i] ];
1295             sgSetVec3( tmp3, normal[0], normal[1], normal[2] );
1296             nl -> add( tmp3 );
1297         }
1298     } else {
1299         // use implied normal indices.  normal index = vertex index.
1300         for ( i = 0; i < size; ++i ) {
1301             normal = normals[ node_index[i] ];
1302             sgSetVec3( tmp3, normal[0], normal[1], normal[2] );
1303             nl -> add( tmp3 );
1304         }
1305     }
1306
1307     // colors
1308     ssgColourArray *cl = new ssgColourArray( 1 );
1309     sgSetVec4( tmp4, 1.0, 1.0, 1.0, 1.0 );
1310     cl->add( tmp4 );
1311
1312     // texture coordinates
1313     size = tex_index.size();
1314     Point3D texcoord;
1315     ssgTexCoordArray *tl = new ssgTexCoordArray( size );
1316     if ( size == 1 ) {
1317         texcoord = texcoords[ tex_index[0] ];
1318         sgSetVec2( tmp2, texcoord[0], texcoord[1] );
1319         sgSetVec2( tmp2, texcoord[0], texcoord[1] );
1320         if ( tex_width > 0 ) {
1321             tmp2[0] *= (1000.0 / tex_width);
1322         }
1323         if ( tex_height > 0 ) {
1324             tmp2[1] *= (1000.0 / tex_height);
1325         }
1326         tl -> add( tmp2 );
1327     } else if ( size > 1 ) {
1328         for ( i = 0; i < size; ++i ) {
1329             texcoord = texcoords[ tex_index[i] ];
1330             sgSetVec2( tmp2, texcoord[0], texcoord[1] );
1331             if ( tex_width > 0 ) {
1332                 tmp2[0] *= (1000.0 / tex_width);
1333             }
1334             if ( tex_height > 0 ) {
1335                 tmp2[1] *= (1000.0 / tex_height);
1336             }
1337             tl -> add( tmp2 );
1338         }
1339     }
1340
1341     ssgLeaf *leaf = new ssgVtxTable ( ty, vl, nl, tl, cl );
1342
1343     // lookup the state record
1344
1345     leaf->setState( state );
1346
1347     if ( calc_lights ) {
1348         if ( coverage > 0.0 ) {
1349             if ( coverage < 10000.0 ) {
1350                 SG_LOG(SG_INPUT, SG_ALERT, "Light coverage is "
1351                        << coverage << ", pushing up to 10000");
1352                 coverage = 10000;
1353             }
1354             gen_random_surface_points(leaf, lights, coverage);
1355         }
1356     }
1357
1358     return leaf;
1359 }
1360
1361
1362 // Load an Binary obj file
1363 bool fgBinObjLoad( const string& path, const bool is_base,
1364                    Point3D *center,
1365                    double *bounding_radius,
1366                    ssgBranch* geometry,
1367                    ssgBranch* rwy_lights,
1368                    ssgVertexArray *ground_lights )
1369 {
1370     SGBinObject obj;
1371     bool use_random_objects =
1372       fgGetBool("/sim/rendering/random-objects", true);
1373
1374     if ( ! obj.read_bin( path ) ) {
1375         return false;
1376     }
1377
1378     geometry->setName( (char *)path.c_str() );
1379
1380     // reference point (center offset/bounding sphere)
1381     *center = obj.get_gbs_center();
1382     *bounding_radius = obj.get_gbs_radius();
1383
1384     point_list const& nodes = obj.get_wgs84_nodes();
1385     point_list const& colors = obj.get_colors();
1386     point_list const& normals = obj.get_normals();
1387     point_list const& texcoords = obj.get_texcoords();
1388
1389     string material;
1390     int_list tex_index;
1391
1392     group_list::size_type i;
1393     bool is_lighting = false;
1394
1395     // generate points
1396     string_list const& pt_materials = obj.get_pt_materials();
1397     group_list const& pts_v = obj.get_pts_v();
1398     group_list const& pts_n = obj.get_pts_n();
1399     for ( i = 0; i < pts_v.size(); ++i ) {
1400         // cout << "pts_v.size() = " << pts_v.size() << endl;
1401         if ( pt_materials[i].substr(0, 3) == "RWY" ) {
1402             sgVec3 up;
1403             sgSetVec3( up, center->x(), center->y(), center->z() );
1404             ssgBranch *branch = gen_directional_lights( nodes, normals,
1405                                                         pts_v[i], pts_n[i],
1406                                                         up );
1407             float ranges[] = { 0, 12000 };
1408             branch->setCallback( SSG_CALLBACK_PREDRAW, runway_lights_predraw );
1409             ssgRangeSelector * lod = new ssgRangeSelector;
1410             lod->setRanges( ranges, 2 );
1411             lod->addKid( branch );
1412             rwy_lights->addKid( lod );
1413         } else {
1414             material = pt_materials[i];
1415             tex_index.clear();
1416             ssgLeaf *leaf = gen_leaf( path, GL_POINTS, material,
1417                                       nodes, normals, texcoords,
1418                                       pts_v[i], pts_n[i], tex_index,
1419                                       false, ground_lights );
1420             geometry->addKid( leaf );
1421         }
1422     }
1423
1424     // Put all randomly-placed objects under a separate branch
1425     // (actually an ssgRangeSelector) named "random-models".
1426     ssgBranch * random_object_branch = 0;
1427     if (use_random_objects) {
1428         float ranges[] = { 0, 20000 }; // Maximum 20km range for random objects
1429       ssgRangeSelector * object_lod = new ssgRangeSelector;
1430       object_lod->setRanges(ranges, 2);
1431       object_lod->setName("random-models");
1432       geometry->addKid(object_lod);
1433       random_object_branch = new ssgBranch;
1434       object_lod->addKid(random_object_branch);
1435     }
1436
1437     // generate triangles
1438     string_list const& tri_materials = obj.get_tri_materials();
1439     group_list const& tris_v = obj.get_tris_v();
1440     group_list const& tris_n = obj.get_tris_n();
1441     group_list const& tris_tc = obj.get_tris_tc();
1442     for ( i = 0; i < tris_v.size(); ++i ) {
1443         ssgLeaf *leaf = gen_leaf( path, GL_TRIANGLES, tri_materials[i],
1444                                   nodes, normals, texcoords,
1445                                   tris_v[i], tris_n[i], tris_tc[i],
1446                                   is_base, ground_lights );
1447
1448         if (use_random_objects)
1449           gen_random_surface_objects(leaf, random_object_branch,
1450                                      center, tri_materials[i]);
1451         geometry->addKid( leaf );
1452     }
1453
1454     // generate strips
1455     string_list const& strip_materials = obj.get_strip_materials();
1456     group_list const& strips_v = obj.get_strips_v();
1457     group_list const& strips_n = obj.get_strips_n();
1458     group_list const& strips_tc = obj.get_strips_tc();
1459     for ( i = 0; i < strips_v.size(); ++i ) {
1460         ssgLeaf *leaf = gen_leaf( path, GL_TRIANGLE_STRIP, strip_materials[i],
1461                                   nodes, normals, texcoords,
1462                                   strips_v[i], strips_n[i], strips_tc[i],
1463                                   is_base, ground_lights );
1464
1465         if (use_random_objects)
1466           gen_random_surface_objects(leaf, random_object_branch,
1467                                      center,strip_materials[i]);
1468         geometry->addKid( leaf );
1469     }
1470
1471     // generate fans
1472     string_list const& fan_materials = obj.get_fan_materials();
1473     group_list const& fans_v = obj.get_fans_v();
1474     group_list const& fans_n = obj.get_fans_n();
1475     group_list const& fans_tc = obj.get_fans_tc();
1476     for ( i = 0; i < fans_v.size(); ++i ) {
1477         ssgLeaf *leaf = gen_leaf( path, GL_TRIANGLE_FAN, fan_materials[i],
1478                                   nodes, normals, texcoords,
1479                                   fans_v[i], fans_n[i], fans_tc[i],
1480                                   is_base, ground_lights );
1481         if (use_random_objects)
1482           gen_random_surface_objects(leaf, random_object_branch,
1483                                      center, fan_materials[i]);
1484         geometry->addKid( leaf );
1485     }
1486
1487     return true;
1488 }