use crate::float::compatible::FloatPointCompatible;
use crate::float::number::FloatNumber;
use crate::float::point::FloatPoint;
use crate::float::rect::FloatRect;
use crate::int::number::int::IntNumber;
use crate::int::number::wide_int::WideIntNumber;
use crate::int::point::IntPoint;
use core::marker::PhantomData;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FloatPointAdapterScaleError {
ScaleTooLarge,
ScaleNonPositive,
ScaleNotFinite,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FloatPointAdapterRangeError {
PointOutOfRange,
}
#[derive(Clone)]
pub struct FloatPointAdapter<P: FloatPointCompatible, I: IntNumber> {
dir_scale: P::Scalar,
inv_scale: P::Scalar,
offset: P,
rect: FloatRect<P::Scalar>,
int: PhantomData<I>,
}
impl<P: FloatPointCompatible, I: IntNumber> FloatPointAdapter<P, I> {
const SCALE_SAFETY_BITS: i32 = 3;
#[inline]
pub fn new(rect: FloatRect<P::Scalar>) -> Self {
let a = rect.width() * P::Scalar::HALF;
let b = rect.height() * P::Scalar::HALF;
let x = rect.min_x + a;
let y = rect.min_y + b;
let offset = P::from_xy(x, y);
let max = a.max(b);
if max == P::Scalar::ZERO {
return Self {
dir_scale: P::Scalar::ONE,
inv_scale: P::Scalar::ONE,
offset,
rect,
int: PhantomData,
};
}
let log2 = max.log2().to_i32();
let safe_bits = I::BITS as i32 - Self::SCALE_SAFETY_BITS;
let ie = safe_bits - log2;
let e = ie as f64;
let dir_scale = FloatNumber::from_float(libm::exp2(e));
let inv_scale = FloatNumber::from_float(libm::exp2(-e));
Self {
dir_scale,
inv_scale,
offset,
rect,
int: PhantomData,
}
}
#[inline]
pub fn with_coordinate_bits(rect: FloatRect<P::Scalar>, coordinate_bits: u32) -> Self {
assert!(coordinate_bits <= I::BITS - 2);
let a = rect.width() * P::Scalar::HALF;
let b = rect.height() * P::Scalar::HALF;
let x = rect.min_x + a;
let y = rect.min_y + b;
let offset = P::from_xy(x, y);
let max = a.max(b);
if max == P::Scalar::ZERO {
return Self {
dir_scale: P::Scalar::ONE,
inv_scale: P::Scalar::ONE,
offset,
rect,
int: PhantomData,
};
}
let log2 = max.log2().to_i32();
let log2_scale = P::Scalar::from_float(libm::exp2(log2 as f64));
let ceil_log2 = if log2_scale < max { log2 + 1 } else { log2 };
let exponent = coordinate_bits as i32 - ceil_log2;
let e = exponent as f64;
let dir_scale = P::Scalar::from_float(libm::exp2(e));
let inv_scale = P::Scalar::from_float(libm::exp2(-e));
Self {
dir_scale,
inv_scale,
offset,
rect,
int: PhantomData,
}
}
#[inline]
pub fn with_scale(rect: FloatRect<P::Scalar>, scale: P::Scalar) -> Self {
let a = rect.width() * P::Scalar::HALF;
let b = rect.height() * P::Scalar::HALF;
let x = rect.min_x + a;
let y = rect.min_y + b;
let offset = P::from_xy(x, y);
let max = a.max(b);
if max == P::Scalar::ZERO {
return Self {
dir_scale: P::Scalar::ONE,
inv_scale: P::Scalar::ONE,
offset,
rect,
int: PhantomData,
};
}
let dir_scale = scale;
let inv_scale = P::Scalar::ONE / scale;
Self {
dir_scale,
inv_scale,
offset,
rect,
int: PhantomData,
}
}
#[inline]
pub fn try_with_scale(
rect: FloatRect<P::Scalar>,
scale: P::Scalar,
) -> Result<Self, FloatPointAdapterScaleError> {
let scale = Self::validate_scale(scale)?;
let mut adapter = Self::new(rect);
let is_degenerate =
adapter.rect.width() == P::Scalar::ZERO && adapter.rect.height() == P::Scalar::ZERO;
if !is_degenerate && adapter.dir_scale < scale {
return Err(FloatPointAdapterScaleError::ScaleTooLarge);
}
adapter.dir_scale = scale;
adapter.inv_scale = P::Scalar::ONE / scale;
Ok(adapter)
}
#[inline]
pub fn try_with_scale_and_coordinate_bits(
rect: FloatRect<P::Scalar>,
scale: P::Scalar,
coordinate_bits: u32,
) -> Result<Self, FloatPointAdapterScaleError> {
let scale = Self::validate_scale(scale)?;
let mut adapter = Self::with_coordinate_bits(rect, coordinate_bits);
let zero = P::Scalar::ZERO;
let is_degenerate = adapter.rect.width() == zero && adapter.rect.height() == zero;
if !is_degenerate && adapter.dir_scale < scale {
return Err(FloatPointAdapterScaleError::ScaleTooLarge);
}
adapter.dir_scale = scale;
adapter.inv_scale = P::Scalar::ONE / scale;
Ok(adapter)
}
#[inline]
pub fn with_radius_and_scale(radius: P::Scalar, scale: P::Scalar) -> FloatPointAdapter<P, I> {
let rect = FloatRect::new(-radius, radius, -radius, radius);
FloatPointAdapter::with_scale(rect, scale)
}
#[inline]
pub fn with_iter<'a, Q>(iter: Q) -> Self
where
Q: Iterator<Item = &'a P>,
P: 'a,
{
Self::new(FloatRect::with_iter(iter).unwrap_or(FloatRect::zero()))
}
#[inline]
pub fn with_iter_and_scale_checked<'a, Q>(
iter: Q,
scale: P::Scalar,
) -> Result<Self, FloatPointAdapterScaleError>
where
Q: Iterator<Item = &'a P>,
P: 'a,
{
Self::try_with_scale(FloatRect::with_iter(iter).unwrap_or(FloatRect::zero()), scale)
}
#[inline]
fn validate_scale(scale: P::Scalar) -> Result<P::Scalar, FloatPointAdapterScaleError> {
if !scale.is_finite() {
return Err(FloatPointAdapterScaleError::ScaleNotFinite);
}
if scale <= P::Scalar::ZERO {
return Err(FloatPointAdapterScaleError::ScaleNonPositive);
}
Ok(scale)
}
#[inline(always)]
pub fn dir_scale(&self) -> P::Scalar {
self.dir_scale
}
#[inline(always)]
pub fn inv_scale(&self) -> P::Scalar {
self.inv_scale
}
#[inline(always)]
pub fn offset(&self) -> P {
self.offset
}
#[inline(always)]
pub fn rect(&self) -> &FloatRect<P::Scalar> {
&self.rect
}
#[inline(always)]
pub fn int_to_float(&self, point: &IntPoint<I>) -> P {
let fx: P::Scalar = FloatNumber::from_int(point.x);
let fy: P::Scalar = FloatNumber::from_int(point.y);
let x = fx * self.inv_scale + self.offset.x();
let y = fy * self.inv_scale + self.offset.y();
let float = P::from_xy(x, y);
if cfg!(debug_assertions) {
let radius = self.rect.height().max(self.rect.width()) * P::Scalar::from_float(0.01);
if !self.rect.contains_with_radius(&float, radius) {
panic!(
"You are trying to convert a point[{}, {}] which is out of rect: {}",
x, y, self.rect
);
}
}
float
}
#[inline(always)]
pub fn try_int_to_float(&self, point: &IntPoint<I>) -> Result<P, FloatPointAdapterRangeError> {
let fx: P::Scalar = FloatNumber::from_int(point.x);
let fy: P::Scalar = FloatNumber::from_int(point.y);
let x = fx * self.inv_scale + self.offset.x();
let y = fy * self.inv_scale + self.offset.y();
let float = P::from_xy(x, y);
if self.rect.contains(&float) {
Ok(float)
} else {
Err(FloatPointAdapterRangeError::PointOutOfRange)
}
}
#[inline(always)]
pub fn float_to_int(&self, point: &P) -> IntPoint<I> {
if cfg!(debug_assertions) {
let radius = self.rect.height().max(self.rect.width()) * P::Scalar::from_float(0.01);
if !self.rect.contains_with_radius(point, radius) {
panic!(
"You are trying to convert a point[{}, {}] which is out of rect: {}",
point.x(),
point.y(),
self.rect
);
}
}
let sx = (point.x() - self.offset.x()) * self.dir_scale;
let sy = (point.y() - self.offset.y()) * self.dir_scale;
let x = I::from_rounded_float(sx);
let y = I::from_rounded_float(sy);
IntPoint { x, y }
}
#[inline(always)]
pub fn try_float_to_int(&self, point: &P) -> Result<IntPoint<I>, FloatPointAdapterRangeError> {
if !point.is_finite() || !self.rect.contains(point) {
return Err(FloatPointAdapterRangeError::PointOutOfRange);
}
let sx = (point.x() - self.offset.x()) * self.dir_scale;
let sy = (point.y() - self.offset.y()) * self.dir_scale;
let x = I::from_rounded_float(sx);
let y = I::from_rounded_float(sy);
Ok(IntPoint { x, y })
}
#[inline(always)]
pub fn snap_to_grid(&self, point: &P) -> P {
self.int_to_float(&self.float_to_int(point))
}
#[inline(always)]
pub fn try_snap_to_grid(&self, point: &P) -> Result<P, FloatPointAdapterRangeError> {
self.try_float_to_int(point)
.map(|point| self.int_to_float(&point))
}
#[inline(always)]
pub fn round_sqr_len_to_int(&self, value: P::Scalar) -> I::Wide {
let scale = self.dir_scale;
let sqr_scale = scale * scale;
I::Wide::from_rounded_float(sqr_scale * value)
}
#[inline(always)]
pub fn round_len_to_int(&self, value: P::Scalar) -> I {
I::from_rounded_float(self.dir_scale * value)
}
#[inline(always)]
pub fn len_to_float(&self, value: I) -> P::Scalar {
let f: P::Scalar = FloatNumber::from_int(value);
f * self.inv_scale
}
#[inline(always)]
pub fn to_float_point_adapter(&self) -> FloatPointAdapter<FloatPoint<P::Scalar>, I> {
FloatPointAdapter {
dir_scale: self.dir_scale,
inv_scale: self.inv_scale,
offset: FloatPoint::from_point(self.offset),
rect: self.rect,
int: self.int,
}
}
}
#[cfg(test)]
mod tests {
use crate::adapter::{FloatPointAdapter, FloatPointAdapterRangeError, FloatPointAdapterScaleError};
use crate::float::compatible::FloatPointCompatible;
use crate::float::point::FloatPoint;
use crate::float::rect::FloatRect;
use crate::int::point::IntPoint;
#[test]
fn test_0() {
let rect = FloatRect {
min_x: 1.0,
max_x: 1.0,
min_y: -2.0,
max_y: -2.0,
};
let adapter = FloatPointAdapter::<FloatPoint<f64>, i32>::new(rect);
assert_eq!(adapter.dir_scale(), 1.0);
assert_eq!(adapter.inv_scale(), 1.0);
}
#[test]
fn test_1() {
let rect = FloatRect {
min_x: 0.0,
max_x: 10.0,
min_y: 0.0,
max_y: 100.0,
};
let adapter = FloatPointAdapter::<[f64; 2], i32>::new(rect);
let f0 = [10.0, 2.0];
let p0 = adapter.float_to_int(&f0);
let f1: [f64; 2] = adapter.int_to_float(&p0);
assert_eq!((f0.x() - f1.x()).abs() < 0.000_0001, true);
assert_eq!((f0.y() - f1.y()).abs() < 0.000_0001, true);
}
#[test]
fn test_2() {
let points = [[-2.0, -4.0], [-2.0, 3.0], [5.0, 3.0], [5.0, -4.0]];
let adapter = FloatPointAdapter::<[f64; 2], i32>::with_iter(points.iter());
let f0 = [1.0, 2.0];
let p0 = adapter.float_to_int(&f0);
let f1: [f64; 2] = adapter.int_to_float(&p0);
assert_eq!((f0.x() - f1.x()).abs() < 0.000_0001, true);
assert_eq!((f0.y() - f1.y()).abs() < 0.000_0001, true);
}
#[test]
fn test_i64_scale() {
let rect = FloatRect {
min_x: -1000.0,
max_x: 1000.0,
min_y: -1000.0,
max_y: 1000.0,
};
let adapter = FloatPointAdapter::<[f64; 2], i64>::new(rect);
let p = adapter.float_to_int(&[1.25, -2.5]);
let f: [f64; 2] = adapter.int_to_float(&p);
assert_eq!(p.x.abs() > i32::MAX as i64, true);
assert_eq!((f.x() - 1.25).abs() < 0.000_0001, true);
assert_eq!((f.y() + 2.5).abs() < 0.000_0001, true);
}
#[test]
fn coordinate_bits_bound_integer_magnitude() {
let adapter = FloatPointAdapter::<[f64; 2], i32>::with_coordinate_bits(
FloatRect::new(-3.0, 3.0, -0.25, 0.25),
10,
);
assert_eq!(adapter.dir_scale(), 256.0);
assert_eq!(adapter.float_to_int(&[-3.0, -0.25]), IntPoint::new(-768, -64));
assert_eq!(adapter.float_to_int(&[3.0, 0.25]), IntPoint::new(768, 64));
}
#[test]
fn coordinate_bits_include_exact_power_of_two_boundary() {
let adapter = FloatPointAdapter::<[f64; 2], i32>::with_coordinate_bits(
FloatRect::new(-4.0, 4.0, -0.25, 0.25),
10,
);
assert_eq!(adapter.dir_scale(), 256.0);
assert_eq!(adapter.float_to_int(&[-4.0, 0.0]), IntPoint::new(-1024, 0));
assert_eq!(adapter.float_to_int(&[4.0, 0.0]), IntPoint::new(1024, 0));
}
#[test]
fn test_round_point_round_length() {
let adapter =
FloatPointAdapter::<[f64; 2], i32>::with_scale(FloatRect::new(-1.0, 1.0, -1.0, 1.0), 10.0);
assert_eq!(adapter.float_to_int(&[0.16, -0.16]), IntPoint::new(2, -2));
assert_eq!(adapter.round_len_to_int(0.16), 2);
}
#[test]
fn test_snap_to_grid() {
let adapter =
FloatPointAdapter::<[f64; 2], i32>::with_scale(FloatRect::new(-1.0, 1.0, -1.0, 1.0), 10.0);
assert_eq!(adapter.snap_to_grid(&[0.16, -0.16]), [0.2, -0.2]);
assert_eq!(adapter.snap_to_grid(&[0.14, -0.14]), [0.1, -0.1]);
}
#[test]
fn test_try_snap_to_grid_range() {
let adapter =
FloatPointAdapter::<[f64; 2], i32>::with_scale(FloatRect::new(-1.0, 1.0, -1.0, 1.0), 10.0);
assert_eq!(adapter.try_snap_to_grid(&[1.0, -1.0]).unwrap(), [1.0, -1.0]);
assert_eq!(
adapter.try_snap_to_grid(&[1.01, 0.0]).err().unwrap(),
FloatPointAdapterRangeError::PointOutOfRange
);
}
#[test]
fn test_try_with_scale() {
let adapter =
FloatPointAdapter::<[f64; 2], i32>::try_with_scale(FloatRect::new(-1.0, 1.0, -1.0, 1.0), 10.0)
.unwrap();
assert_eq!(adapter.dir_scale(), 10.0);
assert_eq!(adapter.inv_scale(), 0.1);
assert_eq!(adapter.float_to_int(&[0.16, -0.16]), IntPoint::new(2, -2));
}
#[test]
fn test_try_with_scale_and_coordinate_bits() {
let rect = FloatRect::new(-4.0, 4.0, -1.0, 1.0);
let adapter =
FloatPointAdapter::<[f64; 2], i32>::try_with_scale_and_coordinate_bits(rect.clone(), 128.0, 10)
.unwrap();
assert_eq!(adapter.float_to_int(&[4.0, 1.0]), IntPoint::new(512, 128));
assert_eq!(
FloatPointAdapter::<[f64; 2], i32>::try_with_scale_and_coordinate_bits(rect, 512.0, 10)
.err()
.unwrap(),
FloatPointAdapterScaleError::ScaleTooLarge
);
}
#[test]
fn test_try_with_scale_errors() {
let rect = FloatRect::new(-1.0, 1.0, -1.0, 1.0);
assert_eq!(
FloatPointAdapter::<[f64; 2], i32>::try_with_scale(rect.clone(), 0.0)
.err()
.unwrap(),
FloatPointAdapterScaleError::ScaleNonPositive
);
assert_eq!(
FloatPointAdapter::<[f64; 2], i32>::try_with_scale(rect.clone(), f64::INFINITY)
.err()
.unwrap(),
FloatPointAdapterScaleError::ScaleNotFinite
);
assert_eq!(
FloatPointAdapter::<[f64; 2], i32>::try_with_scale(rect, i32::MAX as f64)
.err()
.unwrap(),
FloatPointAdapterScaleError::ScaleTooLarge
);
}
#[test]
fn test_with_iter_and_scale_checked() {
let points = [[-2.0, -4.0], [-2.0, 3.0], [5.0, 3.0], [5.0, -4.0]];
let adapter =
FloatPointAdapter::<[f64; 2], i32>::with_iter_and_scale_checked(points.iter(), 100.0).unwrap();
assert_eq!(adapter.dir_scale(), 100.0);
assert_eq!(adapter.float_to_int(&[1.25, 2.25]), IntPoint::new(-25, 275));
}
#[test]
fn test_degenerate_try_with_scale_keeps_requested_scale() {
let adapter =
FloatPointAdapter::<[f64; 2], i32>::try_with_scale(FloatRect::new(1.0, 1.0, -2.0, -2.0), 1000.0)
.unwrap();
assert_eq!(adapter.dir_scale(), 1000.0);
assert_eq!(adapter.float_to_int(&[1.0, -2.0]), IntPoint::ZERO);
}
#[test]
fn test_try_float_to_int_range() {
let adapter =
FloatPointAdapter::<[f64; 2], i32>::with_scale(FloatRect::new(-1.0, 1.0, -1.0, 1.0), 10.0);
assert_eq!(
adapter.try_float_to_int(&[1.0, -1.0]).unwrap(),
IntPoint::new(10, -10)
);
assert_eq!(
adapter.try_float_to_int(&[1.01, 0.0]).err().unwrap(),
FloatPointAdapterRangeError::PointOutOfRange
);
}
#[test]
fn test_try_float_to_int_not_finite() {
let adapter =
FloatPointAdapter::<[f64; 2], i32>::with_scale(FloatRect::new(-1.0, 1.0, -1.0, 1.0), 10.0);
assert_eq!(
adapter.try_float_to_int(&[f64::NAN, 0.0]).err().unwrap(),
FloatPointAdapterRangeError::PointOutOfRange
);
assert_eq!(
adapter.try_float_to_int(&[0.0, f64::INFINITY]).err().unwrap(),
FloatPointAdapterRangeError::PointOutOfRange
);
}
#[test]
fn test_try_int_to_float_range() {
let adapter =
FloatPointAdapter::<[f64; 2], i32>::with_scale(FloatRect::new(-1.0, 1.0, -1.0, 1.0), 10.0);
assert_eq!(
adapter.try_int_to_float(&IntPoint::new(10, -10)).unwrap(),
[1.0, -1.0]
);
assert_eq!(
adapter.try_int_to_float(&IntPoint::new(11, 0)).err().unwrap(),
FloatPointAdapterRangeError::PointOutOfRange
);
}
}