#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TessellationQuality {
Lowest,
Low,
#[default]
Medium,
High,
Highest,
}
impl TessellationQuality {
#[inline]
pub fn density_factor(self) -> f64 {
match self {
Self::Lowest => 0.25,
Self::Low => 0.5,
Self::Medium => 1.0,
Self::High => 2.0,
Self::Highest => 4.0,
}
}
#[inline]
pub fn profile_arc_segments(self, base: usize, min: usize) -> usize {
let n = match self {
Self::Lowest => (base as f64 * 0.25).round() as usize,
Self::Low => (base as f64 * 0.5).round() as usize,
Self::Medium | Self::High | Self::Highest => base,
};
n.max(min)
}
#[inline]
pub fn circle_profile_segments(self, base: usize) -> usize {
match self {
Self::Lowest => base.min(8),
Self::Low => base.min(16),
Self::Medium | Self::High | Self::Highest => base,
}
}
}
#[inline]
pub fn scale_segments(base: usize, min: usize, max: usize, q: TessellationQuality) -> usize {
if q == TessellationQuality::Medium {
return base.clamp(min, max);
}
let f = q.density_factor();
let scaled = (base as f64 * f).round() as usize;
let lo = ((min as f64 * f).round() as usize).max(1);
let hi = (max as f64 * f).round() as usize;
scaled.clamp(lo, hi.max(lo))
}
#[cfg(test)]
mod tests {
use super::*;
const LEVELS: [TessellationQuality; 5] = [
TessellationQuality::Lowest,
TessellationQuality::Low,
TessellationQuality::Medium,
TessellationQuality::High,
TessellationQuality::Highest,
];
#[test]
fn default_is_medium() {
assert_eq!(TessellationQuality::default(), TessellationQuality::Medium);
}
#[test]
fn medium_factor_is_one() {
assert_eq!(TessellationQuality::Medium.density_factor(), 1.0);
}
#[test]
fn medium_is_identity_clamp() {
let cases = [
(26usize, 8usize, 32usize), (4, 8, 32), (200, 8, 32), (24, 24, 24), (36, 36, 36), (12, 2, 128), ];
for (base, min, max) in cases {
assert_eq!(
scale_segments(base, min, max, TessellationQuality::Medium),
base.clamp(min, max),
"Medium must be identity for ({base},{min},{max})"
);
}
}
#[test]
fn monotonic_non_decreasing_across_levels() {
for (base, min, max) in [(26usize, 8usize, 64usize), (24, 8, 128), (36, 8, 144)] {
let counts: Vec<usize> = LEVELS
.iter()
.map(|&q| scale_segments(base, min, max, q))
.collect();
for w in counts.windows(2) {
assert!(
w[0] <= w[1],
"not monotonic for base={base}: {counts:?}"
);
}
assert!(
counts.first() < counts.last(),
"expected strict increase across range for base={base}: {counts:?}"
);
}
}
#[test]
fn circle_profile_segments_coarsen_below_medium_cap_above() {
use TessellationQuality::*;
assert_eq!(Lowest.circle_profile_segments(36), 8);
assert_eq!(Low.circle_profile_segments(36), 16);
for q in [Medium, High, Highest] {
assert_eq!(q.circle_profile_segments(36), 36, "{q:?} must keep base");
}
assert_eq!(Lowest.circle_profile_segments(6), 6);
assert_eq!(Low.circle_profile_segments(12), 12);
assert_eq!(Medium.circle_profile_segments(6), 6);
}
#[test]
fn profile_arc_segments_coarsen_below_medium_cap_above() {
use TessellationQuality::*;
assert_eq!(Lowest.profile_arc_segments(24, 2), 6);
assert_eq!(Low.profile_arc_segments(24, 2), 12);
for q in [Medium, High, Highest] {
assert_eq!(q.profile_arc_segments(24, 2), 24, "{q:?} keeps base");
}
assert_eq!(Lowest.profile_arc_segments(6, 2), 2);
}
#[test]
fn never_below_one() {
assert!(scale_segments(2, 2, 8, TessellationQuality::Lowest) >= 1);
}
}