Skip to content

Commit

Permalink
Add vars and test to meshing
Browse files Browse the repository at this point in the history
  • Loading branch information
mkeeter committed Nov 17, 2024
1 parent 4491184 commit c98c044
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 34 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
- Remove fine-grained features from `fidget` crate, because we aren't actually
testing the power-set of feature combinations in CI (and some were breaking!).
The only remaining features are `rhai`, `jit` and `eval-tests`.
- Add new `ShapeVars<T>` type, representing a map from `VarIndex -> T`. This
type is used for high-level rendering and meshing of `Shape` objects that
include supplementary variables
- Add `Octree::build_with_vars` and `Image/VoxelRenderConfig::run_with_vars`
functions for shapes with supplementary variables
- Change `ShapeBulkEval::eval_v` to take **single** variables (i.e. `x`, `y`,
`z` vary but each variables are contant). Add `ShapeBulkEval::eval_vs` if
`x`, `y`, `z` and variables are _all_ changing through the slices.

# 0.3.3
- `Function` and evaluator types now produce multiple outputs
Expand Down
4 changes: 2 additions & 2 deletions fidget/src/core/shape/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,8 +502,8 @@ where
/// Bulk evaluation of many samples, without any variables
///
/// If the shape includes variables other than `X`, `Y`, `Z`,
/// [`eval_v`](Self::eval_v) should be used instead (and this function will
/// return an error).
/// [`eval_v`](Self::eval_v) or [`eval_vs`](Self::eval_vs) should be used
/// instead (and this function will return an error).
///
/// Before evaluation, the tape's transform matrix is applied (if present).
pub fn eval(
Expand Down
21 changes: 17 additions & 4 deletions fidget/src/mesh/mt/octree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
Octree,
},
render::RenderHints,
shape::ShapeVars,
};
use std::sync::{mpsc::TryRecvError, Arc};

Expand Down Expand Up @@ -123,6 +124,7 @@ pub struct OctreeWorker<F: Function + RenderHints> {
impl<F: Function + RenderHints> OctreeWorker<F> {
pub fn scheduler(
eval: Arc<EvalGroup<F>>,
vars: &ShapeVars<f32>,
settings: MultithreadedSettings,
) -> Octree {
let thread_count = settings.threads.get();
Expand Down Expand Up @@ -151,7 +153,9 @@ impl<F: Function + RenderHints> OctreeWorker<F> {
.collect::<Vec<_>>();

let root = CellIndex::default();
let r = workers[0].octree.eval_cell(&eval, root, settings.depth);
let r = workers[0]
.octree
.eval_cell(&eval, vars, root, settings.depth);
let c = match r {
CellResult::Done(cell) => Some(cell),
CellResult::Recurse(eval) => {
Expand All @@ -168,7 +172,9 @@ impl<F: Function + RenderHints> OctreeWorker<F> {
let out: Vec<Octree> = std::thread::scope(|s| {
let mut handles = vec![];
for w in workers {
handles.push(s.spawn(move || w.run(pool, settings.depth)));
handles.push(
s.spawn(move || w.run(vars, pool, settings.depth)),
);
}
handles.into_iter().map(|h| h.join().unwrap()).collect()
});
Expand All @@ -177,7 +183,12 @@ impl<F: Function + RenderHints> OctreeWorker<F> {
}

/// Runs a single worker to completion as part of a worker group
pub fn run(mut self, threads: &ThreadPool, max_depth: u8) -> Octree {
pub fn run(
mut self,
vars: &ShapeVars<f32>,
threads: &ThreadPool,
max_depth: u8,
) -> Octree {
let mut ctx = threads.start(self.thread_index);
loop {
// First, check to see if anyone has finished a task and sent us
Expand Down Expand Up @@ -212,7 +223,9 @@ impl<F: Function + RenderHints> OctreeWorker<F> {
for i in Corner::iter() {
let sub_cell = task.target_cell.child(index, i);

match self.octree.eval_cell(&task.eval, sub_cell, max_depth)
match self
.octree
.eval_cell(&task.eval, vars, sub_cell, max_depth)
{
// If this child is finished, then record it locally.
// If it's a branching cell, then we'll let a caller
Expand Down
106 changes: 92 additions & 14 deletions fidget/src/mesh/octree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use super::{
use crate::{
eval::{BulkEvaluator, Function, TracingEvaluator},
render::{RenderHints, ThreadCount},
shape::{Shape, ShapeBulkEval, ShapeTape, ShapeTracingEval},
shape::{Shape, ShapeBulkEval, ShapeTape, ShapeTracingEval, ShapeVars},
types::Grad,
};
use std::{num::NonZeroUsize, sync::Arc, sync::OnceLock};
Expand Down Expand Up @@ -96,17 +96,18 @@ impl Octree {
/// Builds an octree to the given depth
///
/// The shape is evaluated on the region specified by `settings.bounds`.
pub fn build<F: Function + RenderHints + Clone>(
pub fn build_with_vars<F: Function + RenderHints + Clone>(
shape: &Shape<F>,
vars: &ShapeVars<f32>,
settings: Settings,
) -> Self {
// Transform the shape given our world-to-model matrix
let t = settings.view.world_to_model();
if t == nalgebra::Matrix4::identity() {
Self::build_inner(shape, settings)
Self::build_inner(shape, vars, settings)
} else {
let shape = shape.clone().apply_transform(t);
let mut out = Self::build_inner(&shape, settings);
let mut out = Self::build_inner(&shape, vars, settings);

// Apply the transform from [-1, +1] back to model space
for v in &mut out.verts {
Expand All @@ -118,22 +119,34 @@ impl Octree {
}
}

/// Builds an octree to the given depth
///
/// The shape is evaluated on the region specified by `settings.bounds`.
pub fn build<F: Function + RenderHints + Clone>(
shape: &Shape<F>,
settings: Settings,
) -> Self {
Self::build_with_vars(shape, &Default::default(), settings)
}

fn build_inner<F: Function + RenderHints + Clone>(
shape: &Shape<F>,
vars: &ShapeVars<f32>,
settings: Settings,
) -> Self {
let eval = Arc::new(EvalGroup::new(shape.clone()));

match settings.threads {
ThreadCount::One => {
let mut out = OctreeBuilder::new();
out.recurse(&eval, CellIndex::default(), settings.depth);
out.recurse(&eval, vars, CellIndex::default(), settings.depth);
out.into()
}

#[cfg(not(target_arch = "wasm32"))]
ThreadCount::Many(threads) => OctreeWorker::scheduler(
eval.clone(),
vars,
MultithreadedSettings {
depth: settings.depth,
threads,
Expand Down Expand Up @@ -353,16 +366,18 @@ impl<F: Function + RenderHints> OctreeBuilder<F> {
pub(crate) fn eval_cell(
&mut self,
eval: &Arc<EvalGroup<F>>,
vars: &ShapeVars<f32>,
cell: CellIndex,
max_depth: u8,
) -> CellResult<F> {
let (i, r) = self
.eval_interval
.eval(
.eval_v(
eval.interval_tape(&mut self.tape_storage),
cell.bounds.x,
cell.bounds.y,
cell.bounds.z,
vars,
)
.unwrap();
if i.upper() < 0.0 {
Expand All @@ -382,7 +397,7 @@ impl<F: Function + RenderHints> OctreeBuilder<F> {
};
if cell.depth == max_depth as usize {
let eval = sub_tape.unwrap_or_else(|| eval.clone());
let out = CellResult::Done(self.leaf(&eval, cell));
let out = CellResult::Done(self.leaf(&eval, vars, cell));
if let Ok(t) = Arc::try_unwrap(eval) {
self.reclaim(t);
}
Expand Down Expand Up @@ -432,10 +447,11 @@ impl<F: Function + RenderHints> OctreeBuilder<F> {
fn recurse(
&mut self,
eval: &Arc<EvalGroup<F>>,
vars: &ShapeVars<f32>,
cell: CellIndex,
max_depth: u8,
) {
match self.eval_cell(eval, cell, max_depth) {
match self.eval_cell(eval, vars, cell, max_depth) {
CellResult::Done(c) => self.o[cell] = c.into(),
CellResult::Recurse(sub_eval) => {
let index = self.o.cells.len();
Expand All @@ -444,7 +460,7 @@ impl<F: Function + RenderHints> OctreeBuilder<F> {
}
for i in Corner::iter() {
let cell = cell.child(index, i);
self.recurse(&sub_eval, cell, max_depth);
self.recurse(&sub_eval, vars, cell, max_depth);
}

if let Ok(t) = Arc::try_unwrap(sub_eval) {
Expand Down Expand Up @@ -473,7 +489,12 @@ impl<F: Function + RenderHints> OctreeBuilder<F> {
/// Writes the leaf vertex to `self.o.verts`, hermite data to
/// `self.hermite`, and the leaf data to `self.leafs`. Does **not** write
/// anything to `self.o.cells`; the cell is returned instead.
fn leaf(&mut self, eval: &EvalGroup<F>, cell: CellIndex) -> Cell {
fn leaf(
&mut self,
eval: &EvalGroup<F>,
vars: &ShapeVars<f32>,
cell: CellIndex,
) -> Cell {
let mut xs = [0.0; 8];
let mut ys = [0.0; 8];
let mut zs = [0.0; 8];
Expand All @@ -486,7 +507,13 @@ impl<F: Function + RenderHints> OctreeBuilder<F> {

let out = self
.eval_float_slice
.eval(eval.float_slice_tape(&mut self.tape_storage), &xs, &ys, &zs)
.eval_v(
eval.float_slice_tape(&mut self.tape_storage),
&xs,
&ys,
&zs,
vars,
)
.unwrap();
debug_assert_eq!(out.len(), 8);

Expand Down Expand Up @@ -587,7 +614,13 @@ impl<F: Function + RenderHints> OctreeBuilder<F> {
// Do the actual evaluation
let out = self
.eval_float_slice
.eval(eval.float_slice_tape(&mut self.tape_storage), xs, ys, zs)
.eval_v(
eval.float_slice_tape(&mut self.tape_storage),
xs,
ys,
zs,
vars,
)
.unwrap();

// Update start and end positions based on evaluation
Expand Down Expand Up @@ -653,7 +686,13 @@ impl<F: Function + RenderHints> OctreeBuilder<F> {
// TODO: special case for cells with multiple gradients ("features")
let grads = self
.eval_grad_slice
.eval(eval.grad_slice_tape(&mut self.tape_storage), xs, ys, zs)
.eval_v(
eval.grad_slice_tape(&mut self.tape_storage),
xs,
ys,
zs,
vars,
)
.unwrap();

let mut verts: arrayvec::ArrayVec<_, 4> = arrayvec::ArrayVec::new();
Expand Down Expand Up @@ -1177,6 +1216,7 @@ mod test {
mesh::types::{Edge, X, Y, Z},
render::{ThreadCount, View3},
shape::EzShape,
var::Var,
vm::{VmFunction, VmShape},
};
use nalgebra::Vector3;
Expand Down Expand Up @@ -1545,7 +1585,12 @@ mod test {
let shape = VmShape::from(shape);
let eval = Arc::new(EvalGroup::new(shape));
let mut out = OctreeBuilder::new();
out.recurse(&eval, CellIndex::default(), settings.depth);
out.recurse(
&eval,
&Default::default(),
CellIndex::default(),
settings.depth,
);
out
}

Expand Down Expand Up @@ -1723,4 +1768,37 @@ mod test {
assert!(n > 0.2 && n < 0.3, "invalid vertex at {v:?}: {n}");
}
}

#[test]
fn test_mesh_vars() {
let (x, y, z) = Tree::axes();
let v = Var::new();
let c = Tree::from(v);
let sphere = (x.square() + y.square() + z.square()).sqrt() - c;
let shape = VmShape::from(sphere);

for threads in
[ThreadCount::One, ThreadCount::Many(4.try_into().unwrap())]
{
let settings = Settings {
depth: 4,
threads,
view: View3::default(),
};

for r in [0.5, 0.75] {
let mut vars = ShapeVars::new();
vars.insert(v.index().unwrap(), r);
let octree = Octree::build_with_vars(&shape, &vars, settings)
.walk_dual(settings);
for v in octree.vertices.iter() {
let n = v.norm();
assert!(
n > r - 0.05 && n < r + 0.05,
"invalid vertex at {v:?}: {n} != {r}"
);
}
}
}
}
}
20 changes: 6 additions & 14 deletions fidget/src/render/render3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,8 @@ pub fn render<F: Function>(
mod test {
use super::*;
use crate::{
eval::MathFunction, render::VoxelSize, var::Var, vm::VmShape, Context,
context::Tree, eval::MathFunction, render::VoxelSize, var::Var,
vm::VmShape, Context,
};

/// Make sure we don't crash if there's only a single tile
Expand All @@ -473,20 +474,11 @@ mod test {
}

fn sphere_var<F: Function + MathFunction>() {
let mut ctx = Context::new();
let x = ctx.x();
let y = ctx.y();
let z = ctx.z();
let x2 = ctx.square(x).unwrap();
let y2 = ctx.square(y).unwrap();
let z2 = ctx.square(z).unwrap();
let x2y2 = ctx.add(x2, y2).unwrap();
let r2 = ctx.add(x2y2, z2).unwrap();
let r = ctx.sqrt(r2).unwrap();
let (x, y, z) = Tree::axes();
let v = Var::new();
let c = ctx.var(v);
let root = ctx.sub(r, c).unwrap();
let shape = Shape::<F>::new(&ctx, root).unwrap();
let c = Tree::from(v);
let sphere = (x.square() + y.square() + z.square()).sqrt() - c;
let shape = Shape::<F>::from(sphere);

let size = 32;
let cfg = VoxelRenderConfig {
Expand Down

0 comments on commit c98c044

Please sign in to comment.