mirror of
https://github.com/3b1b/manim.git
synced 2026-04-26 03:00:23 -04:00
Merge branch 'master' of https://github.com/3b1b/manim into WindingNumber
This commit is contained in:
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())
|
||||
|
||||
Reference in New Issue
Block a user