diff options
-rw-r--r-- | compat/LooseQuadtree-impl.h | 1523 | ||||
-rw-r--r-- | compat/LooseQuadtree.LICENSE | 22 | ||||
-rw-r--r-- | compat/LooseQuadtree.h | 118 | ||||
-rw-r--r-- | test/LooseQuadtreeTest.cpp | 729 |
4 files changed, 2392 insertions, 0 deletions
diff --git a/compat/LooseQuadtree-impl.h b/compat/LooseQuadtree-impl.h new file mode 100644 index 00000000..a8791a46 --- /dev/null +++ b/compat/LooseQuadtree-impl.h @@ -0,0 +1,1523 @@ +#ifndef LOOSEQUADTREE_LOOSEQUADTREE_IMPL_H +#define LOOSEQUADTREE_LOOSEQUADTREE_IMPL_H + +#include "LooseQuadtree.h" + +#include <array> +#include <cassert> +#include <cstddef> +#include <deque> +#include <forward_list> +#include <limits> +#include <map> +#include <memory> +#include <unordered_map> +#include <type_traits> +#include <vector> + +namespace loose_quadtree { +namespace detail { + + + +#define LQT_USE_OWN_ALLOCATOR + + +class BlocksAllocator { +public: + const static std::size_t kBlockAlign = alignof(long double); + const static std::size_t kBlockSize = 16384; + const static std::size_t kMaxAllowedAlloc = sizeof(void*) * 8; + + BlocksAllocator(); + ~BlocksAllocator(); + BlocksAllocator(const BlocksAllocator&) = delete; + BlocksAllocator& operator=(const BlocksAllocator&) = delete; + + void* Allocate(std::size_t object_size); + void Deallocate(void* p, std::size_t object_size); + void ReleaseFreeBlocks(); + template <typename T, typename... Args> + T* New(Args&&... args); + template <typename T> + void Delete(T* p); + +private: + using Block = std::aligned_storage<kBlockSize, kBlockAlign>::type; + + //template <std::size_t kSizeT> + //union Slot { + // Slot* next_empty_slot; + // std::array<char, kSizeT> data; + //}; + + using BlocksAndEmptySlots = std::map<Block*, std::size_t>; + + struct BlocksHead { + BlocksAndEmptySlots address_to_empty_slot_number; + void* first_empty_slot; + std::size_t slots_in_a_block_; + + BlocksHead(std::size_t object_size) { + first_empty_slot = nullptr; + slots_in_a_block_ = kBlockSize / object_size; + } + }; + + std::map<std::size_t, BlocksHead> size_to_blocks_; +}; + + +template <typename T> +struct BlocksAllocatorAdaptor { + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + template<typename U> + struct rebind {using other = BlocksAllocatorAdaptor<U>;}; + + BlocksAllocatorAdaptor(BlocksAllocator& allocator); + template <typename U> + BlocksAllocatorAdaptor(const BlocksAllocatorAdaptor<U>& other); + template <typename U> + BlocksAllocatorAdaptor& operator=(const BlocksAllocatorAdaptor<U>& other); + + pointer address(reference r) const {return &r;} + const_pointer address(const_reference r) const {return &r;} + size_type max_size() const {return std::numeric_limits<size_type>::max();} + template <typename U, typename... Args> + void construct(U* p, Args&&... args) { + new((void*)p) U(std::forward<Args>(args)...); + } + template <typename U> + void destroy(U* p) { + p->~U(); + } + + T* allocate(std::size_t n); + void deallocate(T* p, std::size_t n); + + BlocksAllocator* allocator_; +}; + + +BlocksAllocator::BlocksAllocator() {} + + +BlocksAllocator::~BlocksAllocator() { + for (auto& size_block_pair : size_to_blocks_) { + BlocksHead& blocks_head = size_block_pair.second; + for (auto& address_empty_pair : blocks_head.address_to_empty_slot_number) { + assert(address_empty_pair.second == blocks_head.slots_in_a_block_); + Block* block = address_empty_pair.first; + delete block; + } + } +} + + +void* BlocksAllocator::Allocate(std::size_t object_size) { +#ifdef LQT_USE_OWN_ALLOCATOR + if (object_size < sizeof(void*)) object_size = sizeof(void*); + assert(object_size <= kMaxAllowedAlloc); + auto size_blocks_it = size_to_blocks_.find(object_size); + if (size_blocks_it == size_to_blocks_.end()) { + size_blocks_it = size_to_blocks_.emplace(object_size, object_size).first; + } + BlocksHead& blocks_head = size_blocks_it->second; + assert(blocks_head.slots_in_a_block_ == kBlockSize / object_size); + if (blocks_head.first_empty_slot == nullptr) { + Block* new_block = new Block(); + std::size_t empties = blocks_head.slots_in_a_block_; + blocks_head.address_to_empty_slot_number.emplace(new_block, empties); + blocks_head.first_empty_slot = reinterpret_cast<void*>(new_block); + void* current_slot = reinterpret_cast<void*>(new_block); + empties--; + while (empties > 0) { + void* next_slot = + reinterpret_cast<void*>(reinterpret_cast<char*>(current_slot) + + object_size); + *reinterpret_cast<void**>(current_slot) = next_slot; + current_slot = next_slot; + empties--; + } + *reinterpret_cast<void**>(current_slot) = nullptr; + } + assert(blocks_head.first_empty_slot != nullptr); + void* slot = blocks_head.first_empty_slot; + blocks_head.first_empty_slot = *reinterpret_cast<void**>(slot); + auto address_empties_it = + blocks_head.address_to_empty_slot_number.upper_bound(reinterpret_cast<Block*>(slot)); + assert(address_empties_it != blocks_head.address_to_empty_slot_number.begin()); + address_empties_it--; + assert(address_empties_it->first <= reinterpret_cast<Block*>(slot) && + (std::size_t)(reinterpret_cast<char*>(slot) - + reinterpret_cast<char*>(address_empties_it->first)) < kBlockSize); + assert((std::size_t)(reinterpret_cast<char*>(slot) - + reinterpret_cast<char*>(address_empties_it->first)) % object_size == 0); + assert(address_empties_it->second > 0 && + address_empties_it->second <= blocks_head.slots_in_a_block_); + address_empties_it->second--; + return slot; +#else + return reinterpret_cast<void*>(new char[object_size]); +#endif +} + + +void BlocksAllocator::Deallocate(void* p, std::size_t object_size) { +#ifdef LQT_USE_OWN_ALLOCATOR + if (object_size < sizeof(void*)) object_size = sizeof(void*); + assert(object_size <= kMaxAllowedAlloc); + auto size_blocks_it = size_to_blocks_.find(object_size); + assert(size_blocks_it != size_to_blocks_.end()); + BlocksHead& blocks_head = size_blocks_it->second; + assert(blocks_head.slots_in_a_block_ == kBlockSize / object_size); + auto address_empties_it = + blocks_head.address_to_empty_slot_number.upper_bound(reinterpret_cast<Block*>(p)); + assert(address_empties_it != blocks_head.address_to_empty_slot_number.begin()); + address_empties_it--; + assert(address_empties_it->first <= reinterpret_cast<Block*>(p) && + (std::size_t)(reinterpret_cast<char*>(p) - + reinterpret_cast<char*>(address_empties_it->first)) < kBlockSize); + assert((std::size_t)(reinterpret_cast<char*>(p) - + reinterpret_cast<char*>(address_empties_it->first)) % object_size == 0); + assert(address_empties_it->second < blocks_head.slots_in_a_block_); + void* slot = p; + *reinterpret_cast<void**>(slot) = blocks_head.first_empty_slot; + blocks_head.first_empty_slot = slot; + address_empties_it->second++; + assert(address_empties_it->second > 0 && + address_empties_it->second <= blocks_head.slots_in_a_block_); +#else + (void)object_size; + delete[] reinterpret_cast<char*>(p); +#endif +} + + +void BlocksAllocator::ReleaseFreeBlocks() { + for (auto& size_block_pair : size_to_blocks_) { + BlocksHead& blocks_head = size_block_pair.second; + void** current = &blocks_head.first_empty_slot; + while (*current != nullptr) { + auto address_empties_it = + blocks_head.address_to_empty_slot_number.upper_bound(reinterpret_cast<Block*>(*current)); + assert(address_empties_it != blocks_head.address_to_empty_slot_number.begin()); + address_empties_it--; + assert(address_empties_it->first <= reinterpret_cast<Block*>(*current) && + (std::size_t)(reinterpret_cast<char*>(*current) - + reinterpret_cast<char*>(address_empties_it->first)) < kBlockSize); + assert((std::size_t)(reinterpret_cast<char*>(*current) - + reinterpret_cast<char*>(address_empties_it->first)) % size_block_pair.first == 0); + assert(address_empties_it->second > 0 && + address_empties_it->second <= blocks_head.slots_in_a_block_); + if (address_empties_it->second >= blocks_head.slots_in_a_block_) { + *current = **reinterpret_cast<void***>(current); + } + else { + current = *reinterpret_cast<void***>(current); + } + } + auto address_empties_it = blocks_head.address_to_empty_slot_number.begin(); + while (address_empties_it != blocks_head.address_to_empty_slot_number.end()) { + if (address_empties_it->second >= blocks_head.slots_in_a_block_) { + auto prev_address_empties_it = address_empties_it; + address_empties_it++; + blocks_head.address_to_empty_slot_number.erase(prev_address_empties_it); + } + else { + address_empties_it++; + } + } + } +} + + +template <typename T, typename... Args> +T* BlocksAllocator::New(Args&&... args) { + return new(Allocate(sizeof(T))) T(std::forward<Args>(args)...); +} + + +template <typename T> +void BlocksAllocator::Delete(T* p) { + p->~T(); + Deallocate(p, sizeof(T)); +} + + +template <typename T> +BlocksAllocatorAdaptor<T>::BlocksAllocatorAdaptor(BlocksAllocator& allocator) + : allocator_(&allocator) { +} + +template <typename T> +template <typename U> +BlocksAllocatorAdaptor<T>::BlocksAllocatorAdaptor(const BlocksAllocatorAdaptor<U>& other) + : allocator_(other.allocator_) { +} + +template <typename T> +template <typename U> +BlocksAllocatorAdaptor<T>& +BlocksAllocatorAdaptor<T>::operator=(const BlocksAllocatorAdaptor<U>& other) { + allocator_ = other.allocator_; +} + +template <typename T> +T* BlocksAllocatorAdaptor<T>::allocate(std::size_t n) { + if (sizeof(T) <= BlocksAllocator::kMaxAllowedAlloc && n == 1) { + return reinterpret_cast<T*>(allocator_->Allocate(sizeof(T))); + } + else { + return reinterpret_cast<T*>(new char[sizeof(T) * n]); + } +} + +template <typename T> +void BlocksAllocatorAdaptor<T>::deallocate(T* p, std::size_t n) { + if (sizeof(T) <= BlocksAllocator::kMaxAllowedAlloc && n == 1) { + allocator_->Deallocate(p, sizeof(T)); + } + else { + delete[] reinterpret_cast<char*>(p); + } +} + + + + + + +template <typename NumberT> +struct MakeDistance { using Type = typename std::make_unsigned<NumberT>::type;}; + +template <> +struct MakeDistance<float> { using Type = float;}; + +template <> +struct MakeDistance<double> { using Type = double;}; + +template <> +struct MakeDistance<long double> { using Type = long double;}; + + + +enum class ChildPosition { + kNone, + kTopLeft, + kTopRight, + kBottomRight, + kBottomLeft, +}; + + + +template <typename ObjectT> +struct TreeNode { + using Object = ObjectT; + using ObjectContainer = + std::forward_list<Object*, BlocksAllocatorAdaptor<Object*>>; + + TreeNode(BlocksAllocator& allocator) : + top_left(nullptr), top_right(nullptr), bottom_right(nullptr), + bottom_left(nullptr), objects(BlocksAllocatorAdaptor<Object*>(allocator)) + {} + + TreeNode<Object>* top_left; + TreeNode<Object>* top_right; + TreeNode<Object>* bottom_right; + TreeNode<Object>* bottom_left; + ObjectContainer objects; +}; + + + +template <typename NumberT, typename ObjectT> +class ForwardTreeTraversal { +public: + using Number = NumberT; + using Object = ObjectT; + + struct TreePosition { + TreePosition(const BoundingBox<Number>& _bbox, TreeNode<Object>* _node) : + bounding_box(_bbox), node(_node) { + current_child = ChildPosition::kNone; + } + + BoundingBox<Number> bounding_box; + TreeNode<Object>* node; + ChildPosition current_child; + }; + + ForwardTreeTraversal(); + void StartAt(TreeNode<Object>* root, const BoundingBox<Number>& root_bounds); + int GetDepth() const; ///< starting from 0 + TreeNode<Object>* GetNode() const; + const BoundingBox<Number>& GetNodeBoundingBox() const; + void GoTopLeft(); + void GoTopRight(); + void GoBottomRight(); + void GoBottomLeft(); + +protected: + TreePosition position_; + int depth_; +}; + + + +template <typename NumberT, typename ObjectT> +class FullTreeTraversal : public ForwardTreeTraversal<NumberT, ObjectT> { +public: + using Number = NumberT; + using Object = ObjectT; + using typename ForwardTreeTraversal<Number, Object>::TreePosition; + + void StartAt(TreeNode<Object>* root, const BoundingBox<Number>& root_bounds); + ChildPosition GetNodeCurrentChild() const; + void SetNodeCurrentChild(ChildPosition child_position); + void GoUp(); + void GoTopLeft(); + void GoTopRight(); + void GoBottomRight(); + void GoBottomLeft(); + +private: + using ForwardTreeTraversal<Number, Object>::position_; + using ForwardTreeTraversal<Number, Object>::depth_; + + std::vector<TreePosition> position_stack_; +}; + + + +} //detail + + + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +class + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query:: +Impl { +public: + enum class QueryType {kIntersects, kInside, kContains, kEndOfQuery}; + + Impl(); + void Acquire(typename LooseQuadtree<Number, Object, BoundingBoxExtractor>::Impl* quadtree, + const BoundingBox<Number>* query_region, QueryType query_type); + void Release(); + bool IsAvailable() const; + bool EndOfQuery() const; + Object* GetCurrent() const; + void Next(); + +private: + enum class FitType {kNoFit = 0, kPartialFit, kFreeRide}; + + bool CurrentObjectFits() const; + FitType CurrentNodeFits() const; + + typename LooseQuadtree<Number, Object, BoundingBoxExtractor>::Impl* quadtree_; + detail::FullTreeTraversal<Number, Object> traversal_; + typename detail::TreeNode<Object>::ObjectContainer::iterator object_iterator_; + typename detail::TreeNode<Object>::ObjectContainer::iterator object_iterator_before_; + BoundingBox<Number> query_region_; + QueryType query_type_; + int free_ride_from_level_; +}; + + + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +class + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +Impl { +public: + constexpr static int kInternalMinDepth = 4; + constexpr static int kInternalMaxDepth = (sizeof(long long) * 8 - 1) / 2; + constexpr static Number kMinimalObjectExtent = + std::is_integral<Number>::value ? 1 : + std::numeric_limits<Number>::min() * 16; + + Impl(); + ~Impl(); + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + bool Insert(Object* object); + bool Update(Object* object); + bool Remove(Object* object); + bool Contains(Object* object) const; + Query QueryIntersectsRegion(const BoundingBox<Number>& region); + Query QueryInsideRegion(const BoundingBox<Number>& region); + Query QueryContainsRegion(const BoundingBox<Number>& region); + const BoundingBox<Number>& GetBoundingBox() const; ///< loose sense bounds + int GetSize() const; + void Clear(); + void ForceCleanup(); + +private: + friend class Query::Impl; + using ObjectPointerContainer = + std::unordered_map<Object*, Object**, + std::hash<Object*>, std::equal_to<Object*>, + detail::BlocksAllocatorAdaptor<std::pair<Object *const, Object**>>>; + using QueryPoolContainer = + std::deque<typename LooseQuadtree<Number, Object, BoundingBoxExtractor>::Query::Impl>; + + void RecalculateMaximalDepth(); + void DeleteTree(); + Object** InsertIntoTree(Object* object); + typename Query::Impl* GetAvailableQueryFromPool(); + + detail::BlocksAllocator allocator_; + detail::TreeNode<Object>* root_; + BoundingBox<Number> bounding_box_; + ObjectPointerContainer object_pointers_; + int number_of_objects_; + int maximal_depth_; + detail::FullTreeTraversal<Number, Object> internal_traversal_; + QueryPoolContainer query_pool_; + int running_queries_; ///< queries which are opened and not at their end +}; + + + + + + +template <typename NumberT> +bool + BoundingBox<NumberT>:: +Intersects(const BoundingBox<Number>& other) const { + return !( + left + width <= other.left || other.left + other.width <= left || + top + height <= other.top || other.top + other.height <= top + ); +} + +template <typename NumberT> +bool + BoundingBox<NumberT>:: +Contains(const BoundingBox<Number>& other) const { + return left <= other.left && left + width >= other.left + other.width && + top <= other.top && top + height >= other.top + other.height; +} + +template <typename NumberT> +bool + BoundingBox<NumberT>:: +Contains(Number x, Number y) const { + return left <= x && x < left + width && + top <= y && y < top + height; +} + + + +template <typename NumberT, typename ObjectT> + detail::ForwardTreeTraversal<NumberT, ObjectT>:: +ForwardTreeTraversal() : + position_(BoundingBox<Number>(0, 0, 0, 0), nullptr), depth_(0) { +} + +template <typename NumberT, typename ObjectT> +void + detail::ForwardTreeTraversal<NumberT, ObjectT>:: +StartAt(TreeNode<Object>* root, const BoundingBox<Number>& root_bounds) { + position_.bounding_box = root_bounds; + position_.node = root; + depth_ = 0; +} + +template <typename NumberT, typename ObjectT> +int + detail::ForwardTreeTraversal<NumberT, ObjectT>:: +GetDepth() const { + return depth_; +} + +template <typename NumberT, typename ObjectT> +detail::TreeNode<ObjectT>* + detail::ForwardTreeTraversal<NumberT, ObjectT>:: +GetNode() const { + return position_.node; +} + +template <typename NumberT, typename ObjectT> +const BoundingBox<NumberT>& + detail::ForwardTreeTraversal<NumberT, ObjectT>:: +GetNodeBoundingBox() const { + return position_.bounding_box; +} + +template <typename NumberT, typename ObjectT> +void + detail::ForwardTreeTraversal<NumberT, ObjectT>:: +GoTopLeft() { + BoundingBox<Number>& bbox = position_.bounding_box; + bbox.width = (Number)((typename MakeDistance<Number>::Type)bbox.width / 2); + bbox.height = (Number)((typename MakeDistance<Number>::Type)bbox.height / 2); + position_.node = position_.node->top_left; + assert(position_.node != nullptr); + depth_++; +} + +template <typename NumberT, typename ObjectT> +void + detail::ForwardTreeTraversal<NumberT, ObjectT>:: +GoTopRight() { + BoundingBox<Number>& bbox = position_.bounding_box; + Number right = (Number)(bbox.left + bbox.width); + bbox.left = (Number)(bbox.left + + (Number)((typename MakeDistance<Number>::Type)bbox.width / 2)); + bbox.width = (Number)(right - bbox.left); + bbox.height = (Number)((typename MakeDistance<Number>::Type)bbox.height / 2); + position_.node = position_.node->top_right; + assert(position_.node != nullptr); + depth_++; +} + +template <typename NumberT, typename ObjectT> +void + detail::ForwardTreeTraversal<NumberT, ObjectT>:: +GoBottomRight() { + BoundingBox<Number>& bbox = position_.bounding_box; + Number right = (Number)(bbox.left + bbox.width); + bbox.left = (Number)(bbox.left + + (Number)((typename MakeDistance<Number>::Type)bbox.width / 2)); + bbox.width = (Number)(right - bbox.left); + Number bottom = (Number)(bbox.top + bbox.height); + bbox.top = (Number)(bbox.top + + (Number)((typename MakeDistance<Number>::Type)bbox.height / 2)); + bbox.height = (Number)(bottom - bbox.top); + position_.node = position_.node->bottom_right; + assert(position_.node != nullptr); + depth_++; +} + +template <typename NumberT, typename ObjectT> +void + detail::ForwardTreeTraversal<NumberT, ObjectT>:: +GoBottomLeft() { + BoundingBox<Number>& bbox = position_.bounding_box; + bbox.width = (Number)((typename MakeDistance<Number>::Type)bbox.width / 2); + Number bottom = (Number)(bbox.top + bbox.height); + bbox.top = (Number)(bbox.top + + (Number)((typename MakeDistance<Number>::Type)bbox.height / 2)); + bbox.height = (Number)(bottom - bbox.top); + position_.node = position_.node->bottom_left; + assert(position_.node != nullptr); + depth_++; +} + + + +template <typename NumberT, typename ObjectT> +void + detail::FullTreeTraversal<NumberT, ObjectT>:: +StartAt(TreeNode<Object>* root, const BoundingBox<Number>& root_bounds) { + ForwardTreeTraversal<Number, Object>::StartAt(root, root_bounds); + position_.current_child = ChildPosition::kNone; + position_stack_.clear(); +} + +template <typename NumberT, typename ObjectT> +detail::ChildPosition + detail::FullTreeTraversal<NumberT, ObjectT>:: +GetNodeCurrentChild() const { + return position_.current_child; +} + +template <typename NumberT, typename ObjectT> +void + detail::FullTreeTraversal<NumberT, ObjectT>:: +SetNodeCurrentChild(ChildPosition child_position) { + position_.current_child = child_position; +} + +template <typename NumberT, typename ObjectT> +void + detail::FullTreeTraversal<NumberT, ObjectT>:: +GoTopLeft() { + assert((size_t)depth_ == position_stack_.size()); + position_.current_child = ChildPosition::kTopLeft; + position_stack_.emplace_back(position_); + ForwardTreeTraversal<Number, Object>::GoTopLeft(); + position_.current_child = ChildPosition::kNone; +} + +template <typename NumberT, typename ObjectT> +void + detail::FullTreeTraversal<NumberT, ObjectT>:: +GoTopRight() { + assert((size_t)depth_ == position_stack_.size()); + position_.current_child = ChildPosition::kTopRight; + position_stack_.emplace_back(position_); + ForwardTreeTraversal<Number, Object>::GoTopRight(); + position_.current_child = ChildPosition::kNone; +} + +template <typename NumberT, typename ObjectT> +void + detail::FullTreeTraversal<NumberT, ObjectT>:: +GoBottomRight() { + assert((size_t)depth_ == position_stack_.size()); + position_.current_child = ChildPosition::kBottomRight; + position_stack_.emplace_back(position_); + ForwardTreeTraversal<Number, Object>::GoBottomRight(); + position_.current_child = ChildPosition::kNone; +} + +template <typename NumberT, typename ObjectT> +void + detail::FullTreeTraversal<NumberT, ObjectT>:: +GoBottomLeft() { + assert((size_t)depth_ == position_stack_.size()); + position_.current_child = ChildPosition::kBottomLeft; + position_stack_.emplace_back(position_); + ForwardTreeTraversal<Number, Object>::GoBottomLeft(); + position_.current_child = ChildPosition::kNone; +} + +template <typename NumberT, typename ObjectT> +void + detail::FullTreeTraversal<NumberT, ObjectT>:: +GoUp() { + assert((size_t)depth_ == position_stack_.size() && depth_ > 0); + position_ = position_stack_.back(); + depth_--; + position_stack_.pop_back(); +} + + + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query::Impl:: +Impl() : quadtree_(nullptr), query_region_(0,0,0,0), + query_type_(QueryType::kEndOfQuery), + free_ride_from_level_(LooseQuadtree<Number, Object, BoundingBoxExtractor>::Impl::kInternalMaxDepth) { +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +void + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query::Impl:: +Acquire(typename LooseQuadtree<Number, Object, BoundingBoxExtractor>::Impl* quadtree, + const BoundingBox<Number>* query_region, QueryType query_type) { + assert(IsAvailable()); + assert(query_type != QueryType::kEndOfQuery); + quadtree_ = quadtree; + query_region_ = *query_region; + query_type_ = query_type; + free_ride_from_level_ = + LooseQuadtree<Number, Object, BoundingBoxExtractor>::Impl::kInternalMaxDepth; + if (quadtree->root_ == nullptr) { + query_type_ = QueryType::kEndOfQuery; + } + else { + quadtree_->running_queries_++; + traversal_.StartAt(quadtree->root_, quadtree->bounding_box_); + object_iterator_ = traversal_.GetNode()->objects.before_begin(); + Next(); + } +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +void + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query::Impl:: +Release() { + assert(!IsAvailable()); + quadtree_ = nullptr; +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query::Impl:: +IsAvailable() const { + return quadtree_ == nullptr; +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query::Impl:: +EndOfQuery() const { + assert(!IsAvailable()); + return query_type_ == QueryType::kEndOfQuery; +} + + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +ObjectT* + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query::Impl:: +GetCurrent() const { + assert(!IsAvailable()); + assert(!EndOfQuery()); + return *object_iterator_; +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +void + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query::Impl:: +Next() { + assert(!IsAvailable()); + assert(!EndOfQuery()); + do { + object_iterator_before_ = object_iterator_; + object_iterator_++; + if (object_iterator_ == traversal_.GetNode()->objects.end()) { + do { + switch (traversal_.GetNodeCurrentChild()) { + case detail::ChildPosition::kNone: + if (traversal_.GetNode()->top_left == nullptr) { + traversal_.SetNodeCurrentChild(detail::ChildPosition::kTopLeft); + continue; + } + else { + traversal_.GoTopLeft(); + } + break; + case detail::ChildPosition::kTopLeft: + if (traversal_.GetNode()->top_right == nullptr) { + traversal_.SetNodeCurrentChild(detail::ChildPosition::kTopRight); + continue; + } + else { + traversal_.GoTopRight(); + } + break; + case detail::ChildPosition::kTopRight: + if (traversal_.GetNode()->bottom_right == nullptr) { + traversal_.SetNodeCurrentChild(detail::ChildPosition::kBottomRight); + continue; + } + else { + traversal_.GoBottomRight(); + } + break; + case detail::ChildPosition::kBottomRight: + if (traversal_.GetNode()->bottom_left == nullptr) { + traversal_.SetNodeCurrentChild(detail::ChildPosition::kBottomLeft); + continue; + } + else { + traversal_.GoBottomLeft(); + } + break; + case detail::ChildPosition::kBottomLeft: + +#ifndef NDEBUG + int running_queries = 0; + for (auto& query : quadtree_->query_pool_) { + if (!query.IsAvailable() && !query.EndOfQuery()) running_queries++; + } + assert(running_queries == quadtree_->running_queries_); +#endif + + //only run this if no parallel queries are running + if (traversal_.GetDepth() > quadtree_->maximal_depth_ && + quadtree_->running_queries_ == 1) { + typename detail::TreeNode<Object>::ObjectContainer& objects = + traversal_.GetNode()->objects; + auto iterator = objects.begin(); + while (iterator != objects.end()) { + if (*iterator != nullptr) { + quadtree_->Update(*iterator); + assert(*iterator == nullptr); + } + iterator++; + } + objects.clear(); + } + + if (traversal_.GetDepth() > 0) { + bool remove_node = (traversal_.GetNode()->objects.empty() && + traversal_.GetNode()->top_left == nullptr && + traversal_.GetNode()->top_right == nullptr && + traversal_.GetNode()->bottom_right == nullptr && + traversal_.GetNode()->bottom_left == nullptr); + detail::TreeNode<Object>* node = traversal_.GetNode(); + traversal_.GoUp(); + + // if the node is empty no other queries can be invalidated by deleting + if (remove_node) { + switch (traversal_.GetNodeCurrentChild()) { + case detail::ChildPosition::kTopLeft: + traversal_.GetNode()->top_left = nullptr; + break; + case detail::ChildPosition::kTopRight: + traversal_.GetNode()->top_right = nullptr; + break; + case detail::ChildPosition::kBottomRight: + traversal_.GetNode()->bottom_right = nullptr; + break; + case detail::ChildPosition::kBottomLeft: + traversal_.GetNode()->bottom_left = nullptr; + break; + case detail::ChildPosition::kNone: + assert(false); + } + quadtree_->allocator_.Delete(node); + } + + if (free_ride_from_level_ == traversal_.GetDepth() + 1) { + free_ride_from_level_ = + LooseQuadtree<Number, Object, + BoundingBoxExtractor>::Impl::kInternalMaxDepth; + } + continue; + } + else { + // if the root is empty no other queries can be invalidated by deleting + if (traversal_.GetNode()->objects.empty() && + traversal_.GetNode()->top_left == nullptr && + traversal_.GetNode()->top_right == nullptr && + traversal_.GetNode()->bottom_right == nullptr && + traversal_.GetNode()->bottom_left == nullptr) { + assert(traversal_.GetNode() == quadtree_->root_); + assert(quadtree_->GetSize() == 0); + assert(quadtree_->object_pointers_.size() == 0); + quadtree_->allocator_.Delete(quadtree_->root_); + quadtree_->root_ = nullptr; + quadtree_->bounding_box_ = BoundingBox<Number>(0,0,0,0); + } + + quadtree_->running_queries_--; + query_type_ = QueryType::kEndOfQuery; + return; + } + break; + } + assert(traversal_.GetNodeCurrentChild() == detail::ChildPosition::kNone); + { + FitType fit = FitType::kPartialFit; + if (traversal_.GetDepth() >= free_ride_from_level_ || + (fit = CurrentNodeFits()) != FitType::kNoFit) { + if (fit == FitType::kFreeRide) { + free_ride_from_level_ = traversal_.GetDepth(); + } + } + else { + traversal_.SetNodeCurrentChild(detail::ChildPosition::kBottomLeft); + continue; + } + } + object_iterator_ = traversal_.GetNode()->objects.before_begin(); + break; + } while (true); + } + else if (*object_iterator_ == nullptr) { + // other queries also cannot stop on an empty object, + // so we are not invalidating any iterators/queries with this + traversal_.GetNode()->objects.erase_after(object_iterator_before_); + object_iterator_ = object_iterator_before_; + } + else { + if (traversal_.GetDepth() >= free_ride_from_level_ || + CurrentObjectFits()) { + break; + } + } + } while (true); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query::Impl:: +CurrentObjectFits() const { + BoundingBox<Number> object_bounds(0,0,0,0); + BoundingBoxExtractor::ExtractBoundingBox(GetCurrent(), &object_bounds); + switch (query_type_) { + case QueryType::kIntersects: + return query_region_.Intersects(object_bounds); + case QueryType::kInside: + return query_region_.Contains(object_bounds); + case QueryType::kContains: + return object_bounds.Contains(query_region_); + case QueryType::kEndOfQuery: + assert(false); + } + return false; +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +auto + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query::Impl:: +CurrentNodeFits() const -> FitType { + const BoundingBox<Number>& node_bounds = traversal_.GetNodeBoundingBox(); + BoundingBox<Number> extended_bounds = node_bounds; + Number half_width = + (Number)((typename detail::MakeDistance<Number>::Type)node_bounds.width / 2); + Number half_height = + (Number)((typename detail::MakeDistance<Number>::Type)node_bounds.height / 2); + extended_bounds.width = (Number)(extended_bounds.width * 2); + extended_bounds.height = (Number)(extended_bounds.height * 2); + extended_bounds.left = (Number)(extended_bounds.left - half_width); + extended_bounds.top = (Number)(extended_bounds.top - half_height); + switch (query_type_) { + case QueryType::kIntersects: + if (!query_region_.Intersects(extended_bounds)) { + return FitType::kNoFit; + } + else if (query_region_.Contains(node_bounds)) { + return FitType::kFreeRide; + } + return FitType::kPartialFit; + case QueryType::kInside: + if (!query_region_.Intersects(node_bounds)) { + return FitType::kNoFit; + } + else if (query_region_.Contains(extended_bounds)) { + return FitType::kFreeRide; + } + return FitType::kPartialFit; + case QueryType::kContains: + if (!extended_bounds.Contains(query_region_)) { + return FitType::kNoFit; + } + return FitType::kPartialFit; + case QueryType::kEndOfQuery: + assert(false); + } + return FitType::kNoFit; +} + + + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +Impl() : + root_(nullptr), bounding_box_(0, 0, 0, 0), + object_pointers_(64, std::hash<Object*>(), std::equal_to<Object*>(), + detail::BlocksAllocatorAdaptor<std::pair<const Object*, Object**>>(allocator_)), + number_of_objects_(0), maximal_depth_(kInternalMinDepth), + running_queries_(0) { + assert(maximal_depth_ < kInternalMaxDepth); + //object_pointers_.reserve(64); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +~Impl() { + DeleteTree(); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +Insert(Object* object) { + bool was_removed = Remove(object); + Object** place = InsertIntoTree(object); + object_pointers_.emplace(object, place); + number_of_objects_++; + RecalculateMaximalDepth(); + return !was_removed; +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +Update(Object* object) { + return !Insert(object); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +Remove(Object* object) { + auto it = object_pointers_.find(object); + if (it != object_pointers_.end()) { + assert(*(it->second) == it->first); + *(it->second) = nullptr; + object_pointers_.erase(it); + number_of_objects_--; + RecalculateMaximalDepth(); + return true; + } + return false; +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +Contains(Object* object) const { + return object_pointers_.find(object) != object_pointers_.end(); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +auto + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +QueryIntersectsRegion(const BoundingBox<Number>& region) -> Query { + typename Query::Impl* query_impl = GetAvailableQueryFromPool(); + query_impl->Acquire(this, ®ion, Query::Impl::QueryType::kIntersects); + return Query(query_impl); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +auto + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +QueryInsideRegion(const BoundingBox<Number>& region) -> Query { + typename Query::Impl* query_impl = GetAvailableQueryFromPool(); + query_impl->Acquire(this, ®ion, Query::Impl::QueryType::kInside); + return Query(query_impl); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +auto + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +QueryContainsRegion(const BoundingBox<Number>& region) -> Query { + typename Query::Impl* query_impl = GetAvailableQueryFromPool(); + query_impl->Acquire(this, ®ion, Query::Impl::QueryType::kContains); + return Query(query_impl); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +const BoundingBox<NumberT>& + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +GetBoundingBox() const { + return bounding_box_; +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +void + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +ForceCleanup() { + Query query = QueryIntersectsRegion(bounding_box_); + while (!query.EndOfQuery()) { + query.Next(); + } + allocator_.ReleaseFreeBlocks(); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +int + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +GetSize() const { + return number_of_objects_; +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +void + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +Clear() { + DeleteTree(); +} + + + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +void + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +RecalculateMaximalDepth() { + do { + if (maximal_depth_ < kInternalMaxDepth && + number_of_objects_ > 1ll << (maximal_depth_ << 1)) { + maximal_depth_++; + } + else if (maximal_depth_ > kInternalMinDepth && + number_of_objects_ <= 1ll << ((maximal_depth_ - 1) << 1)) { + maximal_depth_--; + } + else { + break; + } + } while (true); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +void + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +DeleteTree() { + object_pointers_.clear(); + detail::FullTreeTraversal<Number, Object>& trav = internal_traversal_; + trav.StartAt(root_, bounding_box_); + while (root_ != nullptr) { + assert(trav.GetDepth() >= 0 && trav.GetDepth() <= kInternalMaxDepth); + detail::TreeNode<Object>* node = trav.GetNode(); + if (node->top_left != nullptr) { + trav.GoTopLeft(); + } + else if (node->top_right != nullptr) { + trav.GoTopRight(); + } + else if (node->bottom_right != nullptr) { + trav.GoBottomRight(); + } + else if (node->bottom_left != nullptr) { + trav.GoBottomLeft(); + } + else { + if (trav.GetDepth() > 0) { + trav.GoUp(); + switch (trav.GetNodeCurrentChild()) { + case detail::ChildPosition::kNone: + assert(false); + break; + case detail::ChildPosition::kTopLeft: + assert(node == trav.GetNode()->top_left); + trav.GetNode()->top_left = nullptr; + break; + case detail::ChildPosition::kTopRight: + assert(node == trav.GetNode()->top_right); + trav.GetNode()->top_right = nullptr; + break; + case detail::ChildPosition::kBottomRight: + assert(node == trav.GetNode()->bottom_right); + trav.GetNode()->bottom_right = nullptr; + break; + case detail::ChildPosition::kBottomLeft: + assert(node == trav.GetNode()->bottom_left); + trav.GetNode()->bottom_left = nullptr; + break; + } + allocator_.Delete(node); + } + else { + assert(node == root_); + allocator_.Delete(root_); + root_ = nullptr; + } + } + } + + bounding_box_ = BoundingBox<Number>(0, 0, 0, 0); + number_of_objects_ = 0; + maximal_depth_ = kInternalMinDepth; +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +ObjectT** + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +InsertIntoTree(Object* object) { + BoundingBox<Number> object_bounds(0,0,0,0); + BoundingBoxExtractor::ExtractBoundingBox(object, &object_bounds); + assert(object_bounds.width >= 0); + assert(object_bounds.height >= 0); + assert(object_bounds.left <= object_bounds.left + object_bounds.width); + assert(object_bounds.top <= object_bounds.top + object_bounds.height); + Number maximal_object_extent = object_bounds.width >= object_bounds.height ? + object_bounds.width : object_bounds.height; + if (maximal_object_extent < kMinimalObjectExtent) { + maximal_object_extent = kMinimalObjectExtent; + } + Number object_center_x = (Number)(object_bounds.left + + (Number)((typename detail::MakeDistance<Number>::Type)object_bounds.width / 2)); + Number object_center_y = (Number)(object_bounds.top + + (Number)((typename detail::MakeDistance<Number>::Type)object_bounds.height / 2)); + + if (root_ != nullptr) { + assert(number_of_objects_ >= 0); + assert(bounding_box_.width > 0); + assert(bounding_box_.width == bounding_box_.height); + + int depth_increase = 0; + while (!bounding_box_.Contains(object_center_x, object_center_y) || + maximal_object_extent > bounding_box_.width) { + Number previous_size = bounding_box_.width; + bounding_box_.width = (Number)(bounding_box_.width * 2); + bounding_box_.height = bounding_box_.width; + Number previous_half = + (Number)((typename detail::MakeDistance<Number>::Type)previous_size / 2); + Number bb_center_x = (Number)(bounding_box_.left + previous_half); + Number bb_center_y = (Number)(bounding_box_.top + previous_half); + detail::TreeNode<Object>* old_root = root_; + root_ = allocator_.New<detail::TreeNode<Object>>(allocator_); + if (object_center_x <= bb_center_x) { + bounding_box_.left = (Number)(bounding_box_.left - previous_size); + if (object_center_y <= bb_center_y) { + bounding_box_.top = (Number)(bounding_box_.top - previous_size); + root_->bottom_right = old_root; + } + else { + root_->top_right = old_root; + } + } + else { + if (object_center_y <= bb_center_y) { + bounding_box_.top = (Number)(bounding_box_.top - previous_size); + root_->bottom_left = old_root; + } + else { + root_->top_left = old_root; + } + } + depth_increase++; + assert(depth_increase < kInternalMaxDepth); + (void)depth_increase; + assert(bounding_box_.left < bounding_box_.left + bounding_box_.width); + assert(bounding_box_.top < bounding_box_.top + bounding_box_.height); + // If this happens with integral types you are close to get out of bounds + // The bounding box of things should be at least 1/8 of the total interval spanned + assert(!std::is_integral<Number>::value || + bounding_box_.width < std::numeric_limits<Number>::max() / 8 * 7); + assert(!std::is_integral<Number>::value || + bounding_box_.height < std::numeric_limits<Number>::max() / 8 * 7); + } + + detail::ForwardTreeTraversal<Number, Object> trav; + trav.StartAt(root_, bounding_box_); + do { + const BoundingBox<Number>& node_bounds = trav.GetNodeBoundingBox(); + assert(node_bounds.Contains(object_center_x, object_center_y)); + Number maximal_bb_extent = + node_bounds.width >= node_bounds.height ? + node_bounds.width : node_bounds.height; + Number half_bb_extent = + (Number)((typename detail::MakeDistance<Number>::Type)maximal_bb_extent / 2); + assert(maximal_object_extent <= maximal_bb_extent); + + if (maximal_object_extent > half_bb_extent || + trav.GetDepth() >= maximal_depth_) { + break; + } + + Number node_center_x = (Number)(node_bounds.left + + (Number)((typename detail::MakeDistance<Number>::Type)node_bounds.width / 2)); + Number node_center_y = (Number)(node_bounds.top + + (Number)((typename detail::MakeDistance<Number>::Type)node_bounds.height / 2)); + + detail::TreeNode<Object>** direction; + if (object_center_x < node_center_x) { + if (object_center_y < node_center_y) { + direction = &trav.GetNode()->top_left; + } + else { + direction = &trav.GetNode()->bottom_left; + } + } + else { + if (object_center_y < node_center_y) { + direction = &trav.GetNode()->top_right; + } + else { + direction = &trav.GetNode()->bottom_right; + } + } + + if (*direction == nullptr) { + *direction = allocator_.New<detail::TreeNode<Object>>(allocator_); + } + + if (*direction == trav.GetNode()->top_left) { + trav.GoTopLeft(); + } + else if (*direction == trav.GetNode()->top_right) { + trav.GoTopRight(); + } + else if (*direction == trav.GetNode()->bottom_right) { + trav.GoBottomRight(); + } + else { + assert(*direction == trav.GetNode()->bottom_left); + trav.GoBottomLeft(); + } + } while (true); + +#ifndef NDEBUG + BoundingBox<Number> effective_bounds = trav.GetNodeBoundingBox(); + Number half_width = + (Number)((typename detail::MakeDistance<Number>::Type)effective_bounds.width / 2); + Number half_height = + (Number)((typename detail::MakeDistance<Number>::Type)effective_bounds.height / 2); + effective_bounds.width = (Number)(effective_bounds.width * 2); + effective_bounds.height = (Number)(effective_bounds.height * 2); + effective_bounds.left = (Number)(effective_bounds.left - half_width); + effective_bounds.top = (Number)(effective_bounds.top - half_height); + assert(effective_bounds.Contains(object_bounds)); +#endif + + typename detail::TreeNode<Object>::ObjectContainer& objects = + trav.GetNode()->objects; + if (!objects.empty() && objects.front() == nullptr) { + objects.front() = object; + } + else { + objects.emplace_front(object); + } + return &objects.front(); + } + else { + assert(number_of_objects_ == 0); + { + bounding_box_.width = + (Number)((typename detail::MakeDistance<Number>::Type)maximal_object_extent * 2 * 7 / 8); + bounding_box_.height = bounding_box_.width; + Number extent_half = + (Number)((typename detail::MakeDistance<Number>::Type)bounding_box_.width / 2); + bounding_box_.left = (Number)(object_center_x - extent_half); + bounding_box_.top = (Number)(object_center_y - extent_half); + assert(bounding_box_.left < bounding_box_.left + bounding_box_.width); + assert(bounding_box_.top < bounding_box_.top + bounding_box_.height); + } + root_ = allocator_.New<detail::TreeNode<Object>>(allocator_); + root_->objects.emplace_front(object); + return &root_->objects.front(); + } +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +auto + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Impl:: +GetAvailableQueryFromPool() -> typename Query::Impl* { + for (auto it = query_pool_.begin(); it != query_pool_.end(); it++) { + if (it->IsAvailable()) { + return &*it; + } + } + query_pool_.emplace_back(); + assert(query_pool_.back().IsAvailable()); + return &query_pool_.back(); +} + + + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +Insert(Object* object) { + return impl_.Insert(object); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +Update(Object* object) { + return impl_.Update(object); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +Remove(Object* object) { + return impl_.Remove(object); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +Contains(Object* object) const { + return impl_.Contains(object); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +auto + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +QueryIntersectsRegion(const BoundingBox<Number>& region) -> Query { + return impl_.QueryIntersectsRegion(region); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +auto + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +QueryInsideRegion(const BoundingBox<Number>& region) -> Query { + return impl_.QueryInsideRegion(region); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +auto + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +QueryContainsRegion(const BoundingBox<Number>& region) -> Query { + return impl_.QueryContainsRegion(region); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +const BoundingBox<NumberT>& + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +GetLooseBoundingBox() const { + return impl_.GetBoundingBox(); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +void + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +ForceCleanup() { + impl_.ForceCleanup(); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +int + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +GetSize() const { + return impl_.GetSize(); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +IsEmpty() const { + return impl_.GetSize() == 0; +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +void + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>:: +Clear() { + impl_.Clear(); +} + + + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query:: +Query(Impl* pimpl) : pimpl_(pimpl) { + assert(pimpl_ != nullptr); + assert(!pimpl_->IsAvailable()); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query:: +~Query() { + if (pimpl_ != nullptr) { + pimpl_->Release(); + assert(pimpl_->IsAvailable()); + pimpl_ = nullptr; + } +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query:: +Query(Query&& other) : pimpl_(other.pimpl_) { + other.pimpl_ = nullptr; +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +auto + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query:: +operator=(Query&& other) -> Query& { + this->~Query(); + pimpl_ = other.pimpl_; + other.pimpl_ = nullptr; + return *this; +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +bool + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query:: +EndOfQuery() const { + return pimpl_->EndOfQuery(); +} + + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +ObjectT* + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query:: +GetCurrent() const { + return pimpl_->GetCurrent(); +} + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +void + LooseQuadtree<NumberT, ObjectT, BoundingBoxExtractorT>::Query:: +Next() { + pimpl_->Next(); +} + + + +} //loose_quadtree + +#endif //LOOSEQUADTREE_LOOSEQUADTREE_IMPL_H diff --git a/compat/LooseQuadtree.LICENSE b/compat/LooseQuadtree.LICENSE new file mode 100644 index 00000000..4edbdc84 --- /dev/null +++ b/compat/LooseQuadtree.LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Zoltán Gera + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/compat/LooseQuadtree.h b/compat/LooseQuadtree.h new file mode 100644 index 00000000..a27c482d --- /dev/null +++ b/compat/LooseQuadtree.h @@ -0,0 +1,118 @@ +#ifndef LOOSEQUADTREE_LOOSEQUADTREE_H +#define LOOSEQUADTREE_LOOSEQUADTREE_H + +/** + * LooseQuadtree written by Zozo + * use freely under MIT license + * + * Loose quadtree (unlike normal quadtrees which are for points only) is + * a region tree designed to store bounding boxes effectively + * See boost::geometry::index::rtree for a more advanced, general solution! + * + * This implementation features: + * - Fully adaptive behavior, adjusts its bounds, height and memory on demand + * - Every object will be stored on the level to which its size corresponds to + * - Gives theoretically optimal search results (see previous) + * - Uses tree structure instead of hashed (smaller memory footprint, cache friendly) + * - Uses as much data in-place as it can (by using its own allocator) + * - Allocates memory in big chunks + * - Uses axis-aligned bounding boxes for calculations + * - Uses left-top-width-height bounds for better precision (no right-bottom) + * - Uses left-top closed right-bottom open interval logic (for integral types) + * - Uses X-towards-right Y-towards-bottom screen-like coordinate system + * - It is suitable for both floating- and fixed-point logic + * - This library is not thread-safe but multiple queries can be run at once + * + * Generic parameters are: + * - NumberT generic number type allows its floating- and fixed-point usage + * - ObjectT* only pointer is stored, no object copying is done, not an inclusive container + * - BoundingBoxExtractorT allows using your own bounding box type/source, needs + * BoundingBoxExtractor::ExtractBoundingBox(ObjectT* in, BoundingBox<Number>* out) implemented + */ + + + +namespace loose_quadtree { + + + +template <typename NumberT> +struct BoundingBox { + using Number = NumberT; + + BoundingBox(Number _left, Number _top, Number _width, Number _height) : + left(_left), top(_top), width(_width), height(_height) {} + bool Intersects(const BoundingBox<Number>& other) const ; + bool Contains(const BoundingBox<Number>& other) const; + bool Contains(Number x, Number y) const; + + Number left; + Number top; + Number width; + Number height; +}; + + + +template <typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> +class LooseQuadtree { +public: + using Number = NumberT; + using Object = ObjectT; + using BoundingBoxExtractor = BoundingBoxExtractorT; + +private: + class Impl; + +public: + class Query { + public: + ~Query(); + Query() = delete; + Query(const Query&) = delete; + Query& operator=(const Query&) = delete; + Query(Query&&); + Query& operator=(Query&&); + + bool EndOfQuery() const; + Object* GetCurrent() const; + void Next(); + + private: + friend class LooseQuadtree<Number, Object, BoundingBoxExtractor>::Impl; + class Impl; + Query(Impl* pimpl); + Impl* pimpl_; + }; + + LooseQuadtree() {} + ~LooseQuadtree() {} + LooseQuadtree(const LooseQuadtree&) = delete; + LooseQuadtree& operator=(const LooseQuadtree&) = delete; + + bool Insert(Object* object); ///< true if it was inserted (else updated) + bool Update(Object* object); ///< true if it was updated (else inserted) + bool Remove(Object* object); ///< true if it was removed + bool Contains(Object* object) const; ///< true if object is in tree + Query QueryIntersectsRegion(const BoundingBox<Number>& region); + Query QueryInsideRegion(const BoundingBox<Number>& region); + Query QueryContainsRegion(const BoundingBox<Number>& region); + const BoundingBox<Number>& GetLooseBoundingBox() const; + ///< double its size to get a bounding box including everything contained for sure + int GetSize() const; + bool IsEmpty() const; + void Clear(); + void ForceCleanup(); ///< does a full data structure and memory cleanup + ///< cleanup is semi-automatic during queries so you needn't call this normally + +private: + Impl impl_; +}; + + + +} //loose_quadtree + +#include "LooseQuadtree-impl.h" + +#endif //LOOSEQUADTREE_LOOSEQUADTREE_H diff --git a/test/LooseQuadtreeTest.cpp b/test/LooseQuadtreeTest.cpp new file mode 100644 index 00000000..ca1e935d --- /dev/null +++ b/test/LooseQuadtreeTest.cpp @@ -0,0 +1,729 @@ +#include "LooseQuadtree.h" + +#include <chrono> +#include <cstdlib> +#include <cstdio> +#include <random> +#include <vector> + +using namespace loose_quadtree; + + + +#define ASSERT(CONDITION) if (!(CONDITION)) {\ + printf("Assertion failure %s:%d ASSERT(%s)\n", __FILE__, __LINE__, #CONDITION);\ + abort();\ + } + + +template <typename NumberT> +class TrivialBBExtractor { +public: + static void ExtractBoundingBox(const BoundingBox<NumberT> *object, BoundingBox<NumberT> *bbox) { + bbox->left = object->left; + bbox->top = object->top; + bbox->width = object->width; + bbox->height = object->height; + } +}; + + + +template <typename NumberT> +void TestBoundingBox() { + BoundingBox<NumberT> big(100, 100, 200, 50); + BoundingBox<NumberT> small_inside(200, 125, 5, 5); + BoundingBox<NumberT> edge_inside(110, 110, 190, 40); + BoundingBox<NumberT> edge_outside(300, 150, 20, 5); + BoundingBox<NumberT> intersecting1(290, 90, 29, 25); + BoundingBox<NumberT> intersecting2(290, 110, 29, 25); + BoundingBox<NumberT> outside(290, 210, 29, 25); + + ASSERT(big.Contains(100, 100)); + ASSERT(!big.Contains(300, 150)); + + ASSERT(big.Contains(big)); + ASSERT(big.Contains(small_inside)); + ASSERT(!small_inside.Contains(big)); + ASSERT(big.Contains(edge_inside)); + ASSERT(!edge_inside.Contains(big)); + ASSERT(!big.Contains(edge_outside)); + ASSERT(!edge_outside.Contains(big)); + ASSERT(!big.Contains(intersecting1)); + ASSERT(!intersecting1.Contains(big)); + ASSERT(!big.Contains(intersecting2)); + ASSERT(!intersecting1.Contains(big)); + ASSERT(!intersecting1.Contains(intersecting2)); + ASSERT(!big.Contains(outside)); + ASSERT(!outside.Contains(big)); + + ASSERT(big.Intersects(big)); + ASSERT(big.Intersects(small_inside)); + ASSERT(small_inside.Intersects(big)); + ASSERT(big.Intersects(edge_inside)); + ASSERT(edge_inside.Intersects(big)); + ASSERT(!big.Intersects(edge_outside)); + ASSERT(!edge_outside.Intersects(big)); + ASSERT(big.Intersects(intersecting1)); + ASSERT(intersecting1.Intersects(big)); + ASSERT(big.Intersects(intersecting2)); + ASSERT(intersecting2.Intersects(big)); + ASSERT(intersecting1.Intersects(intersecting2)); + ASSERT(!big.Intersects(outside)); + ASSERT(!outside.Intersects(big)); +} + + + +template <typename NumberT> +void TestForwardTreeTraversal() { + detail::BlocksAllocator allocator; + detail::ForwardTreeTraversal<NumberT, BoundingBox<NumberT>> fortt; + detail::TreeNode<BoundingBox<NumberT>> root(allocator); + detail::TreeNode<BoundingBox<NumberT>> tl(allocator); + detail::TreeNode<BoundingBox<NumberT>> tr(allocator); + detail::TreeNode<BoundingBox<NumberT>> br(allocator); + detail::TreeNode<BoundingBox<NumberT>> bl(allocator); + root.top_left = &tl; + tl.top_right = &tr; + tr.bottom_right = &br; + root.bottom_left = &bl; + fortt.StartAt(&root, BoundingBox<NumberT>(0, 0, 64, 64)); + ASSERT(fortt.GetDepth() == 0); + ASSERT(fortt.GetNode() == &root); + ASSERT(fortt.GetNodeBoundingBox().left == 0); + ASSERT(fortt.GetNodeBoundingBox().top == 0); + ASSERT(fortt.GetNodeBoundingBox().width == 64); + ASSERT(fortt.GetNodeBoundingBox().height == 64); + fortt.GoTopLeft(); + ASSERT(fortt.GetDepth() == 1); + ASSERT(fortt.GetNode() == &tl); + ASSERT(fortt.GetNodeBoundingBox().left == 0); + ASSERT(fortt.GetNodeBoundingBox().top == 0); + ASSERT(fortt.GetNodeBoundingBox().width == 32); + ASSERT(fortt.GetNodeBoundingBox().height == 32); + fortt.GoTopRight(); + ASSERT(fortt.GetDepth() == 2); + ASSERT(fortt.GetNode() == &tr); + ASSERT(fortt.GetNodeBoundingBox().left == 16); + ASSERT(fortt.GetNodeBoundingBox().top == 0); + ASSERT(fortt.GetNodeBoundingBox().width == 16); + ASSERT(fortt.GetNodeBoundingBox().height == 16); + fortt.GoBottomRight(); + ASSERT(fortt.GetDepth() == 3); + ASSERT(fortt.GetNode() == &br); + ASSERT(fortt.GetNodeBoundingBox().left == 24); + ASSERT(fortt.GetNodeBoundingBox().top == 8); + ASSERT(fortt.GetNodeBoundingBox().width == 8); + ASSERT(fortt.GetNodeBoundingBox().height == 8); + fortt.StartAt(&root, BoundingBox<NumberT>(0, 0, 64, 64)); + ASSERT(fortt.GetDepth() == 0); + ASSERT(fortt.GetNode() == &root); + ASSERT(fortt.GetNodeBoundingBox().left == 0); + ASSERT(fortt.GetNodeBoundingBox().top == 0); + ASSERT(fortt.GetNodeBoundingBox().width == 64); + ASSERT(fortt.GetNodeBoundingBox().height == 64); + fortt.GoBottomLeft(); + ASSERT(fortt.GetDepth() == 1); + ASSERT(fortt.GetNode() == &bl); + ASSERT(fortt.GetNodeBoundingBox().left == 0); + ASSERT(fortt.GetNodeBoundingBox().top == 32); + ASSERT(fortt.GetNodeBoundingBox().width == 32); + ASSERT(fortt.GetNodeBoundingBox().height == 32); +} + +template <typename NumberT> +void TestFullTreeTraversal() { + detail::BlocksAllocator allocator; + detail::FullTreeTraversal<NumberT, BoundingBox<NumberT>> fultt; + detail::TreeNode<BoundingBox<NumberT>> root(allocator); + detail::TreeNode<BoundingBox<NumberT>> tl(allocator); + detail::TreeNode<BoundingBox<NumberT>> tr(allocator); + detail::TreeNode<BoundingBox<NumberT>> br(allocator); + detail::TreeNode<BoundingBox<NumberT>> bl(allocator); + root.top_left = &tl; + tl.top_right = &tr; + tr.bottom_right = &br; + br.bottom_left = &bl; + fultt.StartAt(&root, BoundingBox<NumberT>(0, 0, 64, 64)); + ASSERT(fultt.GetDepth() == 0); + ASSERT(fultt.GetNode() == &root); + ASSERT(fultt.GetNodeCurrentChild() == detail::ChildPosition::kNone); + fultt.GoTopLeft(); + ASSERT(fultt.GetDepth() == 1); + ASSERT(fultt.GetNode() == &tl); + ASSERT(fultt.GetNodeCurrentChild() == detail::ChildPosition::kNone); + fultt.GoTopRight(); + ASSERT(fultt.GetDepth() == 2); + ASSERT(fultt.GetNode() == &tr); + ASSERT(fultt.GetNodeCurrentChild() == detail::ChildPosition::kNone); + fultt.GoBottomRight(); + ASSERT(fultt.GetDepth() == 3); + ASSERT(fultt.GetNode() == &br); + ASSERT(fultt.GetNodeCurrentChild() == detail::ChildPosition::kNone); + fultt.GoBottomLeft(); + ASSERT(fultt.GetDepth() == 4); + ASSERT(fultt.GetNode() == &bl); + ASSERT(fultt.GetNodeCurrentChild() == detail::ChildPosition::kNone); + fultt.GoUp(); + ASSERT(fultt.GetDepth() == 3); + ASSERT(fultt.GetNode() == &br); + ASSERT(fultt.GetNodeCurrentChild() == detail::ChildPosition::kBottomLeft); + fultt.GoUp(); + ASSERT(fultt.GetDepth() == 2); + ASSERT(fultt.GetNode() == &tr); + ASSERT(fultt.GetNodeCurrentChild() == detail::ChildPosition::kBottomRight); + fultt.GoUp(); + ASSERT(fultt.GetDepth() == 1); + ASSERT(fultt.GetNode() == &tl); + ASSERT(fultt.GetNodeCurrentChild() == detail::ChildPosition::kTopRight); + fultt.GoUp(); + ASSERT(fultt.GetDepth() == 0); + ASSERT(fultt.GetNode() == &root); + ASSERT(fultt.GetNodeCurrentChild() == detail::ChildPosition::kTopLeft); +} + +template <typename NumberT> +void TestBoundingBoxDiscrepancy() { + detail::BlocksAllocator allocator; + detail::FullTreeTraversal<NumberT, BoundingBox<NumberT>> ftt; + detail::TreeNode<BoundingBox<NumberT>> root(allocator); + detail::TreeNode<BoundingBox<NumberT>> tl(allocator); + detail::TreeNode<BoundingBox<NumberT>> tr(allocator); + detail::TreeNode<BoundingBox<NumberT>> br(allocator); + root.top_left = &tl; + root.top_right = &tr; + root.bottom_right = &br; + ftt.StartAt(&root, BoundingBox<NumberT>(10, 10, 17, 19)); + NumberT orig_width = ftt.GetNodeBoundingBox().width; + NumberT orig_height = ftt.GetNodeBoundingBox().height; + ftt.GoTopLeft(); + NumberT tl_width = ftt.GetNodeBoundingBox().width; + NumberT tl_height = ftt.GetNodeBoundingBox().height; + ftt.GoUp(); + ftt.GoTopRight(); + NumberT tr_width = ftt.GetNodeBoundingBox().width; + NumberT tr_height = ftt.GetNodeBoundingBox().height; + ftt.GoUp(); + ftt.GoBottomRight(); + NumberT br_width = ftt.GetNodeBoundingBox().width; + NumberT br_height = ftt.GetNodeBoundingBox().height; + ftt.GoUp(); + ASSERT(orig_width == tl_width + tr_width); + ASSERT(orig_height == tl_height + br_height); + ASSERT(tr_width == br_width); + ASSERT(tl_height == tr_height); +} + +template <typename NumberT> +void TestTraversals() { + TestForwardTreeTraversal<NumberT>(); + TestFullTreeTraversal<NumberT>(); + TestBoundingBoxDiscrepancy<NumberT>(); +} + + +template <typename NumberT> +void TestInsertRemove(bool reclaim_losses) { + std::vector<BoundingBox<NumberT>> objects; + objects.emplace_back(1000, 1300, 50, 30); + objects.emplace_back(1060, 1300, 50, 30); + objects.emplace_back(1060, 1300, 5, 3); + LooseQuadtree<NumberT, BoundingBox<NumberT>, TrivialBBExtractor<NumberT>> lqt; + if (reclaim_losses) lqt.ForceCleanup(); + ASSERT(lqt.GetSize() == 0); + ASSERT(lqt.IsEmpty()); + ASSERT(!lqt.Contains(&objects[0])); + ASSERT(lqt.GetSize() == 0); + lqt.Insert(&objects[0]); + ASSERT(lqt.GetSize() == 1); + ASSERT(!lqt.IsEmpty()); + ASSERT(lqt.Contains(&objects[0])); + ASSERT(!lqt.Contains(&objects[1])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[0])); + ASSERT(!objects[0].Contains(lqt.GetLooseBoundingBox())); + lqt.Remove(&objects[0]); + ASSERT(!lqt.Contains(&objects[0])); + ASSERT(lqt.GetSize() == 0); + ASSERT(lqt.IsEmpty()); + if (reclaim_losses) lqt.ForceCleanup(); + + lqt.Insert(&objects[1]); + ASSERT(lqt.GetSize() == 1); + ASSERT(!lqt.Contains(&objects[0])); + ASSERT(lqt.Contains(&objects[1])); + lqt.Insert(&objects[0]); + lqt.Insert(&objects[0]); + lqt.Insert(&objects[0]); + ASSERT(lqt.GetSize() == 2); + ASSERT(lqt.Contains(&objects[0])); + ASSERT(lqt.Contains(&objects[1])); + ASSERT(!lqt.Contains(&objects[2])); + if (reclaim_losses) lqt.ForceCleanup(); + lqt.Insert(&objects[2]); + ASSERT(lqt.GetSize() == 3); + ASSERT(lqt.Contains(&objects[0])); + ASSERT(lqt.Contains(&objects[1])); + ASSERT(lqt.Contains(&objects[2])); + if (reclaim_losses) lqt.ForceCleanup(); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[0])); + ASSERT(!objects[0].Contains(lqt.GetLooseBoundingBox())); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[1])); + ASSERT(!objects[1].Contains(lqt.GetLooseBoundingBox())); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[2])); + ASSERT(!objects[2].Contains(lqt.GetLooseBoundingBox())); + lqt.Remove(&objects[1]); + lqt.Remove(&objects[1]); + lqt.Remove(&objects[1]); + ASSERT(lqt.GetSize() == 2); + ASSERT(lqt.Contains(&objects[0])); + ASSERT(!lqt.Contains(&objects[1])); + ASSERT(lqt.Contains(&objects[2])); + if (reclaim_losses) lqt.ForceCleanup(); + lqt.Remove(&objects[0]); + ASSERT(lqt.GetSize() == 1); + ASSERT(!lqt.Contains(&objects[0])); + ASSERT(!lqt.Contains(&objects[1])); + ASSERT(lqt.Contains(&objects[2])); + if (reclaim_losses) lqt.ForceCleanup(); + lqt.Remove(&objects[0]); + ASSERT(lqt.GetSize() == 1); + ASSERT(!lqt.Contains(&objects[0])); + ASSERT(!lqt.Contains(&objects[1])); + ASSERT(lqt.Contains(&objects[2])); + if (reclaim_losses) lqt.ForceCleanup(); + lqt.Remove(&objects[2]); + ASSERT(lqt.GetSize() == 0); + ASSERT(!lqt.Contains(&objects[0])); + ASSERT(!lqt.Contains(&objects[1])); + ASSERT(!lqt.Contains(&objects[2])); + if (reclaim_losses) lqt.ForceCleanup(); +} + +template <typename NumberT> +void TestUpdate(bool reclaim_losses) { + std::vector<BoundingBox<NumberT>> objects; + objects.emplace_back(1000, 1000, 50, 30); + objects.emplace_back(1060, 1000, 50, 30); + objects.emplace_back(1060, 1000, 5, 3); + LooseQuadtree<NumberT, BoundingBox<NumberT>, TrivialBBExtractor<NumberT>> lqt; + if (reclaim_losses) lqt.ForceCleanup(); + lqt.Insert(&objects[0]); + lqt.Insert(&objects[1]); + lqt.Insert(&objects[2]); + if (reclaim_losses) lqt.ForceCleanup(); + ASSERT(lqt.GetSize() == 3); + ASSERT(lqt.Contains(&objects[0])); + ASSERT(lqt.Contains(&objects[1])); + ASSERT(lqt.Contains(&objects[2])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[0])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[1])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[2])); + ASSERT(!objects[0].Contains(lqt.GetLooseBoundingBox())); + ASSERT(!objects[1].Contains(lqt.GetLooseBoundingBox())); + ASSERT(!objects[2].Contains(lqt.GetLooseBoundingBox())); + objects[2].width = 50; + objects[2].height = 30; + lqt.Update(&objects[2]); + objects[0].left = 1060; + lqt.Update(&objects[0]); + ASSERT(lqt.GetSize() == 3); + ASSERT(lqt.Contains(&objects[0])); + ASSERT(lqt.Contains(&objects[1])); + ASSERT(lqt.Contains(&objects[2])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[0])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[1])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[2])); + ASSERT(!objects[0].Contains(lqt.GetLooseBoundingBox())); + ASSERT(!objects[1].Contains(lqt.GetLooseBoundingBox())); + ASSERT(!objects[2].Contains(lqt.GetLooseBoundingBox())); + if (reclaim_losses) lqt.ForceCleanup(); + ASSERT(lqt.GetSize() == 3); + ASSERT(lqt.Contains(&objects[0])); + ASSERT(lqt.Contains(&objects[1])); + ASSERT(lqt.Contains(&objects[2])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[0])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[1])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[2])); + ASSERT(!objects[0].Contains(lqt.GetLooseBoundingBox())); + ASSERT(!objects[1].Contains(lqt.GetLooseBoundingBox())); + ASSERT(!objects[2].Contains(lqt.GetLooseBoundingBox())); + lqt.Remove(&objects[0]); + ASSERT(lqt.GetSize() == 2); + if (reclaim_losses) lqt.ForceCleanup(); + lqt.Remove(&objects[1]); + ASSERT(lqt.GetSize() == 1); + if (reclaim_losses) lqt.ForceCleanup(); + lqt.Remove(&objects[2]); + ASSERT(lqt.GetSize() == 0); + if (reclaim_losses) lqt.ForceCleanup(); +} + +template <typename NumberT> +void TestMoreTrees(bool reclaim_losses) { + std::vector<BoundingBox<NumberT>> objects; + objects.emplace_back(1000, 1000, 50, 30); + objects.emplace_back(1060, 1000, 50, 30); + objects.emplace_back(1060, 1000, 5, 3); + LooseQuadtree<NumberT, BoundingBox<NumberT>, TrivialBBExtractor<NumberT>> lqt; + if (reclaim_losses) lqt.ForceCleanup(); + lqt.Insert(&objects[0]); + lqt.Insert(&objects[1]); + ASSERT(lqt.GetSize() == 2); + ASSERT(lqt.Contains(&objects[0])); + ASSERT(lqt.Contains(&objects[1])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[0])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[1])); + ASSERT(!objects[0].Contains(lqt.GetLooseBoundingBox())); + ASSERT(!objects[1].Contains(lqt.GetLooseBoundingBox())); + if (reclaim_losses) lqt.ForceCleanup(); + { + LooseQuadtree<NumberT, BoundingBox<NumberT>, TrivialBBExtractor<NumberT>> lqt2; + if (reclaim_losses) lqt2.ForceCleanup(); + ASSERT(lqt2.GetSize() == 0); + lqt2.Insert(&objects[1]); + lqt2.Insert(&objects[2]); + lqt.Insert(&objects[2]); + if (reclaim_losses) lqt.ForceCleanup(); + ASSERT(lqt2.GetSize() == 2); + ASSERT(!lqt2.Contains(&objects[0])); + ASSERT(lqt2.Contains(&objects[1])); + ASSERT(lqt2.Contains(&objects[2])); + if (reclaim_losses) lqt2.ForceCleanup(); + lqt2.Remove(&objects[1]); + lqt2.Remove(&objects[2]); + ASSERT(lqt2.GetSize() == 0); + if (reclaim_losses) lqt2.ForceCleanup(); + } + { + LooseQuadtree<NumberT, BoundingBox<NumberT>, TrivialBBExtractor<NumberT>> lqt2; + lqt2.Insert(&objects[1]); + if (reclaim_losses) lqt2.ForceCleanup(); + } + { + LooseQuadtree<NumberT, BoundingBox<NumberT>, TrivialBBExtractor<NumberT>> lqt2; + lqt2.Insert(&objects[1]); + lqt2.Insert(&objects[2]); + lqt2.Insert(&objects[0]); + ASSERT(lqt2.Contains(&objects[1])); + lqt2.Clear(); + ASSERT(lqt2.GetSize() == 0); + ASSERT(!lqt2.Contains(&objects[1])); + if (reclaim_losses) lqt2.ForceCleanup(); + ASSERT(lqt2.GetSize() == 0); + ASSERT(!lqt2.Contains(&objects[1])); + lqt2.Insert(&objects[1]); + ASSERT(lqt2.GetSize() == 1); + ASSERT(lqt2.Contains(&objects[1])); + } + ASSERT(lqt.GetSize() == 3); + ASSERT(lqt.Contains(&objects[0])); + ASSERT(lqt.Contains(&objects[1])); + ASSERT(lqt.Contains(&objects[2])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[0])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[1])); + ASSERT(lqt.GetLooseBoundingBox().Intersects(objects[2])); + ASSERT(!objects[0].Contains(lqt.GetLooseBoundingBox())); + ASSERT(!objects[1].Contains(lqt.GetLooseBoundingBox())); + ASSERT(!objects[2].Contains(lqt.GetLooseBoundingBox())); + lqt.Remove(&objects[0]); + lqt.Remove(&objects[2]); + ASSERT(!lqt.Contains(&objects[0])); + ASSERT(lqt.Contains(&objects[1])); + ASSERT(lqt.GetSize() == 1); + if (reclaim_losses) lqt.ForceCleanup(); + lqt.Remove(&objects[1]); + ASSERT(lqt.GetSize() == 0); + if (reclaim_losses) lqt.ForceCleanup(); +} + +template <typename NumberT> +void TestContainer() { + TestInsertRemove<NumberT>(false); + TestInsertRemove<NumberT>(true); + TestUpdate<NumberT>(false); + TestUpdate<NumberT>(true); + TestMoreTrees<NumberT>(false); + TestMoreTrees<NumberT>(true); +} + + + +template <typename NumberT> +void TestQueryIntersects(const std::vector<BoundingBox<NumberT>>& objects, + LooseQuadtree<NumberT, BoundingBox<NumberT>, TrivialBBExtractor<NumberT>>& lqt) { + auto query = lqt.QueryIntersectsRegion(BoundingBox<NumberT>(33,33,1,1)); + ASSERT(query.EndOfQuery()); + + query = lqt.QueryIntersectsRegion(BoundingBox<NumberT>(9000,9000,9000,9000)); + int count = 0; + while (!query.EndOfQuery()) { + BoundingBox<NumberT>* obj = query.GetCurrent(); + (void)obj; + count++; + query.Next(); + } + ASSERT(count == 7); + + query = lqt.QueryIntersectsRegion(BoundingBox<NumberT>(10003,10003,3,7)); + count = 0; + while (!query.EndOfQuery()) { + BoundingBox<NumberT>* obj = query.GetCurrent(); + if (obj != &objects[0] && obj != &objects[1] && obj != &objects[2]) { + ASSERT(false); + } + count++; + query.Next(); + } + ASSERT(count == 3); + + query = lqt.QueryIntersectsRegion(BoundingBox<NumberT>(14900,14900,200,200)); + count = 0; + while (!query.EndOfQuery()) { + BoundingBox<NumberT>* obj = query.GetCurrent(); + if (obj != &objects[0] && obj != &objects[1] && obj != &objects[3] && obj != &objects[5]) { + ASSERT(false); + } + count++; + query.Next(); + } + ASSERT(count == 4); +} + +template <typename NumberT> +void TestQueryInside(const std::vector<BoundingBox<NumberT>>& objects, + LooseQuadtree<NumberT, BoundingBox<NumberT>, TrivialBBExtractor<NumberT>>& lqt) { + auto query = lqt.QueryInsideRegion(BoundingBox<NumberT>(33,33,1,1)); + ASSERT(query.EndOfQuery()); + + query = lqt.QueryInsideRegion(BoundingBox<NumberT>(9000,9000,9000,9000)); + int count = 0; + while (!query.EndOfQuery()) { + BoundingBox<NumberT>* obj = query.GetCurrent(); + (void)obj; + count++; + query.Next(); + } + ASSERT(count == 7); + + query = lqt.QueryInsideRegion(BoundingBox<NumberT>(10003,10003,3,7)); + ASSERT(query.EndOfQuery()); + + query = lqt.QueryInsideRegion(BoundingBox<NumberT>(14900,14900,300,300)); + count = 0; + while (!query.EndOfQuery()) { + BoundingBox<NumberT>* obj = query.GetCurrent(); + if (obj != &objects[5] && obj != &objects[6]) { + ASSERT(false); + } + count++; + query.Next(); + } + ASSERT(count == 2); +} + +template <typename NumberT> +void TestQueryContains(const std::vector<BoundingBox<NumberT>>& objects, + LooseQuadtree<NumberT, BoundingBox<NumberT>, TrivialBBExtractor<NumberT>>& lqt) { + auto query = lqt.QueryContainsRegion(BoundingBox<NumberT>(33,33,1,1)); + ASSERT(query.EndOfQuery()); + + query = lqt.QueryContainsRegion(BoundingBox<NumberT>(9000,9000,9000,9000)); + ASSERT(query.EndOfQuery()); + + query = lqt.QueryContainsRegion(BoundingBox<NumberT>(10003,10003,3,7)); + int count = 0; + while (!query.EndOfQuery()) { + BoundingBox<NumberT>* obj = query.GetCurrent(); + if (obj != &objects[0] && obj != &objects[1]) { + ASSERT(false); + } + count++; + query.Next(); + } + ASSERT(count == 2); + + query = lqt.QueryContainsRegion(BoundingBox<NumberT>(14900,14900,200,200)); + count = 0; + while (!query.EndOfQuery()) { + BoundingBox<NumberT>* obj = query.GetCurrent(); + if (obj != &objects[0] && obj != &objects[1]) { + ASSERT(false); + } + count++; + query.Next(); + } + ASSERT(count == 2); + + query = lqt.QueryContainsRegion(BoundingBox<NumberT>(15000,15000,2,2)); + count = 0; + while (!query.EndOfQuery()) { + BoundingBox<NumberT>* obj = query.GetCurrent(); + if (obj != &objects[0] && obj != &objects[1] && obj != &objects[3] && obj != &objects[5]) { + ASSERT(false); + } + count++; + query.Next(); + } + ASSERT(count == 4); +} + +template <typename NumberT> +void TestQueries() { + std::vector<BoundingBox<NumberT>> objects; + objects.emplace_back(10000, 10000, 8000, 8000);//0 + objects.emplace_back(10000, 10000, 7000, 6000);//1 + objects.emplace_back(10000, 10000, 7, 6);//2 + objects.emplace_back(15000, 15000, 500, 600);//3 + objects.emplace_back(15100, 15100, 200, 200);//4 + objects.emplace_back(15000, 15000, 200, 200);//5 + objects.emplace_back(15100, 15100, 2, 2);//6 + LooseQuadtree<NumberT, BoundingBox<NumberT>, TrivialBBExtractor<NumberT>> lqt; + for (auto& obj : objects) { + lqt.Insert(&obj); + } + TestQueryIntersects<NumberT>(objects, lqt); + TestQueryInside<NumberT>(objects, lqt); + TestQueryContains<NumberT>(objects, lqt); +} + + + +template <typename NumberT> +void StressTest() { +#ifndef NDEBUG + const int objects_generated = 10000; + const int object_fluctuation = 1000; +#else + const int objects_generated = 200000; + const int object_fluctuation = 20000; +#endif + const int full_rounds = 24; + const int query_rounds = 4; + std::minstd_rand rand; + std::uniform_int_distribution<std::size_t> index(0, objects_generated - 1); + std::uniform_real_distribution<float> + coordinate(std::is_integral<NumberT>::value ? + (float)(std::is_signed<NumberT>::value ? + -std::numeric_limits<NumberT>::max() / 8 : + std::numeric_limits<NumberT>::max() / 16 * 7) : + -1.0f, + std::is_integral<NumberT>::value ? + (float)(std::is_signed<NumberT>::value ? + std::numeric_limits<NumberT>::max() / 8 : + std::numeric_limits<NumberT>::max() / 16 * 9) : + 1.0f); + std::uniform_real_distribution<float> + distance(0.0f, std::is_integral<NumberT>::value ? + (float)(std::is_signed<NumberT>::value ? + std::numeric_limits<NumberT>::max() / 8 : + std::numeric_limits<NumberT>::max() / 16) : + 0.5f); + + std::vector<BoundingBox<NumberT>> objects; + objects.reserve(objects_generated); + std::vector<bool> flags(objects_generated, false); + LooseQuadtree<NumberT, BoundingBox<NumberT>, TrivialBBExtractor<NumberT>> lqt; + for (std::size_t i = 0; i < objects_generated; i++) { + objects.emplace_back((NumberT)coordinate(rand), (NumberT)coordinate(rand), + (NumberT)distance(rand), (NumberT)distance(rand)); + lqt.Insert(&objects[i]); + } + ASSERT(objects.size() == objects_generated); + ASSERT(flags.size() == objects_generated); + + for (int round = 0; round < full_rounds; round++) { + for (int fluctobj = 0; fluctobj < object_fluctuation; fluctobj++) { + std::size_t id = index(rand); + objects[id] = BoundingBox<NumberT>((NumberT)coordinate(rand), (NumberT)coordinate(rand), + (NumberT)distance(rand), (NumberT)distance(rand)); + lqt.Update(&objects[id]); + } + for (int query_round = 0; query_round < query_rounds; query_round++) { + BoundingBox<NumberT> query_region((NumberT)coordinate(rand), (NumberT)coordinate(rand), + (NumberT)distance(rand), (NumberT)distance(rand)); + for (std::size_t i = 0; i < objects_generated; i++) { + flags[i] = false; + } + + auto query = lqt.QueryIntersectsRegion(query_region); + while (!query.EndOfQuery()) { + BoundingBox<NumberT>* obj = query.GetCurrent(); + ASSERT(query_region.Intersects(*obj)); + std::size_t id = (std::size_t)(obj - &objects[0]); + ASSERT(id >= 0 && id < objects_generated); + flags[id] = true; + query.Next(); + } + for (std::size_t i = 0; i < objects_generated; i++) { + ASSERT(flags[i] == query_region.Intersects(objects[i])); + flags[i] = false; + } + + query = lqt.QueryInsideRegion(query_region); + while (!query.EndOfQuery()) { + BoundingBox<NumberT>* obj = query.GetCurrent(); + ASSERT(query_region.Contains(*obj)); + std::size_t id = (std::size_t)(obj - &objects[0]); + ASSERT(id >= 0 && id < objects_generated); + flags[id] = true; + query.Next(); + } + for (std::size_t i = 0; i < objects_generated; i++) { + ASSERT(flags[i] == query_region.Contains(objects[i])); + flags[i] = false; + } + + query = lqt.QueryContainsRegion(query_region); + while (!query.EndOfQuery()) { + BoundingBox<NumberT>* obj = query.GetCurrent(); + ASSERT(obj->Contains(query_region)); + std::size_t id = (std::size_t)(obj - &objects[0]); + ASSERT(id >= 0 && id < objects_generated); + flags[id] = true; + query.Next(); + } + for (std::size_t i = 0; i < objects_generated; i++) { + ASSERT(flags[i] == objects[i].Contains(query_region)); + //flags[i] = false; + } + } + } + lqt.ForceCleanup(); +} + + + +template <typename NumberT> +void RunTests(const char* type_str) { + printf("***** Running tests for %s (%lu-bit)... ", type_str, sizeof(NumberT) * 8); + auto start = std::chrono::high_resolution_clock::now(); + TestBoundingBox<NumberT>(); + TestTraversals<NumberT>(); + TestContainer<NumberT>(); + TestQueries<NumberT>(); + StressTest<NumberT>(); + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration<double> time = end - start; + printf("took %f seconds\n", time.count()); +} + + + +int main(int, char*[]) { + puts("***** Testing is about to start *****"); + printf("***** This system is %lu-bit\n", sizeof(void*) * 8); + RunTests<float>("float"); + RunTests<double>("double"); + RunTests<long double>("long double"); + RunTests<int>("int"); + RunTests<long>("long"); + RunTests<short>("short"); + RunTests<unsigned int>("unsigned int"); + RunTests<unsigned long>("unsigned long"); + RunTests<unsigned short>("unsigned short"); + RunTests<long long>("long long"); + puts("***** Testing is successfully finished *****"); + return 0; +} + |