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