diff --git a/README.md b/README.md index 0819acd..ec92fb9 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,17 @@ ## Highest priority: -- Work on my doubly-connected edge list so I can complete some other - things below! -- Implement the continuous parametric transformations from 2020-05-07 - in my notes. This will require some new abstractions. +- Add better docs and possibly abstraction to `dcel.rs`, *before* + it's widely used. Possibly rename too. +- `dcel.rs` needs a helper method for subdivision. +- Redo `examples::parametric_mesh` along the following lines: + - Make a crappy mesh (as a DCEL) like my 'early' method. + - Use my method from around 2020-05-21, and my pile of loose notes, + to subdivide - with the help of DCEL. - Document `parametric_mesh` better. It is pretty hairy and could also benefit from some modularity. +- parametric_mesh: My err/max_err code seems to sometimes give very high + dt values, e.g. if I use just a translation as my transform - Get identical or near-identical meshes to `ramhorn_branch` from Python. (Should just be a matter of tweaking parameters.) - Look at performance. diff --git a/src/dcel.rs b/src/dcel.rs index d0f28f8..0a758dd 100644 --- a/src/dcel.rs +++ b/src/dcel.rs @@ -139,7 +139,12 @@ impl DCELMesh { } else { String::from("") }; - println!("Halfedge {}: vert {}, face {}, prev: {}, next: {}{}", i, h.vert, h.face, h.prev_halfedge, h.next_halfedge, twin) + let v1 = self.verts[h.vert].v; + let v2 = self.verts[self.halfedges[h.next_halfedge].vert].v; + println!("Halfedge {}: vert {} (to {}), face {}, prev: {}, next: {}{}, ({:?} to {:?})", + i, h.vert, self.halfedges[h.next_halfedge].vert, h.face, + h.prev_halfedge, h.next_halfedge, twin, + v1, v2); } } @@ -196,10 +201,14 @@ impl DCELMesh { println!("Halfedge {}: twin {} says it has no twin", i, edge.twin_halfedge); pass = false; - } else if i != twin.twin_halfedge { + } else if i != twin.twin_halfedge { println!("Halfedge {} has twin {}, but reverse isn't true", i, edge.twin_halfedge); pass = false; + } else if edge.vert != self.halfedges[twin.next_halfedge].vert { + println!("Halfedge {} starts at vertex {} but twin {} ends at vertex {}", + i, edge.vert, edge.twin_halfedge, self.halfedges[twin.next_halfedge].vert); + pass = false; } } let p = edge.prev_halfedge; @@ -398,6 +407,7 @@ impl DCELMesh { /// Also: halfedge `twin2_idx` must end at the vertex that starts /// `twin1_idx`. pub fn add_face_twin2(&mut self, twin1_idx: usize, twin2_idx: usize) -> (usize, [usize; 3]) { + println!("DEBUG: add_face_twin2({},{})", twin1_idx, twin2_idx); // The face will be at index f_n: let f_n = self.num_faces; // The half-edges will be at indices e_n, e_n+1, e_n+2: @@ -417,8 +427,11 @@ impl DCELMesh { twin2_idx, twin1_idx, self.halfedges[twin2.next_halfedge].vert, v1)); } + // twin1 is: v1 -> v2, twin2 is: v3 -> v1. + // so the twin of twin1 must be: v2 -> v1 + // and the twin of twin2 must be: v1 -> v3 self.halfedges.push(DCELHalfEdge { - vert: v1, + vert: v2, face: f_n, has_twin: true, twin_halfedge: twin1_idx, @@ -436,7 +449,7 @@ impl DCELMesh { prev_halfedge: e_n, }); // index e_n + 1 self.halfedges.push(DCELHalfEdge { - vert: v2, + vert: v1, face: f_n, has_twin: false, twin_halfedge: 0, diff --git a/src/examples.rs b/src/examples.rs index 67b0080..da92f05 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -12,6 +12,7 @@ use crate::xform::{Transform, Vertex, vertex, Mat4, id}; use crate::rule::{Rule, RuleFn, RuleEval, Child}; use crate::prim; use crate::dcel; +use crate::dcel::{DCELMesh, VertSpec}; /* pub fn cube_thing() -> Rule<()> { @@ -1262,10 +1263,10 @@ impl CurveHorn { pub fn test_parametric() -> Mesh { let base_verts: Vec = 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), + vertex(-1.0, -1.0, 0.0), + vertex(-1.0, 1.0, 0.0), + vertex( 1.0, 1.0, 0.0), + //vertex( 1.0, -1.0, 0.0), ]; //let base_verts = util::subdivide_cycle(&base_verts, 2); //let base_verts = util::subdivide_cycle(&base_verts, 16); @@ -1273,9 +1274,9 @@ pub fn test_parametric() -> Mesh { let t0 = 0.0; let t1 = 16.0; let xform = |t: f32| -> Transform { - id().translate(0.0, 0.0, t/5.0). + id().translate(0.0, 0.0, t/5.0)/*. rotate(&Vector3::z_axis(), -t/2.0). - scale((0.8).powf(t)) + scale((0.8).powf(t))*/ }; crate::rule::parametric_mesh(base_verts, xform, t0, t1, 0.005) @@ -1283,24 +1284,25 @@ pub fn test_parametric() -> Mesh { pub fn test_dcel(fname: &str) { let mut mesh: dcel::DCELMesh = dcel::DCELMesh::new(); - let f1 = mesh.add_face([ - vertex(-0.5, -0.5, 0.0), - vertex(-0.5, 0.5, 0.0), - vertex( 0.5, 0.5, 0.0), + let (f1, _) = mesh.add_face([ + VertSpec::New(vertex(-0.5, -0.5, 0.0)), + VertSpec::New(vertex(-0.5, 0.5, 0.0)), + VertSpec::New(vertex( 0.5, 0.5, 0.0)), ]); mesh.check(); - let f2 = mesh.add_face_twin1(mesh.faces[f1].halfedge, vertex(0.0, 0.0, 1.0)); + let (f2, edges) = mesh.add_face_twin1(mesh.faces[f1].halfedge, vertex(0.0, 0.0, 1.0)); mesh.check(); - // Find the shared edge: - let mut shared: Option<(usize, usize)> = None; - for e in mesh.face_to_halfedges(f1) { - let he = &mesh.halfedges[e]; + // From add_face_twin1, edges[0] is always the 'shared' edge: + let edge = edges[0]; + let twin = { + let he = &mesh.halfedges[edge]; if he.has_twin { - shared = Some((e, he.twin_halfedge)); + he.twin_halfedge + } else { + panic!("Can't find shared edge!"); } - } - let (edge, twin) = shared.unwrap(); + }; println!("Shared edges = {},{}", edge, twin); let ep = mesh.halfedges[edge].prev_halfedge; @@ -1312,9 +1314,9 @@ pub fn test_dcel(fname: &str) { println!("DCEL mesh = {}", mesh); // As we're making *twin* halfedges, we go against the edge // direction: - let f3 = mesh.add_face_twin2(en, tp); + let (f3, _) = mesh.add_face_twin2(en, tp); mesh.check(); - let f4 = mesh.add_face_twin2(tn, ep); + let (f4, _) = mesh.add_face_twin2(tn, ep); mesh.check(); println!("f1 verts: {:?}", mesh.face_to_verts(f1)); diff --git a/src/rule.rs b/src/rule.rs index 1849fc8..b70169b 100644 --- a/src/rule.rs +++ b/src/rule.rs @@ -1,10 +1,12 @@ -use crate::mesh::{Mesh, MeshFunc, VertexUnion}; -use crate::xform::{Transform, Vertex}; -//use crate::prim; use std::borrow::Borrow; use std::rc::Rc; use std::f32; +use crate::mesh::{Mesh, MeshFunc, VertexUnion}; +use crate::xform::{Transform, Vertex}; +use crate::dcel; +use crate::dcel::{DCELMesh, VertSpec}; + pub type RuleFn = Rc>) -> RuleEval>; /// Definition of a rule. In general, a `Rule`: @@ -364,19 +366,27 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f panic!("frame must have at least 3 vertices"); } + let mut mesh: DCELMesh = DCELMesh::new(); + #[derive(Clone, Debug)] struct frontierVert { - vert: Vertex, // Vertex position - t: f32, // Parameter value; f(t) should equal vert - frame_vert: Vertex, // "Starting" vertex position, i.e. at f(t0). - // Always either a frame vertex, or a linear combination of two - // neighboring ones. - mesh_idx: usize, // Index of this vertex in the mesh - // (If it's in 'frontier', the vertex is in the mesh). - neighbor: [usize; 2], // Indices of 'frontier' of each neighbor - side_faces: [(usize,bool); 2], // The two faces (given as an index - // into 'faces' below) which contain the edge from this vertex to - // its neighbors. If the bool is false, there is no face. + // Vertex position + vert: Vertex, + // Parameter value; f(t) should equal vert + t: f32, + // "Starting" vertex position, i.e. at f(t0). Always either a frame + // vertex, or a linear combination of two neighboring ones. + frame_vert: Vertex, + // If the boundaries on either side of this vertex lie on a face + // (which is the case for all vertices *except* the initial ones), + // then this gives the halfedges of those boundaries. halfedges[0] + // connects the 'prior' vertex on the frontier to this, and + // halfedges[1] connect this to the 'next' vertex on the fronter. + // (Direction matters. If halfedges[0] is given, it must *end* at + // 'vert'. If halfedges[1] is given, it must *begin* at 'vert'.) + halfedges: [Option; 2], + // If this vertex is already in 'mesh', its vertex index there: + vert_idx: Option, }; // A face that is still undergoing subdivision. This is used for a stack @@ -409,9 +419,8 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f vert: *v, t: t0, frame_vert: *v, - mesh_idx: i, - neighbor: [(i - 1) % n, (i + 1) % n], - side_faces: [(0, false); 2], // initial vertices have no faces + halfedges: [None; 2], + vert_idx: None, }).collect(); // Every vertex in 'frontier' has a trajectory it follows - which is // simply the position as we transform the original vertex by f(t), @@ -421,18 +430,9 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f // new triangles every time we advance one, until each vertex // reaches t=t1 - in a way that forms the mesh we want. - // That mesh will be built up here, starting with frame vertices: - // (note initial value of mesh_idx) - let mut verts: Vec = frame.clone(); - let mut faces: Vec = vec![]; - - // 'stack' is cleared at every iteration below, and contains faces that - // may still require subdivision. - let mut stack: Vec = vec![]; - while !frontier.is_empty() { for (i, f) in frontier.iter().enumerate() { - println!("DEBUG: frontier[{}]: vert={},{},{} t={} mesh_idx={} neighbor={:?}", i, f.vert.x, f.vert.y, f.vert.z, f.t, f.mesh_idx, f.neighbor); + println!("DEBUG: frontier[{}]: vert={},{},{} t={} halfedges={:?}", i, f.vert.x, f.vert.y, f.vert.z, f.t, f.halfedges); } // Pick a vertex to advance. @@ -451,7 +451,7 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f // TODO: Fix boundary behavior here and make sure final topology // is right. - println!("DEBUG: Moving vertex {}, {:?} (t={}, frame_vert={:?})", i, v.vert, v.t, v.frame_vert); + println!("DEBUG: Moving frontier vertex {}, {:?} (t={}, frame_vert={:?})", i, v.vert, v.t, v.frame_vert); // Move this vertex further along, i.e. t + dt. (dt is set by // the furthest we can go while remaining within 'err', i.e. when we @@ -459,7 +459,7 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f // diverge from the trajectory of the continuous transformation). let mut dt = (t1 - t0) / 100.0; let vf = v.frame_vert; - for iter in 0..100 { + for iter in 0..0 /*100*/ { // DEBUG: Re-enable // Consider an edge from f(v.t)*vf to f(v.t + dt)*vf. // These two endpoints have zero error from the trajectory // (because they are directly on it). @@ -502,30 +502,103 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f println!("l1={} l2={} l3={}", l1, l2, l3); */ - // Add this vertex to our mesh: - let v_next_idx = verts.len(); - verts.push(v_next); + // Add two faces to our mesh. (They share two vertices, and thus + // the boundary in between those vertices.) + let (f1, edges1) = match v.halfedges[0] { + // However, the way we add the face depends on whether we are + // adding to an existing boundary or not: + None => { + let neighbor = &frontier[(i + n - 1) % n]; + println!("DEBUG: add_face()"); + let (f1, edges1) = mesh.add_face([ + VertSpec::New(v_next), + match v.vert_idx { + None => VertSpec::New(v.vert), + Some(idx) => VertSpec::Idx(idx), + }, + match neighbor.vert_idx { + None => VertSpec::New(neighbor.vert), + Some(idx) => VertSpec::Idx(idx), + }, + ]); + + if neighbor.vert_idx.is_none() { + // If neighbor.vert_idx is None, then we had to + // add its vertex to the mesh for the face we just + // made - so mark it in the frontier: + frontier[(i + n - 1) % n].vert_idx = Some(mesh.halfedges[edges1[2]].vert); + // edges[2] is because this is the position of + // neighbor.vert_idx in add_face. + } + + (f1, edges1) + }, + Some(edge_idx) => { + println!("DEBUG: add_face_twin1({},{})", edge_idx, v_next); + mesh.add_face_twin1(edge_idx, v_next) + }, + }; + println!("DEBUG: edges1={:?}", edges1); + + // DEBUG + mesh.check(); + mesh.print(); + + // edge2 should be: the half-edge connecting the 'neighbor' frontier + // vertex to v_next + let (f2, edge2) = match v.halfedges[1] { + // Likewise, the way we add the second face depends on + // the same (but for the other side). Regardless, + // they share the boundary between v_next and v.vert - which + // is edges1[0]. + None => { + let neighbor = &frontier[(i + 1) % n]; + let (f2, edges) = mesh.add_face_twin1(edges1[0], neighbor.vert); + + if neighbor.vert_idx.is_none() { + // Reasoning here is identical to "If neighbor.vert_idx + // is None..." above: + frontier[(i + 1) % n].vert_idx = Some(mesh.halfedges[edges[2]].vert); + } + + (f2, edges[1]) + }, + Some(edge_idx) => { + let (f2, edges) = mesh.add_face_twin2(edge_idx, edges1[2]); + // TODO: Why edges1[2]? + (f2, edges[2]) + }, + }; + println!("DEBUG: edge2={}", edge2); + + // DEBUG + println!("DEBUG: 2nd face"); + mesh.check(); + mesh.print(); + + // The 'shared' half-edge should start at v.vert - hence edges[1]. + // and add two faces: - let face_idx = faces.len(); + /* faces.append(&mut vec![ v_next_idx, v.mesh_idx, frontier[v.neighbor[0]].mesh_idx, // face_idx v.mesh_idx, v_next_idx, frontier[v.neighbor[1]].mesh_idx, // face_idx + 1 ]); + */ // Replace this vertex in the frontier: frontier[i] = frontierVert { vert: v_next, frame_vert: vf, - mesh_idx: v_next_idx, t: t, - neighbor: v.neighbor, - side_faces: [(face_idx, true), (face_idx + 1, true)], + halfedges: [Some(edges1[2]), Some(edge2)], + // Note that edges1[2] *starts* at the new vertex, and + // edge2 *ends* at it. + // DEBUG: The above comment is wrong, but why? + vert_idx: Some(mesh.halfedges[edges1[2]].vert), }; - // Note above that we added two edges that include v_next_idx: - // (frontier[v.neighbor[0]].mesh_idx, v_next_idx) - // (frontier[v.neighbor[1]].mesh_idx, v_next_idx) - // hence, side_faces has the respective face for each. + /* // Also add these faces to the stack of triangles to check for // subdivision. They may be replaced. let f0 = &frontier[v.neighbor[0]]; @@ -548,7 +621,8 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f // several vertices sit in the same trajectory (thus, same 'frame' // vertex). - while !stack.is_empty() { + // TODO: Move this logic elsewhere + while false && !stack.is_empty() { let face = stack.pop().unwrap(); println!("DEBUG: Examining face: {:?}", face); let v0 = verts[face.verts[0]]; @@ -636,7 +710,8 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f // If they refer to the same face I may be invalidating // something here! } + */ } - return Mesh { verts, faces }; + return Mesh { faces: vec![], verts: vec![] }; } \ No newline at end of file