Multiple LoD levels of MP Aircraft
At a basic level, the implementation supports two levels of LoD: FAR from /sim/rendering/static-lod/ai-range-bare to /sim/rendering/static-lod/ai-range-detailed. NEAR from /sim/rendering/static-lod/ai-range-detailed to 0. (First of many digressions: If /sim/rendering/static-lod/ai-range-mode-pixel=true then instead of measuring LOD distance in meters, the size of the object in pixels is used, so the ranges are different) The models that are loaded for FAR and NEAR depend on a combination of the availability of a model in /AI/AIrcraft/ and FG_AIRCRAFT directories. If /sim/rendering/static-lod/ai-range-detailed=false then an AI aircraft will be used in preference for both NEAR and FAR. If /sim/rendering/static-lod/ai-range-detailed=true then an AI aircraft will be used for FAR, and an FG_AIRCRAFT for NEAR. Obviously if only an AI or a FG_AIRCRAFT model are available, that will be used for the entire NEAR+FAR range.
This commit is contained in:
parent
a22c53a807
commit
236b7c0f83
2 changed files with 145 additions and 74 deletions
|
@ -70,11 +70,11 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
~FGAIModelData()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
virtual FGAIModelData* clone() const { return new FGAIModelData(); }
|
||||
|
||||
/** osg callback, thread-safe */
|
||||
|
@ -83,7 +83,7 @@ public:
|
|||
// WARNING: Called in a separate OSG thread! Only use thread-safe stuff here...
|
||||
if (_ready)
|
||||
return;
|
||||
|
||||
|
||||
if(prop->hasChild("interior-path")){
|
||||
_interiorPath = prop->getStringValue("interior-path");
|
||||
_hasInteriorPath = true;
|
||||
|
@ -91,15 +91,15 @@ public:
|
|||
|
||||
_fxpath = prop->getStringValue("sound/path");
|
||||
_nasal->modelLoaded(path, prop, n);
|
||||
|
||||
|
||||
_ready = true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** init hook to be called after model is loaded.
|
||||
* Not thread-safe. Call from main thread only. */
|
||||
void init(void) { _initialized = true; }
|
||||
|
||||
|
||||
bool needInitilization(void) { return _ready && !_initialized;}
|
||||
bool isInitialized(void) { return _initialized;}
|
||||
inline std::string& get_sound_path() { return _fxpath;}
|
||||
|
@ -224,7 +224,7 @@ FGAIBase::removeModel()
|
|||
aip.clear();
|
||||
_modeldata = 0;
|
||||
_model = 0;
|
||||
|
||||
|
||||
// pass it on to the pager, to be be deleted in the pager thread
|
||||
pSceneryManager->getPager()->queueDeleteRequest(temp);
|
||||
}
|
||||
|
@ -333,27 +333,83 @@ void FGAIBase::updateInterior()
|
|||
/** update LOD properties of the model */
|
||||
void FGAIBase::updateLOD()
|
||||
{
|
||||
double maxRangeDetail = fgGetDouble("/sim/rendering/static-lod/ai-detailed", 10000.0);
|
||||
// double maxRangeBare = fgGetDouble("/sim/rendering/static-lod/ai-bare", 20000.0);
|
||||
double maxRangeDetail = fgGetDouble("/sim/rendering/static-lod/ai-detailed", 3000.0);
|
||||
double maxRangeBare = fgGetDouble("/sim/rendering/static-lod/ai-bare", 10000.0);
|
||||
|
||||
_maxRangeInterior = fgGetDouble("/sim/rendering/static-lod/ai-interior", 50.0);
|
||||
|
||||
if (_model.valid())
|
||||
{
|
||||
if( maxRangeDetail == 0.0 )
|
||||
{
|
||||
// disable LOD
|
||||
// Disable LOD. The First entry in the LOD node is the most detailed
|
||||
// so use that.
|
||||
_model->setRange(0, 0.0, FLT_MAX);
|
||||
_model->setRange(1, FLT_MAX, FLT_MAX);
|
||||
if (_model->getNumFileNames() == 2) {
|
||||
_model->setRange(1, FLT_MAX, FLT_MAX);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( fgGetBool("/sim/rendering/static-lod/ai-range-mode-pixel", false ) )
|
||||
if( fgGetBool("/sim/rendering/static-lod/ai-range-mode-pixel", false ) )
|
||||
{
|
||||
/* In pixel size mode, the range sense is reversed, so we want the
|
||||
* detailed model [0] to be displayed when the "range" is really
|
||||
* large (i.e. the object is taking up a large number of pixels on screen),
|
||||
* and the less detailed model [1] to be displayed if the
|
||||
* "range" is between the detailed range and the bare range.
|
||||
* When the "range" is less than the bare value, the aircraft
|
||||
* represents too few pixels to be worth displaying.
|
||||
*/
|
||||
|
||||
if (maxRangeBare > maxRangeDetail) {
|
||||
// Sanity check that we have sensible values.
|
||||
maxRangeBare = maxRangeDetail;
|
||||
SG_LOG(SG_AI,
|
||||
SG_WARN,
|
||||
"/sim/rendering/static-lod/ai-bare greater " <<
|
||||
"than /sim/rendering/static-lod/ai-detailed when using " <<
|
||||
"/sim/rendering/static-lod/ai-range-mode-pixel=true. Ignoring ai-bare."
|
||||
);
|
||||
}
|
||||
|
||||
_model->setRangeMode( osg::LOD::PIXEL_SIZE_ON_SCREEN );
|
||||
_model->setRange(0, maxRangeDetail, 100000 );
|
||||
if (_model->getNumFileNames() == 2) {
|
||||
_model->setRange(0, maxRangeDetail, 100000 );
|
||||
_model->setRange(1, maxRangeBare, maxRangeDetail);
|
||||
} else {
|
||||
/* If we have only one LoD for this model, then we want to
|
||||
* display it from the smallest pixel value
|
||||
*/
|
||||
_model->setRange(0, min(maxRangeBare, maxRangeDetail), 100000 );
|
||||
}
|
||||
} else {
|
||||
/* In non-pixel range mode we're dealing with straight distance.
|
||||
* We use the detailed model [0] for when we are up to the detailed
|
||||
* range, and the less complex model [1] (if available) for further
|
||||
* away up to the bare range.
|
||||
*/
|
||||
|
||||
if (maxRangeBare < maxRangeDetail) {
|
||||
// Sanity check that we have sensible values.
|
||||
maxRangeBare = maxRangeDetail;
|
||||
SG_LOG(SG_AI,
|
||||
SG_WARN,
|
||||
"/sim/rendering/static-lod/ai-bare less than " <<
|
||||
"than /sim/rendering/static-lod/ai-detailed. Ignoring ai-bare."
|
||||
);
|
||||
}
|
||||
|
||||
_model->setRangeMode( osg::LOD:: DISTANCE_FROM_EYE_POINT);
|
||||
_model->setRange(0, 0.0, maxRangeDetail);
|
||||
if (_model->getNumFileNames() == 2) {
|
||||
_model->setRange(0, 0, maxRangeDetail);
|
||||
_model->setRange(1, maxRangeDetail, maxRangeBare);
|
||||
} else {
|
||||
/* If we have only one LoD for this model, then we want to
|
||||
* display it from whatever range.
|
||||
*/
|
||||
_model->setRange(0, 0, max(maxRangeBare, maxRangeDetail));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -378,40 +434,65 @@ void FGAIBase::Transform() {
|
|||
|
||||
}
|
||||
|
||||
std::string FGAIBase::resolveModelPath(ModelSearchOrder searchOrder)
|
||||
/*
|
||||
* Find a set of paths to the model, in order of LOD from most detailed to
|
||||
* least, and accounting for the user preference of detailed models vs. AI
|
||||
* low resolution models.
|
||||
*
|
||||
* This returns a vector of size 1 or 2.
|
||||
*/
|
||||
std::vector<std::string> FGAIBase::resolveModelPath(ModelSearchOrder searchOrder)
|
||||
{
|
||||
std::string aiPath;
|
||||
if (searchOrder != DATA_ONLY) {
|
||||
std::vector<std::string> path_list;
|
||||
|
||||
if (searchOrder == DATA_ONLY) {
|
||||
auto p = simgear::SGModelLib::findDataFile(model_path);
|
||||
if (!p.empty()) {
|
||||
// We've got a model, use it
|
||||
_installed = true;
|
||||
path_list.push_back(p);
|
||||
} else {
|
||||
// No model, so fall back to the default
|
||||
path_list.push_back(fgGetString("/sim/multiplay/default-model", default_model));
|
||||
}
|
||||
} else {
|
||||
// We're either PREFER_AI or PREFER_DATA. Find an AI model first.
|
||||
for (SGPath p : globals->get_data_paths("AI")) {
|
||||
p.append(model_path);
|
||||
if (p.exists()) {
|
||||
aiPath = p.local8BitStr();
|
||||
path_list.push_back(p.local8BitStr());
|
||||
break;
|
||||
}
|
||||
} // of AI data paths iteration
|
||||
|
||||
if ((searchOrder == PREFER_AI) && !path_list.empty()) {
|
||||
// if we prefer AI, and we've got a valid AI path from above, then use it, we're done
|
||||
_installed = true;
|
||||
return path_list;
|
||||
}
|
||||
|
||||
// At this point we've either still to find a valid path, or we're
|
||||
// looking for a regular model to display at closer range.
|
||||
auto p = simgear::SGModelLib::findDataFile(model_path);
|
||||
if (!p.empty()) {
|
||||
_installed = true;
|
||||
path_list.insert(path_list.begin(), p);
|
||||
}
|
||||
|
||||
if (path_list.empty()) {
|
||||
// No model found at all, so fall back to the default
|
||||
path_list.push_back(fgGetString("/sim/multiplay/default-model", default_model));
|
||||
}
|
||||
}
|
||||
|
||||
// if we prefer AI, and it's valid, use it, we're done
|
||||
if ((searchOrder == PREFER_AI) && !aiPath.empty()) {
|
||||
_installed = true;
|
||||
return aiPath;
|
||||
}
|
||||
|
||||
// regular search including aircraft paths
|
||||
auto p = simgear::SGModelLib::findDataFile(model_path);
|
||||
if (!p.empty()) {
|
||||
_installed = true;
|
||||
return p;
|
||||
}
|
||||
|
||||
// if we prefer data, but will still use AI paths, now is the time.
|
||||
if ((searchOrder == PREFER_DATA) && !aiPath.empty()) {
|
||||
_installed = true;
|
||||
return aiPath;
|
||||
}
|
||||
|
||||
// okay, out of options, use the default model (the blue glider)
|
||||
return fgGetString("/sim/multiplay/default-model", default_model);
|
||||
|
||||
/*
|
||||
* We return either one or two models. LoD logic elsewhere relies on this,
|
||||
* so anything else is a logic error in the above code.
|
||||
*/
|
||||
assert(path_list.size() != 0);
|
||||
assert(path_list.size() < 3);
|
||||
|
||||
return path_list;
|
||||
}
|
||||
|
||||
bool FGAIBase::init(ModelSearchOrder searchOrder)
|
||||
|
@ -422,25 +503,16 @@ bool FGAIBase::init(ModelSearchOrder searchOrder)
|
|||
return false;
|
||||
}
|
||||
|
||||
string f = resolveModelPath(searchOrder);
|
||||
|
||||
vector<string> model_list = resolveModelPath(searchOrder);
|
||||
|
||||
props->addChild("type")->setStringValue("AI");
|
||||
_modeldata = new FGAIModelData(props);
|
||||
_model= SGModelLib::loadPagedModel(f, props, _modeldata);
|
||||
|
||||
_model= SGModelLib::loadPagedModel(model_list, props, _modeldata);
|
||||
_model->setName("AI-model range animation node");
|
||||
|
||||
// _model->setCenterMode(osg::LOD::USE_BOUNDING_SPHERE_CENTER);
|
||||
// _model->setRangeMode(osg::LOD::DISTANCE_FROM_EYE_POINT);
|
||||
|
||||
// We really need low-resolution versions of AI/MP aircraft.
|
||||
// Or at least dummy "stubs" with some default silhouette.
|
||||
// _model->addChild( SGModelLib::loadPagedModel(fgGetString("/sim/multiplay/default-model", default_model),
|
||||
// props, new FGNasalModelData(props)), FLT_MAX, FLT_MAX);
|
||||
updateLOD();
|
||||
initModel();
|
||||
|
||||
|
||||
if (_model.valid() && _initialized == false) {
|
||||
aip.init( _model.get() );
|
||||
aip.setVisible(true);
|
||||
|
@ -462,7 +534,7 @@ bool FGAIBase::init(ModelSearchOrder searchOrder)
|
|||
|
||||
void FGAIBase::initModel()
|
||||
{
|
||||
if (_model.valid()) {
|
||||
if (_model.valid()) {
|
||||
|
||||
if( _path != ""){
|
||||
props->setStringValue("submodels/path", _path.c_str());
|
||||
|
@ -596,20 +668,20 @@ double FGAIBase::UpdateRadar(FGAIManager* manager)
|
|||
bool force_on = fgGetBool("/instrumentation/radar/debug-mode", false);
|
||||
radar_range_m *= SG_NM_TO_METER * 1.1; // + 10%
|
||||
radar_range_m *= radar_range_m; // squared
|
||||
|
||||
|
||||
double d2 = distSqr(SGVec3d::fromGeod(pos), globals->get_aircraft_position_cart());
|
||||
double range_ft = sqrt(d2) * SG_METER_TO_FEET;
|
||||
|
||||
|
||||
if (!force_on && (d2 > radar_range_m)) {
|
||||
return range_ft * range_ft;
|
||||
}
|
||||
|
||||
|
||||
props->setBoolValue("radar/in-range", true);
|
||||
|
||||
// copy values from the AIManager
|
||||
double user_heading = manager->get_user_heading();
|
||||
double user_pitch = manager->get_user_pitch();
|
||||
|
||||
|
||||
range = range_ft * SG_FEET_TO_METER * SG_METER_TO_NM;
|
||||
|
||||
// calculate bearing to target
|
||||
|
@ -618,7 +690,7 @@ double FGAIBase::UpdateRadar(FGAIManager* manager)
|
|||
// calculate look left/right to target, without yaw correction
|
||||
horiz_offset = bearing - user_heading;
|
||||
SG_NORMALIZE_RANGE(horiz_offset, -180.0, 180.0);
|
||||
|
||||
|
||||
// calculate elevation to target
|
||||
ht_diff = altitude_ft - globals->get_aircraft_position().getElevationFt();
|
||||
elevation = atan2( ht_diff, range_ft ) * SG_RADIANS_TO_DEGREES;
|
||||
|
@ -643,7 +715,7 @@ double FGAIBase::UpdateRadar(FGAIManager* manager)
|
|||
// calculate values for radar display
|
||||
y_shift = range * cos( horiz_offset * SG_DEGREES_TO_RADIANS);
|
||||
x_shift = range * sin( horiz_offset * SG_DEGREES_TO_RADIANS);
|
||||
|
||||
|
||||
rotation = hdg - user_heading;
|
||||
SG_NORMALIZE_RANGE(rotation, 0.0, 360.0);
|
||||
|
||||
|
@ -959,7 +1031,7 @@ void FGAIBase::setFlightPlan(std::unique_ptr<FGAIFlightPlan> f)
|
|||
bool FGAIBase::isValid() const
|
||||
{
|
||||
//Either no flightplan or it is valid
|
||||
return !fp || fp->isValidPlan();
|
||||
return !fp || fp->isValidPlan();
|
||||
}
|
||||
|
||||
osg::PagedLOD* FGAIBase::getSceneBranch() const
|
||||
|
@ -976,4 +1048,3 @@ void FGAIBase::setGeodPos(const SGGeod& geod)
|
|||
{
|
||||
pos = geod;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// FGAIBase.hxx - abstract base class for AI objects
|
||||
// Written by David Culp, started Nov 2003, based on
|
||||
// David Luff's FGAIEntity class.
|
||||
// - davidculp2@comcast.net
|
||||
// - davidculp2@comcast.net
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
|
@ -62,7 +62,7 @@ public:
|
|||
PREFER_AI, // search AI first, override other paths
|
||||
PREFER_DATA // search data first but fall back to AI
|
||||
};
|
||||
|
||||
|
||||
virtual bool init(ModelSearchOrder searchOrder);
|
||||
virtual void initModel();
|
||||
virtual void update(double dt);
|
||||
|
@ -115,12 +115,12 @@ public:
|
|||
|
||||
bool getDie();
|
||||
bool isValid() const;
|
||||
|
||||
|
||||
void setFlightPlan(std::unique_ptr<FGAIFlightPlan> f);
|
||||
|
||||
|
||||
SGGeod getGeodPos() const;
|
||||
void setGeodPos(const SGGeod& pos);
|
||||
|
||||
|
||||
SGVec3d getCartPosAt(const SGVec3d& off) const;
|
||||
SGVec3d getCartPos() const;
|
||||
|
||||
|
@ -131,29 +131,29 @@ public:
|
|||
double _getCartPosX() const;
|
||||
double _getCartPosY() const;
|
||||
double _getCartPosZ() const;
|
||||
|
||||
|
||||
osg::PagedLOD* getSceneBranch() const;
|
||||
protected:
|
||||
double _elevation_m;
|
||||
|
||||
|
||||
double _maxRangeInterior;
|
||||
|
||||
double _x_offset;
|
||||
double _y_offset;
|
||||
double _z_offset;
|
||||
|
||||
|
||||
double _pitch_offset;
|
||||
double _roll_offset;
|
||||
double _yaw_offset;
|
||||
|
||||
|
||||
double _max_speed;
|
||||
|
||||
|
||||
std::string _path;
|
||||
std::string _callsign;
|
||||
std::string _submodel;
|
||||
std::string _name;
|
||||
std::string _parent;
|
||||
|
||||
|
||||
/**
|
||||
* Tied-properties helper, record nodes which are tied for easy un-tie-ing
|
||||
*/
|
||||
|
@ -254,7 +254,7 @@ private:
|
|||
|
||||
SGSharedPtr<FGFX> _fx;
|
||||
|
||||
std::string resolveModelPath(ModelSearchOrder searchOrder);
|
||||
std::vector<std::string> resolveModelPath(ModelSearchOrder searchOrder);
|
||||
|
||||
public:
|
||||
object_type getType();
|
||||
|
@ -419,7 +419,7 @@ inline void FGAIBase::setPitchoffset(double p) {
|
|||
|
||||
inline void FGAIBase::setRolloffset(double r) {
|
||||
_roll_offset = r;
|
||||
}
|
||||
}
|
||||
|
||||
inline void FGAIBase::setYawoffset(double y) {
|
||||
_yaw_offset = y;
|
||||
|
|
Loading…
Reference in a new issue