Skip to content

Commit 0ca6407

Browse files
⚙ Implemented Remove method, Improved doc-comments
1 parent 41c1f93 commit 0ca6407

File tree

3 files changed

+125
-74
lines changed

3 files changed

+125
-74
lines changed

Scripts/Items/GameObjectItem.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ public abstract class GameObjectItem : MonoBehaviour, IItem<GameObjectItem>
1717
/// </summary>
1818
private Bounds _safeBounds;
1919

20+
//==========================================================================dd==
21+
// MonoBehaviour METHODS
22+
//==========================================================================dd==
23+
2024
private void Start()
2125
{
2226
Init();
@@ -55,6 +59,10 @@ private void LateUpdate()
5559
}
5660
}
5761

62+
//==========================================================================dd==
63+
// CORE TREE ITEM METHODS
64+
//==========================================================================dd==
65+
5866
/// <inheritdoc cref="IItem{TItem}.ParentNode"/>
5967
private Node<GameObjectItem> _parentNode = null;
6068

@@ -76,7 +84,7 @@ public Node<GameObjectItem> ParentNode {
7684
/// <summary>
7785
/// <c>True</c> if the item has been initialized.
7886
/// </summary>
79-
protected bool ItemInitialized = false;
87+
protected internal bool ItemInitialized = false;
8088

8189
public void QuadTree_Root_Initialized(RootNode<GameObjectItem> root)
8290
{
@@ -119,11 +127,16 @@ protected virtual void Init()
119127
}
120128
}
121129

130+
/// <summary>
131+
/// Returns unique identifier of the item.
132+
/// </summary>
133+
/// <remarks>
134+
/// It is extremely important to override this method because nodes are using HashSets to store items and the items are changing during the updates and so are the hash codes which can result in program not working properly.
135+
/// </remarks>
136+
///
137+
/// <returns>Unique identifier</returns>
122138
public override int GetHashCode()
123139
{
124-
// it is extremely important to override this method
125-
// because nodes are using HashSets to store items
126-
// and items are changing during the updates and so are the hash codes
127140
return GetInstanceID();
128141
}
129142
}

Scripts/Node.cs

Lines changed: 93 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@ namespace Quadtree
1111
/// </summary>
1212
public class Node<TItem> where TItem : IItem<TItem>
1313
{
14+
/// <summary>
15+
/// Describes relative local position in respect to the current node.
16+
/// </summary>
17+
/// <remarks>
18+
/// Integer values of <c>UPPER_LEFT</c>, <c>UPPER_RIGHT</c>, <c>LOWER_RIGHT</c>, <c>LOWER_LEFT</c> do correspond with the indices of the sub-nodes.
19+
/// </remarks>
1420
public enum IntraLocation {
1521
UPPER_LEFT, UPPER_RIGHT, LOWER_RIGHT, LOWER_LEFT,
1622
SPANNING_LEFT, SPANNING_RIGHT, SPANNING_UPPER, SPANNING_LOWER, SPANNING
1723
};
1824

19-
/// <summary>
20-
/// Minimum possible value for node size.
21-
/// </summary>
22-
public const float MinSize = 1f;
23-
2425
/// <summary>
2526
/// Bounds of this tree node.
2627
/// </summary>
@@ -35,8 +36,11 @@ public enum IntraLocation {
3536
public Node<TItem> ParentNode { get => _parentNode; internal set => _parentNode = value; }
3637

3738
/// <summary>
38-
/// Parent tree node.
39+
/// Reference to parent tree node.
3940
/// </summary>
41+
/// <remarks>
42+
/// Is <c>null</c> for root node of the tree.
43+
/// </remarks>
4044
private Node<TItem> _parentNode = null;
4145

4246
/// <summary>
@@ -45,16 +49,16 @@ public enum IntraLocation {
4549
private RootNode<TItem> _root;
4650

4751
/// <summary>
48-
/// Child tree nodes.
52+
/// List of child tree nodes.
4953
/// </summary>
5054
private readonly List<Node<TItem>> _subNodes;
5155

5256
/// <summary>
53-
/// Creates new tree node at provided point with provided sizes.
54-
/// List of predefined sub-nodes and/or items may be provided.
57+
/// Creates new tree node at provided point (<paramref name="center"/>) with provided sizes (<paramref name="size"/>).
58+
/// List of predefined sub-nodes (<paramref name="subNodes"/>) and/or items (<paramref name="items"/>) may be provided.
5559
/// </summary>
5660
/// <remarks>
57-
/// No boundary validation is performed neither for provided sub-nodes nor items.
61+
/// No boundary validation is performed neither for provided sub-nodes (<paramref name="subNodes"/>) nor items (<paramref name="items"/>).
5862
/// </remarks>
5963
///
6064
/// <param name="root">Root node of the tree</param>
@@ -74,22 +78,22 @@ public Node(RootNode<TItem> root, Node<TItem> parent, Vector3 center, Vector3 si
7478
}
7579

7680
/// <summary>
77-
/// Verifies whether provided boundaries are fully contained within boundaries of this node.
81+
/// Verifies whether provided boundaries (<paramref name="bounds"/>) are fully contained within the boundaries of the node.
7882
/// </summary>
7983
///
8084
/// <param name="bounds">Boundaries of an object</param>
81-
/// <returns>Object is/is not fully contained</returns>
85+
/// <returns><c>True</c> if object is fully contained within the node, <c>False</c> otherwise</returns>
8286
public bool Contains(Bounds bounds) =>
8387
bounds.min.x >= Bounds.min.x
8488
&& bounds.min.z >= Bounds.min.z
8589
&& bounds.max.x < Bounds.max.x
8690
&& bounds.max.z < Bounds.max.z;
8791

8892
/// <summary>
89-
/// Calculates relative internal position of the provided bounds within the node.
93+
/// Calculates relative internal position of the provided bounds (<paramref name="bounds"/>) within the node.
9094
/// </summary>
9195
/// <remarks>
92-
/// Method does not check if bounds are actually contained within the node.
96+
/// The method expects the boundaries to be fully contained within the node.
9397
/// </remarks>
9498
///
9599
/// <param name="bounds">Boundaries contained within the node</param>
@@ -159,10 +163,10 @@ public IntraLocation Location(Bounds bounds)
159163
}
160164

161165
/// <summary>
162-
/// Inserts item into smallest node possible in the tree.
166+
/// Inserts item (<paramref name="item"/>) into the smallest node possible in the subtree.
163167
/// </summary>
164168
/// <remarks>
165-
/// Node's insert method expects item boundaries to be fully contained within the node.
169+
/// The method expects item boundaries to be fully contained within the node.
166170
/// </remarks>
167171
///
168172
/// <param name="item">Item to be inserted</param>
@@ -204,13 +208,71 @@ public void Insert(TItem item)
204208
}
205209

206210
/// <summary>
207-
/// Updates provided item's location within the tree.
211+
/// Removes the provided item (<paramref name="item"/>) from the node and its subtree.
212+
/// </summary>
213+
///
214+
/// <param name="item">Item to be removed from the tree</param>
215+
public void Remove(TItem item)
216+
{
217+
var itemBounds = item.GetBounds();
218+
var itemBoundsLocation = Location(itemBounds);
219+
switch (itemBoundsLocation)
220+
{
221+
// boundaries are contained within one of the subnodes
222+
case IntraLocation.UPPER_LEFT:
223+
case IntraLocation.UPPER_RIGHT:
224+
case IntraLocation.LOWER_RIGHT:
225+
case IntraLocation.LOWER_LEFT:
226+
_subNodes[(int)itemBoundsLocation].Remove(item);
227+
break;
228+
229+
// boundaries are spanning over 2 or more subnodes
230+
default:
231+
RemoveOwnItem(item);
232+
break;
233+
}
234+
}
235+
236+
/// <summary>
237+
/// Removes provided item (<paramref name="item"/>) from the node.
238+
/// </summary>
239+
///
240+
/// <param name="item">Item to be removed from the node</param>
241+
protected internal void RemoveOwnItem(TItem item)
242+
{
243+
// remove the item from the node
244+
_items.Remove(item);
245+
// update its parent node
246+
item.ParentNode = null;
247+
248+
if (IsEmpty())
249+
{
250+
// remove subnodes if subtree of this node is empty
251+
_subNodes.Clear();
252+
}
253+
}
254+
255+
/// <summary>
256+
/// Checks whether the node and recursively all its subnodes are empty.
257+
/// </summary>
258+
///
259+
/// <returns><c>True</c> if node and all its subnodes are empty, <c>False</c> otherwise</returns>
260+
public bool IsEmpty()
261+
{
262+
if (_items.Count > 0)
263+
return false;
264+
265+
return _subNodes.TrueForAll(node => node.IsEmpty());
266+
}
267+
268+
/// <summary>
269+
/// Updates provided item's (<paramref name="item"/>) location within the tree.
208270
/// </summary>
209271
///
210272
/// <param name="item">Item which's location is to be updated</param>
211273
/// <param name="forceInsertionEvaluation"><c>True</c> forces tree to re-insert the item</param>
212274
/// <param name="hasOriginallyContainedItem"><c>True</c> only for the first called node</param>
213-
protected internal void Update(TItem item, bool forceInsertionEvaluation = false, bool hasOriginallyContainedItem = true)
275+
protected internal void Update(TItem item, bool forceInsertionEvaluation = true, bool hasOriginallyContainedItem = true)
214276
{
215277
if (Contains(item.GetBounds()))
216278
{
@@ -264,50 +326,13 @@ protected internal void Update(TItem item, bool forceInsertionEvaluation = false
264326
}
265327

266328
/// <summary>
267-
/// Removes provided item from the subtree.
268-
/// </summary>
269-
///
270-
/// <param name="item">Item to be removed from the tree</param>
271-
public void Remove(TItem item)
272-
{
273-
274-
}
275-
276-
/// <summary>
277-
/// Removes provided item from the node.
278-
/// </summary>
279-
///
280-
/// <param name="item">Item to be removed from the node</param>
281-
protected internal void RemoveOwnItem(TItem item)
282-
{
283-
_items.Remove(item);
284-
285-
if (IsEmpty())
286-
{
287-
_subNodes.Clear();
288-
}
289-
}
290-
291-
/// <summary>
292-
/// Checks whether the node and recursively all its subnodes are empty.
293-
/// </summary>
294-
///
295-
/// <returns><c>True</c> if node and all its subnodes are empty, <c>False</c> otherwise</returns>
296-
public bool IsEmpty()
297-
{
298-
if (_items.Count > 0)
299-
return false;
300-
301-
return _subNodes.TrueForAll(node => node.IsEmpty());
302-
}
303-
304-
/// <summary>
305-
/// Splits current node to appropriate sub-nodes.
329+
/// Creates sub-nodes for the node.
306330
/// </summary>
307-
private void CreateSubNodes()
331+
protected internal void CreateSubNodes()
308332
{
309333
var subBoundsSize = Bounds.size * .5f;
310-
if (subBoundsSize.x < MinSize || subBoundsSize.z < MinSize)
334+
if (subBoundsSize.x < _root.NodeMinimumPossibleSize
335+
|| subBoundsSize.z < _root.NodeMinimumPossibleSize)
311336
{
312337
// new sub-node bounds are too small
313338
return;
@@ -345,7 +370,7 @@ private void CreateSubNodes()
345370
}
346371

347372
/// <summary>
348-
/// Finds items located within provided boundaries.
373+
/// Finds items (<paramref name="items"/>) located within provided boundaries (<paramref name="bounds"/>).
349374
/// </summary>
350375
///
351376
/// <param name="bounds">Boundaries to look for items within</param>
@@ -406,8 +431,8 @@ public void FindAndAddItems(Bounds bounds, ref List<TItem> items)
406431
}
407432

408433
/// <summary>
409-
/// Adds all items of this node and its sub-nodes to provided list of items.
410-
/// If additional boundaries are provided only items truly intersecting with them will be added.
434+
/// Adds all items of this node and its sub-nodes to the provided list of items (<paramref name="items"/>).
435+
/// If boundaries (<paramref name="bounds"/>) are provided then only items intersecting with them will be added.
411436
/// </summary>
412437
///
413438
/// <param name="items">Output list for found items</param>
@@ -419,8 +444,8 @@ public void AddItems(ref List<TItem> items, Bounds? bounds = null)
419444
}
420445

421446
/// <summary>
422-
/// Adds all items belonging to this node (ignoring sub-nodes).
423-
/// If additional boundaries are provided only items truly intersecting with them will be added.
447+
/// Adds all items belonging to this node (ignoring sub-nodes) to the provided list of items (<paramref name="items"/>).
448+
/// If boundaries (<paramref name="bounds"/>) are provided then only items intersecting with them will be added.
424449
/// </summary>
425450
///
426451
/// <param name="items">Output list for found items</param>
@@ -435,8 +460,8 @@ protected internal void AddOwnItems(ref List<TItem> items, Bounds? bounds = null
435460
}
436461

437462
/// <summary>
438-
/// Adds all items belonging to sub-nodes (ignoring own items).
439-
/// If additional boundaries are provided only items truly intersecting with them will be added.
463+
/// Adds all items belonging to sub-nodes (ignoring own items) to the provided list of items (<paramref name="items"/>).
464+
/// If boundaries (<paramref name="bounds"/>) are provided then only items intersecting with them will be added.
440465
/// </summary>
441466
///
442467
/// <param name="items">Output list for found items</param>
@@ -448,7 +473,7 @@ protected internal void AddSubNodeItems(ref List<TItem> items, Bounds? bounds =
448473
}
449474

450475
/// <summary>
451-
/// Removes any existing items or sub-nodes.
476+
/// Removes any existing items from the node and removes all of its sub-nodes.
452477
/// </summary>
453478
public void Clear()
454479
{
@@ -457,7 +482,7 @@ public void Clear()
457482
}
458483

459484
/// <summary>
460-
/// Displays boundaries of this node and all its sub-nodes.
485+
/// Displays boundaries of this node and all its sub-nodes and a current number of contained items if allowed.
461486
/// </summary>
462487
public void DrawBounds()
463488
{

Scripts/RootNode.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ protected void OnDrawGizmos()
2929
}
3030

3131
//==========================================================================dd==
32-
// CORE Quadtree METHODS
32+
// CORE ROOT NODE METHODS
3333
//==========================================================================dd==
3434

3535
/// <summary>
@@ -51,6 +51,15 @@ protected void OnDrawGizmos()
5151
[SerializeField]
5252
protected Vector3 DefaultRootNodeSize = new Vector3(64f, 0f, 64f);
5353

54+
/// <summary>
55+
/// Minimum possible size of any of the nodes.
56+
/// </summary>
57+
/// <remarks>
58+
/// Must always be a positive number or zero for no size limit.
59+
/// </remarks>
60+
[SerializeField]
61+
protected internal float NodeMinimumPossibleSize = 1f;
62+
5463
/// <summary>
5564
/// Determines whether or not should number of items in nodes be displayed in gizmos.
5665
/// </summary>
@@ -75,6 +84,7 @@ protected void Init()
7584
CurrentRootNode.Clear();
7685
}
7786

87+
// send a message to children that the tree root has been initialized
7888
BroadcastMessage("QuadTree_Root_Initialized", this);
7989
}
8090

@@ -102,14 +112,17 @@ public void Insert(TItem item)
102112
/// </summary>
103113
protected internal void Expand()
104114
{
115+
// the subnodes will be of the same size as current root node
105116
var subBoundsSize = CurrentRootNode.Bounds.size;
106117
var centerOffset = subBoundsSize * .5f;
107118

108119
// center if expanding to left
109120
var center = CurrentRootNode.Bounds.min;
110121
if (ExpansionRight)
122+
{
111123
// center if expanding to right
112124
center = CurrentRootNode.Bounds.max;
125+
}
113126

114127
var subNodes = new List<Node<TItem>>(4);
115128
var newRootNode = new Node<TItem>(this, null, center, subBoundsSize * 2f, subNodes);
@@ -141,7 +154,7 @@ protected internal void Expand()
141154

142155
// assign new root node
143156
CurrentRootNode = newRootNode;
144-
// toggle expansion side
157+
// toggle expansion side for next expansion
145158
ExpansionRight = !ExpansionRight;
146159
}
147160

0 commit comments

Comments
 (0)