swift
traffic.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #ifndef NOMINMAX
7 # define NOMINMAX
8 #endif
9 
10 // clang-format off
11 #include "plugin.h"
12 #include "traffic.h"
13 #include "utils.h"
14 #include "XPMPMultiplayer.h"
15 #include <XPLM/XPLMGraphics.h>
16 #include <XPLM/XPLMProcessing.h>
17 #include <XPLM/XPLMUtilities.h>
18 #include <XPLM/XPLMPlanes.h>
19 #include <XPLM/XPLMPlugin.h>
21 #include <cassert>
22 #include <cstring>
23 #include <cmath>
24 #include <ctime>
25 #include <algorithm>
26 #include <limits>
27 // clang-format on
28 
29 // clazy:excludeall=reserve-candidates
30 
31 float XPMP_PrepListHook(float, float, int, void *); // defined in xplanemp2/src/Renderer.cpp
32 
33 using namespace swift::misc::simulation::xplane::qtfreeutils;
34 using namespace std::chrono_literals;
35 
36 namespace XSwiftBus
37 {
38  CTraffic::Plane::Plane(void *id_, const std::string &callsign_, const std::string &aircraftIcao_,
39  const std::string &airlineIcao_, const std::string &livery_, const std::string &modelName_)
40  : id(id_), callsign(callsign_), aircraftIcao(aircraftIcao_), airlineIcao(airlineIcao_), livery(livery_),
41  modelName(modelName_)
42  {
43  std::memset(static_cast<void *>(&positions), 0, sizeof(positions));
44  for (auto &position : positions) { position.size = sizeof(position); }
45  std::memset(static_cast<void *>(&surveillance), 0, sizeof(surveillance));
46  surveillance.size = sizeof(surveillance);
47  std::memset(static_cast<void *>(&surfaces), 0, sizeof(surfaces));
48  surfaces.size = sizeof(surfaces);
49  surfaces.lights.bcnLights = surfaces.lights.landLights = surfaces.lights.navLights =
50  surfaces.lights.strbLights = 1;
51 
52  std::strncpy(label, callsign.c_str(), sizeof(label));
53  for (auto &position : positions) { memcpy(position.label, label, sizeof(label)); }
54 
55  std::srand(static_cast<unsigned int>(std::time(nullptr)));
56  surfaces.lights.timeOffset = static_cast<uint16_t>(std::rand() % 0xffff);
57  }
58 
59  CTraffic *CTraffic::s_instance = nullptr;
60 
61  // *INDENT-OFF*
62  CTraffic::CTraffic(CSettingsProvider *settingsProvider)
63  : CDBusObject(settingsProvider),
64  m_followPlaneViewNextCommand("org/swift-project/xswiftbus/follow_next_plane",
65  "Changes plane view to follow next plane in sequence",
66  [this] { followNextPlane(); }),
67  m_followPlaneViewPreviousCommand("org/swift-project/xswiftbus/follow_previous_plane",
68  "Changes plane view to follow previous plane in sequence",
69  [this] { followPreviousPlane(); })
70  {
71  assert(!s_instance);
72  s_instance = this;
73  XPLMRegisterKeySniffer(followAircraftKeySniffer, 1, this);
74 
75  // init labels
76  this->setDrawingLabels(this->getSettings().isDrawingLabels());
77  }
78  // *INDENT-ON*
79 
80  CTraffic::~CTraffic()
81  {
82  cleanup();
83  assert(s_instance == this);
84  s_instance = nullptr;
85  }
86 
87  void CTraffic::setPlaneViewMenu(const CMenu &planeViewSubMenu) { m_followPlaneViewSubMenu = planeViewSubMenu; }
88 
89  bool CTraffic::initialize()
90  {
91  if (!m_initialized)
92  {
94  auto dir =
95  g_xplanePath + "Resources" + g_sep + "plugins" + g_sep + "xswiftbus" + g_sep + "LegacyData" + g_sep;
96  std::string related = dir + "related.txt";
97  std::string doc8643 = dir + "Doc8643.txt";
98 
99  updateConfiguration();
100  auto err = XPMPMultiplayerInit(&m_configuration, related.c_str(), doc8643.c_str());
101  if (err && *err) { cleanup(); }
102  else { m_initialized = true; }
103  }
104 
105  return m_initialized;
106  }
107 
108  bool CTraffic::acquireMultiplayerPlanes(std::string *owner)
109  {
110  if (!m_enabledMultiplayer)
111  {
112  auto err = XPMPMultiplayerEnable();
113  if (*err) { cleanup(); }
114  else
115  {
116  m_enabledMultiplayer = true;
117 
118  // we will call xplanemp's callback from within our own callback
119  XPLMUnregisterFlightLoopCallback(XPMP_PrepListHook, nullptr);
120  }
121  }
122 
123  int totalAircraft;
124  int activeAircraft;
125  XPLMPluginID controller;
126  XPLMCountAircraft(&totalAircraft, &activeAircraft, &controller);
127 
128  char pluginName[256];
129  XPLMGetPluginInfo(controller, pluginName, nullptr, nullptr, nullptr);
130  *owner = std::string(pluginName);
131  return m_enabledMultiplayer;
132  }
133 
134  void CTraffic::cleanup()
135  {
136  removeAllPlanes();
137 
138  if (m_enabledMultiplayer)
139  {
140  m_enabledMultiplayer = false;
141  XPMPMultiplayerDisable();
142  }
143 
144  if (m_initialized)
145  {
146  m_initialized = false;
147  XPMPMultiplayerCleanup();
148  }
149  }
150 
151  void CTraffic::emitSimFrame()
152  {
153  if (m_emitSimFrame) { sendDBusSignal("simFrame"); }
154  m_emitSimFrame = !m_emitSimFrame;
155  }
156 
157  void CTraffic::emitPlaneAdded(const std::string &callsign)
158  {
159  CDBusMessage signalPlaneAdded = CDBusMessage::createSignal(
160  XSWIFTBUS_TRAFFIC_OBJECTPATH, XSWIFTBUS_TRAFFIC_INTERFACENAME, "remoteAircraftAdded");
161  signalPlaneAdded.beginArgumentWrite();
162  signalPlaneAdded.appendArgument(callsign);
163  sendDBusMessage(signalPlaneAdded);
164  }
165 
166  void CTraffic::emitPlaneAddingFailed(const std::string &callsign)
167  {
168  CDBusMessage signalPlaneAddingFailed = CDBusMessage::createSignal(
169  XSWIFTBUS_TRAFFIC_OBJECTPATH, XSWIFTBUS_TRAFFIC_INTERFACENAME, "remoteAircraftAddingFailed");
170  signalPlaneAddingFailed.beginArgumentWrite();
171  signalPlaneAddingFailed.appendArgument(callsign);
172  sendDBusMessage(signalPlaneAddingFailed);
173  }
174 
175  void CTraffic::switchToFollowPlaneView(const std::string &callsign)
176  {
177  if (CTraffic::ownAircraftString() != callsign && !this->containsCallsign(callsign))
178  {
179  INFO_LOG("Cannot switch to follow " + callsign);
180  return;
181  }
182 
183  m_followPlaneViewCallsign = callsign;
184 
185  /* This is the hotkey callback. First we simulate a joystick press and
186  * release to put us in 'free view 1'. This guarantees that no panels
187  * are showing and we are an external view. */
188  XPLMCommandButtonPress(xplm_joy_v_fr1);
189  XPLMCommandButtonRelease(xplm_joy_v_fr1);
190 
191  /* Now we control the camera until the view changes. */
192  INFO_LOG("Switch to follow " + callsign);
193  XPLMControlCamera(xplm_ControlCameraUntilViewChanges, CTraffic::orbitPlaneFunc, this);
194  }
195 
196  void CTraffic::followNextPlane()
197  {
198  if (m_planesByCallsign.empty() || m_followPlaneViewCallsign.empty()) { return; }
199  auto callsignIt =
200  std::find(m_followPlaneViewSequence.begin(), m_followPlaneViewSequence.end(), m_followPlaneViewCallsign);
201 
202  // If we are not at the end, increase by one
203  if (callsignIt != m_followPlaneViewSequence.end()) { callsignIt++; }
204  // If we were already at the end or reached it now, start from the beginning
205  if (callsignIt == m_followPlaneViewSequence.end()) { callsignIt = m_followPlaneViewSequence.begin(); }
206 
207  m_followPlaneViewCallsign = *callsignIt;
208  }
209 
210  void CTraffic::followPreviousPlane()
211  {
212  if (m_planesByCallsign.empty() || m_followPlaneViewCallsign.empty()) { return; }
213  auto callsignIt =
214  std::find(m_followPlaneViewSequence.rbegin(), m_followPlaneViewSequence.rend(), m_followPlaneViewCallsign);
215 
216  // If we are not at the end, increase by one
217  if (callsignIt != m_followPlaneViewSequence.rend()) { callsignIt++; }
218  // If we were already at the end or reached it now, start from the beginning
219  if (callsignIt == m_followPlaneViewSequence.rend()) { callsignIt = m_followPlaneViewSequence.rbegin(); }
220 
221  m_followPlaneViewCallsign = *callsignIt;
222  }
223 
224  bool CTraffic::containsCallsign(const std::string &callsign) const
225  {
227  if (callsign.empty()) { return false; }
228  const auto planeIt = m_planesByCallsign.find(callsign);
229  if (planeIt == m_planesByCallsign.end()) { return false; }
230  return true;
231  }
232 
233  // changed T709
234  // static int g_maxPlanes = 100;
235  // static float g_drawDistance = 50.0f;
236 
237  void CTraffic::updateConfiguration()
238  {
239  m_configuration.maxFullAircraftRenderingDistance =
240  static_cast<float>(s_instance->getSettings().getMaxDrawDistanceNM());
241  m_configuration.enableSurfaceClamping = true;
242  m_configuration.debug.modelMatching = false;
243  }
244 
245  std::string CTraffic::loadPlanesPackage(const std::string &path)
246  {
247  initXPlanePath();
248 
249  auto err = XPMPMultiplayerLoadCSLPackages(path.c_str());
250  if (*err) { return err; }
251 
252  for (int i = 0, end = XPMPGetNumberOfInstalledModels(); i < end; ++i)
253  {
254  const char *mixedcase;
255  XPMPGetModelInfo(i, &mixedcase, nullptr, nullptr, nullptr);
256  std::string uppercase(mixedcase);
257  std::transform(uppercase.begin(), uppercase.end(), uppercase.begin(),
258  [](char c) { return static_cast<char>(std::toupper(c)); });
259  m_modelStrings[uppercase] = mixedcase;
260  }
261 
262  return {};
263  }
264 
265  void CTraffic::setDefaultIcao(const std::string &defaultIcao) { XPMPSetDefaultPlaneICAO(defaultIcao.c_str()); }
266 
267  void CTraffic::setDrawingLabels(bool drawing, int rgb)
268  {
269  CSettings s = this->getSettings();
270  if (s.isDrawingLabels() != drawing)
271  {
272  s.setDrawingLabels(drawing);
273  this->setSettings(s);
274  }
275  if (rgb >= 0) { m_labels.setColor((rgb & 0xff0000) >> 16, (rgb & 0x00ff00) >> 8, rgb & 0x0000ff); }
276  if (drawing) { m_labels.show(); }
277  else { m_labels.hide(); }
278  }
279 
280  bool CTraffic::isDrawingLabels() const { return m_labels.isVisible(); }
281 
282  void CTraffic::setMaxPlanes(int planes)
283  {
284  CSettings s = this->getSettings();
285  if (s.setMaxPlanes(planes)) { this->setSettings(s); }
286  }
287 
288  void CTraffic::setMaxDrawDistance(double nauticalMiles)
289  {
290  CSettings s = this->getSettings();
291  if (s.setMaxDrawDistanceNM(nauticalMiles)) { this->setSettings(s); }
292  }
293 
294  void CTraffic::addPlane(const std::string &callsign, const std::string &modelName, const std::string &aircraftIcao,
295  const std::string &airlineIcao, const std::string &livery)
296  {
297  auto planeIt = m_planesByCallsign.find(callsign);
298  if (planeIt != m_planesByCallsign.end()) { return; }
299 
300  XPMPPlaneID id = nullptr;
301  if (modelName.empty() || m_modelStrings.count(modelName) == 0)
302  {
303  DEBUG_LOG("Model " + modelName + " is unknown, falling back to basic xpmp2 model matching");
304  id = XPMPCreatePlane(aircraftIcao.c_str(), airlineIcao.c_str(), livery.c_str());
305  }
306  else
307  {
308  id = XPMPCreatePlaneWithModelName(m_modelStrings[modelName].c_str(), aircraftIcao.c_str(),
309  airlineIcao.c_str(), livery.c_str());
310  }
311 
312  if (!id)
313  {
314  emitPlaneAddingFailed(callsign);
315  return;
316  }
317 
318  Plane *plane = new Plane(id, callsign, aircraftIcao, airlineIcao, livery, modelName);
319  m_planesByCallsign[callsign] = plane;
320  m_planesById[id] = plane;
321 
322  // Create view menu item
323  CMenuItem planeViewMenuItem =
324  m_followPlaneViewSubMenu.item(callsign, [this, callsign] { switchToFollowPlaneView(callsign); });
325  m_followPlaneViewMenuItems[callsign] = planeViewMenuItem;
326  m_followPlaneViewSequence.push_back(callsign);
327 
328  emitPlaneAdded(callsign);
329  }
330 
331  void CTraffic::removePlane(const std::string &callsign)
332  {
333  auto menuItemIt = m_followPlaneViewMenuItems.find(callsign);
334  if (menuItemIt != m_followPlaneViewMenuItems.end())
335  {
336  m_followPlaneViewSubMenu.removeItem(menuItemIt->second);
337  m_followPlaneViewMenuItems.erase(menuItemIt);
338  }
339 
340  auto planeIt = m_planesByCallsign.find(callsign);
341  if (planeIt == m_planesByCallsign.end()) { return; }
342 
343  m_followPlaneViewSequence.erase(
344  std::remove(m_followPlaneViewSequence.begin(), m_followPlaneViewSequence.end(), callsign),
345  m_followPlaneViewSequence.end());
346 
347  Plane *plane = planeIt->second;
348  m_planesByCallsign.erase(callsign);
349  m_planesById.erase(plane->id);
350  XPMPDestroyPlane(plane->id);
351  delete plane;
352  }
353 
354  void CTraffic::removeAllPlanes()
355  {
356  for (const auto &kv : m_planesByCallsign)
357  {
358  Plane *plane = kv.second;
359  assert(plane);
360  XPMPDestroyPlane(plane->id);
361  delete plane;
362  }
363 
364  for (const auto &kv : m_followPlaneViewMenuItems)
365  {
366  CMenuItem item = kv.second;
367  m_followPlaneViewSubMenu.removeItem(item);
368  }
369 
370  m_planesByCallsign.clear();
371  m_planesById.clear();
372  m_followPlaneViewMenuItems.clear();
373  m_followPlaneViewSequence.clear();
374  }
375 
376  void CTraffic::setPlanesPositions(const std::vector<std::string> &callsigns, std::vector<double> latitudesDeg,
377  std::vector<double> longitudesDeg, std::vector<double> altitudesFt,
378  std::vector<double> pitchesDeg, std::vector<double> rollsDeg,
379  std::vector<double> headingsDeg, const std::vector<bool> &onGrounds)
380  {
381  const bool setOnGround = onGrounds.size() == callsigns.size();
382  for (size_t i = 0; i < callsigns.size(); i++)
383  {
384  auto planeIt = m_planesByCallsign.find(callsigns.at(i));
385  if (planeIt == m_planesByCallsign.end()) { continue; }
386 
387  Plane *plane = planeIt->second;
388  if (!plane) { continue; }
389  plane->positions[2].lat = latitudesDeg.at(i);
390  plane->positions[2].lon = longitudesDeg.at(i);
391  plane->positions[2].elevation = altitudesFt.at(i);
392  plane->positions[2].pitch = static_cast<float>(pitchesDeg.at(i));
393  plane->positions[2].roll = static_cast<float>(rollsDeg.at(i));
394  plane->positions[2].heading = static_cast<float>(headingsDeg.at(i));
395  plane->positions[2].offsetScale = 1.0f;
396  plane->positions[2].clampToGround = true;
397  plane->positionTimes[2] = std::chrono::steady_clock::now();
398 
399  // save 2 positions at 1-second intervals for use in interpolation
400  if (plane->positionTimes[2] - plane->positionTimes[1] > 1s)
401  {
402  plane->positionTimes[0] = plane->positionTimes[1];
403  plane->positionTimes[1] = plane->positionTimes[2];
404  std::memcpy(&plane->positions[0], &plane->positions[1], sizeof(plane->positions[0]));
405  std::memcpy(&plane->positions[1], &plane->positions[2], sizeof(plane->positions[0]));
406  }
407 
408  if (setOnGround) { plane->isOnGround = onGrounds.at(i); }
409  }
410  }
411 
412  void CTraffic::setPlanesSurfaces(const std::vector<std::string> &callsigns, const std::vector<double> &gears,
413  const std::vector<double> &flaps, const std::vector<double> &spoilers,
414  const std::vector<double> &speedBrakes, const std::vector<double> &slats,
415  const std::vector<double> &wingSweeps, const std::vector<double> &thrusts,
416  const std::vector<double> &elevators, const std::vector<double> &rudders,
417  const std::vector<double> &ailerons, const std::vector<bool> &landLights,
418  const std::vector<bool> &taxiLights, const std::vector<bool> &beaconLights,
419  const std::vector<bool> &strobeLights, const std::vector<bool> &navLights,
420  const std::vector<int> &lightPatterns)
421  {
422  const bool bundleTaxiLandingLights = this->getSettings().isBundlingTaxiAndLandingLights();
423 
424  for (size_t i = 0; i < callsigns.size(); i++)
425  {
426  auto planeIt = m_planesByCallsign.find(callsigns.at(i));
427  if (planeIt == m_planesByCallsign.end()) { continue; }
428 
429  Plane *plane = planeIt->second;
430  if (!plane) { continue; }
431 
432  plane->hasSurfaces = true;
433  plane->targetGearPosition = static_cast<float>(gears.at(i));
434  plane->surfaces.flapRatio = static_cast<float>(flaps.at(i));
435  plane->surfaces.spoilerRatio = static_cast<float>(spoilers.at(i));
436  plane->surfaces.speedBrakeRatio = static_cast<float>(speedBrakes.at(i));
437  plane->surfaces.slatRatio = static_cast<float>(slats.at(i));
438  plane->surfaces.wingSweep = static_cast<float>(wingSweeps.at(i));
439  plane->surfaces.thrust = static_cast<float>(thrusts.at(i));
440  plane->surfaces.yokePitch = static_cast<float>(elevators.at(i));
441  plane->surfaces.yokeHeading = static_cast<float>(rudders.at(i));
442  plane->surfaces.yokeRoll = static_cast<float>(ailerons.at(i));
443  if (bundleTaxiLandingLights)
444  {
445  const bool on = landLights.at(i) || taxiLights.at(i);
446  plane->surfaces.lights.landLights = on;
447  plane->surfaces.lights.taxiLights = on;
448  }
449  else
450  {
451  plane->surfaces.lights.landLights = landLights.at(i);
452  plane->surfaces.lights.taxiLights = taxiLights.at(i);
453  }
454  plane->surfaces.lights.bcnLights = beaconLights.at(i);
455  plane->surfaces.lights.strbLights = strobeLights.at(i);
456  plane->surfaces.lights.navLights = navLights.at(i);
457  plane->surfaces.lights.flashPattern = static_cast<unsigned int>(lightPatterns.at(i));
458  }
459  }
460 
461  void CTraffic::setPlanesTransponders(const std::vector<std::string> &callsigns, const std::vector<int> &codes,
462  const std::vector<bool> &modeCs, const std::vector<bool> &idents)
463  {
464  for (size_t i = 0; i < callsigns.size(); i++)
465  {
466  auto planeIt = m_planesByCallsign.find(callsigns.at(i));
467  if (planeIt == m_planesByCallsign.end()) { continue; }
468 
469  Plane *plane = planeIt->second;
470  if (!plane) { continue; }
471 
472  plane->surveillance.code = codes.at(i);
473  if (idents.at(i)) { plane->surveillance.mode = xpmpTransponderMode_ModeC_Ident; }
474  else if (modeCs.at(i)) { plane->surveillance.mode = xpmpTransponderMode_ModeC; }
475  else { plane->surveillance.mode = xpmpTransponderMode_Standby; }
476  }
477  }
478 
479  void CTraffic::getRemoteAircraftData(std::vector<std::string> &callsigns, std::vector<double> &latitudesDeg,
480  std::vector<double> &longitudesDeg, std::vector<double> &elevationsM,
481  std::vector<bool> &waterFlags, std::vector<double> &verticalOffsets) const
482  {
483  if (callsigns.empty() || m_planesByCallsign.empty()) { return; }
484 
485  const auto requestedCallsigns = callsigns;
486  callsigns.clear();
487  latitudesDeg.clear();
488  longitudesDeg.clear();
489  elevationsM.clear();
490  verticalOffsets.clear();
491  waterFlags.clear();
492 
493  for (const auto &requestedCallsign : requestedCallsigns)
494  {
495  const auto planeIt = m_planesByCallsign.find(requestedCallsign);
496  if (planeIt == m_planesByCallsign.end()) { continue; }
497 
498  const Plane *plane = planeIt->second;
499  assert(plane);
500 
501  const double latDeg = plane->positions[2].lat;
502  const double lonDeg = plane->positions[2].lon;
503  double groundElevation = 0.0;
504  bool isWater = false;
505  if (getSettings().isTerrainProbeEnabled())
506  {
507  // we expect elevation in meters
508  groundElevation =
509  plane->terrainProbe
510  .getElevation(latDeg, lonDeg, plane->positions[2].elevation, requestedCallsign, isWater)
511  .front();
512  if (std::isnan(groundElevation)) { groundElevation = 0.0; }
513  }
514 
515  callsigns.push_back(requestedCallsign);
516  latitudesDeg.push_back(latDeg);
517  longitudesDeg.push_back(lonDeg);
518  elevationsM.push_back(groundElevation);
519  waterFlags.push_back(isWater);
520  verticalOffsets.push_back(0); // xpmp2 adjusts the offset for us, so effectively always zero
521  }
522  }
523 
524  std::array<double, 3> CTraffic::getElevationAtPosition(const std::string &callsign, double latitudeDeg,
525  double longitudeDeg, double altitudeMeters,
526  bool &o_isWater) const
527  {
528  if (!getSettings().isTerrainProbeEnabled())
529  {
530  return { { std::numeric_limits<double>::quiet_NaN(), latitudeDeg, longitudeDeg } };
531  }
532 
533  const auto planeIt = m_planesByCallsign.find(callsign);
534  if (planeIt != m_planesByCallsign.end())
535  {
536  const Plane *plane = planeIt->second;
537  return plane->terrainProbe.getElevation(latitudeDeg, longitudeDeg, altitudeMeters, callsign, o_isWater);
538  }
539  else
540  {
541  return m_terrainProbe.getElevation(latitudeDeg, longitudeDeg, altitudeMeters,
542  callsign + " (plane not found)", o_isWater);
543  }
544  }
545 
546  void CTraffic::setFollowedAircraft(const std::string &callsign) { this->switchToFollowPlaneView(callsign); }
547 
548  void CTraffic::dbusDisconnectedHandler() { removeAllPlanes(); }
549 
550  static const char *introspection_traffic = DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
551 #include "org.swift_project.xswiftbus.traffic.xml"
552  ;
553 
554  DBusHandlerResult CTraffic::dbusMessageHandler(const CDBusMessage &message_)
555  {
556  CDBusMessage message(message_);
557  const std::string sender = message.getSender();
558  const dbus_uint32_t serial = message.getSerial();
559  const bool wantsReply = message.wantsReply();
560 
561  // Debug message if needed
562  // { const std::string d = "dbusMessageHandler: " + message.getMethodName(); INFO_LOG(d.c_str()); }
563 
564  if (message.getInterfaceName() == DBUS_INTERFACE_INTROSPECTABLE)
565  {
566  if (message.getMethodName() == "Introspect") { sendDBusReply(sender, serial, introspection_traffic); }
567  }
568  else if (message.getInterfaceName() == XSWIFTBUS_TRAFFIC_INTERFACENAME)
569  {
570  if (message.getMethodName() == "acquireMultiplayerPlanes")
571  {
572  queueDBusCall([=]() {
573  std::string owner;
574  bool acquired = acquireMultiplayerPlanes(&owner);
575  CDBusMessage reply = CDBusMessage::createReply(sender, serial);
576  reply.beginArgumentWrite();
577  reply.appendArgument(acquired);
578  reply.appendArgument(owner);
579  sendDBusMessage(reply);
580  });
581  }
582  else if (message.getMethodName() == "initialize") { sendDBusReply(sender, serial, initialize()); }
583  else if (message.getMethodName() == "cleanup")
584  {
585  maybeSendEmptyDBusReply(wantsReply, sender, serial);
586  queueDBusCall([=]() { cleanup(); });
587  }
588  else if (message.getMethodName() == "loadPlanesPackage")
589  {
590  std::string path;
591  message.beginArgumentRead();
592  message.getArgument(path);
593  queueDBusCall([=]() { sendDBusReply(sender, serial, loadPlanesPackage(path)); });
594  }
595  else if (message.getMethodName() == "setDefaultIcao")
596  {
597  std::string defaultIcao;
598  message.beginArgumentRead();
599  message.getArgument(defaultIcao);
600  queueDBusCall([=]() { setDefaultIcao(defaultIcao); });
601  }
602  else if (message.getMethodName() == "setMaxPlanes")
603  {
604  maybeSendEmptyDBusReply(wantsReply, sender, serial);
605  int planes = 100;
606  message.beginArgumentRead();
607  message.getArgument(planes);
608  queueDBusCall([=]() { setMaxPlanes(planes); });
609  }
610  else if (message.getMethodName() == "setMaxDrawDistance")
611  {
612  maybeSendEmptyDBusReply(wantsReply, sender, serial);
613  double nauticalMiles = 100;
614  message.beginArgumentRead();
615  message.getArgument(nauticalMiles);
616  queueDBusCall([=]() { setMaxDrawDistance(nauticalMiles); });
617  }
618  else if (message.getMethodName() == "addPlane")
619  {
620  maybeSendEmptyDBusReply(wantsReply, sender, serial);
621  std::string callsign;
622  std::string modelName;
623  std::string aircraftIcao;
624  std::string airlineIcao;
625  std::string livery;
626  message.beginArgumentRead();
627  message.getArgument(callsign);
628  message.getArgument(modelName);
629  message.getArgument(aircraftIcao);
630  message.getArgument(airlineIcao);
631  message.getArgument(livery);
632 
633  queueDBusCall([=]() { addPlane(callsign, modelName, aircraftIcao, airlineIcao, livery); });
634  }
635  else if (message.getMethodName() == "removePlane")
636  {
637  maybeSendEmptyDBusReply(wantsReply, sender, serial);
638  std::string callsign;
639  message.beginArgumentRead();
640  message.getArgument(callsign);
641  queueDBusCall([=]() { removePlane(callsign); });
642  }
643  else if (message.getMethodName() == "removeAllPlanes")
644  {
645  maybeSendEmptyDBusReply(wantsReply, sender, serial);
646  queueDBusCall([=]() { removeAllPlanes(); });
647  }
648  else if (message.getMethodName() == "setPlanesPositions")
649  {
650  maybeSendEmptyDBusReply(wantsReply, sender, serial);
651  std::vector<std::string> callsigns;
652  std::vector<double> latitudes;
653  std::vector<double> longitudes;
654  std::vector<double> altitudes;
655  std::vector<double> pitches;
656  std::vector<double> rolls;
657  std::vector<double> headings;
658  std::vector<bool> onGrounds;
659  message.beginArgumentRead();
660  message.getArgument(callsigns);
661  message.getArgument(latitudes);
662  message.getArgument(longitudes);
663  message.getArgument(altitudes);
664  message.getArgument(pitches);
665  message.getArgument(rolls);
666  message.getArgument(headings);
667  message.getArgument(onGrounds);
668  queueDBusCall([=]() {
669  setPlanesPositions(callsigns, latitudes, longitudes, altitudes, pitches, rolls, headings,
670  onGrounds);
671  });
672  }
673  else if (message.getMethodName() == "setPlanesSurfaces")
674  {
675  maybeSendEmptyDBusReply(wantsReply, sender, serial);
676  std::vector<std::string> callsigns;
677  std::vector<double> gears;
678  std::vector<double> flaps;
679  std::vector<double> spoilers;
680  std::vector<double> speedBrakes;
681  std::vector<double> slats;
682  std::vector<double> wingSweeps;
683  std::vector<double> thrusts;
684  std::vector<double> elevators;
685  std::vector<double> rudders;
686  std::vector<double> ailerons;
687  std::vector<bool> landLights;
688  std::vector<bool> taxiLights;
689  std::vector<bool> beaconLights;
690  std::vector<bool> strobeLights;
691  std::vector<bool> navLights;
692  std::vector<int> lightPatterns;
693  message.beginArgumentRead();
694  message.getArgument(callsigns);
695  message.getArgument(gears);
696  message.getArgument(flaps);
697  message.getArgument(spoilers);
698  message.getArgument(speedBrakes);
699  message.getArgument(slats);
700  message.getArgument(wingSweeps);
701  message.getArgument(thrusts);
702  message.getArgument(elevators);
703  message.getArgument(rudders);
704  message.getArgument(ailerons);
705  message.getArgument(landLights);
706  message.getArgument(taxiLights);
707  message.getArgument(beaconLights);
708  message.getArgument(strobeLights);
709  message.getArgument(navLights);
710  message.getArgument(lightPatterns);
711  queueDBusCall([=]() {
712  setPlanesSurfaces(callsigns, gears, flaps, spoilers, speedBrakes, slats, wingSweeps, thrusts,
713  elevators, rudders, ailerons, landLights, taxiLights, beaconLights, strobeLights,
714  navLights, lightPatterns);
715  });
716  }
717  else if (message.getMethodName() == "setPlanesTransponders")
718  {
719  maybeSendEmptyDBusReply(wantsReply, sender, serial);
720  std::vector<std::string> callsigns;
721  std::vector<int> codes;
722  std::vector<bool> modeCs;
723  std::vector<bool> idents;
724  message.beginArgumentRead();
725  message.getArgument(callsigns);
726  message.getArgument(codes);
727  message.getArgument(modeCs);
728  message.getArgument(idents);
729  queueDBusCall([=]() { setPlanesTransponders(callsigns, codes, modeCs, idents); });
730  }
731  else if (message.getMethodName() == "getRemoteAircraftData")
732  {
733  std::vector<std::string> requestedCallsigns;
734  message.beginArgumentRead();
735  message.getArgument(requestedCallsigns);
736  queueDBusCall([=]() {
737  std::vector<std::string> callsigns = requestedCallsigns;
738  std::vector<double> latitudesDeg;
739  std::vector<double> longitudesDeg;
740  std::vector<double> elevationsM;
741  std::vector<bool> waterFlags;
742  std::vector<double> verticalOffsets;
743  getRemoteAircraftData(callsigns, latitudesDeg, longitudesDeg, elevationsM, waterFlags,
744  verticalOffsets);
745  CDBusMessage reply = CDBusMessage::createReply(sender, serial);
746  reply.beginArgumentWrite();
747  reply.appendArgument(callsigns);
748  reply.appendArgument(latitudesDeg);
749  reply.appendArgument(longitudesDeg);
750  reply.appendArgument(elevationsM);
751  reply.appendArgument(waterFlags);
752  reply.appendArgument(verticalOffsets);
753  sendDBusMessage(reply);
754  });
755  }
756  else if (message.getMethodName() == "getElevationAtPosition")
757  {
758  std::string callsign;
759  double latitudeDeg;
760  double longitudeDeg;
761  double altitudeMeters;
762  message.beginArgumentRead();
763  message.getArgument(callsign);
764  message.getArgument(latitudeDeg);
765  message.getArgument(longitudeDeg);
766  message.getArgument(altitudeMeters);
767  queueDBusCall([=]() {
768  bool isWater = false;
769  const auto elevation =
770  getElevationAtPosition(callsign, latitudeDeg, longitudeDeg, altitudeMeters, isWater);
771  CDBusMessage reply = CDBusMessage::createReply(sender, serial);
772  reply.beginArgumentWrite();
773  reply.appendArgument(callsign);
774  reply.appendArgument(elevation[0]);
775  reply.appendArgument(elevation[1]);
776  reply.appendArgument(elevation[2]);
777  reply.appendArgument(isWater);
778  sendDBusMessage(reply);
779  });
780  }
781  else if (message.getMethodName() == "setFollowedAircraft")
782  {
783  maybeSendEmptyDBusReply(wantsReply, sender, serial);
784  std::string callsign;
785  message.beginArgumentRead();
786  message.getArgument(callsign);
787  queueDBusCall([=]() { setFollowedAircraft(callsign); });
788  }
789  else
790  {
791  // Unknown message. Tell DBus that we cannot handle it
792  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
793  }
794  }
795  return DBUS_HANDLER_RESULT_HANDLED;
796  }
797 
798  int CTraffic::process()
799  {
800  invokeQueuedDBusCalls();
801  doPlaneUpdates();
802  setDrawingLabels(getSettings().isDrawingLabels(), getSettings().getLabelColor());
803  emitSimFrame();
804  m_countFrame++;
805  return 1;
806  }
807 
809  template <typename T>
810  int memcmpPayload(T *dst, T *src)
811  {
812  return std::memcmp(reinterpret_cast<char *>(dst) + sizeof(dst->size),
813  reinterpret_cast<char *>(src) + sizeof(src->size), sizeof(*dst) - sizeof(dst->size));
814  }
815 
816  void CTraffic::doPlaneUpdates()
817  {
818  m_updates.clear();
819  for (const auto &pair : m_planesById)
820  {
821  Plane *plane = pair.second;
822  interpolatePosition(plane);
823  interpolateGear(plane);
824  m_updates.push_back({ plane->id, &plane->positions[3], &plane->surfaces, &plane->surveillance });
825  }
826  XPMPUpdatePlanes(m_updates.data(), sizeof(XPMPUpdate_t), m_updates.size());
827 
828  XPMP_PrepListHook(0, 0, 0, nullptr);
829  }
830 
831  void CTraffic::interpolatePosition(Plane *plane)
832  {
833  std::memcpy(&plane->positions[3], &plane->positions[2], sizeof(plane->positions[2]));
834 
835  const auto now = std::chrono::steady_clock::now();
836  const auto t1 = plane->positionTimes[2] - plane->positionTimes[0];
837  const auto t2 = now - plane->positionTimes[0];
838 
839  // This interpolation is only intended to smooth over
840  // small errors. Give up if the error is too large.
841  if (t1 > 3s || t2 > 3s) { return; }
842 
843  const double dLat = plane->positions[2].lat - plane->positions[0].lat;
844  const double dLon = plane->positions[2].lon - plane->positions[0].lon;
845  const double dAlt = plane->positions[2].elevation - plane->positions[0].elevation;
846 
847  plane->positions[3].lat = plane->positions[0].lat + dLat * t2 / t1;
848  plane->positions[3].lon = plane->positions[0].lon + dLon * t2 / t1;
849  plane->positions[3].elevation = plane->positions[0].elevation + dAlt * t2 / t1;
850  }
851 
852  void CTraffic::interpolateGear(Plane *plane)
853  {
854  const auto now = std::chrono::steady_clock::now();
855  static const float epsilon = std::numeric_limits<float>::epsilon();
856  const float f = plane->surfaces.gearPosition - plane->targetGearPosition;
857  if (std::abs(f) > epsilon)
858  {
859  constexpr float gearMoveTimeMs = 5000;
860  const auto gearPositionDiffRemaining = plane->targetGearPosition - plane->surfaces.gearPosition;
861 
862  const auto diffMs =
863  std::chrono::duration_cast<std::chrono::milliseconds>(now - plane->prevSurfacesLerpTime);
864  const auto gearPositionDiffThisFrame = (diffMs.count()) / gearMoveTimeMs;
865  plane->surfaces.gearPosition += std::copysign(gearPositionDiffThisFrame, gearPositionDiffRemaining);
866  plane->surfaces.gearPosition = std::max(0.0f, std::min(plane->surfaces.gearPosition, 1.0f));
867  }
868  plane->prevSurfacesLerpTime = now;
869  }
870 
871  void CTraffic::Labels::draw()
872  {
873  static const double maxRangeM = 10000;
874  static const double metersPerFt = 0.3048;
875  std::array<float, 16> worldMat = m_worldMat.getAll();
876  std::array<float, 16> projMat = m_projMat.getAll();
877  double windowWidth = static_cast<double>(m_windowWidth.get());
878  double windowHeight = static_cast<double>(m_windowHeight.get());
879  XPLMCameraPosition_t camPos {};
880  XPLMReadCameraPosition(&camPos);
881 
882  for (const auto &pair : m_traffic->m_planesById)
883  {
884  char *text = const_cast<char *>(pair.second->label);
885  const XPMPPlanePosition_t &planePos = pair.second->positions[3];
886 
887  double worldPos[4] { 0, 0, 0, 1 };
888  double localPos[4] {};
889  double windowPos[4] {};
890  XPLMWorldToLocal(planePos.lat, planePos.lon, planePos.elevation * metersPerFt, &worldPos[0], &worldPos[1],
891  &worldPos[2]);
892  if (distanceSquared(worldPos) > maxRangeM * maxRangeM) { continue; }
893  matrixMultVec(localPos, worldMat.data(), worldPos);
894  matrixMultVec(windowPos, projMat.data(), localPos);
895 
896  windowPos[3] = 1.0 / windowPos[3];
897  windowPos[0] *= windowPos[3];
898  windowPos[1] *= windowPos[3];
899  windowPos[2] *= windowPos[3];
900 
901  if (windowPos[2] < 0.0 || windowPos[2] > 1.0)
902  {
903  continue; // plane is behind camera
904  }
905  XPLMDrawString(m_color.data(), static_cast<int>(std::lround(windowWidth * (windowPos[0] * 0.5 + 0.5))),
906  static_cast<int>(std::lround(windowHeight * (windowPos[1] * 0.5 + 0.5))), text, nullptr,
907  xplmFont_Basic);
908  }
909  }
910 
911  void CTraffic::Labels::matrixMultVec(double out[4], const float m[16], const double v[4])
912  {
913  out[0] = v[0] * m[0] + v[1] * m[4] + v[2] * m[8] + v[3] * m[12];
914  out[1] = v[0] * m[1] + v[1] * m[5] + v[2] * m[9] + v[3] * m[13];
915  out[2] = v[0] * m[2] + v[1] * m[6] + v[2] * m[10] + v[3] * m[14];
916  out[3] = v[0] * m[3] + v[1] * m[7] + v[2] * m[11] + v[3] * m[15];
917  }
918 
919  double CTraffic::Labels::distanceSquared(const double pos[3]) const
920  {
921  const double dx = m_viewX.get() - pos[0];
922  const double dy = m_viewY.get() - pos[1];
923  const double dz = m_viewZ.get() - pos[2];
924  return dx * dx + dy * dy + dz * dz;
925  }
926 
927  int CTraffic::orbitPlaneFunc(XPLMCameraPosition_t *cameraPosition, int isLosingControl, void *refcon)
928  {
929  constexpr bool DEBUG = false;
930  // DEBUG_LOG_C("Follow aircraft entry", DEBUG);
931  if (isLosingControl == 1)
932  {
933  // do NOT use refcon here, might be invalid
934  INFO_LOG("Loosing camera control");
935  return 0;
936  }
937 
938  auto *traffic = static_cast<CTraffic *>(refcon);
939  if (!traffic)
940  {
941  ERROR_LOG("Cannot convert CTraffic object");
942  return 0;
943  }
944 
945  // nothing we can do
946  if (!cameraPosition)
947  {
948  ERROR_LOG("No camera object");
949  traffic->m_followPlaneViewCallsign.clear();
950  return 0;
951  }
952 
953  // Ideally we would like to test against right mouse button, but X-Plane SDK does not
954  // allow that.
955  if (!traffic->m_deltaCameraPosition.isInitialized || traffic->m_isSpacePressed)
956  {
957  int x = 0, y = 0, left = 0, top = 0, right = 0, bottom = 0;
958  // First get the screen size and mouse location. We will use this to decide
959  // what part of the orbit we are in. The mouse will move us up-down and around.
960  // fixme: In a future update, change the orbit only while right mouse button is pressed.
961  XPLMGetScreenBoundsGlobal(&left, &top, &right, &bottom);
962  XPLMGetMouseLocationGlobal(&x, &y);
963  int w = right - left;
964  int h = top - bottom;
965  x -= left;
966  y -= bottom;
967  if (DEBUG)
968  {
969  DEBUG_LOG("Follow aircraft coordinates w,h,x,y: " + std::to_string(w) + " " + std::to_string(h) + " " +
970  std::to_string(x) + " " + std::to_string(y));
971  } // cppcheck-suppress knownConditionTrueFalse
972  if (traffic->m_lastMouseX == x && traffic->m_lastMouseY == y && traffic->m_lastMouseX >= 0 &&
973  traffic->m_lastMouseY >= 0)
974  {
975  // mouse NOT moving, we lost focus or we do NOT move anymore
976  // to avoid issues we reset the space key, see
977  // https://discordapp.com/channels/539048679160676382/539925070550794240/614162134644949002
978  traffic->m_isSpacePressed = false;
979  }
980 
981  // avoid follow aircraft in too small windows
982  // int cannot be NaN
983  if (w < 100 || h < 100)
984  {
985  WARNING_LOG("Screen w/h too small " + std::to_string(w) + "/" + std::to_string(h));
986  return 0;
987  }
988 
989  // the 1.25 factor allows to turn around completely
990  traffic->m_deltaCameraPosition.headingDeg =
991  normalizeToZero360Deg(1.25 * 360.0 * static_cast<double>(x) / static_cast<double>(w)); // range 0-360
992  double usedCameraPitchDeg =
993  60.0 - (60.0 * 2.0 * static_cast<double>(y) / static_cast<double>(h)); // range +-
994 
995  // make sure we can use it with tan in range +-90 degrees and the result of tan not getting too high
996  // we limit to +-85deg, tan 45deg: 1 | tan 60deg: 1.73 | tan 85deg: 11.4
997  if (usedCameraPitchDeg >= 85.0) { usedCameraPitchDeg = 85.0; }
998  else if (usedCameraPitchDeg <= -85.0) { usedCameraPitchDeg = -85.0; }
999  traffic->m_deltaCameraPosition.pitchDeg = usedCameraPitchDeg;
1000 
1001  // Now calculate where the camera should be positioned to be x
1002  // meters from the plane and pointing at the plane at the pitch and
1003  // heading we wanted above.
1004  const double distanceMeterM =
1005  traffic->m_followAircraftDistanceMultiplier *
1006  static_cast<double>(std::max(10, traffic->getSettings().getFollowAircraftDistanceM()));
1007  static const double PI = std::acos(-1);
1008  traffic->m_deltaCameraPosition.dxMeters =
1009  -distanceMeterM * sin(traffic->m_deltaCameraPosition.headingDeg * PI / 180.0);
1010  traffic->m_deltaCameraPosition.dzMeters =
1011  distanceMeterM * cos(traffic->m_deltaCameraPosition.headingDeg * PI / 180.0);
1012  traffic->m_deltaCameraPosition.dyMeters =
1013  -distanceMeterM * tan(traffic->m_deltaCameraPosition.pitchDeg * PI / 180.0);
1014 
1015  traffic->m_deltaCameraPosition.isInitialized = true;
1016  }
1017 
1018  double lxMeters = 0, lyMeters = 0, lzMeters = 0; // normally init not needed, just to avoid any issues
1019  static const double kFtToMeters = 0.3048;
1020 
1021  std::string modelName = "unknown";
1022  if (traffic->m_followPlaneViewCallsign == CTraffic::ownAircraftString())
1023  {
1024  lxMeters = traffic->m_ownAircraftPositionX.get();
1025  lyMeters = traffic->m_ownAircraftPositionY.get();
1026  lzMeters = traffic->m_ownAircraftPositionZ.get();
1027  modelName = CTraffic::ownAircraftString();
1028  }
1029  else
1030  {
1031  if (traffic->m_planesByCallsign.empty())
1032  {
1033  INFO_LOG("Follow aircraft, no planes to follow");
1034  traffic->m_followPlaneViewCallsign.clear();
1035  return 0;
1036  }
1037 
1038  if (traffic->m_followPlaneViewCallsign.empty())
1039  {
1040  INFO_LOG("Follow aircraft, no callsign to follow");
1041  traffic->m_followPlaneViewCallsign.clear();
1042  traffic->m_followAircraftDistanceMultiplier = 1.0;
1043  return 0;
1044  }
1045 
1046  const auto planeIt = traffic->m_planesByCallsign.find(traffic->m_followPlaneViewCallsign);
1047  if (planeIt == traffic->m_planesByCallsign.end())
1048  {
1049  INFO_LOG("Follow aircraft, no plane found for callsign " + traffic->m_followPlaneViewCallsign);
1050  traffic->m_followPlaneViewCallsign.clear();
1051  traffic->m_followAircraftDistanceMultiplier = 1.0;
1052  return 0;
1053  }
1054 
1055  const Plane *plane = planeIt->second;
1056  if (!plane)
1057  {
1058  ERROR_LOG("Follow aircraft, no plane from iterator for callsign " + traffic->m_followPlaneViewCallsign);
1059  traffic->m_followPlaneViewCallsign.clear();
1060  traffic->m_followAircraftDistanceMultiplier = 1.0;
1061  return 0;
1062  }
1063 
1064  modelName = plane->modelName;
1065  if (!isValidPosition(plane->positions[3]))
1066  {
1067  WARNING_LOG("Invalid follow aircraft position for " + plane->callsign);
1068  WARNING_LOG("Pos: " + pos2String(plane->positions[3]));
1069  return 0;
1070  }
1071 
1072  // avoid underflow of camera into ground
1073  if (plane->isOnGround)
1074  {
1075  if (traffic->m_deltaCameraPosition.dyMeters < 10) { traffic->m_deltaCameraPosition.dyMeters = 10; }
1076  }
1077 
1078  XPLMWorldToLocal(plane->positions[3].lat, plane->positions[3].lon,
1079  plane->positions[3].elevation * kFtToMeters, &lxMeters, &lyMeters, &lzMeters);
1080  }
1081 
1082  // Fill out the camera position info.
1083  cameraPosition->x = static_cast<float>(lxMeters + traffic->m_deltaCameraPosition.dxMeters);
1084  cameraPosition->y = static_cast<float>(lyMeters + traffic->m_deltaCameraPosition.dyMeters);
1085  cameraPosition->z = static_cast<float>(lzMeters + traffic->m_deltaCameraPosition.dzMeters);
1086  cameraPosition->pitch =
1087  CTraffic::normalizeToPlusMinus180Deg(static_cast<float>(traffic->m_deltaCameraPosition.pitchDeg));
1088  cameraPosition->heading =
1089  CTraffic::normalizeToPlusMinus180Deg(static_cast<float>(traffic->m_deltaCameraPosition.headingDeg));
1090  cameraPosition->roll = 0.0;
1091  cameraPosition->zoom = 1.0;
1092 
1093  if (!isValidPosition(cameraPosition))
1094  {
1095  WARNING_LOG("Invalid camera aircraft position");
1096  WARNING_LOG("Pos: " + pos2String(cameraPosition));
1097  traffic->m_followAircraftDistanceMultiplier = 1.0;
1098  return 0;
1099  }
1100 
1101  if (DEBUG) // cppcheck-suppress knownConditionTrueFalse
1102  {
1103  DEBUG_LOG("Camera: " + pos2String(cameraPosition));
1104  DEBUG_LOG("Follow aircraft " + traffic->m_followPlaneViewCallsign + " " + modelName);
1105  }
1106  // Return 1 to indicate we want to keep controlling the camera.
1107  return 1;
1108  }
1109 
1110  int CTraffic::followAircraftKeySniffer(char character, XPLMKeyFlags flags, char virtualKey, void *refcon)
1111  {
1112  (void)character;
1113  CTraffic *traffic = static_cast<CTraffic *>(refcon);
1114  if (!traffic || traffic->m_followPlaneViewCallsign.empty())
1115  {
1116  return 1;
1117  } // totally ignore if nothing is being followed
1118 
1119  // We are only interested in Space key
1120  if (virtualKey == XPLM_VK_SPACE)
1121  {
1122  // if XPlane looses focus it can happen that key down is NOT reset
1123  // for the camera we use the init flag instead, so it is only run once
1124  if (flags & xplm_DownFlag) { traffic->m_isSpacePressed = true; }
1125  if (flags & xplm_UpFlag) { traffic->m_isSpacePressed = false; }
1126  }
1127  else if (virtualKey == XPLM_VK_DOWN && (flags & xplm_UpFlag))
1128  {
1129  traffic->m_followAircraftDistanceMultiplier /= 1.2;
1130  }
1131  else if (virtualKey == XPLM_VK_UP && (flags & xplm_UpFlag))
1132  {
1133  traffic->m_followAircraftDistanceMultiplier *= 1.2;
1134  }
1135  else if (virtualKey == XPLM_VK_ESCAPE && (flags & xplm_UpFlag))
1136  {
1137  traffic->m_followAircraftDistanceMultiplier = 1.0;
1138  }
1139 
1140  /* Return 1 to pass the keystroke to plugin windows and X-Plane.
1141  * Returning 0 would consume the keystroke. */
1142  return 1;
1143  }
1144 
1145  bool CTraffic::isPlusMinus180(float v)
1146  {
1147  if (std::isnan(v)) { return false; }
1148  if (v > 180.00001f || v < -180.00001f) { return false; }
1149  return true;
1150  }
1151 
1152  bool CTraffic::isPlusMinus180(double v)
1153  {
1154  if (std::isnan(v)) { return false; }
1155  if (v > 180.00001 || v < -180.00001) { return false; }
1156  return true;
1157  }
1158 
1159  float CTraffic::normalizeToPlusMinus180Deg(float v)
1160  {
1161  if (std::isnan(v)) { return 0.0f; }
1162  return static_cast<float>(normalizeToPlusMinus180Deg(static_cast<double>(v)));
1163  }
1164 
1165  double CTraffic::normalizeToPlusMinus180Deg(double v)
1166  {
1167  if (std::isnan(v)) { return 0.0; }
1168  const double n = normalizeValue(v, -180.0, 180.0);
1169  if (n <= -180.0) { return 180.0; }
1170  if (n > 180.0) { return 180.0; }
1171  return n;
1172  }
1173 
1174  float CTraffic::normalizeToZero360Deg(float v)
1175  {
1176  if (std::isnan(v)) { return 0.0f; }
1177  return static_cast<float>(normalizeToZero360Deg(static_cast<double>(v)));
1178  }
1179 
1180  double CTraffic::normalizeToZero360Deg(double v)
1181  {
1182  if (std::isnan(v)) { return 0.0; }
1183  const double n = normalizeValue(v, 0, 360.0);
1184  if (n >= 360.0) { return 0.0; }
1185  if (n < 0.0) { return 0.0; }
1186  return n;
1187  }
1188 
1189  bool CTraffic::isValidPosition(const XPMPPlanePosition_t &position)
1190  {
1191  if (!isPlusMinus180(position.lat)) { return false; }
1192  if (!isPlusMinus180(position.lon)) { return false; }
1193  if (!isPlusMinus180(position.pitch)) { return false; }
1194  if (!isPlusMinus180(position.roll)) { return false; }
1195  if (!isPlusMinus180(position.heading)) { return false; }
1196  if (position.elevation < -2000.0 || position.elevation > 100000.0) { return false; }
1197 
1198  return true;
1199  }
1200 
1201  bool CTraffic::isValidPosition(const XPLMCameraPosition_t *camPos)
1202  {
1203  if (!isPlusMinus180(camPos->roll)) { return false; }
1204  if (!isPlusMinus180(camPos->pitch)) { return false; }
1205  if (!isPlusMinus180(camPos->heading)) { return false; }
1206 
1207  // x, y, z not in -1..1 range
1208  if (std::isnan(camPos->x)) { return false; }
1209  if (std::isnan(camPos->y)) { return false; }
1210  if (std::isnan(camPos->z)) { return false; }
1211 
1212  return true;
1213  }
1214 
1215  std::string CTraffic::pos2String(const XPMPPlanePosition_t &position)
1216  {
1217  return "lat, lon, el: " + std::to_string(position.lat) + "/" + std::to_string(position.lon) + "/" +
1218  std::to_string(position.elevation) + " prh: " + std::to_string(position.pitch) + "/" +
1219  std::to_string(position.roll) + "/" + std::to_string(position.heading);
1220  }
1221 
1222  std::string CTraffic::pos2String(const XPLMCameraPosition_t *camPos)
1223  {
1224  if (!camPos) { return "camera position invalid"; }
1225  return "x, y, z: " + std::to_string(camPos->x) + "/" + std::to_string(camPos->y) + "/" +
1226  std::to_string(camPos->z) + " zoom: " + std::to_string(camPos->zoom) +
1227  " prh: " + std::to_string(camPos->pitch) + "/" + std::to_string(camPos->roll) + "/" +
1228  std::to_string(camPos->heading);
1229  }
1230 } // namespace XSwiftBus
1231 
Plugin loaded by X-Plane which publishes a DBus service.
Definition: command.h:14
std::string g_xplanePath
Absolute xplane path.
std::string g_sep
Platform specific dir separator.
void initXPlanePath()
Init global xplane path.
T::const_iterator end(const LockFreeReader< T > &reader)
Non-member begin() and end() for so LockFree containers can be used in ranged for loops.
Definition: lockfree.h:338
double normalizeValue(const double value, const double start, const double end)
Normalize value to range start -> end (like for +-180degrees)
Definition: qtfreeutils.h:72
#define DEBUG_LOG(msg)
Logger convenience macros.
Definition: utils.h:47
#define ERROR_LOG(msg)
Logger convenience macros.
Definition: utils.h:52
#define INFO_LOG(msg)
Logger convenience macros.
Definition: utils.h:50
#define WARNING_LOG(msg)
Logger convenience macros.
Definition: utils.h:51