Add some more docs

This commit is contained in:
Chris Hodapp 2020-03-06 08:54:08 -05:00
parent 2fe37fc569
commit 7fd89b63b8
3 changed files with 75 additions and 43 deletions

View File

@ -2,12 +2,18 @@
## Highest priority: ## Highest priority:
- Continue fixing `to_mesh_iter`, which still doesn't yet handle
branching because (for one thing) it never tracks depth properly in
order to backtrack.
- Clean up `twist` - maybe make a struct or trait. - Clean up `twist` - maybe make a struct or trait.
- Do transforms compose in the *reverse* of automata_scratch? This - Do transforms compose in the *reverse* of automata_scratch? This
appears to be the case. appears to be the case.
## Important but less critical: ## Important but less critical:
- Do I actually need `EmptyRule` or can I get rid of a bunch of
extraneous nesting? `RuleEval` with empty meshes and no children is
equivalent. I have used `EmptyRule` exactly zero times.
- Why must I repeat myself so much in these definitions? - Why must I repeat myself so much in these definitions?
- The notation for transforms is really cumbersome. Some syntactic - The notation for transforms is really cumbersome. Some syntactic
sugar might go far. sugar might go far.
@ -15,10 +21,6 @@
the clockwise boundaries, the zigzag connections, the iterating over the clockwise boundaries, the zigzag connections, the iterating over
a `Vec<Vertex>` to transform each element and make another vector. a `Vec<Vertex>` to transform each element and make another vector.
- Docs on modules - Docs on modules
- Consider making `to_mesh` iterative. My call stack seems needlessly
deep in spots - especially in rules which do not branch. Sections
like this should be manageable with just iteration that does not
grow anything in size.
- Grep for all TODOs in code, really. - Grep for all TODOs in code, really.
- Look at everything in README.md in automata_scratch. - Look at everything in README.md in automata_scratch.
- Implement some of the tougher examples from the above too, e.g. the - Implement some of the tougher examples from the above too, e.g. the

View File

@ -98,7 +98,13 @@ impl OpenMesh {
/// Treat this mesh as a 'parent' mesh to connect with any number /// Treat this mesh as a 'parent' mesh to connect with any number
/// of 'child' meshes, all of them paired with their respective /// of 'child' meshes, all of them paired with their respective
/// parent vertex mappings. This returns a new mesh. /// parent vertex mappings. This returns a tuple of (new mesh,
/// offsets), where 'offsets' gives the offset of where child
/// meshes were shifted in the new mesh.
///
/// That is, the vertices of 'children[i]' begin at vertex
/// 'offset[i]' of the new mesh. This is needed in some cases for
/// adjusting a parent vertex mapping, like 'vmap' of Rule::Child.
pub fn connect(&self, children: &Vec<(OpenMesh, &Vec<usize>)>) -> (OpenMesh, Vec<usize>) { pub fn connect(&self, children: &Vec<(OpenMesh, &Vec<usize>)>) -> (OpenMesh, Vec<usize>) {
// TODO: Clean up this description a bit // TODO: Clean up this description a bit
// TODO: Clean up Vec<usize> stuff // TODO: Clean up Vec<usize> stuff

View File

@ -80,9 +80,9 @@ impl<A> Rule<A> {
/// Convert this `Rule` to mesh data, recursively (depth first). /// Convert this `Rule` to mesh data, recursively (depth first).
/// `iters_left` sets the maximum recursion depth. This returns /// `iters_left` sets the maximum recursion depth. This returns
/// (geometry, number of rule evaluations). /// (geometry, number of rule evaluations).
pub fn to_mesh(&self, arg: &A, iters_left: u32) -> (OpenMesh, u32) { pub fn to_mesh(&self, arg: &A, iters_left: u32) -> (OpenMesh, usize) {
let mut evals: u32 = 1; let mut evals = 1;
if iters_left <= 0 { if iters_left <= 0 {
match self { match self {
@ -122,7 +122,10 @@ impl<A> Rule<A> {
} }
} }
pub fn to_mesh_iter(&self, arg: &A, max_depth: usize) -> (OpenMesh, u32) { /// This should be identical to to_mesh, but implemented
/// iteratively with an explicit stack rather than with recursive
/// function calls.
pub fn to_mesh_iter(&self, arg: &A, max_depth: usize) -> (OpenMesh, usize) {
struct State<A> { struct State<A> {
// The set of rules we're currently handling: // The set of rules we're currently handling:
@ -134,56 +137,56 @@ impl<A> Rule<A> {
// 'rules'. // 'rules'.
xf: Mat4, xf: Mat4,
} }
let mut geom = prim::empty_mesh();
let mut stack: Vec<State<A>> = vec![];
// Set up starting state: // 'stack' stores at its last element our "current" State in
// terms of a current world transform and which Child should
// be processed next. Every element prior to this is previous
// states which must be kept around for further backtracking
// (usually because they involve multiple rules).
let mut stack: Vec<State<A>> = vec![];
let mut geom = prim::empty_mesh();
match self { match self {
Rule::Recurse(f) => { Rule::Recurse(f) => {
// Set up the stack's initial state - evaluate our own rule
let eval = f(arg); let eval = f(arg);
let s = State { stack.push(State {
rules: eval.children, rules: eval.children,
next: 0, next: 0,
xf: nalgebra::geometry::Transform3::identity().to_homogeneous(), xf: nalgebra::geometry::Transform3::identity().to_homogeneous(),
}; });
stack.push(s);
geom = eval.geom; geom = eval.geom;
}, },
Rule::EmptyRule => { Rule::EmptyRule => {
// No geometry and nowhere to recurse... // Empty rule and no geometry: quit now.
return (geom, 0); return (geom, 0);
}, },
} }
let mut count = 0; // Number of times we've evaluated a Rule:
let mut eval_count = 1;
// Stack depth (update at every push & pop):
let mut n = stack.len();
// TODO: Why is this forcing it to build up the entire stack
// to the recursion limit, and *then* start generating
// geometry and winding the stack back down?
//
// Isn't there some way that it can generate geometry at every
// step and only have to add to the stack to handle a branch?
while !stack.is_empty() { while !stack.is_empty() {
// TODO: This, more elegantly? // TODO: This, more elegantly?
count += 1; if eval_count > max_depth {
if count > max_depth {
break; break;
} }
let n = stack.len(); // TODO: Just keep a running total.
println!("DEBUG: stack has len {}", n); println!("DEBUG: stack has len {}", n);
// We can increment/decrement as we push/pop.
let s = &mut stack[n-1]; let s = &mut stack[n-1];
if s.next >= s.rules.len() { if s.next >= s.rules.len() {
// If we've run out of child rules, backtrack: // If we've run out of child rules, have the *parent* node (if one) move on:
stack.pop();
// and have the *parent* node (if one) move on:
if n >= 2 { if n >= 2 {
stack[n-2].next += 1; stack[n-2].next += 1;
} }
// and backtrack:
stack.pop();
n -= 1;
// (if there isn't one, it makes no difference, // (if there isn't one, it makes no difference,
// because the loop will end) // because the loop will end)
continue; continue;
@ -194,30 +197,54 @@ impl<A> Rule<A> {
Rule::Recurse(f) => { Rule::Recurse(f) => {
// Evaluate the rule: // Evaluate the rule:
let mut eval = f(arg); let mut eval = f(arg);
eval_count += 1;
// Compose child transform to new world transform: // Make an updated world transform:
let xf = s.xf * child.xf; // TODO: Check order on this let xf = s.xf * child.xf; // TODO: Check order on this
// This rule produced some geometry which we'll
// combine with the 'global' geometry:
let new_geom = eval.geom.transform(&xf); let new_geom = eval.geom.transform(&xf);
println!("DEBUG: Connecting {} faces, vmap={:?}, faces={:?}", println!("DEBUG: Connecting {} faces, vmap={:?}, faces={:?}",
new_geom.verts.len(), child.vmap, new_geom.faces); new_geom.verts.len(), child.vmap, new_geom.faces);
let (g, offsets) = geom.connect(&vec![(new_geom, &child.vmap)]); let (g, offsets) = geom.connect(&vec![(new_geom, &child.vmap)]);
geom = g; geom = g;
// Adjust vmap in all of eval.children: // 'new_geom' may itself be parent geometry for
for (i,offset) in offsets.iter().enumerate() { // something in 'eval.children' (via Tag::Parent),
eval.children[i].vmap = eval.children[i].vmap.iter().map(|n| n + offset).collect(); // and vmap is there to resolve those Tag::Parent
// references to the right vertices in 'new_geom'.
//
// However, we connect() on the global geometry
// which we merged 'new_geom' into, not 'new_geom'
// directly. To account for this, we must shift
// vmap by the offset that 'geom.connect' gave us:
for (offset, child) in offsets.iter().zip(eval.children.iter_mut()) {
child.vmap = child.vmap.iter().map(|n| {
n + offset
}).collect();
} }
// TODO: Explain this better
// TODO: Why does below work?
if (s.next + 1) >= s.rules.len() {
let m = stack.len();
if m >= 2 {
stack[m-2].next += 1;
}
stack.pop();
n -= 1;
}
// I guess we are "done" with the rule after we've
// evaluated it, and it is then safe to increment
// s.next.
// Recurse further (i.e. put more onto stack): // Recurse further (i.e. put more onto stack):
let s2 = State { stack.push(State {
rules: eval.children, rules: eval.children,
next: 0, next: 0,
xf: xf, xf: xf,
}; });
stack.push(s2); n += 1;
}, },
Rule::EmptyRule => { Rule::EmptyRule => {
s.next += 1; s.next += 1;
@ -229,10 +256,7 @@ impl<A> Rule<A> {
// TODO: Handle final_geom // TODO: Handle final_geom
// TODO: Return right number return (geom, eval_count);
return (geom, 0);
} }
} }