extern crate cgmath; extern crate genmesh; use std::collections::HashSet; use cgmath::InnerSpace; use genmesh::{generators, EmitLines, Line, Lines, MapToVertices, Vertex}; #[derive(Debug)] struct Edge { dir: cgmath::Vector3, mid: cgmath::Vector3, nor: cgmath::Vector3, } impl Edge { fn new(line: Line) -> Self { let Line { x: Vertex { pos: x, normal: nx }, y: Vertex { pos: y, normal: ny }, } = line; Edge { dir: cgmath::vec3(y.x - x.x, y.y - x.y, y.z - x.z), mid: cgmath::vec3(y.x + x.x, y.y + x.y, y.z + x.z) * 0.5, nor: cgmath::vec3(nx.x + ny.x, nx.y + ny.y, nx.z + ny.z), } } /// Check that the corner `(self, e)` has outward winding order /// (thus, it's normal is in the same hemisphere as it's offset). fn check_to(&self, e: &Edge) { let normal = self.dir.cross(e.dir); let mid = (self.mid + e.mid) * 0.5; assert!(normal.dot(mid) > 0.0 && e.nor.dot(mid) > 0.0); } } /// Make sure that all the polygons in the `poly_iter` have the outward /// winding order relative to the origin of the coordinate system. /// This is a simplified (and incomplete) convex shape test. fn test_outward(poly_iter: I) where P: EmitLines + ::std::fmt::Debug, I: Iterator, { let mut edges = Vec::new(); for poly in poly_iter { edges.clear(); poly.emit_lines(|l| edges.push(Edge::new(l))); // check last-first corner first, since it wraps edges.last().unwrap().check_to(&edges[0]); // check all the non-wrapping corners for (a, b) in edges.iter().zip(edges[1..].iter()) { a.check_to(b); } } } // this will check that a primitive is closed, and that every edge of // every polygon making up the primitive has a paired neighbor. This // does not test that a polygon has the correcting winding direction // just that the winding directions are consistent for the polygon. // // This is based on ftp://ftp.sgi.com/opengl/contrib/blythe/advanced99/notes/node16.html // fn test_closed(poly_iter: I) where P: EmitLines + ::std::fmt::Debug, I: Iterator, { // convert the vertex to something that we can use to find approximate // polygons. This is mostly to get past the fact that f32 is a cursed // type in rust and can not be used as a key. fn to_checkable(vertex: Vertex) -> [i32; 3] { [ (vertex.pos.x * 1000000.) as i32, (vertex.pos.y * 1000000.) as i32, (vertex.pos.z * 1000000.) as i32, ] } let mut lines = HashSet::new(); for line in poly_iter.lines().vertex(to_checkable) { // if the line was in the set we found the matching pair // which is one less pair that we are looking for if !lines.remove(&line) { // if didn't find the pair, we flip the line around and put it into // the search table. lines.insert(Line { x: line.y, y: line.x, }); } } // if we found all the pairs, we should be valid and a closed geometry // this means that there is no polygon who's neighbor switches winding // direction, but it does not mean that the polygon is correct. They // all could be backwards. So this still requires a secondary inspection. assert!(lines.len() == 0); } #[test] fn wind_plane() { // the plane is not closed, so no point in testing for a closed // shape. test_outward(generators::Plane::new().vertex(|mut v| { v.pos.z = 1.; v })); test_outward(generators::Plane::subdivide(3, 4).vertex(|mut v| { v.pos.z = 1.; v })); } #[test] fn gen_cube() { test_outward(generators::Cube::new()); test_closed(generators::Cube::new()); } #[test] fn gen_cylinder() { test_outward(generators::Cylinder::new(5)); test_closed(generators::Cylinder::new(5)); test_outward(generators::Cylinder::subdivide(3, 4)); test_closed(generators::Cylinder::subdivide(3, 4)); } #[test] fn gen_sphere_uv() { test_outward(generators::SphereUv::new(4, 3)); test_closed(generators::SphereUv::new(4, 3)); } #[test] fn gen_ico_sphere() { test_outward(generators::IcoSphere::new()); test_closed(generators::IcoSphere::new()); test_outward(generators::IcoSphere::subdivide(3)); test_closed(generators::IcoSphere::subdivide(3)); } #[test] fn gen_cone() { test_outward(generators::Cone::new(8)); test_closed(generators::Cone::new(8)); } #[test] fn gen_torus() { // we don't do an outward test because the primitive is not // convex and will fail this test. test_closed(generators::Torus::new(10.0, 5.0, 8, 8)); }