]> git.mxchange.org Git - flightgear.git/blobdiff - src/AIModel/AIBase.cxx
Make the sound-manager optional in a few places.
[flightgear.git] / src / AIModel / AIBase.cxx
index d143296a18f04b8f931701721924fb4bf5634fd8..7cfcfcb0a33418517ff24f767da13d599e186191 100644 (file)
@@ -24,6 +24,8 @@
 #  include <config.h>
 #endif
 
+#include <string.h>
+
 #include <simgear/compiler.h>
 
 #include <string>
 #include <osg/Node>
 #include <osgDB/FileUtils>
 
-#include <simgear/math/SGMath.hxx>
 #include <simgear/misc/sg_path.hxx>
 #include <simgear/scene/model/modellib.hxx>
 #include <simgear/scene/util/SGNodeMasks.hxx>
+#include <simgear/sound/soundmgr_openal.hxx>
 #include <simgear/debug/logstream.hxx>
 #include <simgear/props/props.hxx>
 
 #include <Main/globals.hxx>
 #include <Scenery/scenery.hxx>
 #include <Scripting/NasalSys.hxx>
+#include <Sound/fg_fx.hxx>
 
 #include "AIBase.hxx"
 #include "AIManager.hxx"
@@ -52,12 +55,15 @@ const double FGAIBase::lbs_to_slugs = 0.031080950172;   //conversion factor
 
 using namespace simgear;
 
-FGAIBase::FGAIBase(object_type ot) :
+FGAIBase::FGAIBase(object_type ot, bool enableHot) :
+    _max_speed(300),
+    _name(""),
+    _parent(""),
     props( NULL ),
     model_removed( fgGetNode("/ai/models/model-removed", true) ),
     manager( NULL ),
+    _installed(false),
     fp( NULL ),
-
     _impact_lat(0),
     _impact_lon(0),
     _impact_elev(0),
@@ -65,10 +71,12 @@ FGAIBase::FGAIBase(object_type ot) :
     _impact_pitch(0),
     _impact_roll(0),
     _impact_speed(0),
-
     _refID( _newAIModelID() ),
     _otype(ot),
-    _initialized(false)
+    _initialized(false),
+    _modeldata(0),
+    _fx(0)
+
 {
     tgt_heading = hdg = tgt_altitude_ft = tgt_speed = 0.0;
     tgt_roll = roll = tgt_pitch = tgt_yaw = tgt_vs = vs = pitch = 0.0;
@@ -81,7 +89,7 @@ FGAIBase::FGAIBase(object_type ot) :
     delete_me = false;
     _impact_reported = false;
     _collision_reported = false;
-       _expiry_reported = false;
+    _expiry_reported = false;
 
     _subID = 0;
 
@@ -118,13 +126,15 @@ FGAIBase::FGAIBase(object_type ot) :
     p = 1e5;
     a = 340;
     Mach = 0;
+
+    // explicitly disable HOT for (most) AI models
+    if (!enableHot)
+        aip.getSceneGraph()->setNodeMask(~SG_NODEMASK_TERRAIN_BIT);
 }
 
 FGAIBase::~FGAIBase() {
     // Unregister that one at the scenery manager
-    if (globals->get_scenery()) {
-        globals->get_scenery()->get_scene_graph()->removeChild(aip.getSceneGraph());
-    }
+    removeModel();
 
     if (props) {
         SGPropertyNode* parent = props->getParent();
@@ -132,10 +142,51 @@ FGAIBase::~FGAIBase() {
         if (parent)
             model_removed->setStringValue(props->getPath());
     }
-    delete fp;
+
+  // refID=0 is supposedley impossible, refID=1 is the special ai_ac aircaft
+  // representing the current user, in the ATCManager. Maybe both these
+  // tests could die?
+    if (_fx && _refID != 0 && _refID !=  1) {
+        SGSoundMgr *smgr = globals->get_soundmgr();
+        if (smgr) {
+          stringstream name;
+          name <<  "aifx:";
+          name << _refID;
+          smgr->remove(name.str());
+        }
+    }
+
+    if (fp)
+        delete fp;
     fp = 0;
 }
 
+/** Cleanly remove the model
+ * and let the scenery database pager do the clean-up work.
+ */
+void
+FGAIBase::removeModel()
+{
+    if (!_model.valid())
+        return;
+
+    FGScenery* pSceneryManager = globals->get_scenery();
+    if (pSceneryManager)
+    {
+        osg::ref_ptr<osg::Object> temp = _model.get();
+        pSceneryManager->get_scene_graph()->removeChild(aip.getSceneGraph());
+        // withdraw from SGModelPlacement and drop own reference (unref)
+        aip.init( 0 );
+        _model = 0;
+        // pass it on to the pager, to be be deleted in the pager thread
+        pSceneryManager->getPager()->queueDeleteRequest(temp);
+    }
+    else
+    {
+        SG_LOG(SG_AI, SG_ALERT, "AIBase: Could not unload model. Missing scenery manager!");
+    }
+}
+
 void FGAIBase::readFromScenario(SGPropertyNode* scFileNode)
 {
     if (!scFileNode)
@@ -170,6 +221,64 @@ void FGAIBase::update(double dt) {
 
     ft_per_deg_lat = 366468.96 - 3717.12 * cos(pos.getLatitudeRad());
     ft_per_deg_lon = 365228.16 * cos(pos.getLatitudeRad());
+
+    if ( _fx )
+    {
+        // update model's audio sample values
+        _fx->set_position_geod( pos );
+
+        SGQuatd orient = SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
+        _fx->set_orientation( orient );
+
+        SGVec3d velocity;
+        velocity = SGVec3d( speed_north_deg_sec, speed_east_deg_sec,
+                            pitch*speed );
+        _fx->set_velocity( velocity );
+    }
+    else if ((_modeldata)&&(_modeldata->needInitilization()))
+    {
+        // process deferred nasal initialization,
+        // which must be done in main thread
+        _modeldata->init();
+
+        // sound initialization
+        if (fgGetBool("/sim/sound/aimodels/enabled",false))
+        {
+            string fxpath = _modeldata->get_sound_path();
+            if (fxpath != "")
+            {
+                props->setStringValue("sim/sound/path", fxpath.c_str());
+
+                // initialize the sound configuration
+                stringstream name;
+                name <<  "aifx:";
+                name << _refID;
+                _fx = new FGFX(name.str(), props);
+                _fx->init();
+            }
+        }
+    }
+}
+
+/** 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);
+    if (_model.valid())
+    {
+        if( maxRangeDetail == 0.0 )
+        {
+            // disable LOD
+            _model->setRange(0, 0.0,     FLT_MAX);
+            _model->setRange(1, FLT_MAX, FLT_MAX);
+        }
+        else
+        {
+            _model->setRange(0, 0.0, maxRangeDetail);
+            _model->setRange(1, maxRangeDetail,maxRangeBare);
+        }
+    }
 }
 
 void FGAIBase::Transform() {
@@ -191,33 +300,68 @@ void FGAIBase::Transform() {
 
 }
 
-bool FGAIBase::init(bool search_in_AI_path) {
-    osg::ref_ptr<osgDB::ReaderWriter::Options> opt=
-        new osgDB::ReaderWriter::Options(*osgDB::Registry::instance()->getOptions());
+bool FGAIBase::init(bool search_in_AI_path)
+{
+    if (_model.valid())
+    {
+        SG_LOG(SG_AI, SG_ALERT, "AIBase: Cannot initialize a model multiple times! " << model_path);
+        return false;
+    }
 
+    string f;
     if(search_in_AI_path)
     {
-        SGPath ai_path(globals->get_fg_root());
-        ai_path.append("AI");
-        opt->getDatabasePathList().push_front(ai_path.str());
+    // setup a modified Options structure, with only the $fg-root/AI defined;
+    // we'll check that first, then give the normal search logic a chance.
+    // this ensures that models in AI/ are preferred to normal models, where
+    // both exist.
+        osg::ref_ptr<osgDB::ReaderWriter::Options> 
+          opt(osg::clone(osgDB::Registry::instance()->getOptions(), osg::CopyOp::SHALLOW_COPY));
+
+        SGPath ai_path(globals->get_fg_root(), "AI");
+        opt->setDatabasePath(ai_path.str());
+        
+        f = osgDB::findDataFile(model_path, opt.get());
     }
 
-    string f = osgDB::findDataFile(model_path, opt.get());
-
+    if (f.empty()) {
+      f = simgear::SGModelLib::findDataFile(model_path);
+    }
+    
     if(f.empty())
         f = fgGetString("/sim/multiplay/default-model", default_model);
-
-    model = load3DModel(f, props);
-
-    if (model.valid() && _initialized == false) {
-        aip.init( model.get() );
+    else
+        _installed = true;
+
+    _modeldata = new FGAIModelData(props);
+    osg::Node * mdl = SGModelLib::loadDeferredModel(f, props, _modeldata);
+
+    _model = new osg::LOD;
+    _model->setName("AI-model range animation node");
+
+    _model->addChild( mdl, 0, FLT_MAX );
+    _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(mdl);
+    if (_model.valid() && _initialized == false) {
+        aip.init( _model.get() );
         aip.setVisible(true);
         invisible = false;
         globals->get_scenery()->get_scene_graph()->addChild(aip.getSceneGraph());
         _initialized = true;
 
+        SG_LOG(SG_AI, SG_DEBUG, "AIBase: Loaded model " << model_path);
+
     } else if (!model_path.empty()) {
-        SG_LOG(SG_INPUT, SG_WARN, "AIBase: Could not load model " << model_path);
+        SG_LOG(SG_AI, SG_WARN, "AIBase: Could not load model " << model_path);
+        // not properly installed...
+        _installed = false;
     }
 
     setDie(false);
@@ -226,87 +370,88 @@ bool FGAIBase::init(bool search_in_AI_path) {
 
 void FGAIBase::initModel(osg::Node *node)
 {
-    if (model.valid()) {
+    if (_model.valid()) { 
 
-        fgSetString("/ai/models/model-added", props->getPath().c_str());
+        if( _path != ""){
+            props->setStringValue("submodels/path", _path.c_str());
+            SG_LOG(SG_AI, SG_DEBUG, "AIBase: submodels/path " << _path);
+        }
+
+        if( _parent!= ""){
+            props->setStringValue("parent-name", _parent.c_str());
+        }
 
+        fgSetString("/ai/models/model-added", props->getPath().c_str());
     } else if (!model_path.empty()) {
-        SG_LOG(SG_INPUT, SG_WARN, "AIBase: Could not load model " << model_path);
+        SG_LOG(SG_AI, SG_WARN, "AIBase: Could not load model " << model_path);
     }
 
-    props->setStringValue("submodels/path", _path.c_str());
     setDie(false);
 }
 
 
-osg::Node* FGAIBase::load3DModel(const string &path, SGPropertyNode *prop_root)
-{
-  model = SGModelLib::loadPagedModel(path, prop_root, new FGNasalModelData(prop_root));
-  initModel(model.get());
-  return model.get();
-}
-
 bool FGAIBase::isa( object_type otype ) {
     return otype == _otype;
 }
 
 
 void FGAIBase::bind() {
-    props->tie("id", SGRawValueMethods<FGAIBase,int>(*this,
+    _tiedProperties.setRoot(props);
+    tie("id", SGRawValueMethods<FGAIBase,int>(*this,
         &FGAIBase::getID));
-    props->tie("velocities/true-airspeed-kt",  SGRawValuePointer<double>(&speed));
-    props->tie("velocities/vertical-speed-fps",
+    tie("velocities/true-airspeed-kt",  SGRawValuePointer<double>(&speed));
+    tie("velocities/vertical-speed-fps",
         SGRawValueMethods<FGAIBase,double>(*this,
         &FGAIBase::_getVS_fps,
         &FGAIBase::_setVS_fps));
 
-    props->tie("position/altitude-ft",
+    tie("position/altitude-ft",
         SGRawValueMethods<FGAIBase,double>(*this,
         &FGAIBase::_getAltitude,
         &FGAIBase::_setAltitude));
-    props->tie("position/latitude-deg",
+    tie("position/latitude-deg",
         SGRawValueMethods<FGAIBase,double>(*this,
         &FGAIBase::_getLatitude,
         &FGAIBase::_setLatitude));
-    props->tie("position/longitude-deg",
+    tie("position/longitude-deg",
         SGRawValueMethods<FGAIBase,double>(*this,
         &FGAIBase::_getLongitude,
         &FGAIBase::_setLongitude));
 
-    props->tie("position/global-x",
+    tie("position/global-x",
         SGRawValueMethods<FGAIBase,double>(*this,
         &FGAIBase::_getCartPosX,
         0));
-    props->tie("position/global-y",
+    tie("position/global-y",
         SGRawValueMethods<FGAIBase,double>(*this,
         &FGAIBase::_getCartPosY,
         0));
-    props->tie("position/global-z",
+    tie("position/global-z",
         SGRawValueMethods<FGAIBase,double>(*this,
         &FGAIBase::_getCartPosZ,
         0));
-    props->tie("callsign",
+    tie("callsign",
         SGRawValueMethods<FGAIBase,const char*>(*this,
         &FGAIBase::_getCallsign,
         0));
 
-    props->tie("orientation/pitch-deg",   SGRawValuePointer<double>(&pitch));
-    props->tie("orientation/roll-deg",    SGRawValuePointer<double>(&roll));
-    props->tie("orientation/true-heading-deg", SGRawValuePointer<double>(&hdg));
-
-    props->tie("radar/in-range", SGRawValuePointer<bool>(&in_range));
-    props->tie("radar/bearing-deg",   SGRawValuePointer<double>(&bearing));
-    props->tie("radar/elevation-deg", SGRawValuePointer<double>(&elevation));
-    props->tie("radar/range-nm", SGRawValuePointer<double>(&range));
-    props->tie("radar/h-offset", SGRawValuePointer<double>(&horiz_offset));
-    props->tie("radar/v-offset", SGRawValuePointer<double>(&vert_offset));
-    props->tie("radar/x-shift", SGRawValuePointer<double>(&x_shift));
-    props->tie("radar/y-shift", SGRawValuePointer<double>(&y_shift));
-    props->tie("radar/rotation", SGRawValuePointer<double>(&rotation));
-    props->tie("radar/ht-diff-ft", SGRawValuePointer<double>(&ht_diff));
-    props->tie("subID", SGRawValuePointer<int>(&_subID));
-    props->tie("controls/lighting/nav-lights",
-        SGRawValueFunctions<bool>(_isNight));
+    tie("orientation/pitch-deg",   SGRawValuePointer<double>(&pitch));
+    tie("orientation/roll-deg",    SGRawValuePointer<double>(&roll));
+    tie("orientation/true-heading-deg", SGRawValuePointer<double>(&hdg));
+
+    tie("radar/in-range", SGRawValuePointer<bool>(&in_range));
+    tie("radar/bearing-deg",   SGRawValuePointer<double>(&bearing));
+    tie("radar/elevation-deg", SGRawValuePointer<double>(&elevation));
+    tie("radar/range-nm", SGRawValuePointer<double>(&range));
+    tie("radar/h-offset", SGRawValuePointer<double>(&horiz_offset));
+    tie("radar/v-offset", SGRawValuePointer<double>(&vert_offset));
+    tie("radar/x-shift", SGRawValuePointer<double>(&x_shift));
+    tie("radar/y-shift", SGRawValuePointer<double>(&y_shift));
+    tie("radar/rotation", SGRawValuePointer<double>(&rotation));
+    tie("radar/ht-diff-ft", SGRawValuePointer<double>(&ht_diff));
+    tie("subID", SGRawValuePointer<int>(&_subID));
+    tie("controls/lighting/nav-lights", SGRawValueFunctions<bool>(_isNight));
+
     props->setBoolValue("controls/lighting/beacon", true);
     props->setBoolValue("controls/lighting/strobe", true);
     props->setBoolValue("controls/glide-path", true);
@@ -319,42 +464,21 @@ void FGAIBase::bind() {
     props->setDoubleValue("controls/flight/target-alt", altitude_ft);
     props->setDoubleValue("controls/flight/target-pitch", pitch);
 
-   props->setDoubleValue("controls/flight/target-spd", speed);
+    props->setDoubleValue("controls/flight/target-spd", speed);
 
+    props->setBoolValue("sim/sound/avionics/enabled", false);
+    props->setDoubleValue("sim/sound/avionics/volume", 0.0);
+    props->setBoolValue("sim/sound/avionics/external-view", false);
+    props->setBoolValue("sim/current-view/internal", false);
 }
 
 void FGAIBase::unbind() {
-    props->untie("id");
-    props->untie("velocities/true-airspeed-kt");
-    props->untie("velocities/vertical-speed-fps");
-
-    props->untie("position/altitude-ft");
-    props->untie("position/latitude-deg");
-    props->untie("position/longitude-deg");
-    props->untie("position/global-x");
-    props->untie("position/global-y");
-    props->untie("position/global-z");
-    props->untie("callsign");
-
-    props->untie("orientation/pitch-deg");
-    props->untie("orientation/roll-deg");
-    props->untie("orientation/true-heading-deg");
-
-    props->untie("radar/in-range");
-    props->untie("radar/bearing-deg");
-    props->untie("radar/elevation-deg");
-    props->untie("radar/range-nm");
-    props->untie("radar/h-offset");
-    props->untie("radar/v-offset");
-    props->untie("radar/x-shift");
-    props->untie("radar/y-shift");
-    props->untie("radar/rotation");
-    props->untie("radar/ht-diff-ft");
-
-    props->untie("controls/lighting/nav-lights");
+    _tiedProperties.Untie();
 
-    props->setBoolValue("/sim/controls/radar/", true);
+    props->setBoolValue("/sim/controls/radar", true);
 
+    // drop reference to sound effects now
+    _fx = 0;
 }
 
 double FGAIBase::UpdateRadar(FGAIManager* manager) {
@@ -472,7 +596,7 @@ SGVec3d FGAIBase::getCartPosAt(const SGVec3d& _off) const {
     hlTrans *= SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll);
 
     // The offset converted to the usual body fixed coordinate system
-    // rotated to the earth fiexed coordinates axis
+    // rotated to the earth fixed coordinates axis
     SGVec3d off = hlTrans.backTransform(_off);
 
     // Add the position offset of the AIModel to gain the earth centered position
@@ -487,9 +611,9 @@ SGVec3d FGAIBase::getCartPos() const {
 }
 
 bool FGAIBase::getGroundElevationM(const SGGeod& pos, double& elev,
-                                   const SGMaterial** material) const {
+                                   const simgear::BVHMaterial** material) const {
     return globals->get_scenery()->get_elevation_m(pos, elev, material,
-                                                   model.get());
+                                                   _model.get());
 }
 
 double FGAIBase::_getCartPosX() const {
@@ -525,6 +649,52 @@ void FGAIBase::_setSubID( int s ) {
     _subID = s;
 }
 
+bool FGAIBase::setParentNode() {
+
+    if (_parent == ""){
+       SG_LOG(SG_AI, SG_ALERT, "AIBase: " << _name
+            << " parent not set ");
+       return false;
+    }
+
+    const SGPropertyNode_ptr ai = fgGetNode("/ai/models", true);
+
+    for (int i = ai->nChildren() - 1; i >= -1; i--) {
+        SGPropertyNode_ptr model;
+
+        if (i < 0) { // last iteration: selected model
+            model = _selected_ac;
+        } else {
+            model = ai->getChild(i);
+            string path = ai->getPath();
+            const string name = model->getStringValue("name");
+
+            if (!model->nChildren()){
+                continue;
+            }
+            if (name == _parent) {
+                _selected_ac = model;  // save selected model for last iteration
+                break;
+            }
+
+        }
+        if (!model)
+            continue;
+
+    }// end for loop
+
+    if (_selected_ac != 0){
+        const string name = _selected_ac->getStringValue("name");
+        return true;
+    } else {
+        SG_LOG(SG_AI, SG_ALERT, "AIBase: " << _name
+            << " parent not found: dying ");
+        setDie(true);
+        return false;
+    }
+
+}
+
 double FGAIBase::_getLongitude() const {
     return pos.getLongitudeDeg();
 }
@@ -533,7 +703,7 @@ double FGAIBase::_getLatitude() const {
     return pos.getLatitudeDeg();
 }
 
-double FGAIBase::_getElevationFt () const {
+double FGAIBase::_getElevationFt() const {
     return pos.getElevationFt();
 }
 
@@ -542,7 +712,7 @@ double FGAIBase::_getRdot() const {
 }
 
 double FGAIBase::_getVS_fps() const {
-    return vs*60.0;
+    return vs/60.0;
 }
 
 double FGAIBase::_get_speed_east_fps() const {
@@ -554,13 +724,19 @@ double FGAIBase::_get_speed_north_fps() const {
 }
 
 void FGAIBase::_setVS_fps( double _vs ) {
-    vs = _vs/60.0;
+    vs = _vs*60.0;
 }
 
 double FGAIBase::_getAltitude() const {
     return altitude_ft;
 }
 
+double FGAIBase::_getAltitudeAGL(SGGeod inpos, double start){
+    getGroundElevationM(SGGeod::fromGeodM(inpos, start),
+        _elevation_m, NULL);
+    return inpos.getElevationFt() - _elevation_m * SG_METER_TO_FEET;
+}
+
 bool FGAIBase::_getServiceable() const {
     return serviceable;
 }
@@ -714,3 +890,28 @@ int FGAIBase::_newAIModelID() {
     return id;
 }
 
+
+FGAIModelData::FGAIModelData(SGPropertyNode *root)
+  : _nasal( new FGNasalModelDataProxy(root) ),
+    _ready(false),
+    _initialized(false)
+{
+}
+
+FGAIModelData::~FGAIModelData()
+{
+    delete _nasal;
+    _nasal = NULL;
+}
+
+void FGAIModelData::modelLoaded(const string& path, SGPropertyNode *prop, osg::Node *n)
+{
+    // WARNING: Called in a separate OSG thread! Only use thread-safe stuff here...
+    if (_ready)
+        return;
+
+    _fxpath = prop->getStringValue("sound/path");
+    _nasal->modelLoaded(path, prop, n);
+
+    _ready = true;
+}