Skip to content

Commit

Permalink
New type for render bounds (#55)
Browse files Browse the repository at this point in the history
This replaces #10 as a general strategy for "bounded rendering", using
the same `Bounds` type for both meshing and 2D / 3D rendering.
  • Loading branch information
mkeeter authored Mar 28, 2024
1 parent be7c1b5 commit 6d4b2d0
Show file tree
Hide file tree
Showing 12 changed files with 386 additions and 40 deletions.
15 changes: 9 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
with the difference that unordered results are returned as `NAN`)
- Fix a bug in the x86 JIT evaluator's implementation of interval `abs`
- Add generic `TransformedShape<S>`, representing a shape transformed by a 4x4
homogeneous matrix
- This replaces `RenderConfig::mat` as the way to handle rotation / scale /
translation / perspective transforms, e.g. for interactive visualization
(where you don't want to remap the underlying shape)
- It's a more general solution: for example, we can use the same type to
change bounds for meshing (by translating + scaling the underlying model).
homogeneous matrix. This replaces `RenderConfig::mat` as the flexible
strategy for rotation / scale / translation / perspective transforms, e.g. for
interactive visualization (where you don't want to remap the underlying shape)
- Introduce a new `Bounds` type, representing an X/Y/Z region of interest for
rendering or meshing. This overlaps somewhat with `TransformedShape`, but
it's ergonomic to specify render region instead of having to do the matrix
math every time.
- Replaced `RenderConfig::mat` with a new `bounds` member.
- Added a new `bounds` member to `mesh::Settings`, for octree construction
- Move `Interval` and `Grad` to `fidget::types` module, instead of
`fidget::eval::types`.
- Fix an edge case in meshing where nearly-planar surfaces could produce
Expand Down
3 changes: 3 additions & 0 deletions demo/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ fn run3d<S: fidget::eval::Shape>(
image_size: settings.size as usize,
tile_sizes: S::tile_sizes_3d().to_vec(),
threads: settings.threads,
..Default::default()
};
let shape = shape.apply_transform(mat.into());

Expand Down Expand Up @@ -206,6 +207,7 @@ fn run2d<S: fidget::eval::Shape>(
image_size: settings.size as usize,
tile_sizes: S::tile_sizes_2d().to_vec(),
threads: settings.threads,
..Default::default()
};
if sdf {
let mut image = vec![];
Expand Down Expand Up @@ -250,6 +252,7 @@ fn run_mesh<S: fidget::eval::Shape>(
threads: settings.threads,
min_depth: settings.depth,
max_depth: settings.max_depth.unwrap_or(settings.depth),
..Default::default()
};
let octree = fidget::mesh::Octree::build(&shape, settings);
mesh = octree.walk_dual(settings);
Expand Down
2 changes: 2 additions & 0 deletions fidget/benches/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub fn colonnade_octree_thread_sweep(c: &mut Criterion) {
min_depth: 6,
max_depth: 6,
threads,
..Default::default()
};
#[cfg(feature = "jit")]
group.bench_function(BenchmarkId::new("jit", threads), move |b| {
Expand All @@ -42,6 +43,7 @@ pub fn colonnade_mesh(c: &mut Criterion) {
min_depth: 8,
max_depth: 8,
threads: 8,
..Default::default()
};
let octree = &fidget::mesh::Octree::build(shape_vm, cfg);

Expand Down
4 changes: 4 additions & 0 deletions fidget/benches/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub fn prospero_size_sweep(c: &mut Criterion) {
image_size: size,
tile_sizes: fidget::vm::VmShape::tile_sizes_2d().to_vec(),
threads: 8,
..Default::default()
};
group.bench_function(BenchmarkId::new("vm", size), move |b| {
b.iter(|| {
Expand All @@ -38,6 +39,7 @@ pub fn prospero_size_sweep(c: &mut Criterion) {
image_size: size,
tile_sizes: fidget::jit::JitShape::tile_sizes_2d().to_vec(),
threads: 8,
..Default::default()
};
group.bench_function(BenchmarkId::new("jit", size), move |b| {
b.iter(|| {
Expand Down Expand Up @@ -67,6 +69,7 @@ pub fn prospero_thread_sweep(c: &mut Criterion) {
image_size: 1024,
tile_sizes: fidget::vm::VmShape::tile_sizes_2d().to_vec(),
threads,
..Default::default()
};
group.bench_function(BenchmarkId::new("vm", threads), move |b| {
b.iter(|| {
Expand All @@ -84,6 +87,7 @@ pub fn prospero_thread_sweep(c: &mut Criterion) {
image_size: 1024,
tile_sizes: fidget::jit::JitShape::tile_sizes_2d().to_vec(),
threads,
..Default::default()
};
group.bench_function(BenchmarkId::new("jit", threads), move |b| {
b.iter(|| {
Expand Down
1 change: 1 addition & 0 deletions fidget/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub use context::Context;

pub mod compiler;
pub mod eval;
pub mod shape;
pub mod types;
pub mod vm;

Expand Down
126 changes: 126 additions & 0 deletions fidget/src/core/shape/bounds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use nalgebra::{
allocator::Allocator, Const, DefaultAllocator, DimNameAdd, DimNameSub,
DimNameSum, OVector, Transform, U1,
};

/// A bounded region in space, typically used as a render region
///
/// Right now, all spatial operations take place in a cubical region, so we
/// specify bounds as a center point and region size.
#[derive(Copy, Clone, Debug)]
pub struct Bounds<const N: usize> {
/// Center of the bounds
pub center: OVector<f32, Const<N>>,

/// Size of the bounds in each direction
///
/// The full bounds are given by `[center - size, center + size]` on each
/// axis.
pub size: f32,
}

impl<const N: usize> Default for Bounds<N> {
/// By default, the bounds are the `[-1, +1]` region
fn default() -> Self {
let center = OVector::<f32, Const<N>>::zeros();
Self { center, size: 1.0 }
}
}

impl<const N: usize> Bounds<N>
where
Const<N>: DimNameAdd<U1>,
DefaultAllocator:
Allocator<f32, DimNameSum<Const<N>, U1>, DimNameSum<Const<N>, U1>>,
<Const<N> as DimNameAdd<Const<1>>>::Output: DimNameSub<U1>,
{
/// Returns a homogeneous transform matrix for these bounds
///
/// When this matrix is applied, the `[-1, +1]` region (used for all
/// rendering operations) will be remapped to the original bounds.
pub fn transform(&self) -> Transform<f32, nalgebra::TGeneral, N> {
let mut t = nalgebra::Translation::<f32, N>::identity();
t.vector = self.center / self.size;

let mut out = Transform::<f32, nalgebra::TGeneral, N>::default();
out.matrix_mut().append_scaling_mut(self.size);
out *= t;

out
}
}

#[cfg(test)]
mod test {
use super::*;
use nalgebra::{Point2, Vector2};

#[test]
fn bounds_default() {
let b = Bounds::default();
let t = b.transform();
assert_eq!(
t.transform_point(&Point2::new(-1.0, -1.0)),
Point2::new(-1.0, -1.0)
);
assert_eq!(
t.transform_point(&Point2::new(0.5, 0.0)),
Point2::new(0.5, 0.0)
);
}

#[test]
fn bounds_scale() {
let b = Bounds {
center: Vector2::zeros(),
size: 0.5,
};
let t = b.transform();
assert_eq!(
t.transform_point(&Point2::new(-1.0, -1.0)),
Point2::new(-0.5, -0.5)
);
assert_eq!(
t.transform_point(&Point2::new(1.0, 0.0)),
Point2::new(0.5, 0.0)
);
}

#[test]
fn bounds_translate() {
let b = Bounds {
center: Vector2::new(1.0, 2.0),
size: 1.0,
};
let t = b.transform();
assert_eq!(
t.transform_point(&Point2::new(-1.0, -1.0)),
Point2::new(0.0, 1.0)
);
assert_eq!(
t.transform_point(&Point2::new(1.0, 0.0)),
Point2::new(2.0, 2.0)
);
}

#[test]
fn bounds_translate_scale() {
let b = Bounds {
center: Vector2::new(0.5, 0.5),
size: 0.5,
};
let t = b.transform();
assert_eq!(
t.transform_point(&Point2::new(0.0, 0.0)),
Point2::new(0.5, 0.5)
);
assert_eq!(
t.transform_point(&Point2::new(-1.0, -1.0)),
Point2::new(0.0, 0.0)
);
assert_eq!(
t.transform_point(&Point2::new(1.0, 1.0)),
Point2::new(1.0, 1.0)
);
}
}
3 changes: 3 additions & 0 deletions fidget/src/core/shape/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! Shape-specific data types
mod bounds;
pub use bounds::Bounds;
23 changes: 22 additions & 1 deletion fidget/src/mesh/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@
//!
//! let (node, ctx) = fidget::rhai::eval("sphere(0, 0, 0, 0.6).call(x, y, z)")?;
//! let shape = VmShape::new(&ctx, node)?;
//! let settings = Settings { threads: 8, min_depth: 4, max_depth: 4 };
//! let settings = Settings {
//! threads: 8,
//! min_depth: 4,
//! max_depth: 4,
//! ..Default::default()
//! };
//! let o = Octree::build(&shape, settings);
//! let mesh = o.walk_dual(settings);
//!
Expand All @@ -37,6 +42,8 @@
//! # Ok::<(), fidget::Error>(())
//! ```
use crate::shape::Bounds;

mod builder;
mod cell;
mod dc;
Expand Down Expand Up @@ -92,4 +99,18 @@ pub struct Settings {
///
/// This is **much slower**.
pub max_depth: u8,

/// Bounds for meshing
pub bounds: Bounds<3>,
}

impl Default for Settings {
fn default() -> Self {
Self {
threads: 4,
min_depth: 3,
max_depth: 3,
bounds: Default::default(),
}
}
}
57 changes: 56 additions & 1 deletion fidget/src/mesh/octree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,27 @@ impl Octree {

/// Builds an octree to the given depth
///
/// The shape is evaluated on the region `[-1, 1]` on all axes
/// The shape is evaluated on the region specified by `settings.bounds`.
pub fn build<S: Shape + Clone>(shape: &S, settings: Settings) -> Self {
// Transform the shape given our bounds
let t = settings.bounds.transform();
if t == nalgebra::Transform::identity() {
Self::build_inner(shape, settings)
} else {
let shape = shape.clone().apply_transform(t.into());
let mut out = Self::build_inner(&shape, settings);

// Apply the transform from [-1, +1] back to model space
for v in &mut out.verts {
let p: nalgebra::Point3<f32> = v.pos.into();
let q = t.transform_point(&p);
v.pos = q.coords;
}
out
}
}

fn build_inner<S: Shape + Clone>(shape: &S, settings: Settings) -> Self {
let eval = Arc::new(EvalGroup::new(shape.clone()));

let mut octree = if settings.threads == 0 {
Expand Down Expand Up @@ -1347,19 +1366,29 @@ mod test {
context::bound::{self, BoundContext, BoundNode},
eval::{EzShape, MathShape},
mesh::types::{Edge, X, Y, Z},
shape::Bounds,
vm::VmShape,
};
use nalgebra::Vector3;
use std::collections::BTreeMap;

const DEPTH0_SINGLE_THREAD: Settings = Settings {
min_depth: 0,
max_depth: 0,
threads: 0,
bounds: Bounds {
center: Vector3::new(0.0, 0.0, 0.0),
size: 1.0,
},
};
const DEPTH1_SINGLE_THREAD: Settings = Settings {
min_depth: 1,
max_depth: 1,
threads: 0,
bounds: Bounds {
center: Vector3::new(0.0, 0.0, 0.0),
size: 1.0,
},
};

fn sphere(
Expand Down Expand Up @@ -1536,6 +1565,7 @@ mod test {
min_depth: 5,
max_depth: 5,
threads,
..Default::default()
};
let octree = Octree::build(&shape, settings);
let sphere_mesh = octree.walk_dual(settings);
Expand Down Expand Up @@ -1699,6 +1729,7 @@ mod test {
min_depth: 2,
max_depth: 2,
threads,
..Default::default()
};
let octree = Octree::build(&shape, settings);

Expand Down Expand Up @@ -1763,6 +1794,7 @@ mod test {
min_depth: 1,
max_depth: 1,
threads,
..Default::default()
};
let octree = Octree::build(&tape, settings);
assert_eq!(
Expand All @@ -1784,6 +1816,7 @@ mod test {
min_depth: 5,
max_depth: 5,
threads,
..Default::default()
};
let octree = Octree::build(&tape, settings);
let mesh = octree.walk_dual(settings);
Expand Down Expand Up @@ -1881,6 +1914,7 @@ mod test {
min_depth: 4,
max_depth: 4,
threads: 0,
..Default::default()
};

let octree = Octree::build(&shape, settings).walk_dual(settings);
Expand All @@ -1889,4 +1923,25 @@ mod test {
assert!(n > 0.7 && n < 0.8, "invalid vertex at {v:?}: {n}");
}
}

#[test]
fn test_octree_bounds() {
let ctx = BoundContext::new();
let shape = sphere(&ctx, [1.0; 3], 0.25);

let shape: VmShape = shape.convert();
let center = Vector3::new(1.0, 1.0, 1.0);
let settings = Settings {
min_depth: 4,
max_depth: 4,
threads: 0,
bounds: Bounds { size: 0.5, center },
};

let octree = Octree::build(&shape, settings).walk_dual(settings);
for v in octree.vertices.iter() {
let n = (v - center).norm();
assert!(n > 0.2 && n < 0.3, "invalid vertex at {v:?}: {n}");
}
}
}
Loading

0 comments on commit 6d4b2d0

Please sign in to comment.