mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Merge branch 'simple-improvements'
This commit is contained in:
commit
8c1673d235
98 changed files with 3854 additions and 3727 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,10 +1,8 @@
|
|||
*.pyc
|
||||
.DS_Store
|
||||
homeless.py
|
||||
ka_playgrounds/
|
||||
playground.py
|
||||
special_animations.py
|
||||
prettiness_hall_of_fame.py
|
||||
random_scenes/
|
||||
files/
|
||||
ben_playground.py
|
||||
ben_cairo_test.py
|
||||
|
|
|
@ -1,33 +1,4 @@
|
|||
from constants import *
|
||||
|
||||
from mobject.tex_mobject import TexMobject
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.image_mobject import ImageMobject
|
||||
from mobject.vectorized_mobject import *
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.transform import *
|
||||
from animation.simple_animations import *
|
||||
from animation.compositions import *
|
||||
from animation.playground import *
|
||||
from topics.geometry import *
|
||||
from topics.characters import *
|
||||
from topics.functions import *
|
||||
from topics.fractals import *
|
||||
from topics.number_line import *
|
||||
from topics.combinatorics import *
|
||||
from topics.numerals import *
|
||||
from topics.three_dimensions import *
|
||||
from topics.objects import *
|
||||
from topics.complex_numbers import *
|
||||
from topics.common_scenes import *
|
||||
from topics.probability import *
|
||||
from scene.scene import Scene
|
||||
from scene.reconfigurable_scene import ReconfigurableScene
|
||||
from scene.zoomed_scene import *
|
||||
from camera.camera import Camera
|
||||
from mobject.svg_mobject import *
|
||||
from mobject.tex_mobject import *
|
||||
from big_ol_pile_of_manim_imports import *
|
||||
|
||||
#revert_to_original_skipping_status
|
||||
|
||||
|
|
|
@ -1,33 +1,4 @@
|
|||
from constants import *
|
||||
|
||||
from mobject.tex_mobject import TexMobject
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.image_mobject import ImageMobject
|
||||
from mobject.vectorized_mobject import *
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.transform import *
|
||||
from animation.simple_animations import *
|
||||
from animation.compositions import *
|
||||
from animation.playground import *
|
||||
from topics.geometry import *
|
||||
from topics.characters import *
|
||||
from topics.functions import *
|
||||
from topics.fractals import *
|
||||
from topics.number_line import *
|
||||
from topics.combinatorics import *
|
||||
from topics.numerals import *
|
||||
from topics.three_dimensions import *
|
||||
from topics.objects import *
|
||||
from topics.complex_numbers import *
|
||||
from topics.common_scenes import *
|
||||
from topics.probability import *
|
||||
from scene.scene import Scene
|
||||
from scene.reconfigurable_scene import ReconfigurableScene
|
||||
from scene.zoomed_scene import *
|
||||
from camera.camera import Camera
|
||||
from mobject.svg_mobject import *
|
||||
from mobject.tex_mobject import *
|
||||
from big_ol_pile_of_manim_imports import *
|
||||
|
||||
from eop.bayes import IntroducePokerHand
|
||||
|
||||
|
|
|
@ -1,36 +1,4 @@
|
|||
from constants import *
|
||||
|
||||
from mobject.tex_mobject import TexMobject
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.image_mobject import ImageMobject
|
||||
from mobject.vectorized_mobject import *
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.transform import *
|
||||
from animation.simple_animations import *
|
||||
from animation.compositions import *
|
||||
from animation.playground import *
|
||||
from animation.continual_animation import *
|
||||
from topics.geometry import *
|
||||
from topics.characters import *
|
||||
from topics.functions import *
|
||||
from topics.fractals import *
|
||||
from topics.number_line import *
|
||||
from topics.combinatorics import *
|
||||
from topics.numerals import *
|
||||
from topics.three_dimensions import *
|
||||
from topics.objects import *
|
||||
from topics.probability import *
|
||||
from topics.complex_numbers import *
|
||||
from scene.scene import Scene
|
||||
from scene.reconfigurable_scene import ReconfigurableScene
|
||||
from scene.zoomed_scene import *
|
||||
from camera.camera import Camera
|
||||
from mobject.svg_mobject import *
|
||||
from mobject.tex_mobject import *
|
||||
from topics.graph_scene import *
|
||||
from topics.probability import *
|
||||
from topics.common_scenes import *
|
||||
from big_ol_pile_of_manim_imports import *
|
||||
|
||||
#revert_to_original_skipping_status
|
||||
|
||||
|
|
|
@ -1,33 +1,4 @@
|
|||
from constants import *
|
||||
|
||||
from mobject.tex_mobject import TexMobject
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.image_mobject import ImageMobject
|
||||
from mobject.vectorized_mobject import *
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.transform import *
|
||||
from animation.simple_animations import *
|
||||
from animation.compositions import *
|
||||
from animation.playground import *
|
||||
from topics.geometry import *
|
||||
from topics.characters import *
|
||||
from topics.functions import *
|
||||
from topics.fractals import *
|
||||
from topics.number_line import *
|
||||
from topics.combinatorics import *
|
||||
from topics.numerals import *
|
||||
from topics.three_dimensions import *
|
||||
from topics.objects import *
|
||||
from topics.complex_numbers import *
|
||||
from topics.common_scenes import *
|
||||
from topics.probability import *
|
||||
from scene.scene import Scene
|
||||
from scene.reconfigurable_scene import ReconfigurableScene
|
||||
from scene.zoomed_scene import *
|
||||
from camera.camera import Camera
|
||||
from mobject.svg_mobject import *
|
||||
from mobject.tex_mobject import *
|
||||
from big_ol_pile_of_manim_imports import *
|
||||
|
||||
from scene.scene import ProgressDisplay
|
||||
import scipy
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
__all__ = [
|
||||
"animation",
|
||||
"simple_animations",
|
||||
"transform"
|
||||
]
|
|
@ -1,54 +1,27 @@
|
|||
import numpy as np
|
||||
from __future__ import absolute_import
|
||||
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
|
||||
import warnings
|
||||
from mobject.mobject import Mobject, Group
|
||||
from mobject.vectorized_mobject import VMobject
|
||||
from mobject.tex_mobject import TextMobject
|
||||
from .animation import Animation
|
||||
from transform import Transform
|
||||
|
||||
from animation.animation import Animation
|
||||
from mobject.mobject import Group
|
||||
from mobject.mobject import Mobject
|
||||
from utils.bezier import inverse_interpolate
|
||||
from utils.config_ops import digest_config
|
||||
from utils.rate_functions import squish_rate_func
|
||||
|
||||
class LaggedStart(Animation):
|
||||
class EmptyAnimation(Animation):
|
||||
CONFIG = {
|
||||
"run_time" : 2,
|
||||
"lag_ratio" : 0.5,
|
||||
"run_time" : 0,
|
||||
"empty" : True
|
||||
}
|
||||
def __init__(self, AnimationClass, mobject, arg_creator = None, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
for key in "rate_func", "run_time", "lag_ratio":
|
||||
if key in kwargs:
|
||||
kwargs.pop(key)
|
||||
if arg_creator is None:
|
||||
arg_creator = lambda mobject : (mobject,)
|
||||
self.subanimations = [
|
||||
AnimationClass(
|
||||
*arg_creator(submob),
|
||||
run_time = self.run_time,
|
||||
rate_func = squish_rate_func(
|
||||
self.rate_func, beta, beta + self.lag_ratio
|
||||
),
|
||||
**kwargs
|
||||
)
|
||||
for submob, beta in zip(
|
||||
mobject,
|
||||
np.linspace(0, 1-self.lag_ratio, len(mobject))
|
||||
)
|
||||
]
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update(self, alpha):
|
||||
for anim in self.subanimations:
|
||||
anim.update(alpha)
|
||||
return self
|
||||
|
||||
def clean_up(self, *args, **kwargs):
|
||||
for anim in self.subanimations:
|
||||
anim.clean_up(*args, **kwargs)
|
||||
def __init__(self, *args, **kwargs):
|
||||
return Animation.__init__(self, Group(), *args, **kwargs)
|
||||
|
||||
class Succession(Animation):
|
||||
CONFIG = {
|
||||
|
@ -231,11 +204,64 @@ class AnimationGroup(Animation):
|
|||
for anim in self.sub_anims:
|
||||
anim.update_config(**kwargs)
|
||||
|
||||
class EmptyAnimation(Animation):
|
||||
CONFIG = {
|
||||
"run_time" : 0,
|
||||
"empty" : True
|
||||
}
|
||||
# Variants on mappin an animation over submobjectsg
|
||||
|
||||
class LaggedStart(Animation):
|
||||
CONFIG = {
|
||||
"run_time" : 2,
|
||||
"lag_ratio" : 0.5,
|
||||
}
|
||||
def __init__(self, AnimationClass, mobject, arg_creator = None, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
for key in "rate_func", "run_time", "lag_ratio":
|
||||
if key in kwargs:
|
||||
kwargs.pop(key)
|
||||
if arg_creator is None:
|
||||
arg_creator = lambda mobject : (mobject,)
|
||||
self.subanimations = [
|
||||
AnimationClass(
|
||||
*arg_creator(submob),
|
||||
run_time = self.run_time,
|
||||
rate_func = squish_rate_func(
|
||||
self.rate_func, beta, beta + self.lag_ratio
|
||||
),
|
||||
**kwargs
|
||||
)
|
||||
for submob, beta in zip(
|
||||
mobject,
|
||||
np.linspace(0, 1-self.lag_ratio, len(mobject))
|
||||
)
|
||||
]
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update(self, alpha):
|
||||
for anim in self.subanimations:
|
||||
anim.update(alpha)
|
||||
return self
|
||||
|
||||
def clean_up(self, *args, **kwargs):
|
||||
for anim in self.subanimations:
|
||||
anim.clean_up(*args, **kwargs)
|
||||
|
||||
class ApplyToCenters(Animation):
|
||||
def __init__(self, AnimationClass, mobjects, **kwargs):
|
||||
full_kwargs = AnimationClass.CONFIG
|
||||
full_kwargs.update(kwargs)
|
||||
full_kwargs["mobject"] = Mobject(*[
|
||||
mob.get_point_mobject()
|
||||
for mob in mobjects
|
||||
])
|
||||
self.centers_container = AnimationClass(**full_kwargs)
|
||||
full_kwargs.pop("mobject")
|
||||
Animation.__init__(self, Mobject(*mobjects), **full_kwargs)
|
||||
self.name = str(self) + AnimationClass.__name__
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
self.centers_container.update_mobject(alpha)
|
||||
center_mobs = self.centers_container.mobject.split()
|
||||
mobjects = self.mobject.split()
|
||||
for center_mob, mobject in zip(center_mobs, mobjects):
|
||||
mobject.shift(
|
||||
center_mob.get_center()-mobject.get_center()
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
return Animation.__init__(self, Group(), *args, **kwargs)
|
185
animation/creation.py
Normal file
185
animation/creation.py
Normal file
|
@ -0,0 +1,185 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
|
||||
from animation.animation import Animation
|
||||
from mobject.svg.tex_mobject import TextMobject
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.types.vectorized_mobject import VectorizedPoint
|
||||
from animation.transform import Transform
|
||||
from utils.bezier import interpolate
|
||||
from utils.config_ops import digest_config
|
||||
from utils.paths import counterclockwise_path
|
||||
from utils.rate_functions import double_smooth
|
||||
from utils.rate_functions import smooth
|
||||
|
||||
#Drawing
|
||||
|
||||
class ShowPartial(Animation):
|
||||
def update_submobject(self, submobject, starting_submobject, alpha):
|
||||
submobject.pointwise_become_partial(
|
||||
starting_submobject, *self.get_bounds(alpha)
|
||||
)
|
||||
|
||||
def get_bounds(self, alpha):
|
||||
raise Exception("Not Implemented")
|
||||
|
||||
class ShowCreation(ShowPartial):
|
||||
CONFIG = {
|
||||
"submobject_mode" : "one_at_a_time",
|
||||
}
|
||||
def get_bounds(self, alpha):
|
||||
return (0, alpha)
|
||||
|
||||
class Uncreate(ShowCreation):
|
||||
CONFIG = {
|
||||
"rate_func" : lambda t : smooth(1-t),
|
||||
"remover" : True
|
||||
}
|
||||
|
||||
class Write(ShowCreation):
|
||||
CONFIG = {
|
||||
"rate_func" : None,
|
||||
"submobject_mode" : "lagged_start",
|
||||
}
|
||||
def __init__(self, mob_or_text, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
if isinstance(mob_or_text, str):
|
||||
mobject = TextMobject(mob_or_text)
|
||||
else:
|
||||
mobject = mob_or_text
|
||||
if "run_time" not in kwargs:
|
||||
self.establish_run_time(mobject)
|
||||
if "lag_factor" not in kwargs:
|
||||
if len(mobject.family_members_with_points()) < 4:
|
||||
min_lag_factor = 1
|
||||
else:
|
||||
min_lag_factor = 2
|
||||
self.lag_factor = max(self.run_time - 1, min_lag_factor)
|
||||
ShowCreation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def establish_run_time(self, mobject):
|
||||
num_subs = len(mobject.family_members_with_points())
|
||||
if num_subs < 15:
|
||||
self.run_time = 1
|
||||
else:
|
||||
self.run_time = 2
|
||||
|
||||
class DrawBorderThenFill(Animation):
|
||||
CONFIG = {
|
||||
"run_time" : 2,
|
||||
"stroke_width" : 2,
|
||||
"stroke_color" : None,
|
||||
"rate_func" : double_smooth,
|
||||
}
|
||||
def __init__(self, vmobject, **kwargs):
|
||||
if not isinstance(vmobject, VMobject):
|
||||
raise Exception("DrawBorderThenFill only works for VMobjects")
|
||||
self.reached_halfway_point_before = False
|
||||
Animation.__init__(self, vmobject, **kwargs)
|
||||
|
||||
def update_submobject(self, submobject, starting_submobject, alpha):
|
||||
submobject.pointwise_become_partial(
|
||||
starting_submobject, 0, min(2*alpha, 1)
|
||||
)
|
||||
if alpha < 0.5:
|
||||
if self.stroke_color:
|
||||
color = self.stroke_color
|
||||
elif starting_submobject.stroke_width > 0:
|
||||
color = starting_submobject.get_stroke_color()
|
||||
else:
|
||||
color = starting_submobject.get_color()
|
||||
submobject.set_stroke(color, width = self.stroke_width)
|
||||
submobject.set_fill(opacity = 0)
|
||||
else:
|
||||
if not self.reached_halfway_point_before:
|
||||
self.reached_halfway_point_before = True
|
||||
submobject.points = np.array(starting_submobject.points)
|
||||
width, opacity = [
|
||||
interpolate(start, end, 2*alpha - 1)
|
||||
for start, end in [
|
||||
(self.stroke_width, starting_submobject.get_stroke_width()),
|
||||
(0, starting_submobject.get_fill_opacity())
|
||||
]
|
||||
]
|
||||
submobject.set_stroke(width = width)
|
||||
submobject.set_fill(opacity = opacity)
|
||||
|
||||
#Fading
|
||||
|
||||
class FadeOut(Transform):
|
||||
CONFIG = {
|
||||
"remover" : True,
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
target = mobject.copy()
|
||||
target.fade(1)
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
|
||||
def clean_up(self, surrounding_scene = None):
|
||||
Transform.clean_up(self, surrounding_scene)
|
||||
self.update(0)
|
||||
|
||||
class FadeIn(Transform):
|
||||
def __init__(self, mobject, **kwargs):
|
||||
target = mobject.copy()
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
self.starting_mobject.fade(1)
|
||||
if isinstance(self.starting_mobject, VMobject):
|
||||
self.starting_mobject.set_stroke(width = 0)
|
||||
self.starting_mobject.set_fill(opacity = 0)
|
||||
|
||||
class FadeInAndShiftFromDirection(Transform):
|
||||
CONFIG = {
|
||||
"direction" : DOWN,
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
target = mobject.copy()
|
||||
mobject.shift(self.direction)
|
||||
mobject.fade(1)
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
|
||||
class FadeInFromDown(FadeInAndShiftFromDirection):
|
||||
"""
|
||||
Essential a more convenient form of FadeInAndShiftFromDirection
|
||||
"""
|
||||
CONFIG = {
|
||||
"direction" : DOWN,
|
||||
}
|
||||
|
||||
#Growing
|
||||
class GrowFromPoint(Transform):
|
||||
CONFIG = {
|
||||
"point_color" : None,
|
||||
}
|
||||
def __init__(self, mobject, point, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
target = mobject.copy()
|
||||
point_mob = VectorizedPoint(point)
|
||||
if self.point_color:
|
||||
point_mob.set_color(self.point_color)
|
||||
mobject.replace(point_mob)
|
||||
mobject.set_color(point_mob.get_color())
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
|
||||
class GrowFromCenter(GrowFromPoint):
|
||||
def __init__(self, mobject, **kwargs):
|
||||
GrowFromPoint.__init__(self, mobject, mobject.get_center(), **kwargs)
|
||||
|
||||
class GrowArrow(GrowFromPoint):
|
||||
def __init__(self, arrow, **kwargs):
|
||||
GrowFromPoint.__init__(self, arrow, arrow.get_start(), **kwargs)
|
||||
|
||||
class SpinInFromNothing(GrowFromCenter):
|
||||
CONFIG = {
|
||||
"path_func" : counterclockwise_path()
|
||||
}
|
||||
|
||||
class ShrinkToCenter(Transform):
|
||||
def __init__(self, mobject, **kwargs):
|
||||
Transform.__init__(
|
||||
self, mobject, mobject.get_point_mobject(), **kwargs
|
||||
)
|
187
animation/indication.py
Normal file
187
animation/indication.py
Normal file
|
@ -0,0 +1,187 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.movement import Homotopy
|
||||
from animation.creation import ShowPartial
|
||||
from animation.transform import Transform
|
||||
from mobject.mobject import Group
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.geometry import Circle
|
||||
from mobject.geometry import Dot
|
||||
from utils.config_ops import digest_config
|
||||
from utils.rate_functions import squish_rate_func
|
||||
from utils.rate_functions import there_and_back
|
||||
|
||||
|
||||
class FocusOn(Transform):
|
||||
CONFIG = {
|
||||
"opacity" : 0.2,
|
||||
"color" : GREY,
|
||||
"run_time" : 2,
|
||||
"remover" : True,
|
||||
}
|
||||
def __init__(self, mobject_or_point, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
big_dot = Dot(
|
||||
radius = FRAME_X_RADIUS+FRAME_Y_RADIUS,
|
||||
stroke_width = 0,
|
||||
fill_color = self.color,
|
||||
fill_opacity = 0,
|
||||
)
|
||||
little_dot = Dot(radius = 0)
|
||||
little_dot.set_fill(self.color, opacity = self.opacity)
|
||||
little_dot.move_to(mobject_or_point)
|
||||
|
||||
Transform.__init__(self, big_dot, little_dot, **kwargs)
|
||||
|
||||
class Indicate(Transform):
|
||||
CONFIG = {
|
||||
"rate_func" : there_and_back,
|
||||
"scale_factor" : 1.2,
|
||||
"color" : YELLOW,
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
target = mobject.copy()
|
||||
target.scale_in_place(self.scale_factor)
|
||||
target.set_color(self.color)
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
|
||||
class CircleIndicate(Indicate):
|
||||
CONFIG = {
|
||||
"rate_func" : squish_rate_func(there_and_back, 0, 0.8),
|
||||
"remover" : True
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
circle = Circle(color = self.color, **kwargs)
|
||||
circle.surround(mobject)
|
||||
Indicate.__init__(self, circle, **kwargs)
|
||||
|
||||
class ShowPassingFlash(ShowPartial):
|
||||
CONFIG = {
|
||||
"time_width" : 0.1,
|
||||
"remover" : True,
|
||||
}
|
||||
def get_bounds(self, alpha):
|
||||
alpha *= (1+self.time_width)
|
||||
alpha -= self.time_width/2.0
|
||||
lower = max(0, alpha - self.time_width/2.0)
|
||||
upper = min(1, alpha + self.time_width/2.0)
|
||||
return (lower, upper)
|
||||
|
||||
def clean_up(self, *args, **kwargs):
|
||||
ShowPartial.clean_up(self, *args, **kwargs)
|
||||
for submob, start_submob in self.get_all_families_zipped():
|
||||
submob.pointwise_become_partial(start_submob, 0, 1)
|
||||
|
||||
class ShowCreationThenDestruction(ShowPassingFlash):
|
||||
CONFIG = {
|
||||
"time_width" : 2.0,
|
||||
"run_time" : 1,
|
||||
}
|
||||
|
||||
class ApplyWave(Homotopy):
|
||||
CONFIG = {
|
||||
"direction" : DOWN,
|
||||
"amplitude" : 0.2,
|
||||
"run_time" : 1,
|
||||
"apply_function_kwargs" : {
|
||||
"maintain_smoothness" : False,
|
||||
},
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
left_x = mobject.get_left()[0]
|
||||
right_x = mobject.get_right()[0]
|
||||
vect = self.amplitude*self.direction
|
||||
def homotopy(x, y, z, t):
|
||||
start_point = np.array([x, y, z])
|
||||
alpha = (x-left_x)/(right_x-left_x)
|
||||
power = np.exp(2*(alpha-0.5))
|
||||
nudge = there_and_back(t**power)
|
||||
return np.array([x, y, z]) + nudge*vect
|
||||
Homotopy.__init__(self, homotopy, mobject, **kwargs)
|
||||
|
||||
class WiggleOutThenIn(Animation):
|
||||
CONFIG = {
|
||||
"scale_value" : 1.1,
|
||||
"rotation_angle" : 0.01*TAU,
|
||||
"n_wiggles" : 6,
|
||||
"run_time" : 2,
|
||||
"scale_about_point" : None,
|
||||
"rotate_about_point" : None,
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
if self.scale_about_point is None:
|
||||
self.scale_about_point = mobject.get_center()
|
||||
if self.rotate_about_point is None:
|
||||
self.rotate_about_point = mobject.get_center()
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_submobject(self, submobject, starting_sumobject, alpha):
|
||||
submobject.points[:,:] = starting_sumobject.points
|
||||
submobject.scale(
|
||||
interpolate(1, self.scale_value, there_and_back(alpha)),
|
||||
about_point = self.scale_about_point
|
||||
)
|
||||
submobject.rotate(
|
||||
wiggle(alpha, self.n_wiggles)*self.rotation_angle,
|
||||
about_point = self.rotate_about_point
|
||||
)
|
||||
|
||||
class Vibrate(Animation):
|
||||
CONFIG = {
|
||||
"spatial_period" : 6,
|
||||
"temporal_period" : 1,
|
||||
"overtones" : 4,
|
||||
"amplitude" : 0.5,
|
||||
"radius" : FRAME_X_RADIUS/2,
|
||||
"run_time" : 3.0,
|
||||
"rate_func" : None
|
||||
}
|
||||
def __init__(self, mobject = None, **kwargs):
|
||||
if mobject is None:
|
||||
mobject = Line(3*LEFT, 3*RIGHT)
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def wave_function(self, x, t):
|
||||
return sum([
|
||||
reduce(op.mul, [
|
||||
self.amplitude/(k**2), #Amplitude
|
||||
np.sin(2*np.pi*(k**1.5)*t/self.temporal_period), #Frequency
|
||||
np.sin(2*np.pi*k*x/self.spatial_period) #Number of waves
|
||||
])
|
||||
for k in range(1, self.overtones+1)
|
||||
])
|
||||
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
time = alpha*self.run_time
|
||||
families = map(
|
||||
Mobject.submobject_family,
|
||||
[self.mobject, self.starting_mobject]
|
||||
)
|
||||
for mob, start in zip(*families):
|
||||
mob.points = np.apply_along_axis(
|
||||
lambda (x, y, z) : (x, y + self.wave_function(x, time), z),
|
||||
1, start.points
|
||||
)
|
||||
|
||||
class TurnInsideOut(Transform):
|
||||
CONFIG = {
|
||||
"path_arc" : TAU/4,
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
mobject.sort_points(np.linalg.norm)
|
||||
mob_copy = mobject.copy()
|
||||
mob_copy.sort_points(lambda p : -np.linalg.norm(p))
|
||||
Transform.__init__(self, mobject, mob_copy, **kwargs)
|
||||
|
||||
|
74
animation/movement.py
Normal file
74
animation/movement.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
import warnings
|
||||
|
||||
from animation.animation import Animation
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
class Homotopy(Animation):
|
||||
CONFIG = {
|
||||
"run_time" : 3,
|
||||
"apply_function_kwargs" : {},
|
||||
}
|
||||
def __init__(self, homotopy, mobject, **kwargs):
|
||||
"""
|
||||
Homotopy a function from (x, y, z, t) to (x', y', z')
|
||||
"""
|
||||
def function_at_time_t(t):
|
||||
return lambda p : homotopy(p[0], p[1], p[2], t)
|
||||
self.function_at_time_t = function_at_time_t
|
||||
digest_config(self, kwargs)
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_submobject(self, submob, start, alpha):
|
||||
submob.points = start.points
|
||||
submob.apply_function(
|
||||
self.function_at_time_t(alpha),
|
||||
**self.apply_function_kwargs
|
||||
)
|
||||
|
||||
class SmoothedVectorizedHomotopy(Homotopy):
|
||||
def update_submobject(self, submob, start, alpha):
|
||||
Homotopy.update_submobject(self, submob, start, alpha)
|
||||
submob.make_smooth()
|
||||
|
||||
class ComplexHomotopy(Homotopy):
|
||||
def __init__(self, complex_homotopy, mobject, **kwargs):
|
||||
"""
|
||||
Complex Hootopy a function Cx[0, 1] to C
|
||||
"""
|
||||
def homotopy(event):
|
||||
x, y, z, t = event
|
||||
c = complex_homotopy((complex(x, y), t))
|
||||
return (c.real, c.imag, z)
|
||||
Homotopy.__init__(self, homotopy, mobject, *args, **kwargs)
|
||||
|
||||
|
||||
class PhaseFlow(Animation):
|
||||
CONFIG = {
|
||||
"virtual_time" : 1,
|
||||
"rate_func" : None,
|
||||
}
|
||||
def __init__(self, function, mobject, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
if hasattr(self, "last_alpha"):
|
||||
dt = self.virtual_time*(alpha-self.last_alpha)
|
||||
self.mobject.apply_function(
|
||||
lambda p : p + dt*self.function(p)
|
||||
)
|
||||
self.last_alpha = alpha
|
||||
|
||||
class MoveAlongPath(Animation):
|
||||
def __init__(self, mobject, path, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
point = self.path.point_from_proportion(alpha)
|
||||
self.mobject.move_to(point)
|
||||
|
61
animation/numbers.py
Normal file
61
animation/numbers.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from animation.animation import Animation
|
||||
from mobject.numbers import DecimalNumber
|
||||
from utils.bezier import interpolate
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
class ChangingDecimal(Animation):
|
||||
CONFIG = {
|
||||
"num_decimal_points" : None,
|
||||
"show_ellipsis" : None,
|
||||
"position_update_func" : None,
|
||||
"tracked_mobject" : None,
|
||||
}
|
||||
def __init__(self, decimal_number_mobject, number_update_func, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
self.decimal_number_config = dict(
|
||||
decimal_number_mobject.initial_config
|
||||
)
|
||||
for attr in "num_decimal_points", "show_ellipsis":
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
self.decimal_number_config[attr] = value
|
||||
if hasattr(self.decimal_number_mobject, "background_rectangle"):
|
||||
self.decimal_number_config["include_background_rectangle"] = True
|
||||
if self.tracked_mobject:
|
||||
dmc = decimal_number_mobject.get_center()
|
||||
tmc = self.tracked_mobject.get_center()
|
||||
self.diff_from_tracked_mobject = dmc - tmc
|
||||
Animation.__init__(self, decimal_number_mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
self.update_number(alpha)
|
||||
self.update_position()
|
||||
|
||||
def update_number(self, alpha):
|
||||
decimal = self.decimal_number_mobject
|
||||
new_number = self.number_update_func(alpha)
|
||||
new_decimal = DecimalNumber(
|
||||
new_number, **self.decimal_number_config
|
||||
)
|
||||
new_decimal.match_height(decimal)
|
||||
new_decimal.move_to(decimal)
|
||||
new_decimal.match_style(decimal)
|
||||
|
||||
decimal.submobjects = new_decimal.submobjects
|
||||
decimal.number = new_number
|
||||
|
||||
def update_position(self):
|
||||
if self.position_update_func is not None:
|
||||
self.position_update_func(self.decimal_number_mobject)
|
||||
elif self.tracked_mobject is not None:
|
||||
self.decimal_number_mobject.move_to(self.tracked_mobject.get_center() + self.diff_from_tracked_mobject)
|
||||
|
||||
class ChangeDecimalToValue(ChangingDecimal):
|
||||
def __init__(self, decimal_number_mobject, target_number, **kwargs):
|
||||
start_number = decimal_number_mobject.number
|
||||
func = lambda alpha : interpolate(start_number, target_number, alpha)
|
||||
ChangingDecimal.__init__(self, decimal_number_mobject, func, **kwargs)
|
|
@ -1,62 +0,0 @@
|
|||
import numpy as np
|
||||
import operator as op
|
||||
|
||||
from .animation import Animation
|
||||
from transform import Transform
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.point_cloud_mobject import Mobject1D
|
||||
from topics.geometry import Line
|
||||
from utils.paths import path_along_arc
|
||||
|
||||
from constants import *
|
||||
|
||||
class Vibrate(Animation):
|
||||
CONFIG = {
|
||||
"spatial_period" : 6,
|
||||
"temporal_period" : 1,
|
||||
"overtones" : 4,
|
||||
"amplitude" : 0.5,
|
||||
"radius" : FRAME_X_RADIUS/2,
|
||||
"run_time" : 3.0,
|
||||
"rate_func" : None
|
||||
}
|
||||
def __init__(self, mobject = None, **kwargs):
|
||||
if mobject is None:
|
||||
mobject = Line(3*LEFT, 3*RIGHT)
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def wave_function(self, x, t):
|
||||
return sum([
|
||||
reduce(op.mul, [
|
||||
self.amplitude/(k**2), #Amplitude
|
||||
np.sin(2*np.pi*(k**1.5)*t/self.temporal_period), #Frequency
|
||||
np.sin(2*np.pi*k*x/self.spatial_period) #Number of waves
|
||||
])
|
||||
for k in range(1, self.overtones+1)
|
||||
])
|
||||
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
time = alpha*self.run_time
|
||||
families = map(
|
||||
Mobject.submobject_family,
|
||||
[self.mobject, self.starting_mobject]
|
||||
)
|
||||
for mob, start in zip(*families):
|
||||
mob.points = np.apply_along_axis(
|
||||
lambda (x, y, z) : (x, y + self.wave_function(x, time), z),
|
||||
1, start.points
|
||||
)
|
||||
|
||||
|
||||
class TurnInsideOut(Transform):
|
||||
CONFIG = {
|
||||
"path_func" : path_along_arc(np.pi/2)
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
mobject.sort_points(np.linalg.norm)
|
||||
mob_copy = mobject.copy()
|
||||
mob_copy.sort_points(lambda p : -np.linalg.norm(p))
|
||||
Transform.__init__(self, mobject, mob_copy, **kwargs)
|
||||
|
||||
|
61
animation/rotation.py
Normal file
61
animation/rotation.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
|
||||
import warnings
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.transform import Transform
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
class Rotating(Animation):
|
||||
CONFIG = {
|
||||
"axis" : OUT,
|
||||
"radians" : 2*np.pi,
|
||||
"run_time" : 5,
|
||||
"rate_func" : None,
|
||||
"in_place" : True,
|
||||
"about_point" : None,
|
||||
"about_edge" : None,
|
||||
}
|
||||
def update_submobject(self, submobject, starting_submobject, alpha):
|
||||
submobject.points = np.array(starting_submobject.points)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
Animation.update_mobject(self, alpha)
|
||||
about_point = None
|
||||
if self.about_point is not None:
|
||||
about_point = self.about_point
|
||||
elif self.in_place: #This is superseeded
|
||||
self.about_point = self.mobject.get_center()
|
||||
self.mobject.rotate(
|
||||
alpha*self.radians,
|
||||
axis = self.axis,
|
||||
about_point = self.about_point,
|
||||
about_edge = self.about_edge,
|
||||
)
|
||||
|
||||
class Rotate(Transform):
|
||||
CONFIG = {
|
||||
"in_place" : False,
|
||||
"about_point" : None,
|
||||
}
|
||||
def __init__(self, mobject, angle = np.pi, axis = OUT, **kwargs):
|
||||
if "path_arc" not in kwargs:
|
||||
kwargs["path_arc"] = angle
|
||||
if "path_arc_axis" not in kwargs:
|
||||
kwargs["path_arc_axis"] = axis
|
||||
digest_config(self, kwargs, locals())
|
||||
target = mobject.copy()
|
||||
if self.in_place:
|
||||
self.about_point = mobject.get_center()
|
||||
target.rotate(
|
||||
angle,
|
||||
axis = axis,
|
||||
about_point = self.about_point,
|
||||
)
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
|
|
@ -1,336 +0,0 @@
|
|||
import numpy as np
|
||||
import itertools as it
|
||||
|
||||
from constants import *
|
||||
|
||||
import warnings
|
||||
from mobject.mobject import Mobject, Group
|
||||
from mobject.vectorized_mobject import VMobject
|
||||
from mobject.tex_mobject import TextMobject
|
||||
from .animation import Animation
|
||||
from transform import Transform
|
||||
from utils.bezier import interpolate
|
||||
from utils.config_ops import digest_config
|
||||
from utils.rate_functions import smooth, double_smooth, there_and_back, wiggle
|
||||
|
||||
class Rotating(Animation):
|
||||
CONFIG = {
|
||||
"axis" : OUT,
|
||||
"radians" : 2*np.pi,
|
||||
"run_time" : 5,
|
||||
"rate_func" : None,
|
||||
"in_place" : True,
|
||||
"about_point" : None,
|
||||
"about_edge" : None,
|
||||
}
|
||||
def update_submobject(self, submobject, starting_submobject, alpha):
|
||||
submobject.points = np.array(starting_submobject.points)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
Animation.update_mobject(self, alpha)
|
||||
about_point = None
|
||||
if self.about_point is not None:
|
||||
about_point = self.about_point
|
||||
elif self.in_place: #This is superseeded
|
||||
self.about_point = self.mobject.get_center()
|
||||
self.mobject.rotate(
|
||||
alpha*self.radians,
|
||||
axis = self.axis,
|
||||
about_point = self.about_point,
|
||||
about_edge = self.about_edge,
|
||||
)
|
||||
|
||||
class ShowPartial(Animation):
|
||||
def update_submobject(self, submobject, starting_submobject, alpha):
|
||||
submobject.pointwise_become_partial(
|
||||
starting_submobject, *self.get_bounds(alpha)
|
||||
)
|
||||
|
||||
def get_bounds(self, alpha):
|
||||
raise Exception("Not Implemented")
|
||||
|
||||
class ShowCreation(ShowPartial):
|
||||
CONFIG = {
|
||||
"submobject_mode" : "one_at_a_time",
|
||||
}
|
||||
def get_bounds(self, alpha):
|
||||
return (0, alpha)
|
||||
|
||||
class Uncreate(ShowCreation):
|
||||
CONFIG = {
|
||||
"rate_func" : lambda t : smooth(1-t),
|
||||
"remover" : True
|
||||
}
|
||||
|
||||
class Write(ShowCreation):
|
||||
CONFIG = {
|
||||
"rate_func" : None,
|
||||
"submobject_mode" : "lagged_start",
|
||||
}
|
||||
def __init__(self, mob_or_text, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
if isinstance(mob_or_text, str):
|
||||
mobject = TextMobject(mob_or_text)
|
||||
else:
|
||||
mobject = mob_or_text
|
||||
if "run_time" not in kwargs:
|
||||
self.establish_run_time(mobject)
|
||||
if "lag_factor" not in kwargs:
|
||||
if len(mobject.family_members_with_points()) < 4:
|
||||
min_lag_factor = 1
|
||||
else:
|
||||
min_lag_factor = 2
|
||||
self.lag_factor = max(self.run_time - 1, min_lag_factor)
|
||||
ShowCreation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def establish_run_time(self, mobject):
|
||||
num_subs = len(mobject.family_members_with_points())
|
||||
if num_subs < 15:
|
||||
self.run_time = 1
|
||||
else:
|
||||
self.run_time = 2
|
||||
|
||||
class DrawBorderThenFill(Animation):
|
||||
CONFIG = {
|
||||
"run_time" : 2,
|
||||
"stroke_width" : 2,
|
||||
"stroke_color" : None,
|
||||
"rate_func" : double_smooth,
|
||||
}
|
||||
def __init__(self, vmobject, **kwargs):
|
||||
if not isinstance(vmobject, VMobject):
|
||||
raise Exception("DrawBorderThenFill only works for VMobjects")
|
||||
self.reached_halfway_point_before = False
|
||||
Animation.__init__(self, vmobject, **kwargs)
|
||||
|
||||
def update_submobject(self, submobject, starting_submobject, alpha):
|
||||
submobject.pointwise_become_partial(
|
||||
starting_submobject, 0, min(2*alpha, 1)
|
||||
)
|
||||
if alpha < 0.5:
|
||||
if self.stroke_color:
|
||||
color = self.stroke_color
|
||||
elif starting_submobject.stroke_width > 0:
|
||||
color = starting_submobject.get_stroke_color()
|
||||
else:
|
||||
color = starting_submobject.get_color()
|
||||
submobject.set_stroke(color, width = self.stroke_width)
|
||||
submobject.set_fill(opacity = 0)
|
||||
else:
|
||||
if not self.reached_halfway_point_before:
|
||||
self.reached_halfway_point_before = True
|
||||
submobject.points = np.array(starting_submobject.points)
|
||||
width, opacity = [
|
||||
interpolate(start, end, 2*alpha - 1)
|
||||
for start, end in [
|
||||
(self.stroke_width, starting_submobject.get_stroke_width()),
|
||||
(0, starting_submobject.get_fill_opacity())
|
||||
]
|
||||
]
|
||||
submobject.set_stroke(width = width)
|
||||
submobject.set_fill(opacity = opacity)
|
||||
|
||||
class ShowPassingFlash(ShowPartial):
|
||||
CONFIG = {
|
||||
"time_width" : 0.1,
|
||||
"remover" : True,
|
||||
}
|
||||
def get_bounds(self, alpha):
|
||||
alpha *= (1+self.time_width)
|
||||
alpha -= self.time_width/2.0
|
||||
lower = max(0, alpha - self.time_width/2.0)
|
||||
upper = min(1, alpha + self.time_width/2.0)
|
||||
return (lower, upper)
|
||||
|
||||
def clean_up(self, *args, **kwargs):
|
||||
ShowPartial.clean_up(self, *args, **kwargs)
|
||||
for submob, start_submob in self.get_all_families_zipped():
|
||||
submob.pointwise_become_partial(start_submob, 0, 1)
|
||||
|
||||
class ShowCreationThenDestruction(ShowPassingFlash):
|
||||
CONFIG = {
|
||||
"time_width" : 2.0,
|
||||
"run_time" : 1,
|
||||
}
|
||||
|
||||
class Homotopy(Animation):
|
||||
CONFIG = {
|
||||
"run_time" : 3,
|
||||
"apply_function_kwargs" : {},
|
||||
}
|
||||
def __init__(self, homotopy, mobject, **kwargs):
|
||||
"""
|
||||
Homotopy a function from (x, y, z, t) to (x', y', z')
|
||||
"""
|
||||
def function_at_time_t(t):
|
||||
return lambda p : homotopy(p[0], p[1], p[2], t)
|
||||
self.function_at_time_t = function_at_time_t
|
||||
digest_config(self, kwargs)
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_submobject(self, submob, start, alpha):
|
||||
submob.points = start.points
|
||||
submob.apply_function(
|
||||
self.function_at_time_t(alpha),
|
||||
**self.apply_function_kwargs
|
||||
)
|
||||
|
||||
class SmoothedVectorizedHomotopy(Homotopy):
|
||||
def update_submobject(self, submob, start, alpha):
|
||||
Homotopy.update_submobject(self, submob, start, alpha)
|
||||
submob.make_smooth()
|
||||
|
||||
class ApplyWave(Homotopy):
|
||||
CONFIG = {
|
||||
"direction" : DOWN,
|
||||
"amplitude" : 0.2,
|
||||
"run_time" : 1,
|
||||
"apply_function_kwargs" : {
|
||||
"maintain_smoothness" : False,
|
||||
},
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
left_x = mobject.get_left()[0]
|
||||
right_x = mobject.get_right()[0]
|
||||
vect = self.amplitude*self.direction
|
||||
def homotopy(x, y, z, t):
|
||||
start_point = np.array([x, y, z])
|
||||
alpha = (x-left_x)/(right_x-left_x)
|
||||
power = np.exp(2*(alpha-0.5))
|
||||
nudge = there_and_back(t**power)
|
||||
return np.array([x, y, z]) + nudge*vect
|
||||
Homotopy.__init__(self, homotopy, mobject, **kwargs)
|
||||
|
||||
class PhaseFlow(Animation):
|
||||
CONFIG = {
|
||||
"virtual_time" : 1,
|
||||
"rate_func" : None,
|
||||
}
|
||||
def __init__(self, function, mobject, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
if hasattr(self, "last_alpha"):
|
||||
dt = self.virtual_time*(alpha-self.last_alpha)
|
||||
self.mobject.apply_function(
|
||||
lambda p : p + dt*self.function(p)
|
||||
)
|
||||
self.last_alpha = alpha
|
||||
|
||||
class MoveAlongPath(Animation):
|
||||
def __init__(self, mobject, path, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
point = self.path.point_from_proportion(alpha)
|
||||
self.mobject.move_to(point)
|
||||
|
||||
class UpdateFromFunc(Animation):
|
||||
"""
|
||||
update_function of the form func(mobject), presumably
|
||||
to be used when the state of one mobject is dependent
|
||||
on another simultaneously animated mobject
|
||||
"""
|
||||
def __init__(self, mobject, update_function, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
self.update_function(self.mobject)
|
||||
|
||||
class UpdateFromAlphaFunc(UpdateFromFunc):
|
||||
def update_mobject(self, alpha):
|
||||
self.update_function(self.mobject, alpha)
|
||||
|
||||
class MaintainPositionRelativeTo(Animation):
|
||||
CONFIG = {
|
||||
"tracked_critical_point" : ORIGIN
|
||||
}
|
||||
def __init__(self, mobject, tracked_mobject, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
tcp = self.tracked_critical_point
|
||||
self.diff = mobject.get_critical_point(tcp) - \
|
||||
tracked_mobject.get_critical_point(tcp)
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
self.mobject.shift(
|
||||
self.tracked_mobject.get_critical_point(self.tracked_critical_point) - \
|
||||
self.mobject.get_critical_point(self.tracked_critical_point) + \
|
||||
self.diff
|
||||
)
|
||||
|
||||
class WiggleOutThenIn(Animation):
|
||||
CONFIG = {
|
||||
"scale_value" : 1.1,
|
||||
"rotation_angle" : 0.01*TAU,
|
||||
"n_wiggles" : 6,
|
||||
"run_time" : 2,
|
||||
"scale_about_point" : None,
|
||||
"rotate_about_point" : None,
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
if self.scale_about_point is None:
|
||||
self.scale_about_point = mobject.get_center()
|
||||
if self.rotate_about_point is None:
|
||||
self.rotate_about_point = mobject.get_center()
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_submobject(self, submobject, starting_sumobject, alpha):
|
||||
submobject.points[:,:] = starting_sumobject.points
|
||||
submobject.scale(
|
||||
interpolate(1, self.scale_value, there_and_back(alpha)),
|
||||
about_point = self.scale_about_point
|
||||
)
|
||||
submobject.rotate(
|
||||
wiggle(alpha, self.n_wiggles)*self.rotation_angle,
|
||||
about_point = self.rotate_about_point
|
||||
)
|
||||
|
||||
class ApplyToCenters(Animation):
|
||||
def __init__(self, AnimationClass, mobjects, **kwargs):
|
||||
full_kwargs = AnimationClass.CONFIG
|
||||
full_kwargs.update(kwargs)
|
||||
full_kwargs["mobject"] = Mobject(*[
|
||||
mob.get_point_mobject()
|
||||
for mob in mobjects
|
||||
])
|
||||
self.centers_container = AnimationClass(**full_kwargs)
|
||||
full_kwargs.pop("mobject")
|
||||
Animation.__init__(self, Mobject(*mobjects), **full_kwargs)
|
||||
self.name = str(self) + AnimationClass.__name__
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
self.centers_container.update_mobject(alpha)
|
||||
center_mobs = self.centers_container.mobject.split()
|
||||
mobjects = self.mobject.split()
|
||||
for center_mob, mobject in zip(center_mobs, mobjects):
|
||||
mobject.shift(
|
||||
center_mob.get_center()-mobject.get_center()
|
||||
)
|
||||
|
||||
class FadeInAndShiftFromDirection(Transform):
|
||||
CONFIG = {
|
||||
"direction" : DOWN,
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
target = mobject.copy()
|
||||
mobject.shift(self.direction)
|
||||
mobject.fade(1)
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
|
||||
# Essentially just a more convenient name for the above animation
|
||||
class FadeInFromDown(FadeInAndShiftFromDirection):
|
||||
CONFIG = {
|
||||
"direction" : DOWN,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
67
animation/specialized.py
Normal file
67
animation/specialized.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
|
||||
from animation.transform import ApplyMethod
|
||||
from animation.composition import LaggedStart
|
||||
from mobject.svg.drawings import Car
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.geometry import Circle
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
class MoveCar(ApplyMethod):
|
||||
CONFIG = {
|
||||
"moving_forward" : True,
|
||||
}
|
||||
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 = np.linalg.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 update_mobject(self, alpha):
|
||||
ApplyMethod.update_mobject(self, alpha)
|
||||
if alpha == 0:
|
||||
return
|
||||
radians = alpha*self.total_tire_radians
|
||||
for tire in self.mobject.get_tires():
|
||||
tire.rotate_in_place(radians)
|
||||
|
||||
class Broadcast(LaggedStart):
|
||||
CONFIG = {
|
||||
"small_radius" : 0.0,
|
||||
"big_radius" : 5,
|
||||
"n_circles" : 5,
|
||||
"start_stroke_width" : 8,
|
||||
"color" : WHITE,
|
||||
"remover" : True,
|
||||
"lag_ratio" : 0.7,
|
||||
"run_time" : 3,
|
||||
"remover" : True,
|
||||
}
|
||||
def __init__(self, focal_point, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
circles = VGroup()
|
||||
for x in range(self.n_circles):
|
||||
circle = Circle(
|
||||
radius = self.big_radius,
|
||||
stroke_color = BLACK,
|
||||
stroke_width = 0,
|
||||
)
|
||||
circle.move_to(focal_point)
|
||||
circle.save_state()
|
||||
circle.scale_to_fit_width(self.small_radius*2)
|
||||
circle.set_stroke(self.color, self.start_stroke_width)
|
||||
circles.add(circle)
|
||||
LaggedStart.__init__(
|
||||
self, ApplyMethod, circles,
|
||||
lambda c : (c.restore,),
|
||||
**kwargs
|
||||
)
|
||||
|
|
@ -1,20 +1,21 @@
|
|||
import numpy as np
|
||||
import itertools as it
|
||||
from __future__ import absolute_import
|
||||
|
||||
import inspect
|
||||
import copy
|
||||
import warnings
|
||||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
|
||||
from .animation import Animation
|
||||
from mobject.mobject import Mobject, Group
|
||||
from mobject.vectorized_mobject import VMobject, VectorizedPoint
|
||||
from topics.geometry import Dot, Circle
|
||||
from animation.animation import Animation
|
||||
from mobject.mobject import Group
|
||||
from mobject.mobject import Mobject
|
||||
from utils.config_ops import digest_config
|
||||
from utils.iterables import adjacent_pairs
|
||||
from utils.paths import straight_path, path_along_arc, counterclockwise_path
|
||||
from utils.rate_functions import smooth, there_and_back
|
||||
from utils.paths import path_along_arc
|
||||
from utils.paths import straight_path
|
||||
from utils.config_ops import instantiate
|
||||
from utils.rate_functions import smooth
|
||||
from utils.rate_functions import squish_rate_func
|
||||
from utils.space_ops import complex_to_R3
|
||||
|
||||
class Transform(Animation):
|
||||
CONFIG = {
|
||||
|
@ -74,7 +75,6 @@ class ReplacementTransform(Transform):
|
|||
"replace_mobject_with_target_in_scene" : True,
|
||||
}
|
||||
|
||||
|
||||
class ClockwiseTransform(Transform):
|
||||
CONFIG = {
|
||||
"path_arc" : -np.pi
|
||||
|
@ -91,54 +91,6 @@ class MoveToTarget(Transform):
|
|||
raise Exception("MoveToTarget called on mobject without attribute 'target' ")
|
||||
Transform.__init__(self, mobject, mobject.target, **kwargs)
|
||||
|
||||
class CyclicReplace(Transform):
|
||||
CONFIG = {
|
||||
"path_arc" : np.pi/2
|
||||
}
|
||||
def __init__(self, *mobjects, **kwargs):
|
||||
start = Group(*mobjects)
|
||||
target = Group(*[
|
||||
m1.copy().move_to(m2)
|
||||
for m1, m2 in adjacent_pairs(start)
|
||||
])
|
||||
Transform.__init__(self, start, target, **kwargs)
|
||||
|
||||
class Swap(CyclicReplace):
|
||||
pass #Renaming, more understandable for two entries
|
||||
|
||||
class GrowFromPoint(Transform):
|
||||
CONFIG = {
|
||||
"point_color" : None,
|
||||
}
|
||||
def __init__(self, mobject, point, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
target = mobject.copy()
|
||||
point_mob = VectorizedPoint(point)
|
||||
if self.point_color:
|
||||
point_mob.set_color(self.point_color)
|
||||
mobject.replace(point_mob)
|
||||
mobject.set_color(point_mob.get_color())
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
|
||||
class GrowFromCenter(GrowFromPoint):
|
||||
def __init__(self, mobject, **kwargs):
|
||||
GrowFromPoint.__init__(self, mobject, mobject.get_center(), **kwargs)
|
||||
|
||||
class GrowArrow(GrowFromPoint):
|
||||
def __init__(self, arrow, **kwargs):
|
||||
GrowFromPoint.__init__(self, arrow, arrow.get_start(), **kwargs)
|
||||
|
||||
class SpinInFromNothing(GrowFromCenter):
|
||||
CONFIG = {
|
||||
"path_func" : counterclockwise_path()
|
||||
}
|
||||
|
||||
class ShrinkToCenter(Transform):
|
||||
def __init__(self, mobject, **kwargs):
|
||||
Transform.__init__(
|
||||
self, mobject, mobject.get_point_mobject(), **kwargs
|
||||
)
|
||||
|
||||
class ApplyMethod(Transform):
|
||||
CONFIG = {
|
||||
"submobject_mode" : "all_at_once"
|
||||
|
@ -167,94 +119,6 @@ class ApplyMethod(Transform):
|
|||
method.im_func(target, *args, **method_kwargs)
|
||||
Transform.__init__(self, method.im_self, target, **kwargs)
|
||||
|
||||
class FadeOut(Transform):
|
||||
CONFIG = {
|
||||
"remover" : True,
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
target = mobject.copy()
|
||||
target.fade(1)
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
|
||||
def clean_up(self, surrounding_scene = None):
|
||||
Transform.clean_up(self, surrounding_scene)
|
||||
self.update(0)
|
||||
|
||||
class FadeIn(Transform):
|
||||
def __init__(self, mobject, **kwargs):
|
||||
target = mobject.copy()
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
self.starting_mobject.fade(1)
|
||||
if isinstance(self.starting_mobject, VMobject):
|
||||
self.starting_mobject.set_stroke(width = 0)
|
||||
self.starting_mobject.set_fill(opacity = 0)
|
||||
|
||||
class FocusOn(Transform):
|
||||
CONFIG = {
|
||||
"opacity" : 0.2,
|
||||
"color" : GREY,
|
||||
"run_time" : 2,
|
||||
"remover" : True,
|
||||
}
|
||||
def __init__(self, mobject_or_point, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
big_dot = Dot(
|
||||
radius = FRAME_X_RADIUS+FRAME_Y_RADIUS,
|
||||
stroke_width = 0,
|
||||
fill_color = self.color,
|
||||
fill_opacity = 0,
|
||||
)
|
||||
little_dot = Dot(radius = 0)
|
||||
little_dot.set_fill(self.color, opacity = self.opacity)
|
||||
little_dot.move_to(mobject_or_point)
|
||||
|
||||
Transform.__init__(self, big_dot, little_dot, **kwargs)
|
||||
|
||||
class Indicate(Transform):
|
||||
CONFIG = {
|
||||
"rate_func" : there_and_back,
|
||||
"scale_factor" : 1.2,
|
||||
"color" : YELLOW,
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
target = mobject.copy()
|
||||
target.scale_in_place(self.scale_factor)
|
||||
target.set_color(self.color)
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
|
||||
class CircleIndicate(Indicate):
|
||||
CONFIG = {
|
||||
"rate_func" : squish_rate_func(there_and_back, 0, 0.8),
|
||||
"remover" : True
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
circle = Circle(color = self.color, **kwargs)
|
||||
circle.surround(mobject)
|
||||
Indicate.__init__(self, circle, **kwargs)
|
||||
|
||||
class Rotate(ApplyMethod):
|
||||
CONFIG = {
|
||||
"in_place" : False,
|
||||
"about_point" : None,
|
||||
}
|
||||
def __init__(self, mobject, angle = np.pi, axis = OUT, **kwargs):
|
||||
if "path_arc" not in kwargs:
|
||||
kwargs["path_arc"] = angle
|
||||
if "path_arc_axis" not in kwargs:
|
||||
kwargs["path_arc_axis"] = axis
|
||||
digest_config(self, kwargs, locals())
|
||||
target = mobject.copy()
|
||||
if self.in_place:
|
||||
self.about_point = mobject.get_center()
|
||||
target.rotate(
|
||||
angle,
|
||||
axis = axis,
|
||||
about_point = self.about_point,
|
||||
)
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
|
||||
class ApplyPointwiseFunction(ApplyMethod):
|
||||
CONFIG = {
|
||||
"run_time" : DEFAULT_POINTWISE_FUNCTION_RUN_TIME
|
||||
|
@ -300,7 +164,37 @@ class ApplyMatrix(ApplyPointwiseFunction):
|
|||
return np.dot(p, transpose)
|
||||
ApplyPointwiseFunction.__init__(self, func, mobject, **kwargs)
|
||||
|
||||
class ComplexFunction(ApplyPointwiseFunction):
|
||||
def __init__(self, function, mobject, **kwargs):
|
||||
if "path_func" not in kwargs:
|
||||
self.path_func = path_along_arc(
|
||||
np.log(function(complex(1))).imag
|
||||
)
|
||||
ApplyPointwiseFunction.__init__(
|
||||
self,
|
||||
lambda (x, y, z) : complex_to_R3(function(complex(x, y))),
|
||||
instantiate(mobject),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
###
|
||||
|
||||
class CyclicReplace(Transform):
|
||||
CONFIG = {
|
||||
"path_arc" : np.pi/2
|
||||
}
|
||||
def __init__(self, *mobjects, **kwargs):
|
||||
start = Group(*mobjects)
|
||||
target = Group(*[
|
||||
m1.copy().move_to(m2)
|
||||
for m1, m2 in adjacent_pairs(start)
|
||||
])
|
||||
Transform.__init__(self, start, target, **kwargs)
|
||||
|
||||
class Swap(CyclicReplace):
|
||||
pass #Renaming, more understandable for two entries
|
||||
|
||||
#TODO: Um...does this work
|
||||
class TransformAnimations(Transform):
|
||||
CONFIG = {
|
||||
"rate_func" : squish_rate_func(smooth)
|
||||
|
@ -330,5 +224,3 @@ class TransformAnimations(Transform):
|
|||
self.end_anim.update(alpha)
|
||||
Transform.update(self, alpha)
|
||||
|
||||
|
||||
|
||||
|
|
47
animation/update.py
Normal file
47
animation/update.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from animation.animation import Animation
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
|
||||
class UpdateFromFunc(Animation):
|
||||
"""
|
||||
update_function of the form func(mobject), presumably
|
||||
to be used when the state of one mobject is dependent
|
||||
on another simultaneously animated mobject
|
||||
"""
|
||||
def __init__(self, mobject, update_function, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
self.update_function(self.mobject)
|
||||
|
||||
class UpdateFromAlphaFunc(UpdateFromFunc):
|
||||
def update_mobject(self, alpha):
|
||||
self.update_function(self.mobject, alpha)
|
||||
|
||||
class MaintainPositionRelativeTo(Animation):
|
||||
CONFIG = {
|
||||
"tracked_critical_point" : ORIGIN
|
||||
}
|
||||
def __init__(self, mobject, tracked_mobject, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
tcp = self.tracked_critical_point
|
||||
self.diff = mobject.get_critical_point(tcp) - \
|
||||
tracked_mobject.get_critical_point(tcp)
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
self.mobject.shift(
|
||||
self.tracked_mobject.get_critical_point(self.tracked_critical_point) - \
|
||||
self.mobject.get_critical_point(self.tracked_critical_point) + \
|
||||
self.diff
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -16,47 +16,66 @@ as a convenience for scripts createing scenes for videos
|
|||
from constants import *
|
||||
|
||||
from animation.animation import *
|
||||
from animation.compositions import *
|
||||
from animation.continual_animation import *
|
||||
from animation.playground import *
|
||||
from animation.simple_animations import *
|
||||
from animation.composition import *
|
||||
from animation.creation import *
|
||||
from animation.indication import *
|
||||
from animation.movement import *
|
||||
from animation.numbers import *
|
||||
from animation.rotation import *
|
||||
from animation.specialized import *
|
||||
from animation.transform import *
|
||||
from animation.update import *
|
||||
|
||||
from camera.camera import *
|
||||
from camera.mapping_camera import *
|
||||
from camera.moving_camera import *
|
||||
from camera.three_d_camera import *
|
||||
|
||||
from mobject.image_mobject import *
|
||||
from continual_animation.continual_animation import *
|
||||
from continual_animation.from_animation import *
|
||||
from continual_animation.numbers import *
|
||||
from continual_animation.update import *
|
||||
|
||||
from mobject.frame import *
|
||||
from mobject.functions import *
|
||||
from mobject.geometry import *
|
||||
from mobject.matrix import *
|
||||
from mobject.mobject import *
|
||||
from mobject.point_cloud_mobject import *
|
||||
from mobject.svg_mobject import *
|
||||
from mobject.tex_mobject import *
|
||||
from mobject.vectorized_mobject import *
|
||||
from mobject.number_line import *
|
||||
from mobject.numbers import *
|
||||
from mobject.probability import *
|
||||
from mobject.shape_matchers import *
|
||||
from mobject.svg.brace import *
|
||||
from mobject.svg.svg_mobject import *
|
||||
from mobject.svg.tex_mobject import *
|
||||
from mobject.three_dimensions import *
|
||||
from mobject.types.image_mobject import *
|
||||
from mobject.types.point_cloud_mobject import *
|
||||
from mobject.types.vectorized_mobject import *
|
||||
from mobject.value_tracker import *
|
||||
|
||||
from for_3b1b_videos.common_scenes import *
|
||||
from for_3b1b_videos.pi_creature import *
|
||||
from for_3b1b_videos.pi_creature_animations import *
|
||||
from for_3b1b_videos.pi_creature_scene import *
|
||||
|
||||
from scene.graph_scene import *
|
||||
from scene.moving_camera_scene import *
|
||||
from scene.reconfigurable_scene import *
|
||||
from scene.scene import *
|
||||
from scene.scene_from_video import *
|
||||
from scene.tk_scene import *
|
||||
from scene.three_d_scene import *
|
||||
from scene.vector_space_scene import *
|
||||
from scene.zoomed_scene import *
|
||||
|
||||
from topics.arithmetic import *
|
||||
from topics.characters import *
|
||||
from topics.combinatorics import *
|
||||
from topics.common_scenes import *
|
||||
from topics.complex_numbers import *
|
||||
from topics.counting import *
|
||||
from topics.fractals import *
|
||||
from topics.functions import *
|
||||
from topics.geometry import *
|
||||
from topics.graph_scene import *
|
||||
from topics.graph_theory import *
|
||||
from topics.light import *
|
||||
from topics.matrix import *
|
||||
from topics.number_line import *
|
||||
from topics.numerals import *
|
||||
from topics.objects import *
|
||||
from topics.probability import *
|
||||
from topics.three_dimensions import *
|
||||
from topics.vector_space_scene import *
|
||||
from once_useful_constructs.arithmetic import *
|
||||
from once_useful_constructs.combinatorics import *
|
||||
from once_useful_constructs.complex_transformation_scene import *
|
||||
from once_useful_constructs.counting import *
|
||||
from once_useful_constructs.fractals import *
|
||||
from once_useful_constructs.graph_theory import *
|
||||
from once_useful_constructs.light import *
|
||||
|
||||
|
||||
from utils.bezier import *
|
||||
from utils.color import *
|
||||
|
@ -70,18 +89,17 @@ from utils.sounds import *
|
|||
from utils.space_ops import *
|
||||
from utils.strings import *
|
||||
|
||||
from special_animations import *
|
||||
|
||||
# Non manim libraries that are also nice to have without thinking
|
||||
|
||||
import numpy as np
|
||||
import itertools as it
|
||||
import operator as op
|
||||
import random
|
||||
import inspect
|
||||
import string
|
||||
import re
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
import operator as op
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
from colour import Color
|
||||
|
||||
|
|
155
camera/camera.py
155
camera/camera.py
|
@ -1,25 +1,27 @@
|
|||
import numpy as np
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
from colour import Color
|
||||
import aggdraw
|
||||
import copy
|
||||
import time
|
||||
|
||||
from constants import *
|
||||
from mobject.mobject import Mobject, Group
|
||||
from mobject.point_cloud_mobject import PMobject
|
||||
from mobject.vectorized_mobject import VMobject
|
||||
from mobject.image_mobject import ImageMobject
|
||||
from utils.color import rgb_to_hex, color_to_int_rgba
|
||||
from utils.config_ops import digest_config, digest_locals, DictAsObject
|
||||
from utils.images import get_full_raster_image_path
|
||||
from utils.iterables import remove_list_redundancies, list_difference_update
|
||||
from utils.iterables import batch_by_property
|
||||
from utils.simple_functions import fdiv
|
||||
from PIL import Image
|
||||
from colour import Color
|
||||
|
||||
from constants import *
|
||||
from mobject.types.image_mobject import ImageMobject
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.types.point_cloud_mobject import PMobject
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from utils.color import color_to_int_rgba
|
||||
from utils.color import rgb_to_hex
|
||||
from utils.config_ops import digest_config
|
||||
from utils.images import get_full_raster_image_path
|
||||
from utils.iterables import batch_by_property
|
||||
from utils.iterables import list_difference_update
|
||||
from utils.iterables import remove_list_redundancies
|
||||
from utils.simple_functions import fdiv
|
||||
|
||||
class Camera(object):
|
||||
CONFIG = {
|
||||
|
@ -607,128 +609,3 @@ class BackgroundColoredVMobjectDisplayer(object):
|
|||
self.reset_canvas()
|
||||
return curr_array
|
||||
|
||||
|
||||
class MovingCamera(Camera):
|
||||
"""
|
||||
Stays in line with the height, width and position
|
||||
of a given mobject
|
||||
"""
|
||||
CONFIG = {
|
||||
"aligned_dimension" : "width" #or height
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_locals(self)
|
||||
Camera.__init__(self, **kwargs)
|
||||
|
||||
def capture_mobjects(self, *args, **kwargs):
|
||||
self.space_center = self.mobject.get_center()
|
||||
self.realign_frame_shape()
|
||||
Camera.capture_mobjects(self, *args, **kwargs)
|
||||
|
||||
def realign_frame_shape(self):
|
||||
height, width = self.frame_shape
|
||||
if self.aligned_dimension == "height":
|
||||
self.frame_shape = (self.mobject.get_height(), width)
|
||||
else:
|
||||
self.frame_shape = (height, self.mobject.get_width())
|
||||
self.resize_frame_shape(0 if self.aligned_dimension == "height" else 1)
|
||||
|
||||
# TODO: Add an attribute to mobjects under which they can specify that they should just
|
||||
# map their centers but remain otherwise undistorted (useful for labels, etc.)
|
||||
class MappingCamera(Camera):
|
||||
CONFIG = {
|
||||
"mapping_func" : lambda p : p,
|
||||
"min_anchor_points" : 50,
|
||||
"allow_object_intrusion" : False
|
||||
}
|
||||
|
||||
def points_to_pixel_coords(self, points):
|
||||
return Camera.points_to_pixel_coords(self, np.apply_along_axis(self.mapping_func, 1, points))
|
||||
|
||||
def capture_mobjects(self, mobjects, **kwargs):
|
||||
mobjects = self.get_mobjects_to_display(mobjects, **kwargs)
|
||||
if self.allow_object_intrusion:
|
||||
mobject_copies = mobjects
|
||||
else:
|
||||
mobject_copies = [mobject.copy() for mobject in mobjects]
|
||||
for mobject in mobject_copies:
|
||||
if isinstance(mobject, VMobject) and \
|
||||
0 < mobject.get_num_anchor_points() < self.min_anchor_points:
|
||||
mobject.insert_n_anchor_points(self.min_anchor_points)
|
||||
Camera.capture_mobjects(
|
||||
self, mobject_copies,
|
||||
include_submobjects = False,
|
||||
excluded_mobjects = None,
|
||||
)
|
||||
|
||||
# Note: This allows layering of multiple cameras onto the same portion of the pixel array,
|
||||
# the later cameras overwriting the former
|
||||
#
|
||||
# TODO: Add optional separator borders between cameras (or perhaps peel this off into a
|
||||
# CameraPlusOverlay class)
|
||||
class MultiCamera(Camera):
|
||||
def __init__(self, *cameras_with_start_positions, **kwargs):
|
||||
self.shifted_cameras = [
|
||||
DictAsObject(
|
||||
{
|
||||
"camera" : camera_with_start_positions[0],
|
||||
"start_x" : camera_with_start_positions[1][1],
|
||||
"start_y" : camera_with_start_positions[1][0],
|
||||
"end_x" : camera_with_start_positions[1][1] + camera_with_start_positions[0].pixel_shape[1],
|
||||
"end_y" : camera_with_start_positions[1][0] + camera_with_start_positions[0].pixel_shape[0],
|
||||
})
|
||||
for camera_with_start_positions in cameras_with_start_positions
|
||||
]
|
||||
Camera.__init__(self, **kwargs)
|
||||
|
||||
def capture_mobjects(self, mobjects, **kwargs):
|
||||
for shifted_camera in self.shifted_cameras:
|
||||
shifted_camera.camera.capture_mobjects(mobjects, **kwargs)
|
||||
|
||||
self.pixel_array[
|
||||
shifted_camera.start_y:shifted_camera.end_y,
|
||||
shifted_camera.start_x:shifted_camera.end_x] \
|
||||
= shifted_camera.camera.pixel_array
|
||||
|
||||
def set_background(self, pixel_array, **kwargs):
|
||||
for shifted_camera in self.shifted_cameras:
|
||||
shifted_camera.camera.set_background(
|
||||
pixel_array[
|
||||
shifted_camera.start_y:shifted_camera.end_y,
|
||||
shifted_camera.start_x:shifted_camera.end_x],
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def set_pixel_array(self, pixel_array, **kwargs):
|
||||
Camera.set_pixel_array(self, pixel_array, **kwargs)
|
||||
for shifted_camera in self.shifted_cameras:
|
||||
shifted_camera.camera.set_pixel_array(
|
||||
pixel_array[
|
||||
shifted_camera.start_y:shifted_camera.end_y,
|
||||
shifted_camera.start_x:shifted_camera.end_x],
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def init_background(self):
|
||||
Camera.init_background(self)
|
||||
for shifted_camera in self.shifted_cameras:
|
||||
shifted_camera.camera.init_background()
|
||||
|
||||
# A MultiCamera which, when called with two full-size cameras, initializes itself
|
||||
# as a splitscreen, also taking care to resize each individual camera within it
|
||||
class SplitScreenCamera(MultiCamera):
|
||||
def __init__(self, left_camera, right_camera, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.left_camera = left_camera
|
||||
self.right_camera = right_camera
|
||||
|
||||
half_width = self.pixel_shape[1] / 2
|
||||
for camera in [self.left_camera, self.right_camera]:
|
||||
camera.pixel_shape = (self.pixel_shape[0], half_width) # TODO: Round up on one if width is odd
|
||||
camera.init_background()
|
||||
camera.resize_frame_shape()
|
||||
camera.reset()
|
||||
|
||||
MultiCamera.__init__(self, (left_camera, (0, 0)), (right_camera, (0, half_width)))
|
||||
|
||||
|
||||
|
|
104
camera/mapping_camera.py
Normal file
104
camera/mapping_camera.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from camera.camera import Camera
|
||||
from utils.config_ops import DictAsObject
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
# TODO: Add an attribute to mobjects under which they can specify that they should just
|
||||
# map their centers but remain otherwise undistorted (useful for labels, etc.)
|
||||
class MappingCamera(Camera):
|
||||
CONFIG = {
|
||||
"mapping_func" : lambda p : p,
|
||||
"min_anchor_points" : 50,
|
||||
"allow_object_intrusion" : False
|
||||
}
|
||||
|
||||
def points_to_pixel_coords(self, points):
|
||||
return Camera.points_to_pixel_coords(self, np.apply_along_axis(self.mapping_func, 1, points))
|
||||
|
||||
def capture_mobjects(self, mobjects, **kwargs):
|
||||
mobjects = self.get_mobjects_to_display(mobjects, **kwargs)
|
||||
if self.allow_object_intrusion:
|
||||
mobject_copies = mobjects
|
||||
else:
|
||||
mobject_copies = [mobject.copy() for mobject in mobjects]
|
||||
for mobject in mobject_copies:
|
||||
if isinstance(mobject, VMobject) and \
|
||||
0 < mobject.get_num_anchor_points() < self.min_anchor_points:
|
||||
mobject.insert_n_anchor_points(self.min_anchor_points)
|
||||
Camera.capture_mobjects(
|
||||
self, mobject_copies,
|
||||
include_submobjects = False,
|
||||
excluded_mobjects = None,
|
||||
)
|
||||
|
||||
# Note: This allows layering of multiple cameras onto the same portion of the pixel array,
|
||||
# the later cameras overwriting the former
|
||||
#
|
||||
# TODO: Add optional separator borders between cameras (or perhaps peel this off into a
|
||||
# CameraPlusOverlay class)
|
||||
class MultiCamera(Camera):
|
||||
def __init__(self, *cameras_with_start_positions, **kwargs):
|
||||
self.shifted_cameras = [
|
||||
DictAsObject(
|
||||
{
|
||||
"camera" : camera_with_start_positions[0],
|
||||
"start_x" : camera_with_start_positions[1][1],
|
||||
"start_y" : camera_with_start_positions[1][0],
|
||||
"end_x" : camera_with_start_positions[1][1] + camera_with_start_positions[0].pixel_shape[1],
|
||||
"end_y" : camera_with_start_positions[1][0] + camera_with_start_positions[0].pixel_shape[0],
|
||||
})
|
||||
for camera_with_start_positions in cameras_with_start_positions
|
||||
]
|
||||
Camera.__init__(self, **kwargs)
|
||||
|
||||
def capture_mobjects(self, mobjects, **kwargs):
|
||||
for shifted_camera in self.shifted_cameras:
|
||||
shifted_camera.camera.capture_mobjects(mobjects, **kwargs)
|
||||
|
||||
self.pixel_array[
|
||||
shifted_camera.start_y:shifted_camera.end_y,
|
||||
shifted_camera.start_x:shifted_camera.end_x] \
|
||||
= shifted_camera.camera.pixel_array
|
||||
|
||||
def set_background(self, pixel_array, **kwargs):
|
||||
for shifted_camera in self.shifted_cameras:
|
||||
shifted_camera.camera.set_background(
|
||||
pixel_array[
|
||||
shifted_camera.start_y:shifted_camera.end_y,
|
||||
shifted_camera.start_x:shifted_camera.end_x],
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def set_pixel_array(self, pixel_array, **kwargs):
|
||||
Camera.set_pixel_array(self, pixel_array, **kwargs)
|
||||
for shifted_camera in self.shifted_cameras:
|
||||
shifted_camera.camera.set_pixel_array(
|
||||
pixel_array[
|
||||
shifted_camera.start_y:shifted_camera.end_y,
|
||||
shifted_camera.start_x:shifted_camera.end_x],
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def init_background(self):
|
||||
Camera.init_background(self)
|
||||
for shifted_camera in self.shifted_cameras:
|
||||
shifted_camera.camera.init_background()
|
||||
|
||||
# A MultiCamera which, when called with two full-size cameras, initializes itself
|
||||
# as a splitscreen, also taking care to resize each individual camera within it
|
||||
class SplitScreenCamera(MultiCamera):
|
||||
def __init__(self, left_camera, right_camera, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.left_camera = left_camera
|
||||
self.right_camera = right_camera
|
||||
|
||||
half_width = self.pixel_shape[1] / 2
|
||||
for camera in [self.left_camera, self.right_camera]:
|
||||
camera.pixel_shape = (self.pixel_shape[0], half_width) # TODO: Round up on one if width is odd
|
||||
camera.init_background()
|
||||
camera.resize_frame_shape()
|
||||
camera.reset()
|
||||
|
||||
MultiCamera.__init__(self, (left_camera, (0, 0)), (right_camera, (0, half_width)))
|
||||
|
32
camera/moving_camera.py
Normal file
32
camera/moving_camera.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from camera.camera import Camera
|
||||
|
||||
class MovingCamera(Camera):
|
||||
"""
|
||||
Stays in line with the height, width and position
|
||||
of a given mobject
|
||||
"""
|
||||
CONFIG = {
|
||||
"aligned_dimension" : "width" #or height
|
||||
}
|
||||
def __init__(self, frame, **kwargs):
|
||||
"""
|
||||
frame is a Mobject, (should be a rectangle) determining
|
||||
which region of space the camera displys
|
||||
"""
|
||||
self.frame = frame
|
||||
Camera.__init__(self, **kwargs)
|
||||
|
||||
def capture_mobjects(self, *args, **kwargs):
|
||||
self.space_center = self.frame.get_center()
|
||||
self.realign_frame_shape()
|
||||
Camera.capture_mobjects(self, *args, **kwargs)
|
||||
|
||||
def realign_frame_shape(self):
|
||||
height, width = self.frame_shape
|
||||
if self.aligned_dimension == "height":
|
||||
self.frame_shape = (self.frame.get_height(), width)
|
||||
else:
|
||||
self.frame_shape = (height, self.frame.get_width())
|
||||
self.resize_frame_shape(0 if self.aligned_dimension == "height" else 1)
|
|
@ -1,17 +1,16 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint
|
||||
from topics.geometry import Square, Line
|
||||
from scene.scene import Scene
|
||||
from camera.camera import Camera
|
||||
from animation.continual_animation import AmbientMovement
|
||||
from animation.transform import ApplyMethod
|
||||
from mobject.types.vectorized_mobject import VectorizedPoint
|
||||
from mobject.three_dimensions import should_shade_in_3d
|
||||
|
||||
from utils.bezier import interpolate
|
||||
from utils.iterables import list_update
|
||||
from utils.space_ops import rotation_matrix, rotation_about_z, z_to_vector
|
||||
|
||||
from utils.space_ops import rotation_about_z
|
||||
from utils.space_ops import rotation_matrix
|
||||
|
||||
class CameraWithPerspective(Camera):
|
||||
CONFIG = {
|
||||
|
@ -42,6 +41,7 @@ class ThreeDCamera(CameraWithPerspective):
|
|||
Camera.__init__(self, *args, **kwargs)
|
||||
self.unit_sun_vect = self.sun_vect/np.linalg.norm(self.sun_vect)
|
||||
## rotation_mobject lives in the phi-theta-distance space
|
||||
## TODO, use ValueTracker for this instead
|
||||
self.rotation_mobject = VectorizedPoint()
|
||||
## moving_center lives in the x-y-z space
|
||||
## It representes the center of rotation
|
||||
|
@ -167,128 +167,3 @@ class ThreeDCamera(CameraWithPerspective):
|
|||
self.space_center = self.moving_center.points[0]
|
||||
|
||||
return Camera.points_to_pixel_coords(self, new_points)
|
||||
|
||||
class ThreeDScene(Scene):
|
||||
CONFIG = {
|
||||
"camera_class" : ThreeDCamera,
|
||||
"ambient_camera_rotation" : None,
|
||||
}
|
||||
|
||||
def set_camera_position(self, phi = None, theta = None, distance = None,
|
||||
center_x = None, center_y = None, center_z = None):
|
||||
self.camera.set_position(phi, theta, distance, center_x, center_y, center_z)
|
||||
|
||||
def begin_ambient_camera_rotation(self, rate = 0.01):
|
||||
self.ambient_camera_rotation = AmbientMovement(
|
||||
self.camera.rotation_mobject,
|
||||
direction = UP,
|
||||
rate = rate
|
||||
)
|
||||
self.add(self.ambient_camera_rotation)
|
||||
|
||||
def stop_ambient_camera_rotation(self):
|
||||
if self.ambient_camera_rotation is not None:
|
||||
self.remove(self.ambient_camera_rotation)
|
||||
self.ambient_camera_rotation = None
|
||||
|
||||
def move_camera(
|
||||
self,
|
||||
phi = None, theta = None, distance = None,
|
||||
center_x = None, center_y = None, center_z = None,
|
||||
added_anims = [],
|
||||
**kwargs
|
||||
):
|
||||
target_point = self.camera.get_spherical_coords(phi, theta, distance)
|
||||
movement = ApplyMethod(
|
||||
self.camera.rotation_mobject.move_to,
|
||||
target_point,
|
||||
**kwargs
|
||||
)
|
||||
target_center = self.camera.get_center_of_rotation(center_x, center_y, center_z)
|
||||
movement_center = ApplyMethod(
|
||||
self.camera.moving_center.move_to,
|
||||
target_center,
|
||||
**kwargs
|
||||
)
|
||||
is_camera_rotating = self.ambient_camera_rotation in self.continual_animations
|
||||
if is_camera_rotating:
|
||||
self.remove(self.ambient_camera_rotation)
|
||||
self.play(movement, movement_center, *added_anims)
|
||||
target_point = self.camera.get_spherical_coords(phi, theta, distance)
|
||||
if is_camera_rotating:
|
||||
self.add(self.ambient_camera_rotation)
|
||||
|
||||
def get_moving_mobjects(self, *animations):
|
||||
moving_mobjects = Scene.get_moving_mobjects(self, *animations)
|
||||
if self.camera.rotation_mobject in moving_mobjects:
|
||||
return list_update(self.mobjects, moving_mobjects)
|
||||
return moving_mobjects
|
||||
|
||||
##############
|
||||
|
||||
def should_shade_in_3d(mobject):
|
||||
return hasattr(mobject, "shade_in_3d") and mobject.shade_in_3d
|
||||
|
||||
def shade_in_3d(mobject):
|
||||
for submob in mobject.submobject_family():
|
||||
submob.shade_in_3d = True
|
||||
|
||||
def turn_off_3d_shading(mobject):
|
||||
for submob in mobject.submobject_family():
|
||||
submob.shade_in_3d = False
|
||||
|
||||
class ThreeDMobject(VMobject):
|
||||
def __init__(self, *args, **kwargs):
|
||||
VMobject.__init__(self, *args, **kwargs)
|
||||
shade_in_3d(self)
|
||||
|
||||
class Cube(ThreeDMobject):
|
||||
CONFIG = {
|
||||
"fill_opacity" : 0.75,
|
||||
"fill_color" : BLUE,
|
||||
"stroke_width" : 0,
|
||||
"propagate_style_to_family" : True,
|
||||
"side_length" : 2,
|
||||
}
|
||||
def generate_points(self):
|
||||
for vect in IN, OUT, LEFT, RIGHT, UP, DOWN:
|
||||
face = Square(side_length = self.side_length)
|
||||
face.shift(self.side_length*OUT/2.0)
|
||||
face.apply_function(lambda p : np.dot(p, z_to_vector(vect).T))
|
||||
|
||||
self.add(face)
|
||||
|
||||
class Prism(Cube):
|
||||
CONFIG = {
|
||||
"dimensions" : [3, 2, 1]
|
||||
}
|
||||
def generate_points(self):
|
||||
Cube.generate_points(self)
|
||||
for dim, value in enumerate(self.dimensions):
|
||||
self.rescale_to_fit(value, dim, stretch = True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
0
continual_animation/__init__.py
Normal file
0
continual_animation/__init__.py
Normal file
|
@ -1,9 +1,10 @@
|
|||
from constants import *
|
||||
from mobject.mobject import Mobject, Group
|
||||
from simple_animations import MaintainPositionRelativeTo
|
||||
import copy
|
||||
from utils.config_ops import instantiate
|
||||
|
||||
from constants import *
|
||||
from mobject.mobject import Group
|
||||
from mobject.mobject import Mobject
|
||||
from utils.config_ops import digest_config
|
||||
from utils.config_ops import instantiate
|
||||
|
||||
class ContinualAnimation(object):
|
||||
CONFIG = {
|
||||
|
@ -66,7 +67,7 @@ class ContinualAnimationGroup(ContinualAnimation):
|
|||
for continual_animation in self.continual_animations:
|
||||
continual_animation.update(dt)
|
||||
|
||||
class AmbientRotation(ContinualAnimation):
|
||||
class ContinualRotation(ContinualAnimation):
|
||||
CONFIG = {
|
||||
"axis" : OUT,
|
||||
"rate" : np.pi/12, #Radians per second
|
||||
|
@ -86,7 +87,7 @@ class AmbientRotation(ContinualAnimation):
|
|||
about_point = about_point
|
||||
)
|
||||
|
||||
class AmbientMovement(ContinualAnimation):
|
||||
class ContinualMovement(ContinualAnimation):
|
||||
CONFIG = {
|
||||
"direction" : RIGHT,
|
||||
"rate" : 0.05, #Units per second
|
||||
|
@ -95,59 +96,6 @@ class AmbientMovement(ContinualAnimation):
|
|||
def update_mobject(self, dt):
|
||||
self.mobject.shift(dt*self.rate*self.direction)
|
||||
|
||||
class ContinualUpdateFromFunc(ContinualAnimation):
|
||||
CONFIG = {
|
||||
"function_depends_on_dt" : False
|
||||
}
|
||||
def __init__(self, mobject, func, **kwargs):
|
||||
self.func = func
|
||||
ContinualAnimation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
if self.function_depends_on_dt:
|
||||
self.func(self.mobject, dt)
|
||||
else:
|
||||
self.func(self.mobject)
|
||||
|
||||
class ContinualUpdateFromTimeFunc(ContinualUpdateFromFunc):
|
||||
CONFIG = {
|
||||
"function_depends_on_dt" : True
|
||||
}
|
||||
|
||||
class ContinualMaintainPositionRelativeTo(ContinualAnimation):
|
||||
# TODO: Possibly reimplement using CycleAnimation?
|
||||
def __init__(self, mobject, tracked_mobject, **kwargs):
|
||||
self.anim = MaintainPositionRelativeTo(mobject, tracked_mobject, **kwargs)
|
||||
ContinualAnimation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
self.anim.update(0) # 0 is arbitrary
|
||||
|
||||
class NormalAnimationAsContinualAnimation(ContinualAnimation):
|
||||
CONFIG = {
|
||||
"start_up_time" : 0,
|
||||
"wind_down_time" : 0,
|
||||
}
|
||||
def __init__(self, animation, **kwargs):
|
||||
self.animation = animation
|
||||
ContinualAnimation.__init__(self, animation.mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
self.animation.update(
|
||||
min(float(self.internal_time)/self.animation.run_time, 1)
|
||||
)
|
||||
|
||||
class CycleAnimation(ContinualAnimation):
|
||||
def __init__(self, animation, **kwargs):
|
||||
self.animation = animation
|
||||
ContinualAnimation.__init__(self, animation.mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
mod_value = self.internal_time % self.animation.run_time
|
||||
alpha = mod_value/float(self.animation.run_time)
|
||||
self.animation.update(alpha)
|
||||
|
||||
|
||||
|
||||
|
||||
|
28
continual_animation/from_animation.py
Normal file
28
continual_animation/from_animation.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from continual_animation.continual_animation import ContinualAnimation
|
||||
|
||||
class NormalAnimationAsContinualAnimation(ContinualAnimation):
|
||||
CONFIG = {
|
||||
"start_up_time" : 0,
|
||||
"wind_down_time" : 0,
|
||||
}
|
||||
def __init__(self, animation, **kwargs):
|
||||
self.animation = animation
|
||||
ContinualAnimation.__init__(self, animation.mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
self.animation.update(
|
||||
min(float(self.internal_time)/self.animation.run_time, 1)
|
||||
)
|
||||
|
||||
class CycleAnimation(ContinualAnimation):
|
||||
def __init__(self, animation, **kwargs):
|
||||
self.animation = animation
|
||||
ContinualAnimation.__init__(self, animation.mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
mod_value = self.internal_time % self.animation.run_time
|
||||
alpha = mod_value/float(self.animation.run_time)
|
||||
self.animation.update(alpha)
|
||||
|
10
continual_animation/numbers.py
Normal file
10
continual_animation/numbers.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from continual_animation.from_animation import NormalAnimationAsContinualAnimation
|
||||
from animation.numbers import ChangingDecimal
|
||||
|
||||
class ContinualChangingDecimal(NormalAnimationAsContinualAnimation):
|
||||
def __init__(self, *args, **kwargs):
|
||||
NormalAnimationAsContinualAnimation.__init__(
|
||||
self, ChangingDecimal(*args, **kwargs)
|
||||
)
|
33
continual_animation/update.py
Normal file
33
continual_animation/update.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from continual_animation.continual_animation import ContinualAnimation
|
||||
from animation.update import MaintainPositionRelativeTo
|
||||
|
||||
|
||||
class ContinualUpdateFromFunc(ContinualAnimation):
|
||||
CONFIG = {
|
||||
"function_depends_on_dt" : False
|
||||
}
|
||||
def __init__(self, mobject, func, **kwargs):
|
||||
self.func = func
|
||||
ContinualAnimation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
if self.function_depends_on_dt:
|
||||
self.func(self.mobject, dt)
|
||||
else:
|
||||
self.func(self.mobject)
|
||||
|
||||
class ContinualUpdateFromTimeFunc(ContinualUpdateFromFunc):
|
||||
CONFIG = {
|
||||
"function_depends_on_dt" : True
|
||||
}
|
||||
|
||||
class ContinualMaintainPositionRelativeTo(ContinualAnimation):
|
||||
# TODO: Possibly reimplement using CycleAnimation?
|
||||
def __init__(self, mobject, tracked_mobject, **kwargs):
|
||||
self.anim = MaintainPositionRelativeTo(mobject, tracked_mobject, **kwargs)
|
||||
ContinualAnimation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
self.anim.update(0) # 0 is arbitrary
|
|
@ -4,17 +4,18 @@ import sys
|
|||
# import getopt
|
||||
import argparse
|
||||
import imp
|
||||
import itertools as it
|
||||
import inspect
|
||||
import traceback
|
||||
import imp
|
||||
import inspect
|
||||
import itertools as it
|
||||
import os
|
||||
import subprocess as sp
|
||||
import traceback
|
||||
|
||||
from camera.camera import Camera
|
||||
from constants import *
|
||||
from scene.scene import Scene
|
||||
from camera.camera import Camera
|
||||
from utils.sounds import play_error_sound, play_finish_sound
|
||||
from utils.sounds import play_error_sound
|
||||
from utils.sounds import play_finish_sound
|
||||
|
||||
HELP_MESSAGE = """
|
||||
Usage:
|
||||
|
@ -215,7 +216,6 @@ def get_module(file_name):
|
|||
return get_module_windows(file_name)
|
||||
return get_module_posix(file_name)
|
||||
|
||||
|
||||
def main():
|
||||
config = get_configuration()
|
||||
module = get_module(config["file"])
|
||||
|
|
0
for_3b1b_videos/__init__.py
Normal file
0
for_3b1b_videos/__init__.py
Normal file
|
@ -1,17 +1,27 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import random
|
||||
|
||||
from constants import *
|
||||
|
||||
from scene.scene import Scene
|
||||
from animation.animation import Animation
|
||||
from animation.simple_animations import Write, DrawBorderThenFill
|
||||
from animation.compositions import LaggedStart
|
||||
from animation.transform import FadeIn, FadeOut, ApplyMethod
|
||||
from mobject.vectorized_mobject import VGroup
|
||||
from mobject.tex_mobject import TexMobject, TextMobject
|
||||
from topics.characters import Mortimer, Randolph, Blink
|
||||
from topics.objects import PatreonLogo
|
||||
from topics.geometry import Square, Rectangle, DashedLine
|
||||
|
||||
from animation.composition import LaggedStart
|
||||
from animation.creation import DrawBorderThenFill
|
||||
from animation.creation import Write
|
||||
from animation.transform import ApplyMethod
|
||||
from animation.creation import FadeIn
|
||||
from animation.creation import FadeOut
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.svg.tex_mobject import TextMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from scene.scene import Scene
|
||||
from for_3b1b_videos.pi_creature_animations import Blink
|
||||
from for_3b1b_videos.pi_creature import Mortimer
|
||||
from for_3b1b_videos.pi_creature import Randolph
|
||||
from mobject.geometry import DashedLine
|
||||
from mobject.geometry import Rectangle
|
||||
from mobject.geometry import Square
|
||||
from mobject.svg.drawings import PatreonLogo
|
||||
|
||||
class OpeningQuote(Scene):
|
||||
CONFIG = {
|
||||
|
@ -202,17 +212,3 @@ class TODOStub(Scene):
|
|||
self.add(TextMobject("TODO: %s"%self.message))
|
||||
self.wait()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
316
for_3b1b_videos/pi_creature.py
Normal file
316
for_3b1b_videos/pi_creature.py
Normal file
|
@ -0,0 +1,316 @@
|
|||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.svg.svg_mobject import SVGMobject
|
||||
from mobject.svg.tex_mobject import TextMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
|
||||
from mobject.svg.drawings import ThoughtBubble
|
||||
|
||||
from animation.transform import Transform
|
||||
from utils.config_ops import digest_config
|
||||
from utils.rate_functions import squish_rate_func
|
||||
from utils.rate_functions import there_and_back
|
||||
|
||||
PI_CREATURE_DIR = os.path.join(MEDIA_DIR, "designs", "PiCreature")
|
||||
PI_CREATURE_SCALE_FACTOR = 0.5
|
||||
|
||||
LEFT_EYE_INDEX = 0
|
||||
RIGHT_EYE_INDEX = 1
|
||||
LEFT_PUPIL_INDEX = 2
|
||||
RIGHT_PUPIL_INDEX = 3
|
||||
BODY_INDEX = 4
|
||||
MOUTH_INDEX = 5
|
||||
|
||||
class PiCreature(SVGMobject):
|
||||
CONFIG = {
|
||||
"color" : BLUE_E,
|
||||
"file_name_prefix" : "PiCreatures",
|
||||
"stroke_width" : 0,
|
||||
"stroke_color" : BLACK,
|
||||
"fill_opacity" : 1.0,
|
||||
"propagate_style_to_family" : True,
|
||||
"height" : 3,
|
||||
"corner_scale_factor" : 0.75,
|
||||
"flip_at_start" : False,
|
||||
"is_looking_direction_purposeful" : False,
|
||||
"start_corner" : None,
|
||||
#Range of proportions along body where arms are
|
||||
"right_arm_range" : [0.55, 0.7],
|
||||
"left_arm_range" : [.34, .462],
|
||||
}
|
||||
def __init__(self, mode = "plain", **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.parts_named = False
|
||||
try:
|
||||
svg_file = os.path.join(
|
||||
PI_CREATURE_DIR,
|
||||
"%s_%s.svg"%(self.file_name_prefix, mode)
|
||||
)
|
||||
SVGMobject.__init__(self, file_name = svg_file, **kwargs)
|
||||
except:
|
||||
warnings.warn("No %s design with mode %s"%(self.file_name_prefix, mode))
|
||||
svg_file = os.path.join(
|
||||
FILE_DIR,
|
||||
"PiCreatures_plain.svg",
|
||||
)
|
||||
SVGMobject.__init__(self, file_name = svg_file, **kwargs)
|
||||
|
||||
if self.flip_at_start:
|
||||
self.flip()
|
||||
if self.start_corner is not None:
|
||||
self.to_corner(self.start_corner)
|
||||
|
||||
def name_parts(self):
|
||||
self.mouth = self.submobjects[MOUTH_INDEX]
|
||||
self.body = self.submobjects[BODY_INDEX]
|
||||
self.pupils = VGroup(*[
|
||||
self.submobjects[LEFT_PUPIL_INDEX],
|
||||
self.submobjects[RIGHT_PUPIL_INDEX]
|
||||
])
|
||||
self.eyes = VGroup(*[
|
||||
self.submobjects[LEFT_EYE_INDEX],
|
||||
self.submobjects[RIGHT_EYE_INDEX]
|
||||
])
|
||||
self.eye_parts = VGroup(self.eyes, self.pupils)
|
||||
self.parts_named = True
|
||||
|
||||
def init_colors(self):
|
||||
SVGMobject.init_colors(self)
|
||||
if not self.parts_named:
|
||||
self.name_parts()
|
||||
self.mouth.set_fill(BLACK, opacity = 1)
|
||||
self.body.set_fill(self.color, opacity = 1)
|
||||
self.pupils.set_fill(BLACK, opacity = 1)
|
||||
self.eyes.set_fill(WHITE, opacity = 1)
|
||||
return self
|
||||
|
||||
def copy(self):
|
||||
copy_mobject = SVGMobject.copy(self)
|
||||
copy_mobject.name_parts()
|
||||
return copy_mobject
|
||||
|
||||
def set_color(self, color):
|
||||
self.body.set_fill(color)
|
||||
return self
|
||||
|
||||
def change_mode(self, mode):
|
||||
new_self = self.__class__(
|
||||
mode = mode,
|
||||
color = self.color
|
||||
)
|
||||
new_self.scale_to_fit_height(self.get_height())
|
||||
if self.is_flipped() ^ new_self.is_flipped():
|
||||
new_self.flip()
|
||||
new_self.shift(self.eyes.get_center() - new_self.eyes.get_center())
|
||||
if hasattr(self, "purposeful_looking_direction"):
|
||||
new_self.look(self.purposeful_looking_direction)
|
||||
Transform(self, new_self).update(1)
|
||||
return self
|
||||
|
||||
def look(self, direction):
|
||||
norm = np.linalg.norm(direction)
|
||||
if norm == 0:
|
||||
return
|
||||
direction /= norm
|
||||
self.purposeful_looking_direction = direction
|
||||
for pupil, eye in zip(self.pupils.split(), self.eyes.split()):
|
||||
pupil_radius = pupil.get_width()/2.
|
||||
eye_radius = eye.get_width()/2.
|
||||
pupil.move_to(eye)
|
||||
if direction[1] < 0:
|
||||
pupil.shift(pupil_radius*DOWN/3)
|
||||
pupil.shift(direction*(eye_radius-pupil_radius))
|
||||
bottom_diff = eye.get_bottom()[1] - pupil.get_bottom()[1]
|
||||
if bottom_diff > 0:
|
||||
pupil.shift(bottom_diff*UP)
|
||||
#TODO, how to handle looking up...
|
||||
# top_diff = eye.get_top()[1]-pupil.get_top()[1]
|
||||
# if top_diff < 0:
|
||||
# pupil.shift(top_diff*UP)
|
||||
return self
|
||||
|
||||
def look_at(self, point_or_mobject):
|
||||
if isinstance(point_or_mobject, Mobject):
|
||||
point = point_or_mobject.get_center()
|
||||
else:
|
||||
point = point_or_mobject
|
||||
self.look(point - self.eyes.get_center())
|
||||
return self
|
||||
|
||||
def change(self, new_mode, look_at_arg = None):
|
||||
self.change_mode(new_mode)
|
||||
if look_at_arg is not None:
|
||||
self.look_at(look_at_arg)
|
||||
return self
|
||||
|
||||
def get_looking_direction(self):
|
||||
return np.sign(np.round(
|
||||
self.pupils.get_center() - self.eyes.get_center(),
|
||||
decimals = 2
|
||||
))
|
||||
|
||||
def is_flipped(self):
|
||||
return self.eyes.submobjects[0].get_center()[0] > \
|
||||
self.eyes.submobjects[1].get_center()[0]
|
||||
|
||||
def blink(self):
|
||||
eye_parts = self.eye_parts
|
||||
eye_bottom_y = eye_parts.get_bottom()[1]
|
||||
eye_parts.apply_function(
|
||||
lambda p : [p[0], eye_bottom_y, p[2]]
|
||||
)
|
||||
return self
|
||||
|
||||
def to_corner(self, vect = None, **kwargs):
|
||||
if vect is not None:
|
||||
SVGMobject.to_corner(self, vect, **kwargs)
|
||||
else:
|
||||
self.scale(self.corner_scale_factor)
|
||||
self.to_corner(DOWN+LEFT, **kwargs)
|
||||
return self
|
||||
|
||||
def get_bubble(self, *content, **kwargs):
|
||||
bubble_class = kwargs.get("bubble_class", ThoughtBubble)
|
||||
bubble = bubble_class(**kwargs)
|
||||
if len(content) > 0:
|
||||
if isinstance(content[0], str):
|
||||
content_mob = TextMobject(*content)
|
||||
else:
|
||||
content_mob = content[0]
|
||||
bubble.add_content(content_mob)
|
||||
if "height" not in kwargs and "width" not in kwargs:
|
||||
bubble.resize_to_content()
|
||||
bubble.pin_to(self)
|
||||
self.bubble = bubble
|
||||
return bubble
|
||||
|
||||
def make_eye_contact(self, pi_creature):
|
||||
self.look_at(pi_creature.eyes)
|
||||
pi_creature.look_at(self.eyes)
|
||||
return self
|
||||
|
||||
def shrug(self):
|
||||
self.change_mode("shruggie")
|
||||
top_mouth_point, bottom_mouth_point = [
|
||||
self.mouth.points[np.argmax(self.mouth.points[:,1])],
|
||||
self.mouth.points[np.argmin(self.mouth.points[:,1])]
|
||||
]
|
||||
self.look(top_mouth_point - bottom_mouth_point)
|
||||
return self
|
||||
|
||||
def get_arm_copies(self):
|
||||
body = self.body
|
||||
return VGroup(*[
|
||||
body.copy().pointwise_become_partial(body, *alpha_range)
|
||||
for alpha_range in self.right_arm_range, self.left_arm_range
|
||||
])
|
||||
|
||||
def get_all_pi_creature_modes():
|
||||
result = []
|
||||
prefix = "%s_"%PiCreature.CONFIG["file_name_prefix"]
|
||||
suffix = ".svg"
|
||||
for file in os.listdir(PI_CREATURE_DIR):
|
||||
if file.startswith(prefix) and file.endswith(suffix):
|
||||
result.append(
|
||||
file[len(prefix):-len(suffix)]
|
||||
)
|
||||
return result
|
||||
|
||||
class Randolph(PiCreature):
|
||||
pass #Nothing more than an alternative name
|
||||
|
||||
class Mortimer(PiCreature):
|
||||
CONFIG = {
|
||||
"color" : GREY_BROWN,
|
||||
"flip_at_start" : True,
|
||||
}
|
||||
|
||||
class Mathematician(PiCreature):
|
||||
CONFIG = {
|
||||
"color" : GREY,
|
||||
}
|
||||
|
||||
class BabyPiCreature(PiCreature):
|
||||
CONFIG = {
|
||||
"scale_factor" : 0.5,
|
||||
"eye_scale_factor" : 1.2,
|
||||
"pupil_scale_factor" : 1.3
|
||||
}
|
||||
def __init__(self, *args, **kwargs):
|
||||
PiCreature.__init__(self, *args, **kwargs)
|
||||
self.scale(self.scale_factor)
|
||||
self.shift(LEFT)
|
||||
self.to_edge(DOWN, buff = LARGE_BUFF)
|
||||
eyes = VGroup(self.eyes, self.pupils)
|
||||
eyes_bottom = eyes.get_bottom()
|
||||
eyes.scale(self.eye_scale_factor)
|
||||
eyes.move_to(eyes_bottom, aligned_edge = DOWN)
|
||||
looking_direction = self.get_looking_direction()
|
||||
for pupil in self.pupils:
|
||||
pupil.scale_in_place(self.pupil_scale_factor)
|
||||
self.look(looking_direction)
|
||||
|
||||
class TauCreature(PiCreature):
|
||||
CONFIG = {
|
||||
"file_name_prefix" : "TauCreatures"
|
||||
}
|
||||
|
||||
class ThreeLeggedPiCreature(PiCreature):
|
||||
CONFIG = {
|
||||
"file_name_prefix" : "ThreeLeggedPiCreatures"
|
||||
}
|
||||
|
||||
class Eyes(VMobject):
|
||||
CONFIG = {
|
||||
"height" : 0.3,
|
||||
"thing_looked_at" : None,
|
||||
"mode" : "plain",
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
VMobject.__init__(self, **kwargs)
|
||||
self.mobject = mobject
|
||||
self.submobjects = self.get_eyes().submobjects
|
||||
|
||||
def get_eyes(self, mode = None, thing_to_look_at = None):
|
||||
mode = mode or self.mode
|
||||
if thing_to_look_at is None:
|
||||
thing_to_look_at = self.thing_looked_at
|
||||
|
||||
pi = Randolph(mode = mode)
|
||||
eyes = VGroup(pi.eyes, pi.pupils)
|
||||
pi.scale(self.height/eyes.get_height())
|
||||
if self.submobjects:
|
||||
eyes.move_to(self, DOWN)
|
||||
else:
|
||||
eyes.move_to(self.mobject.get_top(), DOWN)
|
||||
if thing_to_look_at is not None:
|
||||
pi.look_at(thing_to_look_at)
|
||||
return eyes
|
||||
|
||||
def change_mode_anim(self, mode, **kwargs):
|
||||
self.mode = mode
|
||||
return Transform(self, self.get_eyes(mode = mode), **kwargs)
|
||||
|
||||
def look_at_anim(self, point_or_mobject, **kwargs):
|
||||
self.thing_looked_at = point_or_mobject
|
||||
return Transform(
|
||||
self, self.get_eyes(thing_to_look_at = point_or_mobject),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def blink_anim(self, **kwargs):
|
||||
target = self.copy()
|
||||
bottom_y = self.get_bottom()[1]
|
||||
for submob in target:
|
||||
submob.apply_function(
|
||||
lambda p : [p[0], bottom_y, p[2]]
|
||||
)
|
||||
if "rate_func" not in kwargs:
|
||||
kwargs["rate_func"] = squish_rate_func(there_and_back)
|
||||
return Transform(self, target, **kwargs)
|
||||
|
||||
|
96
for_3b1b_videos/pi_creature_animations.py
Normal file
96
for_3b1b_videos/pi_creature_animations.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.mobject import Group
|
||||
|
||||
from mobject.svg.drawings import SpeechBubble
|
||||
|
||||
from animation.creation import ShowCreation
|
||||
from animation.creation import Write
|
||||
from animation.composition import AnimationGroup
|
||||
from animation.transform import ApplyMethod
|
||||
from animation.creation import FadeOut
|
||||
from animation.transform import MoveToTarget
|
||||
from utils.config_ops import digest_config
|
||||
from utils.rate_functions import squish_rate_func
|
||||
from utils.rate_functions import there_and_back
|
||||
|
||||
class Blink(ApplyMethod):
|
||||
CONFIG = {
|
||||
"rate_func" : squish_rate_func(there_and_back)
|
||||
}
|
||||
def __init__(self, pi_creature, **kwargs):
|
||||
ApplyMethod.__init__(self, pi_creature.blink, **kwargs)
|
||||
|
||||
class PiCreatureBubbleIntroduction(AnimationGroup):
|
||||
CONFIG = {
|
||||
"target_mode" : "speaking",
|
||||
"bubble_class" : SpeechBubble,
|
||||
"change_mode_kwargs" : {},
|
||||
"bubble_creation_class" : ShowCreation,
|
||||
"bubble_creation_kwargs" : {},
|
||||
"bubble_kwargs" : {},
|
||||
"content_introduction_class" : Write,
|
||||
"content_introduction_kwargs" : {},
|
||||
"look_at_arg" : None,
|
||||
}
|
||||
def __init__(self, pi_creature, *content, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
bubble = pi_creature.get_bubble(
|
||||
*content,
|
||||
bubble_class = self.bubble_class,
|
||||
**self.bubble_kwargs
|
||||
)
|
||||
Group(bubble, bubble.content).shift_onto_screen()
|
||||
|
||||
pi_creature.generate_target()
|
||||
pi_creature.target.change_mode(self.target_mode)
|
||||
if self.look_at_arg is not None:
|
||||
pi_creature.target.look_at(self.look_at_arg)
|
||||
|
||||
change_mode = MoveToTarget(pi_creature, **self.change_mode_kwargs)
|
||||
bubble_creation = self.bubble_creation_class(
|
||||
bubble, **self.bubble_creation_kwargs
|
||||
)
|
||||
content_introduction = self.content_introduction_class(
|
||||
bubble.content, **self.content_introduction_kwargs
|
||||
)
|
||||
AnimationGroup.__init__(
|
||||
self, change_mode, bubble_creation, content_introduction,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
class PiCreatureSays(PiCreatureBubbleIntroduction):
|
||||
CONFIG = {
|
||||
"target_mode" : "speaking",
|
||||
"bubble_class" : SpeechBubble,
|
||||
}
|
||||
|
||||
class RemovePiCreatureBubble(AnimationGroup):
|
||||
CONFIG = {
|
||||
"target_mode" : "plain",
|
||||
"look_at_arg" : None,
|
||||
"remover" : True,
|
||||
}
|
||||
def __init__(self, pi_creature, **kwargs):
|
||||
assert hasattr(pi_creature, "bubble")
|
||||
digest_config(self, kwargs, locals())
|
||||
|
||||
pi_creature.generate_target()
|
||||
pi_creature.target.change_mode(self.target_mode)
|
||||
if self.look_at_arg is not None:
|
||||
pi_creature.target.look_at(self.look_at_arg)
|
||||
|
||||
AnimationGroup.__init__(
|
||||
self,
|
||||
MoveToTarget(pi_creature),
|
||||
FadeOut(pi_creature.bubble),
|
||||
FadeOut(pi_creature.bubble.content),
|
||||
)
|
||||
|
||||
def clean_up(self, surrounding_scene = None):
|
||||
AnimationGroup.clean_up(self, surrounding_scene)
|
||||
self.pi_creature.bubble = None
|
||||
if surrounding_scene is not None:
|
||||
surrounding_scene.add(self.pi_creature)
|
362
for_3b1b_videos/pi_creature_scene.py
Normal file
362
for_3b1b_videos/pi_creature_scene.py
Normal file
|
@ -0,0 +1,362 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
import random
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
|
||||
from mobject.frame import ScreenRectangle
|
||||
from mobject.svg.drawings import SpeechBubble
|
||||
from mobject.svg.drawings import ThoughtBubble
|
||||
|
||||
from animation.transform import ApplyMethod
|
||||
from animation.transform import ReplacementTransform
|
||||
from animation.transform import Transform
|
||||
from for_3b1b_videos.pi_creature import PiCreature
|
||||
from for_3b1b_videos.pi_creature import Mortimer
|
||||
from for_3b1b_videos.pi_creature import Randolph
|
||||
from for_3b1b_videos.pi_creature_animations import Blink
|
||||
from for_3b1b_videos.pi_creature_animations import PiCreatureBubbleIntroduction
|
||||
from for_3b1b_videos.pi_creature_animations import RemovePiCreatureBubble
|
||||
from scene.scene import Scene
|
||||
from utils.rate_functions import squish_rate_func
|
||||
from utils.rate_functions import there_and_back
|
||||
|
||||
class PiCreatureScene(Scene):
|
||||
CONFIG = {
|
||||
"total_wait_time" : 0,
|
||||
"seconds_to_blink" : 3,
|
||||
"pi_creatures_start_on_screen" : True,
|
||||
"default_pi_creature_kwargs" : {
|
||||
"color" : GREY_BROWN,
|
||||
"flip_at_start" : True,
|
||||
},
|
||||
"default_pi_creature_start_corner" : DOWN+LEFT,
|
||||
}
|
||||
def setup(self):
|
||||
self.pi_creatures = self.create_pi_creatures()
|
||||
self.pi_creature = self.get_primary_pi_creature()
|
||||
if self.pi_creatures_start_on_screen:
|
||||
self.add(*self.pi_creatures)
|
||||
|
||||
def create_pi_creatures(self):
|
||||
"""
|
||||
Likely updated for subclasses
|
||||
"""
|
||||
return VGroup(self.create_pi_creature())
|
||||
|
||||
def create_pi_creature(self):
|
||||
pi_creature = PiCreature(**self.default_pi_creature_kwargs)
|
||||
pi_creature.to_corner(self.default_pi_creature_start_corner)
|
||||
return pi_creature
|
||||
|
||||
def get_pi_creatures(self):
|
||||
return self.pi_creatures
|
||||
|
||||
def get_primary_pi_creature(self):
|
||||
return self.pi_creatures[0]
|
||||
|
||||
def any_pi_creatures_on_screen(self):
|
||||
mobjects = self.get_mobjects()
|
||||
return any([pi in mobjects for pi in self.get_pi_creatures()])
|
||||
|
||||
def get_on_screen_pi_creatures(self):
|
||||
mobjects = self.get_mobjects()
|
||||
return VGroup(*filter(
|
||||
lambda pi : pi in mobjects,
|
||||
self.get_pi_creatures()
|
||||
))
|
||||
|
||||
def introduce_bubble(self, *args, **kwargs):
|
||||
if isinstance(args[0], PiCreature):
|
||||
pi_creature = args[0]
|
||||
content = args[1:]
|
||||
else:
|
||||
pi_creature = self.get_primary_pi_creature()
|
||||
content = args
|
||||
|
||||
bubble_class = kwargs.pop("bubble_class", SpeechBubble)
|
||||
target_mode = kwargs.pop(
|
||||
"target_mode",
|
||||
"thinking" if bubble_class is ThoughtBubble else "speaking"
|
||||
)
|
||||
bubble_kwargs = kwargs.pop("bubble_kwargs", {})
|
||||
bubble_removal_kwargs = kwargs.pop("bubble_removal_kwargs", {})
|
||||
added_anims = kwargs.pop("added_anims", [])
|
||||
|
||||
anims = []
|
||||
on_screen_mobjects = self.camera.extract_mobject_family_members(
|
||||
self.get_mobjects()
|
||||
)
|
||||
def has_bubble(pi):
|
||||
return hasattr(pi, "bubble") and \
|
||||
pi.bubble is not None and \
|
||||
pi.bubble in on_screen_mobjects
|
||||
|
||||
pi_creatures_with_bubbles = filter(has_bubble, self.get_pi_creatures())
|
||||
if pi_creature in pi_creatures_with_bubbles:
|
||||
pi_creatures_with_bubbles.remove(pi_creature)
|
||||
old_bubble = pi_creature.bubble
|
||||
bubble = pi_creature.get_bubble(
|
||||
*content,
|
||||
bubble_class = bubble_class,
|
||||
**bubble_kwargs
|
||||
)
|
||||
anims += [
|
||||
ReplacementTransform(old_bubble, bubble),
|
||||
ReplacementTransform(old_bubble.content, bubble.content),
|
||||
pi_creature.change_mode, target_mode
|
||||
]
|
||||
else:
|
||||
anims.append(PiCreatureBubbleIntroduction(
|
||||
pi_creature,
|
||||
*content,
|
||||
bubble_class = bubble_class,
|
||||
bubble_kwargs = bubble_kwargs,
|
||||
target_mode = target_mode,
|
||||
**kwargs
|
||||
))
|
||||
anims += [
|
||||
RemovePiCreatureBubble(pi, **bubble_removal_kwargs)
|
||||
for pi in pi_creatures_with_bubbles
|
||||
]
|
||||
anims += added_anims
|
||||
|
||||
self.play(*anims, **kwargs)
|
||||
|
||||
def pi_creature_says(self, *args, **kwargs):
|
||||
self.introduce_bubble(
|
||||
*args,
|
||||
bubble_class = SpeechBubble,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def pi_creature_thinks(self, *args, **kwargs):
|
||||
self.introduce_bubble(
|
||||
*args,
|
||||
bubble_class = ThoughtBubble,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def say(self, *content, **kwargs):
|
||||
self.pi_creature_says(self.get_primary_pi_creature(), *content, **kwargs)
|
||||
|
||||
def think(self, *content, **kwargs):
|
||||
self.pi_creature_thinks(self.get_primary_pi_creature(), *content, **kwargs)
|
||||
|
||||
def compile_play_args_to_animation_list(self, *args):
|
||||
"""
|
||||
Add animations so that all pi creatures look at the
|
||||
first mobject being animated with each .play call
|
||||
"""
|
||||
animations = Scene.compile_play_args_to_animation_list(self, *args)
|
||||
if not self.any_pi_creatures_on_screen():
|
||||
return animations
|
||||
|
||||
non_pi_creature_anims = filter(
|
||||
lambda anim : anim.mobject not in self.get_pi_creatures(),
|
||||
animations
|
||||
)
|
||||
if len(non_pi_creature_anims) == 0:
|
||||
return animations
|
||||
first_anim = non_pi_creature_anims[0]
|
||||
#Look at ending state
|
||||
first_anim.update(1)
|
||||
point_of_interest = first_anim.mobject.get_center()
|
||||
first_anim.update(0)
|
||||
|
||||
for pi_creature in self.get_pi_creatures():
|
||||
if pi_creature not in self.get_mobjects():
|
||||
continue
|
||||
if pi_creature in first_anim.mobject.submobject_family():
|
||||
continue
|
||||
anims_with_pi_creature = filter(
|
||||
lambda anim : pi_creature in anim.mobject.submobject_family(),
|
||||
animations
|
||||
)
|
||||
for anim in anims_with_pi_creature:
|
||||
if isinstance(anim, Transform):
|
||||
index = anim.mobject.submobject_family().index(pi_creature)
|
||||
target_family = anim.target_mobject.submobject_family()
|
||||
target = target_family[index]
|
||||
if isinstance(target, PiCreature):
|
||||
target.look_at(point_of_interest)
|
||||
if not anims_with_pi_creature:
|
||||
animations.append(
|
||||
ApplyMethod(pi_creature.look_at, point_of_interest)
|
||||
)
|
||||
return animations
|
||||
|
||||
def blink(self):
|
||||
self.play(Blink(random.choice(self.get_on_screen_pi_creatures())))
|
||||
|
||||
def joint_blink(self, pi_creatures = None, shuffle = True, **kwargs):
|
||||
if pi_creatures is None:
|
||||
pi_creatures = self.get_on_screen_pi_creatures()
|
||||
creatures_list = list(pi_creatures)
|
||||
if shuffle:
|
||||
random.shuffle(creatures_list)
|
||||
|
||||
def get_rate_func(pi):
|
||||
index = creatures_list.index(pi)
|
||||
proportion = float(index)/len(creatures_list)
|
||||
start_time = 0.8*proportion
|
||||
return squish_rate_func(
|
||||
there_and_back,
|
||||
start_time, start_time + 0.2
|
||||
)
|
||||
|
||||
self.play(*[
|
||||
Blink(pi, rate_func = get_rate_func(pi), **kwargs)
|
||||
for pi in creatures_list
|
||||
])
|
||||
return self
|
||||
|
||||
def wait(self, time = 1, blink = True):
|
||||
while time >= 1:
|
||||
time_to_blink = self.total_wait_time%self.seconds_to_blink == 0
|
||||
if blink and self.any_pi_creatures_on_screen() and time_to_blink:
|
||||
self.blink()
|
||||
self.num_plays -= 1 #This shouldn't count as an animation
|
||||
else:
|
||||
self.non_blink_wait()
|
||||
time -= 1
|
||||
self.total_wait_time += 1
|
||||
if time > 0:
|
||||
self.non_blink_wait(time)
|
||||
return self
|
||||
|
||||
def non_blink_wait(self, time = 1):
|
||||
Scene.wait(self, time)
|
||||
return self
|
||||
|
||||
def change_mode(self, mode):
|
||||
self.play(self.get_primary_pi_creature().change_mode, mode)
|
||||
|
||||
def look_at(self, thing_to_look_at, pi_creatures = None):
|
||||
if pi_creatures is None:
|
||||
pi_creatures = self.get_pi_creatures()
|
||||
self.play(*it.chain(*[
|
||||
[pi.look_at, thing_to_look_at]
|
||||
for pi in pi_creatures
|
||||
]))
|
||||
|
||||
class TeacherStudentsScene(PiCreatureScene):
|
||||
CONFIG = {
|
||||
"student_colors" : [BLUE_D, BLUE_E, BLUE_C],
|
||||
"student_scale_factor" : 0.8,
|
||||
"seconds_to_blink" : 2,
|
||||
"screen_height" : 3,
|
||||
}
|
||||
def setup(self):
|
||||
PiCreatureScene.setup(self)
|
||||
self.screen = ScreenRectangle(height = self.screen_height)
|
||||
self.screen.to_corner(UP+LEFT)
|
||||
self.hold_up_spot = self.teacher.get_corner(UP+LEFT) + MED_LARGE_BUFF*UP
|
||||
|
||||
def create_pi_creatures(self):
|
||||
self.teacher = Mortimer()
|
||||
self.teacher.to_corner(DOWN + RIGHT)
|
||||
self.teacher.look(DOWN+LEFT)
|
||||
self.students = VGroup(*[
|
||||
Randolph(color = c)
|
||||
for c in self.student_colors
|
||||
])
|
||||
self.students.arrange_submobjects(RIGHT)
|
||||
self.students.scale(self.student_scale_factor)
|
||||
self.students.to_corner(DOWN+LEFT)
|
||||
self.teacher.look_at(self.students[-1].eyes)
|
||||
for student in self.students:
|
||||
student.look_at(self.teacher.eyes)
|
||||
|
||||
return [self.teacher] + list(self.students)
|
||||
|
||||
def get_teacher(self):
|
||||
return self.teacher
|
||||
|
||||
def get_students(self):
|
||||
return self.students
|
||||
|
||||
def teacher_says(self, *content, **kwargs):
|
||||
return self.pi_creature_says(
|
||||
self.get_teacher(), *content, **kwargs
|
||||
)
|
||||
|
||||
def student_says(self, *content, **kwargs):
|
||||
if "target_mode" not in kwargs:
|
||||
target_mode = random.choice([
|
||||
"raise_right_hand",
|
||||
"raise_left_hand",
|
||||
])
|
||||
kwargs["target_mode"] = target_mode
|
||||
student = self.get_students()[kwargs.get("student_index", 1)]
|
||||
return self.pi_creature_says(
|
||||
student, *content, **kwargs
|
||||
)
|
||||
|
||||
def teacher_thinks(self, *content, **kwargs):
|
||||
return self.pi_creature_thinks(
|
||||
self.get_teacher(), *content, **kwargs
|
||||
)
|
||||
|
||||
def student_thinks(self, *content, **kwargs):
|
||||
student = self.get_students()[kwargs.get("student_index", 1)]
|
||||
return self.pi_creature_thinks(student, *content, **kwargs)
|
||||
|
||||
def change_all_student_modes(self, mode, **kwargs):
|
||||
self.change_student_modes(*[mode]*len(self.students), **kwargs)
|
||||
|
||||
def change_student_modes(self, *modes, **kwargs):
|
||||
added_anims = kwargs.pop("added_anims", [])
|
||||
self.play(
|
||||
self.get_student_changes(*modes, **kwargs),
|
||||
*added_anims
|
||||
)
|
||||
|
||||
def get_student_changes(self, *modes, **kwargs):
|
||||
pairs = zip(self.get_students(), modes)
|
||||
pairs = [(s, m) for s, m in pairs if m is not None]
|
||||
start = VGroup(*[s for s, m in pairs])
|
||||
target = VGroup(*[s.copy().change_mode(m) for s, m in pairs])
|
||||
if "look_at_arg" in kwargs:
|
||||
for pi in target:
|
||||
pi.look_at(kwargs["look_at_arg"])
|
||||
submobject_mode = kwargs.get("submobject_mode", "lagged_start")
|
||||
return Transform(
|
||||
start, target,
|
||||
submobject_mode = submobject_mode,
|
||||
run_time = 2
|
||||
)
|
||||
|
||||
def zoom_in_on_thought_bubble(self, bubble = None, radius = FRAME_Y_RADIUS+FRAME_X_RADIUS):
|
||||
if bubble is None:
|
||||
for pi in self.get_pi_creatures():
|
||||
if hasattr(pi, "bubble") and isinstance(pi.bubble, ThoughtBubble):
|
||||
bubble = pi.bubble
|
||||
break
|
||||
if bubble is None:
|
||||
raise Exception("No pi creatures have a thought bubble")
|
||||
vect = -bubble.get_bubble_center()
|
||||
def func(point):
|
||||
centered = point+vect
|
||||
return radius*centered/np.linalg.norm(centered)
|
||||
self.play(*[
|
||||
ApplyPointwiseFunction(func, mob)
|
||||
for mob in self.get_mobjects()
|
||||
])
|
||||
|
||||
def teacher_holds_up(self, mobject, target_mode = "raise_right_hand", **kwargs):
|
||||
mobject.move_to(self.hold_up_spot, DOWN)
|
||||
mobject.shift_onto_screen()
|
||||
mobject_copy = mobject.copy()
|
||||
mobject_copy.shift(DOWN)
|
||||
mobject_copy.fade(1)
|
||||
self.play(
|
||||
ReplacementTransform(mobject_copy, mobject),
|
||||
self.teacher.change, target_mode,
|
||||
)
|
||||
|
||||
|
|
@ -1,155 +1,22 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.vectorized_mobject import VMobject, VGroup
|
||||
from mobject.tex_mobject import TexMobject
|
||||
from topics.geometry import Line, Arrow
|
||||
from topics.functions import ParametricFunction
|
||||
from scene.scene import Scene
|
||||
from utils.bezier import interpolate
|
||||
from mobject.functions import ParametricFunction
|
||||
from mobject.geometry import Arrow
|
||||
from mobject.geometry import Line
|
||||
from mobject.number_line import NumberLine
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from utils.config_ops import digest_config
|
||||
from utils.space_ops import R3_to_complex
|
||||
from utils.space_ops import angle_of_vector
|
||||
from utils.space_ops import complex_to_R3
|
||||
|
||||
class NumberLine(VMobject):
|
||||
CONFIG = {
|
||||
"color" : BLUE,
|
||||
"x_min" : -FRAME_X_RADIUS,
|
||||
"x_max" : FRAME_X_RADIUS,
|
||||
"unit_size" : 1,
|
||||
"tick_size" : 0.1,
|
||||
"tick_frequency" : 1,
|
||||
"leftmost_tick" : None, #Defaults to value near x_min s.t. 0 is a tick
|
||||
"numbers_with_elongated_ticks" : [0],
|
||||
"numbers_to_show" : None,
|
||||
"longer_tick_multiple" : 2,
|
||||
"number_at_center" : 0,
|
||||
"number_scale_val" : 0.75,
|
||||
"label_direction" : DOWN,
|
||||
"line_to_number_buff" : MED_SMALL_BUFF,
|
||||
"include_tip" : False,
|
||||
"propagate_style_to_family" : True,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
if self.leftmost_tick is None:
|
||||
tf = self.tick_frequency
|
||||
self.leftmost_tick = tf*np.ceil(self.x_min/tf)
|
||||
VMobject.__init__(self, **kwargs)
|
||||
if self.include_tip:
|
||||
self.add_tip()
|
||||
|
||||
def generate_points(self):
|
||||
self.main_line = Line(self.x_min*RIGHT, self.x_max*RIGHT)
|
||||
self.tick_marks = VGroup()
|
||||
self.add(self.main_line, self.tick_marks)
|
||||
rounding_value = int(-np.log10(0.1*self.tick_frequency))
|
||||
rounded_numbers_with_elongated_ticks = np.round(
|
||||
self.numbers_with_elongated_ticks,
|
||||
rounding_value
|
||||
)
|
||||
|
||||
for x in self.get_tick_numbers():
|
||||
rounded_x = np.round(x, rounding_value)
|
||||
if rounded_x in rounded_numbers_with_elongated_ticks:
|
||||
tick_size_used = self.longer_tick_multiple*self.tick_size
|
||||
else:
|
||||
tick_size_used = self.tick_size
|
||||
self.add_tick(x, tick_size_used)
|
||||
|
||||
self.stretch(self.unit_size, 0)
|
||||
self.shift(-self.number_to_point(self.number_at_center))
|
||||
|
||||
def add_tick(self, x, size = None):
|
||||
self.tick_marks.add(self.get_tick(x, size))
|
||||
return self
|
||||
|
||||
def get_tick(self, x, size = None):
|
||||
if size is None: size = self.tick_size
|
||||
result = Line(size*DOWN, size*UP)
|
||||
result.rotate(self.main_line.get_angle())
|
||||
result.move_to(self.number_to_point(x))
|
||||
return result
|
||||
|
||||
def get_tick_marks(self):
|
||||
return self.tick_marks
|
||||
|
||||
def get_tick_numbers(self):
|
||||
epsilon = 0.001
|
||||
return np.arange(
|
||||
self.leftmost_tick, self.x_max+epsilon,
|
||||
self.tick_frequency
|
||||
)
|
||||
|
||||
def number_to_point(self, number):
|
||||
alpha = float(number-self.x_min)/(self.x_max - self.x_min)
|
||||
return interpolate(
|
||||
self.main_line.get_start(),
|
||||
self.main_line.get_end(),
|
||||
alpha
|
||||
)
|
||||
|
||||
def point_to_number(self, point):
|
||||
left_point, right_point = self.main_line.get_start_and_end()
|
||||
full_vect = right_point-left_point
|
||||
def distance_from_left(p):
|
||||
return np.dot(p-left_point, full_vect)/np.linalg.norm(full_vect)
|
||||
|
||||
return interpolate(
|
||||
self.x_min, self.x_max,
|
||||
distance_from_left(point)/distance_from_left(right_point)
|
||||
)
|
||||
|
||||
def default_numbers_to_display(self):
|
||||
if self.numbers_to_show is not None:
|
||||
return self.numbers_to_show
|
||||
return np.arange(int(self.leftmost_tick), int(self.x_max)+1)
|
||||
|
||||
def get_number_mobjects(self, *numbers, **kwargs):
|
||||
#TODO, handle decimals
|
||||
if len(numbers) == 0:
|
||||
numbers = self.default_numbers_to_display()
|
||||
if "force_integers" in kwargs and kwargs["force_integers"]:
|
||||
numbers = map(int, numbers)
|
||||
result = VGroup()
|
||||
for number in numbers:
|
||||
mob = TexMobject(str(number))
|
||||
mob.scale(self.number_scale_val)
|
||||
mob.next_to(
|
||||
self.number_to_point(number),
|
||||
self.label_direction,
|
||||
self.line_to_number_buff,
|
||||
)
|
||||
result.add(mob)
|
||||
return result
|
||||
|
||||
def get_labels(self):
|
||||
return self.get_number_mobjects()
|
||||
|
||||
def add_numbers(self, *numbers, **kwargs):
|
||||
self.numbers = self.get_number_mobjects(
|
||||
*numbers, **kwargs
|
||||
)
|
||||
self.add(*self.numbers)
|
||||
return self
|
||||
|
||||
def add_tip(self):
|
||||
start, end = self.main_line.get_start_and_end()
|
||||
vect = (end - start)/np.linalg.norm(end-start)
|
||||
arrow = Arrow(start, end + MED_SMALL_BUFF*vect, buff = 0)
|
||||
tip = arrow.tip
|
||||
tip.set_color(self.color)
|
||||
self.tip = tip
|
||||
self.add(tip)
|
||||
|
||||
class UnitInterval(NumberLine):
|
||||
CONFIG = {
|
||||
"x_min" : 0,
|
||||
"x_max" : 1,
|
||||
"unit_size" : 6,
|
||||
"tick_frequency" : 0.1,
|
||||
"numbers_with_elongated_ticks" : [0, 1],
|
||||
"number_at_center" : 0.5,
|
||||
}
|
||||
#TODO: There should be much more code reuse between Axes, NumberPlane and GraphScene
|
||||
|
||||
class Axes(VGroup):
|
||||
CONFIG = {
|
||||
|
@ -423,13 +290,62 @@ class NumberPlane(VMobject):
|
|||
mob.make_smooth()
|
||||
return self
|
||||
|
||||
class ComplexPlane(NumberPlane):
|
||||
CONFIG = {
|
||||
"color" : BLUE,
|
||||
"unit_size" : 1,
|
||||
"line_frequency" : 1,
|
||||
"faded_line_frequency" : 0.5,
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
kwargs.update({
|
||||
"x_unit_size" : self.unit_size,
|
||||
"y_unit_size" : self.unit_size,
|
||||
"x_line_frequency" : self.line_frequency,
|
||||
"x_faded_line_frequency" : self.faded_line_frequency,
|
||||
"y_line_frequency" : self.line_frequency,
|
||||
"y_faded_line_frequency" : self.faded_line_frequency,
|
||||
})
|
||||
NumberPlane.__init__(self, **kwargs)
|
||||
|
||||
def number_to_point(self, number):
|
||||
number = complex(number)
|
||||
return self.coords_to_point(number.real, number.imag)
|
||||
|
||||
def point_to_number(self, point):
|
||||
x, y = self.point_to_coords(point)
|
||||
return complex(x, y)
|
||||
|
||||
def get_coordinate_labels(self, *numbers):
|
||||
# TODO: Should merge this with the code from NumberPlane.get_coordinate_labels
|
||||
|
||||
result = VGroup()
|
||||
nudge = 0.1*(DOWN+RIGHT)
|
||||
if len(numbers) == 0:
|
||||
numbers = range(-int(self.x_radius), int(self.x_radius)+1)
|
||||
numbers += [
|
||||
complex(0, y)
|
||||
for y in range(-int(self.y_radius), int(self.y_radius)+1)
|
||||
]
|
||||
for number in numbers:
|
||||
if number == complex(0, 0):
|
||||
continue
|
||||
point = self.number_to_point(number)
|
||||
num_str = str(number).replace("j", "i")
|
||||
if num_str.startswith("0"):
|
||||
num_str = "0"
|
||||
elif num_str in ["1i", "-1i"]:
|
||||
num_str = num_str.replace("1", "")
|
||||
num_mob = TexMobject(num_str)
|
||||
num_mob.add_background_rectangle()
|
||||
num_mob.scale_to_fit_height(self.written_coordinate_height)
|
||||
num_mob.next_to(point, DOWN+LEFT, SMALL_BUFF)
|
||||
result.add(num_mob)
|
||||
self.coordinate_labels = result
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def add_coordinates(self, *numbers):
|
||||
self.add(*self.get_coordinate_labels(*numbers))
|
||||
return self
|
||||
|
44
mobject/frame.py
Normal file
44
mobject/frame.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
from mobject.geometry import Rectangle
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
class ScreenRectangle(Rectangle):
|
||||
CONFIG = {
|
||||
"width_to_height_ratio" : 16.0/9.0,
|
||||
"height" : 4,
|
||||
}
|
||||
def generate_points(self):
|
||||
self.width = self.width_to_height_ratio * self.height
|
||||
Rectangle.generate_points(self)
|
||||
|
||||
class FullScreenRectangle(ScreenRectangle):
|
||||
CONFIG = {
|
||||
"height" : FRAME_HEIGHT,
|
||||
}
|
||||
|
||||
class FullScreenFadeRectangle(FullScreenRectangle):
|
||||
CONFIG = {
|
||||
"stroke_width" : 0,
|
||||
"fill_color" : BLACK,
|
||||
"fill_opacity" : 0.7,
|
||||
}
|
||||
|
||||
class PictureInPictureFrame(Rectangle):
|
||||
CONFIG = {
|
||||
"height" : 3,
|
||||
"aspect_ratio" : (16, 9)
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
height = self.height
|
||||
if "height" in kwargs:
|
||||
kwargs.pop("height")
|
||||
Rectangle.__init__(
|
||||
self,
|
||||
width = self.aspect_ratio[0],
|
||||
height = self.aspect_ratio[1],
|
||||
**kwargs
|
||||
)
|
||||
self.scale_to_fit_height(height)
|
|
@ -1,10 +1,11 @@
|
|||
from scipy import integrate
|
||||
|
||||
from mobject.vectorized_mobject import VMobject
|
||||
from utils.config_ops import digest_config
|
||||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
|
||||
class ParametricFunction(VMobject):
|
||||
CONFIG = {
|
||||
"t_min" : 0,
|
|
@ -1,14 +1,21 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.vectorized_mobject import VMobject, VGroup
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from utils.bezier import interpolate
|
||||
from utils.config_ops import digest_config, digest_locals
|
||||
from utils.config_ops import digest_config
|
||||
from utils.config_ops import digest_locals
|
||||
from utils.paths import path_along_arc
|
||||
from utils.space_ops import rotate_vector, angle_of_vector, compass_directions, center_of_mass
|
||||
from utils.space_ops import angle_of_vector
|
||||
from utils.space_ops import center_of_mass
|
||||
from utils.space_ops import compass_directions
|
||||
from utils.space_ops import rotate_vector
|
||||
|
||||
class Arc(VMobject):
|
||||
CONFIG = {
|
137
mobject/matrix.py
Normal file
137
mobject/matrix.py
Normal file
|
@ -0,0 +1,137 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.shape_matchers import BackgroundRectangle
|
||||
|
||||
from constants import *
|
||||
|
||||
VECTOR_LABEL_SCALE_FACTOR = 0.8
|
||||
|
||||
def matrix_to_tex_string(matrix):
|
||||
matrix = np.array(matrix).astype("string")
|
||||
if matrix.ndim == 1:
|
||||
matrix = matrix.reshape((matrix.size, 1))
|
||||
n_rows, n_cols = matrix.shape
|
||||
prefix = "\\left[ \\begin{array}{%s}"%("c"*n_cols)
|
||||
suffix = "\\end{array} \\right]"
|
||||
rows = [
|
||||
" & ".join(row)
|
||||
for row in matrix
|
||||
]
|
||||
return prefix + " \\\\ ".join(rows) + suffix
|
||||
|
||||
def matrix_to_mobject(matrix):
|
||||
return TexMobject(matrix_to_tex_string(matrix))
|
||||
|
||||
def vector_coordinate_label(vector_mob, integer_labels = True,
|
||||
n_dim = 2, color = WHITE):
|
||||
vect = np.array(vector_mob.get_end())
|
||||
if integer_labels:
|
||||
vect = np.round(vect).astype(int)
|
||||
vect = vect[:n_dim]
|
||||
vect = vect.reshape((n_dim, 1))
|
||||
label = Matrix(vect, add_background_rectangles = True)
|
||||
label.scale(VECTOR_LABEL_SCALE_FACTOR)
|
||||
|
||||
shift_dir = np.array(vector_mob.get_end())
|
||||
if shift_dir[0] >= 0: #Pointing right
|
||||
shift_dir -= label.get_left() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER*LEFT
|
||||
else: #Pointing left
|
||||
shift_dir -= label.get_right() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER*RIGHT
|
||||
label.shift(shift_dir)
|
||||
label.set_color(color)
|
||||
label.rect = BackgroundRectangle(label)
|
||||
label.add_to_back(label.rect)
|
||||
return label
|
||||
|
||||
class Matrix(VMobject):
|
||||
CONFIG = {
|
||||
"v_buff" : 0.5,
|
||||
"h_buff" : 1,
|
||||
"add_background_rectangles" : False
|
||||
}
|
||||
def __init__(self, matrix, **kwargs):
|
||||
"""
|
||||
Matrix can either either include numbres, tex_strings,
|
||||
or mobjects
|
||||
"""
|
||||
VMobject.__init__(self, **kwargs)
|
||||
matrix = np.array(matrix)
|
||||
if matrix.ndim == 1:
|
||||
matrix = matrix.reshape((matrix.size, 1))
|
||||
if not isinstance(matrix[0][0], Mobject):
|
||||
matrix = matrix.astype("string")
|
||||
matrix = self.string_matrix_to_mob_matrix(matrix)
|
||||
self.organize_mob_matrix(matrix)
|
||||
self.add(*matrix.flatten())
|
||||
self.add_brackets()
|
||||
self.center()
|
||||
self.mob_matrix = matrix
|
||||
if self.add_background_rectangles:
|
||||
for mob in matrix.flatten():
|
||||
mob.add_background_rectangle()
|
||||
|
||||
def string_matrix_to_mob_matrix(self, matrix):
|
||||
return np.array([
|
||||
map(TexMobject, row)
|
||||
for row in matrix
|
||||
]).reshape(matrix.shape)
|
||||
|
||||
def organize_mob_matrix(self, matrix):
|
||||
for i, row in enumerate(matrix):
|
||||
for j, elem in enumerate(row):
|
||||
mob = matrix[i][j]
|
||||
if i == 0 and j == 0:
|
||||
continue
|
||||
elif i == 0:
|
||||
mob.next_to(matrix[i][j-1], RIGHT, self.h_buff)
|
||||
else:
|
||||
mob.next_to(matrix[i-1][j], DOWN, self.v_buff)
|
||||
return self
|
||||
|
||||
def add_brackets(self):
|
||||
bracket_pair = TexMobject("\\big[ \\big]")
|
||||
bracket_pair.scale(2)
|
||||
bracket_pair.stretch_to_fit_height(self.get_height() + 0.5)
|
||||
l_bracket, r_bracket = bracket_pair.split()
|
||||
l_bracket.next_to(self, LEFT)
|
||||
r_bracket.next_to(self, RIGHT)
|
||||
self.add(l_bracket, r_bracket)
|
||||
self.brackets = VGroup(l_bracket, r_bracket)
|
||||
return self
|
||||
|
||||
def set_color_columns(self, *colors):
|
||||
for i, color in enumerate(colors):
|
||||
VGroup(*self.mob_matrix[:,i]).set_color(color)
|
||||
return self
|
||||
|
||||
def add_background_to_entries(self):
|
||||
for mob in self.get_entries():
|
||||
mob.add_background_rectangle()
|
||||
return self
|
||||
|
||||
def get_mob_matrix(self):
|
||||
return self.mob_matrix
|
||||
|
||||
def get_entries(self):
|
||||
return VGroup(*self.get_mob_matrix().flatten())
|
||||
|
||||
def get_brackets(self):
|
||||
return self.brackets
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,20 +1,25 @@
|
|||
import copy
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
import operator as op
|
||||
import itertools as it
|
||||
import os
|
||||
import copy
|
||||
|
||||
from PIL import Image
|
||||
from colour import Color
|
||||
|
||||
from constants import *
|
||||
from container.container import Container
|
||||
from utils.bezier import interpolate
|
||||
from utils.color import color_to_rgb, color_gradient
|
||||
from utils.color import color_gradient
|
||||
from utils.color import color_to_rgb
|
||||
from utils.color import interpolate_color
|
||||
from utils.iterables import remove_list_redundancies, list_update
|
||||
from utils.iterables import list_update
|
||||
from utils.iterables import remove_list_redundancies
|
||||
from utils.paths import straight_path
|
||||
from utils.space_ops import rotation_matrix, angle_of_vector
|
||||
from utils.space_ops import complex_to_R3, R3_to_complex
|
||||
from utils.space_ops import R3_to_complex
|
||||
from utils.space_ops import angle_of_vector
|
||||
from utils.space_ops import complex_to_R3
|
||||
from utils.space_ops import rotation_matrix
|
||||
|
||||
|
||||
#TODO: Explain array_attrs
|
||||
|
|
154
mobject/number_line.py
Normal file
154
mobject/number_line.py
Normal file
|
@ -0,0 +1,154 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.geometry import Arrow
|
||||
from mobject.geometry import Line
|
||||
from utils.bezier import interpolate
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
class NumberLine(VMobject):
|
||||
CONFIG = {
|
||||
"color" : BLUE,
|
||||
"x_min" : -FRAME_X_RADIUS,
|
||||
"x_max" : FRAME_X_RADIUS,
|
||||
"unit_size" : 1,
|
||||
"tick_size" : 0.1,
|
||||
"tick_frequency" : 1,
|
||||
"leftmost_tick" : None, #Defaults to value near x_min s.t. 0 is a tick
|
||||
"numbers_with_elongated_ticks" : [0],
|
||||
"numbers_to_show" : None,
|
||||
"longer_tick_multiple" : 2,
|
||||
"number_at_center" : 0,
|
||||
"number_scale_val" : 0.75,
|
||||
"label_direction" : DOWN,
|
||||
"line_to_number_buff" : MED_SMALL_BUFF,
|
||||
"include_tip" : False,
|
||||
"propagate_style_to_family" : True,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
if self.leftmost_tick is None:
|
||||
tf = self.tick_frequency
|
||||
self.leftmost_tick = tf*np.ceil(self.x_min/tf)
|
||||
VMobject.__init__(self, **kwargs)
|
||||
if self.include_tip:
|
||||
self.add_tip()
|
||||
|
||||
def generate_points(self):
|
||||
self.main_line = Line(self.x_min*RIGHT, self.x_max*RIGHT)
|
||||
self.tick_marks = VGroup()
|
||||
self.add(self.main_line, self.tick_marks)
|
||||
rounding_value = int(-np.log10(0.1*self.tick_frequency))
|
||||
rounded_numbers_with_elongated_ticks = np.round(
|
||||
self.numbers_with_elongated_ticks,
|
||||
rounding_value
|
||||
)
|
||||
|
||||
for x in self.get_tick_numbers():
|
||||
rounded_x = np.round(x, rounding_value)
|
||||
if rounded_x in rounded_numbers_with_elongated_ticks:
|
||||
tick_size_used = self.longer_tick_multiple*self.tick_size
|
||||
else:
|
||||
tick_size_used = self.tick_size
|
||||
self.add_tick(x, tick_size_used)
|
||||
|
||||
self.stretch(self.unit_size, 0)
|
||||
self.shift(-self.number_to_point(self.number_at_center))
|
||||
|
||||
def add_tick(self, x, size = None):
|
||||
self.tick_marks.add(self.get_tick(x, size))
|
||||
return self
|
||||
|
||||
def get_tick(self, x, size = None):
|
||||
if size is None: size = self.tick_size
|
||||
result = Line(size*DOWN, size*UP)
|
||||
result.rotate(self.main_line.get_angle())
|
||||
result.move_to(self.number_to_point(x))
|
||||
return result
|
||||
|
||||
def get_tick_marks(self):
|
||||
return self.tick_marks
|
||||
|
||||
def get_tick_numbers(self):
|
||||
epsilon = 0.001
|
||||
return np.arange(
|
||||
self.leftmost_tick, self.x_max+epsilon,
|
||||
self.tick_frequency
|
||||
)
|
||||
|
||||
def number_to_point(self, number):
|
||||
alpha = float(number-self.x_min)/(self.x_max - self.x_min)
|
||||
return interpolate(
|
||||
self.main_line.get_start(),
|
||||
self.main_line.get_end(),
|
||||
alpha
|
||||
)
|
||||
|
||||
def point_to_number(self, point):
|
||||
left_point, right_point = self.main_line.get_start_and_end()
|
||||
full_vect = right_point-left_point
|
||||
def distance_from_left(p):
|
||||
return np.dot(p-left_point, full_vect)/np.linalg.norm(full_vect)
|
||||
|
||||
return interpolate(
|
||||
self.x_min, self.x_max,
|
||||
distance_from_left(point)/distance_from_left(right_point)
|
||||
)
|
||||
|
||||
def default_numbers_to_display(self):
|
||||
if self.numbers_to_show is not None:
|
||||
return self.numbers_to_show
|
||||
return np.arange(int(self.leftmost_tick), int(self.x_max)+1)
|
||||
|
||||
def get_number_mobjects(self, *numbers, **kwargs):
|
||||
#TODO, handle decimals
|
||||
if len(numbers) == 0:
|
||||
numbers = self.default_numbers_to_display()
|
||||
if "force_integers" in kwargs and kwargs["force_integers"]:
|
||||
numbers = map(int, numbers)
|
||||
result = VGroup()
|
||||
for number in numbers:
|
||||
mob = TexMobject(str(number))
|
||||
mob.scale(self.number_scale_val)
|
||||
mob.next_to(
|
||||
self.number_to_point(number),
|
||||
self.label_direction,
|
||||
self.line_to_number_buff,
|
||||
)
|
||||
result.add(mob)
|
||||
return result
|
||||
|
||||
def get_labels(self):
|
||||
return self.get_number_mobjects()
|
||||
|
||||
def add_numbers(self, *numbers, **kwargs):
|
||||
self.numbers = self.get_number_mobjects(
|
||||
*numbers, **kwargs
|
||||
)
|
||||
self.add(*self.numbers)
|
||||
return self
|
||||
|
||||
def add_tip(self):
|
||||
start, end = self.main_line.get_start_and_end()
|
||||
vect = (end - start)/np.linalg.norm(end-start)
|
||||
arrow = Arrow(start, end + MED_SMALL_BUFF*vect, buff = 0)
|
||||
tip = arrow.tip
|
||||
tip.set_color(self.color)
|
||||
self.tip = tip
|
||||
self.add(tip)
|
||||
|
||||
class UnitInterval(NumberLine):
|
||||
CONFIG = {
|
||||
"x_min" : 0,
|
||||
"x_max" : 1,
|
||||
"unit_size" : 6,
|
||||
"tick_frequency" : 0.1,
|
||||
"numbers_with_elongated_ticks" : [0, 1],
|
||||
"number_at_center" : 0.5,
|
||||
}
|
||||
|
85
mobject/numbers.py
Normal file
85
mobject/numbers.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.shape_matchers import BackgroundRectangle
|
||||
|
||||
class DecimalNumber(VMobject):
|
||||
CONFIG = {
|
||||
"num_decimal_points" : 2,
|
||||
"digit_to_digit_buff" : 0.05,
|
||||
"show_ellipsis" : False,
|
||||
"unit" : None, #Aligned to bottom unless it starts with "^"
|
||||
"include_background_rectangle" : False,
|
||||
}
|
||||
def __init__(self, number, **kwargs):
|
||||
VMobject.__init__(self, **kwargs)
|
||||
self.number = number
|
||||
ndp = self.num_decimal_points
|
||||
|
||||
#Build number string
|
||||
if isinstance(number, complex):
|
||||
num_string = '%.*f%s%.*fi'%(
|
||||
ndp, number.real,
|
||||
"-" if number.imag < 0 else "+",
|
||||
ndp, abs(number.imag)
|
||||
)
|
||||
else:
|
||||
num_string = '%.*f'%(ndp, number)
|
||||
negative_zero_string = "-%.*f"%(ndp, 0.)
|
||||
if num_string == negative_zero_string:
|
||||
num_string = num_string[1:]
|
||||
self.add(*[
|
||||
TexMobject(char, **kwargs)
|
||||
for char in num_string
|
||||
])
|
||||
|
||||
#Add non-numerical bits
|
||||
if self.show_ellipsis:
|
||||
self.add(TexMobject("\\dots"))
|
||||
|
||||
|
||||
if num_string.startswith("-"):
|
||||
minus = self.submobjects[0]
|
||||
minus.next_to(
|
||||
self.submobjects[1], LEFT,
|
||||
buff = self.digit_to_digit_buff
|
||||
)
|
||||
|
||||
if self.unit != None:
|
||||
self.unit_sign = TexMobject(self.unit)
|
||||
self.add(self.unit_sign)
|
||||
|
||||
self.arrange_submobjects(
|
||||
buff = self.digit_to_digit_buff,
|
||||
aligned_edge = DOWN
|
||||
)
|
||||
|
||||
#Handle alignment of parts that should be aligned
|
||||
#to the bottom
|
||||
for i, c in enumerate(num_string):
|
||||
if c == "-" and len(num_string) > i+1:
|
||||
self[i].align_to(self[i+1], alignment_vect = UP)
|
||||
if self.unit and self.unit.startswith("^"):
|
||||
self.unit_sign.align_to(self, UP)
|
||||
#
|
||||
if self.include_background_rectangle:
|
||||
self.add_background_rectangle()
|
||||
|
||||
def add_background_rectangle(self):
|
||||
#TODO, is this the best way to handle
|
||||
#background rectangles?
|
||||
self.background_rectangle = BackgroundRectangle(self)
|
||||
self.submobjects = [
|
||||
self.background_rectangle,
|
||||
VGroup(*self.submobjects)
|
||||
]
|
||||
return self
|
||||
|
||||
class Integer(DecimalNumber):
|
||||
CONFIG = {
|
||||
"num_decimal_points" : 0,
|
||||
}
|
251
mobject/probability.py
Normal file
251
mobject/probability.py
Normal file
|
@ -0,0 +1,251 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.svg.brace import Brace
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.svg.tex_mobject import TextMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.geometry import Line
|
||||
from mobject.geometry import Rectangle
|
||||
|
||||
from utils.color import color_gradient
|
||||
from utils.iterables import tuplify
|
||||
|
||||
EPSILON = 0.0001
|
||||
|
||||
class SampleSpace(Rectangle):
|
||||
CONFIG = {
|
||||
"height" : 3,
|
||||
"width" : 3,
|
||||
"fill_color" : DARK_GREY,
|
||||
"fill_opacity" : 1,
|
||||
"stroke_width" : 0.5,
|
||||
"stroke_color" : LIGHT_GREY,
|
||||
##
|
||||
"default_label_scale_val" : 1,
|
||||
}
|
||||
def add_title(self, title = "Sample space", buff = MED_SMALL_BUFF):
|
||||
##TODO, should this really exist in SampleSpaceScene
|
||||
title_mob = TextMobject(title)
|
||||
if title_mob.get_width() > self.get_width():
|
||||
title_mob.scale_to_fit_width(self.get_width())
|
||||
title_mob.next_to(self, UP, buff = buff)
|
||||
self.title = title_mob
|
||||
self.add(title_mob)
|
||||
|
||||
def add_label(self, label):
|
||||
self.label = label
|
||||
|
||||
def complete_p_list(self, p_list):
|
||||
new_p_list = list(tuplify(p_list))
|
||||
remainder = 1.0 - sum(new_p_list)
|
||||
if abs(remainder) > EPSILON:
|
||||
new_p_list.append(remainder)
|
||||
return new_p_list
|
||||
|
||||
def get_division_along_dimension(self, p_list, dim, colors, vect):
|
||||
p_list = self.complete_p_list(p_list)
|
||||
colors = color_gradient(colors, len(p_list))
|
||||
|
||||
last_point = self.get_edge_center(-vect)
|
||||
parts = VGroup()
|
||||
for factor, color in zip(p_list, colors):
|
||||
part = SampleSpace()
|
||||
part.set_fill(color, 1)
|
||||
part.replace(self, stretch = True)
|
||||
part.stretch(factor, dim)
|
||||
part.move_to(last_point, -vect)
|
||||
last_point = part.get_edge_center(vect)
|
||||
parts.add(part)
|
||||
return parts
|
||||
|
||||
def get_horizontal_division(
|
||||
self, p_list,
|
||||
colors = [GREEN_E, BLUE_E],
|
||||
vect = DOWN
|
||||
):
|
||||
return self.get_division_along_dimension(p_list, 1, colors, vect)
|
||||
|
||||
def get_vertical_division(
|
||||
self, p_list,
|
||||
colors = [MAROON_B, YELLOW],
|
||||
vect = RIGHT
|
||||
):
|
||||
return self.get_division_along_dimension(p_list, 0, colors, vect)
|
||||
|
||||
def divide_horizontally(self, *args, **kwargs):
|
||||
self.horizontal_parts = self.get_horizontal_division(*args, **kwargs)
|
||||
self.add(self.horizontal_parts)
|
||||
|
||||
def divide_vertically(self, *args, **kwargs):
|
||||
self.vertical_parts = self.get_vertical_division(*args, **kwargs)
|
||||
self.add(self.vertical_parts)
|
||||
|
||||
def get_subdivision_braces_and_labels(
|
||||
self, parts, labels, direction,
|
||||
buff = SMALL_BUFF,
|
||||
min_num_quads = 1
|
||||
):
|
||||
label_mobs = VGroup()
|
||||
braces = VGroup()
|
||||
for label, part in zip(labels, parts):
|
||||
brace = Brace(
|
||||
part, direction,
|
||||
min_num_quads = min_num_quads,
|
||||
buff = buff
|
||||
)
|
||||
if isinstance(label, Mobject):
|
||||
label_mob = label
|
||||
else:
|
||||
label_mob = TexMobject(label)
|
||||
label_mob.scale(self.default_label_scale_val)
|
||||
label_mob.next_to(brace, direction, buff)
|
||||
|
||||
braces.add(brace)
|
||||
label_mobs.add(label_mob)
|
||||
parts.braces = braces
|
||||
parts.labels = label_mobs
|
||||
parts.label_kwargs = {
|
||||
"labels" : label_mobs.copy(),
|
||||
"direction" : direction,
|
||||
"buff" : buff,
|
||||
}
|
||||
return VGroup(parts.braces, parts.labels)
|
||||
|
||||
def get_side_braces_and_labels(self, labels, direction = LEFT, **kwargs):
|
||||
assert(hasattr(self, "horizontal_parts"))
|
||||
parts = self.horizontal_parts
|
||||
return self.get_subdivision_braces_and_labels(parts, labels, direction, **kwargs)
|
||||
|
||||
def get_top_braces_and_labels(self, labels, **kwargs):
|
||||
assert(hasattr(self, "vertical_parts"))
|
||||
parts = self.vertical_parts
|
||||
return self.get_subdivision_braces_and_labels(parts, labels, UP, **kwargs)
|
||||
|
||||
def get_bottom_braces_and_labels(self, labels, **kwargs):
|
||||
assert(hasattr(self, "vertical_parts"))
|
||||
parts = self.vertical_parts
|
||||
return self.get_subdivision_braces_and_labels(parts, labels, DOWN, **kwargs)
|
||||
|
||||
def add_braces_and_labels(self):
|
||||
for attr in "horizontal_parts", "vertical_parts":
|
||||
if not hasattr(self, attr):
|
||||
continue
|
||||
parts = getattr(self, attr)
|
||||
for subattr in "braces", "labels":
|
||||
if hasattr(parts, subattr):
|
||||
self.add(getattr(parts, subattr))
|
||||
|
||||
def __getitem__(self, index):
|
||||
if hasattr(self, "horizontal_parts"):
|
||||
return self.horizontal_parts[index]
|
||||
elif hasattr(self, "vertical_parts"):
|
||||
return self.vertical_parts[index]
|
||||
return self.split()[index]
|
||||
|
||||
class BarChart(VGroup):
|
||||
CONFIG = {
|
||||
"height" : 4,
|
||||
"width" : 6,
|
||||
"n_ticks" : 4,
|
||||
"tick_width" : 0.2,
|
||||
"label_y_axis" : True,
|
||||
"y_axis_label_height" : 0.25,
|
||||
"max_value" : 1,
|
||||
"bar_colors" : [BLUE, YELLOW],
|
||||
"bar_fill_opacity" : 0.8,
|
||||
"bar_stroke_width" : 3,
|
||||
"bar_names" : [],
|
||||
"bar_label_scale_val" : 0.75,
|
||||
}
|
||||
def __init__(self, values, **kwargs):
|
||||
VGroup.__init__(self, **kwargs)
|
||||
if self.max_value is None:
|
||||
self.max_value = max(values)
|
||||
|
||||
self.add_axes()
|
||||
self.add_bars(values)
|
||||
self.center()
|
||||
|
||||
def add_axes(self):
|
||||
x_axis = Line(self.tick_width*LEFT/2, self.width*RIGHT)
|
||||
y_axis = Line(MED_LARGE_BUFF*DOWN, self.height*UP)
|
||||
ticks = VGroup()
|
||||
heights = np.linspace(0, self.height, self.n_ticks+1)
|
||||
values = np.linspace(0, self.max_value, self.n_ticks+1)
|
||||
for y, value in zip(heights, values):
|
||||
tick = Line(LEFT, RIGHT)
|
||||
tick.scale_to_fit_width(self.tick_width)
|
||||
tick.move_to(y*UP)
|
||||
ticks.add(tick)
|
||||
y_axis.add(ticks)
|
||||
|
||||
self.add(x_axis, y_axis)
|
||||
self.x_axis, self.y_axis = x_axis, y_axis
|
||||
|
||||
if self.label_y_axis:
|
||||
labels = VGroup()
|
||||
for tick, value in zip(ticks, values):
|
||||
label = TexMobject(str(np.round(value, 2)))
|
||||
label.scale_to_fit_height(self.y_axis_label_height)
|
||||
label.next_to(tick, LEFT, SMALL_BUFF)
|
||||
labels.add(label)
|
||||
self.y_axis_labels = labels
|
||||
self.add(labels)
|
||||
|
||||
|
||||
def add_bars(self, values):
|
||||
buff = float(self.width) / (2*len(values) + 1)
|
||||
bars = VGroup()
|
||||
for i, value in enumerate(values):
|
||||
bar = Rectangle(
|
||||
height = (value/self.max_value)*self.height,
|
||||
width = buff,
|
||||
stroke_width = self.bar_stroke_width,
|
||||
fill_opacity = self.bar_fill_opacity,
|
||||
)
|
||||
bar.move_to((2*i+1)*buff*RIGHT, DOWN+LEFT)
|
||||
bars.add(bar)
|
||||
bars.set_color_by_gradient(*self.bar_colors)
|
||||
|
||||
bar_labels = VGroup()
|
||||
for bar, name in zip(bars, self.bar_names):
|
||||
label = TexMobject(str(name))
|
||||
label.scale(self.bar_label_scale_val)
|
||||
label.next_to(bar, DOWN, SMALL_BUFF)
|
||||
bar_labels.add(label)
|
||||
|
||||
self.add(bars, bar_labels)
|
||||
self.bars = bars
|
||||
self.bar_labels = bar_labels
|
||||
|
||||
def change_bar_values(self, values):
|
||||
for bar, value in zip(self.bars, values):
|
||||
bar_bottom = bar.get_bottom()
|
||||
bar.stretch_to_fit_height(
|
||||
(value/self.max_value)*self.height
|
||||
)
|
||||
bar.move_to(bar_bottom, DOWN)
|
||||
|
||||
def copy(self):
|
||||
return self.deepcopy()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
56
mobject/shape_matchers.py
Normal file
56
mobject/shape_matchers.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.geometry import Rectangle
|
||||
from mobject.geometry import Line
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from utils.config_ops import digest_config
|
||||
from utils.color import Color
|
||||
|
||||
class SurroundingRectangle(Rectangle):
|
||||
CONFIG = {
|
||||
"color" : YELLOW,
|
||||
"buff" : SMALL_BUFF,
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
kwargs["width"] = mobject.get_width() + 2*self.buff
|
||||
kwargs["height"] = mobject.get_height() + 2*self.buff
|
||||
Rectangle.__init__(self, **kwargs)
|
||||
self.move_to(mobject)
|
||||
|
||||
class BackgroundRectangle(SurroundingRectangle):
|
||||
CONFIG = {
|
||||
"color" : BLACK,
|
||||
"stroke_width" : 0,
|
||||
"fill_opacity" : 0.75,
|
||||
"buff" : 0
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
SurroundingRectangle.__init__(self, mobject, **kwargs)
|
||||
self.original_fill_opacity = self.fill_opacity
|
||||
|
||||
def pointwise_become_partial(self, mobject, a, b):
|
||||
self.set_fill(opacity = b*self.original_fill_opacity)
|
||||
return self
|
||||
|
||||
def set_color(self):
|
||||
# Can't be changin' me!
|
||||
return self
|
||||
|
||||
def get_fill_color(self):
|
||||
return Color(self.color)
|
||||
|
||||
class Cross(VGroup):
|
||||
CONFIG = {
|
||||
"stroke_color" : RED,
|
||||
"stroke_width" : 6,
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
VGroup.__init__(self,
|
||||
Line(UP+LEFT, DOWN+RIGHT),
|
||||
Line(UP+RIGHT, DOWN+LEFT),
|
||||
)
|
||||
self.replace(mobject, stretch = True)
|
||||
self.set_stroke(self.stroke_color, self.stroke_width)
|
0
mobject/svg/__init__.py
Normal file
0
mobject/svg/__init__.py
Normal file
130
mobject/svg/brace.py
Normal file
130
mobject/svg/brace.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
|
||||
from animation.composition import AnimationGroup
|
||||
from animation.creation import FadeIn
|
||||
from animation.creation import GrowFromCenter
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.svg.tex_mobject import TextMobject
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
class Brace(TexMobject):
|
||||
CONFIG = {
|
||||
"buff" : 0.2,
|
||||
"width_multiplier" : 2,
|
||||
"max_num_quads" : 15,
|
||||
"min_num_quads" : 0,
|
||||
}
|
||||
def __init__(self, mobject, direction = DOWN, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
angle = -np.arctan2(*direction[:2]) + np.pi
|
||||
mobject.rotate(-angle, about_point = ORIGIN)
|
||||
left = mobject.get_corner(DOWN+LEFT)
|
||||
right = mobject.get_corner(DOWN+RIGHT)
|
||||
target_width = right[0]-left[0]
|
||||
|
||||
## Adding int(target_width) qquads gives approximately the right width
|
||||
num_quads = np.clip(
|
||||
int(self.width_multiplier*target_width),
|
||||
self.min_num_quads, self.max_num_quads
|
||||
)
|
||||
tex_string = "\\underbrace{%s}"%(num_quads*"\\qquad")
|
||||
TexMobject.__init__(self, tex_string, **kwargs)
|
||||
self.tip_point_index = np.argmin(self.get_all_points()[:,1])
|
||||
self.stretch_to_fit_width(target_width)
|
||||
self.shift(left - self.get_corner(UP+LEFT) + self.buff*DOWN)
|
||||
for mob in mobject, self:
|
||||
mob.rotate(angle, about_point = ORIGIN)
|
||||
|
||||
def put_at_tip(self, mob, use_next_to = True, **kwargs):
|
||||
if use_next_to:
|
||||
mob.next_to(
|
||||
self.get_tip(),
|
||||
np.round(self.get_direction()),
|
||||
**kwargs
|
||||
)
|
||||
else:
|
||||
mob.move_to(self.get_tip())
|
||||
buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_MOBJECT_BUFFER)
|
||||
shift_distance = mob.get_width()/2.0+buff
|
||||
mob.shift(self.get_direction()*shift_distance)
|
||||
return self
|
||||
|
||||
def get_text(self, *text, **kwargs):
|
||||
text_mob = TextMobject(*text)
|
||||
self.put_at_tip(text_mob, **kwargs)
|
||||
return text_mob
|
||||
|
||||
def get_tex(self, *tex, **kwargs):
|
||||
tex_mob = TexMobject(*tex)
|
||||
self.put_at_tip(tex_mob, **kwargs)
|
||||
return tex_mob
|
||||
|
||||
def get_tip(self):
|
||||
# Very specific to the LaTeX representation
|
||||
# of a brace, but it's the only way I can think
|
||||
# of to get the tip regardless of orientation.
|
||||
return self.get_all_points()[self.tip_point_index]
|
||||
|
||||
def get_direction(self):
|
||||
vect = self.get_tip() - self.get_center()
|
||||
return vect/np.linalg.norm(vect)
|
||||
|
||||
class BraceLabel(VMobject):
|
||||
CONFIG = {
|
||||
"label_constructor" : TexMobject,
|
||||
"label_scale" : 1,
|
||||
}
|
||||
def __init__(self, obj, text, brace_direction = DOWN, **kwargs):
|
||||
VMobject.__init__(self, **kwargs)
|
||||
self.brace_direction = brace_direction
|
||||
if isinstance(obj, list): obj = VMobject(*obj)
|
||||
self.brace = Brace(obj, brace_direction, **kwargs)
|
||||
|
||||
if isinstance(text, tuple) or isinstance(text, list):
|
||||
self.label = self.label_constructor(*text, **kwargs)
|
||||
else: self.label = self.label_constructor(str(text))
|
||||
if self.label_scale != 1: self.label.scale(self.label_scale)
|
||||
|
||||
self.brace.put_at_tip(self.label)
|
||||
self.submobjects = [self.brace, self.label]
|
||||
|
||||
def creation_anim(self, label_anim = FadeIn, brace_anim = GrowFromCenter):
|
||||
return AnimationGroup(brace_anim(self.brace), label_anim(self.label))
|
||||
|
||||
def shift_brace(self, obj, **kwargs):
|
||||
if isinstance(obj, list): obj = VMobject(*obj)
|
||||
self.brace = Brace(obj, self.brace_direction, **kwargs)
|
||||
self.brace.put_at_tip(self.label)
|
||||
self.submobjects[0] = self.brace
|
||||
return self
|
||||
|
||||
def change_label(self, *text, **kwargs):
|
||||
self.label = self.label_constructor(*text, **kwargs)
|
||||
if self.label_scale != 1: self.label.scale(self.label_scale)
|
||||
|
||||
self.brace.put_at_tip(self.label)
|
||||
self.submobjects[1] = self.label
|
||||
return self
|
||||
|
||||
def change_brace_label(self, obj, *text):
|
||||
self.shift_brace(obj)
|
||||
self.change_label(*text)
|
||||
return self
|
||||
|
||||
def copy(self):
|
||||
copy_mobject = copy.copy(self)
|
||||
copy_mobject.brace = self.brace.copy()
|
||||
copy_mobject.label = self.label.copy()
|
||||
copy_mobject.submobjects = [copy_mobject.brace, copy_mobject.label]
|
||||
|
||||
return copy_mobject
|
||||
|
||||
class BraceText(BraceLabel):
|
||||
CONFIG = {
|
||||
"label_constructor" : TextMobject
|
||||
}
|
|
@ -1,21 +1,38 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint
|
||||
from mobject.svg_mobject import SVGMobject
|
||||
from mobject.tex_mobject import TextMobject, TexMobject, Brace
|
||||
from mobject.svg.svg_mobject import SVGMobject
|
||||
from mobject.svg.brace import Brace
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.svg.tex_mobject import TextMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.types.vectorized_mobject import VectorizedPoint
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.simple_animations import Rotating
|
||||
from animation.compositions import LaggedStart, AnimationGroup
|
||||
from animation.transform import ApplyMethod, FadeIn, GrowFromCenter
|
||||
from animation.composition import AnimationGroup
|
||||
from animation.composition import LaggedStart
|
||||
from animation.rotation import Rotating
|
||||
from animation.transform import ApplyMethod
|
||||
from animation.creation import FadeIn
|
||||
from animation.creation import GrowFromCenter
|
||||
|
||||
from topics.geometry import Circle, Line, Rectangle, Square, \
|
||||
Arc, Polygon, SurroundingRectangle
|
||||
from topics.three_dimensions import Cube
|
||||
from utils.config_ops import digest_config, digest_locals
|
||||
from utils.space_ops import rotate_vector, angle_of_vector
|
||||
from utils.space_ops import complex_to_R3, R3_to_complex
|
||||
from mobject.geometry import Arc
|
||||
from mobject.geometry import Circle
|
||||
from mobject.geometry import Line
|
||||
from mobject.geometry import Polygon
|
||||
from mobject.geometry import Rectangle
|
||||
from mobject.geometry import Square
|
||||
from mobject.shape_matchers import SurroundingRectangle
|
||||
from mobject.three_dimensions import Cube
|
||||
from utils.config_ops import digest_config
|
||||
from utils.config_ops import digest_locals
|
||||
from utils.space_ops import R3_to_complex
|
||||
from utils.space_ops import angle_of_vector
|
||||
from utils.space_ops import complex_to_R3
|
||||
from utils.space_ops import rotate_vector
|
||||
|
||||
class Lightbulb(SVGMobject):
|
||||
CONFIG = {
|
||||
|
@ -503,7 +520,7 @@ class Car(SVGMobject):
|
|||
self.set_stroke(color = WHITE, width = 0)
|
||||
self.set_fill(self.color, opacity = 1)
|
||||
|
||||
from topics.characters import Randolph
|
||||
from for_3b1b_videos.pi_creature import Randolph
|
||||
randy = Randolph(mode = "happy")
|
||||
randy.scale_to_fit_height(0.6*self.get_height())
|
||||
randy.stretch(0.8, 0)
|
||||
|
@ -568,131 +585,281 @@ class Car(SVGMobject):
|
|||
def get_rear_light(self):
|
||||
return self[1][8]
|
||||
|
||||
class MoveCar(ApplyMethod):
|
||||
CONFIG = {
|
||||
"moving_forward" : True,
|
||||
}
|
||||
def __init__(self, car, target_point, **kwargs):
|
||||
ApplyMethod.__init__(self, car.move_to, target_point, **kwargs)
|
||||
displacement = self.target_mobject.get_right()-self.starting_mobject.get_right()
|
||||
distance = np.linalg.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
|
||||
### Cards ###
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
ApplyMethod.update_mobject(self, alpha)
|
||||
if alpha == 0:
|
||||
return
|
||||
radians = alpha*self.total_tire_radians
|
||||
for tire in self.mobject.get_tires():
|
||||
tire.rotate_in_place(radians)
|
||||
class DeckOfCards(VGroup):
|
||||
def __init__(self, **kwargs):
|
||||
possible_values = map(str, range(1, 11)) + ["J", "Q", "K"]
|
||||
possible_suits = ["hearts", "diamonds", "spades", "clubs"]
|
||||
VGroup.__init__(self, *[
|
||||
PlayingCard(value = value, suit = suit, **kwargs)
|
||||
for value in possible_values
|
||||
for suit in possible_suits
|
||||
])
|
||||
|
||||
#TODO: Where should this live?
|
||||
class Broadcast(LaggedStart):
|
||||
class PlayingCard(VGroup):
|
||||
CONFIG = {
|
||||
"small_radius" : 0.0,
|
||||
"big_radius" : 5,
|
||||
"n_circles" : 5,
|
||||
"start_stroke_width" : 8,
|
||||
"color" : WHITE,
|
||||
"remover" : True,
|
||||
"lag_ratio" : 0.7,
|
||||
"run_time" : 3,
|
||||
"remover" : True,
|
||||
"value" : None,
|
||||
"suit" : None,
|
||||
"key" : None, ##String like "8H" or "KS"
|
||||
"height" : 2,
|
||||
"height_to_width" : 3.5/2.5,
|
||||
"card_height_to_symbol_height" : 7,
|
||||
"card_width_to_corner_num_width" : 10,
|
||||
"card_height_to_corner_num_height" : 10,
|
||||
"color" : LIGHT_GREY,
|
||||
"turned_over" : False,
|
||||
"possible_suits" : ["hearts", "diamonds", "spades", "clubs"],
|
||||
"possible_values" : map(str, range(2, 11)) + ["J", "Q", "K", "A"],
|
||||
}
|
||||
def __init__(self, focal_point, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
circles = VGroup()
|
||||
for x in range(self.n_circles):
|
||||
circle = Circle(
|
||||
radius = self.big_radius,
|
||||
|
||||
def __init__(self, key = None, **kwargs):
|
||||
VGroup.__init__(self, key = key, **kwargs)
|
||||
|
||||
def generate_points(self):
|
||||
self.add(Rectangle(
|
||||
height = self.height,
|
||||
width = self.height/self.height_to_width,
|
||||
stroke_color = WHITE,
|
||||
stroke_width = 2,
|
||||
fill_color = self.color,
|
||||
fill_opacity = 1,
|
||||
))
|
||||
if self.turned_over:
|
||||
self.set_fill(DARK_GREY)
|
||||
self.set_stroke(LIGHT_GREY)
|
||||
contents = VectorizedPoint(self.get_center())
|
||||
else:
|
||||
value = self.get_value()
|
||||
symbol = self.get_symbol()
|
||||
design = self.get_design(value, symbol)
|
||||
corner_numbers = self.get_corner_numbers(value, symbol)
|
||||
contents = VGroup(design, corner_numbers)
|
||||
self.design = design
|
||||
self.corner_numbers = corner_numbers
|
||||
self.add(contents)
|
||||
|
||||
def get_value(self):
|
||||
value = self.value
|
||||
if value is None:
|
||||
if self.key is not None:
|
||||
value = self.key[:-1]
|
||||
else:
|
||||
value = random.choice(self.possible_values)
|
||||
value = string.upper(str(value))
|
||||
if value == "1":
|
||||
value = "A"
|
||||
if value not in self.possible_values:
|
||||
raise Exception("Invalid card value")
|
||||
|
||||
face_card_to_value = {
|
||||
"J" : 11,
|
||||
"Q" : 12,
|
||||
"K" : 13,
|
||||
"A" : 14,
|
||||
}
|
||||
try:
|
||||
self.numerical_value = int(value)
|
||||
except:
|
||||
self.numerical_value = face_card_to_value[value]
|
||||
return value
|
||||
|
||||
def get_symbol(self):
|
||||
suit = self.suit
|
||||
if suit is None:
|
||||
if self.key is not None:
|
||||
suit = dict([
|
||||
(string.upper(s[0]), s)
|
||||
for s in self.possible_suits
|
||||
])[string.upper(self.key[-1])]
|
||||
else:
|
||||
suit = random.choice(self.possible_suits)
|
||||
if suit not in self.possible_suits:
|
||||
raise Exception("Invalud suit value")
|
||||
self.suit = suit
|
||||
symbol_height = float(self.height) / self.card_height_to_symbol_height
|
||||
symbol = SuitSymbol(suit, height = symbol_height)
|
||||
return symbol
|
||||
|
||||
def get_design(self, value, symbol):
|
||||
if value == "A":
|
||||
return self.get_ace_design(symbol)
|
||||
if value in map(str, range(2, 11)):
|
||||
return self.get_number_design(value, symbol)
|
||||
else:
|
||||
return self.get_face_card_design(value, symbol)
|
||||
|
||||
def get_ace_design(self, symbol):
|
||||
design = symbol.copy().scale(1.5)
|
||||
design.move_to(self)
|
||||
return design
|
||||
|
||||
def get_number_design(self, value, symbol):
|
||||
num = int(value)
|
||||
n_rows = {
|
||||
2 : 2,
|
||||
3 : 3,
|
||||
4 : 2,
|
||||
5 : 2,
|
||||
6 : 3,
|
||||
7 : 3,
|
||||
8 : 3,
|
||||
9 : 4,
|
||||
10 : 4,
|
||||
}[num]
|
||||
n_cols = 1 if num in [2, 3] else 2
|
||||
insertion_indices = {
|
||||
5 : [0],
|
||||
7 : [0],
|
||||
8 : [0, 1],
|
||||
9 : [1],
|
||||
10 : [0, 2],
|
||||
}.get(num, [])
|
||||
|
||||
top = self.get_top() + symbol.get_height()*DOWN
|
||||
bottom = self.get_bottom() + symbol.get_height()*UP
|
||||
column_points = [
|
||||
interpolate(top, bottom, alpha)
|
||||
for alpha in np.linspace(0, 1, n_rows)
|
||||
]
|
||||
|
||||
design = VGroup(*[
|
||||
symbol.copy().move_to(point)
|
||||
for point in column_points
|
||||
])
|
||||
if n_cols == 2:
|
||||
space = 0.2*self.get_width()
|
||||
column_copy = design.copy().shift(space*RIGHT)
|
||||
design.shift(space*LEFT)
|
||||
design.add(*column_copy)
|
||||
design.add(*[
|
||||
symbol.copy().move_to(
|
||||
center_of_mass(column_points[i:i+2])
|
||||
)
|
||||
for i in insertion_indices
|
||||
])
|
||||
for symbol in design:
|
||||
if symbol.get_center()[1] < self.get_center()[1]:
|
||||
symbol.rotate_in_place(np.pi)
|
||||
return design
|
||||
|
||||
def get_face_card_design(self, value, symbol):
|
||||
from for_3b1b_videos.pi_creature import PiCreature
|
||||
sub_rect = Rectangle(
|
||||
stroke_color = BLACK,
|
||||
stroke_width = 0,
|
||||
fill_opacity = 0,
|
||||
height = 0.9*self.get_height(),
|
||||
width = 0.6*self.get_width(),
|
||||
)
|
||||
circle.move_to(focal_point)
|
||||
circle.save_state()
|
||||
circle.scale_to_fit_width(self.small_radius*2)
|
||||
circle.set_stroke(self.color, self.start_stroke_width)
|
||||
circles.add(circle)
|
||||
LaggedStart.__init__(
|
||||
self, ApplyMethod, circles,
|
||||
lambda c : (c.restore,),
|
||||
**kwargs
|
||||
sub_rect.move_to(self)
|
||||
|
||||
# pi_color = average_color(symbol.get_color(), GREY)
|
||||
pi_color = symbol.get_color()
|
||||
pi_mode = {
|
||||
"J" : "plain",
|
||||
"Q" : "thinking",
|
||||
"K" : "hooray"
|
||||
}[value]
|
||||
pi_creature = PiCreature(
|
||||
mode = pi_mode,
|
||||
color = pi_color,
|
||||
)
|
||||
pi_creature.scale_to_fit_width(0.8*sub_rect.get_width())
|
||||
if value in ["Q", "K"]:
|
||||
prefix = "king" if value == "K" else "queen"
|
||||
crown = SVGMobject(file_name = prefix + "_crown")
|
||||
crown.set_stroke(width = 0)
|
||||
crown.set_fill(YELLOW, 1)
|
||||
crown.stretch_to_fit_width(0.5*sub_rect.get_width())
|
||||
crown.stretch_to_fit_height(0.17*sub_rect.get_height())
|
||||
crown.move_to(pi_creature.eyes.get_center(), DOWN)
|
||||
pi_creature.add_to_back(crown)
|
||||
to_top_buff = 0
|
||||
else:
|
||||
to_top_buff = SMALL_BUFF*sub_rect.get_height()
|
||||
pi_creature.next_to(sub_rect.get_top(), DOWN, to_top_buff)
|
||||
# pi_creature.shift(0.05*sub_rect.get_width()*RIGHT)
|
||||
|
||||
pi_copy = pi_creature.copy()
|
||||
pi_copy.rotate(np.pi, about_point = sub_rect.get_center())
|
||||
|
||||
return VGroup(sub_rect, pi_creature, pi_copy)
|
||||
|
||||
def get_corner_numbers(self, value, symbol):
|
||||
value_mob = TextMobject(value)
|
||||
width = self.get_width()/self.card_width_to_corner_num_width
|
||||
height = self.get_height()/self.card_height_to_corner_num_height
|
||||
value_mob.scale_to_fit_width(width)
|
||||
value_mob.stretch_to_fit_height(height)
|
||||
value_mob.next_to(
|
||||
self.get_corner(UP+LEFT), DOWN+RIGHT,
|
||||
buff = MED_LARGE_BUFF*width
|
||||
)
|
||||
value_mob.set_color(symbol.get_color())
|
||||
corner_symbol = symbol.copy()
|
||||
corner_symbol.scale_to_fit_width(width)
|
||||
corner_symbol.next_to(
|
||||
value_mob, DOWN,
|
||||
buff = MED_SMALL_BUFF*width
|
||||
)
|
||||
corner_group = VGroup(value_mob, corner_symbol)
|
||||
opposite_corner_group = corner_group.copy()
|
||||
opposite_corner_group.rotate(
|
||||
np.pi, about_point = self.get_center()
|
||||
)
|
||||
|
||||
class BraceLabel(VMobject):
|
||||
return VGroup(corner_group, opposite_corner_group)
|
||||
|
||||
class SuitSymbol(SVGMobject):
|
||||
CONFIG = {
|
||||
"label_constructor" : TexMobject,
|
||||
"label_scale" : 1,
|
||||
"height" : 0.5,
|
||||
"fill_opacity" : 1,
|
||||
"stroke_width" : 0,
|
||||
"red" : "#D02028",
|
||||
"black" : BLACK,
|
||||
}
|
||||
def __init__(self, obj, text, brace_direction = DOWN, **kwargs):
|
||||
VMobject.__init__(self, **kwargs)
|
||||
self.brace_direction = brace_direction
|
||||
if isinstance(obj, list): obj = VMobject(*obj)
|
||||
self.brace = Brace(obj, brace_direction, **kwargs)
|
||||
|
||||
if isinstance(text, tuple) or isinstance(text, list):
|
||||
self.label = self.label_constructor(*text, **kwargs)
|
||||
else: self.label = self.label_constructor(str(text))
|
||||
if self.label_scale != 1: self.label.scale(self.label_scale)
|
||||
|
||||
self.brace.put_at_tip(self.label)
|
||||
self.submobjects = [self.brace, self.label]
|
||||
|
||||
def creation_anim(self, label_anim = FadeIn, brace_anim = GrowFromCenter):
|
||||
return AnimationGroup(brace_anim(self.brace), label_anim(self.label))
|
||||
|
||||
def shift_brace(self, obj, **kwargs):
|
||||
if isinstance(obj, list): obj = VMobject(*obj)
|
||||
self.brace = Brace(obj, self.brace_direction, **kwargs)
|
||||
self.brace.put_at_tip(self.label)
|
||||
self.submobjects[0] = self.brace
|
||||
return self
|
||||
|
||||
def change_label(self, *text, **kwargs):
|
||||
self.label = self.label_constructor(*text, **kwargs)
|
||||
if self.label_scale != 1: self.label.scale(self.label_scale)
|
||||
|
||||
self.brace.put_at_tip(self.label)
|
||||
self.submobjects[1] = self.label
|
||||
return self
|
||||
|
||||
def change_brace_label(self, obj, *text):
|
||||
self.shift_brace(obj)
|
||||
self.change_label(*text)
|
||||
return self
|
||||
|
||||
def copy(self):
|
||||
copy_mobject = copy.copy(self)
|
||||
copy_mobject.brace = self.brace.copy()
|
||||
copy_mobject.label = self.label.copy()
|
||||
copy_mobject.submobjects = [copy_mobject.brace, copy_mobject.label]
|
||||
|
||||
return copy_mobject
|
||||
|
||||
class BraceText(BraceLabel):
|
||||
CONFIG = {
|
||||
"label_constructor" : TextMobject
|
||||
def __init__(self, suit_name, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
suits_to_colors = {
|
||||
"hearts" : self.red,
|
||||
"diamonds" : self.red,
|
||||
"spades" : self.black,
|
||||
"clubs" : self.black,
|
||||
}
|
||||
if suit_name not in suits_to_colors:
|
||||
raise Exception("Invalid suit name")
|
||||
SVGMobject.__init__(self, file_name = suit_name, **kwargs)
|
||||
|
||||
color = suits_to_colors[suit_name]
|
||||
self.set_stroke(width = 0)
|
||||
self.set_fill(color, 1)
|
||||
self.scale_to_fit_height(self.height)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class DashedMobject(VMobject):
|
||||
CONFIG = {
|
||||
"dashes_num" : 15,
|
||||
"spacing" : 0.5,
|
||||
"color" : WHITE
|
||||
}
|
||||
def __init__(self, mob, **kwargs):
|
||||
digest_locals(self)
|
||||
VMobject.__init__(self, **kwargs)
|
||||
|
||||
buff = float(self.spacing) / self.dashes_num
|
||||
|
||||
for i in range(self.dashes_num):
|
||||
a = ((1+buff) * i)/self.dashes_num
|
||||
b = 1-((1+buff) * (self.dashes_num-1-i)) / self.dashes_num
|
||||
dash = VMobject(color = self.color)
|
||||
dash.pointwise_become_partial(mob, a, b)
|
||||
self.submobjects.append(dash)
|
|
@ -1,14 +1,18 @@
|
|||
from xml.dom import minidom
|
||||
import itertools as it
|
||||
import re
|
||||
import warnings
|
||||
import string
|
||||
import warnings
|
||||
|
||||
from xml.dom import minidom
|
||||
|
||||
from constants import *
|
||||
from vectorized_mobject import VMobject, VGroup
|
||||
from topics.geometry import Rectangle, Circle
|
||||
from mobject.geometry import Circle
|
||||
from mobject.geometry import Rectangle
|
||||
from utils.bezier import is_closed
|
||||
from utils.config_ops import digest_config, digest_locals
|
||||
from utils.config_ops import digest_config
|
||||
from utils.config_ops import digest_locals
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
|
||||
def string_to_numbers(num_string):
|
||||
num_string = num_string.replace("-",",-")
|
||||
|
@ -227,8 +231,6 @@ class SVGMobject(VMobject):
|
|||
if self.width is not None:
|
||||
self.scale_to_fit_width(self.width)
|
||||
|
||||
|
||||
|
||||
class VMobjectFromSVGPathstring(VMobject):
|
||||
def __init__(self, path_string, **kwargs):
|
||||
digest_locals(self)
|
|
@ -1,13 +1,16 @@
|
|||
from constants import *
|
||||
|
||||
from vectorized_mobject import VMobject, VGroup, VectorizedPoint
|
||||
from svg_mobject import SVGMobject, VMobjectFromSVGPathstring
|
||||
from topics.geometry import BackgroundRectangle
|
||||
from svg_mobject import SVGMobject
|
||||
from svg_mobject import VMobjectFromSVGPathstring
|
||||
from mobject.shape_matchers import BackgroundRectangle
|
||||
from utils.config_ops import digest_config
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.types.vectorized_mobject import VectorizedPoint
|
||||
|
||||
import collections
|
||||
import sys
|
||||
import operator as op
|
||||
import sys
|
||||
|
||||
TEX_MOB_SCALE_FACTOR = 0.05
|
||||
|
||||
|
@ -228,68 +231,6 @@ class TextMobject(TexMobject):
|
|||
"alignment" : "\\centering",
|
||||
}
|
||||
|
||||
class Brace(TexMobject):
|
||||
CONFIG = {
|
||||
"buff" : 0.2,
|
||||
"width_multiplier" : 2,
|
||||
"max_num_quads" : 15,
|
||||
"min_num_quads" : 0,
|
||||
}
|
||||
def __init__(self, mobject, direction = DOWN, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
angle = -np.arctan2(*direction[:2]) + np.pi
|
||||
mobject.rotate(-angle, about_point = ORIGIN)
|
||||
left = mobject.get_corner(DOWN+LEFT)
|
||||
right = mobject.get_corner(DOWN+RIGHT)
|
||||
target_width = right[0]-left[0]
|
||||
|
||||
## Adding int(target_width) qquads gives approximately the right width
|
||||
num_quads = np.clip(
|
||||
int(self.width_multiplier*target_width),
|
||||
self.min_num_quads, self.max_num_quads
|
||||
)
|
||||
tex_string = "\\underbrace{%s}"%(num_quads*"\\qquad")
|
||||
TexMobject.__init__(self, tex_string, **kwargs)
|
||||
self.tip_point_index = np.argmin(self.get_all_points()[:,1])
|
||||
self.stretch_to_fit_width(target_width)
|
||||
self.shift(left - self.get_corner(UP+LEFT) + self.buff*DOWN)
|
||||
for mob in mobject, self:
|
||||
mob.rotate(angle, about_point = ORIGIN)
|
||||
|
||||
def put_at_tip(self, mob, use_next_to = True, **kwargs):
|
||||
if use_next_to:
|
||||
mob.next_to(
|
||||
self.get_tip(),
|
||||
np.round(self.get_direction()),
|
||||
**kwargs
|
||||
)
|
||||
else:
|
||||
mob.move_to(self.get_tip())
|
||||
buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_MOBJECT_BUFFER)
|
||||
shift_distance = mob.get_width()/2.0+buff
|
||||
mob.shift(self.get_direction()*shift_distance)
|
||||
return self
|
||||
|
||||
def get_text(self, *text, **kwargs):
|
||||
text_mob = TextMobject(*text)
|
||||
self.put_at_tip(text_mob, **kwargs)
|
||||
return text_mob
|
||||
|
||||
def get_tex(self, *tex, **kwargs):
|
||||
tex_mob = TexMobject(*tex)
|
||||
self.put_at_tip(tex_mob, **kwargs)
|
||||
return tex_mob
|
||||
|
||||
def get_tip(self):
|
||||
# Very specific to the LaTeX representation
|
||||
# of a brace, but it's the only way I can think
|
||||
# of to get the tip regardless of orientation.
|
||||
return self.get_all_points()[self.tip_point_index]
|
||||
|
||||
def get_direction(self):
|
||||
vect = self.get_tip() - self.get_center()
|
||||
return vect/np.linalg.norm(vect)
|
||||
|
||||
class BulletedList(TextMobject):
|
||||
CONFIG = {
|
||||
"buff" : MED_LARGE_BUFF,
|
75
mobject/three_dimensions.py
Normal file
75
mobject/three_dimensions.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.geometry import Square
|
||||
|
||||
from utils.space_ops import z_to_vector
|
||||
|
||||
##############
|
||||
|
||||
def should_shade_in_3d(mobject):
|
||||
return hasattr(mobject, "shade_in_3d") and mobject.shade_in_3d
|
||||
|
||||
def shade_in_3d(mobject):
|
||||
for submob in mobject.submobject_family():
|
||||
submob.shade_in_3d = True
|
||||
|
||||
def turn_off_3d_shading(mobject):
|
||||
for submob in mobject.submobject_family():
|
||||
submob.shade_in_3d = False
|
||||
|
||||
class ThreeDMobject(VMobject):
|
||||
def __init__(self, *args, **kwargs):
|
||||
VMobject.__init__(self, *args, **kwargs)
|
||||
shade_in_3d(self)
|
||||
|
||||
class Cube(ThreeDMobject):
|
||||
CONFIG = {
|
||||
"fill_opacity" : 0.75,
|
||||
"fill_color" : BLUE,
|
||||
"stroke_width" : 0,
|
||||
"propagate_style_to_family" : True,
|
||||
"side_length" : 2,
|
||||
}
|
||||
def generate_points(self):
|
||||
for vect in IN, OUT, LEFT, RIGHT, UP, DOWN:
|
||||
face = Square(side_length = self.side_length)
|
||||
face.shift(self.side_length*OUT/2.0)
|
||||
face.apply_function(lambda p : np.dot(p, z_to_vector(vect).T))
|
||||
|
||||
self.add(face)
|
||||
|
||||
class Prism(Cube):
|
||||
CONFIG = {
|
||||
"dimensions" : [3, 2, 1]
|
||||
}
|
||||
def generate_points(self):
|
||||
Cube.generate_points(self)
|
||||
for dim, value in enumerate(self.dimensions):
|
||||
self.rescale_to_fit(value, dim, stretch = True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
0
mobject/types/__init__.py
Normal file
0
mobject/types/__init__.py
Normal file
|
@ -1,12 +1,15 @@
|
|||
import numpy as np
|
||||
from __future__ import absolute_import
|
||||
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
from random import random
|
||||
|
||||
from constants import *
|
||||
from .mobject import Mobject
|
||||
from point_cloud_mobject import PMobject
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
from utils.bezier import interpolate
|
||||
from utils.color import color_to_int_rgb
|
||||
from utils.color import interpolate_color
|
|
@ -1,9 +1,13 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
from constants import *
|
||||
from .mobject import Mobject
|
||||
from utils.bezier import interpolate
|
||||
from utils.color import color_to_rgb, color_to_rgba, rgba_to_color
|
||||
from utils.color import color_gradient
|
||||
from utils.color import color_to_rgb
|
||||
from utils.color import color_to_rgba
|
||||
from utils.color import interpolate_color
|
||||
from utils.color import rgba_to_color
|
||||
from utils.config_ops import digest_config
|
||||
from utils.iterables import stretch_array_to_length
|
||||
|
|
@ -1,10 +1,16 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from colour import Color
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
from constants import *
|
||||
from .mobject import Mobject
|
||||
from utils.bezier import bezier, partial_bezier_points
|
||||
from utils.bezier import interpolate, get_smooth_handle_points, is_closed
|
||||
from utils.bezier import bezier
|
||||
from utils.bezier import get_smooth_handle_points
|
||||
from utils.bezier import interpolate
|
||||
from utils.bezier import is_closed
|
||||
from utils.bezier import partial_bezier_points
|
||||
from utils.color import color_to_rgb
|
||||
from utils.color import interpolate_color
|
||||
from utils.iterables import make_even
|
||||
|
@ -487,3 +493,21 @@ class VectorizedPoint(VMobject):
|
|||
def set_location(self,new_loc):
|
||||
self.set_points(np.array([new_loc]))
|
||||
|
||||
class DashedMobject(VMobject):
|
||||
CONFIG = {
|
||||
"dashes_num" : 15,
|
||||
"spacing" : 0.5,
|
||||
"color" : WHITE
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
VMobject.__init__(self, **kwargs)
|
||||
|
||||
buff = float(self.spacing) / self.dashes_num
|
||||
|
||||
for i in range(self.dashes_num):
|
||||
a = ((1+buff) * i)/self.dashes_num
|
||||
b = 1-((1+buff) * (self.dashes_num-1-i)) / self.dashes_num
|
||||
dash = VMobject(color = self.color)
|
||||
dash.pointwise_become_partial(mobject, a, b)
|
||||
self.submobjects.append(dash)
|
||||
|
43
mobject/value_tracker.py
Normal file
43
mobject/value_tracker.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.types.vectorized_mobject import VectorizedPoint
|
||||
|
||||
# TODO: Rather than using VectorizedPoint, there should be some UndisplayedPointSet type
|
||||
|
||||
class ValueTracker(VectorizedPoint):
|
||||
"""
|
||||
Note meant to be displayed. Instead the position encodes some
|
||||
number, often one which another animation or continual_animation
|
||||
uses for its update function, and by treating it as a mobject it can
|
||||
still be animated and manipulated just like anything else.
|
||||
"""
|
||||
def __init__(self, value = 0, **kwargs):
|
||||
VectorizedPoint.__init__(self, **kwargs)
|
||||
self.set_value(value)
|
||||
|
||||
def get_value(self):
|
||||
return self.get_center()[0]
|
||||
|
||||
def set_value(self, value):
|
||||
self.move_to(value*RIGHT)
|
||||
return self
|
||||
|
||||
def increment_value(self, d_value):
|
||||
self.set_value(self.get_value() + d_value)
|
||||
|
||||
class ExponentialValueTracker(ValueTracker):
|
||||
"""
|
||||
Operates just like ValueTracker, except it encodes the value as the
|
||||
exponential of a position coordinate, which changes how interpolation
|
||||
behaves
|
||||
"""
|
||||
def get_value(self):
|
||||
return np.exp(self.get_center()[0])
|
||||
|
||||
def set_value(self, value):
|
||||
self.move_to(np.log(value)*RIGHT)
|
||||
return self
|
|
@ -1146,7 +1146,7 @@ class TwoDScreenInOurThreeDWorld(AltTeacherStudentsScene, ThreeDScene):
|
|||
run_time = 4,
|
||||
added_anims = [MoveToTarget(everything, run_time = 4)],
|
||||
)
|
||||
self.add(AmbientRotation(everything, axis = UP, rate = 3*DEGREES))
|
||||
self.add(ContinualRotation(everything, axis = UP, rate = 3*DEGREES))
|
||||
self.wait(10)
|
||||
|
||||
class EveryOutputPointHasAColor(ColorMappedObjectsScene):
|
||||
|
@ -3188,7 +3188,7 @@ class PatreonScroll(Scene):
|
|||
|
||||
# patorons = patrons[:10] ##TO remove
|
||||
|
||||
scroll = AmbientMovement(patrons, direction = UP, rate = 1)
|
||||
scroll = ContinualMovement(patrons, direction = UP, rate = 1)
|
||||
def patrons_opacity_update(patrons):
|
||||
for patron in patrons:
|
||||
y = patron.get_center()[1]
|
||||
|
|
|
@ -1,31 +1,8 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from constants import *
|
||||
from big_ol_pile_of_manim_imports import *
|
||||
|
||||
from mobject.tex_mobject import TexMobject
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.image_mobject import ImageMobject
|
||||
from mobject.vectorized_mobject import *
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.transform import *
|
||||
from animation.simple_animations import *
|
||||
from animation.compositions import *
|
||||
from animation.continual_animation import *
|
||||
|
||||
from animation.playground import *
|
||||
from topics.geometry import *
|
||||
from topics.characters import *
|
||||
from topics.functions import *
|
||||
from topics.number_line import *
|
||||
from topics.numerals import *
|
||||
from scene.scene import Scene
|
||||
from camera.camera import Camera
|
||||
from mobject.svg_mobject import *
|
||||
from mobject.tex_mobject import *
|
||||
from topics.three_dimensions import *
|
||||
|
||||
from topics.light import *
|
||||
from once_useful_constructs.light import *
|
||||
|
||||
import types
|
||||
import functools
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from big_ol_pile_of_manim_imports import *
|
||||
from once_useful_constructs.light import *
|
||||
|
||||
import types
|
||||
import functools
|
||||
|
@ -3847,7 +3848,7 @@ class ThinkBackToHowAmazingThisIs(ThreeDScene):
|
|||
for n in range(0, self.max_shown_n, 2)
|
||||
])
|
||||
|
||||
zoom_out = AmbientMovement(
|
||||
zoom_out = ContinualMovement(
|
||||
self.camera.rotation_mobject,
|
||||
direction = OUT, rate = 0.4
|
||||
)
|
||||
|
|
|
@ -2874,7 +2874,7 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot):
|
|||
self.wait()
|
||||
|
||||
ghost_dot.move_to(ORIGIN)
|
||||
ambient_ghost_dot_movement = AmbientMovement(
|
||||
ambient_ghost_dot_movement = ContinualMovement(
|
||||
ghost_dot, rate = TAU
|
||||
)
|
||||
self.add(ambient_ghost_dot_movement)
|
||||
|
@ -2896,7 +2896,7 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot):
|
|||
)
|
||||
)
|
||||
ghost_dot.move_to(ORIGIN)
|
||||
ambient_ghost_dot_movement = AmbientMovement(
|
||||
ambient_ghost_dot_movement = ContinualMovement(
|
||||
ghost_dot, rate = 0.1*TAU
|
||||
)
|
||||
self.add(ambient_ghost_dot_movement)
|
||||
|
@ -4089,7 +4089,6 @@ class SubscribeOrBinge(PiCreatureScene):
|
|||
)
|
||||
)
|
||||
|
||||
|
||||
class CloseWithAPuzzle(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
self.teacher_says("Close with a puzzle!", run_time = 1)
|
||||
|
|
|
@ -288,7 +288,7 @@ class AboutToyPuzzles(UtilitiesPuzzleScene, TeacherStudentsScene, ThreeDScene):
|
|||
eulers.get_bottom(),
|
||||
color = WHITE
|
||||
)
|
||||
self.add(AmbientRotation(cube, axis = UP))
|
||||
self.add(ContinualRotation(cube, axis = UP))
|
||||
self.play(
|
||||
GrowArrow(arrow_to_eulers),
|
||||
Write(eulers),
|
||||
|
@ -1884,7 +1884,7 @@ class EulersFormulaForGeneralPlanarGraph(LightUpNodes, ThreeDScene):
|
|||
self.play(FadeOut(self.vertices))
|
||||
self.play(ReplacementTransform(regions, cube, run_time = 2))
|
||||
cube.sort_submobjects(lambda p : -p[2])
|
||||
self.add(AmbientRotation(cube, axis = UP, in_place = False))
|
||||
self.add(ContinualRotation(cube, axis = UP, in_place = False))
|
||||
self.wait(3)
|
||||
self.play(
|
||||
FadeOut(self.top_formula),
|
||||
|
|
|
@ -7,34 +7,7 @@ import os.path
|
|||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
from constants import *
|
||||
|
||||
from mobject.tex_mobject import TexMobject
|
||||
from mobject.mobject import Mobject, Group
|
||||
from mobject.image_mobject import ImageMobject
|
||||
from mobject.vectorized_mobject import *
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.transform import *
|
||||
from animation.simple_animations import *
|
||||
from animation.compositions import *
|
||||
from animation.playground import *
|
||||
from animation.continual_animation import *
|
||||
from topics.geometry import *
|
||||
from topics.characters import *
|
||||
from topics.functions import *
|
||||
from topics.fractals import *
|
||||
from topics.number_line import *
|
||||
from topics.combinatorics import *
|
||||
from topics.numerals import *
|
||||
from topics.three_dimensions import *
|
||||
from topics.objects import *
|
||||
from topics.probability import *
|
||||
from topics.complex_numbers import *
|
||||
from scene.scene import Scene
|
||||
from scene.reconfigurable_scene import ReconfigurableScene
|
||||
from scene.zoomed_scene import *
|
||||
from camera.camera import Camera
|
||||
from mobject.svg_mobject import *
|
||||
from mobject.tex_mobject import *
|
||||
from big_ol_pile_of_manim_imports import *
|
||||
|
||||
from nn.network import *
|
||||
from nn.part1 import *
|
||||
|
|
|
@ -7,40 +7,6 @@ from old_projects.fourier import *
|
|||
FREQUENCY_COLOR = RED
|
||||
USE_ALMOST_FOURIER_BY_DEFAULT = False
|
||||
|
||||
class ValueTracker(VectorizedPoint):
|
||||
"""
|
||||
Note meant to be displayed. Instead the position encodes some
|
||||
number, often one which another animation or continual_animation
|
||||
uses for its update function, and by treating it as a mobject it can
|
||||
still be animated and manipulated just like anything else.
|
||||
"""
|
||||
def __init__(self, value = 0, **kwargs):
|
||||
VectorizedPoint.__init__(self, **kwargs)
|
||||
self.set_value(value)
|
||||
|
||||
def get_value(self):
|
||||
return self.get_center()[0]
|
||||
|
||||
def set_value(self, value):
|
||||
self.move_to(value*RIGHT)
|
||||
return self
|
||||
|
||||
def increment_value(self, d_value):
|
||||
self.set_value(self.get_value() + d_value)
|
||||
|
||||
class ExponentialValueTracker(ValueTracker):
|
||||
"""
|
||||
Operates just like ValueTracker, except it encodes the value as the
|
||||
exponential of a position coordinate, which changes how interpolation
|
||||
behaves
|
||||
"""
|
||||
def get_value(self):
|
||||
return np.exp(self.get_center()[0])
|
||||
|
||||
def set_value(self, value):
|
||||
self.move_to(np.log(value)*RIGHT)
|
||||
return self
|
||||
|
||||
class GaussianDistributionWrapper(Line):
|
||||
"""
|
||||
This is meant to encode a 2d normal distribution as
|
||||
|
@ -608,7 +574,7 @@ class ShowPlan(PiCreatureScene):
|
|||
rect = BackgroundRectangle(wave, fill_opacity = 1)
|
||||
rect.stretch(2, 1)
|
||||
rect.next_to(wave, LEFT, buff = 0)
|
||||
wave_shift = AmbientMovement(
|
||||
wave_shift = ContinualMovement(
|
||||
wave, direction = LEFT, rate = 5
|
||||
)
|
||||
wave_fader = UpdateFromAlphaFunc(
|
||||
|
@ -644,7 +610,7 @@ class ShowPlan(PiCreatureScene):
|
|||
target = Plane()
|
||||
# target.match_height(radar_dish)
|
||||
target.next_to(radar_dish, RIGHT, buff = LARGE_BUFF)
|
||||
target_movement = AmbientMovement(target, direction = RIGHT, rate = 1.25)
|
||||
target_movement = ContinualMovement(target, direction = RIGHT, rate = 1.25)
|
||||
|
||||
pulse = RadarPulse(radar_dish, target)
|
||||
|
||||
|
@ -1730,7 +1696,7 @@ class MentionDopplerRadar(TeacherStudentsScene):
|
|||
plane = Plane()
|
||||
plane.to_edge(RIGHT)
|
||||
plane.align_to(dish)
|
||||
plane_flight = AmbientMovement(
|
||||
plane_flight = ContinualMovement(
|
||||
plane,
|
||||
direction = LEFT,
|
||||
rate = 1,
|
||||
|
@ -1911,7 +1877,7 @@ class IntroduceDopplerRadar(Scene):
|
|||
ShowCreation(sum_graph, run_time = 8, rate_func = None)
|
||||
)
|
||||
pulse = RadarPulse(dish, plane, n_pulse_singletons = 12)
|
||||
plane_flight = AmbientMovement(
|
||||
plane_flight = ContinualMovement(
|
||||
plane, direction = LEFT, rate = 1.5
|
||||
)
|
||||
|
||||
|
@ -2690,7 +2656,7 @@ class AmbiguityInLongEchos(IntroduceDopplerRadar, PiCreatureScene):
|
|||
object_velocities = self.object_velocities
|
||||
|
||||
movements = self.object_movements = [
|
||||
AmbientMovement(
|
||||
ContinualMovement(
|
||||
obj,
|
||||
direction = v/np.linalg.norm(v),
|
||||
rate = np.linalg.norm(v)
|
||||
|
@ -3366,7 +3332,7 @@ class SortOfDopplerEffect(PiCreatureScene):
|
|||
t_tracker = VectorizedPoint()
|
||||
#x-coordinate gives wave number
|
||||
k_tracker = VectorizedPoint(2*RIGHT)
|
||||
tk_movement = AmbientMovement(t_tracker, direction = RIGHT, rate = 1)
|
||||
tk_movement = ContinualMovement(t_tracker, direction = RIGHT, rate = 1)
|
||||
def get_wave():
|
||||
t = t_tracker.get_center()[0]
|
||||
k = k_tracker.get_center()[0]
|
||||
|
@ -3399,7 +3365,7 @@ class SortOfDopplerEffect(PiCreatureScene):
|
|||
|
||||
rect = ScreenRectangle(height = 2)
|
||||
rect.to_edge(RIGHT)
|
||||
rect_movement = AmbientMovement(rect, direction = LEFT, rate = 1)
|
||||
rect_movement = ContinualMovement(rect, direction = LEFT, rate = 1)
|
||||
|
||||
randy = self.pi_creature
|
||||
randy_look_at = ContinualUpdateFromFunc(
|
||||
|
@ -3523,7 +3489,7 @@ class HangingWeightsScene(MovingCameraScene):
|
|||
|
||||
k_tracker = self.k_tracker = VectorizedPoint()
|
||||
t_tracker = self.t_tracker = VectorizedPoint()
|
||||
self.t_tracker_walk = AmbientMovement(t_tracker, direction = RIGHT, rate = 1)
|
||||
self.t_tracker_walk = ContinualMovement(t_tracker, direction = RIGHT, rate = 1)
|
||||
equilibrium_height = springs.get_height()
|
||||
def update_springs(springs):
|
||||
for spring in springs:
|
||||
|
@ -3637,7 +3603,7 @@ class HangingWeightsScene(MovingCameraScene):
|
|||
|
||||
def moving_reference_frame(self):
|
||||
rect = ScreenRectangle(height = 2.1*FRAME_Y_RADIUS)
|
||||
rect_movement = AmbientMovement(rect, direction = LEFT, rate = 2)
|
||||
rect_movement = ContinualMovement(rect, direction = LEFT, rate = 2)
|
||||
camera_frame = self.camera_frame
|
||||
|
||||
self.add(rect)
|
||||
|
@ -4371,7 +4337,7 @@ class ThinkOfHeisenbergUncertainty(PiCreatureScene):
|
|||
self.add()
|
||||
freq = 1
|
||||
continual_anims = [
|
||||
AmbientMovement(time_tracker, direction = RIGHT, rate = 1),
|
||||
ContinualMovement(time_tracker, direction = RIGHT, rate = 1),
|
||||
ContinualUpdateFromFunc(
|
||||
dot_gdw,
|
||||
lambda d : d.scale_to_fit_width(
|
||||
|
|
1
once_useful_constructs/NOTE.md
Normal file
1
once_useful_constructs/NOTE.md
Normal file
|
@ -0,0 +1 @@
|
|||
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.
|
0
once_useful_constructs/__init__.py
Normal file
0
once_useful_constructs/__init__.py
Normal file
|
@ -1,10 +1,10 @@
|
|||
import numpy as np
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
from scene.scene import Scene
|
||||
from animation.animation import Animation
|
||||
from mobject.tex_mobject import TexMobject
|
||||
from constants import *
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from scene.scene import Scene
|
||||
|
||||
class RearrangeEquation(Scene):
|
||||
def construct(
|
|
@ -1,7 +1,7 @@
|
|||
from constants import *
|
||||
|
||||
from mobject.vectorized_mobject import VMobject
|
||||
from mobject.tex_mobject import TexMobject
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
|
||||
from scene.scene import Scene
|
||||
from utils.simple_functions import choose
|
|
@ -1,19 +1,12 @@
|
|||
from constants import *
|
||||
|
||||
|
||||
from mobject.vectorized_mobject import VGroup
|
||||
from mobject.tex_mobject import TexMobject, TextMobject
|
||||
from number_line import NumberPlane
|
||||
from animation.animation import Animation
|
||||
from animation.transform import ApplyPointwiseFunction, MoveToTarget
|
||||
from animation.simple_animations import Homotopy, ShowCreation, \
|
||||
SmoothedVectorizedHomotopy
|
||||
from animation.movement import SmoothedVectorizedHomotopy
|
||||
from animation.transform import ApplyPointwiseFunction
|
||||
from animation.transform import MoveToTarget
|
||||
from mobject.coordinate_systems import ComplexPlane
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from scene.scene import Scene
|
||||
from utils.config_ops import instantiate
|
||||
from utils.config_ops import digest_config
|
||||
from utils.paths import path_along_arc
|
||||
from utils.space_ops import complex_to_R3, R3_to_complex
|
||||
|
||||
|
||||
class ComplexTransformationScene(Scene):
|
||||
CONFIG = {
|
||||
|
@ -163,110 +156,6 @@ class ComplexTransformationScene(Scene):
|
|||
*added_anims
|
||||
)
|
||||
|
||||
##### Unsure about what comes under here...
|
||||
|
||||
def complex_string(complex_num):
|
||||
return filter(lambda c : c not in "()", str(complex_num))
|
||||
|
||||
class ComplexPlane(NumberPlane):
|
||||
CONFIG = {
|
||||
"color" : BLUE,
|
||||
"unit_size" : 1,
|
||||
"line_frequency" : 1,
|
||||
"faded_line_frequency" : 0.5,
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
kwargs.update({
|
||||
"x_unit_size" : self.unit_size,
|
||||
"y_unit_size" : self.unit_size,
|
||||
"x_line_frequency" : self.line_frequency,
|
||||
"x_faded_line_frequency" : self.faded_line_frequency,
|
||||
"y_line_frequency" : self.line_frequency,
|
||||
"y_faded_line_frequency" : self.faded_line_frequency,
|
||||
})
|
||||
NumberPlane.__init__(self, **kwargs)
|
||||
|
||||
def number_to_point(self, number):
|
||||
number = complex(number)
|
||||
return self.coords_to_point(number.real, number.imag)
|
||||
|
||||
def point_to_number(self, point):
|
||||
x, y = self.point_to_coords(point)
|
||||
return complex(x, y)
|
||||
|
||||
def get_coordinate_labels(self, *numbers):
|
||||
# TODO: Should merge this with the code from NumberPlane.get_coordinate_labels
|
||||
|
||||
result = VGroup()
|
||||
nudge = 0.1*(DOWN+RIGHT)
|
||||
if len(numbers) == 0:
|
||||
numbers = range(-int(self.x_radius), int(self.x_radius)+1)
|
||||
numbers += [
|
||||
complex(0, y)
|
||||
for y in range(-int(self.y_radius), int(self.y_radius)+1)
|
||||
]
|
||||
for number in numbers:
|
||||
if number == complex(0, 0):
|
||||
continue
|
||||
point = self.number_to_point(number)
|
||||
num_str = str(number).replace("j", "i")
|
||||
if num_str.startswith("0"):
|
||||
num_str = "0"
|
||||
elif num_str in ["1i", "-1i"]:
|
||||
num_str = num_str.replace("1", "")
|
||||
num_mob = TexMobject(num_str)
|
||||
num_mob.add_background_rectangle()
|
||||
num_mob.scale_to_fit_height(self.written_coordinate_height)
|
||||
num_mob.next_to(point, DOWN+LEFT, SMALL_BUFF)
|
||||
result.add(num_mob)
|
||||
self.coordinate_labels = result
|
||||
return result
|
||||
|
||||
def add_coordinates(self, *numbers):
|
||||
self.add(*self.get_coordinate_labels(*numbers))
|
||||
return self
|
||||
|
||||
def add_spider_web(self, circle_freq = 1, angle_freq = np.pi/6):
|
||||
# This code no longer works because it has this reference to self.fade_factor
|
||||
# which is never initialized. Shall we delete this little-used function entirely?
|
||||
self.fade(self.fade_factor)
|
||||
config = {
|
||||
"color" : self.color,
|
||||
"density" : self.density,
|
||||
}
|
||||
for radius in np.arange(circle_freq, FRAME_X_RADIUS, circle_freq):
|
||||
self.add(Circle(radius = radius, **config))
|
||||
for angle in np.arange(0, 2*np.pi, angle_freq):
|
||||
end_point = np.cos(angle)*RIGHT + np.sin(angle)*UP
|
||||
end_point *= FRAME_X_RADIUS
|
||||
self.add(Line(ORIGIN, end_point, **config))
|
||||
return self
|
||||
|
||||
class ComplexFunction(ApplyPointwiseFunction):
|
||||
def __init__(self, function, mobject = ComplexPlane, **kwargs):
|
||||
if "path_func" not in kwargs:
|
||||
self.path_func = path_along_arc(
|
||||
np.log(function(complex(1))).imag
|
||||
)
|
||||
ApplyPointwiseFunction.__init__(
|
||||
self,
|
||||
lambda (x, y, z) : complex_to_R3(function(complex(x, y))),
|
||||
instantiate(mobject),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
class ComplexHomotopy(Homotopy):
|
||||
def __init__(self, complex_homotopy, mobject = ComplexPlane, **kwargs):
|
||||
"""
|
||||
Complex Hootopy a function Cx[0, 1] to C
|
||||
"""
|
||||
def homotopy(event):
|
||||
x, y, z, t = event
|
||||
c = complex_homotopy((complex(x, y), t))
|
||||
return (c.real, c.imag, z)
|
||||
Homotopy.__init__(self, homotopy, mobject, *args, **kwargs)
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,13 +1,18 @@
|
|||
from constants import *
|
||||
|
||||
from mobject.tex_mobject import TexMobject, TextMobject
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.vectorized_mobject import VMobject, VGroup
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.svg.tex_mobject import TextMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
|
||||
from animation.transform import Transform, FadeIn, MoveToTarget
|
||||
from animation.simple_animations import ShowCreation
|
||||
from topics.geometry import Arrow, Circle, Dot
|
||||
# from topics.fractals import *
|
||||
from animation.creation import ShowCreation
|
||||
from animation.creation import FadeIn
|
||||
from animation.transform import MoveToTarget
|
||||
from animation.transform import Transform
|
||||
from mobject.geometry import Arrow
|
||||
from mobject.geometry import Circle
|
||||
from mobject.geometry import Dot
|
||||
from scene.scene import Scene
|
||||
|
||||
|
|
@ -1,18 +1,27 @@
|
|||
# from mobject.mobject import Mobject, Point, Mobject1D
|
||||
from mobject.vectorized_mobject import VMobject, VGroup, VectorizedPoint
|
||||
from scene.scene import Scene
|
||||
from animation.creation import ShowCreation
|
||||
from animation.transform import Transform
|
||||
from animation.simple_animations import ShowCreation
|
||||
from topics.geometry import Line, Polygon, RegularPolygon, Square, Circle
|
||||
from characters import PiCreature, Randolph, get_all_pi_creature_modes
|
||||
from for_3b1b_videos.pi_creature import PiCreature
|
||||
from for_3b1b_videos.pi_creature import Randolph
|
||||
from for_3b1b_videos.pi_creature import get_all_pi_creature_modes
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.types.vectorized_mobject import VectorizedPoint
|
||||
from scene.scene import Scene
|
||||
from mobject.geometry import Circle
|
||||
from mobject.geometry import Line
|
||||
from mobject.geometry import Polygon
|
||||
from mobject.geometry import RegularPolygon
|
||||
from mobject.geometry import Square
|
||||
from utils.bezier import interpolate
|
||||
from utils.color import color_gradient
|
||||
from utils.config_ops import digest_config
|
||||
from utils.space_ops import rotation_matrix, rotate_vector, compass_directions, center_of_mass
|
||||
from utils.space_ops import center_of_mass
|
||||
from utils.space_ops import compass_directions
|
||||
from utils.space_ops import rotate_vector
|
||||
from utils.space_ops import rotation_matrix
|
||||
|
||||
from constants import *
|
||||
|
||||
|
||||
def rotate(points, angle = np.pi, axis = OUT):
|
||||
if axis is None:
|
||||
return points
|
||||
|
@ -61,7 +70,6 @@ def fractalification_iteration(vmobject, dimension = 1.05, num_inserted_anchors_
|
|||
]
|
||||
return vmobject
|
||||
|
||||
|
||||
class SelfSimilarFractal(VMobject):
|
||||
CONFIG = {
|
||||
"order" : 5,
|
||||
|
@ -106,7 +114,6 @@ class SelfSimilarFractal(VMobject):
|
|||
def arrange_subparts(self, *subparts):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
class Sierpinski(SelfSimilarFractal):
|
||||
def get_seed_shape(self):
|
||||
return Polygon(
|
||||
|
@ -118,7 +125,6 @@ class Sierpinski(SelfSimilarFractal):
|
|||
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,
|
||||
|
@ -134,7 +140,6 @@ class DiamondFractal(SelfSimilarFractal):
|
|||
part.next_to(ORIGIN, vect, buff = 0)
|
||||
VGroup(*subparts).rotate(np.pi/4, about_point = ORIGIN)
|
||||
|
||||
|
||||
class PentagonalFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts" : 5,
|
||||
|
@ -171,7 +176,6 @@ class PentagonalPiCreatureFractal(PentagonalFractal):
|
|||
part.rotate(2*np.pi/5, about_point = ORIGIN)
|
||||
PentagonalFractal.arrange_subparts(self, *subparts)
|
||||
|
||||
|
||||
class PiCreatureFractal(VMobject):
|
||||
CONFIG = {
|
||||
"order" : 7,
|
||||
|
@ -231,7 +235,6 @@ class PiCreatureFractal(VMobject):
|
|||
# VMobject.init_colors(self)
|
||||
# self.set_color_by_gradient(*self.colors)
|
||||
|
||||
|
||||
class WonkyHexagonFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts" : 7
|
||||
|
@ -273,8 +276,6 @@ class CircularFractal(SelfSimilarFractal):
|
|||
part.rotate(i*2*np.pi/self.num_subparts, about_point = ORIGIN)
|
||||
self.num_subparts -= 1
|
||||
|
||||
|
||||
|
||||
######## Space filling curves ############
|
||||
|
||||
class JaggedCurvePiece(VMobject):
|
||||
|
@ -285,7 +286,6 @@ class JaggedCurvePiece(VMobject):
|
|||
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,
|
||||
|
@ -324,7 +324,6 @@ class FractalCurve(VMobject):
|
|||
def get_anchor_points(self):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
class LindenmayerCurve(FractalCurve):
|
||||
CONFIG = {
|
||||
"axiom" : "A",
|
||||
|
@ -365,7 +364,6 @@ class LindenmayerCurve(FractalCurve):
|
|||
result.append(curr)
|
||||
return np.array(result) - center_of_mass(result)
|
||||
|
||||
|
||||
class SelfSimilarSpaceFillingCurve(FractalCurve):
|
||||
CONFIG = {
|
||||
"offsets" : [],
|
||||
|
@ -409,8 +407,6 @@ class SelfSimilarSpaceFillingCurve(FractalCurve):
|
|||
def generate_grid(self):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
|
||||
class HilbertCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"offsets" : [
|
||||
|
@ -425,7 +421,6 @@ class HilbertCurve(SelfSimilarSpaceFillingCurve):
|
|||
},
|
||||
}
|
||||
|
||||
|
||||
class HilbertCurve3D(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"offsets" : [
|
||||
|
@ -461,7 +456,6 @@ class HilbertCurve3D(SelfSimilarSpaceFillingCurve):
|
|||
copy += offset*self.radius*self.radius_scale_factor
|
||||
return copy
|
||||
|
||||
|
||||
class PeanoCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"colors" : [PURPLE, TEAL],
|
||||
|
@ -597,7 +591,6 @@ class KochCurve(KochSnowFlake):
|
|||
"axiom" : "A--"
|
||||
}
|
||||
|
||||
|
||||
class QuadraticKoch(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors" : [YELLOW, WHITE, MAROON_B],
|
||||
|
@ -611,7 +604,6 @@ class QuadraticKoch(LindenmayerCurve):
|
|||
"angle" : np.pi/2
|
||||
}
|
||||
|
||||
|
||||
class QuadraticKochIsland(QuadraticKoch):
|
||||
CONFIG = {
|
||||
"axiom" : "A+A+A+A"
|
|
@ -1,6 +1,7 @@
|
|||
import itertools as it
|
||||
import numpy as np
|
||||
import operator as op
|
||||
|
||||
from random import random
|
||||
|
||||
from constants import *
|
||||
|
@ -29,7 +30,6 @@ class Graph():
|
|||
def __str__(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
|
||||
class CubeGraph(Graph):
|
||||
"""
|
||||
5 7
|
||||
|
@ -66,7 +66,6 @@ class CubeGraph(Graph):
|
|||
[4, 6, 7, 5],#By convention, last region will be "outside"
|
||||
]
|
||||
|
||||
|
||||
class SampleGraph(Graph):
|
||||
"""
|
||||
4 2 3 8
|
||||
|
@ -114,7 +113,6 @@ class SampleGraph(Graph):
|
|||
(4, 5, 6, 7, 8, 3, 2),
|
||||
]
|
||||
|
||||
|
||||
class OctohedronGraph(Graph):
|
||||
"""
|
||||
3
|
||||
|
@ -154,7 +152,6 @@ class OctohedronGraph(Graph):
|
|||
(3, 4, 5),
|
||||
]
|
||||
|
||||
|
||||
class CompleteGraph(Graph):
|
||||
def __init__(self, num_vertices, radius = 3):
|
||||
self.num_vertices = num_vertices
|
||||
|
@ -172,7 +169,6 @@ class CompleteGraph(Graph):
|
|||
def __str__(self):
|
||||
return Graph.__str__(self) + str(self.num_vertices)
|
||||
|
||||
|
||||
class GraphScene(Scene):
|
||||
args_list = [
|
||||
(CubeGraph(),),
|
|
@ -1,31 +1,39 @@
|
|||
from constants import *
|
||||
|
||||
from mobject.tex_mobject import TexMobject
|
||||
from mobject.geometry import AnnularSector
|
||||
from mobject.geometry import Arc
|
||||
from mobject.geometry import Annulus
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.vectorized_mobject import *
|
||||
from mobject.svg.svg_mobject import SVGMobject
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.types.vectorized_mobject import VectorizedPoint
|
||||
|
||||
from continual_animation.continual_animation import ContinualAnimation
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.transform import *
|
||||
from animation.simple_animations import *
|
||||
from animation.compositions import *
|
||||
from animation.continual_animation import *
|
||||
from animation.composition import LaggedStart
|
||||
from animation.transform import ApplyMethod
|
||||
from animation.transform import Transform
|
||||
from animation.creation import FadeIn
|
||||
from animation.creation import FadeOut
|
||||
|
||||
from animation.playground import *
|
||||
from topics.geometry import *
|
||||
from topics.functions import *
|
||||
from scene.scene import Scene
|
||||
from camera.camera import Camera
|
||||
from mobject.svg_mobject import *
|
||||
from topics.three_dimensions import *
|
||||
from scene.scene import Scene
|
||||
from camera.three_d_camera import ThreeDCamera
|
||||
from scene.three_d_scene import ThreeDScene
|
||||
|
||||
from utils.space_ops import angle_between
|
||||
from utils.space_ops import angle_between_vectors
|
||||
from utils.space_ops import project_along_vector
|
||||
from utils.space_ops import rotate_vector
|
||||
from utils.space_ops import rotation_matrix
|
||||
from utils.space_ops import z_to_vector
|
||||
|
||||
from scipy.spatial import ConvexHull
|
||||
from traceback import *
|
||||
|
||||
from utils.space_ops import rotation_matrix, z_to_vector
|
||||
from utils.space_ops import rotate_vector, angle_between, angle_between_vectors
|
||||
from utils.space_ops import project_along_vector
|
||||
|
||||
|
||||
LIGHT_COLOR = YELLOW
|
||||
SHADOW_COLOR = BLACK
|
||||
SWITCH_ON_RUN_TIME = 1.5
|
140
once_useful_constructs/matrix_multiplication.py
Normal file
140
once_useful_constructs/matrix_multiplication.py
Normal file
|
@ -0,0 +1,140 @@
|
|||
import numpy as np
|
||||
|
||||
from animation.creation import ShowCreation
|
||||
from animation.transform import ApplyMethod
|
||||
from animation.creation import FadeOut
|
||||
from animation.transform import Transform
|
||||
from mobject.matrix import Matrix
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from scene.scene import Scene
|
||||
from mobject.geometry import Circle
|
||||
from mobject.geometry import Line
|
||||
|
||||
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_submobjects()
|
||||
everything.scale_to_fit_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,
|
||||
submobject_mode = "all_at_once",
|
||||
),
|
||||
*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()
|
|
@ -8,7 +8,8 @@ from utils.iterables import adjacent_pairs
|
|||
|
||||
from constants import *
|
||||
|
||||
#TODO, this whole class should be something vectorized.
|
||||
# Warning: This is all now pretty depricated, and should not be expected to work
|
||||
|
||||
class Region(Mobject):
|
||||
CONFIG = {
|
||||
"display_mode" : "region"
|
|
@ -1,3 +1 @@
|
|||
__all__ = [
|
||||
"scene"
|
||||
]
|
||||
|
||||
|
|
|
@ -1,19 +1,27 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from scene.scene import Scene
|
||||
# from topics.geometry import
|
||||
from mobject.tex_mobject import TexMobject, TextMobject
|
||||
from mobject.vectorized_mobject import VGroup, VectorizedPoint
|
||||
from animation.simple_animations import Write, ShowCreation, UpdateFromAlphaFunc
|
||||
from animation.creation import Write
|
||||
from animation.transform import Transform
|
||||
from topics.number_line import NumberLine
|
||||
from topics.functions import ParametricFunction
|
||||
from topics.geometry import Rectangle, DashedLine, Line
|
||||
from animation.update import UpdateFromAlphaFunc
|
||||
from mobject.functions import ParametricFunction
|
||||
from mobject.geometry import Line
|
||||
from mobject.geometry import Rectangle
|
||||
from mobject.number_line import NumberLine
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.svg.tex_mobject import TextMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VectorizedPoint
|
||||
from utils.bezier import interpolate
|
||||
from utils.color import invert_color
|
||||
from utils.color import color_gradient
|
||||
from utils.color import invert_color
|
||||
from utils.space_ops import angle_of_vector
|
||||
|
||||
# TODO, this should probably reimplemented entirely, especially so as to
|
||||
# better reuse code from mobject/coordinate_systems
|
||||
|
||||
class GraphScene(Scene):
|
||||
CONFIG = {
|
||||
"x_min" : -1,
|
|
@ -1,8 +1,10 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from camera.camera import MovingCamera
|
||||
from .scene import Scene
|
||||
from topics.geometry import ScreenRectangle
|
||||
from scene.scene import Scene
|
||||
from camera.moving_camera import MovingCamera
|
||||
from mobject.frame import ScreenRectangle
|
||||
|
||||
class MovingCameraScene(Scene):
|
||||
def setup(self):
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .scene import Scene
|
||||
from scene.scene import Scene
|
||||
from animation.transform import Transform
|
||||
from mobject.mobject import Mobject
|
||||
|
||||
|
|
146
scene/sample_space_scene.py
Normal file
146
scene/sample_space_scene.py
Normal file
|
@ -0,0 +1,146 @@
|
|||
from constants import *
|
||||
|
||||
from scene.scene import Scene
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.transform import MoveToTarget
|
||||
from animation.transform import Transform
|
||||
from animation.update import UpdateFromFunc
|
||||
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.probability import SampleSpace
|
||||
|
||||
|
||||
class SampleSpaceScene(Scene):
|
||||
def get_sample_space(self, **config):
|
||||
self.sample_space = SampleSpace(**config)
|
||||
return self.sample_space
|
||||
|
||||
def add_sample_space(self, **config):
|
||||
self.add(self.get_sample_space(**config))
|
||||
|
||||
def get_division_change_animations(
|
||||
self, sample_space, parts, p_list,
|
||||
dimension = 1,
|
||||
new_label_kwargs = None,
|
||||
**kwargs
|
||||
):
|
||||
if new_label_kwargs is None:
|
||||
new_label_kwargs = {}
|
||||
anims = []
|
||||
p_list = sample_space.complete_p_list(p_list)
|
||||
space_copy = sample_space.copy()
|
||||
|
||||
vect = DOWN if dimension == 1 else RIGHT
|
||||
parts.generate_target()
|
||||
for part, p in zip(parts.target, p_list):
|
||||
part.replace(space_copy, stretch = True)
|
||||
part.stretch(p, dimension)
|
||||
parts.target.arrange_submobjects(vect, buff = 0)
|
||||
parts.target.move_to(space_copy)
|
||||
anims.append(MoveToTarget(parts))
|
||||
if hasattr(parts, "labels") and parts.labels is not None:
|
||||
label_kwargs = parts.label_kwargs
|
||||
label_kwargs.update(new_label_kwargs)
|
||||
new_braces, new_labels = sample_space.get_subdivision_braces_and_labels(
|
||||
parts.target, **label_kwargs
|
||||
)
|
||||
anims += [
|
||||
Transform(parts.braces, new_braces),
|
||||
Transform(parts.labels, new_labels),
|
||||
]
|
||||
return anims
|
||||
|
||||
def get_horizontal_division_change_animations(self, p_list, **kwargs):
|
||||
assert(hasattr(self.sample_space, "horizontal_parts"))
|
||||
return self.get_division_change_animations(
|
||||
self.sample_space, self.sample_space.horizontal_parts, p_list,
|
||||
dimension = 1,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def get_vertical_division_change_animations(self, p_list, **kwargs):
|
||||
assert(hasattr(self.sample_space, "vertical_parts"))
|
||||
return self.get_division_change_animations(
|
||||
self.sample_space, self.sample_space.vertical_parts, p_list,
|
||||
dimension = 0,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def get_conditional_change_anims(
|
||||
self, sub_sample_space_index, value, post_rects = None,
|
||||
**kwargs
|
||||
):
|
||||
parts = self.sample_space.horizontal_parts
|
||||
sub_sample_space = parts[sub_sample_space_index]
|
||||
anims = self.get_division_change_animations(
|
||||
sub_sample_space, sub_sample_space.vertical_parts, value,
|
||||
dimension = 0,
|
||||
**kwargs
|
||||
)
|
||||
if post_rects is not None:
|
||||
anims += self.get_posterior_rectangle_change_anims(post_rects)
|
||||
return anims
|
||||
|
||||
def get_top_conditional_change_anims(self, *args, **kwargs):
|
||||
return self.get_conditional_change_anims(0, *args, **kwargs)
|
||||
|
||||
def get_bottom_conditional_change_anims(self, *args, **kwargs):
|
||||
return self.get_conditional_change_anims(1, *args, **kwargs)
|
||||
|
||||
def get_prior_rectangles(self):
|
||||
return VGroup(*[
|
||||
self.sample_space.horizontal_parts[i].vertical_parts[0]
|
||||
for i in range(2)
|
||||
])
|
||||
|
||||
def get_posterior_rectangles(self, buff = MED_LARGE_BUFF):
|
||||
prior_rects = self.get_prior_rectangles()
|
||||
areas = [
|
||||
rect.get_width()*rect.get_height()
|
||||
for rect in prior_rects
|
||||
]
|
||||
total_area = sum(areas)
|
||||
total_height = prior_rects.get_height()
|
||||
|
||||
post_rects = prior_rects.copy()
|
||||
for rect, area in zip(post_rects, areas):
|
||||
rect.stretch_to_fit_height(total_height * area/total_area)
|
||||
rect.stretch_to_fit_width(
|
||||
area/rect.get_height()
|
||||
)
|
||||
post_rects.arrange_submobjects(DOWN, buff = 0)
|
||||
post_rects.next_to(
|
||||
self.sample_space, RIGHT, buff
|
||||
)
|
||||
return post_rects
|
||||
|
||||
def get_posterior_rectangle_braces_and_labels(
|
||||
self, post_rects, labels, direction = RIGHT, **kwargs
|
||||
):
|
||||
return self.sample_space.get_subdivision_braces_and_labels(
|
||||
post_rects, labels, direction, **kwargs
|
||||
)
|
||||
|
||||
def update_posterior_braces(self, post_rects):
|
||||
braces = post_rects.braces
|
||||
labels = post_rects.labels
|
||||
for rect, brace, label in zip(post_rects, braces, labels):
|
||||
brace.stretch_to_fit_height(rect.get_height())
|
||||
brace.next_to(rect, RIGHT, SMALL_BUFF)
|
||||
label.next_to(brace, RIGHT, SMALL_BUFF)
|
||||
|
||||
def get_posterior_rectangle_change_anims(self, post_rects):
|
||||
def update_rects(rects):
|
||||
new_rects = self.get_posterior_rectangles()
|
||||
Transform(rects, new_rects).update(1)
|
||||
if hasattr(rects, "braces"):
|
||||
self.update_posterior_braces(rects)
|
||||
return rects
|
||||
|
||||
anims = [UpdateFromFunc(post_rects, update_rects)]
|
||||
if hasattr(post_rects, "braces"):
|
||||
anims += map(Animation, [
|
||||
post_rects.labels, post_rects.braces
|
||||
])
|
||||
return anims
|
|
@ -1,26 +1,26 @@
|
|||
import copy
|
||||
import inspect
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import subprocess as sp
|
||||
import time
|
||||
import warnings
|
||||
|
||||
from PIL import Image
|
||||
from colour import Color
|
||||
import numpy as np
|
||||
import itertools as it
|
||||
import warnings
|
||||
import time
|
||||
import os
|
||||
import shutil
|
||||
import copy
|
||||
from tqdm import tqdm as ProgressDisplay
|
||||
import inspect
|
||||
import subprocess as sp
|
||||
import random
|
||||
|
||||
from constants import *
|
||||
|
||||
from camera.camera import Camera
|
||||
from tk_scene import TkSceneRoot
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.vectorized_mobject import VMobject
|
||||
from animation.animation import Animation
|
||||
from animation.transform import MoveToTarget
|
||||
from animation.continual_animation import ContinualAnimation
|
||||
from camera.camera import Camera
|
||||
from continual_animation.continual_animation import ContinualAnimation
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from utils.iterables import list_update
|
||||
|
||||
from container.container import Container
|
||||
|
@ -538,9 +538,6 @@ class Scene(Container):
|
|||
self.update_frame(dont_update_when_skipping = False)
|
||||
self.get_image().show()
|
||||
|
||||
def preview(self):
|
||||
TkSceneRoot(self)
|
||||
|
||||
def get_image_file_path(self, name = None, dont_update = False):
|
||||
folder = "images"
|
||||
if dont_update:
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import numpy as np
|
||||
from __future__ import absolute_import
|
||||
|
||||
import cv2
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
|
||||
from tqdm import tqdm as show_progress
|
||||
|
||||
from .scene import Scene
|
||||
from scene.scene import Scene
|
||||
|
||||
|
||||
class SceneFromVideo(Scene):
|
||||
|
|
69
scene/three_d_scene.py
Normal file
69
scene/three_d_scene.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from constants import *
|
||||
|
||||
from continual_animation.continual_animation import ContinualMovement
|
||||
from animation.transform import ApplyMethod
|
||||
from camera.three_d_camera import ThreeDCamera
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.three_dimensions import should_shade_in_3d
|
||||
from scene.scene import Scene
|
||||
|
||||
from utils.iterables import list_update
|
||||
|
||||
|
||||
class ThreeDScene(Scene):
|
||||
CONFIG = {
|
||||
"camera_class" : ThreeDCamera,
|
||||
"ambient_camera_rotation" : None,
|
||||
}
|
||||
|
||||
def set_camera_position(self, phi = None, theta = None, distance = None,
|
||||
center_x = None, center_y = None, center_z = None):
|
||||
self.camera.set_position(phi, theta, distance, center_x, center_y, center_z)
|
||||
|
||||
def begin_ambient_camera_rotation(self, rate = 0.01):
|
||||
self.ambient_camera_rotation = ContinualMovement(
|
||||
self.camera.rotation_mobject,
|
||||
direction = UP,
|
||||
rate = rate
|
||||
)
|
||||
self.add(self.ambient_camera_rotation)
|
||||
|
||||
def stop_ambient_camera_rotation(self):
|
||||
if self.ambient_camera_rotation is not None:
|
||||
self.remove(self.ambient_camera_rotation)
|
||||
self.ambient_camera_rotation = None
|
||||
|
||||
def move_camera(
|
||||
self,
|
||||
phi = None, theta = None, distance = None,
|
||||
center_x = None, center_y = None, center_z = None,
|
||||
added_anims = [],
|
||||
**kwargs
|
||||
):
|
||||
target_point = self.camera.get_spherical_coords(phi, theta, distance)
|
||||
movement = ApplyMethod(
|
||||
self.camera.rotation_mobject.move_to,
|
||||
target_point,
|
||||
**kwargs
|
||||
)
|
||||
target_center = self.camera.get_center_of_rotation(center_x, center_y, center_z)
|
||||
movement_center = ApplyMethod(
|
||||
self.camera.moving_center.move_to,
|
||||
target_center,
|
||||
**kwargs
|
||||
)
|
||||
is_camera_rotating = self.ambient_camera_rotation in self.continual_animations
|
||||
if is_camera_rotating:
|
||||
self.remove(self.ambient_camera_rotation)
|
||||
self.play(movement, movement_center, *added_anims)
|
||||
target_point = self.camera.get_spherical_coords(phi, theta, distance)
|
||||
if is_camera_rotating:
|
||||
self.add(self.ambient_camera_rotation)
|
||||
|
||||
def get_moving_mobjects(self, *animations):
|
||||
moving_mobjects = Scene.get_moving_mobjects(self, *animations)
|
||||
if self.camera.rotation_mobject in moving_mobjects:
|
||||
return list_update(self.mobjects, moving_mobjects)
|
||||
return moving_mobjects
|
|
@ -1,44 +0,0 @@
|
|||
import Tkinter
|
||||
from PIL import ImageTk, Image
|
||||
import itertools as it
|
||||
import time
|
||||
|
||||
|
||||
class TkSceneRoot(Tkinter.Tk):
|
||||
def __init__(self, scene):
|
||||
if scene.saved_frames == []:
|
||||
raise Exception(str(scene) + " has no frames!")
|
||||
Tkinter.Tk.__init__(self)
|
||||
|
||||
kwargs = {
|
||||
"height" : scene.camera.pixel_shape[0],
|
||||
"width" : scene.camera.pixel_shape[1],
|
||||
}
|
||||
self.frame = Tkinter.Frame(self, **kwargs)
|
||||
self.frame.pack()
|
||||
self.canvas = Tkinter.Canvas(self.frame, **kwargs)
|
||||
self.canvas.configure(background='black')
|
||||
self.canvas.place(x=0, y=0)
|
||||
|
||||
last_time = time.time()
|
||||
for frame in it.cycle(scene.saved_frames):
|
||||
# try:
|
||||
# self.show_new_image(frame)
|
||||
# except:
|
||||
# break
|
||||
self.show_new_image(frame)
|
||||
sleep_time = scene.frame_duration
|
||||
sleep_time -= time.time() - last_time
|
||||
time.sleep(max(0, sleep_time))
|
||||
last_time = time.time()
|
||||
self.mainloop()
|
||||
|
||||
def show_new_image(self, frame):
|
||||
image = Image.fromarray(frame.astype('uint8')).convert('RGB')
|
||||
photo = ImageTk.PhotoImage(image)
|
||||
self.canvas.delete(Tkinter.ALL)
|
||||
self.canvas.create_image(
|
||||
0, 0,
|
||||
image = photo, anchor = Tkinter.NW
|
||||
)
|
||||
self.update()
|
|
@ -1,28 +1,41 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from scene.scene import Scene
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.vectorized_mobject import VMobject, VGroup
|
||||
from mobject.tex_mobject import TexMobject, TextMobject
|
||||
from animation.animation import Animation
|
||||
from animation.transform import ApplyPointwiseFunction, Transform, \
|
||||
ApplyMethod, FadeOut, ApplyFunction
|
||||
from animation.simple_animations import ShowCreation, Write
|
||||
from topics.number_line import NumberPlane, Axes
|
||||
from topics.geometry import Vector, Line, Circle, Arrow, Dot, \
|
||||
BackgroundRectangle, Square
|
||||
|
||||
from constants import *
|
||||
from topics.matrix import Matrix, VECTOR_LABEL_SCALE_FACTOR, vector_coordinate_label
|
||||
from utils.rate_functions import rush_into, rush_from
|
||||
from utils.space_ops import angle_of_vector
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.creation import ShowCreation
|
||||
from animation.creation import Write
|
||||
from animation.transform import ApplyFunction
|
||||
from animation.transform import ApplyPointwiseFunction
|
||||
from animation.creation import FadeOut
|
||||
from animation.transform import Transform
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from mobject.svg.tex_mobject import TextMobject
|
||||
from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from scene.scene import Scene
|
||||
from mobject.geometry import Arrow
|
||||
from mobject.geometry import Dot
|
||||
from mobject.geometry import Line
|
||||
from mobject.geometry import Square
|
||||
from mobject.geometry import Vector
|
||||
from mobject.coordinate_systems import Axes
|
||||
from mobject.coordinate_systems import NumberPlane
|
||||
|
||||
from mobject.matrix import Matrix
|
||||
from mobject.matrix import VECTOR_LABEL_SCALE_FACTOR
|
||||
from mobject.matrix import vector_coordinate_label
|
||||
from utils.rate_functions import rush_from
|
||||
from utils.rate_functions import rush_into
|
||||
from utils.space_ops import angle_of_vector
|
||||
|
||||
X_COLOR = GREEN_C
|
||||
Y_COLOR = RED_C
|
||||
Z_COLOR = BLUE_D
|
||||
|
||||
|
||||
class VectorScene(Scene):
|
||||
CONFIG = {
|
||||
"basis_vector_stroke_width" : 6
|
|
@ -1,11 +1,13 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .scene import Scene
|
||||
from animation.transform import FadeIn
|
||||
from mobject.mobject import Mobject
|
||||
from topics.geometry import Rectangle
|
||||
from scene.scene import Scene
|
||||
from animation.creation import FadeIn
|
||||
from camera.camera import Camera
|
||||
from camera.camera import MovingCamera
|
||||
from camera.moving_camera import MovingCamera
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.geometry import Rectangle
|
||||
|
||||
from constants import *
|
||||
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import sys
|
||||
import inspect
|
||||
import itertools as it
|
||||
import os
|
||||
import shutil
|
||||
import itertools as it
|
||||
from extract_scene import is_scene, get_module
|
||||
from constants import ANIMATIONS_DIR, STAGED_SCENES_DIR
|
||||
import sys
|
||||
|
||||
from constants import ANIMATIONS_DIR
|
||||
from constants import STAGED_SCENES_DIR
|
||||
from extract_scene import get_module
|
||||
from extract_scene import is_scene
|
||||
|
||||
|
||||
def get_sorted_scene_names(module_name):
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
__all__ = [
|
||||
"arithmetic",
|
||||
"characters",
|
||||
"combinatorics",
|
||||
"complex_numbers",
|
||||
"functions",
|
||||
"geometry",
|
||||
"graph_theory",
|
||||
"number_line",
|
||||
"three_dimensions",
|
||||
]
|
|
@ -1,751 +0,0 @@
|
|||
import random
|
||||
import numpy as np
|
||||
import itertools as it
|
||||
|
||||
from constants import *
|
||||
|
||||
from mobject.mobject import Mobject, Group
|
||||
from mobject.svg_mobject import SVGMobject
|
||||
from mobject.vectorized_mobject import VMobject, VGroup
|
||||
from mobject.tex_mobject import TextMobject, TexMobject
|
||||
|
||||
from topics.objects import Bubble, ThoughtBubble, SpeechBubble
|
||||
from topics.geometry import ScreenRectangle
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.transform import Transform, ApplyMethod, MoveToTarget
|
||||
from animation.transform import ReplacementTransform, FadeOut, FadeIn
|
||||
from animation.simple_animations import Write, ShowCreation
|
||||
from animation.compositions import AnimationGroup
|
||||
from scene.scene import Scene
|
||||
from utils.config_ops import digest_config
|
||||
from utils.rate_functions import there_and_back, squish_rate_func
|
||||
|
||||
|
||||
PI_CREATURE_DIR = os.path.join(MEDIA_DIR, "designs", "PiCreature")
|
||||
PI_CREATURE_SCALE_FACTOR = 0.5
|
||||
|
||||
LEFT_EYE_INDEX = 0
|
||||
RIGHT_EYE_INDEX = 1
|
||||
LEFT_PUPIL_INDEX = 2
|
||||
RIGHT_PUPIL_INDEX = 3
|
||||
BODY_INDEX = 4
|
||||
MOUTH_INDEX = 5
|
||||
|
||||
|
||||
class PiCreature(SVGMobject):
|
||||
CONFIG = {
|
||||
"color" : BLUE_E,
|
||||
"file_name_prefix" : "PiCreatures",
|
||||
"stroke_width" : 0,
|
||||
"stroke_color" : BLACK,
|
||||
"fill_opacity" : 1.0,
|
||||
"propagate_style_to_family" : True,
|
||||
"height" : 3,
|
||||
"corner_scale_factor" : 0.75,
|
||||
"flip_at_start" : False,
|
||||
"is_looking_direction_purposeful" : False,
|
||||
"start_corner" : None,
|
||||
#Range of proportions along body where arms are
|
||||
"right_arm_range" : [0.55, 0.7],
|
||||
"left_arm_range" : [.34, .462],
|
||||
}
|
||||
def __init__(self, mode = "plain", **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.parts_named = False
|
||||
try:
|
||||
svg_file = os.path.join(
|
||||
PI_CREATURE_DIR,
|
||||
"%s_%s.svg"%(self.file_name_prefix, mode)
|
||||
)
|
||||
SVGMobject.__init__(self, file_name = svg_file, **kwargs)
|
||||
except:
|
||||
warnings.warn("No %s design with mode %s"%(self.file_name_prefix, mode))
|
||||
svg_file = os.path.join(
|
||||
FILE_DIR,
|
||||
"PiCreatures_plain.svg",
|
||||
)
|
||||
SVGMobject.__init__(self, file_name = svg_file, **kwargs)
|
||||
|
||||
if self.flip_at_start:
|
||||
self.flip()
|
||||
if self.start_corner is not None:
|
||||
self.to_corner(self.start_corner)
|
||||
|
||||
def name_parts(self):
|
||||
self.mouth = self.submobjects[MOUTH_INDEX]
|
||||
self.body = self.submobjects[BODY_INDEX]
|
||||
self.pupils = VGroup(*[
|
||||
self.submobjects[LEFT_PUPIL_INDEX],
|
||||
self.submobjects[RIGHT_PUPIL_INDEX]
|
||||
])
|
||||
self.eyes = VGroup(*[
|
||||
self.submobjects[LEFT_EYE_INDEX],
|
||||
self.submobjects[RIGHT_EYE_INDEX]
|
||||
])
|
||||
self.eye_parts = VGroup(self.eyes, self.pupils)
|
||||
self.parts_named = True
|
||||
|
||||
def init_colors(self):
|
||||
SVGMobject.init_colors(self)
|
||||
if not self.parts_named:
|
||||
self.name_parts()
|
||||
self.mouth.set_fill(BLACK, opacity = 1)
|
||||
self.body.set_fill(self.color, opacity = 1)
|
||||
self.pupils.set_fill(BLACK, opacity = 1)
|
||||
self.eyes.set_fill(WHITE, opacity = 1)
|
||||
return self
|
||||
|
||||
def copy(self):
|
||||
copy_mobject = SVGMobject.copy(self)
|
||||
copy_mobject.name_parts()
|
||||
return copy_mobject
|
||||
|
||||
def set_color(self, color):
|
||||
self.body.set_fill(color)
|
||||
return self
|
||||
|
||||
def change_mode(self, mode):
|
||||
new_self = self.__class__(
|
||||
mode = mode,
|
||||
color = self.color
|
||||
)
|
||||
new_self.scale_to_fit_height(self.get_height())
|
||||
if self.is_flipped() ^ new_self.is_flipped():
|
||||
new_self.flip()
|
||||
new_self.shift(self.eyes.get_center() - new_self.eyes.get_center())
|
||||
if hasattr(self, "purposeful_looking_direction"):
|
||||
new_self.look(self.purposeful_looking_direction)
|
||||
Transform(self, new_self).update(1)
|
||||
return self
|
||||
|
||||
def look(self, direction):
|
||||
norm = np.linalg.norm(direction)
|
||||
if norm == 0:
|
||||
return
|
||||
direction /= norm
|
||||
self.purposeful_looking_direction = direction
|
||||
for pupil, eye in zip(self.pupils.split(), self.eyes.split()):
|
||||
pupil_radius = pupil.get_width()/2.
|
||||
eye_radius = eye.get_width()/2.
|
||||
pupil.move_to(eye)
|
||||
if direction[1] < 0:
|
||||
pupil.shift(pupil_radius*DOWN/3)
|
||||
pupil.shift(direction*(eye_radius-pupil_radius))
|
||||
bottom_diff = eye.get_bottom()[1] - pupil.get_bottom()[1]
|
||||
if bottom_diff > 0:
|
||||
pupil.shift(bottom_diff*UP)
|
||||
#TODO, how to handle looking up...
|
||||
# top_diff = eye.get_top()[1]-pupil.get_top()[1]
|
||||
# if top_diff < 0:
|
||||
# pupil.shift(top_diff*UP)
|
||||
return self
|
||||
|
||||
def look_at(self, point_or_mobject):
|
||||
if isinstance(point_or_mobject, Mobject):
|
||||
point = point_or_mobject.get_center()
|
||||
else:
|
||||
point = point_or_mobject
|
||||
self.look(point - self.eyes.get_center())
|
||||
return self
|
||||
|
||||
def change(self, new_mode, look_at_arg = None):
|
||||
self.change_mode(new_mode)
|
||||
if look_at_arg is not None:
|
||||
self.look_at(look_at_arg)
|
||||
return self
|
||||
|
||||
def get_looking_direction(self):
|
||||
return np.sign(np.round(
|
||||
self.pupils.get_center() - self.eyes.get_center(),
|
||||
decimals = 2
|
||||
))
|
||||
|
||||
def is_flipped(self):
|
||||
return self.eyes.submobjects[0].get_center()[0] > \
|
||||
self.eyes.submobjects[1].get_center()[0]
|
||||
|
||||
def blink(self):
|
||||
eye_parts = self.eye_parts
|
||||
eye_bottom_y = eye_parts.get_bottom()[1]
|
||||
eye_parts.apply_function(
|
||||
lambda p : [p[0], eye_bottom_y, p[2]]
|
||||
)
|
||||
return self
|
||||
|
||||
def to_corner(self, vect = None, **kwargs):
|
||||
if vect is not None:
|
||||
SVGMobject.to_corner(self, vect, **kwargs)
|
||||
else:
|
||||
self.scale(self.corner_scale_factor)
|
||||
self.to_corner(DOWN+LEFT, **kwargs)
|
||||
return self
|
||||
|
||||
def get_bubble(self, *content, **kwargs):
|
||||
bubble_class = kwargs.get("bubble_class", ThoughtBubble)
|
||||
bubble = bubble_class(**kwargs)
|
||||
if len(content) > 0:
|
||||
if isinstance(content[0], str):
|
||||
content_mob = TextMobject(*content)
|
||||
else:
|
||||
content_mob = content[0]
|
||||
bubble.add_content(content_mob)
|
||||
if "height" not in kwargs and "width" not in kwargs:
|
||||
bubble.resize_to_content()
|
||||
bubble.pin_to(self)
|
||||
self.bubble = bubble
|
||||
return bubble
|
||||
|
||||
def make_eye_contact(self, pi_creature):
|
||||
self.look_at(pi_creature.eyes)
|
||||
pi_creature.look_at(self.eyes)
|
||||
return self
|
||||
|
||||
def shrug(self):
|
||||
self.change_mode("shruggie")
|
||||
top_mouth_point, bottom_mouth_point = [
|
||||
self.mouth.points[np.argmax(self.mouth.points[:,1])],
|
||||
self.mouth.points[np.argmin(self.mouth.points[:,1])]
|
||||
]
|
||||
self.look(top_mouth_point - bottom_mouth_point)
|
||||
return self
|
||||
|
||||
def get_arm_copies(self):
|
||||
body = self.body
|
||||
return VGroup(*[
|
||||
body.copy().pointwise_become_partial(body, *alpha_range)
|
||||
for alpha_range in self.right_arm_range, self.left_arm_range
|
||||
])
|
||||
|
||||
def get_all_pi_creature_modes():
|
||||
result = []
|
||||
prefix = "%s_"%PiCreature.CONFIG["file_name_prefix"]
|
||||
suffix = ".svg"
|
||||
for file in os.listdir(PI_CREATURE_DIR):
|
||||
if file.startswith(prefix) and file.endswith(suffix):
|
||||
result.append(
|
||||
file[len(prefix):-len(suffix)]
|
||||
)
|
||||
return result
|
||||
|
||||
class Randolph(PiCreature):
|
||||
pass #Nothing more than an alternative name
|
||||
|
||||
class Mortimer(PiCreature):
|
||||
CONFIG = {
|
||||
"color" : GREY_BROWN,
|
||||
"flip_at_start" : True,
|
||||
}
|
||||
|
||||
class Mathematician(PiCreature):
|
||||
CONFIG = {
|
||||
"color" : GREY,
|
||||
}
|
||||
|
||||
class BabyPiCreature(PiCreature):
|
||||
CONFIG = {
|
||||
"scale_factor" : 0.5,
|
||||
"eye_scale_factor" : 1.2,
|
||||
"pupil_scale_factor" : 1.3
|
||||
}
|
||||
def __init__(self, *args, **kwargs):
|
||||
PiCreature.__init__(self, *args, **kwargs)
|
||||
self.scale(self.scale_factor)
|
||||
self.shift(LEFT)
|
||||
self.to_edge(DOWN, buff = LARGE_BUFF)
|
||||
eyes = VGroup(self.eyes, self.pupils)
|
||||
eyes_bottom = eyes.get_bottom()
|
||||
eyes.scale(self.eye_scale_factor)
|
||||
eyes.move_to(eyes_bottom, aligned_edge = DOWN)
|
||||
looking_direction = self.get_looking_direction()
|
||||
for pupil in self.pupils:
|
||||
pupil.scale_in_place(self.pupil_scale_factor)
|
||||
self.look(looking_direction)
|
||||
|
||||
class TauCreature(PiCreature):
|
||||
CONFIG = {
|
||||
"file_name_prefix" : "TauCreatures"
|
||||
}
|
||||
|
||||
class ThreeLeggedPiCreature(PiCreature):
|
||||
CONFIG = {
|
||||
"file_name_prefix" : "ThreeLeggedPiCreatures"
|
||||
}
|
||||
|
||||
|
||||
class Blink(ApplyMethod):
|
||||
CONFIG = {
|
||||
"rate_func" : squish_rate_func(there_and_back)
|
||||
}
|
||||
def __init__(self, pi_creature, **kwargs):
|
||||
ApplyMethod.__init__(self, pi_creature.blink, **kwargs)
|
||||
|
||||
class Eyes(VMobject):
|
||||
CONFIG = {
|
||||
"height" : 0.3,
|
||||
"thing_looked_at" : None,
|
||||
"mode" : "plain",
|
||||
}
|
||||
def __init__(self, mobject, **kwargs):
|
||||
VMobject.__init__(self, **kwargs)
|
||||
self.mobject = mobject
|
||||
self.submobjects = self.get_eyes().submobjects
|
||||
|
||||
def get_eyes(self, mode = None, thing_to_look_at = None):
|
||||
mode = mode or self.mode
|
||||
if thing_to_look_at is None:
|
||||
thing_to_look_at = self.thing_looked_at
|
||||
|
||||
pi = Randolph(mode = mode)
|
||||
eyes = VGroup(pi.eyes, pi.pupils)
|
||||
pi.scale(self.height/eyes.get_height())
|
||||
if self.submobjects:
|
||||
eyes.move_to(self, DOWN)
|
||||
else:
|
||||
eyes.move_to(self.mobject.get_top(), DOWN)
|
||||
if thing_to_look_at is not None:
|
||||
pi.look_at(thing_to_look_at)
|
||||
return eyes
|
||||
|
||||
def change_mode_anim(self, mode, **kwargs):
|
||||
self.mode = mode
|
||||
return Transform(self, self.get_eyes(mode = mode), **kwargs)
|
||||
|
||||
def look_at_anim(self, point_or_mobject, **kwargs):
|
||||
self.thing_looked_at = point_or_mobject
|
||||
return Transform(
|
||||
self, self.get_eyes(thing_to_look_at = point_or_mobject),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def blink_anim(self, **kwargs):
|
||||
target = self.copy()
|
||||
bottom_y = self.get_bottom()[1]
|
||||
for submob in target:
|
||||
submob.apply_function(
|
||||
lambda p : [p[0], bottom_y, p[2]]
|
||||
)
|
||||
if "rate_func" not in kwargs:
|
||||
kwargs["rate_func"] = squish_rate_func(there_and_back)
|
||||
return Transform(self, target, **kwargs)
|
||||
|
||||
#######################
|
||||
|
||||
class PiCreatureBubbleIntroduction(AnimationGroup):
|
||||
CONFIG = {
|
||||
"target_mode" : "speaking",
|
||||
"bubble_class" : SpeechBubble,
|
||||
"change_mode_kwargs" : {},
|
||||
"bubble_creation_class" : ShowCreation,
|
||||
"bubble_creation_kwargs" : {},
|
||||
"bubble_kwargs" : {},
|
||||
"content_introduction_class" : Write,
|
||||
"content_introduction_kwargs" : {},
|
||||
"look_at_arg" : None,
|
||||
}
|
||||
def __init__(self, pi_creature, *content, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
bubble = pi_creature.get_bubble(
|
||||
*content,
|
||||
bubble_class = self.bubble_class,
|
||||
**self.bubble_kwargs
|
||||
)
|
||||
Group(bubble, bubble.content).shift_onto_screen()
|
||||
|
||||
pi_creature.generate_target()
|
||||
pi_creature.target.change_mode(self.target_mode)
|
||||
if self.look_at_arg is not None:
|
||||
pi_creature.target.look_at(self.look_at_arg)
|
||||
|
||||
change_mode = MoveToTarget(pi_creature, **self.change_mode_kwargs)
|
||||
bubble_creation = self.bubble_creation_class(
|
||||
bubble, **self.bubble_creation_kwargs
|
||||
)
|
||||
content_introduction = self.content_introduction_class(
|
||||
bubble.content, **self.content_introduction_kwargs
|
||||
)
|
||||
AnimationGroup.__init__(
|
||||
self, change_mode, bubble_creation, content_introduction,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
class PiCreatureSays(PiCreatureBubbleIntroduction):
|
||||
CONFIG = {
|
||||
"target_mode" : "speaking",
|
||||
"bubble_class" : SpeechBubble,
|
||||
}
|
||||
|
||||
class RemovePiCreatureBubble(AnimationGroup):
|
||||
CONFIG = {
|
||||
"target_mode" : "plain",
|
||||
"look_at_arg" : None,
|
||||
"remover" : True,
|
||||
}
|
||||
def __init__(self, pi_creature, **kwargs):
|
||||
assert hasattr(pi_creature, "bubble")
|
||||
digest_config(self, kwargs, locals())
|
||||
|
||||
pi_creature.generate_target()
|
||||
pi_creature.target.change_mode(self.target_mode)
|
||||
if self.look_at_arg is not None:
|
||||
pi_creature.target.look_at(self.look_at_arg)
|
||||
|
||||
AnimationGroup.__init__(
|
||||
self,
|
||||
MoveToTarget(pi_creature),
|
||||
FadeOut(pi_creature.bubble),
|
||||
FadeOut(pi_creature.bubble.content),
|
||||
)
|
||||
|
||||
def clean_up(self, surrounding_scene = None):
|
||||
AnimationGroup.clean_up(self, surrounding_scene)
|
||||
self.pi_creature.bubble = None
|
||||
if surrounding_scene is not None:
|
||||
surrounding_scene.add(self.pi_creature)
|
||||
|
||||
###########
|
||||
|
||||
class PiCreatureScene(Scene):
|
||||
CONFIG = {
|
||||
"total_wait_time" : 0,
|
||||
"seconds_to_blink" : 3,
|
||||
"pi_creatures_start_on_screen" : True,
|
||||
"default_pi_creature_kwargs" : {
|
||||
"color" : GREY_BROWN,
|
||||
"flip_at_start" : True,
|
||||
},
|
||||
"default_pi_creature_start_corner" : DOWN+LEFT,
|
||||
}
|
||||
def setup(self):
|
||||
self.pi_creatures = self.create_pi_creatures()
|
||||
self.pi_creature = self.get_primary_pi_creature()
|
||||
if self.pi_creatures_start_on_screen:
|
||||
self.add(*self.pi_creatures)
|
||||
|
||||
def create_pi_creatures(self):
|
||||
"""
|
||||
Likely updated for subclasses
|
||||
"""
|
||||
return VGroup(self.create_pi_creature())
|
||||
|
||||
def create_pi_creature(self):
|
||||
pi_creature = PiCreature(**self.default_pi_creature_kwargs)
|
||||
pi_creature.to_corner(self.default_pi_creature_start_corner)
|
||||
return pi_creature
|
||||
|
||||
def get_pi_creatures(self):
|
||||
return self.pi_creatures
|
||||
|
||||
def get_primary_pi_creature(self):
|
||||
return self.pi_creatures[0]
|
||||
|
||||
def any_pi_creatures_on_screen(self):
|
||||
mobjects = self.get_mobjects()
|
||||
return any([pi in mobjects for pi in self.get_pi_creatures()])
|
||||
|
||||
def get_on_screen_pi_creatures(self):
|
||||
mobjects = self.get_mobjects()
|
||||
return VGroup(*filter(
|
||||
lambda pi : pi in mobjects,
|
||||
self.get_pi_creatures()
|
||||
))
|
||||
|
||||
def introduce_bubble(self, *args, **kwargs):
|
||||
if isinstance(args[0], PiCreature):
|
||||
pi_creature = args[0]
|
||||
content = args[1:]
|
||||
else:
|
||||
pi_creature = self.get_primary_pi_creature()
|
||||
content = args
|
||||
|
||||
bubble_class = kwargs.pop("bubble_class", SpeechBubble)
|
||||
target_mode = kwargs.pop(
|
||||
"target_mode",
|
||||
"thinking" if bubble_class is ThoughtBubble else "speaking"
|
||||
)
|
||||
bubble_kwargs = kwargs.pop("bubble_kwargs", {})
|
||||
bubble_removal_kwargs = kwargs.pop("bubble_removal_kwargs", {})
|
||||
added_anims = kwargs.pop("added_anims", [])
|
||||
|
||||
anims = []
|
||||
on_screen_mobjects = self.camera.extract_mobject_family_members(
|
||||
self.get_mobjects()
|
||||
)
|
||||
def has_bubble(pi):
|
||||
return hasattr(pi, "bubble") and \
|
||||
pi.bubble is not None and \
|
||||
pi.bubble in on_screen_mobjects
|
||||
|
||||
pi_creatures_with_bubbles = filter(has_bubble, self.get_pi_creatures())
|
||||
if pi_creature in pi_creatures_with_bubbles:
|
||||
pi_creatures_with_bubbles.remove(pi_creature)
|
||||
old_bubble = pi_creature.bubble
|
||||
bubble = pi_creature.get_bubble(
|
||||
*content,
|
||||
bubble_class = bubble_class,
|
||||
**bubble_kwargs
|
||||
)
|
||||
anims += [
|
||||
ReplacementTransform(old_bubble, bubble),
|
||||
ReplacementTransform(old_bubble.content, bubble.content),
|
||||
pi_creature.change_mode, target_mode
|
||||
]
|
||||
else:
|
||||
anims.append(PiCreatureBubbleIntroduction(
|
||||
pi_creature,
|
||||
*content,
|
||||
bubble_class = bubble_class,
|
||||
bubble_kwargs = bubble_kwargs,
|
||||
target_mode = target_mode,
|
||||
**kwargs
|
||||
))
|
||||
anims += [
|
||||
RemovePiCreatureBubble(pi, **bubble_removal_kwargs)
|
||||
for pi in pi_creatures_with_bubbles
|
||||
]
|
||||
anims += added_anims
|
||||
|
||||
self.play(*anims, **kwargs)
|
||||
|
||||
def pi_creature_says(self, *args, **kwargs):
|
||||
self.introduce_bubble(
|
||||
*args,
|
||||
bubble_class = SpeechBubble,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def pi_creature_thinks(self, *args, **kwargs):
|
||||
self.introduce_bubble(
|
||||
*args,
|
||||
bubble_class = ThoughtBubble,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def say(self, *content, **kwargs):
|
||||
self.pi_creature_says(self.get_primary_pi_creature(), *content, **kwargs)
|
||||
|
||||
def think(self, *content, **kwargs):
|
||||
self.pi_creature_thinks(self.get_primary_pi_creature(), *content, **kwargs)
|
||||
|
||||
def compile_play_args_to_animation_list(self, *args):
|
||||
"""
|
||||
Add animations so that all pi creatures look at the
|
||||
first mobject being animated with each .play call
|
||||
"""
|
||||
animations = Scene.compile_play_args_to_animation_list(self, *args)
|
||||
if not self.any_pi_creatures_on_screen():
|
||||
return animations
|
||||
|
||||
non_pi_creature_anims = filter(
|
||||
lambda anim : anim.mobject not in self.get_pi_creatures(),
|
||||
animations
|
||||
)
|
||||
if len(non_pi_creature_anims) == 0:
|
||||
return animations
|
||||
first_anim = non_pi_creature_anims[0]
|
||||
#Look at ending state
|
||||
first_anim.update(1)
|
||||
point_of_interest = first_anim.mobject.get_center()
|
||||
first_anim.update(0)
|
||||
|
||||
for pi_creature in self.get_pi_creatures():
|
||||
if pi_creature not in self.get_mobjects():
|
||||
continue
|
||||
if pi_creature in first_anim.mobject.submobject_family():
|
||||
continue
|
||||
anims_with_pi_creature = filter(
|
||||
lambda anim : pi_creature in anim.mobject.submobject_family(),
|
||||
animations
|
||||
)
|
||||
for anim in anims_with_pi_creature:
|
||||
if isinstance(anim, Transform):
|
||||
index = anim.mobject.submobject_family().index(pi_creature)
|
||||
target_family = anim.target_mobject.submobject_family()
|
||||
target = target_family[index]
|
||||
if isinstance(target, PiCreature):
|
||||
target.look_at(point_of_interest)
|
||||
if not anims_with_pi_creature:
|
||||
animations.append(
|
||||
ApplyMethod(pi_creature.look_at, point_of_interest)
|
||||
)
|
||||
return animations
|
||||
|
||||
def blink(self):
|
||||
self.play(Blink(random.choice(self.get_on_screen_pi_creatures())))
|
||||
|
||||
def joint_blink(self, pi_creatures = None, shuffle = True, **kwargs):
|
||||
if pi_creatures is None:
|
||||
pi_creatures = self.get_on_screen_pi_creatures()
|
||||
creatures_list = list(pi_creatures)
|
||||
if shuffle:
|
||||
random.shuffle(creatures_list)
|
||||
|
||||
def get_rate_func(pi):
|
||||
index = creatures_list.index(pi)
|
||||
proportion = float(index)/len(creatures_list)
|
||||
start_time = 0.8*proportion
|
||||
return squish_rate_func(
|
||||
there_and_back,
|
||||
start_time, start_time + 0.2
|
||||
)
|
||||
|
||||
self.play(*[
|
||||
Blink(pi, rate_func = get_rate_func(pi), **kwargs)
|
||||
for pi in creatures_list
|
||||
])
|
||||
return self
|
||||
|
||||
def wait(self, time = 1, blink = True):
|
||||
while time >= 1:
|
||||
time_to_blink = self.total_wait_time%self.seconds_to_blink == 0
|
||||
if blink and self.any_pi_creatures_on_screen() and time_to_blink:
|
||||
self.blink()
|
||||
self.num_plays -= 1 #This shouldn't count as an animation
|
||||
else:
|
||||
self.non_blink_wait()
|
||||
time -= 1
|
||||
self.total_wait_time += 1
|
||||
if time > 0:
|
||||
self.non_blink_wait(time)
|
||||
return self
|
||||
|
||||
def non_blink_wait(self, time = 1):
|
||||
Scene.wait(self, time)
|
||||
return self
|
||||
|
||||
def change_mode(self, mode):
|
||||
self.play(self.get_primary_pi_creature().change_mode, mode)
|
||||
|
||||
def look_at(self, thing_to_look_at, pi_creatures = None):
|
||||
if pi_creatures is None:
|
||||
pi_creatures = self.get_pi_creatures()
|
||||
self.play(*it.chain(*[
|
||||
[pi.look_at, thing_to_look_at]
|
||||
for pi in pi_creatures
|
||||
]))
|
||||
|
||||
class TeacherStudentsScene(PiCreatureScene):
|
||||
CONFIG = {
|
||||
"student_colors" : [BLUE_D, BLUE_E, BLUE_C],
|
||||
"student_scale_factor" : 0.8,
|
||||
"seconds_to_blink" : 2,
|
||||
"screen_height" : 3,
|
||||
}
|
||||
def setup(self):
|
||||
PiCreatureScene.setup(self)
|
||||
self.screen = ScreenRectangle(height = self.screen_height)
|
||||
self.screen.to_corner(UP+LEFT)
|
||||
self.hold_up_spot = self.teacher.get_corner(UP+LEFT) + MED_LARGE_BUFF*UP
|
||||
|
||||
def create_pi_creatures(self):
|
||||
self.teacher = Mortimer()
|
||||
self.teacher.to_corner(DOWN + RIGHT)
|
||||
self.teacher.look(DOWN+LEFT)
|
||||
self.students = VGroup(*[
|
||||
Randolph(color = c)
|
||||
for c in self.student_colors
|
||||
])
|
||||
self.students.arrange_submobjects(RIGHT)
|
||||
self.students.scale(self.student_scale_factor)
|
||||
self.students.to_corner(DOWN+LEFT)
|
||||
self.teacher.look_at(self.students[-1].eyes)
|
||||
for student in self.students:
|
||||
student.look_at(self.teacher.eyes)
|
||||
|
||||
return [self.teacher] + list(self.students)
|
||||
|
||||
def get_teacher(self):
|
||||
return self.teacher
|
||||
|
||||
def get_students(self):
|
||||
return self.students
|
||||
|
||||
def teacher_says(self, *content, **kwargs):
|
||||
return self.pi_creature_says(
|
||||
self.get_teacher(), *content, **kwargs
|
||||
)
|
||||
|
||||
def student_says(self, *content, **kwargs):
|
||||
if "target_mode" not in kwargs:
|
||||
target_mode = random.choice([
|
||||
"raise_right_hand",
|
||||
"raise_left_hand",
|
||||
])
|
||||
kwargs["target_mode"] = target_mode
|
||||
student = self.get_students()[kwargs.get("student_index", 1)]
|
||||
return self.pi_creature_says(
|
||||
student, *content, **kwargs
|
||||
)
|
||||
|
||||
def teacher_thinks(self, *content, **kwargs):
|
||||
return self.pi_creature_thinks(
|
||||
self.get_teacher(), *content, **kwargs
|
||||
)
|
||||
|
||||
def student_thinks(self, *content, **kwargs):
|
||||
student = self.get_students()[kwargs.get("student_index", 1)]
|
||||
return self.pi_creature_thinks(student, *content, **kwargs)
|
||||
|
||||
def change_all_student_modes(self, mode, **kwargs):
|
||||
self.change_student_modes(*[mode]*len(self.students), **kwargs)
|
||||
|
||||
def change_student_modes(self, *modes, **kwargs):
|
||||
added_anims = kwargs.pop("added_anims", [])
|
||||
self.play(
|
||||
self.get_student_changes(*modes, **kwargs),
|
||||
*added_anims
|
||||
)
|
||||
|
||||
def get_student_changes(self, *modes, **kwargs):
|
||||
pairs = zip(self.get_students(), modes)
|
||||
pairs = [(s, m) for s, m in pairs if m is not None]
|
||||
start = VGroup(*[s for s, m in pairs])
|
||||
target = VGroup(*[s.copy().change_mode(m) for s, m in pairs])
|
||||
if "look_at_arg" in kwargs:
|
||||
for pi in target:
|
||||
pi.look_at(kwargs["look_at_arg"])
|
||||
submobject_mode = kwargs.get("submobject_mode", "lagged_start")
|
||||
return Transform(
|
||||
start, target,
|
||||
submobject_mode = submobject_mode,
|
||||
run_time = 2
|
||||
)
|
||||
|
||||
def zoom_in_on_thought_bubble(self, bubble = None, radius = FRAME_Y_RADIUS+FRAME_X_RADIUS):
|
||||
if bubble is None:
|
||||
for pi in self.get_pi_creatures():
|
||||
if hasattr(pi, "bubble") and isinstance(pi.bubble, ThoughtBubble):
|
||||
bubble = pi.bubble
|
||||
break
|
||||
if bubble is None:
|
||||
raise Exception("No pi creatures have a thought bubble")
|
||||
vect = -bubble.get_bubble_center()
|
||||
def func(point):
|
||||
centered = point+vect
|
||||
return radius*centered/np.linalg.norm(centered)
|
||||
self.play(*[
|
||||
ApplyPointwiseFunction(func, mob)
|
||||
for mob in self.get_mobjects()
|
||||
])
|
||||
|
||||
def teacher_holds_up(self, mobject, target_mode = "raise_right_hand", **kwargs):
|
||||
mobject.move_to(self.hold_up_spot, DOWN)
|
||||
mobject.shift_onto_screen()
|
||||
mobject_copy = mobject.copy()
|
||||
mobject_copy.shift(DOWN)
|
||||
mobject_copy.fade(1)
|
||||
self.play(
|
||||
ReplacementTransform(mobject_copy, mobject),
|
||||
self.teacher.change, target_mode,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
268
topics/matrix.py
268
topics/matrix.py
|
@ -1,268 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
from scene.scene import Scene
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.vectorized_mobject import VMobject, VGroup
|
||||
from mobject.tex_mobject import TexMobject, TextMobject
|
||||
from animation.transform import ApplyPointwiseFunction, Transform, \
|
||||
ApplyMethod, FadeOut, ApplyFunction
|
||||
from animation.simple_animations import ShowCreation, Write
|
||||
from topics.number_line import NumberPlane, Axes
|
||||
from topics.geometry import Vector, Line, Circle, Arrow, Dot, BackgroundRectangle
|
||||
|
||||
from constants import *
|
||||
|
||||
VECTOR_LABEL_SCALE_FACTOR = 0.8
|
||||
|
||||
def matrix_to_tex_string(matrix):
|
||||
matrix = np.array(matrix).astype("string")
|
||||
if matrix.ndim == 1:
|
||||
matrix = matrix.reshape((matrix.size, 1))
|
||||
n_rows, n_cols = matrix.shape
|
||||
prefix = "\\left[ \\begin{array}{%s}"%("c"*n_cols)
|
||||
suffix = "\\end{array} \\right]"
|
||||
rows = [
|
||||
" & ".join(row)
|
||||
for row in matrix
|
||||
]
|
||||
return prefix + " \\\\ ".join(rows) + suffix
|
||||
|
||||
|
||||
def matrix_to_mobject(matrix):
|
||||
return TexMobject(matrix_to_tex_string(matrix))
|
||||
|
||||
def vector_coordinate_label(vector_mob, integer_labels = True,
|
||||
n_dim = 2, color = WHITE):
|
||||
vect = np.array(vector_mob.get_end())
|
||||
if integer_labels:
|
||||
vect = np.round(vect).astype(int)
|
||||
vect = vect[:n_dim]
|
||||
vect = vect.reshape((n_dim, 1))
|
||||
label = Matrix(vect, add_background_rectangles = True)
|
||||
label.scale(VECTOR_LABEL_SCALE_FACTOR)
|
||||
|
||||
shift_dir = np.array(vector_mob.get_end())
|
||||
if shift_dir[0] >= 0: #Pointing right
|
||||
shift_dir -= label.get_left() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER*LEFT
|
||||
else: #Pointing left
|
||||
shift_dir -= label.get_right() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER*RIGHT
|
||||
label.shift(shift_dir)
|
||||
label.set_color(color)
|
||||
label.rect = BackgroundRectangle(label)
|
||||
label.add_to_back(label.rect)
|
||||
return label
|
||||
|
||||
class Matrix(VMobject):
|
||||
CONFIG = {
|
||||
"v_buff" : 0.5,
|
||||
"h_buff" : 1,
|
||||
"add_background_rectangles" : False
|
||||
}
|
||||
def __init__(self, matrix, **kwargs):
|
||||
"""
|
||||
Matrix can either either include numbres, tex_strings,
|
||||
or mobjects
|
||||
"""
|
||||
VMobject.__init__(self, **kwargs)
|
||||
matrix = np.array(matrix)
|
||||
if matrix.ndim == 1:
|
||||
matrix = matrix.reshape((matrix.size, 1))
|
||||
if not isinstance(matrix[0][0], Mobject):
|
||||
matrix = matrix.astype("string")
|
||||
matrix = self.string_matrix_to_mob_matrix(matrix)
|
||||
self.organize_mob_matrix(matrix)
|
||||
self.add(*matrix.flatten())
|
||||
self.add_brackets()
|
||||
self.center()
|
||||
self.mob_matrix = matrix
|
||||
if self.add_background_rectangles:
|
||||
for mob in matrix.flatten():
|
||||
mob.add_background_rectangle()
|
||||
|
||||
def string_matrix_to_mob_matrix(self, matrix):
|
||||
return np.array([
|
||||
map(TexMobject, row)
|
||||
for row in matrix
|
||||
]).reshape(matrix.shape)
|
||||
|
||||
def organize_mob_matrix(self, matrix):
|
||||
for i, row in enumerate(matrix):
|
||||
for j, elem in enumerate(row):
|
||||
mob = matrix[i][j]
|
||||
if i == 0 and j == 0:
|
||||
continue
|
||||
elif i == 0:
|
||||
mob.next_to(matrix[i][j-1], RIGHT, self.h_buff)
|
||||
else:
|
||||
mob.next_to(matrix[i-1][j], DOWN, self.v_buff)
|
||||
return self
|
||||
|
||||
def add_brackets(self):
|
||||
bracket_pair = TexMobject("\\big[ \\big]")
|
||||
bracket_pair.scale(2)
|
||||
bracket_pair.stretch_to_fit_height(self.get_height() + 0.5)
|
||||
l_bracket, r_bracket = bracket_pair.split()
|
||||
l_bracket.next_to(self, LEFT)
|
||||
r_bracket.next_to(self, RIGHT)
|
||||
self.add(l_bracket, r_bracket)
|
||||
self.brackets = VMobject(l_bracket, r_bracket)
|
||||
return self
|
||||
|
||||
def set_color_columns(self, *colors):
|
||||
for i, color in enumerate(colors):
|
||||
VMobject(*self.mob_matrix[:,i]).set_color(color)
|
||||
return self
|
||||
|
||||
def add_background_to_entries(self):
|
||||
for mob in self.get_entries():
|
||||
mob.add_background_rectangle()
|
||||
return self
|
||||
|
||||
def get_mob_matrix(self):
|
||||
return self.mob_matrix
|
||||
|
||||
def get_entries(self):
|
||||
return VMobject(*self.get_mob_matrix().flatten())
|
||||
|
||||
def get_brackets(self):
|
||||
return self.brackets
|
||||
|
||||
|
||||
|
||||
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([VMobject()]).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 = [
|
||||
VMobject(*row) for row in left.get_mob_matrix()
|
||||
]
|
||||
h_lines = VMobject()
|
||||
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 = [
|
||||
VMobject(*col) for col in np.transpose(right.get_mob_matrix())
|
||||
]
|
||||
v_lines = VMobject()
|
||||
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 = VMobject(left, right, equals, result)
|
||||
everything.arrange_submobjects()
|
||||
everything.scale_to_fit_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 = VMobject(*[
|
||||
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 = VMobject(
|
||||
l_matrix[a][c].copy(),
|
||||
r_matrix[c][b].copy()
|
||||
)
|
||||
result_entry = result_matrix[a][b].split()[c]
|
||||
|
||||
new_circles = VMobject(*[
|
||||
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,
|
||||
submobject_mode = "all_at_once",
|
||||
),
|
||||
*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,167 +0,0 @@
|
|||
|
||||
from mobject.vectorized_mobject import VMobject, VGroup, VectorizedPoint
|
||||
from mobject.tex_mobject import TexMobject
|
||||
from animation.animation import Animation
|
||||
from animation.continual_animation import ContinualAnimation
|
||||
from topics.geometry import BackgroundRectangle
|
||||
from scene.scene import Scene
|
||||
from constants import *
|
||||
from utils.bezier import interpolate
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
class DecimalNumber(VMobject):
|
||||
CONFIG = {
|
||||
"num_decimal_points" : 2,
|
||||
"digit_to_digit_buff" : 0.05,
|
||||
"show_ellipsis" : False,
|
||||
"unit" : None, #Aligned to bottom unless it starts with "^"
|
||||
"include_background_rectangle" : False,
|
||||
}
|
||||
def __init__(self, number, **kwargs):
|
||||
VMobject.__init__(self, **kwargs)
|
||||
self.number = number
|
||||
ndp = self.num_decimal_points
|
||||
|
||||
#Build number string
|
||||
if isinstance(number, complex):
|
||||
num_string = '%.*f%s%.*fi'%(
|
||||
ndp, number.real,
|
||||
"-" if number.imag < 0 else "+",
|
||||
ndp, abs(number.imag)
|
||||
)
|
||||
else:
|
||||
num_string = '%.*f'%(ndp, number)
|
||||
negative_zero_string = "-%.*f"%(ndp, 0.)
|
||||
if num_string == negative_zero_string:
|
||||
num_string = num_string[1:]
|
||||
self.add(*[
|
||||
TexMobject(char, **kwargs)
|
||||
for char in num_string
|
||||
])
|
||||
|
||||
#Add non-numerical bits
|
||||
if self.show_ellipsis:
|
||||
self.add(TexMobject("\\dots"))
|
||||
|
||||
|
||||
if num_string.startswith("-"):
|
||||
minus = self.submobjects[0]
|
||||
minus.next_to(
|
||||
self.submobjects[1], LEFT,
|
||||
buff = self.digit_to_digit_buff
|
||||
)
|
||||
|
||||
if self.unit != None:
|
||||
self.unit_sign = TexMobject(self.unit)
|
||||
self.add(self.unit_sign)
|
||||
|
||||
self.arrange_submobjects(
|
||||
buff = self.digit_to_digit_buff,
|
||||
aligned_edge = DOWN
|
||||
)
|
||||
|
||||
#Handle alignment of parts that should be aligned
|
||||
#to the bottom
|
||||
for i, c in enumerate(num_string):
|
||||
if c == "-" and len(num_string) > i+1:
|
||||
self[i].align_to(self[i+1], alignment_vect = UP)
|
||||
if self.unit and self.unit.startswith("^"):
|
||||
self.unit_sign.align_to(self, UP)
|
||||
#
|
||||
if self.include_background_rectangle:
|
||||
self.add_background_rectangle()
|
||||
|
||||
def add_background_rectangle(self):
|
||||
#TODO, is this the best way to handle
|
||||
#background rectangles?
|
||||
self.background_rectangle = BackgroundRectangle(self)
|
||||
self.submobjects = [
|
||||
self.background_rectangle,
|
||||
VGroup(*self.submobjects)
|
||||
]
|
||||
return self
|
||||
|
||||
class Integer(DecimalNumber):
|
||||
CONFIG = {
|
||||
"num_decimal_points" : 0,
|
||||
}
|
||||
|
||||
class ChangingDecimal(Animation):
|
||||
CONFIG = {
|
||||
"num_decimal_points" : None,
|
||||
"show_ellipsis" : None,
|
||||
"position_update_func" : None,
|
||||
"tracked_mobject" : None,
|
||||
}
|
||||
def __init__(self, decimal_number_mobject, number_update_func, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
self.decimal_number_config = dict(
|
||||
decimal_number_mobject.initial_config
|
||||
)
|
||||
for attr in "num_decimal_points", "show_ellipsis":
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
self.decimal_number_config[attr] = value
|
||||
if hasattr(self.decimal_number_mobject, "background_rectangle"):
|
||||
self.decimal_number_config["include_background_rectangle"] = True
|
||||
if self.tracked_mobject:
|
||||
dmc = decimal_number_mobject.get_center()
|
||||
tmc = self.tracked_mobject.get_center()
|
||||
self.diff_from_tracked_mobject = dmc - tmc
|
||||
Animation.__init__(self, decimal_number_mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
self.update_number(alpha)
|
||||
self.update_position()
|
||||
|
||||
def update_number(self, alpha):
|
||||
decimal = self.decimal_number_mobject
|
||||
new_number = self.number_update_func(alpha)
|
||||
new_decimal = DecimalNumber(
|
||||
new_number, **self.decimal_number_config
|
||||
)
|
||||
new_decimal.match_height(decimal)
|
||||
new_decimal.move_to(decimal)
|
||||
new_decimal.match_style(decimal)
|
||||
|
||||
decimal.submobjects = new_decimal.submobjects
|
||||
decimal.number = new_number
|
||||
|
||||
def update_position(self):
|
||||
if self.position_update_func is not None:
|
||||
self.position_update_func(self.decimal_number_mobject)
|
||||
elif self.tracked_mobject is not None:
|
||||
self.decimal_number_mobject.move_to(self.tracked_mobject.get_center() + self.diff_from_tracked_mobject)
|
||||
|
||||
class ChangeDecimalToValue(ChangingDecimal):
|
||||
def __init__(self, decimal_number_mobject, target_number, **kwargs):
|
||||
start_number = decimal_number_mobject.number
|
||||
func = lambda alpha : interpolate(start_number, target_number, alpha)
|
||||
ChangingDecimal.__init__(self, decimal_number_mobject, func, **kwargs)
|
||||
|
||||
class ContinualChangingDecimal(ContinualAnimation):
|
||||
def __init__(self, decimal_number_mobject, number_update_func, **kwargs):
|
||||
self.anim = ChangingDecimal(decimal_number_mobject, number_update_func, **kwargs)
|
||||
ContinualAnimation.__init__(self, decimal_number_mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
self.anim.update(self.internal_time)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,642 +0,0 @@
|
|||
from constants import *
|
||||
|
||||
from scene.scene import Scene
|
||||
|
||||
from animation.animation import Animation
|
||||
from animation.transform import Transform, MoveToTarget
|
||||
from animation.simple_animations import UpdateFromFunc
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint
|
||||
from mobject.svg_mobject import SVGMobject
|
||||
from mobject.tex_mobject import TextMobject, TexMobject, Brace
|
||||
from topics.geometry import Circle, Line, Rectangle, Square, Arc, Polygon
|
||||
|
||||
from utils.bezier import interpolate
|
||||
from utils.color import color_gradient, average_color
|
||||
from utils.config_ops import digest_config
|
||||
from utils.iterables import tuplify
|
||||
from utils.space_ops import center_of_mass
|
||||
|
||||
EPSILON = 0.0001
|
||||
|
||||
class SampleSpaceScene(Scene):
|
||||
def get_sample_space(self, **config):
|
||||
self.sample_space = SampleSpace(**config)
|
||||
return self.sample_space
|
||||
|
||||
def add_sample_space(self, **config):
|
||||
self.add(self.get_sample_space(**config))
|
||||
|
||||
def get_division_change_animations(
|
||||
self, sample_space, parts, p_list,
|
||||
dimension = 1,
|
||||
new_label_kwargs = None,
|
||||
**kwargs
|
||||
):
|
||||
if new_label_kwargs is None:
|
||||
new_label_kwargs = {}
|
||||
anims = []
|
||||
p_list = sample_space.complete_p_list(p_list)
|
||||
space_copy = sample_space.copy()
|
||||
|
||||
vect = DOWN if dimension == 1 else RIGHT
|
||||
parts.generate_target()
|
||||
for part, p in zip(parts.target, p_list):
|
||||
part.replace(space_copy, stretch = True)
|
||||
part.stretch(p, dimension)
|
||||
parts.target.arrange_submobjects(vect, buff = 0)
|
||||
parts.target.move_to(space_copy)
|
||||
anims.append(MoveToTarget(parts))
|
||||
if hasattr(parts, "labels") and parts.labels is not None:
|
||||
label_kwargs = parts.label_kwargs
|
||||
label_kwargs.update(new_label_kwargs)
|
||||
new_braces, new_labels = sample_space.get_subdivision_braces_and_labels(
|
||||
parts.target, **label_kwargs
|
||||
)
|
||||
anims += [
|
||||
Transform(parts.braces, new_braces),
|
||||
Transform(parts.labels, new_labels),
|
||||
]
|
||||
return anims
|
||||
|
||||
def get_horizontal_division_change_animations(self, p_list, **kwargs):
|
||||
assert(hasattr(self.sample_space, "horizontal_parts"))
|
||||
return self.get_division_change_animations(
|
||||
self.sample_space, self.sample_space.horizontal_parts, p_list,
|
||||
dimension = 1,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def get_vertical_division_change_animations(self, p_list, **kwargs):
|
||||
assert(hasattr(self.sample_space, "vertical_parts"))
|
||||
return self.get_division_change_animations(
|
||||
self.sample_space, self.sample_space.vertical_parts, p_list,
|
||||
dimension = 0,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def get_conditional_change_anims(
|
||||
self, sub_sample_space_index, value, post_rects = None,
|
||||
**kwargs
|
||||
):
|
||||
parts = self.sample_space.horizontal_parts
|
||||
sub_sample_space = parts[sub_sample_space_index]
|
||||
anims = self.get_division_change_animations(
|
||||
sub_sample_space, sub_sample_space.vertical_parts, value,
|
||||
dimension = 0,
|
||||
**kwargs
|
||||
)
|
||||
if post_rects is not None:
|
||||
anims += self.get_posterior_rectangle_change_anims(post_rects)
|
||||
return anims
|
||||
|
||||
def get_top_conditional_change_anims(self, *args, **kwargs):
|
||||
return self.get_conditional_change_anims(0, *args, **kwargs)
|
||||
|
||||
def get_bottom_conditional_change_anims(self, *args, **kwargs):
|
||||
return self.get_conditional_change_anims(1, *args, **kwargs)
|
||||
|
||||
def get_prior_rectangles(self):
|
||||
return VGroup(*[
|
||||
self.sample_space.horizontal_parts[i].vertical_parts[0]
|
||||
for i in range(2)
|
||||
])
|
||||
|
||||
def get_posterior_rectangles(self, buff = MED_LARGE_BUFF):
|
||||
prior_rects = self.get_prior_rectangles()
|
||||
areas = [
|
||||
rect.get_width()*rect.get_height()
|
||||
for rect in prior_rects
|
||||
]
|
||||
total_area = sum(areas)
|
||||
total_height = prior_rects.get_height()
|
||||
|
||||
post_rects = prior_rects.copy()
|
||||
for rect, area in zip(post_rects, areas):
|
||||
rect.stretch_to_fit_height(total_height * area/total_area)
|
||||
rect.stretch_to_fit_width(
|
||||
area/rect.get_height()
|
||||
)
|
||||
post_rects.arrange_submobjects(DOWN, buff = 0)
|
||||
post_rects.next_to(
|
||||
self.sample_space, RIGHT, buff
|
||||
)
|
||||
return post_rects
|
||||
|
||||
def get_posterior_rectangle_braces_and_labels(
|
||||
self, post_rects, labels, direction = RIGHT, **kwargs
|
||||
):
|
||||
return self.sample_space.get_subdivision_braces_and_labels(
|
||||
post_rects, labels, direction, **kwargs
|
||||
)
|
||||
|
||||
def update_posterior_braces(self, post_rects):
|
||||
braces = post_rects.braces
|
||||
labels = post_rects.labels
|
||||
for rect, brace, label in zip(post_rects, braces, labels):
|
||||
brace.stretch_to_fit_height(rect.get_height())
|
||||
brace.next_to(rect, RIGHT, SMALL_BUFF)
|
||||
label.next_to(brace, RIGHT, SMALL_BUFF)
|
||||
|
||||
def get_posterior_rectangle_change_anims(self, post_rects):
|
||||
def update_rects(rects):
|
||||
new_rects = self.get_posterior_rectangles()
|
||||
Transform(rects, new_rects).update(1)
|
||||
if hasattr(rects, "braces"):
|
||||
self.update_posterior_braces(rects)
|
||||
return rects
|
||||
|
||||
anims = [UpdateFromFunc(post_rects, update_rects)]
|
||||
if hasattr(post_rects, "braces"):
|
||||
anims += map(Animation, [
|
||||
post_rects.labels, post_rects.braces
|
||||
])
|
||||
return anims
|
||||
|
||||
class SampleSpace(Rectangle):
|
||||
CONFIG = {
|
||||
"height" : 3,
|
||||
"width" : 3,
|
||||
"fill_color" : DARK_GREY,
|
||||
"fill_opacity" : 1,
|
||||
"stroke_width" : 0.5,
|
||||
"stroke_color" : LIGHT_GREY,
|
||||
##
|
||||
"default_label_scale_val" : 1,
|
||||
}
|
||||
def add_title(self, title = "Sample space", buff = MED_SMALL_BUFF):
|
||||
##TODO, should this really exist in SampleSpaceScene
|
||||
title_mob = TextMobject(title)
|
||||
if title_mob.get_width() > self.get_width():
|
||||
title_mob.scale_to_fit_width(self.get_width())
|
||||
title_mob.next_to(self, UP, buff = buff)
|
||||
self.title = title_mob
|
||||
self.add(title_mob)
|
||||
|
||||
def add_label(self, label):
|
||||
self.label = label
|
||||
|
||||
def complete_p_list(self, p_list):
|
||||
new_p_list = list(tuplify(p_list))
|
||||
remainder = 1.0 - sum(new_p_list)
|
||||
if abs(remainder) > EPSILON:
|
||||
new_p_list.append(remainder)
|
||||
return new_p_list
|
||||
|
||||
def get_division_along_dimension(self, p_list, dim, colors, vect):
|
||||
p_list = self.complete_p_list(p_list)
|
||||
colors = color_gradient(colors, len(p_list))
|
||||
|
||||
last_point = self.get_edge_center(-vect)
|
||||
parts = VGroup()
|
||||
for factor, color in zip(p_list, colors):
|
||||
part = SampleSpace()
|
||||
part.set_fill(color, 1)
|
||||
part.replace(self, stretch = True)
|
||||
part.stretch(factor, dim)
|
||||
part.move_to(last_point, -vect)
|
||||
last_point = part.get_edge_center(vect)
|
||||
parts.add(part)
|
||||
return parts
|
||||
|
||||
def get_horizontal_division(
|
||||
self, p_list,
|
||||
colors = [GREEN_E, BLUE_E],
|
||||
vect = DOWN
|
||||
):
|
||||
return self.get_division_along_dimension(p_list, 1, colors, vect)
|
||||
|
||||
def get_vertical_division(
|
||||
self, p_list,
|
||||
colors = [MAROON_B, YELLOW],
|
||||
vect = RIGHT
|
||||
):
|
||||
return self.get_division_along_dimension(p_list, 0, colors, vect)
|
||||
|
||||
def divide_horizontally(self, *args, **kwargs):
|
||||
self.horizontal_parts = self.get_horizontal_division(*args, **kwargs)
|
||||
self.add(self.horizontal_parts)
|
||||
|
||||
def divide_vertically(self, *args, **kwargs):
|
||||
self.vertical_parts = self.get_vertical_division(*args, **kwargs)
|
||||
self.add(self.vertical_parts)
|
||||
|
||||
def get_subdivision_braces_and_labels(
|
||||
self, parts, labels, direction,
|
||||
buff = SMALL_BUFF,
|
||||
min_num_quads = 1
|
||||
):
|
||||
label_mobs = VGroup()
|
||||
braces = VGroup()
|
||||
for label, part in zip(labels, parts):
|
||||
brace = Brace(
|
||||
part, direction,
|
||||
min_num_quads = min_num_quads,
|
||||
buff = buff
|
||||
)
|
||||
if isinstance(label, Mobject):
|
||||
label_mob = label
|
||||
else:
|
||||
label_mob = TexMobject(label)
|
||||
label_mob.scale(self.default_label_scale_val)
|
||||
label_mob.next_to(brace, direction, buff)
|
||||
|
||||
braces.add(brace)
|
||||
label_mobs.add(label_mob)
|
||||
parts.braces = braces
|
||||
parts.labels = label_mobs
|
||||
parts.label_kwargs = {
|
||||
"labels" : label_mobs.copy(),
|
||||
"direction" : direction,
|
||||
"buff" : buff,
|
||||
}
|
||||
return VGroup(parts.braces, parts.labels)
|
||||
|
||||
def get_side_braces_and_labels(self, labels, direction = LEFT, **kwargs):
|
||||
assert(hasattr(self, "horizontal_parts"))
|
||||
parts = self.horizontal_parts
|
||||
return self.get_subdivision_braces_and_labels(parts, labels, direction, **kwargs)
|
||||
|
||||
def get_top_braces_and_labels(self, labels, **kwargs):
|
||||
assert(hasattr(self, "vertical_parts"))
|
||||
parts = self.vertical_parts
|
||||
return self.get_subdivision_braces_and_labels(parts, labels, UP, **kwargs)
|
||||
|
||||
def get_bottom_braces_and_labels(self, labels, **kwargs):
|
||||
assert(hasattr(self, "vertical_parts"))
|
||||
parts = self.vertical_parts
|
||||
return self.get_subdivision_braces_and_labels(parts, labels, DOWN, **kwargs)
|
||||
|
||||
def add_braces_and_labels(self):
|
||||
for attr in "horizontal_parts", "vertical_parts":
|
||||
if not hasattr(self, attr):
|
||||
continue
|
||||
parts = getattr(self, attr)
|
||||
for subattr in "braces", "labels":
|
||||
if hasattr(parts, subattr):
|
||||
self.add(getattr(parts, subattr))
|
||||
|
||||
def __getitem__(self, index):
|
||||
if hasattr(self, "horizontal_parts"):
|
||||
return self.horizontal_parts[index]
|
||||
elif hasattr(self, "vertical_parts"):
|
||||
return self.vertical_parts[index]
|
||||
return self.split()[index]
|
||||
|
||||
class BarChart(VGroup):
|
||||
CONFIG = {
|
||||
"height" : 4,
|
||||
"width" : 6,
|
||||
"n_ticks" : 4,
|
||||
"tick_width" : 0.2,
|
||||
"label_y_axis" : True,
|
||||
"y_axis_label_height" : 0.25,
|
||||
"max_value" : 1,
|
||||
"bar_colors" : [BLUE, YELLOW],
|
||||
"bar_fill_opacity" : 0.8,
|
||||
"bar_stroke_width" : 3,
|
||||
"bar_names" : [],
|
||||
"bar_label_scale_val" : 0.75,
|
||||
}
|
||||
def __init__(self, values, **kwargs):
|
||||
VGroup.__init__(self, **kwargs)
|
||||
if self.max_value is None:
|
||||
self.max_value = max(values)
|
||||
|
||||
self.add_axes()
|
||||
self.add_bars(values)
|
||||
self.center()
|
||||
|
||||
def add_axes(self):
|
||||
x_axis = Line(self.tick_width*LEFT/2, self.width*RIGHT)
|
||||
y_axis = Line(MED_LARGE_BUFF*DOWN, self.height*UP)
|
||||
ticks = VGroup()
|
||||
heights = np.linspace(0, self.height, self.n_ticks+1)
|
||||
values = np.linspace(0, self.max_value, self.n_ticks+1)
|
||||
for y, value in zip(heights, values):
|
||||
tick = Line(LEFT, RIGHT)
|
||||
tick.scale_to_fit_width(self.tick_width)
|
||||
tick.move_to(y*UP)
|
||||
ticks.add(tick)
|
||||
y_axis.add(ticks)
|
||||
|
||||
self.add(x_axis, y_axis)
|
||||
self.x_axis, self.y_axis = x_axis, y_axis
|
||||
|
||||
if self.label_y_axis:
|
||||
labels = VGroup()
|
||||
for tick, value in zip(ticks, values):
|
||||
label = TexMobject(str(np.round(value, 2)))
|
||||
label.scale_to_fit_height(self.y_axis_label_height)
|
||||
label.next_to(tick, LEFT, SMALL_BUFF)
|
||||
labels.add(label)
|
||||
self.y_axis_labels = labels
|
||||
self.add(labels)
|
||||
|
||||
|
||||
def add_bars(self, values):
|
||||
buff = float(self.width) / (2*len(values) + 1)
|
||||
bars = VGroup()
|
||||
for i, value in enumerate(values):
|
||||
bar = Rectangle(
|
||||
height = (value/self.max_value)*self.height,
|
||||
width = buff,
|
||||
stroke_width = self.bar_stroke_width,
|
||||
fill_opacity = self.bar_fill_opacity,
|
||||
)
|
||||
bar.move_to((2*i+1)*buff*RIGHT, DOWN+LEFT)
|
||||
bars.add(bar)
|
||||
bars.set_color_by_gradient(*self.bar_colors)
|
||||
|
||||
bar_labels = VGroup()
|
||||
for bar, name in zip(bars, self.bar_names):
|
||||
label = TexMobject(str(name))
|
||||
label.scale(self.bar_label_scale_val)
|
||||
label.next_to(bar, DOWN, SMALL_BUFF)
|
||||
bar_labels.add(label)
|
||||
|
||||
self.add(bars, bar_labels)
|
||||
self.bars = bars
|
||||
self.bar_labels = bar_labels
|
||||
|
||||
def change_bar_values(self, values):
|
||||
for bar, value in zip(self.bars, values):
|
||||
bar_bottom = bar.get_bottom()
|
||||
bar.stretch_to_fit_height(
|
||||
(value/self.max_value)*self.height
|
||||
)
|
||||
bar.move_to(bar_bottom, DOWN)
|
||||
|
||||
def copy(self):
|
||||
return self.deepcopy()
|
||||
|
||||
|
||||
### Cards ###
|
||||
|
||||
class DeckOfCards(VGroup):
|
||||
def __init__(self, **kwargs):
|
||||
possible_values = map(str, range(1, 11)) + ["J", "Q", "K"]
|
||||
possible_suits = ["hearts", "diamonds", "spades", "clubs"]
|
||||
VGroup.__init__(self, *[
|
||||
PlayingCard(value = value, suit = suit, **kwargs)
|
||||
for value in possible_values
|
||||
for suit in possible_suits
|
||||
])
|
||||
|
||||
class PlayingCard(VGroup):
|
||||
CONFIG = {
|
||||
"value" : None,
|
||||
"suit" : None,
|
||||
"key" : None, ##String like "8H" or "KS"
|
||||
"height" : 2,
|
||||
"height_to_width" : 3.5/2.5,
|
||||
"card_height_to_symbol_height" : 7,
|
||||
"card_width_to_corner_num_width" : 10,
|
||||
"card_height_to_corner_num_height" : 10,
|
||||
"color" : LIGHT_GREY,
|
||||
"turned_over" : False,
|
||||
"possible_suits" : ["hearts", "diamonds", "spades", "clubs"],
|
||||
"possible_values" : map(str, range(2, 11)) + ["J", "Q", "K", "A"],
|
||||
}
|
||||
|
||||
def __init__(self, key = None, **kwargs):
|
||||
VGroup.__init__(self, key = key, **kwargs)
|
||||
|
||||
def generate_points(self):
|
||||
self.add(Rectangle(
|
||||
height = self.height,
|
||||
width = self.height/self.height_to_width,
|
||||
stroke_color = WHITE,
|
||||
stroke_width = 2,
|
||||
fill_color = self.color,
|
||||
fill_opacity = 1,
|
||||
))
|
||||
if self.turned_over:
|
||||
self.set_fill(DARK_GREY)
|
||||
self.set_stroke(LIGHT_GREY)
|
||||
contents = VectorizedPoint(self.get_center())
|
||||
else:
|
||||
value = self.get_value()
|
||||
symbol = self.get_symbol()
|
||||
design = self.get_design(value, symbol)
|
||||
corner_numbers = self.get_corner_numbers(value, symbol)
|
||||
contents = VGroup(design, corner_numbers)
|
||||
self.design = design
|
||||
self.corner_numbers = corner_numbers
|
||||
self.add(contents)
|
||||
|
||||
def get_value(self):
|
||||
value = self.value
|
||||
if value is None:
|
||||
if self.key is not None:
|
||||
value = self.key[:-1]
|
||||
else:
|
||||
value = random.choice(self.possible_values)
|
||||
value = string.upper(str(value))
|
||||
if value == "1":
|
||||
value = "A"
|
||||
if value not in self.possible_values:
|
||||
raise Exception("Invalid card value")
|
||||
|
||||
face_card_to_value = {
|
||||
"J" : 11,
|
||||
"Q" : 12,
|
||||
"K" : 13,
|
||||
"A" : 14,
|
||||
}
|
||||
try:
|
||||
self.numerical_value = int(value)
|
||||
except:
|
||||
self.numerical_value = face_card_to_value[value]
|
||||
return value
|
||||
|
||||
def get_symbol(self):
|
||||
suit = self.suit
|
||||
if suit is None:
|
||||
if self.key is not None:
|
||||
suit = dict([
|
||||
(string.upper(s[0]), s)
|
||||
for s in self.possible_suits
|
||||
])[string.upper(self.key[-1])]
|
||||
else:
|
||||
suit = random.choice(self.possible_suits)
|
||||
if suit not in self.possible_suits:
|
||||
raise Exception("Invalud suit value")
|
||||
self.suit = suit
|
||||
symbol_height = float(self.height) / self.card_height_to_symbol_height
|
||||
symbol = SuitSymbol(suit, height = symbol_height)
|
||||
return symbol
|
||||
|
||||
def get_design(self, value, symbol):
|
||||
if value == "A":
|
||||
return self.get_ace_design(symbol)
|
||||
if value in map(str, range(2, 11)):
|
||||
return self.get_number_design(value, symbol)
|
||||
else:
|
||||
return self.get_face_card_design(value, symbol)
|
||||
|
||||
def get_ace_design(self, symbol):
|
||||
design = symbol.copy().scale(1.5)
|
||||
design.move_to(self)
|
||||
return design
|
||||
|
||||
def get_number_design(self, value, symbol):
|
||||
num = int(value)
|
||||
n_rows = {
|
||||
2 : 2,
|
||||
3 : 3,
|
||||
4 : 2,
|
||||
5 : 2,
|
||||
6 : 3,
|
||||
7 : 3,
|
||||
8 : 3,
|
||||
9 : 4,
|
||||
10 : 4,
|
||||
}[num]
|
||||
n_cols = 1 if num in [2, 3] else 2
|
||||
insertion_indices = {
|
||||
5 : [0],
|
||||
7 : [0],
|
||||
8 : [0, 1],
|
||||
9 : [1],
|
||||
10 : [0, 2],
|
||||
}.get(num, [])
|
||||
|
||||
top = self.get_top() + symbol.get_height()*DOWN
|
||||
bottom = self.get_bottom() + symbol.get_height()*UP
|
||||
column_points = [
|
||||
interpolate(top, bottom, alpha)
|
||||
for alpha in np.linspace(0, 1, n_rows)
|
||||
]
|
||||
|
||||
design = VGroup(*[
|
||||
symbol.copy().move_to(point)
|
||||
for point in column_points
|
||||
])
|
||||
if n_cols == 2:
|
||||
space = 0.2*self.get_width()
|
||||
column_copy = design.copy().shift(space*RIGHT)
|
||||
design.shift(space*LEFT)
|
||||
design.add(*column_copy)
|
||||
design.add(*[
|
||||
symbol.copy().move_to(
|
||||
center_of_mass(column_points[i:i+2])
|
||||
)
|
||||
for i in insertion_indices
|
||||
])
|
||||
for symbol in design:
|
||||
if symbol.get_center()[1] < self.get_center()[1]:
|
||||
symbol.rotate_in_place(np.pi)
|
||||
return design
|
||||
|
||||
def get_face_card_design(self, value, symbol):
|
||||
from topics.characters import PiCreature
|
||||
sub_rect = Rectangle(
|
||||
stroke_color = BLACK,
|
||||
fill_opacity = 0,
|
||||
height = 0.9*self.get_height(),
|
||||
width = 0.6*self.get_width(),
|
||||
)
|
||||
sub_rect.move_to(self)
|
||||
|
||||
# pi_color = average_color(symbol.get_color(), GREY)
|
||||
pi_color = symbol.get_color()
|
||||
pi_mode = {
|
||||
"J" : "plain",
|
||||
"Q" : "thinking",
|
||||
"K" : "hooray"
|
||||
}[value]
|
||||
pi_creature = PiCreature(
|
||||
mode = pi_mode,
|
||||
color = pi_color,
|
||||
)
|
||||
pi_creature.scale_to_fit_width(0.8*sub_rect.get_width())
|
||||
if value in ["Q", "K"]:
|
||||
prefix = "king" if value == "K" else "queen"
|
||||
crown = SVGMobject(file_name = prefix + "_crown")
|
||||
crown.set_stroke(width = 0)
|
||||
crown.set_fill(YELLOW, 1)
|
||||
crown.stretch_to_fit_width(0.5*sub_rect.get_width())
|
||||
crown.stretch_to_fit_height(0.17*sub_rect.get_height())
|
||||
crown.move_to(pi_creature.eyes.get_center(), DOWN)
|
||||
pi_creature.add_to_back(crown)
|
||||
to_top_buff = 0
|
||||
else:
|
||||
to_top_buff = SMALL_BUFF*sub_rect.get_height()
|
||||
pi_creature.next_to(sub_rect.get_top(), DOWN, to_top_buff)
|
||||
# pi_creature.shift(0.05*sub_rect.get_width()*RIGHT)
|
||||
|
||||
pi_copy = pi_creature.copy()
|
||||
pi_copy.rotate(np.pi, about_point = sub_rect.get_center())
|
||||
|
||||
return VGroup(sub_rect, pi_creature, pi_copy)
|
||||
|
||||
def get_corner_numbers(self, value, symbol):
|
||||
value_mob = TextMobject(value)
|
||||
width = self.get_width()/self.card_width_to_corner_num_width
|
||||
height = self.get_height()/self.card_height_to_corner_num_height
|
||||
value_mob.scale_to_fit_width(width)
|
||||
value_mob.stretch_to_fit_height(height)
|
||||
value_mob.next_to(
|
||||
self.get_corner(UP+LEFT), DOWN+RIGHT,
|
||||
buff = MED_LARGE_BUFF*width
|
||||
)
|
||||
value_mob.set_color(symbol.get_color())
|
||||
corner_symbol = symbol.copy()
|
||||
corner_symbol.scale_to_fit_width(width)
|
||||
corner_symbol.next_to(
|
||||
value_mob, DOWN,
|
||||
buff = MED_SMALL_BUFF*width
|
||||
)
|
||||
corner_group = VGroup(value_mob, corner_symbol)
|
||||
opposite_corner_group = corner_group.copy()
|
||||
opposite_corner_group.rotate(
|
||||
np.pi, about_point = self.get_center()
|
||||
)
|
||||
|
||||
return VGroup(corner_group, opposite_corner_group)
|
||||
|
||||
class SuitSymbol(SVGMobject):
|
||||
CONFIG = {
|
||||
"height" : 0.5,
|
||||
"fill_opacity" : 1,
|
||||
"stroke_width" : 0,
|
||||
"red" : "#D02028",
|
||||
"black" : BLACK,
|
||||
}
|
||||
def __init__(self, suit_name, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
suits_to_colors = {
|
||||
"hearts" : self.red,
|
||||
"diamonds" : self.red,
|
||||
"spades" : self.black,
|
||||
"clubs" : self.black,
|
||||
}
|
||||
if suit_name not in suits_to_colors:
|
||||
raise Exception("Invalid suit name")
|
||||
SVGMobject.__init__(self, file_name = suit_name, **kwargs)
|
||||
|
||||
color = suits_to_colors[suit_name]
|
||||
self.set_stroke(width = 0)
|
||||
self.set_fill(color, 1)
|
||||
self.scale_to_fit_height(self.height)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import numpy as np
|
||||
|
||||
from scipy import linalg
|
||||
from utils.simple_functions import choose
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
from colour import Color
|
||||
import numpy as np
|
||||
import random
|
||||
|
||||
from colour import Color
|
||||
from constants import WHITE
|
||||
from constants import PALETTE
|
||||
|
||||
from utils.bezier import interpolate
|
||||
|
||||
def color_to_rgb(color):
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import numpy as np
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
from constants import RASTER_IMAGE_DIR
|
||||
import os
|
||||
|
||||
def get_full_raster_image_path(image_file_name):
|
||||
possible_paths = [
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import numpy as np
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
|
||||
def remove_list_redundancies(l):
|
||||
"""
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import numpy as np
|
||||
|
||||
from constants import OUT
|
||||
from utils.bezier import interpolate
|
||||
from utils.space_ops import rotation_matrix
|
||||
from constants import OUT
|
||||
|
||||
STRAIGHT_PATH_THRESHOLD = 0.01
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import numpy as np
|
||||
from utils.simple_functions import sigmoid
|
||||
|
||||
from utils.bezier import bezier
|
||||
from utils.simple_functions import sigmoid
|
||||
|
||||
def smooth(t, inflection = 10.0):
|
||||
error = sigmoid(-inflection / 2)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import operator as op
|
||||
import numpy as np
|
||||
import operator as op
|
||||
|
||||
def sigmoid(x):
|
||||
return 1.0/(1 + np.exp(-x))
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import numpy as np
|
||||
from constants import RIGHT, OUT
|
||||
|
||||
from constants import OUT
|
||||
from constants import RIGHT
|
||||
|
||||
#Matrix operations
|
||||
|
||||
|
@ -99,12 +101,3 @@ def center_of_mass(points):
|
|||
points = [np.array(point).astype("float") for point in points]
|
||||
return sum(points) / len(points)
|
||||
|
||||
|
||||
# TODO: It feels like this should live elsewhere
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -17,3 +17,6 @@ def initials(name, sep_values = [" ", "_"]):
|
|||
|
||||
def camel_case_initials(name):
|
||||
return filter(lambda c : c.isupper(), name)
|
||||
|
||||
def complex_string(complex_num):
|
||||
return filter(lambda c : c not in "()", str(complex_num))
|
||||
|
|
Loading…
Add table
Reference in a new issue