use super::{diagram2d, mesh_cache::CachedMesh};
use glam::{DVec3, Vec3};
mod overlays;
mod render;
mod shading;
#[cfg(test)]
mod tests;
const NEAR_EPS: f32 = 1e-4;
const HATCH_PERIOD: i32 = 6;
const MIN_LABEL_SPAN: f32 = 26.0;
const EDGE_DEPTH_BIAS: f32 = 5e-2;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FillMode {
#[default]
Opaque,
Transparent,
}
#[derive(Debug, Clone)]
pub struct SolidStyle {
pub base_color: [u8; 3],
pub ambient: f32,
pub diffuse: f32,
pub rim_strength: f32,
pub key_light_dir: Vec3,
pub edge_color: [u8; 3],
pub pending_color: [u8; 3],
pub hatch_color: [u8; 3],
pub selected_color: [u8; 3],
pub hover_color: [u8; 3],
pub selected_facet_color: [u8; 3],
pub multi_selected_color: [u8; 3],
pub background: [u8; 4],
pub fill_mode: FillMode,
pub flagged: Vec<bool>,
pub pending: Vec<bool>,
pub selected: Vec<bool>,
pub hovered: Option<u32>,
pub selected_facet: Option<u32>,
pub multi_selected: Vec<u32>,
pub facet_labels: Vec<String>,
pub show_orientation_marker: bool,
pub preform_plane_count: usize,
pub show_preform: bool,
pub preform_tint_color: [u8; 3],
}
impl Default for SolidStyle {
fn default() -> Self {
Self {
base_color: [200, 205, 215],
ambient: 0.25,
diffuse: 0.75,
rim_strength: 0.18,
key_light_dir: Vec3::new(0.4, 0.8, 0.5).normalize(),
edge_color: [30, 32, 38],
pending_color: [245, 158, 11],
hatch_color: [90, 40, 40],
selected_color: [59, 130, 246],
hover_color: [226, 232, 240],
selected_facet_color: [168, 85, 247],
multi_selected_color: [56, 189, 248],
background: [0, 0, 0, 0],
fill_mode: FillMode::Opaque,
flagged: Vec::new(),
pending: Vec::new(),
selected: Vec::new(),
hovered: None,
selected_facet: None,
multi_selected: Vec::new(),
facet_labels: Vec::new(),
show_orientation_marker: true,
preform_plane_count: 0,
show_preform: true,
preform_tint_color: [80, 90, 140],
}
}
}
#[derive(Debug, Clone)]
pub struct SolidRasterizer {
pub width: u32,
pub height: u32,
pub depth: Vec<f32>,
pub color: Vec<u8>,
pub pick: Vec<u32>,
ring_scratch: Vec<DVec3>,
dedup_scratch: Vec<DVec3>,
edge_points: Vec<(f32, f32, f32)>,
edge_ranges: Vec<(usize, usize, usize)>,
}
impl SolidRasterizer {
#[must_use]
pub fn new(width: u32, height: u32) -> Self {
let n = (width as usize) * (height as usize);
Self {
width,
height,
depth: vec![f32::INFINITY; n],
color: vec![0u8; n * 4],
pick: vec![0u32; n],
ring_scratch: Vec::new(),
dedup_scratch: Vec::new(),
edge_points: Vec::new(),
edge_ranges: Vec::new(),
}
}
pub fn resize(&mut self, width: u32, height: u32) {
if width == self.width && height == self.height {
return;
}
*self = Self::new(width, height);
}
#[must_use]
pub fn pick_at(&self, x: u32, y: u32) -> Option<u32> {
if x >= self.width || y >= self.height {
return None;
}
let v = self.pick[(y * self.width + x) as usize];
(v != 0).then(|| v - 1)
}
fn clear(&mut self, background: [u8; 4]) {
self.depth.fill(f32::INFINITY);
self.pick.fill(0);
let (pixels, _remainder) = self.color.as_chunks_mut::<4>();
for pixel in pixels {
pixel.copy_from_slice(&background);
}
}
}
pub(super) fn simplify_ring(ring: &[DVec3], dedup: &mut Vec<DVec3>, out: &mut Vec<DVec3>) {
const SIN_TOL: f64 = 1e-6;
dedup.clear();
out.clear();
let Some(&first) = ring.first() else {
return;
};
let (mut lo, mut hi) = (first, first);
for &p in ring {
lo = lo.min(p);
hi = hi.max(p);
}
let merge_eps = (hi - lo).length().max(1e-12) * 1e-7;
for &p in ring {
if dedup
.last()
.is_some_and(|&last| (p - last).length() <= merge_eps)
{
continue;
}
dedup.push(p);
}
while dedup.len() > 1 && (dedup[0] - dedup[dedup.len() - 1]).length() <= merge_eps {
dedup.pop();
}
if dedup.len() < 3 {
out.extend_from_slice(dedup);
return;
}
let n = dedup.len();
let mut prev = dedup[n - 1];
for i in 0..n {
let cur = dedup[i];
let next = dedup[(i + 1) % n];
let e1 = cur - prev;
let e2 = next - cur;
let collinear = e1.cross(e2).length() <= SIN_TOL * e1.length() * e2.length();
if !collinear {
out.push(cur);
prev = cur;
}
}
}