Skip to content

Commit 149f847

Browse files
fix: prevent crash when zooming far into Polygons (#1854)
Co-authored-by: Luka S <github@jaffaketchup.dev>
1 parent 8d106d2 commit 149f847

File tree

1 file changed

+149
-30
lines changed

1 file changed

+149
-30
lines changed

lib/src/layer/polygon_layer/painter.dart

Lines changed: 149 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ class _PolygonPainter<R extends Object> extends CustomPainter {
2828

2929
final _hits = <R>[]; // Avoids repetitive memory reallocation
3030

31+
// OutCodes for the Cohen-Sutherland algorithm
32+
static const _csInside = 0; // 0000
33+
static const _csLeft = 1; // 0001
34+
static const _csRight = 2; // 0010
35+
static const _csBottom = 4; // 0100
36+
static const _csTop = 8; // 1000
37+
3138
/// Create a new [_PolygonPainter] instance.
3239
_PolygonPainter({
3340
required this.polygons,
@@ -212,6 +219,9 @@ class _PolygonPainter<R extends Object> extends CustomPainter {
212219
origin: origin,
213220
points: projectedPolygon.points,
214221
),
222+
size,
223+
_getBorderPaint(polygon),
224+
canvas,
215225
);
216226
}
217227

@@ -234,7 +244,8 @@ class _PolygonPainter<R extends Object> extends CustomPainter {
234244
}
235245

236246
if (!polygon.disableHolesBorder && polygon.borderStrokeWidth > 0.0) {
237-
_addHoleBordersToPath(borderPath, polygon, holeOffsetsList);
247+
_addHoleBordersToPath(borderPath, polygon, holeOffsetsList, size,
248+
canvas, _getBorderPaint(polygon));
238249
}
239250
}
240251

@@ -246,7 +257,7 @@ class _PolygonPainter<R extends Object> extends CustomPainter {
246257
// ensure polygons and labels are stacked correctly, i.e.:
247258
// p1, p1_label, p2, p2_label, ... .
248259

249-
// The painter will be null if the layouting algorithm determined that
260+
// The painter will be null if the layOuting algorithm determined that
250261
// there isn't enough space.
251262
final painter = _buildLabelTextPainter(
252263
mapSize: camera.size,
@@ -307,11 +318,15 @@ class _PolygonPainter<R extends Object> extends CustomPainter {
307318
Path path,
308319
Polygon polygon,
309320
List<Offset> offsets,
321+
Size canvasSize,
322+
Paint paint,
323+
Canvas canvas,
310324
) {
311325
if (polygon.isDotted) {
312326
final borderRadius = polygon.borderStrokeWidth / 2;
313327
final spacing = polygon.borderStrokeWidth * 1.5;
314-
_addDottedLineToPath(path, offsets, borderRadius, spacing);
328+
_addDottedLineToPath(
329+
canvas, paint, offsets, borderRadius, spacing, canvasSize);
315330
} else {
316331
_addLineToPath(path, offsets);
317332
}
@@ -321,12 +336,16 @@ class _PolygonPainter<R extends Object> extends CustomPainter {
321336
Path path,
322337
Polygon polygon,
323338
List<List<Offset>> holeOffsetsList,
339+
Size canvasSize,
340+
Canvas canvas,
341+
Paint paint,
324342
) {
325343
if (polygon.isDotted) {
326344
final borderRadius = polygon.borderStrokeWidth / 2;
327345
final spacing = polygon.borderStrokeWidth * 1.5;
328346
for (final offsets in holeOffsetsList) {
329-
_addDottedLineToPath(path, offsets, borderRadius, spacing);
347+
_addDottedLineToPath(
348+
canvas, paint, offsets, borderRadius, spacing, canvasSize);
330349
}
331350
} else {
332351
for (final offsets in holeOffsetsList) {
@@ -335,52 +354,152 @@ class _PolygonPainter<R extends Object> extends CustomPainter {
335354
}
336355
}
337356

357+
// Function to clip a line segment to a rectangular area (canvas)
358+
List<Offset>? _getVisibleSegment(Offset p0, Offset p1, Size canvasSize) {
359+
// Function to compute the outCode for a point relative to the canvas
360+
int computeOutCode(
361+
double x,
362+
double y,
363+
double xMin,
364+
double yMin,
365+
double xMax,
366+
double yMax,
367+
) {
368+
int code = _csInside;
369+
370+
if (x < xMin) {
371+
code |= _csLeft;
372+
} else if (x > xMax) {
373+
code |= _csRight;
374+
}
375+
if (y < yMin) {
376+
code |= _csBottom;
377+
} else if (y > yMax) {
378+
code |= _csTop;
379+
}
380+
381+
return code;
382+
}
383+
384+
const double xMin = 0;
385+
const double yMin = 0;
386+
final double xMax = canvasSize.width;
387+
final double yMax = canvasSize.height;
388+
389+
double x0 = p0.dx;
390+
double y0 = p0.dy;
391+
double x1 = p1.dx;
392+
double y1 = p1.dy;
393+
394+
int outCode0 = computeOutCode(x0, y0, xMin, yMin, xMax, yMax);
395+
int outCode1 = computeOutCode(x1, y1, xMin, yMin, xMax, yMax);
396+
bool accept = false;
397+
398+
while (true) {
399+
if ((outCode0 | outCode1) == 0) {
400+
// Both points inside; trivially accept
401+
accept = true;
402+
break;
403+
} else if ((outCode0 & outCode1) != 0) {
404+
// Both points share an outside zone; trivially reject
405+
break;
406+
} else {
407+
// Could be partially inside; calculate intersection
408+
double x;
409+
double y;
410+
final int outCodeOut = outCode0 != 0 ? outCode0 : outCode1;
411+
412+
if ((outCodeOut & _csTop) != 0) {
413+
x = x0 + (x1 - x0) * (yMax - y0) / (y1 - y0);
414+
y = yMax;
415+
} else if ((outCodeOut & _csBottom) != 0) {
416+
x = x0 + (x1 - x0) * (yMin - y0) / (y1 - y0);
417+
y = yMin;
418+
} else if ((outCodeOut & _csRight) != 0) {
419+
y = y0 + (y1 - y0) * (xMax - x0) / (x1 - x0);
420+
x = xMax;
421+
} else if ((outCodeOut & _csLeft) != 0) {
422+
y = y0 + (y1 - y0) * (xMin - x0) / (x1 - x0);
423+
x = xMin;
424+
} else {
425+
// This else block should never be reached.
426+
break;
427+
}
428+
429+
// Update the point and outCode
430+
if (outCodeOut == outCode0) {
431+
x0 = x;
432+
y0 = y;
433+
outCode0 = computeOutCode(x0, y0, xMin, yMin, xMax, yMax);
434+
} else {
435+
x1 = x;
436+
y1 = y;
437+
outCode1 = computeOutCode(x1, y1, xMin, yMin, xMax, yMax);
438+
}
439+
}
440+
}
441+
442+
if (accept) {
443+
// Make sure we return the points within the canvas
444+
return [Offset(x0, y0), Offset(x1, y1)];
445+
}
446+
return null;
447+
}
448+
338449
void _addDottedLineToPath(
339-
Path path,
450+
Canvas canvas,
451+
Paint paint,
340452
List<Offset> offsets,
341453
double radius,
342454
double stepLength,
455+
Size canvasSize,
343456
) {
344457
if (offsets.isEmpty) {
345458
return;
346459
}
347460

348-
double startDistance = 0;
349-
for (int i = 0; i < offsets.length; i++) {
350-
final o0 = offsets[i % offsets.length];
351-
final o1 = offsets[(i + 1) % offsets.length];
352-
final totalDistance = (o0 - o1).distance;
353-
354-
double distance = startDistance;
355-
while (distance < totalDistance) {
356-
final done = distance / totalDistance;
357-
final remain = 1.0 - done;
358-
final offset = Offset(
359-
o0.dx * remain + o1.dx * done,
360-
o0.dy * remain + o1.dy * done,
361-
);
362-
path.addOval(Rect.fromCircle(center: offset, radius: radius));
363-
364-
distance += stepLength;
461+
// Calculate for all segments, including closing the loop from the last to the first point
462+
final int totalOffsets = offsets.length;
463+
for (int i = 0; i < totalOffsets; i++) {
464+
final Offset start = offsets[i % totalOffsets];
465+
final Offset end =
466+
offsets[(i + 1) % totalOffsets]; // Wrap around to the first point
467+
468+
// Attempt to adjust the segment to the visible part of the canvas
469+
final List<Offset>? visibleSegment =
470+
_getVisibleSegment(start, end, canvasSize);
471+
if (visibleSegment == null) {
472+
continue; // Skip if the segment is completely outside
365473
}
366474

367-
startDistance = distance < totalDistance
368-
? stepLength - (totalDistance - distance)
369-
: distance - totalDistance;
475+
final Offset adjustedStart = visibleSegment[0];
476+
final Offset adjustedEnd = visibleSegment[1];
477+
final double lineLength = (adjustedStart - adjustedEnd).distance;
478+
final Offset stepVector =
479+
(adjustedEnd - adjustedStart) / lineLength * stepLength;
480+
double traveledDistance = 0;
481+
482+
Offset currentPoint = adjustedStart;
483+
while (traveledDistance < lineLength) {
484+
// Draw the circle if within the canvas bounds (additional check now redundant)
485+
canvas.drawCircle(currentPoint, radius, paint);
486+
487+
// Move to the next point
488+
currentPoint = currentPoint + stepVector;
489+
traveledDistance += stepLength;
490+
}
370491
}
371-
372-
path.addOval(Rect.fromCircle(center: offsets.last, radius: radius));
373492
}
374493

375494
void _addLineToPath(Path path, List<Offset> offsets) {
376495
path.addPolygon(offsets, true);
377496
}
378497

379498
({Offset min, Offset max}) _getBounds(Offset origin, Polygon polygon) {
380-
final bbox = polygon.boundingBox;
499+
final bBox = polygon.boundingBox;
381500
return (
382-
min: getOffset(camera, origin, bbox.southWest),
383-
max: getOffset(camera, origin, bbox.northEast),
501+
min: getOffset(camera, origin, bBox.southWest),
502+
max: getOffset(camera, origin, bBox.northEast),
384503
);
385504
}
386505

0 commit comments

Comments
 (0)