1
0
Fork 0
flightgear/src/Objects/obj.cxx
curt c162577340 Begin work on rendering runway lights using environment maps. The basics
are now working.  A runway light is defined by a point and a direction.  The
point and direction are combined with the local up vector to create a small
triangle orthogonal to the direction.  The two ficticous corners of the
triangle are given an alpha value of zero, the orignal corner is given an
alpha of one.  The triangle is drawn in glPolygonMode(GL_FRONT, GL_POINT)
mode which means only the corner points are drawn, and since two have alpha=0
only the original point is drawn.  This is a long way to go to draw a point,
but it ensures that the point is only visible within 90 degrees of the light
direction, behind the light it is not visible.  This is still a long way
to get to drawing a point, but we use an environement map, with the direction
vector as the normal to mimic a light that is brightest when viewed head
on and dimmest when viewed perpendicularly or disappears when viewed from
behind.

- warning, there is a bug in how the current runway light direction vector
  is calculated which will adversely effect runway lighting.  The airports
  should be regenerated in order to fix this problem.
2002-10-06 03:53:19 +00:00

1493 lines
50 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;
}