]> git.mxchange.org Git - flightgear.git/blob - src/FDM/groundcache.cxx
Modified Files:
[flightgear.git] / src / FDM / groundcache.cxx
1 // groundcache.cxx -- carries a small subset of the scenegraph near the vehicle
2 //
3 // Written by Mathias Froehlich, started Nov 2004.
4 //
5 // Copyright (C) 2004  Mathias Froehlich - Mathias.Froehlich@web.de
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26
27 #include <float.h>
28
29 #include <plib/sg.h>
30 #include <osg/CullFace>
31 #include <osg/Drawable>
32 #include <osg/Geode>
33 #include <osg/Geometry>
34 #include <osg/TriangleFunctor>
35
36 #include <simgear/sg_inlines.h>
37 #include <simgear/constants.h>
38 #include <simgear/debug/logstream.hxx>
39 #include <simgear/math/sg_geodesy.hxx>
40 #include <simgear/scene/material/mat.hxx>
41 #include <simgear/scene/material/matlib.hxx>
42 #include <simgear/scene/util/SGNodeMasks.hxx>
43
44 #include <Main/globals.hxx>
45 #include <Scenery/scenery.hxx>
46 #include <Scenery/tilemgr.hxx>
47 #include <AIModel/AICarrier.hxx>
48
49 #include "flight.hxx"
50 #include "groundcache.hxx"
51
52 static inline bool
53 fgdPointInTriangle( const SGVec3d& point, const SGVec3d tri[3] )
54 {
55     SGVec3d dif;
56
57     // Some tolerance in meters we accept a point to be outside of the triangle
58     // and still return that it is inside.
59     SGDfloat eps = 1e-2;
60     SGDfloat min, max;
61     // punt if outside bouding cube
62     SG_MIN_MAX3 ( min, max, tri[0][0], tri[1][0], tri[2][0] );
63     if( (point[0] < min - eps) || (point[0] > max + eps) )
64         return false;
65     dif[0] = max - min;
66
67     SG_MIN_MAX3 ( min, max, tri[0][1], tri[1][1], tri[2][1] );
68     if( (point[1] < min - eps) || (point[1] > max + eps) )
69         return false;
70     dif[1] = max - min;
71
72     SG_MIN_MAX3 ( min, max, tri[0][2], tri[1][2], tri[2][2] );
73     if( (point[2] < min - eps) || (point[2] > max + eps) )
74         return false;
75     dif[2] = max - min;
76
77     // drop the smallest dimension so we only have to work in 2d.
78     SGDfloat min_dim = SG_MIN3 (dif[0], dif[1], dif[2]);
79     SGDfloat x1, y1, x2, y2, x3, y3, rx, ry;
80     if ( fabs(min_dim-dif[0]) <= DBL_EPSILON ) {
81         // x is the smallest dimension
82         x1 = point[1];
83         y1 = point[2];
84         x2 = tri[0][1];
85         y2 = tri[0][2];
86         x3 = tri[1][1];
87         y3 = tri[1][2];
88         rx = tri[2][1];
89         ry = tri[2][2];
90     } else if ( fabs(min_dim-dif[1]) <= DBL_EPSILON ) {
91         // y is the smallest dimension
92         x1 = point[0];
93         y1 = point[2];
94         x2 = tri[0][0];
95         y2 = tri[0][2];
96         x3 = tri[1][0];
97         y3 = tri[1][2];
98         rx = tri[2][0];
99         ry = tri[2][2];
100     } else if ( fabs(min_dim-dif[2]) <= DBL_EPSILON ) {
101         // z is the smallest dimension
102         x1 = point[0];
103         y1 = point[1];
104         x2 = tri[0][0];
105         y2 = tri[0][1];
106         x3 = tri[1][0];
107         y3 = tri[1][1];
108         rx = tri[2][0];
109         ry = tri[2][1];
110     } else {
111         // all dimensions are really small so lets call it close
112         // enough and return a successful match
113         return true;
114     }
115
116     // check if intersection point is on the same side of p1 <-> p2 as p3
117     SGDfloat tmp = (y2 - y3);
118     SGDfloat tmpn = (x2 - x3);
119     int side1 = SG_SIGN (tmp * (rx - x3) + (y3 - ry) * tmpn);
120     int side2 = SG_SIGN (tmp * (x1 - x3) + (y3 - y1) * tmpn
121                          + side1 * eps * fabs(tmpn));
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);
129     tmpn = (x3 - rx);
130     side1 = SG_SIGN (tmp * (x2 - rx) + (ry - y2) * tmpn);
131     side2 = SG_SIGN (tmp * (x1 - rx) + (ry - y1) * tmpn
132                      + side1 * eps * fabs(tmpn));
133     if ( side1 != side2 ) {
134         // printf("failed side 2 check\n");
135         return false;
136     }
137
138     // check if intersection point is on correct side of p1 <-> p3 as p2
139     tmp = (y2 - ry);
140     tmpn = (x2 - rx);
141     side1 = SG_SIGN (tmp * (x3 - rx) + (ry - y3) * tmpn);
142     side2 = SG_SIGN (tmp * (x1 - rx) + (ry - y1) * tmpn
143                      + side1 * eps * fabs(tmpn));
144     if ( side1 != side2 ) {
145         // printf("failed side 3  check\n");
146         return false;
147     }
148
149     return true;
150 }
151
152 // Test if the line given by the point on the line pt_on_line and the
153 // line direction dir intersects the sphere sp.
154 // Adapted from plib.
155 static inline bool
156 fgdIsectSphereInfLine(const SGVec3d& sphereCenter, double radius,
157                       const SGVec3d& pt_on_line, const SGVec3d& dir)
158 {
159   SGVec3d r = sphereCenter - pt_on_line;
160   double projectedDistance = dot(r, dir);
161   double dist = dot(r, r) - projectedDistance * projectedDistance;
162   return dist < radius*radius;
163 }
164
165 template<typename T>
166 class SGExtendedTriangleFunctor : public osg::TriangleFunctor<T> {
167 public:
168   // Ok, to be complete we should also implement the indexed variants
169   // For now this one appears to be enough ...
170   void drawArrays(GLenum mode, GLint first, GLsizei count)
171   {
172     if (_vertexArrayPtr==0 || count==0) return;
173
174     const osg::Vec3* vlast;
175     const osg::Vec3* vptr;
176     switch(mode) {
177     case(GL_LINES):
178       vlast = &_vertexArrayPtr[first+count];
179       for(vptr=&_vertexArrayPtr[first];vptr<vlast;vptr+=2)
180         this->operator()(*(vptr),*(vptr+1),_treatVertexDataAsTemporary);
181       break;
182     case(GL_LINE_STRIP):
183       vlast = &_vertexArrayPtr[first+count-1];
184       for(vptr=&_vertexArrayPtr[first];vptr<vlast;++vptr)
185         this->operator()(*(vptr),*(vptr+1),_treatVertexDataAsTemporary);
186       break;
187     case(GL_LINE_LOOP):
188       vlast = &_vertexArrayPtr[first+count-1];
189       for(vptr=&_vertexArrayPtr[first];vptr<vlast;++vptr)
190         this->operator()(*(vptr),*(vptr+1),_treatVertexDataAsTemporary);
191       this->operator()(_vertexArrayPtr[first+count-1],
192                        _vertexArrayPtr[first],_treatVertexDataAsTemporary);
193       break;
194     default:
195       osg::TriangleFunctor<T>::drawArrays(mode, first, count);
196       break;
197     }
198   }
199 protected:
200   using osg::TriangleFunctor<T>::_vertexArrayPtr;
201   using osg::TriangleFunctor<T>::_treatVertexDataAsTemporary;
202 };
203
204 class GroundCacheFillVisitor : public osg::NodeVisitor {
205 public:
206   
207   /// class to just redirect triangles to the GroundCacheFillVisitor
208   class GroundCacheFill {
209   public:
210     void setGroundCacheFillVisitor(GroundCacheFillVisitor* gcfv)
211     { mGroundCacheFillVisitor = gcfv; }
212     
213     void operator () (const osg::Vec3& v1, const osg::Vec3& v2,
214                       const osg::Vec3& v3, bool)
215     { mGroundCacheFillVisitor->addTriangle(v1, v2, v3); }
216
217     void operator () (const osg::Vec3& v1, const osg::Vec3& v2, bool)
218     { mGroundCacheFillVisitor->addLine(v1, v2); }
219     
220   private:
221     GroundCacheFillVisitor* mGroundCacheFillVisitor;
222   };
223
224
225   GroundCacheFillVisitor(FGGroundCache* groundCache,
226                          const SGVec3d& down, 
227                          const SGVec3d& cacheReference,
228                          double cacheRadius,
229                          double wireCacheRadius) :
230     osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN),
231     mGroundCache(groundCache)
232   {
233     setTraversalMask(SG_NODEMASK_TERRAIN_BIT);
234     mDown = down;
235     sphIsec = true;
236     mBackfaceCulling = false;
237     mCacheReference = cacheReference;
238     mCacheRadius = cacheRadius;
239     mWireCacheRadius = wireCacheRadius;
240
241     mTriangleFunctor.setGroundCacheFillVisitor(this);
242
243     mGroundProperty.wire_id = -1;
244     mGroundProperty.vel = SGVec3d(0, 0, 0);
245     mGroundProperty.rot = SGVec3d(0, 0, 0);
246     mGroundProperty.pivot = SGVec3d(0, 0, 0);
247   }
248
249   void updateCullMode(osg::StateSet* stateSet)
250   {
251     if (!stateSet)
252       return;
253
254     osg::StateAttribute* stateAttribute;
255     stateAttribute = stateSet->getAttribute(osg::StateAttribute::CULLFACE);
256     if (!stateAttribute)
257       return;
258     osg::CullFace* cullFace = static_cast<osg::CullFace*>(stateAttribute);
259     mBackfaceCulling = cullFace->getMode() == osg::CullFace::BACK;
260   }
261
262   bool enterBoundingSphere(const osg::BoundingSphere& bs)
263   {
264     if (!bs.valid())
265       return false;
266
267     SGVec3d cntr(osg::Vec3d(bs.center())*mLocalToGlobal);
268     double rc = bs.radius() + mCacheRadius;
269     // Ok, this node might intersect the cache. Visit it in depth.
270     double centerDist2 = distSqr(mCacheReference, cntr);
271     if (centerDist2 < rc*rc) {
272       sphIsec = true;
273     } else {
274       // Check if the down direction touches the bounding sphere of the node
275       // if so, do at least croase agl computations.
276       // Ther other thing is that we must check if we are in range of
277       // cats or wires
278       double rw = bs.radius() + mWireCacheRadius;
279       if (rw*rw < centerDist2 &&
280           !fgdIsectSphereInfLine(cntr, bs.radius(), mCacheReference, mDown))
281         return false;
282       sphIsec = false;
283     }
284
285     return true;
286   }
287
288   bool enterNode(osg::Node& node)
289   {
290     if (!enterBoundingSphere(node.getBound()))
291       return false;
292
293     updateCullMode(node.getStateSet());
294
295     FGGroundCache::GroundProperty& gp = mGroundProperty;
296     // get some material information for use in the gear model
297     gp.material = globals->get_matlib()->findMaterial(&node);
298     if (gp.material) {
299       gp.type = gp.material->get_solid() ? FGInterface::Solid : FGInterface::Water;
300       return true;
301     }
302     osg::Referenced* base = node.getUserData();
303     if (!base)
304       return true;
305     FGAICarrierHardware *ud =
306       dynamic_cast<FGAICarrierHardware*>(base);
307     if (!ud)
308       return true;
309
310     switch (ud->type) {
311     case FGAICarrierHardware::Wire:
312       gp.type = FGInterface::Wire;
313       gp.wire_id = ud->id;
314       break;
315     case FGAICarrierHardware::Catapult:
316       gp.type = FGInterface::Catapult;
317       break;
318     default:
319       gp.type = FGInterface::Solid;
320       break;
321     }
322     // Copy the velocity from the carrier class.
323     ud->carrier->getVelocityWrtEarth( gp.vel, gp.rot, gp.pivot );
324   
325     return true;
326   }
327
328   void fillWith(osg::Drawable* drawable)
329   {
330     bool oldSphIsec = sphIsec;
331     if (!enterBoundingSphere(drawable->getBound()))
332       return;
333
334     bool oldBackfaceCulling = mBackfaceCulling;
335     updateCullMode(drawable->getStateSet());
336
337     drawable->accept(mTriangleFunctor);
338
339     mBackfaceCulling = oldBackfaceCulling;
340     sphIsec = oldSphIsec;
341   }
342
343   virtual void apply(osg::Geode& geode)
344   {
345     bool oldBackfaceCulling = mBackfaceCulling;
346     bool oldSphIsec = sphIsec;
347     FGGroundCache::GroundProperty oldGp = mGroundProperty;
348     if (!enterNode(geode))
349       return;
350
351     for(unsigned i = 0; i < geode.getNumDrawables(); ++i)
352       fillWith(geode.getDrawable(i));
353     sphIsec = oldSphIsec;
354     mGroundProperty = oldGp;
355     mBackfaceCulling = oldBackfaceCulling;
356   }
357
358   virtual void apply(osg::Group& group)
359   {
360     bool oldBackfaceCulling = mBackfaceCulling;
361     bool oldSphIsec = sphIsec;
362     FGGroundCache::GroundProperty oldGp = mGroundProperty;
363     if (!enterNode(group))
364       return;
365     traverse(group);
366     sphIsec = oldSphIsec;
367     mBackfaceCulling = oldBackfaceCulling;
368     mGroundProperty = oldGp;
369   }
370
371   virtual void apply(osg::Transform& transform)
372   {
373     if (!enterNode(transform))
374       return;
375     bool oldBackfaceCulling = mBackfaceCulling;
376     bool oldSphIsec = sphIsec;
377     FGGroundCache::GroundProperty oldGp = mGroundProperty;
378     /// transform the caches center to local coords
379     osg::Matrix oldLocalToGlobal = mLocalToGlobal;
380     transform.computeLocalToWorldMatrix(mLocalToGlobal, this);
381
382     // walk the children
383     traverse(transform);
384
385     // Restore that one
386     mLocalToGlobal = oldLocalToGlobal;
387     sphIsec = oldSphIsec;
388     mBackfaceCulling = oldBackfaceCulling;
389     mGroundProperty = oldGp;
390   }
391   
392   void addTriangle(const osg::Vec3& v1, const osg::Vec3& v2,
393                    const osg::Vec3& v3)
394   {
395     FGGroundCache::Triangle t;
396     osg::Vec3d gv1 = osg::Vec3d(v1)*mLocalToGlobal;
397     osg::Vec3d gv2 = osg::Vec3d(v2)*mLocalToGlobal;
398     osg::Vec3d gv3 = osg::Vec3d(v3)*mLocalToGlobal;
399     for (unsigned i = 0; i < 3; ++i) {
400       t.vertices[0][i] = gv1[i];
401       t.vertices[1][i] = gv2[i];
402       t.vertices[2][i] = gv3[i];
403     }
404     // FIXME: can do better ...
405     t.boundCenter = (1.0/3)*(t.vertices[0] + t.vertices[1] + t.vertices[2]);
406     t.boundRadius = std::max(length(t.vertices[0] - t.boundCenter),
407                              length(t.vertices[1] - t.boundCenter));
408     t.boundRadius = std::max(t.boundRadius,
409                              length(t.vertices[2] - t.boundCenter));
410
411     sgdMakePlane(t.plane.sg(), t.vertices[0].sg(), t.vertices[1].sg(),
412                  t.vertices[2].sg());
413     double d = sgdScalarProductVec3(mDown.sg(), t.plane.sg());
414     if (d > 0) {
415       if (mBackfaceCulling) {
416         // Surface points downwards, ignore for altitude computations.
417         return;
418       } else
419         t.plane = -t.plane;
420     }
421
422     // Check if the sphere around the vehicle intersects the sphere
423     // around that triangle. If so, put that triangle into the cache.
424     if (sphIsec &&
425         distSqr(t.boundCenter, mCacheReference)
426         < (t.boundRadius + mCacheRadius)*(t.boundRadius + mCacheRadius) ) {
427       t.velocity = mGroundProperty.vel;
428       t.rotation = mGroundProperty.rot;
429       t.rotation_pivot = mGroundProperty.pivot - mGroundCache->cache_center;
430       t.type = mGroundProperty.type;
431       mGroundCache->triangles.push_back(t);
432     }
433     
434     // In case the cache is empty, we still provide agl computations.
435     // But then we use the old way of having a fixed elevation value for
436     // the whole lifetime of this cache.
437     if ( fgdIsectSphereInfLine(t.boundCenter, t.boundRadius,
438                                mCacheReference, mDown) ) {
439       SGVec3d isectpoint;
440       if ( sgdIsectInfLinePlane( isectpoint.sg(), mCacheReference.sg(),
441                                  mDown.sg(), t.plane.sg() ) &&
442            fgdPointInTriangle( isectpoint, t.vertices ) ) {
443         // Only accept the altitude if the intersection point is below the
444         // ground cache midpoint
445         if (0 < dot(isectpoint - mCacheReference, mDown)) {
446           mGroundCache->found_ground = true;
447           isectpoint += mGroundCache->cache_center;
448           double this_radius = length(isectpoint);
449           if (mGroundCache->ground_radius < this_radius)
450             mGroundCache->ground_radius = this_radius;
451         }
452       }
453     }
454   }
455  
456   void addLine(const osg::Vec3& v1, const osg::Vec3& v2)
457   {
458     SGVec3d gv1 = SGVec3d(osg::Vec3d(v1)*mLocalToGlobal);
459     SGVec3d gv2 = SGVec3d(osg::Vec3d(v2)*mLocalToGlobal);
460
461     SGVec3d boundCenter = 0.5*(gv1 + gv2);
462     double boundRadius = length(gv1 - boundCenter);
463
464     if (distSqr(boundCenter, mCacheReference)
465         < (boundRadius + mWireCacheRadius)*(boundRadius + mWireCacheRadius) ) {
466       if (mGroundProperty.type == FGInterface::Wire) {
467         FGGroundCache::Wire wire;
468         wire.ends[0] = gv1;
469         wire.ends[1] = gv2;
470         wire.velocity = mGroundProperty.vel;
471         wire.rotation = mGroundProperty.rot;
472         wire.rotation_pivot = mGroundProperty.pivot - mGroundCache->cache_center;
473         wire.wire_id = mGroundProperty.wire_id;
474
475         mGroundCache->wires.push_back(wire);
476       }
477       if (mGroundProperty.type == FGInterface::Catapult) {
478         FGGroundCache::Catapult cat;
479         // Trick to get the ends in the right order.
480         // Use the x axis in the original coordinate system. Choose the
481         // most negative x-axis as the one pointing forward
482         if (v1[0] < v2[0]) {
483           cat.start = gv1;
484           cat.end = gv2;
485         } else {
486           cat.start = gv2;
487           cat.end = gv1;
488         }
489         cat.velocity = mGroundProperty.vel;
490         cat.rotation = mGroundProperty.rot;
491         cat.rotation_pivot = mGroundProperty.pivot - mGroundCache->cache_center;
492
493         mGroundCache->catapults.push_back(cat);
494       }
495     }
496   }
497
498   SGExtendedTriangleFunctor<GroundCacheFill> mTriangleFunctor;
499   FGGroundCache* mGroundCache;
500   SGVec3d mCacheReference;
501   double mCacheRadius;
502   double mWireCacheRadius;
503   osg::Matrix mLocalToGlobal;
504   SGVec3d mDown;
505   bool sphIsec;
506   bool mBackfaceCulling;
507   FGGroundCache::GroundProperty mGroundProperty;
508 };
509
510 FGGroundCache::FGGroundCache()
511 {
512   cache_center = SGVec3d(0, 0, 0);
513   ground_radius = 0.0;
514   cache_ref_time = 0.0;
515   wire_id = 0;
516   reference_wgs84_point = SGVec3d(0, 0, 0);
517   reference_vehicle_radius = 0.0;
518   found_ground = false;
519 }
520
521 FGGroundCache::~FGGroundCache()
522 {
523 }
524
525 inline void
526 FGGroundCache::velocityTransformTriangle(double dt,
527                                          FGGroundCache::Triangle& dst,
528                                          const FGGroundCache::Triangle& src)
529 {
530   dst = src;
531
532   if (fabs(dt*dot(src.velocity, src.velocity)) < SGLimitsd::epsilon())
533     return;
534
535   for (int i = 0; i < 3; ++i) {
536     SGVec3d pivotoff = src.vertices[i] - src.rotation_pivot;
537     dst.vertices[i] += dt*(src.velocity + cross(src.rotation, pivotoff));
538   }
539   
540   // Transform the plane equation
541   SGVec3d pivotoff, vel;
542   sgdSubVec3(pivotoff.sg(), dst.plane.sg(), src.rotation_pivot.sg());
543   vel = src.velocity + cross(src.rotation, pivotoff);
544   dst.plane[3] += dt*sgdScalarProductVec3(dst.plane.sg(), vel.sg());
545   
546   dst.boundCenter += dt*src.velocity;
547 }
548
549 bool
550 FGGroundCache::prepare_ground_cache(double ref_time, const SGVec3d& pt,
551                                     double rad)
552 {
553   // Empty cache.
554   ground_radius = 0.0;
555   found_ground = false;
556   triangles.resize(0);
557   catapults.resize(0);
558   wires.resize(0);
559
560   // Store the parameters we used to build up that cache.
561   reference_wgs84_point = pt;
562   reference_vehicle_radius = rad;
563   // Store the time reference used to compute movements of moving triangles.
564   cache_ref_time = ref_time;
565
566   // Get a normalized down vector valid for the whole cache
567   SGQuatd hlToEc = SGQuatd::fromLonLat(SGGeod::fromCart(pt));
568   down = hlToEc.rotate(SGVec3d(0, 0, 1));
569
570   // Decide where we put the scenery center.
571   SGVec3d old_cntr = globals->get_scenery()->get_center();
572   SGVec3d cntr(pt);
573   // Only move the cache center if it is unacceptable far away.
574   if (40*40 < distSqr(old_cntr, cntr))
575     globals->get_scenery()->set_center(cntr);
576   else
577     cntr = old_cntr;
578
579   // The center of the cache.
580   cache_center = cntr;
581   
582   // Prepare sphere around the aircraft.
583   SGVec3d ptoff = pt - cache_center;
584   double cacheRadius = rad;
585
586   // Prepare bigger sphere around the aircraft.
587   // This one is required for reliably finding wires we have caught but
588   // have already left the hopefully smaller sphere for the ground reactions.
589   const double max_wire_dist = 300.0;
590   double wireCacheRadius = max_wire_dist < rad ? rad : max_wire_dist;
591
592   // Walk the scene graph and extract solid ground triangles and carrier data.
593   GroundCacheFillVisitor gcfv(this, down, ptoff, cacheRadius, wireCacheRadius);
594   globals->get_scenery()->get_scene_graph()->accept(gcfv);
595
596   // some stats
597   SG_LOG(SG_FLIGHT,SG_DEBUG, "prepare_ground_cache(): ac radius = " << rad
598          << ", # triangles = " << triangles.size()
599          << ", # wires = " << wires.size()
600          << ", # catapults = " << catapults.size()
601          << ", ground_radius = " << ground_radius );
602
603   // If the ground radius is still below 5e6 meters, then we do not yet have
604   // any scenery.
605   found_ground = found_ground && 5e6 < ground_radius;
606   if (!found_ground)
607     SG_LOG(SG_FLIGHT, SG_WARN, "prepare_ground_cache(): trying to build cache "
608            "without any scenery below the aircraft" );
609
610   if (cntr != old_cntr)
611     globals->get_scenery()->set_center(old_cntr);
612
613   return found_ground;
614 }
615
616 bool
617 FGGroundCache::is_valid(double& ref_time, SGVec3d& pt, double& rad)
618 {
619   pt = reference_wgs84_point;
620   rad = reference_vehicle_radius;
621   ref_time = cache_ref_time;
622   return found_ground;
623 }
624
625 double
626 FGGroundCache::get_cat(double t, const SGVec3d& dpt,
627                        SGVec3d end[2], SGVec3d vel[2])
628 {
629   // start with a distance of 1e10 meters...
630   double dist = 1e10;
631
632   // Time difference to the reference time.
633   t -= cache_ref_time;
634
635   size_t sz = catapults.size();
636   for (size_t i = 0; i < sz; ++i) {
637     SGVec3d pivotoff, rvel[2];
638     pivotoff = catapults[i].start - catapults[i].rotation_pivot;
639     rvel[0] = catapults[i].velocity + cross(catapults[i].rotation, pivotoff);
640     pivotoff = catapults[i].end - catapults[i].rotation_pivot;
641     rvel[1] = catapults[i].velocity + cross(catapults[i].rotation, pivotoff);
642
643     SGVec3d thisEnd[2];
644     thisEnd[0] = cache_center + catapults[i].start + t*rvel[0];
645     thisEnd[1] = cache_center + catapults[i].end + t*rvel[1];
646
647     sgdLineSegment3 ls;
648     sgdCopyVec3(ls.a, thisEnd[0].sg());
649     sgdCopyVec3(ls.b, thisEnd[1].sg());
650     double this_dist = sgdDistSquaredToLineSegmentVec3( ls, dpt.sg() );
651
652     if (this_dist < dist) {
653       SG_LOG(SG_FLIGHT,SG_INFO, "Found catapult "
654              << this_dist << " meters away");
655       dist = this_dist;
656         
657       end[0] = thisEnd[0];
658       end[1] = thisEnd[1];
659       vel[0] = rvel[0];
660       vel[1] = rvel[1];
661     }
662   }
663
664   // At the end take the root, we only computed squared distances ...
665   return sqrt(dist);
666 }
667
668 bool
669 FGGroundCache::get_agl(double t, const SGVec3d& dpt, double max_altoff,
670                        SGVec3d& contact, SGVec3d& normal, SGVec3d& vel,
671                        int *type, const SGMaterial** material, double *agl)
672 {
673   bool ret = false;
674
675   *type = FGInterface::Unknown;
676 //   *agl = 0.0;
677   if (material)
678     *material = 0;
679   vel = SGVec3d(0, 0, 0);
680   contact = SGVec3d(0, 0, 0);
681   normal = SGVec3d(0, 0, 0);
682
683   // Time difference to th reference time.
684   t -= cache_ref_time;
685
686   // The double valued point we start to search for intersection.
687   SGVec3d pt = dpt - cache_center;
688
689   // Initialize to something sensible
690   double current_radius = 0.0;
691
692   size_t sz = triangles.size();
693   for (size_t i = 0; i < sz; ++i) {
694     Triangle triangle;
695     velocityTransformTriangle(t, triangle, triangles[i]);
696     if (!fgdIsectSphereInfLine(triangle.boundCenter, triangle.boundRadius, pt, down))
697       continue;
698
699     // Check for intersection.
700     SGVec3d isecpoint;
701     if ( sgdIsectInfLinePlane( isecpoint.sg(), pt.sg(), down.sg(), triangle.plane.sg() ) &&
702          fgdPointInTriangle( isecpoint, triangle.vertices ) ) {
703       // Compute the vector from pt to the intersection point ...
704       SGVec3d off = isecpoint - pt;
705       // ... and check if it is too high or not
706       if (-max_altoff < dot(off, down)) {
707         // Transform to the wgs system
708         isecpoint += cache_center;
709         // compute the radius, good enough approximation to take the geocentric radius
710         double radius = dot(isecpoint, isecpoint);
711         if (current_radius < radius) {
712           current_radius = radius;
713           ret = true;
714           // Save the new potential intersection point.
715           contact = isecpoint;
716           // The first three values in the vector are the plane normal.
717           sgdCopyVec3( normal.sg(), triangle.plane.sg() );
718           // The velocity wrt earth.
719           SGVec3d pivotoff = pt - triangle.rotation_pivot;
720           vel = triangle.velocity + cross(triangle.rotation, pivotoff);
721           // Save the ground type.
722           *type = triangle.type;
723           *agl = dot(down, contact - dpt);
724           if (material)
725             *material = triangle.material;
726         }
727       }
728     }
729   }
730
731   if (ret)
732     return true;
733
734   // Whenever we did not have a ground triangle for the requested point,
735   // take the ground level we found during the current cache build.
736   // This is as good as what we had before for agl.
737   double r = length(dpt);
738   contact = dpt;
739   contact *= ground_radius/r;
740   normal = -down;
741   vel = SGVec3d(0, 0, 0);
742   
743   // The altitude is the distance of the requested point from the
744   // contact point.
745   *agl = dot(down, contact - dpt);
746   *type = FGInterface::Unknown;
747
748   return ret;
749 }
750
751 bool FGGroundCache::caught_wire(double t, const SGVec3d pt[4])
752 {
753   size_t sz = wires.size();
754   if (sz == 0)
755     return false;
756
757   // Time difference to the reference time.
758   t -= cache_ref_time;
759
760   // Build the two triangles spanning the area where the hook has moved
761   // during the past step.
762   SGVec4d plane[2];
763   SGVec3d tri[2][3];
764   sgdMakePlane( plane[0].sg(), pt[0].sg(), pt[1].sg(), pt[2].sg() );
765   tri[0][0] = pt[0];
766   tri[0][1] = pt[1];
767   tri[0][2] = pt[2];
768   sgdMakePlane( plane[1].sg(), pt[0].sg(), pt[2].sg(), pt[3].sg() );
769   tri[1][0] = pt[0];
770   tri[1][1] = pt[2];
771   tri[1][2] = pt[3];
772
773   // Intersect the wire lines with each of these triangles.
774   // You have caught a wire if they intersect.
775   for (size_t i = 0; i < sz; ++i) {
776     SGVec3d le[2];
777     for (int k = 0; k < 2; ++k) {
778       le[k] = wires[i].ends[k];
779       SGVec3d pivotoff = le[k] - wires[i].rotation_pivot;
780       SGVec3d vel = wires[i].velocity + cross(wires[i].rotation, pivotoff);
781       le[k] += t*vel + cache_center;
782     }
783     
784     for (int k=0; k<2; ++k) {
785       SGVec3d isecpoint;
786       double isecval = sgdIsectLinesegPlane(isecpoint.sg(), le[0].sg(),
787                                             le[1].sg(), plane[k].sg());
788       if ( 0.0 <= isecval && isecval <= 1.0 &&
789            fgdPointInTriangle( isecpoint, tri[k] ) ) {
790         SG_LOG(SG_FLIGHT,SG_INFO, "Caught wire");
791         // Store the wire id.
792         wire_id = wires[i].wire_id;
793         return true;
794       }
795     }
796   }
797
798   return false;
799 }
800
801 bool FGGroundCache::get_wire_ends(double t, SGVec3d end[2], SGVec3d vel[2])
802 {
803   // Fast return if we do not have an active wire.
804   if (wire_id < 0)
805     return false;
806
807   // Time difference to the reference time.
808   t -= cache_ref_time;
809
810   // Search for the wire with the matching wire id.
811   size_t sz = wires.size();
812   for (size_t i = 0; i < sz; ++i) {
813     if (wires[i].wire_id == wire_id) {
814       for (size_t k = 0; k < 2; ++k) {
815         SGVec3d pivotoff = end[k] - wires[i].rotation_pivot;
816         vel[k] = wires[i].velocity + cross(wires[i].rotation, pivotoff);
817         end[k] = cache_center + wires[i].ends[k] + t*vel[k];
818       }
819       return true;
820     }
821   }
822
823   return false;
824 }
825
826 void FGGroundCache::release_wire(void)
827 {
828   wire_id = -1;
829 }