use crate::scene::Point;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeepSize {
False,
True,
UnlessLimitsDeclared,
}
impl KeepSize {
pub fn parse(s: &str) -> KeepSize {
match s.trim() {
"false" => KeepSize::False,
"unless limits declared" => KeepSize::UnlessLimitsDeclared,
_ => KeepSize::True,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Strategy {
Auto,
UnitsOnly,
ChangeVertical,
ChangeHorizontal,
}
impl Strategy {
pub fn parse(s: &str) -> Strategy {
match s.trim() {
"units only" => Strategy::UnitsOnly,
"change vertical limits" => Strategy::ChangeVertical,
"change horizontal limits" => Strategy::ChangeHorizontal,
_ => Strategy::Auto,
}
}
}
pub struct Input<'a> {
pub e: [Point; 3],
pub width: f64,
pub height: f64,
pub unit_box: bool,
pub three_d: bool,
pub strategy: Strategy,
pub ratio: &'a str,
pub ratio_axis: &'a str,
pub keep: KeepSize,
pub auto_min: [bool; 3],
pub auto_max: [bool; 3],
}
pub fn uniform(inp: &Input, min: &mut [f64; 3], max: &mut [f64; 3]) -> [Point; 3] {
let zero = inp.e.map(|v| v.x == 0.0 && v.y == 0.0);
let range = [0, 1, 2].map(|a| max[a] - min[a]);
let e = [0, 1, 2].map(|a| if inp.unit_box { inp.e[a] } else { inp.e[a].scale(range[a]) });
let mut inv = if inp.unit_box { range } else { [1.0; 3] };
let mut limit = [1.0f64; 3];
let (w, h) = box_size(&e);
let ext = (0..3).filter(|&a| !zero[a]).map(|a| inv[a]).fold(-16300.0, f64::max);
for a in 0..3 {
if !zero[a] && inp.strategy != Strategy::UnitsOnly {
limit[a] = ext / inv[a];
}
inv[a] = ext;
}
let (s, r) = uniform_scale(inp, &e, w, h, limit);
for (l, r) in limit.iter_mut().zip(r) {
*l *= r;
}
unit_ratio(inp, &zero, &mut inv, &mut limit);
minimize_limits(&zero, &mut inv, &mut limit);
let ev = [0, 1, 2].map(|a| inp.e[a].scale(s / inv[a]));
for (a, &l) in limit.iter().enumerate() {
rescale_limits(inp, a, l, min, max);
}
ev
}
fn box_size(e: &[Point; 3]) -> (f64, f64) {
let (mut x0, mut y0, mut x1, mut y1) = (f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
for i in 0..8 {
let mut p = Point::default();
for (k, v) in e.iter().enumerate() {
if (i >> k) & 1 == 1 {
p = p.plus(*v);
}
}
x0 = x0.min(p.x);
y0 = y0.min(p.y);
x1 = x1.max(p.x);
y1 = y1.max(p.y);
}
(x1 - x0, y1 - y0)
}
fn uniform_scale(inp: &Input, e: &[Point; 3], w: f64, h: f64, limit: [f64; 3]) -> (f64, [f64; 3]) {
let try_one = |s: Strategy| -> (f64, [f64; 3]) {
let units_only = || {
let s = if inp.width < inp.height { inp.width / w } else { inp.height / h };
(s, [1.0; 3])
};
let unit_height = |a: usize| e[a].y.abs();
match s {
Strategy::Auto | Strategy::UnitsOnly => units_only(),
Strategy::ChangeVertical => {
let s = inp.width / w;
let a = if e[2].y == 0.0 {
if e[1].x == 0.0 {
1
} else {
return units_only();
}
} else if e[2].x == 0.0 {
2
} else {
return units_only();
};
let m = unit_height(a);
let mut r = [1.0; 3];
r[a] = (inp.height - s * (h - m)) / (s * m);
(s, r)
}
Strategy::ChangeHorizontal => {
if e[2].y == 0.0 {
if e[1].x == 0.0 && e[0].y == 0.0 {
let s = inp.height / h;
(s, [inp.width / w / s, 1.0, 1.0])
} else {
units_only()
}
} else if e[2].x == 0.0 {
let m = unit_height(2);
let ww = inp.width / w;
let (mut s, mut r) = (1.0, 1.0);
for _ in 0..9 {
s = inp.height / (r * (h - m) + m);
r = ww / s;
}
(s, [r, r, 1.0])
} else {
units_only()
}
}
}
};
if inp.strategy != Strategy::Auto {
return try_one(inp.strategy);
}
let cost = |r: &[f64; 3]| -> f64 {
let total = [0, 1, 2].map(|a| limit[a] * r[a]);
if total.iter().any(|&t| t < 1.0) {
f64::INFINITY
} else {
total.iter().sum()
}
};
let horizontal = try_one(Strategy::ChangeHorizontal);
let vertical = try_one(Strategy::ChangeVertical);
let (ch, cv) = (cost(&horizontal.1), cost(&vertical.1));
let best = if cv < ch { (vertical, cv) } else { (horizontal, ch) };
if best.1.is_infinite() {
try_one(Strategy::UnitsOnly)
} else {
best.0
}
}
fn unit_ratio(inp: &Input, zero: &[bool; 3], inv: &mut [f64; 3], limit: &mut [f64; 3]) {
if inp.ratio.trim().is_empty() {
return;
}
let mut r: Vec<f64> = inp.ratio.split_whitespace().filter_map(|x| x.parse().ok()).collect();
r.extend([1.0, 1.0, 1.0]);
let given = [r[0], r[1], r[2]];
if given[0] == given[1] && (given[0] == given[2] || !inp.three_d) {
return;
}
let relative = |reference: usize| -> [f64; 3] {
let mut k = 1.0 / given[reference];
if reference != 2 {
k *= given[2];
}
given.map(|g| g * k)
};
let scale = |a: usize, reference: usize, q: f64, inv: &[f64; 3]| -> f64 {
if zero[a] || zero[reference] {
16001.0
} else {
inv[a] / inv[reference] * q
}
};
let reference = match inp.ratio_axis.trim() {
"x" => 0,
"y" => 1,
"z" => 2,
_ => {
let one = 1.002;
let mut best: Option<(usize, f64)> = None;
for reference in 0..3 {
let q = relative(reference);
let others: Vec<usize> = (0..3).filter(|&a| a != reference).collect();
let sa = scale(others[0], reference, q[others[0]], inv);
let sb = scale(others[1], reference, q[others[1]], inv);
if one - sa >= 0.0 && one - sb >= 0.0 {
let m = (one - sa).max(one - sb);
if best.is_none_or(|(_, b)| m < b) {
best = Some((reference, m));
}
}
}
best.map(|b| b.0).unwrap_or(if zero[2] { 1 } else { 2 })
}
};
let q = relative(reference);
let before = *inv;
for a in 0..3 {
if zero[a] || (a == reference && (q[a] - 1.0).abs() <= 0.0002) {
continue;
}
let s = scale(a, reference, q[a], &before);
inv[a] /= s;
limit[a] /= s;
}
}
fn minimize_limits(zero: &[bool; 3], inv: &mut [f64; 3], limit: &mut [f64; 3]) {
if (0..3).any(|a| !zero[a] && limit[a] < 1.002) {
return;
}
let smallest = (0..3).filter(|&a| !zero[a]).map(|a| limit[a]).fold(f64::INFINITY, f64::min);
if !smallest.is_finite() {
return;
}
for a in 0..3 {
if !zero[a] || a < 2 {
inv[a] /= smallest;
limit[a] /= smallest;
}
}
}
fn rescale_limits(inp: &Input, a: usize, factor: f64, min: &mut [f64; 3], max: &mut [f64; 3]) {
if factor == 1.0 || inp.keep == KeepSize::False {
return;
}
let d = (factor - 1.0) * (max[a] - min[a]);
let (lo, hi) = match (inp.auto_min[a], inp.auto_max[a]) {
(true, true) => (d / 2.0, d / 2.0),
(true, false) => (d, 0.0),
(false, true) => (0.0, d),
(false, false) => {
if inp.keep == KeepSize::True {
(d / 2.0, d / 2.0)
} else {
(0.0, 0.0)
}
}
};
min[a] -= lo;
max[a] += hi;
}