pub(super) fn local_cast_bounds(points: &[[f64;3]], scale:f64)->Result<Vec<f64>,String> {
if !scale.is_finite() || scale<=0. {return Err("Invalid model length unit scale".into());}
let scale_f32=scale as f32;
if !scale_f32.is_finite() || scale_f32<=0. {return Err("Model length unit scale is not a positive finite f32".into());}
points.iter().map(|point| {
let mut bound=0.;
for &value in point {
let ideal_metres=value*scale;
let reparsed_metres=f64::from((value as f32)*scale_f32);
let error=(ideal_metres-reparsed_metres).abs();
if !ideal_metres.is_finite() || !reparsed_metres.is_finite() || !error.is_finite() {
return Err("Evaluated coordinate exceeds f32 range".into());
}
bound+=error;
}
if bound.is_finite() {Ok(bound)}else{Err("Evaluated coordinate exceeds f32 range".into())}
}).collect()
}
pub(super) fn same_corner(
before:&[f32], before_origin:[f64;3], after:&[f32], after_origin:[f64;3],
local_cast_bound:f64,
)->bool {
(0..3).all(|axis| {
let old=f64::from(before[axis])+before_origin[axis];
let new=f64::from(after[axis])+after_origin[axis];
let value=after[axis];
let spacing=(f64::from(value.next_up())-f64::from(value))
.max(f64::from(value)-f64::from(value.next_down()));
let gamma=32.*f64::EPSILON/(1.-32.*f64::EPSILON);
let arithmetic=gamma*(old.abs()+new.abs()+before_origin[axis].abs()+after_origin[axis].abs()+1.);
let bound=local_cast_bound+spacing*0.5+arithmetic;
old.is_finite() && new.is_finite() && bound.is_finite() && (old-new).abs()<=bound
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn issue_4404_frame_bound_accepts_rounding_but_refuses_one_ulp_deformation() {
let origin=[6.,5.,2.6];
let before=[0.1f32,0.2,0.3];
let world: [f64;3]=std::array::from_fn(|axis|f64::from(before[axis])+origin[axis]);
let after=world.map(|value|value as f32);
assert!(same_corner(&before,origin,&after,[0.;3],0.));
let mut changed=after;
changed[0]=changed[0].next_up().next_up();
assert!(!same_corner(&before,origin,&changed,[0.;3],0.));
let bounds=local_cast_bounds(&[world],1.).unwrap();
assert_eq!(bounds[0],world.iter().zip(after).map(|(a,b)|(a-f64::from(b)).abs()).sum::<f64>());
}
#[test]
fn issue_4550_frame_bound_accounts_for_file_unit_and_scale_casts() {
let point=[1_000.123_456,2_000.234_567,3_000.345_678];
let scale=0.001;
let scale_f32=scale as f32;
let expected=point.iter().map(|&value| {
let ideal_metres=value*scale;
let reparsed_metres=f64::from((value as f32)*scale_f32);
(ideal_metres-reparsed_metres).abs()
}).sum::<f64>();
assert_eq!(local_cast_bounds(&[point],scale).unwrap(),vec![expected]);
}
#[test]
fn issue_4550_frame_bound_refuses_invalid_scale_and_nonfinite_products() {
for scale in [0.,-1.,f64::NAN,f64::INFINITY,f64::from(f32::MAX)*2.] {
assert!(local_cast_bounds(&[[1.,2.,3.]],scale).is_err());
}
assert!(local_cast_bounds(&[[f64::MAX,0.,0.]],1.).is_err());
assert!(local_cast_bounds(&[[f64::NAN,0.,0.]],1.).is_err());
assert!(!same_corner(&[0.;3],[0.;3],&[f32::INFINITY,0.,0.],[0.;3],0.));
}
}