mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-02 03:15:43 +08:00
perf!: Pool CollisionProspects and remove some list creations from the collision detection (#2625)
This change introduces a very simple pool for `CollisionProspect`s so that those objects don't have to be re-created each tick. It means that the `CollisionProspect` needs to be mutable though, so the code becomes a little bit harder to read since sets can't be used anymore.
This commit is contained in:
@ -4,7 +4,7 @@
|
|||||||
# This file should be version controlled.
|
# This file should be version controlled.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
@ -13,26 +13,11 @@ project_type: app
|
|||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||||
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||||
- platform: android
|
- platform: android
|
||||||
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||||
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||||
- platform: ios
|
|
||||||
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
|
||||||
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
|
||||||
- platform: linux
|
|
||||||
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
|
||||||
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
|
||||||
- platform: macos
|
|
||||||
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
|
||||||
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
|
||||||
- platform: web
|
|
||||||
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
|
||||||
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
|
||||||
- platform: windows
|
|
||||||
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
|
||||||
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
|
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
import 'package:examples/main.dart' as examples;
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('main', examples.main);
|
|
||||||
}
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
export 'src/collisions/broadphase/broadphase.dart';
|
export 'src/collisions/broadphase/broadphase.dart';
|
||||||
|
export 'src/collisions/broadphase/prospect_pool.dart';
|
||||||
export 'src/collisions/broadphase/quadtree/has_quadtree_collision_detection.dart';
|
export 'src/collisions/broadphase/quadtree/has_quadtree_collision_detection.dart';
|
||||||
export 'src/collisions/broadphase/quadtree/quad_tree_broadphase.dart';
|
export 'src/collisions/broadphase/quadtree/quad_tree_broadphase.dart';
|
||||||
export 'src/collisions/broadphase/quadtree/quadtree.dart';
|
export 'src/collisions/broadphase/quadtree/quadtree.dart';
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import 'package:flame/collisions.dart';
|
import 'package:flame/collisions.dart';
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
|
|
||||||
/// The [Broadphase] class is used to make collision detection more efficient
|
/// The [Broadphase] class is used to make collision detection more efficient
|
||||||
/// by doing a rough estimation of which hitboxes that can collide before their
|
/// by doing a rough estimation of which hitboxes that can collide before their
|
||||||
@ -32,33 +31,56 @@ abstract class Broadphase<T extends Hitbox<T>> {
|
|||||||
/// detection system.
|
/// detection system.
|
||||||
void add(T item);
|
void add(T item);
|
||||||
|
|
||||||
void addAll(Iterable<T> items) => items.forEach(add);
|
void addAll(Iterable<T> items) {
|
||||||
|
for (final item in items) {
|
||||||
|
add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes an item from the broadphase. Should be called in a
|
/// Removes an item from the broadphase. Should be called in a
|
||||||
/// [CollisionDetection] class while removing a hitbox from its collision
|
/// [CollisionDetection] class while removing a hitbox from its collision
|
||||||
/// detection system.
|
/// detection system.
|
||||||
void remove(T item);
|
void remove(T item);
|
||||||
|
|
||||||
void removeAll(Iterable<T> items) => items.forEach(remove);
|
void removeAll(Iterable<T> items) {
|
||||||
|
for (final item in items) {
|
||||||
|
remove(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the potential hitbox collisions
|
/// Returns the potential hitbox collisions
|
||||||
Set<CollisionProspect<T>> query();
|
Iterable<CollisionProspect<T>> query();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [CollisionProspect] is a tuple that is used to contain two potentially
|
/// A [CollisionProspect] is a tuple that is used to contain two potentially
|
||||||
/// colliding hitboxes.
|
/// colliding hitboxes.
|
||||||
@immutable
|
|
||||||
class CollisionProspect<T> {
|
class CollisionProspect<T> {
|
||||||
final T a;
|
T _a;
|
||||||
final T b;
|
T _b;
|
||||||
|
|
||||||
const CollisionProspect(this.a, this.b);
|
T get a => _a;
|
||||||
|
T get b => _b;
|
||||||
|
|
||||||
@override
|
int get hash => _hash;
|
||||||
bool operator ==(Object other) =>
|
int _hash;
|
||||||
other is CollisionProspect &&
|
|
||||||
((other.a == a && other.b == b) || (other.a == b && other.b == a));
|
|
||||||
|
|
||||||
@override
|
CollisionProspect(this._a, this._b) : _hash = _a.hashCode ^ _b.hashCode;
|
||||||
int get hashCode => Object.hashAllUnordered([a, b]);
|
|
||||||
|
/// Sets the prospect to contain [a] and [b] instead of what it previously
|
||||||
|
/// contained.
|
||||||
|
void set(T a, T b) {
|
||||||
|
_a = a;
|
||||||
|
_b = b;
|
||||||
|
_hash = a.hashCode ^ b.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the prospect to contain the content of [other].
|
||||||
|
void setFrom(CollisionProspect<T> other) {
|
||||||
|
_a = other._a;
|
||||||
|
_b = other._b;
|
||||||
|
_hash = other._hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new prospect object with the same content.
|
||||||
|
CollisionProspect<T> clone() => CollisionProspect(_a, _b);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flame/src/collisions/broadphase/broadphase.dart';
|
||||||
|
import 'package:flame/src/collisions/hitboxes/hitbox.dart';
|
||||||
|
|
||||||
|
/// This pool is used to not create unnecessary [CollisionProspect] objects
|
||||||
|
/// during collision detection, but to re-use the ones that have already been
|
||||||
|
/// created.
|
||||||
|
class ProspectPool<T extends Hitbox<T>> {
|
||||||
|
ProspectPool({this.incrementSize = 1000});
|
||||||
|
|
||||||
|
/// How much the pool should increase in size every time it needs to be made
|
||||||
|
/// larger.
|
||||||
|
final int incrementSize;
|
||||||
|
final _storage = <CollisionProspect<T>>[];
|
||||||
|
int get length => _storage.length;
|
||||||
|
|
||||||
|
/// The size of the pool will expand with [incrementSize] amount of
|
||||||
|
/// [CollisionProspect]s that are initially populated with two [dummyItem]s.
|
||||||
|
void expand(T dummyItem) {
|
||||||
|
for (var i = 0; i < incrementSize; i++) {
|
||||||
|
_storage.add(CollisionProspect<T>(dummyItem, dummyItem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionProspect<T> operator [](int index) => _storage[index];
|
||||||
|
}
|
||||||
@ -16,7 +16,7 @@ import 'package:flame/game.dart';
|
|||||||
/// [initializeCollisionDetection] should be called in the game's [onLoad]
|
/// [initializeCollisionDetection] should be called in the game's [onLoad]
|
||||||
/// method.
|
/// method.
|
||||||
mixin HasQuadTreeCollisionDetection on FlameGame
|
mixin HasQuadTreeCollisionDetection on FlameGame
|
||||||
implements HasCollisionDetection<QuadTreeBroadphase<ShapeHitbox>> {
|
implements HasCollisionDetection<QuadTreeBroadphase> {
|
||||||
late QuadTreeCollisionDetection _collisionDetection;
|
late QuadTreeCollisionDetection _collisionDetection;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -24,7 +24,7 @@ mixin HasQuadTreeCollisionDetection on FlameGame
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
set collisionDetection(
|
set collisionDetection(
|
||||||
CollisionDetection<ShapeHitbox, QuadTreeBroadphase<ShapeHitbox>> cd,
|
CollisionDetection<ShapeHitbox, QuadTreeBroadphase> cd,
|
||||||
) {
|
) {
|
||||||
if (cd is! QuadTreeCollisionDetection) {
|
if (cd is! QuadTreeCollisionDetection) {
|
||||||
throw 'Must be QuadTreeCollisionDetection!';
|
throw 'Must be QuadTreeCollisionDetection!';
|
||||||
|
|||||||
@ -17,44 +17,43 @@ typedef ExternalMinDistanceCheck = bool Function(
|
|||||||
///
|
///
|
||||||
/// See [HasQuadTreeCollisionDetection.initializeCollisionDetection] for a
|
/// See [HasQuadTreeCollisionDetection.initializeCollisionDetection] for a
|
||||||
/// detailed description of its initialization parameters.
|
/// detailed description of its initialization parameters.
|
||||||
class QuadTreeBroadphase<T extends Hitbox<T>> extends Broadphase<T> {
|
class QuadTreeBroadphase extends Broadphase<ShapeHitbox> {
|
||||||
QuadTreeBroadphase({
|
QuadTreeBroadphase({
|
||||||
required Rect mainBoxSize,
|
required Rect mainBoxSize,
|
||||||
required this.broadphaseCheck,
|
required this.broadphaseCheck,
|
||||||
required this.minimumDistanceCheck,
|
required this.minimumDistanceCheck,
|
||||||
int maxObjects = 25,
|
int maxObjects = 25,
|
||||||
int maxDepth = 10,
|
int maxDepth = 10,
|
||||||
}) : tree = QuadTree<T>(
|
}) : tree = QuadTree<ShapeHitbox>(
|
||||||
mainBoxSize: mainBoxSize,
|
mainBoxSize: mainBoxSize,
|
||||||
maxObjects: maxObjects,
|
maxObjects: maxObjects,
|
||||||
maxDepth: maxDepth,
|
maxDepth: maxDepth,
|
||||||
);
|
);
|
||||||
|
|
||||||
final QuadTree<T> tree;
|
final QuadTree<ShapeHitbox> tree;
|
||||||
|
|
||||||
final activeCollisions = HashSet<T>();
|
final activeHitboxes = HashSet<ShapeHitbox>();
|
||||||
|
|
||||||
ExternalBroadphaseCheck broadphaseCheck;
|
ExternalBroadphaseCheck broadphaseCheck;
|
||||||
ExternalMinDistanceCheck minimumDistanceCheck;
|
ExternalMinDistanceCheck minimumDistanceCheck;
|
||||||
final _broadphaseCheckCache = <T, Map<T, bool>>{};
|
final _broadphaseCheckCache = <ShapeHitbox, Map<ShapeHitbox, bool>>{};
|
||||||
|
|
||||||
final _cachedCenters = <ShapeHitbox, Vector2>{};
|
final _cachedCenters = <ShapeHitbox, Vector2>{};
|
||||||
|
|
||||||
final _potentials = HashSet<CollisionProspect<T>>();
|
final _potentials = <int, CollisionProspect<ShapeHitbox>>{};
|
||||||
final _potentialsTmp = <List<ShapeHitbox>>[];
|
final _potentialsTmp = <ShapeHitbox>[];
|
||||||
|
final _prospectPool = ProspectPool<ShapeHitbox>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<T> get items => tree.hitboxes;
|
List<ShapeHitbox> get items => tree.hitboxes;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
HashSet<CollisionProspect<T>> query() {
|
Iterable<CollisionProspect<ShapeHitbox>> query() {
|
||||||
_potentials.clear();
|
_potentials.clear();
|
||||||
_potentialsTmp.clear();
|
_potentialsTmp.clear();
|
||||||
|
|
||||||
for (final activeItem in activeCollisions) {
|
for (final activeItem in activeHitboxes) {
|
||||||
final asShapeItem = activeItem as ShapeHitbox;
|
if (activeItem.isRemoving || !activeItem.isMounted) {
|
||||||
|
|
||||||
if (asShapeItem.isRemoving || asShapeItem.parent == null) {
|
|
||||||
tree.remove(activeItem);
|
tree.remove(activeItem);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -70,63 +69,69 @@ class QuadTreeBroadphase<T extends Hitbox<T>> extends Broadphase<T> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final asShapePotential = potential as ShapeHitbox;
|
if (!potential.allowSiblingCollision &&
|
||||||
|
potential.hitboxParent == activeItem.hitboxParent &&
|
||||||
if (asShapePotential.parent == asShapeItem.parent &&
|
potential.isMounted) {
|
||||||
asShapeItem.parent != null) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final distanceCloseEnough = minimumDistanceCheck.call(
|
final distanceCloseEnough = minimumDistanceCheck.call(
|
||||||
itemCenter,
|
itemCenter,
|
||||||
_cacheCenterOfHitbox(asShapePotential),
|
_cacheCenterOfHitbox(potential),
|
||||||
);
|
);
|
||||||
if (distanceCloseEnough == false) {
|
if (distanceCloseEnough == false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_potentialsTmp.add([asShapeItem, asShapePotential]);
|
_potentialsTmp
|
||||||
|
..add(activeItem)
|
||||||
|
..add(potential);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_potentialsTmp.isNotEmpty) {
|
if (_potentialsTmp.isNotEmpty) {
|
||||||
for (var i = 0; i < _potentialsTmp.length; i++) {
|
for (var i = 0; i < _potentialsTmp.length; i += 2) {
|
||||||
final item0 = _potentialsTmp[i].first;
|
final item0 = _potentialsTmp[i];
|
||||||
final item1 = _potentialsTmp[i].last;
|
final item1 = _potentialsTmp[i + 1];
|
||||||
if (broadphaseCheck(item0, item1)) {
|
if (broadphaseCheck(item0, item1)) {
|
||||||
_potentials.add(CollisionProspect(item0 as T, item1 as T));
|
final CollisionProspect<ShapeHitbox> prospect;
|
||||||
} else {
|
if (_prospectPool.length <= i) {
|
||||||
if (_broadphaseCheckCache[item0 as T] == null) {
|
_prospectPool.expand(item0);
|
||||||
_broadphaseCheckCache[item0 as T] = {};
|
|
||||||
}
|
}
|
||||||
_broadphaseCheckCache[item0 as T]![item1 as T] = false;
|
prospect = _prospectPool[i]..set(item0, item1);
|
||||||
|
_potentials[prospect.hash] = prospect;
|
||||||
|
} else {
|
||||||
|
if (_broadphaseCheckCache[item0] == null) {
|
||||||
|
_broadphaseCheckCache[item0] = {};
|
||||||
|
}
|
||||||
|
_broadphaseCheckCache[item0]![item1] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _potentials;
|
return _potentials.values;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateTransform(T item) {
|
void updateTransform(ShapeHitbox item) {
|
||||||
tree.remove(item, keepOldPosition: true);
|
tree.remove(item, keepOldPosition: true);
|
||||||
_cacheCenterOfHitbox(item as ShapeHitbox);
|
_cacheCenterOfHitbox(item);
|
||||||
tree.add(item);
|
tree.add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void add(T item) {
|
void add(ShapeHitbox item) {
|
||||||
tree.add(item);
|
tree.add(item);
|
||||||
if (item.collisionType == CollisionType.active) {
|
if (item.collisionType == CollisionType.active) {
|
||||||
activeCollisions.add(item);
|
activeHitboxes.add(item);
|
||||||
}
|
}
|
||||||
_cacheCenterOfHitbox(item as ShapeHitbox);
|
_cacheCenterOfHitbox(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void remove(T item) {
|
void remove(ShapeHitbox item) {
|
||||||
tree.remove(item);
|
tree.remove(item);
|
||||||
_cachedCenters.remove(item);
|
_cachedCenters.remove(item);
|
||||||
if (item.collisionType == CollisionType.active) {
|
if (item.collisionType == CollisionType.active) {
|
||||||
activeCollisions.remove(item);
|
activeHitboxes.remove(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
final checkCache = _broadphaseCheckCache[item];
|
final checkCache = _broadphaseCheckCache[item];
|
||||||
@ -140,7 +145,7 @@ class QuadTreeBroadphase<T extends Hitbox<T>> extends Broadphase<T> {
|
|||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
tree.clear();
|
tree.clear();
|
||||||
activeCollisions.clear();
|
activeHitboxes.clear();
|
||||||
_broadphaseCheckCache.clear();
|
_broadphaseCheckCache.clear();
|
||||||
_cachedCenters.clear();
|
_cachedCenters.clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
/// Do not use standard [items] list for components. Instead adds all components
|
/// Do not use standard [items] list for components. Instead adds all components
|
||||||
/// into [QuadTreeBroadphase] class.
|
/// into [QuadTreeBroadphase] class.
|
||||||
class QuadTreeCollisionDetection
|
class QuadTreeCollisionDetection
|
||||||
extends StandardCollisionDetection<QuadTreeBroadphase<ShapeHitbox>> {
|
extends StandardCollisionDetection<QuadTreeBroadphase> {
|
||||||
QuadTreeCollisionDetection({
|
QuadTreeCollisionDetection({
|
||||||
required Rect mapDimensions,
|
required Rect mapDimensions,
|
||||||
required ExternalBroadphaseCheck onComponentTypeCheck,
|
required ExternalBroadphaseCheck onComponentTypeCheck,
|
||||||
@ -14,7 +14,7 @@ class QuadTreeCollisionDetection
|
|||||||
int maxObjects = 25,
|
int maxObjects = 25,
|
||||||
int maxDepth = 10,
|
int maxDepth = 10,
|
||||||
}) : super(
|
}) : super(
|
||||||
broadphase: QuadTreeBroadphase<ShapeHitbox>(
|
broadphase: QuadTreeBroadphase(
|
||||||
mainBoxSize: mapDimensions,
|
mainBoxSize: mapDimensions,
|
||||||
maxObjects: maxObjects,
|
maxObjects: maxObjects,
|
||||||
maxDepth: maxDepth,
|
maxDepth: maxDepth,
|
||||||
@ -29,16 +29,16 @@ class QuadTreeCollisionDetection
|
|||||||
@override
|
@override
|
||||||
void add(ShapeHitbox item) {
|
void add(ShapeHitbox item) {
|
||||||
item.onAabbChanged = () => _scheduledUpdate.add(item);
|
item.onAabbChanged = () => _scheduledUpdate.add(item);
|
||||||
// ignore: prefer_function_declarations_over_variables
|
void listenerCollisionType() {
|
||||||
final listenerCollisionType = () {
|
|
||||||
if (item.isMounted) {
|
if (item.isMounted) {
|
||||||
if (item.collisionType == CollisionType.active) {
|
if (item.collisionType == CollisionType.active) {
|
||||||
broadphase.activeCollisions.add(item);
|
broadphase.activeHitboxes.add(item);
|
||||||
} else {
|
} else {
|
||||||
broadphase.activeCollisions.remove(item);
|
broadphase.activeHitboxes.remove(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
item.collisionTypeNotifier.addListener(listenerCollisionType);
|
item.collisionTypeNotifier.addListener(listenerCollisionType);
|
||||||
_listenerCollisionType[item] = listenerCollisionType;
|
_listenerCollisionType[item] = listenerCollisionType;
|
||||||
|
|
||||||
@ -47,7 +47,9 @@ class QuadTreeCollisionDetection
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void addAll(Iterable<ShapeHitbox> items) {
|
void addAll(Iterable<ShapeHitbox> items) {
|
||||||
items.forEach(add);
|
for (final item in items) {
|
||||||
|
add(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -65,14 +67,16 @@ class QuadTreeCollisionDetection
|
|||||||
@override
|
@override
|
||||||
void removeAll(Iterable<ShapeHitbox> items) {
|
void removeAll(Iterable<ShapeHitbox> items) {
|
||||||
broadphase.clear();
|
broadphase.clear();
|
||||||
items.forEach(remove);
|
for (final item in items) {
|
||||||
|
remove(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void run() {
|
void run() {
|
||||||
_scheduledUpdate.forEach(
|
for (final hitbox in _scheduledUpdate) {
|
||||||
broadphase.updateTransform,
|
broadphase.updateTransform(hitbox);
|
||||||
);
|
}
|
||||||
_scheduledUpdate.clear();
|
_scheduledUpdate.clear();
|
||||||
super.run();
|
super.run();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,9 @@ class Sweep<T extends Hitbox<T>> extends Broadphase<T> {
|
|||||||
@override
|
@override
|
||||||
final List<T> items;
|
final List<T> items;
|
||||||
|
|
||||||
late final List<T> _active = [];
|
final _active = <T>[];
|
||||||
late final Set<CollisionProspect<T>> _potentials = {};
|
final _potentials = <int, CollisionProspect<T>>{};
|
||||||
|
final _prospectPool = ProspectPool<T>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void add(T item) => items.add(item);
|
void add(T item) => items.add(item);
|
||||||
@ -21,9 +22,10 @@ class Sweep<T extends Hitbox<T>> extends Broadphase<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<CollisionProspect<T>> query() {
|
Iterable<CollisionProspect<T>> query() {
|
||||||
_active.clear();
|
_active.clear();
|
||||||
_potentials.clear();
|
_potentials.clear();
|
||||||
|
|
||||||
for (final item in items) {
|
for (final item in items) {
|
||||||
if (item.collisionType == CollisionType.inactive) {
|
if (item.collisionType == CollisionType.inactive) {
|
||||||
continue;
|
continue;
|
||||||
@ -40,7 +42,12 @@ class Sweep<T extends Hitbox<T>> extends Broadphase<T> {
|
|||||||
if (activeBox.max.x >= currentMin) {
|
if (activeBox.max.x >= currentMin) {
|
||||||
if (item.collisionType == CollisionType.active ||
|
if (item.collisionType == CollisionType.active ||
|
||||||
activeItem.collisionType == CollisionType.active) {
|
activeItem.collisionType == CollisionType.active) {
|
||||||
_potentials.add(CollisionProspect<T>(item, activeItem));
|
if (_prospectPool.length <= _potentials.length) {
|
||||||
|
_prospectPool.expand(item);
|
||||||
|
}
|
||||||
|
final prospect = _prospectPool[_potentials.length]
|
||||||
|
..set(item, activeItem);
|
||||||
|
_potentials[prospect.hash] = prospect;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_active.remove(activeItem);
|
_active.remove(activeItem);
|
||||||
@ -48,6 +55,6 @@ class Sweep<T extends Hitbox<T>> extends Broadphase<T> {
|
|||||||
}
|
}
|
||||||
_active.add(item);
|
_active.add(item);
|
||||||
}
|
}
|
||||||
return _potentials;
|
return _potentials.values;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ abstract class CollisionDetection<T extends Hitbox<T>,
|
|||||||
final B broadphase;
|
final B broadphase;
|
||||||
|
|
||||||
List<T> get items => broadphase.items;
|
List<T> get items => broadphase.items;
|
||||||
final Set<CollisionProspect<T>> _lastPotentials = {};
|
final _lastPotentials = <CollisionProspect<T>>[];
|
||||||
|
|
||||||
CollisionDetection({required this.broadphase});
|
CollisionDetection({required this.broadphase});
|
||||||
|
|
||||||
@ -32,9 +32,11 @@ abstract class CollisionDetection<T extends Hitbox<T>,
|
|||||||
void run() {
|
void run() {
|
||||||
broadphase.update();
|
broadphase.update();
|
||||||
final potentials = broadphase.query();
|
final potentials = broadphase.query();
|
||||||
potentials.forEach((tuple) {
|
final hashes = Set.unmodifiable(potentials.map((p) => p.hash));
|
||||||
final itemA = tuple.a;
|
|
||||||
final itemB = tuple.b;
|
for (final potential in potentials) {
|
||||||
|
final itemA = potential.a;
|
||||||
|
final itemB = potential.b;
|
||||||
|
|
||||||
if (itemA.possiblyIntersects(itemB)) {
|
if (itemA.possiblyIntersects(itemB)) {
|
||||||
final intersectionPoints = intersections(itemA, itemB);
|
final intersectionPoints = intersections(itemA, itemB);
|
||||||
@ -49,18 +51,33 @@ abstract class CollisionDetection<T extends Hitbox<T>,
|
|||||||
} else if (itemA.collidingWith(itemB)) {
|
} else if (itemA.collidingWith(itemB)) {
|
||||||
handleCollisionEnd(itemA, itemB);
|
handleCollisionEnd(itemA, itemB);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// Handles callbacks for an ended collision that the broadphase didn't
|
// Handles callbacks for an ended collision that the broadphase didn't
|
||||||
// reports as a potential collision anymore.
|
// report as a potential collision anymore.
|
||||||
_lastPotentials.difference(potentials).forEach((tuple) {
|
for (final prospect in _lastPotentials) {
|
||||||
if (tuple.a.collidingWith(tuple.b)) {
|
if (!hashes.contains(prospect.hash) &&
|
||||||
handleCollisionEnd(tuple.a, tuple.b);
|
prospect.a.collidingWith(prospect.b)) {
|
||||||
|
handleCollisionEnd(prospect.a, prospect.b);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
_lastPotentials
|
_updateLastPotentials(potentials);
|
||||||
..clear()
|
}
|
||||||
..addAll(potentials);
|
|
||||||
|
final _lastPotentialsPool = <CollisionProspect<T>>[];
|
||||||
|
void _updateLastPotentials(Iterable<CollisionProspect<T>> potentials) {
|
||||||
|
_lastPotentials.clear();
|
||||||
|
for (final potential in potentials) {
|
||||||
|
final CollisionProspect<T> lastPotential;
|
||||||
|
if (_lastPotentialsPool.length > _lastPotentials.length) {
|
||||||
|
lastPotential = _lastPotentialsPool[_lastPotentials.length]
|
||||||
|
..setFrom(potential);
|
||||||
|
} else {
|
||||||
|
lastPotential = potential.clone();
|
||||||
|
_lastPotentialsPool.add(lastPotential);
|
||||||
|
}
|
||||||
|
_lastPotentials.add(lastPotential);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check what the intersection points of two items are,
|
/// Check what the intersection points of two items are,
|
||||||
|
|||||||
@ -405,14 +405,16 @@ void main() {
|
|||||||
},
|
},
|
||||||
'component collision callbacks are not called with hitbox '
|
'component collision callbacks are not called with hitbox '
|
||||||
'triggersParentCollision option': (game) async {
|
'triggersParentCollision option': (game) async {
|
||||||
final utilityHitboxA = TestHitbox()..triggersParentCollision = false;
|
final utilityHitboxA = TestHitbox('hitboxA')
|
||||||
|
..triggersParentCollision = false;
|
||||||
final blockA = TestBlock(
|
final blockA = TestBlock(
|
||||||
Vector2.all(10),
|
Vector2.all(10),
|
||||||
Vector2.all(10),
|
Vector2.all(10),
|
||||||
);
|
);
|
||||||
blockA.add(utilityHitboxA);
|
blockA.add(utilityHitboxA);
|
||||||
|
|
||||||
final utilityHitboxB = TestHitbox()..triggersParentCollision = false;
|
final utilityHitboxB = TestHitbox('hitboxB')
|
||||||
|
..triggersParentCollision = false;
|
||||||
final blockB = TestBlock(
|
final blockB = TestBlock(
|
||||||
Vector2.all(15),
|
Vector2.all(15),
|
||||||
Vector2.all(10),
|
Vector2.all(10),
|
||||||
|
|||||||
@ -53,8 +53,9 @@ class TestHitbox extends RectangleHitbox {
|
|||||||
int startCounter = 0;
|
int startCounter = 0;
|
||||||
int onCollisionCounter = 0;
|
int onCollisionCounter = 0;
|
||||||
int endCounter = 0;
|
int endCounter = 0;
|
||||||
|
String? name;
|
||||||
|
|
||||||
TestHitbox() {
|
TestHitbox([this.name]) {
|
||||||
onCollisionCallback = (_, __) {
|
onCollisionCallback = (_, __) {
|
||||||
onCollisionCounter++;
|
onCollisionCounter++;
|
||||||
};
|
};
|
||||||
@ -65,6 +66,13 @@ class TestHitbox extends RectangleHitbox {
|
|||||||
endCounter++;
|
endCounter++;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return name == null
|
||||||
|
? '_TestHitbox[${identityHashCode(this)}]'
|
||||||
|
: '_TestHitbox[$name]';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CompositeTestHitbox extends CompositeHitbox {
|
class CompositeTestHitbox extends CompositeHitbox {
|
||||||
|
|||||||
@ -104,7 +104,6 @@ linter:
|
|||||||
- prefer_final_in_for_each
|
- prefer_final_in_for_each
|
||||||
- prefer_final_locals
|
- prefer_final_locals
|
||||||
- prefer_for_elements_to_map_fromIterable
|
- prefer_for_elements_to_map_fromIterable
|
||||||
- prefer_foreach
|
|
||||||
- prefer_function_declarations_over_variables
|
- prefer_function_declarations_over_variables
|
||||||
- prefer_generic_function_type_aliases
|
- prefer_generic_function_type_aliases
|
||||||
- prefer_if_elements_to_conditional_expressions
|
- prefer_if_elements_to_conditional_expressions
|
||||||
|
|||||||
Reference in New Issue
Block a user