mirror of
https://github.com/3b1b/manim.git
synced 2025-08-06 14:52:05 +08:00
Merge branch 'master' of github.com:3b1b/manim
This commit is contained in:
@ -77,7 +77,7 @@ class DualScene(Scene):
|
|||||||
ShowCreation(output_object),
|
ShowCreation(output_object),
|
||||||
run_time = run_time
|
run_time = run_time
|
||||||
)
|
)
|
||||||
|
|
||||||
class TestDual(DualScene):
|
class TestDual(DualScene):
|
||||||
def construct(self):
|
def construct(self):
|
||||||
self.force_skipping()
|
self.force_skipping()
|
||||||
@ -87,7 +87,7 @@ class TestDual(DualScene):
|
|||||||
|
|
||||||
class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
|
class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"graph_func" : lambda x : x,
|
"func" : lambda x : x,
|
||||||
"targetX" : 0,
|
"targetX" : 0,
|
||||||
"targetY" : 0,
|
"targetY" : 0,
|
||||||
"initial_lower_x" : 0,
|
"initial_lower_x" : 0,
|
||||||
@ -100,7 +100,7 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
|
|||||||
|
|
||||||
def drawGraph(self):
|
def drawGraph(self):
|
||||||
self.setup_axes()
|
self.setup_axes()
|
||||||
self.graph = self.get_graph(self.graph_func)
|
self.graph = self.get_graph(self.func)
|
||||||
self.add(self.graph)
|
self.add(self.graph)
|
||||||
|
|
||||||
if self.graph_label != None:
|
if self.graph_label != None:
|
||||||
@ -127,9 +127,9 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
|
|||||||
yBraces.rotate(np.pi/2)
|
yBraces.rotate(np.pi/2)
|
||||||
|
|
||||||
lowerX = self.initial_lower_x
|
lowerX = self.initial_lower_x
|
||||||
lowerY = self.graph_func(lowerX)
|
lowerY = self.func(lowerX)
|
||||||
upperX = self.initial_upper_x
|
upperX = self.initial_upper_x
|
||||||
upperY = self.graph_func(upperX)
|
upperY = self.func(upperX)
|
||||||
|
|
||||||
leftBrace.move_to(self.coords_to_point(lowerX, 0))
|
leftBrace.move_to(self.coords_to_point(lowerX, 0))
|
||||||
leftBraceLabel = DecimalNumber(lowerX)
|
leftBraceLabel = DecimalNumber(lowerX)
|
||||||
@ -165,12 +165,12 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
|
|||||||
|
|
||||||
lowerDotPoint = self.input_to_graph_point(lowerX, self.graph)
|
lowerDotPoint = self.input_to_graph_point(lowerX, self.graph)
|
||||||
lowerDotXPoint = self.coords_to_point(lowerX, 0)
|
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)
|
lowerDot = Dot(lowerDotPoint)
|
||||||
upperDotPoint = self.input_to_graph_point(upperX, self.graph)
|
upperDotPoint = self.input_to_graph_point(upperX, self.graph)
|
||||||
upperDot = Dot(upperDotPoint)
|
upperDot = Dot(upperDotPoint)
|
||||||
upperDotXPoint = self.coords_to_point(upperX, 0)
|
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)
|
lowerXLine = Line(lowerDotXPoint, lowerDotPoint, stroke_width = 1, color = YELLOW)
|
||||||
upperXLine = Line(upperDotXPoint, upperDotPoint, 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):
|
def updater(group, alpha):
|
||||||
dot, xBrace, yBrace, xLine, yLine = group
|
dot, xBrace, yBrace, xLine, yLine = group
|
||||||
newX = interpolate(xAtStart, midX, alpha)
|
newX = interpolate(xAtStart, midX, alpha)
|
||||||
newY = self.graph_func(newX)
|
newY = self.func(newX)
|
||||||
graphPoint = self.input_to_graph_point(newX,
|
graphPoint = self.input_to_graph_point(newX,
|
||||||
self.graph)
|
self.graph)
|
||||||
dot.move_to(graphPoint)
|
dot.move_to(graphPoint)
|
||||||
@ -209,7 +209,7 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
|
|||||||
return updater
|
return updater
|
||||||
|
|
||||||
midX = (lowerX + upperX)/float(2)
|
midX = (lowerX + upperX)/float(2)
|
||||||
midY = self.graph_func(midX)
|
midY = self.func(midX)
|
||||||
|
|
||||||
midCoords = self.coords_to_point(midX, midY)
|
midCoords = self.coords_to_point(midX, midY)
|
||||||
midColor = RED
|
midColor = RED
|
||||||
@ -255,20 +255,20 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
|
|||||||
class FirstSqrtScene(EquationSolver1d):
|
class FirstSqrtScene(EquationSolver1d):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"x_min" : 0,
|
"x_min" : 0,
|
||||||
"x_max" : 2,
|
"x_max" : 2.5,
|
||||||
"y_min" : 0,
|
"y_min" : 0,
|
||||||
"y_max" : 4,
|
"y_max" : 2.5**2,
|
||||||
"graph_origin" : 2*DOWN + 5 * LEFT,
|
"graph_origin" : 2*DOWN + 5 * LEFT,
|
||||||
"x_axis_width" : 12,
|
"x_axis_width" : 12,
|
||||||
"zoom_factor" : 3,
|
"zoom_factor" : 3,
|
||||||
"zoomed_canvas_center" : 2.25 * UP + 1.75 * LEFT,
|
"zoomed_canvas_center" : 2.25 * UP + 1.75 * LEFT,
|
||||||
"graph_func" : lambda x : x**2,
|
"func" : lambda x : x**2,
|
||||||
"targetX" : np.sqrt(2),
|
"targetX" : np.sqrt(2),
|
||||||
"targetY" : 2,
|
"targetY" : 2,
|
||||||
"initial_lower_x" : 1,
|
"initial_lower_x" : 1,
|
||||||
"initial_upper_x" : 2,
|
"initial_upper_x" : 2,
|
||||||
"num_iterations" : 2,
|
"num_iterations" : 10,
|
||||||
"iteration_at_which_to_start_zoom" : 1,
|
"iteration_at_which_to_start_zoom" : 3,
|
||||||
"graph_label" : "y = x^2",
|
"graph_label" : "y = x^2",
|
||||||
"show_target_line" : True,
|
"show_target_line" : True,
|
||||||
}
|
}
|
||||||
@ -285,7 +285,7 @@ class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene):
|
|||||||
self.drawGraph()
|
self.drawGraph()
|
||||||
newOrigin = self.coords_to_point(0, shiftVal)
|
newOrigin = self.coords_to_point(0, shiftVal)
|
||||||
self.transition_to_alt_config(
|
self.transition_to_alt_config(
|
||||||
graph_func = lambda x : x**2 - shiftVal,
|
func = lambda x : x**2 - shiftVal,
|
||||||
targetY = 0,
|
targetY = 0,
|
||||||
graph_label = "y = x^2 - " + str(shiftVal),
|
graph_label = "y = x^2 - " + str(shiftVal),
|
||||||
y_min = self.y_min - shiftVal,
|
y_min = self.y_min - shiftVal,
|
||||||
@ -294,6 +294,8 @@ class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene):
|
|||||||
graph_origin = newOrigin)
|
graph_origin = newOrigin)
|
||||||
self.solveEquation()
|
self.solveEquation()
|
||||||
|
|
||||||
|
# TODO: Perhaps have pulses fade out and in at ends of line, instead of jarringly
|
||||||
|
# popping out and in?
|
||||||
class LinePulser(ContinualAnimation):
|
class LinePulser(ContinualAnimation):
|
||||||
def __init__(self, line, bullet_template, num_bullets, pulse_time, **kwargs):
|
def __init__(self, line, bullet_template, num_bullets, pulse_time, **kwargs):
|
||||||
self.line = line
|
self.line = line
|
||||||
@ -312,15 +314,6 @@ class LinePulser(ContinualAnimation):
|
|||||||
self.bullets[i].shift((0, 0, 1)) # Temporary hack for z-buffer fidgetiness
|
self.bullets[i].shift((0, 0, 1)) # Temporary hack for z-buffer fidgetiness
|
||||||
|
|
||||||
class LoopSplitScene(Scene):
|
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):
|
def PulsedLine(self, start, end, bullet_template, num_bullets = 4, pulse_time = 1, **kwargs):
|
||||||
line = Line(start, end, **kwargs)
|
line = Line(start, end, **kwargs)
|
||||||
@ -356,19 +349,6 @@ class LoopSplitScene(Scene):
|
|||||||
self.add(*line)
|
self.add(*line)
|
||||||
self.wait()
|
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
|
# Splits in middle
|
||||||
split_line = DashedLine(interpolate(tl, tr, 0.5), interpolate(bl, br, 0.5))
|
split_line = DashedLine(interpolate(tl, tr, 0.5), interpolate(bl, br, 0.5))
|
||||||
self.play(ShowCreation(split_line))
|
self.play(ShowCreation(split_line))
|
||||||
@ -420,9 +400,139 @@ class LoopSplitScene(Scene):
|
|||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
self.play(FadeOut(highlight_circle), FadeOut(mid_lines))
|
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()
|
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):
|
class EquationSolver2d(ZoomedScene):
|
||||||
#TODO
|
#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
|
||||||
|
}
|
69
camera.py
69
camera.py
@ -409,4 +409,73 @@ class MappingCamera(Camera):
|
|||||||
mobject.insert_n_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, **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)))
|
Reference in New Issue
Block a user