use axiolid_core::{Point2, Point3};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DegeneracyRate {
None,
Rare,
Occasional,
Frequent,
}
impl DegeneracyRate {
pub const ALL: [Self; 4] = [Self::None, Self::Rare, Self::Occasional, Self::Frequent];
#[must_use]
pub const fn fraction(self) -> f64 {
match self {
Self::None => 0.0,
Self::Rare => 0.0001,
Self::Occasional => 0.01,
Self::Frequent => 0.1,
}
}
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::None => "0%",
Self::Rare => "0.01%",
Self::Occasional => "1%",
Self::Frequent => "10%",
}
}
#[must_use]
pub fn is_degenerate(self, index: usize) -> bool {
match self {
Self::None => false,
Self::Rare => index % 10_000 == 0,
Self::Occasional => index % 100 == 0,
Self::Frequent => index % 10 == 0,
}
}
}
#[derive(Debug, Clone)]
pub struct SceneRng {
state: u64,
}
impl SceneRng {
#[must_use]
pub const fn new(seed: u64) -> Self {
Self {
state: if seed == 0 {
0x2545_F491_4F6C_DD1D
} else {
seed
},
}
}
pub fn next_u64(&mut self) -> u64 {
self.state ^= self.state << 13;
self.state ^= self.state >> 7;
self.state ^= self.state << 17;
self.state
}
pub fn coordinate(&mut self, bound: i64) -> f64 {
(((self.next_u64() >> 33) as i64) % (2 * bound) - bound) as f64
}
}
pub type Orient2Case = [Point2; 3];
pub type Orient3Case = [Point3; 4];
#[must_use]
pub fn orient2_scene(count: usize, rate: DegeneracyRate, seed: u64) -> Vec<Orient2Case> {
let mut rng = SceneRng::new(seed);
(0..count)
.map(|index| {
let a = Point2::new(rng.coordinate(1_000), rng.coordinate(1_000));
let b = Point2::new(rng.coordinate(1_000), rng.coordinate(1_000));
let c = if rate.is_degenerate(index) {
let k = ((rng.next_u64() >> 40) as i64 % 7) - 3;
Point2::new(
a.x + (k as f64) * (b.x - a.x),
a.y + (k as f64) * (b.y - a.y),
)
} else {
Point2::new(rng.coordinate(1_000), rng.coordinate(1_000))
};
[a, b, c]
})
.collect()
}
#[must_use]
pub fn orient3_scene(count: usize, rate: DegeneracyRate, seed: u64) -> Vec<Orient3Case> {
let mut rng = SceneRng::new(seed);
(0..count)
.map(|index| {
let p = |r: &mut SceneRng| {
Point3::new(
r.coordinate(1_000),
r.coordinate(1_000),
r.coordinate(1_000),
)
};
let (a, b, c) = (p(&mut rng), p(&mut rng), p(&mut rng));
let d = if rate.is_degenerate(index) {
let i = (((rng.next_u64() >> 40) as i64 % 5) - 2) as f64;
let j = (((rng.next_u64() >> 40) as i64 % 5) - 2) as f64;
Point3::new(
a.x + i * (b.x - a.x) + j * (c.x - a.x),
a.y + i * (b.y - a.y) + j * (c.y - a.y),
a.z + i * (b.z - a.z) + j * (c.z - a.z),
)
} else {
p(&mut rng)
};
[a, b, c, d]
})
.collect()
}
#[must_use]
pub fn near_coplanar_scene(count: usize, seed: u64) -> Vec<Orient3Case> {
let mut rng = SceneRng::new(seed);
(0..count)
.map(|_| {
let p = |r: &mut SceneRng| {
Point3::new(
r.coordinate(1_000),
r.coordinate(1_000),
r.coordinate(1_000),
)
};
let (a, b, c) = (p(&mut rng), p(&mut rng), p(&mut rng));
let i = (((rng.next_u64() >> 40) as i64 % 5) - 2) as f64;
let j = (((rng.next_u64() >> 40) as i64 % 5) - 2) as f64;
let z = a.z + i * (b.z - a.z) + j * (c.z - a.z);
[
a,
b,
c,
Point3::new(
a.x + i * (b.x - a.x) + j * (c.x - a.x),
a.y + i * (b.y - a.y) + j * (c.y - a.y),
next_ulp(z),
),
]
})
.collect()
}
#[must_use]
fn next_ulp(value: f64) -> f64 {
if value.is_nan() || value == f64::INFINITY {
return value;
}
let bits = value.to_bits();
f64::from_bits(if value >= 0.0 { bits + 1 } else { bits - 1 })
}