1
0
Fork 0

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:
Stuart Buchanan 2018-06-05 21:58:03 +01:00
parent a22c53a807
commit 236b7c0f83
2 changed files with 145 additions and 74 deletions

View file

@ -70,11 +70,11 @@ public:
{ {
} }
~FGAIModelData() ~FGAIModelData()
{ {
} }
virtual FGAIModelData* clone() const { return new FGAIModelData(); } virtual FGAIModelData* clone() const { return new FGAIModelData(); }
/** osg callback, thread-safe */ /** osg callback, thread-safe */
@ -83,7 +83,7 @@ public:
// WARNING: Called in a separate OSG thread! Only use thread-safe stuff here... // WARNING: Called in a separate OSG thread! Only use thread-safe stuff here...
if (_ready) if (_ready)
return; return;
if(prop->hasChild("interior-path")){ if(prop->hasChild("interior-path")){
_interiorPath = prop->getStringValue("interior-path"); _interiorPath = prop->getStringValue("interior-path");
_hasInteriorPath = true; _hasInteriorPath = true;
@ -91,15 +91,15 @@ public:
_fxpath = prop->getStringValue("sound/path"); _fxpath = prop->getStringValue("sound/path");
_nasal->modelLoaded(path, prop, n); _nasal->modelLoaded(path, prop, n);
_ready = true; _ready = true;
} }
/** init hook to be called after model is loaded. /** init hook to be called after model is loaded.
* Not thread-safe. Call from main thread only. */ * Not thread-safe. Call from main thread only. */
void init(void) { _initialized = true; } void init(void) { _initialized = true; }
bool needInitilization(void) { return _ready && !_initialized;} bool needInitilization(void) { return _ready && !_initialized;}
bool isInitialized(void) { return _initialized;} bool isInitialized(void) { return _initialized;}
inline std::string& get_sound_path() { return _fxpath;} inline std::string& get_sound_path() { return _fxpath;}
@ -224,7 +224,7 @@ FGAIBase::removeModel()
aip.clear(); aip.clear();
_modeldata = 0; _modeldata = 0;
_model = 0; _model = 0;
// pass it on to the pager, to be be deleted in the pager thread // pass it on to the pager, to be be deleted in the pager thread
pSceneryManager->getPager()->queueDeleteRequest(temp); pSceneryManager->getPager()->queueDeleteRequest(temp);
} }
@ -333,27 +333,83 @@ void FGAIBase::updateInterior()
/** update LOD properties of the model */ /** update LOD properties of the model */
void FGAIBase::updateLOD() void FGAIBase::updateLOD()
{ {
double maxRangeDetail = fgGetDouble("/sim/rendering/static-lod/ai-detailed", 10000.0); double maxRangeDetail = fgGetDouble("/sim/rendering/static-lod/ai-detailed", 3000.0);
// double maxRangeBare = fgGetDouble("/sim/rendering/static-lod/ai-bare", 20000.0); double maxRangeBare = fgGetDouble("/sim/rendering/static-lod/ai-bare", 10000.0);
_maxRangeInterior = fgGetDouble("/sim/rendering/static-lod/ai-interior", 50.0); _maxRangeInterior = fgGetDouble("/sim/rendering/static-lod/ai-interior", 50.0);
if (_model.valid()) if (_model.valid())
{ {
if( maxRangeDetail == 0.0 ) 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(0, 0.0, FLT_MAX);
_model->setRange(1, FLT_MAX, FLT_MAX); if (_model->getNumFileNames() == 2) {
_model->setRange(1, FLT_MAX, FLT_MAX);
}
} }
else 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->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 { } 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->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; std::vector<std::string> path_list;
if (searchOrder != DATA_ONLY) {
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")) { for (SGPath p : globals->get_data_paths("AI")) {
p.append(model_path); p.append(model_path);
if (p.exists()) { if (p.exists()) {
aiPath = p.local8BitStr(); path_list.push_back(p.local8BitStr());
break; break;
} }
} // of AI data paths iteration } // 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()) { * We return either one or two models. LoD logic elsewhere relies on this,
_installed = true; * so anything else is a logic error in the above code.
return aiPath; */
} assert(path_list.size() != 0);
assert(path_list.size() < 3);
// regular search including aircraft paths
auto p = simgear::SGModelLib::findDataFile(model_path); return path_list;
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);
} }
bool FGAIBase::init(ModelSearchOrder searchOrder) bool FGAIBase::init(ModelSearchOrder searchOrder)
@ -422,25 +503,16 @@ bool FGAIBase::init(ModelSearchOrder searchOrder)
return false; return false;
} }
string f = resolveModelPath(searchOrder); vector<string> model_list = resolveModelPath(searchOrder);
props->addChild("type")->setStringValue("AI"); props->addChild("type")->setStringValue("AI");
_modeldata = new FGAIModelData(props); _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->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(); updateLOD();
initModel(); initModel();
if (_model.valid() && _initialized == false) { if (_model.valid() && _initialized == false) {
aip.init( _model.get() ); aip.init( _model.get() );
aip.setVisible(true); aip.setVisible(true);
@ -462,7 +534,7 @@ bool FGAIBase::init(ModelSearchOrder searchOrder)
void FGAIBase::initModel() void FGAIBase::initModel()
{ {
if (_model.valid()) { if (_model.valid()) {
if( _path != ""){ if( _path != ""){
props->setStringValue("submodels/path", _path.c_str()); 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); bool force_on = fgGetBool("/instrumentation/radar/debug-mode", false);
radar_range_m *= SG_NM_TO_METER * 1.1; // + 10% radar_range_m *= SG_NM_TO_METER * 1.1; // + 10%
radar_range_m *= radar_range_m; // squared radar_range_m *= radar_range_m; // squared
double d2 = distSqr(SGVec3d::fromGeod(pos), globals->get_aircraft_position_cart()); double d2 = distSqr(SGVec3d::fromGeod(pos), globals->get_aircraft_position_cart());
double range_ft = sqrt(d2) * SG_METER_TO_FEET; double range_ft = sqrt(d2) * SG_METER_TO_FEET;
if (!force_on && (d2 > radar_range_m)) { if (!force_on && (d2 > radar_range_m)) {
return range_ft * range_ft; return range_ft * range_ft;
} }
props->setBoolValue("radar/in-range", true); props->setBoolValue("radar/in-range", true);
// copy values from the AIManager // copy values from the AIManager
double user_heading = manager->get_user_heading(); double user_heading = manager->get_user_heading();
double user_pitch = manager->get_user_pitch(); double user_pitch = manager->get_user_pitch();
range = range_ft * SG_FEET_TO_METER * SG_METER_TO_NM; range = range_ft * SG_FEET_TO_METER * SG_METER_TO_NM;
// calculate bearing to target // calculate bearing to target
@ -618,7 +690,7 @@ double FGAIBase::UpdateRadar(FGAIManager* manager)
// calculate look left/right to target, without yaw correction // calculate look left/right to target, without yaw correction
horiz_offset = bearing - user_heading; horiz_offset = bearing - user_heading;
SG_NORMALIZE_RANGE(horiz_offset, -180.0, 180.0); SG_NORMALIZE_RANGE(horiz_offset, -180.0, 180.0);
// calculate elevation to target // calculate elevation to target
ht_diff = altitude_ft - globals->get_aircraft_position().getElevationFt(); ht_diff = altitude_ft - globals->get_aircraft_position().getElevationFt();
elevation = atan2( ht_diff, range_ft ) * SG_RADIANS_TO_DEGREES; elevation = atan2( ht_diff, range_ft ) * SG_RADIANS_TO_DEGREES;
@ -643,7 +715,7 @@ double FGAIBase::UpdateRadar(FGAIManager* manager)
// calculate values for radar display // calculate values for radar display
y_shift = range * cos( horiz_offset * SG_DEGREES_TO_RADIANS); y_shift = range * cos( horiz_offset * SG_DEGREES_TO_RADIANS);
x_shift = range * sin( horiz_offset * SG_DEGREES_TO_RADIANS); x_shift = range * sin( horiz_offset * SG_DEGREES_TO_RADIANS);
rotation = hdg - user_heading; rotation = hdg - user_heading;
SG_NORMALIZE_RANGE(rotation, 0.0, 360.0); SG_NORMALIZE_RANGE(rotation, 0.0, 360.0);
@ -959,7 +1031,7 @@ void FGAIBase::setFlightPlan(std::unique_ptr<FGAIFlightPlan> f)
bool FGAIBase::isValid() const bool FGAIBase::isValid() const
{ {
//Either no flightplan or it is valid //Either no flightplan or it is valid
return !fp || fp->isValidPlan(); return !fp || fp->isValidPlan();
} }
osg::PagedLOD* FGAIBase::getSceneBranch() const osg::PagedLOD* FGAIBase::getSceneBranch() const
@ -976,4 +1048,3 @@ void FGAIBase::setGeodPos(const SGGeod& geod)
{ {
pos = geod; pos = geod;
} }

View file

@ -1,7 +1,7 @@
// FGAIBase.hxx - abstract base class for AI objects // FGAIBase.hxx - abstract base class for AI objects
// Written by David Culp, started Nov 2003, based on // Written by David Culp, started Nov 2003, based on
// David Luff's FGAIEntity class. // David Luff's FGAIEntity class.
// - davidculp2@comcast.net // - davidculp2@comcast.net
// //
// This program is free software; you can redistribute it and/or // This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as // 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_AI, // search AI first, override other paths
PREFER_DATA // search data first but fall back to AI PREFER_DATA // search data first but fall back to AI
}; };
virtual bool init(ModelSearchOrder searchOrder); virtual bool init(ModelSearchOrder searchOrder);
virtual void initModel(); virtual void initModel();
virtual void update(double dt); virtual void update(double dt);
@ -115,12 +115,12 @@ public:
bool getDie(); bool getDie();
bool isValid() const; bool isValid() const;
void setFlightPlan(std::unique_ptr<FGAIFlightPlan> f); void setFlightPlan(std::unique_ptr<FGAIFlightPlan> f);
SGGeod getGeodPos() const; SGGeod getGeodPos() const;
void setGeodPos(const SGGeod& pos); void setGeodPos(const SGGeod& pos);
SGVec3d getCartPosAt(const SGVec3d& off) const; SGVec3d getCartPosAt(const SGVec3d& off) const;
SGVec3d getCartPos() const; SGVec3d getCartPos() const;
@ -131,29 +131,29 @@ public:
double _getCartPosX() const; double _getCartPosX() const;
double _getCartPosY() const; double _getCartPosY() const;
double _getCartPosZ() const; double _getCartPosZ() const;
osg::PagedLOD* getSceneBranch() const; osg::PagedLOD* getSceneBranch() const;
protected: protected:
double _elevation_m; double _elevation_m;
double _maxRangeInterior; double _maxRangeInterior;
double _x_offset; double _x_offset;
double _y_offset; double _y_offset;
double _z_offset; double _z_offset;
double _pitch_offset; double _pitch_offset;
double _roll_offset; double _roll_offset;
double _yaw_offset; double _yaw_offset;
double _max_speed; double _max_speed;
std::string _path; std::string _path;
std::string _callsign; std::string _callsign;
std::string _submodel; std::string _submodel;
std::string _name; std::string _name;
std::string _parent; std::string _parent;
/** /**
* Tied-properties helper, record nodes which are tied for easy un-tie-ing * Tied-properties helper, record nodes which are tied for easy un-tie-ing
*/ */
@ -254,7 +254,7 @@ private:
SGSharedPtr<FGFX> _fx; SGSharedPtr<FGFX> _fx;
std::string resolveModelPath(ModelSearchOrder searchOrder); std::vector<std::string> resolveModelPath(ModelSearchOrder searchOrder);
public: public:
object_type getType(); object_type getType();
@ -419,7 +419,7 @@ inline void FGAIBase::setPitchoffset(double p) {
inline void FGAIBase::setRolloffset(double r) { inline void FGAIBase::setRolloffset(double r) {
_roll_offset = r; _roll_offset = r;
} }
inline void FGAIBase::setYawoffset(double y) { inline void FGAIBase::setYawoffset(double y) {
_yaw_offset = y; _yaw_offset = y;