nextpnr/gui/quadtree.h
Catherine cd7f7c12f1 CMake: refactor architecture-specific build system parts.
Two user-visible changes were made:
* `-DUSE_RUST` is replaced with `-DBUILD_RUST`, by analogy with
  `-DBUILD_PYTHON`
* `-DCOVERAGE` was removed as it doesn't work with either modern GCC
  or Clang
2025-01-21 17:13:03 +00:00

423 lines
15 KiB
C++

/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Serge Bazanski <q3k@q3k.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef QUADTREE_H
#define QUADTREE_H
// This file implements a quad tree used for committing 2D axis aligned
// bounding boxes and then retrieving them by 2D point.
NEXTPNR_NAMESPACE_BEGIN
// A node of a QuadTree. Internal.
template <typename CoordinateT, typename ElementT> class QuadTreeNode
{
public:
class BoundingBox
{
friend class QuadTreeNode;
private:
CoordinateT x0_, y0_, x1_, y1_;
static constexpr float pinf = std::numeric_limits<CoordinateT>::infinity();
static constexpr float ninf = -std::numeric_limits<CoordinateT>::infinity();
public:
// Standard constructor for a given (x0,y0), (x1,y1) bounding box
//
// @param x0 x coordinate of top-left corner of box
// @param y0 y coordinate of top-left corner of box
// @param x1 x coordinate of bottom-right corner of box
// @param y1 y coordinate of bottom-right corner of box
BoundingBox(CoordinateT x0, CoordinateT y0, CoordinateT x1, CoordinateT y1) : x0_(x0), y0_(y0), x1_(x1), y1_(y1)
{
}
BoundingBox() : x0_(pinf), y0_(pinf), x1_(ninf), y1_(ninf) {}
// Whether a bounding box contains a given points.
// A point is defined to be in a bounding box when it's not lesser than
// the lower coordinate or greater than the higher coordinate, eg:
// A BoundingBox of x0: 20, y0: 30, x1: 100, y1: 130 fits the following
// points:
// [ (50, 50), (20, 50), (20, 30), (100, 130) ]
inline bool contains(CoordinateT x, CoordinateT y) const
{
if (x < x0_ || x > x1_)
return false;
if (y < y0_ || y > y1_)
return false;
return true;
}
// Sort the bounding box coordinates.
void fixup()
{
if (x1_ < x0_)
std::swap(x0_, x1_);
if (y1_ < y0_)
std::swap(y0_, y1_);
}
CoordinateT x0() const { return x0_; }
CoordinateT y0() const { return y0_; }
CoordinateT x1() const { return x1_; }
CoordinateT y1() const { return y1_; }
void setX0(CoordinateT v) { x0_ = v; }
void setY0(CoordinateT v) { y0_ = v; }
void setX1(CoordinateT v) { x1_ = v; }
void setY1(CoordinateT v) { y1_ = v; }
void clear()
{
x0_ = pinf;
y0_ = pinf;
x1_ = ninf;
y1_ = ninf;
}
CoordinateT w() const { return x1_ - x0_; }
CoordinateT h() const { return y1_ - y0_; }
};
private:
// A pair of Element and BoundingBox that contains it.
class BoundElement
{
friend class QuadTreeNode;
private:
BoundingBox bb_;
ElementT elem_;
public:
BoundElement(BoundingBox bb, ElementT elem) : bb_(bb), elem_(elem) {}
};
// The bounding box that this node describes.
BoundingBox bound_;
// How many elements should be contained in this node until it splits into
// sub-nodes.
size_t max_elems_;
// Four sub-nodes or nullptr if it hasn't split yet.
std::unique_ptr<QuadTreeNode<CoordinateT, ElementT>[]> children_ = nullptr;
// Coordinates of the split.
// Anything < split_x is west.
CoordinateT splitx_;
// Anything < split_y is north.
CoordinateT splity_;
// Elements contained directly within this node and not part of children
// nodes.
std::vector<BoundElement> elems_;
// Depth at which this node is - root is at 0, first level at 1, etc.
int depth_;
// Checks whether a given bounding box fits within this node - used for
// sanity checking on insertion.
// @param b bounding box to check
// @returns whether b fits in this node entirely
bool fits(const BoundingBox &b) const
{
if (b.x0_ < bound_.x0_ || b.x0_ > bound_.x1_) {
return false;
} else if (b.x1_ < bound_.x0_ || b.x1_ > bound_.x1_) {
return false;
} else if (b.y0_ < bound_.y0_ || b.y0_ > bound_.y1_) {
return false;
} else if (b.y1_ < bound_.y0_ || b.y1_ > bound_.y1_) {
return false;
}
return true;
}
// Used to describe one of 5 possible places an element can exist:
// - the node itself (THIS)
// - any of the 4 children nodes.
enum Quadrant
{
THIS_NODE = -1,
NW = 0,
NE = 1,
SW = 2,
SE = 3
};
// Finds the quadrant to which a bounding box should go (if the node
// is / were to be split).
// @param b bounding box to check
// @returns quadrant in which b belongs to if the node is were to be split
Quadrant quadrant(const BoundingBox &b) const
{
if (children_ == nullptr) {
return THIS_NODE;
}
bool west0 = b.x0_ < splitx_;
bool west1 = b.x1_ < splitx_;
bool north0 = b.y0_ < splity_;
bool north1 = b.y1_ < splity_;
if (west0 && west1 && north0 && north1)
return NW;
if (!west0 && !west1 && north0 && north1)
return NE;
if (west0 && west1 && !north0 && !north1)
return SW;
if (!west0 && !west1 && !north0 && !north1)
return SE;
return THIS_NODE;
}
// Checks whether this node should split.
bool should_split() const
{
// The node shouldn't split if it's not large enough to merit it.
if (elems_.size() < max_elems_)
return false;
// The node shouldn't split if its' level is too deep (this is true for
// 100k+ entries, where the amount of splits causes us to lose
// significant CPU time on traversing the tree, or worse yet causes a
// stack overflow).
if (depth_ > 5)
return false;
return true;
}
public:
// Standard constructor for node.
// @param b BoundingBox this node covers.
// @param depth depth at which this node is in the tree
// @max_elems how many elements should this node contain before it splits
QuadTreeNode(BoundingBox b, int depth, size_t max_elems = 4) : bound_(b), max_elems_(max_elems), depth_(depth) {}
// Disallow copies.
QuadTreeNode(const QuadTreeNode &other) = delete;
QuadTreeNode &operator=(const QuadTreeNode &other) = delete;
// Allow moves.
QuadTreeNode(QuadTreeNode &&other)
: bound_(other.bound_), max_elems_(other.max_elems_), children_(std::move(other.children_)),
splitx_(other.splitx_), splity_(other.splity_), elems_(std::move(other.elems_)), depth_(other.depth_)
{
other.children_ = nullptr;
}
QuadTreeNode &operator=(QuadTreeNode &&other)
{
if (this == &other)
return *this;
bound_ = other.bound_;
max_elems_ = other.max_elems_;
// children_ = other.max_children_;
children_ = other.children_;
splitx_ = other.splitx_;
splity_ = other.splity_;
elems_ = std::move(other.elems_);
depth_ = other.depth_;
other.children_ = nullptr;
return *this;
}
// Insert an element at a given bounding box.
bool insert(const BoundingBox &k, ElementT v)
{
// Fail early if this BB doesn't fit us at all.
if (!fits(k)) {
return false;
}
// Do we have children?
if (children_ != nullptr) {
// Put the element either recursively into a child if it fits
// entirely or keep it for ourselves if not.
auto quad = quadrant(k);
if (quad == THIS_NODE) {
elems_.push_back(BoundElement(k, std::move(v)));
} else {
return children_[quad].insert(k, std::move(v));
}
} else {
// No children and not about to have any.
if (!should_split()) {
elems_.push_back(BoundElement(k, std::move(v)));
return true;
}
// Splitting. Calculate the split point.
splitx_ = (bound_.x1_ - bound_.x0_) / 2 + bound_.x0_;
splity_ = (bound_.y1_ - bound_.y0_) / 2 + bound_.y0_;
// Create the new children.
children_ = decltype(children_)(new QuadTreeNode<CoordinateT, ElementT>[4]{
// Note: not using [NW] = QuadTreeNode because that seems to
// crash g++ 7.3.0.
/* NW */ QuadTreeNode<CoordinateT, ElementT>(BoundingBox(bound_.x0_, bound_.y0_, splitx_, splity_),
depth_ + 1, max_elems_),
/* NE */
QuadTreeNode<CoordinateT, ElementT>(BoundingBox(splitx_, bound_.y0_, bound_.x1_, splity_),
depth_ + 1, max_elems_),
/* SW */
QuadTreeNode<CoordinateT, ElementT>(BoundingBox(bound_.x0_, splity_, splitx_, bound_.y1_),
depth_ + 1, max_elems_),
/* SE */
QuadTreeNode<CoordinateT, ElementT>(BoundingBox(splitx_, splity_, bound_.x1_, bound_.y1_),
depth_ + 1, max_elems_),
});
// Move all elements to where they belong.
auto it = elems_.begin();
while (it != elems_.end()) {
auto quad = quadrant(it->bb_);
if (quad != THIS_NODE) {
// Move to one of the children.
if (!children_[quad].insert(it->bb_, std::move(it->elem_)))
return false;
// Delete from ourselves.
it = elems_.erase(it);
} else {
// Keep for ourselves.
it++;
}
}
// Insert the actual element now that we've split.
return insert(k, std::move(v));
}
return true;
}
// Dump a human-readable representation of the tree to stdout.
void dump(int level) const
{
for (int i = 0; i < level; i++)
printf(" ");
printf("loc: % 3d % 3d % 3d % 3d\n", bound_.x0_, bound_.y0_, bound_.x1_, bound_.y1_);
if (elems_.size() != 0) {
for (int i = 0; i < level; i++)
printf(" ");
printf("elems: %zu\n", elems_.size());
}
if (children_ != nullptr) {
for (int i = 0; i < level; i++)
printf(" ");
printf("children:\n");
children_[NW].dump(level + 1);
children_[NE].dump(level + 1);
children_[SW].dump(level + 1);
children_[SE].dump(level + 1);
}
}
// Return count of BoundingBoxes/Elements contained.
// @returns count of elements contained.
size_t size() const
{
size_t res = elems_.size();
if (children_ != nullptr) {
res += children_[NW].size();
res += children_[NE].size();
res += children_[SW].size();
res += children_[SE].size();
}
return res;
}
// Retrieve elements whose bounding boxes cover the given coordinates.
//
// @param x X coordinate of points to query.
// @param y Y coordinate of points to query.
// @returns vector of found bounding boxes
void get(CoordinateT x, CoordinateT y, std::vector<ElementT> &res) const
{
if (!bound_.contains(x, y))
return;
for (const auto &elem : elems_) {
const auto &bb = elem.bb_;
if (bb.contains(x, y)) {
res.push_back(elem.elem_);
}
}
if (children_ != nullptr) {
children_[NW].get(x, y, res);
children_[NE].get(x, y, res);
children_[SW].get(x, y, res);
children_[SE].get(x, y, res);
}
}
};
// User facing method to manage a quad tree.
//
// @param CoodinateT scalar type of the coordinate system - int, float, ...
// @param ElementT type of the contained element. Must be movable or copiable.
template <typename CoordinateT, typename ElementT> class QuadTree
{
private:
// Root of the tree.
QuadTreeNode<CoordinateT, ElementT> root_;
public:
// To let user create bounding boxes of the correct type.
// Bounding boxes are composed of two 2D points, which designate their
// top-left and bottom-right corners. All its' edges are axis aligned.
using BoundingBox = typename QuadTreeNode<CoordinateT, ElementT>::BoundingBox;
// Standard constructor.
//
// @param b Bounding box of the entire tree - all committed elements must
// fit within in.
QuadTree(BoundingBox b) : root_(b, 0) {}
// Inserts a new value at a given bounding box.e
// BoundingBoxes are not deduplicated - if two are pushed with the same
// coordinates, the first one will take precedence.
//
// @param k Bounding box at which to store value.
// @param v Value at a given bounding box.
// @returns Whether the insert was successful.
bool insert(BoundingBox k, ElementT v)
{
k.fixup();
return root_.insert(k, v);
}
// Dump a human-readable representation of the tree to stdout.
void dump() const { root_.dump(0); }
// Return count of BoundingBoxes/Elements contained.
// @returns count of elements contained.
size_t size() const { return root_.size(); }
// Retrieve elements whose bounding boxes cover the given coordinates.
//
// @param x X coordinate of points to query.
// @param y Y coordinate of points to query.
// @returns vector of found bounding boxes
std::vector<ElementT> get(CoordinateT x, CoordinateT y) const
{
std::vector<ElementT> res;
root_.get(x, y, res);
return res;
}
};
NEXTPNR_NAMESPACE_END
#endif