mirror of
https://github.com/3b1b/manim.git
synced 2026-04-26 03:00:23 -04:00
Merge pull request #197 from 3b1b/PEP8
Changed all files to (mostly) conform to PEP8
This commit is contained in:
@@ -7,28 +7,30 @@ from utils.rate_functions import smooth
|
||||
from utils.config_ops import instantiate
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
|
||||
class Animation(object):
|
||||
CONFIG = {
|
||||
"run_time" : DEFAULT_ANIMATION_RUN_TIME,
|
||||
"rate_func" : smooth,
|
||||
"name" : None,
|
||||
#Does this animation add or remove a mobject form the screen
|
||||
"remover" : False,
|
||||
#Options are lagged_start, smoothed_lagged_start,
|
||||
#one_at_a_time, all_at_once
|
||||
"submobject_mode" : "all_at_once",
|
||||
"lag_factor" : 2,
|
||||
"run_time": DEFAULT_ANIMATION_RUN_TIME,
|
||||
"rate_func": smooth,
|
||||
"name": None,
|
||||
# Does this animation add or remove a mobject form the screen
|
||||
"remover": False,
|
||||
# Options are lagged_start, smoothed_lagged_start,
|
||||
# one_at_a_time, all_at_once
|
||||
"submobject_mode": "all_at_once",
|
||||
"lag_factor": 2,
|
||||
# Used by EmptyAnimation to announce itself ignorable
|
||||
# in Successions and AnimationGroups
|
||||
"empty" : False
|
||||
"empty": False
|
||||
}
|
||||
|
||||
def __init__(self, mobject, **kwargs):
|
||||
mobject = instantiate(mobject)
|
||||
assert(isinstance(mobject, Mobject))
|
||||
digest_config(self, kwargs, locals())
|
||||
self.starting_mobject = self.mobject.copy()
|
||||
if self.rate_func is None:
|
||||
self.rate_func = (lambda x : x)
|
||||
self.rate_func = (lambda x: x)
|
||||
if self.name is None:
|
||||
self.name = self.__class__.__name__ + str(self.mobject)
|
||||
self.all_families_zipped = self.get_all_families_zipped()
|
||||
@@ -37,7 +39,7 @@ class Animation(object):
|
||||
def update_config(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
if "rate_func" in kwargs and kwargs["rate_func"] is None:
|
||||
self.rate_func = (lambda x : x)
|
||||
self.rate_func = (lambda x: x)
|
||||
return self
|
||||
|
||||
def __str__(self):
|
||||
@@ -58,13 +60,13 @@ class Animation(object):
|
||||
return self
|
||||
|
||||
def update_submobject(self, submobject, starting_sumobject, alpha):
|
||||
#Typically ipmlemented by subclass
|
||||
# Typically ipmlemented by subclass
|
||||
pass
|
||||
|
||||
def get_all_mobjects(self):
|
||||
"""
|
||||
Ordering must match the ording of arguments to update_submobject
|
||||
"""
|
||||
"""
|
||||
return self.mobject, self.starting_mobject
|
||||
|
||||
def get_all_families_zipped(self):
|
||||
@@ -75,15 +77,15 @@ class Animation(object):
|
||||
|
||||
def get_sub_alpha(self, alpha, index, num_submobjects):
|
||||
if self.submobject_mode in ["lagged_start", "smoothed_lagged_start"]:
|
||||
prop = float(index)/num_submobjects
|
||||
prop = float(index) / num_submobjects
|
||||
if self.submobject_mode is "smoothed_lagged_start":
|
||||
prop = smooth(prop)
|
||||
lf = self.lag_factor
|
||||
return np.clip(lf*alpha - (lf-1)*prop, 0, 1)
|
||||
return np.clip(lf * alpha - (lf - 1) * prop, 0, 1)
|
||||
elif self.submobject_mode == "one_at_a_time":
|
||||
lower = float(index)/num_submobjects
|
||||
upper = float(index+1)/num_submobjects
|
||||
return np.clip((alpha-lower)/(upper-lower), 0, 1)
|
||||
lower = float(index) / num_submobjects
|
||||
upper = float(index + 1) / num_submobjects
|
||||
return np.clip((alpha - lower) / (upper - lower), 0, 1)
|
||||
elif self.submobject_mode == "all_at_once":
|
||||
return alpha
|
||||
raise Exception("Invalid submobject mode")
|
||||
@@ -101,7 +103,8 @@ class Animation(object):
|
||||
|
||||
def set_rate_func(self, rate_func):
|
||||
if rate_func is None:
|
||||
rate_func = lambda x : x
|
||||
def rate_func(x):
|
||||
return x
|
||||
self.rate_func = rate_func
|
||||
return self
|
||||
|
||||
@@ -115,7 +118,7 @@ class Animation(object):
|
||||
def is_remover(self):
|
||||
return self.remover
|
||||
|
||||
def clean_up(self, surrounding_scene = None):
|
||||
def clean_up(self, surrounding_scene=None):
|
||||
self.update(1)
|
||||
if surrounding_scene is not None:
|
||||
if self.is_remover():
|
||||
@@ -123,16 +126,3 @@ class Animation(object):
|
||||
else:
|
||||
surrounding_scene.add(self.mobject)
|
||||
return self
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,25 +14,28 @@ from utils.bezier import inverse_interpolate
|
||||
from utils.config_ops import digest_config
|
||||
from utils.rate_functions import squish_rate_func
|
||||
|
||||
|
||||
class EmptyAnimation(Animation):
|
||||
CONFIG = {
|
||||
"run_time" : 0,
|
||||
"empty" : True
|
||||
"run_time": 0,
|
||||
"empty": True
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
return Animation.__init__(self, Group(), *args, **kwargs)
|
||||
|
||||
|
||||
class Succession(Animation):
|
||||
CONFIG = {
|
||||
"rate_func" : None,
|
||||
"rate_func": None,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Each arg will either be an animation, or an animation class
|
||||
followed by its arguments (and potentially a dict for
|
||||
Each arg will either be an animation, or an animation class
|
||||
followed by its arguments (and potentially a dict for
|
||||
configuration).
|
||||
For example,
|
||||
For example,
|
||||
Succession(
|
||||
ShowCreation(circle),
|
||||
Transform, circle, square,
|
||||
@@ -42,16 +45,17 @@ class Succession(Animation):
|
||||
"""
|
||||
animations = []
|
||||
state = {
|
||||
"animations" : animations,
|
||||
"curr_class" : None,
|
||||
"curr_class_args" : [],
|
||||
"curr_class_config" : {},
|
||||
"animations": animations,
|
||||
"curr_class": None,
|
||||
"curr_class_args": [],
|
||||
"curr_class_config": {},
|
||||
}
|
||||
|
||||
def invoke_curr_class(state):
|
||||
if state["curr_class"] is None:
|
||||
return
|
||||
anim = state["curr_class"](
|
||||
*state["curr_class_args"],
|
||||
*state["curr_class_args"],
|
||||
**state["curr_class_config"]
|
||||
)
|
||||
state["animations"].append(anim)
|
||||
@@ -76,43 +80,47 @@ class Succession(Animation):
|
||||
for anim in animations:
|
||||
anim.update(0)
|
||||
|
||||
animations = filter (lambda x : not(x.empty), animations)
|
||||
animations = filter(lambda x: not(x.empty), animations)
|
||||
|
||||
self.run_times = [anim.run_time for anim in animations]
|
||||
if "run_time" in kwargs:
|
||||
run_time = kwargs.pop("run_time")
|
||||
warnings.warn("Succession doesn't currently support explicit run_time.")
|
||||
warnings.warn(
|
||||
"Succession doesn't currently support explicit run_time.")
|
||||
run_time = sum(self.run_times)
|
||||
self.num_anims = len(animations)
|
||||
if self.num_anims == 0:
|
||||
self.empty = True
|
||||
self.animations = animations
|
||||
#Have to keep track of this run_time, because Scene.play
|
||||
#might very well mess with it.
|
||||
# Have to keep track of this run_time, because Scene.play
|
||||
# might very well mess with it.
|
||||
self.original_run_time = run_time
|
||||
|
||||
# critical_alphas[i] is the start alpha of self.animations[i]
|
||||
# critical_alphas[i + 1] is the end alpha of self.animations[i]
|
||||
critical_times = np.concatenate(([0], np.cumsum(self.run_times)))
|
||||
self.critical_alphas = map (lambda x : np.true_divide(x, run_time), critical_times) if self.num_anims > 0 else [0.0]
|
||||
self.critical_alphas = map(lambda x: np.true_divide(
|
||||
x, run_time), critical_times) if self.num_anims > 0 else [0.0]
|
||||
|
||||
# self.scene_mobjects_at_time[i] is the scene's mobjects at start of self.animations[i]
|
||||
# self.scene_mobjects_at_time[i + 1] is the scene mobjects at end of self.animations[i]
|
||||
self.scene_mobjects_at_time = [None for i in range(self.num_anims + 1)]
|
||||
self.scene_mobjects_at_time[0] = Group()
|
||||
for i in range(self.num_anims):
|
||||
self.scene_mobjects_at_time[i + 1] = self.scene_mobjects_at_time[i].copy()
|
||||
self.scene_mobjects_at_time[i +
|
||||
1] = self.scene_mobjects_at_time[i].copy()
|
||||
self.animations[i].clean_up(self.scene_mobjects_at_time[i + 1])
|
||||
|
||||
self.current_alpha = 0
|
||||
self.current_anim_index = 0 # If self.num_anims == 0, this is an invalid index, but so it goes
|
||||
# If self.num_anims == 0, this is an invalid index, but so it goes
|
||||
self.current_anim_index = 0
|
||||
if self.num_anims > 0:
|
||||
self.mobject = self.scene_mobjects_at_time[0]
|
||||
self.mobject.add(self.animations[0].mobject)
|
||||
else:
|
||||
self.mobject = Group()
|
||||
|
||||
Animation.__init__(self, self.mobject, run_time = run_time, **kwargs)
|
||||
Animation.__init__(self, self.mobject, run_time=run_time, **kwargs)
|
||||
|
||||
# Beware: This does NOT take care of calling update(0) on the subanimation.
|
||||
# This was important to avoid a pernicious possibility in which subanimations were called
|
||||
@@ -120,7 +128,8 @@ class Succession(Animation):
|
||||
# continuing exponentially.
|
||||
def jump_to_start_of_anim(self, index):
|
||||
if index != self.current_anim_index:
|
||||
self.mobject.remove(*self.mobject.submobjects) # Should probably have a cleaner "remove_all" method...
|
||||
# Should probably have a cleaner "remove_all" method...
|
||||
self.mobject.remove(*self.mobject.submobjects)
|
||||
self.mobject.add(*self.scene_mobjects_at_time[index].submobjects)
|
||||
self.mobject.add(self.animations[index].mobject)
|
||||
|
||||
@@ -138,18 +147,18 @@ class Succession(Animation):
|
||||
return
|
||||
|
||||
gt_alpha_iter = it.ifilter(
|
||||
lambda i : self.critical_alphas[i+1] >= alpha,
|
||||
lambda i: self.critical_alphas[i + 1] >= alpha,
|
||||
range(self.num_anims)
|
||||
)
|
||||
i = next(gt_alpha_iter, None)
|
||||
if i == None:
|
||||
# In this case, we assume what is happening is that alpha is 1.0,
|
||||
if i is None:
|
||||
# In this case, we assume what is happening is that alpha is 1.0,
|
||||
# but that rounding error is causing us to overshoot the end of
|
||||
# self.critical_alphas (which is also 1.0)
|
||||
if not abs(alpha - 1) < 0.001:
|
||||
warnings.warn(
|
||||
"Rounding error not near alpha=1 in Succession.update_mobject," + \
|
||||
"instead alpha = %f"%alpha
|
||||
"Rounding error not near alpha=1 in Succession.update_mobject," +
|
||||
"instead alpha = %f" % alpha
|
||||
)
|
||||
print self.critical_alphas, alpha
|
||||
i = self.num_anims - 1
|
||||
@@ -158,8 +167,8 @@ class Succession(Animation):
|
||||
|
||||
self.jump_to_start_of_anim(i)
|
||||
sub_alpha = inverse_interpolate(
|
||||
self.critical_alphas[i],
|
||||
self.critical_alphas[i + 1],
|
||||
self.critical_alphas[i],
|
||||
self.critical_alphas[i + 1],
|
||||
alpha
|
||||
)
|
||||
self.animations[i].update(sub_alpha)
|
||||
@@ -171,14 +180,16 @@ class Succession(Animation):
|
||||
for anim in self.animations:
|
||||
anim.clean_up(*args, **kwargs)
|
||||
|
||||
|
||||
class AnimationGroup(Animation):
|
||||
CONFIG = {
|
||||
"rate_func" : None
|
||||
"rate_func": None
|
||||
}
|
||||
|
||||
def __init__(self, *sub_anims, **kwargs):
|
||||
sub_anims = filter (lambda x : not(x.empty), sub_anims)
|
||||
sub_anims = filter(lambda x: not(x.empty), sub_anims)
|
||||
digest_config(self, locals())
|
||||
self.update_config(**kwargs) # Handles propagation to self.sub_anims
|
||||
self.update_config(**kwargs) # Handles propagation to self.sub_anims
|
||||
|
||||
if len(sub_anims) == 0:
|
||||
self.empty = True
|
||||
@@ -198,7 +209,7 @@ class AnimationGroup(Animation):
|
||||
|
||||
def update_config(self, **kwargs):
|
||||
Animation.update_config(self, **kwargs)
|
||||
|
||||
|
||||
# If AnimationGroup is called with any configuration,
|
||||
# it is propagated to the sub_animations
|
||||
for anim in self.sub_anims:
|
||||
@@ -206,30 +217,33 @@ class AnimationGroup(Animation):
|
||||
|
||||
# Variants on mappin an animation over submobjectsg
|
||||
|
||||
|
||||
class LaggedStart(Animation):
|
||||
CONFIG = {
|
||||
"run_time" : 2,
|
||||
"lag_ratio" : 0.5,
|
||||
"run_time": 2,
|
||||
"lag_ratio": 0.5,
|
||||
}
|
||||
def __init__(self, AnimationClass, mobject, arg_creator = None, **kwargs):
|
||||
|
||||
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,)
|
||||
def arg_creator(mobject):
|
||||
return (mobject,)
|
||||
self.subanimations = [
|
||||
AnimationClass(
|
||||
*arg_creator(submob),
|
||||
run_time = self.run_time,
|
||||
rate_func = squish_rate_func(
|
||||
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))
|
||||
mobject,
|
||||
np.linspace(0, 1 - self.lag_ratio, len(mobject))
|
||||
)
|
||||
]
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
@@ -243,6 +257,7 @@ class LaggedStart(Animation):
|
||||
for anim in self.subanimations:
|
||||
anim.clean_up(*args, **kwargs)
|
||||
|
||||
|
||||
class ApplyToCenters(Animation):
|
||||
def __init__(self, AnimationClass, mobjects, **kwargs):
|
||||
full_kwargs = AnimationClass.CONFIG
|
||||
@@ -259,9 +274,8 @@ class ApplyToCenters(Animation):
|
||||
def update_mobject(self, alpha):
|
||||
self.centers_container.update_mobject(alpha)
|
||||
center_mobs = self.centers_container.mobject.split()
|
||||
mobjects = self.mobject.split()
|
||||
mobjects = self.mobject.split()
|
||||
for center_mob, mobject in zip(center_mobs, mobjects):
|
||||
mobject.shift(
|
||||
center_mob.get_center()-mobject.get_center()
|
||||
center_mob.get_center() - mobject.get_center()
|
||||
)
|
||||
|
||||
|
||||
@@ -8,9 +8,7 @@ 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
|
||||
@@ -20,31 +18,34 @@ from utils.rate_functions import there_and_back
|
||||
|
||||
class FocusOn(Transform):
|
||||
CONFIG = {
|
||||
"opacity" : 0.2,
|
||||
"color" : GREY,
|
||||
"run_time" : 2,
|
||||
"remover" : True,
|
||||
"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,
|
||||
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 = 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,
|
||||
"rate_func": there_and_back,
|
||||
"scale_factor": 1.2,
|
||||
"color": YELLOW,
|
||||
}
|
||||
|
||||
def __init__(self, mobject, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
target = mobject.copy()
|
||||
@@ -52,27 +53,31 @@ class Indicate(Transform):
|
||||
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
|
||||
"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 = Circle(color=self.color, **kwargs)
|
||||
circle.surround(mobject)
|
||||
Indicate.__init__(self, circle, **kwargs)
|
||||
|
||||
|
||||
class ShowPassingFlash(ShowPartial):
|
||||
CONFIG = {
|
||||
"time_width" : 0.1,
|
||||
"remover" : True,
|
||||
"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)
|
||||
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):
|
||||
@@ -80,43 +85,49 @@ class ShowPassingFlash(ShowPartial):
|
||||
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,
|
||||
"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,
|
||||
"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
|
||||
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))
|
||||
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
|
||||
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,
|
||||
"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:
|
||||
@@ -126,62 +137,65 @@ class WiggleOutThenIn(Animation):
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_submobject(self, submobject, starting_sumobject, alpha):
|
||||
submobject.points[:,:] = starting_sumobject.points
|
||||
submobject.points[:, :] = starting_sumobject.points
|
||||
submobject.scale(
|
||||
interpolate(1, self.scale_value, there_and_back(alpha)),
|
||||
about_point = self.scale_about_point
|
||||
about_point=self.scale_about_point
|
||||
)
|
||||
submobject.rotate(
|
||||
wiggle(alpha, self.n_wiggles)*self.rotation_angle,
|
||||
about_point = self.rotate_about_point
|
||||
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
|
||||
"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):
|
||||
|
||||
def __init__(self, mobject=None, **kwargs):
|
||||
if mobject is None:
|
||||
mobject = Line(3*LEFT, 3*RIGHT)
|
||||
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
|
||||
self.amplitude / (k**2), # Amplitude
|
||||
np.sin(2 * np.pi * (k**1.5) * t / \
|
||||
self.temporal_period), # Frequency
|
||||
# Number of waves
|
||||
np.sin(2 * np.pi * k * x / self.spatial_period)
|
||||
])
|
||||
for k in range(1, self.overtones+1)
|
||||
for k in range(1, self.overtones + 1)
|
||||
])
|
||||
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
time = alpha*self.run_time
|
||||
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),
|
||||
lambda (x, y, z): (x, y + self.wave_function(x, time), z),
|
||||
1, start.points
|
||||
)
|
||||
|
||||
|
||||
class TurnInsideOut(Transform):
|
||||
CONFIG = {
|
||||
"path_arc" : TAU/4,
|
||||
"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))
|
||||
mob_copy.sort_points(lambda p: -np.linalg.norm(p))
|
||||
Transform.__init__(self, mobject, mob_copy, **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -2,22 +2,22 @@ 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" : {},
|
||||
"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)
|
||||
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)
|
||||
@@ -29,11 +29,13 @@ class Homotopy(Animation):
|
||||
**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):
|
||||
"""
|
||||
@@ -48,21 +50,23 @@ class ComplexHomotopy(Homotopy):
|
||||
|
||||
class PhaseFlow(Animation):
|
||||
CONFIG = {
|
||||
"virtual_time" : 1,
|
||||
"rate_func" : None,
|
||||
"virtual_time": 1,
|
||||
"rate_func": None,
|
||||
}
|
||||
|
||||
def __init__(self, function, mobject, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
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)
|
||||
dt = self.virtual_time * (alpha - self.last_alpha)
|
||||
self.mobject.apply_function(
|
||||
lambda p : p + dt*self.function(p)
|
||||
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())
|
||||
@@ -71,4 +75,3 @@ class MoveAlongPath(Animation):
|
||||
def update_mobject(self, alpha):
|
||||
point = self.path.point_from_proportion(alpha)
|
||||
self.mobject.move_to(point)
|
||||
|
||||
|
||||
@@ -7,13 +7,15 @@ 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,
|
||||
"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(
|
||||
@@ -52,10 +54,14 @@ class ChangingDecimal(Animation):
|
||||
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)
|
||||
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)
|
||||
|
||||
def func(alpha):
|
||||
return interpolate(start_number, target_number, alpha)
|
||||
ChangingDecimal.__init__(self, decimal_number_mobject, func, **kwargs)
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
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,
|
||||
"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)
|
||||
|
||||
@@ -28,22 +27,24 @@ class Rotating(Animation):
|
||||
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 = 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,
|
||||
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,
|
||||
"in_place": False,
|
||||
"about_point": None,
|
||||
}
|
||||
def __init__(self, mobject, angle = np.pi, axis = OUT, **kwargs):
|
||||
|
||||
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:
|
||||
@@ -53,9 +54,8 @@ class Rotate(Transform):
|
||||
if self.in_place:
|
||||
self.about_point = mobject.get_center()
|
||||
target.rotate(
|
||||
angle,
|
||||
axis = axis,
|
||||
about_point = self.about_point,
|
||||
angle,
|
||||
axis=axis,
|
||||
about_point=self.about_point,
|
||||
)
|
||||
Transform.__init__(self, mobject, target, **kwargs)
|
||||
|
||||
|
||||
@@ -11,57 +11,60 @@ 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,
|
||||
"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()
|
||||
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
|
||||
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
|
||||
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,
|
||||
"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,
|
||||
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.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,),
|
||||
lambda c: (c.restore,),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
@@ -17,16 +17,18 @@ 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 = {
|
||||
"path_arc" : 0,
|
||||
"path_arc_axis" : OUT,
|
||||
"path_func" : None,
|
||||
"submobject_mode" : "all_at_once",
|
||||
"replace_mobject_with_target_in_scene" : False,
|
||||
"path_arc": 0,
|
||||
"path_arc_axis": OUT,
|
||||
"path_func": None,
|
||||
"submobject_mode": "all_at_once",
|
||||
"replace_mobject_with_target_in_scene": False,
|
||||
}
|
||||
|
||||
def __init__(self, mobject, target_mobject, **kwargs):
|
||||
#Copy target_mobject so as to not mess with caller
|
||||
# Copy target_mobject so as to not mess with caller
|
||||
self.original_target_mobject = target_mobject
|
||||
target_mobject = target_mobject.copy()
|
||||
mobject.align_data(target_mobject)
|
||||
@@ -35,7 +37,7 @@ class Transform(Animation):
|
||||
self.init_path_func()
|
||||
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
self.name += "To" + str(target_mobject)
|
||||
self.name += "To" + str(target_mobject)
|
||||
|
||||
def update_config(self, **kwargs):
|
||||
Animation.update_config(self, **kwargs)
|
||||
@@ -63,38 +65,45 @@ class Transform(Animation):
|
||||
submob.interpolate(start, end, alpha, self.path_func)
|
||||
return self
|
||||
|
||||
def clean_up(self, surrounding_scene = None):
|
||||
def clean_up(self, surrounding_scene=None):
|
||||
Animation.clean_up(self, surrounding_scene)
|
||||
if self.replace_mobject_with_target_in_scene and surrounding_scene is not None:
|
||||
surrounding_scene.remove(self.mobject)
|
||||
if not self.remover:
|
||||
surrounding_scene.add(self.original_target_mobject)
|
||||
|
||||
|
||||
class ReplacementTransform(Transform):
|
||||
CONFIG = {
|
||||
"replace_mobject_with_target_in_scene" : True,
|
||||
"replace_mobject_with_target_in_scene": True,
|
||||
}
|
||||
|
||||
|
||||
class ClockwiseTransform(Transform):
|
||||
CONFIG = {
|
||||
"path_arc" : -np.pi
|
||||
"path_arc": -np.pi
|
||||
}
|
||||
|
||||
|
||||
class CounterclockwiseTransform(Transform):
|
||||
CONFIG = {
|
||||
"path_arc" : np.pi
|
||||
"path_arc": np.pi
|
||||
}
|
||||
|
||||
|
||||
class MoveToTarget(Transform):
|
||||
def __init__(self, mobject, **kwargs):
|
||||
if not hasattr(mobject, "target"):
|
||||
raise Exception("MoveToTarget called on mobject without attribute 'target' ")
|
||||
raise Exception(
|
||||
"MoveToTarget called on mobject without attribute 'target' ")
|
||||
Transform.__init__(self, mobject, mobject.target, **kwargs)
|
||||
|
||||
|
||||
class ApplyMethod(Transform):
|
||||
CONFIG = {
|
||||
"submobject_mode" : "all_at_once"
|
||||
"submobject_mode": "all_at_once"
|
||||
}
|
||||
|
||||
def __init__(self, method, *args, **kwargs):
|
||||
"""
|
||||
Method is a method of Mobject. *args is for the method,
|
||||
@@ -104,11 +113,11 @@ class ApplyMethod(Transform):
|
||||
"""
|
||||
if not inspect.ismethod(method):
|
||||
raise Exception(
|
||||
"Whoops, looks like you accidentally invoked " + \
|
||||
"the method you want to animate"
|
||||
)
|
||||
"Whoops, looks like you accidentally invoked " +
|
||||
"the method you want to animate"
|
||||
)
|
||||
assert(isinstance(method.im_self, Mobject))
|
||||
args = list(args) #So that args.pop() works
|
||||
args = list(args) # So that args.pop() works
|
||||
if "method_kwargs" in kwargs:
|
||||
method_kwargs = kwargs["method_kwargs"]
|
||||
elif len(args) > 0 and isinstance(args[-1], dict):
|
||||
@@ -119,38 +128,46 @@ class ApplyMethod(Transform):
|
||||
method.im_func(target, *args, **method_kwargs)
|
||||
Transform.__init__(self, method.im_self, target, **kwargs)
|
||||
|
||||
|
||||
class ApplyPointwiseFunction(ApplyMethod):
|
||||
CONFIG = {
|
||||
"run_time" : DEFAULT_POINTWISE_FUNCTION_RUN_TIME
|
||||
"run_time": DEFAULT_POINTWISE_FUNCTION_RUN_TIME
|
||||
}
|
||||
|
||||
def __init__(self, function, mobject, **kwargs):
|
||||
ApplyMethod.__init__(
|
||||
self, mobject.apply_function, function, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class FadeToColor(ApplyMethod):
|
||||
def __init__(self, mobject, color, **kwargs):
|
||||
ApplyMethod.__init__(self, mobject.set_color, color, **kwargs)
|
||||
|
||||
|
||||
class ScaleInPlace(ApplyMethod):
|
||||
def __init__(self, mobject, scale_factor, **kwargs):
|
||||
ApplyMethod.__init__(self, mobject.scale_in_place, scale_factor, **kwargs)
|
||||
ApplyMethod.__init__(self, mobject.scale_in_place,
|
||||
scale_factor, **kwargs)
|
||||
|
||||
|
||||
class ApplyFunction(Transform):
|
||||
CONFIG = {
|
||||
"submobject_mode" : "all_at_once",
|
||||
"submobject_mode": "all_at_once",
|
||||
}
|
||||
|
||||
def __init__(self, function, mobject, **kwargs):
|
||||
Transform.__init__(
|
||||
self,
|
||||
mobject,
|
||||
self,
|
||||
mobject,
|
||||
function(mobject.copy()),
|
||||
**kwargs
|
||||
)
|
||||
self.name = "ApplyFunctionTo"+str(mobject)
|
||||
self.name = "ApplyFunctionTo" + str(mobject)
|
||||
|
||||
|
||||
class ApplyMatrix(ApplyPointwiseFunction):
|
||||
#Truth be told, I'm not sure if this is useful.
|
||||
# Truth be told, I'm not sure if this is useful.
|
||||
def __init__(self, matrix, mobject, **kwargs):
|
||||
matrix = np.array(matrix)
|
||||
if matrix.shape == (2, 2):
|
||||
@@ -160,10 +177,12 @@ class ApplyMatrix(ApplyPointwiseFunction):
|
||||
elif matrix.shape != (3, 3):
|
||||
raise "Matrix has bad dimensions"
|
||||
transpose = np.transpose(matrix)
|
||||
|
||||
def func(p):
|
||||
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:
|
||||
@@ -172,17 +191,19 @@ class ComplexFunction(ApplyPointwiseFunction):
|
||||
)
|
||||
ApplyPointwiseFunction.__init__(
|
||||
self,
|
||||
lambda (x, y, z) : complex_to_R3(function(complex(x, y))),
|
||||
lambda (x, y, z): complex_to_R3(function(complex(x, y))),
|
||||
instantiate(mobject),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
###
|
||||
|
||||
|
||||
class CyclicReplace(Transform):
|
||||
CONFIG = {
|
||||
"path_arc" : np.pi/2
|
||||
"path_arc": np.pi / 2
|
||||
}
|
||||
|
||||
def __init__(self, *mobjects, **kwargs):
|
||||
start = Group(*mobjects)
|
||||
target = Group(*[
|
||||
@@ -191,14 +212,18 @@ class CyclicReplace(Transform):
|
||||
])
|
||||
Transform.__init__(self, start, target, **kwargs)
|
||||
|
||||
class Swap(CyclicReplace):
|
||||
pass #Renaming, more understandable for two entries
|
||||
|
||||
#TODO: Um...does this work
|
||||
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)
|
||||
"rate_func": squish_rate_func(smooth)
|
||||
}
|
||||
|
||||
def __init__(self, start_anim, end_anim, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
if "run_time" in kwargs:
|
||||
@@ -207,15 +232,16 @@ class TransformAnimations(Transform):
|
||||
self.run_time = max(start_anim.run_time, end_anim.run_time)
|
||||
for anim in start_anim, end_anim:
|
||||
anim.set_run_time(self.run_time)
|
||||
|
||||
|
||||
if start_anim.starting_mobject.get_num_points() != end_anim.starting_mobject.get_num_points():
|
||||
start_anim.starting_mobject.align_data(end_anim.starting_mobject)
|
||||
for anim in start_anim, end_anim:
|
||||
if hasattr(anim, "target_mobject"):
|
||||
anim.starting_mobject.align_data(anim.target_mobject)
|
||||
|
||||
Transform.__init__(self, start_anim.mobject, end_anim.mobject, **kwargs)
|
||||
#Rewire starting and ending mobjects
|
||||
Transform.__init__(self, start_anim.mobject,
|
||||
end_anim.mobject, **kwargs)
|
||||
# Rewire starting and ending mobjects
|
||||
start_anim.mobject = self.starting_mobject
|
||||
end_anim.mobject = self.target_mobject
|
||||
|
||||
@@ -223,4 +249,3 @@ class TransformAnimations(Transform):
|
||||
self.start_anim.update(alpha)
|
||||
self.end_anim.update(alpha)
|
||||
Transform.update(self, alpha)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ class UpdateFromFunc(Animation):
|
||||
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)
|
||||
@@ -19,29 +20,27 @@ class UpdateFromFunc(Animation):
|
||||
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
|
||||
"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)
|
||||
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.tracked_mobject.get_critical_point(self.tracked_critical_point) -
|
||||
self.mobject.get_critical_point(self.tracked_critical_point) +
|
||||
self.diff
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"""
|
||||
I won't pretend like this is best practice, by in creating animations for a video,
|
||||
it can be very nice to simply have all of the Mobjects, Animations, Scenes, etc.
|
||||
it can be very nice to simply have all of the Mobjects, Animations, Scenes, etc.
|
||||
of manim available without having to worry about what namespace they come from.
|
||||
|
||||
Rather than having a large pile of "from <module> import *" at the top of every such
|
||||
script, the intent of this file is to make it so that one can just include
|
||||
script, the intent of this file is to make it so that one can just include
|
||||
"from big_ol_pile_of_manim_imports import *". The effects of adding more modules
|
||||
or refactoring the library on current or older scene scripts should be entirely
|
||||
or refactoring the library on current or older scene scripts should be entirely
|
||||
addressible by changing this file.
|
||||
|
||||
Note: One should NOT import from this file for main library code, it is meant only
|
||||
|
||||
306
camera/camera.py
306
camera/camera.py
@@ -1,6 +1,5 @@
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
import os
|
||||
|
||||
import aggdraw
|
||||
import copy
|
||||
@@ -23,28 +22,29 @@ from utils.iterables import list_difference_update
|
||||
from utils.iterables import remove_list_redundancies
|
||||
from utils.simple_functions import fdiv
|
||||
|
||||
|
||||
class Camera(object):
|
||||
CONFIG = {
|
||||
"background_image" : None,
|
||||
"pixel_shape" : (DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH),
|
||||
"background_image": None,
|
||||
"pixel_shape": (DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH),
|
||||
# Note: frame_shape will be resized to match pixel_shape
|
||||
"frame_shape" : (FRAME_HEIGHT, FRAME_WIDTH),
|
||||
"space_center" : ORIGIN,
|
||||
"background_color" : BLACK,
|
||||
#Points in vectorized mobjects with norm greater
|
||||
#than this value will be rescaled.
|
||||
"max_allowable_norm" : FRAME_WIDTH,
|
||||
"image_mode" : "RGBA",
|
||||
"n_rgb_coords" : 4,
|
||||
"background_alpha" : 0, #Out of rgb_max_val
|
||||
"pixel_array_dtype" : 'uint8',
|
||||
"use_z_coordinate_for_display_order" : False,
|
||||
"frame_shape": (FRAME_HEIGHT, FRAME_WIDTH),
|
||||
"space_center": ORIGIN,
|
||||
"background_color": BLACK,
|
||||
# Points in vectorized mobjects with norm greater
|
||||
# than this value will be rescaled.
|
||||
"max_allowable_norm": FRAME_WIDTH,
|
||||
"image_mode": "RGBA",
|
||||
"n_rgb_coords": 4,
|
||||
"background_alpha": 0, # Out of rgb_max_val
|
||||
"pixel_array_dtype": 'uint8',
|
||||
"use_z_coordinate_for_display_order": False,
|
||||
# z_buff_func is only used if the flag above is set to True.
|
||||
# round z coordinate to nearest hundredth when comparring
|
||||
"z_buff_func" : lambda m : np.round(m.get_center()[2], 2),
|
||||
"z_buff_func": lambda m: np.round(m.get_center()[2], 2),
|
||||
}
|
||||
|
||||
def __init__(self, background = None, **kwargs):
|
||||
def __init__(self, background=None, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
self.rgb_max_val = np.iinfo(self.pixel_array_dtype).max
|
||||
self.init_background()
|
||||
@@ -55,22 +55,22 @@ class Camera(object):
|
||||
# This is to address a strange bug where deepcopying
|
||||
# will result in a segfault, which is somehow related
|
||||
# to the aggdraw library
|
||||
self.canvas = None
|
||||
self.canvas = None
|
||||
return copy.copy(self)
|
||||
|
||||
def resize_frame_shape(self, fixed_dimension = 0):
|
||||
def resize_frame_shape(self, fixed_dimension=0):
|
||||
"""
|
||||
Changes frame_shape to match the aspect ratio
|
||||
Changes frame_shape to match the aspect ratio
|
||||
of pixel_shape, where fixed_dimension determines
|
||||
whether frame_shape[0] (height) or frame_shape[1] (width)
|
||||
remains fixed while the other changes accordingly.
|
||||
"""
|
||||
aspect_ratio = float(self.pixel_shape[1])/self.pixel_shape[0]
|
||||
aspect_ratio = float(self.pixel_shape[1]) / self.pixel_shape[0]
|
||||
frame_width, frame_height = self.frame_shape
|
||||
if fixed_dimension == 0:
|
||||
frame_height = aspect_ratio*frame_width
|
||||
frame_height = aspect_ratio * frame_width
|
||||
else:
|
||||
frame_width = frame_height/aspect_ratio
|
||||
frame_width = frame_height / aspect_ratio
|
||||
self.frame_shape = (frame_width, frame_height)
|
||||
|
||||
def init_background(self):
|
||||
@@ -78,53 +78,56 @@ class Camera(object):
|
||||
path = get_full_raster_image_path(self.background_image)
|
||||
image = Image.open(path).convert(self.image_mode)
|
||||
height, width = self.pixel_shape
|
||||
#TODO, how to gracefully handle backgrounds
|
||||
#with different sizes?
|
||||
# TODO, how to gracefully handle backgrounds
|
||||
# with different sizes?
|
||||
self.background = np.array(image)[:height, :width]
|
||||
self.background = self.background.astype(self.pixel_array_dtype)
|
||||
else:
|
||||
background_rgba = color_to_int_rgba(
|
||||
self.background_color, alpha = self.background_alpha
|
||||
self.background_color, alpha=self.background_alpha
|
||||
)
|
||||
self.background = np.zeros(
|
||||
list(self.pixel_shape)+[self.n_rgb_coords],
|
||||
dtype = self.pixel_array_dtype
|
||||
list(self.pixel_shape) + [self.n_rgb_coords],
|
||||
dtype=self.pixel_array_dtype
|
||||
)
|
||||
self.background[:,:] = background_rgba
|
||||
self.background[:, :] = background_rgba
|
||||
|
||||
def get_image(self):
|
||||
return Image.fromarray(
|
||||
self.pixel_array,
|
||||
mode = self.image_mode
|
||||
mode=self.image_mode
|
||||
)
|
||||
|
||||
def get_pixel_array(self):
|
||||
return self.pixel_array
|
||||
|
||||
def convert_pixel_array(self, pixel_array, convert_from_floats = False):
|
||||
def convert_pixel_array(self, pixel_array, convert_from_floats=False):
|
||||
retval = np.array(pixel_array)
|
||||
if convert_from_floats:
|
||||
retval = np.apply_along_axis(
|
||||
lambda f : (f * self.rgb_max_val).astype(self.pixel_array_dtype),
|
||||
lambda f: (
|
||||
f * self.rgb_max_val).astype(self.pixel_array_dtype),
|
||||
2,
|
||||
retval)
|
||||
return retval
|
||||
|
||||
def set_pixel_array(self, pixel_array, convert_from_floats = False):
|
||||
converted_array = self.convert_pixel_array(pixel_array, convert_from_floats)
|
||||
if not (hasattr(self, "pixel_array") and self.pixel_array.shape == converted_array.shape):
|
||||
def set_pixel_array(self, pixel_array, convert_from_floats=False):
|
||||
converted_array = self.convert_pixel_array(
|
||||
pixel_array, convert_from_floats)
|
||||
if not (hasattr(self, "pixel_array") and self.pixel_array.shape == converted_array.shape):
|
||||
self.pixel_array = converted_array
|
||||
else:
|
||||
#Set in place
|
||||
self.pixel_array[:,:,:] = converted_array[:,:,:]
|
||||
# Set in place
|
||||
self.pixel_array[:, :, :] = converted_array[:, :, :]
|
||||
|
||||
def set_background(self, pixel_array, convert_from_floats = False):
|
||||
self.background = self.convert_pixel_array(pixel_array, convert_from_floats)
|
||||
def set_background(self, pixel_array, convert_from_floats=False):
|
||||
self.background = self.convert_pixel_array(
|
||||
pixel_array, convert_from_floats)
|
||||
|
||||
def make_background_from_func(self, coords_to_colors_func):
|
||||
"""
|
||||
Sets background by using coords_to_colors_func to determine each pixel's color. Each input
|
||||
to coords_to_colors_func is an (x, y) pair in space (in ordinary space coordinates; not
|
||||
Sets background by using coords_to_colors_func to determine each pixel's color. Each input
|
||||
to coords_to_colors_func is an (x, y) pair in space (in ordinary space coordinates; not
|
||||
pixel coordinates), and each output is expected to be an RGBA array of 4 floats.
|
||||
"""
|
||||
|
||||
@@ -137,17 +140,18 @@ class Camera(object):
|
||||
)
|
||||
print "Ending set_background; for reference, the current time is ", time.strftime("%H:%M:%S")
|
||||
|
||||
return self.convert_pixel_array(new_background, convert_from_floats = True)
|
||||
return self.convert_pixel_array(new_background, convert_from_floats=True)
|
||||
|
||||
def set_background_from_func(self, coords_to_colors_func):
|
||||
self.set_background(self.make_background_from_func(coords_to_colors_func))
|
||||
self.set_background(
|
||||
self.make_background_from_func(coords_to_colors_func))
|
||||
|
||||
def reset(self):
|
||||
self.set_pixel_array(self.background)
|
||||
|
||||
####
|
||||
|
||||
def extract_mobject_family_members(self, mobjects, only_those_with_points = False):
|
||||
def extract_mobject_family_members(self, mobjects, only_those_with_points=False):
|
||||
if only_those_with_points:
|
||||
method = Mobject.family_members_with_points
|
||||
else:
|
||||
@@ -161,13 +165,13 @@ class Camera(object):
|
||||
))
|
||||
|
||||
def get_mobjects_to_display(
|
||||
self, mobjects,
|
||||
include_submobjects = True,
|
||||
excluded_mobjects = None,
|
||||
):
|
||||
self, mobjects,
|
||||
include_submobjects=True,
|
||||
excluded_mobjects=None,
|
||||
):
|
||||
if include_submobjects:
|
||||
mobjects = self.extract_mobject_family_members(
|
||||
mobjects, only_those_with_points = True
|
||||
mobjects, only_those_with_points=True
|
||||
)
|
||||
if excluded_mobjects:
|
||||
all_excluded = self.extract_mobject_family_members(
|
||||
@@ -192,14 +196,15 @@ class Camera(object):
|
||||
def capture_mobjects(self, mobjects, **kwargs):
|
||||
mobjects = self.get_mobjects_to_display(mobjects, **kwargs)
|
||||
|
||||
# Organize this list into batches of the same type, and
|
||||
# Organize this list into batches of the same type, and
|
||||
# apply corresponding function to those batches
|
||||
type_func_pairs = [
|
||||
(VMobject, self.display_multiple_vectorized_mobjects),
|
||||
(PMobject, self.display_multiple_point_cloud_mobjects),
|
||||
(ImageMobject, self.display_multiple_image_mobjects),
|
||||
(Mobject, lambda batch : batch), #Do nothing
|
||||
(PMobject, self.display_multiple_point_cloud_mobjects),
|
||||
(ImageMobject, self.display_multiple_image_mobjects),
|
||||
(Mobject, lambda batch: batch), # Do nothing
|
||||
]
|
||||
|
||||
def get_mobject_type(mobject):
|
||||
for mobject_type, func in type_func_pairs:
|
||||
if isinstance(mobject, mobject_type):
|
||||
@@ -209,14 +214,14 @@ class Camera(object):
|
||||
)
|
||||
batch_type_pairs = batch_by_property(mobjects, get_mobject_type)
|
||||
|
||||
#Display in these batches
|
||||
# Display in these batches
|
||||
for batch, batch_type in batch_type_pairs:
|
||||
#check what the type is, and call the appropriate function
|
||||
# check what the type is, and call the appropriate function
|
||||
for mobject_type, func in type_func_pairs:
|
||||
if batch_type == mobject_type:
|
||||
func(batch)
|
||||
|
||||
## Methods associated with svg rendering
|
||||
# Methods associated with svg rendering
|
||||
|
||||
def get_aggdraw_canvas(self):
|
||||
if not hasattr(self, "canvas") or not self.canvas:
|
||||
@@ -224,15 +229,15 @@ class Camera(object):
|
||||
return self.canvas
|
||||
|
||||
def reset_aggdraw_canvas(self):
|
||||
image = Image.fromarray(self.pixel_array, mode = self.image_mode)
|
||||
image = Image.fromarray(self.pixel_array, mode=self.image_mode)
|
||||
self.canvas = aggdraw.Draw(image)
|
||||
|
||||
def display_multiple_vectorized_mobjects(self, vmobjects):
|
||||
if len(vmobjects) == 0:
|
||||
return
|
||||
batch_file_pairs = batch_by_property(
|
||||
vmobjects,
|
||||
lambda vm : vm.get_background_image_file()
|
||||
vmobjects,
|
||||
lambda vm: vm.get_background_image_file()
|
||||
)
|
||||
for batch, file_name in batch_file_pairs:
|
||||
if file_name:
|
||||
@@ -247,10 +252,10 @@ class Camera(object):
|
||||
self.display_vectorized(vmobject, canvas)
|
||||
canvas.flush()
|
||||
|
||||
def display_vectorized(self, vmobject, canvas = None):
|
||||
def display_vectorized(self, vmobject, canvas=None):
|
||||
if vmobject.is_subpath:
|
||||
#Subpath vectorized mobjects are taken care
|
||||
#of by their parent
|
||||
# Subpath vectorized mobjects are taken care
|
||||
# of by their parent
|
||||
return
|
||||
canvas = canvas or self.get_aggdraw_canvas()
|
||||
pen, fill = self.get_pen_and_fill(vmobject)
|
||||
@@ -267,7 +272,7 @@ class Camera(object):
|
||||
stroke_hex = rgb_to_hex(stroke_rgb)
|
||||
pen = aggdraw.Pen(stroke_hex, stroke_width)
|
||||
|
||||
fill_opacity = int(self.rgb_max_val*vmobject.get_fill_opacity())
|
||||
fill_opacity = int(self.rgb_max_val * vmobject.get_fill_opacity())
|
||||
if fill_opacity == 0:
|
||||
fill = None
|
||||
else:
|
||||
@@ -291,27 +296,28 @@ class Camera(object):
|
||||
|
||||
def get_pathstring(self, vmobject):
|
||||
result = ""
|
||||
for mob in [vmobject]+vmobject.get_subpath_mobjects():
|
||||
for mob in [vmobject] + vmobject.get_subpath_mobjects():
|
||||
points = mob.points
|
||||
# points = self.adjust_out_of_range_points(points)
|
||||
# points = self.adjust_out_of_range_points(points)
|
||||
if len(points) == 0:
|
||||
continue
|
||||
aligned_points = self.align_points_to_camera(points)
|
||||
coords = self.points_to_pixel_coords(aligned_points)
|
||||
coord_strings = coords.flatten().astype(str)
|
||||
#Start new path string with M
|
||||
# Start new path string with M
|
||||
coord_strings[0] = "M" + coord_strings[0]
|
||||
#The C at the start of every 6th number communicates
|
||||
#that the following 6 define a cubic Bezier
|
||||
coord_strings[2::6] = map(lambda s : "C" + str(s), coord_strings[2::6])
|
||||
#Possibly finish with "Z"
|
||||
# The C at the start of every 6th number communicates
|
||||
# that the following 6 define a cubic Bezier
|
||||
coord_strings[2::6] = map(
|
||||
lambda s: "C" + str(s), coord_strings[2::6])
|
||||
# Possibly finish with "Z"
|
||||
if vmobject.mark_paths_closed:
|
||||
coord_strings[-1] = coord_strings[-1] + " Z"
|
||||
result += " ".join(coord_strings)
|
||||
return result
|
||||
|
||||
def get_background_colored_vmobject_displayer(self):
|
||||
#Quite wordy to type out a bunch
|
||||
# Quite wordy to type out a bunch
|
||||
long_name = "background_colored_vmobject_displayer"
|
||||
if not hasattr(self, long_name):
|
||||
setattr(self, long_name, BackgroundColoredVMobjectDisplayer(self))
|
||||
@@ -323,7 +329,7 @@ class Camera(object):
|
||||
self.overlay_rgba_array(cvmobject_pixel_array)
|
||||
return self
|
||||
|
||||
## Methods for other rendering
|
||||
# Methods for other rendering
|
||||
|
||||
def display_multiple_point_cloud_mobjects(self, pmobjects):
|
||||
for pmobject in pmobjects:
|
||||
@@ -343,23 +349,23 @@ class Camera(object):
|
||||
)
|
||||
rgba_len = self.pixel_array.shape[2]
|
||||
|
||||
rgbas = (self.rgb_max_val*rgbas).astype(self.pixel_array_dtype)
|
||||
rgbas = (self.rgb_max_val * rgbas).astype(self.pixel_array_dtype)
|
||||
target_len = len(pixel_coords)
|
||||
factor = target_len/len(rgbas)
|
||||
rgbas = np.array([rgbas]*factor).reshape((target_len, rgba_len))
|
||||
factor = target_len / len(rgbas)
|
||||
rgbas = np.array([rgbas] * factor).reshape((target_len, rgba_len))
|
||||
|
||||
on_screen_indices = self.on_screen_pixels(pixel_coords)
|
||||
pixel_coords = pixel_coords[on_screen_indices]
|
||||
on_screen_indices = self.on_screen_pixels(pixel_coords)
|
||||
pixel_coords = pixel_coords[on_screen_indices]
|
||||
rgbas = rgbas[on_screen_indices]
|
||||
|
||||
ph, pw = self.pixel_shape
|
||||
|
||||
flattener = np.array([1, pw], dtype = 'int')
|
||||
flattener = np.array([1, pw], dtype='int')
|
||||
flattener = flattener.reshape((2, 1))
|
||||
indices = np.dot(pixel_coords, flattener)[:,0]
|
||||
indices = np.dot(pixel_coords, flattener)[:, 0]
|
||||
indices = indices.astype('int')
|
||||
|
||||
new_pa = self.pixel_array.reshape((ph*pw, rgba_len))
|
||||
|
||||
new_pa = self.pixel_array.reshape((ph * pw, rgba_len))
|
||||
new_pa[indices] = rgbas
|
||||
self.pixel_array = new_pa.reshape((ph, pw, rgba_len))
|
||||
|
||||
@@ -375,47 +381,46 @@ class Camera(object):
|
||||
|
||||
impa = image_mobject.pixel_array
|
||||
|
||||
oh, ow = self.pixel_array.shape[:2] #Outer width and height
|
||||
ih, iw = impa.shape[:2] #inner with and height
|
||||
oh, ow = self.pixel_array.shape[:2] # Outer width and height
|
||||
ih, iw = impa.shape[:2] # inner with and height
|
||||
rgb_len = self.pixel_array.shape[2]
|
||||
|
||||
image = np.zeros((oh, ow, rgb_len), dtype = self.pixel_array_dtype)
|
||||
image = np.zeros((oh, ow, rgb_len), dtype=self.pixel_array_dtype)
|
||||
|
||||
if right_vect[1] == 0 and down_vect[0] == 0:
|
||||
rv0 = right_vect[0]
|
||||
dv1 = down_vect[1]
|
||||
x_indices = np.arange(rv0, dtype = 'int')*iw/rv0
|
||||
y_indices = np.arange(dv1, dtype = 'int')*ih/dv1
|
||||
stretched_impa = impa[y_indices][:,x_indices]
|
||||
x_indices = np.arange(rv0, dtype='int') * iw / rv0
|
||||
y_indices = np.arange(dv1, dtype='int') * ih / dv1
|
||||
stretched_impa = impa[y_indices][:, x_indices]
|
||||
|
||||
x0, x1 = ul_coords[0], ur_coords[0]
|
||||
x0, x1 = ul_coords[0], ur_coords[0]
|
||||
y0, y1 = ul_coords[1], dl_coords[1]
|
||||
if x0 >= ow or x1 < 0 or y0 >= oh or y1 < 0:
|
||||
return
|
||||
siy0 = max(-y0, 0) #stretched_impa y0
|
||||
siy1 = dv1 - max(y1-oh, 0)
|
||||
siy0 = max(-y0, 0) # stretched_impa y0
|
||||
siy1 = dv1 - max(y1 - oh, 0)
|
||||
six0 = max(-x0, 0)
|
||||
six1 = rv0 - max(x1-ow, 0)
|
||||
six1 = rv0 - max(x1 - ow, 0)
|
||||
x0 = max(x0, 0)
|
||||
y0 = max(y0, 0)
|
||||
image[y0:y1, x0:x1] = stretched_impa[siy0:siy1, six0:six1]
|
||||
else:
|
||||
# Alternate (slower) tactic if image is tilted
|
||||
# List of all coordinates of pixels, given as (x, y),
|
||||
# List of all coordinates of pixels, given as (x, y),
|
||||
# which matches the return type of points_to_pixel_coords,
|
||||
# even though np.array indexing naturally happens as (y, x)
|
||||
all_pixel_coords = np.zeros((oh*ow, 2), dtype = 'int')
|
||||
a = np.arange(oh*ow, dtype = 'int')
|
||||
all_pixel_coords[:,0] = a%ow
|
||||
all_pixel_coords[:,1] = a/ow
|
||||
all_pixel_coords = np.zeros((oh * ow, 2), dtype='int')
|
||||
a = np.arange(oh * ow, dtype='int')
|
||||
all_pixel_coords[:, 0] = a % ow
|
||||
all_pixel_coords[:, 1] = a / ow
|
||||
|
||||
recentered_coords = all_pixel_coords - ul_coords
|
||||
coord_norms = np.linalg.norm(recentered_coords, axis = 1)
|
||||
|
||||
with np.errstate(divide = 'ignore'):
|
||||
with np.errstate(divide='ignore'):
|
||||
ix_coords, iy_coords = [
|
||||
np.divide(
|
||||
dim*np.dot(recentered_coords, vect),
|
||||
dim * np.dot(recentered_coords, vect),
|
||||
np.dot(vect, vect),
|
||||
)
|
||||
for vect, dim in (right_vect, iw), (down_vect, ih)
|
||||
@@ -424,12 +429,12 @@ class Camera(object):
|
||||
ix_coords >= 0, ix_coords < iw,
|
||||
iy_coords >= 0, iy_coords < ih,
|
||||
])
|
||||
n_to_change = np.sum(to_change)
|
||||
inner_flat_coords = iw*iy_coords[to_change] + ix_coords[to_change]
|
||||
flat_impa = impa.reshape((iw*ih, rgb_len))
|
||||
inner_flat_coords = iw * \
|
||||
iy_coords[to_change] + ix_coords[to_change]
|
||||
flat_impa = impa.reshape((iw * ih, rgb_len))
|
||||
target_rgbas = flat_impa[inner_flat_coords, :]
|
||||
|
||||
image = image.reshape((ow*oh, rgb_len))
|
||||
image = image.reshape((ow * oh, rgb_len))
|
||||
image[to_change] = target_rgbas
|
||||
image = image.reshape((oh, ow, rgb_len))
|
||||
self.overlay_rgba_array(image)
|
||||
@@ -439,27 +444,27 @@ class Camera(object):
|
||||
bg = self.pixel_array
|
||||
# rgba_max_val = self.rgb_max_val
|
||||
src_rgb, src_a, dst_rgb, dst_a = [
|
||||
a.astype(np.float32)/self.rgb_max_val
|
||||
for a in fg[...,:3], fg[...,3], bg[...,:3], bg[...,3]
|
||||
a.astype(np.float32) / self.rgb_max_val
|
||||
for a in fg[..., :3], fg[..., 3], bg[..., :3], bg[..., 3]
|
||||
]
|
||||
|
||||
out_a = src_a + dst_a*(1.0-src_a)
|
||||
out_a = src_a + dst_a * (1.0 - src_a)
|
||||
|
||||
# When the output alpha is 0 for full transparency,
|
||||
# When the output alpha is 0 for full transparency,
|
||||
# we have a choice over what RGB value to use in our
|
||||
# output representation. We choose 0 here.
|
||||
out_rgb = fdiv(
|
||||
src_rgb*src_a[..., None] + \
|
||||
dst_rgb*dst_a[..., None]*(1.0-src_a[..., None]),
|
||||
src_rgb * src_a[..., None] +
|
||||
dst_rgb * dst_a[..., None] * (1.0 - src_a[..., None]),
|
||||
out_a[..., None],
|
||||
zero_over_zero_value = 0
|
||||
zero_over_zero_value=0
|
||||
)
|
||||
|
||||
self.pixel_array[..., :3] = out_rgb*self.rgb_max_val
|
||||
self.pixel_array[..., 3] = out_a*self.rgb_max_val
|
||||
self.pixel_array[..., :3] = out_rgb * self.rgb_max_val
|
||||
self.pixel_array[..., 3] = out_a * self.rgb_max_val
|
||||
|
||||
def align_points_to_camera(self, points):
|
||||
## This is where projection should live
|
||||
# This is where projection should live
|
||||
return points - self.space_center
|
||||
|
||||
def adjust_out_of_range_points(self, points):
|
||||
@@ -467,10 +472,10 @@ class Camera(object):
|
||||
return points
|
||||
norms = np.apply_along_axis(np.linalg.norm, 1, points)
|
||||
violator_indices = norms > self.max_allowable_norm
|
||||
violators = points[violator_indices,:]
|
||||
violators = points[violator_indices, :]
|
||||
violator_norms = norms[violator_indices]
|
||||
reshaped_norms = np.repeat(
|
||||
violator_norms.reshape((len(violator_norms), 1)),
|
||||
violator_norms.reshape((len(violator_norms), 1)),
|
||||
points.shape[1], 1
|
||||
)
|
||||
rescaled = self.max_allowable_norm * violators / reshaped_norms
|
||||
@@ -481,32 +486,32 @@ class Camera(object):
|
||||
result = np.zeros((len(points), 2))
|
||||
ph, pw = self.pixel_shape
|
||||
sh, sw = self.frame_shape
|
||||
width_mult = pw/sw
|
||||
width_add = pw/2
|
||||
height_mult = ph/sh
|
||||
height_add = ph/2
|
||||
#Flip on y-axis as you go
|
||||
width_mult = pw / sw
|
||||
width_add = pw / 2
|
||||
height_mult = ph / sh
|
||||
height_add = ph / 2
|
||||
# Flip on y-axis as you go
|
||||
height_mult *= -1
|
||||
|
||||
result[:,0] = points[:,0]*width_mult + width_add
|
||||
result[:,1] = points[:,1]*height_mult + height_add
|
||||
result[:, 0] = points[:, 0] * width_mult + width_add
|
||||
result[:, 1] = points[:, 1] * height_mult + height_add
|
||||
return result.astype('int')
|
||||
|
||||
def on_screen_pixels(self, pixel_coords):
|
||||
return reduce(op.and_, [
|
||||
pixel_coords[:,0] >= 0,
|
||||
pixel_coords[:,0] < self.pixel_shape[1],
|
||||
pixel_coords[:,1] >= 0,
|
||||
pixel_coords[:,1] < self.pixel_shape[0],
|
||||
pixel_coords[:, 0] >= 0,
|
||||
pixel_coords[:, 0] < self.pixel_shape[1],
|
||||
pixel_coords[:, 1] >= 0,
|
||||
pixel_coords[:, 1] < self.pixel_shape[0],
|
||||
])
|
||||
|
||||
def adjusted_thickness(self, thickness):
|
||||
big_shape = PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_shape"]
|
||||
factor = sum(big_shape)/sum(self.pixel_shape)
|
||||
return 1 + (thickness-1)/factor
|
||||
factor = sum(big_shape) / sum(self.pixel_shape)
|
||||
return 1 + (thickness - 1) / factor
|
||||
|
||||
def get_thickening_nudges(self, thickness):
|
||||
_range = range(-thickness/2+1, thickness/2+1)
|
||||
_range = range(-thickness / 2 + 1, thickness / 2 + 1)
|
||||
return np.array(list(it.product(_range, _range)))
|
||||
|
||||
def thickened_coordinates(self, pixel_coords, thickness):
|
||||
@@ -516,7 +521,7 @@ class Camera(object):
|
||||
for nudge in nudges
|
||||
])
|
||||
size = pixel_coords.size
|
||||
return pixel_coords.reshape((size/2, 2))
|
||||
return pixel_coords.reshape((size / 2, 2))
|
||||
|
||||
def get_coords_of_all_pixels(self):
|
||||
# These are in x, y order, to help me keep things straight
|
||||
@@ -525,23 +530,26 @@ class Camera(object):
|
||||
|
||||
# These are addressed in the same y, x order as in pixel_array, but the values in them
|
||||
# are listed in x, y order
|
||||
uncentered_pixel_coords = np.indices(self.pixel_shape)[::-1].transpose(1, 2, 0)
|
||||
uncentered_pixel_coords = np.indices(self.pixel_shape)[
|
||||
::-1].transpose(1, 2, 0)
|
||||
uncentered_space_coords = fdiv(
|
||||
uncentered_pixel_coords * full_space_dims,
|
||||
uncentered_pixel_coords * full_space_dims,
|
||||
full_pixel_dims)
|
||||
# Could structure above line's computation slightly differently, but figured (without much
|
||||
# thought) multiplying by frame_shape first, THEN dividing by pixel_shape, is probably
|
||||
# better than the other order, for avoiding underflow quantization in the division (whereas
|
||||
# Could structure above line's computation slightly differently, but figured (without much
|
||||
# thought) multiplying by frame_shape first, THEN dividing by pixel_shape, is probably
|
||||
# better than the other order, for avoiding underflow quantization in the division (whereas
|
||||
# overflow is unlikely to be a problem)
|
||||
|
||||
centered_space_coords = (uncentered_space_coords - fdiv(full_space_dims, 2))
|
||||
centered_space_coords = (
|
||||
uncentered_space_coords - fdiv(full_space_dims, 2))
|
||||
|
||||
# Have to also flip the y coordinates to account for pixel array being listed in
|
||||
# Have to also flip the y coordinates to account for pixel array being listed in
|
||||
# top-to-bottom order, opposite of screen coordinate convention
|
||||
centered_space_coords = centered_space_coords * (1, -1)
|
||||
|
||||
return centered_space_coords
|
||||
|
||||
|
||||
class BackgroundColoredVMobjectDisplayer(object):
|
||||
def __init__(self, camera):
|
||||
self.camera = camera
|
||||
@@ -551,20 +559,20 @@ class BackgroundColoredVMobjectDisplayer(object):
|
||||
def init_canvas(self):
|
||||
self.pixel_array = np.zeros(
|
||||
self.camera.pixel_array.shape,
|
||||
dtype = self.camera.pixel_array_dtype,
|
||||
dtype=self.camera.pixel_array_dtype,
|
||||
)
|
||||
self.reset_canvas()
|
||||
|
||||
def reset_canvas(self):
|
||||
image = Image.fromarray(self.pixel_array, mode = self.camera.image_mode)
|
||||
image = Image.fromarray(self.pixel_array, mode=self.camera.image_mode)
|
||||
self.canvas = aggdraw.Draw(image)
|
||||
|
||||
def resize_background_array(
|
||||
self, background_array,
|
||||
new_width, new_height,
|
||||
mode = "RGBA"
|
||||
):
|
||||
image = Image.fromarray(background_array, mode = mode)
|
||||
self, background_array,
|
||||
new_width, new_height,
|
||||
mode="RGBA"
|
||||
):
|
||||
image = Image.fromarray(background_array, mode=mode)
|
||||
resized_image = image.resize((new_width, new_height))
|
||||
return np.array(resized_image)
|
||||
|
||||
@@ -582,14 +590,15 @@ class BackgroundColoredVMobjectDisplayer(object):
|
||||
|
||||
camera = self.camera
|
||||
if not np.all(camera.pixel_array.shape == array.shape):
|
||||
array = self.resize_background_array_to_match(array, camera.pixel_array)
|
||||
array = self.resize_background_array_to_match(
|
||||
array, camera.pixel_array)
|
||||
|
||||
self.file_name_to_pixel_array_map[file_name] = array
|
||||
return array
|
||||
|
||||
def display(self, *cvmobjects):
|
||||
batch_image_file_pairs = batch_by_property(
|
||||
cvmobjects, lambda cv : cv.get_background_image_file()
|
||||
cvmobjects, lambda cv: cv.get_background_image_file()
|
||||
)
|
||||
curr_array = None
|
||||
for batch, image_file in batch_image_file_pairs:
|
||||
@@ -598,14 +607,13 @@ class BackgroundColoredVMobjectDisplayer(object):
|
||||
self.camera.display_vectorized(cvmobject, self.canvas)
|
||||
self.canvas.flush()
|
||||
new_array = np.array(
|
||||
(background_array*self.pixel_array.astype('float')/255),
|
||||
dtype = self.camera.pixel_array_dtype
|
||||
(background_array * self.pixel_array.astype('float') / 255),
|
||||
dtype=self.camera.pixel_array_dtype
|
||||
)
|
||||
if curr_array is None:
|
||||
curr_array = new_array
|
||||
else:
|
||||
curr_array = np.maximum(curr_array, new_array)
|
||||
self.pixel_array[:,:] = 0
|
||||
self.pixel_array[:, :] = 0
|
||||
self.reset_canvas()
|
||||
return curr_array
|
||||
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from camera.camera import Camera
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
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
|
||||
# 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
|
||||
"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:
|
||||
@@ -24,30 +29,32 @@ class MappingCamera(Camera):
|
||||
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:
|
||||
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,
|
||||
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
|
||||
# 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],
|
||||
})
|
||||
{
|
||||
"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)
|
||||
@@ -55,17 +62,17 @@ class MultiCamera(Camera):
|
||||
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_y:shifted_camera.end_y,
|
||||
shifted_camera.start_x:shifted_camera.end_x] \
|
||||
= shifted_camera.camera.pixel_array
|
||||
= 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_y:shifted_camera.end_y,
|
||||
shifted_camera.start_x:shifted_camera.end_x],
|
||||
**kwargs
|
||||
)
|
||||
@@ -75,7 +82,7 @@ class MultiCamera(Camera):
|
||||
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_y:shifted_camera.end_y,
|
||||
shifted_camera.start_x:shifted_camera.end_x],
|
||||
**kwargs
|
||||
)
|
||||
@@ -87,18 +94,21 @@ class MultiCamera(Camera):
|
||||
|
||||
# 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
|
||||
# TODO: Round up on one if width is odd
|
||||
camera.pixel_shape = (self.pixel_shape[0], half_width)
|
||||
camera.init_background()
|
||||
camera.resize_frame_shape()
|
||||
camera.reset()
|
||||
|
||||
MultiCamera.__init__(self, (left_camera, (0, 0)), (right_camera, (0, half_width)))
|
||||
|
||||
MultiCamera.__init__(self, (left_camera, (0, 0)),
|
||||
(right_camera, (0, half_width)))
|
||||
|
||||
@@ -2,17 +2,19 @@ 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
|
||||
"aligned_dimension": "width" # or height
|
||||
}
|
||||
|
||||
def __init__(self, frame, **kwargs):
|
||||
"""
|
||||
frame is a Mobject, (should be a rectangle) determining
|
||||
frame is a Mobject, (should be a rectangle) determining
|
||||
which region of space the camera displys
|
||||
"""
|
||||
self.frame = frame
|
||||
|
||||
@@ -12,39 +12,43 @@ from utils.bezier import interpolate
|
||||
from utils.space_ops import rotation_about_z
|
||||
from utils.space_ops import rotation_matrix
|
||||
|
||||
|
||||
class CameraWithPerspective(Camera):
|
||||
CONFIG = {
|
||||
"camera_distance" : 20,
|
||||
"camera_distance": 20,
|
||||
}
|
||||
|
||||
def points_to_pixel_coords(self, points):
|
||||
distance_ratios = np.divide(
|
||||
self.camera_distance,
|
||||
self.camera_distance - points[:,2]
|
||||
self.camera_distance - points[:, 2]
|
||||
)
|
||||
scale_factors = interpolate(0, 1, distance_ratios)
|
||||
adjusted_points = np.array(points)
|
||||
for i in 0, 1:
|
||||
adjusted_points[:,i] *= scale_factors
|
||||
adjusted_points[:, i] *= scale_factors
|
||||
|
||||
return Camera.points_to_pixel_coords(self, adjusted_points)
|
||||
|
||||
|
||||
class ThreeDCamera(CameraWithPerspective):
|
||||
CONFIG = {
|
||||
"sun_vect" : 5*UP+LEFT,
|
||||
"shading_factor" : 0.2,
|
||||
"distance" : 5.,
|
||||
"default_distance" : 5.,
|
||||
"phi" : 0, #Angle off z axis
|
||||
"theta" : -TAU/4, #Rotation about z axis
|
||||
"sun_vect": 5 * UP + LEFT,
|
||||
"shading_factor": 0.2,
|
||||
"distance": 5.,
|
||||
"default_distance": 5.,
|
||||
"phi": 0, # Angle off z axis
|
||||
"theta": -TAU / 4, # Rotation about z axis
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
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.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
|
||||
# moving_center lives in the x-y-z space
|
||||
# It representes the center of rotation
|
||||
self.moving_center = VectorizedPoint(self.space_center)
|
||||
self.set_position(self.phi, self.theta, self.distance)
|
||||
|
||||
@@ -63,30 +67,31 @@ class ThreeDCamera(CameraWithPerspective):
|
||||
def get_shaded_rgb(self, rgb, normal_vect):
|
||||
brightness = np.dot(normal_vect, self.unit_sun_vect)**2
|
||||
if brightness > 0:
|
||||
alpha = self.shading_factor*brightness
|
||||
alpha = self.shading_factor * brightness
|
||||
return interpolate(rgb, np.ones(3), alpha)
|
||||
else:
|
||||
alpha = -self.shading_factor*brightness
|
||||
alpha = -self.shading_factor * brightness
|
||||
return interpolate(rgb, np.zeros(3), alpha)
|
||||
|
||||
def get_unit_normal_vect(self, vmobject):
|
||||
anchors = vmobject.get_anchors()
|
||||
if len(anchors) < 3:
|
||||
return OUT
|
||||
normal = np.cross(anchors[1]-anchors[0], anchors[2]-anchors[1])
|
||||
normal = np.cross(anchors[1] - anchors[0], anchors[2] - anchors[1])
|
||||
if normal[2] < 0:
|
||||
normal = -normal
|
||||
length = np.linalg.norm(normal)
|
||||
if length == 0:
|
||||
return OUT
|
||||
return normal/length
|
||||
return normal / length
|
||||
|
||||
def display_multiple_vectorized_mobjects(self, vmobjects):
|
||||
camera_point = self.spherical_coords_to_point(
|
||||
*self.get_spherical_coords()
|
||||
)
|
||||
|
||||
def z_cmp(*vmobs):
|
||||
# Compare to three dimensional mobjects based on
|
||||
# Compare to three dimensional mobjects based on
|
||||
# how close they are to the camera
|
||||
# return cmp(*[
|
||||
# -np.linalg.norm(vm.get_center()-camera_point)
|
||||
@@ -103,22 +108,26 @@ class ThreeDCamera(CameraWithPerspective):
|
||||
else:
|
||||
return 0
|
||||
Camera.display_multiple_vectorized_mobjects(
|
||||
self, sorted(vmobjects, cmp = z_cmp)
|
||||
self, sorted(vmobjects, cmp=z_cmp)
|
||||
)
|
||||
|
||||
def get_spherical_coords(self, phi = None, theta = None, distance = None):
|
||||
def get_spherical_coords(self, phi=None, theta=None, distance=None):
|
||||
curr_phi, curr_theta, curr_d = self.rotation_mobject.points[0]
|
||||
if phi is None: phi = curr_phi
|
||||
if theta is None: theta = curr_theta
|
||||
if distance is None: distance = curr_d
|
||||
if phi is None:
|
||||
phi = curr_phi
|
||||
if theta is None:
|
||||
theta = curr_theta
|
||||
if distance is None:
|
||||
distance = curr_d
|
||||
return np.array([phi, theta, distance])
|
||||
|
||||
def get_cartesian_coords(self, phi = None, theta = None, distance = None):
|
||||
spherical_coords_array = self.get_spherical_coords(phi,theta,distance)
|
||||
def get_cartesian_coords(self, phi=None, theta=None, distance=None):
|
||||
spherical_coords_array = self.get_spherical_coords(
|
||||
phi, theta, distance)
|
||||
phi2 = spherical_coords_array[0]
|
||||
theta2 = spherical_coords_array[1]
|
||||
d2 = spherical_coords_array[2]
|
||||
return self.spherical_coords_to_point(phi2,theta2,d2)
|
||||
return self.spherical_coords_to_point(phi2, theta2, d2)
|
||||
|
||||
def get_phi(self):
|
||||
return self.get_spherical_coords()[0]
|
||||
@@ -130,13 +139,13 @@ class ThreeDCamera(CameraWithPerspective):
|
||||
return self.get_spherical_coords()[2]
|
||||
|
||||
def spherical_coords_to_point(self, phi, theta, distance):
|
||||
return distance*np.array([
|
||||
np.sin(phi)*np.cos(theta),
|
||||
np.sin(phi)*np.sin(theta),
|
||||
return distance * np.array([
|
||||
np.sin(phi) * np.cos(theta),
|
||||
np.sin(phi) * np.sin(theta),
|
||||
np.cos(phi)
|
||||
])
|
||||
|
||||
def get_center_of_rotation(self, x = None, y = None, z = None):
|
||||
def get_center_of_rotation(self, x=None, y=None, z=None):
|
||||
curr_x, curr_y, curr_z = self.moving_center.points[0]
|
||||
if x is None:
|
||||
x = curr_x
|
||||
@@ -146,19 +155,20 @@ class ThreeDCamera(CameraWithPerspective):
|
||||
z = curr_z
|
||||
return np.array([x, y, z])
|
||||
|
||||
def set_position(self, phi = None, theta = None, distance = None,
|
||||
center_x = None, center_y = None, center_z = None):
|
||||
def set_position(self, phi=None, theta=None, distance=None,
|
||||
center_x=None, center_y=None, center_z=None):
|
||||
point = self.get_spherical_coords(phi, theta, distance)
|
||||
self.rotation_mobject.move_to(point)
|
||||
self.phi, self.theta, self.distance = point
|
||||
center_of_rotation = self.get_center_of_rotation(center_x, center_y, center_z)
|
||||
center_of_rotation = self.get_center_of_rotation(
|
||||
center_x, center_y, center_z)
|
||||
self.moving_center.move_to(center_of_rotation)
|
||||
self.space_center = self.moving_center.points[0]
|
||||
|
||||
def get_view_transformation_matrix(self):
|
||||
return (self.default_distance / self.get_distance()) * np.dot(
|
||||
rotation_matrix(self.get_phi(), LEFT),
|
||||
rotation_about_z(-self.get_theta() - np.pi/2),
|
||||
rotation_about_z(-self.get_theta() - np.pi / 2),
|
||||
)
|
||||
|
||||
def points_to_pixel_coords(self, points):
|
||||
|
||||
205
constants.py
205
constants.py
@@ -3,31 +3,34 @@ import numpy as np
|
||||
|
||||
# Things anyone wishing to use this repository for their
|
||||
# own use will want to change this
|
||||
MEDIA_DIR = os.path.join(os.path.expanduser('~'), "Dropbox (3Blue1Brown)/3Blue1Brown Team Folder")
|
||||
MEDIA_DIR = os.path.join(
|
||||
os.path.expanduser('~'),
|
||||
"Dropbox (3Blue1Brown)/3Blue1Brown Team Folder"
|
||||
)
|
||||
#
|
||||
|
||||
|
||||
DEFAULT_PIXEL_HEIGHT = 1080
|
||||
DEFAULT_PIXEL_WIDTH = 1920
|
||||
DEFAULT_PIXEL_WIDTH = 1920
|
||||
|
||||
LOW_QUALITY_FRAME_DURATION = 1./15
|
||||
MEDIUM_QUALITY_FRAME_DURATION = 1./30
|
||||
PRODUCTION_QUALITY_FRAME_DURATION = 1./60
|
||||
LOW_QUALITY_FRAME_DURATION = 1. / 15
|
||||
MEDIUM_QUALITY_FRAME_DURATION = 1. / 30
|
||||
PRODUCTION_QUALITY_FRAME_DURATION = 1. / 60
|
||||
|
||||
#There might be other configuration than pixel_shape later...
|
||||
# There might be other configuration than pixel_shape later...
|
||||
PRODUCTION_QUALITY_CAMERA_CONFIG = {
|
||||
"pixel_shape" : (DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH),
|
||||
"pixel_shape": (DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH),
|
||||
}
|
||||
|
||||
MEDIUM_QUALITY_CAMERA_CONFIG = {
|
||||
"pixel_shape" : (720, 1280),
|
||||
"pixel_shape": (720, 1280),
|
||||
}
|
||||
|
||||
LOW_QUALITY_CAMERA_CONFIG = {
|
||||
"pixel_shape" : (480, 854),
|
||||
"pixel_shape": (480, 854),
|
||||
}
|
||||
|
||||
DEFAULT_POINT_DENSITY_2D = 25
|
||||
DEFAULT_POINT_DENSITY_2D = 25
|
||||
DEFAULT_POINT_DENSITY_1D = 250
|
||||
|
||||
DEFAULT_POINT_THICKNESS = 4
|
||||
@@ -35,8 +38,8 @@ DEFAULT_POINT_THICKNESS = 4
|
||||
|
||||
FRAME_HEIGHT = 8.0
|
||||
FRAME_WIDTH = FRAME_HEIGHT * DEFAULT_PIXEL_WIDTH / DEFAULT_PIXEL_HEIGHT
|
||||
FRAME_Y_RADIUS = FRAME_HEIGHT/2
|
||||
FRAME_X_RADIUS = FRAME_WIDTH/2
|
||||
FRAME_Y_RADIUS = FRAME_HEIGHT / 2
|
||||
FRAME_X_RADIUS = FRAME_WIDTH / 2
|
||||
|
||||
SMALL_BUFF = 0.1
|
||||
MED_SMALL_BUFF = 0.25
|
||||
@@ -47,49 +50,49 @@ DEFAULT_MOBJECT_TO_EDGE_BUFFER = MED_LARGE_BUFF
|
||||
DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = MED_SMALL_BUFF
|
||||
|
||||
|
||||
#All in seconds
|
||||
# All in seconds
|
||||
DEFAULT_ANIMATION_RUN_TIME = 1.0
|
||||
DEFAULT_POINTWISE_FUNCTION_RUN_TIME = 3.0
|
||||
DEFAULT_WAIT_TIME = 1.0
|
||||
|
||||
|
||||
ORIGIN = np.array(( 0., 0., 0.))
|
||||
UP = np.array(( 0., 1., 0.))
|
||||
DOWN = np.array(( 0.,-1., 0.))
|
||||
RIGHT = np.array(( 1., 0., 0.))
|
||||
LEFT = np.array((-1., 0., 0.))
|
||||
IN = np.array(( 0., 0.,-1.))
|
||||
OUT = np.array(( 0., 0., 1.))
|
||||
X_AXIS = np.array(( 1., 0., 0.))
|
||||
Y_AXIS = np.array(( 0., 1., 0.))
|
||||
Z_AXIS = np.array(( 0., 0., 1.))
|
||||
ORIGIN = np.array((0., 0., 0.))
|
||||
UP = np.array((0., 1., 0.))
|
||||
DOWN = np.array((0., -1., 0.))
|
||||
RIGHT = np.array((1., 0., 0.))
|
||||
LEFT = np.array((-1., 0., 0.))
|
||||
IN = np.array((0., 0., -1.))
|
||||
OUT = np.array((0., 0., 1.))
|
||||
X_AXIS = np.array((1., 0., 0.))
|
||||
Y_AXIS = np.array((0., 1., 0.))
|
||||
Z_AXIS = np.array((0., 0., 1.))
|
||||
|
||||
TOP = FRAME_Y_RADIUS*UP
|
||||
BOTTOM = FRAME_Y_RADIUS*DOWN
|
||||
LEFT_SIDE = FRAME_X_RADIUS*LEFT
|
||||
RIGHT_SIDE = FRAME_X_RADIUS*RIGHT
|
||||
TOP = FRAME_Y_RADIUS * UP
|
||||
BOTTOM = FRAME_Y_RADIUS * DOWN
|
||||
LEFT_SIDE = FRAME_X_RADIUS * LEFT
|
||||
RIGHT_SIDE = FRAME_X_RADIUS * RIGHT
|
||||
|
||||
TAU = 2*np.pi
|
||||
DEGREES = TAU/360
|
||||
TAU = 2 * np.pi
|
||||
DEGREES = TAU / 360
|
||||
|
||||
ANIMATIONS_DIR = os.path.join(MEDIA_DIR, "animations")
|
||||
RASTER_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "raster_images")
|
||||
SVG_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "svg_images")
|
||||
#TODO, staged scenes should really go into a subdirectory of a given scenes directory
|
||||
STAGED_SCENES_DIR = os.path.join(ANIMATIONS_DIR, "staged_scenes")
|
||||
# TODO, staged scenes should really go into a subdirectory of a given scenes directory
|
||||
STAGED_SCENES_DIR = os.path.join(ANIMATIONS_DIR, "staged_scenes")
|
||||
###
|
||||
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
FILE_DIR = os.path.join(THIS_DIR, "files")
|
||||
TEX_DIR = os.path.join(FILE_DIR, "Tex")
|
||||
TEX_IMAGE_DIR = TEX_DIR #TODO, What is this doing?
|
||||
#These two may be depricated now.
|
||||
MOBJECT_DIR = os.path.join(FILE_DIR, "mobjects")
|
||||
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
FILE_DIR = os.path.join(THIS_DIR, "files")
|
||||
TEX_DIR = os.path.join(FILE_DIR, "Tex")
|
||||
TEX_IMAGE_DIR = TEX_DIR # TODO, What is this doing?
|
||||
# These two may be depricated now.
|
||||
MOBJECT_DIR = os.path.join(FILE_DIR, "mobjects")
|
||||
IMAGE_MOBJECT_DIR = os.path.join(MOBJECT_DIR, "image")
|
||||
|
||||
if not os.path.exists(MEDIA_DIR):
|
||||
raise Exception("""
|
||||
Redefine MEDIA_DIR in constants.py to point to
|
||||
a valid directory where movies and images will
|
||||
Redefine MEDIA_DIR in constants.py to point to
|
||||
a valid directory where movies and images will
|
||||
be written
|
||||
""")
|
||||
for folder in [FILE_DIR, RASTER_IMAGE_DIR, SVG_IMAGE_DIR, ANIMATIONS_DIR, TEX_DIR,
|
||||
@@ -99,82 +102,72 @@ for folder in [FILE_DIR, RASTER_IMAGE_DIR, SVG_IMAGE_DIR, ANIMATIONS_DIR, TEX_DI
|
||||
os.makedirs(folder)
|
||||
|
||||
TEX_TEXT_TO_REPLACE = "YourTextHere"
|
||||
TEMPLATE_TEX_FILE = os.path.join(THIS_DIR, "template.tex")
|
||||
TEMPLATE_TEX_FILE = os.path.join(THIS_DIR, "template.tex")
|
||||
TEMPLATE_TEXT_FILE = os.path.join(THIS_DIR, "text_template.tex")
|
||||
|
||||
FFMPEG_BIN = "ffmpeg"
|
||||
|
||||
|
||||
### Colors ###
|
||||
# Colors
|
||||
|
||||
COLOR_MAP = {
|
||||
"DARK_BLUE" : "#236B8E",
|
||||
"DARK_BROWN" : "#8B4513",
|
||||
"LIGHT_BROWN" : "#CD853F",
|
||||
"BLUE_E" : "#1C758A",
|
||||
"BLUE_D" : "#29ABCA",
|
||||
"BLUE_C" : "#58C4DD",
|
||||
"BLUE_B" : "#9CDCEB",
|
||||
"BLUE_A" : "#C7E9F1",
|
||||
"TEAL_E" : "#49A88F",
|
||||
"TEAL_D" : "#55C1A7",
|
||||
"TEAL_C" : "#5CD0B3",
|
||||
"TEAL_B" : "#76DDC0",
|
||||
"TEAL_A" : "#ACEAD7",
|
||||
"GREEN_E" : "#699C52",
|
||||
"GREEN_D" : "#77B05D",
|
||||
"GREEN_C" : "#83C167",
|
||||
"GREEN_B" : "#A6CF8C",
|
||||
"GREEN_A" : "#C9E2AE",
|
||||
"YELLOW_E" : "#E8C11C",
|
||||
"YELLOW_D" : "#F4D345",
|
||||
"YELLOW_C" : "#FFFF00",
|
||||
"YELLOW_B" : "#FFEA94",
|
||||
"YELLOW_A" : "#FFF1B6",
|
||||
"GOLD_E" : "#C78D46",
|
||||
"GOLD_D" : "#E1A158",
|
||||
"GOLD_C" : "#F0AC5F",
|
||||
"GOLD_B" : "#F9B775",
|
||||
"GOLD_A" : "#F7C797",
|
||||
"RED_E" : "#CF5044",
|
||||
"RED_D" : "#E65A4C",
|
||||
"RED_C" : "#FC6255",
|
||||
"RED_B" : "#FF8080",
|
||||
"RED_A" : "#F7A1A3",
|
||||
"MAROON_E" : "#94424F",
|
||||
"MAROON_D" : "#A24D61",
|
||||
"MAROON_C" : "#C55F73",
|
||||
"MAROON_B" : "#EC92AB",
|
||||
"MAROON_A" : "#ECABC1",
|
||||
"PURPLE_E" : "#644172",
|
||||
"PURPLE_D" : "#715582",
|
||||
"PURPLE_C" : "#9A72AC",
|
||||
"PURPLE_B" : "#B189C6",
|
||||
"PURPLE_A" : "#CAA3E8",
|
||||
"WHITE" : "#FFFFFF",
|
||||
"BLACK" : "#000000",
|
||||
"LIGHT_GRAY" : "#BBBBBB",
|
||||
"LIGHT_GREY" : "#BBBBBB",
|
||||
"GRAY" : "#888888",
|
||||
"GREY" : "#888888",
|
||||
"DARK_GREY" : "#444444",
|
||||
"DARK_GRAY" : "#444444",
|
||||
"GREY_BROWN" : "#736357",
|
||||
"PINK" : "#D147BD",
|
||||
"DARK_BLUE": "#236B8E",
|
||||
"DARK_BROWN": "#8B4513",
|
||||
"LIGHT_BROWN": "#CD853F",
|
||||
"BLUE_E": "#1C758A",
|
||||
"BLUE_D": "#29ABCA",
|
||||
"BLUE_C": "#58C4DD",
|
||||
"BLUE_B": "#9CDCEB",
|
||||
"BLUE_A": "#C7E9F1",
|
||||
"TEAL_E": "#49A88F",
|
||||
"TEAL_D": "#55C1A7",
|
||||
"TEAL_C": "#5CD0B3",
|
||||
"TEAL_B": "#76DDC0",
|
||||
"TEAL_A": "#ACEAD7",
|
||||
"GREEN_E": "#699C52",
|
||||
"GREEN_D": "#77B05D",
|
||||
"GREEN_C": "#83C167",
|
||||
"GREEN_B": "#A6CF8C",
|
||||
"GREEN_A": "#C9E2AE",
|
||||
"YELLOW_E": "#E8C11C",
|
||||
"YELLOW_D": "#F4D345",
|
||||
"YELLOW_C": "#FFFF00",
|
||||
"YELLOW_B": "#FFEA94",
|
||||
"YELLOW_A": "#FFF1B6",
|
||||
"GOLD_E": "#C78D46",
|
||||
"GOLD_D": "#E1A158",
|
||||
"GOLD_C": "#F0AC5F",
|
||||
"GOLD_B": "#F9B775",
|
||||
"GOLD_A": "#F7C797",
|
||||
"RED_E": "#CF5044",
|
||||
"RED_D": "#E65A4C",
|
||||
"RED_C": "#FC6255",
|
||||
"RED_B": "#FF8080",
|
||||
"RED_A": "#F7A1A3",
|
||||
"MAROON_E": "#94424F",
|
||||
"MAROON_D": "#A24D61",
|
||||
"MAROON_C": "#C55F73",
|
||||
"MAROON_B": "#EC92AB",
|
||||
"MAROON_A": "#ECABC1",
|
||||
"PURPLE_E": "#644172",
|
||||
"PURPLE_D": "#715582",
|
||||
"PURPLE_C": "#9A72AC",
|
||||
"PURPLE_B": "#B189C6",
|
||||
"PURPLE_A": "#CAA3E8",
|
||||
"WHITE": "#FFFFFF",
|
||||
"BLACK": "#000000",
|
||||
"LIGHT_GRAY": "#BBBBBB",
|
||||
"LIGHT_GREY": "#BBBBBB",
|
||||
"GRAY": "#888888",
|
||||
"GREY": "#888888",
|
||||
"DARK_GREY": "#444444",
|
||||
"DARK_GRAY": "#444444",
|
||||
"GREY_BROWN": "#736357",
|
||||
"PINK": "#D147BD",
|
||||
"GREEN_SCREEN": "#00FF00",
|
||||
"ORANGE" : "#FF862F",
|
||||
"ORANGE": "#FF862F",
|
||||
}
|
||||
PALETTE = COLOR_MAP.values()
|
||||
locals().update(COLOR_MAP)
|
||||
for name in filter(lambda s : s.endswith("_C"), COLOR_MAP.keys()):
|
||||
for name in filter(lambda s: s.endswith("_C"), COLOR_MAP.keys()):
|
||||
locals()[name.replace("_C", "")] = locals()[name]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
from constants import *
|
||||
from utils.config_ops import digest_config
|
||||
|
||||
# Currently, this is only used by both Scene and MOBject.
|
||||
# Still, we abstract its functionality here, albeit purely nominally.
|
||||
# All actual implementation has to be handled by derived classes for now.
|
||||
#
|
||||
# Note that although the prototypical instances add and remove MObjects,
|
||||
# there is also the possibility to add ContinualAnimations to Scenes. Thus,
|
||||
# in the Container class in general, we do not make any presumptions about
|
||||
# Note that although the prototypical instances add and remove MObjects,
|
||||
# there is also the possibility to add ContinualAnimations to Scenes. Thus,
|
||||
# in the Container class in general, we do not make any presumptions about
|
||||
# what types of objects may be added; this is again dependent on the specific
|
||||
# derived instance.
|
||||
|
||||
|
||||
class Container(object):
|
||||
def __init__(self, *submobjects, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
|
||||
def add(self, *items):
|
||||
raise Exception("Container.add is not implemented; it is up to derived classes to implement")
|
||||
raise Exception(
|
||||
"Container.add is not implemented; it is up to derived classes to implement")
|
||||
|
||||
def remove(self, *items):
|
||||
raise Exception("Container.remove is not implemented; it is up to derived classes to implement")
|
||||
raise Exception(
|
||||
"Container.remove is not implemented; it is up to derived classes to implement")
|
||||
|
||||
@@ -6,12 +6,14 @@ from mobject.mobject import Mobject
|
||||
from utils.config_ops import digest_config
|
||||
from utils.config_ops import instantiate
|
||||
|
||||
|
||||
class ContinualAnimation(object):
|
||||
CONFIG = {
|
||||
"start_up_time" : 1,
|
||||
"wind_down_time" : 1,
|
||||
"end_time" : np.inf,
|
||||
"start_up_time": 1,
|
||||
"wind_down_time": 1,
|
||||
"end_time": np.inf,
|
||||
}
|
||||
|
||||
def __init__(self, mobject, **kwargs):
|
||||
mobject = instantiate(mobject)
|
||||
assert(isinstance(mobject, Mobject))
|
||||
@@ -22,42 +24,45 @@ class ContinualAnimation(object):
|
||||
self.update(0)
|
||||
|
||||
def setup(self):
|
||||
#To implement in subclass
|
||||
# To implement in subclass
|
||||
pass
|
||||
|
||||
def begin_wind_down(self, wind_down_time = None):
|
||||
def begin_wind_down(self, wind_down_time=None):
|
||||
if wind_down_time is not None:
|
||||
self.wind_down_time = wind_down_time
|
||||
self.end_time = self.external_time + self.wind_down_time
|
||||
|
||||
def update(self, dt):
|
||||
#TODO, currenty time moves slower for a
|
||||
#continual animation during its start up
|
||||
#to help smooth things out. Does this have
|
||||
#unwanted consequences?
|
||||
# TODO, currenty time moves slower for a
|
||||
# continual animation during its start up
|
||||
# to help smooth things out. Does this have
|
||||
# unwanted consequences?
|
||||
self.external_time += dt
|
||||
if self.external_time < self.start_up_time:
|
||||
dt *= float(self.external_time)/self.start_up_time
|
||||
dt *= float(self.external_time) / self.start_up_time
|
||||
elif self.external_time > self.end_time - self.wind_down_time:
|
||||
dt *= np.clip(
|
||||
float(self.end_time - self.external_time)/self.wind_down_time,
|
||||
float(self.end_time - self.external_time) /
|
||||
self.wind_down_time,
|
||||
0, 1
|
||||
)
|
||||
self.internal_time += dt
|
||||
self.update_mobject(dt)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
#To implement in subclass
|
||||
# To implement in subclass
|
||||
pass
|
||||
|
||||
def copy(self):
|
||||
return copy.deepcopy(self)
|
||||
|
||||
|
||||
class ContinualAnimationGroup(ContinualAnimation):
|
||||
CONFIG = {
|
||||
"start_up_time" : 0,
|
||||
"wind_down_time" : 0,
|
||||
"start_up_time": 0,
|
||||
"wind_down_time": 0,
|
||||
}
|
||||
|
||||
def __init__(self, *continual_animations, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
self.group = Group(*[ca.mobject for ca in continual_animations])
|
||||
@@ -67,12 +72,13 @@ class ContinualAnimationGroup(ContinualAnimation):
|
||||
for continual_animation in self.continual_animations:
|
||||
continual_animation.update(dt)
|
||||
|
||||
|
||||
class ContinualRotation(ContinualAnimation):
|
||||
CONFIG = {
|
||||
"axis" : OUT,
|
||||
"rate" : np.pi/12, #Radians per second
|
||||
"in_place" : True,
|
||||
"about_point" : None,
|
||||
"axis": OUT,
|
||||
"rate": np.pi / 12, # Radians per second
|
||||
"in_place": True,
|
||||
"about_point": None,
|
||||
}
|
||||
|
||||
def update_mobject(self, dt):
|
||||
@@ -83,23 +89,16 @@ class ContinualRotation(ContinualAnimation):
|
||||
else:
|
||||
about_point = ORIGIN
|
||||
self.mobject.rotate(
|
||||
dt*self.rate, axis = self.axis,
|
||||
about_point = about_point
|
||||
dt * self.rate, axis=self.axis,
|
||||
about_point=about_point
|
||||
)
|
||||
|
||||
|
||||
class ContinualMovement(ContinualAnimation):
|
||||
CONFIG = {
|
||||
"direction" : RIGHT,
|
||||
"rate" : 0.05, #Units per second
|
||||
"direction": RIGHT,
|
||||
"rate": 0.05, # Units per second
|
||||
}
|
||||
|
||||
def update_mobject(self, dt):
|
||||
self.mobject.shift(dt*self.rate*self.direction)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
self.mobject.shift(dt * self.rate * self.direction)
|
||||
|
||||
@@ -2,20 +2,23 @@ from __future__ import absolute_import
|
||||
|
||||
from continual_animation.continual_animation import ContinualAnimation
|
||||
|
||||
|
||||
class NormalAnimationAsContinualAnimation(ContinualAnimation):
|
||||
CONFIG = {
|
||||
"start_up_time" : 0,
|
||||
"wind_down_time" : 0,
|
||||
"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)
|
||||
min(float(self.internal_time) / self.animation.run_time, 1)
|
||||
)
|
||||
|
||||
|
||||
class CycleAnimation(ContinualAnimation):
|
||||
def __init__(self, animation, **kwargs):
|
||||
self.animation = animation
|
||||
@@ -23,6 +26,5 @@ class CycleAnimation(ContinualAnimation):
|
||||
|
||||
def update_mobject(self, dt):
|
||||
mod_value = self.internal_time % self.animation.run_time
|
||||
alpha = mod_value/float(self.animation.run_time)
|
||||
alpha = mod_value / float(self.animation.run_time)
|
||||
self.animation.update(alpha)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ 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__(
|
||||
|
||||
@@ -6,8 +6,9 @@ from animation.update import MaintainPositionRelativeTo
|
||||
|
||||
class ContinualUpdateFromFunc(ContinualAnimation):
|
||||
CONFIG = {
|
||||
"function_depends_on_dt" : False
|
||||
"function_depends_on_dt": False
|
||||
}
|
||||
|
||||
def __init__(self, mobject, func, **kwargs):
|
||||
self.func = func
|
||||
ContinualAnimation.__init__(self, mobject, **kwargs)
|
||||
@@ -18,16 +19,19 @@ class ContinualUpdateFromFunc(ContinualAnimation):
|
||||
else:
|
||||
self.func(self.mobject)
|
||||
|
||||
|
||||
class ContinualUpdateFromTimeFunc(ContinualUpdateFromFunc):
|
||||
CONFIG = {
|
||||
"function_depends_on_dt" : True
|
||||
"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)
|
||||
self.anim = MaintainPositionRelativeTo(
|
||||
mobject, tracked_mobject, **kwargs)
|
||||
ContinualAnimation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
self.anim.update(0) # 0 is arbitrary
|
||||
self.anim.update(0) # 0 is arbitrary
|
||||
|
||||
@@ -4,8 +4,8 @@ from big_ol_pile_of_manim_imports import *
|
||||
|
||||
# To watch one of these scenes, run the following:
|
||||
# python extract_scene.py file_name <SceneName> -p
|
||||
#
|
||||
# Use the flat -l for a faster rendering at a lower
|
||||
#
|
||||
# Use the flat -l for a faster rendering at a lower
|
||||
# quality, use -s to skip to the end and just show
|
||||
# the final frame, and use -n <number> to skip ahead
|
||||
# to the n'th animation of a scene.
|
||||
@@ -16,17 +16,18 @@ class SquareToCircle(Scene):
|
||||
circle = Circle()
|
||||
square = Square()
|
||||
square.flip(RIGHT)
|
||||
square.rotate(-3*TAU/8)
|
||||
square.rotate(-3 * TAU / 8)
|
||||
|
||||
self.play(ShowCreation(square))
|
||||
self.play(Transform(square, circle))
|
||||
self.play(FadeOut(square))
|
||||
|
||||
|
||||
class WarpSquare(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
self.play(ApplyPointwiseFunction(
|
||||
lambda (x, y, z) : complex_to_R3(np.exp(complex(x, y))),
|
||||
lambda (x, y, z): complex_to_R3(np.exp(complex(x, y))),
|
||||
square
|
||||
))
|
||||
self.wait()
|
||||
@@ -42,8 +43,8 @@ class Rotation3d(ThreeDScene):
|
||||
# STEP 1
|
||||
# Build two cube in the 3D scene, one for around the origin,
|
||||
# the other shifted along the vector RIGHT + UP + OUT
|
||||
cube_origin = Cube(fill_opacity = 0.8, stroke_width = 1.,
|
||||
side_length = 1., fill_color = WHITE)
|
||||
cube_origin = Cube(fill_opacity=0.8, stroke_width=1.,
|
||||
side_length=1., fill_color=WHITE)
|
||||
|
||||
# RIGHT side: Red
|
||||
# UP side: Green
|
||||
@@ -51,15 +52,15 @@ class Rotation3d(ThreeDScene):
|
||||
orientations = [IN, OUT, LEFT, RIGHT, UP, DOWN]
|
||||
for face, orient in zip(cube_origin.family_members_with_points(), orientations):
|
||||
if np.array_equal(orient, RIGHT):
|
||||
face.set_style_data(fill_color = RED)
|
||||
face.set_style_data(fill_color=RED)
|
||||
elif np.array_equal(orient, UP):
|
||||
face.set_style_data(fill_color = GREEN)
|
||||
face.set_style_data(fill_color=GREEN)
|
||||
elif np.array_equal(orient, OUT):
|
||||
face.set_style_data(fill_color = BLUE)
|
||||
face.set_style_data(fill_color=BLUE)
|
||||
|
||||
cube_shifted = Cube(fill_opacity = 0.8, stroke_width = 1.,
|
||||
side_length = 1., fill_color = BLUE)
|
||||
shift_vec = 2*(RIGHT + UP + OUT)
|
||||
cube_shifted = Cube(fill_opacity=0.8, stroke_width=1.,
|
||||
side_length=1., fill_color=BLUE)
|
||||
shift_vec = 2 * (RIGHT + UP + OUT)
|
||||
cube_shifted.shift(shift_vec)
|
||||
|
||||
# STEP 2
|
||||
@@ -71,8 +72,8 @@ class Rotation3d(ThreeDScene):
|
||||
# Setup the camera position
|
||||
phi, theta, distance = ThreeDCamera().get_spherical_coords()
|
||||
angle_factor = 0.9
|
||||
phi += 2*np.pi/4*angle_factor
|
||||
theta += 3*2*np.pi/8
|
||||
phi += 2 * np.pi / 4 * angle_factor
|
||||
theta += 3 * 2 * np.pi / 8
|
||||
self.set_camera_position(phi, theta, distance)
|
||||
self.wait()
|
||||
|
||||
@@ -80,23 +81,23 @@ class Rotation3d(ThreeDScene):
|
||||
# Animation
|
||||
# Animation 1: rotation around the Z-axis with the ORIGIN of the space
|
||||
# as center of rotation
|
||||
theta += 2*np.pi
|
||||
theta += 2 * np.pi
|
||||
self.move_camera(phi, theta, distance,
|
||||
run_time = 5)
|
||||
run_time=5)
|
||||
|
||||
# Animation 2: shift the space in order of to get the center of the shifted cube
|
||||
# as the next center of rotation
|
||||
cube_center = cube_shifted.get_center()
|
||||
self.move_camera(center_x = cube_center[0],
|
||||
center_y = cube_center[1],
|
||||
center_z = cube_center[2],
|
||||
run_time = 2)
|
||||
self.move_camera(center_x=cube_center[0],
|
||||
center_y=cube_center[1],
|
||||
center_z=cube_center[2],
|
||||
run_time=2)
|
||||
|
||||
# Animation 3: rotation around the Z-axis with the center of the shifted cube
|
||||
# Animation 3: rotation around the Z-axis with the center of the shifted cube
|
||||
# as center of rotation
|
||||
theta += 2*np.pi
|
||||
theta += 2 * np.pi
|
||||
self.move_camera(phi, theta, distance,
|
||||
run_time = 5)
|
||||
run_time=5)
|
||||
|
||||
|
||||
class SpinAroundCube(ThreeDScene):
|
||||
@@ -112,28 +113,28 @@ class SpinAroundCube(ThreeDScene):
|
||||
def construct(self):
|
||||
axes = ThreeDAxes()
|
||||
cube = Cube(
|
||||
fill_opacity = 1,
|
||||
stroke_color = LIGHT_GREY,
|
||||
stroke_width = 1,
|
||||
fill_opacity=1,
|
||||
stroke_color=LIGHT_GREY,
|
||||
stroke_width=1,
|
||||
)
|
||||
# The constant OUT is np.array([0, 0, 1])
|
||||
cube.next_to(ORIGIN, UP+RIGHT+OUT)
|
||||
cube.next_to(ORIGIN, UP + RIGHT + OUT)
|
||||
self.add(axes, cube)
|
||||
|
||||
# The camera starts positioned with phi=0, meaning it
|
||||
# is directly above the xy-plane, and theta = -TAU/4,
|
||||
# is directly above the xy-plane, and theta = -TAU/4,
|
||||
# which makes the "down" direction of the screen point
|
||||
# in the negative y direction.
|
||||
|
||||
# This animates a camera movement
|
||||
self.move_camera(
|
||||
# Tilted 20 degrees off xy plane (70 degrees off the vertical)
|
||||
phi = (70./360.)*TAU,
|
||||
phi=(70. / 360.) * TAU,
|
||||
# Positioned above the third quadrant of
|
||||
# the xy-plane
|
||||
theta = (-110./360.)*TAU,
|
||||
theta=(-110. / 360.) * TAU,
|
||||
# pass in animation config just like a .play call
|
||||
run_time = 3
|
||||
run_time=3
|
||||
)
|
||||
self.wait()
|
||||
# If you want the camera to slowly rotate about
|
||||
@@ -142,33 +143,17 @@ class SpinAroundCube(ThreeDScene):
|
||||
self.wait(4)
|
||||
self.play(FadeOut(cube))
|
||||
|
||||
|
||||
|
||||
text = TextMobject("Your ad here")
|
||||
text.rotate(TAU/4, axis = RIGHT)
|
||||
text.rotate(TAU / 4, axis=RIGHT)
|
||||
text.next_to(cube, OUT)
|
||||
self.play(Write(text))
|
||||
# If you want to play animations while moving the camera,
|
||||
# include them in an "added_anims" list to move_camera
|
||||
self.move_camera(
|
||||
theta = -0.2*TAU,
|
||||
added_anims = [
|
||||
text.shift, 3*OUT,
|
||||
text.set_fill, {"opacity" : 1},
|
||||
theta=-0.2 * TAU,
|
||||
added_anims=[
|
||||
text.shift, 3 * OUT,
|
||||
text.set_fill, {"opacity": 1},
|
||||
]
|
||||
)
|
||||
self.wait(4)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
385
extract_scene.py
385
extract_scene.py
@@ -3,7 +3,6 @@
|
||||
import sys
|
||||
import argparse
|
||||
import imp
|
||||
import imp
|
||||
import inspect
|
||||
import itertools as it
|
||||
import os
|
||||
@@ -12,7 +11,6 @@ import traceback
|
||||
|
||||
from constants import *
|
||||
|
||||
from camera.camera import Camera
|
||||
from scene.scene import Scene
|
||||
from utils.sounds import play_error_sound
|
||||
from utils.sounds import play_finish_sound
|
||||
@@ -45,169 +43,178 @@ NO_SCENE_MESSAGE = """
|
||||
|
||||
|
||||
def get_configuration():
|
||||
try:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"file", help = "path to file holding the python code for the scene"
|
||||
)
|
||||
parser.add_argument(
|
||||
"scene_name", help = "Name of the Scene class you want to see"
|
||||
)
|
||||
optional_args = [
|
||||
("-p", "--preview"),
|
||||
("-w", "--write_to_movie"),
|
||||
("-s", "--show_last_frame"),
|
||||
("-l", "--low_quality"),
|
||||
("-m", "--medium_quality"),
|
||||
("-g", "--save_pngs"),
|
||||
("-f", "--show_file_in_finder"),
|
||||
("-t", "--transparent"),
|
||||
("-q", "--quiet"),
|
||||
("-a", "--write_all")
|
||||
]
|
||||
for short_arg, long_arg in optional_args:
|
||||
parser.add_argument(short_arg, long_arg, action = "store_true")
|
||||
parser.add_argument("-o", "--output_name")
|
||||
parser.add_argument("-n", "--start_at_animation_number")
|
||||
args = parser.parse_args()
|
||||
if args.output_name != None:
|
||||
output_name_root, output_name_ext = os.path.splitext(args.output_name)
|
||||
expected_ext = '.png' if args.show_last_frame else '.mp4'
|
||||
if not output_name_ext in ['', expected_ext]:
|
||||
print "WARNING: The output will be to (doubly-dotted) %s%s"%output_name_root%expected_ext
|
||||
try:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"file", help="path to file holding the python code for the scene"
|
||||
)
|
||||
parser.add_argument(
|
||||
"scene_name", help="Name of the Scene class you want to see"
|
||||
)
|
||||
optional_args = [
|
||||
("-p", "--preview"),
|
||||
("-w", "--write_to_movie"),
|
||||
("-s", "--show_last_frame"),
|
||||
("-l", "--low_quality"),
|
||||
("-m", "--medium_quality"),
|
||||
("-g", "--save_pngs"),
|
||||
("-f", "--show_file_in_finder"),
|
||||
("-t", "--transparent"),
|
||||
("-q", "--quiet"),
|
||||
("-a", "--write_all")
|
||||
]
|
||||
for short_arg, long_arg in optional_args:
|
||||
parser.add_argument(short_arg, long_arg, action="store_true")
|
||||
parser.add_argument("-o", "--output_name")
|
||||
parser.add_argument("-n", "--start_at_animation_number")
|
||||
args = parser.parse_args()
|
||||
if args.output_name is not None:
|
||||
output_name_root, output_name_ext = os.path.splitext(
|
||||
args.output_name)
|
||||
expected_ext = '.png' if args.show_last_frame else '.mp4'
|
||||
if output_name_ext not in ['', expected_ext]:
|
||||
print "WARNING: The output will be to (doubly-dotted) %s%s" % output_name_root % expected_ext
|
||||
output_name = args.output_name
|
||||
else:
|
||||
# If anyone wants .mp4.mp4 and is surprised to only get .mp4, or such... Well, too bad.
|
||||
output_name = output_name_root
|
||||
else:
|
||||
output_name = args.output_name
|
||||
else:
|
||||
# If anyone wants .mp4.mp4 and is surprised to only get .mp4, or such... Well, too bad.
|
||||
output_name = output_name_root
|
||||
else:
|
||||
output_name = args.output_name
|
||||
except argparse.ArgumentError as err:
|
||||
print(str(err))
|
||||
sys.exit(2)
|
||||
config = {
|
||||
"file" : args.file,
|
||||
"scene_name" : args.scene_name,
|
||||
"open_video_upon_completion" : args.preview,
|
||||
"show_file_in_finder" : args.show_file_in_finder,
|
||||
#By default, write to file
|
||||
"write_to_movie" : args.write_to_movie or not args.show_last_frame,
|
||||
"show_last_frame" : args.show_last_frame,
|
||||
"save_pngs" : args.save_pngs,
|
||||
#If -t is passed in (for transparent), this will be RGBA
|
||||
"saved_image_mode": "RGBA" if args.transparent else "RGB",
|
||||
"movie_file_extension" : ".mov" if args.transparent else ".mp4",
|
||||
"quiet" : args.quiet or args.write_all,
|
||||
"ignore_waits" : args.preview,
|
||||
"write_all" : args.write_all,
|
||||
"output_name" : output_name,
|
||||
"start_at_animation_number" : args.start_at_animation_number,
|
||||
"end_at_animation_number" : None,
|
||||
}
|
||||
if args.low_quality:
|
||||
config["camera_config"] = LOW_QUALITY_CAMERA_CONFIG
|
||||
config["frame_duration"] = LOW_QUALITY_FRAME_DURATION
|
||||
elif args.medium_quality:
|
||||
config["camera_config"] = MEDIUM_QUALITY_CAMERA_CONFIG
|
||||
config["frame_duration"] = MEDIUM_QUALITY_FRAME_DURATION
|
||||
else:
|
||||
config["camera_config"] = PRODUCTION_QUALITY_CAMERA_CONFIG
|
||||
config["frame_duration"] = PRODUCTION_QUALITY_FRAME_DURATION
|
||||
except argparse.ArgumentError as err:
|
||||
print(str(err))
|
||||
sys.exit(2)
|
||||
config = {
|
||||
"file": args.file,
|
||||
"scene_name": args.scene_name,
|
||||
"open_video_upon_completion": args.preview,
|
||||
"show_file_in_finder": args.show_file_in_finder,
|
||||
# By default, write to file
|
||||
"write_to_movie": args.write_to_movie or not args.show_last_frame,
|
||||
"show_last_frame": args.show_last_frame,
|
||||
"save_pngs": args.save_pngs,
|
||||
# If -t is passed in (for transparent), this will be RGBA
|
||||
"saved_image_mode": "RGBA" if args.transparent else "RGB",
|
||||
"movie_file_extension": ".mov" if args.transparent else ".mp4",
|
||||
"quiet": args.quiet or args.write_all,
|
||||
"ignore_waits": args.preview,
|
||||
"write_all": args.write_all,
|
||||
"output_name": output_name,
|
||||
"start_at_animation_number": args.start_at_animation_number,
|
||||
"end_at_animation_number": None,
|
||||
}
|
||||
if args.low_quality:
|
||||
config["camera_config"] = LOW_QUALITY_CAMERA_CONFIG
|
||||
config["frame_duration"] = LOW_QUALITY_FRAME_DURATION
|
||||
elif args.medium_quality:
|
||||
config["camera_config"] = MEDIUM_QUALITY_CAMERA_CONFIG
|
||||
config["frame_duration"] = MEDIUM_QUALITY_FRAME_DURATION
|
||||
else:
|
||||
config["camera_config"] = PRODUCTION_QUALITY_CAMERA_CONFIG
|
||||
config["frame_duration"] = PRODUCTION_QUALITY_FRAME_DURATION
|
||||
|
||||
stan = config["start_at_animation_number"]
|
||||
if stan is not None:
|
||||
if "," in stan:
|
||||
start, end = stan.split(",")
|
||||
config["start_at_animation_number"] = int(start)
|
||||
config["end_at_animation_number"] = int(end)
|
||||
else:
|
||||
config["start_at_animation_number"] = int(stan)
|
||||
stan = config["start_at_animation_number"]
|
||||
if stan is not None:
|
||||
if "," in stan:
|
||||
start, end = stan.split(",")
|
||||
config["start_at_animation_number"] = int(start)
|
||||
config["end_at_animation_number"] = int(end)
|
||||
else:
|
||||
config["start_at_animation_number"] = int(stan)
|
||||
|
||||
config["skip_animations"] = any([
|
||||
config["show_last_frame"] and not config["write_to_movie"],
|
||||
config["start_at_animation_number"],
|
||||
])
|
||||
return config
|
||||
|
||||
config["skip_animations"] = any([
|
||||
config["show_last_frame"] and not config["write_to_movie"],
|
||||
config["start_at_animation_number"],
|
||||
])
|
||||
return config
|
||||
|
||||
def handle_scene(scene, **config):
|
||||
import platform
|
||||
if config["quiet"]:
|
||||
curr_stdout = sys.stdout
|
||||
sys.stdout = open(os.devnull, "w")
|
||||
import platform
|
||||
if config["quiet"]:
|
||||
curr_stdout = sys.stdout
|
||||
sys.stdout = open(os.devnull, "w")
|
||||
|
||||
if config["show_last_frame"]:
|
||||
scene.save_image(mode = config["saved_image_mode"])
|
||||
open_file = any([
|
||||
config["show_last_frame"],
|
||||
config["open_video_upon_completion"],
|
||||
config["show_file_in_finder"]
|
||||
])
|
||||
if open_file:
|
||||
commands = ["open"]
|
||||
if (platform.system() == "Linux"):
|
||||
commands = ["xdg-open"]
|
||||
if config["show_last_frame"]:
|
||||
scene.save_image(mode=config["saved_image_mode"])
|
||||
open_file = any([
|
||||
config["show_last_frame"],
|
||||
config["open_video_upon_completion"],
|
||||
config["show_file_in_finder"]
|
||||
])
|
||||
if open_file:
|
||||
commands = ["open"]
|
||||
if (platform.system() == "Linux"):
|
||||
commands = ["xdg-open"]
|
||||
|
||||
if config["show_file_in_finder"]:
|
||||
commands.append("-R")
|
||||
#
|
||||
if config["show_last_frame"]:
|
||||
commands.append(scene.get_image_file_path())
|
||||
else:
|
||||
commands.append(scene.get_movie_file_path())
|
||||
FNULL = open(os.devnull, 'w')
|
||||
sp.call(commands, stdout=FNULL, stderr=sp.STDOUT)
|
||||
FNULL.close()
|
||||
if config["show_file_in_finder"]:
|
||||
commands.append("-R")
|
||||
#
|
||||
if config["show_last_frame"]:
|
||||
commands.append(scene.get_image_file_path())
|
||||
else:
|
||||
commands.append(scene.get_movie_file_path())
|
||||
FNULL = open(os.devnull, 'w')
|
||||
sp.call(commands, stdout=FNULL, stderr=sp.STDOUT)
|
||||
FNULL.close()
|
||||
|
||||
if config["quiet"]:
|
||||
sys.stdout.close()
|
||||
sys.stdout = curr_stdout
|
||||
|
||||
if config["quiet"]:
|
||||
sys.stdout.close()
|
||||
sys.stdout = curr_stdout
|
||||
|
||||
def is_scene(obj):
|
||||
if not inspect.isclass(obj):
|
||||
return False
|
||||
if not issubclass(obj, Scene):
|
||||
return False
|
||||
if obj == Scene:
|
||||
return False
|
||||
return True
|
||||
if not inspect.isclass(obj):
|
||||
return False
|
||||
if not issubclass(obj, Scene):
|
||||
return False
|
||||
if obj == Scene:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def prompt_user_for_choice(name_to_obj):
|
||||
num_to_name = {}
|
||||
names = sorted(name_to_obj.keys())
|
||||
for count, name in zip(it.count(1), names):
|
||||
print("%d: %s"%(count, name))
|
||||
num_to_name[count] = name
|
||||
try:
|
||||
user_input = raw_input(CHOOSE_NUMBER_MESSAGE)
|
||||
return [
|
||||
name_to_obj[num_to_name[int(num_str)]]
|
||||
for num_str in user_input.split(",")
|
||||
]
|
||||
except:
|
||||
print(INVALID_NUMBER_MESSAGE)
|
||||
sys.exit()
|
||||
num_to_name = {}
|
||||
names = sorted(name_to_obj.keys())
|
||||
for count, name in zip(it.count(1), names):
|
||||
print("%d: %s" % (count, name))
|
||||
num_to_name[count] = name
|
||||
try:
|
||||
user_input = raw_input(CHOOSE_NUMBER_MESSAGE)
|
||||
return [
|
||||
name_to_obj[num_to_name[int(num_str)]]
|
||||
for num_str in user_input.split(",")
|
||||
]
|
||||
except:
|
||||
print(INVALID_NUMBER_MESSAGE)
|
||||
sys.exit()
|
||||
|
||||
|
||||
def get_scene_classes(scene_names_to_classes, config):
|
||||
if len(scene_names_to_classes) == 0:
|
||||
print(NO_SCENE_MESSAGE)
|
||||
return []
|
||||
if len(scene_names_to_classes) == 1:
|
||||
return scene_names_to_classes.values()
|
||||
if config["scene_name"] in scene_names_to_classes:
|
||||
return [scene_names_to_classes[config["scene_name"]] ]
|
||||
if config["scene_name"] != "":
|
||||
print(SCENE_NOT_FOUND_MESSAGE)
|
||||
return []
|
||||
if config["write_all"]:
|
||||
return scene_names_to_classes.values()
|
||||
return prompt_user_for_choice(scene_names_to_classes)
|
||||
if len(scene_names_to_classes) == 0:
|
||||
print(NO_SCENE_MESSAGE)
|
||||
return []
|
||||
if len(scene_names_to_classes) == 1:
|
||||
return scene_names_to_classes.values()
|
||||
if config["scene_name"] in scene_names_to_classes:
|
||||
return [scene_names_to_classes[config["scene_name"]]]
|
||||
if config["scene_name"] != "":
|
||||
print(SCENE_NOT_FOUND_MESSAGE)
|
||||
return []
|
||||
if config["write_all"]:
|
||||
return scene_names_to_classes.values()
|
||||
return prompt_user_for_choice(scene_names_to_classes)
|
||||
|
||||
|
||||
def get_module_windows(file_name):
|
||||
module_name = file_name.replace(".py", "")
|
||||
last_module = imp.load_module("__init__", *imp.find_module("__init__", ['.']))
|
||||
for part in module_name.split(os.sep):
|
||||
load_args = imp.find_module(part, [os.path.dirname(last_module.__file__)])
|
||||
last_module = imp.load_module(part, *load_args)
|
||||
return last_module
|
||||
module_name = file_name.replace(".py", "")
|
||||
last_module = imp.load_module(
|
||||
"__init__", *imp.find_module("__init__", ['.']))
|
||||
for part in module_name.split(os.sep):
|
||||
load_args = imp.find_module(
|
||||
part, [os.path.dirname(last_module.__file__)])
|
||||
last_module = imp.load_module(part, *load_args)
|
||||
return last_module
|
||||
|
||||
|
||||
def get_module_posix(file_name):
|
||||
module_name = file_name.replace(".py", "")
|
||||
@@ -217,54 +224,54 @@ def get_module_posix(file_name):
|
||||
last_module = imp.load_module(part, *load_args)
|
||||
return last_module
|
||||
|
||||
|
||||
def get_module(file_name):
|
||||
if os.name == 'nt':
|
||||
return get_module_windows(file_name)
|
||||
return get_module_posix(file_name)
|
||||
|
||||
|
||||
def main():
|
||||
config = get_configuration()
|
||||
module = get_module(config["file"])
|
||||
scene_names_to_classes = dict(
|
||||
inspect.getmembers(module, is_scene)
|
||||
)
|
||||
config = get_configuration()
|
||||
module = get_module(config["file"])
|
||||
scene_names_to_classes = dict(inspect.getmembers(module, is_scene))
|
||||
|
||||
config["output_directory"] = os.path.join(
|
||||
ANIMATIONS_DIR,
|
||||
config["file"].replace(".py", "")
|
||||
)
|
||||
config["output_directory"] = os.path.join(
|
||||
ANIMATIONS_DIR,
|
||||
config["file"].replace(".py", "")
|
||||
)
|
||||
|
||||
scene_kwargs = dict([
|
||||
(key, config[key])
|
||||
for key in [
|
||||
"camera_config",
|
||||
"frame_duration",
|
||||
"skip_animations",
|
||||
"write_to_movie",
|
||||
"output_directory",
|
||||
"save_pngs",
|
||||
"movie_file_extension",
|
||||
"start_at_animation_number",
|
||||
"end_at_animation_number",
|
||||
]
|
||||
])
|
||||
|
||||
scene_kwargs["name"] = config["output_name"]
|
||||
if config["save_pngs"]:
|
||||
print "We are going to save a PNG sequence as well..."
|
||||
scene_kwargs["save_pngs"] = True
|
||||
scene_kwargs["pngs_mode"] = config["saved_image_mode"]
|
||||
|
||||
for SceneClass in get_scene_classes(scene_names_to_classes, config):
|
||||
try:
|
||||
handle_scene(SceneClass(**scene_kwargs), **config)
|
||||
play_finish_sound()
|
||||
except:
|
||||
print("\n\n")
|
||||
traceback.print_exc()
|
||||
print("\n\n")
|
||||
play_error_sound()
|
||||
scene_kwargs = dict([
|
||||
(key, config[key])
|
||||
for key in [
|
||||
"camera_config",
|
||||
"frame_duration",
|
||||
"skip_animations",
|
||||
"write_to_movie",
|
||||
"output_directory",
|
||||
"save_pngs",
|
||||
"movie_file_extension",
|
||||
"start_at_animation_number",
|
||||
"end_at_animation_number",
|
||||
]
|
||||
])
|
||||
|
||||
scene_kwargs["name"] = config["output_name"]
|
||||
if config["save_pngs"]:
|
||||
print "We are going to save a PNG sequence as well..."
|
||||
scene_kwargs["save_pngs"] = True
|
||||
scene_kwargs["pngs_mode"] = config["saved_image_mode"]
|
||||
|
||||
for SceneClass in get_scene_classes(scene_names_to_classes, config):
|
||||
try:
|
||||
handle_scene(SceneClass(**scene_kwargs), **config)
|
||||
play_finish_sound()
|
||||
except:
|
||||
print("\n\n")
|
||||
traceback.print_exc()
|
||||
print("\n\n")
|
||||
play_error_sound()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -8,10 +8,8 @@ from animation.animation import Animation
|
||||
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
|
||||
@@ -23,42 +21,45 @@ from mobject.geometry import Rectangle
|
||||
from mobject.geometry import Square
|
||||
from mobject.svg.drawings import PatreonLogo
|
||||
|
||||
|
||||
class OpeningQuote(Scene):
|
||||
CONFIG = {
|
||||
"quote" : [],
|
||||
"quote_arg_separator" : " ",
|
||||
"highlighted_quote_terms" : {},
|
||||
"author" : "",
|
||||
"fade_in_kwargs" : {
|
||||
"submobject_mode" : "lagged_start",
|
||||
"rate_func" : None,
|
||||
"lag_factor" : 4,
|
||||
"run_time" : 5,
|
||||
"quote": [],
|
||||
"quote_arg_separator": " ",
|
||||
"highlighted_quote_terms": {},
|
||||
"author": "",
|
||||
"fade_in_kwargs": {
|
||||
"submobject_mode": "lagged_start",
|
||||
"rate_func": None,
|
||||
"lag_factor": 4,
|
||||
"run_time": 5,
|
||||
},
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.quote = self.get_quote()
|
||||
self.author = self.get_author(self.quote)
|
||||
|
||||
self.play(FadeIn(self.quote, **self.fade_in_kwargs))
|
||||
self.wait(2)
|
||||
self.play(Write(self.author, run_time = 3))
|
||||
self.play(Write(self.author, run_time=3))
|
||||
self.wait()
|
||||
|
||||
def get_quote(self, max_width = FRAME_WIDTH-1):
|
||||
def get_quote(self, max_width=FRAME_WIDTH - 1):
|
||||
text_mobject_kwargs = {
|
||||
"alignment" : "",
|
||||
"arg_separator" : self.quote_arg_separator,
|
||||
"alignment": "",
|
||||
"arg_separator": self.quote_arg_separator,
|
||||
}
|
||||
if isinstance(self.quote, str):
|
||||
quote = TextMobject("``%s''"%self.quote.strip(), **text_mobject_kwargs)
|
||||
quote = TextMobject("``%s''" %
|
||||
self.quote.strip(), **text_mobject_kwargs)
|
||||
else:
|
||||
words = ["\\Large ``"] + list(self.quote) + ["''"]
|
||||
quote = TextMobject(*words, **text_mobject_kwargs)
|
||||
##TODO, make less hacky
|
||||
# TODO, make less hacky
|
||||
if self.quote_arg_separator == " ":
|
||||
quote[0].shift(0.2*RIGHT)
|
||||
quote[-1].shift(0.2*LEFT)
|
||||
quote[0].shift(0.2 * RIGHT)
|
||||
quote[-1].shift(0.2 * LEFT)
|
||||
for term, color in self.set_colored_quote_terms.items():
|
||||
quote.set_color_by_tex(term, color)
|
||||
quote.to_edge(UP)
|
||||
@@ -72,12 +73,14 @@ class OpeningQuote(Scene):
|
||||
author.set_color(YELLOW)
|
||||
return author
|
||||
|
||||
|
||||
class PatreonThanks(Scene):
|
||||
CONFIG = {
|
||||
"specific_patrons" : [],
|
||||
"max_patron_group_size" : 20,
|
||||
"patron_scale_val" : 0.8,
|
||||
"specific_patrons": [],
|
||||
"max_patron_group_size": 20,
|
||||
"patron_scale_val": 0.8,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
morty = Mortimer()
|
||||
morty.next_to(ORIGIN, DOWN)
|
||||
@@ -89,17 +92,17 @@ class PatreonThanks(Scene):
|
||||
patrons = map(TextMobject, self.specific_patrons)
|
||||
num_groups = float(len(patrons)) / self.max_patron_group_size
|
||||
proportion_range = np.linspace(0, 1, num_groups + 1)
|
||||
indices = (len(patrons)*proportion_range).astype('int')
|
||||
indices = (len(patrons) * proportion_range).astype('int')
|
||||
patron_groups = [
|
||||
VGroup(*patrons[i:j])
|
||||
for i, j in zip(indices, indices[1:])
|
||||
]
|
||||
]
|
||||
|
||||
for i, group in enumerate(patron_groups):
|
||||
left_group = VGroup(*group[:len(group)/2])
|
||||
right_group = VGroup(*group[len(group)/2:])
|
||||
left_group = VGroup(*group[:len(group) / 2])
|
||||
right_group = VGroup(*group[len(group) / 2:])
|
||||
for subgroup, vect in (left_group, LEFT), (right_group, RIGHT):
|
||||
subgroup.arrange_submobjects(DOWN, aligned_edge = LEFT)
|
||||
subgroup.arrange_submobjects(DOWN, aligned_edge=LEFT)
|
||||
subgroup.scale(self.patron_scale_val)
|
||||
subgroup.to_edge(vect)
|
||||
|
||||
@@ -109,7 +112,7 @@ class PatreonThanks(Scene):
|
||||
if last_group is not None:
|
||||
self.play(
|
||||
FadeOut(last_group),
|
||||
morty.look, UP+LEFT
|
||||
morty.look, UP + LEFT
|
||||
)
|
||||
else:
|
||||
anims += [
|
||||
@@ -117,25 +120,27 @@ class PatreonThanks(Scene):
|
||||
]
|
||||
self.play(
|
||||
LaggedStart(
|
||||
FadeIn, group,
|
||||
run_time = 2,
|
||||
FadeIn, group,
|
||||
run_time=2,
|
||||
),
|
||||
morty.change, "gracious", group.get_corner(UP+LEFT),
|
||||
morty.change, "gracious", group.get_corner(UP + LEFT),
|
||||
*anims
|
||||
)
|
||||
self.play(morty.look_at, group.get_corner(DOWN+LEFT))
|
||||
self.play(morty.look_at, group.get_corner(UP+RIGHT))
|
||||
self.play(morty.look_at, group.get_corner(DOWN+RIGHT))
|
||||
self.play(morty.look_at, group.get_corner(DOWN + LEFT))
|
||||
self.play(morty.look_at, group.get_corner(UP + RIGHT))
|
||||
self.play(morty.look_at, group.get_corner(DOWN + RIGHT))
|
||||
self.play(Blink(morty))
|
||||
last_group = group
|
||||
|
||||
|
||||
class PatreonEndScreen(PatreonThanks):
|
||||
CONFIG = {
|
||||
"n_patron_columns" : 3,
|
||||
"max_patron_width" : 3,
|
||||
"run_time" : 20,
|
||||
"randomize_order" : True,
|
||||
"n_patron_columns": 3,
|
||||
"max_patron_width": 3,
|
||||
"run_time": 20,
|
||||
"randomize_order": True,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
if self.randomize_order:
|
||||
random.shuffle(self.specific_patrons)
|
||||
@@ -145,32 +150,32 @@ class PatreonEndScreen(PatreonThanks):
|
||||
def add_title(self):
|
||||
title = self.title = TextMobject("Clicky Stuffs")
|
||||
title.scale(1.5)
|
||||
title.to_edge(UP, buff = MED_SMALL_BUFF)
|
||||
title.to_edge(UP, buff=MED_SMALL_BUFF)
|
||||
|
||||
randy, morty = self.pi_creatures = VGroup(Randolph(), Mortimer())
|
||||
for pi, vect in (randy, LEFT), (morty, RIGHT):
|
||||
pi.scale_to_fit_height(title.get_height())
|
||||
pi.change_mode("thinking")
|
||||
pi.look(DOWN)
|
||||
pi.next_to(title, vect, buff = MED_LARGE_BUFF)
|
||||
pi.next_to(title, vect, buff=MED_LARGE_BUFF)
|
||||
self.add_foreground_mobjects(title, randy, morty)
|
||||
|
||||
def scroll_through_patrons(self):
|
||||
logo_box = Square(side_length = 2.5)
|
||||
logo_box.to_corner(DOWN+LEFT, buff = MED_LARGE_BUFF)
|
||||
logo_box = Square(side_length=2.5)
|
||||
logo_box.to_corner(DOWN + LEFT, buff=MED_LARGE_BUFF)
|
||||
total_width = FRAME_X_RADIUS - logo_box.get_right()[0]
|
||||
|
||||
black_rect = Rectangle(
|
||||
fill_color = BLACK,
|
||||
fill_opacity = 1,
|
||||
stroke_width = 0,
|
||||
width = FRAME_WIDTH,
|
||||
height = 1.1*FRAME_Y_RADIUS
|
||||
fill_color=BLACK,
|
||||
fill_opacity=1,
|
||||
stroke_width=0,
|
||||
width=FRAME_WIDTH,
|
||||
height=1.1 * FRAME_Y_RADIUS
|
||||
)
|
||||
black_rect.to_edge(UP, buff = 0)
|
||||
line = DashedLine(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT)
|
||||
black_rect.to_edge(UP, buff=0)
|
||||
line = DashedLine(FRAME_X_RADIUS * LEFT, FRAME_X_RADIUS * RIGHT)
|
||||
line.move_to(black_rect, DOWN)
|
||||
line.shift(SMALL_BUFF*SMALL_BUFF*DOWN)
|
||||
line.shift(SMALL_BUFF * SMALL_BUFF * DOWN)
|
||||
self.add(line)
|
||||
|
||||
patrons = VGroup(*map(TextMobject, self.specific_patrons))
|
||||
@@ -181,34 +186,36 @@ class PatreonEndScreen(PatreonThanks):
|
||||
columns = VGroup(*[
|
||||
VGroup(
|
||||
*patrons[i::self.n_patron_columns]
|
||||
).arrange_submobjects(DOWN, buff = MED_SMALL_BUFF)
|
||||
).arrange_submobjects(DOWN, buff=MED_SMALL_BUFF)
|
||||
for i in range(self.n_patron_columns)
|
||||
])
|
||||
columns.arrange_submobjects(
|
||||
RIGHT, buff = LARGE_BUFF,
|
||||
aligned_edge = UP,
|
||||
RIGHT, buff=LARGE_BUFF,
|
||||
aligned_edge=UP,
|
||||
)
|
||||
columns.scale_to_fit_width(total_width - 1)
|
||||
columns.next_to(black_rect, DOWN, 3*LARGE_BUFF)
|
||||
columns.next_to(black_rect, DOWN, 3 * LARGE_BUFF)
|
||||
columns.to_edge(RIGHT)
|
||||
|
||||
self.play(
|
||||
columns.next_to, FRAME_Y_RADIUS*DOWN, UP, LARGE_BUFF,
|
||||
columns.to_edge, RIGHT,
|
||||
columns.next_to, FRAME_Y_RADIUS * DOWN, UP, LARGE_BUFF,
|
||||
columns.to_edge, RIGHT,
|
||||
Animation(black_rect),
|
||||
rate_func = None,
|
||||
run_time = self.run_time,
|
||||
rate_func=None,
|
||||
run_time=self.run_time,
|
||||
)
|
||||
|
||||
|
||||
class ExternallyAnimatedScene(Scene):
|
||||
def construct(self):
|
||||
raise Exception("Don't actually run this class.")
|
||||
|
||||
|
||||
class TODOStub(Scene):
|
||||
CONFIG = {
|
||||
"message" : ""
|
||||
"message": ""
|
||||
}
|
||||
def construct(self):
|
||||
self.add(TextMobject("TODO: %s"%self.message))
|
||||
self.wait()
|
||||
|
||||
def construct(self):
|
||||
self.add(TextMobject("TODO: %s" % self.message))
|
||||
self.wait()
|
||||
|
||||
@@ -18,46 +18,49 @@ 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
|
||||
LEFT_EYE_INDEX = 0
|
||||
RIGHT_EYE_INDEX = 1
|
||||
LEFT_PUPIL_INDEX = 2
|
||||
RIGHT_PUPIL_INDEX = 3
|
||||
BODY_INDEX = 4
|
||||
MOUTH_INDEX = 5
|
||||
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],
|
||||
"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):
|
||||
|
||||
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)
|
||||
PI_CREATURE_DIR,
|
||||
"%s_%s.svg" % (self.file_name_prefix, mode)
|
||||
)
|
||||
SVGMobject.__init__(self, file_name = svg_file, **kwargs)
|
||||
SVGMobject.__init__(self, file_name=svg_file, **kwargs)
|
||||
except:
|
||||
warnings.warn("No %s design with mode %s"%(self.file_name_prefix, mode))
|
||||
warnings.warn("No %s design with mode %s" %
|
||||
(self.file_name_prefix, mode))
|
||||
svg_file = os.path.join(
|
||||
FILE_DIR,
|
||||
FILE_DIR,
|
||||
"PiCreatures_plain.svg",
|
||||
)
|
||||
SVGMobject.__init__(self, file_name = svg_file, **kwargs)
|
||||
SVGMobject.__init__(self, file_name=svg_file, **kwargs)
|
||||
|
||||
if self.flip_at_start:
|
||||
self.flip()
|
||||
@@ -82,10 +85,10 @@ class PiCreature(SVGMobject):
|
||||
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)
|
||||
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):
|
||||
@@ -99,8 +102,8 @@ class PiCreature(SVGMobject):
|
||||
|
||||
def change_mode(self, mode):
|
||||
new_self = self.__class__(
|
||||
mode = mode,
|
||||
color = self.color
|
||||
mode=mode,
|
||||
color=self.color
|
||||
)
|
||||
new_self.scale_to_fit_height(self.get_height())
|
||||
if self.is_flipped() ^ new_self.is_flipped():
|
||||
@@ -118,16 +121,16 @@ class PiCreature(SVGMobject):
|
||||
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_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))
|
||||
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...
|
||||
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)
|
||||
@@ -141,7 +144,7 @@ class PiCreature(SVGMobject):
|
||||
self.look(point - self.eyes.get_center())
|
||||
return self
|
||||
|
||||
def change(self, new_mode, look_at_arg = None):
|
||||
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)
|
||||
@@ -150,27 +153,27 @@ class PiCreature(SVGMobject):
|
||||
def get_looking_direction(self):
|
||||
return np.sign(np.round(
|
||||
self.pupils.get_center() - self.eyes.get_center(),
|
||||
decimals = 2
|
||||
decimals=2
|
||||
))
|
||||
|
||||
def is_flipped(self):
|
||||
return self.eyes.submobjects[0].get_center()[0] > \
|
||||
self.eyes.submobjects[1].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]]
|
||||
lambda p: [p[0], eye_bottom_y, p[2]]
|
||||
)
|
||||
return self
|
||||
|
||||
def to_corner(self, vect = None, **kwargs):
|
||||
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)
|
||||
self.to_corner(DOWN + LEFT, **kwargs)
|
||||
return self
|
||||
|
||||
def get_bubble(self, *content, **kwargs):
|
||||
@@ -196,8 +199,8 @@ class PiCreature(SVGMobject):
|
||||
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.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
|
||||
@@ -209,9 +212,10 @@ class PiCreature(SVGMobject):
|
||||
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"]
|
||||
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):
|
||||
@@ -220,69 +224,78 @@ def get_all_pi_creature_modes():
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
class Randolph(PiCreature):
|
||||
pass #Nothing more than an alternative name
|
||||
pass # Nothing more than an alternative name
|
||||
|
||||
|
||||
class Mortimer(PiCreature):
|
||||
CONFIG = {
|
||||
"color" : GREY_BROWN,
|
||||
"flip_at_start" : True,
|
||||
"color": GREY_BROWN,
|
||||
"flip_at_start": True,
|
||||
}
|
||||
|
||||
|
||||
class Mathematician(PiCreature):
|
||||
CONFIG = {
|
||||
"color" : GREY,
|
||||
"color": GREY,
|
||||
}
|
||||
|
||||
|
||||
class BabyPiCreature(PiCreature):
|
||||
CONFIG = {
|
||||
"scale_factor" : 0.5,
|
||||
"eye_scale_factor" : 1.2,
|
||||
"pupil_scale_factor" : 1.3
|
||||
"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)
|
||||
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)
|
||||
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"
|
||||
"file_name_prefix": "TauCreatures"
|
||||
}
|
||||
|
||||
|
||||
class ThreeLeggedPiCreature(PiCreature):
|
||||
CONFIG = {
|
||||
"file_name_prefix" : "ThreeLeggedPiCreatures"
|
||||
"file_name_prefix": "ThreeLeggedPiCreatures"
|
||||
}
|
||||
|
||||
|
||||
class Eyes(VMobject):
|
||||
CONFIG = {
|
||||
"height" : 0.3,
|
||||
"thing_looked_at" : None,
|
||||
"mode" : "plain",
|
||||
"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):
|
||||
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)
|
||||
pi = Randolph(mode=mode)
|
||||
eyes = VGroup(pi.eyes, pi.pupils)
|
||||
pi.scale(self.height/eyes.get_height())
|
||||
pi.scale(self.height / eyes.get_height())
|
||||
if self.submobjects:
|
||||
eyes.move_to(self, DOWN)
|
||||
else:
|
||||
@@ -293,12 +306,12 @@ class Eyes(VMobject):
|
||||
|
||||
def change_mode_anim(self, mode, **kwargs):
|
||||
self.mode = mode
|
||||
return Transform(self, self.get_eyes(mode = mode), **kwargs)
|
||||
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),
|
||||
self, self.get_eyes(thing_to_look_at=point_or_mobject),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@@ -307,10 +320,8 @@ class Eyes(VMobject):
|
||||
bottom_y = self.get_bottom()[1]
|
||||
for submob in target:
|
||||
submob.apply_function(
|
||||
lambda p : [p[0], bottom_y, p[2]]
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -16,30 +16,34 @@ 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)
|
||||
"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,
|
||||
"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,
|
||||
bubble_class=self.bubble_class,
|
||||
**self.bubble_kwargs
|
||||
)
|
||||
Group(bubble, bubble.content).shift_onto_screen()
|
||||
@@ -61,18 +65,21 @@ class PiCreatureBubbleIntroduction(AnimationGroup):
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
class PiCreatureSays(PiCreatureBubbleIntroduction):
|
||||
CONFIG = {
|
||||
"target_mode" : "speaking",
|
||||
"bubble_class" : SpeechBubble,
|
||||
"target_mode": "speaking",
|
||||
"bubble_class": SpeechBubble,
|
||||
}
|
||||
|
||||
|
||||
class RemovePiCreatureBubble(AnimationGroup):
|
||||
CONFIG = {
|
||||
"target_mode" : "plain",
|
||||
"look_at_arg" : None,
|
||||
"remover" : True,
|
||||
"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())
|
||||
@@ -89,7 +96,7 @@ class RemovePiCreatureBubble(AnimationGroup):
|
||||
FadeOut(pi_creature.bubble.content),
|
||||
)
|
||||
|
||||
def clean_up(self, surrounding_scene = None):
|
||||
def clean_up(self, surrounding_scene=None):
|
||||
AnimationGroup.clean_up(self, surrounding_scene)
|
||||
self.pi_creature.bubble = None
|
||||
if surrounding_scene is not None:
|
||||
|
||||
@@ -12,51 +12,52 @@ 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
|
||||
|
||||
#TODO: There should be much more code reuse between Axes, NumberPlane and GraphScene
|
||||
# TODO: There should be much more code reuse between Axes, NumberPlane and GraphScene
|
||||
|
||||
|
||||
class Axes(VGroup):
|
||||
CONFIG = {
|
||||
"propagate_style_to_family" : True,
|
||||
"three_d" : False,
|
||||
"number_line_config" : {
|
||||
"color" : LIGHT_GREY,
|
||||
"include_tip" : True,
|
||||
"propagate_style_to_family": True,
|
||||
"three_d": False,
|
||||
"number_line_config": {
|
||||
"color": LIGHT_GREY,
|
||||
"include_tip": True,
|
||||
},
|
||||
"x_axis_config" : {},
|
||||
"y_axis_config" : {},
|
||||
"z_axis_config" : {},
|
||||
"x_min" : -FRAME_X_RADIUS,
|
||||
"x_max" : FRAME_X_RADIUS,
|
||||
"y_min" : -FRAME_Y_RADIUS,
|
||||
"y_max" : FRAME_Y_RADIUS,
|
||||
"z_min" : -3.5,
|
||||
"z_max" : 3.5,
|
||||
"z_normal" : DOWN,
|
||||
"default_num_graph_points" : 100,
|
||||
"x_axis_config": {},
|
||||
"y_axis_config": {},
|
||||
"z_axis_config": {},
|
||||
"x_min": -FRAME_X_RADIUS,
|
||||
"x_max": FRAME_X_RADIUS,
|
||||
"y_min": -FRAME_Y_RADIUS,
|
||||
"y_max": FRAME_Y_RADIUS,
|
||||
"z_min": -3.5,
|
||||
"z_max": 3.5,
|
||||
"z_normal": DOWN,
|
||||
"default_num_graph_points": 100,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
VGroup.__init__(self, **kwargs)
|
||||
self.x_axis = self.get_axis(self.x_min, self.x_max, self.x_axis_config)
|
||||
self.y_axis = self.get_axis(self.y_min, self.y_max, self.y_axis_config)
|
||||
self.y_axis.rotate(np.pi/2, about_point = ORIGIN)
|
||||
self.y_axis.rotate(np.pi / 2, about_point=ORIGIN)
|
||||
self.add(self.x_axis, self.y_axis)
|
||||
if self.three_d:
|
||||
self.z_axis = self.get_axis(self.z_min, self.z_max, self.z_axis_config)
|
||||
self.z_axis.rotate(-np.pi/2, UP, about_point = ORIGIN)
|
||||
self.z_axis = self.get_axis(
|
||||
self.z_min, self.z_max, self.z_axis_config)
|
||||
self.z_axis.rotate(-np.pi / 2, UP, about_point=ORIGIN)
|
||||
self.z_axis.rotate(
|
||||
angle_of_vector(self.z_normal), OUT,
|
||||
about_point = ORIGIN
|
||||
about_point=ORIGIN
|
||||
)
|
||||
self.add(self.z_axis)
|
||||
|
||||
def get_axis(self, min_val, max_val, extra_config):
|
||||
config = dict(self.number_line_config)
|
||||
config.update(extra_config)
|
||||
return NumberLine(x_min = min_val, x_max = max_val, **config)
|
||||
return NumberLine(x_min=min_val, x_max=max_val, **config)
|
||||
|
||||
def coords_to_point(self, x, y):
|
||||
origin = self.x_axis.number_to_point(0)
|
||||
@@ -66,25 +67,25 @@ class Axes(VGroup):
|
||||
|
||||
def point_to_coords(self, point):
|
||||
return (
|
||||
self.x_axis.point_to_number(point),
|
||||
self.x_axis.point_to_number(point),
|
||||
self.y_axis.point_to_number(point),
|
||||
)
|
||||
|
||||
def get_graph(
|
||||
self, function, num_graph_points = None,
|
||||
x_min = None,
|
||||
x_max = None,
|
||||
self, function, num_graph_points=None,
|
||||
x_min=None,
|
||||
x_max=None,
|
||||
**kwargs
|
||||
):
|
||||
):
|
||||
kwargs["fill_opacity"] = kwargs.get("fill_opacity", 0)
|
||||
kwargs["num_anchor_points"] = \
|
||||
num_graph_points or self.default_num_graph_points
|
||||
x_min = x_min or self.x_min
|
||||
x_max = x_max or self.x_max
|
||||
graph = ParametricFunction(
|
||||
lambda t : self.coords_to_point(t, function(t)),
|
||||
t_min = x_min,
|
||||
t_max = x_max,
|
||||
lambda t: self.coords_to_point(t, function(t)),
|
||||
t_min=x_min,
|
||||
t_max=x_max,
|
||||
**kwargs
|
||||
)
|
||||
graph.underlying_function = function
|
||||
@@ -94,7 +95,7 @@ class Axes(VGroup):
|
||||
if hasattr(graph, "underlying_function"):
|
||||
return self.coords_to_point(x, graph.underlying_function(x))
|
||||
else:
|
||||
#binary search
|
||||
# binary search
|
||||
lh, rh = 0, 1
|
||||
while abs(lh - rh) > 0.001:
|
||||
mh = np.mean([lh, rh])
|
||||
@@ -115,66 +116,69 @@ class Axes(VGroup):
|
||||
return points[1]
|
||||
return self.coords_to_point(x, graph.underlying_function(x))
|
||||
|
||||
|
||||
class ThreeDAxes(Axes):
|
||||
CONFIG = {
|
||||
"x_min" : -5.5,
|
||||
"x_max" : 5.5,
|
||||
"y_min" : -4.5,
|
||||
"y_max" : 4.5,
|
||||
"three_d" : True,
|
||||
"x_min": -5.5,
|
||||
"x_max": 5.5,
|
||||
"y_min": -4.5,
|
||||
"y_max": 4.5,
|
||||
"three_d": True,
|
||||
}
|
||||
|
||||
|
||||
class NumberPlane(VMobject):
|
||||
CONFIG = {
|
||||
"color" : BLUE_D,
|
||||
"secondary_color" : BLUE_E,
|
||||
"axes_color" : WHITE,
|
||||
"secondary_stroke_width" : 1,
|
||||
"color": BLUE_D,
|
||||
"secondary_color": BLUE_E,
|
||||
"axes_color": WHITE,
|
||||
"secondary_stroke_width": 1,
|
||||
# TODO: Allow coordinate center of NumberPlane to not be at (0, 0)
|
||||
"x_radius": None,
|
||||
"y_radius": None,
|
||||
"x_unit_size" : 1,
|
||||
"y_unit_size" : 1,
|
||||
"center_point" : ORIGIN,
|
||||
"x_line_frequency" : 1,
|
||||
"y_line_frequency" : 1,
|
||||
"secondary_line_ratio" : 1,
|
||||
"written_coordinate_height" : 0.2,
|
||||
"propagate_style_to_family" : False,
|
||||
"make_smooth_after_applying_functions" : True,
|
||||
"x_unit_size": 1,
|
||||
"y_unit_size": 1,
|
||||
"center_point": ORIGIN,
|
||||
"x_line_frequency": 1,
|
||||
"y_line_frequency": 1,
|
||||
"secondary_line_ratio": 1,
|
||||
"written_coordinate_height": 0.2,
|
||||
"propagate_style_to_family": False,
|
||||
"make_smooth_after_applying_functions": True,
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
if self.x_radius is None:
|
||||
center_to_edge = (FRAME_X_RADIUS + abs(self.center_point[0]))
|
||||
center_to_edge = (FRAME_X_RADIUS + abs(self.center_point[0]))
|
||||
self.x_radius = center_to_edge / self.x_unit_size
|
||||
if self.y_radius is None:
|
||||
center_to_edge = (FRAME_Y_RADIUS + abs(self.center_point[1]))
|
||||
center_to_edge = (FRAME_Y_RADIUS + abs(self.center_point[1]))
|
||||
self.y_radius = center_to_edge / self.y_unit_size
|
||||
self.axes = VMobject()
|
||||
self.main_lines = VMobject()
|
||||
self.secondary_lines = VMobject()
|
||||
tuples = [
|
||||
(
|
||||
self.x_radius,
|
||||
self.x_line_frequency,
|
||||
self.y_radius*DOWN,
|
||||
self.y_radius*UP,
|
||||
self.x_radius,
|
||||
self.x_line_frequency,
|
||||
self.y_radius * DOWN,
|
||||
self.y_radius * UP,
|
||||
RIGHT
|
||||
),
|
||||
(
|
||||
self.y_radius,
|
||||
self.y_line_frequency,
|
||||
self.x_radius*LEFT,
|
||||
self.x_radius*RIGHT,
|
||||
self.y_radius,
|
||||
self.y_line_frequency,
|
||||
self.x_radius * LEFT,
|
||||
self.x_radius * RIGHT,
|
||||
UP,
|
||||
),
|
||||
]
|
||||
for radius, freq, start, end, unit in tuples:
|
||||
main_range = np.arange(0, radius, freq)
|
||||
step = freq/float(freq + self.secondary_line_ratio)
|
||||
step = freq / float(freq + self.secondary_line_ratio)
|
||||
for v in np.arange(0, radius, step):
|
||||
line1 = Line(start+v*unit, end+v*unit)
|
||||
line2 = Line(start-v*unit, end-v*unit)
|
||||
line1 = Line(start + v * unit, end + v * unit)
|
||||
line2 = Line(start - v * unit, end - v * unit)
|
||||
if v == 0:
|
||||
self.axes.add(line1)
|
||||
elif v in main_range:
|
||||
@@ -185,7 +189,7 @@ class NumberPlane(VMobject):
|
||||
self.stretch(self.x_unit_size, 0)
|
||||
self.stretch(self.y_unit_size, 1)
|
||||
self.shift(self.center_point)
|
||||
#Put x_axis before y_axis
|
||||
# Put x_axis before y_axis
|
||||
y_axis, x_axis = self.axes.split()
|
||||
self.axes = VMobject(x_axis, y_axis)
|
||||
|
||||
@@ -204,39 +208,39 @@ class NumberPlane(VMobject):
|
||||
def coords_to_point(self, x, y):
|
||||
x, y = np.array([x, y])
|
||||
result = self.axes.get_center()
|
||||
result += x*self.get_x_unit_size()*RIGHT
|
||||
result += y*self.get_y_unit_size()*UP
|
||||
result += x * self.get_x_unit_size() * RIGHT
|
||||
result += y * self.get_y_unit_size() * UP
|
||||
return result
|
||||
|
||||
def point_to_coords(self, point):
|
||||
new_point = point - self.axes.get_center()
|
||||
x = new_point[0]/self.get_x_unit_size()
|
||||
y = new_point[1]/self.get_y_unit_size()
|
||||
x = new_point[0] / self.get_x_unit_size()
|
||||
y = new_point[1] / self.get_y_unit_size()
|
||||
return x, y
|
||||
|
||||
# Does not recompute center, unit_sizes for each call; useful for
|
||||
# iterating over large lists of points, but does assume these
|
||||
# iterating over large lists of points, but does assume these
|
||||
# attributes are kept accurate. (Could alternatively have a method
|
||||
# which returns a function dynamically created after a single
|
||||
# which returns a function dynamically created after a single
|
||||
# call to each of get_center(), get_x_unit_size(), etc.)
|
||||
def point_to_coords_cheap(self, point):
|
||||
new_point = point - self.center_point
|
||||
x = new_point[0]/self.x_unit_size
|
||||
y = new_point[1]/self.y_unit_size
|
||||
x = new_point[0] / self.x_unit_size
|
||||
y = new_point[1] / self.y_unit_size
|
||||
return x, y
|
||||
|
||||
def get_x_unit_size(self):
|
||||
return self.axes.get_width() / (2.0*self.x_radius)
|
||||
return self.axes.get_width() / (2.0 * self.x_radius)
|
||||
|
||||
def get_y_unit_size(self):
|
||||
return self.axes.get_height() / (2.0*self.y_radius)
|
||||
return self.axes.get_height() / (2.0 * self.y_radius)
|
||||
|
||||
def get_coordinate_labels(self, x_vals = None, y_vals = None):
|
||||
def get_coordinate_labels(self, x_vals=None, y_vals=None):
|
||||
coordinate_labels = VGroup()
|
||||
if x_vals == None:
|
||||
x_vals = range(-int(self.x_radius), int(self.x_radius)+1)
|
||||
if y_vals == None:
|
||||
y_vals = range(-int(self.y_radius), int(self.y_radius)+1)
|
||||
if x_vals is None:
|
||||
x_vals = range(-int(self.x_radius), int(self.x_radius) + 1)
|
||||
if y_vals is None:
|
||||
y_vals = range(-int(self.y_radius), int(self.y_radius) + 1)
|
||||
for index, vals in enumerate([x_vals, y_vals]):
|
||||
num_pair = [0, 0]
|
||||
for val in vals:
|
||||
@@ -249,7 +253,7 @@ class NumberPlane(VMobject):
|
||||
num.scale_to_fit_height(
|
||||
self.written_coordinate_height
|
||||
)
|
||||
num.next_to(point, DOWN+LEFT, buff = SMALL_BUFF)
|
||||
num.next_to(point, DOWN + LEFT, buff=SMALL_BUFF)
|
||||
coordinate_labels.add(num)
|
||||
self.coordinate_labels = coordinate_labels
|
||||
return coordinate_labels
|
||||
@@ -257,7 +261,7 @@ class NumberPlane(VMobject):
|
||||
def get_axes(self):
|
||||
return self.axes
|
||||
|
||||
def get_axis_labels(self, x_label = "x", y_label = "y"):
|
||||
def get_axis_labels(self, x_label="x", y_label="y"):
|
||||
x_axis, y_axis = self.get_axes().split()
|
||||
quads = [
|
||||
(x_axis, x_label, UP, RIGHT),
|
||||
@@ -273,39 +277,42 @@ class NumberPlane(VMobject):
|
||||
self.axis_labels = labels
|
||||
return labels
|
||||
|
||||
def add_coordinates(self, x_vals = None, y_vals = None):
|
||||
def add_coordinates(self, x_vals=None, y_vals=None):
|
||||
self.add(*self.get_coordinate_labels(x_vals, y_vals))
|
||||
return self
|
||||
|
||||
def get_vector(self, coords, **kwargs):
|
||||
point = coords[0]*RIGHT + coords[1]*UP
|
||||
arrow = Arrow(ORIGIN, coords, **kwargs)
|
||||
point = coords[0] * RIGHT + coords[1] * UP
|
||||
arrow = Arrow(ORIGIN, point, **kwargs)
|
||||
return arrow
|
||||
|
||||
def prepare_for_nonlinear_transform(self, num_inserted_anchor_points = 50):
|
||||
def prepare_for_nonlinear_transform(self, num_inserted_anchor_points=50):
|
||||
for mob in self.family_members_with_points():
|
||||
num_anchors = mob.get_num_anchor_points()
|
||||
if num_inserted_anchor_points > num_anchors:
|
||||
mob.insert_n_anchor_points(num_inserted_anchor_points-num_anchors)
|
||||
mob.insert_n_anchor_points(
|
||||
num_inserted_anchor_points - num_anchors)
|
||||
mob.make_smooth()
|
||||
return self
|
||||
|
||||
|
||||
class ComplexPlane(NumberPlane):
|
||||
CONFIG = {
|
||||
"color" : BLUE,
|
||||
"unit_size" : 1,
|
||||
"line_frequency" : 1,
|
||||
"faded_line_frequency" : 0.5,
|
||||
"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,
|
||||
"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)
|
||||
|
||||
@@ -321,12 +328,11 @@ class ComplexPlane(NumberPlane):
|
||||
# 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 = 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 y in range(-int(self.y_radius), int(self.y_radius) + 1)
|
||||
]
|
||||
for number in numbers:
|
||||
if number == complex(0, 0):
|
||||
@@ -340,7 +346,7 @@ class ComplexPlane(NumberPlane):
|
||||
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)
|
||||
num_mob.next_to(point, DOWN + LEFT, SMALL_BUFF)
|
||||
result.add(num_mob)
|
||||
self.coordinate_labels = result
|
||||
return result
|
||||
@@ -348,4 +354,3 @@ class ComplexPlane(NumberPlane):
|
||||
def add_coordinates(self, *numbers):
|
||||
self.add(*self.get_coordinate_labels(*numbers))
|
||||
return self
|
||||
|
||||
|
||||
@@ -4,32 +4,38 @@ 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,
|
||||
"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,
|
||||
"height": FRAME_HEIGHT,
|
||||
}
|
||||
|
||||
|
||||
class FullScreenFadeRectangle(FullScreenRectangle):
|
||||
CONFIG = {
|
||||
"stroke_width" : 0,
|
||||
"fill_color" : BLACK,
|
||||
"fill_opacity" : 0.7,
|
||||
"stroke_width": 0,
|
||||
"fill_color": BLACK,
|
||||
"fill_opacity": 0.7,
|
||||
}
|
||||
|
||||
|
||||
class PictureInPictureFrame(Rectangle):
|
||||
CONFIG = {
|
||||
"height" : 3,
|
||||
"aspect_ratio" : (16, 9)
|
||||
"height": 3,
|
||||
"aspect_ratio": (16, 9)
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
height = self.height
|
||||
@@ -37,8 +43,8 @@ class PictureInPictureFrame(Rectangle):
|
||||
kwargs.pop("height")
|
||||
Rectangle.__init__(
|
||||
self,
|
||||
width = self.aspect_ratio[0],
|
||||
height = self.aspect_ratio[1],
|
||||
width=self.aspect_ratio[0],
|
||||
height=self.aspect_ratio[1],
|
||||
**kwargs
|
||||
)
|
||||
self.scale_to_fit_height(height)
|
||||
|
||||
@@ -8,57 +8,46 @@ from utils.config_ops import digest_config
|
||||
|
||||
class ParametricFunction(VMobject):
|
||||
CONFIG = {
|
||||
"t_min" : 0,
|
||||
"t_max" : 1,
|
||||
"num_anchor_points" : 100,
|
||||
"t_min": 0,
|
||||
"t_max": 1,
|
||||
"num_anchor_points": 100,
|
||||
}
|
||||
|
||||
def __init__(self, function, **kwargs):
|
||||
self.function = function
|
||||
VMobject.__init__(self, **kwargs)
|
||||
|
||||
def generate_points(self):
|
||||
n_points = 3*self.num_anchor_points - 2
|
||||
n_points = 3 * self.num_anchor_points - 2
|
||||
self.points = np.zeros((n_points, self.dim))
|
||||
self.points[:,0] = np.linspace(
|
||||
self.points[:, 0] = np.linspace(
|
||||
self.t_min, self.t_max, n_points
|
||||
)
|
||||
#VMobject.apply_function takes care of preserving
|
||||
#desirable tangent line properties at anchor points
|
||||
self.apply_function(lambda p : self.function(p[0]))
|
||||
# VMobject.apply_function takes care of preserving
|
||||
# desirable tangent line properties at anchor points
|
||||
self.apply_function(lambda p: self.function(p[0]))
|
||||
|
||||
|
||||
class FunctionGraph(ParametricFunction):
|
||||
CONFIG = {
|
||||
"color" : YELLOW,
|
||||
"x_min" : -FRAME_X_RADIUS,
|
||||
"x_max" : FRAME_X_RADIUS,
|
||||
"color": YELLOW,
|
||||
"x_min": -FRAME_X_RADIUS,
|
||||
"x_max": FRAME_X_RADIUS,
|
||||
}
|
||||
|
||||
def __init__(self, function, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
parametric_function = lambda t : t*RIGHT + function(t)*UP
|
||||
|
||||
def parametric_function(t):
|
||||
return t * RIGHT + function(t) * UP
|
||||
ParametricFunction.__init__(
|
||||
self,
|
||||
self,
|
||||
parametric_function,
|
||||
t_min = self.x_min,
|
||||
t_max = self.x_max,
|
||||
t_min=self.x_min,
|
||||
t_max=self.x_max,
|
||||
**kwargs
|
||||
)
|
||||
self.function = function
|
||||
|
||||
def get_function(self):
|
||||
return self.function
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -17,156 +17,159 @@ 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 = {
|
||||
"radius" : 1.0,
|
||||
"start_angle" : 0,
|
||||
"num_anchors" : 9,
|
||||
"anchors_span_full_range" : True,
|
||||
"radius": 1.0,
|
||||
"start_angle": 0,
|
||||
"num_anchors": 9,
|
||||
"anchors_span_full_range": True,
|
||||
}
|
||||
|
||||
def __init__(self, angle, **kwargs):
|
||||
self.angle = angle
|
||||
VMobject.__init__(self, **kwargs)
|
||||
|
||||
def generate_points(self):
|
||||
anchors = np.array([
|
||||
np.cos(a)*RIGHT+np.sin(a)*UP
|
||||
np.cos(a) * RIGHT + np.sin(a) * UP
|
||||
for a in np.linspace(
|
||||
self.start_angle,
|
||||
self.start_angle + self.angle,
|
||||
self.start_angle,
|
||||
self.start_angle + self.angle,
|
||||
self.num_anchors
|
||||
)
|
||||
])
|
||||
#Figure out which control points will give the
|
||||
#Appropriate tangent lines to the circle
|
||||
d_theta = self.angle/(self.num_anchors-1.0)
|
||||
# Figure out which control points will give the
|
||||
# Appropriate tangent lines to the circle
|
||||
d_theta = self.angle / (self.num_anchors - 1.0)
|
||||
tangent_vectors = np.zeros(anchors.shape)
|
||||
tangent_vectors[:,1] = anchors[:,0]
|
||||
tangent_vectors[:,0] = -anchors[:,1]
|
||||
handles1 = anchors[:-1] + (d_theta/3)*tangent_vectors[:-1]
|
||||
handles2 = anchors[1:] - (d_theta/3)*tangent_vectors[1:]
|
||||
tangent_vectors[:, 1] = anchors[:, 0]
|
||||
tangent_vectors[:, 0] = -anchors[:, 1]
|
||||
handles1 = anchors[:-1] + (d_theta / 3) * tangent_vectors[:-1]
|
||||
handles2 = anchors[1:] - (d_theta / 3) * tangent_vectors[1:]
|
||||
self.set_anchors_and_handles(
|
||||
anchors, handles1, handles2
|
||||
)
|
||||
self.scale(self.radius, about_point = ORIGIN)
|
||||
self.scale(self.radius, about_point=ORIGIN)
|
||||
|
||||
def add_tip(self, tip_length = 0.25, at_start = False, at_end = True):
|
||||
def add_tip(self, tip_length=0.25, at_start=False, at_end=True):
|
||||
# clear out any old tips
|
||||
for submob in self.submobjects:
|
||||
if submob.mark_paths_closed == True: # is a tip
|
||||
if submob.mark_paths_closed:
|
||||
self.remove(submob)
|
||||
|
||||
#TODO, do this a better way
|
||||
# TODO, do this a better way
|
||||
p1 = p2 = p3 = p4 = None
|
||||
start_arrow = end_arrow = None
|
||||
if at_end:
|
||||
p1, p2 = self.points[-3:-1]
|
||||
# self.points[-2:] did overshoot
|
||||
start_arrow = Arrow(
|
||||
p1, 2*p2 - p1,
|
||||
tip_length = tip_length,
|
||||
max_tip_length_to_length_ratio = 2.0
|
||||
p1, 2 * p2 - p1,
|
||||
tip_length=tip_length,
|
||||
max_tip_length_to_length_ratio=2.0
|
||||
)
|
||||
self.add(start_arrow.split()[-1]) # just the tip
|
||||
self.add(start_arrow.split()[-1]) # just the tip
|
||||
|
||||
if at_start:
|
||||
p4, p3 = self.points[1:3]
|
||||
# self.points[:2] did overshoot
|
||||
end_arrow = Arrow(
|
||||
p3, 2*p4 - p3,
|
||||
tip_length = tip_length,
|
||||
max_tip_length_to_length_ratio = 2.0
|
||||
p3, 2 * p4 - p3,
|
||||
tip_length=tip_length,
|
||||
max_tip_length_to_length_ratio=2.0
|
||||
)
|
||||
self.add(end_arrow.split()[-1])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
self.set_color(self.get_color())
|
||||
return self
|
||||
|
||||
|
||||
|
||||
def get_arc_center(self):
|
||||
first_point = self.points[0]
|
||||
radial_unit_vector = np.array([np.cos(self.start_angle),np.sin(self.start_angle),0])
|
||||
radial_unit_vector = np.array(
|
||||
[np.cos(self.start_angle), np.sin(self.start_angle), 0])
|
||||
arc_center = first_point - self.radius * radial_unit_vector
|
||||
return arc_center
|
||||
|
||||
|
||||
def move_arc_center_to(self,point):
|
||||
def move_arc_center_to(self, point):
|
||||
v = point - self.get_arc_center()
|
||||
self.shift(v)
|
||||
return self
|
||||
|
||||
|
||||
def stop_angle(self):
|
||||
return self.start_angle + self.angle
|
||||
|
||||
def set_bound_angles(self,start=0,stop=np.pi):
|
||||
def set_bound_angles(self, start=0, stop=np.pi):
|
||||
self.start_angle = start
|
||||
self.angle = stop - start
|
||||
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class ArcBetweenPoints(Arc):
|
||||
|
||||
def __init__(self, start_point, end_point, angle = TAU/4, **kwargs):
|
||||
def __init__(self, start_point, end_point, angle=TAU / 4, **kwargs):
|
||||
if angle == 0:
|
||||
raise Exception("Arc with zero curve angle: use Line instead.")
|
||||
|
||||
midpoint = 0.5 * (start_point + end_point)
|
||||
distance_vector = end_point - start_point
|
||||
normal_vector = np.array([-distance_vector[1], distance_vector[0],0])
|
||||
normal_vector = np.array([-distance_vector[1], distance_vector[0], 0])
|
||||
distance = np.linalg.norm(normal_vector)
|
||||
normal_vector /= distance
|
||||
if angle < 0:
|
||||
normal_vector *= -1
|
||||
|
||||
radius = distance/2 / np.sin(0.5 * np.abs(angle))
|
||||
l = distance/2 / np.tan(0.5 * np.abs(angle))
|
||||
arc_center = midpoint + l * normal_vector
|
||||
radius = distance / 2 / np.sin(0.5 * np.abs(angle))
|
||||
length = distance / 2 / np.tan(0.5 * np.abs(angle))
|
||||
arc_center = midpoint + length * normal_vector
|
||||
w = start_point - arc_center
|
||||
if w[0] != 0:
|
||||
start_angle = np.arctan2(w[1],w[0])
|
||||
start_angle = np.arctan2(w[1], w[0])
|
||||
else:
|
||||
start_angle = np.pi/2
|
||||
start_angle = np.pi / 2
|
||||
|
||||
Arc.__init__(self, angle,
|
||||
radius = radius,
|
||||
start_angle = start_angle,
|
||||
**kwargs)
|
||||
|
||||
radius=radius,
|
||||
start_angle=start_angle,
|
||||
**kwargs)
|
||||
|
||||
self.move_arc_center_to(arc_center)
|
||||
|
||||
|
||||
class CurvedArrow(ArcBetweenPoints):
|
||||
|
||||
def __init__(self, start_point, end_point, angle = TAU/4, **kwargs):
|
||||
def __init__(self, start_point, end_point, angle=TAU / 4, **kwargs):
|
||||
# I know this is in reverse, but it works
|
||||
if angle >= 0:
|
||||
ArcBetweenPoints.__init__(self, start_point, end_point, angle = angle, **kwargs)
|
||||
self.add_tip(at_start = True, at_end = False)
|
||||
ArcBetweenPoints.__init__(
|
||||
self, start_point, end_point, angle=angle, **kwargs)
|
||||
self.add_tip(at_start=True, at_end=False)
|
||||
else:
|
||||
ArcBetweenPoints.__init__(self, end_point, start_point, angle = -angle, **kwargs)
|
||||
self.add_tip(at_start = False, at_end = True)
|
||||
ArcBetweenPoints.__init__(
|
||||
self, end_point, start_point, angle=-angle, **kwargs)
|
||||
self.add_tip(at_start=False, at_end=True)
|
||||
|
||||
|
||||
class CurvedDoubleArrow(ArcBetweenPoints):
|
||||
|
||||
def __init__(self, start_point, end_point, angle = TAU/4, **kwargs):
|
||||
ArcBetweenPoints.__init__(self, start_point, end_point, angle = angle, **kwargs)
|
||||
self.add_tip(at_start = True, at_end = True)
|
||||
def __init__(self, start_point, end_point, angle=TAU / 4, **kwargs):
|
||||
ArcBetweenPoints.__init__(
|
||||
self, start_point, end_point, angle=angle, **kwargs)
|
||||
self.add_tip(at_start=True, at_end=True)
|
||||
|
||||
|
||||
class Circle(Arc):
|
||||
CONFIG = {
|
||||
"color" : RED,
|
||||
"close_new_points" : True,
|
||||
"anchors_span_full_range" : False
|
||||
"color": RED,
|
||||
"close_new_points": True,
|
||||
"anchors_span_full_range": False
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
Arc.__init__(self, 2*np.pi, **kwargs)
|
||||
|
||||
def surround(self, mobject, dim_to_match = 0, stretch = False, buffer_factor = 1.2):
|
||||
def __init__(self, **kwargs):
|
||||
Arc.__init__(self, 2 * np.pi, **kwargs)
|
||||
|
||||
def surround(self, mobject, dim_to_match=0, stretch=False, buffer_factor=1.2):
|
||||
# Ignores dim_to_match and stretch; result will always be a circle
|
||||
# TODO: Perhaps create an ellipse class to handle singele-dimension stretching
|
||||
|
||||
@@ -174,54 +177,60 @@ class Circle(Arc):
|
||||
# TODO: Figure out and fix
|
||||
self.replace(mobject, dim_to_match, stretch)
|
||||
|
||||
self.scale_to_fit_width(np.sqrt(mobject.get_width()**2 + mobject.get_height()**2))
|
||||
self.scale_to_fit_width(
|
||||
np.sqrt(mobject.get_width()**2 + mobject.get_height()**2))
|
||||
self.scale(buffer_factor)
|
||||
|
||||
|
||||
class Dot(Circle):
|
||||
CONFIG = {
|
||||
"radius" : 0.08,
|
||||
"stroke_width" : 0,
|
||||
"fill_opacity" : 1.0,
|
||||
"color" : WHITE
|
||||
"radius": 0.08,
|
||||
"stroke_width": 0,
|
||||
"fill_opacity": 1.0,
|
||||
"color": WHITE
|
||||
}
|
||||
def __init__(self, point = ORIGIN, **kwargs):
|
||||
|
||||
def __init__(self, point=ORIGIN, **kwargs):
|
||||
Circle.__init__(self, **kwargs)
|
||||
self.shift(point)
|
||||
self.init_colors()
|
||||
|
||||
|
||||
class Ellipse(VMobject):
|
||||
CONFIG = {
|
||||
"width" : 2,
|
||||
"height" : 1
|
||||
"width": 2,
|
||||
"height": 1
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
circle = Circle(radius = 1)
|
||||
circle = Circle(radius=1)
|
||||
circle = circle.stretch_to_fit_width(self.width)
|
||||
circle = circle.stretch_to_fit_height(self.height)
|
||||
self.points = circle.points
|
||||
|
||||
|
||||
class AnnularSector(VMobject):
|
||||
CONFIG = {
|
||||
"inner_radius" : 1,
|
||||
"outer_radius" : 2,
|
||||
"angle" : TAU/4,
|
||||
"start_angle" : 0,
|
||||
"fill_opacity" : 1,
|
||||
"stroke_width" : 0,
|
||||
"color" : WHITE,
|
||||
"mark_paths_closed" : True,
|
||||
"inner_radius": 1,
|
||||
"outer_radius": 2,
|
||||
"angle": TAU / 4,
|
||||
"start_angle": 0,
|
||||
"fill_opacity": 1,
|
||||
"stroke_width": 0,
|
||||
"color": WHITE,
|
||||
"mark_paths_closed": True,
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
arc1 = Arc(
|
||||
angle = self.angle,
|
||||
start_angle = self.start_angle,
|
||||
radius = self.inner_radius,
|
||||
angle=self.angle,
|
||||
start_angle=self.start_angle,
|
||||
radius=self.inner_radius,
|
||||
)
|
||||
arc2 = Arc(
|
||||
angle = -1*self.angle,
|
||||
start_angle = self.start_angle+self.angle,
|
||||
radius = self.outer_radius,
|
||||
angle=-1 * self.angle,
|
||||
start_angle=self.start_angle + self.angle,
|
||||
radius=self.outer_radius,
|
||||
)
|
||||
a1_to_a2_points = np.array([
|
||||
interpolate(arc1.points[-1], arc2.points[0], alpha)
|
||||
@@ -236,24 +245,24 @@ class AnnularSector(VMobject):
|
||||
self.add_control_points(arc2.points[1:])
|
||||
self.add_control_points(a2_to_a1_points[1:])
|
||||
|
||||
|
||||
def get_arc_center(self):
|
||||
first_point = self.points[0]
|
||||
last_point = self.points[-2]
|
||||
v = last_point - first_point
|
||||
radial_unit_vector = v/np.linalg.norm(v)
|
||||
radial_unit_vector = v / np.linalg.norm(v)
|
||||
arc_center = first_point - self.inner_radius * radial_unit_vector
|
||||
return arc_center
|
||||
|
||||
def move_arc_center_to(self,point):
|
||||
def move_arc_center_to(self, point):
|
||||
v = point - self.get_arc_center()
|
||||
self.shift(v)
|
||||
return self
|
||||
|
||||
|
||||
class Sector(AnnularSector):
|
||||
CONFIG = {
|
||||
"outer_radius" : 1,
|
||||
"inner_radius" : 0
|
||||
"outer_radius": 1,
|
||||
"inner_radius": 0
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -261,35 +270,38 @@ class Sector(AnnularSector):
|
||||
return self.outer_radius
|
||||
|
||||
@radius.setter
|
||||
def radius(self,new_radius):
|
||||
def radius(self, new_radius):
|
||||
self.outer_radius = new_radius
|
||||
|
||||
|
||||
class Annulus(Circle):
|
||||
CONFIG = {
|
||||
"inner_radius": 1,
|
||||
"outer_radius": 2,
|
||||
"fill_opacity" : 1,
|
||||
"stroke_width" : 0,
|
||||
"color" : WHITE,
|
||||
"mark_paths_closed" : False,
|
||||
"propagate_style_to_family" : True
|
||||
"fill_opacity": 1,
|
||||
"stroke_width": 0,
|
||||
"color": WHITE,
|
||||
"mark_paths_closed": False,
|
||||
"propagate_style_to_family": True
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
self.points = []
|
||||
self.radius = self.outer_radius
|
||||
outer_circle = Circle(radius = self.outer_radius)
|
||||
outer_circle = Circle(radius=self.outer_radius)
|
||||
inner_circle = Circle(radius=self.inner_radius)
|
||||
inner_circle.flip()
|
||||
self.points = outer_circle.points
|
||||
self.add_subpath(inner_circle.points)
|
||||
|
||||
|
||||
class Line(VMobject):
|
||||
CONFIG = {
|
||||
"buff" : 0,
|
||||
"path_arc" : None, # angle of arc specified here
|
||||
"n_arc_anchors" : 10, #Only used if path_arc is not None
|
||||
"buff": 0,
|
||||
"path_arc": None, # angle of arc specified here
|
||||
"n_arc_anchors": 10, # Only used if path_arc is not None
|
||||
}
|
||||
|
||||
def __init__(self, start, end, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.set_start_and_end(start, end)
|
||||
@@ -306,13 +318,13 @@ class Line(VMobject):
|
||||
])
|
||||
self.account_for_buff()
|
||||
|
||||
def set_path_arc(self,new_value):
|
||||
def set_path_arc(self, new_value):
|
||||
self.path_arc = new_value
|
||||
self.generate_points()
|
||||
|
||||
def account_for_buff(self):
|
||||
length = self.get_arc_length()
|
||||
if length < 2*self.buff or self.buff == 0:
|
||||
if length < 2 * self.buff or self.buff == 0:
|
||||
return
|
||||
buff_proportion = self.buff / length
|
||||
self.pointwise_become_partial(
|
||||
@@ -325,7 +337,7 @@ class Line(VMobject):
|
||||
longer_dim = np.argmax(map(abs, start_to_end))
|
||||
vect[longer_dim] = start_to_end[longer_dim]
|
||||
self.start, self.end = [
|
||||
arg.get_edge_center(unit*vect)
|
||||
arg.get_edge_center(unit * vect)
|
||||
if isinstance(arg, Mobject)
|
||||
else np.array(arg)
|
||||
for arg, unit in zip([start, end], [1, -1])
|
||||
@@ -344,7 +356,7 @@ class Line(VMobject):
|
||||
if self.path_arc:
|
||||
anchors = self.get_anchors()
|
||||
return sum([
|
||||
np.linalg.norm(a2-a1)
|
||||
np.linalg.norm(a2 - a1)
|
||||
for a1, a2 in zip(anchors, anchors[1:])
|
||||
])
|
||||
else:
|
||||
@@ -368,11 +380,11 @@ class Line(VMobject):
|
||||
float(end[i] - start[i])
|
||||
for i in [1, 0]
|
||||
]
|
||||
return np.inf if run == 0 else rise/run
|
||||
return np.inf if run == 0 else rise / run
|
||||
|
||||
def get_angle(self):
|
||||
start, end = self.get_start_and_end()
|
||||
return angle_of_vector(end-start)
|
||||
return angle_of_vector(end - start)
|
||||
|
||||
# def put_start_and_end_on(self, new_start, new_end):
|
||||
# self.set_start_and_end(new_start, new_end)
|
||||
@@ -396,7 +408,7 @@ class Line(VMobject):
|
||||
target_norm = np.linalg.norm(target_vect)
|
||||
if target_norm == 0:
|
||||
epsilon = 0.001
|
||||
self.scale(epsilon/curr_norm)
|
||||
self.scale(epsilon / curr_norm)
|
||||
self.move_to(new_start)
|
||||
return
|
||||
unit_target = target_vect / target_norm
|
||||
@@ -410,7 +422,7 @@ class Line(VMobject):
|
||||
angle_diff = np.arccos(
|
||||
np.clip(np.dot(unit_target, unit_curr), -1, 1)
|
||||
)
|
||||
self.scale(target_norm/curr_norm)
|
||||
self.scale(target_norm / curr_norm)
|
||||
self.rotate(-angle_diff, normal)
|
||||
self.shift(new_start - self.get_start())
|
||||
return self
|
||||
@@ -418,7 +430,7 @@ class Line(VMobject):
|
||||
def insert_n_anchor_points(self, n):
|
||||
if not self.path_arc:
|
||||
n_anchors = self.get_num_anchor_points()
|
||||
new_num_points = 3*(n_anchors + n)-2
|
||||
new_num_points = 3 * (n_anchors + n) - 2
|
||||
self.points = np.array([
|
||||
self.point_from_proportion(alpha)
|
||||
for alpha in np.linspace(0, 1, new_num_points)
|
||||
@@ -426,17 +438,19 @@ class Line(VMobject):
|
||||
else:
|
||||
VMobject.insert_n_anchor_points(self, n)
|
||||
|
||||
|
||||
class DashedLine(Line):
|
||||
CONFIG = {
|
||||
"dashed_segment_length" : 0.05
|
||||
"dashed_segment_length": 0.05
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.init_kwargs = kwargs
|
||||
Line.__init__(self, *args, **kwargs)
|
||||
|
||||
def generate_points(self):
|
||||
length = np.linalg.norm(self.end-self.start)
|
||||
num_interp_points = int(length/self.dashed_segment_length)
|
||||
length = np.linalg.norm(self.end - self.start)
|
||||
num_interp_points = int(length / self.dashed_segment_length)
|
||||
points = [
|
||||
interpolate(self.start, self.end, alpha)
|
||||
for alpha in np.linspace(0, 1, num_interp_points)
|
||||
@@ -462,23 +476,25 @@ class DashedLine(Line):
|
||||
else:
|
||||
return self.end
|
||||
|
||||
|
||||
class Arrow(Line):
|
||||
CONFIG = {
|
||||
"tip_length" : 0.25,
|
||||
"tip_width_to_length_ratio" : 1,
|
||||
"max_tip_length_to_length_ratio" : 0.35,
|
||||
"max_stem_width_to_tip_width_ratio" : 0.3,
|
||||
"buff" : MED_SMALL_BUFF,
|
||||
"propagate_style_to_family" : False,
|
||||
"preserve_tip_size_when_scaling" : True,
|
||||
"normal_vector" : OUT,
|
||||
"use_rectangular_stem" : True,
|
||||
"rectangular_stem_width" : 0.05,
|
||||
"tip_length": 0.25,
|
||||
"tip_width_to_length_ratio": 1,
|
||||
"max_tip_length_to_length_ratio": 0.35,
|
||||
"max_stem_width_to_tip_width_ratio": 0.3,
|
||||
"buff": MED_SMALL_BUFF,
|
||||
"propagate_style_to_family": False,
|
||||
"preserve_tip_size_when_scaling": True,
|
||||
"normal_vector": OUT,
|
||||
"use_rectangular_stem": True,
|
||||
"rectangular_stem_width": 0.05,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
points = map(self.pointify, args)
|
||||
if len(args) == 1:
|
||||
args = (points[0]+UP+LEFT, points[0])
|
||||
args = (points[0] + UP + LEFT, points[0])
|
||||
Line.__init__(self, *args, **kwargs)
|
||||
self.init_tip()
|
||||
if self.use_rectangular_stem and not hasattr(self, "rect"):
|
||||
@@ -488,17 +504,17 @@ class Arrow(Line):
|
||||
def init_tip(self):
|
||||
self.add_tip()
|
||||
|
||||
def add_tip(self, add_at_end = True):
|
||||
def add_tip(self, add_at_end=True):
|
||||
tip = VMobject(
|
||||
close_new_points = True,
|
||||
mark_paths_closed = True,
|
||||
fill_color = self.color,
|
||||
fill_opacity = 1,
|
||||
stroke_color = self.color,
|
||||
stroke_width = 0,
|
||||
close_new_points=True,
|
||||
mark_paths_closed=True,
|
||||
fill_color=self.color,
|
||||
fill_opacity=1,
|
||||
stroke_color=self.color,
|
||||
stroke_width=0,
|
||||
)
|
||||
tip.add_at_end = add_at_end
|
||||
self.set_tip_points(tip, add_at_end, preserve_normal = False)
|
||||
self.set_tip_points(tip, add_at_end, preserve_normal=False)
|
||||
self.add(tip)
|
||||
if not hasattr(self, 'tip'):
|
||||
self.tip = VGroup()
|
||||
@@ -508,17 +524,16 @@ class Arrow(Line):
|
||||
|
||||
def add_rectangular_stem(self):
|
||||
self.rect = Rectangle(
|
||||
stroke_width = 0,
|
||||
fill_color = self.tip.get_fill_color(),
|
||||
fill_opacity = self.tip.get_fill_opacity()
|
||||
stroke_width=0,
|
||||
fill_color=self.tip.get_fill_color(),
|
||||
fill_opacity=self.tip.get_fill_opacity()
|
||||
)
|
||||
self.add_to_back(self.rect)
|
||||
self.set_stroke(width = 0)
|
||||
self.set_stroke(width=0)
|
||||
self.set_rectangular_stem_points()
|
||||
|
||||
def set_rectangular_stem_points(self):
|
||||
start, end = self.get_start_and_end()
|
||||
vect = end - start
|
||||
tip_base_points = self.tip[0].get_anchors()[1:]
|
||||
tip_base = center_of_mass(tip_base_points)
|
||||
tbp1, tbp2 = tip_base_points
|
||||
@@ -528,35 +543,35 @@ class Arrow(Line):
|
||||
perp_vect /= tip_base_width
|
||||
width = min(
|
||||
self.rectangular_stem_width,
|
||||
self.max_stem_width_to_tip_width_ratio*tip_base_width,
|
||||
self.max_stem_width_to_tip_width_ratio * tip_base_width,
|
||||
)
|
||||
if hasattr(self, "second_tip"):
|
||||
start = center_of_mass(
|
||||
self.second_tip.get_anchors()[1:]
|
||||
)
|
||||
self.rect.set_points_as_corners([
|
||||
tip_base + perp_vect*width/2,
|
||||
start + perp_vect*width/2,
|
||||
start - perp_vect*width/2,
|
||||
tip_base - perp_vect*width/2,
|
||||
tip_base + perp_vect * width / 2,
|
||||
start + perp_vect * width / 2,
|
||||
start - perp_vect * width / 2,
|
||||
tip_base - perp_vect * width / 2,
|
||||
])
|
||||
return self
|
||||
|
||||
def set_tip_points(
|
||||
self, tip,
|
||||
add_at_end = True,
|
||||
tip_length = None,
|
||||
preserve_normal = True,
|
||||
):
|
||||
add_at_end=True,
|
||||
tip_length=None,
|
||||
preserve_normal=True,
|
||||
):
|
||||
if tip_length is None:
|
||||
tip_length = self.tip_length
|
||||
if preserve_normal:
|
||||
normal_vector = self.get_normal_vector()
|
||||
else:
|
||||
normal_vector = self.normal_vector
|
||||
line_length = np.linalg.norm(self.points[-1]-self.points[0])
|
||||
line_length = np.linalg.norm(self.points[-1] - self.points[0])
|
||||
tip_length = min(
|
||||
tip_length, self.max_tip_length_to_length_ratio*line_length
|
||||
tip_length, self.max_tip_length_to_length_ratio * line_length
|
||||
)
|
||||
|
||||
indices = (-2, -1) if add_at_end else (1, 0)
|
||||
@@ -569,12 +584,12 @@ class Arrow(Line):
|
||||
for v in vect, perp_vect:
|
||||
if np.linalg.norm(v) == 0:
|
||||
v[0] = 1
|
||||
v *= tip_length/np.linalg.norm(v)
|
||||
v *= tip_length / np.linalg.norm(v)
|
||||
ratio = self.tip_width_to_length_ratio
|
||||
tip.set_points_as_corners([
|
||||
end_point,
|
||||
end_point-vect+perp_vect*ratio/2,
|
||||
end_point-vect-perp_vect*ratio/2,
|
||||
end_point - vect + perp_vect * ratio / 2,
|
||||
end_point - vect - perp_vect * ratio / 2,
|
||||
])
|
||||
|
||||
return self
|
||||
@@ -586,7 +601,7 @@ class Arrow(Line):
|
||||
if norm == 0:
|
||||
return self.normal_vector
|
||||
else:
|
||||
return result/norm
|
||||
return result / norm
|
||||
|
||||
def reset_normal_vector(self):
|
||||
self.normal_vector = self.get_normal_vector()
|
||||
@@ -603,7 +618,7 @@ class Arrow(Line):
|
||||
|
||||
def put_start_and_end_on(self, *args, **kwargs):
|
||||
Line.put_start_and_end_on(self, *args, **kwargs)
|
||||
self.set_tip_points(self.tip[0], preserve_normal = False)
|
||||
self.set_tip_points(self.tip[0], preserve_normal=False)
|
||||
self.set_rectangular_stem_points()
|
||||
return self
|
||||
|
||||
@@ -619,92 +634,106 @@ class Arrow(Line):
|
||||
def copy(self):
|
||||
return self.deepcopy()
|
||||
|
||||
|
||||
class Vector(Arrow):
|
||||
CONFIG = {
|
||||
"color" : YELLOW,
|
||||
"buff" : 0,
|
||||
"color": YELLOW,
|
||||
"buff": 0,
|
||||
}
|
||||
|
||||
def __init__(self, direction, **kwargs):
|
||||
if len(direction) == 2:
|
||||
direction = np.append(np.array(direction), 0)
|
||||
Arrow.__init__(self, ORIGIN, direction, **kwargs)
|
||||
|
||||
|
||||
class DoubleArrow(Arrow):
|
||||
def init_tip(self):
|
||||
self.tip = VGroup()
|
||||
for b in True, False:
|
||||
t = self.add_tip(add_at_end = b)
|
||||
t = self.add_tip(add_at_end=b)
|
||||
t.add_at_end = b
|
||||
self.tip.add(t)
|
||||
self.tip.match_style(self.tip[0])
|
||||
|
||||
|
||||
class CubicBezier(VMobject):
|
||||
def __init__(self, points, **kwargs):
|
||||
VMobject.__init__(self, **kwargs)
|
||||
self.set_points(points)
|
||||
|
||||
|
||||
class Polygon(VMobject):
|
||||
CONFIG = {
|
||||
"color" : GREEN_D,
|
||||
"mark_paths_closed" : True,
|
||||
"close_new_points" : True,
|
||||
"color": GREEN_D,
|
||||
"mark_paths_closed": True,
|
||||
"close_new_points": True,
|
||||
}
|
||||
|
||||
def __init__(self, *vertices, **kwargs):
|
||||
assert len(vertices) > 1
|
||||
digest_locals(self)
|
||||
VMobject.__init__(self, **kwargs)
|
||||
|
||||
def generate_points(self):
|
||||
self.set_anchor_points(self.vertices, mode = "corners")
|
||||
self.set_anchor_points(self.vertices, mode="corners")
|
||||
|
||||
def get_vertices(self):
|
||||
return self.get_anchors_and_handles()[0]
|
||||
|
||||
|
||||
class RegularPolygon(Polygon):
|
||||
CONFIG = {
|
||||
"start_angle" : 0
|
||||
"start_angle": 0
|
||||
}
|
||||
def __init__(self, n = 3, **kwargs):
|
||||
|
||||
def __init__(self, n=3, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
start_vect = rotate_vector(RIGHT, self.start_angle)
|
||||
vertices = compass_directions(n, start_vect)
|
||||
Polygon.__init__(self, *vertices, **kwargs)
|
||||
|
||||
|
||||
class Rectangle(VMobject):
|
||||
CONFIG = {
|
||||
"color" : WHITE,
|
||||
"height" : 2.0,
|
||||
"width" : 4.0,
|
||||
"mark_paths_closed" : True,
|
||||
"close_new_points" : True,
|
||||
"color": WHITE,
|
||||
"height": 2.0,
|
||||
"width": 4.0,
|
||||
"mark_paths_closed": True,
|
||||
"close_new_points": True,
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
y, x = self.height/2., self.width/2.
|
||||
y, x = self.height / 2., self.width / 2.
|
||||
self.set_anchor_points([
|
||||
x*LEFT+y*UP,
|
||||
x*RIGHT+y*UP,
|
||||
x*RIGHT+y*DOWN,
|
||||
x*LEFT+y*DOWN
|
||||
], mode = "corners")
|
||||
x * LEFT + y * UP,
|
||||
x * RIGHT + y * UP,
|
||||
x * RIGHT + y * DOWN,
|
||||
x * LEFT + y * DOWN
|
||||
], mode="corners")
|
||||
|
||||
|
||||
class Square(Rectangle):
|
||||
CONFIG = {
|
||||
"side_length" : 2.0,
|
||||
"side_length": 2.0,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
Rectangle.__init__(
|
||||
self,
|
||||
height = self.side_length,
|
||||
width = self.side_length,
|
||||
self,
|
||||
height=self.side_length,
|
||||
width=self.side_length,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
class Grid(VMobject):
|
||||
CONFIG = {
|
||||
"height" : 6.0,
|
||||
"width" : 6.0,
|
||||
"height": 6.0,
|
||||
"width": 6.0,
|
||||
}
|
||||
|
||||
def __init__(self, rows, columns, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
VMobject.__init__(self, **kwargs)
|
||||
@@ -713,15 +742,13 @@ class Grid(VMobject):
|
||||
x_step = self.width / self.columns
|
||||
y_step = self.height / self.rows
|
||||
|
||||
for x in np.arange(0, self.width+x_step, x_step):
|
||||
for x in np.arange(0, self.width + x_step, x_step):
|
||||
self.add(Line(
|
||||
[x-self.width/2., -self.height/2., 0],
|
||||
[x-self.width/2., self.height/2., 0],
|
||||
[x - self.width / 2., -self.height / 2., 0],
|
||||
[x - self.width / 2., self.height / 2., 0],
|
||||
))
|
||||
for y in np.arange(0, self.height+y_step, y_step):
|
||||
for y in np.arange(0, self.height + y_step, y_step):
|
||||
self.add(Line(
|
||||
[-self.width/2., y-self.height/2., 0],
|
||||
[self.width/2., y-self.height/2., 0]
|
||||
[-self.width / 2., y - self.height / 2., 0],
|
||||
[self.width / 2., y - self.height / 2., 0]
|
||||
))
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import numpy as np
|
||||
import operator as op
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
from colour import Color
|
||||
|
||||
from constants import *
|
||||
@@ -16,28 +15,28 @@ from utils.color import interpolate_color
|
||||
from utils.iterables import list_update
|
||||
from utils.iterables import remove_list_redundancies
|
||||
from utils.paths import straight_path
|
||||
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
|
||||
# TODO: Explain array_attrs
|
||||
|
||||
class Mobject(Container):
|
||||
"""
|
||||
Mathematical Object
|
||||
"""
|
||||
CONFIG = {
|
||||
"color" : WHITE,
|
||||
"stroke_width" : DEFAULT_POINT_THICKNESS,
|
||||
"name" : None,
|
||||
"dim" : 3,
|
||||
"target" : None,
|
||||
"color": WHITE,
|
||||
"stroke_width": DEFAULT_POINT_THICKNESS,
|
||||
"name": None,
|
||||
"dim": 3,
|
||||
"target": None,
|
||||
}
|
||||
|
||||
def __init__(self, *submobjects, **kwargs):
|
||||
Container.__init__(self, *submobjects, **kwargs)
|
||||
if not all(map(lambda m : isinstance(m, Mobject), submobjects)):
|
||||
if not all(map(lambda m: isinstance(m, Mobject), submobjects)):
|
||||
raise Exception("All submobjects must be of type Mobject")
|
||||
self.submobjects = list(submobjects)
|
||||
self.color = Color(self.color)
|
||||
@@ -54,11 +53,11 @@ class Mobject(Container):
|
||||
self.points = np.zeros((0, self.dim))
|
||||
|
||||
def init_colors(self):
|
||||
#For subclasses
|
||||
# For subclasses
|
||||
pass
|
||||
|
||||
def generate_points(self):
|
||||
#Typically implemented in subclass, unless purposefully left blank
|
||||
# Typically implemented in subclass, unless purposefully left blank
|
||||
pass
|
||||
|
||||
def add(self, *mobjects):
|
||||
@@ -87,7 +86,7 @@ class Mobject(Container):
|
||||
in the submobjects list.
|
||||
"""
|
||||
mobject_attrs = filter(
|
||||
lambda x : isinstance(x, Mobject),
|
||||
lambda x: isinstance(x, Mobject),
|
||||
self.__dict__.values()
|
||||
)
|
||||
self.submobjects = list_update(self.submobjects, mobject_attrs)
|
||||
@@ -98,24 +97,24 @@ class Mobject(Container):
|
||||
setattr(self, attr, func(getattr(self, attr)))
|
||||
return self
|
||||
|
||||
def get_image(self, camera = None):
|
||||
def get_image(self, camera=None):
|
||||
if camera is None:
|
||||
from camera.camera import Camera
|
||||
camera = Camera()
|
||||
camera.capture_mobject(self)
|
||||
return camera.get_image()
|
||||
|
||||
def show(self, camera = None):
|
||||
self.get_image(camera = camera).show()
|
||||
def show(self, camera=None):
|
||||
self.get_image(camera=camera).show()
|
||||
|
||||
def save_image(self, name = None):
|
||||
def save_image(self, name=None):
|
||||
self.get_image().save(
|
||||
os.path.join(ANIMATIONS_DIR, (name or str(self)) + ".png")
|
||||
)
|
||||
|
||||
def copy(self):
|
||||
#TODO, either justify reason for shallow copy, or
|
||||
#remove this redundancy everywhere
|
||||
# TODO, either justify reason for shallow copy, or
|
||||
# remove this redundancy everywhere
|
||||
return self.deepcopy()
|
||||
|
||||
copy_mobject = copy.copy(self)
|
||||
@@ -132,8 +131,8 @@ class Mobject(Container):
|
||||
def deepcopy(self):
|
||||
return copy.deepcopy(self)
|
||||
|
||||
def generate_target(self, use_deepcopy = False):
|
||||
self.target = None #Prevent exponential explosion
|
||||
def generate_target(self, use_deepcopy=False):
|
||||
self.target = None # Prevent exponential explosion
|
||||
if use_deepcopy:
|
||||
self.target = self.deepcopy()
|
||||
else:
|
||||
@@ -149,8 +148,8 @@ class Mobject(Container):
|
||||
def shift(self, *vectors):
|
||||
total_vector = reduce(op.add, vectors)
|
||||
for mob in self.family_members_with_points():
|
||||
mob.points = mob.points.astype('float')
|
||||
mob.points += total_vector
|
||||
mob.points = mob.points.astype('float')
|
||||
mob.points += total_vector
|
||||
return self
|
||||
|
||||
def scale(self, scale_factor, **kwargs):
|
||||
@@ -164,61 +163,61 @@ class Mobject(Container):
|
||||
respect to that point.
|
||||
"""
|
||||
self.apply_points_function_about_point(
|
||||
lambda points : scale_factor*points, **kwargs
|
||||
lambda points: scale_factor * points, **kwargs
|
||||
)
|
||||
return self
|
||||
|
||||
def rotate_about_origin(self, angle, axis = OUT, axes = []):
|
||||
return self.rotate(angle, axis, about_point = ORIGIN)
|
||||
def rotate_about_origin(self, angle, axis=OUT, axes=[]):
|
||||
return self.rotate(angle, axis, about_point=ORIGIN)
|
||||
|
||||
def rotate(self, angle, axis = OUT, **kwargs):
|
||||
def rotate(self, angle, axis=OUT, **kwargs):
|
||||
rot_matrix = rotation_matrix(angle, axis)
|
||||
self.apply_points_function_about_point(
|
||||
lambda points : np.dot(points, rot_matrix.T),
|
||||
lambda points: np.dot(points, rot_matrix.T),
|
||||
**kwargs
|
||||
)
|
||||
return self
|
||||
|
||||
def flip(self, axis = UP, **kwargs):
|
||||
return self.rotate(TAU/2, axis, **kwargs)
|
||||
def flip(self, axis=UP, **kwargs):
|
||||
return self.rotate(TAU / 2, axis, **kwargs)
|
||||
|
||||
def stretch(self, factor, dim, **kwargs):
|
||||
def func(points):
|
||||
points[:,dim] *= factor
|
||||
points[:, dim] *= factor
|
||||
return points
|
||||
self.apply_points_function_about_point(func, **kwargs)
|
||||
return self
|
||||
|
||||
def apply_function(self, function, **kwargs):
|
||||
#Default to applying matrix about the origin, not mobjects center
|
||||
# Default to applying matrix about the origin, not mobjects center
|
||||
if len(kwargs) == 0:
|
||||
kwargs["about_point"] = ORIGIN
|
||||
self.apply_points_function_about_point(
|
||||
lambda points : np.apply_along_axis(function, 1, points),
|
||||
lambda points: np.apply_along_axis(function, 1, points),
|
||||
**kwargs
|
||||
)
|
||||
return self
|
||||
|
||||
def apply_matrix(self, matrix, **kwargs):
|
||||
#Default to applying matrix about the origin, not mobjects center
|
||||
# Default to applying matrix about the origin, not mobjects center
|
||||
if len(kwargs) == 0:
|
||||
kwargs["about_point"] = ORIGIN
|
||||
full_matrix = np.identity(self.dim)
|
||||
matrix = np.array(matrix)
|
||||
full_matrix[:matrix.shape[0],:matrix.shape[1]] = matrix
|
||||
full_matrix[:matrix.shape[0], :matrix.shape[1]] = matrix
|
||||
self.apply_points_function_about_point(
|
||||
lambda points : np.dot(points, full_matrix.T),
|
||||
lambda points: np.dot(points, full_matrix.T),
|
||||
**kwargs
|
||||
)
|
||||
return self
|
||||
|
||||
def apply_complex_function(self, function, **kwargs):
|
||||
return self.apply_function(
|
||||
lambda (x, y, z) : complex_to_R3(function(complex(x, y))),
|
||||
lambda (x, y, z): complex_to_R3(function(complex(x, y))),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def wag(self, direction = RIGHT, axis = DOWN, wag_factor = 1.0):
|
||||
def wag(self, direction=RIGHT, axis=DOWN, wag_factor=1.0):
|
||||
for mob in self.family_members_with_points():
|
||||
alphas = np.dot(mob.points, np.transpose(axis))
|
||||
alphas -= min(alphas)
|
||||
@@ -233,7 +232,7 @@ class Mobject(Container):
|
||||
def reverse_points(self):
|
||||
for mob in self.family_members_with_points():
|
||||
mob.apply_over_attr_arrays(
|
||||
lambda arr : np.array(list(reversed(arr)))
|
||||
lambda arr: np.array(list(reversed(arr)))
|
||||
)
|
||||
return self
|
||||
|
||||
@@ -243,18 +242,18 @@ class Mobject(Container):
|
||||
"""
|
||||
def repeat_array(array):
|
||||
return reduce(
|
||||
lambda a1, a2 : np.append(a1, a2, axis = 0),
|
||||
[array]*count
|
||||
lambda a1, a2: np.append(a1, a2, axis=0),
|
||||
[array] * count
|
||||
)
|
||||
for mob in self.family_members_with_points():
|
||||
mob.apply_over_attr_arrays(repeat_array)
|
||||
return self
|
||||
|
||||
#### In place operations ######
|
||||
#Note, much of these are now redundant with default behavior of
|
||||
#above methods
|
||||
# Note, much of these are now redundant with default behavior of
|
||||
# above methods
|
||||
|
||||
def apply_points_function_about_point(self, func, about_point = None, about_edge = ORIGIN):
|
||||
def apply_points_function_about_point(self, func, about_point=None, about_edge=ORIGIN):
|
||||
if about_point is None:
|
||||
about_point = self.get_critical_point(about_edge)
|
||||
for mob in self.family_members_with_points():
|
||||
@@ -263,20 +262,20 @@ class Mobject(Container):
|
||||
mob.points += about_point
|
||||
return self
|
||||
|
||||
def rotate_in_place(self, angle, axis = OUT):
|
||||
def rotate_in_place(self, angle, axis=OUT):
|
||||
# redundant with default behavior of rotate now.
|
||||
return self.rotate(angle, axis = axis)
|
||||
return self.rotate(angle, axis=axis)
|
||||
|
||||
def scale_in_place(self, scale_factor, **kwargs):
|
||||
#Redundant with default behavior of scale now.
|
||||
# Redundant with default behavior of scale now.
|
||||
return self.scale(scale_factor, **kwargs)
|
||||
|
||||
def scale_about_point(self, scale_factor, point):
|
||||
#Redundant with default behavior of scale now.
|
||||
return self.scale(scale_factor, about_point = point)
|
||||
# Redundant with default behavior of scale now.
|
||||
return self.scale(scale_factor, about_point=point)
|
||||
|
||||
def pose_at_angle(self, **kwargs):
|
||||
self.rotate(TAU/14, RIGHT+UP, **kwargs)
|
||||
self.rotate(TAU / 14, RIGHT + UP, **kwargs)
|
||||
return self
|
||||
|
||||
#### Positioning methods ####
|
||||
@@ -285,7 +284,7 @@ class Mobject(Container):
|
||||
self.shift(-self.get_center())
|
||||
return self
|
||||
|
||||
def align_on_border(self, direction, buff = DEFAULT_MOBJECT_TO_EDGE_BUFFER):
|
||||
def align_on_border(self, direction, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER):
|
||||
"""
|
||||
Direction just needs to be a vector pointing towards side or
|
||||
corner in the 2d plane.
|
||||
@@ -297,19 +296,19 @@ class Mobject(Container):
|
||||
self.shift(shift_val)
|
||||
return self
|
||||
|
||||
def to_corner(self, corner = LEFT+DOWN, buff = DEFAULT_MOBJECT_TO_EDGE_BUFFER):
|
||||
def to_corner(self, corner=LEFT + DOWN, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER):
|
||||
return self.align_on_border(corner, buff)
|
||||
|
||||
def to_edge(self, edge = LEFT, buff = DEFAULT_MOBJECT_TO_EDGE_BUFFER):
|
||||
def to_edge(self, edge=LEFT, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER):
|
||||
return self.align_on_border(edge, buff)
|
||||
|
||||
def next_to(self, mobject_or_point,
|
||||
direction = RIGHT,
|
||||
buff = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
|
||||
aligned_edge = ORIGIN,
|
||||
submobject_to_align = None,
|
||||
index_of_submobject_to_align = None,
|
||||
coor_mask = np.array([1,1,1]),
|
||||
direction=RIGHT,
|
||||
buff=DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
|
||||
aligned_edge=ORIGIN,
|
||||
submobject_to_align=None,
|
||||
index_of_submobject_to_align=None,
|
||||
coor_mask=np.array([1, 1, 1]),
|
||||
):
|
||||
if isinstance(mobject_or_point, Mobject):
|
||||
mob = mobject_or_point
|
||||
@@ -329,10 +328,11 @@ class Mobject(Container):
|
||||
else:
|
||||
aligner = self
|
||||
point_to_align = aligner.get_critical_point(aligned_edge - direction)
|
||||
self.shift((target_point - point_to_align + buff*direction)*coor_mask)
|
||||
self.shift((target_point - point_to_align +
|
||||
buff * direction) * coor_mask)
|
||||
return self
|
||||
|
||||
def align_to(self, mobject_or_point, direction = ORIGIN, alignment_vect = UP):
|
||||
def align_to(self, mobject_or_point, direction=ORIGIN, alignment_vect=UP):
|
||||
"""
|
||||
Examples:
|
||||
mob1.align_to(mob2, UP) moves mob1 vertically so that its
|
||||
@@ -349,12 +349,12 @@ class Mobject(Container):
|
||||
target_point = mobject_or_point
|
||||
direction_norm = np.linalg.norm(direction)
|
||||
if direction_norm > 0:
|
||||
alignment_vect = np.array(direction)/direction_norm
|
||||
alignment_vect = np.array(direction) / direction_norm
|
||||
reference_point = self.get_critical_point(direction)
|
||||
else:
|
||||
reference_point = self.get_center()
|
||||
diff = target_point - reference_point
|
||||
self.shift(alignment_vect*np.dot(diff, alignment_vect))
|
||||
self.shift(alignment_vect * np.dot(diff, alignment_vect))
|
||||
return self
|
||||
|
||||
def shift_onto_screen(self, **kwargs):
|
||||
@@ -380,57 +380,57 @@ class Mobject(Container):
|
||||
return False
|
||||
|
||||
def stretch_about_point(self, factor, dim, point):
|
||||
return self.stretch(factor, dim, about_point = point)
|
||||
return self.stretch(factor, dim, about_point=point)
|
||||
|
||||
def stretch_in_place(self, factor, dim):
|
||||
#Now redundant with stretch
|
||||
# Now redundant with stretch
|
||||
return self.stretch(factor, dim)
|
||||
|
||||
def rescale_to_fit(self, length, dim, stretch = False, **kwargs):
|
||||
def rescale_to_fit(self, length, dim, stretch=False, **kwargs):
|
||||
old_length = self.length_over_dim(dim)
|
||||
if old_length == 0:
|
||||
return self
|
||||
if stretch:
|
||||
self.stretch(length/old_length, dim, **kwargs)
|
||||
self.stretch(length / old_length, dim, **kwargs)
|
||||
else:
|
||||
self.scale(length/old_length, **kwargs)
|
||||
self.scale(length / old_length, **kwargs)
|
||||
return self
|
||||
|
||||
def stretch_to_fit_width(self, width, **kwargs):
|
||||
return self.rescale_to_fit(width, 0, stretch = True, **kwargs)
|
||||
return self.rescale_to_fit(width, 0, stretch=True, **kwargs)
|
||||
|
||||
def stretch_to_fit_height(self, height, **kwargs):
|
||||
return self.rescale_to_fit(height, 1, stretch = True, **kwargs)
|
||||
return self.rescale_to_fit(height, 1, stretch=True, **kwargs)
|
||||
|
||||
def stretch_to_fit_depth(self, depth, **kwargs):
|
||||
return self.rescale_to_fit(depth, 1, stretch = True, **kwargs)
|
||||
return self.rescale_to_fit(depth, 1, stretch=True, **kwargs)
|
||||
|
||||
def scale_to_fit_width(self, width, **kwargs):
|
||||
return self.rescale_to_fit(width, 0, stretch = False, **kwargs)
|
||||
return self.rescale_to_fit(width, 0, stretch=False, **kwargs)
|
||||
|
||||
def scale_to_fit_height(self, height, **kwargs):
|
||||
return self.rescale_to_fit(height, 1, stretch = False, **kwargs)
|
||||
return self.rescale_to_fit(height, 1, stretch=False, **kwargs)
|
||||
|
||||
def scale_to_fit_depth(self, depth, **kwargs):
|
||||
return self.rescale_to_fit(depth, 2, stretch = False, **kwargs)
|
||||
return self.rescale_to_fit(depth, 2, stretch=False, **kwargs)
|
||||
|
||||
def space_out_submobjects(self, factor = 1.5, **kwargs):
|
||||
def space_out_submobjects(self, factor=1.5, **kwargs):
|
||||
self.scale(factor, **kwargs)
|
||||
for submob in self.submobjects:
|
||||
submob.scale(1./factor)
|
||||
submob.scale(1. / factor)
|
||||
return self
|
||||
|
||||
def move_to(self, point_or_mobject, aligned_edge = ORIGIN,
|
||||
coor_mask = np.array([1,1,1])):
|
||||
def move_to(self, point_or_mobject, aligned_edge=ORIGIN,
|
||||
coor_mask=np.array([1, 1, 1])):
|
||||
if isinstance(point_or_mobject, Mobject):
|
||||
target = point_or_mobject.get_critical_point(aligned_edge)
|
||||
else:
|
||||
target = point_or_mobject
|
||||
point_to_align = self.get_critical_point(aligned_edge)
|
||||
self.shift((target - point_to_align)*coor_mask)
|
||||
self.shift((target - point_to_align) * coor_mask)
|
||||
return self
|
||||
|
||||
def replace(self, mobject, dim_to_match = 0, stretch = False):
|
||||
def replace(self, mobject, dim_to_match=0, stretch=False):
|
||||
if not mobject.get_num_points() and not mobject.submobjects:
|
||||
raise Warning("Attempting to replace mobject with no points")
|
||||
return self
|
||||
@@ -441,12 +441,12 @@ class Mobject(Container):
|
||||
self.rescale_to_fit(
|
||||
mobject.length_over_dim(dim_to_match),
|
||||
dim_to_match,
|
||||
stretch = False
|
||||
stretch=False
|
||||
)
|
||||
self.shift(mobject.get_center() - self.get_center())
|
||||
return self
|
||||
|
||||
def surround(self, mobject, dim_to_match = 0, stretch = False, buffer_factor = 1.2):
|
||||
def surround(self, mobject, dim_to_match=0, stretch=False, buffer_factor=1.2):
|
||||
self.replace(mobject, dim_to_match, stretch)
|
||||
self.scale_in_place(buffer_factor)
|
||||
|
||||
@@ -455,15 +455,15 @@ class Mobject(Container):
|
||||
if np.all(curr_vect == 0):
|
||||
raise Exception("Cannot position endpoints of closed loop")
|
||||
target_vect = end - start
|
||||
self.scale(np.linalg.norm(target_vect)/np.linalg.norm(curr_vect))
|
||||
self.scale(np.linalg.norm(target_vect) / np.linalg.norm(curr_vect))
|
||||
self.rotate(
|
||||
angle_of_vector(target_vect) - \
|
||||
angle_of_vector(target_vect) -
|
||||
angle_of_vector(curr_vect)
|
||||
)
|
||||
self.shift(start-self.points[0])
|
||||
self.shift(start - self.points[0])
|
||||
return self
|
||||
|
||||
## Match other mobvject properties
|
||||
# Match other mobvject properties
|
||||
|
||||
def match_color(self, mobject):
|
||||
return self.set_color(mobject.get_color())
|
||||
@@ -483,9 +483,9 @@ class Mobject(Container):
|
||||
def match_depth(self, mobject, **kwargs):
|
||||
return self.match_dim(mobject, 2, **kwargs)
|
||||
|
||||
## Color functions
|
||||
# Color functions
|
||||
|
||||
def set_color(self, color = YELLOW_C, family = True):
|
||||
def set_color(self, color=YELLOW_C, family=True):
|
||||
"""
|
||||
Condition is function which takes in one arguments, (x, y, z).
|
||||
Here it just recurses to submobjects, but in subclasses this
|
||||
@@ -494,7 +494,7 @@ class Mobject(Container):
|
||||
"""
|
||||
if family:
|
||||
for submob in self.submobjects:
|
||||
submob.set_color(color, family = family)
|
||||
submob.set_color(color, family=family)
|
||||
self.color = color
|
||||
return self
|
||||
|
||||
@@ -502,8 +502,9 @@ class Mobject(Container):
|
||||
self.set_submobject_colors_by_gradient(*colors)
|
||||
return self
|
||||
|
||||
def set_colors_by_radial_gradient(self, center = None, radius = 1, inner_color = WHITE, outer_color = BLACK):
|
||||
self.set_submobject_colors_by_radial_gradient(center, radius, inner_color, outer_color)
|
||||
def set_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK):
|
||||
self.set_submobject_colors_by_radial_gradient(
|
||||
center, radius, inner_color, outer_color)
|
||||
return self
|
||||
|
||||
def set_submobject_colors_by_gradient(self, *colors):
|
||||
@@ -516,19 +517,19 @@ class Mobject(Container):
|
||||
new_colors = color_gradient(colors, len(mobs))
|
||||
|
||||
for mob, color in zip(mobs, new_colors):
|
||||
mob.set_color(color, family = False)
|
||||
mob.set_color(color, family=False)
|
||||
return self
|
||||
|
||||
def set_submobject_colors_by_radial_gradient(self, center = None, radius = 1, inner_color = WHITE, outer_color = BLACK):
|
||||
def set_submobject_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK):
|
||||
mobs = self.family_members_with_points()
|
||||
if center == None:
|
||||
center = self.get_center()
|
||||
|
||||
for mob in self.family_members_with_points():
|
||||
t = np.linalg.norm(mob.get_center() - center)/radius
|
||||
t = min(t,1)
|
||||
t = np.linalg.norm(mob.get_center() - center) / radius
|
||||
t = min(t, 1)
|
||||
mob_color = interpolate_color(inner_color, outer_color, t)
|
||||
mob.set_color(mob_color, family = False)
|
||||
mob.set_color(mob_color, family=False)
|
||||
|
||||
return self
|
||||
|
||||
@@ -538,7 +539,7 @@ class Mobject(Container):
|
||||
|
||||
# Some objects (e.g., VMobjects) have special fading
|
||||
# behavior. We let every object handle its individual
|
||||
# fading via fade_no_recurse (notionally a purely internal method),
|
||||
# fading via fade_no_recurse (notionally a purely internal method),
|
||||
# and then have fade() itself call this recursively on each submobject
|
||||
#
|
||||
# Similarly for fade_to_no_recurse and fade_to, the underlying functions
|
||||
@@ -549,7 +550,7 @@ class Mobject(Container):
|
||||
start = color_to_rgb(self.get_color())
|
||||
end = color_to_rgb(color)
|
||||
new_rgb = interpolate(start, end, alpha)
|
||||
self.set_color(Color(rgb = new_rgb), family = False)
|
||||
self.set_color(Color(rgb=new_rgb), family=False)
|
||||
return self
|
||||
|
||||
def fade_to(self, color, alpha):
|
||||
@@ -561,7 +562,7 @@ class Mobject(Container):
|
||||
self.fade_to_no_recurse(BLACK, darkness)
|
||||
return self
|
||||
|
||||
def fade(self, darkness = 0.5):
|
||||
def fade(self, darkness=0.5):
|
||||
for submob in self.submobject_family():
|
||||
submob.fade_no_recurse(darkness)
|
||||
return self
|
||||
@@ -570,9 +571,9 @@ class Mobject(Container):
|
||||
return self.color
|
||||
##
|
||||
|
||||
def save_state(self, use_deepcopy = False):
|
||||
def save_state(self, use_deepcopy=False):
|
||||
if hasattr(self, "saved_state"):
|
||||
#Prevent exponential growth of data
|
||||
# Prevent exponential growth of data
|
||||
self.saved_state = None
|
||||
if use_deepcopy:
|
||||
self.saved_state = self.deepcopy()
|
||||
@@ -634,7 +635,7 @@ class Mobject(Container):
|
||||
max_point = self.reduce_across_dimension(np.max, np.max, dim)
|
||||
|
||||
if direction[dim] == 0:
|
||||
result[dim] = (max_point+min_point)/2
|
||||
result[dim] = (max_point + min_point) / 2
|
||||
elif direction[dim] < 0:
|
||||
result[dim] = min_point
|
||||
else:
|
||||
@@ -694,8 +695,7 @@ class Mobject(Container):
|
||||
def point_from_proportion(self, alpha):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
## Family matters
|
||||
# Family matters
|
||||
|
||||
def __getitem__(self, value):
|
||||
self_list = self.split()
|
||||
@@ -724,22 +724,22 @@ class Mobject(Container):
|
||||
|
||||
def family_members_with_points(self):
|
||||
return filter(
|
||||
lambda m : m.get_num_points() > 0,
|
||||
lambda m: m.get_num_points() > 0,
|
||||
self.submobject_family()
|
||||
)
|
||||
|
||||
def arrange_submobjects(self, direction = RIGHT, center = True, **kwargs):
|
||||
def arrange_submobjects(self, direction=RIGHT, center=True, **kwargs):
|
||||
for m1, m2 in zip(self.submobjects, self.submobjects[1:]):
|
||||
m2.next_to(m1, direction, **kwargs)
|
||||
if center:
|
||||
self.center()
|
||||
return self
|
||||
|
||||
def arrange_submobjects_in_grid(self, n_rows = None, n_cols = None, **kwargs):
|
||||
def arrange_submobjects_in_grid(self, n_rows=None, n_cols=None, **kwargs):
|
||||
submobs = self.submobjects
|
||||
if n_rows is None and n_cols is None:
|
||||
n_cols = int(np.sqrt(len(submobs)))
|
||||
|
||||
|
||||
if n_rows is not None:
|
||||
v1 = RIGHT
|
||||
v2 = DOWN
|
||||
@@ -749,35 +749,35 @@ class Mobject(Container):
|
||||
v2 = RIGHT
|
||||
n = len(submobs) / n_cols
|
||||
Group(*[
|
||||
Group(*submobs[i:i+n]).arrange_submobjects(v1, **kwargs)
|
||||
Group(*submobs[i:i + n]).arrange_submobjects(v1, **kwargs)
|
||||
for i in range(0, len(submobs), n)
|
||||
]).arrange_submobjects(v2, **kwargs)
|
||||
return self
|
||||
|
||||
def sort_submobjects(self, point_to_num_func = lambda p : p[0]):
|
||||
def sort_submobjects(self, point_to_num_func=lambda p: p[0]):
|
||||
self.submobjects.sort(
|
||||
lambda *mobs : cmp(*[
|
||||
lambda *mobs: cmp(*[
|
||||
point_to_num_func(mob.get_center())
|
||||
for mob in mobs
|
||||
])
|
||||
)
|
||||
return self
|
||||
|
||||
def print_submobject_family(self, n_tabs = 0):
|
||||
def print_submobject_family(self, n_tabs=0):
|
||||
"""For debugging purposes"""
|
||||
print "\t"*n_tabs, self, id(self)
|
||||
print "\t" * n_tabs, self, id(self)
|
||||
for submob in self.submobjects:
|
||||
submob.print_mobject_family(n_tabs + 1)
|
||||
|
||||
## Alignment
|
||||
# Alignment
|
||||
def align_data(self, mobject):
|
||||
self.align_submobjects(mobject)
|
||||
self.align_points(mobject)
|
||||
#Recurse
|
||||
# Recurse
|
||||
for m1, m2 in zip(self.submobjects, mobject.submobjects):
|
||||
m1.align_data(m2)
|
||||
|
||||
def get_point_mobject(self, center = None):
|
||||
def get_point_mobject(self, center=None):
|
||||
"""
|
||||
The simplest mobject to be transformed to or from self.
|
||||
Should by a point of the appropriate type
|
||||
@@ -797,8 +797,8 @@ class Mobject(Container):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
def align_submobjects(self, mobject):
|
||||
#If one is empty, and the other is not,
|
||||
#push it into its submobject list
|
||||
# If one is empty, and the other is not,
|
||||
# push it into its submobject list
|
||||
self_has_points, mob_has_points = [
|
||||
mob.get_num_points() > 0
|
||||
for mob in self, mobject
|
||||
@@ -809,7 +809,7 @@ class Mobject(Container):
|
||||
self.null_point_align(mobject)
|
||||
self_count = len(self.submobjects)
|
||||
mob_count = len(mobject.submobjects)
|
||||
diff = self_count-mob_count
|
||||
diff = self_count - mob_count
|
||||
if diff < 0:
|
||||
self.add_n_more_submobjects(-diff)
|
||||
elif diff > 0:
|
||||
@@ -840,7 +840,7 @@ class Mobject(Container):
|
||||
self.add(self.copy())
|
||||
n -= 1
|
||||
curr += 1
|
||||
indices = curr*np.arange(curr+n)/(curr+n)
|
||||
indices = curr * np.arange(curr + n) / (curr + n)
|
||||
new_submobjects = []
|
||||
for index in indices:
|
||||
submob = self.submobjects[index]
|
||||
@@ -854,7 +854,7 @@ class Mobject(Container):
|
||||
return submob.copy()
|
||||
|
||||
def interpolate(self, mobject1, mobject2,
|
||||
alpha, path_func = straight_path):
|
||||
alpha, path_func=straight_path):
|
||||
"""
|
||||
Turns self into an interpolation between mobject1
|
||||
and mobject2.
|
||||
@@ -865,7 +865,7 @@ class Mobject(Container):
|
||||
self.interpolate_color(mobject1, mobject2, alpha)
|
||||
|
||||
def interpolate_color(self, mobject1, mobject2, alpha):
|
||||
pass #To implement in subclass
|
||||
pass # To implement in subclass
|
||||
|
||||
def become_partial(self, mobject, a, b):
|
||||
"""
|
||||
@@ -874,15 +874,16 @@ class Mobject(Container):
|
||||
Inputs 0 <= a < b <= 1 determine what portion
|
||||
of mobject to become.
|
||||
"""
|
||||
pass #To implement in subclasses
|
||||
pass # To implement in subclasses
|
||||
|
||||
#TODO, color?
|
||||
# TODO, color?
|
||||
|
||||
def pointwise_become_partial(self, mobject, a, b):
|
||||
pass #To implement in subclass
|
||||
pass # To implement in subclass
|
||||
|
||||
|
||||
class Group(Mobject):
|
||||
#Alternate name to improve readibility in cases where
|
||||
#the mobject is used primarily for its submobject housing
|
||||
#functionality.
|
||||
# Alternate name to improve readibility in cases where
|
||||
# the mobject is used primarily for its submobject housing
|
||||
# functionality.
|
||||
pass
|
||||
|
||||
@@ -10,49 +10,50 @@ 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,
|
||||
"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)
|
||||
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.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))
|
||||
rounding_value = int(-np.log10(0.1 * self.tick_frequency))
|
||||
rounded_numbers_with_elongated_ticks = np.round(
|
||||
self.numbers_with_elongated_ticks,
|
||||
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
|
||||
tick_size_used = self.longer_tick_multiple * self.tick_size
|
||||
else:
|
||||
tick_size_used = self.tick_size
|
||||
self.add_tick(x, tick_size_used)
|
||||
@@ -60,13 +61,14 @@ class NumberLine(VMobject):
|
||||
self.stretch(self.unit_size, 0)
|
||||
self.shift(-self.number_to_point(self.number_at_center))
|
||||
|
||||
def add_tick(self, x, size = None):
|
||||
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)
|
||||
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
|
||||
@@ -77,12 +79,12 @@ class NumberLine(VMobject):
|
||||
def get_tick_numbers(self):
|
||||
epsilon = 0.001
|
||||
return np.arange(
|
||||
self.leftmost_tick, self.x_max+epsilon,
|
||||
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)
|
||||
alpha = float(number - self.x_min) / (self.x_max - self.x_min)
|
||||
return interpolate(
|
||||
self.main_line.get_start(),
|
||||
self.main_line.get_end(),
|
||||
@@ -91,22 +93,23 @@ class NumberLine(VMobject):
|
||||
|
||||
def point_to_number(self, point):
|
||||
left_point, right_point = self.main_line.get_start_and_end()
|
||||
full_vect = right_point-left_point
|
||||
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 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)
|
||||
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)
|
||||
return np.arange(int(self.leftmost_tick), int(self.x_max) + 1)
|
||||
|
||||
def get_number_mobjects(self, *numbers, **kwargs):
|
||||
#TODO, handle decimals
|
||||
# TODO, handle decimals
|
||||
if len(numbers) == 0:
|
||||
numbers = self.default_numbers_to_display()
|
||||
if "force_integers" in kwargs and kwargs["force_integers"]:
|
||||
@@ -135,20 +138,20 @@ class NumberLine(VMobject):
|
||||
|
||||
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)
|
||||
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,
|
||||
"x_min": 0,
|
||||
"x_max": 1,
|
||||
"unit_size": 6,
|
||||
"tick_frequency": 0.1,
|
||||
"numbers_with_elongated_ticks": [0, 1],
|
||||
"number_at_center": 0.5,
|
||||
}
|
||||
|
||||
|
||||
@@ -7,29 +7,31 @@ 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,
|
||||
"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
|
||||
# Build number string
|
||||
if isinstance(number, complex):
|
||||
num_string = '%.*f%s%.*fi'%(
|
||||
ndp, number.real,
|
||||
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.)
|
||||
num_string = '%.*f' % (ndp, number)
|
||||
negative_zero_string = "-%.*f" % (ndp, 0.)
|
||||
if num_string == negative_zero_string:
|
||||
num_string = num_string[1:]
|
||||
self.add(*[
|
||||
@@ -37,32 +39,31 @@ class DecimalNumber(VMobject):
|
||||
for char in num_string
|
||||
])
|
||||
|
||||
#Add non-numerical bits
|
||||
# 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
|
||||
buff=self.digit_to_digit_buff
|
||||
)
|
||||
|
||||
if self.unit != None:
|
||||
if self.unit is not None:
|
||||
self.unit_sign = TexMobject(self.unit)
|
||||
self.add(self.unit_sign)
|
||||
|
||||
self.arrange_submobjects(
|
||||
buff = self.digit_to_digit_buff,
|
||||
aligned_edge = DOWN
|
||||
buff=self.digit_to_digit_buff,
|
||||
aligned_edge=DOWN
|
||||
)
|
||||
|
||||
#Handle alignment of parts that should be aligned
|
||||
#to the bottom
|
||||
# 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 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)
|
||||
#
|
||||
@@ -70,8 +71,8 @@ class DecimalNumber(VMobject):
|
||||
self.add_background_rectangle()
|
||||
|
||||
def add_background_rectangle(self):
|
||||
#TODO, is this the best way to handle
|
||||
#background rectangles?
|
||||
# TODO, is this the best way to handle
|
||||
# background rectangles?
|
||||
self.background_rectangle = BackgroundRectangle(self)
|
||||
self.submobjects = [
|
||||
self.background_rectangle,
|
||||
@@ -79,7 +80,8 @@ class DecimalNumber(VMobject):
|
||||
]
|
||||
return self
|
||||
|
||||
|
||||
class Integer(DecimalNumber):
|
||||
CONFIG = {
|
||||
"num_decimal_points" : 0,
|
||||
"num_decimal_points": 0,
|
||||
}
|
||||
|
||||
@@ -15,29 +15,31 @@ 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,
|
||||
"height": 3,
|
||||
"width": 3,
|
||||
"fill_color": DARK_GREY,
|
||||
"fill_opacity": 1,
|
||||
"stroke_width": 0.5,
|
||||
"stroke_color": LIGHT_GREY,
|
||||
##
|
||||
"default_label_scale_val" : 1,
|
||||
"default_label_scale_val": 1,
|
||||
}
|
||||
def add_title(self, title = "Sample space", buff = MED_SMALL_BUFF):
|
||||
##TODO, should this really exist in SampleSpaceScene
|
||||
|
||||
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)
|
||||
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)
|
||||
@@ -54,7 +56,7 @@ class SampleSpace(Rectangle):
|
||||
for factor, color in zip(p_list, colors):
|
||||
part = SampleSpace()
|
||||
part.set_fill(color, 1)
|
||||
part.replace(self, stretch = True)
|
||||
part.replace(self, stretch=True)
|
||||
part.stretch(factor, dim)
|
||||
part.move_to(last_point, -vect)
|
||||
last_point = part.get_edge_center(vect)
|
||||
@@ -62,17 +64,17 @@ class SampleSpace(Rectangle):
|
||||
return parts
|
||||
|
||||
def get_horizontal_division(
|
||||
self, p_list,
|
||||
colors = [GREEN_E, BLUE_E],
|
||||
vect = DOWN
|
||||
):
|
||||
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
|
||||
):
|
||||
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):
|
||||
@@ -85,16 +87,16 @@ class SampleSpace(Rectangle):
|
||||
|
||||
def get_subdivision_braces_and_labels(
|
||||
self, parts, labels, direction,
|
||||
buff = SMALL_BUFF,
|
||||
min_num_quads = 1
|
||||
):
|
||||
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
|
||||
part, direction,
|
||||
min_num_quads=min_num_quads,
|
||||
buff=buff
|
||||
)
|
||||
if isinstance(label, Mobject):
|
||||
label_mob = label
|
||||
@@ -108,13 +110,13 @@ class SampleSpace(Rectangle):
|
||||
parts.braces = braces
|
||||
parts.labels = label_mobs
|
||||
parts.label_kwargs = {
|
||||
"labels" : label_mobs.copy(),
|
||||
"direction" : direction,
|
||||
"buff" : buff,
|
||||
"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):
|
||||
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)
|
||||
@@ -145,21 +147,23 @@ class SampleSpace(Rectangle):
|
||||
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,
|
||||
"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:
|
||||
@@ -170,15 +174,15 @@ class BarChart(VGroup):
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
tick.move_to(y * UP)
|
||||
ticks.add(tick)
|
||||
y_axis.add(ticks)
|
||||
|
||||
@@ -195,18 +199,17 @@ class BarChart(VGroup):
|
||||
self.y_axis_labels = labels
|
||||
self.add(labels)
|
||||
|
||||
|
||||
def add_bars(self, values):
|
||||
buff = float(self.width) / (2*len(values) + 1)
|
||||
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,
|
||||
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)
|
||||
bar.move_to((2 * i + 1) * buff * RIGHT, DOWN + LEFT)
|
||||
bars.add(bar)
|
||||
bars.set_color_by_gradient(*self.bar_colors)
|
||||
|
||||
@@ -225,27 +228,9 @@ class BarChart(VGroup):
|
||||
for bar, value in zip(self.bars, values):
|
||||
bar_bottom = bar.get_bottom()
|
||||
bar.stretch_to_fit_height(
|
||||
(value/self.max_value)*self.height
|
||||
(value / self.max_value) * self.height
|
||||
)
|
||||
bar.move_to(bar_bottom, DOWN)
|
||||
|
||||
def copy(self):
|
||||
return self.deepcopy()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,31 +8,35 @@ 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,
|
||||
"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
|
||||
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
|
||||
"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)
|
||||
self.set_fill(opacity=b * self.original_fill_opacity)
|
||||
return self
|
||||
|
||||
def set_color(self):
|
||||
@@ -42,15 +46,17 @@ class BackgroundRectangle(SurroundingRectangle):
|
||||
def get_fill_color(self):
|
||||
return Color(self.color)
|
||||
|
||||
|
||||
class Cross(VGroup):
|
||||
CONFIG = {
|
||||
"stroke_color" : RED,
|
||||
"stroke_width" : 6,
|
||||
"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)
|
||||
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)
|
||||
|
||||
@@ -12,35 +12,37 @@ 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,
|
||||
"buff": 0.2,
|
||||
"width_multiplier": 2,
|
||||
"max_num_quads": 15,
|
||||
"min_num_quads": 0,
|
||||
}
|
||||
def __init__(self, mobject, direction = DOWN, **kwargs):
|
||||
|
||||
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]
|
||||
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
|
||||
# Adding int(target_width) qquads gives approximately the right width
|
||||
num_quads = np.clip(
|
||||
int(self.width_multiplier*target_width),
|
||||
int(self.width_multiplier * target_width),
|
||||
self.min_num_quads, self.max_num_quads
|
||||
)
|
||||
tex_string = "\\underbrace{%s}"%(num_quads*"\\qquad")
|
||||
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.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)
|
||||
self.shift(left - self.get_corner(UP + LEFT) + self.buff * DOWN)
|
||||
for mob in mobject, self:
|
||||
mob.rotate(angle, about_point = ORIGIN)
|
||||
mob.rotate(angle, about_point=ORIGIN)
|
||||
|
||||
def put_at_tip(self, mob, use_next_to = True, **kwargs):
|
||||
def put_at_tip(self, mob, use_next_to=True, **kwargs):
|
||||
if use_next_to:
|
||||
mob.next_to(
|
||||
self.get_tip(),
|
||||
@@ -50,8 +52,8 @@ class Brace(TexMobject):
|
||||
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)
|
||||
shift_distance = mob.get_width() / 2.0 + buff
|
||||
mob.shift(self.get_direction() * shift_distance)
|
||||
return self
|
||||
|
||||
def get_text(self, *text, **kwargs):
|
||||
@@ -72,32 +74,38 @@ class Brace(TexMobject):
|
||||
|
||||
def get_direction(self):
|
||||
vect = self.get_tip() - self.get_center()
|
||||
return vect/np.linalg.norm(vect)
|
||||
return vect / np.linalg.norm(vect)
|
||||
|
||||
|
||||
class BraceLabel(VMobject):
|
||||
CONFIG = {
|
||||
"label_constructor" : TexMobject,
|
||||
"label_scale" : 1,
|
||||
"label_constructor": TexMobject,
|
||||
"label_scale": 1,
|
||||
}
|
||||
def __init__(self, obj, text, brace_direction = DOWN, **kwargs):
|
||||
|
||||
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)
|
||||
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)
|
||||
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):
|
||||
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)
|
||||
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
|
||||
@@ -105,7 +113,8 @@ class BraceLabel(VMobject):
|
||||
|
||||
def change_label(self, *text, **kwargs):
|
||||
self.label = self.label_constructor(*text, **kwargs)
|
||||
if self.label_scale != 1: self.label.scale(self.label_scale)
|
||||
if self.label_scale != 1:
|
||||
self.label.scale(self.label_scale)
|
||||
|
||||
self.brace.put_at_tip(self.label)
|
||||
self.submobjects[1] = self.label
|
||||
@@ -124,7 +133,8 @@ class BraceLabel(VMobject):
|
||||
|
||||
return copy_mobject
|
||||
|
||||
|
||||
class BraceText(BraceLabel):
|
||||
CONFIG = {
|
||||
"label_constructor" : TextMobject
|
||||
"label_constructor": TextMobject
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,28 +14,31 @@ 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("-",",-")
|
||||
num_string = num_string.replace("e,-","e-")
|
||||
num_string = num_string.replace("-", ",-")
|
||||
num_string = num_string.replace("e,-", "e-")
|
||||
return [
|
||||
float(s)
|
||||
for s in re.split("[ ,]", num_string)
|
||||
if s != ""
|
||||
]
|
||||
|
||||
|
||||
class SVGMobject(VMobject):
|
||||
CONFIG = {
|
||||
"should_center" : True,
|
||||
"height" : 2,
|
||||
"width" : None,
|
||||
#Must be filled in in a subclass, or when called
|
||||
"file_name" : None,
|
||||
"unpack_groups" : True, # if False, creates a hierarchy of VGroups
|
||||
"stroke_width" : 0,
|
||||
"fill_opacity" : 1,
|
||||
"should_center": True,
|
||||
"height": 2,
|
||||
"width": None,
|
||||
# Must be filled in in a subclass, or when called
|
||||
"file_name": None,
|
||||
"unpack_groups": True, # if False, creates a hierarchy of VGroups
|
||||
"stroke_width": 0,
|
||||
"fill_opacity": 1,
|
||||
# "fill_color" : LIGHT_GREY,
|
||||
"propagate_style_to_family" : True,
|
||||
"propagate_style_to_family": True,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
self.ensure_valid_file()
|
||||
@@ -54,15 +57,18 @@ class SVGMobject(VMobject):
|
||||
if os.path.exists(path):
|
||||
self.file_path = path
|
||||
return
|
||||
raise IOError("No file matching %s in image directory"%self.file_name)
|
||||
raise IOError("No file matching %s in image directory" %
|
||||
self.file_name)
|
||||
|
||||
def generate_points(self):
|
||||
doc = minidom.parse(self.file_path)
|
||||
self.ref_to_element = {}
|
||||
for svg in doc.getElementsByTagName("svg"):
|
||||
mobjects = self.get_mobjects_from(svg)
|
||||
if self.unpack_groups: self.add(*mobjects)
|
||||
else: self.add(*mobjects[0].submobjects)
|
||||
if self.unpack_groups:
|
||||
self.add(*mobjects)
|
||||
else:
|
||||
self.add(*mobjects[0].submobjects)
|
||||
doc.unlink()
|
||||
|
||||
def get_mobjects_from(self, element):
|
||||
@@ -72,7 +78,7 @@ class SVGMobject(VMobject):
|
||||
if element.tagName == 'defs':
|
||||
self.update_ref_to_element(element)
|
||||
elif element.tagName == 'style':
|
||||
pass #TODO, handle style
|
||||
pass # TODO, handle style
|
||||
elif element.tagName in ['g', 'svg']:
|
||||
result += it.chain(*[
|
||||
self.get_mobjects_from(child)
|
||||
@@ -93,9 +99,9 @@ class SVGMobject(VMobject):
|
||||
elif element.tagName in ['polygon', 'polyline']:
|
||||
result.append(self.polygon_to_mobject(element))
|
||||
else:
|
||||
pass ##TODO
|
||||
pass # TODO
|
||||
# warnings.warn("Unknown element type: " + element.tagName)
|
||||
result = filter(lambda m : m is not None, result)
|
||||
result = filter(lambda m: m is not None, result)
|
||||
self.handle_transforms(element, VMobject(*result))
|
||||
if len(result) > 1 and not self.unpack_groups:
|
||||
result = [VGroup(*result)]
|
||||
@@ -111,17 +117,17 @@ class SVGMobject(VMobject):
|
||||
return VMobjectFromSVGPathstring(path_string)
|
||||
|
||||
def use_to_mobjects(self, use_element):
|
||||
#Remove initial "#" character
|
||||
# Remove initial "#" character
|
||||
ref = use_element.getAttribute("xlink:href")[1:]
|
||||
if ref not in self.ref_to_element:
|
||||
warnings.warn("%s not recognized"%ref)
|
||||
warnings.warn("%s not recognized" % ref)
|
||||
return VMobject()
|
||||
return self.get_mobjects_from(
|
||||
self.ref_to_element[ref]
|
||||
)
|
||||
|
||||
def polygon_to_mobject(self, polygon_element):
|
||||
#TODO, This seems hacky...
|
||||
# TODO, This seems hacky...
|
||||
path_string = polygon_element.getAttribute("points")
|
||||
for digit in string.digits:
|
||||
path_string = path_string.replace(" " + digit, " L" + digit)
|
||||
@@ -137,7 +143,7 @@ class SVGMobject(VMobject):
|
||||
else 0.0
|
||||
for key in "cx", "cy", "r"
|
||||
]
|
||||
return Circle(radius = r).shift(x*RIGHT+y*DOWN)
|
||||
return Circle(radius=r).shift(x * RIGHT + y * DOWN)
|
||||
|
||||
def ellipse_to_mobject(self, circle_element):
|
||||
x, y, rx, ry = [
|
||||
@@ -146,74 +152,77 @@ class SVGMobject(VMobject):
|
||||
else 0.0
|
||||
for key in "cx", "cy", "rx", "ry"
|
||||
]
|
||||
return Circle().scale(rx*RIGHT + ry*UP).shift(x*RIGHT+y*DOWN)
|
||||
return Circle().scale(rx * RIGHT + ry * UP).shift(x * RIGHT + y * DOWN)
|
||||
|
||||
def rect_to_mobject(self, rect_element):
|
||||
if rect_element.hasAttribute("fill"):
|
||||
if Color(str(rect_element.getAttribute("fill"))) == Color(WHITE):
|
||||
return
|
||||
mob = Rectangle(
|
||||
width = float(rect_element.getAttribute("width")),
|
||||
height = float(rect_element.getAttribute("height")),
|
||||
stroke_width = 0,
|
||||
fill_color = WHITE,
|
||||
fill_opacity = 1.0
|
||||
width=float(rect_element.getAttribute("width")),
|
||||
height=float(rect_element.getAttribute("height")),
|
||||
stroke_width=0,
|
||||
fill_color=WHITE,
|
||||
fill_opacity=1.0
|
||||
)
|
||||
mob.shift(mob.get_center()-mob.get_corner(UP+LEFT))
|
||||
mob.shift(mob.get_center() - mob.get_corner(UP + LEFT))
|
||||
return mob
|
||||
|
||||
def handle_transforms(self, element, mobject):
|
||||
x, y = 0, 0
|
||||
try:
|
||||
x = float(element.getAttribute('x'))
|
||||
#Flip y
|
||||
# Flip y
|
||||
y = -float(element.getAttribute('y'))
|
||||
mobject.shift(x*RIGHT+y*UP)
|
||||
mobject.shift(x * RIGHT + y * UP)
|
||||
except:
|
||||
pass
|
||||
|
||||
transform = element.getAttribute('transform')
|
||||
|
||||
try: # transform matrix
|
||||
try: # transform matrix
|
||||
prefix = "matrix("
|
||||
suffix = ")"
|
||||
if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception()
|
||||
if not transform.startswith(prefix) or not transform.endswith(suffix):
|
||||
raise Exception()
|
||||
transform = transform[len(prefix):-len(suffix)]
|
||||
transform = string_to_numbers(transform)
|
||||
transform = np.array(transform).reshape([3,2])
|
||||
transform = np.array(transform).reshape([3, 2])
|
||||
x = transform[2][0]
|
||||
y = -transform[2][1]
|
||||
matrix = np.identity(self.dim)
|
||||
matrix[:2,:2] = transform[:2,:]
|
||||
matrix[:2, :2] = transform[:2, :]
|
||||
matrix[1] *= -1
|
||||
matrix[:,1] *= -1
|
||||
matrix[:, 1] *= -1
|
||||
|
||||
for mob in mobject.family_members_with_points():
|
||||
mob.points = np.dot(mob.points, matrix)
|
||||
mobject.shift(x*RIGHT+y*UP)
|
||||
mobject.shift(x * RIGHT + y * UP)
|
||||
except:
|
||||
pass
|
||||
|
||||
try: # transform scale
|
||||
try: # transform scale
|
||||
prefix = "scale("
|
||||
suffix = ")"
|
||||
if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception()
|
||||
if not transform.startswith(prefix) or not transform.endswith(suffix):
|
||||
raise Exception()
|
||||
transform = transform[len(prefix):-len(suffix)]
|
||||
scale_x, scale_y = string_to_numbers(transform)
|
||||
mobject.scale(np.array([scale_x, scale_y, 1]))
|
||||
except:
|
||||
pass
|
||||
|
||||
try: # transform translate
|
||||
try: # transform translate
|
||||
prefix = "translate("
|
||||
suffix = ")"
|
||||
if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception()
|
||||
if not transform.startswith(prefix) or not transform.endswith(suffix):
|
||||
raise Exception()
|
||||
transform = transform[len(prefix):-len(suffix)]
|
||||
x, y = string_to_numbers(transform)
|
||||
mobject.shift(x*RIGHT + y*DOWN)
|
||||
mobject.shift(x * RIGHT + y * DOWN)
|
||||
except:
|
||||
pass
|
||||
#TODO, ...
|
||||
# TODO, ...
|
||||
|
||||
def update_ref_to_element(self, defs):
|
||||
new_refs = dict([
|
||||
@@ -231,6 +240,7 @@ 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)
|
||||
@@ -238,42 +248,42 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||
|
||||
def get_path_commands(self):
|
||||
result = [
|
||||
"M", #moveto
|
||||
"L", #lineto
|
||||
"H", #horizontal lineto
|
||||
"V", #vertical lineto
|
||||
"C", #curveto
|
||||
"S", #smooth curveto
|
||||
"Q", #quadratic Bezier curve
|
||||
"T", #smooth quadratic Bezier curveto
|
||||
"A", #elliptical Arc
|
||||
"Z", #closepath
|
||||
"M", # moveto
|
||||
"L", # lineto
|
||||
"H", # horizontal lineto
|
||||
"V", # vertical lineto
|
||||
"C", # curveto
|
||||
"S", # smooth curveto
|
||||
"Q", # quadratic Bezier curve
|
||||
"T", # smooth quadratic Bezier curveto
|
||||
"A", # elliptical Arc
|
||||
"Z", # closepath
|
||||
]
|
||||
result += map(lambda s : s.lower(), result)
|
||||
result += map(lambda s: s.lower(), result)
|
||||
return result
|
||||
|
||||
def generate_points(self):
|
||||
pattern = "[%s]"%("".join(self.get_path_commands()))
|
||||
pattern = "[%s]" % ("".join(self.get_path_commands()))
|
||||
pairs = zip(
|
||||
re.findall(pattern, self.path_string),
|
||||
re.split(pattern, self.path_string)[1:]
|
||||
)
|
||||
#Which mobject should new points be added to
|
||||
# Which mobject should new points be added to
|
||||
self.growing_path = self
|
||||
for command, coord_string in pairs:
|
||||
self.handle_command(command, coord_string)
|
||||
#people treat y-coordinate differently
|
||||
self.rotate(np.pi, RIGHT, about_point = ORIGIN)
|
||||
# people treat y-coordinate differently
|
||||
self.rotate(np.pi, RIGHT, about_point=ORIGIN)
|
||||
|
||||
def handle_command(self, command, coord_string):
|
||||
isLower = command.islower()
|
||||
command = command.upper()
|
||||
#new_points are the points that will be added to the curr_points
|
||||
#list. This variable may get modified in the conditionals below.
|
||||
# new_points are the points that will be added to the curr_points
|
||||
# list. This variable may get modified in the conditionals below.
|
||||
points = self.growing_path.points
|
||||
new_points = self.string_to_points(coord_string)
|
||||
|
||||
if command == "M": #moveto
|
||||
if command == "M": # moveto
|
||||
if isLower and len(points) > 0:
|
||||
new_points[0] += points[-1]
|
||||
if len(points) > 0:
|
||||
@@ -281,7 +291,8 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||
else:
|
||||
self.growing_path.start_at(new_points[0])
|
||||
|
||||
if len(new_points) <= 1: return
|
||||
if len(new_points) <= 1:
|
||||
return
|
||||
|
||||
points = self.growing_path.points
|
||||
new_points = new_points[1:]
|
||||
@@ -290,47 +301,47 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||
if isLower and len(points) > 0:
|
||||
new_points += points[-1]
|
||||
|
||||
if command in ["L", "H", "V"]: #lineto
|
||||
if command in ["L", "H", "V"]: # lineto
|
||||
if command == "H":
|
||||
new_points[0,1] = points[-1,1]
|
||||
new_points[0, 1] = points[-1, 1]
|
||||
elif command == "V":
|
||||
if isLower:
|
||||
new_points[0,0] -= points[-1,0]
|
||||
new_points[0,0] += points[-1,1]
|
||||
new_points[0,1] = new_points[0,0]
|
||||
new_points[0,0] = points[-1,0]
|
||||
new_points = new_points.repeat(3, axis = 0)
|
||||
elif command == "C": #curveto
|
||||
pass #Yay! No action required
|
||||
elif command in ["S", "T"]: #smooth curveto
|
||||
handle1 = points[-1]+(points[-1]-points[-2])
|
||||
new_points = np.append([handle1], new_points, axis = 0)
|
||||
if command in ["Q", "T"]: #quadratic Bezier curve
|
||||
#TODO, this is a suboptimal approximation
|
||||
new_points = np.append([new_points[0]], new_points, axis = 0)
|
||||
elif command == "A": #elliptical Arc
|
||||
new_points[0, 0] -= points[-1, 0]
|
||||
new_points[0, 0] += points[-1, 1]
|
||||
new_points[0, 1] = new_points[0, 0]
|
||||
new_points[0, 0] = points[-1, 0]
|
||||
new_points = new_points.repeat(3, axis=0)
|
||||
elif command == "C": # curveto
|
||||
pass # Yay! No action required
|
||||
elif command in ["S", "T"]: # smooth curveto
|
||||
handle1 = points[-1] + (points[-1] - points[-2])
|
||||
new_points = np.append([handle1], new_points, axis=0)
|
||||
if command in ["Q", "T"]: # quadratic Bezier curve
|
||||
# TODO, this is a suboptimal approximation
|
||||
new_points = np.append([new_points[0]], new_points, axis=0)
|
||||
elif command == "A": # elliptical Arc
|
||||
raise Exception("Not implemented")
|
||||
elif command == "Z": #closepath
|
||||
elif command == "Z": # closepath
|
||||
if not is_closed(points):
|
||||
#Both handles and new anchor are the start
|
||||
# Both handles and new anchor are the start
|
||||
new_points = points[[0, 0, 0]]
|
||||
# self.mark_paths_closed = True
|
||||
|
||||
#Handle situations where there's multiple relative control points
|
||||
# Handle situations where there's multiple relative control points
|
||||
if isLower and len(new_points) > 3:
|
||||
for i in range(3, len(new_points), 3):
|
||||
new_points[i:i+3] -= points[-1]
|
||||
new_points[i:i+3] += new_points[i-1]
|
||||
new_points[i:i + 3] -= points[-1]
|
||||
new_points[i:i + 3] += new_points[i - 1]
|
||||
|
||||
self.growing_path.add_control_points(new_points)
|
||||
|
||||
def string_to_points(self, coord_string):
|
||||
numbers = string_to_numbers(coord_string)
|
||||
if len(numbers)%2 == 1:
|
||||
if len(numbers) % 2 == 1:
|
||||
numbers.append(0)
|
||||
num_points = len(numbers)/2
|
||||
num_points = len(numbers) / 2
|
||||
result = np.zeros((num_points, self.dim))
|
||||
result[:,:2] = np.array(numbers).reshape((num_points, 2))
|
||||
result[:, :2] = np.array(numbers).reshape((num_points, 2))
|
||||
return result
|
||||
|
||||
def get_original_path_string(self):
|
||||
|
||||
@@ -8,49 +8,50 @@ from mobject.types.vectorized_mobject import VGroup
|
||||
from mobject.types.vectorized_mobject import VMobject
|
||||
from mobject.types.vectorized_mobject import VectorizedPoint
|
||||
|
||||
import collections
|
||||
import operator as op
|
||||
import sys
|
||||
|
||||
TEX_MOB_SCALE_FACTOR = 0.05
|
||||
|
||||
|
||||
class TexSymbol(VMobjectFromSVGPathstring):
|
||||
def pointwise_become_partial(self, mobject, a, b):
|
||||
#TODO, this assumes a = 0
|
||||
# TODO, this assumes a = 0
|
||||
if b < 0.5:
|
||||
b = 2*b
|
||||
b = 2 * b
|
||||
added_width = 1
|
||||
opacity = 0
|
||||
else:
|
||||
added_width = 2 - 2*b
|
||||
opacity = 2*b - 1
|
||||
added_width = 2 - 2 * b
|
||||
opacity = 2 * b - 1
|
||||
b = 1
|
||||
VMobjectFromSVGPathstring.pointwise_become_partial(
|
||||
self, mobject, 0, b
|
||||
)
|
||||
self.set_stroke(width = added_width + mobject.get_stroke_width())
|
||||
self.set_fill(opacity = opacity)
|
||||
self.set_stroke(width=added_width + mobject.get_stroke_width())
|
||||
self.set_fill(opacity=opacity)
|
||||
|
||||
|
||||
class TexMobject(SVGMobject):
|
||||
CONFIG = {
|
||||
"template_tex_file" : TEMPLATE_TEX_FILE,
|
||||
"stroke_width" : 0,
|
||||
"fill_opacity" : 1.0,
|
||||
"fill_color" : WHITE,
|
||||
"should_center" : True,
|
||||
"arg_separator" : " ",
|
||||
"height" : None,
|
||||
"organize_left_to_right" : False,
|
||||
"propagate_style_to_family" : True,
|
||||
"alignment" : "",
|
||||
"template_tex_file": TEMPLATE_TEX_FILE,
|
||||
"stroke_width": 0,
|
||||
"fill_opacity": 1.0,
|
||||
"fill_color": WHITE,
|
||||
"should_center": True,
|
||||
"arg_separator": " ",
|
||||
"height": None,
|
||||
"organize_left_to_right": False,
|
||||
"propagate_style_to_family": True,
|
||||
"alignment": "",
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
|
||||
if "color" in kwargs.keys() and not "fill_color" in kwargs.keys():
|
||||
|
||||
if "color" in kwargs.keys() and "fill_color" not in kwargs.keys():
|
||||
self.fill_color = kwargs["color"]
|
||||
|
||||
##TODO, Eventually remove this
|
||||
# TODO, Eventually remove this
|
||||
if len(args) == 1 and isinstance(args[0], list):
|
||||
self.args = args[0]
|
||||
##
|
||||
@@ -60,14 +61,14 @@ class TexMobject(SVGMobject):
|
||||
self.tex_string,
|
||||
self.template_tex_file
|
||||
)
|
||||
SVGMobject.__init__(self, file_name = file_name, **kwargs)
|
||||
SVGMobject.__init__(self, file_name=file_name, **kwargs)
|
||||
self.scale(TEX_MOB_SCALE_FACTOR)
|
||||
if self.organize_left_to_right:
|
||||
self.organize_submobjects_left_to_right()
|
||||
|
||||
def path_string_to_mobject(self, path_string):
|
||||
#Overwrite superclass default to use
|
||||
#specialized path_string mobject
|
||||
# Overwrite superclass default to use
|
||||
# specialized path_string mobject
|
||||
return TexSymbol(path_string)
|
||||
|
||||
def generate_points(self):
|
||||
@@ -86,7 +87,7 @@ class TexMobject(SVGMobject):
|
||||
def modify_special_strings(self, tex):
|
||||
tex = self.remove_stray_braces(tex)
|
||||
if tex in ["\\over", "\\overline"]:
|
||||
#fraction line needs something to be over
|
||||
# fraction line needs something to be over
|
||||
tex += "\\,"
|
||||
if tex == "\\sqrt":
|
||||
tex += "{\\quad}"
|
||||
@@ -132,7 +133,7 @@ class TexMobject(SVGMobject):
|
||||
self.expression_parts = list(self.args)
|
||||
for expr in self.args:
|
||||
sub_tex_mob = TexMobject(expr, **self.CONFIG)
|
||||
sub_tex_mob.tex_string = expr ##Want it unmodified
|
||||
sub_tex_mob.tex_string = expr # Want it unmodified
|
||||
num_submobs = len(sub_tex_mob.submobjects)
|
||||
new_index = curr_index + num_submobs
|
||||
if num_submobs == 0:
|
||||
@@ -150,7 +151,7 @@ class TexMobject(SVGMobject):
|
||||
self.submobjects = new_submobjects
|
||||
return self
|
||||
|
||||
def get_parts_by_tex(self, tex, substring = True, case_sensitive = True):
|
||||
def get_parts_by_tex(self, tex, substring=True, case_sensitive=True):
|
||||
def test(tex1, tex2):
|
||||
if not case_sensitive:
|
||||
tex1 = tex1.lower()
|
||||
@@ -161,13 +162,13 @@ class TexMobject(SVGMobject):
|
||||
return tex1 == tex2
|
||||
|
||||
tex_submobjects = filter(
|
||||
lambda m : isinstance(m, TexMobject),
|
||||
lambda m: isinstance(m, TexMobject),
|
||||
self.submobject_family()
|
||||
)
|
||||
if hasattr(self, "expression_parts"):
|
||||
tex_submobjects.remove(self)
|
||||
return VGroup(*filter(
|
||||
lambda m : test(tex, m.get_tex_string()),
|
||||
lambda m: test(tex, m.get_tex_string()),
|
||||
tex_submobjects
|
||||
))
|
||||
|
||||
@@ -204,7 +205,7 @@ class TexMobject(SVGMobject):
|
||||
return self.index_of_part(part)
|
||||
|
||||
def organize_submobjects_left_to_right(self):
|
||||
self.sort_submobjects(lambda p : p[0])
|
||||
self.sort_submobjects(lambda p: p[0])
|
||||
return self
|
||||
|
||||
def sort_submobjects_alphabetically(self):
|
||||
@@ -215,30 +216,33 @@ class TexMobject(SVGMobject):
|
||||
self.submobjects.sort(alphabetical_cmp)
|
||||
return self
|
||||
|
||||
def add_background_rectangle(self, color = BLACK, opacity = 0.75, **kwargs):
|
||||
def add_background_rectangle(self, color=BLACK, opacity=0.75, **kwargs):
|
||||
self.background_rectangle = BackgroundRectangle(
|
||||
self, color = color,
|
||||
fill_opacity = opacity,
|
||||
self, color=color,
|
||||
fill_opacity=opacity,
|
||||
**kwargs
|
||||
)
|
||||
letters = VMobject(*self.submobjects)
|
||||
self.submobjects = [self.background_rectangle, letters]
|
||||
return self
|
||||
|
||||
|
||||
class TextMobject(TexMobject):
|
||||
CONFIG = {
|
||||
"template_tex_file" : TEMPLATE_TEXT_FILE,
|
||||
"alignment" : "\\centering",
|
||||
"template_tex_file": TEMPLATE_TEXT_FILE,
|
||||
"alignment": "\\centering",
|
||||
}
|
||||
|
||||
|
||||
class BulletedList(TextMobject):
|
||||
CONFIG = {
|
||||
"buff" : MED_LARGE_BUFF,
|
||||
"dot_scale_factor" : 2,
|
||||
#Have to include because of handle_multiple_args implementation
|
||||
"template_tex_file" : TEMPLATE_TEXT_FILE,
|
||||
"alignment" : "",
|
||||
"buff": MED_LARGE_BUFF,
|
||||
"dot_scale_factor": 2,
|
||||
# Have to include because of handle_multiple_args implementation
|
||||
"template_tex_file": TEMPLATE_TEXT_FILE,
|
||||
"alignment": "",
|
||||
}
|
||||
|
||||
def __init__(self, *items, **kwargs):
|
||||
line_separated_items = [s + "\\\\" for s in items]
|
||||
TextMobject.__init__(self, *line_separated_items, **kwargs)
|
||||
@@ -247,12 +251,12 @@ class BulletedList(TextMobject):
|
||||
dot.next_to(part[0], LEFT, SMALL_BUFF)
|
||||
part.add_to_back(dot)
|
||||
self.arrange_submobjects(
|
||||
DOWN,
|
||||
aligned_edge = LEFT,
|
||||
buff = self.buff
|
||||
DOWN,
|
||||
aligned_edge=LEFT,
|
||||
buff=self.buff
|
||||
)
|
||||
|
||||
def fade_all_but(self, index_or_string, opacity = 0.5):
|
||||
def fade_all_but(self, index_or_string, opacity=0.5):
|
||||
arg = index_or_string
|
||||
if isinstance(arg, str):
|
||||
part = self.get_part_by_tex(arg)
|
||||
@@ -262,15 +266,17 @@ class BulletedList(TextMobject):
|
||||
raise Exception("Expected int or string, got {0}".format(arg))
|
||||
for other_part in self.submobjects:
|
||||
if other_part is part:
|
||||
other_part.set_fill(opacity = 1)
|
||||
other_part.set_fill(opacity=1)
|
||||
else:
|
||||
other_part.set_fill(opacity = opacity)
|
||||
other_part.set_fill(opacity=opacity)
|
||||
|
||||
##########
|
||||
|
||||
|
||||
def tex_hash(expression, template_tex_file):
|
||||
return str(hash(expression + template_tex_file))
|
||||
|
||||
|
||||
def tex_to_svg_file(expression, template_tex_file):
|
||||
image_dir = os.path.join(
|
||||
TEX_IMAGE_DIR,
|
||||
@@ -282,13 +288,14 @@ def tex_to_svg_file(expression, template_tex_file):
|
||||
dvi_file = tex_to_dvi(tex_file)
|
||||
return dvi_to_svg(dvi_file)
|
||||
|
||||
|
||||
def generate_tex_file(expression, template_tex_file):
|
||||
result = os.path.join(
|
||||
TEX_DIR,
|
||||
tex_hash(expression, template_tex_file)
|
||||
) + ".tex"
|
||||
if not os.path.exists(result):
|
||||
print("Writing \"%s\" to %s"%(
|
||||
print("Writing \"%s\" to %s" % (
|
||||
"".join(expression), result
|
||||
))
|
||||
with open(template_tex_file, "r") as infile:
|
||||
@@ -298,11 +305,13 @@ def generate_tex_file(expression, template_tex_file):
|
||||
outfile.write(body)
|
||||
return result
|
||||
|
||||
|
||||
def get_null():
|
||||
if os.name == "nt":
|
||||
return "NUL"
|
||||
return "/dev/null"
|
||||
|
||||
|
||||
def tex_to_dvi(tex_file):
|
||||
result = tex_file.replace(".tex", ".dvi")
|
||||
if not os.path.exists(result):
|
||||
@@ -327,7 +336,8 @@ def tex_to_dvi(tex_file):
|
||||
"See log output above or the log file: %s" % log_file)
|
||||
return result
|
||||
|
||||
def dvi_to_svg(dvi_file, regen_if_exists = False):
|
||||
|
||||
def dvi_to_svg(dvi_file, regen_if_exists=False):
|
||||
"""
|
||||
Converts a dvi, which potentially has multiple slides, into a
|
||||
directory full of enumerated pngs corresponding with these slides.
|
||||
|
||||
@@ -9,67 +9,51 @@ 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,
|
||||
"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))
|
||||
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]
|
||||
"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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
self.rescale_to_fit(value, dim, stretch=True)
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
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.mobject import Mobject
|
||||
from utils.bezier import interpolate
|
||||
from utils.color import color_to_int_rgb
|
||||
from utils.color import interpolate_color
|
||||
from utils.config_ops import digest_config
|
||||
from utils.images import get_full_raster_image_path
|
||||
|
||||
|
||||
class ImageMobject(Mobject):
|
||||
"""
|
||||
Automatically filters out black pixels
|
||||
"""
|
||||
CONFIG = {
|
||||
"filter_color" : "black",
|
||||
"invert" : False,
|
||||
"filter_color": "black",
|
||||
"invert": False,
|
||||
# "use_cache" : True,
|
||||
"height": 2.0,
|
||||
"image_mode" : "RGBA",
|
||||
"pixel_array_dtype" : "uint8",
|
||||
"image_mode": "RGBA",
|
||||
"pixel_array_dtype": "uint8",
|
||||
}
|
||||
|
||||
def __init__(self, filename_or_array, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
if isinstance(filename_or_array, str):
|
||||
@@ -38,7 +36,7 @@ class ImageMobject(Mobject):
|
||||
self.pixel_array = np.array(filename_or_array)
|
||||
self.change_to_rgba_array()
|
||||
if self.invert:
|
||||
self.pixel_array[:,:,:3] = 255-self.pixel_array[:,:,:3]
|
||||
self.pixel_array[:, :, :3] = 255 - self.pixel_array[:, :, :3]
|
||||
Mobject.__init__(self, **kwargs)
|
||||
|
||||
def change_to_rgba_array(self):
|
||||
@@ -46,43 +44,43 @@ class ImageMobject(Mobject):
|
||||
if len(pa.shape) == 2:
|
||||
pa = pa.reshape(list(pa.shape) + [1])
|
||||
if pa.shape[2] == 1:
|
||||
pa = pa.repeat(3, axis = 2)
|
||||
pa = pa.repeat(3, axis=2)
|
||||
if pa.shape[2] == 3:
|
||||
alphas = 255*np.ones(
|
||||
list(pa.shape[:2])+[1],
|
||||
dtype = self.pixel_array_dtype
|
||||
alphas = 255 * np.ones(
|
||||
list(pa.shape[:2]) + [1],
|
||||
dtype=self.pixel_array_dtype
|
||||
)
|
||||
pa = np.append(pa, alphas, axis = 2)
|
||||
pa = np.append(pa, alphas, axis=2)
|
||||
self.pixel_array = pa
|
||||
|
||||
def set_color(self, color, alpha = None, family = True):
|
||||
def set_color(self, color, alpha=None, family=True):
|
||||
rgb = color_to_int_rgb(color)
|
||||
self.pixel_array[:,:,:3] = rgb
|
||||
self.pixel_array[:, :, :3] = rgb
|
||||
if alpha is not None:
|
||||
self.pixel_array[:,:,3] = int(255*alpha)
|
||||
self.pixel_array[:, :, 3] = int(255 * alpha)
|
||||
for submob in self.submobjects:
|
||||
submob.set_color(color, alpha, family)
|
||||
self.color = color
|
||||
return self
|
||||
|
||||
def init_points(self):
|
||||
#Corresponding corners of image are fixed to these
|
||||
#Three points
|
||||
# Corresponding corners of image are fixed to these
|
||||
# Three points
|
||||
self.points = np.array([
|
||||
UP+LEFT,
|
||||
UP+RIGHT,
|
||||
DOWN+LEFT,
|
||||
UP + LEFT,
|
||||
UP + RIGHT,
|
||||
DOWN + LEFT,
|
||||
])
|
||||
self.center()
|
||||
self.scale_to_fit_height(self.height)
|
||||
h, w = self.pixel_array.shape[:2]
|
||||
self.stretch_to_fit_width(self.height*w/h)
|
||||
self.stretch_to_fit_width(self.height * w / h)
|
||||
|
||||
def set_opacity(self, alpha):
|
||||
self.pixel_array[:,:,3] = int(255*alpha)
|
||||
self.pixel_array[:, :, 3] = int(255 * alpha)
|
||||
return self
|
||||
|
||||
def fade(self, darkness = 0.5):
|
||||
def fade(self, darkness=0.5):
|
||||
self.set_opacity(1 - darkness)
|
||||
return self
|
||||
|
||||
@@ -94,5 +92,3 @@ class ImageMobject(Mobject):
|
||||
|
||||
def copy(self):
|
||||
return self.deepcopy()
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
from constants import *
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
from utils.bezier import interpolate
|
||||
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
|
||||
|
||||
|
||||
class PMobject(Mobject):
|
||||
def init_points(self):
|
||||
self.rgbas = np.zeros((0, 4))
|
||||
@@ -20,38 +20,38 @@ class PMobject(Mobject):
|
||||
def get_array_attrs(self):
|
||||
return Mobject.get_array_attrs(self) + ["rgbas"]
|
||||
|
||||
def add_points(self, points, rgbas = None, color = None, alpha = 1):
|
||||
def add_points(self, points, rgbas=None, color=None, alpha=1):
|
||||
"""
|
||||
points must be a Nx3 numpy array, as must rgbas if it is not None
|
||||
"""
|
||||
if not isinstance(points, np.ndarray):
|
||||
points = np.array(points)
|
||||
num_new_points = len(points)
|
||||
self.points = np.append(self.points, points, axis = 0)
|
||||
self.points = np.append(self.points, points, axis=0)
|
||||
if rgbas is None:
|
||||
color = Color(color) if color else self.color
|
||||
rgbas = np.repeat(
|
||||
[color_to_rgba(color, alpha)],
|
||||
num_new_points,
|
||||
axis = 0
|
||||
axis=0
|
||||
)
|
||||
elif len(rgbas) != len(points):
|
||||
raise Exception("points and rgbas must have same shape")
|
||||
self.rgbas = np.append(self.rgbas, rgbas, axis = 0)
|
||||
self.rgbas = np.append(self.rgbas, rgbas, axis=0)
|
||||
return self
|
||||
|
||||
def set_color(self, color = YELLOW_C, family = True):
|
||||
def set_color(self, color=YELLOW_C, family=True):
|
||||
rgba = color_to_rgba(color)
|
||||
mobs = self.family_members_with_points() if family else [self]
|
||||
for mob in mobs:
|
||||
mob.rgbas[:,:] = rgba
|
||||
mob.rgbas[:, :] = rgba
|
||||
self.color = color
|
||||
return self
|
||||
|
||||
# def set_color_by_gradient(self, start_color, end_color):
|
||||
def set_color_by_gradient(self, *colors):
|
||||
self.rgbas = np.array(map(
|
||||
color_to_rgba,
|
||||
color_to_rgba,
|
||||
color_gradient(colors, len(self.points))
|
||||
))
|
||||
return self
|
||||
@@ -61,21 +61,21 @@ class PMobject(Mobject):
|
||||
num_points = mob.get_num_points()
|
||||
mob.rgbas = np.array([
|
||||
interpolate(start_rgba, end_rgba, alpha)
|
||||
for alpha in np.arange(num_points)/float(num_points)
|
||||
for alpha in np.arange(num_points) / float(num_points)
|
||||
])
|
||||
return self
|
||||
|
||||
def set_colors_by_radial_gradient(self, center = None, radius = 1, inner_color = WHITE, outer_color = BLACK):
|
||||
def set_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK):
|
||||
start_rgba, end_rgba = map(color_to_rgba, [start_color, end_color])
|
||||
if center == None:
|
||||
if center is None:
|
||||
center = self.get_center()
|
||||
for mob in self.family_members_with_points():
|
||||
num_points = mob.get_num_points()
|
||||
t = min(1,np.abs(mob.get_center() - center)/radius)
|
||||
t = min(1, np.abs(mob.get_center() - center) / radius)
|
||||
|
||||
mob.rgbas = np.array(
|
||||
[ interpolate(start_rgba, end_rgba, t) ] * num_points
|
||||
)
|
||||
[interpolate(start_rgba, end_rgba, t)] * num_points
|
||||
)
|
||||
return self
|
||||
|
||||
def match_colors(self, mobject):
|
||||
@@ -90,20 +90,20 @@ class PMobject(Mobject):
|
||||
mob.rgbas = mob.rgbas[to_eliminate]
|
||||
return self
|
||||
|
||||
def thin_out(self, factor = 5):
|
||||
def thin_out(self, factor=5):
|
||||
"""
|
||||
Removes all but every nth point for n = factor
|
||||
"""
|
||||
for mob in self.family_members_with_points():
|
||||
num_points = self.get_num_points()
|
||||
mob.apply_over_attr_arrays(
|
||||
lambda arr : arr[
|
||||
lambda arr: arr[
|
||||
np.arange(0, num_points, factor)
|
||||
]
|
||||
)
|
||||
return self
|
||||
|
||||
def sort_points(self, function = lambda p : p[0]):
|
||||
def sort_points(self, function=lambda p: p[0]):
|
||||
"""
|
||||
function is any map from R^3 to R
|
||||
"""
|
||||
@@ -111,7 +111,7 @@ class PMobject(Mobject):
|
||||
indices = np.argsort(
|
||||
np.apply_along_axis(function, 1, mob.points)
|
||||
)
|
||||
mob.apply_over_attr_arrays(lambda arr : arr[indices])
|
||||
mob.apply_over_attr_arrays(lambda arr: arr[indices])
|
||||
return self
|
||||
|
||||
def fade_to(self, color, alpha):
|
||||
@@ -135,19 +135,19 @@ class PMobject(Mobject):
|
||||
return rgba_to_color(self.rgbas[0, :])
|
||||
|
||||
def point_from_proportion(self, alpha):
|
||||
index = alpha*(self.get_num_points()-1)
|
||||
index = alpha * (self.get_num_points() - 1)
|
||||
return self.points[index]
|
||||
|
||||
# Alignment
|
||||
def align_points_with_larger(self, larger_mobject):
|
||||
assert(isinstance(larger_mobject, PMobject))
|
||||
self.apply_over_attr_arrays(
|
||||
lambda a : stretch_array_to_length(
|
||||
lambda a: stretch_array_to_length(
|
||||
a, larger_mobject.get_num_points()
|
||||
)
|
||||
)
|
||||
|
||||
def get_point_mobject(self, center = None):
|
||||
def get_point_mobject(self, center=None):
|
||||
if center is None:
|
||||
center = self.get_center()
|
||||
return Point(center)
|
||||
@@ -165,65 +165,70 @@ class PMobject(Mobject):
|
||||
for attr in self.get_array_attrs():
|
||||
full_array = getattr(mobject, attr)
|
||||
partial_array = full_array[lower_index:upper_index]
|
||||
setattr(self, attr, partial_array)
|
||||
setattr(self, attr, partial_array)
|
||||
|
||||
|
||||
#TODO, Make the two implementations bellow non-redundant
|
||||
# TODO, Make the two implementations bellow non-redundant
|
||||
class Mobject1D(PMobject):
|
||||
CONFIG = {
|
||||
"density" : DEFAULT_POINT_DENSITY_1D,
|
||||
"density": DEFAULT_POINT_DENSITY_1D,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.epsilon = 1.0 / self.density
|
||||
self.epsilon = 1.0 / self.density
|
||||
Mobject.__init__(self, **kwargs)
|
||||
|
||||
def add_line(self, start, end, color = None):
|
||||
def add_line(self, start, end, color=None):
|
||||
start, end = map(np.array, [start, end])
|
||||
length = np.linalg.norm(end - start)
|
||||
if length == 0:
|
||||
points = [start]
|
||||
else:
|
||||
epsilon = self.epsilon/length
|
||||
epsilon = self.epsilon / length
|
||||
points = [
|
||||
interpolate(start, end, t)
|
||||
for t in np.arange(0, 1, epsilon)
|
||||
]
|
||||
self.add_points(points, color = color)
|
||||
self.add_points(points, color=color)
|
||||
|
||||
|
||||
class Mobject2D(PMobject):
|
||||
CONFIG = {
|
||||
"density" : DEFAULT_POINT_DENSITY_2D,
|
||||
"density": DEFAULT_POINT_DENSITY_2D,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.epsilon = 1.0 / self.density
|
||||
self.epsilon = 1.0 / self.density
|
||||
Mobject.__init__(self, **kwargs)
|
||||
|
||||
|
||||
class PointCloudDot(Mobject1D):
|
||||
CONFIG = {
|
||||
"radius" : 0.075,
|
||||
"stroke_width" : 2,
|
||||
"density" : DEFAULT_POINT_DENSITY_1D,
|
||||
"color" : YELLOW,
|
||||
"radius": 0.075,
|
||||
"stroke_width": 2,
|
||||
"density": DEFAULT_POINT_DENSITY_1D,
|
||||
"color": YELLOW,
|
||||
}
|
||||
def __init__(self, center = ORIGIN, **kwargs):
|
||||
|
||||
def __init__(self, center=ORIGIN, **kwargs):
|
||||
Mobject1D.__init__(self, **kwargs)
|
||||
self.shift(center)
|
||||
|
||||
def generate_points(self):
|
||||
self.add_points([
|
||||
r*(np.cos(theta)*RIGHT + np.sin(theta)*UP)
|
||||
r * (np.cos(theta) * RIGHT + np.sin(theta) * UP)
|
||||
for r in np.arange(0, self.radius, self.epsilon)
|
||||
for theta in np.arange(0, 2*np.pi, self.epsilon/r)
|
||||
for theta in np.arange(0, 2 * np.pi, self.epsilon / r)
|
||||
])
|
||||
|
||||
|
||||
class Point(PMobject):
|
||||
CONFIG = {
|
||||
"color" : BLACK,
|
||||
"color": BLACK,
|
||||
}
|
||||
def __init__(self, location = ORIGIN, **kwargs):
|
||||
|
||||
def __init__(self, location=ORIGIN, **kwargs):
|
||||
PMobject.__init__(self, **kwargs)
|
||||
self.add_points([location])
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from colour import Color
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
@@ -12,38 +10,36 @@ 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
|
||||
|
||||
|
||||
|
||||
class VMobject(Mobject):
|
||||
CONFIG = {
|
||||
"fill_color" : None,
|
||||
"fill_opacity" : 0.0,
|
||||
"stroke_color" : None,
|
||||
#Indicates that it will not be displayed, but
|
||||
#that it should count in parent mobject's path
|
||||
"is_subpath" : False,
|
||||
"close_new_points" : False,
|
||||
"mark_paths_closed" : False,
|
||||
"propagate_style_to_family" : False,
|
||||
"pre_function_handle_to_anchor_scale_factor" : 0.01,
|
||||
"make_smooth_after_applying_functions" : False,
|
||||
"background_image_file" : None,
|
||||
"fill_color": None,
|
||||
"fill_opacity": 0.0,
|
||||
"stroke_color": None,
|
||||
# Indicates that it will not be displayed, but
|
||||
# that it should count in parent mobject's path
|
||||
"is_subpath": False,
|
||||
"close_new_points": False,
|
||||
"mark_paths_closed": False,
|
||||
"propagate_style_to_family": False,
|
||||
"pre_function_handle_to_anchor_scale_factor": 0.01,
|
||||
"make_smooth_after_applying_functions": False,
|
||||
"background_image_file": None,
|
||||
}
|
||||
|
||||
def get_group_class(self):
|
||||
return VGroup
|
||||
|
||||
## Colors
|
||||
# Colors
|
||||
def init_colors(self):
|
||||
self.set_style_data(
|
||||
stroke_color = self.stroke_color or self.color,
|
||||
stroke_width = self.stroke_width,
|
||||
fill_color = self.fill_color or self.color,
|
||||
fill_opacity = self.fill_opacity,
|
||||
family = self.propagate_style_to_family
|
||||
stroke_color=self.stroke_color or self.color,
|
||||
stroke_width=self.stroke_width,
|
||||
fill_color=self.fill_color or self.color,
|
||||
fill_opacity=self.fill_opacity,
|
||||
family=self.propagate_style_to_family
|
||||
)
|
||||
return self
|
||||
|
||||
@@ -51,12 +47,12 @@ class VMobject(Mobject):
|
||||
for mob in self.submobject_family():
|
||||
setattr(mob, attr, value)
|
||||
|
||||
def set_style_data(self,
|
||||
stroke_color = None,
|
||||
stroke_width = None,
|
||||
fill_color = None,
|
||||
fill_opacity = None,
|
||||
family = True
|
||||
def set_style_data(self,
|
||||
stroke_color=None,
|
||||
stroke_width=None,
|
||||
fill_color=None,
|
||||
fill_opacity=None,
|
||||
family=True
|
||||
):
|
||||
if stroke_color is not None:
|
||||
self.stroke_rgb = color_to_rgb(stroke_color)
|
||||
@@ -69,48 +65,48 @@ class VMobject(Mobject):
|
||||
if family:
|
||||
for mob in self.submobjects:
|
||||
mob.set_style_data(
|
||||
stroke_color = stroke_color,
|
||||
stroke_width = stroke_width,
|
||||
fill_color = fill_color,
|
||||
fill_opacity = fill_opacity,
|
||||
family = family
|
||||
stroke_color=stroke_color,
|
||||
stroke_width=stroke_width,
|
||||
fill_color=fill_color,
|
||||
fill_opacity=fill_opacity,
|
||||
family=family
|
||||
)
|
||||
return self
|
||||
|
||||
def set_fill(self, color = None, opacity = None, family = True):
|
||||
def set_fill(self, color=None, opacity=None, family=True):
|
||||
return self.set_style_data(
|
||||
fill_color = color,
|
||||
fill_opacity = opacity,
|
||||
family = family
|
||||
fill_color=color,
|
||||
fill_opacity=opacity,
|
||||
family=family
|
||||
)
|
||||
|
||||
def set_stroke(self, color = None, width = None, family = True):
|
||||
def set_stroke(self, color=None, width=None, family=True):
|
||||
return self.set_style_data(
|
||||
stroke_color = color,
|
||||
stroke_width = width,
|
||||
family = family
|
||||
stroke_color=color,
|
||||
stroke_width=width,
|
||||
family=family
|
||||
)
|
||||
|
||||
def set_color(self, color, family = True):
|
||||
def set_color(self, color, family=True):
|
||||
self.set_style_data(
|
||||
stroke_color = color,
|
||||
fill_color = color,
|
||||
family = family
|
||||
stroke_color=color,
|
||||
fill_color=color,
|
||||
family=family
|
||||
)
|
||||
self.color = color
|
||||
return self
|
||||
|
||||
def match_style(self, vmobject):
|
||||
self.set_style_data(
|
||||
stroke_color = vmobject.get_stroke_color(),
|
||||
stroke_width = vmobject.get_stroke_width(),
|
||||
fill_color = vmobject.get_fill_color(),
|
||||
fill_opacity = vmobject.get_fill_opacity(),
|
||||
family = False
|
||||
stroke_color=vmobject.get_stroke_color(),
|
||||
stroke_width=vmobject.get_stroke_width(),
|
||||
fill_color=vmobject.get_fill_color(),
|
||||
fill_opacity=vmobject.get_fill_opacity(),
|
||||
family=False
|
||||
)
|
||||
|
||||
#Does its best to match up submobject lists, and
|
||||
#match styles accordingly
|
||||
# Does its best to match up submobject lists, and
|
||||
# match styles accordingly
|
||||
submobs1, submobs2 = self.submobjects, vmobject.submobjects
|
||||
if len(submobs1) == 0:
|
||||
return self
|
||||
@@ -122,12 +118,12 @@ class VMobject(Mobject):
|
||||
|
||||
def fade_no_recurse(self, darkness):
|
||||
self.set_stroke(
|
||||
width = (1-darkness)*self.get_stroke_width(),
|
||||
family = False
|
||||
width=(1 - darkness) * self.get_stroke_width(),
|
||||
family=False
|
||||
)
|
||||
self.set_fill(
|
||||
opacity = (1-darkness)*self.get_fill_opacity(),
|
||||
family = False
|
||||
opacity=(1 - darkness) * self.get_fill_opacity(),
|
||||
family=False
|
||||
)
|
||||
return self
|
||||
|
||||
@@ -137,7 +133,7 @@ class VMobject(Mobject):
|
||||
def get_fill_color(self):
|
||||
try:
|
||||
self.fill_rgb = np.clip(self.fill_rgb, 0.0, 1.0)
|
||||
return Color(rgb = self.fill_rgb)
|
||||
return Color(rgb=self.fill_rgb)
|
||||
except:
|
||||
return Color(WHITE)
|
||||
|
||||
@@ -150,7 +146,7 @@ class VMobject(Mobject):
|
||||
def get_stroke_color(self):
|
||||
try:
|
||||
self.stroke_rgb = np.clip(self.stroke_rgb, 0, 1)
|
||||
return Color(rgb = self.stroke_rgb)
|
||||
return Color(rgb=self.stroke_rgb)
|
||||
except:
|
||||
return Color(WHITE)
|
||||
|
||||
@@ -176,7 +172,7 @@ class VMobject(Mobject):
|
||||
self.color_using_background_image(vmobject.get_background_image_file())
|
||||
return self
|
||||
|
||||
## Drawing
|
||||
# Drawing
|
||||
def start_at(self, point):
|
||||
if len(self.points) == 0:
|
||||
self.points = np.zeros((1, 3))
|
||||
@@ -188,7 +184,7 @@ class VMobject(Mobject):
|
||||
self.points = np.append(
|
||||
self.points,
|
||||
control_points,
|
||||
axis = 0
|
||||
axis=0
|
||||
)
|
||||
return self
|
||||
|
||||
@@ -196,14 +192,14 @@ class VMobject(Mobject):
|
||||
return is_closed(self.points)
|
||||
|
||||
def set_anchors_and_handles(self, anchors, handles1, handles2):
|
||||
assert(len(anchors) == len(handles1)+1)
|
||||
assert(len(anchors) == len(handles2)+1)
|
||||
total_len = 3*(len(anchors)-1) + 1
|
||||
assert(len(anchors) == len(handles1) + 1)
|
||||
assert(len(anchors) == len(handles2) + 1)
|
||||
total_len = 3 * (len(anchors) - 1) + 1
|
||||
self.points = np.zeros((total_len, self.dim))
|
||||
self.points[0] = anchors[0]
|
||||
arrays = [handles1, handles2, anchors[1:]]
|
||||
for index, array in enumerate(arrays):
|
||||
self.points[index+1::3] = array
|
||||
self.points[index + 1::3] = array
|
||||
return self.points
|
||||
|
||||
def set_points_as_corners(self, points):
|
||||
@@ -212,7 +208,7 @@ class VMobject(Mobject):
|
||||
points = np.array(points)
|
||||
self.set_anchors_and_handles(points, *[
|
||||
interpolate(points[:-1], points[1:], alpha)
|
||||
for alpha in 1./3, 2./3
|
||||
for alpha in 1. / 3, 2. / 3
|
||||
])
|
||||
return self
|
||||
|
||||
@@ -227,11 +223,11 @@ class VMobject(Mobject):
|
||||
self.points = np.array(points)
|
||||
return self
|
||||
|
||||
def set_anchor_points(self, points, mode = "smooth"):
|
||||
def set_anchor_points(self, points, mode="smooth"):
|
||||
if not isinstance(points, np.ndarray):
|
||||
points = np.array(points)
|
||||
if self.close_new_points and not is_closed(points):
|
||||
points = np.append(points, [points[0]], axis = 0)
|
||||
points = np.append(points, [points[0]], axis=0)
|
||||
if mode == "smooth":
|
||||
self.set_points_smoothly(points)
|
||||
elif mode == "corners":
|
||||
@@ -243,7 +239,7 @@ class VMobject(Mobject):
|
||||
def change_anchor_mode(self, mode):
|
||||
for submob in self.family_members_with_points():
|
||||
anchors, h1, h2 = submob.get_anchors_and_handles()
|
||||
submob.set_anchor_points(anchors, mode = mode)
|
||||
submob.set_anchor_points(anchors, mode=mode)
|
||||
return self
|
||||
|
||||
def make_smooth(self):
|
||||
@@ -262,7 +258,7 @@ class VMobject(Mobject):
|
||||
but will be tracked in a separate special list for when
|
||||
it comes time to display.
|
||||
"""
|
||||
subpath_mobject = self.copy() ##Really helps to be of the same class
|
||||
subpath_mobject = self.copy() # Really helps to be of the same class
|
||||
subpath_mobject.submobjects = []
|
||||
subpath_mobject.is_subpath = True
|
||||
subpath_mobject.set_points(points)
|
||||
@@ -277,12 +273,12 @@ class VMobject(Mobject):
|
||||
self.start_at(new_points[0])
|
||||
self.add_control_points(new_points[1:])
|
||||
else:
|
||||
self.add_control_points(2*[new_points[0]] + new_points)
|
||||
self.add_control_points(2 * [new_points[0]] + new_points)
|
||||
return self
|
||||
|
||||
def get_subpath_mobjects(self):
|
||||
return filter(
|
||||
lambda m : hasattr(m, 'is_subpath') and m.is_subpath,
|
||||
lambda m: hasattr(m, 'is_subpath') and m.is_subpath,
|
||||
self.submobjects
|
||||
)
|
||||
|
||||
@@ -290,7 +286,7 @@ class VMobject(Mobject):
|
||||
factor = self.pre_function_handle_to_anchor_scale_factor
|
||||
self.scale_handle_to_anchor_distances(factor)
|
||||
Mobject.apply_function(self, function)
|
||||
self.scale_handle_to_anchor_distances(1./factor)
|
||||
self.scale_handle_to_anchor_distances(1. / factor)
|
||||
if self.make_smooth_after_applying_functions:
|
||||
self.make_smooth()
|
||||
return self
|
||||
@@ -300,8 +296,8 @@ class VMobject(Mobject):
|
||||
If the distance between a given handle point H and its associated
|
||||
anchor point A is d, then it changes H to be a distances factor*d
|
||||
away from A, but so that the line from A to H doesn't change.
|
||||
This is mostly useful in the context of applying a (differentiable)
|
||||
function, to preserve tangency properties. One would pull all the
|
||||
This is mostly useful in the context of applying a (differentiable)
|
||||
function, to preserve tangency properties. One would pull all the
|
||||
handles closer to their anchors, apply the function then push them out
|
||||
again.
|
||||
"""
|
||||
@@ -311,27 +307,27 @@ class VMobject(Mobject):
|
||||
# print len(anchors), len(handles1), len(handles2)
|
||||
a_to_h1 = handles1 - anchors[:-1]
|
||||
a_to_h2 = handles2 - anchors[1:]
|
||||
handles1 = anchors[:-1] + factor*a_to_h1
|
||||
handles2 = anchors[1:] + factor*a_to_h2
|
||||
handles1 = anchors[:-1] + factor * a_to_h1
|
||||
handles2 = anchors[1:] + factor * a_to_h2
|
||||
self.set_anchors_and_handles(anchors, handles1, handles2)
|
||||
|
||||
## Information about line
|
||||
# Information about line
|
||||
|
||||
def component_curves(self):
|
||||
for n in range(self.get_num_anchor_points()-1):
|
||||
for n in range(self.get_num_anchor_points() - 1):
|
||||
yield self.get_nth_curve(n)
|
||||
|
||||
def get_nth_curve(self, n):
|
||||
return bezier(self.points[3*n:3*n+4])
|
||||
return bezier(self.points[3 * n:3 * n + 4])
|
||||
|
||||
def get_num_anchor_points(self):
|
||||
return (len(self.points) - 1)/3 + 1
|
||||
return (len(self.points) - 1) / 3 + 1
|
||||
|
||||
def point_from_proportion(self, alpha):
|
||||
num_cubics = self.get_num_anchor_points()-1
|
||||
interpoint_alpha = num_cubics*(alpha % (1./num_cubics))
|
||||
index = min(3*int(alpha*num_cubics), 3*num_cubics)
|
||||
cubic = bezier(self.points[index:index+4])
|
||||
num_cubics = self.get_num_anchor_points() - 1
|
||||
interpoint_alpha = num_cubics * (alpha % (1. / num_cubics))
|
||||
index = min(3 * int(alpha * num_cubics), 3 * num_cubics)
|
||||
cubic = bezier(self.points[index:index + 4])
|
||||
return cubic(interpoint_alpha)
|
||||
|
||||
def get_anchors_and_handles(self):
|
||||
@@ -346,8 +342,7 @@ class VMobject(Mobject):
|
||||
def get_points_defining_boundary(self):
|
||||
return self.get_anchors()
|
||||
|
||||
|
||||
## Alignment
|
||||
# Alignment
|
||||
def align_points(self, mobject):
|
||||
Mobject.align_points(self, mobject)
|
||||
is_subpath = self.is_subpath or mobject.is_subpath
|
||||
@@ -355,61 +350,62 @@ class VMobject(Mobject):
|
||||
mark_closed = self.mark_paths_closed and mobject.mark_paths_closed
|
||||
self.mark_paths_closed = mobject.mark_paths_closed = mark_closed
|
||||
return self
|
||||
|
||||
|
||||
def align_points_with_larger(self, larger_mobject):
|
||||
assert(isinstance(larger_mobject, VMobject))
|
||||
self.insert_n_anchor_points(
|
||||
larger_mobject.get_num_anchor_points()-\
|
||||
larger_mobject.get_num_anchor_points() -
|
||||
self.get_num_anchor_points()
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
def insert_n_anchor_points(self, n):
|
||||
curr = self.get_num_anchor_points()
|
||||
if curr == 0:
|
||||
self.points = np.zeros((1, 3))
|
||||
n = n-1
|
||||
n = n - 1
|
||||
if curr == 1:
|
||||
self.points = np.repeat(self.points, 3*n+1, axis = 0)
|
||||
self.points = np.repeat(self.points, 3 * n + 1, axis=0)
|
||||
return self
|
||||
points = np.array([self.points[0]])
|
||||
num_curves = curr-1
|
||||
#Curves in self are buckets, and we need to know
|
||||
#how many new anchor points to put into each one.
|
||||
#Each element of index_allocation is like a bucket,
|
||||
#and its value tells you the appropriate index of
|
||||
#the smaller curve.
|
||||
index_allocation = (np.arange(curr+n-1) * num_curves)/(curr+n-1)
|
||||
num_curves = curr - 1
|
||||
# Curves in self are buckets, and we need to know
|
||||
# how many new anchor points to put into each one.
|
||||
# Each element of index_allocation is like a bucket,
|
||||
# and its value tells you the appropriate index of
|
||||
# the smaller curve.
|
||||
index_allocation = (np.arange(curr + n - 1) *
|
||||
num_curves) / (curr + n - 1)
|
||||
for index in range(num_curves):
|
||||
curr_bezier_points = self.points[3*index:3*index+4]
|
||||
curr_bezier_points = self.points[3 * index:3 * index + 4]
|
||||
num_inter_curves = sum(index_allocation == index)
|
||||
alphas = np.linspace(0, 1, num_inter_curves+1)
|
||||
alphas = np.linspace(0, 1, num_inter_curves + 1)
|
||||
# alphas = np.arange(0, num_inter_curves+1)/float(num_inter_curves)
|
||||
for a, b in zip(alphas, alphas[1:]):
|
||||
new_points = partial_bezier_points(
|
||||
curr_bezier_points, a, b
|
||||
)
|
||||
points = np.append(
|
||||
points, new_points[1:], axis = 0
|
||||
points, new_points[1:], axis=0
|
||||
)
|
||||
self.set_points(points)
|
||||
return self
|
||||
|
||||
def get_point_mobject(self, center = None):
|
||||
|
||||
def get_point_mobject(self, center=None):
|
||||
if center is None:
|
||||
center = self.get_center()
|
||||
return VectorizedPoint(center)
|
||||
|
||||
|
||||
def repeat_submobject(self, submobject):
|
||||
if submobject.is_subpath:
|
||||
return VectorizedPoint(submobject.points[0])
|
||||
return submobject.copy()
|
||||
|
||||
|
||||
def interpolate_color(self, mobject1, mobject2, alpha):
|
||||
attrs = [
|
||||
"stroke_rgb",
|
||||
"stroke_width",
|
||||
"fill_rgb",
|
||||
"stroke_rgb",
|
||||
"stroke_width",
|
||||
"fill_rgb",
|
||||
"fill_opacity",
|
||||
]
|
||||
for attr in attrs:
|
||||
@@ -421,31 +417,31 @@ class VMobject(Mobject):
|
||||
if alpha == 1.0:
|
||||
# print getattr(mobject2, attr)
|
||||
setattr(self, attr, getattr(mobject2, attr))
|
||||
|
||||
|
||||
def pointwise_become_partial(self, mobject, a, b):
|
||||
assert(isinstance(mobject, VMobject))
|
||||
#Partial curve includes three portions:
|
||||
#- A middle section, which matches the curve exactly
|
||||
#- A start, which is some ending portion of an inner cubic
|
||||
#- An end, which is the starting portion of a later inner cubic
|
||||
# Partial curve includes three portions:
|
||||
# - A middle section, which matches the curve exactly
|
||||
# - A start, which is some ending portion of an inner cubic
|
||||
# - An end, which is the starting portion of a later inner cubic
|
||||
if a <= 0 and b >= 1:
|
||||
self.set_points(mobject.points)
|
||||
self.mark_paths_closed = mobject.mark_paths_closed
|
||||
return self
|
||||
self.mark_paths_closed = False
|
||||
num_cubics = mobject.get_num_anchor_points()-1
|
||||
lower_index = int(a*num_cubics)
|
||||
upper_index = int(b*num_cubics)
|
||||
num_cubics = mobject.get_num_anchor_points() - 1
|
||||
lower_index = int(a * num_cubics)
|
||||
upper_index = int(b * num_cubics)
|
||||
points = np.array(
|
||||
mobject.points[3*lower_index:3*upper_index+4]
|
||||
mobject.points[3 * lower_index:3 * upper_index + 4]
|
||||
)
|
||||
if len(points) > 1:
|
||||
a_residue = (num_cubics*a)%1
|
||||
b_residue = (num_cubics*b)%1
|
||||
a_residue = (num_cubics * a) % 1
|
||||
b_residue = (num_cubics * b) % 1
|
||||
if b == 1:
|
||||
b_residue = 1
|
||||
elif lower_index == upper_index:
|
||||
b_residue = (b_residue - a_residue)/(1-a_residue)
|
||||
b_residue = (b_residue - a_residue) / (1 - a_residue)
|
||||
|
||||
points[:4] = partial_bezier_points(
|
||||
points[:4], a_residue, 1
|
||||
@@ -456,6 +452,7 @@ class VMobject(Mobject):
|
||||
self.set_points(points)
|
||||
return self
|
||||
|
||||
|
||||
class VGroup(VMobject):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if len(args) == 1 and isinstance(args[0], (tuple, list)):
|
||||
@@ -465,19 +462,22 @@ class VGroup(VMobject):
|
||||
for arg in args:
|
||||
if isinstance(arg, (tuple, list)):
|
||||
packed_args.append(VGroup(arg))
|
||||
else: packed_args.append(arg)
|
||||
else:
|
||||
packed_args.append(arg)
|
||||
|
||||
VMobject.__init__(self, *packed_args, **kwargs)
|
||||
|
||||
|
||||
class VectorizedPoint(VMobject):
|
||||
CONFIG = {
|
||||
"color" : BLACK,
|
||||
"fill_opacity" : 0,
|
||||
"stroke_width" : 0,
|
||||
"artificial_width" : 0.01,
|
||||
"artificial_height" : 0.01,
|
||||
"color": BLACK,
|
||||
"fill_opacity": 0,
|
||||
"stroke_width": 0,
|
||||
"artificial_width": 0.01,
|
||||
"artificial_height": 0.01,
|
||||
}
|
||||
def __init__(self, location = ORIGIN, **kwargs):
|
||||
|
||||
def __init__(self, location=ORIGIN, **kwargs):
|
||||
VMobject.__init__(self, **kwargs)
|
||||
self.set_points(np.array([location]))
|
||||
|
||||
@@ -490,24 +490,25 @@ class VectorizedPoint(VMobject):
|
||||
def get_location(self):
|
||||
return self.get_anchors()[0]
|
||||
|
||||
def set_location(self,new_loc):
|
||||
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
|
||||
"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)
|
||||
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)
|
||||
|
||||
|
||||
@@ -8,14 +8,16 @@ 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
|
||||
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):
|
||||
|
||||
def __init__(self, value=0, **kwargs):
|
||||
VectorizedPoint.__init__(self, **kwargs)
|
||||
self.set_value(value)
|
||||
|
||||
@@ -23,21 +25,23 @@ class ValueTracker(VectorizedPoint):
|
||||
return self.get_center()[0]
|
||||
|
||||
def set_value(self, value):
|
||||
self.move_to(value*RIGHT)
|
||||
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
|
||||
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)
|
||||
self.move_to(np.log(value) * RIGHT)
|
||||
return self
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
|
||||
from animation.animation import Animation
|
||||
@@ -6,18 +5,19 @@ from constants import *
|
||||
from mobject.svg.tex_mobject import TexMobject
|
||||
from scene.scene import Scene
|
||||
|
||||
|
||||
class RearrangeEquation(Scene):
|
||||
def construct(
|
||||
self,
|
||||
start_terms,
|
||||
end_terms,
|
||||
self,
|
||||
start_terms,
|
||||
end_terms,
|
||||
index_map,
|
||||
path_arc = np.pi,
|
||||
start_transform = None,
|
||||
end_transform = None,
|
||||
leave_start_terms = False,
|
||||
transform_kwargs = {},
|
||||
):
|
||||
path_arc=np.pi,
|
||||
start_transform=None,
|
||||
end_transform=None,
|
||||
leave_start_terms=False,
|
||||
transform_kwargs={},
|
||||
):
|
||||
transform_kwargs["path_func"] = path
|
||||
start_mobs, end_mobs = self.get_mobs_from_terms(
|
||||
start_terms, end_terms
|
||||
@@ -27,17 +27,17 @@ class RearrangeEquation(Scene):
|
||||
if end_transform:
|
||||
end_mobs = end_transform(Mobject(*end_mobs)).split()
|
||||
unmatched_start_indices = set(range(len(start_mobs)))
|
||||
unmatched_end_indices = set(range(len(end_mobs)))
|
||||
unmatched_end_indices = set(range(len(end_mobs)))
|
||||
unmatched_start_indices.difference_update(
|
||||
[n%len(start_mobs) for n in index_map]
|
||||
[n % len(start_mobs) for n in index_map]
|
||||
)
|
||||
unmatched_end_indices.difference_update(
|
||||
[n%len(end_mobs) for n in index_map.values()]
|
||||
[n % len(end_mobs) for n in index_map.values()]
|
||||
)
|
||||
mobject_pairs = [
|
||||
(start_mobs[a], end_mobs[b])
|
||||
for a, b in index_map.iteritems()
|
||||
]+ [
|
||||
] + [
|
||||
(Point(end_mobs[b].get_center()), end_mobs[b])
|
||||
for b in unmatched_end_indices
|
||||
]
|
||||
@@ -45,7 +45,7 @@ class RearrangeEquation(Scene):
|
||||
mobject_pairs += [
|
||||
(start_mobs[a], Point(start_mobs[a].get_center()))
|
||||
for a in unmatched_start_indices
|
||||
]
|
||||
]
|
||||
|
||||
self.add(*start_mobs)
|
||||
if leave_start_terms:
|
||||
@@ -57,7 +57,6 @@ class RearrangeEquation(Scene):
|
||||
])
|
||||
self.wait()
|
||||
|
||||
|
||||
def get_mobs_from_terms(self, start_terms, end_terms):
|
||||
"""
|
||||
Need to ensure that all image mobjects for a tex expression
|
||||
@@ -67,10 +66,10 @@ class RearrangeEquation(Scene):
|
||||
"""
|
||||
num_start_terms = len(start_terms)
|
||||
all_mobs = np.array(
|
||||
TexMobject(start_terms).split() + \
|
||||
TexMobject(start_terms).split() +
|
||||
TexMobject(end_terms).split()
|
||||
)
|
||||
all_terms = np.array(start_terms+end_terms)
|
||||
all_terms = np.array(start_terms + end_terms)
|
||||
for term in set(all_terms):
|
||||
matches = all_terms == term
|
||||
if sum(matches) > 1:
|
||||
@@ -84,23 +83,21 @@ class RearrangeEquation(Scene):
|
||||
|
||||
class FlipThroughSymbols(Animation):
|
||||
CONFIG = {
|
||||
"start_center" : ORIGIN,
|
||||
"end_center" : ORIGIN,
|
||||
"start_center": ORIGIN,
|
||||
"end_center": ORIGIN,
|
||||
}
|
||||
|
||||
def __init__(self, tex_list, **kwargs):
|
||||
mobject = TexMobject(self.curr_tex).shift(start_center)
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
new_tex = self.tex_list[np.ceil(alpha*len(self.tex_list))-1]
|
||||
new_tex = self.tex_list[np.ceil(alpha * len(self.tex_list)) - 1]
|
||||
|
||||
if new_tex != self.curr_tex:
|
||||
self.curr_tex = new_tex
|
||||
self.mobject = TexMobject(new_tex).shift(self.start_center)
|
||||
if not all(self.start_center == self.end_center):
|
||||
self.mobject.center().shift(
|
||||
(1-alpha)*self.start_center + alpha*self.end_center
|
||||
(1 - alpha) * self.start_center + alpha * self.end_center
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,29 +2,30 @@ from constants import *
|
||||
|
||||
from animation.animation import Animation
|
||||
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
|
||||
|
||||
|
||||
class ComplexTransformationScene(Scene):
|
||||
CONFIG = {
|
||||
"plane_config" : {},
|
||||
"background_fade_factor" : 0.5,
|
||||
"use_multicolored_plane" : False,
|
||||
"vert_start_color" : BLUE, ##TODO
|
||||
"vert_end_color" : BLUE,
|
||||
"horiz_start_color" : BLUE,
|
||||
"horiz_end_color" : BLUE,
|
||||
"num_anchors_to_add_per_line" : 50,
|
||||
"post_transformation_stroke_width" : None,
|
||||
"default_apply_complex_function_kwargs" : {
|
||||
"run_time" : 5,
|
||||
"plane_config": {},
|
||||
"background_fade_factor": 0.5,
|
||||
"use_multicolored_plane": False,
|
||||
"vert_start_color": BLUE, # TODO
|
||||
"vert_end_color": BLUE,
|
||||
"horiz_start_color": BLUE,
|
||||
"horiz_end_color": BLUE,
|
||||
"num_anchors_to_add_per_line": 50,
|
||||
"post_transformation_stroke_width": None,
|
||||
"default_apply_complex_function_kwargs": {
|
||||
"run_time": 5,
|
||||
},
|
||||
"background_label_scale_val" : 0.5,
|
||||
"include_coordinate_labels" : True,
|
||||
"background_label_scale_val": 0.5,
|
||||
"include_coordinate_labels": True,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
self.foreground_mobjects = []
|
||||
self.transformable_mobjects = []
|
||||
@@ -44,12 +45,12 @@ class ComplexTransformationScene(Scene):
|
||||
Scene.add(self, *mobjects)
|
||||
|
||||
def add(self, *mobjects):
|
||||
Scene.add(self, *list(mobjects)+self.foreground_mobjects)
|
||||
Scene.add(self, *list(mobjects) + self.foreground_mobjects)
|
||||
|
||||
def play(self, *animations, **kwargs):
|
||||
Scene.play(
|
||||
self,
|
||||
*list(animations)+map(Animation, self.foreground_mobjects),
|
||||
*list(animations) + map(Animation, self.foreground_mobjects),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@@ -67,7 +68,7 @@ class ComplexTransformationScene(Scene):
|
||||
self.plane = self.get_transformable_plane()
|
||||
self.add(self.plane)
|
||||
|
||||
def get_transformable_plane(self, x_range = None, y_range = None):
|
||||
def get_transformable_plane(self, x_range=None, y_range=None):
|
||||
"""
|
||||
x_range and y_range would be tuples (min, max)
|
||||
"""
|
||||
@@ -76,11 +77,11 @@ class ComplexTransformationScene(Scene):
|
||||
if x_range is not None:
|
||||
x_min, x_max = x_range
|
||||
plane_config["x_radius"] = x_max - x_min
|
||||
shift_val += (x_max+x_min)*RIGHT/2.
|
||||
shift_val += (x_max + x_min) * RIGHT / 2.
|
||||
if y_range is not None:
|
||||
y_min, y_max = y_range
|
||||
plane_config["y_radius"] = y_max - y_min
|
||||
shift_val += (y_max+y_min)*UP/2.
|
||||
shift_val += (y_max + y_min) * UP / 2.
|
||||
plane = ComplexPlane(**plane_config)
|
||||
plane.shift(shift_val)
|
||||
if self.use_multicolored_plane:
|
||||
@@ -92,7 +93,7 @@ class ComplexTransformationScene(Scene):
|
||||
mob.prepare_for_nonlinear_transform(
|
||||
self.num_anchors_to_add_per_line
|
||||
)
|
||||
#TODO...
|
||||
# TODO...
|
||||
|
||||
def paint_plane(self, plane):
|
||||
for lines in plane.main_lines, plane.secondary_lines:
|
||||
@@ -109,7 +110,7 @@ class ComplexTransformationScene(Scene):
|
||||
|
||||
def z_to_point(self, z):
|
||||
return self.background.number_to_point(z)
|
||||
|
||||
|
||||
def get_transformer(self, **kwargs):
|
||||
transform_kwargs = dict(self.default_apply_complex_function_kwargs)
|
||||
transform_kwargs.update(kwargs)
|
||||
@@ -117,16 +118,15 @@ class ComplexTransformationScene(Scene):
|
||||
self.prepare_for_transformation(plane)
|
||||
transformer = VGroup(
|
||||
plane, *self.transformable_mobjects
|
||||
)
|
||||
)
|
||||
return transformer, transform_kwargs
|
||||
|
||||
|
||||
def apply_complex_function(self, func, added_anims = [], **kwargs):
|
||||
def apply_complex_function(self, func, added_anims=[], **kwargs):
|
||||
transformer, transform_kwargs = self.get_transformer(**kwargs)
|
||||
transformer.generate_target()
|
||||
#Rescale, apply function, scale back
|
||||
# Rescale, apply function, scale back
|
||||
transformer.target.shift(-self.background.get_center_point())
|
||||
transformer.target.scale(1./self.background.unit_size)
|
||||
transformer.target.scale(1. / self.background.unit_size)
|
||||
transformer.target.apply_complex_function(func)
|
||||
transformer.target.scale(self.background.unit_size)
|
||||
transformer.target.shift(self.background.get_center_point())
|
||||
@@ -135,14 +135,16 @@ class ComplexTransformationScene(Scene):
|
||||
for mob in transformer.target[0].family_members_with_points():
|
||||
mob.make_smooth()
|
||||
if self.post_transformation_stroke_width is not None:
|
||||
transformer.target.set_stroke(width = self.post_transformation_stroke_width)
|
||||
transformer.target.set_stroke(
|
||||
width=self.post_transformation_stroke_width)
|
||||
self.play(
|
||||
MoveToTarget(transformer, **transform_kwargs),
|
||||
*added_anims
|
||||
)
|
||||
|
||||
def apply_complex_homotopy(self, complex_homotopy, added_anims = [], **kwargs):
|
||||
def apply_complex_homotopy(self, complex_homotopy, added_anims=[], **kwargs):
|
||||
transformer, transform_kwargs = self.get_transformer(**kwargs)
|
||||
|
||||
def homotopy(x, y, z, t):
|
||||
output = complex_homotopy(complex(x, y), t)
|
||||
rescaled_output = self.z_to_point(output)
|
||||
@@ -155,24 +157,3 @@ class ComplexTransformationScene(Scene):
|
||||
),
|
||||
*added_anims
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
from constants import *
|
||||
|
||||
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 animation.creation import ShowCreation
|
||||
from animation.creation import FadeIn
|
||||
@@ -18,17 +15,18 @@ from scene.scene import Scene
|
||||
|
||||
class CountingScene(Scene):
|
||||
CONFIG = {
|
||||
"digit_place_colors" : [YELLOW, MAROON_B, RED, GREEN, BLUE, PURPLE_D],
|
||||
"counting_dot_starting_position" : (FRAME_X_RADIUS-1)*RIGHT + (FRAME_Y_RADIUS-1)*UP,
|
||||
"count_dot_starting_radius" : 0.5,
|
||||
"dot_configuration_height" : 2,
|
||||
"ones_configuration_location" : UP+2*RIGHT,
|
||||
"num_scale_factor" : 2,
|
||||
"num_start_location" : 2*DOWN,
|
||||
"digit_place_colors": [YELLOW, MAROON_B, RED, GREEN, BLUE, PURPLE_D],
|
||||
"counting_dot_starting_position": (FRAME_X_RADIUS - 1) * RIGHT + (FRAME_Y_RADIUS - 1) * UP,
|
||||
"count_dot_starting_radius": 0.5,
|
||||
"dot_configuration_height": 2,
|
||||
"ones_configuration_location": UP + 2 * RIGHT,
|
||||
"num_scale_factor": 2,
|
||||
"num_start_location": 2 * DOWN,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
self.dots = VGroup()
|
||||
self.number = 0
|
||||
self.number = 0
|
||||
self.max_place = 0
|
||||
self.number_mob = VGroup(TexMobject(str(self.number)))
|
||||
self.number_mob.scale(self.num_scale_factor)
|
||||
@@ -43,26 +41,25 @@ class CountingScene(Scene):
|
||||
self.add(self.number_mob)
|
||||
|
||||
def get_template_configuration(self, place):
|
||||
#This should probably be replaced for non-base-10 counting scenes
|
||||
down_right = (0.5)*RIGHT + (np.sqrt(3)/2)*DOWN
|
||||
# This should probably be replaced for non-base-10 counting scenes
|
||||
down_right = (0.5) * RIGHT + (np.sqrt(3) / 2) * DOWN
|
||||
result = []
|
||||
for down_right_steps in range(5):
|
||||
for left_steps in range(down_right_steps):
|
||||
result.append(
|
||||
down_right_steps*down_right + left_steps*LEFT
|
||||
down_right_steps * down_right + left_steps * LEFT
|
||||
)
|
||||
return reversed(result[:self.get_place_max(place)])
|
||||
|
||||
def get_dot_template(self, place):
|
||||
#This should be replaced for non-base-10 counting scenes
|
||||
down_right = (0.5)*RIGHT + (np.sqrt(3)/2)*DOWN
|
||||
# This should be replaced for non-base-10 counting scenes
|
||||
dots = VGroup(*[
|
||||
Dot(
|
||||
point,
|
||||
radius = 0.25,
|
||||
fill_opacity = 0,
|
||||
stroke_width = 2,
|
||||
stroke_color = WHITE,
|
||||
point,
|
||||
radius=0.25,
|
||||
fill_opacity=0,
|
||||
stroke_width=2,
|
||||
stroke_color=WHITE,
|
||||
)
|
||||
for point in self.get_template_configuration(place)
|
||||
])
|
||||
@@ -72,9 +69,9 @@ class CountingScene(Scene):
|
||||
def add_configuration(self):
|
||||
new_template = self.get_dot_template(len(self.dot_templates))
|
||||
new_template.move_to(self.ones_configuration_location)
|
||||
left_vect = (new_template.get_width()+LARGE_BUFF)*LEFT
|
||||
left_vect = (new_template.get_width() + LARGE_BUFF) * LEFT
|
||||
new_template.shift(
|
||||
left_vect*len(self.dot_templates)
|
||||
left_vect * len(self.dot_templates)
|
||||
)
|
||||
self.dot_templates.append(new_template)
|
||||
self.dot_template_iterators.append(
|
||||
@@ -82,27 +79,27 @@ class CountingScene(Scene):
|
||||
)
|
||||
self.curr_configurations.append(VGroup())
|
||||
|
||||
def count(self, max_val, run_time_per_anim = 1):
|
||||
def count(self, max_val, run_time_per_anim=1):
|
||||
for x in range(max_val):
|
||||
self.increment(run_time_per_anim)
|
||||
|
||||
def increment(self, run_time_per_anim = 1):
|
||||
def increment(self, run_time_per_anim=1):
|
||||
moving_dot = Dot(
|
||||
self.counting_dot_starting_position,
|
||||
radius = self.count_dot_starting_radius,
|
||||
color = self.digit_place_colors[0],
|
||||
radius=self.count_dot_starting_radius,
|
||||
color=self.digit_place_colors[0],
|
||||
)
|
||||
moving_dot.generate_target()
|
||||
moving_dot.set_fill(opacity = 0)
|
||||
moving_dot.set_fill(opacity=0)
|
||||
kwargs = {
|
||||
"run_time" : run_time_per_anim
|
||||
"run_time": run_time_per_anim
|
||||
}
|
||||
|
||||
continue_rolling_over = True
|
||||
first_move = True
|
||||
place = 0
|
||||
while continue_rolling_over:
|
||||
added_anims = []
|
||||
added_anims = []
|
||||
if first_move:
|
||||
added_anims += self.get_digit_increment_animations()
|
||||
first_move = False
|
||||
@@ -112,26 +109,25 @@ class CountingScene(Scene):
|
||||
self.play(MoveToTarget(moving_dot), *added_anims, **kwargs)
|
||||
self.curr_configurations[place].add(moving_dot)
|
||||
|
||||
|
||||
if len(self.curr_configurations[place].split()) == self.get_place_max(place):
|
||||
full_configuration = self.curr_configurations[place]
|
||||
self.curr_configurations[place] = VGroup()
|
||||
place += 1
|
||||
center = full_configuration.get_center_of_mass()
|
||||
radius = 0.6*max(
|
||||
radius = 0.6 * max(
|
||||
full_configuration.get_width(),
|
||||
full_configuration.get_height(),
|
||||
)
|
||||
circle = Circle(
|
||||
radius = radius,
|
||||
stroke_width = 0,
|
||||
fill_color = self.digit_place_colors[place],
|
||||
fill_opacity = 0.5,
|
||||
radius=radius,
|
||||
stroke_width=0,
|
||||
fill_color=self.digit_place_colors[place],
|
||||
fill_opacity=0.5,
|
||||
)
|
||||
circle.move_to(center)
|
||||
moving_dot = VGroup(circle, full_configuration)
|
||||
moving_dot.generate_target()
|
||||
moving_dot[0].set_fill(opacity = 0)
|
||||
moving_dot[0].set_fill(opacity=0)
|
||||
else:
|
||||
continue_rolling_over = False
|
||||
|
||||
@@ -139,23 +135,24 @@ class CountingScene(Scene):
|
||||
result = []
|
||||
self.number += 1
|
||||
is_next_digit = self.is_next_digit()
|
||||
if is_next_digit: self.max_place += 1
|
||||
if is_next_digit:
|
||||
self.max_place += 1
|
||||
new_number_mob = self.get_number_mob(self.number)
|
||||
new_number_mob.move_to(self.number_mob, RIGHT)
|
||||
if is_next_digit:
|
||||
self.add_configuration()
|
||||
place = len(new_number_mob.split())-1
|
||||
place = len(new_number_mob.split()) - 1
|
||||
result.append(FadeIn(self.dot_templates[place]))
|
||||
arrow = Arrow(
|
||||
new_number_mob[place].get_top(),
|
||||
self.dot_templates[place].get_bottom(),
|
||||
color = self.digit_place_colors[place]
|
||||
color=self.digit_place_colors[place]
|
||||
)
|
||||
self.arrows.add(arrow)
|
||||
result.append(ShowCreation(arrow))
|
||||
result.append(Transform(
|
||||
self.number_mob, new_number_mob,
|
||||
submobject_mode = "lagged_start"
|
||||
submobject_mode="lagged_start"
|
||||
))
|
||||
return result
|
||||
|
||||
@@ -169,35 +166,42 @@ class CountingScene(Scene):
|
||||
self.digit_place_colors += self.digit_place_colors
|
||||
digit.set_color(self.digit_place_colors[place])
|
||||
digit.scale(self.num_scale_factor)
|
||||
digit.next_to(result, LEFT, buff = SMALL_BUFF, aligned_edge = DOWN)
|
||||
digit.next_to(result, LEFT, buff=SMALL_BUFF, aligned_edge=DOWN)
|
||||
result.add(digit)
|
||||
place += 1
|
||||
return result
|
||||
|
||||
def is_next_digit(self):
|
||||
return False
|
||||
|
||||
def get_place_num(self, num, place):
|
||||
return 0
|
||||
|
||||
def get_place_max(self, place):
|
||||
return 0
|
||||
|
||||
|
||||
class PowerCounter(CountingScene):
|
||||
def is_next_digit(self):
|
||||
number = self.number
|
||||
while number > 1:
|
||||
if number%self.base != 0:
|
||||
if number % self.base != 0:
|
||||
return False
|
||||
number /= self.base
|
||||
return True
|
||||
|
||||
def get_place_max(self, place):
|
||||
return self.base
|
||||
|
||||
def get_place_num(self, num, place):
|
||||
return (num / (self.base ** place)) % self.base
|
||||
|
||||
|
||||
class CountInDecimal(PowerCounter):
|
||||
CONFIG = {
|
||||
"base" : 10,
|
||||
"base": 10,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
for x in range(11):
|
||||
self.increment()
|
||||
@@ -206,35 +210,41 @@ class CountInDecimal(PowerCounter):
|
||||
for x in range(20):
|
||||
self.increment()
|
||||
|
||||
|
||||
class CountInTernary(PowerCounter):
|
||||
CONFIG = {
|
||||
"base" : 3,
|
||||
"dot_configuration_height" : 1,
|
||||
"ones_configuration_location" : UP+4*RIGHT
|
||||
"base": 3,
|
||||
"dot_configuration_height": 1,
|
||||
"ones_configuration_location": UP + 4 * RIGHT
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.count(27)
|
||||
|
||||
# def get_template_configuration(self):
|
||||
# return [ORIGIN, UP]
|
||||
|
||||
|
||||
class CountInBinaryTo256(PowerCounter):
|
||||
CONFIG = {
|
||||
"base" : 2,
|
||||
"dot_configuration_height" : 1,
|
||||
"ones_configuration_location" : UP+5*RIGHT
|
||||
"base": 2,
|
||||
"dot_configuration_height": 1,
|
||||
"ones_configuration_location": UP + 5 * RIGHT
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.count(128, 0.3)
|
||||
|
||||
def get_template_configuration(self):
|
||||
return [ORIGIN, UP]
|
||||
|
||||
|
||||
class FactorialBase(CountingScene):
|
||||
CONFIG = {
|
||||
"dot_configuration_height" : 1,
|
||||
"ones_configuration_location" : UP+4*RIGHT
|
||||
"dot_configuration_height": 1,
|
||||
"ones_configuration_location": UP + 4 * RIGHT
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
self.count(30, 0.4)
|
||||
|
||||
@@ -248,15 +258,7 @@ class FactorialBase(CountingScene):
|
||||
return (num / self.factorial(place + 1)) % self.get_place_max(place)
|
||||
|
||||
def factorial(self, n):
|
||||
if (n == 1): return 1
|
||||
else: return n * self.factorial(n - 1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (n == 1):
|
||||
return 1
|
||||
else:
|
||||
return n * self.factorial(n - 1)
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
from animation.creation import ShowCreation
|
||||
from animation.transform import Transform
|
||||
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
|
||||
@@ -22,64 +16,70 @@ from utils.space_ops import rotation_matrix
|
||||
|
||||
from constants import *
|
||||
|
||||
def rotate(points, angle = np.pi, axis = OUT):
|
||||
|
||||
def rotate(points, angle=np.pi, axis=OUT):
|
||||
if axis is None:
|
||||
return points
|
||||
matrix = rotation_matrix(angle, axis)
|
||||
points = np.dot(points, np.transpose(matrix))
|
||||
return points
|
||||
|
||||
def fractalify(vmobject, order = 3, *args, **kwargs):
|
||||
|
||||
def fractalify(vmobject, order=3, *args, **kwargs):
|
||||
for x in range(order):
|
||||
fractalification_iteration(vmobject)
|
||||
return vmobject
|
||||
|
||||
def fractalification_iteration(vmobject, dimension = 1.05, num_inserted_anchors_range = range(1, 4)):
|
||||
|
||||
def fractalification_iteration(vmobject, dimension=1.05, num_inserted_anchors_range=range(1, 4)):
|
||||
num_points = vmobject.get_num_points()
|
||||
if num_points > 0:
|
||||
# original_anchors = vmobject.get_anchors()
|
||||
original_anchors = [
|
||||
vmobject.point_from_proportion(x)
|
||||
for x in np.linspace(0, 1-1./num_points, num_points)
|
||||
for x in np.linspace(0, 1 - 1. / num_points, num_points)
|
||||
]
|
||||
new_anchors = []
|
||||
for p1, p2, in zip(original_anchors, original_anchors[1:]):
|
||||
num_inserts = random.choice(num_inserted_anchors_range)
|
||||
inserted_points = [
|
||||
interpolate(p1, p2, alpha)
|
||||
for alpha in np.linspace(0, 1, num_inserts+2)[1:-1]
|
||||
for alpha in np.linspace(0, 1, num_inserts + 2)[1:-1]
|
||||
]
|
||||
mass_scaling_factor = 1./(num_inserts+1)
|
||||
length_scaling_factor = mass_scaling_factor**(1./dimension)
|
||||
target_length = np.linalg.norm(p1-p2)*length_scaling_factor
|
||||
curr_length = np.linalg.norm(p1-p2)*mass_scaling_factor
|
||||
#offset^2 + curr_length^2 = target_length^2
|
||||
mass_scaling_factor = 1. / (num_inserts + 1)
|
||||
length_scaling_factor = mass_scaling_factor**(1. / dimension)
|
||||
target_length = np.linalg.norm(p1 - p2) * length_scaling_factor
|
||||
curr_length = np.linalg.norm(p1 - p2) * mass_scaling_factor
|
||||
# offset^2 + curr_length^2 = target_length^2
|
||||
offset_len = np.sqrt(target_length**2 - curr_length**2)
|
||||
unit_vect = (p1-p2)/np.linalg.norm(p1-p2)
|
||||
offset_unit_vect = rotate_vector(unit_vect, np.pi/2)
|
||||
unit_vect = (p1 - p2) / np.linalg.norm(p1 - p2)
|
||||
offset_unit_vect = rotate_vector(unit_vect, np.pi / 2)
|
||||
inserted_points = [
|
||||
point + u*offset_len*offset_unit_vect
|
||||
point + u * offset_len * offset_unit_vect
|
||||
for u, point in zip(it.cycle([-1, 1]), inserted_points)
|
||||
]
|
||||
new_anchors += [p1] + inserted_points
|
||||
new_anchors.append(original_anchors[-1])
|
||||
vmobject.set_points_as_corners(new_anchors)
|
||||
vmobject.submobjects = [
|
||||
fractalification_iteration(submob, dimension, num_inserted_anchors_range)
|
||||
fractalification_iteration(
|
||||
submob, dimension, num_inserted_anchors_range)
|
||||
for submob in vmobject.submobjects
|
||||
]
|
||||
return vmobject
|
||||
|
||||
|
||||
class SelfSimilarFractal(VMobject):
|
||||
CONFIG = {
|
||||
"order" : 5,
|
||||
"num_subparts" : 3,
|
||||
"height" : 4,
|
||||
"colors" : [RED, WHITE],
|
||||
"stroke_width" : 1,
|
||||
"fill_opacity" : 1,
|
||||
"propagate_style_to_family" : True,
|
||||
"order": 5,
|
||||
"num_subparts": 3,
|
||||
"height": 4,
|
||||
"colors": [RED, WHITE],
|
||||
"stroke_width": 1,
|
||||
"fill_opacity": 1,
|
||||
"propagate_style_to_family": True,
|
||||
}
|
||||
|
||||
def init_colors(self):
|
||||
VMobject.init_colors(self)
|
||||
self.set_color_by_gradient(*self.colors)
|
||||
@@ -114,45 +114,51 @@ class SelfSimilarFractal(VMobject):
|
||||
def arrange_subparts(self, *subparts):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
class Sierpinski(SelfSimilarFractal):
|
||||
def get_seed_shape(self):
|
||||
return Polygon(
|
||||
RIGHT, np.sqrt(3)*UP, LEFT,
|
||||
RIGHT, np.sqrt(3) * UP, LEFT,
|
||||
)
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
tri1, tri2, tri3 = subparts
|
||||
tri1.move_to(tri2.get_corner(DOWN+LEFT), UP)
|
||||
tri3.move_to(tri2.get_corner(DOWN+RIGHT), UP)
|
||||
tri1.move_to(tri2.get_corner(DOWN + LEFT), UP)
|
||||
tri3.move_to(tri2.get_corner(DOWN + RIGHT), UP)
|
||||
|
||||
|
||||
class DiamondFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts" : 4,
|
||||
"height" : 4,
|
||||
"colors" : [GREEN_E, YELLOW],
|
||||
"num_subparts": 4,
|
||||
"height": 4,
|
||||
"colors": [GREEN_E, YELLOW],
|
||||
}
|
||||
|
||||
def get_seed_shape(self):
|
||||
return RegularPolygon(n = 4)
|
||||
return RegularPolygon(n=4)
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
# VGroup(*subparts).rotate(np.pi/4)
|
||||
for part, vect in zip(subparts, compass_directions(start_vect = UP+RIGHT)):
|
||||
part.next_to(ORIGIN, vect, buff = 0)
|
||||
VGroup(*subparts).rotate(np.pi/4, about_point = ORIGIN)
|
||||
for part, vect in zip(subparts, compass_directions(start_vect=UP + RIGHT)):
|
||||
part.next_to(ORIGIN, vect, buff=0)
|
||||
VGroup(*subparts).rotate(np.pi / 4, about_point=ORIGIN)
|
||||
|
||||
|
||||
class PentagonalFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts" : 5,
|
||||
"colors" : [MAROON_B, YELLOW, RED],
|
||||
"height" : 6,
|
||||
"num_subparts": 5,
|
||||
"colors": [MAROON_B, YELLOW, RED],
|
||||
"height": 6,
|
||||
}
|
||||
|
||||
def get_seed_shape(self):
|
||||
return RegularPolygon(n = 5, start_angle = np.pi/2)
|
||||
return RegularPolygon(n=5, start_angle=np.pi / 2)
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
for x, part in enumerate(subparts):
|
||||
part.shift(0.95*part.get_height()*UP)
|
||||
part.rotate(2*np.pi*x/5, about_point = ORIGIN)
|
||||
part.shift(0.95 * part.get_height() * UP)
|
||||
part.rotate(2 * np.pi * x / 5, about_point=ORIGIN)
|
||||
|
||||
|
||||
class PentagonalPiCreatureFractal(PentagonalFractal):
|
||||
def init_colors(self):
|
||||
@@ -165,30 +171,32 @@ class PentagonalPiCreatureFractal(PentagonalFractal):
|
||||
colors = color_gradient(self.colors, len(internal_pis))
|
||||
for pi, color in zip(internal_pis, colors):
|
||||
pi.init_colors()
|
||||
pi.body.set_stroke(color, width = 0.5)
|
||||
pi.body.set_stroke(color, width=0.5)
|
||||
pi.set_color(color)
|
||||
|
||||
def get_seed_shape(self):
|
||||
return Randolph(mode = "shruggie")
|
||||
return Randolph(mode="shruggie")
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
for part in subparts:
|
||||
part.rotate(2*np.pi/5, about_point = ORIGIN)
|
||||
part.rotate(2 * np.pi / 5, about_point=ORIGIN)
|
||||
PentagonalFractal.arrange_subparts(self, *subparts)
|
||||
|
||||
|
||||
class PiCreatureFractal(VMobject):
|
||||
CONFIG = {
|
||||
"order" : 7,
|
||||
"scale_val" : 2.5,
|
||||
"start_mode" : "hooray",
|
||||
"height" : 6,
|
||||
"colors" : [
|
||||
"order": 7,
|
||||
"scale_val": 2.5,
|
||||
"start_mode": "hooray",
|
||||
"height": 6,
|
||||
"colors": [
|
||||
BLUE_D, BLUE_B, MAROON_B, MAROON_D, GREY,
|
||||
YELLOW, RED, GREY_BROWN, RED, RED_E,
|
||||
],
|
||||
"random_seed" : 0,
|
||||
"stroke_width" : 0,
|
||||
"random_seed": 0,
|
||||
"stroke_width": 0,
|
||||
}
|
||||
|
||||
def init_colors(self):
|
||||
VMobject.init_colors(self)
|
||||
internal_pis = [
|
||||
@@ -200,13 +208,12 @@ class PiCreatureFractal(VMobject):
|
||||
for pi in reversed(internal_pis):
|
||||
color = random.choice(self.colors)
|
||||
pi.set_color(color)
|
||||
pi.set_stroke(color, width = 0)
|
||||
|
||||
pi.set_stroke(color, width=0)
|
||||
|
||||
def generate_points(self):
|
||||
random.seed(self.random_seed)
|
||||
modes = get_all_pi_creature_modes()
|
||||
seed = PiCreature(mode = self.start_mode)
|
||||
seed = PiCreature(mode=self.start_mode)
|
||||
seed.scale_to_fit_height(self.height)
|
||||
seed.to_edge(DOWN)
|
||||
creatures = [seed]
|
||||
@@ -216,15 +223,15 @@ class PiCreatureFractal(VMobject):
|
||||
for creature in creatures:
|
||||
for eye, vect in zip(creature.eyes, [LEFT, RIGHT]):
|
||||
new_creature = PiCreature(
|
||||
mode = random.choice(modes)
|
||||
mode=random.choice(modes)
|
||||
)
|
||||
new_creature.scale_to_fit_height(
|
||||
self.scale_val*eye.get_height()
|
||||
self.scale_val * eye.get_height()
|
||||
)
|
||||
new_creature.next_to(
|
||||
eye, vect,
|
||||
buff = 0,
|
||||
aligned_edge = DOWN
|
||||
eye, vect,
|
||||
buff=0,
|
||||
aligned_edge=DOWN
|
||||
)
|
||||
new_creatures.append(new_creature)
|
||||
creature.look_at(random.choice(new_creatures))
|
||||
@@ -235,70 +242,77 @@ class PiCreatureFractal(VMobject):
|
||||
# VMobject.init_colors(self)
|
||||
# self.set_color_by_gradient(*self.colors)
|
||||
|
||||
|
||||
class WonkyHexagonFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts" : 7
|
||||
"num_subparts": 7
|
||||
}
|
||||
|
||||
def get_seed_shape(self):
|
||||
return RegularPolygon(n=6)
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
for i, piece in enumerate(subparts):
|
||||
piece.rotate(i*np.pi/12, about_point = ORIGIN)
|
||||
piece.rotate(i * np.pi / 12, about_point=ORIGIN)
|
||||
p1, p2, p3, p4, p5, p6, p7 = subparts
|
||||
center_row = VGroup(p1, p4, p7)
|
||||
center_row.arrange_submobjects(RIGHT, buff = 0)
|
||||
center_row.arrange_submobjects(RIGHT, buff=0)
|
||||
for p in p2, p3, p5, p6:
|
||||
p.scale_to_fit_width(p1.get_width())
|
||||
p2.move_to(p1.get_top(), DOWN+LEFT)
|
||||
p3.move_to(p1.get_bottom(), UP+LEFT)
|
||||
p5.move_to(p4.get_top(), DOWN+LEFT)
|
||||
p6.move_to(p4.get_bottom(), UP+LEFT)
|
||||
p2.move_to(p1.get_top(), DOWN + LEFT)
|
||||
p3.move_to(p1.get_bottom(), UP + LEFT)
|
||||
p5.move_to(p4.get_top(), DOWN + LEFT)
|
||||
p6.move_to(p4.get_bottom(), UP + LEFT)
|
||||
|
||||
|
||||
class CircularFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts" : 3,
|
||||
"colors" : [GREEN, BLUE, GREY]
|
||||
"num_subparts": 3,
|
||||
"colors": [GREEN, BLUE, GREY]
|
||||
}
|
||||
|
||||
def get_seed_shape(self):
|
||||
return Circle()
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
if not hasattr(self, "been_here"):
|
||||
self.num_subparts = 3+self.order
|
||||
self.num_subparts = 3 + self.order
|
||||
self.been_here = True
|
||||
for i, part in enumerate(subparts):
|
||||
theta = np.pi/self.num_subparts
|
||||
theta = np.pi / self.num_subparts
|
||||
part.next_to(
|
||||
ORIGIN, UP,
|
||||
buff = self.height/(2*np.tan(theta))
|
||||
buff=self.height / (2 * np.tan(theta))
|
||||
)
|
||||
part.rotate(i*2*np.pi/self.num_subparts, about_point = ORIGIN)
|
||||
part.rotate(i * 2 * np.pi / self.num_subparts, about_point=ORIGIN)
|
||||
self.num_subparts -= 1
|
||||
|
||||
######## Space filling curves ############
|
||||
|
||||
|
||||
class JaggedCurvePiece(VMobject):
|
||||
def insert_n_anchor_points(self, n):
|
||||
if self.get_num_anchor_points() == 0:
|
||||
self.points = np.zeros((1, 3))
|
||||
anchors = self.get_anchors()
|
||||
indices = np.linspace(0, len(anchors)-1, n+len(anchors)).astype('int')
|
||||
indices = np.linspace(0, len(anchors) - 1, n +
|
||||
len(anchors)).astype('int')
|
||||
self.set_points_as_corners(anchors[indices])
|
||||
|
||||
|
||||
class FractalCurve(VMobject):
|
||||
CONFIG = {
|
||||
"radius" : 3,
|
||||
"order" : 5,
|
||||
"colors" : [RED, GREEN],
|
||||
"num_submobjects" : 20,
|
||||
"monochromatic" : False,
|
||||
"order_to_stroke_width_map" : {
|
||||
3 : 3,
|
||||
4 : 2,
|
||||
5 : 1,
|
||||
"radius": 3,
|
||||
"order": 5,
|
||||
"colors": [RED, GREEN],
|
||||
"num_submobjects": 20,
|
||||
"monochromatic": False,
|
||||
"order_to_stroke_width_map": {
|
||||
3: 3,
|
||||
4: 2,
|
||||
5: 1,
|
||||
},
|
||||
"propagate_style_to_family" : True,
|
||||
"propagate_style_to_family": True,
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
@@ -319,19 +333,20 @@ class FractalCurve(VMobject):
|
||||
self.set_color_by_gradient(*self.colors)
|
||||
for order in sorted(self.order_to_stroke_width_map.keys()):
|
||||
if self.order >= order:
|
||||
self.set_stroke(width = self.order_to_stroke_width_map[order])
|
||||
self.set_stroke(width=self.order_to_stroke_width_map[order])
|
||||
|
||||
def get_anchor_points(self):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
class LindenmayerCurve(FractalCurve):
|
||||
CONFIG = {
|
||||
"axiom" : "A",
|
||||
"rule" : {},
|
||||
"scale_factor" : 2,
|
||||
"radius" : 3,
|
||||
"start_step" : RIGHT,
|
||||
"angle" : np.pi/2,
|
||||
"axiom": "A",
|
||||
"rule": {},
|
||||
"scale_factor": 2,
|
||||
"radius": 3,
|
||||
"start_step": RIGHT,
|
||||
"angle": np.pi / 2,
|
||||
}
|
||||
|
||||
def expand_command_string(self, command):
|
||||
@@ -350,7 +365,7 @@ class LindenmayerCurve(FractalCurve):
|
||||
return result
|
||||
|
||||
def get_anchor_points(self):
|
||||
step = float(self.radius) * self.start_step
|
||||
step = float(self.radius) * self.start_step
|
||||
step /= (self.scale_factor**self.order)
|
||||
curr = np.zeros(3)
|
||||
result = [curr]
|
||||
@@ -364,14 +379,16 @@ class LindenmayerCurve(FractalCurve):
|
||||
result.append(curr)
|
||||
return np.array(result) - center_of_mass(result)
|
||||
|
||||
|
||||
class SelfSimilarSpaceFillingCurve(FractalCurve):
|
||||
CONFIG = {
|
||||
"offsets" : [],
|
||||
#keys must awkwardly be in string form...
|
||||
"offset_to_rotation_axis" : {},
|
||||
"scale_factor" : 2,
|
||||
"radius_scale_factor" : 0.5,
|
||||
"offsets": [],
|
||||
# keys must awkwardly be in string form...
|
||||
"offset_to_rotation_axis": {},
|
||||
"scale_factor": 2,
|
||||
"radius_scale_factor": 0.5,
|
||||
}
|
||||
|
||||
def transform(self, points, offset):
|
||||
"""
|
||||
How to transform the copy of points shifted by
|
||||
@@ -380,11 +397,11 @@ class SelfSimilarSpaceFillingCurve(FractalCurve):
|
||||
copy = np.array(points)
|
||||
if str(offset) in self.offset_to_rotation_axis:
|
||||
copy = rotate(
|
||||
copy,
|
||||
axis = self.offset_to_rotation_axis[str(offset)]
|
||||
copy,
|
||||
axis=self.offset_to_rotation_axis[str(offset)]
|
||||
)
|
||||
copy /= self.scale_factor,
|
||||
copy += offset*self.radius*self.radius_scale_factor
|
||||
copy += offset * self.radius * self.radius_scale_factor
|
||||
return copy
|
||||
|
||||
def refine_into_subparts(self, points):
|
||||
@@ -393,11 +410,10 @@ class SelfSimilarSpaceFillingCurve(FractalCurve):
|
||||
for offset in self.offsets
|
||||
]
|
||||
return reduce(
|
||||
lambda a, b : np.append(a, b, axis = 0),
|
||||
lambda a, b: np.append(a, b, axis=0),
|
||||
transformed_copies
|
||||
)
|
||||
|
||||
|
||||
def get_anchor_points(self):
|
||||
points = np.zeros((1, 3))
|
||||
for count in range(self.order):
|
||||
@@ -407,95 +423,100 @@ class SelfSimilarSpaceFillingCurve(FractalCurve):
|
||||
def generate_grid(self):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
class HilbertCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"offsets" : [
|
||||
LEFT+DOWN,
|
||||
LEFT+UP,
|
||||
RIGHT+UP,
|
||||
RIGHT+DOWN,
|
||||
"offsets": [
|
||||
LEFT + DOWN,
|
||||
LEFT + UP,
|
||||
RIGHT + UP,
|
||||
RIGHT + DOWN,
|
||||
],
|
||||
"offset_to_rotation_axis" : {
|
||||
str(LEFT+DOWN) : RIGHT+UP,
|
||||
str(RIGHT+DOWN) : RIGHT+DOWN,
|
||||
"offset_to_rotation_axis": {
|
||||
str(LEFT + DOWN): RIGHT + UP,
|
||||
str(RIGHT + DOWN): RIGHT + DOWN,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class HilbertCurve3D(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"offsets" : [
|
||||
RIGHT+DOWN+IN,
|
||||
LEFT+DOWN+IN,
|
||||
LEFT+DOWN+OUT,
|
||||
RIGHT+DOWN+OUT,
|
||||
RIGHT+UP+OUT,
|
||||
LEFT+UP+OUT,
|
||||
LEFT+UP+IN,
|
||||
RIGHT+UP+IN,
|
||||
"offsets": [
|
||||
RIGHT + DOWN + IN,
|
||||
LEFT + DOWN + IN,
|
||||
LEFT + DOWN + OUT,
|
||||
RIGHT + DOWN + OUT,
|
||||
RIGHT + UP + OUT,
|
||||
LEFT + UP + OUT,
|
||||
LEFT + UP + IN,
|
||||
RIGHT + UP + IN,
|
||||
],
|
||||
"offset_to_rotation_axis_and_angle" : {
|
||||
str(RIGHT+DOWN+IN) : (LEFT+UP+OUT , 2*np.pi/3),
|
||||
str(LEFT+DOWN+IN) : (RIGHT+DOWN+IN, 2*np.pi/3),
|
||||
str(LEFT+DOWN+OUT) : (RIGHT+DOWN+IN, 2*np.pi/3),
|
||||
str(RIGHT+DOWN+OUT) : (UP , np.pi ),
|
||||
str(RIGHT+UP+OUT) : (UP , np.pi ),
|
||||
str(LEFT+UP+OUT) : (LEFT+DOWN+OUT, 2*np.pi/3),
|
||||
str(LEFT+UP+IN) : (LEFT+DOWN+OUT, 2*np.pi/3),
|
||||
str(RIGHT+UP+IN) : (RIGHT+UP+IN , 2*np.pi/3),
|
||||
"offset_to_rotation_axis_and_angle": {
|
||||
str(RIGHT + DOWN + IN): (LEFT + UP + OUT, 2 * np.pi / 3),
|
||||
str(LEFT + DOWN + IN): (RIGHT + DOWN + IN, 2 * np.pi / 3),
|
||||
str(LEFT + DOWN + OUT): (RIGHT + DOWN + IN, 2 * np.pi / 3),
|
||||
str(RIGHT + DOWN + OUT): (UP, np.pi),
|
||||
str(RIGHT + UP + OUT): (UP, np.pi),
|
||||
str(LEFT + UP + OUT): (LEFT + DOWN + OUT, 2 * np.pi / 3),
|
||||
str(LEFT + UP + IN): (LEFT + DOWN + OUT, 2 * np.pi / 3),
|
||||
str(RIGHT + UP + IN): (RIGHT + UP + IN, 2 * np.pi / 3),
|
||||
},
|
||||
}
|
||||
# Rewrote transform method to include the rotation angle
|
||||
|
||||
def transform(self, points, offset):
|
||||
copy = np.array(points)
|
||||
copy = rotate(
|
||||
copy,
|
||||
axis = self.offset_to_rotation_axis_and_angle[str(offset)][0],
|
||||
angle = self.offset_to_rotation_axis_and_angle[str(offset)][1],
|
||||
copy,
|
||||
axis=self.offset_to_rotation_axis_and_angle[str(offset)][0],
|
||||
angle=self.offset_to_rotation_axis_and_angle[str(offset)][1],
|
||||
)
|
||||
copy /= self.scale_factor,
|
||||
copy += offset*self.radius*self.radius_scale_factor
|
||||
copy += offset * self.radius * self.radius_scale_factor
|
||||
return copy
|
||||
|
||||
|
||||
class PeanoCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"colors" : [PURPLE, TEAL],
|
||||
"offsets" : [
|
||||
LEFT+DOWN,
|
||||
"colors": [PURPLE, TEAL],
|
||||
"offsets": [
|
||||
LEFT + DOWN,
|
||||
LEFT,
|
||||
LEFT+UP,
|
||||
LEFT + UP,
|
||||
UP,
|
||||
ORIGIN,
|
||||
DOWN,
|
||||
RIGHT+DOWN,
|
||||
RIGHT + DOWN,
|
||||
RIGHT,
|
||||
RIGHT+UP,
|
||||
RIGHT + UP,
|
||||
],
|
||||
"offset_to_rotation_axis" : {
|
||||
str(LEFT) : UP,
|
||||
str(UP) : RIGHT,
|
||||
str(ORIGIN) : LEFT+UP,
|
||||
str(DOWN) : RIGHT,
|
||||
str(RIGHT) : UP,
|
||||
"offset_to_rotation_axis": {
|
||||
str(LEFT): UP,
|
||||
str(UP): RIGHT,
|
||||
str(ORIGIN): LEFT + UP,
|
||||
str(DOWN): RIGHT,
|
||||
str(RIGHT): UP,
|
||||
},
|
||||
"scale_factor" : 3,
|
||||
"radius_scale_factor" : 2.0/3,
|
||||
"scale_factor": 3,
|
||||
"radius_scale_factor": 2.0 / 3,
|
||||
}
|
||||
|
||||
|
||||
class TriangleFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"colors" : [MAROON, YELLOW],
|
||||
"offsets" : [
|
||||
LEFT/4.+DOWN/6.,
|
||||
"colors": [MAROON, YELLOW],
|
||||
"offsets": [
|
||||
LEFT / 4. + DOWN / 6.,
|
||||
ORIGIN,
|
||||
RIGHT/4.+DOWN/6.,
|
||||
UP/3.,
|
||||
RIGHT / 4. + DOWN / 6.,
|
||||
UP / 3.,
|
||||
],
|
||||
"offset_to_rotation_axis" : {
|
||||
"offset_to_rotation_axis": {
|
||||
str(ORIGIN): RIGHT,
|
||||
str(UP/3.) : UP,
|
||||
str(UP / 3.): UP,
|
||||
},
|
||||
"scale_factor" : 2,
|
||||
"radius_scale_factor" : 1.5,
|
||||
"scale_factor": 2,
|
||||
"radius_scale_factor": 1.5,
|
||||
}
|
||||
|
||||
# class HexagonFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||
@@ -505,7 +526,7 @@ class TriangleFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||
# "axis_offset_pairs" : [
|
||||
# (None, 1.5*DOWN + 0.5*np.sqrt(3)*LEFT),
|
||||
# (UP+np.sqrt(3)*RIGHT, 1.5*DOWN + 0.5*np.sqrt(3)*RIGHT),
|
||||
# (np.sqrt(3)*UP+RIGHT, ORIGIN),
|
||||
# (np.sqrt(3)*UP+RIGHT, ORIGIN),
|
||||
# ((UP, RIGHT), np.sqrt(3)*LEFT),
|
||||
# (None, 1.5*UP + 0.5*np.sqrt(3)*LEFT),
|
||||
# (None, 1.5*UP + 0.5*np.sqrt(3)*RIGHT),
|
||||
@@ -524,135 +545,132 @@ class TriangleFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||
|
||||
class UtahFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"colors" : [WHITE, BLUE_D],
|
||||
"axis_offset_pairs" : [
|
||||
"colors": [WHITE, BLUE_D],
|
||||
"axis_offset_pairs": [
|
||||
|
||||
],
|
||||
"scale_factor" : 3,
|
||||
"radius_scale_factor" : 2/(3*np.sqrt(3)),
|
||||
"scale_factor": 3,
|
||||
"radius_scale_factor": 2 / (3 * np.sqrt(3)),
|
||||
}
|
||||
|
||||
|
||||
class FlowSnake(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors" : [YELLOW, GREEN],
|
||||
"axiom" : "A",
|
||||
"rule" : {
|
||||
"A" : "A-B--B+A++AA+B-",
|
||||
"B" : "+A-BB--B-A++A+B",
|
||||
"colors": [YELLOW, GREEN],
|
||||
"axiom": "A",
|
||||
"rule": {
|
||||
"A": "A-B--B+A++AA+B-",
|
||||
"B": "+A-BB--B-A++A+B",
|
||||
},
|
||||
"radius" : 6, #TODO, this is innaccurate
|
||||
"scale_factor" : np.sqrt(7),
|
||||
"start_step" : RIGHT,
|
||||
"angle" : -np.pi/3,
|
||||
"radius": 6, # TODO, this is innaccurate
|
||||
"scale_factor": np.sqrt(7),
|
||||
"start_step": RIGHT,
|
||||
"angle": -np.pi / 3,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
LindenmayerCurve.__init__(self, **kwargs)
|
||||
self.rotate(-self.order*np.pi/9, about_point = ORIGIN)
|
||||
self.rotate(-self.order * np.pi / 9, about_point=ORIGIN)
|
||||
|
||||
|
||||
class SierpinskiCurve(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors" : [RED, WHITE],
|
||||
"axiom" : "B",
|
||||
"rule" : {
|
||||
"A" : "+B-A-B+",
|
||||
"B" : "-A+B+A-",
|
||||
"colors": [RED, WHITE],
|
||||
"axiom": "B",
|
||||
"rule": {
|
||||
"A": "+B-A-B+",
|
||||
"B": "-A+B+A-",
|
||||
},
|
||||
"radius" : 6, #TODO, this is innaccurate
|
||||
"scale_factor" : 2,
|
||||
"start_step" : RIGHT,
|
||||
"angle" : -np.pi/3,
|
||||
"radius": 6, # TODO, this is innaccurate
|
||||
"scale_factor": 2,
|
||||
"start_step": RIGHT,
|
||||
"angle": -np.pi / 3,
|
||||
}
|
||||
|
||||
|
||||
class KochSnowFlake(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors" : [BLUE_D, WHITE, BLUE_D],
|
||||
"axiom" : "A--A--A--",
|
||||
"rule" : {
|
||||
"A" : "A+A--A+A"
|
||||
"colors": [BLUE_D, WHITE, BLUE_D],
|
||||
"axiom": "A--A--A--",
|
||||
"rule": {
|
||||
"A": "A+A--A+A"
|
||||
},
|
||||
"radius" : 4,
|
||||
"scale_factor" : 3,
|
||||
"start_step" : RIGHT,
|
||||
"angle" : np.pi/3,
|
||||
"order_to_stroke_width_map" : {
|
||||
3 : 3,
|
||||
5 : 2,
|
||||
6 : 1,
|
||||
"radius": 4,
|
||||
"scale_factor": 3,
|
||||
"start_step": RIGHT,
|
||||
"angle": np.pi / 3,
|
||||
"order_to_stroke_width_map": {
|
||||
3: 3,
|
||||
5: 2,
|
||||
6: 1,
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.scale_factor = 2*(1+np.cos(self.angle))
|
||||
self.scale_factor = 2 * (1 + np.cos(self.angle))
|
||||
LindenmayerCurve.__init__(self, **kwargs)
|
||||
|
||||
|
||||
class KochCurve(KochSnowFlake):
|
||||
CONFIG = {
|
||||
"axiom" : "A--"
|
||||
"axiom": "A--"
|
||||
}
|
||||
|
||||
|
||||
class QuadraticKoch(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors" : [YELLOW, WHITE, MAROON_B],
|
||||
"axiom" : "A",
|
||||
"rule" : {
|
||||
"A" : "A+A-A-AA+A+A-A"
|
||||
"colors": [YELLOW, WHITE, MAROON_B],
|
||||
"axiom": "A",
|
||||
"rule": {
|
||||
"A": "A+A-A-AA+A+A-A"
|
||||
},
|
||||
"radius" : 4,
|
||||
"scale_factor" : 4,
|
||||
"start_step" : RIGHT,
|
||||
"angle" : np.pi/2
|
||||
"radius": 4,
|
||||
"scale_factor": 4,
|
||||
"start_step": RIGHT,
|
||||
"angle": np.pi / 2
|
||||
}
|
||||
|
||||
|
||||
class QuadraticKochIsland(QuadraticKoch):
|
||||
CONFIG = {
|
||||
"axiom" : "A+A+A+A"
|
||||
"axiom": "A+A+A+A"
|
||||
}
|
||||
|
||||
|
||||
class StellarCurve(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"start_color" : RED,
|
||||
"end_color" : BLUE_E,
|
||||
"rule" : {
|
||||
"A" : "+B-A-B+A-B+",
|
||||
"B" : "-A+B+A-B+A-",
|
||||
"start_color": RED,
|
||||
"end_color": BLUE_E,
|
||||
"rule": {
|
||||
"A": "+B-A-B+A-B+",
|
||||
"B": "-A+B+A-B+A-",
|
||||
},
|
||||
"scale_factor" : 3,
|
||||
"angle" : 2*np.pi/5,
|
||||
"scale_factor": 3,
|
||||
"angle": 2 * np.pi / 5,
|
||||
}
|
||||
|
||||
|
||||
class SnakeCurve(FractalCurve):
|
||||
CONFIG = {
|
||||
"start_color" : BLUE,
|
||||
"end_color" : YELLOW,
|
||||
"start_color": BLUE,
|
||||
"end_color": YELLOW,
|
||||
}
|
||||
|
||||
def get_anchor_points(self):
|
||||
result = []
|
||||
resolution = 2**self.order
|
||||
step = 2.0*self.radius / resolution
|
||||
step = 2.0 * self.radius / resolution
|
||||
lower_left = ORIGIN + \
|
||||
LEFT*(self.radius - step/2) + \
|
||||
DOWN*(self.radius - step/2)
|
||||
LEFT * (self.radius - step / 2) + \
|
||||
DOWN * (self.radius - step / 2)
|
||||
|
||||
for y in range(resolution):
|
||||
x_range = range(resolution)
|
||||
if y%2 == 0:
|
||||
if y % 2 == 0:
|
||||
x_range.reverse()
|
||||
for x in x_range:
|
||||
result.append(
|
||||
lower_left + x*step*RIGHT + y*step*UP
|
||||
lower_left + x * step * RIGHT + y * step * UP
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ import itertools as it
|
||||
import numpy as np
|
||||
import operator as op
|
||||
|
||||
from random import random
|
||||
|
||||
from constants import *
|
||||
|
||||
from scene.scene import Scene
|
||||
@@ -13,14 +11,14 @@ from utils.space_ops import center_of_mass
|
||||
|
||||
class Graph():
|
||||
def __init__(self):
|
||||
#List of points in R^3
|
||||
vertices = []
|
||||
#List of pairs of indices of vertices
|
||||
edges = []
|
||||
#List of tuples of indices of vertices. The last should
|
||||
#be a cycle whose interior is the entire graph, and when
|
||||
#regions are computed its complement will be taken.
|
||||
region_cycles = []
|
||||
# List of points in R^3
|
||||
vertices = []
|
||||
# List of pairs of indices of vertices
|
||||
edges = []
|
||||
# List of tuples of indices of vertices. The last should
|
||||
# be a cycle whose interior is the entire graph, and when
|
||||
# regions are computed its complement will be taken.
|
||||
region_cycles = []
|
||||
|
||||
self.construct()
|
||||
|
||||
@@ -30,18 +28,20 @@ class Graph():
|
||||
def __str__(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
|
||||
class CubeGraph(Graph):
|
||||
"""
|
||||
5 7
|
||||
12
|
||||
03
|
||||
12
|
||||
03
|
||||
4 6
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
self.vertices = [
|
||||
(x, y, 0)
|
||||
for r in (1, 2)
|
||||
for x, y in it.product([-r,r], [-r, r])
|
||||
for x, y in it.product([-r, r], [-r, r])
|
||||
]
|
||||
self.edges = [
|
||||
(0, 1),
|
||||
@@ -63,9 +63,10 @@ class CubeGraph(Graph):
|
||||
[4, 6, 2, 0],
|
||||
[6, 7, 3, 2],
|
||||
[7, 5, 1, 3],
|
||||
[4, 6, 7, 5],#By convention, last region will be "outside"
|
||||
[4, 6, 7, 5], # By convention, last region will be "outside"
|
||||
]
|
||||
|
||||
|
||||
class SampleGraph(Graph):
|
||||
"""
|
||||
4 2 3 8
|
||||
@@ -73,17 +74,18 @@ class SampleGraph(Graph):
|
||||
7
|
||||
5 6
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
self.vertices = [
|
||||
( 0, 0, 0),
|
||||
( 2, 0, 0),
|
||||
( 1, 2, 0),
|
||||
( 3, 2, 0),
|
||||
(0, 0, 0),
|
||||
(2, 0, 0),
|
||||
(1, 2, 0),
|
||||
(3, 2, 0),
|
||||
(-1, 2, 0),
|
||||
(-2,-2, 0),
|
||||
( 2,-2, 0),
|
||||
( 4,-1, 0),
|
||||
( 6, 2, 0),
|
||||
(-2, -2, 0),
|
||||
(2, -2, 0),
|
||||
(4, -1, 0),
|
||||
(6, 2, 0),
|
||||
]
|
||||
self.edges = [
|
||||
(0, 1),
|
||||
@@ -113,19 +115,21 @@ class SampleGraph(Graph):
|
||||
(4, 5, 6, 7, 8, 3, 2),
|
||||
]
|
||||
|
||||
|
||||
class OctohedronGraph(Graph):
|
||||
"""
|
||||
3
|
||||
|
||||
|
||||
1 0
|
||||
2
|
||||
4 5
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
self.vertices = [
|
||||
(r*np.cos(angle), r*np.sin(angle)-1, 0)
|
||||
(r * np.cos(angle), r * np.sin(angle) - 1, 0)
|
||||
for r, s in [(1, 0), (3, 3)]
|
||||
for angle in (np.pi/6) * np.array([s, 4 + s, 8 + s])
|
||||
for angle in (np.pi / 6) * np.array([s, 4 + s, 8 + s])
|
||||
]
|
||||
self.edges = [
|
||||
(0, 1),
|
||||
@@ -152,35 +156,38 @@ class OctohedronGraph(Graph):
|
||||
(3, 4, 5),
|
||||
]
|
||||
|
||||
|
||||
class CompleteGraph(Graph):
|
||||
def __init__(self, num_vertices, radius = 3):
|
||||
def __init__(self, num_vertices, radius=3):
|
||||
self.num_vertices = num_vertices
|
||||
self.radius = radius
|
||||
Graph.__init__(self)
|
||||
|
||||
def construct(self):
|
||||
self.vertices = [
|
||||
(self.radius*np.cos(theta), self.radius*np.sin(theta), 0)
|
||||
(self.radius * np.cos(theta), self.radius * np.sin(theta), 0)
|
||||
for x in range(self.num_vertices)
|
||||
for theta in [2*np.pi*x / self.num_vertices]
|
||||
for theta in [2 * np.pi * x / self.num_vertices]
|
||||
]
|
||||
self.edges = it.combinations(range(self.num_vertices), 2)
|
||||
|
||||
def __str__(self):
|
||||
return Graph.__str__(self) + str(self.num_vertices)
|
||||
|
||||
|
||||
class GraphScene(Scene):
|
||||
args_list = [
|
||||
(CubeGraph(),),
|
||||
(SampleGraph(),),
|
||||
(OctohedronGraph(),),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def args_to_string(*args):
|
||||
return str(args[0])
|
||||
|
||||
def __init__(self, graph, *args, **kwargs):
|
||||
#See CubeGraph() above for format of graph
|
||||
# See CubeGraph() above for format of graph
|
||||
self.graph = graph
|
||||
Scene.__init__(self, *args, **kwargs)
|
||||
|
||||
@@ -198,19 +205,19 @@ class GraphScene(Scene):
|
||||
self.region_from_cycle(cycle)
|
||||
for cycle in self.graph.region_cycles
|
||||
]
|
||||
regions[-1].complement()#Outer region painted outwardly...
|
||||
regions[-1].complement() # Outer region painted outwardly...
|
||||
self.regions = regions
|
||||
|
||||
def region_from_cycle(self, cycle):
|
||||
point_pairs = [
|
||||
[
|
||||
self.points[cycle[i]],
|
||||
self.points[cycle[(i+1)%len(cycle)]]
|
||||
self.points[cycle[i]],
|
||||
self.points[cycle[(i + 1) % len(cycle)]]
|
||||
]
|
||||
for i in range(len(cycle))
|
||||
]
|
||||
return region_from_line_boundary(
|
||||
*point_pairs, shape = self.shape
|
||||
*point_pairs, shape=self.shape
|
||||
)
|
||||
|
||||
def draw_vertices(self, **kwargs):
|
||||
@@ -219,7 +226,7 @@ class GraphScene(Scene):
|
||||
|
||||
def draw_edges(self):
|
||||
self.play(*[
|
||||
ShowCreation(edge, run_time = 1.0)
|
||||
ShowCreation(edge, run_time=1.0)
|
||||
for edge in self.edges
|
||||
])
|
||||
|
||||
@@ -227,17 +234,16 @@ class GraphScene(Scene):
|
||||
self.remove(*self.vertices)
|
||||
start = Mobject(*self.vertices)
|
||||
end = Mobject(*[
|
||||
Dot(point, radius = 3*Dot.DEFAULT_RADIUS, color = "lightgreen")
|
||||
Dot(point, radius=3 * Dot.DEFAULT_RADIUS, color="lightgreen")
|
||||
for point in self.points
|
||||
])
|
||||
self.play(Transform(
|
||||
start, end, rate_func = there_and_back,
|
||||
start, end, rate_func=there_and_back,
|
||||
**kwargs
|
||||
))
|
||||
self.remove(start)
|
||||
self.add(*self.vertices)
|
||||
|
||||
|
||||
def replace_vertices_with(self, mobject):
|
||||
mobject.center()
|
||||
diameter = max(mobject.get_height(), mobject.get_width())
|
||||
@@ -255,7 +261,7 @@ class GraphScene(Scene):
|
||||
for edge in self.edges
|
||||
])
|
||||
|
||||
def annotate_edges(self, mobject, fade_in = True, **kwargs):
|
||||
def annotate_edges(self, mobject, fade_in=True, **kwargs):
|
||||
angles = map(np.arctan, map(Line.get_slope, self.edges))
|
||||
self.edge_annotations = [
|
||||
mobject.copy().rotate(angle).move_to(edge.get_center())
|
||||
@@ -267,22 +273,22 @@ class GraphScene(Scene):
|
||||
for ann in self.edge_annotations
|
||||
])
|
||||
|
||||
def trace_cycle(self, cycle = None, color = "yellow", run_time = 2.0):
|
||||
def trace_cycle(self, cycle=None, color="yellow", run_time=2.0):
|
||||
if cycle == None:
|
||||
cycle = self.graph.region_cycles[0]
|
||||
time_per_edge = run_time / len(cycle)
|
||||
next_in_cycle = it.cycle(cycle)
|
||||
next_in_cycle.next()#jump one ahead
|
||||
next_in_cycle.next() # jump one ahead
|
||||
self.traced_cycle = Mobject(*[
|
||||
Line(self.points[i], self.points[j]).set_color(color)
|
||||
for i, j in zip(cycle, next_in_cycle)
|
||||
])
|
||||
self.play(
|
||||
ShowCreation(self.traced_cycle),
|
||||
run_time = run_time
|
||||
ShowCreation(self.traced_cycle),
|
||||
run_time=run_time
|
||||
)
|
||||
|
||||
def generate_spanning_tree(self, root = 0, color = "yellow"):
|
||||
def generate_spanning_tree(self, root=0, color="yellow"):
|
||||
self.spanning_tree_root = 0
|
||||
pairs = deepcopy(self.graph.edges)
|
||||
pairs += [tuple(reversed(pair)) for pair in pairs]
|
||||
@@ -311,10 +317,10 @@ class GraphScene(Scene):
|
||||
y_sep = 2
|
||||
if not hasattr(self, "spanning_tree"):
|
||||
self.generate_spanning_tree()
|
||||
root = self.spanning_tree_root
|
||||
root = self.spanning_tree_root
|
||||
color = self.spanning_tree.get_color()
|
||||
indices = range(len(self.points))
|
||||
#Build dicts
|
||||
# Build dicts
|
||||
parent_of = dict([
|
||||
tuple(reversed(pair))
|
||||
for pair in self.spanning_tree_index_pairs
|
||||
@@ -323,12 +329,12 @@ class GraphScene(Scene):
|
||||
for child in parent_of:
|
||||
children_of[parent_of[child]].append(child)
|
||||
|
||||
x_coord_of = {root : 0}
|
||||
y_coord_of = {root : bottom}
|
||||
#width to allocate to a given node, computed as
|
||||
#the maxium number of decendents in a single generation,
|
||||
#minus 1, multiplied by x_sep
|
||||
width_of = {}
|
||||
x_coord_of = {root: 0}
|
||||
y_coord_of = {root: bottom}
|
||||
# width to allocate to a given node, computed as
|
||||
# the maxium number of decendents in a single generation,
|
||||
# minus 1, multiplied by x_sep
|
||||
width_of = {}
|
||||
for index in indices:
|
||||
next_generation = children_of[index]
|
||||
curr_max = max(1, len(next_generation))
|
||||
@@ -345,9 +351,9 @@ class GraphScene(Scene):
|
||||
if index not in y_coord_of:
|
||||
y_coord_of[index] = y_sep + y_coord_of[parent_of[index]]
|
||||
children = children_of[index]
|
||||
left_hand = x_coord_of[index]-width_of[index]/2.0
|
||||
left_hand = x_coord_of[index] - width_of[index] / 2.0
|
||||
for child in children:
|
||||
x_coord_of[child] = left_hand + width_of[child]/2.0
|
||||
x_coord_of[child] = left_hand + width_of[child] / 2.0
|
||||
left_hand += width_of[child] + x_sep
|
||||
to_process += children
|
||||
|
||||
@@ -365,7 +371,7 @@ class GraphScene(Scene):
|
||||
])
|
||||
|
||||
def generate_dual_graph(self):
|
||||
point_at_infinity = np.array([np.inf]*3)
|
||||
point_at_infinity = np.array([np.inf] * 3)
|
||||
cycles = self.graph.region_cycles
|
||||
self.dual_points = [
|
||||
center_of_mass([
|
||||
@@ -378,7 +384,8 @@ class GraphScene(Scene):
|
||||
Dot(point).set_color("green")
|
||||
for point in self.dual_points
|
||||
]
|
||||
self.dual_vertices[-1] = Circle().scale(FRAME_X_RADIUS + FRAME_Y_RADIUS)
|
||||
self.dual_vertices[-1] = Circle().scale(FRAME_X_RADIUS +
|
||||
FRAME_Y_RADIUS)
|
||||
self.dual_points[-1] = point_at_infinity
|
||||
|
||||
self.dual_edges = []
|
||||
@@ -388,43 +395,20 @@ class GraphScene(Scene):
|
||||
if not (pair[0] in cycle and pair[1] in cycle):
|
||||
continue
|
||||
index1, index2 = cycle.index(pair[0]), cycle.index(pair[1])
|
||||
if abs(index1 - index2) in [1, len(cycle)-1]:
|
||||
if abs(index1 - index2) in [1, len(cycle) - 1]:
|
||||
dual_point_pair.append(
|
||||
self.dual_points[cycles.index(cycle)]
|
||||
)
|
||||
assert(len(dual_point_pair) == 2)
|
||||
for i in 0, 1:
|
||||
if all(dual_point_pair[i] == point_at_infinity):
|
||||
new_point = np.array(dual_point_pair[1-i])
|
||||
new_point = np.array(dual_point_pair[1 - i])
|
||||
vect = center_of_mass([
|
||||
self.points[pair[0]],
|
||||
self.points[pair[1]]
|
||||
]) - new_point
|
||||
new_point += FRAME_X_RADIUS*vect/np.linalg.norm(vect)
|
||||
new_point += FRAME_X_RADIUS * vect / np.linalg.norm(vect)
|
||||
dual_point_pair[i] = new_point
|
||||
self.dual_edges.append(
|
||||
Line(*dual_point_pair).set_color()
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,34 +1,22 @@
|
||||
from constants import *
|
||||
|
||||
from mobject.geometry import AnnularSector
|
||||
from mobject.geometry import Arc
|
||||
from mobject.geometry import Annulus
|
||||
from mobject.mobject import Mobject
|
||||
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.composition import LaggedStart
|
||||
from animation.transform import ApplyMethod
|
||||
from animation.transform import Transform
|
||||
from animation.creation import FadeIn
|
||||
from animation.creation import FadeOut
|
||||
|
||||
from camera.camera import Camera
|
||||
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
|
||||
@@ -39,8 +27,8 @@ SHADOW_COLOR = BLACK
|
||||
SWITCH_ON_RUN_TIME = 1.5
|
||||
FAST_SWITCH_ON_RUN_TIME = 0.1
|
||||
NUM_LEVELS = 30
|
||||
NUM_CONES = 7 # in first lighthouse scene
|
||||
NUM_VISIBLE_CONES = 5 # ibidem
|
||||
NUM_CONES = 7 # in first lighthouse scene
|
||||
NUM_VISIBLE_CONES = 5 # ibidem
|
||||
ARC_TIP_LENGTH = 0.2
|
||||
AMBIENT_FULL = 0.8
|
||||
AMBIENT_DIMMED = 0.5
|
||||
@@ -48,11 +36,15 @@ SPOTLIGHT_FULL = 0.8
|
||||
SPOTLIGHT_DIMMED = 0.5
|
||||
LIGHTHOUSE_HEIGHT = 0.8
|
||||
|
||||
DEGREES = TAU/360
|
||||
DEGREES = TAU / 360
|
||||
|
||||
inverse_power_law = lambda maxint,scale,cutoff,exponent: \
|
||||
(lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent)
|
||||
inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2)
|
||||
|
||||
def inverse_power_law(maxint, scale, cutoff, exponent):
|
||||
return (lambda r: maxint * (cutoff / (r / scale + cutoff))**exponent)
|
||||
|
||||
|
||||
def inverse_quadratic(maxint, scale, cutoff):
|
||||
return inverse_power_law(maxint, scale, cutoff, 2)
|
||||
|
||||
|
||||
class SwitchOn(LaggedStart):
|
||||
@@ -62,12 +54,14 @@ class SwitchOn(LaggedStart):
|
||||
}
|
||||
|
||||
def __init__(self, light, **kwargs):
|
||||
if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)):
|
||||
raise Exception("Only AmbientLights and Spotlights can be switched on")
|
||||
if (not isinstance(light, AmbientLight) and not isinstance(light, Spotlight)):
|
||||
raise Exception(
|
||||
"Only AmbientLights and Spotlights can be switched on")
|
||||
LaggedStart.__init__(
|
||||
self, FadeIn, light, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class SwitchOff(LaggedStart):
|
||||
CONFIG = {
|
||||
"lag_ratio": 0.2,
|
||||
@@ -75,23 +69,26 @@ class SwitchOff(LaggedStart):
|
||||
}
|
||||
|
||||
def __init__(self, light, **kwargs):
|
||||
if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)):
|
||||
raise Exception("Only AmbientLights and Spotlights can be switched off")
|
||||
if (not isinstance(light, AmbientLight) and not isinstance(light, Spotlight)):
|
||||
raise Exception(
|
||||
"Only AmbientLights and Spotlights can be switched off")
|
||||
light.submobjects = light.submobjects[::-1]
|
||||
LaggedStart.__init__(self,
|
||||
FadeOut, light, **kwargs)
|
||||
FadeOut, light, **kwargs)
|
||||
light.submobjects = light.submobjects[::-1]
|
||||
|
||||
|
||||
class Lighthouse(SVGMobject):
|
||||
CONFIG = {
|
||||
"file_name" : "lighthouse",
|
||||
"height" : LIGHTHOUSE_HEIGHT,
|
||||
"fill_color" : WHITE,
|
||||
"fill_opacity" : 1.0,
|
||||
"file_name": "lighthouse",
|
||||
"height": LIGHTHOUSE_HEIGHT,
|
||||
"fill_color": WHITE,
|
||||
"fill_opacity": 1.0,
|
||||
}
|
||||
|
||||
def move_to(self,point):
|
||||
self.next_to(point, DOWN, buff = 0)
|
||||
def move_to(self, point):
|
||||
self.next_to(point, DOWN, buff=0)
|
||||
|
||||
|
||||
class AmbientLight(VMobject):
|
||||
|
||||
@@ -104,18 +101,18 @@ class AmbientLight(VMobject):
|
||||
# * the number of subdivisions (levels, annuli)
|
||||
|
||||
CONFIG = {
|
||||
"source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0),
|
||||
"opacity_function" : lambda r : 1.0/(r+1.0)**2,
|
||||
"color" : LIGHT_COLOR,
|
||||
"max_opacity" : 1.0,
|
||||
"num_levels" : NUM_LEVELS,
|
||||
"radius" : 5.0
|
||||
"source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0),
|
||||
"opacity_function": lambda r: 1.0 / (r + 1.0)**2,
|
||||
"color": LIGHT_COLOR,
|
||||
"max_opacity": 1.0,
|
||||
"num_levels": NUM_LEVELS,
|
||||
"radius": 5.0
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
# in theory, this method is only called once, right?
|
||||
# so removing submobs shd not be necessary
|
||||
#
|
||||
#
|
||||
# Note: Usually, yes, it is only called within Mobject.__init__,
|
||||
# but there is no strong guarantee of that, and you may want certain
|
||||
# update functions to regenerate points here and there.
|
||||
@@ -130,71 +127,61 @@ class AmbientLight(VMobject):
|
||||
for r in np.arange(0, self.radius, dr):
|
||||
alpha = self.max_opacity * self.opacity_function(r)
|
||||
annulus = Annulus(
|
||||
inner_radius = r,
|
||||
outer_radius = r + dr,
|
||||
color = self.color,
|
||||
fill_opacity = alpha
|
||||
inner_radius=r,
|
||||
outer_radius=r + dr,
|
||||
color=self.color,
|
||||
fill_opacity=alpha
|
||||
)
|
||||
annulus.move_to(self.get_source_point())
|
||||
self.add(annulus)
|
||||
|
||||
|
||||
|
||||
def move_source_to(self,point):
|
||||
#old_source_point = self.get_source_point()
|
||||
#self.shift(point - old_source_point)
|
||||
def move_source_to(self, point):
|
||||
# old_source_point = self.get_source_point()
|
||||
# self.shift(point - old_source_point)
|
||||
self.move_to(point)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_source_point(self):
|
||||
return self.source_point.get_location()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def dimming(self,new_alpha):
|
||||
def dimming(self, new_alpha):
|
||||
old_alpha = self.max_opacity
|
||||
self.max_opacity = new_alpha
|
||||
for submob in self.submobjects:
|
||||
old_submob_alpha = submob.fill_opacity
|
||||
new_submob_alpha = old_submob_alpha * new_alpha / old_alpha
|
||||
submob.set_fill(opacity = new_submob_alpha)
|
||||
submob.set_fill(opacity=new_submob_alpha)
|
||||
|
||||
|
||||
class Spotlight(VMobject):
|
||||
CONFIG = {
|
||||
"source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0),
|
||||
"opacity_function" : lambda r : 1.0/(r/2+1.0)**2,
|
||||
"color" : GREEN, # LIGHT_COLOR,
|
||||
"max_opacity" : 1.0,
|
||||
"num_levels" : 10,
|
||||
"radius" : 10.0,
|
||||
"screen" : None,
|
||||
"source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0),
|
||||
"opacity_function": lambda r: 1.0 / (r / 2 + 1.0)**2,
|
||||
"color": GREEN, # LIGHT_COLOR,
|
||||
"max_opacity": 1.0,
|
||||
"num_levels": 10,
|
||||
"radius": 10.0,
|
||||
"screen": None,
|
||||
"camera_mob": None
|
||||
}
|
||||
|
||||
def projection_direction(self):
|
||||
# Note: This seems reasonable, though for it to work you'd
|
||||
# need to be sure that any 3d scene including a spotlight
|
||||
# somewhere assigns that spotlights "camera" attribute
|
||||
# need to be sure that any 3d scene including a spotlight
|
||||
# somewhere assigns that spotlights "camera" attribute
|
||||
# to be the camera associated with that scene.
|
||||
if self.camera_mob == None:
|
||||
if self.camera_mob is None:
|
||||
return OUT
|
||||
else:
|
||||
[phi, theta, r] = self.camera_mob.get_center()
|
||||
v = np.array([np.sin(phi)*np.cos(theta), np.sin(phi)*np.sin(theta), np.cos(phi)])
|
||||
return v #/np.linalg.norm(v)
|
||||
v = np.array([np.sin(phi) * np.cos(theta),
|
||||
np.sin(phi) * np.sin(theta), np.cos(phi)])
|
||||
return v # /np.linalg.norm(v)
|
||||
|
||||
def project(self,point):
|
||||
def project(self, point):
|
||||
v = self.projection_direction()
|
||||
w = project_along_vector(point,v)
|
||||
w = project_along_vector(point, v)
|
||||
return w
|
||||
|
||||
def get_source_point(self):
|
||||
@@ -205,7 +192,7 @@ class Spotlight(VMobject):
|
||||
|
||||
self.add(self.source_point)
|
||||
|
||||
if self.screen != None:
|
||||
if self.screen is not None:
|
||||
# look for the screen and create annular sectors
|
||||
lower_angle, upper_angle = self.viewing_angles(self.screen)
|
||||
self.radius = float(self.radius)
|
||||
@@ -213,18 +200,18 @@ class Spotlight(VMobject):
|
||||
lower_ray, upper_ray = self.viewing_rays(self.screen)
|
||||
|
||||
for r in np.arange(0, self.radius, dr):
|
||||
new_sector = self.new_sector(r,dr,lower_angle,upper_angle)
|
||||
new_sector = self.new_sector(r, dr, lower_angle, upper_angle)
|
||||
self.add(new_sector)
|
||||
|
||||
def new_sector(self,r,dr,lower_angle,upper_angle):
|
||||
def new_sector(self, r, dr, lower_angle, upper_angle):
|
||||
alpha = self.max_opacity * self.opacity_function(r)
|
||||
annular_sector = AnnularSector(
|
||||
inner_radius = r,
|
||||
outer_radius = r + dr,
|
||||
color = self.color,
|
||||
fill_opacity = alpha,
|
||||
start_angle = lower_angle,
|
||||
angle = upper_angle - lower_angle
|
||||
inner_radius=r,
|
||||
outer_radius=r + dr,
|
||||
color=self.color,
|
||||
fill_opacity=alpha,
|
||||
start_angle=lower_angle,
|
||||
angle=upper_angle - lower_angle
|
||||
)
|
||||
# rotate (not project) it into the viewing plane
|
||||
rotation_matrix = z_to_vector(self.projection_direction())
|
||||
@@ -232,13 +219,13 @@ class Spotlight(VMobject):
|
||||
# now rotate it inside that plane
|
||||
rotated_RIGHT = np.dot(RIGHT, rotation_matrix.T)
|
||||
projected_RIGHT = self.project(RIGHT)
|
||||
omega = angle_between_vectors(rotated_RIGHT,projected_RIGHT)
|
||||
annular_sector.rotate(omega, axis = self.projection_direction())
|
||||
omega = angle_between_vectors(rotated_RIGHT, projected_RIGHT)
|
||||
annular_sector.rotate(omega, axis=self.projection_direction())
|
||||
annular_sector.move_arc_center_to(self.get_source_point())
|
||||
|
||||
return annular_sector
|
||||
|
||||
def viewing_angle_of_point(self,point):
|
||||
def viewing_angle_of_point(self, point):
|
||||
# as measured from the positive x-axis
|
||||
v1 = self.project(RIGHT)
|
||||
v2 = self.project(np.array(point) - self.get_source_point())
|
||||
@@ -247,63 +234,66 @@ class Spotlight(VMobject):
|
||||
# choice of orientation. That choice is set by the camera
|
||||
# position, i. e. projection direction
|
||||
|
||||
if np.dot(self.projection_direction(),np.cross(v1, v2)) > 0:
|
||||
if np.dot(self.projection_direction(), np.cross(v1, v2)) > 0:
|
||||
return absolute_angle
|
||||
else:
|
||||
return -absolute_angle
|
||||
|
||||
def viewing_angles(self,screen):
|
||||
def viewing_angles(self, screen):
|
||||
|
||||
screen_points = screen.get_anchors()
|
||||
projected_screen_points = map(self.project,screen_points)
|
||||
projected_screen_points = map(self.project, screen_points)
|
||||
|
||||
viewing_angles = np.array(map(self.viewing_angle_of_point,
|
||||
projected_screen_points))
|
||||
projected_screen_points))
|
||||
|
||||
lower_angle = upper_angle = 0
|
||||
if len(viewing_angles) != 0:
|
||||
lower_angle = np.min(viewing_angles)
|
||||
upper_angle = np.max(viewing_angles)
|
||||
|
||||
if upper_angle - lower_angle > TAU/2:
|
||||
if upper_angle - lower_angle > TAU / 2:
|
||||
lower_angle, upper_angle = upper_angle, lower_angle + TAU
|
||||
return lower_angle, upper_angle
|
||||
|
||||
def viewing_rays(self,screen):
|
||||
def viewing_rays(self, screen):
|
||||
|
||||
lower_angle, upper_angle = self.viewing_angles(screen)
|
||||
projected_RIGHT = self.project(RIGHT)/np.linalg.norm(self.project(RIGHT))
|
||||
lower_ray = rotate_vector(projected_RIGHT,lower_angle, axis = self.projection_direction())
|
||||
upper_ray = rotate_vector(projected_RIGHT,upper_angle, axis = self.projection_direction())
|
||||
|
||||
projected_RIGHT = self.project(
|
||||
RIGHT) / np.linalg.norm(self.project(RIGHT))
|
||||
lower_ray = rotate_vector(
|
||||
projected_RIGHT, lower_angle, axis=self.projection_direction())
|
||||
upper_ray = rotate_vector(
|
||||
projected_RIGHT, upper_angle, axis=self.projection_direction())
|
||||
|
||||
return lower_ray, upper_ray
|
||||
|
||||
def opening_angle(self):
|
||||
l,u = self.viewing_angles(self.screen)
|
||||
l, u = self.viewing_angles(self.screen)
|
||||
return u - l
|
||||
|
||||
def start_angle(self):
|
||||
l,u = self.viewing_angles(self.screen)
|
||||
l, u = self.viewing_angles(self.screen)
|
||||
return l
|
||||
|
||||
def stop_angle(self):
|
||||
l,u = self.viewing_angles(self.screen)
|
||||
l, u = self.viewing_angles(self.screen)
|
||||
return u
|
||||
|
||||
def move_source_to(self,point):
|
||||
def move_source_to(self, point):
|
||||
self.source_point.set_location(np.array(point))
|
||||
#self.source_point.move_to(np.array(point))
|
||||
#self.move_to(point)
|
||||
# self.source_point.move_to(np.array(point))
|
||||
# self.move_to(point)
|
||||
self.update_sectors()
|
||||
return self
|
||||
|
||||
def update_sectors(self):
|
||||
if self.screen == None:
|
||||
if self.screen is None:
|
||||
return
|
||||
for submob in self.submobjects:
|
||||
if type(submob) == AnnularSector:
|
||||
lower_angle, upper_angle = self.viewing_angles(self.screen)
|
||||
#dr = submob.outer_radius - submob.inner_radius
|
||||
# dr = submob.outer_radius - submob.inner_radius
|
||||
dr = self.radius / self.num_levels
|
||||
new_submob = self.new_sector(
|
||||
submob.inner_radius, dr, lower_angle, upper_angle
|
||||
@@ -312,7 +302,7 @@ class Spotlight(VMobject):
|
||||
# submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius))
|
||||
Transform(submob, new_submob).update(1)
|
||||
|
||||
def dimming(self,new_alpha):
|
||||
def dimming(self, new_alpha):
|
||||
old_alpha = self.max_opacity
|
||||
self.max_opacity = new_alpha
|
||||
for submob in self.submobjects:
|
||||
@@ -322,26 +312,28 @@ class Spotlight(VMobject):
|
||||
# it's the shadow, don't dim it
|
||||
continue
|
||||
old_submob_alpha = submob.fill_opacity
|
||||
new_submob_alpha = old_submob_alpha * new_alpha/old_alpha
|
||||
submob.set_fill(opacity = new_submob_alpha)
|
||||
new_submob_alpha = old_submob_alpha * new_alpha / old_alpha
|
||||
submob.set_fill(opacity=new_submob_alpha)
|
||||
|
||||
def change_opacity_function(self,new_f):
|
||||
def change_opacity_function(self, new_f):
|
||||
self.opacity_function = new_f
|
||||
dr = self.radius/self.num_levels
|
||||
dr = self.radius / self.num_levels
|
||||
|
||||
sectors = []
|
||||
for submob in self.submobjects:
|
||||
if type(submob) == AnnularSector:
|
||||
sectors.append(submob)
|
||||
|
||||
for (r,submob) in zip(np.arange(0,self.radius,dr),sectors):
|
||||
for (r, submob) in zip(np.arange(0, self.radius, dr), sectors):
|
||||
if type(submob) != AnnularSector:
|
||||
# it's the shadow, don't dim it
|
||||
continue
|
||||
alpha = self.opacity_function(r)
|
||||
submob.set_fill(opacity = alpha)
|
||||
submob.set_fill(opacity=alpha)
|
||||
|
||||
# Warning: This class is likely quite buggy.
|
||||
|
||||
|
||||
class LightSource(VMobject):
|
||||
# combines:
|
||||
# a lighthouse
|
||||
@@ -349,12 +341,12 @@ class LightSource(VMobject):
|
||||
# a spotlight
|
||||
# and a shadow
|
||||
CONFIG = {
|
||||
"source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0),
|
||||
"source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0),
|
||||
"color": LIGHT_COLOR,
|
||||
"num_levels": 10,
|
||||
"radius": 10.0,
|
||||
"screen": None,
|
||||
"opacity_function": inverse_quadratic(1,2,1),
|
||||
"opacity_function": inverse_quadratic(1, 2, 1),
|
||||
"max_opacity_ambient": AMBIENT_FULL,
|
||||
"max_opacity_spotlight": SPOTLIGHT_FULL,
|
||||
"camera_mob": None
|
||||
@@ -366,39 +358,41 @@ class LightSource(VMobject):
|
||||
|
||||
self.lighthouse = Lighthouse()
|
||||
self.ambient_light = AmbientLight(
|
||||
source_point = VectorizedPoint(location = self.get_source_point()),
|
||||
color = self.color,
|
||||
num_levels = self.num_levels,
|
||||
radius = self.radius,
|
||||
opacity_function = self.opacity_function,
|
||||
max_opacity = self.max_opacity_ambient
|
||||
source_point=VectorizedPoint(location=self.get_source_point()),
|
||||
color=self.color,
|
||||
num_levels=self.num_levels,
|
||||
radius=self.radius,
|
||||
opacity_function=self.opacity_function,
|
||||
max_opacity=self.max_opacity_ambient
|
||||
)
|
||||
if self.has_screen():
|
||||
self.spotlight = Spotlight(
|
||||
source_point = VectorizedPoint(location = self.get_source_point()),
|
||||
color = self.color,
|
||||
num_levels = self.num_levels,
|
||||
radius = self.radius,
|
||||
screen = self.screen,
|
||||
opacity_function = self.opacity_function,
|
||||
max_opacity = self.max_opacity_spotlight,
|
||||
camera_mob = self.camera_mob
|
||||
source_point=VectorizedPoint(location=self.get_source_point()),
|
||||
color=self.color,
|
||||
num_levels=self.num_levels,
|
||||
radius=self.radius,
|
||||
screen=self.screen,
|
||||
opacity_function=self.opacity_function,
|
||||
max_opacity=self.max_opacity_spotlight,
|
||||
camera_mob=self.camera_mob
|
||||
)
|
||||
else:
|
||||
self.spotlight = Spotlight()
|
||||
|
||||
self.shadow = VMobject(fill_color = SHADOW_COLOR, fill_opacity = 1.0, stroke_color = BLACK)
|
||||
self.lighthouse.next_to(self.get_source_point(),DOWN,buff = 0)
|
||||
self.shadow = VMobject(fill_color=SHADOW_COLOR,
|
||||
fill_opacity=1.0, stroke_color=BLACK)
|
||||
self.lighthouse.next_to(self.get_source_point(), DOWN, buff=0)
|
||||
self.ambient_light.move_source_to(self.get_source_point())
|
||||
|
||||
if self.has_screen():
|
||||
self.spotlight.move_source_to(self.get_source_point())
|
||||
self.update_shadow()
|
||||
|
||||
self.add(self.ambient_light,self.spotlight,self.lighthouse, self.shadow)
|
||||
self.add(self.ambient_light, self.spotlight,
|
||||
self.lighthouse, self.shadow)
|
||||
|
||||
def has_screen(self):
|
||||
if self.screen == None:
|
||||
if self.screen is None:
|
||||
return False
|
||||
elif np.size(self.screen.points) == 0:
|
||||
return False
|
||||
@@ -408,18 +402,18 @@ class LightSource(VMobject):
|
||||
def dim_ambient(self):
|
||||
self.set_max_opacity_ambient(AMBIENT_DIMMED)
|
||||
|
||||
def set_max_opacity_ambient(self,new_opacity):
|
||||
def set_max_opacity_ambient(self, new_opacity):
|
||||
self.max_opacity_ambient = new_opacity
|
||||
self.ambient_light.dimming(new_opacity)
|
||||
|
||||
def dim_spotlight(self):
|
||||
self.set_max_opacity_spotlight(SPOTLIGHT_DIMMED)
|
||||
|
||||
def set_max_opacity_spotlight(self,new_opacity):
|
||||
def set_max_opacity_spotlight(self, new_opacity):
|
||||
self.max_opacity_spotlight = new_opacity
|
||||
self.spotlight.dimming(new_opacity)
|
||||
|
||||
def set_camera_mob(self,new_cam_mob):
|
||||
def set_camera_mob(self, new_cam_mob):
|
||||
self.camera_mob = new_cam_mob
|
||||
self.spotlight.camera_mob = new_cam_mob
|
||||
|
||||
@@ -432,40 +426,40 @@ class LightSource(VMobject):
|
||||
camera_mob = self.spotlight.camera_mob
|
||||
self.remove(self.spotlight)
|
||||
self.spotlight = Spotlight(
|
||||
source_point = VectorizedPoint(location = self.get_source_point()),
|
||||
color = self.color,
|
||||
num_levels = self.num_levels,
|
||||
radius = self.radius,
|
||||
screen = new_screen,
|
||||
camera_mob = self.camera_mob,
|
||||
opacity_function = self.opacity_function,
|
||||
max_opacity = self.max_opacity_spotlight,
|
||||
source_point=VectorizedPoint(location=self.get_source_point()),
|
||||
color=self.color,
|
||||
num_levels=self.num_levels,
|
||||
radius=self.radius,
|
||||
screen=new_screen,
|
||||
camera_mob=self.camera_mob,
|
||||
opacity_function=self.opacity_function,
|
||||
max_opacity=self.max_opacity_spotlight,
|
||||
)
|
||||
self.spotlight.move_source_to(self.get_source_point())
|
||||
|
||||
# Note: This line will make spotlight show up at the end
|
||||
# of the submojects list, which can make it show up on
|
||||
# top of the shadow. To make it show up in the
|
||||
# same spot, you could try the following line,
|
||||
# same spot, you could try the following line,
|
||||
# where "index" is what I defined above:
|
||||
self.submobjects.insert(index, self.spotlight)
|
||||
#self.add(self.spotlight)
|
||||
|
||||
# self.add(self.spotlight)
|
||||
|
||||
# in any case
|
||||
self.screen = new_screen
|
||||
|
||||
def move_source_to(self,point):
|
||||
def move_source_to(self, point):
|
||||
apoint = np.array(point)
|
||||
v = apoint - self.get_source_point()
|
||||
# Note: As discussed, things stand to behave better if source
|
||||
# point is a submobject, so that it automatically interpolates
|
||||
# during an animation, and other updates can be defined wrt
|
||||
# during an animation, and other updates can be defined wrt
|
||||
# that source point's location
|
||||
self.source_point.set_location(apoint)
|
||||
#self.lighthouse.next_to(apoint,DOWN,buff = 0)
|
||||
#self.ambient_light.move_source_to(apoint)
|
||||
# self.lighthouse.next_to(apoint,DOWN,buff = 0)
|
||||
# self.ambient_light.move_source_to(apoint)
|
||||
self.lighthouse.shift(v)
|
||||
#self.ambient_light.shift(v)
|
||||
# self.ambient_light.shift(v)
|
||||
self.ambient_light.move_source_to(apoint)
|
||||
if self.has_screen():
|
||||
self.spotlight.move_source_to(apoint)
|
||||
@@ -474,8 +468,8 @@ class LightSource(VMobject):
|
||||
|
||||
def change_spotlight_opacity_function(self, new_of):
|
||||
self.spotlight.change_opacity_function(new_of)
|
||||
|
||||
def set_radius(self,new_radius):
|
||||
|
||||
def set_radius(self, new_radius):
|
||||
self.radius = new_radius
|
||||
self.ambient_light.radius = new_radius
|
||||
self.spotlight.radius = new_radius
|
||||
@@ -496,12 +490,12 @@ class LightSource(VMobject):
|
||||
|
||||
def update_ambient(self):
|
||||
new_ambient_light = AmbientLight(
|
||||
source_point = VectorizedPoint(location = ORIGIN),
|
||||
color = self.color,
|
||||
num_levels = self.num_levels,
|
||||
radius = self.radius,
|
||||
opacity_function = self.opacity_function,
|
||||
max_opacity = self.max_opacity_ambient
|
||||
source_point=VectorizedPoint(location=ORIGIN),
|
||||
color=self.color,
|
||||
num_levels=self.num_levels,
|
||||
radius=self.radius,
|
||||
opacity_function=self.opacity_function,
|
||||
max_opacity=self.max_opacity_ambient
|
||||
)
|
||||
new_ambient_light.apply_matrix(self.rotation_matrix())
|
||||
new_ambient_light.move_source_to(self.get_source_point())
|
||||
@@ -512,13 +506,12 @@ class LightSource(VMobject):
|
||||
|
||||
def rotation_matrix(self):
|
||||
|
||||
if self.camera_mob == None:
|
||||
if self.camera_mob is None:
|
||||
return np.eye(3)
|
||||
|
||||
phi = self.camera_mob.get_center()[0]
|
||||
theta = self.camera_mob.get_center()[1]
|
||||
|
||||
|
||||
R1 = np.array([
|
||||
[1, 0, 0],
|
||||
[0, np.cos(phi), -np.sin(phi)],
|
||||
@@ -526,8 +519,8 @@ class LightSource(VMobject):
|
||||
])
|
||||
|
||||
R2 = np.array([
|
||||
[np.cos(theta + TAU/4), -np.sin(theta + TAU/4), 0],
|
||||
[np.sin(theta + TAU/4), np.cos(theta + TAU/4), 0],
|
||||
[np.cos(theta + TAU / 4), -np.sin(theta + TAU / 4), 0],
|
||||
[np.sin(theta + TAU / 4), np.cos(theta + TAU / 4), 0],
|
||||
[0, 0, 1]
|
||||
])
|
||||
|
||||
@@ -542,28 +535,31 @@ class LightSource(VMobject):
|
||||
for point in self.screen.get_anchors():
|
||||
projected_screen_points.append(self.spotlight.project(point))
|
||||
|
||||
|
||||
projected_source = project_along_vector(self.get_source_point(),self.spotlight.projection_direction())
|
||||
projected_source = project_along_vector(
|
||||
self.get_source_point(), self.spotlight.projection_direction())
|
||||
|
||||
projected_point_cloud_3d = np.append(
|
||||
projected_screen_points,
|
||||
np.reshape(projected_source,(1,3)),
|
||||
axis = 0
|
||||
np.reshape(projected_source, (1, 3)),
|
||||
axis=0
|
||||
)
|
||||
rotation_matrix = self.rotation_matrix() # z_to_vector(self.spotlight.projection_direction())
|
||||
back_rotation_matrix = rotation_matrix.T # i. e. its inverse
|
||||
# z_to_vector(self.spotlight.projection_direction())
|
||||
rotation_matrix = self.rotation_matrix()
|
||||
back_rotation_matrix = rotation_matrix.T # i. e. its inverse
|
||||
|
||||
rotated_point_cloud_3d = np.dot(projected_point_cloud_3d,back_rotation_matrix.T)
|
||||
rotated_point_cloud_3d = np.dot(
|
||||
projected_point_cloud_3d, back_rotation_matrix.T)
|
||||
# these points now should all have z = 0
|
||||
|
||||
point_cloud_2d = rotated_point_cloud_3d[:,:2]
|
||||
point_cloud_2d = rotated_point_cloud_3d[:, :2]
|
||||
# now we can compute the convex hull
|
||||
hull_2d = ConvexHull(point_cloud_2d) # guaranteed to run ccw
|
||||
hull_2d = ConvexHull(point_cloud_2d) # guaranteed to run ccw
|
||||
hull = []
|
||||
|
||||
# we also need the projected source point
|
||||
source_point_2d = np.dot(self.spotlight.project(self.get_source_point()),back_rotation_matrix.T)[:2]
|
||||
|
||||
source_point_2d = np.dot(self.spotlight.project(
|
||||
self.get_source_point()), back_rotation_matrix.T)[:2]
|
||||
|
||||
index = 0
|
||||
for point in point_cloud_2d[hull_2d.vertices]:
|
||||
if np.all(np.abs(point - source_point_2d) < 1.0e-6):
|
||||
@@ -574,35 +570,35 @@ class LightSource(VMobject):
|
||||
hull.append(point_3d)
|
||||
index += 1
|
||||
|
||||
|
||||
hull_mobject = VMobject()
|
||||
hull_mobject.set_points_as_corners(hull)
|
||||
hull_mobject.apply_matrix(rotation_matrix)
|
||||
|
||||
|
||||
anchors = hull_mobject.get_anchors()
|
||||
|
||||
# add two control points for the outer cone
|
||||
if np.size(anchors) == 0:
|
||||
if np.size(anchors) == 0:
|
||||
self.shadow.points = []
|
||||
return
|
||||
|
||||
ray1 = anchors[source_index - 1] - projected_source
|
||||
ray1 = ray1/np.linalg.norm(ray1) * 100
|
||||
ray1 = ray1 / np.linalg.norm(ray1) * 100
|
||||
|
||||
ray2 = anchors[source_index] - projected_source
|
||||
ray2 = ray2/np.linalg.norm(ray2) * 100
|
||||
ray2 = ray2 / np.linalg.norm(ray2) * 100
|
||||
outpoint1 = anchors[source_index - 1] + ray1
|
||||
outpoint2 = anchors[source_index] + ray2
|
||||
|
||||
new_anchors = anchors[:source_index]
|
||||
new_anchors = np.append(new_anchors,np.array([outpoint1, outpoint2]),axis = 0)
|
||||
new_anchors = np.append(new_anchors,anchors[source_index:],axis = 0)
|
||||
new_anchors = np.append(new_anchors, np.array(
|
||||
[outpoint1, outpoint2]), axis=0)
|
||||
new_anchors = np.append(new_anchors, anchors[source_index:], axis=0)
|
||||
self.shadow.set_points_as_corners(new_anchors)
|
||||
|
||||
# shift it closer to the camera so it is in front of the spotlight
|
||||
self.shadow.mark_paths_closed = True
|
||||
|
||||
|
||||
class ScreenTracker(ContinualAnimation):
|
||||
def __init__(self, light_source, **kwargs):
|
||||
self.light_source = light_source
|
||||
@@ -611,20 +607,3 @@ class ScreenTracker(ContinualAnimation):
|
||||
|
||||
def update_mobject(self, dt):
|
||||
self.light_source.update()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import numpy as np
|
||||
|
||||
from constants import *
|
||||
|
||||
from animation.creation import ShowCreation
|
||||
from animation.transform import ApplyMethod
|
||||
from animation.creation import FadeOut
|
||||
@@ -11,12 +13,14 @@ 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,
|
||||
"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")
|
||||
@@ -34,25 +38,24 @@ class NumericalMatrixMultiplication(Scene):
|
||||
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))
|
||||
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])
|
||||
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)
|
||||
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,
|
||||
"color": BLUE,
|
||||
"stroke_width": 2,
|
||||
}
|
||||
left_rows = [
|
||||
VGroup(*row) for row in left.get_mob_matrix()
|
||||
@@ -60,7 +63,7 @@ class NumericalMatrixMultiplication(Scene):
|
||||
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_line.next_to(row, DOWN, buff=left.v_buff / 2.)
|
||||
h_lines.add(h_line)
|
||||
|
||||
right_cols = [
|
||||
@@ -69,7 +72,7 @@ class NumericalMatrixMultiplication(Scene):
|
||||
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_line.next_to(col, RIGHT, buff=right.h_buff / 2.)
|
||||
v_lines.add(v_line)
|
||||
|
||||
self.play(ShowCreation(h_lines))
|
||||
@@ -81,17 +84,16 @@ class NumericalMatrixMultiplication(Scene):
|
||||
equals = TexMobject("=")
|
||||
everything = VGroup(left, right, equals, result)
|
||||
everything.arrange_submobjects()
|
||||
everything.scale_to_fit_width(FRAME_WIDTH-1)
|
||||
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
|
||||
radius=l_matrix[0][0].get_height(),
|
||||
color=GREEN
|
||||
)
|
||||
circles = VGroup(*[
|
||||
entry.get_point_mobject()
|
||||
@@ -120,10 +122,10 @@ class NumericalMatrixMultiplication(Scene):
|
||||
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",
|
||||
start_parts,
|
||||
result_entry.copy().set_color(YELLOW),
|
||||
path_arc=-np.pi / 2,
|
||||
submobject_mode="all_at_once",
|
||||
),
|
||||
*lagging_anims
|
||||
)
|
||||
@@ -137,4 +139,4 @@ class NumericalMatrixMultiplication(Scene):
|
||||
l_matrix[a][c].set_color(WHITE)
|
||||
r_matrix[c][b].set_color(WHITE)
|
||||
self.play(FadeOut(circles), *lagging_anims)
|
||||
self.wait()
|
||||
self.wait()
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import numpy as np
|
||||
import itertools as it
|
||||
from PIL import Image
|
||||
from copy import deepcopy
|
||||
|
||||
from mobject.mobject import Mobject
|
||||
@@ -10,11 +8,13 @@ from constants import *
|
||||
|
||||
# Warning: This is all now pretty depricated, and should not be expected to work
|
||||
|
||||
|
||||
class Region(Mobject):
|
||||
CONFIG = {
|
||||
"display_mode" : "region"
|
||||
"display_mode": "region"
|
||||
}
|
||||
def __init__(self, condition = (lambda x, y : True), **kwargs):
|
||||
|
||||
def __init__(self, condition=(lambda x, y: True), **kwargs):
|
||||
"""
|
||||
Condition must be a function which takes in two real
|
||||
arrays (representing x and y values of space respectively)
|
||||
@@ -26,25 +26,26 @@ class Region(Mobject):
|
||||
self.condition = condition
|
||||
|
||||
def _combine(self, region, op):
|
||||
self.condition = lambda x, y : op(
|
||||
self.condition = lambda x, y: op(
|
||||
self.condition(x, y),
|
||||
region.condition(x, y)
|
||||
)
|
||||
|
||||
def union(self, region):
|
||||
self._combine(region, lambda bg1, bg2 : bg1 | bg2)
|
||||
self._combine(region, lambda bg1, bg2: bg1 | bg2)
|
||||
return self
|
||||
|
||||
def intersect(self, region):
|
||||
self._combine(region, lambda bg1, bg2 : bg1 & bg2)
|
||||
self._combine(region, lambda bg1, bg2: bg1 & bg2)
|
||||
return self
|
||||
|
||||
def complement(self):
|
||||
self.bool_grid = ~self.bool_grid
|
||||
return self
|
||||
|
||||
|
||||
class HalfPlane(Region):
|
||||
def __init__(self, point_pair, upper_left = True, *args, **kwargs):
|
||||
def __init__(self, point_pair, upper_left=True, *args, **kwargs):
|
||||
"""
|
||||
point_pair of the form [(x_0, y_0,...), (x_1, y_1,...)]
|
||||
|
||||
@@ -56,16 +57,19 @@ class HalfPlane(Region):
|
||||
point_pair = list(point_pair)
|
||||
point_pair.reverse()
|
||||
(x0, y0), (x1, y1) = point_pair[0][:2], point_pair[1][:2]
|
||||
|
||||
def condition(x, y):
|
||||
return (x1 - x0)*(y - y0) > (y1 - y0)*(x - x0)
|
||||
return (x1 - x0) * (y - y0) > (y1 - y0) * (x - x0)
|
||||
Region.__init__(self, condition, *args, **kwargs)
|
||||
|
||||
|
||||
def region_from_line_boundary(*lines, **kwargs):
|
||||
reg = Region(**kwargs)
|
||||
for line in lines:
|
||||
reg.intersect(HalfPlane(line, **kwargs))
|
||||
return reg
|
||||
|
||||
|
||||
def region_from_polygon_vertices(*vertices, **kwargs):
|
||||
return region_from_line_boundary(*adjacent_pairs(vertices), **kwargs)
|
||||
|
||||
@@ -81,7 +85,7 @@ def plane_partition(*lines, **kwargs):
|
||||
half_planes = [HalfPlane(line, **kwargs) for line in lines]
|
||||
complements = [deepcopy(hp).complement() for hp in half_planes]
|
||||
num_lines = len(lines)
|
||||
for bool_list in it.product(*[[True, False]]*num_lines):
|
||||
for bool_list in it.product(*[[True, False]] * num_lines):
|
||||
reg = Region(**kwargs)
|
||||
for i in range(num_lines):
|
||||
if bool_list[i]:
|
||||
@@ -92,6 +96,7 @@ def plane_partition(*lines, **kwargs):
|
||||
result.append(reg)
|
||||
return result
|
||||
|
||||
|
||||
def plane_partition_from_points(*points, **kwargs):
|
||||
"""
|
||||
Returns list of regions cut out by the complete graph
|
||||
@@ -101,9 +106,3 @@ def plane_partition_from_points(*points, **kwargs):
|
||||
"""
|
||||
lines = [[p1, p2] for (p1, p2) in it.combinations(points, 2)]
|
||||
return plane_partition(*lines, **kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
132
primes.py
132
primes.py
@@ -1,5 +1,6 @@
|
||||
from big_ol_pile_of_manim_imports import *
|
||||
|
||||
|
||||
def is_prime(n):
|
||||
|
||||
for i in primes(n**0.5):
|
||||
@@ -8,6 +9,7 @@ def is_prime(n):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def primes(max_n):
|
||||
|
||||
if max_n < 2:
|
||||
@@ -23,13 +25,14 @@ def primes(max_n):
|
||||
|
||||
return p
|
||||
|
||||
|
||||
def prime_factors(n):
|
||||
|
||||
if is_prime(n):
|
||||
return [n]
|
||||
|
||||
i = 0
|
||||
primes_list = primes(n/2)
|
||||
primes_list = primes(n / 2)
|
||||
|
||||
factors = []
|
||||
r = n
|
||||
@@ -38,7 +41,7 @@ def prime_factors(n):
|
||||
p = primes_list[i]
|
||||
if r % p == 0:
|
||||
factors.append(p)
|
||||
r = r/p
|
||||
r = r / p
|
||||
else:
|
||||
i += 1
|
||||
|
||||
@@ -48,55 +51,58 @@ def prime_factors(n):
|
||||
RUN_TIME = 0.5
|
||||
DOWN_SHIFT = 0.0 * DOWN
|
||||
|
||||
|
||||
class Primes(Scene):
|
||||
|
||||
def construct(self):
|
||||
|
||||
|
||||
N = 100
|
||||
|
||||
primes_list = np.array(primes(N))
|
||||
|
||||
palette = ["#FBA125", "#76CD42", "#30CCF5", "#9377C4", "#F95137",
|
||||
# 2 3 5 7 11
|
||||
"#1B442E", TEAL_E, MAROON_A, DARK_BROWN, PINK,
|
||||
# 13 17 19 23 29
|
||||
"#9C25FB", GREEN_E, MAROON_E, GOLD_E, GREEN_E,
|
||||
# 31 37 41 43 47 # last prime to occur in a factorization
|
||||
LIGHT_BROWN, DARK_BLUE, GREY_BROWN, GREEN_C, BLUE_C,
|
||||
# 53 59 61 67 71
|
||||
PURPLE_C, RED_C, YELLOW_E, TEAL_C, MAROON_C]
|
||||
palette = ["#FBA125", "#76CD42", "#30CCF5", "#9377C4", "#F95137",
|
||||
# 2 3 5 7 11
|
||||
"#1B442E", TEAL_E, MAROON_A, DARK_BROWN, PINK,
|
||||
# 13 17 19 23 29
|
||||
"#9C25FB", GREEN_E, MAROON_E, GOLD_E, GREEN_E,
|
||||
# 31 37 41 43 47 # last prime to occur in a factorization
|
||||
LIGHT_BROWN, DARK_BLUE, GREY_BROWN, GREEN_C, BLUE_C,
|
||||
# 53 59 61 67 71
|
||||
PURPLE_C, RED_C, YELLOW_E, TEAL_C, MAROON_C]
|
||||
# 73 79 83 89 97
|
||||
|
||||
nb_primes = len(primes_list)
|
||||
print nb_primes
|
||||
|
||||
prime_points_radius = 3.2
|
||||
angles = np.arange(TAU/4, -3*TAU/4, -TAU/float(nb_primes))
|
||||
angles = np.arange(TAU / 4, -3 * TAU / 4, -TAU / float(nb_primes))
|
||||
print len(angles), angles
|
||||
prime_points = [prime_points_radius * (np.cos(theta) * RIGHT
|
||||
+ np.sin(theta) * UP)
|
||||
for theta in angles]
|
||||
+ np.sin(theta) * UP)
|
||||
for theta in angles]
|
||||
print len(prime_points)
|
||||
|
||||
wheel = Wheel()
|
||||
|
||||
|
||||
angles = [TAU]
|
||||
colors = [LIGHT_GREY]
|
||||
|
||||
wheel.update_sectors(angles, colors)
|
||||
wheel.rotate(-TAU/4).shift(DOWN_SHIFT)
|
||||
wheel.rotate(-TAU / 4).shift(DOWN_SHIFT)
|
||||
self.add(wheel)
|
||||
|
||||
number = DecimalNumber(1, num_decimal_points = 0).scale(2).shift(DOWN_SHIFT)
|
||||
number = DecimalNumber(1, num_decimal_points=0).scale(
|
||||
2).shift(DOWN_SHIFT)
|
||||
self.add(number)
|
||||
self.wait(RUN_TIME)
|
||||
|
||||
j = 0
|
||||
|
||||
for i in range(2,N+1):
|
||||
for i in range(2, N + 1):
|
||||
|
||||
factors = prime_factors(i)
|
||||
factor_indices = [np.where(primes_list == x)[0][0] for x in factors]
|
||||
factor_indices = [np.where(primes_list == x)[0][0]
|
||||
for x in factors]
|
||||
|
||||
nb_sectors = float(len(factor_indices))
|
||||
new_angles = np.ones(nb_sectors) / nb_sectors * TAU
|
||||
@@ -104,16 +110,16 @@ class Primes(Scene):
|
||||
new_colors = []
|
||||
for index in factor_indices:
|
||||
new_colors.append(palette[index])
|
||||
|
||||
|
||||
self.play(
|
||||
UpdateAngles(wheel, new_angles = new_angles, new_colors = new_colors,
|
||||
run_time = RUN_TIME),
|
||||
ChangeDecimalToValue(number, i, run_time = RUN_TIME)
|
||||
UpdateAngles(wheel, new_angles=new_angles, new_colors=new_colors,
|
||||
run_time=RUN_TIME),
|
||||
ChangeDecimalToValue(number, i, run_time=RUN_TIME)
|
||||
)
|
||||
self.wait(RUN_TIME)
|
||||
|
||||
if is_prime(i):
|
||||
full_wheel = VGroup(wheel,number).copy()
|
||||
full_wheel = VGroup(wheel, number).copy()
|
||||
full_wheel_copy = full_wheel.copy()
|
||||
full_wheel_copy.scale(0.15).move_to(prime_points[j])
|
||||
print j
|
||||
@@ -123,47 +129,45 @@ class Primes(Scene):
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
class Wheel(VMobject):
|
||||
|
||||
CONFIG = {
|
||||
"inner_radius" : 1.2,
|
||||
"outer_radius" : 2.4,
|
||||
"nb_sectors" : 25,
|
||||
"colors" : [BLACK] * 25
|
||||
"inner_radius": 1.2,
|
||||
"outer_radius": 2.4,
|
||||
"nb_sectors": 25,
|
||||
"colors": [BLACK] * 25
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
|
||||
angle = TAU/self.nb_sectors
|
||||
angle_range = np.arange(0,TAU,angle)
|
||||
angle = TAU / self.nb_sectors
|
||||
angle_range = np.arange(0, TAU, angle)
|
||||
for j in range(self.nb_sectors - len(angle_range)):
|
||||
angle_range = np.append(angle_range, TAU)
|
||||
self.colors.append(BLACK)
|
||||
|
||||
for (i,theta) in enumerate(angle_range):
|
||||
for (i, theta) in enumerate(angle_range):
|
||||
if theta != TAU:
|
||||
use_angle = angle
|
||||
else:
|
||||
use_angle = 0
|
||||
sector = AnnularSector(
|
||||
inner_radius = self.inner_radius,
|
||||
outer_radius = self.outer_radius,
|
||||
angle = use_angle,
|
||||
start_angle = theta,
|
||||
fill_color = self.colors[i],
|
||||
fill_opacity = 1,
|
||||
stroke_color = WHITE,
|
||||
stroke_width = 5
|
||||
).rotate_about_origin(TAU/2, axis = UP).shift(DOWN_SHIFT)
|
||||
inner_radius=self.inner_radius,
|
||||
outer_radius=self.outer_radius,
|
||||
angle=use_angle,
|
||||
start_angle=theta,
|
||||
fill_color=self.colors[i],
|
||||
fill_opacity=1,
|
||||
stroke_color=WHITE,
|
||||
stroke_width=5
|
||||
).rotate_about_origin(TAU / 2, axis=UP).shift(DOWN_SHIFT)
|
||||
self.add(sector)
|
||||
|
||||
def update_sectors(self, new_angles, new_colors):
|
||||
|
||||
if len(new_angles) > self.nb_sectors:
|
||||
raise "More angles than sectors!"
|
||||
|
||||
|
||||
for i in range(len(new_angles), self.nb_sectors):
|
||||
new_angles = np.append(new_angles, 0)
|
||||
new_colors.append(BLACK)
|
||||
@@ -172,30 +176,28 @@ class Wheel(VMobject):
|
||||
|
||||
new_start_angles = -np.cumsum(new_angles) + new_angles
|
||||
|
||||
for (i,sector) in enumerate(self.submobjects):
|
||||
for (i, sector) in enumerate(self.submobjects):
|
||||
sector.angle = new_angles[i]
|
||||
sector.start_angle = new_start_angles[i]
|
||||
sector.set_fill(color = new_colors[i])
|
||||
sector.set_fill(color=new_colors[i])
|
||||
sector.generate_points()
|
||||
sector.rotate_about_origin(TAU/2, axis = UP).shift(DOWN_SHIFT)
|
||||
|
||||
|
||||
|
||||
sector.rotate_about_origin(TAU / 2, axis=UP).shift(DOWN_SHIFT)
|
||||
|
||||
|
||||
class UpdateAngles(Animation):
|
||||
|
||||
def __init__(self,mobject,**kwargs):
|
||||
def __init__(self, mobject, **kwargs):
|
||||
|
||||
self.old_angles = []
|
||||
for (i, sector) in enumerate(mobject.submobjects):
|
||||
self.old_angles.append(sector.angle)
|
||||
|
||||
self.old_angles = np.array(self.old_angles)
|
||||
self.old_start_angles = np.cumsum(self.old_angles) - self.old_angles + TAU/4
|
||||
|
||||
self.old_start_angles = np.cumsum(
|
||||
self.old_angles) - self.old_angles + TAU / 4
|
||||
|
||||
digest_config(self, kwargs)
|
||||
Animation.__init__(self,mobject,**kwargs)
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def update_submobject(self, submobject, starting_submobject, alpha):
|
||||
|
||||
@@ -211,7 +213,8 @@ class UpdateAngles(Animation):
|
||||
self.new_angles = np.append(self.new_angles, 0)
|
||||
self.new_colors.append(BLACK)
|
||||
|
||||
self.new_start_angles = np.cumsum(self.new_angles) - self.new_angles + TAU/4
|
||||
self.new_start_angles = np.cumsum(
|
||||
self.new_angles) - self.new_angles + TAU / 4
|
||||
# this should be in __init__!
|
||||
# but has no effect there
|
||||
|
||||
@@ -223,30 +226,15 @@ class UpdateAngles(Animation):
|
||||
self.new_start_angles[i], alpha
|
||||
)
|
||||
|
||||
|
||||
interpolated_color = interpolate_color(
|
||||
self.mobject.colors[i],
|
||||
self.new_colors[i],
|
||||
alpha
|
||||
)
|
||||
|
||||
submobject.set_fill(color = interpolated_color)
|
||||
submobject.set_fill(color=interpolated_color)
|
||||
submobject.generate_points()
|
||||
submobject.rotate_about_origin(TAU/2, axis = UP).shift(DOWN_SHIFT)
|
||||
submobject.rotate_about_origin(TAU / 2, axis=UP).shift(DOWN_SHIFT)
|
||||
|
||||
if alpha > 0.95:
|
||||
self.mobject.colors[i] = self.new_colors[i]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,101 +22,103 @@ 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,
|
||||
"x_max" : 10,
|
||||
"x_axis_width" : 9,
|
||||
"x_tick_frequency" : 1,
|
||||
"x_leftmost_tick" : None, #Change if different from x_min
|
||||
"x_labeled_nums" : None,
|
||||
"x_axis_label" : "$x$",
|
||||
"y_min" : -1,
|
||||
"y_max" : 10,
|
||||
"y_axis_height" : 6,
|
||||
"y_tick_frequency" : 1,
|
||||
"y_bottom_tick" : None, #Change if different from y_min
|
||||
"y_labeled_nums" : None,
|
||||
"y_axis_label" : "$y$",
|
||||
"axes_color" : GREY,
|
||||
"graph_origin" : 2.5*DOWN + 4*LEFT,
|
||||
"exclude_zero_label" : True,
|
||||
"num_graph_anchor_points" : 25,
|
||||
"default_graph_colors" : [BLUE, GREEN, YELLOW],
|
||||
"default_derivative_color" : GREEN,
|
||||
"default_input_color" : YELLOW,
|
||||
"default_riemann_start_color" : BLUE,
|
||||
"default_riemann_end_color" : GREEN,
|
||||
"x_min": -1,
|
||||
"x_max": 10,
|
||||
"x_axis_width": 9,
|
||||
"x_tick_frequency": 1,
|
||||
"x_leftmost_tick": None, # Change if different from x_min
|
||||
"x_labeled_nums": None,
|
||||
"x_axis_label": "$x$",
|
||||
"y_min": -1,
|
||||
"y_max": 10,
|
||||
"y_axis_height": 6,
|
||||
"y_tick_frequency": 1,
|
||||
"y_bottom_tick": None, # Change if different from y_min
|
||||
"y_labeled_nums": None,
|
||||
"y_axis_label": "$y$",
|
||||
"axes_color": GREY,
|
||||
"graph_origin": 2.5 * DOWN + 4 * LEFT,
|
||||
"exclude_zero_label": True,
|
||||
"num_graph_anchor_points": 25,
|
||||
"default_graph_colors": [BLUE, GREEN, YELLOW],
|
||||
"default_derivative_color": GREEN,
|
||||
"default_input_color": YELLOW,
|
||||
"default_riemann_start_color": BLUE,
|
||||
"default_riemann_end_color": GREEN,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
self.default_graph_colors_cycle = it.cycle(self.default_graph_colors)
|
||||
|
||||
def setup_axes(self, animate = False):
|
||||
##TODO, once eoc is done, refactor this to be less redundant.
|
||||
def setup_axes(self, animate=False):
|
||||
# TODO, once eoc is done, refactor this to be less redundant.
|
||||
x_num_range = float(self.x_max - self.x_min)
|
||||
self.space_unit_to_x = self.x_axis_width/x_num_range
|
||||
self.space_unit_to_x = self.x_axis_width / x_num_range
|
||||
if self.x_labeled_nums is None:
|
||||
self.x_labeled_nums = []
|
||||
if self.x_leftmost_tick is None:
|
||||
self.x_leftmost_tick = self.x_min
|
||||
x_axis = NumberLine(
|
||||
x_min = self.x_min,
|
||||
x_max = self.x_max,
|
||||
unit_size = self.space_unit_to_x,
|
||||
tick_frequency = self.x_tick_frequency,
|
||||
leftmost_tick = self.x_leftmost_tick,
|
||||
numbers_with_elongated_ticks = self.x_labeled_nums,
|
||||
color = self.axes_color
|
||||
x_min=self.x_min,
|
||||
x_max=self.x_max,
|
||||
unit_size=self.space_unit_to_x,
|
||||
tick_frequency=self.x_tick_frequency,
|
||||
leftmost_tick=self.x_leftmost_tick,
|
||||
numbers_with_elongated_ticks=self.x_labeled_nums,
|
||||
color=self.axes_color
|
||||
)
|
||||
x_axis.shift(self.graph_origin - x_axis.number_to_point(0))
|
||||
x_axis.shift(self.graph_origin - x_axis.number_to_point(0))
|
||||
if len(self.x_labeled_nums) > 0:
|
||||
if self.exclude_zero_label:
|
||||
self.x_labeled_nums = filter(
|
||||
lambda x : x != 0,
|
||||
lambda x: x != 0,
|
||||
self.x_labeled_nums
|
||||
)
|
||||
x_axis.add_numbers(*self.x_labeled_nums)
|
||||
if self.x_axis_label:
|
||||
x_label = TextMobject(self.x_axis_label)
|
||||
x_label.next_to(
|
||||
x_axis.get_tick_marks(), UP+RIGHT,
|
||||
buff = SMALL_BUFF
|
||||
x_axis.get_tick_marks(), UP + RIGHT,
|
||||
buff=SMALL_BUFF
|
||||
)
|
||||
x_label.shift_onto_screen()
|
||||
x_axis.add(x_label)
|
||||
self.x_axis_label_mob = x_label
|
||||
|
||||
y_num_range = float(self.y_max - self.y_min)
|
||||
self.space_unit_to_y = self.y_axis_height/y_num_range
|
||||
self.space_unit_to_y = self.y_axis_height / y_num_range
|
||||
|
||||
if self.y_labeled_nums is None:
|
||||
self.y_labeled_nums = []
|
||||
if self.y_bottom_tick is None:
|
||||
self.y_bottom_tick = self.y_min
|
||||
y_axis = NumberLine(
|
||||
x_min = self.y_min,
|
||||
x_max = self.y_max,
|
||||
unit_size = self.space_unit_to_y,
|
||||
tick_frequency = self.y_tick_frequency,
|
||||
leftmost_tick = self.y_bottom_tick,
|
||||
numbers_with_elongated_ticks = self.y_labeled_nums,
|
||||
color = self.axes_color,
|
||||
line_to_number_vect = LEFT,
|
||||
x_min=self.y_min,
|
||||
x_max=self.y_max,
|
||||
unit_size=self.space_unit_to_y,
|
||||
tick_frequency=self.y_tick_frequency,
|
||||
leftmost_tick=self.y_bottom_tick,
|
||||
numbers_with_elongated_ticks=self.y_labeled_nums,
|
||||
color=self.axes_color,
|
||||
line_to_number_vect=LEFT,
|
||||
)
|
||||
y_axis.shift(self.graph_origin-y_axis.number_to_point(0))
|
||||
y_axis.rotate(np.pi/2, about_point = y_axis.number_to_point(0))
|
||||
y_axis.shift(self.graph_origin - y_axis.number_to_point(0))
|
||||
y_axis.rotate(np.pi / 2, about_point=y_axis.number_to_point(0))
|
||||
if len(self.y_labeled_nums) > 0:
|
||||
if self.exclude_zero_label:
|
||||
self.y_labeled_nums = filter(
|
||||
lambda y : y != 0,
|
||||
lambda y: y != 0,
|
||||
self.y_labeled_nums
|
||||
)
|
||||
y_axis.add_numbers(*self.y_labeled_nums)
|
||||
if self.y_axis_label:
|
||||
y_label = TextMobject(self.y_axis_label)
|
||||
y_label.next_to(
|
||||
y_axis.get_tick_marks(), UP+RIGHT,
|
||||
buff = SMALL_BUFF
|
||||
y_axis.get_tick_marks(), UP + RIGHT,
|
||||
buff=SMALL_BUFF
|
||||
)
|
||||
y_label.shift_onto_screen()
|
||||
y_axis.add(y_label)
|
||||
@@ -131,20 +133,20 @@ class GraphScene(Scene):
|
||||
|
||||
def coords_to_point(self, x, y):
|
||||
assert(hasattr(self, "x_axis") and hasattr(self, "y_axis"))
|
||||
result = self.x_axis.number_to_point(x)[0]*RIGHT
|
||||
result += self.y_axis.number_to_point(y)[1]*UP
|
||||
result = self.x_axis.number_to_point(x)[0] * RIGHT
|
||||
result += self.y_axis.number_to_point(y)[1] * UP
|
||||
return result
|
||||
|
||||
def point_to_coords(self, point):
|
||||
return (self.x_axis.point_to_number(point),
|
||||
return (self.x_axis.point_to_number(point),
|
||||
self.y_axis.point_to_number(point))
|
||||
|
||||
def get_graph(
|
||||
self, func,
|
||||
color = None,
|
||||
x_min = None,
|
||||
x_max = None,
|
||||
):
|
||||
self, func,
|
||||
color=None,
|
||||
x_min=None,
|
||||
x_max=None,
|
||||
):
|
||||
if color is None:
|
||||
color = self.default_graph_colors_cycle.next()
|
||||
if x_min is None:
|
||||
@@ -160,9 +162,9 @@ class GraphScene(Scene):
|
||||
return self.coords_to_point(x, y)
|
||||
|
||||
graph = ParametricFunction(
|
||||
parameterized_function,
|
||||
color = color,
|
||||
num_anchor_points = self.num_graph_anchor_points,
|
||||
parameterized_function,
|
||||
color=color,
|
||||
num_anchor_points=self.num_graph_anchor_points,
|
||||
)
|
||||
graph.underlying_function = func
|
||||
return graph
|
||||
@@ -170,34 +172,36 @@ class GraphScene(Scene):
|
||||
def input_to_graph_point(self, x, graph):
|
||||
return self.coords_to_point(x, graph.underlying_function(x))
|
||||
|
||||
def angle_of_tangent(self, x, graph, dx = 0.01):
|
||||
vect = self.input_to_graph_point(x + dx, graph) - self.input_to_graph_point(x, graph)
|
||||
def angle_of_tangent(self, x, graph, dx=0.01):
|
||||
vect = self.input_to_graph_point(
|
||||
x + dx, graph) - self.input_to_graph_point(x, graph)
|
||||
return angle_of_vector(vect)
|
||||
|
||||
def slope_of_tangent(self, *args, **kwargs):
|
||||
return np.tan(self.angle_of_tangent(*args, **kwargs))
|
||||
|
||||
def get_derivative_graph(self, graph, dx = 0.01, **kwargs):
|
||||
def get_derivative_graph(self, graph, dx=0.01, **kwargs):
|
||||
if "color" not in kwargs:
|
||||
kwargs["color"] = self.default_derivative_color
|
||||
|
||||
def deriv(x):
|
||||
return self.slope_of_tangent(x, graph, dx) / self.space_unit_to_y
|
||||
return self.get_graph(deriv, **kwargs)
|
||||
|
||||
def get_graph_label(
|
||||
self,
|
||||
graph,
|
||||
label = "f(x)",
|
||||
x_val = None,
|
||||
direction = RIGHT,
|
||||
buff = MED_SMALL_BUFF,
|
||||
color = None,
|
||||
):
|
||||
self,
|
||||
graph,
|
||||
label="f(x)",
|
||||
x_val=None,
|
||||
direction=RIGHT,
|
||||
buff=MED_SMALL_BUFF,
|
||||
color=None,
|
||||
):
|
||||
label = TexMobject(label)
|
||||
color = color or graph.get_color()
|
||||
label.set_color(color)
|
||||
if x_val is None:
|
||||
#Search from right to left
|
||||
# Search from right to left
|
||||
for x in np.linspace(self.x_max, self.x_min, 100):
|
||||
point = self.input_to_graph_point(x, graph)
|
||||
if point[1] < FRAME_Y_RADIUS:
|
||||
@@ -206,26 +210,26 @@ class GraphScene(Scene):
|
||||
label.next_to(
|
||||
self.input_to_graph_point(x_val, graph),
|
||||
direction,
|
||||
buff = buff
|
||||
buff=buff
|
||||
)
|
||||
label.shift_onto_screen()
|
||||
return label
|
||||
|
||||
def get_riemann_rectangles(
|
||||
self,
|
||||
self,
|
||||
graph,
|
||||
x_min = None,
|
||||
x_max = None,
|
||||
dx = 0.1,
|
||||
input_sample_type = "left",
|
||||
stroke_width = 1,
|
||||
stroke_color = BLACK,
|
||||
fill_opacity = 1,
|
||||
start_color = None,
|
||||
end_color = None,
|
||||
show_signed_area = True,
|
||||
width_scale_factor = 1.001
|
||||
):
|
||||
x_min=None,
|
||||
x_max=None,
|
||||
dx=0.1,
|
||||
input_sample_type="left",
|
||||
stroke_width=1,
|
||||
stroke_color=BLACK,
|
||||
fill_opacity=1,
|
||||
start_color=None,
|
||||
end_color=None,
|
||||
show_signed_area=True,
|
||||
width_scale_factor=1.001
|
||||
):
|
||||
x_min = x_min if x_min is not None else self.x_min
|
||||
x_max = x_max if x_max is not None else self.x_max
|
||||
if start_color is None:
|
||||
@@ -233,47 +237,47 @@ class GraphScene(Scene):
|
||||
if end_color is None:
|
||||
end_color = self.default_riemann_end_color
|
||||
rectangles = VGroup()
|
||||
x_range = np.arange(x_min, x_max, dx)
|
||||
x_range = np.arange(x_min, x_max, dx)
|
||||
colors = color_gradient([start_color, end_color], len(x_range))
|
||||
for x, color in zip(x_range, colors):
|
||||
if input_sample_type == "left":
|
||||
sample_input = x
|
||||
elif input_sample_type == "right":
|
||||
sample_input = x+dx
|
||||
sample_input = x + dx
|
||||
else:
|
||||
raise Exception("Invalid input sample type")
|
||||
graph_point = self.input_to_graph_point(sample_input, graph)
|
||||
points = VGroup(*map(VectorizedPoint, [
|
||||
self.coords_to_point(x, 0),
|
||||
self.coords_to_point(x+width_scale_factor*dx, 0),
|
||||
self.coords_to_point(x + width_scale_factor * dx, 0),
|
||||
graph_point
|
||||
]))
|
||||
|
||||
rect = Rectangle()
|
||||
rect.replace(points, stretch = True)
|
||||
rect.replace(points, stretch=True)
|
||||
if graph_point[1] < self.graph_origin[1] and show_signed_area:
|
||||
fill_color = invert_color(color)
|
||||
else:
|
||||
fill_color = color
|
||||
rect.set_fill(fill_color, opacity = fill_opacity)
|
||||
rect.set_stroke(stroke_color, width = stroke_width)
|
||||
rect.set_fill(fill_color, opacity=fill_opacity)
|
||||
rect.set_stroke(stroke_color, width=stroke_width)
|
||||
rectangles.add(rect)
|
||||
return rectangles
|
||||
|
||||
def get_riemann_rectangles_list(
|
||||
self,
|
||||
self,
|
||||
graph,
|
||||
n_iterations,
|
||||
max_dx = 0.5,
|
||||
power_base = 2,
|
||||
stroke_width = 1,
|
||||
max_dx=0.5,
|
||||
power_base=2,
|
||||
stroke_width=1,
|
||||
**kwargs
|
||||
):
|
||||
):
|
||||
return [
|
||||
self.get_riemann_rectangles(
|
||||
graph = graph,
|
||||
dx = float(max_dx)/(power_base**n),
|
||||
stroke_width = float(stroke_width)/(power_base**n),
|
||||
graph=graph,
|
||||
dx=float(max_dx) / (power_base**n),
|
||||
stroke_width=float(stroke_width) / (power_base**n),
|
||||
**kwargs
|
||||
)
|
||||
for n in range(n_iterations)
|
||||
@@ -281,17 +285,17 @@ class GraphScene(Scene):
|
||||
|
||||
def transform_between_riemann_rects(self, curr_rects, new_rects, **kwargs):
|
||||
transform_kwargs = {
|
||||
"run_time" : 2,
|
||||
"submobject_mode" : "lagged_start"
|
||||
"run_time": 2,
|
||||
"submobject_mode": "lagged_start"
|
||||
}
|
||||
added_anims = kwargs.get("added_anims", [])
|
||||
transform_kwargs.update(kwargs)
|
||||
curr_rects.align_submobjects(new_rects)
|
||||
x_coords = set() #Keep track of new repetitions
|
||||
x_coords = set() # Keep track of new repetitions
|
||||
for rect in curr_rects:
|
||||
x = rect.get_center()[0]
|
||||
if x in x_coords:
|
||||
rect.set_fill(opacity = 0)
|
||||
rect.set_fill(opacity=0)
|
||||
else:
|
||||
x_coords.add(x)
|
||||
self.play(
|
||||
@@ -302,24 +306,24 @@ class GraphScene(Scene):
|
||||
def get_vertical_line_to_graph(
|
||||
self,
|
||||
x, graph,
|
||||
line_class = Line,
|
||||
line_class=Line,
|
||||
**line_kwargs
|
||||
):
|
||||
):
|
||||
if "color" not in line_kwargs:
|
||||
line_kwargs["color"] = graph.get_color()
|
||||
return line_class(
|
||||
self.coords_to_point(x, 0),
|
||||
self.input_to_graph_point(x, graph),
|
||||
**line_kwargs
|
||||
)
|
||||
)
|
||||
|
||||
def get_vertical_lines_to_graph(
|
||||
self, graph,
|
||||
x_min = None,
|
||||
x_max = None,
|
||||
num_lines = 20,
|
||||
x_min=None,
|
||||
x_max=None,
|
||||
num_lines=20,
|
||||
**kwargs
|
||||
):
|
||||
):
|
||||
x_min = x_min or self.x_min
|
||||
x_max = x_max or self.x_max
|
||||
return VGroup(*[
|
||||
@@ -328,20 +332,20 @@ class GraphScene(Scene):
|
||||
])
|
||||
|
||||
def get_secant_slope_group(
|
||||
self,
|
||||
x, graph,
|
||||
dx = None,
|
||||
dx_line_color = None,
|
||||
df_line_color = None,
|
||||
dx_label = None,
|
||||
df_label = None,
|
||||
include_secant_line = True,
|
||||
secant_line_color = None,
|
||||
secant_line_length = 10,
|
||||
):
|
||||
self,
|
||||
x, graph,
|
||||
dx=None,
|
||||
dx_line_color=None,
|
||||
df_line_color=None,
|
||||
dx_label=None,
|
||||
df_label=None,
|
||||
include_secant_line=True,
|
||||
secant_line_color=None,
|
||||
secant_line_length=10,
|
||||
):
|
||||
"""
|
||||
Resulting group is of the form VGroup(
|
||||
dx_line,
|
||||
dx_line,
|
||||
df_line,
|
||||
dx_label, (if applicable)
|
||||
df_label, (if applicable)
|
||||
@@ -354,21 +358,21 @@ class GraphScene(Scene):
|
||||
group = VGroup()
|
||||
group.kwargs = kwargs
|
||||
|
||||
dx = dx or float(self.x_max - self.x_min)/10
|
||||
dx = dx or float(self.x_max - self.x_min) / 10
|
||||
dx_line_color = dx_line_color or self.default_input_color
|
||||
df_line_color = df_line_color or graph.get_color()
|
||||
|
||||
p1 = self.input_to_graph_point(x, graph)
|
||||
p2 = self.input_to_graph_point(x+dx, graph)
|
||||
interim_point = p2[0]*RIGHT + p1[1]*UP
|
||||
p2 = self.input_to_graph_point(x + dx, graph)
|
||||
interim_point = p2[0] * RIGHT + p1[1] * UP
|
||||
|
||||
group.dx_line = Line(
|
||||
p1, interim_point,
|
||||
color = dx_line_color
|
||||
color=dx_line_color
|
||||
)
|
||||
group.df_line = Line(
|
||||
interim_point, p2,
|
||||
color = df_line_color
|
||||
color=df_line_color
|
||||
)
|
||||
group.add(group.dx_line, group.df_line)
|
||||
|
||||
@@ -383,8 +387,8 @@ class GraphScene(Scene):
|
||||
group.add(group.df_label)
|
||||
|
||||
if len(labels) > 0:
|
||||
max_width = 0.8*group.dx_line.get_width()
|
||||
max_height = 0.8*group.df_line.get_height()
|
||||
max_width = 0.8 * group.dx_line.get_width()
|
||||
max_height = 0.8 * group.df_line.get_height()
|
||||
if labels.get_width() > max_width:
|
||||
labels.scale_to_fit_width(max_width)
|
||||
if labels.get_height() > max_height:
|
||||
@@ -392,40 +396,41 @@ class GraphScene(Scene):
|
||||
|
||||
if dx_label is not None:
|
||||
group.dx_label.next_to(
|
||||
group.dx_line,
|
||||
np.sign(dx)*DOWN,
|
||||
buff = group.dx_label.get_height()/2
|
||||
group.dx_line,
|
||||
np.sign(dx) * DOWN,
|
||||
buff=group.dx_label.get_height() / 2
|
||||
)
|
||||
group.dx_label.set_color(group.dx_line.get_color())
|
||||
|
||||
if df_label is not None:
|
||||
group.df_label.next_to(
|
||||
group.df_line,
|
||||
np.sign(dx)*RIGHT,
|
||||
buff = group.df_label.get_height()/2
|
||||
group.df_line,
|
||||
np.sign(dx) * RIGHT,
|
||||
buff=group.df_label.get_height() / 2
|
||||
)
|
||||
group.df_label.set_color(group.df_line.get_color())
|
||||
|
||||
if include_secant_line:
|
||||
secant_line_color = secant_line_color or self.default_derivative_color
|
||||
group.secant_line = Line(p1, p2, color = secant_line_color)
|
||||
group.secant_line = Line(p1, p2, color=secant_line_color)
|
||||
group.secant_line.scale_in_place(
|
||||
secant_line_length/group.secant_line.get_length()
|
||||
secant_line_length / group.secant_line.get_length()
|
||||
)
|
||||
group.add(group.secant_line)
|
||||
|
||||
return group
|
||||
|
||||
def animate_secant_slope_group_change(
|
||||
self, secant_slope_group,
|
||||
target_dx = None,
|
||||
target_x = None,
|
||||
run_time = 3,
|
||||
added_anims = None,
|
||||
self, secant_slope_group,
|
||||
target_dx=None,
|
||||
target_x=None,
|
||||
run_time=3,
|
||||
added_anims=None,
|
||||
**anim_kwargs
|
||||
):
|
||||
):
|
||||
if target_dx is None and target_x is None:
|
||||
raise Exception("At least one of target_x and target_dx must not be None")
|
||||
raise Exception(
|
||||
"At least one of target_x and target_dx must not be None")
|
||||
if added_anims is None:
|
||||
added_anims = []
|
||||
|
||||
@@ -435,6 +440,7 @@ class GraphScene(Scene):
|
||||
target_dx = start_dx
|
||||
if target_x is None:
|
||||
target_x = start_x
|
||||
|
||||
def update_func(group, alpha):
|
||||
dx = interpolate(start_dx, target_dx, alpha)
|
||||
x = interpolate(start_x, target_x, alpha)
|
||||
@@ -448,31 +454,10 @@ class GraphScene(Scene):
|
||||
self.play(
|
||||
UpdateFromAlphaFunc(
|
||||
secant_slope_group, update_func,
|
||||
run_time = run_time,
|
||||
run_time=run_time,
|
||||
**anim_kwargs
|
||||
),
|
||||
*added_anims
|
||||
)
|
||||
secant_slope_group.kwargs["x"] = target_x
|
||||
secant_slope_group.kwargs["dx"] = target_dx
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,10 +6,11 @@ from scene.scene import Scene
|
||||
from camera.moving_camera import MovingCamera
|
||||
from mobject.frame import ScreenRectangle
|
||||
|
||||
|
||||
class MovingCameraScene(Scene):
|
||||
def setup(self):
|
||||
self.camera_frame = ScreenRectangle(height = FRAME_HEIGHT)
|
||||
self.camera_frame.set_stroke(width = 0)
|
||||
self.camera_frame = ScreenRectangle(height=FRAME_HEIGHT)
|
||||
self.camera_frame.set_stroke(width=0)
|
||||
self.camera = MovingCamera(
|
||||
self.camera_frame, **self.camera_config
|
||||
)
|
||||
@@ -22,4 +23,4 @@ class MovingCameraScene(Scene):
|
||||
# When the camera is moving, so is everything,
|
||||
return self.mobjects
|
||||
else:
|
||||
return moving_mobjects
|
||||
return moving_mobjects
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import numpy as np
|
||||
|
||||
from scene.scene import Scene
|
||||
from animation.transform import Transform
|
||||
from mobject.mobject import Mobject
|
||||
|
||||
from constants import *
|
||||
|
||||
|
||||
class ReconfigurableScene(Scene):
|
||||
CONFIG = {
|
||||
"allow_recursion" : True,
|
||||
"allow_recursion": True,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
self.states = []
|
||||
self.num_recursions = 0
|
||||
|
||||
def transition_to_alt_config(
|
||||
self,
|
||||
return_to_original_configuration = True,
|
||||
transformation_kwargs = None,
|
||||
self,
|
||||
return_to_original_configuration=True,
|
||||
transformation_kwargs=None,
|
||||
**new_config
|
||||
):
|
||||
):
|
||||
if transformation_kwargs is None:
|
||||
transformation_kwargs = {}
|
||||
original_state = self.get_state()
|
||||
@@ -30,41 +30,37 @@ class ReconfigurableScene(Scene):
|
||||
if not self.allow_recursion:
|
||||
return
|
||||
alt_scene = self.__class__(
|
||||
skip_animations = True,
|
||||
allow_recursion = False,
|
||||
skip_animations=True,
|
||||
allow_recursion=False,
|
||||
**new_config
|
||||
)
|
||||
alt_state = alt_scene.states[len(self.states)-1]
|
||||
alt_state = alt_scene.states[len(self.states) - 1]
|
||||
|
||||
if return_to_original_configuration:
|
||||
self.clear()
|
||||
self.transition_between_states(
|
||||
state_copy, alt_state,
|
||||
state_copy, alt_state,
|
||||
**transformation_kwargs
|
||||
)
|
||||
self.transition_between_states(
|
||||
state_copy, original_state,
|
||||
state_copy, original_state,
|
||||
**transformation_kwargs
|
||||
)
|
||||
self.clear()
|
||||
self.add(*original_state)
|
||||
else:
|
||||
self.transition_between_states(
|
||||
original_state, alt_state,
|
||||
original_state, alt_state,
|
||||
**transformation_kwargs
|
||||
)
|
||||
self.__dict__.update(new_config)
|
||||
|
||||
def get_state(self):
|
||||
# Want to return a mobject that maintains the most
|
||||
# Want to return a mobject that maintains the most
|
||||
# structure. The way to do that is to extract only
|
||||
# those that aren't inside another.
|
||||
top_level_mobjects = self.get_top_level_mobjects()
|
||||
return Mobject(*self.get_top_level_mobjects())
|
||||
|
||||
def transition_between_states(self, start_state, target_state, **kwargs):
|
||||
self.play(Transform(start_state, target_state, **kwargs))
|
||||
self.wait()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ class SampleSpaceScene(Scene):
|
||||
|
||||
def get_division_change_animations(
|
||||
self, sample_space, parts, p_list,
|
||||
dimension = 1,
|
||||
new_label_kwargs = None,
|
||||
dimension=1,
|
||||
new_label_kwargs=None,
|
||||
**kwargs
|
||||
):
|
||||
):
|
||||
if new_label_kwargs is None:
|
||||
new_label_kwargs = {}
|
||||
anims = []
|
||||
@@ -34,12 +34,12 @@ class SampleSpaceScene(Scene):
|
||||
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.replace(space_copy, stretch=True)
|
||||
part.stretch(p, dimension)
|
||||
parts.target.arrange_submobjects(vect, buff = 0)
|
||||
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:
|
||||
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(
|
||||
@@ -55,7 +55,7 @@ class SampleSpaceScene(Scene):
|
||||
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,
|
||||
dimension=1,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@@ -63,19 +63,19 @@ class SampleSpaceScene(Scene):
|
||||
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,
|
||||
dimension=0,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
def get_conditional_change_anims(
|
||||
self, sub_sample_space_index, value, post_rects = None,
|
||||
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,
|
||||
dimension=0,
|
||||
**kwargs
|
||||
)
|
||||
if post_rects is not None:
|
||||
@@ -94,10 +94,10 @@ class SampleSpaceScene(Scene):
|
||||
for i in range(2)
|
||||
])
|
||||
|
||||
def get_posterior_rectangles(self, buff = MED_LARGE_BUFF):
|
||||
def get_posterior_rectangles(self, buff=MED_LARGE_BUFF):
|
||||
prior_rects = self.get_prior_rectangles()
|
||||
areas = [
|
||||
rect.get_width()*rect.get_height()
|
||||
rect.get_width() * rect.get_height()
|
||||
for rect in prior_rects
|
||||
]
|
||||
total_area = sum(areas)
|
||||
@@ -105,19 +105,19 @@ class SampleSpaceScene(Scene):
|
||||
|
||||
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_height(total_height * area / total_area)
|
||||
rect.stretch_to_fit_width(
|
||||
area/rect.get_height()
|
||||
area / rect.get_height()
|
||||
)
|
||||
post_rects.arrange_submobjects(DOWN, buff = 0)
|
||||
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
|
||||
):
|
||||
self, post_rects, labels, direction=RIGHT, **kwargs
|
||||
):
|
||||
return self.sample_space.get_subdivision_braces_and_labels(
|
||||
post_rects, labels, direction, **kwargs
|
||||
)
|
||||
@@ -132,7 +132,7 @@ class SampleSpaceScene(Scene):
|
||||
|
||||
def get_posterior_rectangle_change_anims(self, post_rects):
|
||||
def update_rects(rects):
|
||||
new_rects = self.get_posterior_rectangles()
|
||||
new_rects = self.get_posterior_rectangles()
|
||||
Transform(rects, new_rects).update(1)
|
||||
if hasattr(rects, "braces"):
|
||||
self.update_posterior_braces(rects)
|
||||
|
||||
170
scene/scene.py
170
scene/scene.py
@@ -1,4 +1,3 @@
|
||||
import copy
|
||||
import inspect
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
@@ -6,11 +5,8 @@ import os
|
||||
import random
|
||||
import shutil
|
||||
import subprocess as sp
|
||||
import time
|
||||
import warnings
|
||||
|
||||
from PIL import Image
|
||||
from colour import Color
|
||||
from tqdm import tqdm as ProgressDisplay
|
||||
|
||||
from constants import *
|
||||
@@ -20,11 +16,11 @@ from animation.transform import MoveToTarget
|
||||
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
|
||||
|
||||
|
||||
def add_extension_if_not_present(file_name, extension):
|
||||
# This could conceivably be smarter about handling existing differing extensions
|
||||
if(file_name[-len(extension):] != extension):
|
||||
@@ -32,29 +28,32 @@ def add_extension_if_not_present(file_name, extension):
|
||||
else:
|
||||
return file_name
|
||||
|
||||
|
||||
class Scene(Container):
|
||||
CONFIG = {
|
||||
"camera_class" : Camera,
|
||||
"camera_config" : {},
|
||||
"frame_duration" : LOW_QUALITY_FRAME_DURATION,
|
||||
"construct_args" : [],
|
||||
"skip_animations" : False,
|
||||
"ignore_waits" : False,
|
||||
"write_to_movie" : False,
|
||||
"save_frames" : False,
|
||||
"save_pngs" : False,
|
||||
"pngs_mode" : "RGBA",
|
||||
"output_directory" : ANIMATIONS_DIR,
|
||||
"movie_file_extension" : ".mp4",
|
||||
"name" : None,
|
||||
"always_continually_update" : False,
|
||||
"random_seed" : 0,
|
||||
"start_at_animation_number" : None,
|
||||
"end_at_animation_number" : None,
|
||||
"include_render_quality_in_output_directory" : True,
|
||||
"camera_class": Camera,
|
||||
"camera_config": {},
|
||||
"frame_duration": LOW_QUALITY_FRAME_DURATION,
|
||||
"construct_args": [],
|
||||
"skip_animations": False,
|
||||
"ignore_waits": False,
|
||||
"write_to_movie": False,
|
||||
"save_frames": False,
|
||||
"save_pngs": False,
|
||||
"pngs_mode": "RGBA",
|
||||
"output_directory": ANIMATIONS_DIR,
|
||||
"movie_file_extension": ".mp4",
|
||||
"name": None,
|
||||
"always_continually_update": False,
|
||||
"random_seed": 0,
|
||||
"start_at_animation_number": None,
|
||||
"end_at_animation_number": None,
|
||||
"include_render_quality_in_output_directory": True,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
Container.__init__(self, **kwargs) # Perhaps allow passing in a non-empty *mobjects parameter?
|
||||
# Perhaps allow passing in a non-empty *mobjects parameter?
|
||||
Container.__init__(self, **kwargs)
|
||||
self.camera = self.camera_class(**self.camera_config)
|
||||
self.mobjects = []
|
||||
self.continual_animations = []
|
||||
@@ -86,7 +85,7 @@ class Scene(Container):
|
||||
|
||||
if self.write_to_movie:
|
||||
self.close_movie_pipe()
|
||||
print("Played a total of %d animations"%self.num_plays)
|
||||
print("Played a total of %d animations" % self.num_plays)
|
||||
|
||||
def setup(self):
|
||||
"""
|
||||
@@ -101,7 +100,7 @@ class Scene(Container):
|
||||
base.setup(self)
|
||||
|
||||
def construct(self):
|
||||
pass #To be implemented in subclasses
|
||||
pass # To be implemented in subclasses
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -128,7 +127,7 @@ class Scene(Container):
|
||||
def get_attrs(self, *keys):
|
||||
return [getattr(self, key) for key in keys]
|
||||
|
||||
### Only these methods should touch the camera
|
||||
# Only these methods should touch the camera
|
||||
|
||||
def set_camera(self, camera):
|
||||
self.camera = camera
|
||||
@@ -152,12 +151,12 @@ class Scene(Container):
|
||||
self.camera.capture_mobjects(mobjects, **kwargs)
|
||||
|
||||
def update_frame(
|
||||
self,
|
||||
mobjects = None,
|
||||
background = None,
|
||||
include_submobjects = True,
|
||||
dont_update_when_skipping = True,
|
||||
**kwargs):
|
||||
self,
|
||||
mobjects=None,
|
||||
background=None,
|
||||
include_submobjects=True,
|
||||
dont_update_when_skipping=True,
|
||||
**kwargs):
|
||||
if self.skip_animations and dont_update_when_skipping:
|
||||
return
|
||||
if mobjects is None:
|
||||
@@ -179,7 +178,7 @@ class Scene(Container):
|
||||
self.clear()
|
||||
###
|
||||
|
||||
def continual_update(self, dt = None):
|
||||
def continual_update(self, dt=None):
|
||||
if dt is None:
|
||||
dt = self.frame_duration
|
||||
for continual_animation in self.continual_animations:
|
||||
@@ -190,10 +189,10 @@ class Scene(Container):
|
||||
for continual_animation in continual_animations:
|
||||
continual_animation.begin_wind_down(wind_down_time)
|
||||
self.wait(wind_down_time)
|
||||
#TODO, this is not done with the remove method so as to
|
||||
#keep the relevant mobjects. Better way?
|
||||
# TODO, this is not done with the remove method so as to
|
||||
# keep the relevant mobjects. Better way?
|
||||
self.continual_animations = filter(
|
||||
lambda ca : ca in continual_animations,
|
||||
lambda ca: ca in continual_animations,
|
||||
self.continual_animations
|
||||
)
|
||||
|
||||
@@ -207,6 +206,7 @@ class Scene(Container):
|
||||
# of another mobject from the scene
|
||||
mobjects = self.get_mobjects()
|
||||
families = [m.submobject_family() for m in mobjects]
|
||||
|
||||
def is_top_level(mobject):
|
||||
num_families = sum([
|
||||
(mobject in family)
|
||||
@@ -239,7 +239,7 @@ class Scene(Container):
|
||||
mobjects, continual_animations = self.separate_mobjects_and_continual_animations(
|
||||
mobjects_or_continual_animations
|
||||
)
|
||||
self.restructure_mobjects(to_remove = mobjects)
|
||||
self.restructure_mobjects(to_remove=mobjects)
|
||||
self.mobjects += mobjects
|
||||
self.continual_animations += continual_animations
|
||||
return self
|
||||
@@ -249,7 +249,7 @@ class Scene(Container):
|
||||
So a scene can just add all mobjects it's defined up to that point
|
||||
by calling add_mobjects_among(locals().values())
|
||||
"""
|
||||
mobjects = filter(lambda x : isinstance(x, Mobject), values)
|
||||
mobjects = filter(lambda x: isinstance(x, Mobject), values)
|
||||
self.add(*mobjects)
|
||||
return self
|
||||
|
||||
@@ -263,17 +263,17 @@ class Scene(Container):
|
||||
self.restructure_mobjects(mobjects, list_name, False)
|
||||
|
||||
self.continual_animations = filter(
|
||||
lambda ca : ca not in continual_animations and \
|
||||
ca.mobject not in to_remove,
|
||||
lambda ca: ca not in continual_animations and
|
||||
ca.mobject not in to_remove,
|
||||
self.continual_animations
|
||||
)
|
||||
return self
|
||||
|
||||
def restructure_mobjects(
|
||||
self, to_remove,
|
||||
mobject_list_name = "mobjects",
|
||||
extract_families = True
|
||||
):
|
||||
self, to_remove,
|
||||
mobject_list_name="mobjects",
|
||||
extract_families=True
|
||||
):
|
||||
"""
|
||||
In cases where the scene contains a group, e.g. Group(m1, m2, m3), but one
|
||||
of its submobjects is removed, e.g. scene.remove(m1), the list of mobjects
|
||||
@@ -289,6 +289,7 @@ class Scene(Container):
|
||||
|
||||
def get_restructured_mobject_list(self, mobjects, to_remove):
|
||||
new_mobjects = []
|
||||
|
||||
def add_safe_mobjects_from_list(list_to_examine, set_to_remove):
|
||||
for mob in list_to_examine:
|
||||
if mob in set_to_remove:
|
||||
@@ -364,7 +365,7 @@ class Scene(Container):
|
||||
run_time = np.max([animation.run_time for animation in animations])
|
||||
time_progression = self.get_time_progression(run_time)
|
||||
time_progression.set_description("".join([
|
||||
"Animation %d: "%self.num_plays,
|
||||
"Animation %d: " % self.num_plays,
|
||||
str(animations[0]),
|
||||
(", etc." if len(animations) > 1 else ""),
|
||||
]))
|
||||
@@ -383,17 +384,18 @@ class Scene(Container):
|
||||
"""
|
||||
animations = []
|
||||
state = {
|
||||
"curr_method" : None,
|
||||
"last_method" : None,
|
||||
"method_args" : [],
|
||||
"curr_method": None,
|
||||
"last_method": None,
|
||||
"method_args": [],
|
||||
}
|
||||
|
||||
def compile_method(state):
|
||||
if state["curr_method"] is None:
|
||||
return
|
||||
mobject = state["curr_method"].im_self
|
||||
if state["last_method"] and state["last_method"].im_self is mobject:
|
||||
animations.pop()
|
||||
#method should already have target then.
|
||||
# method should already have target then.
|
||||
else:
|
||||
mobject.generate_target()
|
||||
#
|
||||
@@ -402,7 +404,7 @@ class Scene(Container):
|
||||
else:
|
||||
method_kwargs = {}
|
||||
state["curr_method"].im_func(
|
||||
mobject.target,
|
||||
mobject.target,
|
||||
*state["method_args"],
|
||||
**method_kwargs
|
||||
)
|
||||
@@ -453,7 +455,7 @@ class Scene(Container):
|
||||
|
||||
# Paint all non-moving objects onto the screen, so they don't
|
||||
# have to be rendered every frame
|
||||
self.update_frame(excluded_mobjects = moving_mobjects)
|
||||
self.update_frame(excluded_mobjects=moving_mobjects)
|
||||
static_image = self.get_frame()
|
||||
for t in self.get_animation_time_progression(animations):
|
||||
for animation in animations:
|
||||
@@ -484,27 +486,28 @@ class Scene(Container):
|
||||
return self.mobjects_from_last_animation
|
||||
return []
|
||||
|
||||
def wait(self, duration = DEFAULT_WAIT_TIME):
|
||||
def wait(self, duration=DEFAULT_WAIT_TIME):
|
||||
if self.should_continually_update():
|
||||
for t in self.get_time_progression(duration):
|
||||
self.continual_update()
|
||||
self.update_frame()
|
||||
self.add_frames(self.get_frame())
|
||||
elif self.skip_animations:
|
||||
#Do nothing
|
||||
# Do nothing
|
||||
return self
|
||||
else:
|
||||
self.update_frame()
|
||||
self.add_frames(*[self.get_frame()]*int(duration / self.frame_duration))
|
||||
self.add_frames(*[self.get_frame()] *
|
||||
int(duration / self.frame_duration))
|
||||
return self
|
||||
|
||||
def wait_to(self, time, assert_positive = True):
|
||||
if self.ignore_waits:
|
||||
def wait_to(self, time, assert_positive=True):
|
||||
if self.ignore_waits:
|
||||
return
|
||||
time -= self.current_scene_time
|
||||
if assert_positive:
|
||||
if assert_positive:
|
||||
assert(time >= 0)
|
||||
elif time < 0:
|
||||
elif time < 0:
|
||||
return
|
||||
|
||||
self.wait(time)
|
||||
@@ -522,23 +525,24 @@ class Scene(Container):
|
||||
def add_frames(self, *frames):
|
||||
if self.skip_animations:
|
||||
return
|
||||
self.current_scene_time += len(frames)*self.frame_duration
|
||||
self.current_scene_time += len(frames) * self.frame_duration
|
||||
if self.write_to_movie:
|
||||
for frame in frames:
|
||||
if self.save_pngs:
|
||||
self.save_image("frame" + str(self.frame_num), self.pngs_mode, True)
|
||||
self.save_image(
|
||||
"frame" + str(self.frame_num), self.pngs_mode, True)
|
||||
self.frame_num = self.frame_num + 1
|
||||
self.writing_process.stdin.write(frame.tostring())
|
||||
if self.save_frames:
|
||||
self.saved_frames += list(frames)
|
||||
|
||||
#Display methods
|
||||
# Display methods
|
||||
|
||||
def show_frame(self):
|
||||
self.update_frame(dont_update_when_skipping = False)
|
||||
self.update_frame(dont_update_when_skipping=False)
|
||||
self.get_image().show()
|
||||
|
||||
def get_image_file_path(self, name = None, dont_update = False):
|
||||
def get_image_file_path(self, name=None, dont_update=False):
|
||||
folder = "images"
|
||||
if dont_update:
|
||||
folder = str(self)
|
||||
@@ -546,23 +550,23 @@ class Scene(Container):
|
||||
file_name = add_extension_if_not_present(name or str(self), ".png")
|
||||
return os.path.join(path, file_name)
|
||||
|
||||
def save_image(self, name = None, mode = "RGB", dont_update = False):
|
||||
def save_image(self, name=None, mode="RGB", dont_update=False):
|
||||
path = self.get_image_file_path(name, dont_update)
|
||||
directory_path = os.path.dirname(path)
|
||||
if not os.path.exists(directory_path):
|
||||
os.makedirs(directory_path)
|
||||
if not dont_update:
|
||||
self.update_frame(dont_update_when_skipping = False)
|
||||
self.update_frame(dont_update_when_skipping=False)
|
||||
image = self.get_image()
|
||||
image = image.convert(mode)
|
||||
image.save(path)
|
||||
|
||||
def get_movie_file_path(self, name = None, extension = None):
|
||||
def get_movie_file_path(self, name=None, extension=None):
|
||||
directory = self.output_directory
|
||||
if self.include_render_quality_in_output_directory:
|
||||
sub_dir = "%dp%d"%(
|
||||
sub_dir = "%dp%d" % (
|
||||
self.camera.pixel_shape[0],
|
||||
int(1.0/self.frame_duration)
|
||||
int(1.0 / self.frame_duration)
|
||||
)
|
||||
directory = os.path.join(directory, sub_dir)
|
||||
if not os.path.exists(directory):
|
||||
@@ -581,21 +585,21 @@ class Scene(Container):
|
||||
name = str(self)
|
||||
file_path = self.get_movie_file_path(name)
|
||||
temp_file_path = file_path.replace(name, name + "Temp")
|
||||
print("Writing to %s"%temp_file_path)
|
||||
print("Writing to %s" % temp_file_path)
|
||||
self.args_to_rename_file = (temp_file_path, file_path)
|
||||
|
||||
fps = int(1/self.frame_duration)
|
||||
fps = int(1 / self.frame_duration)
|
||||
height, width = self.camera.pixel_shape
|
||||
|
||||
|
||||
command = [
|
||||
FFMPEG_BIN,
|
||||
'-y', # overwrite output file if it exists
|
||||
'-y', # overwrite output file if it exists
|
||||
'-f', 'rawvideo',
|
||||
'-s', '%dx%d'%(width, height), # size of one frame
|
||||
'-s', '%dx%d' % (width, height), # size of one frame
|
||||
'-pix_fmt', 'rgba',
|
||||
'-r', str(fps), # frames per second
|
||||
'-i', '-', # The imput comes from a pipe
|
||||
'-an', # Tells FFMPEG not to expect any audio
|
||||
'-r', str(fps), # frames per second
|
||||
'-i', '-', # The imput comes from a pipe
|
||||
'-an', # Tells FFMPEG not to expect any audio
|
||||
'-loglevel', 'error',
|
||||
]
|
||||
if self.movie_file_extension == ".mov":
|
||||
@@ -603,7 +607,7 @@ class Scene(Container):
|
||||
# should be transparent.
|
||||
command += [
|
||||
'-vcodec', 'png',
|
||||
]
|
||||
]
|
||||
else:
|
||||
command += [
|
||||
'-vcodec', 'libx264',
|
||||
@@ -621,14 +625,6 @@ class Scene(Container):
|
||||
else:
|
||||
os.rename(*self.args_to_rename_file)
|
||||
|
||||
|
||||
class EndSceneEarlyException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import cv2
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
|
||||
from tqdm import tqdm as show_progress
|
||||
|
||||
@@ -11,25 +9,25 @@ from scene.scene import Scene
|
||||
|
||||
class SceneFromVideo(Scene):
|
||||
def construct(self, file_name,
|
||||
freeze_last_frame = True,
|
||||
time_range = None):
|
||||
freeze_last_frame=True,
|
||||
time_range=None):
|
||||
cap = cv2.VideoCapture(file_name)
|
||||
self.shape = (
|
||||
int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)),
|
||||
int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
|
||||
)
|
||||
fps = cap.get(cv2.cv.CV_CAP_PROP_FPS)
|
||||
self.frame_duration = 1.0/fps
|
||||
self.frame_duration = 1.0 / fps
|
||||
frame_count = int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_COUNT))
|
||||
if time_range is None:
|
||||
start_frame = 0
|
||||
end_frame = frame_count
|
||||
else:
|
||||
start_frame, end_frame = map(lambda t : fps*t, time_range)
|
||||
start_frame, end_frame = map(lambda t: fps * t, time_range)
|
||||
|
||||
frame_count = end_frame - start_frame
|
||||
print("Reading in " + file_name + "...")
|
||||
for count in show_progress(range(start_frame, end_frame+1)):
|
||||
for count in show_progress(range(start_frame, end_frame + 1)):
|
||||
returned, frame = cap.read()
|
||||
if not returned:
|
||||
break
|
||||
@@ -41,18 +39,17 @@ class SceneFromVideo(Scene):
|
||||
if freeze_last_frame and len(self.frames) > 0:
|
||||
self.original_background = self.background = self.frames[-1]
|
||||
|
||||
def apply_gaussian_blur(self, ksize = (5, 5), sigmaX = 5):
|
||||
def apply_gaussian_blur(self, ksize=(5, 5), sigmaX=5):
|
||||
self.frames = [
|
||||
cv2.GaussianBlur(frame, ksize, sigmaX)
|
||||
for frame in self.frames
|
||||
]
|
||||
|
||||
def apply_edge_detection(self, threshold1 = 50, threshold2 = 100):
|
||||
def apply_edge_detection(self, threshold1=50, threshold2=100):
|
||||
edged_frames = [
|
||||
cv2.Canny(frame, threshold1, threshold2)
|
||||
for frame in self.frames
|
||||
]
|
||||
for index in range(len(self.frames)):
|
||||
for i in range(3):
|
||||
self.frames[index][:,:,i] = edged_frames[index]
|
||||
|
||||
self.frames[index][:, :, i] = edged_frames[index]
|
||||
|
||||
@@ -5,8 +5,6 @@ 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
|
||||
@@ -14,19 +12,20 @@ from utils.iterables import list_update
|
||||
|
||||
class ThreeDScene(Scene):
|
||||
CONFIG = {
|
||||
"camera_class" : ThreeDCamera,
|
||||
"ambient_camera_rotation" : None,
|
||||
"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 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):
|
||||
def begin_ambient_camera_rotation(self, rate=0.01):
|
||||
self.ambient_camera_rotation = ContinualMovement(
|
||||
self.camera.rotation_mobject,
|
||||
direction = UP,
|
||||
rate = rate
|
||||
direction=UP,
|
||||
rate=rate
|
||||
)
|
||||
self.add(self.ambient_camera_rotation)
|
||||
|
||||
@@ -37,18 +36,19 @@ class ThreeDScene(Scene):
|
||||
|
||||
def move_camera(
|
||||
self,
|
||||
phi = None, theta = None, distance = None,
|
||||
center_x = None, center_y = None, center_z = None,
|
||||
added_anims = [],
|
||||
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)
|
||||
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,
|
||||
|
||||
@@ -4,29 +4,29 @@ import numpy as np
|
||||
|
||||
from scene.scene import Scene
|
||||
from animation.creation import FadeIn
|
||||
from camera.camera import Camera
|
||||
from camera.moving_camera import MovingCamera
|
||||
from mobject.mobject import Mobject
|
||||
from mobject.geometry import Rectangle
|
||||
|
||||
from constants import *
|
||||
|
||||
|
||||
class ZoomedScene(Scene):
|
||||
"""
|
||||
Move around self.little_rectangle to determine
|
||||
which part of the screen is zoomed in on.
|
||||
"""
|
||||
CONFIG = {
|
||||
"zoomed_canvas_frame_shape" : (3, 3),
|
||||
"zoomed_canvas_center" : None,
|
||||
"zoomed_canvas_corner" : UP+RIGHT,
|
||||
"zoomed_canvas_corner_buff" : DEFAULT_MOBJECT_TO_EDGE_BUFFER,
|
||||
"zoomed_camera_background" : None,
|
||||
"little_rectangle_start_position" : ORIGIN,
|
||||
"zoom_factor" : 6,
|
||||
"square_color" : WHITE,
|
||||
"zoom_activated" : False,
|
||||
"zoomed_canvas_frame_shape": (3, 3),
|
||||
"zoomed_canvas_center": None,
|
||||
"zoomed_canvas_corner": UP + RIGHT,
|
||||
"zoomed_canvas_corner_buff": DEFAULT_MOBJECT_TO_EDGE_BUFFER,
|
||||
"zoomed_camera_background": None,
|
||||
"little_rectangle_start_position": ORIGIN,
|
||||
"zoom_factor": 6,
|
||||
"square_color": WHITE,
|
||||
"zoom_activated": False,
|
||||
}
|
||||
|
||||
def activate_zooming(self):
|
||||
self.generate_big_rectangle()
|
||||
self.setup_zoomed_canvas()
|
||||
@@ -52,47 +52,46 @@ class ZoomedScene(Scene):
|
||||
def generate_big_rectangle(self):
|
||||
height, width = self.zoomed_canvas_frame_shape
|
||||
self.big_rectangle = Rectangle(
|
||||
height = height,
|
||||
width = width,
|
||||
color = self.square_color
|
||||
height=height,
|
||||
width=width,
|
||||
color=self.square_color
|
||||
)
|
||||
if self.zoomed_canvas_center is not None:
|
||||
self.big_rectangle.shift(self.zoomed_canvas_center)
|
||||
elif self.zoomed_canvas_corner is not None:
|
||||
self.big_rectangle.to_corner(
|
||||
self.zoomed_canvas_corner,
|
||||
buff = self.zoomed_canvas_corner_buff
|
||||
buff=self.zoomed_canvas_corner_buff
|
||||
)
|
||||
self.add(self.big_rectangle)
|
||||
|
||||
|
||||
def setup_zoomed_canvas(self):
|
||||
upper_left = self.big_rectangle.get_corner(UP+LEFT)
|
||||
lower_right = self.big_rectangle.get_corner(DOWN+RIGHT)
|
||||
upper_left = self.big_rectangle.get_corner(UP + LEFT)
|
||||
lower_right = self.big_rectangle.get_corner(DOWN + RIGHT)
|
||||
pixel_coords = self.camera.points_to_pixel_coords(
|
||||
np.array([upper_left, lower_right])
|
||||
)
|
||||
self.zoomed_canvas_pixel_indices = pixel_coords
|
||||
(up, left), (down, right) = pixel_coords
|
||||
self.zoomed_canvas_pixel_shape = (
|
||||
right-left,
|
||||
down-up,
|
||||
right - left,
|
||||
down - up,
|
||||
)
|
||||
|
||||
def setup_zoomed_camera(self):
|
||||
self.little_rectangle = self.big_rectangle.copy()
|
||||
self.little_rectangle.scale(1./self.zoom_factor)
|
||||
self.little_rectangle.scale(1. / self.zoom_factor)
|
||||
self.little_rectangle.move_to(
|
||||
self.little_rectangle_start_position
|
||||
)
|
||||
self.zoomed_camera = MovingCamera(
|
||||
self.little_rectangle,
|
||||
pixel_shape = self.zoomed_canvas_pixel_shape,
|
||||
background = self.zoomed_camera_background
|
||||
pixel_shape=self.zoomed_canvas_pixel_shape,
|
||||
background=self.zoomed_camera_background
|
||||
)
|
||||
self.add(self.little_rectangle)
|
||||
#TODO, is there a better way to hanld this?
|
||||
self.zoomed_camera.adjusted_thickness = lambda x : x
|
||||
# TODO, is there a better way to hanld this?
|
||||
self.zoomed_camera.adjusted_thickness = lambda x: x
|
||||
|
||||
def get_frame(self):
|
||||
frame = Scene.get_frame(self)
|
||||
@@ -105,11 +104,12 @@ class ZoomedScene(Scene):
|
||||
self.camera.set_pixel_array(pixel_array)
|
||||
if self.zoom_activated:
|
||||
(up, left), (down, right) = self.zoomed_canvas_pixel_indices
|
||||
self.zoomed_camera.set_pixel_array(pixel_array[left:right, up:down])
|
||||
self.zoomed_camera.set_pixel_array(
|
||||
pixel_array[left:right, up:down])
|
||||
|
||||
def set_camera_background(self, background):
|
||||
self.set_camera_pixel_array(self, background)
|
||||
#TODO, check this...
|
||||
# TODO, check this...
|
||||
|
||||
def reset_camera(self):
|
||||
self.camera.reset()
|
||||
@@ -125,7 +125,7 @@ class ZoomedScene(Scene):
|
||||
self.zoomed_camera.capture_mobjects(
|
||||
mobjects, **kwargs
|
||||
)
|
||||
|
||||
|
||||
def get_moving_mobjects(self, *animations):
|
||||
moving_mobjects = Scene.get_moving_mobjects(self, *animations)
|
||||
if self.zoom_activated and self.little_rectangle in moving_mobjects:
|
||||
@@ -133,8 +133,3 @@ class ZoomedScene(Scene):
|
||||
return self.mobjects
|
||||
else:
|
||||
return moving_mobjects
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
107
utils/bezier.py
107
utils/bezier.py
@@ -5,18 +5,20 @@ from utils.simple_functions import choose
|
||||
|
||||
CLOSED_THRESHOLD = 0.0
|
||||
|
||||
|
||||
def bezier(points):
|
||||
n = len(points) - 1
|
||||
return lambda t : sum([
|
||||
((1-t)**(n-k))*(t**k)*choose(n, k)*point
|
||||
return lambda t: sum([
|
||||
((1 - t)**(n - k)) * (t**k) * choose(n, k) * point
|
||||
for k, point in enumerate(points)
|
||||
])
|
||||
|
||||
|
||||
def partial_bezier_points(points, a, b):
|
||||
"""
|
||||
Given an array of points which define
|
||||
Given an array of points which define
|
||||
a bezier curve, and two numbers 0<=a<b<=1,
|
||||
return an array of the same size, which
|
||||
return an array of the same size, which
|
||||
describes the portion of the original bezier
|
||||
curve on the interval [a, b].
|
||||
|
||||
@@ -27,7 +29,7 @@ def partial_bezier_points(points, a, b):
|
||||
for i in range(len(points))
|
||||
])
|
||||
return np.array([
|
||||
bezier(a_to_1[:i+1])((b-a)/(1.-a))
|
||||
bezier(a_to_1[:i + 1])((b - a) / (1. - a))
|
||||
for i in range(len(points))
|
||||
])
|
||||
|
||||
@@ -35,20 +37,24 @@ def partial_bezier_points(points, a, b):
|
||||
# Linear interpolation variants
|
||||
|
||||
def interpolate(start, end, alpha):
|
||||
return (1-alpha)*start + alpha*end
|
||||
return (1 - alpha) * start + alpha * end
|
||||
|
||||
|
||||
def mid(start, end):
|
||||
return (start + end)/2.0
|
||||
return (start + end) / 2.0
|
||||
|
||||
|
||||
def inverse_interpolate(start, end, value):
|
||||
return np.true_divide(value - start, end - start)
|
||||
|
||||
|
||||
def match_interpolate(new_start, new_end, old_start, old_end, old_value):
|
||||
return interpolate(
|
||||
new_start, new_end,
|
||||
new_start, new_end,
|
||||
inverse_interpolate(old_start, old_end, old_value)
|
||||
)
|
||||
|
||||
|
||||
def clamp(lower, upper, val):
|
||||
if val < lower:
|
||||
return lower
|
||||
@@ -58,70 +64,75 @@ def clamp(lower, upper, val):
|
||||
|
||||
# Figuring out which bezier curves most smoothly connect a sequence of points
|
||||
|
||||
|
||||
def get_smooth_handle_points(points):
|
||||
points = np.array(points)
|
||||
num_handles = len(points) - 1
|
||||
dim = points.shape[1]
|
||||
dim = points.shape[1]
|
||||
if num_handles < 1:
|
||||
return np.zeros((0, dim)), np.zeros((0, dim))
|
||||
#Must solve 2*num_handles equations to get the handles.
|
||||
#l and u are the number of lower an upper diagonal rows
|
||||
#in the matrix to solve.
|
||||
l, u = 2, 1
|
||||
#diag is a representation of the matrix in diagonal form
|
||||
#See https://www.particleincell.com/2012/bezier-splines/
|
||||
#for how to arive at these equations
|
||||
diag = np.zeros((l+u+1, 2*num_handles))
|
||||
diag[0,1::2] = -1
|
||||
diag[0,2::2] = 1
|
||||
diag[1,0::2] = 2
|
||||
diag[1,1::2] = 1
|
||||
diag[2,1:-2:2] = -2
|
||||
diag[3,0:-3:2] = 1
|
||||
#last
|
||||
diag[2,-2] = -1
|
||||
diag[1,-1] = 2
|
||||
#This is the b as in Ax = b, where we are solving for x,
|
||||
#and A is represented using diag. However, think of entries
|
||||
#to x and b as being points in space, not numbers
|
||||
b = np.zeros((2*num_handles, dim))
|
||||
b[1::2] = 2*points[1:]
|
||||
# Must solve 2*num_handles equations to get the handles.
|
||||
# l and u are the number of lower an upper diagonal rows
|
||||
# in the matrix to solve.
|
||||
l, u = 2, 1
|
||||
# diag is a representation of the matrix in diagonal form
|
||||
# See https://www.particleincell.com/2012/bezier-splines/
|
||||
# for how to arive at these equations
|
||||
diag = np.zeros((l + u + 1, 2 * num_handles))
|
||||
diag[0, 1::2] = -1
|
||||
diag[0, 2::2] = 1
|
||||
diag[1, 0::2] = 2
|
||||
diag[1, 1::2] = 1
|
||||
diag[2, 1:-2:2] = -2
|
||||
diag[3, 0:-3:2] = 1
|
||||
# last
|
||||
diag[2, -2] = -1
|
||||
diag[1, -1] = 2
|
||||
# This is the b as in Ax = b, where we are solving for x,
|
||||
# and A is represented using diag. However, think of entries
|
||||
# to x and b as being points in space, not numbers
|
||||
b = np.zeros((2 * num_handles, dim))
|
||||
b[1::2] = 2 * points[1:]
|
||||
b[0] = points[0]
|
||||
b[-1] = points[-1]
|
||||
solve_func = lambda b : linalg.solve_banded(
|
||||
(l, u), diag, b
|
||||
)
|
||||
|
||||
def solve_func(b):
|
||||
return linalg.solve_banded((l, u), diag, b)
|
||||
if is_closed(points):
|
||||
#Get equations to relate first and last points
|
||||
# Get equations to relate first and last points
|
||||
matrix = diag_to_matrix((l, u), diag)
|
||||
#last row handles second derivative
|
||||
# last row handles second derivative
|
||||
matrix[-1, [0, 1, -2, -1]] = [2, -1, 1, -2]
|
||||
#first row handles first derivative
|
||||
matrix[0,:] = np.zeros(matrix.shape[1])
|
||||
matrix[0,[0, -1]] = [1, 1]
|
||||
b[0] = 2*points[0]
|
||||
# first row handles first derivative
|
||||
matrix[0, :] = np.zeros(matrix.shape[1])
|
||||
matrix[0, [0, -1]] = [1, 1]
|
||||
b[0] = 2 * points[0]
|
||||
b[-1] = np.zeros(dim)
|
||||
solve_func = lambda b : linalg.solve(matrix, b)
|
||||
handle_pairs = np.zeros((2*num_handles, dim))
|
||||
|
||||
def solve_func(b):
|
||||
return linalg.solve(matrix, b)
|
||||
handle_pairs = np.zeros((2 * num_handles, dim))
|
||||
for i in range(dim):
|
||||
handle_pairs[:,i] = solve_func(b[:,i])
|
||||
handle_pairs[:, i] = solve_func(b[:, i])
|
||||
return handle_pairs[0::2], handle_pairs[1::2]
|
||||
|
||||
|
||||
def diag_to_matrix(l_and_u, diag):
|
||||
"""
|
||||
Converts array whose rows represent diagonal
|
||||
Converts array whose rows represent diagonal
|
||||
entries of a matrix into the matrix itself.
|
||||
See scipy.linalg.solve_banded
|
||||
"""
|
||||
l, u = l_and_u
|
||||
dim = diag.shape[1]
|
||||
matrix = np.zeros((dim, dim))
|
||||
for i in range(l+u+1):
|
||||
for i in range(l + u + 1):
|
||||
np.fill_diagonal(
|
||||
matrix[max(0,i-u):,max(0,u-i):],
|
||||
diag[i,max(0,u-i):]
|
||||
matrix[max(0, i - u):, max(0, u - i):],
|
||||
diag[i, max(0, u - i):]
|
||||
)
|
||||
return matrix
|
||||
|
||||
|
||||
def is_closed(points):
|
||||
return np.linalg.norm(points[0] - points[-1]) < CLOSED_THRESHOLD
|
||||
return np.linalg.norm(points[0] - points[-1]) < CLOSED_THRESHOLD
|
||||
|
||||
@@ -7,33 +7,42 @@ from constants import PALETTE
|
||||
|
||||
from utils.bezier import interpolate
|
||||
|
||||
|
||||
def color_to_rgb(color):
|
||||
return np.array(Color(color).get_rgb())
|
||||
|
||||
def color_to_rgba(color, alpha = 1):
|
||||
|
||||
def color_to_rgba(color, alpha=1):
|
||||
return np.append(color_to_rgb(color), [alpha])
|
||||
|
||||
|
||||
def rgb_to_color(rgb):
|
||||
try:
|
||||
return Color(rgb = rgb)
|
||||
return Color(rgb=rgb)
|
||||
except:
|
||||
return Color(WHITE)
|
||||
|
||||
|
||||
def rgba_to_color(rgba):
|
||||
return rgb_to_color(rgba[:3])
|
||||
|
||||
|
||||
def rgb_to_hex(rgb):
|
||||
return "#" + "".join('%02x'%int(255*x) for x in rgb)
|
||||
return "#" + "".join('%02x' % int(255 * x) for x in rgb)
|
||||
|
||||
|
||||
def invert_color(color):
|
||||
return rgb_to_color(1.0 - color_to_rgb(color))
|
||||
|
||||
def color_to_int_rgb(color):
|
||||
return (255*color_to_rgb(color)).astype('uint8')
|
||||
|
||||
def color_to_int_rgba(color, alpha = 255):
|
||||
def color_to_int_rgb(color):
|
||||
return (255 * color_to_rgb(color)).astype('uint8')
|
||||
|
||||
|
||||
def color_to_int_rgba(color, alpha=255):
|
||||
return np.append(color_to_int_rgb(color), alpha)
|
||||
|
||||
|
||||
def color_gradient(reference_colors, length_of_output):
|
||||
if length_of_output == 0:
|
||||
return reference_colors[0]
|
||||
@@ -41,30 +50,34 @@ def color_gradient(reference_colors, length_of_output):
|
||||
alphas = np.linspace(0, (len(rgbs) - 1), length_of_output)
|
||||
floors = alphas.astype('int')
|
||||
alphas_mod1 = alphas % 1
|
||||
#End edge case
|
||||
# End edge case
|
||||
alphas_mod1[-1] = 1
|
||||
floors[-1] = len(rgbs) - 2
|
||||
return [
|
||||
rgb_to_color(interpolate(rgbs[i], rgbs[i+1], alpha))
|
||||
rgb_to_color(interpolate(rgbs[i], rgbs[i + 1], alpha))
|
||||
for i, alpha in zip(floors, alphas_mod1)
|
||||
]
|
||||
|
||||
|
||||
def interpolate_color(color1, color2, alpha):
|
||||
rgb = interpolate(color_to_rgb(color1), color_to_rgb(color2), alpha)
|
||||
return rgb_to_color(rgb)
|
||||
|
||||
|
||||
def average_color(*colors):
|
||||
rgbs = np.array(map(color_to_rgb, colors))
|
||||
mean_rgb = np.apply_along_axis(np.mean, 0, rgbs)
|
||||
return rgb_to_color(mean_rgb)
|
||||
|
||||
|
||||
def random_bright_color():
|
||||
color = random_color()
|
||||
curr_rgb = color_to_rgb(color)
|
||||
new_rgb = interpolate(
|
||||
curr_rgb, np.ones(len(curr_rgb)), 0.5
|
||||
)
|
||||
return Color(rgb = new_rgb)
|
||||
return Color(rgb=new_rgb)
|
||||
|
||||
|
||||
def random_color():
|
||||
return random.choice(PALETTE)
|
||||
return random.choice(PALETTE)
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import inspect
|
||||
import operator as op
|
||||
|
||||
|
||||
def instantiate(obj):
|
||||
"""
|
||||
Useful so that classes or instance of those classes can be
|
||||
Useful so that classes or instance of those classes can be
|
||||
included in configuration, which can prevent defaults from
|
||||
getting created during compilation/importing
|
||||
"""
|
||||
return obj() if isinstance(obj, type) else obj
|
||||
|
||||
|
||||
def get_all_descendent_classes(Class):
|
||||
awaiting_review = [Class]
|
||||
result = []
|
||||
@@ -18,6 +20,7 @@ def get_all_descendent_classes(Class):
|
||||
result.append(Child)
|
||||
return result
|
||||
|
||||
|
||||
def filtered_locals(caller_locals):
|
||||
result = caller_locals.copy()
|
||||
ignored_local_args = ["self", "kwargs"]
|
||||
@@ -25,12 +28,13 @@ def filtered_locals(caller_locals):
|
||||
result.pop(arg, caller_locals)
|
||||
return result
|
||||
|
||||
def digest_config(obj, kwargs, caller_locals = {}):
|
||||
|
||||
def digest_config(obj, kwargs, caller_locals={}):
|
||||
"""
|
||||
Sets init args and CONFIG values as local variables
|
||||
|
||||
The purpose of this function is to ensure that all
|
||||
configuration of any object is inheritable, able to
|
||||
The purpose of this function is to ensure that all
|
||||
configuration of any object is inheritable, able to
|
||||
be easily passed into instantiation, and is attached
|
||||
as an attribute of the object.
|
||||
"""
|
||||
@@ -44,16 +48,17 @@ def digest_config(obj, kwargs, caller_locals = {}):
|
||||
if hasattr(Class, "CONFIG"):
|
||||
static_configs.append(Class.CONFIG)
|
||||
|
||||
#Order matters a lot here, first dicts have higher priority
|
||||
# Order matters a lot here, first dicts have higher priority
|
||||
caller_locals = filtered_locals(caller_locals)
|
||||
all_dicts = [kwargs, caller_locals, obj.__dict__]
|
||||
all_dicts += static_configs
|
||||
all_new_dicts = [kwargs, caller_locals] + static_configs
|
||||
obj.__dict__ = merge_config(all_dicts)
|
||||
#Keep track of the configuration of objects upon
|
||||
#instantiation
|
||||
# Keep track of the configuration of objects upon
|
||||
# instantiation
|
||||
obj.initial_config = merge_config(all_new_dicts)
|
||||
|
||||
|
||||
def merge_config(all_dicts):
|
||||
all_config = reduce(op.add, [d.items() for d in all_dicts])
|
||||
config = dict()
|
||||
@@ -62,11 +67,12 @@ def merge_config(all_dicts):
|
||||
if not key in config:
|
||||
config[key] = value
|
||||
else:
|
||||
#When two dictionaries have the same key, they are merged.
|
||||
# When two dictionaries have the same key, they are merged.
|
||||
if isinstance(value, dict) and isinstance(config[key], dict):
|
||||
config[key] = merge_config([config[key], value])
|
||||
return config
|
||||
|
||||
|
||||
def soft_dict_update(d1, d2):
|
||||
"""
|
||||
Adds key values pairs of d2 to d1 only when d1 doesn't
|
||||
@@ -76,7 +82,8 @@ def soft_dict_update(d1, d2):
|
||||
if key not in d1:
|
||||
d1[key] = value
|
||||
|
||||
def digest_locals(obj, keys = None):
|
||||
|
||||
def digest_locals(obj, keys=None):
|
||||
caller_locals = filtered_locals(
|
||||
inspect.currentframe().f_back.f_locals
|
||||
)
|
||||
@@ -85,31 +92,10 @@ def digest_locals(obj, keys = None):
|
||||
for key in keys:
|
||||
setattr(obj, key, caller_locals[key])
|
||||
|
||||
# Occasionally convenient in order to write dict.x instead of more laborious
|
||||
# Occasionally convenient in order to write dict.x instead of more laborious
|
||||
# (and less in keeping with all other attr accesses) dict["x"]
|
||||
|
||||
|
||||
class DictAsObject(object):
|
||||
def __init__(self, dict):
|
||||
self.__dict__ = dict
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
self.__dict__ = dict
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
from PIL import Image
|
||||
from constants import RASTER_IMAGE_DIR
|
||||
|
||||
|
||||
def get_full_raster_image_path(image_file_name):
|
||||
possible_paths = [
|
||||
image_file_name,
|
||||
@@ -15,7 +16,8 @@ def get_full_raster_image_path(image_file_name):
|
||||
for path in possible_paths:
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
raise IOError("File %s not Found"%image_file_name)
|
||||
raise IOError("File %s not Found" % image_file_name)
|
||||
|
||||
|
||||
def drag_pixels(frames):
|
||||
curr = frames[0]
|
||||
@@ -25,7 +27,8 @@ def drag_pixels(frames):
|
||||
new_frames.append(np.array(curr))
|
||||
return new_frames
|
||||
|
||||
|
||||
def invert_image(image):
|
||||
arr = np.array(image)
|
||||
arr = (255 * np.ones(arr.shape)).astype(arr.dtype) - arr
|
||||
return Image.fromarray(arr)
|
||||
return Image.fromarray(arr)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import itertools as it
|
||||
import numpy as np
|
||||
|
||||
|
||||
def remove_list_redundancies(l):
|
||||
"""
|
||||
Used instead of list(set(l)) to maintain order
|
||||
@@ -9,27 +10,32 @@ def remove_list_redundancies(l):
|
||||
reversed_result = []
|
||||
used = set()
|
||||
for x in reversed(l):
|
||||
if not x in used:
|
||||
if x not in used:
|
||||
reversed_result.append(x)
|
||||
used.add(x)
|
||||
reversed_result.reverse()
|
||||
return reversed_result
|
||||
|
||||
|
||||
def list_update(l1, l2):
|
||||
"""
|
||||
Used instead of list(set(l1).update(l2)) to maintain order,
|
||||
making sure duplicates are removed from l1, not l2.
|
||||
"""
|
||||
return filter(lambda e : e not in l2, l1) + list(l2)
|
||||
return filter(lambda e: e not in l2, l1) + list(l2)
|
||||
|
||||
|
||||
def list_difference_update(l1, l2):
|
||||
return filter(lambda e : e not in l2, l1)
|
||||
return filter(lambda e: e not in l2, l1)
|
||||
|
||||
|
||||
def all_elements_are_instances(iterable, Class):
|
||||
return all(map(lambda e : isinstance(e, Class), iterable))
|
||||
return all(map(lambda e: isinstance(e, Class), iterable))
|
||||
|
||||
|
||||
def adjacent_pairs(objects):
|
||||
return zip(objects, list(objects[1:])+[objects[0]])
|
||||
return zip(objects, list(objects[1:]) + [objects[0]])
|
||||
|
||||
|
||||
def batch_by_property(items, property_func):
|
||||
"""
|
||||
@@ -39,6 +45,7 @@ def batch_by_property(items, property_func):
|
||||
batches together would give the original list.
|
||||
"""
|
||||
batch_prop_pairs = []
|
||||
|
||||
def add_batch_prop_pair(batch):
|
||||
if len(batch) > 0:
|
||||
batch_prop_pairs.append(
|
||||
@@ -57,6 +64,7 @@ def batch_by_property(items, property_func):
|
||||
add_batch_prop_pair(curr_batch)
|
||||
return batch_prop_pairs
|
||||
|
||||
|
||||
def tuplify(obj):
|
||||
if isinstance(obj, str):
|
||||
return (obj,)
|
||||
@@ -65,14 +73,17 @@ def tuplify(obj):
|
||||
except:
|
||||
return (obj,)
|
||||
|
||||
|
||||
def stretch_array_to_length(nparray, length):
|
||||
curr_len = len(nparray)
|
||||
if curr_len > length:
|
||||
raise Warning("Trying to stretch array to a length shorter than its own")
|
||||
indices = np.arange(length)/ float(length)
|
||||
raise Warning(
|
||||
"Trying to stretch array to a length shorter than its own")
|
||||
indices = np.arange(length) / float(length)
|
||||
indices *= curr_len
|
||||
return nparray[indices.astype('int')]
|
||||
|
||||
|
||||
def make_even(iterable_1, iterable_2):
|
||||
list_1, list_2 = list(iterable_1), list(iterable_2)
|
||||
length = max(len(list_1), len(list_2))
|
||||
@@ -81,6 +92,7 @@ def make_even(iterable_1, iterable_2):
|
||||
[list_2[(n * len(list_2)) / length] for n in xrange(length)]
|
||||
)
|
||||
|
||||
|
||||
def make_even_by_cycling(iterable_1, iterable_2):
|
||||
length = max(len(iterable_1), len(iterable_2))
|
||||
cycle1 = it.cycle(iterable_1)
|
||||
@@ -90,24 +102,23 @@ def make_even_by_cycling(iterable_1, iterable_2):
|
||||
[cycle2.next() for x in range(length)]
|
||||
)
|
||||
|
||||
|
||||
def composition(func_list):
|
||||
"""
|
||||
func_list should contain elements of the form (f, args)
|
||||
"""
|
||||
return reduce(
|
||||
lambda (f1, args1), (f2, args2) : (lambda x : f1(f2(x, *args2), *args1)),
|
||||
lambda (f1, args1), (f2, args2): (lambda x: f1(f2(x, *args2), *args1)),
|
||||
func_list,
|
||||
lambda x : x
|
||||
lambda x: x
|
||||
)
|
||||
|
||||
def remove_nones(sequence):
|
||||
return filter(lambda x : x, sequence)
|
||||
|
||||
## Note this is redundant with it.chain
|
||||
def remove_nones(sequence):
|
||||
return filter(lambda x: x, sequence)
|
||||
|
||||
# Note this is redundant with it.chain
|
||||
|
||||
|
||||
def concatenate_lists(*list_of_lists):
|
||||
return [item for l in list_of_lists for item in l]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from utils.space_ops import rotation_matrix
|
||||
|
||||
STRAIGHT_PATH_THRESHOLD = 0.01
|
||||
|
||||
|
||||
def straight_path(start_points, end_points, alpha):
|
||||
"""
|
||||
Same function as interpolate, but renamed to reflect
|
||||
@@ -15,27 +16,31 @@ def straight_path(start_points, end_points, alpha):
|
||||
"""
|
||||
return interpolate(start_points, end_points, alpha)
|
||||
|
||||
def path_along_arc(arc_angle, axis = OUT):
|
||||
|
||||
def path_along_arc(arc_angle, axis=OUT):
|
||||
"""
|
||||
If vect is vector from start to end, [vect[:,1], -vect[:,0]] is
|
||||
If vect is vector from start to end, [vect[:,1], -vect[:,0]] is
|
||||
perpendicular to vect in the left direction.
|
||||
"""
|
||||
if abs(arc_angle) < STRAIGHT_PATH_THRESHOLD:
|
||||
return straight_path
|
||||
if np.linalg.norm(axis) == 0:
|
||||
axis = OUT
|
||||
unit_axis = axis/np.linalg.norm(axis)
|
||||
unit_axis = axis / np.linalg.norm(axis)
|
||||
|
||||
def path(start_points, end_points, alpha):
|
||||
vects = end_points - start_points
|
||||
centers = start_points + 0.5*vects
|
||||
centers = start_points + 0.5 * vects
|
||||
if arc_angle != np.pi:
|
||||
centers += np.cross(unit_axis, vects/2.0)/np.tan(arc_angle/2)
|
||||
rot_matrix = rotation_matrix(alpha*arc_angle, unit_axis)
|
||||
return centers + np.dot(start_points-centers, rot_matrix.T)
|
||||
centers += np.cross(unit_axis, vects / 2.0) / np.tan(arc_angle / 2)
|
||||
rot_matrix = rotation_matrix(alpha * arc_angle, unit_axis)
|
||||
return centers + np.dot(start_points - centers, rot_matrix.T)
|
||||
return path
|
||||
|
||||
|
||||
def clockwise_path():
|
||||
return path_along_arc(-np.pi)
|
||||
|
||||
|
||||
def counterclockwise_path():
|
||||
return path_along_arc(np.pi)
|
||||
return path_along_arc(np.pi)
|
||||
|
||||
@@ -3,49 +3,60 @@ import numpy as np
|
||||
from utils.bezier import bezier
|
||||
from utils.simple_functions import sigmoid
|
||||
|
||||
def smooth(t, inflection = 10.0):
|
||||
|
||||
def smooth(t, inflection=10.0):
|
||||
error = sigmoid(-inflection / 2)
|
||||
return (sigmoid(inflection*(t - 0.5)) - error) / (1 - 2*error)
|
||||
return (sigmoid(inflection * (t - 0.5)) - error) / (1 - 2 * error)
|
||||
|
||||
|
||||
def rush_into(t):
|
||||
return 2*smooth(t/2.0)
|
||||
return 2 * smooth(t / 2.0)
|
||||
|
||||
|
||||
def rush_from(t):
|
||||
return 2*smooth(t/2.0+0.5) - 1
|
||||
return 2 * smooth(t / 2.0 + 0.5) - 1
|
||||
|
||||
|
||||
def slow_into(t):
|
||||
return np.sqrt(1-(1-t)*(1-t))
|
||||
return np.sqrt(1 - (1 - t) * (1 - t))
|
||||
|
||||
|
||||
def double_smooth(t):
|
||||
if t < 0.5:
|
||||
return 0.5*smooth(2*t)
|
||||
return 0.5 * smooth(2 * t)
|
||||
else:
|
||||
return 0.5*(1 + smooth(2*t - 1))
|
||||
return 0.5 * (1 + smooth(2 * t - 1))
|
||||
|
||||
def there_and_back(t, inflection = 10.0):
|
||||
new_t = 2*t if t < 0.5 else 2*(1 - t)
|
||||
|
||||
def there_and_back(t, inflection=10.0):
|
||||
new_t = 2 * t if t < 0.5 else 2 * (1 - t)
|
||||
return smooth(new_t, inflection)
|
||||
|
||||
|
||||
def there_and_back_with_pause(t):
|
||||
if t < 1./3:
|
||||
return smooth(3*t)
|
||||
elif t < 2./3:
|
||||
if t < 1. / 3:
|
||||
return smooth(3 * t)
|
||||
elif t < 2. / 3:
|
||||
return 1
|
||||
else:
|
||||
return smooth(3 - 3*t)
|
||||
return smooth(3 - 3 * t)
|
||||
|
||||
def running_start(t, pull_factor = -0.5):
|
||||
|
||||
def running_start(t, pull_factor=-0.5):
|
||||
return bezier([0, 0, pull_factor, pull_factor, 1, 1, 1])(t)
|
||||
|
||||
def not_quite_there(func = smooth, proportion = 0.7):
|
||||
|
||||
def not_quite_there(func=smooth, proportion=0.7):
|
||||
def result(t):
|
||||
return proportion*func(t)
|
||||
return proportion * func(t)
|
||||
return result
|
||||
|
||||
def wiggle(t, wiggles = 2):
|
||||
return there_and_back(t) * np.sin(wiggles*np.pi*t)
|
||||
|
||||
def squish_rate_func(func, a = 0.4, b = 0.6):
|
||||
def wiggle(t, wiggles=2):
|
||||
return there_and_back(t) * np.sin(wiggles * np.pi * t)
|
||||
|
||||
|
||||
def squish_rate_func(func, a=0.4, b=0.6):
|
||||
def result(t):
|
||||
if a == b:
|
||||
return a
|
||||
@@ -55,15 +66,15 @@ def squish_rate_func(func, a = 0.4, b = 0.6):
|
||||
elif t > b:
|
||||
return func(1)
|
||||
else:
|
||||
return func((t-a)/(b-a))
|
||||
|
||||
return func((t - a) / (b - a))
|
||||
|
||||
return result
|
||||
|
||||
# Stylistically, should this take parameters (with default values)?
|
||||
# Ultimately, the functionality is entirely subsumed by squish_rate_func,
|
||||
# but it may be useful to have a nice name for with nice default params for
|
||||
# but it may be useful to have a nice name for with nice default params for
|
||||
# "lingering", different from squish_rate_func's default params
|
||||
|
||||
|
||||
def lingering(t):
|
||||
return squish_rate_func(lambda t: t, 0, 0.8)(t)
|
||||
|
||||
|
||||
|
||||
@@ -1,27 +1,33 @@
|
||||
import numpy as np
|
||||
import operator as op
|
||||
|
||||
|
||||
def sigmoid(x):
|
||||
return 1.0/(1 + np.exp(-x))
|
||||
return 1.0 / (1 + np.exp(-x))
|
||||
|
||||
|
||||
def choose(n, r):
|
||||
if n < r: return 0
|
||||
if r == 0: return 1
|
||||
denom = reduce(op.mul, xrange(1, r+1), 1)
|
||||
numer = reduce(op.mul, xrange(n, n-r, -1), 1)
|
||||
return numer//denom
|
||||
if n < r:
|
||||
return 0
|
||||
if r == 0:
|
||||
return 1
|
||||
denom = reduce(op.mul, xrange(1, r + 1), 1)
|
||||
numer = reduce(op.mul, xrange(n, n - r, -1), 1)
|
||||
return numer // denom
|
||||
|
||||
# Just to have a less heavyweight name for this extremely common operation
|
||||
#
|
||||
# We may wish to have more fine-grained control over division by zero behavior
|
||||
# in the future (separate specifiable values for 0/0 and x/0 with x != 0),
|
||||
# but for now, we just allow the option to handle indeterminate 0/0.
|
||||
def fdiv(a, b, zero_over_zero_value = None):
|
||||
if zero_over_zero_value != None:
|
||||
|
||||
|
||||
def fdiv(a, b, zero_over_zero_value=None):
|
||||
if zero_over_zero_value is not None:
|
||||
out = np.full_like(a, zero_over_zero_value)
|
||||
where = np.logical_or (a != 0, b != 0)
|
||||
where = np.logical_or(a != 0, b != 0)
|
||||
else:
|
||||
out = None
|
||||
where = True
|
||||
|
||||
return np.true_divide(a, b, out = out, where = where)
|
||||
return np.true_divide(a, b, out=out, where=where)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
|
||||
|
||||
def play_chord(*nums):
|
||||
commands = [
|
||||
"play",
|
||||
@@ -8,10 +9,10 @@ def play_chord(*nums):
|
||||
"--no-show-progress",
|
||||
"synth",
|
||||
] + [
|
||||
"sin %-"+str(num)
|
||||
"sin %-" + str(num)
|
||||
for num in nums
|
||||
] + [
|
||||
"fade h 0.5 1 0.5",
|
||||
"fade h 0.5 1 0.5",
|
||||
"> /dev/null"
|
||||
]
|
||||
try:
|
||||
@@ -19,8 +20,10 @@ def play_chord(*nums):
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def play_error_sound():
|
||||
play_chord(11, 8, 6, 1)
|
||||
|
||||
|
||||
def play_finish_sound():
|
||||
play_chord(12, 9, 5, 2)
|
||||
play_chord(12, 9, 5, 2)
|
||||
|
||||
@@ -3,12 +3,14 @@ import numpy as np
|
||||
from constants import OUT
|
||||
from constants import RIGHT
|
||||
|
||||
#Matrix operations
|
||||
# Matrix operations
|
||||
|
||||
def thick_diagonal(dim, thickness = 2):
|
||||
|
||||
def thick_diagonal(dim, thickness=2):
|
||||
row_indices = np.arange(dim).repeat(dim).reshape((dim, dim))
|
||||
col_indices = np.transpose(row_indices)
|
||||
return (np.abs(row_indices - col_indices)<thickness).astype('uint8')
|
||||
return (np.abs(row_indices - col_indices) < thickness).astype('uint8')
|
||||
|
||||
|
||||
def rotation_matrix(angle, axis):
|
||||
"""
|
||||
@@ -19,16 +21,18 @@ def rotation_matrix(angle, axis):
|
||||
axis_to_z = np.linalg.inv(z_to_axis)
|
||||
return reduce(np.dot, [z_to_axis, about_z, axis_to_z])
|
||||
|
||||
|
||||
def rotation_about_z(angle):
|
||||
return [
|
||||
[np.cos(angle), -np.sin(angle), 0],
|
||||
[np.sin(angle), np.cos(angle), 0],
|
||||
[np.sin(angle), np.cos(angle), 0],
|
||||
[0, 0, 1]
|
||||
]
|
||||
|
||||
|
||||
def z_to_vector(vector):
|
||||
"""
|
||||
Returns some matrix in SO(3) which takes the z-axis to the
|
||||
Returns some matrix in SO(3) which takes the z-axis to the
|
||||
(normalized) vector provided as an argument
|
||||
"""
|
||||
norm = np.linalg.norm(vector)
|
||||
@@ -37,7 +41,7 @@ def z_to_vector(vector):
|
||||
v = np.array(vector) / norm
|
||||
phi = np.arccos(v[2])
|
||||
if any(v[:2]):
|
||||
#projection of vector to unit circle
|
||||
# projection of vector to unit circle
|
||||
axis_proj = v[:2] / np.linalg.norm(v[:2])
|
||||
theta = np.arccos(axis_proj[0])
|
||||
if axis_proj[1] < 0:
|
||||
@@ -51,15 +55,18 @@ def z_to_vector(vector):
|
||||
])
|
||||
return np.dot(rotation_about_z(theta), phi_down)
|
||||
|
||||
def rotate_vector(vector, angle, axis = OUT):
|
||||
|
||||
def rotate_vector(vector, angle, axis=OUT):
|
||||
return np.dot(rotation_matrix(angle, axis), vector)
|
||||
|
||||
|
||||
def angle_between(v1, v2):
|
||||
return np.arccos(np.dot(
|
||||
v1 / np.linalg.norm(v1),
|
||||
v1 / np.linalg.norm(v1),
|
||||
v2 / np.linalg.norm(v2)
|
||||
))
|
||||
|
||||
|
||||
def angle_of_vector(vector):
|
||||
"""
|
||||
Returns polar coordinate theta when vector is project on xy plane
|
||||
@@ -69,6 +76,7 @@ def angle_of_vector(vector):
|
||||
return 0
|
||||
return np.angle(complex(*vector[:2]))
|
||||
|
||||
|
||||
def angle_between_vectors(v1, v2):
|
||||
"""
|
||||
Returns the angle between two 3D vectors.
|
||||
@@ -76,7 +84,8 @@ def angle_between_vectors(v1, v2):
|
||||
"""
|
||||
l1 = np.linalg.norm(v1)
|
||||
l2 = np.linalg.norm(v2)
|
||||
return np.arccos(np.dot(v1,v2)/(l1*l2))
|
||||
return np.arccos(np.dot(v1, v2) / (l1 * l2))
|
||||
|
||||
|
||||
def project_along_vector(point, vector):
|
||||
matrix = np.identity(3) - np.outer(vector, vector)
|
||||
@@ -84,20 +93,23 @@ def project_along_vector(point, vector):
|
||||
|
||||
###
|
||||
|
||||
def compass_directions(n = 4, start_vect = RIGHT):
|
||||
angle = 2*np.pi/n
|
||||
|
||||
def compass_directions(n=4, start_vect=RIGHT):
|
||||
angle = 2 * np.pi / n
|
||||
return np.array([
|
||||
rotate_vector(start_vect, k*angle)
|
||||
rotate_vector(start_vect, k * angle)
|
||||
for k in range(n)
|
||||
])
|
||||
|
||||
|
||||
def complex_to_R3(complex_num):
|
||||
return np.array((complex_num.real, complex_num.imag, 0))
|
||||
|
||||
|
||||
def R3_to_complex(point):
|
||||
return complex(*point[:2])
|
||||
|
||||
|
||||
def center_of_mass(points):
|
||||
points = [np.array(point).astype("float") for point in points]
|
||||
return sum(points) / len(points)
|
||||
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import re
|
||||
import string
|
||||
|
||||
|
||||
def to_camel_case(name):
|
||||
return "".join([
|
||||
filter(
|
||||
lambda c : c not in string.punctuation + string.whitespace, part
|
||||
lambda c: c not in string.punctuation + string.whitespace, part
|
||||
).capitalize()
|
||||
for part in name.split("_")
|
||||
])
|
||||
|
||||
def initials(name, sep_values = [" ", "_"]):
|
||||
|
||||
def initials(name, sep_values=[" ", "_"]):
|
||||
return "".join([
|
||||
(s[0] if s else "")
|
||||
(s[0] if s else "")
|
||||
for s in re.split("|".join(sep_values), name)
|
||||
])
|
||||
|
||||
|
||||
def camel_case_initials(name):
|
||||
return filter(lambda c : c.isupper(), name)
|
||||
return filter(lambda c: c.isupper(), name)
|
||||
|
||||
|
||||
def complex_string(complex_num):
|
||||
return filter(lambda c : c not in "()", str(complex_num))
|
||||
return filter(lambda c: c not in "()", str(complex_num))
|
||||
|
||||
Reference in New Issue
Block a user