]> git.mxchange.org Git - flightgear.git/blob - src/FDM/groundcache.cxx
0d5f4e09274b8c3bf77ce4ec527a3a6c85ba8091
[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, rad))
249         return false;
250     _altitude = 0;
251
252     // If we have an active wire, get some more area into the groundcache
253     if (_wire)
254         rad = SGMiscd::max(200, rad);
255     
256     // Store the parameters we used to build up that cache.
257     reference_wgs84_point = pt;
258     reference_vehicle_radius = rad;
259     // Store the time reference used to compute movements of moving triangles.
260     cache_ref_time = ref_time;
261     
262     // Get a normalized down vector valid for the whole cache
263     SGQuatd hlToEc = SGQuatd::fromLonLat(geodPt);
264     down = hlToEc.rotate(SGVec3d(0, 0, 1));
265     
266     // Get the ground cache, that is a local collision tree of the environment
267     double endTime = cache_ref_time + 1; //FIXME??
268     CacheFill subtreeCollector(pt, rad, cache_ref_time, endTime);
269     globals->get_scenery()->get_scene_graph()->accept(subtreeCollector);
270     _localBvhTree = subtreeCollector.getBVHNode();
271
272     // Try to get a croase altitude value for the ground cache
273     SGLineSegmentd line(pt, pt + 2*reference_vehicle_radius*down);
274     simgear::BVHLineSegmentVisitor lineSegmentVisitor(line, ref_time);
275     if (_localBvhTree)
276         _localBvhTree->accept(lineSegmentVisitor);
277
278     // If this is successful, store this altitude for croase altitude values
279     if (!lineSegmentVisitor.empty()) {
280         SGGeod geodPt = SGGeod::fromCart(lineSegmentVisitor.getPoint());
281         _altitude = geodPt.getElevationM();
282         _material = lineSegmentVisitor.getMaterial();
283         found_ground = true;
284     } else {
285         // Else do a crude scene query for the current point
286         found_ground = globals->get_scenery()->
287             get_cart_elevation_m(pt, rad, _altitude, &_material);
288     }
289     
290     // Still not sucessful??
291     if (!found_ground)
292         SG_LOG(SG_FLIGHT, SG_WARN, "prepare_ground_cache(): trying to build "
293                "cache without any scenery below the aircraft");
294
295     return found_ground;
296 }
297
298 bool
299 FGGroundCache::is_valid(double& ref_time, SGVec3d& pt, double& rad)
300 {
301     pt = reference_wgs84_point;
302     rad = reference_vehicle_radius;
303     ref_time = cache_ref_time;
304     return found_ground;
305 }
306
307 class FGGroundCache::BodyFinder : public BVHVisitor {
308 public:
309     BodyFinder(BVHNode::Id id, const double& t) :
310         _id(id),
311         _bodyToWorld(SGMatrixd::unit()),
312         _linearVelocity(0, 0, 0),
313         _angularVelocity(0, 0, 0),
314         _time(t)
315     { }
316     
317     virtual void apply(BVHGroup& leaf)
318     {
319         if (_foundId)
320             return;
321         leaf.traverse(*this);
322     }
323     virtual void apply(BVHTransform& transform)
324     {
325         if (_foundId)
326             return;
327
328         transform.traverse(*this);
329         
330         if (_foundId) {
331             _linearVelocity = transform.vecToWorld(_linearVelocity);
332             _angularVelocity = transform.vecToWorld(_angularVelocity);
333             _bodyToWorld = transform.getToWorldTransform()*_bodyToWorld;
334         }
335     }
336     virtual void apply(BVHMotionTransform& transform)
337     {
338         if (_foundId)
339             return;
340
341         if (_id == transform.getId()) {
342             _foundId = true;
343             return;
344         }
345         
346         transform.traverse(*this);
347         
348         if (_foundId) {
349             SGMatrixd toWorld = transform.getToWorldTransform(_time);
350             SGVec3d referencePoint = _bodyToWorld.xformPt(SGVec3d::zeros());
351             _linearVelocity += transform.getLinearVelocityAt(referencePoint);
352             _angularVelocity += transform.getAngularVelocity();
353             _linearVelocity = toWorld.xformVec(_linearVelocity);
354             _angularVelocity = toWorld.xformVec(_angularVelocity);
355             _bodyToWorld = toWorld*_bodyToWorld;
356         }
357     }
358     virtual void apply(BVHLineGeometry& node) { }
359     virtual void apply(BVHStaticGeometry& node) { }
360     
361     virtual void apply(const BVHStaticBinary&, const BVHStaticData&) { }
362     virtual void apply(const BVHStaticTriangle&, const BVHStaticData&) { }
363     
364     const SGMatrixd& getBodyToWorld() const
365     { return _bodyToWorld; }
366     const SGVec3d& getLinearVelocity() const
367     { return _linearVelocity; }
368     const SGVec3d& getAngularVelocity() const
369     { return _angularVelocity; }
370     
371     bool empty() const
372     { return !_foundId; }
373     
374 protected:
375     simgear::BVHNode::Id _id;
376
377     SGMatrixd _bodyToWorld;
378
379     SGVec3d _linearVelocity;
380     SGVec3d _angularVelocity;
381     
382     bool _foundId;
383
384     double _time;
385 };
386
387 bool
388 FGGroundCache::get_body(double t, SGMatrixd& bodyToWorld, SGVec3d& linearVel,
389                         SGVec3d& angularVel, simgear::BVHNode::Id id)
390 {
391     // Get the transform matrix and velocities of a moving body with id at t.
392     if (!_localBvhTree)
393         return false;
394     BodyFinder bodyFinder(id, t);
395     _localBvhTree->accept(bodyFinder);
396     if (bodyFinder.empty())
397         return false;
398
399     bodyToWorld = bodyFinder.getBodyToWorld();
400     linearVel = bodyFinder.getLinearVelocity();
401     angularVel = bodyFinder.getAngularVelocity();
402
403     return true;
404 }
405
406 class FGGroundCache::CatapultFinder : public BVHVisitor {
407 public:
408     CatapultFinder(const SGSphered& sphere, const double& t) :
409         _sphere(sphere),
410         _time(t),
411         _haveLineSegment(false)
412     { }
413     
414     virtual void apply(BVHGroup& leaf)
415     {
416         if (!intersects(_sphere, leaf.getBoundingSphere()))
417             return;
418         leaf.traverse(*this);
419     }
420     virtual void apply(BVHTransform& transform)
421     {
422         if (!intersects(_sphere, transform.getBoundingSphere()))
423             return;
424         
425         SGSphered sphere = _sphere;
426         _sphere = transform.sphereToLocal(sphere);
427         bool haveLineSegment = _haveLineSegment;
428         _haveLineSegment = false;
429         
430         transform.traverse(*this);
431         
432         if (_haveLineSegment) {
433             _lineSegment = transform.lineSegmentToWorld(_lineSegment);
434             _linearVelocity = transform.vecToWorld(_linearVelocity);
435             _angularVelocity = transform.vecToWorld(_angularVelocity);
436         }
437         _haveLineSegment |= haveLineSegment;
438         _sphere.setCenter(sphere.getCenter());
439     }
440     virtual void apply(BVHMotionTransform& transform)
441     {
442         if (!intersects(_sphere, transform.getBoundingSphere()))
443             return;
444         
445         SGSphered sphere = _sphere;
446         _sphere = transform.sphereToLocal(sphere, _time);
447         bool haveLineSegment = _haveLineSegment;
448         _haveLineSegment = false;
449         
450         transform.traverse(*this);
451         
452         if (_haveLineSegment) {
453             SGMatrixd toWorld = transform.getToWorldTransform(_time);
454             _linearVelocity
455                 += transform.getLinearVelocityAt(_lineSegment.getStart());
456             _angularVelocity += transform.getAngularVelocity();
457             _linearVelocity = toWorld.xformVec(_linearVelocity);
458             _angularVelocity = toWorld.xformVec(_angularVelocity);
459             _lineSegment = _lineSegment.transform(toWorld);
460         }
461         _haveLineSegment |= haveLineSegment;
462         _sphere.setCenter(sphere.getCenter());
463     }
464     virtual void apply(BVHLineGeometry& node)
465     {
466         if (node.getType() != BVHLineGeometry::CarrierCatapult)
467             return;
468
469         SGLineSegmentd lineSegment(node.getLineSegment());
470         if (!intersects(_sphere, lineSegment))
471             return;
472
473         _lineSegment = lineSegment;
474         double dist = distSqr(lineSegment, getSphere().getCenter());
475         _sphere.setRadius(sqrt(dist));
476         _linearVelocity = SGVec3d::zeros();
477         _angularVelocity = SGVec3d::zeros();
478         _haveLineSegment = true;
479     }
480     virtual void apply(BVHStaticGeometry& node)
481     { }
482     
483     virtual void apply(const BVHStaticBinary&, const BVHStaticData&) { }
484     virtual void apply(const BVHStaticTriangle&, const BVHStaticData&) { }
485     
486     void setSphere(const SGSphered& sphere)
487     { _sphere = sphere; }
488     const SGSphered& getSphere() const
489     { return _sphere; }
490     
491     const SGLineSegmentd& getLineSegment() const
492     { return _lineSegment; }
493     const SGVec3d& getLinearVelocity() const
494     { return _linearVelocity; }
495     const SGVec3d& getAngularVelocity() const
496     { return _angularVelocity; }
497     
498     bool getHaveLineSegment() const
499     { return _haveLineSegment; }
500     
501 protected:
502     SGLineSegmentd _lineSegment;
503     SGVec3d _linearVelocity;
504     SGVec3d _angularVelocity;
505     
506     bool _haveLineSegment;
507
508     SGSphered _sphere;
509     double _time;
510 };
511
512 double
513 FGGroundCache::get_cat(double t, const SGVec3d& pt,
514                        SGVec3d end[2], SGVec3d vel[2])
515 {
516     double maxDistance = 1000;
517
518     // Get the wire in question
519     CatapultFinder catapultFinder(SGSphered(pt, maxDistance), t);
520     if (_localBvhTree)
521         _localBvhTree->accept(catapultFinder);
522     
523     if (!catapultFinder.getHaveLineSegment())
524         return maxDistance;
525
526     // prepare the returns
527     end[0] = catapultFinder.getLineSegment().getStart();
528     end[1] = catapultFinder.getLineSegment().getEnd();
529
530     // The linear velocity is the one at the start of the line segment ...
531     vel[0] = catapultFinder.getLinearVelocity();
532     // ... so the end point has the additional cross product.
533     vel[1] = catapultFinder.getLinearVelocity();
534     vel[1] += cross(catapultFinder.getAngularVelocity(),
535                     catapultFinder.getLineSegment().getDirection());
536
537     // Return the distance to the cat
538     return sqrt(distSqr(catapultFinder.getLineSegment(), pt));
539 }
540
541 bool
542 FGGroundCache::get_agl(double t, const SGVec3d& pt, SGVec3d& contact,
543                        SGVec3d& normal, SGVec3d& linearVel, SGVec3d& angularVel,
544                        simgear::BVHNode::Id& id, const SGMaterial*& material)
545 {
546     // Just set up a ground intersection query for the given point
547     SGLineSegmentd line(pt, pt + 10*reference_vehicle_radius*down);
548     simgear::BVHLineSegmentVisitor lineSegmentVisitor(line, t);
549     if (_localBvhTree)
550         _localBvhTree->accept(lineSegmentVisitor);
551
552     if (!lineSegmentVisitor.empty()) {
553         // Have an intersection
554         contact = lineSegmentVisitor.getPoint();
555         normal = lineSegmentVisitor.getNormal();
556         if (0 < dot(normal, down))
557             normal = -normal;
558         linearVel = lineSegmentVisitor.getLinearVelocity();
559         angularVel = lineSegmentVisitor.getAngularVelocity();
560         material = lineSegmentVisitor.getMaterial();
561         id = lineSegmentVisitor.getId();
562
563         return true;
564     } else {
565         // Whenever we did not have a ground triangle for the requested point,
566         // take the ground level we found during the current cache build.
567         // This is as good as what we had before for agl.
568         SGGeod geodPt = SGGeod::fromCart(pt);
569         geodPt.setElevationM(_altitude);
570         contact = SGVec3d::fromGeod(geodPt);
571         normal = -down;
572         linearVel = SGVec3d(0, 0, 0);
573         angularVel = SGVec3d(0, 0, 0);
574         material = _material;
575         id = 0;
576
577         return found_ground;
578     }
579 }
580
581
582 bool
583 FGGroundCache::get_nearest(double t, const SGVec3d& pt, double maxDist,
584                            SGVec3d& contact, SGVec3d& linearVel,
585                            SGVec3d& angularVel, simgear::BVHNode::Id& id,
586                            const SGMaterial*& material)
587 {
588     if (!_localBvhTree)
589         return false;
590
591     // Just set up a ground intersection query for the given point
592     SGSphered sphere(pt, maxDist);
593     simgear::BVHNearestPointVisitor nearestPointVisitor(sphere, t);
594     _localBvhTree->accept(nearestPointVisitor);
595
596     if (nearestPointVisitor.empty())
597         return false;
598
599     // Have geometry in the range of maxDist
600     contact = nearestPointVisitor.getPoint();
601     linearVel = nearestPointVisitor.getLinearVelocity();
602     angularVel = nearestPointVisitor.getAngularVelocity();
603     material = nearestPointVisitor.getMaterial();
604     id = nearestPointVisitor.getId();
605     
606     return true;
607 }
608
609
610 class FGGroundCache::WireIntersector : public BVHVisitor {
611 public:
612     WireIntersector(const SGVec3d pt[4], const double& t) :
613         _linearVelocity(SGVec3d::zeros()),
614         _angularVelocity(SGVec3d::zeros()),
615         _wire(0),
616         _time(t)
617     {
618         // Build the two triangles spanning the area where the hook has moved
619         // during the past step.
620         _triangles[0].set(pt[0], pt[1], pt[2]);
621         _triangles[1].set(pt[0], pt[2], pt[3]);
622     }
623
624     virtual void apply(BVHGroup& leaf)
625     {
626         if (!_intersects(leaf.getBoundingSphere()))
627             return;
628
629         leaf.traverse(*this);
630     }
631     virtual void apply(BVHTransform& transform)
632     {
633         if (!_intersects(transform.getBoundingSphere()))
634             return;
635         
636         SGTriangled triangles[2] = { _triangles[0], _triangles[1] };
637         _triangles[0] = triangles[0].transform(transform.getToLocalTransform());
638         _triangles[1] = triangles[1].transform(transform.getToLocalTransform());
639         
640         transform.traverse(*this);
641         
642         if (_wire) {
643             _lineSegment = transform.lineSegmentToWorld(_lineSegment);
644             _linearVelocity = transform.vecToWorld(_linearVelocity);
645             _angularVelocity = transform.vecToWorld(_angularVelocity);
646         }
647         _triangles[0] = triangles[0];
648         _triangles[1] = triangles[1];
649     }
650     virtual void apply(BVHMotionTransform& transform)
651     {
652         if (!_intersects(transform.getBoundingSphere()))
653             return;
654         
655         SGMatrixd toLocal = transform.getToLocalTransform(_time);
656
657         SGTriangled triangles[2] = { _triangles[0], _triangles[1] };
658         _triangles[0] = triangles[0].transform(toLocal);
659         _triangles[1] = triangles[1].transform(toLocal);
660         
661         transform.traverse(*this);
662         
663         if (_wire) {
664             SGMatrixd toWorld = transform.getToWorldTransform(_time);
665             _linearVelocity
666                 += transform.getLinearVelocityAt(_lineSegment.getStart());
667             _angularVelocity += transform.getAngularVelocity();
668             _linearVelocity = toWorld.xformVec(_linearVelocity);
669             _angularVelocity = toWorld.xformVec(_angularVelocity);
670             _lineSegment = _lineSegment.transform(toWorld);
671         }
672         _triangles[0] = triangles[0];
673         _triangles[1] = triangles[1];
674     }
675     virtual void apply(BVHLineGeometry& node)
676     {
677         if (node.getType() != BVHLineGeometry::CarrierWire)
678             return;
679         SGLineSegmentd lineSegment(node.getLineSegment());
680         if (!_intersects(lineSegment))
681             return;
682
683         _lineSegment = lineSegment;
684         _linearVelocity = SGVec3d::zeros();
685         _angularVelocity = SGVec3d::zeros();
686         _wire = &node;
687     }
688     virtual void apply(BVHStaticGeometry& node)
689     { }
690     
691     virtual void apply(const BVHStaticBinary&, const BVHStaticData&) { }
692     virtual void apply(const BVHStaticTriangle&, const BVHStaticData&) { }
693
694     bool _intersects(const SGSphered& sphere) const
695     {
696         if (_wire)
697             return false;
698         if (intersects(_triangles[0], sphere))
699             return true;
700         if (intersects(_triangles[1], sphere))
701             return true;
702         return false;
703     }
704     bool _intersects(const SGLineSegmentd& lineSegment) const
705     {
706         if (_wire)
707             return false;
708         if (intersects(_triangles[0], lineSegment))
709             return true;
710         if (intersects(_triangles[1], lineSegment))
711             return true;
712         return false;
713     }
714     
715     const SGLineSegmentd& getLineSegment() const
716     { return _lineSegment; }
717     const SGVec3d& getLinearVelocity() const
718     { return _linearVelocity; }
719     const SGVec3d& getAngularVelocity() const
720     { return _angularVelocity; }
721     
722     const BVHLineGeometry* getWire() const
723     { return _wire; }
724     
725 private:
726     SGLineSegmentd _lineSegment;
727     SGVec3d _linearVelocity;
728     SGVec3d _angularVelocity;
729     const BVHLineGeometry* _wire;
730
731     SGTriangled _triangles[2];
732     double _time;
733 };
734
735 bool FGGroundCache::caught_wire(double t, const SGVec3d pt[4])
736 {
737     // Get the wire in question
738     WireIntersector wireIntersector(pt, t);
739     if (_localBvhTree)
740         _localBvhTree->accept(wireIntersector);
741     
742     _wire = wireIntersector.getWire();
743     return _wire;
744 }
745
746 class FGGroundCache::WireFinder : public BVHVisitor {
747 public:
748     WireFinder(const BVHLineGeometry* wire, const double& t) :
749         _wire(wire),
750         _time(t),
751         _lineSegment(SGVec3d::zeros(), SGVec3d::zeros()),
752         _linearVelocity(SGVec3d::zeros()),
753         _angularVelocity(SGVec3d::zeros()),
754         _haveLineSegment(false)
755     { }
756
757     virtual void apply(BVHGroup& leaf)
758     {
759         if (_haveLineSegment)
760             return;
761         leaf.traverse(*this);
762     }
763     virtual void apply(BVHTransform& transform)
764     {
765         if (_haveLineSegment)
766             return;
767
768         transform.traverse(*this);
769         
770         if (_haveLineSegment) {
771             _linearVelocity = transform.vecToWorld(_linearVelocity);
772             _angularVelocity = transform.vecToWorld(_angularVelocity);
773             _lineSegment = transform.lineSegmentToWorld(_lineSegment);
774         }
775     }
776     virtual void apply(BVHMotionTransform& transform)
777     {
778         if (_haveLineSegment)
779             return;
780
781         transform.traverse(*this);
782         
783         if (_haveLineSegment) {
784             SGMatrixd toWorld = transform.getToWorldTransform(_time);
785             _linearVelocity
786                 += transform.getLinearVelocityAt(_lineSegment.getStart());
787             _angularVelocity += transform.getAngularVelocity();
788             _linearVelocity = toWorld.xformVec(_linearVelocity);
789             _angularVelocity = toWorld.xformVec(_angularVelocity);
790             _lineSegment = _lineSegment.transform(toWorld);
791         }
792     }
793     virtual void apply(BVHLineGeometry& node)
794     {
795         if (_haveLineSegment)
796             return;
797         if (_wire != &node)
798             return;
799         if (node.getType() != BVHLineGeometry::CarrierWire)
800             return;
801         _lineSegment = SGLineSegmentd(node.getLineSegment());
802         _linearVelocity = SGVec3d::zeros();
803         _angularVelocity = SGVec3d::zeros();
804         _haveLineSegment = true;
805     }
806     virtual void apply(BVHStaticGeometry&) { }
807     
808     virtual void apply(const BVHStaticBinary&, const BVHStaticData&) { }
809     virtual void apply(const BVHStaticTriangle&, const BVHStaticData&) { }
810
811     const SGLineSegmentd& getLineSegment() const
812     { return _lineSegment; }
813     
814     bool getHaveLineSegment() const
815     { return _haveLineSegment; }
816
817     const SGVec3d& getLinearVelocity() const
818     { return _linearVelocity; }
819     const SGVec3d& getAngularVelocity() const
820     { return _angularVelocity; }
821
822 private:
823     const BVHLineGeometry* _wire;
824     double _time;
825
826     SGLineSegmentd _lineSegment;
827     SGVec3d _linearVelocity;
828     SGVec3d _angularVelocity;
829
830     bool _haveLineSegment;
831 };
832
833 bool FGGroundCache::get_wire_ends(double t, SGVec3d end[2], SGVec3d vel[2])
834 {
835     // Fast return if we do not have an active wire.
836     if (!_wire)
837         return false;
838
839     // Get the wire in question
840     WireFinder wireFinder(_wire, t);
841     if (_localBvhTree)
842         _localBvhTree->accept(wireFinder);
843     
844     if (!wireFinder.getHaveLineSegment())
845         return false;
846
847     // prepare the returns
848     end[0] = wireFinder.getLineSegment().getStart();
849     end[1] = wireFinder.getLineSegment().getEnd();
850
851     // The linear velocity is the one at the start of the line segment ...
852     vel[0] = wireFinder.getLinearVelocity();
853     // ... so the end point has the additional cross product.
854     vel[1] = wireFinder.getLinearVelocity();
855     vel[1] += cross(wireFinder.getAngularVelocity(),
856                     wireFinder.getLineSegment().getDirection());
857
858     return true;
859 }
860
861 void FGGroundCache::release_wire(void)
862 {
863     _wire = 0;
864 }