#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct AxisGizmoConfig {
pub size_px: u32,
pub screen_position: [f32; 2],
pub shaft_length: f32,
}
#[derive(Debug, Clone)]
pub struct AxisGizmoState {
pub config: AxisGizmoConfig,
pub rotation: [[f32; 3]; 3],
pub visible: bool,
}
#[allow(dead_code)]
pub fn default_axis_gizmo_config() -> AxisGizmoConfig {
AxisGizmoConfig {
size_px: 80,
screen_position: [60.0, 60.0],
shaft_length: 0.8,
}
}
#[allow(dead_code)]
pub fn new_axis_gizmo(cfg: AxisGizmoConfig) -> AxisGizmoState {
AxisGizmoState {
config: cfg,
rotation: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
visible: true,
}
}
#[allow(dead_code)]
pub fn axis_gizmo_update_matrix(state: &mut AxisGizmoState, view_matrix: &[[f32; 4]; 4]) {
for (row, dst_row) in state.rotation.iter_mut().enumerate().take(3) {
for (col, dst) in dst_row.iter_mut().enumerate().take(3) {
*dst = view_matrix[col][row];
}
}
}
#[allow(dead_code)]
pub fn axis_gizmo_axis_color(axis: usize) -> [f32; 4] {
match axis {
0 => [1.0, 0.1, 0.1, 1.0], 1 => [0.1, 1.0, 0.1, 1.0], _ => [0.1, 0.1, 1.0, 1.0], }
}
#[allow(dead_code)]
pub fn axis_gizmo_label(axis: usize) -> &'static str {
match axis {
0 => "X",
1 => "Y",
_ => "Z",
}
}
#[allow(dead_code)]
pub fn axis_gizmo_set_visible(state: &mut AxisGizmoState, visible: bool) {
state.visible = visible;
}
#[allow(dead_code)]
pub fn axis_gizmo_is_visible(state: &AxisGizmoState) -> bool {
state.visible
}
#[allow(dead_code)]
pub fn axis_gizmo_screen_position(state: &AxisGizmoState) -> [f32; 2] {
state.config.screen_position
}
#[allow(dead_code)]
pub fn axis_gizmo_to_json(state: &AxisGizmoState) -> String {
format!(
"{{\"visible\":{},\"size_px\":{},\"screen_position\":[{:.3},{:.3}]}}",
state.visible,
state.config.size_px,
state.config.screen_position[0],
state.config.screen_position[1],
)
}
#[allow(dead_code)]
pub fn axis_gizmo_reset(state: &mut AxisGizmoState) {
state.rotation = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
state.visible = true;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_values() {
let cfg = default_axis_gizmo_config();
assert_eq!(cfg.size_px, 80);
assert!((cfg.shaft_length - 0.8).abs() < 1e-5);
}
#[test]
fn new_gizmo_is_visible_with_identity() {
let cfg = default_axis_gizmo_config();
let state = new_axis_gizmo(cfg);
assert!(axis_gizmo_is_visible(&state));
assert_eq!(state.rotation[0][0], 1.0);
assert_eq!(state.rotation[1][1], 1.0);
assert_eq!(state.rotation[2][2], 1.0);
}
#[test]
fn set_visible_false() {
let mut s = new_axis_gizmo(default_axis_gizmo_config());
axis_gizmo_set_visible(&mut s, false);
assert!(!axis_gizmo_is_visible(&s));
}
#[test]
fn reset_restores_identity_and_visibility() {
let mut s = new_axis_gizmo(default_axis_gizmo_config());
axis_gizmo_set_visible(&mut s, false);
s.rotation[0][0] = 0.5;
axis_gizmo_reset(&mut s);
assert!(axis_gizmo_is_visible(&s));
assert!((s.rotation[0][0] - 1.0).abs() < 1e-5);
}
#[test]
fn axis_colors_are_distinct() {
let cx = axis_gizmo_axis_color(0);
let cy = axis_gizmo_axis_color(1);
let cz = axis_gizmo_axis_color(2);
assert_ne!(cx, cy);
assert_ne!(cy, cz);
}
#[test]
fn axis_labels_are_correct() {
assert_eq!(axis_gizmo_label(0), "X");
assert_eq!(axis_gizmo_label(1), "Y");
assert_eq!(axis_gizmo_label(2), "Z");
}
#[test]
fn screen_position_roundtrip() {
let mut cfg = default_axis_gizmo_config();
cfg.screen_position = [100.0, 200.0];
let s = new_axis_gizmo(cfg);
let pos = axis_gizmo_screen_position(&s);
assert!((pos[0] - 100.0).abs() < 1e-5);
assert!((pos[1] - 200.0).abs() < 1e-5);
}
#[test]
fn update_matrix_reads_upper_left_3x3() {
let mut s = new_axis_gizmo(default_axis_gizmo_config());
let vm: [[f32; 4]; 4] = [
[0.0, 0.0, -1.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
];
axis_gizmo_update_matrix(&mut s, &vm);
assert!((s.rotation[0][0] - 0.0).abs() < 1e-5);
assert!((s.rotation[0][2] - 1.0).abs() < 1e-5);
}
#[test]
fn to_json_contains_visible() {
let s = new_axis_gizmo(default_axis_gizmo_config());
let json = axis_gizmo_to_json(&s);
assert!(json.contains("\"visible\":true"));
}
}