from scipy import linalg import numpy as np from manimlib.utils.simple_functions import choose from manimlib.utils.space_ops import find_intersection CLOSED_THRESHOLD = 0.001 def bezier(points): n = len(points) - 1 return lambda t: sum([ ((1 - t)**(n - k)) * (t**k) * choose(n, k) * point for k, point in enumerate(points) ]) def partial_bezier_points(points, a, b): """ Given an array of points which define a bezier curve, and two numbers 0<=a= 1: return (end - 1, 1.0) if alpha <= 0: return (start, 0) value = int(interpolate(start, end, alpha)) residue = ((end - start) * alpha) % 1 return (value, residue) def mid(start, end): return (start + end) / 2.0 def inverse_interpolate(start, end, value): return np.true_divide(value - start, end - start) def match_interpolate(new_start, new_end, old_start, old_end, old_value): return interpolate( new_start, new_end, inverse_interpolate(old_start, old_end, old_value) ) # Figuring out which bezier curves most smoothly connect a sequence of points def get_smooth_handle_points(points): points = np.array(points) num_handles = len(points) - 1 dim = points.shape[1] if num_handles < 1: return np.zeros((0, dim)), np.zeros((0, dim)) # Must solve 2*num_handles equations to get the handles. # l and u are the number of lower an upper diagonal rows # in the matrix to solve. l, u = 2, 1 # diag is a representation of the matrix in diagonal form # See https://www.particleincell.com/2012/bezier-splines/ # for how to arive at these equations diag = np.zeros((l + u + 1, 2 * num_handles)) diag[0, 1::2] = -1 diag[0, 2::2] = 1 diag[1, 0::2] = 2 diag[1, 1::2] = 1 diag[2, 1:-2:2] = -2 diag[3, 0:-3:2] = 1 # last diag[2, -2] = -1 diag[1, -1] = 2 # This is the b as in Ax = b, where we are solving for x, # and A is represented using diag. However, think of entries # to x and b as being points in space, not numbers b = np.zeros((2 * num_handles, dim)) b[1::2] = 2 * points[1:] b[0] = points[0] b[-1] = points[-1] def solve_func(b): return linalg.solve_banded((l, u), diag, b) use_closed_solve_function = is_closed(points) if use_closed_solve_function: # Get equations to relate first and last points matrix = diag_to_matrix((l, u), diag) # last row handles second derivative matrix[-1, [0, 1, -2, -1]] = [2, -1, 1, -2] # first row handles first derivative matrix[0, :] = np.zeros(matrix.shape[1]) matrix[0, [0, -1]] = [1, 1] b[0] = 2 * points[0] b[-1] = np.zeros(dim) def closed_curve_solve_func(b): return linalg.solve(matrix, b) handle_pairs = np.zeros((2 * num_handles, dim)) for i in range(dim): if use_closed_solve_function: handle_pairs[:, i] = closed_curve_solve_func(b[:, i]) else: handle_pairs[:, i] = solve_func(b[:, i]) return handle_pairs[0::2], handle_pairs[1::2] def diag_to_matrix(l_and_u, diag): """ Converts array whose rows represent diagonal entries of a matrix into the matrix itself. See scipy.linalg.solve_banded """ l, u = l_and_u dim = diag.shape[1] matrix = np.zeros((dim, dim)) for i in range(l + u + 1): np.fill_diagonal( matrix[max(0, i - u):, max(0, u - i):], diag[i, max(0, u - i):] ) return matrix def is_closed(points): return np.allclose(points[0], points[-1]) # Given 4 control points for a cubic bezier curve (or arrays of such) # return control points for 2 quadratics (or 2n quadratics) approximating them. def get_quadratic_approximation_of_cubic(a0, h0, h1, a1): a0 = np.array(a0, ndmin=2) h0 = np.array(h0, ndmin=2) h1 = np.array(h1, ndmin=2) a1 = np.array(a1, ndmin=2) mid = (a0 + 3 * h0 + 3 * h1 + a1) / 8 # Tangent vectors at the start, middle and end. T0 = h0 - a0 Tm = 3 * (-a0 - h0 + h1 + a1) / 4 T1 = a1 - h1 # Intersection between tangent lines at end points and tangent in the middle i0 = find_intersection(a0, T0, mid, Tm) i1 = find_intersection(a1, T1, mid, Tm) m, n = np.shape(a0) result = np.zeros((6 * m, n)) result[0::6] = a0 result[1::6] = i0 result[2::6] = mid result[3::6] = mid result[4::6] = i1 result[5::6] = a1 return result def get_smooth_quadratic_bezier_path_through(points): h0, h1 = get_smooth_handle_points(points) a0 = points[:-1] a1 = points[1:] return get_quadratic_approximation_of_cubic(a0, h0, h1, a1)