Compare commits
10 Commits
5603caa3c1
...
e6c53fafdb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6c53fafdb | ||
|
|
1c9053a5ad | ||
|
|
670b7859a9 | ||
|
|
3ce441d86a | ||
|
|
5bee3c8aa3 | ||
|
|
04aca1b3d0 | ||
|
|
51cab46299 | ||
|
|
a664dbba16 | ||
|
|
9cb467e4d5 | ||
|
|
e7206f4598 |
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mesh_scratch"
|
name = "prosha"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Chris Hodapp <hodapp87@gmail.com>"]
|
authors = ["Chris Hodapp <hodapp87@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@ -16,3 +16,4 @@ euclid = "0.20.7"
|
|||||||
nalgebra = "0.19.0"
|
nalgebra = "0.19.0"
|
||||||
stl_io = "0.4.2"
|
stl_io = "0.4.2"
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
|
itertools = "0.10.0"
|
||||||
|
|||||||
63
README.md
63
README.md
@ -1,4 +1,16 @@
|
|||||||
# This needs a title
|
# Prosha
|
||||||
|
|
||||||
|
Experiments in procedural meshes in Rust
|
||||||
|
|
||||||
|
More detailed docs and examples forthcoming.
|
||||||
|
|
||||||
|
Due to some issues explained a little bit below and a little bit in
|
||||||
|
other blog posts, I'm now treating this as maintenance-only and not as
|
||||||
|
an active project.
|
||||||
|
|
||||||
|
(TODO: Link to these posts)
|
||||||
|
|
||||||
|
## General Notes & History
|
||||||
|
|
||||||
This particular code base was started around 2019 December
|
This particular code base was started around 2019 December
|
||||||
as an attempt to make meshes in a more "generative" style,
|
as an attempt to make meshes in a more "generative" style,
|
||||||
@ -61,7 +73,7 @@ A lot of what I wrote here ended up just being a buggy, half-assed
|
|||||||
interpreter for a buggy, half-assed EDSL/minilanguage.
|
interpreter for a buggy, half-assed EDSL/minilanguage.
|
||||||
(Greenspun's Tenth Rule of Programming, anyone?)
|
(Greenspun's Tenth Rule of Programming, anyone?)
|
||||||
|
|
||||||
On top of this, my implementation is pretty slow when it is
|
On top of this, my implementation was pretty slow when it was
|
||||||
using a large number of rules each producing small geometry
|
using a large number of rules each producing small geometry
|
||||||
(which is almost literally the only way it *can* be used
|
(which is almost literally the only way it *can* be used
|
||||||
if you want to produce a fairly complex mesh). I did some
|
if you want to produce a fairly complex mesh). I did some
|
||||||
@ -69,7 +81,7 @@ profiling some months ago that showed I was spending the
|
|||||||
vast majority of my time in `extend()` and `clone()` for
|
vast majority of my time in `extend()` and `clone()` for
|
||||||
`Vec` - and so I could probably see some huge performance
|
`Vec` - and so I could probably see some huge performance
|
||||||
gains if I could simply pre-allocate vectors and share geometry
|
gains if I could simply pre-allocate vectors and share geometry
|
||||||
more. Also, I'm pretty sure this code does some very task-parallel
|
more. Also, I'm pretty sure this code did some very task-parallel
|
||||||
elements (e.g. anytime a rule branches), and multithreading should
|
elements (e.g. anytime a rule branches), and multithreading should
|
||||||
be able to exploit this if I care.
|
be able to exploit this if I care.
|
||||||
|
|
||||||
@ -86,14 +98,23 @@ branch, thus named because I needed to get rid of my buggy
|
|||||||
implementation of half of Common Lisp. It paid off quite quickly and
|
implementation of half of Common Lisp. It paid off quite quickly and
|
||||||
also was vastly faster at generating meshes.
|
also was vastly faster at generating meshes.
|
||||||
|
|
||||||
|
## What does the name mean?
|
||||||
|
|
||||||
|
Nothing, but it's better than its previous name of
|
||||||
|
"mesh_scratch". I asked for name suggestions, and someone came up
|
||||||
|
with this one.
|
||||||
|
|
||||||
## Highest priority:
|
## Highest priority:
|
||||||
|
|
||||||
- Begin converting older examples.
|
- Work on abstraction/composition. Particularly: factor out
|
||||||
- Trash all the dead code.
|
patterns I use, and be able to *compose* procedural meshes
|
||||||
|
somehow - e.g. the 'context' object I discussed.
|
||||||
- Docs on modules
|
- Docs on modules
|
||||||
|
- Make some examples that are non-deterministic!
|
||||||
- swept-isocontour stuff from
|
- swept-isocontour stuff from
|
||||||
`/mnt/dev/graphics_misc/isosurfaces_2018_2019/spiral*.py`. This
|
`/mnt/dev/graphics_misc/isosurfaces_2018_2019/spiral*.py`. This
|
||||||
will probably require that I figure out parametric curves
|
will probably require that I figure out parametric curves
|
||||||
|
(is this stuff still possible?)
|
||||||
- Make an example that is more discrete-automata, less
|
- Make an example that is more discrete-automata, less
|
||||||
approximation-of-space-curve.
|
approximation-of-space-curve.
|
||||||
|
|
||||||
@ -119,6 +140,7 @@ also was vastly faster at generating meshes.
|
|||||||
- [Geometry and Algorithms for Computer Aided Design (Hartmann)](https://www2.mathematik.tu-darmstadt.de/~ehartmann/cdgen0104.pdf)
|
- [Geometry and Algorithms for Computer Aided Design (Hartmann)](https://www2.mathematik.tu-darmstadt.de/~ehartmann/cdgen0104.pdf)
|
||||||
- https://en.wikipedia.org/wiki/Surface_triangulation
|
- https://en.wikipedia.org/wiki/Surface_triangulation
|
||||||
- https://www.cs.cmu.edu/~quake/triangle.html
|
- https://www.cs.cmu.edu/~quake/triangle.html
|
||||||
|
- OpenSubdiv!
|
||||||
|
|
||||||
## Reflections & Quick Notes
|
## Reflections & Quick Notes
|
||||||
|
|
||||||
@ -130,3 +152,34 @@ also was vastly faster at generating meshes.
|
|||||||
the current local space.
|
the current local space.
|
||||||
- Don't reinvent subdivision surfaces.
|
- Don't reinvent subdivision surfaces.
|
||||||
- Don't reinvent Lisp when you wanted a Lisp!
|
- Don't reinvent Lisp when you wanted a Lisp!
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Barbs
|
||||||
|
|
||||||
|
See `examples::Barbs` & "barbs" test in `lib.rs`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 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`.
|
||||||
|
|
||||||
|

|
||||||
|
|||||||
BIN
images/barbs_preview.png
Normal file
BIN
images/barbs_preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 979 KiB |
BIN
images/nested_spiral.jpg
Normal file
BIN
images/nested_spiral.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
BIN
images/rust_tree_thing_03_smaller.jpg
Normal file
BIN
images/rust_tree_thing_03_smaller.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 292 KiB |
BIN
images/sierpinski_preview.png
Normal file
BIN
images/sierpinski_preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 892 KiB |
BIN
images/tree_thing_preview.jpg
Normal file
BIN
images/tree_thing_preview.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 812 KiB |
752
src/dcel.rs
752
src/dcel.rs
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
959
src/examples.rs
959
src/examples.rs
File diff suppressed because it is too large
Load Diff
140
src/lib.rs
140
src/lib.rs
@ -1,12 +1,10 @@
|
|||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod rule;
|
|
||||||
pub mod prim;
|
pub mod prim;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod xform;
|
|
||||||
pub mod examples;
|
pub mod examples;
|
||||||
pub mod dcel;
|
pub mod xform;
|
||||||
|
|
||||||
//pub use crate::examples;
|
//pub use crate::examples;
|
||||||
//pub use crate::openmesh::test_thing;
|
//pub use crate::openmesh::test_thing;
|
||||||
@ -14,32 +12,9 @@ pub mod dcel;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use nalgebra::*;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use rule::Rule;
|
|
||||||
use nalgebra::*;
|
|
||||||
|
|
||||||
fn run_test<S>(rule: Rule<S>, iters: usize, name: &str, use_old: bool) {
|
|
||||||
let r = Rc::new(rule);
|
|
||||||
println!("---------------------------------------------------");
|
|
||||||
println!("Running {} with {}...",
|
|
||||||
name, if use_old { "to_mesh" } else { "to_mesh_iter" });
|
|
||||||
if false {
|
|
||||||
let start = Instant::now();
|
|
||||||
let n = 5;
|
|
||||||
for _ in 0..n {
|
|
||||||
Rule::to_mesh_iter(r.clone(), iters);
|
|
||||||
}
|
|
||||||
let elapsed = start.elapsed();
|
|
||||||
println!("DEBUG: {} ms per run", elapsed.as_millis() / n);
|
|
||||||
}
|
|
||||||
let mesh_fn = if use_old { Rule::to_mesh } else { Rule::to_mesh_iter };
|
|
||||||
let (mesh, nodes) = mesh_fn(r.clone(), iters);
|
|
||||||
println!("Evaluated {} rules to {} verts", nodes, mesh.verts.len());
|
|
||||||
let fname = format!("{}.stl", name);
|
|
||||||
println!("Writing {}...", fname);
|
|
||||||
mesh.to_mesh().write_stl_file(&fname).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn xform_order() {
|
fn xform_order() {
|
||||||
@ -57,13 +32,27 @@ mod tests {
|
|||||||
let xf2 = rot.translate(dx, 0.0, 0.0);
|
let xf2 = rot.translate(dx, 0.0, 0.0);
|
||||||
|
|
||||||
// Rotate entire space, *then* translate in that rotated plane:
|
// Rotate entire space, *then* translate in that rotated plane:
|
||||||
geom.transform(&trans).transform(&rot).write_stl_file("xform_apply_trans_rot.stl").unwrap();
|
geom.transform(&trans)
|
||||||
geom.transform(&(rot * trans)).write_stl_file("xform_mul_rot_trans.stl").unwrap();
|
.transform(&rot)
|
||||||
geom.transform(&xf2).write_stl_file("xform_rot_trans.stl").unwrap();
|
.write_stl_file("xform_apply_trans_rot.stl")
|
||||||
|
.unwrap();
|
||||||
|
geom.transform(&(rot * trans))
|
||||||
|
.write_stl_file("xform_mul_rot_trans.stl")
|
||||||
|
.unwrap();
|
||||||
|
geom.transform(&xf2)
|
||||||
|
.write_stl_file("xform_rot_trans.stl")
|
||||||
|
.unwrap();
|
||||||
// Translate cube, *then* rotate it:
|
// Translate cube, *then* rotate it:
|
||||||
geom.transform(&rot).transform(&trans).write_stl_file("xform_apply_rot_trans.stl").unwrap();
|
geom.transform(&rot)
|
||||||
geom.transform(&(trans * rot)).write_stl_file("xform_mul_trans_rot.stl").unwrap();
|
.transform(&trans)
|
||||||
geom.transform(&xf1).write_stl_file("xform_trans_rot.stl").unwrap();
|
.write_stl_file("xform_apply_rot_trans.stl")
|
||||||
|
.unwrap();
|
||||||
|
geom.transform(&(trans * rot))
|
||||||
|
.write_stl_file("xform_mul_trans_rot.stl")
|
||||||
|
.unwrap();
|
||||||
|
geom.transform(&xf1)
|
||||||
|
.write_stl_file("xform_trans_rot.stl")
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: These tests don't test any conditions, so this is useful
|
// TODO: These tests don't test any conditions, so this is useful
|
||||||
@ -80,11 +69,10 @@ mod tests {
|
|||||||
let fname = format!("{}.stl", name);
|
let fname = format!("{}.stl", name);
|
||||||
println!("Writing {}...", fname);
|
println!("Writing {}...", fname);
|
||||||
m.write_stl_file(&fname).unwrap();
|
m.write_stl_file(&fname).unwrap();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tree_thing() {
|
fn tree_thing1() {
|
||||||
let name = "tree_thing";
|
let name = "tree_thing";
|
||||||
println!("---------------------------------------------------");
|
println!("---------------------------------------------------");
|
||||||
let b = examples::TreeThing::new(0.6, 10);
|
let b = examples::TreeThing::new(0.6, 10);
|
||||||
@ -95,7 +83,6 @@ mod tests {
|
|||||||
let fname = format!("{}.stl", name);
|
let fname = format!("{}.stl", name);
|
||||||
println!("Writing {}...", fname);
|
println!("Writing {}...", fname);
|
||||||
m.write_stl_file(&fname).unwrap();
|
m.write_stl_file(&fname).unwrap();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -110,73 +97,36 @@ mod tests {
|
|||||||
let fname = format!("{}.stl", name);
|
let fname = format!("{}.stl", name);
|
||||||
println!("Writing {}...", fname);
|
println!("Writing {}...", fname);
|
||||||
m.write_stl_file(&fname).unwrap();
|
m.write_stl_file(&fname).unwrap();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sierpinski() { run_test(examples::sierpinski(), 6, "sierpinski", false); }
|
fn sierpinski() {
|
||||||
/*
|
let name = "sierpinski";
|
||||||
#[test]
|
println!("---------------------------------------------------");
|
||||||
fn twist() {
|
let b = examples::Sierpinski::new(0.50, 0.10, 0.0);
|
||||||
run_test(examples::twist(1.0, 2), 200, "screw", false);
|
//let b = examples::Sierpinski::new(0.51, 0.10, 0.1);
|
||||||
|
let m = b.run();
|
||||||
|
|
||||||
|
println!("Got {} verts...", m.verts.len());
|
||||||
|
|
||||||
|
let fname = format!("{}.stl", name);
|
||||||
|
println!("Writing {}...", fname);
|
||||||
|
m.write_stl_file(&fname).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn twisty_torus() {
|
fn nested_spiral() {
|
||||||
run_test(examples::twisty_torus(), 3000, "twisty_torus", false);
|
let name = "nested_spiral";
|
||||||
}
|
println!("---------------------------------------------------");
|
||||||
|
let b = examples::NestedSpiral::new();
|
||||||
|
//let b = examples::Sierpinski::new(0.51, 0.10, 0.1);
|
||||||
|
let m = b.run();
|
||||||
|
|
||||||
#[test]
|
println!("Got {} verts...", m.verts.len());
|
||||||
fn twisty_torus_hardcode() {
|
|
||||||
run_test(examples::twisty_torus_hardcode(), 1000, "twisty_torus_hardcode", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
let fname = format!("{}.stl", name);
|
||||||
#[ignore]
|
println!("Writing {}...", fname);
|
||||||
fn twisty_torus_full() {
|
m.write_stl_file(&fname).unwrap();
|
||||||
run_test(examples::twisty_torus(), 40000, "twisty_torus_full", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn wind_chime_mistake_thing() {
|
|
||||||
run_test(examples::wind_chime_mistake_thing(), 400, "wind_chime_mistake_thing", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nest_spiral_2() {
|
|
||||||
run_test(examples::nest_spiral_2(), 200, "nest_spiral_2", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This one is very time-consuming to run:
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn twist_full() {
|
|
||||||
let f = 40;
|
|
||||||
run_test(examples::twist(f as f32, 128), 100*f, "screw_full", false);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ramhorn() {
|
|
||||||
run_test(examples::ramhorn(), 100, "ram_horn3", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
#[test]
|
|
||||||
fn ramhorn_branch_random() {
|
|
||||||
run_test(examples::ramhorn_branch_random(24, 0.25), 32, "ram_horn_branch_random", false);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parametric() {
|
|
||||||
examples::test_parametric().write_stl_file("test_parametric.stl").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_dcel() {
|
|
||||||
examples::test_dcel("test_dcel.stl");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// need this for now:
|
// need this for now:
|
||||||
|
|||||||
198
src/mesh.rs
198
src/mesh.rs
@ -1,8 +1,7 @@
|
|||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::borrow::Borrow;
|
|
||||||
|
|
||||||
use crate::xform::{Vertex, Transform};
|
use crate::xform::{Transform, Vertex};
|
||||||
|
|
||||||
/// Basic face-vertex mesh. `faces` contains indices of `verts` and is
|
/// Basic face-vertex mesh. `faces` contains indices of `verts` and is
|
||||||
/// taken in groups of 3 for each triangle.
|
/// taken in groups of 3 for each triangle.
|
||||||
@ -26,19 +25,25 @@ impl Mesh {
|
|||||||
/// Write this mesh as an STL file. This will fail if any element
|
/// Write this mesh as an STL file. This will fail if any element
|
||||||
/// of `faces` is `Tag::Parent`.
|
/// of `faces` is `Tag::Parent`.
|
||||||
pub fn write_stl_file(&self, fname: &str) -> io::Result<()> {
|
pub fn write_stl_file(&self, fname: &str) -> io::Result<()> {
|
||||||
let mut file = OpenOptions::new().write(true).create(true).truncate(true).open(fname)?;
|
let mut file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(fname)?;
|
||||||
self.write_stl(&mut file)
|
self.write_stl(&mut file)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_stl<W: std::io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
fn write_stl<W: std::io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
|
|
||||||
// Every group of 3 indices in self.faces is one triangle, so
|
// Every group of 3 indices in self.faces is one triangle, so
|
||||||
// pre-allocate in the format stl_io wants:
|
// pre-allocate in the format stl_io wants:
|
||||||
let num_faces = self.faces.len() / 3;
|
let num_faces = self.faces.len() / 3;
|
||||||
let mut triangles = vec![stl_io::Triangle {
|
let mut triangles = vec![
|
||||||
|
stl_io::Triangle {
|
||||||
normal: [0.0; 3],
|
normal: [0.0; 3],
|
||||||
vertices: [[0.0; 3]; 3],
|
vertices: [[0.0; 3]; 3],
|
||||||
}; num_faces];
|
};
|
||||||
|
num_faces
|
||||||
|
];
|
||||||
|
|
||||||
// Turn every face into an stl_io::Triangle:
|
// Turn every face into an stl_io::Triangle:
|
||||||
for i in 0..num_faces {
|
for i in 0..num_faces {
|
||||||
@ -61,185 +66,4 @@ impl Mesh {
|
|||||||
|
|
||||||
stl_io::write_stl(writer, triangles.iter())
|
stl_io::write_stl(writer, triangles.iter())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_meshfunc(&self) -> MeshFunc {
|
|
||||||
MeshFunc {
|
|
||||||
faces: self.faces.clone(),
|
|
||||||
verts: self.verts.iter().map(|v| VertexUnion::Vertex(*v)).collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum VertexUnion {
|
|
||||||
/// A concrete vertex.
|
|
||||||
Vertex(Vertex),
|
|
||||||
/// A vertex argument - something like an argument to a function with
|
|
||||||
/// the given positional index.
|
|
||||||
///
|
|
||||||
/// The job of `MeshFunc.connect` is to bind these arguments to concrete
|
|
||||||
/// vertices.
|
|
||||||
Arg(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vert_args<T: IntoIterator<Item = usize>>(v: T) -> Vec<VertexUnion> {
|
|
||||||
v.into_iter().map(|i| VertexUnion::Arg(i)).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A face-vertex mesh, some of whose vertices may be 'vertex arguments'
|
|
||||||
/// rather than concrete vertices. The job of `connect()` is to resolve
|
|
||||||
/// these.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct MeshFunc {
|
|
||||||
pub verts: Vec<VertexUnion>,
|
|
||||||
/// Indices of triangles (taken as every 3 values).
|
|
||||||
pub faces: Vec<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MeshFunc {
|
|
||||||
pub fn to_mesh(&self) -> Mesh {
|
|
||||||
Mesh {
|
|
||||||
faces: self.faces.clone(),
|
|
||||||
verts: self.verts.iter().map(|v| match *v {
|
|
||||||
VertexUnion::Vertex(v) => v,
|
|
||||||
VertexUnion::Arg(_) => panic!("Mesh still has vertex arguments!"),
|
|
||||||
}).collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a new `MeshFunc` whose concrete vertices have
|
|
||||||
/// been transformed. Note that alias vertices are left untouched.
|
|
||||||
pub fn transform(&self, xfm: &Transform) -> MeshFunc {
|
|
||||||
let v = self.verts.iter().map(|v| {
|
|
||||||
match v {
|
|
||||||
VertexUnion::Vertex(v) => VertexUnion::Vertex(xfm.mtx * v),
|
|
||||||
a @ _ => a.clone(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
MeshFunc {
|
|
||||||
verts: v.collect(),
|
|
||||||
faces: self.faces.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Appends any number of meshes together. Returns both a single
|
|
||||||
/// mesh, and a vector which gives the offset by which each
|
|
||||||
/// corresponding input mesh was shifted. That is, for the i'th
|
|
||||||
/// index in `meshes`, all of its triangle indices were shifted by
|
|
||||||
/// the i'th offset in the resultant mesh.
|
|
||||||
pub fn append<T, U>(meshes: T) -> (MeshFunc, Vec<usize>)
|
|
||||||
where U: Borrow<MeshFunc>,
|
|
||||||
T: IntoIterator<Item=U>
|
|
||||||
{
|
|
||||||
let mut offsets: Vec<usize> = vec![];
|
|
||||||
let mut v: Vec<VertexUnion> = vec![];
|
|
||||||
let mut f: Vec<usize> = vec![];
|
|
||||||
for mesh_ in meshes {
|
|
||||||
let mesh = mesh_.borrow();
|
|
||||||
|
|
||||||
// Position in 'verts' at which we're appending
|
|
||||||
// mesh.verts, which we need to know to shift indices:
|
|
||||||
let offset = v.len();
|
|
||||||
offsets.push(offset);
|
|
||||||
|
|
||||||
// Copy all vertices:
|
|
||||||
v.append(&mut mesh.verts.clone());
|
|
||||||
// Append its faces, applying offset:
|
|
||||||
f.extend(mesh.faces.iter().map(|n| n + offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
(MeshFunc { verts: v, faces: f }, offsets)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Treat this mesh as a 'parent' mesh to connect with any number
|
|
||||||
/// of 'child' meshes, all of them paired with their respective
|
|
||||||
/// vertex argument values (i.e. `arg_vals` from `Child`).
|
|
||||||
///
|
|
||||||
/// This returns a tuple of (new `MeshFunc`, new `arg_vals`), where
|
|
||||||
/// `arg_vals[i]` is the new index of `self.verts[i]` in the
|
|
||||||
/// returned `MeshFunc`.
|
|
||||||
pub fn connect<T, U>(&self, children: T) -> (MeshFunc, Vec<Vec<usize>>)
|
|
||||||
where U: Borrow<MeshFunc>,
|
|
||||||
T: IntoIterator<Item=(U, Vec<usize>)>
|
|
||||||
{
|
|
||||||
// TODO: Clean up this description a bit
|
|
||||||
// TODO: Clean up Vec<usize> stuff
|
|
||||||
|
|
||||||
// Copy body vertices & faces:
|
|
||||||
let mut verts: Vec<VertexUnion> = self.verts.clone();
|
|
||||||
let mut faces = self.faces.clone();
|
|
||||||
|
|
||||||
let mut remaps: Vec<Vec<usize>> = vec![];
|
|
||||||
|
|
||||||
for (child_, arg_vals) in children {
|
|
||||||
let child = child_.borrow();
|
|
||||||
|
|
||||||
// 'offset' corresponds to the position in 'verts' at
|
|
||||||
// which we begin appending everything in 'child.verts'.
|
|
||||||
let offset = verts.len();
|
|
||||||
|
|
||||||
// 'remap[i]' - if 'child.verts[i]' is a Vertex, not an Arg -
|
|
||||||
// will contain the index in 'verts' that this vertex was
|
|
||||||
// copied to. (This is needed because below we copy only
|
|
||||||
// the Vertex elements, not the Arg ones.)
|
|
||||||
let mut remap = vec![0; child.verts.len()];
|
|
||||||
let mut j = 0;
|
|
||||||
|
|
||||||
// Like mentioned, copy just the Vertex in 'child.verts':
|
|
||||||
verts.extend(child.verts.iter().enumerate().filter_map(|(i,v)| {
|
|
||||||
match v {
|
|
||||||
VertexUnion::Vertex(_) => {
|
|
||||||
// TODO: A less-side-effectful way?
|
|
||||||
remap[i] = offset + j;
|
|
||||||
j += 1;
|
|
||||||
Some(v.clone())
|
|
||||||
},
|
|
||||||
VertexUnion::Arg(_) => None,
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
// So, note that:
|
|
||||||
// 1st Vertex in 'child.verts' went to 'verts[offset + 0]'.
|
|
||||||
// 2nd Vertex in 'child.verts' went to 'verts[offset + 1]'.
|
|
||||||
// 3rd Vertex in 'child.verts' went to 'verts[offset + 2]'.
|
|
||||||
// and so forth.
|
|
||||||
// Since this skips Arg elements, we used 'remap' to
|
|
||||||
// store the mapping: 'child.verts[i]' was copied to
|
|
||||||
// 'verts[remap[i]].'
|
|
||||||
|
|
||||||
// We then use 'remap' below to update the indices in
|
|
||||||
// 'child.faces' to vertices in the returned mesh.
|
|
||||||
//
|
|
||||||
// Also, we didn't copy Arg elements from 'child.verts', but
|
|
||||||
// we do use them below - if 'child.faces' makes a
|
|
||||||
// reference to this Arg, we resolve it with the help of
|
|
||||||
// 'arg_vals', passed in by the caller.
|
|
||||||
faces.extend(child.faces.iter().map(|n| {
|
|
||||||
let f = match child.verts[*n] {
|
|
||||||
VertexUnion::Vertex(_) => remap[*n],
|
|
||||||
VertexUnion::Arg(m) => {
|
|
||||||
println!("remap face: vert {} arg {} = vert {} ({:?})",
|
|
||||||
*n, m, arg_vals[m], verts[arg_vals[m]]
|
|
||||||
);
|
|
||||||
arg_vals[m]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if f >= verts.len() {
|
|
||||||
panic!("face >= num_verts")
|
|
||||||
}
|
|
||||||
f
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Since the caller might need this same remapping (there
|
|
||||||
// could be an Arg that referred to these vertices), we
|
|
||||||
// save this information to return it later:
|
|
||||||
remaps.push(remap);
|
|
||||||
}
|
|
||||||
|
|
||||||
let m = MeshFunc {
|
|
||||||
verts: verts,
|
|
||||||
faces: faces,
|
|
||||||
};
|
|
||||||
(m, remaps)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/prim.rs
27
src/prim.rs
@ -1,4 +1,4 @@
|
|||||||
use crate::mesh::{Mesh, MeshFunc};
|
use crate::mesh::Mesh;
|
||||||
use crate::xform::{vertex, Transform};
|
use crate::xform::{vertex, Transform};
|
||||||
|
|
||||||
/// Returns an empty mesh (no vertices, no faces).
|
/// Returns an empty mesh (no vertices, no faces).
|
||||||
@ -9,14 +9,6 @@ pub fn empty_mesh() -> Mesh {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an empty MeshFn (no vertices, no faces, thus no args).
|
|
||||||
pub fn empty_meshfunc() -> MeshFunc {
|
|
||||||
MeshFunc {
|
|
||||||
verts: vec![],
|
|
||||||
faces: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a cube of sidelength one centered at (0,0,0).
|
/// Returns a cube of sidelength one centered at (0,0,0).
|
||||||
pub fn cube() -> Mesh {
|
pub fn cube() -> Mesh {
|
||||||
Mesh {
|
Mesh {
|
||||||
@ -31,18 +23,9 @@ pub fn cube() -> Mesh {
|
|||||||
vertex(1.0, 1.0, 1.0),
|
vertex(1.0, 1.0, 1.0),
|
||||||
],
|
],
|
||||||
faces: vec![
|
faces: vec![
|
||||||
0, 3, 1,
|
0, 3, 1, 0, 2, 3, 1, 7, 5, 1, 3, 7, 5, 6, 4, 5, 7, 6, 4, 2, 0, 4, 6, 2, 2, 7, 3, 2, 6,
|
||||||
0, 2, 3,
|
7, 0, 1, 5, 0, 5, 4,
|
||||||
1, 7, 5,
|
|
||||||
1, 3, 7,
|
|
||||||
5, 6, 4,
|
|
||||||
5, 7, 6,
|
|
||||||
4, 2, 0,
|
|
||||||
4, 6, 2,
|
|
||||||
2, 7, 3,
|
|
||||||
2, 6, 7,
|
|
||||||
0, 1, 5,
|
|
||||||
0, 5, 4,
|
|
||||||
],
|
],
|
||||||
}.transform(&Transform::new().translate(-0.5, -0.5, -0.5))
|
}
|
||||||
|
.transform(&Transform::new().translate(-0.5, -0.5, -0.5))
|
||||||
}
|
}
|
||||||
|
|||||||
498
src/rule.rs
498
src/rule.rs
@ -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 };
|
|
||||||
}
|
|
||||||
65
src/util.rs
65
src/util.rs
@ -1,6 +1,6 @@
|
|||||||
|
use crate::mesh::Mesh;
|
||||||
|
use crate::xform::Vertex;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use crate::mesh::{Mesh, MeshFunc, VertexUnion};
|
|
||||||
use crate::xform::{Vertex};
|
|
||||||
|
|
||||||
/// This is like `vec!`, but it can handle elements that are given
|
/// This is like `vec!`, but it can handle elements that are given
|
||||||
/// with `@var element` rather than `element`, e.g. like
|
/// with `@var element` rather than `element`, e.g. like
|
||||||
@ -42,7 +42,8 @@ impl<T> VecExt<T> for Vec<T> {
|
|||||||
/// (thus, the returned length will be `count*p.len()`).
|
/// (thus, the returned length will be `count*p.len()`).
|
||||||
pub fn subdivide_cycle(p: &Vec<Vertex>, count: usize) -> Vec<Vertex> {
|
pub fn subdivide_cycle(p: &Vec<Vertex>, count: usize) -> Vec<Vertex> {
|
||||||
let n = p.len();
|
let n = p.len();
|
||||||
(0..n).map(|i| {
|
(0..n)
|
||||||
|
.map(|i| {
|
||||||
// The inner loop is interpolating between v1 and v2:
|
// The inner loop is interpolating between v1 and v2:
|
||||||
let v1 = &p[i];
|
let v1 = &p[i];
|
||||||
let v2 = &p[(i + 1) % n];
|
let v2 = &p[(i + 1) % n];
|
||||||
@ -51,7 +52,9 @@ pub fn subdivide_cycle(p: &Vec<Vertex>, count: usize) -> Vec<Vertex> {
|
|||||||
let f = (j as f32) / (count as f32);
|
let f = (j as f32) / (count as f32);
|
||||||
v1 * (1.0 - f) + v2 * f
|
v1 * (1.0 - f) + v2 * f
|
||||||
})
|
})
|
||||||
}).flatten().collect()
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
// TODO: This can be generalized to an iterator or to IntoIterator
|
// TODO: This can be generalized to an iterator or to IntoIterator
|
||||||
// trait bound
|
// trait bound
|
||||||
@ -61,28 +64,27 @@ pub fn parallel_zigzag_faces(r1: Range<usize>, r2: Range<usize>) -> Vec<usize> {
|
|||||||
if (r2.end - r2.start) != count {
|
if (r2.end - r2.start) != count {
|
||||||
panic!("Ranges must have the same size");
|
panic!("Ranges must have the same size");
|
||||||
}
|
}
|
||||||
if (r2.end > r1.start && r2.end < r1.end) ||
|
if (r2.end > r1.start && r2.end < r1.end) || (r1.end > r2.start && r1.end < r2.end) {
|
||||||
(r1.end > r2.start && r1.end < r2.end) {
|
|
||||||
panic!("Ranges cannot overlap");
|
panic!("Ranges cannot overlap");
|
||||||
}
|
}
|
||||||
|
|
||||||
(0..count).map(|i0| {
|
(0..count)
|
||||||
|
.map(|i0| {
|
||||||
// i0 is an *offset* for the 'current' index.
|
// i0 is an *offset* for the 'current' index.
|
||||||
// i1 is for the 'next' index, wrapping back to 0.
|
// i1 is for the 'next' index, wrapping back to 0.
|
||||||
let i1 = (i0 + 1) % count;
|
let i1 = (i0 + 1) % count;
|
||||||
vec![
|
vec![
|
||||||
// Mind winding order!
|
// Mind winding order!
|
||||||
r1.start + i1, r2.start + i0, r1.start + i0,
|
r1.start + i1,
|
||||||
r2.start + i1, r2.start + i0, r1.start + i1,
|
r2.start + i0,
|
||||||
|
r1.start + i0,
|
||||||
|
r2.start + i1,
|
||||||
|
r2.start + i0,
|
||||||
|
r1.start + i1,
|
||||||
]
|
]
|
||||||
}).flatten().collect()
|
})
|
||||||
}
|
.flatten()
|
||||||
|
.collect()
|
||||||
pub fn parallel_zigzag(verts: Vec<VertexUnion>, main: Range<usize>, parent: Range<usize>) -> MeshFunc {
|
|
||||||
MeshFunc {
|
|
||||||
verts: verts,
|
|
||||||
faces: parallel_zigzag_faces(main, parent),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parallel_zigzag_mesh(verts: Vec<Vertex>, main: Range<usize>, parent: Range<usize>) -> Mesh {
|
pub fn parallel_zigzag_mesh(verts: Vec<Vertex>, main: Range<usize>, parent: Range<usize>) -> Mesh {
|
||||||
@ -93,7 +95,9 @@ pub fn parallel_zigzag_mesh(verts: Vec<Vertex>, main: Range<usize>, parent: Rang
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn parallel_zigzag2<T, U>(main: T, parent: U) -> Vec<usize>
|
pub fn parallel_zigzag2<T, U>(main: T, parent: U) -> Vec<usize>
|
||||||
where T: IntoIterator<Item=usize>, U: IntoIterator<Item=usize>
|
where
|
||||||
|
T: IntoIterator<Item = usize>,
|
||||||
|
U: IntoIterator<Item = usize>,
|
||||||
{
|
{
|
||||||
let m: Vec<usize> = main.into_iter().collect();
|
let m: Vec<usize> = main.into_iter().collect();
|
||||||
let p: Vec<usize> = parent.into_iter().collect();
|
let p: Vec<usize> = parent.into_iter().collect();
|
||||||
@ -102,16 +106,18 @@ where T: IntoIterator<Item=usize>, U: IntoIterator<Item=usize>
|
|||||||
panic!("Vectors must be the same size!")
|
panic!("Vectors must be the same size!")
|
||||||
}
|
}
|
||||||
|
|
||||||
(0..l).map(|i0| {
|
(0..l)
|
||||||
|
.map(|i0| {
|
||||||
// i0 is an *offset* for the 'current' index.
|
// i0 is an *offset* for the 'current' index.
|
||||||
// i1 is for the 'next' index, wrapping back to 0.
|
// i1 is for the 'next' index, wrapping back to 0.
|
||||||
let i1 = (i0 + 1) % l;
|
let i1 = (i0 + 1) % l;
|
||||||
vec![
|
vec![
|
||||||
// Mind winding order!
|
// Mind winding order!
|
||||||
m[i1], p[i0], m[i0],
|
m[i1], p[i0], m[i0], p[i1], p[i0], m[i1],
|
||||||
p[i1], p[i0], m[i1],
|
|
||||||
]
|
]
|
||||||
}).flatten().collect()
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn centroid(verts: &Vec<Vertex>) -> Vertex {
|
pub fn centroid(verts: &Vec<Vertex>) -> Vertex {
|
||||||
@ -125,17 +131,22 @@ pub fn centroid(verts: &Vec<Vertex>) -> Vertex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn connect_convex(range: Range<usize>, target: usize, as_parent: bool) -> Vec<usize> {
|
pub fn connect_convex(range: Range<usize>, target: usize, as_parent: bool) -> Vec<usize> {
|
||||||
|
|
||||||
let count = range.end - range.start;
|
let count = range.end - range.start;
|
||||||
if as_parent {
|
if as_parent {
|
||||||
(0..count).map(|i0| {
|
(0..count)
|
||||||
|
.map(|i0| {
|
||||||
let i1 = (i0 + 1) % count;
|
let i1 = (i0 + 1) % count;
|
||||||
vec![range.start + i1, range.start + i0, target]
|
vec![range.start + i1, range.start + i0, target]
|
||||||
}).flatten().collect()
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
(0..count).map(|i0| {
|
(0..count)
|
||||||
|
.map(|i0| {
|
||||||
let i1 = (i0 + 1) % count;
|
let i1 = (i0 + 1) % count;
|
||||||
vec![range.start + i0, range.start + i1, target]
|
vec![range.start + i0, range.start + i1, target]
|
||||||
}).flatten().collect()
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user