mirror of
https://github.com/3b1b/manim.git
synced 2025-09-19 04:41:56 +00:00
Merge branch 'master' of https://github.com/3b1b/manim into WindingNumber
This commit is contained in:
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
288
ben_playground.py
Normal 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()
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
Loading…
Add table
Reference in a new issue