Skip to content

Commit 976b84d

Browse files
committed
Update and fix tests
1 parent 6cc7593 commit 976b84d

File tree

3 files changed

+54
-25
lines changed

3 files changed

+54
-25
lines changed

lib/src/collisions/quadtree.dart

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,27 @@ extension type QuadTree$QueryResult._(Float32List _bytes) {
8383
/// The QuadTree can store objects with a width, height, and position.
8484
/// Positions are represented as a point (x, y) in the 2D space at the top-left
8585
/// corner of the object.
86+
///
87+
/// The QuadTree is a tree data structure in which each internal node has
88+
/// exactly four children: North-West, North-East, South-West, and South-East.
89+
/// Each node represents a rectangular region of the space.
90+
///
91+
/// The QuadTree is a spatial partitioning algorithm that is used to subdivide
92+
/// a two-dimensional space into smaller regions for efficient collision
93+
/// detection and spatial queries.
94+
///
95+
/// [boundary] is the boundary of the QuadTree, usually the size of the game
96+
/// world coordinates or the screen size.
97+
///
98+
/// [capacity] is the maximum number of objects that can be stored in a node
99+
/// before it subdivides.
100+
/// Suitable values for the capacity are usually between 18 and 24.
101+
/// Should be always greater or equal than 6.
102+
///
103+
/// [depth] is the maximum depth of the QuadTree. If the depth is reached,
104+
/// the QuadTree will not subdivide further and [capacity] will be ignored.
105+
/// Suitable values for the depth are usually between 8 and 16.
106+
/// Should be always greater or equal than 1.
86107
/// {@endtemplate}
87108
final class QuadTree {
88109
/// Creates a new Quadtree with [boundary] and a [capacity].
@@ -92,9 +113,9 @@ final class QuadTree {
92113
// Boundary of the QuadTree.
93114
required ui.Rect boundary,
94115
// Capacity of the each QuadTree node.
95-
int capacity = 18,
116+
int capacity = 24,
96117
// Maximum depth of the QuadTree.
97-
int depth = 8,
118+
int depth = 12,
98119
}) {
99120
assert(boundary.isFinite, 'The boundary must be finite.');
100121
assert(!boundary.isEmpty, 'The boundary must not be empty.');
@@ -271,7 +292,7 @@ final class QuadTree {
271292
/// Get rectangle bounds of the object with the given [objectId].
272293
ui.Rect get(int objectId) {
273294
final objects = _objects;
274-
if (objectId < 0 || objectId >= _nextObjectId || objectId >= objects.length)
295+
if (objectId < 0 || objectId >= objects.length)
275296
throw ArgumentError('Object with id $objectId not found.');
276297
final offset = objectId * _objectSize;
277298
return ui.Rect.fromLTWH(
@@ -318,6 +339,14 @@ final class QuadTree {
318339
// Do not change the object's id.
319340
node._remove(objectId);
320341

342+
// Mark the node and all its parents as dirty
343+
// and possibly needs optimization.
344+
// Also decrease the length of the node and all its parents.
345+
for (QuadTree$Node? n = node; n != null; n = n.parent) {
346+
n._dirty = true;
347+
n._length--;
348+
}
349+
321350
// Insert the object back into the QuadTree at the new position
322351
// with the same id.
323352
final nodeId = root._insert(objectId, left, top, width, height);
@@ -328,9 +357,7 @@ final class QuadTree {
328357
/// Removes [objectId] from the Quadtree if it exists.
329358
/// After removal, tries merging nodes upward if possible.
330359
bool remove(int objectId) {
331-
if (objectId < 0 ||
332-
objectId >= _nextObjectId ||
333-
objectId >= _id2node.length) return false; // Invalid id
360+
if (objectId < 0 || objectId >= _id2node.length) return false; // Invalid id
334361
final node = _nodes[_id2node[objectId]];
335362
if (node == null) return false; // Node not found
336363

test/benchmark/quadtree_benchmark_test.dart

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ void main() => group(
106106
});
107107

108108
/*
109-
RePaint QuadTree move(RunTime): 199.9547 us.
110-
Flame QuadTree move(RunTime): 306.89086614173226 us.
109+
RePaint QuadTree move(RunTime): 121.95089894606323 us.
110+
Flame QuadTree move(RunTime): 310.9901587301587 us.
111111
*/
112112
test('Move', () {
113113
final repaint = _RePaintQuadTreeMoveBenchmark();
@@ -127,19 +127,21 @@ void main() => group(
127127
});
128128

129129
/*
130-
6: 4797.539325842697 us. Max depth 10 nodes
131-
8: 4889.759550561797 us. Max depth 9 nodes
132-
10: 3929.9825174825173 us. Max depth 8 nodes
133-
12: 3874.2534965034965 us. Max depth 8 nodes
134-
14: 3927.8164335664337 us. Max depth 7 nodes
135-
16: 3682.304195804196 us. Max depth 7 nodes
136-
18: 3289.215892053973 us. Max depth 7 nodes
137-
20: 3391.6356821589206 us. Max depth 7 nodes
138-
22: 3264.529235382309 us. Max depth 6 nodes
139-
24: 3314.206896551724 us. Max depth 6 nodes
140-
26: 3485.5034965034965 us. Max depth 6 nodes
141-
28: 3630.701048951049 us. Max depth 6 nodes
142-
30: 3602.9562937062938 us. Max depth 6 nodes
130+
18..24 - Best capacity
131+
132+
6: 4128.678 us. Max depth 8 nodes
133+
8: 4361.878 us. Max depth 8 nodes
134+
10: 3336.8653846153848 us. Max depth 8 nodes
135+
12: 3393.3898050974512 us. Max depth 8 nodes
136+
14: 3488.1083916083917 us. Max depth 7 nodes
137+
16: 3500.551724137931 us. Max depth 7 nodes
138+
18: 2740.64125 us. Max depth 7 nodes
139+
20: 2738.4475 us. Max depth 7 nodes
140+
22: 2809.03625 us. Max depth 6 nodes
141+
24: 2790.7125 us. Max depth 6 nodes
142+
26: 2818.23 us. Max depth 6 nodes
143+
28: 2896.32125 us. Max depth 6 nodes
144+
30: 2836.28125 us. Max depth 6 nodes
143145
*/
144146
test('Capacity', () {
145147
final results = <int, String>{};

test/unit/quadtree_test.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ void main() => group('Quadtree', () {
106106
);
107107
final id = qt.insert(const ui.Rect.fromLTWH(10, 10, 10, 10));
108108
expect(id, isNotNull);
109-
id!;
110109
expect(() => qt.move(id, 10, 10), returnsNormally);
111110
expect(() => qt.move(id, 20, 20), returnsNormally);
112111
expect(qt.length, equals(1));
@@ -120,7 +119,6 @@ void main() => group('Quadtree', () {
120119
final id = qt.insert(const ui.Rect.fromLTWH(10, 10, 10, 10));
121120
expect(id, isNotNull);
122121
expect(qt.length, equals(1));
123-
id!;
124122
expect(() => qt.remove(id), returnsNormally);
125123
expect(qt.length, equals(0));
126124
});
@@ -193,6 +191,7 @@ void main() => group('Quadtree', () {
193191
returnsNormally,
194192
);
195193
}
194+
expect(qt.length, equals(10));
196195
expect(
197196
qt.query(const ui.Rect.fromLTWH(5, 5, 1000, 1000)).length,
198197
equals(10),
@@ -202,15 +201,16 @@ void main() => group('Quadtree', () {
202201
qt.query(const ui.Rect.fromLTWH(5, 5, 1000, 1000)).length,
203202
equals(10),
204203
);
205-
expect(() => qt.remove(10), returnsNormally);
204+
expect(qt.length, equals(10));
205+
expect(() => qt.remove(9), returnsNormally);
206206
expect(() => qt.remove(5), returnsNormally);
207207
expect(qt.length, equals(8));
208208
expect(
209209
qt.queryIds(const ui.Rect.fromLTWH(-10000, -10000, 20000, 20000)),
210210
allOf(
211211
isA<List<int>>(),
212212
hasLength(8),
213-
containsAll([1, 2, 3, 4, 6, 7, 8, 9]),
213+
containsAll([0, 1, 2, 3, 4, 6, 7, 8]),
214214
),
215215
);
216216
expect(qt.optimize, returnsNormally);

0 commit comments

Comments
 (0)