├── .gitignore ├── LICENSE ├── README.md ├── aoi.h ├── assets └── 1.jpg ├── build ├── .gitignore ├── g.bat ├── g.sh ├── premake5 ├── premake5.exe └── premake5.lua ├── impl ├── alloc.h ├── quadtree.h ├── quadtreenode.h └── quadtreenode_impl.h ├── point.h ├── rect.h ├── tests ├── test1 │ └── main.cpp └── test2 │ └── main.cpp └── wiki └── AOI介绍.md /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 fananchong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aoi 2 | 3 | 相关文档:[wiki/AOI介绍.md](wiki/AOI介绍.md) 4 | 5 | ## 已完成 6 | 7 | - 实现传统四叉树功能 8 | - 树节点内存分配优化 9 | 10 | ## 目前性能参考 11 | 12 | ![图1](assets/1.jpg) 13 | 14 | 相关内容 | 说明 15 | --------- | ----------------------------------- 16 | 测试例子 | [tests/test2](tests/test2/main.cpp) 17 | 场景大小 | 1000 * 1000 18 | 游戏对象数 | 5000 19 | AOI半径 | 0.6 20 | 一次op | 所有游戏对象都做一次查询 21 | query1 | 从游戏对象所在节点做查询。条件不符,则改为从Root遍历四叉查询 22 | query2 | 从Root遍历四叉查询 23 | 24 | **测试机器配置** 25 | 26 | 阿里云ECS服务器,配置: 1 vCPU 2 GB (I/O优化) ecs.t5-lc1m2.small 1Mbps 27 | -------------------------------------------------------------------------------- /aoi.h: -------------------------------------------------------------------------------- 1 | #ifndef __AOI_SCENE_H__ 2 | #define __AOI_SCENE_H__ 3 | 4 | #include "point.h" 5 | #include "impl/quadtree.h" 6 | 7 | namespace aoi 8 | { 9 | class Object : public Point 10 | { 11 | public: 12 | Object() : Point(), mNode(nullptr), mQueryNext(nullptr), mItemNext(nullptr) {} 13 | Object(float x, float y) : Point(x, y), mNode(nullptr), mQueryNext(nullptr), mItemNext(nullptr) {} 14 | virtual ~Object() {} 15 | 16 | inline Object* Next() { return mQueryNext; } 17 | 18 | private: 19 | void* mNode; 20 | Object* mQueryNext; 21 | void* mItemNext; 22 | 23 | template friend class impl::QuadTree; 24 | template friend class impl::QuadTreeNode; 25 | }; 26 | 27 | template>> 28 | using Scene = impl::QuadTree; 29 | } 30 | 31 | #endif -------------------------------------------------------------------------------- /assets/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fananchong/aoi/61bf7a34e007954f43dfd90aff986da7430c4d4a/assets/1.jpg -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | /obj 2 | /.vs 3 | /*.sln 4 | /*.vcxproj 5 | /*.filters 6 | /*.user 7 | -------------------------------------------------------------------------------- /build/g.bat: -------------------------------------------------------------------------------- 1 | premake5 vs2017 2 | pause -------------------------------------------------------------------------------- /build/g.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./premake5 --os=linux gmake 3 | -------------------------------------------------------------------------------- /build/premake5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fananchong/aoi/61bf7a34e007954f43dfd90aff986da7430c4d4a/build/premake5 -------------------------------------------------------------------------------- /build/premake5.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fananchong/aoi/61bf7a34e007954f43dfd90aff986da7430c4d4a/build/premake5.exe -------------------------------------------------------------------------------- /build/premake5.lua: -------------------------------------------------------------------------------- 1 | workspace "aoi" 2 | configurations { "Debug", "Release" } 3 | platforms { "x32", "x64" } 4 | targetdir "../bin/" 5 | objdir "obj/%{cfg.platform}/%{cfg.buildcfg}" 6 | language "C++" 7 | includedirs { 8 | "..", 9 | } 10 | flags { 11 | "C++11", 12 | "StaticRuntime", 13 | } 14 | 15 | filter "configurations:Debug" 16 | defines { "_DEBUG" } 17 | symbols "On" 18 | libdirs { } 19 | filter "configurations:Release" 20 | defines { "NDEBUG" } 21 | libdirs { } 22 | optimize "On" 23 | filter { } 24 | 25 | 26 | 27 | project "aoi" 28 | kind "StaticLib" 29 | targetname "aoi" 30 | files { 31 | "../*.h", 32 | "../impl/*.h", 33 | } 34 | 35 | project "test1" 36 | kind "ConsoleApp" 37 | targetname "test1" 38 | libdirs { "../../bin" } 39 | files { 40 | "../tests/test1/*.h", 41 | "../tests/test1/*.cpp", 42 | } 43 | 44 | project "test2" 45 | kind "ConsoleApp" 46 | targetname "test2" 47 | libdirs { "../../bin" } 48 | files { 49 | "../tests/test2/*.h", 50 | "../tests/test2/*.cpp", 51 | } 52 | -------------------------------------------------------------------------------- /impl/alloc.h: -------------------------------------------------------------------------------- 1 | #ifndef __AOI_IMPL_ALLOC_H__ 2 | #define __AOI_IMPL_ALLOC_H__ 3 | 4 | #include 5 | #include 6 | 7 | namespace aoi 8 | { 9 | namespace impl 10 | { 11 | template 12 | class MemBase 13 | { 14 | public: 15 | MemBase() {} 16 | virtual ~MemBase() {} 17 | 18 | virtual void* _alloc(size_t size) = 0; 19 | virtual void _free(void* ptr) = 0; 20 | 21 | template 22 | inline T* New(Args... args) 23 | { 24 | void *ptr = _alloc(sizeof(T)); 25 | return new (ptr)T(args...); 26 | } 27 | inline void Delete(T* ptr) 28 | { 29 | ptr->~T(); 30 | _free(ptr); 31 | } 32 | }; 33 | 34 | template 35 | class Blocks : public MemBase 36 | { 37 | public: 38 | using TMallocFunc = std::function; 39 | using TFreeFunc = std::function; 40 | 41 | Blocks(const TMallocFunc& f1, const TFreeFunc& f2) 42 | : mBlocks(nullptr) 43 | , mHead(nullptr) 44 | , mMalloc(f1) 45 | , mFree(f2) 46 | { 47 | } 48 | 49 | ~Blocks() 50 | { 51 | while (mBlocks) 52 | { 53 | Item* next = mBlocks->next; 54 | mFree(mBlocks); 55 | mBlocks = next; 56 | } 57 | } 58 | 59 | inline void* _alloc(size_t size) 60 | { 61 | if (!mHead) 62 | { 63 | newBlock(); 64 | } 65 | void* ptr = mHead; 66 | mHead = mHead->next; 67 | return ptr; 68 | } 69 | 70 | inline void _free(void* ptr) 71 | { 72 | Item* p = (Item*)ptr; 73 | p->next = mHead; 74 | mHead = p; 75 | } 76 | 77 | private: 78 | void newBlock() 79 | { 80 | assert(!mHead); 81 | Item* ptr = (Item*)mMalloc(BlockSize); 82 | if (mBlocks) 83 | { 84 | ptr->next = mBlocks; 85 | mBlocks = ptr; 86 | } 87 | else 88 | { 89 | mBlocks = ptr; 90 | mBlocks->next = 0; 91 | } 92 | ptr = ptr + 1; 93 | 94 | #define PTR(N) ((Item*)((char*)ptr + (N) * sizeof(T))) 95 | mHead = PTR(0); 96 | size_t lstIndex = (BlockSize - sizeof(Item)) / sizeof(T) - 1; 97 | for (size_t i = 0; i < lstIndex; i++) 98 | { 99 | PTR(i)->next = PTR(i + 1); 100 | } 101 | PTR(lstIndex)->next = nullptr; 102 | #undef PTR 103 | } 104 | 105 | struct Item 106 | { 107 | Item* next; 108 | }; 109 | Item* mBlocks; 110 | Item* mHead; 111 | TMallocFunc mMalloc; 112 | TFreeFunc mFree; 113 | }; 114 | 115 | 116 | template 117 | class Mem : public Blocks 118 | { 119 | public: 120 | Mem() : Blocks(malloc, free) 121 | { 122 | } 123 | 124 | ~Mem() 125 | { 126 | } 127 | }; 128 | 129 | template 130 | class AlignedMem : public Blocks 131 | { 132 | public: 133 | #ifdef _MSC_VER 134 | AlignedMem() : Blocks(std::bind(_aligned_malloc, std::placeholders::_1, Alignment), _aligned_free) 135 | #else 136 | AlignedMem() : Blocks(std::bind(aligned_alloc, Alignment, std::placeholders::_1), free) 137 | #endif 138 | { 139 | } 140 | 141 | ~AlignedMem() 142 | { 143 | } 144 | }; 145 | } 146 | } 147 | 148 | #endif 149 | -------------------------------------------------------------------------------- /impl/quadtree.h: -------------------------------------------------------------------------------- 1 | #ifndef __AOI_IMPL_QUADTREE_H__ 2 | #define __AOI_IMPL_QUADTREE_H__ 3 | 4 | #include "alloc.h" 5 | #include "quadtreenode.h" 6 | 7 | namespace aoi 8 | { 9 | namespace impl 10 | { 11 | template 12 | class QuadTree { 13 | public: 14 | using TNode = QuadTreeNode; 15 | 16 | QuadTree() : mRoot(0, &mAlloc, NodeTypeLeaf, nullptr, Rect()) {} 17 | QuadTree(const Rect& bounds) : mRoot(0, &mAlloc, NodeTypeLeaf, nullptr, bounds) {} 18 | ~QuadTree() {} 19 | 20 | bool Insert(TItem* item) { return mRoot.Insert(item); } 21 | 22 | bool Remove(TItem* item) 23 | { 24 | TNode* node = (TNode*)item->mNode; 25 | return node ? node->Remove(item) : false; 26 | } 27 | 28 | TItem* Query(const Rect& area) 29 | { 30 | TItem* head = nullptr; 31 | TItem* tail = nullptr; 32 | mRoot.Query(area, head, tail); 33 | tail ? tail->mQueryNext = nullptr : 0; 34 | return head; 35 | } 36 | 37 | inline TItem* Query(TItem* source, float radius) 38 | { 39 | Rect area(source->X - radius, source->X + radius, source->Y - radius, source->Y + radius); 40 | return Query(source, area); 41 | } 42 | 43 | inline TItem* Query(TItem* source, float halfExtentsX, float halfExtentsY) 44 | { 45 | Rect area(source->X - halfExtentsX, source->X + halfExtentsX, source->Y - halfExtentsY, source->Y + halfExtentsY); 46 | return Query(source, area); 47 | } 48 | 49 | TItem* Query(TItem* source, const Rect& area) 50 | { 51 | TItem* head = nullptr; 52 | TItem* tail = nullptr; 53 | TNode* node = (TNode*)source->mNode; 54 | (node && node->mBounds.Contains(area)) ? node->Query(area, head, tail) : mRoot.Query(area, head, tail); 55 | tail ? tail->mQueryNext = nullptr : 0; 56 | return head; 57 | } 58 | 59 | bool Update(TItem* item) 60 | { 61 | TNode* node = (TNode*)item->mNode; 62 | if (node) 63 | { 64 | if (node->mBounds.Contains(item)) 65 | { 66 | return true; 67 | } 68 | node->Remove(item); 69 | } 70 | return mRoot.Insert(item); 71 | } 72 | 73 | unsigned GetItemCount() { return mRoot.GetItemCount(); } 74 | 75 | inline Rect& GetBounds() { return mRoot.mBounds; } 76 | inline void SetBounds(const Rect& rect) { mRoot.mBounds = rect; } 77 | 78 | private: 79 | TAlloc mAlloc; 80 | TNode mRoot; 81 | }; 82 | } 83 | } 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /impl/quadtreenode.h: -------------------------------------------------------------------------------- 1 | #ifndef __AOI_IMPL_QUADTREENODE_H__ 2 | #define __AOI_IMPL_QUADTREENODE_H__ 3 | 4 | #include "alloc.h" 5 | #include "../rect.h" 6 | #include 7 | 8 | namespace aoi 9 | { 10 | namespace impl 11 | { 12 | enum ENodeType 13 | { 14 | NodeTypeNormal = 0, // Non-leaf node 15 | NodeTypeLeaf = 1, // Leaf node 16 | }; 17 | 18 | const unsigned ChildrenNum = 4; 19 | 20 | template 21 | class QuadTreeNode 22 | { 23 | public: 24 | using TNode = QuadTreeNode; 25 | 26 | QuadTreeNode(unsigned level, MemBase* alloc, ENodeType type, QuadTreeNode* parent, const Rect& bounds); 27 | ~QuadTreeNode(); 28 | 29 | bool Insert(TItem* item); 30 | bool Remove(TItem* item); 31 | void Query(const Rect& area, TItem*& head, TItem*& tail); 32 | unsigned GetItemCount(); 33 | 34 | public: 35 | unsigned mLevel; // The level at which the current node is located 36 | Rect mBounds; // Node border range 37 | QuadTreeNode* mParent; // Parent node 38 | ENodeType mNodeType; // Node type 39 | QuadTreeNode* mChildrens[ChildrenNum]; // Child node 40 | unsigned mItemCount; // Number of items on the leaf node 41 | TItem* mItems; // Items on the leaf node 42 | 43 | private: 44 | MemBase* mAlloc; // Node allocator 45 | void split(); 46 | void tryMerge(); 47 | }; 48 | } 49 | } 50 | 51 | #include "quadtreenode_impl.h" 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /impl/quadtreenode_impl.h: -------------------------------------------------------------------------------- 1 | #include "../point.h" 2 | #include 3 | #include 4 | 5 | namespace aoi 6 | { 7 | namespace impl 8 | { 9 | template 10 | QuadTreeNode::QuadTreeNode(unsigned level, MemBase* alloc, ENodeType type, QuadTreeNode* parent, const Rect& bounds) 11 | : mLevel(level) 12 | , mAlloc(alloc) 13 | , mParent(parent) 14 | , mNodeType(type) 15 | , mBounds(bounds) 16 | , mItemCount(0) 17 | , mItems(nullptr) 18 | { 19 | memset(mChildrens, 0, sizeof(mChildrens)); 20 | } 21 | 22 | template 23 | QuadTreeNode::~QuadTreeNode() 24 | { 25 | 26 | } 27 | 28 | template 29 | bool QuadTreeNode::Insert(TItem* item) 30 | { 31 | if (mNodeType == NodeTypeNormal) 32 | { 33 | LABLE_NORMAL: 34 | int index = mBounds.GetQuadrant(item) - 1; 35 | return index >= 0 ? mChildrens[index]->Insert(item) : false; 36 | } 37 | else 38 | { 39 | if (mItemCount < NodeCapacity) 40 | { 41 | if (mBounds.Contains(item)) 42 | { 43 | mItemCount++; 44 | item->mItemNext = mItems; 45 | mItems = item; 46 | item->mNode = this; 47 | return true; 48 | } 49 | else 50 | { 51 | return false; 52 | } 53 | } 54 | else 55 | { 56 | if (mLevel + 1 >= LevelLimit) 57 | { 58 | return false; 59 | } 60 | split(); 61 | goto LABLE_NORMAL; 62 | } 63 | } 64 | } 65 | 66 | template 67 | void QuadTreeNode::split() 68 | { 69 | assert(mNodeType == NodeTypeLeaf); 70 | mNodeType = NodeTypeNormal; 71 | 72 | #ifdef _DEBUG 73 | printf("split, level:%u, bounds:(left %f, right %f, bottom %f, top %f)\n" 74 | , mLevel, mBounds.Left(), mBounds.Right(), mBounds.Bottom(), mBounds.Top()); 75 | #endif 76 | 77 | // First quadrant, top right 78 | Rect rect0(mBounds.MidX(), mBounds.Right(), mBounds.MidY(), mBounds.Top()); 79 | 80 | // Second quadrant, upper left 81 | Rect rect1(mBounds.Left(), mBounds.MidX(), mBounds.MidY(), mBounds.Top()); 82 | 83 | // Third quadrant, bottom left 84 | Rect rect2(mBounds.Left(), mBounds.MidX(), mBounds.Bottom(), mBounds.MidY()); 85 | 86 | // Fourth quadrant, bottom right 87 | Rect rect3(mBounds.MidX(), mBounds.Right(), mBounds.Bottom(), mBounds.MidY()); 88 | 89 | mChildrens[0] = mAlloc->New(mLevel + 1, mAlloc, NodeTypeLeaf, this, rect0); 90 | mChildrens[1] = mAlloc->New(mLevel + 1, mAlloc, NodeTypeLeaf, this, rect1); 91 | mChildrens[2] = mAlloc->New(mLevel + 1, mAlloc, NodeTypeLeaf, this, rect2); 92 | mChildrens[3] = mAlloc->New(mLevel + 1, mAlloc, NodeTypeLeaf, this, rect3); 93 | 94 | for (TItem* it = mItems; it;) 95 | { 96 | TItem* head = (TItem*)(it->mItemNext); 97 | int index = mBounds.GetQuadrant2(it) - 1; 98 | mChildrens[index]->Insert(it); 99 | it = head; 100 | } 101 | mItemCount = 0; 102 | mItems = nullptr; 103 | } 104 | 105 | template 106 | bool QuadTreeNode::Remove(TItem* item) 107 | { 108 | assert(mNodeType == NodeTypeLeaf); 109 | assert(mItems); 110 | TItem* pre = nullptr; 111 | for (TItem* it = mItems; it;) 112 | { 113 | TItem* head = (TItem*)(it->mItemNext); 114 | if (it == item) 115 | { 116 | --mItemCount; 117 | pre ? pre->mItemNext = it->mItemNext : mItems = (TItem*)(mItems->mItemNext); 118 | tryMerge(); 119 | return true; 120 | } 121 | else 122 | { 123 | pre = it; 124 | } 125 | it = head; 126 | } 127 | return false; 128 | } 129 | 130 | template 131 | void QuadTreeNode::tryMerge() 132 | { 133 | TNode* node = mParent; 134 | while (node) { 135 | assert(node->mNodeType == NodeTypeNormal); 136 | unsigned count = 0; 137 | auto& childrens = node->mChildrens; 138 | for (size_t i = 0; i < ChildrenNum; i++) 139 | { 140 | if (childrens[i]->mNodeType != NodeTypeLeaf) { 141 | return; 142 | } 143 | count += childrens[i]->mItemCount; 144 | } 145 | 146 | if (count <= NodeCapacity) 147 | { 148 | #ifdef _DEBUG 149 | printf("merge, level:%u, bounds:(left %f, right %f, bottom %f, top %f)\n" 150 | , mLevel, mBounds.Left(), mBounds.Right(), mBounds.Bottom(), mBounds.Top()); 151 | #endif 152 | node->mNodeType = NodeTypeLeaf; 153 | node->mItemCount = 0; 154 | node->mItems = nullptr; 155 | for (size_t i = 0; i < ChildrenNum; i++) 156 | { 157 | for (TItem* it = childrens[i]->mItems; it;) 158 | { 159 | TItem* head = (TItem*)(it->mItemNext); 160 | node->mItemCount++; 161 | it->mItemNext = node->mItems; 162 | node->mItems = it; 163 | it->mNode = node; 164 | it = head; 165 | } 166 | mAlloc->Delete(childrens[i]); 167 | } 168 | node = node->mParent; 169 | } 170 | else 171 | { 172 | break; 173 | } 174 | } 175 | } 176 | 177 | template 178 | void QuadTreeNode::Query(const Rect& area, TItem*& head, TItem*& tail) 179 | { 180 | if (mNodeType == NodeTypeNormal) 181 | { 182 | for (size_t i = 0; i < ChildrenNum; i++) 183 | { 184 | if (area.Intersects(mChildrens[i]->mBounds)) 185 | { 186 | mChildrens[i]->Query(area, head, tail); 187 | } 188 | } 189 | } 190 | else 191 | { 192 | for (TItem* it = mItems; it; it = (TItem*)(it->mItemNext)) 193 | { 194 | if (area.Contains(it)) 195 | { 196 | head ? (tail->mQueryNext = it, tail = it) : head = tail = it; 197 | } 198 | } 199 | } 200 | } 201 | 202 | template 203 | unsigned QuadTreeNode::GetItemCount() 204 | { 205 | unsigned count = 0; 206 | if (mNodeType == NodeTypeNormal) 207 | { 208 | for (size_t i = 0; i < ChildrenNum; i++) 209 | { 210 | count += mChildrens[i]->GetItemCount(); 211 | } 212 | } 213 | else 214 | { 215 | count += mItemCount; 216 | } 217 | return count; 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /point.h: -------------------------------------------------------------------------------- 1 | #ifndef __AOI_POINT_H__ 2 | #define __AOI_POINT_H__ 3 | 4 | namespace aoi 5 | { 6 | class Point 7 | { 8 | public: 9 | Point() : X(0), Y(0) {} 10 | Point(float x, float y) : X(x), Y(y) {} 11 | Point(const Point& other) : X(other.X), Y(other.Y) {} 12 | 13 | inline bool IsZero() const { return X == 0 && Y == 0; } 14 | 15 | public: 16 | float X; 17 | float Y; 18 | }; 19 | 20 | typedef Point Size; 21 | } 22 | 23 | #endif -------------------------------------------------------------------------------- /rect.h: -------------------------------------------------------------------------------- 1 | #ifndef __AOI_RECT_H__ 2 | #define __AOI_RECT_H__ 3 | 4 | #include "point.h" 5 | 6 | namespace aoi 7 | { 8 | enum EQuadrant 9 | { 10 | UnknowQuadrant = 0, 11 | RightTop = 1, // Top right: quadrant 1 12 | LeftTop = 2, // Top left: quadrant 2 13 | LeftDown = 3, // Bottom left: Quadrant 3 14 | RightDown = 4, // Bottom right: Quadrant 4 15 | }; 16 | 17 | class Rect 18 | { 19 | public: 20 | 21 | Rect() : mLeft(0), mRight(0), mTop(0), mBottom(0), mMidX(0), mMidY(0) { } 22 | 23 | Rect(float left, float right, float bottom, float top) 24 | : mLeft(left) 25 | , mRight(right) 26 | , mTop(top) 27 | , mBottom(bottom) 28 | , mMidX(left + (right - left) / 2) 29 | , mMidY(bottom + (top - bottom) / 2) 30 | { 31 | } 32 | 33 | Rect(const Rect& other) 34 | : mLeft(other.mLeft) 35 | , mRight(other.mRight) 36 | , mTop(other.mTop) 37 | , mBottom(other.mBottom) 38 | , mMidX(other.mMidX) 39 | , mMidY(other.mMidY) 40 | { 41 | } 42 | 43 | Rect(Point center, Size halfExtents) 44 | : mLeft(center.X - halfExtents.X) 45 | , mRight(center.X + halfExtents.X) 46 | , mTop(center.Y + halfExtents.Y) 47 | , mBottom(center.Y - halfExtents.Y) 48 | , mMidX(mLeft + (mRight - mLeft) / 2) 49 | , mMidY(mBottom + (mTop - mBottom) / 2) 50 | { 51 | } 52 | 53 | inline void Reset() 54 | { 55 | mLeft = 0; 56 | mRight = 0; 57 | mTop = 0; 58 | mBottom = 0; 59 | mMidX = 0; 60 | mMidY = 0; 61 | } 62 | 63 | inline void Reset(float left, float right, float bottom, float top) 64 | { 65 | mLeft = left; 66 | mRight = right; 67 | mTop = top; 68 | mBottom = bottom; 69 | mMidX = left + (right - left) / 2; 70 | mMidY = bottom + (top - bottom) / 2; 71 | } 72 | 73 | inline float Left() const { return mLeft; } 74 | inline float Right() const { return mRight; } 75 | inline float Bottom() const { return mBottom; } 76 | inline float Top() const { return mTop; } 77 | inline float MidX() const { return mMidX; } 78 | inline float MidY() const { return mMidY; } 79 | 80 | inline bool Contains(const Rect& Rect) const 81 | { 82 | return (mLeft <= Rect.mLeft 83 | && mBottom <= Rect.mBottom 84 | && Rect.mRight <= mRight 85 | && Rect.mTop <= mTop); 86 | } 87 | 88 | inline bool Contains(float x, float y) const 89 | { 90 | return (x >= mLeft && x <= mRight 91 | && y >= mBottom && y <= mTop); 92 | } 93 | 94 | inline bool Contains(const Point& point) const 95 | { 96 | return Contains(point.X, point.Y); 97 | } 98 | 99 | inline bool Contains(const Point* point) const 100 | { 101 | return Contains(point->X, point->Y); 102 | } 103 | 104 | inline bool Intersects(const Rect& Rect) const 105 | { 106 | return !(mRight < Rect.mLeft 107 | || Rect.mRight < mLeft 108 | || mTop < Rect.mBottom 109 | || Rect.mTop < mBottom); 110 | } 111 | 112 | inline EQuadrant GetQuadrant(const Point& point) 113 | { 114 | return Contains(point) ? GetQuadrant2(point) : UnknowQuadrant; 115 | } 116 | 117 | inline EQuadrant GetQuadrant(const Point* point) 118 | { 119 | return GetQuadrant(*point); 120 | } 121 | 122 | inline EQuadrant GetQuadrant2(const Point& point) 123 | { 124 | return (point.Y >= mMidY 125 | ? (point.X >= mMidX ? RightTop : LeftTop) 126 | : (point.X >= mMidX ? RightDown : LeftDown)); 127 | } 128 | 129 | inline EQuadrant GetQuadrant2(const Point* point) 130 | { 131 | return GetQuadrant2(*point); 132 | } 133 | 134 | private: 135 | float mLeft; 136 | float mRight; 137 | float mTop; 138 | float mBottom; 139 | float mMidX; 140 | float mMidY; 141 | }; 142 | } 143 | 144 | #endif 145 | -------------------------------------------------------------------------------- /tests/test1/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | long long get_tick_count(void) 8 | { 9 | typedef std::chrono::time_point microClock_type; 10 | microClock_type tp = std::chrono::time_point_cast(std::chrono::system_clock::now()); 11 | return tp.time_since_epoch().count(); 12 | } 13 | 14 | class A 15 | { 16 | public: 17 | A() :a1(0), b1(0) 18 | { 19 | //printf("call A()#1\n"); 20 | } 21 | 22 | A(int a, float b) :a1(a), b1(b) 23 | { 24 | //printf("call A()#2\n"); 25 | } 26 | 27 | ~A() 28 | { 29 | //printf("call ~A()\n"); 30 | } 31 | public: 32 | int a1; 33 | char c1; 34 | float b1; 35 | char d1[168]; 36 | }; 37 | 38 | typedef aoi::impl::QuadTreeNode NodeType; 39 | 40 | int main() { 41 | 42 | printf("sizeof(NodeType): %d\n", int(sizeof(NodeType))); 43 | aoi::impl::Mem mem; 44 | 45 | size_t COUNT = 10000000; 46 | 47 | auto t1 = get_tick_count(); 48 | for (size_t i = 0; i < COUNT; i++) 49 | { 50 | auto temp = new NodeType(0, &mem, aoi::impl::NodeTypeLeaf, nullptr, aoi::Rect()); 51 | delete temp; 52 | } 53 | auto t2 = get_tick_count(); 54 | printf("new cost:%lld\n", t2 - t1); 55 | 56 | aoi::impl::Mem mem2; 57 | t1 = get_tick_count(); 58 | for (size_t i = 0; i < COUNT; i++) 59 | { 60 | auto temp = mem2.New(0, &mem, aoi::impl::NodeTypeLeaf, nullptr, aoi::Rect()); 61 | mem2.Delete(temp); 62 | } 63 | t2 = get_tick_count(); 64 | printf("mem cost:%lld\n", t2 - t1); 65 | 66 | aoi::impl::AlignedMem mem3; 67 | t1 = get_tick_count(); 68 | for (size_t i = 0; i < COUNT; i++) 69 | { 70 | auto temp = mem3.New(0, &mem, aoi::impl::NodeTypeLeaf, nullptr, aoi::Rect()); 71 | mem3.Delete(temp); 72 | } 73 | t2 = get_tick_count(); 74 | printf("aligned mem cost:%lld\n", t2 - t1); 75 | 76 | return 0; 77 | } 78 | 79 | -------------------------------------------------------------------------------- /tests/test2/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | 10 | long long get_tick_count(void) 11 | { 12 | typedef std::chrono::time_point nanoClock_type; 13 | nanoClock_type tp = std::chrono::time_point_cast(std::chrono::system_clock::now()); 14 | return tp.time_since_epoch().count(); 15 | } 16 | 17 | #ifdef _DEBUG 18 | #define myassert(X) assert(X) 19 | #else 20 | #define myassert(X) \ 21 | if (!(X))\ 22 | {\ 23 | throw "error!";\ 24 | } 25 | #endif 26 | 27 | // Need to inherit aoi::Object 28 | class A : public aoi::Object 29 | { 30 | public: 31 | A(float x, float y) : aoi::Object(x, y) {} 32 | 33 | private: 34 | }; 35 | 36 | typedef aoi::Scene SceneType; 37 | 38 | void _test_add(SceneType& scn, std::vector& items) 39 | { 40 | float x = float(rand() % int(scn.GetBounds().Right() - scn.GetBounds().Left())) + scn.GetBounds().Left(); 41 | float y = float(rand() % int(scn.GetBounds().Top() - scn.GetBounds().Bottom())) + scn.GetBounds().Bottom(); 42 | A* temp = new A(x, y); 43 | myassert(scn.Insert(temp)); 44 | items.push_back(temp); 45 | } 46 | 47 | void _test_delete(SceneType& scn, std::vector& items, size_t count) 48 | { 49 | size_t itemsNum = items.size(); 50 | if (itemsNum == 0) 51 | { 52 | return; 53 | } 54 | std::random_shuffle(items.begin(), items.end()); 55 | for (size_t i = 0; i < (std::min)(count, itemsNum); i++) 56 | { 57 | A* temp = items.back(); 58 | myassert(scn.Remove(temp)); 59 | delete(temp); 60 | items.pop_back(); 61 | } 62 | } 63 | 64 | void _test_query(SceneType& scn, std::vector& items) 65 | { 66 | unsigned testCount = 0; 67 | aoi::Rect queryArea = aoi::Rect( 68 | float(rand() % 10), 69 | float(rand() % int(scn.GetBounds().Right() - scn.GetBounds().Left())) + scn.GetBounds().Left(), 70 | float(rand() % 10), 71 | float(rand() % int(scn.GetBounds().Top() - scn.GetBounds().Bottom())) + scn.GetBounds().Bottom()); 72 | 73 | for (size_t i = 0; i < items.size(); i++) 74 | { 75 | if (queryArea.Contains(items[i])) 76 | { 77 | testCount++; 78 | } 79 | } 80 | 81 | unsigned findCount = 0; 82 | aoi::Object* ptr = scn.Query(queryArea); 83 | while (ptr) 84 | { 85 | findCount++; 86 | ptr = ptr->Next(); 87 | } 88 | //printf("find obj count:%u, test count:%u, total count:%u\n", findCount, testCount, scn.GetItemCount()); 89 | myassert(testCount == findCount); 90 | } 91 | 92 | void _test_query(SceneType& scn, std::vector& items, float radius) 93 | { 94 | unsigned testCount = 0; 95 | 96 | size_t index = rand() % items.size(); 97 | aoi::Rect queryArea = aoi::Rect( 98 | items[index]->X - radius, 99 | items[index]->X + radius, 100 | items[index]->Y - radius, 101 | items[index]->Y + radius); 102 | 103 | for (size_t i = 0; i < items.size(); i++) 104 | { 105 | if (queryArea.Contains(items[i])) 106 | { 107 | testCount++; 108 | } 109 | } 110 | 111 | unsigned findCount = 0; 112 | aoi::Object* ptr = scn.Query(items[index], radius); 113 | while (ptr) 114 | { 115 | findCount++; 116 | ptr = ptr->Next(); 117 | } 118 | //printf("find obj count:%u, test count:%u, total count:%u\n", findCount, testCount, scn.GetItemCount()); 119 | myassert(testCount == findCount); 120 | } 121 | 122 | 123 | void test1() 124 | { 125 | aoi::Rect rect(0, 1000, 0, 1000); 126 | SceneType scn(rect); 127 | 128 | // Test insertion 129 | std::vector items; 130 | for (size_t i = 0; i < 8192; i++) 131 | { 132 | _test_add(scn, items); 133 | } 134 | 135 | // Test query 136 | for (size_t i = 0; i < 1000; i++) 137 | { 138 | _test_query(scn, items); 139 | _test_query(scn, items, float(rand() % 200 + 50)); 140 | } 141 | 142 | 143 | // Test delete 144 | unsigned itemsNum = items.size(); 145 | _test_delete(scn, items, itemsNum); 146 | items.clear(); 147 | //printf("delete obj count:%u, total count:%u\n", itemsNum, scn.GetItemCount()); 148 | } 149 | 150 | void test2() 151 | { 152 | aoi::Rect rect(0, 1000, 0, 1000); 153 | SceneType scn(rect); 154 | 155 | std::vector items; 156 | while (true) 157 | { 158 | unsigned op = rand() % 10; 159 | 160 | if (op <= 6) 161 | { 162 | _test_add(scn, items); 163 | } 164 | else if (op <= 8) 165 | { 166 | int itemsNum = (int)items.size(); 167 | _test_delete(scn, items, itemsNum % 3 + 1); 168 | } 169 | else 170 | { 171 | _test_query(scn, items); 172 | _test_query(scn, items, float(rand() % 200 + 50)); 173 | } 174 | } 175 | } 176 | 177 | void test3() 178 | { 179 | unsigned w = 1000; 180 | unsigned h = 1000; 181 | float r = 0.6f; 182 | 183 | aoi::Rect rect(0, float(w), 0, float(h)); 184 | SceneType scn(rect); 185 | 186 | 187 | size_t PLAYER_COUNT = 5000; 188 | size_t QUERYCOUNT = 10000; 189 | 190 | auto t1 = get_tick_count(); 191 | for (size_t i = 0; i < PLAYER_COUNT; i++) 192 | { 193 | rand(); 194 | rand(); 195 | } 196 | auto t2 = get_tick_count(); 197 | //printf("rand cost:%lldns %fns/op\n", t2 - t1, float(t2 - t1) / COUNT); 198 | long long ttr = t2 - t1; 199 | long long ttrop = (long long)(float(t2 - t1) / PLAYER_COUNT); 200 | 201 | printf("width:%u, heigth:%u\n", w, h); 202 | printf("player count: %u, query redius: %f\n", unsigned(PLAYER_COUNT), r); 203 | 204 | std::vector items; 205 | t1 = get_tick_count(); 206 | for (size_t i = 0; i < PLAYER_COUNT; i++) 207 | { 208 | _test_add(scn, items); 209 | } 210 | t2 = get_tick_count(); 211 | printf("insert cost:%12lldns %12.3fns/op\n", t2 - t1 - ttr, float(t2 - t1) / PLAYER_COUNT - ttrop); 212 | 213 | t1 = get_tick_count(); 214 | for (size_t i = 0; i < QUERYCOUNT; i++) 215 | { 216 | for (size_t j = 0; j < PLAYER_COUNT; j++) 217 | { 218 | aoi::Rect rect(items[j]->X - r, items[j]->X + r, items[j]->Y - r, items[j]->Y + r); 219 | aoi::Object* ptr = scn.Query(items[j], float(r)); 220 | } 221 | } 222 | t2 = get_tick_count(); 223 | printf("query1 cost:%12lldns %12.3fns/op %12.3fms/op\n", t2 - t1, float(t2 - t1) / QUERYCOUNT, float(t2 - t1) / QUERYCOUNT / 1000000); 224 | 225 | t1 = get_tick_count(); 226 | for (size_t i = 0; i < QUERYCOUNT; i++) 227 | { 228 | for (size_t j = 0; j < PLAYER_COUNT; j++) 229 | { 230 | aoi::Rect rect(items[j]->X - r, items[j]->X + r, items[j]->Y - r, items[j]->Y + r); 231 | aoi::Object* ptr = scn.Query(rect); 232 | } 233 | } 234 | t2 = get_tick_count(); 235 | printf("query2 cost:%12lldns %12.3fns/op %12.3fms/op\n", t2 - t1, float(t2 - t1) / QUERYCOUNT, float(t2 - t1) / QUERYCOUNT / 1000000); 236 | } 237 | 238 | void test4() 239 | { 240 | aoi::Rect rect(0, 1000, 0, 1000); 241 | SceneType scn(rect); 242 | for (size_t i = 0; i < 10000; i++) 243 | { 244 | float x = 0; 245 | float y = 0; 246 | A* temp = new A(x, y); 247 | myassert(scn.Insert(temp)); 248 | } 249 | } 250 | 251 | int main() 252 | { 253 | srand((unsigned)time(0)); 254 | 255 | //test1(); 256 | //test2(); 257 | test3(); 258 | //test4(); 259 | return 0; 260 | } 261 | -------------------------------------------------------------------------------- /wiki/AOI介绍.md: -------------------------------------------------------------------------------- 1 | ## 前提假设 2 | 3 | **本文讨论的AOI,场景中的游戏对象均有碰撞。** 4 | 5 | **因此,不存在所有游戏对象重叠在一起的情况。** 6 | 7 | 8 | ## 讨论内容 9 | 10 | 本文主要讨论以下AOI算法: 11 | - 基于Tile的灯塔算法 12 | - 基于QuadTree算法 13 | 14 | 15 | 并按以下方面来比较算法优劣: 16 | - 占用内存 17 | - Add操作 18 | - Leave操作 19 | - Move操作 20 | - AOI操作 21 | 22 | ## 基础算法对比 23 | 24 | 算法 | 占用内存 | Add | Leave | Move | AOI | 说明 25 | --------------|----------|-----|-------|------|-----|------ 26 | Tile算法 | O(W*H) | O(1) | O(1) | O(T) | O(T)
| 2维数组。
第1维表达所有Tile;
第2维表达每个Tile上的游戏对象列表 27 | QuadTree算法 | O(N) | O(logN) | O(1) | 通常O(L);
跨边界O(logN)+O(L) | O(logN) + O(L) | 四叉树。
叶子节点上有游戏对象列表;
叶子节点游戏对象超过指定数量限制,则分裂子节点
叶子节点有游戏对象Leave,可能触发4兄弟节点合并
节点边界上的游戏对象放在父节点上
AOI从Root节点开始做广度遍历 28 | 29 | 其中: 30 | - N为游戏对象数量。 31 | - W为地图宽 32 | - H为地图长 33 | - T为Tile上能容纳的游戏对象数量 34 | - L为叶子节点区域能容纳的游戏对象数量 35 | 36 | 有上可以看出: 37 | - Tile算法优势在于CPU消耗低,缺点是内存占用大 38 | - QuadTree算法优势在于内存占用低,缺点是CPU消耗大 39 | 40 | 总体而言,QuadTree算法占优势,特别是人数少,且地图很大的情况。 41 | --------------------------------------------------------------------------------