1use rand::prelude::*;
4use phyz_math::{GRAVITY, Mat3, SpatialInertia, SpatialTransform, Vec3};
5use phyz_model::{Model, ModelBuilder};
6
7pub struct WorldGenerator {
9 #[allow(dead_code)]
10 seed: u64,
11 rng: StdRng,
12}
13
14impl WorldGenerator {
15 pub fn new(seed: u64) -> Self {
17 Self {
18 seed,
19 rng: StdRng::seed_from_u64(seed),
20 }
21 }
22
23 pub fn random_chain(
28 &mut self,
29 nlinkages: usize,
30 mass_range: [f64; 2],
31 length_range: [f64; 2],
32 ) -> Model {
33 let mut builder = ModelBuilder::new()
34 .gravity(Vec3::new(0.0, 0.0, -GRAVITY))
35 .dt(0.001);
36
37 for i in 0..nlinkages {
38 let parent = if i == 0 { -1 } else { (i - 1) as i32 };
39 let mass = self.rng.gen_range(mass_range[0]..=mass_range[1]);
40 let length = self.rng.gen_range(length_range[0]..=length_range[1]);
41
42 let parent_to_joint = if i == 0 {
43 SpatialTransform::identity()
44 } else {
45 SpatialTransform::translation(Vec3::new(0.0, 0.0, -length))
47 };
48
49 let com = Vec3::new(0.0, 0.0, -length / 2.0);
52 let inertia_val = mass * length * length / 3.0;
53 let inertia = SpatialInertia::new(
54 mass,
55 com,
56 Mat3::from_diagonal(&Vec3::new(inertia_val, inertia_val, 0.0)),
57 );
58
59 builder =
60 builder.add_revolute_body(&format!("link{}", i), parent, parent_to_joint, inertia);
61 }
62
63 builder.build()
64 }
65
66 pub fn random_quadruped(&mut self) -> Model {
71 let body_mass = 5.0;
72 let body_size = 0.4;
73 let leg_mass = 0.5;
74 let leg_length = 0.3;
75
76 let mut builder = ModelBuilder::new()
77 .gravity(Vec3::new(0.0, 0.0, -GRAVITY))
78 .dt(0.001);
79
80 let torso_inertia = SpatialInertia::new(
82 body_mass,
83 Vec3::zeros(),
84 Mat3::from_diagonal(&Vec3::new(
85 body_mass * body_size * body_size / 6.0,
86 body_mass * body_size * body_size / 6.0,
87 body_mass * body_size * body_size / 6.0,
88 )),
89 );
90 builder = builder.add_free_body("torso", -1, SpatialTransform::identity(), torso_inertia);
91
92 let leg_positions = [
94 Vec3::new(body_size / 2.0, body_size / 2.0, 0.0),
95 Vec3::new(body_size / 2.0, -body_size / 2.0, 0.0),
96 Vec3::new(-body_size / 2.0, body_size / 2.0, 0.0),
97 Vec3::new(-body_size / 2.0, -body_size / 2.0, 0.0),
98 ];
99
100 for (leg_idx, leg_pos) in leg_positions.iter().enumerate() {
101 let upper_leg_inertia = SpatialInertia::new(
103 leg_mass,
104 Vec3::new(0.0, 0.0, -leg_length / 2.0),
105 Mat3::from_diagonal(&Vec3::new(
106 leg_mass * leg_length * leg_length / 12.0,
107 leg_mass * leg_length * leg_length / 12.0,
108 0.0,
109 )),
110 );
111 let upper_joint = SpatialTransform::translation(*leg_pos);
112 builder = builder.add_revolute_body(
113 &format!("leg{}_upper", leg_idx),
114 0, upper_joint,
116 upper_leg_inertia,
117 );
118
119 let lower_leg_inertia = upper_leg_inertia;
121 let lower_joint = SpatialTransform::translation(Vec3::new(0.0, 0.0, -leg_length));
122 let upper_body_idx = 1 + leg_idx * 2;
123 builder = builder.add_revolute_body(
124 &format!("leg{}_lower", leg_idx),
125 upper_body_idx as i32,
126 lower_joint,
127 lower_leg_inertia,
128 );
129 }
130
131 builder.build()
132 }
133
134 pub fn platform_with_obstacles(
139 &mut self,
140 width: f64,
141 nobs: usize,
142 obs_height_range: [f64; 2],
143 ) -> Model {
144 let mut builder = ModelBuilder::new()
145 .gravity(Vec3::new(0.0, 0.0, -GRAVITY))
146 .dt(0.001);
147
148 let platform_inertia = SpatialInertia::new(
150 1000.0, Vec3::zeros(),
152 Mat3::identity(),
153 );
154 builder = builder.add_fixed_body(
155 "platform",
156 -1,
157 SpatialTransform::identity(),
158 platform_inertia,
159 );
160
161 for i in 0..nobs {
163 let x = self.rng.gen_range(-width / 2.0..width / 2.0);
164 let y = self.rng.gen_range(-width / 2.0..width / 2.0);
165 let height = self
166 .rng
167 .gen_range(obs_height_range[0]..=obs_height_range[1]);
168
169 let obs_pos = Vec3::new(x, y, height / 2.0);
170 let obs_inertia = SpatialInertia::new(
171 10.0,
172 Vec3::zeros(),
173 Mat3::from_diagonal(&Vec3::new(0.1, 0.1, 0.1)),
174 );
175
176 builder = builder.add_fixed_body(
177 &format!("obstacle{}", i),
178 0, SpatialTransform::translation(obs_pos),
180 obs_inertia,
181 );
182 }
183
184 builder.build()
185 }
186
187 pub fn random_tree(&mut self, depth: usize, branching_factor: usize) -> Model {
192 let mut builder = ModelBuilder::new()
193 .gravity(Vec3::new(0.0, 0.0, -GRAVITY))
194 .dt(0.001);
195
196 let mass = 1.0;
197 let length = 0.5;
198
199 let mut body_count: i32 = 0;
201 let mut current_level = vec![(None, 0)]; for level in 0..=depth {
204 let mut next_level = Vec::new();
205
206 for (parent_opt, _) in current_level {
207 let num_children = if level == 0 { 1 } else { branching_factor };
208
209 for i in 0..num_children {
210 let parent_idx = parent_opt.unwrap_or(-1);
211
212 let (offset, name) = if level == 0 {
213 (SpatialTransform::identity(), "root".to_string())
214 } else {
215 let angle = 2.0 * std::f64::consts::PI * i as f64 / branching_factor as f64;
216 let offset = Vec3::new(
217 length * angle.cos() * 0.3,
218 length * angle.sin() * 0.3,
219 -length,
220 );
221 (
222 SpatialTransform::translation(offset),
223 format!("node_{}_{}", body_count, i),
224 )
225 };
226
227 let inertia = SpatialInertia::new(
228 mass * 0.8_f64.powi(level as i32),
229 Vec3::new(0.0, 0.0, -length / 2.0),
230 Mat3::from_diagonal(&Vec3::new(
231 0.1 * 0.8_f64.powi(level as i32),
232 0.1 * 0.8_f64.powi(level as i32),
233 0.0,
234 )),
235 );
236
237 builder = builder.add_revolute_body(&name, parent_idx, offset, inertia);
238
239 if level < depth {
240 next_level.push((Some(body_count), level + 1));
241 }
242
243 body_count += 1;
244 }
245 }
246
247 current_level = next_level;
248 }
249
250 builder.build()
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_random_chain() {
260 let mut generator = WorldGenerator::new(42);
261 let model = generator.random_chain(5, [0.5, 2.0], [0.3, 1.5]);
262 assert_eq!(model.nbodies(), 5);
263 assert_eq!(model.nq, 5);
264 assert_eq!(model.nv, 5);
265 }
266
267 #[test]
268 fn test_random_quadruped() {
269 let mut generator = WorldGenerator::new(42);
270 let model = generator.random_quadruped();
271 assert_eq!(model.nbodies(), 9);
273 assert_eq!(model.nv, 14);
275 }
276
277 #[test]
278 fn test_platform_with_obstacles() {
279 let mut generator = WorldGenerator::new(42);
280 let model = generator.platform_with_obstacles(10.0, 5, [0.5, 2.0]);
281 assert_eq!(model.nbodies(), 6);
283 assert_eq!(model.nv, 0);
285 }
286
287 #[test]
288 fn test_random_tree() {
289 let mut generator = WorldGenerator::new(42);
290 let model = generator.random_tree(2, 2);
291 assert_eq!(model.nbodies(), 7);
293 }
294
295 #[test]
296 fn test_deterministic_generation() {
297 let mut gen1 = WorldGenerator::new(123);
298 let mut gen2 = WorldGenerator::new(123);
299
300 let m1 = gen1.random_chain(3, [0.5, 1.5], [0.5, 1.0]);
301 let m2 = gen2.random_chain(3, [0.5, 1.5], [0.5, 1.0]);
302
303 for i in 0..3 {
305 assert_eq!(m1.bodies[i].inertia.mass, m2.bodies[i].inertia.mass);
306 }
307 }
308}