Arc line intersect, take 2 (#60)

* add Line.point_to_t() and tests

* add Arc.point_to_t() and tests

* add a bunch of failing arc/line intersection tests

This commit contains a bunch of failing arc/line intersections that I
and other people have run into.

All these tests are fixed in the following commit.

* better implementation of Arc.intersect(Line)

Fixes mathandy/svgpathtools#35.

This commit fixes all the arc/line intersection test cases added in the
previous commit.

This implementation provides special handling in Arc.intersect() when
`self` is a non-rotated Arc and `other_seg` is a Line.  In this case
it uses the straight-forward closed-form solution to identify the
intersection points.

Rotated Arcs and Arcs intersecting with non-Line objects still use
the pre-existing intersection code, that part is totally untouched by
this commit.
pull/63/head
Sebastian Kuzminsky 2018-08-22 01:19:05 -06:00 committed by Andy Port
parent 4bc146fd62
commit 2da39e4c02
2 changed files with 579 additions and 2 deletions

View File

@ -4,7 +4,7 @@ Arc."""
# External dependencies # External dependencies
from __future__ import division, absolute_import, print_function from __future__ import division, absolute_import, print_function
from math import sqrt, cos, sin, acos, degrees, radians, log, pi from math import sqrt, cos, sin, acos, asin, degrees, radians, log, pi
from cmath import exp, sqrt as csqrt, phase from cmath import exp, sqrt as csqrt, phase
from collections import MutableSequence from collections import MutableSequence
from warnings import warn from warnings import warn
@ -686,6 +686,29 @@ class Line(object):
ymax = max(self.start.imag, self.end.imag) ymax = max(self.start.imag, self.end.imag)
return xmin, xmax, ymin, ymax return xmin, xmax, ymin, ymax
def point_to_t(self, point):
"""If the point lies on the Line, returns its `t` parameter.
If the point does not lie on the Line, returns None."""
# Single-precision floats have only 7 significant figures of
# resolution, so test that we're within 6 sig figs.
if np.isclose(point, self.start, rtol=0, atol=1e-6):
return 0.0
elif np.isclose(point, self.end, rtol=0, atol=1e-6):
return 1.0
# Finding the point "by hand" here is much faster than calling
# radialrange(), see the discussion on PR #40:
# https://github.com/mathandy/svgpathtools/pull/40#issuecomment-358134261
p = self.poly()
# p(t) = (p_1 * t) + p_0 = point
# t = (point - p_0) / p_1
t = (point - p[0]) / p[1]
if np.isclose(t.imag, 0) and (t.real >= 0.0) and (t.real <= 1.0):
return t.real
return None
def cropped(self, t0, t1): def cropped(self, t0, t1):
"""returns a cropped copy of this segment which starts at """returns a cropped copy of this segment which starts at
self.point(t0) and ends at self.point(t1).""" self.point(t0) and ends at self.point(t1)."""
@ -1448,6 +1471,128 @@ class Arc(object):
y = rx*sinphi*cos(angle) + ry*cosphi*sin(angle) + self.center.imag y = rx*sinphi*cos(angle) + ry*cosphi*sin(angle) + self.center.imag
return complex(x, y) return complex(x, y)
def point_to_t(self, point):
"""If the point lies on the Arc, returns its `t` parameter.
If the point does not lie on the Arc, returns None.
This function only works on Arcs with rotation == 0.0"""
def in_range(min, max, val):
return (min <= val) and (max >= val)
# Single-precision floats have only 7 significant figures of
# resolution, so test that we're within 6 sig figs.
if np.isclose(point, self.start, rtol=0.0, atol=1e-6):
return 0.0
elif np.isclose(point, self.end, rtol=0.0, atol=1e-6):
return 1.0
if self.rotation != 0.0:
raise ValueError("Arc.point_to_t() only works on non-rotated Arcs.")
v = point - self.center
distance_from_center = sqrt((v.real * v.real) + (v.imag * v.imag))
min_radius = min(self.radius.real, self.radius.imag)
max_radius = max(self.radius.real, self.radius.imag)
if (distance_from_center < min_radius) and not np.isclose(distance_from_center, min_radius):
return None
if (distance_from_center > max_radius) and not np.isclose(distance_from_center, max_radius):
return None
# x = center_x + radius_x cos(radians(theta + t delta))
# y = center_y + radius_y sin(radians(theta + t delta))
#
# For x:
# cos(radians(theta + t delta)) = (x - center_x) / radius_x
# radians(theta + t delta) = acos((x - center_x) / radius_x)
# theta + t delta = degrees(acos((x - center_x) / radius_x))
# t_x = (degrees(acos((x - center_x) / radius_x)) - theta) / delta
#
# Similarly for y:
# t_y = (degrees(asin((y - center_y) / radius_y)) - theta) / delta
x = point.real
y = point.imag
#
# +Y points down!
#
# sweep mean clocwise
# sweep && (delta > 0)
# !sweep && (delta < 0)
#
# -180 <= theta_1 <= 180
#
# large_arc && (-360 <= delta <= 360)
# !large_arc && (-180 < delta < 180)
#
end_angle = self.theta + self.delta
min_angle = min(self.theta, end_angle)
max_angle = max(self.theta, end_angle)
acos_arg = (x - self.center.real) / self.radius.real
if acos_arg > 1.0:
acos_arg = 1.0
elif acos_arg < -1.0:
acos_arg = -1.0
x_angle_0 = degrees(acos(acos_arg))
while x_angle_0 < min_angle:
x_angle_0 += 360.0
while x_angle_0 > max_angle:
x_angle_0 -= 360.0
x_angle_1 = -1.0 * x_angle_0
while x_angle_1 < min_angle:
x_angle_1 += 360.0
while x_angle_1 > max_angle:
x_angle_1 -= 360.0
t_x_0 = (x_angle_0 - self.theta) / self.delta
t_x_1 = (x_angle_1 - self.theta) / self.delta
asin_arg = (y - self.center.imag) / self.radius.imag
if asin_arg > 1.0:
asin_arg = 1.0
elif asin_arg < -1.0:
asin_arg = -1.0
y_angle_0 = degrees(asin(asin_arg))
while y_angle_0 < min_angle:
y_angle_0 += 360.0
while y_angle_0 > max_angle:
y_angle_0 -= 360.0
y_angle_1 = 180 - y_angle_0
while y_angle_1 < min_angle:
y_angle_1 += 360.0
while y_angle_1 > max_angle:
y_angle_1 -= 360.0
t_y_0 = (y_angle_0 - self.theta) / self.delta
t_y_1 = (y_angle_1 - self.theta) / self.delta
t = None
if np.isclose(t_x_0, t_y_0):
t = (t_x_0 + t_y_0) / 2.0
elif np.isclose(t_x_0, t_y_1):
t= (t_x_0 + t_y_1) / 2.0
elif np.isclose(t_x_1, t_y_0):
t = (t_x_1 + t_y_0) / 2.0
elif np.isclose(t_x_1, t_y_1):
t = (t_x_1 + t_y_1) / 2.0
else:
# Comparing None and float yields a result in python2,
# but throws TypeError in python3. This fix (suggested by
# @CatherineH) explicitly handles and avoids the case where
# the None-vs-float comparison would have happened below.
return None
if (t >= 0.0) and (t <= 1.0):
return t
return None
def centeriso(self, z): def centeriso(self, z):
"""This is an isometry that translates and rotates self so that it """This is an isometry that translates and rotates self so that it
is centered on the origin and has its axes aligned with the xy axes.""" is centered on the origin and has its axes aligned with the xy axes."""
@ -1619,7 +1764,123 @@ class Arc(object):
to let me know if you're interested in such a feature -- or even better to let me know if you're interested in such a feature -- or even better
please submit an implementation if you want to code one.""" please submit an implementation if you want to code one."""
if is_bezier_segment(other_seg): # This special case can be easily solved algebraically.
if (self.rotation == 0) and isinstance(other_seg, Line):
a = self.radius.real
b = self.radius.imag
# Ignore the ellipse's center point (to pretend that it's
# centered at the origin), and translate the Line to match.
l = Line(start=(other_seg.start-self.center), end=(other_seg.end-self.center))
# This gives us the translated Line as a parametric equation.
# s = p1 t + p0
p = l.poly()
if p[1].real == 0.0:
# The `x` value doesn't depend on `t`, the line is vertical.
c = p[0].real
x_values = [c]
# Substitute the line `x = c` into the equation for the
# (origin-centered) ellipse.
#
# x^2/a^2 + y^2/b^2 = 1
# c^2/a^2 + y^2/b^2 = 1
# y^2/b^2 = 1 - c^2/a^2
# y^2 = b^2(1 - c^2/a^2)
# y = +-b sqrt(1 - c^2/a^2)
discriminant = 1 - (c * c)/(a * a)
if discriminant < 0:
return []
elif discriminant == 0:
y_values = [0]
else:
val = b * sqrt(discriminant)
y_values = [val, -val]
else:
# This is a non-vertical line.
#
# Convert the Line's parametric equation to the "y = mx + c" format.
# x = p1.real t + p0.real
# y = p1.imag t + p0.imag
#
# t = (x - p0.real) / p1.real
# t = (y - p0.imag) / p1.imag
#
# (y - p0.imag) / p1.imag = (x - p0.real) / p1.real
# (y - p0.imag) = ((x - p0.real) * p1.imag) / p1.real
# y = ((x - p0.real) * p1.imag) / p1.real + p0.imag
# y = (x p1.imag - p0.real * p1.imag) / p1.real + p0.imag
# y = x p1.imag/p1.real - p0.real p1.imag / p1.real + p0.imag
# m = p1.imag/p1.real
# c = -m p0.real + p0.imag
m = p[1].imag / p[1].real
c = (-m * p[0].real) + p[0].imag
# Substitute the line's y(x) equation into the equation for
# the ellipse. We can pretend the ellipse is centered at the
# origin, since we shifted the Line by the ellipse's center.
#
# x^2/a^2 + y^2/b^2 = 1
# x^2/a^2 + (mx+c)^2/b^2 = 1
# (b^2 x^2 + a^2 (mx+c)^2)/(a^2 b^2) = 1
# b^2 x^2 + a^2 (mx+c)^2 = a^2 b^2
# b^2 x^2 + a^2(m^2 x^2 + 2mcx + c^2) = a^2 b^2
# b^2 x^2 + a^2 m^2 x^2 + 2a^2 mcx + a^2 c^2 - a^2 b^2 = 0
# (a^2 m^2 + b^2)x^2 + 2a^2 mcx + a^2(c^2 - b^2) = 0
#
# The quadratic forumla tells us: x = (-B +- sqrt(B^2 - 4AC)) / 2A
# Where:
# A = a^2 m^2 + b^2
# B = 2 a^2 mc
# C = a^2(c^2 - b^2)
#
# The determinant is: B^2 - 4AC
#
# The solution simplifies to:
# x = (-a^2 mc +- a b sqrt(a^2 m^2 + b^2 - c^2)) / (a^2 m^2 + b^2)
#
# Solving the line for x(y) and substituting *that* into
# the equation for the ellipse gives this solution for y:
# y = (b^2 c +- abm sqrt(a^2 m^2 + b^2 - c^2)) / (a^2 m^2 + b^2)
denominator = (a * a * m * m) + (b * b)
discriminant = denominator - (c * c)
if discriminant < 0:
return []
x_sqrt = a * b * sqrt(discriminant)
x1 = (-(a * a * m * c) + x_sqrt) / denominator
x2 = (-(a * a * m * c) - x_sqrt) / denominator
x_values = [x1]
if x1 != x2:
x_values.append(x2)
y_sqrt = x_sqrt * m
y1 = ((b * b * c) + y_sqrt) / denominator
y2 = ((b * b * c) - y_sqrt) / denominator
y_values = [y1]
if y1 != y2:
y_values.append(y2)
intersections = []
for x in x_values:
for y in y_values:
p = complex(x, y) + self.center
my_t = self.point_to_t(p)
if my_t == None:
continue
other_t = other_seg.point_to_t(p)
if other_t == None:
continue
intersections.append([my_t, other_t])
return intersections
elif is_bezier_segment(other_seg):
u1poly = self.u1transform(other_seg.poly()) u1poly = self.u1transform(other_seg.poly())
u1poly_mag2 = real(u1poly)**2 + imag(u1poly)**2 u1poly_mag2 = real(u1poly)**2 + imag(u1poly)**2
t2s = polyroots01(u1poly_mag2 - 1) t2s = polyroots01(u1poly_mag2 - 1)

View File

@ -4,6 +4,7 @@ import unittest
from math import sqrt, pi from math import sqrt, pi
from operator import itemgetter from operator import itemgetter
import numpy as np import numpy as np
import random
# Internal dependencies # Internal dependencies
from svgpathtools import * from svgpathtools import *
@ -16,6 +17,48 @@ from svgpathtools.path import _NotImplemented4ArcException
# to be correct visually with the disvg() function. # to be correct visually with the disvg() function.
def random_line():
x = (random.random() - 0.5) * 2000
y = (random.random() - 0.5) * 2000
start = complex(x, y)
x = (random.random() - 0.5) * 2000
y = (random.random() - 0.5) * 2000
end = complex(x, y)
return Line(start, end)
def random_arc():
x = (random.random() - 0.5) * 2000
y = (random.random() - 0.5) * 2000
start = complex(x, y)
x = (random.random() - 0.5) * 2000
y = (random.random() - 0.5) * 2000
end = complex(x, y)
x = (random.random() - 0.5) * 2000
y = (random.random() - 0.5) * 2000
radius = complex(x, y)
large_arc = random.choice([True, False])
sweep = random.choice([True, False])
return Arc(start=start, radius=radius, rotation=0.0, large_arc=large_arc, sweep=sweep, end=end)
def assert_intersections(a_seg, b_seg, intersections, count):
if count != None:
assert(len(intersections) == count)
for i in intersections:
assert(i[0] >= 0.0)
assert(i[0] <= 1.0)
assert(i[1] >= 0.0)
assert(i[1] <= 1.0)
assert(np.isclose(a_seg.point(i[0]), b_seg.point(i[1])))
class LineTest(unittest.TestCase): class LineTest(unittest.TestCase):
def test_lines(self): def test_lines(self):
@ -56,6 +99,73 @@ class LineTest(unittest.TestCase):
self.assertTrue(line != str(line)) self.assertTrue(line != str(line))
self.assertFalse(cubic == line) self.assertFalse(cubic == line)
def test_point_to_t(self):
l = Line(start=(0+0j), end=(0+10j))
assert(l.point_to_t(0+0j) == 0.0)
assert(np.isclose(l.point_to_t(0+5j), 0.5))
assert(l.point_to_t(0+10j) == 1.0)
assert(l.point_to_t(1+0j) == None)
assert(l.point_to_t(0-1j) == None)
assert(l.point_to_t(0+11j) == None)
l = Line(start=(0+0j), end=(10+10j))
assert(l.point_to_t(0+0j) == 0.0)
assert(np.isclose(l.point_to_t(5+5j), 0.5))
assert(l.point_to_t(10+10j) == 1.0)
assert(l.point_to_t(1+0j) == None)
assert(l.point_to_t(0-1j) == None)
assert(l.point_to_t(0+11j) == None)
assert(l.point_to_t(10.001+10.001j) == None)
assert(l.point_to_t(-0.001-0.001j) == None)
l = Line(start=(0+0j), end=(10+0j))
assert(l.point_to_t(0+0j) == 0.0)
assert(np.isclose(l.point_to_t(5+0j), 0.5))
assert(l.point_to_t(10+0j) == 1.0)
assert(l.point_to_t(0+1j) == None)
assert(l.point_to_t(0-1j) == None)
assert(l.point_to_t(0+11j) == None)
assert(l.point_to_t(10.001+0j) == None)
assert(l.point_to_t(-0.001-0j) == None)
l = Line(start=(-2-1j), end=(11-20j))
assert(l.point_to_t(-2-1j) == 0.0)
assert(np.isclose(l.point_to_t(4.5-10.5j), 0.5))
assert(l.point_to_t(11-20j) == 1.0)
assert(l.point_to_t(0+1j) == None)
assert(l.point_to_t(0-1j) == None)
assert(l.point_to_t(0+11j) == None)
assert(l.point_to_t(10.001+0j) == None)
assert(l.point_to_t(-0.001-0j) == None)
l = Line(start=(40.234-32.613j), end=(12.7-32.613j))
assert(l.point_to_t(40.234-32.613j) == 0.0)
assert(np.isclose(l.point_to_t(33.3505-32.613j), 0.25))
assert(np.isclose(l.point_to_t(26.467-32.613j), 0.50))
assert(np.isclose(l.point_to_t(19.5835-32.613j), 0.75))
assert(l.point_to_t(12.7-32.613j) == 1.0)
assert(l.point_to_t(40.25-32.613j) == None)
assert(l.point_to_t(12.65-32.613j) == None)
assert(l.point_to_t(11-20j) == None)
assert(l.point_to_t(0+1j) == None)
assert(l.point_to_t(0-1j) == None)
assert(l.point_to_t(0+11j) == None)
assert(l.point_to_t(10.001+0j) == None)
assert(l.point_to_t(-0.001-0j) == None)
random.seed()
for line_index in range(100):
l = random_line()
print(l)
for t_index in range(100):
orig_t = random.random()
p = l.point(orig_t)
computed_t = l.point_to_t(p)
print("orig_t=%f, p=%s, computed_t=%f" % (orig_t, p, computed_t))
assert(np.isclose(orig_t, computed_t))
class CubicBezierTest(unittest.TestCase): class CubicBezierTest(unittest.TestCase):
def test_approx_circle(self): def test_approx_circle(self):
@ -478,6 +588,71 @@ class ArcTest(unittest.TestCase):
self.assertTrue(segment == Arc(0j, 100 + 50j, 0, 0, 0, 100 + 50j)) self.assertTrue(segment == Arc(0j, 100 + 50j, 0, 0, 0, 100 + 50j))
self.assertTrue(segment != Arc(0j, 100 + 50j, 0, 1, 0, 100 + 50j)) self.assertTrue(segment != Arc(0j, 100 + 50j, 0, 1, 0, 100 + 50j))
def test_point_to_t(self):
a = Arc(start=(0+0j), radius=(5+5j), rotation=0.0, large_arc=True, sweep=True, end=(0+10j))
assert(a.point_to_t(0+0j) == 0.0)
assert(np.isclose(a.point_to_t(5+5j), 0.5))
assert(a.point_to_t(0+10j) == 1.0)
assert(a.point_to_t(-5+5j) == None)
assert(a.point_to_t(0+5j) == None)
assert(a.point_to_t(1+0j) == None)
assert(a.point_to_t(0-1j) == None)
assert(a.point_to_t(0+11j) == None)
a = Arc(start=(0+0j), radius=(5+5j), rotation=0.0, large_arc=True, sweep=False, end=(0+10j))
assert(a.point_to_t(0+0j) == 0.0)
assert(np.isclose(a.point_to_t(-5+5j), 0.5))
assert(a.point_to_t(0+10j) == 1.0)
assert(a.point_to_t(5+5j) == None)
assert(a.point_to_t(0+5j) == None)
assert(a.point_to_t(1+0j) == None)
assert(a.point_to_t(0-1j) == None)
assert(a.point_to_t(0+11j) == None)
a = Arc(start=(-10+0j), radius=(10+20j), rotation=0.0, large_arc=True, sweep=True, end=(10+0j))
assert(a.point_to_t(-10+0j) == 0.0)
assert(np.isclose(a.point_to_t(0-20j), 0.5))
assert(a.point_to_t(10+0j) == 1.0)
assert(a.point_to_t(0+20j) == None)
assert(a.point_to_t(-5+5j) == None)
assert(a.point_to_t(0+5j) == None)
assert(a.point_to_t(1+0j) == None)
assert(a.point_to_t(0-1j) == None)
assert(a.point_to_t(0+11j) == None)
a = Arc(start=(100.834+27.987j), radius=(60.6+60.6j), rotation=0.0, large_arc=False, sweep=False, end=(40.234-32.613j))
assert(a.point_to_t(100.834+27.987j) == 0.0)
assert(np.isclose(a.point_to_t(96.2210993246+4.7963831644j), 0.25))
assert(np.isclose(a.point_to_t(83.0846703014-14.8636715784j), 0.50))
assert(np.isclose(a.point_to_t(63.4246151671-28.0001000158j), 0.75))
assert(a.point_to_t(40.234-32.613j) == 1.00)
assert(a.point_to_t(-10+0j) == None)
assert(a.point_to_t(0+0j) == None)
a = Arc(start=(423.049961698-41.3779390229j), radius=(904.283878032+597.298520765j), rotation=0.0, large_arc=True, sweep=False, end=(548.984030235-312.385118044j))
orig_t = 0.854049465076
p = a.point(orig_t)
computed_t = a.point_to_t(p)
assert(np.isclose(orig_t, computed_t))
a = Arc(start=(-1-750j), radius=(750+750j), rotation=0.0, large_arc=True, sweep=False, end=1-750j)
assert(np.isclose(a.point_to_t(730.5212132777968+169.8191111892562j), 0.71373858))
assert(a.point_to_t(730.5212132777968+169j) == None)
assert(a.point_to_t(730.5212132777968+171j) == None)
random.seed()
for arc_index in range(100):
a = random_arc()
print(a)
for t_index in range(100):
orig_t = random.random()
p = a.point(orig_t)
computed_t = a.point_to_t(p)
print("t:", orig_t)
print("p:", p)
print("computed t:", computed_t)
assert(np.isclose(orig_t, computed_t))
class TestPath(unittest.TestCase): class TestPath(unittest.TestCase):
@ -1205,6 +1380,147 @@ class Test_intersect(unittest.TestCase):
assert(abs(l0.point(i[0][0])-l1.point(i[0][1])) < 1e-9) assert(abs(l0.point(i[0][0])-l1.point(i[0][1])) < 1e-9)
def test_arc_line(self):
l = Line(start=(-20+1j), end=(20+1j))
a = Arc(start=(-10+0), radius=(10+10j), rotation=0.0, large_arc=True, sweep=False, end=(10+0j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 2)
l = Line(start=(-20-1j), end=(20-1j))
a = Arc(start=(-10+0), radius=(10+10j), rotation=0.0, large_arc=True, sweep=False, end=(10+0j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 0)
l = Line(start=(-20+1j), end=(20+1j))
a = Arc(start=(-10+0), radius=(10+10j), rotation=0.0, large_arc=True, sweep=True, end=(10+0j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 0)
l = Line(start=(-20-1j), end=(20-1j))
a = Arc(start=(-10+0), radius=(10+10j), rotation=0.0, large_arc=True, sweep=True, end=(10+0j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 2)
l = Line(start=(-20+0j), end=(20+0j))
a = Arc(start=(-10+0), radius=(10+10j), rotation=0.0, large_arc=True, sweep=True, end=(10+0j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 2)
l = Line(start=(-20+0j), end=(20+0j))
a = Arc(start=(-10+0), radius=(10+10j), rotation=0.0, large_arc=True, sweep=False, end=(10+0j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 2)
l = Line(start=(-20+10j), end=(20+10j))
a = Arc(start=(-10+0), radius=(10+10j), rotation=0.0, large_arc=True, sweep=False, end=(10+0j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 1)
l = Line(start=(229.226097475-282.403591377j), end=(751.681212592+188.907748894j))
a = Arc(start=(-1-750j), radius=(750+750j), rotation=0.0, large_arc=True, sweep=False, end=(1-750j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 1)
# end of arc touches start of horizontal line
l = Line(start=(40.234-32.613j), end=(12.7-32.613j))
a = Arc(start=(100.834+27.987j), radius=(60.6+60.6j), rotation=0.0, large_arc=False, sweep=False, end=(40.234-32.613j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 1)
# vertical line, intersects half-arc once
l = Line(start=(1-100j), end=(1+100j))
a = Arc(start=(10.0+0j), radius=(10+10j), rotation=0, large_arc=False, sweep=True, end=(-10.0+0j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 1)
# vertical line, intersects nearly-full arc twice
l = Line(start=(1-100j), end=(1+100j))
a = Arc(start=(0.1-10j), radius=(10+10j), rotation=0, large_arc=True, sweep=True, end=(-0.1-10j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 2)
# vertical line, start of line touches end of arc
l = Line(start=(15.4+100j), end=(15.4+90.475j))
a = Arc(start=(25.4+90j), radius=(10+10j), rotation=0, large_arc=False, sweep=True, end=(15.4+100j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 1)
l = Line(start=(100-60.913j), end=(40+59j))
a = Arc(start=(100.834+27.987j), radius=(60.6+60.6j), rotation=0.0, large_arc=False, sweep=False, end=(40.234-32.613j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 1)
l = Line(start=(128.57143 + 380.93364j), end=(300.00001 + 389.505069j))
a = Arc(start=(214.28572 + 598.07649j), radius=(85.714287 + 108.57143j), rotation=0.0, large_arc=False, sweep=True, end=(128.57143 + 489.50507j))
intersections = a.intersect(l)
assert_intersections(a, l, intersections, 0)
random.seed()
for arc_index in range(50):
a = random_arc()
print(a)
for line_index in range(100):
l = random_line()
print(l)
intersections = a.intersect(l)
assert_intersections(a, l, intersections, None)
def test_intersect_arc_line_1(self):
"""Verify the return value of intersects() when an Arc ends at
the starting point of a Line."""
a = Arc(start=(0+0j), radius=(10+10j), rotation=0, large_arc=False,
sweep=False, end=(10+10j), autoscale_radius=False)
l = Line(start=(10+10j), end=(20+10j))
i = a.intersect(l)
assert(len(i) == 1)
assert(i[0][0] == 1.0)
assert(i[0][1] == 0.0)
def test_intersect_arc_line_2(self):
"""Verify the return value of intersects() when an Arc is pierced
once by a Line."""
a = Arc(start=(0+0j), radius=(10+10j), rotation=0, large_arc=False,
sweep=False, end=(10+10j), autoscale_radius=False)
l = Line(start=(0+9j), end=(20+9j))
i = a.intersect(l)
assert(len(i) == 1)
assert(i[0][0] >= 0.0)
assert(i[0][0] <= 1.0)
assert(i[0][1] >= 0.0)
assert(i[0][1] <= 1.0)
def test_intersect_arc_line_3(self):
"""Verify the return value of intersects() when an Arc misses
a Line, but the circle that the Arc is part of hits the Line."""
a = Arc(start=(0+0j), radius=(10+10j), rotation=0, large_arc=False,
sweep=False, end=(10+10j), autoscale_radius=False)
l = Line(start=(11+100j), end=(11-100j))
i = a.intersect(l)
assert(len(i) == 0)
def test_intersect_arc_line_disjoint_bboxes(self):
# The arc is very short, which contributes to the problem here.
l = Line(start=(125.314540561+144.192926144j), end=(125.798713132+144.510685287j))
a = Arc(start=(128.26640649+146.908463323j), radius=(2+2j),
rotation=0, large_arc=False, sweep=True,
end=(128.26640606+146.90846449j))
i = l.intersect(a)
assert(i == [])
class TestPathTools(unittest.TestCase): class TestPathTools(unittest.TestCase):
# moved from test_pathtools.py # moved from test_pathtools.py