Skip to content

Commit ad93f37

Browse files
committed
Implement mesh clamping for MeshWithData, implement keep_vertices
1 parent aaef0f0 commit ad93f37

File tree

2 files changed

+178
-51
lines changed

2 files changed

+178
-51
lines changed

splashsurf/src/reconstruction.rs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ pub struct ReconstructSubcommandArgs {
210210
ignore_case = true,
211211
require_equals = true
212212
)]
213-
pub keep_vertices: Switch,
213+
pub keep_verts: Switch,
214214
/// Whether to compute surface normals at the mesh vertices and write them to the output file
215215
#[arg(
216216
help_heading = ARGS_INTERP,
@@ -283,6 +283,7 @@ pub struct ReconstructSubcommandArgs {
283283
require_equals = true
284284
)]
285285
pub output_raw_mesh: Switch,
286+
286287
/// Whether to try to convert triangles to quads if they meet quality criteria
287288
#[arg(
288289
help_heading = ARGS_INTERP,
@@ -303,7 +304,7 @@ pub struct ReconstructSubcommandArgs {
303304
#[arg(help_heading = ARGS_INTERP, long, default_value = "135")]
304305
pub quad_max_interior_angle: f64,
305306

306-
/// Lower corner of the bounding-box for the surface mesh, mesh outside gets cut away (requires mesh-max to be specified)
307+
/// Lower corner of the bounding-box for the surface mesh, triangles completely outside are removed (requires mesh-aabb-max to be specified)
307308
#[arg(
308309
help_heading = ARGS_POSTPROC,
309310
long,
@@ -313,7 +314,7 @@ pub struct ReconstructSubcommandArgs {
313314
requires = "mesh_aabb_max",
314315
)]
315316
pub mesh_aabb_min: Option<Vec<f64>>,
316-
/// Upper corner of the bounding-box for the surface mesh, mesh outside gets cut away (requires mesh-min to be specified)
317+
/// Upper corner of the bounding-box for the surface mesh, triangles completely outside are removed (requires mesh-aabb-min to be specified)
317318
#[arg(
318319
help_heading = ARGS_POSTPROC,
319320
long,
@@ -323,6 +324,16 @@ pub struct ReconstructSubcommandArgs {
323324
requires = "mesh_aabb_min",
324325
)]
325326
pub mesh_aabb_max: Option<Vec<f64>>,
327+
/// Whether to clamp vertices outside of the specified mesh AABB to the AABB (only has an effect if mesh-aabb-min/max are specified)
328+
#[arg(
329+
help_heading = ARGS_POSTPROC,
330+
long,
331+
default_value = "off",
332+
value_name = "off|on",
333+
ignore_case = true,
334+
require_equals = true
335+
)]
336+
pub mesh_aabb_clamp_verts: Switch,
326337

327338
/// Optional filename for writing the point cloud representation of the intermediate density map to disk
328339
#[arg(help_heading = ARGS_DEBUG, long, value_parser = value_parser!(PathBuf))]
@@ -456,6 +467,7 @@ mod arguments {
456467
pub output_raw_normals: bool,
457468
pub output_raw_mesh: bool,
458469
pub mesh_aabb: Option<Aabb3d<f64>>,
470+
pub mesh_aabb_clamp_vertices: bool,
459471
}
460472

461473
/// All arguments that can be supplied to the surface reconstruction tool converted to useful types
@@ -590,7 +602,7 @@ mod arguments {
590602
check_mesh: args.check_mesh.into_bool(),
591603
mesh_cleanup: args.mesh_cleanup.into_bool(),
592604
decimate_barnacles: args.decimate_barnacles.into_bool(),
593-
keep_vertices: args.keep_vertices.into_bool(),
605+
keep_vertices: args.keep_verts.into_bool(),
594606
compute_normals: args.normals.into_bool(),
595607
sph_normals: args.sph_normals.into_bool(),
596608
normals_smoothing_iters: args.normals_smoothing_iters,
@@ -606,6 +618,7 @@ mod arguments {
606618
output_raw_normals: args.output_raw_normals.into_bool(),
607619
output_raw_mesh: args.output_raw_mesh.into_bool(),
608620
mesh_aabb,
621+
mesh_aabb_clamp_vertices: args.mesh_aabb_clamp_verts.into_bool(),
609622
};
610623

611624
Ok(ReconstructionRunnerArgs {
@@ -1308,6 +1321,22 @@ pub(crate) fn reconstruction_pipeline_generic<I: Index, R: Real>(
13081321
}
13091322
}
13101323

1324+
// Remove and clamp cells outside of AABB
1325+
let mesh_with_data = if let Some(mesh_aabb) = &postprocessing.mesh_aabb {
1326+
profile!("clamp mesh to aabb");
1327+
info!("Post-processing: Clamping mesh to AABB...");
1328+
1329+
mesh_with_data.par_clamp_with_aabb(
1330+
&mesh_aabb
1331+
.try_convert()
1332+
.ok_or_else(|| anyhow!("Failed to convert mesh AABB"))?,
1333+
postprocessing.mesh_aabb_clamp_vertices,
1334+
postprocessing.keep_vertices,
1335+
)
1336+
} else {
1337+
mesh_with_data
1338+
};
1339+
13111340
// Convert triangles to quads
13121341
let (tri_mesh, tri_quad_mesh) = if postprocessing.generate_quads {
13131342
info!("Post-processing: Convert triangles to quads...");

splashsurf_lib/src/mesh.rs

Lines changed: 145 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -310,20 +310,91 @@ where
310310
}
311311

312312
/// Returns a new mesh containing only the specified cells and removes all unreferenced vertices
313-
fn keep_cells(&self, cell_indices: &[usize]) -> Self {
313+
fn keep_cells(&self, cell_indices: &[usize], keep_vertices: bool) -> Self {
314+
if keep_vertices {
315+
keep_cells_impl(self, cell_indices, &[])
316+
} else {
317+
let vertex_keep_table = vertex_keep_table(self, cell_indices);
318+
keep_cells_impl(self, cell_indices, &vertex_keep_table)
319+
}
320+
}
321+
322+
/// Removes all cells from the mesh that are completely outside of the given AABB and clamps the remaining cells to the boundary
323+
fn par_clamp_with_aabb(
324+
&self,
325+
aabb: &Aabb3d<R>,
326+
clamp_vertices: bool,
327+
keep_vertices: bool,
328+
) -> Self
329+
where
330+
Self::Cell: Sync,
331+
{
332+
// Find all triangles with at least one vertex inside of AABB
314333
let vertices = self.vertices();
315-
let cells = self.cells();
316-
317-
// Each entry is true if this vertex should be kept, false otherwise
318-
let vertex_keep_table = {
319-
let mut table = vec![false; vertices.len()];
320-
for cell in cell_indices.iter().copied().map(|c_i| &cells[c_i]) {
321-
for &vertex_index in cell.vertices() {
322-
table[vertex_index] = true;
323-
}
334+
let cells_to_keep = self
335+
.cells()
336+
.par_iter()
337+
.enumerate()
338+
.filter(|(_, cell)| {
339+
cell.vertices()
340+
.iter()
341+
.copied()
342+
.any(|v| aabb.contains_point(&vertices[v]))
343+
})
344+
.map(|(i, _)| i)
345+
.collect::<Vec<_>>();
346+
// Remove all other cells from mesh
347+
let mut new_mesh = self.keep_cells(&cells_to_keep, keep_vertices);
348+
// Clamp remaining vertices to AABB
349+
if clamp_vertices {
350+
new_mesh.vertices_mut().par_iter_mut().for_each(|v| {
351+
let min = aabb.min();
352+
let max = aabb.max();
353+
v.x = v.x.clamp(min.x, max.x);
354+
v.y = v.y.clamp(min.y, max.y);
355+
v.z = v.z.clamp(min.z, max.z);
356+
});
357+
}
358+
359+
new_mesh
360+
}
361+
}
362+
363+
/// Returns the list of vertices that should remain in the given mesh after keeping only the given cells
364+
fn vertex_keep_table<R: Real, MeshT: Mesh3d<R>>(mesh: &MeshT, cell_indices: &[usize]) -> Vec<bool> {
365+
let vertices = mesh.vertices();
366+
let cells = mesh.cells();
367+
368+
// Each entry is true if this vertex should be kept, false otherwise
369+
let vertex_keep_table = {
370+
let mut table = vec![false; vertices.len()];
371+
for cell in cell_indices.iter().copied().map(|c_i| &cells[c_i]) {
372+
for &vertex_index in cell.vertices() {
373+
table[vertex_index] = true;
324374
}
325-
table
326-
};
375+
}
376+
table
377+
};
378+
379+
vertex_keep_table
380+
}
381+
382+
/// Returns a new mesh keeping only the given cells and vertices in the mesh
383+
fn keep_cells_impl<R: Real, MeshT: Mesh3d<R>>(
384+
mesh: &MeshT,
385+
cell_indices: &[usize],
386+
vertex_keep_table: &[bool],
387+
) -> MeshT {
388+
let vertices = mesh.vertices();
389+
let cells = mesh.cells();
390+
391+
if vertex_keep_table.is_empty() {
392+
MeshT::from_vertices_and_connectivity(
393+
mesh.vertices().to_vec(),
394+
cell_indices.iter().map(|&i| &cells[i]).cloned().collect(),
395+
)
396+
} else {
397+
assert_eq!(mesh.vertices().len(), vertex_keep_table.len());
327398

328399
let old_to_new_label_map = {
329400
let mut label_map = MapType::default();
@@ -353,45 +424,13 @@ where
353424

354425
let relabeled_vertices: Vec<_> = vertex_keep_table
355426
.iter()
427+
.copied()
356428
.enumerate()
357-
.filter_map(|(i, should_keep)| if *should_keep { Some(i) } else { None })
429+
.filter_map(|(i, should_keep)| if should_keep { Some(i) } else { None })
358430
.map(|index| vertices[index].clone())
359431
.collect();
360432

361-
Self::from_vertices_and_connectivity(relabeled_vertices, relabeled_cells)
362-
}
363-
364-
/// Removes all cells from the mesh that are completely outside of the given AABB and clamps the remaining cells to the boundary
365-
fn par_clamp_with_aabb(&self, aabb: &Aabb3d<R>) -> Self
366-
where
367-
Self::Cell: Sync,
368-
{
369-
// Find all triangles with at least one vertex inside of AABB
370-
let vertices = self.vertices();
371-
let cells_to_keep = self
372-
.cells()
373-
.par_iter()
374-
.enumerate()
375-
.filter(|(_, cell)| {
376-
cell.vertices()
377-
.iter()
378-
.copied()
379-
.any(|v| aabb.contains_point(&vertices[v]))
380-
})
381-
.map(|(i, _)| i)
382-
.collect::<Vec<_>>();
383-
// Remove all other cells from mesh
384-
let mut new_mesh = self.keep_cells(&cells_to_keep);
385-
// Clamp remaining vertices to AABB
386-
new_mesh.vertices_mut().par_iter_mut().for_each(|v| {
387-
let min = aabb.min();
388-
let max = aabb.max();
389-
v.x = v.x.clamp(min.x, max.x);
390-
v.y = v.y.clamp(min.y, max.y);
391-
v.z = v.z.clamp(min.z, max.z);
392-
});
393-
394-
new_mesh
433+
MeshT::from_vertices_and_connectivity(relabeled_vertices, relabeled_cells)
395434
}
396435
}
397436

@@ -927,6 +966,45 @@ impl<R: Real, MeshT: Mesh3d<R>> Mesh3d<R> for MeshWithData<R, MeshT> {
927966
connectivity,
928967
))
929968
}
969+
970+
/// Returns a new mesh containing only the specified cells and removes all unreferenced vertices and attributes
971+
fn keep_cells(&self, cell_indices: &[usize], keep_all_vertices: bool) -> Self {
972+
// Filter internal mesh
973+
let mut new_mesh = if keep_all_vertices {
974+
let mut new_mesh = keep_cells_impl(self, cell_indices, &[]);
975+
new_mesh.point_attributes = self.point_attributes.clone();
976+
new_mesh
977+
} else {
978+
let vertex_keep_table = vertex_keep_table(self, cell_indices);
979+
let mut new_mesh = keep_cells_impl(self, cell_indices, &vertex_keep_table);
980+
981+
let vertex_indices = vertex_keep_table
982+
.iter()
983+
.copied()
984+
.enumerate()
985+
.filter_map(|(i, should_keep)| if should_keep { Some(i) } else { None })
986+
.collect::<Vec<_>>();
987+
988+
// Filter the point attributes
989+
new_mesh.point_attributes = self
990+
.point_attributes
991+
.iter()
992+
.map(|attr| attr.keep_indices(&vertex_indices))
993+
.collect();
994+
995+
new_mesh
996+
};
997+
998+
// Filter the cell attributes
999+
let cell_attributes = self
1000+
.cell_attributes
1001+
.iter()
1002+
.map(|attr| attr.keep_indices(&cell_indices))
1003+
.collect();
1004+
new_mesh.cell_attributes = cell_attributes;
1005+
1006+
new_mesh
1007+
}
9301008
}
9311009

9321010
/// Returns an mesh data wrapper with a default mesh and without attached attributes
@@ -1026,6 +1104,26 @@ impl<R: Real> MeshAttribute<R> {
10261104
.with_data(vec3r_vec.iter().flatten().copied().collect::<Vec<R>>()),
10271105
}
10281106
}
1107+
1108+
/// Returns a new attribute keeping only the entries with the given index
1109+
fn keep_indices(&self, indices: &[usize]) -> Self {
1110+
let data = match &self.data {
1111+
AttributeData::ScalarU64(d) => {
1112+
AttributeData::ScalarU64(indices.iter().copied().map(|i| d[i].clone()).collect())
1113+
}
1114+
AttributeData::ScalarReal(d) => {
1115+
AttributeData::ScalarReal(indices.iter().copied().map(|i| d[i].clone()).collect())
1116+
}
1117+
AttributeData::Vector3Real(d) => {
1118+
AttributeData::Vector3Real(indices.iter().copied().map(|i| d[i].clone()).collect())
1119+
}
1120+
};
1121+
1122+
Self {
1123+
name: self.name.clone(),
1124+
data,
1125+
}
1126+
}
10291127
}
10301128

10311129
impl<R: Real> AttributeData<R> {

0 commit comments

Comments
 (0)