Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions apps/scripts/quadtree_benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
FastQuadTree Depth Benchmarking Script

This tool evaluates how varying the depth of the FastQuadTree affects build and
query performance for spatial collision detection on 2D rectangular tiles.

Features:
- Randomly generates a set of rectangular tiles within a defined bounding box.
- Builds a FastQuadTree structure using a configurable depth parameter.
- Runs multiple spatial queries to check which tiles intersect a test rectangle.
- Measures and prints build time and total query time across a range of depths.

Usage:
Run the script with optional CLI arguments:
--min-depth Minimum quadtree depth to test (default: 2)
--max-depth Maximum quadtree depth to test (default: 6)
--items Number of rectangles to generate (default: 5000)
--queries Number of queries to perform per depth level (default: 1000)

This is useful for profiling and tuning FastQuadTree performance across different
configurations.
"""

import random
import timeit
from argparse import ArgumentParser

from pygame.rect import Rect

from pyscroll.quadtree import FastQuadTree


def generate_tile_rects(n, bounds=(0, 0, 800, 600), tile_size=(32, 32)) -> list[Rect]:
x0, y0, w, h = bounds
tw, th = tile_size
return [
Rect(random.randint(x0, x0 + w - tw), random.randint(y0, y0 + h - th), tw, th)
for _ in range(n)
]


def run_benchmark(
depth: int, item_count: int, query_count: int, tile_size=(32, 32)
) -> dict:
items = generate_tile_rects(item_count, tile_size=tile_size)

# Measure build time
start_build = timeit.default_timer()
tree = FastQuadTree(items, depth=depth)
build_time = timeit.default_timer() - start_build

# Measure query time
test_rect = Rect(400, 300, tile_size[0] * 2, tile_size[1] * 2)
start_query = timeit.default_timer()
for _ in range(query_count):
tree.hit(test_rect)
query_time = timeit.default_timer() - start_query

return {
"depth": depth,
"items": item_count,
"queries": query_count,
"build_time": build_time,
"query_time": query_time,
}


def main():
parser = ArgumentParser(description="FastQuadTree depth benchmark")
parser.add_argument("--min-depth", type=int, default=2)
parser.add_argument("--max-depth", type=int, default=6)
parser.add_argument("--items", type=int, default=5000)
parser.add_argument("--queries", type=int, default=1000)
args = parser.parse_args()

results = []
for depth in range(args.min_depth, args.max_depth + 1):
result = run_benchmark(depth, args.items, args.queries)
results.append(result)

print("\nBenchmark Configuration:")
print(f" Items : {args.items}")
print(f" Queries : {args.queries}")
print(f" Min Depth : {args.min_depth}")
print(f" Max Depth : {args.max_depth}")

print("\nFastQuadTree Benchmark Results")
print(f"{'Depth':>5} | {'Build (s)':>10} | {'Query (s)':>10}")
print("-" * 32)
for r in results:
print(f"{r['depth']:>5} | {r['build_time']:.6f} | {r['query_time']:.6f}")
print()


if __name__ == "__main__":
main()
97 changes: 97 additions & 0 deletions apps/scripts/quadtree_hitmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""
FastQuadTree Hit Pattern Visualizer

This standalone script provides a visual, interactive demonstration of how
FastQuadTree spatial queries work. It generates a random set of rectangles
across a 2D screen, builds a FastQuadTree index, and displays:
- The fixed query rectangle (blue)
- Tile rectangles (gray)
- Hit results from the query (red outlines)

Users can toggle hit visibility using the [H] key and observe spatial
distribution and query precision.

Features:
- Adjustable depth, tile size, and rectangle count via configuration variables
- Visual feedback for hit detection
- Overlay stats to track hit count live
"""

import random

import pygame
from pygame.rect import Rect

from pyscroll.quadtree import FastQuadTree

# Configuration
SCREEN_SIZE = (800, 600)
TILE_SIZE = (32, 32)
RECT_COUNT = 1000
DEPTH = 3
QUERY_RECT = Rect(400, 300, 64, 64)
FPS = 30


def generate_tile_rects(n, bounds=(0, 0, 800, 600), tile_size=(32, 32)) -> list[Rect]:
x0, y0, w, h = bounds
tw, th = tile_size
return [
Rect(random.randint(x0, x0 + w - tw), random.randint(y0, y0 + h - th), tw, th)
for _ in range(n)
]


def main():
pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)
pygame.display.set_caption("FastQuadTree Hit Pattern Visualizer")
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 24)

# Generate tiles and build quadtree
tile_rects = generate_tile_rects(
RECT_COUNT, bounds=(0, 0, *SCREEN_SIZE), tile_size=TILE_SIZE
)
tree = FastQuadTree(tile_rects, depth=DEPTH)

running = True
show_hits = True

while running:
screen.fill((30, 30, 30)) # Dark background

for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_h: # Toggle hit visibility
show_hits = not show_hits

# Draw all rectangles (gray)
for rect in tile_rects:
pygame.draw.rect(screen, (120, 120, 120), rect, 1)

# Draw query rect (blue)
pygame.draw.rect(screen, (0, 128, 255), QUERY_RECT, 2)

# Draw hits (red)
hits = tree.hit(QUERY_RECT)
if show_hits:
for hit_rect in hits:
pygame.draw.rect(screen, (255, 0, 0), hit_rect, 2)

# Info overlay
text = font.render(
f"Hits: {len(hits)} | Press [H] to toggle hits", True, (240, 240, 240)
)
screen.blit(text, (10, 10))

pygame.display.flip()
clock.tick(FPS)

pygame.quit()


if __name__ == "__main__":
main()
69 changes: 69 additions & 0 deletions apps/scripts/quadtree_vs_bruteforce.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
Performance Benchmark: FastQuadTree vs Brute-force Collision Detection

This script compares the speed and efficiency of two collision detection
methods in a 2D space:
1. FastQuadTree - a spatial partitioning structure optimized for querying
rectangular areas.
2. Brute-force - a simple approach that checks every rectangle for intersection.

Features:
- Generates a random set of rectangular objects within a defined area.
- Performs repeated collision queries using both methods.
- Measures and prints build/setup time and query time for each method.
"""

import random
import timeit

from pygame.rect import Rect

from pyscroll.quadtree import FastQuadTree


def generate_rects(n, bounds=(0, 0, 800, 600), tile_size=(32, 32)) -> list[Rect]:
x0, y0, w, h = bounds
tw, th = tile_size
return [
Rect(random.randint(x0, x0 + w - tw), random.randint(y0, y0 + h - th), tw, th)
for _ in range(n)
]


def brute_force_hit(items: list[Rect], target: Rect) -> list[Rect]:
return [r for r in items if r.colliderect(target)]


def benchmark(item_count=5000, query_count=1000, depth=4):
print(f"\nBenchmark: {item_count} rects, {query_count} queries, depth={depth}")

# Generate test data
items = generate_rects(item_count)
test_rect = Rect(400, 300, 64, 64)

# Benchmark Quadtree
start = timeit.default_timer()
tree = FastQuadTree(items, depth=depth)
build_time = timeit.default_timer() - start

start = timeit.default_timer()
for _ in range(query_count):
tree.hit(test_rect)
quadtree_query_time = timeit.default_timer() - start

# Benchmark Brute-force
start = timeit.default_timer()
for _ in range(query_count):
brute_force_hit(items, test_rect)
brute_query_time = timeit.default_timer() - start

print(
f"FastQuadTree:\n Build Time: {build_time:.6f}s\n Query Time: {quadtree_query_time:.6f}s"
)
print(
f"Brute-force:\n Setup Time: negligible\n Query Time: {brute_query_time:.6f}s\n"
)


if __name__ == "__main__":
benchmark()
Loading