diff --git a/README.md b/README.md index 5f68e1a..9127c03 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,31 @@ ## Highest priority: -- Clean up `ramhorn_branch` because it's fugly. +- Adaptive subdivision - which means having to generalize past some + `vmap` stuff. +- Try some non-deterministic examples +- Get identical or near-identical meshes to `ramhorn_branch` from + Python. (Should just be a matter of tweaking parameters.) - See `automata_scratch/examples.py` and implement some of the tougher examples. - - `spiral_nested_2` & `spiral_nested_3` (how to compose - efficiently?) - - `twisty_torus` + - `twisty_torus`, `spiral_nested_2`, & `spiral_nested_3` are all + that remain. To do them, I need to compose transformations (not + in the matrix sense), but I also probably need to produce + RuleEvals which always have `xf` of identity transformation since + the Python code does not 'inherit' transforms unless I tell it to. ## Important but less critical: -- Elegance & succinctness (my recent closure work may help with this): +- Look at performance. + - Start at `to_mesh_iter()`. The cost of small appends/connects + seems to be killing performance. + - `connect()` is a big performance hot-spot: 85% of total time in + one test, around 51% in `extend()`, 33% in `clone()`. It seems + like I should be able to share geometry with the `Rc` (like noted + above), defer copying until actually needed, and pre-allocate the + vector to its size (which should be easy to compute). +- Elegance & succinctness: + - Clean up `ramhorn_branch` because it's ugly. - What patterns can I factor out? I do some things regularly, like: the clockwise boundaries, the zigzag connections. - Declarative macro to shorten this `Tag::Parent`, `Tag::Body` @@ -22,14 +37,6 @@ things like my patterns with closures (e.g. the Y combinator like method for recursive calls). - Docs on modules -- Look at performance. - - Start at `to_mesh_iter()`. The cost of small appends/connects - seems to be killing performance. - - `connect()` is a big performance hot-spot: 85% of total time in - one test, around 51% in `extend()`, 33% in `clone()`. It seems - like I should be able to share geometry with the `Rc` (like noted - above), defer copying until actually needed, and pre-allocate the - vector to its size (which should be easy to compute). - Compute global scale factor, and perhaps pass it to a rule (to eventually be used for, perhaps, adaptive subdivision) - swept-isocontour stuff from diff --git a/src/examples.rs b/src/examples.rs index 84d9374..3cb474e 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -44,7 +44,8 @@ pub fn cube_thing() -> Rule<()> { Rule { eval: Rc::new(rec), ctxt: () } } -// Meant to be a copy of twist_from_gen from Python & automata_scratch +// Meant to be a copy of twist_from_gen from Python & +// automata_scratch, but has since acquired a sort of life of its own pub fn twist(f: f32, subdiv: usize) -> Rule<()> { // TODO: Clean this code up. It was a very naive conversion from // the non-closure version. @@ -71,6 +72,9 @@ pub fn twist(f: f32, subdiv: usize) -> Rule<()> { let seed2 = seed.clone(); // TODO: Why do I need the above? + // TODO: Could a macro get rid of some of this or would it just be + // equally cumbersome because I'd have to sort of pass 'seed' + // explicitly? let recur = move |incr: Transform| -> RuleFn<()> { let seed_next = incr.transform(&seed2); @@ -133,6 +137,96 @@ pub fn twist(f: f32, subdiv: usize) -> Rule<()> { Rule { eval: Rc::new(start), ctxt: () } } +#[derive(Copy, Clone)] +pub struct TorusCtxt { + xform1: Transform, + xform2: Transform, +} + +pub fn twisty_torus() -> Rule { + let subdiv = 8; + let seed = vec![ + vertex(-0.5, -0.5, 1.0), + vertex(-0.5, 0.5, 1.0), + vertex( 0.5, 0.5, 1.0), + vertex( 0.5, -0.5, 1.0), + ]; + let seed = util::subdivide_cycle(&seed, subdiv); + + let n = seed.len(); + let geom = Rc::new(util::zigzag_to_parent(seed.clone(), n)); + let (vc, faces) = util::connect_convex(&seed, true); + let final_geom = Rc::new(OpenMesh { + verts: vec![vc], + faces: faces, + }); + + let rad = 4.0; + let dx0 = 2.0; + let ang = 0.1; + + let recur = move |self_: Rc>| -> RuleEval { + let y = &Vector3::y_axis(); + let z = &Vector3::z_axis(); + let xf1 = self_.ctxt.xform1; + let xf2 = self_.ctxt.xform2; + let next_rule = Rule { + eval: self_.eval.clone(), + ctxt: TorusCtxt { + xform1: xf1.rotate(y, 0.1), + xform2: xf2.rotate(z, ang), + }, + }; + let xf = xf1 * xf2; + RuleEval { + geom: Rc::new(geom.transform(&xf)), + final_geom: Rc::new(final_geom.transform(&xf)), + children: vec![ + Child { + rule: Rc::new(next_rule), + xf: Transform::new(), + vmap: (0..n).collect(), + }, + ], + } + }; + + let start = move |self_: Rc>| -> RuleEval { + let xf1 = self_.ctxt.xform1; + let xf2 = self_.ctxt.xform2; + let xf = xf1 * xf2; + + let mut s2 = seed.clone(); + let (centroid, f) = util::connect_convex(&s2, false); + s2.push(centroid); + let n2 = s2.len(); + let g = OpenMesh { verts: s2, faces: f }; + let fg = prim::empty_mesh(); + RuleEval { + geom: Rc::new(g.transform(&xf)), + final_geom: Rc::new(fg), + children: vec![ + Child { + rule: Rc::new(Rule { + eval: Rc::new(recur.clone()), + ctxt: self_.ctxt, + }), + xf: Transform::new(), + vmap: (0..n2).collect(), + }, + ], + } + }; + + Rule { + eval: Rc::new(start), + ctxt: TorusCtxt { + xform1: Transform::new().translate(rad, 0.0, 0.0), + xform2: Transform::new().translate(dx0, 0.0, 0.0), + }, + } +} + pub fn ramhorn() -> Rule<()> { let v = Unit::new_normalize(Vector3::new(-1.0, 0.0, 1.0)); diff --git a/src/lib.rs b/src/lib.rs index 07a1734..74ba7d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,11 @@ mod tests { run_test(examples::twist(1.0, 2), 200, "screw", false); } + #[test] + fn twisty_torus() { + run_test(examples::twisty_torus(), 50, "twisty_torus", false); + } + // This one is very time-consuming to run: #[test] #[ignore]