]> git.mxchange.org Git - flightgear.git/blob - src/Scenery/hitlist.cxx
Fix a terrain intersection calculating bug. The code wasn't quite handling
[flightgear.git] / src / Scenery / hitlist.cxx
1 // hitlist.cxx -
2 // Height Over Terrain and Assosciated Routines for FlightGear based Scenery
3 // Written by Norman Vine, started 2000.
4
5 #ifdef HAVE_CONFIG_H
6 #  include <config.h>
7 #endif
8
9 #include <float.h>
10 #include <math.h>
11
12 #include <simgear/sg_inlines.h>
13 #include <simgear/debug/logstream.hxx>
14 #include <simgear/math/point3d.hxx>
15 #include <simgear/math/sg_geodesy.hxx>
16 #include <simgear/math/vector.hxx>
17
18 #include <Main/globals.hxx>
19 #include <Main/viewer.hxx>
20 #include <Scenery/scenery.hxx>
21
22 #include "hitlist.hxx"
23
24 // forward declaration of our helper/convenience functions
25 static void sgMultMat4(sgdMat4 dst, sgdMat4 m1, sgMat4 m2);
26 static void ssgGetEntityTransform(ssgEntity *entity, sgMat4 m );
27 static void ssgGetCurrentBSphere( ssgEntity *entity, sgVec3 center, float *radius, sgMat4 m );
28
29
30 // ======================
31 // This is same as PLib's sgdIsectInfLinePlane()
32 // and can be replaced by it after the next PLib release
33 static int fgdIsectInfLinePlane( sgdVec3 dst, const sgdVec3 l_org,
34                                  const sgdVec3 l_vec, const sgdVec4 plane )
35 {
36     SGDfloat tmp = sgdScalarProductVec3 ( l_vec, plane ) ;
37
38     /* Is line parallel to plane? */
39
40     if ( fabs ( tmp ) < FLT_EPSILON )
41         return false ;
42
43     sgdScaleVec3 ( dst, l_vec, -( sgdScalarProductVec3 ( l_org, plane )
44                                   + plane[3] ) / tmp ) ;
45     sgdAddVec3  ( dst, l_org ) ;
46
47     return true ;
48 }
49
50 // ======================
51
52 /*
53  * Given a point and a triangle lying on the same plane
54  * check to see if the point is inside the triangle
55  */
56 // This is same as PLib's sgdPointInTriangle()
57 // and can be replaced by it after the next PLib release
58 static bool fgdPointInTriangle( sgdVec3 point, sgdVec3 tri[3] )
59 {
60     sgdVec3 dif;
61
62     SGDfloat min, max;
63     // punt if outside bouding cube
64     SG_MIN_MAX3 ( min, max, tri[0][0], tri[1][0], tri[2][0] );
65     if( (point[0] < min) || (point[0] > max) )
66         return false;
67     dif[0] = max - min;
68
69     SG_MIN_MAX3 ( min, max, tri[0][1], tri[1][1], tri[2][1] );
70     if( (point[1] < min) || (point[1] > max) )
71         return false;
72     dif[1] = max - min;
73
74     SG_MIN_MAX3 ( min, max, tri[0][2], tri[1][2], tri[2][2] );
75     if( (point[2] < min) || (point[2] > max) )
76         return false;
77     dif[2] = max - min;
78
79     // drop the smallest dimension so we only have to work in 2d.
80     SGDfloat min_dim = SG_MIN3 (dif[0], dif[1], dif[2]);
81     SGDfloat x1, y1, x2, y2, x3, y3, rx, ry;
82     if ( fabs(min_dim-dif[0]) <= DBL_EPSILON ) {
83         // x is the smallest dimension
84         x1 = point[1];
85         y1 = point[2];
86         x2 = tri[0][1];
87         y2 = tri[0][2];
88         x3 = tri[1][1];
89         y3 = tri[1][2];
90         rx = tri[2][1];
91         ry = tri[2][2];
92     } else if ( fabs(min_dim-dif[1]) <= DBL_EPSILON ) {
93         // y is the smallest dimension
94         x1 = point[0];
95         y1 = point[2];
96         x2 = tri[0][0];
97         y2 = tri[0][2];
98         x3 = tri[1][0];
99         y3 = tri[1][2];
100         rx = tri[2][0];
101         ry = tri[2][2];
102     } else if ( fabs(min_dim-dif[2]) <= DBL_EPSILON ) {
103         // z is the smallest dimension
104         x1 = point[0];
105         y1 = point[1];
106         x2 = tri[0][0];
107         y2 = tri[0][1];
108         x3 = tri[1][0];
109         y3 = tri[1][1];
110         rx = tri[2][0];
111         ry = tri[2][1];
112     } else {
113         // all dimensions are really small so lets call it close
114         // enough and return a successful match
115         return true;
116     }
117
118     // check if intersection point is on the same side of p1 <-> p2 as p3
119     SGDfloat tmp = (y2 - y3) / (x2 - x3);
120     int side1 = SG_SIGN (tmp * (rx - x3) + y3 - ry);
121     int side2 = SG_SIGN (tmp * (x1 - x3) + y3 - y1);
122     if ( side1 != side2 ) {
123         // printf("failed side 1 check\n");
124         return false;
125     }
126
127     // check if intersection point is on correct side of p2 <-> p3 as p1
128     tmp = (y3 - ry) / (x3 - rx);
129     side1 = SG_SIGN (tmp * (x2 - rx) + ry - y2);
130     side2 = SG_SIGN (tmp * (x1 - rx) + ry - y1);
131     if ( side1 != side2 ) {
132         // printf("failed side 2 check\n");
133         return false;
134     }
135
136     // check if intersection point is on correct side of p1 <-> p3 as p2
137     tmp = (y2 - ry) / (x2 - rx);
138     side1 = SG_SIGN (tmp * (x3 - rx) + ry - y3);
139     side2 = SG_SIGN (tmp * (x1 - rx) + ry - y1);
140     if ( side1 != side2 ) {
141         // printf("failed side 3  check\n");
142         return false;
143     }
144
145     return true;
146 }
147
148 // ======================
149
150 inline static int isZeroAreaTri( sgdVec3 tri[3] )
151 {
152     return( sgdEqualVec3(tri[0], tri[1]) ||
153             sgdEqualVec3(tri[1], tri[2]) ||
154             sgdEqualVec3(tri[2], tri[0]) );
155 }
156
157 FGHitList::FGHitList() :
158         last(NULL), test_dist(DBL_MAX)
159 {
160 }
161
162 FGHitList::~FGHitList() {}
163
164
165 /*
166 Find the intersection of an infinite line with a leaf
167 the line being defined by a point and direction.
168
169 Variables
170 In:
171 ssgLeaf pointer  -- leaf
172 qualified matrix -- m
173 line origin      -- orig
174 line direction   -- dir
175 Out:
176 result  -- intersection point
177 normal  -- intersected tri's normal
178
179 Returns:
180 true if intersection found
181 false otherwise
182
183 !!! WARNING !!!
184 If you need an exhaustive list of hitpoints YOU MUST use
185 the generic version of this function as the specialized
186 versions will do an early out of expensive tests if the point
187 can not be the closest one found
188 !!! WARNING !!!
189 */
190 int FGHitList::IntersectLeaf( ssgLeaf *leaf, sgdMat4 m,
191                               sgdVec3 orig, sgdVec3 dir )
192 {
193     int num_hits = 0;
194     int i = 0;
195
196     for ( ; i < leaf->getNumTriangles(); ++i ) {
197         short i1, i2, i3;
198         leaf->getTriangle( i, &i1, &i2, &i3 );
199
200         sgdVec3 tri[3];
201         sgdSetVec3( tri[0], leaf->getVertex( i1 ) );
202         sgdSetVec3( tri[1], leaf->getVertex( i2 ) );
203         sgdSetVec3( tri[2], leaf->getVertex( i3 ) );
204
205         if( isZeroAreaTri( tri ) )
206             continue;
207
208         sgdVec4 plane;
209         sgdMakePlane( plane, tri[0], tri[1], tri[2] );
210
211         sgdVec3 point;
212         if( fgdIsectInfLinePlane( point, orig, dir, plane ) ) {
213             if( fgdPointInTriangle( point, tri ) ) {
214                 // transform point into passed into desired coordinate frame
215                 sgdXformPnt3( point, point, m );
216                 sgdXformPnt4(plane,plane,m);
217                 add(leaf,i,point,plane);
218                 num_hits++;
219             }
220         }
221     }
222     return num_hits;
223 }
224
225 // ======================
226
227 int FGHitList::IntersectLeaf( ssgLeaf *leaf, sgdMat4 m,
228                               sgdVec3 orig, sgdVec3 dir,
229                               GLenum primType )
230 {
231     double tmp_dist;
232
233     // number of hits but there could be more that
234     // were not found because of short circut switch !
235     // so you may want to use the unspecialized IntersectLeaf()
236     int n, num_hits = 0;
237
238     int ntri = leaf->getNumTriangles();
239     for ( n = 0; n < ntri; ++n )
240     {
241         sgdVec3 tri[3];
242
243         switch ( primType )
244         {
245         case GL_POLYGON :
246             SG_LOG( SG_TERRAIN, SG_ALERT,
247                     "WARNING: dubiously handled GL_POLYGON" );
248         case GL_TRIANGLE_FAN :
249             /* SG_LOG( SG_TERRAIN, SG_ALERT,
250                "IntersectLeaf: GL_TRIANGLE_FAN" ); */
251             if ( !n ) {
252                 sgdSetVec3( tri[0], leaf->getVertex( short(0) ) );
253                 sgdSetVec3( tri[1], leaf->getVertex( short(1) ) );
254                 sgdSetVec3( tri[2], leaf->getVertex( short(2) ) );
255             } else {
256                 sgdCopyVec3( tri[1], tri[2] );
257                 sgdSetVec3( tri[2], leaf->getVertex( short(n+2) ) );
258             }
259             break;
260         case GL_TRIANGLES :
261             /* SG_LOG( SG_TERRAIN, SG_DEBUG,
262                "IntersectLeaf: GL_TRIANGLES" ); */
263             sgdSetVec3( tri[0], leaf->getVertex( short(n*3) ) );
264             sgdSetVec3( tri[1], leaf->getVertex( short(n*3+1) ) );
265             sgdSetVec3( tri[2], leaf->getVertex( short(n*3+2) ) );
266             break;
267         case GL_QUAD_STRIP :
268             SG_LOG( SG_TERRAIN, SG_ALERT,
269                     "WARNING: dubiously handled GL_QUAD_STRIP" );
270         case GL_TRIANGLE_STRIP :
271             /* SG_LOG( SG_TERRAIN, SG_ALERT,
272                "IntersectLeaf: GL_TRIANGLE_STRIP" ); */
273             if ( !n ) {
274                 sgdSetVec3( tri[0], leaf->getVertex( short(0) ) );
275                 sgdSetVec3( tri[1], leaf->getVertex( short(1) ) );
276                 sgdSetVec3( tri[2], leaf->getVertex( short(2) ) );
277             } else {
278                 if ( n & 1 ) {
279                     sgdCopyVec3( tri[0], tri[2] );
280                     sgdSetVec3( tri[2], leaf->getVertex( short(n+2) ) );
281                 } else {
282                     sgdCopyVec3( tri[2], tri[1] );
283                     sgdSetVec3( tri[2], leaf->getVertex( short(n+2) ) );
284                 }
285             }
286             break;
287         case GL_QUADS :
288             SG_LOG( SG_TERRAIN, SG_ALERT,
289                     "WARNING: dubiously handled GL_QUADS" );
290             sgdSetVec3( tri[0], leaf->getVertex( short(n*2) ) );
291             sgdSetVec3( tri[1], leaf->getVertex( short(n*2+1) ) );
292             sgdSetVec3( tri[2], leaf->getVertex( short(n*2 + 2 - (n&1)*4) ) );
293             break;
294         default:
295             SG_LOG( SG_TERRAIN, SG_ALERT,
296                     "WARNING: not-handled structure: " << primType );
297             return IntersectLeaf( leaf, m, orig, dir);
298         }
299
300         if( isZeroAreaTri( tri ) )
301             continue;
302
303         sgdVec4 plane;
304         sgdMakePlane( plane, tri[0], tri[1], tri[2] );
305
306         sgdVec3 point;
307
308         // find point of intersection of line from point org
309         // in direction dir with triangle's plane
310         SGDfloat tmp = sgdScalarProductVec3 ( dir, plane ) ;
311         /* Is line parallel to plane? */
312         if ( sgdAbs ( tmp ) < FLT_EPSILON /*DBL_EPSILON*/ )
313             continue ;
314
315         // find parametric point
316         sgdScaleVec3 ( point, dir,
317                        -( sgdScalarProductVec3 ( orig, plane ) + plane[3] )
318                        / tmp ) ;
319
320         // short circut if this point is further away then a previous hit
321         tmp_dist = sgdDistanceSquaredVec3(point, orig );
322         if( tmp_dist > test_dist )
323             continue;
324
325         // place parametric point in world
326         sgdAddVec3 ( point, orig ) ;
327
328         if( fgdPointInTriangle( point, tri ) ) {
329             // transform point into passed coordinate frame
330             sgdXformPnt3( point, point, m );
331             sgdXformPnt4(plane,plane,m);
332             add(leaf,n,point,plane);
333             test_dist = tmp_dist;
334             num_hits++;
335         }
336     }
337     return num_hits;
338 }
339
340 // ======================
341 inline static bool IN_RANGE( sgdVec3 v, double radius ) {
342     return ( sgdScalarProductVec3(v, v) < (radius*radius) );
343 }
344
345 // ======================
346 void FGHitList::IntersectBranch( ssgBranch *branch, sgdMat4 m,
347                                  sgdVec3 orig, sgdVec3 dir )
348 {
349     /* the lookat vector and matrix in branch's coordinate frame
350      * but we won't determine these unless needed,
351      * This 'lazy evaluation' is a result of profiling data */
352     sgdVec3 orig_leaf, dir_leaf;
353     sgdMat4 m_leaf;
354
355     // 'lazy evaluation' flag
356     int first_time = 1;
357
358     for ( ssgEntity *kid = branch->getKid( 0 );
359             kid != NULL;
360             kid = branch->getNextKid() )
361     {
362         if ( kid->getTraversalMask() & SSGTRAV_HOT
363              && !kid->getBSphere()->isEmpty() )
364         {
365             sgdVec3 center;
366             sgdSetVec3( center,
367                         kid->getBSphere()->getCenter()[0],
368                         kid->getBSphere()->getCenter()[1],
369                         kid->getBSphere()->getCenter()[2] );
370             sgdXformPnt3( center, m ) ;
371
372             // sgdClosestPointToLineDistSquared( center, orig, dir )
373             // inlined here because because of profiling results
374             sgdVec3 u, u1, v;
375             sgdSubVec3(u, center, orig);
376             sgdScaleVec3( u1, dir, sgdScalarProductVec3(u,dir)
377                           / sgdScalarProductVec3(dir,dir) );
378             sgdSubVec3(v, u, u1);
379
380             // double because of possible overflow
381             if ( IN_RANGE( v, double(kid->getBSphere()->getRadius()) ) )
382             {
383                 if ( kid->isAKindOf ( ssgTypeBranch() ) )
384                 {
385                     sgdMat4 m_new;
386                     if ( kid->isA( ssgTypeTransform() ) )
387                     {
388                         sgMat4 fxform;
389                         ((ssgTransform *)kid)->getTransform( fxform );
390                         sgMultMat4(m_new, m, fxform);
391                     } else {
392                         sgdCopyMat4(m_new, m);
393                     }
394                     IntersectBranch( (ssgBranch *)kid, m_new, orig, dir );
395                 }
396                 else if ( kid->isAKindOf( ssgTypeLeaf() ) )
397                 {
398                     if ( first_time ) {
399                         sgdTransposeNegateMat4( m_leaf, m );
400                         sgdXformPnt3( orig_leaf, orig, m_leaf );
401                         sgdXformPnt3( dir_leaf,  dir,  m_leaf );
402                         first_time = 0;
403                     }
404                     GLenum primType = ((ssgLeaf *)kid)->getPrimitiveType();
405                     IntersectLeaf( (ssgLeaf *)kid, m, orig_leaf, dir_leaf, primType );
406                 }
407             }  // Out of range
408         } // branch not requested to be traversed
409     } // end for loop
410 }
411
412
413
414 // ======================
415 // a temporary hack until we get everything rewritten with sgdVec3
416 static inline Point3D operator + (const Point3D& a, const sgdVec3 b)
417 {
418     return Point3D(a.x()+b[0], a.y()+b[1], a.z()+b[2]);
419 }
420
421
422 // ======================
423 void FGHitList::Intersect( ssgBranch *scene, sgdVec3 orig, sgdVec3 dir ) {
424     sgdMat4 m;
425     clear();
426     sgdMakeIdentMat4 ( m ) ;
427     IntersectBranch( scene, m, orig, dir );
428 }
429
430 // ======================
431 void FGHitList::Intersect( ssgBranch *scene, sgdMat4 m, sgdVec3 orig, sgdVec3 dir )
432 {
433     clear();
434     IntersectBranch( scene, m, orig, dir );
435 }
436
437 // ======================
438 // Need these because of mixed matrix types
439 static void sgMultMat4(sgdMat4 dst, sgdMat4 m1, sgMat4 m2)
440 {
441     for ( int j = 0 ; j < 4 ; j++ )
442     {
443         dst[0][j] = m2[0][0] * m1[0][j] +
444                     m2[0][1] * m1[1][j] +
445                     m2[0][2] * m1[2][j] +
446                     m2[0][3] * m1[3][j] ;
447
448         dst[1][j] = m2[1][0] * m1[0][j] +
449                     m2[1][1] * m1[1][j] +
450                     m2[1][2] * m1[2][j] +
451                     m2[1][3] * m1[3][j] ;
452
453         dst[2][j] = m2[2][0] * m1[0][j] +
454                     m2[2][1] * m1[1][j] +
455                     m2[2][2] * m1[2][j] +
456                     m2[2][3] * m1[3][j] ;
457
458         dst[3][j] = m2[3][0] * m1[0][j] +
459                     m2[3][1] * m1[1][j] +
460                     m2[3][2] * m1[2][j] +
461                     m2[3][3] * m1[3][j] ;
462     }
463 }
464
465 // ======================
466 static void ssgGetEntityTransform(ssgEntity *entity, sgMat4 m )
467 {
468     /*
469        Walk backwards up the tree, transforming the
470        vertex by all the matrices along the way.
471
472        Upwards recursion hurts my head.
473        */
474
475     sgMat4 mat ;
476
477     /*
478        If this node has a parent - get the composite
479        matrix for the parent.
480        */
481     if ( entity->getNumParents() > 0 )
482         ssgGetEntityTransform ( entity->getParent(0), mat ) ;
483     else
484         sgMakeIdentMat4 ( mat ) ;
485
486     /*
487        If this node has a transform - then concatenate it.
488        */
489     if ( entity -> isAKindOf ( ssgTypeTransform () ) ) {
490         sgMat4 this_mat ;
491         ((ssgTransform *) entity) -> getTransform ( this_mat ) ;
492         sgPostMultMat4 ( mat, this_mat ) ;
493     }
494
495     sgCopyMat4 ( m, mat ) ;
496 }
497
498 // ======================
499 // return the passed entitity's bsphere's center point radius and
500 // fully formed current model matrix for entity
501 static void ssgGetCurrentBSphere( ssgEntity *entity, sgVec3 center, float *radius, sgMat4 m )
502 {
503     sgSphere *bsphere = entity->getBSphere();
504     *radius = (double)bsphere->getRadius();
505     sgCopyVec3( center, bsphere->getCenter() );
506     sgMakeIdentMat4 ( m ) ;
507     ssgGetEntityTransform( entity, m );
508 }
509
510
511 // ======================
512 // Determine scenery altitude via ssg.
513 // returned results are in meters
514 bool fgCurrentElev( sgdVec3 abs_view_pos, sgdVec3 scenery_center,
515                     FGHitList *hit_list,
516                     double *terrain_elev, double *radius, double *normal)
517 {
518     sgdVec3 view_pos;
519     sgdSubVec3( view_pos, abs_view_pos, scenery_center );
520
521     sgdVec3 orig, dir;
522     sgdCopyVec3(orig, view_pos );
523     sgdCopyVec3(dir, abs_view_pos );
524
525     hit_list->Intersect( globals->get_scenery()->get_terrain_branch(),
526                          orig, dir );
527
528     int this_hit=0;
529     Point3D geoc;
530     double result = -9999;
531     Point3D sc(scenery_center[0], scenery_center[1], scenery_center[2]) ;
532
533     int hitcount = hit_list->num_hits();
534     // cout << "hits = " << hitcount << endl;
535     for ( int i = 0; i < hitcount; ++i ) {
536         geoc = sgCartToPolar3d( sc + hit_list->get_point(i) );
537         double lat_geod, alt, sea_level_r;
538         sgGeocToGeod(geoc.lat(), geoc.radius(), &lat_geod,
539                      &alt, &sea_level_r);
540         // cout << "hit " << i << " lon = " << geoc.lon() << " lat = "
541         //      << lat_geod << " alt = " << alt << endl;
542         if ( alt > result && alt < 10000 ) {
543             result = alt;
544             this_hit = i;
545         }
546     }
547     // cout << endl;
548
549     if ( result > -9000 ) {
550         *terrain_elev = result;
551         *radius = geoc.radius();
552         sgVec3 tmp;
553         sgSetVec3(tmp, hit_list->get_normal(this_hit));
554         // cout << "cur_normal: " << tmp[0] << " " << tmp[1] << " "  << tmp[2] << endl;
555         sgdSetVec3( normal, tmp );
556         // float *up = globals->get_current_view()->get_world_up();
557         // cout << "world_up  : " << up[0] << " " << up[1] << " " << up[2] << endl;
558         /* ssgState *IntersectedLeafState =
559               ((ssgLeaf*)hit_list->get_entity(this_hit))->getState(); */
560         return true;
561     } else {
562         SG_LOG( SG_TERRAIN, SG_INFO, "no terrain intersection" );
563         *terrain_elev = 0.0;
564         float *up = globals->get_current_view()->get_world_up();
565         sgdSetVec3(normal, up[0], up[1], up[2]);
566         return false;
567     }
568 }
569
570
571 // ======================
572 // Determine scenery altitude via ssg.
573 // returned results are in meters
574 bool fgCurrentElev( sgdVec3 abs_view_pos, sgdVec3 scenery_center,
575                     ssgTransform *terra_transform,
576                     FGHitList *hit_list,
577                     double *terrain_elev, double *radius, double *normal)
578 {
579     sgdVec3 view_pos;
580     sgdSubVec3( view_pos, abs_view_pos, scenery_center );
581
582     sgdVec3 orig, dir;
583     sgdCopyVec3(orig, view_pos );
584
585     sgdCopyVec3(dir, abs_view_pos );
586     sgdNormalizeVec3(dir);
587
588     sgMat4 fxform;
589     sgMakeIdentMat4 ( fxform ) ;
590     ssgGetEntityTransform( terra_transform, fxform );
591
592     sgdMat4 xform;
593     sgdSetMat4(xform,fxform);
594     hit_list->Intersect( terra_transform, xform, orig, dir );
595
596     int this_hit=0;
597     Point3D geoc;
598     double result = -9999;
599     Point3D sc(scenery_center[0], scenery_center[1], scenery_center[2]) ;
600
601     int hitcount = hit_list->num_hits();
602     for ( int i = 0; i < hitcount; ++i ) {
603         geoc = sgCartToPolar3d( sc + hit_list->get_point(i) );
604         double lat_geod, alt, sea_level_r;
605         sgGeocToGeod(geoc.lat(), geoc.radius(), &lat_geod,
606                      &alt, &sea_level_r);
607         if ( alt > result && alt < 20000 ) {
608             result = alt;
609             this_hit = i;
610         }
611     }
612
613     if ( result > -9000 ) {
614         *terrain_elev = result;
615         *radius = geoc.radius();
616         sgVec3 tmp;
617         sgSetVec3(tmp, hit_list->get_normal(this_hit));
618         // cout << "cur_normal: " << tmp[0] << " " << tmp[1] << " "  << tmp[2] << endl;
619         sgdSetVec3( normal, tmp );
620         // float *up = globals->get_current_view()->get_world_up();
621        // cout << "world_up  : " << up[0] << " " << up[1] << " " << up[2] << endl;
622         /* ssgState *IntersectedLeafState =
623               ((ssgLeaf*)hit_list->get_entity(this_hit))->getState(); */
624         return true;
625     } else {
626         SG_LOG( SG_TERRAIN, SG_DEBUG, "DOING FULL TERRAIN INTERSECTION" );
627         return fgCurrentElev( abs_view_pos, scenery_center, hit_list,
628                               terrain_elev,radius,normal);
629     }
630 }
631