Skip to content

Commit

Permalink
fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
cpederkoff committed Jun 26, 2024
1 parent 0f387fd commit 19364ac
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 31 deletions.
35 changes: 19 additions & 16 deletions stltovoxel/polygon_repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,20 @@ def find_polyline_endpoints(segs):


def atan_sum(f1, f2):
# Angle sum and difference identity
# Atan sum identity
# atan2(atan_sum(f1, f2)) == atan2(f1) + atan2(f2)
x1, y1 = f1
x2, y2 = f2
return (x1*x2 - y1*y2, y1*x2 + x1*y2)


def atan_neg(f1):
# atan2(atan_neg(f1)) == -atan2(f1)
x, y = f1
return x, -y
def atan_diff(f1, f2):
# Atan difference identity
# atan2(atan_diff(f1, f2)) == atan2(f1) - atan2(f2)
x1, y1 = f1
x2, y2 = f2
return (x1*x2 + y1*y2, y1*x2 - x1*y2)



def subtract(s1, s2):
Expand Down Expand Up @@ -144,7 +147,7 @@ def distance(p1, p2):
return math.sqrt((y2 - y1)**2 + (x2 - x1)**2)


def initial_direction(my_seg, other_segs):
def initial_direction(pt, segs):
# Computing the winding number of a given point requires 2 angle computations per line segment (one per point).
# This method makes 2 angle computations for every segment in other_segs to compute the winding number at my_seg[1].
# It also makes one angle computation from my_seg[0] to my_seg[1].
Expand All @@ -153,11 +156,15 @@ def initial_direction(my_seg, other_segs):
# Also, generally angle computation would take the form of [atan2(start-pt)-atan2(end-pt)]+... , but multiple atan2 calls
# can be avoided through use of atan2 identities.
# Since the final angle would be converted back into a vector, no atan2 call is required.
pt = my_seg[1]
accum = subtract(my_seg[0], my_seg[1])
for seg_start, seg_end in other_segs:
accum = atan_sum(accum, subtract(seg_start, pt))
accum = atan_sum(accum, atan_neg(subtract(seg_end, pt)))
accum = (1, 0)
for seg_start, seg_end in segs:
# Low quality meshes may have multiple segments with the same start or end point, so we should not attempt
# to compute the angle from pt because the result is undefined.
if seg_start != pt:
accum = atan_sum(accum, subtract(seg_start, pt))
if seg_end != pt:
# This will trigger for at least one segment because pt comes from this same list of segments.
accum = atan_diff(accum, subtract(seg_end, pt))
# Without this accum can get arbitrarily large and lead to floating point problems
accum = normalize(accum)
return np.array(accum)
Expand All @@ -176,12 +183,8 @@ def winding_contour(pos, segs):


def winding_number_search(start, ends, segs, polyline_endpoints, max_iterations):
# find the segment I am a part of
my_seg = next(filter(lambda seg: seg[1] == start, segs))
# find all other segments
other_segs = list(filter(lambda seg: seg[1] != start, segs))
# Find the initial direction to start marching towards.
direction = initial_direction(my_seg, other_segs)
direction = initial_direction(start, segs)
# Move slightly toward that direction to pick the contour that we will use below
pos = start + (direction * 0.1)
seg_outs = [(tuple(start), tuple(pos))]
Expand Down
44 changes: 29 additions & 15 deletions test/test_polygon_repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,39 +35,39 @@ def test_find_polylines_out_of_order(self):
actual_polylines = polygon_repair.find_polylines(line_segments)
self.assertEqual(actual_polylines, expected_polylines)

def test_get_direction(self):
other_segs = [((1, 0), (1, 1)), ((1, 1), (0, 1)), ]
my_seg = ((0, 1), (0, 0))
actual = polygon_repair.initial_direction(my_seg, other_segs)
def test_initial_direction(self):
segs = [((0, 1), (0, 0)), ((1, 0), (1, 1)), ((1, 1), (0, 1)), ]
pt = (0, 0)
actual = polygon_repair.initial_direction(pt, segs)
actual = tuple(actual)
expected = (1.0, 0)
self.tuples_almost_equal(actual, expected)

def test_get_direction2(self):
other_segs = [((1, 0), (0, 0))]
my_seg = ((0, 1), (1, 1))
actual = polygon_repair.initial_direction(my_seg, other_segs)
def test_initial_direction2(self):
segs = [((0, 1), (1, 1)), ((1, 0), (0, 0))]
pt = (1, 1)
actual = polygon_repair.initial_direction(pt, segs)
actual = tuple(actual)
expected = polygon_repair.normalize((-1, -1))
self.tuples_almost_equal(actual, expected)

def test_get_direction3(self):
other_segs = [((1, 1), (1, 0))]
my_seg = ((1, 0), (0, 0))
actual = polygon_repair.initial_direction(my_seg, other_segs)
def test_initial_direction3(self):
segs = [((1, 0), (0, 0)), ((1, 1), (1, 0))]
pt = (0, 0)
actual = polygon_repair.initial_direction(pt, segs)
actual = tuple(actual)
expected = polygon_repair.normalize((1, 1))
self.tuples_almost_equal(actual, expected)

def test_grad_90_norm(self):
def test_winding_contour(self):
segs = [((0, 0), (1, 0)), ((1, 0), (1, 1))]
pos = np.array((1, 1)) - np.array((0.1, 0.1))
# Treats starts as repellers and ends as attractors
actual = polygon_repair.winding_contour(pos, segs)
expected = polygon_repair.normalize((-1, -1))
self.tuples_almost_equal(actual, expected)

def test_grad_90_norm2(self):
def test_winding_contour2(self):
segs = [((0, 0), (1, 0)), ((1, 0), (1, 1)), ((1, 1), (0, 1))]
pos = (0, 0.5)
actual = polygon_repair.winding_contour(pos, segs)
Expand All @@ -84,13 +84,27 @@ def test_grad_90_norm2(self):
expected = polygon_repair.normalize((0, -1))
self.tuples_almost_equal(actual, expected)

def test_grad_90_norm3(self):
def test_winding_contour3(self):
segs = [((0, 0), (1, 0)), ((1, 1), (0, 1))]
pos = (0.00001, 0.00001)
actual = polygon_repair.winding_contour(pos, segs)
expected = polygon_repair.normalize((-1, -1))
self.tuples_almost_equal(actual, expected)

def test_repair_all_close_case(self):
segs = [((0, 0), (1, 0)), ((1, .5), (0, .5))]
repair = polygon_repair.PolygonRepair(segs, (10, 10))
repair.repair_all()
expected = [[(0, 0), (1, 0), (1, 0.5), (0, 0.5), (0, 0)]]
self.assertEqual(repair.loops, expected)

def test_repair_all_far_case(self):
segs = [((0, 0), (1, 0)), ((1, 1.5), (0, 1.5))]
repair = polygon_repair.PolygonRepair(segs, (10, 10))
repair.repair_all()
expected = [[(0, 0), (1, 0), (0, 0)], [(1, 1.5), (0, 1.5), (1, 1.5)]]
self.assertEqual(repair.loops, expected)


if __name__ == '__main__':
unittest.main()

0 comments on commit 19364ac

Please sign in to comment.