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()
{
}
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;
}

View file

@ -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;