From 545b7c9a6054e360230db78875da23192bfb026c Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Tue, 16 Jun 2020 16:15:02 -0400 Subject: [PATCH] Meh, kind of over trying to roll my own subdivision crap --- README.md | 15 +++------ src/dcel.rs | 68 +++++++++++++++++++++++------------------ src/examples.rs | 19 ++++++------ src/rule.rs | 81 +++++++++++++++++++++++++++++++------------------ 4 files changed, 105 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index d5af0d8..b3589e4 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,10 @@ ## Highest priority: -- 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: - - DONE: 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 +- 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. - 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 2ae7800..2940028 100644 --- a/src/dcel.rs +++ b/src/dcel.rs @@ -134,10 +134,9 @@ impl DCELMesh { } for (i,h) in self.halfedges.iter().enumerate() { - let twin = if h.has_twin { - format!(", twin half-edge {}", h.twin_halfedge) + let twin = if h.has_twin { format!(", twin half-edge {}", h.twin_halfedge) } else { - String::from("") + format!(", no twin") }; let v1 = self.verts[h.vert].v; let v2 = self.verts[self.halfedges[h.next_halfedge].vert].v; @@ -485,22 +484,43 @@ impl DCELMesh { (f_n, [e_n, e_n+1, e_n+2]) } - pub fn split_face(&mut self, face: usize, verts: Vec) { + /// Splits a face (assumed to be a triangle) in this mesh by splitting + /// its half-edges, in order, at the vertices given, and then creating + /// half-edges between each pair of them. + /// + /// This returns; (new face indices, updated face indices). That is, + /// the first vector is new face indices that were created, and the + /// second is existing face indices that were reused (but the faces + /// were updated). + /// + /// This splits the given face into 4 smaller faces, and it splits the + /// three *bordering* faces each into 2 faces. That is, this + /// increases the number of faces by 6 and the number of vertices + /// by 3. + /// + /// Right now, this requires that every halfedge of `face` has a twin. + /// If this is not the case, this returns `None` and does not subdivide. + /// This behavior will likely be changed in the future. + /// + /// Disclaimer: This code is completely awful; just don't use it. + pub fn full_subdiv_face(&mut self, face: usize, verts: Vec) -> Option<(Vec, Vec)> { // 'verts' maps 1:1 to vertices for 'face' (i.e. face_to_verts). let mut edge_idx = self.faces[face].halfedge; let n = verts.len(); - // - let new_edges: Vec<(usize, usize)> = verts.iter().map(|v| { + let mut faces_new = vec![]; + let mut faces_upd = vec![]; - println!("DEBUG: halfedge 3: {:?}", self.halfedges[3]); - println!("DEBUG: halfedge {}: {:?}", self.halfedges[3].next_halfedge, self.halfedges[self.halfedges[3].next_halfedge]); + let mut fail = false; + let new_edges: Vec<(usize, usize)> = verts.iter().map(|v| { // As we iterate over every vertex, we walk the half-edges: let mut edge = self.halfedges[edge_idx].clone(); if !edge.has_twin { - panic!("Halfedge {} has no twin, and split_face (for now) requires twins", edge_idx); + println!("Halfedge {} has no twin, and split_face (for now) requires twins", edge_idx); + fail = true; + return (0,0); } // TODO: Remove the above limitation and just don't try to split // nonexistent twins. I think all logic works the same way. @@ -516,10 +536,8 @@ impl DCELMesh { // Half of edge_idx is split into j_edge. // Half of twin_idx (its twin) is split into i_edge. - println!("DEBUG: edge_idx={} next_idx={} twin_idx={} i_edge={} j_edge={}", edge_idx, next_idx, twin_idx, i_edge, j_edge); // This is where the vertex will be inserted: let v_idx = self.num_verts; - println!("DEBUG: adding v_idx={}", v_idx); self.verts.push(DCELVertex { v: *v, halfedge: i_edge, // j_edge is also fine @@ -531,7 +549,6 @@ impl DCELMesh { twin.twin_halfedge = j_edge; let i_next = twin.next_halfedge; - println!("DEBUG: new twin of {} = {}; new twin of {} = {}", edge_idx, i_edge, twin_idx, j_edge); self.halfedges.push(DCELHalfEdge { vert: v_idx, @@ -541,8 +558,6 @@ impl DCELMesh { next_halfedge: i_next, prev_halfedge: twin_idx, }); // i_edge - println!("DEBUG: edge {}: vert {} twin {} next {} prev {} (ends at vertex {})", i_edge, v_idx, edge_idx, i_next, twin_idx, self.halfedges[self.halfedges[twin_idx].next_halfedge].vert); - self.halfedges.push(DCELHalfEdge { vert: v_idx, face: 0, // This is set properly in the next loop @@ -551,7 +566,6 @@ impl DCELMesh { next_halfedge: j_next, prev_halfedge: edge_idx, }); // j_edge - println!("DEBUG: edge {}: vert {} twin {} next {} prev {} (ends at vertex {})", j_edge, v_idx, twin_idx, j_next, edge_idx, self.halfedges[self.halfedges[edge_idx].next_halfedge].vert); self.num_halfedges += 2; @@ -565,7 +579,9 @@ impl DCELMesh { r }).collect(); - println!("DEBUG: {:?}", new_edges); + if fail { + return None; + } // We then must connect some edges up 'across' vertices // in order to form the smaller face at each vertex. @@ -585,9 +601,6 @@ impl DCELMesh { // And the face here: let face_new = self.num_faces; - println!("DEBUG: i0={} i1={} ep_idx={} en_idx={} e_cross_idx={} e_twin_idx={} face_new={}", - i0, i1, ep_idx, en_idx, e_cross_idx, e_twin_idx, face_new); - // So, the vertex for i0 had two halfedges (one pointing in, // one pointing out). We split both those half-edges earlier. // en_idx & ep_idx are the halves that are nearest the @@ -604,7 +617,6 @@ impl DCELMesh { next_halfedge: ep_idx, prev_halfedge: en_idx, }); // e_cross_idx - println!("DEBUG: edge {}: vert {} twin {} next {} prev {} (ends at vertex {})", e_cross_idx, self.halfedges[en_idx].vert, e_twin_idx, ep_idx, en_idx, self.halfedges[self.halfedges[e_cross_idx].next_halfedge].vert); // It also requires a twin half-edge. These all form a single // central face with each edge sharing a boundary with the @@ -617,24 +629,21 @@ impl DCELMesh { next_halfedge: 0, // TODO prev_halfedge: 0, // TODO }); // e_twin_idx - println!("DEBUG: edge {}: vert {} twin {} next/prev incorrect", e_twin_idx, self.halfedges[ep_idx].vert, e_cross_idx); - self.num_halfedges += 2; // en/ep also need directed to 'new' edges and each other self.halfedges[en_idx].prev_halfedge = ep_idx; self.halfedges[en_idx].next_halfedge = e_cross_idx; - println!("DEBUG: edge {}: now next {} prev {} ends at vert {}", en_idx, e_cross_idx, ep_idx, self.halfedges[self.halfedges[en_idx].next_halfedge].vert); self.halfedges[ep_idx].next_halfedge = en_idx; self.halfedges[ep_idx].prev_halfedge = e_cross_idx; - println!("DEBUG: edge {}: now next {} prev {} ends at vert {}", ep_idx, en_idx, e_cross_idx, self.halfedges[self.halfedges[ep_idx].next_halfedge].vert); self.halfedges[ep_idx].face = face_new; self.faces.push(DCELFace { halfedge: e_cross_idx, // en_idx or ep_idx is fine too - }); + }); // face_new + faces_new.push(face_new); self.num_faces += 1; // We also need to split the opposite side to make the two @@ -674,8 +683,10 @@ impl DCELMesh { self.faces.push(DCELFace { halfedge: outer2, // base2 or edge_side2 is fine too }); + faces_new.push(self.num_faces); self.num_faces += 1; self.faces[face1].halfedge = outer1; // base1 or edge_side1 is fine too + faces_upd.push(face1); self.halfedges[outer1].next_halfedge = edge_side1; self.halfedges[outer1].prev_halfedge = base1; @@ -691,13 +702,9 @@ impl DCELMesh { self.halfedges[base2].prev_halfedge = outer2; self.halfedges[base2].next_halfedge = edge_side2; - println!("DEBUG: base1={} base2={} outer1={} outer2={} face1={} face2={} edge_side1={} edge_side2={}", - base1, base2, outer1, outer2, face1, face2, edge_side1, edge_side2); e_twin_idx }).collect(); - println!("DEBUG: cross_edges={:?}", twin_edges); - for i0 in 0..n { let i1 = (i0 + 1) % n; @@ -709,6 +716,9 @@ impl DCELMesh { // We need something at this index, and the other three already have // indices, so reuse it for the smaller central face: self.faces[face].halfedge = e_twin_idx; + faces_upd.push(face); + + return Some((faces_new, faces_upd)); } pub fn convert_mesh(&self, f: F) -> Mesh diff --git a/src/examples.rs b/src/examples.rs index 260fc21..3382e65 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -1268,11 +1268,11 @@ pub fn test_parametric() -> Mesh { vertex( 1.0, 1.0, 0.0), vertex( 1.0, -1.0, 0.0), ]; - let base_verts = util::subdivide_cycle(&base_verts, 4); + let base_verts = util::subdivide_cycle(&base_verts, 2); //let base_verts = util::subdivide_cycle(&base_verts, 16); let t0 = 0.0; - let t1 = 15.0; + let t1 = 15; let xform = |t: f32| -> Transform { id(). translate(0.0, 0.0, t/5.0). @@ -1280,7 +1280,7 @@ pub fn test_parametric() -> Mesh { scale((0.8).powf(t)) }; - crate::rule::parametric_mesh(base_verts, xform, t0, t1, 0.001) + crate::rule::parametric_mesh(base_verts, xform, t0, t1, 0.01) } pub fn test_dcel(fname: &str) { @@ -1325,18 +1325,19 @@ pub fn test_dcel(fname: &str) { println!("f3 verts: {:?}", mesh.face_to_verts(f3)); println!("f4 verts: {:?}", mesh.face_to_verts(f4)); - println!("DCEL mesh: "); - mesh.print(); + //println!("DCEL mesh: "); + //mesh.print(); - mesh.split_face(f1, vec![ + let faces = mesh.full_subdiv_face(f1, vec![ vertex(-0.5, 0.0, 0.0), vertex(0.0, 0.5, 0.0), vertex(0.0, 0.0, 0.0), ]); + println!("full_subdiv_face returned: {:?}", faces); - println!("DCEL mesh after subdiv"); - mesh.check(); - mesh.print(); + //println!("DCEL mesh after subdiv"); + //mesh.check(); + //mesh.print(); let mesh_conv = mesh.convert_mesh(|i| i); diff --git a/src/rule.rs b/src/rule.rs index c50c3c5..504bd9a 100644 --- a/src/rule.rs +++ b/src/rule.rs @@ -1,6 +1,7 @@ use std::borrow::Borrow; use std::rc::Rc; use std::f32; +use std::collections::HashMap; use crate::mesh::{Mesh, MeshFunc, VertexUnion}; use crate::xform::{Transform, Vertex}; @@ -658,11 +659,25 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f // A stack of face indices for faces in 'mesh' that are still // undergoing subdivision - let mut stack: Vec = (0..mesh.num_faces).collect(); - while !stack.is_empty() { - let face = stack.pop().unwrap(); - println!("DEBUG: Examining face: {:?}", face); + let mut stack: HashMap = (0..mesh.num_faces).map(|i| (i, 0)).collect(); + + let max_subdiv = 1; + + // This is masked off because it's just horrible: + while false && !stack.is_empty() { + + let (face, count) = match stack.iter().next() { + None => break, + Some((f, c)) => (*f, *c), + }; + stack.remove(&face); + + println!("DEBUG: Examining face: {:?} ({} subdivs)", face, count); + + if count >= max_subdiv { + continue; + } let v_idx = mesh.face_to_verts(face); if v_idx.len() != 3 { @@ -703,17 +718,18 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f let d = p.xyz().dot(&normal); + if d.is_nan() { + println!("DEBUG: p={:?} normal={:?}", p, normal); + println!("DEBUG: a={:?} b={:?}", a, b); + println!("DEBUG: v0={:?} v1={:?} v2={:?}", v0, v1, v2); + //panic!("Distance is NaN?"); + println!("DEBUG: distance is NaN?"); + continue; + } + println!("DEBUG: t_mid={} v_mid={},{},{} p={},{},{}", t_mid, v_mid.x, v_mid.y, v_mid.z, p.x, p.y, p.z); println!("DEBUG: d={}", d); - // DEBUG - /* - let n = verts.len(); - verts.push(p); - faces.push(face.verts[0]); - faces.push(face.verts[1]); - faces.push(n); - */ if (d <= max_err) { // This triangle is already in the mesh, and already popped // off of the stack. We're done. @@ -728,28 +744,35 @@ pub fn parametric_mesh(frame: Vec, f: F, t0: f32, t1: f32, max_err: f // This split is done in 'parameter' space: let pairs = [(0,1), (1,2), (0,2)]; - let mut mids: Vec = pairs.iter().map(|(i,j)| { + let mut mids: Vec = pairs.iter().map(|(i,j)| { let t = (tr[*i].t + tr[*j].t) / 2.0; let v = (tr[*i].frame_vert + tr[*j].frame_vert) / 2.0; - f(t).mtx * v + VertexTrajectory { + vert: f(t).mtx * v, + frame_vert: v, + t: t, + } }).collect(); - // DEBUG - //let n = verts.len(); - // Index n+0 is (0,1), n+1 is (1,2), n+2 is (0,2) - /* - verts.append(&mut mids); - faces[face.face] = n + 0; - faces[face.face + 1] = n + 1; - faces[face.face + 2] = n + 2; - faces.extend_from_slice(&[ - face.verts[0], n + 0, n + 2, - face.verts[1], n + 1, n + 0, - face.verts[2], n + 2, n + 1, - //n + 0, n + 1, n + 2, - ]); - */ + // TODO: Get indices of added faces and put these on the stack too + match mesh.full_subdiv_face(face, mids) { + None => {}, + Some((new, upd)) => { + + // The 'updated' faces can remain on the stack if they + // already were there. If they were taken off of the stack + // because they were too small, they are now even smaller, + // so it is fine to leave them off. + stack.extend(new.iter().map(|i| (*i, count + 1))); + stack.extend(upd.iter().map(|i| (*i, count + 1))); + // TODO: Why does the resultant mesh have holes if I do this + // and then increase max_subdiv? + }, + } + } + //mesh.print(); + return mesh.convert_mesh(|i| i.vert ); }