def get_smooth_quadratic_bezier_handle_points( points: Sequence[np.ndarray]) -> np.ndarray | list[np.ndarray]: """ Figuring out which bezier curves most smoothly connect a sequence of points. Given three successive points, P0, P1 and P2, you can compute that by defining h = (1/4) P0 + P1 - (1/4)P2, the bezier curve defined by (P0, h, P1) will pass through the point P2. So for a given set of four successive points, P0, P1, P2, P3, if we want to add a handle point h between P1 and P2 so that the quadratic bezier (P1, h, P2) is part of a smooth curve passing through all four points, we calculate one solution for h that would produce a parbola passing through P3, call it smooth_to_right, and another that would produce a parabola passing through P0, call it smooth_to_left, and use the midpoint between the two. """ if len(points) == 2: return midpoint(*points) smooth_to_right, smooth_to_left = [ 0.25 * ps[0:-2] + ps[1:-1] - 0.25 * ps[2:] for ps in (points, points[::-1]) ] if np.isclose(points[0], points[-1]).all(): last_str = 0.25 * points[-2] + points[-1] - 0.25 * points[1] last_stl = 0.25 * points[1] + points[0] - 0.25 * points[-2] else: last_str = smooth_to_left[0] last_stl = smooth_to_right[0] handles = 0.5 * np.vstack([smooth_to_right, [last_str]]) handles += 0.5 * np.vstack([last_stl, smooth_to_left[::-1]]) return handles
def add_black_keys(self): key = Rectangle(*self.black_key_dims) key.set_fill(self.black_key_color, 1) key.set_stroke(width=0) self.black_keys = VGroup() for i in range(len(self.white_keys) - 1): if i % self.white_keys_per_octave not in self.black_pattern: continue wk1 = self.white_keys[i] wk2 = self.white_keys[i + 1] bk = key.copy() bk.move_to(midpoint(wk1.get_top(), wk2.get_top()), UP) big_bk = bk.copy() big_bk.stretch((bk.get_width() + self.key_buff) / bk.get_width(), 0) big_bk.stretch((bk.get_height() + self.key_buff) / bk.get_height(), 1) big_bk.move_to(bk, UP) for wk in wk1, wk2: wk.become(Difference(wk, big_bk).match_style(wk)) self.black_keys.add(bk) self.add(*self.black_keys)