Compare commits

..

10 Commits

Author SHA1 Message Date
Chris Hodapp
e6c53fafdb Misc notes in README; minor fix in tests 2021-07-27 13:41:44 -04:00
Chris Hodapp
1c9053a5ad Minor updates to README 2021-07-26 16:48:32 -04:00
Chris Hodapp
670b7859a9 Refactor to use itertools 2021-01-24 18:48:34 -05:00
Chris Hodapp
3ce441d86a Add 'fraying' effect on spiral 2021-01-24 00:11:42 -05:00
Chris Hodapp
5bee3c8aa3 Fix NestedSpiral & add to README.md 2021-01-22 17:44:23 -05:00
Chris Hodapp
04aca1b3d0 Almost complete NestedSpiral (only winding order wrong) 2021-01-22 17:22:04 -05:00
Chris Hodapp
51cab46299 Misc formatting & not-yet-ready NestedSpiral example 2021-01-21 17:03:04 -05:00
Chris Hodapp
a664dbba16 Add some example images 2021-01-19 12:48:35 -05:00
Chris Hodapp
9cb467e4d5 Rename to prosha 2020-10-10 17:34:10 -04:00
Chris Hodapp
e7206f4598 Remove lots of dead code. Convert Sierpinski example. 2020-10-09 23:32:49 -04:00
14 changed files with 364 additions and 2415 deletions

View File

@ -1,5 +1,5 @@
[package] [package]
name = "mesh_scratch" name = "prosha"
version = "0.1.0" version = "0.1.0"
authors = ["Chris Hodapp <hodapp87@gmail.com>"] authors = ["Chris Hodapp <hodapp87@gmail.com>"]
edition = "2018" edition = "2018"
@ -16,3 +16,4 @@ euclid = "0.20.7"
nalgebra = "0.19.0" nalgebra = "0.19.0"
stl_io = "0.4.2" stl_io = "0.4.2"
rand = "0.7.3" rand = "0.7.3"
itertools = "0.10.0"

View File

@ -1,4 +1,16 @@
# This needs a title # Prosha
Experiments in procedural meshes in Rust
More detailed docs and examples forthcoming.
Due to some issues explained a little bit below and a little bit in
other blog posts, I'm now treating this as maintenance-only and not as
an active project.
(TODO: Link to these posts)
## General Notes & History
This particular code base was started around 2019 December This particular code base was started around 2019 December
as an attempt to make meshes in a more "generative" style, as an attempt to make meshes in a more "generative" style,
@ -61,7 +73,7 @@ A lot of what I wrote here ended up just being a buggy, half-assed
interpreter for a buggy, half-assed EDSL/minilanguage. interpreter for a buggy, half-assed EDSL/minilanguage.
(Greenspun's Tenth Rule of Programming, anyone?) (Greenspun's Tenth Rule of Programming, anyone?)
On top of this, my implementation is pretty slow when it is On top of this, my implementation was pretty slow when it was
using a large number of rules each producing small geometry using a large number of rules each producing small geometry
(which is almost literally the only way it *can* be used (which is almost literally the only way it *can* be used
if you want to produce a fairly complex mesh). I did some if you want to produce a fairly complex mesh). I did some
@ -69,7 +81,7 @@ profiling some months ago that showed I was spending the
vast majority of my time in `extend()` and `clone()` for vast majority of my time in `extend()` and `clone()` for
`Vec` - and so I could probably see some huge performance `Vec` - and so I could probably see some huge performance
gains if I could simply pre-allocate vectors and share geometry gains if I could simply pre-allocate vectors and share geometry
more. Also, I'm pretty sure this code does some very task-parallel more. Also, I'm pretty sure this code did some very task-parallel
elements (e.g. anytime a rule branches), and multithreading should elements (e.g. anytime a rule branches), and multithreading should
be able to exploit this if I care. be able to exploit this if I care.
@ -86,14 +98,23 @@ branch, thus named because I needed to get rid of my buggy
implementation of half of Common Lisp. It paid off quite quickly and implementation of half of Common Lisp. It paid off quite quickly and
also was vastly faster at generating meshes. also was vastly faster at generating meshes.
## What does the name mean?
Nothing, but it's better than its previous name of
"mesh_scratch". I asked for name suggestions, and someone came up
with this one.
## Highest priority: ## Highest priority:
- Begin converting older examples. - Work on abstraction/composition. Particularly: factor out
- Trash all the dead code. patterns I use, and be able to *compose* procedural meshes
somehow - e.g. the 'context' object I discussed.
- Docs on modules - Docs on modules
- Make some examples that are non-deterministic!
- swept-isocontour stuff from - swept-isocontour stuff from
`/mnt/dev/graphics_misc/isosurfaces_2018_2019/spiral*.py`. This `/mnt/dev/graphics_misc/isosurfaces_2018_2019/spiral*.py`. This
will probably require that I figure out parametric curves will probably require that I figure out parametric curves
(is this stuff still possible?)
- Make an example that is more discrete-automata, less - Make an example that is more discrete-automata, less
approximation-of-space-curve. approximation-of-space-curve.
@ -119,6 +140,7 @@ also was vastly faster at generating meshes.
- [Geometry and Algorithms for Computer Aided Design (Hartmann)](https://www2.mathematik.tu-darmstadt.de/~ehartmann/cdgen0104.pdf) - [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://en.wikipedia.org/wiki/Surface_triangulation
- https://www.cs.cmu.edu/~quake/triangle.html - https://www.cs.cmu.edu/~quake/triangle.html
- OpenSubdiv!
## Reflections & Quick Notes ## Reflections & Quick Notes
@ -130,3 +152,34 @@ also was vastly faster at generating meshes.
the current local space. the current local space.
- Don't reinvent subdivision surfaces. - Don't reinvent subdivision surfaces.
- Don't reinvent Lisp when you wanted a Lisp! - Don't reinvent Lisp when you wanted a Lisp!
## Examples
### Barbs
See `examples::Barbs` & "barbs" test in `lib.rs`.
![Barbs](./images/barbs_preview.png)
### Tree Thing
See `examples::TreeThing`. First one is "tree_thing" test in `lib.rs`:
![tree_thing](./images/tree_thing_preview.jpg)
Second is "tree_thing2" (this was from a larger Blender render):
![tree_thing2](./images/rust_tree_thing_03_smaller.jpg)
### Sierpinski
See `examples::Sierpinski` & "sierpinski" test in `lib.rs`.
This looks cooler with some randomness added and 3D printed.
![sierpinski](./images/sierpinski_preview.png)
### Triple Nested Spiral
See `examples::NestedSpiral` & "nested_spiral" test in `lib.rs`.
![nested_spiral](./images/nested_spiral.jpg)

BIN
images/barbs_preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

BIN
images/nested_spiral.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 KiB

View File

@ -1,752 +0,0 @@
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,
}
#[derive(Debug)]
pub enum VertSpec<V> {
New(V),
Idx(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{}=e{} {:?}", i, v.halfedge, 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 + std::fmt::Debug> 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 print(&self) {
println!("DCELMesh has {} verts, {} faces, {} halfedges:",
self.num_verts,
self.num_faces,
self.num_halfedges);
for (i,v) in self.verts.iter().enumerate() {
println!("Vert {}: halfedge {}, {:?}", i, v.halfedge, v.v);
}
for (i,f) in self.faces.iter().enumerate() {
println!("Face {}: halfedge {} (halfedges {:?}, verts {:?})",
i, f.halfedge, self.face_to_halfedges(i), self.face_to_verts(i));
}
for (i,h) in self.halfedges.iter().enumerate() {
let twin = if h.has_twin { format!(", twin half-edge {}", h.twin_halfedge)
} else {
format!(", no 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);
}
}
/// Runs various checks on the mesh. This will return true if the mesh
/// looks okay, and otherwise false. It will print messages as it
/// runs.
pub fn check(&self) -> bool {
let mut pass = true;
if self.num_halfedges != self.halfedges.len() {
pass = false;
println!("self.num_halfedges={} != self.halfedges.len()={}",
self.num_halfedges, self.halfedges.len());
} else {
println!("self.num_halfedges matches self.halfedges.len()");
}
if self.num_faces != self.faces.len() {
pass = false;
println!("self.faces={} != self.faces.len()={}",
self.num_faces, self.faces.len());
} else {
println!("self.num_faces matches self.faces.len()");
}
if self.num_verts != self.verts.len() {
pass = false;
println!("self.verts={} != self.verts.len()={}",
self.num_verts, self.verts.len());
} else {
println!("self.num_verts matches self.verts.len()");
}
for (i,v) in self.verts.iter().enumerate() {
if v.halfedge >= self.halfedges.len() {
println!("Vertex {}: halfedge index {} out of range",
i, v.halfedge);
pass = false;
}
if self.halfedges[v.halfedge].vert != i {
println!("Vertex {} names halfedge {}, which has a different origin vertex ({})",
i, v.halfedge, self.halfedges[v.halfedge].vert);
}
}
for (i,edge) in self.halfedges.iter().enumerate() {
if edge.vert >= self.verts.len() {
println!("Halfedge {}: vertex index {} out of range", i, edge.vert);
pass = false;
}
if edge.has_twin {
let twin = &self.halfedges[edge.twin_halfedge];
if !twin.has_twin {
println!("Halfedge {}: twin {} says it has no twin",
i, edge.twin_halfedge);
pass = false;
} 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;
if p >= self.halfedges.len() {
println!("Halfedge {}: previous halfedge index {} out of range",
i, p);
pass = false;
}
let n = edge.next_halfedge;
if p >= self.halfedges.len() {
println!("Halfedge {}: next halfedge index {} out of range",
i, n);
pass = false;
}
let pn = self.halfedges[p].next_halfedge;
if pn != i {
println!("Halfedge {}: previous halfedge {} has next halfedge of {}, not {}",
i, p, pn, i);
pass = false;
}
let np = self.halfedges[n].prev_halfedge;
if np != i {
println!("Halfedge {}: next halfedge {} has previous halfedge of {}, not {}",
i, n, np, i);
pass = false;
}
// TODO: Check that following prev always leads back to start
// likewise following next
}
for (i,face) in self.faces.iter().enumerate() {
if face.halfedge >= self.halfedges.len() {
println!("Face {}: halfedge index {} out of range",
i, face.halfedge);
pass = false;
}
let face2 = self.halfedges[face.halfedge].face;
if i != face2 {
println!("Face {} gives boundary halfedge {}, which gives different face ({})",
i, face.halfedge, face2);
pass = false;
}
// TODO: Check that face never visits same vertex twice?
// This might belong in halfedge checking
}
if pass {
println!("Mesh OK")
} else {
println!("Mesh has errors!")
}
pass
}
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()
}
/// Adds a face that shares no edges with anything else in the mesh.
/// Returns (face index, half-edge indices); half-edge indices are
/// given in the order of the vertices (i.e. the first half-edge's
/// origin is verts[0], second is verts[1], third is verts[2]).
pub fn add_face(&mut self, verts: [VertSpec<V>; 3]) -> (usize, [usize; 3]) {
// *New* vertices will be at index v_n onward.
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:
let mut new_verts: usize = 0;
for i in 0..3 {
let n = (i + 1) % 3;
let p = (i + 2) % 3;
// Either insert a new vertex, or use an existing one.
// In both cases, 'v' is its index.
let v = match verts[i] {
VertSpec::New(v) => {
self.verts.push(DCELVertex {
v: v,
halfedge: e_n + i,
});
let idx = v_n + new_verts;
new_verts += 1;
idx
},
VertSpec::Idx(v) => v,
};
// Note that its half-edge is e_n + i, which technically
// doesn't exist yet, but is inserted below:
self.halfedges.push(DCELHalfEdge {
vert: v,
face: f_n,
has_twin: false,
twin_halfedge: 0,
next_halfedge: e_n + n,
prev_halfedge: e_n + p,
});
}
self.num_halfedges += 3;
self.num_verts += new_verts;
// Finally, add the face (any halfedge is fine):
self.faces.push(DCELFace { halfedge: e_n });
self.num_faces += 1;
(f_n, [e_n, e_n+1, e_n+2])
}
/// 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.
/// Returns (face index, halfedge indices). Halfedge indices begin
/// at the twin half-edge to the one specified.
pub fn add_face_twin1(&mut self, twin: usize, vert: V) -> (usize, [usize; 3]) {
// 'vert' will be at index v_n:
let v_n = self.num_verts;
// The half-edges will be at indices e_n, e_n+1, e_n+2:
let e_n = self.num_halfedges;
self.verts.push(DCELVertex {
v: vert,
halfedge: e_n + 2,
});
self.num_verts += 1;
self.add_face_twin1_ref(twin, v_n)
}
/// Like `add_face_twin1`, but for a vertex already present in the
/// mesh rather than a new one. All else is identical.
pub fn add_face_twin1_ref(&mut self, twin: usize, vert_idx: usize) -> (usize, [usize; 3]) {
// 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;
// twin is: v2 -> v1
// Insert *its* twin, v1 -> v2, 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,
});
// DEBUG
if self.halfedges[twin].has_twin {
panic!("Trying to add twin to {}, which already has twin ({})",
twin, self.halfedges[twin].twin_halfedge);
}
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: vert_idx,
face: f_n,
has_twin: false,
twin_halfedge: 0,
next_halfedge: e_n,
prev_halfedge: e_n + 1,
});
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, [e_n, e_n+1, e_n+2])
}
/// 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: halfedge `twin2_idx` must end at the vertex that starts
/// `twin1_idx`.
///
/// Returns (face index, halfedge indices). Halfedge indices begin
/// at the twin halfedge to twin1, then twin halfedge of
/// twin2, then the 'new' halfedge (which starts where twin2 starts,
/// and ends where twin1 ends).
pub fn add_face_twin2(&mut self, twin1_idx: usize, twin2_idx: usize) -> (usize, [usize; 3]) {
// 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 twin1 = &self.halfedges[twin1_idx];
let twin2 = &self.halfedges[twin2_idx];
let v1 = twin1.vert;
let v2 = self.halfedges[twin1.next_halfedge].vert;
// Final vertex is back around to twin2's origin:
let v3 = twin2.vert;
if v1 != self.halfedges[twin2.next_halfedge].vert {
panic!("twin2 ({}) must end where twin1 ({}) begins, but does not (vertex {} vs. {})",
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
self.halfedges.push(DCELHalfEdge {
vert: v2,
face: f_n,
has_twin: true,
twin_halfedge: twin1_idx,
next_halfedge: e_n + 1,
prev_halfedge: e_n + 2,
}); // index e_n
self.halfedges[twin1_idx].has_twin = true;
self.halfedges[twin1_idx].twin_halfedge = e_n;
// and the twin of twin2 must be: v1 -> v3
self.halfedges.push(DCELHalfEdge {
vert: v1,
face: f_n,
has_twin: true,
twin_halfedge: twin2_idx,
next_halfedge: e_n + 2,
prev_halfedge: e_n,
}); // index e_n + 1
self.halfedges[twin2_idx].has_twin = true;
self.halfedges[twin2_idx].twin_halfedge = e_n + 1;
// and final edge must be v3 -> v2:
self.halfedges.push(DCELHalfEdge {
vert: v3,
face: f_n,
has_twin: false,
twin_halfedge: 0,
next_halfedge: e_n,
prev_halfedge: e_n + 1,
}); // index 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, [e_n, e_n+1, e_n+2])
}
/// 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).
let mut edge_idx = self.faces[face].halfedge;
let n = verts.len();
let mut faces_new = vec![];
let mut faces_upd = vec![];
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 {
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.
let next_idx = edge.next_halfedge;
let twin_idx = edge.twin_halfedge;
let mut twin = self.halfedges[twin_idx].clone();
// This half-edge, and its twin, are both split (at this
// vertex). Half-edges i & j will be the new ones created.
let i_edge = self.num_halfedges;
let j_edge = i_edge + 1;
// Half of edge_idx is split into j_edge.
// Half of twin_idx (its twin) is split into i_edge.
// This is where the vertex will be inserted:
let v_idx = self.num_verts;
self.verts.push(DCELVertex {
v: *v,
halfedge: i_edge, // j_edge is also fine
});
self.num_verts += 1;
edge.twin_halfedge = i_edge;
let j_next = edge.next_halfedge;
twin.twin_halfedge = j_edge;
let i_next = twin.next_halfedge;
self.halfedges.push(DCELHalfEdge {
vert: v_idx,
face: 0, // This is set properly in the next loop
has_twin: true,
twin_halfedge: edge_idx,
next_halfedge: i_next,
prev_halfedge: twin_idx,
}); // i_edge
self.halfedges.push(DCELHalfEdge {
vert: v_idx,
face: 0, // This is set properly in the next loop
has_twin: true,
twin_halfedge: twin_idx,
next_halfedge: j_next,
prev_halfedge: edge_idx,
}); // j_edge
self.num_halfedges += 2;
self.halfedges[edge_idx] = edge;
self.halfedges[twin_idx] = twin;
let r = (edge_idx, j_edge);
edge_idx = next_idx;
r
}).collect();
if fail {
return None;
}
// We then must connect some edges up 'across' vertices
// in order to form the smaller face at each vertex.
//
// This is outside the loop because we need one iteration's
// value (any iteration, doesn't matter) to reassign a face:
let mut e_twin_idx = 0;
let twin_edges: Vec<usize> = (0..n).map(|i0| {
let i1 = (i0 + 1) % n;
let (_, ep_idx) = new_edges[i0];
let (en_idx, _) = new_edges[i1];
// Halfedges will be inserted here:
let e_cross_idx = self.num_halfedges;
e_twin_idx = e_cross_idx + 1;
// And the face here:
let face_new = self.num_faces;
// 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
// vertex. The point of below is to form a smaller triangle
// (which includes this vertex, and the point at which both
// edges were split). This requires a half-edge from ep_idx
// (which *starts* where the other was split) to en_idx (which
// *ends* where one edge was split).
self.halfedges.push(DCELHalfEdge {
vert: self.halfedges[self.halfedges[en_idx].twin_halfedge].vert,
face: face_new,
has_twin: true,
twin_halfedge: e_twin_idx,
next_halfedge: ep_idx,
prev_halfedge: en_idx,
}); // e_cross_idx
// It also requires a twin half-edge. These all form a single
// central face with each edge sharing a boundary with the
// 'cross' edge we just created.
self.halfedges.push(DCELHalfEdge {
vert: self.halfedges[ep_idx].vert,
face: face, // Reuse index for the smaller *central* face
has_twin: true,
twin_halfedge: e_cross_idx,
next_halfedge: 0, // TODO
prev_halfedge: 0, // TODO
}); // e_twin_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;
self.halfedges[ep_idx].next_halfedge = en_idx;
self.halfedges[ep_idx].prev_halfedge = e_cross_idx;
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
// 'side' triangles (which means two new half-edges).
// First, we have to find the halfedge that starts the
// 'opposite' vertex (outer2):
let base1 = self.halfedges[en_idx].twin_halfedge;
let base2 = self.halfedges[base1].prev_halfedge;
let outer1 = self.halfedges[base1].next_halfedge;
let outer2 = self.halfedges[outer1].next_halfedge;
let v_opp = self.halfedges[outer2].vert;
// One face will reuse the old index:
let face1 = self.halfedges[outer2].face;
// Another will be inserted at this index:
let face2 = self.num_faces;
// Half-edges will be inserted here:
let edge_side1 = self.num_halfedges;
let edge_side2 = edge_side1 + 1;
self.halfedges.push(DCELHalfEdge {
vert: v_opp,
face: face1,
has_twin: true,
twin_halfedge: edge_side2,
next_halfedge: base1,
prev_halfedge: outer1,
}); // edge_side1
self.halfedges.push(DCELHalfEdge {
vert: self.halfedges[base1].vert,
face: face2,
has_twin: true,
twin_halfedge: edge_side1,
next_halfedge: outer2,
prev_halfedge: base2,
}); // edge_side2
self.num_halfedges += 2;
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;
self.halfedges[outer1].face = face1;
self.halfedges[base1].face = face1;
self.halfedges[base1].prev_halfedge = edge_side1;
self.halfedges[base1].next_halfedge = outer1;
self.halfedges[outer2].next_halfedge = base2;
self.halfedges[outer2].prev_halfedge = edge_side2;
self.halfedges[outer2].face = face2;
self.halfedges[base2].face = face2;
self.halfedges[base2].prev_halfedge = outer2;
self.halfedges[base2].next_halfedge = edge_side2;
e_twin_idx
}).collect();
for i0 in 0..n {
let i1 = (i0 + 1) % n;
self.halfedges[twin_edges[i0]].next_halfedge = twin_edges[i1];
self.halfedges[twin_edges[i1]].prev_halfedge = twin_edges[i0];
}
// We split up original 'face' completely and created four new faces,
// 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<F>(&self, f: F) -> Mesh
where F: Fn(V) -> Vertex,
{
let n = self.faces.len();
let mut faces: Vec<usize> = vec![0; 3 * n];
for i in 0..n {
let e0 = self.faces[i].halfedge;
let h0 = &self.halfedges[e0];
faces[3*i + 0] = h0.vert;
let e1 = h0.next_halfedge;
let h1 = &self.halfedges[e1];
faces[3*i + 1] = h1.vert;
let e2 = h1.next_halfedge;
let h2 = &self.halfedges[e2];
faces[3*i + 2] = h2.vert;
if h2.next_halfedge != e0 {
panic!("Face {}: half-edges {},{},{} return to {}, not {}",
i, e0, e1, e2, h2.next_halfedge, e0);
}
}
Mesh {
verts: self.verts.iter().map(|e| f(e.v)).collect(),
faces: faces,
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,10 @@
pub mod mesh; pub mod mesh;
#[macro_use] #[macro_use]
pub mod rule;
pub mod prim; pub mod prim;
#[macro_use] #[macro_use]
pub mod util; pub mod util;
pub mod xform;
pub mod examples; pub mod examples;
pub mod dcel; pub mod xform;
//pub use crate::examples; //pub use crate::examples;
//pub use crate::openmesh::test_thing; //pub use crate::openmesh::test_thing;
@ -14,32 +12,9 @@ pub mod dcel;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use nalgebra::*;
use std::rc::Rc; use std::rc::Rc;
use std::time::Instant; use std::time::Instant;
use rule::Rule;
use nalgebra::*;
fn run_test<S>(rule: Rule<S>, iters: usize, name: &str, use_old: bool) {
let r = Rc::new(rule);
println!("---------------------------------------------------");
println!("Running {} with {}...",
name, if use_old { "to_mesh" } else { "to_mesh_iter" });
if false {
let start = Instant::now();
let n = 5;
for _ in 0..n {
Rule::to_mesh_iter(r.clone(), iters);
}
let elapsed = start.elapsed();
println!("DEBUG: {} ms per run", elapsed.as_millis() / n);
}
let mesh_fn = if use_old { Rule::to_mesh } else { Rule::to_mesh_iter };
let (mesh, nodes) = mesh_fn(r.clone(), iters);
println!("Evaluated {} rules to {} verts", nodes, mesh.verts.len());
let fname = format!("{}.stl", name);
println!("Writing {}...", fname);
mesh.to_mesh().write_stl_file(&fname).unwrap();
}
#[test] #[test]
fn xform_order() { fn xform_order() {
@ -57,13 +32,27 @@ mod tests {
let xf2 = rot.translate(dx, 0.0, 0.0); let xf2 = rot.translate(dx, 0.0, 0.0);
// Rotate entire space, *then* translate in that rotated plane: // Rotate entire space, *then* translate in that rotated plane:
geom.transform(&trans).transform(&rot).write_stl_file("xform_apply_trans_rot.stl").unwrap(); geom.transform(&trans)
geom.transform(&(rot * trans)).write_stl_file("xform_mul_rot_trans.stl").unwrap(); .transform(&rot)
geom.transform(&xf2).write_stl_file("xform_rot_trans.stl").unwrap(); .write_stl_file("xform_apply_trans_rot.stl")
.unwrap();
geom.transform(&(rot * trans))
.write_stl_file("xform_mul_rot_trans.stl")
.unwrap();
geom.transform(&xf2)
.write_stl_file("xform_rot_trans.stl")
.unwrap();
// Translate cube, *then* rotate it: // Translate cube, *then* rotate it:
geom.transform(&rot).transform(&trans).write_stl_file("xform_apply_rot_trans.stl").unwrap(); geom.transform(&rot)
geom.transform(&(trans * rot)).write_stl_file("xform_mul_trans_rot.stl").unwrap(); .transform(&trans)
geom.transform(&xf1).write_stl_file("xform_trans_rot.stl").unwrap(); .write_stl_file("xform_apply_rot_trans.stl")
.unwrap();
geom.transform(&(trans * rot))
.write_stl_file("xform_mul_trans_rot.stl")
.unwrap();
geom.transform(&xf1)
.write_stl_file("xform_trans_rot.stl")
.unwrap();
} }
// TODO: These tests don't test any conditions, so this is useful // TODO: These tests don't test any conditions, so this is useful
@ -80,11 +69,10 @@ mod tests {
let fname = format!("{}.stl", name); let fname = format!("{}.stl", name);
println!("Writing {}...", fname); println!("Writing {}...", fname);
m.write_stl_file(&fname).unwrap(); m.write_stl_file(&fname).unwrap();
} }
#[test] #[test]
fn tree_thing() { fn tree_thing1() {
let name = "tree_thing"; let name = "tree_thing";
println!("---------------------------------------------------"); println!("---------------------------------------------------");
let b = examples::TreeThing::new(0.6, 10); let b = examples::TreeThing::new(0.6, 10);
@ -95,7 +83,6 @@ mod tests {
let fname = format!("{}.stl", name); let fname = format!("{}.stl", name);
println!("Writing {}...", fname); println!("Writing {}...", fname);
m.write_stl_file(&fname).unwrap(); m.write_stl_file(&fname).unwrap();
} }
#[test] #[test]
@ -110,73 +97,36 @@ mod tests {
let fname = format!("{}.stl", name); let fname = format!("{}.stl", name);
println!("Writing {}...", fname); println!("Writing {}...", fname);
m.write_stl_file(&fname).unwrap(); m.write_stl_file(&fname).unwrap();
} }
#[test] #[test]
fn sierpinski() { run_test(examples::sierpinski(), 6, "sierpinski", false); } fn sierpinski() {
/* let name = "sierpinski";
#[test] println!("---------------------------------------------------");
fn twist() { let b = examples::Sierpinski::new(0.50, 0.10, 0.0);
run_test(examples::twist(1.0, 2), 200, "screw", false); //let b = examples::Sierpinski::new(0.51, 0.10, 0.1);
let m = b.run();
println!("Got {} verts...", m.verts.len());
let fname = format!("{}.stl", name);
println!("Writing {}...", fname);
m.write_stl_file(&fname).unwrap();
} }
#[test] #[test]
fn twisty_torus() { fn nested_spiral() {
run_test(examples::twisty_torus(), 3000, "twisty_torus", false); let name = "nested_spiral";
} println!("---------------------------------------------------");
let b = examples::NestedSpiral::new();
//let b = examples::Sierpinski::new(0.51, 0.10, 0.1);
let m = b.run();
#[test] println!("Got {} verts...", m.verts.len());
fn twisty_torus_hardcode() {
run_test(examples::twisty_torus_hardcode(), 1000, "twisty_torus_hardcode", false);
}
#[test] let fname = format!("{}.stl", name);
#[ignore] println!("Writing {}...", fname);
fn twisty_torus_full() { m.write_stl_file(&fname).unwrap();
run_test(examples::twisty_torus(), 40000, "twisty_torus_full", false);
}
#[test]
#[ignore]
fn wind_chime_mistake_thing() {
run_test(examples::wind_chime_mistake_thing(), 400, "wind_chime_mistake_thing", false);
}
#[test]
fn nest_spiral_2() {
run_test(examples::nest_spiral_2(), 200, "nest_spiral_2", false);
}
// This one is very time-consuming to run:
#[test]
#[ignore]
fn twist_full() {
let f = 40;
run_test(examples::twist(f as f32, 128), 100*f, "screw_full", false);
}
*/
#[test]
fn ramhorn() {
run_test(examples::ramhorn(), 100, "ram_horn3", false);
}
/*
#[test]
fn ramhorn_branch_random() {
run_test(examples::ramhorn_branch_random(24, 0.25), 32, "ram_horn_branch_random", false);
}
*/
#[test]
fn test_parametric() {
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:

View File

@ -1,8 +1,7 @@
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io; use std::io;
use std::borrow::Borrow;
use crate::xform::{Vertex, Transform}; use crate::xform::{Transform, Vertex};
/// Basic face-vertex mesh. `faces` contains indices of `verts` and is /// Basic face-vertex mesh. `faces` contains indices of `verts` and is
/// taken in groups of 3 for each triangle. /// taken in groups of 3 for each triangle.
@ -26,19 +25,25 @@ impl Mesh {
/// Write this mesh as an STL file. This will fail if any element /// Write this mesh as an STL file. This will fail if any element
/// of `faces` is `Tag::Parent`. /// of `faces` is `Tag::Parent`.
pub fn write_stl_file(&self, fname: &str) -> io::Result<()> { pub fn write_stl_file(&self, fname: &str) -> io::Result<()> {
let mut file = OpenOptions::new().write(true).create(true).truncate(true).open(fname)?; let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(fname)?;
self.write_stl(&mut file) self.write_stl(&mut file)
} }
fn write_stl<W: std::io::Write>(&self, writer: &mut W) -> io::Result<()> { fn write_stl<W: std::io::Write>(&self, writer: &mut W) -> io::Result<()> {
// Every group of 3 indices in self.faces is one triangle, so // Every group of 3 indices in self.faces is one triangle, so
// pre-allocate in the format stl_io wants: // pre-allocate in the format stl_io wants:
let num_faces = self.faces.len() / 3; let num_faces = self.faces.len() / 3;
let mut triangles = vec![stl_io::Triangle { let mut triangles = vec![
stl_io::Triangle {
normal: [0.0; 3], normal: [0.0; 3],
vertices: [[0.0; 3]; 3], vertices: [[0.0; 3]; 3],
}; num_faces]; };
num_faces
];
// Turn every face into an stl_io::Triangle: // Turn every face into an stl_io::Triangle:
for i in 0..num_faces { for i in 0..num_faces {
@ -61,185 +66,4 @@ impl Mesh {
stl_io::write_stl(writer, triangles.iter()) stl_io::write_stl(writer, triangles.iter())
} }
pub fn to_meshfunc(&self) -> MeshFunc {
MeshFunc {
faces: self.faces.clone(),
verts: self.verts.iter().map(|v| VertexUnion::Vertex(*v)).collect(),
}
}
}
#[derive(Clone, Debug)]
pub enum VertexUnion {
/// A concrete vertex.
Vertex(Vertex),
/// A vertex argument - something like an argument to a function with
/// the given positional index.
///
/// The job of `MeshFunc.connect` is to bind these arguments to concrete
/// vertices.
Arg(usize),
}
pub fn vert_args<T: IntoIterator<Item = usize>>(v: T) -> Vec<VertexUnion> {
v.into_iter().map(|i| VertexUnion::Arg(i)).collect()
}
/// A face-vertex mesh, some of whose vertices may be 'vertex arguments'
/// rather than concrete vertices. The job of `connect()` is to resolve
/// these.
#[derive(Clone, Debug)]
pub struct MeshFunc {
pub verts: Vec<VertexUnion>,
/// Indices of triangles (taken as every 3 values).
pub faces: Vec<usize>,
}
impl MeshFunc {
pub fn to_mesh(&self) -> Mesh {
Mesh {
faces: self.faces.clone(),
verts: self.verts.iter().map(|v| match *v {
VertexUnion::Vertex(v) => v,
VertexUnion::Arg(_) => panic!("Mesh still has vertex arguments!"),
}).collect(),
}
}
/// Returns a new `MeshFunc` whose concrete vertices have
/// been transformed. Note that alias vertices are left untouched.
pub fn transform(&self, xfm: &Transform) -> MeshFunc {
let v = self.verts.iter().map(|v| {
match v {
VertexUnion::Vertex(v) => VertexUnion::Vertex(xfm.mtx * v),
a @ _ => a.clone(),
}
});
MeshFunc {
verts: v.collect(),
faces: self.faces.clone(),
}
}
/// Appends any number of meshes together. Returns both a single
/// mesh, and a vector which gives the offset by which each
/// corresponding input mesh was shifted. That is, for the i'th
/// index in `meshes`, all of its triangle indices were shifted by
/// the i'th offset in the resultant mesh.
pub fn append<T, U>(meshes: T) -> (MeshFunc, Vec<usize>)
where U: Borrow<MeshFunc>,
T: IntoIterator<Item=U>
{
let mut offsets: Vec<usize> = vec![];
let mut v: Vec<VertexUnion> = vec![];
let mut f: Vec<usize> = vec![];
for mesh_ in meshes {
let mesh = mesh_.borrow();
// Position in 'verts' at which we're appending
// mesh.verts, which we need to know to shift indices:
let offset = v.len();
offsets.push(offset);
// Copy all vertices:
v.append(&mut mesh.verts.clone());
// Append its faces, applying offset:
f.extend(mesh.faces.iter().map(|n| n + offset));
}
(MeshFunc { verts: v, faces: f }, offsets)
}
/// Treat this mesh as a 'parent' mesh to connect with any number
/// of 'child' meshes, all of them paired with their respective
/// vertex argument values (i.e. `arg_vals` from `Child`).
///
/// This returns a tuple of (new `MeshFunc`, new `arg_vals`), where
/// `arg_vals[i]` is the new index of `self.verts[i]` in the
/// returned `MeshFunc`.
pub fn connect<T, U>(&self, children: T) -> (MeshFunc, Vec<Vec<usize>>)
where U: Borrow<MeshFunc>,
T: IntoIterator<Item=(U, Vec<usize>)>
{
// TODO: Clean up this description a bit
// TODO: Clean up Vec<usize> stuff
// Copy body vertices & faces:
let mut verts: Vec<VertexUnion> = self.verts.clone();
let mut faces = self.faces.clone();
let mut remaps: Vec<Vec<usize>> = vec![];
for (child_, arg_vals) in children {
let child = child_.borrow();
// 'offset' corresponds to the position in 'verts' at
// which we begin appending everything in 'child.verts'.
let offset = verts.len();
// 'remap[i]' - if 'child.verts[i]' is a Vertex, not an Arg -
// will contain the index in 'verts' that this vertex was
// copied to. (This is needed because below we copy only
// the Vertex elements, not the Arg ones.)
let mut remap = vec![0; child.verts.len()];
let mut j = 0;
// Like mentioned, copy just the Vertex in 'child.verts':
verts.extend(child.verts.iter().enumerate().filter_map(|(i,v)| {
match v {
VertexUnion::Vertex(_) => {
// TODO: A less-side-effectful way?
remap[i] = offset + j;
j += 1;
Some(v.clone())
},
VertexUnion::Arg(_) => None,
}
}));
// So, note that:
// 1st Vertex in 'child.verts' went to 'verts[offset + 0]'.
// 2nd Vertex in 'child.verts' went to 'verts[offset + 1]'.
// 3rd Vertex in 'child.verts' went to 'verts[offset + 2]'.
// and so forth.
// Since this skips Arg elements, we used 'remap' to
// store the mapping: 'child.verts[i]' was copied to
// 'verts[remap[i]].'
// We then use 'remap' below to update the indices in
// 'child.faces' to vertices in the returned mesh.
//
// Also, we didn't copy Arg elements from 'child.verts', but
// we do use them below - if 'child.faces' makes a
// reference to this Arg, we resolve it with the help of
// 'arg_vals', passed in by the caller.
faces.extend(child.faces.iter().map(|n| {
let f = match child.verts[*n] {
VertexUnion::Vertex(_) => remap[*n],
VertexUnion::Arg(m) => {
println!("remap face: vert {} arg {} = vert {} ({:?})",
*n, m, arg_vals[m], verts[arg_vals[m]]
);
arg_vals[m]
},
};
if f >= verts.len() {
panic!("face >= num_verts")
}
f
}));
// Since the caller might need this same remapping (there
// could be an Arg that referred to these vertices), we
// save this information to return it later:
remaps.push(remap);
}
let m = MeshFunc {
verts: verts,
faces: faces,
};
(m, remaps)
}
} }

View File

@ -1,4 +1,4 @@
use crate::mesh::{Mesh, MeshFunc}; use crate::mesh::Mesh;
use crate::xform::{vertex, Transform}; use crate::xform::{vertex, Transform};
/// Returns an empty mesh (no vertices, no faces). /// Returns an empty mesh (no vertices, no faces).
@ -9,14 +9,6 @@ pub fn empty_mesh() -> Mesh {
} }
} }
/// Returns an empty MeshFn (no vertices, no faces, thus no args).
pub fn empty_meshfunc() -> MeshFunc {
MeshFunc {
verts: vec![],
faces: vec![],
}
}
/// Returns a cube of sidelength one centered at (0,0,0). /// Returns a cube of sidelength one centered at (0,0,0).
pub fn cube() -> Mesh { pub fn cube() -> Mesh {
Mesh { Mesh {
@ -31,18 +23,9 @@ pub fn cube() -> Mesh {
vertex(1.0, 1.0, 1.0), vertex(1.0, 1.0, 1.0),
], ],
faces: vec![ faces: vec![
0, 3, 1, 0, 3, 1, 0, 2, 3, 1, 7, 5, 1, 3, 7, 5, 6, 4, 5, 7, 6, 4, 2, 0, 4, 6, 2, 2, 7, 3, 2, 6,
0, 2, 3, 7, 0, 1, 5, 0, 5, 4,
1, 7, 5,
1, 3, 7,
5, 6, 4,
5, 7, 6,
4, 2, 0,
4, 6, 2,
2, 7, 3,
2, 6, 7,
0, 1, 5,
0, 5, 4,
], ],
}.transform(&Transform::new().translate(-0.5, -0.5, -0.5)) }
.transform(&Transform::new().translate(-0.5, -0.5, -0.5))
} }

View File

@ -1,498 +0,0 @@
use std::borrow::Borrow;
use std::rc::Rc;
use std::f32;
use crate::mesh::{Mesh, MeshFunc, VertexUnion};
use crate::xform::{Transform, Vertex};
pub type RuleFn<S> = Rc<dyn Fn(Rc<Rule<S>>) -> RuleEval<S>>;
/// Definition of a rule. In general, a `Rule`:
///
/// - produces geometry when it is evaluated
/// - tells what other rules to invoke, and what to do with their
/// geometry
pub struct Rule<S> {
pub eval: RuleFn<S>,
pub ctxt: S,
}
// TODO: It may be possible to have just a 'static' rule that requires
// no function call.
// TODO: Do I benefit with Rc<Rule> below so Rule can be shared?
// TODO: Why *can't* I make this FnOnce?
// The above looks like it is going to require a lifetime parameter
// regardless, in which case I don't really need Box.
/// `RuleEval` supplies the results of evaluating some `Rule` for one
/// iteration: it contains the geometry produced at this step
/// (`geom`), and it tells what to do next depending on whether
/// recursion continues further, or is stopped here (due to hitting
/// some limit of iterations or some lower limit on overall scale).
///
/// That is:
/// - if recursion stops, `final_geom` is connected with `geom`.
/// - if recursion continues, the rules of `children` are evaluated,
/// and the resultant geometry is transformed and then connected with
/// `geom`.
pub struct RuleEval<S> {
/// The geometry generated at just this iteration
pub geom: Rc<MeshFunc>,
/// The "final" geometry that is merged with `geom` via
/// `connect()` in the event that recursion stops. This must be
/// in the same coordinate space as `geom`.
///
/// Parent vertex references will be resolved directly to `geom`
/// with no mapping.
/// (TODO: Does this make sense? Nowhere else do I treat Arg(n) as
/// an index - it's always a positional argument.)
pub final_geom: Rc<MeshFunc>,
/// The child invocations (used if recursion continues). The
/// 'parent' mesh, from the perspective of all geometry produced
/// by `children`, is `geom`.
pub children: Vec<Child<S>>,
}
/// `Child` evaluations, pairing another `Rule` with the
/// transformations and parent vertex mappings that should be applied
/// to it.
pub struct Child<S> {
/// Rule to evaluate to produce geometry
pub rule: Rc<Rule<S>>,
/// The transform to apply to all geometry produced by `rule`
/// (including its own `geom` and `final_geom` if needed, as well
/// as all sub-geometry produced recursively).
pub xf: Transform,
/// The 'argument values' to apply to vertex arguments of a `MeshFunc`
/// from `geom` and `final_geom` that `rule` produces when evaluated.
/// The values of this are treated as indices into the parent
/// `RuleEval` that produced this `Child`.
///
/// In specific: if `arg_vals[i] = j` and `rule` produces some `geom` or
/// `final_geom`, then any vertex of `VertexUnion::Arg(i)` will be mapped
/// to `geom.verts[j]` in the *parent* geometry.
pub arg_vals: Vec<usize>,
}
#[macro_export]
macro_rules! child {
( $Rule:expr, $Xform:expr, $( $Arg:expr ),* ) => {
Child {
rule: /*std::rc::Rc::new*/($Rule).clone(),
xf: $Xform,
arg_vals: vec![$($Arg,)*],
}
}
}
#[macro_export]
macro_rules! child_iter {
( $Rule:expr, $Xform:expr, $Args:expr ) => {
Child {
rule: /*std::rc::Rc::new*/($Rule).clone(),
xf: $Xform,
arg_vals: $Args.collect(), // does this even need a macro?
}
}
}
#[macro_export]
macro_rules! rule {
( $RuleFn:expr, $Ctxt:expr ) => {
std::rc::Rc::new(Rule {
eval: $RuleFn.clone(),
ctxt: $Ctxt,
})
}
}
#[macro_export]
macro_rules! rule_fn {
( $Ty:ty => |$Self:ident $(,$x:ident)*| $Body:expr ) => {
{
$(let $x = $x.clone();)*
std::rc::Rc::new(move |$Self: std::rc::Rc<Rule<$Ty>>| -> RuleEval<$Ty> {
$(let $x = $x.clone();)*
let $Self = $Self.clone();
$Body
})
}
}
}
// TODO: Shouldn't I fully-qualify Rule & RuleEval?
// TODO: Document all of the above macros
// TODO: Why must I clone twice?
impl<S> Rule<S> {
/// Convert this `Rule` to mesh data, recursively (depth first).
/// `iters_left` sets the maximum recursion depth. This returns
/// (geometry, number of rule evaluations).
pub fn to_mesh(s: Rc<Rule<S>>, iters_left: usize) -> (MeshFunc, usize) {
let mut evals = 1;
let rs: RuleEval<S> = (s.eval)(s.clone());
if iters_left <= 0 {
return ((*rs.final_geom).clone(), 1);
// TODO: This is probably wrong because of the way that
// sub.arg_vals is used below. final_geom is not supposed to
// have any vertex mapping applied.
}
// TODO: This logic is more or less right, but it
// could perhaps use some un-tupling or something.
let subgeom: Vec<(MeshFunc, Vec<usize>)> = rs.children.iter().map(|sub| {
// Get sub-geometry (still un-transformed):
let (submesh, eval) = Rule::to_mesh(sub.rule.clone(), iters_left - 1);
// Tally up eval count:
evals += eval;
let m2 = submesh.transform(&sub.xf);
(m2, sub.arg_vals.clone())
// TODO: Fix clone?
}).collect();
// Connect geometry from this rule (not child rules):
return (rs.geom.connect(subgeom).0, evals);
}
/// This should be identical to to_mesh, but implemented
/// iteratively with an explicit stack rather than with recursive
/// function calls.
pub fn to_mesh_iter(s: Rc<Rule<S>>, max_depth: usize) -> (MeshFunc, usize) {
struct State<S> {
// The set of rules we're currently handling:
rules: Vec<Child<S>>,
// The next element of 'children' to handle:
next: usize,
// World transform of the *parent* of 'rules', that is,
// not including any transform of any element of 'rules'.
xf: Transform,
// How many levels 'deeper' can we recurse?
depth: usize,
}
// 'stack' stores at its last element our "current" State in
// terms of a current world transform and which Child should
// be processed next. Every element prior to this is previous
// states which must be kept around for further backtracking
// (usually because they involve multiple rules).
//
// We evaluate our own rule to initialize the stack:
let eval = (s.eval)(s.clone());
let mut stack: Vec<State<S>> = vec![State {
rules: eval.children,
next: 0,
xf: Transform::new(),
depth: max_depth,
}];
let mut geom = (*eval.geom).clone();
// Number of times we've evaluated a Rule:
let mut eval_count = 1;
// Stack depth (update at every push & pop):
let mut n = stack.len();
while !stack.is_empty() {
// s = the 'current' state:
let s = &mut stack[n-1];
let depth = s.depth;
if s.rules.is_empty() {
stack.pop();
n -= 1;
continue;
}
// Evaluate the rule:
let child = &s.rules[s.next];
let mut eval = (child.rule.eval)(child.rule.clone());
eval_count += 1;
// Make an updated world transform:
let xf = s.xf * child.xf;
// This rule produced some geometry which we'll
// combine with the 'global' geometry:
let new_geom = eval.geom.transform(&xf);
// See if we can still recurse further:
if depth <= 0 {
// As we're stopping recursion, we need to connect
// final_geom with all else in order to actually close
// geometry properly:
let final_geom = eval.final_geom.transform(&xf);
// TODO: Fix the awful hack below. I do this only to
// generate an identity mapping for arg_vals when I don't
// actually need arg_vals.
let m = {
let mut m_ = 0;
for v in &final_geom.verts {
match *v {
VertexUnion::Arg(a) => {
if a > m_ {
m_ = a;
}
},
VertexUnion::Vertex(_) => (),
}
}
m_ + 1
};
let arg_vals: Vec<usize> = (0..m).collect();
let (geom2, _) = new_geom.connect(vec![(final_geom, arg_vals)]);
geom = geom.connect(vec![(geom2, child.arg_vals.clone())]).0;
// TODO: Fix clone?
// If we end recursion on one child, we must end it
// similarly on every sibling (i.e. get its geometry &
// final geometry, and merge it in) - so we increment
// s.next and let the loop re-run.
s.next += 1;
if s.next >= s.rules.len() {
// Backtrack only at the last child:
stack.pop();
n -= 1;
}
continue;
}
let (g, remaps) = geom.connect(vec![(new_geom, child.arg_vals.clone())]);
geom = g;
// 'eval.children' may contain (via 'arg_vals') references to
// indices of 'new_geom'. However, we don't connect() to
// 'new_geom', but to the global geometry we just merged it
// into. To account for this, we must remap 'arg_vals' by the
// mapping connect() gave us:
let remap = &remaps[0];
// (We pass a one-element vector to geom.connect() above
// so remaps always has just one element.)
for child in eval.children.iter_mut() {
child.arg_vals = child.arg_vals.iter().map(|n| remap[*n]).collect();
}
// We're done evaluating this rule, so increment 'next'.
// If that was the last rule at this level (i.e. ignoring
// eval.children), remove it - we're done with it.
s.next += 1;
if s.next >= s.rules.len() {
stack.pop();
n -= 1;
}
if !eval.children.is_empty() {
// Recurse further (i.e. put more onto stack):
stack.push(State {
rules: eval.children,
next: 0,
xf: xf,
depth: depth - 1,
});
n += 1;
}
}
return (geom, eval_count);
}
}
impl<S> RuleEval<S> {
/// Turn an iterator of (MeshFunc, Child) into a single RuleEval.
/// All meshes are merged, and the `arg_vals` in each child has the
/// correct offsets applied to account for this merge.
///
/// (`final_geom` is passed through to the RuleEval unmodified.)
pub fn from_pairs<T, U>(m: T, final_geom: MeshFunc) -> RuleEval<S>
where U: Borrow<MeshFunc>,
T: IntoIterator<Item = (U, Child<S>)>
{
let (meshes, children): (Vec<_>, Vec<_>) = m.into_iter().unzip();
let (mesh, offsets) = MeshFunc::append(meshes);
// Patch up arg_vals in each child, and copy everything else:
let children2: Vec<Child<S>> = children.iter().zip(offsets.iter()).map(|(c,off)| {
Child {
rule: c.rule.clone(),
xf: c.xf.clone(),
// simply add offset:
arg_vals: c.arg_vals.iter().map(|i| i + off).collect(),
}
}).collect();
RuleEval {
geom: Rc::new(mesh),
final_geom: Rc::new(final_geom),
children: children2,
}
}
}
// fa001f47d40de989da6963e442f31c278c88abc8
/// Produce a mesh from a starting frame, and a function `f` which produces
/// transformations that change continuously over its argument (the range
/// of which is given by `t0` and `t1`). By convention, `f(t0)` should
/// always produce an identity transformation.
///
/// Facetization is guided by the given error, `max_err`, which is treated
/// as a distance in 3D space.
pub fn parametric_mesh<F>(frame: Vec<Vertex>, f: F, t0: f32, t1: f32, max_err: f32) -> Mesh
where F: Fn(f32) -> Transform
{
let n = frame.len();
// Sanity checks:
if t1 <= t0 {
panic!("t1 must be greater than t0");
}
if n < 3 {
panic!("frame must have at least 3 vertices");
}
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
mesh_idx: usize, // Index of this vertex in the mesh
neighbor1: usize, // Index of 'frontier' of one neighbor
neighbor2: usize, // Index of 'frontier' of other neighbor
};
// Init 'frontier' with each 'frame' vertex, and start it at t=t0.
let mut frontier: Vec<FrontierVert> = frame.iter().enumerate().map(|(i,v)| FrontierVert {
vert: *v,
t: t0,
frame_idx: i,
mesh_idx: i,
neighbor1: (i - 1) % n,
neighbor2: (i + 1) % n,
}).collect();
// Every vertex in 'frontier' has a trajectory it follows - which is
// simply the position as we transform the original vertex by f(t),
// and increment t through the range [t0, t1].
//
// The goal is to advance the vertices, one at a time, building up
// 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<Vertex> = frame.clone();
let mut faces: Vec<usize> = vec![];
while !frontier.is_empty() {
// Pick a vertex to advance.
//
// Heuristic for now: pick the 'furthest back' (lowest t)
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?
if v.t >= t1 {
break;
}
println!("DEBUG: Moving vertex {}, {:?} (t={}, frame_idx={})", i, v.vert, v.t, v.frame_idx);
let mut dt = (t1 - t0) / 100.0;
let vf = frame[v.frame_idx];
for iter in 0..100 {
// 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).
//
// If we assume some continuity in f, then we can guess that
// the worst error occurs at the midpoint of the edge:
let edge_mid = 0.5*(f(v.t).mtx + f(v.t + dt).mtx)*vf;
// ...relative to the trajectory midpoint:
let traj_mid = f(v.t + dt/2.0).mtx * vf;
let err = (edge_mid - traj_mid).norm();
println!("DEBUG iter {}: dt={}, edge_mid={:?}, traj_mid={:?}, err={}", iter, dt, edge_mid, traj_mid, err);
let r = (err - max_err).abs() / max_err;
if r < 0.10 {
println!("err close enough");
break;
} else if err > max_err {
dt = dt / 2.0;
println!("err > max_err, reducing dt to {}", dt);
} else {
dt = dt * 1.2;
println!("err < max_err, increasing dt to {}", dt);
}
}
let t = v.t + dt;
let v_next = f(t).mtx * vf;
// Add this vertex to our mesh:
let pos = verts.len();
verts.push(v_next);
// There are 3 other vertices of interest: the one we started
// from (v) and its two neighbors. We make two edges - one on
// each side of the edge (v, v_next).
faces.append(&mut vec![
v.mesh_idx, pos, frontier[v.neighbor1].mesh_idx,
pos, v.mesh_idx, frontier[v.neighbor2].mesh_idx,
]);
// Replace this vertex in the frontier:
frontier[i] = FrontierVert {
vert: v_next,
frame_idx: v.frame_idx,
mesh_idx: pos,
t: t,
neighbor1: v.neighbor1,
neighbor2: v.neighbor2,
}
}
// 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 };
}

View File

@ -1,6 +1,6 @@
use crate::mesh::Mesh;
use crate::xform::Vertex;
use std::ops::Range; use std::ops::Range;
use crate::mesh::{Mesh, MeshFunc, VertexUnion};
use crate::xform::{Vertex};
/// This is like `vec!`, but it can handle elements that are given /// This is like `vec!`, but it can handle elements that are given
/// with `@var element` rather than `element`, e.g. like /// with `@var element` rather than `element`, e.g. like
@ -42,7 +42,8 @@ impl<T> VecExt<T> for Vec<T> {
/// (thus, the returned length will be `count*p.len()`). /// (thus, the returned length will be `count*p.len()`).
pub fn subdivide_cycle(p: &Vec<Vertex>, count: usize) -> Vec<Vertex> { pub fn subdivide_cycle(p: &Vec<Vertex>, count: usize) -> Vec<Vertex> {
let n = p.len(); let n = p.len();
(0..n).map(|i| { (0..n)
.map(|i| {
// The inner loop is interpolating between v1 and v2: // The inner loop is interpolating between v1 and v2:
let v1 = &p[i]; let v1 = &p[i];
let v2 = &p[(i + 1) % n]; let v2 = &p[(i + 1) % n];
@ -51,7 +52,9 @@ pub fn subdivide_cycle(p: &Vec<Vertex>, count: usize) -> Vec<Vertex> {
let f = (j as f32) / (count as f32); let f = (j as f32) / (count as f32);
v1 * (1.0 - f) + v2 * f v1 * (1.0 - f) + v2 * f
}) })
}).flatten().collect() })
.flatten()
.collect()
} }
// TODO: This can be generalized to an iterator or to IntoIterator // TODO: This can be generalized to an iterator or to IntoIterator
// trait bound // trait bound
@ -61,28 +64,27 @@ pub fn parallel_zigzag_faces(r1: Range<usize>, r2: Range<usize>) -> Vec<usize> {
if (r2.end - r2.start) != count { if (r2.end - r2.start) != count {
panic!("Ranges must have the same size"); panic!("Ranges must have the same size");
} }
if (r2.end > r1.start && r2.end < r1.end) || if (r2.end > r1.start && r2.end < r1.end) || (r1.end > r2.start && r1.end < r2.end) {
(r1.end > r2.start && r1.end < r2.end) {
panic!("Ranges cannot overlap"); panic!("Ranges cannot overlap");
} }
(0..count).map(|i0| { (0..count)
.map(|i0| {
// i0 is an *offset* for the 'current' index. // i0 is an *offset* for the 'current' index.
// i1 is for the 'next' index, wrapping back to 0. // i1 is for the 'next' index, wrapping back to 0.
let i1 = (i0 + 1) % count; let i1 = (i0 + 1) % count;
vec![ vec![
// Mind winding order! // Mind winding order!
r1.start + i1, r2.start + i0, r1.start + i0, r1.start + i1,
r2.start + i1, r2.start + i0, r1.start + i1, r2.start + i0,
r1.start + i0,
r2.start + i1,
r2.start + i0,
r1.start + i1,
] ]
}).flatten().collect() })
} .flatten()
.collect()
pub fn parallel_zigzag(verts: Vec<VertexUnion>, main: Range<usize>, parent: Range<usize>) -> MeshFunc {
MeshFunc {
verts: verts,
faces: parallel_zigzag_faces(main, parent),
}
} }
pub fn parallel_zigzag_mesh(verts: Vec<Vertex>, main: Range<usize>, parent: Range<usize>) -> Mesh { pub fn parallel_zigzag_mesh(verts: Vec<Vertex>, main: Range<usize>, parent: Range<usize>) -> Mesh {
@ -93,7 +95,9 @@ pub fn parallel_zigzag_mesh(verts: Vec<Vertex>, main: Range<usize>, parent: Rang
} }
pub fn parallel_zigzag2<T, U>(main: T, parent: U) -> Vec<usize> pub fn parallel_zigzag2<T, U>(main: T, parent: U) -> Vec<usize>
where T: IntoIterator<Item=usize>, U: IntoIterator<Item=usize> where
T: IntoIterator<Item = usize>,
U: IntoIterator<Item = usize>,
{ {
let m: Vec<usize> = main.into_iter().collect(); let m: Vec<usize> = main.into_iter().collect();
let p: Vec<usize> = parent.into_iter().collect(); let p: Vec<usize> = parent.into_iter().collect();
@ -102,16 +106,18 @@ where T: IntoIterator<Item=usize>, U: IntoIterator<Item=usize>
panic!("Vectors must be the same size!") panic!("Vectors must be the same size!")
} }
(0..l).map(|i0| { (0..l)
.map(|i0| {
// i0 is an *offset* for the 'current' index. // i0 is an *offset* for the 'current' index.
// i1 is for the 'next' index, wrapping back to 0. // i1 is for the 'next' index, wrapping back to 0.
let i1 = (i0 + 1) % l; let i1 = (i0 + 1) % l;
vec![ vec![
// Mind winding order! // Mind winding order!
m[i1], p[i0], m[i0], m[i1], p[i0], m[i0], p[i1], p[i0], m[i1],
p[i1], p[i0], m[i1],
] ]
}).flatten().collect() })
.flatten()
.collect()
} }
pub fn centroid(verts: &Vec<Vertex>) -> Vertex { pub fn centroid(verts: &Vec<Vertex>) -> Vertex {
@ -125,17 +131,22 @@ pub fn centroid(verts: &Vec<Vertex>) -> Vertex {
} }
pub fn connect_convex(range: Range<usize>, target: usize, as_parent: bool) -> Vec<usize> { pub fn connect_convex(range: Range<usize>, target: usize, as_parent: bool) -> Vec<usize> {
let count = range.end - range.start; let count = range.end - range.start;
if as_parent { if as_parent {
(0..count).map(|i0| { (0..count)
.map(|i0| {
let i1 = (i0 + 1) % count; let i1 = (i0 + 1) % count;
vec![range.start + i1, range.start + i0, target] vec![range.start + i1, range.start + i0, target]
}).flatten().collect() })
.flatten()
.collect()
} else { } else {
(0..count).map(|i0| { (0..count)
.map(|i0| {
let i1 = (i0 + 1) % count; let i1 = (i0 + 1) % count;
vec![range.start + i0, range.start + i1, target] vec![range.start + i0, range.start + i1, target]
}).flatten().collect() })
.flatten()
.collect()
} }
} }