use crate::descriptor::{DESCRIPTOR_LEN, GroupDescriptor};
use crate::error::ParseError;
use crate::header::{HEADER_LEN, Header};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Face {
Front,
Back,
Left,
Right,
Down,
Up,
}
impl Face {
pub const ALL: [Face; 6] = [Face::Front, Face::Back, Face::Left, Face::Right, Face::Down, Face::Up];
pub const fn index(self) -> usize {
match self {
Face::Front => 0,
Face::Back => 1,
Face::Left => 2,
Face::Right => 3,
Face::Down => 4,
Face::Up => 5,
}
}
pub const fn from_index(i: usize) -> Option<Face> {
match i {
0 => Some(Face::Front),
1 => Some(Face::Back),
2 => Some(Face::Left),
3 => Some(Face::Right),
4 => Some(Face::Down),
5 => Some(Face::Up),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TileLoc {
pub group: u8,
pub level: u8,
pub face: Face,
pub row: u32,
pub col: u32,
}
impl TileLoc {
pub const fn new(group: u8, level: u8, face: Face, row: u32, col: u32) -> TileLoc {
TileLoc {
group,
level,
face,
row,
col,
}
}
}
const MAX_TILES: u64 = 64 * 1024 * 1024;
fn dim_at(root: u32, shift: u32) -> u32 {
if shift >= 32 { 1 } else { root.div_ceil(1u32 << shift) }
}
#[derive(Debug, Clone)]
pub struct Layout {
header: Header,
groups: Vec<GroupDescriptor>,
group_base: Vec<u64>,
level_base: Vec<Vec<u64>>,
total: u64,
}
impl Layout {
pub fn new(header: Header, groups: Vec<GroupDescriptor>) -> Result<Layout, ParseError> {
if groups.len() != header.group_count as usize {
return Err(ParseError::Inconsistent("group_count does not match descriptor count"));
}
for g in &groups {
if g.level_count < 1 || g.level_count > header.levels {
return Err(ParseError::Inconsistent("level_count out of 1..=levels"));
}
if g.level_skip > header.levels - g.level_count {
return Err(ParseError::Inconsistent("level_skip + level_count exceeds levels"));
}
}
let mut group_base = Vec::with_capacity(groups.len());
let mut level_base = Vec::with_capacity(groups.len());
let mut running: u64 = 0;
for g in &groups {
group_base.push(running);
let hi = header.levels - g.level_skip;
let lo = hi - g.level_count;
let mut bases = Vec::with_capacity(g.level_count as usize);
let mut group_running: u64 = 0;
for level in lo..hi {
bases.push(group_running);
let count = level_tile_count(&header, g, level);
group_running = group_running
.checked_add(count)
.ok_or(ParseError::Inconsistent("tile count overflow"))?;
if group_running > MAX_TILES {
return Err(ParseError::Inconsistent("implied tile count exceeds the sanity cap"));
}
}
level_base.push(bases);
running = running
.checked_add(group_running)
.ok_or(ParseError::Inconsistent("tile count overflow"))?;
if running > MAX_TILES {
return Err(ParseError::Inconsistent("implied tile count exceeds the sanity cap"));
}
}
Ok(Layout {
header,
groups,
group_base,
level_base,
total: running,
})
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn groups(&self) -> &[GroupDescriptor] {
&self.groups
}
pub fn total_tiles(&self) -> u64 {
self.total
}
pub fn front_matter_len(&self) -> u64 {
HEADER_LEN as u64 + (DESCRIPTOR_LEN as u64) * self.header.group_count as u64 + 8 * (self.total + 1)
}
pub fn level_dims(&self, level: u8) -> (u32, u32) {
let shift = (self.header.levels - 1 - level) as u32;
(dim_at(self.header.root_w, shift), dim_at(self.header.root_h, shift))
}
pub fn grid(&self, level: u8) -> (u32, u32) {
let (w, h) = self.level_dims(level);
(w.div_ceil(self.header.tile_size as u32), h.div_ceil(self.header.tile_size as u32))
}
pub fn group_grid(&self, g: usize, level: u8) -> (u32, u32) {
if self.groups[g].flags.untiled() { (1, 1) } else { self.grid(level) }
}
pub fn tile_dims(&self, g: usize, level: u8, col: u32, row: u32) -> (u32, u32) {
let (lw, lh) = self.level_dims(level);
if self.groups[g].flags.untiled() {
return (lw, lh);
}
let ts = self.header.tile_size as u32;
let tw = ts.min(lw.saturating_sub(col * ts));
let th = ts.min(lh.saturating_sub(row * ts));
(tw.max(1), th.max(1))
}
pub fn group_levels(&self, g: usize) -> std::ops::Range<u8> {
let hi = self.header.levels - self.groups[g].level_skip;
(hi - self.groups[g].level_count)..hi
}
pub fn face_count(&self) -> u32 {
self.header.face_count as u32
}
pub fn group_run(&self, g: usize) -> std::ops::Range<usize> {
let start = self.group_base[g] as usize;
let end = if g + 1 < self.group_base.len() {
self.group_base[g + 1] as usize
} else {
self.total as usize
};
start..end
}
pub fn level_run(&self, g: usize, level: u8) -> Option<std::ops::Range<usize>> {
let levels = self.group_levels(g);
if level < levels.start || level >= levels.end {
return None;
}
let lo = levels.start;
let base = self.group_base[g] + self.level_base[g][(level - lo) as usize];
let count = level_tile_count(&self.header, &self.groups[g], level);
Some(base as usize..(base + count) as usize)
}
pub fn tile_ordinal(&self, loc: TileLoc) -> Option<usize> {
let g = loc.group as usize;
if g >= self.groups.len() {
return None;
}
let levels = self.group_levels(g);
if loc.level < levels.start || loc.level >= levels.end {
return None;
}
let face_i = loc.face.index() as u32;
if face_i >= self.face_count() {
return None;
}
let (cols, rows) = self.group_grid(g, loc.level);
if loc.col >= cols || loc.row >= rows {
return None;
}
let lo = levels.start;
let base = self.group_base[g] + self.level_base[g][(loc.level - lo) as usize];
let per_face = (cols as u64) * (rows as u64);
let within = (face_i as u64) * per_face + (loc.row as u64) * (cols as u64) + loc.col as u64;
Some((base + within) as usize)
}
pub fn ordinal_loc(&self, ordinal: usize) -> Option<TileLoc> {
let ord = ordinal as u64;
if ord >= self.total {
return None;
}
let g = match self.group_base.binary_search(&ord) {
Ok(i) => i,
Err(i) => i - 1,
};
let mut rem = ord - self.group_base[g];
let levels = self.group_levels(g);
for level in levels {
let count = level_tile_count(&self.header, &self.groups[g], level);
if rem < count {
let (cols, rows) = self.group_grid(g, level);
let per_face = (cols as u64) * (rows as u64);
let face_i = (rem / per_face) as usize;
let in_face = rem % per_face;
let row = (in_face / cols as u64) as u32;
let col = (in_face % cols as u64) as u32;
return Some(TileLoc {
group: g as u8,
level,
face: Face::from_index(face_i)?,
row,
col,
});
}
rem -= count;
}
None
}
}
fn level_tile_count(header: &Header, g: &GroupDescriptor, level: u8) -> u64 {
let (cols, rows) = if g.flags.untiled() {
(1u32, 1u32)
} else {
let shift = (header.levels - 1 - level) as u32;
let lw = dim_at(header.root_w, shift);
let lh = dim_at(header.root_h, shift);
(lw.div_ceil(header.tile_size as u32), lh.div_ceil(header.tile_size as u32))
};
(header.face_count as u64) * (cols as u64) * (rows as u64)
}