#pragma once #include "StarRect.hpp" #include "StarMap.hpp" #include "StarSet.hpp" #include "StarBlockAllocator.hpp" namespace Star { // Dual-map based on key and 2 dimensional bounding rectangle. Implements a 2d // spatial hash for fast bounding box queries. Each entry may have more than // one bounding rectangle. template class SpatialHash2D { public: typedef KeyT Key; typedef ScalarT Scalar; typedef Box Rect; typedef typename Rect::Coord Coord; typedef ValueT Value; struct Entry { Entry(); SmallList rects; Value value; }; typedef StableHashMap, std::equal_to, BlockAllocator, AllocatorBlockSize>> EntryMap; SpatialHash2D(Scalar const& sectorSize); List keys() const; List values() const; EntryMap const& entries() const; size_t size() const; bool contains(Key const& key) const; Value const& get(Key const& key) const; Value& get(Key const& key); // Returns default constructed value if key not found Value value(Key const& key) const; // Query values from several bounding boxes at once with no duplicates. List queryValues(Rect const& rect) const; template List queryValues(RectCollection const& rects) const; // Iterate over entries in the given bounding boxes without duplication. It // is safe to modify rects or add entries from the given callback, but it is // not safe to remove entries from it. template void forEach(Rect const& rect, Function&& function) const; template void forEach(RectCollection const& rects, Function&& function) const; void set(Key const& key, Coord const& pos); void set(Key const& key, Rect const& rect); template void set(Key const& key, RectCollection const& rects); void set(Key const& key, Coord const& pos, Value value); void set(Key const& key, Rect const& rect, Value value); template void set(Key const& key, RectCollection const& rects, Value value); Maybe remove(Key const& key); // Recalculates every item in sector map void setSectorSize(Scalar const& sectorSize); private: typedef Vector Sector; typedef Box SectorRange; typedef HashSet, std::equal_to> SectorEntrySet; typedef HashMap SectorMap; SectorRange getSectors(Rect const& r) const; void addSpatial(Entry const* entry); void removeSpatial(Entry const* entry); template void updateSpatial(Entry* entry, RectCollection const& rects); Scalar m_sectorSize; EntryMap m_entryMap; SectorMap m_sectorMap; }; template SpatialHash2D::Entry::Entry() : value() {} template SpatialHash2D::SpatialHash2D(Scalar const& sectorSize) : m_sectorSize(sectorSize) {} template List SpatialHash2D::keys() const { return m_entryMap.keys(); } template List::Value> SpatialHash2D::values() const { List values; for (auto const& pair : m_entryMap) values.append(pair.second.value); return values; } template typename SpatialHash2D::EntryMap const& SpatialHash2D::entries() const { return m_entryMap; } template size_t SpatialHash2D::size() const { return m_entryMap.size(); } template bool SpatialHash2D::contains(Key const& key) const { return m_entryMap.contains(key); } template typename SpatialHash2D::Value const& SpatialHash2D::get( Key const& key) const { return m_entryMap.get(key).value; } template typename SpatialHash2D::Value& SpatialHash2D::get( Key const& key) { return m_entryMap.get(key).value; } template typename SpatialHash2D::Value SpatialHash2D::value( Key const& key) const { auto iter = m_entryMap.find(key); if (iter == m_entryMap.end()) return Value(); else return iter->second.value; } template List SpatialHash2D::queryValues(Rect const& rect) const { return queryValues(initializer_list{rect}); } template template List SpatialHash2D::queryValues(RectCollection const& rects) const { List values; forEach(rects, [&values](Value const& value) { values.append(value); }); return values; } template template void SpatialHash2D::forEach(Rect const& rect, Function&& function) const { return forEach(initializer_list{rect}, forward(function)); } template template void SpatialHash2D::forEach(RectCollection const& rects, Function&& function) const { SmallList foundEntries; for (Rect const& rect : rects) { if (rect.isNull()) continue; auto sectorResult = getSectors(rect); for (IntT x = sectorResult.xMin(); x < sectorResult.xMax(); ++x) { for (IntT y = sectorResult.yMin(); y < sectorResult.yMax(); ++y) { auto i = m_sectorMap.find(Sector{x, y}); if (i != m_sectorMap.end()) { for (auto e : i->second) { for (Rect const& r : e->rects) { if (r.intersects(rect)) { foundEntries.append(e); break; } } } } } } } // Rather than keep a Set of keys to avoid duplication in found entries, it // is much faster to simply keep all encountered intersected entries and then // sort them later for all but the most massive and most populated searches, // due to the allocation cost of Set and HashSet. sort(foundEntries); // Looping over the found entries in sorted order with potential duplication, // so need to skip over the entry if the previous entry is the same as the // current entry Entry const* prev = nullptr; for (auto const& entry : foundEntries) { if (entry == prev) continue; prev = entry; function(entry->value); } } template void SpatialHash2D::set(Key const& key, Coord const& pos) { set(key, {Rect(pos, pos)}); } template void SpatialHash2D::set(Key const& key, Rect const& rect) { set(key, {rect}); } template template void SpatialHash2D::set(Key const& key, RectCollection const& rects) { updateSpatial(&m_entryMap.get(key), rects); } template void SpatialHash2D::set(Key const& key, Coord const& pos, Value value) { set(key, {Rect(pos, pos)}, std::move(value)); } template void SpatialHash2D::set(Key const& key, Rect const& rect, Value value) { set(key, {rect}, std::move(value)); } template template void SpatialHash2D::set(Key const& key, RectCollection const& rects, Value value) { Entry& entry = m_entryMap[key]; entry.value = std::move(value); updateSpatial(&entry, rects); } template auto SpatialHash2D::remove(Key const& key) -> Maybe { auto iter = m_entryMap.find(key); if (iter == m_entryMap.end()) return {}; removeSpatial(&iter->second); Maybe val = std::move(iter->second.value); m_entryMap.erase(iter); return val; } template void SpatialHash2D::setSectorSize(Scalar const& sectorSize) { m_sectorSize = sectorSize; m_sectorMap.clear(); for (auto const& pair : m_entryMap) addSpatial(pair.first, pair.second); } template typename SpatialHash2D::SectorRange SpatialHash2D::getSectors(Rect const& r) const { return SectorRange( floor(r.xMin() / m_sectorSize), floor(r.yMin() / m_sectorSize), ceil(r.xMax() / m_sectorSize), ceil(r.yMax() / m_sectorSize)); } template void SpatialHash2D::addSpatial(Entry const* entry) { for (Rect const& rect : entry->rects) { if (rect.isNull()) continue; auto sectorResult = getSectors(rect); for (IntT x = sectorResult.xMin(); x < sectorResult.xMax(); ++x) { for (IntT y = sectorResult.yMin(); y < sectorResult.yMax(); ++y) { Sector sector(x, y); SectorEntrySet* p = m_sectorMap.ptr(sector); if (!p) p = &m_sectorMap.add(sector, SectorEntrySet()); p->add(entry); } } } } template void SpatialHash2D::removeSpatial(Entry const* entry) { for (Rect const& rect : entry->rects) { if (rect.isNull()) continue; auto sectorResult = getSectors(rect); for (IntT x = sectorResult.xMin(); x < sectorResult.xMax(); ++x) { for (IntT y = sectorResult.yMin(); y < sectorResult.yMax(); ++y) { auto i = m_sectorMap.find(Sector{x, y}); if (i != m_sectorMap.end()) { i->second.remove(entry); if (i->second.empty()) m_sectorMap.erase(i); } } } } } template template void SpatialHash2D::updateSpatial(Entry* entry, RectCollection const& rects) { removeSpatial(entry); entry->rects.clear(); entry->rects.appendAll(rects); addSpatial(entry); } }