Skip to content

Commit bc3a014

Browse files
committed
Add an id to each octree node that is also exported as cell data
1 parent 65fd81b commit bc3a014

6 files changed

Lines changed: 130 additions & 45 deletions

File tree

splashsurf/src/reconstruction.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use log::info;
77
use rayon::prelude::*;
88
use splashsurf_lib::mesh::PointCloud3d;
99
use splashsurf_lib::profile;
10-
use splashsurf_lib::vtkio::model::UnstructuredGridPiece;
1110
use splashsurf_lib::{density_map, Index, Real};
1211
use std::convert::TryFrom;
1312
use std::path::PathBuf;
@@ -564,7 +563,11 @@ pub(crate) fn reconstruction_pipeline_generic<I: Index, R: Real>(
564563
if let Some(output_octree_file) = &paths.output_octree_file {
565564
info!("Writing octree to \"{}\"...", output_octree_file.display());
566565
io::vtk_format::write_vtk(
567-
UnstructuredGridPiece::from(&reconstruction.octree().unwrap().hexmesh(grid, false)),
566+
reconstruction
567+
.octree()
568+
.unwrap()
569+
.hexmesh(grid, true)
570+
.to_dataset(),
568571
output_octree_file,
569572
"mesh",
570573
)

splashsurf_lib/src/density_map.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
2121
use crate::aabb::AxisAlignedBoundingBox3d;
2222
use crate::kernel::DiscreteSquaredDistanceCubicKernel;
23-
use crate::mesh::{HexMesh3d, MeshWithPointData};
23+
use crate::mesh::{HexMesh3d, MeshWithData};
2424
use crate::uniform_grid::{OwningSubdomainGrid, Subdomain, UniformGrid};
2525
use crate::utils::{ChunkSize, ParallelPolicy};
2626
use crate::{new_map, profile, HashState, Index, MapType, ParallelMapType, Real};
@@ -781,7 +781,7 @@ pub fn sparse_density_map_to_hex_mesh<I: Index, R: Real>(
781781
density_map: &DensityMap<I, R>,
782782
grid: &UniformGrid<I, R>,
783783
default_value: R,
784-
) -> MeshWithPointData<HexMesh3d<R>, R> {
784+
) -> MeshWithData<HexMesh3d<R>, R> {
785785
profile!("sparse_density_map_to_hex_mesh");
786786

787787
let mut mesh = HexMesh3d {
@@ -859,5 +859,5 @@ pub fn sparse_density_map_to_hex_mesh<I: Index, R: Real>(
859859
]);
860860
}
861861

862-
MeshWithPointData { mesh, data: values }
862+
MeshWithData::with_point_data(mesh, values)
863863
}

splashsurf_lib/src/mesh.rs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,28 +230,71 @@ pub struct PointCloud3d<R: Real> {
230230

231231
/// A mesh with attached vertex or point data
232232
#[derive(Clone, Debug, Default)]
233-
pub struct MeshWithPointData<MeshT, DataT> {
233+
pub struct MeshWithData<MeshT, PointDataT, CellDataT = ()> {
234234
/// The mesh geometry itself
235235
pub mesh: MeshT,
236236
/// Data attached to each vertex or point of the mesh
237-
pub data: Vec<DataT>,
237+
pub point_data: Vec<PointDataT>,
238+
/// Data attached to each cell of the mesh
239+
pub cell_data: Vec<CellDataT>,
240+
}
241+
242+
impl<MeshT, PointDataT, CellDataT> MeshWithData<MeshT, PointDataT, CellDataT> {
243+
/// Creates a new mesh the given point data
244+
pub fn with_point_data<PointData: Into<Vec<PointDataT>>>(
245+
mesh: MeshT,
246+
point_data: PointData,
247+
) -> Self {
248+
Self {
249+
mesh,
250+
point_data: point_data.into(),
251+
cell_data: vec![],
252+
}
253+
}
254+
255+
/// Creates a new mesh the given cell data
256+
pub fn with_cell_data<CellData: Into<Vec<CellDataT>>>(
257+
mesh: MeshT,
258+
cell_data: CellData,
259+
) -> Self {
260+
Self {
261+
mesh,
262+
point_data: vec![],
263+
cell_data: cell_data.into(),
264+
}
265+
}
238266
}
239267

240268
#[cfg(feature = "vtk_extras")]
241269
use vtkio::model::{Attribute, UnstructuredGridPiece};
242270

243271
#[cfg(feature = "vtk_extras")]
244-
impl<'a, MeshT: 'a, DataT> MeshWithPointData<MeshT, DataT>
272+
impl<'a, MeshT: 'a, PointDataT> MeshWithData<MeshT, PointDataT, ()>
245273
where
246274
&'a MeshT: Into<UnstructuredGridPiece>,
247-
DataT: Real,
275+
PointDataT: Real,
248276
{
249277
pub fn to_dataset(&'a self) -> UnstructuredGridPiece {
250278
let mut grid_piece: UnstructuredGridPiece = (&self.mesh).into();
251279
grid_piece
252280
.data
253281
.point
254-
.push(Attribute::scalars("density", 1).with_data(self.data.clone()));
282+
.push(Attribute::scalars("density", 1).with_data(self.point_data.clone()));
283+
grid_piece
284+
}
285+
}
286+
287+
#[cfg(feature = "vtk_extras")]
288+
impl<'a, MeshT: 'a> MeshWithData<MeshT, (), u64>
289+
where
290+
&'a MeshT: Into<UnstructuredGridPiece>,
291+
{
292+
pub fn to_dataset(&'a self) -> UnstructuredGridPiece {
293+
let mut grid_piece: UnstructuredGridPiece = (&self.mesh).into();
294+
grid_piece
295+
.data
296+
.cell
297+
.push(Attribute::scalars("node_id", 1).with_data(self.cell_data.clone()));
255298
grid_piece
256299
}
257300
}

splashsurf_lib/src/octree.rs

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Octree for spatially partitioning particle sets
22
33
use crate::generic_tree::*;
4-
use crate::mesh::{HexMesh3d, TriMesh3d};
4+
use crate::marching_cubes::SurfacePatch;
5+
use crate::mesh::{HexMesh3d, MeshWithData, TriMesh3d};
6+
use crate::topology::{Axis, Direction};
57
use crate::uniform_grid::{PointIndex, UniformGrid};
68
use crate::utils::{ChunkSize, ParallelPolicy};
79
use crate::{
@@ -14,7 +16,9 @@ use nalgebra::Vector3;
1416
use octant_helper::{HalfspaceFlags, Octant, OctantAxisDirections};
1517
use rayon::prelude::*;
1618
use smallvec::SmallVec;
19+
use split_criterion::{default_split_criterion, LeafSplitCriterion};
1720
use std::cell::RefCell;
21+
use std::sync::atomic::{AtomicUsize, Ordering};
1822
use thread_local::ThreadLocal;
1923

2024
// TODO: Make margin an Option
@@ -31,12 +35,17 @@ pub enum SubdivisionCriterion {
3135
/// Data structure for octree based spatial subdivision of particles sets, for tree iteration/visitation use the [`root`](Self::root) [`OctreeNode`]
3236
#[derive(Clone, Debug)]
3337
pub struct Octree<I: Index, R: Real> {
38+
/// Root node of the tree
3439
root: OctreeNode<I, R>,
40+
/// Counter for assigning ids to subdivided nodes
41+
next_id: usize,
3542
}
3643

3744
/// Represents a node in the octree hierarchy and stores child nodes, implements tree iteration/visitation from the [`generic_tree`](crate::generic_tree) module
3845
#[derive(Clone, Debug)]
3946
pub struct OctreeNode<I: Index, R: Real> {
47+
/// Id of the node used to identify it for debugging
48+
id: usize,
4049
/// All child nodes of this octree node
4150
children: ArrayVec<[Box<Self>; 8]>,
4251
/// Lower corner point of the octree node on the background grid
@@ -108,15 +117,12 @@ impl<I: Index, R: Real> SurfacePatchWrapper<I, R> {
108117

109118
type OctreeNodeParticleStorage = SmallVec<[usize; 6]>;
110119

111-
use crate::marching_cubes::SurfacePatch;
112-
use crate::topology::{Axis, Direction};
113-
use split_criterion::{default_split_criterion, LeafSplitCriterion};
114-
115120
impl<I: Index, R: Real> Octree<I, R> {
116121
/// Creates a new octree with a single leaf node containing all vertices
117122
pub fn new(grid: &UniformGrid<I, R>, n_particles: usize) -> Self {
118123
Self {
119124
root: OctreeNode::new_root(grid, n_particles),
125+
next_id: 0,
120126
}
121127
}
122128

@@ -135,6 +141,8 @@ impl<I: Index, R: Real> Octree<I, R> {
135141
) -> Self {
136142
let mut tree = Octree::new(&grid, particle_positions.len());
137143

144+
println!("Margin: {}", margin);
145+
138146
if enable_multi_threading {
139147
tree.subdivide_recursively_margin_par(
140148
grid,
@@ -156,11 +164,6 @@ impl<I: Index, R: Real> Octree<I, R> {
156164
tree
157165
}
158166

159-
/// Creates a new octree with the given node as root
160-
pub fn with_root(root: OctreeNode<I, R>) -> Self {
161-
Self { root }
162-
}
163-
164167
/// Returns a reference to the root node of the octree
165168
pub fn root(&self) -> &OctreeNode<I, R> {
166169
&self.root
@@ -188,15 +191,17 @@ impl<I: Index, R: Real> Octree<I, R> {
188191
enable_stitching,
189192
);
190193

194+
let next_id = AtomicUsize::new(0);
191195
self.root.visit_mut_bfs(|node| {
192196
// Stop recursion if split criterion is not fulfilled
193197
if !split_criterion.split_leaf(node) {
194198
return;
195199
}
196200

197201
// Perform one octree split on the node
198-
node.subdivide_with_margin(grid, particle_positions, margin);
202+
node.subdivide_with_margin(grid, particle_positions, margin, &next_id);
199203
});
204+
self.next_id = next_id.into_inner();
200205
}
201206

202207
/// Subdivide the octree recursively and in parallel using the given splitting criterion and a margin to add ghost particles
@@ -217,39 +222,55 @@ impl<I: Index, R: Real> Octree<I, R> {
217222
);
218223
let parallel_policy = ParallelPolicy::default();
219224

220-
let visitor = move |node: &mut OctreeNode<I, R>| {
221-
// Stop recursion if split criterion is not fulfilled
222-
if !split_criterion.split_leaf(node) {
223-
return;
224-
}
225+
let next_id = AtomicUsize::new(0);
226+
let visitor = {
227+
let next_id = &next_id;
228+
move |node: &mut OctreeNode<I, R>| {
229+
// Stop recursion if split criterion is not fulfilled
230+
if !split_criterion.split_leaf(node) {
231+
return;
232+
}
225233

226-
// Perform one octree split on the leaf
227-
if node
228-
.data
229-
.particle_set()
230-
.expect("Node is not a leaf")
231-
.particles
232-
.len()
233-
< parallel_policy.min_task_size
234-
{
235-
node.subdivide_with_margin(grid, particle_positions, margin);
236-
} else {
237-
node.subdivide_with_margin_par(grid, particle_positions, margin, &parallel_policy);
234+
// Perform one octree split on the leaf
235+
if node
236+
.data
237+
.particle_set()
238+
.expect("Node is not a leaf")
239+
.particles
240+
.len()
241+
< parallel_policy.min_task_size
242+
{
243+
node.subdivide_with_margin(grid, particle_positions, margin, &next_id);
244+
} else {
245+
node.subdivide_with_margin_par(
246+
grid,
247+
particle_positions,
248+
margin,
249+
&parallel_policy,
250+
&next_id,
251+
);
252+
}
238253
}
239254
};
240255

241256
self.root.par_visit_mut_bfs(visitor);
257+
self.next_id = next_id.into_inner();
242258
}
243259

244260
/// Constructs a hex mesh visualizing the cells of the octree, may contain hanging and duplicate vertices as cells are not connected
245-
pub fn hexmesh(&self, grid: &UniformGrid<I, R>, only_non_empty: bool) -> HexMesh3d<R> {
261+
pub fn hexmesh(
262+
&self,
263+
grid: &UniformGrid<I, R>,
264+
only_non_empty: bool,
265+
) -> MeshWithData<HexMesh3d<R>, (), u64> {
246266
profile!("convert octree into hexmesh");
247267

248268
let mut mesh = HexMesh3d {
249269
vertices: Vec::new(),
250270
cells: Vec::new(),
251271
};
252272

273+
let mut ids = Vec::new();
253274
self.root.dfs_iter().for_each(|node| {
254275
if node.children().is_empty() {
255276
if only_non_empty
@@ -290,16 +311,18 @@ impl<I: Index, R: Real> Octree<I, R> {
290311

291312
mesh.vertices.extend(vertices);
292313
mesh.cells.push(cell);
314+
ids.push(node.id as u64);
293315
}
294316
});
295317

296-
mesh
318+
assert_eq!(mesh.cells.len(), ids.len());
319+
MeshWithData::with_cell_data(mesh, ids)
297320
}
298321
}
299322

300323
impl<I: Index, R: Real> OctreeNode<I, R> {
301-
pub fn new(min_corner: PointIndex<I>, max_corner: PointIndex<I>) -> Self {
302-
Self::with_data(min_corner, max_corner, NodeData::None)
324+
pub fn new(id: usize, min_corner: PointIndex<I>, max_corner: PointIndex<I>) -> Self {
325+
Self::with_data(id, min_corner, max_corner, NodeData::None)
303326
}
304327

305328
fn new_root(grid: &UniformGrid<I, R>, n_particles: usize) -> Self {
@@ -312,6 +335,7 @@ impl<I: Index, R: Real> OctreeNode<I, R> {
312335
];
313336

314337
Self::with_data(
338+
0,
315339
grid.get_point(min_point)
316340
.expect("Cannot get lower corner of grid"),
317341
grid.get_point(max_point)
@@ -321,18 +345,25 @@ impl<I: Index, R: Real> OctreeNode<I, R> {
321345
}
322346

323347
fn with_data(
348+
id: usize,
324349
min_corner: PointIndex<I>,
325350
max_corner: PointIndex<I>,
326351
data: NodeData<I, R>,
327352
) -> Self {
328353
Self {
354+
id,
329355
children: Default::default(),
330356
min_corner,
331357
max_corner,
332358
data,
333359
}
334360
}
335361

362+
/// Returns the id of the node
363+
pub fn id(&self) -> usize {
364+
self.id
365+
}
366+
336367
/// Returns a reference to the data stored in the node
337368
pub fn data(&self) -> &NodeData<I, R> {
338369
&self.data
@@ -385,6 +416,7 @@ impl<I: Index, R: Real> OctreeNode<I, R> {
385416
grid: &UniformGrid<I, R>,
386417
particle_positions: &[Vector3<R>],
387418
margin: R,
419+
next_id: &AtomicUsize,
388420
) {
389421
// Convert node body from Leaf to Children
390422
if let NodeData::ParticleSet(particle_set) = self.data.take() {
@@ -460,6 +492,7 @@ impl<I: Index, R: Real> OctreeNode<I, R> {
460492
assert_eq!(octant_particles.len(), octant_particle_count);
461493

462494
let child = Box::new(OctreeNode::with_data(
495+
next_id.fetch_add(1, Ordering::SeqCst),
463496
min_corner,
464497
max_corner,
465498
NodeData::new_particle_set(
@@ -485,6 +518,7 @@ impl<I: Index, R: Real> OctreeNode<I, R> {
485518
particle_positions: &[Vector3<R>],
486519
margin: R,
487520
parallel_policy: &ParallelPolicy,
521+
next_id: &AtomicUsize,
488522
) {
489523
// Convert node body from Leaf to Children
490524
if let NodeData::ParticleSet(particle_set) = self.data.take() {
@@ -594,6 +628,7 @@ impl<I: Index, R: Real> OctreeNode<I, R> {
594628
assert_eq!(octant_particles.len(), octant_particle_count);
595629

596630
let child = Box::new(OctreeNode::with_data(
631+
next_id.fetch_add(1, Ordering::SeqCst),
597632
min_corner,
598633
max_corner,
599634
NodeData::new_particle_set(

splashsurf_lib/src/profiling.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ impl Profiler {
143143

144144
/// Enter a scope with the given name and push it onto the stack of scopes. It will be a child scope of the current top of the stack.
145145
pub fn enter(&mut self, name: &'static str) -> (Guard, ScopeId) {
146+
// TODO: If the scope on top of the stack has the same name, use the next one as parent, to avoid huge chains for recursive functions
146147
let id = self.new_id(name, self.scope_stack.last());
147148
self.enter_with_id(name, id)
148149
}

0 commit comments

Comments
 (0)