124 lines
4.4 KiB
Rust
124 lines
4.4 KiB
Rust
use crate::openmesh::{OpenMesh, Mat4};
|
|
use crate::prim;
|
|
|
|
/// 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 enum Rule {
|
|
/// Produce some geometry, and possibly recurse further.
|
|
Recurse(fn () -> RuleStep),
|
|
/// Produce nothing and recurse no further.
|
|
EmptyRule,
|
|
}
|
|
// TODO: Rename rules?
|
|
// 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?
|
|
|
|
/// `RuleStep` 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 RuleStep {
|
|
/// The geometry generated at just this iteration
|
|
pub geom: OpenMesh,
|
|
|
|
/// 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.
|
|
pub final_geom: OpenMesh,
|
|
|
|
/// 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>,
|
|
}
|
|
|
|
/// `Child` evaluations, pairing another `Rule` with the
|
|
/// transformations and parent vertex mappings that should be applied
|
|
/// to it.
|
|
pub struct Child {
|
|
|
|
/// Rule to evaluate to produce geometry
|
|
pub rule: Rule,
|
|
|
|
/// 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: Mat4,
|
|
|
|
/// The mapping to apply to turn a Tag::Parent vertex reference
|
|
/// into a vertex index of the parent mesh. That is, if `rule`
|
|
/// produces an `OpenMesh` with a face of `Tag::Parent(n)`, this
|
|
/// will correspond to index `vmap[n]` in the parent mesh.
|
|
pub vmap: Vec<usize>,
|
|
}
|
|
|
|
impl Rule {
|
|
|
|
// TODO: Do I want to make 'geom' shared somehow, maybe with Rc? I
|
|
// could end up having a lot of identical geometry that need not be
|
|
// duplicated until it is transformed into the global space.
|
|
//
|
|
// This might produce bigger gains if I rewrite to_mesh so that
|
|
// rather than repeatedly transforming meshes, it stacks
|
|
// transformations and then applies them all at once.
|
|
|
|
/// Convert this `Rule` to mesh data, recursively. `iters_left`
|
|
/// sets the maximum recursion depth. This returns (geometry,
|
|
/// number of rule evaluations).
|
|
pub fn to_mesh(&self, iters_left: u32) -> (OpenMesh, u32) {
|
|
|
|
let mut evals: u32 = 1;
|
|
|
|
if iters_left <= 0 {
|
|
match self {
|
|
Rule::Recurse(f) => {
|
|
let rs: RuleStep = f();
|
|
return (rs.final_geom, 1);
|
|
}
|
|
Rule::EmptyRule => {
|
|
return (prim::empty_mesh(), evals);
|
|
}
|
|
}
|
|
}
|
|
|
|
match self {
|
|
Rule::Recurse(f) => {
|
|
let rs: RuleStep = f();
|
|
// TODO: This logic is more or less right, but it
|
|
// could perhaps use some un-tupling or something.
|
|
|
|
let subgeom: Vec<(OpenMesh, &Vec<usize>)> = rs.children.iter().map(|sub| {
|
|
// Get sub-geometry (still un-transformed):
|
|
let (submesh, eval) = sub.rule.to_mesh(iters_left - 1);
|
|
// Tally up eval count:
|
|
evals += eval;
|
|
|
|
let m2 = submesh.transform(&sub.xf);
|
|
|
|
(m2, &sub.vmap)
|
|
}).collect();
|
|
|
|
// Connect geometry from this rule (not child rules):
|
|
return (rs.geom.connect(&subgeom), evals);
|
|
}
|
|
Rule::EmptyRule => {
|
|
return (prim::empty_mesh(), evals);
|
|
}
|
|
}
|
|
}
|
|
}
|