1use std::{
6 ffi::{c_char, c_void, CStr, CString, NulError},
7 marker::PhantomData,
8 path::{Path, PathBuf},
9};
10
11use snafu::prelude::*;
12use xplane_sys::{
13 XPLMCreateInstance, XPLMCreateProbe, XPLMDestroyProbe, XPLMLoadObject, XPLMLoadObjectAsync,
14 XPLMLookupObjects, XPLMObjectRef, XPLMProbeInfo_t, XPLMProbeRef, XPLMProbeResult,
15 XPLMProbeTerrainXYZ, XPLMProbeType, XPLMReloadScenery, XPLMUnloadObject,
16};
17
18#[cfg(feature = "XPLM300")]
19use xplane_sys::{XPLMDegMagneticToDegTrue, XPLMDegTrueToDegMagnetic, XPLMGetMagneticVariation};
20
21#[cfg(feature = "XPLM303")]
22use crate::obj_instance::Instance;
23
24use crate::NoSendSync;
25
26pub struct TerrainProbe {
30 handle: XPLMProbeRef,
31 _typ: XPLMProbeType,
32 _phantom: NoSendSync,
33}
34
35impl TerrainProbe {
36 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
37 pub fn probe_terrain(
49 &mut self,
50 x: f32,
51 y: f32,
52 z: f32,
53 ) -> Result<Option<XPLMProbeInfo_t>, ProbeError> {
54 let mut probe_info = XPLMProbeInfo_t {
55 structSize: std::mem::size_of::<XPLMProbeInfo_t>() as i32,
56 locationX: 0.0,
57 locationY: 0.0,
58 locationZ: 0.0,
59 normalX: 0.0,
60 normalY: 0.0,
61 normalZ: 0.0,
62 velocityX: 0.0,
63 velocityY: 0.0,
64 velocityZ: 0.0,
65 is_wet: 0,
66 };
67 match unsafe { XPLMProbeTerrainXYZ(self.handle, x, y, z, &mut probe_info) } {
68 XPLMProbeResult::Missed => Ok(None),
69 XPLMProbeResult::HitTerrain => Ok(Some(probe_info)),
70 XPLMProbeResult::Error => Err(ProbeError),
71 _ => panic!("XPLMProbeTerrainXYZ has returned an invalid result!"),
72 }
73 }
74}
75
76impl Drop for TerrainProbe {
77 fn drop(&mut self) {
78 unsafe {
79 XPLMDestroyProbe(self.handle);
80 }
81 }
82}
83
84#[derive(Debug, Snafu)]
85#[snafu(display("The terrain probe did not succeed."))]
86pub struct ProbeError;
89
90pub struct XObject {
92 handle: XPLMObjectRef,
93 path: PathBuf,
94 _phantom: NoSendSync,
95}
96
97impl XObject {
98 #[must_use]
99 pub fn path(&self) -> &Path {
101 &self.path
102 }
103
104 #[must_use]
105 pub fn try_clone(&self) -> Option<Self> {
109 let path_c =
110 std::ffi::CString::new(self.path.as_os_str().to_string_lossy().into_owned()).ok()?;
111 let handle = unsafe { XPLMLoadObject(path_c.as_ptr()) };
112 if handle.is_null() {
113 None
114 } else {
115 Some(Self {
116 handle,
117 path: self.path.clone(),
118 _phantom: PhantomData,
119 })
120 }
121 }
122
123 #[cfg(feature = "XPLM303")]
124 pub fn new_instance<const NUM_DATAREFS: usize, S: Into<Vec<u8>>>(
130 self,
131 datarefs: [S; NUM_DATAREFS],
132 ) -> Result<Instance<NUM_DATAREFS>, NulError> {
133 let datarefs = datarefs
134 .into_iter()
135 .map(|s| CString::new(s))
136 .collect::<Result<Vec<_>, _>>()?;
137 let mut dr_ptrs: Vec<_> = datarefs
138 .iter()
139 .map(|dr| dr.as_ptr())
140 .chain([std::ptr::null()])
141 .collect();
142 let handle = unsafe { XPLMCreateInstance(self.handle, dr_ptrs.as_mut_ptr()) };
143 Ok(Instance {
144 handle,
145 _phantom: PhantomData,
146 })
147 }
148}
149
150impl Drop for XObject {
151 fn drop(&mut self) {
152 unsafe {
153 XPLMUnloadObject(self.handle);
154 }
155 }
156}
157
158struct ObjectLoadContext<C>
159where
160 C: FnOnce(Option<XObject>),
161{
162 obj_path: PathBuf,
163 callback: C,
164}
165
166pub struct SceneryApi {
168 pub(crate) _phantom: NoSendSync,
169}
170
171impl SceneryApi {
172 pub fn new_terrain_probe(&mut self, typ: XPLMProbeType) -> TerrainProbe {
174 let handle = unsafe { XPLMCreateProbe(typ) };
175 TerrainProbe {
176 handle,
177 _typ: typ,
178 _phantom: PhantomData,
179 }
180 }
181
182 #[cfg(feature = "XPLM300")]
183 pub fn get_magnetic_variation(&mut self, lat: f64, lon: f64) -> f32 {
185 unsafe { XPLMGetMagneticVariation(lat, lon) }
186 }
187
188 #[cfg(feature = "XPLM300")]
189 pub fn deg_true_to_mag(&mut self, deg: f32) -> f32 {
192 unsafe { XPLMDegTrueToDegMagnetic(deg) }
193 }
194
195 #[cfg(feature = "XPLM300")]
196 pub fn deg_mag_to_true(&mut self, deg: f32) -> f32 {
199 unsafe { XPLMDegMagneticToDegTrue(deg) }
200 }
201
202 pub fn lookup_objects<P: AsRef<Path>>(
210 &mut self,
211 path: P,
212 lat: f32,
213 lon: f32,
214 ) -> Result<Vec<PathBuf>, NulError> {
215 let mut objects = Vec::new();
216 let objects_ptr: *mut _ = &mut objects;
217 let objects_ptr: *mut c_void = objects_ptr.cast();
218 let path_c =
219 std::ffi::CString::new(path.as_ref().as_os_str().to_string_lossy().into_owned())?;
220
221 unsafe {
222 XPLMLookupObjects(
223 path_c.as_ptr(),
224 lat,
225 lon,
226 Some(library_enumerator),
227 objects_ptr,
228 );
229 }
230
231 Ok(objects)
232 }
233
234 pub fn load_object(&mut self, path: PathBuf) -> Result<Option<XObject>, NulError> {
240 let path_c = std::ffi::CString::new(path.as_os_str().to_string_lossy().into_owned())?;
241 let handle = unsafe { XPLMLoadObject(path_c.as_ptr()) };
242 if handle.is_null() {
243 Ok(None)
244 } else {
245 Ok(Some(XObject {
246 handle,
247 path,
248 _phantom: PhantomData,
249 }))
250 }
251 }
252
253 pub fn load_object_async<C>(&mut self, path: PathBuf, callback: C) -> Result<(), NulError>
263 where
264 C: FnOnce(Option<XObject>),
265 {
266 let path_c = std::ffi::CString::new(path.as_os_str().to_string_lossy().into_owned())?;
267
268 let ctx = Box::into_raw(Box::new(ObjectLoadContext {
269 obj_path: path,
270 callback,
271 }));
272
273 unsafe {
274 XPLMLoadObjectAsync(
275 path_c.as_ptr(),
276 Some(object_loaded_callback::<C>),
277 ctx.cast::<c_void>(),
278 );
279 }
280 Ok(())
281 }
282
283 pub fn reload_scenery(&mut self) {
288 unsafe {
289 XPLMReloadScenery();
290 }
291 }
292}
293
294unsafe extern "C-unwind" fn library_enumerator(file_path: *const c_char, refcon: *mut c_void) {
295 let out = unsafe {
296 refcon.cast::<Vec<PathBuf>>().as_mut().unwrap() };
298 let file_path = unsafe { CStr::from_ptr(file_path) };
299 let file_path = file_path.to_owned();
300 let file_path = file_path.into_string().unwrap(); let file_path = PathBuf::from(file_path);
302 out.push(file_path);
303}
304
305unsafe extern "C-unwind" fn object_loaded_callback<C>(obj: XPLMObjectRef, refcon: *mut c_void)
306where
307 C: FnOnce(Option<XObject>),
308{
309 let ctx = unsafe { Box::from_raw(refcon.cast::<ObjectLoadContext<C>>()) };
310 let obj = if obj.is_null() {
311 None
312 } else {
313 Some(XObject {
314 handle: obj,
315 path: ctx.obj_path,
316 _phantom: PhantomData,
317 })
318 };
319 (ctx.callback)(obj);
320}