bevy_editor_prototypes
bevy_editor_prototypes copied to clipboard
Improved grid zoom
trafficstars
Work in progress
Original implementation provided by our friends at Foresight (@aevyrie)
/// Update the grid to match the [`GridSettings`] and the current camera angle.
pub fn update_grid(
// TODO use fse specific marker
camera_query: Query<
(&GlobalTransform, Ref<EditorCam>, &EditorCam, &Camera),
Without<InfiniteGrid>,
>,
mut grid_query: Query<(&GlobalTransform, &mut InfiniteGridSettings), With<InfiniteGrid>>,
grid_colors: Res<GridSettings>,
) {
for (camera_transform, camera_change_tracker, editor_cam, cam) in &camera_query {
if !camera_change_tracker.is_changed() && !grid_colors.is_changed() {
continue;
}
let Ok((grid_transform, mut grid_params)) = grid_query.get_single_mut() else {
continue;
};
let z_distance = (camera_transform.translation().z - grid_transform.translation().z)
.abs()
.max(editor_cam.last_anchor_depth as f32);
// To scale the grid, we need to know how far the camera is from the grid plane. The naive
// solution is to simply use the distance, however this breaks down during dolly zooms or
// when using an orthographic projection.
//
// Instead, we want a solution that is related to the size of objects on screen. If an
// object on screen is the same size during a dolly zoom switch from perspective to ortho,
// we would expect that the grid scale should also not change.
//
// First, we raycast against the plane:
let world_to_screen = cam.get_world_to_screen(camera_transform);
let ray = Ray3d {
origin: camera_transform.translation(),
direction: Direction3d::new_unchecked(camera_transform.forward()),
};
let hit = ray
.intersect_plane(
grid_transform.translation(),
Plane3d::new(grid_transform.up()),
)
.unwrap_or_default();
let hit_world = ray.origin + ray.direction.normalize() * hit;
// Then we offset that hit one world-space unit in the direction of the camera's right.
let hit_world_offset = hit_world + camera_transform.right();
// Now we project these two positions into screen space, and determine the distance between
// them when projected on the screen:
let hit_screen = world_to_screen(hit_world).unwrap_or_default();
let hit_screen_offset = world_to_screen(hit_world_offset).unwrap_or_default();
let size = (hit_screen_offset - hit_screen).length();
// Finally, we use the relationship that the scale of an object is inversely proportional to
// the distance from the camera. We can now do the reverse - compute a distance based on the
// size on the screen. If we are very far from the plane, the two points will be very close
// on the screen, if we are very close to the plane, the two objects will be very far apart
// on the screen. This will work for any camera projection regardless of the camera's
// translational distance.
let screen_distance_unchecked = (1_000.0 / size as f64).abs() as f32;
let screen_distance =
if !screen_distance_unchecked.is_finite() || screen_distance_unchecked == 0.0 {
z_distance
} else {
// The distance blows up when the camera is very close, this looks much nicer
screen_distance_unchecked.min(z_distance)
};
// We need to add `1` to screen_distance because the logarithm is negative when x < 1;
let log_scale = (screen_distance + 1.0).log10();
if grid_params.x_axis_color.a() != 0. {
let GridSettings {
lightness,
alpha,
fadeout_multiplier,
edge_on_fadeout_strength,
} = grid_colors.to_owned();
// lerp minor grid line alpha based on scale
let minor_alpha = (1.0 - log_scale.fract()) * alpha;
grid_params.minor_line_color =
Color::rgba(lightness, lightness, lightness, minor_alpha);
grid_params.major_line_color = Color::rgba(lightness, lightness, lightness, alpha);
grid_params.fadeout_distance = fadeout_multiplier * z_distance;
grid_params.x_axis_color = Color::rgba(1.0, 0.0, 0.0, 1.0);
grid_params.z_axis_color = Color::rgba(0.0, 1.0, 0.0, 1.0);
grid_params.dot_fadeout_strength = edge_on_fadeout_strength;
grid_params.scale = 10f32.powi(1i32.saturating_sub(log_scale.floor() as i32));
}
}
}
I'm doing something wrong calculating the view_space_distance. It should smoothly become less as the camera moves further away, instead it seems to be changing randomly :(