Skip to content

Commit

Permalink
more polishing & documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
JeroenGar committed Feb 7, 2024
1 parent 66d512a commit 0c8c9e4
Show file tree
Hide file tree
Showing 31 changed files with 215 additions and 186 deletions.
61 changes: 33 additions & 28 deletions jaguars/src/collision_detection/cd_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ use crate::geometry::transformation::Transformation;
use crate::util::assertions;
use crate::util::config::{CDEConfig, HazProxConfig, QuadTreeConfig};


/// The Collision Detection Engine (CDE) is responsible for validating potential placements of items in a Layout.
/// It can be queried to check if a given shape or surrogate collides with any hazards in the layout.
/// It is updated by the Layout, when its state changes.
#[derive(Clone, Debug)]
pub struct CDEngine {
quadtree: QTNode,
Expand All @@ -31,6 +35,15 @@ pub struct CDEngine {
uncommited_deregisters: Vec<Hazard>,
}


/// Snapshot of the state of CDE at a given time.
/// The CDE can take snapshots of itself at any time, and use them to restore to that state later.
#[derive(Clone, Debug)]
pub struct CDESnapshot {
dynamic_hazards: Vec<Hazard>,
grid: Option<Grid<HPGCell>>
}

impl CDEngine {
pub fn new(bbox: AARectangle, static_hazards: Vec<Hazard>, config: CDEConfig) -> CDEngine {
let qt_depth = match config.quadtree {
Expand Down Expand Up @@ -217,27 +230,27 @@ impl CDEngine {
}

//QUERY ----------------------------------------------------------------------------------------
pub fn shape_collides(&self, shape: &SimplePolygon, ignored_entities: &[HazardEntity]) -> bool {
pub fn shape_collides(&self, shape: &SimplePolygon, irrelevant_hazards: &[HazardEntity]) -> bool {
match self.bbox.relation_to(&shape.bbox()) {
//Not fully inside bbox => definite collision
GeoRelation::Disjoint | GeoRelation::Enclosed | GeoRelation::Intersecting => true,
GeoRelation::Surrounding => {
self.collision_by_edge_intersection(shape, ignored_entities) ||
self.collision_by_containment(shape, ignored_entities)
self.collision_by_edge_intersection(shape, irrelevant_hazards) ||
self.collision_by_containment(shape, irrelevant_hazards)
}
}
}

pub fn surrogate_collides(&self, base_surrogate: &SPSurrogate, transform: &Transformation, ignored_entities: &[HazardEntity]) -> bool {
pub fn surrogate_collides(&self, base_surrogate: &SPSurrogate, transform: &Transformation, irrelevant_hazards: &[HazardEntity]) -> bool {
for pole in base_surrogate.ff_poles() {
let t_pole = pole.transform_clone(transform);
if self.quadtree.collides(&t_pole, ignored_entities).is_some() {
if self.quadtree.collides(&t_pole, irrelevant_hazards).is_some() {
return true;
}
}
for pier in base_surrogate.ff_piers() {
let t_pier = pier.transform_clone(transform);
if self.quadtree.collides(&t_pier, ignored_entities).is_some() {
if self.quadtree.collides(&t_pier, irrelevant_hazards).is_some() {
return true;
}
}
Expand All @@ -251,35 +264,35 @@ impl CDEngine {
}
}

pub fn edge_definitely_collides(&self, edge: &Edge, ignored_entities: &[HazardEntity]) -> Tribool {
pub fn edge_definitely_collides(&self, edge: &Edge, irrelevant_hazards: &[HazardEntity]) -> Tribool {
match !self.bbox.collides_with(&edge.start()) || !self.bbox.collides_with(&edge.end()) {
true => Tribool::True, //if either the start or end of the edge is outside the quadtree, it definitely collides
false => self.quadtree.definitely_collides(edge, ignored_entities)
false => self.quadtree.definitely_collides(edge, irrelevant_hazards)
}
}

pub fn circle_definitely_collides(&self, circle: &Circle, ignored_entities: &[HazardEntity]) -> Tribool {
pub fn circle_definitely_collides(&self, circle: &Circle, irrelevant_hazards: &[HazardEntity]) -> Tribool {
match self.bbox.collides_with(&circle.center) {
false => Tribool::True, //outside the quadtree, so definitely collides
true => self.quadtree.definitely_collides(circle, ignored_entities)
true => self.quadtree.definitely_collides(circle, irrelevant_hazards)
}
}

pub fn entities_in_circle(&self, circle: &Circle, ignored_entities: &[HazardEntity]) -> Vec<HazardEntity> {
pub fn entities_in_circle(&self, circle: &Circle, irrelevant_hazards: &[HazardEntity]) -> Vec<HazardEntity> {
let mut colliding_entities = vec![];
let mut ignored_entities = ignored_entities.iter().cloned().collect_vec();
let mut irrelevant_hazards = irrelevant_hazards.iter().cloned().collect_vec();

//Keep testing the quadtree for intersections until no (non-ignored) entities collide
while let Some(haz_entity) = self.quadtree.collides(circle, &ignored_entities) {
while let Some(haz_entity) = self.quadtree.collides(circle, &irrelevant_hazards) {
colliding_entities.push(haz_entity.clone());
ignored_entities.push(haz_entity.clone());
irrelevant_hazards.push(haz_entity.clone());
}

let circle_center_in_qt = self.bbox.collides_with(&circle.center);

if !circle_center_in_qt && colliding_entities.is_empty() {
// The circle center is outside the quadtree
if !ignored_entities.contains(&&HazardEntity::BinExterior) {
if !irrelevant_hazards.contains(&&HazardEntity::BinExterior) {
//Add the bin as a hazard, unless it is ignored
colliding_entities.push(HazardEntity::BinExterior);
}
Expand All @@ -288,16 +301,16 @@ impl CDEngine {
colliding_entities
}

fn collision_by_edge_intersection(&self, shape: &SimplePolygon, ignored_entities: &[HazardEntity]) -> bool {
fn collision_by_edge_intersection(&self, shape: &SimplePolygon, irrelevant_hazards: &[HazardEntity]) -> bool {
shape.edge_iter()
.any(|e| self.quadtree.collides(&e, ignored_entities).is_some())
.any(|e| self.quadtree.collides(&e, irrelevant_hazards).is_some())
}

fn collision_by_containment(&self, shape: &SimplePolygon, ignored_entities: &[HazardEntity]) -> bool
fn collision_by_containment(&self, shape: &SimplePolygon, irrelevant_hazards: &[HazardEntity]) -> bool
{
//collect all active and non-ignored hazard_filters
//collect all active and non-ignored hazards
let mut relevant_hazards = self.all_hazards()
.filter(|h| h.active && !ignored_entities.contains(&h.entity));
.filter(|h| h.active && !irrelevant_hazards.contains(&h.entity));

relevant_hazards.any(|haz| {
//Due to possible fp issues, we check if the bboxes are "almost" related
Expand Down Expand Up @@ -334,12 +347,4 @@ impl CDEngine {
}
})
}
}

//Snapshot of the CDE state at a given time.
//Can be used to restore the CDE to a previous state.
#[derive(Clone, Debug)]
pub struct CDESnapshot {
dynamic_hazards: Vec<Hazard>,
grid: Option<Grid<HPGCell>>
}
18 changes: 12 additions & 6 deletions jaguars/src/collision_detection/haz_prox_grid/boundary_fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use crate::collision_detection::haz_prox_grid::circling_iterator::CirclingIterat
use crate::collision_detection::haz_prox_grid::grid::Grid;
use crate::geometry::primitives::aa_rectangle::AARectangle;

//Boundary Fill algorithm
//1. Queue all cells within the seed bounding box
//2. Visit the cells in the queue, once a seed is found, dequeue all cells and queue all its neighbors
//3. Explore and queue unvisited neighbors until queue is empty

///Boundary Fill type of algorithm.
///Iteratively visits cells within a grid.
///While unseeded, the struct will visit cells from the seed_bbox, from the inside out.
///When a cell's neighbors are queued, the struct is considered seeded, and will from then on visit cells from the queue.
///The "Fill" finished when there are no more cells to visit, i.e. the seed_iterator runs out (unseeded) or the queue is empty (seeded)
#[derive(Debug, Clone)]
pub struct BoundaryFillGrid {
state: Vec<CellState>,
Expand All @@ -19,7 +19,9 @@ pub struct BoundaryFillGrid {
}

impl BoundaryFillGrid {
pub fn new<T>(seed_bbox: AARectangle, grid: &Grid<T>) -> Self {

/// Creates a new BoundaryFillGrid, add all cells inside the seed_bbox to the queue
pub fn new<T>(grid: &Grid<T>, seed_bbox: AARectangle) -> Self {
let state = vec![CellState::new(); grid.n_rows() * grid.n_cols()];

//Find the range of rows and columns which reside inside the seed_bbox
Expand All @@ -41,6 +43,8 @@ impl BoundaryFillGrid {
}
}

/// Returns the next cell to visit and pops it from the queue,
/// if there are no more cells to visit return None
pub fn pop<T>(&mut self, grid: &Grid<T>) -> Option<usize> {
match self.seeded {
false => {
Expand Down Expand Up @@ -69,6 +73,8 @@ impl BoundaryFillGrid {
}
}

/// Adds all unvisited neighbors of the cell at index to the queue
/// Also marks the grid as seeded
pub fn queue_neighbors<T>(&mut self, index: usize, grid: &Grid<T>) {
self.seeded = true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::ops::RangeInclusive;

use crate::collision_detection::haz_prox_grid::outward_iterator::OutwardIterator;

/// 2D version of OutwardIterator
/// Iterates over a 2D range from the center outwards
#[derive(Debug, Clone)]
pub struct CirclingIterator {
original_col_iter: OutwardIterator,
Expand Down
23 changes: 13 additions & 10 deletions jaguars/src/collision_detection/haz_prox_grid/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ use std::ops::RangeInclusive;

use itertools::Itertools;
use ordered_float::NotNan;
use crate::geometry::primitives::point::Point;

//abstract representation of a grid of elements at specific coordinates
//divided into rows and columns

/// Representation of a grid of optional elements of type T
/// Divided into rows and columns, where each row and column has a unique coordinate
#[derive(Clone, Debug)]
pub struct Grid<T> {
cells: Vec<Option<T>>,
Expand All @@ -17,14 +17,16 @@ pub struct Grid<T> {
}

impl<T> Grid<T> {
pub fn new(elements: Vec<(T, (f64, f64))>) -> Self {

/// Creates a new grid from a vector of values of type T and their coordinates
pub fn new(elements: Vec<(T, Point)>) -> Self {
//find all unique rows and columns from the element's coordinates
let rows = elements.iter()
.map(|(_e, (_x, y))| NotNan::new(*y).unwrap())
.map(|(_e, Point(_x, y))| NotNan::new(*y).unwrap())
.unique().sorted().collect::<Vec<NotNan<f64>>>();

let cols = elements.iter()
.map(|(_e, (x, _y))| NotNan::new(*x).unwrap())
.map(|(_e, Point(x, _y))| NotNan::new(*x).unwrap())
.unique().sorted().collect::<Vec<NotNan<f64>>>();

let n_rows = rows.len();
Expand All @@ -33,7 +35,7 @@ impl<T> Grid<T> {
//create a vector of cells, with the correct size
let mut cells = (0..n_rows * n_cols).map(|_| None).collect_vec();

for (element, (x, y)) in elements {
for (element, Point(x, y)) in elements {
//search correct row and col for the cell
let row = match rows.binary_search(&NotNan::new(y).unwrap()) {
Ok(row) => row,
Expand Down Expand Up @@ -91,13 +93,14 @@ impl<T> Grid<T> {
start_col..=end_col
}

///Returns the indices of the 8 directly neighboring cells.
///If the cell is on the edge, the index of the cell itself is returned instead for neighbors out of bounds
pub fn get_neighbors(&self, idx: usize) -> [usize; 8] {
//returns the indices of the 8 directly neighboring cells. If the cell is on the edge, the index of the cell itself is returned
let mut neighbors = [0; 8];
let (row, col) = (idx / self.n_cols, idx % self.n_cols);
let (n_cols, n_rows) = (self.n_cols, self.n_rows);

//ugly but fast
//ugly, but seems to be the fastest way of doing it
neighbors[0] = if row > 0 && col > 0 { idx - n_cols - 1 } else { idx };
neighbors[1] = if row > 0 { idx - n_cols } else { idx };
neighbors[2] = if row > 0 && col < n_cols - 1 { idx - n_cols + 1 } else { idx };
Expand All @@ -116,7 +119,7 @@ impl<T> Grid<T> {

fn calculate_index(row: usize, col: usize, n_rows: usize, n_cols: usize) -> Option<usize> {
match (row.cmp(&n_rows), col.cmp(&n_cols)) {
(Ordering::Less, Ordering::Less) => Some(row * n_cols + col), //out of bounds
(Ordering::Less, Ordering::Less) => Some(row * n_cols + col),
_ => None //out of bounds
}
}
Expand Down
15 changes: 7 additions & 8 deletions jaguars/src/collision_detection/haz_prox_grid/grid_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use crate::geometry::geo_traits::{DistanceFrom, Shape};
use crate::geometry::primitives::aa_rectangle::AARectangle;
use crate::geometry::primitives::point::Point;

/// Generates a grid of equal sized square rectangles within a shape.
/// The number of cells is approximately equal to target_n_cells, but can be slightly more or less
pub fn generate(bbox: AARectangle, hazards: &[Hazard], target_n_cells: usize) -> Vec<AARectangle> {
//generates a grid of equal sized square cells in the shape.
//the number of cells is approximately equal to target_n_cells, but can be slightly more or less
assert!(bbox.area() > 0.0, "bbox has zero area");

let mut cells = vec![];
Expand Down Expand Up @@ -39,7 +39,7 @@ pub fn generate(bbox: AARectangle, hazards: &[Hazard], target_n_cells: usize) ->
}
}
if n_iters >= 25 {
debug!("grid generation is taking too long, aborting after 25 iterations ({} cells instead of target {})", cells.len(), target_n_cells);
debug!("grid generation is taking too long, stopping after 25 iterations ({} cells instead of target {})", cells.len(), target_n_cells);
break;
}

Expand All @@ -51,15 +51,14 @@ pub fn generate(bbox: AARectangle, hazards: &[Hazard], target_n_cells: usize) ->
}

match attempt {
Ordering::Equal => {
//just right
break;
}
//Close enough
Ordering::Equal => break,
//not enough cells, decrease their size
Ordering::Less => {
//not enough cells, decrease their size
correction_factor -= step_size;
cells.clear();
}
//too many cells, increase their size
Ordering::Greater => {
correction_factor += step_size;
cells.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ impl HazardProximityGrid {
let elements = cells.into_iter()
.map(|bbox| HPGCell::new(bbox, static_hazards))
.map(|cell| {
let centroid = cell.centroid();
(cell, centroid.into())
}).collect_vec();
let pos = cell.centroid();
(cell, pos)
})
.collect_vec();
Grid::new(elements)
};

Expand Down Expand Up @@ -58,7 +59,7 @@ impl HazardProximityGrid {
)
};

let mut b_fill = BoundaryFillGrid::new(seed_bbox, &self.grid);
let mut b_fill = BoundaryFillGrid::new(&self.grid, seed_bbox);

while let Some(next_dot_index) = b_fill.pop(&self.grid) {
let cell = self.grid.elements_mut()[next_dot_index].as_mut();
Expand All @@ -75,6 +76,7 @@ impl HazardProximityGrid {
}
}

//TODO: move this to an assertion check
debug_assert!(
{
let old_cells = self.grid.elements().clone();
Expand Down
6 changes: 4 additions & 2 deletions jaguars/src/collision_detection/haz_prox_grid/hpg_cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ use crate::geometry::primitives::circle::Circle;
use crate::geometry::primitives::point::Point;
use crate::N_QUALITIES;


/// Represents a cell in the Hazard Proximity Grid
#[derive(Clone, Debug)]
pub struct HPGCell {
bbox: AARectangle,
centroid: Point,
radius: f64,
///Proximity of closest hazard which is universally applicable (bin or item)
uni_haz_prox: (Proximity, HazardEntity),
///Proximity of universal static hazard_filters
///Proximity of universal static hazards
static_uni_haz_prox: (Proximity, HazardEntity),
///proximity of closest quality zone for each quality
qz_haz_prox: [Proximity; N_QUALITIES],
Expand Down Expand Up @@ -115,7 +117,7 @@ impl HPGCell {
pub fn register_hazard(&mut self, to_register: &Hazard) -> HPGCellUpdate {
let current_prox = self.universal_hazard_proximity().0;

//For dynamic hazard_filters, the surrogate poles are used to calculate the distance to the hazard (overestimation, but fast)
//For dynamic hazards, the surrogate poles are used to calculate the distance to the hazard (overestimation, but fast)
let haz_prox = match to_register.entity.position() {
GeoPosition::Interior => distance_to_surrogate_poles_border(self, &to_register.shape.surrogate().poles),
GeoPosition::Exterior => unreachable!("No implementation yet for dynamic exterior hazards")
Expand Down
9 changes: 7 additions & 2 deletions jaguars/src/collision_detection/hazard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ use crate::entities::placed_item::PlacedItemUID;
use crate::geometry::geo_enums::GeoPosition;
use crate::geometry::primitives::simple_polygon::SimplePolygon;

/// Defines a certain spatial constraint that affects the feasibility of placed items
/// Hazards are defined by a certain entity, have a shape and can be active or inactive
#[derive(Clone, Debug)]
pub struct Hazard {
/// The entity inducing the hazard
pub entity: HazardEntity,
/// The shape of the hazard
pub shape: Arc<SimplePolygon>,
/// Whether the hazard is currently active or not
pub active: bool,
}

Expand All @@ -32,7 +37,7 @@ pub enum HazardEntity {

impl HazardEntity {

/// Returns whether the entity induces an Interior or Exterior hazard
/// Whether the entity induces an Interior or Exterior hazard
pub fn position(&self) -> GeoPosition {
match self {
HazardEntity::PlacedItem(_) => GeoPosition::Interior,
Expand All @@ -42,7 +47,7 @@ impl HazardEntity {
}
}

/// Returns true if the hazard is dynamic in nature, i.e. it can be modified by the optimizer
/// True if the hazard is dynamic in nature, i.e. it can be modified by the optimizer
pub fn dynamic(&self) -> bool {
match self {
HazardEntity::PlacedItem(_) => true,
Expand Down
Loading

0 comments on commit 0c8c9e4

Please sign in to comment.