Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix some issues with Sketch.hull() #1344

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

TerranceN
Copy link

I wanted to use hull for something I'm building, and immediately ran into this issue: #1224, so I gave it a shot at fixing it.

I wouldn't put money on this being perfect / not having an issue I missed, but this worked for the few examples I tried, and for the project I'm working on.

@@ -187,6 +189,9 @@ def _pt_arc(p: Point, a: Arc) -> Tuple[float, float, float, float]:
xc, yc = a.c.x, a.c.y
dx, dy = x - xc, y - yc
l = sqrt(dx ** 2 + dy ** 2)
ld = l ** 2 - r ** 2
if (ld < 0.001):
return x, y, x, y
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is when the point is on the arc's circle, so I return a no-length segment (which is later ignored). The point might not necessarily be on the arc though, so this should return a segment to the start or end of the arc.

This is taken care of elsewhere because I add the start and end points of the arc to the list of points in convert_and_validate, so if the next segment is between the point and a start or end point of an arc, it's handled elsewhere as a pt_pt comparison.

angles = atan2p(x - x1, y - y1), atan2p(x - x2, y - y2)
points = Point(x1, y1), Point(x2, y2)

ix = int(argmax(angles))
ix = int(argmax([a + 2*pi if a < angle else a for a in angles]))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the angle loops around back past 0 radians, this caused the argmin/argmax of pt_arc and arc_pt to choose the wrong side of the arc to continue from.

return angles[ix], segments[ix]
segment_angle = atan2p(segments[ix].b.x - xc2, segments[ix].b.y - yc2)
if (segment_angle < a2.a1 or segment_angle > a2.a2):
return inf, Segment(Point(inf, inf), Point(inf, inf))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this function that well, so this might have been from something else wrong and is now redundant, but somehow it was selecting a point not actually on the arc, so this if statement throws that out if that happens.

I'd bet this isn't the best way to handle it and there's more edge cases that would fail here.

if (current.s == e.e and not e.s == current.e):
return pt_pt(current.e, e.s)
if (e.s == current.e and not current.s == e.e):
return (current.a2 + pi) % (2 * pi), Segment(Point(inf, inf), Point(inf, inf))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes allow a circle that's broken into multiple arcs continue around the circle

# march around
while not finished:
while not finished and iterations < len(entities):
iterations += 1
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While working on this it was very easy for this to loop infinitely. Setting a sensible maximum number of iterations (if we've iterated more than the number of entities, something has gone wrong) seems better than having the system hang in an infinite loop.

@@ -18,7 +18,7 @@ def test_hull():

h = hull.find_hull(edges)

assert len(h.Vertices()) == 11
assert len(h.Vertices()) == 10
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow my changes made this hull have one fewer vertices. Based on how the points and arcs are laid out, I'm guessing there were multiple possible next entities with the same angle and my hull changes made it skip one.

There should probably be a 'tie-breaker' for choosing the smallest angle so that the system is more deterministic. (either always choosing the closer to ensure the loop always ends up closed, or choosing the further, but we'd have to be more careful about choosing the starting entity, lest we start on an entity we later skip and fail to close the loop)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant