Example #1
0
def curveIntersections(p1, p2, p3, p4, x1, y1, x2, y2):
    """
    Computes intersection between a cubic spline and a line segment.
    Adapted from: https://www.particleincell.com/2013/cubic-line-intersection/

    Takes four defcon points describing curve and four scalars describing line
    parameters.
    """

    bx, by = x1 - x2, y2 - y1
    m = x1 * (y1 - y2) + y1 * (x2 - x1)
    a, b, c, d = bezierTools.calcCubicParameters(
        (p1.x, p1.y), (p2.x, p2.y), (p3.x, p3.y), (p4.x, p4.y))

    pc0 = by * a[0] + bx * a[1]
    pc1 = by * b[0] + bx * b[1]
    pc2 = by * c[0] + bx * c[1]
    pc3 = by * d[0] + bx * d[1] + m
    r = bezierTools.solveCubic(pc0, pc1, pc2, pc3)

    sol = []
    for t in r:
        s0 = a[0] * t ** 3 + b[0] * t ** 2 + c[0] * t + d[0]
        s1 = a[1] * t ** 3 + b[1] * t ** 2 + c[1] * t + d[1]
        if (x2 - x1) != 0:
            s = (s0 - x1) / (x2 - x1)
        else:
            s = (s1 - y1) / (y2 - y1)
        if not (t < 0 or t > 1 or s < 0 or s > 1):
            sol.append((s0, s1, t))
    return sol
Example #2
0
def _tValueForPointOnCubicCurve(point, cubicCurve, isHorizontal=0):
    """
    Finds a t value on a curve from a point.
    The points must be originaly be a point on the curve.
    This will only back trace the t value, needed to split the curve in parts
    """
    pt1, pt2, pt3, pt4 = cubicCurve
    a, b, c, d = bezierTools.calcCubicParameters(pt1, pt2, pt3, pt4)
    solutions = bezierTools.solveCubic(a[isHorizontal], b[isHorizontal],
                                       c[isHorizontal],
                                       d[isHorizontal] - point[isHorizontal])
    solutions = [t for t in solutions if 0 <= t < 1]
    if not solutions and not isHorizontal:
        # can happen that a horizontal line doens intersect, try the vertical
        return _tValueForPointOnCubicCurve(point, (pt1, pt2, pt3, pt4),
                                           isHorizontal=1)
    if len(solutions) > 1:
        intersectionLenghts = {}
        for t in solutions:
            tp = _getCubicPoint(t, pt1, pt2, pt3, pt4)
            dist = _distance(tp, point)
            intersectionLenghts[dist] = t
        minDist = min(intersectionLenghts.keys())
        solutions = [intersectionLenghts[minDist]]
    return solutions
Example #3
0
def collectsPointsOnBezierCurve(pt1, pt2, pt3, pt4, tStep):
    """Adapted from calcCubicBounds in fontTools
       by Just van Rossum https://github.com/behdad/fonttools"""
    a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
    steps = [t / float(tStep) for t in xrange(tStep)]

    pointsWithT = [(pt1, 0)]
    for eachT in steps:
        pt = calcPointOnBezier(a, b, c, d, eachT)
        pointsWithT.append((pt, eachT))
    pointsWithT.append((pt4, 1))
    return pointsWithT
Example #4
0
def convertCurveToArcs(pt1, pt2, pt3, pt4):
    aa, bb, cc, dd = calcCubicParameters((pt1.x, pt1.y), (pt2.x, pt2.y),
                                         (pt3.x, pt3.y), (pt4.x, pt4.y))

    startT = 0
    endT = 1
    arcs = []
    while startT != 1:
        center, startAngle, endAngle, radius, approxT = binaryArcApprox(
            pt1, pt2, pt3, pt4, startT, endT)
        startT = approxT
        arcs.append((center, startAngle, endAngle, radius, endT))
    return arcs
Example #5
0
def getExtremaForCubic(pt1, pt2, pt3, pt4, h=True, v=False):
	(ax, ay), (bx, by), c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
	ax *= 3.0
	ay *= 3.0
	bx *= 2.0
	by *= 2.0
	h_roots = []
	v_roots = []
	if h:
		h_roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 < t < 1]
	if v:
		v_roots  = [t for t in solveQuadratic(ax, bx, c[0]) if 0 < t < 1]
	roots = h_roots + v_roots
	return [p[3] for p in splitCubicAtT(pt1, pt2, pt3, pt4, *roots)[:-1]]
Example #6
0
 def getExtremes(self, point=Tuple[int, ...]) -> list:
     # gets extremes of a cubic bezier curve
     pt1, pt2, pt3, pt4 = point
     (ax, ay), (bx, by), (cx, cy), _ = calcCubicParameters(pt1, pt2, pt3, pt4)
     ax3 = ax * 3.0
     ay3 = ay * 3.0
     bx2 = bx * 2.0
     by2 = by * 2.0
     collector: list = []
     if self.horizontal:
         collector += [t for t in solveQuadratic(ax3, bx2, cx) if 0 <= t < 1]
     if self.vertical:
         collector += [t for t in solveQuadratic(ay3, by2, cy) if 0 <= t < 1]
     return sorted(collector)
Example #7
0
def getExtremaForCubic(pt1, pt2, pt3, pt4, h=True, v=False):
    (ax, ay), (bx, by), c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
    ax *= 3.0
    ay *= 3.0
    bx *= 2.0
    by *= 2.0
    h_roots = []
    v_roots = []
    if h:
        h_roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 < t < 1]
    if v:
        v_roots = [t for t in solveQuadratic(ax, bx, c[0]) if 0 < t < 1]
    roots = h_roots + v_roots
    return [p[3] for p in splitCubicAtT(pt1, pt2, pt3, pt4, *roots)[:-1]]
Example #8
0
def getExtremaForCubic(pt1, pt2, pt3, pt4, h=True, v=False):
	(ax, ay), (bx, by), c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
	ax *= 3.0
	ay *= 3.0
	bx *= 2.0
	by *= 2.0
	points = []
	vectors = []
	if h:
		roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 < t < 1]
		points, vectors = get_extrema_points_vectors(roots, pt1, pt2, pt3, pt4)
	if v:
		roots = [t for t in solveQuadratic(ax, bx, c[0]) if 0 < t < 1]
		v_points, v_vectors = get_extrema_points_vectors(roots, pt1, pt2, pt3, pt4)
		points += v_points
		vectors += v_vectors
	return points, vectors
def getExtremaForCubic(pt1, pt2, pt3, pt4, h=True, v=False):
    (ax, ay), (bx, by), c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
    ax *= 3.0
    ay *= 3.0
    bx *= 2.0
    by *= 2.0
    points = []
    vectors = []
    if h:
        roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 < t < 1]
        points, vectors = get_extrema_points_vectors(roots, pt1, pt2, pt3, pt4)
    if v:
        roots = [t for t in solveQuadratic(ax, bx, c[0]) if 0 < t < 1]
        v_points, v_vectors = get_extrema_points_vectors(
            roots, pt1, pt2, pt3, pt4)
        points += v_points
        vectors += v_vectors
    return points, vectors
Example #10
0
def binaryArcApprox(pt1, pt2, pt3, pt4, startT, endT, prevStatus=False):
    assert endT <= 1, f'startT {startT}, endT {endT}'
    """ as soon as isGoodArc() returns True and then False,
    we stop and pick the True arc :) """

    midT = lerp(startT, endT, .5)

    # curve parameters
    aa, bb, cc, dd = calcCubicParameters((pt1.x, pt1.y), (pt2.x, pt2.y),
                                         (pt3.x, pt3.y), (pt4.x, pt4.y))

    # hypothetical arc points
    startPt = calcPointOnBezier(aa, bb, cc, dd, startT)
    midPt = calcPointOnBezier(aa, bb, cc, dd, midT)
    endPt = calcPointOnBezier(aa, bb, cc, dd, endT)

    # test points
    belowT = startT + (endT - startT) * .25
    belowMidPt = calcPointOnBezier(aa, bb, cc, dd, belowT)

    aboveT = startT + (endT - startT) * .75
    aboveMidPt = calcPointOnBezier(aa, bb, cc, dd, aboveT)

    center, startAngle, endAngle, radius = getCCenter(startPt, midPt, endPt)
    arcStatus, errors = isGoodArc(center, radius, belowMidPt, aboveMidPt)

    if arcStatus is True:
        if endT == 1:  # the curve is finished, it is time to stop recursion
            return center, startAngle, endAngle, radius, endT
        else:
            # to the top!
            return binaryArcApprox(pt1, pt2, pt3, pt4, startT,
                                   endT + (endT - startT) * .25, arcStatus)
    else:
        # to the bottom!
        if prevStatus is True:
            return center, startAngle, endAngle, radius, endT

        else:
            return binaryArcApprox(pt1, pt2, pt3, pt4, startT, midT, arcStatus)
Example #11
0
def getExtremaForCubic(
    pt0: Point,
    pt1: Point,
    pt2: Point,
    pt3: Point,
    h: bool = True,
    v: bool = False,
    include_start_end: bool = False,
) -> List[float]:
    """
    Return a list of t values at which the cubic curve defined by pt0, pt1,
    pt2, pt3 has extrema.

    :param h: Calculate extrema for horizontal derivative == 0 (= what type
       designers call vertical extrema!).
    :type h: bool
    :param v: Calculate extrema for vertical derivative == 0 (= what type
       designers call horizontal extrema!).
    :type v: bool
    :param include_start_end: Also calculate extrema that lie at the start or
       end point of the curve.
    :type include_start_end: bool
    """
    (ax, ay), (bx, by), c, _d = calcCubicParameters(pt0, pt1, pt2, pt3)
    ax *= 3.0
    ay *= 3.0
    bx *= 2.0
    by *= 2.0
    roots = []
    if include_start_end:
        if h:
            roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 <= t <= 1]
        if v:
            roots += [t for t in solveQuadratic(ax, bx, c[0]) if 0 <= t <= 1]
    else:
        if h:
            roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 < t < 1]
        if v:
            roots += [t for t in solveQuadratic(ax, bx, c[0]) if 0 < t < 1]
    return roots
Example #12
0
def split_at_extrema(pt1, pt2, pt3, pt4, transform=Transform()):
    """
    Add extrema to a cubic curve, after applying a transformation.
    Example ::

        >>> # A segment in which no extrema will be added.
        >>> split_at_extrema((297, 52), (406, 52), (496, 142), (496, 251))
        [((297, 52), (406, 52), (496, 142), (496, 251))]
        >>> from fontTools.misc.transform import Transform
        >>> split_at_extrema((297, 52), (406, 52), (496, 142), (496, 251), Transform().rotate(-27))
        [((297.0, 52.0), (84.48072108963274, -212.56513799170233), (15.572491694678519, -361.3686192413668), (15.572491694678547, -445.87035970621713)), ((15.572491694678547, -445.8703597062171), (15.572491694678554, -506.84825401175414), (51.4551516055374, -534.3422304091257), (95.14950889754756, -547.6893014808263))]
    """
    # Transform the points for extrema calculation;
    # transform is expected to rotate the points by - nib angle.
    t2, t3, t4 = transform.transformPoints([pt2, pt3, pt4])
    # When pt1 is the current point of the path,  it is already transformed, so
    # we keep it like it is.
    t1 = pt1

    (ax, ay), (bx, by), c, d = calcCubicParameters(t1, t2, t3, t4)
    ax *= 3.0
    ay *= 3.0
    bx *= 2.0
    by *= 2.0

    # vertical
    roots = [t for t in solveQuadratic(ay, by, c[1]) if 0 < t < 1]

    # horizontal
    roots += [t for t in solveQuadratic(ax, bx, c[0]) if 0 < t < 1]

    # Use only unique roots and sort them
    # They should be unique before, or can a root be duplicated (in h and v?)
    roots = sorted(set(roots))

    if not roots:
        return [(t1, t2, t3, t4)]

    return splitCubicAtT(t1, t2, t3, t4, *roots)
Example #13
0
def eqQuadratic(p0, p1, p2, p3):
    # Nearest quadratic bezier (TT curve)
    #print("In: ", p0, p1, p2, p3)
    a, b, c, d = calcCubicParameters((p0.x, p0.y), (p1.x, p1.y), (p2.x, p2.y), (p3.x, p3.y))
    #print("Par: %0.0f x^3 + %0.0f x^2 + %0.0f x + %0.0f" % (a[0], b[0], c[0], d[0]))
    #print("     %0.0f y^3 + %0.0f y^2 + %0.0f y + %0.0f" % (a[1], b[1], c[1], d[1]))
    a = (0.0, 0.0)
    q0, q1, q2, q3 = calcCubicPoints((0.0, 0.0), b, c, d)
    # Find a cubic for a quadratic:
    #cp1 = (q0[0] + 2.0/3 * (q1[0] - q0[0]), q0[1] + 2.0/3 * (q1[1] - q0[1]))
    #cp2 = (q2[0] + 2.0/3 * (q1[0] - q2[0]), q2[1] + 2.0/3 * (q1[1] - q2[1]))
    #print("Out:", q0, q1, q2, q3)
    scaleX = (p3.x - p0.x) / (q3[0] - q0[0])
    scaleY = (p3.y - p0.y) / (q3[1] - q0[1])
    #print(scaleX, scaleY)
    p1.x = (q1[0] - q0[0]) * scaleX + q0[0]
    p1.y = (q1[1] - q0[1]) * scaleY + q0[1]
    p2.x = (q2[0] - q0[0]) * scaleX + q0[0]
    p2.y = (q2[1] - q0[1]) * scaleY + q0[1]
    p3.x = (q3[0] - q0[0]) * scaleX + q0[0]
    p3.y = (q3[1] - q0[1]) * scaleY + q0[1]
    #print(p0, p1, p2, p3)
    return p1, p2
Example #14
0
def _tValueForPointOnCubicCurve(point, cubicCurve, isHorizontal=0):
    """
    Finds a t value on a curve from a point.
    The points must be originaly be a point on the curve.
    This will only back trace the t value, needed to split the curve in parts
    """
    pt1, pt2, pt3, pt4 = cubicCurve
    a, b, c, d = bezierTools.calcCubicParameters(pt1, pt2, pt3, pt4)
    solutions = bezierTools.solveCubic(a[isHorizontal], b[isHorizontal], c[isHorizontal],
        d[isHorizontal] - point[isHorizontal])
    solutions = [t for t in solutions if 0 <= t < 1]
    if not solutions and not isHorizontal:
        # can happen that a horizontal line doens intersect, try the vertical
        return _tValueForPointOnCubicCurve(point, (pt1, pt2, pt3, pt4), isHorizontal=1)
    if len(solutions) > 1:
        intersectionLenghts = {}
        for t in solutions:
            tp = _getCubicPoint(t, pt1, pt2, pt3, pt4)
            dist = _distance(tp, point)
            intersectionLenghts[dist] = t
        minDist = min(intersectionLenghts.keys())
        solutions = [intersectionLenghts[minDist]]
    return solutions
Example #15
0
 def eqQuadratic(self, p0, p1, p2, p3):
     # Nearest quadratic bezier (TT curve)
     #print "In: ", p0, p1, p2, p3
     a, b, c, d = calcCubicParameters((p0.x, p0.y), (p1.x, p1.y), (p2.x, p2.y), (p3.x, p3.y))
     #print "Par: %0.0f x^3 + %0.0f x^2 + %0.0f x + %0.0f" % (a[0], b[0], c[0], d[0])
     #print "     %0.0f y^3 + %0.0f y^2 + %0.0f y + %0.0f" % (a[1], b[1], c[1], d[1])
     a = (0.0, 0.0)
     q0, q1, q2, q3 = calcCubicPoints((0.0, 0.0), b, c, d)
     # Find a cubic for a quadratic:
     #cp1 = (q0[0] + 2.0/3 * (q1[0] - q0[0]), q0[1] + 2.0/3 * (q1[1] - q0[1]))
     #cp2 = (q2[0] + 2.0/3 * (q1[0] - q2[0]), q2[1] + 2.0/3 * (q1[1] - q2[1]))
     #print "Out:", q0, q1, q2, q3
     scaleX = (p3.x - p0.x) / (q3[0] - q0[0])
     scaleY = (p3.y - p0.y) / (q3[1] - q0[1])
     #print scaleX, scaleY
     p1.x = (q1[0] - q0[0]) * scaleX + q0[0]
     p1.y = (q1[1] - q0[1]) * scaleY + q0[1]
     p2.x = (q2[0] - q0[0]) * scaleX + q0[0]
     p2.y = (q2[1] - q0[1]) * scaleY + q0[1]
     p3.x = (q3[0] - q0[0]) * scaleX + q0[0]
     p3.y = (q3[1] - q0[1]) * scaleY + q0[1]
     #print p0, p1, p2, p3
     return p1, p2
    def eqQuadratic(self, p0, p1, p2, p3):
        # Nearest quadratic bezier (TT curve)
        #print "In: ", p0, p1, p2, p3
        a, b, c, d = calcCubicParameters((p0.x, p0.y), (p1.x, p1.y), (p2.x, p2.y), (p3.x, p3.y))
        #print "Par: %0.0f x^3 + %0.0f x^2 + %0.0f x + %0.0f" % (a[0], b[0], c[0], d[0])
        #print "     %0.0f y^3 + %0.0f y^2 + %0.0f y + %0.0f" % (a[1], b[1], c[1], d[1])
        a = (0.0, 0.0)
        q0, q1, q2, q3 = calcCubicPoints((0.0, 0.0), b, c, d)
        # Find a cubic for a quadratic:
        #cp1 = (q0[0] + 2.0/3 * (q1[0] - q0[0]), q0[1] + 2.0/3 * (q1[1] - q0[1]))
        #cp2 = (q2[0] + 2.0/3 * (q1[0] - q2[0]), q2[1] + 2.0/3 * (q1[1] - q2[1]))
        #print "Out:", q0, q1, q2, q3
        scaleX = (p3.x - p0.x) / (q3[0] - q0[0])
        scaleY = (p3.y - p0.y) / (q3[1] - q0[1])

        # THESE LINES ARE ACTUALLY MOVING POINTS
        p1.x = (q1[0] - q0[0]) * scaleX + q0[0]
        p1.y = (q1[1] - q0[1]) * scaleY + q0[1]
        p2.x = (q2[0] - q0[0]) * scaleX + q0[0]
        p2.y = (q2[1] - q0[1]) * scaleY + q0[1]
        p3.x = (q3[0] - q0[0]) * scaleX + q0[0]
        p3.y = (q3[1] - q0[1]) * scaleY + q0[1]
        #print p0, p1, p2, p3
        return p1, p2
Example #17
0
 def params(self) -> Tuple[Point, Point, Point, Point]:
     if self._params is None:
         self._params = calcCubicParameters(self.p0, self.p1, self.p2,
                                            self.p3)
     return self._params
Example #18
0
    segments = zip(points, points[1:] + [points[0]])
    # get the area
    area = sum([x0 * y1 - x1 * y0 for ((x0, y0), (x1, y1)) in segments])
    return area <= 0

# ----------
# Misc. Math
# ----------

def _tValueForPointOnCubicCurve(point, (pt1, pt2, pt3, pt4), isHorizontal=0):
    """
    Finds a t value on a curve from a point.
    The points must be originaly be a point on the curve.
    This will only back trace the t value, needed to split the curve in parts
    """
    a, b, c, d = bezierTools.calcCubicParameters(pt1, pt2, pt3, pt4)
    solutions = bezierTools.solveCubic(a[isHorizontal], b[isHorizontal], c[isHorizontal],
        d[isHorizontal] - point[isHorizontal])
    solutions = [t for t in solutions if 0 <= t < 1]
    if not solutions and not isHorizontal:
        # can happen that a horizontal line doens intersect, try the vertical
        return _tValueForPointOnCubicCurve(point, (pt1, pt2, pt3, pt4), isHorizontal=1)
    if len(solutions) > 1:
        intersectionLenghts = {}
        for t in solutions:
            tp = _getCubicPoint(t, pt1, pt2, pt3, pt4)
            dist = _distance(tp, point)
            intersectionLenghts[dist] = t
        minDist = min(intersectionLenghts.keys())
        solutions = [intersectionLenghts[minDist]]
    return solutions
Example #19
0
    area = sum([x0 * y1 - x1 * y0 for ((x0, y0), (x1, y1)) in segments])
    return area <= 0


# ----------
# Misc. Math
# ----------


def _tValueForPointOnCubicCurve(point, (pt1, pt2, pt3, pt4), isHorizontal=0):
    """
    Finds a t value on a curve from a point.
    The points must be originaly be a point on the curve.
    This will only back trace the t value, needed to split the curve in parts
    """
    a, b, c, d = bezierTools.calcCubicParameters(pt1, pt2, pt3, pt4)
    solutions = bezierTools.solveCubic(a[isHorizontal], b[isHorizontal],
                                       c[isHorizontal],
                                       d[isHorizontal] - point[isHorizontal])
    solutions = [t for t in solutions if 0 <= t < 1]
    if not solutions and not isHorizontal:
        # can happen that a horizontal line doens intersect, try the vertical
        return _tValueForPointOnCubicCurve(point, (pt1, pt2, pt3, pt4),
                                           isHorizontal=1)
    if len(solutions) > 1:
        intersectionLenghts = {}
        for t in solutions:
            tp = _getCubicPoint(t, pt1, pt2, pt3, pt4)
            dist = _distance(tp, point)
            intersectionLenghts[dist] = t
        minDist = min(intersectionLenghts.keys())