1 // tile.cxx -- routines to handle a scenery tile
3 // Written by Curtis Olson, started May 1998.
5 // Copyright (C) 1998, 1999 Curtis L. Olson - curt@flightgear.org
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 #include <simgear/compiler.h>
30 #ifdef SG_MATH_EXCEPTION_CLASH
34 #include STL_FUNCTIONAL
35 #include STL_ALGORITHM
38 #include <simgear/bucket/newbucket.hxx>
39 #include <simgear/debug/logstream.hxx>
40 #include <simgear/math/sg_geodesy.hxx>
41 #include <simgear/math/sg_random.h>
42 #include <simgear/misc/sgstream.hxx>
44 #include <Aircraft/aircraft.hxx>
45 #include <Include/general.hxx>
46 #include <Main/globals.hxx>
47 #include <Scenery/scenery.hxx>
48 #include <Time/light.hxx>
49 #include <Objects/matlib.hxx>
50 #include <Objects/newmat.hxx>
51 #include <Objects/obj.hxx>
53 #include "tileentry.hxx"
55 SG_USING_STD(for_each);
56 SG_USING_STD(mem_fun_ref);
61 FGTileEntry::FGTileEntry ( const SGBucket& b )
63 center( Point3D( 0.0 ) ),
65 terra_transform( new ssgTransform ),
66 terra_range( new ssgRangeSelector ),
71 // update the contents
72 // if ( vec3_ptrs.size() || vec2_ptrs.size() || index_ptrs.size() ) {
73 // SG_LOG( SG_TERRAIN, SG_ALERT,
74 // "Attempting to overwrite existing or"
75 // << " not properly freed leaf data." );
82 FGTileEntry::~FGTileEntry () {
83 // cout << "nodes = " << nodes.size() << endl;;
88 // recurse an ssg tree and call removeKid() on every node from the
89 // bottom up. Leaves the original branch in existance, but empty so
90 // it can be removed by the calling routine.
91 static void my_remove_branch( ssgBranch * branch ) {
92 for ( ssgEntity *k = branch->getKid( 0 );
94 k = branch->getNextKid() )
96 if ( k -> isAKindOf ( ssgTypeBranch() ) ) {
97 my_remove_branch( (ssgBranch *)k );
98 branch -> removeKid ( k );
99 } else if ( k -> isAKindOf ( ssgTypeLeaf() ) ) {
100 branch -> removeKid ( k ) ;
106 // Clean up the memory used by this tile and delete the arrays used by
107 // ssg as well as the whole ssg branch
108 void FGTileEntry::free_tile() {
110 SG_LOG( SG_TERRAIN, SG_INFO,
111 "FREEING TILE = (" << tile_bucket << ")" );
113 SG_LOG( SG_TERRAIN, SG_DEBUG,
114 " deleting " << nodes.size() << " nodes" );
117 // delete the ssg structures
118 SG_LOG( SG_TERRAIN, SG_DEBUG,
119 " deleting (leaf data) vertex, normal, and "
120 << " texture coordinate arrays" );
122 for ( i = 0; i < (int)vec3_ptrs.size(); ++i ) {
123 delete [] vec3_ptrs[i];
127 for ( i = 0; i < (int)vec2_ptrs.size(); ++i ) {
128 delete [] vec2_ptrs[i];
132 for ( i = 0; i < (int)index_ptrs.size(); ++i ) {
133 delete index_ptrs[i];
137 // delete the terrain branch
138 int pcount = terra_transform->getNumParents();
140 // find the first parent (should only be one)
141 ssgBranch *parent = terra_transform->getParent( 0 ) ;
143 // my_remove_branch( select_ptr );
144 parent->removeKid( terra_transform );
145 terra_transform = NULL;
147 SG_LOG( SG_TERRAIN, SG_ALERT,
148 "parent pointer is NULL! Dying" );
152 SG_LOG( SG_TERRAIN, SG_ALERT,
153 "Parent count is zero for an ssg tile! Dying" );
157 if ( lights_transform ) {
158 // delete the terrain lighting branch
159 pcount = lights_transform->getNumParents();
161 // find the first parent (should only be one)
162 ssgBranch *parent = lights_transform->getParent( 0 ) ;
164 parent->removeKid( lights_transform );
165 lights_transform = NULL;
167 SG_LOG( SG_TERRAIN, SG_ALERT,
168 "parent pointer is NULL! Dying" );
172 SG_LOG( SG_TERRAIN, SG_ALERT,
173 "Parent count is zero for an ssg light tile! Dying" );
180 // Update the ssg transform node for this tile so it can be
181 // properly drawn relative to our (0,0,0) point
182 void FGTileEntry::prep_ssg_node( const Point3D& p, float vis) {
183 if ( !loaded ) return;
187 // #define USE_UP_AND_COMING_PLIB_FEATURE
188 #ifdef USE_UP_AND_COMING_PLIB_FEATURE
189 terra_range->setRange( 0, SG_ZERO );
190 terra_range->setRange( 1, vis + bounding_radius );
191 lights_range->setRange( 0, SG_ZERO );
192 lights_range->setRange( 1, vis * 1.5 + bounding_radius );
196 ranges[1] = vis + bounding_radius;
197 terra_range->setRanges( ranges, 2 );
198 if ( lights_range ) {
199 ranges[1] = vis * 1.5 + bounding_radius;
200 lights_range->setRanges( ranges, 2 );
204 sgSetVec3( sgTrans, offset.x(), offset.y(), offset.z() );
205 terra_transform->setTransform( sgTrans );
207 if ( lights_transform ) {
208 // we need to lift the lights above the terrain to avoid
209 // z-buffer fighting. We do this based on our altitude and
210 // the distance this tile is away from scenery center.
213 sgCopyVec3( up, globals->get_current_view()->get_world_up() );
216 if ( current_aircraft.fdm_state ) {
217 agl = current_aircraft.fdm_state->get_Altitude() * SG_FEET_TO_METER
223 // sgTrans just happens to be the
224 // vector from scenery center to the center of this tile which
225 // is what we want to calculate the distance of
227 sgCopyVec3( to, sgTrans );
228 double dist = sgLengthVec3( to );
230 if ( general.get_glDepthBits() > 16 ) {
231 sgScaleVec3( up, 10.0 + agl / 100.0 + dist / 10000 );
233 sgScaleVec3( up, 10.0 + agl / 20.0 + dist / 5000 );
235 sgAddVec3( sgTrans, up );
236 lights_transform->setTransform( sgTrans );
238 // select which set of lights based on sun angle
239 float sun_angle = cur_light_params.sun_angle * SGD_RADIANS_TO_DEGREES;
240 if ( sun_angle > 95 ) {
241 lights_brightness->select(0x04);
242 } else if ( sun_angle > 92 ) {
243 lights_brightness->select(0x02);
244 } else if ( sun_angle > 89 ) {
245 lights_brightness->select(0x01);
247 lights_brightness->select(0x00);
253 ssgLeaf* FGTileEntry::gen_lights( ssgVertexArray *lights, int inc, float bright ) {
254 // generate a repeatable random seed
255 float *p1 = lights->get( 0 );
256 unsigned int *seed = (unsigned int *)p1;
259 int size = lights->getNum() / inc;
261 // Allocate ssg structure
262 ssgVertexArray *vl = new ssgVertexArray( size + 1 );
263 ssgNormalArray *nl = NULL;
264 ssgTexCoordArray *tl = NULL;
265 ssgColourArray *cl = new ssgColourArray( size + 1 );
268 for ( int i = 0; i < lights->getNum(); ++i ) {
269 // this loop is slightly less efficient than it otherwise
270 // could be, but we want a red light to always be red, and a
271 // yellow light to always be yellow, etc. so we are trying to
272 // preserve the random sequence.
273 float zombie = sg_random();
274 if ( i % inc == 0 ) {
275 vl->add( lights->get(i) );
277 // factor = sg_random() ^ 2, range = 0 .. 1 concentrated towards 0
278 float factor = sg_random();
281 if ( zombie > 0.5 ) {
282 // 50% chance of yellowish
283 sgSetVec4( color, 0.9, 0.9, 0.3, bright - factor * 0.2 );
284 } else if ( zombie > 0.15 ) {
285 // 35% chance of whitish
286 sgSetVec4( color, 0.9, 0.9, 0.8, bright - factor * 0.2 );
287 } else if ( zombie > 0.05 ) {
288 // 10% chance of orangish
289 sgSetVec4( color, 0.9, 0.6, 0.2, bright - factor * 0.2 );
291 // 5% chance of redish
292 sgSetVec4( color, 0.9, 0.2, 0.2, bright - factor * 0.2 );
300 new ssgVtxTable ( GL_POINTS, vl, nl, tl, cl );
303 FGNewMat *newmat = material_lib.find( "LIGHTS" );
304 leaf->setState( newmat->get_state() );
311 FGTileEntry::obj_load( const std::string& path,
312 ssgVertexArray* lights, bool is_base )
314 ssgBranch* result = 0;
316 // try loading binary format
317 result = fgBinObjLoad( path, this, lights, is_base );
318 if ( result == NULL ) {
319 // next try the older ascii format
320 result = fgAsciiObjLoad( path, this, lights, is_base );
321 if ( result == NULL ) {
322 // default to an ocean tile
323 result = fgGenTile( path, this );
332 FGTileEntry::load( const SGPath& base, bool is_base )
334 string index_str = tile_bucket.gen_index_str();
336 SGPath tile_path = base;
337 // Generate name of file to load.
338 tile_path.append( tile_bucket.gen_base_path() );
339 SGPath basename = tile_path;
340 basename.append( index_str );
341 string path = basename.str();
343 SG_LOG( SG_TERRAIN, SG_INFO, "Loading tile " << path );
345 // fgObjLoad will generate ground lighting for us ...
346 ssgVertexArray *light_pts = new ssgVertexArray( 100 );
348 ssgBranch* new_tile = obj_load( path, light_pts, is_base );
349 if ( new_tile != NULL ) {
350 terra_range->addKid( new_tile );
353 // load custom objects
354 SG_LOG( SG_TERRAIN, SG_DEBUG, "Checking for custom objects ..." );
356 SGPath index_path = tile_path;
357 index_path.append( index_str );
358 index_path.concat( ".ind" );
360 SG_LOG( SG_TERRAIN, SG_DEBUG, "Looking in " << index_path.str() );
362 sg_gzifstream in( index_path.str() );
364 if ( in.is_open() ) {
367 while ( ! in.eof() ) {
370 if ( token == "OBJECT" ) {
371 in >> name >> ::skipws;
372 SG_LOG( SG_TERRAIN, SG_DEBUG, "token = " << token
373 << " name = " << name );
375 SGPath custom_path = tile_path;
376 custom_path.append( name );
377 ssgBranch *custom_obj
378 = obj_load( custom_path.str(), NULL, false );
379 if ( (new_tile != NULL) && (custom_obj != NULL) ) {
380 new_tile -> addKid( custom_obj );
382 } else if ( token == "OBJECT_STATIC" ) {
384 double lon, lat, elev, hdg;
385 in >> name >> lon >> lat >> elev >> hdg >> ::skipws;
386 SG_LOG( SG_TERRAIN, SG_INFO, "token = " << token
387 << " name = " << name
388 << " pos = " << lon << ", " << lat
389 << " elevation = " << elev
390 << " heading = " << hdg );
392 // load the object itself
393 SGPath custom_path = tile_path;
394 ssgTexturePath( (char *)custom_path.c_str() );
395 custom_path.append( name );
396 ssgEntity *obj_model = ssgLoad( (char *)custom_path.c_str() );
399 Point3D geod( lon * SGD_DEGREES_TO_RADIANS,
400 lat * SGD_DEGREES_TO_RADIANS,
402 Point3D world_pos = sgGeodToCart( geod );
403 Point3D offset = world_pos - center;
405 sgMakeTransMat4( POS, offset.x(), offset.y(), offset.z() );
407 sgVec3 obj_rt, obj_up;
408 sgSetVec3( obj_rt, 0.0, 1.0, 0.0); // Y axis
409 sgSetVec3( obj_up, 0.0, 0.0, 1.0); // Z axis
411 sgMat4 ROT_lon, ROT_lat, ROT_hdg;
412 sgMakeRotMat4( ROT_lon, lon, obj_up );
413 sgMakeRotMat4( ROT_lat, 90 - lat, obj_rt );
414 sgMakeRotMat4( ROT_hdg, hdg, obj_up );
417 sgCopyMat4( TUX, ROT_hdg );
418 sgPostMultMat4( TUX, ROT_lat );
419 sgPostMultMat4( TUX, ROT_lon );
420 sgPostMultMat4( TUX, POS );
423 sgSetCoord( &obj_pos, TUX );
424 ssgTransform *obj_trans = new ssgTransform;
425 obj_trans->setTransform( &obj_pos );
427 // wire the scene graph together
428 obj_trans->addKid( obj_model );
429 new_tile->addKid( obj_trans );
431 SG_LOG( SG_TERRAIN, SG_ALERT,
432 "Unknown token " << token << " in "
433 << index_path.str() );
439 terra_transform->addKid( terra_range );
441 // calculate initial tile offset
442 SetOffset( scenery.center );
444 sgSetCoord( &sgcoord,
445 offset.x(), offset.y(), offset.z(),
447 terra_transform->setTransform( &sgcoord );
448 // terrain->addKid( terra_transform );
450 lights_transform = NULL;
452 /* uncomment this section for testing ground lights */
453 if ( light_pts->getNum() ) {
454 SG_LOG( SG_TERRAIN, SG_DEBUG, "generating lights" );
455 lights_transform = new ssgTransform;
456 lights_range = new ssgRangeSelector;
457 lights_brightness = new ssgSelector;
460 lights = gen_lights( light_pts, 4, 0.7 );
461 lights_brightness->addKid( lights );
463 lights = gen_lights( light_pts, 2, 0.85 );
464 lights_brightness->addKid( lights );
466 lights = gen_lights( light_pts, 1, 1.0 );
467 lights_brightness->addKid( lights );
469 lights_range->addKid( lights_brightness );
470 lights_transform->addKid( lights_range );
471 lights_transform->setTransform( &sgcoord );
472 // ground->addKid( lights_transform );
474 /* end of ground light section */
479 FGTileEntry::add_ssg_nodes( ssgBranch* terrain, ssgBranch* ground )
481 terrain->addKid( terra_transform );
482 if (lights_transform != 0)
483 ground->addKid( lights_transform );