Line data Source code
1 : import 'dart:math';
2 : import 'dart:ui';
3 :
4 : import 'package:flutter/material.dart';
5 :
6 : import '../../extensions.dart';
7 : import '../../geometry.dart';
8 : import 'circle.dart';
9 : import 'polygon.dart';
10 : import 'shape.dart';
11 :
12 : abstract class Intersections<T1 extends Shape, T2 extends Shape> {
13 : Set<Vector2> intersect(T1 shapeA, T2 shapeB);
14 :
15 3 : bool supportsShapes(Shape shapeA, Shape shapeB) {
16 12 : return shapeA is T1 && shapeB is T2 || shapeA is T2 && shapeB is T1;
17 : }
18 :
19 3 : Set<Vector2> unorderedIntersect(Shape shapeA, Shape shapeB) {
20 6 : if (shapeA is T1 && shapeB is T2) {
21 3 : return intersect(shapeA, shapeB);
22 0 : } else if (shapeA is T2 && shapeB is T1) {
23 0 : return intersect(shapeB, shapeA);
24 : } else {
25 : throw 'Unsupported shapes';
26 : }
27 : }
28 : }
29 :
30 : class PolygonPolygonIntersections extends Intersections<Polygon, Polygon> {
31 : /// Returns the intersection points of [polygonA] and [polygonB]
32 : /// The two polygons are required to be convex
33 : /// If they share a segment of a line, both end points and the center point of
34 : /// that line segment will be counted as collision points
35 3 : @override
36 : Set<Vector2> intersect(
37 : Polygon polygonA,
38 : Polygon polygonB, {
39 : Rect? overlappingRect,
40 : }) {
41 : final intersectionPoints = <Vector2>{};
42 3 : final intersectionsA = polygonA.possibleIntersectionVertices(
43 : overlappingRect,
44 : );
45 3 : final intersectionsB = polygonB.possibleIntersectionVertices(
46 : overlappingRect,
47 : );
48 6 : for (final lineA in intersectionsA) {
49 6 : for (final lineB in intersectionsB) {
50 6 : intersectionPoints.addAll(lineA.intersections(lineB));
51 : }
52 : }
53 : return intersectionPoints;
54 : }
55 : }
56 :
57 : class CirclePolygonIntersections extends Intersections<Circle, Polygon> {
58 1 : @override
59 : Set<Vector2> intersect(
60 : Circle circle,
61 : Polygon polygon, {
62 : Rect? overlappingRect,
63 : }) {
64 : final intersectionPoints = <Vector2>{};
65 1 : final possibleVertices = polygon.possibleIntersectionVertices(
66 : overlappingRect,
67 : );
68 2 : for (final line in possibleVertices) {
69 2 : intersectionPoints.addAll(circle.lineSegmentIntersections(line));
70 : }
71 : return intersectionPoints;
72 : }
73 : }
74 :
75 : class CircleCircleIntersections extends Intersections<Circle, Circle> {
76 1 : @override
77 : Set<Vector2> intersect(Circle shapeA, Circle shapeB) {
78 3 : final distance = shapeA.absoluteCenter.distanceTo(shapeB.absoluteCenter);
79 1 : final radiusA = shapeA.radius;
80 1 : final radiusB = shapeB.radius;
81 2 : if (distance > radiusA + radiusB) {
82 : // Since the circles are too far away from each other to intersect we
83 : // return the empty set.
84 : return {};
85 3 : } else if (distance < (radiusA - radiusB).abs()) {
86 : // Since one circle is contained within the other there can't be any
87 : // intersections.
88 : return {};
89 2 : } else if (distance == 0 && radiusA == radiusB) {
90 : // The circles are identical and on top of each other, so there are an
91 : // infinite number of solutions. Since it is problematic to return a
92 : // set of infinite size, we'll return 4 distinct points here.
93 : return {
94 3 : shapeA.absoluteCenter + Vector2(radiusA, 0),
95 4 : shapeA.absoluteCenter + Vector2(0, -radiusA),
96 4 : shapeA.absoluteCenter + Vector2(-radiusA, 0),
97 3 : shapeA.absoluteCenter + Vector2(0, radiusA),
98 : };
99 : } else {
100 : /// There are definitely collision points if we end up in here.
101 : /// To calculate these we use the fact that we can form two triangles going
102 : /// between the center of shapeA, the point in between the shapes which the
103 : /// intersecting line goes through, and then two different triangles are
104 : /// formed with the two intersection points as the last corners.
105 : /// The length to the point in between the circles is first calculated,
106 : /// this is [lengthA], then we calculate the length of the other cathetus
107 : /// [lengthB]. Then the [centerPoint] is calculated, which is the point
108 : /// which the intersecting line goes through in between the shapes.
109 : /// At this point we know the two first points of the triangles, the center
110 : /// of [shapeA] and the [centerPoint], the two third points of the
111 : /// different triangles are the intersection points that we are looking for
112 : /// and we get those points by calculating the [delta] from the
113 : /// [centerPoint] to the intersection points.
114 : /// The result is then [centerPoint] +- [delta].
115 6 : final lengthA = (pow(radiusA, 2) - pow(radiusB, 2) + pow(distance, 2)) /
116 1 : (2 * distance);
117 5 : final lengthB = sqrt((pow(radiusA, 2) - pow(lengthA, 2)).abs());
118 2 : final centerPoint = shapeA.absoluteCenter +
119 5 : (shapeB.absoluteCenter - shapeA.absoluteCenter) * lengthA / distance;
120 1 : final delta = Vector2(
121 1 : lengthB *
122 7 : (shapeB.absoluteCenter.y - shapeA.absoluteCenter.y).abs() /
123 : distance,
124 2 : -lengthB *
125 7 : (shapeB.absoluteCenter.x - shapeA.absoluteCenter.x).abs() /
126 : distance,
127 : );
128 : return {
129 1 : centerPoint + delta,
130 1 : centerPoint - delta,
131 : };
132 : }
133 : }
134 : }
135 :
136 9 : final List<Intersections> _intersectionSystems = [
137 3 : CircleCircleIntersections(),
138 3 : CirclePolygonIntersections(),
139 3 : PolygonPolygonIntersections(),
140 : ];
141 :
142 3 : Set<Vector2> intersections(Shape shapeA, Shape shapeB) {
143 6 : final intersectionSystem = _intersectionSystems.firstWhere(
144 6 : (system) => system.supportsShapes(shapeA, shapeB),
145 0 : orElse: () {
146 0 : throw 'Unsupported shape detected + ${shapeA.runtimeType} ${shapeB.runtimeType}';
147 : },
148 : );
149 3 : return intersectionSystem.unorderedIntersect(shapeA, shapeB);
150 : }
|