Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

1
vendor/bevy_gltf/.cargo-checksum.json vendored Normal file
View File

@@ -0,0 +1 @@
{"files":{"Cargo.lock":"ef521ae2e9b19bf6a63afd99d30826f84a44297659f15131cf22afbfd5515585","Cargo.toml":"7c55a774da6f4a4c51736313e6f744a26ad70f1947e097a9b4f76df8f922118c","LICENSE-APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE-MIT":"508a77d2e7b51d98adeed32648ad124b7b30241a8e70b2e72c99f92d8e5874d1","README.md":"33cba71e7b8a9d9e5a2a28f3da15f166f4c56c927c1dda5e21f1de05986edc45","src/assets.rs":"4e351964ce7fb99665e62c9e5039ca2a0c2bbba9bde64391b21b09b3b6da4f9e","src/label.rs":"ab12a1de2eaffe7633a291eebda707ab3826ace5fb2237d0379b97c1d5d3aab9","src/lib.rs":"d64b5a8a391ab25a4b10136540394e40828c6b1bdf748b5c55244c55ef2dbb29","src/loader/extensions/khr_materials_anisotropy.rs":"06e100a63cc1c105a843fb3c7c38180140e7296b8503a0296422777fbe5c8a54","src/loader/extensions/khr_materials_clearcoat.rs":"f59b9d283c7601cfd84ca9461a1a574930c749e8b52d8dbe0d71717a6e97f2ea","src/loader/extensions/khr_materials_specular.rs":"8913d034e625c2cb7cf2ed1dd082d6186a4ffa9f55561ec4fcc6a3a00a3cf455","src/loader/extensions/mod.rs":"0bea2d9695bce4674bb47982aa831111920dcb744a1b8247fd313103b2ebc092","src/loader/gltf_ext/material.rs":"0257a1e4ec6bc8bde026c4a44e30c69ddde61f9125e52a291948749499da076b","src/loader/gltf_ext/mesh.rs":"a4cbb98735d728ecdcb4e13e6f6cbf5040b3342fd57f67f158c161eaa16b3392","src/loader/gltf_ext/mod.rs":"05643b0b54eac06d7429e1e83e2e7ab8dd68c9f235f92b7904592a8ed7b90669","src/loader/gltf_ext/scene.rs":"40c5f7c50e2f92d6e4d567736329c10b23677fe98b32e9b7d1cdb36a0ad0e358","src/loader/gltf_ext/texture.rs":"9d1091cf8d7573825f0a9a79132a82be095ca46375ae4e929bb72c44b5bfb818","src/loader/mod.rs":"3cffa42adcdffae5b5f3ca523e016524bf743188f38f916644e76856c266c237","src/vertex_attributes.rs":"5373b61438da1fb62f04fd63991038c791dda855896efa115b55f87c76bdd947"},"package":"10a080237c0b8842ccc15a06d3379302c68580eeea4497b1c7387e470eda1f07"}

3390
vendor/bevy_gltf/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

195
vendor/bevy_gltf/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,195 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "bevy_gltf"
version = "0.16.1"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Bevy Engine GLTF loading"
homepage = "https://bevyengine.org"
readme = "README.md"
keywords = ["bevy"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/bevyengine/bevy"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"-Zunstable-options",
"--generate-link-to-definition",
]
[features]
dds = [
"bevy_render/dds",
"bevy_image/dds",
"bevy_core_pipeline/dds",
]
pbr_anisotropy_texture = ["bevy_pbr/pbr_anisotropy_texture"]
pbr_multi_layer_material_textures = ["bevy_pbr/pbr_multi_layer_material_textures"]
pbr_specular_textures = ["bevy_pbr/pbr_specular_textures"]
pbr_transmission_textures = ["bevy_pbr/pbr_transmission_textures"]
[lib]
name = "bevy_gltf"
path = "src/lib.rs"
[dependencies.base64]
version = "0.22.0"
[dependencies.bevy_animation]
version = "0.16.1"
optional = true
[dependencies.bevy_app]
version = "0.16.1"
[dependencies.bevy_asset]
version = "0.16.1"
[dependencies.bevy_color]
version = "0.16.2"
[dependencies.bevy_core_pipeline]
version = "0.16.1"
[dependencies.bevy_ecs]
version = "0.16.1"
[dependencies.bevy_image]
version = "0.16.1"
[dependencies.bevy_math]
version = "0.16.1"
[dependencies.bevy_mesh]
version = "0.16.1"
[dependencies.bevy_pbr]
version = "0.16.1"
[dependencies.bevy_platform]
version = "0.16.1"
features = [
"std",
"serialize",
]
default-features = false
[dependencies.bevy_reflect]
version = "0.16.1"
[dependencies.bevy_render]
version = "0.16.1"
[dependencies.bevy_scene]
version = "0.16.1"
features = ["bevy_render"]
[dependencies.bevy_tasks]
version = "0.16.1"
[dependencies.bevy_transform]
version = "0.16.1"
[dependencies.bevy_utils]
version = "0.16.1"
[dependencies.fixedbitset]
version = "0.5"
[dependencies.gltf]
version = "1.4.0"
features = [
"KHR_lights_punctual",
"KHR_materials_transmission",
"KHR_materials_ior",
"KHR_materials_volume",
"KHR_materials_unlit",
"KHR_materials_emissive_strength",
"KHR_texture_transform",
"extras",
"extensions",
"names",
"utils",
]
default-features = false
[dependencies.itertools]
version = "0.14"
[dependencies.percent-encoding]
version = "2.1"
[dependencies.serde]
version = "1.0"
features = ["derive"]
[dependencies.serde_json]
version = "1.0.140"
[dependencies.smallvec]
version = "1.11"
[dependencies.thiserror]
version = "2"
default-features = false
[dependencies.tracing]
version = "0.1"
features = ["std"]
default-features = false
[dev-dependencies.bevy_log]
version = "0.16.1"
[lints.clippy]
alloc_instead_of_core = "warn"
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
doc_markdown = "warn"
manual_let_else = "warn"
match_same_arms = "warn"
needless_lifetimes = "allow"
nonstandard_macro_braces = "warn"
print_stderr = "warn"
print_stdout = "warn"
ptr_as_ptr = "warn"
ptr_cast_constness = "warn"
redundant_closure_for_method_calls = "warn"
redundant_else = "warn"
ref_as_ptr = "warn"
semicolon_if_nothing_returned = "warn"
std_instead_of_alloc = "warn"
std_instead_of_core = "warn"
too_long_first_doc_paragraph = "allow"
too_many_arguments = "allow"
type_complexity = "allow"
undocumented_unsafe_blocks = "warn"
unwrap_or_default = "warn"
[lints.rust]
missing_docs = "warn"
unsafe_code = "deny"
unsafe_op_in_unsafe_fn = "warn"
unused_qualifications = "warn"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = ["cfg(docsrs_dep)"]

176
vendor/bevy_gltf/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

19
vendor/bevy_gltf/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7
vendor/bevy_gltf/README.md vendored Normal file
View File

@@ -0,0 +1,7 @@
# Bevy glTF
[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license)
[![Crates.io](https://img.shields.io/crates/v/bevy_gltf.svg)](https://crates.io/crates/bevy_gltf)
[![Downloads](https://img.shields.io/crates/d/bevy_gltf.svg)](https://crates.io/crates/bevy_gltf)
[![Docs](https://docs.rs/bevy_gltf/badge.svg)](https://docs.rs/bevy_gltf/latest/bevy_gltf/)
[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy)

315
vendor/bevy_gltf/src/assets.rs vendored Normal file
View File

@@ -0,0 +1,315 @@
//! Representation of assets present in a glTF file
#[cfg(feature = "bevy_animation")]
use bevy_animation::AnimationClip;
use bevy_asset::{Asset, Handle};
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_mesh::{skinning::SkinnedMeshInverseBindposes, Mesh};
use bevy_pbr::StandardMaterial;
use bevy_platform::collections::HashMap;
use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath};
use bevy_scene::Scene;
use crate::GltfAssetLabel;
/// Representation of a loaded glTF file.
#[derive(Asset, Debug, TypePath)]
pub struct Gltf {
/// All scenes loaded from the glTF file.
pub scenes: Vec<Handle<Scene>>,
/// Named scenes loaded from the glTF file.
pub named_scenes: HashMap<Box<str>, Handle<Scene>>,
/// All meshes loaded from the glTF file.
pub meshes: Vec<Handle<GltfMesh>>,
/// Named meshes loaded from the glTF file.
pub named_meshes: HashMap<Box<str>, Handle<GltfMesh>>,
/// All materials loaded from the glTF file.
pub materials: Vec<Handle<StandardMaterial>>,
/// Named materials loaded from the glTF file.
pub named_materials: HashMap<Box<str>, Handle<StandardMaterial>>,
/// All nodes loaded from the glTF file.
pub nodes: Vec<Handle<GltfNode>>,
/// Named nodes loaded from the glTF file.
pub named_nodes: HashMap<Box<str>, Handle<GltfNode>>,
/// All skins loaded from the glTF file.
pub skins: Vec<Handle<GltfSkin>>,
/// Named skins loaded from the glTF file.
pub named_skins: HashMap<Box<str>, Handle<GltfSkin>>,
/// Default scene to be displayed.
pub default_scene: Option<Handle<Scene>>,
/// All animations loaded from the glTF file.
#[cfg(feature = "bevy_animation")]
pub animations: Vec<Handle<AnimationClip>>,
/// Named animations loaded from the glTF file.
#[cfg(feature = "bevy_animation")]
pub named_animations: HashMap<Box<str>, Handle<AnimationClip>>,
/// The gltf root of the gltf asset, see <https://docs.rs/gltf/latest/gltf/struct.Gltf.html>. Only has a value when `GltfLoaderSettings::include_source` is true.
pub source: Option<gltf::Gltf>,
}
/// A glTF mesh, which may consist of multiple [`GltfPrimitives`](GltfPrimitive)
/// and an optional [`GltfExtras`].
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh).
#[derive(Asset, Debug, Clone, TypePath)]
pub struct GltfMesh {
/// Index of the mesh inside the scene
pub index: usize,
/// Computed name for a mesh - either a user defined mesh name from gLTF or a generated name from index
pub name: String,
/// Primitives of the glTF mesh.
pub primitives: Vec<GltfPrimitive>,
/// Additional data.
pub extras: Option<GltfExtras>,
}
impl GltfMesh {
/// Create a mesh extracting name and index from glTF def
pub fn new(
mesh: &gltf::Mesh,
primitives: Vec<GltfPrimitive>,
extras: Option<GltfExtras>,
) -> Self {
Self {
index: mesh.index(),
name: if let Some(name) = mesh.name() {
name.to_string()
} else {
format!("GltfMesh{}", mesh.index())
},
primitives,
extras,
}
}
/// Subasset label for this mesh within the gLTF parent asset.
pub fn asset_label(&self) -> GltfAssetLabel {
GltfAssetLabel::Mesh(self.index)
}
}
/// A glTF node with all of its child nodes, its [`GltfMesh`],
/// [`Transform`](bevy_transform::prelude::Transform), its optional [`GltfSkin`]
/// and an optional [`GltfExtras`].
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-node).
#[derive(Asset, Debug, Clone, TypePath)]
pub struct GltfNode {
/// Index of the node inside the scene
pub index: usize,
/// Computed name for a node - either a user defined node name from gLTF or a generated name from index
pub name: String,
/// Direct children of the node.
pub children: Vec<Handle<GltfNode>>,
/// Mesh of the node.
pub mesh: Option<Handle<GltfMesh>>,
/// Skin of the node.
pub skin: Option<Handle<GltfSkin>>,
/// Local transform.
pub transform: bevy_transform::prelude::Transform,
/// Is this node used as an animation root
#[cfg(feature = "bevy_animation")]
pub is_animation_root: bool,
/// Additional data.
pub extras: Option<GltfExtras>,
}
impl GltfNode {
/// Create a node extracting name and index from glTF def
pub fn new(
node: &gltf::Node,
children: Vec<Handle<GltfNode>>,
mesh: Option<Handle<GltfMesh>>,
transform: bevy_transform::prelude::Transform,
skin: Option<Handle<GltfSkin>>,
extras: Option<GltfExtras>,
) -> Self {
Self {
index: node.index(),
name: if let Some(name) = node.name() {
name.to_string()
} else {
format!("GltfNode{}", node.index())
},
children,
mesh,
transform,
skin,
#[cfg(feature = "bevy_animation")]
is_animation_root: false,
extras,
}
}
/// Create a node with animation root mark
#[cfg(feature = "bevy_animation")]
pub fn with_animation_root(self, is_animation_root: bool) -> Self {
Self {
is_animation_root,
..self
}
}
/// Subasset label for this node within the gLTF parent asset.
pub fn asset_label(&self) -> GltfAssetLabel {
GltfAssetLabel::Node(self.index)
}
}
/// Part of a [`GltfMesh`] that consists of a [`Mesh`], an optional [`StandardMaterial`] and [`GltfExtras`].
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh-primitive).
#[derive(Asset, Debug, Clone, TypePath)]
pub struct GltfPrimitive {
/// Index of the primitive inside the mesh
pub index: usize,
/// Index of the parent [`GltfMesh`] of this primitive
pub parent_mesh_index: usize,
/// Computed name for a primitive - either a user defined primitive name from gLTF or a generated name from index
pub name: String,
/// Topology to be rendered.
pub mesh: Handle<Mesh>,
/// Material to apply to the `mesh`.
pub material: Option<Handle<StandardMaterial>>,
/// Additional data.
pub extras: Option<GltfExtras>,
/// Additional data of the `material`.
pub material_extras: Option<GltfExtras>,
}
impl GltfPrimitive {
/// Create a primitive extracting name and index from glTF def
pub fn new(
gltf_mesh: &gltf::Mesh,
gltf_primitive: &gltf::Primitive,
mesh: Handle<Mesh>,
material: Option<Handle<StandardMaterial>>,
extras: Option<GltfExtras>,
material_extras: Option<GltfExtras>,
) -> Self {
GltfPrimitive {
index: gltf_primitive.index(),
parent_mesh_index: gltf_mesh.index(),
name: {
let mesh_name = gltf_mesh.name().unwrap_or("Mesh");
if gltf_mesh.primitives().len() > 1 {
format!("{}.{}", mesh_name, gltf_primitive.index())
} else {
mesh_name.to_string()
}
},
mesh,
material,
extras,
material_extras,
}
}
/// Subasset label for this primitive within its parent [`GltfMesh`] within the gLTF parent asset.
pub fn asset_label(&self) -> GltfAssetLabel {
GltfAssetLabel::Primitive {
mesh: self.parent_mesh_index,
primitive: self.index,
}
}
}
/// A glTF skin with all of its joint nodes, [`SkinnedMeshInversiveBindposes`](bevy_mesh::skinning::SkinnedMeshInverseBindposes)
/// and an optional [`GltfExtras`].
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-skin).
#[derive(Asset, Debug, Clone, TypePath)]
pub struct GltfSkin {
/// Index of the skin inside the scene
pub index: usize,
/// Computed name for a skin - either a user defined skin name from gLTF or a generated name from index
pub name: String,
/// All the nodes that form this skin.
pub joints: Vec<Handle<GltfNode>>,
/// Inverse-bind matrices of this skin.
pub inverse_bind_matrices: Handle<SkinnedMeshInverseBindposes>,
/// Additional data.
pub extras: Option<GltfExtras>,
}
impl GltfSkin {
/// Create a skin extracting name and index from glTF def
pub fn new(
skin: &gltf::Skin,
joints: Vec<Handle<GltfNode>>,
inverse_bind_matrices: Handle<SkinnedMeshInverseBindposes>,
extras: Option<GltfExtras>,
) -> Self {
Self {
index: skin.index(),
name: if let Some(name) = skin.name() {
name.to_string()
} else {
format!("GltfSkin{}", skin.index())
},
joints,
inverse_bind_matrices,
extras,
}
}
/// Subasset label for this skin within the gLTF parent asset.
pub fn asset_label(&self) -> GltfAssetLabel {
GltfAssetLabel::Skin(self.index)
}
}
/// Additional untyped data that can be present on most glTF types at the primitive level.
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
#[derive(Clone, Debug, Reflect, Default, Component)]
#[reflect(Component, Clone, Default, Debug)]
pub struct GltfExtras {
/// Content of the extra data.
pub value: String,
}
impl From<&serde_json::value::RawValue> for GltfExtras {
fn from(value: &serde_json::value::RawValue) -> Self {
GltfExtras {
value: value.get().to_string(),
}
}
}
/// Additional untyped data that can be present on most glTF types at the scene level.
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
#[derive(Clone, Debug, Reflect, Default, Component)]
#[reflect(Component, Clone, Default, Debug)]
pub struct GltfSceneExtras {
/// Content of the extra data.
pub value: String,
}
/// Additional untyped data that can be present on most glTF types at the mesh level.
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
#[derive(Clone, Debug, Reflect, Default, Component)]
#[reflect(Component, Clone, Default, Debug)]
pub struct GltfMeshExtras {
/// Content of the extra data.
pub value: String,
}
/// Additional untyped data that can be present on most glTF types at the material level.
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
#[derive(Clone, Debug, Reflect, Default, Component)]
#[reflect(Component, Clone, Default, Debug)]
pub struct GltfMaterialExtras {
/// Content of the extra data.
pub value: String,
}
/// The material name of a glTF primitive.
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-material).
#[derive(Clone, Debug, Reflect, Default, Component)]
#[reflect(Component, Clone)]
pub struct GltfMaterialName(pub String);

127
vendor/bevy_gltf/src/label.rs vendored Normal file
View File

@@ -0,0 +1,127 @@
//! Labels that can be used to load part of a glTF
use bevy_asset::AssetPath;
/// Labels that can be used to load part of a glTF
///
/// You can use [`GltfAssetLabel::from_asset`] to add it to an asset path
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_asset::prelude::*;
/// # use bevy_scene::prelude::*;
/// # use bevy_gltf::prelude::*;
///
/// fn load_gltf_scene(asset_server: Res<AssetServer>) {
/// let gltf_scene: Handle<Scene> = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
/// }
/// ```
///
/// Or when formatting a string for the path
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_asset::prelude::*;
/// # use bevy_scene::prelude::*;
/// # use bevy_gltf::prelude::*;
///
/// fn load_gltf_scene(asset_server: Res<AssetServer>) {
/// let gltf_scene: Handle<Scene> = asset_server.load(format!("models/FlightHelmet/FlightHelmet.gltf#{}", GltfAssetLabel::Scene(0)));
/// }
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GltfAssetLabel {
/// `Scene{}`: glTF Scene as a Bevy [`Scene`](bevy_scene::Scene)
Scene(usize),
/// `Node{}`: glTF Node as a [`GltfNode`](crate::GltfNode)
Node(usize),
/// `Mesh{}`: glTF Mesh as a [`GltfMesh`](crate::GltfMesh)
Mesh(usize),
/// `Mesh{}/Primitive{}`: glTF Primitive as a Bevy [`Mesh`](bevy_mesh::Mesh)
Primitive {
/// Index of the mesh for this primitive
mesh: usize,
/// Index of this primitive in its parent mesh
primitive: usize,
},
/// `Mesh{}/Primitive{}/MorphTargets`: Morph target animation data for a glTF Primitive
/// as a Bevy [`Image`](bevy_image::prelude::Image)
MorphTarget {
/// Index of the mesh for this primitive
mesh: usize,
/// Index of this primitive in its parent mesh
primitive: usize,
},
/// `Texture{}`: glTF Texture as a Bevy [`Image`](bevy_image::prelude::Image)
Texture(usize),
/// `Material{}`: glTF Material as a Bevy [`StandardMaterial`](bevy_pbr::StandardMaterial)
Material {
/// Index of this material
index: usize,
/// Used to set the [`Face`](bevy_render::render_resource::Face) of the material,
/// useful if it is used with negative scale
is_scale_inverted: bool,
},
/// `DefaultMaterial`: glTF's default Material as a
/// Bevy [`StandardMaterial`](bevy_pbr::StandardMaterial)
DefaultMaterial,
/// `Animation{}`: glTF Animation as Bevy [`AnimationClip`](bevy_animation::AnimationClip)
Animation(usize),
/// `Skin{}`: glTF mesh skin as [`GltfSkin`](crate::GltfSkin)
Skin(usize),
/// `Skin{}/InverseBindMatrices`: glTF mesh skin matrices as Bevy
/// [`SkinnedMeshInverseBindposes`](bevy_mesh::skinning::SkinnedMeshInverseBindposes)
InverseBindMatrices(usize),
}
impl core::fmt::Display for GltfAssetLabel {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
GltfAssetLabel::Scene(index) => f.write_str(&format!("Scene{index}")),
GltfAssetLabel::Node(index) => f.write_str(&format!("Node{index}")),
GltfAssetLabel::Mesh(index) => f.write_str(&format!("Mesh{index}")),
GltfAssetLabel::Primitive { mesh, primitive } => {
f.write_str(&format!("Mesh{mesh}/Primitive{primitive}"))
}
GltfAssetLabel::MorphTarget { mesh, primitive } => {
f.write_str(&format!("Mesh{mesh}/Primitive{primitive}/MorphTargets"))
}
GltfAssetLabel::Texture(index) => f.write_str(&format!("Texture{index}")),
GltfAssetLabel::Material {
index,
is_scale_inverted,
} => f.write_str(&format!(
"Material{index}{}",
if *is_scale_inverted {
" (inverted)"
} else {
""
}
)),
GltfAssetLabel::DefaultMaterial => f.write_str("DefaultMaterial"),
GltfAssetLabel::Animation(index) => f.write_str(&format!("Animation{index}")),
GltfAssetLabel::Skin(index) => f.write_str(&format!("Skin{index}")),
GltfAssetLabel::InverseBindMatrices(index) => {
f.write_str(&format!("Skin{index}/InverseBindMatrices"))
}
}
}
}
impl GltfAssetLabel {
/// Add this label to an asset path
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_asset::prelude::*;
/// # use bevy_scene::prelude::*;
/// # use bevy_gltf::prelude::*;
///
/// fn load_gltf_scene(asset_server: Res<AssetServer>) {
/// let gltf_scene: Handle<Scene> = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
/// }
/// ```
pub fn from_asset(&self, path: impl Into<AssetPath<'static>>) -> AssetPath<'static> {
path.into().with_label(self.to_string())
}
}

165
vendor/bevy_gltf/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,165 @@
#![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"
)]
//! Plugin providing an [`AssetLoader`](bevy_asset::AssetLoader) and type definitions
//! for loading glTF 2.0 (a standard 3D scene definition format) files in Bevy.
//!
//! The [glTF 2.0 specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html) defines the format of the glTF files.
//!
//! # Quick Start
//!
//! Here's how to spawn a simple glTF scene
//!
//! ```
//! # use bevy_ecs::prelude::*;
//! # use bevy_asset::prelude::*;
//! # use bevy_scene::prelude::*;
//! # use bevy_transform::prelude::*;
//! # use bevy_gltf::prelude::*;
//!
//! fn spawn_gltf(mut commands: Commands, asset_server: Res<AssetServer>) {
//! commands.spawn((
//! // This is equivalent to "models/FlightHelmet/FlightHelmet.gltf#Scene0"
//! // The `#Scene0` label here is very important because it tells bevy to load the first scene in the glTF file.
//! // If this isn't specified bevy doesn't know which part of the glTF file to load.
//! SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"))),
//! // You can use the transform to give it a position
//! Transform::from_xyz(2.0, 0.0, -5.0),
//! ));
//! }
//! ```
//! # Loading parts of a glTF asset
//!
//! ## Using `Gltf`
//!
//! If you want to access part of the asset, you can load the entire `Gltf` using the `AssetServer`.
//! Once the `Handle<Gltf>` is loaded you can then use it to access named parts of it.
//!
//! ```
//! # use bevy_ecs::prelude::*;
//! # use bevy_asset::prelude::*;
//! # use bevy_scene::prelude::*;
//! # use bevy_transform::prelude::*;
//! # use bevy_gltf::Gltf;
//!
//! // Holds the scene handle
//! #[derive(Resource)]
//! struct HelmetScene(Handle<Gltf>);
//!
//! fn load_gltf(mut commands: Commands, asset_server: Res<AssetServer>) {
//! let gltf = asset_server.load("models/FlightHelmet/FlightHelmet.gltf");
//! commands.insert_resource(HelmetScene(gltf));
//! }
//!
//! fn spawn_gltf_objects(
//! mut commands: Commands,
//! helmet_scene: Res<HelmetScene>,
//! gltf_assets: Res<Assets<Gltf>>,
//! mut loaded: Local<bool>,
//! ) {
//! // Only do this once
//! if *loaded {
//! return;
//! }
//! // Wait until the scene is loaded
//! let Some(gltf) = gltf_assets.get(&helmet_scene.0) else {
//! return;
//! };
//! *loaded = true;
//!
//! // Spawns the first scene in the file
//! commands.spawn(SceneRoot(gltf.scenes[0].clone()));
//!
//! // Spawns the scene named "Lenses_low"
//! commands.spawn((
//! SceneRoot(gltf.named_scenes["Lenses_low"].clone()),
//! Transform::from_xyz(1.0, 2.0, 3.0),
//! ));
//! }
//! ```
//!
//! ## Asset Labels
//!
//! The glTF loader let's you specify labels that let you target specific parts of the glTF.
//!
//! Be careful when using this feature, if you misspell a label it will simply ignore it without warning.
//!
//! You can use [`GltfAssetLabel`] to ensure you are using the correct label.
mod assets;
mod label;
mod loader;
mod vertex_attributes;
extern crate alloc;
use bevy_platform::collections::HashMap;
use bevy_app::prelude::*;
use bevy_asset::AssetApp;
use bevy_image::CompressedImageFormats;
use bevy_mesh::MeshVertexAttribute;
use bevy_render::renderer::RenderDevice;
/// The glTF prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[doc(hidden)]
pub use crate::{assets::Gltf, assets::GltfExtras, label::GltfAssetLabel};
}
pub use {assets::*, label::GltfAssetLabel, loader::*};
/// Adds support for glTF file loading to the app.
#[derive(Default)]
pub struct GltfPlugin {
custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
}
impl GltfPlugin {
/// Register a custom vertex attribute so that it is recognized when loading a glTF file with the [`GltfLoader`].
///
/// `name` must be the attribute name as found in the glTF data, which must start with an underscore.
/// See [this section of the glTF specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)
/// for additional details on custom attributes.
pub fn add_custom_vertex_attribute(
mut self,
name: &str,
attribute: MeshVertexAttribute,
) -> Self {
self.custom_vertex_attributes.insert(name.into(), attribute);
self
}
}
impl Plugin for GltfPlugin {
fn build(&self, app: &mut App) {
app.register_type::<GltfExtras>()
.register_type::<GltfSceneExtras>()
.register_type::<GltfMeshExtras>()
.register_type::<GltfMaterialExtras>()
.register_type::<GltfMaterialName>()
.init_asset::<Gltf>()
.init_asset::<GltfNode>()
.init_asset::<GltfPrimitive>()
.init_asset::<GltfMesh>()
.init_asset::<GltfSkin>()
.preregister_asset_loader::<GltfLoader>(&["gltf", "glb"]);
}
fn finish(&self, app: &mut App) {
let supported_compressed_formats = match app.world().get_resource::<RenderDevice>() {
Some(render_device) => CompressedImageFormats::from_features(render_device.features()),
None => CompressedImageFormats::NONE,
};
app.register_asset_loader(GltfLoader {
supported_compressed_formats,
custom_vertex_attributes: self.custom_vertex_attributes.clone(),
});
}
}

View File

@@ -0,0 +1,71 @@
use bevy_asset::LoadContext;
use gltf::{Document, Material};
use serde_json::Value;
#[cfg(feature = "pbr_anisotropy_texture")]
use {
crate::loader::gltf_ext::{material::uv_channel, texture::texture_handle_from_info},
bevy_asset::Handle,
bevy_image::Image,
bevy_pbr::UvChannel,
gltf::json::texture::Info,
serde_json::value,
};
/// Parsed data from the `KHR_materials_anisotropy` extension.
///
/// See the specification:
/// <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_anisotropy/README.md>
#[derive(Default)]
pub(crate) struct AnisotropyExtension {
pub(crate) anisotropy_strength: Option<f64>,
pub(crate) anisotropy_rotation: Option<f64>,
#[cfg(feature = "pbr_anisotropy_texture")]
pub(crate) anisotropy_channel: UvChannel,
#[cfg(feature = "pbr_anisotropy_texture")]
pub(crate) anisotropy_texture: Option<Handle<Image>>,
}
impl AnisotropyExtension {
#[expect(
clippy::allow_attributes,
reason = "`unused_variables` is not always linted"
)]
#[allow(
unused_variables,
reason = "Depending on what features are used to compile this crate, certain parameters may end up unused."
)]
pub(crate) fn parse(
load_context: &mut LoadContext,
document: &Document,
material: &Material,
) -> Option<AnisotropyExtension> {
let extension = material
.extensions()?
.get("KHR_materials_anisotropy")?
.as_object()?;
#[cfg(feature = "pbr_anisotropy_texture")]
let (anisotropy_channel, anisotropy_texture) = extension
.get("anisotropyTexture")
.and_then(|value| value::from_value::<Info>(value.clone()).ok())
.map(|json_info| {
(
uv_channel(material, "anisotropy", json_info.tex_coord),
texture_handle_from_info(&json_info, document, load_context),
)
})
.unzip();
Some(AnisotropyExtension {
anisotropy_strength: extension.get("anisotropyStrength").and_then(Value::as_f64),
anisotropy_rotation: extension.get("anisotropyRotation").and_then(Value::as_f64),
#[cfg(feature = "pbr_anisotropy_texture")]
anisotropy_channel: anisotropy_channel.unwrap_or_default(),
#[cfg(feature = "pbr_anisotropy_texture")]
anisotropy_texture,
})
}
}

View File

@@ -0,0 +1,104 @@
use bevy_asset::LoadContext;
use gltf::{Document, Material};
use serde_json::Value;
#[cfg(feature = "pbr_multi_layer_material_textures")]
use {
crate::loader::gltf_ext::material::parse_material_extension_texture, bevy_asset::Handle,
bevy_image::Image, bevy_pbr::UvChannel,
};
/// Parsed data from the `KHR_materials_clearcoat` extension.
///
/// See the specification:
/// <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_clearcoat/README.md>
#[derive(Default)]
pub(crate) struct ClearcoatExtension {
pub(crate) clearcoat_factor: Option<f64>,
#[cfg(feature = "pbr_multi_layer_material_textures")]
pub(crate) clearcoat_channel: UvChannel,
#[cfg(feature = "pbr_multi_layer_material_textures")]
pub(crate) clearcoat_texture: Option<Handle<Image>>,
pub(crate) clearcoat_roughness_factor: Option<f64>,
#[cfg(feature = "pbr_multi_layer_material_textures")]
pub(crate) clearcoat_roughness_channel: UvChannel,
#[cfg(feature = "pbr_multi_layer_material_textures")]
pub(crate) clearcoat_roughness_texture: Option<Handle<Image>>,
#[cfg(feature = "pbr_multi_layer_material_textures")]
pub(crate) clearcoat_normal_channel: UvChannel,
#[cfg(feature = "pbr_multi_layer_material_textures")]
pub(crate) clearcoat_normal_texture: Option<Handle<Image>>,
}
impl ClearcoatExtension {
#[expect(
clippy::allow_attributes,
reason = "`unused_variables` is not always linted"
)]
#[allow(
unused_variables,
reason = "Depending on what features are used to compile this crate, certain parameters may end up unused."
)]
pub(crate) fn parse(
load_context: &mut LoadContext,
document: &Document,
material: &Material,
) -> Option<ClearcoatExtension> {
let extension = material
.extensions()?
.get("KHR_materials_clearcoat")?
.as_object()?;
#[cfg(feature = "pbr_multi_layer_material_textures")]
let (clearcoat_channel, clearcoat_texture) = parse_material_extension_texture(
material,
load_context,
document,
extension,
"clearcoatTexture",
"clearcoat",
);
#[cfg(feature = "pbr_multi_layer_material_textures")]
let (clearcoat_roughness_channel, clearcoat_roughness_texture) =
parse_material_extension_texture(
material,
load_context,
document,
extension,
"clearcoatRoughnessTexture",
"clearcoat roughness",
);
#[cfg(feature = "pbr_multi_layer_material_textures")]
let (clearcoat_normal_channel, clearcoat_normal_texture) = parse_material_extension_texture(
material,
load_context,
document,
extension,
"clearcoatNormalTexture",
"clearcoat normal",
);
Some(ClearcoatExtension {
clearcoat_factor: extension.get("clearcoatFactor").and_then(Value::as_f64),
clearcoat_roughness_factor: extension
.get("clearcoatRoughnessFactor")
.and_then(Value::as_f64),
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_channel,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_texture,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_roughness_channel,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_roughness_texture,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_normal_channel,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_normal_texture,
})
}
}

View File

@@ -0,0 +1,100 @@
use bevy_asset::LoadContext;
use gltf::{Document, Material};
use serde_json::Value;
#[cfg(feature = "pbr_specular_textures")]
use {
crate::loader::gltf_ext::material::parse_material_extension_texture, bevy_asset::Handle,
bevy_image::Image, bevy_pbr::UvChannel,
};
/// Parsed data from the `KHR_materials_specular` extension.
///
/// We currently don't parse `specularFactor` and `specularTexture`, since
/// they're incompatible with Filament.
///
/// Note that the map is a *specular map*, not a *reflectance map*. In Bevy and
/// Filament terms, the reflectance values in the specular map range from [0.0,
/// 0.5], rather than [0.0, 1.0]. This is an unfortunate
/// `KHR_materials_specular` specification requirement that stems from the fact
/// that glTF is specified in terms of a specular strength model, not the
/// reflectance model that Filament and Bevy use. A workaround, which is noted
/// in the [`StandardMaterial`](bevy_pbr::StandardMaterial) documentation, is to set the reflectance value
/// to 2.0, which spreads the specular map range from [0.0, 1.0] as normal.
///
/// See the specification:
/// <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_specular/README.md>
#[derive(Default)]
pub(crate) struct SpecularExtension {
pub(crate) specular_factor: Option<f64>,
#[cfg(feature = "pbr_specular_textures")]
pub(crate) specular_channel: UvChannel,
#[cfg(feature = "pbr_specular_textures")]
pub(crate) specular_texture: Option<Handle<Image>>,
pub(crate) specular_color_factor: Option<[f64; 3]>,
#[cfg(feature = "pbr_specular_textures")]
pub(crate) specular_color_channel: UvChannel,
#[cfg(feature = "pbr_specular_textures")]
pub(crate) specular_color_texture: Option<Handle<Image>>,
}
impl SpecularExtension {
pub(crate) fn parse(
_load_context: &mut LoadContext,
_document: &Document,
material: &Material,
) -> Option<Self> {
let extension = material
.extensions()?
.get("KHR_materials_specular")?
.as_object()?;
#[cfg(feature = "pbr_specular_textures")]
let (_specular_channel, _specular_texture) = parse_material_extension_texture(
material,
_load_context,
_document,
extension,
"specularTexture",
"specular",
);
#[cfg(feature = "pbr_specular_textures")]
let (_specular_color_channel, _specular_color_texture) = parse_material_extension_texture(
material,
_load_context,
_document,
extension,
"specularColorTexture",
"specular color",
);
Some(SpecularExtension {
specular_factor: extension.get("specularFactor").and_then(Value::as_f64),
#[cfg(feature = "pbr_specular_textures")]
specular_channel: _specular_channel,
#[cfg(feature = "pbr_specular_textures")]
specular_texture: _specular_texture,
specular_color_factor: extension
.get("specularColorFactor")
.and_then(Value::as_array)
.and_then(|json_array| {
if json_array.len() < 3 {
None
} else {
Some([
json_array[0].as_f64()?,
json_array[1].as_f64()?,
json_array[2].as_f64()?,
])
}
}),
#[cfg(feature = "pbr_specular_textures")]
specular_color_channel: _specular_color_channel,
#[cfg(feature = "pbr_specular_textures")]
specular_color_texture: _specular_color_texture,
})
}
}

View File

@@ -0,0 +1,10 @@
//! glTF extensions defined by the Khronos Group and other vendors
mod khr_materials_anisotropy;
mod khr_materials_clearcoat;
mod khr_materials_specular;
pub(crate) use self::{
khr_materials_anisotropy::AnisotropyExtension, khr_materials_clearcoat::ClearcoatExtension,
khr_materials_specular::SpecularExtension,
};

View File

@@ -0,0 +1,165 @@
use bevy_math::Affine2;
use bevy_pbr::UvChannel;
use bevy_render::alpha::AlphaMode;
use gltf::{json::texture::Info, Material};
use serde_json::value;
use crate::GltfAssetLabel;
use super::texture::texture_transform_to_affine2;
#[cfg(any(
feature = "pbr_specular_textures",
feature = "pbr_multi_layer_material_textures"
))]
use {
super::texture::texture_handle_from_info,
bevy_asset::{Handle, LoadContext},
bevy_image::Image,
gltf::Document,
serde_json::{Map, Value},
};
/// Parses a texture that's part of a material extension block and returns its
/// UV channel and image reference.
#[cfg(any(
feature = "pbr_specular_textures",
feature = "pbr_multi_layer_material_textures"
))]
pub(crate) fn parse_material_extension_texture(
material: &Material,
load_context: &mut LoadContext,
document: &Document,
extension: &Map<String, Value>,
texture_name: &str,
texture_kind: &str,
) -> (UvChannel, Option<Handle<Image>>) {
match extension
.get(texture_name)
.and_then(|value| value::from_value::<Info>(value.clone()).ok())
{
Some(json_info) => (
uv_channel(material, texture_kind, json_info.tex_coord),
Some(texture_handle_from_info(&json_info, document, load_context)),
),
None => (UvChannel::default(), None),
}
}
pub(crate) fn uv_channel(material: &Material, texture_kind: &str, tex_coord: u32) -> UvChannel {
match tex_coord {
0 => UvChannel::Uv0,
1 => UvChannel::Uv1,
_ => {
let material_name = material
.name()
.map(|n| format!("the material \"{n}\""))
.unwrap_or_else(|| "an unnamed material".to_string());
let material_index = material
.index()
.map(|i| format!("index {i}"))
.unwrap_or_else(|| "default".to_string());
tracing::warn!(
"Only 2 UV Channels are supported, but {material_name} ({material_index}) \
has the TEXCOORD attribute {} on texture kind {texture_kind}, which will fallback to 0.",
tex_coord,
);
UvChannel::Uv0
}
}
}
pub(crate) fn alpha_mode(material: &Material) -> AlphaMode {
match material.alpha_mode() {
gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
gltf::material::AlphaMode::Mask => AlphaMode::Mask(material.alpha_cutoff().unwrap_or(0.5)),
gltf::material::AlphaMode::Blend => AlphaMode::Blend,
}
}
/// Returns the index (within the `textures` array) of the texture with the
/// given field name in the data for the material extension with the given name,
/// if there is one.
pub(crate) fn extension_texture_index(
material: &Material,
extension_name: &str,
texture_field_name: &str,
) -> Option<usize> {
Some(
value::from_value::<Info>(
material
.extensions()?
.get(extension_name)?
.as_object()?
.get(texture_field_name)?
.clone(),
)
.ok()?
.index
.value(),
)
}
/// Returns true if the material needs mesh tangents in order to be successfully
/// rendered.
///
/// We generate them if this function returns true.
pub(crate) fn needs_tangents(material: &Material) -> bool {
[
material.normal_texture().is_some(),
#[cfg(feature = "pbr_multi_layer_material_textures")]
extension_texture_index(
material,
"KHR_materials_clearcoat",
"clearcoatNormalTexture",
)
.is_some(),
]
.into_iter()
.reduce(|a, b| a || b)
.unwrap_or(false)
}
pub(crate) fn warn_on_differing_texture_transforms(
material: &Material,
info: &gltf::texture::Info,
texture_transform: Affine2,
texture_kind: &str,
) {
let has_differing_texture_transform = info
.texture_transform()
.map(texture_transform_to_affine2)
.is_some_and(|t| t != texture_transform);
if has_differing_texture_transform {
let material_name = material
.name()
.map(|n| format!("the material \"{n}\""))
.unwrap_or_else(|| "an unnamed material".to_string());
let texture_name = info
.texture()
.name()
.map(|n| format!("its {texture_kind} texture \"{n}\""))
.unwrap_or_else(|| format!("its unnamed {texture_kind} texture"));
let material_index = material
.index()
.map(|i| format!("index {i}"))
.unwrap_or_else(|| "default".to_string());
tracing::warn!(
"Only texture transforms on base color textures are supported, but {material_name} ({material_index}) \
has a texture transform on {texture_name} (index {}), which will be ignored.", info.texture().index()
);
}
}
pub(crate) fn material_label(material: &Material, is_scale_inverted: bool) -> GltfAssetLabel {
if let Some(index) = material.index() {
GltfAssetLabel::Material {
index,
is_scale_inverted,
}
} else {
GltfAssetLabel::DefaultMaterial
}
}

View File

@@ -0,0 +1,33 @@
use bevy_mesh::PrimitiveTopology;
use gltf::mesh::{Mesh, Mode, Primitive};
use crate::GltfError;
pub(crate) fn primitive_name(mesh: &Mesh<'_>, primitive: &Primitive) -> String {
let mesh_name = mesh.name().unwrap_or("Mesh");
if mesh.primitives().len() > 1 {
format!("{}.{}", mesh_name, primitive.index())
} else {
mesh_name.to_string()
}
}
/// Maps the `primitive_topology` from glTF to `wgpu`.
#[cfg_attr(
not(target_arch = "wasm32"),
expect(
clippy::result_large_err,
reason = "`GltfError` is only barely past the threshold for large errors."
)
)]
pub(crate) fn primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
match mode {
Mode::Points => Ok(PrimitiveTopology::PointList),
Mode::Lines => Ok(PrimitiveTopology::LineList),
Mode::LineStrip => Ok(PrimitiveTopology::LineStrip),
Mode::Triangles => Ok(PrimitiveTopology::TriangleList),
Mode::TriangleStrip => Ok(PrimitiveTopology::TriangleStrip),
mode => Err(GltfError::UnsupportedPrimitive { mode }),
}
}

View File

@@ -0,0 +1,82 @@
//! Methods to access information from [`gltf`] types
pub mod material;
pub mod mesh;
pub mod scene;
pub mod texture;
use bevy_platform::collections::HashSet;
use fixedbitset::FixedBitSet;
use gltf::{Document, Gltf};
use super::GltfError;
use self::{material::extension_texture_index, scene::check_is_part_of_cycle};
#[cfg_attr(
not(target_arch = "wasm32"),
expect(
clippy::result_large_err,
reason = "need to be signature compatible with `load_gltf`"
)
)]
/// Checks all glTF nodes for cycles, starting at the scene root.
pub(crate) fn check_for_cycles(gltf: &Gltf) -> Result<(), GltfError> {
// Initialize with the scene roots.
let mut roots = FixedBitSet::with_capacity(gltf.nodes().len());
for root in gltf.scenes().flat_map(|scene| scene.nodes()) {
roots.insert(root.index());
}
// Check each one.
let mut visited = FixedBitSet::with_capacity(gltf.nodes().len());
for root in roots.ones() {
let Some(node) = gltf.nodes().nth(root) else {
unreachable!("Index of a root node should always exist.");
};
check_is_part_of_cycle(&node, &mut visited)?;
}
Ok(())
}
pub(crate) fn get_linear_textures(document: &Document) -> HashSet<usize> {
let mut linear_textures = HashSet::default();
for material in document.materials() {
if let Some(texture) = material.normal_texture() {
linear_textures.insert(texture.texture().index());
}
if let Some(texture) = material.occlusion_texture() {
linear_textures.insert(texture.texture().index());
}
if let Some(texture) = material
.pbr_metallic_roughness()
.metallic_roughness_texture()
{
linear_textures.insert(texture.texture().index());
}
if let Some(texture_index) =
extension_texture_index(&material, "KHR_materials_anisotropy", "anisotropyTexture")
{
linear_textures.insert(texture_index);
}
// None of the clearcoat maps should be loaded as sRGB.
#[cfg(feature = "pbr_multi_layer_material_textures")]
for texture_field_name in [
"clearcoatTexture",
"clearcoatRoughnessTexture",
"clearcoatNormalTexture",
] {
if let Some(texture_index) =
extension_texture_index(&material, "KHR_materials_clearcoat", texture_field_name)
{
linear_textures.insert(texture_index);
}
}
}
linear_textures
}

View File

@@ -0,0 +1,94 @@
use bevy_ecs::name::Name;
use bevy_math::{Mat4, Vec3};
use bevy_transform::components::Transform;
use gltf::scene::Node;
use fixedbitset::FixedBitSet;
use itertools::Itertools;
#[cfg(feature = "bevy_animation")]
use bevy_platform::collections::{HashMap, HashSet};
use crate::GltfError;
pub(crate) fn node_name(node: &Node) -> Name {
let name = node
.name()
.map(ToString::to_string)
.unwrap_or_else(|| format!("GltfNode{}", node.index()));
Name::new(name)
}
/// Calculate the transform of gLTF [`Node`].
///
/// This should be used instead of calling [`gltf::scene::Transform::matrix()`]
/// on [`Node::transform()`](gltf::Node::transform) directly because it uses optimized glam types and
/// if `libm` feature of `bevy_math` crate is enabled also handles cross
/// platform determinism properly.
pub(crate) fn node_transform(node: &Node) -> Transform {
match node.transform() {
gltf::scene::Transform::Matrix { matrix } => {
Transform::from_matrix(Mat4::from_cols_array_2d(&matrix))
}
gltf::scene::Transform::Decomposed {
translation,
rotation,
scale,
} => Transform {
translation: Vec3::from(translation),
rotation: bevy_math::Quat::from_array(rotation),
scale: Vec3::from(scale),
},
}
}
#[cfg_attr(
not(target_arch = "wasm32"),
expect(
clippy::result_large_err,
reason = "need to be signature compatible with `load_gltf`"
)
)]
/// Check if [`Node`] is part of cycle
pub(crate) fn check_is_part_of_cycle(
node: &Node,
visited: &mut FixedBitSet,
) -> Result<(), GltfError> {
// Do we have a cycle?
if visited.contains(node.index()) {
return Err(GltfError::CircularChildren(format!(
"glTF nodes form a cycle: {} -> {}",
visited.ones().map(|bit| bit.to_string()).join(" -> "),
node.index()
)));
}
// Recurse.
visited.insert(node.index());
for kid in node.children() {
check_is_part_of_cycle(&kid, visited)?;
}
visited.remove(node.index());
Ok(())
}
#[cfg(feature = "bevy_animation")]
pub(crate) fn collect_path(
node: &Node,
current_path: &[Name],
paths: &mut HashMap<usize, (usize, Vec<Name>)>,
root_index: usize,
visited: &mut HashSet<usize>,
) {
let mut path = current_path.to_owned();
path.push(node_name(node));
visited.insert(node.index());
for child in node.children() {
if !visited.contains(&child.index()) {
collect_path(&child, &path, paths, root_index, visited);
}
}
paths.insert(node.index(), (root_index, path));
}

View File

@@ -0,0 +1,126 @@
use bevy_asset::{Handle, LoadContext};
use bevy_image::{Image, ImageAddressMode, ImageFilterMode, ImageSamplerDescriptor};
use bevy_math::Affine2;
use gltf::{
image::Source,
texture::{MagFilter, MinFilter, Texture, TextureTransform, WrappingMode},
};
#[cfg(any(
feature = "pbr_anisotropy_texture",
feature = "pbr_multi_layer_material_textures",
feature = "pbr_specular_textures"
))]
use gltf::{json::texture::Info, Document};
use crate::{loader::DataUri, GltfAssetLabel};
pub(crate) fn texture_handle(
texture: &Texture<'_>,
load_context: &mut LoadContext,
) -> Handle<Image> {
match texture.source().source() {
Source::View { .. } => load_context.get_label_handle(texture_label(texture).to_string()),
Source::Uri { uri, .. } => {
let uri = percent_encoding::percent_decode_str(uri)
.decode_utf8()
.unwrap();
let uri = uri.as_ref();
if let Ok(_data_uri) = DataUri::parse(uri) {
load_context.get_label_handle(texture_label(texture).to_string())
} else {
let parent = load_context.path().parent().unwrap();
let image_path = parent.join(uri);
load_context.load(image_path)
}
}
}
}
/// Extracts the texture sampler data from the glTF [`Texture`].
pub(crate) fn texture_sampler(texture: &Texture<'_>) -> ImageSamplerDescriptor {
let gltf_sampler = texture.sampler();
ImageSamplerDescriptor {
address_mode_u: address_mode(&gltf_sampler.wrap_s()),
address_mode_v: address_mode(&gltf_sampler.wrap_t()),
mag_filter: gltf_sampler
.mag_filter()
.map(|mf| match mf {
MagFilter::Nearest => ImageFilterMode::Nearest,
MagFilter::Linear => ImageFilterMode::Linear,
})
.unwrap_or(ImageSamplerDescriptor::default().mag_filter),
min_filter: gltf_sampler
.min_filter()
.map(|mf| match mf {
MinFilter::Nearest
| MinFilter::NearestMipmapNearest
| MinFilter::NearestMipmapLinear => ImageFilterMode::Nearest,
MinFilter::Linear
| MinFilter::LinearMipmapNearest
| MinFilter::LinearMipmapLinear => ImageFilterMode::Linear,
})
.unwrap_or(ImageSamplerDescriptor::default().min_filter),
mipmap_filter: gltf_sampler
.min_filter()
.map(|mf| match mf {
MinFilter::Nearest
| MinFilter::Linear
| MinFilter::NearestMipmapNearest
| MinFilter::LinearMipmapNearest => ImageFilterMode::Nearest,
MinFilter::NearestMipmapLinear | MinFilter::LinearMipmapLinear => {
ImageFilterMode::Linear
}
})
.unwrap_or(ImageSamplerDescriptor::default().mipmap_filter),
..Default::default()
}
}
pub(crate) fn texture_label(texture: &Texture<'_>) -> GltfAssetLabel {
GltfAssetLabel::Texture(texture.index())
}
pub(crate) fn address_mode(wrapping_mode: &WrappingMode) -> ImageAddressMode {
match wrapping_mode {
WrappingMode::ClampToEdge => ImageAddressMode::ClampToEdge,
WrappingMode::Repeat => ImageAddressMode::Repeat,
WrappingMode::MirroredRepeat => ImageAddressMode::MirrorRepeat,
}
}
pub(crate) fn texture_transform_to_affine2(texture_transform: TextureTransform) -> Affine2 {
Affine2::from_scale_angle_translation(
texture_transform.scale().into(),
-texture_transform.rotation(),
texture_transform.offset().into(),
)
}
#[cfg(any(
feature = "pbr_anisotropy_texture",
feature = "pbr_multi_layer_material_textures",
feature = "pbr_specular_textures"
))]
/// Given a [`Info`], returns the handle of the texture that this
/// refers to.
///
/// This is a low-level function only used when the [`gltf`] crate has no support
/// for an extension, forcing us to parse its texture references manually.
pub(crate) fn texture_handle_from_info(
info: &Info,
document: &Document,
load_context: &mut LoadContext,
) -> Handle<Image> {
let texture = document
.textures()
.nth(info.index.value())
.expect("Texture info references a nonexistent texture");
texture_handle(&texture, load_context)
}

2176
vendor/bevy_gltf/src/loader/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,301 @@
use bevy_mesh::{Mesh, MeshVertexAttribute, VertexAttributeValues as Values, VertexFormat};
use bevy_platform::collections::HashMap;
use gltf::{
accessor::{DataType, Dimensions},
mesh::util::{ReadColors, ReadJoints, ReadTexCoords, ReadWeights},
};
use thiserror::Error;
/// Represents whether integer data requires normalization
#[derive(Copy, Clone)]
struct Normalization(bool);
impl Normalization {
fn apply_either<T, U>(
self,
value: T,
normalized_ctor: impl Fn(T) -> U,
unnormalized_ctor: impl Fn(T) -> U,
) -> U {
if self.0 {
normalized_ctor(value)
} else {
unnormalized_ctor(value)
}
}
}
/// An error that occurs when accessing buffer data
#[derive(Error, Debug)]
pub(crate) enum AccessFailed {
#[error("Malformed vertex attribute data")]
MalformedData,
#[error("Unsupported vertex attribute format")]
UnsupportedFormat,
}
/// Helper for reading buffer data
struct BufferAccessor<'a> {
accessor: gltf::Accessor<'a>,
buffer_data: &'a Vec<Vec<u8>>,
normalization: Normalization,
}
impl<'a> BufferAccessor<'a> {
/// Creates an iterator over the elements in this accessor
fn iter<T: gltf::accessor::Item>(self) -> Result<gltf::accessor::Iter<'a, T>, AccessFailed> {
gltf::accessor::Iter::new(self.accessor, |buffer: gltf::Buffer| {
self.buffer_data.get(buffer.index()).map(Vec::as_slice)
})
.ok_or(AccessFailed::MalformedData)
}
/// Applies the element iterator to a constructor or fails if normalization is required
fn with_no_norm<T: gltf::accessor::Item, U>(
self,
ctor: impl Fn(gltf::accessor::Iter<'a, T>) -> U,
) -> Result<U, AccessFailed> {
if self.normalization.0 {
return Err(AccessFailed::UnsupportedFormat);
}
self.iter().map(ctor)
}
/// Applies the element iterator and the normalization flag to a constructor
fn with_norm<T: gltf::accessor::Item, U>(
self,
ctor: impl Fn(gltf::accessor::Iter<'a, T>, Normalization) -> U,
) -> Result<U, AccessFailed> {
let normalized = self.normalization;
self.iter().map(|v| ctor(v, normalized))
}
}
/// An enum of the iterators user by different vertex attribute formats
enum VertexAttributeIter<'a> {
// For reading native WGPU formats
F32(gltf::accessor::Iter<'a, f32>),
U32(gltf::accessor::Iter<'a, u32>),
F32x2(gltf::accessor::Iter<'a, [f32; 2]>),
U32x2(gltf::accessor::Iter<'a, [u32; 2]>),
F32x3(gltf::accessor::Iter<'a, [f32; 3]>),
U32x3(gltf::accessor::Iter<'a, [u32; 3]>),
F32x4(gltf::accessor::Iter<'a, [f32; 4]>),
U32x4(gltf::accessor::Iter<'a, [u32; 4]>),
S16x2(gltf::accessor::Iter<'a, [i16; 2]>, Normalization),
U16x2(gltf::accessor::Iter<'a, [u16; 2]>, Normalization),
S16x4(gltf::accessor::Iter<'a, [i16; 4]>, Normalization),
U16x4(gltf::accessor::Iter<'a, [u16; 4]>, Normalization),
S8x2(gltf::accessor::Iter<'a, [i8; 2]>, Normalization),
U8x2(gltf::accessor::Iter<'a, [u8; 2]>, Normalization),
S8x4(gltf::accessor::Iter<'a, [i8; 4]>, Normalization),
U8x4(gltf::accessor::Iter<'a, [u8; 4]>, Normalization),
// Additional on-disk formats used for RGB colors
U16x3(gltf::accessor::Iter<'a, [u16; 3]>, Normalization),
U8x3(gltf::accessor::Iter<'a, [u8; 3]>, Normalization),
}
impl<'a> VertexAttributeIter<'a> {
/// Creates an iterator over the elements in a vertex attribute accessor
fn from_accessor(
accessor: gltf::Accessor<'a>,
buffer_data: &'a Vec<Vec<u8>>,
) -> Result<VertexAttributeIter<'a>, AccessFailed> {
let normalization = Normalization(accessor.normalized());
let format = (accessor.data_type(), accessor.dimensions());
let acc = BufferAccessor {
accessor,
buffer_data,
normalization,
};
match format {
(DataType::F32, Dimensions::Scalar) => acc.with_no_norm(VertexAttributeIter::F32),
(DataType::U32, Dimensions::Scalar) => acc.with_no_norm(VertexAttributeIter::U32),
(DataType::F32, Dimensions::Vec2) => acc.with_no_norm(VertexAttributeIter::F32x2),
(DataType::U32, Dimensions::Vec2) => acc.with_no_norm(VertexAttributeIter::U32x2),
(DataType::F32, Dimensions::Vec3) => acc.with_no_norm(VertexAttributeIter::F32x3),
(DataType::U32, Dimensions::Vec3) => acc.with_no_norm(VertexAttributeIter::U32x3),
(DataType::F32, Dimensions::Vec4) => acc.with_no_norm(VertexAttributeIter::F32x4),
(DataType::U32, Dimensions::Vec4) => acc.with_no_norm(VertexAttributeIter::U32x4),
(DataType::I16, Dimensions::Vec2) => acc.with_norm(VertexAttributeIter::S16x2),
(DataType::U16, Dimensions::Vec2) => acc.with_norm(VertexAttributeIter::U16x2),
(DataType::I16, Dimensions::Vec4) => acc.with_norm(VertexAttributeIter::S16x4),
(DataType::U16, Dimensions::Vec4) => acc.with_norm(VertexAttributeIter::U16x4),
(DataType::I8, Dimensions::Vec2) => acc.with_norm(VertexAttributeIter::S8x2),
(DataType::U8, Dimensions::Vec2) => acc.with_norm(VertexAttributeIter::U8x2),
(DataType::I8, Dimensions::Vec4) => acc.with_norm(VertexAttributeIter::S8x4),
(DataType::U8, Dimensions::Vec4) => acc.with_norm(VertexAttributeIter::U8x4),
(DataType::U16, Dimensions::Vec3) => acc.with_norm(VertexAttributeIter::U16x3),
(DataType::U8, Dimensions::Vec3) => acc.with_norm(VertexAttributeIter::U8x3),
_ => Err(AccessFailed::UnsupportedFormat),
}
}
/// Materializes values for any supported format of vertex attribute
fn into_any_values(self) -> Result<Values, AccessFailed> {
match self {
VertexAttributeIter::F32(it) => Ok(Values::Float32(it.collect())),
VertexAttributeIter::U32(it) => Ok(Values::Uint32(it.collect())),
VertexAttributeIter::F32x2(it) => Ok(Values::Float32x2(it.collect())),
VertexAttributeIter::U32x2(it) => Ok(Values::Uint32x2(it.collect())),
VertexAttributeIter::F32x3(it) => Ok(Values::Float32x3(it.collect())),
VertexAttributeIter::U32x3(it) => Ok(Values::Uint32x3(it.collect())),
VertexAttributeIter::F32x4(it) => Ok(Values::Float32x4(it.collect())),
VertexAttributeIter::U32x4(it) => Ok(Values::Uint32x4(it.collect())),
VertexAttributeIter::S16x2(it, n) => {
Ok(n.apply_either(it.collect(), Values::Snorm16x2, Values::Sint16x2))
}
VertexAttributeIter::U16x2(it, n) => {
Ok(n.apply_either(it.collect(), Values::Unorm16x2, Values::Uint16x2))
}
VertexAttributeIter::S16x4(it, n) => {
Ok(n.apply_either(it.collect(), Values::Snorm16x4, Values::Sint16x4))
}
VertexAttributeIter::U16x4(it, n) => {
Ok(n.apply_either(it.collect(), Values::Unorm16x4, Values::Uint16x4))
}
VertexAttributeIter::S8x2(it, n) => {
Ok(n.apply_either(it.collect(), Values::Snorm8x2, Values::Sint8x2))
}
VertexAttributeIter::U8x2(it, n) => {
Ok(n.apply_either(it.collect(), Values::Unorm8x2, Values::Uint8x2))
}
VertexAttributeIter::S8x4(it, n) => {
Ok(n.apply_either(it.collect(), Values::Snorm8x4, Values::Sint8x4))
}
VertexAttributeIter::U8x4(it, n) => {
Ok(n.apply_either(it.collect(), Values::Unorm8x4, Values::Uint8x4))
}
_ => Err(AccessFailed::UnsupportedFormat),
}
}
/// Materializes RGBA values, converting compatible formats to Float32x4
fn into_rgba_values(self) -> Result<Values, AccessFailed> {
match self {
VertexAttributeIter::U8x3(it, Normalization(true)) => Ok(Values::Float32x4(
ReadColors::RgbU8(it).into_rgba_f32().collect(),
)),
VertexAttributeIter::U16x3(it, Normalization(true)) => Ok(Values::Float32x4(
ReadColors::RgbU16(it).into_rgba_f32().collect(),
)),
VertexAttributeIter::F32x3(it) => Ok(Values::Float32x4(
ReadColors::RgbF32(it).into_rgba_f32().collect(),
)),
VertexAttributeIter::U8x4(it, Normalization(true)) => Ok(Values::Float32x4(
ReadColors::RgbaU8(it).into_rgba_f32().collect(),
)),
VertexAttributeIter::U16x4(it, Normalization(true)) => Ok(Values::Float32x4(
ReadColors::RgbaU16(it).into_rgba_f32().collect(),
)),
s => s.into_any_values(),
}
}
/// Materializes joint index values, converting compatible formats to Uint16x4
fn into_joint_index_values(self) -> Result<Values, AccessFailed> {
match self {
VertexAttributeIter::U8x4(it, Normalization(false)) => {
Ok(Values::Uint16x4(ReadJoints::U8(it).into_u16().collect()))
}
s => s.into_any_values(),
}
}
/// Materializes joint weight values, converting compatible formats to Float32x4
fn into_joint_weight_values(self) -> Result<Values, AccessFailed> {
match self {
VertexAttributeIter::U8x4(it, Normalization(true)) => {
Ok(Values::Float32x4(ReadWeights::U8(it).into_f32().collect()))
}
VertexAttributeIter::U16x4(it, Normalization(true)) => {
Ok(Values::Float32x4(ReadWeights::U16(it).into_f32().collect()))
}
s => s.into_any_values(),
}
}
/// Materializes texture coordinate values, converting compatible formats to Float32x2
fn into_tex_coord_values(self) -> Result<Values, AccessFailed> {
match self {
VertexAttributeIter::U8x2(it, Normalization(true)) => Ok(Values::Float32x2(
ReadTexCoords::U8(it).into_f32().collect(),
)),
VertexAttributeIter::U16x2(it, Normalization(true)) => Ok(Values::Float32x2(
ReadTexCoords::U16(it).into_f32().collect(),
)),
s => s.into_any_values(),
}
}
}
enum ConversionMode {
Any,
Rgba,
JointIndex,
JointWeight,
TexCoord,
}
#[derive(Error, Debug)]
pub(crate) enum ConvertAttributeError {
#[error("Vertex attribute {0} has format {1:?} but expected {3:?} for target attribute {2}")]
WrongFormat(String, VertexFormat, String, VertexFormat),
#[error("{0} in accessor {1}")]
AccessFailed(AccessFailed, usize),
#[error("Unknown vertex attribute {0}")]
UnknownName(String),
}
pub(crate) fn convert_attribute(
semantic: gltf::Semantic,
accessor: gltf::Accessor,
buffer_data: &Vec<Vec<u8>>,
custom_vertex_attributes: &HashMap<Box<str>, MeshVertexAttribute>,
) -> Result<(MeshVertexAttribute, Values), ConvertAttributeError> {
if let Some((attribute, conversion)) = match &semantic {
gltf::Semantic::Positions => Some((Mesh::ATTRIBUTE_POSITION, ConversionMode::Any)),
gltf::Semantic::Normals => Some((Mesh::ATTRIBUTE_NORMAL, ConversionMode::Any)),
gltf::Semantic::Tangents => Some((Mesh::ATTRIBUTE_TANGENT, ConversionMode::Any)),
gltf::Semantic::Colors(0) => Some((Mesh::ATTRIBUTE_COLOR, ConversionMode::Rgba)),
gltf::Semantic::TexCoords(0) => Some((Mesh::ATTRIBUTE_UV_0, ConversionMode::TexCoord)),
gltf::Semantic::TexCoords(1) => Some((Mesh::ATTRIBUTE_UV_1, ConversionMode::TexCoord)),
gltf::Semantic::Joints(0) => {
Some((Mesh::ATTRIBUTE_JOINT_INDEX, ConversionMode::JointIndex))
}
gltf::Semantic::Weights(0) => {
Some((Mesh::ATTRIBUTE_JOINT_WEIGHT, ConversionMode::JointWeight))
}
gltf::Semantic::Extras(name) => custom_vertex_attributes
.get(name.as_str())
.map(|attr| (*attr, ConversionMode::Any)),
_ => None,
} {
let raw_iter = VertexAttributeIter::from_accessor(accessor.clone(), buffer_data);
let converted_values = raw_iter.and_then(|iter| match conversion {
ConversionMode::Any => iter.into_any_values(),
ConversionMode::Rgba => iter.into_rgba_values(),
ConversionMode::TexCoord => iter.into_tex_coord_values(),
ConversionMode::JointIndex => iter.into_joint_index_values(),
ConversionMode::JointWeight => iter.into_joint_weight_values(),
});
match converted_values {
Ok(values) => {
let loaded_format = VertexFormat::from(&values);
if attribute.format == loaded_format {
Ok((attribute, values))
} else {
Err(ConvertAttributeError::WrongFormat(
semantic.to_string(),
loaded_format,
attribute.name.to_string(),
attribute.format,
))
}
}
Err(err) => Err(ConvertAttributeError::AccessFailed(err, accessor.index())),
}
} else {
Err(ConvertAttributeError::UnknownName(semantic.to_string()))
}
}