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

This commit is contained in:
Grant Sanderson 2018-02-15 18:36:41 -08:00
commit 0fdfb55258
11 changed files with 732 additions and 249 deletions

View file

@ -32,189 +32,32 @@ from topics.graph_scene import *
# TODO/WARNING: There's a lot of refactoring and cleanup to be done in this code, # TODO/WARNING: There's a lot of refactoring and cleanup to be done in this code,
# (and it will be done, but first I'll figure out what I'm doing with all this...) # (and it will be done, but first I'll figure out what I'm doing with all this...)
# -SR # -SR
class EquationSolver1d(GraphScene, ZoomedScene):
CONFIG = {
"func" : lambda x : x,
"targetX" : 0,
"targetY" : 0,
"initial_lower_x" : 0,
"initial_upper_x" : 10,
"num_iterations" : 10,
"iteration_at_which_to_start_zoom" : None,
"graph_label" : None,
"show_target_line" : True
}
def drawGraph(self):
self.setup_axes()
self.graph = self.get_graph(self.func)
self.add(self.graph)
if self.graph_label != None:
self.add(self.get_graph_label(self.graph, self.graph_label,
x_val = 4, direction = RIGHT))
if self.show_target_line:
target_line_object = DashedLine(
self.coords_to_point(self.x_min, self.targetY),
self.coords_to_point(self.x_max, self.targetY),
dashed_segment_length = 0.1)
self.add(target_line_object)
target_line_label = TexMobject("y = " + str(self.targetY))
target_line_label.next_to(target_line_object.get_left(), UP + RIGHT)
self.add(target_line_label)
def solveEquation(self):
leftBrace = TexMobject("[")
rightBrace = TexMobject("]")
xBraces = Group(leftBrace, rightBrace)
xBraces.stretch(2, 0)
downBrace = TexMobject("[")
upBrace = TexMobject("]")
yBraces = Group(downBrace, upBrace)
yBraces.stretch(2, 0)
yBraces.rotate(TAU/4)
lowerX = self.initial_lower_x
lowerY = self.func(lowerX)
upperX = self.initial_upper_x
upperY = self.func(upperX)
leftBrace.move_to(self.coords_to_point(lowerX, 0), aligned_edge = LEFT)
leftBraceLabel = DecimalNumber(lowerX)
leftBraceLabel.next_to(leftBrace, DOWN + LEFT, buff = SMALL_BUFF)
leftBraceLabelAnimation = ContinualChangingDecimal(leftBraceLabel,
lambda alpha : self.point_to_coords(leftBrace.get_center())[0],
tracked_mobject = leftBrace)
self.add(leftBraceLabelAnimation)
rightBrace.move_to(self.coords_to_point(upperX, 0), aligned_edge = RIGHT)
rightBraceLabel = DecimalNumber(upperX)
rightBraceLabel.next_to(rightBrace, DOWN + RIGHT, buff = SMALL_BUFF)
rightBraceLabelAnimation = ContinualChangingDecimal(rightBraceLabel,
lambda alpha : self.point_to_coords(rightBrace.get_center())[0],
tracked_mobject = rightBrace)
self.add(rightBraceLabelAnimation)
downBrace.move_to(self.coords_to_point(0, lowerY), aligned_edge = DOWN)
downBraceLabel = DecimalNumber(lowerY)
downBraceLabel.next_to(downBrace, LEFT + DOWN, buff = SMALL_BUFF)
downBraceLabelAnimation = ContinualChangingDecimal(downBraceLabel,
lambda alpha : self.point_to_coords(downBrace.get_center())[1],
tracked_mobject = downBrace)
self.add(downBraceLabelAnimation)
upBrace.move_to(self.coords_to_point(0, upperY), aligned_edge = UP)
upBraceLabel = DecimalNumber(upperY)
upBraceLabel.next_to(upBrace, LEFT + UP, buff = SMALL_BUFF)
upBraceLabelAnimation = ContinualChangingDecimal(upBraceLabel,
lambda alpha : self.point_to_coords(upBrace.get_center())[1],
tracked_mobject = upBrace)
self.add(upBraceLabelAnimation)
lowerDotPoint = self.input_to_graph_point(lowerX, self.graph)
lowerDotXPoint = self.coords_to_point(lowerX, 0)
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.func(upperX))
lowerXLine = Line(lowerDotXPoint, lowerDotPoint, stroke_width = 1, color = YELLOW)
upperXLine = Line(upperDotXPoint, upperDotPoint, stroke_width = 1, color = YELLOW)
lowerYLine = Line(lowerDotYPoint, lowerDotPoint, stroke_width = 1, color = YELLOW)
upperYLine = Line(upperDotYPoint, upperDotPoint, stroke_width = 1, color = YELLOW)
self.add(lowerXLine, upperXLine, lowerYLine, upperYLine)
self.add(xBraces, yBraces, lowerDot, upperDot)
for i in range(self.num_iterations):
if i == self.iteration_at_which_to_start_zoom:
self.activate_zooming()
self.little_rectangle.move_to(
self.coords_to_point(self.targetX, self.targetY))
inverseZoomFactor = 1/float(self.zoom_factor)
self.play(
lowerDot.scale_in_place, inverseZoomFactor,
upperDot.scale_in_place, inverseZoomFactor)
def makeUpdater(xAtStart):
def updater(group, alpha):
dot, xBrace, yBrace, xLine, yLine = group
newX = interpolate(xAtStart, midX, alpha)
newY = self.func(newX)
graphPoint = self.input_to_graph_point(newX,
self.graph)
dot.move_to(graphPoint)
xAxisPoint = self.coords_to_point(newX, 0)
xBrace.move_to(xAxisPoint)
yAxisPoint = self.coords_to_point(0, newY)
yBrace.move_to(yAxisPoint)
xLine.put_start_and_end_on(xAxisPoint, graphPoint)
yLine.put_start_and_end_on(yAxisPoint, graphPoint)
return group
return updater
midX = (lowerX + upperX)/float(2)
midY = self.func(midX)
midCoords = self.coords_to_point(midX, midY)
midColor = RED
midXPoint = Dot(self.coords_to_point(midX, 0), color = midColor)
self.play(
ReplacementTransform(leftBrace.copy(), midXPoint),
ReplacementTransform(rightBrace.copy(), midXPoint))
midXLine = Line(self.coords_to_point(midX, 0), midCoords, color = midColor)
self.play(ShowCreation(midXLine))
midDot = Dot(midCoords, color = midColor)
if(self.iteration_at_which_to_start_zoom != None and
i >= self.iteration_at_which_to_start_zoom):
midDot.scale_in_place(inverseZoomFactor)
self.add(midDot)
midYLine = Line(midCoords, self.coords_to_point(0, midY), color = midColor)
self.play(ShowCreation(midYLine))
if midY < self.targetY:
movingGroup = Group(lowerDot,
leftBrace, downBrace,
lowerXLine, lowerYLine)
self.play(
UpdateFromAlphaFunc(movingGroup, makeUpdater(lowerX)))
lowerX = midX
lowerY = midY
else:
movingGroup = Group(upperDot,
rightBrace, upBrace,
upperXLine, upperYLine)
self.play(
UpdateFromAlphaFunc(movingGroup, makeUpdater(upperX)))
upperX = midX
upperY = midY
self.remove(midXLine, midDot, midYLine)
self.wait()
def construct(self):
self.drawGraph()
self.solveEquation()
colorslist = map(color_to_rgba, ["#FF0000", "#FFFF00", "#00FF00", "#0000FF"])
def rev_to_rgba(alpha): def rev_to_rgba(alpha):
alpha = alpha % 1 alpha = (0.5 - alpha) % 1 # For convenience, to go CW from red on left instead of CCW from right
colors = colorslist # 0 is red, 1/6 is yellow, 1/3 is green, 2/3 is blue
num_colors = len(colors) hue_list = [0, 0.5/6.0, 1/6.0, 1.1/6.0, 2/6.0, 3/6.0, 4/6.0, 5/6.0]
beta = (alpha % (1.0/num_colors)) * num_colors num_hues = len(hue_list)
start_index = int(np.floor(num_colors * alpha)) % num_colors start_index = int(np.floor(num_hues * alpha)) % num_hues
end_index = (start_index + 1) % num_colors end_index = (start_index + 1) % num_hues
beta = (alpha % (1.0/num_hues)) * num_hues
return interpolate(colors[start_index], colors[end_index], beta) start_hue = hue_list[start_index]
end_hue = hue_list[end_index]
if end_hue < start_hue:
end_hue = end_hue + 1
hue = interpolate(start_hue, end_hue, beta)
return color_to_rgba(Color(hue = hue, saturation = 1, luminance = 0.5))
# alpha = alpha % 1
# colors = colorslist
# num_colors = len(colors)
# beta = (alpha % (1.0/num_colors)) * num_colors
# start_index = int(np.floor(num_colors * alpha)) % num_colors
# end_index = (start_index + 1) % num_colors
# return interpolate(colors[start_index], colors[end_index], beta)
def rev_to_color(alpha): def rev_to_color(alpha):
return rgba_to_color(rev_to_rgba(alpha)) return rgba_to_color(rev_to_rgba(alpha))
@ -235,6 +78,255 @@ def point_to_rgba(point):
rescaled_size = np.sqrt(base_size/(base_size + 1)) rescaled_size = np.sqrt(base_size/(base_size + 1))
return rgba * rescaled_size return rgba * rescaled_size
positive_color = rev_to_color(0)
negative_color = rev_to_color(0.5)
neutral_color = rev_to_color(0.25)
class EquationSolver1d(GraphScene, ZoomedScene):
CONFIG = {
"camera_config" :
{
"use_z_coordinate_for_display_order": True,
},
"func" : lambda x : x,
"targetX" : 0,
"targetY" : 0,
"initial_lower_x" : 0,
"initial_upper_x" : 10,
"num_iterations" : 10,
"iteration_at_which_to_start_zoom" : None,
"graph_label" : None,
"show_target_line" : True
}
def drawGraph(self):
self.setup_axes()
self.graph = self.get_graph(self.func)
self.add(self.graph)
if self.graph_label != None:
curve_label = self.get_graph_label(self.graph, self.graph_label,
x_val = 4, direction = LEFT)
curve_label.shift(LEFT)
self.add(curve_label)
if self.show_target_line:
target_line_object = DashedLine(
self.coords_to_point(self.x_min, self.targetY),
self.coords_to_point(self.x_max, self.targetY),
dashed_segment_length = 0.1)
self.add(target_line_object)
target_line_label = TexMobject("y = " + str(self.targetY))
target_line_label.next_to(target_line_object.get_left(), UP + RIGHT)
self.add(target_line_label)
self.wait() # Give us time to appreciate the graph
self.play(FadeOut(target_line_label)) # Reduce clutter
print "For reference, graphOrigin: ", self.coords_to_point(0, 0)
print "targetYPoint: ", self.coords_to_point(0, self.targetY)
# This is a mess right now (first major animation coded),
# but it works; can be refactored later or never
def solveEquation(self):
leftBrace = TexMobject("|") # Not using [ and ] because they end up crossing over
leftBrace.set_color(negative_color)
rightBrace = TexMobject("|")
rightBrace.set_color(positive_color)
xBraces = Group(leftBrace, rightBrace)
xBraces.stretch(2, 0)
downBrace = TexMobject("|")
downBrace.set_color(negative_color)
upBrace = TexMobject("|")
upBrace.set_color(positive_color)
yBraces = Group(downBrace, upBrace)
yBraces.stretch(2, 0)
yBraces.rotate(TAU/4)
lowerX = self.initial_lower_x
lowerY = self.func(lowerX)
upperX = self.initial_upper_x
upperY = self.func(upperX)
leftBrace.move_to(self.coords_to_point(lowerX, 0)) #, aligned_edge = RIGHT)
leftBraceLabel = DecimalNumber(lowerX)
leftBraceLabel.next_to(leftBrace, DOWN + LEFT, buff = SMALL_BUFF)
leftBraceLabelAnimation = ContinualChangingDecimal(leftBraceLabel,
lambda alpha : self.point_to_coords(leftBrace.get_center())[0],
tracked_mobject = leftBrace)
self.add(leftBraceLabelAnimation)
rightBrace.move_to(self.coords_to_point(upperX, 0)) #, aligned_edge = LEFT)
rightBraceLabel = DecimalNumber(upperX)
rightBraceLabel.next_to(rightBrace, DOWN + RIGHT, buff = SMALL_BUFF)
rightBraceLabelAnimation = ContinualChangingDecimal(rightBraceLabel,
lambda alpha : self.point_to_coords(rightBrace.get_center())[0],
tracked_mobject = rightBrace)
self.add(rightBraceLabelAnimation)
downBrace.move_to(self.coords_to_point(0, lowerY)) #, aligned_edge = UP)
downBraceLabel = DecimalNumber(lowerY)
downBraceLabel.next_to(downBrace, LEFT + DOWN, buff = SMALL_BUFF)
downBraceLabelAnimation = ContinualChangingDecimal(downBraceLabel,
lambda alpha : self.point_to_coords(downBrace.get_center())[1],
tracked_mobject = downBrace)
self.add(downBraceLabelAnimation)
upBrace.move_to(self.coords_to_point(0, upperY)) #, aligned_edge = DOWN)
upBraceLabel = DecimalNumber(upperY)
upBraceLabel.next_to(upBrace, LEFT + UP, buff = SMALL_BUFF)
upBraceLabelAnimation = ContinualChangingDecimal(upBraceLabel,
lambda alpha : self.point_to_coords(upBrace.get_center())[1],
tracked_mobject = upBrace)
self.add(upBraceLabelAnimation)
lowerDotPoint = self.input_to_graph_point(lowerX, self.graph)
lowerDotXPoint = self.coords_to_point(lowerX, 0)
lowerDotYPoint = self.coords_to_point(0, self.func(lowerX))
lowerDot = Dot(lowerDotPoint + OUT, color = negative_color)
upperDotPoint = self.input_to_graph_point(upperX, self.graph)
upperDot = Dot(upperDotPoint + OUT, color = positive_color)
upperDotXPoint = self.coords_to_point(upperX, 0)
upperDotYPoint = self.coords_to_point(0, self.func(upperX))
lowerXLine = Line(lowerDotXPoint, lowerDotPoint, color = negative_color)
upperXLine = Line(upperDotXPoint, upperDotPoint, color = positive_color)
lowerYLine = Line(lowerDotYPoint, lowerDotPoint, color = negative_color)
upperYLine = Line(upperDotYPoint, upperDotPoint, color = positive_color)
self.add(lowerXLine, upperXLine, lowerYLine, upperYLine)
self.add(xBraces, yBraces, lowerDot, upperDot)
x_guess_line = Line(lowerDotXPoint, upperDotXPoint, color = neutral_color)
self.add(x_guess_line)
lowerGroup = Group(
lowerDot,
leftBrace, downBrace,
lowerXLine, lowerYLine,
x_guess_line
)
upperGroup = Group(
upperDot,
rightBrace, upBrace,
upperXLine, upperYLine,
x_guess_line
)
initialLowerXDot = Dot(lowerDotXPoint, color = negative_color)
initialUpperXDot = Dot(upperDotXPoint, color = positive_color)
initialLowerYDot = Dot(lowerDotYPoint, color = negative_color)
initialUpperYDot = Dot(upperDotYPoint, color = positive_color)
self.add(initialLowerXDot, initialUpperXDot, initialLowerYDot, initialUpperYDot)
for i in range(self.num_iterations):
if i == self.iteration_at_which_to_start_zoom:
self.activate_zooming()
self.little_rectangle.move_to(
self.coords_to_point(self.targetX, self.targetY))
inverseZoomFactor = 1/float(self.zoom_factor)
self.play(
lowerDot.scale_in_place, inverseZoomFactor,
upperDot.scale_in_place, inverseZoomFactor)
def makeUpdater(xAtStart, fixed_guess_x):
def updater(group, alpha):
dot, xBrace, yBrace, xLine, yLine, guess_line = group
newX = interpolate(xAtStart, midX, alpha)
newY = self.func(newX)
graphPoint = self.input_to_graph_point(newX,
self.graph)
dot.move_to(graphPoint)
xAxisPoint = self.coords_to_point(newX, 0)
xBrace.move_to(xAxisPoint)
yAxisPoint = self.coords_to_point(0, newY)
yBrace.move_to(yAxisPoint)
xLine.put_start_and_end_on(xAxisPoint, graphPoint)
yLine.put_start_and_end_on(yAxisPoint, graphPoint)
fixed_guess_point = self.coords_to_point(fixed_guess_x, 0)
guess_line.put_start_and_end_on(xAxisPoint, fixed_guess_point)
return group
return updater
midX = (lowerX + upperX)/float(2)
midY = self.func(midX)
in_negative_branch = midY < self.targetY
sign_color = negative_color if in_negative_branch else positive_color
midCoords = self.coords_to_point(midX, midY)
midColor = neutral_color
# Hm... even the z buffer isn't helping keep this above x_guess_line
midXPoint = Dot(self.coords_to_point(midX, 0) + OUT, color = midColor)
x_guess_label_caption = TextMobject("New guess: x = ", fill_color = midColor)
x_guess_label_num = DecimalNumber(midX, fill_color = midColor)
x_guess_label_num.move_to(0.9 * SPACE_HEIGHT * DOWN)
x_guess_label_caption.next_to(x_guess_label_num, LEFT)
x_guess_label = Group(x_guess_label_caption, x_guess_label_num)
y_guess_label_caption = TextMobject(", y = ", fill_color = midColor)
y_guess_label_num = DecimalNumber(midY, fill_color = sign_color)
y_guess_label_caption.next_to(x_guess_label_num, RIGHT)
y_guess_label_num.next_to(y_guess_label_caption, RIGHT)
y_guess_label = Group(y_guess_label_caption, y_guess_label_num)
guess_labels = Group(x_guess_label, y_guess_label)
self.play(
ReplacementTransform(leftBrace.copy(), midXPoint),
ReplacementTransform(rightBrace.copy(), midXPoint),
FadeIn(x_guess_label))
midXLine = DashedLine(self.coords_to_point(midX, 0), midCoords, color = midColor)
self.play(ShowCreation(midXLine))
midDot = Dot(midCoords, color = sign_color)
if(self.iteration_at_which_to_start_zoom != None and
i >= self.iteration_at_which_to_start_zoom):
midDot.scale_in_place(inverseZoomFactor)
self.add(midDot)
midYLine = DashedLine(midCoords, self.coords_to_point(0, midY), color = sign_color)
self.play(
ShowCreation(midYLine),
FadeIn(y_guess_label),
ApplyMethod(midXPoint.set_color, sign_color),
ApplyMethod(midXLine.set_color, sign_color))
midYPoint = Dot(self.coords_to_point(0, midY), color = sign_color)
self.add(midYPoint)
if in_negative_branch:
self.play(
UpdateFromAlphaFunc(lowerGroup,
makeUpdater(lowerX,
fixed_guess_x = upperX
)
),
FadeOut(guess_labels),
)
lowerX = midX
lowerY = midY
else:
self.play(
UpdateFromAlphaFunc(upperGroup,
makeUpdater(upperX,
fixed_guess_x = lowerX
)
),
FadeOut(guess_labels),
)
upperX = midX
upperY = midY
#mid_group = Group(midXLine, midDot, midYLine) Removing groups doesn't flatten as expected?
self.remove(midXLine, midDot, midYLine)
self.wait()
def construct(self):
self.drawGraph()
self.solveEquation()
# Returns the value with the same fractional component as x, closest to m # Returns the value with the same fractional component as x, closest to m
def resit_near(x, m): def resit_near(x, m):
frac_diff = (x - m) % 1 frac_diff = (x - m) % 1
@ -319,7 +411,7 @@ class RectangleData():
return tuple([mid(x, y) for (x, y) in sides]) return tuple([mid(x, y) for (x, y) in sides])
def complex_to_pair(c): def complex_to_pair(c):
return (c.real, c.imag) return np.array((c.real, c.imag))
def plane_poly_with_roots(*points): def plane_poly_with_roots(*points):
def f((x, y)): def f((x, y)):
@ -441,26 +533,63 @@ class ColorMappedByFuncScene(Scene):
def setup(self): def setup(self):
if self.show_output: if self.show_output:
self.pos_func = self.func self.input_to_pos_func = self.func
self.pos_to_color_func = lambda p : p
else: else:
self.pos_func = lambda p : p self.input_to_pos_func = lambda p : p
self.pos_to_color_func = self.func
# func_hash hashes the function at some random points
func_hash_points = [(-0.93, 1), (1, -2.7), (20, 4)]
to_hash = tuple((self.func(p)[0], self.func(p)[1]) for p in func_hash_points)
func_hash = hash(to_hash)
full_hash = hash((func_hash, self.camera.pixel_shape))
self.background_image_file = "color_mapped_background_" + str(full_hash)
try:
file_path = get_full_raster_image_path(self.background_image_file)
# If we succeed in finding the file:
self.in_background_pass = False
except IOError:
file_path = os.path.join(RASTER_IMAGE_DIR, self.background_image_file + ".png")
self.in_background_pass = True
print "Background file: " + file_path
if self.in_background_pass:
print "The background file does not exist yet; this will be the background creation pass"
print "If not already doing so, please re-run this render with the flags -s -n 0,1 -o \"%s\""%file_path
self.show_num_plane = False
else:
print "The background file already exists; this will be the video pass as usual"
def construct(self): def construct(self):
display_func = self.func if not self.show_output else lambda p : p
if self.show_num_plane: if self.show_num_plane:
self.num_plane.fade() self.num_plane.fade()
self.add(self.num_plane) self.add(self.num_plane)
self.camera.set_background_from_func(
lambda (x, y): point_to_rgba( if self.in_background_pass:
display_func( self.camera.set_background_from_func(
# Should be self.num_plane.point_to_coords_cheap(np.array([x, y, 0])), lambda (x, y): point_to_rgba(
# but for cheapness, we'll go with just (x, y), having never altered self.pos_to_color_func(
# any num_plane's from default settings so far # Should be self.num_plane.point_to_coords_cheap(np.array([x, y, 0])),
(x, y) # but for cheapness, we'll go with just (x, y), having never altered
# any num_plane's from default settings so far
(x, y)
)
) )
) )
)
# The one scene to be rendered by the desired -s -n 0, 1 invocation:
self.play(EmptyAnimation())
self.wait()
else:
self.camera.background_image = self.background_image_file
self.camera.init_background()
class PureColorMap(ColorMappedByFuncScene):
CONFIG = {
"show_num_plane" : False
}
class ColorMappedByFuncStill(ColorMappedByFuncScene): class ColorMappedByFuncStill(ColorMappedByFuncScene):
def construct(self): def construct(self):
@ -535,6 +664,7 @@ class PiWalkerCircle(PiWalker):
# TODO: Give drawn lines a bit of buffer, so that the rectangle's corners are filled in # TODO: Give drawn lines a bit of buffer, so that the rectangle's corners are filled in
class EquationSolver2d(ColorMappedByFuncScene): class EquationSolver2d(ColorMappedByFuncScene):
CONFIG = { CONFIG = {
"camera_config" : {"use_z_coordinate_for_display_order": True},
"initial_lower_x" : -5.1, "initial_lower_x" : -5.1,
"initial_upper_x" : 5.1, "initial_upper_x" : 5.1,
"initial_lower_y" : -3.1, "initial_lower_y" : -3.1,
@ -572,10 +702,10 @@ class EquationSolver2d(ColorMappedByFuncScene):
stroke_width = 10, stroke_width = 10,
color = RED) color = RED)
if self.use_fancy_lines: if self.use_fancy_lines:
colored_line = BackgroundColoredVMobject(thick_line, background_image_file = None) colored_line = thick_line.color_using_background_image(self.background_image_file)
colored_line.set_background_array(background) # colored_line.set_background_array(background)
else: else:
colored_line = thick_line.set_stroke_with(4) colored_line = thick_line.set_stroke(width = 4)
walker_anim = LinearWalker( walker_anim = LinearWalker(
start_coords = start, start_coords = start,
@ -622,11 +752,11 @@ class EquationSolver2d(ColorMappedByFuncScene):
rect.get_bottom_right(), rect.get_bottom_right(),
rect.get_bottom_left() rect.get_bottom_left()
] ]
points = np.array([num_plane.coords_to_point(x, y) for (x, y) in coords]) + IN points = np.array([num_plane.coords_to_point(x, y) for (x, y) in coords]) + 2 * IN
# TODO: Maybe use diagonal lines or something to fill in rectangles indicating # TODO: Maybe use diagonal lines or something to fill in rectangles indicating
# their "Nothing here" status? # their "Nothing here" status?
# Or draw a large X or something # Or draw a large X or something
fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = GREY) fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = DARK_BROWN)
return Succession(anim, FadeIn(fill_rect)) return Succession(anim, FadeIn(fill_rect))
else: else:
(sub_rect1, sub_rect2) = rect.splits_on_dim(dim_to_split) (sub_rect1, sub_rect2) = rect.splits_on_dim(dim_to_split)
@ -644,7 +774,7 @@ class EquationSolver2d(ColorMappedByFuncScene):
for (sub_rect, side_to_draw) in sub_rect_and_sides for (sub_rect, side_to_draw) in sub_rect_and_sides
] ]
mid_line_coords = rect.split_line_on_dim(dim_to_split) mid_line_coords = rect.split_line_on_dim(dim_to_split)
mid_line_points = [num_plane.coords_to_point(x, y) for (x, y) in mid_line_coords] mid_line_points = [num_plane.coords_to_point(x, y) + IN for (x, y) in mid_line_coords]
# TODO: Have this match rectangle line style, apart from dashes and thin-ness? # TODO: Have this match rectangle line style, apart from dashes and thin-ness?
# Though there is also informational value in seeing the dashed line separately from rectangle lines # Though there is also informational value in seeing the dashed line separately from rectangle lines
mid_line = DashedLine(*mid_line_points) mid_line = DashedLine(*mid_line_points)
@ -723,7 +853,9 @@ class ArrowCircleTest(Scene):
return x return x
num_arrows = 8 * 3 num_arrows = 8 * 3
arrows = [rev_rotate(base_arrow.copy(), (fdiv(i, num_arrows))) for i in range(num_arrows)]
# 0.5 - fdiv below so as to get a clockwise rotation from left
arrows = [rev_rotate(base_arrow.copy(), 0.5 - (fdiv(i, num_arrows))) for i in range(num_arrows)]
arrows_vgroup = VGroup(*arrows) arrows_vgroup = VGroup(*arrows)
self.play(ShowCreation(arrows_vgroup), run_time = 2.5, rate_func = None) self.play(ShowCreation(arrows_vgroup), run_time = 2.5, rate_func = None)
@ -803,7 +935,7 @@ class FirstSqrtScene(EquationSolver1d):
"x_max" : 2.5, "x_max" : 2.5,
"y_min" : 0, "y_min" : 0,
"y_max" : 2.5**2, "y_max" : 2.5**2,
"graph_origin" : 2*DOWN + 5 * LEFT, "graph_origin" : 2.5*DOWN + 5.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,
@ -818,31 +950,141 @@ class FirstSqrtScene(EquationSolver1d):
"show_target_line" : True, "show_target_line" : True,
} }
class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene): FirstSqrtSceneConfig = FirstSqrtScene.CONFIG
# TODO: Don't bother with ReconfigurableScene; just use new config from start shiftVal = FirstSqrtSceneConfig["targetY"]
# (But can also use this as written, and just cut into middle in Premiere)
def setup(self): class SecondSqrtScene(FirstSqrtScene):
FirstSqrtScene.setup(self) CONFIG = {
ReconfigurableScene.setup(self) "func" : lambda x : FirstSqrtSceneConfig["func"](x) - shiftVal,
"targetY" : 0,
def construct(self): "graph_label" : FirstSqrtSceneConfig["graph_label"] + " - " + str(shiftVal),
shiftVal = self.targetY "y_min" : FirstSqrtSceneConfig["y_min"] - shiftVal,
"y_max" : FirstSqrtSceneConfig["y_max"] - shiftVal,
self.drawGraph() "show_target_line" : False,
newOrigin = self.coords_to_point(0, shiftVal) # 0.96 hacked in by checking calculations above
self.transition_to_alt_config( "graph_origin" : 0.96 * shiftVal * UP + FirstSqrtSceneConfig["graph_origin"],
func = lambda x : x**2 - shiftVal, }
targetY = 0,
graph_label = "y = x^2 - " + str(shiftVal),
y_min = self.y_min - shiftVal,
y_max = self.y_max - shiftVal,
show_target_line = False,
graph_origin = newOrigin)
self.solveEquation()
# TODO: Pi creatures intrigued # TODO: Pi creatures intrigued
class RewriteEquation(Scene):
def construct(self):
# Can maybe fitz around with smoothening the transform, so just = goes to - and new stuff
# is added at the right end, while things re-center
f_old = TexMobject("f(x)")
f_new = f_old.copy()
equals_old = TexMobject("=")
equals_old_2 = equals_old.copy()
equals_new = equals_old.copy()
g_old = TexMobject("g(x)")
g_new = g_old.copy()
minus_new = TexMobject("-")
zero_new = TexMobject("0")
f_old.next_to(equals_old, LEFT)
g_old.next_to(equals_old, RIGHT)
minus_new.next_to(g_new, LEFT)
f_new.next_to(minus_new, LEFT)
equals_new.next_to(g_new, RIGHT)
zero_new.next_to(equals_new, RIGHT)
self.add(f_old, equals_old, equals_old_2, g_old)
self.wait()
self.play(
ReplacementTransform(f_old, f_new),
ReplacementTransform(equals_old, equals_new),
ReplacementTransform(g_old, g_new),
ReplacementTransform(equals_old_2, minus_new),
ShowCreation(zero_new),
)
self.wait()
class SignsExplanation(Scene):
def construct(self):
num_line = NumberLine(stroke_width = 1)
largest_num = 10
num_line.add_numbers(*range(-largest_num, largest_num + 1))
self.add(num_line)
self.wait()
pos_num = 3
neg_num = -pos_num
pos_arrow = Arrow(
num_line.number_to_point(0),
num_line.number_to_point(pos_num),
buff = 0,
color = positive_color)
neg_arrow = Arrow(
num_line.number_to_point(0),
num_line.number_to_point(neg_num),
buff = 0,
color = negative_color)
#num_line.add_numbers(pos_num)
self.play(ShowCreation(pos_arrow))
#num_line.add_numbers(neg_num)
self.play(ShowCreation(neg_arrow))
class VectorField(Scene):
CONFIG = {
"func" : plane_func_from_complex_func(lambda p : p**2 + 2),
"granularity" : 10,
"arrow_scale_factor" : 0.1,
"normalized_arrow_scale_factor" : 5
}
def construct(self):
num_plane = NumberPlane()
self.add(num_plane)
x_min, y_min = num_plane.point_to_coords(SPACE_WIDTH * LEFT + SPACE_HEIGHT * UP)
x_max, y_max = num_plane.point_to_coords(SPACE_WIDTH * RIGHT + SPACE_HEIGHT * DOWN)
x_points = range_via_num_steps(x_min, x_max, self.granularity)
y_points = range_via_num_steps(y_min, y_max, self.granularity)
points = it.product(x_points, y_points)
sized_arrows = Group()
unsized_arrows = Group()
for (x, y) in points:
output = self.func((x, y))
output_size = np.sqrt(sum(output**2))
normalized_output = output * fdiv(self.normalized_arrow_scale_factor, output_size) # Assume output has nonzero size here
arrow = Vector(output * self.arrow_scale_factor)
normalized_arrow = Vector(normalized_output * self.arrow_scale_factor)
arrow.move_to(num_plane.coords_to_point(x, y))
normalized_arrow.move_to(arrow)
sized_arrows.add(arrow)
unsized_arrows.add(normalized_arrow)
self.add(sized_arrows)
self.wait()
self.play(ReplacementTransform(sized_arrows, unsized_arrows))
self.wait()
class HasItsLimitations(Scene):
def construct(self):
num_line = NumberLine()
num_line.add_numbers()
self.add(num_line)
self.wait()
num_plane = NumberPlane()
num_plane.add_coordinates()
self.play(FadeOut(num_line), FadeIn(num_plane))
self.wait()
complex_plane = ComplexPlane()
complex_plane.add_coordinates()
self.play(FadeOut(num_plane), FadeIn(complex_plane))
class ComplexPlaneIs2d(Scene): class ComplexPlaneIs2d(Scene):
def construct(self): def construct(self):
com_plane = ComplexPlane() com_plane = ComplexPlane()
@ -1129,7 +1371,8 @@ class FundThmAlg(EquationSolver2d):
CONFIG = { CONFIG = {
"func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5)), "func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5)),
"num_iterations" : 5, "num_iterations" : 5,
"display_in_parallel" : True "display_in_parallel" : True,
"use_fancy_lines" : False
} }
# TODO: Borsuk-Ulam visuals # TODO: Borsuk-Ulam visuals
@ -1159,6 +1402,96 @@ class DiffOdometer(OdometerScene):
"biased_display_start" : 0 "biased_display_start" : 0
} }
class CombineInterval(Scene):
def construct(self):
plus_sign = TexMobject("+", fill_color = positive_color)
minus_sign = TexMobject("-", fill_color = negative_color)
left_point = Dot(LEFT, color = positive_color)
right_point = Dot(RIGHT, color = negative_color)
line1 = Line(LEFT, RIGHT)
interval1 = Group(line1, left_point, right_point)
plus_sign.next_to(left_point, UP)
minus_sign.next_to(right_point, UP)
self.add(interval1, plus_sign, minus_sign)
self.wait()
self.play(
CircleIndicate(plus_sign),
CircleIndicate(minus_sign),
)
self.wait()
mid_point = Dot(ORIGIN, color = GREY)
question_mark = TexMobject("?", fill_color = GREY)
plus_sign_copy = plus_sign.copy()
minus_sign_copy = minus_sign.copy()
new_signs = Group(question_mark, plus_sign_copy, minus_sign_copy)
for sign in new_signs: sign.next_to(mid_point, UP)
self.play(FadeIn(mid_point), FadeIn(question_mark))
self.wait()
self.play(
ApplyMethod(mid_point.set_color, positive_color),
ReplacementTransform(question_mark, plus_sign_copy),
)
self.play(
CircleIndicate(plus_sign_copy),
CircleIndicate(minus_sign),
)
self.wait()
self.play(
ApplyMethod(mid_point.set_color, negative_color),
ReplacementTransform(plus_sign_copy, minus_sign_copy),
)
self.play(
CircleIndicate(minus_sign_copy),
CircleIndicate(plus_sign),
)
self.wait()
class CombineInterval2(Scene):
def construct(self):
plus_sign = TexMobject("+", fill_color = positive_color)
def make_interval(a, b):
line = Line(a, b)
start_dot = Dot(a, color = positive_color)
end_dot = Dot(b, color = positive_color)
start_sign = plus_sign.copy().next_to(start_dot, UP)
end_sign = plus_sign.copy().next_to(end_dot, UP)
return Group(start_sign, end_sign, line, start_dot, end_dot)
def pair_indicate(a, b):
self.play(
CircleIndicate(a),
CircleIndicate(b)
)
left_interval = make_interval(2 * LEFT, LEFT)
right_interval = make_interval(RIGHT, 2 * RIGHT)
self.play(FadeIn(left_interval), FadeIn(right_interval))
pair_indicate(left_interval[0], left_interval[1])
pair_indicate(right_interval[0], right_interval[1])
self.play(
ApplyMethod(left_interval.shift, RIGHT),
ApplyMethod(right_interval.shift, LEFT),
)
pair_indicate(left_interval[0], right_interval[1])
self.wait()
# TODO: Brouwer's fixed point theorem visuals # TODO: Brouwer's fixed point theorem visuals
# class BFTScene(Scene): # class BFTScene(Scene):
@ -1210,4 +1543,10 @@ class ShowBack(PiWalkerRect):
"func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5)) "func" : plane_poly_with_roots((1, 2), (-1, 1.5), (-1, 1.5))
} }
class Diagnostic(Scene):
def construct(self):
testList = map( (lambda n : (n, rev_to_rgba(n))), [0, 0.25, 0.5, 0.9])
print "rev_to_rgbas", testList
self.wait()
# FIN # FIN

View file

@ -8,7 +8,7 @@ from helpers import *
from animation import Animation from animation import Animation
from mobject import Mobject, Point, VMobject, Group from mobject import Mobject, Point, VMobject, Group
from topics.geometry import Dot from topics.geometry import Dot, Circle
class Transform(Animation): class Transform(Animation):
CONFIG = { CONFIG = {
@ -214,6 +214,17 @@ class Indicate(Transform):
target.highlight(self.color) target.highlight(self.color)
Transform.__init__(self, mobject, target, **kwargs) Transform.__init__(self, mobject, target, **kwargs)
class CircleIndicate(Indicate):
CONFIG = {
"rate_func" : squish_rate_func(there_and_back, 0, 0.8),
"remover" : True
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs)
circle = Circle(color = self.color, **kwargs)
circle.surround(mobject)
Indicate.__init__(self, circle, **kwargs)
class Rotate(ApplyMethod): class Rotate(ApplyMethod):
CONFIG = { CONFIG = {
"in_place" : False, "in_place" : False,

View file

@ -53,6 +53,9 @@ RIGHT = np.array(( 1., 0., 0.))
LEFT = np.array((-1., 0., 0.)) LEFT = np.array((-1., 0., 0.))
IN = np.array(( 0., 0.,-1.)) IN = np.array(( 0., 0.,-1.))
OUT = np.array(( 0., 0., 1.)) OUT = np.array(( 0., 0., 1.))
X_AXIS = np.array(( 1., 0., 0.))
Y_AXIS = np.array(( 0., 1., 0.))
Z_AXIS = np.array(( 0., 0., 1.))
TOP = SPACE_HEIGHT*UP TOP = SPACE_HEIGHT*UP
BOTTOM = SPACE_HEIGHT*DOWN BOTTOM = SPACE_HEIGHT*DOWN

View file

@ -24,6 +24,8 @@ from topics.number_line import *
from topics.combinatorics import * from topics.combinatorics import *
from topics.three_dimensions import * from topics.three_dimensions import *
from topics.three_dimensions import *
# To watch one of these scenes, run the following: # To watch one of these scenes, run the following:
# python extract_scene.py file_name <SceneName> -p # python extract_scene.py file_name <SceneName> -p
# #
@ -59,6 +61,67 @@ class WriteStuff(Scene):
self.play(Write(TextMobject("Stuff").scale(3))) self.play(Write(TextMobject("Stuff").scale(3)))
class Rotation3d(ThreeDScene):
def construct(self):
# STEP 1
# Build two cube in the 3D scene, one for around the origin,
# the other shifted along the vector RIGHT + UP + OUT
cube_origin = Cube(fill_opacity = 0.8, stroke_width = 1.,
side_length = 1., fill_color = WHITE)
# RIGHT side: Red
# UP side: Green
# OUT side: Blue
orientations = [IN, OUT, LEFT, RIGHT, UP, DOWN]
for face, orient in zip(cube_origin.family_members_with_points(), orientations):
if np.array_equal(orient, RIGHT):
face.set_style_data(fill_color = RED)
elif np.array_equal(orient, UP):
face.set_style_data(fill_color = GREEN)
elif np.array_equal(orient, OUT):
face.set_style_data(fill_color = BLUE)
cube_shifted = Cube(fill_opacity = 0.8, stroke_width = 1.,
side_length = 1., fill_color = BLUE)
shift_vec = 2*(RIGHT + UP + OUT)
cube_shifted.shift(shift_vec)
# STEP 2
# Add the cubes in the 3D scene
self.add(cube_origin)
self.add(cube_shifted)
# STEP 3
# Setup the camera position
phi, theta, distance = ThreeDCamera().get_spherical_coords()
angle_factor = 0.9
phi += 2*np.pi/4*angle_factor
theta += 3*2*np.pi/8
self.set_camera_position(phi, theta, distance)
self.wait()
# STEP 4
# Animation
# Animation 1: rotation around the Z-axis with the ORIGIN of the space
# as center of rotation
theta += 2*np.pi
self.move_camera(phi, theta, distance,
run_time = 5)
# Animation 2: shift the space in order of to get the center of the shifted cube
# as the next center of rotation
cube_center = cube_shifted.get_center()
self.move_camera(center_x = cube_center[0],
center_y = cube_center[1],
center_z = cube_center[2],
run_time = 2)
# Animation 3: rotation around the Z-axis with the center of the shifted cube
# as center of rotation
theta += 2*np.pi
self.move_camera(phi, theta, distance,
run_time = 5)
class SpinAroundCube(ThreeDScene): class SpinAroundCube(ThreeDScene):
# Take a look at ThreeDSCene in three_dimensions.py. # Take a look at ThreeDSCene in three_dimensions.py.

View file

@ -70,6 +70,17 @@ def get_configuration():
parser.add_argument("-o", "--output_name") parser.add_argument("-o", "--output_name")
parser.add_argument("-n", "--start_at_animation_number") parser.add_argument("-n", "--start_at_animation_number")
args = parser.parse_args() args = parser.parse_args()
if args.output_name != None:
output_name_root, output_name_ext = os.path.splitext(args.output_name)
expected_ext = '.png' if args.show_last_frame else '.mp4'
if not output_name_ext in ['', expected_ext]
print "WARNING: The output will be to (doubly-dotted) %s%s"%output_name_root%expected_ext
output_name = args.output_name
else:
# If anyone wants .mp4.mp4 and is surprised to only get .mp4, or such... Well, too bad.
output_name = output_name_root
else:
output_name = args.output_name
except argparse.ArgumentError as err: except argparse.ArgumentError as err:
print(str(err)) print(str(err))
sys.exit(2) sys.exit(2)
@ -87,7 +98,7 @@ def get_configuration():
"quiet" : args.quiet or args.write_all, "quiet" : args.quiet or args.write_all,
"ignore_waits" : args.preview, "ignore_waits" : args.preview,
"write_all" : args.write_all, "write_all" : args.write_all,
"output_name" : args.output_name, "output_name" : output_name,
"start_at_animation_number" : args.start_at_animation_number, "start_at_animation_number" : args.start_at_animation_number,
"end_at_animation_number" : None, "end_at_animation_number" : None,
} }

View file

@ -689,6 +689,10 @@ class DictAsObject(object):
def fdiv(a, b): def fdiv(a, b):
return np.true_divide(a,b) return np.true_divide(a,b)
def range_via_num_steps(start, end, num_steps, include_end = True):
num_points = num_steps + (1 if include_end else 0)
return [interpolate(start, end, fdiv(i, num_steps)) for i in range(num_points)]
# For debugging purposes # For debugging purposes
def print_mobject_family(mob, n_tabs = 0): def print_mobject_family(mob, n_tabs = 0):

View file

@ -296,6 +296,7 @@ class Mobject(Container):
aligned_edge = ORIGIN, aligned_edge = ORIGIN,
submobject_to_align = None, submobject_to_align = None,
index_of_submobject_to_align = None, index_of_submobject_to_align = None,
coor_mask = np.array([1,1,1]),
): ):
if isinstance(mobject_or_point, Mobject): if isinstance(mobject_or_point, Mobject):
mob = mobject_or_point mob = mobject_or_point
@ -315,7 +316,7 @@ class Mobject(Container):
else: else:
aligner = self aligner = self
point_to_align = aligner.get_critical_point(aligned_edge - direction) point_to_align = aligner.get_critical_point(aligned_edge - direction)
self.shift(target_point - point_to_align + buff*direction) self.shift((target_point - point_to_align + buff*direction)*coor_mask)
return self return self
def align_to(self, mobject_or_point, direction = ORIGIN, alignment_vect = UP): def align_to(self, mobject_or_point, direction = ORIGIN, alignment_vect = UP):
@ -403,13 +404,14 @@ class Mobject(Container):
submob.scale(1./factor) submob.scale(1./factor)
return self return self
def move_to(self, point_or_mobject, aligned_edge = ORIGIN): def move_to(self, point_or_mobject, aligned_edge = ORIGIN,
coor_mask = np.array([1,1,1])):
if isinstance(point_or_mobject, Mobject): if isinstance(point_or_mobject, Mobject):
target = point_or_mobject.get_critical_point(aligned_edge) target = point_or_mobject.get_critical_point(aligned_edge)
else: else:
target = point_or_mobject target = point_or_mobject
point_to_align = self.get_critical_point(aligned_edge) point_to_align = self.get_critical_point(aligned_edge)
self.shift(target - point_to_align) self.shift((target - point_to_align)*coor_mask)
return self return self
def replace(self, mobject, dim_to_match = 0, stretch = False): def replace(self, mobject, dim_to_match = 0, stretch = False):

View file

@ -159,31 +159,52 @@ class SVGMobject(VMobject):
x = float(element.getAttribute('x')) x = float(element.getAttribute('x'))
#Flip y #Flip y
y = -float(element.getAttribute('y')) y = -float(element.getAttribute('y'))
mobject.shift(x*RIGHT+y*UP)
except: except:
pass pass
try: transform = element.getAttribute('transform')
transform = element.getAttribute('transform')
try: # transform matrix
prefix = "matrix(" prefix = "matrix("
suffix = ")" suffix = ")"
if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception() if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception()
transform = transform[len(prefix):-len(suffix)] transform = transform[len(prefix):-len(suffix)]
transform = string_to_numbers(transform) transform = string_to_numbers(transform)
transform = np.array(transform).reshape([3,2]) transform = np.array(transform).reshape([3,2])
x += transform[2][0] x = transform[2][0]
y -= transform[2][1] y = -transform[2][1]
matrix = np.identity(self.dim) matrix = np.identity(self.dim)
matrix[:2,:2] = transform[:2,:] matrix[:2,:2] = transform[:2,:]
t_matrix = np.transpose(matrix) matrix[1] *= -1
matrix[:,1] *= -1
for mob in mobject.family_members_with_points(): for mob in mobject.family_members_with_points():
mob.points = np.dot(mob.points, t_matrix) mob.points = np.dot(mob.points, matrix)
mobject.shift(x*RIGHT+y*UP)
except: except:
pass pass
mobject.shift(x*RIGHT+y*UP) try: # transform scale
#TODO, transforms prefix = "scale("
suffix = ")"
if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception()
transform = transform[len(prefix):-len(suffix)]
scale_x, scale_y = string_to_numbers(transform)
mobject.scale(np.array([scale_x, scale_y, 1]))
except:
pass
try: # transform translate
prefix = "translate("
suffix = ")"
if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception()
transform = transform[len(prefix):-len(suffix)]
x, y = string_to_numbers(transform)
mobject.shift(x*RIGHT + y*DOWN)
except:
pass
#TODO, ...
def update_ref_to_element(self, defs): def update_ref_to_element(self, defs):
new_refs = dict([ new_refs = dict([

View file

@ -193,6 +193,8 @@ class ComplexPlane(NumberPlane):
return complex(x, y) return complex(x, y)
def get_coordinate_labels(self, *numbers): def get_coordinate_labels(self, *numbers):
# TODO: Should merge this with the code from NumberPlane.get_coordinate_labels
result = VGroup() result = VGroup()
nudge = 0.1*(DOWN+RIGHT) nudge = 0.1*(DOWN+RIGHT)
if len(numbers) == 0: if len(numbers) == 0:
@ -211,10 +213,7 @@ class ComplexPlane(NumberPlane):
num_mob = TexMobject(num_str) num_mob = TexMobject(num_str)
num_mob.add_background_rectangle() num_mob.add_background_rectangle()
num_mob.scale(self.number_scale_factor) num_mob.scale(self.number_scale_factor)
if complex(number).imag != 0: vect = DOWN + LEFT
vect = DOWN+RIGHT
else:
vect = DOWN+RIGHT
num_mob.next_to(point, vect, SMALL_BUFF) num_mob.next_to(point, vect, SMALL_BUFF)
result.add(num_mob) result.add(num_mob)
return result return result
@ -224,6 +223,8 @@ class ComplexPlane(NumberPlane):
return self return self
def add_spider_web(self, circle_freq = 1, angle_freq = np.pi/6): def add_spider_web(self, circle_freq = 1, angle_freq = np.pi/6):
# This code no longer works because it has this reference to self.fade_factor
# which is never initialized. Shall we delete this little-used function entirely?
self.fade(self.fade_factor) self.fade(self.fade_factor)
config = { config = {
"color" : self.color, "color" : self.color,

View file

@ -3,10 +3,10 @@ from helpers import *
from mobject import Mobject from mobject import Mobject
from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint
from mobject.svg_mobject import SVGMobject from mobject.svg_mobject import SVGMobject
from mobject.tex_mobject import TextMobject, TexMobject from mobject.tex_mobject import TextMobject, TexMobject, Brace
from animation import Animation from animation import Animation
from animation.simple_animations import Rotating, LaggedStart from animation.simple_animations import Rotating, LaggedStart, AnimationGroup
from animation.transform import ApplyMethod, FadeIn, GrowFromCenter from animation.transform import ApplyMethod, FadeIn, GrowFromCenter
from topics.geometry import Circle, Line, Rectangle, Square, \ from topics.geometry import Circle, Line, Rectangle, Square, \

View file

@ -36,8 +36,11 @@ class ThreeDCamera(CameraWithPerspective):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
Camera.__init__(self, *args, **kwargs) Camera.__init__(self, *args, **kwargs)
self.unit_sun_vect = self.sun_vect/np.linalg.norm(self.sun_vect) self.unit_sun_vect = self.sun_vect/np.linalg.norm(self.sun_vect)
## Lives in the phi-theta-distance space ## rotation_mobject lives in the phi-theta-distance space
self.rotation_mobject = VectorizedPoint() self.rotation_mobject = VectorizedPoint()
## moving_center lives in the x-y-z space
## It representes the center of rotation
self.moving_center = VectorizedPoint(self.space_center)
self.set_position(self.phi, self.theta, self.distance) self.set_position(self.phi, self.theta, self.distance)
def modified_rgb(self, vmobject, rgb): def modified_rgb(self, vmobject, rgb):
@ -128,10 +131,24 @@ class ThreeDCamera(CameraWithPerspective):
np.cos(phi) np.cos(phi)
]) ])
def set_position(self, phi = None, theta = None, distance = None): def get_center_of_rotation(self, x = None, y = None, z = None):
curr_x, curr_y, curr_z = self.moving_center.points[0]
if x is None:
x = curr_x
if y is None:
y = curr_y
if z is None:
z = curr_z
return np.array([x, y, z])
def set_position(self, phi = None, theta = None, distance = None,
center_x = None, center_y = None, center_z = None):
point = self.get_spherical_coords(phi, theta, distance) point = self.get_spherical_coords(phi, theta, distance)
self.rotation_mobject.move_to(point) self.rotation_mobject.move_to(point)
self.phi, self.theta, self.distance = point self.phi, self.theta, self.distance = point
center_of_rotation = self.get_center_of_rotation(center_x, center_y, center_z)
self.moving_center.move_to(center_of_rotation)
self.space_center = self.moving_center.points[0]
def get_view_transformation_matrix(self): def get_view_transformation_matrix(self):
return (self.default_distance / self.get_distance()) * np.dot( return (self.default_distance / self.get_distance()) * np.dot(
@ -142,6 +159,8 @@ class ThreeDCamera(CameraWithPerspective):
def points_to_pixel_coords(self, points): def points_to_pixel_coords(self, points):
matrix = self.get_view_transformation_matrix() matrix = self.get_view_transformation_matrix()
new_points = np.dot(points, matrix.T) new_points = np.dot(points, matrix.T)
self.space_center = self.moving_center.points[0]
return Camera.points_to_pixel_coords(self, new_points) return Camera.points_to_pixel_coords(self, new_points)
class ThreeDScene(Scene): class ThreeDScene(Scene):
@ -150,8 +169,9 @@ class ThreeDScene(Scene):
"ambient_camera_rotation" : None, "ambient_camera_rotation" : None,
} }
def set_camera_position(self, phi = None, theta = None, distance = None): def set_camera_position(self, phi = None, theta = None, distance = None,
self.camera.set_position(phi, theta, distance) center_x = None, center_y = None, center_z = None):
self.camera.set_position(phi, theta, distance, center_x, center_y, center_z)
def begin_ambient_camera_rotation(self, rate = 0.01): def begin_ambient_camera_rotation(self, rate = 0.01):
self.ambient_camera_rotation = AmbientMovement( self.ambient_camera_rotation = AmbientMovement(
@ -167,8 +187,9 @@ class ThreeDScene(Scene):
self.ambient_camera_rotation = None self.ambient_camera_rotation = None
def move_camera( def move_camera(
self, self,
phi = None, theta = None, distance = None, phi = None, theta = None, distance = None,
center_x = None, center_y = None, center_z = None,
added_anims = [], added_anims = [],
**kwargs **kwargs
): ):
@ -178,10 +199,17 @@ class ThreeDScene(Scene):
target_point, target_point,
**kwargs **kwargs
) )
target_center = self.camera.get_center_of_rotation(center_x, center_y, center_z)
movement_center = ApplyMethod(
self.camera.moving_center.move_to,
target_center,
**kwargs
)
is_camera_rotating = self.ambient_camera_rotation in self.continual_animations is_camera_rotating = self.ambient_camera_rotation in self.continual_animations
if is_camera_rotating: if is_camera_rotating:
self.remove(self.ambient_camera_rotation) self.remove(self.ambient_camera_rotation)
self.play(movement, *added_anims) self.play(movement, movement_center, *added_anims)
target_point = self.camera.get_spherical_coords(phi, theta, distance)
if is_camera_rotating: if is_camera_rotating:
self.add(self.ambient_camera_rotation) self.add(self.ambient_camera_rotation)