Compare commits
No commits in common. "e6c53fafdb261a6eb5873a2bf0d4016f48cbb02d" and "5603caa3c1c5a97e903bfcaf1acae0b46e56c61a" have entirely different histories.
e6c53fafdb
...
5603caa3c1
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "prosha"
|
||||
name = "mesh_scratch"
|
||||
version = "0.1.0"
|
||||
authors = ["Chris Hodapp <hodapp87@gmail.com>"]
|
||||
edition = "2018"
|
||||
@ -16,4 +16,3 @@ euclid = "0.20.7"
|
||||
nalgebra = "0.19.0"
|
||||
stl_io = "0.4.2"
|
||||
rand = "0.7.3"
|
||||
itertools = "0.10.0"
|
||||
|
||||
63
README.md
63
README.md
@ -1,16 +1,4 @@
|
||||
# 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 needs a title
|
||||
|
||||
This particular code base was started around 2019 December
|
||||
as an attempt to make meshes in a more "generative" style,
|
||||
@ -73,7 +61,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 was pretty slow when it was
|
||||
On top of this, my implementation is pretty slow when it is
|
||||
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
|
||||
@ -81,7 +69,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 did some very task-parallel
|
||||
more. Also, I'm pretty sure this code does some very task-parallel
|
||||
elements (e.g. anytime a rule branches), and multithreading should
|
||||
be able to exploit this if I care.
|
||||
|
||||
@ -98,23 +86,14 @@ 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:
|
||||
|
||||
- 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.
|
||||
- Begin converting older examples.
|
||||
- Trash all the dead code.
|
||||
- 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.
|
||||
|
||||
@ -140,7 +119,6 @@ with this one.
|
||||
- [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
|
||||
|
||||
@ -152,34 +130,3 @@ with this one.
|
||||
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`.
|
||||
|
||||

|
||||
|
||||
### Tree Thing
|
||||
|
||||
See `examples::TreeThing`. First one is "tree_thing" test in `lib.rs`:
|
||||
|
||||

|
||||
|
||||
Second is "tree_thing2" (this was from a larger Blender render):
|
||||
|
||||

|
||||
|
||||
### Sierpinski
|
||||
|
||||
See `examples::Sierpinski` & "sierpinski" test in `lib.rs`.
|
||||
This looks cooler with some randomness added and 3D printed.
|
||||
|
||||

|
||||
|
||||
### Triple Nested Spiral
|
||||
|
||||
See `examples::NestedSpiral` & "nested_spiral" test in `lib.rs`.
|
||||
|
||||

|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 979 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.0 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 292 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 892 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 812 KiB |
752
src/dcel.rs
Normal file
752
src/dcel.rs
Normal file
@ -0,0 +1,752 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
979
src/examples.rs
979
src/examples.rs
File diff suppressed because it is too large
Load Diff
140
src/lib.rs
140
src/lib.rs
@ -1,10 +1,12 @@
|
||||
pub mod mesh;
|
||||
#[macro_use]
|
||||
pub mod rule;
|
||||
pub mod prim;
|
||||
#[macro_use]
|
||||
pub mod util;
|
||||
pub mod examples;
|
||||
pub mod xform;
|
||||
pub mod examples;
|
||||
pub mod dcel;
|
||||
|
||||
//pub use crate::examples;
|
||||
//pub use crate::openmesh::test_thing;
|
||||
@ -12,9 +14,32 @@ pub mod xform;
|
||||
#[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() {
|
||||
@ -32,27 +57,13 @@ 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
|
||||
@ -69,10 +80,11 @@ mod tests {
|
||||
let fname = format!("{}.stl", name);
|
||||
println!("Writing {}...", fname);
|
||||
m.write_stl_file(&fname).unwrap();
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_thing1() {
|
||||
fn tree_thing() {
|
||||
let name = "tree_thing";
|
||||
println!("---------------------------------------------------");
|
||||
let b = examples::TreeThing::new(0.6, 10);
|
||||
@ -83,6 +95,7 @@ mod tests {
|
||||
let fname = format!("{}.stl", name);
|
||||
println!("Writing {}...", fname);
|
||||
m.write_stl_file(&fname).unwrap();
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -97,36 +110,73 @@ mod tests {
|
||||
let fname = format!("{}.stl", name);
|
||||
println!("Writing {}...", fname);
|
||||
m.write_stl_file(&fname).unwrap();
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
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();
|
||||
fn sierpinski() { run_test(examples::sierpinski(), 6, "sierpinski", false); }
|
||||
/*
|
||||
#[test]
|
||||
fn twist() {
|
||||
run_test(examples::twist(1.0, 2), 200, "screw", false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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();
|
||||
fn twisty_torus() {
|
||||
run_test(examples::twisty_torus(), 3000, "twisty_torus", false);
|
||||
}
|
||||
|
||||
println!("Got {} verts...", m.verts.len());
|
||||
#[test]
|
||||
fn twisty_torus_hardcode() {
|
||||
run_test(examples::twisty_torus_hardcode(), 1000, "twisty_torus_hardcode", false);
|
||||
}
|
||||
|
||||
let fname = format!("{}.stl", name);
|
||||
println!("Writing {}...", fname);
|
||||
m.write_stl_file(&fname).unwrap();
|
||||
#[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");
|
||||
}
|
||||
}
|
||||
// need this for now:
|
||||
|
||||
210
src/mesh.rs
210
src/mesh.rs
@ -1,7 +1,8 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::io;
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use crate::xform::{Transform, Vertex};
|
||||
use crate::xform::{Vertex, Transform};
|
||||
|
||||
/// Basic face-vertex mesh. `faces` contains indices of `verts` and is
|
||||
/// taken in groups of 3 for each triangle.
|
||||
@ -25,33 +26,27 @@ 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 {
|
||||
normal: [0.0; 3],
|
||||
vertices: [[0.0; 3]; 3],
|
||||
};
|
||||
num_faces
|
||||
];
|
||||
let mut triangles = vec![stl_io::Triangle {
|
||||
normal: [0.0; 3],
|
||||
vertices: [[0.0; 3]; 3],
|
||||
}; num_faces];
|
||||
|
||||
// Turn every face into an stl_io::Triangle:
|
||||
for i in 0..num_faces {
|
||||
let v0 = self.verts[self.faces[3 * i + 0]].xyz();
|
||||
let v1 = self.verts[self.faces[3 * i + 1]].xyz();
|
||||
let v2 = self.verts[self.faces[3 * i + 2]].xyz();
|
||||
let v0 = self.verts[self.faces[3*i + 0]].xyz();
|
||||
let v1 = self.verts[self.faces[3*i + 1]].xyz();
|
||||
let v2 = self.verts[self.faces[3*i + 2]].xyz();
|
||||
|
||||
let normal = (v1 - v0).cross(&(v2 - v0));
|
||||
let normal = (v1-v0).cross(&(v2-v0));
|
||||
|
||||
triangles[i].normal.copy_from_slice(&normal.as_slice());
|
||||
triangles[i].vertices[0].copy_from_slice(v0.as_slice());
|
||||
@ -66,4 +61,185 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
27
src/prim.rs
27
src/prim.rs
@ -1,4 +1,4 @@
|
||||
use crate::mesh::Mesh;
|
||||
use crate::mesh::{Mesh, MeshFunc};
|
||||
use crate::xform::{vertex, Transform};
|
||||
|
||||
/// Returns an empty mesh (no vertices, no faces).
|
||||
@ -9,6 +9,14 @@ 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 {
|
||||
@ -23,9 +31,18 @@ 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))
|
||||
}
|
||||
|
||||
498
src/rule.rs
Normal file
498
src/rule.rs
Normal file
@ -0,0 +1,498 @@
|
||||
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 };
|
||||
}
|
||||
115
src/util.rs
115
src/util.rs
@ -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,49 +42,47 @@ 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| {
|
||||
// The inner loop is interpolating between v1 and v2:
|
||||
let v1 = &p[i];
|
||||
let v2 = &p[(i + 1) % n];
|
||||
(0..count).map(move |j| {
|
||||
// This is just lerping in count+1 equally spaced steps:
|
||||
let f = (j as f32) / (count as f32);
|
||||
v1 * (1.0 - f) + v2 * f
|
||||
})
|
||||
(0..n).map(|i| {
|
||||
// The inner loop is interpolating between v1 and v2:
|
||||
let v1 = &p[i];
|
||||
let v2 = &p[(i+1) % n];
|
||||
(0..count).map(move |j| {
|
||||
// This is just lerping in count+1 equally spaced steps:
|
||||
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
|
||||
|
||||
pub fn parallel_zigzag_faces(r1: Range<usize>, r2: Range<usize>) -> Vec<usize> {
|
||||
let count = r1.end - r1.start;
|
||||
if (r2.end - r2.start) != count {
|
||||
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| {
|
||||
// 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,
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
(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,
|
||||
]
|
||||
}).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 {
|
||||
@ -94,10 +92,8 @@ 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>,
|
||||
pub fn parallel_zigzag2<T,U>(main: T, parent: U) -> Vec<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();
|
||||
@ -106,18 +102,16 @@ where
|
||||
panic!("Vectors must be the same size!")
|
||||
}
|
||||
|
||||
(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],
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
(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],
|
||||
]
|
||||
}).flatten().collect()
|
||||
}
|
||||
|
||||
pub fn centroid(verts: &Vec<Vertex>) -> Vertex {
|
||||
@ -131,22 +125,17 @@ 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| {
|
||||
let i1 = (i0 + 1) % count;
|
||||
vec![range.start + i1, range.start + i0, target]
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
(0..count).map(|i0| {
|
||||
let i1 = (i0 + 1) % count;
|
||||
vec![range.start + i1, range.start + i0, target]
|
||||
}).flatten().collect()
|
||||
} else {
|
||||
(0..count)
|
||||
.map(|i0| {
|
||||
let i1 = (i0 + 1) % count;
|
||||
vec![range.start + i0, range.start + i1, target]
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
(0..count).map(|i0| {
|
||||
let i1 = (i0 + 1) % count;
|
||||
vec![range.start + i0, range.start + i1, target]
|
||||
}).flatten().collect()
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user