Merge branch 'master' of https://github.com/3b1b/manim into WindingNumber

This commit is contained in:
Sridhar Ramesh 2018-01-29 13:34:20 -08:00
commit 322a172c28
10 changed files with 2308 additions and 331 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

288
ben_playground.py Normal file
View file

@ -0,0 +1,288 @@
#!/usr/bin/env python
from helpers import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from mobject.point_cloud_mobject import PointCloudDot
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.continual_animation import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.number_line import *
from topics.combinatorics import *
from scene import Scene
from camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from mobject.vectorized_mobject import *
## To watch one of these scenes, run the following:
## python extract_scene.py -p file_name <SceneName>
LIGHT_COLOR = YELLOW
DEGREES = 360/TAU
SWITCH_ON_RUN_TIME = 1.5
class AmbientLight(VMobject):
# Parameters are:
# * a source point
# * an opacity function
# * a light color
# * a max opacity
# * a radius (larger than the opacity's dropoff length)
# * the number of subdivisions (levels, annuli)
CONFIG = {
"source_point" : ORIGIN,
"opacity_function" : lambda r : 1.0/(r+1.0)**2,
"color" : LIGHT_COLOR,
"max_opacity" : 1.0,
"num_levels" : 10,
"radius" : 5.0
}
def generate_points(self):
# in theory, this method is only called once, right?
# so removing submobs shd not be necessary
for submob in self.submobjects:
self.remove(submob)
# create annuli
dr = self.radius / self.num_levels
for r in np.arange(0, self.radius, dr):
alpha = self.max_opacity * self.opacity_function(r)
annulus = Annulus(
inner_radius = r,
outer_radius = r + dr,
color = self.color,
fill_opacity = alpha
)
annulus.move_arc_center_to(self.source_point)
self.add(annulus)
def move_source_to(self,point):
self.shift(point - self.source_point)
self.source_point = np.array(point)
# for submob in self.submobjects:
# if type(submob) == Annulus:
# submob.shift(self.source_point - submob.get_center())
def dimming(self,new_alpha):
old_alpha = self.max_opacity
self.max_opacity = new_alpha
for submob in self.submobjects:
old_submob_alpha = submob.fill_opacity
new_submob_alpha = old_submob_alpha * new_alpha/old_alpha
submob.set_fill(opacity = new_submob_alpha)
class Spotlight(VMobject):
CONFIG = {
"source_point" : ORIGIN,
"opacity_function" : lambda r : 1.0/(r+1.0)**2,
"color" : LIGHT_COLOR,
"max_opacity" : 1.0,
"num_levels" : 10,
"radius" : 5.0,
"screen" : None,
"shadow" : VMobject(fill_color = RED, stroke_width = 0, fill_opacity = 1.0)
}
def track_screen(self):
self.generate_points()
def generate_points(self):
for submob in self.submobjects:
self.remove(submob)
if self.screen != None:
# look for the screen and create annular sectors
lower_angle, upper_angle = self.viewing_angles(self.screen)
dr = self.radius / self.num_levels
for r in np.arange(0, self.radius, dr):
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
)
annular_sector.move_arc_center_to(self.source_point)
self.add(annular_sector)
self.update_shadow(point = self.source_point)
self.add(self.shadow)
def viewing_angle_of_point(self,point):
distance_vector = point - self.source_point
angle = angle_of_vector(distance_vector)
return angle
def viewing_angles(self,screen):
viewing_angles = np.array(map(self.viewing_angle_of_point,
screen.get_anchors()))
lower_angle = upper_angle = 0
if len(viewing_angles) != 0:
lower_angle = np.min(viewing_angles)
upper_angle = np.max(viewing_angles)
return lower_angle, upper_angle
def move_source_to(self,point):
self.source_point = np.array(point)
self.recalculate_sectors(point = point, screen = self.screen)
self.update_shadow(point = point)
def recalculate_sectors(self, point = ORIGIN, screen = None):
if screen == None:
return
for submob in self.submobject_family():
if type(submob) == AnnularSector:
lower_angle, upper_angle = self.viewing_angles(screen)
new_submob = AnnularSector(
start_angle = lower_angle,
angle = upper_angle - lower_angle,
inner_radius = submob.inner_radius,
outer_radius = submob.outer_radius
)
new_submob.move_arc_center_to(point)
submob.points = new_submob.points
def update_shadow(self,point = ORIGIN):
print "updating shadow"
use_point = point #self.source_point
self.shadow.points = self.screen.points
ray1 = self.screen.points[0] - use_point
ray2 = self.screen.points[-1] - use_point
ray1 = ray1/np.linalg.norm(ray1) * 100
ray1 = rotate_vector(ray1,-TAU/16)
ray2 = ray2/np.linalg.norm(ray2) * 100
ray2 = rotate_vector(ray2,TAU/16)
outpoint1 = self.screen.points[0] + ray1
outpoint2 = self.screen.points[-1] + ray2
self.shadow.add_control_points([outpoint2,outpoint1,self.screen.points[0]])
self.shadow.mark_paths_closed = True
def dimming(self,new_alpha):
old_alpha = self.max_opacity
self.max_opacity = new_alpha
for submob in self.submobjects:
if type(submob) != AnnularSector:
# it's the shadow, don't dim it
continue
old_submob_alpha = submob.fill_opacity
new_submob_alpha = old_submob_alpha * new_alpha/old_alpha
submob.set_fill(opacity = new_submob_alpha)
class SwitchOn(LaggedStart):
CONFIG = {
"lag_ratio": 0.2,
"run_time": SWITCH_ON_RUN_TIME
}
def __init__(self, light, **kwargs):
if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight):
raise Exception("Only LightCones and Candles can be switched on")
LaggedStart.__init__(self,
FadeIn, light, **kwargs)
class SwitchOff(LaggedStart):
CONFIG = {
"lag_ratio": 0.2,
"run_time": SWITCH_ON_RUN_TIME
}
def __init__(self, light, **kwargs):
if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight):
raise Exception("Only LightCones and Candles can be switched on")
light.submobjects = light.submobjects[::-1]
LaggedStart.__init__(self,
FadeOut, light, **kwargs)
light.submobjects = light.submobjects[::-1]
class ScreenTracker(ContinualAnimation):
def __init__(self, mobject, **kwargs):
ContinualAnimation.__init__(self, mobject, **kwargs)
def update_mobject(self, dt):
self.mobject.recalculate_sectors(
point = self.mobject.source_point,
screen = self.mobject.screen)
self.mobject.update_shadow(self.mobject.source_point)
class IntroScene(Scene):
def construct(self):
screen = Line([2,-2,0],[1,2,0]).shift([1,0,0])
self.add(screen)
ambient_light = AmbientLight(
source_point = np.array([-1,1,0]),
max_opacity = 1.0,
opacity_function = lambda r: 1.0/(r/2+1)**2,
num_levels = 4,
)
spotlight = Spotlight(
source_point = np.array([-1,1,0]),
max_opacity = 1.0,
opacity_function = lambda r: 1.0/(r/2+1)**2,
num_levels = 4,
screen = screen,
)
self.add(spotlight)
screen_updater = ScreenTracker(spotlight)
#self.add(ca)
#self.play(SwitchOn(ambient_light))
#self.play(ApplyMethod(ambient_light.move_source_to,[-3,1,0]))
#self.play(SwitchOn(spotlight))
self.add(screen_updater)
self.play(ApplyMethod(spotlight.screen.rotate,TAU/8))
self.remove(screen_updater)
self.play(ApplyMethod(spotlight.move_source_to,[-3,-1,0]))
self.add(screen_updater)
spotlight.source_point = [-3,-1,0]
self.play(ApplyMethod(spotlight.dimming,0.2))
#self.play(ApplyMethod(spotlight.move_source_to,[-4,0,0]))
#self.wait()

View file

@ -455,14 +455,14 @@ class Mobject(Container):
**kwargs
)
def match_width(self, mobject):
return self.match_dim(mobject, 0)
def match_width(self, mobject, **kwargs):
return self.match_dim(mobject, 0, **kwargs)
def match_height(self, mobject):
return self.match_dim(mobject, 1)
def match_height(self, mobject, **kwargs):
return self.match_dim(mobject, 1, **kwargs)
def match_depth(self, mobject):
return self.match_dim(mobject, 2)
def match_depth(self, mobject, **kwargs):
return self.match_dim(mobject, 2, **kwargs)
## Color functions

View file

@ -100,12 +100,12 @@ class VMobject(Mobject):
#match styles accordingly
submobs1, submobs2 = self.submobjects, vmobject.submobjects
if len(submobs1) == 0:
return
return self
elif len(submobs2) == 0:
submobs2 = [vmobject]
for sm1, sm2 in zip(*make_even(submobs1, submobs2)):
sm1.match_style(sm2)
return
return self
def fade(self, darkness = 0.5):
for submob in self.submobject_family():
@ -353,7 +353,8 @@ class VMobject(Mobject):
for index in range(num_curves):
curr_bezier_points = self.points[3*index:3*index+4]
num_inter_curves = sum(index_allocation == index)
alphas = np.arange(0, num_inter_curves+1)/float(num_inter_curves)
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

View file

@ -18,9 +18,7 @@ def get_sorted_scene_names(module_name):
for line_no in sorted(line_to_scene.keys())
]
def stage_animaions(module_name):
def stage_animations(module_name):
scene_names = get_sorted_scene_names(module_name)
animation_dir = os.path.join(
ANIMATIONS_DIR, module_name.replace(".py", "")
@ -32,19 +30,27 @@ def stage_animaions(module_name):
sorted_files.append(
os.path.join(animation_dir, clip)
)
for f in os.listdir(STAGED_SCENES_DIR):
os.remove(os.path.join(STAGED_SCENES_DIR, f))
for f, count in zip(sorted_files, it.count()):
staged_scenes_dir = os.path.join(animation_dir, "staged_scenes")
count = 0
while True:
staged_scenes_dir = os.path.join(
animation_dir, "staged_scenes_%d"%count
)
if not os.path.exists(staged_scenes_dir):
os.makedirs(staged_scenes_dir)
break
#Otherwise, keep trying new names until
#there is a free one
count += 1
for count, f in enumerate(sorted_files):
symlink_name = os.path.join(
STAGED_SCENES_DIR,
staged_scenes_dir,
"Scene_%03d"%count + f.split(os.sep)[-1]
)
os.symlink(f, symlink_name)
if __name__ == "__main__":
if len(sys.argv) < 2:
raise Exception("No module given.")
module_name = sys.argv[1]
stage_animaions(module_name)
stage_animations(module_name)

View file

@ -443,7 +443,9 @@ class PiCreatureScene(Scene):
added_anims = kwargs.pop("added_anims", [])
anims = []
on_screen_mobjects = self.get_mobjects()
on_screen_mobjects = self.camera.extract_mobject_family_members(
self.get_mobjects()
)
def has_bubble(pi):
return hasattr(pi, "bubble") and \
pi.bubble is not None and \
@ -478,7 +480,7 @@ class PiCreatureScene(Scene):
]
anims += added_anims
self.play(*anims)
self.play(*anims, **kwargs)
def pi_creature_says(self, *args, **kwargs):
self.introduce_bubble(

View file

@ -97,9 +97,6 @@ class Arc(VMobject):
return self
class Circle(Arc):
CONFIG = {
"color" : RED,
@ -128,6 +125,19 @@ class Dot(Circle):
self.shift(point)
self.init_colors()
class Ellipse(VMobject):
CONFIG = {
"width" : 2,
"height" : 1
}
def generate_points(self):
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,
@ -177,6 +187,7 @@ class AnnularSector(VMobject):
self.shift(v)
return self
class Sector(AnnularSector):
CONFIG = {
"outer_radius" : 1,
@ -203,10 +214,12 @@ class Annulus(Circle):
}
def generate_points(self):
self.points = []
self.radius = self.outer_radius
Circle.generate_points(self)
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):
@ -231,6 +244,10 @@ class Line(VMobject):
])
self.account_for_buff()
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:
@ -336,6 +353,17 @@ class Line(VMobject):
self.shift(new_start - self.get_start())
return self
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)+1
self.points = np.array([
self.point_from_proportion(alpha)
for alpha in np.linspace(0, 1, new_num_points)
])
else:
VMobject.insert_n_anchor_points(self, n)
class DashedLine(Line):
CONFIG = {
"dashed_segment_length" : 0.05
@ -511,6 +539,7 @@ class Arrow(Line):
Line.put_start_and_end_on(self, *args, **kwargs)
self.set_tip_points(self.tip, preserve_normal = False)
self.set_rectangular_stem_points()
return self
def scale(self, scale_factor, **kwargs):
Line.scale(self, scale_factor, **kwargs)
@ -520,6 +549,9 @@ class Arrow(Line):
self.set_rectangular_stem_points()
return self
def copy(self):
return self.deepcopy()
class Vector(Arrow):
CONFIG = {
"color" : YELLOW,

View file

@ -200,14 +200,21 @@ class Axes(VGroup):
self.y_axis.point_to_number(point),
)
def get_graph(self, function, num_graph_points = None, **kwargs):
def get_graph(
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 = self.x_min,
t_max = self.x_max,
t_min = x_min,
t_max = x_max,
**kwargs
)
graph.underlying_function = function

View file

@ -12,7 +12,7 @@ class DecimalNumber(VMobject):
"num_decimal_points" : 2,
"digit_to_digit_buff" : 0.05,
"show_ellipsis" : False,
"unit" : None,
"unit" : None, #Aligned to bottom unless it starts with "^"
"include_background_rectangle" : False,
}
def __init__(self, number, **kwargs):
@ -41,8 +41,17 @@ class DecimalNumber(VMobject):
if self.show_ellipsis:
self.add(TexMobject("\\dots"))
if self.unit is not None:
self.add(TexMobject(self.unit))
if num_string.startswith("-"):
minus = self.submobjects[0]
minus.next_to(
self.submobjects[1], LEFT,
buff = self.digit_to_digit_buff
)
if self.unit != None:
self.unit_sign = TexMobject(self.unit)
self.add(self.unit_sign)
self.arrange_submobjects(
buff = self.digit_to_digit_buff,
@ -54,8 +63,8 @@ class DecimalNumber(VMobject):
for i, c in enumerate(num_string):
if c == "-" and len(num_string) > i+1:
self[i].align_to(self[i+1], alignment_vect = UP)
if self.unit == "\\circ":
self[-1].align_to(self, UP)
if self.unit and self.unit.startswith("^"):
self.unit_sign.align_to(self, UP)
#
if self.include_background_rectangle:
self.add_background_rectangle()
@ -80,7 +89,7 @@ class ChangingDecimal(Animation):
"num_decimal_points" : None,
"show_ellipsis" : None,
"position_update_func" : None,
"tracked_mobject" : None
"tracked_mobject" : None,
}
def __init__(self, decimal_number_mobject, number_update_func, **kwargs):
digest_config(self, kwargs, locals())