1 /* 2 Copyright 2008-2024 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG:true, define: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 import Mat from "./math.js"; 36 import Geometry from "./geometry.js"; 37 import Type from "../utils/type.js"; 38 39 /** 40 * Instantiate a new quadtree. 41 * 42 * @name JXG.Math.Quadtree 43 * @exports Mat.Quadtree as JXG.Math.Quadtree 44 * @param {Array} bbox Bounding box of the new quad (sub)tree. 45 * @param {Object} config Configuration object. Default value: to {capacity: 10} 46 * @param {Object} [parent] Parent object or null if root. 47 * 48 * @constructor 49 */ 50 Mat.Quadtree = function (bbox, config, parent) { 51 config = config || { 52 capacity: 10, 53 pointType: 'coords' 54 }; 55 56 /** 57 * Configuration object for quadtree. 58 * 59 * @name JXG.Math.Quadtree.config 60 * @type Object 61 */ 62 this.config = {}; 63 /** 64 * The maximum number of points stored in a quadtree node 65 * before it is subdivided. 66 * @name JXG.Math.Quadtree.config#capacity 67 * @type Number 68 * @default 10 69 */ 70 this.config.capacity = config.capacity || 10; 71 72 /** 73 * Type of a point object. Possible values are: 74 * 'coords', 'object'. 75 * @name JXG.Math.Quadtree.config#pointType 76 * @type String 77 * @default 'coords' 78 */ 79 this.config.pointType = config.pointType || 'coords'; 80 81 /** 82 * Point storage. 83 * @name JXG.Math.Quadtree#points 84 * @type Array 85 */ 86 this.points = []; 87 88 this.xlb = bbox[0]; 89 this.xub = bbox[2]; 90 this.ylb = bbox[3]; 91 this.yub = bbox[1]; 92 93 /** 94 * Parent quadtree or null if there is not parent. 95 * 96 * @name JXG.Math.Quadtree#parent 97 * @type JXG.Math.Quadtree 98 * 99 */ 100 this.parent = parent || null; 101 102 /** 103 * In a subdivided quadtree this represents the top left subtree. 104 * @name JXG.Math.Quadtree#northWest 105 * @type JXG.Math.Quadtree 106 */ 107 this.northWest = null; 108 109 /** 110 * In a subdivided quadtree this represents the top right subtree. 111 * @name JXG.Math.Quadtree#northEast 112 * @type JXG.Math.Quadtree 113 */ 114 this.northEast = null; 115 116 /** 117 * In a subdivided quadtree this represents the bottom right subtree. 118 * @name JXG.Math.Quadtree#southEast 119 * @type JXG.Math.Quadtree 120 */ 121 this.southEast = null; 122 123 /** 124 * In a subdivided quadtree this represents the bottom left subtree. 125 * @name JXG.Math.Quadtree#southWest 126 * @type JXG.Math.Quadtree 127 */ 128 this.southWest = null; 129 130 }; 131 132 Type.extend( 133 Mat.Quadtree.prototype, 134 /** @lends JXG.Math.Quadtree.prototype */ { 135 /** 136 * Checks if the given coordinates are inside of the boundaries of the quadtree. 137 * The quadtree is open to the left and botton and closed to 138 * right and top. 139 * 140 * @param {Number} x 141 * @param {Number} y 142 * @returns {Boolean} 143 */ 144 contains: function (x, y) { 145 return this.xlb < x && x <= this.xub && this.ylb < y && y <= this.yub; 146 }, 147 148 /** 149 * Insert a new point into this quadtree if it is inside of 150 * the quadtree's boundaries. 151 * 152 * @param {JXG.Coords} p 153 * @returns {Boolean} true if insert succeeded, false otherwise. 154 */ 155 insert: function (p) { 156 switch (this.config.pointType) { 157 case 'coords': 158 if (!this.contains(p.usrCoords[1], p.usrCoords[2])) { 159 return false; 160 } 161 break; 162 case 'object': 163 if (!this.contains(p.x, p.y)) { 164 return false; 165 } 166 break; 167 } 168 169 if (this.points.length < this.config.capacity && this.northWest === null) { 170 this.points.push(p); 171 return true; 172 } 173 174 // At this point the point has to be inserted into a subtree. 175 if (this.northWest === null) { 176 this.subdivide(); 177 } 178 179 if (this.northWest.insert(p)) { 180 return true; 181 } 182 183 if (this.northEast.insert(p)) { 184 return true; 185 } 186 187 if (this.southEast.insert(p)) { 188 return true; 189 } 190 191 return !!this.southWest.insert(p); 192 }, 193 194 /** 195 * Subdivide the quadtree. 196 */ 197 subdivide: function () { 198 var // i, le, 199 cx = this.xlb + (this.xub - this.xlb) * 0.5, 200 cy = this.ylb + (this.yub - this.ylb) * 0.5; 201 202 this.northWest = new Mat.Quadtree([this.xlb, this.yub, cx, cy], this.config, this); 203 this.northEast = new Mat.Quadtree([cx, this.yub, this.xub, cy], this.config, this); 204 this.southEast = new Mat.Quadtree([this.xlb, cy, cx, this.ylb], this.config, this); 205 this.southWest = new Mat.Quadtree([cx, cy, this.xub, this.ylb], this.config, this); 206 207 // for (i = 0; i < le; i++) { 208 // if (this.northWest.insert(this.points[i])) { continue; } 209 // if (this.northEast.insert(this.points[i])) { continue; } 210 // if (this.southEast.insert(this.points[i])) { continue; } 211 // this.southWest.insert(this.points[i]); 212 // } 213 }, 214 215 /** 216 * Internal _query method that lacks adjustment of the parameter. 217 * @name JXG.Math.Quadtree#_query 218 * @param {Number} x 219 * @param {Number} y 220 * @returns {Boolean|JXG.Quadtree} The quadtree if the point is found, false 221 * if none of the quadtrees contains the point (i.e. the point is not inside 222 * the root tree's AABB,i.e. axis-aligned bounding box). 223 * @private 224 */ 225 _query: function (x, y) { 226 var r; 227 228 if (this.contains(x, y)) { 229 if (this.northWest === null) { 230 return this; 231 } 232 233 r = this.northWest._query(x, y); 234 if (r) { 235 return r; 236 } 237 238 r = this.northEast._query(x, y); 239 if (r) { 240 return r; 241 } 242 243 r = this.southEast._query(x, y); 244 if (r) { 245 return r; 246 } 247 248 r = this.southWest._query(x, y); 249 if (r) { 250 return r; 251 } 252 } 253 254 return false; 255 }, 256 257 /** 258 * Retrieve the smallest quad tree that contains the given coordinate pair. 259 * @name JXG.Math.Quadtree#query 260 * @param {JXG.Coords|Number} xp 261 * @param {Number} y 262 * @returns {Boolean|JXG.Quadtree} The quadtree if the point is found, false 263 * if none of the quadtrees contains the point (i.e. the point is not inside 264 * the root tree's AABB (Axis-Aligned Bounding Box)). 265 */ 266 query: function (xp, y) { 267 var _x, _y; 268 269 if (Type.exists(y)) { 270 _x = xp; 271 _y = y; 272 } else { 273 _x = xp.usrCoords[1]; 274 _y = xp.usrCoords[2]; 275 } 276 277 return this._query(_x, _y); 278 }, 279 280 /** 281 * Check if the quadtree has a point which is inside of a sphere of 282 * radius tol around [x, y]. 283 * @param {Number} x 284 * @param {Number} y 285 * @param {Number} tol 286 * @returns {Boolean} 287 */ 288 hasPoint: function (x, y, tol) { 289 var r, i, le; 290 291 if (this.contains(x, y)) { 292 le = this.points.length; 293 294 switch (this.config.pointType) { 295 case 'coords': 296 for (i = 0; i < le; i++) { 297 if (Geometry.distance([x, y], this.points[i].usrCoords.slice(1), 2) < tol) { 298 return true; 299 } 300 } 301 break; 302 case 'object': 303 for (i = 0; i < le; i++) { 304 if (Geometry.distance([x, y], [this.points[i].x, this.points[i].y], 2) < tol) { 305 return true; 306 } 307 } 308 break; 309 } 310 311 312 if (this.northWest === null) { 313 return false; 314 } 315 316 r = this.northWest.hasPoint(x, y, tol); 317 if (r) { 318 return r; 319 } 320 321 r = this.northEast.hasPoint(x, y, tol); 322 if (r) { 323 return r; 324 } 325 326 r = this.southEast.hasPoint(x, y, tol); 327 if (r) { 328 return r; 329 } 330 331 r = this.southWest.hasPoint(x, y, tol); 332 if (r) { 333 return r; 334 } 335 } 336 337 return false; 338 }, 339 340 /** 341 * 342 * @returns {Array} 343 */ 344 getAllPoints: function() { 345 var pointsList = []; 346 this.getAllPointsRecursive(pointsList); 347 return pointsList; 348 }, 349 350 /** 351 * 352 * @param {Array} pointsList 353 * @private 354 */ 355 getAllPointsRecursive(pointsList) { 356 Array.prototype.push.apply(pointsList, this.points.slice()); 357 358 if (this.northWest === null) { 359 return; 360 } 361 362 this.northWest.getAllPointsRecursive(pointsList); 363 this.northEast.getAllPointsRecursive(pointsList); 364 this.southEast.getAllPointsRecursive(pointsList); 365 this.southWest.getAllPointsRecursive(pointsList); 366 } 367 368 } 369 ); 370 371 export default Mat.Quadtree; 372