Half-made DCEL; half-broken continuous transform thingy
This commit is contained in:
parent
f2f8b833e2
commit
6bb170c7ff
@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
## Highest priority:
|
## 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
|
- Implement the continuous parametric transformations from 2020-05-07
|
||||||
in my notes. This will require some new abstractions.
|
in my notes. This will require some new abstractions.
|
||||||
|
- Document `parametric_mesh` better. It is pretty hairy and could
|
||||||
|
also benefit from some modularity.
|
||||||
- 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.
|
||||||
@ -56,6 +60,10 @@
|
|||||||
|
|
||||||
## Research Areas
|
## Research Areas
|
||||||
|
|
||||||
|
- [Geometry and Algorithms for Computer Aided Design (Hartmann)](https://www2.mathematik.tu-darmstadt.de/~ehartmann/cdgen0104.pdf)
|
||||||
|
- https://en.wikipedia.org/wiki/Surface_triangulation
|
||||||
|
- https://www.cs.cmu.edu/~quake/triangle.html
|
||||||
|
|
||||||
## Reflections & Quick Notes
|
## Reflections & Quick Notes
|
||||||
|
|
||||||
- Generalizing to space curves moves this away from the "discrete
|
- Generalizing to space curves moves this away from the "discrete
|
||||||
|
|||||||
313
src/dcel.rs
Normal file
313
src/dcel.rs
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::mesh::{Mesh};
|
||||||
|
use crate::xform::{Vertex};
|
||||||
|
|
||||||
|
/// Doubly-connected edge list mesh (or a half-edge mesh),
|
||||||
|
/// parametrized over some vertex type.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct DCELMesh<V: Copy> {
|
||||||
|
pub verts: Vec<DCELVertex<V>>,
|
||||||
|
pub faces: Vec<DCELFace>,
|
||||||
|
pub halfedges: Vec<DCELHalfEdge>,
|
||||||
|
|
||||||
|
pub num_verts: usize,
|
||||||
|
pub num_faces: usize,
|
||||||
|
pub num_halfedges: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A vertex of a mesh, combined with an arbitrary half-edge that has
|
||||||
|
/// this vertex as its origin. This is always relative to some parent
|
||||||
|
/// Mesh<V>.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct DCELVertex<V> {
|
||||||
|
/// The vertex itself.
|
||||||
|
pub v: V,
|
||||||
|
/// A half-edge (given as an index into 'halfedges');
|
||||||
|
/// arbitrary, but `mesh.halfedges[halfedge] = v` must be true
|
||||||
|
pub halfedge: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A face, given as a half-edge that lies on its boundary (and must
|
||||||
|
/// traverse it counter-clockwise). This is always relative to some
|
||||||
|
/// parent Mesh<V>, as in Vertex.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct DCELFace {
|
||||||
|
/// A boundary half-edge of this face (given as an index into
|
||||||
|
/// 'halfedges').
|
||||||
|
pub halfedge: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A half-edge, given in terms of its origin vertex, the face that the
|
||||||
|
/// half-edge lies on the boundary of, its optional "twin" half-edge that
|
||||||
|
/// lies on an adjacent face, and previous and next half-edges (to
|
||||||
|
/// traverse the boundaries of the face). This is always relative to
|
||||||
|
/// some parent Mesh<V>, as in Vertex and Face.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct DCELHalfEdge {
|
||||||
|
/// Origin vertex (given as an index into 'verts')
|
||||||
|
pub vert: usize,
|
||||||
|
/// Face this half-edge lies on the boundary of (given as an index
|
||||||
|
/// into 'faces')
|
||||||
|
pub face: usize,
|
||||||
|
/// If false, ignore twin_halfedge. (If this is true, then it must
|
||||||
|
/// also be true for the twin.)
|
||||||
|
pub has_twin: bool,
|
||||||
|
/// The twin half-edge (given as an index into 'halfedges').
|
||||||
|
/// The twin of the twin must point back to this HalfEdge.
|
||||||
|
pub twin_halfedge: usize,
|
||||||
|
/// The next half-edge on the boundary (given as an index into
|
||||||
|
/// 'halfedges'). 'prev_halfedge' of this half-edge must point
|
||||||
|
/// back to this same HalfEdge. Repeatedly following 'next_halfedge'
|
||||||
|
/// must also lead back to this same HalfEdge.
|
||||||
|
pub next_halfedge: usize,
|
||||||
|
/// The previous half-edge on the boundary (given as an index into
|
||||||
|
/// 'halfedges'). 'next_halfedge' of this half-edge must point
|
||||||
|
/// back to this HalfEdge. Repeatedly following 'prev_halfedge'
|
||||||
|
/// must also lead back to this same HalfEdge.
|
||||||
|
pub prev_halfedge: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Copy + std::fmt::Debug> fmt::Display for DCELMesh<V> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
|
||||||
|
let v_strs: Vec<String> = self.verts.iter().enumerate().map(|(i,v)| {
|
||||||
|
format!("v{}={:?}", i, v.v)
|
||||||
|
}).collect();
|
||||||
|
let v_str = v_strs.join(",");
|
||||||
|
|
||||||
|
let f_strs: Vec<String> = self.faces.iter().enumerate().map(|(i,f)| {
|
||||||
|
format!("f{}=e{}", i, f.halfedge)
|
||||||
|
}).collect();
|
||||||
|
let f_str = f_strs.join(",");
|
||||||
|
|
||||||
|
let e_strs: Vec<String> = self.halfedges.iter().enumerate().map(|(i,h)| {
|
||||||
|
let twin = if h.has_twin {
|
||||||
|
format!(" tw{}", h.twin_halfedge)
|
||||||
|
} else {
|
||||||
|
String::from("")
|
||||||
|
};
|
||||||
|
format!("e{}=v{} f{}{} n{} p{}", i, h.vert, h.face, twin, h.next_halfedge, h.prev_halfedge)
|
||||||
|
}).collect();
|
||||||
|
let e_str = e_strs.join(",");
|
||||||
|
|
||||||
|
write!(f, "DCELMesh({} verts, {}; {} faces, {}; {} halfedges, {})",
|
||||||
|
self.num_verts, v_str,
|
||||||
|
self.num_faces, f_str,
|
||||||
|
self.num_halfedges, e_str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Copy> DCELMesh<V> {
|
||||||
|
|
||||||
|
pub fn new() -> DCELMesh<V> {
|
||||||
|
DCELMesh {
|
||||||
|
verts: vec![],
|
||||||
|
faces: vec![],
|
||||||
|
halfedges: vec![],
|
||||||
|
num_verts: 0,
|
||||||
|
num_faces: 0,
|
||||||
|
num_halfedges: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn face_to_halfedges(&self, face_idx: usize) -> Vec<usize> {
|
||||||
|
let mut edges: Vec<usize> = vec![];
|
||||||
|
let start_idx = self.faces[face_idx].halfedge;
|
||||||
|
edges.push(start_idx);
|
||||||
|
|
||||||
|
let mut idx = self.halfedges[start_idx].next_halfedge;
|
||||||
|
while start_idx != idx {
|
||||||
|
edges.push(idx);
|
||||||
|
idx = self.halfedges[idx].next_halfedge;
|
||||||
|
}
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn face_to_verts(&self, face_idx: usize) -> Vec<usize> {
|
||||||
|
self.face_to_halfedges(face_idx).iter().map(|e| {
|
||||||
|
self.halfedges[*e].vert
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a face with no connections
|
||||||
|
pub fn add_face(&mut self, verts: [V; 3]) -> usize {
|
||||||
|
// Vertices will be at indices v_n, v_n+1, v_n+2:
|
||||||
|
let v_n = self.num_verts;
|
||||||
|
// 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:
|
||||||
|
let e_n = self.num_halfedges;
|
||||||
|
|
||||||
|
// Half-edges and vertices can be inserted both at once:
|
||||||
|
for i in 0..3 {
|
||||||
|
let n = (i + 1) % 3;
|
||||||
|
let p = (i + 2) % 3;
|
||||||
|
self.halfedges.push(DCELHalfEdge {
|
||||||
|
vert: v_n + i,
|
||||||
|
face: f_n,
|
||||||
|
has_twin: false,
|
||||||
|
twin_halfedge: 0,
|
||||||
|
next_halfedge: e_n + n,
|
||||||
|
prev_halfedge: e_n + p,
|
||||||
|
});
|
||||||
|
self.verts.push(DCELVertex {
|
||||||
|
v: verts[i],
|
||||||
|
halfedge: e_n + i,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.num_halfedges += 3;
|
||||||
|
self.num_verts += 3;
|
||||||
|
|
||||||
|
// Finally, add the face (any halfedge is fine):
|
||||||
|
self.faces.push(DCELFace { halfedge: e_n });
|
||||||
|
self.num_faces += 1;
|
||||||
|
|
||||||
|
f_n
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a face that lies on an existing boundary - i.e. one half-edge
|
||||||
|
/// has a twin half-edge already on the mesh. As this gives two
|
||||||
|
/// vertices, only one other vertex needs specified.
|
||||||
|
pub fn add_face_twin1(&mut self, twin: usize, vert: V) -> usize {
|
||||||
|
// 'vert' will be at index v_n:
|
||||||
|
let v_n = self.num_verts;
|
||||||
|
// 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:
|
||||||
|
let e_n = self.num_halfedges;
|
||||||
|
|
||||||
|
// Note the reversal of direction
|
||||||
|
let twin_halfedge = &self.halfedges[twin];
|
||||||
|
let v1 = self.halfedges[twin_halfedge.next_halfedge].vert;
|
||||||
|
let v2 = twin_halfedge.vert;
|
||||||
|
|
||||||
|
// Insert 'twin' half-edge first:
|
||||||
|
self.halfedges.push(DCELHalfEdge {
|
||||||
|
vert: v1,
|
||||||
|
face: f_n,
|
||||||
|
has_twin: true,
|
||||||
|
twin_halfedge: twin,
|
||||||
|
next_halfedge: e_n + 1,
|
||||||
|
prev_halfedge: e_n + 2,
|
||||||
|
});
|
||||||
|
self.halfedges[twin].has_twin = true;
|
||||||
|
self.halfedges[twin].twin_halfedge = e_n;
|
||||||
|
self.halfedges.push(DCELHalfEdge {
|
||||||
|
vert: v2,
|
||||||
|
face: f_n,
|
||||||
|
has_twin: false,
|
||||||
|
twin_halfedge: 0,
|
||||||
|
next_halfedge: e_n + 2,
|
||||||
|
prev_halfedge: e_n,
|
||||||
|
});
|
||||||
|
self.halfedges.push(DCELHalfEdge {
|
||||||
|
vert: v_n,
|
||||||
|
face: f_n,
|
||||||
|
has_twin: false,
|
||||||
|
twin_halfedge: 0,
|
||||||
|
next_halfedge: e_n,
|
||||||
|
prev_halfedge: e_n + 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.num_halfedges += 3;
|
||||||
|
|
||||||
|
// Since the 2nd halfedge we inserted (e_n + 1) has origin v_n:
|
||||||
|
self.verts.push(DCELVertex {
|
||||||
|
v: vert,
|
||||||
|
halfedge: e_n + 1,
|
||||||
|
});
|
||||||
|
self.num_verts += 1;
|
||||||
|
|
||||||
|
// Finally, add the face (any halfedge is fine):
|
||||||
|
self.faces.push(DCELFace { halfedge: e_n });
|
||||||
|
self.num_faces += 1;
|
||||||
|
|
||||||
|
f_n
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a face that lies on two connected boundaries - i.e. two of its
|
||||||
|
/// half-edges have twins already on the mesh.
|
||||||
|
///
|
||||||
|
/// Twin half-edges should be given in counter-clockwise order; that
|
||||||
|
/// is, for the resultant face, one half-edge's twin will be twin1, and
|
||||||
|
/// the next half-edge's twin will be twin2.
|
||||||
|
/// Also: `self.halfedges[twin2].next_halfedge` must equal `twin1`.
|
||||||
|
pub fn add_face_twin2(&mut self, twin1: usize, twin2: usize) -> usize {
|
||||||
|
// 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:
|
||||||
|
let e_n = self.num_halfedges;
|
||||||
|
|
||||||
|
// The origin vertex is 'between' the two edges, but because their
|
||||||
|
// order is reversed (as twins), this is twin1's origin:
|
||||||
|
let twin_halfedge = &self.halfedges[twin1];
|
||||||
|
let v1 = twin_halfedge.vert;
|
||||||
|
let v2 = self.halfedges[twin_halfedge.next_halfedge].vert;
|
||||||
|
// Final vertex is back around to twin2's origin:
|
||||||
|
let v3 = self.halfedges[twin2].vert;
|
||||||
|
|
||||||
|
self.halfedges.push(DCELHalfEdge {
|
||||||
|
vert: v1,
|
||||||
|
face: f_n,
|
||||||
|
has_twin: true,
|
||||||
|
twin_halfedge: twin1,
|
||||||
|
next_halfedge: e_n + 1,
|
||||||
|
prev_halfedge: e_n + 2,
|
||||||
|
}); // index e_n
|
||||||
|
self.halfedges[twin1].has_twin = true;
|
||||||
|
self.halfedges[twin1].twin_halfedge = e_n;
|
||||||
|
self.halfedges.push(DCELHalfEdge {
|
||||||
|
vert: v2,
|
||||||
|
face: f_n,
|
||||||
|
has_twin: false,
|
||||||
|
twin_halfedge: 0,
|
||||||
|
next_halfedge: e_n + 2,
|
||||||
|
prev_halfedge: e_n,
|
||||||
|
}); // index e_n + 1
|
||||||
|
self.halfedges.push(DCELHalfEdge {
|
||||||
|
vert: v3,
|
||||||
|
face: f_n,
|
||||||
|
has_twin: true,
|
||||||
|
twin_halfedge: twin2,
|
||||||
|
next_halfedge: e_n,
|
||||||
|
prev_halfedge: e_n + 1,
|
||||||
|
}); // index e_n + 2
|
||||||
|
self.halfedges[twin2].has_twin = true;
|
||||||
|
self.halfedges[twin2].twin_halfedge = e_n + 2;
|
||||||
|
self.num_halfedges += 3;
|
||||||
|
|
||||||
|
// Finally, add the face (any halfedge is fine):
|
||||||
|
self.faces.push(DCELFace { halfedge: e_n });
|
||||||
|
self.num_faces += 1;
|
||||||
|
|
||||||
|
f_n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_mesh(m: &DCELMesh<Vertex>) -> Mesh {
|
||||||
|
let n = m.faces.len();
|
||||||
|
let mut faces: Vec<usize> = vec![0; 3 * n];
|
||||||
|
|
||||||
|
for i in 0..n {
|
||||||
|
|
||||||
|
let e0 = m.faces[i].halfedge;
|
||||||
|
let h0 = &m.halfedges[e0];
|
||||||
|
faces[3*i + 0] = h0.vert;
|
||||||
|
let e1 = h0.next_halfedge;
|
||||||
|
let h1 = &m.halfedges[e1];
|
||||||
|
faces[3*i + 1] = h1.vert;
|
||||||
|
let e2 = h1.next_halfedge;
|
||||||
|
let h2 = &m.halfedges[e2];
|
||||||
|
faces[3*i + 2] = h2.vert;
|
||||||
|
if h2.next_halfedge != e0 {
|
||||||
|
panic!(format!("Face {}: half-edges {},{},{} return to {}, not {}",
|
||||||
|
i, e0, e1, e2, h2.next_halfedge, e0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mesh {
|
||||||
|
verts: m.verts.iter().map(|e| e.v).collect(),
|
||||||
|
faces: faces,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ use crate::mesh::{Mesh, MeshFunc, VertexUnion, vert_args};
|
|||||||
use crate::xform::{Transform, Vertex, vertex, Mat4, id};
|
use crate::xform::{Transform, Vertex, vertex, Mat4, id};
|
||||||
use crate::rule::{Rule, RuleFn, RuleEval, Child};
|
use crate::rule::{Rule, RuleFn, RuleEval, Child};
|
||||||
use crate::prim;
|
use crate::prim;
|
||||||
|
use crate::dcel;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
pub fn cube_thing() -> Rule<()> {
|
pub fn cube_thing() -> Rule<()> {
|
||||||
@ -1266,6 +1267,8 @@ pub fn test_parametric() -> Mesh {
|
|||||||
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),
|
||||||
];
|
];
|
||||||
|
//let base_verts = util::subdivide_cycle(&base_verts, 2);
|
||||||
|
//let base_verts = util::subdivide_cycle(&base_verts, 16);
|
||||||
|
|
||||||
let t0 = 0.0;
|
let t0 = 0.0;
|
||||||
let t1 = 16.0;
|
let t1 = 16.0;
|
||||||
@ -1275,5 +1278,30 @@ 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.0001)
|
crate::rule::parametric_mesh(base_verts, xform, t0, t1, 0.005)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_dcel(fname: &str) {
|
||||||
|
let mut mesh: dcel::DCELMesh<Vertex> = 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 f2 = mesh.add_face_twin1(mesh.faces[f1].halfedge, vertex(0.0, 0.0, 1.0));
|
||||||
|
|
||||||
|
let vl1 = mesh.face_to_verts(f1);
|
||||||
|
println!("verts = {:?}", vl1);
|
||||||
|
let vl2 = mesh.face_to_verts(f2);
|
||||||
|
println!("verts = {:?}", vl2);
|
||||||
|
|
||||||
|
//let f3 = mesh.add_face_twin2();
|
||||||
|
|
||||||
|
println!("DCEL mesh = {}", mesh);
|
||||||
|
|
||||||
|
let mesh_conv = dcel::convert_mesh(&mesh);
|
||||||
|
|
||||||
|
println!("Mesh = {:?}", mesh_conv);
|
||||||
|
|
||||||
|
mesh_conv.write_stl_file(fname).unwrap();
|
||||||
}
|
}
|
||||||
@ -6,6 +6,7 @@ pub mod prim;
|
|||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod xform;
|
pub mod xform;
|
||||||
pub mod examples;
|
pub mod examples;
|
||||||
|
pub mod dcel;
|
||||||
|
|
||||||
//pub use crate::examples;
|
//pub use crate::examples;
|
||||||
//pub use crate::openmesh::test_thing;
|
//pub use crate::openmesh::test_thing;
|
||||||
@ -138,7 +139,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parametric() {
|
fn test_parametric() {
|
||||||
examples::test_parametric().write_stl_file("test_parametric.stl");
|
examples::test_parametric().write_stl_file("test_parametric.stl").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dcel() {
|
||||||
|
examples::test_dcel("test_dcel.stl");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// need this for now:
|
// need this for now:
|
||||||
|
|||||||
286
src/rule.rs
286
src/rule.rs
@ -373,18 +373,45 @@ pub fn parametric_mesh<F>(frame: Vec<Vertex>, f: F, t0: f32, t1: f32, max_err: f
|
|||||||
// neighboring ones.
|
// neighboring ones.
|
||||||
mesh_idx: usize, // Index of this vertex in the mesh
|
mesh_idx: usize, // Index of this vertex in the mesh
|
||||||
// (If it's in 'frontier', the vertex is in the mesh).
|
// (If it's in 'frontier', the vertex is in the mesh).
|
||||||
neighbor1: usize, // Index of 'frontier' of one neighbor
|
neighbor: [usize; 2], // Indices of 'frontier' of each neighbor
|
||||||
neighbor2: usize, // Index of 'frontier' of other 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.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A face that is still undergoing subdivision. This is used for a stack
|
||||||
|
// in the main loop. 'verts' gives the index of vertices of the face,
|
||||||
|
// and 'shared_faces' gives neighboring faces, i.e. those which share
|
||||||
|
// an edge with the face. This always refers to a face that is already
|
||||||
|
// in 'faces' - but this face may be replaced in the course of
|
||||||
|
// processing.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct tempFace {
|
||||||
|
// Indices into 'verts' below:
|
||||||
|
verts: [usize; 3],
|
||||||
|
// The parameter values corresponding to 'verts':
|
||||||
|
ts: [f32; 3],
|
||||||
|
// The 'frame' vertices (i.e. vertex at f(t0)) corresponding
|
||||||
|
// to 'verts':
|
||||||
|
frame_verts: [Vertex; 3],
|
||||||
|
// Index into 'faces' below for the starting vertex:
|
||||||
|
face: usize,
|
||||||
|
// If the bool is true, this gives an index into 'faces' below for
|
||||||
|
// a face that shares an edge with this face; if the bool is false,
|
||||||
|
// disregard (there is no neighbor here). This goes in a specific
|
||||||
|
// order: the face sharing edge (verts[0], verts[1]), then edge
|
||||||
|
// (verts[1], verts[2]), then edge (verts[2], verts[0]).
|
||||||
|
shared_faces: [(usize, bool); 3],
|
||||||
|
}
|
||||||
|
|
||||||
// Init 'frontier' with each 'frame' vertex, and start it at t=t0.
|
// Init 'frontier' with each 'frame' vertex, and start it at t=t0.
|
||||||
let mut frontier: Vec<frontierVert> = frame.iter().enumerate().map(|(i,v)| frontierVert {
|
let mut frontier: Vec<frontierVert> = frame.iter().enumerate().map(|(i,v)| frontierVert {
|
||||||
vert: *v,
|
vert: *v,
|
||||||
t: t0,
|
t: t0,
|
||||||
frame_vert: *v,
|
frame_vert: *v,
|
||||||
mesh_idx: i,
|
mesh_idx: i,
|
||||||
neighbor1: (i - 1) % n,
|
neighbor: [(i - 1) % n, (i + 1) % n],
|
||||||
neighbor2: (i + 1) % n,
|
side_faces: [(0, false); 2], // initial vertices have no faces
|
||||||
}).collect();
|
}).collect();
|
||||||
// Every vertex in 'frontier' has a trajectory it follows - which is
|
// Every vertex in 'frontier' has a trajectory it follows - which is
|
||||||
// simply the position as we transform the original vertex by f(t),
|
// simply the position as we transform the original vertex by f(t),
|
||||||
@ -399,12 +426,13 @@ pub fn parametric_mesh<F>(frame: Vec<Vertex>, f: F, t0: f32, t1: f32, max_err: f
|
|||||||
let mut verts: Vec<Vertex> = frame.clone();
|
let mut verts: Vec<Vertex> = frame.clone();
|
||||||
let mut faces: Vec<usize> = vec![];
|
let mut faces: Vec<usize> = vec![];
|
||||||
|
|
||||||
let mut count = 0;
|
// 'stack' is cleared at every iteration below, and contains faces that
|
||||||
|
// may still require subdivision.
|
||||||
|
let mut stack: Vec<tempFace> = vec![];
|
||||||
|
|
||||||
while !frontier.is_empty() {
|
while !frontier.is_empty() {
|
||||||
|
|
||||||
for (i, f) in frontier.iter().enumerate() {
|
for (i, f) in frontier.iter().enumerate() {
|
||||||
println!("DEBUG: frontier[{}]: vert={},{},{} t={} mesh_idx={} neighbors={},{}", i, f.vert.x, f.vert.y, f.vert.z, f.t, f.mesh_idx, f.neighbor1, f.neighbor2);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick a vertex to advance.
|
// Pick a vertex to advance.
|
||||||
@ -420,9 +448,15 @@ pub fn parametric_mesh<F>(frame: Vec<Vertex>, f: F, t0: f32, t1: f32, max_err: f
|
|||||||
if v.t >= t1 {
|
if v.t >= t1 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// 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 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
|
||||||
|
// make our connections we look at how far points on the *edges*
|
||||||
|
// diverge from the trajectory of the continuous transformation).
|
||||||
let mut dt = (t1 - t0) / 100.0;
|
let mut dt = (t1 - t0) / 100.0;
|
||||||
let vf = v.frame_vert;
|
let vf = v.frame_vert;
|
||||||
for iter in 0..100 {
|
for iter in 0..100 {
|
||||||
@ -460,133 +494,149 @@ pub fn parametric_mesh<F>(frame: Vec<Vertex>, f: F, t0: f32, t1: f32, max_err: f
|
|||||||
let t = v.t + dt;
|
let t = v.t + dt;
|
||||||
let v_next = f(t).mtx * vf;
|
let v_next = f(t).mtx * vf;
|
||||||
|
|
||||||
|
// DEBUG
|
||||||
|
/*
|
||||||
|
let l1 = (v.vert - v_next).norm();
|
||||||
|
let l2 = (frontier[v.neighbor1].vert - v.vert).norm();
|
||||||
|
let l3 = (frontier[v.neighbor2].vert - v.vert).norm();
|
||||||
|
println!("l1={} l2={} l3={}", l1, l2, l3);
|
||||||
|
*/
|
||||||
|
|
||||||
// Add this vertex to our mesh:
|
// Add this vertex to our mesh:
|
||||||
let pos = verts.len();
|
let v_next_idx = verts.len();
|
||||||
verts.push(v_next);
|
verts.push(v_next);
|
||||||
|
// 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:
|
// Replace this vertex in the frontier:
|
||||||
frontier[i] = frontierVert {
|
frontier[i] = frontierVert {
|
||||||
vert: v_next,
|
vert: v_next,
|
||||||
frame_vert: vf,
|
frame_vert: vf,
|
||||||
mesh_idx: pos,
|
mesh_idx: v_next_idx,
|
||||||
t: t,
|
t: t,
|
||||||
neighbor1: v.neighbor1,
|
neighbor: v.neighbor,
|
||||||
neighbor2: v.neighbor2,
|
side_faces: [(face_idx, true), (face_idx + 1, true)],
|
||||||
};
|
};
|
||||||
// We might update neighbor1/neighbor2 below.
|
// 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.
|
||||||
|
|
||||||
// The starting vertex (v.vert) is already in the mesh, but
|
// Also add these faces to the stack of triangles to check for
|
||||||
// the two 'neighbor' vertices need to be considered:
|
// subdivision. They may be replaced.
|
||||||
let neighbors = [v.neighbor1, v.neighbor2];
|
let f0 = &frontier[v.neighbor[0]];
|
||||||
|
stack.push(tempFace {
|
||||||
// We can consider two faces - one on each side of the 'main'
|
verts: [v_next_idx, v.mesh_idx, f0.mesh_idx],
|
||||||
// edge (v, v_next), going to vertex side1_idx or side2_idx.
|
ts: [t, v.t, f0.t],
|
||||||
// We may also need to subdivide one or both of these faces
|
frame_verts: [vf, vf, f0.frame_vert],
|
||||||
// along the edge (v_next, side1_idx) or (v_next, side2_idx).
|
face: face_idx,
|
||||||
for (j,neighbor_idx) in neighbors.iter().enumerate() {
|
shared_faces: [(face_idx + 1, true), (0, false), f0.side_faces[1]],
|
||||||
let neighbor = &frontier[*neighbor_idx];
|
|
||||||
let side_idx = neighbor.mesh_idx;
|
|
||||||
// To do so, we can consider the error on the edge
|
|
||||||
// (v_next, neighbor), using mostly the same 'midpoint' method
|
|
||||||
// as each iteration when computing dt.
|
|
||||||
// First, find edge midpoint:
|
|
||||||
let edge_mid = 0.5*(v_next + neighbor.vert);
|
|
||||||
// To find the 'interpolated' trajectory we need to interpolate
|
|
||||||
// back at the original frame (we may need this anyway for other
|
|
||||||
// computations later):
|
|
||||||
let frame_mid = 0.5*(vf + neighbor.frame_vert);
|
|
||||||
// ...then trace these to find 'trajectory' midpoint;
|
|
||||||
let t_mid = v.t + dt/2.0;
|
|
||||||
let xf = f(t_mid).mtx;
|
|
||||||
let traj_mid = xf * frame_mid;
|
|
||||||
// and finally, error:
|
|
||||||
let mid_err = (edge_mid - traj_mid).norm();
|
|
||||||
println!("DEBUG: j={} considering edge {},{}, edge_mid={:?} traj_mid={:?} mid_err={}", j, i, *neighbor_idx, edge_mid, traj_mid, mid_err);
|
|
||||||
|
|
||||||
// DEBUG: This is disabled until fixed
|
|
||||||
if false && mid_err > max_err {
|
|
||||||
println!("Error over threshold, splitting ({},{})", i, neighbor_idx);
|
|
||||||
// Error is high enough we should split.
|
|
||||||
let pos_new = verts.len();
|
|
||||||
verts.push(traj_mid);
|
|
||||||
let new_neighbor = frontier.len();
|
|
||||||
frontier.push(frontierVert {
|
|
||||||
vert: traj_mid,
|
|
||||||
frame_vert: frame_mid,
|
|
||||||
mesh_idx: pos_new,
|
|
||||||
t: t_mid,
|
|
||||||
neighbor1: *neighbor_idx,
|
|
||||||
neighbor2: i,
|
|
||||||
});
|
});
|
||||||
// We've put a new vertex in between (see neighbor1/2)
|
let f1 = &frontier[v.neighbor[1]];
|
||||||
// so we have to update this information.
|
stack.push(tempFace {
|
||||||
let mut patch_neighbor = |idx: usize, from: usize, to: usize| {
|
verts: [v.mesh_idx, v_next_idx, f1.mesh_idx],
|
||||||
println!("DEBUG: patch_neighbor of idx {} from {} to {}", idx, from, to);
|
ts: [v.t, t, f1.t],
|
||||||
if frontier[idx].neighbor1 == from { frontier[idx].neighbor1 = to; }
|
frame_verts: [vf, vf, f1.frame_vert],
|
||||||
if frontier[idx].neighbor2 == from { frontier[idx].neighbor2 = to; }
|
face: face_idx + 1,
|
||||||
};
|
shared_faces: [(face_idx, true), f1.side_faces[0], (0, false)],
|
||||||
patch_neighbor(i, *neighbor_idx, new_neighbor);
|
});
|
||||||
patch_neighbor(*neighbor_idx, i, new_neighbor);
|
// Note that vf appears several times in frame_verts because
|
||||||
|
// several vertices sit in the same trajectory (thus, same 'frame'
|
||||||
|
// vertex).
|
||||||
|
|
||||||
let mut new_faces = if j % 2 == 0 {
|
while !stack.is_empty() {
|
||||||
vec![
|
let face = stack.pop().unwrap();
|
||||||
v.mesh_idx, pos, pos_new,
|
println!("DEBUG: Examining face: {:?}", face);
|
||||||
v.mesh_idx, pos_new, side_idx,
|
let v0 = verts[face.verts[0]];
|
||||||
]
|
let v1 = verts[face.verts[1]];
|
||||||
|
let v2 = verts[face.verts[2]];
|
||||||
|
let d01 = (v0 - v1).norm();
|
||||||
|
let d02 = (v0 - v2).norm();
|
||||||
|
let d12 = (v1 - v2).norm();
|
||||||
|
// Law of cosines:
|
||||||
|
let cos0 = (d01*d01 + d02*d02 - d12*d12) / (2.0 * d01 * d02);
|
||||||
|
let cos1 = (d01*d01 + d12*d12 - d02*d02) / (2.0 * d01 * d12);
|
||||||
|
let cos2 = (d02*d02 + d12*d12 - d01*d01) / (2.0 * d02 * d12);
|
||||||
|
// TODO: Perhaps use https://en.wikipedia.org/wiki/Circumscribed_circle#Cartesian_coordinates
|
||||||
|
// to go by circumradius instead. Or - find it by law of sines?
|
||||||
|
println!("DEBUG: d01={} d02={} d12={} cos0={} cos1={} cos2={}", d01, d02, d12, cos0, cos1, cos2);
|
||||||
|
if (cos0 < 0.0 || cos0 > 0.7 ||
|
||||||
|
cos1 < 0.0 || cos1 > 0.7 ||
|
||||||
|
cos2 < 0.0 || cos2 > 0.7) {
|
||||||
|
println!("DEBUG: Angles out of range!");
|
||||||
} else {
|
} else {
|
||||||
vec![
|
println!("DEBUG: Angles OK");
|
||||||
pos, v.mesh_idx, pos_new,
|
}
|
||||||
pos_new, v.mesh_idx, side_idx,
|
// TODO: Figure out how to subdivide in this case
|
||||||
]
|
|
||||||
};
|
|
||||||
faces.append(&mut new_faces);
|
|
||||||
} else {
|
|
||||||
// Error is within limit. Just add one face.
|
|
||||||
|
|
||||||
let mut new_faces = if j % 2 == 0 {
|
// The triangle forms a plane. Get this plane's normal vector.
|
||||||
vec![v.mesh_idx, pos, side_idx]
|
let a = (v0 - v1).xyz();
|
||||||
} else {
|
let b = (v0 - v2).xyz();
|
||||||
vec![pos, v.mesh_idx, side_idx]
|
let normal = a.cross(&b).normalize();
|
||||||
};
|
// Make a new point that is on the surface, but roughly a
|
||||||
faces.append(&mut new_faces);
|
// midpoint in parameter space. Exact location isn't crucial.
|
||||||
// TODO: This is likely the wrong 'general' way to handle
|
let t_mid = (face.ts[0] + face.ts[1] + face.ts[2]) / 3.0;
|
||||||
// winding order but for n=2 it probably is fine
|
let v_mid = (face.frame_verts[0] + face.frame_verts[1] + face.frame_verts[2]) / 3.0;
|
||||||
|
let p = f(t_mid).mtx * v_mid;
|
||||||
|
|
||||||
|
let d = p.xyz().dot(&normal);
|
||||||
|
|
||||||
|
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.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
println!("DEBUG: d > err, splitting this triangle");
|
||||||
|
|
||||||
|
// The face has 3 edges. We split each of them (making a new
|
||||||
|
// vertex in the middle), and then make 3 new edges - one
|
||||||
|
// between each pair of new vertices - to replace the face
|
||||||
|
// with four smaller faces.
|
||||||
|
|
||||||
|
// This split is done in 'parameter' space:
|
||||||
|
let pairs = [(0,1), (1,2), (0,2)];
|
||||||
|
let mut mids = pairs.iter().map(|(i,j)| {
|
||||||
|
let t = (face.ts[*i] + face.ts[*j]) / 2.0;
|
||||||
|
let v = (face.frame_verts[*i] + face.frame_verts[*j]) / 2.0;
|
||||||
|
f(t).mtx * v
|
||||||
|
}).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: Look at shared_faces because these must be split too.
|
||||||
|
|
||||||
|
// TODO: Do I have to correct *other* things in the stack too?
|
||||||
|
// If they refer to the same face I may be invalidating
|
||||||
|
// something here!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
count += 1;
|
|
||||||
if count > 50 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// make our connections we look at how far points on the *edges*
|
|
||||||
// diverge from the trajectory of the continuous transformation).
|
|
||||||
|
|
||||||
// Add this vertex to the mesh, and connect it to: the vertex we
|
|
||||||
// started with, and the two neighbors of that vertex.
|
|
||||||
|
|
||||||
// Repeat at "Pick a vertex...".
|
|
||||||
|
|
||||||
// Don't move t + dt past t1. Once a frontier vertex is placed at
|
|
||||||
// that value of t, remove it.
|
|
||||||
|
|
||||||
|
|
||||||
// Missing: Anything about when to subdivide an edge.
|
|
||||||
// If I assume a good criterion of "when" to subdivide an edge, the
|
|
||||||
// "how" is straightforward: find the edge's two neighbors in the
|
|
||||||
// frontier. Trace them back to their 'original' vertices at t=t0
|
|
||||||
// (these should just be stored alongside each frontier member),
|
|
||||||
// produce an interpolated vertex. Produce an interpolated t from
|
|
||||||
// respective t of the two neighbors in the frontier; use that t
|
|
||||||
// to move the 'interpolated' vertex along its trajectory.
|
|
||||||
//
|
|
||||||
// Add new vertex to mesh (and make the necessary connections)
|
|
||||||
// and to frontier.
|
|
||||||
|
|
||||||
// But still missing from that: When do I collapse a subdivision
|
|
||||||
// back down?
|
|
||||||
return Mesh { verts, faces };
|
return Mesh { verts, faces };
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user