/* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_SPATIAL #define INCLUDED_SPATIAL #include "simulation2/serialization/SerializeTemplates.h" /** * A very basic subdivision scheme for finding items in ranges. * Items are stored in lists in fixed-size divisions. * Items have a size (min/max values of their axis-aligned bounding box) * and are stored in all divisions overlapping that area. * * It is the caller's responsibility to ensure items are only added * once, aren't removed unless they've been added, etc, and that * Move/Remove are called with the same coordinates originally passed * to Add (since this class doesn't remember which divisions an item * occupies). * * (TODO: maybe an adaptive quadtree would be better than fixed sizes?) */ template class SpatialSubdivision { public: SpatialSubdivision() : m_DivisionsW(0), m_DivisionsH(0) { } /** * Equivalence test (ignoring order of items within each subdivision) */ bool operator==(const SpatialSubdivision& rhs) { if (m_DivisionSize != rhs.m_DivisionSize || m_DivisionsW != rhs.m_DivisionsW || m_DivisionsH != rhs.m_DivisionsH) return false; for (u32 j = 0; j < m_DivisionsH; ++j) { for (u32 i = 0; i < m_DivisionsW; ++i) { std::vector div1 = m_Divisions.at(i + j*m_DivisionsW); std::vector div2 = rhs.m_Divisions.at(i + j*m_DivisionsW); std::sort(div1.begin(), div1.end()); std::sort(div2.begin(), div2.end()); if (div1 != div2) return false; } } return true; } bool operator!=(const SpatialSubdivision& rhs) { return !(*this == rhs); } void Reset(entity_pos_t maxX, entity_pos_t maxZ, entity_pos_t divisionSize) { m_DivisionSize = divisionSize; m_DivisionsW = (maxX / m_DivisionSize).ToInt_RoundToInfinity(); m_DivisionsH = (maxZ / m_DivisionSize).ToInt_RoundToInfinity(); m_Divisions.clear(); m_Divisions.resize(m_DivisionsW * m_DivisionsH); } /** * Add an item with the given 'to' size. * The item must not already be present. */ void Add(T item, CFixedVector2D toMin, CFixedVector2D toMax) { ENSURE(toMin.X <= toMax.X && toMin.Y <= toMax.Y); u32 i0 = GetI0(toMin.X); u32 j0 = GetJ0(toMin.Y); u32 i1 = GetI1(toMax.X); u32 j1 = GetJ1(toMax.Y); for (u32 j = j0; j <= j1; ++j) { for (u32 i = i0; i <= i1; ++i) { std::vector& div = m_Divisions.at(i + j*m_DivisionsW); div.push_back(item); } } } /** * Remove an item with the given 'from' size. * The item should already be present. * The size must match the size that was last used when adding the item. */ void Remove(T item, CFixedVector2D fromMin, CFixedVector2D fromMax) { ENSURE(fromMin.X <= fromMax.X && fromMin.Y <= fromMax.Y); u32 i0 = GetI0(fromMin.X); u32 j0 = GetJ0(fromMin.Y); u32 i1 = GetI1(fromMax.X); u32 j1 = GetJ1(fromMax.Y); for (u32 j = j0; j <= j1; ++j) { for (u32 i = i0; i <= i1; ++i) { std::vector& div = m_Divisions.at(i + j*m_DivisionsW); for (u32 n = 0; n < div.size(); ++n) { if (div[n] == item) { // Delete by swapping with the last element then popping div[n] = div.back(); div.pop_back(); break; } } } } } /** * Equivalent to Remove() then Add(), but potentially faster. */ void Move(T item, CFixedVector2D fromMin, CFixedVector2D fromMax, CFixedVector2D toMin, CFixedVector2D toMax) { // Skip the work if we're staying in the same divisions if (GetIndex0(fromMin) == GetIndex0(toMin) && GetIndex1(fromMax) == GetIndex1(toMax)) return; Remove(item, fromMin, fromMax); Add(item, toMin, toMax); } /** * Convenience function for Add() of individual points. * (Note that points on a boundary may occupy multiple divisions.) */ void Add(T item, CFixedVector2D to) { Add(item, to, to); } /** * Convenience function for Remove() of individual points. */ void Remove(T item, CFixedVector2D from) { Remove(item, from, from); } /** * Convenience function for Move() of individual points. */ void Move(T item, CFixedVector2D from, CFixedVector2D to) { Move(item, from, from, to, to); } /** * Returns a sorted list of unique items that includes all items * within the given axis-aligned square range. */ std::vector GetInRange(CFixedVector2D posMin, CFixedVector2D posMax) { std::vector ret; ENSURE(posMin.X <= posMax.X && posMin.Y <= posMax.Y); u32 i0 = GetI0(posMin.X); u32 j0 = GetJ0(posMin.Y); u32 i1 = GetI1(posMax.X); u32 j1 = GetJ1(posMax.Y); for (u32 j = j0; j <= j1; ++j) { for (u32 i = i0; i <= i1; ++i) { std::vector& div = m_Divisions.at(i + j*m_DivisionsW); ret.insert(ret.end(), div.begin(), div.end()); } } // Remove duplicates std::sort(ret.begin(), ret.end()); ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); return ret; } /** * Returns a sorted list of unique items that includes all items * within the given circular distance of the given point. */ std::vector GetNear(CFixedVector2D pos, entity_pos_t range) { // TODO: be cleverer and return a circular pattern of divisions, // not this square over-approximation return GetInRange(pos - CFixedVector2D(range, range), pos + CFixedVector2D(range, range)); } private: // Helper functions for translating coordinates into division indexes // (avoiding out-of-bounds accesses, and rounding correctly so that // points precisely between divisions are counted in both): u32 GetI0(entity_pos_t x) { return Clamp((x / m_DivisionSize).ToInt_RoundToInfinity()-1, 0, (int)m_DivisionsW-1); } u32 GetJ0(entity_pos_t z) { return Clamp((z / m_DivisionSize).ToInt_RoundToInfinity()-1, 0, (int)m_DivisionsH-1); } u32 GetI1(entity_pos_t x) { return Clamp((x / m_DivisionSize).ToInt_RoundToNegInfinity(), 0, (int)m_DivisionsW-1); } u32 GetJ1(entity_pos_t z) { return Clamp((z / m_DivisionSize).ToInt_RoundToNegInfinity(), 0, (int)m_DivisionsH-1); } u32 GetIndex0(CFixedVector2D pos) { return GetI0(pos.X) + GetJ0(pos.Y)*m_DivisionsW; } u32 GetIndex1(CFixedVector2D pos) { return GetI1(pos.X) + GetJ1(pos.Y)*m_DivisionsW; } entity_pos_t m_DivisionSize; std::vector > m_Divisions; u32 m_DivisionsW; u32 m_DivisionsH; template friend struct SerializeSpatialSubdivision; }; /** * Serialization helper template for SpatialSubdivision */ template struct SerializeSpatialSubdivision { template void operator()(ISerializer& serialize, const char* UNUSED(name), SpatialSubdivision& value) { serialize.NumberFixed_Unbounded("div size", value.m_DivisionSize); SerializeVector >()(serialize, "divs", value.m_Divisions); serialize.NumberU32_Unbounded("divs w", value.m_DivisionsW); serialize.NumberU32_Unbounded("divs h", value.m_DivisionsH); } template void operator()(IDeserializer& serialize, const char* UNUSED(name), SpatialSubdivision& value) { serialize.NumberFixed_Unbounded("div size", value.m_DivisionSize); SerializeVector >()(serialize, "divs", value.m_Divisions); u32 w, h; serialize.NumberU32_Unbounded("divs w", w); serialize.NumberU32_Unbounded("divs h", h); value.m_DivisionsW = w; value.m_DivisionsH = h; } }; #endif // INCLUDED_SPATIAL