mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-03-22 23:05:36 +08:00
1239 lines
51 KiB
C++
1239 lines
51 KiB
C++
/* Copyright (C) 2013
|
|
* swift Project Community / Contributors
|
|
*
|
|
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
|
|
* directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated,
|
|
* or distributed except according to the terms contained in the LICENSE file.
|
|
*/
|
|
|
|
//! \cond PRIVATE
|
|
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include "plugin.h"
|
|
#include "traffic.h"
|
|
#include "utils.h"
|
|
#include "XPMPMultiplayer.h"
|
|
#include <XPLM/XPLMGraphics.h>
|
|
#include <XPLM/XPLMProcessing.h>
|
|
#include <XPLM/XPLMUtilities.h>
|
|
#include <XPLM/XPLMPlanes.h>
|
|
#include <XPLM/XPLMPlugin.h>
|
|
#include "blackmisc/simulation/xplane/qtfreeutils.h"
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <cmath>
|
|
#include <ctime>
|
|
#include <algorithm>
|
|
#include <limits>
|
|
|
|
// clazy:excludeall=reserve-candidates
|
|
|
|
float XPMP_PrepListHook(float, float, int, void *); // defined in xplanemp2/src/Renderer.cpp
|
|
|
|
using namespace BlackMisc::Simulation::XPlane::QtFreeUtils;
|
|
using namespace std::chrono_literals;
|
|
|
|
namespace XSwiftBus
|
|
{
|
|
CTraffic::Plane::Plane(void *id_, const std::string &callsign_, const std::string &aircraftIcao_, const std::string &airlineIcao_, const std::string &livery_, const std::string &modelName_)
|
|
: id(id_), callsign(callsign_), aircraftIcao(aircraftIcao_), airlineIcao(airlineIcao_), livery(livery_), modelName(modelName_)
|
|
{
|
|
std::memset(static_cast<void *>(&positions), 0, sizeof(positions));
|
|
for (auto &position : positions) { position.size = sizeof(position); }
|
|
std::memset(static_cast<void *>(&surveillance), 0, sizeof(surveillance));
|
|
surveillance.size = sizeof(surveillance);
|
|
std::memset(static_cast<void *>(&surfaces), 0, sizeof(surfaces));
|
|
surfaces.size = sizeof(surfaces);
|
|
surfaces.lights.bcnLights = surfaces.lights.landLights = surfaces.lights.navLights = surfaces.lights.strbLights = 1;
|
|
|
|
std::strncpy(label, callsign.c_str(), sizeof(label));
|
|
for (auto &position : positions) { memcpy(position.label, label, sizeof(label)); }
|
|
|
|
std::srand(static_cast<unsigned int>(std::time(nullptr)));
|
|
surfaces.lights.timeOffset = static_cast<uint16_t>(std::rand() % 0xffff);
|
|
}
|
|
|
|
CTraffic *CTraffic::s_instance = nullptr;
|
|
|
|
// *INDENT-OFF*
|
|
CTraffic::CTraffic(CSettingsProvider *settingsProvider) :
|
|
CDBusObject(settingsProvider),
|
|
m_followPlaneViewNextCommand("org/swift-project/xswiftbus/follow_next_plane", "Changes plane view to follow next plane in sequence", [this] { followNextPlane(); }),
|
|
m_followPlaneViewPreviousCommand("org/swift-project/xswiftbus/follow_previous_plane", "Changes plane view to follow previous plane in sequence", [this] { followPreviousPlane(); })
|
|
{
|
|
assert(!s_instance);
|
|
s_instance = this;
|
|
XPLMRegisterKeySniffer(followAircraftKeySniffer, 1, this);
|
|
|
|
// init labels
|
|
this->setDrawingLabels(this->getSettings().isDrawingLabels());
|
|
}
|
|
// *INDENT-ON*
|
|
|
|
CTraffic::~CTraffic()
|
|
{
|
|
cleanup();
|
|
assert(s_instance == this);
|
|
s_instance = nullptr;
|
|
}
|
|
|
|
void CTraffic::setPlaneViewMenu(const CMenu &planeViewSubMenu)
|
|
{
|
|
m_followPlaneViewSubMenu = planeViewSubMenu;
|
|
}
|
|
|
|
bool CTraffic::initialize()
|
|
{
|
|
if (! m_initialized)
|
|
{
|
|
initXPlanePath();
|
|
auto dir = g_xplanePath + "Resources" + g_sep + "plugins" + g_sep + "xswiftbus" + g_sep + "LegacyData" + g_sep;
|
|
std::string related = dir + "related.txt";
|
|
std::string doc8643 = dir + "Doc8643.txt";
|
|
|
|
updateConfiguration();
|
|
auto err = XPMPMultiplayerInit(&m_configuration, related.c_str(), doc8643.c_str());
|
|
if (err && *err) { cleanup(); }
|
|
else { m_initialized = true; }
|
|
}
|
|
|
|
return m_initialized;
|
|
}
|
|
|
|
bool CTraffic::acquireMultiplayerPlanes(std::string *owner)
|
|
{
|
|
if (! m_enabledMultiplayer)
|
|
{
|
|
auto err = XPMPMultiplayerEnable();
|
|
if (*err)
|
|
{
|
|
cleanup();
|
|
}
|
|
else
|
|
{
|
|
m_enabledMultiplayer = true;
|
|
|
|
// we will call xplanemp's callback from within our own callback
|
|
XPLMUnregisterFlightLoopCallback(XPMP_PrepListHook, nullptr);
|
|
}
|
|
}
|
|
|
|
int totalAircraft;
|
|
int activeAircraft;
|
|
XPLMPluginID controller;
|
|
XPLMCountAircraft(&totalAircraft, &activeAircraft, &controller);
|
|
|
|
char pluginName[256];
|
|
XPLMGetPluginInfo(controller, pluginName, nullptr, nullptr, nullptr);
|
|
*owner = std::string(pluginName);
|
|
return m_enabledMultiplayer;
|
|
}
|
|
|
|
void CTraffic::cleanup()
|
|
{
|
|
removeAllPlanes();
|
|
|
|
if (m_enabledMultiplayer)
|
|
{
|
|
m_enabledMultiplayer = false;
|
|
XPMPMultiplayerDisable();
|
|
}
|
|
|
|
if (m_initialized)
|
|
{
|
|
m_initialized = false;
|
|
XPMPMultiplayerCleanup();
|
|
}
|
|
}
|
|
|
|
void CTraffic::emitSimFrame()
|
|
{
|
|
if (m_emitSimFrame) { sendDBusSignal("simFrame"); }
|
|
m_emitSimFrame = !m_emitSimFrame;
|
|
}
|
|
|
|
void CTraffic::emitPlaneAdded(const std::string &callsign)
|
|
{
|
|
CDBusMessage signalPlaneAdded = CDBusMessage::createSignal(XSWIFTBUS_TRAFFIC_OBJECTPATH, XSWIFTBUS_TRAFFIC_INTERFACENAME, "remoteAircraftAdded");
|
|
signalPlaneAdded.beginArgumentWrite();
|
|
signalPlaneAdded.appendArgument(callsign);
|
|
sendDBusMessage(signalPlaneAdded);
|
|
}
|
|
|
|
void CTraffic::emitPlaneAddingFailed(const std::string &callsign)
|
|
{
|
|
CDBusMessage signalPlaneAddingFailed = CDBusMessage::createSignal(XSWIFTBUS_TRAFFIC_OBJECTPATH, XSWIFTBUS_TRAFFIC_INTERFACENAME, "remoteAircraftAddingFailed");
|
|
signalPlaneAddingFailed.beginArgumentWrite();
|
|
signalPlaneAddingFailed.appendArgument(callsign);
|
|
sendDBusMessage(signalPlaneAddingFailed);
|
|
}
|
|
|
|
void CTraffic::switchToFollowPlaneView(const std::string &callsign)
|
|
{
|
|
if (CTraffic::ownAircraftString() != callsign && !this->containsCallsign(callsign))
|
|
{
|
|
INFO_LOG("Cannot switch to follow " + callsign);
|
|
return;
|
|
}
|
|
|
|
m_followPlaneViewCallsign = callsign;
|
|
|
|
/* This is the hotkey callback. First we simulate a joystick press and
|
|
* release to put us in 'free view 1'. This guarantees that no panels
|
|
* are showing and we are an external view. */
|
|
XPLMCommandButtonPress(xplm_joy_v_fr1);
|
|
XPLMCommandButtonRelease(xplm_joy_v_fr1);
|
|
|
|
/* Now we control the camera until the view changes. */
|
|
INFO_LOG("Switch to follow " + callsign);
|
|
XPLMControlCamera(xplm_ControlCameraUntilViewChanges, CTraffic::orbitPlaneFunc, this);
|
|
}
|
|
|
|
void CTraffic::followNextPlane()
|
|
{
|
|
if (m_planesByCallsign.empty() || m_followPlaneViewCallsign.empty()) { return; }
|
|
auto callsignIt = std::find(m_followPlaneViewSequence.begin(), m_followPlaneViewSequence.end(), m_followPlaneViewCallsign);
|
|
|
|
// If we are not at the end, increase by one
|
|
if (callsignIt != m_followPlaneViewSequence.end()) { callsignIt++; }
|
|
// If we were already at the end or reached it now, start from the beginning
|
|
if (callsignIt == m_followPlaneViewSequence.end()) { callsignIt = m_followPlaneViewSequence.begin(); }
|
|
|
|
m_followPlaneViewCallsign = *callsignIt;
|
|
}
|
|
|
|
void CTraffic::followPreviousPlane()
|
|
{
|
|
if (m_planesByCallsign.empty() || m_followPlaneViewCallsign.empty()) { return; }
|
|
auto callsignIt = std::find(m_followPlaneViewSequence.rbegin(), m_followPlaneViewSequence.rend(), m_followPlaneViewCallsign);
|
|
|
|
// If we are not at the end, increase by one
|
|
if (callsignIt != m_followPlaneViewSequence.rend()) { callsignIt++; }
|
|
// If we were already at the end or reached it now, start from the beginning
|
|
if (callsignIt == m_followPlaneViewSequence.rend()) { callsignIt = m_followPlaneViewSequence.rbegin(); }
|
|
|
|
m_followPlaneViewCallsign = *callsignIt;
|
|
}
|
|
|
|
bool CTraffic::containsCallsign(const std::string &callsign) const
|
|
{
|
|
//! \fixme can be removed with C++20, as it then has a contains function
|
|
if (callsign.empty()) { return false; }
|
|
const auto planeIt = m_planesByCallsign.find(callsign);
|
|
if (planeIt == m_planesByCallsign.end()) { return false; }
|
|
return true;
|
|
}
|
|
|
|
// changed T709
|
|
// static int g_maxPlanes = 100;
|
|
// static float g_drawDistance = 50.0f;
|
|
|
|
void CTraffic::updateConfiguration()
|
|
{
|
|
m_configuration.maxFullAircraftRenderingDistance = static_cast<float>(s_instance->getSettings().getMaxDrawDistanceNM());
|
|
m_configuration.enableSurfaceClamping = true;
|
|
m_configuration.debug.modelMatching = false;
|
|
}
|
|
|
|
std::string CTraffic::loadPlanesPackage(const std::string &path)
|
|
{
|
|
initXPlanePath();
|
|
|
|
auto err = XPMPMultiplayerLoadCSLPackages(path.c_str());
|
|
if (*err) { return err; }
|
|
|
|
for (int i = 0, end = XPMPGetNumberOfInstalledModels(); i < end; ++i)
|
|
{
|
|
const char *mixedcase;
|
|
XPMPGetModelInfo(i, &mixedcase, nullptr, nullptr, nullptr);
|
|
std::string uppercase(mixedcase);
|
|
std::transform(uppercase.begin(), uppercase.end(), uppercase.begin(), [](char c) { return static_cast<char>(std::toupper(c)); });
|
|
m_modelStrings[uppercase] = mixedcase;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void CTraffic::setDefaultIcao(const std::string &defaultIcao)
|
|
{
|
|
XPMPSetDefaultPlaneICAO(defaultIcao.c_str());
|
|
}
|
|
|
|
void CTraffic::setDrawingLabels(bool drawing, int rgb)
|
|
{
|
|
CSettings s = this->getSettings();
|
|
if (s.isDrawingLabels() != drawing)
|
|
{
|
|
s.setDrawingLabels(drawing);
|
|
this->setSettings(s);
|
|
}
|
|
if (rgb >= 0)
|
|
{
|
|
m_labels.setColor((rgb & 0xff0000) >> 16, (rgb & 0x00ff00) >> 8, rgb & 0x0000ff);
|
|
}
|
|
if (drawing)
|
|
{
|
|
m_labels.show();
|
|
}
|
|
else
|
|
{
|
|
m_labels.hide();
|
|
}
|
|
}
|
|
|
|
bool CTraffic::isDrawingLabels() const
|
|
{
|
|
return m_labels.isVisible();
|
|
}
|
|
|
|
void CTraffic::setMaxPlanes(int planes)
|
|
{
|
|
CSettings s = this->getSettings();
|
|
if (s.setMaxPlanes(planes)) { this->setSettings(s); }
|
|
}
|
|
|
|
void CTraffic::setMaxDrawDistance(double nauticalMiles)
|
|
{
|
|
CSettings s = this->getSettings();
|
|
if (s.setMaxDrawDistanceNM(nauticalMiles)) { this->setSettings(s); }
|
|
}
|
|
|
|
void CTraffic::addPlane(const std::string &callsign, const std::string &modelName, const std::string &aircraftIcao, const std::string &airlineIcao, const std::string &livery)
|
|
{
|
|
auto planeIt = m_planesByCallsign.find(callsign);
|
|
if (planeIt != m_planesByCallsign.end()) { return; }
|
|
|
|
XPMPPlaneID id = nullptr;
|
|
if (modelName.empty() || m_modelStrings.count(modelName) == 0)
|
|
{
|
|
DEBUG_LOG("Model " + modelName + " is unknown, falling back to basic xpmp2 model matching");
|
|
id = XPMPCreatePlane(aircraftIcao.c_str(), airlineIcao.c_str(), livery.c_str());
|
|
}
|
|
else
|
|
{
|
|
id = XPMPCreatePlaneWithModelName(m_modelStrings[modelName].c_str(), aircraftIcao.c_str(), airlineIcao.c_str(), livery.c_str());
|
|
}
|
|
|
|
if (!id)
|
|
{
|
|
emitPlaneAddingFailed(callsign);
|
|
return;
|
|
}
|
|
|
|
Plane *plane = new Plane(id, callsign, aircraftIcao, airlineIcao, livery, modelName);
|
|
m_planesByCallsign[callsign] = plane;
|
|
m_planesById[id] = plane;
|
|
|
|
// Create view menu item
|
|
CMenuItem planeViewMenuItem = m_followPlaneViewSubMenu.item(callsign, [this, callsign] { switchToFollowPlaneView(callsign); });
|
|
m_followPlaneViewMenuItems[callsign] = planeViewMenuItem;
|
|
m_followPlaneViewSequence.push_back(callsign);
|
|
|
|
emitPlaneAdded(callsign);
|
|
}
|
|
|
|
void CTraffic::removePlane(const std::string &callsign)
|
|
{
|
|
auto menuItemIt = m_followPlaneViewMenuItems.find(callsign);
|
|
if (menuItemIt != m_followPlaneViewMenuItems.end())
|
|
{
|
|
m_followPlaneViewSubMenu.removeItem(menuItemIt->second);
|
|
m_followPlaneViewMenuItems.erase(menuItemIt);
|
|
}
|
|
|
|
auto planeIt = m_planesByCallsign.find(callsign);
|
|
if (planeIt == m_planesByCallsign.end()) { return; }
|
|
|
|
m_followPlaneViewSequence.erase(std::remove(m_followPlaneViewSequence.begin(), m_followPlaneViewSequence.end(), callsign),
|
|
m_followPlaneViewSequence.end());
|
|
|
|
Plane *plane = planeIt->second;
|
|
m_planesByCallsign.erase(callsign);
|
|
m_planesById.erase(plane->id);
|
|
XPMPDestroyPlane(plane->id);
|
|
delete plane;
|
|
}
|
|
|
|
void CTraffic::removeAllPlanes()
|
|
{
|
|
for (const auto &kv : m_planesByCallsign)
|
|
{
|
|
Plane *plane = kv.second;
|
|
assert(plane);
|
|
XPMPDestroyPlane(plane->id);
|
|
delete plane;
|
|
}
|
|
|
|
for (const auto &kv : m_followPlaneViewMenuItems)
|
|
{
|
|
CMenuItem item = kv.second;
|
|
m_followPlaneViewSubMenu.removeItem(item);
|
|
}
|
|
|
|
m_planesByCallsign.clear();
|
|
m_planesById.clear();
|
|
m_followPlaneViewMenuItems.clear();
|
|
m_followPlaneViewSequence.clear();
|
|
}
|
|
|
|
void CTraffic::setPlanesPositions(const std::vector<std::string> &callsigns, std::vector<double> latitudesDeg, std::vector<double> longitudesDeg, std::vector<double> altitudesFt,
|
|
std::vector<double> pitchesDeg, std::vector<double> rollsDeg, std::vector<double> headingsDeg, const std::vector<bool> &onGrounds)
|
|
{
|
|
const bool setOnGround = onGrounds.size() == callsigns.size();
|
|
for (size_t i = 0; i < callsigns.size(); i++)
|
|
{
|
|
auto planeIt = m_planesByCallsign.find(callsigns.at(i));
|
|
if (planeIt == m_planesByCallsign.end()) { continue; }
|
|
|
|
Plane *plane = planeIt->second;
|
|
if (!plane) { continue; }
|
|
plane->positions[2].lat = latitudesDeg.at(i);
|
|
plane->positions[2].lon = longitudesDeg.at(i);
|
|
plane->positions[2].elevation = altitudesFt.at(i);
|
|
plane->positions[2].pitch = static_cast<float>(pitchesDeg.at(i));
|
|
plane->positions[2].roll = static_cast<float>(rollsDeg.at(i));
|
|
plane->positions[2].heading = static_cast<float>(headingsDeg.at(i));
|
|
plane->positions[2].offsetScale = 1.0f;
|
|
plane->positions[2].clampToGround = true;
|
|
plane->positionTimes[2] = std::chrono::steady_clock::now();
|
|
|
|
// save 2 positions at 1-second intervals for use in interpolation
|
|
if (plane->positionTimes[2] - plane->positionTimes[1] > 1s)
|
|
{
|
|
plane->positionTimes[0] = plane->positionTimes[1];
|
|
plane->positionTimes[1] = plane->positionTimes[2];
|
|
std::memcpy(&plane->positions[0], &plane->positions[1], sizeof(plane->positions[0]));
|
|
std::memcpy(&plane->positions[1], &plane->positions[2], sizeof(plane->positions[0]));
|
|
}
|
|
|
|
if (setOnGround) { plane->isOnGround = onGrounds.at(i); }
|
|
}
|
|
}
|
|
|
|
void CTraffic::setPlanesSurfaces(const std::vector<std::string> &callsigns, const std::vector<double> &gears, const std::vector<double> &flaps, const std::vector<double> &spoilers,
|
|
const std::vector<double> &speedBrakes, const std::vector<double> &slats, const std::vector<double> &wingSweeps, const std::vector<double> &thrusts,
|
|
const std::vector<double> &elevators, const std::vector<double> &rudders, const std::vector<double> &ailerons,
|
|
const std::vector<bool> &landLights, const std::vector<bool> &taxiLights,
|
|
const std::vector<bool> &beaconLights, const std::vector<bool> &strobeLights, const std::vector<bool> &navLights, const std::vector<int> &lightPatterns)
|
|
{
|
|
const bool bundleTaxiLandingLights = this->getSettings().isBundlingTaxiAndLandingLights();
|
|
|
|
for (size_t i = 0; i < callsigns.size(); i++)
|
|
{
|
|
auto planeIt = m_planesByCallsign.find(callsigns.at(i));
|
|
if (planeIt == m_planesByCallsign.end()) { continue; }
|
|
|
|
Plane *plane = planeIt->second;
|
|
if (!plane) { continue; }
|
|
|
|
plane->hasSurfaces = true;
|
|
plane->targetGearPosition = static_cast<float>(gears.at(i));
|
|
plane->surfaces.flapRatio = static_cast<float>(flaps.at(i));
|
|
plane->surfaces.spoilerRatio = static_cast<float>(spoilers.at(i));
|
|
plane->surfaces.speedBrakeRatio = static_cast<float>(speedBrakes.at(i));
|
|
plane->surfaces.slatRatio = static_cast<float>(slats.at(i));
|
|
plane->surfaces.wingSweep = static_cast<float>(wingSweeps.at(i));
|
|
plane->surfaces.thrust = static_cast<float>(thrusts.at(i));
|
|
plane->surfaces.yokePitch = static_cast<float>(elevators.at(i));
|
|
plane->surfaces.yokeHeading = static_cast<float>(rudders.at(i));
|
|
plane->surfaces.yokeRoll = static_cast<float>(ailerons.at(i));
|
|
if (bundleTaxiLandingLights)
|
|
{
|
|
const bool on = landLights.at(i) || taxiLights.at(i);
|
|
plane->surfaces.lights.landLights = on;
|
|
plane->surfaces.lights.taxiLights = on;
|
|
}
|
|
else
|
|
{
|
|
plane->surfaces.lights.landLights = landLights.at(i);
|
|
plane->surfaces.lights.taxiLights = taxiLights.at(i);
|
|
}
|
|
plane->surfaces.lights.bcnLights = beaconLights.at(i);
|
|
plane->surfaces.lights.strbLights = strobeLights.at(i);
|
|
plane->surfaces.lights.navLights = navLights.at(i);
|
|
plane->surfaces.lights.flashPattern = static_cast<unsigned int>(lightPatterns.at(i));
|
|
}
|
|
}
|
|
|
|
void CTraffic::setPlanesTransponders(const std::vector<std::string> &callsigns, const std::vector<int> &codes, const std::vector<bool> &modeCs, const std::vector<bool> &idents)
|
|
{
|
|
for (size_t i = 0; i < callsigns.size(); i++)
|
|
{
|
|
auto planeIt = m_planesByCallsign.find(callsigns.at(i));
|
|
if (planeIt == m_planesByCallsign.end()) { continue; }
|
|
|
|
Plane *plane = planeIt->second;
|
|
if (!plane) { continue; }
|
|
|
|
plane->surveillance.code = codes.at(i);
|
|
if (idents.at(i)) { plane->surveillance.mode = xpmpTransponderMode_ModeC_Ident; }
|
|
else if (modeCs.at(i)) { plane->surveillance.mode = xpmpTransponderMode_ModeC; }
|
|
else { plane->surveillance.mode = xpmpTransponderMode_Standby; }
|
|
}
|
|
}
|
|
|
|
void CTraffic::getRemoteAircraftData(std::vector<std::string> &callsigns, std::vector<double> &latitudesDeg, std::vector<double> &longitudesDeg,
|
|
std::vector<double> &elevationsM, std::vector<bool> &waterFlags, std::vector<double> &verticalOffsets) const
|
|
{
|
|
if (callsigns.empty() || m_planesByCallsign.empty()) { return; }
|
|
|
|
const auto requestedCallsigns = callsigns;
|
|
callsigns.clear();
|
|
latitudesDeg.clear();
|
|
longitudesDeg.clear();
|
|
elevationsM.clear();
|
|
verticalOffsets.clear();
|
|
waterFlags.clear();
|
|
|
|
for (const auto &requestedCallsign : requestedCallsigns)
|
|
{
|
|
const auto planeIt = m_planesByCallsign.find(requestedCallsign);
|
|
if (planeIt == m_planesByCallsign.end()) { continue; }
|
|
|
|
const Plane *plane = planeIt->second;
|
|
assert(plane);
|
|
|
|
const double latDeg = plane->positions[2].lat;
|
|
const double lonDeg = plane->positions[2].lon;
|
|
double groundElevation = 0.0;
|
|
bool isWater = false;
|
|
if (getSettings().isTerrainProbeEnabled())
|
|
{
|
|
// we expect elevation in meters
|
|
groundElevation = plane->terrainProbe.getElevation(latDeg, lonDeg, plane->positions[2].elevation, requestedCallsign, isWater).front();
|
|
if (std::isnan(groundElevation)) { groundElevation = 0.0; }
|
|
}
|
|
|
|
callsigns.push_back(requestedCallsign);
|
|
latitudesDeg.push_back(latDeg);
|
|
longitudesDeg.push_back(lonDeg);
|
|
elevationsM.push_back(groundElevation);
|
|
waterFlags.push_back(isWater);
|
|
verticalOffsets.push_back(0); // xpmp2 adjusts the offset for us, so effectively always zero
|
|
}
|
|
}
|
|
|
|
std::array<double, 3> CTraffic::getElevationAtPosition(const std::string &callsign, double latitudeDeg, double longitudeDeg, double altitudeMeters, bool &o_isWater) const
|
|
{
|
|
if (!getSettings().isTerrainProbeEnabled()) { return {{ std::numeric_limits<double>::quiet_NaN(), latitudeDeg, longitudeDeg }}; }
|
|
|
|
const auto planeIt = m_planesByCallsign.find(callsign);
|
|
if (planeIt != m_planesByCallsign.end())
|
|
{
|
|
const Plane *plane = planeIt->second;
|
|
return plane->terrainProbe.getElevation(latitudeDeg, longitudeDeg, altitudeMeters, callsign, o_isWater);
|
|
}
|
|
else
|
|
{
|
|
return m_terrainProbe.getElevation(latitudeDeg, longitudeDeg, altitudeMeters, callsign + " (plane not found)", o_isWater);
|
|
}
|
|
}
|
|
|
|
void CTraffic::setFollowedAircraft(const std::string &callsign)
|
|
{
|
|
this->switchToFollowPlaneView(callsign);
|
|
}
|
|
|
|
void CTraffic::dbusDisconnectedHandler()
|
|
{
|
|
removeAllPlanes();
|
|
}
|
|
|
|
static const char *introspection_traffic =
|
|
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
|
|
#include "org.swift_project.xswiftbus.traffic.xml"
|
|
;
|
|
|
|
DBusHandlerResult CTraffic::dbusMessageHandler(const CDBusMessage &message_)
|
|
{
|
|
CDBusMessage message(message_);
|
|
const std::string sender = message.getSender();
|
|
const dbus_uint32_t serial = message.getSerial();
|
|
const bool wantsReply = message.wantsReply();
|
|
|
|
// Debug message if needed
|
|
// { const std::string d = "dbusMessageHandler: " + message.getMethodName(); INFO_LOG(d.c_str()); }
|
|
|
|
if (message.getInterfaceName() == DBUS_INTERFACE_INTROSPECTABLE)
|
|
{
|
|
if (message.getMethodName() == "Introspect")
|
|
{
|
|
sendDBusReply(sender, serial, introspection_traffic);
|
|
}
|
|
}
|
|
else if (message.getInterfaceName() == XSWIFTBUS_TRAFFIC_INTERFACENAME)
|
|
{
|
|
if (message.getMethodName() == "acquireMultiplayerPlanes")
|
|
{
|
|
queueDBusCall([ = ]()
|
|
{
|
|
std::string owner;
|
|
bool acquired = acquireMultiplayerPlanes(&owner);
|
|
CDBusMessage reply = CDBusMessage::createReply(sender, serial);
|
|
reply.beginArgumentWrite();
|
|
reply.appendArgument(acquired);
|
|
reply.appendArgument(owner);
|
|
sendDBusMessage(reply);
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "initialize")
|
|
{
|
|
sendDBusReply(sender, serial, initialize());
|
|
}
|
|
else if (message.getMethodName() == "cleanup")
|
|
{
|
|
maybeSendEmptyDBusReply(wantsReply, sender, serial);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
cleanup();
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "loadPlanesPackage")
|
|
{
|
|
std::string path;
|
|
message.beginArgumentRead();
|
|
message.getArgument(path);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
sendDBusReply(sender, serial, loadPlanesPackage(path));
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "setDefaultIcao")
|
|
{
|
|
std::string defaultIcao;
|
|
message.beginArgumentRead();
|
|
message.getArgument(defaultIcao);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
setDefaultIcao(defaultIcao);
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "setMaxPlanes")
|
|
{
|
|
maybeSendEmptyDBusReply(wantsReply, sender, serial);
|
|
int planes = 100;
|
|
message.beginArgumentRead();
|
|
message.getArgument(planes);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
setMaxPlanes(planes);
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "setMaxDrawDistance")
|
|
{
|
|
maybeSendEmptyDBusReply(wantsReply, sender, serial);
|
|
double nauticalMiles = 100;
|
|
message.beginArgumentRead();
|
|
message.getArgument(nauticalMiles);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
setMaxDrawDistance(nauticalMiles);
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "addPlane")
|
|
{
|
|
maybeSendEmptyDBusReply(wantsReply, sender, serial);
|
|
std::string callsign;
|
|
std::string modelName;
|
|
std::string aircraftIcao;
|
|
std::string airlineIcao;
|
|
std::string livery;
|
|
message.beginArgumentRead();
|
|
message.getArgument(callsign);
|
|
message.getArgument(modelName);
|
|
message.getArgument(aircraftIcao);
|
|
message.getArgument(airlineIcao);
|
|
message.getArgument(livery);
|
|
|
|
queueDBusCall([ = ]()
|
|
{
|
|
addPlane(callsign, modelName, aircraftIcao, airlineIcao, livery);
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "removePlane")
|
|
{
|
|
maybeSendEmptyDBusReply(wantsReply, sender, serial);
|
|
std::string callsign;
|
|
message.beginArgumentRead();
|
|
message.getArgument(callsign);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
removePlane(callsign);
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "removeAllPlanes")
|
|
{
|
|
maybeSendEmptyDBusReply(wantsReply, sender, serial);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
removeAllPlanes();
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "setPlanesPositions")
|
|
{
|
|
maybeSendEmptyDBusReply(wantsReply, sender, serial);
|
|
std::vector<std::string> callsigns;
|
|
std::vector<double> latitudes;
|
|
std::vector<double> longitudes;
|
|
std::vector<double> altitudes;
|
|
std::vector<double> pitches;
|
|
std::vector<double> rolls;
|
|
std::vector<double> headings;
|
|
std::vector<bool> onGrounds;
|
|
message.beginArgumentRead();
|
|
message.getArgument(callsigns);
|
|
message.getArgument(latitudes);
|
|
message.getArgument(longitudes);
|
|
message.getArgument(altitudes);
|
|
message.getArgument(pitches);
|
|
message.getArgument(rolls);
|
|
message.getArgument(headings);
|
|
message.getArgument(onGrounds);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
setPlanesPositions(callsigns, latitudes, longitudes, altitudes, pitches, rolls, headings, onGrounds);
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "setPlanesSurfaces")
|
|
{
|
|
maybeSendEmptyDBusReply(wantsReply, sender, serial);
|
|
std::vector<std::string> callsigns;
|
|
std::vector<double> gears;
|
|
std::vector<double> flaps;
|
|
std::vector<double> spoilers;
|
|
std::vector<double> speedBrakes;
|
|
std::vector<double> slats;
|
|
std::vector<double> wingSweeps;
|
|
std::vector<double> thrusts;
|
|
std::vector<double> elevators;
|
|
std::vector<double> rudders;
|
|
std::vector<double> ailerons;
|
|
std::vector<bool> landLights;
|
|
std::vector<bool> taxiLights;
|
|
std::vector<bool> beaconLights;
|
|
std::vector<bool> strobeLights;
|
|
std::vector<bool> navLights;
|
|
std::vector<int> lightPatterns;
|
|
message.beginArgumentRead();
|
|
message.getArgument(callsigns);
|
|
message.getArgument(gears);
|
|
message.getArgument(flaps);
|
|
message.getArgument(spoilers);
|
|
message.getArgument(speedBrakes);
|
|
message.getArgument(slats);
|
|
message.getArgument(wingSweeps);
|
|
message.getArgument(thrusts);
|
|
message.getArgument(elevators);
|
|
message.getArgument(rudders);
|
|
message.getArgument(ailerons);
|
|
message.getArgument(landLights);
|
|
message.getArgument(taxiLights);
|
|
message.getArgument(beaconLights);
|
|
message.getArgument(strobeLights);
|
|
message.getArgument(navLights);
|
|
message.getArgument(lightPatterns);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
setPlanesSurfaces(callsigns, gears, flaps, spoilers, speedBrakes, slats, wingSweeps, thrusts, elevators,
|
|
rudders, ailerons, landLights, taxiLights, beaconLights, strobeLights, navLights, lightPatterns);
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "setPlanesTransponders")
|
|
{
|
|
maybeSendEmptyDBusReply(wantsReply, sender, serial);
|
|
std::vector<std::string> callsigns;
|
|
std::vector<int> codes;
|
|
std::vector<bool> modeCs;
|
|
std::vector<bool> idents;
|
|
message.beginArgumentRead();
|
|
message.getArgument(callsigns);
|
|
message.getArgument(codes);
|
|
message.getArgument(modeCs);
|
|
message.getArgument(idents);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
setPlanesTransponders(callsigns, codes, modeCs, idents);
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "getRemoteAircraftData")
|
|
{
|
|
std::vector<std::string> requestedCallsigns;
|
|
message.beginArgumentRead();
|
|
message.getArgument(requestedCallsigns);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
std::vector<std::string> callsigns = requestedCallsigns;
|
|
std::vector<double> latitudesDeg;
|
|
std::vector<double> longitudesDeg;
|
|
std::vector<double> elevationsM;
|
|
std::vector<bool> waterFlags;
|
|
std::vector<double> verticalOffsets;
|
|
getRemoteAircraftData(callsigns, latitudesDeg, longitudesDeg, elevationsM, waterFlags, verticalOffsets);
|
|
CDBusMessage reply = CDBusMessage::createReply(sender, serial);
|
|
reply.beginArgumentWrite();
|
|
reply.appendArgument(callsigns);
|
|
reply.appendArgument(latitudesDeg);
|
|
reply.appendArgument(longitudesDeg);
|
|
reply.appendArgument(elevationsM);
|
|
reply.appendArgument(waterFlags);
|
|
reply.appendArgument(verticalOffsets);
|
|
sendDBusMessage(reply);
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "getElevationAtPosition")
|
|
{
|
|
std::string callsign;
|
|
double latitudeDeg;
|
|
double longitudeDeg;
|
|
double altitudeMeters;
|
|
message.beginArgumentRead();
|
|
message.getArgument(callsign);
|
|
message.getArgument(latitudeDeg);
|
|
message.getArgument(longitudeDeg);
|
|
message.getArgument(altitudeMeters);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
bool isWater = false;
|
|
const auto elevation = getElevationAtPosition(callsign, latitudeDeg, longitudeDeg, altitudeMeters, isWater);
|
|
CDBusMessage reply = CDBusMessage::createReply(sender, serial);
|
|
reply.beginArgumentWrite();
|
|
reply.appendArgument(callsign);
|
|
reply.appendArgument(elevation[0]);
|
|
reply.appendArgument(elevation[1]);
|
|
reply.appendArgument(elevation[2]);
|
|
reply.appendArgument(isWater);
|
|
sendDBusMessage(reply);
|
|
});
|
|
}
|
|
else if (message.getMethodName() == "setFollowedAircraft")
|
|
{
|
|
maybeSendEmptyDBusReply(wantsReply, sender, serial);
|
|
std::string callsign;
|
|
message.beginArgumentRead();
|
|
message.getArgument(callsign);
|
|
queueDBusCall([ = ]()
|
|
{
|
|
setFollowedAircraft(callsign);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// Unknown message. Tell DBus that we cannot handle it
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
}
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
int CTraffic::process()
|
|
{
|
|
invokeQueuedDBusCalls();
|
|
doPlaneUpdates();
|
|
setDrawingLabels(getSettings().isDrawingLabels(), getSettings().getLabelColor());
|
|
emitSimFrame();
|
|
m_countFrame++;
|
|
return 1;
|
|
}
|
|
|
|
//! memcmp function which ignores the header ("size" member) and compares only the payload (the rest of the struct)
|
|
template <typename T>
|
|
int memcmpPayload(T *dst, T *src)
|
|
{
|
|
return std::memcmp(reinterpret_cast<char *>(dst) + sizeof(dst->size),
|
|
reinterpret_cast<char *>(src) + sizeof(src->size),
|
|
sizeof(*dst) - sizeof(dst->size));
|
|
}
|
|
|
|
void CTraffic::doPlaneUpdates()
|
|
{
|
|
m_updates.clear();
|
|
for (const auto &pair : m_planesById)
|
|
{
|
|
Plane *plane = pair.second;
|
|
interpolatePosition(plane);
|
|
interpolateGear(plane);
|
|
m_updates.push_back({ plane->id, &plane->positions[3], &plane->surfaces, &plane->surveillance });
|
|
}
|
|
XPMPUpdatePlanes(m_updates.data(), sizeof(XPMPUpdate_t), m_updates.size());
|
|
|
|
XPMP_PrepListHook(0, 0, 0, nullptr);
|
|
}
|
|
|
|
void CTraffic::interpolatePosition(Plane *plane)
|
|
{
|
|
std::memcpy(&plane->positions[3], &plane->positions[2], sizeof(plane->positions[2]));
|
|
|
|
const auto now = std::chrono::steady_clock::now();
|
|
const auto t1 = plane->positionTimes[2] - plane->positionTimes[0];
|
|
const auto t2 = now - plane->positionTimes[0];
|
|
|
|
// This interpolation is only intended to smooth over
|
|
// small errors. Give up if the error is too large.
|
|
if (t1 > 3s || t2 > 3s) { return; }
|
|
|
|
const double dLat = plane->positions[2].lat - plane->positions[0].lat;
|
|
const double dLon = plane->positions[2].lon - plane->positions[0].lon;
|
|
const double dAlt = plane->positions[2].elevation - plane->positions[0].elevation;
|
|
|
|
plane->positions[3].lat = plane->positions[0].lat + dLat * t2 / t1;
|
|
plane->positions[3].lon = plane->positions[0].lon + dLon * t2 / t1;
|
|
plane->positions[3].elevation = plane->positions[0].elevation + dAlt * t2 / t1;
|
|
}
|
|
|
|
void CTraffic::interpolateGear(Plane *plane)
|
|
{
|
|
const auto now = std::chrono::steady_clock::now();
|
|
static const float epsilon = std::numeric_limits<float>::epsilon();
|
|
const float f = plane->surfaces.gearPosition - plane->targetGearPosition;
|
|
if (std::abs(f) > epsilon)
|
|
{
|
|
constexpr float gearMoveTimeMs = 5000;
|
|
const auto gearPositionDiffRemaining = plane->targetGearPosition - plane->surfaces.gearPosition;
|
|
|
|
const auto diffMs = std::chrono::duration_cast<std::chrono::milliseconds>(now - plane->prevSurfacesLerpTime);
|
|
const auto gearPositionDiffThisFrame = (diffMs.count()) / gearMoveTimeMs;
|
|
plane->surfaces.gearPosition += std::copysign(gearPositionDiffThisFrame, gearPositionDiffRemaining);
|
|
plane->surfaces.gearPosition = std::max(0.0f, std::min(plane->surfaces.gearPosition, 1.0f));
|
|
}
|
|
plane->prevSurfacesLerpTime = now;
|
|
}
|
|
|
|
void CTraffic::Labels::draw()
|
|
{
|
|
static const double maxRangeM = 10000;
|
|
static const double metersPerFt = 0.3048;
|
|
std::array<float, 16> worldMat = m_worldMat.getAll();
|
|
std::array<float, 16> projMat = m_projMat.getAll();
|
|
double windowWidth = static_cast<double>(m_windowWidth.get());
|
|
double windowHeight = static_cast<double>(m_windowHeight.get());
|
|
XPLMCameraPosition_t camPos {};
|
|
XPLMReadCameraPosition(&camPos);
|
|
|
|
for (const auto &pair : m_traffic->m_planesById)
|
|
{
|
|
char *text = const_cast<char *>(pair.second->label);
|
|
const XPMPPlanePosition_t &planePos = pair.second->positions[3];
|
|
|
|
double worldPos[4]{ 0, 0, 0, 1 };
|
|
double localPos[4]{};
|
|
double windowPos[4]{};
|
|
XPLMWorldToLocal(planePos.lat, planePos.lon, planePos.elevation * metersPerFt, &worldPos[0], &worldPos[1], &worldPos[2]);
|
|
if (distanceSquared(worldPos) > maxRangeM * maxRangeM) { continue; }
|
|
matrixMultVec(localPos, worldMat.data(), worldPos);
|
|
matrixMultVec(windowPos, projMat.data(), localPos);
|
|
|
|
windowPos[3] = 1.0 / windowPos[3];
|
|
windowPos[0] *= windowPos[3];
|
|
windowPos[1] *= windowPos[3];
|
|
windowPos[2] *= windowPos[3];
|
|
|
|
if (windowPos[2] < 0.0 || windowPos[2] > 1.0)
|
|
{
|
|
continue; // plane is behind camera
|
|
}
|
|
XPLMDrawString(m_color.data(),
|
|
static_cast<int>(std::lround(windowWidth * (windowPos[0] * 0.5 + 0.5))),
|
|
static_cast<int>(std::lround(windowHeight * (windowPos[1] * 0.5 + 0.5))),
|
|
text, nullptr, xplmFont_Basic);
|
|
}
|
|
}
|
|
|
|
void CTraffic::Labels::matrixMultVec(double out[4], const float m[16], const double v[4])
|
|
{
|
|
out[0] = v[0] * m[0] + v[1] * m[4] + v[2] * m[8] + v[3] * m[12];
|
|
out[1] = v[0] * m[1] + v[1] * m[5] + v[2] * m[9] + v[3] * m[13];
|
|
out[2] = v[0] * m[2] + v[1] * m[6] + v[2] * m[10] + v[3] * m[14];
|
|
out[3] = v[0] * m[3] + v[1] * m[7] + v[2] * m[11] + v[3] * m[15];
|
|
}
|
|
|
|
double CTraffic::Labels::distanceSquared(const double pos[3]) const
|
|
{
|
|
const double dx = m_viewX.get() - pos[0];
|
|
const double dy = m_viewY.get() - pos[1];
|
|
const double dz = m_viewZ.get() - pos[2];
|
|
return dx * dx + dy * dy + dz * dz;
|
|
}
|
|
|
|
int CTraffic::orbitPlaneFunc(XPLMCameraPosition_t *cameraPosition, int isLosingControl, void *refcon)
|
|
{
|
|
constexpr bool DEBUG = false;
|
|
// DEBUG_LOG_C("Follow aircraft entry", DEBUG);
|
|
if (isLosingControl == 1)
|
|
{
|
|
// do NOT use refcon here, might be invalid
|
|
INFO_LOG("Loosing camera control");
|
|
return 0;
|
|
}
|
|
|
|
auto *traffic = static_cast<CTraffic *>(refcon);
|
|
if (!traffic)
|
|
{
|
|
ERROR_LOG("Cannot convert CTraffic object");
|
|
return 0;
|
|
}
|
|
|
|
// nothing we can do
|
|
if (!cameraPosition)
|
|
{
|
|
ERROR_LOG("No camera object");
|
|
traffic->m_followPlaneViewCallsign.clear();
|
|
return 0;
|
|
}
|
|
|
|
// Ideally we would like to test against right mouse button, but X-Plane SDK does not
|
|
// allow that.
|
|
if (!traffic->m_deltaCameraPosition.isInitialized || traffic->m_isSpacePressed)
|
|
{
|
|
int w = 0, h = 0, x = 0, y = 0;
|
|
// First get the screen size and mouse location. We will use this to decide
|
|
// what part of the orbit we are in. The mouse will move us up-down and around.
|
|
// fixme: In a future update, change the orbit only while right mouse button is pressed.
|
|
XPLMGetScreenSize(&w, &h);
|
|
XPLMGetMouseLocation(&x, &y);
|
|
// cppcheck-suppress knownConditionTrueFalse
|
|
if (DEBUG) { DEBUG_LOG("Follow aircraft coordinates w,h,x,y: " + std::to_string(w) + " " + std::to_string(h) + " " + std::to_string(x) + " " + std::to_string(y)); }
|
|
if (traffic->m_lastMouseX == x && traffic->m_lastMouseY == y && traffic->m_lastMouseX >= 0 && traffic->m_lastMouseY >= 0)
|
|
{
|
|
// mouse NOT moving, we lost focus or we do NOT move anymore
|
|
// to avoid issues we reset the space key, see https://discordapp.com/channels/539048679160676382/539925070550794240/614162134644949002
|
|
traffic->m_isSpacePressed = false;
|
|
}
|
|
|
|
// avoid follow aircraft in too small windows
|
|
// int cannot be NaN
|
|
if (w < 100 || h < 100)
|
|
{
|
|
WARNING_LOG("Screen w/h too small " + std::to_string(w) + "/" + std::to_string(h));
|
|
return 0;
|
|
}
|
|
|
|
// the 1.25 factor allows to turn around completely
|
|
traffic->m_deltaCameraPosition.headingDeg = normalizeToZero360Deg(1.25 * 360.0 * static_cast<double>(x) / static_cast<double>(w)); // range 0-360
|
|
double usedCameraPitchDeg = 60.0 - (60.0 * 2.0 * static_cast<double>(y) / static_cast<double>(h)); // range +-
|
|
|
|
// make sure we can use it with tan in range +-90 degrees and the result of tan not getting too high
|
|
// we limit to +-85deg, tan 45deg: 1 | tan 60deg: 1.73 | tan 85deg: 11.4
|
|
if (usedCameraPitchDeg >= 85.0) { usedCameraPitchDeg = 85.0; }
|
|
else if (usedCameraPitchDeg <= -85.0) { usedCameraPitchDeg = -85.0; }
|
|
traffic->m_deltaCameraPosition.pitchDeg = usedCameraPitchDeg;
|
|
|
|
// Now calculate where the camera should be positioned to be x
|
|
// meters from the plane and pointing at the plane at the pitch and
|
|
// heading we wanted above.
|
|
const double distanceMeterM = traffic->m_followAircraftDistanceMultiplier * static_cast<double>(std::max(10, traffic->getSettings().getFollowAircraftDistanceM()));
|
|
static const double PI = std::acos(-1);
|
|
traffic->m_deltaCameraPosition.dxMeters = -distanceMeterM * sin(traffic->m_deltaCameraPosition.headingDeg * PI / 180.0);
|
|
traffic->m_deltaCameraPosition.dzMeters = distanceMeterM * cos(traffic->m_deltaCameraPosition.headingDeg * PI / 180.0);
|
|
traffic->m_deltaCameraPosition.dyMeters = -distanceMeterM * tan(traffic->m_deltaCameraPosition.pitchDeg * PI / 180.0);
|
|
|
|
traffic->m_deltaCameraPosition.isInitialized = true;
|
|
}
|
|
|
|
double lxMeters = 0, lyMeters = 0, lzMeters = 0; // normally init not needed, just to avoid any issues
|
|
static const double kFtToMeters = 0.3048;
|
|
|
|
std::string modelName = "unknown";
|
|
if (traffic->m_followPlaneViewCallsign == CTraffic::ownAircraftString())
|
|
{
|
|
lxMeters = traffic->m_ownAircraftPositionX.get();
|
|
lyMeters = traffic->m_ownAircraftPositionY.get();
|
|
lzMeters = traffic->m_ownAircraftPositionZ.get();
|
|
modelName = CTraffic::ownAircraftString();
|
|
}
|
|
else
|
|
{
|
|
if (traffic->m_planesByCallsign.empty())
|
|
{
|
|
INFO_LOG("Follow aircraft, no planes to follow");
|
|
traffic->m_followPlaneViewCallsign.clear();
|
|
return 0;
|
|
}
|
|
|
|
if (traffic->m_followPlaneViewCallsign.empty())
|
|
{
|
|
INFO_LOG("Follow aircraft, no callsign to follow");
|
|
traffic->m_followPlaneViewCallsign.clear();
|
|
traffic->m_followAircraftDistanceMultiplier = 1.0;
|
|
return 0;
|
|
}
|
|
|
|
const auto planeIt = traffic->m_planesByCallsign.find(traffic->m_followPlaneViewCallsign);
|
|
if (planeIt == traffic->m_planesByCallsign.end())
|
|
{
|
|
INFO_LOG("Follow aircraft, no plane found for callsign " + traffic->m_followPlaneViewCallsign);
|
|
traffic->m_followPlaneViewCallsign.clear();
|
|
traffic->m_followAircraftDistanceMultiplier = 1.0;
|
|
return 0;
|
|
}
|
|
|
|
const Plane *plane = planeIt->second;
|
|
if (!plane)
|
|
{
|
|
ERROR_LOG("Follow aircraft, no plane from iterator for callsign " + traffic->m_followPlaneViewCallsign);
|
|
traffic->m_followPlaneViewCallsign.clear();
|
|
traffic->m_followAircraftDistanceMultiplier = 1.0;
|
|
return 0;
|
|
}
|
|
|
|
modelName = plane->modelName;
|
|
if (!isValidPosition(plane->positions[3]))
|
|
{
|
|
WARNING_LOG("Invalid follow aircraft position for " + plane->callsign);
|
|
WARNING_LOG("Pos: " + pos2String(plane->positions[3]));
|
|
return 0;
|
|
}
|
|
|
|
// avoid underflow of camera into ground
|
|
if (plane->isOnGround)
|
|
{
|
|
if (traffic->m_deltaCameraPosition.dyMeters < 10) { traffic->m_deltaCameraPosition.dyMeters = 10; }
|
|
}
|
|
|
|
XPLMWorldToLocal(plane->positions[3].lat, plane->positions[3].lon, plane->positions[3].elevation * kFtToMeters, &lxMeters, &lyMeters, &lzMeters);
|
|
}
|
|
|
|
// Fill out the camera position info.
|
|
cameraPosition->x = static_cast<float>(lxMeters + traffic->m_deltaCameraPosition.dxMeters);
|
|
cameraPosition->y = static_cast<float>(lyMeters + traffic->m_deltaCameraPosition.dyMeters);
|
|
cameraPosition->z = static_cast<float>(lzMeters + traffic->m_deltaCameraPosition.dzMeters);
|
|
cameraPosition->pitch = CTraffic::normalizeToPlusMinus180Deg(static_cast<float>(traffic->m_deltaCameraPosition.pitchDeg));
|
|
cameraPosition->heading = CTraffic::normalizeToPlusMinus180Deg(static_cast<float>(traffic->m_deltaCameraPosition.headingDeg));
|
|
cameraPosition->roll = 0.0;
|
|
cameraPosition->zoom = 1.0;
|
|
|
|
if (!isValidPosition(cameraPosition))
|
|
{
|
|
WARNING_LOG("Invalid camera aircraft position");
|
|
WARNING_LOG("Pos: " + pos2String(cameraPosition));
|
|
traffic->m_followAircraftDistanceMultiplier = 1.0;
|
|
return 0;
|
|
}
|
|
|
|
// cppcheck-suppress knownConditionTrueFalse
|
|
if (DEBUG)
|
|
{
|
|
DEBUG_LOG("Camera: " + pos2String(cameraPosition));
|
|
DEBUG_LOG("Follow aircraft " + traffic->m_followPlaneViewCallsign + " " + modelName);
|
|
}
|
|
// Return 1 to indicate we want to keep controlling the camera.
|
|
return 1;
|
|
}
|
|
|
|
int CTraffic::followAircraftKeySniffer(char character, XPLMKeyFlags flags, char virtualKey, void *refcon)
|
|
{
|
|
(void) character;
|
|
CTraffic *traffic = static_cast<CTraffic *>(refcon);
|
|
if (!traffic || traffic->m_followPlaneViewCallsign.empty()) { return 1; } // totally ignore if nothing is being followed
|
|
|
|
// We are only interested in Space key
|
|
if (virtualKey == XPLM_VK_SPACE)
|
|
{
|
|
// if XPlane looses focus it can happen that key down is NOT reset
|
|
// for the camera we use the init flag instead, so it is only run once
|
|
if (flags & xplm_DownFlag) { traffic->m_isSpacePressed = true; }
|
|
if (flags & xplm_UpFlag) { traffic->m_isSpacePressed = false; }
|
|
}
|
|
else if (virtualKey == XPLM_VK_DOWN && (flags & xplm_UpFlag)) { traffic->m_followAircraftDistanceMultiplier /= 1.2; }
|
|
else if (virtualKey == XPLM_VK_UP && (flags & xplm_UpFlag)) { traffic->m_followAircraftDistanceMultiplier *= 1.2; }
|
|
else if (virtualKey == XPLM_VK_ESCAPE && (flags & xplm_UpFlag)) { traffic->m_followAircraftDistanceMultiplier = 1.0; }
|
|
|
|
/* Return 1 to pass the keystroke to plugin windows and X-Plane.
|
|
* Returning 0 would consume the keystroke. */
|
|
return 1;
|
|
}
|
|
|
|
bool CTraffic::isPlusMinus180(float v)
|
|
{
|
|
if (std::isnan(v)) { return false; }
|
|
if (v > 180.00001f || v < -180.00001f) { return false; }
|
|
return true;
|
|
}
|
|
|
|
bool CTraffic::isPlusMinus180(double v)
|
|
{
|
|
if (std::isnan(v)) { return false; }
|
|
if (v > 180.00001 || v < -180.00001) { return false; }
|
|
return true;
|
|
}
|
|
|
|
float CTraffic::normalizeToPlusMinus180Deg(float v)
|
|
{
|
|
if (std::isnan(v)) { return 0.0f; }
|
|
return static_cast<float>(normalizeToPlusMinus180Deg(static_cast<double>(v)));
|
|
}
|
|
|
|
double CTraffic::normalizeToPlusMinus180Deg(double v)
|
|
{
|
|
if (std::isnan(v)) { return 0.0; }
|
|
const double n = normalizeValue(v, -180.0, 180.0);
|
|
if (n <= -180.0) { return 180.0; }
|
|
if (n > 180.0) { return 180.0; }
|
|
return n;
|
|
}
|
|
|
|
float CTraffic::normalizeToZero360Deg(float v)
|
|
{
|
|
if (std::isnan(v)) { return 0.0f; }
|
|
return static_cast<float>(normalizeToZero360Deg(static_cast<double>(v)));
|
|
|
|
}
|
|
|
|
double CTraffic::normalizeToZero360Deg(double v)
|
|
{
|
|
if (std::isnan(v)) { return 0.0; }
|
|
const double n = normalizeValue(v, 0, 360.0);
|
|
if (n >= 360.0) { return 0.0;}
|
|
if (n < 0.0) { return 0.0;}
|
|
return n;
|
|
}
|
|
|
|
bool CTraffic::isValidPosition(const XPMPPlanePosition_t &position)
|
|
{
|
|
if (!isPlusMinus180(position.lat)) { return false; }
|
|
if (!isPlusMinus180(position.lon)) { return false; }
|
|
if (!isPlusMinus180(position.pitch)) { return false; }
|
|
if (!isPlusMinus180(position.roll)) { return false; }
|
|
if (!isPlusMinus180(position.heading)) { return false; }
|
|
if (position.elevation < -2000.0 || position.elevation > 100000.0) { return false; }
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CTraffic::isValidPosition(const XPLMCameraPosition_t *camPos)
|
|
{
|
|
if (!isPlusMinus180(camPos->roll)) { return false; }
|
|
if (!isPlusMinus180(camPos->pitch)) { return false; }
|
|
if (!isPlusMinus180(camPos->heading)) { return false; }
|
|
|
|
// x, y, z not in -1..1 range
|
|
if (std::isnan(camPos->x)) { return false; }
|
|
if (std::isnan(camPos->y)) { return false; }
|
|
if (std::isnan(camPos->z)) { return false; }
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string CTraffic::pos2String(const XPMPPlanePosition_t &position)
|
|
{
|
|
return "lat, lon, el: " +
|
|
std::to_string(position.lat) + "/" + std::to_string(position.lon) + "/" + std::to_string(position.elevation) +
|
|
" prh: " +
|
|
std::to_string(position.pitch) + "/" + std::to_string(position.roll) + "/" + std::to_string(position.heading);
|
|
}
|
|
|
|
std::string CTraffic::pos2String(const XPLMCameraPosition_t *camPos)
|
|
{
|
|
if (!camPos) { return "camera position invalid"; }
|
|
return "x, y, z: " +
|
|
std::to_string(camPos->x) + "/" + std::to_string(camPos->y) + "/" + std::to_string(camPos->z) +
|
|
" zoom: " +
|
|
std::to_string(camPos->zoom) +
|
|
" prh: " +
|
|
std::to_string(camPos->pitch) + "/" + std::to_string(camPos->roll) + "/" + std::to_string(camPos->heading);
|
|
}
|
|
} // ns
|
|
|
|
//! \endcond
|