Attempt to organize into modules, though still pretty rough

This commit is contained in:
Chris Hodapp 2020-02-17 14:29:43 -05:00
parent e3e7391cea
commit cae2d3df10
7 changed files with 422 additions and 396 deletions

View File

@ -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?

149
src/examples.rs Normal file
View File

@ -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<Vertex> = 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<Mat4> = 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");
}

8
src/lib.rs Normal file
View File

@ -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;

View File

@ -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<f32>;
pub type Mat4 = Matrix4<f32>;
/// 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<Vertex>,
// Triangles, taken as every 3 values, treated each as indices
// into 'verts':
faces: Vec<Tag>,
exit_groups: Vec<usize>,
}
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<W: std::io::Write>(&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>) -> OpenMesh {
// Copy body vertices & faces:
let mut verts: Vec<Vertex> = self.verts.clone();
let mut faces = self.faces.clone();
let mut exit_groups: Vec<usize> = vec![];
let mut body_offset = self.verts.len();
let mut exit_offset = 0;
let mut offsets: Vec<usize> = 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<Rule> 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<OpenMesh> = 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<Vertex> = 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<Mat4> = 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();
}

140
src/openmesh.rs Normal file
View File

@ -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<f32>;
pub type Mat4 = Matrix4<f32>;
/// 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<Vertex>,
// Triangles, taken as every 3 values, treated each as indices
// into 'verts':
pub faces: Vec<Tag>,
pub exit_groups: Vec<usize>,
}
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<W: std::io::Write>(&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>) -> OpenMesh {
// Copy body vertices & faces:
let mut verts: Vec<Vertex> = self.verts.clone();
let mut faces = self.faces.clone();
let mut exit_groups: Vec<usize> = vec![];
let mut body_offset = self.verts.len();
let mut exit_offset = 0;
let mut offsets: Vec<usize> = 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,
}
}
}

41
src/prim.rs Normal file
View File

@ -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())
}

82
src/rule.rs Normal file
View File

@ -0,0 +1,82 @@
use crate::openmesh::{OpenMesh, Mat4};
use crate::prim;
// TODO: Do I benefit with Rc<Rule> 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<OpenMesh> = 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);
}
}
}
}