1use serde_json::{Map, Value};
53
54use crate::json::{field, object};
55use crate::{Attributes, DecodeError};
56
57pub const VISIBILITY: &str = "usd::usdgeom::visibility";
59pub const DIFFUSE_COLOR: &str = "bsi::ifc::presentation::diffuseColor";
61pub const OPACITY: &str = "bsi::ifc::presentation::opacity";
63pub const GLTF_MATERIAL: &str = "gltf::material";
65
66fn wrong(at: impl Into<String>, expected: &'static str) -> DecodeError {
67 DecodeError::WrongShape {
68 at: at.into(),
69 expected,
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
75pub enum Visibility {
76 Inherited,
78 Invisible,
80}
81
82impl Visibility {
83 pub fn from_value(value: &Value) -> Result<Self, DecodeError> {
85 let token = field(object(value, VISIBILITY)?, "visibility")?
86 .as_str()
87 .ok_or_else(|| wrong("visibility", "string"))?;
88 match token {
89 "inherited" => Ok(Self::Inherited),
90 "invisible" => Ok(Self::Invisible),
91 other => Err(DecodeError::UnknownToken {
92 at: "visibility".to_owned(),
93 token: other.to_owned(),
94 expected: "inherited or invisible",
95 }),
96 }
97 }
98}
99
100pub fn is_visible(parent_visible: bool, own: Option<Visibility>) -> bool {
104 parent_visible && own != Some(Visibility::Invisible)
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
109pub enum AlphaMode {
110 #[default]
111 Opaque,
112 Mask,
113 Blend,
114}
115
116#[derive(Debug, Clone, PartialEq)]
118pub struct NormalTexture {
119 pub texture: String,
121 pub scale: f32,
123}
124
125#[derive(Debug, Clone, PartialEq)]
127pub struct OcclusionTexture {
128 pub texture: String,
130 pub strength: f32,
132}
133
134#[derive(Debug, Clone, PartialEq)]
141pub struct GltfMaterial {
142 pub base_color_factor: [f32; 4],
144 pub base_color_texture: Option<String>,
146 pub metallic_factor: f32,
148 pub roughness_factor: f32,
150 pub metallic_roughness_texture: Option<String>,
152 pub normal_texture: Option<NormalTexture>,
153 pub occlusion_texture: Option<OcclusionTexture>,
154 pub emissive_texture: Option<String>,
155 pub emissive_factor: [f32; 3],
157 pub alpha_mode: AlphaMode,
159 pub alpha_cutoff: f32,
161 pub double_sided: bool,
163}
164
165impl Default for GltfMaterial {
166 fn default() -> Self {
167 Self {
168 base_color_factor: [1.0; 4],
169 base_color_texture: None,
170 metallic_factor: 1.0,
171 roughness_factor: 1.0,
172 metallic_roughness_texture: None,
173 normal_texture: None,
174 occlusion_texture: None,
175 emissive_texture: None,
176 emissive_factor: [0.0; 3],
177 alpha_mode: AlphaMode::Opaque,
178 alpha_cutoff: 0.5,
179 double_sided: false,
180 }
181 }
182}
183
184const GLTF_MEMBERS: [&str; 8] = [
185 "pbrMetallicRoughness",
186 "normalTexture",
187 "occlusionTexture",
188 "emissiveTexture",
189 "emissiveFactor",
190 "alphaMode",
191 "alphaCutoff",
192 "doubleSided",
193];
194
195impl GltfMaterial {
196 pub fn from_value(value: &Value) -> Result<Option<Self>, DecodeError> {
201 let object = object(value, GLTF_MATERIAL)?;
202 if !GLTF_MEMBERS.iter().any(|m| member(object, m).is_some()) {
203 return Ok(None);
204 }
205 let mut material = Self::default();
206 if let Some(pbr) = member(object, "pbrMetallicRoughness") {
207 let pbr = pbr
208 .as_object()
209 .ok_or_else(|| wrong("pbrMetallicRoughness", "object"))?;
210 let at = |name: &str| format!("pbrMetallicRoughness.{name}");
211 if let Some(v) = member(pbr, "baseColorFactor") {
212 material.base_color_factor =
213 reals(v).ok_or_else(|| wrong(at("baseColorFactor"), "array of 4 numbers"))?;
214 }
215 material.base_color_texture = text(pbr, "baseColorTexture", at)?;
216 if let Some(v) = member(pbr, "metallicFactor") {
217 material.metallic_factor = real(v, at("metallicFactor"))?;
218 }
219 if let Some(v) = member(pbr, "roughnessFactor") {
220 material.roughness_factor = real(v, at("roughnessFactor"))?;
221 }
222 material.metallic_roughness_texture = text(pbr, "metallicRoughnessTexture", at)?;
223 }
224 if let Some((texture, scale)) = texture_info(object, "normalTexture", "scale")? {
225 material.normal_texture = Some(NormalTexture { texture, scale });
226 }
227 if let Some((texture, strength)) = texture_info(object, "occlusionTexture", "strength")? {
228 material.occlusion_texture = Some(OcclusionTexture { texture, strength });
229 }
230 material.emissive_texture = text(object, "emissiveTexture", str::to_owned)?;
231 if let Some(v) = member(object, "emissiveFactor") {
232 material.emissive_factor =
233 reals(v).ok_or_else(|| wrong("emissiveFactor", "array of 3 numbers"))?;
234 }
235 if let Some(v) = member(object, "alphaMode") {
236 material.alpha_mode = match v.as_str() {
237 Some("OPAQUE") => AlphaMode::Opaque,
238 Some("MASK") => AlphaMode::Mask,
239 Some("BLEND") => AlphaMode::Blend,
240 _ => {
241 return Err(DecodeError::UnknownToken {
242 at: "alphaMode".to_owned(),
243 token: v.as_str().map_or_else(|| v.to_string(), str::to_owned),
244 expected: "OPAQUE, MASK, or BLEND",
245 })
246 }
247 };
248 }
249 if let Some(v) = member(object, "alphaCutoff") {
250 material.alpha_cutoff = real(v, "alphaCutoff")?;
251 }
252 if let Some(v) = member(object, "doubleSided") {
253 material.double_sided = v.as_bool().ok_or_else(|| wrong("doubleSided", "boolean"))?;
254 }
255 Ok(Some(material))
256 }
257}
258
259fn member<'v>(object: &'v Map<String, Value>, name: &str) -> Option<&'v Value> {
260 object.get(name).filter(|v| !v.is_null())
261}
262
263fn real(value: &Value, at: impl Into<String>) -> Result<f32, DecodeError> {
264 value
265 .as_f64()
266 .map(|v| v as f32)
267 .ok_or_else(|| wrong(at, "number"))
268}
269
270fn reals<const N: usize>(value: &Value) -> Option<[f32; N]> {
271 let items = value.as_array()?;
272 if items.len() != N {
273 return None;
274 }
275 let mut out = [0.0; N];
276 for (slot, item) in out.iter_mut().zip(items) {
277 *slot = item.as_f64()? as f32;
278 }
279 Some(out)
280}
281
282fn text(
283 object: &Map<String, Value>,
284 name: &str,
285 at: impl Fn(&str) -> String,
286) -> Result<Option<String>, DecodeError> {
287 member(object, name)
288 .map(|v| {
289 v.as_str()
290 .map(str::to_owned)
291 .ok_or_else(|| wrong(at(name), "string"))
292 })
293 .transpose()
294}
295
296fn texture_info(
297 object: &Map<String, Value>,
298 name: &str,
299 factor: &str,
300) -> Result<Option<(String, f32)>, DecodeError> {
301 let Some(value) = member(object, name) else {
302 return Ok(None);
303 };
304 let info = value.as_object().ok_or_else(|| wrong(name, "object"))?;
305 let at = |field: &str| format!("{name}.{field}");
306 let texture =
307 text(info, "texture", at)?.ok_or(DecodeError::MissingField { field: "texture" })?;
308 let factor = match member(info, factor) {
309 Some(v) => real(v, at(factor))?,
310 None => 1.0,
311 };
312 Ok(Some((texture, factor)))
313}
314
315#[derive(Debug, Clone, PartialEq, Default)]
317pub struct NodePresentation {
318 pub visibility: Option<Visibility>,
319 pub diffuse_color: Option<[f32; 3]>,
321 pub opacity: Option<f32>,
322 pub gltf_material: Option<GltfMaterial>,
323}
324
325impl NodePresentation {
326 pub fn from_attributes<A: Attributes + ?Sized>(attributes: &A) -> Result<Self, DecodeError> {
330 let visibility = attributes
331 .attribute(VISIBILITY)
332 .map(Visibility::from_value)
333 .transpose()?;
334 let diffuse_color = attributes
335 .attribute(DIFFUSE_COLOR)
336 .map(decode_diffuse_color)
337 .transpose()?;
338 let opacity = attributes
339 .attribute(OPACITY)
340 .map(decode_opacity)
341 .transpose()?;
342 let gltf_material = match attributes.attribute(GLTF_MATERIAL) {
343 Some(v) => GltfMaterial::from_value(v)?,
344 None => None,
345 };
346 Ok(Self {
347 visibility,
348 diffuse_color,
349 opacity,
350 gltf_material,
351 })
352 }
353
354 pub(crate) fn from_attributes_lenient<A: Attributes + ?Sized>(
358 attributes: &A,
359 errors: &mut Vec<(&'static str, DecodeError)>,
360 ) -> Self {
361 fn keep<T>(
362 id: &'static str,
363 result: Result<Option<T>, DecodeError>,
364 errors: &mut Vec<(&'static str, DecodeError)>,
365 ) -> Option<T> {
366 result.unwrap_or_else(|e| {
367 errors.push((id, e));
368 None
369 })
370 }
371 let get = |id| attributes.attribute(id);
372 Self {
373 visibility: keep(
374 VISIBILITY,
375 get(VISIBILITY).map(Visibility::from_value).transpose(),
376 errors,
377 ),
378 diffuse_color: keep(
379 DIFFUSE_COLOR,
380 get(DIFFUSE_COLOR).map(decode_diffuse_color).transpose(),
381 errors,
382 ),
383 opacity: keep(
384 OPACITY,
385 get(OPACITY).map(decode_opacity).transpose(),
386 errors,
387 ),
388 gltf_material: keep(
389 GLTF_MATERIAL,
390 get(GLTF_MATERIAL).map_or(Ok(None), GltfMaterial::from_value),
391 errors,
392 ),
393 }
394 }
395}
396
397fn decode_diffuse_color(value: &Value) -> Result<[f32; 3], DecodeError> {
398 reals(value).ok_or_else(|| wrong(DIFFUSE_COLOR, "array of 3 numbers"))
399}
400
401fn decode_opacity(value: &Value) -> Result<f32, DecodeError> {
402 real(value, OPACITY)
403}
404
405#[derive(Debug, Clone, Copy, PartialEq)]
407pub struct BasicMaterial {
408 pub color: [f32; 3],
410 pub opacity: f32,
412}
413
414impl BasicMaterial {
415 pub const DEFAULT: Self = Self {
418 color: [0.6; 3],
419 opacity: 1.0,
420 };
421
422 pub fn is_transparent(&self) -> bool {
424 self.opacity < 1.0
425 }
426}
427
428impl Default for BasicMaterial {
429 fn default() -> Self {
430 Self::DEFAULT
431 }
432}
433
434#[derive(Debug, Clone, PartialEq)]
436pub enum Material {
437 Basic(BasicMaterial),
439 Pbr(GltfMaterial),
441}
442
443pub fn resolve_basic_material<'a, I>(node_then_ancestors: I) -> BasicMaterial
448where
449 I: IntoIterator<Item = &'a NodePresentation>,
450{
451 node_then_ancestors
452 .into_iter()
453 .find_map(|p| {
454 p.diffuse_color.map(|color| BasicMaterial {
455 color,
456 opacity: p.opacity.unwrap_or(1.0),
457 })
458 })
459 .unwrap_or_default()
460}
461
462pub fn resolve_mesh_material<'a, I>(node_then_ancestors: I) -> Material
467where
468 I: IntoIterator<Item = &'a NodePresentation>,
469 I::IntoIter: Clone,
470{
471 let nodes = node_then_ancestors.into_iter();
472 match nodes.clone().find_map(|p| p.gltf_material.as_ref()) {
473 Some(pbr) => Material::Pbr(pbr.clone()),
474 None => Material::Basic(resolve_basic_material(nodes)),
475 }
476}