mirror of
https://github.com/3b1b/manim.git
synced 2026-04-26 03:00:23 -04:00
Merge remote-tracking branch 'origin/lighthouse' into lighthouse
# Conflicts: # topics/geometry.py
This commit is contained in:
@@ -42,11 +42,38 @@ INDICATOR_TEXT_COLOR = WHITE
|
||||
INDICATOR_UPDATE_TIME = 0.2
|
||||
FAST_INDICATOR_UPDATE_TIME = 0.1
|
||||
OPACITY_FOR_UNIT_INTENSITY = 0.2
|
||||
SWITCH_ON_RUN_TIME = 2.0
|
||||
SWITCH_ON_RUN_TIME = 2.5
|
||||
FAST_SWITCH_ON_RUN_TIME = 0.1
|
||||
LIGHT_CONE_NUM_SECTORS = 10
|
||||
NUM_CONES = 10
|
||||
NUM_VISIBLE_CONES = 6
|
||||
LIGHT_CONE_NUM_SECTORS = 30
|
||||
NUM_CONES = 50 # in first lighthouse scene
|
||||
NUM_VISIBLE_CONES = 5 # ibidem
|
||||
ARC_TIP_LENGTH = 0.2
|
||||
DIM_OPACITY = 0.2
|
||||
|
||||
|
||||
def show_line_length(line):
|
||||
v = line.points[1] - line.points[0]
|
||||
print v[0]**2 + v[1]**2
|
||||
|
||||
|
||||
class AngleUpdater(ContinualAnimation):
|
||||
def __init__(self, angle_arc, lc, **kwargs):
|
||||
self.angle_arc = angle_arc
|
||||
self.source_point = angle_arc.get_arc_center()
|
||||
self.lc = lc
|
||||
#self.angle_decimal = angle_decimal
|
||||
ContinualAnimation.__init__(self, self.angle_arc, **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
# angle arc
|
||||
new_arc = self.angle_arc.copy().set_bound_angles(
|
||||
start = self.lc.start_angle,
|
||||
stop = self.lc.stop_angle()
|
||||
)
|
||||
new_arc.generate_points()
|
||||
new_arc.move_arc_center_to(self.source_point)
|
||||
self.angle_arc.points = new_arc.points
|
||||
self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH, at_start = True, at_end = True)
|
||||
|
||||
|
||||
|
||||
@@ -69,6 +96,7 @@ class LightScreen(VMobject):
|
||||
|
||||
def update_light_cone(self,lc):
|
||||
lower_angle, upper_angle = self.viewing_angles()
|
||||
#print lower_angle, upper_angle
|
||||
self.light_cone.update_opening(start_angle = lower_angle,
|
||||
stop_angle = upper_angle)
|
||||
return self
|
||||
@@ -99,14 +127,18 @@ class LightScreen(VMobject):
|
||||
ray1 = self.screen.points[0] - self.light_source
|
||||
ray2 = self.screen.points[-1] - self.light_source
|
||||
ray1 = ray1/np.linalg.norm(ray1) * 100
|
||||
ray1 = rotate_vector(ray1,TAU/16)
|
||||
ray1 = rotate_vector(ray1,-TAU/16)
|
||||
ray2 = ray2/np.linalg.norm(ray2) * 100
|
||||
ray2 = rotate_vector(ray2,-TAU/16)
|
||||
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 move_source_to(self,new_point):
|
||||
self.light_source = new_point
|
||||
#self.update_light_cone(self.light_cone)
|
||||
|
||||
|
||||
|
||||
class LightCone(VGroup):
|
||||
@@ -114,6 +146,7 @@ class LightCone(VGroup):
|
||||
"start_angle": 0,
|
||||
"angle" : TAU/8,
|
||||
"radius" : 10,
|
||||
"brightness" : 1,
|
||||
"opacity_function" : lambda r : 1./max(r, 0.01),
|
||||
"num_sectors" : 10,
|
||||
"color": LIGHT_COLOR,
|
||||
@@ -130,7 +163,7 @@ class LightCone(VGroup):
|
||||
stroke_width = 0,
|
||||
stroke_color = self.color,
|
||||
fill_color = self.color,
|
||||
fill_opacity = self.opacity_function(r1),
|
||||
fill_opacity = self.brightness * self.opacity_function(r1),
|
||||
)
|
||||
for r1, r2 in zip(radii, radii[1:])
|
||||
]
|
||||
@@ -147,6 +180,7 @@ class LightCone(VGroup):
|
||||
return
|
||||
source = self.submobjects[0].get_arc_center()
|
||||
self.shift(point - source)
|
||||
self.generate_points()
|
||||
|
||||
def update_opening(self, start_angle, stop_angle):
|
||||
self.start_angle = start_angle
|
||||
@@ -160,6 +194,15 @@ class LightCone(VGroup):
|
||||
submob.generate_points()
|
||||
submob.shift(source_point - submob.get_arc_center())
|
||||
|
||||
def set_brightness(self,new_brightness):
|
||||
self.brightness = new_brightness
|
||||
radii = np.linspace(0, self.radius, self.num_sectors+1)
|
||||
for (r1,sector) in zip(radii,self.submobjects):
|
||||
sector.set_fill(opacity = self.brightness * self.opacity_function(r1))
|
||||
|
||||
def stop_angle(self):
|
||||
return self.start_angle + self.angle
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -169,13 +212,14 @@ class LightCone(VGroup):
|
||||
class Candle(VGroup):
|
||||
CONFIG = {
|
||||
"radius" : 5,
|
||||
"brightness" : 1.0,
|
||||
"opacity_function" : lambda r : 1./max(r, 0.01),
|
||||
"num_sectors" : 10,
|
||||
"num_annuli" : 10,
|
||||
"color": LIGHT_COLOR,
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
radii = np.linspace(0, self.radius, self.num_sectors+1)
|
||||
radii = np.linspace(0, self.radius, self.num_annuli+1)
|
||||
annuli = [
|
||||
Annulus(
|
||||
inner_radius = r1,
|
||||
@@ -183,7 +227,7 @@ class Candle(VGroup):
|
||||
stroke_width = 0,
|
||||
stroke_color = self.color,
|
||||
fill_color = self.color,
|
||||
fill_opacity = self.opacity_function(r1),
|
||||
fill_opacity = self.brightness * self.opacity_function(r1),
|
||||
)
|
||||
for r1, r2 in zip(radii, radii[1:])
|
||||
]
|
||||
@@ -201,6 +245,14 @@ class Candle(VGroup):
|
||||
source = self.submobjects[0].get_center()
|
||||
self.shift(point - source)
|
||||
|
||||
def set_brightness(self,new_brightness):
|
||||
self.brightness = new_brightness
|
||||
radii = np.linspace(0, self.radius, self.num_annuli+1)
|
||||
for (r1,annulus) in zip(radii,self.submobjects):
|
||||
annulus.set_fill(opacity = self.brightness * self.opacity_function(r1))
|
||||
|
||||
|
||||
|
||||
|
||||
class SwitchOn(LaggedStart):
|
||||
CONFIG = {
|
||||
@@ -558,8 +610,7 @@ class FirstLightHouseScene(PiCreatureScene):
|
||||
|
||||
|
||||
euler_sum_above = TexMobject("1", "+", "{1\over 4}",
|
||||
"+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25 }", "+", "{1\over 36}")
|
||||
euler_sum_above.fill_color = YELLOW
|
||||
"+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "{1\over 36}")
|
||||
|
||||
for (i,term) in zip(range(len(euler_sum_above)),euler_sum_above):
|
||||
#horizontal alignment with tick marks
|
||||
@@ -568,7 +619,7 @@ class FirstLightHouseScene(PiCreatureScene):
|
||||
old_y = term.get_center()[1]
|
||||
new_y = light_indicator.get_center()[1]
|
||||
term.shift([0,new_y - old_y,0])
|
||||
|
||||
|
||||
|
||||
|
||||
for i in range(1,NUM_CONES+1):
|
||||
@@ -576,8 +627,8 @@ class FirstLightHouseScene(PiCreatureScene):
|
||||
point = self.number_line.number_to_point(i)
|
||||
light_cone = Candle(
|
||||
opacity_function = inverse_quadratic(1,1),
|
||||
num_sectors = LIGHT_CONE_NUM_SECTORS,
|
||||
radius = 10)
|
||||
num_annuli = LIGHT_CONE_NUM_SECTORS,
|
||||
radius = 12)
|
||||
|
||||
light_cone.move_source_to(point)
|
||||
lighthouse.next_to(point,DOWN,0)
|
||||
@@ -599,10 +650,9 @@ class FirstLightHouseScene(PiCreatureScene):
|
||||
|
||||
# slowly switch on visible light cones and increment indicator
|
||||
for (i,lc) in zip(range(NUM_VISIBLE_CONES),light_cones[:NUM_VISIBLE_CONES]):
|
||||
print i
|
||||
indicator_start_time = 0.5 * (i+1) * SWITCH_ON_RUN_TIME/lc.radius * self.number_line.unit_size
|
||||
indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/lc.radius * self.number_line.unit_size
|
||||
indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME
|
||||
indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9)
|
||||
indicator_rate_func = squish_rate_func(
|
||||
smooth,indicator_start_time,indicator_stop_time)
|
||||
self.play(
|
||||
SwitchOn(lc),
|
||||
@@ -624,11 +674,8 @@ class FirstLightHouseScene(PiCreatureScene):
|
||||
light_indicator_copy.shift,[0, new_y - old_y,0]
|
||||
)
|
||||
|
||||
print "fast now"
|
||||
|
||||
# quickly switch on off-screen light cones and increment indicator
|
||||
for (i,lc) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),light_cones[NUM_VISIBLE_CONES:NUM_CONES]):
|
||||
print i
|
||||
indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/lc.radius * self.number_line.unit_size
|
||||
indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME
|
||||
indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9)
|
||||
@@ -644,10 +691,17 @@ class FirstLightHouseScene(PiCreatureScene):
|
||||
# show limit value in light indicator and an equals sign
|
||||
limit_reading = TexMobject("{\pi^2 \over 6}")
|
||||
limit_reading.move_to(light_indicator.reading)
|
||||
|
||||
equals_sign = TexMobject("=")
|
||||
equals_sign.next_to(randy, UP)
|
||||
old_y = equals_sign.get_center()[1]
|
||||
new_y = euler_sum_above.get_center()[1]
|
||||
equals_sign.shift([0,new_y - old_y,0])
|
||||
|
||||
self.play(
|
||||
FadeOut(indicator.reading),
|
||||
FadeIn(limit_reading)
|
||||
# Transform(light_indicator.reading,limit_reading)
|
||||
FadeOut(light_indicator.reading),
|
||||
FadeIn(limit_reading),
|
||||
FadeIn(equals_sign),
|
||||
)
|
||||
|
||||
|
||||
@@ -674,52 +728,125 @@ class SingleLightHouseScene(PiCreatureScene):
|
||||
lighthouse = LightHouse()
|
||||
candle = Candle(
|
||||
opacity_function = inverse_quadratic(1,1),
|
||||
num_sectors = LIGHT_CONE_NUM_SECTORS,
|
||||
radius = 10
|
||||
num_annuli = LIGHT_CONE_NUM_SECTORS,
|
||||
radius = 10,
|
||||
brightness = 1,
|
||||
)
|
||||
lighthouse.scale(2).next_to(source_point, DOWN, buff = 0)
|
||||
candle.move_to(source_point)
|
||||
morty = self.get_primary_pi_creature()
|
||||
morty.scale(0.5)
|
||||
morty.move_to(observer_point)
|
||||
self.add(lighthouse, candle)
|
||||
self.wait()
|
||||
self.add(lighthouse)
|
||||
self.play(
|
||||
SwitchOn(candle)
|
||||
)
|
||||
|
||||
light_cone = LightCone()
|
||||
light_cone = LightCone(
|
||||
opacity_function = inverse_quadratic(1,1),
|
||||
num_sectors = LIGHT_CONE_NUM_SECTORS,
|
||||
radius = 10,
|
||||
brightness = 5,
|
||||
)
|
||||
light_cone.move_source_to(source_point)
|
||||
screen = Arc(TAU/4).rotate_in_place(TAU/2).shift(3*RIGHT)
|
||||
screen.radius = 4
|
||||
screen.start_angle = -TAU/5
|
||||
screen.next_to(morty, LEFT)
|
||||
screen = Line([0,-1,0],[0,1,0])
|
||||
show_line_length(screen)
|
||||
|
||||
screen.rotate_in_place(-TAU/6)
|
||||
show_line_length(screen)
|
||||
|
||||
screen.next_to(morty, LEFT, buff = 1)
|
||||
|
||||
light_screen = LightScreen(light_source = source_point,
|
||||
screen = screen, light_cone = light_cone)
|
||||
light_screen.screen.color = WHITE
|
||||
light_screen.screen.fill_opacity = 1
|
||||
light_screen.update_light_cone(light_cone)
|
||||
self.add(light_screen)
|
||||
# dim the light that misses the screen
|
||||
self.play(
|
||||
ApplyMethod(light_cone.set_intensity,0.3)
|
||||
)
|
||||
# self.play(
|
||||
# FadeIn(light_screen, run_time = 2),
|
||||
# # dim the light that misses the screen
|
||||
# ApplyMethod(candle.set_brightness,0.3),
|
||||
# ApplyMethod(light_screen.update_shadow,light_screen.shadow),
|
||||
# FadeIn(light_cone),
|
||||
# )
|
||||
|
||||
|
||||
lc_updater = lambda lc: light_screen.update_light_cone(lc)
|
||||
sh_updater = lambda sh: light_screen.update_shadow(sh)
|
||||
|
||||
ca1 = ContinualUpdateFromFunc(light_screen.light_cone,
|
||||
lc_updater)
|
||||
ca15 = ContinualUpdateFromFunc(light_screen,
|
||||
lc_updater)
|
||||
ca2 = ContinualUpdateFromFunc(light_screen.shadow,
|
||||
sh_updater)
|
||||
|
||||
self.add(ca1, ca2)
|
||||
self.add(ca1, ca15, ca2)
|
||||
self.add_foreground_mobject(morty)
|
||||
|
||||
moving_screen = ApplyMethod(screen.move_to, [1,0,0], run_time=3)
|
||||
pointing_screen_at_source = ApplyMethod(screen.rotate,TAU/6)
|
||||
#self.play(pointing_screen_at_source)
|
||||
#self.wait()
|
||||
|
||||
self.play(moving_screen)
|
||||
arc_angle = light_cone.angle
|
||||
# draw arc arrows to show the opening angle
|
||||
angle_arc = Arc(radius = 5, start_angle = light_cone.start_angle,
|
||||
angle = light_cone.angle, tip_length = ARC_TIP_LENGTH)
|
||||
#angle_arc.add_tip(at_start = True, at_end = True)
|
||||
angle_arc.move_arc_center_to(source_point)
|
||||
|
||||
self.add(angle_arc)
|
||||
|
||||
angle_indicator = DecimalNumber(arc_angle/TAU*360,
|
||||
num_decimal_points = 0,
|
||||
unit = "^\\circ")
|
||||
angle_indicator.next_to(angle_arc,RIGHT)
|
||||
self.add_foreground_mobject(angle_indicator)
|
||||
|
||||
angle_update_func = lambda x: light_cone.angle/TAU * 360
|
||||
ca3 = ContinualChangingDecimal(angle_indicator,angle_update_func)
|
||||
self.add(ca3)
|
||||
|
||||
#ca4 = ContinualUpdateFromFunc(angle_arc,update_angle_arc)
|
||||
ca4 = AngleUpdater(angle_arc, light_screen.light_cone)
|
||||
self.add(ca4)
|
||||
|
||||
rotating_screen = ApplyMethod(light_screen.screen.rotate,
|
||||
TAU/8, run_time=1.5)
|
||||
#self.wait(2)
|
||||
rotating_screen_2 = ApplyMethod(light_screen.screen.rotate,
|
||||
-TAU/4, run_time=3, rate_func = there_and_back)
|
||||
#self.wait(2)
|
||||
rotating_screen_3 = ApplyMethod(light_screen.screen.rotate,
|
||||
TAU/8, run_time=1.5)
|
||||
|
||||
#self.play(rotating_screen)
|
||||
#self.play(rotating_screen_2)
|
||||
#self.play(rotating_screen_3)
|
||||
|
||||
#rotating_screen_back = ApplyMethod(light_screen.screen.rotate_in_place, -TAU/6) #, run_time=3, rate_func = wiggle)
|
||||
#self.play(rotating_screen_back)
|
||||
|
||||
self.wait()
|
||||
|
||||
|
||||
|
||||
# morph into Earth scene
|
||||
|
||||
globe = Circle(radius = 3)
|
||||
globe.move_to([2,0,0])
|
||||
sun_position = [-100,0,0]
|
||||
self.play(
|
||||
#ApplyMethod(lighthouse.move_to,sun_position),
|
||||
#ApplyMethod(candle.move_to,sun_position),
|
||||
ApplyMethod(light_screen.move_source_to,sun_position),
|
||||
#FadeOut(angle_arc),
|
||||
#FadeOut(angle_indicator),
|
||||
#FadeIn(globe),
|
||||
#ApplyMethod(light_screen.move_to,[0,0,0]),
|
||||
#ApplyMethod(morty.move_to,[1,0,0])
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2392,10 +2392,12 @@ class ApplyFourierToFourier(DrawFrequencyPlot):
|
||||
kwargs["scale_val"] = 1.0
|
||||
return DrawFrequencyPlot.get_cosine_wave(self, freq, **kwargs)
|
||||
|
||||
class WhiteComplexExponentialExpression(DrawFrequencyPlot):
|
||||
class WriteComplexExponentialExpression(DrawFrequencyPlot):
|
||||
CONFIG = {
|
||||
"signal_frequency" : 2.0,
|
||||
"default_num_v_lines_indicating_periods" : 0,
|
||||
"time_axes_scale_val" : 0.7,
|
||||
"initial_winding_frequency" : 0.1,
|
||||
}
|
||||
def construct(self):
|
||||
self.remove(self.pi_creature)
|
||||
@@ -2404,15 +2406,16 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot):
|
||||
self.show_winding_with_both_coordinates()
|
||||
self.show_plane_as_complex_plane()
|
||||
self.show_eulers_formula()
|
||||
self.reference_other_video()
|
||||
self.show_winding_graph_expression()
|
||||
self.find_center_of_mass()
|
||||
|
||||
def setup_plane(self):
|
||||
circle_plane = ComplexPlane(
|
||||
unit_size = 2,
|
||||
y_radius = SPACE_HEIGHT+LARGE_BUFF
|
||||
y_radius = SPACE_HEIGHT+LARGE_BUFF,
|
||||
x_radius = SPACE_WIDTH+LARGE_BUFF
|
||||
)
|
||||
circle_plane.shift(DOWN)
|
||||
circle_plane.shift(DOWN+LEFT)
|
||||
circle = DashedLine(ORIGIN, TAU*UP)
|
||||
circle.apply_complex_function(
|
||||
lambda z : R3_to_complex(
|
||||
@@ -2427,7 +2430,7 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot):
|
||||
fill_opacity = 0.9,
|
||||
buff = MED_SMALL_BUFF,
|
||||
))
|
||||
time_axes.scale(0.7)
|
||||
time_axes.scale(self.time_axes_scale_val)
|
||||
time_axes.to_corner(UP+LEFT, buff = 0)
|
||||
time_axes.set_stroke(color = WHITE, width = 1)
|
||||
|
||||
@@ -2444,7 +2447,7 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot):
|
||||
scale_val = 0.5,
|
||||
shift_val = 0.75,
|
||||
)
|
||||
freq = 0.1
|
||||
freq = self.initial_winding_frequency
|
||||
pol_graph = self.get_polarized_mobject(graph, freq = freq)
|
||||
wps_label = self.get_winding_frequency_label()
|
||||
ChangeDecimalToValue(wps_label[0], freq).update(1)
|
||||
@@ -2455,35 +2458,607 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot):
|
||||
self.generate_center_of_mass_dot_update_anim()
|
||||
|
||||
self.add(graph, pol_graph, wps_label)
|
||||
self.set_variables_as_attrs(pol_graph, wps_label)
|
||||
self.time_axes_group = VGroup(self.time_axes, graph)
|
||||
|
||||
def show_winding_with_both_coordinates(self):
|
||||
#TODO, tie dashed lines to dot
|
||||
com_dot = self.center_of_mass_dot
|
||||
plane = self.circle_plane
|
||||
v_line = Line(ORIGIN, UP)
|
||||
h_line = Line(ORIGIN, RIGHT)
|
||||
lines = VGroup(v_line, h_line)
|
||||
lines.highlight(PINK)
|
||||
def lines_update(lines):
|
||||
point = com_dot.get_center()
|
||||
x, y = plane.point_to_coords(point)
|
||||
h_line.put_start_and_end_on(
|
||||
plane.coords_to_point(0, y), point
|
||||
)
|
||||
v_line.put_start_and_end_on(
|
||||
plane.coords_to_point(x, 0), point
|
||||
)
|
||||
lines_update_anim = ContinualUpdateFromFunc(lines, lines_update)
|
||||
lines_update_anim.update(0)
|
||||
self.add(lines_update_anim)
|
||||
|
||||
self.change_frequency(
|
||||
2.0, run_time = 15,
|
||||
2.04,
|
||||
added_anims = [
|
||||
self.center_of_mass_dot_anim,
|
||||
],
|
||||
run_time = 15,
|
||||
rate_func = bezier([0, 0, 1, 1])
|
||||
)
|
||||
self.wait()
|
||||
|
||||
self.dot_component_anim = lines_update_anim
|
||||
|
||||
def show_plane_as_complex_plane(self):
|
||||
pass
|
||||
to_fade = VGroup(
|
||||
self.time_axes_group, self.pol_graph, self.wps_label
|
||||
)
|
||||
plane = self.circle_plane
|
||||
dot = self.center_of_mass_dot
|
||||
complex_plane_title = TextMobject("Complex plane")
|
||||
complex_plane_title.add_background_rectangle()
|
||||
complex_plane_title.to_edge(UP)
|
||||
coordinate_labels = plane.get_coordinate_labels()
|
||||
number_label = DecimalNumber(
|
||||
0, include_background_rectangle = True,
|
||||
)
|
||||
number_label_update_anim = ContinualChangingDecimal(
|
||||
number_label,
|
||||
lambda a : plane.point_to_number(dot.get_center()),
|
||||
position_update_func = lambda l : l.next_to(
|
||||
dot, DOWN+RIGHT,
|
||||
buff = SMALL_BUFF
|
||||
),
|
||||
)
|
||||
number_label_update_anim.update(0)
|
||||
flower_path = ParametricFunction(
|
||||
lambda t : plane.coords_to_point(
|
||||
np.sin(2*t)*np.cos(t),
|
||||
np.sin(2*t)*np.sin(t),
|
||||
),
|
||||
t_min = 0, t_max = TAU,
|
||||
)
|
||||
flower_path.move_to(self.center_of_mass_dot)
|
||||
|
||||
self.play(FadeOut(to_fade))
|
||||
self.play(Write(complex_plane_title))
|
||||
self.play(Write(coordinate_labels))
|
||||
self.wait()
|
||||
self.play(FadeIn(number_label))
|
||||
self.add(number_label_update_anim)
|
||||
self.play(MoveAlongPath(
|
||||
dot, flower_path,
|
||||
run_time = 10,
|
||||
rate_func = bezier([0, 0, 1, 1])
|
||||
))
|
||||
self.wait()
|
||||
self.play(ShowCreation(
|
||||
self.pol_graph, run_time = 3,
|
||||
))
|
||||
self.play(FadeOut(self.pol_graph))
|
||||
self.wait()
|
||||
self.play(FadeOut(VGroup(
|
||||
dot, self.dot_component_anim.mobject, number_label
|
||||
)))
|
||||
self.remove(self.dot_component_anim)
|
||||
self.remove(number_label_update_anim)
|
||||
|
||||
self.set_variables_as_attrs(
|
||||
number_label,
|
||||
number_label_update_anim,
|
||||
complex_plane_title,
|
||||
)
|
||||
|
||||
def show_eulers_formula(self):
|
||||
pass
|
||||
plane = self.circle_plane
|
||||
|
||||
def reference_other_video(self):
|
||||
pass
|
||||
ghost_dot = Dot(ORIGIN, fill_opacity = 0)
|
||||
def get_t():
|
||||
return ghost_dot.get_center()[0]
|
||||
def get_circle_point(scalar = 1, t_shift = 0):
|
||||
return plane.number_to_point(
|
||||
scalar*np.exp(complex(0, get_t()+t_shift))
|
||||
)
|
||||
vector = Vector(plane.number_to_point(1), color = GREEN)
|
||||
exp_base = TexMobject("e").scale(1.3)
|
||||
exp_base.add_background_rectangle()
|
||||
exp_decimal = DecimalNumber(0, unit = "i", include_background_rectangle = True)
|
||||
exp_decimal.scale(0.75)
|
||||
VGroup(exp_base, exp_decimal).match_color(vector)
|
||||
exp_decimal_update = ContinualChangingDecimal(
|
||||
exp_decimal, lambda a : get_t(),
|
||||
position_update_func = lambda d : d.move_to(
|
||||
exp_base.get_corner(UP+RIGHT), DOWN+LEFT
|
||||
)
|
||||
)
|
||||
exp_base_update = ContinualUpdateFromFunc(
|
||||
exp_base, lambda e : e.move_to(get_circle_point(
|
||||
scalar = 1.1, t_shift = 0.01*TAU
|
||||
))
|
||||
)
|
||||
vector_update = ContinualUpdateFromFunc(
|
||||
vector, lambda v : v.put_start_and_end_on(
|
||||
plane.number_to_point(0), get_circle_point()
|
||||
)
|
||||
)
|
||||
updates = [exp_base_update, exp_decimal_update, vector_update]
|
||||
for update in updates:
|
||||
update.update(0)
|
||||
|
||||
#Show initial vector
|
||||
self.play(
|
||||
GrowArrow(vector),
|
||||
FadeIn(exp_base),
|
||||
Write(exp_decimal)
|
||||
)
|
||||
self.add(*updates)
|
||||
self.play(ghost_dot.shift, 2*RIGHT, run_time = 3)
|
||||
self.wait()
|
||||
|
||||
#Show arc
|
||||
arc, circle = [
|
||||
Line(ORIGIN, t*UP)
|
||||
for t in get_t(), TAU
|
||||
]
|
||||
for mob in arc, circle:
|
||||
mob.insert_n_anchor_points(20)
|
||||
mob.set_stroke(RED, 4)
|
||||
mob.apply_function(
|
||||
lambda p : plane.number_to_point(
|
||||
np.exp(R3_to_complex(p))
|
||||
)
|
||||
)
|
||||
distance_label = DecimalNumber(
|
||||
exp_decimal.number,
|
||||
unit = "\\text{units}"
|
||||
)
|
||||
distance_label[-1].shift(SMALL_BUFF*RIGHT)
|
||||
distance_label.match_color(arc)
|
||||
distance_label.add_background_rectangle()
|
||||
distance_label.move_to(
|
||||
plane.number_to_point(
|
||||
1.1*np.exp(complex(0, 0.4*get_t())),
|
||||
),
|
||||
DOWN+LEFT
|
||||
)
|
||||
|
||||
self.play(ShowCreation(arc))
|
||||
self.play(ReplacementTransform(
|
||||
exp_decimal.copy(), distance_label
|
||||
))
|
||||
self.wait()
|
||||
self.play(FadeOut(distance_label))
|
||||
|
||||
#Show full cycle
|
||||
self.remove(arc)
|
||||
self.play(
|
||||
ghost_dot.move_to, TAU*RIGHT,
|
||||
ShowCreation(
|
||||
circle,
|
||||
rate_func = lambda a : interpolate(
|
||||
2.0/TAU, 1, smooth(a)
|
||||
),
|
||||
),
|
||||
run_time = 6,
|
||||
)
|
||||
self.wait()
|
||||
|
||||
#Write exponential expression
|
||||
exp_expression = TexMobject("e", "^{-", "2\\pi i", "f", "t}")
|
||||
e, minus, two_pi_i, f, t = exp_expression
|
||||
exp_expression.next_to(
|
||||
plane.coords_to_point(1, 1),
|
||||
UP+RIGHT
|
||||
)
|
||||
f.highlight(RED)
|
||||
t.highlight(YELLOW)
|
||||
exp_expression.add_background_rectangle()
|
||||
two_pi_i_f_t_group = VGroup(two_pi_i, f, t)
|
||||
two_pi_i_f_t_group.save_state()
|
||||
two_pi_i_f_t_group.move_to(minus, LEFT)
|
||||
exp_expression[1].remove(minus)
|
||||
t.save_state()
|
||||
t.align_to(f, LEFT)
|
||||
exp_expression[1].remove(f)
|
||||
|
||||
labels = VGroup()
|
||||
for sym, word in (t, "Time"), (f, "Frequency"):
|
||||
label = TextMobject(word)
|
||||
label.match_style(sym)
|
||||
label.next_to(sym, UP, buff = MED_LARGE_BUFF)
|
||||
label.add_background_rectangle()
|
||||
label.arrow = Arrow(label, sym, buff = SMALL_BUFF)
|
||||
label.arrow.match_style(sym)
|
||||
labels.add(label)
|
||||
time_label, frequency_label = labels
|
||||
example_frequency = TexMobject("f = 1/10")
|
||||
example_frequency.add_background_rectangle()
|
||||
example_frequency.match_style(frequency_label)
|
||||
example_frequency.move_to(frequency_label, DOWN)
|
||||
|
||||
self.play(ReplacementTransform(
|
||||
VGroup(exp_base[1], exp_decimal[1]).copy(),
|
||||
exp_expression
|
||||
))
|
||||
self.play(FadeOut(circle))
|
||||
self.wait()
|
||||
|
||||
ghost_dot.move_to(ORIGIN)
|
||||
ambient_ghost_dot_movement = AmbientMovement(
|
||||
ghost_dot, rate = TAU
|
||||
)
|
||||
self.add(ambient_ghost_dot_movement)
|
||||
|
||||
self.play(
|
||||
Write(time_label),
|
||||
GrowArrow(time_label.arrow),
|
||||
)
|
||||
self.wait(6.5) #Leave time to say let's slow down
|
||||
self.remove(ambient_ghost_dot_movement)
|
||||
self.play(
|
||||
FadeOut(time_label),
|
||||
FadeIn(frequency_label),
|
||||
t.restore,
|
||||
GrowFromPoint(f, frequency_label.get_center()),
|
||||
ReplacementTransform(
|
||||
time_label.arrow,
|
||||
frequency_label.arrow,
|
||||
)
|
||||
)
|
||||
ghost_dot.move_to(ORIGIN)
|
||||
ambient_ghost_dot_movement = AmbientMovement(
|
||||
ghost_dot, rate = 0.1*TAU
|
||||
)
|
||||
self.add(ambient_ghost_dot_movement)
|
||||
self.wait(3)
|
||||
self.play(
|
||||
FadeOut(frequency_label),
|
||||
FadeIn(example_frequency)
|
||||
)
|
||||
self.wait(15) #Give time to reference other video
|
||||
#Reverse directions
|
||||
ambient_ghost_dot_movement.rate *= -1
|
||||
self.play(
|
||||
FadeOut(example_frequency),
|
||||
FadeOut(frequency_label.arrow),
|
||||
GrowFromCenter(minus),
|
||||
two_pi_i_f_t_group.restore
|
||||
)
|
||||
self.wait(4)
|
||||
|
||||
ambient_ghost_dot_movement.rate = 0
|
||||
self.remove(*updates)
|
||||
self.play(*map(FadeOut, [
|
||||
update.mobject
|
||||
for update in updates
|
||||
if update.mobject is not vector
|
||||
]))
|
||||
self.play(ghost_dot.move_to, ORIGIN)
|
||||
|
||||
exp_expression[1].add(minus, f)
|
||||
exp_expression[1].sort_submobjects(lambda p : p[0])
|
||||
|
||||
self.set_variables_as_attrs(
|
||||
ambient_ghost_dot_movement, ghost_dot,
|
||||
vector, vector_update, exp_expression
|
||||
)
|
||||
|
||||
def show_winding_graph_expression(self):
|
||||
ambient_ghost_dot_movement = self.ambient_ghost_dot_movement
|
||||
ghost_dot = self.ghost_dot
|
||||
vector = self.vector
|
||||
exp_expression = self.exp_expression
|
||||
plane = self.circle_plane
|
||||
time_axes_group = self.time_axes_group
|
||||
graph = self.graph
|
||||
pol_graph = self.get_polarized_mobject(graph, freq = 0.2)
|
||||
g_label = TexMobject("g(t)")
|
||||
g_label.match_color(graph)
|
||||
g_label.next_to(graph, UP)
|
||||
g_label.add_background_rectangle()
|
||||
g_scalar = g_label.copy()
|
||||
g_scalar.move_to(exp_expression, DOWN+LEFT)
|
||||
|
||||
vector_animations = self.get_vector_animations(graph)
|
||||
vector_animations[1].mobject = vector
|
||||
graph_y_vector = vector_animations[0].mobject
|
||||
|
||||
self.play(
|
||||
FadeIn(time_axes_group),
|
||||
FadeOut(self.complex_plane_title)
|
||||
)
|
||||
self.play(Write(g_label))
|
||||
self.wait()
|
||||
self.play(
|
||||
ReplacementTransform(g_label.copy(), g_scalar),
|
||||
exp_expression.next_to, g_scalar, RIGHT, SMALL_BUFF,
|
||||
exp_expression.shift, 0.5*SMALL_BUFF*UP,
|
||||
)
|
||||
self.play(*vector_animations, run_time = 15)
|
||||
self.add(*self.mobjects_from_last_animation)
|
||||
self.wait()
|
||||
|
||||
integrand = VGroup(g_scalar, exp_expression)
|
||||
rect = SurroundingRectangle(integrand)
|
||||
morty = Mortimer()
|
||||
morty.next_to(rect, DOWN+RIGHT)
|
||||
morty.shift_onto_screen()
|
||||
self.play(
|
||||
ShowCreation(rect),
|
||||
FadeIn(morty)
|
||||
)
|
||||
self.play(morty.change, "raise_right_hand")
|
||||
self.play(Blink(morty))
|
||||
self.play(morty.change, "hooray", rect)
|
||||
self.wait(2)
|
||||
self.play(*map(FadeOut, [
|
||||
morty, rect, graph_y_vector, vector
|
||||
]))
|
||||
|
||||
self.integrand = integrand
|
||||
|
||||
def find_center_of_mass(self):
|
||||
integrand = self.integrand
|
||||
integrand.generate_target()
|
||||
integrand.target.to_edge(RIGHT, buff = LARGE_BUFF)
|
||||
integrand.target.shift(MED_LARGE_BUFF*DOWN)
|
||||
sum_expr = TexMobject(
|
||||
"{1", "\\over", "N}",
|
||||
"\\sum", "_{k = 1}", "^N",
|
||||
)
|
||||
sum_expr.add_background_rectangle()
|
||||
sum_expr.shift(SMALL_BUFF*(UP+5*RIGHT))
|
||||
sum_expr.next_to(integrand.target, LEFT)
|
||||
|
||||
integral = TexMobject(
|
||||
"{1", "\\over", "t_2 - t_1}",
|
||||
"\\int", "_{t_1}", "^{t_2}"
|
||||
)
|
||||
integral.move_to(sum_expr, RIGHT)
|
||||
time_interval_indicator = SurroundingRectangle(integral[2])
|
||||
integral.add_background_rectangle()
|
||||
axes = self.time_axes
|
||||
time_interval = Line(
|
||||
axes.coords_to_point(axes.x_min, 0),
|
||||
axes.coords_to_point(axes.x_max, 0),
|
||||
)
|
||||
time_interval.match_style(time_interval_indicator)
|
||||
time_interval_indicator.add(time_interval)
|
||||
dt_mob = TexMobject("dt")
|
||||
dt_mob.next_to(integrand.target, RIGHT, SMALL_BUFF, DOWN)
|
||||
dt_mob.add_background_rectangle()
|
||||
|
||||
dots = self.show_center_of_mass_sampling(20)
|
||||
self.wait()
|
||||
self.play(
|
||||
Write(sum_expr),
|
||||
MoveToTarget(integrand),
|
||||
)
|
||||
|
||||
#Add k subscript to t's
|
||||
t1 = integrand[0][1][2]
|
||||
t2 = integrand[1][1][-1]
|
||||
t_mobs = VGroup(t1, t2)
|
||||
t_mobs.save_state()
|
||||
t_mobs.generate_target()
|
||||
for i, t_mob in enumerate(t_mobs.target):
|
||||
k = TexMobject("k")
|
||||
k.match_style(t_mob)
|
||||
k.match_height(t_mob)
|
||||
k.scale(0.5)
|
||||
k.move_to(t_mob.get_corner(DOWN+RIGHT), LEFT)
|
||||
k.add_background_rectangle()
|
||||
t_mob.add(k)
|
||||
if i == 0:
|
||||
t_mob.shift(0.5*SMALL_BUFF*LEFT)
|
||||
|
||||
self.play(MoveToTarget(t_mobs))
|
||||
self.play(LaggedStart(
|
||||
Indicate, dots[1],
|
||||
rate_func = there_and_back,
|
||||
color = TEAL,
|
||||
))
|
||||
self.show_center_of_mass_sampling(100)
|
||||
dots = self.show_center_of_mass_sampling(500)
|
||||
self.wait()
|
||||
self.play(FadeOut(dots))
|
||||
self.play(
|
||||
ReplacementTransform(sum_expr, integral),
|
||||
FadeIn(dt_mob),
|
||||
t_mobs.restore,
|
||||
)
|
||||
self.wait()
|
||||
self.play(ShowCreation(time_interval_indicator))
|
||||
self.wait()
|
||||
self.play(FadeOut(time_interval_indicator))
|
||||
self.wait()
|
||||
|
||||
#Show confusion
|
||||
randy = Randolph()
|
||||
randy.flip()
|
||||
randy.next_to(integrand, DOWN, LARGE_BUFF)
|
||||
randy.to_edge(RIGHT)
|
||||
full_expression_rect = SurroundingRectangle(
|
||||
VGroup(integral, dt_mob), color = RED
|
||||
)
|
||||
com_dot = self.center_of_mass_dot
|
||||
self.center_of_mass_dot_anim.update(0)
|
||||
com_arrow = Arrow(
|
||||
full_expression_rect.get_left(), com_dot,
|
||||
buff = SMALL_BUFF
|
||||
)
|
||||
com_arrow.match_color(com_dot)
|
||||
|
||||
|
||||
self.play(FadeIn(randy))
|
||||
self.play(randy.change, "confused", integral)
|
||||
self.play(Blink(randy))
|
||||
self.wait(2)
|
||||
self.play(ShowCreation(full_expression_rect))
|
||||
self.play(
|
||||
randy.change, "thinking", self.pol_graph,
|
||||
GrowArrow(com_arrow),
|
||||
GrowFromCenter(com_dot),
|
||||
)
|
||||
self.play(Blink(randy))
|
||||
self.wait(2)
|
||||
|
||||
def show_center_of_mass_sampling(self, n_dots):
|
||||
time_graph = self.graph
|
||||
pol_graph = self.graph.polarized_mobject
|
||||
axes = self.time_axes
|
||||
|
||||
dot = Dot(radius = 0.05, color = PINK)
|
||||
pre_dots = VGroup(*[
|
||||
dot.copy().move_to(axes.coords_to_point(t, 0))
|
||||
for t in np.linspace(axes.x_min, axes.x_max, n_dots)
|
||||
])
|
||||
pre_dots.set_fill(opacity = 0)
|
||||
for graph in time_graph, pol_graph:
|
||||
if hasattr(graph, "dots"):
|
||||
graph.dot_fade_anims = [FadeOut(graph.dots)]
|
||||
else:
|
||||
graph.dot_fade_anims = []
|
||||
graph.save_state()
|
||||
graph.generate_target()
|
||||
if not hasattr(graph, "is_faded"):
|
||||
graph.target.fade(0.7)
|
||||
graph.is_faded = True
|
||||
graph.dots = VGroup(*[
|
||||
dot.copy().move_to(graph.point_from_proportion(a))
|
||||
for a in np.linspace(0, 1, n_dots)
|
||||
])
|
||||
|
||||
self.play(
|
||||
ReplacementTransform(
|
||||
pre_dots, time_graph.dots,
|
||||
submobject_mode = "lagged_start",
|
||||
run_time = 2,
|
||||
),
|
||||
MoveToTarget(time_graph),
|
||||
*time_graph.dot_fade_anims
|
||||
)
|
||||
self.play(
|
||||
ReplacementTransform(
|
||||
time_graph.copy(), pol_graph.target
|
||||
),
|
||||
MoveToTarget(pol_graph),
|
||||
ReplacementTransform(
|
||||
time_graph.dots.copy(),
|
||||
pol_graph.dots,
|
||||
),
|
||||
*pol_graph.dot_fade_anims,
|
||||
run_time = 2
|
||||
)
|
||||
return VGroup(time_graph.dots, pol_graph.dots)
|
||||
|
||||
|
||||
class WhyAreYouTellingUsThis(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
self.student_says("Why are you \\\\ telling us this?")
|
||||
self.play(self.teacher.change, "happy")
|
||||
self.wait(2)
|
||||
|
||||
class BuildUpExpressionStepByStep(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
expression = TexMobject(
|
||||
"\\frac{1}{t_2 - t_1}", "\\int_{t_1}^{t_2}",
|
||||
"g(t)", "e", "^{2\\pi i", "f", "t}", "dt"
|
||||
)
|
||||
frac, integral, g, e, two_pi_i, f, t, dt = expression
|
||||
expression.next_to(self.teacher, UP+LEFT)
|
||||
t.highlight(YELLOW)
|
||||
g[2].highlight(YELLOW)
|
||||
dt[1].highlight(YELLOW)
|
||||
f.highlight(GREEN)
|
||||
t.save_state()
|
||||
t.move_to(f, LEFT)
|
||||
|
||||
self.play(
|
||||
self.teacher.change, "raise_right_hand",
|
||||
FadeIn(e),
|
||||
FadeIn(two_pi_i),
|
||||
)
|
||||
self.play(
|
||||
self.get_student_changes(*["pondering"]*3),
|
||||
FadeIn(t),
|
||||
)
|
||||
self.play(
|
||||
FadeIn(f),
|
||||
t.restore,
|
||||
)
|
||||
self.wait()
|
||||
self.play(FadeIn(g), Blink(self.students[1]))
|
||||
self.wait()
|
||||
self.play(
|
||||
FadeIn(integral),
|
||||
FadeIn(frac),
|
||||
FadeIn(dt),
|
||||
)
|
||||
self.wait(3)
|
||||
self.teacher_says(
|
||||
"Just one final \\\\ distinction.",
|
||||
bubble_kwargs = {"height" : 2.5, "width" : 3.5},
|
||||
added_anims = [expression.to_corner, UP+RIGHT]
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
class ScaleUpCenterOfMass(WriteComplexExponentialExpression):
|
||||
CONFIG = {
|
||||
"time_axes_scale_val" : 0.6,
|
||||
"initial_winding_frequency" : 1.95
|
||||
}
|
||||
def construct(self):
|
||||
self.remove(self.pi_creature)
|
||||
self.setup_plane()
|
||||
self.setup_graph()
|
||||
self.add_expression()
|
||||
self.add_center_of_mass_dot()
|
||||
|
||||
self.cross_out_denominator()
|
||||
self.scale_up_center_of_mass()
|
||||
self.what_this_means_for_various_winding_frequencies()
|
||||
|
||||
|
||||
def add_expression(self):
|
||||
expression = TexMobject(
|
||||
"\\frac{1}{t_2 - t_1}", "\\int_{t_1}^{t_2}",
|
||||
"g(t)", "e", "^{2\\pi i", "f", "t}", "dt"
|
||||
)
|
||||
frac, integral, g, e, two_pi_i, f, t, dt = expression
|
||||
expression.to_corner(UP+RIGHT)
|
||||
t.highlight(YELLOW)
|
||||
g[2].highlight(YELLOW)
|
||||
dt[1].highlight(YELLOW)
|
||||
f.highlight(GREEN)
|
||||
expression.add_background_rectangle()
|
||||
self.expression = expression
|
||||
self.add(expression)
|
||||
|
||||
self.winding_freq_label.to_edge(RIGHT)
|
||||
self.winding_freq_label[1].match_color(f)
|
||||
|
||||
def add_center_of_mass_dot(self):
|
||||
self.center_of_mass_dot = self.get_center_of_mass_dot()
|
||||
self.generate_center_of_mass_dot_update_anim()
|
||||
self.add(self.center_of_mass_dot)
|
||||
|
||||
|
||||
def cross_out_denominator(self):
|
||||
frac = self.expression[0]
|
||||
integral = VGroup(*self.expression[1:])
|
||||
|
||||
|
||||
def scale_up_center_of_mass(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def what_this_means_for_various_winding_frequencies(self):
|
||||
pass
|
||||
|
||||
|
||||
class CloseWithAPuzzle(TeacherStudentsScene):
|
||||
|
||||
@@ -12,20 +12,19 @@ from transform import Transform
|
||||
|
||||
class Rotating(Animation):
|
||||
CONFIG = {
|
||||
"axes" : [],
|
||||
"axis" : OUT,
|
||||
"radians" : 2*np.pi,
|
||||
"run_time" : 5,
|
||||
"rate_func" : None,
|
||||
"in_place" : True,
|
||||
"about_point" : None,
|
||||
"about_edge" : None,
|
||||
}
|
||||
def update_submobject(self, submobject, starting_submobject, alpha):
|
||||
submobject.points = np.array(starting_submobject.points)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
Animation.update_mobject(self, alpha)
|
||||
axes = self.axes if self.axes else [self.axis]
|
||||
about_point = None
|
||||
if self.about_point is not None:
|
||||
about_point = self.about_point
|
||||
@@ -33,8 +32,9 @@ class Rotating(Animation):
|
||||
self.about_point = self.mobject.get_center()
|
||||
self.mobject.rotate(
|
||||
alpha*self.radians,
|
||||
axes = axes,
|
||||
about_point = self.about_point
|
||||
axis = self.axis,
|
||||
about_point = self.about_point,
|
||||
about_edge = self.about_edge,
|
||||
)
|
||||
|
||||
class ShowPartial(Animation):
|
||||
|
||||
21
camera.py
21
camera.py
@@ -99,10 +99,7 @@ class Camera(object):
|
||||
])
|
||||
))
|
||||
|
||||
def capture_mobject(self, mobject):
|
||||
return self.capture_mobjects([mobject])
|
||||
|
||||
def capture_mobjects(
|
||||
def get_mobjects_to_display(
|
||||
self, mobjects,
|
||||
include_submobjects = True,
|
||||
excluded_mobjects = None,
|
||||
@@ -116,6 +113,13 @@ class Camera(object):
|
||||
excluded_mobjects
|
||||
)
|
||||
mobjects = list_difference_update(mobjects, all_excluded)
|
||||
return mobjects
|
||||
|
||||
def capture_mobject(self, mobject, **kwargs):
|
||||
return self.capture_mobjects([mobject], **kwargs)
|
||||
|
||||
def capture_mobjects(self, mobjects, **kwargs):
|
||||
mobjects = self.get_mobjects_to_display(mobjects, **kwargs)
|
||||
vmobjects = []
|
||||
for mobject in mobjects:
|
||||
if isinstance(mobject, VMobject):
|
||||
@@ -411,7 +415,7 @@ class MovingCamera(Camera):
|
||||
class MappingCamera(Camera):
|
||||
CONFIG = {
|
||||
"mapping_func" : lambda p : p,
|
||||
"min_anchor_points" : 20,
|
||||
"min_anchor_points" : 50,
|
||||
"allow_object_intrusion" : False
|
||||
}
|
||||
|
||||
@@ -419,6 +423,7 @@ class MappingCamera(Camera):
|
||||
return Camera.points_to_pixel_coords(self, np.apply_along_axis(self.mapping_func, 1, points))
|
||||
|
||||
def capture_mobjects(self, mobjects, **kwargs):
|
||||
mobjects = self.get_mobjects_to_display(mobjects, **kwargs)
|
||||
if self.allow_object_intrusion:
|
||||
mobject_copies = mobjects
|
||||
else:
|
||||
@@ -427,7 +432,11 @@ class MappingCamera(Camera):
|
||||
if isinstance(mobject, VMobject) and \
|
||||
0 < mobject.get_num_anchor_points() < self.min_anchor_points:
|
||||
mobject.insert_n_anchor_points(self.min_anchor_points)
|
||||
Camera.capture_mobjects(self, mobject_copies, **kwargs)
|
||||
Camera.capture_mobjects(
|
||||
self, mobject_copies,
|
||||
include_submobjects = False,
|
||||
excluded_mobjects = None,
|
||||
)
|
||||
|
||||
# TODO: Put this in different utility/helpers file? Convenient for me (Sridhar); I like it.
|
||||
class DictAsObject(object):
|
||||
|
||||
33
helpers.py
33
helpers.py
@@ -252,14 +252,14 @@ def get_all_descendent_classes(Class):
|
||||
result.append(Child)
|
||||
return result
|
||||
|
||||
def filtered_locals(local_args):
|
||||
result = local_args.copy()
|
||||
def filtered_locals(caller_locals):
|
||||
result = caller_locals.copy()
|
||||
ignored_local_args = ["self", "kwargs"]
|
||||
for arg in ignored_local_args:
|
||||
result.pop(arg, local_args)
|
||||
result.pop(arg, caller_locals)
|
||||
return result
|
||||
|
||||
def digest_config(obj, kwargs, local_args = {}):
|
||||
def digest_config(obj, kwargs, caller_locals = {}):
|
||||
"""
|
||||
Sets init args and CONFIG values as local variables
|
||||
|
||||
@@ -268,19 +268,25 @@ def digest_config(obj, kwargs, local_args = {}):
|
||||
be easily passed into instantiation, and is attached
|
||||
as an attribute of the object.
|
||||
"""
|
||||
### Assemble list of CONFIGs from all super classes
|
||||
|
||||
# Assemble list of CONFIGs from all super classes
|
||||
classes_in_hierarchy = [obj.__class__]
|
||||
configs = []
|
||||
static_configs = []
|
||||
while len(classes_in_hierarchy) > 0:
|
||||
Class = classes_in_hierarchy.pop()
|
||||
classes_in_hierarchy += Class.__bases__
|
||||
if hasattr(Class, "CONFIG"):
|
||||
configs.append(Class.CONFIG)
|
||||
static_configs.append(Class.CONFIG)
|
||||
|
||||
#Order matters a lot here, first dicts have higher priority
|
||||
all_dicts = [kwargs, filtered_locals(local_args), obj.__dict__]
|
||||
all_dicts += configs
|
||||
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
|
||||
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])
|
||||
@@ -295,6 +301,15 @@ def merge_config(all_dicts):
|
||||
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
|
||||
already have that key
|
||||
"""
|
||||
for key, value in d2.items():
|
||||
if key not in d1:
|
||||
d1[key] = value
|
||||
|
||||
def digest_locals(obj, keys = None):
|
||||
caller_locals = filtered_locals(
|
||||
inspect.currentframe().f_back.f_locals
|
||||
|
||||
@@ -15,11 +15,11 @@ class Mobject(object):
|
||||
Mathematical Object
|
||||
"""
|
||||
CONFIG = {
|
||||
"color" : WHITE,
|
||||
"color" : WHITE,
|
||||
"stroke_width" : DEFAULT_POINT_THICKNESS,
|
||||
"name" : None,
|
||||
"dim" : 3,
|
||||
"target" : None,
|
||||
"name" : None,
|
||||
"dim" : 3,
|
||||
"target" : None,
|
||||
}
|
||||
def __init__(self, *submobjects, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
@@ -316,14 +316,29 @@ class Mobject(object):
|
||||
self.shift(target_point - point_to_align + buff*direction)
|
||||
return self
|
||||
|
||||
def align_to(self, mobject_or_point, direction = 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
|
||||
top edge lines ups with mob2's top edge.
|
||||
|
||||
mob1.align_to(mob2, alignment_vector = RIGHT) moves mob1
|
||||
horizontally so that it's center is directly above/below
|
||||
the center of mob2
|
||||
"""
|
||||
if isinstance(mobject_or_point, Mobject):
|
||||
mob = mobject_or_point
|
||||
point = mob.get_edge_center(direction)
|
||||
target_point = mob.get_critical_point(direction)
|
||||
else:
|
||||
point = mobject_or_point
|
||||
diff = point - self.get_edge_center(direction)
|
||||
self.shift(direction*np.dot(diff, direction))
|
||||
target_point = mobject_or_point
|
||||
direction_norm = np.linalg.norm(direction)
|
||||
if direction_norm > 0:
|
||||
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))
|
||||
return self
|
||||
|
||||
def shift_onto_screen(self, **kwargs):
|
||||
@@ -648,8 +663,12 @@ class Mobject(object):
|
||||
|
||||
## Family matters
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.split()[index]
|
||||
def __getitem__(self, value):
|
||||
self_list = self.split()
|
||||
if isinstance(value, slice):
|
||||
GroupClass = self.get_group_class()
|
||||
return GroupClass(*self_list.__getitem__(value))
|
||||
return self_list.__getitem__(value)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.split())
|
||||
@@ -657,6 +676,9 @@ class Mobject(object):
|
||||
def __len__(self):
|
||||
return len(self.split())
|
||||
|
||||
def get_group_class(self):
|
||||
return Group
|
||||
|
||||
def split(self):
|
||||
result = [self] if len(self.points) > 0 else []
|
||||
return result + self.submobjects
|
||||
|
||||
@@ -19,6 +19,9 @@ class VMobject(Mobject):
|
||||
"make_smooth_after_applying_functions" : False,
|
||||
}
|
||||
|
||||
def get_group_class(self):
|
||||
return VGroup
|
||||
|
||||
## Colors
|
||||
def init_colors(self):
|
||||
self.set_style_data(
|
||||
@@ -85,15 +88,23 @@ class VMobject(Mobject):
|
||||
return self
|
||||
|
||||
def match_style(self, vmobject):
|
||||
#TODO: Should this be smart about matching the
|
||||
#style of the family members, if they happen to
|
||||
#be different?
|
||||
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
|
||||
)
|
||||
|
||||
#Does its best to match up submobject lists, and
|
||||
#match styles accordingly
|
||||
submobs1, submobs2 = self.submobjects, vmobject.submobjects
|
||||
if len(submobs1) == 0:
|
||||
return
|
||||
elif len(submobs2) == 0:
|
||||
submobs2 = [vmobject]
|
||||
for sm1, sm2 in zip(*make_even(submobs1, submobs2)):
|
||||
sm1.match_style(sm2)
|
||||
return
|
||||
|
||||
def fade(self, darkness = 0.5):
|
||||
|
||||
@@ -482,7 +482,7 @@ class Scene(object):
|
||||
|
||||
def preview(self):
|
||||
TkSceneRoot(self)
|
||||
|
||||
|
||||
def get_image_file_path(self, name = None, dont_update = False):
|
||||
folder = "images"
|
||||
if dont_update:
|
||||
@@ -540,7 +540,6 @@ class Scene(object):
|
||||
'-loglevel', 'error',
|
||||
temp_file_path,
|
||||
]
|
||||
|
||||
# self.writing_process = sp.Popen(command, stdin=sp.PIPE, shell=True)
|
||||
self.writing_process = sp.Popen(command, stdin=sp.PIPE)
|
||||
|
||||
|
||||
@@ -36,15 +36,39 @@ class Arc(VMobject):
|
||||
)
|
||||
self.scale(self.radius, about_point = ORIGIN)
|
||||
|
||||
def add_tip(self, tip_length = 0.25):
|
||||
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
|
||||
self.remove(submob)
|
||||
|
||||
#TODO, do this a better way
|
||||
p1, p2 = self.points[-2:]
|
||||
arrow = Arrow(
|
||||
p1, 2*p2 - p1,
|
||||
tip_length = tip_length,
|
||||
max_tip_length_to_length_ratio = 2.0
|
||||
)
|
||||
self.add(arrow.split()[-1])
|
||||
p1 = p2 = p3 = p4 = None
|
||||
start_arrow = end_arrow = None
|
||||
if at_start:
|
||||
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
|
||||
)
|
||||
self.add(start_arrow.split()[-1]) # just the tip
|
||||
|
||||
if at_end:
|
||||
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
|
||||
)
|
||||
self.add(end_arrow.split()[-1])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
self.highlight(self.get_color())
|
||||
return self
|
||||
|
||||
@@ -66,6 +90,13 @@ class Arc(VMobject):
|
||||
def stop_angle(self):
|
||||
return self.start_angle + self.angle
|
||||
|
||||
def set_bound_angles(self,start=0,stop=np.pi):
|
||||
self.start_angle = start
|
||||
self.angle = stop - start
|
||||
|
||||
return self
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -141,18 +172,13 @@ class AnnularSector(VMobject):
|
||||
arc_center = first_point - self.inner_radius * radial_unit_vector
|
||||
return arc_center
|
||||
|
||||
<<<<<<< HEAD
|
||||
def move_arc_center_to(self,point):
|
||||
v = point - self.get_arc_center()
|
||||
self.shift(v)
|
||||
return self
|
||||
|
||||
|
||||
|
||||
=======
|
||||
>>>>>>> master
|
||||
class Sector(AnnularSector):
|
||||
|
||||
CONFIG = {
|
||||
"outer_radius" : 1,
|
||||
"inner_radius" : 0
|
||||
@@ -178,10 +204,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):
|
||||
|
||||
@@ -3,6 +3,7 @@ from mobject.vectorized_mobject import VMobject, VGroup, VectorizedPoint
|
||||
from mobject.tex_mobject import TexMobject
|
||||
from animation import Animation
|
||||
from animation.continual_animation import ContinualAnimation
|
||||
from topics.geometry import BackgroundRectangle
|
||||
from scene import Scene
|
||||
from helpers import *
|
||||
|
||||
@@ -10,26 +11,37 @@ class DecimalNumber(VMobject):
|
||||
CONFIG = {
|
||||
"num_decimal_points" : 2,
|
||||
"digit_to_digit_buff" : 0.05,
|
||||
"show_ellipsis" : False
|
||||
"show_ellipsis" : False,
|
||||
"unit" : None,
|
||||
"include_background_rectangle" : False,
|
||||
}
|
||||
def __init__(self, number, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
num_string = '%.*f'%(self.num_decimal_points, number)
|
||||
negative_zero_string = "-%.*f"%(self.num_decimal_points, 0.)
|
||||
if num_string == negative_zero_string:
|
||||
num_string = num_string[1:]
|
||||
VMobject.__init__(self, *[
|
||||
TexMobject(char)
|
||||
for char in num_string
|
||||
], **kwargs)
|
||||
VMobject.__init__(self, **kwargs)
|
||||
self.number = number
|
||||
ndp = self.num_decimal_points
|
||||
|
||||
#Build number string
|
||||
if isinstance(number, complex):
|
||||
num_string = '%.*f%s%.*fi'%(
|
||||
ndp, number.real,
|
||||
"-" if number.imag < 0 else "+",
|
||||
ndp, abs(number.imag)
|
||||
)
|
||||
else:
|
||||
num_string = '%.*f'%(ndp, number)
|
||||
negative_zero_string = "-%.*f"%(ndp, 0.)
|
||||
if num_string == negative_zero_string:
|
||||
num_string = num_string[1:]
|
||||
self.add(*[
|
||||
TexMobject(char, **kwargs)
|
||||
for char in num_string
|
||||
])
|
||||
|
||||
#Add non-numerical bits
|
||||
if self.show_ellipsis:
|
||||
self.add(TexMobject("\\dots"))
|
||||
|
||||
self.arrange_submobjects(
|
||||
buff = self.digit_to_digit_buff,
|
||||
aligned_edge = DOWN
|
||||
)
|
||||
|
||||
|
||||
if num_string.startswith("-"):
|
||||
minus = self.submobjects[0]
|
||||
minus.next_to(
|
||||
@@ -37,38 +49,68 @@ class DecimalNumber(VMobject):
|
||||
buff = self.digit_to_digit_buff
|
||||
)
|
||||
|
||||
class Integer(VGroup):
|
||||
CONFIG = {
|
||||
"digit_buff" : 0.8*SMALL_BUFF
|
||||
}
|
||||
def __init__(self, integer, **kwargs):
|
||||
self.number = integer
|
||||
num_str = str(integer)
|
||||
VGroup.__init__(self, *map(TexMobject, num_str), **kwargs)
|
||||
if self.unit != None:
|
||||
unit_sign = TexMobject(self.unit)
|
||||
unit_sign.next_to(self.submobjects[-1],RIGHT,
|
||||
buff = self.digit_to_digit_buff)
|
||||
|
||||
if self.unit == "^\\circ":
|
||||
unit_sign.align_to(self,UP)
|
||||
else:
|
||||
unit_sign.align_to(self,DOWN)
|
||||
self.add(unit_sign)
|
||||
|
||||
self.arrange_submobjects(
|
||||
RIGHT, buff = self.digit_buff, aligned_edge = DOWN
|
||||
buff = self.digit_to_digit_buff,
|
||||
aligned_edge = DOWN
|
||||
)
|
||||
if num_str[0] == "-":
|
||||
self[0].next_to(self[1], LEFT, buff = SMALL_BUFF)
|
||||
|
||||
|
||||
class Integer(VGroup):
|
||||
#Handle alignment of parts that should be aligned
|
||||
#to the bottom
|
||||
for i, c in enumerate(num_string):
|
||||
if c == "-" and len(num_string) > i+1:
|
||||
self[i].align_to(self[i+1], alignment_vect = UP)
|
||||
if self.unit == "\\circ":
|
||||
self[-1].align_to(self, UP)
|
||||
#
|
||||
if self.include_background_rectangle:
|
||||
self.add_background_rectangle()
|
||||
|
||||
def add_background_rectangle(self):
|
||||
#TODO, is this the best way to handle
|
||||
#background rectangles?
|
||||
self.background_rectangle = BackgroundRectangle(self)
|
||||
self.submobjects = [
|
||||
self.background_rectangle,
|
||||
VGroup(*self.submobjects)
|
||||
]
|
||||
return self
|
||||
|
||||
class Integer(DecimalNumber):
|
||||
CONFIG = {
|
||||
"num_decimal_points" : 0,
|
||||
}
|
||||
|
||||
class ChangingDecimal(Animation):
|
||||
CONFIG = {
|
||||
"num_decimal_points" : None,
|
||||
"show_ellipsis" : None,
|
||||
"spare_parts" : 2,
|
||||
"position_update_func" : None,
|
||||
"tracked_mobject" : None
|
||||
}
|
||||
def __init__(self, decimal_number_mobject, number_update_func, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
if self.num_decimal_points is None:
|
||||
self.num_decimal_points = decimal_number_mobject.num_decimal_points
|
||||
if self.show_ellipsis is None:
|
||||
self.show_ellipsis = decimal_number_mobject.show_ellipsis
|
||||
decimal_number_mobject.add(*[
|
||||
VectorizedPoint(decimal_number_mobject.get_corner(DOWN+LEFT))
|
||||
for x in range(self.spare_parts)]
|
||||
self.decimal_number_config = dict(
|
||||
decimal_number_mobject.initial_config
|
||||
)
|
||||
for attr in "num_decimal_points", "show_ellipsis":
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
self.decimal_number_config[attr] = value
|
||||
if hasattr(self.decimal_number_mobject, "background_rectangle"):
|
||||
self.decimal_number_config["include_background_rectangle"] = True
|
||||
if self.tracked_mobject:
|
||||
dmc = decimal_number_mobject.get_center()
|
||||
tmc = self.tracked_mobject.get_center()
|
||||
@@ -83,12 +125,11 @@ class ChangingDecimal(Animation):
|
||||
decimal = self.decimal_number_mobject
|
||||
new_number = self.number_update_func(alpha)
|
||||
new_decimal = DecimalNumber(
|
||||
new_number,
|
||||
num_decimal_points = self.num_decimal_points,
|
||||
show_ellipsis = self.show_ellipsis
|
||||
new_number, **self.decimal_number_config
|
||||
)
|
||||
new_decimal.replace(decimal, dim_to_match = 1)
|
||||
new_decimal.highlight(decimal.get_color())
|
||||
new_decimal.match_height(decimal)
|
||||
new_decimal.move_to(decimal)
|
||||
new_decimal.match_style(decimal)
|
||||
|
||||
decimal.submobjects = new_decimal.submobjects
|
||||
decimal.number = new_number
|
||||
|
||||
Reference in New Issue
Block a user