]> git.mxchange.org Git - flightgear.git/blob - src/FDM/groundcache.cxx
b55cd81d320e16ca58032c3fdcf8130fc5963f0c
[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, 2009  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 <utility>
28
29 #include <osg/Drawable>
30 #include <osg/Geode>
31 #include <osg/Geometry>
32 #include <osg/Camera>
33 #include <osg/Transform>
34 #include <osg/MatrixTransform>
35 #include <osg/PositionAttitudeTransform>
36 #include <osg/CameraView>
37
38 #include <simgear/sg_inlines.h>
39 #include <simgear/constants.h>
40 #include <simgear/debug/logstream.hxx>
41 #include <simgear/math/sg_geodesy.hxx>
42 #include <simgear/scene/util/SGNodeMasks.hxx>
43 #include <simgear/scene/util/SGSceneUserData.hxx>
44
45 #include <simgear/scene/bvh/BVHNode.hxx>
46 #include <simgear/scene/bvh/BVHGroup.hxx>
47 #include <simgear/scene/bvh/BVHTransform.hxx>
48 #include <simgear/scene/bvh/BVHMotionTransform.hxx>
49 #include <simgear/scene/bvh/BVHLineGeometry.hxx>
50 #include <simgear/scene/bvh/BVHStaticGeometry.hxx>
51 #include <simgear/scene/bvh/BVHStaticData.hxx>
52 #include <simgear/scene/bvh/BVHStaticNode.hxx>
53 #include <simgear/scene/bvh/BVHStaticTriangle.hxx>
54 #include <simgear/scene/bvh/BVHStaticBinary.hxx>
55 #include <simgear/scene/bvh/BVHSubTreeCollector.hxx>
56 #include <simgear/scene/bvh/BVHLineSegmentVisitor.hxx>
57 #include <simgear/scene/bvh/BVHNearestPointVisitor.hxx>
58
59 #include <Main/globals.hxx>
60 #include <Scenery/scenery.hxx>
61 #include <Scenery/tilemgr.hxx>
62
63 #include "flight.hxx"
64 #include "groundcache.hxx"
65
66 using namespace simgear;
67
68 class FGGroundCache::CacheFill : public osg::NodeVisitor {
69 public:
70     CacheFill(const SGVec3d& center, const double& radius,
71               const double& startTime, const double& endTime) :
72         osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN),
73         _center(center),
74         _radius(radius),
75         _startTime(startTime),
76         _endTime(endTime)
77     {
78         setTraversalMask(SG_NODEMASK_TERRAIN_BIT);
79     }
80     virtual void apply(osg::Node& node)
81     {
82         if (!testBoundingSphere(node.getBound()))
83             return;
84
85         addBoundingVolume(node);
86     }
87     
88     virtual void apply(osg::Group& group)
89     {
90         if (!testBoundingSphere(group.getBound()))
91             return;
92
93         simgear::BVHSubTreeCollector::NodeList parentNodeList;
94         mSubTreeCollector.pushNodeList(parentNodeList);
95         
96         traverse(group);
97         addBoundingVolume(group);
98         
99         mSubTreeCollector.popNodeList(parentNodeList);
100     }
101     
102     virtual void apply(osg::Transform& transform)
103     { handleTransform(transform); }
104     virtual void apply(osg::Camera& camera)
105     {
106         if (camera.getRenderOrder() != osg::Camera::NESTED_RENDER)
107             return;
108         handleTransform(camera);
109     }
110     virtual void apply(osg::CameraView& transform)
111     { handleTransform(transform); }
112     virtual void apply(osg::MatrixTransform& transform)
113     { handleTransform(transform); }
114     virtual void apply(osg::PositionAttitudeTransform& transform)
115     { handleTransform(transform); }
116         
117     void handleTransform(osg::Transform& transform)
118     {
119         // Hmm, may be this needs to be refined somehow ...
120         if (transform.getReferenceFrame() != osg::Transform::RELATIVE_RF)
121             return;
122
123         if (!testBoundingSphere(transform.getBound()))
124             return;
125
126         osg::Matrix inverseMatrix;
127         if (!transform.computeWorldToLocalMatrix(inverseMatrix, this))
128             return;
129         osg::Matrix matrix;
130         if (!transform.computeLocalToWorldMatrix(matrix, this))
131             return;
132
133         // Look for a velocity note
134         const SGSceneUserData::Velocity* velocity = getVelocity(transform);
135
136         SGVec3d center = _center;
137         _center = SGVec3d(inverseMatrix.preMult(_center.osg()));
138         double radius = _radius;
139         if (velocity)
140             _radius += (_endTime - _startTime)*norm(velocity->linear);
141         
142         simgear::BVHSubTreeCollector::NodeList parentNodeList;
143         mSubTreeCollector.pushNodeList(parentNodeList);
144
145         addBoundingVolume(transform);
146         traverse(transform);
147
148         if (mSubTreeCollector.haveChildren()) {
149             if (velocity) {
150                 simgear::BVHMotionTransform* bvhTransform;
151                 bvhTransform = new simgear::BVHMotionTransform;
152                 bvhTransform->setToWorldTransform(SGMatrixd(matrix.ptr()));
153                 bvhTransform->setLinearVelocity(velocity->linear);
154                 bvhTransform->setAngularVelocity(velocity->angular);
155                 bvhTransform->setReferenceTime(_startTime);
156                 bvhTransform->setEndTime(_endTime);
157                 bvhTransform->setId(velocity->id);
158
159                 mSubTreeCollector.popNodeList(parentNodeList, bvhTransform);
160             } else {
161                 simgear::BVHTransform* bvhTransform;
162                 bvhTransform = new simgear::BVHTransform;
163                 bvhTransform->setToWorldTransform(SGMatrixd(matrix.ptr()));
164
165                 mSubTreeCollector.popNodeList(parentNodeList, bvhTransform);
166             }
167         } else {
168             mSubTreeCollector.popNodeList(parentNodeList);
169         }
170         _center = center;
171         _radius = radius;
172     }
173
174     const SGSceneUserData::Velocity* getVelocity(osg::Node& node)
175     {
176         SGSceneUserData* userData = SGSceneUserData::getSceneUserData(&node);
177         if (!userData)
178             return 0;
179         return userData->getVelocity();
180     }
181     simgear::BVHNode* getNodeBoundingVolume(osg::Node& node)
182     {
183         SGSceneUserData* userData = SGSceneUserData::getSceneUserData(&node);
184         if (!userData)
185             return 0;
186         return userData->getBVHNode();
187     }
188     void addBoundingVolume(osg::Node& node)
189     {
190         simgear::BVHNode* bvNode = getNodeBoundingVolume(node);
191         if (!bvNode)
192             return;
193
194         // Get that part of the local bv tree that intersects our sphere
195         // of interrest.
196         mSubTreeCollector.setSphere(SGSphered(_center, _radius));
197         bvNode->accept(mSubTreeCollector);
198     }
199     
200     bool testBoundingSphere(const osg::BoundingSphere& bound) const
201     {
202         if (!bound.valid())
203             return false;
204
205         double maxDist = bound._radius + _radius;
206         return distSqr(SGVec3d(bound._center), _center) <= maxDist*maxDist;
207     }
208     
209     SGSharedPtr<simgear::BVHNode> getBVHNode() const
210     { return mSubTreeCollector.getNode(); }
211     
212 private:
213     
214     SGVec3d _center;
215     double _radius;
216     double _startTime;
217     double _endTime;
218
219     simgear::BVHSubTreeCollector mSubTreeCollector;
220 };
221
222 FGGroundCache::FGGroundCache() :
223     _altitude(0),
224     _material(0),
225     cache_ref_time(0),
226     _wire(0),
227     reference_wgs84_point(SGVec3d(0, 0, 0)),
228     reference_vehicle_radius(0),
229     down(0.0, 0.0, 0.0),
230     found_ground(false)
231 {
232 }
233
234 FGGroundCache::~FGGroundCache()
235 {
236 }
237
238 bool
239 FGGroundCache::prepare_ground_cache(double ref_time, const SGVec3d& pt,
240                                     double rad)
241 {
242     // Empty cache.
243     found_ground = false;
244
245     SGGeod geodPt = SGGeod::fromCart(pt);
246     // Don't blow away the cache ground_radius and stuff if there's no
247     // scenery
248     if (!globals->get_tile_mgr()->scenery_available(geodPt.getLatitudeDeg(),
249                                                     geodPt.getLongitudeDeg(),
250                                                     rad))
251         return false;
252     _altitude = 0;
253
254     // If we have an active wire, get some more area into the groundcache
255     if (_wire)
256         rad = SGMiscd::max(200, rad);
257     
258     // Store the parameters we used to build up that cache.
259     reference_wgs84_point = pt;
260     reference_vehicle_radius = rad;
261     // Store the time reference used to compute movements of moving triangles.
262     cache_ref_time = ref_time;
263     
264     // Get a normalized down vector valid for the whole cache
265     SGQuatd hlToEc = SGQuatd::fromLonLat(geodPt);
266     down = hlToEc.rotate(SGVec3d(0, 0, 1));
267     
268     // Get the ground cache, that is a local collision tree of the environment
269     double endTime = cache_ref_time + 1; //FIXME??
270     CacheFill subtreeCollector(pt, rad, cache_ref_time, endTime);
271     globals->get_scenery()->get_scene_graph()->accept(subtreeCollector);
272     _localBvhTree = subtreeCollector.getBVHNode();
273
274     // Try to get a croase altitude value for the ground cache
275     SGLineSegmentd line(pt, pt + 2*reference_vehicle_radius*down);
276     simgear::BVHLineSegmentVisitor lineSegmentVisitor(line, ref_time);
277     if (_localBvhTree)
278         _localBvhTree->accept(lineSegmentVisitor);
279
280     // If this is successful, store this altitude for croase altitude values
281     if (!lineSegmentVisitor.empty()) {
282         SGGeod geodPt = SGGeod::fromCart(lineSegmentVisitor.getPoint());
283         _altitude = geodPt.getElevationM();
284         _material = lineSegmentVisitor.getMaterial();
285         found_ground = true;
286     } else {
287         // Else do a crude scene query for the current point
288         found_ground = globals->get_scenery()->
289             get_cart_elevation_m(pt, rad, _altitude, &_material);
290     }
291     
292     // Still not sucessful??
293     if (!found_ground)
294         SG_LOG(SG_FLIGHT, SG_WARN, "prepare_ground_cache(): trying to build "
295                "cache without any scenery below the aircraft");
296
297     return found_ground;
298 }
299
300 bool
301 FGGroundCache::is_valid(double& ref_time, SGVec3d& pt, double& rad)
302 {
303     pt = reference_wgs84_point;
304     rad = reference_vehicle_radius;
305     ref_time = cache_ref_time;
306     return found_ground;
307 }
308
309 class FGGroundCache::BodyFinder : public BVHVisitor {
310 public:
311     BodyFinder(BVHNode::Id id, const double& t) :
312         _id(id),
313         _bodyToWorld(SGMatrixd::unit()),
314         _linearVelocity(0, 0, 0),
315         _angularVelocity(0, 0, 0),
316         _time(t)
317     { }
318     
319     virtual void apply(BVHGroup& leaf)
320     {
321         if (_foundId)
322             return;
323         leaf.traverse(*this);
324     }
325     virtual void apply(BVHTransform& transform)
326     {
327         if (_foundId)
328             return;
329
330         transform.traverse(*this);
331         
332         if (_foundId) {
333             _linearVelocity = transform.vecToWorld(_linearVelocity);
334             _angularVelocity = transform.vecToWorld(_angularVelocity);
335             _bodyToWorld = transform.getToWorldTransform()*_bodyToWorld;
336         }
337     }
338     virtual void apply(BVHMotionTransform& transform)
339     {
340         if (_foundId)
341             return;
342
343         if (_id == transform.getId()) {
344             _foundId = true;
345             return;
346         }
347         
348         transform.traverse(*this);
349         
350         if (_foundId) {
351             SGMatrixd toWorld = transform.getToWorldTransform(_time);
352             SGVec3d referencePoint = _bodyToWorld.xformPt(SGVec3d::zeros());
353             _linearVelocity += transform.getLinearVelocityAt(referencePoint);
354             _angularVelocity += transform.getAngularVelocity();
355             _linearVelocity = toWorld.xformVec(_linearVelocity);
356             _angularVelocity = toWorld.xformVec(_angularVelocity);
357             _bodyToWorld = toWorld*_bodyToWorld;
358         }
359     }
360     virtual void apply(BVHLineGeometry& node) { }
361     virtual void apply(BVHStaticGeometry& node) { }
362     
363     virtual void apply(const BVHStaticBinary&, const BVHStaticData&) { }
364     virtual void apply(const BVHStaticTriangle&, const BVHStaticData&) { }
365     
366     const SGMatrixd& getBodyToWorld() const
367     { return _bodyToWorld; }
368     const SGVec3d& getLinearVelocity() const
369     { return _linearVelocity; }
370     const SGVec3d& getAngularVelocity() const
371     { return _angularVelocity; }
372     
373     bool empty() const
374     { return !_foundId; }
375     
376 protected:
377     simgear::BVHNode::Id _id;
378
379     SGMatrixd _bodyToWorld;
380
381     SGVec3d _linearVelocity;
382     SGVec3d _angularVelocity;
383     
384     bool _foundId;
385
386     double _time;
387 };
388
389 bool
390 FGGroundCache::get_body(double t, SGMatrixd& bodyToWorld, SGVec3d& linearVel,
391                         SGVec3d& angularVel, simgear::BVHNode::Id id)
392 {
393     // Get the transform matrix and velocities of a moving body with id at t.
394     if (!_localBvhTree)
395         return false;
396     BodyFinder bodyFinder(id, t);
397     _localBvhTree->accept(bodyFinder);
398     if (bodyFinder.empty())
399         return false;
400
401     bodyToWorld = bodyFinder.getBodyToWorld();
402     linearVel = bodyFinder.getLinearVelocity();
403     angularVel = bodyFinder.getAngularVelocity();
404
405     return true;
406 }
407
408 class FGGroundCache::CatapultFinder : public BVHVisitor {
409 public:
410     CatapultFinder(const SGSphered& sphere, const double& t) :
411         _sphere(sphere),
412         _time(t),
413         _haveLineSegment(false)
414     { }
415     
416     virtual void apply(BVHGroup& leaf)
417     {
418         if (!intersects(_sphere, leaf.getBoundingSphere()))
419             return;
420         leaf.traverse(*this);
421     }
422     virtual void apply(BVHTransform& transform)
423     {
424         if (!intersects(_sphere, transform.getBoundingSphere()))
425             return;
426         
427         SGSphered sphere = _sphere;
428         _sphere = transform.sphereToLocal(sphere);
429         bool haveLineSegment = _haveLineSegment;
430         _haveLineSegment = false;
431         
432         transform.traverse(*this);
433         
434         if (_haveLineSegment) {
435             _lineSegment = transform.lineSegmentToWorld(_lineSegment);
436             _linearVelocity = transform.vecToWorld(_linearVelocity);
437             _angularVelocity = transform.vecToWorld(_angularVelocity);
438         }
439         _haveLineSegment |= haveLineSegment;
440         _sphere.setCenter(sphere.getCenter());
441     }
442     virtual void apply(BVHMotionTransform& transform)
443     {
444         if (!intersects(_sphere, transform.getBoundingSphere()))
445             return;
446         
447         SGSphered sphere = _sphere;
448         _sphere = transform.sphereToLocal(sphere, _time);
449         bool haveLineSegment = _haveLineSegment;
450         _haveLineSegment = false;
451         
452         transform.traverse(*this);
453         
454         if (_haveLineSegment) {
455             SGMatrixd toWorld = transform.getToWorldTransform(_time);
456             _linearVelocity
457                 += transform.getLinearVelocityAt(_lineSegment.getStart());
458             _angularVelocity += transform.getAngularVelocity();
459             _linearVelocity = toWorld.xformVec(_linearVelocity);
460             _angularVelocity = toWorld.xformVec(_angularVelocity);
461             _lineSegment = _lineSegment.transform(toWorld);
462         }
463         _haveLineSegment |= haveLineSegment;
464         _sphere.setCenter(sphere.getCenter());
465     }
466     virtual void apply(BVHLineGeometry& node)
467     {
468         if (node.getType() != BVHLineGeometry::CarrierCatapult)
469             return;
470
471         SGLineSegmentd lineSegment(node.getLineSegment());
472         if (!intersects(_sphere, lineSegment))
473             return;
474
475         _lineSegment = lineSegment;
476         double dist = distSqr(lineSegment, getSphere().getCenter());
477         _sphere.setRadius(sqrt(dist));
478         _linearVelocity = SGVec3d::zeros();
479         _angularVelocity = SGVec3d::zeros();
480         _haveLineSegment = true;
481     }
482     virtual void apply(BVHStaticGeometry& node)
483     { }
484     
485     virtual void apply(const BVHStaticBinary&, const BVHStaticData&) { }
486     virtual void apply(const BVHStaticTriangle&, const BVHStaticData&) { }
487     
488     void setSphere(const SGSphered& sphere)
489     { _sphere = sphere; }
490     const SGSphered& getSphere() const
491     { return _sphere; }
492     
493     const SGLineSegmentd& getLineSegment() const
494     { return _lineSegment; }
495     const SGVec3d& getLinearVelocity() const
496     { return _linearVelocity; }
497     const SGVec3d& getAngularVelocity() const
498     { return _angularVelocity; }
499     
500     bool getHaveLineSegment() const
501     { return _haveLineSegment; }
502     
503 protected:
504     SGLineSegmentd _lineSegment;
505     SGVec3d _linearVelocity;
506     SGVec3d _angularVelocity;
507     
508     bool _haveLineSegment;
509
510     SGSphered _sphere;
511     double _time;
512 };
513
514 double
515 FGGroundCache::get_cat(double t, const SGVec3d& pt,
516                        SGVec3d end[2], SGVec3d vel[2])
517 {
518     double maxDistance = 1000;
519
520     // Get the wire in question
521     CatapultFinder catapultFinder(SGSphered(pt, maxDistance), t);
522     if (_localBvhTree)
523         _localBvhTree->accept(catapultFinder);
524     
525     if (!catapultFinder.getHaveLineSegment())
526         return maxDistance;
527
528     // prepare the returns
529     end[0] = catapultFinder.getLineSegment().getStart();
530     end[1] = catapultFinder.getLineSegment().getEnd();
531
532     // The linear velocity is the one at the start of the line segment ...
533     vel[0] = catapultFinder.getLinearVelocity();
534     // ... so the end point has the additional cross product.
535     vel[1] = catapultFinder.getLinearVelocity();
536     vel[1] += cross(catapultFinder.getAngularVelocity(),
537                     catapultFinder.getLineSegment().getDirection());
538
539     // Return the distance to the cat
540     return sqrt(distSqr(catapultFinder.getLineSegment(), pt));
541 }
542
543 bool
544 FGGroundCache::get_agl(double t, const SGVec3d& pt, SGVec3d& contact,
545                        SGVec3d& normal, SGVec3d& linearVel, SGVec3d& angularVel,
546                        simgear::BVHNode::Id& id, const SGMaterial*& material)
547 {
548     // Just set up a ground intersection query for the given point
549     SGLineSegmentd line(pt, pt + 10*reference_vehicle_radius*down);
550     simgear::BVHLineSegmentVisitor lineSegmentVisitor(line, t);
551     if (_localBvhTree)
552         _localBvhTree->accept(lineSegmentVisitor);
553
554     if (!lineSegmentVisitor.empty()) {
555         // Have an intersection
556         contact = lineSegmentVisitor.getPoint();
557         normal = lineSegmentVisitor.getNormal();
558         if (0 < dot(normal, down))
559             normal = -normal;
560         linearVel = lineSegmentVisitor.getLinearVelocity();
561         angularVel = lineSegmentVisitor.getAngularVelocity();
562         material = lineSegmentVisitor.getMaterial();
563         id = lineSegmentVisitor.getId();
564
565         return true;
566     } else {
567         // Whenever we did not have a ground triangle for the requested point,
568         // take the ground level we found during the current cache build.
569         // This is as good as what we had before for agl.
570         SGGeod geodPt = SGGeod::fromCart(pt);
571         geodPt.setElevationM(_altitude);
572         contact = SGVec3d::fromGeod(geodPt);
573         normal = -down;
574         linearVel = SGVec3d(0, 0, 0);
575         angularVel = SGVec3d(0, 0, 0);
576         material = _material;
577         id = 0;
578
579         return found_ground;
580     }
581 }
582
583
584 bool
585 FGGroundCache::get_nearest(double t, const SGVec3d& pt, double maxDist,
586                            SGVec3d& contact, SGVec3d& linearVel,
587                            SGVec3d& angularVel, simgear::BVHNode::Id& id,
588                            const SGMaterial*& material)
589 {
590     if (!_localBvhTree)
591         return false;
592
593     // Just set up a ground intersection query for the given point
594     SGSphered sphere(pt, maxDist);
595     simgear::BVHNearestPointVisitor nearestPointVisitor(sphere, t);
596     _localBvhTree->accept(nearestPointVisitor);
597
598     if (nearestPointVisitor.empty())
599         return false;
600
601     // Have geometry in the range of maxDist
602     contact = nearestPointVisitor.getPoint();
603     linearVel = nearestPointVisitor.getLinearVelocity();
604     angularVel = nearestPointVisitor.getAngularVelocity();
605     material = nearestPointVisitor.getMaterial();
606     id = nearestPointVisitor.getId();
607     
608     return true;
609 }
610
611
612 class FGGroundCache::WireIntersector : public BVHVisitor {
613 public:
614     WireIntersector(const SGVec3d pt[4], const double& t) :
615         _linearVelocity(SGVec3d::zeros()),
616         _angularVelocity(SGVec3d::zeros()),
617         _wire(0),
618         _time(t)
619     {
620         // Build the two triangles spanning the area where the hook has moved
621         // during the past step.
622         _triangles[0].set(pt[0], pt[1], pt[2]);
623         _triangles[1].set(pt[0], pt[2], pt[3]);
624     }
625
626     virtual void apply(BVHGroup& leaf)
627     {
628         if (!_intersects(leaf.getBoundingSphere()))
629             return;
630
631         leaf.traverse(*this);
632     }
633     virtual void apply(BVHTransform& transform)
634     {
635         if (!_intersects(transform.getBoundingSphere()))
636             return;
637         
638         SGTriangled triangles[2] = { _triangles[0], _triangles[1] };
639         _triangles[0] = triangles[0].transform(transform.getToLocalTransform());
640         _triangles[1] = triangles[1].transform(transform.getToLocalTransform());
641         
642         transform.traverse(*this);
643         
644         if (_wire) {
645             _lineSegment = transform.lineSegmentToWorld(_lineSegment);
646             _linearVelocity = transform.vecToWorld(_linearVelocity);
647             _angularVelocity = transform.vecToWorld(_angularVelocity);
648         }
649         _triangles[0] = triangles[0];
650         _triangles[1] = triangles[1];
651     }
652     virtual void apply(BVHMotionTransform& transform)
653     {
654         if (!_intersects(transform.getBoundingSphere()))
655             return;
656         
657         SGMatrixd toLocal = transform.getToLocalTransform(_time);
658
659         SGTriangled triangles[2] = { _triangles[0], _triangles[1] };
660         _triangles[0] = triangles[0].transform(toLocal);
661         _triangles[1] = triangles[1].transform(toLocal);
662         
663         transform.traverse(*this);
664         
665         if (_wire) {
666             SGMatrixd toWorld = transform.getToWorldTransform(_time);
667             _linearVelocity
668                 += transform.getLinearVelocityAt(_lineSegment.getStart());
669             _angularVelocity += transform.getAngularVelocity();
670             _linearVelocity = toWorld.xformVec(_linearVelocity);
671             _angularVelocity = toWorld.xformVec(_angularVelocity);
672             _lineSegment = _lineSegment.transform(toWorld);
673         }
674         _triangles[0] = triangles[0];
675         _triangles[1] = triangles[1];
676     }
677     virtual void apply(BVHLineGeometry& node)
678     {
679         if (node.getType() != BVHLineGeometry::CarrierWire)
680             return;
681         SGLineSegmentd lineSegment(node.getLineSegment());
682         if (!_intersects(lineSegment))
683             return;
684
685         _lineSegment = lineSegment;
686         _linearVelocity = SGVec3d::zeros();
687         _angularVelocity = SGVec3d::zeros();
688         _wire = &node;
689     }
690     virtual void apply(BVHStaticGeometry& node)
691     { }
692     
693     virtual void apply(const BVHStaticBinary&, const BVHStaticData&) { }
694     virtual void apply(const BVHStaticTriangle&, const BVHStaticData&) { }
695
696     bool _intersects(const SGSphered& sphere) const
697     {
698         if (_wire)
699             return false;
700         if (intersects(_triangles[0], sphere))
701             return true;
702         if (intersects(_triangles[1], sphere))
703             return true;
704         return false;
705     }
706     bool _intersects(const SGLineSegmentd& lineSegment) const
707     {
708         if (_wire)
709             return false;
710         if (intersects(_triangles[0], lineSegment))
711             return true;
712         if (intersects(_triangles[1], lineSegment))
713             return true;
714         return false;
715     }
716     
717     const SGLineSegmentd& getLineSegment() const
718     { return _lineSegment; }
719     const SGVec3d& getLinearVelocity() const
720     { return _linearVelocity; }
721     const SGVec3d& getAngularVelocity() const
722     { return _angularVelocity; }
723     
724     const BVHLineGeometry* getWire() const
725     { return _wire; }
726     
727 private:
728     SGLineSegmentd _lineSegment;
729     SGVec3d _linearVelocity;
730     SGVec3d _angularVelocity;
731     const BVHLineGeometry* _wire;
732
733     SGTriangled _triangles[2];
734     double _time;
735 };
736
737 bool FGGroundCache::caught_wire(double t, const SGVec3d pt[4])
738 {
739     // Get the wire in question
740     WireIntersector wireIntersector(pt, t);
741     if (_localBvhTree)
742         _localBvhTree->accept(wireIntersector);
743     
744     _wire = wireIntersector.getWire();
745     return _wire;
746 }
747
748 class FGGroundCache::WireFinder : public BVHVisitor {
749 public:
750     WireFinder(const BVHLineGeometry* wire, const double& t) :
751         _wire(wire),
752         _time(t),
753         _lineSegment(SGVec3d::zeros(), SGVec3d::zeros()),
754         _linearVelocity(SGVec3d::zeros()),
755         _angularVelocity(SGVec3d::zeros()),
756         _haveLineSegment(false)
757     { }
758
759     virtual void apply(BVHGroup& leaf)
760     {
761         if (_haveLineSegment)
762             return;
763         leaf.traverse(*this);
764     }
765     virtual void apply(BVHTransform& transform)
766     {
767         if (_haveLineSegment)
768             return;
769
770         transform.traverse(*this);
771         
772         if (_haveLineSegment) {
773             _linearVelocity = transform.vecToWorld(_linearVelocity);
774             _angularVelocity = transform.vecToWorld(_angularVelocity);
775             _lineSegment = transform.lineSegmentToWorld(_lineSegment);
776         }
777     }
778     virtual void apply(BVHMotionTransform& transform)
779     {
780         if (_haveLineSegment)
781             return;
782
783         transform.traverse(*this);
784         
785         if (_haveLineSegment) {
786             SGMatrixd toWorld = transform.getToWorldTransform(_time);
787             _linearVelocity
788                 += transform.getLinearVelocityAt(_lineSegment.getStart());
789             _angularVelocity += transform.getAngularVelocity();
790             _linearVelocity = toWorld.xformVec(_linearVelocity);
791             _angularVelocity = toWorld.xformVec(_angularVelocity);
792             _lineSegment = _lineSegment.transform(toWorld);
793         }
794     }
795     virtual void apply(BVHLineGeometry& node)
796     {
797         if (_haveLineSegment)
798             return;
799         if (_wire != &node)
800             return;
801         if (node.getType() != BVHLineGeometry::CarrierWire)
802             return;
803         _lineSegment = SGLineSegmentd(node.getLineSegment());
804         _linearVelocity = SGVec3d::zeros();
805         _angularVelocity = SGVec3d::zeros();
806         _haveLineSegment = true;
807     }
808     virtual void apply(BVHStaticGeometry&) { }
809     
810     virtual void apply(const BVHStaticBinary&, const BVHStaticData&) { }
811     virtual void apply(const BVHStaticTriangle&, const BVHStaticData&) { }
812
813     const SGLineSegmentd& getLineSegment() const
814     { return _lineSegment; }
815     
816     bool getHaveLineSegment() const
817     { return _haveLineSegment; }
818
819     const SGVec3d& getLinearVelocity() const
820     { return _linearVelocity; }
821     const SGVec3d& getAngularVelocity() const
822     { return _angularVelocity; }
823
824 private:
825     const BVHLineGeometry* _wire;
826     double _time;
827
828     SGLineSegmentd _lineSegment;
829     SGVec3d _linearVelocity;
830     SGVec3d _angularVelocity;
831
832     bool _haveLineSegment;
833 };
834
835 bool FGGroundCache::get_wire_ends(double t, SGVec3d end[2], SGVec3d vel[2])
836 {
837     // Fast return if we do not have an active wire.
838     if (!_wire)
839         return false;
840
841     // Get the wire in question
842     WireFinder wireFinder(_wire, t);
843     if (_localBvhTree)
844         _localBvhTree->accept(wireFinder);
845     
846     if (!wireFinder.getHaveLineSegment())
847         return false;
848
849     // prepare the returns
850     end[0] = wireFinder.getLineSegment().getStart();
851     end[1] = wireFinder.getLineSegment().getEnd();
852
853     // The linear velocity is the one at the start of the line segment ...
854     vel[0] = wireFinder.getLinearVelocity();
855     // ... so the end point has the additional cross product.
856     vel[1] = wireFinder.getLinearVelocity();
857     vel[1] += cross(wireFinder.getAngularVelocity(),
858                     wireFinder.getLineSegment().getDirection());
859
860     return true;
861 }
862
863 void FGGroundCache::release_wire(void)
864 {
865     _wire = 0;
866 }