-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Merged by Bors] - fix mul_vec3 tranformation order: should be scale -> rotate -> translate #3811
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is well-justified, and I agree with this convention. Thanks for tracking this down, and finding references to back up the choice.
I couldn't see a link to the previous discussion in #1755 in the PR description. |
I agree with @rsk700 that Though there are some inconsistencies and questions that are still not answered in #1755: mainly SRT vs RST and how to propagate scaling. It's out of the scope of this PR but I think it's still relevant to consider. In my opinion it would make sense to double down on one strategy and stick with that. |
I have recently bumped into this issue as an unknowing novice... I am using To display the colliders, And it turned out that this approach, sadly, doesn't work with current |
Adding |
Worth noting that without this PR:
|
Unity apparently does SRT. Godot empirically does translation last but I don't know if it is RST or SRT. Unreal Engine 4.27.2 empirically does translation last but I don't know if RST or SRT. |
I did an "engine op order" investigation here: #1755 (comment) |
Ah good. :) However, that comment says they basically all do SRT and that bevy does too. But this PR seems to be about making bevy do SRT. Do you have a comment on this PR? |
With the exception of the Sprites, on the other hand. Use The solution here is exactly whats done in this PR: fix the mul_vec3 implementation to line up with the approach we use elsewhere (SRT). I think this made it this far in an inconsistent state because most people aren't manually transforming Vec3s with transforms. The "batched sprite rendering" is new as of 0.6, which "surfaced" this for the first time. |
bors r+ |
…ate (#3811) # Objective Lets say we need to rotate stretched object for this purpose we can created stretched `Child` and add as child to `Parent`, later we can rotate `Parent`, `Child` in this situation should rotate keeping it form, it is not the case with `SpriteBundle` currently. If you try to do it with `SpriteBundle` it will deform. ## Solution My pull request fixes order of transformations to scale -> rotate -> translate, with this fix `SpriteBundle` behaves as expected in described rotation, without deformation. Here is quote from "Essential Mathematics for Games": > Generally, the desired order we wish to use for these transforms is to scale first, then rotate, then translate. Scaling first gives us the scaling along the axes we expect. We can then rotate around the origin of the frame, and then translate it into place. I'm must say when I was using `MaterialMesh2dBundle` it behaves correctly in both cases with `bevy main` and with my fix, don't know why, was not able to figure it out why there is difference. here is code I was using for testing: ```rust use bevy::{ prelude::*, render::render_resource::{Extent3d, TextureDimension, TextureFormat}, sprite::{MaterialMesh2dBundle, Mesh2dHandle}, }; fn main() { let mut app = App::new(); app.insert_resource(ClearColor(Color::rgb(0.1, 0.2, 0.3))) .add_plugins(DefaultPlugins) .add_startup_system(setup); app.run(); } fn setup( mut commands: Commands, mut images: ResMut<Assets<Image>>, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<ColorMaterial>>, ) { let mut c = OrthographicCameraBundle::new_2d(); c.orthographic_projection.scale = 1.0 / 10.0; commands.spawn_bundle(c); // note: mesh somehow works for both variants // let quad: Mesh2dHandle = meshes.add(Mesh::from(shape::Quad::default())).into(); // let child = commands // .spawn_bundle(MaterialMesh2dBundle { // mesh: quad.clone(), // transform: Transform::from_translation(Vec3::new(0.0, 0.0, -1.0)) // .with_scale(Vec3::new(10.0, 1.0, 1.0)), // material: materials.add(ColorMaterial::from(Color::BLACK)), // ..Default::default() // }) // .id(); // commands // .spawn_bundle(MaterialMesh2dBundle { // mesh: quad, // transform: Transform::from_rotation(Quat::from_rotation_z(0.78)) // .with_translation(Vec3::new(0.0, 0.0, 10.0)), // material: materials.add(ColorMaterial::from(Color::WHITE)), // ..Default::default() // }) // .push_children(&[child]); let white = images.add(get_image(Color::rgb(1.0, 1.0, 1.0))); let black = images.add(get_image(Color::rgb(0.0, 0.0, 0.0))); let child = commands .spawn_bundle(SpriteBundle { texture: black, transform: Transform::from_translation(Vec3::new(0.0, 0.0, -1.0)) .with_scale(Vec3::new(10.0, 1.0, 1.0)), ..Default::default() }) .id(); commands .spawn_bundle(SpriteBundle { texture: white, transform: Transform::from_rotation(Quat::from_rotation_z(0.78)) .with_translation(Vec3::new(0.0, 0.0, 10.0)), ..Default::default() }) .push_children(&[child]); } fn get_image(color: Color) -> Image { let mut bytes = Vec::with_capacity((1 * 1 * 4 * 4) as usize); let color = color.as_rgba_f32(); bytes.extend(color[0].to_le_bytes()); bytes.extend(color[1].to_le_bytes()); bytes.extend(color[2].to_le_bytes()); bytes.extend(1.0_f32.to_le_bytes()); Image::new( Extent3d { width: 1, height: 1, depth_or_array_layers: 1, }, TextureDimension::D2, bytes, TextureFormat::Rgba32Float, ) } ``` here is screenshot with `bevy main` and my fix: ![examples](https://user-images.githubusercontent.com/816292/151708304-c07c891e-da70-43f4-9c41-f85fa166a96d.png)
Thanks for the explanation. |
…ate (bevyengine#3811) # Objective Lets say we need to rotate stretched object for this purpose we can created stretched `Child` and add as child to `Parent`, later we can rotate `Parent`, `Child` in this situation should rotate keeping it form, it is not the case with `SpriteBundle` currently. If you try to do it with `SpriteBundle` it will deform. ## Solution My pull request fixes order of transformations to scale -> rotate -> translate, with this fix `SpriteBundle` behaves as expected in described rotation, without deformation. Here is quote from "Essential Mathematics for Games": > Generally, the desired order we wish to use for these transforms is to scale first, then rotate, then translate. Scaling first gives us the scaling along the axes we expect. We can then rotate around the origin of the frame, and then translate it into place. I'm must say when I was using `MaterialMesh2dBundle` it behaves correctly in both cases with `bevy main` and with my fix, don't know why, was not able to figure it out why there is difference. here is code I was using for testing: ```rust use bevy::{ prelude::*, render::render_resource::{Extent3d, TextureDimension, TextureFormat}, sprite::{MaterialMesh2dBundle, Mesh2dHandle}, }; fn main() { let mut app = App::new(); app.insert_resource(ClearColor(Color::rgb(0.1, 0.2, 0.3))) .add_plugins(DefaultPlugins) .add_startup_system(setup); app.run(); } fn setup( mut commands: Commands, mut images: ResMut<Assets<Image>>, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<ColorMaterial>>, ) { let mut c = OrthographicCameraBundle::new_2d(); c.orthographic_projection.scale = 1.0 / 10.0; commands.spawn_bundle(c); // note: mesh somehow works for both variants // let quad: Mesh2dHandle = meshes.add(Mesh::from(shape::Quad::default())).into(); // let child = commands // .spawn_bundle(MaterialMesh2dBundle { // mesh: quad.clone(), // transform: Transform::from_translation(Vec3::new(0.0, 0.0, -1.0)) // .with_scale(Vec3::new(10.0, 1.0, 1.0)), // material: materials.add(ColorMaterial::from(Color::BLACK)), // ..Default::default() // }) // .id(); // commands // .spawn_bundle(MaterialMesh2dBundle { // mesh: quad, // transform: Transform::from_rotation(Quat::from_rotation_z(0.78)) // .with_translation(Vec3::new(0.0, 0.0, 10.0)), // material: materials.add(ColorMaterial::from(Color::WHITE)), // ..Default::default() // }) // .push_children(&[child]); let white = images.add(get_image(Color::rgb(1.0, 1.0, 1.0))); let black = images.add(get_image(Color::rgb(0.0, 0.0, 0.0))); let child = commands .spawn_bundle(SpriteBundle { texture: black, transform: Transform::from_translation(Vec3::new(0.0, 0.0, -1.0)) .with_scale(Vec3::new(10.0, 1.0, 1.0)), ..Default::default() }) .id(); commands .spawn_bundle(SpriteBundle { texture: white, transform: Transform::from_rotation(Quat::from_rotation_z(0.78)) .with_translation(Vec3::new(0.0, 0.0, 10.0)), ..Default::default() }) .push_children(&[child]); } fn get_image(color: Color) -> Image { let mut bytes = Vec::with_capacity((1 * 1 * 4 * 4) as usize); let color = color.as_rgba_f32(); bytes.extend(color[0].to_le_bytes()); bytes.extend(color[1].to_le_bytes()); bytes.extend(color[2].to_le_bytes()); bytes.extend(1.0_f32.to_le_bytes()); Image::new( Extent3d { width: 1, height: 1, depth_or_array_layers: 1, }, TextureDimension::D2, bytes, TextureFormat::Rgba32Float, ) } ``` here is screenshot with `bevy main` and my fix: ![examples](https://user-images.githubusercontent.com/816292/151708304-c07c891e-da70-43f4-9c41-f85fa166a96d.png)
…ate (bevyengine#3811) # Objective Lets say we need to rotate stretched object for this purpose we can created stretched `Child` and add as child to `Parent`, later we can rotate `Parent`, `Child` in this situation should rotate keeping it form, it is not the case with `SpriteBundle` currently. If you try to do it with `SpriteBundle` it will deform. ## Solution My pull request fixes order of transformations to scale -> rotate -> translate, with this fix `SpriteBundle` behaves as expected in described rotation, without deformation. Here is quote from "Essential Mathematics for Games": > Generally, the desired order we wish to use for these transforms is to scale first, then rotate, then translate. Scaling first gives us the scaling along the axes we expect. We can then rotate around the origin of the frame, and then translate it into place. I'm must say when I was using `MaterialMesh2dBundle` it behaves correctly in both cases with `bevy main` and with my fix, don't know why, was not able to figure it out why there is difference. here is code I was using for testing: ```rust use bevy::{ prelude::*, render::render_resource::{Extent3d, TextureDimension, TextureFormat}, sprite::{MaterialMesh2dBundle, Mesh2dHandle}, }; fn main() { let mut app = App::new(); app.insert_resource(ClearColor(Color::rgb(0.1, 0.2, 0.3))) .add_plugins(DefaultPlugins) .add_startup_system(setup); app.run(); } fn setup( mut commands: Commands, mut images: ResMut<Assets<Image>>, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<ColorMaterial>>, ) { let mut c = OrthographicCameraBundle::new_2d(); c.orthographic_projection.scale = 1.0 / 10.0; commands.spawn_bundle(c); // note: mesh somehow works for both variants // let quad: Mesh2dHandle = meshes.add(Mesh::from(shape::Quad::default())).into(); // let child = commands // .spawn_bundle(MaterialMesh2dBundle { // mesh: quad.clone(), // transform: Transform::from_translation(Vec3::new(0.0, 0.0, -1.0)) // .with_scale(Vec3::new(10.0, 1.0, 1.0)), // material: materials.add(ColorMaterial::from(Color::BLACK)), // ..Default::default() // }) // .id(); // commands // .spawn_bundle(MaterialMesh2dBundle { // mesh: quad, // transform: Transform::from_rotation(Quat::from_rotation_z(0.78)) // .with_translation(Vec3::new(0.0, 0.0, 10.0)), // material: materials.add(ColorMaterial::from(Color::WHITE)), // ..Default::default() // }) // .push_children(&[child]); let white = images.add(get_image(Color::rgb(1.0, 1.0, 1.0))); let black = images.add(get_image(Color::rgb(0.0, 0.0, 0.0))); let child = commands .spawn_bundle(SpriteBundle { texture: black, transform: Transform::from_translation(Vec3::new(0.0, 0.0, -1.0)) .with_scale(Vec3::new(10.0, 1.0, 1.0)), ..Default::default() }) .id(); commands .spawn_bundle(SpriteBundle { texture: white, transform: Transform::from_rotation(Quat::from_rotation_z(0.78)) .with_translation(Vec3::new(0.0, 0.0, 10.0)), ..Default::default() }) .push_children(&[child]); } fn get_image(color: Color) -> Image { let mut bytes = Vec::with_capacity((1 * 1 * 4 * 4) as usize); let color = color.as_rgba_f32(); bytes.extend(color[0].to_le_bytes()); bytes.extend(color[1].to_le_bytes()); bytes.extend(color[2].to_le_bytes()); bytes.extend(1.0_f32.to_le_bytes()); Image::new( Extent3d { width: 1, height: 1, depth_or_array_layers: 1, }, TextureDimension::D2, bytes, TextureFormat::Rgba32Float, ) } ``` here is screenshot with `bevy main` and my fix: ![examples](https://user-images.githubusercontent.com/816292/151708304-c07c891e-da70-43f4-9c41-f85fa166a96d.png)
Objective
Lets say we need to rotate stretched object for this purpose we can created stretched
Child
and add as child toParent
, later we can rotateParent
,Child
in this situation should rotate keeping it form, it is not the case withSpriteBundle
currently. If you try to do it withSpriteBundle
it will deform.Solution
My pull request fixes order of transformations to scale -> rotate -> translate, with this fix
SpriteBundle
behaves as expected in described rotation, without deformation. Here is quote from "Essential Mathematics for Games":I'm must say when I was using
MaterialMesh2dBundle
it behaves correctly in both cases withbevy main
and with my fix, don't know why, was not able to figure it out why there is difference.here is code I was using for testing:
here is screenshot with
bevy main
and my fix: