robot_description_builder/material/
data.rs

1//! The raw `Matarial` data handlers.
2//!
3//! This module contains:
4//!  - The [`MaterialData`] container, which contains the raw material data such as the color.
5//!  - The [`MaterialDataReference`], which is a referenced which can refer to a material data from both (Global) named  and unnamed materials.
6use std::sync::Arc;
7
8use crate::utils::{ArcLock, ArcRW, ErroredRead};
9
10#[cfg(feature = "urdf")]
11use crate::to_rdf::to_urdf::ToURDF;
12#[cfg(feature = "xml")]
13use quick_xml::{events::attributes::Attribute, name::QName};
14
15/// A enum containing all allowed `Material` types and their data.
16#[derive(Debug, PartialEq, Clone)]
17pub enum MaterialData {
18	/// Color as RGBA.
19	///
20	/// The fields need to be between 0 and 1 (for most simulators). (Not enforced)
21	Color(f32, f32, f32, f32),
22	/// Texture, containing the texture path as a valid package path (e.g. `"package://robot_description/textures/{texture}"`). You are on your own here.
23	Texture(String),
24}
25
26#[cfg(feature = "urdf")]
27impl ToURDF for MaterialData {
28	fn to_urdf(
29		&self,
30		writer: &mut quick_xml::Writer<std::io::Cursor<Vec<u8>>>,
31		_urdf_config: &crate::to_rdf::to_urdf::URDFConfig,
32	) -> Result<(), quick_xml::Error> {
33		match self {
34			MaterialData::Color(red, green, blue, alpha) => {
35				writer
36					.create_element("color")
37					.with_attribute(Attribute {
38						key: QName(b"rgba"),
39						value: format!("{} {} {} {}", red, green, blue, alpha)
40							.as_bytes()
41							.into(),
42					})
43					.write_empty()?;
44				Ok(())
45			}
46			MaterialData::Texture(texture_path) => {
47				writer
48					.create_element("texture")
49					.with_attribute(Attribute {
50						key: QName(b"filename"),
51						value: texture_path.clone().as_bytes().into(),
52					})
53					.write_empty()?;
54				Ok(())
55			}
56		}
57	}
58}
59
60/// A wrapper for [`MaterialData`] references.
61///
62/// This is neccessary for the global [`Material`](super::Material) implementation.
63#[derive(Debug)]
64pub enum MaterialDataReference<'a> {
65	/// A normal Reference to a [`MaterialData`] of an unnamed/unshared [`Material`](super::Material).
66	Direct(&'a MaterialData),
67	/// A Global Reference to a [`MaterialData`] of a named/shared [`Material`](super::Material) via a `Arc<RwLock<T>>`.
68	Global(ArcLock<MaterialData>),
69}
70
71impl<'a> MaterialDataReference<'a> {
72	/// Check if the two referenced [`MaterialData`] structs describe the same appearance.
73	///
74	/// If one of the `MaterialData`s is the [`Global`](MaterialDataReference::Global) and it is poisoned,
75	/// then we replace the data from the [`Direct`](MaterialDataReference::Direct) with the other one and return `true`.
76	pub fn same_material_data(&self, other: &MaterialDataReference) -> bool {
77		#[allow(unreachable_code)] // This is for the Future Feature support
78		match (self, other) {
79			(MaterialDataReference::Direct(left), MaterialDataReference::Direct(right)) => {
80				left == right
81			}
82			(MaterialDataReference::Direct(left), MaterialDataReference::Global(right)) => {
83				match !right.is_poisoned() {
84					true => (*left).clone() == right.read().unwrap().clone(), // We can safely unwrap, since we have checked for poisoning.
85					false => {
86						// When the right lock has been poisoned, recover by overwriting with the left [`MaterialData`]
87						*right.write().map_err(|err| err.into_inner()).unwrap() = (*left).clone();
88						todo!("Unpoisoning is still a nightly-only experimental feature. (mutex_unpoison #96469)");
89						true
90					}
91				}
92			}
93			(MaterialDataReference::Global(left), MaterialDataReference::Direct(right)) => {
94				match !left.is_poisoned() {
95					true => (*right).clone() == left.read().unwrap().clone(), // We can safely unwrap, since we have checked for poisoning.
96					false => {
97						// When the left lock has been poisoned, recover by overwriting with the right [`MaterialData`]
98						*left.write().map_err(|err| err.into_inner()).unwrap() = (*right).clone();
99						todo!("Unpoisoning is still a nightly-only experimental feature. (mutex_unpoison #96469)");
100						true
101					}
102				}
103			}
104			(MaterialDataReference::Global(left), MaterialDataReference::Global(right)) => {
105				Arc::ptr_eq(left, right)
106					|| left.read().unwrap().clone() == right.read().unwrap().clone() // FIXME: Unwrap not OK
107			}
108		}
109	}
110}
111
112impl<'a> PartialEq for MaterialDataReference<'a> {
113	fn eq(&self, other: &Self) -> bool {
114		match (self, other) {
115			(Self::Direct(l0), Self::Direct(r0)) => l0 == r0,
116			(Self::Global(l0), Self::Global(r0)) => Arc::ptr_eq(l0, r0),
117			_ => false,
118		}
119	}
120}
121
122impl<'a> From<&'a MaterialData> for MaterialDataReference<'a> {
123	fn from(value: &'a MaterialData) -> Self {
124		Self::Direct(value)
125	}
126}
127
128impl<'a> From<ArcLock<MaterialData>> for MaterialDataReference<'a> {
129	fn from(value: ArcLock<MaterialData>) -> Self {
130		MaterialDataReference::Global(value)
131	}
132}
133
134impl<'a> TryFrom<MaterialDataReference<'a>> for MaterialData {
135	type Error = std::sync::PoisonError<ErroredRead<ArcLock<MaterialData>>>;
136
137	fn try_from(value: MaterialDataReference) -> Result<Self, Self::Error> {
138		match value {
139			MaterialDataReference::Direct(data) => Ok(data.clone()),
140			MaterialDataReference::Global(arc_data) => {
141				let data_ref = arc_data.mread().map(|data| data.clone());
142				data_ref
143			}
144		}
145	}
146}