use super::funejson::Value; use super::math_atan::atan; use super::math_round_float::round_float; // 180 / pi as a double, written out so no language computes it differently. const DEGREES_PER_RADIAN: f64 = 57.29577951308232; const LIMIT: f64 = 10000.0; fn check_length(name: &str, value: f64, allow_zero: bool) { let ok = value.is_finite() && value <= LIMIT && if allow_zero { value >= 0.0 } else { value > 0.0 }; if !ok { let want = if allow_zero { "zero or more" } else { "greater than zero" }; panic!( "{} must be a finite number of metres, {} and at most 10000, received {}", name, want, value ); } } /// Pitch, rafter length and slope area of a duo- or mono-pitch roof. /// /// The area is worked from the unrounded rafter, not the one rounded to a /// millimetre for display: over a long roof the difference shows in the /// second decimal place. /// /// # Panics /// Panics on a span or roof length that is not above zero, a negative rise, /// any dimension over 10,000 m, or an unknown roof type. pub fn roof_pitch(span: f64, rise: f64, roof_length: f64, roof_type: &str) -> RoofPitch { check_length("span", span, false); check_length("rise", rise, true); check_length("roofLength", roof_length, false); let (run, slopes) = match roof_type { "duo" => (span / 2.0, 2.0), "mono" => (span, 1.0), other => panic!("unknown roof type \"{}\"", other), }; let radians = atan(rise / run); // sqrt is one of the operations IEEE 754 requires to be correctly rounded, // so f64::sqrt is the same double in every language. let rafter = (run * run + rise * rise).sqrt(); RoofPitch { pitch_degrees: round_float(radians * DEGREES_PER_RADIAN, 2), rafter_length: round_float(rafter, 3), roof_area: round_float(rafter * roof_length * slopes, 2), } } pub fn roof_pitch_to_value(r: &RoofPitch) -> Value { Value::obj(vec![ ("pitchDegrees", Value::Float(r.pitch_degrees)), ("rafterLength", Value::Float(r.rafter_length)), ("roofArea", Value::Float(r.roof_area)), ]) } pub fn fune_vector(args: &[Value]) -> Value { for (i, name) in ["span", "rise", "roofLength"].iter().enumerate() { if let Value::Str(s) = &args[i] { panic!("{} must be a finite number of metres, received \"{}\"", name, s); } } roof_pitch_to_value(&roof_pitch( args[0].as_f64(), args[1].as_f64(), args[2].as_f64(), args[3].as_str(), )) }