771 lines
25 KiB
Rust
771 lines
25 KiB
Rust
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
#![forbid(unsafe_code)]
|
|
#![doc(
|
|
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
|
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
|
)]
|
|
|
|
//!
|
|
//! Loader for FBX scenes using [`ufbx`](https://github.com/ufbx/ufbx-rust).
|
|
//! The implementation is intentionally minimal and focuses on importing
|
|
//! mesh geometry into Bevy.
|
|
|
|
use bevy_app::prelude::*;
|
|
use bevy_asset::{
|
|
io::Reader, Asset, AssetApp, AssetLoader, Handle, LoadContext, RenderAssetUsages,
|
|
};
|
|
use bevy_ecs::prelude::*;
|
|
use bevy_mesh::{Indices, Mesh, PrimitiveTopology};
|
|
use bevy_pbr::{MeshMaterial3d, StandardMaterial};
|
|
|
|
use bevy_platform::collections::HashMap;
|
|
use bevy_reflect::TypePath;
|
|
use bevy_render::mesh::Mesh3d;
|
|
use bevy_render::prelude::Visibility;
|
|
use bevy_scene::Scene;
|
|
|
|
use bevy_animation::AnimationClip;
|
|
use bevy_transform::prelude::*;
|
|
use bevy_math::{Mat4, Vec3, Quat};
|
|
use bevy_color::Color;
|
|
use bevy_image::Image;
|
|
use bevy_render::alpha::AlphaMode;
|
|
|
|
mod label;
|
|
pub use label::FbxAssetLabel;
|
|
|
|
pub mod prelude {
|
|
//! Commonly used items.
|
|
pub use crate::{Fbx, FbxAssetLabel, FbxPlugin};
|
|
}
|
|
|
|
/// Type of relationship between two objects in the FBX hierarchy.
|
|
#[derive(Debug, Clone)]
|
|
pub enum FbxConnKind {
|
|
/// Standard parent-child connection.
|
|
Parent,
|
|
/// Connection from an object to one of its properties.
|
|
ObjectProperty,
|
|
/// Constraint relationship.
|
|
Constraint,
|
|
}
|
|
|
|
/// Simplified connection entry extracted from the FBX file.
|
|
#[derive(Debug, Clone)]
|
|
pub struct FbxConnection {
|
|
/// Source object identifier.
|
|
pub src: String,
|
|
/// Destination object identifier.
|
|
pub dst: String,
|
|
/// The type of this connection.
|
|
pub kind: FbxConnKind,
|
|
}
|
|
|
|
/// Handedness of a coordinate system.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum Handedness {
|
|
/// Right handed coordinate system.
|
|
Right,
|
|
/// Left handed coordinate system.
|
|
Left,
|
|
}
|
|
|
|
/// Coordinate axes definition stored in an FBX file.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct FbxAxisSystem {
|
|
/// Up axis.
|
|
pub up: Vec3,
|
|
/// Forward axis.
|
|
pub front: Vec3,
|
|
/// Coordinate system handedness.
|
|
pub handedness: Handedness,
|
|
}
|
|
|
|
/// Metadata found in the FBX header.
|
|
#[derive(Debug, Clone)]
|
|
pub struct FbxMeta {
|
|
/// Creator string.
|
|
pub creator: Option<String>,
|
|
/// Timestamp when the file was created.
|
|
pub creation_time: Option<String>,
|
|
/// Original application that generated the file.
|
|
pub original_application: Option<String>,
|
|
}
|
|
|
|
/// Placeholder type for skeleton data.
|
|
#[derive(Asset, Debug, Clone, TypePath)]
|
|
pub struct Skeleton;
|
|
|
|
/// Types of textures supported in FBX materials.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub enum FbxTextureType {
|
|
/// Base color (albedo) texture.
|
|
BaseColor,
|
|
/// Normal map texture.
|
|
Normal,
|
|
/// Metallic texture.
|
|
Metallic,
|
|
/// Roughness texture.
|
|
Roughness,
|
|
/// Emission texture.
|
|
Emission,
|
|
/// Ambient occlusion texture.
|
|
AmbientOcclusion,
|
|
/// Height/displacement texture.
|
|
Height,
|
|
}
|
|
|
|
/// Texture wrapping modes.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum FbxWrapMode {
|
|
/// Repeat the texture.
|
|
Repeat,
|
|
/// Clamp to edge.
|
|
Clamp,
|
|
}
|
|
|
|
/// Texture information from FBX.
|
|
#[derive(Debug, Clone)]
|
|
pub struct FbxTexture {
|
|
/// Texture name.
|
|
pub name: String,
|
|
/// Relative filename.
|
|
pub filename: String,
|
|
/// Absolute filename if available.
|
|
pub absolute_filename: String,
|
|
/// UV set name.
|
|
pub uv_set: String,
|
|
/// UV transformation matrix.
|
|
pub uv_transform: Mat4,
|
|
/// U-axis wrapping mode.
|
|
pub wrap_u: FbxWrapMode,
|
|
/// V-axis wrapping mode.
|
|
pub wrap_v: FbxWrapMode,
|
|
}
|
|
|
|
/// Enhanced material representation from FBX.
|
|
#[derive(Debug, Clone)]
|
|
pub struct FbxMaterial {
|
|
/// Material name.
|
|
pub name: String,
|
|
/// Base color (albedo).
|
|
pub base_color: Color,
|
|
/// Metallic factor.
|
|
pub metallic: f32,
|
|
/// Roughness factor.
|
|
pub roughness: f32,
|
|
/// Emission color.
|
|
pub emission: Color,
|
|
/// Normal map scale.
|
|
pub normal_scale: f32,
|
|
/// Alpha value.
|
|
pub alpha: f32,
|
|
/// Associated textures.
|
|
pub textures: HashMap<FbxTextureType, FbxTexture>,
|
|
}
|
|
|
|
/// Types of lights supported in FBX.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum FbxLightType {
|
|
/// Directional light.
|
|
Directional,
|
|
/// Point light.
|
|
Point,
|
|
/// Spot light with cone.
|
|
Spot,
|
|
/// Area light.
|
|
Area,
|
|
/// Volume light.
|
|
Volume,
|
|
}
|
|
|
|
/// Light definition from FBX.
|
|
#[derive(Debug, Clone)]
|
|
pub struct FbxLight {
|
|
/// Light name.
|
|
pub name: String,
|
|
/// Light type.
|
|
pub light_type: FbxLightType,
|
|
/// Light color.
|
|
pub color: Color,
|
|
/// Light intensity.
|
|
pub intensity: f32,
|
|
/// Whether the light casts shadows.
|
|
pub cast_shadows: bool,
|
|
/// Inner cone angle for spot lights (degrees).
|
|
pub inner_angle: Option<f32>,
|
|
/// Outer cone angle for spot lights (degrees).
|
|
pub outer_angle: Option<f32>,
|
|
}
|
|
|
|
/// Camera projection modes.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum FbxProjectionMode {
|
|
/// Perspective projection.
|
|
Perspective,
|
|
/// Orthographic projection.
|
|
Orthographic,
|
|
}
|
|
|
|
/// Camera definition from FBX.
|
|
#[derive(Debug, Clone)]
|
|
pub struct FbxCamera {
|
|
/// Camera name.
|
|
pub name: String,
|
|
/// Projection mode.
|
|
pub projection_mode: FbxProjectionMode,
|
|
/// Field of view in degrees.
|
|
pub field_of_view_deg: f32,
|
|
/// Aspect ratio.
|
|
pub aspect_ratio: f32,
|
|
/// Near clipping plane.
|
|
pub near_plane: f32,
|
|
/// Far clipping plane.
|
|
pub far_plane: f32,
|
|
/// Focal length in millimeters.
|
|
pub focal_length_mm: f32,
|
|
}
|
|
|
|
/// An FBX node with all of its child nodes, its mesh, transform, and optional skin.
|
|
#[derive(Asset, Debug, Clone, TypePath)]
|
|
pub struct FbxNode {
|
|
/// Index of the node inside the scene.
|
|
pub index: usize,
|
|
/// Computed name for a node - either a user defined node name from FBX or a generated name from index.
|
|
pub name: String,
|
|
/// Direct children of the node.
|
|
pub children: Vec<Handle<FbxNode>>,
|
|
/// Mesh of the node.
|
|
pub mesh: Option<Handle<Mesh>>,
|
|
/// Skin of the node.
|
|
pub skin: Option<Handle<FbxSkin>>,
|
|
/// Local transform.
|
|
pub transform: Transform,
|
|
/// Visibility flag.
|
|
pub visible: bool,
|
|
}
|
|
|
|
/// An FBX skin with all of its joint nodes and inverse bind matrices.
|
|
#[derive(Asset, Debug, Clone, TypePath)]
|
|
pub struct FbxSkin {
|
|
/// Index of the skin inside the scene.
|
|
pub index: usize,
|
|
/// Computed name for a skin - either a user defined skin name from FBX or a generated name from index.
|
|
pub name: String,
|
|
/// All the nodes that form this skin.
|
|
pub joints: Vec<Handle<FbxNode>>,
|
|
/// Inverse-bind matrices of this skin.
|
|
pub inverse_bind_matrices: Handle<bevy_mesh::skinning::SkinnedMeshInverseBindposes>,
|
|
}
|
|
|
|
/// Animation stack representing a timeline.
|
|
#[derive(Debug, Clone)]
|
|
pub struct FbxAnimStack {
|
|
/// Animation stack name.
|
|
pub name: String,
|
|
/// Start time in seconds.
|
|
pub time_begin: f64,
|
|
/// End time in seconds.
|
|
pub time_end: f64,
|
|
/// Animation layers in this stack.
|
|
pub layers: Vec<FbxAnimLayer>,
|
|
}
|
|
|
|
/// Animation layer within a stack.
|
|
#[derive(Debug, Clone)]
|
|
pub struct FbxAnimLayer {
|
|
/// Layer name.
|
|
pub name: String,
|
|
/// Layer weight.
|
|
pub weight: f32,
|
|
/// Whether this layer is additive.
|
|
pub additive: bool,
|
|
/// Property animations in this layer.
|
|
pub property_animations: Vec<FbxPropertyAnim>,
|
|
}
|
|
|
|
/// Property animation data.
|
|
#[derive(Debug, Clone)]
|
|
pub struct FbxPropertyAnim {
|
|
/// Target node ID.
|
|
pub node_id: u32,
|
|
/// Property name (e.g., "Lcl Translation", "Lcl Rotation").
|
|
pub property: String,
|
|
/// Animation curves for each component.
|
|
pub curves: Vec<FbxAnimCurve>,
|
|
}
|
|
|
|
/// Animation curve data.
|
|
#[derive(Debug, Clone)]
|
|
pub struct FbxAnimCurve {
|
|
/// Keyframe times.
|
|
pub times: Vec<f64>,
|
|
/// Keyframe values.
|
|
pub values: Vec<f32>,
|
|
/// Interpolation mode.
|
|
pub interpolation: FbxInterpolation,
|
|
}
|
|
|
|
/// Animation interpolation modes.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum FbxInterpolation {
|
|
/// Constant interpolation.
|
|
Constant,
|
|
/// Linear interpolation.
|
|
Linear,
|
|
/// Cubic interpolation.
|
|
Cubic,
|
|
}
|
|
|
|
/// Representation of a loaded FBX file.
|
|
#[derive(Asset, Debug, TypePath)]
|
|
pub struct Fbx {
|
|
/// All scenes loaded from the FBX file.
|
|
pub scenes: Vec<Handle<Scene>>,
|
|
/// Named scenes loaded from the FBX file.
|
|
pub named_scenes: HashMap<Box<str>, Handle<Scene>>,
|
|
/// All meshes loaded from the FBX file.
|
|
pub meshes: Vec<Handle<Mesh>>,
|
|
/// Named meshes loaded from the FBX file.
|
|
pub named_meshes: HashMap<Box<str>, Handle<Mesh>>,
|
|
/// All materials loaded from the FBX file.
|
|
pub materials: Vec<Handle<StandardMaterial>>,
|
|
/// Named materials loaded from the FBX file.
|
|
pub named_materials: HashMap<Box<str>, Handle<StandardMaterial>>,
|
|
/// All nodes loaded from the FBX file.
|
|
pub nodes: Vec<Handle<FbxNode>>,
|
|
/// Named nodes loaded from the FBX file.
|
|
pub named_nodes: HashMap<Box<str>, Handle<FbxNode>>,
|
|
/// All skins loaded from the FBX file.
|
|
pub skins: Vec<Handle<FbxSkin>>,
|
|
/// Named skins loaded from the FBX file.
|
|
pub named_skins: HashMap<Box<str>, Handle<FbxSkin>>,
|
|
/// Default scene to be displayed.
|
|
pub default_scene: Option<Handle<Scene>>,
|
|
/// All animations loaded from the FBX file.
|
|
pub animations: Vec<Handle<AnimationClip>>,
|
|
/// Named animations loaded from the FBX file.
|
|
pub named_animations: HashMap<Box<str>, Handle<AnimationClip>>,
|
|
/// Original axis system of the file.
|
|
pub axis_system: FbxAxisSystem,
|
|
/// Conversion factor from the original unit to meters.
|
|
pub unit_scale: f32,
|
|
/// Copyright, creator and tool information.
|
|
pub metadata: FbxMeta,
|
|
}
|
|
|
|
/// Errors that may occur while loading an FBX asset.
|
|
#[derive(Debug)]
|
|
pub enum FbxError {
|
|
/// IO error while reading the file.
|
|
Io(std::io::Error),
|
|
/// Error reported by the `ufbx` parser.
|
|
Parse(String),
|
|
}
|
|
|
|
impl core::fmt::Display for FbxError {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
match self {
|
|
FbxError::Io(err) => write!(f, "{}", err),
|
|
FbxError::Parse(err) => write!(f, "{}", err),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for FbxError {}
|
|
|
|
impl From<std::io::Error> for FbxError {
|
|
fn from(err: std::io::Error) -> Self {
|
|
FbxError::Io(err)
|
|
}
|
|
}
|
|
|
|
/// Loader implementation for FBX files.
|
|
#[derive(Default)]
|
|
pub struct FbxLoader;
|
|
|
|
impl AssetLoader for FbxLoader {
|
|
type Asset = Fbx;
|
|
type Settings = ();
|
|
type Error = FbxError;
|
|
|
|
async fn load(
|
|
&self,
|
|
reader: &mut dyn Reader,
|
|
_settings: &Self::Settings,
|
|
load_context: &mut LoadContext<'_>,
|
|
) -> Result<Fbx, FbxError> {
|
|
// Read the complete file.
|
|
let mut bytes = Vec::new();
|
|
reader.read_to_end(&mut bytes).await?;
|
|
|
|
// Basic validation
|
|
if bytes.is_empty() {
|
|
return Err(FbxError::Parse("Empty FBX file".to_string()));
|
|
}
|
|
|
|
if bytes.len() < 32 {
|
|
return Err(FbxError::Parse("FBX file too small to be valid".to_string()));
|
|
}
|
|
|
|
// Parse using `ufbx` and normalize the units/axes so that `1.0` equals
|
|
// one meter and the coordinate system matches Bevy's.
|
|
let root = ufbx::load_memory(
|
|
&bytes,
|
|
ufbx::LoadOpts {
|
|
target_unit_meters: 1.0,
|
|
target_axes: ufbx::CoordinateAxes::right_handed_y_up(),
|
|
..Default::default()
|
|
},
|
|
)
|
|
.map_err(|e| FbxError::Parse(format!("{:?}", e)))?;
|
|
let scene: &ufbx::Scene = &*root;
|
|
|
|
let mut meshes = Vec::new();
|
|
let mut named_meshes = HashMap::new();
|
|
let mut transforms = Vec::new();
|
|
let mut scratch = 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();
|
|
|
|
// Basic mesh validation
|
|
if mesh.num_vertices == 0 || mesh.faces.as_ref().is_empty() {
|
|
continue;
|
|
}
|
|
|
|
// Each mesh becomes a Bevy `Mesh` asset.
|
|
let handle =
|
|
load_context.labeled_asset_scope::<_, FbxError>(FbxAssetLabel::Mesh(index).to_string(), |_lc| {
|
|
let positions: Vec<[f32; 3]> = mesh
|
|
.vertex_position
|
|
.values
|
|
.as_ref()
|
|
.iter()
|
|
.map(|v| [v.x as f32, v.y as f32, v.z as f32])
|
|
.collect();
|
|
|
|
let mut bevy_mesh = Mesh::new(
|
|
PrimitiveTopology::TriangleList,
|
|
RenderAssetUsages::default(),
|
|
);
|
|
bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
|
|
|
if mesh.vertex_normal.exists {
|
|
let normals: Vec<[f32; 3]> = (0..mesh.num_vertices)
|
|
.map(|i| {
|
|
let n = 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);
|
|
}
|
|
|
|
if mesh.vertex_uv.exists {
|
|
let uvs: Vec<[f32; 2]> = (0..mesh.num_vertices)
|
|
.map(|i| {
|
|
let uv = mesh.vertex_uv[i];
|
|
[uv.x as f32, uv.y as f32]
|
|
})
|
|
.collect();
|
|
bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
|
|
}
|
|
|
|
let mut indices = Vec::new();
|
|
for &face in mesh.faces.as_ref() {
|
|
scratch.clear();
|
|
ufbx::triangulate_face_vec(&mut scratch, mesh, face);
|
|
for idx in &scratch {
|
|
let v = mesh.vertex_indices[*idx as usize];
|
|
indices.push(v);
|
|
}
|
|
}
|
|
bevy_mesh.insert_indices(Indices::U32(indices));
|
|
|
|
Ok(bevy_mesh)
|
|
})?;
|
|
if !node.element.name.is_empty() {
|
|
named_meshes.insert(Box::from(node.element.name.as_ref()), handle.clone());
|
|
}
|
|
meshes.push(handle);
|
|
transforms.push(node.geometry_to_world);
|
|
}
|
|
|
|
// Process textures and materials
|
|
let mut fbx_textures = Vec::new();
|
|
let mut texture_handles = HashMap::new();
|
|
|
|
// First pass: collect all textures
|
|
for texture in scene.textures.as_ref().iter() {
|
|
let fbx_texture = FbxTexture {
|
|
name: texture.element.name.to_string(),
|
|
filename: texture.filename.to_string(),
|
|
absolute_filename: texture.absolute_filename.to_string(),
|
|
uv_set: texture.uv_set.to_string(),
|
|
uv_transform: Mat4::IDENTITY, // TODO: Convert ufbx::Transform to Mat4
|
|
wrap_u: match texture.wrap_u {
|
|
ufbx::WrapMode::Repeat => FbxWrapMode::Repeat,
|
|
_ => FbxWrapMode::Clamp,
|
|
},
|
|
wrap_v: match texture.wrap_v {
|
|
ufbx::WrapMode::Repeat => FbxWrapMode::Repeat,
|
|
_ => FbxWrapMode::Clamp,
|
|
},
|
|
};
|
|
|
|
// Try to load the texture file
|
|
if !texture.filename.is_empty() {
|
|
let texture_path = if !texture.absolute_filename.is_empty() {
|
|
texture.absolute_filename.to_string()
|
|
} else {
|
|
// Try relative to the FBX file
|
|
let fbx_dir = load_context.path().parent().unwrap_or_else(|| std::path::Path::new(""));
|
|
fbx_dir.join(texture.filename.as_ref()).to_string_lossy().to_string()
|
|
};
|
|
|
|
// Load texture as Image asset
|
|
let image_handle: Handle<Image> = load_context.load(texture_path);
|
|
texture_handles.insert(texture.element.element_id, image_handle);
|
|
}
|
|
|
|
fbx_textures.push(fbx_texture);
|
|
}
|
|
|
|
// Convert materials with enhanced PBR support
|
|
let mut materials = Vec::new();
|
|
let mut named_materials = HashMap::new();
|
|
let mut fbx_materials = Vec::new();
|
|
|
|
for (index, ufbx_material) in scene.materials.as_ref().iter().enumerate() {
|
|
// Extract material properties
|
|
let mut base_color = Color::srgb(1.0, 1.0, 1.0);
|
|
let mut metallic = 0.0f32;
|
|
let mut roughness = 0.5f32;
|
|
let mut emission = Color::BLACK;
|
|
let mut normal_scale = 1.0f32;
|
|
let mut alpha = 1.0f32;
|
|
let mut material_textures = HashMap::new();
|
|
|
|
// TODO: Process material properties from ufbx
|
|
// For now, use default values
|
|
roughness = 0.5f32;
|
|
|
|
// TODO: Process material textures from ufbx
|
|
// For now, use empty textures map
|
|
|
|
let fbx_material = FbxMaterial {
|
|
name: ufbx_material.element.name.to_string(),
|
|
base_color,
|
|
metallic,
|
|
roughness,
|
|
emission,
|
|
normal_scale,
|
|
alpha,
|
|
textures: material_textures,
|
|
};
|
|
|
|
// Create StandardMaterial with textures
|
|
let mut standard_material = StandardMaterial {
|
|
base_color: fbx_material.base_color,
|
|
metallic: fbx_material.metallic,
|
|
perceptual_roughness: fbx_material.roughness,
|
|
emissive: fbx_material.emission.into(),
|
|
alpha_mode: if fbx_material.alpha < 1.0 {
|
|
AlphaMode::Blend
|
|
} else {
|
|
AlphaMode::Opaque
|
|
},
|
|
..Default::default()
|
|
};
|
|
|
|
// TODO: Apply textures to StandardMaterial
|
|
// For now, skip texture application
|
|
|
|
let handle = load_context.add_labeled_asset(
|
|
FbxAssetLabel::Material(index).to_string(),
|
|
standard_material,
|
|
);
|
|
|
|
if !ufbx_material.element.name.is_empty() {
|
|
named_materials.insert(Box::from(ufbx_material.element.name.as_ref()), handle.clone());
|
|
}
|
|
|
|
fbx_materials.push(fbx_material);
|
|
materials.push(handle);
|
|
}
|
|
|
|
// Process nodes and build hierarchy
|
|
let mut nodes = Vec::new();
|
|
let mut named_nodes = HashMap::new();
|
|
let mut node_map = HashMap::new(); // Map from ufbx node ID to FbxNode handle
|
|
|
|
// First pass: create all nodes
|
|
for (index, ufbx_node) in scene.nodes.as_ref().iter().enumerate() {
|
|
let name = if ufbx_node.element.name.is_empty() {
|
|
format!("Node_{}", index)
|
|
} else {
|
|
ufbx_node.element.name.to_string()
|
|
};
|
|
|
|
// Find associated mesh
|
|
let mesh_handle = if let Some(mesh_ref) = &ufbx_node.mesh {
|
|
// Find the mesh in our processed meshes
|
|
meshes.iter().enumerate().find_map(|(mesh_idx, mesh_handle)| {
|
|
// Check if this mesh corresponds to this node
|
|
if let Some(mesh_node) = scene.nodes.as_ref().get(mesh_idx) {
|
|
if mesh_node.element.element_id == ufbx_node.element.element_id {
|
|
Some(mesh_handle.clone())
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Convert transform
|
|
let transform = Transform {
|
|
translation: Vec3::new(
|
|
ufbx_node.local_transform.translation.x as f32,
|
|
ufbx_node.local_transform.translation.y as f32,
|
|
ufbx_node.local_transform.translation.z as f32,
|
|
),
|
|
rotation: Quat::from_xyzw(
|
|
ufbx_node.local_transform.rotation.x as f32,
|
|
ufbx_node.local_transform.rotation.y as f32,
|
|
ufbx_node.local_transform.rotation.z as f32,
|
|
ufbx_node.local_transform.rotation.w as f32,
|
|
),
|
|
scale: Vec3::new(
|
|
ufbx_node.local_transform.scale.x as f32,
|
|
ufbx_node.local_transform.scale.y as f32,
|
|
ufbx_node.local_transform.scale.z as f32,
|
|
),
|
|
};
|
|
|
|
let fbx_node = FbxNode {
|
|
index,
|
|
name: name.clone(),
|
|
children: Vec::new(), // Will be filled in second pass
|
|
mesh: mesh_handle,
|
|
skin: None, // TODO: Process skins
|
|
transform,
|
|
visible: ufbx_node.visible,
|
|
};
|
|
|
|
let node_handle = load_context.add_labeled_asset(
|
|
FbxAssetLabel::Node(index).to_string(),
|
|
fbx_node,
|
|
);
|
|
|
|
node_map.insert(ufbx_node.element.element_id, node_handle.clone());
|
|
nodes.push(node_handle.clone());
|
|
|
|
if !ufbx_node.element.name.is_empty() {
|
|
named_nodes.insert(Box::from(ufbx_node.element.name.as_ref()), node_handle);
|
|
}
|
|
}
|
|
|
|
// Second pass: establish parent-child relationships
|
|
// Note: We skip this for now to avoid ufbx crashes with children access
|
|
// TODO: Implement safe children processing
|
|
|
|
// Process skins (placeholder for now)
|
|
let skins = Vec::new();
|
|
let named_skins = HashMap::new();
|
|
|
|
// Process animations (placeholder for now)
|
|
let animations = Vec::new();
|
|
let named_animations = HashMap::new();
|
|
|
|
let mut scenes = Vec::new();
|
|
let named_scenes = HashMap::new();
|
|
|
|
// Build a scene with all meshes (simplified approach)
|
|
let mut world = World::new();
|
|
let default_material = materials.get(0).cloned().unwrap_or_else(|| {
|
|
load_context.add_labeled_asset(
|
|
FbxAssetLabel::DefaultMaterial.to_string(),
|
|
StandardMaterial::default(),
|
|
)
|
|
});
|
|
|
|
tracing::info!("FBX Loader: Found {} meshes, {} nodes", meshes.len(), scene.nodes.len());
|
|
|
|
// For now, spawn all meshes with their original transforms
|
|
for (mesh_index, (mesh_handle, transform_matrix)) in meshes.iter().zip(transforms.iter()).enumerate() {
|
|
let transform = Transform::from_matrix(Mat4::from_cols_array(&[
|
|
transform_matrix.m00 as f32, transform_matrix.m10 as f32, transform_matrix.m20 as f32, 0.0,
|
|
transform_matrix.m01 as f32, transform_matrix.m11 as f32, transform_matrix.m21 as f32, 0.0,
|
|
transform_matrix.m02 as f32, transform_matrix.m12 as f32, transform_matrix.m22 as f32, 0.0,
|
|
transform_matrix.m03 as f32, transform_matrix.m13 as f32, transform_matrix.m23 as f32, 1.0,
|
|
]));
|
|
|
|
tracing::info!("FBX Loader: Spawning mesh {} with transform: {:?}", mesh_index, transform);
|
|
|
|
world.spawn((
|
|
Mesh3d(mesh_handle.clone()),
|
|
MeshMaterial3d(default_material.clone()),
|
|
transform,
|
|
GlobalTransform::default(),
|
|
Visibility::default(),
|
|
));
|
|
}
|
|
|
|
let scene_handle = load_context.add_labeled_asset(FbxAssetLabel::Scene(0).to_string(), Scene::new(world));
|
|
scenes.push(scene_handle.clone());
|
|
|
|
Ok(Fbx {
|
|
scenes,
|
|
named_scenes,
|
|
meshes,
|
|
named_meshes,
|
|
materials,
|
|
named_materials,
|
|
nodes,
|
|
named_nodes,
|
|
skins,
|
|
named_skins,
|
|
default_scene: Some(scene_handle),
|
|
animations,
|
|
named_animations,
|
|
// FBX_TODO
|
|
axis_system: FbxAxisSystem {
|
|
up: Vec3::Y,
|
|
front: Vec3::Z,
|
|
handedness: Handedness::Right,
|
|
},
|
|
// FBX_TODO
|
|
unit_scale: 1.0,
|
|
// FBX_TODO
|
|
metadata: FbxMeta {
|
|
creator: None,
|
|
creation_time: None,
|
|
original_application: None,
|
|
},
|
|
})
|
|
}
|
|
|
|
fn extensions(&self) -> &[&str] {
|
|
&["fbx"]
|
|
}
|
|
}
|
|
|
|
/// Plugin adding the FBX loader to an [`App`].
|
|
#[derive(Default)]
|
|
pub struct FbxPlugin;
|
|
|
|
impl Plugin for FbxPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.init_asset::<Fbx>()
|
|
.init_asset::<FbxNode>()
|
|
.init_asset::<FbxSkin>()
|
|
.init_asset::<Skeleton>()
|
|
.register_asset_loader(FbxLoader::default());
|
|
}
|
|
}
|