diff --git a/README.md b/README.md index b0db568..5e54e8f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ patch up or subdivide the meshes in post-processing. These grammars by their nature worked in discrete steps, but at one point I tried (unsuccessfully) to extend this system to working in a more continuous and parametric -way. +way. (See `parametric_mesh` and any DCEL code.) I also ran into problems anytime I wanted to produce meshes in a way that was more "refining" than "generative". @@ -21,6 +21,16 @@ from a 'parent' rule, besides being able to connect to its vertices - and sometimes the "refining" part of things required this in order to work right. +The problems with the parametric/continuous, and the +aforementioned "refining", were related. The issue is that +in order to get good meshes, I needed to be able to minimize +approximation error with the triangles and avoid triangles +with extreme angles, and there was seemingly no good way to +do this by incremental construction (like I was trying to +use elsewhere in my model) - and so its seems I just ended up +reinventing, badly, a lot of existing work with subdivision +and meshing. + I've also disliked how much my model felt like it tied me down to the "triangle mesh" representation. I haven't found a good way to build up higher-level representations @@ -36,6 +46,18 @@ consider how much those assume the presence of garbage collection. Really, I wanted a Lisp, and then the presence of a REPL would have been another bonus. +On top of this, my implementation is pretty slow when it is +using a large number of rules each producing small geometry +(which is almost literally the only way it *can* be used +if you want to produce a fairly complex mesh). I did some +profiling some months ago that showed I was spending the +vast majority of my time in `extend()` and `clone()` for +`Vec` - and so I could probably see some huge performance +gains if I could simply pre-allocate vectors and share geometry +more. Also, I'm pretty sure this code does some very task-parallel +elements (e.g. anytime a rule branches), and multithreading should +be able to exploit this if I care. + If I actually understood my goals enough to put better constraints on my model, Rust probably would have been fine. As it stands now, the lack of clarity in both my theory @@ -44,16 +66,11 @@ related to Rust. ## Highest priority: +- Fix `ramhorn_branch`. +- Once I've fixed that, see about a refactor that respects the + same model, but involves much less ceremony and boilerplate. - Figure out the crash bug in `vec_indexed!` if I put a Vertex *after* an Arg. -- Just scrap `parametric_mesh` as much as possible and use existing - tools (e.g. OpenSubdiv) because this DCEL method is just painful for - what it is and I have some questions on how it can even work - theoretically. -- Connect up the `parametric_mesh` stuff that remains, and worry about - perfect meshes later. -- Get identical or near-identical meshes to `ramhorn_branch` from - Python. (Should just be a matter of tweaking parameters.) - Look at performance. - Start at `to_mesh_iter()`. The cost of small appends/connects seems to be killing performance. @@ -62,13 +79,6 @@ related to Rust. 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). -- See `automata_scratch/examples.py` and implement some of the tougher - examples. - - `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: @@ -85,7 +95,8 @@ related to Rust. - Catch-alls: - 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`, + my old Python code from around 2019-09. ## If I'm bored: @@ -96,9 +107,6 @@ related to Rust. - Would being able to name a rule node (perhaps conditionally under some compile-time flag) help for debugging? - Use an actual logging framework. -- Take a square. Wrap it around to a torus. Now add a twist (about - the axis that is normal to the square). This is simple, but it looks - pretty cool. - How can I take tangled things like the cinquefoil and produce more 'iterative' versions that still weave around? @@ -118,3 +126,4 @@ related to Rust. - If you *pre* multiply a transformation: you are transforming the entire global space. If you *post* multiply: you are transforming the current local space. +- Don't reinvent subdivision surfaces. \ No newline at end of file diff --git a/src/examples.rs b/src/examples.rs index e2dee88..9a226ff 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -1,5 +1,5 @@ use std::rc::Rc; -use std::f32::consts::{FRAC_PI_2, FRAC_PI_3, FRAC_PI_6, PI}; +use std::f32::consts::{FRAC_PI_2, FRAC_PI_3}; use std::f32; use nalgebra::*; @@ -8,11 +8,11 @@ use rand::Rng; use crate::util; use crate::util::VecExt; use crate::mesh::{Mesh, MeshFunc, VertexUnion, vert_args}; -use crate::xform::{Transform, Vertex, vertex, Mat4, id}; -use crate::rule::{Rule, RuleFn, RuleEval, Child}; +use crate::xform::{Transform, Vertex, vertex, id}; +use crate::rule::{Rule, RuleEval, Child}; use crate::prim; use crate::dcel; -use crate::dcel::{DCELMesh, VertSpec}; +use crate::dcel::{VertSpec}; pub fn cube_thing() -> Rule<()> { @@ -106,7 +106,7 @@ pub fn barbs(random: bool) -> Rule<()> { }; let main_incr = |random| { if random { - let t = rand::thread_rng().gen_range(0.75, 1.25); + //let t = rand::thread_rng().gen_range(0.75, 1.25); let s = rand::thread_rng().gen_range(0.85, 1.10); let rz = rand::thread_rng().gen_range(0.05, 0.25); let rx = rand::thread_rng().gen_range(0.08, 0.12); @@ -124,7 +124,7 @@ pub fn barbs(random: bool) -> Rule<()> { let main = rule_fn!(() => |self_, base_verts| { let mut next_verts = base_verts; - let (a0, a1) = next_verts.append_indexed(vert_args(0..4)); + let (a0, _) = next_verts.append_indexed(vert_args(0..4)); // This contributes no faces of its own - just vertices. let geom = MeshFunc { verts: next_verts.clone(), faces: vec![] }; @@ -164,17 +164,22 @@ pub fn barbs(random: bool) -> Rule<()> { Rule { eval: base, ctxt: () } } -pub fn pyramid() -> Rule<()> { +pub fn sierpinski() -> Rule<()> { + + // Initial height step: + let dz = 0.10; + // 'Extra' z rotation (0.0 for normal Sierpinski) + let dr = 0.1; + // Scale factor (0.5 for normal Sierpinski) + let s = 0.51; let rt3 = (3.0).sqrt(); - let dz = 0.10; - // Indices: // b+0,b+1,b+2 = base vertices // t+0,t+1,t+2 = 'top' vertices above base // tm01, tm12, tm20 = midpoints of (t0,t1), (t1,t2), (t2,t0). - let (b, t, tm01, tm12, tm20); + let (b, t, tm01, tm12, tm20, n); let base_verts: Vec = { let v0 = vertex(rt3/3.0, 0.0, 0.0); let v1 = vertex(-rt3/6.0, 1.0/2.0, 0.0); @@ -192,24 +197,24 @@ pub fn pyramid() -> Rule<()> { @tm01 VertexUnion::Vertex((v0b+v1b)/2.0), @tm12 VertexUnion::Vertex((v1b+v2b)/2.0), @tm20 VertexUnion::Vertex((v2b+v0b)/2.0), + @n, ] }; let tri_split = move |i| { let rt3 = (3.0).sqrt(); - let angle = 2.0 * FRAC_PI_3 * (i as f32); + let angle = 2.0 * FRAC_PI_3 * (i as f32) + dr; id(). rotate(&Vector3::z_axis(), angle). translate(rt3/12.0, 0.0, 0.0). - scale(0.49). + scale(s). translate(0.0, 0.0, dz) }; let split = rule_fn!(() => |_s, base_verts| { let mut next_verts = base_verts.clone(); - let mut final_verts = base_verts; - let (a0, a1) = next_verts.append_indexed(vert_args(0..3)); + let (a0, _) = next_verts.append_indexed(vert_args(0..3)); RuleEval { geom: Rc::new(MeshFunc { @@ -231,8 +236,12 @@ pub fn pyramid() -> Rule<()> { ], }), final_geom: Rc::new(MeshFunc { - verts: vert_args(t..(t+3)), - faces: vec![ 0, 1, 2 ], + verts: vert_args(0..n), // just duplicate same verts + faces: vec![ + t+0, tm01, tm20, + t+1, tm12, tm01, + t+2, tm20, tm12, + ], }), children: vec![ child!(_s, tri_split(0), t+0, tm01, tm20), @@ -494,7 +503,6 @@ pub fn nest_spiral_2() -> Rule { }, } } -*/ #[derive(Copy, Clone)] pub struct TorusCtxt { @@ -503,7 +511,6 @@ pub struct TorusCtxt { stack: [Transform; 3], } -/* pub fn twisty_torus() -> Rule { let subdiv = 8; let seed = vec![ diff --git a/src/lib.rs b/src/lib.rs index 75defb2..eb061b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,7 +80,7 @@ mod tests { fn barbs_random() { run_test(examples::barbs(true), 80, "barbs_random", false); } #[test] - fn pyramid() { run_test(examples::pyramid2(), 3, "pyramid2", false); } + fn sierpinski() { run_test(examples::sierpinski(), 6, "sierpinski", false); } /* #[test] fn twist() { diff --git a/src/rule.rs b/src/rule.rs index 7c91da0..e7828c3 100644 --- a/src/rule.rs +++ b/src/rule.rs @@ -96,7 +96,7 @@ macro_rules! child { macro_rules! child_iter { ( $Rule:expr, $Xform:expr, $Args:expr ) => { Child { - rule: /*std::rc::Rc::new*/($Rule), + rule: /*std::rc::Rc::new*/($Rule).clone(), xf: $Xform, arg_vals: $Args.collect(), // does this even need a macro? } @@ -366,7 +366,7 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f panic!("frame must have at least 3 vertices"); } - struct frontierVert { + struct FrontierVert { vert: Vertex, // Vertex position t: f32, // Parameter value; f(t) should equal vert frame_idx: usize, // Index of 'frame' this sits in the trajectory of @@ -376,7 +376,7 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f }; // Init 'frontier' with each 'frame' vertex, and start it at t=t0. - let mut frontier: Vec = frame.iter().enumerate().map(|(i,v)| frontierVert { + let mut frontier: Vec = frame.iter().enumerate().map(|(i,v)| FrontierVert { vert: *v, t: t0, frame_idx: i, @@ -402,7 +402,7 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f // Pick a vertex to advance. // // Heuristic for now: pick the 'furthest back' (lowest t) - let (i,v) = frontier.iter().enumerate().min_by(|(i,f), (j, g)| + let (i,v) = frontier.iter().enumerate().min_by(|(_,f), (_, g)| f.t.partial_cmp(&g.t).unwrap_or(std::cmp::Ordering::Equal)).unwrap(); // TODO: Make this less ugly? @@ -456,7 +456,7 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f ]); // Replace this vertex in the frontier: - frontier[i] = frontierVert { + frontier[i] = FrontierVert { vert: v_next, frame_idx: v.frame_idx, mesh_idx: pos,