aiquadtreejs 0.5.8 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/README_ZHTW.md +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/llms-full.txt +8 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Tiny 2D quadtree for per-frame rebuild collision broadphase. Insert AABBs, retrieve candidates, then run precise collision checks yourself.
|
|
4
4
|
|
|
5
|
-
> **Status: 0.5.
|
|
5
|
+
> **Status: 0.5.9 - stable 1.0-track surface.** The root entry is the public API.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -58,7 +58,7 @@ const candidates = tree.retrieve({ x: 80, y: 80, width: 120, height: 120 });
|
|
|
58
58
|
|
|
59
59
|
## Sharp Edges
|
|
60
60
|
|
|
61
|
-
-
|
|
61
|
+
- Zero-size points (width = 0, height = 0) follow right-open `[x, x+width)` semantics: a point on the minimum `x/y` boundary is **inclusive** and is inserted/retrieved correctly; a point at the exclusive maximum edge is outside the root and is ignored. (Was a bug before 0.5.8; fixed.)
|
|
62
62
|
- Fully outside objects are ignored by retrieval.
|
|
63
63
|
- Negative width/height and non-finite coordinates throw.
|
|
64
64
|
- `retrieveInto()` clears the target array before writing results.
|
package/README_ZHTW.md
CHANGED
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["QuadtreeError","QuadtreeDisposedError","rectsOverlap","a","b","rootContains","bounds","obj","inX","inY","quadrantIndices","node","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","state","ck","insert","scratchSet","scratchStack","scratchRegion","retrieveSet","region","rx","ry","rw","rh","validateRegion","retrieve","retrieveInto","target","set","v","clear","dispose"],"mappings":"aAiKO,IAAMA,CAAAA,CAAN,cAA4B,KAAM,CACrB,KAAO,eAC3B,CAAA,CAOaC,CAAAA,CAAN,cAAoC,KAAM,CAC7B,KAAO,uBAC3B,EAwBA,SAASC,CAAAA,CAAaC,CAAAA,CAASC,CAAAA,CAAkB,CAC/C,OAAOD,CAAAA,CAAE,CAAA,CAAIC,CAAAA,CAAE,CAAA,CAAIA,CAAAA,CAAE,OAASD,CAAAA,CAAE,CAAA,CAAIA,CAAAA,CAAE,KAAA,CAAQC,CAAAA,CAAE,CAAA,EAAKD,EAAE,CAAA,CAAIC,CAAAA,CAAE,CAAA,CAAIA,CAAAA,CAAE,MAAA,EAAUD,CAAAA,CAAE,EAAIA,CAAAA,CAAE,MAAA,CAASC,CAAAA,CAAE,CAClG,CAiBA,SAASC,EAAaC,CAAAA,CAAcC,CAAAA,CAAoB,CACtD,IAAMC,CAAAA,CACJD,CAAAA,CAAI,QAAU,CAAA,CACVA,CAAAA,CAAI,GAAKD,CAAAA,CAAO,CAAA,EAAKC,EAAI,CAAA,CAAID,CAAAA,CAAO,CAAA,CAAIA,CAAAA,CAAO,KAAA,CAC/CC,CAAAA,CAAI,EAAID,CAAAA,CAAO,CAAA,CAAIA,CAAAA,CAAO,KAAA,EAASC,CAAAA,CAAI,CAAA,CAAIA,EAAI,KAAA,CAAQD,CAAAA,CAAO,CAAA,CAC9DG,CAAAA,CACJF,CAAAA,CAAI,MAAA,GAAW,EACXA,CAAAA,CAAI,CAAA,EAAKD,CAAAA,CAAO,CAAA,EAAKC,CAAAA,CAAI,CAAA,CAAID,EAAO,CAAA,CAAIA,CAAAA,CAAO,MAAA,CAC/CC,CAAAA,CAAI,CAAA,CAAID,CAAAA,CAAO,EAAIA,CAAAA,CAAO,MAAA,EAAUC,CAAAA,CAAI,CAAA,CAAIA,CAAAA,CAAI,MAAA,CAASD,EAAO,CAAA,CACtE,OAAOE,CAAAA,EAAOC,CAChB,CAEA,SAASC,EAAgCC,CAAAA,CAAeJ,CAAAA,CAAqB,CAC3E,IAAMK,CAAAA,CAAOD,EAAK,MAAA,CAAO,CAAA,CAAIA,CAAAA,CAAK,MAAA,CAAO,KAAA,CAAQ,CAAA,CAC3CE,EAAOF,CAAAA,CAAK,MAAA,CAAO,CAAA,CAAIA,CAAAA,CAAK,MAAA,CAAO,MAAA,CAAS,EAI5CG,CAAAA,CAASP,CAAAA,CAAI,CAAA,CAAIK,CAAAA,CACjBG,CAAAA,CAAUR,CAAAA,CAAI,QAAU,CAAA,CAAIA,CAAAA,CAAI,CAAA,EAAKK,CAAAA,CAAOL,CAAAA,CAAI,CAAA,CAAIA,EAAI,KAAA,CAAQK,CAAAA,CAChEI,CAAAA,CAAQT,CAAAA,CAAI,CAAA,CAAIM,CAAAA,CAChBI,EAAWV,CAAAA,CAAI,MAAA,GAAW,CAAA,CAAIA,CAAAA,CAAI,CAAA,EAAKM,CAAAA,CAAON,EAAI,CAAA,CAAIA,CAAAA,CAAI,MAAA,CAASM,CAAAA,CACnEK,CAAAA,CAAmB,GACzB,OAAIF,CAAAA,EAASF,GAAQI,CAAAA,CAAO,IAAA,CAAK,CAAC,CAAA,CAC9BF,CAAAA,EAASD,CAAAA,EAASG,CAAAA,CAAO,IAAA,CAAK,CAAC,EAC/BD,CAAAA,EAAYH,CAAAA,EAAQI,CAAAA,CAAO,IAAA,CAAK,CAAC,CAAA,CACjCD,GAAYF,CAAAA,EAASG,CAAAA,CAAO,IAAA,CAAK,CAAC,CAAA,CAC/BA,CACT,CAEA,SAASC,CAAAA,CAA0BR,CAAAA,CAAqB,CACtD,IAAMS,CAAAA,CAAIT,EAAK,MAAA,CAAO,KAAA,CAAQ,CAAA,CACxBU,CAAAA,CAAIV,CAAAA,CAAK,MAAA,CAAO,OAAS,CAAA,CACzBW,CAAAA,CAAIX,CAAAA,CAAK,MAAA,CAAO,CAAA,CAChBY,CAAAA,CAAIZ,EAAK,MAAA,CAAO,CAAA,CAChBa,CAAAA,CAAMb,CAAAA,CAAK,KAAA,CAAQ,CAAA,CACzBA,EAAK,QAAA,CAAS,IAAA,CACZ,CAAE,MAAA,CAAQ,CAAE,EAAAW,CAAAA,CAAG,CAAA,CAAAC,CAAAA,CAAG,KAAA,CAAOH,CAAAA,CAAG,MAAA,CAAQC,CAAE,CAAA,CAAG,KAAA,CAAOG,CAAAA,CAAK,OAAA,CAAS,EAAC,CAAG,SAAU,EAAG,CAAA,CAC/E,CAAE,MAAA,CAAQ,CAAE,EAAGF,CAAAA,CAAIF,CAAAA,CAAG,CAAA,CAAAG,CAAAA,CAAG,KAAA,CAAOH,CAAAA,CAAG,OAAQC,CAAE,CAAA,CAAG,KAAA,CAAOG,CAAAA,CAAK,OAAA,CAAS,GAAI,QAAA,CAAU,EAAG,CAAA,CACtF,CAAE,MAAA,CAAQ,CAAE,CAAA,CAAAF,CAAAA,CAAG,CAAA,CAAGC,CAAAA,CAAIF,CAAAA,CAAG,KAAA,CAAOD,EAAG,MAAA,CAAQC,CAAE,EAAG,KAAA,CAAOG,CAAAA,CAAK,QAAS,EAAC,CAAG,QAAA,CAAU,EAAG,CAAA,CACtF,CAAE,MAAA,CAAQ,CAAE,CAAA,CAAGF,CAAAA,CAAIF,CAAAA,CAAG,CAAA,CAAGG,EAAIF,CAAAA,CAAG,KAAA,CAAOD,CAAAA,CAAG,MAAA,CAAQC,CAAE,CAAA,CAAG,MAAOG,CAAAA,CAAK,OAAA,CAAS,EAAC,CAAG,QAAA,CAAU,EAAG,CAC/F,CAAA,CACA,IAAA,IAAWjB,CAAAA,IAAOI,CAAAA,CAAK,OAAA,CACrB,QAAWc,CAAAA,IAAKf,CAAAA,CAAgBC,CAAAA,CAAMJ,CAAG,CAAA,CAAG,CAC1C,IAAMmB,CAAAA,CAAQf,CAAAA,CAAK,QAAA,CAASc,CAAC,CAAA,CACzBC,CAAAA,GAAU,QAAWA,CAAAA,CAAM,OAAA,CAAQ,KAAKnB,CAAG,EACjD,CAEFI,CAAAA,CAAK,OAAA,CAAQ,MAAA,CAAS,EACxB,CAEA,SAASgB,EACPhB,CAAAA,CACAJ,CAAAA,CACAqB,CAAAA,CACAC,CAAAA,CACM,CASN,GAAI,EAAAlB,CAAAA,CAAK,KAAA,GAAU,CAAA,EAAK,CAACN,CAAAA,CAAaM,CAAAA,CAAK,OAAQJ,CAAG,CAAA,CAAA,CACtD,CAAA,GAAII,CAAAA,CAAK,QAAA,CAAS,MAAA,GAAW,EAAG,CAC9B,IAAA,IAAWc,CAAAA,IAAKf,CAAAA,CAAgBC,CAAAA,CAAMJ,CAAG,EAAG,CAC1C,IAAMmB,CAAAA,CAAQf,CAAAA,CAAK,QAAA,CAASc,CAAC,EACzBC,CAAAA,GAAU,MAAA,EAAWC,CAAAA,CAAWD,CAAAA,CAAOnB,CAAAA,CAAKqB,CAAAA,CAAYC,CAAS,EACvE,CACA,MACF,CACAlB,CAAAA,CAAK,QAAQ,IAAA,CAAKJ,CAAG,CAAA,CACjBI,CAAAA,CAAK,OAAA,CAAQ,MAAA,CAASiB,GAAcjB,CAAAA,CAAK,KAAA,CAAQkB,CAAAA,EACnDV,CAAAA,CAAUR,CAAI,EAAA,CAElB,CAEA,SAASmB,CAAAA,CAA0BnB,CAAAA,CAAqB,CACtDA,CAAAA,CAAK,OAAA,CAAQ,OAAS,CAAA,CACtB,IAAA,IAAWe,CAAAA,IAASf,CAAAA,CAAK,QAAA,CACvBmB,CAAAA,CAAUJ,CAAK,CAAA,CAEjBf,CAAAA,CAAK,QAAA,CAAS,MAAA,CAAS,EACzB,CAyCO,SAASoB,CAAAA,CAA+BC,CAAAA,CAAoC,CACjF,GAAM,CAAE,MAAA,CAAA1B,CAAO,CAAA,CAAI0B,CAAAA,CACbJ,CAAAA,CAAaI,CAAAA,CAAK,UAAA,EAAc,EAAA,CAChCH,EAAYG,CAAAA,CAAK,SAAA,EAAa,EAEpC,GACE,CAAC,OAAO,QAAA,CAAS1B,CAAAA,CAAO,CAAC,CAAA,EACzB,CAAC,MAAA,CAAO,SAASA,CAAAA,CAAO,CAAC,CAAA,EACzB,CAAC,MAAA,CAAO,QAAA,CAASA,EAAO,KAAK,CAAA,EAC7B,CAAC,MAAA,CAAO,QAAA,CAASA,CAAAA,CAAO,MAAM,CAAA,CAE9B,MAAM,IAAIN,CAAAA,CAAc,oCAAoC,CAAA,CAE9D,GAAIM,CAAAA,CAAO,KAAA,EAAS,CAAA,CAClB,MAAM,IAAIN,CAAAA,CAAc,0BAA0B,CAAA,CAEpD,GAAIM,CAAAA,CAAO,MAAA,EAAU,CAAA,CACnB,MAAM,IAAIN,CAAAA,CAAc,2BAA2B,CAAA,CAErD,GAAI,CAAC,MAAA,CAAO,UAAU4B,CAAU,CAAA,EAAKA,GAAc,CAAA,CACjD,MAAM,IAAI5B,CAAAA,CAAc,uCAAuC,CAAA,CAEjE,GAAI,CAAC,MAAA,CAAO,UAAU6B,CAAS,CAAA,EAAKA,CAAAA,EAAa,CAAA,CAC/C,MAAM,IAAI7B,EAAc,sCAAsC,CAAA,CAGhE,IAAMiC,CAAAA,CAAkB,CACtB,IAAA,CAAM,CACJ,MAAA,CAAQ,CAAE,GAAG3B,CAAO,CAAA,CACpB,KAAA,CAAO,EACP,OAAA,CAAS,EAAC,CACV,QAAA,CAAU,EACZ,EACA,UAAA,CAAAsB,CAAAA,CACA,SAAA,CAAAC,CAAAA,CACA,QAAA,CAAU,KACZ,EAEA,SAASK,CAAAA,EAAW,CAClB,GAAID,CAAAA,CAAM,QAAA,CAAU,MAAM,IAAIhC,CAAAA,CAAsB,0CAA0C,CAChG,CAEA,SAASkC,CAAAA,CAAO5B,CAAAA,CAAc,CAE5B,GADA2B,CAAAA,EAAG,CAED,CAAC3B,CAAAA,EACD,CAAC,MAAA,CAAO,QAAA,CAASA,CAAAA,CAAI,CAAC,GACtB,CAAC,MAAA,CAAO,QAAA,CAASA,CAAAA,CAAI,CAAC,CAAA,EACtB,CAAC,MAAA,CAAO,QAAA,CAASA,CAAAA,CAAI,KAAK,CAAA,EAC1B,CAAC,OAAO,QAAA,CAASA,CAAAA,CAAI,MAAM,CAAA,CAE3B,MAAM,IAAIP,EACR,4EACF,CAAA,CAEF,GAAIO,CAAAA,CAAI,KAAA,CAAQ,CAAA,CACd,MAAM,IAAIP,CAAAA,CAAc,oCAAoC,CAAA,CAE9D,GAAIO,CAAAA,CAAI,OAAS,CAAA,CACf,MAAM,IAAIP,CAAAA,CAAc,qCAAqC,EAE/D2B,CAAAA,CAAWM,CAAAA,CAAM,IAAA,CAAM1B,CAAAA,CAAK0B,CAAAA,CAAM,UAAA,CAAYA,EAAM,SAAS,EAC/D,CAgBA,IAAMG,CAAAA,CAAa,IAAI,IACjBC,CAAAA,CAA0B,EAAC,CAC3BC,CAAAA,CAAsB,CAAE,CAAA,CAAG,EAAG,CAAA,CAAG,CAAA,CAAG,KAAA,CAAO,CAAA,CAAG,MAAA,CAAQ,CAAE,EAE9D,SAASC,CAAAA,CAAYC,CAAAA,CAAsB,CAGzC,IAAMC,CAAAA,CAAKD,EAAO,CAAA,CACZE,CAAAA,CAAKF,CAAAA,CAAO,CAAA,CACZG,CAAAA,CAAKH,CAAAA,CAAO,MACZI,CAAAA,CAAKJ,CAAAA,CAAO,MAAA,CAQlB,IAPAF,CAAAA,CAAc,CAAA,CAAIG,EAClBH,CAAAA,CAAc,CAAA,CAAII,EAClBJ,CAAAA,CAAc,KAAA,CAAQK,EACtBL,CAAAA,CAAc,MAAA,CAASM,CAAAA,CACvBR,CAAAA,CAAW,KAAA,EAAM,CACjBC,EAAa,MAAA,CAAS,CAAA,CACtBA,CAAAA,CAAa,IAAA,CAAKJ,CAAAA,CAAM,IAAI,EACrBI,CAAAA,CAAa,MAAA,CAAS,CAAA,EAAG,CAC9B,IAAM1B,CAAAA,CAAO0B,EAAa,GAAA,EAAI,CAC9B,GAAI1B,CAAAA,GAAS,MAAA,EACRT,CAAAA,CAAaS,EAAK,MAAA,CAAQ2B,CAAa,CAAA,CAC5C,CAAA,IAAA,IAAW/B,CAAAA,IAAOI,CAAAA,CAAK,QAASyB,CAAAA,CAAW,GAAA,CAAI7B,CAAG,CAAA,CAClD,IAAA,IAAWmB,CAAAA,IAASf,EAAK,QAAA,CAAU0B,CAAAA,CAAa,IAAA,CAAKX,CAAK,EAAA,CAC5D,CACA,OAAOU,CACT,CAOA,SAASS,CAAAA,CAAeL,CAAAA,CAAoB,CAC1C,GACE,CAACA,CAAAA,EACD,CAAC,MAAA,CAAO,QAAA,CAASA,EAAO,CAAC,CAAA,EACzB,CAAC,MAAA,CAAO,QAAA,CAASA,CAAAA,CAAO,CAAC,CAAA,EACzB,CAAC,MAAA,CAAO,QAAA,CAASA,CAAAA,CAAO,KAAK,GAC7B,CAAC,MAAA,CAAO,QAAA,CAASA,CAAAA,CAAO,MAAM,CAAA,CAE9B,MAAM,IAAIxC,CAAAA,CACR,+EACF,CAAA,CAEF,GAAIwC,CAAAA,CAAO,MAAQ,CAAA,CACjB,MAAM,IAAIxC,CAAAA,CAAc,kDAAkD,CAAA,CAE5E,GAAIwC,CAAAA,CAAO,MAAA,CAAS,CAAA,CAClB,MAAM,IAAIxC,CAAAA,CAAc,mDAAmD,CAE/E,CAEA,SAAS8C,CAAAA,CAASN,CAAAA,CAAmB,CACnC,OAAAN,CAAAA,EAAG,CACHW,CAAAA,CAAeL,CAAM,CAAA,CACd,MAAM,IAAA,CAAKD,CAAAA,CAAYC,CAAM,CAAC,CACvC,CAEA,SAASO,CAAAA,CAAaP,CAAAA,CAAcQ,CAAAA,CAAkB,CACpDd,CAAAA,EAAG,CACHW,EAAeL,CAAM,CAAA,CACrB,IAAMS,CAAAA,CAAMV,CAAAA,CAAYC,CAAM,EAC9BQ,CAAAA,CAAO,MAAA,CAAS,CAAA,CAChB,IAAA,IAAWE,CAAAA,IAAKD,CAAAA,CAAKD,EAAO,IAAA,CAAKE,CAAC,CAAA,CAClC,OAAOF,CACT,CAEA,SAASG,CAAAA,EAAc,CACrBjB,CAAAA,EAAG,CACHJ,CAAAA,CAAUG,CAAAA,CAAM,IAAI,CAAA,CAKpBG,CAAAA,CAAW,OAAM,CACjBC,CAAAA,CAAa,OAAS,EACxB,CAEA,SAASe,CAAAA,EAAgB,CACnBnB,CAAAA,CAAM,WACVA,CAAAA,CAAM,QAAA,CAAW,IAAA,CACjBA,CAAAA,CAAM,IAAA,CAAK,OAAA,CAAQ,OAAS,CAAA,CAC5BA,CAAAA,CAAM,IAAA,CAAK,QAAA,CAAS,MAAA,CAAS,CAAA,CAC7BG,EAAW,KAAA,EAAM,CACjBC,CAAAA,CAAa,MAAA,CAAS,CAAA,EACxB,CAEA,OAAO,CACL,MAAA,CAAAF,CAAAA,CACA,QAAA,CAAAW,CAAAA,CACA,YAAA,CAAAC,EACA,KAAA,CAAAI,CAAAA,CACA,OAAA,CAAAC,CAAAA,CACA,IAAI,QAAA,EAAW,CACb,OAAOnB,CAAAA,CAAM,QACf,CACF,CACF","file":"index.cjs","sourcesContent":["// aiquadtreejs — 2D quadtree for per-frame rebuild collision broadphase.\n//\n// v0.5.1: full implementation. Plain-object nodes, iterative-DFS retrieve,\n// Set-based dedup, idempotent dispose, destructurable methods (no `this`).\n\n/**\n * Axis-aligned bounding box.\n *\n * Right-open coordinate semantics: `x` / `y` are the top-left corner and\n * `x + width` / `y + height` are **exclusive**. A 32×32 box at `(0, 0)`\n * covers `[0, 32)` on both axes. (This matches the convention used by\n * renderers such as PixiJS `getBounds()`, but the type is renderer-agnostic.)\n *\n * @public\n */\nexport interface AABB {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Configuration for {@link createQuadtree}.\n *\n * @public\n */\nexport interface QuadtreeOptions {\n /**\n * Outer bounds. Objects partially outside `bounds` still insert into\n * whichever child nodes they overlap; objects fully outside are ignored\n * by `retrieve()` because no node overlaps them.\n */\n bounds: AABB;\n\n /**\n * Threshold above which a node subdivides. Default `10`. Lower values\n * mean deeper trees and fewer candidates per `retrieve()`; higher values\n * mean shallower trees and cheaper `insert()`.\n */\n maxObjects?: number;\n\n /**\n * Maximum subdivision depth. Default `4`. Caps recursion so a very dense\n * cluster doesn't blow up into an unbounded tree.\n *\n * **Spanning-object cost warning:** an object that spans multiple quadrant\n * boundaries is copied into every child node it overlaps. In the worst case\n * (an object covering the entire tree bounds) at depth `L`, up to `4^L`\n * nodes each hold a reference to that object. The default of `4` means at\n * most 256 leaf nodes; raising `maxLevels` to `10` allows ~1 M nodes, and\n * `20` allows ~10^12 — **OOM territory for dense inputs with spanning\n * objects**. Raise this value only when you understand the distribution of\n * large vs small objects in your scene. No upper-bound cap is applied\n * (the caller knows their workload); the default `4` is safe for typical\n * game scenes with 500–10,000 entities.\n */\n maxLevels?: number;\n}\n\n/**\n * Quadtree storing objects that extend {@link AABB}. `T` may carry any\n * payload (entity ID, sprite reference, user data) alongside the geometry.\n *\n * The expected usage pattern is **per-frame rebuild**: at the start of each\n * frame, call `clear()` and re-`insert()` every active object. This is\n * cheaper than tracking movements through the tree and gives correct results\n * regardless of how objects moved.\n *\n * @public\n */\nexport interface Quadtree<T extends AABB> {\n /**\n * Insert an object. The same object reference may legitimately appear\n * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`\n * deduplicates with a `Set` so the caller sees it exactly once.\n *\n * @throws {@link QuadtreeError} if any of `x`, `y`, `width`, or `height`\n * is non-finite (`NaN`, `Infinity`, `-Infinity`), or if `width` or\n * `height` is negative. Zero-extent objects (points / lines) are valid.\n */\n insert(obj: T): void;\n\n /**\n * Return every inserted object whose containing node overlaps `region`,\n * deduplicated. The result is a **broadphase**: callers must still run\n * a precise AABB or pixel-level hit test on each candidate.\n *\n * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,\n * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,\n * `-Infinity`), or if `region.width` or `region.height` is negative.\n * Zero-extent regions are valid (they still query any overlapping node).\n */\n retrieve(region: AABB): T[];\n\n /**\n * Zero-allocation variant of {@link retrieve}.\n *\n * Clears `target` (sets `target.length = 0`), walks the tree using the same\n * iterative DFS + Set-based dedup as {@link retrieve}, then writes every\n * deduplicated candidate into `target` and returns it.\n *\n * Designed for hot-path callers (per-frame broadphase queries in a game\n * loop) that hold a permanent `T[]` buffer and want to avoid allocating a\n * fresh result array on every call.\n *\n * @invariant `target` identity is preserved — only its contents are\n * replaced. `retrieveInto(r, buf) === buf` always holds.\n * @invariant After return, `target.length` equals the deduplicated\n * candidate count. No `undefined` / `null` holes.\n * @invariant Empty result set → `target.length === 0`.\n * @invariant Dedup semantics identical to {@link retrieve}: objects\n * spanning multiple quadrants appear exactly once.\n *\n * Allocation: in steady state this performs no per-call heap allocation.\n * The dedup `Set` and DFS stack are reused across calls (cleared, not\n * re-created), and results are written into the caller's `target` instead\n * of a fresh array. The first calls may grow the internal scratch; once\n * result sizes stabilise, allocation amortises to zero — the design goal\n * for per-frame broadphase loops issuing thousands of queries.\n *\n * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,\n * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,\n * `-Infinity`), or if `region.width` or `region.height` is negative.\n * Zero-extent regions are valid.\n */\n retrieveInto(region: AABB, target: T[]): T[];\n\n /**\n * Reset the tree to empty. The root node object is reused across\n * frames; child nodes are released on clear() and re-created next\n * time subdivision triggers. The per-frame churn is bounded by\n * `4 * (subdivided-internal-node-count)` and stays well inside V8's\n * young-generation budget for typical game-loop usage.\n *\n * Internal scratch buffers (dedup `Set` + DFS stack) are also drained on\n * clear(), matching the GC guarantee already provided by {@link dispose}.\n * This ensures a tree held alive but not queried after clear() does not\n * retain the previous query's object references.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops references so the GC can reclaim everything.\n * After disposal, every method except `dispose` itself — `insert`,\n * `retrieve`, `retrieveInto`, `clear` — throws {@link QuadtreeDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable quadtree error — thrown by `createQuadtree` for invalid\n * construction options and by `insert()` for precondition violations\n * (e.g. an inserted object with non-finite coordinates or negative\n * `width` / `height`).\n *\n * @public\n */\nexport class QuadtreeError extends Error {\n override readonly name = \"QuadtreeError\";\n}\n\n/**\n * Thrown by any quadtree method called after {@link Quadtree.dispose}.\n *\n * @public\n */\nexport class QuadtreeDisposedError extends Error {\n override readonly name = \"QuadtreeDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\ninterface Node<T extends AABB> {\n bounds: AABB;\n level: number;\n objects: T[];\n children: Node<T>[];\n}\n\ninterface State<T extends AABB> {\n root: Node<T>;\n maxObjects: number;\n maxLevels: number;\n disposed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction rectsOverlap(a: AABB, b: AABB): boolean {\n return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n}\n\n// Root containment check used only at the insert root gate.\n//\n// Right-open semantics for positive-extent dimensions (matching rectsOverlap):\n// contained iff obj.x < bounds.x + bounds.width AND obj.x + obj.width > bounds.x\n//\n// Zero-extent exception for the minimum edge: a zero-size point sitting exactly\n// on bounds.x or bounds.y satisfies neither side of the strict-inequality test,\n// so it would be silently dropped. Instead, per axis:\n// - zero-extent: contained iff coordinate is within [bounds.min, bounds.max) —\n// inclusive minimum, exclusive maximum (right-open, matching the box contract)\n// - positive-extent: keep the existing strict right-open overlap (unchanged)\n//\n// This matches quadrantIndices' own zero-extent fallback (obj.x >= midX etc.)\n// and preserves the invariant that a positive-size object flush on the right/bottom\n// exclusive boundary stays rejected.\nfunction rootContains(bounds: AABB, obj: AABB): boolean {\n const inX =\n obj.width === 0\n ? obj.x >= bounds.x && obj.x < bounds.x + bounds.width\n : obj.x < bounds.x + bounds.width && obj.x + obj.width > bounds.x;\n const inY =\n obj.height === 0\n ? obj.y >= bounds.y && obj.y < bounds.y + bounds.height\n : obj.y < bounds.y + bounds.height && obj.y + obj.height > bounds.y;\n return inX && inY;\n}\n\nfunction quadrantIndices<T extends AABB>(node: Node<T>, obj: AABB): number[] {\n const midX = node.bounds.x + node.bounds.width / 2;\n const midY = node.bounds.y + node.bounds.height / 2;\n // Zero-extent objects (points) sitting exactly on midX / midY would fall\n // through both `<` and `>` checks; treat the point as belonging to the\n // right/bottom side so it doesn't silently disappear.\n const inLeft = obj.x < midX;\n const inRight = obj.width === 0 ? obj.x >= midX : obj.x + obj.width > midX;\n const inTop = obj.y < midY;\n const inBottom = obj.height === 0 ? obj.y >= midY : obj.y + obj.height > midY;\n const result: number[] = [];\n if (inTop && inLeft) result.push(0);\n if (inTop && inRight) result.push(1);\n if (inBottom && inLeft) result.push(2);\n if (inBottom && inRight) result.push(3);\n return result;\n}\n\nfunction subdivide<T extends AABB>(node: Node<T>): void {\n const w = node.bounds.width / 2;\n const h = node.bounds.height / 2;\n const x = node.bounds.x;\n const y = node.bounds.y;\n const lvl = node.level + 1;\n node.children.push(\n { bounds: { x, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n );\n for (const obj of node.objects) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) child.objects.push(obj);\n }\n }\n node.objects.length = 0;\n}\n\nfunction insertNode<T extends AABB>(\n node: Node<T>,\n obj: T,\n maxObjects: number,\n maxLevels: number,\n): void {\n // Reject objects entirely outside the root bounds; for inner nodes we\n // trust `quadrantIndices` to route correctly (it has zero-extent fallback\n // logic that `rectsOverlap` does not, so the strict check is too tight\n // at child level for points sitting on a child boundary).\n // rootContains is used instead of rectsOverlap here so that zero-size\n // points/lines sitting exactly on the minimum (left/top) edge are accepted\n // with inclusive semantics, whilst positive-size objects retain right-open\n // exclusion on the maximum edge.\n if (node.level === 0 && !rootContains(node.bounds, obj)) return;\n if (node.children.length === 4) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) insertNode(child, obj, maxObjects, maxLevels);\n }\n return;\n }\n node.objects.push(obj);\n if (node.objects.length > maxObjects && node.level < maxLevels) {\n subdivide(node);\n }\n}\n\nfunction clearNode<T extends AABB>(node: Node<T>): void {\n node.objects.length = 0;\n for (const child of node.children) {\n clearNode(child);\n }\n node.children.length = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a 2D quadtree.\n *\n * @example\n * ```ts\n * import { createQuadtree, type AABB } from \"aiquadtreejs\";\n *\n * interface Body extends AABB {\n * id: number;\n * }\n *\n * const entities: Body[] = [\n * { id: 1, x: 100, y: 100, width: 32, height: 32 },\n * { id: 2, x: 400, y: 250, width: 32, height: 32 },\n * ];\n * const player: Body = { id: 0, x: 200, y: 200, width: 32, height: 32 };\n *\n * const qt = createQuadtree<Body>({\n * bounds: { x: 0, y: 0, width: 800, height: 600 },\n * maxObjects: 10,\n * maxLevels: 4,\n * });\n *\n * // Per-frame:\n * qt.clear();\n * for (const e of entities) qt.insert(e);\n *\n * // Broadphase lookup near the player:\n * const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };\n * const candidates = qt.retrieve(region);\n * // Caller runs a precise hit test on `candidates`.\n * ```\n *\n * @public\n */\nexport function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T> {\n const { bounds } = opts;\n const maxObjects = opts.maxObjects ?? 10;\n const maxLevels = opts.maxLevels ?? 4;\n\n if (\n !Number.isFinite(bounds.x) ||\n !Number.isFinite(bounds.y) ||\n !Number.isFinite(bounds.width) ||\n !Number.isFinite(bounds.height)\n ) {\n throw new QuadtreeError(\"bounds must contain finite numbers\");\n }\n if (bounds.width <= 0) {\n throw new QuadtreeError(\"bounds.width must be > 0\");\n }\n if (bounds.height <= 0) {\n throw new QuadtreeError(\"bounds.height must be > 0\");\n }\n if (!Number.isInteger(maxObjects) || maxObjects <= 0) {\n throw new QuadtreeError(\"maxObjects must be a positive integer\");\n }\n if (!Number.isInteger(maxLevels) || maxLevels <= 0) {\n throw new QuadtreeError(\"maxLevels must be a positive integer\");\n }\n\n const state: State<T> = {\n root: {\n bounds: { ...bounds },\n level: 0,\n objects: [],\n children: [],\n },\n maxObjects,\n maxLevels,\n disposed: false,\n };\n\n function ck(): void {\n if (state.disposed) throw new QuadtreeDisposedError(\"aiquadtreejs: quadtree has been disposed\");\n }\n\n function insert(obj: T): void {\n ck();\n if (\n !obj ||\n !Number.isFinite(obj.x) ||\n !Number.isFinite(obj.y) ||\n !Number.isFinite(obj.width) ||\n !Number.isFinite(obj.height)\n ) {\n throw new QuadtreeError(\n \"inserted object must be defined with finite numeric x, y, width and height\",\n );\n }\n if (obj.width < 0) {\n throw new QuadtreeError(\"inserted object width must be >= 0\");\n }\n if (obj.height < 0) {\n throw new QuadtreeError(\"inserted object height must be >= 0\");\n }\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n // Reusable scratch for retrieveSet, hoisted so steady-state queries\n // allocate nothing. Safe because the returned Set never escapes the\n // module: retrieve copies it out via Array.from and retrieveInto via a\n // push loop, both synchronously and fully before any subsequent call.\n //\n // Plain-data assumption (tightened, QDT-B-02): region.x/y/width/height\n // are read once into locals at the top of retrieveSet, then written into\n // the reusable scratchRegion (no per-query allocation — the zero-alloc\n // contract of retrieveInto holds). This prevents a structurally-typed\n // region whose getter calls back into retrieve* from corrupting the shared\n // scratch mid-walk: any re-entrant call triggered by a getter completes\n // synchronously during the four reads, before this call touches scratch.\n // Adversarial-only: plain-object callers (all documented examples) are\n // unaffected.\n const scratchSet = new Set<T>();\n const scratchStack: Node<T>[] = [];\n const scratchRegion: AABB = { x: 0, y: 0, width: 0, height: 0 };\n\n function retrieveSet(region: AABB): Set<T> {\n // Snapshot region fields into locals once so that a getter-bearing\n // region cannot mutate the walk by re-entering retrieve* mid-DFS.\n const rx = region.x;\n const ry = region.y;\n const rw = region.width;\n const rh = region.height;\n scratchRegion.x = rx;\n scratchRegion.y = ry;\n scratchRegion.width = rw;\n scratchRegion.height = rh;\n scratchSet.clear();\n scratchStack.length = 0;\n scratchStack.push(state.root);\n while (scratchStack.length > 0) {\n const node = scratchStack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, scratchRegion)) continue;\n for (const obj of node.objects) scratchSet.add(obj);\n for (const child of node.children) scratchStack.push(child);\n }\n return scratchSet;\n }\n\n /**\n * Validate a region AABB for use in retrieve / retrieveInto.\n * Mirrors insert()'s 0.5.1 validation: non-finite coordinates or negative\n * dimensions throw QuadtreeError with an `aiquadtreejs: ` prefix message.\n */\n function validateRegion(region: AABB): void {\n if (\n !region ||\n !Number.isFinite(region.x) ||\n !Number.isFinite(region.y) ||\n !Number.isFinite(region.width) ||\n !Number.isFinite(region.height)\n ) {\n throw new QuadtreeError(\n \"aiquadtreejs: retrieve region must have finite numeric x, y, width and height\",\n );\n }\n if (region.width < 0) {\n throw new QuadtreeError(\"aiquadtreejs: retrieve region width must be >= 0\");\n }\n if (region.height < 0) {\n throw new QuadtreeError(\"aiquadtreejs: retrieve region height must be >= 0\");\n }\n }\n\n function retrieve(region: AABB): T[] {\n ck();\n validateRegion(region);\n return Array.from(retrieveSet(region));\n }\n\n function retrieveInto(region: AABB, target: T[]): T[] {\n ck();\n validateRegion(region);\n const set = retrieveSet(region);\n target.length = 0;\n for (const v of set) target.push(v);\n return target;\n }\n\n function clear(): void {\n ck();\n clearNode(state.root);\n // Drain internal scratch so that a tree held alive but not queried after\n // clear() does not pin the previous query's object references against GC.\n // (dispose() drains scratch for the same reason; clear() now provides the\n // same guarantee for the per-frame rebuild pattern.)\n scratchSet.clear();\n scratchStack.length = 0;\n }\n\n function dispose(): void {\n if (state.disposed) return;\n state.disposed = true;\n state.root.objects.length = 0;\n state.root.children.length = 0;\n scratchSet.clear();\n scratchStack.length = 0;\n }\n\n return {\n insert,\n retrieve,\n retrieveInto,\n clear,\n dispose,\n get disposed() {\n return state.disposed;\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["QuadtreeError","QuadtreeDisposedError","rectsOverlap","a","b","rootContains","bounds","obj","inX","inY","quadrantIndices","node","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","state","ck","insert","scratchSet","scratchStack","scratchRegion","retrieveSet","region","rx","ry","rw","rh","validateRegion","retrieve","retrieveInto","target","set","v","clear","dispose"],"mappings":"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","file":"index.cjs","sourcesContent":["// aiquadtreejs — 2D quadtree for per-frame rebuild collision broadphase.\n//\n// Plain-object nodes, iterative-DFS retrieve, Set-based dedup, idempotent\n// dispose, destructurable methods (no `this`). Version: see package.json.\n\n/**\n * Axis-aligned bounding box.\n *\n * Right-open coordinate semantics: `x` / `y` are the top-left corner and\n * `x + width` / `y + height` are **exclusive**. A 32×32 box at `(0, 0)`\n * covers `[0, 32)` on both axes. (This matches the convention used by\n * renderers such as PixiJS `getBounds()`, but the type is renderer-agnostic.)\n *\n * @public\n */\nexport interface AABB {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Configuration for {@link createQuadtree}.\n *\n * @public\n */\nexport interface QuadtreeOptions {\n /**\n * Outer bounds. Objects partially outside `bounds` still insert into\n * whichever child nodes they overlap; objects fully outside are ignored\n * by `retrieve()` because no node overlaps them.\n */\n bounds: AABB;\n\n /**\n * Threshold above which a node subdivides. Default `10`. Lower values\n * mean deeper trees and fewer candidates per `retrieve()`; higher values\n * mean shallower trees and cheaper `insert()`.\n */\n maxObjects?: number;\n\n /**\n * Maximum subdivision depth. Default `4`. Caps recursion so a very dense\n * cluster doesn't blow up into an unbounded tree.\n *\n * **Spanning-object cost warning:** an object that spans multiple quadrant\n * boundaries is copied into every child node it overlaps. In the worst case\n * (an object covering the entire tree bounds) at depth `L`, up to `4^L`\n * nodes each hold a reference to that object. The default of `4` means at\n * most 256 leaf nodes; raising `maxLevels` to `10` allows ~1 M nodes, and\n * `20` allows ~10^12 — **OOM territory for dense inputs with spanning\n * objects**. Raise this value only when you understand the distribution of\n * large vs small objects in your scene. No upper-bound cap is applied\n * (the caller knows their workload); the default `4` is safe for typical\n * game scenes with 500–10,000 entities.\n */\n maxLevels?: number;\n}\n\n/**\n * Quadtree storing objects that extend {@link AABB}. `T` may carry any\n * payload (entity ID, sprite reference, user data) alongside the geometry.\n *\n * The expected usage pattern is **per-frame rebuild**: at the start of each\n * frame, call `clear()` and re-`insert()` every active object. This is\n * cheaper than tracking movements through the tree and gives correct results\n * regardless of how objects moved.\n *\n * @public\n */\nexport interface Quadtree<T extends AABB> {\n /**\n * Insert an object. The same object reference may legitimately appear\n * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`\n * deduplicates with a `Set` so the caller sees it exactly once.\n *\n * @throws {@link QuadtreeError} if any of `x`, `y`, `width`, or `height`\n * is non-finite (`NaN`, `Infinity`, `-Infinity`), or if `width` or\n * `height` is negative. Zero-extent objects (points / lines) are valid.\n */\n insert(obj: T): void;\n\n /**\n * Return every inserted object whose containing node overlaps `region`,\n * deduplicated. The result is a **broadphase**: callers must still run\n * a precise AABB or pixel-level hit test on each candidate.\n *\n * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,\n * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,\n * `-Infinity`), or if `region.width` or `region.height` is negative.\n * Zero-extent regions are valid (they still query any overlapping node).\n */\n retrieve(region: AABB): T[];\n\n /**\n * Zero-allocation variant of {@link retrieve}.\n *\n * Clears `target` (sets `target.length = 0`), walks the tree using the same\n * iterative DFS + Set-based dedup as {@link retrieve}, then writes every\n * deduplicated candidate into `target` and returns it.\n *\n * Designed for hot-path callers (per-frame broadphase queries in a game\n * loop) that hold a permanent `T[]` buffer and want to avoid allocating a\n * fresh result array on every call.\n *\n * @invariant `target` identity is preserved — only its contents are\n * replaced. `retrieveInto(r, buf) === buf` always holds.\n * @invariant After return, `target.length` equals the deduplicated\n * candidate count. No `undefined` / `null` holes.\n * @invariant Empty result set → `target.length === 0`.\n * @invariant Dedup semantics identical to {@link retrieve}: objects\n * spanning multiple quadrants appear exactly once.\n *\n * Allocation: in steady state this performs no per-call heap allocation.\n * The dedup `Set` and DFS stack are reused across calls (cleared, not\n * re-created), and results are written into the caller's `target` instead\n * of a fresh array. The first calls may grow the internal scratch; once\n * result sizes stabilise, allocation amortises to zero — the design goal\n * for per-frame broadphase loops issuing thousands of queries.\n *\n * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,\n * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,\n * `-Infinity`), or if `region.width` or `region.height` is negative.\n * Zero-extent regions are valid.\n */\n retrieveInto(region: AABB, target: T[]): T[];\n\n /**\n * Reset the tree to empty. The root node object is reused across\n * frames; child nodes are released on clear() and re-created next\n * time subdivision triggers. The per-frame churn is bounded by\n * `4 * (subdivided-internal-node-count)` and stays well inside V8's\n * young-generation budget for typical game-loop usage.\n *\n * Internal scratch buffers (dedup `Set` + DFS stack) are also drained on\n * clear(), matching the GC guarantee already provided by {@link dispose}.\n * This ensures a tree held alive but not queried after clear() does not\n * retain the previous query's object references.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops references so the GC can reclaim everything.\n * After disposal, every method except `dispose` itself — `insert`,\n * `retrieve`, `retrieveInto`, `clear` — throws {@link QuadtreeDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable quadtree error — thrown by `createQuadtree` for invalid\n * construction options and by `insert()` for precondition violations\n * (e.g. an inserted object with non-finite coordinates or negative\n * `width` / `height`).\n *\n * @public\n */\nexport class QuadtreeError extends Error {\n override readonly name = \"QuadtreeError\";\n}\n\n/**\n * Thrown by any quadtree method called after {@link Quadtree.dispose}.\n *\n * @public\n */\nexport class QuadtreeDisposedError extends Error {\n override readonly name = \"QuadtreeDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\ninterface Node<T extends AABB> {\n bounds: AABB;\n level: number;\n objects: T[];\n children: Node<T>[];\n}\n\ninterface State<T extends AABB> {\n root: Node<T>;\n maxObjects: number;\n maxLevels: number;\n disposed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction rectsOverlap(a: AABB, b: AABB): boolean {\n return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n}\n\n// Root containment check used only at the insert root gate.\n//\n// Right-open semantics for positive-extent dimensions (matching rectsOverlap):\n// contained iff obj.x < bounds.x + bounds.width AND obj.x + obj.width > bounds.x\n//\n// Zero-extent exception for the minimum edge: a zero-size point sitting exactly\n// on bounds.x or bounds.y satisfies neither side of the strict-inequality test,\n// so it would be silently dropped. Instead, per axis:\n// - zero-extent: contained iff coordinate is within [bounds.min, bounds.max) —\n// inclusive minimum, exclusive maximum (right-open, matching the box contract)\n// - positive-extent: keep the existing strict right-open overlap (unchanged)\n//\n// This matches quadrantIndices' own zero-extent fallback (obj.x >= midX etc.)\n// and preserves the invariant that a positive-size object flush on the right/bottom\n// exclusive boundary stays rejected.\nfunction rootContains(bounds: AABB, obj: AABB): boolean {\n const inX =\n obj.width === 0\n ? obj.x >= bounds.x && obj.x < bounds.x + bounds.width\n : obj.x < bounds.x + bounds.width && obj.x + obj.width > bounds.x;\n const inY =\n obj.height === 0\n ? obj.y >= bounds.y && obj.y < bounds.y + bounds.height\n : obj.y < bounds.y + bounds.height && obj.y + obj.height > bounds.y;\n return inX && inY;\n}\n\nfunction quadrantIndices<T extends AABB>(node: Node<T>, obj: AABB): number[] {\n const midX = node.bounds.x + node.bounds.width / 2;\n const midY = node.bounds.y + node.bounds.height / 2;\n // Zero-extent objects (points) sitting exactly on midX / midY would fall\n // through both `<` and `>` checks; treat the point as belonging to the\n // right/bottom side so it doesn't silently disappear.\n const inLeft = obj.x < midX;\n const inRight = obj.width === 0 ? obj.x >= midX : obj.x + obj.width > midX;\n const inTop = obj.y < midY;\n const inBottom = obj.height === 0 ? obj.y >= midY : obj.y + obj.height > midY;\n const result: number[] = [];\n if (inTop && inLeft) result.push(0);\n if (inTop && inRight) result.push(1);\n if (inBottom && inLeft) result.push(2);\n if (inBottom && inRight) result.push(3);\n return result;\n}\n\nfunction subdivide<T extends AABB>(node: Node<T>): void {\n const w = node.bounds.width / 2;\n const h = node.bounds.height / 2;\n const x = node.bounds.x;\n const y = node.bounds.y;\n const lvl = node.level + 1;\n node.children.push(\n { bounds: { x, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n );\n for (const obj of node.objects) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) child.objects.push(obj);\n }\n }\n node.objects.length = 0;\n}\n\nfunction insertNode<T extends AABB>(\n node: Node<T>,\n obj: T,\n maxObjects: number,\n maxLevels: number,\n): void {\n // Reject objects entirely outside the root bounds; for inner nodes we\n // trust `quadrantIndices` to route correctly (it has zero-extent fallback\n // logic that `rectsOverlap` does not, so the strict check is too tight\n // at child level for points sitting on a child boundary).\n // rootContains is used instead of rectsOverlap here so that zero-size\n // points/lines sitting exactly on the minimum (left/top) edge are accepted\n // with inclusive semantics, whilst positive-size objects retain right-open\n // exclusion on the maximum edge.\n if (node.level === 0 && !rootContains(node.bounds, obj)) return;\n if (node.children.length === 4) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) insertNode(child, obj, maxObjects, maxLevels);\n }\n return;\n }\n node.objects.push(obj);\n if (node.objects.length > maxObjects && node.level < maxLevels) {\n subdivide(node);\n }\n}\n\nfunction clearNode<T extends AABB>(node: Node<T>): void {\n node.objects.length = 0;\n for (const child of node.children) {\n clearNode(child);\n }\n node.children.length = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a 2D quadtree.\n *\n * @example\n * ```ts\n * import { createQuadtree, type AABB } from \"aiquadtreejs\";\n *\n * interface Body extends AABB {\n * id: number;\n * }\n *\n * const entities: Body[] = [\n * { id: 1, x: 100, y: 100, width: 32, height: 32 },\n * { id: 2, x: 400, y: 250, width: 32, height: 32 },\n * ];\n * const player: Body = { id: 0, x: 200, y: 200, width: 32, height: 32 };\n *\n * const qt = createQuadtree<Body>({\n * bounds: { x: 0, y: 0, width: 800, height: 600 },\n * maxObjects: 10,\n * maxLevels: 4,\n * });\n *\n * // Per-frame:\n * qt.clear();\n * for (const e of entities) qt.insert(e);\n *\n * // Broadphase lookup near the player:\n * const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };\n * const candidates = qt.retrieve(region);\n * // Caller runs a precise hit test on `candidates`.\n * ```\n *\n * @public\n */\nexport function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T> {\n const { bounds } = opts;\n const maxObjects = opts.maxObjects ?? 10;\n const maxLevels = opts.maxLevels ?? 4;\n\n if (\n !Number.isFinite(bounds.x) ||\n !Number.isFinite(bounds.y) ||\n !Number.isFinite(bounds.width) ||\n !Number.isFinite(bounds.height)\n ) {\n throw new QuadtreeError(\"bounds must contain finite numbers\");\n }\n if (bounds.width <= 0) {\n throw new QuadtreeError(\"bounds.width must be > 0\");\n }\n if (bounds.height <= 0) {\n throw new QuadtreeError(\"bounds.height must be > 0\");\n }\n if (!Number.isInteger(maxObjects) || maxObjects <= 0) {\n throw new QuadtreeError(\"maxObjects must be a positive integer\");\n }\n if (!Number.isInteger(maxLevels) || maxLevels <= 0) {\n throw new QuadtreeError(\"maxLevels must be a positive integer\");\n }\n\n const state: State<T> = {\n root: {\n bounds: { ...bounds },\n level: 0,\n objects: [],\n children: [],\n },\n maxObjects,\n maxLevels,\n disposed: false,\n };\n\n function ck(): void {\n if (state.disposed) throw new QuadtreeDisposedError(\"aiquadtreejs: quadtree has been disposed\");\n }\n\n function insert(obj: T): void {\n ck();\n if (\n !obj ||\n !Number.isFinite(obj.x) ||\n !Number.isFinite(obj.y) ||\n !Number.isFinite(obj.width) ||\n !Number.isFinite(obj.height)\n ) {\n throw new QuadtreeError(\n \"inserted object must be defined with finite numeric x, y, width and height\",\n );\n }\n if (obj.width < 0) {\n throw new QuadtreeError(\"inserted object width must be >= 0\");\n }\n if (obj.height < 0) {\n throw new QuadtreeError(\"inserted object height must be >= 0\");\n }\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n // Reusable scratch for retrieveSet, hoisted so steady-state queries\n // allocate nothing. Safe because the returned Set never escapes the\n // module: retrieve copies it out via Array.from and retrieveInto via a\n // push loop, both synchronously and fully before any subsequent call.\n //\n // Plain-data assumption (tightened, QDT-B-02): region.x/y/width/height\n // are read once into locals at the top of retrieveSet, then written into\n // the reusable scratchRegion (no per-query allocation — the zero-alloc\n // contract of retrieveInto holds). This prevents a structurally-typed\n // region whose getter calls back into retrieve* from corrupting the shared\n // scratch mid-walk: any re-entrant call triggered by a getter completes\n // synchronously during the four reads, before this call touches scratch.\n // Adversarial-only: plain-object callers (all documented examples) are\n // unaffected.\n const scratchSet = new Set<T>();\n const scratchStack: Node<T>[] = [];\n const scratchRegion: AABB = { x: 0, y: 0, width: 0, height: 0 };\n\n function retrieveSet(region: AABB): Set<T> {\n // Snapshot region fields into locals once so that a getter-bearing\n // region cannot mutate the walk by re-entering retrieve* mid-DFS.\n const rx = region.x;\n const ry = region.y;\n const rw = region.width;\n const rh = region.height;\n scratchRegion.x = rx;\n scratchRegion.y = ry;\n scratchRegion.width = rw;\n scratchRegion.height = rh;\n scratchSet.clear();\n scratchStack.length = 0;\n scratchStack.push(state.root);\n while (scratchStack.length > 0) {\n const node = scratchStack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, scratchRegion)) continue;\n for (const obj of node.objects) scratchSet.add(obj);\n for (const child of node.children) scratchStack.push(child);\n }\n return scratchSet;\n }\n\n /**\n * Validate a region AABB for use in retrieve / retrieveInto.\n * Non-finite coordinates or negative dimensions throw QuadtreeError with\n * an `aiquadtreejs: ` prefix message.\n */\n function validateRegion(region: AABB): void {\n if (\n !region ||\n !Number.isFinite(region.x) ||\n !Number.isFinite(region.y) ||\n !Number.isFinite(region.width) ||\n !Number.isFinite(region.height)\n ) {\n throw new QuadtreeError(\n \"aiquadtreejs: retrieve region must have finite numeric x, y, width and height\",\n );\n }\n if (region.width < 0) {\n throw new QuadtreeError(\"aiquadtreejs: retrieve region width must be >= 0\");\n }\n if (region.height < 0) {\n throw new QuadtreeError(\"aiquadtreejs: retrieve region height must be >= 0\");\n }\n }\n\n function retrieve(region: AABB): T[] {\n ck();\n validateRegion(region);\n return Array.from(retrieveSet(region));\n }\n\n function retrieveInto(region: AABB, target: T[]): T[] {\n ck();\n validateRegion(region);\n const set = retrieveSet(region);\n target.length = 0;\n for (const v of set) target.push(v);\n return target;\n }\n\n function clear(): void {\n ck();\n clearNode(state.root);\n // Drain internal scratch so that a tree held alive but not queried after\n // clear() does not pin the previous query's object references against GC.\n // (dispose() drains scratch for the same reason; clear() now provides the\n // same guarantee for the per-frame rebuild pattern.)\n scratchSet.clear();\n scratchStack.length = 0;\n }\n\n function dispose(): void {\n if (state.disposed) return;\n state.disposed = true;\n state.root.objects.length = 0;\n state.root.children.length = 0;\n scratchSet.clear();\n scratchStack.length = 0;\n }\n\n return {\n insert,\n retrieve,\n retrieveInto,\n clear,\n dispose,\n get disposed() {\n return state.disposed;\n },\n };\n}\n"]}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["QuadtreeError","QuadtreeDisposedError","rectsOverlap","a","b","rootContains","bounds","obj","inX","inY","quadrantIndices","node","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","state","ck","insert","scratchSet","scratchStack","scratchRegion","retrieveSet","region","rx","ry","rw","rh","validateRegion","retrieve","retrieveInto","target","set","v","clear","dispose"],"mappings":"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","file":"index.js","sourcesContent":["// aiquadtreejs — 2D quadtree for per-frame rebuild collision broadphase.\n//\n// v0.5.1: full implementation. Plain-object nodes, iterative-DFS retrieve,\n// Set-based dedup, idempotent dispose, destructurable methods (no `this`).\n\n/**\n * Axis-aligned bounding box.\n *\n * Right-open coordinate semantics: `x` / `y` are the top-left corner and\n * `x + width` / `y + height` are **exclusive**. A 32×32 box at `(0, 0)`\n * covers `[0, 32)` on both axes. (This matches the convention used by\n * renderers such as PixiJS `getBounds()`, but the type is renderer-agnostic.)\n *\n * @public\n */\nexport interface AABB {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Configuration for {@link createQuadtree}.\n *\n * @public\n */\nexport interface QuadtreeOptions {\n /**\n * Outer bounds. Objects partially outside `bounds` still insert into\n * whichever child nodes they overlap; objects fully outside are ignored\n * by `retrieve()` because no node overlaps them.\n */\n bounds: AABB;\n\n /**\n * Threshold above which a node subdivides. Default `10`. Lower values\n * mean deeper trees and fewer candidates per `retrieve()`; higher values\n * mean shallower trees and cheaper `insert()`.\n */\n maxObjects?: number;\n\n /**\n * Maximum subdivision depth. Default `4`. Caps recursion so a very dense\n * cluster doesn't blow up into an unbounded tree.\n *\n * **Spanning-object cost warning:** an object that spans multiple quadrant\n * boundaries is copied into every child node it overlaps. In the worst case\n * (an object covering the entire tree bounds) at depth `L`, up to `4^L`\n * nodes each hold a reference to that object. The default of `4` means at\n * most 256 leaf nodes; raising `maxLevels` to `10` allows ~1 M nodes, and\n * `20` allows ~10^12 — **OOM territory for dense inputs with spanning\n * objects**. Raise this value only when you understand the distribution of\n * large vs small objects in your scene. No upper-bound cap is applied\n * (the caller knows their workload); the default `4` is safe for typical\n * game scenes with 500–10,000 entities.\n */\n maxLevels?: number;\n}\n\n/**\n * Quadtree storing objects that extend {@link AABB}. `T` may carry any\n * payload (entity ID, sprite reference, user data) alongside the geometry.\n *\n * The expected usage pattern is **per-frame rebuild**: at the start of each\n * frame, call `clear()` and re-`insert()` every active object. This is\n * cheaper than tracking movements through the tree and gives correct results\n * regardless of how objects moved.\n *\n * @public\n */\nexport interface Quadtree<T extends AABB> {\n /**\n * Insert an object. The same object reference may legitimately appear\n * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`\n * deduplicates with a `Set` so the caller sees it exactly once.\n *\n * @throws {@link QuadtreeError} if any of `x`, `y`, `width`, or `height`\n * is non-finite (`NaN`, `Infinity`, `-Infinity`), or if `width` or\n * `height` is negative. Zero-extent objects (points / lines) are valid.\n */\n insert(obj: T): void;\n\n /**\n * Return every inserted object whose containing node overlaps `region`,\n * deduplicated. The result is a **broadphase**: callers must still run\n * a precise AABB or pixel-level hit test on each candidate.\n *\n * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,\n * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,\n * `-Infinity`), or if `region.width` or `region.height` is negative.\n * Zero-extent regions are valid (they still query any overlapping node).\n */\n retrieve(region: AABB): T[];\n\n /**\n * Zero-allocation variant of {@link retrieve}.\n *\n * Clears `target` (sets `target.length = 0`), walks the tree using the same\n * iterative DFS + Set-based dedup as {@link retrieve}, then writes every\n * deduplicated candidate into `target` and returns it.\n *\n * Designed for hot-path callers (per-frame broadphase queries in a game\n * loop) that hold a permanent `T[]` buffer and want to avoid allocating a\n * fresh result array on every call.\n *\n * @invariant `target` identity is preserved — only its contents are\n * replaced. `retrieveInto(r, buf) === buf` always holds.\n * @invariant After return, `target.length` equals the deduplicated\n * candidate count. No `undefined` / `null` holes.\n * @invariant Empty result set → `target.length === 0`.\n * @invariant Dedup semantics identical to {@link retrieve}: objects\n * spanning multiple quadrants appear exactly once.\n *\n * Allocation: in steady state this performs no per-call heap allocation.\n * The dedup `Set` and DFS stack are reused across calls (cleared, not\n * re-created), and results are written into the caller's `target` instead\n * of a fresh array. The first calls may grow the internal scratch; once\n * result sizes stabilise, allocation amortises to zero — the design goal\n * for per-frame broadphase loops issuing thousands of queries.\n *\n * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,\n * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,\n * `-Infinity`), or if `region.width` or `region.height` is negative.\n * Zero-extent regions are valid.\n */\n retrieveInto(region: AABB, target: T[]): T[];\n\n /**\n * Reset the tree to empty. The root node object is reused across\n * frames; child nodes are released on clear() and re-created next\n * time subdivision triggers. The per-frame churn is bounded by\n * `4 * (subdivided-internal-node-count)` and stays well inside V8's\n * young-generation budget for typical game-loop usage.\n *\n * Internal scratch buffers (dedup `Set` + DFS stack) are also drained on\n * clear(), matching the GC guarantee already provided by {@link dispose}.\n * This ensures a tree held alive but not queried after clear() does not\n * retain the previous query's object references.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops references so the GC can reclaim everything.\n * After disposal, every method except `dispose` itself — `insert`,\n * `retrieve`, `retrieveInto`, `clear` — throws {@link QuadtreeDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable quadtree error — thrown by `createQuadtree` for invalid\n * construction options and by `insert()` for precondition violations\n * (e.g. an inserted object with non-finite coordinates or negative\n * `width` / `height`).\n *\n * @public\n */\nexport class QuadtreeError extends Error {\n override readonly name = \"QuadtreeError\";\n}\n\n/**\n * Thrown by any quadtree method called after {@link Quadtree.dispose}.\n *\n * @public\n */\nexport class QuadtreeDisposedError extends Error {\n override readonly name = \"QuadtreeDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\ninterface Node<T extends AABB> {\n bounds: AABB;\n level: number;\n objects: T[];\n children: Node<T>[];\n}\n\ninterface State<T extends AABB> {\n root: Node<T>;\n maxObjects: number;\n maxLevels: number;\n disposed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction rectsOverlap(a: AABB, b: AABB): boolean {\n return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n}\n\n// Root containment check used only at the insert root gate.\n//\n// Right-open semantics for positive-extent dimensions (matching rectsOverlap):\n// contained iff obj.x < bounds.x + bounds.width AND obj.x + obj.width > bounds.x\n//\n// Zero-extent exception for the minimum edge: a zero-size point sitting exactly\n// on bounds.x or bounds.y satisfies neither side of the strict-inequality test,\n// so it would be silently dropped. Instead, per axis:\n// - zero-extent: contained iff coordinate is within [bounds.min, bounds.max) —\n// inclusive minimum, exclusive maximum (right-open, matching the box contract)\n// - positive-extent: keep the existing strict right-open overlap (unchanged)\n//\n// This matches quadrantIndices' own zero-extent fallback (obj.x >= midX etc.)\n// and preserves the invariant that a positive-size object flush on the right/bottom\n// exclusive boundary stays rejected.\nfunction rootContains(bounds: AABB, obj: AABB): boolean {\n const inX =\n obj.width === 0\n ? obj.x >= bounds.x && obj.x < bounds.x + bounds.width\n : obj.x < bounds.x + bounds.width && obj.x + obj.width > bounds.x;\n const inY =\n obj.height === 0\n ? obj.y >= bounds.y && obj.y < bounds.y + bounds.height\n : obj.y < bounds.y + bounds.height && obj.y + obj.height > bounds.y;\n return inX && inY;\n}\n\nfunction quadrantIndices<T extends AABB>(node: Node<T>, obj: AABB): number[] {\n const midX = node.bounds.x + node.bounds.width / 2;\n const midY = node.bounds.y + node.bounds.height / 2;\n // Zero-extent objects (points) sitting exactly on midX / midY would fall\n // through both `<` and `>` checks; treat the point as belonging to the\n // right/bottom side so it doesn't silently disappear.\n const inLeft = obj.x < midX;\n const inRight = obj.width === 0 ? obj.x >= midX : obj.x + obj.width > midX;\n const inTop = obj.y < midY;\n const inBottom = obj.height === 0 ? obj.y >= midY : obj.y + obj.height > midY;\n const result: number[] = [];\n if (inTop && inLeft) result.push(0);\n if (inTop && inRight) result.push(1);\n if (inBottom && inLeft) result.push(2);\n if (inBottom && inRight) result.push(3);\n return result;\n}\n\nfunction subdivide<T extends AABB>(node: Node<T>): void {\n const w = node.bounds.width / 2;\n const h = node.bounds.height / 2;\n const x = node.bounds.x;\n const y = node.bounds.y;\n const lvl = node.level + 1;\n node.children.push(\n { bounds: { x, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n );\n for (const obj of node.objects) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) child.objects.push(obj);\n }\n }\n node.objects.length = 0;\n}\n\nfunction insertNode<T extends AABB>(\n node: Node<T>,\n obj: T,\n maxObjects: number,\n maxLevels: number,\n): void {\n // Reject objects entirely outside the root bounds; for inner nodes we\n // trust `quadrantIndices` to route correctly (it has zero-extent fallback\n // logic that `rectsOverlap` does not, so the strict check is too tight\n // at child level for points sitting on a child boundary).\n // rootContains is used instead of rectsOverlap here so that zero-size\n // points/lines sitting exactly on the minimum (left/top) edge are accepted\n // with inclusive semantics, whilst positive-size objects retain right-open\n // exclusion on the maximum edge.\n if (node.level === 0 && !rootContains(node.bounds, obj)) return;\n if (node.children.length === 4) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) insertNode(child, obj, maxObjects, maxLevels);\n }\n return;\n }\n node.objects.push(obj);\n if (node.objects.length > maxObjects && node.level < maxLevels) {\n subdivide(node);\n }\n}\n\nfunction clearNode<T extends AABB>(node: Node<T>): void {\n node.objects.length = 0;\n for (const child of node.children) {\n clearNode(child);\n }\n node.children.length = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a 2D quadtree.\n *\n * @example\n * ```ts\n * import { createQuadtree, type AABB } from \"aiquadtreejs\";\n *\n * interface Body extends AABB {\n * id: number;\n * }\n *\n * const entities: Body[] = [\n * { id: 1, x: 100, y: 100, width: 32, height: 32 },\n * { id: 2, x: 400, y: 250, width: 32, height: 32 },\n * ];\n * const player: Body = { id: 0, x: 200, y: 200, width: 32, height: 32 };\n *\n * const qt = createQuadtree<Body>({\n * bounds: { x: 0, y: 0, width: 800, height: 600 },\n * maxObjects: 10,\n * maxLevels: 4,\n * });\n *\n * // Per-frame:\n * qt.clear();\n * for (const e of entities) qt.insert(e);\n *\n * // Broadphase lookup near the player:\n * const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };\n * const candidates = qt.retrieve(region);\n * // Caller runs a precise hit test on `candidates`.\n * ```\n *\n * @public\n */\nexport function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T> {\n const { bounds } = opts;\n const maxObjects = opts.maxObjects ?? 10;\n const maxLevels = opts.maxLevels ?? 4;\n\n if (\n !Number.isFinite(bounds.x) ||\n !Number.isFinite(bounds.y) ||\n !Number.isFinite(bounds.width) ||\n !Number.isFinite(bounds.height)\n ) {\n throw new QuadtreeError(\"bounds must contain finite numbers\");\n }\n if (bounds.width <= 0) {\n throw new QuadtreeError(\"bounds.width must be > 0\");\n }\n if (bounds.height <= 0) {\n throw new QuadtreeError(\"bounds.height must be > 0\");\n }\n if (!Number.isInteger(maxObjects) || maxObjects <= 0) {\n throw new QuadtreeError(\"maxObjects must be a positive integer\");\n }\n if (!Number.isInteger(maxLevels) || maxLevels <= 0) {\n throw new QuadtreeError(\"maxLevels must be a positive integer\");\n }\n\n const state: State<T> = {\n root: {\n bounds: { ...bounds },\n level: 0,\n objects: [],\n children: [],\n },\n maxObjects,\n maxLevels,\n disposed: false,\n };\n\n function ck(): void {\n if (state.disposed) throw new QuadtreeDisposedError(\"aiquadtreejs: quadtree has been disposed\");\n }\n\n function insert(obj: T): void {\n ck();\n if (\n !obj ||\n !Number.isFinite(obj.x) ||\n !Number.isFinite(obj.y) ||\n !Number.isFinite(obj.width) ||\n !Number.isFinite(obj.height)\n ) {\n throw new QuadtreeError(\n \"inserted object must be defined with finite numeric x, y, width and height\",\n );\n }\n if (obj.width < 0) {\n throw new QuadtreeError(\"inserted object width must be >= 0\");\n }\n if (obj.height < 0) {\n throw new QuadtreeError(\"inserted object height must be >= 0\");\n }\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n // Reusable scratch for retrieveSet, hoisted so steady-state queries\n // allocate nothing. Safe because the returned Set never escapes the\n // module: retrieve copies it out via Array.from and retrieveInto via a\n // push loop, both synchronously and fully before any subsequent call.\n //\n // Plain-data assumption (tightened, QDT-B-02): region.x/y/width/height\n // are read once into locals at the top of retrieveSet, then written into\n // the reusable scratchRegion (no per-query allocation — the zero-alloc\n // contract of retrieveInto holds). This prevents a structurally-typed\n // region whose getter calls back into retrieve* from corrupting the shared\n // scratch mid-walk: any re-entrant call triggered by a getter completes\n // synchronously during the four reads, before this call touches scratch.\n // Adversarial-only: plain-object callers (all documented examples) are\n // unaffected.\n const scratchSet = new Set<T>();\n const scratchStack: Node<T>[] = [];\n const scratchRegion: AABB = { x: 0, y: 0, width: 0, height: 0 };\n\n function retrieveSet(region: AABB): Set<T> {\n // Snapshot region fields into locals once so that a getter-bearing\n // region cannot mutate the walk by re-entering retrieve* mid-DFS.\n const rx = region.x;\n const ry = region.y;\n const rw = region.width;\n const rh = region.height;\n scratchRegion.x = rx;\n scratchRegion.y = ry;\n scratchRegion.width = rw;\n scratchRegion.height = rh;\n scratchSet.clear();\n scratchStack.length = 0;\n scratchStack.push(state.root);\n while (scratchStack.length > 0) {\n const node = scratchStack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, scratchRegion)) continue;\n for (const obj of node.objects) scratchSet.add(obj);\n for (const child of node.children) scratchStack.push(child);\n }\n return scratchSet;\n }\n\n /**\n * Validate a region AABB for use in retrieve / retrieveInto.\n * Mirrors insert()'s 0.5.1 validation: non-finite coordinates or negative\n * dimensions throw QuadtreeError with an `aiquadtreejs: ` prefix message.\n */\n function validateRegion(region: AABB): void {\n if (\n !region ||\n !Number.isFinite(region.x) ||\n !Number.isFinite(region.y) ||\n !Number.isFinite(region.width) ||\n !Number.isFinite(region.height)\n ) {\n throw new QuadtreeError(\n \"aiquadtreejs: retrieve region must have finite numeric x, y, width and height\",\n );\n }\n if (region.width < 0) {\n throw new QuadtreeError(\"aiquadtreejs: retrieve region width must be >= 0\");\n }\n if (region.height < 0) {\n throw new QuadtreeError(\"aiquadtreejs: retrieve region height must be >= 0\");\n }\n }\n\n function retrieve(region: AABB): T[] {\n ck();\n validateRegion(region);\n return Array.from(retrieveSet(region));\n }\n\n function retrieveInto(region: AABB, target: T[]): T[] {\n ck();\n validateRegion(region);\n const set = retrieveSet(region);\n target.length = 0;\n for (const v of set) target.push(v);\n return target;\n }\n\n function clear(): void {\n ck();\n clearNode(state.root);\n // Drain internal scratch so that a tree held alive but not queried after\n // clear() does not pin the previous query's object references against GC.\n // (dispose() drains scratch for the same reason; clear() now provides the\n // same guarantee for the per-frame rebuild pattern.)\n scratchSet.clear();\n scratchStack.length = 0;\n }\n\n function dispose(): void {\n if (state.disposed) return;\n state.disposed = true;\n state.root.objects.length = 0;\n state.root.children.length = 0;\n scratchSet.clear();\n scratchStack.length = 0;\n }\n\n return {\n insert,\n retrieve,\n retrieveInto,\n clear,\n dispose,\n get disposed() {\n return state.disposed;\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["QuadtreeError","QuadtreeDisposedError","rectsOverlap","a","b","rootContains","bounds","obj","inX","inY","quadrantIndices","node","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","state","ck","insert","scratchSet","scratchStack","scratchRegion","retrieveSet","region","rx","ry","rw","rh","validateRegion","retrieve","retrieveInto","target","set","v","clear","dispose"],"mappings":"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","file":"index.js","sourcesContent":["// aiquadtreejs — 2D quadtree for per-frame rebuild collision broadphase.\n//\n// Plain-object nodes, iterative-DFS retrieve, Set-based dedup, idempotent\n// dispose, destructurable methods (no `this`). Version: see package.json.\n\n/**\n * Axis-aligned bounding box.\n *\n * Right-open coordinate semantics: `x` / `y` are the top-left corner and\n * `x + width` / `y + height` are **exclusive**. A 32×32 box at `(0, 0)`\n * covers `[0, 32)` on both axes. (This matches the convention used by\n * renderers such as PixiJS `getBounds()`, but the type is renderer-agnostic.)\n *\n * @public\n */\nexport interface AABB {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Configuration for {@link createQuadtree}.\n *\n * @public\n */\nexport interface QuadtreeOptions {\n /**\n * Outer bounds. Objects partially outside `bounds` still insert into\n * whichever child nodes they overlap; objects fully outside are ignored\n * by `retrieve()` because no node overlaps them.\n */\n bounds: AABB;\n\n /**\n * Threshold above which a node subdivides. Default `10`. Lower values\n * mean deeper trees and fewer candidates per `retrieve()`; higher values\n * mean shallower trees and cheaper `insert()`.\n */\n maxObjects?: number;\n\n /**\n * Maximum subdivision depth. Default `4`. Caps recursion so a very dense\n * cluster doesn't blow up into an unbounded tree.\n *\n * **Spanning-object cost warning:** an object that spans multiple quadrant\n * boundaries is copied into every child node it overlaps. In the worst case\n * (an object covering the entire tree bounds) at depth `L`, up to `4^L`\n * nodes each hold a reference to that object. The default of `4` means at\n * most 256 leaf nodes; raising `maxLevels` to `10` allows ~1 M nodes, and\n * `20` allows ~10^12 — **OOM territory for dense inputs with spanning\n * objects**. Raise this value only when you understand the distribution of\n * large vs small objects in your scene. No upper-bound cap is applied\n * (the caller knows their workload); the default `4` is safe for typical\n * game scenes with 500–10,000 entities.\n */\n maxLevels?: number;\n}\n\n/**\n * Quadtree storing objects that extend {@link AABB}. `T` may carry any\n * payload (entity ID, sprite reference, user data) alongside the geometry.\n *\n * The expected usage pattern is **per-frame rebuild**: at the start of each\n * frame, call `clear()` and re-`insert()` every active object. This is\n * cheaper than tracking movements through the tree and gives correct results\n * regardless of how objects moved.\n *\n * @public\n */\nexport interface Quadtree<T extends AABB> {\n /**\n * Insert an object. The same object reference may legitimately appear\n * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`\n * deduplicates with a `Set` so the caller sees it exactly once.\n *\n * @throws {@link QuadtreeError} if any of `x`, `y`, `width`, or `height`\n * is non-finite (`NaN`, `Infinity`, `-Infinity`), or if `width` or\n * `height` is negative. Zero-extent objects (points / lines) are valid.\n */\n insert(obj: T): void;\n\n /**\n * Return every inserted object whose containing node overlaps `region`,\n * deduplicated. The result is a **broadphase**: callers must still run\n * a precise AABB or pixel-level hit test on each candidate.\n *\n * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,\n * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,\n * `-Infinity`), or if `region.width` or `region.height` is negative.\n * Zero-extent regions are valid (they still query any overlapping node).\n */\n retrieve(region: AABB): T[];\n\n /**\n * Zero-allocation variant of {@link retrieve}.\n *\n * Clears `target` (sets `target.length = 0`), walks the tree using the same\n * iterative DFS + Set-based dedup as {@link retrieve}, then writes every\n * deduplicated candidate into `target` and returns it.\n *\n * Designed for hot-path callers (per-frame broadphase queries in a game\n * loop) that hold a permanent `T[]` buffer and want to avoid allocating a\n * fresh result array on every call.\n *\n * @invariant `target` identity is preserved — only its contents are\n * replaced. `retrieveInto(r, buf) === buf` always holds.\n * @invariant After return, `target.length` equals the deduplicated\n * candidate count. No `undefined` / `null` holes.\n * @invariant Empty result set → `target.length === 0`.\n * @invariant Dedup semantics identical to {@link retrieve}: objects\n * spanning multiple quadrants appear exactly once.\n *\n * Allocation: in steady state this performs no per-call heap allocation.\n * The dedup `Set` and DFS stack are reused across calls (cleared, not\n * re-created), and results are written into the caller's `target` instead\n * of a fresh array. The first calls may grow the internal scratch; once\n * result sizes stabilise, allocation amortises to zero — the design goal\n * for per-frame broadphase loops issuing thousands of queries.\n *\n * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,\n * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,\n * `-Infinity`), or if `region.width` or `region.height` is negative.\n * Zero-extent regions are valid.\n */\n retrieveInto(region: AABB, target: T[]): T[];\n\n /**\n * Reset the tree to empty. The root node object is reused across\n * frames; child nodes are released on clear() and re-created next\n * time subdivision triggers. The per-frame churn is bounded by\n * `4 * (subdivided-internal-node-count)` and stays well inside V8's\n * young-generation budget for typical game-loop usage.\n *\n * Internal scratch buffers (dedup `Set` + DFS stack) are also drained on\n * clear(), matching the GC guarantee already provided by {@link dispose}.\n * This ensures a tree held alive but not queried after clear() does not\n * retain the previous query's object references.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops references so the GC can reclaim everything.\n * After disposal, every method except `dispose` itself — `insert`,\n * `retrieve`, `retrieveInto`, `clear` — throws {@link QuadtreeDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable quadtree error — thrown by `createQuadtree` for invalid\n * construction options and by `insert()` for precondition violations\n * (e.g. an inserted object with non-finite coordinates or negative\n * `width` / `height`).\n *\n * @public\n */\nexport class QuadtreeError extends Error {\n override readonly name = \"QuadtreeError\";\n}\n\n/**\n * Thrown by any quadtree method called after {@link Quadtree.dispose}.\n *\n * @public\n */\nexport class QuadtreeDisposedError extends Error {\n override readonly name = \"QuadtreeDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\ninterface Node<T extends AABB> {\n bounds: AABB;\n level: number;\n objects: T[];\n children: Node<T>[];\n}\n\ninterface State<T extends AABB> {\n root: Node<T>;\n maxObjects: number;\n maxLevels: number;\n disposed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction rectsOverlap(a: AABB, b: AABB): boolean {\n return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n}\n\n// Root containment check used only at the insert root gate.\n//\n// Right-open semantics for positive-extent dimensions (matching rectsOverlap):\n// contained iff obj.x < bounds.x + bounds.width AND obj.x + obj.width > bounds.x\n//\n// Zero-extent exception for the minimum edge: a zero-size point sitting exactly\n// on bounds.x or bounds.y satisfies neither side of the strict-inequality test,\n// so it would be silently dropped. Instead, per axis:\n// - zero-extent: contained iff coordinate is within [bounds.min, bounds.max) —\n// inclusive minimum, exclusive maximum (right-open, matching the box contract)\n// - positive-extent: keep the existing strict right-open overlap (unchanged)\n//\n// This matches quadrantIndices' own zero-extent fallback (obj.x >= midX etc.)\n// and preserves the invariant that a positive-size object flush on the right/bottom\n// exclusive boundary stays rejected.\nfunction rootContains(bounds: AABB, obj: AABB): boolean {\n const inX =\n obj.width === 0\n ? obj.x >= bounds.x && obj.x < bounds.x + bounds.width\n : obj.x < bounds.x + bounds.width && obj.x + obj.width > bounds.x;\n const inY =\n obj.height === 0\n ? obj.y >= bounds.y && obj.y < bounds.y + bounds.height\n : obj.y < bounds.y + bounds.height && obj.y + obj.height > bounds.y;\n return inX && inY;\n}\n\nfunction quadrantIndices<T extends AABB>(node: Node<T>, obj: AABB): number[] {\n const midX = node.bounds.x + node.bounds.width / 2;\n const midY = node.bounds.y + node.bounds.height / 2;\n // Zero-extent objects (points) sitting exactly on midX / midY would fall\n // through both `<` and `>` checks; treat the point as belonging to the\n // right/bottom side so it doesn't silently disappear.\n const inLeft = obj.x < midX;\n const inRight = obj.width === 0 ? obj.x >= midX : obj.x + obj.width > midX;\n const inTop = obj.y < midY;\n const inBottom = obj.height === 0 ? obj.y >= midY : obj.y + obj.height > midY;\n const result: number[] = [];\n if (inTop && inLeft) result.push(0);\n if (inTop && inRight) result.push(1);\n if (inBottom && inLeft) result.push(2);\n if (inBottom && inRight) result.push(3);\n return result;\n}\n\nfunction subdivide<T extends AABB>(node: Node<T>): void {\n const w = node.bounds.width / 2;\n const h = node.bounds.height / 2;\n const x = node.bounds.x;\n const y = node.bounds.y;\n const lvl = node.level + 1;\n node.children.push(\n { bounds: { x, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n );\n for (const obj of node.objects) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) child.objects.push(obj);\n }\n }\n node.objects.length = 0;\n}\n\nfunction insertNode<T extends AABB>(\n node: Node<T>,\n obj: T,\n maxObjects: number,\n maxLevels: number,\n): void {\n // Reject objects entirely outside the root bounds; for inner nodes we\n // trust `quadrantIndices` to route correctly (it has zero-extent fallback\n // logic that `rectsOverlap` does not, so the strict check is too tight\n // at child level for points sitting on a child boundary).\n // rootContains is used instead of rectsOverlap here so that zero-size\n // points/lines sitting exactly on the minimum (left/top) edge are accepted\n // with inclusive semantics, whilst positive-size objects retain right-open\n // exclusion on the maximum edge.\n if (node.level === 0 && !rootContains(node.bounds, obj)) return;\n if (node.children.length === 4) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) insertNode(child, obj, maxObjects, maxLevels);\n }\n return;\n }\n node.objects.push(obj);\n if (node.objects.length > maxObjects && node.level < maxLevels) {\n subdivide(node);\n }\n}\n\nfunction clearNode<T extends AABB>(node: Node<T>): void {\n node.objects.length = 0;\n for (const child of node.children) {\n clearNode(child);\n }\n node.children.length = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a 2D quadtree.\n *\n * @example\n * ```ts\n * import { createQuadtree, type AABB } from \"aiquadtreejs\";\n *\n * interface Body extends AABB {\n * id: number;\n * }\n *\n * const entities: Body[] = [\n * { id: 1, x: 100, y: 100, width: 32, height: 32 },\n * { id: 2, x: 400, y: 250, width: 32, height: 32 },\n * ];\n * const player: Body = { id: 0, x: 200, y: 200, width: 32, height: 32 };\n *\n * const qt = createQuadtree<Body>({\n * bounds: { x: 0, y: 0, width: 800, height: 600 },\n * maxObjects: 10,\n * maxLevels: 4,\n * });\n *\n * // Per-frame:\n * qt.clear();\n * for (const e of entities) qt.insert(e);\n *\n * // Broadphase lookup near the player:\n * const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };\n * const candidates = qt.retrieve(region);\n * // Caller runs a precise hit test on `candidates`.\n * ```\n *\n * @public\n */\nexport function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T> {\n const { bounds } = opts;\n const maxObjects = opts.maxObjects ?? 10;\n const maxLevels = opts.maxLevels ?? 4;\n\n if (\n !Number.isFinite(bounds.x) ||\n !Number.isFinite(bounds.y) ||\n !Number.isFinite(bounds.width) ||\n !Number.isFinite(bounds.height)\n ) {\n throw new QuadtreeError(\"bounds must contain finite numbers\");\n }\n if (bounds.width <= 0) {\n throw new QuadtreeError(\"bounds.width must be > 0\");\n }\n if (bounds.height <= 0) {\n throw new QuadtreeError(\"bounds.height must be > 0\");\n }\n if (!Number.isInteger(maxObjects) || maxObjects <= 0) {\n throw new QuadtreeError(\"maxObjects must be a positive integer\");\n }\n if (!Number.isInteger(maxLevels) || maxLevels <= 0) {\n throw new QuadtreeError(\"maxLevels must be a positive integer\");\n }\n\n const state: State<T> = {\n root: {\n bounds: { ...bounds },\n level: 0,\n objects: [],\n children: [],\n },\n maxObjects,\n maxLevels,\n disposed: false,\n };\n\n function ck(): void {\n if (state.disposed) throw new QuadtreeDisposedError(\"aiquadtreejs: quadtree has been disposed\");\n }\n\n function insert(obj: T): void {\n ck();\n if (\n !obj ||\n !Number.isFinite(obj.x) ||\n !Number.isFinite(obj.y) ||\n !Number.isFinite(obj.width) ||\n !Number.isFinite(obj.height)\n ) {\n throw new QuadtreeError(\n \"inserted object must be defined with finite numeric x, y, width and height\",\n );\n }\n if (obj.width < 0) {\n throw new QuadtreeError(\"inserted object width must be >= 0\");\n }\n if (obj.height < 0) {\n throw new QuadtreeError(\"inserted object height must be >= 0\");\n }\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n // Reusable scratch for retrieveSet, hoisted so steady-state queries\n // allocate nothing. Safe because the returned Set never escapes the\n // module: retrieve copies it out via Array.from and retrieveInto via a\n // push loop, both synchronously and fully before any subsequent call.\n //\n // Plain-data assumption (tightened, QDT-B-02): region.x/y/width/height\n // are read once into locals at the top of retrieveSet, then written into\n // the reusable scratchRegion (no per-query allocation — the zero-alloc\n // contract of retrieveInto holds). This prevents a structurally-typed\n // region whose getter calls back into retrieve* from corrupting the shared\n // scratch mid-walk: any re-entrant call triggered by a getter completes\n // synchronously during the four reads, before this call touches scratch.\n // Adversarial-only: plain-object callers (all documented examples) are\n // unaffected.\n const scratchSet = new Set<T>();\n const scratchStack: Node<T>[] = [];\n const scratchRegion: AABB = { x: 0, y: 0, width: 0, height: 0 };\n\n function retrieveSet(region: AABB): Set<T> {\n // Snapshot region fields into locals once so that a getter-bearing\n // region cannot mutate the walk by re-entering retrieve* mid-DFS.\n const rx = region.x;\n const ry = region.y;\n const rw = region.width;\n const rh = region.height;\n scratchRegion.x = rx;\n scratchRegion.y = ry;\n scratchRegion.width = rw;\n scratchRegion.height = rh;\n scratchSet.clear();\n scratchStack.length = 0;\n scratchStack.push(state.root);\n while (scratchStack.length > 0) {\n const node = scratchStack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, scratchRegion)) continue;\n for (const obj of node.objects) scratchSet.add(obj);\n for (const child of node.children) scratchStack.push(child);\n }\n return scratchSet;\n }\n\n /**\n * Validate a region AABB for use in retrieve / retrieveInto.\n * Non-finite coordinates or negative dimensions throw QuadtreeError with\n * an `aiquadtreejs: ` prefix message.\n */\n function validateRegion(region: AABB): void {\n if (\n !region ||\n !Number.isFinite(region.x) ||\n !Number.isFinite(region.y) ||\n !Number.isFinite(region.width) ||\n !Number.isFinite(region.height)\n ) {\n throw new QuadtreeError(\n \"aiquadtreejs: retrieve region must have finite numeric x, y, width and height\",\n );\n }\n if (region.width < 0) {\n throw new QuadtreeError(\"aiquadtreejs: retrieve region width must be >= 0\");\n }\n if (region.height < 0) {\n throw new QuadtreeError(\"aiquadtreejs: retrieve region height must be >= 0\");\n }\n }\n\n function retrieve(region: AABB): T[] {\n ck();\n validateRegion(region);\n return Array.from(retrieveSet(region));\n }\n\n function retrieveInto(region: AABB, target: T[]): T[] {\n ck();\n validateRegion(region);\n const set = retrieveSet(region);\n target.length = 0;\n for (const v of set) target.push(v);\n return target;\n }\n\n function clear(): void {\n ck();\n clearNode(state.root);\n // Drain internal scratch so that a tree held alive but not queried after\n // clear() does not pin the previous query's object references against GC.\n // (dispose() drains scratch for the same reason; clear() now provides the\n // same guarantee for the per-frame rebuild pattern.)\n scratchSet.clear();\n scratchStack.length = 0;\n }\n\n function dispose(): void {\n if (state.disposed) return;\n state.disposed = true;\n state.root.objects.length = 0;\n state.root.children.length = 0;\n scratchSet.clear();\n scratchStack.length = 0;\n }\n\n return {\n insert,\n retrieve,\n retrieveInto,\n clear,\n dispose,\n get disposed() {\n return state.disposed;\n },\n };\n}\n"]}
|
package/llms-full.txt
CHANGED
|
@@ -15,7 +15,7 @@ The short index lives at `llms.txt` (see https://llmstxt.org/).
|
|
|
15
15
|
|
|
16
16
|
Tiny 2D quadtree for per-frame rebuild collision broadphase. Insert AABBs, retrieve candidates, then run precise collision checks yourself.
|
|
17
17
|
|
|
18
|
-
> **Status: 0.5.
|
|
18
|
+
> **Status: 0.5.9 - stable 1.0-track surface.** The root entry is the public API.
|
|
19
19
|
|
|
20
20
|
## Install
|
|
21
21
|
|
|
@@ -71,7 +71,7 @@ const candidates = tree.retrieve({ x: 80, y: 80, width: 120, height: 120 });
|
|
|
71
71
|
|
|
72
72
|
## Sharp Edges
|
|
73
73
|
|
|
74
|
-
-
|
|
74
|
+
- Zero-size points (width = 0, height = 0) follow right-open `[x, x+width)` semantics: a point on the minimum `x/y` boundary is **inclusive** and is inserted/retrieved correctly; a point at the exclusive maximum edge is outside the root and is ignored. (Was a bug before 0.5.8; fixed.)
|
|
75
75
|
- Fully outside objects are ignored by retrieval.
|
|
76
76
|
- Negative width/height and non-finite coordinates throw.
|
|
77
77
|
- `retrieveInto()` clears the target array before writing results.
|
|
@@ -99,6 +99,10 @@ All notable changes to aiquadtreejs are summarized here.
|
|
|
99
99
|
|
|
100
100
|
## [Unreleased]
|
|
101
101
|
|
|
102
|
+
## [0.5.9] - 2026-06-29
|
|
103
|
+
|
|
104
|
+
- Docs: corrected the zero-size root boundary description — a point on the root min boundary is inclusive and the max boundary is exclusive (right-open `[x, x+width)`), shipped in 0.5.8; removed the stale "known bug" wording and version tokens in source comments.
|
|
105
|
+
|
|
102
106
|
## [0.5.8] - 2026-06-14
|
|
103
107
|
|
|
104
108
|
- Fixed: a zero-size point on the root left/top minimum boundary (`{ x: bounds.x, y: bounds.y, width: 0, height: 0 }`) is now inserted and retrievable. The root insert gate's right-open overlap test dropped it; a new `rootContains` check is inclusive on the minimum edge and exclusive on the maximum edge, preserving right-open `[x, x+width)` semantics. Boundary regression tests added.
|
|
@@ -140,9 +144,9 @@ All notable changes to aiquadtreejs are summarized here.
|
|
|
140
144
|
- `clear()` drains node contents and scratch references.
|
|
141
145
|
- `dispose()` is idempotent and permanent.
|
|
142
146
|
|
|
143
|
-
##
|
|
147
|
+
## Zero-Size Point Boundary Semantics
|
|
144
148
|
|
|
145
|
-
|
|
149
|
+
Zero-size points (width = 0, height = 0) follow right-open `[x, x+width)` semantics on the root boundary: a point exactly on the minimum `x/y` edge is **inclusive** and will be inserted and retrieved correctly. A point at the exclusive maximum edge (`bounds.x + bounds.width`, `bounds.y + bounds.height`) is outside the root and is ignored. This was a known bug in versions before 0.5.8; it is fixed and covered by tests as of 0.5.8.
|
|
146
150
|
|
|
147
151
|
## Out of Scope
|
|
148
152
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiquadtreejs",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "Tiny 2D quadtree for per-frame rebuild collision broadphase. Insert AABBs, retrieve candidates, clear. Caller does precise hit-testing. Designed for PixiJS games with 500–10,000 active entities.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"quadtree",
|