mirror of
https://github.com/3b1b/manim.git
synced 2025-07-31 05:52:34 +08:00
Incremental progress on WindingNumber
This commit is contained in:
@ -128,7 +128,7 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
|
||||
|
||||
downBrace, upBrace = yBraces = TexMobject("||")
|
||||
yBraces.stretch(2, 0)
|
||||
yBraces.rotate(np.pi/2)
|
||||
yBraces.rotate(TAU/4)
|
||||
|
||||
lowerX = self.initial_lower_x
|
||||
lowerY = self.func(lowerX)
|
||||
@ -261,13 +261,14 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
|
||||
# popping out and in?
|
||||
#
|
||||
# TODO: Perhaps have bullets change color corresponding to a function of their coordinates?
|
||||
# This could involve some merging of functoinality with PiColorWakler
|
||||
# This could involve some merging of functoinality with PiWalker
|
||||
class LinePulser(ContinualAnimation):
|
||||
def __init__(self, line, bullet_template, num_bullets, pulse_time, **kwargs):
|
||||
def __init__(self, line, bullet_template, num_bullets, pulse_time, color_func = None, **kwargs):
|
||||
self.line = line
|
||||
self.num_bullets = num_bullets
|
||||
self.pulse_time = pulse_time
|
||||
self.bullets = [bullet_template.copy() for i in range(num_bullets)]
|
||||
self.color_func = color_func
|
||||
ContinualAnimation.__init__(self, VGroup(line, VGroup(*self.bullets)), **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
@ -275,8 +276,11 @@ class LinePulser(ContinualAnimation):
|
||||
start = self.line.get_start()
|
||||
end = self.line.get_end()
|
||||
for i in range(self.num_bullets):
|
||||
self.bullets[i].move_to(interpolate(start, end,
|
||||
np.true_divide((i + alpha),(self.num_bullets))))
|
||||
position = interpolate(start, end,
|
||||
np.true_divide((i + alpha),(self.num_bullets)))
|
||||
self.bullets[i].move_to(position)
|
||||
if self.color_func:
|
||||
self.bullets.set_color(self.color_func(position))
|
||||
|
||||
|
||||
def color_func(alpha):
|
||||
@ -298,7 +302,7 @@ class ArrowCircleTest(Scene):
|
||||
base_arrow = Arrow(circle_radius * 0.7 * RIGHT, circle_radius * 1.3 * RIGHT)
|
||||
|
||||
def rev_rotate(x, revs):
|
||||
x.rotate(revs * 2 * np.pi)
|
||||
x.rotate(revs * TAU)
|
||||
x.set_color(color_func(revs))
|
||||
return x
|
||||
|
||||
@ -322,8 +326,9 @@ class FuncRotater(Animation):
|
||||
def update_mobject(self, alpha):
|
||||
Animation.update_mobject(self, alpha)
|
||||
angle_revs = self.rotate_func(alpha)
|
||||
# We do a clockwise rotation
|
||||
self.mobject.rotate(
|
||||
angle_revs * 2 * np.pi,
|
||||
-angle_revs * TAU,
|
||||
about_point = ORIGIN
|
||||
)
|
||||
self.mobject.set_color(color_func(angle_revs))
|
||||
@ -336,21 +341,40 @@ class TestRotater(Scene):
|
||||
rotate_func = lambda x : x % 0.25,
|
||||
run_time = 10))
|
||||
|
||||
# TODO: Be careful about clockwise vs. counterclockwise convention throughout!
|
||||
# Make sure this is correct everywhere in resulting video.
|
||||
class OdometerScene(Scene):
|
||||
CONFIG = {
|
||||
"rotate_func" : lambda x : np.sin(x * 2 * np.pi),
|
||||
"run_time" : 5
|
||||
"rotate_func" : lambda x : np.sin(x * TAU),
|
||||
"run_time" : 5,
|
||||
"dashed_line_angle" : None,
|
||||
"biased_display_start" : None
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
base_arrow = Arrow(ORIGIN, RIGHT)
|
||||
circle = Circle(center = ORIGIN, radius = 1.3)
|
||||
radius = 1.3
|
||||
circle = Circle(center = ORIGIN, radius = radius)
|
||||
self.add(circle)
|
||||
|
||||
if self.dashed_line_angle:
|
||||
dashed_line = DashedLine(ORIGIN, radius * RIGHT)
|
||||
# Clockwise rotation
|
||||
dashed_line.rotate(-self.dashed_line_angle * TAU, about_point = ORIGIN)
|
||||
self.add(dashed_line)
|
||||
|
||||
num_display = DecimalNumber(0)
|
||||
num_display.move_to(2 * DOWN)
|
||||
|
||||
display_val_bias = 0
|
||||
if self.biased_display_start != None:
|
||||
display_val_bias = self.biased_display_start - self.rotate_func(0)
|
||||
display_func = lambda alpha : self.rotate_func(alpha) + display_val_bias
|
||||
|
||||
base_arrow = Arrow(ORIGIN, RIGHT, buff = 0)
|
||||
|
||||
self.play(
|
||||
FuncRotater(base_arrow, rotate_func = self.rotate_func),
|
||||
ChangingDecimal(num_display, self.rotate_func),
|
||||
ChangingDecimal(num_display, display_func),
|
||||
run_time = self.run_time,
|
||||
rate_func = None)
|
||||
|
||||
@ -361,7 +385,7 @@ def point_to_rev((x, y)):
|
||||
if (x, y) == (0, 0):
|
||||
print "Error! Angle of (0, 0) computed!"
|
||||
return None
|
||||
return np.true_divide(np.arctan2(y, x), 2 * np.pi)
|
||||
return np.true_divide(np.arctan2(y, x), TAU)
|
||||
|
||||
# Returns the value with the same fractional component as x, closest to m
|
||||
def resit_near(x, m):
|
||||
@ -441,6 +465,8 @@ class RectangleData():
|
||||
sides = (self.get_top(), self.get_bottom())
|
||||
elif dim == 1:
|
||||
sides = (self.get_left(), self.get_right())
|
||||
else:
|
||||
print "RectangleData.split_line_on_dim passed illegitimate dimension!"
|
||||
|
||||
return tuple([mid(x, y) for (x, y) in sides])
|
||||
|
||||
@ -455,10 +481,120 @@ def plane_poly_with_roots(*points):
|
||||
def plane_func_from_complex_func(f):
|
||||
return lambda (x, y) : complex_to_pair(f(complex(x,y)))
|
||||
|
||||
def point_func_from_complex_func(f):
|
||||
return lambda (x, y, z): complex_to_R3(f(complex(x, y)))
|
||||
|
||||
empty_animation = Animation(Mobject())
|
||||
def EmptyAnimation():
|
||||
return empty_animation
|
||||
|
||||
class WalkerAnimation(Animation):
|
||||
CONFIG = {
|
||||
"walk_func" : None, # Must be initialized to use
|
||||
"remover" : True,
|
||||
"rate_func" : None,
|
||||
"coords_to_point" : None
|
||||
}
|
||||
|
||||
def __init__(self, walk_func, rev_func, coords_to_point, **kwargs):
|
||||
self.walk_func = walk_func
|
||||
self.rev_func = rev_func
|
||||
self.coords_to_point = coords_to_point
|
||||
self.compound_walker = VGroup()
|
||||
self.compound_walker.walker = PiCreature(color = RED)
|
||||
self.compound_walker.walker.scale(0.35)
|
||||
self.compound_walker.arrow = Arrow(ORIGIN, RIGHT) #, buff = 0)
|
||||
self.compound_walker.digest_mobject_attrs()
|
||||
Animation.__init__(self, self.compound_walker, **kwargs)
|
||||
|
||||
# Perhaps abstract this out into an "Animation updating from original object" class
|
||||
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)
|
||||
cur_x, cur_y = cur_coords = self.walk_func(alpha)
|
||||
self.mobject.walker.move_to(self.coords_to_point(cur_x, cur_y))
|
||||
rev = self.rev_func(cur_coords)
|
||||
self.mobject.walker.set_color(color_func(rev))
|
||||
self.mobject.arrow.set_color(color_func(rev))
|
||||
self.mobject.arrow.rotate(
|
||||
rev * TAU,
|
||||
about_point = ORIGIN #self.mobject.arrow.get_start()
|
||||
)
|
||||
|
||||
def LinearWalker(start_coords, end_coords, coords_to_point, rev_func, **kwargs):
|
||||
walk_func = lambda alpha : interpolate(start_coords, end_coords, alpha)
|
||||
return WalkerAnimation(
|
||||
walk_func = walk_func,
|
||||
coords_to_point = coords_to_point,
|
||||
rev_func = rev_func,
|
||||
**kwargs)
|
||||
|
||||
class PiWalker(Scene):
|
||||
CONFIG = {
|
||||
"func" : plane_func_from_complex_func(lambda c : c**2),
|
||||
"walk_coords" : [],
|
||||
"step_run_time" : 1
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
|
||||
rev_func = lambda p : point_to_rev(self.func(p))
|
||||
|
||||
num_plane = NumberPlane()
|
||||
num_plane.fade()
|
||||
self.add(num_plane)
|
||||
|
||||
walk_coords = self.walk_coords
|
||||
for i in range(len(walk_coords)):
|
||||
start_x, start_y = start_coords = walk_coords[i]
|
||||
start_point = num_plane.coords_to_point(start_x, start_y)
|
||||
end_x, end_y = end_coords = walk_coords[(i + 1) % len(walk_coords)]
|
||||
end_point = num_plane.coords_to_point(end_x, end_y)
|
||||
self.play(
|
||||
LinearWalker(
|
||||
start_coords = start_coords,
|
||||
end_coords = end_coords,
|
||||
coords_to_point = num_plane.coords_to_point,
|
||||
rev_func = rev_func,
|
||||
remover = (i < len(walk_coords) - 1)
|
||||
),
|
||||
ShowCreation(Line(start_point, end_point)),
|
||||
run_time = self.step_run_time)
|
||||
|
||||
|
||||
self.wait()
|
||||
|
||||
class PiWalkerRect(PiWalker):
|
||||
CONFIG = {
|
||||
"start_x" : -1,
|
||||
"start_y" : 1,
|
||||
"walk_width" : 2,
|
||||
"walk_height" : 2,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
TL = np.array((self.start_x, self.start_y))
|
||||
TR = TL + (self.walk_width, 0)
|
||||
BR = TR + (0, -self.walk_height)
|
||||
BL = BR + (-self.walk_width, 0)
|
||||
self.walk_coords = [TL, TR, BR, BL]
|
||||
PiWalker.setup(self)
|
||||
|
||||
class PiWalkerCircle(PiWalker):
|
||||
CONFIG = {
|
||||
"radius" : 1,
|
||||
"num_steps" : 100,
|
||||
"step_run_time" : 0.01
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
r = self.radius
|
||||
N = self.num_steps
|
||||
self.walk_coords = [r * np.array((np.cos(i * TAU/N), np.sin(i * TAU/N))) for i in range(N)]
|
||||
PiWalker.setup(self)
|
||||
|
||||
# TODO: Perhaps restructure this to avoid using AnimationGroup, and instead
|
||||
# use lists of animations or lists or other such data, to be merged and processed into parallel
|
||||
# animations later
|
||||
@ -495,8 +631,17 @@ class EquationSolver2d(Scene):
|
||||
color = RED)
|
||||
thin_line = line.copy()
|
||||
thin_line.set_stroke(width = 1)
|
||||
walker_anim = LinearWalker(
|
||||
start_coords = start,
|
||||
end_coords = end,
|
||||
coords_to_point = num_plane.coords_to_point,
|
||||
rev_func = rev_func,
|
||||
remover = True
|
||||
)
|
||||
line_draw_anim = AnimationGroup(ShowCreation(line, rate_func = None), walker_anim,
|
||||
run_time = 2)
|
||||
anim = Succession(
|
||||
ShowCreation, line,
|
||||
line_draw_anim,
|
||||
Transform, line, thin_line
|
||||
)
|
||||
return (anim, rebased_winder(1))
|
||||
@ -523,6 +668,8 @@ class EquationSolver2d(Scene):
|
||||
rect.get_bottom_left()
|
||||
]
|
||||
points = [num_plane.coords_to_point(x, y) for (x, y) in coords]
|
||||
# TODO: Maybe use diagonal lines or something to fill in rectangles indicating
|
||||
# their "Nothing here" status?
|
||||
fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = RED)
|
||||
return Succession(anim, FadeIn(fill_rect))
|
||||
else:
|
||||
@ -541,7 +688,7 @@ class EquationSolver2d(Scene):
|
||||
mid_line = DashedLine(*mid_line_points)
|
||||
return Succession(anim,
|
||||
ShowCreation(mid_line),
|
||||
FadeOut(mid_line),
|
||||
# FadeOut(mid_line), # TODO: Can change timing so this fades out at just the time it would be overdrawn
|
||||
AnimationGroup(*sub_anims)
|
||||
)
|
||||
|
||||
@ -565,80 +712,6 @@ class EquationSolver2d(Scene):
|
||||
|
||||
self.wait()
|
||||
|
||||
class PiColorWalker(Scene):
|
||||
CONFIG = {
|
||||
"func" : plane_func_from_complex_func(lambda c : c**2),
|
||||
"start_x" : -1,
|
||||
"start_y" : 1,
|
||||
"walk_width" : 2,
|
||||
"walk_height" : 2,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
|
||||
rev_func = lambda p : point_to_rev(self.func(p))
|
||||
|
||||
num_plane = NumberPlane()
|
||||
num_plane.fade()
|
||||
self.add(num_plane)
|
||||
|
||||
class WalkerAnimation(Animation):
|
||||
CONFIG = {
|
||||
"remover" : True,
|
||||
"rate_func" : None,
|
||||
"start_coords" : None,
|
||||
"end_coords" : None,
|
||||
}
|
||||
|
||||
def __init__(self, start_coords, end_coords, **kwargs):
|
||||
self.start_coords = start_coords
|
||||
self.end_coords = end_coords
|
||||
self.compound_walker = VGroup()
|
||||
self.compound_walker.walker = PiCreature(color = RED)
|
||||
self.compound_walker.walker.scale(0.35)
|
||||
self.compound_walker.arrow = Arrow(ORIGIN, RIGHT) #, buff = 0)
|
||||
self.compound_walker.digest_mobject_attrs()
|
||||
Animation.__init__(self, self.compound_walker, **kwargs)
|
||||
|
||||
# Perhaps abstract this out into an "Animation updating from original object" class
|
||||
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)
|
||||
cur_x, cur_y = cur_coords = interpolate(self.start_coords, self.end_coords, alpha)
|
||||
self.mobject.walker.move_to(num_plane.coords_to_point(cur_x, cur_y))
|
||||
rev = rev_func(cur_coords)
|
||||
self.mobject.walker.set_color(color_func(rev))
|
||||
self.mobject.arrow.set_color(color_func(rev))
|
||||
self.mobject.arrow.rotate(
|
||||
rev * 2 * np.pi,
|
||||
about_point = ORIGIN #self.mobject.arrow.get_start()
|
||||
)
|
||||
|
||||
|
||||
TL = np.array((self.start_x, self.start_y))
|
||||
TR = TL + (self.walk_width, 0)
|
||||
BR = TR + (0, -self.walk_height)
|
||||
BL = BR + (-self.walk_width, 0)
|
||||
|
||||
walk_coords = [TL, TR, BR, BL]
|
||||
for i in range(len(walk_coords)):
|
||||
start_x, start_y = start_coords = walk_coords[i]
|
||||
start_point = num_plane.coords_to_point(start_x, start_y)
|
||||
end_x, end_y = end_coords = walk_coords[(i + 1) % len(walk_coords)]
|
||||
end_point = num_plane.coords_to_point(end_x, end_y)
|
||||
self.play(
|
||||
WalkerAnimation(
|
||||
start_coords = start_coords,
|
||||
end_coords = end_coords,
|
||||
remover = (i < len(walk_coords) - 1)
|
||||
),
|
||||
ShowCreation(Line(start_point, end_point)))
|
||||
|
||||
|
||||
self.wait()
|
||||
|
||||
#############
|
||||
# Above are mostly general tools; here, we list, in order, finished or near-finished scenes
|
||||
|
||||
@ -679,31 +752,62 @@ class NumberLineScene(Scene):
|
||||
# TODO: Add labels, arrows, specific points
|
||||
self.wait()
|
||||
|
||||
interval_1d = Line(num_line.number_to_point(-1), num_line.number_to_point(1),
|
||||
stroke_color = RED, stroke_width = 10)
|
||||
self.play(ShowCreation(interval_1d))
|
||||
border_color = PURPLE_E
|
||||
inner_color = RED
|
||||
stroke_width = 10
|
||||
|
||||
left_point = num_line.number_to_point(-1)
|
||||
right_point = num_line.number_to_point(1)
|
||||
interval_1d = Line(left_point, right_point,
|
||||
stroke_color = inner_color, stroke_width = stroke_width)
|
||||
left_dot = Dot(left_point, stroke_width = stroke_width, color = border_color)
|
||||
right_dot = Dot(right_point, stroke_width = stroke_width, color = border_color)
|
||||
endpoints_1d = VGroup(left_dot, right_dot)
|
||||
full_1d = VGroup(interval_1d, endpoints_1d)
|
||||
self.play(ShowCreation(full_1d))
|
||||
self.wait()
|
||||
|
||||
num_plane = NumberPlane()
|
||||
|
||||
random_points = [UP + LEFT, 2 * UP, RIGHT, DOWN, DOWN + RIGHT, LEFT]
|
||||
random_points = [UP + LEFT, UP + RIGHT, DOWN + RIGHT, DOWN + LEFT]
|
||||
|
||||
interval_2d = Polygon(
|
||||
border_2d = Polygon(
|
||||
*random_points,
|
||||
stroke_color = RED,
|
||||
stroke_width = 10)
|
||||
# TODO: Turn this into a more complicated, curvy loop?
|
||||
# TODO: Illustrate borders and filled interiors with a particular color
|
||||
# on both 1d and 2d region?
|
||||
stroke_color = border_color,
|
||||
stroke_width = stroke_width)
|
||||
|
||||
filling_2d = Polygon(
|
||||
*random_points,
|
||||
fill_color = inner_color,
|
||||
fill_opacity = 0.8,
|
||||
stroke_width = stroke_width)
|
||||
full_2d = VGroup(filling_2d, border_2d)
|
||||
|
||||
self.play(
|
||||
FadeOut(num_line),
|
||||
FadeIn(num_plane),
|
||||
ReplacementTransform(interval_1d, interval_2d))
|
||||
ReplacementTransform(full_1d, full_2d))
|
||||
|
||||
self.wait()
|
||||
|
||||
# TODO: Split screen illustration of 2d function (before domain coloring)
|
||||
class Initial2dFuncScene(Scene):
|
||||
|
||||
def setup(self):
|
||||
left_camera = Camera(**self.camera_config)
|
||||
right_camera = MappingCamera(
|
||||
mapping_func = point_func_from_complex_func(lambda c : np.exp(c)),
|
||||
**self.camera_config)
|
||||
split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config)
|
||||
self.camera = split_screen_camera
|
||||
|
||||
def construct(self):
|
||||
num_plane = NumberPlane()
|
||||
num_plane.fade()
|
||||
self.add(num_plane)
|
||||
points = [LEFT + DOWN, RIGHT + DOWN, LEFT + UP, RIGHT + UP]
|
||||
for i in range(len(points) - 1):
|
||||
line = Line(points[i], points[i + 1], color = RED)
|
||||
self.play(ShowCreation(line))
|
||||
|
||||
# TODO: Illustrations for introducing domain coloring
|
||||
|
||||
@ -844,13 +948,13 @@ class LoopSplitScene(Scene):
|
||||
|
||||
self.wait()
|
||||
|
||||
# Is there a way to abstract this into a general process to derive a new mapped scene from an old scene?
|
||||
class LoopSplitSceneMapped(LoopSplitScene):
|
||||
|
||||
def setup(self):
|
||||
left_camera = Camera(**self.camera_config)
|
||||
right_camera = MappingCamera(
|
||||
mapping_func = lambda (x, y, z) : complex_to_R3(((complex(x,y) + 3)**1.1) - 3),
|
||||
allow_object_intrusion = False
|
||||
**self.camera_config)
|
||||
split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config)
|
||||
self.camera = split_screen_camera
|
||||
@ -860,11 +964,35 @@ class LoopSplitSceneMapped(LoopSplitScene):
|
||||
class FundThmAlg(EquationSolver2d):
|
||||
CONFIG = {
|
||||
"func" : plane_poly_with_roots((1, 2), (-1, 3), (-1, 3)),
|
||||
"num_iterations" : 1,
|
||||
}
|
||||
|
||||
# TODO: Borsuk-Ulam visuals
|
||||
# Note: May want to do an ordinary square scene, then mapping func it into a circle
|
||||
# class BorsukUlamScene(PiWalker):
|
||||
|
||||
# TODO: "Good enough" property visuals (swinging arrows and odometer, like in odometer scene above)
|
||||
# 3-way scene of "Good enough"-illustrating odometers; to be composed in Premiere
|
||||
left_func = lambda x : x**2 - x + 1
|
||||
diff_func = lambda x : np.cos(1.4 * (x - 0.1) * (np.log(x + 0.1) - 0.3) * TAU)/2.1
|
||||
|
||||
class LeftOdometer(OdometerScene):
|
||||
CONFIG = {
|
||||
"rotate_func" : left_func,
|
||||
"biased_display_start" : 0
|
||||
}
|
||||
|
||||
class RightOdometer(OdometerScene):
|
||||
CONFIG = {
|
||||
"rotate_func" : lambda x : left_func(x) + diff_func(x),
|
||||
"biased_display_start" : 0
|
||||
}
|
||||
|
||||
class DiffOdometer(OdometerScene):
|
||||
CONFIG = {
|
||||
"rotate_func" : diff_func,
|
||||
"dashed_line_angle" : 0.5,
|
||||
"biased_display_start" : 0
|
||||
}
|
||||
|
||||
# TODO: Brouwer's fixed point theorem visuals
|
||||
|
||||
@ -876,16 +1004,18 @@ class FundThmAlg(EquationSolver2d):
|
||||
|
||||
# Minor fiddling with little things in each animation; placements, colors, timing
|
||||
|
||||
# Writing new Pi creature walker scenes off of general template
|
||||
|
||||
# Odometer/swinging arrows stuff
|
||||
|
||||
# Pi creature emotion stuff
|
||||
# Writing new Pi creature walker scenes off of general template
|
||||
|
||||
# Split screen illustration of 2d function (before domain coloring)
|
||||
|
||||
# Generalizing Pi color walker stuff/making bullets on pulsing lines change colors dynamically according to function traced out
|
||||
|
||||
# ----
|
||||
|
||||
# Pi creature emotion stuff
|
||||
|
||||
# BFT visuals
|
||||
|
||||
# Borsuk-Ulam visuals
|
||||
|
Reference in New Issue
Block a user