mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
remove more unnecessary files
This commit is contained in:
parent
482e7cd486
commit
c367374129
11 changed files with 0 additions and 2650 deletions
4
LICENSE
4
LICENSE
|
|
@ -1,7 +1,3 @@
|
|||
All files of this project under the directory "from_3b1b" are copyright 3Blue1Brown LLC and used by permission for this project only.
|
||||
|
||||
Any other file of this project is available under the MIT license as follow:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 3Blue1Brown LLC
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
This folder contains a collection of various things that were built for a video at some point, but were really one-off and should be given more careful consideration before being brought into the main library. In particular, there is really no guarantee of these being fully functional.
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
from manimlib.animation.animation import Animation
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.scene.scene import Scene
|
||||
|
||||
|
||||
class RearrangeEquation(Scene):
|
||||
def construct(
|
||||
self,
|
||||
start_terms,
|
||||
end_terms,
|
||||
index_map,
|
||||
path_arc=np.pi,
|
||||
start_transform=None,
|
||||
end_transform=None,
|
||||
leave_start_terms=False,
|
||||
transform_kwargs={},
|
||||
):
|
||||
transform_kwargs["path_func"] = path
|
||||
start_mobs, end_mobs = self.get_mobs_from_terms(
|
||||
start_terms, end_terms
|
||||
)
|
||||
if start_transform:
|
||||
start_mobs = start_transform(Mobject(*start_mobs)).split()
|
||||
if end_transform:
|
||||
end_mobs = end_transform(Mobject(*end_mobs)).split()
|
||||
unmatched_start_indices = set(range(len(start_mobs)))
|
||||
unmatched_end_indices = set(range(len(end_mobs)))
|
||||
unmatched_start_indices.difference_update(
|
||||
[n % len(start_mobs) for n in index_map]
|
||||
)
|
||||
unmatched_end_indices.difference_update(
|
||||
[n % len(end_mobs) for n in list(index_map.values())]
|
||||
)
|
||||
mobject_pairs = [
|
||||
(start_mobs[a], end_mobs[b])
|
||||
for a, b in index_map.items()
|
||||
] + [
|
||||
(Point(end_mobs[b].get_center()), end_mobs[b])
|
||||
for b in unmatched_end_indices
|
||||
]
|
||||
if not leave_start_terms:
|
||||
mobject_pairs += [
|
||||
(start_mobs[a], Point(start_mobs[a].get_center()))
|
||||
for a in unmatched_start_indices
|
||||
]
|
||||
|
||||
self.add(*start_mobs)
|
||||
if leave_start_terms:
|
||||
self.add(Mobject(*start_mobs))
|
||||
self.wait()
|
||||
self.play(*[
|
||||
Transform(*pair, **transform_kwargs)
|
||||
for pair in mobject_pairs
|
||||
])
|
||||
self.wait()
|
||||
|
||||
def get_mobs_from_terms(self, start_terms, end_terms):
|
||||
"""
|
||||
Need to ensure that all image mobjects for a tex expression
|
||||
stemming from the same string are point-for-point copies of one
|
||||
and other. This makes transitions much smoother, and not look
|
||||
like point-clouds.
|
||||
"""
|
||||
num_start_terms = len(start_terms)
|
||||
all_mobs = np.array(
|
||||
TexMobject(start_terms).split() + TexMobject(end_terms).split())
|
||||
all_terms = np.array(start_terms + end_terms)
|
||||
for term in set(all_terms):
|
||||
matches = all_terms == term
|
||||
if sum(matches) > 1:
|
||||
base_mob = all_mobs[list(all_terms).index(term)]
|
||||
all_mobs[matches] = [
|
||||
base_mob.copy().replace(target_mob)
|
||||
for target_mob in all_mobs[matches]
|
||||
]
|
||||
return all_mobs[:num_start_terms], all_mobs[num_start_terms:]
|
||||
|
||||
|
||||
class FlipThroughSymbols(Animation):
|
||||
CONFIG = {
|
||||
"start_center": ORIGIN,
|
||||
"end_center": ORIGIN,
|
||||
}
|
||||
|
||||
def __init__(self, tex_list, **kwargs):
|
||||
mobject = TexMobject(self.curr_tex).shift(start_center)
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def interpolate_mobject(self, alpha):
|
||||
new_tex = self.tex_list[np.ceil(alpha * len(self.tex_list)) - 1]
|
||||
|
||||
if new_tex != self.curr_tex:
|
||||
self.curr_tex = new_tex
|
||||
self.mobject = TexMobject(new_tex).shift(self.start_center)
|
||||
if not all(self.start_center == self.end_center):
|
||||
self.mobject.center().shift(
|
||||
(1 - alpha) * self.start_center + alpha * self.end_center
|
||||
)
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
from manimlib.constants import *
|
||||
from manimlib.mobject.numbers import Integer
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject, VGroup
|
||||
from manimlib.scene.scene import Scene
|
||||
from manimlib.utils.simple_functions import choose
|
||||
|
||||
|
||||
DEFAULT_COUNT_NUM_OFFSET = (FRAME_X_RADIUS - 1, FRAME_Y_RADIUS - 1, 0)
|
||||
DEFAULT_COUNT_RUN_TIME = 5.0
|
||||
|
||||
|
||||
class CountingScene(Scene):
|
||||
def count(self, items, item_type="mobject", *args, **kwargs):
|
||||
if item_type == "mobject":
|
||||
self.count_mobjects(items, *args, **kwargs)
|
||||
elif item_type == "region":
|
||||
self.count_regions(items, *args, **kwargs)
|
||||
else:
|
||||
raise Exception("Unknown item_type, should be mobject or region")
|
||||
return self
|
||||
|
||||
def count_mobjects(
|
||||
self, mobjects, mode="highlight",
|
||||
color="red",
|
||||
display_numbers=True,
|
||||
num_offset=DEFAULT_COUNT_NUM_OFFSET,
|
||||
run_time=DEFAULT_COUNT_RUN_TIME,
|
||||
):
|
||||
"""
|
||||
Note, leaves final number mobject as "number" attribute
|
||||
|
||||
mode can be "highlight", "show_creation" or "show", otherwise
|
||||
a warning is given and nothing is animating during the count
|
||||
"""
|
||||
if len(mobjects) > 50: # TODO
|
||||
raise Exception("I don't know if you should be counting \
|
||||
too many mobjects...")
|
||||
if len(mobjects) == 0:
|
||||
raise Exception("Counting mobject list of length 0")
|
||||
if mode not in ["highlight", "show_creation", "show"]:
|
||||
raise Warning("Unknown mode")
|
||||
frame_time = run_time / len(mobjects)
|
||||
if mode == "highlight":
|
||||
self.add(*mobjects)
|
||||
for mob, num in zip(mobjects, it.count(1)):
|
||||
if display_numbers:
|
||||
num_mob = TexMobject(str(num))
|
||||
num_mob.center().shift(num_offset)
|
||||
self.add(num_mob)
|
||||
if mode == "highlight":
|
||||
original_color = mob.color
|
||||
mob.set_color(color)
|
||||
self.wait(frame_time)
|
||||
mob.set_color(original_color)
|
||||
if mode == "show_creation":
|
||||
self.play(ShowCreation(mob, run_time=frame_time))
|
||||
if mode == "show":
|
||||
self.add(mob)
|
||||
self.wait(frame_time)
|
||||
if display_numbers:
|
||||
self.remove(num_mob)
|
||||
if display_numbers:
|
||||
self.add(num_mob)
|
||||
self.number = num_mob
|
||||
return self
|
||||
|
||||
def count_regions(self, regions,
|
||||
mode="one_at_a_time",
|
||||
num_offset=DEFAULT_COUNT_NUM_OFFSET,
|
||||
run_time=DEFAULT_COUNT_RUN_TIME,
|
||||
**unused_kwargsn):
|
||||
if mode not in ["one_at_a_time", "show_all"]:
|
||||
raise Warning("Unknown mode")
|
||||
frame_time = run_time / (len(regions))
|
||||
for region, count in zip(regions, it.count(1)):
|
||||
num_mob = TexMobject(str(count))
|
||||
num_mob.center().shift(num_offset)
|
||||
self.add(num_mob)
|
||||
self.set_color_region(region)
|
||||
self.wait(frame_time)
|
||||
if mode == "one_at_a_time":
|
||||
self.reset_background()
|
||||
self.remove(num_mob)
|
||||
self.add(num_mob)
|
||||
self.number = num_mob
|
||||
return self
|
||||
|
||||
|
||||
def combinationMobject(n, k):
|
||||
return Integer(choose(n, k))
|
||||
|
||||
|
||||
class GeneralizedPascalsTriangle(VMobject):
|
||||
CONFIG = {
|
||||
"nrows": 7,
|
||||
"height": FRAME_HEIGHT - 1,
|
||||
"width": 1.5 * FRAME_X_RADIUS,
|
||||
"portion_to_fill": 0.7,
|
||||
"submob_class": combinationMobject,
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
self.cell_height = float(self.height) / self.nrows
|
||||
self.cell_width = float(self.width) / self.nrows
|
||||
self.bottom_left = (self.cell_width * self.nrows / 2.0) * LEFT + \
|
||||
(self.cell_height * self.nrows / 2.0) * DOWN
|
||||
self.coords_to_mobs = {}
|
||||
self.coords = [
|
||||
(n, k)
|
||||
for n in range(self.nrows)
|
||||
for k in range(n + 1)
|
||||
]
|
||||
for n, k in self.coords:
|
||||
center = self.coords_to_center(n, k)
|
||||
num_mob = self.submob_class(n, k) # TexMobject(str(num))
|
||||
scale_factor = min(
|
||||
1,
|
||||
self.portion_to_fill * self.cell_height / num_mob.get_height(),
|
||||
self.portion_to_fill * self.cell_width / num_mob.get_width(),
|
||||
)
|
||||
num_mob.center().scale(scale_factor).shift(center)
|
||||
if n not in self.coords_to_mobs:
|
||||
self.coords_to_mobs[n] = {}
|
||||
self.coords_to_mobs[n][k] = num_mob
|
||||
self.add(*[
|
||||
self.coords_to_mobs[n][k]
|
||||
for n, k in self.coords
|
||||
])
|
||||
return self
|
||||
|
||||
def coords_to_center(self, n, k):
|
||||
x_offset = self.cell_width * (k + self.nrows / 2.0 - n / 2.0)
|
||||
y_offset = self.cell_height * (self.nrows - n)
|
||||
return self.bottom_left + x_offset * RIGHT + y_offset * UP
|
||||
|
||||
def generate_n_choose_k_mobs(self):
|
||||
self.coords_to_n_choose_k = {}
|
||||
for n, k in self.coords:
|
||||
nck_mob = TexMobject(r"{%d \choose %d}" % (n, k))
|
||||
scale_factor = min(
|
||||
1,
|
||||
self.portion_to_fill * self.cell_height / nck_mob.get_height(),
|
||||
self.portion_to_fill * self.cell_width / nck_mob.get_width(),
|
||||
)
|
||||
center = self.coords_to_mobs[n][k].get_center()
|
||||
nck_mob.center().scale(scale_factor).shift(center)
|
||||
if n not in self.coords_to_n_choose_k:
|
||||
self.coords_to_n_choose_k[n] = {}
|
||||
self.coords_to_n_choose_k[n][k] = nck_mob
|
||||
return self
|
||||
|
||||
def fill_with_n_choose_k(self):
|
||||
if not hasattr(self, "coords_to_n_choose_k"):
|
||||
self.generate_n_choose_k_mobs()
|
||||
self.submobjects = []
|
||||
self.add(*[
|
||||
self.coords_to_n_choose_k[n][k]
|
||||
for n, k in self.coords
|
||||
])
|
||||
return self
|
||||
|
||||
def generate_sea_of_zeros(self):
|
||||
zero = TexMobject("0")
|
||||
self.sea_of_zeros = []
|
||||
for n in range(self.nrows):
|
||||
for a in range((self.nrows - n) / 2 + 1):
|
||||
for k in (n + a + 1, -a - 1):
|
||||
self.coords.append((n, k))
|
||||
mob = zero.copy()
|
||||
mob.shift(self.coords_to_center(n, k))
|
||||
self.coords_to_mobs[n][k] = mob
|
||||
self.add(mob)
|
||||
return self
|
||||
|
||||
def get_lowest_row(self):
|
||||
n = self.nrows - 1
|
||||
lowest_row = VGroup(*[
|
||||
self.coords_to_mobs[n][k]
|
||||
for k in range(n + 1)
|
||||
])
|
||||
return lowest_row
|
||||
|
||||
|
||||
class PascalsTriangle(GeneralizedPascalsTriangle):
|
||||
CONFIG = {
|
||||
"submob_class": combinationMobject,
|
||||
}
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
from manimlib.animation.animation import Animation
|
||||
from manimlib.animation.movement import ComplexHomotopy
|
||||
from manimlib.animation.transform import MoveToTarget
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.coordinate_systems import ComplexPlane
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.scene.scene import Scene
|
||||
|
||||
|
||||
# TODO, refactor this full scene
|
||||
class ComplexTransformationScene(Scene):
|
||||
CONFIG = {
|
||||
"plane_config": {},
|
||||
"background_fade_factor": 0.5,
|
||||
"use_multicolored_plane": False,
|
||||
"vert_start_color": BLUE, # TODO
|
||||
"vert_end_color": BLUE,
|
||||
"horiz_start_color": BLUE,
|
||||
"horiz_end_color": BLUE,
|
||||
"num_anchors_to_add_per_line": 50,
|
||||
"post_transformation_stroke_width": None,
|
||||
"default_apply_complex_function_kwargs": {
|
||||
"run_time": 5,
|
||||
},
|
||||
"background_label_scale_val": 0.5,
|
||||
"include_coordinate_labels": True,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
self.foreground_mobjects = []
|
||||
self.transformable_mobjects = []
|
||||
self.add_background_plane()
|
||||
if self.include_coordinate_labels:
|
||||
self.add_coordinate_labels()
|
||||
|
||||
def add_foreground_mobject(self, mobject):
|
||||
self.add_foreground_mobjects(mobject)
|
||||
|
||||
def add_transformable_mobjects(self, *mobjects):
|
||||
self.transformable_mobjects += list(mobjects)
|
||||
self.add(*mobjects)
|
||||
|
||||
def add_foreground_mobjects(self, *mobjects):
|
||||
self.foreground_mobjects += list(mobjects)
|
||||
Scene.add(self, *mobjects)
|
||||
|
||||
def add(self, *mobjects):
|
||||
Scene.add(self, *list(mobjects) + self.foreground_mobjects)
|
||||
|
||||
def play(self, *animations, **kwargs):
|
||||
Scene.play(
|
||||
self,
|
||||
*list(animations) + list(map(Animation, self.foreground_mobjects)),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def add_background_plane(self):
|
||||
background = ComplexPlane(**self.plane_config)
|
||||
background.fade(self.background_fade_factor)
|
||||
self.add(background)
|
||||
self.background = background
|
||||
|
||||
def add_coordinate_labels(self):
|
||||
self.background.add_coordinates()
|
||||
self.add(self.background)
|
||||
|
||||
def add_transformable_plane(self, **kwargs):
|
||||
self.plane = self.get_transformable_plane()
|
||||
self.add(self.plane)
|
||||
|
||||
def get_transformable_plane(self, x_range=None, y_range=None):
|
||||
"""
|
||||
x_range and y_range would be tuples (min, max)
|
||||
"""
|
||||
plane_config = dict(self.plane_config)
|
||||
shift_val = ORIGIN
|
||||
if x_range is not None:
|
||||
x_min, x_max = x_range
|
||||
plane_config["x_radius"] = x_max - x_min
|
||||
shift_val += (x_max + x_min) * RIGHT / 2.
|
||||
if y_range is not None:
|
||||
y_min, y_max = y_range
|
||||
plane_config["y_radius"] = y_max - y_min
|
||||
shift_val += (y_max + y_min) * UP / 2.
|
||||
plane = ComplexPlane(**plane_config)
|
||||
plane.shift(shift_val)
|
||||
if self.use_multicolored_plane:
|
||||
self.paint_plane(plane)
|
||||
return plane
|
||||
|
||||
def prepare_for_transformation(self, mob):
|
||||
if hasattr(mob, "prepare_for_nonlinear_transform"):
|
||||
mob.prepare_for_nonlinear_transform(
|
||||
self.num_anchors_to_add_per_line
|
||||
)
|
||||
# TODO...
|
||||
|
||||
def paint_plane(self, plane):
|
||||
for lines in planes, plane.secondary_lines:
|
||||
lines.set_color_by_gradient(
|
||||
self.vert_start_color,
|
||||
self.vert_end_color,
|
||||
self.horiz_start_color,
|
||||
self.horiz_end_color,
|
||||
)
|
||||
# plane.axes.set_color_by_gradient(
|
||||
# self.horiz_start_color,
|
||||
# self.vert_start_color
|
||||
# )
|
||||
|
||||
def z_to_point(self, z):
|
||||
return self.background.number_to_point(z)
|
||||
|
||||
def get_transformer(self, **kwargs):
|
||||
transform_kwargs = dict(self.default_apply_complex_function_kwargs)
|
||||
transform_kwargs.update(kwargs)
|
||||
transformer = VGroup()
|
||||
if hasattr(self, "plane"):
|
||||
self.prepare_for_transformation(self.plane)
|
||||
transformer.add(self.plane)
|
||||
transformer.add(*self.transformable_mobjects)
|
||||
return transformer, transform_kwargs
|
||||
|
||||
def apply_complex_function(self, func, added_anims=[], **kwargs):
|
||||
transformer, transform_kwargs = self.get_transformer(**kwargs)
|
||||
transformer.generate_target()
|
||||
# Rescale, apply function, scale back
|
||||
transformer.target.shift(-self.background.get_center_point())
|
||||
transformer.target.scale(1. / self.background.unit_size)
|
||||
transformer.target.apply_complex_function(func)
|
||||
transformer.target.scale(self.background.unit_size)
|
||||
transformer.target.shift(self.background.get_center_point())
|
||||
#
|
||||
|
||||
for mob in transformer.target[0].family_members_with_points():
|
||||
mob.make_smooth()
|
||||
if self.post_transformation_stroke_width is not None:
|
||||
transformer.target.set_stroke(
|
||||
width=self.post_transformation_stroke_width)
|
||||
self.play(
|
||||
MoveToTarget(transformer, **transform_kwargs),
|
||||
*added_anims
|
||||
)
|
||||
|
||||
def apply_complex_homotopy(self, complex_homotopy, added_anims=[], **kwargs):
|
||||
transformer, transform_kwargs = self.get_transformer(**kwargs)
|
||||
|
||||
# def homotopy(x, y, z, t):
|
||||
# output = complex_homotopy(complex(x, y), t)
|
||||
# rescaled_output = self.z_to_point(output)
|
||||
# return (rescaled_output.real, rescaled_output.imag, z)
|
||||
|
||||
self.play(
|
||||
ComplexHomotopy(complex_homotopy, transformer, **transform_kwargs),
|
||||
*added_anims
|
||||
)
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
from manimlib.animation.creation import ShowCreation
|
||||
from manimlib.animation.fading import FadeIn
|
||||
from manimlib.animation.transform import MoveToTarget
|
||||
from manimlib.animation.transform import Transform
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.geometry import Arrow
|
||||
from manimlib.mobject.geometry import Circle
|
||||
from manimlib.mobject.geometry import Dot
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.scene.scene import Scene
|
||||
|
||||
|
||||
class CountingScene(Scene):
|
||||
CONFIG = {
|
||||
"digit_place_colors": [YELLOW, MAROON_B, RED, GREEN, BLUE, PURPLE_D],
|
||||
"counting_dot_starting_position": (FRAME_X_RADIUS - 1) * RIGHT + (FRAME_Y_RADIUS - 1) * UP,
|
||||
"count_dot_starting_radius": 0.5,
|
||||
"dot_configuration_height": 2,
|
||||
"ones_configuration_location": UP + 2 * RIGHT,
|
||||
"num_scale_factor": 2,
|
||||
"num_start_location": 2 * DOWN,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
self.dots = VGroup()
|
||||
self.number = 0
|
||||
self.max_place = 0
|
||||
self.number_mob = VGroup(TexMobject(str(self.number)))
|
||||
self.number_mob.scale(self.num_scale_factor)
|
||||
self.number_mob.shift(self.num_start_location)
|
||||
|
||||
self.dot_templates = []
|
||||
self.dot_template_iterators = []
|
||||
self.curr_configurations = []
|
||||
|
||||
self.arrows = VGroup()
|
||||
|
||||
self.add(self.number_mob)
|
||||
|
||||
def get_template_configuration(self, place):
|
||||
# This should probably be replaced for non-base-10 counting scenes
|
||||
down_right = (0.5) * RIGHT + (np.sqrt(3) / 2) * DOWN
|
||||
result = []
|
||||
for down_right_steps in range(5):
|
||||
for left_steps in range(down_right_steps):
|
||||
result.append(
|
||||
down_right_steps * down_right + left_steps * LEFT
|
||||
)
|
||||
return reversed(result[:self.get_place_max(place)])
|
||||
|
||||
def get_dot_template(self, place):
|
||||
# This should be replaced for non-base-10 counting scenes
|
||||
dots = VGroup(*[
|
||||
Dot(
|
||||
point,
|
||||
radius=0.25,
|
||||
fill_opacity=0,
|
||||
stroke_width=2,
|
||||
stroke_color=WHITE,
|
||||
)
|
||||
for point in self.get_template_configuration(place)
|
||||
])
|
||||
dots.set_height(self.dot_configuration_height)
|
||||
return dots
|
||||
|
||||
def add_configuration(self):
|
||||
new_template = self.get_dot_template(len(self.dot_templates))
|
||||
new_template.move_to(self.ones_configuration_location)
|
||||
left_vect = (new_template.get_width() + LARGE_BUFF) * LEFT
|
||||
new_template.shift(
|
||||
left_vect * len(self.dot_templates)
|
||||
)
|
||||
self.dot_templates.append(new_template)
|
||||
self.dot_template_iterators.append(
|
||||
it.cycle(new_template)
|
||||
)
|
||||
self.curr_configurations.append(VGroup())
|
||||
|
||||
def count(self, max_val, run_time_per_anim=1):
|
||||
for x in range(max_val):
|
||||
self.increment(run_time_per_anim)
|
||||
|
||||
def increment(self, run_time_per_anim=1):
|
||||
moving_dot = Dot(
|
||||
self.counting_dot_starting_position,
|
||||
radius=self.count_dot_starting_radius,
|
||||
color=self.digit_place_colors[0],
|
||||
)
|
||||
moving_dot.generate_target()
|
||||
moving_dot.set_fill(opacity=0)
|
||||
kwargs = {
|
||||
"run_time": run_time_per_anim
|
||||
}
|
||||
|
||||
continue_rolling_over = True
|
||||
first_move = True
|
||||
place = 0
|
||||
while continue_rolling_over:
|
||||
added_anims = []
|
||||
if first_move:
|
||||
added_anims += self.get_digit_increment_animations()
|
||||
first_move = False
|
||||
moving_dot.target.replace(
|
||||
next(self.dot_template_iterators[place])
|
||||
)
|
||||
self.play(MoveToTarget(moving_dot), *added_anims, **kwargs)
|
||||
self.curr_configurations[place].add(moving_dot)
|
||||
|
||||
if len(self.curr_configurations[place].split()) == self.get_place_max(place):
|
||||
full_configuration = self.curr_configurations[place]
|
||||
self.curr_configurations[place] = VGroup()
|
||||
place += 1
|
||||
center = full_configuration.get_center_of_mass()
|
||||
radius = 0.6 * max(
|
||||
full_configuration.get_width(),
|
||||
full_configuration.get_height(),
|
||||
)
|
||||
circle = Circle(
|
||||
radius=radius,
|
||||
stroke_width=0,
|
||||
fill_color=self.digit_place_colors[place],
|
||||
fill_opacity=0.5,
|
||||
)
|
||||
circle.move_to(center)
|
||||
moving_dot = VGroup(circle, full_configuration)
|
||||
moving_dot.generate_target()
|
||||
moving_dot[0].set_fill(opacity=0)
|
||||
else:
|
||||
continue_rolling_over = False
|
||||
|
||||
def get_digit_increment_animations(self):
|
||||
result = []
|
||||
self.number += 1
|
||||
is_next_digit = self.is_next_digit()
|
||||
if is_next_digit:
|
||||
self.max_place += 1
|
||||
new_number_mob = self.get_number_mob(self.number)
|
||||
new_number_mob.move_to(self.number_mob, RIGHT)
|
||||
if is_next_digit:
|
||||
self.add_configuration()
|
||||
place = len(new_number_mob.split()) - 1
|
||||
result.append(FadeIn(self.dot_templates[place]))
|
||||
arrow = Arrow(
|
||||
new_number_mob[place].get_top(),
|
||||
self.dot_templates[place].get_bottom(),
|
||||
color=self.digit_place_colors[place]
|
||||
)
|
||||
self.arrows.add(arrow)
|
||||
result.append(ShowCreation(arrow))
|
||||
result.append(Transform(
|
||||
self.number_mob, new_number_mob,
|
||||
lag_ratio=0.5
|
||||
))
|
||||
return result
|
||||
|
||||
def get_number_mob(self, num):
|
||||
result = VGroup()
|
||||
place = 0
|
||||
max_place = self.max_place
|
||||
while place < max_place:
|
||||
digit = TexMobject(str(self.get_place_num(num, place)))
|
||||
if place >= len(self.digit_place_colors):
|
||||
self.digit_place_colors += self.digit_place_colors
|
||||
digit.set_color(self.digit_place_colors[place])
|
||||
digit.scale(self.num_scale_factor)
|
||||
digit.next_to(result, LEFT, buff=SMALL_BUFF, aligned_edge=DOWN)
|
||||
result.add(digit)
|
||||
place += 1
|
||||
return result
|
||||
|
||||
def is_next_digit(self):
|
||||
return False
|
||||
|
||||
def get_place_num(self, num, place):
|
||||
return 0
|
||||
|
||||
def get_place_max(self, place):
|
||||
return 0
|
||||
|
||||
|
||||
class PowerCounter(CountingScene):
|
||||
def is_next_digit(self):
|
||||
number = self.number
|
||||
while number > 1:
|
||||
if number % self.base != 0:
|
||||
return False
|
||||
number /= self.base
|
||||
return True
|
||||
|
||||
def get_place_max(self, place):
|
||||
return self.base
|
||||
|
||||
def get_place_num(self, num, place):
|
||||
return (num / (self.base ** place)) % self.base
|
||||
|
||||
|
||||
class CountInDecimal(PowerCounter):
|
||||
CONFIG = {
|
||||
"base": 10,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
for x in range(11):
|
||||
self.increment()
|
||||
for x in range(85):
|
||||
self.increment(0.25)
|
||||
for x in range(20):
|
||||
self.increment()
|
||||
|
||||
|
||||
class CountInTernary(PowerCounter):
|
||||
CONFIG = {
|
||||
"base": 3,
|
||||
"dot_configuration_height": 1,
|
||||
"ones_configuration_location": UP + 4 * RIGHT
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.count(27)
|
||||
|
||||
# def get_template_configuration(self):
|
||||
# return [ORIGIN, UP]
|
||||
|
||||
|
||||
class CountInBinaryTo256(PowerCounter):
|
||||
CONFIG = {
|
||||
"base": 2,
|
||||
"dot_configuration_height": 1,
|
||||
"ones_configuration_location": UP + 5 * RIGHT
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.count(128, 0.3)
|
||||
|
||||
def get_template_configuration(self):
|
||||
return [ORIGIN, UP]
|
||||
|
||||
|
||||
class FactorialBase(CountingScene):
|
||||
CONFIG = {
|
||||
"dot_configuration_height": 1,
|
||||
"ones_configuration_location": UP + 4 * RIGHT
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.count(30, 0.4)
|
||||
|
||||
def is_next_digit(self):
|
||||
return self.number == self.factorial(self.max_place + 1)
|
||||
|
||||
def get_place_max(self, place):
|
||||
return place + 2
|
||||
|
||||
def get_place_num(self, num, place):
|
||||
return (num / self.factorial(place + 1)) % self.get_place_max(place)
|
||||
|
||||
def factorial(self, n):
|
||||
if (n == 1):
|
||||
return 1
|
||||
else:
|
||||
return n * self.factorial(n - 1)
|
||||
|
|
@ -1,676 +0,0 @@
|
|||
from functools import reduce
|
||||
|
||||
from manimlib.constants import *
|
||||
from manimlib.for_3b1b_videos.pi_creature import PiCreature
|
||||
from manimlib.for_3b1b_videos.pi_creature import Randolph
|
||||
from manimlib.for_3b1b_videos.pi_creature import get_all_pi_creature_modes
|
||||
from manimlib.mobject.geometry import Circle
|
||||
from manimlib.mobject.geometry import Polygon
|
||||
from manimlib.mobject.geometry import RegularPolygon
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.utils.bezier import interpolate
|
||||
from manimlib.utils.color import color_gradient
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.space_ops import center_of_mass
|
||||
from manimlib.utils.space_ops import compass_directions
|
||||
from manimlib.utils.space_ops import rotate_vector
|
||||
from manimlib.utils.space_ops import rotation_matrix
|
||||
|
||||
|
||||
def rotate(points, angle=np.pi, axis=OUT):
|
||||
if axis is None:
|
||||
return points
|
||||
matrix = rotation_matrix(angle, axis)
|
||||
points = np.dot(points, np.transpose(matrix))
|
||||
return points
|
||||
|
||||
|
||||
def fractalify(vmobject, order=3, *args, **kwargs):
|
||||
for x in range(order):
|
||||
fractalification_iteration(vmobject)
|
||||
return vmobject
|
||||
|
||||
|
||||
def fractalification_iteration(vmobject, dimension=1.05, num_inserted_anchors_range=list(range(1, 4))):
|
||||
num_points = vmobject.get_num_points()
|
||||
if num_points > 0:
|
||||
# original_anchors = vmobject.get_anchors()
|
||||
original_anchors = [
|
||||
vmobject.point_from_proportion(x)
|
||||
for x in np.linspace(0, 1 - 1. / num_points, num_points)
|
||||
]
|
||||
new_anchors = []
|
||||
for p1, p2, in zip(original_anchors, original_anchors[1:]):
|
||||
num_inserts = random.choice(num_inserted_anchors_range)
|
||||
inserted_points = [
|
||||
interpolate(p1, p2, alpha)
|
||||
for alpha in np.linspace(0, 1, num_inserts + 2)[1:-1]
|
||||
]
|
||||
mass_scaling_factor = 1. / (num_inserts + 1)
|
||||
length_scaling_factor = mass_scaling_factor**(1. / dimension)
|
||||
target_length = get_norm(p1 - p2) * length_scaling_factor
|
||||
curr_length = get_norm(p1 - p2) * mass_scaling_factor
|
||||
# offset^2 + curr_length^2 = target_length^2
|
||||
offset_len = np.sqrt(target_length**2 - curr_length**2)
|
||||
unit_vect = (p1 - p2) / get_norm(p1 - p2)
|
||||
offset_unit_vect = rotate_vector(unit_vect, np.pi / 2)
|
||||
inserted_points = [
|
||||
point + u * offset_len * offset_unit_vect
|
||||
for u, point in zip(it.cycle([-1, 1]), inserted_points)
|
||||
]
|
||||
new_anchors += [p1] + inserted_points
|
||||
new_anchors.append(original_anchors[-1])
|
||||
vmobject.set_points_as_corners(new_anchors)
|
||||
vmobject.submobjects = [
|
||||
fractalification_iteration(
|
||||
submob, dimension, num_inserted_anchors_range)
|
||||
for submob in vmobject.submobjects
|
||||
]
|
||||
return vmobject
|
||||
|
||||
|
||||
class SelfSimilarFractal(VMobject):
|
||||
CONFIG = {
|
||||
"order": 5,
|
||||
"num_subparts": 3,
|
||||
"height": 4,
|
||||
"colors": [RED, WHITE],
|
||||
"stroke_width": 1,
|
||||
"fill_opacity": 1,
|
||||
}
|
||||
|
||||
def init_colors(self):
|
||||
VMobject.init_colors(self)
|
||||
self.set_color_by_gradient(*self.colors)
|
||||
|
||||
def generate_points(self):
|
||||
order_n_self = self.get_order_n_self(self.order)
|
||||
if self.order == 0:
|
||||
self.submobjects = [order_n_self]
|
||||
else:
|
||||
self.submobjects = order_n_self.submobjects
|
||||
return self
|
||||
|
||||
def get_order_n_self(self, order):
|
||||
if order == 0:
|
||||
result = self.get_seed_shape()
|
||||
else:
|
||||
lower_order = self.get_order_n_self(order - 1)
|
||||
subparts = [
|
||||
lower_order.copy()
|
||||
for x in range(self.num_subparts)
|
||||
]
|
||||
self.arrange_subparts(*subparts)
|
||||
result = VGroup(*subparts)
|
||||
|
||||
result.set_height(self.height)
|
||||
result.center()
|
||||
return result
|
||||
|
||||
def get_seed_shape(self):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
class Sierpinski(SelfSimilarFractal):
|
||||
def get_seed_shape(self):
|
||||
return Polygon(
|
||||
RIGHT, np.sqrt(3) * UP, LEFT,
|
||||
)
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
tri1, tri2, tri3 = subparts
|
||||
tri1.move_to(tri2.get_corner(DOWN + LEFT), UP)
|
||||
tri3.move_to(tri2.get_corner(DOWN + RIGHT), UP)
|
||||
|
||||
|
||||
class DiamondFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts": 4,
|
||||
"height": 4,
|
||||
"colors": [GREEN_E, YELLOW],
|
||||
}
|
||||
|
||||
def get_seed_shape(self):
|
||||
return RegularPolygon(n=4)
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
# VGroup(*subparts).rotate(np.pi/4)
|
||||
for part, vect in zip(subparts, compass_directions(start_vect=UP + RIGHT)):
|
||||
part.next_to(ORIGIN, vect, buff=0)
|
||||
VGroup(*subparts).rotate(np.pi / 4, about_point=ORIGIN)
|
||||
|
||||
|
||||
class PentagonalFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts": 5,
|
||||
"colors": [MAROON_B, YELLOW, RED],
|
||||
"height": 6,
|
||||
}
|
||||
|
||||
def get_seed_shape(self):
|
||||
return RegularPolygon(n=5, start_angle=np.pi / 2)
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
for x, part in enumerate(subparts):
|
||||
part.shift(0.95 * part.get_height() * UP)
|
||||
part.rotate(2 * np.pi * x / 5, about_point=ORIGIN)
|
||||
|
||||
|
||||
class PentagonalPiCreatureFractal(PentagonalFractal):
|
||||
def init_colors(self):
|
||||
SelfSimilarFractal.init_colors(self)
|
||||
internal_pis = [
|
||||
pi
|
||||
for pi in self.get_family()
|
||||
if isinstance(pi, PiCreature)
|
||||
]
|
||||
colors = color_gradient(self.colors, len(internal_pis))
|
||||
for pi, color in zip(internal_pis, colors):
|
||||
pi.init_colors()
|
||||
pi.body.set_stroke(color, width=0.5)
|
||||
pi.set_color(color)
|
||||
|
||||
def get_seed_shape(self):
|
||||
return Randolph(mode="shruggie")
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
for part in subparts:
|
||||
part.rotate(2 * np.pi / 5, about_point=ORIGIN)
|
||||
PentagonalFractal.arrange_subparts(self, *subparts)
|
||||
|
||||
|
||||
class PiCreatureFractal(VMobject):
|
||||
CONFIG = {
|
||||
"order": 7,
|
||||
"scale_val": 2.5,
|
||||
"start_mode": "hooray",
|
||||
"height": 6,
|
||||
"colors": [
|
||||
BLUE_D, BLUE_B, MAROON_B, MAROON_D, GREY,
|
||||
YELLOW, RED, GREY_BROWN, RED, RED_E,
|
||||
],
|
||||
"random_seed": 0,
|
||||
"stroke_width": 0,
|
||||
}
|
||||
|
||||
def init_colors(self):
|
||||
VMobject.init_colors(self)
|
||||
internal_pis = [
|
||||
pi
|
||||
for pi in self.get_family()
|
||||
if isinstance(pi, PiCreature)
|
||||
]
|
||||
random.seed(self.random_seed)
|
||||
for pi in reversed(internal_pis):
|
||||
color = random.choice(self.colors)
|
||||
pi.set_color(color)
|
||||
pi.set_stroke(color, width=0)
|
||||
|
||||
def generate_points(self):
|
||||
random.seed(self.random_seed)
|
||||
modes = get_all_pi_creature_modes()
|
||||
seed = PiCreature(mode=self.start_mode)
|
||||
seed.set_height(self.height)
|
||||
seed.to_edge(DOWN)
|
||||
creatures = [seed]
|
||||
self.add(VGroup(seed))
|
||||
for x in range(self.order):
|
||||
new_creatures = []
|
||||
for creature in creatures:
|
||||
for eye, vect in zip(creature.eyes, [LEFT, RIGHT]):
|
||||
new_creature = PiCreature(
|
||||
mode=random.choice(modes)
|
||||
)
|
||||
new_creature.set_height(
|
||||
self.scale_val * eye.get_height()
|
||||
)
|
||||
new_creature.next_to(
|
||||
eye, vect,
|
||||
buff=0,
|
||||
aligned_edge=DOWN
|
||||
)
|
||||
new_creatures.append(new_creature)
|
||||
creature.look_at(random.choice(new_creatures))
|
||||
self.add_to_back(VGroup(*new_creatures))
|
||||
creatures = new_creatures
|
||||
|
||||
# def init_colors(self):
|
||||
# VMobject.init_colors(self)
|
||||
# self.set_color_by_gradient(*self.colors)
|
||||
|
||||
|
||||
class WonkyHexagonFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts": 7
|
||||
}
|
||||
|
||||
def get_seed_shape(self):
|
||||
return RegularPolygon(n=6)
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
for i, piece in enumerate(subparts):
|
||||
piece.rotate(i * np.pi / 12, about_point=ORIGIN)
|
||||
p1, p2, p3, p4, p5, p6, p7 = subparts
|
||||
center_row = VGroup(p1, p4, p7)
|
||||
center_row.arrange(RIGHT, buff=0)
|
||||
for p in p2, p3, p5, p6:
|
||||
p.set_width(p1.get_width())
|
||||
p2.move_to(p1.get_top(), DOWN + LEFT)
|
||||
p3.move_to(p1.get_bottom(), UP + LEFT)
|
||||
p5.move_to(p4.get_top(), DOWN + LEFT)
|
||||
p6.move_to(p4.get_bottom(), UP + LEFT)
|
||||
|
||||
|
||||
class CircularFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts": 3,
|
||||
"colors": [GREEN, BLUE, GREY]
|
||||
}
|
||||
|
||||
def get_seed_shape(self):
|
||||
return Circle()
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
if not hasattr(self, "been_here"):
|
||||
self.num_subparts = 3 + self.order
|
||||
self.been_here = True
|
||||
for i, part in enumerate(subparts):
|
||||
theta = np.pi / self.num_subparts
|
||||
part.next_to(
|
||||
ORIGIN, UP,
|
||||
buff=self.height / (2 * np.tan(theta))
|
||||
)
|
||||
part.rotate(i * 2 * np.pi / self.num_subparts, about_point=ORIGIN)
|
||||
self.num_subparts -= 1
|
||||
|
||||
######## Space filling curves ############
|
||||
|
||||
|
||||
class JaggedCurvePiece(VMobject):
|
||||
def insert_n_curves(self, n):
|
||||
if self.get_num_curves() == 0:
|
||||
self.set_points(np.zeros((1, 3)))
|
||||
anchors = self.get_anchors()
|
||||
indices = np.linspace(
|
||||
0, len(anchors) - 1, n + len(anchors)
|
||||
).astype('int')
|
||||
self.set_points_as_corners(anchors[indices])
|
||||
|
||||
|
||||
class FractalCurve(VMobject):
|
||||
CONFIG = {
|
||||
"radius": 3,
|
||||
"order": 5,
|
||||
"colors": [RED, GREEN],
|
||||
"num_submobjects": 20,
|
||||
"monochromatic": False,
|
||||
"order_to_stroke_width_map": {
|
||||
3: 3,
|
||||
4: 2,
|
||||
5: 1,
|
||||
},
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
points = self.get_anchor_points()
|
||||
self.set_points_as_corners(points)
|
||||
if not self.monochromatic:
|
||||
alphas = np.linspace(0, 1, self.num_submobjects)
|
||||
for alpha_pair in zip(alphas, alphas[1:]):
|
||||
submobject = JaggedCurvePiece()
|
||||
submobject.pointwise_become_partial(
|
||||
self, *alpha_pair
|
||||
)
|
||||
self.add(submobject)
|
||||
self.points = np.zeros((0, 3))
|
||||
|
||||
def init_colors(self):
|
||||
VMobject.init_colors(self)
|
||||
self.set_color_by_gradient(*self.colors)
|
||||
for order in sorted(self.order_to_stroke_width_map.keys()):
|
||||
if self.order >= order:
|
||||
self.set_stroke(width=self.order_to_stroke_width_map[order])
|
||||
|
||||
def get_anchor_points(self):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
class LindenmayerCurve(FractalCurve):
|
||||
CONFIG = {
|
||||
"axiom": "A",
|
||||
"rule": {},
|
||||
"scale_factor": 2,
|
||||
"radius": 3,
|
||||
"start_step": RIGHT,
|
||||
"angle": np.pi / 2,
|
||||
}
|
||||
|
||||
def expand_command_string(self, command):
|
||||
result = ""
|
||||
for letter in command:
|
||||
if letter in self.rule:
|
||||
result += self.rule[letter]
|
||||
else:
|
||||
result += letter
|
||||
return result
|
||||
|
||||
def get_command_string(self):
|
||||
result = self.axiom
|
||||
for x in range(self.order):
|
||||
result = self.expand_command_string(result)
|
||||
return result
|
||||
|
||||
def get_anchor_points(self):
|
||||
step = float(self.radius) * self.start_step
|
||||
step /= (self.scale_factor**self.order)
|
||||
curr = np.zeros(3)
|
||||
result = [curr]
|
||||
for letter in self.get_command_string():
|
||||
if letter == "+":
|
||||
step = rotate(step, self.angle)
|
||||
elif letter == "-":
|
||||
step = rotate(step, -self.angle)
|
||||
else:
|
||||
curr = curr + step
|
||||
result.append(curr)
|
||||
return np.array(result) - center_of_mass(result)
|
||||
|
||||
|
||||
class SelfSimilarSpaceFillingCurve(FractalCurve):
|
||||
CONFIG = {
|
||||
"offsets": [],
|
||||
# keys must awkwardly be in string form...
|
||||
"offset_to_rotation_axis": {},
|
||||
"scale_factor": 2,
|
||||
"radius_scale_factor": 0.5,
|
||||
}
|
||||
|
||||
def transform(self, points, offset):
|
||||
"""
|
||||
How to transform the copy of points shifted by
|
||||
offset. Generally meant to be extended in subclasses
|
||||
"""
|
||||
copy = np.array(points)
|
||||
if str(offset) in self.offset_to_rotation_axis:
|
||||
copy = rotate(
|
||||
copy,
|
||||
axis=self.offset_to_rotation_axis[str(offset)]
|
||||
)
|
||||
copy /= self.scale_factor,
|
||||
copy += offset * self.radius * self.radius_scale_factor
|
||||
return copy
|
||||
|
||||
def refine_into_subparts(self, points):
|
||||
transformed_copies = [
|
||||
self.transform(points, offset)
|
||||
for offset in self.offsets
|
||||
]
|
||||
return reduce(
|
||||
lambda a, b: np.append(a, b, axis=0),
|
||||
transformed_copies
|
||||
)
|
||||
|
||||
def get_anchor_points(self):
|
||||
points = np.zeros((1, 3))
|
||||
for count in range(self.order):
|
||||
points = self.refine_into_subparts(points)
|
||||
return points
|
||||
|
||||
def generate_grid(self):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
class HilbertCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"offsets": [
|
||||
LEFT + DOWN,
|
||||
LEFT + UP,
|
||||
RIGHT + UP,
|
||||
RIGHT + DOWN,
|
||||
],
|
||||
"offset_to_rotation_axis": {
|
||||
str(LEFT + DOWN): RIGHT + UP,
|
||||
str(RIGHT + DOWN): RIGHT + DOWN,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class HilbertCurve3D(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"offsets": [
|
||||
RIGHT + DOWN + IN,
|
||||
LEFT + DOWN + IN,
|
||||
LEFT + DOWN + OUT,
|
||||
RIGHT + DOWN + OUT,
|
||||
RIGHT + UP + OUT,
|
||||
LEFT + UP + OUT,
|
||||
LEFT + UP + IN,
|
||||
RIGHT + UP + IN,
|
||||
],
|
||||
"offset_to_rotation_axis_and_angle": {
|
||||
str(RIGHT + DOWN + IN): (LEFT + UP + OUT, 2 * np.pi / 3),
|
||||
str(LEFT + DOWN + IN): (RIGHT + DOWN + IN, 2 * np.pi / 3),
|
||||
str(LEFT + DOWN + OUT): (RIGHT + DOWN + IN, 2 * np.pi / 3),
|
||||
str(RIGHT + DOWN + OUT): (UP, np.pi),
|
||||
str(RIGHT + UP + OUT): (UP, np.pi),
|
||||
str(LEFT + UP + OUT): (LEFT + DOWN + OUT, 2 * np.pi / 3),
|
||||
str(LEFT + UP + IN): (LEFT + DOWN + OUT, 2 * np.pi / 3),
|
||||
str(RIGHT + UP + IN): (RIGHT + UP + IN, 2 * np.pi / 3),
|
||||
},
|
||||
}
|
||||
# Rewrote transform method to include the rotation angle
|
||||
|
||||
def transform(self, points, offset):
|
||||
copy = np.array(points)
|
||||
copy = rotate(
|
||||
copy,
|
||||
axis=self.offset_to_rotation_axis_and_angle[str(offset)][0],
|
||||
angle=self.offset_to_rotation_axis_and_angle[str(offset)][1],
|
||||
)
|
||||
copy /= self.scale_factor,
|
||||
copy += offset * self.radius * self.radius_scale_factor
|
||||
return copy
|
||||
|
||||
|
||||
class PeanoCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"colors": [PURPLE, TEAL],
|
||||
"offsets": [
|
||||
LEFT + DOWN,
|
||||
LEFT,
|
||||
LEFT + UP,
|
||||
UP,
|
||||
ORIGIN,
|
||||
DOWN,
|
||||
RIGHT + DOWN,
|
||||
RIGHT,
|
||||
RIGHT + UP,
|
||||
],
|
||||
"offset_to_rotation_axis": {
|
||||
str(LEFT): UP,
|
||||
str(UP): RIGHT,
|
||||
str(ORIGIN): LEFT + UP,
|
||||
str(DOWN): RIGHT,
|
||||
str(RIGHT): UP,
|
||||
},
|
||||
"scale_factor": 3,
|
||||
"radius_scale_factor": 2.0 / 3,
|
||||
}
|
||||
|
||||
|
||||
class TriangleFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"colors": [MAROON, YELLOW],
|
||||
"offsets": [
|
||||
LEFT / 4. + DOWN / 6.,
|
||||
ORIGIN,
|
||||
RIGHT / 4. + DOWN / 6.,
|
||||
UP / 3.,
|
||||
],
|
||||
"offset_to_rotation_axis": {
|
||||
str(ORIGIN): RIGHT,
|
||||
str(UP / 3.): UP,
|
||||
},
|
||||
"scale_factor": 2,
|
||||
"radius_scale_factor": 1.5,
|
||||
}
|
||||
|
||||
# class HexagonFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||
# CONFIG = {
|
||||
# "start_color" : WHITE,
|
||||
# "end_color" : BLUE_D,
|
||||
# "axis_offset_pairs" : [
|
||||
# (None, 1.5*DOWN + 0.5*np.sqrt(3)*LEFT),
|
||||
# (UP+np.sqrt(3)*RIGHT, 1.5*DOWN + 0.5*np.sqrt(3)*RIGHT),
|
||||
# (np.sqrt(3)*UP+RIGHT, ORIGIN),
|
||||
# ((UP, RIGHT), np.sqrt(3)*LEFT),
|
||||
# (None, 1.5*UP + 0.5*np.sqrt(3)*LEFT),
|
||||
# (None, 1.5*UP + 0.5*np.sqrt(3)*RIGHT),
|
||||
# (RIGHT, np.sqrt(3)*RIGHT),
|
||||
# ],
|
||||
# "scale_factor" : 3,
|
||||
# "radius_scale_factor" : 2/(3*np.sqrt(3)),
|
||||
# }
|
||||
|
||||
# def refine_into_subparts(self, points):
|
||||
# return SelfSimilarSpaceFillingCurve.refine_into_subparts(
|
||||
# self,
|
||||
# rotate(points, np.pi/6, IN)
|
||||
# )
|
||||
|
||||
|
||||
class UtahFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"colors": [WHITE, BLUE_D],
|
||||
"axis_offset_pairs": [
|
||||
|
||||
],
|
||||
"scale_factor": 3,
|
||||
"radius_scale_factor": 2 / (3 * np.sqrt(3)),
|
||||
}
|
||||
|
||||
|
||||
class FlowSnake(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors": [YELLOW, GREEN],
|
||||
"axiom": "A",
|
||||
"rule": {
|
||||
"A": "A-B--B+A++AA+B-",
|
||||
"B": "+A-BB--B-A++A+B",
|
||||
},
|
||||
"radius": 6, # TODO, this is innaccurate
|
||||
"scale_factor": np.sqrt(7),
|
||||
"start_step": RIGHT,
|
||||
"angle": -np.pi / 3,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
LindenmayerCurve.__init__(self, **kwargs)
|
||||
self.rotate(-self.order * np.pi / 9, about_point=ORIGIN)
|
||||
|
||||
|
||||
class SierpinskiCurve(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors": [RED, WHITE],
|
||||
"axiom": "B",
|
||||
"rule": {
|
||||
"A": "+B-A-B+",
|
||||
"B": "-A+B+A-",
|
||||
},
|
||||
"radius": 6, # TODO, this is innaccurate
|
||||
"scale_factor": 2,
|
||||
"start_step": RIGHT,
|
||||
"angle": -np.pi / 3,
|
||||
}
|
||||
|
||||
|
||||
class KochSnowFlake(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors": [BLUE_D, WHITE, BLUE_D],
|
||||
"axiom": "A--A--A--",
|
||||
"rule": {
|
||||
"A": "A+A--A+A"
|
||||
},
|
||||
"radius": 4,
|
||||
"scale_factor": 3,
|
||||
"start_step": RIGHT,
|
||||
"angle": np.pi / 3,
|
||||
"order_to_stroke_width_map": {
|
||||
3: 3,
|
||||
5: 2,
|
||||
6: 1,
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.scale_factor = 2 * (1 + np.cos(self.angle))
|
||||
LindenmayerCurve.__init__(self, **kwargs)
|
||||
|
||||
|
||||
class KochCurve(KochSnowFlake):
|
||||
CONFIG = {
|
||||
"axiom": "A--"
|
||||
}
|
||||
|
||||
|
||||
class QuadraticKoch(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors": [YELLOW, WHITE, MAROON_B],
|
||||
"axiom": "A",
|
||||
"rule": {
|
||||
"A": "A+A-A-AA+A+A-A"
|
||||
},
|
||||
"radius": 4,
|
||||
"scale_factor": 4,
|
||||
"start_step": RIGHT,
|
||||
"angle": np.pi / 2
|
||||
}
|
||||
|
||||
|
||||
class QuadraticKochIsland(QuadraticKoch):
|
||||
CONFIG = {
|
||||
"axiom": "A+A+A+A"
|
||||
}
|
||||
|
||||
|
||||
class StellarCurve(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"start_color": RED,
|
||||
"end_color": BLUE_E,
|
||||
"rule": {
|
||||
"A": "+B-A-B+A-B+",
|
||||
"B": "-A+B+A-B+A-",
|
||||
},
|
||||
"scale_factor": 3,
|
||||
"angle": 2 * np.pi / 5,
|
||||
}
|
||||
|
||||
|
||||
class SnakeCurve(FractalCurve):
|
||||
CONFIG = {
|
||||
"start_color": BLUE,
|
||||
"end_color": YELLOW,
|
||||
}
|
||||
|
||||
def get_anchor_points(self):
|
||||
result = []
|
||||
resolution = 2**self.order
|
||||
step = 2.0 * self.radius / resolution
|
||||
lower_left = ORIGIN + \
|
||||
LEFT * (self.radius - step / 2) + \
|
||||
DOWN * (self.radius - step / 2)
|
||||
|
||||
for y in range(resolution):
|
||||
x_range = list(range(resolution))
|
||||
if y % 2 == 0:
|
||||
x_range.reverse()
|
||||
for x in x_range:
|
||||
result.append(
|
||||
lower_left + x * step * RIGHT + y * step * UP
|
||||
)
|
||||
return result
|
||||
|
|
@ -1,414 +0,0 @@
|
|||
from functools import reduce
|
||||
import itertools as it
|
||||
import operator as op
|
||||
|
||||
import numpy as np
|
||||
|
||||
from manimlib.constants import *
|
||||
from manimlib.scene.scene import Scene
|
||||
from manimlib.utils.rate_functions import there_and_back
|
||||
from manimlib.utils.space_ops import center_of_mass
|
||||
|
||||
|
||||
class Graph():
|
||||
def __init__(self):
|
||||
# List of points in R^3
|
||||
# vertices = []
|
||||
# List of pairs of indices of vertices
|
||||
# edges = []
|
||||
# List of tuples of indices of vertices. The last should
|
||||
# be a cycle whose interior is the entire graph, and when
|
||||
# regions are computed its complement will be taken.
|
||||
# region_cycles = []
|
||||
|
||||
self.construct()
|
||||
|
||||
def construct(self):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
|
||||
class CubeGraph(Graph):
|
||||
"""
|
||||
5 7
|
||||
12
|
||||
03
|
||||
4 6
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
self.vertices = [
|
||||
(x, y, 0)
|
||||
for r in (1, 2)
|
||||
for x, y in it.product([-r, r], [-r, r])
|
||||
]
|
||||
self.edges = [
|
||||
(0, 1),
|
||||
(0, 2),
|
||||
(3, 1),
|
||||
(3, 2),
|
||||
(4, 5),
|
||||
(4, 6),
|
||||
(7, 5),
|
||||
(7, 6),
|
||||
(0, 4),
|
||||
(1, 5),
|
||||
(2, 6),
|
||||
(3, 7),
|
||||
]
|
||||
self.region_cycles = [
|
||||
[0, 2, 3, 1],
|
||||
[4, 0, 1, 5],
|
||||
[4, 6, 2, 0],
|
||||
[6, 7, 3, 2],
|
||||
[7, 5, 1, 3],
|
||||
[4, 6, 7, 5], # By convention, last region will be "outside"
|
||||
]
|
||||
|
||||
|
||||
class SampleGraph(Graph):
|
||||
"""
|
||||
4 2 3 8
|
||||
0 1
|
||||
7
|
||||
5 6
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
self.vertices = [
|
||||
(0, 0, 0),
|
||||
(2, 0, 0),
|
||||
(1, 2, 0),
|
||||
(3, 2, 0),
|
||||
(-1, 2, 0),
|
||||
(-2, -2, 0),
|
||||
(2, -2, 0),
|
||||
(4, -1, 0),
|
||||
(6, 2, 0),
|
||||
]
|
||||
self.edges = [
|
||||
(0, 1),
|
||||
(1, 2),
|
||||
(1, 3),
|
||||
(3, 2),
|
||||
(2, 4),
|
||||
(4, 0),
|
||||
(2, 0),
|
||||
(4, 5),
|
||||
(0, 5),
|
||||
(1, 5),
|
||||
(5, 6),
|
||||
(6, 7),
|
||||
(7, 1),
|
||||
(7, 8),
|
||||
(8, 3),
|
||||
]
|
||||
self.region_cycles = [
|
||||
(0, 1, 2),
|
||||
(1, 3, 2),
|
||||
(2, 4, 0),
|
||||
(4, 5, 0),
|
||||
(0, 5, 1),
|
||||
(1, 5, 6, 7),
|
||||
(1, 7, 8, 3),
|
||||
(4, 5, 6, 7, 8, 3, 2),
|
||||
]
|
||||
|
||||
|
||||
class OctohedronGraph(Graph):
|
||||
"""
|
||||
3
|
||||
|
||||
1 0
|
||||
2
|
||||
4 5
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
self.vertices = [
|
||||
(r * np.cos(angle), r * np.sin(angle) - 1, 0)
|
||||
for r, s in [(1, 0), (3, 3)]
|
||||
for angle in (np.pi / 6) * np.array([s, 4 + s, 8 + s])
|
||||
]
|
||||
self.edges = [
|
||||
(0, 1),
|
||||
(1, 2),
|
||||
(2, 0),
|
||||
(5, 0),
|
||||
(0, 3),
|
||||
(3, 5),
|
||||
(3, 1),
|
||||
(3, 4),
|
||||
(1, 4),
|
||||
(4, 2),
|
||||
(4, 5),
|
||||
(5, 2),
|
||||
]
|
||||
self.region_cycles = [
|
||||
(0, 1, 2),
|
||||
(0, 5, 3),
|
||||
(3, 1, 0),
|
||||
(3, 4, 1),
|
||||
(1, 4, 2),
|
||||
(2, 4, 5),
|
||||
(5, 0, 2),
|
||||
(3, 4, 5),
|
||||
]
|
||||
|
||||
|
||||
class CompleteGraph(Graph):
|
||||
def __init__(self, num_vertices, radius=3):
|
||||
self.num_vertices = num_vertices
|
||||
self.radius = radius
|
||||
Graph.__init__(self)
|
||||
|
||||
def construct(self):
|
||||
self.vertices = [
|
||||
(self.radius * np.cos(theta), self.radius * np.sin(theta), 0)
|
||||
for x in range(self.num_vertices)
|
||||
for theta in [2 * np.pi * x / self.num_vertices]
|
||||
]
|
||||
self.edges = it.combinations(list(range(self.num_vertices)), 2)
|
||||
|
||||
def __str__(self):
|
||||
return Graph.__str__(self) + str(self.num_vertices)
|
||||
|
||||
|
||||
class DiscreteGraphScene(Scene):
|
||||
args_list = [
|
||||
(CubeGraph(),),
|
||||
(SampleGraph(),),
|
||||
(OctohedronGraph(),),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def args_to_string(*args):
|
||||
return str(args[0])
|
||||
|
||||
def __init__(self, graph, *args, **kwargs):
|
||||
# See CubeGraph() above for format of graph
|
||||
self.graph = graph
|
||||
Scene.__init__(self, *args, **kwargs)
|
||||
|
||||
def construct(self):
|
||||
self.points = list(map(np.array, self.graph.vertices))
|
||||
self.vertices = self.dots = [Dot(p) for p in self.points]
|
||||
self.edges = self.lines = [
|
||||
Line(self.points[i], self.points[j])
|
||||
for i, j in self.graph.edges
|
||||
]
|
||||
self.add(*self.dots + self.edges)
|
||||
|
||||
def generate_regions(self):
|
||||
regions = [
|
||||
self.region_from_cycle(cycle)
|
||||
for cycle in self.graph.region_cycles
|
||||
]
|
||||
regions[-1].complement() # Outer region painted outwardly...
|
||||
self.regions = regions
|
||||
|
||||
def region_from_cycle(self, cycle):
|
||||
point_pairs = [
|
||||
[
|
||||
self.points[cycle[i]],
|
||||
self.points[cycle[(i + 1) % len(cycle)]]
|
||||
]
|
||||
for i in range(len(cycle))
|
||||
]
|
||||
return region_from_line_boundary(
|
||||
*point_pairs, shape=self.shape
|
||||
)
|
||||
|
||||
def draw_vertices(self, **kwargs):
|
||||
self.clear()
|
||||
self.play(ShowCreation(Mobject(*self.vertices), **kwargs))
|
||||
|
||||
def draw_edges(self):
|
||||
self.play(*[
|
||||
ShowCreation(edge, run_time=1.0)
|
||||
for edge in self.edges
|
||||
])
|
||||
|
||||
def accent_vertices(self, **kwargs):
|
||||
self.remove(*self.vertices)
|
||||
start = Mobject(*self.vertices)
|
||||
end = Mobject(*[
|
||||
Dot(point, radius=3 * Dot.DEFAULT_RADIUS, color="lightgreen")
|
||||
for point in self.points
|
||||
])
|
||||
self.play(Transform(
|
||||
start, end, rate_func=there_and_back,
|
||||
**kwargs
|
||||
))
|
||||
self.remove(start)
|
||||
self.add(*self.vertices)
|
||||
|
||||
def replace_vertices_with(self, mobject):
|
||||
mobject.center()
|
||||
diameter = max(mobject.get_height(), mobject.get_width())
|
||||
self.play(*[
|
||||
CounterclockwiseTransform(
|
||||
vertex,
|
||||
mobject.copy().shift(vertex.get_center())
|
||||
)
|
||||
for vertex in self.vertices
|
||||
] + [
|
||||
ApplyMethod(
|
||||
edge.scale_in_place,
|
||||
(edge.get_length() - diameter) / edge.get_length()
|
||||
)
|
||||
for edge in self.edges
|
||||
])
|
||||
|
||||
def annotate_edges(self, mobject, fade_in=True, **kwargs):
|
||||
angles = list(map(np.arctan, list(map(Line.get_slope, self.edges))))
|
||||
self.edge_annotations = [
|
||||
mobject.copy().rotate(angle).move_to(edge.get_center())
|
||||
for angle, edge in zip(angles, self.edges)
|
||||
]
|
||||
if fade_in:
|
||||
self.play(*[
|
||||
FadeIn(ann, **kwargs)
|
||||
for ann in self.edge_annotations
|
||||
])
|
||||
|
||||
def trace_cycle(self, cycle=None, color="yellow", run_time=2.0):
|
||||
if cycle is None:
|
||||
cycle = self.graph.region_cycles[0]
|
||||
time_per_edge = run_time / len(cycle)
|
||||
next_in_cycle = it.cycle(cycle)
|
||||
next(next_in_cycle) # jump one ahead
|
||||
self.traced_cycle = Mobject(*[
|
||||
Line(self.points[i], self.points[j]).set_color(color)
|
||||
for i, j in zip(cycle, next_in_cycle)
|
||||
])
|
||||
self.play(
|
||||
ShowCreation(self.traced_cycle),
|
||||
run_time=run_time
|
||||
)
|
||||
|
||||
def generate_spanning_tree(self, root=0, color="yellow"):
|
||||
self.spanning_tree_root = 0
|
||||
pairs = deepcopy(self.graph.edges)
|
||||
pairs += [tuple(reversed(pair)) for pair in pairs]
|
||||
self.spanning_tree_index_pairs = []
|
||||
curr = root
|
||||
spanned_vertices = set([curr])
|
||||
to_check = set([curr])
|
||||
while len(to_check) > 0:
|
||||
curr = to_check.pop()
|
||||
for pair in pairs:
|
||||
if pair[0] == curr and pair[1] not in spanned_vertices:
|
||||
self.spanning_tree_index_pairs.append(pair)
|
||||
spanned_vertices.add(pair[1])
|
||||
to_check.add(pair[1])
|
||||
self.spanning_tree = Mobject(*[
|
||||
Line(
|
||||
self.points[pair[0]],
|
||||
self.points[pair[1]]
|
||||
).set_color(color)
|
||||
for pair in self.spanning_tree_index_pairs
|
||||
])
|
||||
|
||||
def generate_treeified_spanning_tree(self):
|
||||
bottom = -FRAME_Y_RADIUS + 1
|
||||
x_sep = 1
|
||||
y_sep = 2
|
||||
if not hasattr(self, "spanning_tree"):
|
||||
self.generate_spanning_tree()
|
||||
root = self.spanning_tree_root
|
||||
color = self.spanning_tree.get_color()
|
||||
indices = list(range(len(self.points)))
|
||||
# Build dicts
|
||||
parent_of = dict([
|
||||
tuple(reversed(pair))
|
||||
for pair in self.spanning_tree_index_pairs
|
||||
])
|
||||
children_of = dict([(index, []) for index in indices])
|
||||
for child in parent_of:
|
||||
children_of[parent_of[child]].append(child)
|
||||
|
||||
x_coord_of = {root: 0}
|
||||
y_coord_of = {root: bottom}
|
||||
# width to allocate to a given node, computed as
|
||||
# the maxium number of decendents in a single generation,
|
||||
# minus 1, multiplied by x_sep
|
||||
width_of = {}
|
||||
for index in indices:
|
||||
next_generation = children_of[index]
|
||||
curr_max = max(1, len(next_generation))
|
||||
while next_generation != []:
|
||||
next_generation = reduce(op.add, [
|
||||
children_of[node]
|
||||
for node in next_generation
|
||||
])
|
||||
curr_max = max(curr_max, len(next_generation))
|
||||
width_of[index] = x_sep * (curr_max - 1)
|
||||
to_process = [root]
|
||||
while to_process != []:
|
||||
index = to_process.pop()
|
||||
if index not in y_coord_of:
|
||||
y_coord_of[index] = y_sep + y_coord_of[parent_of[index]]
|
||||
children = children_of[index]
|
||||
left_hand = x_coord_of[index] - width_of[index] / 2.0
|
||||
for child in children:
|
||||
x_coord_of[child] = left_hand + width_of[child] / 2.0
|
||||
left_hand += width_of[child] + x_sep
|
||||
to_process += children
|
||||
|
||||
new_points = [
|
||||
np.array([
|
||||
x_coord_of[index],
|
||||
y_coord_of[index],
|
||||
0
|
||||
])
|
||||
for index in indices
|
||||
]
|
||||
self.treeified_spanning_tree = Mobject(*[
|
||||
Line(new_points[i], new_points[j]).set_color(color)
|
||||
for i, j in self.spanning_tree_index_pairs
|
||||
])
|
||||
|
||||
def generate_dual_graph(self):
|
||||
point_at_infinity = np.array([np.inf] * 3)
|
||||
cycles = self.graph.region_cycles
|
||||
self.dual_points = [
|
||||
center_of_mass([
|
||||
self.points[index]
|
||||
for index in cycle
|
||||
])
|
||||
for cycle in cycles
|
||||
]
|
||||
self.dual_vertices = [
|
||||
Dot(point).set_color("green")
|
||||
for point in self.dual_points
|
||||
]
|
||||
self.dual_vertices[-1] = Circle().scale(FRAME_X_RADIUS + FRAME_Y_RADIUS)
|
||||
self.dual_points[-1] = point_at_infinity
|
||||
|
||||
self.dual_edges = []
|
||||
for pair in self.graph.edges:
|
||||
dual_point_pair = []
|
||||
for cycle in cycles:
|
||||
if not (pair[0] in cycle and pair[1] in cycle):
|
||||
continue
|
||||
index1, index2 = cycle.index(pair[0]), cycle.index(pair[1])
|
||||
if abs(index1 - index2) in [1, len(cycle) - 1]:
|
||||
dual_point_pair.append(
|
||||
self.dual_points[cycles.index(cycle)]
|
||||
)
|
||||
assert(len(dual_point_pair) == 2)
|
||||
for i in 0, 1:
|
||||
if all(dual_point_pair[i] == point_at_infinity):
|
||||
new_point = np.array(dual_point_pair[1 - i])
|
||||
vect = center_of_mass([
|
||||
self.points[pair[0]],
|
||||
self.points[pair[1]]
|
||||
]) - new_point
|
||||
new_point += FRAME_X_RADIUS * vect / get_norm(vect)
|
||||
dual_point_pair[i] = new_point
|
||||
self.dual_edges.append(
|
||||
Line(*dual_point_pair).set_color()
|
||||
)
|
||||
|
|
@ -1,600 +0,0 @@
|
|||
from traceback import *
|
||||
|
||||
from scipy.spatial import ConvexHull
|
||||
|
||||
from manimlib.animation.composition import LaggedStartMap
|
||||
from manimlib.animation.fading import FadeIn
|
||||
from manimlib.animation.fading import FadeOut
|
||||
from manimlib.animation.transform import Transform
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.geometry import AnnularSector
|
||||
from manimlib.mobject.geometry import Annulus
|
||||
from manimlib.mobject.svg.svg_mobject import SVGMobject
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.mobject.types.vectorized_mobject import VectorizedPoint
|
||||
from manimlib.utils.space_ops import angle_between_vectors
|
||||
from manimlib.utils.space_ops import project_along_vector
|
||||
from manimlib.utils.space_ops import rotate_vector
|
||||
from manimlib.utils.space_ops import z_to_vector
|
||||
|
||||
LIGHT_COLOR = YELLOW
|
||||
SHADOW_COLOR = BLACK
|
||||
SWITCH_ON_RUN_TIME = 1.5
|
||||
FAST_SWITCH_ON_RUN_TIME = 0.1
|
||||
NUM_LEVELS = 30
|
||||
NUM_CONES = 7 # in first lighthouse scene
|
||||
NUM_VISIBLE_CONES = 5 # ibidem
|
||||
ARC_TIP_LENGTH = 0.2
|
||||
AMBIENT_FULL = 0.8
|
||||
AMBIENT_DIMMED = 0.5
|
||||
SPOTLIGHT_FULL = 0.8
|
||||
SPOTLIGHT_DIMMED = 0.5
|
||||
LIGHTHOUSE_HEIGHT = 0.8
|
||||
|
||||
DEGREES = TAU / 360
|
||||
|
||||
|
||||
def inverse_power_law(maxint, scale, cutoff, exponent):
|
||||
return (lambda r: maxint * (cutoff / (r / scale + cutoff))**exponent)
|
||||
|
||||
|
||||
def inverse_quadratic(maxint, scale, cutoff):
|
||||
return inverse_power_law(maxint, scale, cutoff, 2)
|
||||
|
||||
|
||||
class SwitchOn(LaggedStartMap):
|
||||
CONFIG = {
|
||||
"lag_ratio": 0.2,
|
||||
"run_time": SWITCH_ON_RUN_TIME
|
||||
}
|
||||
|
||||
def __init__(self, light, **kwargs):
|
||||
if (not isinstance(light, AmbientLight) and not isinstance(light, Spotlight)):
|
||||
raise Exception(
|
||||
"Only AmbientLights and Spotlights can be switched on")
|
||||
LaggedStartMap.__init__(
|
||||
self, FadeIn, light, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class SwitchOff(LaggedStartMap):
|
||||
CONFIG = {
|
||||
"lag_ratio": 0.2,
|
||||
"run_time": SWITCH_ON_RUN_TIME
|
||||
}
|
||||
|
||||
def __init__(self, light, **kwargs):
|
||||
if (not isinstance(light, AmbientLight) and not isinstance(light, Spotlight)):
|
||||
raise Exception(
|
||||
"Only AmbientLights and Spotlights can be switched off")
|
||||
light.submobjects = light.submobjects[::-1]
|
||||
LaggedStartMap.__init__(self, FadeOut, light, **kwargs)
|
||||
light.submobjects = light.submobjects[::-1]
|
||||
|
||||
|
||||
class Lighthouse(SVGMobject):
|
||||
CONFIG = {
|
||||
"file_name": "lighthouse",
|
||||
"height": LIGHTHOUSE_HEIGHT,
|
||||
"fill_color": WHITE,
|
||||
"fill_opacity": 1.0,
|
||||
}
|
||||
|
||||
def move_to(self, point):
|
||||
self.next_to(point, DOWN, buff=0)
|
||||
|
||||
|
||||
class AmbientLight(VMobject):
|
||||
|
||||
# Parameters are:
|
||||
# * a source point
|
||||
# * an opacity function
|
||||
# * a light color
|
||||
# * a max opacity
|
||||
# * a radius (larger than the opacity's dropoff length)
|
||||
# * the number of subdivisions (levels, annuli)
|
||||
|
||||
CONFIG = {
|
||||
"source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0),
|
||||
"opacity_function": lambda r: 1.0 / (r + 1.0)**2,
|
||||
"color": LIGHT_COLOR,
|
||||
"max_opacity": 1.0,
|
||||
"num_levels": NUM_LEVELS,
|
||||
"radius": 5.0
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
# in theory, this method is only called once, right?
|
||||
# so removing submobs shd not be necessary
|
||||
#
|
||||
# Note: Usually, yes, it is only called within Mobject.__init__,
|
||||
# but there is no strong guarantee of that, and you may want certain
|
||||
# update functions to regenerate points here and there.
|
||||
for submob in self.submobjects:
|
||||
self.remove(submob)
|
||||
|
||||
self.add(self.source_point)
|
||||
|
||||
# create annuli
|
||||
self.radius = float(self.radius)
|
||||
dr = self.radius / self.num_levels
|
||||
for r in np.arange(0, self.radius, dr):
|
||||
alpha = self.max_opacity * self.opacity_function(r)
|
||||
annulus = Annulus(
|
||||
inner_radius=r,
|
||||
outer_radius=r + dr,
|
||||
color=self.color,
|
||||
fill_opacity=alpha
|
||||
)
|
||||
annulus.move_to(self.get_source_point())
|
||||
self.add(annulus)
|
||||
|
||||
def move_source_to(self, point):
|
||||
# old_source_point = self.get_source_point()
|
||||
# self.shift(point - old_source_point)
|
||||
self.move_to(point)
|
||||
|
||||
return self
|
||||
|
||||
def get_source_point(self):
|
||||
return self.source_point.get_location()
|
||||
|
||||
def dimming(self, new_alpha):
|
||||
old_alpha = self.max_opacity
|
||||
self.max_opacity = new_alpha
|
||||
for submob in self.submobjects:
|
||||
old_submob_alpha = submob.fill_opacity
|
||||
new_submob_alpha = old_submob_alpha * new_alpha / old_alpha
|
||||
submob.set_fill(opacity=new_submob_alpha)
|
||||
|
||||
|
||||
class Spotlight(VMobject):
|
||||
CONFIG = {
|
||||
"source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0),
|
||||
"opacity_function": lambda r: 1.0 / (r / 2 + 1.0)**2,
|
||||
"color": GREEN, # LIGHT_COLOR,
|
||||
"max_opacity": 1.0,
|
||||
"num_levels": 10,
|
||||
"radius": 10.0,
|
||||
"screen": None,
|
||||
"camera_mob": None
|
||||
}
|
||||
|
||||
def projection_direction(self):
|
||||
# Note: This seems reasonable, though for it to work you'd
|
||||
# need to be sure that any 3d scene including a spotlight
|
||||
# somewhere assigns that spotlights "camera" attribute
|
||||
# to be the camera associated with that scene.
|
||||
if self.camera_mob is None:
|
||||
return OUT
|
||||
else:
|
||||
[phi, theta, r] = self.camera_mob.get_center()
|
||||
v = np.array([np.sin(phi) * np.cos(theta),
|
||||
np.sin(phi) * np.sin(theta), np.cos(phi)])
|
||||
return v # /get_norm(v)
|
||||
|
||||
def project(self, point):
|
||||
v = self.projection_direction()
|
||||
w = project_along_vector(point, v)
|
||||
return w
|
||||
|
||||
def get_source_point(self):
|
||||
return self.source_point.get_location()
|
||||
|
||||
def generate_points(self):
|
||||
self.submobjects = []
|
||||
|
||||
self.add(self.source_point)
|
||||
|
||||
if self.screen is not None:
|
||||
# look for the screen and create annular sectors
|
||||
lower_angle, upper_angle = self.viewing_angles(self.screen)
|
||||
self.radius = float(self.radius)
|
||||
dr = self.radius / self.num_levels
|
||||
lower_ray, upper_ray = self.viewing_rays(self.screen)
|
||||
|
||||
for r in np.arange(0, self.radius, dr):
|
||||
new_sector = self.new_sector(r, dr, lower_angle, upper_angle)
|
||||
self.add(new_sector)
|
||||
|
||||
def new_sector(self, r, dr, lower_angle, upper_angle):
|
||||
alpha = self.max_opacity * self.opacity_function(r)
|
||||
annular_sector = AnnularSector(
|
||||
inner_radius=r,
|
||||
outer_radius=r + dr,
|
||||
color=self.color,
|
||||
fill_opacity=alpha,
|
||||
start_angle=lower_angle,
|
||||
angle=upper_angle - lower_angle
|
||||
)
|
||||
# rotate (not project) it into the viewing plane
|
||||
rotation_matrix = z_to_vector(self.projection_direction())
|
||||
annular_sector.apply_matrix(rotation_matrix)
|
||||
# now rotate it inside that plane
|
||||
rotated_RIGHT = np.dot(RIGHT, rotation_matrix.T)
|
||||
projected_RIGHT = self.project(RIGHT)
|
||||
omega = angle_between_vectors(rotated_RIGHT, projected_RIGHT)
|
||||
annular_sector.rotate(omega, axis=self.projection_direction())
|
||||
annular_sector.move_arc_center_to(self.get_source_point())
|
||||
|
||||
return annular_sector
|
||||
|
||||
def viewing_angle_of_point(self, point):
|
||||
# as measured from the positive x-axis
|
||||
v1 = self.project(RIGHT)
|
||||
v2 = self.project(np.array(point) - self.get_source_point())
|
||||
absolute_angle = angle_between_vectors(v1, v2)
|
||||
# determine the angle's sign depending on their plane's
|
||||
# choice of orientation. That choice is set by the camera
|
||||
# position, i. e. projection direction
|
||||
|
||||
if np.dot(self.projection_direction(), np.cross(v1, v2)) > 0:
|
||||
return absolute_angle
|
||||
else:
|
||||
return -absolute_angle
|
||||
|
||||
def viewing_angles(self, screen):
|
||||
|
||||
screen_points = screen.get_anchors()
|
||||
projected_screen_points = list(map(self.project, screen_points))
|
||||
|
||||
viewing_angles = np.array(list(map(self.viewing_angle_of_point,
|
||||
projected_screen_points)))
|
||||
|
||||
lower_angle = upper_angle = 0
|
||||
if len(viewing_angles) != 0:
|
||||
lower_angle = np.min(viewing_angles)
|
||||
upper_angle = np.max(viewing_angles)
|
||||
|
||||
if upper_angle - lower_angle > TAU / 2:
|
||||
lower_angle, upper_angle = upper_angle, lower_angle + TAU
|
||||
return lower_angle, upper_angle
|
||||
|
||||
def viewing_rays(self, screen):
|
||||
|
||||
lower_angle, upper_angle = self.viewing_angles(screen)
|
||||
projected_RIGHT = self.project(
|
||||
RIGHT) / get_norm(self.project(RIGHT))
|
||||
lower_ray = rotate_vector(
|
||||
projected_RIGHT, lower_angle, axis=self.projection_direction())
|
||||
upper_ray = rotate_vector(
|
||||
projected_RIGHT, upper_angle, axis=self.projection_direction())
|
||||
|
||||
return lower_ray, upper_ray
|
||||
|
||||
def opening_angle(self):
|
||||
l, u = self.viewing_angles(self.screen)
|
||||
return u - l
|
||||
|
||||
def start_angle(self):
|
||||
l, u = self.viewing_angles(self.screen)
|
||||
return l
|
||||
|
||||
def stop_angle(self):
|
||||
l, u = self.viewing_angles(self.screen)
|
||||
return u
|
||||
|
||||
def move_source_to(self, point):
|
||||
self.source_point.set_location(np.array(point))
|
||||
# self.source_point.move_to(np.array(point))
|
||||
# self.move_to(point)
|
||||
self.update_sectors()
|
||||
return self
|
||||
|
||||
def update_sectors(self):
|
||||
if self.screen is None:
|
||||
return
|
||||
for submob in self.submobjects:
|
||||
if type(submob) == AnnularSector:
|
||||
lower_angle, upper_angle = self.viewing_angles(self.screen)
|
||||
# dr = submob.outer_radius - submob.inner_radius
|
||||
dr = self.radius / self.num_levels
|
||||
new_submob = self.new_sector(
|
||||
submob.inner_radius, dr, lower_angle, upper_angle
|
||||
)
|
||||
# submob.points = new_submob.points
|
||||
# submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius))
|
||||
Transform(submob, new_submob).update(1)
|
||||
|
||||
def dimming(self, new_alpha):
|
||||
old_alpha = self.max_opacity
|
||||
self.max_opacity = new_alpha
|
||||
for submob in self.submobjects:
|
||||
# Note: Maybe it'd be best to have a Shadow class so that the
|
||||
# type can be checked directly?
|
||||
if type(submob) != AnnularSector:
|
||||
# it's the shadow, don't dim it
|
||||
continue
|
||||
old_submob_alpha = submob.fill_opacity
|
||||
new_submob_alpha = old_submob_alpha * new_alpha / old_alpha
|
||||
submob.set_fill(opacity=new_submob_alpha)
|
||||
|
||||
def change_opacity_function(self, new_f):
|
||||
self.opacity_function = new_f
|
||||
dr = self.radius / self.num_levels
|
||||
|
||||
sectors = []
|
||||
for submob in self.submobjects:
|
||||
if type(submob) == AnnularSector:
|
||||
sectors.append(submob)
|
||||
|
||||
for (r, submob) in zip(np.arange(0, self.radius, dr), sectors):
|
||||
if type(submob) != AnnularSector:
|
||||
# it's the shadow, don't dim it
|
||||
continue
|
||||
alpha = self.opacity_function(r)
|
||||
submob.set_fill(opacity=alpha)
|
||||
|
||||
# Warning: This class is likely quite buggy.
|
||||
|
||||
|
||||
class LightSource(VMobject):
|
||||
# combines:
|
||||
# a lighthouse
|
||||
# an ambient light
|
||||
# a spotlight
|
||||
# and a shadow
|
||||
CONFIG = {
|
||||
"source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0),
|
||||
"color": LIGHT_COLOR,
|
||||
"num_levels": 10,
|
||||
"radius": 10.0,
|
||||
"screen": None,
|
||||
"opacity_function": inverse_quadratic(1, 2, 1),
|
||||
"max_opacity_ambient": AMBIENT_FULL,
|
||||
"max_opacity_spotlight": SPOTLIGHT_FULL,
|
||||
"camera_mob": None
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
|
||||
self.add(self.source_point)
|
||||
|
||||
self.lighthouse = Lighthouse()
|
||||
self.ambient_light = AmbientLight(
|
||||
source_point=VectorizedPoint(location=self.get_source_point()),
|
||||
color=self.color,
|
||||
num_levels=self.num_levels,
|
||||
radius=self.radius,
|
||||
opacity_function=self.opacity_function,
|
||||
max_opacity=self.max_opacity_ambient
|
||||
)
|
||||
if self.has_screen():
|
||||
self.spotlight = Spotlight(
|
||||
source_point=VectorizedPoint(location=self.get_source_point()),
|
||||
color=self.color,
|
||||
num_levels=self.num_levels,
|
||||
radius=self.radius,
|
||||
screen=self.screen,
|
||||
opacity_function=self.opacity_function,
|
||||
max_opacity=self.max_opacity_spotlight,
|
||||
camera_mob=self.camera_mob
|
||||
)
|
||||
else:
|
||||
self.spotlight = Spotlight()
|
||||
|
||||
self.shadow = VMobject(fill_color=SHADOW_COLOR,
|
||||
fill_opacity=1.0, stroke_color=BLACK)
|
||||
self.lighthouse.next_to(self.get_source_point(), DOWN, buff=0)
|
||||
self.ambient_light.move_source_to(self.get_source_point())
|
||||
|
||||
if self.has_screen():
|
||||
self.spotlight.move_source_to(self.get_source_point())
|
||||
self.update_shadow()
|
||||
|
||||
self.add(self.ambient_light, self.spotlight,
|
||||
self.lighthouse, self.shadow)
|
||||
|
||||
def has_screen(self):
|
||||
if self.screen is None:
|
||||
return False
|
||||
elif np.size(self.screen.points) == 0:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def dim_ambient(self):
|
||||
self.set_max_opacity_ambient(AMBIENT_DIMMED)
|
||||
|
||||
def set_max_opacity_ambient(self, new_opacity):
|
||||
self.max_opacity_ambient = new_opacity
|
||||
self.ambient_light.dimming(new_opacity)
|
||||
|
||||
def dim_spotlight(self):
|
||||
self.set_max_opacity_spotlight(SPOTLIGHT_DIMMED)
|
||||
|
||||
def set_max_opacity_spotlight(self, new_opacity):
|
||||
self.max_opacity_spotlight = new_opacity
|
||||
self.spotlight.dimming(new_opacity)
|
||||
|
||||
def set_camera_mob(self, new_cam_mob):
|
||||
self.camera_mob = new_cam_mob
|
||||
self.spotlight.camera_mob = new_cam_mob
|
||||
|
||||
def set_screen(self, new_screen):
|
||||
if self.has_screen():
|
||||
self.spotlight.screen = new_screen
|
||||
else:
|
||||
# Note: See below
|
||||
index = self.submobjects.index(self.spotlight)
|
||||
# camera_mob = self.spotlight.camera_mob
|
||||
self.remove(self.spotlight)
|
||||
self.spotlight = Spotlight(
|
||||
source_point=VectorizedPoint(location=self.get_source_point()),
|
||||
color=self.color,
|
||||
num_levels=self.num_levels,
|
||||
radius=self.radius,
|
||||
screen=new_screen,
|
||||
camera_mob=self.camera_mob,
|
||||
opacity_function=self.opacity_function,
|
||||
max_opacity=self.max_opacity_spotlight,
|
||||
)
|
||||
self.spotlight.move_source_to(self.get_source_point())
|
||||
|
||||
# Note: This line will make spotlight show up at the end
|
||||
# of the submojects list, which can make it show up on
|
||||
# top of the shadow. To make it show up in the
|
||||
# same spot, you could try the following line,
|
||||
# where "index" is what I defined above:
|
||||
self.submobjects.insert(index, self.spotlight)
|
||||
# self.add(self.spotlight)
|
||||
|
||||
# in any case
|
||||
self.screen = new_screen
|
||||
|
||||
def move_source_to(self, point):
|
||||
apoint = np.array(point)
|
||||
v = apoint - self.get_source_point()
|
||||
# Note: As discussed, things stand to behave better if source
|
||||
# point is a submobject, so that it automatically interpolates
|
||||
# during an animation, and other updates can be defined wrt
|
||||
# that source point's location
|
||||
self.source_point.set_location(apoint)
|
||||
# self.lighthouse.next_to(apoint,DOWN,buff = 0)
|
||||
# self.ambient_light.move_source_to(apoint)
|
||||
self.lighthouse.shift(v)
|
||||
# self.ambient_light.shift(v)
|
||||
self.ambient_light.move_source_to(apoint)
|
||||
if self.has_screen():
|
||||
self.spotlight.move_source_to(apoint)
|
||||
self.update()
|
||||
return self
|
||||
|
||||
def change_spotlight_opacity_function(self, new_of):
|
||||
self.spotlight.change_opacity_function(new_of)
|
||||
|
||||
def set_radius(self, new_radius):
|
||||
self.radius = new_radius
|
||||
self.ambient_light.radius = new_radius
|
||||
self.spotlight.radius = new_radius
|
||||
|
||||
def update(self):
|
||||
self.update_lighthouse()
|
||||
self.update_ambient()
|
||||
self.spotlight.update_sectors()
|
||||
self.update_shadow()
|
||||
|
||||
def update_lighthouse(self):
|
||||
self.lighthouse.move_to(self.get_source_point())
|
||||
# new_lh = Lighthouse()
|
||||
# new_lh.move_to(ORIGIN)
|
||||
# new_lh.apply_matrix(self.rotation_matrix())
|
||||
# new_lh.shift(self.get_source_point())
|
||||
# self.lighthouse.submobjects = new_lh.submobjects
|
||||
|
||||
def update_ambient(self):
|
||||
new_ambient_light = AmbientLight(
|
||||
source_point=VectorizedPoint(location=ORIGIN),
|
||||
color=self.color,
|
||||
num_levels=self.num_levels,
|
||||
radius=self.radius,
|
||||
opacity_function=self.opacity_function,
|
||||
max_opacity=self.max_opacity_ambient
|
||||
)
|
||||
new_ambient_light.apply_matrix(self.rotation_matrix())
|
||||
new_ambient_light.move_source_to(self.get_source_point())
|
||||
self.ambient_light.submobjects = new_ambient_light.submobjects
|
||||
|
||||
def get_source_point(self):
|
||||
return self.source_point.get_location()
|
||||
|
||||
def rotation_matrix(self):
|
||||
|
||||
if self.camera_mob is None:
|
||||
return np.eye(3)
|
||||
|
||||
phi = self.camera_mob.get_center()[0]
|
||||
theta = self.camera_mob.get_center()[1]
|
||||
|
||||
R1 = np.array([
|
||||
[1, 0, 0],
|
||||
[0, np.cos(phi), -np.sin(phi)],
|
||||
[0, np.sin(phi), np.cos(phi)]
|
||||
])
|
||||
|
||||
R2 = np.array([
|
||||
[np.cos(theta + TAU / 4), -np.sin(theta + TAU / 4), 0],
|
||||
[np.sin(theta + TAU / 4), np.cos(theta + TAU / 4), 0],
|
||||
[0, 0, 1]
|
||||
])
|
||||
|
||||
R = np.dot(R2, R1)
|
||||
return R
|
||||
|
||||
def update_shadow(self):
|
||||
point = self.get_source_point()
|
||||
projected_screen_points = []
|
||||
if not self.has_screen():
|
||||
return
|
||||
for point in self.screen.get_anchors():
|
||||
projected_screen_points.append(self.spotlight.project(point))
|
||||
|
||||
projected_source = project_along_vector(
|
||||
self.get_source_point(), self.spotlight.projection_direction())
|
||||
|
||||
projected_point_cloud_3d = np.append(
|
||||
projected_screen_points,
|
||||
np.reshape(projected_source, (1, 3)),
|
||||
axis=0
|
||||
)
|
||||
# z_to_vector(self.spotlight.projection_direction())
|
||||
rotation_matrix = self.rotation_matrix()
|
||||
back_rotation_matrix = rotation_matrix.T # i. e. its inverse
|
||||
|
||||
rotated_point_cloud_3d = np.dot(
|
||||
projected_point_cloud_3d, back_rotation_matrix.T)
|
||||
# these points now should all have z = 0
|
||||
|
||||
point_cloud_2d = rotated_point_cloud_3d[:, :2]
|
||||
# now we can compute the convex hull
|
||||
hull_2d = ConvexHull(point_cloud_2d) # guaranteed to run ccw
|
||||
hull = []
|
||||
|
||||
# we also need the projected source point
|
||||
source_point_2d = np.dot(self.spotlight.project(
|
||||
self.get_source_point()), back_rotation_matrix.T)[:2]
|
||||
|
||||
index = 0
|
||||
for point in point_cloud_2d[hull_2d.vertices]:
|
||||
if np.all(np.abs(point - source_point_2d) < 1.0e-6):
|
||||
source_index = index
|
||||
index += 1
|
||||
continue
|
||||
point_3d = np.array([point[0], point[1], 0])
|
||||
hull.append(point_3d)
|
||||
index += 1
|
||||
|
||||
hull_mobject = VMobject()
|
||||
hull_mobject.set_points_as_corners(hull)
|
||||
hull_mobject.apply_matrix(rotation_matrix)
|
||||
|
||||
anchors = hull_mobject.get_anchors()
|
||||
|
||||
# add two control points for the outer cone
|
||||
if np.size(anchors) == 0:
|
||||
self.shadow.points = []
|
||||
return
|
||||
|
||||
ray1 = anchors[source_index - 1] - projected_source
|
||||
ray1 = ray1 / get_norm(ray1) * 100
|
||||
|
||||
ray2 = anchors[source_index] - projected_source
|
||||
ray2 = ray2 / get_norm(ray2) * 100
|
||||
outpoint1 = anchors[source_index - 1] + ray1
|
||||
outpoint2 = anchors[source_index] + ray2
|
||||
|
||||
new_anchors = anchors[:source_index]
|
||||
new_anchors = np.append(new_anchors, np.array(
|
||||
[outpoint1, outpoint2]), axis=0)
|
||||
new_anchors = np.append(new_anchors, anchors[source_index:], axis=0)
|
||||
self.shadow.set_points_as_corners(new_anchors)
|
||||
|
||||
# shift it closer to the camera so it is in front of the spotlight
|
||||
self.shadow.mark_paths_closed = True
|
||||
|
||||
|
||||
# Redefining what was once a ContinualAnimation class
|
||||
# as a function
|
||||
def ScreenTracker(light_source):
|
||||
light_source.add_updater(lambda m: m.update())
|
||||
return light_source
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
from manimlib.animation.creation import ShowCreation
|
||||
from manimlib.animation.fading import FadeOut
|
||||
from manimlib.animation.transform import ApplyMethod
|
||||
from manimlib.animation.transform import Transform
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.geometry import Circle
|
||||
from manimlib.mobject.geometry import Line
|
||||
from manimlib.mobject.matrix import Matrix
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.scene.scene import Scene
|
||||
|
||||
|
||||
class NumericalMatrixMultiplication(Scene):
|
||||
CONFIG = {
|
||||
"left_matrix": [[1, 2], [3, 4]],
|
||||
"right_matrix": [[5, 6], [7, 8]],
|
||||
"use_parens": True,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
left_string_matrix, right_string_matrix = [
|
||||
np.array(matrix).astype("string")
|
||||
for matrix in (self.left_matrix, self.right_matrix)
|
||||
]
|
||||
if right_string_matrix.shape[0] != left_string_matrix.shape[1]:
|
||||
raise Exception("Incompatible shapes for matrix multiplication")
|
||||
|
||||
left = Matrix(left_string_matrix)
|
||||
right = Matrix(right_string_matrix)
|
||||
result = self.get_result_matrix(
|
||||
left_string_matrix, right_string_matrix
|
||||
)
|
||||
|
||||
self.organize_matrices(left, right, result)
|
||||
self.animate_product(left, right, result)
|
||||
|
||||
def get_result_matrix(self, left, right):
|
||||
(m, k), n = left.shape, right.shape[1]
|
||||
mob_matrix = np.array([VGroup()]).repeat(m * n).reshape((m, n))
|
||||
for a in range(m):
|
||||
for b in range(n):
|
||||
template = "(%s)(%s)" if self.use_parens else "%s%s"
|
||||
parts = [
|
||||
prefix + template % (left[a][c], right[c][b])
|
||||
for c in range(k)
|
||||
for prefix in ["" if c == 0 else "+"]
|
||||
]
|
||||
mob_matrix[a][b] = TexMobject(parts, next_to_buff=0.1)
|
||||
return Matrix(mob_matrix)
|
||||
|
||||
def add_lines(self, left, right):
|
||||
line_kwargs = {
|
||||
"color": BLUE,
|
||||
"stroke_width": 2,
|
||||
}
|
||||
left_rows = [
|
||||
VGroup(*row) for row in left.get_mob_matrix()
|
||||
]
|
||||
h_lines = VGroup()
|
||||
for row in left_rows[:-1]:
|
||||
h_line = Line(row.get_left(), row.get_right(), **line_kwargs)
|
||||
h_line.next_to(row, DOWN, buff=left.v_buff / 2.)
|
||||
h_lines.add(h_line)
|
||||
|
||||
right_cols = [
|
||||
VGroup(*col) for col in np.transpose(right.get_mob_matrix())
|
||||
]
|
||||
v_lines = VGroup()
|
||||
for col in right_cols[:-1]:
|
||||
v_line = Line(col.get_top(), col.get_bottom(), **line_kwargs)
|
||||
v_line.next_to(col, RIGHT, buff=right.h_buff / 2.)
|
||||
v_lines.add(v_line)
|
||||
|
||||
self.play(ShowCreation(h_lines))
|
||||
self.play(ShowCreation(v_lines))
|
||||
self.wait()
|
||||
self.show_frame()
|
||||
|
||||
def organize_matrices(self, left, right, result):
|
||||
equals = TexMobject("=")
|
||||
everything = VGroup(left, right, equals, result)
|
||||
everything.arrange()
|
||||
everything.set_width(FRAME_WIDTH - 1)
|
||||
self.add(everything)
|
||||
|
||||
def animate_product(self, left, right, result):
|
||||
l_matrix = left.get_mob_matrix()
|
||||
r_matrix = right.get_mob_matrix()
|
||||
result_matrix = result.get_mob_matrix()
|
||||
circle = Circle(
|
||||
radius=l_matrix[0][0].get_height(),
|
||||
color=GREEN
|
||||
)
|
||||
circles = VGroup(*[
|
||||
entry.get_point_mobject()
|
||||
for entry in (l_matrix[0][0], r_matrix[0][0])
|
||||
])
|
||||
(m, k), n = l_matrix.shape, r_matrix.shape[1]
|
||||
for mob in result_matrix.flatten():
|
||||
mob.set_color(BLACK)
|
||||
lagging_anims = []
|
||||
for a in range(m):
|
||||
for b in range(n):
|
||||
for c in range(k):
|
||||
l_matrix[a][c].set_color(YELLOW)
|
||||
r_matrix[c][b].set_color(YELLOW)
|
||||
for c in range(k):
|
||||
start_parts = VGroup(
|
||||
l_matrix[a][c].copy(),
|
||||
r_matrix[c][b].copy()
|
||||
)
|
||||
result_entry = result_matrix[a][b].split()[c]
|
||||
|
||||
new_circles = VGroup(*[
|
||||
circle.copy().shift(part.get_center())
|
||||
for part in start_parts.split()
|
||||
])
|
||||
self.play(Transform(circles, new_circles))
|
||||
self.play(
|
||||
Transform(
|
||||
start_parts,
|
||||
result_entry.copy().set_color(YELLOW),
|
||||
path_arc=-np.pi / 2,
|
||||
lag_ratio=0,
|
||||
),
|
||||
*lagging_anims
|
||||
)
|
||||
result_entry.set_color(YELLOW)
|
||||
self.remove(start_parts)
|
||||
lagging_anims = [
|
||||
ApplyMethod(result_entry.set_color, WHITE)
|
||||
]
|
||||
|
||||
for c in range(k):
|
||||
l_matrix[a][c].set_color(WHITE)
|
||||
r_matrix[c][b].set_color(WHITE)
|
||||
self.play(FadeOut(circles), *lagging_anims)
|
||||
self.wait()
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
from copy import deepcopy
|
||||
import itertools as it
|
||||
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.utils.iterables import adjacent_pairs
|
||||
|
||||
# Warning: This is all now pretty depricated, and should not be expected to work
|
||||
|
||||
|
||||
class Region(Mobject):
|
||||
CONFIG = {
|
||||
"display_mode": "region"
|
||||
}
|
||||
|
||||
def __init__(self, condition=(lambda x, y: True), **kwargs):
|
||||
"""
|
||||
Condition must be a function which takes in two real
|
||||
arrays (representing x and y values of space respectively)
|
||||
and return a boolean array. This can essentially look like
|
||||
a function from R^2 to {True, False}, but & and | must be
|
||||
used in place of "and" and "or"
|
||||
"""
|
||||
Mobject.__init__(self, **kwargs)
|
||||
self.condition = condition
|
||||
|
||||
def _combine(self, region, op):
|
||||
self.condition = lambda x, y: op(
|
||||
self.condition(x, y),
|
||||
region.condition(x, y)
|
||||
)
|
||||
|
||||
def union(self, region):
|
||||
self._combine(region, lambda bg1, bg2: bg1 | bg2)
|
||||
return self
|
||||
|
||||
def intersect(self, region):
|
||||
self._combine(region, lambda bg1, bg2: bg1 & bg2)
|
||||
return self
|
||||
|
||||
def complement(self):
|
||||
self.bool_grid = ~self.bool_grid
|
||||
return self
|
||||
|
||||
|
||||
class HalfPlane(Region):
|
||||
def __init__(self, point_pair, upper_left=True, *args, **kwargs):
|
||||
"""
|
||||
point_pair of the form [(x_0, y_0,...), (x_1, y_1,...)]
|
||||
|
||||
Pf upper_left is True, the side of the region will be
|
||||
everything on the upper left side of the line through
|
||||
the point pair
|
||||
"""
|
||||
if not upper_left:
|
||||
point_pair = list(point_pair)
|
||||
point_pair.reverse()
|
||||
(x0, y0), (x1, y1) = point_pair[0][:2], point_pair[1][:2]
|
||||
|
||||
def condition(x, y):
|
||||
return (x1 - x0) * (y - y0) > (y1 - y0) * (x - x0)
|
||||
Region.__init__(self, condition, *args, **kwargs)
|
||||
|
||||
|
||||
def region_from_line_boundary(*lines, **kwargs):
|
||||
reg = Region(**kwargs)
|
||||
for line in lines:
|
||||
reg.intersect(HalfPlane(line, **kwargs))
|
||||
return reg
|
||||
|
||||
|
||||
def region_from_polygon_vertices(*vertices, **kwargs):
|
||||
return region_from_line_boundary(*adjacent_pairs(vertices), **kwargs)
|
||||
|
||||
|
||||
def plane_partition(*lines, **kwargs):
|
||||
"""
|
||||
A 'line' is a pair of points [(x0, y0,...), (x1, y1,...)]
|
||||
|
||||
Returns the list of regions of the plane cut out by
|
||||
these lines
|
||||
"""
|
||||
result = []
|
||||
half_planes = [HalfPlane(line, **kwargs) for line in lines]
|
||||
complements = [deepcopy(hp).complement() for hp in half_planes]
|
||||
num_lines = len(lines)
|
||||
for bool_list in it.product(*[[True, False]] * num_lines):
|
||||
reg = Region(**kwargs)
|
||||
for i in range(num_lines):
|
||||
if bool_list[i]:
|
||||
reg.intersect(half_planes[i])
|
||||
else:
|
||||
reg.intersect(complements[i])
|
||||
if reg.bool_grid.any():
|
||||
result.append(reg)
|
||||
return result
|
||||
|
||||
|
||||
def plane_partition_from_points(*points, **kwargs):
|
||||
"""
|
||||
Returns list of regions cut out by the complete graph
|
||||
with points from the argument as vertices.
|
||||
|
||||
Each point comes in the form (x, y)
|
||||
"""
|
||||
lines = [[p1, p2] for (p1, p2) in it.combinations(points, 2)]
|
||||
return plane_partition(*lines, **kwargs)
|
||||
Loading…
Add table
Add a link
Reference in a new issue