2 // Height Over Terrain and Assosciated Routines for FlightGear based Scenery
3 // Written by Norman Vine, started 2000.
21 #include <simgear/constants.h>
22 #include <simgear/sg_inlines.h>
23 #include <simgear/debug/logstream.hxx>
24 #include <simgear/math/point3d.hxx>
25 #include <simgear/math/sg_geodesy.hxx>
26 #include <simgear/math/vector.hxx>
28 #include <Main/globals.hxx>
29 #include <Main/viewer.hxx>
31 #include "hitlist.hxx"
33 extern ssgBranch *terrain_branch;
35 static int sgdIsectInfLinePlane( sgdVec3 dst, const sgdVec3 l_org,
36 const sgdVec3 l_vec, const sgdVec4 plane )
38 SGDfloat tmp = sgdScalarProductVec3 ( l_vec, plane ) ;
40 /* Is line parallel to plane? */
42 if ( fabs ( tmp ) < FLT_EPSILON )
45 sgdScaleVec3 ( dst, l_vec, -( sgdScalarProductVec3 ( l_org, plane )
46 + plane[3] ) / tmp ) ;
47 sgdAddVec3 ( dst, l_org ) ;
54 * Given a point and a triangle lying on the same plane
55 * check to see if the point is inside the triangle
57 static bool sgdPointInTriangle( sgdVec3 point, sgdVec3 tri[3] )
62 // punt if outside bouding cube
63 SG_MIN_MAX3 ( min, max, tri[0][0], tri[1][0], tri[2][0] );
64 if( (point[0] < min) || (point[0] > max) )
68 SG_MIN_MAX3 ( min, max, tri[0][1], tri[1][1], tri[2][1] );
69 if( (point[1] < min) || (point[1] > max) )
73 SG_MIN_MAX3 ( min, max, tri[0][2], tri[1][2], tri[2][2] );
74 if( (point[2] < min) || (point[2] > max) )
78 // drop the smallest dimension so we only have to work in 2d.
79 SGDfloat min_dim = SG_MIN3 (dif[0], dif[1], dif[2]);
80 SGDfloat x1, y1, x2, y2, x3, y3, rx, ry;
81 if ( fabs(min_dim-dif[0]) <= DBL_EPSILON ) {
82 // x is the smallest dimension
91 } else if ( fabs(min_dim-dif[1]) <= DBL_EPSILON ) {
92 // y is the smallest dimension
101 } else if ( fabs(min_dim-dif[2]) <= DBL_EPSILON ) {
102 // z is the smallest dimension
112 // all dimensions are really small so lets call it close
113 // enough and return a successful match
117 // check if intersection point is on the same side of p1 <-> p2 as p3
118 SGDfloat tmp = (y2 - y3) / (x2 - x3);
119 int side1 = SG_SIGN (tmp * (rx - x3) + y3 - ry);
120 int side2 = SG_SIGN (tmp * (x1 - x3) + y3 - y1);
121 if ( side1 != side2 ) {
122 // printf("failed side 1 check\n");
126 // check if intersection point is on correct side of p2 <-> p3 as p1
127 tmp = (y3 - ry) / (x3 - rx);
128 side1 = SG_SIGN (tmp * (x2 - rx) + ry - y2);
129 side2 = SG_SIGN (tmp * (x1 - rx) + ry - y1);
130 if ( side1 != side2 ) {
131 // printf("failed side 2 check\n");
135 // check if intersection point is on correct side of p1 <-> p3 as p2
136 tmp = (y2 - ry) / (x2 - rx);
137 side1 = SG_SIGN (tmp * (x3 - rx) + ry - y3);
138 side2 = SG_SIGN (tmp * (x1 - rx) + ry - y1);
139 if ( side1 != side2 ) {
140 // printf("failed side 3 check\n");
148 inline static int isZeroAreaTri( sgdVec3 tri[3] )
150 return( sgdEqualVec3(tri[0], tri[1]) ||
151 sgdEqualVec3(tri[1], tri[2]) ||
152 sgdEqualVec3(tri[2], tri[0]) );
156 * Given a point and a triangle lying on the same plane
157 * check to see if the point is inside the triangle
161 static bool PointInTri( sgdVec3 P, sgdVec3 V[3] )
164 sgdSubVec3(X,P,V[0]);
165 sgdSubVec3(W1,V[1],V[2]);
166 sgdSubVec3(W2,V[2],V[0]);
169 sgdVectorProductVec3(C, W1, W2);
171 double d = sgdScalarProductVec3(X,C);
173 // Test not needed if you KNOW point is on plane of triangle
174 // and triangle is not degenerate
175 if( d > -FLT_EPSILON && d < FLT_EPSILON )
178 double u11 = sgdScalarProductVec3(W1,W1);
179 double u22 = sgdScalarProductVec3(W2,W2);
180 double u12 = sgdScalarProductVec3(W1,W2);
182 double y1 = sgdScalarProductVec3(X,W1);
183 double y2 = sgdScalarProductVec3(X,W2);
184 double z1 = (y1*u22 - y2*u12)/d;
189 double z2 = (y2*u11 - y1*u12)/d;
199 Find the intersection of an infinite line with a leaf
200 the line being defined by a point and direction.
204 ssgLeaf pointer -- leaf
205 qualified matrix -- m
207 line direction -- dir
209 result -- intersection point
210 normal -- intersected tri's normal
213 true if intersection found
217 If you need an exhaustive list of hitpoints YOU MUST use
218 the generic version of this function as the specialized
219 versions will do an early out of expensive tests if the point
220 can not be the closest one found
223 int FGHitList::IntersectLeaf( ssgLeaf *leaf, sgdMat4 m,
224 sgdVec3 orig, sgdVec3 dir )
229 for ( ; i < leaf->getNumTriangles(); ++i ) {
231 leaf->getTriangle( i, &i1, &i2, &i3 );
234 sgdSetVec3( tri[0], leaf->getVertex( i1 ) );
235 sgdSetVec3( tri[1], leaf->getVertex( i2 ) );
236 sgdSetVec3( tri[2], leaf->getVertex( i3 ) );
238 // avoid division by zero when two points are the same
239 if( isZeroAreaTri( tri ) )
243 sgdMakePlane( plane, tri[0], tri[1], tri[2] );
246 if( sgdIsectInfLinePlane( point, orig, dir, plane ) ) {
247 if( sgdPointInTriangle( point, tri ) ) {
248 // transform point into passed into desired coordinate frame
249 sgdXformPnt3( point, point, m );
250 add(leaf,i,point,plane);
260 int FGHitList::IntersectPolyOrFanLeaf( ssgLeaf *leaf, sgdMat4 m,
261 sgdVec3 orig, sgdVec3 dir )
265 // number of hits but there could be more that
266 // were not found because of short circut switch !
267 // so you may want to use the unspecialized IntersectLeaf()
270 int ntri = leaf->getNumTriangles();
271 for ( int n = 0; n < ntri; ++n ) {
275 sgdSetVec3( tri[0], leaf->getVertex( short(0) ) );
276 sgdSetVec3( tri[1], leaf->getVertex( short(1) ) );
277 sgdSetVec3( tri[2], leaf->getVertex( short(2) ) );
279 sgdCopyVec3( tri[1], tri[2] );
280 sgdSetVec3( tri[2], leaf->getVertex( short(n+2) ) );
283 if( isZeroAreaTri( tri ) )
287 sgdMakePlane( plane, tri[0], tri[1], tri[2] );
291 //inlined IsectInfLinePlane( point dst, orig, dir, plane )
293 SGDfloat tmp = sgdScalarProductVec3 ( dir, plane ) ;
295 /* Is line parallel to plane? */
296 if ( sgdAbs ( tmp ) < FLT_EPSILON /*DBL_EPSILON*/ )
299 sgdScaleVec3 ( point, dir,
300 -( sgdScalarProductVec3 ( orig, plane ) + plane[3] )
303 sgdAddVec3 ( point, orig ) ;
304 } // end of inlined intersection routine
306 // short circut if this point is further away then a previous hit
307 tmp_dist = sgdScalarProductVec3(point,point);
308 if( tmp_dist > test_dist )
311 if( sgdPointInTriangle( point, tri ) ) {
312 // transform point into passed coordinate frame
313 sgdXformPnt3( point, point, m );
314 add(leaf,n,point,plane);
315 test_dist = tmp_dist;
325 int FGHitList::IntersectTriLeaf( ssgLeaf *leaf, sgdMat4 m,
326 sgdVec3 orig, sgdVec3 dir )
330 // number of hits but there could be more that
331 // were not found because of short circut switch !
332 // so you may want to use the unspecialized IntersectLeaf()
335 int ntri = leaf->getNumTriangles();
336 for ( int n = 0; n < ntri; ++n ) {
338 sgdSetVec3( tri[0], leaf->getVertex( short(n*3) ) );
339 sgdSetVec3( tri[1], leaf->getVertex( short(n*3+1) ) );
340 sgdSetVec3( tri[2], leaf->getVertex( short(n*3+2) ) );
342 // avoid division by zero when two points are the same
343 if( isZeroAreaTri( tri ) )
347 sgdMakePlane( plane, tri[0], tri[1], tri[2] );
350 //inlined IsectInfLinePlane( point dst, orig, dir, plane )
352 SGDfloat tmp = sgdScalarProductVec3 ( dir, plane ) ;
354 /* Is line parallel to plane? */
355 if ( sgdAbs ( tmp ) < FLT_EPSILON /*DBL_EPSILON*/ )
358 sgdScaleVec3 ( point, dir,
359 -( sgdScalarProductVec3 ( orig, plane ) + plane[3] )
362 sgdAddVec3 ( point, orig ) ;
363 } // end of inlined intersection routine
365 // short circut if this point is further away then a previous hit
366 tmp_dist = sgdScalarProductVec3(point,point);
367 if( tmp_dist > test_dist )
370 if( sgdPointInTriangle( point, tri ) ) {
371 // transform point into passed coordinate frame
372 sgdXformPnt3( point, point, m );
373 add(leaf,n,point,plane);
374 test_dist = tmp_dist;
381 //============================
383 int FGHitList::IntersectStripLeaf( ssgLeaf *leaf, sgdMat4 m,
384 sgdVec3 orig, sgdVec3 dir )
388 // number of hits but there could be more that
389 // were not found because of short circut switch !
390 // so you may want to use the unspecialized IntersectLeaf()
393 int ntri = leaf->getNumTriangles();
394 for ( int n = 0; n < ntri; ++n ) {
398 sgdSetVec3( tri[0], leaf->getVertex( short(0) ) );
399 sgdSetVec3( tri[1], leaf->getVertex( short(1) ) );
400 sgdSetVec3( tri[2], leaf->getVertex( short(2) ) );
403 sgdSetVec3( tri[0], leaf->getVertex( short(n+2) ) );
404 sgdCopyVec3( tri[1], tri[2] );
405 sgdCopyVec3( tri[2], tri[1] );
408 sgdCopyVec3( tri[0], tri[1] );
409 sgdCopyVec3( tri[1], tri[2] );
410 sgdSetVec3( tri[2], leaf->getVertex( short(n+2) ) );
414 if( isZeroAreaTri( tri ) )
418 sgdMakePlane( plane, tri[0], tri[1], tri[2] );
422 //inlined IsectInfLinePlane( point dst, orig, dir, plane )
424 SGDfloat tmp = sgdScalarProductVec3 ( dir, plane ) ;
426 /* Is line parallel to plane? */
427 if ( sgdAbs ( tmp ) < FLT_EPSILON /*DBL_EPSILON*/ )
430 sgdScaleVec3 ( point, dir,
431 -( sgdScalarProductVec3 ( orig, plane ) + plane[3] )
434 sgdAddVec3 ( point, orig ) ;
435 } // end of inlined intersection routine
437 // short circut if this point is further away then a previous hit
438 tmp_dist = sgdScalarProductVec3(point,point);
439 if( tmp_dist > test_dist )
442 if( sgdPointInTriangle( point, tri ) ) {
443 // transform point into passed coordinate frame
444 sgdXformPnt3( point, point, m );
445 add(leaf,n,point,plane);
446 test_dist = tmp_dist;
453 // ======================
455 int FGHitList::IntersectQuadsLeaf( ssgLeaf *leaf, sgdMat4 m,
456 sgdVec3 orig, sgdVec3 dir )
460 // number of hits but there could be more that
461 // were not found because of short circut switch !
462 // so you may want to use the unspecialized IntersectLeaf()
465 int ntri = leaf->getNumTriangles();
466 for ( int n = 0; n < ntri; ++n ) {
469 sgdSetVec3( tri[0], leaf->getVertex( short(n*2) ) );
470 sgdSetVec3( tri[1], leaf->getVertex( short(n*2+1) ) );
471 sgdSetVec3( tri[2], leaf->getVertex( short(n*2 + 2 - (n&1)*4) ) );
473 if( isZeroAreaTri( tri ) )
477 sgdMakePlane( plane, tri[0], tri[1], tri[2] );
481 //inlined IsectInfLinePlane( point dst, orig, dir, plane )
483 SGDfloat tmp = sgdScalarProductVec3 ( dir, plane ) ;
485 /* Is line parallel to plane? */
486 if ( sgdAbs ( tmp ) < FLT_EPSILON /*DBL_EPSILON*/ )
489 sgdScaleVec3 ( point, dir,
490 -( sgdScalarProductVec3 ( orig, plane ) + plane[3] )
493 sgdAddVec3 ( point, orig ) ;
494 } // end of inlined intersection routine
496 // short circut if this point is further away then a previous hit
497 tmp_dist = sgdScalarProductVec3(point,point);
498 if( tmp_dist > test_dist )
501 if( sgdPointInTriangle( point, tri ) ) {
502 // transform point into passed coordinate frame
503 sgdXformPnt3( point, point, m );
504 add(leaf,n,point,plane);
505 test_dist = tmp_dist;
514 // Need these because of mixed matrix types
515 static void sgMultMat4(sgdMat4 dst, sgdMat4 m1, sgMat4 m2)
517 for ( int j = 0 ; j < 4 ; j++ ) {
518 dst[0][j] = m2[0][0] * m1[0][j] +
519 m2[0][1] * m1[1][j] +
520 m2[0][2] * m1[2][j] +
521 m2[0][3] * m1[3][j] ;
523 dst[1][j] = m2[1][0] * m1[0][j] +
524 m2[1][1] * m1[1][j] +
525 m2[1][2] * m1[2][j] +
526 m2[1][3] * m1[3][j] ;
528 dst[2][j] = m2[2][0] * m1[0][j] +
529 m2[2][1] * m1[1][j] +
530 m2[2][2] * m1[2][j] +
531 m2[2][3] * m1[3][j] ;
533 dst[3][j] = m2[3][0] * m1[0][j] +
534 m2[3][1] * m1[1][j] +
535 m2[3][2] * m1[2][j] +
536 m2[3][3] * m1[3][j] ;
544 void FGHitList::IntersectBranch( ssgBranch *branch, sgdMat4 m,
545 sgdVec3 orig, sgdVec3 dir )
547 /* the lookat vector and matrix in branch's coordinate frame
548 * but we won't determine these unless needed,
549 * This 'lazy evaluation' is a result of profiling data */
550 sgdVec3 orig_leaf, dir_leaf;
554 // 'lazy evaluation' flag
557 for ( ssgEntity *kid = branch->getKid( 0 );
559 kid = branch->getNextKid() )
561 if ( kid->getTraversalMask() & SSGTRAV_HOT ) {
564 kid->getBSphere()->getCenter()[0],
565 kid->getBSphere()->getCenter()[1],
566 kid->getBSphere()->getCenter()[2] );
567 sgdXformPnt3( center, m ) ;
569 // sgdClosestPointToLineDistSquared( center, orig, dir )
570 // inlined here because because of profiling results
572 sgdSubVec3(u, center, orig);
573 sgdScaleVec3( u1, dir, sgdScalarProductVec3(u,dir)
574 / sgdScalarProductVec3(dir,dir) );
575 sgdSubVec3(v, u, u1);
577 // doubles because of possible overflow
578 #define SQUARE(x) (x*x)
579 if( sgdScalarProductVec3(v, v)
580 < SQUARE( double(kid->getBSphere()->getRadius()) ) )
582 // possible intersections
583 if ( kid->isAKindOf ( ssgTypeBranch() ) ) {
585 if ( kid->isA( ssgTypeTransform() ) )
588 ((ssgTransform *)kid)->getTransform( fxform );
589 sgMultMat4(m_new, m, fxform);
591 sgdCopyMat4(m_new, m);
593 IntersectBranch( (ssgBranch *)kid, m_new, orig, dir );
594 } else if ( kid->isAKindOf( ssgTypeLeaf() ) ) {
597 sgdTransposeNegateMat4( m_leaf, m);
598 sgdXformPnt3( orig_leaf, orig, m_leaf );
599 sgdXformPnt3( dir_leaf, dir, m_leaf );
603 GLenum primType = ((ssgLeaf *)kid)->getPrimitiveType();
605 switch ( primType ) {
607 case GL_TRIANGLE_FAN :
608 IntersectPolyOrFanLeaf( (ssgLeaf *)kid, m,
609 orig_leaf, dir_leaf );
612 IntersectTriLeaf( (ssgLeaf *)kid, m,
613 orig_leaf, dir_leaf );
615 case GL_TRIANGLE_STRIP :
617 IntersectStripLeaf( (ssgLeaf *)kid, m,
618 orig_leaf, dir_leaf );
621 IntersectQuadsLeaf( (ssgLeaf *)kid, m,
622 orig_leaf, dir_leaf );
625 IntersectLeaf( (ssgLeaf *)kid, m,
626 orig_leaf, dir_leaf );
630 // end of the line for this branch
633 // branch requested not to be traversed
638 void ssgGetEntityTransform(ssgEntity *entity, sgMat4 m )
641 Walk backwards up the tree, transforming the
642 vertex by all the matrices along the way.
644 Upwards recursion hurts my head.
650 If this node has a parent - get the composite
651 matrix for the parent.
654 if ( entity->getNumParents() > 0 )
655 ssgGetEntityTransform ( entity->getParent(0), mat ) ;
657 sgMakeIdentMat4 ( mat ) ;
660 If this node has a transform - then concatenate it.
663 if ( entity -> isAKindOf ( ssgTypeTransform () ) ) {
665 ((ssgTransform *) entity) -> getTransform ( this_mat ) ;
666 sgPostMultMat4 ( mat, this_mat ) ;
669 sgCopyMat4 ( m, mat ) ;
672 // previously used method
673 // This expects the inital m to be the identity transform
674 void ssgGetEntityTransform(ssgEntity *branch, sgMat4 m )
676 for ( ssgEntity *parent = branch->getParent(0);
678 parent = parent->getParent(0) )
680 // recurse till we are at the scene root
681 // then just unwinding the stack should
682 // give us our cumulative transform :-) NHV
683 ssgGetEntityTransform( parent, m );
684 if ( parent->isA( ssgTypeTransform() ) ) {
686 ((ssgTransform *)parent)->getTransform( xform );
687 sgPreMultMat4( m, xform );
693 // return the passed entitity's bsphere's center point radius and
694 // fully formed current model matrix for entity
695 void ssgGetCurrentBSphere( ssgEntity *entity, sgVec3 center,
696 float *radius, sgMat4 m )
698 sgSphere *bsphere = entity->getBSphere();
699 *radius = (double)bsphere->getRadius();
700 sgCopyVec3( center, bsphere->getCenter() );
701 sgMakeIdentMat4 ( m ) ;
702 ssgGetEntityTransform( entity, m );
706 void FGHitList::Intersect( ssgBranch *scene, sgdVec3 orig, sgdVec3 dir ) {
709 sgdMakeIdentMat4 ( m ) ;
710 IntersectBranch( scene, m, orig, dir);
714 static void CurrentNormalInLocalPlane(sgVec3 dst, sgVec3 src) {
716 sgSetVec3(tmp, src[0], src[1], src[2] );
718 sgTransposeNegateMat4 ( TMP, globals->get_current_view()->get_UP() ) ;
719 sgXformVec3(tmp, tmp, TMP);
720 sgSetVec3(dst, tmp[2], tmp[1], tmp[0] );
724 // a temporary hack until we get everything rewritten with sgdVec3
725 static inline Point3D operator + (const Point3D& a, const sgdVec3 b)
727 return Point3D(a.x()+b[0], a.y()+b[1], a.z()+b[2]);
732 * This method is faster
735 void FGHitList::Intersect( ssgBranch *scene, sgdMat4 m, sgdVec3 orig,
739 IntersectBranch( scene, m, orig, dir);
743 // Determine scenery altitude via ssg.
744 // returned results are in meters
745 bool fgCurrentElev( sgdVec3 abs_view_pos, sgdVec3 scenery_center,
747 double *terrain_elev, double *radius, double *normal)
750 sgdSubVec3( view_pos, abs_view_pos, scenery_center );
753 sgdCopyVec3(orig, view_pos );
754 sgdCopyVec3(dir, abs_view_pos );
755 // sgdNormalizeVec3(dir);
757 // !! why is terrain not globals->get_terrain()
758 hit_list->Intersect( terrain_branch, orig, dir );
762 double result = -9999;
763 Point3D sc(scenery_center[0], scenery_center[1], scenery_center[2]) ;
765 // cout << "hits = ";
766 int hitcount = hit_list->num_hits();
767 for ( int i = 0; i < hitcount; ++i ) {
768 geoc = sgCartToPolar3d( sc + hit_list->get_point(i) );
769 double lat_geod, alt, sea_level_r;
770 sgGeocToGeod(geoc.lat(), geoc.radius(), &lat_geod,
772 // cout << alt << " ";
773 if ( alt > result && alt < 10000 ) {
780 if ( result > -9000 ) {
781 *terrain_elev = result;
782 *radius = geoc.radius();
784 sgSetVec3(tmp, hit_list->get_normal(this_hit));
785 // cout << "cur_normal: " << tmp[0] << " " << tmp[1] << " "
786 // << tmp[2] << endl;
787 /* ssgState *IntersectedLeafState =
788 ((ssgLeaf*)hit_list->get_entity(this_hit))->getState(); */
789 CurrentNormalInLocalPlane(tmp, tmp);
790 sgdSetVec3( normal, tmp );
791 // cout << "NED: " << tmp[0] << " " << tmp[1] << " " << tmp[2] << endl;
794 SG_LOG( SG_TERRAIN, SG_INFO, "no terrain intersection" );
796 float *up = globals->get_current_view()->get_world_up();
797 sgdSetVec3(normal, up[0], up[1], up[2]);
803 // Determine scenery altitude via ssg.
804 // returned results are in meters
805 bool fgCurrentElev( sgdVec3 abs_view_pos, sgdVec3 scenery_center,
806 ssgTransform *terra_transform,
808 double *terrain_elev, double *radius, double *normal)
810 #ifndef FAST_HITLIST__TEST
811 return fgCurrentElev( abs_view_pos, scenery_center, hit_list,
812 terrain_elev,radius,normal);
815 sgdSubVec3( view_pos, abs_view_pos, scenery_center );
818 sgdCopyVec3(orig, view_pos );
820 sgdCopyVec3(dir, abs_view_pos );
821 sgdNormalizeVec3(dir);
824 sgMakeIdentMat4 ( fxform ) ;
825 ssgGetEntityTransform( terra_transform, fxform );
828 sgdSetMat4(xform,fxform);
829 hit_list->Intersect( terra_transform, xform, orig, dir );
833 double result = -9999;
834 Point3D sc(scenery_center[0], scenery_center[1], scenery_center[2]) ;
836 int hitcount = hit_list->num_hits();
837 for ( int i = 0; i < hitcount; ++i ) {
838 geoc = sgCartToPolar3d( sc + hit_list->get_point(i) );
839 double lat_geod, alt, sea_level_r;
840 sgGeocToGeod(geoc.lat(), geoc.radius(), &lat_geod,
842 if ( alt > result && alt < 20000 ) {
848 if ( result > -9000 ) {
849 *terrain_elev = result;
850 *radius = geoc.radius();
852 sgSetVec3(tmp, hit_list->get_normal(this_hit));
853 // cout << "cur_normal: " << tmp[0] << " " << tmp[1] << " "
854 // << tmp[2] << endl;
855 /* ssgState *IntersectedLeafState =
856 ((ssgLeaf*)hit_list->get_entity(this_hit))->getState(); */
857 CurrentNormalInLocalPlane(tmp, tmp);
858 sgdSetVec3( normal, tmp );
859 // cout << "NED: " << tmp[0] << " " << tmp[1] << " " << tmp[2] << endl;
862 SG_LOG( SG_TERRAIN, SG_DEBUG, "DOING FULL TERRAIN INTERSECTION" );
863 return fgCurrentElev( abs_view_pos, scenery_center, hit_list,
864 terrain_elev,radius,normal);
866 #endif // !FAST_HITLIST__TEST