#ifdef HAVE_CONFIG_H # include "config.h" #endif #include "Math.hpp" #include "BodyEnvironment.hpp" #include "RigidBody.hpp" #include #include #include "Hitch.hpp" using std::vector; namespace yasim { Hitch::Hitch(const char *name) { if (fgGetNode("/sim/hitches", true)) _node = fgGetNode("/sim/hitches", true)->getNode(name, true); else _node = 0; int i; for(i=0; i<3; i++) _pos[i] = _force[i] = _winchPos[i] = _mp_lpos[i]=_towEndForce[i]=_mp_force[i]=0; for(i=0; i<2; i++) _global_ground[i] = 0; _global_ground[2] = 1; _global_ground[3] = -1e5; _forceMagnitude=0; _open=true; _oldOpen=_open; _towLength=60; _towElasticConstant=1e5; _towBrakeForce=100000; _towWeightPerM=1; _winchMaxSpeed=40; _winchRelSpeed=0; _winchInitialTowLength=1000; _winchPower=100000; _winchMaxForce=10000; _winchActualForce=0; _winchMaxTowLength=1000; _winchMinTowLength=0; _dist=0; _towEndIsConnectedToProperty=false; _towEndNode=0; _nodeIsMultiplayer=false; _nodeIsAiAircraft=false; _forceIsCalculatedByMaster=false; _nodeID=0; //_ai_MP_callsign=0; _height_above_ground=0; _winch_height_above_ground=0; _loPosFrac=0; _lowest_tow_height=0; _state=new State; _displayed_len_lower_dist_message=false; _last_wish=true; _isSlave=false; _mpAutoConnectPeriod=0; _timeToNextAutoConnectTry=0; _timeToNextReConnectTry=10; _speed_in_tow_direction=0; _mp_time_lag=1; _mp_last_reported_dist=0; _mp_last_reported_v=0; _mp_is_slave=false; _mp_open_last_state=false; _timeLagCorrectedDist=0; if (_node) { #define TIE(x,v) _tiedProperties.Tie(_node->getNode(x, true),v) TIE("tow/length", SGRawValuePointer(&_towLength)); TIE("tow/elastic-constant",SGRawValuePointer(&_towElasticConstant)); TIE("tow/weight-per-m-kg-m",SGRawValuePointer(&_towWeightPerM)); TIE("tow/brake-force",SGRawValuePointer(&_towBrakeForce)); TIE("winch/max-speed-m-s",SGRawValuePointer(&_winchMaxSpeed)); TIE("winch/rel-speed",SGRawValuePointer(&_winchRelSpeed)); TIE("winch/initial-tow-length-m",SGRawValuePointer(&_winchInitialTowLength)); TIE("winch/min-tow-length-m",SGRawValuePointer(&_winchMinTowLength)); TIE("winch/max-tow-length-m",SGRawValuePointer(&_winchMaxTowLength)); TIE("winch/global-pos-x",SGRawValuePointer(&_winchPos[0])); TIE("winch/global-pos-y",SGRawValuePointer(&_winchPos[1])); TIE("winch/global-pos-z",SGRawValuePointer(&_winchPos[2])); TIE("winch/max-power",SGRawValuePointer(&_winchPower)); TIE("winch/max-force",SGRawValuePointer(&_winchMaxForce)); TIE("winch/actual-force",SGRawValuePointer(&_winchActualForce)); TIE("tow/end-force-x",SGRawValuePointer(&_reportTowEndForce[0])); TIE("tow/end-force-y",SGRawValuePointer(&_reportTowEndForce[1])); TIE("tow/end-force-z",SGRawValuePointer(&_reportTowEndForce[2])); TIE("force",SGRawValuePointer(&_forceMagnitude)); TIE("open",SGRawValuePointer(&_open)); TIE("force-is-calculated-by-other",SGRawValuePointer(&_forceIsCalculatedByMaster)); TIE("local-pos-x",SGRawValuePointer(&_pos[0])); TIE("local-pos-y",SGRawValuePointer(&_pos[1])); TIE("local-pos-z",SGRawValuePointer(&_pos[2])); TIE("tow/dist",SGRawValuePointer(&_dist)); TIE("tow/dist-time-lag-corrected",SGRawValuePointer(&_timeLagCorrectedDist)); TIE("tow/connected-to-property-node",SGRawValuePointer(&_towEndIsConnectedToProperty)); TIE("tow/connected-to-mp-node",SGRawValuePointer(&_nodeIsMultiplayer)); TIE("tow/connected-to-ai-node",SGRawValuePointer(&_nodeIsAiAircraft)); TIE("tow/connected-to-ai-or-mp-id",SGRawValuePointer(&_nodeID)); TIE("debug/hitch-height-above-ground",SGRawValuePointer(&_height_above_ground)); TIE("debug/tow-end-height-above-ground",SGRawValuePointer(&_winch_height_above_ground)); TIE("debug/tow-rel-lo-pos",SGRawValuePointer(&_loPosFrac)); TIE("debug/tow-lowest-pos-height",SGRawValuePointer(&_lowest_tow_height)); TIE("is-slave",SGRawValuePointer(&_isSlave)); TIE("speed-in-tow-direction",SGRawValuePointer(&_speed_in_tow_direction)); TIE("mp-auto-connect-period",SGRawValuePointer(&_mpAutoConnectPeriod)); TIE("mp-time-lag",SGRawValuePointer(&_mp_time_lag)); #undef TIE _node->setStringValue("tow/node",""); _node->setStringValue("tow/connected-to-ai-or-mp-callsign"); _node->setBoolValue("broken",false); } } Hitch::~Hitch() { _tiedProperties.Untie(); delete _state; } void Hitch::setPosition(float* position) { int i; for(i=0; i<3; i++) _pos[i] = position[i]; } void Hitch::setTowLength(float length) { _towLength = length; } void Hitch::setOpen(bool isOpen) { //test if we already processed this before //without this test a binded property could //try to close the Hitch every run //it will close, if we are near the end //e.g. if we are flying over the parked //tow-aircraft.... if (isOpen==_last_wish) return; _last_wish=isOpen; _open=isOpen; } void Hitch::setTowElasticConstant(float sc) { _towElasticConstant=sc; } void Hitch::setTowBreakForce(float bf) { _towBrakeForce=bf; } void Hitch::setWinchMaxForce(float f) { _winchMaxForce=f; } void Hitch::setTowWeightPerM(float rw) { _towWeightPerM=rw; } void Hitch::setWinchMaxSpeed(float mws) { _winchMaxSpeed=mws; } void Hitch::setWinchRelSpeed(float rws) { _winchRelSpeed=rws; } void Hitch::setWinchPosition(double *winchPosition)//in global coordinates! { for (int i=0; i<3;i++) _winchPos[i]=winchPosition[i]; } void Hitch::setMpAutoConnectPeriod(float dt) { _mpAutoConnectPeriod=dt; } void Hitch::setForceIsCalculatedByOther(bool b) { _forceIsCalculatedByMaster=b; } std::string Hitch::getConnectedPropertyNode() const { if (_towEndNode) return _towEndNode->getDisplayName(); else return std::string(""); } void Hitch::setConnectedPropertyNode(const char *nodename) { _towEndNode=fgGetNode(nodename,false); } void Hitch::setWinchPositionAuto(bool doit) { static bool lastState=false; if(!_state) return; if (!doit) { lastState=false; return; } if(lastState) return; lastState=true; float lWinchPos[3]; // The ground plane transformed to the local frame. float ground[4]; _state->planeGlobalToLocal(_global_ground, ground); float help[3]; //find a normalized vector pointing forward parallel to the ground help[0]=0; help[1]=1; help[2]=0; Math::cross3(ground,help,help); //multiplay by initial tow length; //reduced by 1m to be able to close the //hitch either if the glider slips backwards a bit Math::mul3((_winchInitialTowLength-1.),help,help); //add to the actual pos Math::add3(_pos,help,lWinchPos); //put it onto the ground plane Math::mul3(ground[3],ground,help); Math::add3(lWinchPos,help,lWinchPos); _state->posLocalToGlobal(lWinchPos,_winchPos); _towLength=_winchInitialTowLength; fgSetString("/sim/messages/pilot", "Connected to winch!"); _open=false; _node->setBoolValue("broken",false); //set the dist value (if not, the hitch would open in the next calcforce run float delta[3]; Math::sub3(lWinchPos,_pos,delta); _dist=Math::mag3(delta); } void Hitch::findBestAIObject(bool doit,bool running_as_autoconnect) { static bool lastState=false; if(!_state) return; if (!running_as_autoconnect) { //if this function is binded to an input, it will be called every frame as long as the key is pressed. //therefore wait for a key-release before running it again. if (!doit) { lastState=false; return; } if(lastState) return; lastState=true; } double gpos[3]; _state->posLocalToGlobal(_pos,gpos); double bestdist=_towLength*_towLength;//squared! _towEndIsConnectedToProperty=false; SGPropertyNode * ainode = fgGetNode("/ai/models",false); if(!ainode) return; char myCallsign[256]="***********"; if (running_as_autoconnect) { //get own callsign SGPropertyNode *cs=fgGetNode("/sim/multiplay/callsign",false); if (cs) { strncpy(myCallsign,cs->getStringValue(),256); myCallsign[255]=0; } //reset tow length for search radius. Lentgh will be later copied from master _towLength=_winchInitialTowLength; } bool found=false; vector nodes;// for (int i=0;inChildren();i++) { SGPropertyNode * n=ainode->getChild(i); _nodeIsMultiplayer = strncmp("multiplayer",n->getName(),11)==0; _nodeIsAiAircraft = strncmp("aircraft",n->getName(),8)==0; if (!(_nodeIsMultiplayer || _nodeIsAiAircraft)) continue; if (running_as_autoconnect) { if (!_nodeIsMultiplayer) continue; if(n->getBoolValue("sim/hitches/aerotow/open",true)) continue; if(strncmp(myCallsign,n->getStringValue("sim/hitches/aerotow/tow/connected-to-ai-or-mp-callsign"),255)!=0) continue; } double pos[3]; pos[0]=n->getDoubleValue("position/global-x",0); pos[1]=n->getDoubleValue("position/global-y",0); pos[2]=n->getDoubleValue("position/global-z",0); double dist=0; for (int j=0;j<3;j++) dist+=(pos[j]-gpos[j])*(pos[j]-gpos[j]); if (distsetStringValue("tow/node",n->getPath()); _node->setStringValue("tow/node",n->getDisplayName()); _nodeID=n->getIntValue("id",0); _node->setStringValue("tow/connected-to-ai-or-mp-callsign",n->getStringValue("callsign")); _open=false; found = true; } } if (found) { if (!running_as_autoconnect) { std::stringstream message; message<<_node->getStringValue("tow/connected-to-ai-or-mp-callsign") <<", I am on your hook, distance "<getStringValue("tow/connected-to-ai-or-mp-callsign") <<": I am on your hook, distance "<posGlobalToLocal(_winchPos,lWinchPos); Math::sub3(lWinchPos,_pos,delta); if(!_towEndIsConnectedToProperty) _dist=Math::mag3(delta); else _dist=Math::mag3(lWinchPos); //use the aircraft center as reference for distance calculation //this position is transferred to the MP-Aircraft. //With this trick, both player in aerotow get the same length Math::unit3(delta,deltaN); float lvel[3]; s->velGlobalToLocal(s->v,lvel); _speed_in_tow_direction=Math::dot3(lvel,deltaN); if (_towEndIsConnectedToProperty && _nodeIsMultiplayer) { float mp_delta_dist_due_to_time_lag=0.5*_mp_time_lag*(-_mp_v+_speed_in_tow_direction); _timeLagCorrectedDist=_dist+mp_delta_dist_due_to_time_lag; if(_forceIsCalculatedByMaster && !_open) { s->velGlobalToLocal(_mp_force,_force); return; } } else _timeLagCorrectedDist=_dist; if (_open) { _force[0]=_force[1]=_force[2]=0; return; } if(_dist>_towLength) if(_towLength>1e-3) _forceMagnitude=(_dist-_towLength)/_towLength*_towElasticConstant; else _forceMagnitude=2*_towBrakeForce; else _forceMagnitude=0; if(_forceMagnitude>=_towBrakeForce) { _forceMagnitude=0; _open=true; _node->setBoolValue("broken",true); _force[0]=_force[1]=_force[2]=0; _towEndForce[0]=_towEndForce[1]=_towEndForce[2]=0; _reportTowEndForce[0]=_reportTowEndForce[1]=_reportTowEndForce[2]=0; return; } Math::mul3(_forceMagnitude,deltaN,_force); Math::mul3(-1.,_force,_towEndForce); _winchActualForce=_forceMagnitude; //missing: gravity on this end and friction //Add the gravitiy of the rope. //calculate some numbers: float grav_force=_towWeightPerM*_towLength*9.81; //the length of the gravity-expanded row: float leng=_towLength+grav_force*_towLength/_towElasticConstant; // The ground plane transformed to the local frame. float ground[4]; s->planeGlobalToLocal(_global_ground, ground); // The velocity of the contact patch transformed to local coordinates. //float glvel[3]; //s->velGlobalToLocal(_global_vel, glvel); _height_above_ground = ground[3] - Math::dot3(_pos, ground); //the same for the winch-pos (the pos of the tow end) _winch_height_above_ground = ground[3] - Math::dot3(lWinchPos, ground); //the frac of the grav force acting on _pos: float grav_frac=0.5*(1+(_height_above_ground-_winch_height_above_ground)/leng); grav_frac=Math::clamp(grav_frac,0,1); float grav_frac_tow_end=1-grav_frac; //reduce grav_frac, if the tow has ground contact. if (_height_above_ground1e-3) _loPosFrac=fa_/(fa_+fb_); else _loPosFrac=0.5; //dist to tow-end parallel to ground float ground_dist; float help[3]; //Math::cross3(delta,ground,help);//as long as we calculate the dist without _pos, od it with lWinchpos, the dist to our center.... Math::cross3(lWinchPos,ground,help); ground_dist=Math::mag3(help); //height of lowest tow pos (relative to _pos) _lowest_tow_height=_loPosFrac*Math::sqrt(Math::abs(stretchedTowLen*stretchedTowLen-ground_dist*ground_dist)); if (_height_above_ground<_lowest_tow_height) { if (_height_above_ground>1e-3) grav_frac*=_height_above_ground/_lowest_tow_height; else grav_frac=0; } if (_winch_height_above_ground<(_lowest_tow_height-_height_above_ground+_winch_height_above_ground)) { if (_winch_height_above_ground>1e-3) grav_frac_tow_end*=_winch_height_above_ground/ (_lowest_tow_height-_height_above_ground+_winch_height_above_ground); else grav_frac_tow_end=0; } } else _lowest_tow_height=_loPosFrac=-1; //for debug output float grav_force_v[3]; Math::mul3(grav_frac*grav_force,ground,grav_force_v); Math::add3(grav_force_v,_force,_force); _forceMagnitude=Math::mag3(_force); //the same for the tow end: Math::mul3(grav_frac_tow_end*grav_force,ground,grav_force_v); Math::add3(grav_force_v,_towEndForce,_towEndForce); s->velLocalToGlobal(_towEndForce,_towEndForce); if(_forceMagnitude>=_towBrakeForce) { _forceMagnitude=0; _open=true; _node->setBoolValue("broken",true); _force[0]=_force[1]=_force[2]=0; _towEndForce[0]=_towEndForce[1]=_towEndForce[2]=0; } } // Computed values: total force void Hitch::getForce(float* force, float* off) { Math::set3(_force, force); Math::set3(_pos, off); } void Hitch::integrate (float dt) { //check if hitch has opened or closed, if yes: message if (_open !=_oldOpen) { if (_oldOpen) { if (_dist>_towLength*1.00001) { std::stringstream message; message<<"Could not lock hitch (tow length is insufficient) on hitch " <<_node->getName()<<" "<<_node->getIndex()<<"!"; fgSetString("/sim/messages/pilot", message.str().c_str()); _open=true; return; } _node->setBoolValue("broken",false); } std::stringstream message; if (_node->getBoolValue("broken",false)&&_open) message<<"Oh no, the tow is broken"; else message<<(_open?"Opened hitch ":"Locked hitch ")<<_node->getName()<<" "<<_node->getIndex()<<"!"; fgSetString("/sim/messages/pilot", message.str().c_str()); _oldOpen=_open; } //check, if tow end should be searched in all MP-aircrafts if(_open && _mpAutoConnectPeriod) { _isSlave=false; _timeToNextAutoConnectTry-=dt; if ((_timeToNextAutoConnectTry>_mpAutoConnectPeriod) || (_timeToNextAutoConnectTry<0)) { _timeToNextAutoConnectTry=_mpAutoConnectPeriod; //search for MP-Aircraft, which is towed with us findBestAIObject(true,true); } } //check, if tow end can be modified by property, if yes: update if(_towEndIsConnectedToProperty) { if (_node) { //_towEndNode=fgGetNode(_node->getStringValue("tow/node"), false); char towNode[256]; strncpy(towNode,_node->getStringValue("tow/node"),256); towNode[255]=0; _towEndNode=fgGetNode("ai/models")->getNode(towNode, false); //AI and multiplayer objects seem to change node? //Check if we have the right one by callsign if (_nodeIsMultiplayer || _nodeIsAiAircraft) { char MPcallsign[256]=""; const char *MPc; MPc=_node->getStringValue("tow/connected-to-ai-or-mp-callsign"); if (MPc) { strncpy(MPcallsign,MPc,256); MPcallsign[255]=0; } if (((_towEndNode)&&(strncmp(_towEndNode->getStringValue("callsign"),MPcallsign,255)!=0))||!_towEndNode) { _timeToNextReConnectTry-=dt; if((_timeToNextReConnectTry<0)||(_timeToNextReConnectTry>10)) { _timeToNextReConnectTry=10; SGPropertyNode * ainode = fgGetNode("/ai/models",false); if(ainode) { for (int i=0;inChildren();i++) { SGPropertyNode * n=ainode->getChild(i); if(_nodeIsMultiplayer?strncmp("multiplayer",n->getName(),11)==0:strncmp("aircraft",n->getName(),8)) if (strcmp(n->getStringValue("callsign"),MPcallsign)==0)//found { _towEndNode=n; //_node->setStringValue("tow/node",n->getPath()); _node->setStringValue("tow/node",n->getDisplayName()); } } } } } } if(_towEndNode) { _winchPos[0]=_towEndNode->getDoubleValue("position/global-x",_winchPos[0]); _winchPos[1]=_towEndNode->getDoubleValue("position/global-y",_winchPos[1]); _winchPos[2]=_towEndNode->getDoubleValue("position/global-z",_winchPos[2]); _mp_lpos[0]=_towEndNode->getFloatValue("sim/hitches/aerotow/local-pos-x",0); _mp_lpos[1]=_towEndNode->getFloatValue("sim/hitches/aerotow/local-pos-y",0); _mp_lpos[2]=_towEndNode->getFloatValue("sim/hitches/aerotow/local-pos-z",0); _mp_dist=_towEndNode->getFloatValue("sim/hitches/aerotow/tow/dist"); _mp_v=_towEndNode->getFloatValue("sim/hitches/aerotow/speed-in-tow-direction"); _mp_force[0]=_towEndNode->getFloatValue("sim/hitches/aerotow/tow/end-force-x",0); _mp_force[1]=_towEndNode->getFloatValue("sim/hitches/aerotow/tow/end-force-y",0); _mp_force[2]=_towEndNode->getFloatValue("sim/hitches/aerotow/tow/end-force-z",0); if(_isSlave) { #define gf(a,b) a=_towEndNode->getFloatValue(b,a) #define gb(a,b) a=_towEndNode->getBoolValue(b,a) gf(_towLength,"sim/hitches/aerotow/tow/length"); gf(_towElasticConstant,"sim/hitches/aerotow/tow/elastic-constant"); gf(_towWeightPerM,"sim/hitches/aerotow/tow/weight-per-m-kg-m"); gf(_towBrakeForce,"sim/hitches/aerotow/brake-force"); gb(_open,"sim/hitches/aerotow/open"); gb(_mp_is_slave,"sim/hitches/aerotow/is-slave"); #undef gf #undef gb if (_mp_is_slave) _isSlave=false; //someone should be master } else { //check if other has opened hitch, but is neccessary, that it was closed before bool mp_open=_towEndNode->getBoolValue("sim/hitches/aerotow/open",_mp_open_last_state); if (mp_open != _mp_open_last_state) //state has changed { _mp_open_last_state=mp_open; //store that value if(!_open) { if(mp_open) { _oldOpen=_open=true; std::stringstream message; message<<_node->getStringValue("tow/connected-to-ai-or-mp-callsign") <<": I have released the tow!"; fgSetString("/sim/messages/ai-plane", message.str().c_str()); } } } } //try to calculate the time lag if ((_mp_last_reported_dist!=_mp_dist)||(_mp_last_reported_v!=_mp_v)) //new data; { _mp_last_reported_dist=_mp_dist; _mp_last_reported_v=_mp_v; float total_v=-_mp_v+_speed_in_tow_direction;//mp has opposite tow direction float abs_v=Math::abs(total_v); if (abs_v>0.1) { float actual_time_lag_guess=(_mp_dist-_dist)/total_v; //check, if it sounds ok if((actual_time_lag_guess>0)&&(actual_time_lag_guess<5)) { float frac=abs_v*0.01; if (frac>0.05) frac=0.05; // if we are slow, the guess of the lag can be rather wrong. as faster we are // the better the guess. Therefore frac is proportiona to the speed. Clamp it // at 5m/s _mp_time_lag=(1-frac)*_mp_time_lag+frac*actual_time_lag_guess; } } } } } } //set the _reported_tow_end_force Math::set3(_towEndForce,_reportTowEndForce); if (_open) return; if (_winchRelSpeed==0) return; float factor=1,offset=0; if (_winchActualForce>_winchMaxForce) offset=-(_winchActualForce-_winchMaxForce)/_winchMaxForce*20; if (_winchRelSpeed>0.01) // to avoit div by zero { float maxForcePowerLimit=_winchPower/(_winchRelSpeed*_winchMaxSpeed); if (_winchActualForce>maxForcePowerLimit) factor-=(_winchActualForce-maxForcePowerLimit)/maxForcePowerLimit; } _towLength-=dt*(factor*_winchRelSpeed+offset)*_winchMaxSpeed; if (_towLength<=_winchMinTowLength) { if (_winchRelSpeed<0) _winchRelSpeed=0; _towLength=_winchMinTowLength; return; } if (_towLength>=_winchMaxTowLength) { if (_winchRelSpeed<0) _winchRelSpeed=0; _towLength=_winchMaxTowLength; return; } } }; // namespace yasim