1 // obj.cxx -- routines to handle "sorta" WaveFront .obj format files.
3 // Written by Curtis Olson, started October 1997.
5 // Copyright (C) 1997 Curtis L. Olson - curt@infoplane.com
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.
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.
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.
28 #ifdef SG_MATH_EXCEPTION_CLASH
35 #include <simgear/compiler.h>
36 #include <simgear/sg_inlines.h>
37 #include <simgear/io/sg_binobj.hxx>
41 #include <vector> // STL
42 #include <ctype.h> // isdigit()
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>
55 #include <Main/globals.hxx>
56 #include <Main/fg_props.hxx>
57 #include <Time/light.hxx>
58 #include <Scenery/tileentry.hxx>
68 typedef vector < int > int_list;
69 typedef int_list::iterator int_list_iterator;
70 typedef int_list::const_iterator int_point_list_iterator;
73 static double normals[FG_MAX_NODES][3];
74 static double tex_coords[FG_MAX_NODES*3][3];
77 runway_lights_predraw (ssgEntity * e)
79 // Turn on lights only at night
80 float sun_angle = cur_light_params.sun_angle * SGD_RADIANS_TO_DEGREES;
81 return int((sun_angle > 90.0) ||
82 (fgGetDouble("/environment/visibility-m") < 5000.0));
86 #define FG_TEX_CONSTANT 69.0
88 // Calculate texture coordinates for a given point.
89 static Point3D local_calc_tex_coords(const Point3D& node, const Point3D& ref) {
92 // double tmplon, tmplat;
94 // cout << "-> " << node[0] << " " << node[1] << " " << node[2] << endl;
95 // cout << "-> " << ref.x() << " " << ref.y() << " " << ref.z() << endl;
97 cp = Point3D( node[0] + ref.x(),
101 pp = sgCartToPolar3d(cp);
103 // tmplon = pp.lon() * SGD_RADIANS_TO_DEGREES;
104 // tmplat = pp.lat() * SGD_RADIANS_TO_DEGREES;
105 // cout << tmplon << " " << tmplat << endl;
107 pp.setx( fmod(SGD_RADIANS_TO_DEGREES * FG_TEX_CONSTANT * pp.x(), 11.0) );
108 pp.sety( fmod(SGD_RADIANS_TO_DEGREES * FG_TEX_CONSTANT * pp.y(), 11.0) );
110 if ( pp.x() < 0.0 ) {
111 pp.setx( pp.x() + 11.0 );
114 if ( pp.y() < 0.0 ) {
115 pp.sety( pp.y() + 11.0 );
118 // cout << pp << endl;
124 // Generate an ocean tile
125 bool fgGenTile( const string& path, SGBucket b,
127 double *bounding_radius,
128 ssgBranch* geometry )
132 ssgSimpleState *state = NULL;
134 geometry -> setName ( (char *)path.c_str() ) ;
136 double tex_width = 1000.0;
137 // double tex_height;
139 // find Ocean material in the properties list
140 newmat = material_lib.find( "Ocean" );
141 if ( newmat != NULL ) {
142 // set the texture width and height values for this
144 tex_width = newmat->get_xsize();
145 // tex_height = newmat->get_ysize();
148 state = newmat->get_state();
150 SG_LOG( SG_TERRAIN, SG_ALERT,
151 "Ack! unknown usemtl name = " << "Ocean"
155 // Calculate center point
156 double clon = b.get_center_lon();
157 double clat = b.get_center_lat();
158 double height = b.get_height();
159 double width = b.get_width();
161 *center = sgGeodToCart( Point3D(clon*SGD_DEGREES_TO_RADIANS,
162 clat*SGD_DEGREES_TO_RADIANS,
164 // cout << "center = " << center << endl;;
166 // Caculate corner vertices
168 geod[0] = Point3D( clon - width/2.0, clat - height/2.0, 0.0 );
169 geod[1] = Point3D( clon + width/2.0, clat - height/2.0, 0.0 );
170 geod[2] = Point3D( clon + width/2.0, clat + height/2.0, 0.0 );
171 geod[3] = Point3D( clon - width/2.0, clat + height/2.0, 0.0 );
175 for ( i = 0; i < 4; ++i ) {
176 rad[i] = Point3D( geod[i].x() * SGD_DEGREES_TO_RADIANS,
177 geod[i].y() * SGD_DEGREES_TO_RADIANS,
181 Point3D cart[4], rel[4];
182 for ( i = 0; i < 4; ++i ) {
183 cart[i] = sgGeodToCart(rad[i]);
184 rel[i] = cart[i] - *center;
185 // cout << "corner " << i << " = " << cart[i] << endl;
188 // Calculate bounding radius
189 *bounding_radius = center->distance3D( cart[0] );
190 // cout << "bounding radius = " << t->bounding_radius << endl;
194 for ( i = 0; i < 4; ++i ) {
195 double length = cart[i].distance3D( Point3D(0.0) );
196 normals[i] = cart[i] / length;
197 // cout << "normal = " << normals[i] << endl;
200 // Calculate texture coordinates
201 point_list geod_nodes;
203 geod_nodes.reserve(4);
206 rectangle.reserve(4);
207 for ( i = 0; i < 4; ++i ) {
208 geod_nodes.push_back( geod[i] );
209 rectangle.push_back( i );
211 point_list texs = calc_tex_coords( b, geod_nodes, rectangle,
212 1000.0 / tex_width );
214 // Allocate ssg structure
215 ssgVertexArray *vl = new ssgVertexArray( 4 );
216 ssgNormalArray *nl = new ssgNormalArray( 4 );
217 ssgTexCoordArray *tl = new ssgTexCoordArray( 4 );
218 ssgColourArray *cl = new ssgColourArray( 1 );
221 sgSetVec4( color, 1.0, 1.0, 1.0, 1.0 );
224 // sgVec3 *vtlist = new sgVec3 [ 4 ];
225 // t->vec3_ptrs.push_back( vtlist );
226 // sgVec3 *vnlist = new sgVec3 [ 4 ];
227 // t->vec3_ptrs.push_back( vnlist );
228 // sgVec2 *tclist = new sgVec2 [ 4 ];
229 // t->vec2_ptrs.push_back( tclist );
233 for ( i = 0; i < 4; ++i ) {
235 rel[i].x(), rel[i].y(), rel[i].z() );
239 normals[i].x(), normals[i].y(), normals[i].z() );
242 sgSetVec2( tmp2, texs[i].x(), texs[i].y());
247 new ssgVtxTable ( GL_TRIANGLE_FAN, vl, nl, tl, cl );
249 leaf->setState( state );
251 geometry->addKid( leaf );
257 static void random_pt_inside_tri( float *res,
258 float *n1, float *n2, float *n3 )
260 double a = sg_random();
261 double b = sg_random();
266 double c = 1 - a - b;
268 res[0] = n1[0]*a + n2[0]*b + n3[0]*c;
269 res[1] = n1[1]*a + n2[1]*b + n3[1]*c;
270 res[2] = n1[2]*a + n2[2]*b + n3[2]*c;
274 static void gen_random_surface_points( ssgLeaf *leaf, ssgVertexArray *lights,
276 int num = leaf->getNumTriangles();
278 short int n1, n2, n3;
282 // generate a repeatable random seed
283 p1 = leaf->getVertex( 0 );
284 unsigned int seed = (unsigned int)(fabs(p1[0]*100));
287 for ( int i = 0; i < num; ++i ) {
288 leaf->getTriangle( i, &n1, &n2, &n3 );
289 p1 = leaf->getVertex(n1);
290 p2 = leaf->getVertex(n2);
291 p3 = leaf->getVertex(n3);
292 double area = sgTriArea( p1, p2, p3 );
293 double num = area / factor;
295 // generate a light point for each unit of area
296 while ( num > 1.0 ) {
297 random_pt_inside_tri( result, p1, p2, p3 );
298 lights->add( result );
301 // for partial units of area, use a zombie door method to
302 // create the proper random chance of a light being created
305 if ( sg_random() <= num ) {
306 // a zombie made it through our door
307 random_pt_inside_tri( result, p1, p2, p3 );
308 lights->add( result );
317 * User data for populating leaves when they come in range.
319 class LeafUserData : public ssgBase
331 void setup_triangle( int i );
336 * User data for populating triangles when they come in range.
338 class TriUserData : public ssgBase
347 FGNewMat::ObjectGroup * object_group;
349 LeafUserData * leafData;
352 void fill_in_triangle();
353 void add_object_to_triangle(FGNewMat::Object * object);
354 void makeWorldMatrix (sgMat4 ROT, double hdg_deg );
359 * Fill in a triangle with randomly-placed objects.
361 * This method is invoked by a callback when the triangle is in range
362 * but not yet populated.
366 void TriUserData::fill_in_triangle ()
368 // generate a repeatable random seed
371 int nObjects = object_group->get_object_count();
373 for (int i = 0; i < nObjects; i++) {
374 FGNewMat::Object * object = object_group->get_object(i);
375 double num = area / object->get_coverage_m2();
377 // place an object each unit of area
378 while ( num > 1.0 ) {
379 add_object_to_triangle(object);
382 // for partial units of area, use a zombie door method to
383 // create the proper random chance of an object being created
386 if ( sg_random() <= num ) {
387 // a zombie made it through our door
388 add_object_to_triangle(object);
394 void TriUserData::add_object_to_triangle (FGNewMat::Object * object)
396 // Set up the random heading if required.
398 if (object->get_heading_type() == FGNewMat::Object::HEADING_RANDOM)
399 hdg_deg = sg_random() * 360;
402 makeWorldMatrix(mat, hdg_deg);
404 ssgTransform * pos = new ssgTransform;
405 pos->setTransform(mat);
406 pos->addKid(object->get_random_model());
410 void TriUserData::makeWorldMatrix (sgMat4 mat, double hdg_deg )
413 mat[0][0] = leafData->sin_lat * leafData->cos_lon;
414 mat[0][1] = leafData->sin_lat * leafData->sin_lon;
415 mat[0][2] = -leafData->cos_lat;
418 mat[1][0] = -leafData->sin_lon;
419 mat[1][1] = leafData->cos_lon;
423 float sin_hdg = sin( hdg_deg * SGD_DEGREES_TO_RADIANS ) ;
424 float cos_hdg = cos( hdg_deg * SGD_DEGREES_TO_RADIANS ) ;
425 mat[0][0] = cos_hdg * leafData->sin_lat * leafData->cos_lon - sin_hdg * leafData->sin_lon;
426 mat[0][1] = cos_hdg * leafData->sin_lat * leafData->sin_lon + sin_hdg * leafData->cos_lon;
427 mat[0][2] = -cos_hdg * leafData->cos_lat;
430 mat[1][0] = -sin_hdg * leafData->sin_lat * leafData->cos_lon - cos_hdg * leafData->sin_lon;
431 mat[1][1] = -sin_hdg * leafData->sin_lat * leafData->sin_lon + cos_hdg * leafData->cos_lon;
432 mat[1][2] = sin_hdg * leafData->cos_lat;
436 mat[2][0] = leafData->cos_lat * leafData->cos_lon;
437 mat[2][1] = leafData->cos_lat * leafData->sin_lon;
438 mat[2][2] = leafData->sin_lat;
441 // translate to random point in triangle
443 random_pt_inside_tri(result, p1, p2, p3);
444 sgSubVec3(mat[3], result, center);
450 * SSG callback for an in-range triangle of randomly-placed objects.
452 * This pretraversal callback is attached to a branch that is traversed
453 * only when a triangle is in range. If the triangle is not currently
454 * populated with randomly-placed objects, this callback will populate
457 * @param entity The entity to which the callback is attached (not used).
458 * @param mask The entity's traversal mask (not used).
459 * @return Always 1, to allow traversal and culling to continue.
462 tri_in_range_callback (ssgEntity * entity, int mask)
464 TriUserData * data = (TriUserData *)entity->getUserData();
465 if (!data->is_filled_in) {
466 data->fill_in_triangle();
467 data->is_filled_in = true;
474 * SSG callback for an out-of-range triangle of randomly-placed objects.
476 * This pretraversal callback is attached to a branch that is traversed
477 * only when a triangle is out of range. If the triangle is currently
478 * populated with randomly-placed objects, the objects will be removed.
481 * @param entity The entity to which the callback is attached (not used).
482 * @param mask The entity's traversal mask (not used).
483 * @return Always 0, to prevent any further traversal or culling.
486 tri_out_of_range_callback (ssgEntity * entity, int mask)
488 TriUserData * data = (TriUserData *)entity->getUserData();
489 if (data->is_filled_in) {
490 data->branch->removeAllKids();
491 data->is_filled_in = false;
498 * ssgEntity with a dummy bounding sphere, to fool culling.
500 * This forces the in-range and out-of-range branches to be visited
501 * when appropriate, even if they have no children. It's ugly, but
502 * it works and seems fairly efficient (since branches can still
503 * be culled when they're out of the view frustum).
505 class DummyBSphereEntity : public ssgEntity
508 DummyBSphereEntity (float radius)
510 bsphere.setCenter(0, 0, 0);
511 bsphere.setRadius(radius);
513 virtual ~DummyBSphereEntity () {}
514 virtual void recalcBSphere () { bsphere_is_invalid = false; }
515 virtual void cull (sgFrustum *f, sgMat4 m, int test_needed) {}
516 virtual void isect (sgSphere *s, sgMat4 m, int test_needed) {}
517 virtual void hot (sgVec3 s, sgMat4 m, int test_needed) {}
518 virtual void los (sgVec3 s, sgMat4 m, int test_needed) {}
523 * Calculate the bounding radius of a triangle from its center.
525 * @param center The triangle center.
526 * @param p1 The first point in the triangle.
527 * @param p2 The second point in the triangle.
528 * @param p3 The third point in the triangle.
529 * @return The greatest distance any point lies from the center.
532 get_bounding_radius( sgVec3 center, float *p1, float *p2, float *p3)
534 return sqrt( SG_MAX3( sgDistanceSquaredVec3(center, p1),
535 sgDistanceSquaredVec3(center, p2),
536 sgDistanceSquaredVec3(center, p3) ) );
541 * Set up a triangle for randomly-placed objects.
543 * No objects will be added unless the triangle comes into range.
547 void LeafUserData::setup_triangle (int i )
550 leaf->getTriangle(i, &n1, &n2, &n3);
552 float * p1 = leaf->getVertex(n1);
553 float * p2 = leaf->getVertex(n2);
554 float * p3 = leaf->getVertex(n3);
556 // Set up a single center point for LOD
559 (p1[0] + p2[0] + p3[0]) / 3.0,
560 (p1[1] + p2[1] + p3[1]) / 3.0,
561 (p1[2] + p2[2] + p3[2]) / 3.0);
562 double area = sgTriArea(p1, p2, p3);
564 // maximum radius of an object from center.
565 double bounding_radius = get_bounding_radius(center, p1, p2, p3);
567 // Set up a transformation to the center
568 // point, so that everything else can
569 // be specified relative to it.
570 ssgTransform * location = new ssgTransform;
572 sgMakeTransMat4(TRANS, center);
573 location->setTransform(TRANS);
574 branch->addKid(location);
576 // Iterate through all the object types.
577 int num_groups = mat->get_object_group_count();
578 for (int j = 0; j < num_groups; j++) {
579 // Look up the random object.
580 FGNewMat::ObjectGroup * group = mat->get_object_group(j);
582 // Set up the range selector for the entire
583 // triangle; note that we use the object
584 // range plus the bounding radius here, to
585 // allow for objects far from the center.
586 float ranges[] = { 0,
587 group->get_range_m() + bounding_radius,
589 ssgRangeSelector * lod = new ssgRangeSelector;
590 lod->setRanges(ranges, 3);
591 location->addKid(lod);
593 // Create the in-range and out-of-range
595 ssgBranch * in_range = new ssgBranch;
596 ssgBranch * out_of_range = new ssgBranch;
598 // Set up the user data for if/when
599 // the random objects in this triangle
601 TriUserData * data = new TriUserData;
602 data->is_filled_in = false;
606 sgCopyVec3 (data->center, center);
608 data->object_group = group;
609 data->branch = in_range;
610 data->leafData = this;
611 data->seed = (unsigned int)(p1[0] * j);
613 // Set up the in-range node.
614 in_range->setUserData(data);
615 in_range->setTravCallback(SSG_CALLBACK_PRETRAV,
616 tri_in_range_callback);
617 lod->addKid(in_range);
619 // Set up the out-of-range node.
620 out_of_range->setUserData(data);
621 out_of_range->setTravCallback(SSG_CALLBACK_PRETRAV,
622 tri_out_of_range_callback);
623 out_of_range->addKid(new DummyBSphereEntity(bounding_radius));
624 lod->addKid(out_of_range);
629 * SSG callback for an in-range leaf of randomly-placed objects.
631 * This pretraversal callback is attached to a branch that is
632 * traversed only when a leaf is in range. If the leaf is not
633 * currently prepared to be populated with randomly-placed objects,
634 * this callback will prepare it (actual population is handled by
635 * the tri_in_range_callback for individual triangles).
637 * @param entity The entity to which the callback is attached (not used).
638 * @param mask The entity's traversal mask (not used).
639 * @return Always 1, to allow traversal and culling to continue.
642 leaf_in_range_callback (ssgEntity * entity, int mask)
644 LeafUserData * data = (LeafUserData *)entity->getUserData();
646 if (!data->is_filled_in) {
647 // Iterate through all the triangles
648 // and populate them.
649 int num_tris = data->leaf->getNumTriangles();
650 for ( int i = 0; i < num_tris; ++i ) {
651 data->setup_triangle(i);
653 data->is_filled_in = true;
660 * SSG callback for an out-of-range leaf of randomly-placed objects.
662 * This pretraversal callback is attached to a branch that is
663 * traversed only when a leaf is out of range. If the leaf is
664 * currently prepared to be populated with randomly-placed objects (or
665 * is actually populated), the objects will be removed.
667 * @param entity The entity to which the callback is attached (not used).
668 * @param mask The entity's traversal mask (not used).
669 * @return Always 0, to prevent any further traversal or culling.
672 leaf_out_of_range_callback (ssgEntity * entity, int mask)
674 LeafUserData * data = (LeafUserData *)entity->getUserData();
675 if (data->is_filled_in) {
676 data->branch->removeAllKids();
677 data->is_filled_in = false;
684 * Randomly place objects on a surface.
686 * The leaf node provides the geometry of the surface, while the
687 * material provides the objects and placement density. Latitude
688 * and longitude are required so that the objects can be rotated
689 * to the world-up vector. This function does not actually add
690 * any objects; instead, it attaches an ssgRangeSelector to the
691 * branch with callbacks to generate the objects when needed.
693 * @param leaf The surface where the objects should be placed.
694 * @param branch The branch that will hold the randomly-placed objects.
695 * @param center The center of the leaf in FlightGear coordinates.
696 * @param material_name The name of the surface's material.
699 gen_random_surface_objects (ssgLeaf *leaf,
702 const string &material_name)
704 // If the surface has no triangles, return
706 int num_tris = leaf->getNumTriangles();
710 // Get the material for this surface.
711 FGNewMat * mat = material_lib.find(material_name);
713 SG_LOG(SG_INPUT, SG_ALERT, "Unknown material " << material_name);
717 // If the material has no randomly-placed
718 // objects, return now.
719 if (mat->get_object_group_count() < 1)
722 // Calculate the geodetic centre of
723 // the tile, for aligning automatic
725 double lon_deg, lat_rad, lat_deg, alt_m, sl_radius_m;
726 Point3D geoc = sgCartToPolar3d(*center);
727 lon_deg = geoc.lon() * SGD_RADIANS_TO_DEGREES;
728 sgGeocToGeod(geoc.lat(), geoc.radius(),
729 &lat_rad, &alt_m, &sl_radius_m);
730 lat_deg = lat_rad * SGD_RADIANS_TO_DEGREES;
733 // max random object range: 20000m
734 float ranges[] = { 0, 20000, 1000000 };
735 ssgRangeSelector * lod = new ssgRangeSelector;
736 lod->setRanges(ranges, 3);
739 // Create the in-range and out-of-range
741 ssgBranch * in_range = new ssgBranch;
742 ssgBranch * out_of_range = new ssgBranch;
743 lod->addKid(in_range);
744 lod->addKid(out_of_range);
746 LeafUserData * data = new LeafUserData;
747 data->is_filled_in = false;
750 data->branch = in_range;
751 data->sin_lat = sin(lat_deg * SGD_DEGREES_TO_RADIANS);
752 data->cos_lat = cos(lat_deg * SGD_DEGREES_TO_RADIANS);
753 data->sin_lon = sin(lon_deg * SGD_DEGREES_TO_RADIANS);
754 data->cos_lon = cos(lon_deg * SGD_DEGREES_TO_RADIANS);
756 in_range->setUserData(data);
757 in_range->setTravCallback(SSG_CALLBACK_PRETRAV, leaf_in_range_callback);
758 out_of_range->setUserData(data);
759 out_of_range->setTravCallback(SSG_CALLBACK_PRETRAV,
760 leaf_out_of_range_callback);
762 ->addKid(new DummyBSphereEntity(leaf->getBSphere()->getRadius()));
767 ////////////////////////////////////////////////////////////////////////
769 ////////////////////////////////////////////////////////////////////////
772 // Load an Ascii obj file
773 ssgBranch *fgAsciiObjLoad( const string& path, FGTileEntry *t,
774 ssgVertexArray *lights, const bool is_base)
776 FGNewMat *newmat = NULL;
780 // sgVec3 approx_normal;
781 // double normal[3], scale = 0.0;
782 // double x, y, z, xmax, xmin, ymax, ymin, zmax, zmin;
783 // GLfloat sgenparams[] = { 1.0, 0.0, 0.0, 0.0 };
784 // GLint display_list = 0;
786 bool in_faces = false;
787 int vncount, vtcount;
788 int n1 = 0, n2 = 0, n3 = 0;
790 // int last1 = 0, last2 = 0;
795 double scenery_version = 0.0;
796 double tex_width = 1000.0, tex_height = 1000.0;
797 bool shared_done = false;
798 int_list fan_vertices;
799 int_list fan_tex_coords;
801 ssgSimpleState *state = NULL;
802 sgVec3 *vtlist, *vnlist;
805 ssgBranch *tile = new ssgBranch () ;
807 tile -> setName ( (char *)path.c_str() ) ;
809 // Attempt to open "path.gz" or "path"
810 sg_gzifstream in( path );
811 if ( ! in.is_open() ) {
812 SG_LOG( SG_TERRAIN, SG_DEBUG, "Cannot open file: " << path );
813 SG_LOG( SG_TERRAIN, SG_DEBUG, "default to ocean tile: " << path );
820 shading = fgGetBool("/sim/rendering/shading");
828 t->bounding_radius = 0.0;
832 // StopWatch stopwatch;
833 // stopwatch.start();
835 // ignore initial comments and blank lines. (priming the pump)
836 // in >> skipcomment;
843 while ( in.get(c) && c != '\0' ) {
846 while ( ! in.eof() ) {
850 if ( in.get( c ) && c == '#' ) {
851 // process a comment line
853 // getline( in, line );
854 // cout << "comment = " << line << endl;
858 if ( token == "Version" ) {
859 // read scenery versions number
860 in >> scenery_version;
861 // cout << "scenery_version = " << scenery_version << endl;
862 if ( scenery_version > 0.4 ) {
863 SG_LOG( SG_TERRAIN, SG_ALERT,
864 "\nYou are attempting to load a tile format that\n"
865 << "is newer than this version of flightgear can\n"
866 << "handle. You should upgrade your copy of\n"
867 << "FlightGear to the newest version. For\n"
868 << "details, please see:\n"
869 << "\n http://www.flightgear.org\n" );
872 } else if ( token == "gbs" ) {
873 // reference point (center offset)
875 in >> t->center >> t->bounding_radius;
879 in >> junk1 >> junk2;
882 // cout << "center = " << center
883 // << " radius = " << t->bounding_radius << endl;
884 } else if ( token == "bs" ) {
885 // reference point (center offset)
889 in >> junk1 >> junk2;
890 } else if ( token == "usemtl" ) {
891 // material property specification
893 // if first usemtl with shared_done = false, then set
894 // shared_done true and build the ssg shared lists
895 if ( ! shared_done ) {
897 if ( (int)nodes.size() != vncount ) {
898 SG_LOG( SG_TERRAIN, SG_ALERT,
899 "Tile has mismatched nodes = " << nodes.size()
900 << " and normals = " << vncount << " : "
906 vtlist = new sgVec3 [ nodes.size() ];
907 t->vec3_ptrs.push_back( vtlist );
908 vnlist = new sgVec3 [ vncount ];
909 t->vec3_ptrs.push_back( vnlist );
910 tclist = new sgVec2 [ vtcount ];
911 t->vec2_ptrs.push_back( tclist );
913 for ( i = 0; i < (int)nodes.size(); ++i ) {
914 sgSetVec3( vtlist[i],
915 nodes[i][0], nodes[i][1], nodes[i][2] );
917 for ( i = 0; i < vncount; ++i ) {
918 sgSetVec3( vnlist[i],
923 for ( i = 0; i < vtcount; ++i ) {
924 sgSetVec2( tclist[i],
930 // display_list = xglGenLists(1);
931 // xglNewList(display_list, GL_COMPILE);
932 // printf("xglGenLists(); xglNewList();\n");
935 // scan the material line
938 // find this material in the properties list
940 newmat = material_lib.find( material );
941 if ( newmat == NULL ) {
942 // see if this is an on the fly texture
944 int pos = file.rfind( "/" );
945 file = file.substr( 0, pos );
946 // cout << "current file = " << file << endl;
949 // cout << "current file = " << file << endl;
950 if ( ! material_lib.add_item( file ) ) {
951 SG_LOG( SG_TERRAIN, SG_ALERT,
952 "Ack! unknown usemtl name = " << material
955 // locate our newly created material
956 newmat = material_lib.find( material );
957 if ( newmat == NULL ) {
958 SG_LOG( SG_TERRAIN, SG_ALERT,
959 "Ack! bad on the fly materia create = "
960 << material << " in " << path );
965 if ( newmat != NULL ) {
966 // set the texture width and height values for this
968 tex_width = newmat->get_xsize();
969 tex_height = newmat->get_ysize();
970 state = newmat->get_state();
971 coverage = newmat->get_light_coverage();
972 // cout << "(w) = " << tex_width << " (h) = "
973 // << tex_width << endl;
978 // unknown comment, just gobble the input until the
988 // cout << "token = " << token << endl;
990 if ( token == "vn" ) {
992 if ( vncount < FG_MAX_NODES ) {
993 in >> normals[vncount][0]
994 >> normals[vncount][1]
995 >> normals[vncount][2];
998 SG_LOG( SG_TERRAIN, SG_ALERT,
999 "Read too many vertex normals in " << path
1000 << " ... dying :-(" );
1003 } else if ( token == "vt" ) {
1004 // vertex texture coordinate
1005 if ( vtcount < FG_MAX_NODES*3 ) {
1006 in >> tex_coords[vtcount][0]
1007 >> tex_coords[vtcount][1];
1010 SG_LOG( SG_TERRAIN, SG_ALERT,
1011 "Read too many vertex texture coords in " << path
1016 } else if ( token == "v" ) {
1018 if ( t->ncount < FG_MAX_NODES ) {
1019 /* in >> nodes[t->ncount][0]
1020 >> nodes[t->ncount][1]
1021 >> nodes[t->ncount][2]; */
1023 nodes.push_back(node);
1028 SG_LOG( SG_TERRAIN, SG_ALERT,
1029 "Read too many nodes in " << path
1030 << " ... dying :-(");
1033 } else if ( (token == "tf") || (token == "ts") || (token == "f") ) {
1034 // triangle fan, strip, or individual face
1035 // SG_LOG( SG_TERRAIN, SG_INFO, "new fan or strip");
1037 fan_vertices.clear();
1038 fan_tex_coords.clear();
1041 // xglBegin(GL_TRIANGLE_FAN);
1044 fan_vertices.push_back( n1 );
1045 // xglNormal3dv(normals[n1]);
1046 if ( in.get( c ) && c == '/' ) {
1048 fan_tex_coords.push_back( tex );
1049 if ( scenery_version >= 0.4 ) {
1050 if ( tex_width > 0 ) {
1051 tclist[tex][0] *= (1000.0 / tex_width);
1053 if ( tex_height > 0 ) {
1054 tclist[tex][1] *= (1000.0 / tex_height);
1057 pp.setx( tex_coords[tex][0] * (1000.0 / tex_width) );
1058 pp.sety( tex_coords[tex][1] * (1000.0 / tex_height) );
1061 pp = local_calc_tex_coords(nodes[n1], center);
1063 // xglTexCoord2f(pp.x(), pp.y());
1064 // xglVertex3dv(nodes[n1].get_n());
1067 fan_vertices.push_back( n2 );
1068 // xglNormal3dv(normals[n2]);
1069 if ( in.get( c ) && c == '/' ) {
1071 fan_tex_coords.push_back( tex );
1072 if ( scenery_version >= 0.4 ) {
1073 if ( tex_width > 0 ) {
1074 tclist[tex][0] *= (1000.0 / tex_width);
1076 if ( tex_height > 0 ) {
1077 tclist[tex][1] *= (1000.0 / tex_height);
1080 pp.setx( tex_coords[tex][0] * (1000.0 / tex_width) );
1081 pp.sety( tex_coords[tex][1] * (1000.0 / tex_height) );
1084 pp = local_calc_tex_coords(nodes[n2], center);
1086 // xglTexCoord2f(pp.x(), pp.y());
1087 // xglVertex3dv(nodes[n2].get_n());
1089 // read all subsequent numbers until next thing isn't a number
1096 if ( ! isdigit(c) || in.eof() ) {
1101 fan_vertices.push_back( n3 );
1102 // cout << " triangle = "
1103 // << n1 << "," << n2 << "," << n3
1105 // xglNormal3dv(normals[n3]);
1106 if ( in.get( c ) && c == '/' ) {
1108 fan_tex_coords.push_back( tex );
1109 if ( scenery_version >= 0.4 ) {
1110 if ( tex_width > 0 ) {
1111 tclist[tex][0] *= (1000.0 / tex_width);
1113 if ( tex_height > 0 ) {
1114 tclist[tex][1] *= (1000.0 / tex_height);
1117 pp.setx( tex_coords[tex][0] * (1000.0 / tex_width) );
1118 pp.sety( tex_coords[tex][1] * (1000.0 / tex_height) );
1121 pp = local_calc_tex_coords(nodes[n3], center);
1123 // xglTexCoord2f(pp.x(), pp.y());
1124 // xglVertex3dv(nodes[n3].get_n());
1126 if ( (token == "tf") || (token == "f") ) {
1139 // build the ssg entity
1140 int size = (int)fan_vertices.size();
1141 ssgVertexArray *vl = new ssgVertexArray( size );
1142 ssgNormalArray *nl = new ssgNormalArray( size );
1143 ssgTexCoordArray *tl = new ssgTexCoordArray( size );
1144 ssgColourArray *cl = new ssgColourArray( 1 );
1147 sgSetVec4( color, 1.0, 1.0, 1.0, 1.0 );
1152 for ( i = 0; i < size; ++i ) {
1153 sgCopyVec3( tmp3, vtlist[ fan_vertices[i] ] );
1156 sgCopyVec3( tmp3, vnlist[ fan_vertices[i] ] );
1159 sgCopyVec2( tmp2, tclist[ fan_tex_coords[i] ] );
1163 ssgLeaf *leaf = NULL;
1164 if ( token == "tf" ) {
1167 new ssgVtxTable ( GL_TRIANGLE_FAN, vl, nl, tl, cl );
1168 } else if ( token == "ts" ) {
1171 new ssgVtxTable ( GL_TRIANGLE_STRIP, vl, nl, tl, cl );
1172 } else if ( token == "f" ) {
1175 new ssgVtxTable ( GL_TRIANGLES, vl, nl, tl, cl );
1177 // leaf->makeDList();
1178 leaf->setState( state );
1180 tile->addKid( leaf );
1183 if ( coverage > 0.0 ) {
1184 if ( coverage < 10000.0 ) {
1185 SG_LOG(SG_INPUT, SG_ALERT, "Light coverage is "
1186 << coverage << ", pushing up to 10000");
1189 gen_random_surface_points(leaf, lights, coverage);
1193 SG_LOG( SG_TERRAIN, SG_WARN, "Unknown token in "
1194 << path << " = " << token );
1197 // eat white space before start of while loop so if we are
1198 // done with useful input it is noticed before hand.
1207 // stopwatch.stop();
1208 // SG_LOG( SG_TERRAIN, SG_DEBUG,
1209 // "Loaded " << path << " in "
1210 // << stopwatch.elapsedSeconds() << " seconds" );
1216 ssgLeaf *gen_leaf( const string& path,
1217 const GLenum ty, const string& material,
1218 const point_list& nodes, const point_list& normals,
1219 const point_list& texcoords,
1220 const int_list& node_index,
1221 const int_list& normal_index,
1222 const int_list& tex_index,
1223 const bool calc_lights, ssgVertexArray *lights )
1225 double tex_width = 1000.0, tex_height = 1000.0;
1226 ssgSimpleState *state = NULL;
1227 float coverage = -1;
1229 FGNewMat *newmat = material_lib.find( material );
1230 if ( newmat == NULL ) {
1231 // see if this is an on the fly texture
1233 string::size_type pos = file.rfind( "/" );
1234 file = file.substr( 0, pos );
1235 // cout << "current file = " << file << endl;
1238 // cout << "current file = " << file << endl;
1239 if ( ! material_lib.add_item( file ) ) {
1240 SG_LOG( SG_TERRAIN, SG_ALERT,
1241 "Ack! unknown usemtl name = " << material
1242 << " in " << path );
1244 // locate our newly created material
1245 newmat = material_lib.find( material );
1246 if ( newmat == NULL ) {
1247 SG_LOG( SG_TERRAIN, SG_ALERT,
1248 "Ack! bad on the fly material create = "
1249 << material << " in " << path );
1254 if ( newmat != NULL ) {
1255 // set the texture width and height values for this
1257 tex_width = newmat->get_xsize();
1258 tex_height = newmat->get_ysize();
1259 state = newmat->get_state();
1260 coverage = newmat->get_light_coverage();
1261 // cout << "(w) = " << tex_width << " (h) = "
1262 // << tex_width << endl;
1273 int size = node_index.size();
1275 SG_LOG( SG_TERRAIN, SG_ALERT, "Woh! node list size < 1" );
1278 ssgVertexArray *vl = new ssgVertexArray( size );
1280 for ( i = 0; i < size; ++i ) {
1281 node = nodes[ node_index[i] ];
1282 sgSetVec3( tmp3, node[0], node[1], node[2] );
1288 ssgNormalArray *nl = new ssgNormalArray( size );
1289 if ( normal_index.size() ) {
1290 // object file specifies normal indices (i.e. normal indices
1292 for ( i = 0; i < size; ++i ) {
1293 normal = normals[ normal_index[i] ];
1294 sgSetVec3( tmp3, normal[0], normal[1], normal[2] );
1298 // use implied normal indices. normal index = vertex index.
1299 for ( i = 0; i < size; ++i ) {
1300 normal = normals[ node_index[i] ];
1301 sgSetVec3( tmp3, normal[0], normal[1], normal[2] );
1307 ssgColourArray *cl = new ssgColourArray( 1 );
1308 sgSetVec4( tmp4, 1.0, 1.0, 1.0, 1.0 );
1311 // texture coordinates
1312 size = tex_index.size();
1314 ssgTexCoordArray *tl = new ssgTexCoordArray( size );
1316 texcoord = texcoords[ tex_index[0] ];
1317 sgSetVec2( tmp2, texcoord[0], texcoord[1] );
1318 sgSetVec2( tmp2, texcoord[0], texcoord[1] );
1319 if ( tex_width > 0 ) {
1320 tmp2[0] *= (1000.0 / tex_width);
1322 if ( tex_height > 0 ) {
1323 tmp2[1] *= (1000.0 / tex_height);
1326 } else if ( size > 1 ) {
1327 for ( i = 0; i < size; ++i ) {
1328 texcoord = texcoords[ tex_index[i] ];
1329 sgSetVec2( tmp2, texcoord[0], texcoord[1] );
1330 if ( tex_width > 0 ) {
1331 tmp2[0] *= (1000.0 / tex_width);
1333 if ( tex_height > 0 ) {
1334 tmp2[1] *= (1000.0 / tex_height);
1340 ssgLeaf *leaf = new ssgVtxTable ( ty, vl, nl, tl, cl );
1342 // lookup the state record
1344 leaf->setState( state );
1346 if ( calc_lights ) {
1347 if ( coverage > 0.0 ) {
1348 if ( coverage < 10000.0 ) {
1349 SG_LOG(SG_INPUT, SG_ALERT, "Light coverage is "
1350 << coverage << ", pushing up to 10000");
1353 gen_random_surface_points(leaf, lights, coverage);
1361 // Load an Binary obj file
1362 bool fgBinObjLoad( const string& path, const bool is_base,
1364 double *bounding_radius,
1365 ssgBranch* geometry,
1366 ssgBranch* rwy_lights,
1367 ssgVertexArray *ground_lights )
1370 bool use_random_objects =
1371 fgGetBool("/sim/rendering/random-objects", true);
1373 if ( ! obj.read_bin( path ) ) {
1377 geometry->setName( (char *)path.c_str() );
1380 // reference point (center offset/bounding sphere)
1381 *center = obj.get_gbs_center();
1382 *bounding_radius = obj.get_gbs_radius();
1386 point_list const& nodes = obj.get_wgs84_nodes();
1387 point_list const& colors = obj.get_colors();
1388 point_list const& normals = obj.get_normals();
1389 point_list const& texcoords = obj.get_texcoords();
1394 group_list::size_type i;
1395 bool is_lighting = false;
1398 string_list const& pt_materials = obj.get_pt_materials();
1399 group_list const& pts_v = obj.get_pts_v();
1400 group_list const& pts_n = obj.get_pts_n();
1401 for ( i = 0; i < pts_v.size(); ++i ) {
1402 // cout << "pts_v.size() = " << pts_v.size() << endl;
1403 if ( pt_materials[i].substr(0, 3) == "RWY" ) {
1404 material = "LIGHTS";
1407 material = pt_materials[i];
1410 ssgLeaf *leaf = gen_leaf( path, GL_POINTS, material,
1411 nodes, normals, texcoords,
1412 pts_v[i], pts_n[i], tex_index,
1413 false, ground_lights );
1415 if ( is_lighting ) {
1420 leaf->setCallback(SSG_CALLBACK_PREDRAW, runway_lights_predraw);
1421 ssgRangeSelector * lod = new ssgRangeSelector;
1422 lod->setRanges(ranges, 2);
1424 rwy_lights->addKid(lod);
1426 geometry->addKid( leaf );
1430 // Put all randomly-placed objects under a separate branch
1431 // (actually an ssgRangeSelector) named "random-models".
1432 ssgBranch * random_object_branch = 0;
1433 if (use_random_objects) {
1434 float ranges[] = { 0, 20000 }; // Maximum 20km range for random objects
1435 ssgRangeSelector * object_lod = new ssgRangeSelector;
1436 object_lod->setRanges(ranges, 2);
1437 object_lod->setName("random-models");
1438 geometry->addKid(object_lod);
1439 random_object_branch = new ssgBranch;
1440 object_lod->addKid(random_object_branch);
1443 // generate triangles
1444 string_list const& tri_materials = obj.get_tri_materials();
1445 group_list const& tris_v = obj.get_tris_v();
1446 group_list const& tris_n = obj.get_tris_n();
1447 group_list const& tris_tc = obj.get_tris_tc();
1448 for ( i = 0; i < tris_v.size(); ++i ) {
1449 ssgLeaf *leaf = gen_leaf( path, GL_TRIANGLES, tri_materials[i],
1450 nodes, normals, texcoords,
1451 tris_v[i], tris_n[i], tris_tc[i],
1452 is_base, ground_lights );
1454 if (use_random_objects)
1455 gen_random_surface_objects(leaf, random_object_branch,
1456 center, tri_materials[i]);
1457 geometry->addKid( leaf );
1461 string_list const& strip_materials = obj.get_strip_materials();
1462 group_list const& strips_v = obj.get_strips_v();
1463 group_list const& strips_n = obj.get_strips_n();
1464 group_list const& strips_tc = obj.get_strips_tc();
1465 for ( i = 0; i < strips_v.size(); ++i ) {
1466 ssgLeaf *leaf = gen_leaf( path, GL_TRIANGLE_STRIP, strip_materials[i],
1467 nodes, normals, texcoords,
1468 strips_v[i], strips_n[i], strips_tc[i],
1469 is_base, ground_lights );
1471 if (use_random_objects)
1472 gen_random_surface_objects(leaf, random_object_branch,
1473 center,strip_materials[i]);
1474 geometry->addKid( leaf );
1478 string_list const& fan_materials = obj.get_fan_materials();
1479 group_list const& fans_v = obj.get_fans_v();
1480 group_list const& fans_n = obj.get_fans_n();
1481 group_list const& fans_tc = obj.get_fans_tc();
1482 for ( i = 0; i < fans_v.size(); ++i ) {
1483 ssgLeaf *leaf = gen_leaf( path, GL_TRIANGLE_FAN, fan_materials[i],
1484 nodes, normals, texcoords,
1485 fans_v[i], fans_n[i], fans_tc[i],
1486 is_base, ground_lights );
1487 if (use_random_objects)
1488 gen_random_surface_objects(leaf, random_object_branch,
1489 center, fan_materials[i]);
1490 geometry->addKid( leaf );