def cubic_bezier_t_of_s_dynamic(p0,p1,p2,p3, initial_step = 50): ''' returns a dictionary mapping of arclen values ot t values approximated at steps along the curve. Dumber method than the decastelejue subdivision. ''' s_t_map = {} s_t_map[0] = 0 pi0 = p0 cumul_length = 0 iters = 0 dt = 1/initial_step t = dt while t < 1 and iters < 1000: iters += 1 weights = cubic_bezier_weights(t) pi1 = cubic_bezier_blend_weights(p0, p1, p2, p3, weights) cumul_length += (pi1 - pi0).length v_num = (pi1 - pi0).length/dt v_cls = cubic_bezier_derivative(p0, p1, p2, p3, t).length s_t_map[cumul_length] = t pi0 = pi1 dt *= v_cls/v_num t += dt if iters == 1000: print('maxed iters') #take care of the last point weights = cubic_bezier_weights(1) pi1 = cubic_bezier_blend_weights(p0, p1, p2, p3, weights) cumul_length += (pi1 - pi0).length s_t_map[cumul_length] = 1 dprint('initial dt %f, final dt %f' % (1/initial_step, dt), l=4) return s_t_map
def cubic_bezier_t_of_s_dynamic(p0, p1, p2, p3, initial_step=50): ''' returns a dictionary mapping of arclen values ot t values approximated at steps along the curve. Dumber method than the decastelejue subdivision. ''' s_t_map = {} s_t_map[0] = 0 pi0 = p0 cumul_length = 0 iters = 0 dt = 1 / initial_step t = dt while t < 1 and iters < 1000: iters += 1 weights = cubic_bezier_weights(t) pi1 = cubic_bezier_blend_weights(p0, p1, p2, p3, weights) cumul_length += (pi1 - pi0).length v_num = (pi1 - pi0).length / dt v_cls = cubic_bezier_derivative(p0, p1, p2, p3, t).length s_t_map[cumul_length] = t pi0 = pi1 dt *= v_cls / v_num t += dt if iters == 1000: print('maxed iters') #take care of the last point weights = cubic_bezier_weights(1) pi1 = cubic_bezier_blend_weights(p0, p1, p2, p3, weights) cumul_length += (pi1 - pi0).length s_t_map[cumul_length] = 1 dprint('initial dt %f, final dt %f' % (1 / initial_step, dt), l=4) return s_t_map
def cubic_bezier_fit_points(l_co, error_scale, depth=0, t0=0, t3=1, allow_split=True, force_split=False): ''' fits cubic bezier to given points returns list of tuples of (t0,t3,p0,p1,p2,p3) that best fits the given points l_co where t0 and t3 are the passed-in t0 and t3 and p0,p1,p2,p3 are the control points of bezier ''' assert l_co if len(l_co)<3: p0,p3 = l_co[0],l_co[-1] p12 = (p0+p3)/2 return [(t0,t3,p0,p12,p12,p3)] l_d = [0] + [(v0-v1).length for v0,v1 in zip(l_co[:-1],l_co[1:])] l_ad = [s for d,s in common_utilities.iter_running_sum(l_d)] dist = sum(l_d) if dist <= 0: print('cubic_bezier_fit_points: returning []') return [] #[(t0,t3,l_co[0],l_co[0],l_co[0],l_co[0])] l_t = [ad/dist for ad in l_ad] ex,x0,x1,x2,x3 = cubic_bezier_fit_value([co[0] for co in l_co], l_t) ey,y0,y1,y2,y3 = cubic_bezier_fit_value([co[1] for co in l_co], l_t) ez,z0,z1,z2,z3 = cubic_bezier_fit_value([co[2] for co in l_co], l_t) tot_error = ex+ey+ez dprint('total error = %f (%f)' % (tot_error,error_scale), l=4) if not force_split: if tot_error < error_scale or depth == 4 or len(l_co)<=15 or not allow_split: p0,p1,p2,p3 = Vector((x0,y0,z0)),Vector((x1,y1,z1)),Vector((x2,y2,z2)),Vector((x3,y3,z3)) return [(t0,t3,p0,p1,p2,p3)] # too much error in fit. split sequence in two, and fit each sub-sequence # find a good split point ind_split = -1 mindot = 1.0 for ind in range(5,len(l_co)-5): if l_t[ind] < 0.4: continue if l_t[ind] > 0.6: break #if l_ad[ind] < 0.1: continue #if l_ad[ind] > dist-0.1: break v0 = l_co[ind-4] v1 = l_co[ind+0] v2 = l_co[ind+4] d0 = (v1-v0).normalized() d1 = (v2-v1).normalized() dot01 = d0.dot(d1) if ind_split==-1 or dot01 < mindot: ind_split = ind mindot = dot01 if ind_split == -1: # did not find a good splitting point! p0,p1,p2,p3 = Vector((x0,y0,z0)),Vector((x1,y1,z1)),Vector((x2,y2,z2)),Vector((x3,y3,z3)) return [(t0,t3,p0,p1,p2,p3)] l_co_left = l_co[:ind_split] l_co_right = l_co[ind_split:] tsplit = ind_split / (len(l_co)-1) return cubic_bezier_fit_points(l_co_left, error_scale, depth=depth+1, t0=t0, t3=tsplit) + cubic_bezier_fit_points(l_co_right, error_scale, depth=depth+1, t0=tsplit, t3=t3)
def cubic_bezier_fit_points(l_co, error_scale, depth=0, t0=0, t3=1, allow_split=True, force_split=False): ''' fits cubic bezier to given points returns list of tuples of (t0,t3,p0,p1,p2,p3) that best fits the given points l_co where t0 and t3 are the passed-in t0 and t3 and p0,p1,p2,p3 are the control points of bezier ''' assert l_co if len(l_co) < 3: p0, p3 = l_co[0], l_co[-1] p12 = (p0 + p3) / 2 return [(t0, t3, p0, p12, p12, p3)] l_d = [0] + [(v0 - v1).length for v0, v1 in zip(l_co[:-1], l_co[1:])] l_ad = [s for d, s in common_utilities.iter_running_sum(l_d)] dist = sum(l_d) if dist <= 0: print('cubic_bezier_fit_points: returning []') return [] #[(t0,t3,l_co[0],l_co[0],l_co[0],l_co[0])] l_t = [ad / dist for ad in l_ad] ex, x0, x1, x2, x3 = cubic_bezier_fit_value([co[0] for co in l_co], l_t) ey, y0, y1, y2, y3 = cubic_bezier_fit_value([co[1] for co in l_co], l_t) ez, z0, z1, z2, z3 = cubic_bezier_fit_value([co[2] for co in l_co], l_t) tot_error = ex + ey + ez dprint('total error = %f (%f)' % (tot_error, error_scale), l=4) if not force_split: if tot_error < error_scale or depth == 4 or len( l_co) <= 15 or not allow_split: p0, p1, p2, p3 = Vector((x0, y0, z0)), Vector( (x1, y1, z1)), Vector((x2, y2, z2)), Vector((x3, y3, z3)) return [(t0, t3, p0, p1, p2, p3)] # too much error in fit. split sequence in two, and fit each sub-sequence # find a good split point ind_split = -1 mindot = 1.0 for ind in range(5, len(l_co) - 5): if l_t[ind] < 0.4: continue if l_t[ind] > 0.6: break #if l_ad[ind] < 0.1: continue #if l_ad[ind] > dist-0.1: break v0 = l_co[ind - 4] v1 = l_co[ind + 0] v2 = l_co[ind + 4] d0 = (v1 - v0).normalized() d1 = (v2 - v1).normalized() dot01 = d0.dot(d1) if ind_split == -1 or dot01 < mindot: ind_split = ind mindot = dot01 if ind_split == -1: # did not find a good splitting point! p0, p1, p2, p3 = Vector((x0, y0, z0)), Vector((x1, y1, z1)), Vector( (x2, y2, z2)), Vector((x3, y3, z3)) return [(t0, t3, p0, p1, p2, p3)] l_co_left = l_co[:ind_split] l_co_right = l_co[ind_split:] tsplit = ind_split / (len(l_co) - 1) return cubic_bezier_fit_points( l_co_left, error_scale, depth=depth + 1, t0=t0, t3=tsplit) + cubic_bezier_fit_points( l_co_right, error_scale, depth=depth + 1, t0=tsplit, t3=t3)