@@ -39,7 +39,7 @@ impl TileQuadtree {
3939 return TileQuadtree :: new_empty ( level) . unwrap ( ) ;
4040 } ;
4141
42- let root = Node :: build_node ( level , ( 0 , 0 ) , 1u64 << level, & bbox) ;
42+ let root = Node :: build_node ( & BBox :: root ( level) , & bbox) ;
4343 TileQuadtree { level, root }
4444 }
4545
@@ -73,8 +73,7 @@ impl TileQuadtree {
7373 let mut codes: Vec < u64 > = tiles. iter ( ) . map ( |& ( x, y) | morton ( u64:: from ( x) , u64:: from ( y) ) ) . collect ( ) ;
7474 codes. sort_unstable ( ) ;
7575 codes. dedup ( ) ;
76- let size = 1u64 << level;
77- let root = Node :: from_morton_sorted ( & codes, 0 , 0 , size) ;
76+ let root = Node :: from_morton_sorted ( & codes, & BBox :: root ( level) ) ;
7877 Ok ( TileQuadtree { level, root } )
7978 }
8079
@@ -91,59 +90,44 @@ impl TileQuadtree {
9190}
9291
9392impl Node {
94- /// Recursively build a quadtree node for the cell covering
95- /// `[x_off, x_off+size) × [y_off, y_off+size)` against the bbox
96- /// `[bbox.x_min, bbox.x_max) × [bbox.y_min, bbox.y_max)` (all exclusive on max side).
97- pub fn build_node ( depth : u8 , ( x_off, y_off) : ( u64 , u64 ) , size : u64 , bbox : & BBox ) -> Node {
98- // Intersection of bbox with this cell
99- let ix_min = bbox. x_min . max ( x_off) ;
100- let iy_min = bbox. y_min . max ( y_off) ;
101- let ix_max = bbox. x_max . min ( x_off + size) ;
102- let iy_max = bbox. y_max . min ( y_off + size) ;
103-
104- if ix_min >= ix_max || iy_min >= iy_max {
93+ /// Recursively build a quadtree node for `cell` against `bbox` (both using
94+ /// exclusive max coordinates).
95+ pub fn build_node ( cell : & BBox , bbox : & BBox ) -> Node {
96+ let Some ( clip) = cell. intersection ( bbox) else {
10597 return Node :: Empty ;
106- }
107-
108- if ix_min == x_off && iy_min == y_off && ix_max == x_off + size && iy_max == y_off + size {
98+ } ;
99+ if clip. covers ( cell) {
109100 return Node :: Full ;
110101 }
111-
112- if depth == 0 {
113- // We've reached leaf level — any intersection means Full
102+ // At size == 1, overlap implies coverage for tile-aligned bboxes,
103+ // so this branch is defensive — it should never fire in practice.
104+ if cell . size ( ) == 1 {
114105 return Node :: Full ;
115106 }
116-
117- let half = size / 2 ;
118- let mid_x = x_off + half;
119- let mid_y = y_off + half;
120- Node :: new_partial ( [
121- Node :: build_node ( depth - 1 , ( x_off, y_off) , half, bbox) ,
122- Node :: build_node ( depth - 1 , ( mid_x, y_off) , half, bbox) ,
123- Node :: build_node ( depth - 1 , ( x_off, mid_y) , half, bbox) ,
124- Node :: build_node ( depth - 1 , ( mid_x, mid_y) , half, bbox) ,
125- ] )
107+ let quads = cell. quadrants ( ) ;
108+ Node :: new_partial ( std:: array:: from_fn ( |i| Node :: build_node ( & quads[ i] , bbox) ) )
126109 }
127110
128111 /// Build a `Node` from a sorted, deduplicated slice of Morton codes.
129112 ///
130- /// The slice must only contain codes for tiles within the cell
131- /// `[x_off, x_off+size) × [y_off, y_off+size)` and be sorted ascending.
132- /// Each quadrant split uses `partition_point` with a trivial `u64 <`
133- /// comparison — no Morton recomputation per tile.
134- pub ( super ) fn from_morton_sorted ( codes : & [ u64 ] , x_off : u64 , y_off : u64 , size : u64 ) -> Node {
113+ /// The slice must only contain codes for tiles within `cell` and be sorted
114+ /// ascending. Each quadrant split uses `partition_point` with a trivial
115+ /// `u64 <` comparison — no Morton recomputation per tile.
116+ pub ( super ) fn from_morton_sorted ( codes : & [ u64 ] , cell : & BBox ) -> Node {
135117 if codes. is_empty ( ) {
136118 return Node :: Empty ;
137119 }
120+ let size = cell. size ( ) ;
138121 if codes. len ( ) as u64 == size * size {
139122 return Node :: Full ;
140123 }
141124 if size == 1 {
142125 return Node :: Full ;
143126 }
144- let half = size / 2 ;
145- let mid_x = x_off + half;
146- let mid_y = y_off + half;
127+ let quads = cell. quadrants ( ) ;
128+ let ( x_off, y_off) = ( cell. x_min , cell. y_min ) ;
129+ let mid_x = quads[ 0 ] . x_max ;
130+ let mid_y = quads[ 0 ] . y_max ;
147131 // Morton code of the first tile in each quadrant.
148132 let ne_base = morton ( mid_x, y_off) ;
149133 let sw_base = morton ( x_off, mid_y) ;
@@ -154,10 +138,10 @@ impl Node {
154138 let se_start = codes. partition_point ( |& c| c < se_base) ;
155139
156140 Node :: new_partial ( [
157- Node :: from_morton_sorted ( & codes[ ..ne_start] , x_off , y_off , half ) , // NW
158- Node :: from_morton_sorted ( & codes[ ne_start..sw_start] , mid_x , y_off , half ) , // NE
159- Node :: from_morton_sorted ( & codes[ sw_start..se_start] , x_off , mid_y , half ) , // SW
160- Node :: from_morton_sorted ( & codes[ se_start..] , mid_x , mid_y , half ) , // SE
141+ Node :: from_morton_sorted ( & codes[ ..ne_start] , & quads [ 0 ] ) ,
142+ Node :: from_morton_sorted ( & codes[ ne_start..sw_start] , & quads [ 1 ] ) ,
143+ Node :: from_morton_sorted ( & codes[ sw_start..se_start] , & quads [ 2 ] ) ,
144+ Node :: from_morton_sorted ( & codes[ se_start..] , & quads [ 3 ] ) ,
161145 ] )
162146 }
163147}
0 commit comments