Skip to content

[in3Utils] Problematic polygon/path orientation method leading to wrong direction of buffer applied during particle initialization #1337

Description

@bskerlak

Describe the bug

geoTrans.isCounterClockWise() decides a polygon's winding direction with a majority vote on vertex angles measured relative to whichever vertex is listed first (V0). This is not rotation-invariant: the identical polygon can get a different (wrong) answer purely depending on which vertex is listed first — or, with vertex order held completely fixed, purely from the polygon's physical orientation in the plane (arctan2's ±180° branch cut moves relative to the vertex angles as the shape rotates). This is not limited to concave shapes — a plain convex square is affected too.

polygon2Raster() uses this sign to decide whether its radius tolerance dilates or shrinks a polygon before rasterizing it. The real release-raster call in com1DFA.py (radius = sqrt(2), ~line 1249) is meant to dilate slightly; when winding is misjudged, it shrinks instead. On a real 59-vertex building-footprint release polygon this reduced 6 legitimate non-empty release cells to 1 — roughly 92% of the intended release mass silently dropped, no error or warning.

To Reproduce

Take l_shape = [[0,0],[0,4],[3,4],[3,2],[1,2],[1,0]].
Rotate it 100° about vertex 0 — vertex order/starting index untouched:

[0.000000, 0.000000], [-3.939231, -0.694593], [-4.460176, 2.259831],
[-2.490560, 2.607127], [-2.143264, 0.637511], [-0.173648, 0.984808]
geoTrans.isCounterClockWise(matplotlib.path.Path(vertices)) on this exact order returns True.
The correct answer (shoelace formula, sum(x_i·y_(i+1) − x_(i+1)·y_i) > 0) is False.
Sweeping this same shape through a full 360° turn (vertex order fixed throughout) finds a contiguous 90° band (91°–180°) where this happens — a quarter of all possible orientations for this one shape.

Expected behavior

isCounterClockWise() should return the same, correct answer regardless of which vertex is listed first or the polygon's absolute orientation — winding is a geometric property of the polygon, not of vertex-list bookkeeping.

Screenshots

Interactive derivation, steps both algorithms side-by-side over six test polygons (including this exact case) can be found on https://avaframe-bug.skerlak.ch.

OS:
N/A — pure Python/numpy/matplotlib logic bug, not platform-specific

Full derivation, six test shapes (a convex square is also affected; a more-concave star and a circle happen to survive every rotation tested), and the fix are in bug_report.md (attached in the linked package).
Proposed fix, already implemented and tested against the existing suite:

def isCounterClockWise(path):
x = path.vertices[:, 0]
y = path.vertices[:, 1]
signedArea = np.sum(x * np.roll(y, -1) - np.roll(x, -1) * y)
return signedArea > 0
Runnable pytest package attached (shapes.py, ccw_compare.py, test_isCounterClockWise_bug.py): fails against the current implementation, passes against the proposed one.

Additional context
bojan@skerlak.ch
www.skerlak.ch

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions