Skip to content
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

Improved grid zoom #142

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

tim-blackbird
Copy link
Contributor

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 :(

@alice-i-cecile alice-i-cecile added the S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged label Nov 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants