]> git.mxchange.org Git - flightgear.git/blob - src/Scenery/tilemgr.cxx
Fear the penguin ...
[flightgear.git] / src / Scenery / tilemgr.cxx
1 // tilemgr.cxx -- routines to handle dynamic management of scenery tiles
2 //
3 // Written by Curtis Olson, started January 1998.
4 //
5 // Copyright (C) 1997  Curtis L. Olson  - curt@infoplane.com
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 //
21 // $Id$
22
23
24 #ifdef HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27
28 #ifdef HAVE_WINDOWS_H
29 #  include <windows.h>
30 #endif
31
32 #include <GL/glut.h>
33 #include <XGL/xgl.h>
34
35 #include <Aircraft/aircraft.hxx>
36
37 #include <Debug/logstream.hxx>
38 // #include <Bucket/bucketutils.hxx>
39 #include <Include/fg_constants.h>
40 #include <Main/options.hxx>
41 #include <Main/views.hxx>
42 #include <Math/fg_geodesy.hxx>
43 #include <Math/mat3.h>
44 #include <Math/point3d.hxx>
45 #include <Math/polar3d.hxx>
46 #include <Math/vector.hxx>
47 #include <Objects/materialmgr.hxx>
48 #include <Objects/obj.hxx>
49 #include <Weather/weather.hxx>
50
51 #include "scenery.hxx"
52 #include "tilecache.hxx"
53 #include "tileentry.hxx"
54 #include "tilemgr.hxx"
55
56
57 // to test clipping speedup in fgTileMgrRender()
58 #if defined ( USE_FAST_FOV_CLIP )
59 // #define TEST_FOV_CLIP
60 // #define TEST_ELEV
61 #endif
62
63
64 // the tile manager
65 FGTileMgr global_tile_mgr;
66
67
68 // Constructor
69 FGTileMgr::FGTileMgr ( void ):
70     state( Start )
71 {
72 }
73
74
75 // Destructor
76 FGTileMgr::~FGTileMgr ( void ) {
77 }
78
79
80 // Initialize the Tile Manager subsystem
81 int FGTileMgr::init( void ) {
82     FG_LOG( FG_TERRAIN, FG_INFO, "Initializing Tile Manager subsystem." );
83
84     // load default material library
85     if ( ! material_mgr.loaded() ) {
86         material_mgr.load_lib();
87     }
88
89     state = Inited;
90
91     return 1;
92 }
93
94
95 // schedule a tile for loading
96 void FGTileMgr::sched_tile( const FGBucket& b, int *index ) {
97     // see if tile already exists in the cache
98     *index = global_tile_cache.exists( b );
99     if ( *index < 0 ) {
100         // find the next availabel cache entry and mark it as scheduled
101         *index = global_tile_cache.next_avail();
102         FGTileEntry *t = global_tile_cache.get_tile( *index );
103         t->mark_scheduled();
104
105         // register a load request
106         FGLoadRec request;
107         request.b = b;
108         request.index = *index;
109         load_queue.push_back( request );
110     }
111 }
112
113
114 // load a tile
115 void FGTileMgr::load_tile( const FGBucket& b, int cache_index) {
116
117     FG_LOG( FG_TERRAIN, FG_DEBUG, "Loading tile " << b );
118     
119     global_tile_cache.fill_in(cache_index, b);
120
121     FG_LOG( FG_TERRAIN, FG_DEBUG, "Loaded for cache index: " << cache_index );
122 }
123
124
125 // Calculate shortest distance from point to line
126 static double point_line_dist_squared( const Point3D& tc, const Point3D& vp, 
127                                        MAT3vec d )
128 {
129     MAT3vec p, p0;
130
131     p[0] = tc.x(); p[1] = tc.y(); p[2] = tc.z();
132     p0[0] = vp.x(); p0[1] = vp.y(); p0[2] = vp.z();
133
134     return fgPointLineSquared(p, p0, d);
135 }
136
137
138 // Determine scenery altitude.  Normally this just happens when we
139 // render the scene, but we'd also like to be able to do this
140 // explicitely.  lat & lon are in radians.  abs_view_pos in meters.
141 // Returns result in meters.
142 double
143 FGTileMgr::current_elev_new( const FGBucket& p ) {
144     FGTileEntry *t;
145     fgFRAGMENT *frag_ptr;
146     Point3D abs_view_pos = current_view.get_abs_view_pos();
147     Point3D earth_center(0.0);
148     Point3D result;
149     MAT3vec local_up;
150     double dist, lat_geod, alt, sea_level_r;
151     int index;
152
153     local_up[0] = abs_view_pos.x();
154     local_up[1] = abs_view_pos.y();
155     local_up[2] = abs_view_pos.z();
156
157     // Find current translation offset
158     // fgBucketFind(lon * RAD_TO_DEG, lat * RAD_TO_DEG, &p);
159     index = global_tile_cache.exists(p);
160     if ( index < 0 ) {
161         FG_LOG( FG_TERRAIN, FG_WARN, "Tile not found" );
162         return 0.0;
163     }
164
165     t = global_tile_cache.get_tile(index);
166
167     scenery.next_center = t->center;
168     
169     FG_LOG( FG_TERRAIN, FG_DEBUG, 
170             "Current bucket = " << p << "  Index = " << p.gen_index_str() );
171     FG_LOG( FG_TERRAIN, FG_DEBUG,
172             "abs_view_pos = " << abs_view_pos );
173
174     // calculate tile offset
175     // x = (t->offset.x = t->center.x - scenery.center.x);
176     // y = (t->offset.y = t->center.y - scenery.center.y);
177     // z = (t->offset.z = t->center.z - scenery.center.z);
178     
179     // calc current terrain elevation calculate distance from
180     // vertical tangent line at current position to center of
181     // tile.
182         
183     /* printf("distance squared = %.2f, bounding radius = %.2f\n", 
184        point_line_dist_squared(&(t->offset), &(v->view_pos), 
185        v->local_up), t->bounding_radius); */
186
187     dist = point_line_dist_squared( t->center, abs_view_pos, local_up );
188     if ( dist < FG_SQUARE(t->bounding_radius) ) {
189
190         // traverse fragment list for tile
191         FGTileEntry::FragmentIterator current = t->begin();
192         FGTileEntry::FragmentIterator last = t->end();
193
194         for ( ; current != last; ++current ) {
195             frag_ptr = &(*current);
196             /* printf("distance squared = %.2f, bounding radius = %.2f\n", 
197                point_line_dist_squared( &(frag_ptr->center), 
198                &abs_view_pos), local_up),
199                frag_ptr->bounding_radius); */
200
201             dist = point_line_dist_squared( frag_ptr->center,
202                                             abs_view_pos,
203                                             local_up);
204             if ( dist <= FG_SQUARE(frag_ptr->bounding_radius) ) {
205                 if ( frag_ptr->intersect( abs_view_pos, 
206                                           earth_center, 0, result ) ) {
207                     FG_LOG( FG_TERRAIN, FG_DEBUG, "intersection point " <<
208                             result );
209                     // compute geocentric coordinates of tile center
210                     Point3D pp = fgCartToPolar3d(result);
211                     FG_LOG( FG_TERRAIN, FG_DEBUG, "  polar form = " << pp );
212                     // convert to geodetic coordinates
213                     fgGeocToGeod(pp.lat(), pp.radius(), &lat_geod, 
214                                  &alt, &sea_level_r);
215
216                     // printf("alt = %.2f\n", alt);
217                     // exit since we found an intersection
218                     if ( alt > -9999.0 ) {
219                         // printf("returning alt\n");
220                         return alt;
221                     } else {
222                         // printf("returning 0\n");
223                         return 0.0;
224                     }
225                 }
226             }
227         }
228     }
229
230     FG_LOG( FG_TERRAIN, FG_INFO, "(new) no terrain intersection found" );
231
232     return 0.0;
233 }
234
235
236 // Determine scenery altitude.  Normally this just happens when we
237 // render the scene, but we'd also like to be able to do this
238 // explicitely.  lat & lon are in radians.  abs_view_pos in meters.
239 // Returns result in meters.
240 double
241 FGTileMgr::current_elev( double lon, double lat, const Point3D& abs_view_pos ) {
242     FGTileCache *c;
243     FGTileEntry *t;
244     fgFRAGMENT *frag_ptr;
245     Point3D earth_center(0.0);
246     Point3D result;
247     MAT3vec local_up;
248     double dist, lat_geod, alt, sea_level_r;
249     int index;
250
251     c = &global_tile_cache;
252
253     local_up[0] = abs_view_pos.x();
254     local_up[1] = abs_view_pos.y();
255     local_up[2] = abs_view_pos.z();
256
257     FG_LOG( FG_TERRAIN, FG_DEBUG, "Absolute view pos = " << abs_view_pos );
258
259     // Find current translation offset
260     FGBucket p( lon * RAD_TO_DEG, lat * RAD_TO_DEG );
261     index = c->exists(p);
262     if ( index < 0 ) {
263         FG_LOG( FG_TERRAIN, FG_WARN, "Tile not found" );
264         return 0.0;
265     }
266
267     t = c->get_tile(index);
268
269     scenery.next_center = t->center;
270     
271     FG_LOG( FG_TERRAIN, FG_DEBUG, 
272             "Pos = (" << lon * RAD_TO_DEG << ", " << lat * RAD_TO_DEG
273             << ")  Current bucket = " << p 
274             << "  Index = " << p.gen_index_str() );
275
276     FG_LOG( FG_TERRAIN, FG_DEBUG, "Tile center " << t->center 
277             << "  bounding radius = " << t->bounding_radius );
278
279     // calculate tile offset
280     // x = (t->offset.x = t->center.x - scenery.center.x);
281     // y = (t->offset.y = t->center.y - scenery.center.y);
282     // z = (t->offset.z = t->center.z - scenery.center.z);
283     
284     // calc current terrain elevation calculate distance from
285     // vertical tangent line at current position to center of
286     // tile.
287         
288     /* printf("distance squared = %.2f, bounding radius = %.2f\n", 
289        point_line_dist_squared(&(t->offset), &(v->view_pos), 
290        v->local_up), t->bounding_radius); */
291
292     dist = point_line_dist_squared( t->center, abs_view_pos, local_up );
293     FG_LOG( FG_TERRAIN, FG_DEBUG, "(gross check) dist squared = " << dist );
294
295     if ( dist < FG_SQUARE(t->bounding_radius) ) {
296
297         // traverse fragment list for tile
298         FGTileEntry::FragmentIterator current = t->begin();
299         FGTileEntry::FragmentIterator last = t->end();
300
301         for ( ; current != last; ++current ) {
302             frag_ptr = &(*current);
303             /* printf("distance squared = %.2f, bounding radius = %.2f\n", 
304                point_line_dist_squared( &(frag_ptr->center), 
305                &abs_view_pos), local_up),
306                frag_ptr->bounding_radius); */
307
308             dist = point_line_dist_squared( frag_ptr->center,
309                                             abs_view_pos,
310                                             local_up);
311             if ( dist <= FG_SQUARE(frag_ptr->bounding_radius) ) {
312                 if ( frag_ptr->intersect( abs_view_pos, 
313                                           earth_center, 0, result ) ) {
314                     FG_LOG( FG_TERRAIN, FG_DEBUG, "intersection point " <<
315                             result );
316                     // compute geocentric coordinates of tile center
317                     Point3D pp = fgCartToPolar3d(result);
318                     FG_LOG( FG_TERRAIN, FG_DEBUG, "  polar form = " << pp );
319                     // convert to geodetic coordinates
320                     fgGeocToGeod(pp.lat(), pp.radius(), &lat_geod, 
321                                  &alt, &sea_level_r);
322
323                     // printf("alt = %.2f\n", alt);
324                     // exit since we found an intersection
325                     if ( alt > -9999.0 ) {
326                         // printf("returning alt\n");
327                         return alt;
328                     } else {
329                         // printf("returning 0\n");
330                         return 0.0;
331                     }
332                 }
333             }
334         }
335     }
336
337     FG_LOG( FG_TERRAIN, FG_INFO, "(old) no terrain intersection found" );
338
339     return 0.0;
340 }
341
342
343 // given the current lon/lat, fill in the array of local chunks.  If
344 // the chunk isn't already in the cache, then read it from disk.
345 int FGTileMgr::update( void ) {
346     FGTileCache *c;
347     FGInterface *f;
348     FGBucket p2;
349     static FGBucket p_last(false);
350     static double last_lon = -1000.0;  // in degrees
351     static double last_lat = -1000.0;  // in degrees
352     int tile_diameter;
353     int i, j, dw, dh;
354
355     c = &global_tile_cache;
356     f = current_aircraft.fdm_state;
357
358     tile_diameter = current_options.get_tile_diameter();
359
360     FGBucket p1( f->get_Longitude() * RAD_TO_DEG,
361                  f->get_Latitude() * RAD_TO_DEG );
362     dw = tile_diameter / 2;
363     dh = tile_diameter / 2;
364
365     if ( (p1 == p_last) && (state == Running) ) {
366         // same bucket as last time
367         FG_LOG( FG_TERRAIN, FG_DEBUG, "Same bucket as last time" );
368     } else if ( (state == Start) || (state == Inited) ) {
369         state = Running;
370
371         // First time through or we have teleporte, initialize the
372         // system and load all relavant tiles
373
374         FG_LOG( FG_TERRAIN, FG_INFO, "Updating Tile list for " << p1 );
375         FG_LOG( FG_TERRAIN, FG_INFO, "  First time through ... " );
376         FG_LOG( FG_TERRAIN, FG_INFO, "  Updating Tile list for " << p1 );
377         FG_LOG( FG_TERRAIN, FG_INFO, "  Loading " 
378                 << tile_diameter * tile_diameter << " tiles" );
379
380         // wipe/initialize tile cache
381         c->init();
382         p_last.make_bad();
383
384         // build the local area list and schedule tiles for loading
385
386         // start with the center tile and work out in concentric
387         // "rings"
388
389         p2 = fgBucketOffset( f->get_Longitude() * RAD_TO_DEG,
390                              f->get_Latitude() * RAD_TO_DEG,
391                              0, 0 );
392         sched_tile( p2, &tiles[(dh*tile_diameter) + dw]);
393
394         for ( i = 3; i <= tile_diameter; i = i + 2 ) {
395             int span = i / 2;
396
397             // bottom row
398             for ( j = -span; j <= span; ++j ) {
399                 p2 = fgBucketOffset( f->get_Longitude() * RAD_TO_DEG,
400                                      f->get_Latitude() * RAD_TO_DEG,
401                                      j, -span );
402                 sched_tile( p2, &tiles[((dh-span)*tile_diameter) + dw+j]);
403             }
404
405             // top row
406             for ( j = -span; j <= span; ++j ) {
407                 p2 = fgBucketOffset( f->get_Longitude() * RAD_TO_DEG,
408                                      f->get_Latitude() * RAD_TO_DEG,
409                                      j, span );
410                 sched_tile( p2, &tiles[((dh+span)*tile_diameter) + dw+j]);
411             }
412
413             // middle rows
414             for ( j = -span + 1; j <= span - 1; ++j ) {
415                 p2 = fgBucketOffset( f->get_Longitude() * RAD_TO_DEG,
416                                      f->get_Latitude() * RAD_TO_DEG,
417                                      -span, j );
418                 sched_tile( p2, &tiles[((dh+j)*tile_diameter) + dw-span]);
419                 p2 = fgBucketOffset( f->get_Longitude() * RAD_TO_DEG,
420                                      f->get_Latitude() * RAD_TO_DEG,
421                                      span, j );
422                 sched_tile( p2, &tiles[((dh+j)*tile_diameter) + dw+span]);
423             }
424
425         }
426
427         /* for ( j = 0; j < tile_diameter; j++ ) {
428             for ( i = 0; i < tile_diameter; i++ ) {
429                 // fgBucketOffset(&p1, &p2, i - dw, j - dh);
430                 p2 = fgBucketOffset( f->get_Longitude() * RAD_TO_DEG,
431                                      f->get_Latitude() * RAD_TO_DEG,
432                                      i - dw, j -dh );
433                 sched_tile( p2, &tiles[(j*tile_diameter) + i]);
434             }
435         } */
436
437         // Now force a load of the center tile and inner ring so we
438         // have something to see in our first frame.
439         for ( i = 0; i < 9; ++i ) {
440             if ( load_queue.size() ) {
441                 FG_LOG( FG_TERRAIN, FG_INFO, 
442                         "Load queue not empty, loading a tile" );
443             
444                 FGLoadRec pending = load_queue.front();
445                 load_queue.pop_front();
446                 load_tile( pending.b, pending.index );
447             }
448         }
449
450     } else {
451         // We've moved to a new bucket, we need to scroll our
452         // structures, and load in the new tiles
453
454         // CURRENTLY THIS ASSUMES WE CAN ONLY MOVE TO ADJACENT TILES.
455         // AT ULTRA HIGH SPEEDS THIS ASSUMPTION MAY NOT BE VALID IF
456         // THE AIRCRAFT CAN SKIP A TILE IN A SINGLE ITERATION.
457
458         FG_LOG( FG_TERRAIN, FG_INFO, "Updating Tile list for " << p1 );
459
460         if ( (p1.get_lon() > p_last.get_lon()) ||
461              ( (p1.get_lon() == p_last.get_lon()) && (p1.get_x() > p_last.get_x()) ) ) {
462             FG_LOG( FG_TERRAIN, FG_INFO, 
463                     "  Loading " << tile_diameter << "tiles" );
464             for ( j = 0; j < tile_diameter; j++ ) {
465                 // scrolling East
466                 for ( i = 0; i < tile_diameter - 1; i++ ) {
467                     tiles[(j*tile_diameter) + i] = 
468                         tiles[(j*tile_diameter) + i + 1];
469                 }
470                 // load in new column
471                 // fgBucketOffset(&p_last, &p2, dw + 1, j - dh);
472                 p2 = fgBucketOffset( last_lon, last_lat, dw + 1, j - dh );
473                 sched_tile( p2, &tiles[(j*tile_diameter) + 
474                                              tile_diameter - 1]);
475             }
476         } else if ( (p1.get_lon() < p_last.get_lon()) ||
477                     ( (p1.get_lon() == p_last.get_lon()) && (p1.get_x() < p_last.get_x()) ) ) {
478             FG_LOG( FG_TERRAIN, FG_INFO, 
479                     "  Loading " << tile_diameter << "tiles" );
480             for ( j = 0; j < tile_diameter; j++ ) {
481                 // scrolling West
482                 for ( i = tile_diameter - 1; i > 0; i-- ) {
483                     tiles[(j*tile_diameter) + i] = 
484                         tiles[(j*tile_diameter) + i - 1];
485                 }
486                 // load in new column
487                 // fgBucketOffset(&p_last, &p2, -dw - 1, j - dh);
488                 p2 = fgBucketOffset( last_lon, last_lat, -dw - 1, j - dh );
489                 sched_tile( p2, &tiles[(j*tile_diameter) + 0]);
490             }
491         }
492
493         if ( (p1.get_lat() > p_last.get_lat()) ||
494              ( (p1.get_lat() == p_last.get_lat()) && (p1.get_y() > p_last.get_y()) ) ) {
495             FG_LOG( FG_TERRAIN, FG_INFO, 
496                     "  Loading " << tile_diameter << "tiles" );
497             for ( i = 0; i < tile_diameter; i++ ) {
498                 // scrolling North
499                 for ( j = 0; j < tile_diameter - 1; j++ ) {
500                     tiles[(j * tile_diameter) + i] =
501                         tiles[((j+1) * tile_diameter) + i];
502                 }
503                 // load in new column
504                 // fgBucketOffset(&p_last, &p2, i - dw, dh + 1);
505                 p2 = fgBucketOffset( last_lon, last_lat, i - dw, dh + 1);
506                 sched_tile( p2, &tiles[((tile_diameter-1) * 
507                                                tile_diameter) + i]);
508             }
509         } else if ( (p1.get_lat() < p_last.get_lat()) ||
510                     ( (p1.get_lat() == p_last.get_lat()) && (p1.get_y() < p_last.get_y()) ) ) {
511             FG_LOG( FG_TERRAIN, FG_INFO, 
512                     "  Loading " << tile_diameter << "tiles" );
513             for ( i = 0; i < tile_diameter; i++ ) {
514                 // scrolling South
515                 for ( j = tile_diameter - 1; j > 0; j-- ) {
516                     tiles[(j * tile_diameter) + i] = 
517                         tiles[((j-1) * tile_diameter) + i];
518                 }
519                 // load in new column
520                 // fgBucketOffset(&p_last, &p2, i - dw, -dh - 1);
521                 p2 = fgBucketOffset( last_lon, last_lat, i - dw, -dh - 1);
522                 sched_tile( p2, &tiles[0 + i]);
523             }
524         }
525     }
526
527     if ( load_queue.size() ) {
528         FG_LOG( FG_TERRAIN, FG_INFO, "Load queue not empty, loading a tile" );
529
530         FGLoadRec pending = load_queue.front();
531         load_queue.pop_front();
532         load_tile( pending.b, pending.index );
533     }
534
535     // find our current elevation (feed in the current bucket to save work)
536     Point3D geod_pos = Point3D( f->get_Longitude(), f->get_Latitude(), 0.0);
537     Point3D tmp_abs_view_pos = fgGeodToCart(geod_pos);
538
539     scenery.cur_elev = 
540         current_elev( f->get_Longitude(), f->get_Latitude(), tmp_abs_view_pos );
541
542     p_last = p1;
543     last_lon = f->get_Longitude() * RAD_TO_DEG;
544     last_lat = f->get_Latitude() * RAD_TO_DEG;
545
546     return 1;
547 }
548
549
550 // Calculate if point/radius is inside view frustum
551 static int viewable( const Point3D& cp, double radius ) {
552     int viewable = 1; // start by assuming it's viewable
553     double x1, y1;
554
555     /********************************/
556 #if defined( USE_FAST_FOV_CLIP ) // views.hxx
557     /********************************/
558         
559     MAT3vec eye;        
560     double *mat;
561     double x, y, z;
562
563     x = cp.x();
564     y = cp.y();
565     z = cp.z();
566         
567     mat = (double *)(current_view.get_WORLD_TO_EYE());
568         
569     eye[2] =  x*mat[2] + y*mat[6] + z*mat[10] + mat[14];
570         
571     // Check near and far clip plane
572     if( ( eye[2] > radius ) ||
573         ( eye[2] + radius + current_weather.get_visibility() < 0) )
574         {
575             return(0);
576         }
577         
578     eye[0] = (x*mat[0] + y*mat[4] + z*mat[8] + mat[12])
579         * current_view.get_slope_x();
580
581     // check right and left clip plane (from eye perspective)
582     x1 = radius * current_view.get_fov_x_clip();
583     if( (eye[2] > -(eye[0]+x1)) || (eye[2] > (eye[0]-x1)) )
584         {
585             return(0);
586         }
587         
588     eye[1] = (x*mat[1] + y*mat[5] + z*mat[9] + mat[13]) 
589         * current_view.get_slope_y();
590
591     // check bottom and top clip plane (from eye perspective)
592     y1 = radius * current_view.get_fov_y_clip();
593     if( (eye[2] > -(eye[1]+y1)) || (eye[2] > (eye[1]-y1)) )
594         {
595             return(0);
596         }
597
598     /********************************/  
599 #else // DO NOT USE_FAST_FOV_CLIP
600     /********************************/  
601
602     fgVIEW *v;
603     MAT3hvec world, eye;
604     double x0, slope;
605
606     v = &current_view;
607
608     MAT3_SET_HVEC(world, cp->x, cp->y, cp->z, 1.0);
609     // MAT3mult_vec(eye, world, v->WORLD_TO_EYE);
610     // printf( "\nworld -> eye = %.2f %.2f %.2f  radius = %.2f\n", 
611     //         eye[0], eye[1], eye[2], radius);
612
613     // Use lazy evaluation for calculating eye hvec.
614 #define vec world
615 #define mat v->WORLD_TO_EYE
616     eye[2] = vec[0]*mat[0][2]+vec[1]*mat[1][2]+vec[2]*mat[2][2]+mat[3][2];
617
618     // Check near clip plane
619     if ( eye[2] > radius ) {
620         return(0);
621     }
622
623     // Check far clip plane
624     if ( eye[2] + radius < -current_weather.get_visibility() ) {
625         return(0);
626     }
627
628     // check right clip plane (from eye perspective)
629     // y = m * (x - x0) = equation of a line intercepting X axis at x0
630     x1 = v->cos_fov_x * radius;
631     y1 = v->sin_fov_x * radius;
632     slope = v->slope_x;
633     eye[0] = vec[0]*mat[0][0]+vec[1]*mat[1][0]+vec[2]*mat[2][0]+mat[3][0];
634
635     if ( eye[2] > ((slope * (eye[0] - x1)) + y1) ) {
636         return( false );
637     }
638
639     // check left clip plane (from eye perspective)
640     if ( eye[2] > -((slope * (eye[0] + x1)) - y1) ) {
641         return( false );
642     }
643
644     // check bottom clip plane (from eye perspective)
645     x1 = -(v->cos_fov_y) * radius;
646     y1 = v->sin_fov_y * radius;
647     slope = v->slope_y;
648     eye[1] = vec[0]*mat[0][1]+vec[1]*mat[1][1]+vec[2]*mat[2][1]+mat[3][1];
649 #undef vec
650 #undef mat
651
652     if ( eye[2] > ((slope * (eye[1] - x1)) + y1) ) {
653         return( false );
654     }
655
656     // check top clip plane (from eye perspective)
657     if ( eye[2] > -((slope * (eye[1] + x1)) - y1) ) {
658         return( false );
659     }
660
661 #endif // defined( USE_FAST_FOV_CLIP )
662         
663     return(viewable);
664 }
665
666
667 // NEW 
668
669 // inrange() IS THIS POINT WITHIN POSSIBLE VIEWING RANGE ?
670 //      calculate distance from vertical tangent line at
671 //      current position to center of object.
672 //      this is equivalent to
673 //      dist = point_line_dist_squared( &(t->center), &(v->abs_view_pos), 
674 //                                      v->local_up );
675 //      if ( dist < FG_SQUARE(t->bounding_radius) ) {
676 //
677 // the compiler should inline this for us
678
679 static int
680 inrange( const double radius, const Point3D& center, const Point3D& vp,
681          const MAT3vec up)
682 {
683     MAT3vec u, u1, v;
684     //  double tmp;
685         
686     // u = p - p0
687     u[0] = center.x() - vp.x();
688     u[1] = center.y() - vp.y();
689     u[2] = center.z() - vp.z();
690         
691     // calculate the projection, u1, of u along d.
692     // u1 = ( dot_prod(u, d) / dot_prod(d, d) ) * d;
693         
694     MAT3_SCALE_VEC(u1, up,
695                    (MAT3_DOT_PRODUCT(u, up) / MAT3_DOT_PRODUCT(up, up)) );
696     
697     // v = u - u1 = vector from closest point on line, p1, to the
698     // original point, p.
699     MAT3_SUB_VEC(v, u, u1);
700         
701     return( FG_SQUARE(radius) >= MAT3_DOT_PRODUCT(v, v));
702 }
703
704
705 // NEW for legibility
706
707 // update this tile's geometry for current view
708 // The Compiler should inline this
709 static void
710 update_tile_geometry( FGTileEntry *t, GLdouble *MODEL_VIEW)
711 {
712     GLfloat *m;
713     double x, y, z;
714         
715     // calculate tile offset
716     t->offset = t->center - scenery.center;
717
718     x = t->offset.x();
719     y = t->offset.y();
720     z = t->offset.z();
721         
722     m = t->model_view;
723         
724     // Calculate the model_view transformation matrix for this tile
725     FG_MEM_COPY( m, MODEL_VIEW, 16*sizeof(GLdouble) );
726     
727     // This is equivalent to doing a glTranslatef(x, y, z);
728     m[12] += (m[0]*x + m[4]*y + m[8] *z);
729     m[13] += (m[1]*x + m[5]*y + m[9] *z);
730     m[14] += (m[2]*x + m[6]*y + m[10]*z);
731     // m[15] += (m[3]*x + m[7]*y + m[11]*z);
732     // m[3] m7[] m[11] are 0.0 see LookAt() in views.cxx
733     // so m[15] is unchanged
734 }
735
736
737 // Render the local tiles
738 void FGTileMgr::render( void ) {
739     FGInterface *f;
740     FGTileCache *c;
741     FGTileEntry *t;
742     FGView *v;
743     Point3D frag_offset;
744     fgFRAGMENT *frag_ptr;
745     FGMaterialSlot *mtl_ptr;
746     int i;
747     int tile_diameter;
748     int index;
749     int culled = 0;
750     int drawn = 0;
751
752     c = &global_tile_cache;
753     f = current_aircraft.fdm_state;
754     v = &current_view;
755
756     tile_diameter = current_options.get_tile_diameter();
757
758     // moved to fgTileMgrUpdate, right after we check if we need to
759     // load additional tiles:
760     // scenery.cur_elev = fgTileMgrCurElev( FG_Longitude, FG_Latitude, 
761     //                                      v->abs_view_pos );
762  
763     // initialize the transient per-material fragment lists
764     material_mgr.init_transient_material_lists();
765    
766     // Pass 1
767     // traverse the potentially viewable tile list
768     for ( i = 0; i < (tile_diameter * tile_diameter); i++ ) {
769         index = tiles[i];
770         // fgPrintf( FG_TERRAIN, FG_DEBUG, "Index = %d\n", index);
771         t = c->get_tile(index);
772
773         if ( t->is_loaded() ) {
774
775             // calculate tile offset
776             t->SetOffset( scenery.center );
777
778             // calculate ssg transform
779             sgCoord sgcoord;
780             sgSetCoord( &sgcoord,
781                         t->offset.x(), t->offset.y(), t->offset.z(),
782                         0.0, 0.0, 0.0 );
783             t->branch_ptr->setTransform( &sgcoord );
784
785             // Course (tile based) culling
786             if ( viewable(t->offset, t->bounding_radius) ) {
787                 // at least a portion of this tile could be viewable
788             
789                 // Calculate the model_view transformation matrix for this tile
790                 // This is equivalent to doing a glTranslatef(x, y, z);
791                 t->update_view_matrix( v->get_MODEL_VIEW() );
792
793                 // xglPushMatrix();
794                 // xglTranslatef(t->offset.x, t->offset.y, t->offset.z);
795
796                 // traverse fragment list for tile
797                 FGTileEntry::FragmentIterator current = t->begin();
798                 FGTileEntry::FragmentIterator last = t->end();
799
800                 for ( ; current != last; ++current ) {
801                     frag_ptr = &(*current);
802                 
803                     if ( frag_ptr->display_list >= 0 ) {
804                         // Fine (fragment based) culling
805                         frag_offset = frag_ptr->center - scenery.center;
806
807                         if ( viewable(frag_offset, 
808                                       frag_ptr->bounding_radius*2) )
809                         {
810                             // add to transient per-material property
811                             // fragment list
812
813                             // frag_ptr->tile_offset.x = t->offset.x;
814                             // frag_ptr->tile_offset.y = t->offset.y;
815                             // frag_ptr->tile_offset.z = t->offset.z;
816
817                             mtl_ptr = frag_ptr->material_ptr;
818                             // printf(" lookup = %s\n", mtl_ptr->texture_name);
819                             if ( ! mtl_ptr->append_sort_list( frag_ptr ) ) {
820                                 FG_LOG( FG_TERRAIN, FG_ALERT,
821                                         "Overran material sorting array" );
822                             }
823
824                             // xglCallList(frag_ptr->display_list);
825                             drawn++;
826                         } else {
827                             // printf("Culled a fragment %.2f %.2f %.2f %.2f\n",
828                             //        frag_ptr->center.x, frag_ptr->center.y,
829                             //        frag_ptr->center.z, 
830                             //        frag_ptr->bounding_radius);
831                             culled++;
832                         }
833                     }
834                 }
835
836                 // xglPopMatrix();
837             } else {
838                 culled += t->fragment_list.size();
839             }
840         } else {
841             FG_LOG( FG_TERRAIN, FG_DEBUG, "Skipping a not yet loaded tile" );
842         }
843     }
844
845     if ( (drawn + culled) > 0 ) {
846         v->set_vfc_ratio( (double)culled / (double)(drawn + culled) );
847     } else {
848         v->set_vfc_ratio( 0.0 );
849     }
850     // printf("drawn = %d  culled = %d  saved = %.2f\n", drawn, culled, 
851     //        v->vfc_ratio);
852
853     // Pass 2
854     // traverse the transient per-material fragment lists and render
855     // out all fragments for each material property.
856     xglPushMatrix();
857     // material_mgr.render_fragments();
858     xglPopMatrix();
859 }