aiquadtreejs 0.4.0 → 0.5.1

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 ysl
3
+ Copyright (c) 2026 yshengliao
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -27,7 +27,7 @@ Naïve pairwise collision detection on `N` entities is `O(N²)`. At 1,000 entiti
27
27
 
28
28
  Why not just import `@timohausmann/quadtree-ts`? That library is solid and you should use it for stand-alone work. `aiquadtreejs` exists so that an ai*js stack can talk to entity IDs from `aiecsjs` without per-frame object adaptation — `insert({ id: eid, x, y, width, height })` lines up with the SoA columns you already maintain.
29
29
 
30
- > `aiquadtreejs` is one of the four 0.3-cycle siblings joining the family — alongside [aipooljs](https://github.com/yshengliao/aipooljs) (object pool), `aieventjs` (typed events; self-built, not a `mitt` fork — see the [evaluation in LEARNINGS.md](../LEARNINGS.md)), and `aiaudiojs` (Web Audio shell over a Howler.js `peerDependency`).
30
+ > `aiquadtreejs` is one of the four 0.3-cycle siblings joining the family — alongside [aipooljs](https://github.com/yshengliao/aipooljs) (object pool), `aieventjs` (typed events; self-built, not a `mitt` fork), and `aiaudiojs` (Web Audio shell over a Howler.js `peerDependency`).
31
31
 
32
32
  ---
33
33
 
package/README_ZHTW.md CHANGED
@@ -27,7 +27,7 @@
27
27
 
28
28
  那為什麼不直接用 `@timohausmann/quadtree-ts`?它做得很好,獨立場景直接用沒問題。`aiquadtreejs` 存在的理由是讓 ai*js stack 能直接接 `aiecsjs` 的 entity ID,不必每 frame 再轉一次物件 ── `insert({ id: eid, x, y, width, height })` 直接對齊你已經維護的 SoA 欄位。
29
29
 
30
- > `aiquadtreejs` 是 v0.3 cycle 四個新加入兄弟套件之一 ── 另外三個是 [aipooljs](https://github.com/yshengliao/aipooljs)(物件池)、`aieventjs`(typed event;**自寫不 fork mitt**,理由見 [LEARNINGS.md 的評估](../LEARNINGS.md))、`aiaudiojs`(Web Audio 薄殼,底層用 Howler.js 作 `peerDependency`)。
30
+ > `aiquadtreejs` 是 v0.3 cycle 四個新加入兄弟套件之一 ── 另外三個是 [aipooljs](https://github.com/yshengliao/aipooljs)(物件池)、`aieventjs`(typed event;**自寫不 fork mitt**)、`aiaudiojs`(Web Audio 薄殼,底層用 Howler.js 作 `peerDependency`)。
31
31
 
32
32
  ---
33
33
 
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var l=class extends Error{name="QuadtreeError"},b=class extends Error{name="QuadtreeDisposedError"};function m(e,t){return e.x<t.x+t.width&&e.x+e.width>t.x&&e.y<t.y+t.height&&e.y+e.height>t.y}function x(e,t){let i=e.bounds.x+e.bounds.width/2,s=e.bounds.y+e.bounds.height/2,n=t.x<i,o=t.width===0?t.x>=i:t.x+t.width>i,u=t.y<s,d=t.height===0?t.y>=s:t.y+t.height>s,r=[];return u&&n&&r.push(0),u&&o&&r.push(1),d&&n&&r.push(2),d&&o&&r.push(3),r}function y(e){let t=e.bounds.width/2,i=e.bounds.height/2,s=e.bounds.x,n=e.bounds.y,o=e.level+1;e.children.push({bounds:{x:s,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s,y:n+i,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n+i,width:t,height:i},level:o,objects:[],children:[]});for(let u of e.objects)for(let d of x(e,u)){let r=e.children[d];r!==void 0&&r.objects.push(u);}e.objects.length=0;}function A(e,t,i,s){if(!(e.level===0&&!m(e.bounds,t))){if(e.children.length===4){for(let n of x(e,t)){let o=e.children[n];o!==void 0&&A(o,t,i,s);}return}e.objects.push(t),e.objects.length>i&&e.level<s&&y(e);}}function B(e){e.objects.length=0;for(let t of e.children)B(t);e.children.length=0;}function N(e){let{bounds:t}=e,i=e.maxObjects??10,s=e.maxLevels??4;if(!Number.isFinite(t.x)||!Number.isFinite(t.y)||!Number.isFinite(t.width)||!Number.isFinite(t.height))throw new l("bounds must contain finite numbers");if(t.width<=0)throw new l("bounds.width must be > 0");if(t.height<=0)throw new l("bounds.height must be > 0");if(!Number.isInteger(i)||i<=0)throw new l("maxObjects must be a positive integer");if(!Number.isInteger(s)||s<=0)throw new l("maxLevels must be a positive integer");let n={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:i,maxLevels:s,disposed:false};function o(){if(n.disposed)throw new b("aiquadtreejs: quadtree has been disposed")}function u(h){o(),A(n.root,h,n.maxObjects,n.maxLevels);}let d=new Set,r=[];function a(h){for(d.clear(),r.length=0,r.push(n.root);r.length>0;){let c=r.pop();if(c!==void 0&&m(c.bounds,h)){for(let f of c.objects)d.add(f);for(let f of c.children)r.push(f);}}return d}function v(h){return o(),Array.from(a(h))}function p(h,c){o();let f=a(h);c.length=0;for(let w of f)c.push(w);return c}function g(){o(),B(n.root);}function T(){n.disposed||(n.disposed=true,n.root.objects.length=0,n.root.children.length=0,d.clear(),r.length=0);}return {insert:u,retrieve:v,retrieveInto:p,clear:g,dispose:T,get disposed(){return n.disposed}}}exports.QuadtreeDisposedError=b;exports.QuadtreeError=l;exports.createQuadtree=N;//# sourceMappingURL=index.cjs.map
1
+ 'use strict';var h=class extends Error{name="QuadtreeError"},b=class extends Error{name="QuadtreeDisposedError"};function m(e,t){return e.x<t.x+t.width&&e.x+e.width>t.x&&e.y<t.y+t.height&&e.y+e.height>t.y}function x(e,t){let n=e.bounds.x+e.bounds.width/2,s=e.bounds.y+e.bounds.height/2,i=t.x<n,r=t.width===0?t.x>=n:t.x+t.width>n,l=t.y<s,c=t.height===0?t.y>=s:t.y+t.height>s,o=[];return l&&i&&o.push(0),l&&r&&o.push(1),c&&i&&o.push(2),c&&r&&o.push(3),o}function y(e){let t=e.bounds.width/2,n=e.bounds.height/2,s=e.bounds.x,i=e.bounds.y,r=e.level+1;e.children.push({bounds:{x:s,y:i,width:t,height:n},level:r,objects:[],children:[]},{bounds:{x:s+t,y:i,width:t,height:n},level:r,objects:[],children:[]},{bounds:{x:s,y:i+n,width:t,height:n},level:r,objects:[],children:[]},{bounds:{x:s+t,y:i+n,width:t,height:n},level:r,objects:[],children:[]});for(let l of e.objects)for(let c of x(e,l)){let o=e.children[c];o!==void 0&&o.objects.push(l);}e.objects.length=0;}function w(e,t,n,s){if(!(e.level===0&&!m(e.bounds,t))){if(e.children.length===4){for(let i of x(e,t)){let r=e.children[i];r!==void 0&&w(r,t,n,s);}return}e.objects.push(t),e.objects.length>n&&e.level<s&&y(e);}}function A(e){e.objects.length=0;for(let t of e.children)A(t);e.children.length=0;}function N(e){let{bounds:t}=e,n=e.maxObjects??10,s=e.maxLevels??4;if(!Number.isFinite(t.x)||!Number.isFinite(t.y)||!Number.isFinite(t.width)||!Number.isFinite(t.height))throw new h("bounds must contain finite numbers");if(t.width<=0)throw new h("bounds.width must be > 0");if(t.height<=0)throw new h("bounds.height must be > 0");if(!Number.isInteger(n)||n<=0)throw new h("maxObjects must be a positive integer");if(!Number.isInteger(s)||s<=0)throw new h("maxLevels must be a positive integer");let i={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:n,maxLevels:s,disposed:false};function r(){if(i.disposed)throw new b("aiquadtreejs: quadtree has been disposed")}function l(d){if(r(),!d||!Number.isFinite(d.x)||!Number.isFinite(d.y)||!Number.isFinite(d.width)||!Number.isFinite(d.height))throw new h("inserted object must be defined with finite numeric x, y, width and height");if(d.width<0)throw new h("inserted object width must be >= 0");if(d.height<0)throw new h("inserted object height must be >= 0");w(i.root,d,i.maxObjects,i.maxLevels);}let c=new Set,o=[];function a(d){for(c.clear(),o.length=0,o.push(i.root);o.length>0;){let u=o.pop();if(u!==void 0&&m(u.bounds,d)){for(let f of u.objects)c.add(f);for(let f of u.children)o.push(f);}}return c}function B(d){return r(),Array.from(a(d))}function v(d,u){r();let f=a(d);u.length=0;for(let T of f)u.push(T);return u}function g(){r(),A(i.root);}function p(){i.disposed||(i.disposed=true,i.root.objects.length=0,i.root.children.length=0,c.clear(),o.length=0);}return {insert:l,retrieve:B,retrieveInto:v,clear:g,dispose:p,get disposed(){return i.disposed}}}exports.QuadtreeDisposedError=b;exports.QuadtreeError=h;exports.createQuadtree=N;//# sourceMappingURL=index.cjs.map
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["QuadtreeError","QuadtreeDisposedError","rectsOverlap","a","b","quadrantIndices","node","obj","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","bounds","state","ck","insert","scratchSet","scratchStack","retrieveSet","region","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// v0.1.0: 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 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 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 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 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 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 — currently unused at the public surface, but\n * reserved for future precondition violations (e.g. an inserted object with\n * `NaN` coordinates or negative `width`).\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\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 if (node.level === 0 && !rectsOverlap(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 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 const scratchSet = new Set<T>();\n const scratchStack: Node<T>[] = [];\n\n function retrieveSet(region: AABB): Set<T> {\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, region)) 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 function retrieve(region: AABB): T[] {\n ck();\n return Array.from(retrieveSet(region));\n }\n\n function retrieveInto(region: AABB, target: T[]): T[] {\n ck();\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 }\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","quadrantIndices","node","obj","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","bounds","state","ck","insert","scratchSet","scratchStack","retrieveSet","region","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// v0.1.0: 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 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 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 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 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\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 if (node.level === 0 && !rectsOverlap(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 const scratchSet = new Set<T>();\n const scratchStack: Node<T>[] = [];\n\n function retrieveSet(region: AABB): Set<T> {\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, region)) 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 function retrieve(region: AABB): T[] {\n ck();\n return Array.from(retrieveSet(region));\n }\n\n function retrieveInto(region: AABB, target: T[]): T[] {\n ck();\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 }\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.d.cts CHANGED
@@ -54,6 +54,10 @@ interface Quadtree<T extends AABB> {
54
54
  * Insert an object. The same object reference may legitimately appear
55
55
  * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`
56
56
  * deduplicates with a `Set` so the caller sees it exactly once.
57
+ *
58
+ * @throws {@link QuadtreeError} if any of `x`, `y`, `width`, or `height`
59
+ * is non-finite (`NaN`, `Infinity`, `-Infinity`), or if `width` or
60
+ * `height` is negative. Zero-extent objects (points / lines) are valid.
57
61
  */
58
62
  insert(obj: T): void;
59
63
  /**
@@ -107,9 +111,10 @@ interface Quadtree<T extends AABB> {
107
111
  readonly disposed: boolean;
108
112
  }
109
113
  /**
110
- * Recoverable quadtree error — currently unused at the public surface, but
111
- * reserved for future precondition violations (e.g. an inserted object with
112
- * `NaN` coordinates or negative `width`).
114
+ * Recoverable quadtree error — thrown by `createQuadtree` for invalid
115
+ * construction options and by `insert()` for precondition violations
116
+ * (e.g. an inserted object with non-finite coordinates or negative
117
+ * `width` / `height`).
113
118
  *
114
119
  * @public
115
120
  */
package/dist/index.d.ts CHANGED
@@ -54,6 +54,10 @@ interface Quadtree<T extends AABB> {
54
54
  * Insert an object. The same object reference may legitimately appear
55
55
  * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`
56
56
  * deduplicates with a `Set` so the caller sees it exactly once.
57
+ *
58
+ * @throws {@link QuadtreeError} if any of `x`, `y`, `width`, or `height`
59
+ * is non-finite (`NaN`, `Infinity`, `-Infinity`), or if `width` or
60
+ * `height` is negative. Zero-extent objects (points / lines) are valid.
57
61
  */
58
62
  insert(obj: T): void;
59
63
  /**
@@ -107,9 +111,10 @@ interface Quadtree<T extends AABB> {
107
111
  readonly disposed: boolean;
108
112
  }
109
113
  /**
110
- * Recoverable quadtree error — currently unused at the public surface, but
111
- * reserved for future precondition violations (e.g. an inserted object with
112
- * `NaN` coordinates or negative `width`).
114
+ * Recoverable quadtree error — thrown by `createQuadtree` for invalid
115
+ * construction options and by `insert()` for precondition violations
116
+ * (e.g. an inserted object with non-finite coordinates or negative
117
+ * `width` / `height`).
113
118
  *
114
119
  * @public
115
120
  */
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var l=class extends Error{name="QuadtreeError"},b=class extends Error{name="QuadtreeDisposedError"};function m(e,t){return e.x<t.x+t.width&&e.x+e.width>t.x&&e.y<t.y+t.height&&e.y+e.height>t.y}function x(e,t){let i=e.bounds.x+e.bounds.width/2,s=e.bounds.y+e.bounds.height/2,n=t.x<i,o=t.width===0?t.x>=i:t.x+t.width>i,u=t.y<s,d=t.height===0?t.y>=s:t.y+t.height>s,r=[];return u&&n&&r.push(0),u&&o&&r.push(1),d&&n&&r.push(2),d&&o&&r.push(3),r}function y(e){let t=e.bounds.width/2,i=e.bounds.height/2,s=e.bounds.x,n=e.bounds.y,o=e.level+1;e.children.push({bounds:{x:s,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s,y:n+i,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n+i,width:t,height:i},level:o,objects:[],children:[]});for(let u of e.objects)for(let d of x(e,u)){let r=e.children[d];r!==void 0&&r.objects.push(u);}e.objects.length=0;}function A(e,t,i,s){if(!(e.level===0&&!m(e.bounds,t))){if(e.children.length===4){for(let n of x(e,t)){let o=e.children[n];o!==void 0&&A(o,t,i,s);}return}e.objects.push(t),e.objects.length>i&&e.level<s&&y(e);}}function B(e){e.objects.length=0;for(let t of e.children)B(t);e.children.length=0;}function N(e){let{bounds:t}=e,i=e.maxObjects??10,s=e.maxLevels??4;if(!Number.isFinite(t.x)||!Number.isFinite(t.y)||!Number.isFinite(t.width)||!Number.isFinite(t.height))throw new l("bounds must contain finite numbers");if(t.width<=0)throw new l("bounds.width must be > 0");if(t.height<=0)throw new l("bounds.height must be > 0");if(!Number.isInteger(i)||i<=0)throw new l("maxObjects must be a positive integer");if(!Number.isInteger(s)||s<=0)throw new l("maxLevels must be a positive integer");let n={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:i,maxLevels:s,disposed:false};function o(){if(n.disposed)throw new b("aiquadtreejs: quadtree has been disposed")}function u(h){o(),A(n.root,h,n.maxObjects,n.maxLevels);}let d=new Set,r=[];function a(h){for(d.clear(),r.length=0,r.push(n.root);r.length>0;){let c=r.pop();if(c!==void 0&&m(c.bounds,h)){for(let f of c.objects)d.add(f);for(let f of c.children)r.push(f);}}return d}function v(h){return o(),Array.from(a(h))}function p(h,c){o();let f=a(h);c.length=0;for(let w of f)c.push(w);return c}function g(){o(),B(n.root);}function T(){n.disposed||(n.disposed=true,n.root.objects.length=0,n.root.children.length=0,d.clear(),r.length=0);}return {insert:u,retrieve:v,retrieveInto:p,clear:g,dispose:T,get disposed(){return n.disposed}}}export{b as QuadtreeDisposedError,l as QuadtreeError,N as createQuadtree};//# sourceMappingURL=index.js.map
1
+ var h=class extends Error{name="QuadtreeError"},b=class extends Error{name="QuadtreeDisposedError"};function m(e,t){return e.x<t.x+t.width&&e.x+e.width>t.x&&e.y<t.y+t.height&&e.y+e.height>t.y}function x(e,t){let n=e.bounds.x+e.bounds.width/2,s=e.bounds.y+e.bounds.height/2,i=t.x<n,r=t.width===0?t.x>=n:t.x+t.width>n,l=t.y<s,c=t.height===0?t.y>=s:t.y+t.height>s,o=[];return l&&i&&o.push(0),l&&r&&o.push(1),c&&i&&o.push(2),c&&r&&o.push(3),o}function y(e){let t=e.bounds.width/2,n=e.bounds.height/2,s=e.bounds.x,i=e.bounds.y,r=e.level+1;e.children.push({bounds:{x:s,y:i,width:t,height:n},level:r,objects:[],children:[]},{bounds:{x:s+t,y:i,width:t,height:n},level:r,objects:[],children:[]},{bounds:{x:s,y:i+n,width:t,height:n},level:r,objects:[],children:[]},{bounds:{x:s+t,y:i+n,width:t,height:n},level:r,objects:[],children:[]});for(let l of e.objects)for(let c of x(e,l)){let o=e.children[c];o!==void 0&&o.objects.push(l);}e.objects.length=0;}function w(e,t,n,s){if(!(e.level===0&&!m(e.bounds,t))){if(e.children.length===4){for(let i of x(e,t)){let r=e.children[i];r!==void 0&&w(r,t,n,s);}return}e.objects.push(t),e.objects.length>n&&e.level<s&&y(e);}}function A(e){e.objects.length=0;for(let t of e.children)A(t);e.children.length=0;}function N(e){let{bounds:t}=e,n=e.maxObjects??10,s=e.maxLevels??4;if(!Number.isFinite(t.x)||!Number.isFinite(t.y)||!Number.isFinite(t.width)||!Number.isFinite(t.height))throw new h("bounds must contain finite numbers");if(t.width<=0)throw new h("bounds.width must be > 0");if(t.height<=0)throw new h("bounds.height must be > 0");if(!Number.isInteger(n)||n<=0)throw new h("maxObjects must be a positive integer");if(!Number.isInteger(s)||s<=0)throw new h("maxLevels must be a positive integer");let i={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:n,maxLevels:s,disposed:false};function r(){if(i.disposed)throw new b("aiquadtreejs: quadtree has been disposed")}function l(d){if(r(),!d||!Number.isFinite(d.x)||!Number.isFinite(d.y)||!Number.isFinite(d.width)||!Number.isFinite(d.height))throw new h("inserted object must be defined with finite numeric x, y, width and height");if(d.width<0)throw new h("inserted object width must be >= 0");if(d.height<0)throw new h("inserted object height must be >= 0");w(i.root,d,i.maxObjects,i.maxLevels);}let c=new Set,o=[];function a(d){for(c.clear(),o.length=0,o.push(i.root);o.length>0;){let u=o.pop();if(u!==void 0&&m(u.bounds,d)){for(let f of u.objects)c.add(f);for(let f of u.children)o.push(f);}}return c}function B(d){return r(),Array.from(a(d))}function v(d,u){r();let f=a(d);u.length=0;for(let T of f)u.push(T);return u}function g(){r(),A(i.root);}function p(){i.disposed||(i.disposed=true,i.root.objects.length=0,i.root.children.length=0,c.clear(),o.length=0);}return {insert:l,retrieve:B,retrieveInto:v,clear:g,dispose:p,get disposed(){return i.disposed}}}export{b as QuadtreeDisposedError,h as QuadtreeError,N as createQuadtree};//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["QuadtreeError","QuadtreeDisposedError","rectsOverlap","a","b","quadrantIndices","node","obj","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","bounds","state","ck","insert","scratchSet","scratchStack","retrieveSet","region","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.1.0: 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 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 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 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 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 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 — currently unused at the public surface, but\n * reserved for future precondition violations (e.g. an inserted object with\n * `NaN` coordinates or negative `width`).\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\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 if (node.level === 0 && !rectsOverlap(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 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 const scratchSet = new Set<T>();\n const scratchStack: Node<T>[] = [];\n\n function retrieveSet(region: AABB): Set<T> {\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, region)) 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 function retrieve(region: AABB): T[] {\n ck();\n return Array.from(retrieveSet(region));\n }\n\n function retrieveInto(region: AABB, target: T[]): T[] {\n ck();\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 }\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","quadrantIndices","node","obj","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","bounds","state","ck","insert","scratchSet","scratchStack","retrieveSet","region","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.1.0: 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 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 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 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 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\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 if (node.level === 0 && !rectsOverlap(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 const scratchSet = new Set<T>();\n const scratchStack: Node<T>[] = [];\n\n function retrieveSet(region: AABB): Set<T> {\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, region)) 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 function retrieve(region: AABB): T[] {\n ck();\n return Array.from(retrieveSet(region));\n }\n\n function retrieveInto(region: AABB, target: T[]): T[] {\n ck();\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 }\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
@@ -40,7 +40,7 @@ Naïve pairwise collision detection on `N` entities is `O(N²)`. At 1,000 entiti
40
40
 
41
41
  Why not just import `@timohausmann/quadtree-ts`? That library is solid and you should use it for stand-alone work. `aiquadtreejs` exists so that an ai*js stack can talk to entity IDs from `aiecsjs` without per-frame object adaptation — `insert({ id: eid, x, y, width, height })` lines up with the SoA columns you already maintain.
42
42
 
43
- > `aiquadtreejs` is one of the four 0.3-cycle siblings joining the family — alongside [aipooljs](https://github.com/yshengliao/aipooljs) (object pool), `aieventjs` (typed events; self-built, not a `mitt` fork — see the [evaluation in LEARNINGS.md](../LEARNINGS.md)), and `aiaudiojs` (Web Audio shell over a Howler.js `peerDependency`).
43
+ > `aiquadtreejs` is one of the four 0.3-cycle siblings joining the family — alongside [aipooljs](https://github.com/yshengliao/aipooljs) (object pool), `aieventjs` (typed events; self-built, not a `mitt` fork), and `aiaudiojs` (Web Audio shell over a Howler.js `peerDependency`).
44
44
 
45
45
  ---
46
46
 
@@ -156,6 +156,35 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
156
156
 
157
157
  ## [Unreleased]
158
158
 
159
+ ## [0.5.1] - 2026-06-02
160
+
161
+ ### Fixed
162
+
163
+ - **`insert()` input validation** — `insert()` now throws `QuadtreeError`
164
+ when the inserted object contains a non-finite coordinate (`NaN`,
165
+ `Infinity`, `-Infinity` in any of `x`, `y`, `width`, `height`) or a
166
+ negative dimension (`width < 0` or `height < 0`). Previously, such
167
+ objects were silently mishandled (kept before subdivide, silently dropped
168
+ after). Zero-extent objects (points / lines with `width=0` and/or
169
+ `height=0`) remain valid and are not rejected. The `QuadtreeError`
170
+ JSDoc already noted this use was reserved; the behaviour is now
171
+ implemented and documented in `STABILITY.md`.
172
+
173
+ ### Tests
174
+
175
+ - Added J-group (J1–J15): `insert()` throws `QuadtreeError` for
176
+ `width<0`, `height<0`, `NaN`/`Infinity` in any of `x/y/width/height`, and
177
+ for `null`/`undefined` objects (J14/J15); valid zero-extent objects
178
+ (width=0, height=0, mixed) are accepted.
179
+ - Added K-group (K1–K7): oversized object appears exactly once in
180
+ full-region and per-quadrant sub-queries after subdivide; `retrieveInto`
181
+ buffer identity preserved across 60 clear+insert+retrieveInto frames;
182
+ negative-origin bounds with negative-coord objects and queries; zero-
183
+ extent point at a non-midpoint survives deep subdivision (maxLevels=4)
184
+ and is retrievable with a tight region; maxLevels=1 with many same-
185
+ quadrant objects — all retrievable, zero duplicates; non-origin bounds
186
+ insert+retrieve correctness.
187
+
159
188
  ## [0.4.0] - 2026-05-29
160
189
 
161
190
  Dependency hygiene + stability freeze. **No runtime API addition or behaviour
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiquadtreejs",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
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",
@@ -14,7 +14,7 @@
14
14
  "typescript",
15
15
  "esm"
16
16
  ],
17
- "author": "ysl <ysl@sheng.page>",
17
+ "author": "yshengliao",
18
18
  "license": "MIT",
19
19
  "homepage": "https://github.com/yshengliao/aiquadtreejs#readme",
20
20
  "repository": {