#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MeasurementConfig {
pub unit_label: String,
pub decimal_places: u8,
pub snap_to_grid: bool,
pub grid_size: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MeasurementPoint {
pub position: [f32; 3],
pub label: String,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MeasurementResult {
pub value: f32,
pub description: String,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MeasurementSession {
pub config: MeasurementConfig,
pub points: Vec<MeasurementPoint>,
}
#[allow(dead_code)]
pub fn default_measurement_config() -> MeasurementConfig {
MeasurementConfig {
unit_label: "m".to_string(),
decimal_places: 4,
snap_to_grid: false,
grid_size: 0.1,
}
}
#[allow(dead_code)]
pub fn new_measurement_session(cfg: &MeasurementConfig) -> MeasurementSession {
MeasurementSession {
config: cfg.clone(),
points: Vec::new(),
}
}
#[allow(dead_code)]
pub fn measure_add_point(session: &mut MeasurementSession, point: [f32; 3]) {
let label = format!("P{}", session.points.len() + 1);
session.points.push(MeasurementPoint {
position: point,
label,
});
}
#[allow(dead_code)]
pub fn measure_distance(a: [f32; 3], b: [f32; 3]) -> f32 {
let dx = b[0] - a[0];
let dy = b[1] - a[1];
let dz = b[2] - a[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
#[allow(dead_code)]
pub fn measure_angle_deg(a: [f32; 3], vertex: [f32; 3], b: [f32; 3]) -> f32 {
let va = [a[0] - vertex[0], a[1] - vertex[1], a[2] - vertex[2]];
let vb = [b[0] - vertex[0], b[1] - vertex[1], b[2] - vertex[2]];
let dot = va[0] * vb[0] + va[1] * vb[1] + va[2] * vb[2];
let len_a = (va[0] * va[0] + va[1] * va[1] + va[2] * va[2]).sqrt();
let len_b = (vb[0] * vb[0] + vb[1] * vb[1] + vb[2] * vb[2]).sqrt();
if len_a < 1e-9 || len_b < 1e-9 {
return 0.0;
}
let cos_theta = (dot / (len_a * len_b)).clamp(-1.0, 1.0);
cos_theta.acos().to_degrees()
}
#[allow(dead_code)]
pub fn measure_triangle_area(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> f32 {
let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
let cross = [
ab[1] * ac[2] - ab[2] * ac[1],
ab[2] * ac[0] - ab[0] * ac[2],
ab[0] * ac[1] - ab[1] * ac[0],
];
let mag = (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt();
mag * 0.5
}
#[allow(dead_code)]
pub fn measurement_point_count(session: &MeasurementSession) -> usize {
session.points.len()
}
#[allow(dead_code)]
pub fn measurement_total_length(session: &MeasurementSession) -> f32 {
session
.points
.windows(2)
.map(|w| measure_distance(w[0].position, w[1].position))
.sum()
}
#[allow(dead_code)]
pub fn measurement_clear(session: &mut MeasurementSession) {
session.points.clear();
}
#[allow(dead_code)]
pub fn measurement_to_string(session: &MeasurementSession) -> String {
let prec = session.config.decimal_places as usize;
let mut out = format!(
"MeasurementSession [{} points, unit={}]\n",
session.points.len(),
session.config.unit_label,
);
for (i, pt) in session.points.iter().enumerate() {
out.push_str(&format!(
" {}: ({:.prec$}, {:.prec$}, {:.prec$}) [{}]\n",
i,
pt.position[0],
pt.position[1],
pt.position[2],
pt.label,
));
}
let total = measurement_total_length(session);
out.push_str(&format!(
" total_length = {:.prec$} {}\n",
total,
session.config.unit_label,
));
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let cfg = default_measurement_config();
assert_eq!(cfg.unit_label, "m");
assert!(!cfg.snap_to_grid);
}
#[test]
fn test_new_session_empty() {
let cfg = default_measurement_config();
let session = new_measurement_session(&cfg);
assert_eq!(measurement_point_count(&session), 0);
}
#[test]
fn test_add_point() {
let cfg = default_measurement_config();
let mut session = new_measurement_session(&cfg);
measure_add_point(&mut session, [1.0, 0.0, 0.0]);
assert_eq!(measurement_point_count(&session), 1);
}
#[test]
fn test_measure_distance_unit() {
let d = measure_distance([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]);
assert!((d - 1.0).abs() < 1e-5);
}
#[test]
fn test_measure_distance_3d() {
let d = measure_distance([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
assert!((d - 3.0_f32.sqrt()).abs() < 1e-5);
}
#[test]
fn test_measure_angle_right() {
let angle = measure_angle_deg([1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert!((angle - 90.0).abs() < 1e-4);
}
#[test]
fn test_measure_triangle_area_unit() {
let area = measure_triangle_area(
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
);
assert!((area - 0.5).abs() < 1e-5);
}
#[test]
fn test_total_length() {
let cfg = default_measurement_config();
let mut session = new_measurement_session(&cfg);
measure_add_point(&mut session, [0.0, 0.0, 0.0]);
measure_add_point(&mut session, [1.0, 0.0, 0.0]);
measure_add_point(&mut session, [1.0, 1.0, 0.0]);
let total = measurement_total_length(&session);
assert!((total - 2.0).abs() < 1e-5);
}
#[test]
fn test_measurement_clear() {
let cfg = default_measurement_config();
let mut session = new_measurement_session(&cfg);
measure_add_point(&mut session, [1.0, 2.0, 3.0]);
measurement_clear(&mut session);
assert_eq!(measurement_point_count(&session), 0);
}
#[test]
fn test_measurement_to_string() {
let cfg = default_measurement_config();
let mut session = new_measurement_session(&cfg);
measure_add_point(&mut session, [0.0, 0.0, 0.0]);
let s = measurement_to_string(&session);
assert!(s.contains("MeasurementSession"));
assert!(s.contains("total_length"));
}
}