├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── img └── capture.png ├── include ├── aabb.h ├── glm_vec.h ├── isect2d.h ├── obb.h ├── vec.h └── vec2.h └── tests └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # vim 8 | *.swp 9 | 10 | # build 11 | build/ 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | 25 | # Compiled Static libraries 26 | *.lai 27 | *.la 28 | *.a 29 | *.lib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | 36 | # CMake 37 | CMakeCache.txt 38 | CMakeFiles 39 | Makefile 40 | cmake_install.cmake 41 | install_manifest.txt 42 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(isect2d) 3 | 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11 -g -O0") 5 | 6 | find_package(PkgConfig REQUIRED) 7 | pkg_search_module(GLFW REQUIRED glfw3) 8 | 9 | set(EXECUTABLE_NAME isect2d.out) 10 | 11 | if(NOT GLFW_FOUND) 12 | message(SEND_ERROR "GLFW not found") 13 | return() 14 | else() 15 | include_directories(${GLFW_INCLUDE_DIRS}) 16 | message(STATUS "Found GLFW ${GLFW_PREFIX}") 17 | endif() 18 | 19 | include_directories(include) 20 | 21 | add_executable(${EXECUTABLE_NAME} tests/main.cpp include/isect2d.h) 22 | 23 | target_link_libraries(${EXECUTABLE_NAME} ${GLFW_STATIC_LIBRARIES}) 24 | 25 | if(APPLE) 26 | target_link_libraries(${EXECUTABLE_NAME} "-framework OpenGL") 27 | endif() 28 | 29 | if(UNIX AND NOT APPLE) 30 | target_link_libraries(${EXECUTABLE_NAME} "-lGL") 31 | endif() 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tangram 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # isect2d 2 | 3 | ![00](img/capture.png) 4 | 5 | Collision detection algorithms, using basic tile grid for broad-phase and separating axes theorem 6 | (SAT) for narrow-phase. 7 | 8 | Example 9 | ======= 10 | 11 | ```cpp 12 | // Provide your own vector 2d implementation 13 | #ifdef USE_GLM 14 | using Vec2 = glm::vec2; 15 | #else 16 | using Vec2 = isect2d::Vec2; 17 | #endif 18 | 19 | std::vector obbs; 20 | std::vector aabbs; 21 | 22 | // Init the set of OBBs from the AABBs set 23 | for (auto& obb : obbs) { 24 | auto aabb = obb.getExtent(); 25 | aabbs.push_back(aabb); 26 | } 27 | ``` 28 | 29 | Using the Isect2D context 30 | ------------------------- 31 | 32 | ```cpp 33 | isect2d::ISect2D context({16, 16}, {800, 600}); 34 | 35 | context.clear(); 36 | 37 | // Broadphase 38 | context.intersect(aabbs); 39 | 40 | // Narrow-phase 41 | for (auto& pair : context.pairs) { 42 | if (intersect(obbs[pair.first], obbs[pair.second])) { 43 | // Both oriented bounding boxes collide 44 | } 45 | } 46 | ``` 47 | 48 | Using the naive grid based implementation 49 | ----------------------------------------- 50 | 51 | ```cpp 52 | // Broadphase 53 | auto pairs = intersect( 54 | aabbs, 55 | {4, 4}, // split resolution 56 | {800, 600}, // screen resolution 57 | ); 58 | 59 | // Narrow-phase 60 | for (auto& pair : pairs) { 61 | if (intersect(obbs[pair.first], obbs[pair.second])) { 62 | // Both oriented bounding boxes collide 63 | } 64 | } 65 | ``` 66 | -------------------------------------------------------------------------------- /img/capture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tangrams/isect2d/2e1a75cee09d9949900e926c61c86505b09205b2/img/capture.png -------------------------------------------------------------------------------- /include/aabb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace isect2d { 9 | 10 | enum Dimension { 11 | X, Y 12 | }; 13 | 14 | template 15 | struct AABB { 16 | AABB() : AABB(INT_MAX, INT_MAX, -INT_MAX, -INT_MAX) {} 17 | 18 | AABB(float _minx, float _miny, float _maxx, float _maxy) 19 | : min(_minx, _miny), max(_maxx, _maxy) { 20 | } 21 | 22 | inline bool intersect(const AABB& _other) const { 23 | return _other.max.x >= min.x && 24 | _other.max.y >= min.y && 25 | _other.min.x <= max.x && 26 | _other.min.y <= max.y; 27 | } 28 | 29 | const V& getMin() const { 30 | return min; 31 | } 32 | 33 | const V& getMax() const { 34 | return max; 35 | } 36 | 37 | V getCentroid() const { 38 | return (min + max) * 0.5; 39 | } 40 | 41 | Dimension maxExtent() { 42 | V diagonal = max - min; 43 | if(diagonal.x > diagonal.y) { 44 | return X; 45 | } 46 | return Y; 47 | } 48 | 49 | void include(float _x, float _y) { 50 | min.x = std::min(min.x, _x); 51 | min.y = std::min(min.y, _y); 52 | max.x = std::max(max.x, _x); 53 | max.y = std::max(max.y, _y); 54 | } 55 | 56 | void* m_userData = nullptr; 57 | 58 | V min; 59 | V max; 60 | 61 | }; 62 | 63 | template 64 | static inline AABB unionAABB(const AABB& _aabb1, const AABB& _aabb2) { 65 | AABB aabb; 66 | 67 | aabb.min.x = std::min(_aabb1.min.x, _aabb2.min.x); 68 | aabb.min.y = std::min(_aabb1.min.y, _aabb2.min.y); 69 | aabb.max.x = std::max(_aabb1.max.x, _aabb2.max.x); 70 | aabb.max.y = std::max(_aabb1.max.y, _aabb2.max.y); 71 | 72 | return aabb; 73 | } 74 | 75 | template 76 | inline bool operator==(const AABB& lh, const AABB& rh) { 77 | return lh.min == rh.min && lh.max == rh.max; 78 | } 79 | 80 | template 81 | inline bool operator!=(const AABB& lh, const AABB& rh) { 82 | return !(lh == rh); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /include/glm_vec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "glm/glm.hpp" 4 | 5 | namespace isect2d { 6 | 7 | template<> 8 | inline glm::vec2 normalize(const glm::vec2& _v) { 9 | return glm::normalize(_v); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /include/isect2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include // for hash function 9 | #include // for std::max 10 | 11 | #include "aabb.h" 12 | #include "obb.h" 13 | 14 | 15 | namespace std { 16 | template <> 17 | struct hash> { 18 | size_t operator()(const pair& k) const { 19 | return hash()(int64_t(k.first) << 32 | k.second); 20 | } 21 | }; 22 | } 23 | 24 | namespace isect2d { 25 | 26 | template 27 | static constexpr const T clamp(T val, T min, T max) { 28 | return val < min ? min 29 | : val > max ? max : val; 30 | } 31 | 32 | template 33 | struct ISect2D { 34 | using i32 = int_fast32_t; 35 | 36 | struct Pair { 37 | Pair(int _a, int _b, int _next) : first(_a), second(_b), next(_next) {} 38 | int first; 39 | int second; 40 | 41 | int next; 42 | }; 43 | 44 | i32 split_x = 0; 45 | i32 split_y = 0; 46 | i32 res_x = 0; 47 | i32 res_y = 0; 48 | i32 xpad = 0; 49 | i32 ypad = 0; 50 | 51 | std::vector> gridAABBs; 52 | std::vector pairs; 53 | std::vector pairMap; 54 | std::vector> aabbs; 55 | 56 | ISect2D(size_t collisionHashSize = 2048) { 57 | pairMap.assign(collisionHashSize, -1); 58 | } 59 | 60 | void resize(const V _split, const V _resolution) { 61 | split_x = _split.x; 62 | split_y = _split.y; 63 | res_x = _resolution.x; 64 | res_y = _resolution.y; 65 | 66 | // Ensure no division by zero 67 | split_x = std::max(split_x, static_cast(1)); 68 | split_y = std::max(split_y, static_cast(1)); 69 | res_x = std::max(res_x, split_x); 70 | res_y = std::max(res_y, split_y); 71 | 72 | xpad = res_x / split_x; 73 | ypad = res_y / split_y; 74 | 75 | gridAABBs.resize(split_x * split_y); 76 | } 77 | 78 | void clear() { 79 | pairs.clear(); 80 | pairMap.assign(pairMap.size(), -1); 81 | 82 | aabbs.clear(); 83 | 84 | for (auto& grid : gridAABBs){ 85 | grid.clear(); 86 | } 87 | } 88 | 89 | void intersect(const AABB& _aabb, 90 | std::function& _aabb, const AABB& _other)> _cb, 91 | bool _insert = true) { 92 | 93 | i32 x1 = _aabb.min.x / xpad; 94 | i32 y1 = _aabb.min.y / ypad; 95 | i32 x2 = _aabb.max.x / xpad + 1; 96 | i32 y2 = _aabb.max.y / ypad + 1; 97 | 98 | x1 = clamp(x1, i32(0), split_x-1); 99 | y1 = clamp(y1, i32(0), split_y-1); 100 | x2 = clamp(x2, i32(1), split_x); 101 | y2 = clamp(y2, i32(1), split_y); 102 | 103 | for (i32 y = y1; y < y2; y++) { 104 | for (i32 x = x1; x < x2; x++) { 105 | 106 | auto& v = gridAABBs[x + y * split_x]; 107 | 108 | for (int32_t i : v) { 109 | const auto& other = aabbs[i]; 110 | 111 | if (_aabb.intersect(other)) { 112 | if (!_cb(_aabb, other)) { 113 | return; 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | if (_insert) { 121 | aabbs.push_back(_aabb); 122 | int index = aabbs.size() - 1; 123 | 124 | for (i32 y = y1; y < y2; y++) { 125 | for (i32 x = x1; x < x2; x++) { 126 | gridAABBs[x + y * split_x].push_back(index); 127 | } 128 | } 129 | } 130 | } 131 | 132 | void insert(const AABB& _aabb) { 133 | i32 x1 = _aabb.min.x / xpad; 134 | i32 y1 = _aabb.min.y / ypad; 135 | i32 x2 = _aabb.max.x / xpad + 1; 136 | i32 y2 = _aabb.max.y / ypad + 1; 137 | 138 | x1 = clamp(x1, i32(0), split_x-1); 139 | y1 = clamp(y1, i32(0), split_y-1); 140 | x2 = clamp(x2, i32(1), split_x); 141 | y2 = clamp(y2, i32(1), split_y); 142 | 143 | aabbs.push_back(_aabb); 144 | int index = aabbs.size() - 1; 145 | 146 | for (i32 y = y1; y < y2; y++) { 147 | for (i32 x = x1; x < x2; x++) { 148 | gridAABBs[x + y * split_x].push_back(index); 149 | } 150 | } 151 | } 152 | 153 | /* 154 | * Performs broadphase collision detection on _aabbs dividing the 155 | * screen size _resolution by _split on X and Y dimension Returns the 156 | * set of colliding pairs in the _aabbs container 157 | */ 158 | void intersect(const std::vector>& _aabbs) { 159 | 160 | clear(); 161 | 162 | size_t index = 0; 163 | for (const auto& aabb : _aabbs) { 164 | i32 x1 = aabb.min.x / xpad; 165 | i32 y1 = aabb.min.y / ypad; 166 | i32 x2 = aabb.max.x / xpad + 1; 167 | i32 y2 = aabb.max.y / ypad + 1; 168 | 169 | x1 = clamp(x1, i32(0), split_x-1); 170 | y1 = clamp(y1, i32(0), split_y-1); 171 | x2 = clamp(x2, i32(1), split_x); 172 | y2 = clamp(y2, i32(1), split_y); 173 | 174 | for (i32 y = y1; y < y2; y++) { 175 | for (i32 x = x1; x < x2; x++) { 176 | gridAABBs[x + y * split_x].push_back(index); 177 | } 178 | } 179 | 180 | index++; 181 | } 182 | 183 | for (auto& v : gridAABBs) { 184 | if (v.empty()) { continue; } 185 | // check all items against each other 186 | for (size_t j = 0; j < v.size()-1; ++j) { 187 | const auto& a(_aabbs[v[j]]); 188 | 189 | for (size_t k = j + 1; k < v.size(); ++k) { 190 | const auto& b(_aabbs[v[k]]); 191 | 192 | if (a.intersect(b)) { 193 | size_t key = hash_key(hash_int(v[j])<<32 | hash_int(v[k])); 194 | key &= (pairMap.size()-1); 195 | 196 | int i = pairMap[key]; 197 | 198 | while (i != -1) { 199 | if (pairs[i].first == v[j] && pairs[i].second == v[k]) { 200 | // found 201 | break; 202 | } 203 | i = pairs[i].next; 204 | } 205 | 206 | if (i == -1) { 207 | pairs.push_back(Pair{v[j], v[k], pairMap[key]}); 208 | pairMap[key] = pairs.size()-1; 209 | } 210 | } 211 | } 212 | } 213 | v.clear(); 214 | } 215 | } 216 | 217 | private: 218 | // from fontstash 219 | static uint64_t hash_int(uint32_t a) { 220 | a += ~(a<<15); 221 | a ^= (a>>10); 222 | a += (a<<3); 223 | a ^= (a>>6); 224 | a += ~(a<<11); 225 | a ^= (a>>16); 226 | return a; 227 | } 228 | 229 | // from https://gist.github.com/badboy/6267743 230 | // - 64 bit to 32 bit Hash Functions 231 | static uint32_t hash_key(uint64_t key) { 232 | key = (~key) + (key << 18); 233 | key = key ^ (key >> 31); 234 | key = key * 21; 235 | key = key ^ (key >> 11); 236 | key = key + (key << 6); 237 | key = key ^ (key >> 22); 238 | return key; 239 | } 240 | }; 241 | 242 | /* 243 | * Performs broadphase collision detection on _aabbs dividing the 244 | * screen size _resolution by _split on X and Y dimension Returns the 245 | * set of colliding pairs in the _aabbs container 246 | * 247 | * NB: Likely to be slower than ISect2D::intersect() ! 248 | */ 249 | template 250 | static std::unordered_set> intersect(const std::vector>& _aabbs, 251 | V _split, V _resolution) { 252 | struct AABBPair { 253 | const AABB* aabb; 254 | unsigned int index; 255 | }; 256 | 257 | std::unordered_set> pairs; 258 | int n = int(_split.x * _split.y); 259 | std::vector* gridAABBs = new std::vector[n]; 260 | 261 | const short xpad = short(ceilf(_resolution.x / _split.x)); 262 | const short ypad = short(ceilf(_resolution.y / _split.y)); 263 | 264 | short x = 0, y = 0; 265 | 266 | for (int j = 0; j < _split.y; ++j) { 267 | for (int i = 0; i < _split.x; ++i) { 268 | AABB cell(x, y, x + xpad, y + ypad); 269 | 270 | for (unsigned int index = 0; index < _aabbs.size(); ++index) { 271 | const AABB* aabb = &_aabbs[index]; 272 | // test the aabb against the current grid cell 273 | if (cell.intersect(*aabb)) { 274 | gridAABBs[int(i + j * _split.x)].push_back({aabb, index}); 275 | } 276 | } 277 | x += xpad; 278 | 279 | if (x >= _resolution.x) { 280 | x = 0; 281 | y += ypad; 282 | } 283 | } 284 | } 285 | 286 | for (int i = 0; i < n; ++i) { 287 | auto& v = gridAABBs[i]; 288 | 289 | for (size_t j = 0; j < v.size(); ++j) { 290 | for (size_t k = j + 1; k < v.size(); ++k) { 291 | 292 | if (v[j].index != v[k].index && v[j].aabb->intersect(*v[k].aabb)) { 293 | pairs.insert({ v[j].index, v[k].index }); 294 | } 295 | } 296 | } 297 | } 298 | 299 | delete[] gridAABBs; 300 | return pairs; 301 | } 302 | 303 | /* 304 | * Performs bruteforce broadphase collision detection on _aabbs 305 | * Returns the set of colliding pairs in the _aabbs container 306 | */ 307 | template 308 | static std::unordered_set> intersect(const std::vector>& _aabbs) { 309 | std::unordered_set> pairs; 310 | 311 | if (_aabbs.size() == 0) { 312 | return pairs; 313 | } 314 | 315 | for (size_t i = 0; i < _aabbs.size(); ++i) { 316 | for (size_t j = i + 1; j < _aabbs.size(); ++j) { 317 | if (_aabbs[i].intersect(_aabbs[j])) { 318 | pairs.insert({ i, j }); 319 | } 320 | } 321 | } 322 | 323 | return pairs; 324 | } 325 | 326 | } 327 | -------------------------------------------------------------------------------- /include/obb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "vec.h" 6 | 7 | namespace isect2d { 8 | 9 | template 10 | struct OBB { 11 | 12 | OBB() : m_width(0), m_height(0) {} 13 | 14 | OBB(float _cx, float _cy, float _a, float _w, float _h) : 15 | m_width(_w), m_height(_h), 16 | m_centroid(V(_cx, _cy)) { 17 | 18 | rotate(_a); 19 | update(); 20 | } 21 | 22 | OBB(V _center, V _normal, float _w, float _h) : 23 | m_width(_w), m_height(_h), 24 | m_centroid(_center), 25 | m_axes(_normal) { 26 | 27 | update(); 28 | } 29 | 30 | void move(const float _px, const float _py) { 31 | m_centroid = V(_px, _py); 32 | 33 | update(); 34 | } 35 | 36 | void rotate(float _angle) { 37 | float cosa = cos(-_angle); 38 | float sina = sin(-_angle); 39 | 40 | m_axes = { -cosa, sina }; 41 | 42 | update(); 43 | } 44 | 45 | float getAngle() const { 46 | return -atan2(-m_axes.y, m_axes.x); 47 | } 48 | 49 | const std::array& getQuad() const { 50 | return m_quad; 51 | } 52 | 53 | V getAxes() const{ 54 | return m_axes; 55 | } 56 | 57 | V getCentroid() const { 58 | return m_centroid; 59 | } 60 | 61 | float getWidth() const { 62 | return m_width; 63 | } 64 | 65 | float getHeight() const { 66 | return m_height; 67 | } 68 | 69 | float radius() const { 70 | V extent(m_width, m_height); 71 | 72 | return extent.length(); 73 | } 74 | 75 | AABB getExtent() const { 76 | float inf = std::numeric_limits::infinity(); 77 | float aabb[4] = { inf, inf, -inf, -inf }; 78 | 79 | for (int i = 0; i < 4; ++i) { 80 | aabb[0] = std::min(m_quad[i].x, aabb[0]); 81 | aabb[1] = std::min(m_quad[i].y, aabb[1]); 82 | aabb[2] = std::max(m_quad[i].x, aabb[2]); 83 | aabb[3] = std::max(m_quad[i].y, aabb[3]); 84 | } 85 | 86 | return { aabb[0], aabb[1], aabb[2], aabb[3] }; 87 | } 88 | 89 | private: 90 | 91 | void update() { 92 | V x = m_axes * (m_width / 2); 93 | V y = V{ -m_axes.y, m_axes.x } * (m_height / 2); 94 | 95 | m_quad[0] = m_centroid - x - y; // lower-left 96 | m_quad[1] = m_centroid + x - y; // lower-right 97 | m_quad[2] = m_centroid + x + y; // uper-right 98 | m_quad[3] = m_centroid - x + y; // uper-left 99 | } 100 | 101 | float m_width; 102 | float m_height; 103 | V m_centroid; 104 | 105 | V m_axes; 106 | std::array m_quad; 107 | 108 | }; 109 | 110 | template 111 | inline bool operator==(const OBB& lh, const OBB& rh) { 112 | return lh.getCentroid() == rh.getCentroid() && lh.m_axes == rh.m_axes; 113 | } 114 | 115 | template 116 | inline bool operator!=(const OBB& lh, const OBB& rh) { 117 | return !(lh == rh); 118 | } 119 | 120 | template 121 | static std::pair projectToAxis(const OBB& _obb, const V& axis) { 122 | using Value = typename V::value_type; 123 | 124 | Value inf = std::numeric_limits::infinity(); 125 | Value min = inf; 126 | Value max = -inf; 127 | 128 | const std::array& p = _obb.getQuad(); 129 | 130 | for (int i = 0; i < 4; ++i) { 131 | Value d = dot(p[i], axis); 132 | 133 | min = std::min(min, d); 134 | max = std::max(max, d); 135 | } 136 | 137 | return { min, max }; 138 | } 139 | 140 | template 141 | inline static bool axisCollide(const OBB& _a, const OBB& _b, V axes) { 142 | 143 | using Value = typename V::value_type; 144 | using Result = std::pair; 145 | 146 | Result aproj = projectToAxis(_a, axes); 147 | Result bproj = projectToAxis(_b, axes); 148 | 149 | if (aproj.second < bproj.first || bproj.second < aproj.first) { 150 | return false; 151 | } 152 | 153 | axes = V{-axes.y, axes.x}; 154 | 155 | aproj = projectToAxis(_a, axes); 156 | bproj = projectToAxis(_b, axes); 157 | 158 | if (aproj.second < bproj.first || bproj.second < aproj.first) { 159 | return false; 160 | } 161 | 162 | return true; 163 | } 164 | 165 | template 166 | inline static bool intersect(const OBB& _a, const OBB& _b) { 167 | return axisCollide(_a, _b, _a.getAxes()) && axisCollide(_a, _b, _b.getAxes()); 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /include/vec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace isect2d { 4 | 5 | // TODO there is probably a better way do define the return type 6 | template 7 | decltype(V::x) dot(const V& _v, const V& _b); 8 | 9 | template 10 | V normalize(const V& _v); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /include/vec2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "vec.h" 5 | 6 | namespace isect2d { 7 | 8 | struct Vec2 { 9 | float x, y; 10 | 11 | Vec2(const Vec2& other) { 12 | x = other.x; 13 | y = other.y; 14 | } 15 | 16 | Vec2(float _x = 0, float _y = 0) { 17 | x = _x; 18 | y = _y; 19 | } 20 | 21 | Vec2 operator+(const Vec2& _b) const { 22 | return Vec2(x + _b.x, y + _b.y); 23 | } 24 | 25 | Vec2 operator-(const Vec2& _b) const { 26 | return Vec2(x - _b.x, y - _b.y); 27 | } 28 | 29 | Vec2 operator*(float _b) const { 30 | return Vec2(x * _b, y * _b); 31 | } 32 | 33 | float operator[](const int i) const { 34 | return i == 0 ? x : y; 35 | } 36 | 37 | float length() const { 38 | return sqrt(x * x + y * y); 39 | } 40 | 41 | Vec2& normalize() { 42 | return *this = *this * (1 / length()); 43 | } 44 | 45 | float dot(const Vec2& _b) const { 46 | return x * _b.x + y * _b.y; 47 | } 48 | 49 | Vec2 perp() const { 50 | return Vec2(-y, x); 51 | } 52 | }; 53 | 54 | inline bool operator==(const Vec2& lh, const Vec2& rh) { 55 | return lh.x == rh.x && lh.y == rh.y; 56 | } 57 | 58 | inline bool operator!=(const Vec2& lh, const Vec2& rh) { 59 | return !(lh == rh); 60 | } 61 | 62 | template<> 63 | inline float dot(const Vec2& _v, const Vec2& _b) { 64 | return _v.x * _b.x + _v.y * _b.y; 65 | } 66 | 67 | template<> 68 | inline Vec2 normalize(const Vec2& _v) { 69 | return _v * (1.0f / _v.length()); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "isect2d.h" 2 | #include "vec2.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #ifdef USE_GLM 14 | #include 15 | #endif 16 | 17 | //#define CIRCLES 18 | #define N_CIRCLES 500 19 | 20 | #define AREA 21 | #define N_BOX 2000 22 | 23 | GLFWwindow* window; 24 | float width = 800; 25 | float height = 600; 26 | float dpiRatio = 1; 27 | 28 | bool isPause = false; 29 | 30 | #ifdef USE_GLM 31 | using Vec2 = glm::vec2; 32 | #else 33 | using Vec2 = isect2d::Vec2; 34 | #endif 35 | 36 | using OBB = isect2d::OBB; 37 | using AABB = isect2d::AABB; 38 | 39 | std::vector obbs; 40 | 41 | float rand_0_1(float scale) { 42 | return ((float)rand() / (float)(RAND_MAX)) * scale; 43 | } 44 | 45 | void update() { 46 | double time = glfwGetTime(); 47 | 48 | if(!isPause) { 49 | int i = 0; 50 | 51 | for (auto& obb : obbs) { 52 | float r1 = rand_0_1(10); 53 | float r2 = rand_0_1(20); 54 | float r3 = rand_0_1(M_PI); 55 | 56 | auto centroid = obb.getCentroid(); 57 | 58 | if (++i % 2 == 0) { 59 | obb.move(centroid.x, centroid.y + .02 * cos(time * 0.25) * r2); 60 | obb.rotate(cos(r3) * 0.1 + obb.getAngle()); 61 | } else { 62 | obb.move(centroid.x + 0.1 * cos(time) * r1, centroid.y); 63 | obb.rotate(cos(r2) * 0.1 + obb.getAngle()); 64 | } 65 | } 66 | } 67 | } 68 | 69 | void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { 70 | if(key == 'P' && action == GLFW_PRESS) { 71 | isPause = !isPause; 72 | } 73 | } 74 | 75 | void initBBoxes() { 76 | 77 | #if defined CIRCLES 78 | int n = N_BOX; 79 | float o = (2 * M_PI) / n; 80 | float size = 200; 81 | float boxSize = n / (0.4 * n); 82 | 83 | for (int i = 0; i < n; ++i) { 84 | float r = rand_0_1(20); 85 | 86 | obbs.push_back(OBB(cos(o * i) * size + width / 2, 87 | sin(o * i) * size + height / 2, r, 88 | r + boxSize * 8, r * boxSize / 3 + boxSize)); 89 | } 90 | #elif defined AREA 91 | int n = N_BOX; 92 | float boxWidth = 10; 93 | float boxHeight = 5; 94 | 95 | std::default_random_engine generator; 96 | std::uniform_real_distribution xDistribution(-350.0,350.0); 97 | std::uniform_real_distribution yDistribution(-250.0,250.0); 98 | std::uniform_real_distribution boxScaleDist(-2.0f,2.0f); 99 | for(int i = 0; i < n; i++) { 100 | float boxSizeFactorW = boxScaleDist(generator); 101 | float boxSizeFactorH = boxScaleDist(generator); 102 | float xVal = xDistribution(generator) + width/2.0f; 103 | float yVal = yDistribution(generator) + height/2.0f; 104 | float angle = yVal/(xVal+1.0f); 105 | obbs.push_back(OBB(xVal, yVal, angle+M_PI*i/4, 106 | boxWidth-boxSizeFactorW, 107 | boxHeight-boxSizeFactorH)); 108 | } 109 | #else 110 | int n = 10; 111 | float o = (2 * M_PI) / n; 112 | float size = 50; 113 | float boxSize = 15; 114 | 115 | for (int i = 0; i < n; ++i) { 116 | float r = rand_0_1(20); 117 | 118 | obbs.push_back(OBB(cos(o * i) * size + width / 2, 119 | sin(o * i) * size + height / 2, r, 120 | r + boxSize * 8, r * boxSize / 3 + boxSize)); 121 | } 122 | #endif 123 | 124 | } 125 | 126 | void init() { 127 | glfwInit(); 128 | 129 | glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); 130 | glfwWindowHint(GLFW_SAMPLES, 4); 131 | window = glfwCreateWindow(width, height, "isect2d", NULL, NULL); 132 | 133 | if (!window) { 134 | glfwTerminate(); 135 | } 136 | 137 | int fbWidth, fbHeight; 138 | glfwGetFramebufferSize(window, &fbWidth, &fbHeight); 139 | glfwSetKeyCallback(window, keyCallback); 140 | 141 | dpiRatio = fbWidth / width; 142 | 143 | glfwMakeContextCurrent(window); 144 | 145 | initBBoxes(); 146 | } 147 | 148 | void line(float sx, float sy, float ex, float ey) { 149 | glBegin(GL_LINES); 150 | glVertex2f(sx, sy); 151 | glVertex2f(ex, ey); 152 | glEnd(); 153 | } 154 | 155 | void cross(float x, float y, float size = 3) { 156 | line(x - size, y, x + size, y); 157 | line(x, y - size, x, y + size); 158 | } 159 | 160 | void drawAABB(const AABB& _aabb) { 161 | line(_aabb.getMin().x, _aabb.getMin().y, _aabb.getMin().x, _aabb.getMax().y); 162 | line(_aabb.getMin().x, _aabb.getMin().y, _aabb.getMax().x, _aabb.getMin().y); 163 | line(_aabb.getMax().x, _aabb.getMin().y, _aabb.getMax().x, _aabb.getMax().y); 164 | line(_aabb.getMin().x, _aabb.getMax().y, _aabb.getMax().x, _aabb.getMax().y); 165 | } 166 | 167 | void drawOBB(const OBB& obb, bool isect) { 168 | const auto& quad = obb.getQuad(); 169 | 170 | for(int i = 0; i < 4; ++i) { 171 | if(isect) { 172 | glColor4f(0.5, 1.0, 0.5, 1.0); 173 | } else { 174 | glColor4f(1.0, 0.5, 0.5, 1.0); 175 | } 176 | 177 | auto start = quad[i]; 178 | auto end = quad[(i + 1) % 4]; 179 | 180 | line(start.x, start.y, end.x, end.y); 181 | 182 | glColor4f(1.0, 1.0, 1.0, 0.1); 183 | cross(obb.getCentroid().x, obb.getCentroid().y, 2); 184 | } 185 | } 186 | 187 | void render() { 188 | 189 | const int n1 = 4; 190 | const int n2 = 16; 191 | 192 | isect2d::ISect2D context; 193 | context.resize({n2, n2}, {800, 600}); 194 | 195 | while (!glfwWindowShouldClose(window)) { 196 | update(); 197 | 198 | if (isPause) { 199 | glfwPollEvents(); 200 | continue; 201 | } 202 | 203 | glViewport(0, 0, width * dpiRatio, height * dpiRatio); 204 | glClearColor(0.18f, 0.18f, 0.22f, 1.0f); 205 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 206 | glMatrixMode(GL_PROJECTION); 207 | glLoadIdentity(); 208 | glOrtho(0, width, 0, height, -1, 1); 209 | glMatrixMode(GL_MODELVIEW); 210 | glLoadIdentity(); 211 | 212 | for (auto& obb : obbs) { 213 | drawOBB(obb, false); 214 | } 215 | 216 | // bruteforce broadphase 217 | { 218 | std::unordered_set> pairs; 219 | std::vector aabbs; 220 | 221 | for (auto& obb : obbs) { 222 | auto aabb = obb.getExtent(); 223 | aabb.m_userData = (void*)&obb; 224 | aabbs.push_back(aabb); 225 | } 226 | 227 | const clock_t beginBroadPhaseTime = clock(); 228 | pairs = intersect(aabbs); 229 | float broadTime = (float(clock() - beginBroadPhaseTime) / CLOCKS_PER_SEC) * 1000; 230 | 231 | // narrow phase 232 | clock_t beginNarrowTime = clock(); 233 | int collisions = 0; 234 | for (auto& pair : pairs) { 235 | if (intersect(obbs[pair.first], obbs[pair.second])) 236 | collisions++; 237 | } 238 | float narrowTime = (float(clock() - beginNarrowTime) / CLOCKS_PER_SEC) * 1000; 239 | 240 | std::cout << "0 - broadphase: " << broadTime 241 | << "\t narrowphase: " << narrowTime << "ms" 242 | << "\t pairs: " << pairs.size() 243 | << "\t collision: " << collisions 244 | << std::endl; 245 | } 246 | 247 | // grid broad phase 248 | { 249 | std::vector aabbs; 250 | std::unordered_set> pairs; 251 | 252 | for (auto& obb : obbs) { 253 | auto aabb = obb.getExtent(); 254 | aabb.m_userData = (void*)&obb; 255 | aabbs.push_back(aabb); 256 | } 257 | 258 | const clock_t beginBroadPhaseTime = clock(); 259 | pairs = intersect(aabbs, {n1, n1}, {800, 600}); 260 | float broadTime = (float(clock() - beginBroadPhaseTime) / CLOCKS_PER_SEC) * 1000; 261 | 262 | // narrow phase 263 | clock_t beginNarrowTime = clock(); 264 | int collisions = 0; 265 | for (auto& pair : pairs) { 266 | if (intersect(obbs[pair.first], obbs[pair.second])) 267 | collisions++; 268 | } 269 | float narrowTime = (float(clock() - beginNarrowTime) / CLOCKS_PER_SEC) * 1000; 270 | 271 | std::cout << "1 - broadphase: " << broadTime 272 | << "\t narrowphase: " << narrowTime << "ms" 273 | << "\t pairs: " << pairs.size() 274 | << "\t collision: " << collisions 275 | << std::endl; 276 | } 277 | 278 | // grid broad phase 279 | { 280 | std::vector aabbs; 281 | for (auto& obb : obbs) { 282 | auto aabb = obb.getExtent(); 283 | aabb.m_userData = (void*)&obb; 284 | aabbs.push_back(aabb); 285 | } 286 | 287 | const clock_t beginBroadPhaseTime = clock(); 288 | context.clear(); 289 | context.intersect(aabbs); 290 | float broadTime = (float(clock() - beginBroadPhaseTime) / CLOCKS_PER_SEC) * 1000; 291 | 292 | // narrow phase 293 | clock_t beginNarrowTime = clock(); 294 | int collisions = 0; 295 | for (auto& pair : context.pairs) { 296 | if (intersect(obbs[pair.first], obbs[pair.second])) 297 | collisions++; 298 | } 299 | float narrowTime = (float(clock() - beginNarrowTime) / CLOCKS_PER_SEC) * 1000; 300 | 301 | std::cout << "2 - broadphase: " << broadTime 302 | << "\t narrowphase: " << narrowTime << "ms" 303 | << "\t pairs: " << context.pairs.size() 304 | << "\t collision: " << collisions 305 | << std::endl; 306 | } 307 | 308 | std::cout << std::endl; 309 | 310 | // narrow phase 311 | { 312 | for (auto& pair : context.pairs) { 313 | auto obb1 = obbs[pair.first]; 314 | auto obb2 = obbs[pair.second]; 315 | 316 | bool isect = intersect(obb1, obb2); 317 | 318 | if (isect) { 319 | drawOBB(obb1, true); 320 | drawOBB(obb2, true); 321 | 322 | line(obb1.getCentroid().x, obb1.getCentroid().y, 323 | obb2.getCentroid().x, obb2.getCentroid().y); 324 | } 325 | } 326 | } 327 | 328 | glfwSwapBuffers(window); 329 | 330 | glfwPollEvents(); 331 | } 332 | } 333 | 334 | int main() { 335 | 336 | init(); 337 | render(); 338 | 339 | return 0; 340 | } 341 | --------------------------------------------------------------------------------