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>
31float XPMP_PrepListHook(
float,
float,
int,
void *);
33using namespace swift::misc::simulation::xplane::qtfreeutils;
34using namespace std::chrono_literals;
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_),
43 std::memset(
static_cast<void *
>(&positions), 0,
sizeof(positions));
44 for (
auto &position : positions)
46 position.size =
sizeof(position);
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;
55 std::strncpy(label, callsign.c_str(),
sizeof(label));
56 for (
auto &position : positions)
58 memcpy(position.label, label,
sizeof(label));
61 std::srand(
static_cast<unsigned int>(std::time(
nullptr)));
62 surfaces.lights.timeOffset =
static_cast<uint16_t
>(std::rand() % 0xffff);
65 CTraffic *CTraffic::s_instance =
nullptr;
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(); })
78 XPLMRegisterKeySniffer(followAircraftKeySniffer, 1,
this);
81 this->setDrawingLabels(this->getSettings().isDrawingLabels());
87 assert(s_instance ==
this);
91 void CTraffic::setPlaneViewMenu(
const CMenu &planeViewSubMenu) { m_followPlaneViewSubMenu = planeViewSubMenu; }
93 bool CTraffic::initialize()
100 std::string related = dir +
"related.txt";
101 std::string doc8643 = dir +
"Doc8643.txt";
103 updateConfiguration();
104 auto err = XPMPMultiplayerInit(&m_configuration, related.c_str(), doc8643.c_str());
111 m_initialized =
true;
115 return m_initialized;
118 bool CTraffic::acquireMultiplayerPlanes(std::string *owner)
120 if (!m_enabledMultiplayer)
122 auto err = XPMPMultiplayerEnable();
129 m_enabledMultiplayer =
true;
132 XPLMUnregisterFlightLoopCallback(XPMP_PrepListHook,
nullptr);
138 XPLMPluginID controller;
139 XPLMCountAircraft(&totalAircraft, &activeAircraft, &controller);
141 char pluginName[256];
142 XPLMGetPluginInfo(controller, pluginName,
nullptr,
nullptr,
nullptr);
143 *owner = std::string(pluginName);
144 return m_enabledMultiplayer;
147 void CTraffic::cleanup()
151 if (m_enabledMultiplayer)
153 m_enabledMultiplayer =
false;
154 XPMPMultiplayerDisable();
159 m_initialized =
false;
160 XPMPMultiplayerCleanup();
164 void CTraffic::emitSimFrame()
168 sendDBusSignal(
"simFrame");
170 m_emitSimFrame = !m_emitSimFrame;
173 void CTraffic::emitPlaneAdded(
const std::string &callsign)
175 CDBusMessage signalPlaneAdded = CDBusMessage::createSignal(
176 XSWIFTBUS_TRAFFIC_OBJECTPATH, XSWIFTBUS_TRAFFIC_INTERFACENAME,
"remoteAircraftAdded");
177 signalPlaneAdded.beginArgumentWrite();
178 signalPlaneAdded.appendArgument(callsign);
179 sendDBusMessage(signalPlaneAdded);
182 void CTraffic::emitPlaneAddingFailed(
const std::string &callsign)
184 CDBusMessage signalPlaneAddingFailed = CDBusMessage::createSignal(
185 XSWIFTBUS_TRAFFIC_OBJECTPATH, XSWIFTBUS_TRAFFIC_INTERFACENAME,
"remoteAircraftAddingFailed");
186 signalPlaneAddingFailed.beginArgumentWrite();
187 signalPlaneAddingFailed.appendArgument(callsign);
188 sendDBusMessage(signalPlaneAddingFailed);
191 void CTraffic::switchToFollowPlaneView(
const std::string &callsign)
193 if (CTraffic::ownAircraftString() != callsign && !this->containsCallsign(callsign))
195 INFO_LOG(
"Cannot switch to follow " + callsign);
199 m_followPlaneViewCallsign = callsign;
204 XPLMCommandButtonPress(xplm_joy_v_fr1);
205 XPLMCommandButtonRelease(xplm_joy_v_fr1);
208 INFO_LOG(
"Switch to follow " + callsign);
209 XPLMControlCamera(xplm_ControlCameraUntilViewChanges, CTraffic::orbitPlaneFunc,
this);
212 void CTraffic::followNextPlane()
214 if (m_planesByCallsign.empty() || m_followPlaneViewCallsign.empty())
219 std::find(m_followPlaneViewSequence.begin(), m_followPlaneViewSequence.end(), m_followPlaneViewCallsign);
222 if (callsignIt != m_followPlaneViewSequence.end())
227 if (callsignIt == m_followPlaneViewSequence.end())
229 callsignIt = m_followPlaneViewSequence.begin();
232 m_followPlaneViewCallsign = *callsignIt;
235 void CTraffic::followPreviousPlane()
237 if (m_planesByCallsign.empty() || m_followPlaneViewCallsign.empty())
242 std::find(m_followPlaneViewSequence.rbegin(), m_followPlaneViewSequence.rend(), m_followPlaneViewCallsign);
245 if (callsignIt != m_followPlaneViewSequence.rend())
250 if (callsignIt == m_followPlaneViewSequence.rend())
252 callsignIt = m_followPlaneViewSequence.rbegin();
255 m_followPlaneViewCallsign = *callsignIt;
258 bool CTraffic::containsCallsign(
const std::string &callsign)
const
261 if (callsign.empty())
265 const auto planeIt = m_planesByCallsign.find(callsign);
266 if (planeIt == m_planesByCallsign.end())
277 void CTraffic::updateConfiguration()
279 m_configuration.maxFullAircraftRenderingDistance =
280 static_cast<float>(s_instance->getSettings().getMaxDrawDistanceNM());
281 m_configuration.enableSurfaceClamping =
true;
282 m_configuration.debug.modelMatching =
false;
285 std::string CTraffic::loadPlanesPackage(
const std::string &path)
289 auto err = XPMPMultiplayerLoadCSLPackages(path.c_str());
295 for (
int i = 0,
end = XPMPGetNumberOfInstalledModels(); i <
end; ++i)
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;
308 void CTraffic::setDefaultIcao(
const std::string &defaultIcao) { XPMPSetDefaultPlaneICAO(defaultIcao.c_str()); }
310 void CTraffic::setDrawingLabels(
bool drawing,
int rgb)
312 CSettings s = this->getSettings();
313 if (s.isDrawingLabels() != drawing)
315 s.setDrawingLabels(drawing);
316 this->setSettings(s);
320 m_labels.setColor((rgb & 0xff0000) >> 16, (rgb & 0x00ff00) >> 8, rgb & 0x0000ff);
332 bool CTraffic::isDrawingLabels()
const {
return m_labels.isVisible(); }
334 void CTraffic::setMaxPlanes(
int planes)
336 CSettings s = this->getSettings();
337 if (s.setMaxPlanes(planes))
339 this->setSettings(s);
343 void CTraffic::setMaxDrawDistance(
double nauticalMiles)
345 CSettings s = this->getSettings();
346 if (s.setMaxDrawDistanceNM(nauticalMiles))
348 this->setSettings(s);
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)
355 auto planeIt = m_planesByCallsign.find(callsign);
356 if (planeIt != m_planesByCallsign.end())
361 XPMPPlaneID
id =
nullptr;
362 if (modelName.empty() || m_modelStrings.count(modelName) == 0)
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());
369 id = XPMPCreatePlaneWithModelName(m_modelStrings[modelName].c_str(), aircraftIcao.c_str(),
370 airlineIcao.c_str(), livery.c_str());
375 emitPlaneAddingFailed(callsign);
379 Plane *plane =
new Plane(
id, callsign, aircraftIcao, airlineIcao, livery, modelName);
380 m_planesByCallsign[callsign] = plane;
381 m_planesById[id] = plane;
384 CMenuItem planeViewMenuItem =
385 m_followPlaneViewSubMenu.appendItem(callsign, [
this, callsign] { switchToFollowPlaneView(callsign); });
386 m_followPlaneViewMenuItems[callsign] = planeViewMenuItem;
387 m_followPlaneViewSequence.push_back(callsign);
389 emitPlaneAdded(callsign);
392 void CTraffic::removePlane(
const std::string &callsign)
394 auto menuItemIt = m_followPlaneViewMenuItems.find(callsign);
395 if (menuItemIt != m_followPlaneViewMenuItems.end())
397 m_followPlaneViewSubMenu.removeItem(menuItemIt->second);
398 m_followPlaneViewMenuItems.erase(menuItemIt);
401 auto planeIt = m_planesByCallsign.find(callsign);
402 if (planeIt == m_planesByCallsign.end())
407 m_followPlaneViewSequence.erase(
408 std::remove(m_followPlaneViewSequence.begin(), m_followPlaneViewSequence.end(), callsign),
409 m_followPlaneViewSequence.end());
411 Plane *plane = planeIt->second;
412 m_planesByCallsign.erase(callsign);
413 m_planesById.erase(plane->id);
414 XPMPDestroyPlane(plane->id);
418 void CTraffic::removeAllPlanes()
420 for (
const auto &kv : m_planesByCallsign)
422 Plane *plane = kv.second;
424 XPMPDestroyPlane(plane->id);
428 for (
const auto &kv : m_followPlaneViewMenuItems)
430 CMenuItem item = kv.second;
431 m_followPlaneViewSubMenu.removeItem(item);
434 m_planesByCallsign.clear();
435 m_planesById.clear();
436 m_followPlaneViewMenuItems.clear();
437 m_followPlaneViewSequence.clear();
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)
446 const bool setOnGround = onGrounds.size() == callsigns.size();
447 for (
size_t i = 0; i < callsigns.size(); i++)
449 auto planeIt = m_planesByCallsign.find(callsigns.at(i));
450 if (planeIt == m_planesByCallsign.end())
455 Plane *plane = planeIt->second;
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();
471 if (plane->positionTimes[2] - plane->positionTimes[1] > 1s)
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]));
481 plane->isOnGround = onGrounds.at(i);
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)
496 const bool bundleTaxiLandingLights = this->getSettings().isBundlingTaxiAndLandingLights();
498 for (
size_t i = 0; i < callsigns.size(); i++)
500 auto planeIt = m_planesByCallsign.find(callsigns.at(i));
501 if (planeIt == m_planesByCallsign.end())
506 Plane *plane = planeIt->second;
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)
525 const bool on = landLights.at(i) || taxiLights.at(i);
526 plane->surfaces.lights.landLights = on;
527 plane->surfaces.lights.taxiLights = on;
531 plane->surfaces.lights.landLights = landLights.at(i);
532 plane->surfaces.lights.taxiLights = taxiLights.at(i);
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));
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)
544 for (
size_t i = 0; i < callsigns.size(); i++)
546 auto planeIt = m_planesByCallsign.find(callsigns.at(i));
547 if (planeIt == m_planesByCallsign.end())
552 Plane *plane = planeIt->second;
558 plane->surveillance.code = codes.at(i);
561 plane->surveillance.mode = xpmpTransponderMode_ModeC_Ident;
563 else if (modeCs.at(i))
565 plane->surveillance.mode = xpmpTransponderMode_ModeC;
569 plane->surveillance.mode = xpmpTransponderMode_Standby;
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
578 if (callsigns.empty() || m_planesByCallsign.empty())
583 const auto requestedCallsigns = callsigns;
585 latitudesDeg.clear();
586 longitudesDeg.clear();
588 verticalOffsets.clear();
591 for (
const auto &requestedCallsign : requestedCallsigns)
593 const auto planeIt = m_planesByCallsign.find(requestedCallsign);
594 if (planeIt == m_planesByCallsign.end())
599 const Plane *plane = planeIt->second;
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())
611 .getElevation(latDeg, lonDeg, plane->positions[2].elevation, requestedCallsign, isWater)
613 if (std::isnan(groundElevation))
615 groundElevation = 0.0;
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);
628 std::array<double, 3> CTraffic::getElevationAtPosition(
const std::string &callsign,
double latitudeDeg,
629 double longitudeDeg,
double altitudeMeters,
630 bool &o_isWater)
const
632 if (!getSettings().isTerrainProbeEnabled())
634 return { { std::numeric_limits<double>::quiet_NaN(), latitudeDeg, longitudeDeg } };
637 const auto planeIt = m_planesByCallsign.find(callsign);
638 if (planeIt != m_planesByCallsign.end())
640 const Plane *plane = planeIt->second;
641 return plane->terrainProbe.getElevation(latitudeDeg, longitudeDeg, altitudeMeters, callsign, o_isWater);
645 return m_terrainProbe.getElevation(latitudeDeg, longitudeDeg, altitudeMeters,
646 callsign +
" (plane not found)", o_isWater);
650 void CTraffic::setFollowedAircraft(
const std::string &callsign) { this->switchToFollowPlaneView(callsign); }
652 void CTraffic::dbusDisconnectedHandler() { removeAllPlanes(); }
654 static const char *introspection_traffic = DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
655#include "org.swift_project.xswiftbus.traffic.xml"
659 DBusHandlerResult CTraffic::dbusMessageHandler(
const CDBusMessage &message_)
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();
669 if (message.getInterfaceName() == DBUS_INTERFACE_INTROSPECTABLE)
671 if (message.getMethodName() ==
"Introspect")
673 sendDBusReply(sender, serial, introspection_traffic);
676 else if (message.getInterfaceName() == XSWIFTBUS_TRAFFIC_INTERFACENAME)
678 if (message.getMethodName() ==
"acquireMultiplayerPlanes")
680 queueDBusCall([=,
this]() {
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);
690 else if (message.getMethodName() ==
"initialize")
692 sendDBusReply(sender, serial, initialize());
694 else if (message.getMethodName() ==
"cleanup")
696 maybeSendEmptyDBusReply(wantsReply, sender, serial);
697 queueDBusCall([=,
this]() { cleanup(); });
699 else if (message.getMethodName() ==
"loadPlanesPackage")
702 message.beginArgumentRead();
703 message.getArgument(path);
704 queueDBusCall([=,
this]() { sendDBusReply(sender, serial, loadPlanesPackage(path)); });
706 else if (message.getMethodName() ==
"setDefaultIcao")
708 std::string defaultIcao;
709 message.beginArgumentRead();
710 message.getArgument(defaultIcao);
711 queueDBusCall([=]() { setDefaultIcao(defaultIcao); });
713 else if (message.getMethodName() ==
"setMaxPlanes")
715 maybeSendEmptyDBusReply(wantsReply, sender, serial);
717 message.beginArgumentRead();
718 message.getArgument(planes);
719 queueDBusCall([=,
this]() { setMaxPlanes(planes); });
721 else if (message.getMethodName() ==
"setMaxDrawDistance")
723 maybeSendEmptyDBusReply(wantsReply, sender, serial);
724 double nauticalMiles = 100;
725 message.beginArgumentRead();
726 message.getArgument(nauticalMiles);
727 queueDBusCall([=,
this]() { setMaxDrawDistance(nauticalMiles); });
729 else if (message.getMethodName() ==
"addPlane")
731 maybeSendEmptyDBusReply(wantsReply, sender, serial);
732 std::string callsign;
733 std::string modelName;
734 std::string aircraftIcao;
735 std::string airlineIcao;
737 message.beginArgumentRead();
738 message.getArgument(callsign);
739 message.getArgument(modelName);
740 message.getArgument(aircraftIcao);
741 message.getArgument(airlineIcao);
742 message.getArgument(livery);
744 queueDBusCall([=,
this]() { addPlane(callsign, modelName, aircraftIcao, airlineIcao, livery); });
746 else if (message.getMethodName() ==
"removePlane")
748 maybeSendEmptyDBusReply(wantsReply, sender, serial);
749 std::string callsign;
750 message.beginArgumentRead();
751 message.getArgument(callsign);
752 queueDBusCall([=,
this]() { removePlane(callsign); });
754 else if (message.getMethodName() ==
"removeAllPlanes")
756 maybeSendEmptyDBusReply(wantsReply, sender, serial);
757 queueDBusCall([=,
this]() { removeAllPlanes(); });
759 else if (message.getMethodName() ==
"setPlanesPositions")
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,
784 else if (message.getMethodName() ==
"setPlanesSurfaces")
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);
828 else if (message.getMethodName() ==
"setPlanesTransponders")
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); });
842 else if (message.getMethodName() ==
"getRemoteAircraftData")
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,
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);
867 else if (message.getMethodName() ==
"getElevationAtPosition")
869 std::string callsign;
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);
892 else if (message.getMethodName() ==
"setFollowedAircraft")
894 maybeSendEmptyDBusReply(wantsReply, sender, serial);
895 std::string callsign;
896 message.beginArgumentRead();
897 message.getArgument(callsign);
898 queueDBusCall([=,
this]() { setFollowedAircraft(callsign); });
903 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
906 return DBUS_HANDLER_RESULT_HANDLED;
909 int CTraffic::process()
911 invokeQueuedDBusCalls();
913 setDrawingLabels(getSettings().isDrawingLabels(), getSettings().getLabelColor());
920 template <
typename T>
921 int memcmpPayload(T *dst, T *src)
923 return std::memcmp(
reinterpret_cast<char *
>(dst) +
sizeof(dst->size),
924 reinterpret_cast<char *
>(src) +
sizeof(src->size),
sizeof(*dst) -
sizeof(dst->size));
927 void CTraffic::doPlaneUpdates()
930 for (
const auto &pair : m_planesById)
932 Plane *plane = pair.second;
933 interpolatePosition(plane);
934 interpolateGear(plane);
935 XPMPUpdate_t update = { plane->id, &plane->positions[3], &plane->surfaces, &plane->surveillance,
937 m_updates.push_back(update);
939 XPMPUpdatePlanes(m_updates.data(),
sizeof(XPMPUpdate_t), m_updates.size());
941 XPMP_PrepListHook(0, 0, 0,
nullptr);
944 void CTraffic::interpolatePosition(Plane *plane)
946 std::memcpy(&plane->positions[3], &plane->positions[2],
sizeof(plane->positions[2]));
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];
954 if (t1 > 3s || t2 > 3s)
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;
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;
968 void CTraffic::interpolateGear(Plane *plane)
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)
975 constexpr float gearMoveTimeMs = 5000;
976 const auto gearPositionDiffRemaining = plane->targetGearPosition - plane->surfaces.gearPosition;
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));
984 plane->prevSurfacesLerpTime = now;
987 void CTraffic::Labels::draw()
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);
998 for (
const auto &pair : m_traffic->m_planesById)
1000 char *text =
const_cast<char *
>(pair.second->label);
1001 const XPMPPlanePosition_t &planePos = pair.second->positions[3];
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],
1008 if (distanceSquared(worldPos) > maxRangeM * maxRangeM)
1012 matrixMultVec(localPos, worldMat.data(), worldPos);
1013 matrixMultVec(windowPos, projMat.data(), localPos);
1015 windowPos[3] = 1.0 / windowPos[3];
1016 windowPos[0] *= windowPos[3];
1017 windowPos[1] *= windowPos[3];
1018 windowPos[2] *= windowPos[3];
1020 if (windowPos[2] < 0.0 || windowPos[2] > 1.0)
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,
1030 void CTraffic::Labels::matrixMultVec(
double out[4],
const float m[16],
const double v[4])
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];
1038 double CTraffic::Labels::distanceSquared(
const double pos[3])
const
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;
1046 int CTraffic::orbitPlaneFunc(XPLMCameraPosition_t *cameraPosition,
int isLosingControl,
void *refcon)
1048 constexpr bool DEBUG =
false;
1050 if (isLosingControl == 1)
1053 INFO_LOG(
"Loosing camera control");
1057 auto *traffic =
static_cast<CTraffic *
>(refcon);
1060 ERROR_LOG(
"Cannot convert CTraffic object");
1065 if (!cameraPosition)
1068 traffic->m_followPlaneViewCallsign.clear();
1074 if (!traffic->m_deltaCameraPosition.isInitialized || traffic->m_isSpacePressed)
1076 int x = 0, y = 0,
left = 0, top = 0,
right = 0, bottom = 0;
1080 XPLMGetScreenBoundsGlobal(&left, &top, &right, &bottom);
1081 XPLMGetMouseLocationGlobal(&x, &y);
1083 int h = top - bottom;
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));
1091 if (traffic->m_lastMouseX == x && traffic->m_lastMouseY == y && traffic->m_lastMouseX >= 0 &&
1092 traffic->m_lastMouseY >= 0)
1097 traffic->m_isSpacePressed =
false;
1102 if (w < 100 || h < 100)
1104 WARNING_LOG(
"Screen w/h too small " + std::to_string(w) +
"/" + std::to_string(h));
1109 traffic->m_deltaCameraPosition.headingDeg =
1110 normalizeToZero360Deg(1.25 * 360.0 *
static_cast<double>(x) /
static_cast<double>(w));
1111 double usedCameraPitchDeg =
1112 60.0 - (60.0 * 2.0 *
static_cast<double>(y) /
static_cast<double>(h));
1116 if (usedCameraPitchDeg >= 85.0)
1118 usedCameraPitchDeg = 85.0;
1120 else if (usedCameraPitchDeg <= -85.0)
1122 usedCameraPitchDeg = -85.0;
1124 traffic->m_deltaCameraPosition.pitchDeg = usedCameraPitchDeg;
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);
1140 traffic->m_deltaCameraPosition.isInitialized =
true;
1143 double lxMeters = 0, lyMeters = 0, lzMeters = 0;
1144 static const double kFtToMeters = 0.3048;
1146 std::string modelName =
"unknown";
1147 if (traffic->m_followPlaneViewCallsign == CTraffic::ownAircraftString())
1149 lxMeters = traffic->m_ownAircraftPositionX.get();
1150 lyMeters = traffic->m_ownAircraftPositionY.get();
1151 lzMeters = traffic->m_ownAircraftPositionZ.get();
1152 modelName = CTraffic::ownAircraftString();
1156 if (traffic->m_planesByCallsign.empty())
1158 INFO_LOG(
"Follow aircraft, no planes to follow");
1159 traffic->m_followPlaneViewCallsign.clear();
1163 if (traffic->m_followPlaneViewCallsign.empty())
1165 INFO_LOG(
"Follow aircraft, no callsign to follow");
1166 traffic->m_followPlaneViewCallsign.clear();
1167 traffic->m_followAircraftDistanceMultiplier = 1.0;
1171 const auto planeIt = traffic->m_planesByCallsign.find(traffic->m_followPlaneViewCallsign);
1172 if (planeIt == traffic->m_planesByCallsign.end())
1174 INFO_LOG(
"Follow aircraft, no plane found for callsign " + traffic->m_followPlaneViewCallsign);
1175 traffic->m_followPlaneViewCallsign.clear();
1176 traffic->m_followAircraftDistanceMultiplier = 1.0;
1180 const Plane *plane = planeIt->second;
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;
1189 modelName = plane->modelName;
1190 if (!isValidPosition(plane->positions[3]))
1192 WARNING_LOG(
"Invalid follow aircraft position for " + plane->callsign);
1193 WARNING_LOG(
"Pos: " + pos2String(plane->positions[3]));
1198 if (plane->isOnGround)
1200 if (traffic->m_deltaCameraPosition.dyMeters < 10)
1202 traffic->m_deltaCameraPosition.dyMeters = 10;
1206 XPLMWorldToLocal(plane->positions[3].lat, plane->positions[3].lon,
1207 plane->positions[3].elevation * kFtToMeters, &lxMeters, &lyMeters, &lzMeters);
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;
1221 if (!isValidPosition(cameraPosition))
1224 WARNING_LOG(
"Pos: " + pos2String(cameraPosition));
1225 traffic->m_followAircraftDistanceMultiplier = 1.0;
1231 DEBUG_LOG(
"Camera: " + pos2String(cameraPosition));
1232 DEBUG_LOG(
"Follow aircraft " + traffic->m_followPlaneViewCallsign +
" " + modelName);
1238 int CTraffic::followAircraftKeySniffer(
char character, XPLMKeyFlags flags,
char virtualKey,
void *refcon)
1241 CTraffic *traffic =
static_cast<CTraffic *
>(refcon);
1242 if (!traffic || traffic->m_followPlaneViewCallsign.empty())
1248 if (virtualKey == XPLM_VK_SPACE)
1252 if (flags & xplm_DownFlag)
1254 traffic->m_isSpacePressed =
true;
1256 if (flags & xplm_UpFlag)
1258 traffic->m_isSpacePressed =
false;
1261 else if (virtualKey == XPLM_VK_DOWN && (flags & xplm_UpFlag))
1263 traffic->m_followAircraftDistanceMultiplier /= 1.2;
1265 else if (virtualKey == XPLM_VK_UP && (flags & xplm_UpFlag))
1267 traffic->m_followAircraftDistanceMultiplier *= 1.2;
1269 else if (virtualKey == XPLM_VK_ESCAPE && (flags & xplm_UpFlag))
1271 traffic->m_followAircraftDistanceMultiplier = 1.0;
1279 bool CTraffic::isPlusMinus180(
float v)
1285 if (v > 180.00001f || v < -180.00001f)
1292 bool CTraffic::isPlusMinus180(
double v)
1298 if (v > 180.00001 || v < -180.00001)
1305 float CTraffic::normalizeToPlusMinus180Deg(
float v)
1311 return static_cast<float>(normalizeToPlusMinus180Deg(
static_cast<double>(v)));
1314 double CTraffic::normalizeToPlusMinus180Deg(
double v)
1332 float CTraffic::normalizeToZero360Deg(
float v)
1338 return static_cast<float>(normalizeToZero360Deg(
static_cast<double>(v)));
1341 double CTraffic::normalizeToZero360Deg(
double v)
1359 bool CTraffic::isValidPosition(
const XPMPPlanePosition_t &position)
1361 if (!isPlusMinus180(position.lat))
1365 if (!isPlusMinus180(position.lon))
1369 if (!isPlusMinus180(position.pitch))
1373 if (!isPlusMinus180(position.roll))
1377 if (!isPlusMinus180(position.heading))
1381 if (position.elevation < -2000.0 || position.elevation > 100000.0)
1389 bool CTraffic::isValidPosition(
const XPLMCameraPosition_t *camPos)
1391 if (!isPlusMinus180(camPos->roll))
1395 if (!isPlusMinus180(camPos->pitch))
1399 if (!isPlusMinus180(camPos->heading))
1405 if (std::isnan(camPos->x))
1409 if (std::isnan(camPos->y))
1413 if (std::isnan(camPos->z))
1421 std::string CTraffic::pos2String(
const XPMPPlanePosition_t &position)
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);
1428 std::string CTraffic::pos2String(
const XPLMCameraPosition_t *camPos)
1432 return "camera position invalid";
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);
Plugin loaded by X-Plane which publishes a DBus service.
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.
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).
#define DEBUG_LOG(msg)
Logger convenience macros.
#define ERROR_LOG(msg)
Logger convenience macros.
#define INFO_LOG(msg)
Logger convenience macros.
#define WARNING_LOG(msg)
Logger convenience macros.