Skip to content

Commit cbd9efb

Browse files
committed
feat: Implement QuadTree class for spatial partitioning and entity management
1 parent e9816b4 commit cbd9efb

File tree

1 file changed

+148
-0
lines changed

1 file changed

+148
-0
lines changed

src/core/math/QuadTree.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { Rectangle } from "./Rectangle";
2+
import { ShapeEntity } from "./SpatialHash";
3+
4+
export class QuadTree {
5+
private readonly bounds: Rectangle;
6+
private readonly capacity: number;
7+
private readonly maxDepth: number;
8+
private readonly depth: number;
9+
private readonly entities: ShapeEntity[];
10+
private divided: boolean;
11+
12+
private northWest?: QuadTree;
13+
private northEast?: QuadTree;
14+
private southWest?: QuadTree;
15+
private southEast?: QuadTree;
16+
17+
constructor(bounds: Rectangle, capacity = 4, maxDepth = 8, depth = 0) {
18+
this.bounds = bounds;
19+
this.capacity = capacity;
20+
this.maxDepth = maxDepth;
21+
this.depth = depth;
22+
this.entities = [];
23+
this.divided = false;
24+
}
25+
26+
public insert(entity: ShapeEntity): boolean {
27+
// If entity doesn't intersect this quadrant, reject
28+
if (!this.bounds.intersects(entity.shape.getBounds())) {
29+
return false;
30+
}
31+
32+
// If capacity not reached and not divided, add here
33+
if (this.entities.length < this.capacity && !this.divided) {
34+
this.entities.push(entity);
35+
return true;
36+
}
37+
38+
// Subdivide if needed
39+
if (!this.divided) {
40+
this.subdivide();
41+
}
42+
43+
// Try to insert into children
44+
return (
45+
this.northWest!.insert(entity) ||
46+
this.northEast!.insert(entity) ||
47+
this.southWest!.insert(entity) ||
48+
this.southEast!.insert(entity)
49+
);
50+
}
51+
52+
private subdivide(): void {
53+
if (this.depth >= this.maxDepth) {
54+
return; // Max depth reached
55+
}
56+
57+
const x = this.bounds.getPosition().x;
58+
const y = this.bounds.getPosition().y;
59+
const w = this.bounds.getWidth() / 2;
60+
const h = this.bounds.getHeight() / 2;
61+
62+
this.northWest = new QuadTree(
63+
new Rectangle(x, y, w, h),
64+
this.capacity,
65+
this.maxDepth,
66+
this.depth + 1
67+
);
68+
this.northEast = new QuadTree(
69+
new Rectangle(x + w, y, w, h),
70+
this.capacity,
71+
this.maxDepth,
72+
this.depth + 1
73+
);
74+
this.southWest = new QuadTree(
75+
new Rectangle(x, y + h, w, h),
76+
this.capacity,
77+
this.maxDepth,
78+
this.depth + 1
79+
);
80+
this.southEast = new QuadTree(
81+
new Rectangle(x + w, y + h, w, h),
82+
this.capacity,
83+
this.maxDepth,
84+
this.depth + 1
85+
);
86+
87+
// Re-insert existing entities into children
88+
const entitiesToRedistribute = [...this.entities];
89+
this.entities.length = 0;
90+
91+
for (const entity of entitiesToRedistribute) {
92+
this.northWest.insert(entity) ||
93+
this.northEast.insert(entity) ||
94+
this.southWest.insert(entity) ||
95+
this.southEast.insert(entity);
96+
}
97+
98+
this.divided = true;
99+
}
100+
101+
public query(range: Rectangle, found: ShapeEntity[] = []): ShapeEntity[] {
102+
// No intersection, return
103+
if (!this.bounds.intersects(range)) {
104+
return found;
105+
}
106+
107+
// Check entities in this node
108+
for (const entity of this.entities) {
109+
if (range.intersects(entity.shape.getBounds())) {
110+
found.push(entity);
111+
}
112+
}
113+
114+
// Recursively check children
115+
if (this.divided) {
116+
this.northWest!.query(range, found);
117+
this.northEast!.query(range, found);
118+
this.southWest!.query(range, found);
119+
this.southEast!.query(range, found);
120+
}
121+
122+
return found;
123+
}
124+
125+
public clear(): void {
126+
this.entities.length = 0;
127+
this.divided = false;
128+
this.northWest = undefined;
129+
this.northEast = undefined;
130+
this.southWest = undefined;
131+
this.southEast = undefined;
132+
}
133+
134+
public getAllEntities(): ShapeEntity[] {
135+
let all = [...this.entities];
136+
137+
if (this.divided) {
138+
all = all.concat(
139+
this.northWest!.getAllEntities(),
140+
this.northEast!.getAllEntities(),
141+
this.southWest!.getAllEntities(),
142+
this.southEast!.getAllEntities()
143+
);
144+
}
145+
146+
return all;
147+
}
148+
}

0 commit comments

Comments
 (0)