use crate::types::{Point, Rect};
pub(crate) const PITCH: f64 = 2048.0;
pub(crate) const PAD: f64 = 40.0;
#[derive(Clone, PartialEq, Debug)]
pub(crate) struct Tile {
pub cell: (i64, i64),
pub rect: Rect,
pub members: Vec<usize>,
pub raised: bool,
}
impl Tile {
pub fn origin(&self) -> Point {
self.rect.origin()
}
}
pub(crate) fn tiles(items: impl IntoIterator<Item = (Rect, bool)>) -> Vec<Tile> {
let mut tiles: Vec<Tile> = Vec::new();
let mut index: Vec<((i64, i64), usize)> = Vec::new();
for (i, (rect, raised)) in items.into_iter().enumerate() {
let cell = if rect.x.is_finite() && rect.y.is_finite() {
(
(rect.x / PITCH).floor() as i64,
(rect.y / PITCH).floor() as i64,
)
} else {
(i64::MIN, i as i64)
};
match index.iter().find(|(c, _)| *c == cell) {
Some((_, at)) => {
let tile = &mut tiles[*at];
tile.rect = tile.rect.union(&rect);
tile.members.push(i);
tile.raised |= raised;
}
None => {
index.push((cell, tiles.len()));
tiles.push(Tile {
cell,
rect,
members: vec![i],
raised,
});
}
}
}
for tile in &mut tiles {
tile.rect = Rect::new(
tile.rect.x - PAD,
tile.rect.y - PAD,
tile.rect.width + 2.0 * PAD,
tile.rect.height + 2.0 * PAD,
);
}
tiles
}
#[cfg(test)]
mod tests {
use super::*;
fn rect(x: f64, y: f64) -> Rect {
Rect::new(x, y, 130.0, 46.0)
}
fn plain(rects: &[Rect]) -> Vec<Tile> {
tiles(rects.iter().map(|r| (*r, false)))
}
#[test]
fn nothing_tiles_to_nothing() {
assert!(plain(&[]).is_empty());
}
#[test]
fn a_small_graph_is_one_tile() {
let tiles = plain(&[rect(0.0, 0.0), rect(300.0, 100.0), rect(80.0, 500.0)]);
assert_eq!(tiles.len(), 1);
assert_eq!(tiles[0].members, vec![0, 1, 2]);
}
#[test]
fn every_tile_contains_its_members() {
let rects: Vec<Rect> = (0..200)
.map(|i| rect((i % 20) as f64 * 190.0, (i / 20) as f64 * 110.0))
.collect();
for tile in plain(&rects) {
for &i in &tile.members {
let member = rects[i];
assert!(
tile.rect.x <= member.x
&& tile.rect.y <= member.y
&& tile.rect.max_x() >= member.max_x()
&& tile.rect.max_y() >= member.max_y(),
"tile {:?} does not contain member {member:?}",
tile.rect,
);
}
}
}
#[test]
fn a_tile_leaves_room_for_ink_outside_the_box() {
let tiles = plain(&[rect(500.0, 500.0)]);
assert_eq!(tiles.len(), 1);
let member = rect(500.0, 500.0);
assert!(member.x - tiles[0].rect.x >= PAD);
assert!(member.y - tiles[0].rect.y >= PAD);
assert!(tiles[0].rect.max_x() - member.max_x() >= PAD);
assert!(tiles[0].rect.max_y() - member.max_y() >= PAD);
}
#[test]
fn every_item_is_placed_once() {
let rects: Vec<Rect> = (0..500)
.map(|i| rect((i % 25) as f64 * 400.0, (i / 25) as f64 * 400.0))
.collect();
let mut seen: Vec<usize> = plain(&rects).into_iter().flat_map(|t| t.members).collect();
seen.sort_unstable();
assert_eq!(seen, (0..rects.len()).collect::<Vec<_>>());
}
#[test]
fn a_long_item_widens_its_own_tile() {
let long = Rect::new(0.0, 0.0, PITCH * 6.0, 10.0);
let tiles = plain(&[long]);
assert_eq!(tiles.len(), 1);
assert!(tiles[0].rect.width >= long.width);
}
#[test]
fn members_keep_their_original_order() {
let rects = [rect(0.0, 0.0), rect(9000.0, 0.0), rect(100.0, 0.0)];
let tiles = plain(&rects);
let first = tiles.iter().find(|t| t.cell == (0, 0)).unwrap();
assert_eq!(first.members, vec![0, 2]);
}
#[test]
fn a_raised_member_raises_its_tile() {
let tiles = tiles([
(rect(0.0, 0.0), false),
(rect(100.0, 0.0), true),
(rect(9000.0, 0.0), false),
]);
let raised: Vec<bool> = tiles.iter().map(|t| t.raised).collect();
assert_eq!(raised, vec![true, false]);
}
#[test]
fn a_nonsense_position_is_quarantined() {
let tiles = plain(&[
rect(0.0, 0.0),
rect(f64::NAN, 0.0),
rect(100.0, 0.0),
rect(f64::INFINITY, 0.0),
]);
let good = tiles.iter().find(|t| t.cell == (0, 0)).unwrap();
assert_eq!(good.members, vec![0, 2]);
assert!(good.rect.x.is_finite() && good.rect.width.is_finite());
assert_eq!(tiles.len(), 3);
}
#[test]
fn cells_key_tiles_independently_of_list_order() {
let a = plain(&[rect(0.0, 0.0), rect(9000.0, 9000.0)]);
let b = plain(&[rect(9000.0, 9000.0), rect(0.0, 0.0)]);
let mut cells_a: Vec<_> = a.iter().map(|t| t.cell).collect();
let mut cells_b: Vec<_> = b.iter().map(|t| t.cell).collect();
cells_a.sort_unstable();
cells_b.sort_unstable();
assert_eq!(cells_a, cells_b);
}
}