Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
04ec20a
Removed `gltf_convert_coordinates_default` feature and Bevy 0.18 warn…
greeble-dev Aug 1, 2025
a67c1e9
Changed `convert_coordinates` from a bool to a `GltfConvertCoordinate…
greeble-dev Aug 1, 2025
391741f
Removed node conversion.
greeble-dev Aug 1, 2025
1cf0d3f
Removed camera and rotation conversions that were only used by node c…
greeble-dev Aug 1, 2025
3d2bf55
Added scene conversion.
greeble-dev Aug 1, 2025
6625359
Fixed skinned mesh inverse bindposes. The Mat4 conversion assumed tha…
greeble-dev Aug 2, 2025
13d13b5
Mesh conversion now rotates the mesh primitive entities. This negates…
greeble-dev Aug 2, 2025
080c3bc
Added TODOs.
greeble-dev Aug 2, 2025
25d8992
Added utility functions for conversion transforms.
greeble-dev Aug 2, 2025
592e076
More TODOs.
greeble-dev Aug 3, 2025
20b3c3a
Merge branch 'main' into gltf-coordinate-conversion
greeble-dev Aug 3, 2025
0042c67
Merge branch 'main' into gltf-coordinate-conversion
greeble-dev Aug 11, 2025
63b16ac
Updated documentation. Fixed `coordinate_conversion` not being a publ…
greeble-dev Aug 11, 2025
f24c988
Renamed `GltfConvertCoordinates` variable from `scene` to `scenes`. T…
greeble-dev Aug 11, 2025
7984aa6
Removed the awkwardly special case `ConvertInverseCoordinates` in fav…
greeble-dev Aug 11, 2025
5cde975
Updated docs. Clarified that the mesh entity actually gets the invers…
greeble-dev Aug 11, 2025
0b4235e
Merge branch 'main' into gltf-coordinate-conversion
greeble-dev Aug 16, 2025
9248860
Merge branch 'main' into gltf-coordinate-conversion
greeble-dev Aug 16, 2025
415b989
Fixed cases that were still using `use_model_forward_direction`.
greeble-dev Aug 16, 2025
e0e777f
Updated release notes.
greeble-dev Aug 16, 2025
b24b258
Fixed typo
greeble-dev Aug 17, 2025
fb77cfb
Merge branch 'main' into gltf-coordinate-conversion
greeble-dev Aug 21, 2025
62f4018
Updated morph and bounds conversions.
greeble-dev Aug 21, 2025
b2567a5
Updated examples.
greeble-dev Aug 21, 2025
3369ef0
Reverted all changes to the 0.17 release note.
greeble-dev Aug 21, 2025
292dad0
Added new migration guide for 0.18.
greeble-dev Aug 22, 2025
f02a9e2
Fixed markdown lint.
greeble-dev Aug 22, 2025
b82be31
Improved comment.
greeble-dev Aug 22, 2025
9e9dea5
Fixed markdown lint for real this time... maybe?
greeble-dev Aug 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 78 additions & 46 deletions crates/bevy_gltf/src/convert_coordinates.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,16 @@
use core::f32::consts::PI;
//! Utilities for converting from glTF's [standard coordinate system](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#coordinate-system-and-units)
//! to Bevy's.
use serde::{Deserialize, Serialize};

use bevy_math::{Mat4, Quat, Vec3};
use bevy_transform::components::Transform;

pub(crate) trait ConvertCoordinates {
/// Converts the glTF coordinates to Bevy's coordinate system.
/// - glTF:
/// - forward: Z
/// - up: Y
/// - right: -X
/// - Bevy:
/// - forward: -Z
/// - up: Y
/// - right: X
///
/// See <https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#coordinate-system-and-units>
/// Converts from glTF coordinates to Bevy's coordinate system. See
/// [`GltfConvertCoordinates`] for an explanation of the conversion.
fn convert_coordinates(self) -> Self;
}

pub(crate) trait ConvertCameraCoordinates {
/// Like `convert_coordinates`, but uses the following for the lens rotation:
/// - forward: -Z
/// - up: Y
/// - right: X
///
/// The same convention is used for lights.
/// See <https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#view-matrix>
fn convert_camera_coordinates(self) -> Self;
}

impl ConvertCoordinates for Vec3 {
fn convert_coordinates(self) -> Self {
Vec3::new(-self.x, self.y, -self.z)
Expand All @@ -48,34 +30,84 @@ impl ConvertCoordinates for [f32; 4] {
}
}

impl ConvertCoordinates for Quat {
fn convert_coordinates(self) -> Self {
// Solution of q' = r q r*
Quat::from_array([-self.x, self.y, -self.z, self.w])
}
/// Options for converting scenes and assets from glTF's [standard coordinate system](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#coordinate-system-and-units)
/// (+Z forward) to Bevy's coordinate system (-Z forward).
///
/// The exact coordinate system conversion is as follows:
/// - glTF:
/// - forward: +Z
/// - up: +Y
/// - right: -X
/// - Bevy:
/// - forward: -Z
/// - up: +Y
/// - right: +X
///
/// Note that some glTF files may not follow the glTF standard.
///
/// If your glTF scene is +Z forward and you want it converted to match Bevy's
/// `Transform::forward`, enable the `scenes` option. If you also want `Mesh`
/// assets to be converted, enable the `meshes` option.
///
/// Cameras and lights in glTF files are an exception - they already use Bevy's
/// coordinate system. This means cameras and lights will match
/// `Transform::forward` even if conversion is disabled.
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)]
pub struct GltfConvertCoordinates {
/// If true, convert scenes via the transform of the scene entity.
///
/// The glTF loader creates an entity for each glTF scene. Entities are
/// then created for each node within the glTF scene. Nodes without a
/// parent in the glTF scene become children of the scene entity.
///
/// This option only changes the transform of the scene entity. It does not
/// directly change the transforms of node entities - it only changes them
/// indirectly through transform inheritance.
pub scenes: bool,

/// If true, convert mesh assets. This includes skinned mesh bind poses.
///
/// This option only changes mesh assets and the transforms of entities that
/// instance meshes. It does not change the transforms of entities that
/// correspond to glTF nodes.
pub meshes: bool,
}

impl ConvertCoordinates for Mat4 {
fn convert_coordinates(self) -> Self {
let m: Mat4 = Mat4::from_scale(Vec3::new(-1.0, 1.0, -1.0));
// Same as the original matrix
let m_inv = m;
m_inv * self * m
impl GltfConvertCoordinates {
const CONVERSION_TRANSFORM: Transform =
Transform::from_rotation(Quat::from_xyzw(0.0, 1.0, 0.0, 0.0));

fn conversion_mat4() -> Mat4 {
Mat4::from_scale(Vec3::new(-1.0, 1.0, -1.0))
}
}

impl ConvertCoordinates for Transform {
fn convert_coordinates(mut self) -> Self {
self.translation = self.translation.convert_coordinates();
self.rotation = self.rotation.convert_coordinates();
self
pub(crate) fn scene_conversion_transform(&self) -> Transform {
if self.scenes {
Self::CONVERSION_TRANSFORM
} else {
Transform::IDENTITY
}
}

pub(crate) fn mesh_conversion_transform(&self) -> Transform {
if self.meshes {
Self::CONVERSION_TRANSFORM
} else {
Transform::IDENTITY
}
}

pub(crate) fn mesh_conversion_transform_inverse(&self) -> Transform {
// We magically know that the transform is its own inverse. We still
// make a distinction at the interface level in case that changes.
self.mesh_conversion_transform()
}
}

impl ConvertCameraCoordinates for Transform {
fn convert_camera_coordinates(mut self) -> Self {
self.translation = self.translation.convert_coordinates();
self.rotate_y(PI);
self
pub(crate) fn mesh_conversion_mat4(&self) -> Mat4 {
if self.meshes {
Self::conversion_mat4()
} else {
Mat4::IDENTITY
}
}
}
24 changes: 8 additions & 16 deletions crates/bevy_gltf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
//! You can use [`GltfAssetLabel`] to ensure you are using the correct label.

mod assets;
mod convert_coordinates;
pub mod convert_coordinates;
mod label;
mod loader;
mod vertex_attributes;
Expand All @@ -118,6 +118,8 @@ pub mod prelude {
pub use crate::{assets::Gltf, assets::GltfExtras, label::GltfAssetLabel};
}

use crate::convert_coordinates::GltfConvertCoordinates;

pub use {assets::*, label::GltfAssetLabel, loader::*};

// Has to store an Arc<Mutex<...>> as there is no other way to mutate fields of asset loaders.
Expand Down Expand Up @@ -159,19 +161,9 @@ pub struct GltfPlugin {
/// Can be modified with the [`DefaultGltfImageSampler`] resource.
pub default_sampler: ImageSamplerDescriptor,

/// _CAUTION: This is an experimental feature with [known issues](https://github.com/bevyengine/bevy/issues/20621). Behavior may change in future versions._
///
/// How to convert glTF coordinates on import. Assuming glTF cameras, glTF lights, and glTF meshes had global identity transforms,
/// their Bevy [`Transform::forward`](bevy_transform::components::Transform::forward) will be pointing in the following global directions:
/// - When set to `false`
/// - glTF cameras and glTF lights: global -Z,
/// - glTF models: global +Z.
/// - When set to `true`
/// - glTF cameras and glTF lights: global +Z,
/// - glTF models: global -Z.
///
/// The default is `false`.
pub use_model_forward_direction: bool,
/// The default glTF coordinate conversion setting. This can be overridden
/// per-load by [`GltfLoaderSettings::convert_coordinates`].
pub convert_coordinates: GltfConvertCoordinates,

/// Registry for custom vertex attributes.
///
Expand All @@ -184,7 +176,7 @@ impl Default for GltfPlugin {
GltfPlugin {
default_sampler: ImageSamplerDescriptor::linear(),
custom_vertex_attributes: HashMap::default(),
use_model_forward_direction: false,
convert_coordinates: GltfConvertCoordinates::default(),
}
}
}
Expand Down Expand Up @@ -234,7 +226,7 @@ impl Plugin for GltfPlugin {
supported_compressed_formats,
custom_vertex_attributes: self.custom_vertex_attributes.clone(),
default_sampler,
default_use_model_forward_direction: self.use_model_forward_direction,
default_convert_coordinates: self.convert_coordinates,
});
}
}
18 changes: 3 additions & 15 deletions crates/bevy_gltf/src/loader/gltf_ext/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ use itertools::Itertools;
#[cfg(feature = "bevy_animation")]
use bevy_platform::collections::{HashMap, HashSet};

use crate::{
convert_coordinates::{ConvertCameraCoordinates as _, ConvertCoordinates as _},
GltfError,
};
use crate::GltfError;

pub(crate) fn node_name(node: &Node) -> Name {
let name = node
Expand All @@ -29,8 +26,8 @@ pub(crate) fn node_name(node: &Node) -> Name {
/// 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, convert_coordinates: bool) -> Transform {
let transform = match node.transform() {
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))
}
Expand All @@ -43,15 +40,6 @@ pub(crate) fn node_transform(node: &Node, convert_coordinates: bool) -> Transfor
rotation: bevy_math::Quat::from_array(rotation),
scale: Vec3::from(scale),
},
};
if convert_coordinates {
if node.camera().is_some() || node.light().is_some() {
transform.convert_camera_coordinates()
} else {
transform.convert_coordinates()
}
} else {
transform
}
}

Expand Down
Loading
Loading