282 lines
8.9 KiB
Standard ML
282 lines
8.9 KiB
Standard ML
|
|
structure QuadTree =
|
||
|
|
struct
|
||
|
|
type item = {itemID: int, startX: int, startY: int, width: int, height: int}
|
||
|
|
|
||
|
|
fun mkItem (id, startX, startY, width, height) : item =
|
||
|
|
{ itemID = id
|
||
|
|
, startX = startX
|
||
|
|
, startY = startY
|
||
|
|
, width = width
|
||
|
|
, height = height
|
||
|
|
}
|
||
|
|
|
||
|
|
datatype t =
|
||
|
|
NODE of
|
||
|
|
{ topLeft: t
|
||
|
|
, topRight: t
|
||
|
|
, bottomLeft: t
|
||
|
|
, bottomRight: t
|
||
|
|
, elements: item vector
|
||
|
|
}
|
||
|
|
| LEAF of item vector
|
||
|
|
|
||
|
|
(* max size of vector before we split it further *)
|
||
|
|
val maxSize = 9
|
||
|
|
|
||
|
|
fun isItemInQuad (iX, iY, iWidth, iHeight, qX, qY, qWidth, qHeight) =
|
||
|
|
iX >= qX andalso iY >= qY andalso iWidth <= qWidth
|
||
|
|
andalso iHeight <= qHeight
|
||
|
|
|
||
|
|
datatype quadrant =
|
||
|
|
TOP_LEFT
|
||
|
|
| TOP_RIGHT
|
||
|
|
| BOTTOM_LEFT
|
||
|
|
| BOTTOM_RIGHT
|
||
|
|
| PARENT_QUADRANT
|
||
|
|
|
||
|
|
fun whichQuadrant
|
||
|
|
(itemX, itemY, itemWidth, itemHeight, quadX, quadY, quadWidth, quadHeight) =
|
||
|
|
let
|
||
|
|
(* calculate quadrants *)
|
||
|
|
val halfWidth = quadWidth div 2
|
||
|
|
val halfHeight = quadHeight div 2
|
||
|
|
|
||
|
|
val middleX = quadX + halfWidth
|
||
|
|
val middleY = quadY + halfHeight
|
||
|
|
|
||
|
|
val isInTopLeft = isItemInQuad
|
||
|
|
( itemX, itemY, itemWidth, itemHeight
|
||
|
|
, quadX, quadY, halfWidth, halfHeight
|
||
|
|
)
|
||
|
|
|
||
|
|
val isInTopRight = isItemInQuad
|
||
|
|
( itemX, itemY, itemWidth, itemHeight
|
||
|
|
, middleX, quadY, halfWidth, halfHeight
|
||
|
|
)
|
||
|
|
|
||
|
|
val isInBottomLeft = isItemInQuad
|
||
|
|
( itemX, itemY, itemWidth, itemHeight
|
||
|
|
, quadX, middleY, halfWidth, halfHeight
|
||
|
|
)
|
||
|
|
|
||
|
|
val isInBottomRight = isItemInQuad
|
||
|
|
( itemX, itemY, itemWidth, itemHeight
|
||
|
|
, middleX, middleY, halfWidth, halfHeight
|
||
|
|
)
|
||
|
|
in
|
||
|
|
if isInTopLeft then TOP_LEFT
|
||
|
|
else if isInTopRight then TOP_RIGHT
|
||
|
|
else if isInBottomLeft then BOTTOM_LEFT
|
||
|
|
else if isInBottomRight then BOTTOM_RIGHT
|
||
|
|
else PARENT_QUADRANT
|
||
|
|
end
|
||
|
|
|
||
|
|
fun splitLeaf (qX, qY, qW, qH, tl, tr, bl, br, pe, elements, pos) =
|
||
|
|
if pos < 0 then
|
||
|
|
let
|
||
|
|
val tl = Vector.fromList tl
|
||
|
|
val tr = Vector.fromList tr
|
||
|
|
val bl = Vector.fromList bl
|
||
|
|
val br = Vector.fromList br
|
||
|
|
val pe = Vector.fromList pe
|
||
|
|
in
|
||
|
|
NODE
|
||
|
|
{ topLeft = LEAF tl
|
||
|
|
, topRight = LEAF tr
|
||
|
|
, bottomLeft = LEAF bl
|
||
|
|
, bottomRight = LEAF br
|
||
|
|
, elements = pe
|
||
|
|
}
|
||
|
|
end
|
||
|
|
else
|
||
|
|
let
|
||
|
|
val item = Vector.sub (elements, pos)
|
||
|
|
val {startX = iX, startY = iY, width = iW, height = iH, ...} = item
|
||
|
|
in
|
||
|
|
case whichQuadrant (iX, iY, iW, iH, qX, qY, qW, qH) of
|
||
|
|
TOP_LEFT =>
|
||
|
|
splitLeaf
|
||
|
|
(qX, qY, qW, qH, item :: tl, tr, bl, br, pe, elements, pos - 1)
|
||
|
|
| TOP_RIGHT =>
|
||
|
|
splitLeaf
|
||
|
|
(qX, qY, qW, qH, tl, item :: tr, bl, br, pe, elements, pos - 1)
|
||
|
|
| BOTTOM_LEFT =>
|
||
|
|
splitLeaf
|
||
|
|
(qX, qY, qW, qH, tl, tr, item :: bl, br, pe, elements, pos - 1)
|
||
|
|
| BOTTOM_RIGHT =>
|
||
|
|
splitLeaf
|
||
|
|
(qX, qY, qW, qH, tl, tr, bl, item :: br, pe, elements, pos - 1)
|
||
|
|
| PARENT_QUADRANT =>
|
||
|
|
splitLeaf
|
||
|
|
(qX, qY, qW, qH, tl, tr, bl, br, item :: pe, elements, pos - 1)
|
||
|
|
end
|
||
|
|
|
||
|
|
fun insert
|
||
|
|
( itemX, itemY, itemWidth, itemHeight
|
||
|
|
, quadX, quadY, quadWidth, quadHeight
|
||
|
|
, itemID, tree: t
|
||
|
|
) =
|
||
|
|
case tree of
|
||
|
|
NODE {topLeft, topRight, bottomLeft, bottomRight, elements} =>
|
||
|
|
(* check which quadrant item is in, if any.
|
||
|
|
* If in any child quadrants, recurse insertion into there.
|
||
|
|
* Else, add to elements vector in current node. *)
|
||
|
|
(case
|
||
|
|
whichQuadrant
|
||
|
|
( itemX, itemY, itemWidth, itemHeight
|
||
|
|
, quadX, quadY, quadWidth, quadHeight
|
||
|
|
)
|
||
|
|
of
|
||
|
|
TOP_LEFT =>
|
||
|
|
let
|
||
|
|
(* I know I am repeating myself by recalculating
|
||
|
|
* halfWidth/halfHeight in case branches but I prefer this
|
||
|
|
* over increating the indentation level further
|
||
|
|
* *)
|
||
|
|
val halfWidth = quadWidth div 2
|
||
|
|
val halfHeight = quadHeight div 2
|
||
|
|
|
||
|
|
val newTopLeft = insert
|
||
|
|
( itemX, itemY, itemWidth, itemHeight
|
||
|
|
, quadX, quadY, halfWidth, halfHeight
|
||
|
|
, itemID, topLeft
|
||
|
|
)
|
||
|
|
in
|
||
|
|
NODE
|
||
|
|
{ topLeft = newTopLeft
|
||
|
|
, topRight = topRight
|
||
|
|
, bottomLeft = bottomLeft
|
||
|
|
, bottomRight = bottomRight
|
||
|
|
, elements = elements
|
||
|
|
}
|
||
|
|
end
|
||
|
|
| TOP_RIGHT =>
|
||
|
|
let
|
||
|
|
val halfWidth = quadWidth div 2
|
||
|
|
val halfHeight = quadHeight div 2
|
||
|
|
val middleX = quadX + halfWidth
|
||
|
|
|
||
|
|
val newTopRight = insert
|
||
|
|
( itemX, itemY, itemWidth, itemHeight
|
||
|
|
, middleX, quadY, halfWidth, halfHeight
|
||
|
|
, itemID, topRight
|
||
|
|
)
|
||
|
|
in
|
||
|
|
NODE
|
||
|
|
{ topLeft = topLeft
|
||
|
|
, topRight = newTopRight
|
||
|
|
, bottomLeft = bottomLeft
|
||
|
|
, bottomRight = bottomRight
|
||
|
|
, elements = elements
|
||
|
|
}
|
||
|
|
end
|
||
|
|
| BOTTOM_LEFT =>
|
||
|
|
let
|
||
|
|
val halfWidth = quadWidth div 2
|
||
|
|
val halfHeight = quadHeight div 2
|
||
|
|
val middleY = quadY + halfHeight
|
||
|
|
|
||
|
|
val newBottomLeft = insert
|
||
|
|
( itemX, itemY, itemWidth, itemHeight
|
||
|
|
, quadX, middleY, halfWidth, halfHeight
|
||
|
|
, itemID, bottomLeft
|
||
|
|
)
|
||
|
|
in
|
||
|
|
NODE
|
||
|
|
{ topLeft = topLeft
|
||
|
|
, topRight = topRight
|
||
|
|
, bottomLeft = newBottomLeft
|
||
|
|
, bottomRight = bottomRight
|
||
|
|
, elements = elements
|
||
|
|
}
|
||
|
|
end
|
||
|
|
| BOTTOM_RIGHT =>
|
||
|
|
let
|
||
|
|
val halfWidth = quadWidth div 2
|
||
|
|
val halfHeight = quadHeight div 2
|
||
|
|
val middleX = quadX + halfWidth
|
||
|
|
val middleY = quadY + halfHeight
|
||
|
|
|
||
|
|
val newBottomRight = insert
|
||
|
|
( itemX, itemY, itemWidth, itemHeight
|
||
|
|
, middleX, middleY, halfWidth, halfHeight
|
||
|
|
, itemID, bottomRight
|
||
|
|
)
|
||
|
|
in
|
||
|
|
NODE
|
||
|
|
{ topLeft = topLeft
|
||
|
|
, topRight = topRight
|
||
|
|
, bottomLeft = bottomLeft
|
||
|
|
, bottomRight = newBottomRight
|
||
|
|
, elements = elements
|
||
|
|
}
|
||
|
|
end
|
||
|
|
| PARENT_QUADRANT =>
|
||
|
|
(* Does not fit in any of the child quadrants
|
||
|
|
* so we must add to the current parent quadrant. *)
|
||
|
|
let
|
||
|
|
val item = mkItem (itemID, itemX, itemY, itemWidth, itemHeight)
|
||
|
|
val elements = Vector.concat [elements, Vector.fromList [item]]
|
||
|
|
in
|
||
|
|
NODE
|
||
|
|
{ topLeft = topLeft
|
||
|
|
, topRight = topRight
|
||
|
|
, bottomLeft = bottomLeft
|
||
|
|
, bottomRight = bottomRight
|
||
|
|
, elements = elements
|
||
|
|
}
|
||
|
|
end)
|
||
|
|
| LEAF elements =>
|
||
|
|
if Vector.length elements + 1 > maxSize then
|
||
|
|
(* have to calculate quadrants and split *)
|
||
|
|
let
|
||
|
|
val pos = Vector.length elements - 1
|
||
|
|
val item = mkItem (itemID, itemX, itemY, itemWidth, itemHeight)
|
||
|
|
in
|
||
|
|
(case
|
||
|
|
whichQuadrant
|
||
|
|
( itemX, itemY, itemWidth, itemHeight
|
||
|
|
, quadX, quadY, quadWidth, quadHeight
|
||
|
|
)
|
||
|
|
of
|
||
|
|
TOP_LEFT =>
|
||
|
|
splitLeaf
|
||
|
|
( quadX, quadY, quadWidth, quadHeight
|
||
|
|
, [item], [], [], [], []
|
||
|
|
, elements, pos
|
||
|
|
)
|
||
|
|
| TOP_RIGHT =>
|
||
|
|
splitLeaf
|
||
|
|
( quadX, quadY, quadWidth, quadHeight
|
||
|
|
, [], [item], [], [], []
|
||
|
|
, elements, pos
|
||
|
|
)
|
||
|
|
| BOTTOM_LEFT =>
|
||
|
|
splitLeaf
|
||
|
|
( quadX, quadY, quadWidth, quadHeight
|
||
|
|
, [], [], [item], [], []
|
||
|
|
, elements, pos
|
||
|
|
)
|
||
|
|
| BOTTOM_RIGHT =>
|
||
|
|
splitLeaf
|
||
|
|
( quadX, quadY, quadWidth, quadHeight
|
||
|
|
, [], [], [], [item], []
|
||
|
|
, elements, pos
|
||
|
|
)
|
||
|
|
| PARENT_QUADRANT =>
|
||
|
|
splitLeaf
|
||
|
|
( quadX, quadY, quadWidth, quadHeight
|
||
|
|
, [], [], [], [], [item]
|
||
|
|
, elements, pos
|
||
|
|
))
|
||
|
|
end
|
||
|
|
else
|
||
|
|
(* can insert itemID in elements vector *)
|
||
|
|
let
|
||
|
|
val item = mkItem (itemID, itemX, itemY, itemWidth, itemHeight)
|
||
|
|
val elements = Vector.concat [elements, Vector.fromList [item]]
|
||
|
|
in
|
||
|
|
LEAF elements
|
||
|
|
end
|
||
|
|
end
|