1use crate::env::CuCollisionEnv;
14use crate::shapes::CuCollisionShape;
15use crate::shapes::transform_shape;
16use bincode::Decode;
17use bincode::Encode;
18use cu_spatial_payloads::Transform3D;
19use cu29::prelude::*;
20use serde::Deserialize;
21use serde::Serialize;
22
23pub const DEFAULT_MAX_OBSTACLES: usize = 32;
25
26pub const DEFAULT_MAX_ATTACHED: usize = 8;
28
29fn is_finite_transform(frame: &Transform3D<f64>) -> bool {
31 frame.to_matrix().iter().flatten().all(|v| v.is_finite())
32}
33
34#[derive(
36 Default,
37 Debug,
38 Clone,
39 Copy,
40 PartialEq,
41 Eq,
42 PartialOrd,
43 Ord,
44 Hash,
45 Encode,
46 Decode,
47 Serialize,
48 Deserialize,
49 Reflect,
50)]
51pub struct CuObjectId(u32);
52
53impl CuObjectId {
54 pub const NIL: Self = Self(0);
56
57 pub const fn new(id: u32) -> Self {
59 Self(id)
60 }
61
62 pub const fn as_u32(self) -> u32 {
64 self.0
65 }
66
67 pub const fn is_valid(self) -> bool {
69 self.0 != 0 && self.0 < u32::MAX
70 }
71}
72
73impl core::fmt::Display for CuObjectId {
74 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75 write!(f, "ObjectId({})", self.0)
76 }
77}
78
79#[derive(
81 Default,
82 Debug,
83 Clone,
84 Copy,
85 PartialEq,
86 Eq,
87 PartialOrd,
88 Ord,
89 Hash,
90 Encode,
91 Decode,
92 Serialize,
93 Deserialize,
94 Reflect,
95)]
96pub struct CuSceneRevision(u64);
97
98impl CuSceneRevision {
99 pub const INITIAL: Self = Self(0);
101
102 pub const fn new(revision: u64) -> Self {
104 Self(revision)
105 }
106
107 pub const fn as_u64(self) -> u64 {
109 self.0
110 }
111
112 #[must_use]
114 pub const fn checked_next(self) -> Option<Self> {
115 match self.0.checked_add(1) {
116 Some(v) => Some(Self(v)),
117 None => None,
118 }
119 }
120
121 #[must_use]
123 pub const fn next(self) -> Self {
124 Self(self.0.saturating_add(1))
125 }
126}
127
128impl core::fmt::Display for CuSceneRevision {
129 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
130 write!(f, "Revision({})", self.0)
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum CuSceneError {
137 ObstacleCapacityExceeded,
139 AttachedCapacityExceeded,
141 ObjectNotFound(CuObjectId),
143 InvalidObjectId(CuObjectId),
145 DuplicateObjectId(CuObjectId),
147 InvalidLinkIndex { link_index: usize, max_links: usize },
149 OutputCapacityExceeded,
151 RevisionExhausted,
153 InvalidGeometry,
155}
156
157impl core::fmt::Display for CuSceneError {
158 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
159 match self {
160 Self::ObstacleCapacityExceeded => {
161 write!(f, "planning scene obstacle capacity exceeded")
162 }
163 Self::AttachedCapacityExceeded => {
164 write!(f, "planning scene attached shape capacity exceeded")
165 }
166 Self::ObjectNotFound(id) => {
167 write!(f, "planning scene object {id} not found")
168 }
169 Self::InvalidObjectId(id) => {
170 write!(f, "planning scene object {id} is invalid")
171 }
172 Self::DuplicateObjectId(id) => {
173 write!(f, "planning scene object {id} already exists")
174 }
175 Self::InvalidLinkIndex {
176 link_index,
177 max_links,
178 } => {
179 write!(
180 f,
181 "invalid robot link index {link_index} (chain has {max_links} links)"
182 )
183 }
184 Self::OutputCapacityExceeded => {
185 write!(f, "target output buffer capacity exceeded")
186 }
187 Self::RevisionExhausted => {
188 write!(f, "planning scene revision counter exhausted")
189 }
190 Self::InvalidGeometry => {
191 write!(
192 f,
193 "planning scene collision shape contains invalid or non-finite geometry"
194 )
195 }
196 }
197 }
198}
199
200impl CuSceneError {
201 pub const fn as_str(&self) -> &'static str {
203 match self {
204 Self::ObstacleCapacityExceeded => {
205 "repose_collision: planning scene obstacle capacity exceeded"
206 }
207 Self::AttachedCapacityExceeded => {
208 "repose_collision: planning scene attached shape capacity exceeded"
209 }
210 Self::ObjectNotFound(_) => "repose_collision: planning scene object not found",
211 Self::InvalidObjectId(_) => "repose_collision: planning scene object id is invalid",
212 Self::DuplicateObjectId(_) => "repose_collision: planning scene duplicate object id",
213 Self::InvalidLinkIndex { .. } => "repose_collision: invalid robot link index",
214 Self::OutputCapacityExceeded => {
215 "repose_collision: target output buffer capacity exceeded"
216 }
217 Self::RevisionExhausted => "repose_collision: planning scene revision exhausted",
218 Self::InvalidGeometry => {
219 "repose_collision: invalid or non-finite collision shape geometry"
220 }
221 }
222 }
223}
224
225impl From<CuSceneError> for CuError {
226 fn from(err: CuSceneError) -> Self {
227 CuError::from(err.as_str())
228 }
229}
230
231#[derive(
233 Default, Debug, Clone, Copy, PartialEq, Encode, Decode, Serialize, Deserialize, Reflect,
234)]
235pub struct CuSceneObstacle {
236 pub id: CuObjectId,
238 pub shape: CuCollisionShape,
240}
241
242impl CuSceneObstacle {
243 pub fn new(id: CuObjectId, shape: CuCollisionShape) -> CuResult<Self> {
245 if !id.is_valid() {
246 return Err(CuSceneError::InvalidObjectId(id).into());
247 }
248 shape.validate()?;
249 Ok(Self { id, shape })
250 }
251}
252
253#[derive(
255 Default, Debug, Clone, Copy, PartialEq, Encode, Decode, Serialize, Deserialize, Reflect,
256)]
257pub struct CuAttachedShape {
258 pub id: CuObjectId,
260 pub frame: Option<usize>,
262 pub shape: CuCollisionShape,
264}
265
266impl CuAttachedShape {
267 pub fn new(id: CuObjectId, frame: Option<usize>, shape: CuCollisionShape) -> CuResult<Self> {
270 if !id.is_valid() {
271 return Err(CuSceneError::InvalidObjectId(id).into());
272 }
273 shape.validate()?;
274 Ok(Self { id, frame, shape })
275 }
276
277 pub fn with_transform(mut self, frame_from_shape: &Transform3D<f64>) -> CuResult<Self> {
280 if !is_finite_transform(frame_from_shape) {
281 return Err(CuSceneError::InvalidGeometry.into());
282 }
283 let local = self.shape;
284 transform_shape(frame_from_shape, &local, &mut self.shape);
285 self.shape.validate()?;
286 Ok(self)
287 }
288}
289
290#[derive(Debug, Clone, Copy, Default, PartialEq)]
292pub struct CuAttachedUpdate {
293 frame: Option<Option<usize>>,
294 shape: Option<CuCollisionShape>,
295}
296
297impl CuAttachedUpdate {
298 pub const fn new() -> Self {
300 Self {
301 frame: None,
302 shape: None,
303 }
304 }
305
306 pub const fn with_frame(mut self, frame: Option<usize>) -> Self {
308 self.frame = Some(frame);
309 self
310 }
311
312 pub const fn with_shape(mut self, shape: CuCollisionShape) -> Self {
314 self.shape = Some(shape);
315 self
316 }
317}
318
319#[derive(Serialize, Deserialize)]
320struct CuPlanningSceneSnapshot<const MAX_OBSTACLES: usize, const MAX_ATTACHED: usize> {
321 revision: CuSceneRevision,
322 next_id: u32,
323 #[serde(with = "crate::serde_arrays::obstacle_array")]
324 obstacles: [CuSceneObstacle; MAX_OBSTACLES],
325 obstacle_count: u32,
326 #[serde(with = "crate::serde_arrays::attached_array")]
327 attached_shapes: [CuAttachedShape; MAX_ATTACHED],
328 attached_count: u32,
329}
330
331#[derive(Debug, Clone, PartialEq, Reflect)]
337#[reflect(no_field_bounds, from_reflect = false)]
338pub struct CuPlanningScene<
339 const MAX_OBSTACLES: usize = DEFAULT_MAX_OBSTACLES,
340 const MAX_ATTACHED: usize = DEFAULT_MAX_ATTACHED,
341> {
342 revision: CuSceneRevision,
343 next_id: u32,
344 obstacle_ids: [CuObjectId; MAX_OBSTACLES],
345 obstacle_shapes: [CuCollisionShape; MAX_OBSTACLES],
346 obstacle_count: u32,
347 attached_shapes: [CuAttachedShape; MAX_ATTACHED],
348 attached_count: u32,
349}
350
351impl<const MAX_OBSTACLES: usize, const MAX_ATTACHED: usize> Default
352 for CuPlanningScene<MAX_OBSTACLES, MAX_ATTACHED>
353{
354 fn default() -> Self {
355 Self::new()
356 }
357}
358
359impl<const MAX_OBSTACLES: usize, const MAX_ATTACHED: usize> Serialize
360 for CuPlanningScene<MAX_OBSTACLES, MAX_ATTACHED>
361{
362 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
363 where
364 S: serde::Serializer,
365 {
366 if let Err(err) = self.validate_invariants() {
367 return Err(serde::ser::Error::custom(err.as_str()));
368 }
369 let snapshot = CuPlanningSceneSnapshot::<MAX_OBSTACLES, MAX_ATTACHED> {
370 revision: self.revision,
371 next_id: self.next_id,
372 obstacles: core::array::from_fn(|i| CuSceneObstacle {
373 id: self.obstacle_ids[i],
374 shape: self.obstacle_shapes[i],
375 }),
376 obstacle_count: self.obstacle_count,
377 attached_shapes: self.attached_shapes,
378 attached_count: self.attached_count,
379 };
380 snapshot.serialize(serializer)
381 }
382}
383
384impl<'de, const MAX_OBSTACLES: usize, const MAX_ATTACHED: usize> Deserialize<'de>
385 for CuPlanningScene<MAX_OBSTACLES, MAX_ATTACHED>
386{
387 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
388 where
389 D: serde::Deserializer<'de>,
390 {
391 let raw =
392 CuPlanningSceneSnapshot::<MAX_OBSTACLES, MAX_ATTACHED>::deserialize(deserializer)?;
393 let scene = Self {
394 revision: raw.revision,
395 next_id: raw.next_id,
396 obstacle_ids: raw.obstacles.map(|o| o.id),
397 obstacle_shapes: raw.obstacles.map(|o| o.shape),
398 obstacle_count: raw.obstacle_count,
399 attached_shapes: raw.attached_shapes,
400 attached_count: raw.attached_count,
401 };
402 if let Err(err) = scene.validate_invariants() {
403 return Err(serde::de::Error::custom(err.as_str()));
404 }
405 Ok(scene)
406 }
407}
408
409impl<const MAX_OBSTACLES: usize, const MAX_ATTACHED: usize> Encode
410 for CuPlanningScene<MAX_OBSTACLES, MAX_ATTACHED>
411{
412 fn encode<E: bincode::enc::Encoder>(
413 &self,
414 encoder: &mut E,
415 ) -> Result<(), bincode::error::EncodeError> {
416 if let Err(err) = self.validate_invariants() {
417 return Err(bincode::error::EncodeError::Other(err.as_str()));
418 }
419 Encode::encode(&self.revision, encoder)?;
420 Encode::encode(&self.next_id, encoder)?;
421 Encode::encode(&self.obstacle_count, encoder)?;
422 Encode::encode(&self.obstacle_ids, encoder)?;
423 Encode::encode(&self.obstacle_shapes, encoder)?;
424 Encode::encode(&self.attached_count, encoder)?;
425 Encode::encode(&self.attached_shapes, encoder)
426 }
427}
428
429impl<C, const MAX_OBSTACLES: usize, const MAX_ATTACHED: usize> Decode<C>
430 for CuPlanningScene<MAX_OBSTACLES, MAX_ATTACHED>
431{
432 fn decode<D: bincode::de::Decoder<Context = C>>(
433 decoder: &mut D,
434 ) -> Result<Self, bincode::error::DecodeError> {
435 let scene = Self {
436 revision: Decode::decode(decoder)?,
437 next_id: Decode::decode(decoder)?,
438 obstacle_count: Decode::decode(decoder)?,
439 obstacle_ids: Decode::decode(decoder)?,
440 obstacle_shapes: Decode::decode(decoder)?,
441 attached_count: Decode::decode(decoder)?,
442 attached_shapes: Decode::decode(decoder)?,
443 };
444 if let Err(err) = scene.validate_invariants() {
445 return Err(bincode::error::DecodeError::Other(err.as_str()));
446 }
447 Ok(scene)
448 }
449}
450
451impl<'de, C, const MAX_OBSTACLES: usize, const MAX_ATTACHED: usize> bincode::BorrowDecode<'de, C>
452 for CuPlanningScene<MAX_OBSTACLES, MAX_ATTACHED>
453{
454 fn borrow_decode<D: bincode::de::BorrowDecoder<'de, Context = C>>(
455 decoder: &mut D,
456 ) -> Result<Self, bincode::error::DecodeError> {
457 Decode::decode(decoder)
458 }
459}
460
461impl<const MAX_OBSTACLES: usize, const MAX_ATTACHED: usize>
462 CuPlanningScene<MAX_OBSTACLES, MAX_ATTACHED>
463{
464 pub const OBSTACLE_CAPACITY: usize = MAX_OBSTACLES;
466 pub const ATTACHED_CAPACITY: usize = MAX_ATTACHED;
468
469 pub fn new() -> Self {
471 Self {
472 revision: CuSceneRevision::INITIAL,
473 next_id: 1,
474 obstacle_ids: [CuObjectId::NIL; MAX_OBSTACLES],
475 obstacle_shapes: [CuCollisionShape::default(); MAX_OBSTACLES],
476 obstacle_count: 0,
477 attached_shapes: [CuAttachedShape::default(); MAX_ATTACHED],
478 attached_count: 0,
479 }
480 }
481
482 pub(crate) fn validate_invariants(&self) -> Result<(), CuSceneError> {
486 let obs_count = self.obstacle_count as usize;
487 if obs_count > MAX_OBSTACLES {
488 return Err(CuSceneError::ObstacleCapacityExceeded);
489 }
490 let att_count = self.attached_count as usize;
491 if att_count > MAX_ATTACHED {
492 return Err(CuSceneError::AttachedCapacityExceeded);
493 }
494 if self.next_id == 0 {
495 return Err(CuSceneError::InvalidObjectId(CuObjectId::NIL));
496 }
497 let obstacle_ids = &self.obstacle_ids[..obs_count];
498 let check = |id: CuObjectId, shape: &CuCollisionShape| {
499 if !id.is_valid() || id.as_u32() >= self.next_id {
500 return Err(CuSceneError::InvalidObjectId(id));
501 }
502 if shape.validate().is_err() {
503 return Err(CuSceneError::InvalidGeometry);
504 }
505 Ok(())
506 };
507 for (i, (&id, shape)) in obstacle_ids
508 .iter()
509 .zip(&self.obstacle_shapes[..obs_count])
510 .enumerate()
511 {
512 check(id, shape)?;
513 if obstacle_ids[i + 1..].contains(&id) {
514 return Err(CuSceneError::DuplicateObjectId(id));
515 }
516 }
517 let attached = self.attached_shapes();
518 for (i, att) in attached.iter().enumerate() {
519 check(att.id, &att.shape)?;
520 if attached[i + 1..].iter().any(|o| o.id == att.id) || obstacle_ids.contains(&att.id) {
521 return Err(CuSceneError::DuplicateObjectId(att.id));
522 }
523 }
524 Ok(())
525 }
526
527 pub const fn revision(&self) -> CuSceneRevision {
529 self.revision
530 }
531
532 pub const fn is_empty(&self) -> bool {
534 self.obstacle_count == 0 && self.attached_count == 0
535 }
536
537 pub fn obstacle_shapes(&self) -> &[CuCollisionShape] {
539 &self.obstacle_shapes[..self.obstacle_len()]
540 }
541
542 pub fn obstacle_ids(&self) -> &[CuObjectId] {
544 &self.obstacle_ids[..self.obstacle_len()]
545 }
546
547 pub fn obstacles(&self) -> impl Iterator<Item = CuSceneObstacle> + '_ {
549 self.obstacle_ids()
550 .iter()
551 .zip(self.obstacle_shapes())
552 .map(|(&id, &shape)| CuSceneObstacle { id, shape })
553 }
554
555 pub fn attached_shapes(&self) -> &[CuAttachedShape] {
557 let count = (self.attached_count as usize).min(MAX_ATTACHED);
558 &self.attached_shapes[..count]
559 }
560
561 pub fn obstacle(&self, id: CuObjectId) -> Option<&CuCollisionShape> {
563 self.find_obstacle_index(id)
564 .map(|idx| &self.obstacle_shapes[idx])
565 }
566
567 pub fn attached_shape(&self, id: CuObjectId) -> Option<&CuAttachedShape> {
569 self.find_attached_index(id)
570 .map(|idx| &self.attached_shapes[idx])
571 }
572
573 pub fn contains(&self, id: CuObjectId) -> bool {
575 self.find_obstacle_index(id).is_some() || self.find_attached_index(id).is_some()
576 }
577
578 pub fn collision_env(&self) -> CuCollisionEnv<'_> {
581 CuCollisionEnv::with_obstacle_ids(self.obstacle_shapes(), self.obstacle_ids())
582 .with_attached(self.attached_shapes())
583 }
584
585 fn obstacle_len(&self) -> usize {
586 (self.obstacle_count as usize).min(MAX_OBSTACLES)
587 }
588
589 pub(crate) fn find_obstacle_index(&self, id: CuObjectId) -> Option<usize> {
590 if !id.is_valid() {
591 return None;
592 }
593 self.obstacle_ids().iter().position(|&o| o == id)
594 }
595
596 pub(crate) fn find_attached_index(&self, id: CuObjectId) -> Option<usize> {
597 if !id.is_valid() {
598 return None;
599 }
600 self.attached_shapes().iter().position(|a| a.id == id)
601 }
602
603 fn next_revision(&self) -> CuResult<CuSceneRevision> {
604 Ok(self
605 .revision
606 .checked_next()
607 .ok_or(CuSceneError::RevisionExhausted)?)
608 }
609
610 fn plan_id(&self, id: Option<CuObjectId>) -> CuResult<(CuObjectId, u32)> {
612 let overflow = || CuError::from("repose_collision: object id overflow");
613 match id {
614 Some(id) => {
615 if !id.is_valid() {
616 return Err(CuSceneError::InvalidObjectId(id).into());
617 }
618 if self.contains(id) {
619 return Err(CuSceneError::DuplicateObjectId(id).into());
620 }
621 let next = if id.as_u32() >= self.next_id {
622 id.as_u32()
623 .checked_add(1)
624 .ok_or(CuSceneError::InvalidObjectId(id))?
625 } else {
626 self.next_id
627 };
628 Ok((id, next))
629 }
630 None => {
631 let mut candidate = self.next_id;
632 while candidate == 0 || self.contains(CuObjectId::new(candidate)) {
633 candidate = candidate.checked_add(1).ok_or_else(overflow)?;
634 }
635 let id = CuObjectId::new(candidate);
636 if !id.is_valid() {
637 return Err(overflow());
638 }
639 Ok((id, candidate.checked_add(1).ok_or_else(overflow)?))
640 }
641 }
642 }
643
644 fn push_obstacle(&mut self, id: CuObjectId, shape: CuCollisionShape) {
645 let idx = self.obstacle_count as usize;
646 self.obstacle_ids[idx] = id;
647 self.obstacle_shapes[idx] = shape;
648 self.obstacle_count += 1;
649 }
650
651 fn take_obstacle(&mut self, idx: usize) -> CuCollisionShape {
652 let count = self.obstacle_len();
653 let removed = self.obstacle_shapes[idx];
654 self.obstacle_ids.copy_within(idx + 1..count, idx);
655 self.obstacle_shapes.copy_within(idx + 1..count, idx);
656 self.obstacle_ids[count - 1] = CuObjectId::NIL;
657 self.obstacle_shapes[count - 1] = CuCollisionShape::default();
658 self.obstacle_count -= 1;
659 removed
660 }
661
662 fn push_attached(&mut self, attached: CuAttachedShape) {
663 self.attached_shapes[self.attached_count as usize] = attached;
664 self.attached_count += 1;
665 }
666
667 fn take_attached(&mut self, idx: usize) -> CuAttachedShape {
668 let count = self.attached_shapes().len();
669 let removed = self.attached_shapes[idx];
670 self.attached_shapes.copy_within(idx + 1..count, idx);
671 self.attached_shapes[count - 1] = CuAttachedShape::default();
672 self.attached_count -= 1;
673 removed
674 }
675
676 pub fn add_obstacle(
682 &mut self,
683 shape: CuCollisionShape,
684 id: Option<CuObjectId>,
685 ) -> CuResult<CuObjectId> {
686 if self.obstacle_len() >= MAX_OBSTACLES {
687 return Err(CuSceneError::ObstacleCapacityExceeded.into());
688 }
689 if shape.validate().is_err() {
690 return Err(CuSceneError::InvalidGeometry.into());
691 }
692 let revision = self.next_revision()?;
693 let (id, next_id) = self.plan_id(id)?;
694 self.push_obstacle(id, shape);
695 self.next_id = next_id;
696 self.revision = revision;
697 Ok(id)
698 }
699
700 pub fn update_obstacle(&mut self, id: CuObjectId, shape: CuCollisionShape) -> CuResult<()> {
702 if !id.is_valid() {
703 return Err(CuSceneError::InvalidObjectId(id).into());
704 }
705 if shape.validate().is_err() {
706 return Err(CuSceneError::InvalidGeometry.into());
707 }
708 let idx = self
709 .find_obstacle_index(id)
710 .ok_or(CuSceneError::ObjectNotFound(id))?;
711 self.revision = self.next_revision()?;
712 self.obstacle_shapes[idx] = shape;
713 Ok(())
714 }
715
716 pub fn remove_obstacle(&mut self, id: CuObjectId) -> CuResult<CuCollisionShape> {
718 if !id.is_valid() {
719 return Err(CuSceneError::InvalidObjectId(id).into());
720 }
721 let idx = self
722 .find_obstacle_index(id)
723 .ok_or(CuSceneError::ObjectNotFound(id))?;
724 self.revision = self.next_revision()?;
725 Ok(self.take_obstacle(idx))
726 }
727
728 pub fn add_attached(
732 &mut self,
733 frame: Option<usize>,
734 shape: CuCollisionShape,
735 id: Option<CuObjectId>,
736 ) -> CuResult<CuObjectId> {
737 if self.attached_shapes().len() >= MAX_ATTACHED {
738 return Err(CuSceneError::AttachedCapacityExceeded.into());
739 }
740 if shape.validate().is_err() {
741 return Err(CuSceneError::InvalidGeometry.into());
742 }
743 let revision = self.next_revision()?;
744 let (id, next_id) = self.plan_id(id)?;
745 self.push_attached(CuAttachedShape { id, frame, shape });
746 self.next_id = next_id;
747 self.revision = revision;
748 Ok(id)
749 }
750
751 pub fn update_attached(&mut self, id: CuObjectId, update: CuAttachedUpdate) -> CuResult<()> {
753 if !id.is_valid() {
754 return Err(CuSceneError::InvalidObjectId(id).into());
755 }
756 if update.shape.is_some_and(|s| s.validate().is_err()) {
757 return Err(CuSceneError::InvalidGeometry.into());
758 }
759 let idx = self
760 .find_attached_index(id)
761 .ok_or(CuSceneError::ObjectNotFound(id))?;
762 self.revision = self.next_revision()?;
763 let attached = &mut self.attached_shapes[idx];
764 if let Some(frame) = update.frame {
765 attached.frame = frame;
766 }
767 if let Some(shape) = update.shape {
768 attached.shape = shape;
769 }
770 Ok(())
771 }
772
773 pub fn remove_attached(&mut self, id: CuObjectId) -> CuResult<CuAttachedShape> {
776 if !id.is_valid() {
777 return Err(CuSceneError::InvalidObjectId(id).into());
778 }
779 let idx = self
780 .find_attached_index(id)
781 .ok_or(CuSceneError::ObjectNotFound(id))?;
782 self.revision = self.next_revision()?;
783 Ok(self.take_attached(idx))
784 }
785
786 pub fn attach(
792 &mut self,
793 id: CuObjectId,
794 frame: Option<usize>,
795 local_shape: CuCollisionShape,
796 ) -> CuResult<()> {
797 if !id.is_valid() {
798 return Err(CuSceneError::InvalidObjectId(id).into());
799 }
800 if local_shape.validate().is_err() {
801 return Err(CuSceneError::InvalidGeometry.into());
802 }
803 if self.attached_shapes().len() >= MAX_ATTACHED {
804 return Err(CuSceneError::AttachedCapacityExceeded.into());
805 }
806 let idx = self
807 .find_obstacle_index(id)
808 .ok_or(CuSceneError::ObjectNotFound(id))?;
809 self.revision = self.next_revision()?;
810 self.take_obstacle(idx);
811 self.push_attached(CuAttachedShape {
812 id,
813 frame,
814 shape: local_shape,
815 });
816 Ok(())
817 }
818
819 pub fn detach(&mut self, id: CuObjectId, world_shape: CuCollisionShape) -> CuResult<()> {
825 if !id.is_valid() {
826 return Err(CuSceneError::InvalidObjectId(id).into());
827 }
828 if world_shape.validate().is_err() {
829 return Err(CuSceneError::InvalidGeometry.into());
830 }
831 if self.obstacle_len() >= MAX_OBSTACLES {
832 return Err(CuSceneError::ObstacleCapacityExceeded.into());
833 }
834 let idx = self
835 .find_attached_index(id)
836 .ok_or(CuSceneError::ObjectNotFound(id))?;
837 self.revision = self.next_revision()?;
838 self.take_attached(idx);
839 self.push_obstacle(id, world_shape);
840 Ok(())
841 }
842
843 pub fn clear(&mut self) -> CuResult<()> {
846 if !self.is_empty() {
847 self.revision = self.next_revision()?;
848 *self = Self {
849 revision: self.revision,
850 next_id: self.next_id,
851 ..Self::new()
852 };
853 }
854 Ok(())
855 }
856
857 pub fn clear_obstacles(&mut self) -> CuResult<()> {
860 if self.obstacle_count > 0 {
861 self.revision = self.next_revision()?;
862 self.obstacle_ids = [CuObjectId::NIL; MAX_OBSTACLES];
863 self.obstacle_shapes = [CuCollisionShape::default(); MAX_OBSTACLES];
864 self.obstacle_count = 0;
865 }
866 Ok(())
867 }
868
869 pub fn clear_attached(&mut self) -> CuResult<()> {
872 if self.attached_count > 0 {
873 self.revision = self.next_revision()?;
874 self.attached_shapes = [CuAttachedShape::default(); MAX_ATTACHED];
875 self.attached_count = 0;
876 }
877 Ok(())
878 }
879
880 pub fn transaction<F, R>(&mut self, f: F) -> CuResult<R>
886 where
887 F: FnOnce(&mut Self) -> CuResult<R>,
888 {
889 let backup = self.clone();
890 match f(self) {
891 Ok(result) => {
892 if *self != backup && self.revision == backup.revision {
893 match self.revision.checked_next() {
894 Some(rev) => self.revision = rev,
895 None => {
896 *self = backup;
897 return Err(CuSceneError::RevisionExhausted.into());
898 }
899 }
900 }
901 Ok(result)
902 }
903 Err(e) => {
904 *self = backup;
905 Err(e)
906 }
907 }
908 }
909
910 pub fn world_attached_shapes<const N: usize, const MAX_OUT: usize>(
916 &self,
917 link_frames: &[Transform3D<f64>; N],
918 out_shapes: &mut [CuCollisionShape; MAX_OUT],
919 ) -> CuResult<usize> {
920 let attached = self.attached_shapes();
921 if attached.len() > MAX_OUT {
922 return Err(CuSceneError::OutputCapacityExceeded.into());
923 }
924 let world = |att: &CuAttachedShape| -> CuResult<CuCollisionShape> {
925 let Some(frame) = att.frame else {
926 return Ok(att.shape);
927 };
928 let Some(transform) = link_frames.get(frame) else {
929 return Err(CuSceneError::InvalidLinkIndex {
930 link_index: frame,
931 max_links: N,
932 }
933 .into());
934 };
935 if !is_finite_transform(transform) {
936 return Err(CuSceneError::InvalidGeometry.into());
937 }
938 let mut out = att.shape;
939 transform_shape(transform, &att.shape, &mut out);
940 if out.validate().is_err() {
941 return Err(CuSceneError::InvalidGeometry.into());
942 }
943 Ok(out)
944 };
945 for att in attached {
946 world(att)?;
947 }
948 for (att, out) in attached.iter().zip(out_shapes.iter_mut()) {
949 *out = world(att)?;
950 }
951 Ok(attached.len())
952 }
953}