├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── src ├── utils │ ├── mod.rs │ ├── triangulate.rs │ └── fbx_extend.rs ├── lib.rs ├── data.rs ├── fbx_transform.rs ├── material_loader.rs └── loader.rs ├── assets └── cube.fbx ├── .gitignore ├── scripts ├── test_wasm.html └── test_wasm.sh ├── .editorconfig ├── LICENSE-MIT ├── Cargo.toml ├── dev_docs ├── fbx_tree_printer.rs └── custom_material_design.md ├── examples ├── cube.rs └── scene_viewer.rs ├── CONTRIBUTING.md ├── README.md └── LICENSE-APACHE /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [nicopap, HeavyRain266] 2 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod fbx_extend; 2 | pub(crate) mod triangulate; 3 | -------------------------------------------------------------------------------- /assets/cube.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicopap/bevy_mod_fbx/HEAD/assets/cube.fbx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Cargo 2 | /target 3 | Cargo.lock 4 | 5 | # Nix/NixOS 6 | .envrc # direnv 7 | shell.nix 8 | flake.nix 9 | flake.lock 10 | 11 | # VScode 12 | .vscode 13 | 14 | # Jetbrains 15 | .idea 16 | .fleet 17 | -------------------------------------------------------------------------------- /scripts/test_wasm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 4 6 | indent_style = tab 7 | end_of_line = crlf 8 | max_line_length = 80 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.rs] 13 | indent_size = 4 14 | indent_style = space 15 | 16 | [*.toml] 17 | indent_size = 2 18 | indent_style = space 19 | 20 | [*.yml] 21 | indent_size = 2 22 | indent_style = space 23 | 24 | [*.md] 25 | indent_style = space 26 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_mod_fbx" 3 | authors = ["Nicola Papale", "HeavyRain266"] 4 | description = "Autodesk Filmbox (*.fbx) loader for Bevy Engine" 5 | license = "MIT OR Apache-2.0" 6 | readme = "README.md" 7 | keywords = ["bevy", "bevy_plugin", "fbx_loader"] 8 | categories = ["game-development"] 9 | repository = "https://github.com/nicopap/bevy_mod_fbx" 10 | exclude = ["assets/**/*", "scripts/**/*", ".github/**/*"] 11 | version = "0.4.0" 12 | edition = "2021" 13 | 14 | [features] 15 | profile = [] 16 | maya_3dsmax_pbr = [] 17 | 18 | [dependencies] 19 | rgb = "0.8" 20 | anyhow = "1.0.58" 21 | glam = { version = "0.23", features = ["mint"] } 22 | mint = "0.5" 23 | # fbxcel-dom = { version = "0.0.9", path = "../fbxcel-dom" } 24 | fbxcel-dom = "0.0.9" 25 | 26 | [dependencies.bevy] 27 | version = "0.10" 28 | default-features = false 29 | features = [ 30 | "bevy_pbr", 31 | "bevy_asset", 32 | "bevy_render", 33 | "bevy_scene", 34 | ] 35 | 36 | [dev-dependencies.bevy] 37 | version = "0.10" 38 | default-features = false 39 | features = [ 40 | "x11", #"wayland", 41 | 42 | "tga", "dds", 43 | "bevy_pbr", 44 | "bevy_render", 45 | "bevy_winit", 46 | "bevy_scene", 47 | "filesystem_watcher", 48 | "bevy_core_pipeline" 49 | ] 50 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::{AddAsset, App, Plugin, Resource}; 2 | 3 | pub use data::{FbxMesh, FbxScene}; 4 | pub use loader::FbxLoader; 5 | 6 | pub(crate) mod data; 7 | pub(crate) mod fbx_transform; 8 | pub(crate) mod loader; 9 | pub mod material_loader; 10 | pub(crate) mod utils; 11 | 12 | use material_loader::MaterialLoader; 13 | 14 | /// Adds support for FBX file loading to the app. 15 | #[derive(Default)] 16 | pub struct FbxPlugin; 17 | 18 | /// Resource to control which material loaders the `FbxLoader` 19 | /// uses. 20 | /// 21 | /// See [`MaterialLoader`] documentation for more details. 22 | /// 23 | /// You can define your own by inserting this as a resource 24 | /// **before** adding the `FbxPlugin` to the app. 25 | /// If you define your own, make sure to add back the default 26 | /// fallback methods if you need them! 27 | /// 28 | /// The default loaders are defined by [`material_loader::default_loader_order`]. 29 | #[derive(Clone, Resource)] 30 | pub struct FbxMaterialLoaders(pub Vec); 31 | impl Default for FbxMaterialLoaders { 32 | fn default() -> Self { 33 | Self(material_loader::default_loader_order().into()) 34 | } 35 | } 36 | 37 | impl Plugin for FbxPlugin { 38 | fn build(&self, app: &mut App) { 39 | app.init_asset_loader::() 40 | .add_asset::() 41 | .add_asset::(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /dev_docs/fbx_tree_printer.rs: -------------------------------------------------------------------------------- 1 | fn print_children(depth: usize, children: Children) { 2 | let show_depth = depth * 3; 3 | for child in children { 4 | print!("{space:>depth$}", space = "", depth = show_depth); 5 | if child.name().len() > 1 { 6 | print!("{name} ", name = child.name(),); 7 | } 8 | let attr_display = |att: &AttributeValue| match att { 9 | AttributeValue::Bool(v) => v.to_string(), 10 | AttributeValue::I16(v) => v.to_string(), 11 | AttributeValue::I32(v) => v.to_string(), 12 | AttributeValue::I64(v) => v.to_string(), 13 | AttributeValue::F32(v) => v.to_string(), 14 | AttributeValue::F64(v) => v.to_string(), 15 | AttributeValue::ArrBool(_) => "[bool]".to_owned(), 16 | AttributeValue::ArrI32(_) => "[i32]".to_owned(), 17 | AttributeValue::ArrI64(_) => "[i64]".to_owned(), 18 | AttributeValue::ArrF32(_) => "[f32]".to_owned(), 19 | AttributeValue::ArrF64(_) => "[f64]".to_owned(), 20 | AttributeValue::String(s) => s.clone(), 21 | AttributeValue::Binary(_) => "[u8]".to_owned(), 22 | }; 23 | print!("["); 24 | for (i, attr) in child.attributes().iter().map(attr_display).enumerate() { 25 | // if matches!(i, 1 | 2 | 3) { 26 | // continue; 27 | // } 28 | if i == 0 { 29 | print!("{attr}: "); 30 | } else { 31 | print!("{attr}, "); 32 | } 33 | } 34 | println!("]"); 35 | if child.children().next().is_some() { 36 | println!("{:>depth$}{{", "", depth = show_depth); 37 | } 38 | print_children(depth + 1, child.children()); 39 | if child.children().next().is_some() { 40 | println!("{:>depth$}}}", "", depth = show_depth); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/data.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | prelude::{Handle, Image, Mesh, StandardMaterial, Transform}, 3 | reflect::TypeUuid, 4 | utils::HashMap, 5 | }; 6 | use fbxcel_dom::v7400::object::ObjectId; 7 | 8 | #[derive(Debug, Clone, TypeUuid)] 9 | #[uuid = "966d55c0-515b-4141-97a1-de30ac8ee44c"] 10 | pub struct FbxMesh { 11 | pub name: Option, 12 | pub bevy_mesh_handles: Vec>, 13 | pub materials: Vec>, 14 | } 15 | 16 | /// The data loaded from a FBX scene. 17 | /// 18 | /// Note that the loader spawns a [`Scene`], with all the 19 | /// FBX nodes spawned as entities (with their corresponding [`Name`] set) 20 | /// in the ECS, 21 | /// and you should absolutely use the ECS entities over 22 | /// manipulating this data structure. 23 | /// It is provided publicly, because it might be a good store for strong handles. 24 | /// 25 | /// [`Scene`]: bevy::scene::Scene 26 | /// [`Name`]: bevy::core::Name 27 | #[derive(Default, Debug, Clone, TypeUuid)] 28 | #[uuid = "e87d49b6-8d6a-43c7-bb33-5315db8516eb"] 29 | pub struct FbxScene { 30 | pub name: Option, 31 | pub bevy_meshes: HashMap, String>, 32 | pub materials: HashMap>, 33 | pub textures: HashMap>, 34 | pub meshes: HashMap>, 35 | pub hierarchy: HashMap, 36 | pub roots: Vec, 37 | } 38 | 39 | /// An FBX object in the scene tree. 40 | /// 41 | /// This serves as a node in the transform hierarchy. 42 | #[derive(Default, Debug, Clone)] 43 | pub struct FbxObject { 44 | pub name: Option, 45 | pub transform: Transform, 46 | /// The children of this node. 47 | /// 48 | /// # Notes 49 | /// Not all [`ObjectId`] declared as child of an `FbxObject` 50 | /// are relevant to Bevy. 51 | /// Meaning that you won't find the `ObjectId` in `hierarchy` or `meshes` 52 | /// `HashMap`s of the [`FbxScene`] structure. 53 | pub children: Vec, 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continous Integration 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | CARGO_TERM_COLORS: always 11 | 12 | jobs: 13 | clippy_check: 14 | name: Clippy 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout sources 18 | uses: actions/checkout@v3 19 | - name: Cache 20 | uses: actions/cache@v2 21 | with: 22 | path: | 23 | ~/.cargo/bin/ 24 | ~/.cargo/registry/index/ 25 | ~/.cargo/registry/cache/ 26 | ~/.cargo/git/db/ 27 | target/ 28 | key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.toml') }} 29 | - name: Install stable toolchain 30 | uses: actions-rs/toolchain@v1 31 | with: 32 | toolchain: stable 33 | profile: minimal 34 | components: clippy 35 | override: true 36 | - name: Install Dependencies 37 | run: sudo apt-get update; sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev 38 | - name: Run clippy 39 | uses: actions-rs/clippy-check@v1 40 | with: 41 | token: ${{ secrets.GITHUB_TOKEN }} 42 | args: --examples -- -D warnings 43 | - name: Run clippy for optional features 44 | uses: actions-rs/clippy-check@v1 45 | with: 46 | token: ${{ secrets.GITHUB_TOKEN }} 47 | args: --examples --features maya_3dsmax_pbr -- -D warnings 48 | 49 | format: 50 | name: Format 51 | runs-on: ubuntu-latest 52 | steps: 53 | - name: Checkout sources 54 | uses: actions/checkout@v2 55 | - name: Install stable toolchain 56 | uses: actions-rs/toolchain@v1 57 | with: 58 | toolchain: stable 59 | profile: minimal 60 | components: rustfmt 61 | override: true 62 | - name: Run cargo fmt 63 | uses: actions-rs/cargo@v1 64 | with: 65 | command: fmt 66 | args: --all -- --check 67 | -------------------------------------------------------------------------------- /examples/cube.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | log::{Level, LogPlugin}, 3 | prelude::*, 4 | render::camera::ScalingMode, 5 | window::{close_on_esc, WindowResolution}, 6 | }; 7 | use bevy_mod_fbx::FbxPlugin; 8 | 9 | #[derive(Component)] 10 | pub struct Spin; 11 | 12 | fn main() { 13 | let mut app = App::new(); 14 | 15 | app.add_plugins( 16 | DefaultPlugins 17 | .set(LogPlugin { 18 | level: Level::INFO, 19 | filter: "bevy_mod_fbx=trace,wgpu=warn".to_owned(), 20 | }) 21 | .set(WindowPlugin { 22 | primary_window: Some(Window { 23 | title: "Spinning Cube".into(), 24 | resolution: WindowResolution::new(756., 574.), 25 | ..default() 26 | }), 27 | ..default() 28 | }), 29 | ) 30 | .add_plugin(FbxPlugin) 31 | .add_startup_system(setup) 32 | .add_system(spin_cube) 33 | .add_system(close_on_esc); 34 | 35 | app.run(); 36 | } 37 | 38 | fn spin_cube(time: Res