2004-11-22 10:10:33 +00:00
|
|
|
// groundcache.cxx -- carries a small subset of the scenegraph near the vehicle
|
|
|
|
//
|
|
|
|
// Written by Mathias Froehlich, started Nov 2004.
|
|
|
|
//
|
|
|
|
// Copyright (C) 2004 Mathias Froehlich - Mathias.Froehlich@web.de
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
2006-02-21 01:16:04 +00:00
|
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
2004-11-22 10:10:33 +00:00
|
|
|
//
|
|
|
|
// $Id$
|
|
|
|
|
2006-02-18 13:58:09 +00:00
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
# include "config.h"
|
|
|
|
#endif
|
2004-11-22 10:10:33 +00:00
|
|
|
|
|
|
|
#include <float.h>
|
|
|
|
|
|
|
|
#include <plib/sg.h>
|
2006-10-29 19:30:21 +00:00
|
|
|
#include <osg/CullFace>
|
|
|
|
#include <osg/Drawable>
|
|
|
|
#include <osg/Geode>
|
|
|
|
#include <osg/Geometry>
|
|
|
|
#include <osg/TriangleFunctor>
|
2004-11-22 10:10:33 +00:00
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
#include <simgear/sg_inlines.h>
|
2004-11-22 10:10:33 +00:00
|
|
|
#include <simgear/constants.h>
|
|
|
|
#include <simgear/debug/logstream.hxx>
|
|
|
|
#include <simgear/math/sg_geodesy.hxx>
|
2006-06-11 13:34:18 +00:00
|
|
|
#include <simgear/scene/material/mat.hxx>
|
|
|
|
#include <simgear/scene/material/matlib.hxx>
|
2006-10-29 19:30:21 +00:00
|
|
|
#include <simgear/scene/util/SGNodeMasks.hxx>
|
2004-11-22 10:10:33 +00:00
|
|
|
|
|
|
|
#include <Main/globals.hxx>
|
|
|
|
#include <Scenery/scenery.hxx>
|
|
|
|
#include <Scenery/tilemgr.hxx>
|
|
|
|
#include <AIModel/AICarrier.hxx>
|
|
|
|
|
|
|
|
#include "flight.hxx"
|
|
|
|
#include "groundcache.hxx"
|
|
|
|
|
2006-11-21 18:25:36 +00:00
|
|
|
static inline bool
|
|
|
|
fgdRayTriangle(SGVec3d& x, const SGVec3d& point, const SGVec3d& dir,
|
|
|
|
const SGVec3d v[3])
|
|
|
|
{
|
|
|
|
double eps = 1e-4;
|
|
|
|
// Method based on the observation that we are looking for a
|
|
|
|
// point x that can be expressed in terms of the triangle points
|
|
|
|
// x = p_0 + \mu_1*(p_1 - p_0) + \mu_2*(p_2 - p_0)
|
|
|
|
// with 0 <= \mu_1, \mu_2 and \mu_1 + \mu_2 <= 1.
|
|
|
|
// OTOH it could be expressed in terms of the ray
|
|
|
|
// x = point + \lambda*dir
|
|
|
|
// Now we can compute \mu_i and \lambda.
|
|
|
|
// define
|
|
|
|
SGVec3d d1 = v[1] - v[0];
|
|
|
|
SGVec3d d2 = v[2] - v[0];
|
|
|
|
SGVec3d b = point - v[0];
|
|
|
|
|
|
|
|
// the vector in normal direction, but not normalized
|
|
|
|
SGVec3d d1crossd2 = cross(d1, d2);
|
|
|
|
|
|
|
|
double denom = -dot(dir, d1crossd2);
|
|
|
|
double signDenom = copysign(1, denom);
|
|
|
|
// return if paralell ??? FIXME what if paralell and in plane?
|
|
|
|
// may be we are ok below than anyway??
|
|
|
|
// if (SGMiscd::abs(denom) <= SGLimitsd::min())
|
|
|
|
// return false;
|
|
|
|
|
|
|
|
// Now \lambda would read
|
|
|
|
// lambda = 1/denom*dot(b, d1crossd2);
|
|
|
|
// To avoid an expensive division we multiply by |denom|
|
|
|
|
double lambdaDenom = signDenom*dot(b, d1crossd2);
|
|
|
|
if (lambdaDenom < 0)
|
|
|
|
return false;
|
|
|
|
// For line segment we would test against
|
|
|
|
// if (1 < lambda)
|
|
|
|
// return false;
|
|
|
|
// with the original lambda. The multiplied test would read
|
|
|
|
// if (absDenom < lambdaDenom)
|
|
|
|
// return false;
|
|
|
|
|
|
|
|
double absDenom = fabs(denom);
|
|
|
|
double absDenomEps = absDenom*eps;
|
|
|
|
|
|
|
|
SGVec3d bcrossr = cross(b, dir);
|
|
|
|
// double mu1 = 1/denom*dot(d2, bcrossr);
|
|
|
|
double mu1 = signDenom*dot(d2, bcrossr);
|
|
|
|
if (mu1 < -absDenomEps)
|
|
|
|
return false;
|
|
|
|
// double mu2 = -1/denom*dot(d1, bcrossr);
|
|
|
|
// if (mu2 < -eps)
|
|
|
|
// return false;
|
|
|
|
double mmu2 = signDenom*dot(d1, bcrossr);
|
|
|
|
if (mmu2 > absDenomEps)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (mu1 - mmu2 > absDenom + absDenomEps)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
x = point;
|
|
|
|
// if we have survived here it could only happen with denom == 0
|
|
|
|
// that the point is already in plane. Then return the origin ...
|
|
|
|
if (SGLimitsd::min() < absDenom)
|
|
|
|
x += (lambdaDenom/absDenom)*dir;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
static inline bool
|
|
|
|
fgdPointInTriangle( const SGVec3d& point, const SGVec3d tri[3] )
|
2005-05-30 08:48:27 +00:00
|
|
|
{
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d dif;
|
2005-05-30 08:48:27 +00:00
|
|
|
|
|
|
|
// Some tolerance in meters we accept a point to be outside of the triangle
|
|
|
|
// and still return that it is inside.
|
|
|
|
SGDfloat min, max;
|
|
|
|
// punt if outside bouding cube
|
|
|
|
SG_MIN_MAX3 ( min, max, tri[0][0], tri[1][0], tri[2][0] );
|
2006-11-21 18:25:36 +00:00
|
|
|
if( (point[0] < min) || (point[0] > max) )
|
2005-05-30 08:48:27 +00:00
|
|
|
return false;
|
|
|
|
dif[0] = max - min;
|
|
|
|
|
|
|
|
SG_MIN_MAX3 ( min, max, tri[0][1], tri[1][1], tri[2][1] );
|
2006-11-21 18:25:36 +00:00
|
|
|
if( (point[1] < min) || (point[1] > max) )
|
2005-05-30 08:48:27 +00:00
|
|
|
return false;
|
|
|
|
dif[1] = max - min;
|
|
|
|
|
|
|
|
SG_MIN_MAX3 ( min, max, tri[0][2], tri[1][2], tri[2][2] );
|
2006-11-21 18:25:36 +00:00
|
|
|
if( (point[2] < min) || (point[2] > max) )
|
2005-05-30 08:48:27 +00:00
|
|
|
return false;
|
|
|
|
dif[2] = max - min;
|
|
|
|
|
|
|
|
// drop the smallest dimension so we only have to work in 2d.
|
|
|
|
SGDfloat min_dim = SG_MIN3 (dif[0], dif[1], dif[2]);
|
|
|
|
SGDfloat x1, y1, x2, y2, x3, y3, rx, ry;
|
|
|
|
if ( fabs(min_dim-dif[0]) <= DBL_EPSILON ) {
|
|
|
|
// x is the smallest dimension
|
|
|
|
x1 = point[1];
|
|
|
|
y1 = point[2];
|
|
|
|
x2 = tri[0][1];
|
|
|
|
y2 = tri[0][2];
|
|
|
|
x3 = tri[1][1];
|
|
|
|
y3 = tri[1][2];
|
|
|
|
rx = tri[2][1];
|
|
|
|
ry = tri[2][2];
|
|
|
|
} else if ( fabs(min_dim-dif[1]) <= DBL_EPSILON ) {
|
|
|
|
// y is the smallest dimension
|
|
|
|
x1 = point[0];
|
|
|
|
y1 = point[2];
|
|
|
|
x2 = tri[0][0];
|
|
|
|
y2 = tri[0][2];
|
|
|
|
x3 = tri[1][0];
|
|
|
|
y3 = tri[1][2];
|
|
|
|
rx = tri[2][0];
|
|
|
|
ry = tri[2][2];
|
|
|
|
} else if ( fabs(min_dim-dif[2]) <= DBL_EPSILON ) {
|
|
|
|
// z is the smallest dimension
|
|
|
|
x1 = point[0];
|
|
|
|
y1 = point[1];
|
|
|
|
x2 = tri[0][0];
|
|
|
|
y2 = tri[0][1];
|
|
|
|
x3 = tri[1][0];
|
|
|
|
y3 = tri[1][1];
|
|
|
|
rx = tri[2][0];
|
|
|
|
ry = tri[2][1];
|
|
|
|
} else {
|
|
|
|
// all dimensions are really small so lets call it close
|
|
|
|
// enough and return a successful match
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if intersection point is on the same side of p1 <-> p2 as p3
|
|
|
|
SGDfloat tmp = (y2 - y3);
|
|
|
|
SGDfloat tmpn = (x2 - x3);
|
|
|
|
int side1 = SG_SIGN (tmp * (rx - x3) + (y3 - ry) * tmpn);
|
2006-11-21 18:25:36 +00:00
|
|
|
int side2 = SG_SIGN (tmp * (x1 - x3) + (y3 - y1) * tmpn);
|
2005-05-30 08:48:27 +00:00
|
|
|
if ( side1 != side2 ) {
|
|
|
|
// printf("failed side 1 check\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if intersection point is on correct side of p2 <-> p3 as p1
|
|
|
|
tmp = (y3 - ry);
|
|
|
|
tmpn = (x3 - rx);
|
|
|
|
side1 = SG_SIGN (tmp * (x2 - rx) + (ry - y2) * tmpn);
|
2006-11-21 18:25:36 +00:00
|
|
|
side2 = SG_SIGN (tmp * (x1 - rx) + (ry - y1) * tmpn);
|
2005-05-30 08:48:27 +00:00
|
|
|
if ( side1 != side2 ) {
|
|
|
|
// printf("failed side 2 check\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if intersection point is on correct side of p1 <-> p3 as p2
|
|
|
|
tmp = (y2 - ry);
|
|
|
|
tmpn = (x2 - rx);
|
|
|
|
side1 = SG_SIGN (tmp * (x3 - rx) + (ry - y3) * tmpn);
|
2006-11-21 18:25:36 +00:00
|
|
|
side2 = SG_SIGN (tmp * (x1 - rx) + (ry - y1) * tmpn);
|
2005-05-30 08:48:27 +00:00
|
|
|
if ( side1 != side2 ) {
|
|
|
|
// printf("failed side 3 check\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test if the line given by the point on the line pt_on_line and the
|
|
|
|
// line direction dir intersects the sphere sp.
|
|
|
|
// Adapted from plib.
|
|
|
|
static inline bool
|
2006-10-29 19:30:21 +00:00
|
|
|
fgdIsectSphereInfLine(const SGVec3d& sphereCenter, double radius,
|
|
|
|
const SGVec3d& pt_on_line, const SGVec3d& dir)
|
2005-05-30 08:48:27 +00:00
|
|
|
{
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d r = sphereCenter - pt_on_line;
|
|
|
|
double projectedDistance = dot(r, dir);
|
|
|
|
double dist = dot(r, r) - projectedDistance * projectedDistance;
|
2005-05-30 08:48:27 +00:00
|
|
|
return dist < radius*radius;
|
|
|
|
}
|
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
template<typename T>
|
|
|
|
class SGExtendedTriangleFunctor : public osg::TriangleFunctor<T> {
|
|
|
|
public:
|
|
|
|
// Ok, to be complete we should also implement the indexed variants
|
|
|
|
// For now this one appears to be enough ...
|
|
|
|
void drawArrays(GLenum mode, GLint first, GLsizei count)
|
|
|
|
{
|
|
|
|
if (_vertexArrayPtr==0 || count==0) return;
|
|
|
|
|
|
|
|
const osg::Vec3* vlast;
|
|
|
|
const osg::Vec3* vptr;
|
|
|
|
switch(mode) {
|
|
|
|
case(GL_LINES):
|
|
|
|
vlast = &_vertexArrayPtr[first+count];
|
|
|
|
for(vptr=&_vertexArrayPtr[first];vptr<vlast;vptr+=2)
|
|
|
|
this->operator()(*(vptr),*(vptr+1),_treatVertexDataAsTemporary);
|
|
|
|
break;
|
|
|
|
case(GL_LINE_STRIP):
|
|
|
|
vlast = &_vertexArrayPtr[first+count-1];
|
|
|
|
for(vptr=&_vertexArrayPtr[first];vptr<vlast;++vptr)
|
|
|
|
this->operator()(*(vptr),*(vptr+1),_treatVertexDataAsTemporary);
|
|
|
|
break;
|
|
|
|
case(GL_LINE_LOOP):
|
|
|
|
vlast = &_vertexArrayPtr[first+count-1];
|
|
|
|
for(vptr=&_vertexArrayPtr[first];vptr<vlast;++vptr)
|
|
|
|
this->operator()(*(vptr),*(vptr+1),_treatVertexDataAsTemporary);
|
|
|
|
this->operator()(_vertexArrayPtr[first+count-1],
|
|
|
|
_vertexArrayPtr[first],_treatVertexDataAsTemporary);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
osg::TriangleFunctor<T>::drawArrays(mode, first, count);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
protected:
|
|
|
|
using osg::TriangleFunctor<T>::_vertexArrayPtr;
|
|
|
|
using osg::TriangleFunctor<T>::_treatVertexDataAsTemporary;
|
|
|
|
};
|
2004-12-08 14:48:06 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
class GroundCacheFillVisitor : public osg::NodeVisitor {
|
|
|
|
public:
|
2004-11-22 10:10:33 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
/// class to just redirect triangles to the GroundCacheFillVisitor
|
|
|
|
class GroundCacheFill {
|
|
|
|
public:
|
|
|
|
void setGroundCacheFillVisitor(GroundCacheFillVisitor* gcfv)
|
|
|
|
{ mGroundCacheFillVisitor = gcfv; }
|
|
|
|
|
|
|
|
void operator () (const osg::Vec3& v1, const osg::Vec3& v2,
|
|
|
|
const osg::Vec3& v3, bool)
|
|
|
|
{ mGroundCacheFillVisitor->addTriangle(v1, v2, v3); }
|
|
|
|
|
|
|
|
void operator () (const osg::Vec3& v1, const osg::Vec3& v2, bool)
|
|
|
|
{ mGroundCacheFillVisitor->addLine(v1, v2); }
|
|
|
|
|
|
|
|
private:
|
|
|
|
GroundCacheFillVisitor* mGroundCacheFillVisitor;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
GroundCacheFillVisitor(FGGroundCache* groundCache,
|
|
|
|
const SGVec3d& down,
|
|
|
|
const SGVec3d& cacheReference,
|
|
|
|
double cacheRadius,
|
|
|
|
double wireCacheRadius) :
|
|
|
|
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN),
|
|
|
|
mGroundCache(groundCache)
|
|
|
|
{
|
|
|
|
setTraversalMask(SG_NODEMASK_TERRAIN_BIT);
|
|
|
|
mDown = down;
|
2006-11-08 20:46:07 +00:00
|
|
|
mLocalDown = down;
|
2006-10-29 19:30:21 +00:00
|
|
|
sphIsec = true;
|
|
|
|
mBackfaceCulling = false;
|
|
|
|
mCacheReference = cacheReference;
|
2006-11-08 20:46:07 +00:00
|
|
|
mLocalCacheReference = cacheReference;
|
2006-10-29 19:30:21 +00:00
|
|
|
mCacheRadius = cacheRadius;
|
|
|
|
mWireCacheRadius = wireCacheRadius;
|
|
|
|
|
|
|
|
mTriangleFunctor.setGroundCacheFillVisitor(this);
|
|
|
|
|
|
|
|
mGroundProperty.wire_id = -1;
|
|
|
|
mGroundProperty.vel = SGVec3d(0, 0, 0);
|
|
|
|
mGroundProperty.rot = SGVec3d(0, 0, 0);
|
|
|
|
mGroundProperty.pivot = SGVec3d(0, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void updateCullMode(osg::StateSet* stateSet)
|
|
|
|
{
|
|
|
|
if (!stateSet)
|
|
|
|
return;
|
|
|
|
|
|
|
|
osg::StateAttribute* stateAttribute;
|
|
|
|
stateAttribute = stateSet->getAttribute(osg::StateAttribute::CULLFACE);
|
|
|
|
if (!stateAttribute)
|
|
|
|
return;
|
|
|
|
osg::CullFace* cullFace = static_cast<osg::CullFace*>(stateAttribute);
|
|
|
|
mBackfaceCulling = cullFace->getMode() == osg::CullFace::BACK;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool enterBoundingSphere(const osg::BoundingSphere& bs)
|
|
|
|
{
|
|
|
|
if (!bs.valid())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
SGVec3d cntr(osg::Vec3d(bs.center())*mLocalToGlobal);
|
|
|
|
double rc = bs.radius() + mCacheRadius;
|
|
|
|
// Ok, this node might intersect the cache. Visit it in depth.
|
|
|
|
double centerDist2 = distSqr(mCacheReference, cntr);
|
|
|
|
if (centerDist2 < rc*rc) {
|
|
|
|
sphIsec = true;
|
|
|
|
} else {
|
|
|
|
// Check if the down direction touches the bounding sphere of the node
|
|
|
|
// if so, do at least croase agl computations.
|
|
|
|
// Ther other thing is that we must check if we are in range of
|
|
|
|
// cats or wires
|
|
|
|
double rw = bs.radius() + mWireCacheRadius;
|
|
|
|
if (rw*rw < centerDist2 &&
|
|
|
|
!fgdIsectSphereInfLine(cntr, bs.radius(), mCacheReference, mDown))
|
|
|
|
return false;
|
|
|
|
sphIsec = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool enterNode(osg::Node& node)
|
|
|
|
{
|
|
|
|
if (!enterBoundingSphere(node.getBound()))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
updateCullMode(node.getStateSet());
|
|
|
|
|
|
|
|
FGGroundCache::GroundProperty& gp = mGroundProperty;
|
|
|
|
// get some material information for use in the gear model
|
|
|
|
gp.material = globals->get_matlib()->findMaterial(&node);
|
|
|
|
if (gp.material) {
|
|
|
|
gp.type = gp.material->get_solid() ? FGInterface::Solid : FGInterface::Water;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
osg::Referenced* base = node.getUserData();
|
|
|
|
if (!base)
|
|
|
|
return true;
|
|
|
|
FGAICarrierHardware *ud =
|
|
|
|
dynamic_cast<FGAICarrierHardware*>(base);
|
|
|
|
if (!ud)
|
|
|
|
return true;
|
|
|
|
|
2004-11-26 10:24:48 +00:00
|
|
|
switch (ud->type) {
|
|
|
|
case FGAICarrierHardware::Wire:
|
2005-05-30 08:48:27 +00:00
|
|
|
gp.type = FGInterface::Wire;
|
|
|
|
gp.wire_id = ud->id;
|
2004-11-26 10:24:48 +00:00
|
|
|
break;
|
|
|
|
case FGAICarrierHardware::Catapult:
|
2005-05-30 08:48:27 +00:00
|
|
|
gp.type = FGInterface::Catapult;
|
2004-11-26 10:24:48 +00:00
|
|
|
break;
|
|
|
|
default:
|
2005-05-30 08:48:27 +00:00
|
|
|
gp.type = FGInterface::Solid;
|
2004-11-26 10:24:48 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Copy the velocity from the carrier class.
|
2006-11-08 20:46:07 +00:00
|
|
|
ud->carrier->getVelocityWrtEarth(gp.vel, gp.rot, gp.pivot);
|
2006-10-29 19:30:21 +00:00
|
|
|
|
|
|
|
return true;
|
2004-11-26 10:24:48 +00:00
|
|
|
}
|
2004-11-22 10:10:33 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
void fillWith(osg::Drawable* drawable)
|
|
|
|
{
|
|
|
|
bool oldSphIsec = sphIsec;
|
|
|
|
if (!enterBoundingSphere(drawable->getBound()))
|
|
|
|
return;
|
2006-06-11 13:34:18 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
bool oldBackfaceCulling = mBackfaceCulling;
|
|
|
|
updateCullMode(drawable->getStateSet());
|
|
|
|
|
|
|
|
drawable->accept(mTriangleFunctor);
|
|
|
|
|
|
|
|
mBackfaceCulling = oldBackfaceCulling;
|
|
|
|
sphIsec = oldSphIsec;
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
virtual void apply(osg::Geode& geode)
|
|
|
|
{
|
|
|
|
bool oldBackfaceCulling = mBackfaceCulling;
|
|
|
|
bool oldSphIsec = sphIsec;
|
|
|
|
FGGroundCache::GroundProperty oldGp = mGroundProperty;
|
|
|
|
if (!enterNode(geode))
|
|
|
|
return;
|
|
|
|
|
|
|
|
for(unsigned i = 0; i < geode.getNumDrawables(); ++i)
|
|
|
|
fillWith(geode.getDrawable(i));
|
|
|
|
sphIsec = oldSphIsec;
|
|
|
|
mGroundProperty = oldGp;
|
|
|
|
mBackfaceCulling = oldBackfaceCulling;
|
|
|
|
}
|
2005-05-30 08:48:27 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
virtual void apply(osg::Group& group)
|
|
|
|
{
|
|
|
|
bool oldBackfaceCulling = mBackfaceCulling;
|
|
|
|
bool oldSphIsec = sphIsec;
|
|
|
|
FGGroundCache::GroundProperty oldGp = mGroundProperty;
|
|
|
|
if (!enterNode(group))
|
|
|
|
return;
|
|
|
|
traverse(group);
|
|
|
|
sphIsec = oldSphIsec;
|
|
|
|
mBackfaceCulling = oldBackfaceCulling;
|
|
|
|
mGroundProperty = oldGp;
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
virtual void apply(osg::Transform& transform)
|
|
|
|
{
|
2006-11-07 06:09:36 +00:00
|
|
|
if (!enterNode(transform))
|
|
|
|
return;
|
|
|
|
bool oldBackfaceCulling = mBackfaceCulling;
|
|
|
|
bool oldSphIsec = sphIsec;
|
|
|
|
FGGroundCache::GroundProperty oldGp = mGroundProperty;
|
2006-10-29 19:30:21 +00:00
|
|
|
/// transform the caches center to local coords
|
|
|
|
osg::Matrix oldLocalToGlobal = mLocalToGlobal;
|
2006-11-08 20:46:07 +00:00
|
|
|
osg::Matrix oldGlobalToLocal = mGlobalToLocal;
|
2006-10-29 19:30:21 +00:00
|
|
|
transform.computeLocalToWorldMatrix(mLocalToGlobal, this);
|
2006-11-08 20:46:07 +00:00
|
|
|
transform.computeWorldToLocalMatrix(mGlobalToLocal, this);
|
|
|
|
|
|
|
|
SGVec3d oldLocalCacheReference = mLocalCacheReference;
|
|
|
|
mLocalCacheReference.osg() = mCacheReference.osg()*mGlobalToLocal;
|
|
|
|
SGVec3d oldLocalDown = mLocalDown;
|
|
|
|
mLocalDown.osg() = osg::Matrixd::transform3x3(mDown.osg(), mGlobalToLocal);
|
2006-10-29 19:30:21 +00:00
|
|
|
|
|
|
|
// walk the children
|
2006-11-07 06:09:36 +00:00
|
|
|
traverse(transform);
|
2005-05-30 08:48:27 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
// Restore that one
|
2006-11-08 20:46:07 +00:00
|
|
|
mLocalDown = oldLocalDown;
|
|
|
|
mLocalCacheReference = oldLocalCacheReference;
|
2006-10-29 19:30:21 +00:00
|
|
|
mLocalToGlobal = oldLocalToGlobal;
|
2006-11-08 20:46:07 +00:00
|
|
|
mGlobalToLocal = oldGlobalToLocal;
|
2006-11-07 06:09:36 +00:00
|
|
|
sphIsec = oldSphIsec;
|
|
|
|
mBackfaceCulling = oldBackfaceCulling;
|
|
|
|
mGroundProperty = oldGp;
|
2006-10-29 19:30:21 +00:00
|
|
|
}
|
2006-11-07 06:09:36 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
void addTriangle(const osg::Vec3& v1, const osg::Vec3& v2,
|
|
|
|
const osg::Vec3& v3)
|
|
|
|
{
|
2006-11-08 20:46:07 +00:00
|
|
|
SGVec3d v[3] = {
|
|
|
|
SGVec3d(v1),
|
|
|
|
SGVec3d(v2),
|
|
|
|
SGVec3d(v3)
|
|
|
|
};
|
|
|
|
|
|
|
|
// a bounding sphere in the node local system
|
|
|
|
SGVec3d boundCenter = (1.0/3)*(v[0] + v[1] + v[2]);
|
|
|
|
#if 0
|
|
|
|
double boundRadius = std::max(norm1(v[0] - boundCenter),
|
|
|
|
norm1(v[1] - boundCenter));
|
|
|
|
boundRadius = std::max(boundRadius, norm1(v[2] - boundCenter));
|
|
|
|
// Ok, we take the 1-norm instead of the expensive 2 norm.
|
|
|
|
// Therefore we need that scaling factor - roughly sqrt(3)
|
|
|
|
boundRadius = 1.733*boundRadius;
|
|
|
|
#else
|
|
|
|
double boundRadius = std::max(distSqr(v[0], boundCenter),
|
|
|
|
distSqr(v[1], boundCenter));
|
|
|
|
boundRadius = std::max(boundRadius, distSqr(v[2], boundCenter));
|
|
|
|
boundRadius = sqrt(boundRadius);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// if we are not in the downward cylinder bail out
|
|
|
|
if (!fgdIsectSphereInfLine(boundCenter, boundRadius + mCacheRadius,
|
|
|
|
mLocalCacheReference, mLocalDown))
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
// The normal and plane in the node local coordinate system
|
|
|
|
SGVec3d n = normalize(cross(v[1] - v[0], v[2] - v[0]));
|
|
|
|
if (0 < dot(mLocalDown, n)) {
|
2006-10-29 19:30:21 +00:00
|
|
|
if (mBackfaceCulling) {
|
2005-05-30 08:48:27 +00:00
|
|
|
// Surface points downwards, ignore for altitude computations.
|
2006-10-29 19:30:21 +00:00
|
|
|
return;
|
2006-11-21 18:25:36 +00:00
|
|
|
} else {
|
2006-11-08 20:46:07 +00:00
|
|
|
n = -n;
|
2006-11-21 18:25:36 +00:00
|
|
|
std::swap(v[1], v[2]);
|
|
|
|
}
|
2005-05-30 08:48:27 +00:00
|
|
|
}
|
2006-11-08 20:46:07 +00:00
|
|
|
|
|
|
|
// Only check if the triangle is in the cache sphere if the plane
|
|
|
|
// containing the triangle is near enough
|
|
|
|
if (sphIsec && fabs(dot(n, v[0] - mLocalCacheReference)) < mCacheRadius) {
|
|
|
|
// Check if the sphere around the vehicle intersects the sphere
|
|
|
|
// around that triangle. If so, put that triangle into the cache.
|
|
|
|
double r2 = boundRadius + mCacheRadius;
|
|
|
|
if (distSqr(boundCenter, mLocalCacheReference) < r2*r2) {
|
|
|
|
FGGroundCache::Triangle t;
|
|
|
|
for (unsigned i = 0; i < 3; ++i)
|
|
|
|
t.vertices[i].osg() = v[i].osg()*mLocalToGlobal;
|
|
|
|
t.boundCenter.osg() = boundCenter.osg()*mLocalToGlobal;
|
|
|
|
t.boundRadius = boundRadius;
|
|
|
|
|
|
|
|
SGVec3d tmp;
|
|
|
|
tmp.osg() = osg::Matrixd::transform3x3(n.osg(), mLocalToGlobal);
|
|
|
|
t.plane = SGVec4d(tmp[0], tmp[1], tmp[2], -dot(tmp, t.vertices[0]));
|
|
|
|
t.velocity = mGroundProperty.vel;
|
|
|
|
t.rotation = mGroundProperty.rot;
|
|
|
|
t.rotation_pivot = mGroundProperty.pivot - mGroundCache->cache_center;
|
|
|
|
t.type = mGroundProperty.type;
|
2006-12-27 10:47:10 +00:00
|
|
|
t.material=mGroundProperty.material;
|
2006-11-08 20:46:07 +00:00
|
|
|
mGroundCache->triangles.push_back(t);
|
|
|
|
}
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// In case the cache is empty, we still provide agl computations.
|
|
|
|
// But then we use the old way of having a fixed elevation value for
|
|
|
|
// the whole lifetime of this cache.
|
2006-11-08 20:46:07 +00:00
|
|
|
SGVec4d plane = SGVec4d(n[0], n[1], n[2], -dot(n, v[0]));
|
|
|
|
SGVec3d isectpoint;
|
2006-11-21 18:25:36 +00:00
|
|
|
|
|
|
|
if (fgdRayTriangle(isectpoint, mLocalCacheReference, mLocalDown, v)) {
|
|
|
|
mGroundCache->found_ground = true;
|
|
|
|
isectpoint.osg() = isectpoint.osg()*mLocalToGlobal;
|
|
|
|
isectpoint += mGroundCache->cache_center;
|
|
|
|
double this_radius = length(isectpoint);
|
|
|
|
if (mGroundCache->ground_radius < this_radius)
|
|
|
|
mGroundCache->ground_radius = this_radius;
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
|
|
|
}
|
2006-11-08 20:46:07 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
void addLine(const osg::Vec3& v1, const osg::Vec3& v2)
|
|
|
|
{
|
2006-11-08 20:46:07 +00:00
|
|
|
SGVec3d gv1(osg::Vec3d(v1)*mLocalToGlobal);
|
|
|
|
SGVec3d gv2(osg::Vec3d(v2)*mLocalToGlobal);
|
2006-10-29 19:30:21 +00:00
|
|
|
|
|
|
|
SGVec3d boundCenter = 0.5*(gv1 + gv2);
|
|
|
|
double boundRadius = length(gv1 - boundCenter);
|
|
|
|
|
|
|
|
if (distSqr(boundCenter, mCacheReference)
|
|
|
|
< (boundRadius + mWireCacheRadius)*(boundRadius + mWireCacheRadius) ) {
|
|
|
|
if (mGroundProperty.type == FGInterface::Wire) {
|
|
|
|
FGGroundCache::Wire wire;
|
|
|
|
wire.ends[0] = gv1;
|
|
|
|
wire.ends[1] = gv2;
|
|
|
|
wire.velocity = mGroundProperty.vel;
|
|
|
|
wire.rotation = mGroundProperty.rot;
|
|
|
|
wire.rotation_pivot = mGroundProperty.pivot - mGroundCache->cache_center;
|
|
|
|
wire.wire_id = mGroundProperty.wire_id;
|
|
|
|
|
|
|
|
mGroundCache->wires.push_back(wire);
|
|
|
|
}
|
|
|
|
if (mGroundProperty.type == FGInterface::Catapult) {
|
|
|
|
FGGroundCache::Catapult cat;
|
|
|
|
// Trick to get the ends in the right order.
|
|
|
|
// Use the x axis in the original coordinate system. Choose the
|
|
|
|
// most negative x-axis as the one pointing forward
|
|
|
|
if (v1[0] < v2[0]) {
|
|
|
|
cat.start = gv1;
|
|
|
|
cat.end = gv2;
|
|
|
|
} else {
|
|
|
|
cat.start = gv2;
|
|
|
|
cat.end = gv1;
|
|
|
|
}
|
|
|
|
cat.velocity = mGroundProperty.vel;
|
|
|
|
cat.rotation = mGroundProperty.rot;
|
|
|
|
cat.rotation_pivot = mGroundProperty.pivot - mGroundCache->cache_center;
|
|
|
|
|
|
|
|
mGroundCache->catapults.push_back(cat);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SGExtendedTriangleFunctor<GroundCacheFill> mTriangleFunctor;
|
|
|
|
FGGroundCache* mGroundCache;
|
|
|
|
SGVec3d mCacheReference;
|
|
|
|
double mCacheRadius;
|
|
|
|
double mWireCacheRadius;
|
|
|
|
osg::Matrix mLocalToGlobal;
|
2006-11-08 20:46:07 +00:00
|
|
|
osg::Matrix mGlobalToLocal;
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d mDown;
|
2006-11-08 20:46:07 +00:00
|
|
|
SGVec3d mLocalDown;
|
|
|
|
SGVec3d mLocalCacheReference;
|
2006-10-29 19:30:21 +00:00
|
|
|
bool sphIsec;
|
|
|
|
bool mBackfaceCulling;
|
|
|
|
FGGroundCache::GroundProperty mGroundProperty;
|
|
|
|
};
|
|
|
|
|
|
|
|
FGGroundCache::FGGroundCache()
|
|
|
|
{
|
|
|
|
cache_center = SGVec3d(0, 0, 0);
|
|
|
|
ground_radius = 0.0;
|
|
|
|
cache_ref_time = 0.0;
|
|
|
|
wire_id = 0;
|
|
|
|
reference_wgs84_point = SGVec3d(0, 0, 0);
|
|
|
|
reference_vehicle_radius = 0.0;
|
|
|
|
found_ground = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
FGGroundCache::~FGGroundCache()
|
|
|
|
{
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
inline void
|
|
|
|
FGGroundCache::velocityTransformTriangle(double dt,
|
|
|
|
FGGroundCache::Triangle& dst,
|
|
|
|
const FGGroundCache::Triangle& src)
|
2004-11-22 10:10:33 +00:00
|
|
|
{
|
2006-10-29 19:30:21 +00:00
|
|
|
dst = src;
|
2004-11-22 10:10:33 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
if (fabs(dt*dot(src.velocity, src.velocity)) < SGLimitsd::epsilon())
|
|
|
|
return;
|
2005-05-30 08:48:27 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
|
|
SGVec3d pivotoff = src.vertices[i] - src.rotation_pivot;
|
|
|
|
dst.vertices[i] += dt*(src.velocity + cross(src.rotation, pivotoff));
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
2006-10-29 19:30:21 +00:00
|
|
|
|
|
|
|
// Transform the plane equation
|
|
|
|
SGVec3d pivotoff, vel;
|
|
|
|
sgdSubVec3(pivotoff.sg(), dst.plane.sg(), src.rotation_pivot.sg());
|
|
|
|
vel = src.velocity + cross(src.rotation, pivotoff);
|
|
|
|
dst.plane[3] += dt*sgdScalarProductVec3(dst.plane.sg(), vel.sg());
|
|
|
|
|
|
|
|
dst.boundCenter += dt*src.velocity;
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2006-10-29 19:30:21 +00:00
|
|
|
FGGroundCache::prepare_ground_cache(double ref_time, const SGVec3d& pt,
|
2005-05-30 08:48:27 +00:00
|
|
|
double rad)
|
2004-11-22 10:10:33 +00:00
|
|
|
{
|
|
|
|
// Empty cache.
|
|
|
|
ground_radius = 0.0;
|
|
|
|
found_ground = false;
|
2005-05-30 08:48:27 +00:00
|
|
|
triangles.resize(0);
|
|
|
|
catapults.resize(0);
|
|
|
|
wires.resize(0);
|
2004-11-22 10:10:33 +00:00
|
|
|
|
|
|
|
// Store the parameters we used to build up that cache.
|
2006-10-29 19:30:21 +00:00
|
|
|
reference_wgs84_point = pt;
|
2004-11-22 10:10:33 +00:00
|
|
|
reference_vehicle_radius = rad;
|
|
|
|
// Store the time reference used to compute movements of moving triangles.
|
|
|
|
cache_ref_time = ref_time;
|
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
// Get a normalized down vector valid for the whole cache
|
|
|
|
SGQuatd hlToEc = SGQuatd::fromLonLat(SGGeod::fromCart(pt));
|
|
|
|
down = hlToEc.rotate(SGVec3d(0, 0, 1));
|
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
// Decide where we put the scenery center.
|
2006-07-27 16:36:22 +00:00
|
|
|
SGVec3d old_cntr = globals->get_scenery()->get_center();
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d cntr(pt);
|
2005-12-04 10:43:49 +00:00
|
|
|
// Only move the cache center if it is unacceptable far away.
|
2006-07-27 16:36:22 +00:00
|
|
|
if (40*40 < distSqr(old_cntr, cntr))
|
2005-05-30 08:48:27 +00:00
|
|
|
globals->get_scenery()->set_center(cntr);
|
|
|
|
else
|
|
|
|
cntr = old_cntr;
|
|
|
|
|
2004-11-22 10:10:33 +00:00
|
|
|
// The center of the cache.
|
2006-10-29 19:30:21 +00:00
|
|
|
cache_center = cntr;
|
2004-11-22 10:10:33 +00:00
|
|
|
|
|
|
|
// Prepare sphere around the aircraft.
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d ptoff = pt - cache_center;
|
|
|
|
double cacheRadius = rad;
|
2004-11-22 10:10:33 +00:00
|
|
|
|
|
|
|
// Prepare bigger sphere around the aircraft.
|
|
|
|
// This one is required for reliably finding wires we have caught but
|
|
|
|
// have already left the hopefully smaller sphere for the ground reactions.
|
|
|
|
const double max_wire_dist = 300.0;
|
2006-10-29 19:30:21 +00:00
|
|
|
double wireCacheRadius = max_wire_dist < rad ? rad : max_wire_dist;
|
2004-11-22 10:10:33 +00:00
|
|
|
|
Mathias:
I have done a patch to eliminate the jitter of 3D-objects near the viewpoint
(for example 3D cockpit objects).
The problem is the roundoff accuracy of the float values used in the
scenegraph together with the transforms of the eyepoint relative to the
scenery center.
The solution will be to move the scenery center near the view point.
This way floats relative accuracy is enough to show a stable picture.
To get that right I have introduced a transform node for the scenegraph which
is responsible for that shift and uses double values as long as possible.
The scenery subsystem now has a list of all those transforms required to place
objects in the world and will tell all those transforms that the scenery
center has changed when the set_scenery_center() of the scenery subsystem is
called.
The problem was not solvable by SGModelPlacement and SGLocation, since not all
objects, especially the scenery, are placed using these classes.
The first approach was to have the scenery center exactly at the eyepoint.
This works well for the cockpit.
But then the ground jitters a bit below the aircraft. With our default views
you can't see that, but that F-18 has a camera view below the left engine
intake with the nose gear and the ground in its field of view, here I could
see that.
Having the scenery center constant will still have this roundoff problems, but
like it is now too, the roundoff error here is exactly the same in each
frame, so you will not notice any jitter.
The real solution is now to keep the scenery center constant as long as it is
in a ball of 30m radius around the view point. If the scenery center is
outside this ball, just put it at the view point.
As a sideeffect of now beeing able to switch the scenery center in the whole
scenegraph with one function call, I was able to remove a one half of a
problem when switching views, where the scenery center was far off for one or
two frames past switching from one view to the next. Also included is a fix
to the other half of this problem, where the view position was not yet copied
into a view when it is switched (at least under glut). This was responsible
for the 'Error: ...' messages of the cloud subsystem when views were
switched.
2005-04-29 14:38:24 +00:00
|
|
|
// Walk the scene graph and extract solid ground triangles and carrier data.
|
2006-10-29 19:30:21 +00:00
|
|
|
GroundCacheFillVisitor gcfv(this, down, ptoff, cacheRadius, wireCacheRadius);
|
|
|
|
globals->get_scenery()->get_scene_graph()->accept(gcfv);
|
2004-11-22 10:10:33 +00:00
|
|
|
|
|
|
|
// some stats
|
2005-10-22 11:26:58 +00:00
|
|
|
SG_LOG(SG_FLIGHT,SG_DEBUG, "prepare_ground_cache(): ac radius = " << rad
|
2005-05-30 08:48:27 +00:00
|
|
|
<< ", # triangles = " << triangles.size()
|
|
|
|
<< ", # wires = " << wires.size()
|
|
|
|
<< ", # catapults = " << catapults.size()
|
2004-11-22 10:10:33 +00:00
|
|
|
<< ", ground_radius = " << ground_radius );
|
|
|
|
|
|
|
|
// If the ground radius is still below 5e6 meters, then we do not yet have
|
|
|
|
// any scenery.
|
|
|
|
found_ground = found_ground && 5e6 < ground_radius;
|
|
|
|
if (!found_ground)
|
|
|
|
SG_LOG(SG_FLIGHT, SG_WARN, "prepare_ground_cache(): trying to build cache "
|
|
|
|
"without any scenery below the aircraft" );
|
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
if (cntr != old_cntr)
|
|
|
|
globals->get_scenery()->set_center(old_cntr);
|
Mathias:
I have done a patch to eliminate the jitter of 3D-objects near the viewpoint
(for example 3D cockpit objects).
The problem is the roundoff accuracy of the float values used in the
scenegraph together with the transforms of the eyepoint relative to the
scenery center.
The solution will be to move the scenery center near the view point.
This way floats relative accuracy is enough to show a stable picture.
To get that right I have introduced a transform node for the scenegraph which
is responsible for that shift and uses double values as long as possible.
The scenery subsystem now has a list of all those transforms required to place
objects in the world and will tell all those transforms that the scenery
center has changed when the set_scenery_center() of the scenery subsystem is
called.
The problem was not solvable by SGModelPlacement and SGLocation, since not all
objects, especially the scenery, are placed using these classes.
The first approach was to have the scenery center exactly at the eyepoint.
This works well for the cockpit.
But then the ground jitters a bit below the aircraft. With our default views
you can't see that, but that F-18 has a camera view below the left engine
intake with the nose gear and the ground in its field of view, here I could
see that.
Having the scenery center constant will still have this roundoff problems, but
like it is now too, the roundoff error here is exactly the same in each
frame, so you will not notice any jitter.
The real solution is now to keep the scenery center constant as long as it is
in a ball of 30m radius around the view point. If the scenery center is
outside this ball, just put it at the view point.
As a sideeffect of now beeing able to switch the scenery center in the whole
scenegraph with one function call, I was able to remove a one half of a
problem when switching views, where the scenery center was far off for one or
two frames past switching from one view to the next. Also included is a fix
to the other half of this problem, where the view position was not yet copied
into a view when it is switched (at least under glut). This was responsible
for the 'Error: ...' messages of the cloud subsystem when views were
switched.
2005-04-29 14:38:24 +00:00
|
|
|
|
2004-11-22 10:10:33 +00:00
|
|
|
return found_ground;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2006-10-29 19:30:21 +00:00
|
|
|
FGGroundCache::is_valid(double& ref_time, SGVec3d& pt, double& rad)
|
2004-11-22 10:10:33 +00:00
|
|
|
{
|
2006-10-29 19:30:21 +00:00
|
|
|
pt = reference_wgs84_point;
|
|
|
|
rad = reference_vehicle_radius;
|
|
|
|
ref_time = cache_ref_time;
|
2004-11-22 10:10:33 +00:00
|
|
|
return found_ground;
|
|
|
|
}
|
|
|
|
|
|
|
|
double
|
2006-10-29 19:30:21 +00:00
|
|
|
FGGroundCache::get_cat(double t, const SGVec3d& dpt,
|
|
|
|
SGVec3d end[2], SGVec3d vel[2])
|
2004-11-22 10:10:33 +00:00
|
|
|
{
|
|
|
|
// start with a distance of 1e10 meters...
|
|
|
|
double dist = 1e10;
|
|
|
|
|
|
|
|
// Time difference to the reference time.
|
|
|
|
t -= cache_ref_time;
|
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
size_t sz = catapults.size();
|
|
|
|
for (size_t i = 0; i < sz; ++i) {
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d pivotoff, rvel[2];
|
|
|
|
pivotoff = catapults[i].start - catapults[i].rotation_pivot;
|
|
|
|
rvel[0] = catapults[i].velocity + cross(catapults[i].rotation, pivotoff);
|
|
|
|
pivotoff = catapults[i].end - catapults[i].rotation_pivot;
|
|
|
|
rvel[1] = catapults[i].velocity + cross(catapults[i].rotation, pivotoff);
|
2004-11-22 10:10:33 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d thisEnd[2];
|
|
|
|
thisEnd[0] = cache_center + catapults[i].start + t*rvel[0];
|
|
|
|
thisEnd[1] = cache_center + catapults[i].end + t*rvel[1];
|
2005-08-16 09:37:23 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
sgdLineSegment3 ls;
|
|
|
|
sgdCopyVec3(ls.a, thisEnd[0].sg());
|
|
|
|
sgdCopyVec3(ls.b, thisEnd[1].sg());
|
|
|
|
double this_dist = sgdDistSquaredToLineSegmentVec3( ls, dpt.sg() );
|
2005-05-30 08:48:27 +00:00
|
|
|
|
|
|
|
if (this_dist < dist) {
|
|
|
|
SG_LOG(SG_FLIGHT,SG_INFO, "Found catapult "
|
|
|
|
<< this_dist << " meters away");
|
|
|
|
dist = this_dist;
|
2004-11-22 10:10:33 +00:00
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
end[0] = thisEnd[0];
|
|
|
|
end[1] = thisEnd[1];
|
|
|
|
vel[0] = rvel[0];
|
|
|
|
vel[1] = rvel[1];
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// At the end take the root, we only computed squared distances ...
|
|
|
|
return sqrt(dist);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2006-10-29 19:30:21 +00:00
|
|
|
FGGroundCache::get_agl(double t, const SGVec3d& dpt, double max_altoff,
|
|
|
|
SGVec3d& contact, SGVec3d& normal, SGVec3d& vel,
|
2006-06-11 13:34:18 +00:00
|
|
|
int *type, const SGMaterial** material, double *agl)
|
2004-11-22 10:10:33 +00:00
|
|
|
{
|
|
|
|
bool ret = false;
|
|
|
|
|
|
|
|
*type = FGInterface::Unknown;
|
|
|
|
// *agl = 0.0;
|
2006-06-11 13:34:18 +00:00
|
|
|
if (material)
|
|
|
|
*material = 0;
|
2006-10-29 19:30:21 +00:00
|
|
|
vel = SGVec3d(0, 0, 0);
|
|
|
|
contact = SGVec3d(0, 0, 0);
|
|
|
|
normal = SGVec3d(0, 0, 0);
|
2004-11-22 10:10:33 +00:00
|
|
|
|
|
|
|
// Time difference to th reference time.
|
|
|
|
t -= cache_ref_time;
|
|
|
|
|
|
|
|
// The double valued point we start to search for intersection.
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d pt = dpt - cache_center;
|
2006-11-21 18:25:36 +00:00
|
|
|
// shift the start of our ray by maxaltoff upwards
|
|
|
|
SGVec3d raystart = pt - max_altoff*down;
|
2004-11-22 10:10:33 +00:00
|
|
|
|
|
|
|
// Initialize to something sensible
|
Mathias Fröhlich:
I have introduced the posibility to start directly on the carrier.
With that patch you will have a --carrrier=id argument where id can either be
the pennant number configured in the nimitz scenario or the carriers name
also configured in the carriers scenario.
Additionaly you can use --parkpos=id to select different positions on the
carrier. They are also configured in the scenario file.
That includes the switch of the whole FGInterface class to make use of the
groundcache.
That means that an aircraft no longer uses the current elevation value from
the scenery class. It rather has its own local cache of the aircrafts
environment which is setup in the common_init method of FGInterface and
updated either manually by calling
FGInterface::get_groundlevel_m(lat, lon, alt_m);
or implicitly by calling the above method in the
FGInterface::_updateGeo*Position(lat, lon, alt);
methods.
A call get_groundlevel_m rebuilds the groundcache if the request is outside
the range of the cache.
Note that for the real usage of the groundcache including the correct
information about the movement of objects and the velocity information, you
still need to set up the groundcache in the usual way like YASim and JSBSim
currently does.
If you use the native interface, you will get only static objects correctly.
But for FDM's only using one single ground level for a whole step this is IMO
sufficient.
The AIManager gets a way to return the location of a object which is placed
wrt an AI Object. At the moment it only honours AICarriers for that.
That method is a static one, which loads the scenario file for that reason and
throws it away afterwards. This looked like the aprioriate way, because the
AIManager is initialized much later in flightgears bootstrap, and I did not
find an easy way to reorder that for my needs. Since this additional load is
very small and does only happen if such a relative location is required, I
think that this is ok.
Note that moving on the carrier will only work correctly for JSBSim and YASim,
but you should now be able to start and move on every not itself moving
object with any FDM.
2005-07-03 09:39:14 +00:00
|
|
|
double current_radius = 0.0;
|
2004-11-22 10:10:33 +00:00
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
size_t sz = triangles.size();
|
|
|
|
for (size_t i = 0; i < sz; ++i) {
|
|
|
|
Triangle triangle;
|
|
|
|
velocityTransformTriangle(t, triangle, triangles[i]);
|
2006-10-29 19:30:21 +00:00
|
|
|
if (!fgdIsectSphereInfLine(triangle.boundCenter, triangle.boundRadius, pt, down))
|
2004-11-22 10:10:33 +00:00
|
|
|
continue;
|
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
// Check for intersection.
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d isecpoint;
|
2006-11-21 18:25:36 +00:00
|
|
|
if (fgdRayTriangle(isecpoint, raystart, down, triangle.vertices)) {
|
2005-11-04 14:49:15 +00:00
|
|
|
// Compute the vector from pt to the intersection point ...
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d off = isecpoint - pt;
|
2005-11-04 14:49:15 +00:00
|
|
|
// ... and check if it is too high or not
|
2006-11-21 18:25:36 +00:00
|
|
|
// Transform to the wgs system
|
|
|
|
isecpoint += cache_center;
|
|
|
|
// compute the radius, good enough approximation to take the geocentric radius
|
|
|
|
double radius = dot(isecpoint, isecpoint);
|
|
|
|
if (current_radius < radius) {
|
|
|
|
current_radius = radius;
|
|
|
|
ret = true;
|
|
|
|
// Save the new potential intersection point.
|
|
|
|
contact = isecpoint;
|
|
|
|
// The first three values in the vector are the plane normal.
|
|
|
|
sgdCopyVec3( normal.sg(), triangle.plane.sg() );
|
|
|
|
// The velocity wrt earth.
|
|
|
|
SGVec3d pivotoff = pt - triangle.rotation_pivot;
|
|
|
|
vel = triangle.velocity + cross(triangle.rotation, pivotoff);
|
|
|
|
// Save the ground type.
|
|
|
|
*type = triangle.type;
|
|
|
|
*agl = dot(down, contact - dpt);
|
|
|
|
if (material)
|
|
|
|
*material = triangle.material;
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Whenever we did not have a ground triangle for the requested point,
|
|
|
|
// take the ground level we found during the current cache build.
|
|
|
|
// This is as good as what we had before for agl.
|
2006-10-29 19:30:21 +00:00
|
|
|
double r = length(dpt);
|
|
|
|
contact = dpt;
|
|
|
|
contact *= ground_radius/r;
|
|
|
|
normal = -down;
|
|
|
|
vel = SGVec3d(0, 0, 0);
|
2004-11-22 10:10:33 +00:00
|
|
|
|
|
|
|
// The altitude is the distance of the requested point from the
|
|
|
|
// contact point.
|
2006-10-29 19:30:21 +00:00
|
|
|
*agl = dot(down, contact - dpt);
|
2004-11-22 10:10:33 +00:00
|
|
|
*type = FGInterface::Unknown;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
bool FGGroundCache::caught_wire(double t, const SGVec3d pt[4])
|
2004-11-22 10:10:33 +00:00
|
|
|
{
|
2005-05-30 08:48:27 +00:00
|
|
|
size_t sz = wires.size();
|
|
|
|
if (sz == 0)
|
|
|
|
return false;
|
2004-11-22 10:10:33 +00:00
|
|
|
|
|
|
|
// Time difference to the reference time.
|
|
|
|
t -= cache_ref_time;
|
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
// Build the two triangles spanning the area where the hook has moved
|
|
|
|
// during the past step.
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec4d plane[2];
|
|
|
|
SGVec3d tri[2][3];
|
|
|
|
sgdMakePlane( plane[0].sg(), pt[0].sg(), pt[1].sg(), pt[2].sg() );
|
|
|
|
tri[0][0] = pt[0];
|
|
|
|
tri[0][1] = pt[1];
|
|
|
|
tri[0][2] = pt[2];
|
|
|
|
sgdMakePlane( plane[1].sg(), pt[0].sg(), pt[2].sg(), pt[3].sg() );
|
|
|
|
tri[1][0] = pt[0];
|
|
|
|
tri[1][1] = pt[2];
|
|
|
|
tri[1][2] = pt[3];
|
2005-05-30 08:48:27 +00:00
|
|
|
|
|
|
|
// Intersect the wire lines with each of these triangles.
|
2005-12-04 10:43:49 +00:00
|
|
|
// You have caught a wire if they intersect.
|
2005-05-30 08:48:27 +00:00
|
|
|
for (size_t i = 0; i < sz; ++i) {
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d le[2];
|
2005-08-16 09:37:23 +00:00
|
|
|
for (int k = 0; k < 2; ++k) {
|
2006-10-29 19:30:21 +00:00
|
|
|
le[k] = wires[i].ends[k];
|
|
|
|
SGVec3d pivotoff = le[k] - wires[i].rotation_pivot;
|
|
|
|
SGVec3d vel = wires[i].velocity + cross(wires[i].rotation, pivotoff);
|
|
|
|
le[k] += t*vel + cache_center;
|
2005-08-16 09:37:23 +00:00
|
|
|
}
|
2004-11-22 10:10:33 +00:00
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
for (int k=0; k<2; ++k) {
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d isecpoint;
|
|
|
|
double isecval = sgdIsectLinesegPlane(isecpoint.sg(), le[0].sg(),
|
|
|
|
le[1].sg(), plane[k].sg());
|
2005-05-30 08:48:27 +00:00
|
|
|
if ( 0.0 <= isecval && isecval <= 1.0 &&
|
2006-10-29 19:30:21 +00:00
|
|
|
fgdPointInTriangle( isecpoint, tri[k] ) ) {
|
2005-05-30 08:48:27 +00:00
|
|
|
SG_LOG(SG_FLIGHT,SG_INFO, "Caught wire");
|
|
|
|
// Store the wire id.
|
|
|
|
wire_id = wires[i].wire_id;
|
|
|
|
return true;
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
return false;
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
|
|
|
|
2006-10-29 19:30:21 +00:00
|
|
|
bool FGGroundCache::get_wire_ends(double t, SGVec3d end[2], SGVec3d vel[2])
|
2004-11-22 10:10:33 +00:00
|
|
|
{
|
|
|
|
// Fast return if we do not have an active wire.
|
|
|
|
if (wire_id < 0)
|
|
|
|
return false;
|
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
// Time difference to the reference time.
|
2004-11-22 10:10:33 +00:00
|
|
|
t -= cache_ref_time;
|
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
// Search for the wire with the matching wire id.
|
|
|
|
size_t sz = wires.size();
|
|
|
|
for (size_t i = 0; i < sz; ++i) {
|
|
|
|
if (wires[i].wire_id == wire_id) {
|
2005-08-16 09:37:23 +00:00
|
|
|
for (size_t k = 0; k < 2; ++k) {
|
2006-10-29 19:30:21 +00:00
|
|
|
SGVec3d pivotoff = end[k] - wires[i].rotation_pivot;
|
|
|
|
vel[k] = wires[i].velocity + cross(wires[i].rotation, pivotoff);
|
|
|
|
end[k] = cache_center + wires[i].ends[k] + t*vel[k];
|
2005-08-16 09:37:23 +00:00
|
|
|
}
|
2005-05-30 08:48:27 +00:00
|
|
|
return true;
|
|
|
|
}
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
|
|
|
|
2005-05-30 08:48:27 +00:00
|
|
|
return false;
|
2004-11-22 10:10:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FGGroundCache::release_wire(void)
|
|
|
|
{
|
|
|
|
wire_id = -1;
|
|
|
|
}
|