2 // Height Over Terrain and Assosciated Routines for FlightGear based Scenery
3 // Written by Norman Vine, started 2000.
15 #include <simgear/sg_inlines.h>
16 #include <simgear/debug/logstream.hxx>
17 #include <simgear/math/point3d.hxx>
18 #include <simgear/math/sg_geodesy.hxx>
19 #include <simgear/math/vector.hxx>
20 #include <simgear/timing/timestamp.hxx>
22 #include <Main/globals.hxx>
23 #include <Main/viewer.hxx>
24 #include <Scenery/scenery.hxx>
26 #include "hitlist.hxx"
28 // Specialized version of sgMultMat4 needed because of mixed matrix
30 static inline void sgMultMat4(sgdMat4 dst, sgdMat4 m1, sgMat4 m2) {
31 for ( int j = 0 ; j < 4 ; j++ ) {
32 dst[0][j] = m2[0][0] * m1[0][j] +
37 dst[1][j] = m2[1][0] * m1[0][j] +
42 dst[2][j] = m2[2][0] * m1[0][j] +
47 dst[3][j] = m2[3][0] * m1[0][j] +
56 * Walk backwards up the tree, transforming the vertex by all the
57 * matrices along the way.
59 * Upwards recursion hurts my head.
61 static void ssgGetEntityTransform(ssgEntity *entity, sgMat4 m ) {
64 // If this node has a parent - get the composite matrix for the
66 if ( entity->getNumParents() > 0 )
67 ssgGetEntityTransform ( entity->getParent(0), mat ) ;
69 sgMakeIdentMat4 ( mat ) ;
71 // If this node has a transform - then concatenate it.
72 if ( entity -> isAKindOf ( ssgTypeTransform () ) ) {
74 ((ssgTransform *) entity) -> getTransform ( this_mat ) ;
75 sgPostMultMat4 ( mat, this_mat ) ;
78 sgCopyMat4 ( m, mat ) ;
82 // return the passed entitity's bsphere's center point radius and
83 // fully formed current model matrix for entity
84 static inline void ssgGetCurrentBSphere( ssgEntity *entity, sgVec3 center,
85 float *radius, sgMat4 m )
87 sgSphere *bsphere = entity->getBSphere();
88 *radius = (double)bsphere->getRadius();
89 sgCopyVec3( center, bsphere->getCenter() );
90 sgMakeIdentMat4 ( m ) ;
91 ssgGetEntityTransform( entity, m );
95 // This is same as PLib's sgdIsectInfLinePlane() and can be replaced
96 // by it after the next PLib release
97 static inline bool fgdIsectInfLinePlane( sgdVec3 dst,
100 const sgdVec4 plane )
102 SGDfloat tmp = sgdScalarProductVec3 ( l_vec, plane ) ;
104 /* Is line parallel to plane? */
106 if ( fabs ( tmp ) < FLT_EPSILON )
109 sgdScaleVec3 ( dst, l_vec, -( sgdScalarProductVec3 ( l_org, plane )
110 + plane[3] ) / tmp ) ;
111 sgdAddVec3 ( dst, l_org ) ;
117 * Given a point and a triangle lying on the same plane check to see
118 * if the point is inside the triangle
120 * This is same as PLib's sgdPointInTriangle() and can be replaced by
121 * it after the next PLib release
123 static inline bool fgdPointInTriangle( sgdVec3 point, sgdVec3 tri[3] )
127 // Some tolerance in meters we accept a point to be outside of the triangle
128 // and still return that it is inside.
131 // punt if outside bouding cube
132 SG_MIN_MAX3 ( min, max, tri[0][0], tri[1][0], tri[2][0] );
133 if( (point[0] < min - eps) || (point[0] > max + eps) )
137 SG_MIN_MAX3 ( min, max, tri[0][1], tri[1][1], tri[2][1] );
138 if( (point[1] < min - eps) || (point[1] > max + eps) )
142 SG_MIN_MAX3 ( min, max, tri[0][2], tri[1][2], tri[2][2] );
143 if( (point[2] < min - eps) || (point[2] > max + eps) )
147 // drop the smallest dimension so we only have to work in 2d.
148 SGDfloat min_dim = SG_MIN3 (dif[0], dif[1], dif[2]);
149 SGDfloat x1, y1, x2, y2, x3, y3, rx, ry;
150 if ( fabs(min_dim-dif[0]) <= DBL_EPSILON ) {
151 // x is the smallest dimension
160 } else if ( fabs(min_dim-dif[1]) <= DBL_EPSILON ) {
161 // y is the smallest dimension
170 } else if ( fabs(min_dim-dif[2]) <= DBL_EPSILON ) {
171 // z is the smallest dimension
181 // all dimensions are really small so lets call it close
182 // enough and return a successful match
186 // check if intersection point is on the same side of p1 <-> p2 as p3
187 SGDfloat tmp = (y2 - y3);
188 SGDfloat tmpn = (x2 - x3);
189 int side1 = SG_SIGN (tmp * (rx - x3) + (y3 - ry) * tmpn);
190 int side2 = SG_SIGN (tmp * (x1 - x3) + (y3 - side1*eps - y1) * tmpn);
191 if ( side1 != side2 ) {
192 // printf("failed side 1 check\n");
196 // check if intersection point is on correct side of p2 <-> p3 as p1
199 side1 = SG_SIGN (tmp * (x2 - rx) + (ry - y2) * tmpn);
200 side2 = SG_SIGN (tmp * (x1 - rx) + (ry - side1*eps - y1) * tmpn);
201 if ( side1 != side2 ) {
202 // printf("failed side 2 check\n");
206 // check if intersection point is on correct side of p1 <-> p3 as p2
209 side1 = SG_SIGN (tmp * (x3 - rx) + (ry - y3) * tmpn);
210 side2 = SG_SIGN (tmp * (x1 - rx) + (ry - side1*eps - y1) * tmpn);
211 if ( side1 != side2 ) {
212 // printf("failed side 3 check\n");
220 // Check if all three vertices are the same point (or close enough)
221 static inline int isZeroAreaTri( sgdVec3 tri[3] )
223 return( sgdEqualVec3(tri[0], tri[1]) ||
224 sgdEqualVec3(tri[1], tri[2]) ||
225 sgdEqualVec3(tri[2], tri[0]) );
230 FGHitList::FGHitList() :
231 last(NULL), test_dist(DBL_MAX)
237 FGHitList::~FGHitList() {}
241 Find the intersection of an infinite line with a leaf the line being
242 defined by a point and direction.
246 ssgLeaf pointer -- leaf
247 qualified matrix -- m
249 line direction -- dir
251 result -- intersection point
252 normal -- intersected tri's normal
255 true if intersection found
260 If you need an exhaustive list of hitpoints YOU MUST use the generic
261 version of this function as the specialized versions will do an early
262 out of expensive tests if the point can not be the closest one found
266 int FGHitList::IntersectLeaf( ssgLeaf *leaf, sgdMat4 m,
267 sgdVec3 orig, sgdVec3 dir )
272 for ( ; i < leaf->getNumTriangles(); ++i ) {
274 leaf->getTriangle( i, &i1, &i2, &i3 );
277 sgdSetVec3( tri[0], leaf->getVertex( i1 ) );
278 sgdSetVec3( tri[1], leaf->getVertex( i2 ) );
279 sgdSetVec3( tri[2], leaf->getVertex( i3 ) );
281 if( isZeroAreaTri( tri ) )
285 sgdMakePlane( plane, tri[0], tri[1], tri[2] );
288 if( fgdIsectInfLinePlane( point, orig, dir, plane ) ) {
289 if( fgdPointInTriangle( point, tri ) ) {
290 // transform point into passed into desired coordinate frame
291 sgdXformPnt3( point, point, m );
292 sgdXformPnt4(plane,plane,m);
293 add(leaf,i,point,plane);
302 // Short circuit/slightly optimized version of the full IntersectLeaf()
303 int FGHitList::IntersectLeaf( ssgLeaf *leaf, sgdMat4 m,
304 sgdVec3 orig, sgdVec3 dir,
309 // number of hits but there could be more that
310 // were not found because of short circut switch !
311 // so you may want to use the unspecialized IntersectLeaf()
314 int ntri = leaf->getNumTriangles();
315 for ( n = 0; n < ntri; ++n )
322 SG_LOG( SG_TERRAIN, SG_ALERT,
323 "WARNING: dubiously handled GL_POLYGON" );
324 case GL_TRIANGLE_FAN :
325 /* SG_LOG( SG_TERRAIN, SG_ALERT,
326 "IntersectLeaf: GL_TRIANGLE_FAN" ); */
328 sgdSetVec3( tri[0], leaf->getVertex( short(0) ) );
329 sgdSetVec3( tri[1], leaf->getVertex( short(1) ) );
330 sgdSetVec3( tri[2], leaf->getVertex( short(2) ) );
332 sgdCopyVec3( tri[1], tri[2] );
333 sgdSetVec3( tri[2], leaf->getVertex( short(n+2) ) );
337 /* SG_LOG( SG_TERRAIN, SG_DEBUG,
338 "IntersectLeaf: GL_TRIANGLES" ); */
339 sgdSetVec3( tri[0], leaf->getVertex( short(n*3) ) );
340 sgdSetVec3( tri[1], leaf->getVertex( short(n*3+1) ) );
341 sgdSetVec3( tri[2], leaf->getVertex( short(n*3+2) ) );
344 SG_LOG( SG_TERRAIN, SG_ALERT,
345 "WARNING: dubiously handled GL_QUAD_STRIP" );
346 case GL_TRIANGLE_STRIP :
347 /* SG_LOG( SG_TERRAIN, SG_ALERT,
348 "IntersectLeaf: GL_TRIANGLE_STRIP" ); */
350 sgdSetVec3( tri[0], leaf->getVertex( short(0) ) );
351 sgdSetVec3( tri[1], leaf->getVertex( short(1) ) );
352 sgdSetVec3( tri[2], leaf->getVertex( short(2) ) );
355 sgdCopyVec3( tri[0], tri[2] );
356 sgdSetVec3( tri[2], leaf->getVertex( short(n+2) ) );
358 sgdCopyVec3( tri[1], tri[2] );
359 sgdSetVec3( tri[2], leaf->getVertex( short(n+2) ) );
364 SG_LOG( SG_TERRAIN, SG_ALERT,
365 "WARNING: dubiously handled GL_QUADS" );
366 sgdSetVec3( tri[0], leaf->getVertex( short(n*2) ) );
367 sgdSetVec3( tri[1], leaf->getVertex( short(n*2+1) ) );
368 sgdSetVec3( tri[2], leaf->getVertex( short(n*2 + 2 - (n&1)*4) ) );
371 SG_LOG( SG_TERRAIN, SG_ALERT,
372 "WARNING: not-handled structure: " << primType );
373 return IntersectLeaf( leaf, m, orig, dir);
376 if( isZeroAreaTri( tri ) )
380 sgdMakePlane( plane, tri[0], tri[1], tri[2] );
384 // find point of intersection of line from point org
385 // in direction dir with triangle's plane
386 SGDfloat tmp = sgdScalarProductVec3 ( dir, plane ) ;
387 /* Is line parallel to plane? */
388 if ( sgdAbs ( tmp ) < FLT_EPSILON /*DBL_EPSILON*/ )
391 // find parametric point
392 sgdScaleVec3 ( point, dir,
393 -( sgdScalarProductVec3 ( orig, plane ) + plane[3] )
396 // short circut if this point is further away then a previous hit
397 tmp_dist = sgdDistanceSquaredVec3(point, orig );
398 if( tmp_dist > test_dist )
401 // place parametric point in world
402 sgdAddVec3 ( point, orig ) ;
404 if( fgdPointInTriangle( point, tri ) ) {
405 // transform point into passed coordinate frame
406 sgdXformPnt3( point, point, m );
407 sgdXformPnt4(plane,plane,m);
408 add(leaf,n,point,plane);
409 test_dist = tmp_dist;
418 inline static bool IN_RANGE( sgdVec3 v, double radius ) {
419 return ( sgdScalarProductVec3(v, v) < (radius*radius) );
423 void FGHitList::IntersectBranch( ssgBranch *branch, sgdMat4 m,
424 sgdVec3 orig, sgdVec3 dir )
426 /* the lookat vector and matrix in branch's coordinate frame but
427 * we won't determine these unless needed, This 'lazy evaluation'
428 * is a result of profiling data */
429 sgdVec3 orig_leaf, dir_leaf;
432 // 'lazy evaluation' flag
435 for ( ssgEntity *kid = branch->getKid( 0 );
437 kid = branch->getNextKid() )
439 if ( kid->getTraversalMask() & SSGTRAV_HOT
440 && !kid->getBSphere()->isEmpty() )
444 kid->getBSphere()->getCenter()[0],
445 kid->getBSphere()->getCenter()[1],
446 kid->getBSphere()->getCenter()[2] );
447 sgdXformPnt3( center, m ) ;
449 // sgdClosestPointToLineDistSquared( center, orig, dir )
450 // inlined here because because of profiling results
452 sgdSubVec3(u, center, orig);
453 sgdScaleVec3( u1, dir, sgdScalarProductVec3(u,dir)
454 / sgdScalarProductVec3(dir,dir) );
455 sgdSubVec3(v, u, u1);
457 // double because of possible overflow
458 if ( IN_RANGE( v, double(kid->getBSphere()->getRadius()) ) )
460 if ( kid->isAKindOf ( ssgTypeBranch() ) )
463 if ( kid->isA( ssgTypeTransform() ) )
466 ((ssgTransform *)kid)->getTransform( fxform );
467 sgMultMat4(m_new, m, fxform);
469 sgdCopyMat4(m_new, m);
471 IntersectBranch( (ssgBranch *)kid, m_new, orig, dir );
473 else if ( kid->isAKindOf( ssgTypeLeaf() ) )
476 sgdTransposeNegateMat4( m_leaf, m );
477 sgdXformPnt3( orig_leaf, orig, m_leaf );
478 sgdXformVec3( dir_leaf, dir, m_leaf );
481 // GLenum primType = ((ssgLeaf *)kid)->getPrimitiveType();
482 // IntersectLeaf( (ssgLeaf *)kid, m, orig_leaf, dir_leaf,
484 IntersectLeaf( (ssgLeaf *)kid, m, orig_leaf, dir_leaf );
487 } // branch not requested to be traversed
492 // a temporary hack until we get everything rewritten with sgdVec3
493 static inline Point3D operator + (const Point3D& a, const sgdVec3 b)
495 return Point3D(a.x()+b[0], a.y()+b[1], a.z()+b[2]);
499 void FGHitList::Intersect( ssgBranch *scene, sgdVec3 orig, sgdVec3 dir ) {
502 sgdMakeIdentMat4 ( m ) ;
503 IntersectBranch( scene, m, orig, dir );
507 void FGHitList::Intersect( ssgBranch *scene, sgdMat4 m, sgdVec3 orig, sgdVec3 dir )
510 IntersectBranch( scene, m, orig, dir );
514 // Determine scenery altitude via ssg.
515 // returned results are in meters
516 // static double hitlist1_time = 0.0;
518 bool fgCurrentElev( sgdVec3 abs_view_pos, double max_alt_m,
519 sgdVec3 scenery_center,
521 double *terrain_elev, double *radius, double *normal)
523 // SGTimeStamp start; start.stamp();
527 sgdSubVec3( view_pos, abs_view_pos, scenery_center );
530 sgdCopyVec3(orig, view_pos );
531 sgdCopyVec3(dir, abs_view_pos );
533 hit_list->Intersect( globals->get_scenery()->get_terrain_branch(),
539 double hit_elev = -9999;
540 double max_elev = -9999;
541 Point3D sc(scenery_center[0], scenery_center[1], scenery_center[2]) ;
543 int hitcount = hit_list->num_hits();
544 // cout << "hits = " << hitcount << endl;
545 for ( int i = 0; i < hitcount; ++i ) {
546 // FIXME: sgCartToGeod is slow. Call it just once for the
547 // "sc" point, and then handle the rest with a geodetic "up"
548 // vector approximation. Across one tile, this will be
550 double alt = sgCartToGeod( sc + hit_list->get_point(i) ).elev();
551 // cout << "hit " << i << " lon = " << geoc.lon() << " lat = "
552 // << lat_geod << " alt = " << alt << " max alt = " << max_alt_m
554 if ( alt > hit_elev && alt < max_alt_m ) {
555 // cout << " it's a keeper" << endl;
559 if ( alt > hit_elev ) {
565 if ( this_hit < 0 ) {
566 // no hits below us, take the max hit
571 if ( hit_elev > -9000 ) {
572 *terrain_elev = hit_elev;
573 *radius = geoc.radius();
575 sgSetVec3(tmp, hit_list->get_normal(this_hit));
576 // cout << "cur_normal: " << tmp[0] << " " << tmp[1] << " " << tmp[2] << endl;
577 sgdSetVec3( normal, tmp );
578 // float *up = globals->get_current_view()->get_world_up();
579 // cout << "world_up : " << up[0] << " " << up[1] << " " << up[2] << endl;
580 /* ssgState *IntersectedLeafState =
581 ((ssgLeaf*)hit_list->get_entity(this_hit))->getState(); */
584 SG_LOG( SG_TERRAIN, SG_INFO, "no terrain intersection" );
586 float *up = globals->get_current_view()->get_world_up();
587 sgdSetVec3(normal, up[0], up[1], up[2]);
591 // SGTimeStamp finish; finish.stamp();
592 // hitlist1_time = ( 29.0 * hitlist1_time + (finish - start) ) / 30.0;
593 // cout << " time per call = " << hitlist1_time << endl;
599 // static double hitlist2_time = 0.0;
601 // Determine scenery altitude via ssg.
602 // returned results are in meters
603 bool fgCurrentElev( sgdVec3 abs_view_pos, double max_alt_m,
604 sgdVec3 scenery_center,
605 ssgTransform *terra_transform,
607 double *terrain_elev, double *radius, double *normal)
609 // SGTimeStamp start; start.stamp();
613 sgdSubVec3( view_pos, abs_view_pos, scenery_center );
616 sgdCopyVec3(orig, view_pos );
618 sgdCopyVec3(dir, abs_view_pos );
619 sgdNormalizeVec3(dir);
622 sgMakeIdentMat4 ( fxform ) ;
623 ssgGetEntityTransform( terra_transform, fxform );
626 sgdSetMat4(xform,fxform);
627 hit_list->Intersect( terra_transform, xform, orig, dir );
632 double hit_elev = -9999;
633 double max_elev = -9999;
634 Point3D sc(scenery_center[0], scenery_center[1], scenery_center[2]) ;
636 int hitcount = hit_list->num_hits();
637 // cout << "hits = " << hitcount << endl;
638 for ( int i = 0; i < hitcount; ++i ) {
639 // FIXME: sgCartToGeod is slow. Call it just once for the
640 // "sc" point, and then handle the rest with a geodetic "up"
641 // vector approximation. Across one tile, this will be
643 double alt = sgCartToGeod( sc + hit_list->get_point(i) ).elev();
644 // cout << "hit " << i << " lon = " << geoc.lon() << " lat = "
645 // << lat_geod << " alt = " << alt << " max alt = " << max_alt_m
647 if ( alt > hit_elev && alt < max_alt_m ) {
650 // cout << " it's a keeper" << endl;
652 if ( alt > hit_elev ) {
658 if ( this_hit < 0 ) {
659 // no hits below us, take the max hit
664 if ( hit_elev > -9000 ) {
665 *terrain_elev = hit_elev;
666 *radius = geoc.radius();
668 sgSetVec3(tmp, hit_list->get_normal(this_hit));
669 // cout << "cur_normal: " << tmp[0] << " " << tmp[1] << " " << tmp[2] << endl;
670 sgdSetVec3( normal, tmp );
671 // float *up = globals->get_current_view()->get_world_up();
672 // cout << "world_up : " << up[0] << " " << up[1] << " " << up[2] << endl;
673 /* ssgState *IntersectedLeafState =
674 ((ssgLeaf*)hit_list->get_entity(this_hit))->getState(); */
677 SG_LOG( SG_TERRAIN, SG_DEBUG, "DOING FULL TERRAIN INTERSECTION" );
678 result = fgCurrentElev( abs_view_pos, max_alt_m, scenery_center,
679 hit_list, terrain_elev, radius, normal);
682 // SGTimeStamp finish; finish.stamp();
683 // hitlist2_time = ( 29.0 * hitlist2_time + (finish - start) ) / 30.0;
684 // cout << "time per call 2 = " << hitlist2_time << endl;