use crate::validation::{Checked, Validate}; use crate::{extensions, image, Extras, Index}; use gltf_derive::Validate; use serde::{de, ser}; use serde_derive::{Deserialize, Serialize}; use std::fmt; /// Corresponds to `GL_NEAREST`. pub const NEAREST: u32 = 9728; /// Corresponds to `GL_LINEAR`. pub const LINEAR: u32 = 9729; /// Corresponds to `GL_NEAREST_MIPMAP_NEAREST`. pub const NEAREST_MIPMAP_NEAREST: u32 = 9984; /// Corresponds to `GL_LINEAR_MIPMAP_NEAREST`. pub const LINEAR_MIPMAP_NEAREST: u32 = 9985; /// Corresponds to `GL_NEAREST_MIPMAP_LINEAR`. pub const NEAREST_MIPMAP_LINEAR: u32 = 9986; /// Corresponds to `GL_LINEAR_MIPMAP_LINEAR`. pub const LINEAR_MIPMAP_LINEAR: u32 = 9987; /// Corresponds to `GL_CLAMP_TO_EDGE`. pub const CLAMP_TO_EDGE: u32 = 33_071; /// Corresponds to `GL_MIRRORED_REPEAT`. pub const MIRRORED_REPEAT: u32 = 33_648; /// Corresponds to `GL_REPEAT`. pub const REPEAT: u32 = 10_497; /// All valid magnification filters. pub const VALID_MAG_FILTERS: &[u32] = &[NEAREST, LINEAR]; /// All valid minification filters. pub const VALID_MIN_FILTERS: &[u32] = &[ NEAREST, LINEAR, NEAREST_MIPMAP_NEAREST, LINEAR_MIPMAP_NEAREST, NEAREST_MIPMAP_LINEAR, LINEAR_MIPMAP_LINEAR, ]; /// All valid wrapping modes. pub const VALID_WRAPPING_MODES: &[u32] = &[CLAMP_TO_EDGE, MIRRORED_REPEAT, REPEAT]; /// Magnification filter. #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] pub enum MagFilter { /// Corresponds to `GL_NEAREST`. Nearest = 1, /// Corresponds to `GL_LINEAR`. Linear, } impl MagFilter { /// OpenGL enum pub fn as_gl_enum(&self) -> u32 { match *self { MagFilter::Nearest => NEAREST, MagFilter::Linear => LINEAR, } } } /// Minification filter. #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] pub enum MinFilter { /// Corresponds to `GL_NEAREST`. Nearest = 1, /// Corresponds to `GL_LINEAR`. Linear, /// Corresponds to `GL_NEAREST_MIPMAP_NEAREST`. NearestMipmapNearest, /// Corresponds to `GL_LINEAR_MIPMAP_NEAREST`. LinearMipmapNearest, /// Corresponds to `GL_NEAREST_MIPMAP_LINEAR`. NearestMipmapLinear, /// Corresponds to `GL_LINEAR_MIPMAP_LINEAR`. LinearMipmapLinear, } impl MinFilter { /// Returns the corresponding OpenGL enum value. pub fn as_gl_enum(&self) -> u32 { match *self { MinFilter::Nearest => NEAREST, MinFilter::Linear => LINEAR, MinFilter::NearestMipmapNearest => NEAREST_MIPMAP_NEAREST, MinFilter::LinearMipmapNearest => LINEAR_MIPMAP_NEAREST, MinFilter::NearestMipmapLinear => NEAREST_MIPMAP_LINEAR, MinFilter::LinearMipmapLinear => LINEAR_MIPMAP_LINEAR, } } } /// Texture co-ordinate wrapping mode. #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] pub enum WrappingMode { /// Corresponds to `GL_CLAMP_TO_EDGE`. ClampToEdge = 1, /// Corresponds to `GL_MIRRORED_REPEAT`. MirroredRepeat, /// Corresponds to `GL_REPEAT`. Repeat, } impl WrappingMode { /// Returns the corresponding OpenGL enum value. pub fn as_gl_enum(&self) -> u32 { match *self { WrappingMode::ClampToEdge => CLAMP_TO_EDGE, WrappingMode::MirroredRepeat => MIRRORED_REPEAT, WrappingMode::Repeat => REPEAT, } } } /// Texture sampler properties for filtering and wrapping modes. #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] #[serde(default)] pub struct Sampler { /// Magnification filter. #[serde(rename = "magFilter")] #[serde(skip_serializing_if = "Option::is_none")] pub mag_filter: Option>, /// Minification filter. #[serde(rename = "minFilter")] #[serde(skip_serializing_if = "Option::is_none")] pub min_filter: Option>, /// Optional user-defined name for this object. #[cfg(feature = "names")] #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] pub name: Option, /// `s` wrapping mode. #[serde(default, rename = "wrapS")] pub wrap_s: Checked, /// `t` wrapping mode. #[serde(default, rename = "wrapT")] pub wrap_t: Checked, /// Extension specific data. #[serde(default, skip_serializing_if = "Option::is_none")] pub extensions: Option, /// Optional application specific data. #[serde(default)] #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] pub extras: Extras, } fn source_default() -> Index { Index::new(u32::MAX) } fn source_is_empty(source: &Index) -> bool { source.value() == u32::MAX as usize } fn source_validate(source: &Index, root: &crate::Root, path: P, report: &mut R) where P: Fn() -> crate::Path, R: FnMut(&dyn Fn() -> crate::Path, crate::validation::Error), { if cfg!(feature = "allow_empty_texture") { if !source_is_empty(source) { source.validate(root, path, report); } } else if source_is_empty(source) { report(&path, crate::validation::Error::Missing); } else { source.validate(root, &path, report); } } /// A texture and its sampler. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Texture { /// Optional user-defined name for this object. #[cfg(feature = "names")] #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] pub name: Option, /// The index of the sampler used by this texture. #[serde(skip_serializing_if = "Option::is_none")] pub sampler: Option>, /// The index of the image used by this texture. #[serde(default = "source_default", skip_serializing_if = "source_is_empty")] pub source: Index, /// Extension specific data. #[serde(default, skip_serializing_if = "Option::is_none")] pub extensions: Option, /// Optional application specific data. #[serde(default)] #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] pub extras: Extras, } impl Validate for Texture { fn validate(&self, root: &crate::Root, path: P, report: &mut R) where P: Fn() -> crate::Path, R: FnMut(&dyn Fn() -> crate::Path, crate::validation::Error), { self.sampler .validate(root, || path().field("sampler"), report); self.extensions .validate(root, || path().field("extensions"), report); source_validate(&self.source, root, || path().field("source"), report); } } #[derive(Clone, Debug, Deserialize, Serialize, Validate)] /// Reference to a `Texture`. pub struct Info { /// The index of the texture. pub index: Index, /// The set index of the texture's `TEXCOORD` attribute. #[serde(default, rename = "texCoord")] pub tex_coord: u32, /// Extension specific data. #[serde(default, skip_serializing_if = "Option::is_none")] pub extensions: Option, /// Optional application specific data. #[serde(default)] #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] pub extras: Extras, } impl<'de> de::Deserialize<'de> for Checked { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = Checked; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "any of: {:?}", VALID_MAG_FILTERS) } fn visit_u64(self, value: u64) -> Result where E: de::Error, { use self::MagFilter::*; use crate::validation::Checked::*; Ok(match value as u32 { NEAREST => Valid(Nearest), LINEAR => Valid(Linear), _ => Invalid, }) } } deserializer.deserialize_u64(Visitor) } } impl<'de> de::Deserialize<'de> for Checked { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = Checked; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "any of: {:?}", VALID_MIN_FILTERS) } fn visit_u64(self, value: u64) -> Result where E: de::Error, { use self::MinFilter::*; use crate::validation::Checked::*; Ok(match value as u32 { NEAREST => Valid(Nearest), LINEAR => Valid(Linear), NEAREST_MIPMAP_NEAREST => Valid(NearestMipmapNearest), LINEAR_MIPMAP_NEAREST => Valid(LinearMipmapNearest), NEAREST_MIPMAP_LINEAR => Valid(NearestMipmapLinear), LINEAR_MIPMAP_LINEAR => Valid(LinearMipmapLinear), _ => Invalid, }) } } deserializer.deserialize_u64(Visitor) } } impl ser::Serialize for MinFilter { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { serializer.serialize_u32(self.as_gl_enum()) } } impl<'de> de::Deserialize<'de> for Checked { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = Checked; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "any of: {:?}", VALID_WRAPPING_MODES) } fn visit_u64(self, value: u64) -> Result where E: de::Error, { use self::WrappingMode::*; use crate::validation::Checked::*; Ok(match value as u32 { CLAMP_TO_EDGE => Valid(ClampToEdge), MIRRORED_REPEAT => Valid(MirroredRepeat), REPEAT => Valid(Repeat), _ => Invalid, }) } } deserializer.deserialize_u64(Visitor) } } impl ser::Serialize for MagFilter { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { serializer.serialize_u32(self.as_gl_enum()) } } impl Default for WrappingMode { fn default() -> Self { WrappingMode::Repeat } } impl ser::Serialize for WrappingMode { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { serializer.serialize_u32(self.as_gl_enum()) } } #[cfg(test)] mod tests { #[test] fn deserialize_source() { let json = r#"{"asset":{"version":"2.0"},"textures":[{"source": 0}]}"#; let root = serde_json::from_str::(json).unwrap(); assert_eq!(0, root.textures[0].source.value()); } #[test] fn deserialize_empty_source() { let json = r#"{"asset":{"version":"2.0"},"textures":[{}]}"#; let root = serde_json::from_str::(json).unwrap(); assert_eq!(u32::MAX as usize, root.textures[0].source.value()); } #[test] fn serialize_source() { let root = crate::Root { textures: vec![crate::Texture { #[cfg(feature = "names")] name: None, sampler: None, source: crate::Index::new(0), extensions: None, extras: Default::default(), }], ..Default::default() }; let json = serde_json::to_string(&root).unwrap(); assert_eq!( r#"{"asset":{"version":"2.0"},"textures":[{"source":0}]}"#, &json ); } #[test] fn serialize_empty_source() { let root = crate::Root { textures: vec![crate::Texture { #[cfg(feature = "names")] name: None, sampler: None, source: crate::Index::new(u32::MAX), extensions: None, extras: Default::default(), }], ..Default::default() }; let json = serde_json::to_string(&root).unwrap(); assert_eq!(r#"{"asset":{"version":"2.0"},"textures":[{}]}"#, &json); } #[test] fn validate_source() { use crate::validation::{Error, Validate}; use crate::Path; let json = r#"{"asset":{"version":"2.0"},"textures":[{"source":0}]}"#; let root = serde_json::from_str::(json).unwrap(); let mut errors = Vec::new(); root.textures[0].validate( &root, || Path::new().field("textures").index(0), &mut |path, error| { errors.push((path(), error)); }, ); assert_eq!(1, errors.len()); let (path, error) = &errors[0]; assert_eq!("textures[0].source", path.as_str()); assert_eq!(Error::IndexOutOfBounds, *error); } #[test] fn validate_empty_source() { use crate::validation::{Error, Validate}; use crate::Path; let json = r#"{"asset":{"version":"2.0"},"textures":[{}]}"#; let root = serde_json::from_str::(json).unwrap(); let mut errors = Vec::new(); root.textures[0].validate( &root, || Path::new().field("textures").index(0), &mut |path, error| { errors.push((path(), error)); }, ); if cfg!(feature = "allow_empty_texture") { assert!(errors.is_empty()); } else { assert_eq!(1, errors.len()); let (path, error) = &errors[0]; assert_eq!("textures[0].source", path.as_str()); assert_eq!(Error::Missing, *error); } } }