decrease memory consumption of quad tree by refraining from storing bounding box metadata (except for the global root which stores the width and height)

This commit is contained in:
2025-02-01 01:03:57 +00:00
parent 73aeeb5301
commit 9e9675aaab
4 changed files with 168 additions and 274 deletions

View File

@@ -11,7 +11,7 @@ sig
structure Fn: QUAD_FOLDER structure Fn: QUAD_FOLDER
val foldRegion: int * int * int * int * val foldRegion: int * int * int * int *
Fn.env * Fn.state * QuadTreeType.t Fn.env * Fn.state * {tree: QuadTreeType.t, width: int, height: int}
-> Fn.state -> Fn.state
end end
@@ -36,25 +36,52 @@ struct
foldRegionVec (rx, ry, rw, rh, env, state, pos + 1, elements) foldRegionVec (rx, ry, rw, rh, env, state, pos + 1, elements)
end end
fun foldRegion (rx, ry, rw, rh, env, state, tree) = fun helpFoldRegion (rx, ry, rw, rh, env, state, qx, qy, qw, qh, tree) =
case tree of case tree of
NODE {nodes, x, y, w, h} => NODE nodes =>
if isCollidingPlus (rx, ry, rw, rh, x, y, w, h) then
let let
val vtl = visitTopLeft (rx, ry, rw, rh, qx, qy, qw, qh)
val vtr = visitTopRight (rx, ry, rw, rh, qx, qy, qw, qh)
val vbl = visitBottomLeft (rx, ry, rw, rh, qx, qy, qw, qh)
val vbr = visitBottomRight (rx, ry, rw, rh, qx, qy, qw, qh)
val hw = qw div 2
val hh = qh div 2
val state = val state =
foldRegion (rx, ry, rw, rh, env, state, Vector.sub (nodes, tlIdx)) if vtl then
helpFoldRegion
(rx, ry, rw, rh, env, state, qx, qy, hw, hh, Vector.sub (nodes, tlIdx))
else
state
val state = val state =
foldRegion (rx, ry, rw, rh, env, state, Vector.sub (nodes, trIdx)) if vtr then
helpFoldRegion
(rx, ry, rw, rh, env, state, qx + hw, qy, hw, hh, Vector.sub (nodes, trIdx))
else
state
val state = val state =
foldRegion (rx, ry, rw, rh, env, state, Vector.sub (nodes, blIdx)) if vbl then
helpFoldRegion
(rx, ry, rw, rh, env, state, qx, qy + hh, hw, hh, Vector.sub (nodes, blIdx))
else
state
in in
foldRegion (rx, ry, rw, rh, env, state, Vector.sub (nodes, brIdx)) if vbr then
end helpFoldRegion
(rx, ry, rw, rh, env, state, qw + hw, qy + hh, hw, hh, Vector.sub (nodes, brIdx))
else else
state state
| LEAF {items, x, y, w, h} => end
if isCollidingPlus (rx, ry, rw, rh, x, y, w, h) then | LEAF items =>
foldRegionVec (rx, ry, rw, rh, env, state, 0, items) foldRegionVec (rx, ry, rw, rh, env, state, 0, items)
else
state fun foldRegion (rx, ry, rw, rh, env, state, tree) =
let
val {width, height, tree} = tree
in
helpFoldRegion (rx, ry, rw, rh, env, state, 0, 0, width, height, tree)
end
end end

View File

@@ -3,14 +3,8 @@ sig
type item = {itemID: int, startX: int, startY: int, width: int, height: int} type item = {itemID: int, startX: int, startY: int, width: int, height: int}
datatype t = datatype t =
NODE of NODE of t vector
{ nodes: t vector | LEAF of item vector
, x: int
, y: int
, w: int
, h: int
}
| LEAF of {items: item vector, x: int, y: int, w: int, h: int}
val tlIdx: int val tlIdx: int
val trIdx: int val trIdx: int
@@ -51,14 +45,8 @@ struct
type item = {itemID: int, startX: int, startY: int, width: int, height: int} type item = {itemID: int, startX: int, startY: int, width: int, height: int}
datatype t = datatype t =
NODE of NODE of t vector
{ nodes: t vector | LEAF of item vector
, x: int
, y: int
, w: int
, h: int
}
| LEAF of {items: item vector, x: int, y: int, w: int, h: int}
val tlIdx = 0 val tlIdx = 0
val trIdx = 1 val trIdx = 1

View File

@@ -6,8 +6,6 @@ sig
val getCollisions: int * int * int * int * int * t -> int list val getCollisions: int * int * int * int * int * t -> int list
val helpGetCollisions: int * int * int * int * int * int list * t -> int list
val hasCollisionAt: int * int * int * int * int * t -> bool val hasCollisionAt: int * int * int * int * int * t -> bool
val getItemID: int * int * int * int * t -> int val getItemID: int * int * int * int * t -> int
@@ -21,8 +19,15 @@ struct
type item = QuadTreeType.item type item = QuadTreeType.item
type t = {tree: QuadTreeType.t, width: int, height: int}
fun create (width, height) = fun create (width, height) =
LEAF {items = Vector.fromList [], x = 0, y = 0, w = width, h = height} let
val vec = Vector.fromList []
val tree = LEAF vec
in
{ tree = tree, width = width, height = height }
end
fun mkItem (id, startX, startY, width, height) : item = fun mkItem (id, startX, startY, width, height) : item =
{ itemID = id { itemID = id
@@ -32,49 +37,14 @@ struct
, height = height , height = height
} }
type t = QuadTreeType.t
(* max size of vector before we split it further *) (* max size of vector before we split it further *)
val maxSize = 9 val maxSize = 16
fun mkTopLeft (x, y, w, h, items) = fun mkLeaf items =
let let
val items = Vector.fromList items val items = Vector.fromList items
val hw = w div 2
val hh = h div 2
in in
LEAF {items = items, x = x, y = y, w = hw, h = hh} LEAF items
end
fun mkTopRight (x, y, w, h, items) =
let
val items = Vector.fromList items
val hw = w div 2
val hh = h div 2
val x = x + hw
in
LEAF {items = items, x = x, y = y, w = hw, h = hh}
end
fun mkBottomLeft (x, y, w, h, items) =
let
val items = Vector.fromList items
val hw = w div 2
val hh = h div 2
val y = y + hh
in
LEAF {items = items, x = x, y = y, w = hw, h = hh}
end
fun mkBottomRight (x, y, w, h, items) =
let
val items = Vector.fromList items
val hw = w div 2
val hh = h div 2
val x = x + hw
val y = y + hh
in
LEAF {items = items, x = x, y = y, w = hw, h = hh}
end end
fun splitLeaf fun splitLeaf
@@ -91,18 +61,13 @@ struct
) = ) =
if pos < 0 then if pos < 0 then
let let
val tl = mkTopLeft (x, y, w, h, tl) val tl = mkLeaf tl
val tr = mkTopRight (x, y, w, h, tr) val tr = mkLeaf tr
val bl = mkBottomLeft (x, y, w, h, bl) val bl = mkLeaf bl
val br = mkBottomRight (x, y, w, h, br) val br = mkLeaf br
val nodes = Vector.fromList [tl, tr, bl, br]
in in
NODE NODE nodes
{ nodes = Vector.fromList [tl, tr, bl, br]
, x = x
, y = y
, w = w
, h = h
}
end end
else else
let let
@@ -115,202 +80,116 @@ struct
val vbr = visitBottomRight (iX, iY, iW, iH, x, y, w, h) val vbr = visitBottomRight (iX, iY, iW, iH, x, y, w, h)
val tl = if vtl then item :: tl else tl val tl = if vtl then item :: tl else tl
val tr = if vtr then item :: tr else tr val tr = if vtr then item :: tr else tr
val bl = if vbl then item :: bl else bl val bl = if vbl then item :: bl else bl
val br = if vbr then item :: br else br val br = if vbr then item :: br else br
in in
splitLeaf (x, y, w, h, tl, tr, bl, br, elements, pos - 1) splitLeaf (x, y, w, h, tl, tr, bl, br, elements, pos - 1)
end end
fun insert (iX, iY, iW, iH, itemID, tree: t) = fun helpInsert (ix, iy, iw, ih, itemID, qx, qy, qw, qh, tree) =
case tree of case tree of
NODE {nodes, x, y, w, h} => NODE nodes =>
if isCollidingPlus (iX, iY, iW, iH, x, y, w, h) then
let let
(* we are not necessarily inserting into all nodes. val vtl = visitTopLeft (ix, iy, iw, ih, qx, qy, qw, qh)
* If isCollidingPlus returns false recursively, val vtr = visitTopRight (ix, iy, iw, ih, qx, qy, qw, qh)
* we return the same node back. *) val vbl = visitBottomLeft (ix, iy, iw, ih, qx, qy, qw, qh)
val tl = insert (iX, iY, iW, iH, itemID, Vector.sub (nodes, tlIdx)) val vbr = visitBottomRight (ix, iy, iw, ih, qx, qy, qw, qh)
val tr = insert (iX, iY, iW, iH, itemID, Vector.sub (nodes, trIdx))
val bl = insert (iX, iY, iW, iH, itemID, Vector.sub (nodes, blIdx)) val hw = qw div 2
val br = insert (iX, iY, iW, iH, itemID, Vector.sub (nodes, brIdx)) val hh = qh div 2
in
NODE val tl = Vector.sub (nodes, tlIdx)
{ nodes = Vector.fromList [tl, tr, bl, br] val tl =
, x = x if vtl then
, y = y helpInsert (ix, iy, iw, ih, itemID, qw, qy, hw, hh, tl)
, w = w
, h = h
}
end
else else
tree tl
| LEAF {items, x, y, w, h} =>
if isCollidingPlus (iX, iY, iW, iH, x, y, w, h) then val tr = Vector.sub (nodes, trIdx)
val tr =
if vtr then
helpInsert (ix, iy, iw, ih, itemID, qx + hw, qy, hw, hh, tr)
else
tr
val bl = Vector.sub (nodes, blIdx)
val bl =
if vbl then
helpInsert (ix, iy, iw, ih, itemID, qx, qy + hh, hw, hh, bl)
else
bl
val br = Vector.sub (nodes, brIdx)
val br =
if vbr then
helpInsert (ix, iy, iw, ih, itemID, qx + hw, qy + hh, hw, hh, br)
else
br
val nodes = Vector.fromList [tl, tr, bl, br]
in
NODE nodes
end
| LEAF items =>
if Vector.length items + 1 > maxSize then if Vector.length items + 1 > maxSize then
(* have to calculate quadrants and split *)
let let
val pos = Vector.length items - 1 val vtl = visitTopLeft (ix, iy, iw, ih, qx, qy, qw, qh)
val item = mkItem (itemID, iX, iY, iW, iH) val vtr = visitTopRight (ix, iy, iw, ih, qx, qy, qw, qh)
val vbl = visitBottomLeft (ix, iy, iw, ih, qx, qy, qw, qh)
val vbr = visitBottomRight (ix, iy, iw, ih, qx, qy, qw, qh)
val vtl = visitTopLeft (iX, iY, iW, iH, x, y, w, h) val newItem = mkItem (itemID, ix, iy, iw, ih)
val vtr = visitTopRight (iX, iY, iW, iH, x, y, w, h)
val vbl = visitBottomLeft (iX, iY, iW, iH, x, y, w, h)
val vbr = visitBottomRight (iX, iY, iW, iH, x, y, w, h)
val tl = if vtl then [item] else [] val tl = if vtl then [newItem] else []
val tr = if vtr then [newItem] else []
val tr = if vtr then [item] else [] val bl = if vbl then [newItem] else []
val br = if vbr then [newItem] else []
val bl = if vbl then [item] else []
val br = if vbr then [item] else []
in in
splitLeaf (x, y, w, h, tl, tr, bl, br, items, pos) splitLeaf
(qx, qy, qw, qh, tl, tr, bl, br, items, Vector.length items - 1)
end end
else
(* can insert itemID in items vector *)
let
val item = mkItem (itemID, iX, iY, iW, iH)
val items = Vector.concat [items, Vector.fromList [item]]
in
LEAF {items = items, x = x, y = y, w = w, h = h}
end
else
(* bounds of new item don't fit inside leaf so return old tree *)
tree
fun getCollisionsVec (iX, iY, iW, iH, itemID, pos, elements, acc) =
if pos = Vector.length elements then
acc
else else
let let
val item = Vector.sub (elements, pos) val newItem = mkItem (itemID, ix, iy, iw, ih)
val acc = val newItems = Vector.concat [items, Vector.fromList [newItem]]
if isCollidingItem (iX, iY, iW, iH, itemID, item) then
#itemID item :: acc
else
acc
in in
getCollisionsVec (iX, iY, iW, iH, itemID, pos + 1, elements, acc) LEAF newItems
end end
fun getCollisionsAll (iX, iY, iW, iH, itemID, acc, tree) = fun insert (iX, iY, iW, iH, itemID, tree: t) =
case tree of
NODE {nodes, x, y, w, h} =>
if isCollidingPlus (iX, iY, iW, iH, x, y, w, h) then
let let
val acc = val {width, height, tree} = tree
getCollisionsAll (iX, iY, iW, iH, itemID, acc, Vector.sub (nodes, tlIdx)) val tree =
helpInsert (iX, iY, iW, iH, itemID, 0, 0, width, height, tree)
val acc =
getCollisionsAll (iX, iY, iW, iH, itemID, acc, Vector.sub (nodes, trIdx))
val acc =
getCollisionsAll (iX, iY, iW, iH, itemID, acc, Vector.sub (nodes, blIdx))
in in
getCollisionsAll (iX, iY, iW, iH, itemID, acc, Vector.sub (nodes, brIdx)) {width = width, height = height, tree = tree}
end
else
acc
| LEAF {items, x, y, w, h} =>
if isCollidingPlus (iX, iY, iW, iH, x, y, w, h) then
getCollisionsVec (iX, iY, iW, iH, itemID, 0, items, acc)
else
acc
fun helpGetCollisions (iX, iY, iW, iH, itemID, acc, tree: t) =
case tree of
NODE {nodes, x, y, w, h} =>
if isCollidingPlus (iX, iY, iW, iH, x, y, w, h) then
let
val acc =
helpGetCollisions (iX, iY, iW, iH, itemID, acc, Vector.sub (nodes, tlIdx))
val acc =
helpGetCollisions (iX, iY, iW, iH, itemID, acc, Vector.sub (nodes, trIdx))
val acc =
helpGetCollisions (iX, iY, iW, iH, itemID, acc, Vector.sub (nodes, blIdx))
in
helpGetCollisions
(iX, iY, iW, iH, itemID, acc, Vector.sub (nodes, brIdx))
end
else
acc
| LEAF {items, x, y, w, h} =>
if isCollidingPlus (iX, iY, iW, iH, x, y, w, h) then
getCollisionsVec (iX, iY, iW, iH, itemID, 0, items, acc)
else
acc
fun getCollisions (itemX, itemY, itemWidth, itemHeight, itemID, tree) =
helpGetCollisions (itemX, itemY, itemWidth, itemHeight, itemID, [], tree)
fun hasCollisionAtVec (iX, iY, iW, iH, itemID, pos, elements) =
if pos = Vector.length elements then
false
else
let
val item = Vector.sub (elements, pos)
in
isCollidingItem (iX, iY, iW, iH, itemID, item)
orelse hasCollisionAtVec (iX, iY, iW, iH, itemID, pos + 1, elements)
end end
fun hasCollisionAt (iX, iY, iW, iH, itemID, tree) = structure GetCollisions = MakeQuadTreeFold (struct
case tree of type env = unit
NODE {nodes, x, y, w, h} => type state = int list
if isCollidingPlus (iX, iY, iW, iH, x, y, w, h) then fun fold (itemID, (), lst) = itemID :: lst
hasCollisionAt (iX, iY, iW, iH, itemID, Vector.sub (nodes, tlIdx)) end)
orelse hasCollisionAt (iX, iY, iW, iH, itemID, Vector.sub (nodes, trIdx))
orelse hasCollisionAt (iX, iY, iW, iH, itemID, Vector.sub (nodes, blIdx))
orelse hasCollisionAt (iX, iY, iW, iH, itemID, Vector.sub (nodes, brIdx))
else
false
| LEAF {items, x, y, w, h} =>
if isCollidingPlus (iX, iY, iW, iH, x, y, w, h) then
hasCollisionAtVec (iX, iY, iW, iH, itemID, 0, items)
else
false
fun getItemIDVec (iX, iY, iW, iH, pos, elements) = fun getCollisions (itemX, itemY, itemWidth, itemHeight, _, tree) =
if pos = Vector.length elements then GetCollisions.foldRegion (itemX, itemY, itemWidth, itemHeight, (), [], tree)
~1
else
let
val item = Vector.sub (elements, pos)
in
if isCollidingItem (iX, iY, iW, iH, ~1, item) then #itemID item
else getItemIDVec (iX, iY, iW, iH, pos + 1, elements)
end
fun getItemID (iX, iY, iW, iH, tree) = structure HasCollisionAt = MakeQuadTreeFold (struct
case tree of type env = unit
NODE {nodes, x, y, w, h} => type state = bool
if isCollidingPlus (iX, iY, iW, iH, x, y, w, h) then fun fold _ = true
let end)
val try1 = getItemID (iX, iY, iW, iH, Vector.sub (nodes, tlIdx))
val try2 = getItemID (iX, iY, iW, iH, Vector.sub (nodes, trIdx))
val try3 = getItemID (iX, iY, iW, iH, Vector.sub (nodes, blIdx))
val try4 = getItemID (iX, iY, iW, iH, Vector.sub (nodes, brIdx))
(* get max: we assume query was narrow enough fun hasCollisionAt (ix, iy, iw, ih, _, tree) =
* that only one ID is valid *) HasCollisionAt.foldRegion (ix, iy, iw, ih, (), false, tree)
val a = Int.max (try1, try2)
val a = Int.max (a, try3) structure GetItemID = MakeQuadTreeFold (struct
val a = Int.max (a, try4) type env = unit
in type state = int
a fun fold (itemID, (), curID) = Int.max (itemID, curID)
end end)
else
~1 fun getItemID (ix, iy, iw, ih, tree) =
| LEAF {items, x, y, w, h} => GetItemID.foldRegion (ix, iy, iw, ih, (), ~1, tree)
if isCollidingPlus (iX, iY, iW, iH, x, y, w, h) then
getItemIDVec (iX, iY, iW, iH, 0, items)
else
~1
end end

View File

@@ -4,8 +4,8 @@ $(SML_LIB)/basis/basis.mlb
fcore/constants.sml fcore/constants.sml
fcore/quad-tree-type.sml fcore/quad-tree-type.sml
fcore/quad-tree.sml
fcore/quad-tree-fold.sml fcore/quad-tree-fold.sml
fcore/quad-tree.sml
fcore/bin-search.sml fcore/bin-search.sml
fcore/bin-vec.sml fcore/bin-vec.sml