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

This commit is contained in:
Grant Sanderson
2018-01-16 18:42:07 -08:00
2 changed files with 218 additions and 39 deletions

View File

@ -77,7 +77,7 @@ class DualScene(Scene):
ShowCreation(output_object),
run_time = run_time
)
class TestDual(DualScene):
def construct(self):
self.force_skipping()
@ -87,7 +87,7 @@ class TestDual(DualScene):
class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
CONFIG = {
"graph_func" : lambda x : x,
"func" : lambda x : x,
"targetX" : 0,
"targetY" : 0,
"initial_lower_x" : 0,
@ -100,7 +100,7 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
def drawGraph(self):
self.setup_axes()
self.graph = self.get_graph(self.graph_func)
self.graph = self.get_graph(self.func)
self.add(self.graph)
if self.graph_label != None:
@ -127,9 +127,9 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
yBraces.rotate(np.pi/2)
lowerX = self.initial_lower_x
lowerY = self.graph_func(lowerX)
lowerY = self.func(lowerX)
upperX = self.initial_upper_x
upperY = self.graph_func(upperX)
upperY = self.func(upperX)
leftBrace.move_to(self.coords_to_point(lowerX, 0))
leftBraceLabel = DecimalNumber(lowerX)
@ -165,12 +165,12 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
lowerDotPoint = self.input_to_graph_point(lowerX, self.graph)
lowerDotXPoint = self.coords_to_point(lowerX, 0)
lowerDotYPoint = self.coords_to_point(0, self.graph_func(lowerX))
lowerDotYPoint = self.coords_to_point(0, self.func(lowerX))
lowerDot = Dot(lowerDotPoint)
upperDotPoint = self.input_to_graph_point(upperX, self.graph)
upperDot = Dot(upperDotPoint)
upperDotXPoint = self.coords_to_point(upperX, 0)
upperDotYPoint = self.coords_to_point(0, self.graph_func(upperX))
upperDotYPoint = self.coords_to_point(0, self.func(upperX))
lowerXLine = Line(lowerDotXPoint, lowerDotPoint, stroke_width = 1, color = YELLOW)
upperXLine = Line(upperDotXPoint, upperDotPoint, stroke_width = 1, color = YELLOW)
@ -195,7 +195,7 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
def updater(group, alpha):
dot, xBrace, yBrace, xLine, yLine = group
newX = interpolate(xAtStart, midX, alpha)
newY = self.graph_func(newX)
newY = self.func(newX)
graphPoint = self.input_to_graph_point(newX,
self.graph)
dot.move_to(graphPoint)
@ -209,7 +209,7 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
return updater
midX = (lowerX + upperX)/float(2)
midY = self.graph_func(midX)
midY = self.func(midX)
midCoords = self.coords_to_point(midX, midY)
midColor = RED
@ -255,20 +255,20 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
class FirstSqrtScene(EquationSolver1d):
CONFIG = {
"x_min" : 0,
"x_max" : 2,
"x_max" : 2.5,
"y_min" : 0,
"y_max" : 4,
"y_max" : 2.5**2,
"graph_origin" : 2*DOWN + 5 * LEFT,
"x_axis_width" : 12,
"zoom_factor" : 3,
"zoomed_canvas_center" : 2.25 * UP + 1.75 * LEFT,
"graph_func" : lambda x : x**2,
"func" : lambda x : x**2,
"targetX" : np.sqrt(2),
"targetY" : 2,
"initial_lower_x" : 1,
"initial_upper_x" : 2,
"num_iterations" : 2,
"iteration_at_which_to_start_zoom" : 1,
"num_iterations" : 10,
"iteration_at_which_to_start_zoom" : 3,
"graph_label" : "y = x^2",
"show_target_line" : True,
}
@ -285,7 +285,7 @@ class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene):
self.drawGraph()
newOrigin = self.coords_to_point(0, shiftVal)
self.transition_to_alt_config(
graph_func = lambda x : x**2 - shiftVal,
func = lambda x : x**2 - shiftVal,
targetY = 0,
graph_label = "y = x^2 - " + str(shiftVal),
y_min = self.y_min - shiftVal,
@ -294,6 +294,8 @@ class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene):
graph_origin = newOrigin)
self.solveEquation()
# TODO: Perhaps have pulses fade out and in at ends of line, instead of jarringly
# popping out and in?
class LinePulser(ContinualAnimation):
def __init__(self, line, bullet_template, num_bullets, pulse_time, **kwargs):
self.line = line
@ -312,15 +314,6 @@ class LinePulser(ContinualAnimation):
self.bullets[i].shift((0, 0, 1)) # Temporary hack for z-buffer fidgetiness
class LoopSplitScene(Scene):
# def LinePulser(self, line, bullet_template, num_bullets):
# start, end = line.get_start(), line.get_end()
# bullets = [
# bullet_template.copy().move_to(interpolate(start, end, i/float(num_bullets)))
# for i in range(num_bullets)
# ]
# bullets_group = VGroup(*bullets)
# return ApplyMethod(bullets_group.shift, (end - start)/float(num_bullets),
# rate_func = None, run_time = 0.5)
def PulsedLine(self, start, end, bullet_template, num_bullets = 4, pulse_time = 1, **kwargs):
line = Line(start, end, **kwargs)
@ -356,19 +349,6 @@ class LoopSplitScene(Scene):
self.add(*line)
self.wait()
# TODO: Make the following a continual animation, and on all split loops do the same
# bullet = TexMobject("*", fill_color = RED)
# bullet.move_to(tl)
# self.add(bullet)
# list_of_args = reduce(op.add,
# [
# [ApplyMethod, bullet.move_to, point, {"rate_func" : None}] for
# point in [tr, br, bl, tl]
# ]
# )
# succ_anim = Succession(*list_of_args)
# self.add(CycleAnimation(succ_anim))
# Splits in middle
split_line = DashedLine(interpolate(tl, tr, 0.5), interpolate(bl, br, 0.5))
self.play(ShowCreation(split_line))
@ -420,9 +400,139 @@ class LoopSplitScene(Scene):
self.wait()
self.play(FadeOut(highlight_circle), FadeOut(mid_lines))
# Because FadeOut didn't remove the continual pulsers, we remove them manually
self.remove(mid_line_left[1], mid_line_right[1])
# Brings loop back together; keep in sync with motions which bring loop apart above
self.play(
ApplyMethod(left_open_loop.shift, 2 * RIGHT),
ApplyMethod(right_open_loop.shift, 2 * LEFT)
)
self.wait()
class NumberLineScene(Scene):
def construct(self):
num_line = NumberLine()
self.add(num_line)
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))
self.wait()
num_plane = NumberPlane()
random_points = [UP + LEFT, 2 * UP, RIGHT, DOWN, DOWN + RIGHT, LEFT]
interval_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?
self.play(
FadeOut(num_line),
FadeIn(num_plane),
ReplacementTransform(interval_1d, interval_2d))
self.wait()
class ArrowCircleTest(Scene):
def construct(self):
circle_radius = 3
circle = Circle(radius = circle_radius)
self.add(circle)
base_arrow = Arrow(circle_radius * 0.7 * RIGHT, circle_radius * 1.3 * RIGHT)
def rev_rotate(x, revs):
return x.rotate(revs * 2 * np.pi)
num_arrows = 8 * 3
arrows = [rev_rotate(base_arrow.copy(), (np.true_divide(i, num_arrows))) for i in range(num_arrows)]
arrows_vgroup = VGroup(*arrows)
self.play(ShowCreation(arrows_vgroup), run_time = 2.5, rate_func = None)
self.wait()
class FuncRotater(Animation):
CONFIG = {
"rotate_func" : lambda x : x # Func from alpha to revolutions
}
# Perhaps abstract this out into an "Animation from base 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)
self.mobject.rotate(
self.rotate_func(alpha) * 2 * np.pi,
)
# Will want to have arrow colors change to match direction as well
class TestRotater(Scene):
def construct(self):
test_line = Line(ORIGIN, RIGHT)
self.play(FuncRotater(
test_line,
rotate_func = lambda x : x % 0.25,
run_time = 10))
class OdometerScene(Scene):
CONFIG = {
"rotate_func" : lambda x : np.sin(x * 2 * np.pi),
"run_time" : 5
}
def construct(self):
base_arrow = Arrow(ORIGIN, RIGHT)
circle = Circle(center = ORIGIN, radius = 1.3)
self.add(circle)
num_display = DecimalNumber(0)
num_display.move_to(2 * DOWN)
self.play(
FuncRotater(base_arrow, rotate_func = self.rotate_func),
ChangingDecimal(num_display, self.rotate_func),
run_time = self.run_time,
rate_func = None)
def Vect2dToRevAngle(x, y):
return np.true_divide(np.arctan2(y, x), 2 * np.pi)
# Returns the value with the same fractional component as x, closest to m
def resit_near(x, m):
frac_diff = (x - m) % 1
if frac_diff > 0.5:
frac_diff -= 1
return m + frac_diff
# Perhaps use modulus of (uniform) continuity instead of num_check_points, calculating
# latter as needed from former?
def winding_func(func, start, end, num_check_points):
check_points = [None for i in range(num_check_points)]
check_points[0] = func(0)
step_size = np.true_divide(end - start, num_check_points)
for i in range(num_check_points - 1):
check_points[i + 1] = \
resit_near(
func(start + (i + 1) * step_size),
check_points[i])
return lambda x : resit_near(func(x), check_points[int((x - start)/step_size)])
class EquationSolver2d(ZoomedScene):
#TODO
CONFIG = {}
CONFIG = {
"func" : lambda p : p,
"target_input" : (0, 0),
"target_output" : (0, 0),
"initial_top_left_point" : (0, 0),
"initial_guess_dimensions" : (0, 0),
"num_iterations" : 10,
"iteration_at_which_to_start_zoom" : None
}

View File

@ -409,4 +409,73 @@ class MappingCamera(Camera):
mobject.insert_n_anchor_points(self.min_anchor_points)
Camera.capture_mobjects(self, mobject_copies, **kwargs)
# TODO: Put this in different utility/helpers file? Convenient for me (Sridhar); I like it.
class DictAsObject(object):
def __init__(self, dict):
self.__dict__ = dict
# Note: This allows layering of multiple cameras onto the same portion of the pixel array,
# the later cameras overwriting the former
#
# TODO: Add optional separator borders between cameras (or perhaps peel this off into a
# CameraPlusOverlay class)
class MultiCamera(Camera):
def __init__(self, *cameras_with_start_positions, **kwargs):
self.shifted_cameras = [
DictAsObject(
{
"camera" : camera_with_start_positions[0],
"start_x" : camera_with_start_positions[1][1],
"start_y" : camera_with_start_positions[1][0],
"end_x" : camera_with_start_positions[1][1] + camera_with_start_positions[0].pixel_shape[1],
"end_y" : camera_with_start_positions[1][0] + camera_with_start_positions[0].pixel_shape[0],
})
for camera_with_start_positions in cameras_with_start_positions
]
Camera.__init__(self, **kwargs)
def capture_mobjects(self, mobjects, **kwargs):
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.capture_mobjects(mobjects, **kwargs)
self.pixel_array[
shifted_camera.start_y:shifted_camera.end_y,
shifted_camera.start_x:shifted_camera.end_x] \
= shifted_camera.camera.pixel_array
def set_background(self, pixel_array):
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.set_background(
pixel_array[
shifted_camera.start_y:shifted_camera.end_y,
shifted_camera.start_x:shifted_camera.end_x])
def set_pixel_array(self, pixel_array):
Camera.set_pixel_array(self, pixel_array)
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.set_pixel_array(
pixel_array[
shifted_camera.start_y:shifted_camera.end_y,
shifted_camera.start_x:shifted_camera.end_x])
def init_background(self):
Camera.init_background(self)
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.init_background()
# A MultiCamera which, when called with two full-size cameras, initializes itself
# as a splitscreen, also taking care to resize each individual camera within it
class SplitScreenCamera(MultiCamera):
def __init__(self, left_camera, right_camera, **kwargs):
digest_config(self, kwargs)
self.left_camera = left_camera
self.right_camera = right_camera
half_width = self.pixel_shape[1] / 2
for camera in [self.left_camera, self.right_camera]:
camera.pixel_shape = (self.pixel_shape[0], half_width) # TODO: Round up on one if width is odd
camera.init_background()
camera.resize_space_shape()
camera.reset()
MultiCamera.__init__(self, (left_camera, (0, 0)), (right_camera, (0, half_width)))