Skip to main content

geng_utils/
pixel.rs

1use geng::prelude::*;
2
3use crate::conversions::Vec2RealConversions;
4
5/// Align a world position with a framebuffer pixel.
6pub fn pixel_perfect_pos(
7    pos: vec2<f32>,
8    camera: &impl geng::AbstractCamera2d,
9    framebuffer_size: vec2<f32>,
10) -> vec2<f32> {
11    // Transform to screen space
12    let pos = camera_world_to_screen(pos, camera, framebuffer_size);
13    let pos = pos.map(f32::floor);
14    // Transform back to world
15    camera.screen_to_world(framebuffer_size, pos)
16}
17
18/// Align a rectangle of the given pixel size with the framebuffer's pixels.
19pub fn pixel_perfect_aabb(
20    pos: vec2<f32>,
21    align: vec2<f32>,
22    size: vec2<usize>,
23    camera: &impl geng::AbstractCamera2d,
24    framebuffer_size: vec2<f32>,
25) -> Aabb2<f32> {
26    // Transform to screen space
27    let pos = camera_world_to_screen(pos, camera, framebuffer_size);
28    let align_size = (size.as_f32() * align).map(f32::fract);
29    let pos = pos.map(f32::floor) + align_size;
30    let screen_aabb = Aabb2::point(pos).extend_symmetric(size.as_f32() / 2.0);
31    // Transform back to world
32    screen_aabb.map_bounds(|pos| camera.screen_to_world(framebuffer_size, pos))
33}
34
35fn camera_world_to_screen(
36    pos: vec2<f32>,
37    camera: &impl geng::AbstractCamera2d,
38    framebuffer_size: vec2<f32>,
39) -> vec2<f32> {
40    let pos = (camera.projection_matrix(framebuffer_size) * camera.view_matrix()) * pos.extend(1.0);
41    let pos = pos.xy() / pos.z;
42    // if pos.x.abs() > 1.0 || pos.y.abs() > 1.0 {
43    //     return None;
44    // }
45    vec2(
46        (pos.x + 1.0) / 2.0 * framebuffer_size.x,
47        (pos.y + 1.0) / 2.0 * framebuffer_size.y,
48    )
49}