Skip to content

Commit aaa8f70

Browse files
committed
WIP: Fix glTF model forward direction
1 parent 33bed5d commit aaa8f70

File tree

18 files changed

+266
-11
lines changed

18 files changed

+266
-11
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3278,6 +3278,17 @@ description = "A demonstration of Transform's axis-alignment feature"
32783278
category = "Transforms"
32793279
wasm = true
32803280

3281+
[[example]]
3282+
name = "model_forward"
3283+
path = "examples/transforms/model_forward.rs"
3284+
doc-scrape-examples = true
3285+
3286+
[package.metadata.example.model_forward]
3287+
name = "model_forward"
3288+
description = "Illustrates when to use camera_forward vs model_forward, and different cases for model_forward"
3289+
category = "Transforms"
3290+
wasm = true
3291+
32813292
[[example]]
32823293
name = "scale"
32833294
path = "examples/transforms/scale.rs"

crates/bevy_animation/src/animatable.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,20 @@ impl Animatable for Transform {
136136
translation: Vec3::interpolate(&a.translation, &b.translation, t),
137137
rotation: Quat::interpolate(&a.rotation, &b.rotation, t),
138138
scale: Vec3::interpolate(&a.scale, &b.scale, t),
139+
flip_model_forward: a.flip_model_forward,
139140
}
140141
}
141142

142143
fn blend(inputs: impl Iterator<Item = BlendInput<Self>>) -> Self {
143144
let mut translation = Vec3A::ZERO;
144145
let mut scale = Vec3A::ZERO;
145146
let mut rotation = Quat::IDENTITY;
147+
let mut flip_model_forward = None;
146148

147149
for input in inputs {
150+
if flip_model_forward.is_none() {
151+
flip_model_forward = Some(input.value.flip_model_forward);
152+
}
148153
if input.additive {
149154
translation += input.weight * Vec3A::from(input.value.translation);
150155
scale += input.weight * Vec3A::from(input.value.scale);
@@ -165,6 +170,7 @@ impl Animatable for Transform {
165170
translation: Vec3::from(translation),
166171
rotation,
167172
scale: Vec3::from(scale),
173+
flip_model_forward: flip_model_forward.unwrap_or_default(),
168174
}
169175
}
170176
}

crates/bevy_gltf/src/loader/gltf_ext/scene.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub(crate) fn node_transform(node: &Node, convert_coordinates: bool) -> Transfor
4242
translation: Vec3::from(translation),
4343
rotation: bevy_math::Quat::from_array(rotation),
4444
scale: Vec3::from(scale),
45+
flip_model_forward: true,
4546
},
4647
};
4748
if convert_coordinates {

crates/bevy_transform/src/components/global_transform.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ impl GlobalTransform {
136136
translation,
137137
rotation,
138138
scale,
139+
..Default::default()
139140
}
140141
}
141142

@@ -193,6 +194,7 @@ impl GlobalTransform {
193194
translation,
194195
rotation,
195196
scale,
197+
..Default::default()
196198
}
197199
}
198200

@@ -367,11 +369,13 @@ mod test {
367369
translation: Vec3::new(1034.0, 34.0, -1324.34),
368370
rotation: Quat::from_euler(XYZ, 1.0, 0.9, 2.1),
369371
scale: Vec3::new(1.0, 1.0, 1.0),
372+
..Default::default()
370373
});
371374
let t2 = GlobalTransform::from(Transform {
372375
translation: Vec3::new(0.0, -54.493, 324.34),
373376
rotation: Quat::from_euler(XYZ, 1.9, 0.3, 3.0),
374377
scale: Vec3::new(1.345, 1.345, 1.345),
378+
..Default::default()
375379
});
376380
let retransformed = reparent_to_same(t1, t2);
377381
assert!(
@@ -387,11 +391,13 @@ mod test {
387391
translation: Vec3::new(1034.0, 34.0, -1324.34),
388392
rotation: Quat::from_euler(XYZ, 0.8, 1.9, 2.1),
389393
scale: Vec3::new(10.9, 10.9, 10.9),
394+
..Default::default()
390395
});
391396
let t2 = GlobalTransform::from(Transform {
392397
translation: Vec3::new(28.0, -54.493, 324.34),
393398
rotation: Quat::from_euler(XYZ, 0.0, 3.1, 0.1),
394399
scale: Vec3::new(0.9, 0.9, 0.9),
400+
..Default::default()
395401
});
396402
// goal: find `X` such as `t2 * X = t1`
397403
let reparented = t1.reparented_to(&t2);

crates/bevy_transform/src/components/transform.rs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ pub struct Transform {
9898
///
9999
/// [`scale`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/scale.rs
100100
pub scale: Vec3,
101+
/// Whether the model forward direction is flipped from -z to +z.
102+
///
103+
/// glTF specifies that models have a forward direction of +z whereas cameras and lights have -z.
104+
/// This option allows the glTF importer and other usages to make appropriate adjustments.
105+
pub flip_model_forward: bool,
101106
}
102107

103108
impl Transform {
@@ -106,6 +111,7 @@ impl Transform {
106111
translation: Vec3::ZERO,
107112
rotation: Quat::IDENTITY,
108113
scale: Vec3::ONE,
114+
flip_model_forward: false,
109115
};
110116

111117
/// Creates a new [`Transform`] at the position `(x, y, z)`. In 2d, the `z` component
@@ -126,6 +132,7 @@ impl Transform {
126132
translation,
127133
rotation,
128134
scale,
135+
flip_model_forward: false,
129136
}
130137
}
131138

@@ -317,16 +324,44 @@ impl Transform {
317324

318325
/// Equivalent to [`-local_z()`][Transform::local_z]
319326
#[inline]
320-
pub fn forward(&self) -> Dir3 {
327+
pub fn camera_forward(&self) -> Dir3 {
321328
-self.local_z()
322329
}
323330

324331
/// Equivalent to [`local_z()`][Transform::local_z]
325332
#[inline]
326-
pub fn back(&self) -> Dir3 {
333+
pub fn camera_back(&self) -> Dir3 {
327334
self.local_z()
328335
}
329336

337+
/// Equivalent to [`-local_z()`][Transform::local_z] if `flip_model_forward` is false,
338+
/// else [`local_z()`][Transform::local_z]
339+
///
340+
/// glTF has opposing forward directions for cameras and lights, and for models. Model
341+
/// forward is +z, whereas camera and light forward is -z.
342+
#[inline]
343+
pub fn model_forward(&self) -> Dir3 {
344+
if self.flip_model_forward {
345+
self.local_z()
346+
} else {
347+
-self.local_z()
348+
}
349+
}
350+
351+
/// Equivalent to [`local_z()`][Transform::local_z] if `flip_model_forward` is false,
352+
/// else [`-local_z()`][Transform::local_z]
353+
///
354+
/// glTF has opposing forward directions for cameras and lights, and for models. Model
355+
/// forward is +z, whereas camera and light forward is -z. Back is the opposite of this.
356+
#[inline]
357+
pub fn model_back(&self) -> Dir3 {
358+
if self.flip_model_forward {
359+
-self.local_z()
360+
} else {
361+
self.local_z()
362+
}
363+
}
364+
330365
/// Rotates this [`Transform`] by the given rotation.
331366
///
332367
/// If this [`Transform`] has a parent, the `rotation` is relative to the rotation of the parent.
@@ -469,7 +504,11 @@ impl Transform {
469504
/// * if `direction` is parallel with `up`, an orthogonal vector is used as the "right" direction
470505
#[inline]
471506
pub fn look_to(&mut self, direction: impl TryInto<Dir3>, up: impl TryInto<Dir3>) {
472-
let back = -direction.try_into().unwrap_or(Dir3::NEG_Z);
507+
let back = if self.flip_model_forward {
508+
direction.try_into().unwrap_or(Dir3::NEG_Z)
509+
} else {
510+
-direction.try_into().unwrap_or(Dir3::NEG_Z)
511+
};
473512
let up = up.try_into().unwrap_or(Dir3::Y);
474513
let right = up
475514
.cross(back.into())
@@ -572,6 +611,7 @@ impl Transform {
572611
translation,
573612
rotation,
574613
scale,
614+
flip_model_forward: self.flip_model_forward,
575615
}
576616
}
577617

examples/3d/motion_blur.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,8 @@ fn move_camera(
343343
}
344344
CameraMode::Chase => {
345345
transform.translation =
346-
tracked.translation + Vec3::new(0.0, 0.15, 0.0) + tracked.back() * 0.6;
347-
transform.look_to(tracked.forward(), Vec3::Y);
346+
tracked.translation + Vec3::new(0.0, 0.15, 0.0) + tracked.camera_back() * 0.6;
347+
transform.look_to(tracked.camera_forward(), Vec3::Y);
348348
if let Projection::Perspective(perspective) = &mut *projection {
349349
perspective.fov = 1.0;
350350
}

examples/3d/parallax_mapping.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,21 +166,25 @@ const CAMERA_POSITIONS: &[Transform] = &[
166166
translation: Vec3::new(1.5, 1.5, 1.5),
167167
rotation: Quat::from_xyzw(-0.279, 0.364, 0.115, 0.880),
168168
scale: Vec3::ONE,
169+
flip_model_forward: false,
169170
},
170171
Transform {
171172
translation: Vec3::new(2.4, 0.0, 0.2),
172173
rotation: Quat::from_xyzw(0.094, 0.676, 0.116, 0.721),
173174
scale: Vec3::ONE,
175+
flip_model_forward: false,
174176
},
175177
Transform {
176178
translation: Vec3::new(2.4, 2.6, -4.3),
177179
rotation: Quat::from_xyzw(0.170, 0.908, 0.308, 0.225),
178180
scale: Vec3::ONE,
181+
flip_model_forward: false,
179182
},
180183
Transform {
181184
translation: Vec3::new(-1.0, 0.8, -1.2),
182185
rotation: Quat::from_xyzw(-0.004, 0.909, 0.247, -0.335),
183186
scale: Vec3::ONE,
187+
flip_model_forward: false,
184188
},
185189
];
186190

examples/3d/tonemapping.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ fn setup_color_gradient_scene(
139139
camera_transform: Res<CameraTransform>,
140140
) {
141141
let mut transform = camera_transform.0;
142-
transform.translation += *transform.forward();
142+
transform.translation += *transform.camera_forward();
143143

144144
commands.spawn((
145145
Mesh3d(meshes.add(Rectangle::new(0.7, 0.7))),
@@ -157,7 +157,7 @@ fn setup_image_viewer_scene(
157157
camera_transform: Res<CameraTransform>,
158158
) {
159159
let mut transform = camera_transform.0;
160-
transform.translation += *transform.forward();
160+
transform.translation += *transform.camera_forward();
161161

162162
// exr/hdr viewer (exr requires enabling bevy feature)
163163
commands.spawn((

examples/camera/camera_orbit.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,5 +137,5 @@ fn orbit(
137137
// Adjust the translation to maintain the correct orientation toward the orbit target.
138138
// In our example it's a static target, but this could easily be customized.
139139
let target = Vec3::ZERO;
140-
camera.translation = target - camera.forward() * camera_settings.orbit_distance;
140+
camera.translation = target - camera.camera_forward() * camera_settings.orbit_distance;
141141
}

examples/gizmos/axes.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ fn random_transform(rng: &mut impl Rng) -> Transform {
143143
translation: random_translation(rng),
144144
rotation: random_rotation(rng),
145145
scale: random_scale(rng),
146+
..default()
146147
}
147148
}
148149

@@ -216,5 +217,6 @@ fn interpolate_transforms(t1: Transform, t2: Transform, t: f32) -> Transform {
216217
translation,
217218
rotation,
218219
scale,
220+
flip_model_forward: t1.flip_model_forward,
219221
}
220222
}

0 commit comments

Comments
 (0)