2 // Height Over Terrain and Assosciated Routines for FlightGear based Scenery
3 // Written by Norman Vine, started 2000.
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>
18 #include <Main/globals.hxx>
19 #include <Main/viewer.hxx>
20 #include <Scenery/scenery.hxx>
22 #include "hitlist.hxx"
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 );
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 )
36 SGDfloat tmp = sgdScalarProductVec3 ( l_vec, plane ) ;
38 /* Is line parallel to plane? */
40 if ( fabs ( tmp ) < FLT_EPSILON )
43 sgdScaleVec3 ( dst, l_vec, -( sgdScalarProductVec3 ( l_org, plane )
44 + plane[3] ) / tmp ) ;
45 sgdAddVec3 ( dst, l_org ) ;
50 // ======================
53 * Given a point and a triangle lying on the same plane
54 * check to see if the point is inside the triangle
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] )
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) )
69 SG_MIN_MAX3 ( min, max, tri[0][1], tri[1][1], tri[2][1] );
70 if( (point[1] < min) || (point[1] > max) )
74 SG_MIN_MAX3 ( min, max, tri[0][2], tri[1][2], tri[2][2] );
75 if( (point[2] < min) || (point[2] > max) )
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
92 } else if ( fabs(min_dim-dif[1]) <= DBL_EPSILON ) {
93 // y is the smallest dimension
102 } else if ( fabs(min_dim-dif[2]) <= DBL_EPSILON ) {
103 // z is the smallest dimension
113 // all dimensions are really small so lets call it close
114 // enough and return a successful match
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");
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");
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");
148 // ======================
150 inline static int isZeroAreaTri( sgdVec3 tri[3] )
152 return( sgdEqualVec3(tri[0], tri[1]) ||
153 sgdEqualVec3(tri[1], tri[2]) ||
154 sgdEqualVec3(tri[2], tri[0]) );
157 FGHitList::FGHitList() :
158 last(NULL), test_dist(DBL_MAX)
162 FGHitList::~FGHitList() {}
166 Find the intersection of an infinite line with a leaf
167 the line being defined by a point and direction.
171 ssgLeaf pointer -- leaf
172 qualified matrix -- m
174 line direction -- dir
176 result -- intersection point
177 normal -- intersected tri's normal
180 true if intersection found
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
190 int FGHitList::IntersectLeaf( ssgLeaf *leaf, sgdMat4 m,
191 sgdVec3 orig, sgdVec3 dir )
196 for ( ; i < leaf->getNumTriangles(); ++i ) {
198 leaf->getTriangle( i, &i1, &i2, &i3 );
201 sgdSetVec3( tri[0], leaf->getVertex( i1 ) );
202 sgdSetVec3( tri[1], leaf->getVertex( i2 ) );
203 sgdSetVec3( tri[2], leaf->getVertex( i3 ) );
205 if( isZeroAreaTri( tri ) )
209 sgdMakePlane( plane, tri[0], tri[1], tri[2] );
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);
225 // ======================
227 int FGHitList::IntersectLeaf( ssgLeaf *leaf, sgdMat4 m,
228 sgdVec3 orig, sgdVec3 dir,
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()
238 int ntri = leaf->getNumTriangles();
239 for ( n = 0; n < ntri; ++n )
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" ); */
252 sgdSetVec3( tri[0], leaf->getVertex( short(0) ) );
253 sgdSetVec3( tri[1], leaf->getVertex( short(1) ) );
254 sgdSetVec3( tri[2], leaf->getVertex( short(2) ) );
256 sgdCopyVec3( tri[1], tri[2] );
257 sgdSetVec3( tri[2], leaf->getVertex( short(n+2) ) );
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) ) );
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" ); */
274 sgdSetVec3( tri[0], leaf->getVertex( short(0) ) );
275 sgdSetVec3( tri[1], leaf->getVertex( short(1) ) );
276 sgdSetVec3( tri[2], leaf->getVertex( short(2) ) );
279 sgdCopyVec3( tri[0], tri[2] );
280 sgdSetVec3( tri[2], leaf->getVertex( short(n+2) ) );
282 sgdCopyVec3( tri[2], tri[1] );
283 sgdSetVec3( tri[2], leaf->getVertex( short(n+2) ) );
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) ) );
295 SG_LOG( SG_TERRAIN, SG_ALERT,
296 "WARNING: not-handled structure: " << primType );
297 return IntersectLeaf( leaf, m, orig, dir);
300 if( isZeroAreaTri( tri ) )
304 sgdMakePlane( plane, tri[0], tri[1], tri[2] );
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*/ )
315 // find parametric point
316 sgdScaleVec3 ( point, dir,
317 -( sgdScalarProductVec3 ( orig, plane ) + plane[3] )
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 )
325 // place parametric point in world
326 sgdAddVec3 ( point, orig ) ;
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;
340 // ======================
341 inline static bool IN_RANGE( sgdVec3 v, double radius ) {
342 return ( sgdScalarProductVec3(v, v) < (radius*radius) );
345 // ======================
346 void FGHitList::IntersectBranch( ssgBranch *branch, sgdMat4 m,
347 sgdVec3 orig, sgdVec3 dir )
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;
355 // 'lazy evaluation' flag
358 for ( ssgEntity *kid = branch->getKid( 0 );
360 kid = branch->getNextKid() )
362 if ( kid->getTraversalMask() & SSGTRAV_HOT
363 && !kid->getBSphere()->isEmpty() )
367 kid->getBSphere()->getCenter()[0],
368 kid->getBSphere()->getCenter()[1],
369 kid->getBSphere()->getCenter()[2] );
370 sgdXformPnt3( center, m ) ;
372 // sgdClosestPointToLineDistSquared( center, orig, dir )
373 // inlined here because because of profiling results
375 sgdSubVec3(u, center, orig);
376 sgdScaleVec3( u1, dir, sgdScalarProductVec3(u,dir)
377 / sgdScalarProductVec3(dir,dir) );
378 sgdSubVec3(v, u, u1);
380 // double because of possible overflow
381 if ( IN_RANGE( v, double(kid->getBSphere()->getRadius()) ) )
383 if ( kid->isAKindOf ( ssgTypeBranch() ) )
386 if ( kid->isA( ssgTypeTransform() ) )
389 ((ssgTransform *)kid)->getTransform( fxform );
390 sgMultMat4(m_new, m, fxform);
392 sgdCopyMat4(m_new, m);
394 IntersectBranch( (ssgBranch *)kid, m_new, orig, dir );
396 else if ( kid->isAKindOf( ssgTypeLeaf() ) )
399 sgdTransposeNegateMat4( m_leaf, m );
400 sgdXformPnt3( orig_leaf, orig, m_leaf );
401 sgdXformPnt3( dir_leaf, dir, m_leaf );
404 GLenum primType = ((ssgLeaf *)kid)->getPrimitiveType();
405 IntersectLeaf( (ssgLeaf *)kid, m, orig_leaf, dir_leaf, primType );
408 } // branch not requested to be traversed
414 // ======================
415 // a temporary hack until we get everything rewritten with sgdVec3
416 static inline Point3D operator + (const Point3D& a, const sgdVec3 b)
418 return Point3D(a.x()+b[0], a.y()+b[1], a.z()+b[2]);
422 // ======================
423 void FGHitList::Intersect( ssgBranch *scene, sgdVec3 orig, sgdVec3 dir ) {
426 sgdMakeIdentMat4 ( m ) ;
427 IntersectBranch( scene, m, orig, dir );
430 // ======================
431 void FGHitList::Intersect( ssgBranch *scene, sgdMat4 m, sgdVec3 orig, sgdVec3 dir )
434 IntersectBranch( scene, m, orig, dir );
437 // ======================
438 // Need these because of mixed matrix types
439 static void sgMultMat4(sgdMat4 dst, sgdMat4 m1, sgMat4 m2)
441 for ( int j = 0 ; j < 4 ; j++ )
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] ;
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] ;
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] ;
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] ;
465 // ======================
466 static void ssgGetEntityTransform(ssgEntity *entity, sgMat4 m )
469 Walk backwards up the tree, transforming the
470 vertex by all the matrices along the way.
472 Upwards recursion hurts my head.
478 If this node has a parent - get the composite
479 matrix for the parent.
481 if ( entity->getNumParents() > 0 )
482 ssgGetEntityTransform ( entity->getParent(0), mat ) ;
484 sgMakeIdentMat4 ( mat ) ;
487 If this node has a transform - then concatenate it.
489 if ( entity -> isAKindOf ( ssgTypeTransform () ) ) {
491 ((ssgTransform *) entity) -> getTransform ( this_mat ) ;
492 sgPostMultMat4 ( mat, this_mat ) ;
495 sgCopyMat4 ( m, mat ) ;
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 )
503 sgSphere *bsphere = entity->getBSphere();
504 *radius = (double)bsphere->getRadius();
505 sgCopyVec3( center, bsphere->getCenter() );
506 sgMakeIdentMat4 ( m ) ;
507 ssgGetEntityTransform( entity, m );
511 // ======================
512 // Determine scenery altitude via ssg.
513 // returned results are in meters
514 bool fgCurrentElev( sgdVec3 abs_view_pos, sgdVec3 scenery_center,
516 double *terrain_elev, double *radius, double *normal)
519 sgdSubVec3( view_pos, abs_view_pos, scenery_center );
522 sgdCopyVec3(orig, view_pos );
523 sgdCopyVec3(dir, abs_view_pos );
525 hit_list->Intersect( globals->get_scenery()->get_terrain_branch(),
530 double result = -9999;
531 Point3D sc(scenery_center[0], scenery_center[1], scenery_center[2]) ;
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,
540 // cout << "hit " << i << " lon = " << geoc.lon() << " lat = "
541 // << lat_geod << " alt = " << alt << endl;
542 if ( alt > result && alt < 10000 ) {
549 if ( result > -9000 ) {
550 *terrain_elev = result;
551 *radius = geoc.radius();
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(); */
562 SG_LOG( SG_TERRAIN, SG_INFO, "no terrain intersection" );
564 float *up = globals->get_current_view()->get_world_up();
565 sgdSetVec3(normal, up[0], up[1], up[2]);
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,
577 double *terrain_elev, double *radius, double *normal)
580 sgdSubVec3( view_pos, abs_view_pos, scenery_center );
583 sgdCopyVec3(orig, view_pos );
585 sgdCopyVec3(dir, abs_view_pos );
586 sgdNormalizeVec3(dir);
589 sgMakeIdentMat4 ( fxform ) ;
590 ssgGetEntityTransform( terra_transform, fxform );
593 sgdSetMat4(xform,fxform);
594 hit_list->Intersect( terra_transform, xform, orig, dir );
598 double result = -9999;
599 Point3D sc(scenery_center[0], scenery_center[1], scenery_center[2]) ;
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,
607 if ( alt > result && alt < 20000 ) {
613 if ( result > -9000 ) {
614 *terrain_elev = result;
615 *radius = geoc.radius();
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(); */
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);