// FGAIGroundVehicle - FGAIShip-derived class creates an AI Ground Vehicle // by adding a ground following utility // // Written by Vivian Meazza, started August 2009. // - vivian.meazza at lineone.net // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #ifdef HAVE_CONFIG_H # include #endif #include #include
#include #include #include #include "AIGroundVehicle.hxx" FGAIGroundVehicle::FGAIGroundVehicle() : FGAIShip(otGroundVehicle), _pitch(0), _pitch_deg(0), _speed_kt(0), _selected_ac(0), _range_ft(0), _relbrg (0), _parent_speed(0), _dt_count(0), _next_run(0), _parent_x_offset(0), _parent_y_offset(0), _parent("") { invisible = false; } FGAIGroundVehicle::~FGAIGroundVehicle() {} void FGAIGroundVehicle::readFromScenario(SGPropertyNode* scFileNode) { if (!scFileNode) return; FGAIShip::readFromScenario(scFileNode); setNoRoll(scFileNode->getBoolValue("no-roll", true)); setName(scFileNode->getStringValue("name", "groundvehicle")); setSMPath(scFileNode->getStringValue("submodel-path", "")); setContactX1offset(scFileNode->getDoubleValue("contact-x1-offset", 0.0)); setContactX2offset(scFileNode->getDoubleValue("contact-x2-offset", 0.0)); setXOffset(scFileNode->getDoubleValue("hitch-x-offset", 38.55)); setYOffset(scFileNode->getDoubleValue("hitch-y-offset", 0.0)); setPitchoffset(scFileNode->getDoubleValue("pitch-offset", 0.0)); setRolloffset(scFileNode->getDoubleValue("roll-offset", 0.0)); setYawoffset(scFileNode->getDoubleValue("yaw-offset", 0.0)); setPitchCoeff(scFileNode->getDoubleValue("pitch-coefficient", 0.1)); setElevCoeff(scFileNode->getDoubleValue("elevation-coefficient", 0.25)); setParentName(scFileNode->getStringValue("parent", "")); setTowAngleGain(scFileNode->getDoubleValue("tow-angle-gain", 2.0)); setTowAngleLimit(scFileNode->getDoubleValue("tow-angle-limit-deg", 2.0)); //we may need these later for towed vehicles // setSubID(scFileNode->getIntValue("SubID", 0)); // setGroundOffset(scFileNode->getDoubleValue("ground-offset", 0.0)); // setFormate(scFileNode->getBoolValue("formate", true)); } void FGAIGroundVehicle::bind() { FGAIShip::bind(); props->tie("controls/constants/elevation-coeff", SGRawValuePointer(&_elevation_coeff)); props->tie("controls/constants/pitch-coeff", SGRawValuePointer(&_pitch_coeff)); props->tie("position/ht-AGL-ft", SGRawValuePointer(&_ht_agl_ft)); props->tie("hitch/rel-bearing-deg", SGRawValuePointer(&_relbrg)); props->tie("hitch/tow-angle-deg", SGRawValuePointer(&_tow_angle)); props->tie("hitch/range-ft", SGRawValuePointer(&_range_ft)); props->tie("hitch/x-offset-ft", SGRawValuePointer(&_x_offset)); props->tie("hitch/y-offset-ft", SGRawValuePointer(&_y_offset)); props->tie("hitch/parent-x-offset-ft", SGRawValuePointer(&_parent_x_offset)); props->tie("hitch/parent-y-offset-ft", SGRawValuePointer(&_parent_y_offset)); props->tie("controls/constants/tow-angle/gain", SGRawValuePointer(&_tow_angle_gain)); props->tie("controls/constants/tow-angle/limit-deg", SGRawValuePointer(&_tow_angle_limit)); props->tie("controls/contact-x1-offset-ft", SGRawValuePointer(&_contact_x1_offset)); props->tie("controls/contact-x2-offset-ft", SGRawValuePointer(&_contact_x2_offset)); } void FGAIGroundVehicle::unbind() { FGAIShip::unbind(); props->untie("controls/constants/elevation-coeff"); props->untie("position/ht-AGL-ft"); props->untie("controls/constants/pitch-coeff"); props->untie("hitch/rel-bearing-deg"); props->untie("hitch/tow-angle-deg"); props->untie("hitch/range-ft"); props->untie("hitch/x-offset-ft"); props->untie("hitch/y-offset-ft"); props->untie("hitch/parent-x-offset-ft"); props->untie("hitch/parent-y-offset-ft"); props->untie("controls/constants/tow-angle/gain"); props->untie("controls/constants/tow-angle/limit-deg"); props->untie("controls/contact-x1-offset-ft"); props->untie("controls/contact-x2-offset-ft"); } bool FGAIGroundVehicle::init(bool search_in_AI_path) { if (!FGAIShip::init(search_in_AI_path)) return false; invisible = false; _limit = 200; no_roll = true; return true; } void FGAIGroundVehicle::update(double dt) { // SG_LOG(SG_GENERAL, SG_ALERT, "updating GroundVehicle: " << _name ); RunGroundVehicle(dt); // FGAIShip::update(dt); } void FGAIGroundVehicle::setNoRoll(bool nr) { no_roll = nr; } void FGAIGroundVehicle::setContactX1offset(double x1) { _contact_x1_offset = x1; } void FGAIGroundVehicle::setContactX2offset(double x2) { _contact_x2_offset = x2; } void FGAIGroundVehicle::setXOffset(double x) { _x_offset = x; } void FGAIGroundVehicle::setYOffset(double y) { _y_offset = y; } void FGAIGroundVehicle::setPitchCoeff(double pc) { _pitch_coeff = pc; } void FGAIGroundVehicle::setElevCoeff(double ec) { _elevation_coeff = ec; } void FGAIGroundVehicle::setTowAngleGain(double g) { _tow_angle_gain = g; } void FGAIGroundVehicle::setTowAngleLimit(double l) { _tow_angle_limit = l; } void FGAIGroundVehicle::setElevation(double h, double dt, double coeff){ double c = dt / (coeff + dt); _elevation_ft = (h * c) + (_elevation_ft * (1 - c)); } void FGAIGroundVehicle::setPitch(double p, double dt, double coeff){ double c = dt / (coeff + dt); _pitch_deg = (p * c) + (_pitch_deg * (1 - c)); } void FGAIGroundVehicle::setParentName(const string& p) { _parent = p; } void FGAIGroundVehicle::setTowAngle(double ta, double dt, double coeff){ //_tow_angle = ta * _tow_angle_gain; _tow_angle = pow(ta,2) * sign(ta); SG_CLAMP_RANGE(_tow_angle, -_tow_angle_limit, _tow_angle_limit); } bool FGAIGroundVehicle::getGroundElev(SGGeod inpos) { double height_m ; if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(inpos, 3000), height_m, &_material,0)){ _ht_agl_ft = inpos.getElevationFt() - height_m * SG_METER_TO_FEET; if (_material) { const vector& names = _material->get_names(); _solid = _material->get_solid(); if (!names.empty()) props->setStringValue("material/name", names[0].c_str()); else props->setStringValue("material/name", ""); //cout << "material " << names[0].c_str() // << " _elevation_m " << _elevation_m // << " solid " << _solid // << " load " << _load_resistance // << " frictionFactor " << _frictionFactor // << endl; } return true; } else { return false; } } bool FGAIGroundVehicle::getPitch() { if (!_tunnel){ double vel = props->getDoubleValue("velocities/true-airspeed-kt", 0); double contact_offset_x1_m = _contact_x1_offset * SG_FEET_TO_METER; double contact_offset_x2_m = _contact_x2_offset * SG_FEET_TO_METER; SGVec3d front(-contact_offset_x1_m, 0, 0); SGVec3d rear(-contact_offset_x2_m, 0, 0); SGVec3d Front = getCartPosAt(front); SGVec3d Rear = getCartPosAt(rear); SGGeod geodFront = SGGeod::fromCart(Front); SGGeod geodRear = SGGeod::fromCart(Rear); double front_elev_m = 0; double rear_elev_m = 0; double elev_front = 0; double elev_rear = 0; double max_alt = 10000; if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(geodFront, 3000), elev_front, &_material, 0)){ front_elev_m = elev_front; } else return false; if (globals->get_scenery()->get_elevation_m(SGGeod::fromGeodM(geodRear, 3000), elev_rear, &_material, 0)){ rear_elev_m = elev_rear; } else return false; if (vel >= 0){ double diff = front_elev_m - rear_elev_m; _pitch = atan2 (diff, fabs(contact_offset_x1_m) + fabs(contact_offset_x2_m)) * SG_RADIANS_TO_DEGREES; _elevation = (rear_elev_m + diff/2) * SG_METER_TO_FEET; } else { double diff = rear_elev_m - front_elev_m; _pitch = atan2 (diff, fabs(contact_offset_x1_m) + fabs(contact_offset_x2_m)) * SG_RADIANS_TO_DEGREES; _elevation = (front_elev_m + diff/2) * SG_METER_TO_FEET; _pitch = -_pitch; } } else { if (prev->altitude == 0 || curr->altitude == 0) return false; static double distance; static double d_alt; static double curr_alt; static double prev_alt; if (_new_waypoint){ //cout << "new waypoint, calculating pitch " << endl; curr_alt = curr->altitude * SG_METER_TO_FEET; prev_alt = prev->altitude * SG_METER_TO_FEET; d_alt = curr_alt - prev_alt; distance = SGGeodesy::distanceM(SGGeod::fromDeg(prev->longitude, prev->latitude), SGGeod::fromDeg(curr->longitude, curr->latitude)); _pitch = atan2(d_alt, distance * SG_METER_TO_FEET) * SG_RADIANS_TO_DEGREES; // cout << "new waypoint, calculating pitch " << _pitch << endl; } double distance_to_go = SGGeodesy::distanceM(SGGeod::fromDeg(pos.getLongitudeDeg(), pos.getLatitudeDeg()), SGGeod::fromDeg(curr->longitude, curr->latitude)); //cout << "tunnel " << _tunnel // << " distance curr & prev " << prev->name << " " << curr->name << " " << distance * SG_METER_TO_FEET // << " distance to go " << distance_to_go * SG_METER_TO_FEET // << " d_alt ft " << d_alt // << endl; if (distance_to_go > distance) _elevation = prev_alt; else _elevation = curr_alt - (tan(_pitch * SG_DEGREES_TO_RADIANS) * distance_to_go * SG_METER_TO_FEET); } getGroundElev(pos); return true; } void FGAIGroundVehicle::setParent() { const SGPropertyNode *ai = fgGetNode("/ai/models", true); for (int i = ai->nChildren() - 1; i >= -1; i--) { const SGPropertyNode *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"); double lat = _selected_ac->getDoubleValue("position/latitude-deg"); double lon = _selected_ac->getDoubleValue("position/longitude-deg"); double elevation = _selected_ac->getDoubleValue("position/altitude-ft"); double hitch_x_offset_m = _selected_ac->getDoubleValue("hitch/x-offset-ft") * SG_FEET_TO_METER; double hitch_y_offset_m = _selected_ac->getDoubleValue("hitch/y-offset-ft") * SG_FEET_TO_METER; _selectedpos.setLatitudeDeg(lat); _selectedpos.setLongitudeDeg(lon); _selectedpos.setElevationFt(elevation); _parent_x_offset = _selected_ac->getDoubleValue("hitch/x-offset-ft"); _parent_y_offset = _selected_ac->getDoubleValue("hitch/y-offset-ft"); _parent_speed = _selected_ac->getDoubleValue("velocities/true-airspeed-kt"); SGVec3d rear_hitch(-hitch_x_offset_m, hitch_y_offset_m, 0); SGVec3d RearHitch = getCartHitchPosAt(rear_hitch); SGGeod rearpos = SGGeod::fromCart(RearHitch); double user_lat = rearpos.getLatitudeDeg(); double user_lon = rearpos.getLongitudeDeg(); double range, bearing; calcRangeBearing(pos.getLatitudeDeg(), pos.getLongitudeDeg(), user_lat, user_lon, range, bearing); _range_ft = range * 6076.11549; _relbrg = calcRelBearingDeg(bearing, hdg); } else { SG_LOG(SG_GENERAL, SG_ALERT, "AIGroundVeh1cle: " << _name << " parent not found: dying "); setDie(true); } } void FGAIGroundVehicle::calcRangeBearing(double lat, double lon, double lat2, double lon2, double &range, double &bearing) const { // calculate the bearing and range of the second pos from the first double az2, distance; geo_inverse_wgs_84(lat, lon, lat2, lon2, &bearing, &az2, &distance); range = distance * SG_METER_TO_NM; } double FGAIGroundVehicle::calcRelBearingDeg(double bearing, double heading) { double angle = bearing - heading; SG_NORMALIZE_RANGE(angle, -180.0, 180.0); return angle; } SGVec3d FGAIGroundVehicle::getCartHitchPosAt(const SGVec3d& _off) const { double hdg = _selected_ac->getDoubleValue("orientation/true-heading-deg"); double pitch = _selected_ac->getDoubleValue("orientation/pitch-deg"); double roll = _selected_ac->getDoubleValue("orientation/roll-deg"); // Transform that one to the horizontal local coordinate system. SGQuatd hlTrans = SGQuatd::fromLonLat(_selectedpos); // and postrotate the orientation of the AIModel wrt the horizontal // local frame hlTrans *= SGQuatd::fromYawPitchRollDeg(hdg, pitch, roll); // The offset converted to the usual body fixed coordinate system // rotated to the earth fiexed coordinates axis SGVec3d off = hlTrans.backTransform(_off); // Add the position offset of the AIModel to gain the earth centered position SGVec3d cartPos = SGVec3d::fromGeod(_selectedpos); return cartPos + off; } void FGAIGroundVehicle::AdvanceFP(){ double count = 0; string parent_next_name =_selected_ac->getStringValue("waypoint/name-next"); while(fp->getNextWaypoint() != 0 && fp->getNextWaypoint()->name != "END" && count < 5){ SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name <<" advancing waypoint to: " << parent_next_name); if (fp->getNextWaypoint()->name == parent_next_name){ SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name << " not setting waypoint already at: " << fp->getNextWaypoint()->name); return; } prev = curr; fp->IncrementWaypoint(false); curr = fp->getCurrentWaypoint(); next = fp->getNextWaypoint(); if (fp->getNextWaypoint()->name == parent_next_name){ SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name << " waypoint set to: " << fp->getNextWaypoint()->name); return; } count++; }// end while loop while(fp->getPreviousWaypoint() != 0 && fp->getPreviousWaypoint()->name != "END" && count > -10){ SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name << " retreating waypoint to: " << parent_next_name << " at: " << fp->getNextWaypoint()->name); if (fp->getNextWaypoint()->name == parent_next_name){ SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name << " not setting waypoint already at:" << fp->getNextWaypoint()->name ); return; } prev = curr; fp->DecrementWaypoint(false); curr = fp->getCurrentWaypoint(); next = fp->getNextWaypoint(); if (fp->getNextWaypoint()->name == parent_next_name){ SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name << " waypoint set to: " << fp->getNextWaypoint()->name); return; } count--; }// end while loop } void FGAIGroundVehicle::setTowSpeed(){ double diff = _range_ft - _x_offset; double x = 0; if (_range_ft > _x_offset * 3) x = 50; if (_relbrg < -90 || _relbrg > 90){ setSpeed(_parent_speed - 5 - x); //cout << _name << " case 1r _relbrg spd - 5 " << _relbrg << " " << diff << endl; }else if (_range_ft > _x_offset + 0.25 && _relbrg >= -90 && _relbrg <= 90){ setSpeed(_parent_speed + 1 + x); //cout << _name << " case 2r _relbrg spd + 1 " << _relbrg << " " // << diff << " range " << _range_ft << endl; } else if (_range_ft < _x_offset - 0.25 && _relbrg >= -90 && _relbrg <= 90){ setSpeed(_parent_speed - 1 - x); //cout << _name << " case 3r _relbrg spd - 2 " << _relbrg << " " // << diff << " " << _range_ft << endl; } else { setSpeed(_parent_speed); //cout << _name << " else r _relbrg " << _relbrg << " " << diff << endl; } } void FGAIGroundVehicle::RunGroundVehicle(double dt){ _dt_count += dt; /////////////////////////////////////////////////////////////////////////// // Check execution time (currently once every 0.05 sec or 20 fps) // Add a bit of randomization to prevent the execution of all flight plans // in synchrony, which can add significant periodic framerate flutter. // Randomization removed to get better appearance /////////////////////////////////////////////////////////////////////////// //cout << "_start_sec " << _start_sec << " time_sec " << time_sec << endl; if (_dt_count < _next_run) return; _next_run = 0.055 /*+ (0.015 * sg_random())*/; if (getPitch()){ setElevation(_elevation, _dt_count, _elevation_coeff); ClimbTo(_elevation_ft); setPitch(_pitch, _dt_count, _pitch_coeff); PitchTo(_pitch_deg); } if(_parent == ""){ AccelTo(prev->speed); FGAIShip::update(_dt_count); _dt_count = 0; return; } setParent(); string parent_next_name = _selected_ac->getStringValue("waypoint/name-next"); bool parent_waiting = _selected_ac->getBoolValue("waypoint/waiting"); if (parent_next_name == "END" && fp->getNextWaypoint()->name != "END" ){ SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name << " setting END: getting new waypoints "); AdvanceFP(); setWPNames(); /*} else if (parent_next_name == "WAIT" && fp->getNextWaypoint()->name != "WAIT" ){*/ } else if (parent_waiting && !_waiting){ SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name << " setting WAIT/WAITUNTIL: getting new waypoints "); AdvanceFP(); setWPNames(); _waiting = true; } else if (parent_next_name != "WAIT" && fp->getNextWaypoint()->name == "WAIT"){ SG_LOG(SG_GENERAL, SG_DEBUG, "AIGroundVeh1cle: " << _name << " wait done: getting new waypoints "); _waiting = false; _wait_count = 0; fp->IncrementWaypoint(false); next = fp->getNextWaypoint(); if (next->name == "WAITUNTIL" || next->name == "WAIT" || next->name == "END"){ } else { prev = curr; fp->IncrementWaypoint(false); curr = fp->getCurrentWaypoint(); next = fp->getNextWaypoint(); } setWPNames(); } else if (_range_ft > _parent_x_offset * 4){ SG_LOG(SG_GENERAL, SG_INFO, "AIGroundVeh1cle: " << _name << " rescue: reforming train " << _range_ft << " " << _x_offset * 15); setTowAngle(0, dt, 1); setSpeed(_parent_speed * 2); } else if (_parent_speed > 1){ setTowSpeed(); setTowAngle(_relbrg, dt, 1); } else if (_parent_speed < -1){ setTowSpeed(); if (_relbrg < 0) setTowAngle(-(180 - (360 + _relbrg)), dt, 1); else setTowAngle(-(180 - _relbrg), dt, 1); } else setSpeed(_parent_speed); FGAIShip::update(_dt_count); _dt_count = 0; } // end AIGroundvehicle