Skip to content

Commit

Permalink
Non faillible triangulation (#56)
Browse files Browse the repository at this point in the history
* update spade

* use new non-faillible api for constraint edge

* remove merging api that is slower and not needed anymore

* remove old test
  • Loading branch information
mockersf authored Jun 28, 2024
1 parent f764738 commit 0450cd8
Show file tree
Hide file tree
Showing 9 changed files with 40 additions and 188 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ glam = { version = "0.27", features = ["approx"] }
smallvec = { version = "1.9", features = ["union", "const_generics"] }
bvh2d = { version = "0.5", git = "https://github.com/mockersf/bvh2d" }
serde = { version = "1.0", features = ["derive"], optional = true }
spade = "2.2"
spade = "2.9"
geo = "0.28.0"
log = "0.4"
thiserror = "1"
Expand Down
12 changes: 5 additions & 7 deletions benches/merger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ fn merger(c: &mut Criterion) {
triangulation.add_obstacle(ARENA_OBSTACLES[2].to_vec());
triangulation.add_obstacle(ARENA_OBSTACLES[3].to_vec());
triangulation.add_obstacle(ARENA_OBSTACLES[4].to_vec());
let mesh: Mesh = triangulation.as_navmesh().unwrap();
let mesh: Mesh = triangulation.as_navmesh();
b.iter(|| {
let mut mesh = mesh.clone();

Expand Down Expand Up @@ -1970,9 +1970,8 @@ fn random_with_many_obstacles() -> Triangulation {

fn merger_many_overlapping(c: &mut Criterion) {
c.bench_function(&"merger many overlapping".to_string(), |b| {
let mut triangulation = random_with_many_obstacles();
triangulation.merge_overlapping_obstacles();
let mesh: Mesh = triangulation.as_navmesh().unwrap();
let triangulation = random_with_many_obstacles();
let mesh: Mesh = triangulation.as_navmesh();
b.iter(|| {
let mut mesh = mesh.clone();
while mesh.merge_polygons() {}
Expand All @@ -1983,9 +1982,8 @@ fn merger_many_overlapping(c: &mut Criterion) {

fn merger_many_overlapping_once(c: &mut Criterion) {
c.bench_function(&"merger many overlapping (once)".to_string(), |b| {
let mut triangulation = random_with_many_obstacles();
triangulation.merge_overlapping_obstacles();
let mesh: Mesh = triangulation.as_navmesh().unwrap();
let triangulation = random_with_many_obstacles();
let mesh: Mesh = triangulation.as_navmesh();
b.iter(|| {
let mut mesh = mesh.clone();
mesh.merge_polygons();
Expand Down
20 changes: 8 additions & 12 deletions benches/triangulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ fn triangulation(c: &mut Criterion) {
triangulation.add_obstacle(ARENA_OBSTACLES[3].to_vec());
triangulation.add_obstacle(ARENA_OBSTACLES[4].to_vec());

let mesh: Mesh = triangulation.as_navmesh().unwrap();
let mesh: Mesh = triangulation.as_navmesh();
black_box(mesh);
})
});
Expand All @@ -161,7 +161,7 @@ fn triangulation_bulk(c: &mut Criterion) {
ARENA_OBSTACLES[3].to_vec(),
ARENA_OBSTACLES[4].to_vec(),
]);
let mesh: Mesh = triangulation.as_navmesh().unwrap();
let mesh: Mesh = triangulation.as_navmesh();
black_box(mesh);
})
});
Expand All @@ -178,9 +178,8 @@ fn triangulation_overlapping(c: &mut Criterion) {
triangulation.add_obstacle(ARENA_OBSTACLES[2].to_vec());
triangulation.add_obstacle(ARENA_OBSTACLES[3].to_vec());
triangulation.add_obstacle(ARENA_OBSTACLES[4].to_vec());
triangulation.merge_overlapping_obstacles();

let mesh: Mesh = triangulation.as_navmesh().unwrap();
let mesh: Mesh = triangulation.as_navmesh();
black_box(mesh);
})
});
Expand Down Expand Up @@ -219,7 +218,7 @@ fn triangulation_square(c: &mut Criterion) {
vec2(7.5, 7.5),
vec2(7.5, 5.01),
]);
let mesh: Mesh = triangulation.as_navmesh().unwrap();
let mesh: Mesh = triangulation.as_navmesh();
black_box(mesh);
})
});
Expand Down Expand Up @@ -258,8 +257,7 @@ fn triangulation_square_overlapping(c: &mut Criterion) {
vec2(7.5, 7.5),
vec2(7.5, 4.0),
]);
triangulation.merge_overlapping_obstacles();
let mesh: Mesh = triangulation.as_navmesh().unwrap();
let mesh: Mesh = triangulation.as_navmesh();
black_box(mesh);
})
});
Expand Down Expand Up @@ -2086,9 +2084,8 @@ fn random_with_many_obstacles() -> Triangulation {
fn triangulation_many_overlapping(c: &mut Criterion) {
c.bench_function(&"triangulation many overlapping".to_string(), |b| {
b.iter(|| {
let mut triangulation = random_with_many_obstacles();
triangulation.merge_overlapping_obstacles();
let mesh: Mesh = triangulation.as_navmesh().unwrap();
let triangulation = random_with_many_obstacles();
let mesh: Mesh = triangulation.as_navmesh();
black_box(mesh);
})
});
Expand All @@ -2100,9 +2097,8 @@ fn triangulation_many_overlapping_simplified(c: &mut Criterion) {
|b| {
b.iter(|| {
let mut triangulation = random_with_many_obstacles();
triangulation.merge_overlapping_obstacles();
triangulation.simplify(0.005);
let mesh: Mesh = triangulation.as_navmesh().unwrap();
let mesh: Mesh = triangulation.as_navmesh();
black_box(mesh);
})
},
Expand Down
3 changes: 1 addition & 2 deletions examples/traced/src/bin/merged.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1826,9 +1826,8 @@ fn main() {
vec2(2.7777152, 2.6639614),
],
]);
triangulation.merge_overlapping_obstacles();
triangulation.simplify(0.005);
let mut mesh = triangulation.as_navmesh().unwrap();
let mut mesh = triangulation.as_navmesh();
while mesh.merge_polygons() {}
}
}
3 changes: 1 addition & 2 deletions examples/traced/src/bin/triangulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1826,8 +1826,7 @@ fn main() {
vec2(2.7777152, 2.6639614),
],
]);
triangulation.merge_overlapping_obstacles();
triangulation.simplify(0.005);
triangulation.as_navmesh().unwrap();
triangulation.as_navmesh();
}
}
123 changes: 17 additions & 106 deletions src/input/triangulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ use std::collections::VecDeque;
use tracing::instrument;

pub use geo::LineString;
use geo::{
BooleanOps, Contains, Coord, CoordsIter, Intersects, MultiPolygon, Polygon as GeoPolygon,
SimplifyVwPreserve,
};
use geo::{Contains, Coord, Polygon as GeoPolygon, SimplifyVwPreserve};
use glam::{vec2, Vec2};
use spade::{ConstrainedDelaunayTriangulation, Point2, Triangulation as SpadeTriangulation};

Expand All @@ -31,19 +28,13 @@ impl Triangulation {
}

/// Add an obstacle delimited by the list of points on its edges.
///
/// Obstacles *MUST NOT* overlap. If some obstacles do overlap, use [`Triangulation::merge_overlapping_obstacles`]
/// before calling [`Triangulation::as_navmesh`].
pub fn add_obstacle(&mut self, edges: Vec<Vec2>) {
self.inner.interiors_push(LineString::from(
edges.iter().map(|v| (v.x, v.y)).collect::<Vec<_>>(),
));
}

/// Add obstacles delimited by the list of points on their edges.
///
/// Obstacles *MUST NOT* overlap. If some obstacles do overlap, use [`Triangulation::merge_overlapping_obstacles`]
/// before calling [`Triangulation::as_navmesh`].
pub fn add_obstacles(&mut self, obstacles: impl IntoIterator<Item = Vec<Vec2>>) {
let (exterior, interiors) =
std::mem::replace(&mut self.inner, GeoPolygon::new(LineString(vec![]), vec![]))
Expand All @@ -64,73 +55,6 @@ impl Triangulation {
);
}

/// Merge overlapping obstacles.
///
/// This must be called before converting the triangulation into a [`Mesh`] if there are overlapping obstacles,
/// otherwise it will fail.
#[cfg_attr(feature = "tracing", instrument(skip_all))]
pub fn merge_overlapping_obstacles(&mut self) {
let (mut exterior, interiors) =
std::mem::replace(&mut self.inner, GeoPolygon::new(LineString(vec![]), vec![]))
.into_inner();

let mut not_intersecting: Vec<LineString<f32>> = vec![];
for poly in interiors.into_iter() {
let intersecting = not_intersecting
.iter()
.enumerate()
.filter(|(_, other)| poly.intersects(*other))
.map(|(i, _)| i)
.collect::<Vec<_>>();

let to_keep = if intersecting.is_empty() {
poly
} else {
#[cfg(feature = "tracing")]
let _merging_span = tracing::info_span!("merging polygons").entered();

let mut merged = MultiPolygon::<f32>(
intersecting
.iter()
.rev()
.map(|other| GeoPolygon::new(not_intersecting.remove(*other), vec![]))
.collect(),
);
merged = merged.union(&GeoPolygon::new(poly, vec![]).into());
LineString(merged.exterior_coords_iter().collect::<Vec<_>>())
};

if to_keep.intersects(&exterior) {
let new_exterior =
GeoPolygon::new(exterior, vec![]).difference(&GeoPolygon::new(to_keep, vec![]));
// Keep the biggest of the new exterior polygons
if new_exterior.0.len() > 1 {
let mut biggest = 0;
let mut biggest_length = 0;
for (i, poly) in new_exterior.0.iter().enumerate() {
let exterior_length = poly.exterior_coords_iter().len();
if exterior_length > biggest_length {
biggest = i;
biggest_length = exterior_length;
}
}
exterior = LineString(
new_exterior.0[biggest]
.exterior_coords_iter()
.collect::<Vec<_>>(),
);
} else {
exterior =
LineString(new_exterior.0[0].exterior_coords_iter().collect::<Vec<_>>());
}
} else {
not_intersecting.push(to_keep);
}
}

self.inner = GeoPolygon::new(exterior, not_intersecting);
}

/// Simplify the outer edge and obstacles, using a topology-preserving variant of the
/// [Visvalingam-Whyatt algorithm](https://www.tandfonline.com/doi/abs/10.1179/000870493786962263).
///
Expand All @@ -145,41 +69,32 @@ impl Triangulation {
fn add_constraint_edges(
cdt: &mut ConstrainedDelaunayTriangulation<Point2<f32>>,
edges: &LineString<f32>,
) -> Option<()> {
) {
let mut edge_iter = edges.coords().peekable();
let mut next_point = None;
loop {
let from = edge_iter.next().unwrap();
let next = edge_iter.peek();

let point_a = cdt
.insert(Point2 {
let point_a = next_point.unwrap_or_else(|| {
cdt.insert(Point2 {
x: from.x,
y: from.y,
})
.unwrap();
.unwrap()
});
let point_b = if let Some(next) = next {
cdt.insert(Point2 {
x: next.x,
y: next.y,
})
.unwrap()
} else {
cdt.insert(Point2 {
x: edges[0].x,
y: edges[0].y,
})
.unwrap()
};
if cdt.can_add_constraint(point_a, point_b) {
cdt.add_constraint(point_a, point_b);
} else {
return None;
}
if next.is_none() {
break;
}
};
cdt.add_constraint_and_split(point_a, point_b, |v| v);
next_point = Some(point_b);
}
Some(())
}

/// Convert the triangulation into a [`Mesh`].
Expand All @@ -191,7 +106,7 @@ impl Triangulation {
/// # use glam::vec2;
/// # use polyanya::Triangulation;
/// # let triangulation = Triangulation::from_outer_edges(&[vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 1.0)]);
/// let mut mesh = triangulation.as_navmesh().unwrap();
/// let mut mesh = triangulation.as_navmesh();
///
/// // Merge polygons at least once before baking.
/// mesh.merge_polygons();
Expand All @@ -200,18 +115,14 @@ impl Triangulation {
/// mesh.bake();
/// ```
#[cfg_attr(feature = "tracing", instrument(skip_all))]
pub fn as_navmesh(&self) -> Option<Mesh> {
pub fn as_navmesh(&self) -> Mesh {
let mut cdt = ConstrainedDelaunayTriangulation::<Point2<f32>>::new();
Triangulation::add_constraint_edges(&mut cdt, self.inner.exterior())?;
Triangulation::add_constraint_edges(&mut cdt, self.inner.exterior());

if self
.inner
self.inner
.interiors()
.iter()
.any(|obstacle| Triangulation::add_constraint_edges(&mut cdt, obstacle).is_none())
{
return None;
}
.for_each(|obstacle| Triangulation::add_constraint_edges(&mut cdt, obstacle));

#[cfg(feature = "tracing")]
let polygon_span = tracing::info_span!("listing polygons").entered();
Expand Down Expand Up @@ -278,10 +189,10 @@ impl Triangulation {
#[cfg(feature = "tracing")]
drop(vertex_span);

Some(Mesh {
Mesh {
vertices,
polygons,
..Default::default()
})
}
}
}
4 changes: 2 additions & 2 deletions src/merger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ mod test {
Vec2::new(1., 1.),
Vec2::new(1., -1.),
]);
let mut mesh = triangulation.as_navmesh().unwrap();
let mut mesh = triangulation.as_navmesh();
// println!("{:#?}", mesh);
while mesh.merge_polygons() {
// println!("{:#?}", mesh);
Expand Down Expand Up @@ -316,7 +316,7 @@ mod test {
],
]);
triangulation.simplify(0.001);
let mut mesh = triangulation.as_navmesh().unwrap();
let mut mesh = triangulation.as_navmesh();

mesh.unbake();
// println!("{:#?}", mesh);
Expand Down
2 changes: 1 addition & 1 deletion tests/arena-triangulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ fn arena_mesh() -> Mesh {
vec2(26., 7.),
vec2(26., 10.),
]);
triangulation.as_navmesh().unwrap()
triangulation.as_navmesh()
}

#[test]
Expand Down
Loading

0 comments on commit 0450cd8

Please sign in to comment.