use std::path::Path;
use glam::{Vec2, Vec3};
use log::info;
use anvilkit_core::error::{AnvilKitError, Result};
use crate::mesh::MeshData;
use crate::material::{TextureData, MaterialData};
use crate::scene::SceneData;
pub fn load_gltf_mesh(path: impl AsRef<Path>) -> Result<MeshData> {
let path = path.as_ref();
info!("加载 glTF 文件: {}", path.display());
let (document, buffers, _images) = gltf::import(path)
.map_err(|e| AnvilKitError::asset_with_path(
format!("glTF 导入失败: {}", e),
path.to_string_lossy().to_string(),
))?;
let mesh = document.meshes().next()
.ok_or_else(|| AnvilKitError::asset_with_path(
"glTF 文件中没有网格".to_string(),
path.to_string_lossy().to_string(),
))?;
info!("网格名称: {:?}", mesh.name());
let primitive = mesh.primitives().next()
.ok_or_else(|| AnvilKitError::asset_with_path(
"网格中没有图元".to_string(),
path.to_string_lossy().to_string(),
))?;
let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
let positions: Vec<Vec3> = reader.read_positions()
.ok_or_else(|| AnvilKitError::asset_with_path(
"网格缺少顶点位置属性".to_string(),
path.to_string_lossy().to_string(),
))?
.map(|p| Vec3::from(p))
.collect();
let normals: Vec<Vec3> = reader.read_normals()
.ok_or_else(|| AnvilKitError::asset_with_path(
"网格缺少法线属性".to_string(),
path.to_string_lossy().to_string(),
))?
.map(|n| Vec3::from(n))
.collect();
let texcoords: Vec<Vec2> = reader.read_tex_coords(0)
.map(|tc| tc.into_f32().map(|uv| Vec2::from(uv)).collect())
.unwrap_or_else(|| vec![Vec2::ZERO; positions.len()]);
let tangents: Vec<[f32; 4]> = reader.read_tangents()
.map(|t| t.collect())
.unwrap_or_else(|| vec![[1.0, 0.0, 0.0, 1.0]; positions.len()]);
let indices: Vec<u32> = reader.read_indices()
.ok_or_else(|| AnvilKitError::asset_with_path(
"网格缺少索引数据".to_string(),
path.to_string_lossy().to_string(),
))?
.into_u32()
.collect();
info!("加载完成: {} 顶点, {} 索引", positions.len(), indices.len());
Ok(MeshData {
positions,
normals,
texcoords,
tangents,
indices,
})
}
pub fn load_gltf_scene(path: impl AsRef<Path>) -> Result<SceneData> {
let path = path.as_ref();
info!("加载 glTF 场景: {}", path.display());
let (document, buffers, images) = gltf::import(path)
.map_err(|e| AnvilKitError::asset_with_path(
format!("glTF 导入失败: {}", e),
path.to_string_lossy().to_string(),
))?;
let gltf_mesh = document.meshes().next()
.ok_or_else(|| AnvilKitError::asset_with_path(
"glTF 文件中没有网格".to_string(),
path.to_string_lossy().to_string(),
))?;
let primitive = gltf_mesh.primitives().next()
.ok_or_else(|| AnvilKitError::asset_with_path(
"网格中没有图元".to_string(),
path.to_string_lossy().to_string(),
))?;
let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
let positions: Vec<Vec3> = reader.read_positions()
.ok_or_else(|| AnvilKitError::asset_with_path(
"网格缺少顶点位置属性".to_string(),
path.to_string_lossy().to_string(),
))?
.map(Vec3::from)
.collect();
let normals: Vec<Vec3> = reader.read_normals()
.ok_or_else(|| AnvilKitError::asset_with_path(
"网格缺少法线属性".to_string(),
path.to_string_lossy().to_string(),
))?
.map(Vec3::from)
.collect();
let texcoords: Vec<Vec2> = reader.read_tex_coords(0)
.map(|tc| tc.into_f32().map(Vec2::from).collect())
.unwrap_or_else(|| vec![Vec2::ZERO; positions.len()]);
let tangents: Vec<[f32; 4]> = reader.read_tangents()
.map(|t| t.collect())
.unwrap_or_else(|| vec![[1.0, 0.0, 0.0, 1.0]; positions.len()]);
let indices: Vec<u32> = reader.read_indices()
.ok_or_else(|| AnvilKitError::asset_with_path(
"网格缺少索引数据".to_string(),
path.to_string_lossy().to_string(),
))?
.into_u32()
.collect();
let mesh = MeshData { positions, normals, texcoords, tangents, indices };
let material = extract_material(&primitive, &images);
info!("场景加载完成: {} 顶点, {} 索引, 纹理: {}, 法线贴图: {}",
mesh.vertex_count(), mesh.index_count(),
material.base_color_texture.is_some(),
material.normal_texture.is_some());
Ok(SceneData { mesh, material })
}
pub fn load_gltf_scene_multi(path: impl AsRef<Path>) -> Result<crate::scene::MultiMeshScene> {
let path = path.as_ref();
info!("加载 glTF 多子网格场景: {}", path.display());
let (document, buffers, images) = gltf::import(path)
.map_err(|e| AnvilKitError::asset_with_path(
format!("glTF 导入失败: {}", e),
path.to_string_lossy().to_string(),
))?;
let mut submeshes = Vec::new();
for gltf_mesh in document.meshes() {
for primitive in gltf_mesh.primitives() {
let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
let Some(positions) = reader.read_positions().map(|p| p.map(Vec3::from).collect::<Vec<_>>()) else {
continue;
};
let normals: Vec<Vec3> = reader.read_normals()
.map(|n| n.map(Vec3::from).collect())
.unwrap_or_else(|| vec![Vec3::Z; positions.len()]);
let texcoords: Vec<Vec2> = reader.read_tex_coords(0)
.map(|tc| tc.into_f32().map(Vec2::from).collect())
.unwrap_or_else(|| vec![Vec2::ZERO; positions.len()]);
let tangents: Vec<[f32; 4]> = reader.read_tangents()
.map(|t| t.collect())
.unwrap_or_else(|| vec![[1.0, 0.0, 0.0, 1.0]; positions.len()]);
let Some(indices) = reader.read_indices().map(|i| i.into_u32().collect::<Vec<_>>()) else {
continue;
};
let mesh = MeshData { positions, normals, texcoords, tangents, indices };
let material = extract_material(&primitive, &images);
info!("子网格: {} 顶点, {} 索引", mesh.vertex_count(), mesh.index_count());
submeshes.push(crate::scene::Submesh { mesh, material });
}
}
info!("多子网格场景加载完成: {} 个子网格", submeshes.len());
Ok(crate::scene::MultiMeshScene { submeshes })
}
fn extract_material(
primitive: &gltf::Primitive<'_>,
images: &[gltf::image::Data],
) -> MaterialData {
let gltf_material = primitive.material();
let pbr = gltf_material.pbr_metallic_roughness();
let base_color_factor = pbr.base_color_factor();
let base_color_texture = pbr.base_color_texture()
.and_then(|tex_info| {
let texture = tex_info.texture();
let source = texture.source();
let image_index = source.index();
if image_index < images.len() {
let image = &images[image_index];
let rgba_data = convert_to_rgba8(image);
rgba_data.map(|data| {
info!("加载基础色纹理: {}x{}", image.width, image.height);
TextureData {
width: image.width,
height: image.height,
data,
}
})
} else {
None
}
});
let metallic_factor = pbr.metallic_factor();
let roughness_factor = pbr.roughness_factor();
let (normal_texture, normal_scale) = gltf_material.normal_texture()
.map(|normal_tex| {
let scale = normal_tex.scale();
let texture = normal_tex.texture();
let source = texture.source();
let image_index = source.index();
let tex_data = if image_index < images.len() {
let image = &images[image_index];
convert_to_rgba8(image).map(|data| {
info!("加载法线贴图: {}x{}", image.width, image.height);
TextureData {
width: image.width,
height: image.height,
data,
}
})
} else {
None
};
(tex_data, scale)
})
.unwrap_or((None, 1.0));
let metallic_roughness_texture = pbr.metallic_roughness_texture()
.and_then(|tex_info| extract_texture_by_source(&tex_info.texture(), images, "金属度/粗糙度"));
let occlusion_texture = gltf_material.occlusion_texture()
.and_then(|tex_info| extract_texture_by_source(&tex_info.texture(), images, "AO"));
let emissive_texture = gltf_material.emissive_texture()
.and_then(|tex_info| extract_texture_by_source(&tex_info.texture(), images, "自发光"));
let emissive_factor = gltf_material.emissive_factor();
info!("材质: metallic={}, roughness={}, normal={}, mr_tex={}, ao={}, emissive={}",
metallic_factor, roughness_factor, normal_texture.is_some(),
metallic_roughness_texture.is_some(), occlusion_texture.is_some(),
emissive_texture.is_some());
MaterialData {
base_color_texture,
base_color_factor,
metallic_factor,
roughness_factor,
normal_texture,
normal_scale,
metallic_roughness_texture,
occlusion_texture,
emissive_texture,
emissive_factor,
}
}
fn extract_texture_by_source(
texture: &gltf::Texture<'_>,
images: &[gltf::image::Data],
label: &str,
) -> Option<TextureData> {
let image_index = texture.source().index();
if image_index < images.len() {
let image = &images[image_index];
convert_to_rgba8(image).map(|data| {
info!("加载{}纹理: {}x{}", label, image.width, image.height);
TextureData {
width: image.width,
height: image.height,
data,
}
})
} else {
None
}
}
fn convert_to_rgba8(image: &gltf::image::Data) -> Option<Vec<u8>> {
match image.format {
gltf::image::Format::R8G8B8A8 => Some(image.pixels.clone()),
gltf::image::Format::R8G8B8 => {
let pixel_count = (image.width * image.height) as usize;
let mut rgba = Vec::with_capacity(pixel_count * 4);
for chunk in image.pixels.chunks(3) {
rgba.push(chunk[0]);
rgba.push(chunk[1]);
rgba.push(chunk[2]);
rgba.push(255);
}
Some(rgba)
}
_ => {
log::warn!("不支持的纹理格式: {:?}", image.format);
None
}
}
}