forked from 1b-agent/fieldcraft-upstream
mesh: the octree roots at a field's largest size and plans depth to its smallest (#435)
SizeField::range gives a field's smallest and largest size. The octree starts its root cells at the largest instead of the size at the part's centre, and refuses before building when the smallest needs more levels than max_depth. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
950d86c54e
commit
4d0790565c
3 files changed
+60
-2
No files matched your search
@@ -236,7 +236,8 @@ enum Attempt {
|
||||
}
|
||||
|
||||
/// Mesh the solid bounded by `surface` with hexahedra of about `field`'s size (the root cell
|
||||
/// size is the field at the centre of the part's box; finer cells follow the part's curvature,
|
||||
/// size is the field's largest size ([`SizeField::range`]), else the field at the centre of the
|
||||
/// part's box; cells are refined where the field is smaller than they are; finer cells follow the part's curvature,
|
||||
/// thickness and narrow faces). See the module docs.
|
||||
pub fn octree_hex_mesh(surface: &BoundarySurface, field: &dyn SizeField, options: &OctreeOptions) -> Result<OctreeHexMesh> {
|
||||
check_options(options)?;
|
||||
@@ -323,7 +324,26 @@ fn plan<'a>(g: &'a Geometry<'a>, field: &'a dyn SizeField, options: &'a OctreeOp
|
||||
let frame = choose_frame(g);
|
||||
let (lo, hi) = frame_box(g, &frame);
|
||||
let centre = from_frame(&frame, [0, 1, 2].map(|k| 0.5 * (lo[k] + hi[k])));
|
||||
let h = checked_size(field, centre)?;
|
||||
// The root cells at the largest size; the depth reaches the smallest (size controls).
|
||||
let h = match field.range() {
|
||||
Some((_, coarsest)) if coarsest > 0.0 && coarsest.is_finite() => coarsest,
|
||||
_ => checked_size(field, centre)?,
|
||||
};
|
||||
if let Some((finest, _)) = field.range()
|
||||
&& finest > 0.0
|
||||
&& finest < h
|
||||
{
|
||||
match levels_below(h, finest) {
|
||||
Some(d) if d <= options.max_depth => {}
|
||||
d => {
|
||||
let n = d.map_or_else(|| format!("more than {}", grid::MAX_LEVEL), |l| l.to_string());
|
||||
return Err(Error::Limit(format!(
|
||||
"a size of {finest:.4} below the root size {h:.4} needs {n} levels (each a third); `max_depth` is {}: raise it, or use a larger local size",
|
||||
options.max_depth
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
let needs = Needs::of(g, thickness, options);
|
||||
let deep = levels_below(h, needs.edge);
|
||||
let mut anchors: Vec<(f64, Option<u8>)> = vec![(h, deep)];
|
||||
|
||||
@@ -159,3 +159,26 @@ fn meshing_is_deterministic() {
|
||||
let b = octree_hex_mesh(&s, &UniformSize(2.0), &OctreeOptions::default()).unwrap();
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
/// A size box refines the grid inside it to the level its size needs (closure may carry that
|
||||
/// level further), and the root cells keep the global size; a size deeper than `max_depth` allows is refused before meshing.
|
||||
#[test]
|
||||
fn a_size_box_refines_the_grid_inside_it() {
|
||||
use crate::size::{LocalSize, Region, RegionSize};
|
||||
let s = box_surface([0.0; 3], [18.0, 18.0, 18.0]);
|
||||
let local = LocalSize { region: Region::Box { lo: [0.0; 3], hi: [6.0; 3] }, size: 2.0 };
|
||||
let field = RegionSize::new(6.0, 0.3, vec![local.clone()]).unwrap();
|
||||
let m = octree_hex_mesh(&s, &field, &OctreeOptions::default()).unwrap();
|
||||
let edge = |h: &[u32; 8]| {
|
||||
let (a, b) = (m.nodes[h[0] as usize], m.nodes[h[1] as usize]);
|
||||
((a[0] - b[0]).powi(2) + (a[1] - b[1]).powi(2) + (a[2] - b[2]).powi(2)).sqrt()
|
||||
};
|
||||
let centre = |h: &[u32; 8]| [0, 1, 2].map(|k| h.iter().map(|&v| m.nodes[v as usize][k]).sum::<f64>() / 8.0);
|
||||
let inside: Vec<f64> = m.hexes.iter().filter(|h| centre(h).iter().all(|&c| (1.0..5.0).contains(&c))).map(edge).collect();
|
||||
assert!(!inside.is_empty() && inside.iter().all(|&e| e < 2.0 * 1.2), "{inside:?}");
|
||||
assert_eq!((m.report.root_size, m.report.levels), (6.0, 1), "{:?}", m.report);
|
||||
let shallow = OctreeOptions { max_depth: 1, curvature_levels: 1, ..OctreeOptions::default() };
|
||||
let deep = RegionSize::new(6.0, 0.3, vec![LocalSize { size: 0.5, ..local }]).unwrap();
|
||||
let e = octree_hex_mesh(&s, &deep, &shallow).unwrap_err().to_string();
|
||||
assert!(e.contains("max_depth"), "{e}");
|
||||
}
|
||||
@@ -29,6 +29,13 @@ use crate::{Error, Result};
|
||||
/// treat anything else as invalid input.
|
||||
pub trait SizeField {
|
||||
fn size(&self, p: [f64; 3]) -> f64;
|
||||
|
||||
/// The smallest and largest size anywhere, when the field knows them (a mesher that builds
|
||||
/// from a root size, such as the octree, starts from the largest and plans its depth from
|
||||
/// the smallest).
|
||||
fn range(&self) -> Option<(f64, f64)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Fn([f64; 3]) -> f64> SizeField for F {
|
||||
@@ -45,6 +52,10 @@ impl SizeField for UniformSize {
|
||||
fn size(&self, _: [f64; 3]) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<(f64, f64)> {
|
||||
Some((self.0, self.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// The largest division count of one curve.
|
||||
@@ -348,6 +359,10 @@ impl SizeField for RegionSize {
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<(f64, f64)> {
|
||||
Some((self.finest(), self.global))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in new issue
Block a user