Fix Polygon::offsetInward for non-convex polygons and collinear vertices - #550
Open
andreolf wants to merge 1 commit into
Open
Fix Polygon::offsetInward for non-convex polygons and collinear vertices#550andreolf wants to merge 1 commit into
andreolf wants to merge 1 commit into
Conversation
The previous implementation offset each vertex along v1 + v2 (the sum of the unit vectors towards its neighbours) scaled by margin / sin(angle). This is only correct for convex vertices of counter-clockwise ordered polygons: reflex vertices were offset outward instead of inward, and collinear vertices did not move at all because v1 + v2 degenerates to the zero vector (with sin(angle) = 0 additionally producing NaNs). Rework the algorithm following the analysis in ANYbotics#514: offset each vertex along the bisector of the inward normals of its two adjacent edges, at the miter distance margin / cos(alpha / 2). The winding order is determined with the shoelace formula so clockwise polygons are also offset towards the interior. Degenerate inputs (fewer than three vertices, zero area, repeated vertices, spike vertices) now return false and leave the polygon unchanged. The pre-existing convex triangle test passes unchanged. New tests cover the non-convex polygon from the issue (asserting each offset vertex keeps the margin distance to both adjacent original edges on the interior side), collinear vertices, clockwise ordering, outward offset via negative margin, and degenerate inputs. Fixes ANYbotics#514 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Tick the box to add this pull request to the merge queue (same as
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #514
Implements the approach @Arjan-TNO analyzed and prototyped in #514, as requested by @Ryanf55 ("Can you submit a pull request with perhaps a unit test that verifies the fix?").
Problem
offsetInwardoffset each vertex alongv1 + v2(unit vectors towards its neighbours) scaled bymargin / sin(angle). That is only correct for convex vertices of CCW-ordered polygons:acos(v1·v2)cannot distinguish a reflex corner from its convex mirror.v1 + v2degenerates to the zero vector, andsin(angle) = 0additionally produces NaNs.Fix
Each vertex is offset along the bisector of the inward normals of its two adjacent edges at the miter distance
margin / cos(α/2), where α is the angle between the normals (|n1 + n2| = 2·cos(α/2), so no trigonometric calls are needed). This is correct for convex, reflex, and collinear vertices alike, because by construction the offset vertex keeps the distancemarginto the lines through both adjacent edges, on the interior side.Additionally:
falseand leave the polygon unchanged, matching the documented@returncontract (the old code always returnedtrue).Tests
The pre-existing
offsetInward.triangleexpectations pass unchanged (convex CCW behavior is identical). New tests:nonConvexPolygon— the exact 10-vertex polygon from unexpected/wrong output of Polygon::offsetInward function #514; asserts every offset vertex keeps signed distancemarginto the lines through both adjacent original edges (signed = interior side, so a wrong-direction offset cannot pass), plus exact positions for the two collinear vertices.collinearVertices— square with a collinear mid-edge vertex (NaN in the old code).clockwiseOrder— mirrored triangle test with CW ordering.outwardWithNegativeMargin— documented negative-margin behavior.degenerateInputs—false+ polygon unchanged for <3 vertices, repeated vertices, zero area.All 17 tests in
PolygonTest.cpppass locally (compiled standalone against Eigen 3.4 / googletest with clang on macOS; full colcon CI deferred to this repo's pipeline).No other code in the repository calls
offsetInward, so the behavior change for previously-broken inputs (CW polygons, reflex/collinear vertices) has no in-tree callers to migrate.🤖 Generated with Claude Code