Dear Bezier and Math Masers,
for a piece of software I'm writing for some time now I need to calculate points on a Bezier curve. These points need to be of exactly identical absolute distance from each other.
At the moment I'm using the common SplitCubicAtT function (T of which is a float between 0 and 1) to part the curve into two with T being 0.5 at first, then calculate the distance between P1 and P1,4 using the Pythagoreic Triangle. Then I repeat the calculation with increasing or decreasing T until the difference in distance is below a certain threshold (= binary search). But this method is 1) slow and 2) not precise.

Step 1: SplitCubicAtT, result is (among other points) the point P1,4. The difference of position in x and y direction between P1,4 and P1 is used in
Step 2: to calculate the absolute distance between P1 and P1,4 using the Pythagoreic Triangle.
From what I understand there should be a possibility to invert the combination of these two funtions so one could hand over the absolute distance and receive the positions of the P1,4.
From what I can see from here this classifies as rocket science, but I thought that someone here has already stepped across this problem earlier and might have found a solution.
As SplitCubicAtT I'm using a simple interpolation function
def SplitCubicAtT(p1, p2, p3, p4, t): u"""\ Split cubic Beziers curve at relative value t, return the two resulting segments. """ from ynlib.maths import Interpolate p12 = (Interpolate(p1[0], p2[0], t), Interpolate(p1[1], p2[1], t)) p23 = (Interpolate(p2[0], p3[0], t), Interpolate(p2[1], p3[1], t)) p34 = (Interpolate(p3[0], p4[0], t), Interpolate(p3[1], p4[1], t)) p123 = (Interpolate(p12[0], p23[0], t), Interpolate(p12[1], p23[1], t)) p234 = (Interpolate(p23[0], p34[0], t), Interpolate(p23[1], p34[1], t)) p1234 = (Interpolate(p123[0], p234[0], t), Interpolate(p123[1], p234[1], t)) return (p1, p12, p123, p1234), (p1234, p234, p34, p4) def SameLengthSegments(segment, distance, precision, firstpoint = None): u"""\ Finds points on a curve segment with equal distance (approximated through binary search, with given precision). If firstpoint is given, that would in most cases be the second last calculated point of the previous segment (to avoid gaps between smooth connection segments), this point is used as the starting point instead of p1. The distance from firstpoint to p1 should then be less than 'distance'. Returns a list with calculated points and the position of the last calculated point. """ from ynlib.maths import Distance from ynlib.beziers import SplitCubicAtT points = [] p1, p2, p3, p4 = segment l = distance t = None segments = SplitCubicAtT(p1, p2, p3, p4, .5) # Use firstpoint firstrun = True if firstrun and firstpoint != None: d = Distance(firstpoint, segments[0][3]) else: d = Distance(p1, segments[0][3]) count = 0 while Distance(segments[1][0], p4) > l: min = 0 max = 1 # if t != None: # min = t t = min + (max - min) / 2.0 segments = SplitCubicAtT(p1, p2, p3, p4, t) if firstrun and firstpoint != None: d = Distance(firstpoint, segments[0][3]) else: d = Distance(p1, segments[0][3]) # Binary search while (d - l) > precision or (d - l) < (precision * -1): if (d-l) > 0: max = t elif (d-l) < 0: min = t t = min + (max - min) / 2.0 segments = SplitCubicAtT(p1, p2, p3, p4, t) # Use last point of previous curve as first point if firstrun and firstpoint != None: d = Distance(firstpoint, segments[0][3]) else: d = Distance(segments[0][0], segments[0][3]) count += 1 p1 = segments[1][0] p2 = segments[1][1] p3 = segments[1][2] points.append(segments[0][3]) firstrun = False # List of points excluding, last point return points, segment[3], count def Distance(p1, p2): u"""\ Return distance between two points definded as (x, y). """ import math return math.sqrt( (p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2 )
instead of the probably more correct (but much slower) equation found in fontTools (results are identical):
def splitCubicAtT(pt1, pt2, pt3, pt4, *ts): """Split the cubic curve between pt1, pt2, pt3 and pt4 at one or more values of t. Return a list of curve segments. >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5)) ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0)) ((50.0, 75.0), (68.75, 75.0), (87.5, 50.0), (100.0, 0.0)) >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75)) ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0)) ((50.0, 75.0), (59.375, 75.0), (68.75, 68.75), (77.34375, 56.25)) ((77.34375, 56.25), (85.9375, 43.75), (93.75, 25.0), (100.0, 0.0)) """ a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4) return _splitCubicAtT(a, b, c, d, *ts) def _splitCubicAtT(a, b, c, d, *ts): ts = list(ts) ts.insert(0, 0.0) ts.append(1.0) segments = [] for i in range(len(ts) - 1): t1 = ts[i] t2 = ts[i+1] delta = (t2 - t1) # calc new a, b, c and d a1 = a * delta**3 b1 = (3*a*t1 + b) * delta**2 c1 = (2*b*t1 + c + 3*a*t1**2) * delta d1 = a*t1**3 + b*t1**2 + c*t1 + d pt1, pt2, pt3, pt4 = calcCubicPoints(a1, b1, c1, d1) segments.append((pt1, pt2, pt3, pt4)) return segments def calcQuadraticParameters(pt1, pt2, pt3): pt1, pt2, pt3 = numpy.array((pt1, pt2, pt3)) c = pt1 b = (pt2 - c) * 2.0 a = pt3 - c - b return a, b, c def calcCubicPoints(a, b, c, d): pt1 = d pt2 = (c / 3.0) + d pt3 = (b + c) / 3.0 + pt2 pt4 = a + d + c + b return pt1, pt2, pt3, pt4

| Attachment | Size |
|---|---|
| curve.png | 5.76 KB |

