QuadTree API Introduction
authorRomain Chanoir <chanoir.romain@courrier.uqam.ca>
Wed, 4 Jun 2014 16:52:18 +0000 (12:52 -0400)
committerRomain Chanoir <chanoir.romain@courrier.uqam.ca>
Wed, 4 Jun 2014 16:52:18 +0000 (12:52 -0400)
Signed-off-by: Romain Chanoir <chanoir.romain@courrier.uqam.ca>

lib/geometry/quadtree.nit [new file with mode: 0644]

diff --git a/lib/geometry/quadtree.nit b/lib/geometry/quadtree.nit
new file mode 100644 (file)
index 0000000..97c0783
--- /dev/null
@@ -0,0 +1,247 @@
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Copyright 2014 Romain Chanoir <romain.chanoir@viacesi.fr>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# QuadTree API mostly used for 2 dimensional collision detection
+# The QuadTree data structure partition a 2D space by recursively
+# subdividing it into 4 regions when it's capacity is reached
+# This module introduce 2 principal implementation of the structure,
+# the static and the dynamic QuadTree
+module quadtree
+
+import boxes
+import pipeline
+
+# Abstract QuadTree implementing most of the basic functions
+# and introducing specific QuadTree data
+abstract class QuadTree[E: Boxed[Numeric]]
+       super BoxedCollection[E]
+
+       protected var center: nullable Point[Numeric]
+       var data: Array[E] = new Array[E]
+
+       #  ________________
+       #  |       |       |
+       #  |   1   |   2   |
+       #  |-------|-------|
+       #  |   0   |   3   |
+       #  |_______|_______|
+
+       protected var child0: nullable QuadTree[E]
+       protected var child1: nullable QuadTree[E]
+       protected var child2: nullable QuadTree[E]
+       protected var child3: nullable QuadTree[E]
+
+       # represent the threshold before subdividing the node
+       protected var item_limit = 4
+       protected var parent_node: nullable QuadTree[E]
+
+       # create the quadtree and set the item limit for each node
+       init(limit: Int)
+       do
+               self.item_limit = limit
+       end
+
+       # create a node, giving him self as a parent. Used to create children nodes
+       init with_parent(limit: Int, parent: QuadTree[E])
+       do
+               init(limit)
+               self.parent_node = parent
+       end
+
+       redef fun items_overlapping(region :Boxed[Numeric]): SimpleCollection[E] do
+        var res = new Array[E]
+        items_overlapping_in(region,res)
+        return res
+     end  
+
+       # add the item to the tree, create children if the limit is reached
+       redef fun add(item: E) do if self.is_leaf then self.data.add(item) else add_to_children(item)
+
+       private fun add_to_children(item: Boxed[Numeric])
+       do
+               if self.child0 != null then
+                       if self.center.x > item.right then
+                               if self.center.y > item.top then
+                                       child0.add(item)
+                               else if self.center.y < item.bottom then
+                                       child1.add(item)
+                               else
+                                       self.data.add(item)
+                               end
+                       else if self.center.x < item.left then
+                               if self.center.y > item.top then
+                                       child3.add(item)
+                               else if self.center.y < item.bottom then
+                                       child2.add(item)
+                               else
+                                       self.data.add(item)
+                               end
+                       else if self.center.y > item.top then
+                               self.data.add(item)
+                       else if self.center.y < item.bottom then
+                                       self.data.add(item)
+                       else
+                               self.data.add(item)
+                       end
+               end
+       end
+
+       redef fun is_empty: Bool do return data.is_empty and (self.is_leaf or (child0.is_empty and child1.is_empty and child2.is_empty and child3.is_empty))
+
+       # Return whether or not the Node is a leaf of the tree
+       fun is_leaf: Bool do return child0 == null
+
+       #     var dquadtree = new DQuadTree[Point[Int]](2)
+       #     var p1 = new Point[Int](0,0)
+       #     var p2 = new Point[Int](0,9)
+       #     var p3 = new Point[Int](9,0)
+       #     dquadtree.add(p1)
+       #     dquadtree.add(p2)
+       #     dquadtree.add(p3)
+       #     var result = dquadtree.items_overlapping(p3)
+       #     assert result.length == 1
+       #     result.clear
+       #     var p4 = new Point[Int](9,9)
+       #     result = dquadtree.items_overlapping(p4)
+       #     assert result.length == 0
+       #     result = dquadtree.items_overlapping(p4.padded(10))
+       #     assert result.length == 3
+       fun items_overlapping_in(region: Boxed[Numeric], mdata: SimpleCollection[E])
+       do
+               if self.is_leaf and data.length >= item_limit then
+                       subdivide
+                       var data_copy = data
+                       data = new Array[E]
+                       #add to the right Node
+                       for d in data_copy do
+                               add_to_children(d)
+                       end
+               end
+               for i in data do if i.intersects(region) then mdata.add(i)
+               if self.child0 != null then
+                       if self.center.x > region.right then
+                               if self.center.y > region.top then
+                                       child0.items_overlapping_in(region, mdata)
+                               else if self.center.y < region.bottom then
+                                       child1.items_overlapping_in(region, mdata)
+                               else
+                                       child0.items_overlapping_in(region,mdata)
+                                       child1.items_overlapping_in(region, mdata)
+                               end
+                       else if self.center.x < region.left then
+                               if self.center.y > region.top then
+                                       child3.items_overlapping_in(region, mdata)
+                               else if self.center.y < region.bottom then
+                                       child2.items_overlapping_in(region, mdata)
+                               else
+                                       child3.items_overlapping_in(region, mdata)
+                                       child2.items_overlapping_in(region, mdata)
+                               end
+                       else if self.center.y > region.top then
+                               child0.items_overlapping_in(region, mdata)
+                               child3.items_overlapping_in(region, mdata)
+                       else if self.center.y < region.bottom then
+                               child1.items_overlapping_in(region, mdata)
+                               child2.items_overlapping_in(region, mdata)
+                       else
+                               child0.items_overlapping_in(region, mdata)
+                               child1.items_overlapping_in(region, mdata)
+                               child2.items_overlapping_in(region, mdata)
+                               child3.items_overlapping_in(region, mdata)
+                       end
+               end
+       end
+
+       # this function is responsible of the creation of the children,
+       # depending on your needs
+       protected fun subdivide is abstract
+
+       redef fun iterator do if self.is_leaf then return data.iterator else return data.iterator + child0.iterator + child1.iterator + child2.iterator + child3.iterator
+end
+
+# A dynamic implementation of the quadtree data structure
+# the center of the parent node is determined by the average
+# values of the data it contains when the item limit is reached
+class DQuadTree[E: Boxed[Numeric]]
+       super QuadTree[E]
+
+       redef fun subdivide
+       do
+               self.center = new Point[Numeric](average_x, average_y)
+               child0 = new DQuadTree[E].with_parent(self.item_limit, self)
+               child1 = new DQuadTree[E].with_parent(self.item_limit, self)
+               child2 = new DQuadTree[E].with_parent(self.item_limit, self)
+               child3 = new DQuadTree[E].with_parent(self.item_limit, self)
+       end
+
+       # average x of data in this node
+       fun average_x: Numeric
+       do
+               var x_total = data.first.left.zero
+               for data in self.data do
+                       x_total += (data.left + data.right)/x_total.value_of(2)
+               end
+               return x_total/x_total.value_of(self.data.length)
+       end
+
+       # average y of data in this node
+       fun average_y: Numeric
+       do
+               var y_total = data.first.left.zero
+               for data in self.data do
+                       y_total += (data.left + data.right)/y_total.value_of(2)
+               end
+               return y_total/y_total.value_of(self.data.length)
+       end
+end
+
+# Static implementation of the quadtree structure.
+# You need to specify a zone when creating the quadtree,
+# which will be the zone corresponding to the root node.
+# each subdivision cut the space in 4 equal regions from
+# the center of the parent node
+class SQuadTree[E: Boxed[Numeric]]
+       super QuadTree[E]
+       
+       # the width of the current node of the QuadTree
+       var width: Numeric
+       # the height of the current node of the QuadTree
+       var height: Numeric
+
+       init(l: Int, c: Point[Numeric], w: Numeric, h: Numeric)
+       do
+               self.item_limit = l
+               self.center = c
+               self.width = w
+               self.height = h
+       end
+
+       init with_parent(l: Int, c: Point[Numeric], w: Numeric, h: Numeric, p: QuadTree[E])
+       do
+               init(l, c, w, h)
+               self.parent_node = p
+       end
+
+       redef fun subdivide
+       do
+               var two = self.center.x.value_of(2)
+               var tree = self.center.x.value_of(3)
+               child0 = new SQuadTree[E].with_parent(self.item_limit, new Point[Numeric](self.center.x/two, self.center.y/two), self.width/two, self.height/two, self)
+               child1 = new SQuadTree[E].with_parent(self.item_limit, new Point[Numeric](self.center.x/two, (self.center.y*tree)/two), self.width/two, self.height/two, self)
+               child2 = new SQuadTree[E].with_parent(self.item_limit, new Point[Numeric]((self.center.x*tree)/two, (self.center.y*tree)/two), self.width/two, self.height/two, self)
+               child3 = new SQuadTree[E].with_parent(self.item_limit, new Point[Numeric]((self.center.x*tree)/two, self.center.y/two), self.width/two, self.height/two, self)
+       end
+end