use super::{
MeetConstraint, MeetTierInput,
blocks::{Block, classify_blocks},
};
const GIRDLE_REFERENCE_MAST: f64 = 1.0;
fn girdle_radius_for_anchor(tiers: &[MeetTierInput], blocks: &[Block]) -> f64 {
let mut radii: Vec<f64> = (0..tiers.len())
.filter(|&i| blocks[i] == Block::Girdle)
.filter_map(|i| match tiers[i].constraint {
MeetConstraint::ScaleReference(v) => Some(v.abs()),
_ => None,
})
.collect();
radii.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
radii.first().copied().unwrap_or(GIRDLE_REFERENCE_MAST)
}
fn geometric_ratio_anchor(theta_deg: f64, ratio: f64, girdle_r: f64, is_flat: bool) -> f64 {
let theta = theta_deg.abs().to_radians();
if is_flat {
ratio * 2.0 * girdle_r
} else {
theta.sin() * girdle_r
}
}
pub(super) fn explicit_table_or_culet(
tiers: &[MeetTierInput],
blocks: &[Block],
want: Block,
) -> Option<usize> {
(0..tiers.len())
.find(|&i| blocks[i] == want && tiers[i].angle_deg == 0.0 && tiers[i].indices.len() <= 1)
}
pub fn apply_ratio_anchors(
tiers: &mut [MeetTierInput],
cw_ratio: Option<f64>,
pw_ratio: Option<f64>,
) -> (bool, bool) {
let blocks = classify_blocks(tiers);
let is_anchored = |t: &MeetTierInput| matches!(t.constraint, MeetConstraint::ScaleReference(_));
let girdle_idxs: Vec<usize> = (0..tiers.len())
.filter(|&i| blocks[i] == Block::Girdle)
.collect();
if let Some(&first) = girdle_idxs.first()
&& !girdle_idxs.iter().any(|&i| is_anchored(&tiers[i]))
{
tiers[first].constraint = MeetConstraint::ScaleReference(GIRDLE_REFERENCE_MAST);
}
let girdle_r = girdle_radius_for_anchor(tiers, &blocks);
let mut anchor_block = |block: Block, ratio: Option<f64>| -> bool {
let Some(ratio) = ratio else { return false };
let idxs: Vec<usize> = (0..tiers.len()).filter(|&i| blocks[i] == block).collect();
if idxs.is_empty() || idxs.iter().any(|&i| is_anchored(&tiers[i])) {
return false;
}
let explicit = explicit_table_or_culet(tiers, &blocks, block);
let target = explicit.unwrap_or(idxs[0]);
let theta_deg = tiers[target].angle_deg;
let mast = geometric_ratio_anchor(theta_deg, ratio, girdle_r, explicit.is_some());
tiers[target].constraint = MeetConstraint::ScaleReference(mast);
true
};
let crown_anchored = anchor_block(Block::Crown, cw_ratio);
let pavilion_anchored = anchor_block(Block::Pavilion, pw_ratio);
(crown_anchored, pavilion_anchored)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn apply_ratio_anchors_uses_the_explicit_table_tier_when_present() {
let mut tiers = vec![
MeetTierInput {
angle_deg: -43.0,
indices: vec![4.0, 12.0],
constraint: MeetConstraint::MeetExisting,
names: vec!["P1".to_string()],
},
MeetTierInput {
angle_deg: -90.0,
indices: vec![4.0, 12.0],
constraint: MeetConstraint::MeetExisting,
names: vec!["G1".to_string()],
},
MeetTierInput {
angle_deg: 44.92,
indices: vec![4.0, 12.0],
constraint: MeetConstraint::MeetExisting,
names: vec!["A".to_string()],
},
MeetTierInput {
angle_deg: 0.0,
indices: vec![],
constraint: MeetConstraint::MeetExisting,
names: vec!["Table".to_string()],
},
];
let (crown_anchored, pavilion_anchored) =
apply_ratio_anchors(&mut tiers, Some(0.167), None);
assert!(crown_anchored, "crown should anchor: cw_ratio was supplied");
assert!(
!pavilion_anchored,
"pavilion should not anchor: pw_ratio was None and no tier already anchored"
);
match tiers[1].constraint {
MeetConstraint::ScaleReference(v) => {
assert!((v - GIRDLE_REFERENCE_MAST).abs() < 1e-12);
}
_ => panic!("girdle tier should have been anchored"),
}
match tiers[3].constraint {
MeetConstraint::ScaleReference(v) => {
let expected = 0.167 * 2.0 * GIRDLE_REFERENCE_MAST;
assert!((v - expected).abs() < 1e-9, "expected {expected}, got {v}");
}
_ => panic!("table tier should have been anchored"),
}
assert!(
matches!(tiers[2].constraint, MeetConstraint::MeetExisting),
"the non-table crown tier must be left alone"
);
assert!(matches!(tiers[0].constraint, MeetConstraint::MeetExisting));
}
#[test]
fn apply_ratio_anchors_falls_back_to_the_first_tier_of_the_block() {
let mut tiers = vec![
MeetTierInput {
angle_deg: -41.0,
indices: vec![1.0],
constraint: MeetConstraint::MeetExisting,
names: vec!["P1".to_string()],
},
MeetTierInput {
angle_deg: -90.0,
indices: vec![1.0],
constraint: MeetConstraint::MeetExisting,
names: vec!["G1".to_string()],
},
MeetTierInput {
angle_deg: 38.42,
indices: vec![1.0],
constraint: MeetConstraint::MeetExisting,
names: vec!["A".to_string()],
},
];
let (crown_anchored, pavilion_anchored) =
apply_ratio_anchors(&mut tiers, Some(0.184), Some(0.435));
assert!(crown_anchored);
assert!(pavilion_anchored);
match tiers[2].constraint {
MeetConstraint::ScaleReference(v) => {
let expected = geometric_ratio_anchor(38.42, 0.184, GIRDLE_REFERENCE_MAST, false);
assert!((v - expected).abs() < 1e-9, "expected {expected}, got {v}");
}
_ => panic!("only crown tier should have been anchored (first-tier fallback)"),
}
match tiers[0].constraint {
MeetConstraint::ScaleReference(v) => {
let expected = geometric_ratio_anchor(-41.0, 0.435, GIRDLE_REFERENCE_MAST, false);
assert!((v - expected).abs() < 1e-9, "expected {expected}, got {v}");
}
_ => panic!("only pavilion tier should have been anchored (first-tier fallback)"),
}
match tiers[1].constraint {
MeetConstraint::ScaleReference(v) => assert!((v - GIRDLE_REFERENCE_MAST).abs() < 1e-12),
_ => panic!("girdle tier should have been anchored"),
}
}
#[test]
fn geometric_ratio_anchor_matches_its_two_cases() {
let girdle_r = 1.37;
let ratio = 0.21;
let flat = geometric_ratio_anchor(123.0, ratio, girdle_r, true);
assert!((ratio * 2.0).mul_add(-girdle_r, flat).abs() < 1e-12);
let theta_deg = 41.0;
let a = geometric_ratio_anchor(theta_deg, 0.0, girdle_r, false);
let b = geometric_ratio_anchor(theta_deg, 0.9, girdle_r, false);
let expected = theta_deg.to_radians().sin() * girdle_r;
assert!((a - expected).abs() < 1e-12);
assert!((b - expected).abs() < 1e-12);
let neg = geometric_ratio_anchor(-theta_deg, ratio, girdle_r, false);
assert!((neg - expected).abs() < 1e-12);
}
#[test]
fn apply_ratio_anchors_uses_this_designs_own_girdle_radius_not_a_fixed_one() {
let mut tiers = vec![
MeetTierInput {
angle_deg: 0.0,
indices: vec![],
constraint: MeetConstraint::MeetExisting,
names: vec!["Table".to_string()],
},
MeetTierInput {
angle_deg: -90.0,
indices: vec![0.0],
constraint: MeetConstraint::ScaleReference(0.75), names: vec!["G1".to_string()],
},
MeetTierInput {
angle_deg: -90.0,
indices: vec![4.0],
constraint: MeetConstraint::ScaleReference(1.20), names: vec!["G2".to_string()],
},
];
let (crown_anchored, _) = apply_ratio_anchors(&mut tiers, Some(0.20), None);
assert!(crown_anchored);
match tiers[0].constraint {
MeetConstraint::ScaleReference(v) => {
let expected = 0.20 * 2.0 * 0.75;
assert!((v - expected).abs() < 1e-9, "expected {expected}, got {v}");
}
_ => panic!("table tier should have been anchored"),
}
assert!(
matches!(tiers[1].constraint, MeetConstraint::ScaleReference(v) if (v - 0.75).abs() < 1e-12)
);
assert!(
matches!(tiers[2].constraint, MeetConstraint::ScaleReference(v) if (v - 1.20).abs() < 1e-12)
);
}
#[test]
fn apply_ratio_anchors_never_overrides_an_existing_scale_reference() {
let mut tiers = vec![
MeetTierInput {
angle_deg: -90.0,
indices: vec![1.0],
constraint: MeetConstraint::ScaleReference(0.789),
names: vec!["G1".to_string()],
},
MeetTierInput {
angle_deg: 40.0,
indices: vec![1.0],
constraint: MeetConstraint::ScaleReference(0.709),
names: vec!["C1".to_string()],
},
];
let (crown_anchored, _) = apply_ratio_anchors(&mut tiers, Some(0.244), None);
assert!(!crown_anchored, "the crown block already had an anchor");
assert!(
matches!(tiers[0].constraint, MeetConstraint::ScaleReference(v) if (v - 0.789).abs() < 1e-12)
);
assert!(
matches!(tiers[1].constraint, MeetConstraint::ScaleReference(v) if (v - 0.709).abs() < 1e-12)
);
}
}