sheets.rs
9.3 kB · rust · 339 lines
1use super::faces::Quad;2use crate::space::Vec3;3use std::f32::consts::TAU;45/// The working width of the sheet plane.6pub const WIDTH: f32 = 320.0;7/// The working height of the sheet plane.8pub const HEIGHT: f32 = 452.0;910const HALF: f32 = HEIGHT / 2.0;11const MARGIN: f32 = 16.0;12const MAX_LINE: usize = 48;13const MIN_SEGMENTS: usize = 8;14const MAX_SEGMENTS: usize = 24;15const MAX_FACETS: usize = 9600;16const MIN_THICK: f32 = 1.0;17const MAX_THICK: f32 = 80.0;18const BORE_MAX: f32 = 0.45;19const EPS: f32 = 1e-6;2021const UP: Vec3 = Vec3 {22 x: 0.0,23 y: 0.0,24 z: 1.0,25};26const DOWN: Vec3 = Vec3 {27 x: 0.0,28 y: 0.0,29 z: -1.0,30};3132// SHAPE3334fn fit(cols: usize, rows: usize, segments: usize) -> (usize, usize, usize) {35 let mut cols = cols.min(MAX_LINE);36 let mut rows = rows.min(MAX_LINE);37 let mut segments = segments.clamp(MIN_SEGMENTS, MAX_SEGMENTS);38 while cols * rows * segments > MAX_FACETS && segments > MIN_SEGMENTS {39 segments -= 1;40 }41 while cols * rows * segments > MAX_FACETS {42 if cols >= rows {43 cols -= 1;44 } else {45 rows -= 1;46 }47 }48 (cols, rows, segments)49}5051fn depth(thickness: f32) -> f32 {52 let held = if thickness.is_finite() {53 thickness54 } else {55 0.056 };57 held.clamp(MIN_THICK, MAX_THICK) / 2.058}5960fn point(x: f32, y: f32, z: f32) -> Vec3 {61 Vec3::new(x / HALF, y / HALF, z / HALF)62}6364fn reach(hx: f32, hy: f32, c: f32, s: f32) -> f32 {65 let tx = if c.abs() > EPS {66 hx / c.abs()67 } else {68 f32::MAX69 };70 let ty = if s.abs() > EPS {71 hy / s.abs()72 } else {73 f32::MAX74 };75 tx.min(ty)76}7778// PARTS7980fn plate(out: &mut Vec<Quad>, x0: f32, y0: f32, x1: f32, y1: f32, t: f32) {81 if x1 - x0 <= EPS || y1 - y0 <= EPS {82 return;83 }84 out.push(Quad {85 normal: UP,86 verts: [87 point(x0, y0, t),88 point(x1, y0, t),89 point(x1, y1, t),90 point(x0, y1, t),91 ],92 });93 out.push(Quad {94 normal: DOWN,95 verts: [96 point(x0, y0, -t),97 point(x0, y1, -t),98 point(x1, y1, -t),99 point(x1, y0, -t),100 ],101 });102}103104fn rim(out: &mut Vec<Quad>, w: f32, h: f32, t: f32) {105 out.push(Quad {106 normal: Vec3::new(1.0, 0.0, 0.0),107 verts: [108 point(w, -h, -t),109 point(w, h, -t),110 point(w, h, t),111 point(w, -h, t),112 ],113 });114 out.push(Quad {115 normal: Vec3::new(-1.0, 0.0, 0.0),116 verts: [117 point(-w, -h, -t),118 point(-w, -h, t),119 point(-w, h, t),120 point(-w, h, -t),121 ],122 });123 out.push(Quad {124 normal: Vec3::new(0.0, 1.0, 0.0),125 verts: [126 point(-w, h, -t),127 point(-w, h, t),128 point(w, h, t),129 point(w, h, -t),130 ],131 });132 out.push(Quad {133 normal: Vec3::new(0.0, -1.0, 0.0),134 verts: [135 point(-w, -h, -t),136 point(w, -h, -t),137 point(w, -h, t),138 point(-w, -h, t),139 ],140 });141}142143fn bored(out: &mut Vec<Quad>, at: [f32; 2], span: [f32; 2], bore: f32, t: f32, segments: usize) {144 let step = TAU / segments as f32;145 let ring: Vec<[f32; 2]> = (0..segments)146 .map(|s| {147 let a = step * s as f32;148 [a.cos(), a.sin()]149 })150 .collect();151 for s in 0..segments {152 let [c0, s0] = ring[s];153 let [c1, s1] = ring[(s + 1) % segments];154 let a = [at[0] + bore * c0, at[1] + bore * s0];155 let b = [at[0] + bore * c1, at[1] + bore * s1];156 let ra = reach(span[0], span[1], c0, s0);157 let rb = reach(span[0], span[1], c1, s1);158 let oa = [at[0] + ra * c0, at[1] + ra * s0];159 let ob = [at[0] + rb * c1, at[1] + rb * s1];160 out.push(Quad {161 normal: UP,162 verts: [163 point(oa[0], oa[1], t),164 point(ob[0], ob[1], t),165 point(b[0], b[1], t),166 point(a[0], a[1], t),167 ],168 });169 out.push(Quad {170 normal: DOWN,171 verts: [172 point(a[0], a[1], -t),173 point(b[0], b[1], -t),174 point(ob[0], ob[1], -t),175 point(oa[0], oa[1], -t),176 ],177 });178 let mid = step * (s as f32 + 0.5);179 out.push(Quad {180 normal: Vec3::new(-mid.cos(), -mid.sin(), 0.0),181 verts: [182 point(a[0], a[1], t),183 point(b[0], b[1], t),184 point(b[0], b[1], -t),185 point(a[0], a[1], -t),186 ],187 });188 }189}190191// SHEET192193/// Builds the quads of a rimmed plate bored with a cols-by-rows grid of holes.194pub fn sheet(195 cols: usize,196 rows: usize,197 diameter: f32,198 thickness: f32,199 segments: usize,200) -> Vec<Quad> {201 let (cols, rows, segments) = fit(cols, rows, segments);202 let t = depth(thickness);203 let (w, h) = (WIDTH / 2.0, HALF);204 let mut out = Vec::new();205 rim(&mut out, w, h, t);206 let pitch = [207 (WIDTH - 2.0 * MARGIN) / cols.max(1) as f32,208 (HEIGHT - 2.0 * MARGIN) / rows.max(1) as f32,209 ];210 let wide = if diameter.is_finite() { diameter } else { 0.0 };211 let bore = (wide / 2.0).clamp(0.0, BORE_MAX * pitch[0].min(pitch[1]));212 if cols == 0 || rows == 0 || bore <= EPS {213 plate(&mut out, -w, -h, w, h, t);214 return out;215 }216 plate(&mut out, -w, -h, w, -h + MARGIN, t);217 plate(&mut out, -w, h - MARGIN, w, h, t);218 plate(&mut out, -w, -h + MARGIN, -w + MARGIN, h - MARGIN, t);219 plate(&mut out, w - MARGIN, -h + MARGIN, w, h - MARGIN, t);220 let span = [pitch[0] / 2.0, pitch[1] / 2.0];221 for j in 0..rows {222 for i in 0..cols {223 let at = [224 -w + MARGIN + (i as f32 + 0.5) * pitch[0],225 -h + MARGIN + (j as f32 + 0.5) * pitch[1],226 ];227 bored(&mut out, at, span, bore, t, segments);228 }229 }230 out231}232233/// Returns the twelve wire edges of the sheet's box at the given thickness.234pub fn sheet_edges(thickness: f32) -> Vec<[Vec3; 2]> {235 let t = depth(thickness);236 let (w, h) = (WIDTH / 2.0, HALF);237 let mut out = Vec::new();238 for z in [-t, t] {239 let ring = [240 point(-w, -h, z),241 point(w, -h, z),242 point(w, h, z),243 point(-w, h, z),244 ];245 for k in 0..4 {246 out.push([ring[k], ring[(k + 1) % 4]]);247 }248 }249 for [x, y] in [[-w, -h], [w, -h], [w, h], [-w, h]] {250 out.push([point(x, y, -t), point(x, y, t)]);251 }252 out253}254255#[cfg(test)]256mod tests {257 use super::*;258 use crate::space::Pack;259260 fn buffer(quads: &[Quad]) -> Vec<f32> {261 let mut pack = Pack::new();262 for quad in quads {263 pack.quad(quad.verts, quad.normal);264 }265 pack.buffer()266 }267268 #[test]269 fn the_buffer_holds_its_header() {270 let quads = sheet(6, 8, 24.0, 8.0, 12);271 let buf = buffer(&quads);272 assert_eq!(buf[0] as usize, quads.len() * 36);273 assert_eq!(buf[1], 0.0);274 assert_eq!(buf.len(), 2 + quads.len() * 36);275 assert_eq!(buf[0] as usize % 18, 0);276 assert!(quads.len() > 6 * 8 * 12);277 }278 #[test]279 fn the_plate_keeps_the_sheet_law() {280 let quads = sheet(4, 6, 20.0, 6.0, 8);281 let mut wide = 0.0f32;282 let mut tall = 0.0f32;283 for quad in &quads {284 for v in quad.verts {285 wide = wide.max(v.x.abs());286 tall = tall.max(v.y.abs());287 assert!(v.z.abs() <= 1.0);288 }289 }290 assert!((tall - 1.0).abs() < 1e-6);291 assert!((wide - WIDTH / HEIGHT).abs() < 1e-6);292 }293 #[test]294 fn the_normals_stay_flat_and_true() {295 for quad in sheet(3, 4, 30.0, 10.0, 8) {296 let n = quad.normal;297 assert!((n.dot(n) - 1.0).abs() < 1e-5);298 for k in 0..3 {299 let e = quad.verts[k + 1] - quad.verts[0];300 assert!(n.dot(e).abs() < 1e-5);301 }302 }303 }304 #[test]305 fn the_holes_open_through_the_plate() {306 let solid = sheet(0, 0, 0.0, 8.0, 12);307 let holed = sheet(5, 7, 24.0, 8.0, 12);308 assert_eq!(solid.len(), 6);309 assert!(holed.len() > solid.len());310 }311 #[test]312 fn the_params_clamp_to_the_budget() {313 let (cols, rows, segments) = fit(4000, 4000, 999);314 assert!(cols <= MAX_LINE && rows <= MAX_LINE);315 assert!((MIN_SEGMENTS..=MAX_SEGMENTS).contains(&segments));316 assert!(cols * rows * segments <= MAX_FACETS);317 let buf = buffer(&sheet(4000, 4000, 999.0, 999.0, 999));318 assert!(buf.len() * 4 < 5_000_000);319 let (_, _, tiny) = fit(2, 2, 0);320 assert_eq!(tiny, MIN_SEGMENTS);321 }322 #[test]323 fn the_degenerate_sheets_hold_together() {324 assert_eq!(sheet(0, 8, 20.0, 8.0, 12).len(), 6);325 assert_eq!(sheet(8, 0, 20.0, 8.0, 12).len(), 6);326 assert_eq!(sheet(8, 8, 0.0, 8.0, 12).len(), 6);327 assert!(!sheet(8, 8, 4000.0, 0.0, 12).is_empty());328 assert!(!sheet(8, 8, -4.0, -4.0, 12).is_empty());329 assert!(!sheet(1, 1, f32::NAN, f32::INFINITY, 12).is_empty());330 }331 #[test]332 fn the_frame_wires_the_box() {333 let wires = sheet_edges(8.0);334 assert_eq!(wires.len(), 12);335 for [a, b] in wires {336 assert!(a != b);337 }338 }339}