Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non faillible triangulation #56

Merged
merged 4 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading