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