Path: blob/main/crates/bevy_math/src/primitives/polygon.rs
6596 views
#[cfg(feature = "alloc")]1use {2super::{Measured2d, Triangle2d},3alloc::{collections::BTreeMap, vec::Vec},4core::cmp::Ordering,5};67use crate::Vec2;89#[derive(Debug, Clone, Copy)]10#[cfg(feature = "alloc")]11enum Endpoint {12Left,13Right,14}1516/// An event in the [`EventQueue`] is either the left or right vertex of an edge of the polygon.17///18/// Events are ordered so that any event `e1` which is to the left of another event `e2` is less than that event.19/// If `e1.position().x == e2.position().x` the events are ordered from bottom to top.20///21/// This is the order expected by the [`SweepLine`].22#[cfg(feature = "alloc")]23#[derive(Debug, Clone, Copy)]24struct SweepLineEvent {25segment: Segment,26/// Type of the vertex (left or right)27endpoint: Endpoint,28}2930#[cfg(feature = "alloc")]31impl SweepLineEvent {32fn position(&self) -> Vec2 {33match self.endpoint {34Endpoint::Left => self.segment.left,35Endpoint::Right => self.segment.right,36}37}38}3940#[cfg(feature = "alloc")]41impl PartialEq for SweepLineEvent {42fn eq(&self, other: &Self) -> bool {43self.position() == other.position()44}45}4647#[cfg(feature = "alloc")]48impl Eq for SweepLineEvent {}4950#[cfg(feature = "alloc")]51impl PartialOrd for SweepLineEvent {52fn partial_cmp(&self, other: &Self) -> Option<Ordering> {53Some(self.cmp(other))54}55}5657#[cfg(feature = "alloc")]58impl Ord for SweepLineEvent {59fn cmp(&self, other: &Self) -> Ordering {60xy_order(self.position(), other.position())61}62}6364/// Orders 2D points according to the order expected by the sweep line and event queue from -X to +X and then -Y to Y.65#[cfg(feature = "alloc")]66fn xy_order(a: Vec2, b: Vec2) -> Ordering {67a.x.total_cmp(&b.x).then_with(|| a.y.total_cmp(&b.y))68}6970/// The event queue holds an ordered list of all events the [`SweepLine`] will encounter when checking the current polygon.71#[cfg(feature = "alloc")]72#[derive(Debug, Clone)]73struct EventQueue {74events: Vec<SweepLineEvent>,75}76#[cfg(feature = "alloc")]77impl EventQueue {78/// Initialize a new `EventQueue` with all events from the polygon represented by `vertices`.79///80/// The events in the event queue will be ordered.81fn new(vertices: &[Vec2]) -> Self {82if vertices.is_empty() {83return Self { events: Vec::new() };84}8586let mut events = Vec::with_capacity(vertices.len() * 2);87for i in 0..vertices.len() {88let v1 = vertices[i];89let v2 = *vertices.get(i + 1).unwrap_or(&vertices[0]);90let (left, right) = if xy_order(v1, v2) == Ordering::Less {91(v1, v2)92} else {93(v2, v1)94};9596let segment = Segment {97edge_index: i,98left,99right,100};101events.push(SweepLineEvent {102segment,103endpoint: Endpoint::Left,104});105events.push(SweepLineEvent {106segment,107endpoint: Endpoint::Right,108});109}110111events.sort();112113Self { events }114}115}116117/// Represents a segment or rather an edge of the polygon in the [`SweepLine`].118///119/// Segments are ordered from bottom to top based on their left vertices if possible.120/// If their y values are identical, the segments are ordered based on the y values of their right vertices.121#[derive(Debug, Clone, Copy)]122#[cfg(feature = "alloc")]123struct Segment {124edge_index: usize,125left: Vec2,126right: Vec2,127}128129#[cfg(feature = "alloc")]130impl PartialEq for Segment {131fn eq(&self, other: &Self) -> bool {132self.edge_index == other.edge_index133}134}135136#[cfg(feature = "alloc")]137impl Eq for Segment {}138139#[cfg(feature = "alloc")]140impl PartialOrd for Segment {141fn partial_cmp(&self, other: &Self) -> Option<Ordering> {142Some(self.cmp(other))143}144}145146#[cfg(feature = "alloc")]147impl Ord for Segment {148fn cmp(&self, other: &Self) -> Ordering {149self.left150.y151.total_cmp(&other.left.y)152.then_with(|| self.right.y.total_cmp(&other.right.y))153}154}155156/// Holds information about which segment is above and which is below a given [`Segment`]157#[cfg(feature = "alloc")]158#[derive(Debug, Clone, Copy)]159struct SegmentOrder {160above: Option<usize>,161below: Option<usize>,162}163164/// A sweep line allows for an efficient search for intersections between [segments](`Segment`).165///166/// It can be thought of as a vertical line sweeping from -X to +X across the polygon that keeps track of the order of the segments167/// the sweep line is intersecting at any given moment.168#[derive(Debug, Clone)]169#[cfg(feature = "alloc")]170struct SweepLine<'a> {171vertices: &'a [Vec2],172tree: BTreeMap<Segment, SegmentOrder>,173}174#[cfg(feature = "alloc")]175impl<'a> SweepLine<'a> {176const fn new(vertices: &'a [Vec2]) -> Self {177Self {178vertices,179tree: BTreeMap::new(),180}181}182183/// Determine whether the given edges of the polygon intersect.184fn intersects(&self, edge1: Option<usize>, edge2: Option<usize>) -> bool {185let Some(edge1) = edge1 else {186return false;187};188let Some(edge2) = edge2 else {189return false;190};191192// All adjacent edges intersect at their shared vertex193// but these intersections do not count so we ignore them here.194// Likewise a segment will always intersect itself / an identical edge.195if edge1 == edge2196|| (edge1 + 1) % self.vertices.len() == edge2197|| (edge2 + 1) % self.vertices.len() == edge1198{199return false;200}201202let s11 = self.vertices[edge1];203let s12 = *self.vertices.get(edge1 + 1).unwrap_or(&self.vertices[0]);204let s21 = self.vertices[edge2];205let s22 = *self.vertices.get(edge2 + 1).unwrap_or(&self.vertices[0]);206207// When both points of the second edge are on the same side of the first edge, no intersection is possible.208if point_side(s11, s12, s21) * point_side(s11, s12, s22) > 0.0 {209return false;210}211if point_side(s21, s22, s11) * point_side(s21, s22, s12) > 0.0 {212return false;213}214215true216}217218/// Add a new segment to the sweep line219fn add(&mut self, s: Segment) -> SegmentOrder {220let above = if let Some((next_s, next_ord)) = self.tree.range_mut(s..).next() {221next_ord.below.replace(s.edge_index);222Some(next_s.edge_index)223} else {224None225};226let below = if let Some((prev_s, prev_ord)) = self.tree.range_mut(..s).next_back() {227prev_ord.above.replace(s.edge_index);228Some(prev_s.edge_index)229} else {230None231};232233let s_ord = SegmentOrder { above, below };234self.tree.insert(s, s_ord);235s_ord236}237238/// Get the segment order for the given segment.239///240/// If `s` has not been added to the [`SweepLine`] `None` will be returned.241fn find(&self, s: &Segment) -> Option<&SegmentOrder> {242self.tree.get(s)243}244245/// Remove `s` from the [`SweepLine`].246fn remove(&mut self, s: &Segment) {247let Some(s_ord) = self.tree.get(s).copied() else {248return;249};250251if let Some((_, above_ord)) = self.tree.range_mut(s..).next() {252above_ord.below = s_ord.below;253}254if let Some((_, below_ord)) = self.tree.range_mut(..s).next_back() {255below_ord.above = s_ord.above;256}257258self.tree.remove(s);259}260}261262/// Test what side of the line through `p1` and `p2` `q` is.263///264/// The result will be `0` if the `q` is on the segment, negative for one side and positive for the other.265#[cfg_attr(266not(feature = "alloc"),267expect(268dead_code,269reason = "this function is only used with the alloc feature"270)271)]272#[inline(always)]273const fn point_side(p1: Vec2, p2: Vec2, q: Vec2) -> f32 {274(p2.x - p1.x) * (q.y - p1.y) - (q.x - p1.x) * (p2.y - p1.y)275}276277/// Tests whether the `vertices` describe a simple polygon.278/// The last vertex must not be equal to the first vertex.279///280/// A polygon is simple if it is not self intersecting and not self tangent.281/// As such, no two edges of the polygon may cross each other and each vertex must not lie on another edge.282///283/// Any 'polygon' with less than three vertices is simple.284///285/// The algorithm used is the Shamos-Hoey algorithm, a version of the Bentley-Ottman algorithm adapted to only detect whether any intersections exist.286/// This function will run in O(n * log n)287#[cfg(feature = "alloc")]288pub fn is_polygon_simple(vertices: &[Vec2]) -> bool {289if vertices.len() < 3 {290return true;291}292if vertices.len() == 3 {293return Triangle2d::new(vertices[0], vertices[1], vertices[2]).area() > 0.0;294}295296let event_queue = EventQueue::new(vertices);297let mut sweep_line = SweepLine::new(vertices);298299for e in event_queue.events {300match e.endpoint {301Endpoint::Left => {302let s = sweep_line.add(e.segment);303if sweep_line.intersects(Some(e.segment.edge_index), s.above)304|| sweep_line.intersects(Some(e.segment.edge_index), s.below)305{306return false;307}308}309Endpoint::Right => {310if let Some(s) = sweep_line.find(&e.segment) {311if sweep_line.intersects(s.above, s.below) {312return false;313}314sweep_line.remove(&e.segment);315}316}317}318}319320true321}322323#[cfg(test)]324mod tests {325use crate::{primitives::polygon::is_polygon_simple, Vec2};326327#[test]328fn complex_polygon() {329// A square with one side punching through the opposite side.330let verts = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y, Vec2::new(2.0, 0.5)];331assert!(!is_polygon_simple(&verts));332333// A square with a vertex from one side touching the opposite side.334let verts = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y, Vec2::new(1.0, 0.5)];335assert!(!is_polygon_simple(&verts));336337// A square with one side touching the opposite side.338let verts = [339Vec2::ZERO,340Vec2::X,341Vec2::ONE,342Vec2::Y,343Vec2::new(1.0, 0.6),344Vec2::new(1.0, 0.4),345];346assert!(!is_polygon_simple(&verts));347348// Four points lying on a line349let verts = [Vec2::ONE, Vec2::new(3., 2.), Vec2::new(5., 3.), Vec2::NEG_X];350assert!(!is_polygon_simple(&verts));351352// Three points lying on a line353let verts = [Vec2::ONE, Vec2::new(3., 2.), Vec2::NEG_X];354assert!(!is_polygon_simple(&verts));355356// Two identical points and one other point357let verts = [Vec2::ONE, Vec2::ONE, Vec2::NEG_X];358assert!(!is_polygon_simple(&verts));359360// Two triangles with one shared side361let verts = [Vec2::ZERO, Vec2::X, Vec2::Y, Vec2::ONE, Vec2::X, Vec2::Y];362assert!(!is_polygon_simple(&verts));363}364365#[test]366fn simple_polygon() {367// A square368let verts = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y];369assert!(is_polygon_simple(&verts));370371let verts = [];372assert!(is_polygon_simple(&verts));373}374}375376377