use ::gltf::Document;
use crate::diagnostics::AssetError;
use crate::material::{Color, TextureTransform};
use super::super::AssetPath;
#[derive(Debug, Clone, Copy)]
pub(super) struct ClearcoatExtension {
pub(super) factor: f32,
pub(super) roughness_factor: f32,
pub(super) texture: Option<ExtensionTextureInfo>,
pub(super) roughness_texture: Option<ExtensionTextureInfo>,
pub(super) normal_texture: Option<ExtensionTextureInfo>,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct SheenExtension {
pub(super) color_factor: Color,
pub(super) roughness_factor: f32,
pub(super) color_texture: Option<ExtensionTextureInfo>,
pub(super) roughness_texture: Option<ExtensionTextureInfo>,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct AnisotropyExtension {
pub(super) strength: f32,
pub(super) rotation: f32,
pub(super) texture: Option<ExtensionTextureInfo>,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct IridescenceExtension {
pub(super) factor: f32,
pub(super) ior: f32,
pub(super) thickness_minimum: f32,
pub(super) thickness_maximum: f32,
pub(super) texture: Option<ExtensionTextureInfo>,
pub(super) thickness_texture: Option<ExtensionTextureInfo>,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct DispersionExtension {
pub(super) factor: f32,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct TransmissionExtension {
pub(super) factor: f32,
pub(super) texture: Option<ExtensionTextureInfo>,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct IorExtension {
pub(super) ior: f32,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct VolumeExtension {
pub(super) thickness_factor: f32,
pub(super) thickness_texture: Option<ExtensionTextureInfo>,
pub(super) attenuation_distance: f32,
pub(super) attenuation_color: Color,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct ExtensionTextureInfo {
pub(super) index: usize,
pub(super) transform: Option<TextureTransform>,
pub(super) scale: Option<f32>,
}
pub(super) fn clearcoat_extension(
document: &Document,
material_index: usize,
) -> Option<ClearcoatExtension> {
let extension = document
.as_json()
.materials
.get(material_index)?
.extensions
.as_ref()?
.others
.get("KHR_materials_clearcoat")?;
Some(ClearcoatExtension {
factor: read_factor(extension, "clearcoatFactor").unwrap_or(0.0),
roughness_factor: read_factor(extension, "clearcoatRoughnessFactor").unwrap_or(0.0),
texture: read_extension_texture_info(extension, "clearcoatTexture"),
roughness_texture: read_extension_texture_info(extension, "clearcoatRoughnessTexture"),
normal_texture: read_extension_texture_info(extension, "clearcoatNormalTexture"),
})
}
pub(super) fn sheen_extension(
document: &Document,
material_index: usize,
) -> Option<SheenExtension> {
let extension = document
.as_json()
.materials
.get(material_index)?
.extensions
.as_ref()?
.others
.get("KHR_materials_sheen")?;
let color_factor = read_vec3_factor(extension, "sheenColorFactor").unwrap_or([0.0, 0.0, 0.0]);
Some(SheenExtension {
color_factor: Color::from_linear_rgb(color_factor[0], color_factor[1], color_factor[2]),
roughness_factor: read_factor(extension, "sheenRoughnessFactor").unwrap_or(0.0),
color_texture: read_extension_texture_info(extension, "sheenColorTexture"),
roughness_texture: read_extension_texture_info(extension, "sheenRoughnessTexture"),
})
}
pub(super) fn anisotropy_extension(
document: &Document,
material_index: usize,
) -> Option<AnisotropyExtension> {
let extension = document
.as_json()
.materials
.get(material_index)?
.extensions
.as_ref()?
.others
.get("KHR_materials_anisotropy")?;
Some(AnisotropyExtension {
strength: read_factor(extension, "anisotropyStrength").unwrap_or(0.0),
rotation: read_factor(extension, "anisotropyRotation").unwrap_or(0.0),
texture: read_extension_texture_info(extension, "anisotropyTexture"),
})
}
pub(super) fn iridescence_extension(
document: &Document,
material_index: usize,
) -> Option<IridescenceExtension> {
let extension = document
.as_json()
.materials
.get(material_index)?
.extensions
.as_ref()?
.others
.get("KHR_materials_iridescence")?;
Some(IridescenceExtension {
factor: read_factor(extension, "iridescenceFactor").unwrap_or(0.0),
ior: read_factor(extension, "iridescenceIor").unwrap_or(1.3),
thickness_minimum: read_factor(extension, "iridescenceThicknessMinimum").unwrap_or(100.0),
thickness_maximum: read_factor(extension, "iridescenceThicknessMaximum").unwrap_or(400.0),
texture: read_extension_texture_info(extension, "iridescenceTexture"),
thickness_texture: read_extension_texture_info(extension, "iridescenceThicknessTexture"),
})
}
pub(super) fn dispersion_extension(
document: &Document,
material_index: usize,
) -> Option<DispersionExtension> {
let extension = document
.as_json()
.materials
.get(material_index)?
.extensions
.as_ref()?
.others
.get("KHR_materials_dispersion")?;
Some(DispersionExtension {
factor: read_factor(extension, "dispersion").unwrap_or(0.0),
})
}
pub(super) fn transmission_extension(
document: &Document,
material_index: usize,
) -> Option<TransmissionExtension> {
let extension = document
.as_json()
.materials
.get(material_index)?
.extensions
.as_ref()?
.others
.get("KHR_materials_transmission")?;
Some(TransmissionExtension {
factor: read_factor(extension, "transmissionFactor").unwrap_or(0.0),
texture: read_extension_texture_info(extension, "transmissionTexture"),
})
}
pub(super) fn ior_extension(document: &Document, material_index: usize) -> Option<IorExtension> {
let extension = document
.as_json()
.materials
.get(material_index)?
.extensions
.as_ref()?
.others
.get("KHR_materials_ior")?;
Some(IorExtension {
ior: read_factor(extension, "ior").unwrap_or(1.5),
})
}
pub(super) fn volume_extension(
document: &Document,
material_index: usize,
) -> Option<VolumeExtension> {
let extension = document
.as_json()
.materials
.get(material_index)?
.extensions
.as_ref()?
.others
.get("KHR_materials_volume")?;
let attenuation_color =
read_vec3_factor(extension, "attenuationColor").unwrap_or([1.0, 1.0, 1.0]);
Some(VolumeExtension {
thickness_factor: read_factor(extension, "thicknessFactor").unwrap_or(0.0),
thickness_texture: read_extension_texture_info(extension, "thicknessTexture"),
attenuation_distance: read_factor(extension, "attenuationDistance")
.unwrap_or(f32::INFINITY),
attenuation_color: Color::from_linear_rgb(
attenuation_color[0],
attenuation_color[1],
attenuation_color[2],
),
})
}
pub(super) fn read_extension_texture_info(
extension: &serde_json::Value,
key: &str,
) -> Option<ExtensionTextureInfo> {
let info = extension.get(key)?;
let index = usize::try_from(info.get("index")?.as_u64()?).ok()?;
let transform = extension_texture_transform(
info.get("extensions")
.and_then(|extensions| extensions.get("KHR_texture_transform")),
);
Some(ExtensionTextureInfo {
index,
transform,
scale: read_factor(info, "scale"),
})
}
pub(super) fn validate_material_texture_indices(
path: &AssetPath,
document: &Document,
texture_count: usize,
) -> Result<(), AssetError> {
let raw = document.as_json();
for (material_index, material) in raw.materials.iter().enumerate() {
let pbr = &material.pbr_metallic_roughness;
validate_texture_info(
path,
"baseColorTexture",
pbr.base_color_texture
.as_ref()
.map(|info| info.index.value()),
texture_count,
)?;
validate_texture_info(
path,
"metallicRoughnessTexture",
pbr.metallic_roughness_texture
.as_ref()
.map(|info| info.index.value()),
texture_count,
)?;
for (slot, index) in [
(
"normalTexture",
material
.normal_texture
.as_ref()
.map(|info| info.index.value()),
),
(
"occlusionTexture",
material
.occlusion_texture
.as_ref()
.map(|info| info.index.value()),
),
(
"emissiveTexture",
material
.emissive_texture
.as_ref()
.map(|info| info.index.value()),
),
] {
validate_texture_info(path, slot, index, texture_count)?;
}
validate_extension_texture_slots(path, material_index, material, texture_count)?;
}
Ok(())
}
fn validate_extension_texture_slots(
path: &AssetPath,
_material_index: usize,
material: &::gltf::json::material::Material,
texture_count: usize,
) -> Result<(), AssetError> {
let Some(extensions) = material.extensions.as_ref() else {
return Ok(());
};
for (extension, slots) in [
(
"KHR_materials_clearcoat",
&[
("clearcoatTexture", "clearcoatTexture"),
("clearcoatRoughnessTexture", "clearcoatRoughnessTexture"),
("clearcoatNormalTexture", "clearcoatNormalTexture"),
][..],
),
(
"KHR_materials_sheen",
&[
("sheenColorTexture", "sheenColorTexture"),
("sheenRoughnessTexture", "sheenRoughnessTexture"),
][..],
),
(
"KHR_materials_anisotropy",
&[("anisotropyTexture", "anisotropyTexture")][..],
),
(
"KHR_materials_iridescence",
&[
("iridescenceTexture", "iridescenceTexture"),
("iridescenceThicknessTexture", "iridescenceThicknessTexture"),
][..],
),
(
"KHR_materials_transmission",
&[("transmissionTexture", "transmissionTexture")][..],
),
(
"KHR_materials_volume",
&[("thicknessTexture", "thicknessTexture")][..],
),
] {
let Some(value) = extensions.others.get(extension) else {
continue;
};
for (slot, key) in slots {
validate_texture_info(
path,
slot,
read_extension_texture_info(value, key).map(|info| info.index),
texture_count,
)?;
}
}
Ok(())
}
fn validate_texture_info(
path: &AssetPath,
material_slot: &'static str,
index: Option<usize>,
texture_count: usize,
) -> Result<(), AssetError> {
if let Some(index) = index
&& index >= texture_count
{
return Err(AssetError::MissingTexture {
path: path.as_str().to_string(),
material_slot: material_slot.to_string(),
texture_index: index,
help: "export the referenced image or remove the broken material slot",
});
}
Ok(())
}
fn read_factor(value: &serde_json::Value, key: &str) -> Option<f32> {
value
.get(key)?
.as_f64()
.filter(|value| value.is_finite())
.map(|value| value as f32)
}
pub(super) fn extension_texture_transform(
value: Option<&serde_json::Value>,
) -> Option<TextureTransform> {
let value = value?;
let offset = read_vec2(value, "offset").unwrap_or([0.0, 0.0]);
let rotation = value
.get("rotation")
.and_then(serde_json::Value::as_f64)
.map(|value| value as f32)
.unwrap_or(0.0);
let scale = read_vec2(value, "scale").unwrap_or([1.0, 1.0]);
let tex_coord = value
.get("texCoord")
.and_then(serde_json::Value::as_u64)
.and_then(|value| u32::try_from(value).ok());
Some(TextureTransform::new(offset, rotation, scale, tex_coord))
}
fn read_vec2(value: &serde_json::Value, key: &str) -> Option<[f32; 2]> {
let array = value.get(key)?.as_array()?;
let x = array.first()?.as_f64()? as f32;
let y = array.get(1)?.as_f64()? as f32;
Some([x, y])
}
fn read_vec3_factor(value: &serde_json::Value, key: &str) -> Option<[f32; 3]> {
let array = value.get(key)?.as_array()?;
let x = finite_f32(array.first()?.as_f64()?)?;
let y = finite_f32(array.get(1)?.as_f64()?)?;
let z = finite_f32(array.get(2)?.as_f64()?)?;
Some([x, y, z])
}
fn finite_f32(value: f64) -> Option<f32> {
value.is_finite().then_some(value as f32)
}