LCOV - code coverage report
Current view: top level - lib/src/geometry - shape_intersections.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 46 50 92.0 %
Date: 2021-08-10 15:50:53 Functions: 0 0 -

          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             : }

Generated by: LCOV version 1.15