Skip to content

Commit

Permalink
Rework intersection to return a compound polyline
Browse files Browse the repository at this point in the history
And add a new polyline helper function to extract connected components.
  • Loading branch information
sixfold-origami committed Jan 3, 2023
1 parent 6d2cdc4 commit c48cca3
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 42 deletions.
65 changes: 42 additions & 23 deletions crates/parry3d/tests/geometry/trimesh_intersection.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use na::{Point3, Vector3};
use parry3d::math::{Isometry, Real};
use parry3d::query::IntersectResult;
use parry3d::shape::TriMesh;

fn build_diamond() -> TriMesh {
fn build_diamond(position: &Isometry<Real>) -> TriMesh {
// Two tetrahedrons sharing a face
let points = vec![
Point3::new(0.0, 2.0, 0.0),
Point3::new(-2.0, -1.0, 0.0),
Point3::new(0.0, 0.0, 2.0),
Point3::new(2.0, -1.0, 0.0),
Point3::new(0.0, 0.0, -2.0),
position * Point3::new(0.0, 2.0, 0.0),
position * Point3::new(-2.0, -1.0, 0.0),
position * Point3::new(0.0, 0.0, 2.0),
position * Point3::new(2.0, -1.0, 0.0),
position * Point3::new(0.0, 0.0, -2.0),
];

let indices = vec![
Expand All @@ -26,17 +27,15 @@ fn build_diamond() -> TriMesh {

#[test]
fn trimesh_plane_edge_intersection() {
let mesh = build_diamond();
let mesh = build_diamond(&Isometry::identity());

let result = mesh.intersection_with_local_plane(&Vector3::ith_axis(2), 0.5, std::f32::EPSILON);

assert!(matches!(result, IntersectResult::Intersect(_)));

if let IntersectResult::Intersect(lines) = result {
assert_eq!(lines.len(), 1);

if let IntersectResult::Intersect(line) = result {
// Need to check points individually since order is not garunteed
let vertices = lines[0].vertices();
let vertices = line.vertices();
assert_eq!(vertices.len(), 3);
assert!(vertices.contains(&Point3::new(-1.5, -0.75, 0.5)));
assert!(vertices.contains(&Point3::new(1.5, -0.75, 0.5)));
Expand All @@ -46,17 +45,15 @@ fn trimesh_plane_edge_intersection() {

#[test]
fn trimesh_plane_vertex_intersection() {
let mesh = build_diamond();
let mesh = build_diamond(&Isometry::identity());

let result = mesh.intersection_with_local_plane(&Vector3::ith_axis(2), 0.0, std::f32::EPSILON);

assert!(matches!(result, IntersectResult::Intersect(_)));

if let IntersectResult::Intersect(lines) = result {
assert_eq!(lines.len(), 1);

if let IntersectResult::Intersect(line) = result {
// Need to check points individually since order is not garunteed
let vertices = lines[0].vertices();
let vertices = line.vertices();
assert_eq!(vertices.len(), 3);
assert!(vertices.contains(&Point3::new(-2.0, -1.0, 0.0)));
assert!(vertices.contains(&Point3::new(2.0, -1.0, 0.0)));
Expand All @@ -66,17 +63,15 @@ fn trimesh_plane_vertex_intersection() {

#[test]
fn trimesh_plane_mixed_intersection() {
let mesh = build_diamond();
let mesh = build_diamond(&Isometry::identity());

let result = mesh.intersection_with_local_plane(&Vector3::ith_axis(0), 0.0, std::f32::EPSILON);

assert!(matches!(result, IntersectResult::Intersect(_)));

if let IntersectResult::Intersect(lines) = result {
assert_eq!(lines.len(), 1);

if let IntersectResult::Intersect(line) = result {
// Need to check points individually since order is not garunteed
let vertices = lines[0].vertices();
let vertices = line.vertices();
assert_eq!(vertices.len(), 4);
assert!(vertices.contains(&Point3::new(0.0, 2.0, 0.0)));
assert!(vertices.contains(&Point3::new(0.0, 0.0, 2.0)));
Expand All @@ -85,9 +80,33 @@ fn trimesh_plane_mixed_intersection() {
}
}

#[test]
fn trimesh_plane_multi_intersection() {
let mut mesh = build_diamond(&Isometry::identity());
mesh.append(&build_diamond(&Isometry::translation(-5.0, 0.0, 0.0)));

let result = mesh.intersection_with_local_plane(&Vector3::ith_axis(2), 0.5, std::f32::EPSILON);

assert!(matches!(result, IntersectResult::Intersect(_)));

if let IntersectResult::Intersect(line) = result {
// Need to check points individually since order is not garunteed
let vertices = line.vertices();
assert_eq!(vertices.len(), 6);

assert!(vertices.contains(&Point3::new(-1.5, -0.75, 0.5)));
assert!(vertices.contains(&Point3::new(1.5, -0.75, 0.5)));
assert!(vertices.contains(&Point3::new(0.0, 1.5, 0.5)));

assert!(vertices.contains(&Point3::new(-6.5, -0.75, 0.5)));
assert!(vertices.contains(&Point3::new(-3.5, -0.75, 0.5)));
assert!(vertices.contains(&Point3::new(-5.0, 1.5, 0.5)));
}
}

#[test]
fn trimesh_plane_above() {
let mesh = build_diamond();
let mesh = build_diamond(&Isometry::identity());

let result = mesh.intersection_with_local_plane(&Vector3::ith_axis(2), -5.0, std::f32::EPSILON);

Expand All @@ -96,7 +115,7 @@ fn trimesh_plane_above() {

#[test]
fn trimesh_plane_below() {
let mesh = build_diamond();
let mesh = build_diamond(&Isometry::identity());

let result = mesh.intersection_with_local_plane(&Vector3::ith_axis(2), 5.0, std::f32::EPSILON);

Expand Down
30 changes: 11 additions & 19 deletions src/query/split/split_trimesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,12 +350,14 @@ impl TriMesh {
/// This will intersect the mesh by a plane with a normal with it’s `axis`-th component set to 1.
/// The splitting plane is shifted wrt. the origin by the `bias` (i.e. it passes through the point
/// equal to `normal * bias`).
///
/// Note that the resultant polyline may have multiple connected components
pub fn canonical_intersection_with_plane(
&self,
axis: usize,
bias: Real,
epsilon: Real,
) -> IntersectResult<Vec<Polyline>> {
) -> IntersectResult<Polyline> {
self.intersection_with_local_plane(&Vector::ith_axis(axis), bias, epsilon)
}

Expand All @@ -368,7 +370,7 @@ impl TriMesh {
axis: &UnitVector<Real>,
bias: Real,
epsilon: Real,
) -> IntersectResult<Vec<Polyline>> {
) -> IntersectResult<Polyline> {
let local_axis = position.inverse_transform_unit_vector(axis);
let added_bias = -position.translation.vector.dot(&axis);
self.intersection_with_local_plane(&local_axis, bias + added_bias, epsilon)
Expand All @@ -382,7 +384,7 @@ impl TriMesh {
local_axis: &UnitVector<Real>,
bias: Real,
epsilon: Real,
) -> IntersectResult<Vec<Polyline>> {
) -> IntersectResult<Polyline> {
// 1. Partition the vertices.
let vertices = self.vertices();
let indices = self.indices();
Expand Down Expand Up @@ -561,7 +563,7 @@ impl TriMesh {
}

// 3. Ensure consistent edge orientation by traversing the adjacency list
let mut polylines: Vec<Polyline> = Vec::new();
let mut polyline_indices: Vec<[u32; 2]> = Vec::with_capacity(index_adjacencies.len() + 1);

let mut seen = vec![false; index_adjacencies.len()];
for (idx, neighbors) in index_adjacencies.iter().enumerate() {
Expand All @@ -573,37 +575,27 @@ impl TriMesh {
let mut prev = first;
let mut next = neighbors.first(); // Arbitrary neighbor

let mut vertex_indices = vec![prev];

'traversal: while let Some(current) = next {
seen[*current] = true;
vertex_indices.push(*current);
polyline_indices.push([prev as u32, *current as u32]);

for neighbor in index_adjacencies[*current].iter() {
if *neighbor != prev && *neighbor != first {
prev = *current;
next = Some(neighbor);
continue 'traversal;
} else if *neighbor != prev && *neighbor == first {
// If the next index is same as the first, exit
// If the next index is same as the first, close the polyline and exit
polyline_indices.push([*current as u32, first as u32]);
next = None;
continue 'traversal;
}
}
}

// Select vertices and construct final polyline
let mut polyline_vertices = Vec::with_capacity(vertex_indices.len());
for idx in vertex_indices.into_iter() {
polyline_vertices.push(new_vertices[idx]);
}
let polyline_indices = (0..polyline_vertices.len() as u32)
.map(|i| [i, (i + 1) % polyline_vertices.len() as u32])
.collect();
polylines.push(Polyline::new(polyline_vertices, Some(polyline_indices)));
}
}

IntersectResult::Intersect(polylines)
IntersectResult::Intersect(Polyline::new(new_vertices, Some(polyline_indices)))
}

/// Computes the intersection mesh between an Aabb and this mesh.
Expand Down
49 changes: 49 additions & 0 deletions src/shape/polyline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,55 @@ impl Polyline {
}
}

/// Extracts the connected components of this polyline, consuming `self`.
///
/// Assumes that:
/// - This polyline is closed with `self.indices[i][1] == self.indices[(i + 1) % component_length][0]`
/// for each component, where `component_length` is the number of vertices in that component.
/// - The indices for each component are found contiguously in the index buffer.
pub fn extract_connected_components(self) -> Vec<Polyline> {
let vertices = self.vertices();
let indices = self.indices();

if indices.len() == 0 {
// Polyline is empty, return empty Vec
return Vec::new();
} else {
let mut components = Vec::new();

let mut start_i = 0; // Start position of component
let mut start_node = indices[0][0]; // Start vertex index of component

let mut component_vertices = Vec::new();
let mut component_indices: Vec<[u32; 2]> = Vec::new();

// Iterate over indices, building polylines as we go
for (i, idx) in indices.into_iter().enumerate() {
component_vertices.push(vertices[idx[0] as usize]);

if idx[1] != start_node {
// Keep scanning and adding data
component_indices.push([(i - start_i) as u32, (i - start_i + 1) as u32]);
} else {
// Start node reached: build polyline and start next component
component_indices.push([(i - start_i) as u32, 0]);
components.push(Polyline::new(
component_vertices.drain(..).collect(),
Some(component_indices.drain(..).collect()),
));

if i + 1 < indices.len() {
// More components to find
start_node = indices[i + 1][0];
start_i = i + 1;
}
}
}

components
}
}

/// Perform a point projection assuming a solid interior based on a counter-clock-wise orientation.
///
/// This is similar to `self.project_local_point_and_get_location` except that the resulting
Expand Down

0 comments on commit c48cca3

Please sign in to comment.