13#include <simgear/compiler.h>
19#include <osgDB/FileUtils>
21#include <simgear/debug/ErrorReportingCallback.hxx>
22#include <simgear/debug/logstream.hxx>
23#include <simgear/misc/sg_path.hxx>
24#include <simgear/props/props.hxx>
25#include <simgear/scene/model/modellib.hxx>
26#include <simgear/scene/util/SGNodeMasks.hxx>
66 _errorContext[key] = value;
78 void modelLoaded(
const std::string& path, SGPropertyNode *prop, osg::Node *n)
81 if (_ready && _modelLoaded.count(path) > 0)
84 _modelLoaded[path] =
true;
86 if(prop->hasChild(
"interior-path")){
87 _interiorPath = prop->getStringValue(
"interior-path");
88 _hasInteriorPath =
true;
92 auto bs = n->getBound();
94 _radius = bs.radius();
97 _fxpath = prop->getStringValue(
"sound/path");
99 _nasal[path]->modelLoaded(path, prop, n);
101 _initialized =
false;
106 void init(
void) { _initialized =
true; }
130 std::string _interiorPath;
132 std::map<string, bool> _modelLoaded;
133 std::map<string, std::unique_ptr<FGNasalModelDataProxy>> _nasal;
135 bool _initialized =
false;
136 bool _hasInteriorPath =
false;
137 bool _hasHighResolutionModel =
false;
138 bool _interiorLoaded =
false;
140 float _radius = -1.0;
141 SGPropertyNode* _root;
143 ErrorContext _errorContext;
148 pos(SGGeod::fromDeg(0.0, 0.0)),
197 aip.getSceneGraph()->setNodeMask(~SG_NODEMASK_TERRAIN_BIT);
205 SGPropertyNode* parent =
props->getParent();
226 osg::ref_ptr<osg::Object> temp = _model.get();
230 _modeldata =
nullptr;
240 _modeldata =
nullptr;
254 setPath(scFileNode->getStringValue(
"model",
259 setHeading(scFileNode->getDoubleValue(
"heading", 0.0));
260 setSpeed(scFileNode->getDoubleValue(
"speed", 0.0));
261 setAltitude(scFileNode->getDoubleValue(
"altitude", 0.0));
262 setLongitude(scFileNode->getDoubleValue(
"longitude", 0.0));
263 setLatitude(scFileNode->getDoubleValue(
"latitude", 0.0));
264 setBank(scFileNode->getDoubleValue(
"roll", 0.0));
265 setPitch(scFileNode->getDoubleValue(
"pitch", 0.0));
269 SGPropertyNode* submodels = scFileNode->getChild(
"submodels");
273 setSMPath(submodels->getStringValue(
"path",
""));
276 string searchOrder = scFileNode->getStringValue(
"search-order",
"");
277 if (!searchOrder.empty()) {
278 if (searchOrder ==
"DATA_ONLY") {
280 }
else if (searchOrder ==
"PREFER_AI") {
282 }
else if (searchOrder ==
"PREFER_DATA") {
285 SG_LOG(SG_AI, SG_WARN,
"invalid model search order " << searchOrder <<
". Use either DATA_ONLY, PREFER_AI or PREFER_DATA");
288 const string modelLowres = scFileNode->getStringValue(
"model-lowres",
"");
289 if (!modelLowres.empty()) {
305 if (_modeldata && _modeldata->needInitialization()) {
311 const auto radius = _modeldata->getRadius();
313 _model->setRadius(radius);
314 _model->dirtyBound();
316 SG_LOG(SG_AI, SG_WARN,
"AIBase: model radius not set.");
320 if (
fgGetBool(
"/sim/sound/aimodels/enabled",
false))
322 const string& fxpath = _modeldata->get_sound_path();
323 if (!fxpath.empty()) {
324 simgear::ErrorReportContext ec(
"ai-model",
_name);
330 _modeldata->addErrorContext(
"multiplayer",
getCallSign());
333 props->setStringValue(
"sim/sound/path", fxpath.c_str());
339 std::stringstream
name;
351 _fx->set_position_geod(
pos );
353 SGQuatd orient = SGQuatd::fromYawPitchRollDeg(
hdg,
pitch,
roll);
354 _fx->set_orientation( orient );
359 _fx->set_velocity( velocity );
367 if(!_modeldata || !_modeldata->hasInteriorPath())
370 if (!_modeldata->getInteriorLoaded()) {
371 _model->setFileName(_model->getNumRanges(), _modeldata->getInteriorPath());
373 bool distance_mode =
fgGetBool(
"/sim/rendering/static-lod/aimp-range-mode-distance",
false);
374 double maxRangeInterior =
fgGetDouble(
"/sim/rendering/static-lod/aimp-interior", 50.0);
377 _model->setRange(0, 0.0, maxRangeInterior);
379 _model->setRange(0, maxRangeInterior, FLT_MAX);
382 _modeldata->setInteriorLoaded(
true);
383 SG_LOG(SG_AI, SG_INFO,
"AIBase: Loading interior model " << _modeldata->getInteriorPath());
390 double maxRangeDetail =
fgGetDouble(
"/sim/rendering/static-lod/aimp-detailed", 3000.0);
391 double maxRangeBare =
fgGetDouble(
"/sim/rendering/static-lod/aimp-bare", 10000.0);
392 double maxRangeInterior =
fgGetDouble(
"/sim/rendering/static-lod/aimp-interior", 50.0);
394 if (_model.valid()) {
395 bool distance_mode =
fgGetBool(
"/sim/rendering/static-lod/aimp-range-mode-distance",
false);
397 _model->setRangeMode(osg::LOD::DISTANCE_FROM_EYE_POINT);
401 if (maxRangeDetail < 0) {
403 if (_modeldata->hasHighResolutionModel()) {
405 _model->setRange(_modeldata->getLowResolutionLoDIndex(), 0.0, 0.0);
406 _model->setRange(_modeldata->getHighResolutionLoDIndex(), 0.0, maxRangeBare);
409 _model->setRange(_modeldata->getLowResolutionLoDIndex(), 0.0, maxRangeBare);
411 }
else if ((
int)maxRangeBare == (
int)maxRangeDetail) {
413 if (_modeldata->hasHighResolutionModel()) {
414 _model->setRange(_modeldata->getLowResolutionLoDIndex(), 0.0, maxRangeBare);
415 _model->setRange(_modeldata->getHighResolutionLoDIndex(), 0.0, 0.0);
418 _model->setRange(_modeldata->getLowResolutionLoDIndex(), 0.0, maxRangeBare);
425 if (maxRangeBare <= 0) {
430 "/sim/rendering/static-lod/aimp-bare is <= 0. This should be a delta on top of aimp-detailed in meters mode. setting to 1.");
433 if (_modeldata->hasHighResolutionModel()) {
434 _model->setRange(_modeldata->getLowResolutionLoDIndex(), maxRangeDetail, maxRangeDetail+maxRangeBare);
435 _model->setRange(_modeldata->getHighResolutionLoDIndex(), 0.0, maxRangeDetail);
438 _model->setRange(_modeldata->getLowResolutionLoDIndex(), 0.0, maxRangeDetail+maxRangeBare);
442 if (_modeldata->getInteriorLoaded()) {
444 _model->setRange(_modeldata->getInteriorLoDIndex(), 0.0, maxRangeInterior);
447 _model->setRangeMode(osg::LOD::PIXEL_SIZE_ON_SCREEN);
458 if (maxRangeDetail < 0) {
460 if (_modeldata->hasHighResolutionModel()) {
462 _model->setRange(_modeldata->getLowResolutionLoDIndex(), 0.0, 0.0);
463 _model->setRange(_modeldata->getHighResolutionLoDIndex(), maxRangeBare, FLT_MAX);
466 _model->setRange(_modeldata->getLowResolutionLoDIndex(), maxRangeBare, FLT_MAX);
468 }
else if ((
int)maxRangeBare == (
int)maxRangeDetail) {
470 if (_modeldata->hasHighResolutionModel()) {
472 _model->setRange(_modeldata->getLowResolutionLoDIndex(), maxRangeBare, FLT_MAX);
473 _model->setRange(_modeldata->getHighResolutionLoDIndex(), 0.0, 0.0);
476 _model->setRange(_modeldata->getLowResolutionLoDIndex(), maxRangeBare, FLT_MAX);
484 if (maxRangeBare > maxRangeDetail) {
486 maxRangeBare = maxRangeDetail;
489 "/sim/rendering/static-lod/aimp-bare greater " <<
490 "than /sim/rendering/static-lod/aimp-detailed when using " <<
491 "/sim/rendering/static-lod/aimp-range-mode-distance=false. Ignoring ai-bare."
495 if (_modeldata->hasHighResolutionModel()) {
496 _model->setRange(_modeldata->getLowResolutionLoDIndex(), maxRangeBare, maxRangeDetail);
497 _model->setRange(_modeldata->getHighResolutionLoDIndex(), maxRangeDetail, FLT_MAX);
500 _model->setRange(_modeldata->getLowResolutionLoDIndex(), maxRangeBare, FLT_MAX );
504 if (_modeldata->getInteriorLoaded()) {
506 _model->setRange(_modeldata->getInteriorLoDIndex(), maxRangeInterior, FLT_MAX);
512 _model->setPriorityScale(_modeldata->getLowResolutionLoDIndex(), 0.2);
513 if (_modeldata->hasHighResolutionModel()) _model->setPriorityScale(_modeldata->getHighResolutionLoDIndex(), 0.2);
514 if (_modeldata->getInteriorLoaded()) _model->setPriorityScale(_modeldata->getInteriorLoDIndex(), 0.2);
521 aip.setVisible(
true);
531 aip.setVisible(
false);
544std::vector<std::string> FGAIBase::resolveModelPath(ModelSearchOrder searchOrder)
549 SG_LOG(SG_AI, SG_DEBUG,
"Resolving model path: DATA only");
550 auto p = simgear::SGModelLib::findDataFile(
model_path);
554 SG_LOG(SG_AI, SG_DEBUG,
"Found model " <<
p);
555 path_list.push_back(
p);
561 path_list.insert(path_list.begin(),
p);
567 path_list.push_back(defaultModelPath.utf8Str());
570 SG_LOG(SG_AI, SG_DEBUG,
"Resolving model path: PREFER_AI/PREFER_DATA");
575 SG_LOG(SG_AI, SG_DEBUG,
"Found AI model: " <<
p);
576 path_list.push_back(
p.utf8Str());
581 if (path_list.empty()) {
584 std::string fallback_path;
585 const SGPropertyNode* fallbackNode =
588 if (fallbackNode !=
nullptr) {
589 fallback_path = fallbackNode->getStringValue();
591 fallback_path =
globals->
get_props()->getNode(
"/sim/multiplay/fallback-models/model", 0,
true)->getStringValue();
595 p.append(fallback_path);
598 path_list.push_back(
p.utf8Str());
613 auto p = simgear::SGModelLib::findDataFile(
model_path);
616 SG_LOG(SG_AI, SG_DEBUG,
"Found DATA model " <<
p);
617 path_list.insert(path_list.end(),
p);
625 assert(path_list.size() != 0);
626 assert(path_list.size() < 3);
628 _modeldata->setHasHighResolutionModel(path_list.size() == 2);
637 SG_LOG(SG_AI, SG_ALERT,
"AIBase: Cannot initialize a model multiple times! " <<
model_path);
641 props->addChild(
"type")->setStringValue(
"AI");
643 _modeldata->addErrorContext(
"ai",
_name);
644 _modeldata->captureErrorContext(
"scenario-path");
647 _modeldata->captureErrorContext(
"traffic-aircraft-callsign");
650 _modeldata->addErrorContext(
"multiplayer",
getCallSign());
654 std::vector<string> model_list = resolveModelPath(searchOrder);
657 _model = SGModelLib::loadPagedModel(model_list,
props, _modeldata);
658 _model->setName(
"AI-model range animation node");
664 if (_model.valid() && _initialized ==
false) {
665 aip.init( _model.get() );
666 aip.setVisible(
true);
669 auto scenery =
globals->get_scenery();
671 scenery->get_models_branch()->addChild(
aip.getSceneGraph());
675 SG_LOG(SG_AI, SG_DEBUG,
"AIBase: Loaded model " <<
model_path);
678 SG_LOG(SG_AI, SG_WARN,
"AIBase: Could not load model " <<
model_path);
689 if (_model.valid()) {
692 props->setStringValue(
"submodels/path",
_path.c_str());
693 SG_LOG(SG_AI, SG_DEBUG,
"AIBase: submodels/path " <<
_path);
702 SG_LOG(SG_AI, SG_WARN,
"AIBase: Could not load model " <<
model_path);
710 return otype == _otype;
715 tie(
"id", SGRawValueMethods<FGAIBase,int>(*
this,
717 tie(
"velocities/true-airspeed-kt", SGRawValuePointer<double>(&
speed));
718 tie(
"velocities/vertical-speed-fps",
719 SGRawValueMethods<FGAIBase,double>(*
this,
723 tie(
"position/altitude-ft",
724 SGRawValueMethods<FGAIBase,double>(*
this,
727 tie(
"position/latitude-deg",
728 SGRawValueMethods<FGAIBase,double>(*
this,
731 tie(
"position/longitude-deg",
732 SGRawValueMethods<FGAIBase,double>(*
this,
736 tie(
"position/global-x",
737 SGRawValueMethods<FGAIBase,double>(*
this,
740 tie(
"position/global-y",
741 SGRawValueMethods<FGAIBase,double>(*
this,
744 tie(
"position/global-z",
745 SGRawValueMethods<FGAIBase,double>(*
this,
749 SGRawValueMethods<FGAIBase,const char*>(*
this,
753 tie(
"sim/multiplay/callsign",
756 tie(
"orientation/pitch-deg", SGRawValuePointer<double>(&
pitch));
757 tie(
"orientation/roll-deg", SGRawValuePointer<double>(&
roll));
758 tie(
"orientation/true-heading-deg", SGRawValuePointer<double>(&
hdg));
760 tie(
"radar/in-range", SGRawValuePointer<bool>(&
in_range));
761 tie(
"radar/bearing-deg", SGRawValuePointer<double>(&
bearing));
762 tie(
"radar/elevation-deg", SGRawValuePointer<double>(&
elevation));
763 tie(
"radar/range-nm", SGRawValuePointer<double>(&
range));
766 tie(
"radar/x-shift", SGRawValuePointer<double>(&
x_shift));
767 tie(
"radar/y-shift", SGRawValuePointer<double>(&
y_shift));
768 tie(
"radar/rotation", SGRawValuePointer<double>(&
rotation));
769 tie(
"radar/ht-diff-ft", SGRawValuePointer<double>(&
ht_diff));
770 tie(
"subID", SGRawValuePointer<int>(&
_subID));
776 props->setBoolValue(
"controls/glide-path",
true);
778 props->setStringValue(
"controls/flight/lateral-mode",
"roll");
779 props->setDoubleValue(
"controls/flight/target-hdg",
hdg);
780 props->setDoubleValue(
"controls/flight/target-roll",
roll);
782 props->setStringValue(
"controls/flight/vertical-mode",
"alt");
786 auto node =
props->getNode(
"controls/flight/longitude-mode",
true);
787 node->alias(
props->getNode(
"controls/flight/vertical-mode"),
false);
790 props->setDoubleValue(
"controls/flight/target-pitch",
pitch);
792 props->setDoubleValue(
"controls/flight/target-spd",
speed);
794 props->setBoolValue(
"sim/sound/avionics/enabled",
false);
795 props->setDoubleValue(
"sim/sound/avionics/volume", 0.0);
796 props->setBoolValue(
"sim/sound/avionics/external-view",
false);
797 props->setBoolValue(
"sim/current-view/internal",
false);
803 props->setBoolValue(
"/sim/controls/radar",
true);
821 if (!
manager->isRadarEnabled())
824 const double radar_range_m =
manager->radarRangeM() * 1.1;
825 bool force_on =
manager->enableRadarDebug();
826 double d = dist(SGVec3d::fromGeod(
pos),
globals->get_aircraft_position_cart());
827 double dFt = d * SG_METER_TO_FEET;
835 double user_heading =
manager->get_user_heading();
836 double user_pitch =
manager->get_user_pitch();
838 range = d * SG_METER_TO_NM;
872 SG_NORMALIZE_RANGE(
rotation, 0.0, 360.0);
883 SGQuatd hlTrans = SGQuatd::fromLonLat(
pos);
887 hlTrans *= SGQuatd::fromYawPitchRollDeg(
hdg,
pitch,
roll);
891 SGVec3d off = hlTrans.backTransform(_off);
894 SGVec3d cartPos = SGVec3d::fromGeod(
pos);
896 return cartPos + off;
900 SGVec3d cartPos = SGVec3d::fromGeod(
pos);
905 const simgear::BVHMaterial** material)
const {
906 return globals->get_scenery()->get_elevation_m(
pos, elev, material,
911 SGPropertyNode* positionNode = scFileNode->getChild(key);
916 position(0) = -positionNode->getDoubleValue(
"x-offset-m", 0);
917 position(1) = positionNode->getDoubleValue(
"y-offset-m", 0);
918 position(2) = -positionNode->getDoubleValue(
"z-offset-m", 0);
922 position = SGVec3d::zeros();
955 SG_LOG(SG_AI, SG_ALERT,
"AIBase: " <<
_name <<
" parent not set ");
959 const SGPropertyNode_ptr ai =
fgGetNode(
"/ai/models",
true);
961 for (
int i = ai->nChildren() - 1;
i >= -1;
i--) {
962 SGPropertyNode_ptr model;
967 model = ai->getChild(
i);
968 const string name = model->getStringValue(
"name");
970 if (!model->nChildren()){
988 SG_LOG(SG_AI, SG_ALERT,
"AIBase: " <<
_name <<
" parent not found: dying ");
995 return pos.getLongitudeDeg();
999 return pos.getLatitudeDeg();
1003 return pos.getElevationFt();
1033 return inpos.getElevationFt() -
_elevation_m * SG_METER_TO_FEET;
1049 return (
fgGetFloat(
"/sim/time/sun-angle-rad") > 1.57);
1133 return _path.c_str();
1137 return _name.c_str();
1169 return !
fp ||
fp->isValidPlan();
1179 return _model->getNumChildren() > 0;
static std::string default_model
double _getAltitudeAGL(SGGeod inpos, double start)
void setSpeed(double speed_KTAS)
SGPropertyNode * getPositionFromNode(SGPropertyNode *scFileNode, const std::string &key, SGVec3d &position)
simgear::TiedPropertyList _tiedProperties
static const double lbs_to_slugs
double _getZOffset() const
void setFallbackModelIndex(const int i)
void setLatitude(double latitude)
virtual void readFromScenario(SGPropertyNode *scFileNode)
virtual double getDefaultModelRadius()
int _fallback_model_index
double _getElevationFt() const
double _getYOffset() const
SGVec3d getCartPos() const
const std::string & getCallSign() const
double _getLatitude() const
void _setAltitude(double _alt)
SGVec3d getCartPosAt(const SGVec3d &off) const
SGPropertyNode * _getProps() const
double _getCartPosX() const
double _get_speed_north_fps() const
void setFlightPlan(std::unique_ptr< FGAIFlightPlan > f)
void setPathLowres(std::string model)
void updateLOD()
update LOD properties of the model
void setCollisionLength(int range)
FGAIBase(object_type ot, bool enableHot)
void setServiceable(bool serviceable)
const char * _getSMPath() const
double _getImpactElevFt() const
bool getGroundElevationM(const SGGeod &pos, double &elev, const simgear::BVHMaterial **material) const
void setBank(double bank)
SGPropertyNode_ptr _selected_ac
SGGeod getGeodPos() const
double _getCartPosY() const
double _getHeading() const
std::string _scenarioPath
virtual bool init(ModelSearchOrder searchOrder)
double _getImpactHdg() const
void setAltitude(double altitude_ft)
virtual void update(double dt)
double _getXOffset() const
std::unique_ptr< FGAIFlightPlan > fp
double UpdateRadar(FGAIManager *manager)
void setGeodPos(const SGGeod &pos)
double _getImpactRoll() const
static int _newAIModelID()
double _getAltitude() const
double _getCartPosZ() const
SGPropertyNode_ptr model_removed
bool isa(object_type otype)
double _getLongitude() const
void _setLatitude(double latitude)
const char * _getName() const
const char * _getCallsign() const
int _getFallbackModelIndex() const
void setSMPath(const std::string &p)
const char * _getPath() const
void setLongitude(double longitude)
void setPath(const char *model)
void setScenarioPath(const std::string &scenarioPath)
double speed_east_deg_sec
void setCollisionHeight(int height)
osg::LOD * getSceneBranch() const
double _getImpactPitch() const
bool _getServiceable() const
double _getImpactLon() const
std::string model_path_lowres
void removeModel()
Cleanly remove the model and let the scenery database pager do the clean-up work.
const char * _getSubmodel() const
void _setVS_fps(double _vs)
void setHeading(double heading)
void _setLongitude(double longitude)
double speed_north_deg_sec
double _get_speed_east_fps() const
double _getImpactSpeed() const
double _getVS_fps() const
void setPitch(double newpitch)
ModelSearchOrder _searchOrder
SGPropertyNode_ptr replay_time
void tie(const char *aRelPath, const SGRawValue< T > &aRawValue)
Tied-properties helper, record nodes which are tied for easy un-tie-ing.
double _getImpactLat() const
ErrorContext getErrorContext() const override
FGAIModelData * clone() const override
std::string & getInteriorPath()
bool getInteriorLoaded(void)
bool hasHighResolutionModel(void)
int getHighResolutionLoDIndex(void)
void setHasHighResolutionModel(bool hasHighResolutionModel)
bool needInitialization(void)
void init(void)
init hook to be called after model is loaded.
void captureErrorContext(const std::string &key)
std::string & get_sound_path()
int getInteriorLoDIndex(void)
bool hasInteriorPath(void)
void modelLoaded(const std::string &path, SGPropertyNode *prop, osg::Node *n)
osg callback, thread-safe
FGAIModelData(SGPropertyNode *root=nullptr)
void addErrorContext(const std::string &key, const std::string &value)
int getLowResolutionLoDIndex(void)
void setInteriorLoaded(const bool state)
Generator for FlightGear model sound effects.
PathList get_data_paths() const
Get list of data locations.
SGPropertyNode * get_props()
Thread-safe proxy for FGNasalModelData.
osg::Group * get_models_branch() const
flightgear::SceneryPager * getPager()
static std::string threadSpecificContextValue(const std::string &key)
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
std::vector< std::string > string_list
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
bool fgSetString(char const *name, char const *str)
Set a string value for a property.
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
float fgGetFloat(const char *name, float defaultValue)
Get a float value for a property.