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]
name = "mesh_scratch"
name = "prosha"
version = "0.1.0"
authors = ["Chris Hodapp <hodapp87@gmail.com>"]
edition = "2018"
@ -16,3 +16,4 @@ euclid = "0.20.7"
nalgebra = "0.19.0"
stl_io = "0.4.2"
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
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.
(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
(which is almost literally the only way it *can* be used
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
`Vec` - and so I could probably see some huge performance
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
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
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:
- Begin converting older examples.
- Trash all the dead code.
- Work on abstraction/composition. Particularly: factor out
patterns I use, and be able to *compose* procedural meshes
somehow - e.g. the 'context' object I discussed.
- Docs on modules
- Make some examples that are non-deterministic!
- swept-isocontour stuff from
`/mnt/dev/graphics_misc/isosurfaces_2018_2019/spiral*.py`. This
will probably require that I figure out parametric curves
(is this stuff still possible?)
- Make an example that is more discrete-automata, less
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)
- https://en.wikipedia.org/wiki/Surface_triangulation
- https://www.cs.cmu.edu/~quake/triangle.html
- OpenSubdiv!
## Reflections & Quick Notes
@ -130,3 +152,34 @@ also was vastly faster at generating meshes.
the current local space.
- Don't reinvent subdivision surfaces.
- 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;
#[macro_use]
pub mod rule;
pub mod prim;
#[macro_use]
pub mod util;
pub mod xform;
pub mod examples;
pub mod dcel;
pub mod xform;
//pub use crate::examples;
//pub use crate::openmesh::test_thing;
@ -14,32 +12,9 @@ pub mod dcel;
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::*;
use std::rc::Rc;
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]
fn xform_order() {
@ -57,13 +32,27 @@ mod tests {
let xf2 = rot.translate(dx, 0.0, 0.0);
// 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(&(rot * trans)).write_stl_file("xform_mul_rot_trans.stl").unwrap();
geom.transform(&xf2).write_stl_file("xform_rot_trans.stl").unwrap();
geom.transform(&trans)
.transform(&rot)
.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:
geom.transform(&rot).transform(&trans).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();
geom.transform(&rot)
.transform(&trans)
.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
@ -80,11 +69,10 @@ mod tests {
let fname = format!("{}.stl", name);
println!("Writing {}...", fname);
m.write_stl_file(&fname).unwrap();
}
#[test]
fn tree_thing() {
fn tree_thing1() {
let name = "tree_thing";
println!("---------------------------------------------------");
let b = examples::TreeThing::new(0.6, 10);
@ -95,7 +83,6 @@ mod tests {
let fname = format!("{}.stl", name);
println!("Writing {}...", fname);
m.write_stl_file(&fname).unwrap();
}
#[test]
@ -110,73 +97,36 @@ mod tests {
let fname = format!("{}.stl", name);
println!("Writing {}...", fname);
m.write_stl_file(&fname).unwrap();
}
#[test]
fn sierpinski() { run_test(examples::sierpinski(), 6, "sierpinski", false); }
/*
#[test]
fn twist() {
run_test(examples::twist(1.0, 2), 200, "screw", false);
fn sierpinski() {
let name = "sierpinski";
println!("---------------------------------------------------");
let b = examples::Sierpinski::new(0.50, 0.10, 0.0);
//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]
fn twisty_torus() {
run_test(examples::twisty_torus(), 3000, "twisty_torus", false);
}
fn nested_spiral() {
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]
fn twisty_torus_hardcode() {
run_test(examples::twisty_torus_hardcode(), 1000, "twisty_torus_hardcode", false);
}
println!("Got {} verts...", m.verts.len());
#[test]
#[ignore]
fn twisty_torus_full() {
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");
let fname = format!("{}.stl", name);
println!("Writing {}...", fname);
m.write_stl_file(&fname).unwrap();
}
}
// need this for now:

View File

@ -1,8 +1,7 @@
use std::fs::OpenOptions;
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
/// 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
/// of `faces` is `Tag::Parent`.
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)
}
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
// pre-allocate in the format stl_io wants:
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],
vertices: [[0.0; 3]; 3],
}; num_faces];
};
num_faces
];
// Turn every face into an stl_io::Triangle:
for i in 0..num_faces {
@ -61,185 +66,4 @@ impl Mesh {
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};
/// 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).
pub fn cube() -> Mesh {
Mesh {
@ -31,18 +23,9 @@ pub fn cube() -> Mesh {
vertex(1.0, 1.0, 1.0),
],
faces: vec![
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, 7,
0, 1, 5,
0, 5, 4,
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,
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 crate::mesh::{Mesh, MeshFunc, VertexUnion};
use crate::xform::{Vertex};
/// This is like `vec!`, but it can handle elements that are given
/// 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()`).
pub fn subdivide_cycle(p: &Vec<Vertex>, count: usize) -> Vec<Vertex> {
let n = p.len();
(0..n).map(|i| {
(0..n)
.map(|i| {
// The inner loop is interpolating between v1 and v2:
let v1 = &p[i];
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);
v1 * (1.0 - f) + v2 * f
})
}).flatten().collect()
})
.flatten()
.collect()
}
// TODO: This can be generalized to an iterator or to IntoIterator
// 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 {
panic!("Ranges must have the same size");
}
if (r2.end > r1.start && r2.end < r1.end) ||
(r1.end > r2.start && r1.end < r2.end) {
if (r2.end > r1.start && r2.end < r1.end) || (r1.end > r2.start && r1.end < r2.end) {
panic!("Ranges cannot overlap");
}
(0..count).map(|i0| {
(0..count)
.map(|i0| {
// i0 is an *offset* for the 'current' index.
// i1 is for the 'next' index, wrapping back to 0.
let i1 = (i0 + 1) % count;
vec![
// Mind winding order!
r1.start + i1, r2.start + i0, r1.start + i0,
r2.start + i1, r2.start + i0, r1.start + i1,
r1.start + i1,
r2.start + i0,
r1.start + i0,
r2.start + i1,
r2.start + i0,
r1.start + i1,
]
}).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),
}
})
.flatten()
.collect()
}
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>
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 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!")
}
(0..l).map(|i0| {
(0..l)
.map(|i0| {
// i0 is an *offset* for the 'current' index.
// i1 is for the 'next' index, wrapping back to 0.
let i1 = (i0 + 1) % l;
vec![
// Mind winding order!
m[i1], p[i0], m[i0],
p[i1], p[i0], m[i1],
m[i1], p[i0], m[i0], p[i1], p[i0], m[i1],
]
}).flatten().collect()
})
.flatten()
.collect()
}
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> {
let count = range.end - range.start;
if as_parent {
(0..count).map(|i0| {
(0..count)
.map(|i0| {
let i1 = (i0 + 1) % count;
vec![range.start + i1, range.start + i0, target]
}).flatten().collect()
})
.flatten()
.collect()
} else {
(0..count).map(|i0| {
(0..count)
.map(|i0| {
let i1 = (i0 + 1) % count;
vec![range.start + i0, range.start + i1, target]
}).flatten().collect()
})
.flatten()
.collect()
}
}