Skip to content

Commit

Permalink
Revert "Arc line intersect, take 2 (mathandy#60)" (mathandy#63)
Browse files Browse the repository at this point in the history
This reverts commit 2da39e4.
  • Loading branch information
mathandy authored Aug 22, 2018
1 parent 2da39e4 commit 1653725
Show file tree
Hide file tree
Showing 2 changed files with 2 additions and 579 deletions.
265 changes: 2 additions & 263 deletions svgpathtools/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# External dependencies
from __future__ import division, absolute_import, print_function
from math import sqrt, cos, sin, acos, asin, degrees, radians, log, pi
from math import sqrt, cos, sin, acos, degrees, radians, log, pi
from cmath import exp, sqrt as csqrt, phase
from collections import MutableSequence
from warnings import warn
Expand Down Expand Up @@ -686,29 +686,6 @@ def bbox(self):
ymax = max(self.start.imag, self.end.imag)
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):
"""returns a cropped copy of this segment which starts at
self.point(t0) and ends at self.point(t1)."""
Expand Down Expand Up @@ -1471,128 +1448,6 @@ def point(self, t):
y = rx*sinphi*cos(angle) + ry*cosphi*sin(angle) + self.center.imag
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):
"""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."""
Expand Down Expand Up @@ -1764,123 +1619,7 @@ def intersect(self, other_seg, tol=1e-12):
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."""

# 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):
if is_bezier_segment(other_seg):
u1poly = self.u1transform(other_seg.poly())
u1poly_mag2 = real(u1poly)**2 + imag(u1poly)**2
t2s = polyroots01(u1poly_mag2 - 1)
Expand Down
Loading

0 comments on commit 1653725

Please sign in to comment.