def get_vector_label(self, vector, label, at_tip=False, direction="left", rotate=False, color=None, label_scale_factor=VECTOR_LABEL_SCALE_FACTOR): if not isinstance(label, TexMobject): if len(label) == 1: label = "\\vec{\\textbf{%s}}" % label label = TexMobject(label) if color is None: color = vector.get_color() label.set_color(color) label.scale(label_scale_factor) label.add_background_rectangle() if at_tip: vect = vector.get_vector() vect /= get_norm(vect) label.next_to(vector.get_end(), vect, buff=SMALL_BUFF) else: angle = vector.get_angle() if not rotate: label.rotate(-angle, about_point=ORIGIN) if direction == "left": label.shift(-label.get_bottom() + 0.1 * UP) else: label.shift(-label.get_top() + 0.1 * DOWN) label.rotate(angle, about_point=ORIGIN) label.shift((vector.get_end() - vector.get_start()) / 2) return label
def get_arc_length(self): if self.path_arc: anchors = self.get_anchors() return sum( [get_norm(a2 - a1) for a1, a2 in zip(anchors, anchors[1:])]) else: return self.get_length()
def get_vector_movement(self, func): for v in self.moving_vectors: v.target = Vector(func(v.get_end()), color=v.get_color()) norm = get_norm(v.target.get_end()) if norm < 0.1: v.target.get_tip().scale_in_place(norm) return self.get_piece_movement(self.moving_vectors)
def get_unit_vector(self): vect = self.get_vector() norm = get_norm(vect) if norm == 0: # TODO, is this the behavior I want? return np.array(ORIGIN) return vect / norm
def __init__(self, start_point, end_point, angle=TAU / 4, **kwargs): if angle == 0: raise Exception("Arc with zero curve angle: use Line instead.") midpoint = 0.5 * (start_point + end_point) distance_vector = end_point - start_point normal_vector = np.array([-distance_vector[1], distance_vector[0], 0]) distance = get_norm(normal_vector) normal_vector /= distance if angle < 0: normal_vector *= -1 radius = distance / 2 / np.sin(0.5 * np.abs(angle)) length = distance / 2 / np.tan(0.5 * np.abs(angle)) arc_center = midpoint + length * normal_vector w = start_point - arc_center if w[0] != 0: start_angle = np.arctan2(w[1], w[0]) else: start_angle = np.pi / 2 Arc.__init__(self, angle, radius=radius, start_angle=start_angle, **kwargs) self.move_arc_center_to(arc_center)
def init_style(self): if self.color_by_magnitude: values_to_rgbs = get_vectorized_rgb_gradient_function( *self.magnitude_range, self.color_map, ) cs = self.coordinate_system for line in self.submobjects: norms = [ get_norm(self.func(*cs.p2c(point))) for point in line.get_points() ] rgbs = values_to_rgbs(norms) rgbas = np.zeros((len(rgbs), 4)) rgbas[:, :3] = rgbs rgbas[:, 3] = self.stroke_opacity line.set_rgba_array(rgbas, "stroke_rgba") else: self.set_stroke(self.stroke_color, opacity=self.stroke_opacity) if self.taper_stroke_width: width = [0, self.stroke_width, 0] else: width = self.stroke_width self.set_stroke(width=width)
def init_points(self): t_min, t_max = self.t_min, self.t_max dt = self.dt discontinuities = filter(lambda t: t_min <= t <= t_max, self.discontinuities) discontinuities = np.array(list(discontinuities)) boundary_times = [ self.t_min, self.t_max, *(discontinuities - dt), *(discontinuities + dt), ] boundary_times.sort() for t1, t2 in zip(boundary_times[0::2], boundary_times[1::2]): # Get an initial sample of points t_range = list(np.linspace(t1, t2, self.min_samples + 1)) samples = [self.function(t) for t in t_range] # Take more samples based on the distances between them norms = [get_norm(p2 - p1) for p1, p2 in zip(samples, samples[1:])] full_t_range = [t1] for s1, s2, norm in zip(t_range, t_range[1:], norms): n_inserts = int(norm / self.step_size) full_t_range += list(np.linspace(s1, s2, n_inserts + 1)[1:]) points = np.array([self.function(t) for t in full_t_range]) valid_indices = np.isfinite(points).all(1) points = points[valid_indices] if len(points) > 0: self.start_new_path(points[0]) self.add_points_as_corners(points[1:]) self.make_smooth() return self
def get_vector_label(self, vector, label, at_tip=False, direction="left", rotate=False, color=None, label_scale_factor=VECTOR_LABEL_SCALE_FACTOR): if not isinstance(label, Tex): if len(label) == 1: label = "\\vec{\\textbf{%s}}" % label label = Tex(label) if color is None: color = vector.get_color() label.set_color(color) label.scale(label_scale_factor) label.add_background_rectangle() if at_tip: vect = vector.get_vector() vect /= get_norm(vect) label.next_to(vector.get_end(), vect, buff=SMALL_BUFF) else: angle = vector.get_angle() if not rotate: label.rotate(-angle, about_point=ORIGIN) if direction == "left": label.shift(-label.get_bottom() + 0.1 * UP) else: label.shift(-label.get_top() + 0.1 * DOWN) label.rotate(angle, about_point=ORIGIN) label.shift((vector.get_end() - vector.get_start()) / 2) return label
def set_points_by_ends(self, start: np.ndarray, end: np.ndarray, buff: float = 0, path_arc: float = 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_vector_movement(self, func): for v in self.moving_vectors: v.target = Vector(func(v.get_end()), color=v.get_color()) norm = get_norm(v.target.get_end()) if norm < 0.1: v.target.get_tip().scale(norm) return self.get_piece_movement(self.moving_vectors)
def set_rectangular_stem_points(self): start, end = self.get_start_and_end() tip_base_points = self.tip[0].get_anchors()[1:3] tip_base = center_of_mass(tip_base_points) tbp1, tbp2 = tip_base_points perp_vect = tbp2 - tbp1 tip_base_width = get_norm(perp_vect) if tip_base_width > 0: perp_vect /= tip_base_width width = min( self.rectangular_stem_width, self.max_stem_width_to_tip_width_ratio * tip_base_width, ) if hasattr(self, "second_tip"): start = center_of_mass( self.second_tip.get_anchors()[1:] ) self.rect.set_points_as_corners([ tip_base - perp_vect * width / 2, start - perp_vect * width / 2, start + perp_vect * width / 2, tip_base + perp_vect * width / 2, ]) self.stem = self.rect # Alternate name return self
def __init__(self, start: np.ndarray, end: np.ndarray, **kwargs): digest_config(self, kwargs) axis = end - start super().__init__(height=get_norm(axis), radius=self.width / 2, axis=axis) self.shift((start + end) / 2)
def get_arc_center(self): first_point = self.points[0] last_point = self.points[-2] v = last_point - first_point radial_unit_vector = v / get_norm(v) arc_center = first_point - self.inner_radius * radial_unit_vector return arc_center
def insert_n_curves_to_point_list(self, n, points): nppc = self.n_points_per_curve if len(points) == 1: return np.repeat(points, nppc * n, 0) bezier_groups = self.get_bezier_tuples_from_points(points) norms = np.array([ get_norm(bg[nppc - 1] - bg[0]) for bg in bezier_groups ]) total_norm = sum(norms) # Calculate insertions per curve (ipc) if total_norm < 1e-6: ipc = [n] + [0] * (len(bezier_groups) - 1) else: ipc = np.round(n * norms / sum(norms)).astype(int) diff = n - sum(ipc) for x in range(diff): ipc[np.argmin(ipc)] += 1 for x in range(-diff): ipc[np.argmax(ipc)] -= 1 new_points = [] for group, n_inserts in zip(bezier_groups, ipc): # What was once a single quadratic curve defined # by "group" will now be broken into n_inserts + 1 # smaller quadratic curves alphas = np.linspace(0, 1, n_inserts + 2) for a1, a2 in zip(alphas, alphas[1:]): new_points += partial_quadratic_bezier_points(group, a1, a2) return np.vstack(new_points)
def put_start_and_end_on(self, start, end): curr_start, curr_end = self.get_start_and_end() curr_vect = curr_end - curr_start if np.all(curr_vect == 0): raise Exception("Cannot position endpoints of closed loop") target_vect = end - start self.scale( get_norm(target_vect) / get_norm(curr_vect), about_point=curr_start, ) self.rotate( angle_of_vector(target_vect) - angle_of_vector(curr_vect), about_point=curr_start ) self.shift(start - curr_start) return self
def look(self, direction): norm = get_norm(direction) if norm == 0: return direction /= norm self.purposeful_looking_direction = direction for pupil, eye in zip(self.pupils.split(), self.eyes.split()): c = eye.get_center() right = eye.get_right() - c up = eye.get_top() - c vect = direction[0] * right + direction[1] * up v_norm = get_norm(vect) p_radius = 0.5 * pupil.get_width() vect *= (v_norm - 0.75 * p_radius) / v_norm pupil.move_to(c + vect) self.pupils[1].align_to(self.pupils[0], DOWN) return self
def handle_resizing(self, point: np.ndarray): if not hasattr(self, "scale_about_point"): return vect = point - self.scale_about_point if self.window.is_key_pressed(CTRL_SYMBOL): for i in (0, 1): scalar = vect[i] / self.scale_ref_vect[i] self.selection.rescale_to_fit( scalar * [self.scale_ref_width, self.scale_ref_height][i], dim=i, about_point=self.scale_about_point, stretch=True, ) else: scalar = get_norm(vect) / get_norm(self.scale_ref_vect) self.selection.set_width(scalar * self.scale_ref_width, about_point=self.scale_about_point)
def put_start_and_end_on(self, start, end): curr_start, curr_end = self.get_start_and_end() curr_vect = curr_end - curr_start if np.all(curr_vect == 0): raise Exception("Cannot position endpoints of closed loop") target_vect = end - start self.scale( get_norm(target_vect) / get_norm(curr_vect), about_point=curr_start, ) self.rotate( angle_of_vector(target_vect) - angle_of_vector(curr_vect), about_point=curr_start ) self.shift(start - curr_start) return self
def look(self, direction): norm = get_norm(direction) if norm == 0: return direction /= norm self.purposeful_looking_direction = direction for pupil, eye in zip(self.pupils.split(), self.eyes.split()): c = eye.get_center() right = eye.get_right() - c up = eye.get_top() - c vect = direction[0] * right + direction[1] * up v_norm = get_norm(vect) p_radius = 0.5 * pupil.get_width() vect *= (v_norm - 0.75 * p_radius) / v_norm pupil.move_to(c + vect) self.pupils[1].align_to(self.pupils[0], DOWN) return self
def get_normal_vector(self): p0, p1, p2 = self.tip[0].get_anchors()[:3] result = np.cross(p2 - p1, p1 - p0) norm = get_norm(result) if norm == 0: return self.normal_vector else: return result / norm
def __init__(self, func, **kwargs): if not hasattr(self, "args"): self.args = serialize_args([func]) if not hasattr(self, "config"): self.config = serialize_config({ **kwargs, }) VGroup.__init__(self, **kwargs) self.func = func dt = self.dt start_points = self.get_start_points( **self.start_points_generator_config) for point in start_points: points = [point] for t in np.arange(0, self.virtual_time, dt): last_point = points[-1] points.append(last_point + dt * func(last_point)) if get_norm(last_point) > self.cutoff_norm: break line = VMobject() step = max(1, int(len(points) / self.n_anchors_per_line)) line.set_points_smoothly(points[::step]) self.add(line) self.set_stroke(self.stroke_color, self.stroke_width) if self.color_by_arc_length: len_to_rgb = get_rgb_gradient_function( self.min_arc_length, self.max_arc_length, colors=self.colors, ) for line in self: arc_length = line.get_arc_length() rgb = len_to_rgb([arc_length])[0] color = rgb_to_color(rgb) line.set_color(color) elif self.color_by_magnitude: image_file = get_color_field_image_file( lambda p: get_norm(func(p)), min_value=self.min_magnitude, max_value=self.max_magnitude, colors=self.colors, ) self.color_using_background_image(image_file)
def get_arc_length(self, n_sample_points=None): if n_sample_points is None: n_sample_points = 4 * self.get_num_curves() + 1 points = np.array( [self.point_from_proportion(a) for a in np.linspace(0, 1, n_sample_points)] ) diffs = points[1:] - points[:-1] norms = np.array([get_norm(d) for d in diffs]) return norms.sum()
def add_tip(self): start, end = self.main_line.get_start_and_end() vect = (end - start) / get_norm(end - start) arrow = Arrow(start, end + MED_SMALL_BUFF * vect, buff=0) tip = arrow.tip tip.set_stroke(width=self.get_stroke_width()) tip.set_color(self.color) self.tip = tip self.add(tip)
def get_vector_label(self, vector, label, at_tip=False, direction="left", rotate=False, color=None, label_scale_factor=VECTOR_LABEL_SCALE_FACTOR): """ Returns naming labels for the passed vector. Parameters ---------- vector Vector Object for which to get the label. at_tip (bool) Whether or not to place the label at the tip of the vector. direction (str="left") If the label should be on the "left" or right of the vector. rotate (bool=False) Whether or not to rotate it to align it with the vector. color (str) The color to give the label. label_scale_factor (Union[int,float]) How much to scale the label by. Returns ------- TexMobject The TexMobject of the label. """ if not isinstance(label, TexMobject): if len(label) == 1: label = "\\vec{\\textbf{%s}}" % label label = TexMobject(label) if color is None: color = vector.get_color() label.set_color(color) label.scale(label_scale_factor) label.add_background_rectangle() if at_tip: vect = vector.get_vector() vect /= get_norm(vect) label.next_to(vector.get_end(), vect, buff=SMALL_BUFF) else: angle = vector.get_angle() if not rotate: label.rotate(-angle, about_point=ORIGIN) if direction == "left": label.shift(-label.get_bottom() + 0.1 * UP) else: label.shift(-label.get_top() + 0.1 * DOWN) label.rotate(angle, about_point=ORIGIN) label.shift((vector.get_end() - vector.get_start()) / 2) return label
def path_along_arc(arc_angle, axis=OUT): """ If vect is vector from start to end, [vect[:,1], -vect[:,0]] is perpendicular to vect in the left direction. """ if abs(arc_angle) < STRAIGHT_PATH_THRESHOLD: return straight_path if get_norm(axis) == 0: axis = OUT unit_axis = axis / get_norm(axis) def path(start_points, end_points, alpha): vects = end_points - start_points centers = start_points + 0.5 * vects if arc_angle != np.pi: centers += np.cross(unit_axis, vects / 2.0) / np.tan(arc_angle / 2) rot_matrix = rotation_matrix(alpha * arc_angle, unit_axis) return centers + np.dot(start_points - centers, rot_matrix.T) return path
def get_length(self): """ Get length Returns ------- Lenght of the line : float """ start, end = self.get_start_and_end() return get_norm(start - end)
def __init__(self, car, target_point, **kwargs): assert isinstance(car, Car) ApplyMethod.__init__(self, car.move_to, target_point, **kwargs) displacement = self.target_mobject.get_right( ) - self.starting_mobject.get_right() distance = get_norm(displacement) if not self.moving_forward: distance *= -1 tire_radius = car.get_tires()[0].get_width() / 2 self.total_tire_radians = -distance / tire_radius
def on_mouse_motion(self, point: np.ndarray, d_point: np.ndarray) -> None: super().on_mouse_motion(point, d_point) # Move selection if self.window.is_key_pressed(ord("g")): self.selection.move_to(point - self.mouse_to_selection) # Move selection restricted to horizontal elif self.window.is_key_pressed(ord("h")): self.selection.set_x((point - self.mouse_to_selection)[0]) # Move selection restricted to vertical elif self.window.is_key_pressed(ord("v")): self.selection.set_y((point - self.mouse_to_selection)[1]) # Scale selection elif self.window.is_key_pressed(ord("t")): # TODO, allow for scaling about the opposite corner vect = point - self.scale_about_point scalar = get_norm(vect) / get_norm(self.scale_ref_vect) self.selection.set_width( scalar * self.scale_ref_width, about_point=self.scale_about_point )
def begin(self): super().begin() car = self.mobject distance = get_norm(op.sub( self.target_mobject.get_right(), self.starting_mobject.get_right(), )) if not self.moving_forward: distance *= -1 tire_radius = car.get_tires()[0].get_width() / 2 self.total_tire_radians = -distance / tire_radius
def put_start_and_end_on(self, start, end): curr_start, curr_end = self.get_start_and_end() if np.all(curr_start == curr_end): # TODO, any problems with resetting # these attrs? self.start = start self.end = end self.generate_points() curr_vect = curr_end - curr_start if np.all(curr_vect == 0): raise Exception("Cannot position endpoints of closed loop") target_vect = end - start self.scale( get_norm(target_vect) / get_norm(curr_vect), about_point=curr_start, ) self.rotate(angle_of_vector(target_vect) - angle_of_vector(curr_vect), about_point=curr_start) self.shift(start - curr_start) return self
def set_submobject_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK): if center is None: center = self.get_center() for mob in self.family_members_with_points(): t = get_norm(mob.get_center() - center) / radius t = min(t, 1) mob_color = interpolate_color(inner_color, outer_color, t) mob.set_color(mob_color, family=False) return self
def draw_lines(self): lines = [] for point in self.get_start_points(): points = [point] total_arc_len = 0 # for t in np.arange(0, self.virtual_time, self.dt): for x in range(self.max_time_steps): last_point = points[-1] new_point = last_point + self.dt * self.point_func(last_point) points.append(new_point) total_arc_len += get_norm(new_point - last_point) if get_norm(last_point) > self.cutoff_norm: break if total_arc_len > self.arc_len: break line = VMobject() step = max(1, int(len(points) / self.n_samples_per_line)) line.set_points_smoothly(points[::step]) lines.append(line) self.set_submobjects(lines)
def begin(self): super().begin() car = self.mobject distance = get_norm(op.sub( self.target_mobject.get_right(), self.starting_mobject.get_right(), )) if not self.moving_forward: distance *= -1 tire_radius = car.get_tires()[0].get_width() / 2 self.total_tire_radians = -distance / tire_radius
def interpolate_mobject(self, alpha): alpha_left = self.rec_rate(alpha) alpha_right = 1 - self.rec_rate(1 - alpha) self.left = interpolate(self.mob_left, self.mob_right, alpha_left) self.right = interpolate(self.mob_left, self.mob_right, alpha_right) self.mobject.become( Rectangle( width=get_norm(self.right - self.left), height=self.height, **self.rectangle_config, ).move_to((self.left + self.right) / 2))
def path_along_arc(arc_angle, axis=OUT): """ If vect is vector from start to end, [vect[:,1], -vect[:,0]] is perpendicular to vect in the left direction. """ if abs(arc_angle) < STRAIGHT_PATH_THRESHOLD: return straight_path if get_norm(axis) == 0: axis = OUT unit_axis = axis / get_norm(axis) def path(start_points, end_points, alpha): vects = end_points - start_points centers = start_points + 0.5 * vects if arc_angle != np.pi: centers += np.cross(unit_axis, vects / 2.0) / np.tan(arc_angle / 2) rot_matrix = rotation_matrix(alpha * arc_angle, unit_axis) return centers + np.dot(start_points - centers, rot_matrix.T) return path
def set_submobject_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK): if center is None: center = self.get_center() for mob in self.family_members_with_points(): t = get_norm(mob.get_center() - center) / radius t = min(t, 1) mob_color = interpolate_color(inner_color, outer_color, t) mob.set_color(mob_color, family=False) return self
def add_line(self, start, end, color=None): start, end = list(map(np.array, [start, end])) length = get_norm(end - start) if length == 0: points = [start] else: epsilon = self.epsilon / length points = [ interpolate(start, end, t) for t in np.arange(0, 1, epsilon) ] self.add_points(points, color=color)
def __init__(self, func, **kwargs): VGroup.__init__(self, **kwargs) self.func = func dt = self.dt start_points = self.get_start_points( **self.start_points_generator_config ) for point in start_points: points = [point] for t in np.arange(0, self.virtual_time, dt): last_point = points[-1] points.append(last_point + dt * func(last_point)) if get_norm(last_point) > self.cutoff_norm: break line = VMobject() step = max(1, int(len(points) / self.n_anchors_per_line)) line.set_points_smoothly(points[::step]) self.add(line) self.set_stroke(self.stroke_color, self.stroke_width) if self.color_by_arc_length: len_to_rgb = get_rgb_gradient_function( self.min_arc_length, self.max_arc_length, colors=self.colors, ) for line in self: arc_length = line.get_arc_length() rgb = len_to_rgb([arc_length])[0] color = rgb_to_color(rgb) line.set_color(color) elif self.color_by_magnitude: image_file = get_color_field_image_file( lambda p: get_norm(func(p)), min_value=self.min_magnitude, max_value=self.max_magnitude, colors=self.colors, ) self.color_using_background_image(image_file)
def add_line(self, start, end, color=None): start, end = list(map(np.array, [start, end])) length = get_norm(end - start) if length == 0: points = [start] else: epsilon = self.epsilon / length points = [ interpolate(start, end, t) for t in np.arange(0, 1, epsilon) ] self.add_points(points, color=color)
def get_3d_vmob_unit_normal(vmob, point_index): n_points = vmob.get_num_points() if len(vmob.get_anchors()) <= 2: return np.array(UP) i = point_index im3 = i - 3 if i > 2 else (n_points - 4) ip3 = i + 3 if i < (n_points - 3) else 3 unit_normal = get_unit_normal( vmob.points[ip3] - vmob.points[i], vmob.points[im3] - vmob.points[i], ) if get_norm(unit_normal) == 0: return np.array(UP) return unit_normal
def get_vector(self, point, **kwargs): output = np.array(self.func(point)) norm = get_norm(output) if norm == 0: output *= 0 else: output *= self.length_func(norm) / norm vector_config = dict(self.vector_config) vector_config.update(kwargs) vect = Vector(output, **vector_config) vect.shift(point) fill_color = rgb_to_color( self.rgb_gradient_function(np.array([norm]))[0] ) vect.set_color(fill_color) return vect
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)
def func(point): centered = point + vect return radius * centered / get_norm(centered)
def get_length(self): return get_norm(self.get_vector())
def get_length(self): start, end = self.get_start_and_end() return get_norm(start - end)
def get_direction(self): vect = self.get_tip() - self.get_center() return vect / get_norm(vect)