mapf/graph/occupancy/
accessibility_graph.rs

1/*
2 * Copyright (C) 2023 Open Source Robotics Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16*/
17
18use crate::{
19    domain::Reversible,
20    error::NoError,
21    graph::{
22        occupancy::{Cell, Grid},
23        Graph,
24    },
25    motion::r2::Point,
26};
27use bitfield::{bitfield, Bit};
28use std::{
29    collections::{HashMap, HashSet},
30    sync::Arc,
31};
32
33/// From any unoccupied cell, expand towards any adjacent cells for whom the
34/// expansion is valid.
35pub struct AccessibilityGraph<G: Grid> {
36    accessibility: Arc<Accessibility<G>>,
37}
38
39impl<G: Grid> AccessibilityGraph<G> {
40    pub fn new(accessibility: Arc<Accessibility<G>>) -> Self {
41        Self { accessibility }
42    }
43}
44
45impl<G: Grid> Clone for AccessibilityGraph<G> {
46    fn clone(&self) -> Self {
47        Self {
48            accessibility: self.accessibility.clone(),
49        }
50    }
51}
52
53impl<G: Grid> Graph for AccessibilityGraph<G> {
54    type Key = Cell;
55    type Vertex = Point;
56    type EdgeAttributes = ();
57
58    type VertexRef<'a>
59        = Point
60    where
61        G: 'a;
62    type Edge<'a>
63        = (Cell, Cell)
64    where
65        G: 'a;
66    type EdgeIter<'a>
67        = AccessibilityGraphEdges
68    where
69        Self: 'a;
70
71    fn vertex<'a>(&'a self, key: &Cell) -> Option<Point> {
72        if self.accessibility.is_inaccessible(key) {
73            return None;
74        }
75
76        Some(key.center_point(self.accessibility.grid.cell_size()))
77    }
78
79    fn edges_from_vertex<'a>(&'a self, key: &Self::Key) -> Self::EdgeIter<'a>
80    where
81        Self: 'a,
82        Self::Vertex: 'a,
83        Self::Key: 'a,
84        Self::EdgeAttributes: 'a,
85    {
86        if self.accessibility.grid.is_occupied(key) {
87            return AccessibilityGraphEdges::None;
88        }
89
90        let directions = match self.accessibility.constraints.get(key) {
91            Some(constraints) => match constraints {
92                CellAccessibility::Accessible(constraints) => *constraints,
93                CellAccessibility::Inaccessible => return AccessibilityGraphEdges::None,
94            },
95            None => CellDirections::all(),
96        };
97
98        let from_cell = *key;
99        let directions = directions.into_iter();
100        AccessibilityGraphEdges::Edges {
101            from_cell,
102            directions,
103        }
104    }
105
106    type LazyEdgeIter<'a>
107        = [(Cell, Cell); 0]
108    where
109        G: 'a;
110
111    fn lazy_edges_between<'a>(&'a self, _: &Self::Key, _: &Self::Key) -> Self::LazyEdgeIter<'a>
112    where
113        Self: 'a,
114        Self::Vertex: 'a,
115        Self::Key: 'a,
116        Self::EdgeAttributes: 'a,
117    {
118        []
119    }
120}
121
122pub enum AccessibilityGraphEdges {
123    None,
124    Edges {
125        from_cell: Cell,
126        directions: CellDirectionsIter,
127    },
128}
129
130impl Iterator for AccessibilityGraphEdges {
131    type Item = (Cell, Cell);
132    fn next(&mut self) -> Option<Self::Item> {
133        let Self::Edges {
134            from_cell,
135            directions,
136        } = self
137        else {
138            return None;
139        };
140
141        directions
142            .next()
143            .map(|[i, j]| (*from_cell, from_cell.shifted(i, j)))
144    }
145}
146
147impl<G: Grid> Reversible for AccessibilityGraph<G> {
148    type ReversalError = NoError;
149    fn reversed(&self) -> Result<Self, Self::ReversalError>
150    where
151        Self: Sized,
152    {
153        // Accessibility is always symmetric/bidirectional, so we can just clone
154        // the graph in order to reverse it.
155        Ok(self.clone())
156    }
157}
158
159bitfield! {
160    #[derive(Clone, Copy, PartialEq, Eq)]
161    pub struct CellDirections(u8);
162    impl Debug;
163    u8;
164    pub north, set_north: 0;
165    pub northeast, set_northeast: 1;
166    pub east, set_east: 2;
167    pub southeast, set_southeast: 3;
168    pub south, set_south: 4;
169    pub southwest, set_southwest: 5;
170    pub west, set_west: 6;
171    pub northwest, set_northwest: 7;
172}
173
174impl CellDirections {
175    pub fn iter_from(self, cell: Cell) -> impl Iterator<Item = Cell> {
176        self.iter().map(move |[i, j]| cell.shifted(i, j))
177    }
178
179    /// Iterate over the directions that are accessible
180    pub fn iter(self) -> CellDirectionsIter {
181        CellDirectionsIter {
182            next_dir: 0,
183            directions: self,
184            accessibility: true,
185        }
186    }
187
188    /// Iterate over the directions that are inaccessible
189    pub fn iter_inaccessible(self) -> CellDirectionsIter {
190        CellDirectionsIter {
191            next_dir: 0,
192            directions: self,
193            accessibility: false,
194        }
195    }
196}
197
198impl IntoIterator for CellDirections {
199    type Item = [i64; 2];
200    type IntoIter = CellDirectionsIter;
201    fn into_iter(self) -> Self::IntoIter {
202        self.iter()
203    }
204}
205
206pub struct CellDirectionsIter {
207    // NOTE: We need to allow next_dir to increment beyond 255 so we don't get
208    // an integer overflow while iterating
209    next_dir: u16,
210    directions: CellDirections,
211    /// Do we want to iterate on directions that are accessible (true) or
212    /// inaccessible (false)?
213    accessibility: bool,
214}
215
216impl Iterator for CellDirectionsIter {
217    type Item = [i64; 2];
218    fn next(&mut self) -> Option<Self::Item> {
219        if self.next_dir > 7 {
220            // We have already iterated over all the directions
221            return None;
222        }
223
224        while self.directions.bit(self.next_dir as usize) != self.accessibility {
225            self.next_dir += 1;
226            if self.next_dir > 7 {
227                return None;
228            }
229        }
230
231        let shift = match self.next_dir {
232            0 => [0, 1],
233            1 => [1, 1],
234            2 => [1, 0],
235            3 => [1, -1],
236            4 => [0, -1],
237            5 => [-1, -1],
238            6 => [-1, 0],
239            7 => [-1, 1],
240            _ => return None,
241        };
242
243        self.next_dir += 1;
244        Some(shift)
245    }
246}
247
248impl CellDirections {
249    pub fn all() -> Self {
250        Self(u8::MAX)
251    }
252
253    pub fn is_all(&self) -> bool {
254        self.0 == u8::MAX
255    }
256
257    pub fn set_direction(&mut self, i: i8, j: i8, value: bool) -> Result<(), [i8; 2]> {
258        match [i, j] {
259            [0, 1] => self.set_north(value),
260            [1, 1] => self.set_northeast(value),
261            [1, 0] => self.set_east(value),
262            [1, -1] => self.set_southeast(value),
263            [0, -1] => self.set_south(value),
264            [-1, -1] => self.set_southwest(value),
265            [-1, 0] => self.set_west(value),
266            [-1, 1] => self.set_northwest(value),
267            _ => return Err([i, j]),
268        }
269
270        Ok(())
271    }
272}
273
274#[derive(Clone, Debug)]
275pub enum CellAccessibility {
276    /// The cell is accessible but it has some constraints on where the agent
277    /// can move from it.
278    Accessible(CellDirections),
279    /// The cell is entirely inaccessible. The agent cannot be centered at this
280    /// cell.
281    Inaccessible,
282}
283
284impl CellAccessibility {
285    pub fn is_inaccessible(&self) -> bool {
286        matches!(self, CellAccessibility::Inaccessible)
287    }
288}
289
290#[derive(Clone, Debug)]
291pub struct Accessibility<G: Grid> {
292    grid: G,
293    agent_radius: f64,
294    cell_shift: i64,
295    constraints: HashMap<Cell, CellAccessibility>,
296}
297
298impl<G: Grid> Accessibility<G> {
299    pub fn new(grid: G, agent_radius: f64) -> Self {
300        let cell_size = grid.cell_size();
301        let mut output = Self {
302            grid,
303            agent_radius,
304            cell_shift: Self::calculate_cell_shift(agent_radius, cell_size),
305            constraints: HashMap::new(),
306        };
307
308        Self::update_constraints(
309            output.grid.occupied_cells(),
310            &output.grid,
311            output.agent_radius,
312            output.cell_shift,
313            &mut output.constraints,
314        );
315
316        output
317    }
318
319    pub fn iter_accessibility<'a>(
320        &'a self,
321    ) -> impl Iterator<Item = (&'a Cell, &'a CellAccessibility)> {
322        self.constraints.iter()
323    }
324
325    pub fn grid(&self) -> &G {
326        &self.grid
327    }
328
329    pub fn agent_radius(&self) -> f64 {
330        self.agent_radius
331    }
332
333    pub fn is_inaccessible(&self, cell: &Cell) -> bool {
334        self.grid.is_occupied(cell)
335            || self
336                .constraints
337                .get(cell)
338                .filter(|c| c.is_inaccessible())
339                .is_some()
340    }
341
342    pub fn change_cells(&mut self, mut changes: HashMap<Cell, bool>) -> bool {
343        changes.retain(|cell, value| self.grid.is_occupied(cell) != *value);
344        if changes.is_empty() {
345            return false;
346        }
347
348        self.grid.change_cells(&changes);
349        Self::update_constraints(
350            changes.keys(),
351            &self.grid,
352            self.agent_radius,
353            self.cell_shift,
354            &mut self.constraints,
355        );
356
357        return true;
358    }
359
360    pub fn change_agent_radius(&mut self, value: f64) {
361        self.agent_radius = value;
362        self.cell_shift = Self::calculate_cell_shift(self.agent_radius, self.grid.cell_size());
363        self.constraints.clear();
364
365        Self::update_constraints(
366            self.grid.occupied_cells(),
367            &self.grid,
368            self.agent_radius,
369            self.cell_shift,
370            &mut self.constraints,
371        );
372    }
373
374    fn update_constraints<'a>(
375        changed_cells: impl IntoIterator<Item = &'a Cell>,
376        grid: &G,
377        agent_radius: f64,
378        cell_shift: i64,
379        constraints: &mut HashMap<Cell, CellAccessibility>,
380    ) {
381        let mut inspect_cells: HashSet<Cell> = HashSet::new();
382        for cell in changed_cells {
383            let cell: Cell = *cell;
384            for x in -cell_shift..=cell_shift {
385                for y in -cell_shift..=cell_shift {
386                    inspect_cells.insert(cell.shifted(x, y));
387                }
388            }
389        }
390
391        // For each cell, determine if it is occupied or unavailable
392        for cell in &inspect_cells {
393            if grid.is_occupied(cell) {
394                // If the cell is occupied there's no need to refer to store it
395                // in the constraints because we know that it has no adjacency
396                // by virtue of being occupied.
397                constraints.remove(cell);
398                continue;
399            }
400
401            let p = cell.center_point(grid.cell_size());
402            if grid.is_circle_occupied(p, agent_radius).is_some() {
403                constraints.insert(*cell, CellAccessibility::Inaccessible);
404                continue;
405            }
406
407            // Remove any constraint that might exist on this cell for now. We
408            // will put it back if needed in the next loop.
409            constraints.remove(cell);
410        }
411
412        // For each cell, determine which of its neighbors it can travel to
413        for from_cell in &inspect_cells {
414            if grid.is_occupied(from_cell) {
415                // If the cell is occupied, there's no need to check for
416                // expansions out of it
417                continue;
418            }
419
420            if constraints.contains_key(from_cell) {
421                // If it was set as Unavailable in the previous loop, there's
422                // no need to check for expansions out of it.
423                continue;
424            }
425
426            let from_p = from_cell.center_point(grid.cell_size());
427            let mut cell_directions = CellDirections::all();
428            for i in [-1, 0, 1] {
429                for j in [-1, 0, 1] {
430                    if i == 0 && j == 0 {
431                        continue;
432                    }
433
434                    let to_cell: Cell = from_cell.shifted(i, j);
435                    if grid.is_occupied(&to_cell)
436                        || constraints
437                            .get(&to_cell)
438                            .filter(|c| c.is_inaccessible())
439                            .is_some()
440                    {
441                        cell_directions.set_direction(i as i8, j as i8, false).ok();
442                        continue;
443                    }
444
445                    let to_p = to_cell.center_point(grid.cell_size());
446                    if grid
447                        .is_sweep_occupied(from_p, to_p, 2.0 * agent_radius)
448                        .is_some()
449                    {
450                        cell_directions.set_direction(i as i8, j as i8, false).ok();
451                    }
452                }
453            }
454
455            if !cell_directions.is_all() {
456                constraints.insert(*from_cell, CellAccessibility::Accessible(cell_directions));
457            }
458        }
459    }
460
461    fn calculate_cell_shift(agent_radius: f64, cell_size: f64) -> i64 {
462        (agent_radius / cell_size + 0.5).ceil() as i64
463    }
464}