Skip to content

Commit d1c5c32

Browse files
committed
quadtree
1 parent 988c436 commit d1c5c32

File tree

6 files changed

+396
-69
lines changed

6 files changed

+396
-69
lines changed

.github/workflows/python-app.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ jobs:
3434
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
3535
- name: Test
3636
run: |
37-
python -m unittest tests/pyscroll/test_pyscroll.py
37+
python -m unittest discover -s tests/pyscroll -p "test_*.py"

apps/scripts/quadtree_benchmark.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""
2+
FastQuadTree Depth Benchmarking Script
3+
4+
This tool evaluates how varying the depth of the FastQuadTree affects build and
5+
query performance for spatial collision detection on 2D rectangular tiles.
6+
7+
Features:
8+
- Randomly generates a set of rectangular tiles within a defined bounding box.
9+
- Builds a FastQuadTree structure using a configurable depth parameter.
10+
- Runs multiple spatial queries to check which tiles intersect a test rectangle.
11+
- Measures and prints build time and total query time across a range of depths.
12+
13+
Usage:
14+
Run the script with optional CLI arguments:
15+
--min-depth Minimum quadtree depth to test (default: 2)
16+
--max-depth Maximum quadtree depth to test (default: 6)
17+
--items Number of rectangles to generate (default: 5000)
18+
--queries Number of queries to perform per depth level (default: 1000)
19+
20+
This is useful for profiling and tuning FastQuadTree performance across different
21+
configurations.
22+
"""
23+
24+
import random
25+
import timeit
26+
from argparse import ArgumentParser
27+
28+
from pygame import Rect
29+
30+
from pyscroll.quadtree import FastQuadTree
31+
32+
33+
def generate_tile_rects(n, bounds=(0, 0, 800, 600), tile_size=(32, 32)) -> list[Rect]:
34+
x0, y0, w, h = bounds
35+
tw, th = tile_size
36+
return [
37+
Rect(random.randint(x0, x0 + w - tw), random.randint(y0, y0 + h - th), tw, th)
38+
for _ in range(n)
39+
]
40+
41+
42+
def run_benchmark(
43+
depth: int, item_count: int, query_count: int, tile_size=(32, 32)
44+
) -> dict:
45+
items = generate_tile_rects(item_count, tile_size=tile_size)
46+
47+
# Measure build time
48+
start_build = timeit.default_timer()
49+
tree = FastQuadTree(items, depth=depth)
50+
build_time = timeit.default_timer() - start_build
51+
52+
# Measure query time
53+
test_rect = Rect(400, 300, tile_size[0] * 2, tile_size[1] * 2)
54+
start_query = timeit.default_timer()
55+
for _ in range(query_count):
56+
tree.hit(test_rect)
57+
query_time = timeit.default_timer() - start_query
58+
59+
return {
60+
"depth": depth,
61+
"items": item_count,
62+
"queries": query_count,
63+
"build_time": build_time,
64+
"query_time": query_time,
65+
}
66+
67+
68+
def main():
69+
parser = ArgumentParser(description="FastQuadTree depth benchmark")
70+
parser.add_argument("--min-depth", type=int, default=2)
71+
parser.add_argument("--max-depth", type=int, default=6)
72+
parser.add_argument("--items", type=int, default=5000)
73+
parser.add_argument("--queries", type=int, default=1000)
74+
args = parser.parse_args()
75+
76+
results = []
77+
for depth in range(args.min_depth, args.max_depth + 1):
78+
result = run_benchmark(depth, args.items, args.queries)
79+
results.append(result)
80+
81+
print("\nFastQuadTree Benchmark Results")
82+
print(f"{'Depth':>5} | {'Build (s)':>10} | {'Query (s)':>10}")
83+
print("-" * 32)
84+
for r in results:
85+
print(f"{r['depth']:>5} | {r['build_time']:.6f} | {r['query_time']:.6f}")
86+
print()
87+
88+
89+
if __name__ == "__main__":
90+
main()

apps/scripts/quadtree_hitmap.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""
2+
FastQuadTree Hit Pattern Visualizer
3+
4+
This standalone script provides a visual, interactive demonstration of how
5+
FastQuadTree spatial queries work. It generates a random set of rectangles
6+
across a 2D screen, builds a FastQuadTree index, and displays:
7+
- The fixed query rectangle (blue)
8+
- Tile rectangles (gray)
9+
- Hit results from the query (red outlines)
10+
11+
Users can toggle hit visibility using the [H] key and observe spatial
12+
distribution and query precision.
13+
14+
Features:
15+
- Adjustable depth, tile size, and rectangle count via configuration variables
16+
- Visual feedback for hit detection
17+
- Overlay stats to track hit count live
18+
"""
19+
20+
import random
21+
22+
import pygame
23+
from pygame import Rect
24+
25+
from pyscroll.quadtree import FastQuadTree
26+
27+
# Configuration
28+
SCREEN_SIZE = (800, 600)
29+
TILE_SIZE = (32, 32)
30+
RECT_COUNT = 1000
31+
DEPTH = 3
32+
QUERY_RECT = Rect(400, 300, 64, 64)
33+
FPS = 30
34+
35+
36+
def generate_tile_rects(n, bounds=(0, 0, 800, 600), tile_size=(32, 32)) -> list[Rect]:
37+
x0, y0, w, h = bounds
38+
tw, th = tile_size
39+
return [
40+
Rect(random.randint(x0, x0 + w - tw), random.randint(y0, y0 + h - th), tw, th)
41+
for _ in range(n)
42+
]
43+
44+
45+
def main():
46+
pygame.init()
47+
screen = pygame.display.set_mode(SCREEN_SIZE)
48+
pygame.display.set_caption("FastQuadTree Hit Pattern Visualizer")
49+
clock = pygame.time.Clock()
50+
font = pygame.font.SysFont(None, 24)
51+
52+
# Generate tiles and build quadtree
53+
tile_rects = generate_tile_rects(
54+
RECT_COUNT, bounds=(0, 0, *SCREEN_SIZE), tile_size=TILE_SIZE
55+
)
56+
tree = FastQuadTree(tile_rects, depth=DEPTH)
57+
58+
running = True
59+
show_hits = True
60+
61+
while running:
62+
screen.fill((30, 30, 30)) # Dark background
63+
64+
for event in pygame.event.get():
65+
if event.type == pygame.QUIT:
66+
running = False
67+
elif event.type == pygame.KEYDOWN:
68+
if event.key == pygame.K_h: # Toggle hit visibility
69+
show_hits = not show_hits
70+
71+
# Draw all rectangles (gray)
72+
for rect in tile_rects:
73+
pygame.draw.rect(screen, (120, 120, 120), rect, 1)
74+
75+
# Draw query rect (blue)
76+
pygame.draw.rect(screen, (0, 128, 255), QUERY_RECT, 2)
77+
78+
# Draw hits (red)
79+
hits = tree.hit(QUERY_RECT)
80+
if show_hits:
81+
for hit_rect in hits:
82+
pygame.draw.rect(screen, (255, 0, 0), hit_rect, 2)
83+
84+
# Info overlay
85+
text = font.render(
86+
f"Hits: {len(hits)} | Press [H] to toggle hits", True, (240, 240, 240)
87+
)
88+
screen.blit(text, (10, 10))
89+
90+
pygame.display.flip()
91+
clock.tick(FPS)
92+
93+
pygame.quit()
94+
95+
96+
if __name__ == "__main__":
97+
main()
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
Performance Benchmark: FastQuadTree vs Brute-force Collision Detection
3+
4+
This script compares the speed and efficiency of two collision detection
5+
methods in a 2D space:
6+
1. FastQuadTree - a spatial partitioning structure optimized for querying
7+
rectangular areas.
8+
2. Brute-force - a simple approach that checks every rectangle for intersection.
9+
10+
Features:
11+
- Generates a random set of rectangular objects within a defined area.
12+
- Performs repeated collision queries using both methods.
13+
- Measures and prints build/setup time and query time for each method.
14+
"""
15+
16+
import random
17+
import timeit
18+
19+
from pygame import Rect
20+
21+
from pyscroll.quadtree import FastQuadTree
22+
23+
24+
def generate_rects(n, bounds=(0, 0, 800, 600), tile_size=(32, 32)) -> list[Rect]:
25+
x0, y0, w, h = bounds
26+
tw, th = tile_size
27+
return [
28+
Rect(random.randint(x0, x0 + w - tw), random.randint(y0, y0 + h - th), tw, th)
29+
for _ in range(n)
30+
]
31+
32+
33+
def brute_force_hit(items: list[Rect], target: Rect) -> list[Rect]:
34+
return [r for r in items if r.colliderect(target)]
35+
36+
37+
def benchmark(item_count=5000, query_count=1000, depth=4):
38+
print(f"\nBenchmark: {item_count} rects, {query_count} queries, depth={depth}")
39+
40+
# Generate test data
41+
items = generate_rects(item_count)
42+
test_rect = Rect(400, 300, 64, 64)
43+
44+
# Benchmark Quadtree
45+
start = timeit.default_timer()
46+
tree = FastQuadTree(items, depth=depth)
47+
build_time = timeit.default_timer() - start
48+
49+
start = timeit.default_timer()
50+
for _ in range(query_count):
51+
tree.hit(test_rect)
52+
quadtree_query_time = timeit.default_timer() - start
53+
54+
# Benchmark Brute-force
55+
start = timeit.default_timer()
56+
for _ in range(query_count):
57+
brute_force_hit(items, test_rect)
58+
brute_query_time = timeit.default_timer() - start
59+
60+
print(
61+
f"FastQuadTree:\n Build Time: {build_time:.6f}s\n Query Time: {quadtree_query_time:.6f}s"
62+
)
63+
print(
64+
f"Brute-force:\n Setup Time: negligible\n Query Time: {brute_query_time:.6f}s\n"
65+
)
66+
67+
68+
if __name__ == "__main__":
69+
benchmark()

0 commit comments

Comments
 (0)