├── .gitignore ├── LICENSE ├── README.md ├── kvad.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── src ├── kvad.c └── kvad.h └── test └── KvadTests.m /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | xcuserdata -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 appscape gmbh 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kvad 2 | 3 | Kvad is a [quadtree](http://en.wikipedia.org/wiki/Quadtree) data structure implementation in C99, 4 | extracted from the [Superpin clustering library](http://getsuperpin.com). 5 | 6 | ## Usage 7 | 8 | void walk(double x, double y, void* payload, void* context) { 9 | printf("Found %s at %f,%f", payload, x, y); 10 | } 11 | 12 | kvad_tree* tree = kvad_tree_create(0, 0, 1000, 1000, 16 /* max number of levels */, 16 /* max points per level */); 13 | kvad_tree_insert(tree, 10, 10, "Place 1"); 14 | kvad_tree_insert(tree, 10, 10, "Place 2"); 15 | kvad_tree_insert(tree, 10, 10, "Place 3"); 16 | kvad_tree_find(tree, 5, 5, 10, 10, walk, NULL); // Find all points in (5,5)-(15,15) rectangle 17 | kvad_tree_release(tree); 18 | 19 | ## Assertions 20 | 21 | To enable internal assertions and checks on supplied arguments, define `KVAD_ASSERTS=1`. 22 | 23 | If defined, the assertion macro Kvad uses internally will expand to a stdlib `assert()` call that 24 | aborts the program if an inconstitent state is encountered. Use this in your debug builds. -------------------------------------------------------------------------------- /kvad.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C79E9C1C18EA755A00335403 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C79E9C1B18EA755A00335403 /* XCTest.framework */; }; 11 | C79E9C2418EA755A00335403 /* KvadTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C79E9C2318EA755A00335403 /* KvadTests.m */; }; 12 | C79E9C2C18EA75AA00335403 /* kvad.c in Sources */ = {isa = PBXBuildFile; fileRef = C79E9C2A18EA75AA00335403 /* kvad.c */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXFileReference section */ 16 | C79E9C1818EA755A00335403 /* KvadTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KvadTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 17 | C79E9C1B18EA755A00335403 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 18 | C79E9C2318EA755A00335403 /* KvadTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = KvadTests.m; path = test/KvadTests.m; sourceTree = ""; }; 19 | C79E9C2A18EA75AA00335403 /* kvad.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = kvad.c; path = ../src/kvad.c; sourceTree = ""; }; 20 | C79E9C2B18EA75AA00335403 /* kvad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = kvad.h; path = ../src/kvad.h; sourceTree = ""; }; 21 | /* End PBXFileReference section */ 22 | 23 | /* Begin PBXFrameworksBuildPhase section */ 24 | C79E9C1518EA755A00335403 /* Frameworks */ = { 25 | isa = PBXFrameworksBuildPhase; 26 | buildActionMask = 2147483647; 27 | files = ( 28 | C79E9C1C18EA755A00335403 /* XCTest.framework in Frameworks */, 29 | ); 30 | runOnlyForDeploymentPostprocessing = 0; 31 | }; 32 | /* End PBXFrameworksBuildPhase section */ 33 | 34 | /* Begin PBXGroup section */ 35 | C79E9C1918EA755A00335403 /* Products */ = { 36 | isa = PBXGroup; 37 | children = ( 38 | C79E9C1818EA755A00335403 /* KvadTests.xctest */, 39 | ); 40 | name = Products; 41 | sourceTree = ""; 42 | }; 43 | C79E9C1A18EA755A00335403 /* Frameworks */ = { 44 | isa = PBXGroup; 45 | children = ( 46 | C79E9C1B18EA755A00335403 /* XCTest.framework */, 47 | ); 48 | name = Frameworks; 49 | sourceTree = ""; 50 | }; 51 | C79E9C2918EA759200335403 /* Sources */ = { 52 | isa = PBXGroup; 53 | children = ( 54 | C79E9C2A18EA75AA00335403 /* kvad.c */, 55 | C79E9C2B18EA75AA00335403 /* kvad.h */, 56 | ); 57 | name = Sources; 58 | path = src; 59 | sourceTree = SOURCE_ROOT; 60 | }; 61 | C7A1F1EF18EA74C40035034A = { 62 | isa = PBXGroup; 63 | children = ( 64 | C79E9C2318EA755A00335403 /* KvadTests.m */, 65 | C79E9C2918EA759200335403 /* Sources */, 66 | C79E9C1A18EA755A00335403 /* Frameworks */, 67 | C79E9C1918EA755A00335403 /* Products */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | /* End PBXGroup section */ 72 | 73 | /* Begin PBXNativeTarget section */ 74 | C79E9C1718EA755A00335403 /* KvadTests */ = { 75 | isa = PBXNativeTarget; 76 | buildConfigurationList = C79E9C2618EA755A00335403 /* Build configuration list for PBXNativeTarget "KvadTests" */; 77 | buildPhases = ( 78 | C79E9C1418EA755A00335403 /* Sources */, 79 | C79E9C1518EA755A00335403 /* Frameworks */, 80 | C79E9C1618EA755A00335403 /* Resources */, 81 | ); 82 | buildRules = ( 83 | ); 84 | dependencies = ( 85 | ); 86 | name = KvadTests; 87 | productName = KvadTests; 88 | productReference = C79E9C1818EA755A00335403 /* KvadTests.xctest */; 89 | productType = "com.apple.product-type.bundle.unit-test"; 90 | }; 91 | /* End PBXNativeTarget section */ 92 | 93 | /* Begin PBXProject section */ 94 | C7A1F1F018EA74C40035034A /* Project object */ = { 95 | isa = PBXProject; 96 | attributes = { 97 | LastUpgradeCheck = 0510; 98 | }; 99 | buildConfigurationList = C7A1F1F318EA74C40035034A /* Build configuration list for PBXProject "kvad" */; 100 | compatibilityVersion = "Xcode 3.2"; 101 | developmentRegion = English; 102 | hasScannedForEncodings = 0; 103 | knownRegions = ( 104 | en, 105 | ); 106 | mainGroup = C7A1F1EF18EA74C40035034A; 107 | productRefGroup = C79E9C1918EA755A00335403 /* Products */; 108 | projectDirPath = ""; 109 | projectRoot = ""; 110 | targets = ( 111 | C79E9C1718EA755A00335403 /* KvadTests */, 112 | ); 113 | }; 114 | /* End PBXProject section */ 115 | 116 | /* Begin PBXResourcesBuildPhase section */ 117 | C79E9C1618EA755A00335403 /* Resources */ = { 118 | isa = PBXResourcesBuildPhase; 119 | buildActionMask = 2147483647; 120 | files = ( 121 | ); 122 | runOnlyForDeploymentPostprocessing = 0; 123 | }; 124 | /* End PBXResourcesBuildPhase section */ 125 | 126 | /* Begin PBXSourcesBuildPhase section */ 127 | C79E9C1418EA755A00335403 /* Sources */ = { 128 | isa = PBXSourcesBuildPhase; 129 | buildActionMask = 2147483647; 130 | files = ( 131 | C79E9C2418EA755A00335403 /* KvadTests.m in Sources */, 132 | C79E9C2C18EA75AA00335403 /* kvad.c in Sources */, 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXSourcesBuildPhase section */ 137 | 138 | /* Begin XCBuildConfiguration section */ 139 | C79E9C2718EA755A00335403 /* Debug */ = { 140 | isa = XCBuildConfiguration; 141 | buildSettings = { 142 | ALWAYS_SEARCH_USER_PATHS = NO; 143 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 144 | CLANG_CXX_LIBRARY = "libc++"; 145 | CLANG_ENABLE_MODULES = YES; 146 | CLANG_ENABLE_OBJC_ARC = YES; 147 | CLANG_WARN_BOOL_CONVERSION = YES; 148 | CLANG_WARN_CONSTANT_CONVERSION = YES; 149 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 150 | CLANG_WARN_EMPTY_BODY = YES; 151 | CLANG_WARN_ENUM_CONVERSION = YES; 152 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO; 153 | CLANG_WARN_INT_CONVERSION = YES; 154 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 155 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; 156 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 157 | COPY_PHASE_STRIP = NO; 158 | FRAMEWORK_SEARCH_PATHS = ( 159 | "$(DEVELOPER_FRAMEWORKS_DIR)", 160 | "$(inherited)", 161 | ); 162 | GCC_C_LANGUAGE_STANDARD = gnu99; 163 | GCC_DYNAMIC_NO_PIC = NO; 164 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 165 | GCC_OPTIMIZATION_LEVEL = 0; 166 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 167 | GCC_PREFIX_HEADER = ""; 168 | GCC_PREPROCESSOR_DEFINITIONS = ( 169 | "DEBUG=1", 170 | "$(inherited)", 171 | ); 172 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 173 | GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; 174 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 175 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 176 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 177 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 178 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 179 | GCC_WARN_SHADOW = YES; 180 | GCC_WARN_SIGN_COMPARE = NO; 181 | GCC_WARN_UNDECLARED_SELECTOR = YES; 182 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 183 | GCC_WARN_UNUSED_FUNCTION = YES; 184 | GCC_WARN_UNUSED_VARIABLE = YES; 185 | INFOPLIST_FILE = ""; 186 | MACOSX_DEPLOYMENT_TARGET = 10.9; 187 | ONLY_ACTIVE_ARCH = YES; 188 | OTHER_CFLAGS = "-DKVAD_ASSERTS=1"; 189 | PRODUCT_NAME = "$(TARGET_NAME)"; 190 | SDKROOT = macosx; 191 | WRAPPER_EXTENSION = xctest; 192 | }; 193 | name = Debug; 194 | }; 195 | C79E9C2818EA755A00335403 /* Release */ = { 196 | isa = XCBuildConfiguration; 197 | buildSettings = { 198 | ALWAYS_SEARCH_USER_PATHS = NO; 199 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 200 | CLANG_CXX_LIBRARY = "libc++"; 201 | CLANG_ENABLE_MODULES = YES; 202 | CLANG_ENABLE_OBJC_ARC = YES; 203 | CLANG_WARN_BOOL_CONVERSION = YES; 204 | CLANG_WARN_CONSTANT_CONVERSION = YES; 205 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 206 | CLANG_WARN_EMPTY_BODY = YES; 207 | CLANG_WARN_ENUM_CONVERSION = YES; 208 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO; 209 | CLANG_WARN_INT_CONVERSION = YES; 210 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 211 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; 212 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 213 | COPY_PHASE_STRIP = YES; 214 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 215 | ENABLE_NS_ASSERTIONS = NO; 216 | FRAMEWORK_SEARCH_PATHS = ( 217 | "$(DEVELOPER_FRAMEWORKS_DIR)", 218 | "$(inherited)", 219 | ); 220 | GCC_C_LANGUAGE_STANDARD = gnu99; 221 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 222 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 223 | GCC_PREFIX_HEADER = ""; 224 | GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; 225 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 226 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 227 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 228 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 229 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 230 | GCC_WARN_SHADOW = YES; 231 | GCC_WARN_SIGN_COMPARE = NO; 232 | GCC_WARN_UNDECLARED_SELECTOR = YES; 233 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 234 | GCC_WARN_UNUSED_FUNCTION = YES; 235 | GCC_WARN_UNUSED_VARIABLE = YES; 236 | INFOPLIST_FILE = ""; 237 | MACOSX_DEPLOYMENT_TARGET = 10.9; 238 | OTHER_CFLAGS = "-DKVAD_ASSERTS=1"; 239 | PRODUCT_NAME = "$(TARGET_NAME)"; 240 | SDKROOT = macosx; 241 | WRAPPER_EXTENSION = xctest; 242 | }; 243 | name = Release; 244 | }; 245 | C7A1F1F418EA74C40035034A /* Debug */ = { 246 | isa = XCBuildConfiguration; 247 | buildSettings = { 248 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 249 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 250 | }; 251 | name = Debug; 252 | }; 253 | C7A1F1F518EA74C40035034A /* Release */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 257 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 258 | }; 259 | name = Release; 260 | }; 261 | /* End XCBuildConfiguration section */ 262 | 263 | /* Begin XCConfigurationList section */ 264 | C79E9C2618EA755A00335403 /* Build configuration list for PBXNativeTarget "KvadTests" */ = { 265 | isa = XCConfigurationList; 266 | buildConfigurations = ( 267 | C79E9C2718EA755A00335403 /* Debug */, 268 | C79E9C2818EA755A00335403 /* Release */, 269 | ); 270 | defaultConfigurationIsVisible = 0; 271 | defaultConfigurationName = Release; 272 | }; 273 | C7A1F1F318EA74C40035034A /* Build configuration list for PBXProject "kvad" */ = { 274 | isa = XCConfigurationList; 275 | buildConfigurations = ( 276 | C7A1F1F418EA74C40035034A /* Debug */, 277 | C7A1F1F518EA74C40035034A /* Release */, 278 | ); 279 | defaultConfigurationIsVisible = 0; 280 | defaultConfigurationName = Release; 281 | }; 282 | /* End XCConfigurationList section */ 283 | }; 284 | rootObject = C7A1F1F018EA74C40035034A /* Project object */; 285 | } 286 | -------------------------------------------------------------------------------- /kvad.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/kvad.c: -------------------------------------------------------------------------------- 1 | // Kvad - a C99 quadtree implementation 2 | // http://github.com/appscape/kvad 3 | 4 | #include "kvad.h" 5 | 6 | #include 7 | #include 8 | 9 | // Activate asserts only if KVAD_ASSERTS=1 is defined 10 | #if KVAD_ASSERTS 11 | #include 12 | #define ASSERT(expr) assert(expr) 13 | #else 14 | #define ASSERT(expr) /* */ 15 | #endif 16 | 17 | typedef struct { double x; double y; } kvad_point; 18 | typedef struct { double width; double height; } kvad_size; 19 | typedef struct { kvad_point origin; kvad_size size; } kvad_rect; 20 | 21 | // A linked list element storing a point and the reference to associated payload 22 | struct _kvad_data { 23 | kvad_point point; 24 | void* payload; 25 | struct _kvad_data* next; 26 | }; 27 | 28 | typedef struct _kvad_data kvad_data; 29 | 30 | struct _kvad_node { 31 | unsigned int level; // This node's level in the tree 32 | kvad_rect rect; // Bounding box 33 | struct _kvad_node* subnodes[4]; // 0=NW,1=NE,2=SW,3=SE quadrants 34 | kvad_data* data; // Pointer to the first point data (linked list) 35 | unsigned int data_count; // Number of points stored in the linked list 36 | }; 37 | 38 | typedef struct _kvad_node kvad_node; 39 | 40 | struct _kvad_tree { 41 | unsigned int max_levels; 42 | unsigned int max_points_per_node; 43 | kvad_node* root_node; 44 | }; 45 | 46 | static inline bool _kvad_rect_contains_point(kvad_rect rect, kvad_point point) { 47 | return ((point.x >= rect.origin.x) && (point.x <= rect.origin.x + rect.size.width) && 48 | (point.y >= rect.origin.y) && (point.y <= rect.origin.y + rect.size.height)); 49 | } 50 | 51 | static inline bool _kvad_rect_intersects_rect(kvad_rect r0, kvad_rect r1) { 52 | double r0_max_x = r0.origin.x + r0.size.width; 53 | double r1_max_x = r1.origin.x + r1.size.width; 54 | double r0_max_y = r0.origin.y + r0.size.height; 55 | double r1_max_y = r1.origin.y + r1.size.height; 56 | 57 | if ((r0_max_x < r1.origin.x) || (r0.origin.x > r1_max_x) || 58 | (r0_max_y < r1.origin.y) || (r0.origin.y > r1_max_y)) { 59 | return false; 60 | } else { 61 | return true; 62 | } 63 | } 64 | 65 | static kvad_node* _kvad_node_create(kvad_rect rect, unsigned int level) { 66 | kvad_node* n = malloc(sizeof(kvad_node)); 67 | if (!n) return NULL; 68 | n->rect = rect; 69 | n->level = level; 70 | n->data = NULL; 71 | n->data_count = 0; 72 | for (size_t i=0;i<4;i++) { n->subnodes[i] = NULL; } 73 | return n; 74 | } 75 | 76 | static kvad_node* _kvad_node_subnode_at_point(kvad_node *node, kvad_point point) { 77 | // Assert all subnodes are initialized 78 | for (size_t i=0;i<4;i++) { ASSERT(node->subnodes[i]); } 79 | ASSERT(!node->data); //..and non-leaf node has no data 80 | ASSERT(node->data_count == 0); 81 | 82 | // Determine which subnode to insert into 83 | double mid_x = node->rect.origin.x + node->rect.size.width / 2.0; 84 | double mid_y = node->rect.origin.y + node->rect.size.height / 2.0; 85 | 86 | size_t quadrant = (point.y > mid_y) * 2 + (point.x > mid_x); 87 | ASSERT(quadrant < 4); 88 | return node->subnodes[quadrant]; 89 | } 90 | 91 | static unsigned long _kvad_node_remove_payload(kvad_node* node, kvad_point point, void* payload) { 92 | if (node->subnodes[0]) { 93 | return _kvad_node_remove_payload(_kvad_node_subnode_at_point(node, point), point, payload); 94 | } else { 95 | unsigned long count = 0; 96 | 97 | kvad_data *data = node->data; 98 | kvad_data *prev_data = NULL; 99 | 100 | while(data) { 101 | if (data->payload == payload) { 102 | // Payload matches, remove this entry 103 | count++; 104 | node->data_count--; 105 | if (prev_data) { 106 | prev_data->next = data->next; 107 | free(data); 108 | data = prev_data->next; 109 | } else { 110 | node->data = data->next; 111 | free(data); 112 | data = node->data; 113 | } 114 | } else { 115 | prev_data = data; 116 | data = data->next; 117 | } 118 | } 119 | 120 | return count; 121 | } 122 | } 123 | 124 | static void _kvad_node_insert(kvad_tree* tree, kvad_node* node, kvad_point point, void* payload) { 125 | ASSERT(tree); 126 | ASSERT(node); 127 | ASSERT(_kvad_rect_contains_point(node->rect, point)); 128 | if (!_kvad_rect_contains_point(node->rect, point)) return; 129 | 130 | if (node->subnodes[0]) { 131 | // Non-leaf node 132 | _kvad_node_insert(tree, _kvad_node_subnode_at_point(node, point), point, payload); 133 | } else { 134 | // Leaf node, insert here by creating a new element at the head of linked list 135 | kvad_data* new_data = malloc(sizeof(kvad_data)); 136 | new_data->point = point; 137 | new_data->payload = payload; 138 | new_data->next = NULL; 139 | if (node->data) { 140 | ASSERT(node->data_count > 0); 141 | new_data->next = node->data; 142 | } 143 | node->data = new_data; 144 | node->data_count++; 145 | 146 | if (node->level < tree->max_levels && node->data_count > tree->max_points_per_node) { 147 | // Maximum number of points reached AND below max level: split this node 148 | kvad_size subnode_rect_size = {node->rect.size.width / 2.0, node->rect.size.height / 2.0}; 149 | 150 | for (size_t i=0;i<4;i++) { 151 | kvad_rect r = {{ 152 | node->rect.origin.x + (i%2==1 ? subnode_rect_size.width : 0), 153 | node->rect.origin.y + (i>1 ? subnode_rect_size.height : 0) 154 | },subnode_rect_size}; 155 | node->subnodes[i] = _kvad_node_create(r, node->level+1); 156 | } 157 | 158 | // ..and move data from it into subnodes 159 | kvad_data* data = node->data; 160 | node->data = NULL; 161 | node->data_count = 0; 162 | 163 | while (data) { 164 | _kvad_node_insert(tree, node, data->point, data->payload); 165 | kvad_data* next = data->next; 166 | free(data); 167 | data = next; 168 | } 169 | } 170 | } 171 | } 172 | 173 | static unsigned long _kvad_node_find(kvad_node* node, kvad_rect rect, kvad_callback callback, void* context) { 174 | unsigned long count = 0; 175 | if (node->subnodes[0]) { 176 | // Non-leaf node 177 | for (size_t i=0;i<4;i++) { 178 | if (_kvad_rect_intersects_rect(node->subnodes[i]->rect, rect)) { 179 | count += _kvad_node_find(node->subnodes[i], rect, callback, context); 180 | } 181 | } 182 | } else { 183 | // Leaf node 184 | kvad_data* data = node->data; 185 | while (data) { 186 | if (_kvad_rect_contains_point(rect, data->point)) { 187 | count++; 188 | if (callback) callback(data->point.x, data->point.y, data->payload, context); 189 | } 190 | data = data->next; 191 | } 192 | } 193 | return count; 194 | } 195 | 196 | static unsigned long _kvad_node_walk(kvad_node* node, kvad_callback callback, void* context) { 197 | unsigned long count = 0; 198 | if (node->subnodes[0]) { 199 | for (size_t i=0;i<4;i++) { 200 | count += _kvad_node_walk(node->subnodes[i], callback, context); 201 | } 202 | } else { 203 | kvad_data* data = node->data; 204 | while(data) { 205 | count++; 206 | if (callback) { 207 | callback(data->point.x, data->point.y, data->payload, context); 208 | } 209 | data = data->next; 210 | } 211 | ASSERT(count == node->data_count); 212 | } 213 | return count; 214 | } 215 | 216 | static void _kvad_node_release(kvad_node* node) { 217 | if (node->subnodes[0]) { 218 | // Non-leaf node: first free subnodes.. 219 | for (size_t i=0;i<4;i++) { 220 | _kvad_node_release(node->subnodes[i]); 221 | } 222 | } else { 223 | // Leaf node: free data.. 224 | kvad_data *data = node->data; 225 | while (data) { 226 | kvad_data *curr = data; 227 | data = data->next; 228 | free(curr); 229 | } 230 | } 231 | //..then free this node 232 | free(node); 233 | } 234 | 235 | // Public functions 236 | 237 | kvad_tree* kvad_tree_create(double x, double y, double width, double height, unsigned int max_levels, 238 | unsigned int max_points_per_node) { 239 | ASSERT(width > 0); 240 | ASSERT(height > 0); 241 | ASSERT(max_points_per_node > 0); 242 | // Sane behaviour in case asserts are disabled 243 | if (max_points_per_node == 0) max_points_per_node = 1; 244 | kvad_tree* t = malloc(sizeof(kvad_tree)); 245 | if (!t) return NULL; 246 | t->max_levels = max_levels; 247 | t->max_points_per_node = max_points_per_node; 248 | kvad_rect world_rect = {{x, y}, {width, height}}; 249 | t->root_node = _kvad_node_create(world_rect, 0); 250 | if (!t->root_node) return NULL; 251 | return t; 252 | } 253 | 254 | void kvad_tree_release(kvad_tree* tree) { 255 | _kvad_node_release(tree->root_node); 256 | free(tree); 257 | } 258 | 259 | void kvad_tree_insert(kvad_tree* tree, double x, double y, void* payload) { 260 | kvad_point p = {x, y}; 261 | _kvad_node_insert(tree, tree->root_node, p, payload); 262 | } 263 | 264 | unsigned long kvad_tree_remove_payload(kvad_tree* tree, double x, double y, void* payload) { 265 | kvad_point p = {x,y}; 266 | return _kvad_node_remove_payload(tree->root_node, p, payload); 267 | } 268 | 269 | unsigned long kvad_tree_find(kvad_tree* tree, double x, double y, double width, double height, 270 | kvad_callback callback, void* context) { 271 | kvad_rect r = {{x,y},{width, height}}; 272 | return _kvad_node_find(tree->root_node, r, callback, context); 273 | } 274 | 275 | unsigned long kvad_tree_walk(kvad_tree* tree, kvad_callback callback, void* context) { 276 | return _kvad_node_walk(tree->root_node, callback, context); 277 | } -------------------------------------------------------------------------------- /src/kvad.h: -------------------------------------------------------------------------------- 1 | // Kvad - a C99 quadtree implementation 2 | // http://github.com/appscape/kvad 3 | 4 | /** Opaque struct representing the quadtree. */ 5 | typedef struct _kvad_tree kvad_tree; 6 | 7 | typedef void (*kvad_callback)(double x, double y, void* payload, void* context); 8 | 9 | /** 10 | Creates the quadtree. Note that you must manually release the tree using 11 | kvad_tree_release(). 12 | 13 | Operations on the quadtree are not thread-safe. 14 | 15 | @param x 16 | The x coordinate of the bounding box. Can be negative. 17 | @param y 18 | The y coordinate of the bounding box. Can be negative. 19 | @param width 20 | Width of the bounding box. 21 | @param height 22 | Height of the bounding box. 23 | @param max_levels 24 | Maximum number of levels. If maximum level is reached, the nodes are not split anymore 25 | and points are added to the node at the last level, ignoring max_points_per_node. 26 | @param max_points_per_node 27 | Maximum number of points to store in a single node. After this many points are added to 28 | a single node, it is split into into 4 subnodes/quadrants. 29 | @return Pointer to the quadtree object if successful, otherwise NULL 30 | */ 31 | kvad_tree* kvad_tree_create(double x, double y, double width, double height, unsigned int max_levels, 32 | unsigned int max_points_per_node); 33 | 34 | /** Releases the quadtree, freeing up memory. */ 35 | void kvad_tree_release(kvad_tree* tree); 36 | 37 | /** Inserts a point and an associated payload into the quadtree. You are responsible for 38 | making sure that payload reference remains valid during the tree lifecycle. 39 | No check for duplicates is performed. 40 | */ 41 | void kvad_tree_insert(kvad_tree* tree, double x, double y, void* payload); 42 | 43 | /** Removes a point with the matching coordinates and payload from the quadtree. 44 | Point is identified the payload, coordinates are just used to speed up the lookup of the point 45 | to be removed. 46 | @return Number of points removed. 47 | */ 48 | unsigned long kvad_tree_remove_payload(kvad_tree* tree, double x, double y, void* payload); 49 | 50 | /** Searches for points inside the rectangle. Calls supplied callback for every matched point. 51 | @return Number of points found. 52 | */ 53 | unsigned long kvad_tree_find(kvad_tree* tree, double x, double y, double width, double height, 54 | kvad_callback callback, void *context); 55 | 56 | /** Visits all points in the tree. 57 | @return Total number of points currently stored in the tree. 58 | */ 59 | unsigned long kvad_tree_walk(kvad_tree* tree, kvad_callback callback, void* context); -------------------------------------------------------------------------------- /test/KvadTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "kvad.h" 3 | 4 | @interface KvadTests : XCTestCase 5 | @end 6 | 7 | @implementation KvadTests 8 | 9 | static void KvadArrayAppendCallback(double x, double y, void *payload, void *context) { 10 | CFSetAddValue(context, payload); 11 | } 12 | 13 | - (void)testBasic { 14 | void* payload = @"foo"; 15 | 16 | kvad_tree *tree = kvad_tree_create(0, 0, 100, 100, 32, 6); 17 | kvad_tree_insert(tree, 5, 5, payload); 18 | kvad_tree_insert(tree, 5, 5, payload); 19 | kvad_tree_insert(tree, 5, 5, payload); 20 | kvad_tree_insert(tree, 5.5, 5, payload); 21 | 22 | unsigned long count = kvad_tree_walk(tree, NULL, NULL); 23 | XCTAssertEqual(count, 4); 24 | 25 | count = kvad_tree_find(tree, -5.0, -5.0, 10.0, 10.0, NULL, NULL); 26 | XCTAssertEqual(count, 3); 27 | 28 | count = kvad_tree_find(tree, -5.0, -5.0, 11.0, 10.0, NULL, NULL); 29 | XCTAssertEqual(count, 4); 30 | 31 | // Even 5.5 is removed, because coordinates are ignored 32 | count = kvad_tree_remove_payload(tree, 5, 5, payload); 33 | XCTAssertEqual(count, 4); 34 | 35 | count = kvad_tree_walk(tree, NULL, NULL); 36 | XCTAssertEqual(count, 0); 37 | } 38 | 39 | - (void)_testInsertFindRemoveWithMaxLevels:(unsigned int)maxLevels pointsPerNode:(unsigned int)ppn { 40 | kvad_tree* tree = kvad_tree_create(-100, -100, 200, 200, maxLevels, ppn); 41 | 42 | NSMutableArray *payloadStrings = [NSMutableArray array]; 43 | for (int i=0;i<100;i++) { 44 | NSString *s = [NSString stringWithFormat:@"Point %04d",i]; 45 | [payloadStrings addObject:s]; 46 | kvad_tree_insert(tree, i, i, (__bridge void*)s); 47 | } 48 | 49 | NSMutableArray* result = [NSMutableArray array]; 50 | unsigned long count = kvad_tree_find(tree, 0, 0, 50, 50, KvadArrayAppendCallback, (__bridge void *)(result)); 51 | 52 | XCTAssertEqual(result.count, (NSUInteger)51); 53 | XCTAssertEqual(result.count, count); 54 | [result sortUsingSelector:@selector(compare:)]; 55 | 56 | int i = 0; 57 | for (NSString* msg in result) { 58 | NSString* expected = [NSString stringWithFormat:@"Point %04d",i]; 59 | XCTAssertTrue([msg isEqualToString:expected]); 60 | i++; 61 | } 62 | 63 | for (i=0;i<100;i++) { 64 | kvad_tree_remove_payload(tree, i,i, (__bridge void*)payloadStrings[i]); 65 | } 66 | 67 | count = kvad_tree_walk(tree, NULL, NULL); 68 | XCTAssertEqual(count, 0); 69 | 70 | for (i=0;i<100;i++) { 71 | // Insert all at same point 72 | kvad_tree_insert(tree, 0, 0, (__bridge void*)payloadStrings[i]); 73 | } 74 | [result removeAllObjects]; 75 | count = kvad_tree_find(tree, 0,0,1,1, KvadArrayAppendCallback, (__bridge void*)(result)); 76 | 77 | XCTAssertEqual(count, 100); 78 | } 79 | 80 | - (void)testBasicLookup { 81 | [self _testInsertFindRemoveWithMaxLevels:0 pointsPerNode:1]; 82 | [self _testInsertFindRemoveWithMaxLevels:1 pointsPerNode:100]; 83 | [self _testInsertFindRemoveWithMaxLevels:16 pointsPerNode:4]; 84 | } 85 | 86 | @end 87 | --------------------------------------------------------------------------------