1use crate::{Sensor, SensorOutput, Tendon};
4use phyz_model::{Model, State};
5
6pub struct World {
8 pub model: Model,
10 pub state: State,
12 pub sensors: Vec<Sensor>,
14 pub sensor_history: Vec<Vec<SensorOutput>>,
16 pub tendons: Vec<Tendon>,
18}
19
20impl World {
21 pub fn new(model: Model) -> Self {
23 let state = model.default_state();
24 Self {
25 model,
26 state,
27 sensors: Vec::new(),
28 sensor_history: Vec::new(),
29 tendons: Vec::new(),
30 }
31 }
32
33 pub fn with_state(model: Model, state: State) -> Self {
35 Self {
36 model,
37 state,
38 sensors: Vec::new(),
39 sensor_history: Vec::new(),
40 tendons: Vec::new(),
41 }
42 }
43
44 pub fn add_sensor(&mut self, sensor: Sensor) {
46 self.sensors.push(sensor);
47 }
48
49 pub fn add_tendon(&mut self, tendon: Tendon) {
51 if !tendon.is_valid(&self.model) {
52 panic!("Invalid tendon: path contains out-of-bounds body indices");
53 }
54 self.tendons.push(tendon);
55 }
56
57 pub fn step<F>(&mut self, step_fn: F)
62 where
63 F: FnOnce(&Model, &mut State),
64 {
65 self.apply_tendon_forces();
67
68 step_fn(&self.model, &mut self.state);
70
71 let readings = self.read_sensors();
73 self.sensor_history.push(readings);
74 }
75
76 fn read_sensors(&self) -> Vec<SensorOutput> {
78 self.sensors
79 .iter()
80 .enumerate()
81 .map(|(id, sensor)| sensor.read(&self.model, &self.state, id))
82 .collect()
83 }
84
85 fn apply_tendon_forces(&mut self) {
89 for tendon in &self.tendons {
90 let forces = tendon.compute_forces(&self.state);
91
92 for (body_idx, _force) in forces {
96 if body_idx < self.model.nbodies() {
97 let joint_idx = self.model.bodies[body_idx].joint_idx;
98 let v_off = self.model.v_offsets[joint_idx];
99
100 if v_off < self.state.ctrl.len() {
103 let current_len = tendon.current_length(&self.state);
105 let stretch = current_len - tendon.rest_length;
106 let torque = -tendon.stiffness * stretch * 0.1; self.state.ctrl[v_off] += torque;
108 }
109 }
110 }
111 }
112 }
113
114 pub fn latest_sensor_readings(&self) -> Option<&Vec<SensorOutput>> {
116 self.sensor_history.last()
117 }
118
119 pub fn clear_history(&mut self) {
121 self.sensor_history.clear();
122 }
123
124 pub fn history_len(&self) -> usize {
126 self.sensor_history.len()
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use phyz_math::{SpatialInertia, SpatialTransform, Vec3};
134 use phyz_model::ModelBuilder;
135
136 #[test]
137 fn test_world_creation() {
138 let model = ModelBuilder::new()
139 .add_revolute_body(
140 "link",
141 -1,
142 SpatialTransform::identity(),
143 SpatialInertia::point_mass(1.0, Vec3::new(0.0, 0.0, -0.5)),
144 )
145 .build();
146
147 let world = World::new(model);
148 assert_eq!(world.sensors.len(), 0);
149 assert_eq!(world.tendons.len(), 0);
150 assert_eq!(world.history_len(), 0);
151 }
152
153 #[test]
154 fn test_world_with_sensor() {
155 let model = ModelBuilder::new()
156 .add_revolute_body(
157 "link",
158 -1,
159 SpatialTransform::identity(),
160 SpatialInertia::point_mass(1.0, Vec3::new(0.0, 0.0, -0.5)),
161 )
162 .build();
163
164 let mut world = World::new(model);
165 world.add_sensor(Sensor::JointState { joint_idx: 0 });
166
167 assert_eq!(world.sensors.len(), 1);
168 }
169
170 #[test]
171 fn test_world_step_with_sensors() {
172 let model = ModelBuilder::new()
173 .dt(0.01)
174 .add_revolute_body(
175 "link",
176 -1,
177 SpatialTransform::identity(),
178 SpatialInertia::point_mass(1.0, Vec3::new(0.0, 0.0, -0.5)),
179 )
180 .build();
181
182 let mut world = World::new(model);
183 world.add_sensor(Sensor::JointState { joint_idx: 0 });
184
185 for _ in 0..5 {
187 world.step(|_model, state| {
188 state.time += 0.01;
189 });
190 }
191
192 assert_eq!(world.history_len(), 5);
193 assert!(world.latest_sensor_readings().is_some());
194 }
195
196 #[test]
197 fn test_world_with_tendon() {
198 let model = ModelBuilder::new()
199 .add_revolute_body(
200 "link1",
201 -1,
202 SpatialTransform::identity(),
203 SpatialInertia::point_mass(1.0, Vec3::new(0.0, 0.0, -0.5)),
204 )
205 .add_revolute_body(
206 "link2",
207 0,
208 SpatialTransform::translation(Vec3::new(0.0, 0.0, -1.0)),
209 SpatialInertia::point_mass(1.0, Vec3::new(0.0, 0.0, -0.5)),
210 )
211 .build();
212
213 let mut world = World::new(model);
214 let tendon = Tendon::new(vec![0, 1], 100.0, 1.0, 10.0);
215 world.add_tendon(tendon);
216
217 assert_eq!(world.tendons.len(), 1);
218 }
219
220 #[test]
221 #[should_panic(expected = "Invalid tendon")]
222 fn test_world_invalid_tendon() {
223 let model = ModelBuilder::new()
224 .add_revolute_body(
225 "link",
226 -1,
227 SpatialTransform::identity(),
228 SpatialInertia::point_mass(1.0, Vec3::new(0.0, 0.0, -0.5)),
229 )
230 .build();
231
232 let mut world = World::new(model);
233 let tendon = Tendon::new(vec![0, 99], 100.0, 1.0, 10.0);
235 world.add_tendon(tendon);
236 }
237}