Skip to content

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
@jbuehler23
Copy link
Contributor

@tim-blackbird Appreciate this is an older PR, just wanted to check if you were still looking at this or should we try and pick up?

@alice-i-cecile alice-i-cecile added the A-Camera Looking at the world label Jun 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Camera Looking at the world 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.

3 participants