1use std::sync::OnceLock;
3
4use crate::channel::{channel_singleton, Channel};
5use crate::ffi::CameraInfoMeta;
6use crate::sensor::Sensor;
7
8pub struct CameraInfo;
10
11impl Sensor for CameraInfo {
12 const NAME: &'static str = "camera_info";
13}
14
15#[allow(missing_docs)]
19pub struct CameraInfoFrame<'a> {
20 pub frame_id: &'a str,
21 pub distortion_model: &'a str,
22 pub projection_type: &'a str,
23 pub k: &'a [f64],
24 pub r: &'a [f64],
25 pub p: &'a [f64],
26 pub distortion: &'a [f32],
27 pub meta: &'a CameraInfoMeta,
28}
29
30pub type Callback = Box<dyn Fn(&str, &CameraInfoFrame<'_>) + Send + Sync + 'static>;
31
32#[unsafe(no_mangle)]
33pub extern "C" fn isaac_sim_bridge_channel_camera_info() -> *const Channel<Callback> {
34 static SLOT: OnceLock<Box<Channel<Callback>>> = OnceLock::new();
35 channel_singleton(&SLOT)
36}
37
38fn channel() -> &'static Channel<Callback> {
39 unsafe { &*isaac_sim_bridge_channel_camera_info() }
40}
41
42pub fn register_camera_info_consumer<F>(cb: F)
45where
46 F: Fn(&str, &CameraInfoFrame<'_>) + Send + Sync + 'static,
47{
48 channel().register(Box::new(cb));
49}
50
51pub fn dispatch_camera_info(source_id: &str, frame: &CameraInfoFrame<'_>) {
53 channel().for_each(|cb| cb(source_id, frame));
54}
55
56pub fn camera_info_consumer_count() -> usize {
58 channel().count()
59}
60
61#[allow(clippy::too_many_arguments)]
64pub fn forward_camera_info(
65 source_id: &str,
66 frame_id: &str,
67 distortion_model: &str,
68 projection_type: &str,
69 k: &[f64],
70 r: &[f64],
71 p: &[f64],
72 distortion: &[f32],
73 meta: &CameraInfoMeta,
74) {
75 log::debug!(
76 "[isaac-sim-rs] forward_camera_info: source='{}' frame='{}' wxh={}x{} k={} r={} p={} d={} model='{}' proj='{}'",
77 source_id,
78 frame_id,
79 meta.width,
80 meta.height,
81 k.len(),
82 r.len(),
83 p.len(),
84 distortion.len(),
85 distortion_model,
86 projection_type,
87 );
88 let frame = CameraInfoFrame {
89 frame_id,
90 distortion_model,
91 projection_type,
92 k,
93 r,
94 p,
95 distortion,
96 meta,
97 };
98 dispatch_camera_info(source_id, &frame);
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use std::sync::atomic::{AtomicUsize, Ordering};
105 use std::sync::Arc;
106
107 fn fake_meta(w: i32, h: i32) -> CameraInfoMeta {
108 CameraInfoMeta {
109 width: w,
110 height: h,
111 timestamp_ns: 0,
112 }
113 }
114
115 #[test]
116 fn registered_consumer_receives_dispatch_with_source() {
117 let count = Arc::new(AtomicUsize::new(0));
118 let count_clone = Arc::clone(&count);
119 let n_baseline = camera_info_consumer_count();
120
121 register_camera_info_consumer(move |src, frame| {
122 assert_eq!(src, "/World/Camera");
123 assert_eq!(frame.frame_id, "sim_camera");
124 assert_eq!(frame.k.len(), 9);
125 assert_eq!(frame.p.len(), 12);
126 assert_eq!(frame.meta.width, 640);
127 assert_eq!(frame.meta.height, 480);
128 assert_eq!(frame.distortion_model, "plumb_bob");
129 count_clone.fetch_add(1, Ordering::SeqCst);
130 });
131
132 assert_eq!(camera_info_consumer_count(), n_baseline + 1);
133
134 let k = [500.0, 0.0, 320.0, 0.0, 500.0, 240.0, 0.0, 0.0, 1.0];
135 let r = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
136 let p = [
137 500.0, 0.0, 320.0, 0.0, 0.0, 500.0, 240.0, 0.0, 0.0, 0.0, 1.0, 0.0,
138 ];
139 let d = [0.0_f32, 0.0, 0.0, 0.0, 0.0];
140 let meta = fake_meta(640, 480);
141 forward_camera_info(
142 "/World/Camera",
143 "sim_camera",
144 "plumb_bob",
145 "pinhole",
146 &k,
147 &r,
148 &p,
149 &d,
150 &meta,
151 );
152
153 assert_eq!(count.load(Ordering::SeqCst), 1);
154 }
155}