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

          Line data    Source code
       1             : import 'dart:math';
       2             : import 'dart:ui' hide Canvas;
       3             : 
       4             : import '../../game.dart';
       5             : import '../../geometry.dart';
       6             : import '../components/cache/value_cache.dart';
       7             : import '../extensions/canvas.dart';
       8             : import '../extensions/rect.dart';
       9             : import '../extensions/vector2.dart';
      10             : import 'shape.dart';
      11             : 
      12             : class Polygon extends Shape {
      13             :   final List<Vector2> normalizedVertices;
      14             :   // These lists are used to minimize the amount of [Vector2] objects that are
      15             :   // created, only change them if the cache is deemed invalid
      16             :   late final List<Vector2> _sizedVertices;
      17             :   late final List<Vector2> _hitboxVertices;
      18             : 
      19             :   /// With this constructor you create your [Polygon] from positions in your
      20             :   /// intended space. It will automatically calculate the [size] and center
      21             :   /// ([position]) of the Polygon.
      22           1 :   factory Polygon(
      23             :     List<Vector2> points, {
      24             :     double angle = 0,
      25             :   }) {
      26           1 :     final center = points.fold<Vector2>(
      27           1 :           Vector2.zero(),
      28           2 :           (sum, v) => sum + v,
      29           1 :         ) /
      30           2 :         points.length.toDouble();
      31           1 :     final bottomRight = points.fold<Vector2>(
      32           1 :       Vector2.zero(),
      33           1 :       (bottomRight, v) {
      34           1 :         return Vector2(
      35           3 :           max(bottomRight.x, v.x),
      36           3 :           max(bottomRight.y, v.y),
      37             :         );
      38             :       },
      39             :     );
      40           1 :     final halfSize = bottomRight - center;
      41             :     final definition =
      42           5 :         points.map<Vector2>((v) => (v - center)..divide(halfSize)).toList();
      43           1 :     return Polygon.fromDefinition(
      44             :       definition,
      45             :       position: center,
      46           1 :       size: halfSize * 2,
      47             :       angle: angle,
      48             :     );
      49             :   }
      50             : 
      51             :   /// With this constructor you define the [Polygon] from the center of and with
      52             :   /// percentages of the size of the shape.
      53             :   /// Example: [[1.0, 0.0], [0.0, 1.0], [-1.0, 0.0], [0.0, -1.0]]
      54             :   /// This will form a diamond shape within the bounding size box.
      55             :   /// NOTE: Always define your shape in a clockwise fashion
      56           8 :   Polygon.fromDefinition(
      57             :     this.normalizedVertices, {
      58             :     Vector2? position,
      59             :     Vector2? size,
      60             :     double? angle,
      61           8 :   }) : super(
      62             :           position: position,
      63             :           size: size,
      64             :           angle: angle ?? 0,
      65             :         ) {
      66           8 :     _sizedVertices =
      67          40 :         normalizedVertices.map((_) => Vector2.zero()).toList(growable: false);
      68           8 :     _hitboxVertices =
      69          40 :         normalizedVertices.map((_) => Vector2.zero()).toList(growable: false);
      70             :   }
      71             : 
      72             :   final _cachedScaledShape = ValueCache<Iterable<Vector2>>();
      73             : 
      74             :   /// Gives back the shape vectors multiplied by the size
      75           7 :   Iterable<Vector2> scaled() {
      76          28 :     if (!_cachedScaledShape.isCacheValid([size])) {
      77          28 :       for (var i = 0; i < _sizedVertices.length; i++) {
      78          14 :         final point = normalizedVertices[i];
      79          35 :         (_sizedVertices[i]..setFrom(point)).multiply(halfSize);
      80             :       }
      81          42 :       _cachedScaledShape.updateCache(_sizedVertices, [size.clone()]);
      82             :     }
      83          14 :     return _cachedScaledShape.value!;
      84             :   }
      85             : 
      86             :   final _cachedRenderPath = ValueCache<Path>();
      87             : 
      88           0 :   @override
      89             :   void render(Canvas canvas, Paint paint) {
      90           0 :     if (!_cachedRenderPath
      91           0 :         .isCacheValid([offsetPosition, relativeOffset, size, angle])) {
      92           0 :       final center = localCenter;
      93           0 :       _cachedRenderPath.updateCache(
      94           0 :         Path()
      95           0 :           ..addPolygon(
      96           0 :             scaled().map(
      97           0 :               (point) {
      98           0 :                 final pathPoint = center + point;
      99           0 :                 if (!isCanvasPrepared) {
     100           0 :                   pathPoint.rotate(angle, center: center);
     101             :                 }
     102           0 :                 return pathPoint.toOffset();
     103             :               },
     104           0 :             ).toList(),
     105             :             true,
     106             :           ),
     107           0 :         [
     108           0 :           offsetPosition.clone(),
     109           0 :           relativeOffset.clone(),
     110           0 :           size.clone(),
     111           0 :           angle,
     112             :         ],
     113             :       );
     114             :     }
     115           0 :     canvas.drawPath(_cachedRenderPath.value!, paint);
     116             :   }
     117             : 
     118             :   final _cachedHitbox = ValueCache<List<Vector2>>();
     119             : 
     120             :   /// Gives back the vertices represented as a list of points which
     121             :   /// are the "corners" of the hitbox rotated with [angle].
     122           7 :   List<Vector2> hitbox() {
     123             :     // Use cached bounding vertices if state of the component hasn't changed
     124           7 :     if (!_cachedHitbox
     125          42 :         .isCacheValid([absoluteCenter, size, parentAngle, angle])) {
     126          14 :       final scaledVertices = scaled().toList(growable: false);
     127           7 :       final center = absoluteCenter;
     128          28 :       for (var i = 0; i < _hitboxVertices.length; i++) {
     129          14 :         _hitboxVertices[i]
     130           7 :           ..setFrom(center)
     131          14 :           ..add(scaledVertices[i])
     132          28 :           ..rotate(parentAngle + angle, center: center);
     133             :       }
     134          14 :       _cachedHitbox.updateCache(
     135           7 :         _hitboxVertices,
     136          42 :         [absoluteCenter, size.clone(), parentAngle, angle],
     137             :       );
     138             :     }
     139          14 :     return _cachedHitbox.value!;
     140             :   }
     141             : 
     142             :   /// Checks whether the polygon represented by the list of [Vector2] contains
     143             :   /// the [point].
     144           5 :   @override
     145             :   bool containsPoint(Vector2 point) {
     146             :     // If the size is 0 then it can't contain any points
     147          30 :     if (size.x == 0 || size.y == 0) {
     148             :       return false;
     149             :     }
     150             : 
     151           5 :     final vertices = hitbox();
     152          15 :     for (var i = 0; i < vertices.length; i++) {
     153           5 :       final edge = getEdge(i, vertices: vertices);
     154          55 :       final isOutside = (edge.to.x - edge.from.x) * (point.y - edge.from.y) -
     155          55 :               (point.x - edge.from.x) * (edge.to.y - edge.from.y) >
     156             :           0;
     157             :       if (isOutside) {
     158             :         // Point is outside of convex polygon
     159             :         return false;
     160             :       }
     161             :     }
     162             :     return true;
     163             :   }
     164             : 
     165             :   /// Return all vertices as [LineSegment]s that intersect [rect], if [rect]
     166             :   /// is null return all vertices as [LineSegment]s.
     167           3 :   List<LineSegment> possibleIntersectionVertices(Rect? rect) {
     168           3 :     final rectIntersections = <LineSegment>[];
     169           3 :     final vertices = hitbox();
     170           9 :     for (var i = 0; i < vertices.length; i++) {
     171           3 :       final edge = getEdge(i, vertices: vertices);
     172           0 :       if (rect?.intersectsSegment(edge.from, edge.to) ?? true) {
     173           3 :         rectIntersections.add(edge);
     174             :       }
     175             :     }
     176             :     return rectIntersections;
     177             :   }
     178             : 
     179           7 :   LineSegment getEdge(int i, {required List<Vector2> vertices}) {
     180           7 :     return LineSegment(
     181           7 :       getVertex(i, vertices: vertices),
     182           7 :       getVertex(
     183           7 :         i + 1,
     184             :         vertices: vertices,
     185             :       ),
     186             :     );
     187             :   }
     188             : 
     189           7 :   Vector2 getVertex(int i, {List<Vector2>? vertices}) {
     190           0 :     vertices ??= hitbox();
     191          21 :     return vertices[i % vertices.length];
     192             :   }
     193             : }
     194             : 
     195             : class HitboxPolygon extends Polygon with HitboxShape {
     196           2 :   HitboxPolygon(List<Vector2> definition) : super.fromDefinition(definition);
     197             : }

Generated by: LCOV version 1.15