diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/core/StarSpatialHash2D.hpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/core/StarSpatialHash2D.hpp')
-rw-r--r-- | source/core/StarSpatialHash2D.hpp | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/source/core/StarSpatialHash2D.hpp b/source/core/StarSpatialHash2D.hpp new file mode 100644 index 0000000..2241dfd --- /dev/null +++ b/source/core/StarSpatialHash2D.hpp @@ -0,0 +1,337 @@ +#ifndef STAR_SPATIAL_HASH_2D_HPP +#define STAR_SPATIAL_HASH_2D_HPP + +#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 <typename KeyT, typename ScalarT, typename ValueT, typename IntT = int, size_t AllocatorBlockSize = 4096> +class SpatialHash2D { +public: + typedef KeyT Key; + typedef ScalarT Scalar; + typedef Box<ScalarT, 2> Rect; + typedef typename Rect::Coord Coord; + typedef ValueT Value; + + struct Entry { + Entry(); + + SmallList<Rect, 2> rects; + Value value; + }; + + typedef StableHashMap<Key, Entry, hash<Key>, std::equal_to<Key>, BlockAllocator<pair<Key const, Entry>, AllocatorBlockSize>> EntryMap; + + SpatialHash2D(Scalar const& sectorSize); + + List<Key> keys() const; + List<Value> 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<Value> queryValues(Rect const& rect) const; + template <typename RectCollection> + List<Value> 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 <typename Function> + void forEach(Rect const& rect, Function&& function) const; + template <typename RectCollection, typename Function> + 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 <typename RectCollection> + 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 <typename RectCollection> + void set(Key const& key, RectCollection const& rects, Value value); + + Maybe<Value> remove(Key const& key); + + // Recalculates every item in sector map + void setSectorSize(Scalar const& sectorSize); + +private: + typedef Vector<IntT, 2> Sector; + typedef Box<IntT, 2> SectorRange; + typedef HashSet<Entry const*, hash<Entry const*>, std::equal_to<Entry const*>> SectorEntrySet; + typedef HashMap<Sector, SectorEntrySet> SectorMap; + + SectorRange getSectors(Rect const& r) const; + + void addSpatial(Entry const* entry); + void removeSpatial(Entry const* entry); + + template <typename RectCollection> + void updateSpatial(Entry* entry, RectCollection const& rects); + + Scalar m_sectorSize; + EntryMap m_entryMap; + SectorMap m_sectorMap; +}; + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::Entry::Entry() + : value() {} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::SpatialHash2D(Scalar const& sectorSize) + : m_sectorSize(sectorSize) {} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +List<KeyT> SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::keys() const { + return m_entryMap.keys(); +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +List<typename SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::Value> SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::values() const { + List<Value> values; + for (auto const& pair : m_entryMap) + values.append(pair.second.value); + + return values; +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +typename SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::EntryMap const& +SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::entries() const { + return m_entryMap; +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +size_t SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::size() const { + return m_entryMap.size(); +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +bool SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::contains(Key const& key) const { + return m_entryMap.contains(key); +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +typename SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::Value const& SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::get( + Key const& key) const { + return m_entryMap.get(key).value; +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +typename SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::Value& SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::get( + Key const& key) { + return m_entryMap.get(key).value; +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +typename SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::Value SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::value( + Key const& key) const { + auto iter = m_entryMap.find(key); + if (iter == m_entryMap.end()) + return Value(); + else + return iter->second.value; +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +List<ValueT> SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::queryValues(Rect const& rect) const { + return queryValues(initializer_list<Rect>{rect}); +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +template <typename RectCollection> +List<ValueT> SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::queryValues(RectCollection const& rects) const { + List<Value> values; + forEach(rects, [&values](Value const& value) { + values.append(value); + }); + return values; +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +template <typename Function> +void SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::forEach(Rect const& rect, Function&& function) const { + return forEach(initializer_list<Rect>{rect}, forward<Function>(function)); +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +template <typename RectCollection, typename Function> +void SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::forEach(RectCollection const& rects, Function&& function) const { + SmallList<Entry const*, 32> 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 <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +void SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::set(Key const& key, Coord const& pos) { + set(key, {Rect(pos, pos)}); +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +void SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::set(Key const& key, Rect const& rect) { + set(key, {rect}); +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +template <typename RectCollection> +void SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::set(Key const& key, RectCollection const& rects) { + updateSpatial(&m_entryMap.get(key), rects); +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +void SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::set(Key const& key, Coord const& pos, Value value) { + set(key, {Rect(pos, pos)}, move(value)); +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +void SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::set(Key const& key, Rect const& rect, Value value) { + set(key, {rect}, move(value)); +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +template <typename RectCollection> +void SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::set(Key const& key, RectCollection const& rects, Value value) { + Entry& entry = m_entryMap[key]; + entry.value = move(value); + updateSpatial(&entry, rects); +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +auto SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::remove(Key const& key) -> Maybe<Value> { + auto iter = m_entryMap.find(key); + if (iter == m_entryMap.end()) + return {}; + + removeSpatial(&iter->second); + Maybe<Value> val = move(iter->second.value); + m_entryMap.erase(iter); + return val; +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +void SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::setSectorSize(Scalar const& sectorSize) { + m_sectorSize = sectorSize; + m_sectorMap.clear(); + for (auto const& pair : m_entryMap) + addSpatial(pair.first, pair.second); +} + +template <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +typename SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::SectorRange SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::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 <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +void SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::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 <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +void SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::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 <typename KeyT, typename ScalarT, typename ValueT, typename IntT, size_t AllocatorBlockSize> +template <typename RectCollection> +void SpatialHash2D<KeyT, ScalarT, ValueT, IntT, AllocatorBlockSize>::updateSpatial(Entry* entry, RectCollection const& rects) { + removeSpatial(entry); + entry->rects.clear(); + entry->rects.appendAll(rects); + addSpatial(entry); +} + +} + +#endif |