//! GLTF material asset and simulator material-override encoding. #![allow( clippy::assigning_clones, clippy::cast_possible_truncation, clippy::collapsible_if, clippy::float_cmp, clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate, clippy::needless_pass_by_value, clippy::should_implement_trait )] use crate::{Error, assets::GltfAlphaMode}; use libremetaverse_structured_data::{OSD, OSDMap}; use libremetaverse_types::{AssetType, Color4, UUID, Vector2, Vector3}; use serde_json::{Value, json}; use std::collections::HashMap; const FLOAT_EPSILON: f32 = 1.192_092_9e-7; #[derive(Clone, Copy, Debug, PartialEq)] pub struct GltfTextureTransform { pub offset: Vector2, pub rotation: f32, pub scale: Vector2, } impl GltfTextureTransform { pub fn default() -> Self { Self { offset: Vector2::zero(), rotation: 0.0, scale: Vector2 { x: 1.0, y: 1.0 }, } } pub fn equals_with_gltf_texture_transform(&self, other: Self) -> bool { *self == other } pub fn equals_with_object(&self, _obj: Option) -> bool { false } pub fn get_hash_code(&self) -> i32 { (self.offset.x.to_bits() ^ self.offset.y.to_bits() ^ self.rotation.to_bits() ^ self.scale.x.to_bits() ^ self.scale.y.to_bits()) .cast_signed() } pub fn is_default(&self) -> bool { self.offset.x.abs() <= FLOAT_EPSILON && self.offset.y.abs() <= FLOAT_EPSILON && self.rotation.abs() <= FLOAT_EPSILON && (self.scale.x - 1.0).abs() <= FLOAT_EPSILON && (self.scale.y - 1.0).abs() <= FLOAT_EPSILON } } #[derive(Clone, Debug)] pub struct AssetMaterial { asset_id: UUID, asset_data: Vec, name: String, texture_ids: Vec, texture_transforms: Vec, base_color_factor: Color4, emissive_factor: Vector3, metallic_factor: f32, roughness_factor: f32, alpha_cutoff: f32, alpha_mode: GltfAlphaMode, double_sided: bool, override_alpha_mode: bool, override_double_sided: bool, } impl AssetMaterial { pub const TEXTURE_BASE_COLOR: i32 = 0; pub const TEXTURE_NORMAL: i32 = 1; pub const TEXTURE_METALLIC_ROUGHNESS: i32 = 2; pub const TEXTURE_EMISSIVE: i32 = 3; pub const TEXTURE_COUNT: i32 = 4; pub fn default() -> Self { Self { asset_id: UUID::zero(), asset_data: Vec::new(), name: String::new(), texture_ids: vec![UUID::zero(); Self::TEXTURE_COUNT as usize], texture_transforms: vec![GltfTextureTransform::default(); Self::TEXTURE_COUNT as usize], base_color_factor: Color4 { r: 1.0, g: 1.0, b: 1.0, a: 1.0, }, emissive_factor: Vector3::zero(), metallic_factor: 1.0, roughness_factor: 1.0, alpha_cutoff: 0.5, alpha_mode: GltfAlphaMode::Opaque, double_sided: false, override_alpha_mode: false, override_double_sided: false, } } pub fn gltf_override_null_uuid() -> UUID { UUID::new_with_string("ffffffff-ffff-ffff-ffff-ffffffffffff".into()) .expect("constant UUID is valid") } pub fn new_with_constructor() -> Result { Ok(Self::default()) } pub fn new_with_uuid_bytes(asset_id: UUID, asset_data: Vec) -> Result { if asset_data.len() > crate::asset_models::MAX_ASSET_BYTES { return Err(Error::Argument); } let mut material = Self { asset_id, asset_data, ..Self::default() }; if !material.decode()? { return Err(Error::Argument); } Ok(material) } fn slot(slot: i32) -> Result { usize::try_from(slot) .ok() .filter(|slot| *slot < Self::TEXTURE_COUNT as usize) .ok_or(Error::Argument) } pub fn set_texture_id( &mut self, slot: i32, id: UUID, for_override: Option, ) -> Result<(), Error> { let slot = Self::slot(slot)?; self.texture_ids[slot] = if for_override.unwrap_or(false) && id == UUID::zero() { Self::gltf_override_null_uuid() } else { id }; Ok(()) } pub fn set_texture_offset(&mut self, slot: i32, offset: Vector2) -> Result<(), Error> { self.texture_transforms[Self::slot(slot)?].offset = offset; Ok(()) } pub fn set_texture_scale(&mut self, slot: i32, scale: Vector2) -> Result<(), Error> { self.texture_transforms[Self::slot(slot)?].scale = scale; Ok(()) } pub fn set_texture_rotation(&mut self, slot: i32, rotation: f32) -> Result<(), Error> { self.texture_transforms[Self::slot(slot)?].rotation = rotation; Ok(()) } pub fn set_base_color_factor_with_color4_boolean( &mut self, mut color: Color4, for_override: Option, ) -> Result<(), Error> { if for_override.unwrap_or(false) && color == Self::default().base_color_factor { color.a -= FLOAT_EPSILON; } self.base_color_factor = color; Ok(()) } pub fn set_emissive_factor_with_vector3_boolean( &mut self, mut color: Vector3, for_override: Option, ) -> Result<(), Error> { if for_override.unwrap_or(false) && color == Vector3::zero() { color.x += FLOAT_EPSILON; } self.emissive_factor = color; Ok(()) } pub fn set_metallic_factor_with_single_boolean( &mut self, metallic: f32, for_override: Option, ) -> Result<(), Error> { if !metallic.is_finite() { return Err(Error::Argument); } self.metallic_factor = if for_override.unwrap_or(false) { metallic.min(1.0 - FLOAT_EPSILON) } else { metallic }; Ok(()) } pub fn set_roughness_factor_with_single_boolean( &mut self, roughness: f32, for_override: Option, ) -> Result<(), Error> { if !roughness.is_finite() { return Err(Error::Argument); } self.roughness_factor = if for_override.unwrap_or(false) { roughness.min(1.0 - FLOAT_EPSILON) } else { roughness }; Ok(()) } pub fn set_alpha_cutoff_with_single_boolean( &mut self, cutoff: f32, for_override: Option, ) -> Result<(), Error> { if !cutoff.is_finite() { return Err(Error::Argument); } self.alpha_cutoff = if for_override.unwrap_or(false) && cutoff == 0.5 { cutoff - FLOAT_EPSILON } else { cutoff }; Ok(()) } pub fn set_alpha_mode_with_gltf_alpha_mode_boolean( &mut self, mode: GltfAlphaMode, for_override: Option, ) -> Result<(), Error> { self.alpha_mode = mode; self.override_alpha_mode = for_override.unwrap_or(false); Ok(()) } pub fn set_double_sided_with_boolean_boolean( &mut self, double_sided: bool, for_override: Option, ) -> Result<(), Error> { self.double_sided = double_sided; self.override_double_sided = for_override.unwrap_or(false); Ok(()) } pub fn set_base_material(&mut self) -> Result<(), Error> { let transforms = self.texture_transforms.clone(); let asset_id = self.asset_id; *self = Self::default(); self.asset_id = asset_id; self.texture_transforms = transforms; Ok(()) } pub fn apply_override(&mut self, value: Self) -> Result<(), Error> { let sentinel = Self::gltf_override_null_uuid(); for slot in 0..Self::TEXTURE_COUNT as usize { if value.texture_ids[slot] == sentinel { self.texture_ids[slot] = UUID::zero(); } else if value.texture_ids[slot] != UUID::zero() { self.texture_ids[slot] = value.texture_ids[slot]; } if !value.texture_transforms[slot].is_default() { self.texture_transforms[slot] = value.texture_transforms[slot]; } } let defaults = Self::default(); if value.base_color_factor != defaults.base_color_factor { self.base_color_factor = value.base_color_factor; } if value.emissive_factor != defaults.emissive_factor { self.emissive_factor = value.emissive_factor; } if value.metallic_factor != defaults.metallic_factor { self.metallic_factor = value.metallic_factor; } if value.roughness_factor != defaults.roughness_factor { self.roughness_factor = value.roughness_factor; } if value.alpha_cutoff != defaults.alpha_cutoff { self.alpha_cutoff = value.alpha_cutoff; } if value.override_alpha_mode { self.alpha_mode = value.alpha_mode; } if value.override_double_sided { self.double_sided = value.double_sided; } Ok(()) } fn alpha_name(mode: GltfAlphaMode) -> &'static str { match mode { GltfAlphaMode::Opaque => "OPAQUE", GltfAlphaMode::Blend => "BLEND", GltfAlphaMode::Mask => "MASK", } } fn parse_alpha(value: Option<&str>) -> Result { match value.unwrap_or("OPAQUE") { "OPAQUE" => Ok(GltfAlphaMode::Opaque), "BLEND" => Ok(GltfAlphaMode::Blend), "MASK" => Ok(GltfAlphaMode::Mask), _ => Err(Error::Argument), } } fn texture_json( &self, slot: usize, images: &mut Vec, textures: &mut Vec, ) -> Option { let id = self.texture_ids[slot]; if id == UUID::zero() || id == Self::gltf_override_null_uuid() { return None; } let index = images.len(); images.push(json!({"uri": id.to_string()})); textures.push(json!({"source": index})); let mut info = json!({"index": index}); let transform = self.texture_transforms[slot]; if !transform.is_default() { info["extensions"] = json!({"KHR_texture_transform": {"offset": [transform.offset.x, transform.offset.y], "scale": [transform.scale.x, transform.scale.y], "rotation": transform.rotation}}); } Some(info) } pub fn to_json(&self) -> Result { let mut images = Vec::new(); let mut textures = Vec::new(); let base = self.texture_json( Self::TEXTURE_BASE_COLOR as usize, &mut images, &mut textures, ); let normal = self.texture_json(Self::TEXTURE_NORMAL as usize, &mut images, &mut textures); let metallic = self.texture_json( Self::TEXTURE_METALLIC_ROUGHNESS as usize, &mut images, &mut textures, ); let emissive = self.texture_json(Self::TEXTURE_EMISSIVE as usize, &mut images, &mut textures); let mut pbr = json!({"baseColorFactor": [self.base_color_factor.r,self.base_color_factor.g,self.base_color_factor.b,self.base_color_factor.a], "metallicFactor": self.metallic_factor, "roughnessFactor": self.roughness_factor}); if let Some(value) = base { pbr["baseColorTexture"] = value; } if let Some(value) = metallic { pbr["metallicRoughnessTexture"] = value; } let mut material = json!({"name": self.name, "pbrMetallicRoughness": pbr, "emissiveFactor": [self.emissive_factor.x,self.emissive_factor.y,self.emissive_factor.z], "alphaMode": Self::alpha_name(self.alpha_mode), "alphaCutoff": self.alpha_cutoff, "doubleSided": self.double_sided}); if let Some(value) = normal { material["normalTexture"] = value; } if let Some(value) = emissive { material["emissiveTexture"] = value; } let mut root = json!({"asset":{"version":"2.0"},"materials":[material]}); if !images.is_empty() { root["images"] = Value::Array(images); root["textures"] = Value::Array(textures); root["extensionsUsed"] = json!(["KHR_texture_transform"]); } serde_json::to_string(&root).map_err(|_| Error::InvalidOperation) } pub fn encode(&mut self) -> Result<(), Error> { self.asset_data = self.to_json()?.into_bytes(); Ok(()) } pub fn decode(&mut self) -> Result { let root: Value = serde_json::from_slice(&self.asset_data).map_err(|_| Error::Argument)?; let mat = root .get("materials") .and_then(Value::as_array) .and_then(|v| v.first()) .and_then(Value::as_object) .ok_or(Error::Argument)?; self.name = mat .get("name") .and_then(Value::as_str) .unwrap_or_default() .to_owned(); let pbr = mat.get("pbrMetallicRoughness").and_then(Value::as_object); if let Some(values) = pbr .and_then(|v| v.get("baseColorFactor")) .and_then(Value::as_array) { if values.len() == 4 { self.base_color_factor = Color4 { r: number(&values[0])?, g: number(&values[1])?, b: number(&values[2])?, a: number(&values[3])?, }; } } self.metallic_factor = pbr .and_then(|v| v.get("metallicFactor")) .map(number) .transpose()? .unwrap_or(1.0); self.roughness_factor = pbr .and_then(|v| v.get("roughnessFactor")) .map(number) .transpose()? .unwrap_or(1.0); if let Some(values) = mat.get("emissiveFactor").and_then(Value::as_array) { if values.len() == 3 { self.emissive_factor = Vector3 { x: number(&values[0])?, y: number(&values[1])?, z: number(&values[2])?, }; } } self.alpha_mode = Self::parse_alpha(mat.get("alphaMode").and_then(Value::as_str))?; self.alpha_cutoff = mat .get("alphaCutoff") .map(number) .transpose()? .unwrap_or(0.5); self.double_sided = mat .get("doubleSided") .and_then(Value::as_bool) .unwrap_or(false); let textures = root.get("textures").and_then(Value::as_array); let images = root.get("images").and_then(Value::as_array); for (slot, info) in [ (0, pbr.and_then(|v| v.get("baseColorTexture"))), (1, mat.get("normalTexture")), (2, pbr.and_then(|v| v.get("metallicRoughnessTexture"))), (3, mat.get("emissiveTexture")), ] { if let Some(info) = info { self.decode_texture(slot, info, textures, images)?; } } Ok(true) } fn decode_texture( &mut self, slot: usize, info: &Value, textures: Option<&Vec>, images: Option<&Vec>, ) -> Result<(), Error> { let index = info .get("index") .and_then(Value::as_u64) .and_then(|v| usize::try_from(v).ok()) .ok_or(Error::Argument)?; let source = textures .and_then(|v| v.get(index)) .and_then(|v| v.get("source")) .and_then(Value::as_u64) .and_then(|v| usize::try_from(v).ok()) .ok_or(Error::Argument)?; let uri = images .and_then(|v| v.get(source)) .and_then(|v| v.get("uri")) .and_then(Value::as_str) .ok_or(Error::Argument)?; self.texture_ids[slot] = UUID::new_with_string(uri.to_owned())?; if let Some(transform) = info.pointer("/extensions/KHR_texture_transform") { if let Some(v) = transform.get("offset").and_then(Value::as_array) { if v.len() == 2 { self.texture_transforms[slot].offset = Vector2 { x: number(&v[0])?, y: number(&v[1])?, }; } } if let Some(v) = transform.get("scale").and_then(Value::as_array) { if v.len() == 2 { self.texture_transforms[slot].scale = Vector2 { x: number(&v[0])?, y: number(&v[1])?, }; } } if let Some(v) = transform.get("rotation") { self.texture_transforms[slot].rotation = number(v)?; } } Ok(()) } pub fn to_override_osd(&self) -> Result { let defaults = Self::default(); let mut map = HashMap::new(); let mut tex = Vec::new(); let mut last = None; for (index, id) in self.texture_ids.iter().enumerate() { if *id != UUID::zero() { last = Some(index); } } if let Some(last) = last { for id in &self.texture_ids[..=last] { tex.push(OSD::UUID(*id)); } map.insert("tex".into(), OSD::Array(tex)); } if self.base_color_factor != defaults.base_color_factor { map.insert("bc".into(), color_osd(self.base_color_factor)); } if self.emissive_factor != defaults.emissive_factor { map.insert("ec".into(), vector3_osd(self.emissive_factor)); } if self.metallic_factor != defaults.metallic_factor { map.insert("mf".into(), OSD::Real(f64::from(self.metallic_factor))); } if self.roughness_factor != defaults.roughness_factor { map.insert("rf".into(), OSD::Real(f64::from(self.roughness_factor))); } if self.override_alpha_mode { map.insert("am".into(), OSD::Integer(self.alpha_mode as i32)); } if self.alpha_cutoff != defaults.alpha_cutoff { map.insert("ac".into(), OSD::Real(f64::from(self.alpha_cutoff))); } if self.override_double_sided { map.insert("ds".into(), OSD::Boolean(self.double_sided)); } let transforms: Vec<_> = self .texture_transforms .iter() .map(|t| { OSD::Map(HashMap::from([ ("o".into(), vector2_osd(t.offset)), ("s".into(), vector2_osd(t.scale)), ("r".into(), OSD::Real(f64::from(t.rotation))), ])) }) .collect(); if self.texture_transforms.iter().any(|v| !v.is_default()) { map.insert("ti".into(), OSD::Array(transforms)); } OSDMap::new_with_dictionary(map) } pub fn from_override_osd(data: OSDMap) -> Result { let map = data.snapshot(); let mut value = Self::default(); if let Some(OSD::Array(ids)) = map.get("tex") { for (slot, id) in ids.iter().take(Self::TEXTURE_COUNT as usize).enumerate() { value.texture_ids[slot] = id.as_uuid()?; } } if let Some(v) = map.get("bc") { value.base_color_factor = v.as_color4()?; if value.base_color_factor == Self::default().base_color_factor { value.base_color_factor.a -= FLOAT_EPSILON; } } if let Some(v) = map.get("ec") { value.emissive_factor = v.as_vector3()?; if value.emissive_factor == Vector3::zero() { value.emissive_factor.x += FLOAT_EPSILON; } } if let Some(v) = map.get("mf") { value.metallic_factor = (v.as_real()? as f32).min(1.0 - FLOAT_EPSILON); } if let Some(v) = map.get("rf") { value.roughness_factor = (v.as_real()? as f32).min(1.0 - FLOAT_EPSILON); } if let Some(v) = map.get("am") { value.alpha_mode = match v.as_integer()? { 0 => GltfAlphaMode::Opaque, 1 => GltfAlphaMode::Blend, 2 => GltfAlphaMode::Mask, _ => return Err(Error::Argument), }; value.override_alpha_mode = true; } if let Some(v) = map.get("ac") { value.alpha_cutoff = v.as_real()? as f32; if value.alpha_cutoff == 0.5 { value.alpha_cutoff -= FLOAT_EPSILON; } } if let Some(v) = map.get("ds") { value.double_sided = v.as_boolean()?; value.override_double_sided = true; } if let Some(OSD::Array(values)) = map.get("ti") { for (slot, transform) in values.iter().take(Self::TEXTURE_COUNT as usize).enumerate() { if let OSD::Map(t) = transform { if let Some(v) = t.get("o") { value.texture_transforms[slot].offset = v.as_vector2()?; } if let Some(v) = t.get("s") { value.texture_transforms[slot].scale = v.as_vector2()?; } if let Some(v) = t.get("r") { value.texture_transforms[slot].rotation = v.as_real()? as f32; } } } } Ok(value) } pub const fn asset_type(&self) -> AssetType { AssetType::Material } pub fn alpha_cutoff(&self) -> f32 { self.alpha_cutoff } pub fn set_alpha_cutoff(&mut self, value: f32) { self.alpha_cutoff = value; } pub fn alpha_mode(&self) -> GltfAlphaMode { self.alpha_mode } pub fn set_alpha_mode(&mut self, value: GltfAlphaMode) { self.alpha_mode = value; } pub fn base_color_factor(&self) -> Color4 { self.base_color_factor } pub fn set_base_color_factor(&mut self, value: Color4) { self.base_color_factor = value; } pub fn double_sided(&self) -> bool { self.double_sided } pub fn set_double_sided(&mut self, value: bool) { self.double_sided = value; } pub fn emissive_factor(&self) -> Vector3 { self.emissive_factor } pub fn set_emissive_factor(&mut self, value: Vector3) { self.emissive_factor = value; } pub fn metallic_factor(&self) -> f32 { self.metallic_factor } pub fn set_metallic_factor(&mut self, value: f32) { self.metallic_factor = value; } pub fn roughness_factor(&self) -> f32 { self.roughness_factor } pub fn set_roughness_factor(&mut self, value: f32) { self.roughness_factor = value; } pub fn name(&self) -> String { self.name.clone() } pub fn set_name(&mut self, value: String) { self.name = value; } pub fn override_alpha_mode(&self) -> bool { self.override_alpha_mode } pub fn set_override_alpha_mode(&mut self, value: bool) { self.override_alpha_mode = value; } pub fn override_double_sided(&self) -> bool { self.override_double_sided } pub fn set_override_double_sided(&mut self, value: bool) { self.override_double_sided = value; } pub fn texture_ids(&self) -> Vec { self.texture_ids.clone() } pub fn set_texture_ids(&mut self, value: Vec) { self.texture_ids = value; } pub fn texture_transforms(&self) -> Vec { self.texture_transforms.clone() } pub fn set_texture_transforms(&mut self, value: Vec) { self.texture_transforms = value; } } fn number(value: &Value) -> Result { value .as_f64() .map(|v| v as f32) .filter(|v| v.is_finite()) .ok_or(Error::Argument) } fn vector2_osd(value: Vector2) -> OSD { OSD::Array(vec![ OSD::Real(f64::from(value.x)), OSD::Real(f64::from(value.y)), ]) } fn vector3_osd(value: Vector3) -> OSD { OSD::Array(vec![ OSD::Real(f64::from(value.x)), OSD::Real(f64::from(value.y)), OSD::Real(f64::from(value.z)), ]) } fn color_osd(value: Color4) -> OSD { OSD::Array(vec![ OSD::Real(f64::from(value.r)), OSD::Real(f64::from(value.g)), OSD::Real(f64::from(value.b)), OSD::Real(f64::from(value.a)), ]) }