Meh, kind of over trying to roll my own subdivision crap

This commit is contained in:
Chris Hodapp 2020-06-16 16:15:02 -04:00
parent 701b1df915
commit 545b7c9a60
4 changed files with 105 additions and 78 deletions

View File

@ -2,17 +2,10 @@
## Highest priority: ## Highest priority:
- Add better docs and possibly abstraction to `dcel.rs`, *before* - Just scrap `parametric_mesh` as much as possible and use existing
it's widely used. Possibly rename too. tools (e.g. OpenSubdiv) because this DCEL method is just painful for
- `dcel.rs` needs a helper method for subdivision. what it is and I have some questions on how it can even work
- Redo `examples::parametric_mesh` along the following lines: theoretically.
- 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
- Get identical or near-identical meshes to `ramhorn_branch` from - Get identical or near-identical meshes to `ramhorn_branch` from
Python. (Should just be a matter of tweaking parameters.) Python. (Should just be a matter of tweaking parameters.)
- Look at performance. - Look at performance.

View File

@ -134,10 +134,9 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
} }
for (i,h) in self.halfedges.iter().enumerate() { for (i,h) in self.halfedges.iter().enumerate() {
let twin = if h.has_twin { let twin = if h.has_twin { format!(", twin half-edge {}", h.twin_halfedge)
format!(", twin half-edge {}", h.twin_halfedge)
} else { } else {
String::from("") format!(", no twin")
}; };
let v1 = self.verts[h.vert].v; let v1 = self.verts[h.vert].v;
let v2 = self.verts[self.halfedges[h.next_halfedge].vert].v; let v2 = self.verts[self.halfedges[h.next_halfedge].vert].v;
@ -485,22 +484,43 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
(f_n, [e_n, e_n+1, e_n+2]) (f_n, [e_n, e_n+1, e_n+2])
} }
pub fn split_face(&mut self, face: usize, verts: Vec<V>) { /// 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<V>) -> Option<(Vec<usize>, Vec<usize>)> {
// 'verts' maps 1:1 to vertices for 'face' (i.e. face_to_verts). // 'verts' maps 1:1 to vertices for 'face' (i.e. face_to_verts).
let mut edge_idx = self.faces[face].halfedge; let mut edge_idx = self.faces[face].halfedge;
let n = verts.len(); let n = verts.len();
// let mut faces_new = vec![];
let new_edges: Vec<(usize, usize)> = verts.iter().map(|v| { let mut faces_upd = vec![];
println!("DEBUG: halfedge 3: {:?}", self.halfedges[3]); let mut fail = false;
println!("DEBUG: halfedge {}: {:?}", self.halfedges[3].next_halfedge, self.halfedges[self.halfedges[3].next_halfedge]); let new_edges: Vec<(usize, usize)> = verts.iter().map(|v| {
// As we iterate over every vertex, we walk the half-edges: // As we iterate over every vertex, we walk the half-edges:
let mut edge = self.halfedges[edge_idx].clone(); let mut edge = self.halfedges[edge_idx].clone();
if !edge.has_twin { 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 // TODO: Remove the above limitation and just don't try to split
// nonexistent twins. I think all logic works the same way. // nonexistent twins. I think all logic works the same way.
@ -516,10 +536,8 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
// Half of edge_idx is split into j_edge. // Half of edge_idx is split into j_edge.
// Half of twin_idx (its twin) is split into i_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: // This is where the vertex will be inserted:
let v_idx = self.num_verts; let v_idx = self.num_verts;
println!("DEBUG: adding v_idx={}", v_idx);
self.verts.push(DCELVertex { self.verts.push(DCELVertex {
v: *v, v: *v,
halfedge: i_edge, // j_edge is also fine halfedge: i_edge, // j_edge is also fine
@ -531,7 +549,6 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
twin.twin_halfedge = j_edge; twin.twin_halfedge = j_edge;
let i_next = twin.next_halfedge; 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 { self.halfedges.push(DCELHalfEdge {
vert: v_idx, vert: v_idx,
@ -541,8 +558,6 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
next_halfedge: i_next, next_halfedge: i_next,
prev_halfedge: twin_idx, prev_halfedge: twin_idx,
}); // i_edge }); // 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 { self.halfedges.push(DCELHalfEdge {
vert: v_idx, vert: v_idx,
face: 0, // This is set properly in the next loop face: 0, // This is set properly in the next loop
@ -551,7 +566,6 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
next_halfedge: j_next, next_halfedge: j_next,
prev_halfedge: edge_idx, prev_halfedge: edge_idx,
}); // j_edge }); // 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; self.num_halfedges += 2;
@ -565,7 +579,9 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
r r
}).collect(); }).collect();
println!("DEBUG: {:?}", new_edges); if fail {
return None;
}
// We then must connect some edges up 'across' vertices // We then must connect some edges up 'across' vertices
// in order to form the smaller face at each vertex. // in order to form the smaller face at each vertex.
@ -585,9 +601,6 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
// And the face here: // And the face here:
let face_new = self.num_faces; 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, // So, the vertex for i0 had two halfedges (one pointing in,
// one pointing out). We split both those half-edges earlier. // one pointing out). We split both those half-edges earlier.
// en_idx & ep_idx are the halves that are nearest the // en_idx & ep_idx are the halves that are nearest the
@ -604,7 +617,6 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
next_halfedge: ep_idx, next_halfedge: ep_idx,
prev_halfedge: en_idx, prev_halfedge: en_idx,
}); // e_cross_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 // It also requires a twin half-edge. These all form a single
// central face with each edge sharing a boundary with the // central face with each edge sharing a boundary with the
@ -617,24 +629,21 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
next_halfedge: 0, // TODO next_halfedge: 0, // TODO
prev_halfedge: 0, // TODO prev_halfedge: 0, // TODO
}); // e_twin_idx }); // 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; self.num_halfedges += 2;
// en/ep also need directed to 'new' edges and each other // en/ep also need directed to 'new' edges and each other
self.halfedges[en_idx].prev_halfedge = ep_idx; self.halfedges[en_idx].prev_halfedge = ep_idx;
self.halfedges[en_idx].next_halfedge = e_cross_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].next_halfedge = en_idx;
self.halfedges[ep_idx].prev_halfedge = e_cross_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.halfedges[ep_idx].face = face_new;
self.faces.push(DCELFace { self.faces.push(DCELFace {
halfedge: e_cross_idx, // en_idx or ep_idx is fine too halfedge: e_cross_idx, // en_idx or ep_idx is fine too
}); }); // face_new
faces_new.push(face_new);
self.num_faces += 1; self.num_faces += 1;
// We also need to split the opposite side to make the two // We also need to split the opposite side to make the two
@ -674,8 +683,10 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
self.faces.push(DCELFace { self.faces.push(DCELFace {
halfedge: outer2, // base2 or edge_side2 is fine too halfedge: outer2, // base2 or edge_side2 is fine too
}); });
faces_new.push(self.num_faces);
self.num_faces += 1; self.num_faces += 1;
self.faces[face1].halfedge = outer1; // base1 or edge_side1 is fine too 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].next_halfedge = edge_side1;
self.halfedges[outer1].prev_halfedge = base1; self.halfedges[outer1].prev_halfedge = base1;
@ -691,13 +702,9 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
self.halfedges[base2].prev_halfedge = outer2; self.halfedges[base2].prev_halfedge = outer2;
self.halfedges[base2].next_halfedge = edge_side2; 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 e_twin_idx
}).collect(); }).collect();
println!("DEBUG: cross_edges={:?}", twin_edges);
for i0 in 0..n { for i0 in 0..n {
let i1 = (i0 + 1) % n; let i1 = (i0 + 1) % n;
@ -709,6 +716,9 @@ impl<V: Copy + std::fmt::Debug> DCELMesh<V> {
// We need something at this index, and the other three already have // We need something at this index, and the other three already have
// indices, so reuse it for the smaller central face: // indices, so reuse it for the smaller central face:
self.faces[face].halfedge = e_twin_idx; self.faces[face].halfedge = e_twin_idx;
faces_upd.push(face);
return Some((faces_new, faces_upd));
} }
pub fn convert_mesh<F>(&self, f: F) -> Mesh pub fn convert_mesh<F>(&self, f: F) -> Mesh

View File

@ -1268,11 +1268,11 @@ pub fn test_parametric() -> Mesh {
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, 4); let base_verts = util::subdivide_cycle(&base_verts, 2);
//let base_verts = util::subdivide_cycle(&base_verts, 16); //let base_verts = util::subdivide_cycle(&base_verts, 16);
let t0 = 0.0; let t0 = 0.0;
let t1 = 15.0; let t1 = 15;
let xform = |t: f32| -> Transform { let xform = |t: f32| -> Transform {
id(). id().
translate(0.0, 0.0, t/5.0). translate(0.0, 0.0, t/5.0).
@ -1280,7 +1280,7 @@ pub fn test_parametric() -> Mesh {
scale((0.8).powf(t)) 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) { 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!("f3 verts: {:?}", mesh.face_to_verts(f3));
println!("f4 verts: {:?}", mesh.face_to_verts(f4)); println!("f4 verts: {:?}", mesh.face_to_verts(f4));
println!("DCEL mesh: "); //println!("DCEL mesh: ");
mesh.print(); //mesh.print();
mesh.split_face(f1, vec![ let faces = mesh.full_subdiv_face(f1, vec![
vertex(-0.5, 0.0, 0.0), vertex(-0.5, 0.0, 0.0),
vertex(0.0, 0.5, 0.0), vertex(0.0, 0.5, 0.0),
vertex(0.0, 0.0, 0.0), vertex(0.0, 0.0, 0.0),
]); ]);
println!("full_subdiv_face returned: {:?}", faces);
println!("DCEL mesh after subdiv"); //println!("DCEL mesh after subdiv");
mesh.check(); //mesh.check();
mesh.print(); //mesh.print();
let mesh_conv = mesh.convert_mesh(|i| i); let mesh_conv = mesh.convert_mesh(|i| i);

View File

@ -1,6 +1,7 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use std::rc::Rc; use std::rc::Rc;
use std::f32; use std::f32;
use std::collections::HashMap;
use crate::mesh::{Mesh, MeshFunc, VertexUnion}; use crate::mesh::{Mesh, MeshFunc, VertexUnion};
use crate::xform::{Transform, Vertex}; use crate::xform::{Transform, Vertex};
@ -658,11 +659,25 @@ pub fn parametric_mesh<F>(frame: Vec<Vertex>, f: F, t0: f32, t1: f32, max_err: f
// A stack of face indices for faces in 'mesh' that are still // A stack of face indices for faces in 'mesh' that are still
// undergoing subdivision // undergoing subdivision
let mut stack: Vec<usize> = (0..mesh.num_faces).collect();
while !stack.is_empty() { let mut stack: HashMap<usize, usize> = (0..mesh.num_faces).map(|i| (i, 0)).collect();
let face = stack.pop().unwrap();
println!("DEBUG: Examining face: {:?}", face); 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); let v_idx = mesh.face_to_verts(face);
if v_idx.len() != 3 { if v_idx.len() != 3 {
@ -703,17 +718,18 @@ pub fn parametric_mesh<F>(frame: Vec<Vertex>, f: F, t0: f32, t1: f32, max_err: f
let d = p.xyz().dot(&normal); 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: 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); 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) { if (d <= max_err) {
// This triangle is already in the mesh, and already popped // This triangle is already in the mesh, and already popped
// off of the stack. We're done. // off of the stack. We're done.
@ -728,28 +744,35 @@ pub fn parametric_mesh<F>(frame: Vec<Vertex>, f: F, t0: f32, t1: f32, max_err: f
// This split is done in 'parameter' space: // This split is done in 'parameter' space:
let pairs = [(0,1), (1,2), (0,2)]; let pairs = [(0,1), (1,2), (0,2)];
let mut mids: Vec<Vertex> = pairs.iter().map(|(i,j)| { let mut mids: Vec<VertexTrajectory> = pairs.iter().map(|(i,j)| {
let t = (tr[*i].t + tr[*j].t) / 2.0; let t = (tr[*i].t + tr[*j].t) / 2.0;
let v = (tr[*i].frame_vert + tr[*j].frame_vert) / 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(); }).collect();
// DEBUG // TODO: Get indices of added faces and put these on the stack too
//let n = verts.len(); match mesh.full_subdiv_face(face, mids) {
// Index n+0 is (0,1), n+1 is (1,2), n+2 is (0,2) None => {},
/* Some((new, upd)) => {
verts.append(&mut mids);
faces[face.face] = n + 0; // The 'updated' faces can remain on the stack if they
faces[face.face + 1] = n + 1; // already were there. If they were taken off of the stack
faces[face.face + 2] = n + 2; // because they were too small, they are now even smaller,
faces.extend_from_slice(&[ // so it is fine to leave them off.
face.verts[0], n + 0, n + 2, stack.extend(new.iter().map(|i| (*i, count + 1)));
face.verts[1], n + 1, n + 0, stack.extend(upd.iter().map(|i| (*i, count + 1)));
face.verts[2], n + 2, n + 1, // TODO: Why does the resultant mesh have holes if I do this
//n + 0, n + 1, n + 2, // and then increase max_subdiv?
]); },
*/
} }
}
//mesh.print();
return mesh.convert_mesh(|i| i.vert ); return mesh.convert_mesh(|i| i.vert );
} }