From 4db0b8331058b4a4bc7bc50fa82f76df0bda5043 Mon Sep 17 00:00:00 2001 From: Roland Winklmeier Date: Sun, 18 Feb 2018 12:42:54 +0100 Subject: [PATCH] Move X-Plane interpolation into driver ref T259 --- .../simulator/xplane/simulatorxplane.cpp | 446 +++++++++++++++--- .../simulator/xplane/simulatorxplane.h | 24 + .../simulator/xplane/xplanempaircraft.cpp | 124 +++++ .../simulator/xplane/xplanempaircraft.h | 136 ++++++ .../xplane/xswiftbustrafficproxy.cpp | 12 + .../simulator/xplane/xswiftbustrafficproxy.h | 11 + src/xswiftbus/traffic.cpp | 92 +++- src/xswiftbus/traffic.h | 24 +- 8 files changed, 792 insertions(+), 77 deletions(-) create mode 100644 src/plugins/simulator/xplane/xplanempaircraft.cpp create mode 100644 src/plugins/simulator/xplane/xplanempaircraft.h diff --git a/src/plugins/simulator/xplane/simulatorxplane.cpp b/src/plugins/simulator/xplane/simulatorxplane.cpp index ca0d6d2b5..454ed5731 100644 --- a/src/plugins/simulator/xplane/simulatorxplane.cpp +++ b/src/plugins/simulator/xplane/simulatorxplane.cpp @@ -79,7 +79,6 @@ namespace BlackSimPlugin { namespace XPlane { - CSimulatorXPlane::CSimulatorXPlane(const BlackMisc::Simulation::CSimulatorPluginInfo &info, IOwnAircraftProvider *ownAircraftProvider, IRemoteAircraftProvider *remoteAircraftProvider, @@ -247,6 +246,7 @@ namespace BlackSimPlugin connect(m_service, &CXSwiftBusServiceProxy::aircraftModelChanged, this, &CSimulatorXPlane::ps_emitOwnAircraftModelChanged); connect(m_service, &CXSwiftBusServiceProxy::airportsInRangeUpdated, this, &CSimulatorXPlane::ps_setAirportsInRange); m_service->updateAirportsInRange(); + connect(m_traffic, &CXSwiftBusTrafficProxy::simFrame, this, &CSimulatorXPlane::updateRemoteAircraft); if (m_watcher) { m_watcher->setConnection(m_conn); } loadCslPackages(); emitSimulatorCombinedStatus(); @@ -375,8 +375,25 @@ namespace BlackSimPlugin { if (!isConnected()) { return false; } - m_traffic->setInterpolatorMode(callsign.asString(), mode == CInterpolatorMulti::ModeSpline); - return true; + if(c_driverInterpolation) + { + if (mode == CInterpolatorMulti::ModeUnknown) { return false; } + if (callsign.isEmpty()) + { + const int c = m_xplaneAircrafts.setInterpolatorModes(mode); + return c > 0; + } + else + { + if (!m_xplaneAircrafts.contains(callsign)) { return false; } + return m_xplaneAircrafts[callsign].setInterpolatorMode(mode); + } + } + else + { + m_traffic->setInterpolatorMode(callsign.asString(), mode == CInterpolatorMulti::ModeSpline); + return true; + } } QDBusConnection CSimulatorXPlane::connectionFromString(const QString &str) @@ -398,8 +415,15 @@ namespace BlackSimPlugin bool CSimulatorXPlane::isPhysicallyRenderedAircraft(const CCallsign &callsign) const { - //! \todo XP implement isRenderedAircraft correctly. This is a workaround, but not telling me if a callsign is really(!) visible in simulator - return getAircraftInRangeForCallsign(callsign).isRendered(); + if (c_driverInterpolation) + { + return m_xplaneAircrafts.contains(callsign); + } + else + { + //! \todo XP implement isRenderedAircraft correctly. This is a workaround, but not telling me if a callsign is really(!) visible in simulator + return getAircraftInRangeForCallsign(callsign).isRendered(); + } } bool CSimulatorXPlane::updateOwnSimulatorCockpit(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, const CIdentifier &originator) @@ -487,90 +511,174 @@ namespace BlackSimPlugin bool CSimulatorXPlane::physicallyAddRemoteAircraft(const CSimulatedAircraft &newRemoteAircraft) { Q_ASSERT(isConnected()); - //! \todo XPlane driver check if already exists, how? - //! \todo XPlane driver set correct return value + if (c_driverInterpolation) + { + // entry checks + Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "thread"); + Q_ASSERT_X(!newRemoteAircraft.getCallsign().isEmpty(), Q_FUNC_INFO, "empty callsign"); + Q_ASSERT_X(newRemoteAircraft.hasModelString(), Q_FUNC_INFO, "missing model string"); - CAircraftModel aircraftModel = newRemoteAircraft.getModel(); - QString livery = aircraftModel.getLivery().getCombinedCode(); //! \todo livery resolution for XP - m_traffic->addPlane(newRemoteAircraft.getCallsign().asString(), aircraftModel.getModelString(), - newRemoteAircraft.getAircraftIcaoCode().getDesignator(), - newRemoteAircraft.getAirlineIcaoCode().getDesignator(), - livery); + m_xplaneAircrafts.insert(newRemoteAircraft.getCallsign(), CXPlaneMPAircraft(newRemoteAircraft, &m_interpolationLogger)); + CAircraftModel aircraftModel = newRemoteAircraft.getModel(); + QString livery = aircraftModel.getLivery().getCombinedCode(); //! \todo livery resolution for XP + m_traffic->addPlane(newRemoteAircraft.getCallsign().asString(), aircraftModel.getModelString(), + newRemoteAircraft.getAircraftIcaoCode().getDesignator(), + newRemoteAircraft.getAirlineIcaoCode().getDesignator(), + livery); - CLogMessage(this).info("XP: Added aircraft %1") << newRemoteAircraft.getCallsign().toQString(); + CLogMessage(this).info("XP: Added aircraft %1") << newRemoteAircraft.getCallsign().toQString(); - bool rendered = true; - updateAircraftRendered(newRemoteAircraft.getCallsign(), rendered); + bool rendered = true; + updateAircraftRendered(newRemoteAircraft.getCallsign(), rendered); - CSimulatedAircraft remoteAircraftCopy(newRemoteAircraft); - remoteAircraftCopy.setRendered(rendered); - emit aircraftRenderingChanged(remoteAircraftCopy); - return true; + CSimulatedAircraft remoteAircraftCopy(newRemoteAircraft); + remoteAircraftCopy.setRendered(rendered); + emit aircraftRenderingChanged(remoteAircraftCopy); + return true; + } + else + { + CAircraftModel aircraftModel = newRemoteAircraft.getModel(); + QString livery = aircraftModel.getLivery().getCombinedCode(); //! \todo livery resolution for XP + m_traffic->addPlane(newRemoteAircraft.getCallsign().asString(), aircraftModel.getModelString(), + newRemoteAircraft.getAircraftIcaoCode().getDesignator(), + newRemoteAircraft.getAirlineIcaoCode().getDesignator(), + livery); + + CLogMessage(this).info("XP: Added aircraft %1") << newRemoteAircraft.getCallsign().toQString(); + + bool rendered = true; + updateAircraftRendered(newRemoteAircraft.getCallsign(), rendered); + + CSimulatedAircraft remoteAircraftCopy(newRemoteAircraft); + remoteAircraftCopy.setRendered(rendered); + emit aircraftRenderingChanged(remoteAircraftCopy); + return true; + } } void CSimulatorXPlane::onRemoteProviderAddedAircraftSituation(const BlackMisc::Aviation::CAircraftSituation &situation) { Q_ASSERT(isConnected()); - using namespace BlackMisc::PhysicalQuantities; - m_traffic->addPlanePosition(situation.getCallsign().asString(), - situation.latitude().value(CAngleUnit::deg()), - situation.longitude().value(CAngleUnit::deg()), - situation.getAltitude().value(CLengthUnit::ft()), - situation.getPitch().value(CAngleUnit::deg()), - situation.getBank().value(CAngleUnit::deg()), - situation.getHeading().value(CAngleUnit::deg()), - situation.getMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch(), - situation.getTimeOffsetMs()); - - if (! isRemoteAircraftSupportingParts(situation.getCallsign())) + if (c_driverInterpolation) { - // if aircraft not supporting parts then guess the basics (onGround, gear, lights) - //! \todo not working for vtol - BlackMisc::Aviation::CAircraftParts parts; - parts.setMSecsSinceEpoch(situation.getMSecsSinceEpoch()); - parts.setTimeOffsetMs(situation.getTimeOffsetMs()); - if (situation.getGroundSpeed() < CSpeed(50, CSpeedUnit::kts())) + if (m_xplaneAircrafts.contains(situation.getCallsign())) { - const auto nearestAirport = std::min_element(m_airportsInRange.cbegin(), m_airportsInRange.cend(), [&situation](auto &&a, auto &&b) - { - return calculateEuclideanDistanceSquared(situation, a) < calculateEuclideanDistanceSquared(situation, b); - }); - if (nearestAirport != m_airportsInRange.cend() && situation.getAltitude() - nearestAirport->getElevation() < CLength(50, CLengthUnit::ft())) - { - parts.setOnGround(true); - parts.setGearDown(true); - } + m_xplaneAircrafts[situation.getCallsign()].addAircraftSituation(situation); } - if (situation.getAltitude() < CAltitude(10000, CLengthUnit::ft())) - { - parts.setLights({ true, true, true, true, true, true, true, true }); - } - else - { - parts.setLights({ true, false, false, true, true, true, true, true }); - } - onRemoteProviderAddedAircraftParts(situation.getCallsign(), parts); } + else + { + using namespace BlackMisc::PhysicalQuantities; + m_traffic->addPlanePosition(situation.getCallsign().asString(), + situation.latitude().value(CAngleUnit::deg()), + situation.longitude().value(CAngleUnit::deg()), + situation.getAltitude().value(CLengthUnit::ft()), + situation.getPitch().value(CAngleUnit::deg()), + situation.getBank().value(CAngleUnit::deg()), + situation.getHeading().value(CAngleUnit::deg()), + situation.getMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch(), + situation.getTimeOffsetMs()); + + if (! isRemoteAircraftSupportingParts(situation.getCallsign())) + { + // if aircraft not supporting parts then guess the basics (onGround, gear, lights) + //! \todo not working for vtol + BlackMisc::Aviation::CAircraftParts parts; + parts.setMSecsSinceEpoch(situation.getMSecsSinceEpoch()); + parts.setTimeOffsetMs(situation.getTimeOffsetMs()); + if (situation.getGroundSpeed() < CSpeed(50, CSpeedUnit::kts())) + { + const auto nearestAirport = std::min_element(m_airportsInRange.cbegin(), m_airportsInRange.cend(), [&situation](auto &&a, auto &&b) + { + return calculateEuclideanDistanceSquared(situation, a) < calculateEuclideanDistanceSquared(situation, b); + }); + if (nearestAirport != m_airportsInRange.cend() && situation.getAltitude() - nearestAirport->getElevation() < CLength(50, CLengthUnit::ft())) + { + parts.setOnGround(true); + parts.setGearDown(true); + } + } + if (situation.getAltitude() < CAltitude(10000, CLengthUnit::ft())) + { + parts.setLights({ true, true, true, true, true, true, true, true }); + } + else + { + parts.setLights({ true, false, false, true, true, true, true, true }); + } + onRemoteProviderAddedAircraftParts(situation.getCallsign(), parts); + } + } + + } void CSimulatorXPlane::onRemoteProviderAddedAircraftParts(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CAircraftParts &parts) { Q_ASSERT(isConnected()); - m_traffic->addPlaneSurfaces(callsign.asString(), parts.isGearDown() ? 1 : 0, - parts.getFlapsPercent() / 100.0, parts.isSpoilersOut() ? 1 : 0, parts.isSpoilersOut() ? 1 : 0, parts.getFlapsPercent() / 100.0, - 0, parts.isAnyEngineOn() ? 0 : 0.75, 0, 0, 0, - parts.getLights().isLandingOn(), parts.getLights().isBeaconOn(), parts.getLights().isStrobeOn(), parts.getLights().isNavOn(), - 0, parts.isOnGround(), parts.getMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch(), parts.getTimeOffsetMs()); - m_traffic->setPlaneTransponder(callsign.asString(), 2000, true, false); + + if (c_driverInterpolation) + { + if (m_xplaneAircrafts.contains(callsign)) + { + m_xplaneAircrafts[callsign].addAircraftParts(parts); + } + } + else + { + m_traffic->addPlaneSurfaces(callsign.asString(), parts.isGearDown() ? 1 : 0, + parts.getFlapsPercent() / 100.0, parts.isSpoilersOut() ? 1 : 0, parts.isSpoilersOut() ? 1 : 0, parts.getFlapsPercent() / 100.0, + 0, parts.isAnyEngineOn() ? 0 : 0.75, 0, 0, 0, + parts.getLights().isLandingOn(), parts.getLights().isBeaconOn(), parts.getLights().isStrobeOn(), parts.getLights().isNavOn(), + 0, parts.isOnGround(), parts.getMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch(), parts.getTimeOffsetMs()); + m_traffic->setPlaneTransponder(callsign.asString(), 2000, true, false); + } } bool CSimulatorXPlane::physicallyRemoveRemoteAircraft(const BlackMisc::Aviation::CCallsign &callsign) { Q_ASSERT(isConnected()); - m_traffic->removePlane(callsign.asString()); - updateAircraftRendered(callsign, false); - CLogMessage(this).info("XP: Removed aircraft %1") << callsign.toQString(); - return true; + + if (c_driverInterpolation) + { + // only remove from sim + Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "wrong thread"); + if (callsign.isEmpty()) { return false; } // can happen if an object is not an aircraft + + // clean up anyway + m_hints.remove(callsign); + + // really remove from simulator + if (!m_xplaneAircrafts.contains(callsign)) { return false; } // already fully removed or not yet added + CXPlaneMPAircraft &xplaneAircraft = m_xplaneAircrafts[callsign]; + + // avoid further data from simulator + // this->stopRequestingDataForSimObject(simObject); + + m_traffic->removePlane(callsign.asString()); + + m_xplaneAircrafts.remove(callsign); + + // mark in provider + const bool updated = this->updateAircraftRendered(callsign, false); + if (updated) + { + CSimulatedAircraft aircraft(xplaneAircraft.getAircraft()); + aircraft.setRendered(false); + emit this->aircraftRenderingChanged(aircraft); + } + + // bye + return true; + } + else + { + m_traffic->removePlane(callsign.asString()); + updateAircraftRendered(callsign, false); + CLogMessage(this).info("XP: Removed aircraft %1") << callsign.toQString(); + return true; + } } int CSimulatorXPlane::physicallyRemoveAllRemoteAircraft() @@ -578,11 +686,26 @@ namespace BlackSimPlugin Q_ASSERT(isConnected()); //! \todo XP driver obtain number of removed aircraft resetHighlighting(); - int r = getAircraftInRangeCount(); - m_traffic->removeAllPlanes(); - updateMarkAllAsNotRendered(); - CLogMessage(this).info("XP: Removed all aircraft"); - return r; + + if (c_driverInterpolation) + { + // remove one by one + int r = 0; + const CCallsignSet callsigns = m_xplaneAircrafts.getAllCallsigns(); + for (const CCallsign &cs : callsigns) + { + if (this->physicallyRemoveRemoteAircraft(cs)) { r++; } + } + return r; + } + else + { + int r = getAircraftInRangeCount(); + m_traffic->removeAllPlanes(); + updateMarkAllAsNotRendered(); + CLogMessage(this).info("XP: Removed all aircraft"); + return r; + } } CCallsignSet CSimulatorXPlane::physicallyRenderedAircraft() const @@ -701,6 +824,187 @@ namespace BlackSimPlugin m_weather->setThunderstormRatio(0.0); } + void CSimulatorXPlane::updateRemoteAircraft() + { + Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "thread"); + + const int remoteAircraftNo = this->getAircraftInRangeCount(); + if (remoteAircraftNo < 1) { m_interpolationRequest = 0; return; } + + // interpolate and send to simulator + m_interpolationRequest++; + const bool enableAircraftParts = m_interpolationRenderingSetup.isAircraftPartsEnabled(); + const CCallsignSet aircraftWithParts = enableAircraftParts ? this->remoteAircraftSupportingParts() : CCallsignSet(); // optimization, fetch all parts supporting aircraft in one step (one lock) + + // values used for position and parts + const qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch(); + const CCallsignSet callsignsToLog(m_interpolationRenderingSetup.getLogCallsigns()); + + // interpolation for all remote aircraft + const QList xplaneAircrafts(m_xplaneAircrafts.values()); + for (const CXPlaneMPAircraft &xplaneAircraft : xplaneAircrafts) + { + const CCallsign callsign(xplaneAircraft.getCallsign()); + Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "missing callsign"); + + // fetch parts, as they are needed for ground interpolation + const bool useAircraftParts = enableAircraftParts && aircraftWithParts.contains(callsign); + const bool logInterpolationAndParts = callsignsToLog.contains(callsign); + const CInterpolationAndRenderingSetup setup(this->getInterpolationAndRenderingSetup()); + CPartsStatus partsStatus(useAircraftParts); + const CAircraftParts parts = useAircraftParts ? xplaneAircraft.getInterpolatedParts(currentTimestamp, setup, partsStatus, logInterpolationAndParts) : CAircraftParts(); + + // get interpolated situation + CInterpolationStatus interpolatorStatus; + CInterpolationHints hints(m_hints[callsign]); + hints.setAircraftParts(useAircraftParts ? parts : CAircraftParts(), useAircraftParts); + hints.setLoggingInterpolation(logInterpolationAndParts); + const CAircraftSituation interpolatedSituation = xplaneAircraft.getInterpolatedSituation(currentTimestamp, setup, hints, interpolatorStatus); + + if (interpolatorStatus.hasValidSituation()) + { + // update situation + if (!xplaneAircraft.isSameAsSent(interpolatedSituation)) + { + m_xplaneAircrafts[xplaneAircraft.getCallsign()].setPositionAsSent(interpolatedSituation); + m_traffic->setPlanePosition(interpolatedSituation.getCallsign().asString(), + interpolatedSituation.latitude().value(CAngleUnit::deg()), + interpolatedSituation.longitude().value(CAngleUnit::deg()), + interpolatedSituation.getAltitude().value(CLengthUnit::ft()), + interpolatedSituation.getPitch().value(CAngleUnit::deg()), + interpolatedSituation.getBank().value(CAngleUnit::deg()), + interpolatedSituation.getHeading().value(CAngleUnit::deg())); + } + } + else + { + CLogMessage(this).warning("Invalid situation for callsign: '%1' info: '%2'") + << callsign + << interpolatorStatus.toQString(); + } + + if (useAircraftParts) + { + this->updateRemoteAircraftParts(xplaneAircraft, parts, partsStatus); + } + else + { + // guess on position, but not every frame + if (m_interpolationRequest % GuessRemoteAircraftPartsCycle == 0) + { + this->guessAndUpdateRemoteAircraftParts(xplaneAircraft, interpolatedSituation, interpolatorStatus); + } + } + } // all callsigns + + const qint64 dt = QDateTime::currentMSecsSinceEpoch() - currentTimestamp; + m_statsUpdateAircraftTimeTotalMs += dt; + m_statsUpdateAircraftCountMs++; + m_statsUpdateAircraftTimeAvgMs = m_statsUpdateAircraftTimeTotalMs / m_statsUpdateAircraftCountMs; + } + + bool CSimulatorXPlane::updateRemoteAircraftParts(const CXPlaneMPAircraft &xplaneAircraft, const CAircraftParts &parts, const CPartsStatus &partsStatus) + { + if (!partsStatus.isSupportingParts()) { return false; } + return this->sendRemoteAircraftPartsToSimulator(xplaneAircraft, parts); + } + + bool CSimulatorXPlane::guessAndUpdateRemoteAircraftParts(const CXPlaneMPAircraft &xplaneAircraft, const CAircraftSituation &interpolatedSituation, const CInterpolationStatus &interpolationStatus) + { + if (!interpolationStatus.isInterpolated()) { return false; } + + CAircraftLights lights; + CAircraftParts parts; // init members + const bool isOnGround = interpolatedSituation.isOnGround() == CAircraftSituation::OnGround; + const double gsKts = interpolatedSituation.getGroundSpeed().value(CSpeedUnit::kts()); + + parts.setEngines({ true, true, true, true }); + lights.setCabinOn(true); + lights.setRecognitionOn(true); + + // when first detected moving, lights on + if (isOnGround) + { + parts.setGearDown(true); + lights.setTaxiOn(true); + lights.setBeaconOn(true); + lights.setNavOn(true); + + if (gsKts > 5) + { + // mode taxi + lights.setTaxiOn(true); + lights.setLandingOn(false); + } + else if (gsKts > 30) + { + // mode accelaration for takeoff + lights.setTaxiOn(false); + lights.setLandingOn(true); + } + else + { + // slow movements or parking + lights.setTaxiOn(false); + lights.setLandingOn(false); + parts.setEngines({ false, false, false, false }); + } + } + else + { + // not on ground + parts.setGearDown(false); + lights.setTaxiOn(false); + lights.setBeaconOn(true); + lights.setNavOn(true); + // landing lights for < 10000ft (normally MSL, here ignored) + lights.setLandingOn(interpolatedSituation.getAltitude().value(CLengthUnit::ft()) < 10000); + + if (!xplaneAircraft.isVtol() && interpolatedSituation.hasGroundElevation()) + { + if (interpolatedSituation.getHeightAboveGround().value(CLengthUnit::ft()) < 1000) + { + parts.setGearDown(true); + parts.setFlapsPercent(25); + } + else if (interpolatedSituation.getHeightAboveGround().value(CLengthUnit::ft()) < 2000) + { + parts.setGearDown(true); + parts.setFlapsPercent(10); + } + } + } + + parts.setLights(lights); + return this->sendRemoteAircraftPartsToSimulator(xplaneAircraft, parts); + } + + bool CSimulatorXPlane::sendRemoteAircraftPartsToSimulator(const CXPlaneMPAircraft &xplaneAircraft, const CAircraftParts &parts) + { + // same as in simulator or same as already send to simulator? + if (xplaneAircraft.getPartsAsSent() == parts) + { + return true; + } + + m_traffic->setPlaneSurfaces(xplaneAircraft.getCallsign().asString(), + parts.isGearDown() ? 1 : 0, + parts.getFlapsPercent() / 100.0, + parts.isSpoilersOut() ? 1 : 0, + parts.isSpoilersOut() ? 1 : 0, + parts.getFlapsPercent() / 100.0, + 0, parts.isAnyEngineOn() ? 0 : 0.75, + 0, 0, 0, + parts.getLights().isLandingOn(), parts.getLights().isBeaconOn(), parts.getLights().isStrobeOn(), parts.getLights().isNavOn(), + 0, parts.isOnGround()); + + CAircraftLights lights = parts.getLights(); + lights.setRecognitionOn(parts.isAnyEngineOn()); + lights.setCabinOn(parts.isAnyEngineOn()); + + return true; + } + BlackCore::ISimulator *CSimulatorXPlaneFactory::create(const CSimulatorPluginInfo &info, IOwnAircraftProvider *ownAircraftProvider, IRemoteAircraftProvider *remoteAircraftProvider, diff --git a/src/plugins/simulator/xplane/simulatorxplane.h b/src/plugins/simulator/xplane/simulatorxplane.h index 3d60c8808..6a83cbd60 100644 --- a/src/plugins/simulator/xplane/simulatorxplane.h +++ b/src/plugins/simulator/xplane/simulatorxplane.h @@ -12,6 +12,7 @@ #ifndef BLACKSIMPLUGIN_SIMULATOR_XPLANE_H #define BLACKSIMPLUGIN_SIMULATOR_XPLANE_H +#include "xplanempaircraft.h" #include "blackcore/simulator.h" #include "blackcore/simulatorcommon.h" #include "plugins/simulator/xplaneconfig/simulatorxplaneconfig.h" @@ -143,6 +144,25 @@ namespace BlackSimPlugin void loadCslPackages(); QString findCslPackage(const QString &modelFileName); + //! Update remote aircraft + //! \remark this is where the interpolated data are set + void updateRemoteAircraft(); + + //! Update remote aircraft parts (send to XSwiftBus) + bool updateRemoteAircraftParts(const CXPlaneMPAircraft &xplaneAircraft, + const BlackMisc::Aviation::CAircraftParts &parts, const BlackMisc::Simulation::CPartsStatus &partsStatus); + + //! Update remote aircraft parts by guessing (send to XSwiftBus) + bool guessAndUpdateRemoteAircraftParts(const CXPlaneMPAircraft &xplaneAircraft, + const BlackMisc::Aviation::CAircraftSituation &interpolatedSituation, const BlackMisc::Simulation::CInterpolationStatus &interpolationStatus); + + //! Send parts to simulator + bool sendRemoteAircraftPartsToSimulator(const CXPlaneMPAircraft &xplaneAircraft, const BlackMisc::Aviation::CAircraftParts &parts); + + static constexpr bool c_driverInterpolation = true; + static constexpr int GuessRemoteAircraftPartsCycle = 20; //!< guess every n-th cycle + + // XSwiftBus interpolation QDBusConnection m_conn { "default" }; QDBusServiceWatcher *m_watcher { nullptr }; CXSwiftBusServiceProxy *m_service { nullptr }; @@ -153,6 +173,10 @@ namespace BlackSimPlugin BlackMisc::Aviation::CAirportList m_airportsInRange; //!< aiports in range of own aircraft BlackMisc::CData m_modelSet { this }; + // Driver Interpolation + CXPlaneMPAircrafts m_xplaneAircrafts; //!< XPlane multiplayer aircrafts + int m_interpolationRequest = 0; //!< current interpolation request + //! \todo Add units to members? pitchDeg?, altitudeFt? struct // data is written by DBus async method callbacks { diff --git a/src/plugins/simulator/xplane/xplanempaircraft.cpp b/src/plugins/simulator/xplane/xplanempaircraft.cpp new file mode 100644 index 000000000..31227999c --- /dev/null +++ b/src/plugins/simulator/xplane/xplanempaircraft.cpp @@ -0,0 +1,124 @@ +/* Copyright (C) 2018 + * 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 and at http://www.swift-project.org/license.html. 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. + */ + +#include "xplanempaircraft.h" +#include "blackmisc/simulation/interpolatormulti.h" + +using namespace BlackMisc::Aviation; +using namespace BlackMisc::Simulation; + +namespace BlackSimPlugin +{ + namespace XPlane + { + CXPlaneMPAircraft::CXPlaneMPAircraft() + { } + + CXPlaneMPAircraft::CXPlaneMPAircraft(const CSimulatedAircraft &aircraft, + CInterpolationLogger *logger) : + m_aircraft(aircraft), + m_interpolator(QSharedPointer::create(aircraft.getCallsign())) + { + m_interpolator->attachLogger(logger); + + // if available set situation and parts + if (aircraft.isPartsSynchronized()) { this->addAircraftParts(aircraft.getParts()); } + if (aircraft.getSituation().hasValidTimestamp()) { this->addAircraftSituation(aircraft.getSituation()); } + } + + CXPlaneMPAircraft::CXPlaneMPAircraft(const CAircraftSituation &situation) : + m_interpolator(QSharedPointer::create(situation.getCallsign())) + { + if (situation.hasValidTimestamp()) { this->addAircraftSituation(situation); } + } + + CXPlaneMPAircraft::CXPlaneMPAircraft(const CAircraftParts &parts, const CCallsign &callsign) : + m_interpolator(QSharedPointer::create(callsign)) + { + if (parts.hasValidTimestamp()) { this->addAircraftParts(parts); } + } + + void CXPlaneMPAircraft::addAircraftParts(const CAircraftParts &parts) + { + Q_ASSERT(m_interpolator); + Q_ASSERT(parts.hasValidTimestamp()); + m_interpolator->addAircraftParts(parts); + m_aircraft.setParts(parts); + } + + void CXPlaneMPAircraft::addAircraftSituation(const CAircraftSituation &situation) + { + Q_ASSERT(m_interpolator); + Q_ASSERT(situation.hasValidTimestamp()); + m_interpolator->addAircraftSituation(situation); + m_aircraft.setSituation(situation); // update with last situation + } + + bool CXPlaneMPAircraft::isSameAsSent(const CAircraftSituation &position) const + { + return m_positionAsSent == position; + } + + void CXPlaneMPAircraft::toggleInterpolatorMode() + { + Q_ASSERT(m_interpolator); + m_interpolator->toggleMode(); + } + + bool CXPlaneMPAircraft::setInterpolatorMode(CInterpolatorMulti::Mode mode) + { + Q_ASSERT(m_interpolator); + return m_interpolator->setMode(mode); + } + + QString CXPlaneMPAircraft::getInterpolatorInfo() const + { + Q_ASSERT(m_interpolator); + return m_interpolator->getInterpolatorInfo(); + } + + void CXPlaneMPAircraft::attachInterpolatorLogger(CInterpolationLogger *logger) + { + Q_ASSERT(m_interpolator); + return m_interpolator->attachLogger(logger); + } + + CAircraftSituation CXPlaneMPAircraft::getInterpolatedSituation( + qint64 currentTimeSinceEpoc, + const CInterpolationAndRenderingSetup &setup, + const CInterpolationHints &hints, CInterpolationStatus &status) const + { + Q_ASSERT(m_interpolator); + return m_interpolator->getInterpolatedSituation(currentTimeSinceEpoc, setup, hints, status); + } + + CAircraftParts CXPlaneMPAircraft::getInterpolatedParts( + qint64 currentTimeSinceEpoc, const CInterpolationAndRenderingSetup &setup, + CPartsStatus &partsStatus, bool log) const + { + Q_ASSERT(m_interpolator); + return m_interpolator->getInterpolatedParts(currentTimeSinceEpoc, setup, partsStatus, log); + } + + CCallsignSet CXPlaneMPAircrafts::getAllCallsigns() const + { + return CCallsignSet(this->keys()); + } + + int CXPlaneMPAircrafts::setInterpolatorModes(CInterpolatorMulti::Mode mode) + { + int c = 0; + for (const CCallsign &cs : this->keys()) + { + if ((*this)[cs].setInterpolatorMode(mode)) c++; + } + return c; + } + } // namespace +} // namespace diff --git a/src/plugins/simulator/xplane/xplanempaircraft.h b/src/plugins/simulator/xplane/xplanempaircraft.h new file mode 100644 index 000000000..1e8990457 --- /dev/null +++ b/src/plugins/simulator/xplane/xplanempaircraft.h @@ -0,0 +1,136 @@ +/* Copyright (C) 2018 + * 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 and at http://www.swift-project.org/license.html. 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. + */ + +//! \file + +#ifndef BLACKSIMPLUGIN_XPLANE_XPLANEMPAIRCRAFT_H +#define BLACKSIMPLUGIN_XPLANE_XPLANEMPAIRCRAFT_H + +#include "blackmisc/simulation/simulatedaircraft.h" +#include "blackmisc/simulation/interpolatormulti.h" +#include +#include + +namespace BlackSimPlugin +{ + namespace XPlane + { + //! Class representing a X-Plane multiplayer aircraft + class CXPlaneMPAircraft + { + public: + //! Constructor + CXPlaneMPAircraft(); + + //! Constructor providing initial situation/parts + CXPlaneMPAircraft(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, + BlackMisc::Simulation::CInterpolationLogger *logger); + + //! Constructor providing initial situation + CXPlaneMPAircraft(const BlackMisc::Aviation::CAircraftSituation &situation); + + //! Constructor providing initial parts + CXPlaneMPAircraft(const BlackMisc::Aviation::CAircraftParts &parts, const BlackMisc::Aviation::CCallsign &callsign); + + //! Destructor + ~CXPlaneMPAircraft() {} + + //! Get callsign + const BlackMisc::Aviation::CCallsign &getCallsign() const { return m_aircraft.getCallsign(); } + + //! Simulated aircraft (as added) + const BlackMisc::Simulation::CSimulatedAircraft &getAircraft() const { return m_aircraft; } + + //! Simulated aircraft model string + const QString &getAircraftModelString() const { return m_aircraft.getModelString(); } + + //! Set the aircraft + void setAircraft(const BlackMisc::Simulation::CSimulatedAircraft &aircraft) { m_aircraft = aircraft; } + + //! Add parts for interpolator + void addAircraftParts(const BlackMisc::Aviation::CAircraftParts &parts); + + //! Add situation for interpolator + void addAircraftSituation(const BlackMisc::Aviation::CAircraftSituation &situation); + + //! Parts as sent to simulator + const BlackMisc::Aviation::CAircraftParts &getPartsAsSent() const { return m_partsAsSent; } + + //! Parts as sent to simulator + void setPartsAsSent(const BlackMisc::Aviation::CAircraftParts &parts) { m_partsAsSent = parts; } + + //! Position as sent + void setPositionAsSent(const BlackMisc::Aviation::CAircraftSituation &position) { m_positionAsSent = position; } + + //! Same as sent + bool isSameAsSent(const BlackMisc::Aviation::CAircraftSituation &position) const; + + //! VTOL? + bool isVtol() const { return m_aircraft.isVtol(); } + + //! \copydoc BlackMisc::Simulation::CInterpolatorMulti::toggleMode + void toggleInterpolatorMode(); + + //! \copydoc BlackMisc::Simulation::CInterpolatorMulti::setMode + bool setInterpolatorMode(BlackMisc::Simulation::CInterpolatorMulti::Mode mode); + + //! \copydoc BlackMisc::Simulation::CInterpolator::getInterpolatorInfo + QString getInterpolatorInfo() const; + + //! \copydoc BlackMisc::Simulation::CInterpolator::attachLogger + void attachInterpolatorLogger(BlackMisc::Simulation::CInterpolationLogger *logger); + + //! \copydoc BlackMisc::Simulation::CInterpolator::getInterpolatedSituation + BlackMisc::Aviation::CAircraftSituation getInterpolatedSituation( + qint64 currentTimeSinceEpoc, + const BlackMisc::Simulation::CInterpolationAndRenderingSetup &setup, + const BlackMisc::Simulation::CInterpolationHints &hints, BlackMisc::Simulation::CInterpolationStatus &status) const; + + //! \copydoc BlackMisc::Simulation::CInterpolator::getInterpolatedParts + BlackMisc::Aviation::CAircraftParts getInterpolatedParts( + qint64 currentTimeSinceEpoc, + const BlackMisc::Simulation::CInterpolationAndRenderingSetup &setup, + BlackMisc::Simulation::CPartsStatus &partsStatus, bool log) const; + + //! Interpolator + BlackMisc::Simulation::CInterpolatorMulti *getInterpolator() const { return m_interpolator.data(); } + + private: + BlackMisc::Simulation::CSimulatedAircraft m_aircraft; //!< corresponding aircraft + QSharedPointer m_interpolator; //!< shared pointer because CSimConnectObject can be copied + BlackMisc::Aviation::CAircraftSituation m_positionAsSent; + BlackMisc::Aviation::CAircraftParts m_partsAsSent; + }; + + //! Simulator objects (aka AI aircraft) + class CXPlaneMPAircrafts : public QHash + { + public: + //! Get all callsigns + BlackMisc::Aviation::CCallsignSet getAllCallsigns() const; + + //! Get all callsign strings + QStringList getAllCallsignStrings(bool sorted = false) const; + + //! Get all callsign strings as string + QString getAllCallsignStringsAsString(bool sorted = false, const QString &separator = ", ") const; + + //! Toggle interpolator modes + void toggleInterpolatorModes(); + + //! Toggle interpolator modes + void toggleInterpolatorMode(const BlackMisc::Aviation::CCallsign &callsign); + + //! Set interpolator modes + int setInterpolatorModes(BlackMisc::Simulation::CInterpolatorMulti::Mode mode); + }; + } // namespace +} // namespace + +#endif // guard diff --git a/src/plugins/simulator/xplane/xswiftbustrafficproxy.cpp b/src/plugins/simulator/xplane/xswiftbustrafficproxy.cpp index 58a9a6fb1..bf0258c7f 100644 --- a/src/plugins/simulator/xplane/xswiftbustrafficproxy.cpp +++ b/src/plugins/simulator/xplane/xswiftbustrafficproxy.cpp @@ -86,6 +86,11 @@ namespace BlackSimPlugin m_dbusInterface->callDBus(QLatin1String("addPlanePosition"), callsign, latitude, longitude, altitude, pitch, roll, heading, relativeTime, timeOffset); } + void CXSwiftBusTrafficProxy::setPlanePosition(const QString &callsign, double latitude, double longitude, double altitude, double pitch, double roll, double heading) + { + m_dbusInterface->callDBus(QLatin1String("setPlanePosition"), callsign, latitude, longitude, altitude, pitch, roll, heading); + } + void CXSwiftBusTrafficProxy::addPlaneSurfaces(const QString &callsign, double gear, double flap, double spoiler, double speedBrake, double slat, double wingSweep, double thrust, double elevator, double rudder, double aileron, bool landLight, bool beaconLight, bool strobeLight, bool navLight, int lightPattern, bool onGround, qint64 relativeTime, qint64 timeOffset) { @@ -93,6 +98,13 @@ namespace BlackSimPlugin landLight, beaconLight, strobeLight, navLight, lightPattern, onGround, relativeTime, timeOffset); } + void CXSwiftBusTrafficProxy::setPlaneSurfaces(const QString &callsign, double gear, double flap, double spoiler, double speedBrake, double slat, double wingSweep, double thrust, + double elevator, double rudder, double aileron, bool landLight, bool beaconLight, bool strobeLight, bool navLight, int lightPattern, bool onGround) + { + m_dbusInterface->callDBus(QLatin1String("setPlaneSurfaces"), callsign, gear, flap, spoiler, speedBrake, slat, wingSweep, thrust, elevator, rudder, aileron, + landLight, beaconLight, strobeLight, navLight, lightPattern, onGround); + } + void CXSwiftBusTrafficProxy::setPlaneTransponder(const QString &callsign, int code, bool modeC, bool ident) { m_dbusInterface->callDBus(QLatin1String("setPlaneTransponder"), callsign, code, modeC, ident); diff --git a/src/plugins/simulator/xplane/xswiftbustrafficproxy.h b/src/plugins/simulator/xplane/xswiftbustrafficproxy.h index 75e3843ed..64d577fd2 100644 --- a/src/plugins/simulator/xplane/xswiftbustrafficproxy.h +++ b/src/plugins/simulator/xplane/xswiftbustrafficproxy.h @@ -61,6 +61,10 @@ namespace BlackSimPlugin private: BlackMisc::CGenericDBusInterface *m_dbusInterface = nullptr; + signals: + //! \copydoc XSwiftBus::CTraffic::simFrame + void simFrame(); + public slots: //! \copydoc XSwiftBus::CTraffic::initialize bool initialize(); @@ -98,10 +102,17 @@ namespace BlackSimPlugin //! \copydoc XSwiftBus::CTraffic::addPlanePosition void addPlanePosition(const QString &callsign, double latitude, double longitude, double altitude, double pitch, double roll, double heading, qint64 relativeTime, qint64 timeOffset); + //! \copydoc XSwiftBus::CTraffic::setPlanePosition + void setPlanePosition(const QString &callsign, double latitude, double longitude, double altitude, double pitch, double roll, double heading); + //! \copydoc XSwiftBus::CTraffic::addPlaneSurfaces void addPlaneSurfaces(const QString &callsign, double gear, double flap, double spoiler, double speedBrake, double slat, double wingSweep, double thrust, double elevator, double rudder, double aileron, bool landLight, bool beaconLight, bool strobeLight, bool navLight, int lightPattern, bool onGround, qint64 relativeTime, qint64 timeOffset); + //! \copydoc XSwiftBus::CTraffic::setPlaneSurfaces + void setPlaneSurfaces(const QString &callsign, double gear, double flap, double spoiler, double speedBrake, double slat, double wingSweep, double thrust, + double elevator, double rudder, double aileron, bool landLight, bool beaconLight, bool strobeLight, bool navLight, int lightPattern, bool onGround); + //! \copydoc XSwiftBus::CTraffic::setPlaneTransponder void setPlaneTransponder(const QString &callsign, int code, bool modeC, bool ident); diff --git a/src/xswiftbus/traffic.cpp b/src/xswiftbus/traffic.cpp index 9d48ca787..8c7f0eda0 100644 --- a/src/xswiftbus/traffic.cpp +++ b/src/xswiftbus/traffic.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,7 @@ namespace XSwiftBus CTraffic::CTraffic(QObject *parent) : QObject(parent) { + XPLMRegisterDrawCallback(CTraffic::drawCallback, xplm_Phase_Airplanes, 0, this); } CTraffic::~CTraffic() @@ -124,6 +126,21 @@ namespace XSwiftBus m_initialized = false; XPMPMultiplayerCleanup(); } + + XPLMUnregisterDrawCallback(CTraffic::drawCallback, xplm_Phase_Airplanes, 0, this); + } + + void CTraffic::emitSimFrame() + { + qint64 currentMSecsSinceEpoch = QDateTime::currentMSecsSinceEpoch(); + + // The draw callback is called twice for unknown reasons each frame. We can filter the second one by + // requiring a minimum offset of 5 ms (equal to 200 fps). + if (currentMSecsSinceEpoch > m_timestampLastSimFrame + 5) + { + emit simFrame(); + m_timestampLastSimFrame = currentMSecsSinceEpoch; + } } int g_maxPlanes = 100; @@ -258,6 +275,17 @@ namespace XSwiftBus } } + void CTraffic::setPlanePosition(const QString &callsign, double latitude, double longitude, double altitude, double pitch, double roll, double heading) + { + const auto plane = m_planesByCallsign.value(callsign, nullptr); + plane->position.lat = latitude; + plane->position.lon = longitude; + plane->position.elevation = altitude; + plane->position.pitch = static_cast(pitch); + plane->position.roll = static_cast(roll); + plane->position.heading = static_cast(heading); + } + void CTraffic::addPlaneSurfaces(const QString &callsign, double gear, double flap, double spoiler, double speedBrake, double slat, double wingSweep, double thrust, double elevator, double rudder, double aileron, bool landLight, bool beaconLight, bool strobeLight, bool navLight, int lightPattern, bool onGround, qint64 relativeTime, qint64 timeOffset) { @@ -295,6 +323,32 @@ namespace XSwiftBus } } + void CTraffic::setPlaneSurfaces(const QString &callsign, double gear, double flap, double spoiler, double speedBrake, double slat, double wingSweep, double thrust, + double elevator, double rudder, double aileron, bool landLight, bool beaconLight, bool strobeLight, bool navLight, int lightPattern, bool onGround) + { + Q_UNUSED(onGround); + const auto plane = m_planesByCallsign.value(callsign, nullptr); + if (plane) + { + plane->hasSurfaces = true; + plane->targetGearPosition = gear; + plane->surfaces.flapRatio = flap; + plane->surfaces.spoilerRatio = spoiler; + plane->surfaces.speedBrakeRatio = speedBrake; + plane->surfaces.slatRatio = slat; + plane->surfaces.wingSweep = wingSweep; + plane->surfaces.thrust = thrust; + plane->surfaces.yokePitch = elevator; + plane->surfaces.yokeHeading = rudder; + plane->surfaces.yokeRoll = aileron; + plane->surfaces.lights.landLights = landLight; + plane->surfaces.lights.bcnLights = beaconLight; + plane->surfaces.lights.strbLights = strobeLight; + plane->surfaces.lights.navLights = navLight; + plane->surfaces.lights.flashPattern = lightPattern; + } + } + void CTraffic::setPlaneTransponder(const QString &callsign, int code, bool modeC, bool ident) { const auto plane = m_planesByCallsign.value(callsign, nullptr); @@ -342,6 +396,20 @@ namespace XSwiftBus switch (dataType) { case xpmpDataType_Position: + { + if (c_driverInterpolation) + { + const auto io_position = static_cast(io_data); + io_position->lat = plane->position.lat; + io_position->lon = plane->position.lon; + io_position->elevation = plane->position.elevation; + io_position->pitch = plane->position.pitch; + io_position->roll = plane->position.roll; + io_position->heading = plane->position.heading; + std::strncpy(io_position->label, plane->label, sizeof(plane->label)); // fixme don't need to copy on every frame + return xpmpData_NewData; + } + else { BlackMisc::Simulation::CInterpolationAndRenderingSetup setup; BlackMisc::Simulation::CInterpolationStatus status; @@ -364,16 +432,23 @@ namespace XSwiftBus return xpmpData_NewData; } + } + case xpmpDataType_Surfaces: if (plane->hasSurfaces) { const auto currentTime = QDateTime::currentMSecsSinceEpoch(); - while (! plane->pendingSurfaces.isEmpty() && plane->pendingSurfaces.constFirst().first <= currentTime) + + if (! c_driverInterpolation) { - //! \todo if gear is currently retracted, look ahead and pull gear position from pendingSurfaces up to 5 seconds in the future - plane->pendingSurfaces.constFirst().second(plane); - plane->pendingSurfaces.pop_front(); + while (! plane->pendingSurfaces.isEmpty() && plane->pendingSurfaces.constFirst().first <= currentTime) + { + //! \todo if gear is currently retracted, look ahead and pull gear position from pendingSurfaces up to 5 seconds in the future + plane->pendingSurfaces.constFirst().second(plane); + plane->pendingSurfaces.pop_front(); + } } + if (plane->surfaces.gearPosition != plane->targetGearPosition) { // interpolate gear position @@ -412,6 +487,15 @@ namespace XSwiftBus } } + int CTraffic::drawCallback(XPLMDrawingPhase phase, int isBefore, void *refcon) + { + Q_UNUSED(phase); + Q_UNUSED(isBefore); + CTraffic *traffic = static_cast(refcon); + traffic->emitSimFrame(); + return 1; + } + } //! \endcond diff --git a/src/xswiftbus/traffic.h b/src/xswiftbus/traffic.h index 05833c3d7..6c2afa262 100644 --- a/src/xswiftbus/traffic.h +++ b/src/xswiftbus/traffic.h @@ -22,6 +22,7 @@ #include #include #include "XPMPMultiplayer.h" +#include #include #include @@ -65,6 +66,10 @@ namespace XSwiftBus //! Called by XPluginStart static void initLegacyData(); + signals: + //! Signal emitted for each simulator rendering frame + void simFrame(); + public slots: //! Initialize the multiplayer planes rendering and return true if successful bool initialize(); @@ -99,13 +104,20 @@ namespace XSwiftBus //! Remove all traffic aircraft void removeAllPlanes(); - //! Set the position of a traffic aircraft + //! Add the position of a traffic aircraft void addPlanePosition(const QString &callsign, double latitude, double longitude, double altitude, double pitch, double roll, double heading, qint64 relativeTime, qint64 timeOffset); - //! Set the flight control surfaces and lights of a traffic aircraft + //! Set the position of a traffic aircraft + void setPlanePosition(const QString &callsign, double latitude, double longitude, double altitude, double pitch, double roll, double heading); + + //! Add the flight control surfaces and lights of a traffic aircraft void addPlaneSurfaces(const QString &callsign, double gear, double flap, double spoiler, double speedBrake, double slat, double wingSweep, double thrust, double elevator, double rudder, double aileron, bool landLight, bool beaconLight, bool strobeLight, bool navLight, int lightPattern, bool onGround, qint64 relativeTime, qint64 timeOffset); + //! Set the flight control surfaces and lights of a traffic aircraft + void setPlaneSurfaces(const QString &callsign, double gear, double flap, double spoiler, double speedBrake, double slat, double wingSweep, double thrust, + double elevator, double rudder, double aileron, bool landLight, bool beaconLight, bool strobeLight, bool navLight, int lightPattern, bool onGround); + //! Set the transponder of a traffic aircraft void setPlaneTransponder(const QString &callsign, int code, bool modeC, bool ident); @@ -116,6 +128,10 @@ namespace XSwiftBus bool m_initialized = false; bool m_enabled = false; + static constexpr bool c_driverInterpolation = true; + + void emitSimFrame(); + static int preferences(const char *section, const char *name, int def); static float preferences(const char *section, const char *name, float def); @@ -137,16 +153,20 @@ namespace XSwiftBus float targetGearPosition = 0; qint64 prevSurfacesLerpTime = 0; XPMPPlaneRadar_t xpdr; + XPMPPlanePosition_t position; Plane(void *id_, QString callsign_, QString aircraftIcao_, QString airlineIcao_, QString livery_); }; QHash m_planesByCallsign; QHash m_planesById; + qint64 m_timestampLastSimFrame = QDateTime::currentMSecsSinceEpoch(); int getPlaneData(void *id, int dataType, void *io_data); static int getPlaneData(void *id, int dataType, void *io_data, void *self) { return static_cast(self)->getPlaneData(id, dataType, io_data); } + + static int drawCallback(XPLMDrawingPhase phase, int isBefore, void *refcon); }; }