1use nalgebra::Matrix3;
2
3use crate::{
4 identifiers::GroupIDChanger,
5 link::{
6 builder::CollisionBuilder,
7 geometry::{GeometryInterface, GeometryShapeData},
8 visual::Visual,
9 },
10 material::MaterialDescriptor,
11 transform::{Mirror, Transform},
12};
13
14#[derive(Debug)]
32pub struct VisualBuilder {
33 pub(crate) name: Option<String>,
37 pub(crate) transform: Option<Transform>,
43 pub(crate) geometry: Box<dyn GeometryInterface + Sync + Send>,
45 pub(crate) material_description: Option<MaterialDescriptor>,
47}
48
49impl VisualBuilder {
50 pub fn new(geometry: impl Into<Box<dyn GeometryInterface + Sync + Send>>) -> Self {
52 Self {
53 name: None,
54 transform: None,
55 geometry: geometry.into(),
56 material_description: None,
57 }
58 }
59
60 pub fn new_full(
63 name: Option<String>,
64 transform: Option<Transform>,
65 geometry: impl Into<Box<dyn GeometryInterface + Sync + Send>>,
66 material_description: Option<MaterialDescriptor>,
67 ) -> Self {
68 Self {
69 name,
70 transform,
71 geometry: geometry.into(),
72 material_description,
73 }
74 }
75
76 pub fn named(mut self, name: impl Into<String>) -> Self {
78 self.name = Some(name.into());
79 self
80 }
81
82 pub fn transformed(mut self, transform: Transform) -> Self {
86 self.transform = Some(transform);
87 self
88 }
89
90 pub fn materialized(mut self, material_description: MaterialDescriptor) -> Self {
94 self.material_description = Some(material_description);
95 self
96 }
97
98 pub fn to_collision(&self) -> CollisionBuilder {
106 CollisionBuilder {
107 name: self.name.clone(),
108 transform: self.transform,
109 geometry: self.geometry.boxed_clone(),
110 }
111 }
112
113 pub(crate) fn build(self) -> Visual {
114 let material = self
115 .material_description
116 .map(|description| description.build());
117
118 Visual {
119 name: self.name,
120 transform: self.transform,
121 geometry: self.geometry,
122 material,
123 }
124 }
125
126 pub(crate) fn get_geometry_data(&self) -> GeometryShapeData {
127 GeometryShapeData {
128 transform: self.transform.unwrap_or_default(),
129 geometry: self.geometry.shape_container(),
130 }
131 }
132}
133
134impl Mirror for VisualBuilder {
135 fn mirrored(&self, mirror_matrix: &Matrix3<f32>) -> Self {
136 Self {
137 name: self.name.as_ref().cloned(), transform: self
139 .transform
140 .as_ref()
141 .map(|transform| transform.mirrored(mirror_matrix)),
142 geometry: self.geometry.boxed_mirrored(mirror_matrix),
143 material_description: self.material_description.clone(),
144 }
145 }
146}
147
148impl VisualBuilder {
150 pub fn name(&self) -> Option<&String> {
152 self.name.as_ref()
153 }
154
155 pub fn transform(&self) -> Option<&Transform> {
157 self.transform.as_ref()
158 }
159
160 pub fn geometry(&self) -> &Box<dyn GeometryInterface + Sync + Send> {
162 &self.geometry
163 }
164
165 pub fn material(&self) -> Option<&MaterialDescriptor> {
167 self.material_description.as_ref()
168 }
169}
170
171impl GroupIDChanger for VisualBuilder {
172 unsafe fn change_group_id_unchecked(&mut self, new_group_id: &str) {
173 if let Some(name) = self.name.as_mut() {
174 name.change_group_id_unchecked(new_group_id);
175 }
176
177 if let Some(material_builder) = self.material_description.as_mut() {
178 material_builder.change_group_id_unchecked(new_group_id);
179 }
180 }
181
182 fn apply_group_id(&mut self) {
183 if let Some(name) = self.name.as_mut() {
184 name.apply_group_id();
185 }
186
187 if let Some(material_builder) = self.material_description.as_mut() {
188 material_builder.apply_group_id();
189 }
190 }
191}
192
193impl PartialEq for VisualBuilder {
194 fn eq(&self, other: &Self) -> bool {
195 self.name == other.name
196 && self.transform == other.transform
197 && *self.geometry == *other.geometry
198 && self.material_description == other.material_description
199 }
200}
201
202impl Clone for VisualBuilder {
203 fn clone(&self) -> Self {
204 Self {
205 name: self.name.clone(),
206 transform: self.transform,
207 geometry: self.geometry.boxed_clone(),
208 material_description: self.material_description.clone(),
209 }
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::VisualBuilder;
216 use crate::link::link_data::geometry::{BoxGeometry, CylinderGeometry, SphereGeometry};
217 use test_log::test;
218 mod group_id_changer {
220 use super::{test, BoxGeometry, CylinderGeometry, SphereGeometry, VisualBuilder};
221 use crate::identifiers::{GroupIDChanger, GroupIDError};
222
223 #[test]
224 fn change_group_id_unchecked_no_material() {
225 #[inline]
226 fn test(collision_builder: VisualBuilder, new_group_id: &str, name: Option<&str>) {
227 let mut visual_builder = collision_builder;
228 unsafe {
229 visual_builder.change_group_id_unchecked(new_group_id);
230 }
231 assert_eq!(
232 visual_builder.name,
233 name.and_then(|name| Some(name.to_owned()))
234 )
235 }
236
237 test(VisualBuilder::new(BoxGeometry::new(1., 2., 3.)), "7", None);
239 test(
240 VisualBuilder::new(CylinderGeometry::new(32., 5.)),
241 "[[invalid]]",
242 None,
243 );
244 test(VisualBuilder::new(SphereGeometry::new(3.3e9)), "", None);
245
246 test(
248 VisualBuilder::new(BoxGeometry::new(1., 2., 3.)).named("ThisCoolName"),
249 "7",
250 Some("ThisCoolName"),
251 );
252 test(
253 VisualBuilder::new(CylinderGeometry::new(32., 5.)).named("ADAdsadsdasdDS[]"),
254 "valid4",
255 Some("ADAdsadsdasdDS[]"),
256 );
257 test(
258 VisualBuilder::new(SphereGeometry::new(3.3e9)).named("Bal"),
259 "bol",
260 Some("Bal"),
261 );
262
263 test(
265 VisualBuilder::new(BoxGeometry::new(1., 2., 3.)).named("Leg_[[L01]]_l04_col"),
266 "7",
267 Some("Leg_[[7]]_l04_col"),
268 );
269 test(
270 VisualBuilder::new(CylinderGeometry::new(32., 5.)).named("Arm_[[B01d]]_link_0313c"),
271 "valid4",
272 Some("Arm_[[valid4]]_link_0313c"),
273 );
274 test(
275 VisualBuilder::new(SphereGeometry::new(3.3e9))
276 .named("Bal_[[F900]]_this_doesn't_matter"),
277 "G0-02",
278 Some("Bal_[[G0-02]]_this_doesn't_matter"),
279 );
280
281 test(
283 VisualBuilder::new(BoxGeometry::new(1., 2., 3.)).named("Leg_[[L01]]_l04_col"),
284 "[[7",
285 Some("Leg_[[[[7]]_l04_col"),
286 );
287 test(
288 VisualBuilder::new(CylinderGeometry::new(32., 5.)).named("Arm_[[B01d]]_link_0313c"),
289 "[[invalid]]",
290 Some("Arm_[[[[invalid]]]]_link_0313c"),
291 );
292 test(
293 VisualBuilder::new(SphereGeometry::new(3.3e9))
294 .named("Bal_[[F900]]_this_doesn't_matter"),
295 "",
296 Some("Bal_[[]]_this_doesn't_matter"),
297 );
298 }
299
300 #[test]
301 #[ignore = "TODO"]
302 fn change_group_id_unchecked_with_material() {
303 todo!()
304 }
305
306 #[test]
307 fn change_group_id_no_material() {
308 #[inline]
309 fn test(
310 visual_builder: VisualBuilder,
311 new_group_id: &str,
312 result_change: Result<(), GroupIDError>,
313 name: Option<&str>,
314 ) {
315 let mut visual_builder = visual_builder;
316 assert_eq!(visual_builder.change_group_id(new_group_id), result_change);
317 assert_eq!(
318 visual_builder.name,
319 name.and_then(|name| Some(name.to_owned()))
320 )
321 }
322
323 test(
325 VisualBuilder::new(BoxGeometry::new(1., 2., 3.)),
326 "7",
327 Ok(()),
328 None,
329 );
330 test(
331 VisualBuilder::new(CylinderGeometry::new(32., 5.)),
332 "valid5",
333 Ok(()),
334 None,
335 );
336 test(
337 VisualBuilder::new(SphereGeometry::new(7.)),
338 "R04",
339 Ok(()),
340 None,
341 );
342
343 test(
345 VisualBuilder::new(BoxGeometry::new(1., 2., 3.)),
346 "7]]",
347 Err(GroupIDError::new_close("7]]")),
348 None,
349 );
350 test(
351 VisualBuilder::new(CylinderGeometry::new(32., 5.)),
352 "[[invalid]]",
353 Err(GroupIDError::new_open("[[invalid]]")),
354 None,
355 );
356 test(
357 VisualBuilder::new(SphereGeometry::new(3.3e9)),
358 "",
359 Err(GroupIDError::new_empty()),
360 None,
361 );
362
363 test(
365 VisualBuilder::new(BoxGeometry::new(1., 2., 3.)).named("ThisCoolName"),
366 "7",
367 Ok(()),
368 Some("ThisCoolName"),
369 );
370 test(
371 VisualBuilder::new(CylinderGeometry::new(32., 5.)).named("ADAdsadsdasdDS[]"),
372 "valid4",
373 Ok(()),
374 Some("ADAdsadsdasdDS[]"),
375 );
376 test(
377 VisualBuilder::new(SphereGeometry::new(3.3e9)).named("Bal"),
378 "bol",
379 Ok(()),
380 Some("Bal"),
381 );
382
383 test(
385 VisualBuilder::new(BoxGeometry::new(1., 2., 3.)).named("ThisCoolName"),
386 "7]]",
387 Err(GroupIDError::new_close("7]]")),
388 Some("ThisCoolName"),
389 );
390 test(
391 VisualBuilder::new(CylinderGeometry::new(32., 5.)).named("ADAdsadsdasdDS[]"),
392 "[[invalid]]",
393 Err(GroupIDError::new_open("[[invalid]]")),
394 Some("ADAdsadsdasdDS[]"),
395 );
396 test(
397 VisualBuilder::new(SphereGeometry::new(3.3e9)).named("Bal"),
398 "",
399 Err(GroupIDError::new_empty()),
400 Some("Bal"),
401 );
402
403 test(
405 VisualBuilder::new(BoxGeometry::new(1., 2., 3.)).named("Leg_[[L01]]_l04_col"),
406 "7",
407 Ok(()),
408 Some("Leg_[[7]]_l04_col"),
409 );
410 test(
411 VisualBuilder::new(CylinderGeometry::new(32., 5.)).named("Arm_[[B01d]]_link_0313c"),
412 "valid4",
413 Ok(()),
414 Some("Arm_[[valid4]]_link_0313c"),
415 );
416 test(
417 VisualBuilder::new(SphereGeometry::new(3.3e9))
418 .named("Bal_[[F900]]_this_doesn't_matter"),
419 "G0-02",
420 Ok(()),
421 Some("Bal_[[G0-02]]_this_doesn't_matter"),
422 );
423
424 test(
426 VisualBuilder::new(BoxGeometry::new(1., 2., 3.)).named("Leg_[[L01]]_l04_col"),
427 "[[7",
428 Err(GroupIDError::new_open("[[7")),
429 Some("Leg_[[L01]]_l04_col"),
430 );
431 test(
432 VisualBuilder::new(CylinderGeometry::new(32., 5.)).named("Arm_[[B01d]]_link_0313c"),
433 "[[invalid]]",
434 Err(GroupIDError::new_open("[[invalid]]")),
435 Some("Arm_[[B01d]]_link_0313c"),
436 );
437 test(
438 VisualBuilder::new(SphereGeometry::new(3.3e9))
439 .named("Bal_[[F900]]_this_doesn't_matter"),
440 "",
441 Err(GroupIDError::new_empty()),
442 Some("Bal_[[F900]]_this_doesn't_matter"),
443 );
444 }
445
446 #[test]
447 #[ignore = "TODO"]
448 fn change_group_id_with_material() {
449 todo!()
450 }
451
452 #[test]
453 fn apply_group_id_no_material() {
454 #[inline]
455 fn test(visual_builder: VisualBuilder, name: Option<&str>) {
456 let mut collision_builder = visual_builder;
457 collision_builder.apply_group_id();
458 assert_eq!(
459 collision_builder.name,
460 name.and_then(|name| Some(name.to_owned()))
461 )
462 }
463
464 test(VisualBuilder::new(BoxGeometry::new(1., 2., 3.)), None);
466 test(VisualBuilder::new(CylinderGeometry::new(32., 5.)), None);
467 test(VisualBuilder::new(SphereGeometry::new(7.)), None);
468
469 test(
471 VisualBuilder::new(BoxGeometry::new(1., 2., 3.)).named("ThisCoolName"),
472 Some("ThisCoolName"),
473 );
474 test(
475 VisualBuilder::new(CylinderGeometry::new(32., 5.)).named("ADAdsadsdasdDS[]"),
476 Some("ADAdsadsdasdDS[]"),
477 );
478 test(
479 VisualBuilder::new(SphereGeometry::new(3.3e9)).named("Bal"),
480 Some("Bal"),
481 );
482
483 test(
485 VisualBuilder::new(BoxGeometry::new(1., 2., 3.)).named("This[\\[Cool]\\]Name"),
486 Some("This[[Cool]]Name"),
487 );
488 test(
489 VisualBuilder::new(CylinderGeometry::new(32., 5.)).named("ADAdsadsdasdDS[\\[]"),
490 Some("ADAdsadsdasdDS[[]"),
491 );
492 test(
493 VisualBuilder::new(SphereGeometry::new(3.3e9)).named("Bal]\\]"),
494 Some("Bal]]"),
495 );
496
497 test(
499 VisualBuilder::new(BoxGeometry::new(1., 2., 3.)).named("Leg_[[L01]]_l04_col"),
500 Some("Leg_L01_l04_col"),
501 );
502 test(
503 VisualBuilder::new(CylinderGeometry::new(32., 5.)).named("Arm_[[B01d]]_link_0313c"),
504 Some("Arm_B01d_link_0313c"),
505 );
506 test(
507 VisualBuilder::new(SphereGeometry::new(3.3e9))
508 .named("Bal_[[F900]]_this_doesn't_matter"),
509 Some("Bal_F900_this_doesn't_matter"),
510 );
511
512 test(
514 VisualBuilder::new(BoxGeometry::new(1., 2., 3.))
515 .named("Leg_[\\[L01]\\]_[[l04]]_col"),
516 Some("Leg_[[L01]]_l04_col"),
517 );
518 test(
519 VisualBuilder::new(CylinderGeometry::new(32., 5.))
520 .named("Arm_[[B01d]\\]_[\\[link_0313c]]"),
521 Some("Arm_B01d]]_[[link_0313c"),
522 );
523 test(
524 VisualBuilder::new(SphereGeometry::new(3.3e9))
525 .named("Bal_[[F900]]_this_[\\[doesn't]\\]_matter"),
526 Some("Bal_F900_this_[[doesn't]]_matter"),
527 );
528 }
529
530 #[test]
531 #[ignore = "TODO"]
532 fn apply_group_id_with_material() {
533 todo!()
534 }
535 }
536}