From cae2d3df1049ed068ff4dc8a3c5ed51ad35bc7d4 Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Mon, 17 Feb 2020 14:29:43 -0500 Subject: [PATCH] Attempt to organize into modules, though still pretty rough --- README.md | 1 + src/examples.rs | 149 ++++++++++++++++++ src/lib.rs | 8 + src/main.rs | 397 +----------------------------------------------- src/openmesh.rs | 140 +++++++++++++++++ src/prim.rs | 41 +++++ src/rule.rs | 82 ++++++++++ 7 files changed, 422 insertions(+), 396 deletions(-) create mode 100644 src/examples.rs create mode 100644 src/lib.rs create mode 100644 src/openmesh.rs create mode 100644 src/prim.rs create mode 100644 src/rule.rs diff --git a/README.md b/README.md index 2f22774..17f27da 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ## Highest priority: +- Try a more complex case with multiple exit groups - Consider trampolining `to_mesh`. My call stack seems needlessly deep in spots. Can I make tail-recursive? diff --git a/src/examples.rs b/src/examples.rs new file mode 100644 index 0000000..6d05829 --- /dev/null +++ b/src/examples.rs @@ -0,0 +1,149 @@ +use nalgebra::*; +//pub mod examples; + +use crate::openmesh::{OpenMesh, Tag, Mat4, Vertex, vertex}; +use crate::rule::{Rule, RuleStep}; +use crate::prim; + +fn curve_horn_start() -> RuleStep { + let id = nalgebra::geometry::Transform3::identity().to_homogeneous(); + let flip180 = nalgebra::geometry::Rotation3::from_axis_angle( + &nalgebra::Vector3::y_axis(), + std::f32::consts::PI).to_homogeneous(); + RuleStep { + geom: OpenMesh { + verts: vec![], + faces: vec![ + Tag::Exit(1, 0), Tag::Exit(1, 2), Tag::Exit(0, 1), + Tag::Exit(1, 2), Tag::Exit(0, 3), Tag::Exit(0, 1), + Tag::Exit(0, 0), Tag::Exit(0, 2), Tag::Exit(1, 1), + Tag::Exit(0, 2), Tag::Exit(1, 3), Tag::Exit(1, 1), + Tag::Exit(0, 3), Tag::Exit(1, 2), Tag::Exit(0, 2), + Tag::Exit(1, 2), Tag::Exit(1, 3), Tag::Exit(0, 2), + Tag::Exit(1, 0), Tag::Exit(0, 1), Tag::Exit(0, 0), + Tag::Exit(1, 1), Tag::Exit(1, 0), Tag::Exit(0, 0), + // The above is connecting group 0 to group 1, + // straight across + with diagonal - but with group 1 + // being flipped 180, so we remap vertices (0,1,2,3) + // to (1,0,3,2) and then flip winding order. + ], + exit_groups: vec![4, 4], + }, + final_geom: prim::empty_mesh(), + children: vec![ + (Rule::Recurse(curve_horn_thing_rule), id), // exit group 0 + (Rule::Recurse(curve_horn_thing_rule), flip180), // exit group 1 + ], + } + // TODO: The starting vertices above are duplicated because I + // don't have any way for an exit vertex to stand in for multiple + // child vertices that happen to share the same location. I don't + // yet know a good way around this, so I am duplicating vertices. +} + +fn curve_horn_thing_rule() -> RuleStep { + + let y = &Vector3::y_axis(); + + let m: Mat4 = geometry::Rotation3::from_axis_angle(y, 0.1).to_homogeneous() * + Matrix4::new_scaling(0.95) * + geometry::Translation3::new(0.0, 0.0, 0.2).to_homogeneous(); + + let verts = vec![ + vertex(-0.5, -0.5, 0.0), + vertex(0.5, -0.5, 0.0), + vertex(-0.5, 0.5, 0.0), + vertex(0.5, 0.5, 0.0), + ]; + let final_verts: Vec = verts.iter().map(|v| m * v).collect(); + + let geom = OpenMesh { + verts: verts, + faces: vec![ + // The below is just connecting two groups of 4 vertices + // each, straight across and then to the next. Note that + // since 'verts' doesn't go in a circle, it will look a + // little strange. + Tag::Body(1), Tag::Exit(0, 3), Tag::Exit(0, 1), + Tag::Body(1), Tag::Body(3), Tag::Exit(0, 3), + Tag::Exit(0, 0), Tag::Body(2), Tag::Body(0), + Tag::Exit(0, 0), Tag::Exit(0, 2), Tag::Body(2), + Tag::Body(2), Tag::Exit(0, 3), Tag::Body(3), + Tag::Body(2), Tag::Exit(0, 2), Tag::Exit(0, 3), + Tag::Body(0), Tag::Body(1), Tag::Exit(0, 1), + Tag::Body(0), Tag::Exit(0, 1), Tag::Exit(0, 0), + // TODO: I should really generate these, not hard-code them. + ], + exit_groups: vec![4], + }; + + // TODO: This could be made slightly nicer by taking it to a peak + // instead of just flattening it in XY, but this is a pretty minor + // change. + let final_geom = OpenMesh { + verts: final_verts, + faces: vec![ + Tag::Body(0), Tag::Body(1), Tag::Body(3), + Tag::Body(0), Tag::Body(3), Tag::Body(2), + ], + exit_groups: vec![], + }; + + RuleStep{ + geom: geom, + final_geom: final_geom, + children: vec![ + (Rule::Recurse(curve_horn_thing_rule), m), // exit group 0 + ], + } +} + +fn cube_thing_rule() -> RuleStep { + + let mesh = prim::cube(); + + // Quarter-turn in radians: + let qtr = std::f32::consts::FRAC_PI_2; + + let y = &Vector3::y_axis(); + let z = &Vector3::z_axis(); + + // Each element of this turns to a branch for the recursion: + let turns: Vec = vec![ + geometry::Transform3::identity().to_homogeneous(), + geometry::Rotation3::from_axis_angle(y, qtr).to_homogeneous(), + geometry::Rotation3::from_axis_angle(y, qtr * 2.0).to_homogeneous(), + geometry::Rotation3::from_axis_angle(y, qtr * 3.0).to_homogeneous(), + geometry::Rotation3::from_axis_angle(z, qtr).to_homogeneous(), + geometry::Rotation3::from_axis_angle(z, -qtr).to_homogeneous(), + ]; + + let gen_rulestep = |rot: &Mat4| -> (Rule, Mat4) { + let m: Mat4 = rot * + Matrix4::new_scaling(0.5) * + geometry::Translation3::new(6.0, 0.0, 0.0).to_homogeneous(); + (Rule::Recurse(cube_thing_rule), m) + }; + + RuleStep { + geom: mesh, + final_geom: prim::empty_mesh(), // no exit groups + children: turns.iter().map(gen_rulestep).collect(), + } +} + +pub fn main() { + + let run_test = |r: Rule, iters, name| { + println!("Running {}...", name); + let (mesh, nodes) = r.to_mesh(iters); + println!("Merged {} nodes", nodes); + let fname = format!("{}.stl", name); + println!("Writing {}...", fname); + mesh.write_stl_file(&fname).unwrap(); + }; + + run_test(Rule::Recurse(cube_thing_rule), 3, "cube_thing"); + run_test(Rule::Recurse(curve_horn_thing_rule), 100, "curve_horn_thing"); + run_test(Rule::Recurse(curve_horn_start), 100, "curve_horn2"); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9fb430e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +pub mod examples; +pub mod openmesh; +pub mod rule; +pub mod prim; + +//pub use crate::examples; +//pub use crate::openmesh::test_thing; + diff --git a/src/main.rs b/src/main.rs index f2d1c3c..71c2f07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,398 +1,3 @@ -use nalgebra::*; -use std::fs::OpenOptions; -use std::io; - -/// A type for custom mesh vertices. Initialize with [vertex][self::vertex]. -pub type Vertex = Vector4; -pub type Mat4 = Matrix4; - -/// Initializes a vertex: -pub fn vertex(x: f32, y: f32, z: f32) -> Vertex { - Vertex::new(x, y, z, 1.0) -} - -#[derive(Clone, Debug)] -enum Tag { - Body(usize), - Exit(usize, usize), // (group, vertex) -} - -#[derive(Clone, Debug)] -struct OpenMesh { - // Vertices (in homogeneous coordinates). - verts: Vec, - // Triangles, taken as every 3 values, treated each as indices - // into 'verts': - faces: Vec, - exit_groups: Vec, -} - -impl OpenMesh { - - fn transform(&self, xfm: Mat4) -> OpenMesh { - OpenMesh { - verts: self.verts.iter().map(|v| xfm * v).collect(), - // TODO: Is the above faster if I pack vectors into a - // bigger matrix, and transform that? - faces: self.faces.clone(), // TODO: Use Rc? - exit_groups: self.exit_groups.clone(), - } - } - - fn write_stl_file(&self, fname: &str) -> io::Result<()> { - let mut file = OpenOptions::new().write(true).create(true).truncate(true).open(fname)?; - self.write_stl(&mut file) - } - - fn write_stl(&self, writer: &mut W) -> io::Result<()> { - - // Every group of 3 indices in self.faces is one triangle, so - // pre-allocate in the format stl_io wants: - let num_faces = self.faces.len() / 3; - let mut triangles = vec![stl_io::Triangle { - normal: [0.0; 3], - vertices: [[0.0; 3]; 3], - }; num_faces]; - - let get_vert = |j| { - match self.faces[j] { - Tag::Body(n) => self.verts[n].xyz(), - Tag::Exit(_, _) => panic!("Cannot write_stl() if mesh has exit groups!"), - } - }; - // TODO: Handle this behavior - - // Turn every face into an stl_io::Triangle: - for i in 0..num_faces { - let v0 = get_vert(3*i + 0); - let v1 = get_vert(3*i + 1); - let v2 = get_vert(3*i + 2); - - let normal = (v1-v0).cross(&(v2-v0)); - - triangles[i].normal.copy_from_slice(&normal.as_slice()); - triangles[i].vertices[0].copy_from_slice(v0.as_slice()); - triangles[i].vertices[1].copy_from_slice(v1.as_slice()); - triangles[i].vertices[2].copy_from_slice(v2.as_slice()); - // TODO: Is there a cleaner way to do the above? - } - - // I could also solve this with something like - // https://doc.rust-lang.org/std/primitive.slice.html#method.chunks_exact - // however I don't know what performance difference may be. - - stl_io::write_stl(writer, triangles.iter()) - } - - fn connect(&self, others: &Vec) -> OpenMesh { - - // Copy body vertices & faces: - let mut verts: Vec = self.verts.clone(); - let mut faces = self.faces.clone(); - - let mut exit_groups: Vec = vec![]; - - let mut body_offset = self.verts.len(); - let mut exit_offset = 0; - let mut offsets: Vec = vec![0; others.len()]; - for (i,other) in others.iter().enumerate() { - - // Append body vertices & exit vertices directly: - verts.append(&mut other.verts.clone()); - - // Append faces, shifting each kind by respective offset: - faces.extend(other.faces.iter().map(|t| { - match t { - Tag::Body(n) => Tag::Body(n + body_offset), - Tag::Exit(g, n) => Tag::Exit(g + exit_groups.len(), n + exit_offset), - } - })); - if i < self.exit_groups.len() { - exit_offset += self.exit_groups[i]; - } - exit_groups.append(&mut other.exit_groups.clone()); - - offsets[i] = body_offset; - // Increase offsets by the number of elements we appended: - body_offset += other.verts.len(); - } - - // All of the Exit face indices from 'self' need to be - // modified to refer to Body vertices of something in - // 'others': - for i in 0..faces.len() { - match faces[i] { - Tag::Exit(g, n) => { - faces[i] = Tag::Body(n + offsets[g]); - }, - _ => { }, - }; - } - - OpenMesh { - verts: verts, - faces: faces, - exit_groups: exit_groups, - } - } -} - -// TODO: Do I benefit with Rc below so Rule can be shared? -enum Rule { - // Produce geometry, and possibly recurse further: - Recurse(fn () -> RuleStep), - // Stop recursing here: - EmptyRule, -} -// TODO: Rename rules? -// TODO: It may be possible to have just a 'static' rule that requires -// no function call. - -struct RuleStep { - // The geometry generated by this rule on its own (not by any of - // the child rules). - geom: OpenMesh, - - // The "final" geometry, used only if recursion must be stopped. - // This should be in the same coordinate space as 'geom', and - // properly close any exit groups that it may have (and have no - // exit groups of its own). - final_geom: OpenMesh, - - // Child rules, paired with the transform that will be applied to - // all of their geometry - children: Vec<(Rule, Mat4)>, -} - -impl Rule { - - // TODO: Do I want to make 'geom' shared somehow, maybe with Rc? I - // could end up having a lot of identical geometry that need not be - // duplicated until it is transformed into the global space. - // - // This might produce bigger gains if I rewrite rule_to_mesh so that - // rather than repeatedly transforming meshes, it stacks - // transformations and then applies them all at once. - - fn to_mesh(&self, iters_left: u32) -> (OpenMesh, u32) { - - let mut nodes: u32 = 1; - - if iters_left <= 0 { - match self { - Rule::Recurse(f) => { - let rs: RuleStep = f(); - return (rs.final_geom, 1); - } - Rule::EmptyRule => { - return (empty_mesh(), nodes); - } - } - } - - match self { - Rule::Recurse(f) => { - let rs: RuleStep = f(); - - // Get sub-geometry (from child rules) and transform it: - let subgeom: Vec<(OpenMesh, Mat4, u32)> = rs.children.iter().map(|(subrule, subxform)| { - let (m,n) = subrule.to_mesh(iters_left - 1); - (m, *subxform, n) - }).collect(); - - // Tally up node count: - subgeom.iter().for_each(|(_,_,n)| nodes += n); - - let g: Vec = subgeom.iter().map(|(m,x,_)| m.transform(*x)).collect(); - - // Connect geometry from this rule (not child rules): - return (rs.geom.connect(&g), nodes); - } - Rule::EmptyRule => { - return (empty_mesh(), nodes); - } - } - } -} - -// is there a better way to do this? -fn empty_mesh() -> OpenMesh { - OpenMesh { - verts: vec![], - faces: vec![], - exit_groups: vec![], - } -} - -fn cube() -> OpenMesh { - OpenMesh { - verts: vec![ - vertex(0.0, 0.0, 0.0), - vertex(1.0, 0.0, 0.0), - vertex(0.0, 1.0, 0.0), - vertex(1.0, 1.0, 0.0), - vertex(0.0, 0.0, 1.0), - vertex(1.0, 0.0, 1.0), - vertex(0.0, 1.0, 1.0), - vertex(1.0, 1.0, 1.0), - ], - faces: vec![ - Tag::Body(0), Tag::Body(3), Tag::Body(1), - Tag::Body(0), Tag::Body(2), Tag::Body(3), - Tag::Body(1), Tag::Body(7), Tag::Body(5), - Tag::Body(1), Tag::Body(3), Tag::Body(7), - Tag::Body(5), Tag::Body(6), Tag::Body(4), - Tag::Body(5), Tag::Body(7), Tag::Body(6), - Tag::Body(4), Tag::Body(2), Tag::Body(0), - Tag::Body(4), Tag::Body(6), Tag::Body(2), - Tag::Body(2), Tag::Body(7), Tag::Body(3), - Tag::Body(2), Tag::Body(6), Tag::Body(7), - Tag::Body(0), Tag::Body(1), Tag::Body(5), - Tag::Body(0), Tag::Body(5), Tag::Body(4), - ], - exit_groups: vec![], - }.transform(geometry::Translation3::new(-0.5, -0.5, -0.5).to_homogeneous()) -} - -fn curve_horn_start() -> RuleStep { - let id = nalgebra::geometry::Transform3::identity().to_homogeneous(); - let flip180 = nalgebra::geometry::Rotation3::from_axis_angle( - &nalgebra::Vector3::y_axis(), - std::f32::consts::PI).to_homogeneous(); - RuleStep { - geom: OpenMesh { - verts: vec![], - faces: vec![ - Tag::Exit(1, 0), Tag::Exit(1, 2), Tag::Exit(0, 1), - Tag::Exit(1, 2), Tag::Exit(0, 3), Tag::Exit(0, 1), - Tag::Exit(0, 0), Tag::Exit(0, 2), Tag::Exit(1, 1), - Tag::Exit(0, 2), Tag::Exit(1, 3), Tag::Exit(1, 1), - Tag::Exit(0, 3), Tag::Exit(1, 2), Tag::Exit(0, 2), - Tag::Exit(1, 2), Tag::Exit(1, 3), Tag::Exit(0, 2), - Tag::Exit(1, 0), Tag::Exit(0, 1), Tag::Exit(0, 0), - Tag::Exit(1, 1), Tag::Exit(1, 0), Tag::Exit(0, 0), - // The above is connecting group 0 to group 1, - // straight across + with diagonal - but with group 1 - // being flipped 180, so we remap vertices (0,1,2,3) - // to (1,0,3,2) and then flip winding order. - ], - exit_groups: vec![4, 4], - }, - final_geom: empty_mesh(), - children: vec![ - (Rule::Recurse(curve_horn_thing_rule), id), // exit group 0 - (Rule::Recurse(curve_horn_thing_rule), flip180), // exit group 1 - ], - } - // TODO: The starting vertices above are duplicated because I - // don't have any way for an exit vertex to stand in for multiple - // child vertices that happen to share the same location. I don't - // yet know a good way around this, so I am duplicating vertices. -} - -fn curve_horn_thing_rule() -> RuleStep { - - let y = &Vector3::y_axis(); - - let m: Mat4 = geometry::Rotation3::from_axis_angle(y, 0.1).to_homogeneous() * - Matrix4::new_scaling(0.95) * - geometry::Translation3::new(0.0, 0.0, 0.2).to_homogeneous(); - - let verts = vec![ - vertex(-0.5, -0.5, 0.0), - vertex(0.5, -0.5, 0.0), - vertex(-0.5, 0.5, 0.0), - vertex(0.5, 0.5, 0.0), - ]; - let final_verts: Vec = verts.iter().map(|v| m * v).collect(); - - let geom = OpenMesh { - verts: verts, - faces: vec![ - // The below is just connecting two groups of 4 vertices - // each, straight across and then to the next. Note that - // since 'verts' doesn't go in a circle, it will look a - // little strange. - Tag::Body(1), Tag::Exit(0, 3), Tag::Exit(0, 1), - Tag::Body(1), Tag::Body(3), Tag::Exit(0, 3), - Tag::Exit(0, 0), Tag::Body(2), Tag::Body(0), - Tag::Exit(0, 0), Tag::Exit(0, 2), Tag::Body(2), - Tag::Body(2), Tag::Exit(0, 3), Tag::Body(3), - Tag::Body(2), Tag::Exit(0, 2), Tag::Exit(0, 3), - Tag::Body(0), Tag::Body(1), Tag::Exit(0, 1), - Tag::Body(0), Tag::Exit(0, 1), Tag::Exit(0, 0), - // TODO: I should really generate these, not hard-code them. - ], - exit_groups: vec![4], - }; - - // TODO: This could be made slightly nicer by taking it to a peak - // instead of just flattening it in XY, but this is a pretty minor - // change. - let final_geom = OpenMesh { - verts: final_verts, - faces: vec![ - Tag::Body(0), Tag::Body(1), Tag::Body(3), - Tag::Body(0), Tag::Body(3), Tag::Body(2), - ], - exit_groups: vec![], - }; - - RuleStep{ - geom: geom, - final_geom: final_geom, - children: vec![ - (Rule::Recurse(curve_horn_thing_rule), m), // exit group 0 - ], - } -} - -fn cube_thing_rule() -> RuleStep { - - let mesh = cube(); - - // Quarter-turn in radians: - let qtr = std::f32::consts::FRAC_PI_2; - - let y = &Vector3::y_axis(); - let z = &Vector3::z_axis(); - - // Each element of this turns to a branch for the recursion: - let turns: Vec = vec![ - geometry::Transform3::identity().to_homogeneous(), - geometry::Rotation3::from_axis_angle(y, qtr).to_homogeneous(), - geometry::Rotation3::from_axis_angle(y, qtr * 2.0).to_homogeneous(), - geometry::Rotation3::from_axis_angle(y, qtr * 3.0).to_homogeneous(), - geometry::Rotation3::from_axis_angle(z, qtr).to_homogeneous(), - geometry::Rotation3::from_axis_angle(z, -qtr).to_homogeneous(), - ]; - - let gen_rulestep = |rot: &Mat4| -> (Rule, Mat4) { - let m: Mat4 = rot * - Matrix4::new_scaling(0.5) * - geometry::Translation3::new(6.0, 0.0, 0.0).to_homogeneous(); - (Rule::Recurse(cube_thing_rule), m) - }; - - RuleStep { - geom: mesh, - final_geom: empty_mesh(), // no exit groups - children: turns.iter().map(gen_rulestep).collect(), - } -} - fn main() { - - let run_test = |r: Rule, iters, name| { - println!("Running {}...", name); - let (mesh, nodes) = r.to_mesh(iters); - println!("Merged {} nodes", nodes); - let fname = format!("{}.stl", name); - println!("Writing {}...", fname); - mesh.write_stl_file(&fname).unwrap(); - }; - - run_test(Rule::Recurse(cube_thing_rule), 3, "cube_thing"); - run_test(Rule::Recurse(curve_horn_thing_rule), 100, "curve_horn_thing"); - run_test(Rule::Recurse(curve_horn_start), 100, "curve_horn2"); + mesh_scratch::examples::main(); } diff --git a/src/openmesh.rs b/src/openmesh.rs new file mode 100644 index 0000000..925f63c --- /dev/null +++ b/src/openmesh.rs @@ -0,0 +1,140 @@ +//pub mod openmesh; + +use nalgebra::*; +use std::fs::OpenOptions; +use std::io; + +/// A type for custom mesh vertices. Initialize with [vertex][self::vertex]. +pub type Vertex = Vector4; +pub type Mat4 = Matrix4; + +/// Initializes a vertex: +pub fn vertex(x: f32, y: f32, z: f32) -> Vertex { + Vertex::new(x, y, z, 1.0) +} + +#[derive(Clone, Debug)] +pub enum Tag { + Body(usize), + Exit(usize, usize), // (group, vertex) +} + +#[derive(Clone, Debug)] +pub struct OpenMesh { + // Vertices (in homogeneous coordinates). + pub verts: Vec, + // Triangles, taken as every 3 values, treated each as indices + // into 'verts': + pub faces: Vec, + pub exit_groups: Vec, +} + +impl OpenMesh { + + pub fn transform(&self, xfm: Mat4) -> OpenMesh { + OpenMesh { + verts: self.verts.iter().map(|v| xfm * v).collect(), + // TODO: Is the above faster if I pack vectors into a + // bigger matrix, and transform that? + faces: self.faces.clone(), // TODO: Use Rc? + exit_groups: self.exit_groups.clone(), + } + } + + pub fn write_stl_file(&self, fname: &str) -> io::Result<()> { + let mut file = OpenOptions::new().write(true).create(true).truncate(true).open(fname)?; + self.write_stl(&mut file) + } + + fn write_stl(&self, writer: &mut W) -> io::Result<()> { + + // Every group of 3 indices in self.faces is one triangle, so + // pre-allocate in the format stl_io wants: + let num_faces = self.faces.len() / 3; + let mut triangles = vec![stl_io::Triangle { + normal: [0.0; 3], + vertices: [[0.0; 3]; 3], + }; num_faces]; + + let get_vert = |j| { + match self.faces[j] { + Tag::Body(n) => self.verts[n].xyz(), + Tag::Exit(_, _) => panic!("Cannot write_stl() if mesh has exit groups!"), + } + }; + // TODO: Handle this behavior + + // Turn every face into an stl_io::Triangle: + for i in 0..num_faces { + let v0 = get_vert(3*i + 0); + let v1 = get_vert(3*i + 1); + let v2 = get_vert(3*i + 2); + + let normal = (v1-v0).cross(&(v2-v0)); + + triangles[i].normal.copy_from_slice(&normal.as_slice()); + triangles[i].vertices[0].copy_from_slice(v0.as_slice()); + triangles[i].vertices[1].copy_from_slice(v1.as_slice()); + triangles[i].vertices[2].copy_from_slice(v2.as_slice()); + // TODO: Is there a cleaner way to do the above? + } + + // I could also solve this with something like + // https://doc.rust-lang.org/std/primitive.slice.html#method.chunks_exact + // however I don't know what performance difference may be. + + stl_io::write_stl(writer, triangles.iter()) + } + + pub fn connect(&self, others: &Vec) -> OpenMesh { + + // Copy body vertices & faces: + let mut verts: Vec = self.verts.clone(); + let mut faces = self.faces.clone(); + + let mut exit_groups: Vec = vec![]; + + let mut body_offset = self.verts.len(); + let mut exit_offset = 0; + let mut offsets: Vec = vec![0; others.len()]; + for (i,other) in others.iter().enumerate() { + + // Append body vertices & exit vertices directly: + verts.append(&mut other.verts.clone()); + + // Append faces, shifting each kind by respective offset: + faces.extend(other.faces.iter().map(|t| { + match t { + Tag::Body(n) => Tag::Body(n + body_offset), + Tag::Exit(g, n) => Tag::Exit(g + exit_groups.len(), n + exit_offset), + } + })); + if i < self.exit_groups.len() { + exit_offset += self.exit_groups[i]; + } + exit_groups.append(&mut other.exit_groups.clone()); + + offsets[i] = body_offset; + // Increase offsets by the number of elements we appended: + body_offset += other.verts.len(); + } + + // All of the Exit face indices from 'self' need to be + // modified to refer to Body vertices of something in + // 'others': + for i in 0..faces.len() { + match faces[i] { + Tag::Exit(g, n) => { + faces[i] = Tag::Body(n + offsets[g]); + }, + _ => { }, + }; + } + + OpenMesh { + verts: verts, + faces: faces, + exit_groups: exit_groups, + } + } +} diff --git a/src/prim.rs b/src/prim.rs new file mode 100644 index 0000000..1f2915c --- /dev/null +++ b/src/prim.rs @@ -0,0 +1,41 @@ +use nalgebra::*; +use crate::openmesh::{OpenMesh, Tag, vertex}; + +// is there a better way to do this? +pub fn empty_mesh() -> OpenMesh { + OpenMesh { + verts: vec![], + faces: vec![], + exit_groups: vec![], + } +} + +pub fn cube() -> OpenMesh { + OpenMesh { + verts: vec![ + vertex(0.0, 0.0, 0.0), + vertex(1.0, 0.0, 0.0), + vertex(0.0, 1.0, 0.0), + vertex(1.0, 1.0, 0.0), + vertex(0.0, 0.0, 1.0), + vertex(1.0, 0.0, 1.0), + vertex(0.0, 1.0, 1.0), + vertex(1.0, 1.0, 1.0), + ], + faces: vec![ + Tag::Body(0), Tag::Body(3), Tag::Body(1), + Tag::Body(0), Tag::Body(2), Tag::Body(3), + Tag::Body(1), Tag::Body(7), Tag::Body(5), + Tag::Body(1), Tag::Body(3), Tag::Body(7), + Tag::Body(5), Tag::Body(6), Tag::Body(4), + Tag::Body(5), Tag::Body(7), Tag::Body(6), + Tag::Body(4), Tag::Body(2), Tag::Body(0), + Tag::Body(4), Tag::Body(6), Tag::Body(2), + Tag::Body(2), Tag::Body(7), Tag::Body(3), + Tag::Body(2), Tag::Body(6), Tag::Body(7), + Tag::Body(0), Tag::Body(1), Tag::Body(5), + Tag::Body(0), Tag::Body(5), Tag::Body(4), + ], + exit_groups: vec![], + }.transform(geometry::Translation3::new(-0.5, -0.5, -0.5).to_homogeneous()) +} diff --git a/src/rule.rs b/src/rule.rs new file mode 100644 index 0000000..986d2e7 --- /dev/null +++ b/src/rule.rs @@ -0,0 +1,82 @@ +use crate::openmesh::{OpenMesh, Mat4}; +use crate::prim; + +// TODO: Do I benefit with Rc below so Rule can be shared? +pub enum Rule { + // Produce geometry, and possibly recurse further: + Recurse(fn () -> RuleStep), + // Stop recursing here: + EmptyRule, +} +// TODO: Rename rules? +// TODO: It may be possible to have just a 'static' rule that requires +// no function call. + +pub struct RuleStep { + // The geometry generated by this rule on its own (not by any of + // the child rules). + pub geom: OpenMesh, + + // The "final" geometry, used only if recursion must be stopped. + // This should be in the same coordinate space as 'geom', and + // properly close any exit groups that it may have (and have no + // exit groups of its own). + pub final_geom: OpenMesh, + + // Child rules, paired with the transform that will be applied to + // all of their geometry + pub children: Vec<(Rule, Mat4)>, +} + +impl Rule { + + // TODO: Do I want to make 'geom' shared somehow, maybe with Rc? I + // could end up having a lot of identical geometry that need not be + // duplicated until it is transformed into the global space. + // + // This might produce bigger gains if I rewrite rule_to_mesh so that + // rather than repeatedly transforming meshes, it stacks + // transformations and then applies them all at once. + + pub fn to_mesh(&self, iters_left: u32) -> (OpenMesh, u32) { + + + + let mut nodes: u32 = 1; + + if iters_left <= 0 { + match self { + Rule::Recurse(f) => { + let rs: RuleStep = f(); + return (rs.final_geom, 1); + } + Rule::EmptyRule => { + return (prim::empty_mesh(), nodes); + } + } + } + + match self { + Rule::Recurse(f) => { + let rs: RuleStep = f(); + + // Get sub-geometry (from child rules) and transform it: + let subgeom: Vec<(OpenMesh, Mat4, u32)> = rs.children.iter().map(|(subrule, subxform)| { + let (m,n) = subrule.to_mesh(iters_left - 1); + (m, *subxform, n) + }).collect(); + + // Tally up node count: + subgeom.iter().for_each(|(_,_,n)| nodes += n); + + let g: Vec = subgeom.iter().map(|(m,x,_)| m.transform(*x)).collect(); + + // Connect geometry from this rule (not child rules): + return (rs.geom.connect(&g), nodes); + } + Rule::EmptyRule => { + return (prim::empty_mesh(), nodes); + } + } + } +}