Quadtree

A Quadtree is a tree data structure where each internal node has exactly four children. Used for spatial partitioning in 2D space, commonly in graphics for Level of Detail (LOD) and collision detection.

Structure

 1type Point struct {
 2    X, Y float64
 3}
 4
 5type Rectangle struct {
 6    X, Y, Width, Height float64
 7}
 8
 9func (r *Rectangle) Contains(p Point) bool {
10    return p.X >= r.X && p.X < r.X+r.Width &&
11           p.Y >= r.Y && p.Y < r.Y+r.Height
12}
13
14func (r *Rectangle) Intersects(other *Rectangle) bool {
15    return !(other.X > r.X+r.Width ||
16             other.X+other.Width < r.X ||
17             other.Y > r.Y+r.Height ||
18             other.Y+other.Height < r.Y)
19}
20
21type QuadTree struct {
22    boundary  *Rectangle
23    capacity  int
24    points    []Point
25    divided   bool
26    
27    // Four quadrants
28    northwest *QuadTree
29    northeast *QuadTree
30    southwest *QuadTree
31    southeast *QuadTree
32}
33
34func NewQuadTree(boundary *Rectangle, capacity int) *QuadTree {
35    return &QuadTree{
36        boundary: boundary,
37        capacity: capacity,
38        points:   make([]Point, 0, capacity),
39    }
40}

Visualization

Core Operations

1. Insert

 1func (qt *QuadTree) Insert(p Point) bool {
 2    // Point not in boundary
 3    if !qt.boundary.Contains(p) {
 4        return false
 5    }
 6    
 7    // If not at capacity, add point
 8    if len(qt.points) < qt.capacity {
 9        qt.points = append(qt.points, p)
10        return true
11    }
12    
13    // Subdivide if not already divided
14    if !qt.divided {
15        qt.subdivide()
16    }
17    
18    // Insert into appropriate quadrant
19    if qt.northwest.Insert(p) {
20        return true
21    }
22    if qt.northeast.Insert(p) {
23        return true
24    }
25    if qt.southwest.Insert(p) {
26        return true
27    }
28    if qt.southeast.Insert(p) {
29        return true
30    }
31    
32    return false
33}
34
35func (qt *QuadTree) subdivide() {
36    x := qt.boundary.X
37    y := qt.boundary.Y
38    w := qt.boundary.Width / 2
39    h := qt.boundary.Height / 2
40    
41    qt.northwest = NewQuadTree(&Rectangle{x, y, w, h}, qt.capacity)
42    qt.northeast = NewQuadTree(&Rectangle{x + w, y, w, h}, qt.capacity)
43    qt.southwest = NewQuadTree(&Rectangle{x, y + h, w, h}, qt.capacity)
44    qt.southeast = NewQuadTree(&Rectangle{x + w, y + h, w, h}, qt.capacity)
45    
46    qt.divided = true
47}

Time: $O(\log n)$ average, $O(n)$ worst case
Space: $O(n)$

 1func (qt *QuadTree) Query(searchRange *Rectangle) []Point {
 2    found := []Point{}
 3    
 4    // If range doesn't intersect boundary, return empty
 5    if !qt.boundary.Intersects(searchRange) {
 6        return found
 7    }
 8    
 9    // Check points in this node
10    for _, p := range qt.points {
11        if searchRange.Contains(p) {
12            found = append(found, p)
13        }
14    }
15    
16    // If divided, check children
17    if qt.divided {
18        found = append(found, qt.northwest.Query(searchRange)...)
19        found = append(found, qt.northeast.Query(searchRange)...)
20        found = append(found, qt.southwest.Query(searchRange)...)
21        found = append(found, qt.southeast.Query(searchRange)...)
22    }
23    
24    return found
25}
26
27// Example
28func main() {
29    qt := NewQuadTree(&Rectangle{0, 0, 100, 100}, 4)
30    
31    qt.Insert(Point{25, 25})
32    qt.Insert(Point{75, 25})
33    qt.Insert(Point{25, 75})
34    qt.Insert(Point{75, 75})
35    
36    // Query points in region
37    searchRange := &Rectangle{20, 20, 60, 60}
38    points := qt.Query(searchRange)
39    fmt.Println("Found points:", points)
40}

Time: $O(\log n + k)$ where $k$ is number of points found
Space: $O(k)$

Graphics Applications

1. Level of Detail (LOD)

 1type LODQuadTree struct {
 2    boundary *Rectangle
 3    level    int
 4    maxLevel int
 5    
 6    // Mesh data for this LOD level
 7    vertices  []Point
 8    indices   []int
 9    
10    // Children for higher detail
11    children [4]*LODQuadTree
12}
13
14func NewLODQuadTree(boundary *Rectangle, level, maxLevel int) *LODQuadTree {
15    qt := &LODQuadTree{
16        boundary: boundary,
17        level:    level,
18        maxLevel: maxLevel,
19    }
20    
21    // Generate mesh for this level
22    qt.generateMesh()
23    
24    return qt
25}
26
27func (qt *LODQuadTree) generateMesh() {
28    // Generate vertices based on level
29    // Higher level = more detail
30    detail := 1 << uint(qt.level) // 2^level
31    
32    for i := 0; i <= detail; i++ {
33        for j := 0; j <= detail; j++ {
34            x := qt.boundary.X + float64(i)*qt.boundary.Width/float64(detail)
35            y := qt.boundary.Y + float64(j)*qt.boundary.Height/float64(detail)
36            qt.vertices = append(qt.vertices, Point{x, y})
37        }
38    }
39    
40    // Generate indices for triangles
41    // ... (triangle strip or indexed triangles)
42}
43
44func (qt *LODQuadTree) GetLODForDistance(cameraPos Point, distance float64) *LODQuadTree {
45    // Calculate distance from camera to quad center
46    centerX := qt.boundary.X + qt.boundary.Width/2
47    centerY := qt.boundary.Y + qt.boundary.Height/2
48    
49    dx := cameraPos.X - centerX
50    dy := cameraPos.Y - centerY
51    dist := math.Sqrt(dx*dx + dy*dy)
52    
53    // Determine LOD level based on distance
54    if dist < distance && qt.level < qt.maxLevel {
55        // Need higher detail, subdivide
56        if qt.children[0] == nil {
57            qt.subdivide()
58        }
59        
60        // Return appropriate child
61        // ... (determine which child based on camera position)
62    }
63    
64    return qt
65}
66
67func (qt *LODQuadTree) subdivide() {
68    x := qt.boundary.X
69    y := qt.boundary.Y
70    w := qt.boundary.Width / 2
71    h := qt.boundary.Height / 2
72    
73    qt.children[0] = NewLODQuadTree(&Rectangle{x, y, w, h}, qt.level+1, qt.maxLevel)
74    qt.children[1] = NewLODQuadTree(&Rectangle{x + w, y, w, h}, qt.level+1, qt.maxLevel)
75    qt.children[2] = NewLODQuadTree(&Rectangle{x, y + h, w, h}, qt.level+1, qt.maxLevel)
76    qt.children[3] = NewLODQuadTree(&Rectangle{x + w, y + h, w, h}, qt.level+1, qt.maxLevel)
77}
78
79func (qt *LODQuadTree) Render(cameraPos Point, lodDistance float64) {
80    lod := qt.GetLODForDistance(cameraPos, lodDistance)
81    
82    // Render mesh at appropriate LOD
83    // ... (OpenGL/Vulkan rendering code)
84}

2. Frustum Culling

 1type Frustum struct {
 2    planes [6]Plane // 6 planes defining view frustum
 3}
 4
 5type Plane struct {
 6    normal Point
 7    distance float64
 8}
 9
10func (qt *LODQuadTree) FrustumCull(frustum *Frustum) []*LODQuadTree {
11    visible := []*LODQuadTree{}
12    
13    // Check if boundary intersects frustum
14    if !qt.boundary.IntersectsFrustum(frustum) {
15        return visible
16    }
17    
18    // If leaf or all children visible, add this node
19    if qt.children[0] == nil {
20        visible = append(visible, qt)
21    } else {
22        // Recursively check children
23        for _, child := range qt.children {
24            if child != nil {
25                visible = append(visible, child.FrustumCull(frustum)...)
26            }
27        }
28    }
29    
30    return visible
31}

3. Terrain Rendering

 1type TerrainQuadTree struct {
 2    boundary  *Rectangle
 3    heightmap [][]float64
 4    level     int
 5    maxLevel  int
 6    
 7    // Mesh data
 8    vertices []Vertex
 9    normals  []Point
10    
11    children [4]*TerrainQuadTree
12}
13
14type Vertex struct {
15    pos    Point
16    height float64
17}
18
19func (tqt *TerrainQuadTree) GenerateTerrainMesh() {
20    detail := 1 << uint(tqt.level)
21    
22    for i := 0; i <= detail; i++ {
23        for j := 0; j <= detail; j++ {
24            // Sample heightmap
25            x := tqt.boundary.X + float64(i)*tqt.boundary.Width/float64(detail)
26            y := tqt.boundary.Y + float64(j)*tqt.boundary.Height/float64(detail)
27            
28            height := tqt.sampleHeightmap(x, y)
29            
30            tqt.vertices = append(tqt.vertices, Vertex{
31                pos:    Point{x, y},
32                height: height,
33            })
34        }
35    }
36    
37    // Calculate normals for lighting
38    tqt.calculateNormals()
39}
40
41func (tqt *TerrainQuadTree) sampleHeightmap(x, y float64) float64 {
42    // Bilinear interpolation from heightmap
43    // ... (implementation)
44    return 0.0
45}
46
47func (tqt *TerrainQuadTree) calculateNormals() {
48    // Calculate normals from vertices for smooth shading
49    // ... (implementation)
50}

Collision Detection

 1type CollisionObject struct {
 2    pos    Point
 3    radius float64
 4}
 5
 6func (qt *QuadTree) CheckCollisions(obj *CollisionObject) []Point {
 7    // Create search range based on object's bounding box
 8    searchRange := &Rectangle{
 9        X:      obj.pos.X - obj.radius,
10        Y:      obj.pos.Y - obj.radius,
11        Width:  obj.radius * 2,
12        Height: obj.radius * 2,
13    }
14    
15    // Query potential collisions
16    candidates := qt.Query(searchRange)
17    
18    // Precise collision check
19    collisions := []Point{}
20    for _, p := range candidates {
21        dx := p.X - obj.pos.X
22        dy := p.Y - obj.pos.Y
23        dist := math.Sqrt(dx*dx + dy*dy)
24        
25        if dist < obj.radius {
26            collisions = append(collisions, p)
27        }
28    }
29    
30    return collisions
31}

Image Compression

 1type ImageQuadTree struct {
 2    boundary *Rectangle
 3    color    [3]uint8 // RGB
 4    error    float64
 5    
 6    children [4]*ImageQuadTree
 7}
 8
 9func CompressImage(img [][]color.RGBA, threshold float64) *ImageQuadTree {
10    boundary := &Rectangle{0, 0, float64(len(img[0])), float64(len(img))}
11    return compressRegion(img, boundary, threshold)
12}
13
14func compressRegion(img [][]color.RGBA, boundary *Rectangle, threshold float64) *ImageQuadTree {
15    qt := &ImageQuadTree{boundary: boundary}
16    
17    // Calculate average color
18    avgColor, colorError := calculateAverageColor(img, boundary)
19    qt.color = avgColor
20    qt.error = colorError
21    
22    // If error is below threshold, don't subdivide
23    if colorError < threshold || boundary.Width <= 1 || boundary.Height <= 1 {
24        return qt
25    }
26    
27    // Subdivide
28    x := boundary.X
29    y := boundary.Y
30    w := boundary.Width / 2
31    h := boundary.Height / 2
32    
33    qt.children[0] = compressRegion(img, &Rectangle{x, y, w, h}, threshold)
34    qt.children[1] = compressRegion(img, &Rectangle{x + w, y, w, h}, threshold)
35    qt.children[2] = compressRegion(img, &Rectangle{x, y + h, w, h}, threshold)
36    qt.children[3] = compressRegion(img, &Rectangle{x + w, y + h, w, h}, threshold)
37    
38    return qt
39}
40
41func calculateAverageColor(img [][]color.RGBA, boundary *Rectangle) ([3]uint8, float64) {
42    var sumR, sumG, sumB uint32
43    count := 0
44    
45    for y := int(boundary.Y); y < int(boundary.Y+boundary.Height); y++ {
46        for x := int(boundary.X); x < int(boundary.X+boundary.Width); x++ {
47            c := img[y][x]
48            sumR += uint32(c.R)
49            sumG += uint32(c.G)
50            sumB += uint32(c.B)
51            count++
52        }
53    }
54    
55    avgR := uint8(sumR / uint32(count))
56    avgG := uint8(sumG / uint32(count))
57    avgB := uint8(sumB / uint32(count))
58    
59    // Calculate error (variance)
60    var errorSum float64
61    for y := int(boundary.Y); y < int(boundary.Y+boundary.Height); y++ {
62        for x := int(boundary.X); x < int(boundary.X+boundary.Width); x++ {
63            c := img[y][x]
64            dr := float64(c.R) - float64(avgR)
65            dg := float64(c.G) - float64(avgG)
66            db := float64(c.B) - float64(avgB)
67            errorSum += dr*dr + dg*dg + db*db
68        }
69    }
70    
71    return [3]uint8{avgR, avgG, avgB}, errorSum / float64(count)
72}

Complexity Analysis

OperationAverageWorst Case
Insert$O(\log n)$$O(n)$
Query$O(\log n + k)$$O(n)$
Delete$O(\log n)$$O(n)$
Build$O(n \log n)$$O(n^2)$

where $k$ is number of points found

Space: $O(n)$

Octree (3D Extension)

For 3D space, use Octree (8 children instead of 4):

 1type Octree struct {
 2    boundary *Cube
 3    capacity int
 4    points   []Point3D
 5    divided  bool
 6    
 7    // Eight octants
 8    children [8]*Octree
 9}
10
11type Point3D struct {
12    X, Y, Z float64
13}
14
15type Cube struct {
16    X, Y, Z, Size float64
17}

Applications: 3D collision detection, 3D LOD, voxel engines, ray tracing

When to Use Quadtree

Use when:

  • 2D spatial queries (games, GIS)
  • Level of Detail rendering
  • Collision detection in 2D
  • Image compression
  • Adaptive mesh refinement

Don't use when:

  • 1D data (use binary search tree)
  • 3D data (use octree)
  • Uniform distribution (use grid)
  • Very dynamic data (consider loose quadtree)

Related Snippets

  • Binary Search
    Binary search is an efficient algorithm for finding a target value in a sorted …
  • Binary Tree
    A binary tree is a tree data structure where each node has at most two children, …
  • General Tree
    A tree is a hierarchical data structure consisting of nodes connected by edges, …
  • Heap Data Structure
    A heap is a specialized tree-based data structure that satisfies the heap …
  • Heap Sort
    Heap sort is a comparison-based sorting algorithm that uses a binary heap data …
  • Linked List Operations
    Optimal techniques for common linked list operations using pointer manipulation, …
  • LRU and LFU Cache
    Cache replacement policies determine which items to evict when the cache is …
  • Merge Sort
    Merge sort is a stable, comparison-based sorting algorithm that uses the …
  • Quick Sort
    Quick sort is an efficient, in-place, comparison-based sorting algorithm that …
  • String Similarity Algorithms
    Algorithms for measuring similarity between strings, used in spell checking, DNA …
  • Terrain & Heightmap Generation
    Procedural terrain generation creates realistic landscapes using mathematical …
  • Trie (Prefix Tree)
    A Trie (pronounced &quot;try&quot;) is a tree-like data structure used to store …
  • Wave Function Collapse
    Wave Function Collapse (WFC) is a procedural generation algorithm that creates …