Skip to content

upstream: jackdaw transform gizmo#23435

Open
jbuehler23 wants to merge 9 commits intobevyengine:mainfrom
jbuehler23:transform-gizmo
Open

upstream: jackdaw transform gizmo#23435
jbuehler23 wants to merge 9 commits intobevyengine:mainfrom
jbuehler23:transform-gizmo

Conversation

@jbuehler23
Copy link
Contributor

Objective

Upstream jackdaw's TransformGizmo!

Solution

Add TransformGizmoPlugin to bevy_gizmos as an optional plugin that draws on a focused entity and lets the user click/drag to manipulate its transform. Rotate, Translate, and Scale are all individual gizmos - perfect opportunity for someone to extend and create a combined gizmo in a follow-up :)

Testing

  • Manually tested with cargo run --example transform_gizmo --features free_camera:
    • All three modes (translate, rotate, scale) work
    • Focus switch between objects, and gizmo moves across
    • World/local space toggle and editing
  • Tested on Linux (X11)

Showcase

Mark any entity with TransformGizmoFocus to get interactive handles:

app.add_plugins(TransformGizmoPlugin);

commands.spawn((
    Mesh3d(mesh),
    MeshMaterial3d(material),
    TransformGizmoFocus,
));
 

// Switch modes via resource
mode.set(TransformGizmoMode::Rotate);

The transform_gizmo example shows input cycling and keyboard mode switching (1/2/3 for translate/rotate/scale, X to toggle world/local space)

Video

Jackdaw

gizmo_demo.mp4

Transform Example

bevy_transform_gizmo.mp4

@jbuehler23 jbuehler23 added A-Gizmos Visual editor and debug gizmos C-Feature A new feature, making something new possible labels Mar 20, 2026
Copy link

@PatrickChodowski PatrickChodowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope you don't mind review from common user :) I tried out this transform gizmo in my own project and I really like the design. I think separating the translation, rotation and scale is a very good idea.

I added some comments that I believe is useful feedback ( the Single camera especially surprised me in the beginning because in the project I have a general main camera but also another camera to get a depth texture)

Thank you for your work

@alice-i-cecile alice-i-cecile added A-Editor Graphical tools to make Bevy games S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Mar 22, 2026
@alice-i-cecile alice-i-cecile added the M-Release-Note Work that should be called out in the blog due to impact label Mar 22, 2026
@github-actions
Copy link
Contributor

It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note.

Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes.

@alice-i-cecile
Copy link
Member

I hope you don't mind review from common user

Bevy explicitly encourages this! Thanks; this is really helpful feedback.

@alice-i-cecile alice-i-cecile added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Mar 22, 2026
@jbuehler23
Copy link
Contributor Author

I hope you don't mind review from common user :) I tried out this transform gizmo in my own project and I really like the design. I think separating the translation, rotation and scale is a very good idea.

I added some comments that I believe is useful feedback ( the Single camera especially surprised me in the beginning because in the project I have a general main camera but also another camera to get a depth texture)

Thank you for your work

Much appreciated for the review, I'll go through these and address them

@jbuehler23
Copy link
Contributor Author

@PatrickChodowski I'm pretty sure I've addressed all your feedback, and all very valid! I've pushed the latest changes here if you want to take another look

Copy link

@PatrickChodowski PatrickChodowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the changes! It looks good to me. I have been using this gizmo last couple of days in my project and It feels good

@jbuehler23
Copy link
Contributor Author

I've added the release note as well, once CI is passing ready for a final review @alice-i-cecile :)

drag_state.axis = None;
drag_state.entity = None;
if config.confine_cursor {
cursor_opts.grab_mode = CursorGrabMode::None;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't quite right: we need to be resetting the cursor grab mode to the previous state, not None. Caching this in a Local is probably the way.

}

// End drag
if drag_state.active && mouse.just_released(MouseButton::Left) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not robust enough: just_released events can be missed if focus is lost.

We need to be careful that the drag state doesn't persist indefinitely.

I can actually reproduce this bug by starting to drag, then Alt-Tabbing away.

//! # use bevy_app::App;
//! # use bevy_gizmos::transform_gizmo::{TransformGizmoPlugin, TransformGizmoFocus};
//! // 1. Add the plugin
//! // app.add_plugins(TransformGizmoPlugin);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this style where we comment out lines and no_run the doc test.

IMO simply using words is going to be clearer.


/// Marker component for the camera the transform gizmo should use.
///
/// If no camera has this component, the gizmo systems will silently do nothing.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior kind of sucks. We should fallback more gracefully, using the only camera that exists, and then warning loudly if more than one are present and we need to choose.

Scale,
}

/// Whether the gizmo operates in world or local space.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Whether the gizmo operates in world or local space.
/// Whether the gizmo transforms the object using world or local space axes.

/// Which manipulation mode the gizmo is in.
#[derive(Resource, Default, PartialEq, Eq, Clone, Copy, Debug, Reflect)]
#[reflect(Resource, Default)]
pub enum TransformGizmoMode {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of resources here, which will almost always be accessed together. Could we cut this down to 2: TransformGizmoState and TransformGizmoSettings?


let raw_delta = axis_dir * projected * scale;
let snapped_delta = match config.snap_translate {
Some(inc) => snap_vec3(raw_delta, inc),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only be snapping the axis that is being modified.

))
.observe(on_click_select);

// Sphere
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a number of subtle interactions in this code WRT local vs global behavior and parenting.

I would prefer to swap the sphere and cylinder out for a compound object of some form.

let scale = cam_dist * config.translate_sensitivity;

let raw_delta = axis_dir * projected * scale;
let snapped_delta = match config.snap_translate {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? Shouldn't we be snapping the final coordinates, not the delta?

));
}

fn on_click_select(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have a quick filter for PointButton::Primary just to teach good practices.

normal: Vec3,
radius: f32,
) -> f32 {
const RING_SAMPLES: usize = 16;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const RING_SAMPLES: usize = 16;
const RING_SAMPLES: usize = 64;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to cheap out here.

// Instructions
commands.spawn((
Text::new(
"Click an object to select it\n1: Translate | 2: Rotate | 3: Scale | X: Toggle space",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should just use the standard Blender hotkey shortcuts for these modes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i believe in blender it's 'S' to scale, but that conflicts with the free camera WASD movement right?

commands.spawn((
Text::new(
"Click an object to select it\n1: Translate | 2: Rotate | 3: Scale | X: Toggle space",
),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Toggle space" is unclear. "World/Local space" or something would be better.

@aevyrie
Copy link
Member

aevyrie commented Mar 24, 2026

  1. The transform math doesn't seem to actually be projecting the user's drag distance, making the gizmo feel laggy and unresponsive - it doesn't "stick" to the user's pointer.
  2. There looks to be some frame lag, the gizmo and object update at different frames.
  3. Are you accounting for different camera projections?

This is all handled in bevy_transform_gizmo fwiw.

227469248-726b21c8-5308-49f0-9b04-e567833774e1.mp4

Not saying we should upstream that instead, it is horribly out of date, but the math has already been written, we should do it correctly.

@jbuehler23
Copy link
Contributor Author

  1. The transform math doesn't seem to actually be projecting the user's drag distance, making the gizmo feel laggy and unresponsive - it doesn't "stick" to the user's pointer.
  2. There looks to be some frame lag, the gizmo and object update at different frames.
  3. Are you accounting for different camera projections?

This is all handled in bevy_transform_gizmo fwiw.

227469248-726b21c8-5308-49f0-9b04-e567833774e1.mp4
Not saying we should upstream that instead, it is horribly out of date, but the math has already been written, we should do it correctly.

oh sick, thank you i'll have a look at the math here

@chompaa
Copy link
Member

chompaa commented Mar 24, 2026

Adding to Aevyrie's comments about prior art - I mentioned this on Discord as well but also mentioning on here for visibility. I think it's also worth checking out transform-gizmo. Its bevy integration is only slightly out of date (0.17). It is agnostic w.r.t. rendering and transforms, and the UX feels great (I think it's just like Blender's?) Being transform agnostic is pretty important for a gizmo like this IMO.

@alice-i-cecile alice-i-cecile added the X-Contentious There are nontrivial implications that should be thought through label Mar 25, 2026
@8th-Boundary
Copy link

https://github.com/8th-Boundary/bevy_transform_tools
Can take a look here as well for another reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Editor Graphical tools to make Bevy games A-Gizmos Visual editor and debug gizmos C-Feature A new feature, making something new possible M-Release-Note Work that should be called out in the blog due to impact S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged X-Contentious There are nontrivial implications that should be thought through

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants