use std::fmt::Write as _;
const SAMPLE_COUNT: usize = 19;
pub const FULL_AXIS_SAMPLE_COUNT: usize = 181;
fn curve_path_generic<const N: usize>(values: &[f32; N]) -> String {
let mut commands = String::new();
for (i, &value) in values.iter().enumerate() {
if i > 0 {
commands.push(' ');
}
let x = i as f32 * 100.0 / (N - 1) as f32;
let y = 100.0 - value.clamp(0.0, 100.0);
let cmd = if i == 0 { 'M' } else { 'L' };
let _ = write!(commands, "{cmd} {x:.2} {y:.2}");
}
commands
}
pub fn tilt_curve_path(values: &[f32; SAMPLE_COUNT]) -> String {
curve_path_generic(values)
}
pub fn full_axis_curve_path(values: &[f32; FULL_AXIS_SAMPLE_COUNT]) -> String {
curve_path_generic(values)
}
#[cfg(test)]
mod tests {
use super::{FULL_AXIS_SAMPLE_COUNT, SAMPLE_COUNT, full_axis_curve_path, tilt_curve_path};
#[test]
fn known_input_produces_expected_path() {
let values = [0.0f32; SAMPLE_COUNT];
let path = tilt_curve_path(&values);
assert_eq!(
path,
"M 0.00 100.00 L 5.56 100.00 L 11.11 100.00 L 16.67 100.00 L 22.22 100.00 \
L 27.78 100.00 L 33.33 100.00 L 38.89 100.00 L 44.44 100.00 L 50.00 100.00 \
L 55.56 100.00 L 61.11 100.00 L 66.67 100.00 L 72.22 100.00 L 77.78 100.00 \
L 83.33 100.00 L 88.89 100.00 L 94.44 100.00 L 100.00 100.00"
);
}
#[test]
fn first_and_last_points_span_full_width() {
let values = [42.0f32; SAMPLE_COUNT];
let path = tilt_curve_path(&values);
let tokens: Vec<&str> = path.split_whitespace().collect();
assert_eq!(tokens.len(), SAMPLE_COUNT * 3);
assert_eq!(tokens[1], "0.00", "first sample must sit at x=0");
assert_eq!(
tokens[tokens.len() - 2],
"100.00",
"last sample must sit at x=100"
);
}
#[test]
fn value_100_maps_to_top_and_0_maps_to_bottom() {
let mut values = [50.0f32; SAMPLE_COUNT];
values[0] = 100.0;
values[1] = 0.0;
let path = tilt_curve_path(&values);
let tokens: Vec<&str> = path.split_whitespace().collect();
assert_eq!(tokens[2], "0.00", "value 100 must map to y=0 (top)");
assert_eq!(tokens[5], "100.00", "value 0 must map to y=100 (bottom)");
}
#[test]
fn out_of_range_values_are_clamped() {
let mut values = [50.0f32; SAMPLE_COUNT];
values[0] = 150.0; values[1] = -20.0; let path = tilt_curve_path(&values);
let tokens: Vec<&str> = path.split_whitespace().collect();
assert_eq!(tokens[2], "0.00", "150 must clamp to 100 -> y=0");
assert_eq!(tokens[5], "100.00", "-20 must clamp to 0 -> y=100");
}
#[test]
fn full_axis_first_and_last_points_span_full_width() {
let values = [42.0f32; FULL_AXIS_SAMPLE_COUNT];
let path = full_axis_curve_path(&values);
let tokens: Vec<&str> = path.split_whitespace().collect();
assert_eq!(tokens.len(), FULL_AXIS_SAMPLE_COUNT * 3);
assert_eq!(tokens[1], "0.00", "first sample (-90 deg) must sit at x=0");
assert_eq!(
tokens[tokens.len() - 2],
"100.00",
"last sample (+90 deg) must sit at x=100"
);
}
#[test]
fn full_axis_midpoint_sits_at_the_horizontal_center() {
let values = [50.0f32; FULL_AXIS_SAMPLE_COUNT];
let path = full_axis_curve_path(&values);
let tokens: Vec<&str> = path.split_whitespace().collect();
assert_eq!(tokens[90 * 3 + 1], "50.00");
}
#[test]
fn full_axis_value_100_maps_to_top_and_0_maps_to_bottom() {
let mut values = [50.0f32; FULL_AXIS_SAMPLE_COUNT];
values[0] = 100.0;
values[FULL_AXIS_SAMPLE_COUNT - 1] = 0.0;
let path = full_axis_curve_path(&values);
let tokens: Vec<&str> = path.split_whitespace().collect();
assert_eq!(tokens[2], "0.00", "value 100 must map to y=0 (top)");
assert_eq!(
tokens[tokens.len() - 1],
"100.00",
"value 0 must map to y=100 (bottom)"
);
}
#[test]
fn full_axis_out_of_range_values_are_clamped() {
let mut values = [50.0f32; FULL_AXIS_SAMPLE_COUNT];
values[0] = 150.0; values[1] = -20.0; let path = full_axis_curve_path(&values);
let tokens: Vec<&str> = path.split_whitespace().collect();
assert_eq!(tokens[2], "0.00", "150 must clamp to 100 -> y=0");
assert_eq!(tokens[5], "100.00", "-20 must clamp to 0 -> y=100");
}
}