129 lines
4.2 KiB
Rust
129 lines
4.2 KiB
Rust
//! Bounded screen-space broad phase. Projective world UI uses the precise
|
|
//! ray-based narrow phase rather than an unsafe screen rectangle approximation.
|
|
use crate::{document::MountGeometry, geometry::Point};
|
|
use std::collections::HashMap;
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct Index {
|
|
count: usize,
|
|
cells: HashMap<(i32, i32), Vec<usize>>,
|
|
fallback: Vec<usize>,
|
|
}
|
|
impl Index {
|
|
pub fn build(mounts: &[MountGeometry]) -> Self {
|
|
let mut result = Self {
|
|
count: mounts.len(),
|
|
..Self::default()
|
|
};
|
|
let mut entries = 0;
|
|
for (index, mount) in mounts.iter().enumerate() {
|
|
if !mount.interactive
|
|
&& !mount.canvas_viewport
|
|
&& !mount.scene3d_viewport
|
|
&& mount.scroll.is_none()
|
|
{
|
|
continue;
|
|
}
|
|
if mount.panel.is_some() {
|
|
result.fallback.push(index);
|
|
continue;
|
|
}
|
|
let a = Point::new(mount.x, mount.y);
|
|
let b = Point::new(mount.x + mount.width, mount.y + mount.height);
|
|
let a = mount.view.map_or(a, |view| view.point(a));
|
|
let b = mount.view.map_or(b, |view| view.point(b));
|
|
let (mut x0, mut y0, mut x1, mut y1) =
|
|
(a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y));
|
|
if let Some([x, y, w, h]) = mount.clip {
|
|
x0 = x0.max(x);
|
|
y0 = y0.max(y);
|
|
x1 = x1.min(x + w);
|
|
y1 = y1.min(y + h);
|
|
}
|
|
if x1 < x0 || y1 < y0 {
|
|
continue;
|
|
}
|
|
if ![x0, y0, x1, y1]
|
|
.iter()
|
|
.all(|value| value.is_finite() && value.abs() < 1e12)
|
|
{
|
|
result.fallback.push(index);
|
|
continue;
|
|
}
|
|
let (left, top, right, bottom) = (cell(x0), cell(y0), cell(x1), cell(y1));
|
|
let cells = (i64::from(right) - i64::from(left) + 1)
|
|
.saturating_mul(i64::from(bottom) - i64::from(top) + 1);
|
|
if cells > 256 || entries + cells > 65_536 {
|
|
result.fallback.push(index);
|
|
continue;
|
|
}
|
|
entries += cells;
|
|
for x in left..=right {
|
|
for y in top..=bottom {
|
|
result.cells.entry((x, y)).or_default().push(index);
|
|
}
|
|
}
|
|
}
|
|
result
|
|
}
|
|
pub fn candidates(&self, point: Point, count: usize) -> Vec<usize> {
|
|
if self.count != count {
|
|
return (0..count).rev().collect();
|
|
}
|
|
let mut output = self.fallback.clone();
|
|
if let Some(items) = self.cells.get(&(cell(point.x), cell(point.y))) {
|
|
output.extend(items);
|
|
}
|
|
output.sort_unstable_by(|a, b| b.cmp(a));
|
|
output.dedup();
|
|
output
|
|
}
|
|
}
|
|
fn cell(value: f32) -> i32 {
|
|
(value / 128.)
|
|
.floor()
|
|
.clamp(i32::MIN as f32, i32::MAX as f32) as i32
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
#[test]
|
|
fn screen_grid_limits_candidates_and_keeps_huge_rectangles_hittable() {
|
|
let (_, document) = crate::endor_host::ReplSession::load(
|
|
"page=Page()\npage.render(Button(\"hit\"))",
|
|
"app.main",
|
|
1,
|
|
930,
|
|
)
|
|
.unwrap();
|
|
let template = document
|
|
.frame
|
|
.mounts
|
|
.iter()
|
|
.find(|mount| mount.interactive)
|
|
.unwrap();
|
|
let mut mounts = Vec::new();
|
|
for index in 0..200 {
|
|
let mut mount = template.clone();
|
|
mount.x = index as f32 * 400.;
|
|
mount.y = 0.;
|
|
mount.width = 32.;
|
|
mount.height = 32.;
|
|
mount.view = None;
|
|
mount.clip = None;
|
|
mounts.push(mount);
|
|
}
|
|
let index = Index::build(&mounts);
|
|
assert_eq!(index.candidates(Point::new(5., 5.), mounts.len()), vec![0]);
|
|
mounts[199].x = -1_000_000.;
|
|
mounts[199].width = 2_000_000.;
|
|
let index = Index::build(&mounts);
|
|
assert_eq!(
|
|
index.candidates(Point::new(5., 5.), mounts.len()),
|
|
vec![199, 0]
|
|
);
|
|
assert!(mounts[199].contains(Point::new(5., 5.)));
|
|
}
|
|
}
|