use super::{NORMAL_EPS, PanelKind};
use glam::{DVec3, Vec3};
use indicatrix::geometry::stone_metrics::SolidMesh;
#[must_use]
pub fn crown_visible(normal: Vec3) -> bool {
normal.y > NORMAL_EPS
}
#[must_use]
pub fn pavilion_visible(normal: Vec3) -> bool {
normal.y < -NORMAL_EPS
}
#[must_use]
pub fn profile_visible(normal: Vec3) -> bool {
normal.x.hypot(normal.z) > NORMAL_EPS
}
pub struct PanelLayout {
pub kind: PanelKind,
pub center_x: f32,
pub center_y: f32,
pub scale: f32,
pub wheel_radius_px: f32,
pub clip: (i32, i32, i32, i32),
}
pub fn wheel_direction(phi: f32, mirrored: bool) -> (f32, f32) {
let (sin_phi, cos_phi) = phi.sin_cos();
let world_x = cos_phi;
let world_z = sin_phi;
let screen_right = if mirrored { -world_z } else { world_z };
let screen_up = world_x;
(screen_right, screen_up)
}
pub const fn crown_project(p: DVec3, layout: &PanelLayout) -> (f32, f32, f32) {
let (world_x, world_y, world_z) = (p.x as f32, p.y as f32, p.z as f32);
let screen_x = layout.center_x + world_z * layout.scale;
let screen_y = layout.center_y - world_x * layout.scale;
(screen_x, screen_y, -world_y)
}
pub const fn pavilion_project(p: DVec3, layout: &PanelLayout) -> (f32, f32, f32) {
let (world_x, world_y, world_z) = (p.x as f32, p.y as f32, p.z as f32);
let screen_x = layout.center_x - world_z * layout.scale;
let screen_y = layout.center_y - world_x * layout.scale;
(screen_x, screen_y, world_y)
}
pub const fn profile_project(p: DVec3, layout: &PanelLayout) -> (f32, f32, f32) {
let (world_x, world_y, world_z) = (p.x as f32, p.y as f32, p.z as f32);
let screen_x = layout.center_x + world_x * layout.scale;
let screen_y = layout.center_y - world_y * layout.scale;
(screen_x, screen_y, -world_z)
}
pub fn facet_normals(mesh: &SolidMesh) -> Vec<Option<DVec3>> {
let facet_count = mesh.rings.iter().map(|(id, _)| id + 1).max().unwrap_or(0);
let mut facet_normal: Vec<Option<DVec3>> = vec![None; facet_count];
for (&id, &normal) in mesh.facet_id.iter().zip(&mesh.normals) {
if let Some(slot) = facet_normal.get_mut(id)
&& slot.is_none()
{
*slot = Some(normal);
}
}
facet_normal
}
fn mesh_radii(mesh: &SolidMesh) -> (f32, f32) {
let (mut min_x, mut max_x, mut min_y, mut max_y) = (0.0f64, 0.0f64, 0.0f64, 0.0f64);
let mut xz_radius = 1e-6f64;
for p in &mesh.positions {
min_x = min_x.min(p.x);
max_x = max_x.max(p.x);
min_y = min_y.min(p.y);
max_y = max_y.max(p.y);
xz_radius = xz_radius.max(p.x.hypot(p.z));
}
let half_w = ((max_x - min_x) / 2.0).max(1e-6);
let half_h = ((max_y - min_y) / 2.0).max(1e-6);
(xz_radius as f32, half_w.max(half_h) as f32)
}
struct GridMetrics {
col_width: f32,
panel_box: f32,
center_y: f32,
}
fn grid_metrics(width: u32, height: u32, columns: u32) -> GridMetrics {
let (width_f, height_f) = (width as f32, height as f32);
let col_width = width_f / columns.max(1) as f32;
let caption_height = (height_f * 0.06).clamp(10.0, 20.0);
let avail_height = (height_f - caption_height).max(1.0);
GridMetrics {
col_width,
panel_box: col_width.min(avail_height),
center_y: caption_height + avail_height / 2.0,
}
}
fn build_panel_layout(
metrics: &GridMetrics,
index: usize,
kind: PanelKind,
world_radius: f32,
body_frac: f32,
height: u32,
) -> PanelLayout {
let center_x = (index as f32).mul_add(metrics.col_width, metrics.col_width / 2.0);
let wheel_radius_px = metrics.panel_box / 2.0 * 0.86;
let body_radius_px = metrics.panel_box / 2.0 * body_frac;
let scale = body_radius_px / world_radius.max(1e-6);
let clip = (
(index as f32 * metrics.col_width).floor() as i32,
0,
((index as f32 + 1.0) * metrics.col_width).ceil() as i32 - 1,
height as i32 - 1,
);
PanelLayout {
kind,
center_x,
center_y: metrics.center_y,
scale,
wheel_radius_px,
clip,
}
}
pub fn compute_layout(mesh: &SolidMesh, width: u32, height: u32) -> [PanelLayout; 3] {
let (xz_radius, profile_radius) = mesh_radii(mesh);
let metrics = grid_metrics(width, height, 3);
[
build_panel_layout(&metrics, 0, PanelKind::Crown, xz_radius, 0.62, height),
build_panel_layout(&metrics, 1, PanelKind::Pavilion, xz_radius, 0.62, height),
build_panel_layout(
&metrics,
2,
PanelKind::Profile,
profile_radius,
0.85,
height,
),
]
}
pub fn compute_single_panel_layout(
mesh: &SolidMesh,
width: u32,
height: u32,
kind: PanelKind,
) -> PanelLayout {
let (xz_radius, profile_radius) = mesh_radii(mesh);
let (world_radius, body_frac) = match kind {
PanelKind::Crown | PanelKind::Pavilion => (xz_radius, 0.62),
PanelKind::Profile => (profile_radius, 0.85),
};
let metrics = grid_metrics(width, height, 1);
build_panel_layout(&metrics, 0, kind, world_radius, body_frac, height)
}