1use phyz_model::{Model, State};
4
5#[derive(Debug, Clone)]
7pub enum Sensor {
8 JointState { joint_idx: usize },
10 BodyAccel { body_idx: usize },
12 BodyAngularVel { body_idx: usize },
14 ForceTorque { body_idx: usize },
16 Rangefinder { body_idx: usize, max_dist: f64 },
18 Imu { body_idx: usize },
20 FrameCapture { body_idx: usize },
22}
23
24#[derive(Debug, Clone)]
26pub struct SensorOutput {
27 pub sensor_id: usize,
29 pub timestamp: f64,
31 pub data: Vec<f64>,
33}
34
35impl Sensor {
36 pub fn read(&self, model: &Model, state: &State, sensor_id: usize) -> SensorOutput {
40 let data = match self {
41 Sensor::JointState { joint_idx } => {
42 let q_off = model.q_offsets[*joint_idx];
43 let v_off = model.v_offsets[*joint_idx];
44 let ndof = model.joints[*joint_idx].ndof();
45
46 let mut out = Vec::with_capacity(ndof * 2);
47 for i in 0..ndof {
48 out.push(state.q[q_off + i]);
49 }
50 for i in 0..ndof {
51 out.push(state.v[v_off + i]);
52 }
53 out
54 }
55 Sensor::BodyAccel { body_idx: _ } => {
56 vec![0.0, 0.0, 0.0]
60 }
61 Sensor::BodyAngularVel { body_idx } => {
62 if *body_idx >= model.nbodies() {
66 vec![0.0, 0.0, 0.0]
67 } else {
68 let joint_idx = model.bodies[*body_idx].joint_idx;
69 let v_off = model.v_offsets[joint_idx];
70 let joint = &model.joints[joint_idx];
71
72 match joint.ndof() {
73 1 => {
74 let qd = state.v[v_off];
76 let w = joint.axis * qd;
77 vec![w.x, w.y, w.z]
78 }
79 3 | 6 => {
80 vec![state.v[v_off], state.v[v_off + 1], state.v[v_off + 2]]
82 }
83 _ => vec![0.0, 0.0, 0.0],
84 }
85 }
86 }
87 Sensor::ForceTorque { body_idx: _ } => {
88 vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
91 }
92 Sensor::Rangefinder {
93 body_idx: _,
94 max_dist,
95 } => {
96 vec![*max_dist]
99 }
100 Sensor::Imu { body_idx } => {
101 let mut imu_data = vec![0.0, 0.0, 0.0]; if *body_idx < model.nbodies() {
106 let joint_idx = model.bodies[*body_idx].joint_idx;
107 let v_off = model.v_offsets[joint_idx];
108 let joint = &model.joints[joint_idx];
109
110 let angular_vel = match joint.ndof() {
111 1 => {
112 let qd = state.v[v_off];
113 let w = joint.axis * qd;
114 vec![w.x, w.y, w.z]
115 }
116 3 | 6 => {
117 vec![state.v[v_off], state.v[v_off + 1], state.v[v_off + 2]]
118 }
119 _ => vec![0.0, 0.0, 0.0],
120 };
121 imu_data.extend(angular_vel);
122 } else {
123 imu_data.extend(vec![0.0, 0.0, 0.0]);
124 }
125
126 imu_data
127 }
128 Sensor::FrameCapture { body_idx } => {
129 if *body_idx < state.body_xform.len() {
131 let xform = &state.body_xform[*body_idx];
132 let pos = xform.pos;
133
134 let quat = mat3_to_quat(&xform.rot);
137
138 vec![pos.x, pos.y, pos.z, quat.0, quat.1, quat.2, quat.3]
139 } else {
140 vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]
141 }
142 }
143 };
144
145 SensorOutput {
146 sensor_id,
147 timestamp: state.time,
148 data,
149 }
150 }
151
152 pub fn output_dim(&self) -> usize {
154 match self {
155 Sensor::JointState { .. } => 2, Sensor::BodyAccel { .. } => 3,
157 Sensor::BodyAngularVel { .. } => 3,
158 Sensor::ForceTorque { .. } => 6,
159 Sensor::Rangefinder { .. } => 1,
160 Sensor::Imu { .. } => 6, Sensor::FrameCapture { .. } => 7, }
163 }
164}
165
166fn mat3_to_quat(mat: &phyz_math::Mat3) -> (f64, f64, f64, f64) {
169 let trace = mat[(0, 0)] + mat[(1, 1)] + mat[(2, 2)];
171
172 if trace > 0.0 {
173 let s = (trace + 1.0).sqrt() * 2.0;
174 let w = 0.25 * s;
175 let x = (mat[(2, 1)] - mat[(1, 2)]) / s;
176 let y = (mat[(0, 2)] - mat[(2, 0)]) / s;
177 let z = (mat[(1, 0)] - mat[(0, 1)]) / s;
178 (w, x, y, z)
179 } else if mat[(0, 0)] > mat[(1, 1)] && mat[(0, 0)] > mat[(2, 2)] {
180 let s = (1.0 + mat[(0, 0)] - mat[(1, 1)] - mat[(2, 2)]).sqrt() * 2.0;
181 let w = (mat[(2, 1)] - mat[(1, 2)]) / s;
182 let x = 0.25 * s;
183 let y = (mat[(0, 1)] + mat[(1, 0)]) / s;
184 let z = (mat[(0, 2)] + mat[(2, 0)]) / s;
185 (w, x, y, z)
186 } else if mat[(1, 1)] > mat[(2, 2)] {
187 let s = (1.0 + mat[(1, 1)] - mat[(0, 0)] - mat[(2, 2)]).sqrt() * 2.0;
188 let w = (mat[(0, 2)] - mat[(2, 0)]) / s;
189 let x = (mat[(0, 1)] + mat[(1, 0)]) / s;
190 let y = 0.25 * s;
191 let z = (mat[(1, 2)] + mat[(2, 1)]) / s;
192 (w, x, y, z)
193 } else {
194 let s = (1.0 + mat[(2, 2)] - mat[(0, 0)] - mat[(1, 1)]).sqrt() * 2.0;
195 let w = (mat[(1, 0)] - mat[(0, 1)]) / s;
196 let x = (mat[(0, 2)] + mat[(2, 0)]) / s;
197 let y = (mat[(1, 2)] + mat[(2, 1)]) / s;
198 let z = 0.25 * s;
199 (w, x, y, z)
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use phyz_math::{SpatialInertia, SpatialTransform, Vec3};
207 use phyz_model::ModelBuilder;
208
209 #[test]
210 fn test_joint_state_sensor() {
211 let model = ModelBuilder::new()
212 .add_revolute_body(
213 "link",
214 -1,
215 SpatialTransform::identity(),
216 SpatialInertia::point_mass(1.0, Vec3::new(0.0, 0.0, -0.5)),
217 )
218 .build();
219
220 let mut state = model.default_state();
221 state.q[0] = 0.5;
222 state.v[0] = 1.0;
223
224 let sensor = Sensor::JointState { joint_idx: 0 };
225 let output = sensor.read(&model, &state, 0);
226
227 assert_eq!(output.data.len(), 2);
228 assert_eq!(output.data[0], 0.5);
229 assert_eq!(output.data[1], 1.0);
230 }
231
232 #[test]
233 fn test_body_angular_vel_sensor() {
234 let model = ModelBuilder::new()
235 .add_revolute_body(
236 "link",
237 -1,
238 SpatialTransform::identity(),
239 SpatialInertia::point_mass(1.0, Vec3::new(0.0, 0.0, -0.5)),
240 )
241 .build();
242
243 let mut state = model.default_state();
244 state.v[0] = 2.0;
245
246 let sensor = Sensor::BodyAngularVel { body_idx: 0 };
247 let output = sensor.read(&model, &state, 0);
248
249 assert_eq!(output.data.len(), 3);
250 assert!((output.data[2] - 2.0).abs() < 1e-10);
252 }
253
254 #[test]
255 fn test_imu_sensor() {
256 let model = ModelBuilder::new()
257 .add_revolute_body(
258 "link",
259 -1,
260 SpatialTransform::identity(),
261 SpatialInertia::point_mass(1.0, Vec3::new(0.0, 0.0, -0.5)),
262 )
263 .build();
264
265 let state = model.default_state();
266 let sensor = Sensor::Imu { body_idx: 0 };
267 let output = sensor.read(&model, &state, 0);
268
269 assert_eq!(output.data.len(), 6); }
271
272 #[test]
273 fn test_frame_capture_sensor() {
274 let model = ModelBuilder::new()
275 .add_revolute_body(
276 "link",
277 -1,
278 SpatialTransform::identity(),
279 SpatialInertia::point_mass(1.0, Vec3::new(0.0, 0.0, -0.5)),
280 )
281 .build();
282
283 let state = model.default_state();
284 let sensor = Sensor::FrameCapture { body_idx: 0 };
285 let output = sensor.read(&model, &state, 0);
286
287 assert_eq!(output.data.len(), 7); assert!((output.data[3] - 1.0).abs() < 0.1); }
291}