Spaces:
Running
Running
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| from scipy.special import binom | |
| from numpy.linalg import norm | |
| def num_bezier(n_ctrl, degree=3): | |
| if type(n_ctrl) == np.ndarray: | |
| n_ctrl = len(n_ctrl) | |
| return int((n_ctrl - 1) / degree) | |
| def bernstein(n, i): | |
| bi = binom(n, i) | |
| return lambda t, bi=bi, n=n, i=i: bi * t ** i * (1 - t) ** (n - i) | |
| def bezier(P, t, d=0): | |
| """Bezier curve of degree len(P)-1. d is the derivative order (0 gives positions)""" | |
| n = P.shape[0] - 1 | |
| if d > 0: | |
| Q = np.diff(P, axis=0) * n | |
| return bezier(Q, t, d - 1) | |
| B = np.vstack([bernstein(n, i)(t) for i, p in enumerate(P)]) | |
| return (P.T @ B).T | |
| def cubic_bezier(P, t): | |
| return (1.0 - t) ** 3 * P[0] + 3 * (1.0 - t) ** 2 * t * P[1] + 3 * (1.0 - t) * t ** 2 * P[2] + t ** 3 * P[3] | |
| def bezier_piecewise(Cp, subd=100, degree=3, d=0): | |
| """sample a piecewise Bezier curve given a sequence of control points""" | |
| num = num_bezier(Cp.shape[0], degree) | |
| X = [] | |
| for i in range(num): | |
| P = Cp[i * degree:i * degree + degree + 1, :] | |
| t = np.linspace(0, 1., subd)[:-1] | |
| Y = bezier(P, t, d) | |
| X += [Y] | |
| X.append(Cp[-1]) | |
| X = np.vstack(X) | |
| return X | |
| def compute_beziers(beziers, subd=100, degree=3): | |
| chain = beziers_to_chain(beziers) | |
| return bezier_piecewise(chain, subd, degree) | |
| def plot_control_polygon(Cp, degree=3, lw=0.5, linecolor=np.ones(3) * 0.1): | |
| n_bezier = num_bezier(len(Cp), degree) | |
| for i in range(n_bezier): | |
| cp = Cp[i * degree:i * degree + degree + 1, :] | |
| if degree == 3: | |
| plt.plot(cp[0:2, 0], cp[0:2, 1], ':', color=linecolor, linewidth=lw) | |
| plt.plot(cp[2:, 0], cp[2:, 1], ':', color=linecolor, linewidth=lw) | |
| plt.plot(cp[:, 0], cp[:, 1], 'o', color=[0, 0.5, 1.], markersize=4) | |
| else: | |
| plt.plot(cp[:, 0], cp[:, 1], ':', color=linecolor, linewidth=lw) | |
| plt.plot(cp[:, 0], cp[:, 1], 'o', color=[0, 0.5, 1.]) | |
| def chain_to_beziers(chain, degree=3): | |
| """Convert Bezier chain to list of curve segments (4 control points each)""" | |
| num = num_bezier(chain.shape[0], degree) | |
| beziers = [] | |
| for i in range(num): | |
| beziers.append(chain[i * degree:i * degree + degree + 1, :]) | |
| return beziers | |
| def beziers_to_chain(beziers): | |
| """Convert list of Bezier curve segments to a piecewise bezier chain (shares vertices)""" | |
| n = len(beziers) | |
| chain = [] | |
| for i in range(n): | |
| chain.append(list(beziers[i][:-1])) | |
| chain.append([beziers[-1][-1]]) | |
| return np.array(sum(chain, [])) | |
| def split_cubic(bez, t): | |
| p1, p2, p3, p4 = bez | |
| p12 = (p2 - p1) * t + p1 | |
| p23 = (p3 - p2) * t + p2 | |
| p34 = (p4 - p3) * t + p3 | |
| p123 = (p23 - p12) * t + p12 | |
| p234 = (p34 - p23) * t + p23 | |
| p1234 = (p234 - p123) * t + p123 | |
| return np.array([p1, p12, p123, p1234]), np.array([p1234, p234, p34, p4]) | |
| def approx_arc_length(bez): | |
| c0, c1, c2, c3 = bez | |
| v0 = norm(c1 - c0) * 0.15 | |
| v1 = norm(-0.558983582205757 * c0 + 0.325650248872424 * c1 + 0.208983582205757 * c2 + 0.024349751127576 * c3) | |
| v2 = norm(c3 - c0 + c2 - c1) * 0.26666666666666666 | |
| v3 = norm(-0.024349751127576 * c0 - 0.208983582205757 * c1 - 0.325650248872424 * c2 + 0.558983582205757 * c3) | |
| v4 = norm(c3 - c2) * .15 | |
| return v0 + v1 + v2 + v3 + v4 | |
| def subdivide_bezier(bez, thresh): | |
| stack = [bez] | |
| res = [] | |
| while stack: | |
| bez = stack.pop() | |
| l = approx_arc_length(bez) | |
| if l < thresh: | |
| res.append(bez) | |
| else: | |
| b1, b2 = split_cubic(bez, 0.5) | |
| stack += [b2, b1] | |
| return res | |
| def subdivide_bezier_chain(C, thresh): | |
| beziers = chain_to_beziers(C) | |
| res = [] | |
| for bez in beziers: | |
| res += subdivide_bezier(bez, thresh) | |
| return beziers_to_chain(res) | |