Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::math_round_float::round_float; ← from math.round-float ^1.0.0 · built alongside by fune
// A 215 x 65 mm brick face plus a 10 mm joint is 225 x 75 mm of wall.
const FACE_MM2: i64 = 225 * 75;
// Mortar per mm2 of wall face, in mm3: each skin fills the joints around a
// brick, (16875 - 215 x 65) / 16875 of the face, 102.5 mm deep, which is
// 2378/135 mm; each collar joint between skins is 10 mm = 1350/135 mm.
const SKIN_MORTAR: i64 = 2378;
const COLLAR_MORTAR: i64 = 1350;
const MORTAR_DIVISOR: i64 = 135;
const MM3_PER_LITRE: i64 = 1_000_000;
fn millimetres(value: f64, message: &str, max: i64) -> i64 {
if !value.is_finite() || value <= 0.0 || value > max as f64 {
panic!(
"{} must be a finite number greater than 0 and at most {} metres, received {}",
message, max, value
);
}
(round_float(value, 3) * 1000.0).round() as i64
}
/// Bricks and mortar for a wall of UK standard bricks, openings subtracted.
///
/// The count is the net face area over one brick's 225 x 75 mm share of it,
/// 59.26 per m2 per skin (the trade's "60 a metre"), times the skins, plus
/// wastage, rounded up. Mortar is the joint volume that geometry implies.
/// Everything is whole millimetres and integer arithmetic.
///
/// # Panics
/// Panics on a dimension, skin count or wastage out of range, or openings
/// larger than the wall.
pub fn brick_count(
wall_length: f64,
wall_height: f64,
openings: &[Opening],
skins: i64,
wastage_basis_points: i64,
) -> BrickCount {
let length = millimetres(wall_length, "wallLength", 1000);
let height = millimetres(wall_height, "wallHeight", 100);
if !(1..=4).contains(&skins) {
panic!("skins must be a whole number from 1 to 4, received {}", skins);
}
if !(0..=10000).contains(&wastage_basis_points) {
panic!("wastageBasisPoints must be a whole number from 0 to 10000, received {}", wastage_basis_points);
}
let mut net = length * height;
for (i, opening) in openings.iter().enumerate() {
let w = millimetres(opening.width, &format!("opening {}: width", i + 1), 1000);
let h = millimetres(opening.height, &format!("opening {}: height", i + 1), 1000);
net -= w * h;
}
if net < 0 {
panic!("openings are larger than the wall");
}
let bricks = round_div(net * skins * (10000 + wastage_basis_points), FACE_MM2 * 10000, "up");
let per_mm2 = skins * SKIN_MORTAR + (skins - 1) * COLLAR_MORTAR;
// Up to the whole litre first, then the wastage, so no product passes 2^53.
let litres = round_div(net * per_mm2, MORTAR_DIVISOR * MM3_PER_LITRE, "up");
let mortar = round_div(litres * (10000 + wastage_basis_points), 10000, "up");
BrickCount {
net_area_square_metres: net as f64 / 1_000_000.0,
bricks,
mortar_cubic_metres: mortar as f64 / 1000.0,
}
}
pub fn brick_count_to_value(b: &BrickCount) -> Value {
Value::obj(vec![
("netAreaSquareMetres", Value::Float(b.net_area_square_metres)),
("bricks", Value::Int(b.bricks)),
("mortarCubicMetres", Value::Float(b.mortar_cubic_metres)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let openings: Vec<Opening> = args[2]
.as_arr()
.iter()
.map(|v| Opening { width: v.get("width").as_f64(), height: v.get("height").as_f64() })
.collect();
if let Value::Float(f) = &args[3] {
panic!("skins must be a whole number from 1 to 4, received {}", f);
}
if let Value::Float(f) = &args[4] {
panic!("wastageBasisPoints must be a whole number from 0 to 10000, received {}", f);
}
brick_count_to_value(&brick_count(
args[0].as_f64(),
args[1].as_f64(),
&openings,
args[3].as_i64(),
args[4].as_i64(),
))
}