Skip to content

Commit 4319dce

Browse files
committed
feat: quadtree simulation
1 parent d0a7cbb commit 4319dce

File tree

5 files changed

+760
-12
lines changed

5 files changed

+760
-12
lines changed

public/apps/apps.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,14 @@
870870
"icon": "PaletteIcon",
871871
"created_at": "2026-01-20T12:00:00+03:00"
872872
},
873+
{
874+
"slug": "quadtree-sim",
875+
"to": "/apps/quadtree-sim",
876+
"title": "Quadtree Simulation",
877+
"description": "Visualize spatial partitioning and search optimization with a recursive Quadtree.",
878+
"icon": "GridFourIcon",
879+
"created_at": "2026-02-21T12:00:00+03:00"
880+
},
873881
{
874882
"slug": "js-masterclass",
875883
"to": "/apps/js-masterclass",

public/logs/music/dying-light-ost.txt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
# Dying Light Original Soundtrack
2-
3-
# Dying Light Original Soundtrack
4-
51
The original soundtrack for the 2015 survival horror hit **Dying Light**, composed by the legendary Polish composer **Pawel Blaszczak**.
62

73
## About Pawel Blaszczak
84

9-
Pawel Blaszczak is a renowned Polish video game music composer who has been active in the industry since 1997. He is best known for his work with **Techland**, where he served as Sound Director for many years.
5+
Pawel Blaszczak is a renowned Polish video game music composer who has been active in the industry since 1997. He is best known for his work with **Techland**, where he served as Sound Director for many years.
106

117
His impressive portfolio includes:
128
- **Call of Juarez** series
@@ -34,4 +30,4 @@ The soundtrack is famous for its transition between the rhythmic, synth-heavy da
3430
It is truly one of the best video game OSTs out there. It doesn't just provide background noise; it defines the identity of the game. The way it fits the atmosphere is nothing short of perfect.
3531

3632

37-
Rating: 5/5
33+
Rating: 5/5

public/posts/quadtree-algorithm-spatial-indexing.txt

Lines changed: 142 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,150 @@ A robust Quadtree implementation relies on three core components:
6060
| :--- | :--- | :--- |
6161
| **1. Insert** | Add a point | Check if point is within Boundary. If not, return false. |
6262
| **2. Check Capacity** | Room available? | If points < Capacity, add to list. Return true. |
63-
| **3. Subdivide** | Split! | If points == Capacity, create 4 children. Move current points to children? (Depends on implementation). |
63+
| **3. Subdivide** | Split! | If points == Capacity, create 4 children. Move current points to children. |
6464
| **4. Query** | Find neighbors | Define a "Search Range". Only check nodes that intersect with this range. |
6565

66-
### The "Query" Optimization
67-
68-
The real magic happens during the `query`. Instead of checking all $N$ points, the Quadtree traverses the tree. If a node's boundary doesn't intersect with your search range, you prune the entire branch.
66+
## Part 4: The Lifecycle (The "Breathe" of the Tree)
67+
68+
If you watch a Quadtree simulation, you'll see the boxes constantly flickering. This is the tree "breathing" as it adapts to movement.
69+
70+
### The Subdivision-Collapse Loop
71+
72+
A Quadtree is never static. As objects move, the tree must maintain its efficiency through two opposing forces:
73+
74+
1. **The Split (Expansion)**: When a node becomes too dense (exceeding `capacity`), it must subdivide. This prevents the "local $O(N^2)$" problem within that specific box.
75+
2. **The Merge (Contraction)**: If an area becomes sparse (e.g., a grenade explodes and clears out a room), the four child quadrants should be destroyed. Their remaining points are moved back up to the parent.
76+
77+
**Why merge?** Because traversing deep branches of an empty tree is a waste of CPU cycles. We want the tree to be as shallow as possible while still keeping individual node counts low. In many real-time simulations (like the one below), we simply **rebuild the entire tree** every frame. This implicitly handles both splitting and merging, as the new tree only exists where particles currently reside.
78+
79+
## Part 5: The Go Implementation
80+
81+
I speak Go. Here is how you implement a production-ready, type-safe Quadtree in Golang:
82+
83+
```go
84+
package spatial
85+
86+
// Point represents a 2D coordinate with optional metadata
87+
type Point struct {
88+
X, Y float64
89+
Data interface{}
90+
}
91+
92+
// Rectangle defines a boundary (Center X, Y and Half-Dimensions W, H)
93+
type Rectangle struct {
94+
X, Y, W, H float64
95+
}
96+
97+
func (r Rectangle) Contains(p Point) bool {
98+
return p.X >= r.X-r.W && p.X < r.X+r.W &&
99+
p.Y >= r.Y-r.H && p.Y < r.Y+r.H
100+
}
101+
102+
func (r Rectangle) Intersects(other Rectangle) bool {
103+
return !(other.X-other.W > r.X+r.W ||
104+
other.X+other.W < r.X-r.W ||
105+
other.Y-other.H > r.Y+r.H ||
106+
other.Y+other.H < r.Y-r.H)
107+
}
108+
109+
type Quadtree struct {
110+
Boundary Rectangle
111+
Capacity int
112+
Points []Point
113+
Divided bool
114+
NW, NE, SW, SE *Quadtree
115+
}
116+
117+
func NewQuadtree(boundary Rectangle, capacity int) *Quadtree {
118+
return &Quadtree{
119+
Boundary: boundary,
120+
Capacity: capacity,
121+
Points: []Point{},
122+
}
123+
}
124+
125+
func (qt *Quadtree) Subdivide() {
126+
x, y, w, h := qt.Boundary.X, qt.Boundary.Y, qt.Boundary.W/2, qt.Boundary.H/2
127+
128+
qt.NW = NewQuadtree(Rectangle{x - w, y - h, w, h}, qt.Capacity)
129+
qt.NE = NewQuadtree(Rectangle{x + w, y - h, w, h}, qt.Capacity)
130+
qt.SW = NewQuadtree(Rectangle{x - w, y + h, w, h}, qt.Capacity)
131+
qt.SE = NewQuadtree(Rectangle{x + w, y + h, w, h}, qt.Capacity)
132+
qt.Divided = true
133+
}
134+
135+
func (qt *Quadtree) Insert(p Point) bool {
136+
if !qt.Boundary.Contains(p) {
137+
return false
138+
}
139+
140+
if len(qt.Points) < qt.Capacity {
141+
qt.Points = append(qt.Points, p)
142+
return true
143+
}
144+
145+
if !qt.Divided {
146+
qt.Subdivide()
147+
}
148+
149+
return qt.NW.Insert(p) || qt.NE.Insert(p) ||
150+
qt.SW.Insert(p) || qt.SE.Insert(p)
151+
}
152+
153+
func (qt *Quadtree) Query(rangeRect Rectangle, found []Point) []Point {
154+
if !qt.Boundary.Intersects(rangeRect) {
155+
return found
156+
}
157+
158+
for _, p := range qt.Points {
159+
if rangeRect.Contains(p) {
160+
found = append(found, p)
161+
}
162+
}
163+
164+
if qt.Divided {
165+
found = qt.NW.Query(rangeRect, found)
166+
found = qt.NE.Query(rangeRect, found)
167+
found = qt.SW.Query(rangeRect, found)
168+
found = qt.SE.Query(rangeRect, found)
169+
}
170+
171+
return found
172+
}
173+
174+
// Example Main function for local execution
175+
func main() {
176+
// 1. Define the world boundary (Center 200, 200 with 200 half-width/height)
177+
boundary := Rectangle{200, 200, 200, 200}
178+
179+
// 2. Initialize the Quadtree with a capacity of 4 points per node
180+
qt := NewQuadtree(boundary, 4)
181+
182+
// 3. Insert some random points
183+
points := []Point{
184+
{100, 100, "Point A"},
185+
{105, 110, "Point B"},
186+
{110, 105, "Point C"},
187+
{120, 120, "Point D"},
188+
{300, 300, "Point E"}, // This will be in a different quadrant
189+
}
190+
191+
for _, p := range points {
192+
qt.Insert(p)
193+
}
194+
195+
// 4. Query a specific area (Center 100, 100 with 50 half-width/height)
196+
searchRange := Rectangle{100, 100, 50, 50}
197+
found := qt.Query(searchRange, []Point{})
198+
199+
fmt.Printf("Found %d points in search range:\n", len(found))
200+
for _, p := range found {
201+
fmt.Printf("- %s at (%.2f, %.2f)\n", p.Data, p.X, p.Y)
202+
}
203+
}
204+
```
69205

70-
## Part 4: The Deductions (The Performance Verdict)
206+
## Part 6: The Deductions (The Performance Verdict)
71207

72208
By shifting from $O(N^2)$ to $O(N \log N)$ or even $O(\log N)$ for localized queries, the Quadtree transforms the impossible into the trivial.
73209

@@ -104,4 +240,4 @@ The Quadtree is a masterclass in **Spatial Hashing**. It teaches us that the bes
104240

105241
Whether you're building a 2D bullet hell, a map of the stars, or an image compression algorithm (where quadrants of the same color are merged), the Quadtree is your most reliable spatial ally.
106242

107-
You can check a visual implementation of this in my **Knowledge Graph** or any of my generative art projects that rely on particle proximity!
243+
You can check a visual implementation of this in my **[Quadtree Simulation](/apps/quadtree-sim)** app or any of my generative art projects that rely on particle proximity!

src/components/AnimatedRoutes.jsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ const JsMasterclassPage = lazy(() => import('../pages/apps/JsMasterclassPage'));
188188
const CsvFlashcardsPage = lazy(() => import('../pages/apps/CsvFlashcardsPage'));
189189
const ColorTheoryPage = lazy(() => import('../pages/apps/ColorTheoryPage'));
190190
const TierForgePage = lazy(() => import('../pages/apps/TierForgePage'));
191+
const QuadtreeSimulationPage = lazy(
192+
() => import('../pages/apps/QuadtreeSimulationPage'),
193+
);
191194
const FezGlyphPage = lazy(() => import('../pages/apps/FezGlyphPage'));
192195
const GokturkishConverterPage = lazy(
193196
() => import('../pages/apps/GokturkishConverterPage'),
@@ -1408,6 +1411,22 @@ const AnimatedRoutes = ({
14081411
</motion.div>
14091412
}
14101413
/>
1414+
<Route
1415+
path="/apps/quadtree-sim"
1416+
element={
1417+
<motion.div
1418+
initial="initial"
1419+
animate="in"
1420+
exit="out"
1421+
variants={pageVariants}
1422+
transition={pageTransition}
1423+
>
1424+
<Suspense fallback={<Loading />}>
1425+
<QuadtreeSimulationPage />
1426+
</Suspense>
1427+
</motion.div>
1428+
}
1429+
/>
14111430
<Route
14121431
path="/apps::brew"
14131432
element={<Navigate to="/apps/brew-master" replace />}

0 commit comments

Comments
 (0)