]> git.mxchange.org Git - flightgear.git/blob - src/FDM/groundcache.cxx
683bb27d117d916619bf9de45d07106bd21e9a79
[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., 675 Mass Ave, Cambridge, MA 02139, USA.
20 //
21 // $Id$
22
23
24 #include <float.h>
25
26 #include <plib/sg.h>
27
28 #include <simgear/constants.h>
29 #include <simgear/debug/logstream.hxx>
30 #include <simgear/math/sg_geodesy.hxx>
31
32 #include <Main/globals.hxx>
33 #include <Scenery/scenery.hxx>
34 #include <Scenery/tilemgr.hxx>
35 #include <AIModel/AICarrier.hxx>
36
37 #include "flight.hxx"
38 #include "groundcache.hxx"
39
40 FGGroundCache::GroundProperty*
41 FGGroundCache::extractGroundProperty( ssgLeaf* l )
42 {
43   // FIXME: Do more ...
44   // Idea: have a get_globals() function which knows about that stuff.
45   // Or most propably read that from a configuration file,
46   // from property tree or whatever ...
47   
48   // Get ground dependent data.
49   GroundProperty *gp = new GroundProperty;
50   gp->wire_id = -1;
51   
52   FGAICarrierHardware *ud =
53     dynamic_cast<FGAICarrierHardware*>(l->getUserData());
54   if (ud) {
55     switch (ud->type) {
56     case FGAICarrierHardware::Wire:
57       gp->type = FGInterface::Wire;
58       gp->wire_id = ud->id;
59       break;
60     case FGAICarrierHardware::Catapult:
61       gp->type = FGInterface::Catapult;
62       break;
63     default:
64       gp->type = FGInterface::Solid;
65       break;
66     }
67
68     // Copy the velocity from the carrier class.
69     ud->carrier->getVelocityWrtEarth( gp->vel );
70   }
71
72   else {
73
74     // Initialize velocity field.
75     sgSetVec3( gp->vel, 0.0, 0.0, 0.0 );
76   }
77   
78   // Get the texture name and decide what ground type we have.
79   ssgState *st = l->getState();
80   if (st != NULL && st->isAKindOf(ssgTypeSimpleState())) {
81     ssgSimpleState *ss = (ssgSimpleState*)st;
82     SGPath fullPath( ss->getTextureFilename() ? ss->getTextureFilename(): "" );
83     string file = fullPath.file();
84     SGPath dirPath(fullPath.dir());
85     string category = dirPath.file();
86     
87     SG_LOG(SG_FLIGHT,SG_INFO,
88            "New triangle in cache: " << category << " " << file );
89     
90     if (category == "Runway")
91       gp->type = FGInterface::Solid;
92     else {
93       if (file == "asphault.rgb" || file == "airport.rgb")
94         gp->type = FGInterface::Solid;
95       else if (file == "water.rgb" || file == "water-lake.rgb")
96         gp->type = FGInterface::Water;
97       else if (file == "forest.rgb" || file == "cropwood.rgb")
98         gp->type = FGInterface::Forest;
99     }
100   }
101   
102   return gp;
103 }
104
105 // Test if the line given by the point on the line pt_on_line and the
106 // line direction dir intersects the sphere sp.
107 // Adapted from plib.
108 static bool
109 sgIsectSphereInfLine(const sgSphere *sp,
110                      const sgVec3 pt_on_line, const sgVec3 dir)
111 {
112   sgVec3 r ;
113   sgSubVec3 ( r, sp->getCenter(), pt_on_line ) ;
114
115   SGfloat projectedDistance = sgScalarProductVec3(r, dir);
116  
117   SGfloat dist = sgScalarProductVec3 ( r, r ) -
118     projectedDistance * projectedDistance; 
119
120   SGfloat radius = sp->getRadius();
121   return dist < radius*radius;
122 }
123
124 void
125 FGGroundCache::addAndFlattenLeaf(GLenum ty, ssgLeaf *l, ssgIndexArray *ia,
126                                  const sgMat4 xform)
127 {
128   // Extract data from the leaf which is just copied.
129   ssgVertexArray *va = ((ssgVtxTable *)l)->getVertices();
130   // Create a new leaf.
131   ssgVtxArray *vtxa = new ssgVtxArray( ty, va, 0, 0, 0, ia );
132   // Clones data ...
133   vtxa->removeUnusedVertices();
134   // Apply transform. We won't store transforms in our cache.
135   vtxa->transform( xform );
136   // Check for magic texture names object names and such ...
137   vtxa->setUserData( extractGroundProperty( l ) );
138   // Finally append to cache.
139   cache_root.addKid((ssgEntity*)vtxa);
140 }
141
142 void
143 FGGroundCache::putLineLeafIntoCache(const sgSphere *wsp, const sgMat4 xform,
144                                     ssgLeaf *l)
145 {
146   ssgIndexArray *ia = 0;
147   
148   // Lines must have special meanings.
149   // Wires and catapults are done with lines.
150   int nl = l->getNumLines();
151   for (int i = 0; i < nl; ++i) {
152     sgSphere tmp;
153     short v[2];
154     l->getLine(i, v, v+1 );
155     for (int k=0; k<2; ++k)
156       tmp.extend( l->getVertex( v[k] ) );
157     tmp.orthoXform(xform);
158     
159     if (wsp->intersects( &tmp )) {
160       if (ia == 0)
161         ia = new ssgIndexArray();
162         
163       ia->add( v[0] );
164       ia->add( v[1] );
165     }
166   }
167   if (!ia)
168     return;
169   
170   addAndFlattenLeaf(GL_LINES, l, ia, xform);
171 }
172
173 void
174 FGGroundCache::putSurfaceLeafIntoCache(const sgSphere *sp, const sgMat4 xform,
175                                        bool sphIsec, sgVec3 down, ssgLeaf *l)
176 {
177   ssgIndexArray *ia = 0;
178   
179   int nt = l->getNumTriangles();
180   for (int i = 0; i < nt; ++i) {
181     // Build up a sphere around that particular triangle-
182     sgSphere tmp;
183     short v[3];
184     l->getTriangle(i, v, v+1, v+2 );
185     for (int k=0; k<3; ++k)
186       tmp.extend( l->getVertex( v[k] ) );
187     tmp.orthoXform(xform);
188
189     // Check if the sphere around the vehicle intersects the sphere
190     // around that triangle. If so, put that triangle into the cache.
191     if (sphIsec && sp->intersects( &tmp )) {
192       if (ia == 0)
193         ia = new ssgIndexArray();
194       
195       ia->add( v[0] );
196       ia->add( v[1] );
197       ia->add( v[2] );
198     }
199     
200     // In case the cache is empty, we still provide agl computations.
201     // But then we use the old way of having a fixed elevation value for
202     // the whole lifetime of this cache.
203     if ( sgIsectSphereInfLine(&tmp, sp->getCenter(), down) ) {
204       sgVec3 tri[3];
205       for (int k=0; k<3; ++k) {
206         sgCopyVec3( tri[k], l->getVertex( v[k] ) );
207         sgXformPnt3( tri[k], xform );
208       }
209       
210       sgVec4 plane;
211       sgMakePlane( plane, tri[0], tri[1], tri[2]  );
212       sgVec3 ac_cent;
213       sgCopyVec3(ac_cent, sp->getCenter());
214       sgVec3 dst;
215       sgIsectInfLinePlane( dst, ac_cent, down, plane );
216       if ( sgPointInTriangle ( dst, tri ) ) {
217         found_ground = true;
218         sgdVec3 ddst;
219         sgdSetVec3(ddst, dst);
220         sgdAddVec3(ddst, cache_center);
221         double this_radius = sgdLengthVec3(ddst);
222         if (ground_radius < this_radius)
223           ground_radius = this_radius;
224       }
225     }
226   }
227   if (!ia)
228     return;
229   
230   addAndFlattenLeaf(GL_TRIANGLES, l, ia, xform);
231 }
232
233 // Here is the point where rotation should be handled
234 void
235 FGGroundCache::extractCacheRelativeVertex(double t, ssgVtxArray *va,
236                                           GroundProperty *gp,
237                                           short i, sgVec3 rel_pos,
238                                           sgdVec3 wgs84_vel)
239 {
240   sgCopyVec3( rel_pos, va->getVertex( i ) );
241   sgAddScaledVec3( rel_pos, gp->vel, t );
242
243   // Set velocity.
244   sgdSetVec3( wgs84_vel, gp->vel );
245 }
246
247 void
248 FGGroundCache::extractWgs84Vertex(double t, ssgVtxArray *va,
249                                   GroundProperty *gp, short i,
250                                   sgdVec3 wgs84_pos, sgdVec3 wgs84_vel)
251 {
252   sgVec3 rel_pos;
253   extractCacheRelativeVertex(t, va, gp, i, rel_pos, wgs84_vel);
254   sgdSetVec3( wgs84_pos, rel_pos );
255   sgdAddVec3( wgs84_pos, cache_center );
256 }
257
258
259 void
260 FGGroundCache::cache_fill(ssgBranch *branch, sgMat4 xform,
261                           sgSphere* sp, sgVec3 down, sgSphere* wsp)
262 {
263   // Travel through all kids.
264   ssgEntity *e;
265   for ( e = branch->getKid(0); e != NULL ; e = branch->getNextKid() ) {
266     if ( !( e->getTraversalMask() & SSGTRAV_HOT) )
267       continue;
268     if ( e->getBSphere()->isEmpty() )
269       continue;
270     
271     // Wee need to check further if either the sphere around the branch
272     // intersects the sphere around the aircraft or the line downwards from
273     // the aircraft intersects the branchs sphere.
274     sgSphere esphere = *(e->getBSphere());
275     esphere.orthoXform(xform);
276     bool wspIsec = wsp->intersects(&esphere);
277     bool downIsec = sgIsectSphereInfLine(&esphere, sp->getCenter(), down);
278     if (!wspIsec && !downIsec)
279       continue;
280       
281     // For branches collect up the transforms to reach that branch and
282     // call cache_fill recursively.
283     if ( e->isAKindOf( ssgTypeBranch() ) ) {
284       ssgBranch *b = (ssgBranch *)e;
285       if ( b->isAKindOf( ssgTypeTransform() ) ) {
286         // Collect up the transfors required to reach that part of
287         // the branch.
288         sgMat4 xform2;
289         sgMakeIdentMat4( xform2 );
290         ssgTransform *t = (ssgTransform*)b;
291         t->getTransform( xform2 );
292         sgPostMultMat4( xform2, xform );
293         cache_fill( b, xform2, sp, down, wsp );
294       } else
295         cache_fill( b, xform, sp, down, wsp );
296     }
297    
298     // For leafs, check each triangle for intersection.
299     // This will minimize the number of vertices/triangles in the cache.
300     else if (e->isAKindOf(ssgTypeLeaf())) {
301       // Since we reach that leaf if we have an intersection with the
302       // most propably bigger wire/catapult cache sphere, we need to check
303       // that here, if the smaller cache for the surface has a chance for hits.
304       // Also, if the spheres do not intersect compute a croase agl value
305       // by following the line downwards originating at the aircraft.
306       bool spIsec = sp->intersects(&esphere);
307       putSurfaceLeafIntoCache(sp, xform, spIsec, down, (ssgLeaf *)e);
308
309       // If we are here, we need to put all special hardware here into
310       // the cache.
311       if (wspIsec)
312         putLineLeafIntoCache(wsp, xform, (ssgLeaf *)e);
313     }
314   }
315 }
316
317 bool
318 FGGroundCache::prepare_ground_cache(double ref_time, const double pt[3],
319                                       double rad)
320 {
321   // Empty cache.
322   cache_root.removeAllKids();
323   ground_radius = 0.0;
324   found_ground = false;
325
326   // Store the parameters we used to build up that cache.
327   sgdCopyVec3(reference_wgs84_point, pt);
328   reference_vehicle_radius = rad;
329   // Store the time reference used to compute movements of moving triangles.
330   cache_ref_time = ref_time;
331
332   // The center of the cache.
333   Point3D psc = globals->get_tile_mgr()->get_current_center();
334   sgdSetVec3(cache_center, psc[0], psc[1], psc[2]);
335   
336   // Prepare sphere around the aircraft.
337   sgSphere acSphere;
338   acSphere.setRadius(rad);
339
340   // Compute the postion of the aircraft relative to the scenery center.
341   sgdVec3 doffset;
342   sgdSubVec3( doffset, pt, cache_center );
343   sgVec3 offset;
344   sgSetVec3( offset, doffset[0], doffset[1], doffset[2] );
345   acSphere.setCenter( offset );
346
347   // Prepare bigger sphere around the aircraft.
348   // This one is required for reliably finding wires we have caught but
349   // have already left the hopefully smaller sphere for the ground reactions.
350   const double max_wire_dist = 300.0;
351   sgSphere wireSphere;
352   wireSphere.setRadius( max_wire_dist < rad ? rad : max_wire_dist );
353   wireSphere.setCenter( offset );
354
355   // Down vector. Is used for croase agl computations when we are far enough
356   // from ground that we have an empty cache.
357   sgVec3 down;
358   sgSetVec3(down, -pt[0], -pt[1], -pt[2]);
359   sgNormalizeVec3(down);
360
361   // We collaps all transforms we need to reach a particular leaf.
362   // The leafs itself will be then transformed later.
363   // So our cache is just flat.
364   // For leafs which are moving (carriers surface, etc ...)
365   // we will later store a speed in the GroundType class. We can then apply
366   // some translations to that nodes according to the time which has passed
367   // compared to that snapshot.
368   sgMat4 xform;
369   sgMakeIdentMat4(xform);
370
371   // Walk the terrain branch for now.
372   ssgBranch *terrain = globals->get_scenery()->get_scene_graph();
373   cache_fill(terrain, xform, &acSphere, down, &wireSphere);
374
375   // some stats
376   SG_LOG(SG_FLIGHT,SG_INFO, "prepare_ground_cache(): ac radius = " << rad
377          << ", # leafs = " << cache_root.getNumKids()
378          << ", ground_radius = " << ground_radius );
379
380   // If the ground radius is still below 5e6 meters, then we do not yet have
381   // any scenery.
382   found_ground = found_ground && 5e6 < ground_radius;
383   if (!found_ground)
384     SG_LOG(SG_FLIGHT, SG_WARN, "prepare_ground_cache(): trying to build cache "
385            "without any scenery below the aircraft" );
386
387   return found_ground;
388 }
389
390 bool
391 FGGroundCache::is_valid(double *ref_time, double pt[3], double *rad)
392 {
393   sgdCopyVec3(pt, reference_wgs84_point);
394   *rad = reference_vehicle_radius;
395   *ref_time = cache_ref_time;
396   return found_ground;
397 }
398
399 double
400 FGGroundCache::get_cat(double t, const double dpt[3],
401                          double end[2][3], double vel[2][3])
402 {
403   // start with a distance of 1e10 meters...
404   double dist = 1e10;
405
406   // Time difference to the reference time.
407   t -= cache_ref_time;
408
409   // We know that we have a flat cache ...
410   ssgEntity *e;
411   for ( e = cache_root.getKid(0); e != NULL ; e = cache_root.getNextKid() ) {
412     // We just know that, because we build that ourselfs ...
413     ssgVtxArray *va = (ssgVtxArray *)e;
414     // Only lines are interresting ...
415     if (va->getPrimitiveType() != GL_LINES)
416       continue;
417     GroundProperty *gp = dynamic_cast<GroundProperty*>(va->getUserData());
418     // Assertation???
419     if ( !gp )
420       continue;
421     // Check if we have a catapult ...
422     if ( gp->type != FGInterface::Catapult )
423       continue;
424
425     int nl = va->getNumLines();
426     for (int i=0; i < nl; ++i) {
427       sgdLineSegment3 ls;
428       sgdVec3 lsVel[2];
429       short vi[2];
430       va->getLine(i, vi, vi+1 );
431       extractWgs84Vertex(t, va, gp, vi[0], ls.a, lsVel[0]);
432       extractWgs84Vertex(t, va, gp, vi[1], ls.b, lsVel[1]);
433       
434       double this_dist = sgdDistSquaredToLineSegmentVec3( ls, dpt );
435       if (this_dist < dist) {
436         dist = this_dist;
437         
438         // end[0] is the end where the cat starts.
439         // end[1] is the end where the cat ends.
440         // The carrier code takes care of that ordering.
441         sgdCopyVec3( end[0], ls.a );
442         sgdCopyVec3( end[1], ls.b );
443         sgdCopyVec3( vel[0], lsVel[0] );
444         sgdCopyVec3( vel[1], lsVel[1] );
445       }
446     }
447   }
448
449   // At the end take the root, we only computed squared distances ...
450   return sqrt(dist);
451 }
452
453 bool
454 FGGroundCache::get_agl(double t, const double dpt[3],
455                          double contact[3], double normal[3], double vel[3],
456                          int *type, double *loadCapacity,
457                          double *frictionFactor, double *agl)
458 {
459   bool ret = false;
460
461   *type = FGInterface::Unknown;
462 //   *agl = 0.0;
463   *loadCapacity = DBL_MAX;
464   *frictionFactor = 1.0;
465   sgdSetVec3( vel, 0.0, 0.0, 0.0 );
466   sgdSetVec3( contact, 0.0, 0.0, 0.0 );
467   sgdSetVec3( normal, 0.0, 0.0, 0.0 );
468
469   // Time difference to th reference time.
470   t -= cache_ref_time;
471
472   // The double valued point we start to search for intersection.
473   sgdVec3 tmp;
474   sgdSubVec3( tmp, dpt, cache_center );
475   sgVec3 pt;
476   sgSetVec3( pt, tmp );
477
478   // The search direction
479   sgVec3 dir;
480   sgSetVec3( dir, -dpt[0], -dpt[1], -dpt[2] );
481
482   // Initialize to something sensible
483   double sqdist = DBL_MAX;
484
485   // We know that we have a flat cache ...
486   // We just know that, because we build that ourselfs ...
487   ssgEntity *e;
488   for ( e = cache_root.getKid(0) ; e != NULL ; e = cache_root.getNextKid() ) {
489     // We just know that, because we build that ourselfs ...
490     ssgVtxArray *va = (ssgVtxArray *)e;
491     // AGL computations are done with triangle/surface leafs.
492     if (va->getPrimitiveType() != GL_TRIANGLES)
493       continue;
494     GroundProperty *gp = dynamic_cast<GroundProperty*>(va->getUserData());
495     // Assertation???
496     if ( !gp )
497       continue;
498
499     int nt = va->getNumTriangles();
500     for (int i=0; i < nt; ++i) {
501       short vi[3];
502       va->getTriangle( i, vi, vi+1, vi+2 );
503       
504       sgVec3 tri[3];
505       sgdVec3 dvel[3];
506       for (int k=0; k<3; ++k)
507         extractCacheRelativeVertex(t, va, gp, vi[k], tri[k], dvel[k]);
508       sgVec4 plane;
509       sgMakePlane( plane, tri[0], tri[1], tri[2] );
510       
511       // Check for intersection.
512       sgVec3 isecpoint;
513       if ( sgIsectInfLinePlane( isecpoint, pt, dir, plane ) &&
514            sgPointInTriangle3( isecpoint, tri ) ) {
515         // Check for the closest intersection point.
516         // FIXME: is this the right one?
517         double newSqdist = sgDistanceSquaredVec3( isecpoint, pt );
518         if ( newSqdist < sqdist ) {
519           sqdist = newSqdist;
520           ret = true;
521           // Save the new potential intersection point.
522           sgdSetVec3( contact, isecpoint );
523           sgdAddVec3( contact, cache_center );
524           // The first three values in the vector are the plane normal.
525           sgdSetVec3( normal, plane );
526           // The velocity wrt earth.
527           /// FIXME: only true for non rotating objects!!!!
528           sgdCopyVec3( vel, dvel[0] );
529           // Save the ground type.
530           *type = gp->type;
531           // FIXME: figure out how to get that sign ...
532 //           *agl = sqrt(sqdist);
533           *agl = sgdLengthVec3( dpt ) - sgdLengthVec3( contact );
534 //           *loadCapacity = DBL_MAX;
535 //           *frictionFactor = 1.0;
536         }
537       }
538     }
539   }
540
541   if (ret)
542     return true;
543
544   // Whenever we did not have a ground triangle for the requested point,
545   // take the ground level we found during the current cache build.
546   // This is as good as what we had before for agl.
547   double r = sgdLengthVec3( dpt );
548   sgdCopyVec3( contact, dpt );
549   sgdScaleVec3( contact, ground_radius/r );
550   sgdCopyVec3( normal, dpt );
551   sgdNormaliseVec3( normal );
552   sgdSetVec3( vel, 0.0, 0.0, 0.0 );
553   
554   // The altitude is the distance of the requested point from the
555   // contact point.
556   *agl = sgdLengthVec3( dpt ) - sgdLengthVec3( contact );
557   *type = FGInterface::Unknown;
558   *loadCapacity = DBL_MAX;
559   *frictionFactor = 1.0;
560
561   return ret;
562 }
563
564 bool FGGroundCache::caught_wire(double t, const double cpt[4][3])
565 {
566   bool ret = false;
567
568   // Time difference to the reference time.
569   t -= cache_ref_time;
570
571   bool firsttime = true;
572   sgVec4 plane[2];
573   sgVec3 tri[2][3];
574
575   // We know that we have a flat cache ...
576   ssgEntity *e;
577   for ( e = cache_root.getKid(0); e != NULL ; e = cache_root.getNextKid() ) {
578     // We just know that, because we build that ourselfs ...
579     ssgVtxArray *va = (ssgVtxArray *)e;
580     // Only lines are interresting ...
581     if (va->getPrimitiveType() != GL_LINES)
582       continue;
583     GroundProperty *gp = dynamic_cast<GroundProperty*>(va->getUserData());
584     // Assertation???
585     if ( !gp )
586       continue;
587     // Check if we have a catapult ...
588     if ( gp->type != FGInterface::Wire )
589       continue;
590
591     // Lazy compute the values required for intersectiion tests.
592     // Since we normally do not have wires in the cache this is a
593     // huge benefit.
594     if (firsttime) {
595       firsttime = false;
596       sgVec3 pt[4];
597       for (int k=0; k<4; ++k) {
598         sgdVec3 tmp;
599         sgdSubVec3( tmp, cpt[k], cache_center );
600         sgSetVec3( pt[k], tmp );
601       }
602       sgMakePlane( plane[0], pt[0], pt[1], pt[2] );
603       sgCopyVec3( tri[0][0], pt[0] );
604       sgCopyVec3( tri[0][1], pt[1] );
605       sgCopyVec3( tri[0][2], pt[2] );
606       sgMakePlane( plane[1], pt[0], pt[2], pt[3] );
607       sgCopyVec3( tri[1][0], pt[0] );
608       sgCopyVec3( tri[1][1], pt[2] );
609       sgCopyVec3( tri[1][2], pt[3] );
610     }
611     
612     int nl = va->getNumLines();
613     for (int i=0; i < nl; ++i) {
614       short vi[2];
615       va->getLine(i, vi, vi+1 );
616       sgVec3 le[2];
617       sgdVec3 dummy;
618       extractCacheRelativeVertex(t, va, gp, vi[0], le[0], dummy);
619       extractCacheRelativeVertex(t, va, gp, vi[1], le[1], dummy);
620       
621       for (int k=0; k<2; ++k) {
622         sgVec3 isecpoint;
623         float isecval = sgIsectLinesegPlane( isecpoint, le[0], le[1], plane[k] );
624         
625         if ( 0.0 <= isecval && isecval <= 1.0 &&
626              sgPointInTriangle( isecpoint, tri[k] ) ) {
627           // Store the wire id.
628           wire_id = gp->wire_id;
629           ret = true;
630         }
631       }
632     }
633   }
634
635   return ret;
636 }
637
638 bool FGGroundCache::get_wire_ends(double t, double end[2][3], double vel[2][3])
639 {
640   // Fast return if we do not have an active wire.
641   if (wire_id < 0)
642     return false;
643
644   bool ret = false;
645
646   // Time difference to th reference time.
647   t -= cache_ref_time;
648
649   // We know that we have a flat cache ...
650   ssgEntity *e;
651   for ( e = cache_root.getKid(0); e != NULL ; e = cache_root.getNextKid() ) {
652     // We just know that, because we build that ourselfs ...
653     ssgVtxArray *va = (ssgVtxArray *)e;
654     // Only lines are interresting ...
655     if (va->getPrimitiveType() != GL_LINES)
656       continue;
657     GroundProperty *gp = dynamic_cast<GroundProperty*>(va->getUserData());
658     // Assertation???
659     if ( !gp )
660       continue;
661     // Check if we have a catapult ...
662     if ( gp->type != FGInterface::Wire )
663       continue;
664     if ( gp->wire_id != wire_id )
665       continue;
666
667     // Get the line ends, that are the wire endpoints.
668     short vi[2];
669     va->getLine(0, vi, vi+1 );
670     extractWgs84Vertex(t, va, gp, vi[0], end[0], vel[0]);
671     extractWgs84Vertex(t, va, gp, vi[1], end[1], vel[1]);
672
673     ret = true;
674   }
675
676   return ret;
677 }
678
679 void FGGroundCache::release_wire(void)
680 {
681   wire_id = -1;
682 }