mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-14 03:40:21 +08:00
685 lines
20 KiB
Dart
685 lines
20 KiB
Dart
import 'package:flame/geometry.dart';
|
|
import 'package:flame/extensions.dart';
|
|
import 'package:flame/geometry.dart' as geometry;
|
|
import 'package:flame/src/geometry/circle.dart';
|
|
import 'package:flame/src/geometry/line_segment.dart';
|
|
import 'package:flame/src/geometry/line.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
group('LineSegment.isPointOnSegment tests', () {
|
|
test('Can catch simple point', () {
|
|
final segment = LineSegment(
|
|
Vector2.all(0),
|
|
Vector2.all(1),
|
|
);
|
|
final point = Vector2.all(0.5);
|
|
expect(
|
|
segment.containsPoint(point),
|
|
true,
|
|
reason: 'Point should be on segment',
|
|
);
|
|
});
|
|
|
|
test('Should not catch point outside of segment, but on line', () {
|
|
final segment = LineSegment(
|
|
Vector2.all(0),
|
|
Vector2.all(1),
|
|
);
|
|
final point = Vector2.all(3);
|
|
expect(
|
|
segment.containsPoint(point),
|
|
false,
|
|
reason: 'Point should not be on segment',
|
|
);
|
|
});
|
|
|
|
test('Should not catch point outside of segment', () {
|
|
final segment = LineSegment(
|
|
Vector2.all(0),
|
|
Vector2.all(1),
|
|
);
|
|
final point = Vector2(3, 2);
|
|
expect(
|
|
segment.containsPoint(point),
|
|
false,
|
|
reason: 'Point should not be on segment',
|
|
);
|
|
});
|
|
|
|
test('Point on end of segment', () {
|
|
final segment = LineSegment(
|
|
Vector2.all(0),
|
|
Vector2.all(1),
|
|
);
|
|
final point = Vector2.all(1);
|
|
expect(
|
|
segment.containsPoint(point),
|
|
true,
|
|
reason: 'Point should be on segment',
|
|
);
|
|
});
|
|
|
|
test('Point on beginning of segment', () {
|
|
final segment = LineSegment(
|
|
Vector2.all(0),
|
|
Vector2.all(1),
|
|
);
|
|
final point = Vector2.all(0);
|
|
expect(
|
|
segment.containsPoint(point),
|
|
true,
|
|
reason: 'Point should be on segment',
|
|
);
|
|
});
|
|
});
|
|
|
|
group('LineSegment.intersections tests', () {
|
|
test('Simple intersection', () {
|
|
final segmentA = LineSegment(Vector2.all(0), Vector2.all(1));
|
|
final segmentB = LineSegment(Vector2(0, 1), Vector2(1, 0));
|
|
final intersection = segmentA.intersections(segmentB);
|
|
expect(
|
|
intersection.isNotEmpty,
|
|
true,
|
|
reason: 'Should have intersection at (0.5, 0.5)',
|
|
);
|
|
expect(intersection.first == Vector2.all(0.5), true);
|
|
});
|
|
|
|
test('No intersection', () {
|
|
final segmentA = LineSegment(Vector2.all(0), Vector2.all(1));
|
|
final segmentB = LineSegment(Vector2(0, 1), Vector2(1, 2));
|
|
final intersection = segmentA.intersections(segmentB);
|
|
expect(
|
|
intersection.isEmpty,
|
|
true,
|
|
reason: 'Should not have any intersection',
|
|
);
|
|
});
|
|
|
|
test('Same line segments', () {
|
|
final segmentA = LineSegment(Vector2.all(0), Vector2.all(1));
|
|
final segmentB = LineSegment(Vector2.all(0), Vector2.all(1));
|
|
final intersection = segmentA.intersections(segmentB);
|
|
expect(
|
|
intersection.isNotEmpty,
|
|
true,
|
|
reason: 'Should have intersection at (0.5, 0.5)',
|
|
);
|
|
expect(intersection.first == Vector2.all(0.5), true);
|
|
});
|
|
|
|
test('Overlapping line segments', () {
|
|
final segmentA = LineSegment(Vector2.all(0), Vector2.all(1));
|
|
final segmentB = LineSegment(Vector2.all(0.5), Vector2.all(1.5));
|
|
final intersection = segmentA.intersections(segmentB);
|
|
expect(
|
|
intersection.isNotEmpty,
|
|
true,
|
|
reason: 'Should intersect at (0.75, 0.75)',
|
|
);
|
|
expect(intersection.first == Vector2.all(0.75), true);
|
|
});
|
|
|
|
test('One pixel overlap in different angles', () {
|
|
final segmentA = LineSegment(Vector2.all(0), Vector2.all(1));
|
|
final segmentB = LineSegment(Vector2.all(0), Vector2(1, -1));
|
|
final intersection = segmentA.intersections(segmentB);
|
|
expect(
|
|
intersection.isNotEmpty,
|
|
true,
|
|
reason: 'Should have intersection at (0, 0)',
|
|
);
|
|
expect(intersection.first == Vector2.all(0), true);
|
|
});
|
|
|
|
test('One pixel parallel overlap in same angle', () {
|
|
final segmentA = LineSegment(Vector2.all(0), Vector2.all(1));
|
|
final segmentB = LineSegment(Vector2.all(1), Vector2.all(2));
|
|
final intersection = segmentA.intersections(segmentB);
|
|
expect(
|
|
intersection.isNotEmpty,
|
|
true,
|
|
reason: 'Should have intersection at (1, 1)',
|
|
);
|
|
expect(intersection.first == Vector2.all(1), true);
|
|
});
|
|
});
|
|
|
|
group('Line.intersections tests', () {
|
|
test('Simple line intersection', () {
|
|
const line1 = Line(1, -1, 0);
|
|
const line2 = Line(1, 1, 0);
|
|
final intersection = line1.intersections(line2);
|
|
expect(intersection.isNotEmpty, true, reason: 'Should have intersection');
|
|
expect(intersection.first == Vector2.all(0), true);
|
|
});
|
|
|
|
test('Lines with c value', () {
|
|
const line1 = Line(1, 1, 1);
|
|
const line2 = Line(1, -1, 1);
|
|
final intersection = line1.intersections(line2);
|
|
expect(intersection.isNotEmpty, true, reason: 'Should have intersection');
|
|
expect(intersection.first == Vector2(1, 0), true);
|
|
});
|
|
|
|
test('Does not catch parallel lines', () {
|
|
const line1 = Line(1, 1, -3);
|
|
const line2 = Line(1, 1, 6);
|
|
final intersection = line1.intersections(line2);
|
|
expect(
|
|
intersection.isEmpty,
|
|
true,
|
|
reason: 'Should not have intersection',
|
|
);
|
|
});
|
|
|
|
test('Does not catch same line', () {
|
|
const line1 = Line(1, 1, 1);
|
|
const line2 = Line(1, 1, 1);
|
|
final intersection = line1.intersections(line2);
|
|
expect(
|
|
intersection.isEmpty,
|
|
true,
|
|
reason: 'Should not have intersection',
|
|
);
|
|
});
|
|
});
|
|
|
|
group('LinearEquation.fromPoints tests', () {
|
|
test('Simple line from points', () {
|
|
final line = Line.fromPoints(Vector2.zero(), Vector2.all(1));
|
|
expect(line.a == 1.0, true, reason: 'a value is not correct');
|
|
expect(line.b == -1.0, true, reason: 'b value is not correct');
|
|
expect(line.c == 0.0, true, reason: 'c value is not correct');
|
|
});
|
|
|
|
test('Line not going through origin', () {
|
|
final line = Line.fromPoints(Vector2(-2, 0), Vector2(0, 2));
|
|
expect(line.a == 2.0, true, reason: 'a value is not correct');
|
|
expect(line.b == -2.0, true, reason: 'b value is not correct');
|
|
expect(line.c == -4.0, true, reason: 'c value is not correct');
|
|
});
|
|
|
|
test('Straight vertical line', () {
|
|
final line = Line.fromPoints(Vector2.all(1), Vector2(1, -1));
|
|
expect(line.a == -2.0, true, reason: 'a value is not correct');
|
|
expect(line.b == 0.0, true, reason: 'b value is not correct');
|
|
expect(line.c == -2.0, true, reason: 'c value is not correct');
|
|
});
|
|
|
|
test('Straight horizontal line', () {
|
|
final line = Line.fromPoints(Vector2.all(1), Vector2(2, 1));
|
|
expect(line.a == 0.0, true, reason: 'a value is not correct');
|
|
expect(line.b == -1.0, true, reason: 'b value is not correct');
|
|
expect(line.c == -1.0, true, reason: 'c value is not correct');
|
|
});
|
|
});
|
|
|
|
group('LineSegment.pointsAt tests', () {
|
|
test('Simple pointing', () {
|
|
final segment = LineSegment(Vector2.zero(), Vector2.all(1));
|
|
const line = Line(1, 1, 3);
|
|
final isPointingAt = segment.pointsAt(line);
|
|
expect(isPointingAt, true, reason: 'Line should be pointed at');
|
|
});
|
|
|
|
test('Is not pointed at when crossed', () {
|
|
final segment = LineSegment(Vector2.zero(), Vector2.all(3));
|
|
const line = Line(1, 1, 3);
|
|
final isPointingAt = segment.pointsAt(line);
|
|
expect(isPointingAt, false, reason: 'Line should not be pointed at');
|
|
});
|
|
|
|
test('Is not pointed at when parallel', () {
|
|
final segment = LineSegment(Vector2.zero(), Vector2(1, -1));
|
|
const line = Line(1, 1, 3);
|
|
final isPointingAt = segment.pointsAt(line);
|
|
expect(isPointingAt, false, reason: 'Line should not be pointed at');
|
|
});
|
|
|
|
test('Horizontal line can be pointed at', () {
|
|
final segment = LineSegment(Vector2.zero(), Vector2.all(1));
|
|
const line = Line(0, 1, 2);
|
|
final isPointingAt = segment.pointsAt(line);
|
|
expect(isPointingAt, true, reason: 'Line should be pointed at');
|
|
});
|
|
|
|
test('Vertical line can be pointed at', () {
|
|
final segment = LineSegment(Vector2.zero(), Vector2.all(1));
|
|
const line = Line(1, 0, 2);
|
|
final isPointingAt = segment.pointsAt(line);
|
|
expect(isPointingAt, true, reason: 'Line should be pointed at');
|
|
});
|
|
});
|
|
|
|
group('Polygon intersections tests', () {
|
|
test('Simple polygon collision', () {
|
|
final polygonA = Polygon([
|
|
Vector2(2, 2),
|
|
Vector2(3, 1),
|
|
Vector2(2, 0),
|
|
Vector2(1, 1),
|
|
]);
|
|
final polygonB = Polygon([
|
|
Vector2(1, 2),
|
|
Vector2(2, 1),
|
|
Vector2(1, 0),
|
|
Vector2(0, 1),
|
|
]);
|
|
final intersections = geometry.intersections(polygonA, polygonB);
|
|
expect(
|
|
intersections.contains(Vector2(1.5, 0.5)),
|
|
true,
|
|
reason: 'Missed one intersection',
|
|
);
|
|
expect(
|
|
intersections.contains(Vector2(1.5, 1.5)),
|
|
true,
|
|
reason: 'Missed one intersection',
|
|
);
|
|
expect(
|
|
intersections.length == 2,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
|
|
test('Collision on shared line segment', () {
|
|
final polygonA = Polygon([
|
|
Vector2(1, 1),
|
|
Vector2(1, 2),
|
|
Vector2(2, 2),
|
|
Vector2(2, 1),
|
|
]);
|
|
final polygonB = Polygon([
|
|
Vector2(2, 1),
|
|
Vector2(2, 2),
|
|
Vector2(3, 2),
|
|
Vector2(3, 1),
|
|
]);
|
|
final intersections = geometry.intersections(polygonA, polygonB);
|
|
expect(
|
|
intersections.containsAll([
|
|
Vector2(2.0, 2.0),
|
|
Vector2(2.0, 1.5),
|
|
Vector2(2.0, 1.0),
|
|
]),
|
|
true,
|
|
reason: 'Does not have all the correct intersection points',
|
|
);
|
|
expect(
|
|
intersections.length == 3,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
|
|
test('One point collision', () {
|
|
final polygonA = Polygon([
|
|
Vector2(1, 1),
|
|
Vector2(1, 2),
|
|
Vector2(2, 2),
|
|
Vector2(2, 1),
|
|
]);
|
|
final polygonB = Polygon([
|
|
Vector2(2, 2),
|
|
Vector2(2, 3),
|
|
Vector2(3, 3),
|
|
Vector2(3, 2),
|
|
]);
|
|
final intersections = geometry.intersections(polygonA, polygonB);
|
|
expect(
|
|
intersections.contains(Vector2(2.0, 2.0)),
|
|
true,
|
|
reason: 'Does not have all the correct intersection points',
|
|
);
|
|
expect(
|
|
intersections.length == 1,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
|
|
test('Collision while no corners are inside the other body', () {
|
|
final polygonA = Polygon.fromDefinition(
|
|
[
|
|
Vector2(1, 1),
|
|
Vector2(1, -1),
|
|
Vector2(-1, -1),
|
|
Vector2(-1, 1),
|
|
],
|
|
position: Vector2.zero(),
|
|
size: Vector2(2, 4),
|
|
);
|
|
final polygonB = Polygon.fromDefinition(
|
|
[
|
|
Vector2(1, 1),
|
|
Vector2(1, -1),
|
|
Vector2(-1, -1),
|
|
Vector2(-1, 1),
|
|
],
|
|
position: Vector2.zero(),
|
|
size: Vector2(4, 2),
|
|
);
|
|
final intersections = geometry.intersections(polygonA, polygonB);
|
|
expect(
|
|
intersections.containsAll([
|
|
Vector2(1, 1),
|
|
Vector2(1, -1),
|
|
Vector2(-1, 1),
|
|
Vector2(-1, -1),
|
|
]),
|
|
true,
|
|
reason: 'Does not have all the correct intersection points',
|
|
);
|
|
expect(
|
|
intersections.length == 4,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
|
|
test('Collision with advanced hitboxes in different quadrants', () {
|
|
final polygonA = Polygon([
|
|
Vector2(0, 0),
|
|
Vector2(-1, 1),
|
|
Vector2(0, 3),
|
|
Vector2(2, 2),
|
|
Vector2(1.5, 0.5),
|
|
]);
|
|
final polygonB = Polygon([
|
|
Vector2(-2, -2),
|
|
Vector2(-3, 0),
|
|
Vector2(-2, 3),
|
|
Vector2(1, 2),
|
|
Vector2(2, 1),
|
|
]);
|
|
final intersections = geometry.intersections(polygonA, polygonB);
|
|
intersections.containsAll([
|
|
Vector2(-0.2857142857142857, 2.4285714285714284),
|
|
Vector2(1.7500000000000002, 1.2500000000000002),
|
|
Vector2(1.5555555555555556, 0.6666666666666667),
|
|
Vector2(1.1999999999999997, 0.39999999999999997),
|
|
]);
|
|
expect(
|
|
intersections.length == 4,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
});
|
|
|
|
group('Rectangle intersections tests', () {
|
|
test('Simple intersection', () {
|
|
final rectangleA = Rectangle(
|
|
position: Vector2(4, 0),
|
|
size: Vector2.all(4),
|
|
);
|
|
final rectangleB = Rectangle(
|
|
position: Vector2.zero(),
|
|
size: Vector2.all(4),
|
|
);
|
|
final intersections = geometry.intersections(rectangleA, rectangleB);
|
|
expect(
|
|
intersections.containsAll([
|
|
Vector2(2, -2),
|
|
Vector2(2, 0),
|
|
Vector2(2, 2),
|
|
]),
|
|
true,
|
|
reason: 'Missed intersections',
|
|
);
|
|
expect(
|
|
intersections.length == 3,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
});
|
|
|
|
group('Circle intersections tests', () {
|
|
test('Simple collision', () {
|
|
final circleA = Circle.fromDefinition(
|
|
position: Vector2(4, 0),
|
|
size: Vector2.all(4),
|
|
);
|
|
final circleB = Circle.fromDefinition(
|
|
position: Vector2.zero(),
|
|
size: Vector2.all(4),
|
|
);
|
|
final intersections = geometry.intersections(circleA, circleB);
|
|
expect(
|
|
intersections.contains(Vector2(2, 0)),
|
|
true,
|
|
reason: 'Missed one intersection',
|
|
);
|
|
expect(
|
|
intersections.length == 1,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
|
|
test('Two point collision', () {
|
|
final circleA = Circle.fromDefinition(
|
|
position: Vector2(3, 0),
|
|
size: Vector2.all(4),
|
|
);
|
|
final circleB = Circle.fromDefinition(
|
|
position: Vector2.zero(),
|
|
size: Vector2.all(4),
|
|
);
|
|
final intersections = geometry.intersections(circleA, circleB);
|
|
expect(
|
|
intersections.contains(Vector2(1.5, -1.3228756555322954)),
|
|
true,
|
|
reason: 'Missed one intersection',
|
|
);
|
|
expect(
|
|
intersections.contains(Vector2(1.5, 1.3228756555322954)),
|
|
true,
|
|
reason: 'Missed one intersection',
|
|
);
|
|
expect(
|
|
intersections.length == 2,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
|
|
test('Same size and position', () {
|
|
final circleA = Circle.fromDefinition(
|
|
position: Vector2.all(3),
|
|
size: Vector2.all(4),
|
|
);
|
|
final circleB = Circle.fromDefinition(
|
|
position: Vector2.all(3),
|
|
size: Vector2.all(4),
|
|
);
|
|
final intersections = geometry.intersections(circleA, circleB);
|
|
expect(
|
|
intersections.containsAll([
|
|
Vector2(5, 3),
|
|
Vector2(3, 5),
|
|
Vector2(3, 1),
|
|
Vector2(1, 3),
|
|
]),
|
|
true,
|
|
reason: 'Missed intersections',
|
|
);
|
|
expect(
|
|
intersections.length == 4,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
|
|
test('Not overlapping', () {
|
|
final circleA = Circle.fromDefinition(
|
|
position: Vector2.all(-1),
|
|
size: Vector2.all(4),
|
|
);
|
|
final circleB = Circle.fromDefinition(
|
|
position: Vector2.all(3),
|
|
size: Vector2.all(4),
|
|
);
|
|
final intersections = geometry.intersections(circleA, circleB);
|
|
expect(
|
|
intersections.isEmpty,
|
|
true,
|
|
reason: 'Should not have any intersections',
|
|
);
|
|
});
|
|
|
|
test('In third quadrant', () {
|
|
final circleA = Circle.fromDefinition(
|
|
position: Vector2.all(-1),
|
|
size: Vector2.all(2),
|
|
);
|
|
final circleB = Circle.fromDefinition(
|
|
position: Vector2.all(-2),
|
|
size: Vector2.all(2),
|
|
);
|
|
final intersections = geometry.intersections(circleA, circleB).toList();
|
|
expect(
|
|
intersections.any((v) => v.distanceTo(Vector2(-1, -2)) < 0.000001),
|
|
true,
|
|
);
|
|
expect(
|
|
intersections.any((v) => v.distanceTo(Vector2(-2, -1)) < 0.000001),
|
|
true,
|
|
);
|
|
expect(
|
|
intersections.length == 2,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
|
|
test('In different quadrants', () {
|
|
final circleA = Circle.fromDefinition(
|
|
position: Vector2.all(-1),
|
|
size: Vector2.all(4),
|
|
);
|
|
final circleB = Circle.fromDefinition(
|
|
position: Vector2.all(1),
|
|
size: Vector2.all(4),
|
|
);
|
|
final intersections = geometry.intersections(circleA, circleB).toList();
|
|
expect(
|
|
intersections.any((v) => v.distanceTo(Vector2(1, -1)) < 0.000001),
|
|
true,
|
|
);
|
|
expect(
|
|
intersections.any((v) => v.distanceTo(Vector2(-1, 1)) < 0.000001),
|
|
true,
|
|
);
|
|
expect(
|
|
intersections.length == 2,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
});
|
|
|
|
group('Circle-Polygon intersections tests', () {
|
|
test('Simple circle-polygon intersection', () {
|
|
final circle = Circle.fromDefinition(
|
|
position: Vector2.zero(),
|
|
size: Vector2.all(2),
|
|
);
|
|
final polygon = Polygon([
|
|
Vector2(1, 2),
|
|
Vector2(2, 1),
|
|
Vector2(1, 0),
|
|
Vector2(0, 1),
|
|
]);
|
|
final intersections = geometry.intersections(circle, polygon);
|
|
expect(
|
|
intersections.containsAll([Vector2(0, 1), Vector2(1, 0)]),
|
|
true,
|
|
reason: 'Missed intersections',
|
|
);
|
|
expect(
|
|
intersections.length == 2,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
|
|
test('Single point circle-polygon intersection', () {
|
|
final circle = Circle.fromDefinition(
|
|
position: Vector2(-1, 1),
|
|
size: Vector2.all(2),
|
|
);
|
|
final polygon = Polygon([
|
|
Vector2(1, 2),
|
|
Vector2(2, 1),
|
|
Vector2(1, 0),
|
|
Vector2(0, 1),
|
|
]);
|
|
final intersections = geometry.intersections(circle, polygon);
|
|
expect(
|
|
intersections.contains(Vector2(0, 1)),
|
|
true,
|
|
reason: 'Missed intersections',
|
|
);
|
|
expect(
|
|
intersections.length == 1,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
|
|
test('Four point circle-polygon intersection', () {
|
|
final circle = Circle.fromDefinition(
|
|
position: Vector2.all(1),
|
|
size: Vector2.all(2),
|
|
);
|
|
final polygon = Polygon([
|
|
Vector2(1, 2),
|
|
Vector2(2, 1),
|
|
Vector2(1, 0),
|
|
Vector2(0, 1),
|
|
]);
|
|
final intersections = geometry.intersections(circle, polygon);
|
|
expect(
|
|
intersections.containsAll([
|
|
Vector2(1, 2),
|
|
Vector2(2, 1),
|
|
Vector2(1, 0),
|
|
Vector2(0, 1),
|
|
]),
|
|
true,
|
|
reason: 'Missed intersections',
|
|
);
|
|
expect(
|
|
intersections.length == 4,
|
|
true,
|
|
reason: 'Wrong number of intersections',
|
|
);
|
|
});
|
|
|
|
test('Polygon within circle, no intersections', () {
|
|
final circle = Circle.fromDefinition(
|
|
position: Vector2.all(1),
|
|
size: Vector2.all(2.1),
|
|
);
|
|
final polygon = Polygon([
|
|
Vector2(1, 2),
|
|
Vector2(2, 1),
|
|
Vector2(1, 0),
|
|
Vector2(0, 1),
|
|
]);
|
|
final intersections = geometry.intersections(circle, polygon);
|
|
expect(
|
|
intersections.isEmpty,
|
|
true,
|
|
reason: 'Should not be any intersections',
|
|
);
|
|
});
|
|
});
|
|
}
|