use core::{primitive::{u32, usize, f64}, result::Result, array::from_fn, marker::PhantomData, ops::{Add, AddAssign, Sub, Mul, Div}};
use alloc::{vec::Vec, boxed::Box, rc::Rc};
use crate::RectgridError;
pub struct Value<Tag>(f64, PhantomData<Tag>);
impl<Tag> Clone for Value<Tag> {
fn clone(&self) -> Self {
*self
}
}
impl<Tag> Copy for Value<Tag> {}
impl<Tag> Value<Tag> {
pub fn new(v: f64) -> Self {
Self(v, PhantomData)
}
pub fn get(self) -> f64 {
self.0
}
}
impl<Tag> From<f64> for Value<Tag> {
fn from(v: f64) -> Self {
Self::new(v)
}
}
impl<Tag> Add for Value<Tag> {
type Output = Value<Tag>;
fn add(self, rhs: Self) -> Self::Output {
Value::new(self.0 + rhs.0)
}
}
impl<Tag> AddAssign for Value<Tag> {
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
}
impl<Tag> Sub for Value<Tag> {
type Output = Value<Tag>;
fn sub(self, rhs: Self) -> Self::Output {
Value::new(self.0 - rhs.0)
}
}
impl<Tag> Mul<f64> for Value<Tag> {
type Output = Value<Tag>;
fn mul(self, rhs: f64) -> Self::Output {
Value::new(self.0 * rhs)
}
}
impl<Tag> Div for Value<Tag> {
type Output = f64;
fn div(self, rhs: Self) -> f64 {
self.0 / rhs.0
}
}
pub struct PxTag;
pub struct UnitTag;
pub struct ParameterTag;
pub type Px = Value<PxTag>;
pub type Unit = Value<UnitTag>;
pub type Parameter = Value<ParameterTag>;
pub type Point<const D: usize> = [Unit; D];
#[derive(Clone, Copy)]
pub struct BBox<const D: usize> {
pub base: Point<D>,
pub offset: Point<D>,
}
impl<const D: usize> BBox<D> {
pub fn snap_floor(&mut self, extend: Option<[Unit; D]>) -> &mut Self {
for (d, u) in self.base.iter_mut().enumerate() {
let v = if let Some(ext) = extend { *u + ext[d] } else { *u };
*u = Unit::new(libm::floor(v.get()));
}
for u in self.offset.iter_mut() {
*u = Unit::new(libm::floor(u.get()));
}
self
}
pub fn has_size(&self) -> bool {
self.offset.iter().all(|u| u.get() != 0.0)
}
}
pub enum IncrementFunction {
ForwardDifference(Rc<dyn Fn(u32) -> Result<Px, RectgridError>>),
VectorList(Vec<Px>),
Scale(f64),
}
impl IncrementFunction {
pub fn accumulate(&self) -> Result<Box<dyn Fn(f64) -> Result<Px, RectgridError>>, RectgridError> {
match self {
Self::ForwardDifference(f) => {
let f = f.clone();
Ok(Box::new(move |x| {
let n = libm::floor(x) as u32;
let frac = x - n as f64;
let mut acc = Px::new(0.0);
for k in 0..n {
acc += f(k)?;
}
if frac != 0.0 {
acc += f(n)? * frac;
}
Ok(acc)
}))
}
Self::VectorList(pxs) => {
if pxs.is_empty() {
return Err(RectgridError::InvalidDefinition);
}
let pxs = pxs.clone();
Ok(Box::new(move |x| {
let last = (pxs.len() - 1) as u32;
let n = libm::floor(x) as usize;
let frac = x - n as f64;
let lo = *pxs.get(n).ok_or(RectgridError::OutOfIndex(last))?;
if frac == 0.0 {
return Ok(lo);
}
let hi = *pxs.get(n + 1).ok_or(RectgridError::OutOfIndex(last))?;
Ok(lo + (hi - lo) * frac)
}))
}
Self::Scale(s) => {
let s = *s;
Ok(Box::new(move |x| Ok(Px::new(s * x))))
}
}
}
}
pub struct RectGrid<const D: usize> {
pub origin: [Px; D],
accumulator: [Box<dyn Fn(f64) -> Result<Px, RectgridError>>; D],
}
impl<const D: usize> RectGrid<D> {
pub fn new(origin: [Px; D], definitions: [IncrementFunction; D]) -> Result<Self, RectgridError> {
let accumulator: Vec<_> = definitions.into_iter()
.map(|d| d.accumulate())
.collect::<Result<_, _>>()?;
let accumulator = match accumulator.try_into() {
Ok(a) => a,
Err(_) => unreachable!("definitions and accumulator share length D"),
};
Ok(Self { origin, accumulator })
}
pub fn set_definition(&mut self, definition: IncrementFunction, d: usize) -> Result<(), RectgridError> {
self.accumulator[d] = definition.accumulate()?;
Ok(())
}
pub fn point_to_unit(&self, point: [Px; D]) -> [Result<Unit, RectgridError>; D] {
from_fn(|i| self.px_to_unit_axis(i, point[i] - self.origin[i]))
}
fn px_to_unit_axis(&self, i: usize, target: Px) -> Result<Unit, RectgridError> {
let target = target.get();
let f = |x: f64| self.accumulator[i](x);
let mut lo = 0.0;
let mut hi = 1.0;
loop {
match f(hi) {
Ok(px) if px.get() >= target => break,
Ok(_) => {
lo = hi;
hi *= 2.0;
}
Err(RectgridError::OutOfIndex(last)) => {
hi = last as f64;
if f(hi)?.get() < target {
return Err(RectgridError::OutOfIndex(last));
}
break;
}
Err(e) => return Err(e),
}
}
const EPSILON: f64 = 1e-9;
for _ in 0..64 {
if hi - lo < EPSILON {
break;
}
let mid = (lo + hi) / 2.0;
if f(mid)?.get() < target {
lo = mid;
} else {
hi = mid;
}
}
Ok(Unit::new((lo + hi) / 2.0))
}
pub fn unit_to_px(&self, d: usize, unit: &Unit) -> Result<Px, RectgridError> {
self.accumulator[d](unit.get())
}
pub fn point_as_px(&self, points: &Vec<Point<D>>) -> Vec<Result<[Px; D], RectgridError>> {
points.iter().map(|pt| -> Result<[Px; D], RectgridError> {
let mut px = [Px::new(0.0); D];
for d in 0..D {
px[d] = self.unit_to_px(d, &pt[d])?;
}
Ok(px)
}).collect()
}
fn contains(&self, point: [Px; D], bx: &BBox<D>, extend: Option<([Unit; D], [Unit; D])>) -> (bool, [Px; D], [Px; D]) {
let local: [Px; D] = from_fn(|d| point[d] - self.origin[d]);
let base_px: [Px; D] = from_fn(|d| self.unit_to_px(d, &bx.base[d]).unwrap_or(Px::new(0.0)));
let offset_px: [Px; D] = from_fn(|d| self.unit_to_px(d, &(bx.base[d] + bx.offset[d])).unwrap_or(Px::new(0.0)));
let (lo, hi): ([Px; D], [Px; D]) = if let Some((eb, eo)) = extend {
(from_fn(|d| self.unit_to_px(d, &(bx.base[d] + eb[d])).unwrap_or(Px::new(0.0))),
from_fn(|d| self.unit_to_px(d, &(bx.base[d] + bx.offset[d] + eo[d])).unwrap_or(Px::new(0.0))))
} else {
(base_px, offset_px)
};
let hit = (0..D).all(|d| local[d].get() >= lo[d].get() && local[d].get() <= hi[d].get());
(hit, base_px, offset_px)
}
fn parameter_from_px(point: [Px; D], base_px: [Px; D], offset_px: [Px; D]) -> [Parameter; D] {
from_fn(|d| {
let width = offset_px[d] - base_px[d];
if width.get() == 0.0 { Parameter::new(0.0) } else { Parameter::new((point[d] - base_px[d]) / width) }
})
}
pub fn hit_test(&self, point: [Px; D], boxes: &Vec<BBox<D>>, extend: Option<([Unit; D], [Unit; D])>) -> Option<usize> {
boxes.iter()
.enumerate()
.rev()
.find_map(|(i, bx)| self.contains(point, bx, extend).0.then_some(i))
}
pub fn hit_test_with_parameter(&self, point: [Px; D], boxes: &Vec<BBox<D>>, extend: Option<([Unit; D], [Unit; D])>) -> Option<(usize, [Parameter; D])> {
let local: [Px; D] = from_fn(|d| point[d] - self.origin[d]);
boxes.iter()
.enumerate()
.rev()
.find_map(|(i, bx)| {
let (hit, base_px, offset_px) = self.contains(point, bx, extend);
hit.then(|| (i, Self::parameter_from_px(local, base_px, offset_px)))
})
}
pub fn hit_tests(&self, point: [Px; D], boxes: &Vec<BBox<D>>, extend: Option<([Unit; D], [Unit; D])>) -> Vec<bool> {
boxes.iter()
.map(|bx| self.contains(point, bx, extend).0)
.collect()
}
pub fn get_parameter(&self, point: [Px; D], bx: BBox<D>) -> [Parameter; D] {
let local: [Px; D] = from_fn(|d| point[d] - self.origin[d]);
let base_px = from_fn(|d| self.unit_to_px(d, &bx.base[d]).unwrap_or(Px::new(0.0)));
let offset_px = from_fn(|d| self.unit_to_px(d, &(bx.base[d] + bx.offset[d])).unwrap_or(Px::new(1.0)));
Self::parameter_from_px(local, base_px, offset_px)
}
pub fn box_as_px(&self, boxes: &Vec<BBox<D>>) -> Vec<Result<([Px; D], [Px; D]), RectgridError>> {
boxes.iter().map(|bx| -> Result<([Px; D], [Px; D]), RectgridError> {
let mut base_px = [Px::new(0.0); D];
let mut offset_px = [Px::new(0.0); D];
for d in 0..D {
base_px[d] = self.unit_to_px(d, &bx.base[d])?;
offset_px[d] = self.unit_to_px(d, &(bx.base[d] + bx.offset[d]))? - base_px[d];
}
Ok((base_px, offset_px))
}).collect()
}
pub fn offset(&self, pointer: [Px; D], z: [Px; D]) -> [Px; D] {
from_fn(|d| (pointer[d] - self.origin[d]) - z[d])
}
}
pub fn corner_test<const D: usize>(
grid: &RectGrid<D>,
point: [Px; D],
bx: &BBox<D>,
threshold: f64,
) -> (Option<[Parameter; D]>, Option<[Option<bool>; D]>) {
if !bx.has_size() { return (None, None); }
let parameter = grid.get_parameter(point, *bx);
let inside = parameter.iter().all(|r| r.get() >= 0.0 && r.get() <= 1.0);
if !inside { return (None, None); }
let corner: [Option<bool>; D] = from_fn(|d| {
let r = parameter[d].get();
if r <= threshold { Some(true) }
else if r >= 1.0 - threshold { Some(false) }
else { None }
});
let corner = if corner.iter().any(Option::is_some) { Some(corner) } else { None };
(Some(parameter), corner)
}
pub fn drag_resize<const D: usize>(
grid: &RectGrid<D>,
pointer: [Px; D],
bx: &BBox<D>,
corner: [Option<bool>; D],
) -> Result<BBox<D>, RectgridError> {
let unit = grid.point_to_unit(pointer);
let mut resized = *bx;
for d in 0..D {
let Some(base_side) = corner[d] else { continue };
let new_u = Unit::new(libm::floor(unit[d]?.get()));
let base_u = bx.base[d];
let offset_u = bx.offset[d];
if base_side {
let new_offset = ((base_u + offset_u) - new_u).get().max(1.0);
resized.base[d] = new_u;
resized.offset[d] = Unit::new(new_offset);
} else {
let new_offset = (new_u - base_u).get().max(1.0);
resized.offset[d] = Unit::new(new_offset);
}
}
Ok(resized)
}
pub fn drag_translate<const D: usize>(
grid: &RectGrid<D>,
pointer: [Px; D],
drag_offset: [Px; D],
) -> [Px; D] {
grid.offset(pointer, drag_offset)
}
pub fn snap_region_to_unit<const D: usize>(
grid: &RectGrid<D>,
pointer: [Px; D],
drag_offset: [Px; D],
bx: &BBox<D>,
extend: Option<[Unit; D]>,
) -> Result<BBox<D>, RectgridError> {
let mut snapped = *bx;
for d in 0..D {
let local = pointer[d] - grid.origin[d] - drag_offset[d];
snapped.base[d] = grid.px_to_unit_axis(d, local)?;
}
snapped.snap_floor(extend);
Ok(snapped)
}
pub fn snap_point_to_unit<const D: usize>(
grid: &RectGrid<D>,
pointer: [Px; D],
drag_offset: [Px; D],
snap: [Unit; D],
) -> Result<BBox<D>, RectgridError> {
let mut base: Point<D> = [Unit::new(0.0); D];
for d in 0..D {
let local = pointer[d] - grid.origin[d] - drag_offset[d];
let u = grid.px_to_unit_axis(d, local)?;
base[d] = Unit::new(libm::floor((u + snap[d]).get()));
}
Ok(BBox { base, offset: from_fn(|_| Unit::new(0.0)) })
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::rc::Rc;
#[test]
fn point_to_unit_scale_roundtrip() {
let grid = RectGrid::<2>::new(
[Px::new(0.0), Px::new(0.0)],
[IncrementFunction::Scale(200.0), IncrementFunction::Scale(64.0)],
).unwrap();
let result = grid.point_to_unit([Px::new(450.0), Px::new(10.0)]);
let x = result[0].as_ref().unwrap().get();
let y = result[1].as_ref().unwrap().get();
assert!((x - 2.25).abs() < 1e-6, "x = {}", x);
assert!((y - 0.15625).abs() < 1e-6, "y = {}", y);
}
#[test]
fn point_to_unit_vector_list_roundtrip() {
let grid = RectGrid::<1>::new(
[Px::new(0.0)],
[IncrementFunction::VectorList(alloc::vec![Px::new(0.0), Px::new(10.0), Px::new(30.0), Px::new(60.0)])],
).unwrap();
let result = grid.point_to_unit([Px::new(45.0)]);
let x = result[0].as_ref().unwrap().get();
assert!((x - 2.5).abs() < 1e-6, "x = {}", x);
}
#[test]
fn point_to_unit_vector_list_out_of_range() {
let grid = RectGrid::<1>::new(
[Px::new(0.0)],
[IncrementFunction::VectorList(alloc::vec![Px::new(0.0), Px::new(10.0), Px::new(30.0), Px::new(60.0)])],
).unwrap();
let result = grid.point_to_unit([Px::new(100.0)]);
assert!(matches!(result[0], Err(RectgridError::OutOfIndex(3))));
}
#[test]
fn point_to_unit_applies_origin_offset() {
let grid = RectGrid::<2>::new(
[Px::new(10.0), Px::new(20.0)],
[IncrementFunction::Scale(200.0), IncrementFunction::Scale(64.0)],
).unwrap();
let result = grid.point_to_unit([Px::new(230.0), Px::new(50.0)]);
let x = result[0].as_ref().unwrap().get();
let y = result[1].as_ref().unwrap().get();
assert!((x - 1.1).abs() < 1e-6, "x = {}", x);
assert!((y - 0.46875).abs() < 1e-6, "y = {}", y);
}
#[test]
fn accumulate_forward_difference() {
let f = IncrementFunction::ForwardDifference(Rc::new(|i| Ok(Px::new((i + 1) as f64 * 10.0))));
let acc = f.accumulate().unwrap();
assert_eq!(acc(0.0).unwrap().get(), 0.0);
assert!((acc(2.5).unwrap().get() - 45.0).abs() < 1e-9);
}
#[test]
fn snap_floor_no_extend() {
let mut bx = BBox { base: [Unit::new(2.7)], offset: [Unit::new(1.9)] };
bx.snap_floor(None);
assert_eq!(bx.base[0].get(), 2.0);
assert_eq!(bx.offset[0].get(), 1.0);
}
#[test]
fn box_as_px_out_of_index() {
let grid = RectGrid::<1>::new(
[Px::new(0.0)],
[IncrementFunction::VectorList(alloc::vec![Px::new(0.0), Px::new(10.0)])],
).unwrap();
let boxes = alloc::vec![BBox { base: [Unit::new(0.0)], offset: [Unit::new(5.0)] }];
assert!(matches!(grid.box_as_px(&boxes)[0], Err(RectgridError::OutOfIndex(1))));
}
#[test]
fn hit_test_with_parameter_no_hit() {
let grid = RectGrid::<2>::new(
[Px::new(0.0), Px::new(0.0)],
[IncrementFunction::Scale(200.0), IncrementFunction::Scale(64.0)],
).unwrap();
let boxes = alloc::vec![
BBox { base: [Unit::new(0.0), Unit::new(0.0)], offset: [Unit::new(1.0), Unit::new(1.0)] },
];
assert!(grid.hit_test_with_parameter([Px::new(500.0), Px::new(10.0)], &boxes, None).is_none());
}
#[test]
fn hit_tests_partial_and_no_hit() {
let grid = RectGrid::<2>::new(
[Px::new(0.0), Px::new(0.0)],
[IncrementFunction::Scale(200.0), IncrementFunction::Scale(64.0)],
).unwrap();
let boxes = alloc::vec![
BBox { base: [Unit::new(0.0), Unit::new(0.0)], offset: [Unit::new(1.0), Unit::new(1.0)] },
BBox { base: [Unit::new(2.0), Unit::new(0.0)], offset: [Unit::new(1.0), Unit::new(1.0)] },
];
assert_eq!(grid.hit_tests([Px::new(50.0), Px::new(10.0)], &boxes, None), alloc::vec![true, false]);
assert_eq!(grid.hit_tests([Px::new(-1.0), Px::new(-1.0)], &boxes, None), alloc::vec![false, false]);
}
#[test]
fn drag_resize_offset_side() {
let grid = RectGrid::<2>::new(
[Px::new(0.0), Px::new(0.0)],
[IncrementFunction::Scale(200.0), IncrementFunction::Scale(64.0)],
).unwrap();
let bx = BBox {
base: [Unit::new(2.0), Unit::new(0.0)],
offset: [Unit::new(1.0), Unit::new(3.0)],
};
let resized = drag_resize(&grid, [Px::new(900.0), Px::new(0.0)], &bx, [Some(false), None]).unwrap();
assert_eq!(resized.base[0].get(), 2.0);
assert_eq!(resized.offset[0].get(), 2.0);
}
#[test]
fn drag_resize_minimum_clamp() {
let grid = RectGrid::<2>::new(
[Px::new(0.0), Px::new(0.0)],
[IncrementFunction::Scale(200.0), IncrementFunction::Scale(64.0)],
).unwrap();
let bx = BBox {
base: [Unit::new(2.0), Unit::new(0.0)],
offset: [Unit::new(2.0), Unit::new(3.0)],
};
let resized = drag_resize(&grid, [Px::new(1100.0), Px::new(0.0)], &bx, [Some(true), None]).unwrap();
assert_eq!(resized.base[0].get(), 5.0);
assert_eq!(resized.offset[0].get(), 1.0);
}
}