FlightGear next
AIMultiplayer.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: AIMultiplayer.cxx
3 * SPDX-FileComment: AIBase-derived class creates an AI multiplayer aircraft
4 * SPDX-FileCopyrightText: Copyright (C) 2003 David P. Culp - davidculp2@comcast.net
5 * SPDX-FileContributor: Also by Gregor Richards, started December 2005.
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include <config.h>
10
11#include <string>
12#include <stdio.h>
13
14#include <Aircraft/replay.hxx>
15#include <Main/globals.hxx>
16#include <Main/fg_props.hxx>
17#include <Time/TimeManager.hxx>
18
19#include "AIMultiplayer.hxx"
20
21using std::string;
22
23// #define SG_DEBUG SG_ALERT
24
26 m_simple_time_enabled(fgGetNode("/sim/time/simple-time/enabled", true)),
27 m_sim_replay_replay_state(fgGetNode("/sim/replay/replay-state", true)),
28 m_sim_replay_time(fgGetNode("/sim/replay/time", true)),
29 mLogRawSpeedMultiplayer(fgGetNode("/sim/replay/log-raw-speed-multiplayer", true))
30{
31 no_roll = false;
33}
34
36{
37 props->setStringValue("sim/model/path", model_path);
38 props->setIntValue("sim/model/fallback-model-index", _getFallbackModelIndex());
39
40 //refuel_node = fgGetNode("systems/refuel/contact", true);
41 isTanker = false; // do this until this property is
42 // passed over the net
43
44 const string& str1 = _getCallsign();
45 const string str2 = "MOBIL";
46
47 string::size_type loc1= str1.find( str2, 0 );
48 if ( (loc1 != string::npos) ){
49 // cout << " string found " << str2 << " in " << str1 << endl;
50 isTanker = true;
51 // cout << "isTanker " << isTanker << " " << mCallSign <<endl;
52 }
53 // ensure that these are created prior to calling base class init
54 // as otherwise the MP list will break
55 m_lagPPSAveragedNode = props->getNode("lag/pps-averaged", true);
56 m_lagPPSAveragedNode->setDoubleValue(0);
57 m_lagModAveragedNode = props->getNode("lag/lag-mod-averaged", true);
58 m_lagModAveragedNode->setDoubleValue(0);
59
60 // load model
61 bool result = FGAIBase::init(searchOrder);
62 // propagate installation state (used by MP pilot list)
63 props->setBoolValue("model-installed", _installed);
64
65 m_node_simple_time_latest = props->getNode("simple-time/latest", true);
66 m_node_simple_time_offset = props->getNode("simple-time/offset", true);
67 m_node_simple_time_offset_smoothed = props->getNode("simple-time/offset-smoothed", true);
68 m_node_simple_time_compensation = props->getNode("simple-time/compensation", true);
69
70 return result;
71}
72
74{
76
77 //2018.1 mp-clock-mode indicates the clock mode that the client is running, so for backwards
78 // compatibility ensure this is initialized to 0 which means pre 2018.1
79 props->setIntValue("sim/multiplay/mp-clock-mode", 0);
80
81 tie("refuel/contact", SGRawValuePointer<bool>(&contact));
82 tie("tanker", SGRawValuePointer<bool>(&isTanker));
83
84 tie("controls/invisible",
85 SGRawValuePointer<bool>(&invisible));
86 _uBodyNode = props->getNode("velocities/uBody-fps", true);
87 _vBodyNode = props->getNode("velocities/vBody-fps", true);
88 _wBodyNode = props->getNode("velocities/wBody-fps", true);
89
90 m_node_ai_latch = props->getNode("ai-latch", true /*create*/);
91 m_node_log_multiplayer = globals->get_props()->getNode("/sim/log-multiplayer-callsign", true /*create*/);
92
93#define AIMPROProp(type, name) \
94SGRawValueMethods<FGAIMultiplayer, type>(*this, &FGAIMultiplayer::get##name)
95
96#define AIMPRWProp(type, name) \
97SGRawValueMethods<FGAIMultiplayer, type>(*this, \
98 &FGAIMultiplayer::get##name, &FGAIMultiplayer::set##name)
99
100 //tie("callsign", AIMPROProp(const char *, CallSign));
101
102 tie("controls/allow-extrapolation",
103 AIMPRWProp(bool, AllowExtrapolation));
104 tie("controls/lag-adjust-system-speed",
105 AIMPRWProp(double, LagAdjustSystemSpeed));
106 tie("controls/player-lag",
107 AIMPRWProp(double, playerLag));
108 tie("controls/compensate-lag",
109 AIMPRWProp(int, compensateLag));
110
111#undef AIMPROProp
112#undef AIMPRWProp
113}
114
115
116void FGAIMultiplayer::FGAIMultiplayerInterpolate(
117 MotionInfo::iterator prevIt,
118 MotionInfo::iterator nextIt,
119 double tau,
120 SGVec3d& ecPos,
121 SGQuatf& ecOrient,
122 SGVec3f& ecLinearVel
123 )
124{
125 // Here we do just linear interpolation on the position
126 ecPos = interpolate(tau, prevIt->second.position, nextIt->second.position);
127 ecOrient = interpolate((float)tau, prevIt->second.orientation,
128 nextIt->second.orientation);
129 ecLinearVel = interpolate((float)tau, prevIt->second.linearVel, nextIt->second.linearVel);
130 speed = norm(ecLinearVel) * SG_METER_TO_NM * 3600.0;
131
132 if (prevIt->second.properties.size() == nextIt->second.properties.size()) {
133 std::vector<FGPropertyData*>::const_iterator prevPropIt;
134 std::vector<FGPropertyData*>::const_iterator prevPropItEnd;
135 std::vector<FGPropertyData*>::const_iterator nextPropIt;
136 std::vector<FGPropertyData*>::const_iterator nextPropItEnd;
137
138 prevPropIt = prevIt->second.properties.begin();
139 prevPropItEnd = prevIt->second.properties.end();
140 nextPropIt = nextIt->second.properties.begin();
141 nextPropItEnd = nextIt->second.properties.end();
142
143 while (prevPropIt != prevPropItEnd)
144 {
145 PropertyMap::iterator pIt = mPropertyMap.find((*prevPropIt)->id);
146 //cout << " Setting property..." << (*prevPropIt)->id;
147
148 if (pIt != mPropertyMap.end())
149 {
150 //cout << "Found " << pIt->second->getPath() << ":";
151
152 /*
153 * RJH - 2017-01-25
154 * During multiplayer operations a series of crashes were encountered that affected all players
155 * within range of each other and resulting in an exception being thrown at exactly the same moment in time
156 * (within case props::STRING: ref http://i.imgur.com/y6MBoXq.png)
157 * Investigation showed that the nextPropIt and prevPropIt were pointing to different properties
158 * which may be caused due to certain models that have overloaded mp property transmission and
159 * these craft have their properties truncated due to packet size. However the result of this
160 * will be different contents in the previous and current packets, so here we protect against
161 * this by only considering properties where the previous and next id are the same.
162 * It might be a better solution to search the previous and next lists to locate the matching id's
163 */
164 if (*nextPropIt && (*nextPropIt)->id == (*prevPropIt)->id)
165 {
166 switch ((*prevPropIt)->type)
167 {
168 case simgear::props::INT:
169 case simgear::props::BOOL:
170 case simgear::props::LONG:
171 // Jean Pellotier, 2018-01-02 : we don't want interpolation for integer values, they are mostly used
172 // for non linearly changing values (e.g. transponder etc ...)
173 // fixes: https://sourceforge.net/p/flightgear/codetickets/1885/
174 pIt->second->setIntValue((*nextPropIt)->int_value);
175 break;
176
177 case simgear::props::FLOAT:
178 case simgear::props::DOUBLE:
179 {
180 float val = (1 - tau)*(*prevPropIt)->float_value +
181 tau*(*nextPropIt)->float_value;
182 pIt->second->setFloatValue(val);
183 }
184 break;
185
186 case simgear::props::STRING:
187 case simgear::props::UNSPECIFIED:
188 //cout << "Str: " << (*nextPropIt)->string_value << "\n";
189 pIt->second->setStringValue((*nextPropIt)->string_value);
190 break;
191
192 default:
193 // FIXME - currently defaults to float values
194 {
195 float val = (1 - tau)*(*prevPropIt)->float_value +
196 tau*(*nextPropIt)->float_value;
197 pIt->second->setFloatValue(val);
198 }
199 break;
200 }
201 }
202 else
203 {
204 SG_LOG(SG_AI, SG_WARN, "MP packet mismatch during lag interpolation: " << (*prevPropIt)->id << " != " << (*nextPropIt)->id << "\n");
205 }
206 }
207 else
208 {
209 SG_LOG(SG_AI, SG_DEBUG, "Unable to find property: " << (*prevPropIt)->id << "\n");
210 }
211
212 ++prevPropIt;
213 ++nextPropIt;
214 }
215 }
216}
217
218void FGAIMultiplayer::FGAIMultiplayerExtrapolate(
219 MotionInfo::iterator nextIt,
220 double tInterp,
221 bool motion_logging,
222 SGVec3d& ecPos,
223 SGQuatf& ecOrient,
224 SGVec3f& ecLinearVel
225 )
226{
227 const FGExternalMotionData& motionInfo = nextIt->second;
228
229 // The time to predict, limit to 3 seconds. But don't do this if we are
230 // running motion tests, because it can mess up the results.
231 //
232 double t = tInterp - nextIt->first;
233 if (!motion_logging)
234 {
235 props->setDoubleValue("lag/extrapolation-t", t);
236 if (t > 3)
237 {
238 t = 3;
239 props->setBoolValue("lag/extrapolation-out-of-range", true);
240 }
241 else
242 {
243 props->setBoolValue("lag/extrapolation-out-of-range", false);
244 }
245 }
246
247 // using velocity and acceleration to guess a parabolic position...
248 ecPos = motionInfo.position;
249 ecOrient = motionInfo.orientation;
250 ecLinearVel = motionInfo.linearVel;
251 SGVec3d ecVel = toVec3d(ecOrient.backTransform(ecLinearVel));
252 SGVec3f angularVel = motionInfo.angularVel;
253 SGVec3d ecAcc = toVec3d(ecOrient.backTransform(motionInfo.linearAccel));
254
255 double normVel = norm(ecVel);
256 double normAngularVel = norm(angularVel);
257 props->setDoubleValue("lag/norm-vel", normVel);
258 props->setDoubleValue("lag/norm-angular-vel", normAngularVel);
259
260 // not doing rotational prediction for small speed or rotation rate,
261 // to avoid agitated parked plane
262
263 if (( normAngularVel > 0.05 ) || ( normVel > 1.0 ))
264 {
265 ecOrient += t*ecOrient.derivative(angularVel);
266 }
267
268 // not using acceleration for small speed, to have better parked planes
269 // note that anyway acceleration is not transmit yet by mp
270 if ( normVel > 1.0 )
271 {
272 ecPos += t*(ecVel + 0.5*t*ecAcc);
273 }
274 else
275 {
276 ecPos += t*(ecVel);
277 }
278
279 std::vector<FGPropertyData*>::const_iterator firstPropIt;
280 std::vector<FGPropertyData*>::const_iterator firstPropItEnd;
281 speed = norm(ecLinearVel) * SG_METER_TO_NM * 3600.0;
282 firstPropIt = motionInfo.properties.begin();
283 firstPropItEnd = motionInfo.properties.end();
284 while (firstPropIt != firstPropItEnd)
285 {
286 PropertyMap::iterator pIt = mPropertyMap.find((*firstPropIt)->id);
287 //cout << " Setting property..." << (*firstPropIt)->id;
288
289 if (pIt != mPropertyMap.end())
290 {
291 switch ((*firstPropIt)->type)
292 {
293 case simgear::props::INT:
294 case simgear::props::BOOL:
295 case simgear::props::LONG:
296 pIt->second->setIntValue((*firstPropIt)->int_value);
297 //cout << "Int: " << (*firstPropIt)->int_value << "\n";
298 break;
299 case simgear::props::FLOAT:
300 case simgear::props::DOUBLE:
301 pIt->second->setFloatValue((*firstPropIt)->float_value);
302 //cout << "Flo: " << (*firstPropIt)->float_value << "\n";
303 break;
304 case simgear::props::STRING:
305 case simgear::props::UNSPECIFIED:
306 pIt->second->setStringValue((*firstPropIt)->string_value);
307 //cout << "Str: " << (*firstPropIt)->string_value << "\n";
308 break;
309 default:
310 // FIXME - currently defaults to float values
311 pIt->second->setFloatValue((*firstPropIt)->float_value);
312 //cout << "Unk: " << (*firstPropIt)->float_value << "\n";
313 break;
314 }
315 }
316 else
317 {
318 SG_LOG(SG_AI, SG_DEBUG, "Unable to find property: " << (*firstPropIt)->id << "\n");
319 }
320
321 ++firstPropIt;
322 }
323}
324
325
326// Populate
327// /sim/replay/log-raw-speed-multiplayer-post-*-values/value[] with information
328// on motion of MP and user aircraft.
329//
330// For use with scripts/python/recordreplay.py --test-motion-mp.
331//
332static void s_MotionLogging(const std::string& _callsign, double tInterp, SGVec3d ecPos, const SGGeod& pos)
333{
334 SGGeod pos_geod = pos;
335 if (!fgGetBool("/sim/replay/replay-state-eof"))
336 {
337 static SGVec3d s_pos_0;
338 static SGVec3d s_pos_prev;
339 static double s_t_prev = -1;
340 SGVec3d pos2 = ecPos;
341 double sim_replay_time = fgGetDouble("/sim/replay/time");
342 double t = fgGetBool("/sim/replay/replay-state") ? sim_replay_time : tInterp;
343
344 if (s_t_prev == -1)
345 {
346 s_pos_0 = pos2;
347 }
348
349 double t_dt = t - s_t_prev;
350 if (s_t_prev != -1 /*&& t_dt != 0*/)
351 {
352 SGVec3d delta_pos = pos2 - s_pos_prev;
353 SGVec3d delta_pos_norm = normalize(delta_pos);
354 double distance0 = length(pos2 - s_pos_0);
355 double distance = length(delta_pos);
356 double speed = (t_dt) ? distance / t_dt : -999;
357
358 if (t_dt)
359 {
360 SGPropertyNode* n0 = fgGetNode("/sim/replay/log-raw-speed-multiplayer-post-values", true /*create*/);
361 SGPropertyNode* n;
362
363 n = n0->addChild("value");
364 n->setDoubleValue(speed);
365
366 n = n0->addChild("description");
367 char buffer[128];
368 snprintf(buffer, sizeof(buffer), "s_t_prev=%f t=%f t_dt=%f distance=%f",
369 s_t_prev, t, t_dt, distance);
370 n->setStringValue(buffer);
371 }
372
373 SGGeod user_pos_geod = globals->get_aircraft_position();
374 SGVec3d user_pos = globals->get_aircraft_position_cart();
375
376 double user_to_mp_distance = SGGeodesy::distanceM(user_pos_geod, pos_geod);
377 double user_to_mp_bearing = SGGeodesy::courseDeg(user_pos_geod, pos_geod);
378 double user_distance0 = length(user_pos - s_pos_0);
379
380 fgGetNode("/sim/replay/log-raw-speed-multiplayer-post-relative-distance", true /*create*/)
381 ->addChild("value")
382 ->setDoubleValue(user_to_mp_distance);
383 fgGetNode("/sim/replay/log-raw-speed-multiplayer-post-relative-bearing", true /*create*/)
384 ->addChild("value")
385 ->setDoubleValue(user_to_mp_bearing);
386 fgGetNode("/sim/replay/log-raw-speed-multiplayer-post-absolute-distance", true /*create*/)
387 ->addChild("value")
388 ->setDoubleValue(distance0);
389 fgGetNode("/sim/replay/log-raw-speed-multiplayer-post-user-absolute-distance", true /*create*/)
390 ->addChild("value")
391 ->setDoubleValue(user_distance0);
392 }
393
394 if (t_dt)
395 {
396 s_t_prev = t;
397 s_pos_prev = pos2;
398 }
399 }
400}
401
403{
404 using namespace simgear;
405
406 if (dt <= 0)
407 {
408 return;
409 }
411
412 // Check if we already got data
413 if (mMotionInfo.empty())
414 {
415 return;
416 }
417
418 // motion_logging is true if we are being run by
419 // scripts/python/recordreplay.py --test-motion-mp.
420 //
421 bool motion_logging = false;
422 {
423 string callsign = mLogRawSpeedMultiplayer->getStringValue();
424 if (!callsign.empty() && this->_callsign == callsign)
425 {
426 motion_logging = true;
427 }
428 }
429
430 double curtime;
431 double tInterp;
432 if (m_simple_time_enabled->getBoolValue())
433 {
434 // Simplified timekeeping.
435 //
436 if (m_sim_replay_replay_state->getBoolValue())
437 {
438 tInterp = m_sim_replay_time->getDoubleValue();
439 SG_LOG(SG_GENERAL, SG_BULK, "tInterp=" << std::fixed << std::setprecision(6) << tInterp);
440 }
441 else
442 {
443 tInterp = globals->get_subsystem<TimeManager>()->getMPProtocolClockSec();
444 }
445 curtime = tInterp;
446 }
447 else
448 {
449 // Get the last available time
450 MotionInfo::reverse_iterator motioninfo_back = mMotionInfo.rbegin();
451 const double curentPkgTime = motioninfo_back->first;
452
453 // The current simulation time we need to update for,
454 // note that the simulation time is updated before calling all the
455 // update methods. Thus motioninfo_back contains the time intervals *end* time
456 // 2018: notice this time is specifically used for mp protocol
457 curtime = globals->get_subsystem<TimeManager>()->getMPProtocolClockSec();
458
459 // Dynamically optimize the time offset between the feeder and the client
460 // Well, 'dynamically' means that the dynamic of that update must be very
461 // slow. You would otherwise notice huge jumps in the multiplayer models.
462 // The reason is that we want to avoid huge extrapolation times since
463 // extrapolation is highly error prone. For that we need something
464 // approaching the average latency of the packets. This first order lag
465 // component will provide this. We just take the error of the currently
466 // requested time to the most recent available packet. This is the
467 // target we want to reach in average.
468 double lag = motioninfo_back->second.lag;
469
470 rawLag = curentPkgTime - curtime;
471 realTime = false; //default behaviour
472
473 if (!mTimeOffsetSet)
474 {
475 mTimeOffsetSet = true;
476 mTimeOffset = curentPkgTime - curtime - lag;
477 lastTime = curentPkgTime;
478 lagModAveraged = remainder((curentPkgTime - curtime), 3600.0);
479 m_lagPPSAveragedNode->setDoubleValue(lagPpsAveraged);
480 m_lagModAveragedNode->setDoubleValue(lagModAveraged);
481 }
482 else
483 {
484 if ((curentPkgTime - lastTime) != 0)
485 {
486 lagPpsAveraged = 0.99 * lagPpsAveraged + 0.01 * fabs( 1 / (lastTime - curentPkgTime));
487 lastTime = curentPkgTime;
488 rawLagMod = remainder(rawLag, 3600.0);
489 lagModAveraged = lagModAveraged *0.99 + 0.01 * rawLagMod;
490 m_lagPPSAveragedNode->setDoubleValue(lagPpsAveraged);
491 m_lagModAveragedNode->setDoubleValue(lagModAveraged);
492 }
493
494 double offset = 0.0;
495
496 //spectator mode, more late to be in the interpolation zone
497 if (compensateLag == 3)
498 {
499 offset = curentPkgTime - curtime - lag + playerLag;
500 // old behaviour
501 }
502 else if (compensateLag == 1)
503 {
504 offset = curentPkgTime - curtime - lag;
505
506 // using the prediction mode to display the mpaircraft in the futur/past with given playerlag value
507 //currently compensatelag = 2
508 }
509 else if (fabs(lagModAveraged) < 0.3)
510 {
511 mTimeOffset = (round(rawLag/3600))*3600; //real time mode if close enough
512 realTime = true;
513 }
514 else
515 {
516 offset = curentPkgTime - curtime + 0.48*lag + playerLag;
517 }
518
519 if (!realTime)
520 {
521 if ((!mAllowExtrapolation && offset + lag < mTimeOffset) || (offset - 10 > mTimeOffset))
522 {
523 mTimeOffset = offset;
524 SG_LOG(SG_AI, SG_DEBUG, "Resetting time offset adjust system to "
525 "avoid extrapolation: time offset = " << mTimeOffset);
526 }
527 else
528 {
529 // the error of the offset, respectively the negative error to avoid
530 // a minus later ...
531 double err = offset - mTimeOffset;
532 // limit errors leading to shorter lag values somehow, that is late
533 // arriving packets will pessimize the overall lag much more than
534 // early packets will shorten the overall lag
535 double sysSpeed;
536 //trying to slow the rudderlag phenomenon thus using more the prediction system
537 //if we are off by less than 1.5s, do a little correction, and bigger step above 1.5s
538 if (fabs(err) < 1.5)
539 {
540 if (err < 0)
541 {
542 sysSpeed = mLagAdjustSystemSpeed*err*0.01;
543 }
544 else
545 {
546 sysSpeed = SGMiscd::min(0.5*err*err, 0.05);
547 }
548 }
549 else
550 {
551 if (err < 0)
552 {
553 // Ok, we have some very late packets and nothing newer increase the
554 // lag by the given speedadjust
555 sysSpeed = mLagAdjustSystemSpeed*err;
556 }
557 else
558 {
559 // We have a too pessimistic display delay shorten that a small bit
560 sysSpeed = SGMiscd::min(0.1*err*err, 0.5);
561 }
562 }
563
564 // simple euler integration for that first order system including some
565 // overshooting guard to prevent to aggressive system speeds
566 // (stiff systems) to explode the systems state
567 double systemIncrement = dt*sysSpeed;
568 if (fabs(err) < fabs(systemIncrement))
569 {
570 systemIncrement = err;
571 }
572 mTimeOffset += systemIncrement;
573
574 SG_LOG(SG_AI, SG_DEBUG, "Offset adjust system: time offset = "
575 << mTimeOffset << ", expected longitudinal position error due to "
576 " current adjustment of the offset: "
577 << fabs(norm(motioninfo_back->second.linearVel)*systemIncrement));
578 }
579 }
580 }
581
582 // Compute the time in the feeders time scale which fits the current time
583 // we need to
584 tInterp = curtime + mTimeOffset;
585 }
586
587 SGVec3d ecPos;
588 SGQuatf ecOrient;
589 SGVec3f ecLinearVel;
590
591 MotionInfo::iterator nextIt = mMotionInfo.upper_bound(tInterp);
592 MotionInfo::iterator prevIt = nextIt;
593
594 if (nextIt != mMotionInfo.end() && nextIt->first >= tInterp)
595 {
596 // Ok, we need a time previous to the last available packet,
597 // that is good ...
598 // the case tInterp = curentPkgTime need to be in the interpolation, to avoid a bug zeroing the position
599
600 double tau = 0;
601 if (nextIt == mMotionInfo.begin())
602 {
603 // Leave prevIt and nextIt pointing at same item.
604 SG_LOG(SG_GENERAL, SG_DEBUG, "Only one frame for interpolation: " << _callsign);
605 }
606 else
607 {
608 --prevIt;
609 // Interpolation coefficient is between 0 and 1
610 double intervalStart = prevIt->first;
611 double intervalEnd = nextIt->first;
612
613 double intervalLen = intervalEnd - intervalStart;
614 if (intervalLen != 0.0)
615 {
616 tau = (tInterp - intervalStart) / intervalLen;
617 }
618 }
619
620 FGAIMultiplayerInterpolate(prevIt, nextIt, tau, ecPos, ecOrient, ecLinearVel);
621 }
622 else
623 {
624 // Ok, we need to predict the future, so, take the best data we can have
625 // and do some eom computation to guess that for now.
626 --nextIt;
627 --prevIt; // so mMotionInfo.erase() does the right thing below.
628 FGAIMultiplayerExtrapolate(nextIt, tInterp, motion_logging, ecPos, ecOrient, ecLinearVel);
629 }
630
631 // Remove any motion information before <prevIt> - we will not need this in
632 // the future.
633 //
634 mMotionInfo.erase(mMotionInfo.begin(), prevIt);
635
636 // extract the position
637 pos = SGGeod::fromCart(ecPos);
638 double recent_alt_ft = altitude_ft;
639 altitude_ft = pos.getElevationFt();
640
641 // expose a valid vertical speed
642 if (lastUpdateTime != 0)
643 {
644 double dT = curtime - lastUpdateTime;
645 double Weighting=1;
646 if (dt < 1.0)
647 {
648 Weighting = dt;
649 }
650 // simple smoothing over 1 second
651 vs_fps = (1.0-Weighting)*vs_fps + Weighting * (altitude_ft - recent_alt_ft) / dT * 60;
652 }
653 lastUpdateTime = curtime;
654
655 // The quaternion rotating from the earth centered frame to the
656 // horizontal local frame
657 SGQuatf qEc2Hl = SGQuatf::fromLonLatRad((float)pos.getLongitudeRad(), (float)pos.getLatitudeRad());
658 // The orientation wrt the horizontal local frame
659 SGQuatf hlOr = conj(qEc2Hl)*ecOrient;
660 float hDeg, pDeg, rDeg;
661 hlOr.getEulerDeg(hDeg, pDeg, rDeg);
662 hdg = hDeg;
663 roll = rDeg;
664 pitch = pDeg;
665
666 // expose velocities/u,v,wbody-fps in the mp tree
667 _uBodyNode->setValue(ecLinearVel[0] * SG_METER_TO_FEET);
668 _vBodyNode->setValue(ecLinearVel[1] * SG_METER_TO_FEET);
669 _wBodyNode->setValue(ecLinearVel[2] * SG_METER_TO_FEET);
670
671 if (ecLinearVel[0] == 0) {
672 // MP packets for carriers have zero ecLinearVel, but do specify
673 // velocities/speed-kts.
674 double speed_kts = props->getDoubleValue("velocities/speed-kts");
675 double speed_fps = speed_kts * SG_KT_TO_FPS;
676 _uBodyNode->setDoubleValue(speed_fps);
677 }
678
679 std::string ai_latch = m_node_ai_latch->getStringValue();
680 if (ai_latch != m_ai_latch) {
681 SG_LOG(SG_AI, SG_ALERT, "latching _callsign=" << _callsign << " to mp " << ai_latch);
682 m_ai_latch = ai_latch;
683 SGPropertyNode* mp_node = globals->get_props()->getNode(ai_latch, false /*create*/);
684 m_node_ai_latch_latitude = mp_node ? mp_node->getNode("position/latitude-deg", true /*create*/) : nullptr;
685 m_node_ai_latch_longitude = mp_node ? mp_node->getNode("position/longitude-deg", true /*create*/) : nullptr;
686 m_node_ai_latch_altitude = mp_node ? mp_node->getNode("position/altitude-ft", true /*create*/) : nullptr;
687 m_node_ai_latch_heading = mp_node ? mp_node->getNode("orientation/true-heading-deg", true /*create*/) : nullptr;
688 m_node_ai_latch_pitch = mp_node ? mp_node->getNode("orientation/pitch-deg", true /*create*/) : nullptr;
689 m_node_ai_latch_roll = mp_node ? mp_node->getNode("orientation/roll-deg", true /*create*/) : nullptr;
690 m_node_ai_latch_ubody_fps = mp_node ? mp_node->getNode("velocities/uBody-fps", true /*create*/) : nullptr;
691 m_node_ai_latch_vbody_fps = mp_node ? mp_node->getNode("velocities/vBody-fps", true /*create*/) : nullptr;
692 m_node_ai_latch_wbody_fps = mp_node ? mp_node->getNode("velocities/wBody-fps", true /*create*/) : nullptr;
693 m_node_ai_latch_speed_kts = mp_node ? mp_node->getNode("velocities/speed-kts", true /*create*/) : nullptr;
694 }
695 if (ai_latch != "") {
696 SGPropertyNode* mp_node = globals->get_props()->getNode(ai_latch, true /*create*/);
697 assert(m_node_ai_latch_latitude->getPath() == mp_node->getNode("position/latitude-deg")->getPath());
698 m_node_ai_latch_latitude->setDoubleValue(pos.getLatitudeDeg());
699 m_node_ai_latch_longitude->setDoubleValue(pos.getLongitudeDeg());
700 m_node_ai_latch_altitude->setDoubleValue(pos.getElevationFt());
701 m_node_ai_latch_heading->setDoubleValue(hDeg);
702 m_node_ai_latch_pitch->setDoubleValue(pitch);
703 m_node_ai_latch_roll->setDoubleValue(roll);
704 m_node_ai_latch_ubody_fps->setDoubleValue(_uBodyNode->getDoubleValue());
705 m_node_ai_latch_vbody_fps->setDoubleValue(_vBodyNode->getDoubleValue());
706 m_node_ai_latch_wbody_fps->setDoubleValue(_wBodyNode->getDoubleValue());
707
708 // /ai/models/carrier[]/velocities/speed-kts seems to be used
709 // to calculate friction between carrier deck and our aircraft
710 // undercarriage when brakes are on, so we set it here from
711 // /ai/models/multiplayer[]/velocities/uBody-fps.
712 //
713 // [We could possibly use
714 // /ai/models/multiplayer[]/velocities/true-airspeed-kt instead, but
715 // seems nicer to use uBody.]
716 //
717 // [todo: use rate of change of heading to calculating
718 // movement/rotation of the ship under the aircraft, so aircraft
719 // doesn't slip when carrier changes course. Also would be good to
720 // handle vbody and wbody?]
721 //
722 m_node_ai_latch_speed_kts->setDoubleValue(_uBodyNode->getDoubleValue() * SG_FPS_TO_KT);
723 }
724 assert(m_node_ai_latch == props->getNode("ai-latch"));
725
726 //SG_LOG(SG_AI, SG_DEBUG, "Multiplayer position and orientation: " << ecPos << ", " << hlOr);
727
728 if (motion_logging)
729 {
730 s_MotionLogging(_callsign, tInterp, ecPos, pos);
731 }
732
733 //###########################//
734 // do calculations for radar //
735 //###########################//
736 double range_ft2 = UpdateRadar(manager);
737
738 //************************************//
739 // Tanker code //
740 //************************************//
741
742 if ( isTanker)
743 {
744 //cout << "IS tanker ";
745 if (range_ft2 < 250.0 * 250.0 && y_shift > 0.0 && elevation > 0.0)
746 {
747 // refuel_node->setBoolValue(true);
748 //cout << "in contact" << endl;
749 contact = true;
750 }
751 else
752 {
753 // refuel_node->setBoolValue(false);
754 //cout << "not in contact" << endl;
755 contact = false;
756 }
757 }
758 else
759 {
760 //cout << "NOT tanker " << endl;
761 contact = false;
762 }
763
764 Transform();
765}
766
767void
769 long stamp)
770{
771 mLastTimestamp = stamp;
772
773 if (m_simple_time_enabled->getBoolValue()) {
774 // Update simple-time stats and set m_simple_time_compensation.
775 //
776 double t = m_sim_replay_replay_state->getBoolValue()
777 ? m_sim_replay_time->getDoubleValue()
778 : globals->get_subsystem<TimeManager>()->getMPProtocolClockSec()
779 ;
780
781 m_simple_time_offset = motionInfo.time - t;
782 if (m_simple_time_first_time)
783 {
784 m_simple_time_first_time = false;
785 m_simple_time_offset_smoothed = m_simple_time_offset;
786 }
787 double e = 0.01;
788 m_simple_time_offset_smoothed = (1-e) * m_simple_time_offset_smoothed
789 + e * m_simple_time_offset;
790
791 if (m_simple_time_offset_smoothed < -2.0 || m_simple_time_offset_smoothed > 1)
792 {
793 // If the sender is using simple-time mode and their clock is
794 // synchronised to ours (e.g. both using NTP), <time_offset> will
795 // be a negative value due to the network delay plus any difference
796 // in the remote and local UTC time.
797 //
798 // Thus we could expect m_time_offset_max around -0.2.
799 //
800 // If m_time_offset_max is very different from zero, this indicates
801 // that the sender is not putting UTC time into MP packets. E.g.
802 // they are using simple-time mode but their system clock is not
803 // set correctly; or they are not using simple-time mode so are
804 // sending their FDM time or a time that has been synchronised with
805 // other MP aircraft. Another possibility is that our UTC clock is
806 // incorrect.
807 //
808 // We need a time that can be consistently compared with our UTC
809 // tInterp. So we set m_time_compensation to something to be
810 // added to all times received in MP packets from _callsign. We
811 // use compensated time for keys in the the mMotionInfo[]
812 // map, thus code should generally use these key values, not
813 // mMotionInfo[].time.
814 //
815 m_simple_time_compensation = -m_simple_time_offset_smoothed;
816
817 // m_simple_time_offset_smoothed will usually be too big to be
818 // useful here.
819 //
820 m_lagModAveragedNode->setDoubleValue(0.0);
821 }
822 else
823 {
824 m_simple_time_compensation = 0;
825 m_lagModAveragedNode->setDoubleValue(m_simple_time_offset_smoothed);
826 }
827
828 m_node_simple_time_latest->setDoubleValue(motionInfo.time);
829 m_node_simple_time_offset->setDoubleValue(m_simple_time_offset);
830 m_node_simple_time_offset_smoothed->setDoubleValue(m_simple_time_offset_smoothed);
831 m_node_simple_time_compensation->setDoubleValue(m_simple_time_compensation);
832
833 double t_key = motionInfo.time + m_simple_time_compensation;
834 if (m_simple_time_recent_packet_time)
835 {
836 double dt = t_key - m_simple_time_recent_packet_time;
837 if (dt != 0)
838 {
839 double ep = 0.05;
840 lagPpsAveraged = (1-ep) * lagPpsAveraged + ep * (1/dt);
841 m_lagPPSAveragedNode->setDoubleValue(lagPpsAveraged);
842 }
843 }
844
845 m_simple_time_recent_packet_time = t_key;
846
847 // We use compensated time <t_key> as key in mMotionInfo.
848 // m_time_compensation is set to non-zero if packets seem to have
849 // wildly different times from us, if simple-time mode is enabled.
850 //
851 // So most code with an <iterator> into mMotionInfo that needs to
852 // use the MP packet's time, will actually use iterator->first, not
853 // iterator->second.time..
854 //
855 mMotionInfo[t_key] = motionInfo;
856 }
857 else
858 {
859 mMotionInfo[motionInfo.time] = motionInfo;
860 }
861
862 // We just copied the property (pointer) list - they are ours now. Clear the
863 // properties list in given/returned object, so former owner won't deallocate them.
864 motionInfo.properties.clear();
865
866 {
867 // Gather data on multiplayer speed, used by scripts/python/recordreplay.py.
868 //
869 if (m_node_log_multiplayer->getStringValue() == this->_callsign)
870 {
871 static SGVec3d pos_prev;
872 static double t_prev = 0;
873
874 double distance = length(motionInfo.position - pos_prev);
875 double dt = motionInfo.time - t_prev;
876 double speed = distance / dt;
877
878 double linear_vel = norm(motionInfo.linearVel);
879
880 SGPropertyNode* item = fgGetNode("/sim/log-multiplayer", true /*create*/)->addChild("mppacket");
881 item->setDoubleValue("distance", distance);
882 item->setDoubleValue("speed", speed);
883 item->setDoubleValue("dt", dt);
884 item->setDoubleValue("linear_vel", linear_vel);
885 item->setDoubleValue("t", motionInfo.time );
886
887 pos_prev = motionInfo.position;
888 t_prev = motionInfo.time;
889 }
890 }
891
892}
893
894#if 0
895void
896FGAIMultiplayer::setDoubleProperty(const std::string& prop, double val)
897{
898 SGPropertyNode* pNode = props->getChild(prop.c_str(), true);
899 pNode->setDoubleValue(val);
900}
901#endif
902
904{
905 mMotionInfo.clear();
906 mLastTimestamp = 0;
907}
#define AIMPRWProp(type, name)
static void s_MotionLogging(const std::string &_callsign, double tInterp, SGVec3d ecPos, const SGGeod &pos)
bool _installed
Definition AIBase.hxx:259
double speed_fps
Definition AIBase.hxx:217
double altitude_ft
Definition AIBase.hxx:218
SGGeod pos
Definition AIBase.hxx:212
std::string _callsign
Definition AIBase.hxx:188
bool invisible
Definition AIBase.hxx:256
FGAIBase(object_type ot, bool enableHot)
Definition AIBase.cxx:146
std::string model_path
Definition AIBase.hxx:250
virtual bool init(ModelSearchOrder searchOrder)
Definition AIBase.cxx:633
virtual void update(double dt)
Definition AIBase.cxx:294
double speed
Definition AIBase.hxx:216
double UpdateRadar(FGAIManager *manager)
Definition AIBase.cxx:819
double vs_fps
Definition AIBase.hxx:219
double hdg
Definition AIBase.hxx:213
double elevation
Definition AIBase.hxx:240
const char * _getCallsign() const
Definition AIBase.cxx:1140
void Transform()
Definition AIBase.cxx:518
int _getFallbackModelIndex() const
Definition AIBase.cxx:1148
static const double e
Definition AIBase.hxx:359
double pitch
Definition AIBase.hxx:215
ModelSearchOrder
Definition AIBase.hxx:63
ModelSearchOrder _searchOrder
Definition AIBase.hxx:278
double roll
Definition AIBase.hxx:214
void tie(const char *aRelPath, const SGRawValue< T > &aRawValue)
Tied-properties helper, record nodes which are tied for easy un-tie-ing.
Definition AIBase.hxx:198
SGPropertyNode_ptr props
Definition AIBase.hxx:205
FGAIManager * manager
Definition AIBase.hxx:209
virtual void bind()
Definition AIBase.cxx:713
bool no_roll
Definition AIBase.hxx:257
bool init(ModelSearchOrder searchOrder) override
void addMotionInfo(FGExternalMotionData &motionInfo, long stamp)
void update(double dt) override
void bind() override
FGGlobals * globals
Definition globals.cxx:142
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
Definition proptest.cpp:30
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
static void interpolate(FGReplayInternal &self, double time, const std::deque< FGReplayData * > &list)
interpolate a specific time from a specific list
std::vector< FGPropertyData * > properties