219 lines
7.1 KiB
Rust
219 lines
7.1 KiB
Rust
//! Mesh processing functionality for FBX files.
|
|
|
|
use crate::error::FbxError;
|
|
use crate::label::FbxAssetLabel;
|
|
use crate::loader::FbxLoaderSettings;
|
|
use bevy::asset::{Handle, LoadContext};
|
|
use bevy::prelude::*;
|
|
use bevy::mesh::{Indices, PrimitiveTopology, VertexAttributeValues};
|
|
use std::collections::HashMap;
|
|
|
|
/// Process all meshes from the FBX scene.
|
|
pub fn process_meshes(
|
|
scene: &ufbx::Scene,
|
|
settings: &FbxLoaderSettings,
|
|
load_context: &mut LoadContext,
|
|
) -> Result<
|
|
(
|
|
Vec<Handle<Mesh>>,
|
|
HashMap<Box<str>, Handle<Mesh>>,
|
|
Vec<ufbx::Matrix>,
|
|
Vec<Vec<String>>,
|
|
),
|
|
FbxError,
|
|
> {
|
|
let mut meshes = Vec::new();
|
|
let mut named_meshes = HashMap::new();
|
|
let mut transforms = Vec::new();
|
|
let mut mesh_material_info = Vec::new();
|
|
|
|
for (index, node) in scene.nodes.as_ref().iter().enumerate() {
|
|
let Some(mesh_ref) = node.mesh.as_ref() else {
|
|
continue;
|
|
};
|
|
let mesh = mesh_ref.as_ref();
|
|
|
|
if mesh.num_vertices == 0 || mesh.faces.as_ref().is_empty() {
|
|
continue;
|
|
}
|
|
|
|
// Group faces by material
|
|
let material_groups = group_faces_by_material(mesh);
|
|
|
|
// Create mesh for each material group
|
|
for (material_idx, indices) in material_groups.iter() {
|
|
let mesh_handle = create_mesh_from_group(
|
|
mesh,
|
|
indices,
|
|
index,
|
|
*material_idx,
|
|
settings,
|
|
load_context,
|
|
)?;
|
|
|
|
if *material_idx == 0 && !node.element.name.is_empty() {
|
|
named_meshes.insert(Box::from(node.element.name.as_ref()), mesh_handle.clone());
|
|
}
|
|
|
|
meshes.push(mesh_handle);
|
|
transforms.push(node.geometry_to_world);
|
|
|
|
let material_name = if *material_idx < mesh.materials.len() {
|
|
mesh.materials[*material_idx].element.name.to_string()
|
|
} else {
|
|
"default".to_string()
|
|
};
|
|
mesh_material_info.push(vec![material_name]);
|
|
}
|
|
}
|
|
|
|
Ok((meshes, named_meshes, transforms, mesh_material_info))
|
|
}
|
|
|
|
/// Group mesh faces by material index.
|
|
pub fn group_faces_by_material(mesh: &ufbx::Mesh) -> HashMap<usize, Vec<u32>> {
|
|
let mut material_groups: HashMap<usize, Vec<u32>> = HashMap::new();
|
|
let mut scratch = Vec::new();
|
|
|
|
if mesh.materials.is_empty() {
|
|
// No materials - create single group
|
|
let mut all_indices = Vec::new();
|
|
for &face in mesh.faces.as_ref().iter() {
|
|
scratch.clear();
|
|
ufbx::triangulate_face_vec(&mut scratch, mesh, face);
|
|
for idx in &scratch {
|
|
if (*idx as usize) < mesh.vertex_indices.len() {
|
|
all_indices.push(mesh.vertex_indices[*idx as usize]);
|
|
}
|
|
}
|
|
}
|
|
material_groups.insert(0, all_indices);
|
|
} else {
|
|
// Group by material
|
|
for (face_idx, &face) in mesh.faces.as_ref().iter().enumerate() {
|
|
let material_idx =
|
|
if !mesh.face_material.is_empty() && face_idx < mesh.face_material.len() {
|
|
mesh.face_material[face_idx] as usize
|
|
} else {
|
|
0
|
|
};
|
|
|
|
scratch.clear();
|
|
ufbx::triangulate_face_vec(&mut scratch, mesh, face);
|
|
|
|
let indices = material_groups.entry(material_idx).or_insert_with(Vec::new);
|
|
for idx in &scratch {
|
|
if (*idx as usize) < mesh.vertex_indices.len() {
|
|
indices.push(mesh.vertex_indices[*idx as usize]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
material_groups
|
|
}
|
|
|
|
/// Create a Bevy mesh from a material group.
|
|
pub fn create_mesh_from_group(
|
|
ufbx_mesh: &ufbx::Mesh,
|
|
indices: &[u32],
|
|
mesh_index: usize,
|
|
material_index: usize,
|
|
settings: &FbxLoaderSettings,
|
|
load_context: &mut LoadContext,
|
|
) -> Result<Handle<Mesh>, FbxError> {
|
|
let label = FbxAssetLabel::Mesh(mesh_index * 1000 + material_index).to_string();
|
|
|
|
let handle = load_context.labeled_asset_scope(label, |_| {
|
|
let mut bevy_mesh = Mesh::new(PrimitiveTopology::TriangleList, settings.load_meshes);
|
|
|
|
// Positions
|
|
let positions: Vec<[f32; 3]> = ufbx_mesh
|
|
.vertex_position
|
|
.values
|
|
.as_ref()
|
|
.iter()
|
|
.map(|v| [v.x as f32, v.y as f32, v.z as f32])
|
|
.collect();
|
|
bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
|
|
|
// Normals
|
|
if ufbx_mesh.vertex_normal.exists {
|
|
let normals: Vec<[f32; 3]> = (0..ufbx_mesh.num_vertices)
|
|
.map(|i| {
|
|
let n = ufbx_mesh.vertex_normal[i];
|
|
[n.x as f32, n.y as f32, n.z as f32]
|
|
})
|
|
.collect();
|
|
bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
|
|
}
|
|
|
|
// UVs
|
|
if ufbx_mesh.vertex_uv.exists {
|
|
let uvs: Vec<[f32; 2]> = (0..ufbx_mesh.num_vertices)
|
|
.map(|i| {
|
|
let uv = ufbx_mesh.vertex_uv[i];
|
|
[uv.x as f32, uv.y as f32]
|
|
})
|
|
.collect();
|
|
bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
|
|
}
|
|
|
|
// Skinning
|
|
if !ufbx_mesh.skin_deformers.is_empty() {
|
|
process_skinning_data(ufbx_mesh, &mut bevy_mesh);
|
|
}
|
|
|
|
// Indices
|
|
bevy_mesh.insert_indices(Indices::U32(indices.to_vec()));
|
|
|
|
Ok::<_, FbxError>(bevy_mesh)
|
|
})?;
|
|
|
|
Ok(handle)
|
|
}
|
|
|
|
/// Process skinning data for a mesh.
|
|
pub fn process_skinning_data(ufbx_mesh: &ufbx::Mesh, bevy_mesh: &mut Mesh) {
|
|
let skin_deformer = &ufbx_mesh.skin_deformers[0];
|
|
let mut joint_indices = vec![[0u16; 4]; ufbx_mesh.num_vertices];
|
|
let mut joint_weights = vec![[0.0f32; 4]; ufbx_mesh.num_vertices];
|
|
|
|
for vertex_index in 0..ufbx_mesh.num_vertices {
|
|
let mut weight_count = 0;
|
|
let mut total_weight = 0.0f32;
|
|
|
|
for (cluster_index, cluster) in skin_deformer.clusters.iter().enumerate() {
|
|
if weight_count >= 4 {
|
|
break;
|
|
}
|
|
|
|
for (i, &vert_idx) in cluster.vertices.iter().enumerate() {
|
|
if vert_idx as usize == vertex_index && i < cluster.weights.len() {
|
|
let weight = cluster.weights[i] as f32;
|
|
if weight > 0.0 {
|
|
joint_indices[vertex_index][weight_count] = cluster_index as u16;
|
|
joint_weights[vertex_index][weight_count] = weight;
|
|
total_weight += weight;
|
|
weight_count += 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Normalize weights
|
|
if total_weight > 0.0 {
|
|
for i in 0..weight_count {
|
|
joint_weights[vertex_index][i] /= total_weight;
|
|
}
|
|
}
|
|
}
|
|
|
|
bevy_mesh.insert_attribute(
|
|
Mesh::ATTRIBUTE_JOINT_INDEX,
|
|
VertexAttributeValues::Uint16x4(joint_indices),
|
|
);
|
|
bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_JOINT_WEIGHT, joint_weights);
|
|
}
|