// obj.cxx -- routines to handle "sorta" WaveFront .obj format files. // // Written by Curtis Olson, started October 1997. // // Copyright (C) 1997 Curtis L. Olson - curt@infoplane.com // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // // $Id$ #ifdef HAVE_CONFIG_H # include <config.h> #endif #ifdef SG_MATH_EXCEPTION_CLASH # include <math.h> #endif #include <stdio.h> #include <string.h> #include <simgear/compiler.h> #include <simgear/sg_inlines.h> #include <simgear/io/sg_binobj.hxx> #include STL_STRING #include <map> // STL #include <vector> // STL #include <ctype.h> // isdigit() #include <simgear/constants.h> #include <simgear/debug/logstream.hxx> #include <simgear/math/point3d.hxx> #include <simgear/math/polar3d.hxx> #include <simgear/math/sg_geodesy.hxx> #include <simgear/math/sg_random.h> #include <simgear/math/vector.hxx> #include <simgear/misc/sgstream.hxx> #include <simgear/misc/stopwatch.hxx> #include <simgear/misc/texcoord.hxx> #include <Main/globals.hxx> #include <Main/fg_props.hxx> #include <Time/light.hxx> #include <Scenery/tileentry.hxx> #include "newmat.hxx" #include "matlib.hxx" #include "pt_lights.hxx" #include "obj.hxx" SG_USING_STD(string); SG_USING_STD(vector); typedef vector < int > int_list; typedef int_list::iterator int_list_iterator; typedef int_list::const_iterator int_point_list_iterator; static double normals[FG_MAX_NODES][3]; static double tex_coords[FG_MAX_NODES*3][3]; static int runway_lights_predraw (ssgEntity * e) { // Turn on lights only at night float sun_angle = cur_light_params.sun_angle * SGD_RADIANS_TO_DEGREES; return int((sun_angle > 90.0) || (fgGetDouble("/environment/visibility-m") < 5000.0)); } #define FG_TEX_CONSTANT 69.0 // Calculate texture coordinates for a given point. static Point3D local_calc_tex_coords(const Point3D& node, const Point3D& ref) { Point3D cp; Point3D pp; // double tmplon, tmplat; // cout << "-> " << node[0] << " " << node[1] << " " << node[2] << endl; // cout << "-> " << ref.x() << " " << ref.y() << " " << ref.z() << endl; cp = Point3D( node[0] + ref.x(), node[1] + ref.y(), node[2] + ref.z() ); pp = sgCartToPolar3d(cp); // tmplon = pp.lon() * SGD_RADIANS_TO_DEGREES; // tmplat = pp.lat() * SGD_RADIANS_TO_DEGREES; // cout << tmplon << " " << tmplat << endl; pp.setx( fmod(SGD_RADIANS_TO_DEGREES * FG_TEX_CONSTANT * pp.x(), 11.0) ); pp.sety( fmod(SGD_RADIANS_TO_DEGREES * FG_TEX_CONSTANT * pp.y(), 11.0) ); if ( pp.x() < 0.0 ) { pp.setx( pp.x() + 11.0 ); } if ( pp.y() < 0.0 ) { pp.sety( pp.y() + 11.0 ); } // cout << pp << endl; return(pp); } // Generate an ocean tile bool fgGenTile( const string& path, SGBucket b, Point3D *center, double *bounding_radius, ssgBranch* geometry ) { FGNewMat *newmat; ssgSimpleState *state = NULL; geometry -> setName ( (char *)path.c_str() ) ; double tex_width = 1000.0; // double tex_height; // find Ocean material in the properties list newmat = material_lib.find( "Ocean" ); if ( newmat != NULL ) { // set the texture width and height values for this // material tex_width = newmat->get_xsize(); // tex_height = newmat->get_ysize(); // set ssgState state = newmat->get_state(); } else { SG_LOG( SG_TERRAIN, SG_ALERT, "Ack! unknown usemtl name = " << "Ocean" << " in " << path ); } // Calculate center point double clon = b.get_center_lon(); double clat = b.get_center_lat(); double height = b.get_height(); double width = b.get_width(); *center = sgGeodToCart( Point3D(clon*SGD_DEGREES_TO_RADIANS, clat*SGD_DEGREES_TO_RADIANS, 0.0) ); // cout << "center = " << center << endl;; // Caculate corner vertices Point3D geod[4]; geod[0] = Point3D( clon - width/2.0, clat - height/2.0, 0.0 ); geod[1] = Point3D( clon + width/2.0, clat - height/2.0, 0.0 ); geod[2] = Point3D( clon + width/2.0, clat + height/2.0, 0.0 ); geod[3] = Point3D( clon - width/2.0, clat + height/2.0, 0.0 ); Point3D rad[4]; int i; for ( i = 0; i < 4; ++i ) { rad[i] = Point3D( geod[i].x() * SGD_DEGREES_TO_RADIANS, geod[i].y() * SGD_DEGREES_TO_RADIANS, geod[i].z() ); } Point3D cart[4], rel[4]; for ( i = 0; i < 4; ++i ) { cart[i] = sgGeodToCart(rad[i]); rel[i] = cart[i] - *center; // cout << "corner " << i << " = " << cart[i] << endl; } // Calculate bounding radius *bounding_radius = center->distance3D( cart[0] ); // cout << "bounding radius = " << t->bounding_radius << endl; // Calculate normals Point3D normals[4]; for ( i = 0; i < 4; ++i ) { double length = cart[i].distance3D( Point3D(0.0) ); normals[i] = cart[i] / length; // cout << "normal = " << normals[i] << endl; } // Calculate texture coordinates point_list geod_nodes; geod_nodes.clear(); geod_nodes.reserve(4); int_list rectangle; rectangle.clear(); rectangle.reserve(4); for ( i = 0; i < 4; ++i ) { geod_nodes.push_back( geod[i] ); rectangle.push_back( i ); } point_list texs = calc_tex_coords( b, geod_nodes, rectangle, 1000.0 / tex_width ); // Allocate ssg structure ssgVertexArray *vl = new ssgVertexArray( 4 ); ssgNormalArray *nl = new ssgNormalArray( 4 ); ssgTexCoordArray *tl = new ssgTexCoordArray( 4 ); ssgColourArray *cl = new ssgColourArray( 1 ); sgVec4 color; sgSetVec4( color, 1.0, 1.0, 1.0, 1.0 ); cl->add( color ); // sgVec3 *vtlist = new sgVec3 [ 4 ]; // t->vec3_ptrs.push_back( vtlist ); // sgVec3 *vnlist = new sgVec3 [ 4 ]; // t->vec3_ptrs.push_back( vnlist ); // sgVec2 *tclist = new sgVec2 [ 4 ]; // t->vec2_ptrs.push_back( tclist ); sgVec2 tmp2; sgVec3 tmp3; for ( i = 0; i < 4; ++i ) { sgSetVec3( tmp3, rel[i].x(), rel[i].y(), rel[i].z() ); vl->add( tmp3 ); sgSetVec3( tmp3, normals[i].x(), normals[i].y(), normals[i].z() ); nl->add( tmp3 ); sgSetVec2( tmp2, texs[i].x(), texs[i].y()); tl->add( tmp2 ); } ssgLeaf *leaf = new ssgVtxTable ( GL_TRIANGLE_FAN, vl, nl, tl, cl ); leaf->setState( state ); geometry->addKid( leaf ); return true; } static void random_pt_inside_tri( float *res, float *n1, float *n2, float *n3 ) { double a = sg_random(); double b = sg_random(); if ( a + b > 1.0 ) { a = 1.0 - a; b = 1.0 - b; } double c = 1 - a - b; res[0] = n1[0]*a + n2[0]*b + n3[0]*c; res[1] = n1[1]*a + n2[1]*b + n3[1]*c; res[2] = n1[2]*a + n2[2]*b + n3[2]*c; } static void gen_random_surface_points( ssgLeaf *leaf, ssgVertexArray *lights, double factor ) { int num = leaf->getNumTriangles(); if ( num > 0 ) { short int n1, n2, n3; float *p1, *p2, *p3; sgVec3 result; // generate a repeatable random seed p1 = leaf->getVertex( 0 ); unsigned int seed = (unsigned int)(fabs(p1[0]*100)); sg_srandom( seed ); for ( int i = 0; i < num; ++i ) { leaf->getTriangle( i, &n1, &n2, &n3 ); p1 = leaf->getVertex(n1); p2 = leaf->getVertex(n2); p3 = leaf->getVertex(n3); double area = sgTriArea( p1, p2, p3 ); double num = area / factor; // generate a light point for each unit of area while ( num > 1.0 ) { random_pt_inside_tri( result, p1, p2, p3 ); lights->add( result ); num -= 1.0; } // for partial units of area, use a zombie door method to // create the proper random chance of a light being created // for this triangle if ( num > 0.0 ) { if ( sg_random() <= num ) { // a zombie made it through our door random_pt_inside_tri( result, p1, p2, p3 ); lights->add( result ); } } } } } /** * User data for populating leaves when they come in range. */ class LeafUserData : public ssgBase { public: bool is_filled_in; ssgLeaf * leaf; FGNewMat * mat; ssgBranch * branch; float sin_lat; float cos_lat; float sin_lon; float cos_lon; void setup_triangle( int i ); }; /** * User data for populating triangles when they come in range. */ class TriUserData : public ssgBase { public: bool is_filled_in; float * p1; float * p2; float * p3; sgVec3 center; double area; FGNewMat::ObjectGroup * object_group; ssgBranch * branch; LeafUserData * leafData; unsigned int seed; void fill_in_triangle(); void add_object_to_triangle(FGNewMat::Object * object); void makeWorldMatrix (sgMat4 ROT, double hdg_deg ); }; /** * Fill in a triangle with randomly-placed objects. * * This method is invoked by a callback when the triangle is in range * but not yet populated. * */ void TriUserData::fill_in_triangle () { // generate a repeatable random seed sg_srandom(seed); int nObjects = object_group->get_object_count(); for (int i = 0; i < nObjects; i++) { FGNewMat::Object * object = object_group->get_object(i); double num = area / object->get_coverage_m2(); // place an object each unit of area while ( num > 1.0 ) { add_object_to_triangle(object); num -= 1.0; } // for partial units of area, use a zombie door method to // create the proper random chance of an object being created // for this triangle if ( num > 0.0 ) { if ( sg_random() <= num ) { // a zombie made it through our door add_object_to_triangle(object); } } } } void TriUserData::add_object_to_triangle (FGNewMat::Object * object) { // Set up the random heading if required. double hdg_deg = 0; if (object->get_heading_type() == FGNewMat::Object::HEADING_RANDOM) hdg_deg = sg_random() * 360; sgMat4 mat; makeWorldMatrix(mat, hdg_deg); ssgTransform * pos = new ssgTransform; pos->setTransform(mat); pos->addKid(object->get_random_model()); branch->addKid(pos); } void TriUserData::makeWorldMatrix (sgMat4 mat, double hdg_deg ) { if (hdg_deg == 0) { mat[0][0] = leafData->sin_lat * leafData->cos_lon; mat[0][1] = leafData->sin_lat * leafData->sin_lon; mat[0][2] = -leafData->cos_lat; mat[0][3] = SG_ZERO; mat[1][0] = -leafData->sin_lon; mat[1][1] = leafData->cos_lon; mat[1][2] = SG_ZERO; mat[1][3] = SG_ZERO; } else { float sin_hdg = sin( hdg_deg * SGD_DEGREES_TO_RADIANS ) ; float cos_hdg = cos( hdg_deg * SGD_DEGREES_TO_RADIANS ) ; mat[0][0] = cos_hdg * leafData->sin_lat * leafData->cos_lon - sin_hdg * leafData->sin_lon; mat[0][1] = cos_hdg * leafData->sin_lat * leafData->sin_lon + sin_hdg * leafData->cos_lon; mat[0][2] = -cos_hdg * leafData->cos_lat; mat[0][3] = SG_ZERO; mat[1][0] = -sin_hdg * leafData->sin_lat * leafData->cos_lon - cos_hdg * leafData->sin_lon; mat[1][1] = -sin_hdg * leafData->sin_lat * leafData->sin_lon + cos_hdg * leafData->cos_lon; mat[1][2] = sin_hdg * leafData->cos_lat; mat[1][3] = SG_ZERO; } mat[2][0] = leafData->cos_lat * leafData->cos_lon; mat[2][1] = leafData->cos_lat * leafData->sin_lon; mat[2][2] = leafData->sin_lat; mat[2][3] = SG_ZERO; // translate to random point in triangle sgVec3 result; random_pt_inside_tri(result, p1, p2, p3); sgSubVec3(mat[3], result, center); mat[3][3] = SG_ONE ; } /** * SSG callback for an in-range triangle of randomly-placed objects. * * This pretraversal callback is attached to a branch that is traversed * only when a triangle is in range. If the triangle is not currently * populated with randomly-placed objects, this callback will populate * it. * * @param entity The entity to which the callback is attached (not used). * @param mask The entity's traversal mask (not used). * @return Always 1, to allow traversal and culling to continue. */ static int tri_in_range_callback (ssgEntity * entity, int mask) { TriUserData * data = (TriUserData *)entity->getUserData(); if (!data->is_filled_in) { data->fill_in_triangle(); data->is_filled_in = true; } return 1; } /** * SSG callback for an out-of-range triangle of randomly-placed objects. * * This pretraversal callback is attached to a branch that is traversed * only when a triangle is out of range. If the triangle is currently * populated with randomly-placed objects, the objects will be removed. * * * @param entity The entity to which the callback is attached (not used). * @param mask The entity's traversal mask (not used). * @return Always 0, to prevent any further traversal or culling. */ static int tri_out_of_range_callback (ssgEntity * entity, int mask) { TriUserData * data = (TriUserData *)entity->getUserData(); if (data->is_filled_in) { data->branch->removeAllKids(); data->is_filled_in = false; } return 0; } /** * ssgEntity with a dummy bounding sphere, to fool culling. * * This forces the in-range and out-of-range branches to be visited * when appropriate, even if they have no children. It's ugly, but * it works and seems fairly efficient (since branches can still * be culled when they're out of the view frustum). */ class DummyBSphereEntity : public ssgEntity { public: DummyBSphereEntity (float radius) { bsphere.setCenter(0, 0, 0); bsphere.setRadius(radius); } virtual ~DummyBSphereEntity () {} virtual void recalcBSphere () { bsphere_is_invalid = false; } virtual void cull (sgFrustum *f, sgMat4 m, int test_needed) {} virtual void isect (sgSphere *s, sgMat4 m, int test_needed) {} virtual void hot (sgVec3 s, sgMat4 m, int test_needed) {} virtual void los (sgVec3 s, sgMat4 m, int test_needed) {} }; /** * Calculate the bounding radius of a triangle from its center. * * @param center The triangle center. * @param p1 The first point in the triangle. * @param p2 The second point in the triangle. * @param p3 The third point in the triangle. * @return The greatest distance any point lies from the center. */ static inline float get_bounding_radius( sgVec3 center, float *p1, float *p2, float *p3) { return sqrt( SG_MAX3( sgDistanceSquaredVec3(center, p1), sgDistanceSquaredVec3(center, p2), sgDistanceSquaredVec3(center, p3) ) ); } /** * Set up a triangle for randomly-placed objects. * * No objects will be added unless the triangle comes into range. * */ void LeafUserData::setup_triangle (int i ) { short n1, n2, n3; leaf->getTriangle(i, &n1, &n2, &n3); float * p1 = leaf->getVertex(n1); float * p2 = leaf->getVertex(n2); float * p3 = leaf->getVertex(n3); // Set up a single center point for LOD sgVec3 center; sgSetVec3(center, (p1[0] + p2[0] + p3[0]) / 3.0, (p1[1] + p2[1] + p3[1]) / 3.0, (p1[2] + p2[2] + p3[2]) / 3.0); double area = sgTriArea(p1, p2, p3); // maximum radius of an object from center. double bounding_radius = get_bounding_radius(center, p1, p2, p3); // Set up a transformation to the center // point, so that everything else can // be specified relative to it. ssgTransform * location = new ssgTransform; sgMat4 TRANS; sgMakeTransMat4(TRANS, center); location->setTransform(TRANS); branch->addKid(location); // Iterate through all the object types. int num_groups = mat->get_object_group_count(); for (int j = 0; j < num_groups; j++) { // Look up the random object. FGNewMat::ObjectGroup * group = mat->get_object_group(j); // Set up the range selector for the entire // triangle; note that we use the object // range plus the bounding radius here, to // allow for objects far from the center. float ranges[] = { 0, group->get_range_m() + bounding_radius, SG_MAX }; ssgRangeSelector * lod = new ssgRangeSelector; lod->setRanges(ranges, 3); location->addKid(lod); // Create the in-range and out-of-range // branches. ssgBranch * in_range = new ssgBranch; ssgBranch * out_of_range = new ssgBranch; // Set up the user data for if/when // the random objects in this triangle // are filled in. TriUserData * data = new TriUserData; data->is_filled_in = false; data->p1 = p1; data->p2 = p2; data->p3 = p3; sgCopyVec3 (data->center, center); data->area = area; data->object_group = group; data->branch = in_range; data->leafData = this; data->seed = (unsigned int)(p1[0] * j); // Set up the in-range node. in_range->setUserData(data); in_range->setTravCallback(SSG_CALLBACK_PRETRAV, tri_in_range_callback); lod->addKid(in_range); // Set up the out-of-range node. out_of_range->setUserData(data); out_of_range->setTravCallback(SSG_CALLBACK_PRETRAV, tri_out_of_range_callback); out_of_range->addKid(new DummyBSphereEntity(bounding_radius)); lod->addKid(out_of_range); } } /** * SSG callback for an in-range leaf of randomly-placed objects. * * This pretraversal callback is attached to a branch that is * traversed only when a leaf is in range. If the leaf is not * currently prepared to be populated with randomly-placed objects, * this callback will prepare it (actual population is handled by * the tri_in_range_callback for individual triangles). * * @param entity The entity to which the callback is attached (not used). * @param mask The entity's traversal mask (not used). * @return Always 1, to allow traversal and culling to continue. */ static int leaf_in_range_callback (ssgEntity * entity, int mask) { LeafUserData * data = (LeafUserData *)entity->getUserData(); if (!data->is_filled_in) { // Iterate through all the triangles // and populate them. int num_tris = data->leaf->getNumTriangles(); for ( int i = 0; i < num_tris; ++i ) { data->setup_triangle(i); } data->is_filled_in = true; } return 1; } /** * SSG callback for an out-of-range leaf of randomly-placed objects. * * This pretraversal callback is attached to a branch that is * traversed only when a leaf is out of range. If the leaf is * currently prepared to be populated with randomly-placed objects (or * is actually populated), the objects will be removed. * * @param entity The entity to which the callback is attached (not used). * @param mask The entity's traversal mask (not used). * @return Always 0, to prevent any further traversal or culling. */ static int leaf_out_of_range_callback (ssgEntity * entity, int mask) { LeafUserData * data = (LeafUserData *)entity->getUserData(); if (data->is_filled_in) { data->branch->removeAllKids(); data->is_filled_in = false; } return 0; } /** * Randomly place objects on a surface. * * The leaf node provides the geometry of the surface, while the * material provides the objects and placement density. Latitude * and longitude are required so that the objects can be rotated * to the world-up vector. This function does not actually add * any objects; instead, it attaches an ssgRangeSelector to the * branch with callbacks to generate the objects when needed. * * @param leaf The surface where the objects should be placed. * @param branch The branch that will hold the randomly-placed objects. * @param center The center of the leaf in FlightGear coordinates. * @param material_name The name of the surface's material. */ static void gen_random_surface_objects (ssgLeaf *leaf, ssgBranch *branch, Point3D * center, const string &material_name) { // If the surface has no triangles, return // now. int num_tris = leaf->getNumTriangles(); if (num_tris < 1) return; // Get the material for this surface. FGNewMat * mat = material_lib.find(material_name); if (mat == 0) { SG_LOG(SG_INPUT, SG_ALERT, "Unknown material " << material_name); return; } // If the material has no randomly-placed // objects, return now. if (mat->get_object_group_count() < 1) return; // Calculate the geodetic centre of // the tile, for aligning automatic // objects. double lon_deg, lat_rad, lat_deg, alt_m, sl_radius_m; Point3D geoc = sgCartToPolar3d(*center); lon_deg = geoc.lon() * SGD_RADIANS_TO_DEGREES; sgGeocToGeod(geoc.lat(), geoc.radius(), &lat_rad, &alt_m, &sl_radius_m); lat_deg = lat_rad * SGD_RADIANS_TO_DEGREES; // LOD for the leaf // max random object range: 20000m float ranges[] = { 0, 20000, 1000000 }; ssgRangeSelector * lod = new ssgRangeSelector; lod->setRanges(ranges, 3); branch->addKid(lod); // Create the in-range and out-of-range // branches. ssgBranch * in_range = new ssgBranch; ssgBranch * out_of_range = new ssgBranch; lod->addKid(in_range); lod->addKid(out_of_range); LeafUserData * data = new LeafUserData; data->is_filled_in = false; data->leaf = leaf; data->mat = mat; data->branch = in_range; data->sin_lat = sin(lat_deg * SGD_DEGREES_TO_RADIANS); data->cos_lat = cos(lat_deg * SGD_DEGREES_TO_RADIANS); data->sin_lon = sin(lon_deg * SGD_DEGREES_TO_RADIANS); data->cos_lon = cos(lon_deg * SGD_DEGREES_TO_RADIANS); in_range->setUserData(data); in_range->setTravCallback(SSG_CALLBACK_PRETRAV, leaf_in_range_callback); out_of_range->setUserData(data); out_of_range->setTravCallback(SSG_CALLBACK_PRETRAV, leaf_out_of_range_callback); out_of_range ->addKid(new DummyBSphereEntity(leaf->getBSphere()->getRadius())); } //////////////////////////////////////////////////////////////////////// // Scenery loaders. //////////////////////////////////////////////////////////////////////// // Load an Ascii obj file ssgBranch *fgAsciiObjLoad( const string& path, FGTileEntry *t, ssgVertexArray *lights, const bool is_base) { FGNewMat *newmat = NULL; string material; float coverage = -1; Point3D pp; // sgVec3 approx_normal; // double normal[3], scale = 0.0; // double x, y, z, xmax, xmin, ymax, ymin, zmax, zmin; // GLfloat sgenparams[] = { 1.0, 0.0, 0.0, 0.0 }; // GLint display_list = 0; int shading; bool in_faces = false; int vncount, vtcount; int n1 = 0, n2 = 0, n3 = 0; int tex; // int last1 = 0, last2 = 0; bool odd = false; point_list nodes; Point3D node; Point3D center; double scenery_version = 0.0; double tex_width = 1000.0, tex_height = 1000.0; bool shared_done = false; int_list fan_vertices; int_list fan_tex_coords; int i; ssgSimpleState *state = NULL; sgVec3 *vtlist, *vnlist; sgVec2 *tclist; ssgBranch *tile = new ssgBranch () ; tile -> setName ( (char *)path.c_str() ) ; // Attempt to open "path.gz" or "path" sg_gzifstream in( path ); if ( ! in.is_open() ) { SG_LOG( SG_TERRAIN, SG_DEBUG, "Cannot open file: " << path ); SG_LOG( SG_TERRAIN, SG_DEBUG, "default to ocean tile: " << path ); delete tile; return NULL; } shading = fgGetBool("/sim/rendering/shading"); if ( is_base ) { t->ncount = 0; } vncount = 0; vtcount = 0; if ( is_base ) { t->bounding_radius = 0.0; } center = t->center; // StopWatch stopwatch; // stopwatch.start(); // ignore initial comments and blank lines. (priming the pump) // in >> skipcomment; // string line; string token; char c; #ifdef __MWERKS__ while ( in.get(c) && c != '\0' ) { in.putback(c); #else while ( ! in.eof() ) { #endif in >> ::skipws; if ( in.get( c ) && c == '#' ) { // process a comment line // getline( in, line ); // cout << "comment = " << line << endl; in >> token; if ( token == "Version" ) { // read scenery versions number in >> scenery_version; // cout << "scenery_version = " << scenery_version << endl; if ( scenery_version > 0.4 ) { SG_LOG( SG_TERRAIN, SG_ALERT, "\nYou are attempting to load a tile format that\n" << "is newer than this version of flightgear can\n" << "handle. You should upgrade your copy of\n" << "FlightGear to the newest version. For\n" << "details, please see:\n" << "\n http://www.flightgear.org\n" ); exit(-1); } } else if ( token == "gbs" ) { // reference point (center offset) if ( is_base ) { in >> t->center >> t->bounding_radius; } else { Point3D junk1; double junk2; in >> junk1 >> junk2; } center = t->center; // cout << "center = " << center // << " radius = " << t->bounding_radius << endl; } else if ( token == "bs" ) { // reference point (center offset) // (skip past this) Point3D junk1; double junk2; in >> junk1 >> junk2; } else if ( token == "usemtl" ) { // material property specification // if first usemtl with shared_done = false, then set // shared_done true and build the ssg shared lists if ( ! shared_done ) { // sanity check if ( (int)nodes.size() != vncount ) { SG_LOG( SG_TERRAIN, SG_ALERT, "Tile has mismatched nodes = " << nodes.size() << " and normals = " << vncount << " : " << path ); // exit(-1); } shared_done = true; vtlist = new sgVec3 [ nodes.size() ]; t->vec3_ptrs.push_back( vtlist ); vnlist = new sgVec3 [ vncount ]; t->vec3_ptrs.push_back( vnlist ); tclist = new sgVec2 [ vtcount ]; t->vec2_ptrs.push_back( tclist ); for ( i = 0; i < (int)nodes.size(); ++i ) { sgSetVec3( vtlist[i], nodes[i][0], nodes[i][1], nodes[i][2] ); } for ( i = 0; i < vncount; ++i ) { sgSetVec3( vnlist[i], normals[i][0], normals[i][1], normals[i][2] ); } for ( i = 0; i < vtcount; ++i ) { sgSetVec2( tclist[i], tex_coords[i][0], tex_coords[i][1] ); } } // display_list = xglGenLists(1); // xglNewList(display_list, GL_COMPILE); // printf("xglGenLists(); xglNewList();\n"); in_faces = false; // scan the material line in >> material; // find this material in the properties list newmat = material_lib.find( material ); if ( newmat == NULL ) { // see if this is an on the fly texture string file = path; int pos = file.rfind( "/" ); file = file.substr( 0, pos ); // cout << "current file = " << file << endl; file += "/"; file += material; // cout << "current file = " << file << endl; if ( ! material_lib.add_item( file ) ) { SG_LOG( SG_TERRAIN, SG_ALERT, "Ack! unknown usemtl name = " << material << " in " << path ); } else { // locate our newly created material newmat = material_lib.find( material ); if ( newmat == NULL ) { SG_LOG( SG_TERRAIN, SG_ALERT, "Ack! bad on the fly materia create = " << material << " in " << path ); } } } if ( newmat != NULL ) { // set the texture width and height values for this // material tex_width = newmat->get_xsize(); tex_height = newmat->get_ysize(); state = newmat->get_state(); coverage = newmat->get_light_coverage(); // cout << "(w) = " << tex_width << " (h) = " // << tex_width << endl; } else { coverage = -1; } } else { // unknown comment, just gobble the input until the // end of line in >> skipeol; } } else { in.putback( c ); in >> token; // cout << "token = " << token << endl; if ( token == "vn" ) { // vertex normal if ( vncount < FG_MAX_NODES ) { in >> normals[vncount][0] >> normals[vncount][1] >> normals[vncount][2]; vncount++; } else { SG_LOG( SG_TERRAIN, SG_ALERT, "Read too many vertex normals in " << path << " ... dying :-(" ); exit(-1); } } else if ( token == "vt" ) { // vertex texture coordinate if ( vtcount < FG_MAX_NODES*3 ) { in >> tex_coords[vtcount][0] >> tex_coords[vtcount][1]; vtcount++; } else { SG_LOG( SG_TERRAIN, SG_ALERT, "Read too many vertex texture coords in " << path << " ... dying :-(" ); exit(-1); } } else if ( token == "v" ) { // node (vertex) if ( t->ncount < FG_MAX_NODES ) { /* in >> nodes[t->ncount][0] >> nodes[t->ncount][1] >> nodes[t->ncount][2]; */ in >> node; nodes.push_back(node); if ( is_base ) { t->ncount++; } } else { SG_LOG( SG_TERRAIN, SG_ALERT, "Read too many nodes in " << path << " ... dying :-("); exit(-1); } } else if ( (token == "tf") || (token == "ts") || (token == "f") ) { // triangle fan, strip, or individual face // SG_LOG( SG_TERRAIN, SG_INFO, "new fan or strip"); fan_vertices.clear(); fan_tex_coords.clear(); odd = true; // xglBegin(GL_TRIANGLE_FAN); in >> n1; fan_vertices.push_back( n1 ); // xglNormal3dv(normals[n1]); if ( in.get( c ) && c == '/' ) { in >> tex; fan_tex_coords.push_back( tex ); if ( scenery_version >= 0.4 ) { if ( tex_width > 0 ) { tclist[tex][0] *= (1000.0 / tex_width); } if ( tex_height > 0 ) { tclist[tex][1] *= (1000.0 / tex_height); } } pp.setx( tex_coords[tex][0] * (1000.0 / tex_width) ); pp.sety( tex_coords[tex][1] * (1000.0 / tex_height) ); } else { in.putback( c ); pp = local_calc_tex_coords(nodes[n1], center); } // xglTexCoord2f(pp.x(), pp.y()); // xglVertex3dv(nodes[n1].get_n()); in >> n2; fan_vertices.push_back( n2 ); // xglNormal3dv(normals[n2]); if ( in.get( c ) && c == '/' ) { in >> tex; fan_tex_coords.push_back( tex ); if ( scenery_version >= 0.4 ) { if ( tex_width > 0 ) { tclist[tex][0] *= (1000.0 / tex_width); } if ( tex_height > 0 ) { tclist[tex][1] *= (1000.0 / tex_height); } } pp.setx( tex_coords[tex][0] * (1000.0 / tex_width) ); pp.sety( tex_coords[tex][1] * (1000.0 / tex_height) ); } else { in.putback( c ); pp = local_calc_tex_coords(nodes[n2], center); } // xglTexCoord2f(pp.x(), pp.y()); // xglVertex3dv(nodes[n2].get_n()); // read all subsequent numbers until next thing isn't a number while ( true ) { in >> ::skipws; char c; in.get(c); in.putback(c); if ( ! isdigit(c) || in.eof() ) { break; } in >> n3; fan_vertices.push_back( n3 ); // cout << " triangle = " // << n1 << "," << n2 << "," << n3 // << endl; // xglNormal3dv(normals[n3]); if ( in.get( c ) && c == '/' ) { in >> tex; fan_tex_coords.push_back( tex ); if ( scenery_version >= 0.4 ) { if ( tex_width > 0 ) { tclist[tex][0] *= (1000.0 / tex_width); } if ( tex_height > 0 ) { tclist[tex][1] *= (1000.0 / tex_height); } } pp.setx( tex_coords[tex][0] * (1000.0 / tex_width) ); pp.sety( tex_coords[tex][1] * (1000.0 / tex_height) ); } else { in.putback( c ); pp = local_calc_tex_coords(nodes[n3], center); } // xglTexCoord2f(pp.x(), pp.y()); // xglVertex3dv(nodes[n3].get_n()); if ( (token == "tf") || (token == "f") ) { // triangle fan n2 = n3; } else { // triangle strip odd = !odd; n1 = n2; n2 = n3; } } // xglEnd(); // build the ssg entity int size = (int)fan_vertices.size(); ssgVertexArray *vl = new ssgVertexArray( size ); ssgNormalArray *nl = new ssgNormalArray( size ); ssgTexCoordArray *tl = new ssgTexCoordArray( size ); ssgColourArray *cl = new ssgColourArray( 1 ); sgVec4 color; sgSetVec4( color, 1.0, 1.0, 1.0, 1.0 ); cl->add( color ); sgVec2 tmp2; sgVec3 tmp3; for ( i = 0; i < size; ++i ) { sgCopyVec3( tmp3, vtlist[ fan_vertices[i] ] ); vl -> add( tmp3 ); sgCopyVec3( tmp3, vnlist[ fan_vertices[i] ] ); nl -> add( tmp3 ); sgCopyVec2( tmp2, tclist[ fan_tex_coords[i] ] ); tl -> add( tmp2 ); } ssgLeaf *leaf = NULL; if ( token == "tf" ) { // triangle fan leaf = new ssgVtxTable ( GL_TRIANGLE_FAN, vl, nl, tl, cl ); } else if ( token == "ts" ) { // triangle strip leaf = new ssgVtxTable ( GL_TRIANGLE_STRIP, vl, nl, tl, cl ); } else if ( token == "f" ) { // triangle leaf = new ssgVtxTable ( GL_TRIANGLES, vl, nl, tl, cl ); } // leaf->makeDList(); leaf->setState( state ); tile->addKid( leaf ); if ( is_base ) { if ( coverage > 0.0 ) { if ( coverage < 10000.0 ) { SG_LOG(SG_INPUT, SG_ALERT, "Light coverage is " << coverage << ", pushing up to 10000"); coverage = 10000; } gen_random_surface_points(leaf, lights, coverage); } } } else { SG_LOG( SG_TERRAIN, SG_WARN, "Unknown token in " << path << " = " << token ); } // eat white space before start of while loop so if we are // done with useful input it is noticed before hand. in >> ::skipws; } } if ( is_base ) { t->nodes = nodes; } // stopwatch.stop(); // SG_LOG( SG_TERRAIN, SG_DEBUG, // "Loaded " << path << " in " // << stopwatch.elapsedSeconds() << " seconds" ); return tile; } ssgLeaf *gen_leaf( const string& path, const GLenum ty, const string& material, const point_list& nodes, const point_list& normals, const point_list& texcoords, const int_list& node_index, const int_list& normal_index, const int_list& tex_index, const bool calc_lights, ssgVertexArray *lights ) { double tex_width = 1000.0, tex_height = 1000.0; ssgSimpleState *state = NULL; float coverage = -1; FGNewMat *newmat = material_lib.find( material ); if ( newmat == NULL ) { // see if this is an on the fly texture string file = path; string::size_type pos = file.rfind( "/" ); file = file.substr( 0, pos ); // cout << "current file = " << file << endl; file += "/"; file += material; // cout << "current file = " << file << endl; if ( ! material_lib.add_item( file ) ) { SG_LOG( SG_TERRAIN, SG_ALERT, "Ack! unknown usemtl name = " << material << " in " << path ); } else { // locate our newly created material newmat = material_lib.find( material ); if ( newmat == NULL ) { SG_LOG( SG_TERRAIN, SG_ALERT, "Ack! bad on the fly material create = " << material << " in " << path ); } } } if ( newmat != NULL ) { // set the texture width and height values for this // material tex_width = newmat->get_xsize(); tex_height = newmat->get_ysize(); state = newmat->get_state(); coverage = newmat->get_light_coverage(); // cout << "(w) = " << tex_width << " (h) = " // << tex_width << endl; } else { coverage = -1; } sgVec2 tmp2; sgVec3 tmp3; sgVec4 tmp4; int i; // vertices int size = node_index.size(); if ( size < 1 ) { SG_LOG( SG_TERRAIN, SG_ALERT, "Woh! node list size < 1" ); exit(-1); } ssgVertexArray *vl = new ssgVertexArray( size ); Point3D node; for ( i = 0; i < size; ++i ) { node = nodes[ node_index[i] ]; sgSetVec3( tmp3, node[0], node[1], node[2] ); vl -> add( tmp3 ); } // normals Point3D normal; ssgNormalArray *nl = new ssgNormalArray( size ); if ( normal_index.size() ) { // object file specifies normal indices (i.e. normal indices // aren't 'implied' for ( i = 0; i < size; ++i ) { normal = normals[ normal_index[i] ]; sgSetVec3( tmp3, normal[0], normal[1], normal[2] ); nl -> add( tmp3 ); } } else { // use implied normal indices. normal index = vertex index. for ( i = 0; i < size; ++i ) { normal = normals[ node_index[i] ]; sgSetVec3( tmp3, normal[0], normal[1], normal[2] ); nl -> add( tmp3 ); } } // colors ssgColourArray *cl = new ssgColourArray( 1 ); sgSetVec4( tmp4, 1.0, 1.0, 1.0, 1.0 ); cl->add( tmp4 ); // texture coordinates size = tex_index.size(); Point3D texcoord; ssgTexCoordArray *tl = new ssgTexCoordArray( size ); if ( size == 1 ) { texcoord = texcoords[ tex_index[0] ]; sgSetVec2( tmp2, texcoord[0], texcoord[1] ); sgSetVec2( tmp2, texcoord[0], texcoord[1] ); if ( tex_width > 0 ) { tmp2[0] *= (1000.0 / tex_width); } if ( tex_height > 0 ) { tmp2[1] *= (1000.0 / tex_height); } tl -> add( tmp2 ); } else if ( size > 1 ) { for ( i = 0; i < size; ++i ) { texcoord = texcoords[ tex_index[i] ]; sgSetVec2( tmp2, texcoord[0], texcoord[1] ); if ( tex_width > 0 ) { tmp2[0] *= (1000.0 / tex_width); } if ( tex_height > 0 ) { tmp2[1] *= (1000.0 / tex_height); } tl -> add( tmp2 ); } } ssgLeaf *leaf = new ssgVtxTable ( ty, vl, nl, tl, cl ); // lookup the state record leaf->setState( state ); if ( calc_lights ) { if ( coverage > 0.0 ) { if ( coverage < 10000.0 ) { SG_LOG(SG_INPUT, SG_ALERT, "Light coverage is " << coverage << ", pushing up to 10000"); coverage = 10000; } gen_random_surface_points(leaf, lights, coverage); } } return leaf; } // Load an Binary obj file bool fgBinObjLoad( const string& path, const bool is_base, Point3D *center, double *bounding_radius, ssgBranch* geometry, ssgBranch* rwy_lights, ssgVertexArray *ground_lights ) { SGBinObject obj; bool use_random_objects = fgGetBool("/sim/rendering/random-objects", true); if ( ! obj.read_bin( path ) ) { return false; } geometry->setName( (char *)path.c_str() ); // reference point (center offset/bounding sphere) *center = obj.get_gbs_center(); *bounding_radius = obj.get_gbs_radius(); point_list const& nodes = obj.get_wgs84_nodes(); point_list const& colors = obj.get_colors(); point_list const& normals = obj.get_normals(); point_list const& texcoords = obj.get_texcoords(); string material; int_list tex_index; group_list::size_type i; bool is_lighting = false; // generate points string_list const& pt_materials = obj.get_pt_materials(); group_list const& pts_v = obj.get_pts_v(); group_list const& pts_n = obj.get_pts_n(); for ( i = 0; i < pts_v.size(); ++i ) { // cout << "pts_v.size() = " << pts_v.size() << endl; if ( pt_materials[i].substr(0, 3) == "RWY" ) { material = "GROUND_LIGHTS"; is_lighting = true; sgVec3 up; sgSetVec3( up, center->x(), center->y(), center->z() ); ssgBranch *branch = gen_directional_lights( nodes, normals, pts_v[i], pts_n[i], up ); float ranges[] = { 0, 12000 }; // branch->setCallback( SSG_CALLBACK_PREDRAW, runway_lights_predraw ); ssgRangeSelector * lod = new ssgRangeSelector; lod->setRanges( ranges, 2 ); lod->addKid( branch ); rwy_lights->addKid( lod ); } else { material = pt_materials[i]; tex_index.clear(); ssgLeaf *leaf = gen_leaf( path, GL_POINTS, material, nodes, normals, texcoords, pts_v[i], pts_n[i], tex_index, false, ground_lights ); geometry->addKid( leaf ); } if ( is_lighting ) { } } // Put all randomly-placed objects under a separate branch // (actually an ssgRangeSelector) named "random-models". ssgBranch * random_object_branch = 0; if (use_random_objects) { float ranges[] = { 0, 20000 }; // Maximum 20km range for random objects ssgRangeSelector * object_lod = new ssgRangeSelector; object_lod->setRanges(ranges, 2); object_lod->setName("random-models"); geometry->addKid(object_lod); random_object_branch = new ssgBranch; object_lod->addKid(random_object_branch); } // generate triangles string_list const& tri_materials = obj.get_tri_materials(); group_list const& tris_v = obj.get_tris_v(); group_list const& tris_n = obj.get_tris_n(); group_list const& tris_tc = obj.get_tris_tc(); for ( i = 0; i < tris_v.size(); ++i ) { ssgLeaf *leaf = gen_leaf( path, GL_TRIANGLES, tri_materials[i], nodes, normals, texcoords, tris_v[i], tris_n[i], tris_tc[i], is_base, ground_lights ); if (use_random_objects) gen_random_surface_objects(leaf, random_object_branch, center, tri_materials[i]); geometry->addKid( leaf ); } // generate strips string_list const& strip_materials = obj.get_strip_materials(); group_list const& strips_v = obj.get_strips_v(); group_list const& strips_n = obj.get_strips_n(); group_list const& strips_tc = obj.get_strips_tc(); for ( i = 0; i < strips_v.size(); ++i ) { ssgLeaf *leaf = gen_leaf( path, GL_TRIANGLE_STRIP, strip_materials[i], nodes, normals, texcoords, strips_v[i], strips_n[i], strips_tc[i], is_base, ground_lights ); if (use_random_objects) gen_random_surface_objects(leaf, random_object_branch, center,strip_materials[i]); geometry->addKid( leaf ); } // generate fans string_list const& fan_materials = obj.get_fan_materials(); group_list const& fans_v = obj.get_fans_v(); group_list const& fans_n = obj.get_fans_n(); group_list const& fans_tc = obj.get_fans_tc(); for ( i = 0; i < fans_v.size(); ++i ) { ssgLeaf *leaf = gen_leaf( path, GL_TRIANGLE_FAN, fan_materials[i], nodes, normals, texcoords, fans_v[i], fans_n[i], fans_tc[i], is_base, ground_lights ); if (use_random_objects) gen_random_surface_objects(leaf, random_object_branch, center, fan_materials[i]); geometry->addKid( leaf ); } return true; }