use stl_io::{self, Normal, Triangle, Vertex}; use crate::layers::{Layer, Layers}; fn new_triangle(p1: &Vertex, p2: &Vertex, p3: &Vertex) -> stl_io::Triangle { // Set Vector U to (Triangle.p2 minus Triangle.p1) // Set Vector V to (Triangle.p3 minus Triangle.p1) // Set Normal.x to (multiply U.y by V.z) minus (multiply U.z by V.y) // Set Normal.y to (multiply U.z by V.x) minus (multiply U.x by V.z) // Set Normal.z to (multiply U.x by V.y) minus (multiply U.y by V.x) let u = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]]; let v = [p3[0] - p1[0], p3[1] - p1[1], p3[2] - p1[2]]; let n_x = (u[1] * v[2]) - (u[2] * v[1]); let n_y = (u[2] * v[0]) - (u[0] * v[2]); let n_z = (u[0] * v[1]) - (u[1] * v[0]); stl_io::Triangle { normal: Normal::new([n_x, n_y, n_z]), vertices: [*p1, *p2, *p3], } } /// Find a point on the cirle at a given angle (in radians). fn vertex_on_circle(center: &Vertex, radius: f32, angle: f32) -> Vertex { let x = center[0] + (radius * angle.cos()); let y = center[1] + (radius * angle.sin()); let z = center[2]; Vertex::new([x, y, z]) } fn vertexes_on_circle(layer: &Layer, steps: u32) -> Vec { let mut result = Vec::with_capacity(steps as usize); let stepsize = std::f64::consts::TAU / steps as f64; for i in 0..steps { let angle = (i as f64 * stepsize) as f32; result.push(vertex_on_circle(&layer.center, layer.radius, angle)); } result } fn tube_base(center: &Vertex, edge: &[Vertex]) -> Vec { let mut result = Vec::with_capacity(edge.len()); for i in 0..edge.len() { result.push(new_triangle(center, &edge[(i + 1) % edge.len()], &edge[i])); } result } fn tube_sides(botoom_edge: &[Vertex], top_edge: &[Vertex]) -> Vec { assert_eq!(botoom_edge.len(), top_edge.len()); let mut result = Vec::with_capacity(2 * botoom_edge.len()); for i in 0..botoom_edge.len() { let j = (i + 1) % botoom_edge.len(); result.push(new_triangle(&botoom_edge[i], &top_edge[j], &top_edge[i])); result.push(new_triangle(&botoom_edge[i], &botoom_edge[j], &top_edge[j])); } result } fn tube_top(center: &Vertex, edge: &[Vertex]) -> Vec { // The top is the same, except the normals are reversed. We can do that by reversing the order of the vertexes. let mut edge = edge.to_owned(); edge.reverse(); tube_base(center, &edge) } pub fn render(layers: &Layers, resolution: f32) -> Vec { // Top and Bottom must be a single point, and must be at least one middle layer. assert!(!layers.layers.is_empty()); // How many vertexes per circle. IE: the resolution of the circle. let circle_steps = ((layers.max_radius() * std::f32::consts::TAU) / resolution).max(64.0) as u32; let mut layer_vertexes: Vec> = Vec::with_capacity(layers.layers.len()); for layer in &layers.layers { layer_vertexes.push(vertexes_on_circle(layer, circle_steps)); } // Estimate triangle count: base + sides + top let estimated_triangles = layers.layers.len() * circle_steps as usize * 2; let mut result: Vec = Vec::with_capacity(estimated_triangles as usize); result.append(&mut tube_base(&layers.base, &layer_vertexes[0])); for i in 0..layer_vertexes.len() - 1 { result.append(&mut tube_sides(&layer_vertexes[i], &layer_vertexes[i + 1])); } result.append(&mut tube_top(&layers.top, layer_vertexes.last().unwrap())); result } #[cfg(test)] mod tests { use approx::relative_eq; use super::*; #[test] fn new_triangle_normal() { // Simple triangle let a = Vertex::new([1.0, 0.0, 0.0]); let b = Vertex::new([0.0, 1.0, 0.0]); let c = Vertex::new([-1.0, 0.0, 0.0]); let tri = new_triangle(&a, &b, &c); assert_eq!(tri.normal, Normal::new([0.0, 0.0, 2.0])); // Same triangle, reverse order. let tri = new_triangle(&c, &b, &a); assert_eq!(tri.normal, Normal::new([0.0, 0.0, -2.0])); // Uneven numbers. let a = Vertex::new([2.0, 0.0, 0.0]); let b = Vertex::new([0.0, 3.0, 0.0]); let c = Vertex::new([-4.0, 0.0, 0.0]); let tri_big = new_triangle(&a, &b, &c); assert_eq!(tri_big.normal, Normal::new([0.0, 0.0, 18.0])); } fn validate_vec_vertex_relative_eq(val: &Vec, expected: &Vec) { let mut pass = true; if val.len() != expected.len() { pass = false; } for i in 0..val.len() { for j in 0..3 { if !relative_eq!(val[i][j], expected[i][j]) { pass = false; } } } if !pass { // We re-compare here (which will always fail) to get the friendly error message, // which I can't figure out how to generate directly. assert_eq!(val, expected); } } #[test] fn vertexes_on_circle_basic() { let val = vertexes_on_circle( &Layer { center: Vertex::new([0.0, 0.0, 0.0]), radius: 1.0, }, 4, ); let expected = [ Vertex::new([1.0, 0.0, 0.0]), Vertex::new([0.0, 1.0, 0.0]), Vertex::new([-1.0, 0.0, 0.0]), Vertex::new([0.0, -1.0, 0.0]), ] .to_vec(); validate_vec_vertex_relative_eq(&val, &expected); } #[test] fn vertexes_on_circle_complex() { let val = vertexes_on_circle( &Layer { center: Vertex::new([4.0, 5.0, 6.0]), radius: 3.0, }, 4, ); let expected = [ Vertex::new([7.0, 5.0, 6.0]), Vertex::new([4.0, 8.0, 6.0]), Vertex::new([1.0, 5.0, 6.0]), Vertex::new([4.0, 2.0, 6.0]), ] .to_vec(); validate_vec_vertex_relative_eq(&val, &expected); } #[test] fn tube_base_test() { // fn tube_base(center: &Vertex, edge: &Vec) -> Vec { let center = Vertex::new([0.0, 0.0, 0.0]); let edge = vertexes_on_circle( &Layer { center: center.clone(), radius: 1.0, }, 4, ); let triangles = tube_base(¢er, &edge); assert_eq!(triangles.len(), edge.len()); for t in triangles { // All of these triangles should have the same normal (down). assert_eq!(t.normal, Vertex::new([0.0, 0.0, -1.0])); } } #[test] fn tube_top_test() { let center = Vertex::new([0.0, 0.0, 0.0]); let edge = vertexes_on_circle( &Layer { center: center.clone(), radius: 1.0, }, 4, ); let triangles = tube_top(¢er, &edge); assert_eq!(triangles.len(), edge.len()); for t in triangles { // All of these triangles should have the same normal (up). assert_eq!(t.normal, Vertex::new([0.0, 0.0, 1.0])); } } #[test] fn render_test() { let layers = Layers { base: Vertex::new([0.0, 0.0, 0.0]), layers: [ Layer { center: Vertex::new([0.0, 0.0, 1.0]), radius: 1.0, }, Layer { center: Vertex::new([0.0, 0.0, 2.0]), radius: 1.0, }, ] .to_vec(), top: Vertex::new([0.0, 0.0, 3.0]), }; let mesh = render(&layers, 10.0); // Large resolution ensures minimum steps of 64. // 64 triangles for top, 128 for sides, 64 for bottom. assert_eq!(mesh.len(), 64 + 128 + 64); for i in 0..mesh.len() { if i < 64 { assert!(mesh[i].normal[2] < 0.0) // Bottom triangles point down. } else if i < (64 + 128) { assert!(mesh[i].normal[2] == 0.0) // side triangles point out. } else { assert!(mesh[i].normal[2] > 0.0) // top triangles point up. } } } }