From 7fd89b63b8d1db30c9441609f52e6bc063a04441 Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Fri, 6 Mar 2020 08:54:08 -0500 Subject: [PATCH] Add some more docs --- README.md | 10 +++-- src/openmesh.rs | 8 +++- src/rule.rs | 100 ++++++++++++++++++++++++++++++------------------ 3 files changed, 75 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 0f1bf83..639c710 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,18 @@ ## 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. - Do transforms compose in the *reverse* of automata_scratch? This appears to be the case. ## 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? - The notation for transforms is really cumbersome. Some syntactic sugar might go far. @@ -15,10 +21,6 @@ the clockwise boundaries, the zigzag connections, the iterating over a `Vec` to transform each element and make another vector. - 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. - Look at everything in README.md in automata_scratch. - Implement some of the tougher examples from the above too, e.g. the diff --git a/src/openmesh.rs b/src/openmesh.rs index 7e412c6..c28104c 100644 --- a/src/openmesh.rs +++ b/src/openmesh.rs @@ -98,7 +98,13 @@ impl OpenMesh { /// Treat this mesh as a 'parent' mesh to connect with any number /// 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)>) -> (OpenMesh, Vec) { // TODO: Clean up this description a bit // TODO: Clean up Vec stuff diff --git a/src/rule.rs b/src/rule.rs index a2fd9b5..b6b2887 100644 --- a/src/rule.rs +++ b/src/rule.rs @@ -80,9 +80,9 @@ impl Rule { /// Convert this `Rule` to mesh data, recursively (depth first). /// `iters_left` sets the maximum recursion depth. This returns /// (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 { match self { @@ -122,7 +122,10 @@ impl Rule { } } - 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 { // The set of rules we're currently handling: @@ -134,56 +137,56 @@ impl Rule { // 'rules'. xf: Mat4, } - - let mut geom = prim::empty_mesh(); - let mut stack: Vec> = 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> = vec![]; + let mut geom = prim::empty_mesh(); + match self { Rule::Recurse(f) => { + // Set up the stack's initial state - evaluate our own rule let eval = f(arg); - let s = State { + stack.push(State { rules: eval.children, next: 0, xf: nalgebra::geometry::Transform3::identity().to_homogeneous(), - }; - stack.push(s); + }); geom = eval.geom; }, Rule::EmptyRule => { - // No geometry and nowhere to recurse... + // Empty rule and no geometry: quit now. 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() { // TODO: This, more elegantly? - count += 1; - if count > max_depth { + if eval_count > max_depth { break; } - let n = stack.len(); // TODO: Just keep a running total. println!("DEBUG: stack has len {}", n); - // We can increment/decrement as we push/pop. let s = &mut stack[n-1]; if s.next >= s.rules.len() { - // If we've run out of child rules, backtrack: - stack.pop(); - // and have the *parent* node (if one) move on: + // If we've run out of child rules, have the *parent* node (if one) move on: if n >= 2 { stack[n-2].next += 1; } + // and backtrack: + stack.pop(); + n -= 1; // (if there isn't one, it makes no difference, // because the loop will end) continue; @@ -194,30 +197,54 @@ impl Rule { Rule::Recurse(f) => { // Evaluate the rule: 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 + // This rule produced some geometry which we'll + // combine with the 'global' geometry: let new_geom = eval.geom.transform(&xf); println!("DEBUG: Connecting {} faces, vmap={:?}, faces={:?}", new_geom.verts.len(), child.vmap, new_geom.faces); let (g, offsets) = geom.connect(&vec![(new_geom, &child.vmap)]); geom = g; - // Adjust vmap in all of eval.children: - for (i,offset) in offsets.iter().enumerate() { - eval.children[i].vmap = eval.children[i].vmap.iter().map(|n| n + offset).collect(); + // 'new_geom' may itself be parent geometry for + // something in 'eval.children' (via Tag::Parent), + // 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): - let s2 = State { + stack.push(State { rules: eval.children, next: 0, xf: xf, - }; - stack.push(s2); - + }); + n += 1; }, Rule::EmptyRule => { s.next += 1; @@ -229,10 +256,7 @@ impl Rule { // TODO: Handle final_geom - // TODO: Return right number - return (geom, 0); - + return (geom, eval_count); } - }