swift
simulatorp3d.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2017 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "simulatorp3d.h"
5 
6 #include "../fscommon/simulatorfscommonfunctions.h"
7 #include "../fsxcommon/simconnectsymbols.h"
8 
9 #include "config/buildconfig.h"
10 #include "misc/logmessage.h"
11 #include "misc/threadutils.h"
12 
13 using namespace swift::config;
14 using namespace swift::misc;
15 using namespace swift::misc::aviation;
16 using namespace swift::misc::physical_quantities;
17 using namespace swift::misc::geo;
18 using namespace swift::misc::network;
19 using namespace swift::misc::simulation;
20 using namespace swift::misc::simulation::fscommon;
21 using namespace swift::simplugin::fsxcommon;
22 using namespace swift::core;
23 using namespace swift::simplugin::fscommon;
24 
25 namespace swift::simplugin::p3d
26 {
27  CSimulatorP3D::CSimulatorP3D(const CSimulatorPluginInfo &info, IOwnAircraftProvider *ownAircraftProvider,
28  IRemoteAircraftProvider *remoteAircraftProvider, IClientProvider *clientProvider,
29  QObject *parent)
30  : CSimulatorFsxCommon(info, ownAircraftProvider, remoteAircraftProvider, clientProvider, parent)
31  {
32  // set build/sim specific SimConnectProc, which is the FSX SimConnectProc on WIN32 systems
33  if (CBuildConfig::isCompiledWithP3DSupport() && CBuildConfig::isRunningOnWindowsNtPlatform() &&
34  CBuildConfig::buildWordSize() == 64)
35  {
36  // modern x64 P3D
37  this->setUsingFsxTerrainProbe(false);
39  }
40  this->setDefaultModel(CAircraftModel("LOCKHEED L049_2", CAircraftModel::TypeModelMatchingDefaultModel,
41  "Constellation in TWA livery", CAircraftIcaoCode("CONI", "L4P")));
42  }
43 
45  {
46 #ifdef Q_OS_WIN64
47  if (!loadAndResolveP3DSimConnectByString(m_p3dVersion.get())) { return false; }
48  return CSimulatorFsxCommon::connectTo();
49 #else
50  if (!loadAndResolveFsxSimConnect(true)) { return false; }
51  return CSimulatorFsxCommon::connectTo();
52 #endif
53  }
54 
55 #ifdef Q_OS_WIN64
56  void CSimulatorP3D::SimConnectProc(SIMCONNECT_RECV *pData, DWORD cbData, void *pContext)
57  {
58  CSimulatorP3D *simulatorP3D = static_cast<CSimulatorP3D *>(pContext);
59  Q_ASSERT_X(simulatorP3D, Q_FUNC_INFO, "Cannot convert context to CSimulatorP3D");
60 
61  switch (pData->dwID)
62  {
63  // case SIMCONNECT_RECV_ID_CAMERA_6DOF: break;
64  // case SIMCONNECT_RECV_ID_CAMERA_FOV: break;
65  // case SIMCONNECT_RECV_ID_CAMERA_SENSOR_MODE: break;
66  // case SIMCONNECT_RECV_ID_CAMERA_WINDOW_POSITION: break;
67  // case SIMCONNECT_RECV_ID_CAMERA_WINDOW_SIZE: break;
68 
69  case SIMCONNECT_RECV_ID_GROUND_INFO:
70  {
71  // https://www.prepar3d.com/SDKv4/sdk/simconnect_api/references/structures_and_enumerations.html#SIMCONNECT_RECV_GROUND_INFO
72  const SIMCONNECT_RECV_GROUND_INFO *pObjData = static_cast<SIMCONNECT_RECV_GROUND_INFO *>(pData);
73  const DWORD requestId = pObjData->dwRequestID;
74  if (!CSimulatorFsxCommon::isRequestForSimObjTerrainProbe(requestId)) { break; }
75  // valid elevation request
76  // https://www.prepar3d.com/SDKv4/sdk/simconnect_api/references/structures_and_enumerations.html#SIMCONNECT_DATA_GROUND_INFO
77  if (pObjData->dwArraySize != 1) { break; }
78  const SIMCONNECT_DATA_GROUND_INFO gi = pObjData->rgData[0];
79  if (!gi.bIsValid) { break; }
80  const CLatitude lat(gi.fLat, CAngleUnit::deg());
81  const CLongitude lng(gi.fLon, CAngleUnit::deg());
82  const CAltitude alt(gi.fAlt, CAltitude::MeanSeaLevel, CAltitude::TrueAltitude, CLengthUnit::ft());
83  const CCoordinateGeodetic coordinate(lat, lng, alt);
84  const CElevationPlane ep(coordinate, CElevationPlane::singlePointRadius());
85 
86  const CCallsign cs(simulatorP3D->getCallsignForPendingProbeRequests(requestId, true));
87  simulatorP3D->callbackReceivedRequestedElevation(ep, cs, false);
88  }
89  break;
90  default: CSimulatorFsxCommon::SimConnectProc(pData, cbData, pContext); break;
91  }
92  }
93 
94  // P3D version with new P3D simconnect functions
95  bool CSimulatorP3D::requestElevation(const ICoordinateGeodetic &reference, const CCallsign &callsign)
96  {
97  if (reference.isNull()) { return false; }
98  if (this->isShuttingDown()) { return false; }
99  if (!this->isConnected()) { return false; }
100 
101  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
102  const bool hasHeight = reference.hasMSLGeodeticHeight();
103  const double latDeg = reference.latitude().value(CAngleUnit::deg());
104  const double lngDeg = reference.longitude().value(CAngleUnit::deg());
105  const double maxAltFt = hasHeight ? reference.geodeticHeight().value(CLengthUnit::ft()) : 50000;
106  const DWORD dwGridWidth = 1.0;
107  const DWORD dwGridHeight = 1.0;
108 
109  const SIMCONNECT_DATA_REQUEST_ID requestId =
110  this->obtainRequestIdForSimObjTerrainProbe(); // P3D we use new request id each time (no simobject)
111 
112  // returns SIMCONNECT_RECV_GROUND_INFO -> SIMCONNECT_DATA_GROUND_INFO
113  const HRESULT hr = this->logAndTraceSendId(
114  SimConnect_RequestGroundInfo(m_hSimConnect, requestId, latDeg, lngDeg, 0, latDeg, lngDeg, maxAltFt,
115  dwGridWidth, dwGridHeight, SIMCONNECT_GROUND_INFO_LATLON_FORMAT_DEGREES,
116  SIMCONNECT_GROUND_INFO_ALT_FORMAT_FEET,
117  SIMCONNECT_GROUND_INFO_SOURCE_FLAG_PLATFORMS),
118  Q_FUNC_INFO, "SimConnect_RequestGroundInfo");
119 
120  bool ok = false;
121  if (isOk(hr))
122  {
123  ok = true;
124  emit this->requestedElevation(callsign);
125  m_pendingProbeRequests.insert(requestId, callsign);
126  }
127  else
128  {
129  const CStatusMessage msg = CStatusMessage(this).error(u"SimConnect, can not request ground info: '%1' '%2'")
130  << requestId << callsign.asString();
131  CLogMessage::preformatted(msg);
132  }
133 
134  return ok;
135  }
136 
137  bool CSimulatorP3D::followAircraft(const CCallsign &callsign)
138  {
139  if (this->isShuttingDownOrDisconnected()) { return false; }
140 
141  CSimConnectObject &simObject = m_simConnectObjects[callsign];
142  if (!simObject.hasValidRequestAndObjectId()) { return false; }
143  if (simObject.getCallsignByteArray().isEmpty()) { return false; }
144  HRESULT hr = s_false();
145 
146  //
147  // Experimental code
148  //
149 
186  // Observer is P3D only, not FSX
187  const CAircraftSituation situation = m_lastSentSituations[callsign];
188  if (situation.isNull()) { return false; }
189  SIMCONNECT_DATA_OBSERVER obs;
191  pbh.Pitch = pbh.Bank = pbh.Heading = 0;
192  obs.Rotation = pbh;
193  obs.Position = coordinateToFsxLatLonAlt(situation);
194  obs.Regime = SIMCONNECT_OBSERVER_REGIME_GHOST;
195  obs.RotateOnTarget = TRUE;
196  obs.FocusFixed = TRUE;
197  obs.FieldOfViewH = 30; // deg.
198  obs.FieldOfViewV = 30; // deg.
199  obs.LinearStep = 20; // meters
200  obs.AngularStep = 10; // deg.
201 
202  const char *observerName = simObject.getCallsignByteArray().constData();
203  hr = SimConnect_CreateObserver(m_hSimConnect, observerName, obs);
204  if (isOk(hr))
205  {
206  SIMCONNECT_DATA_XYZ offset;
207  offset.x = offset.y = 30;
208  offset.z = 0;
209  hr = SimConnect_ObserverAttachToEntityOn(m_hSimConnect, observerName, simObject.getObjectId(), offset);
210  if (isOk(hr))
211  {
212  SimConnect_SetObserverLookAt(m_hSimConnect, observerName, obs.Position);
213 
214  // const QByteArray viewName = QStringLiteral("Observer %1").arg(callsign.asString()).toLatin1();
215  hr = SimConnect_OpenView(m_hSimConnect, observerName);
216 
217  simObject.setObserverName(callsign.asString());
218  }
219  }
220 
221  return isOk(hr);
222  }
223 
225  {
226  HRESULT hr = s_ok();
227  if (isFailure(hr))
228  {
229  CLogMessage(this).error(u"P3D plugin error: %1") << "initEventsP3D failed";
230  return hr;
231  }
232  return hr;
233  }
234 
235  void CSimulatorP3D::removeCamera(fsxcommon::CSimConnectObject &simObject)
236  {
237  if (!simObject.hasCamera()) { return; }
238  simObject.removeCamera();
239  // const char *cameraName = simObject.getCallsignByteArray().constData();
240  // SimConnect_DeleteCameraInstance(m_hSimConnect, simObject.getCameraGUID(), 0);
241  // SimConnect_CloseView(m_hSimConnect, cameraName);
242  }
243 
245  {
246  if (simObject.getObserverName().isEmpty()) { return; }
247 
248  QByteArray viewName = simObject.getObserverName().toLatin1();
249  SimConnect_CloseView(m_hSimConnect, viewName.constData());
250  }
251 
252  bool CSimulatorP3D::releaseAIControl(const CSimConnectObject &simObject, SIMCONNECT_DATA_REQUEST_ID requestId)
253  {
254  // completely remove AI control
255  const SIMCONNECT_OBJECT_ID objectId = simObject.getObjectId();
256  const HRESULT hr1 =
257  this->logAndTraceSendId(SimConnect_AIReleaseControlEx(m_hSimConnect, objectId, requestId, TRUE), simObject,
258  "Release control", Q_FUNC_INFO, "SimConnect_AIReleaseControlEx");
259  const HRESULT hr2 =
260  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFreezeLatLng, 1,
261  SIMCONNECT_GROUP_PRIORITY_HIGHEST,
262  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
263  simObject, "EventFreezeLatLng", Q_FUNC_INFO, "SimConnect_TransmitClientEvent");
264  const HRESULT hr3 =
265  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFreezeAlt, 1,
266  SIMCONNECT_GROUP_PRIORITY_HIGHEST,
267  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
268  simObject, "EventFreezeAlt", Q_FUNC_INFO, "SimConnect_TransmitClientEvent");
269  const HRESULT hr4 =
270  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFreezeAtt, 1,
271  SIMCONNECT_GROUP_PRIORITY_HIGHEST,
272  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
273  simObject, "EventFreezeAtt", Q_FUNC_INFO, "SimConnect_TransmitClientEvent");
274 
275  return isOk(hr1, hr2, hr3, hr4);
276  }
277 #else
278  HRESULT CSimulatorP3D::initEventsP3D() { return s_ok(); }
279 
280  void CSimulatorP3D::SimConnectProc(SIMCONNECT_RECV *pData, DWORD cbData, void *pContext)
281  {
282  CSimulatorFsxCommon::SimConnectProc(pData, cbData, pContext);
283  }
284 #endif
285 
287  {
288 #ifdef Q_OS_WIN64
289  if (!loadAndResolveP3DSimConnectByString(m_p3dVersion.get())) { return; }
290  CSimulatorFsxCommonListener::startImpl();
291 #else
292  if (!loadAndResolveFsxSimConnect(true)) { return; }
293  CSimulatorFsxCommonListener::startImpl();
294 #endif
295  }
296 
297 } // namespace swift::simplugin::p3d
virtual void callbackReceivedRequestedElevation(const swift::misc::geo::CElevationPlane &plane, const swift::misc::aviation::CCallsign &callsign, bool isWater)
A requested elevation has been received.
Definition: simulator.cpp:254
virtual bool followAircraft(const swift::misc::aviation::CCallsign &callsign)
Follow aircraft.
Definition: simulator.cpp:112
virtual bool isShuttingDown() const
Is overall (swift) application shutting down.
Definition: simulator.h:211
void requestedElevation(const swift::misc::aviation::CCallsign &callsign)
Requested elevation, call pending.
virtual bool isShuttingDownOrDisconnected() const
Shutting down or disconnected?
Definition: simulator.h:214
swift::misc::aviation::CAircraftSituationPerCallsign m_lastSentSituations
last situations sent to simulator
Definition: simulator.h:582
T get() const
Get a copy of the current value.
Definition: valuecache.h:408
Class for emitting a log message.
Definition: logmessage.h:27
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
Streamable status message, e.g.
Value object for ICAO classification.
Value object encapsulating information of an aircraft's situation.
virtual bool isNull() const
Null situation.
Altitude as used in aviation, can be AGL or MSL altitude.
Definition: altitude.h:52
Value object encapsulating information of a callsign.
Definition: callsign.h:30
const QString & asString() const
Get callsign (normalized)
Definition: callsign.h:96
Plane of same elevation, can be a single point or larger area (e.g. airport)
Geodetic coordinate, a position in 3D space relative to the reference geoid.
bool hasMSLGeodeticHeight() const
Geodetic height not null and aviation::CAltitude::MeanSeaLevel.
virtual CLongitude longitude() const =0
Longitude.
virtual bool isNull() const
Is null, means vector x, y, z == 0.
virtual const aviation::CAltitude & geodeticHeight() const =0
Height, ellipsoidal or geodetic height (used in GPS)
virtual CLatitude latitude() const =0
Latitude.
Direct in memory access to client (network client) data.
double value(MU unit) const
Value in given unit.
Aircraft model (used by another pilot, my models on disk)
Definition: aircraftmodel.h:71
Direct threadsafe in memory access to own aircraft.
Direct thread safe in memory access to remote aircraft.
void setDefaultModel(const CAircraftModel &defaultModel)
Default model.
Class representing a SimConnect object.
DWORD getObjectId() const
Get SimConnect object id.
bool hasValidRequestAndObjectId() const
Was the object really added to simulator.
const QByteArray & getCallsignByteArray() const
Callsign as LATIN1.
void setObserverName(const QString &observer)
Set observer.
const QString & getObserverName() const
Observer name.
SIMCONNECT_DATA_REQUEST_ID obtainRequestIdForSimObjTerrainProbe()
Get new request id, overflow safe.
CSimConnectObjects m_simConnectObjects
AI objects and their object and request ids.
QMap< DWORD, swift::misc::aviation::CCallsign > m_pendingProbeRequests
pending elevation requests: requestId/aircraft callsign
virtual bool requestElevation(const swift::misc::geo::ICoordinateGeodetic &reference, const swift::misc::aviation::CCallsign &aircraftCallsign)
Request elevation, there is no guarantee the requested elevation will be available in the provider.
HRESULT logAndTraceSendId(HRESULT hr, const QString &warningMsg, const QString &functionName, const QString &functionDetails={})
Trace if required, log errors.
virtual void removeCamera(CSimConnectObject &simObject)
Remove camera if any.
HANDLE m_hSimConnect
handle to SimConnect object
DispatchProc m_dispatchProc
called function for dispatch, can be overriden by specialized P3D function
virtual void removeObserver(CSimConnectObject &simObject)
Remove observer if any.
virtual bool releaseAIControl(const CSimConnectObject &simObject, SIMCONNECT_DATA_REQUEST_ID requestId)
Release AI control.
swift::misc::aviation::CCallsign getCallsignForPendingProbeRequests(DWORD requestId, bool remove)
Callsign for pending request.
void setUsingFsxTerrainProbe(bool use)
FSX terrain probe.
virtual bool isConnected() const
Are we connected to the simulator?
static SIMCONNECT_DATA_LATLONALT coordinateToFsxLatLonAlt(const swift::misc::geo::ICoordinateGeodetic &coordinate)
Format conversion.
P3D Simulator Implementation.
Definition: simulatorp3d.h:25
static void CALLBACK SimConnectProc(SIMCONNECT_RECV *pData, DWORD cbData, void *pContext)
SimConnect Callback.
virtual HRESULT initEventsP3D()
Specific P3D events.
virtual bool connectTo()
Connect to simulator.
virtual void startImpl()
Plugin specific implementation to start listener.
Backend services of the swift project, like dealing with the network or the simulators.
Definition: actionbind.cpp:7
Free functions in swift::misc.
FSXCOMMON_EXPORT bool loadAndResolveFsxSimConnect(bool manifestProbing)
Load and resolve FSX SimConnect.
unsigned long DWORD
Fake Windows DWORD.
HRESULT s_ok()
Correctly casted values/checks.
bool isFailure(HRESULT result)
Correctly casted values/checks.
HRESULT s_false()
Correctly casted values/checks.
adding struct SIMCONNECT_DATA_PBH not existing in SimConnect FSX