use ::gltf::Document;
use ::gltf::texture::Info;
use crate::diagnostics::AssetError;
use crate::material::{AlphaMode, Color, MaterialDesc, TextureColorSpace, TextureTransform};
use super::super::{AssetPath, AssetStorage, MaterialHandle};
use super::material_extensions::{
anisotropy_extension, clearcoat_extension, dispersion_extension, extension_texture_transform,
ior_extension, iridescence_extension, sheen_extension, transmission_extension,
validate_material_texture_indices, volume_extension,
};
use super::textures::{GltfTexture, texture_slot};
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
fn material_now_ms() -> f64 {
js_sys::Date::now()
}
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
fn log_material_step(label: &str, start_ms: f64) -> f64 {
let now = material_now_ms();
if crate::diagnostics::browser_timing_enabled() {
web_sys::console::log_1(
&format!("[scena-demo] material {label}: {:.1}ms", now - start_ms).into(),
);
}
now
}
pub(super) fn parse_materials(
path: &AssetPath,
document: &Document,
storage: &mut AssetStorage,
textures: &[GltfTexture],
) -> Result<Vec<MaterialHandle>, AssetError> {
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
let total_start = material_now_ms();
validate_material_texture_indices(path, document, textures.len())?;
let materials = document
.materials()
.filter_map(|material| material.index().map(|index| (index, material)))
.map(|(material_index, material)| {
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
let material_start = material_now_ms();
let pbr = material.pbr_metallic_roughness();
let base_color = pbr.base_color_factor();
let base_color =
Color::from_linear_rgba(base_color[0], base_color[1], base_color[2], base_color[3]);
let metallic = pbr.metallic_factor();
let roughness = pbr.roughness_factor();
let mut desc = if material.unlit() {
MaterialDesc::unlit(base_color)
} else {
MaterialDesc::pbr_metallic_roughness(base_color, metallic, roughness)
};
if let Some(info) = pbr.base_color_texture() {
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
let slot_start = material_now_ms();
let texture = texture_slot(
path,
"baseColorTexture",
info.texture().index(),
textures,
storage,
TextureColorSpace::Srgb,
)?;
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
{
log_material_step("baseColorTexture", slot_start);
}
desc = desc.with_base_color_texture(texture);
if let Some(transform) = texture_transform(&info) {
desc = desc.with_base_color_texture_transform(transform);
}
}
if let Some(info) = pbr.metallic_roughness_texture() {
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
let slot_start = material_now_ms();
let texture = texture_slot(
path,
"metallicRoughnessTexture",
info.texture().index(),
textures,
storage,
TextureColorSpace::Linear,
)?;
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
{
log_material_step("metallicRoughnessTexture", slot_start);
}
desc = desc.with_metallic_roughness_texture(texture);
if let Some(transform) = texture_transform(&info) {
desc = desc.with_metallic_roughness_texture_transform(transform);
}
}
if let Some(normal) = material.normal_texture() {
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
let slot_start = material_now_ms();
let texture = texture_slot(
path,
"normalTexture",
normal.texture().index(),
textures,
storage,
TextureColorSpace::Linear,
)?;
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
{
log_material_step("normalTexture", slot_start);
}
desc = desc
.with_normal_texture(texture)
.with_normal_scale(normal.scale());
if let Some(transform) = normal_texture_transform(&normal) {
desc = desc.with_normal_texture_transform(transform);
}
}
if let Some(occlusion) = material.occlusion_texture() {
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
let slot_start = material_now_ms();
let texture = texture_slot(
path,
"occlusionTexture",
occlusion.texture().index(),
textures,
storage,
TextureColorSpace::Linear,
)?;
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
{
log_material_step("occlusionTexture", slot_start);
}
desc = desc
.with_occlusion_texture(texture)
.with_occlusion_strength(occlusion.strength());
if let Some(transform) = occlusion_texture_transform(&occlusion) {
desc = desc.with_occlusion_texture_transform(transform);
}
}
if let Some(info) = material.emissive_texture() {
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
let slot_start = material_now_ms();
let texture = texture_slot(
path,
"emissiveTexture",
info.texture().index(),
textures,
storage,
TextureColorSpace::Srgb,
)?;
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
{
log_material_step("emissiveTexture", slot_start);
}
desc = desc.with_emissive_texture(texture);
if let Some(transform) = texture_transform(&info) {
desc = desc.with_emissive_texture_transform(transform);
}
}
let emissive = material.emissive_factor();
if emissive != [0.0, 0.0, 0.0] {
desc = desc.with_emissive(Color::from_linear_rgb(
emissive[0],
emissive[1],
emissive[2],
));
}
if let Some(strength) = material.emissive_strength() {
desc = desc.with_emissive_strength(strength);
}
if let Some(clearcoat) = clearcoat_extension(document, material_index) {
desc = desc
.with_clearcoat_factor(clearcoat.factor)
.with_clearcoat_roughness_factor(clearcoat.roughness_factor);
if let Some(info) = clearcoat.texture {
let texture = texture_slot(
path,
"clearcoatTexture",
info.index,
textures,
storage,
TextureColorSpace::Linear,
)?;
desc = desc.with_clearcoat_texture(texture);
if let Some(transform) = info.transform {
desc = desc.with_clearcoat_texture_transform(transform);
}
}
if let Some(info) = clearcoat.roughness_texture {
let texture = texture_slot(
path,
"clearcoatRoughnessTexture",
info.index,
textures,
storage,
TextureColorSpace::Linear,
)?;
desc = desc.with_clearcoat_roughness_texture(texture);
if let Some(transform) = info.transform {
desc = desc.with_clearcoat_roughness_texture_transform(transform);
}
}
if let Some(info) = clearcoat.normal_texture {
let texture = texture_slot(
path,
"clearcoatNormalTexture",
info.index,
textures,
storage,
TextureColorSpace::Linear,
)?;
desc = desc
.with_clearcoat_normal_texture(texture)
.with_clearcoat_normal_scale(info.scale.unwrap_or(1.0));
if let Some(transform) = info.transform {
desc = desc.with_clearcoat_normal_texture_transform(transform);
}
}
}
if let Some(sheen) = sheen_extension(document, material_index) {
desc = desc
.with_sheen_color_factor(sheen.color_factor)
.with_sheen_roughness_factor(sheen.roughness_factor);
if let Some(info) = sheen.color_texture {
let texture = texture_slot(
path,
"sheenColorTexture",
info.index,
textures,
storage,
TextureColorSpace::Srgb,
)?;
desc = desc.with_sheen_color_texture(texture);
if let Some(transform) = info.transform {
desc = desc.with_sheen_color_texture_transform(transform);
}
}
if let Some(info) = sheen.roughness_texture {
let texture = texture_slot(
path,
"sheenRoughnessTexture",
info.index,
textures,
storage,
TextureColorSpace::Linear,
)?;
desc = desc.with_sheen_roughness_texture(texture);
if let Some(transform) = info.transform {
desc = desc.with_sheen_roughness_texture_transform(transform);
}
}
}
if let Some(anisotropy) = anisotropy_extension(document, material_index) {
desc = desc
.with_anisotropy_strength_factor(anisotropy.strength)
.with_anisotropy_rotation_radians(anisotropy.rotation);
if let Some(info) = anisotropy.texture {
let texture = texture_slot(
path,
"anisotropyTexture",
info.index,
textures,
storage,
TextureColorSpace::Linear,
)?;
desc = desc.with_anisotropy_texture(texture);
if let Some(transform) = info.transform {
desc = desc.with_anisotropy_texture_transform(transform);
}
}
}
if let Some(iridescence) = iridescence_extension(document, material_index) {
desc = desc
.with_iridescence_factor(iridescence.factor)
.with_iridescence_ior(iridescence.ior)
.with_iridescence_thickness_range_nm(
iridescence.thickness_minimum,
iridescence.thickness_maximum,
);
if let Some(info) = iridescence.texture {
let texture = texture_slot(
path,
"iridescenceTexture",
info.index,
textures,
storage,
TextureColorSpace::Linear,
)?;
desc = desc.with_iridescence_texture(texture);
if let Some(transform) = info.transform {
desc = desc.with_iridescence_texture_transform(transform);
}
}
if let Some(info) = iridescence.thickness_texture {
let texture = texture_slot(
path,
"iridescenceThicknessTexture",
info.index,
textures,
storage,
TextureColorSpace::Linear,
)?;
desc = desc.with_iridescence_thickness_texture(texture);
if let Some(transform) = info.transform {
desc = desc.with_iridescence_thickness_texture_transform(transform);
}
}
}
if let Some(dispersion) = dispersion_extension(document, material_index) {
desc = desc.with_dispersion_factor(dispersion.factor);
}
if let Some(transmission) = transmission_extension(document, material_index) {
desc = desc.with_transmission_factor(transmission.factor);
if let Some(info) = transmission.texture {
let texture = texture_slot(
path,
"transmissionTexture",
info.index,
textures,
storage,
TextureColorSpace::Linear,
)?;
desc = desc.with_transmission_texture(texture);
if let Some(transform) = info.transform {
desc = desc.with_transmission_texture_transform(transform);
}
}
}
if let Some(ior) = ior_extension(document, material_index) {
desc = desc.with_ior(ior.ior);
}
if let Some(volume) = volume_extension(document, material_index) {
desc = desc
.with_thickness_factor(volume.thickness_factor)
.with_attenuation_distance(volume.attenuation_distance)
.with_attenuation_color(volume.attenuation_color);
if let Some(info) = volume.thickness_texture {
let texture = texture_slot(
path,
"thicknessTexture",
info.index,
textures,
storage,
TextureColorSpace::Linear,
)?;
desc = desc.with_thickness_texture(texture);
if let Some(transform) = info.transform {
desc = desc.with_thickness_texture_transform(transform);
}
}
}
desc = match material.alpha_mode() {
::gltf::material::AlphaMode::Opaque => desc,
::gltf::material::AlphaMode::Mask => desc.with_alpha_mode(AlphaMode::Mask {
cutoff: material.alpha_cutoff().unwrap_or(0.5),
}),
::gltf::material::AlphaMode::Blend => desc.with_alpha_mode(AlphaMode::Blend),
};
if material.double_sided() {
desc = desc.with_double_sided(true);
}
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
{
log_material_step("material total", material_start);
}
Ok(storage.materials.insert(desc))
})
.collect();
#[cfg(all(target_arch = "wasm32", feature = "demo-page"))]
{
log_material_step("parse_materials total", total_start);
}
materials
}
fn texture_transform(info: &Info<'_>) -> Option<TextureTransform> {
info.texture_transform().map(|transform| {
TextureTransform::new(
transform.offset(),
transform.rotation(),
transform.scale(),
transform.tex_coord(),
)
})
}
fn normal_texture_transform(
normal: &::gltf::material::NormalTexture<'_>,
) -> Option<TextureTransform> {
extension_texture_transform(normal.extension_value("KHR_texture_transform"))
}
fn occlusion_texture_transform(
occlusion: &::gltf::material::OcclusionTexture<'_>,
) -> Option<TextureTransform> {
extension_texture_transform(occlusion.extension_value("KHR_texture_transform"))
}