def round_corners(self, radius=0.5): vertices = self.get_vertices() arcs = [] for v1, v2, v3 in adjacent_n_tuples(vertices, 3): vect1 = v2 - v1 vect2 = v3 - v2 unit_vect1 = normalize(vect1) unit_vect2 = normalize(vect2) angle = angle_between_vectors(vect1, vect2) # Negative radius gives concave curves angle *= np.sign(radius) # Distance between vertex and start of the arc cut_off_length = radius * np.tan(angle / 2) # Determines counterclockwise vs. clockwise sign = np.sign(np.cross(vect1, vect2)[2]) arc = ArcBetweenPoints( v2 - unit_vect1 * cut_off_length, v2 + unit_vect2 * cut_off_length, angle=sign * angle ) arcs.append(arc) self.clear_points() # To ensure that we loop through starting with last arcs = [arcs[-1], *arcs[:-1]] for arc1, arc2 in adjacent_pairs(arcs): self.append_points(arc1.points) line = Line(arc1.get_end(), arc2.get_start()) # Make sure anchors are evenly distributed len_ratio = line.get_length() / arc1.get_arc_length() line.insert_n_curves( int(arc1.get_num_curves() * len_ratio) ) self.append_points(line.get_points()) return self
def round_corners(self, radius=0.5): vertices = self.get_vertices() arcs = [] for v1, v2, v3 in adjacent_n_tuples(vertices, 3): vect1 = v2 - v1 vect2 = v3 - v2 unit_vect1 = normalize(vect1) unit_vect2 = normalize(vect2) angle = angle_between_vectors(vect1, vect2) # Negative radius gives concave curves angle *= np.sign(radius) # Distance between vertex and start of the arc cut_off_length = radius * np.tan(angle / 2) # Determines counterclockwise vs. clockwise sign = np.sign(np.cross(vect1, vect2)[2]) arcs.append( ArcBetweenPoints(v2 - unit_vect1 * cut_off_length, v2 + unit_vect2 * cut_off_length, angle=sign * angle)) self.clear_points() # To ensure that we loop through starting with last arcs = [arcs[-1], *arcs[:-1]] for arc1, arc2 in adjacent_pairs(arcs): self.append_points(arc1.points) self.add_line_to(arc2.get_start()) return self
def set_points_by_ends(self, start, end, buff=0, path_arc=0): vect = end - start dist = get_norm(vect) if np.isclose(dist, 0): self.set_points_as_corners([start, end]) return self if path_arc: neg = path_arc < 0 if neg: path_arc = -path_arc start, end = end, start radius = (dist / 2) / math.sin(path_arc / 2) alpha = (PI - path_arc) / 2 center = start + radius * normalize( rotate_vector(end - start, alpha)) raw_arc_points = Arc.create_quadratic_bezier_points( angle=path_arc - 2 * buff / radius, start_angle=angle_of_vector(start - center) + buff / radius, ) if neg: raw_arc_points = raw_arc_points[::-1] self.set_points(center + radius * raw_arc_points) else: if buff > 0 and dist > 0: start = start + vect * (buff / dist) end = end - vect * (buff / dist) self.set_points_as_corners([start, end]) return self
def get_shaded_rgb(rgb, point, unit_normal_vect, light_source): to_sun = normalize(light_source - point) factor = 0.5 * np.dot(unit_normal_vect, to_sun)**3 if factor < 0: factor *= 0.5 result = rgb + factor clip_in_place(rgb + factor, 0, 1) return result
def set_start_and_end_attrs(self, start, end): # If either start or end are Mobjects, this # gives their centers rough_start = self.pointify(start) rough_end = self.pointify(end) vect = normalize(rough_end - rough_start) # Now that we know the direction between them, # we can the appropriate boundary point from # start and end, if they're mobjects self.start = self.pointify(start, vect) self.end = self.pointify(end, -vect)
def point_to_number(self, point): start_point, end_point = self.get_start_and_end() full_vect = end_point - start_point unit_vect = normalize(full_vect) def distance_from_start(p): return np.dot(p - start_point, unit_vect) proportion = fdiv(distance_from_start(point), distance_from_start(end_point)) return interpolate(self.x_min, self.x_max, proportion)
def point_to_number(self, point): start_point, end_point = self.get_start_and_end() full_vect = end_point - start_point unit_vect = normalize(full_vect) def distance_from_start(p): return np.dot(p - start_point, unit_vect) proportion = fdiv( distance_from_start(point), distance_from_start(end_point) ) return interpolate(self.x_min, self.x_max, proportion)
def VisitVoronoi(face): visitvalue = face.Visit notvisitvalue = not visitvalue faces = [face] # 访问过 face.Visit = notvisitvalue voronoi = [] while faces: eachface = faces[-1] faces.pop(-1) # 面所在的三条边 e1 = eachface.HalfEdge e2 = e1.Suc e3 = e2.Suc ## 将正在访问的面的三个相邻的面加入faces eis = [e1, e2, e3] for ei in eis: # ei的孪生兄弟 eiTwin = ei.Twin # ei未被访问过 if ei.Visit == visitvalue: ei.Visit = notvisitvalue if eiTwin: ls, le = ei.Start.Point, ei.End.Point if abs(ls[0]) != Infinity and abs(ls[1]) != Infinity and abs(le[0]) != Infinity and abs(le[1]) != Infinity: efc, etfc = ei.Face.Center, eiTwin.Face.Center ese = eiTwin.Suc.End.Point # 边的对点是无穷点 if abs(ese[0]) == Infinity or abs(ese[1]) == Infinity: eis, eie = np.array(ei.Start.Point), np.array(ei.End.Point) vertical = np.cross(eie - eis, np.array([0, 0, 1])) vertical = normalize(vertical) vertical = Infinity * vertical newle = efc + vertical voronoi.append([efc, newle]) else: voronoi.append([efc, etfc]) faces.append(eiTwin.Face) # 访问过 eiTwin.Face.Visit = notvisitvalue eiTwin.Visit = notvisitvalue return voronoi
def rotate(self, angle: float, axis: np.ndarray = OUT, **kwargs): rot = Rotation.from_rotvec(angle * normalize(axis)) self.set_orientation(rot * self.get_orientation()) return self
def get_unit_vector(self): return normalize(self.get_vector())
def scroll_through_patrons(self): logo_box = Square(side_length=2.5) logo_box.to_corner(DOWN + LEFT, buff=MED_LARGE_BUFF) total_width = FRAME_X_RADIUS - logo_box.get_right()[0] black_rect = Rectangle( fill_color=BLACK, fill_opacity=1, stroke_width=3, stroke_color=BLACK, width=FRAME_WIDTH, height=0.6 * FRAME_HEIGHT, ) black_rect.to_edge(UP, buff=0) line = DashedLine(FRAME_X_RADIUS * LEFT, FRAME_X_RADIUS * RIGHT) line.move_to(ORIGIN) thanks = TextMobject(self.thanks_words) thanks.scale(0.9) thanks.next_to(black_rect.get_bottom(), UP, SMALL_BUFF) thanks.set_color(YELLOW) underline = Line(LEFT, RIGHT) underline.match_width(thanks) underline.scale(1.1) underline.next_to(thanks, DOWN, SMALL_BUFF) thanks.add(underline) changed_patron_names = map( self.modify_patron_name, self.specific_patrons, ) patrons = VGroup(*map( TextMobject, changed_patron_names, )) patrons.scale(self.patron_scale_val) for patron in patrons: if patron.get_width() > self.max_patron_width: patron.set_width(self.max_patron_width) columns = VGroup(*[ VGroup(*patrons[i::self.n_patron_columns]) for i in range(self.n_patron_columns) ]) for column in columns: for n, name in enumerate(column): name.shift(n * self.name_y_spacing * DOWN) columns.arrange( RIGHT, buff=LARGE_BUFF, aligned_edge=UP, ) max_width = FRAME_WIDTH - 1 if columns.get_width() > max_width: columns.set_width(max_width) underline.match_width(columns) # thanks.to_edge(RIGHT, buff=MED_SMALL_BUFF) columns.next_to(underline, DOWN, buff=2) columns.generate_target() columns.target.to_edge(DOWN, buff=2) vect = columns.target.get_center() - columns.get_center() distance = get_norm(vect) wait_time = 20 always_shift( columns, direction=normalize(vect), rate=(distance / wait_time) ) self.add(columns, black_rect, line, thanks) self.wait(wait_time)
def get_looking_direction(self): vect = self.pupils.get_center() - self.eyes.get_center() return normalize(vect)
def set_points_by_ends(self, start, end, buff=0, path_arc=0): # Find the right tip length and thickness vect = end - start length = max(get_norm(vect), 1e-8) thickness = self.thickness w_ratio = fdiv(self.max_width_to_length_ratio, fdiv(thickness, length)) if w_ratio < 1: thickness *= w_ratio tip_width = self.tip_width_ratio * thickness tip_length = tip_width / (2 * np.tan(self.tip_angle / 2)) t_ratio = fdiv(self.max_tip_length_to_length_ratio, fdiv(tip_length, length)) if t_ratio < 1: tip_length *= t_ratio tip_width *= t_ratio # Find points for the stem if path_arc == 0: points1 = (length - tip_length) * np.array( [RIGHT, 0.5 * RIGHT, ORIGIN]) points1 += thickness * UP / 2 points2 = points1[::-1] + thickness * DOWN else: # Solve for radius so that the tip-to-tail length matches |end - start| a = 2 * (1 - np.cos(path_arc)) b = -2 * tip_length * np.sin(path_arc) c = tip_length**2 - length**2 R = (-b + np.sqrt(b**2 - 4 * a * c)) / (2 * a) # Find arc points points1 = Arc.create_quadratic_bezier_points(path_arc) points2 = np.array(points1[::-1]) points1 *= (R + thickness / 2) points2 *= (R - thickness / 2) if path_arc < 0: tip_length *= -1 rot_T = rotation_matrix_transpose(PI / 2 - path_arc, OUT) for points in points1, points2: points[:] = np.dot(points, rot_T) points += R * DOWN self.set_points(points1) # Tip self.add_line_to(tip_width * UP / 2) self.add_line_to(tip_length * LEFT) self.tip_index = len(self.get_points()) - 1 self.add_line_to(tip_width * DOWN / 2) self.add_line_to(points2[0]) # Close it out self.append_points(points2) self.add_line_to(points1[0]) if length > 0: # Final correction super().scale(length / self.get_length()) self.rotate(angle_of_vector(vect) - self.get_angle()) self.rotate( PI / 2 - np.arccos(normalize(vect)[2]), axis=rotate_vector(self.get_unit_vector(), -PI / 2), ) self.shift(start - self.get_start()) self.refresh_triangulation()
def get_normal_vector(self): p0, p1, p2 = self.tip.get_start_anchors()[:3] return normalize(np.cross(p2 - p1, p1 - p0))
def get_unit_vector(self) -> np.ndarray: return normalize(self.get_vector())
def scroll_through_patrons(self): logo_box = Square(side_length=2.5) logo_box.to_corner(DOWN + LEFT, buff=MED_LARGE_BUFF) total_width = FRAME_X_RADIUS - logo_box.get_right()[0] black_rect = Rectangle( fill_color=BLACK, fill_opacity=1, stroke_width=3, stroke_color=BLACK, width=FRAME_WIDTH, height=0.6 * FRAME_HEIGHT, ) black_rect.to_edge(UP, buff=0) line = DashedLine(FRAME_X_RADIUS * LEFT, FRAME_X_RADIUS * RIGHT) line.move_to(ORIGIN) thanks = TextMobject(self.thanks_words) thanks.scale(0.9) thanks.next_to(black_rect.get_bottom(), UP, SMALL_BUFF) thanks.set_color(YELLOW) underline = Line(LEFT, RIGHT) underline.match_width(thanks) underline.scale(1.1) underline.next_to(thanks, DOWN, SMALL_BUFF) thanks.add(underline) changed_patron_names = map( self.modify_patron_name, self.specific_patrons, ) patrons = VGroup(*map( TextMobject, changed_patron_names, )) patrons.scale(self.patron_scale_val) for patron in patrons: if patron.get_width() > self.max_patron_width: patron.set_width(self.max_patron_width) columns = VGroup(*[ VGroup(*patrons[i::self.n_patron_columns]) for i in range(self.n_patron_columns) ]) for column in columns: for n, name in enumerate(column): name.shift(n * self.name_y_spacing * DOWN) columns.arrange( RIGHT, buff=LARGE_BUFF, aligned_edge=UP, ) if columns.get_width() > self.max_patron_width: columns.set_width(total_width - 1) thanks.to_edge(RIGHT, buff=MED_SMALL_BUFF) columns.next_to(underline, DOWN, buff=2) columns.generate_target() columns.target.to_edge(DOWN, buff=2) vect = columns.target.get_center() - columns.get_center() distance = get_norm(vect) wait_time = 20 always_shift( columns, direction=normalize(vect), rate=(distance / wait_time) ) self.add(columns, black_rect, line, thanks) self.wait(wait_time)