diff --git a/animation/simple_animations.py b/animation/simple_animations.py index c0bc7393..f8ff7bff 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -68,8 +68,6 @@ class ShowPassingFlash(ShowPartial): return (lower, upper) - - class Flash(Animation): CONFIG = { "color" : "white", diff --git a/brachistochrone/curves.py b/brachistochrone/curves.py index 5a307da4..b66fa9a2 100644 --- a/brachistochrone/curves.py +++ b/brachistochrone/curves.py @@ -6,7 +6,7 @@ from helpers import * from mobject.tex_mobject import TexMobject, TextMobject, Brace from mobject import Mobject from mobject.image_mobject import \ - MobjectFromRegion, ImageMobject, MobjectFromPixelArray + ImageMobject, MobjectFromPixelArray from topics.three_dimensions import Stars from animation import Animation diff --git a/brachistochrone/drawing_images.py b/brachistochrone/drawing_images.py index f37796d7..d0632c1b 100644 --- a/brachistochrone/drawing_images.py +++ b/brachistochrone/drawing_images.py @@ -14,7 +14,7 @@ from helpers import * from mobject.tex_mobject import TexMobject from mobject import Mobject from mobject.image_mobject import \ - MobjectFromRegion, ImageMobject, MobjectFromPixelArray + ImageMobject, MobjectFromPixelArray from mobject.tex_mobject import TextMobject, TexMobject from animation.transform import \ diff --git a/brachistochrone/light.py b/brachistochrone/light.py index 8c077a15..4d4501c7 100644 --- a/brachistochrone/light.py +++ b/brachistochrone/light.py @@ -6,7 +6,7 @@ from helpers import * from mobject.tex_mobject import TexMobject, TextMobject, Brace from mobject import Mobject, Mobject1D from mobject.image_mobject import \ - MobjectFromRegion, ImageMobject, MobjectFromPixelArray + ImageMobject, MobjectFromPixelArray from topics.three_dimensions import Stars from animation import Animation @@ -26,6 +26,7 @@ from topics.functions import ParametricFunction, FunctionGraph from topics.number_line import NumberPlane from mobject.region import Region, region_from_polygon_vertices from scene import Scene +from scene.zoomed_scene import ZoomedScene from brachistochrone.curves import Cycloid @@ -89,7 +90,7 @@ class MultipathPhotonScene(PhotonScene): CONFIG = { "num_paths" : 5 } - def run_along_paths(self): + def run_along_paths(self, **kwargs): paths = self.get_paths() colors = Color(YELLOW).range_to(WHITE, len(paths)) for path, color in zip(paths, colors): @@ -104,7 +105,8 @@ class MultipathPhotonScene(PhotonScene): ShowCreation( path, rate_func = lambda t : 0.9*smooth(t) - ) + ), + **kwargs ) self.dither() @@ -306,64 +308,59 @@ class ShowMultiplePathsInGlass(ShowMultiplePathsScene): ] -class MultilayeredGlass(PhotonScene): +class MultilayeredGlass(PhotonScene, ZoomedScene): CONFIG = { "num_discrete_layers" : 5, "num_variables" : 3, "top_color" : BLUE_E, "bottom_color" : BLUE_A, + "zoomed_canvas_space_shape" : (5, 5), + "square_color" : GREEN_B, } def construct(self): self.cycloid = Cycloid(end_theta = np.pi) + self.cycloid.highlight(YELLOW) self.top = self.cycloid.get_top()[1] self.bottom = self.cycloid.get_bottom()[1]-1 - self.generate_layer_regions() + self.generate_layers() self.generate_discrete_path() photon_run = self.photon_run_along_path( - self.augmented_path, + self.discrete_path, run_time = 1, rate_func = rush_into ) - # self.continuous_to_smooth() - self.paint_layers() + self.continuous_to_smooth() + self.add(*self.layers) self.show_layer_variables() self.play(photon_run) self.play(ShowCreation(self.discrete_path)) self.isolate_bend_points() - # self.dither() + self.clear() + self.add(*self.layers) + self.show_main_equation() + self.ask_continuous_question() def continuous_to_smooth(self): + self.add(*self.layers) continuous = self.get_continuous_background() - layers = Mobject(*[ - MobjectFromRegion(region, color) - for region, color in zip( - self.layer_regions, self.layer_colors - ) - ]) - layers.ingest_sub_mobjects() - - self.play(FadeIn(continuous)) - self.play(Transform(continuous, layers)) - self.remove(continuous) - self.paint_layers() + self.add(continuous) self.dither() - - def paint_layers(self): - # for region, color in zip(self.layer_regions, self.layer_colors): - # self.highlight_region(region, color) - for top, color in zip(self.layer_tops, self.layer_colors): - self.add(Line( - SPACE_WIDTH*LEFT+top*UP, SPACE_WIDTH*RIGHT+top*UP, - color = color - )) - - def get_continuous_background(self): - glass = MobjectFromRegion(Region( - lambda x, y : (y < self.top) & (y > self.bottom) + self.play(ShowCreation( + continuous, + rate_func = lambda t : smooth(1-t) )) + self.remove(continuous) + self.dither() + + def get_continuous_background(self): + glass = FilledRectangle( + height = self.top-self.bottom, + width = 2*SPACE_WIDTH, + ) + glass.sort_points(lambda p : -p[1]) + glass.shift((self.top-glass.get_top()[1])*UP) glass.gradient_highlight(self.top_color, self.bottom_color) - glass.scale_in_place(0.99) return glass def generate_layer_info(self): @@ -381,27 +378,42 @@ class MultilayeredGlass(PhotonScene): for alpha in np.arange(0, 1+epsilon, epsilon) ] - def generate_layer_regions(self): + def generate_layers(self): self.generate_layer_info() - self.layer_regions = [ - Region(lambda x, y : (y < top) & (y > top-self.layer_thickness)) - for top in self.layer_tops + def create_region(top, color): + return Region( + lambda x, y : (y < top) & (y > top-self.layer_thickness), + color = color + ) + self.layers = [ + create_region(top, color) + for top, color in zip(self.layer_tops, self.layer_colors) ] + def generate_discrete_path(self): points = self.cycloid.points + tops = list(self.layer_tops) + tops.append(tops[-1]-self.layer_thickness) indices = [ np.argmin(np.abs(points[:, 1]-top)) - for top in self.layer_tops + for top in tops ] self.bend_points = points[indices[1:-1]] - self.discrete_path = Mobject1D(color = YELLOW) + self.path_angles = [] + self.discrete_path = Mobject1D( + color = YELLOW, + density = 3*DEFAULT_POINT_DENSITY_1D + ) for start, end in zip(indices, indices[1:]): + start_point, end_point = points[start], points[end] self.discrete_path.add_line( - points[start], points[end] + start_point, end_point ) - self.augmented_path = self.discrete_path.copy() - self.augmented_path.add_line( + self.path_angles.append( + angle_of_vector(start_point-end_point)-np.pi/2 + ) + self.discrete_path.add_line( points[end], SPACE_WIDTH*RIGHT+(self.layer_tops[-1]-1)*UP ) @@ -443,6 +455,7 @@ class MultilayeredGlass(PhotonScene): start_ys.append(start_y) end_ys.append(end_y) braces.append(brace) + for v_eq, path, time in zip(v_equations, center_paths, [2, 1, 0.5]): photon_run = self.photon_run_along_path( path, @@ -455,7 +468,6 @@ class MultilayeredGlass(PhotonScene): ) self.dither() for start_y, brace in zip(start_ys, braces): - start_y.highlight(BLACK) self.add(start_y) self.play(GrowFromCenter(brace)) self.dither() @@ -473,49 +485,184 @@ class MultilayeredGlass(PhotonScene): self.equations.append(Mobject(*v_eq)) def isolate_bend_points(self): - little_square = Square(side_length = 4, color = WHITE) - little_square.scale(0.25) - little_square.shift(self.bend_points[0]) - big_square = little_square.copy() - big_square.scale(4) - big_square.to_corner(UP+RIGHT) + arc_radius = 0.1 + self.activate_zooming() + little_square = self.get_zoomed_camera_mobject() - - first_time = True - for bend_point in self.bend_points: - if first_time: - self.play(ShowCreation(little_square)) - first_time = False - else: - self.remove(lines, big_square) - self.play(ApplyMethod( - little_square.shift, - bend_point - little_square.get_center() - )) - lines = self.lines_connecting_squares(little_square, big_square) - self.play( - ShowCreation(lines), - ShowCreation(big_square) + for index in range(3): + bend_point = self.bend_points[index] + line = Line( + bend_point+DOWN, + bend_point+UP, + color = WHITE, + density = self.zoom_factor*DEFAULT_POINT_DENSITY_1D ) - self.dither(2) + angle_arcs = [] + for i, rotation in [(index, np.pi/2), (index+1, -np.pi/2)]: + arc = Arc(angle = self.path_angles[i]) + arc.scale(arc_radius) + arc.rotate(rotation) + arc.shift(bend_point) + angle_arcs.append(arc) + thetas = [] + for i in [index+1, index+2]: + theta = TexMobject("\\theta_%d"%i) + theta.scale(0.5/self.zoom_factor) + vert = UP if i == index+1 else DOWN + horiz = rotate_vector(vert, np.pi/2) + theta.next_to( + Point(bend_point), + horiz, + buff = 0.01 + ) + theta.shift(1.5*arc_radius*vert) + thetas.append(theta) + figure_marks = [line] + angle_arcs + thetas - - - def lines_connecting_squares(self, square1, square2): - return Mobject(*[ - Line( - square1.get_corner(vect), - square2.get_corner(vect), + self.play(ApplyMethod( + little_square.shift, + bend_point - little_square.get_center(), + run_time = 2 + )) + self.play(*map(ShowCreation, figure_marks)) + self.dither() + equation_frame = little_square.copy() + equation_frame.scale(0.5) + equation_frame.shift( + little_square.get_corner(UP+RIGHT) - \ + equation_frame.get_corner(UP+RIGHT) ) - for vect in [UP+LEFT, DOWN+LEFT] - ]).highlight(square1.get_color()) + equation_frame.scale_in_place(0.9) + self.show_snells(index+1, equation_frame) + self.remove(*figure_marks) + self.disactivate_zooming() + + def show_snells(self, index, frame): + left_text, right_text = [ + "\\dfrac{\\sin(\\theta_%d)}{\\phantom{\\sqrt{y_1}}}"%x + for x in index, index+1 + ] + left, equals, right = TexMobject( + [left_text, "=", right_text] + ).split() + vs = [] + sqrt_ys = [] + for x, numerator in [(index, left), (index+1, right)]: + v, sqrt_y = [ + TexMobject( + text, size = "\\Large" + ).next_to(numerator, DOWN) + for text in "v_%d"%x, "\\sqrt{y_%d}"%x + ] + vs.append(v) + sqrt_ys.append(sqrt_y) + start, end = [ + Mobject( + left.copy(), mobs[0], equals.copy(), right.copy(), mobs[1] + ).replace(frame) + for mobs in vs, sqrt_ys + ] + + self.add(start) + self.dither(2) + self.play(Transform( + start, end, + path_func = counterclockwise_path() + )) + self.dither(2) + self.remove(start, end) + + def show_main_equation(self): + self.equation = TexMobject(""" + \\dfrac{\\sin(\\theta)}{\\sqrt{y}} = + \\text{constant} + """) + self.equation.shift(LEFT) + self.equation.shift( + (self.layer_tops[0]-self.equation.get_top())*UP + ) + self.add(self.equation) + self.dither() + + def ask_continuous_question(self): + continuous = self.get_continuous_background() + line = Line( + UP, DOWN, + density = self.zoom_factor*DEFAULT_POINT_DENSITY_1D + ) + theta = TexMobject("\\theta") + theta.scale(0.5/self.zoom_factor) + + self.play( + ShowCreation(continuous), + Animation(self.equation) + ) + self.remove(*self.layers) + self.play(ShowCreation(self.cycloid)) + self.activate_zooming() + little_square = self.get_zoomed_camera_mobject() + + self.add(line) + indices = np.arange( + 0, self.cycloid.get_num_points()-1, 10 + ) + for index in indices: + point = self.cycloid.points[index] + next_point = self.cycloid.points[index+1] + angle = angle_of_vector(point - next_point) + for mob in little_square, line: + mob.shift(point - mob.get_center()) + arc = Arc(angle-np.pi/2, start_angle = np.pi/2) + arc.scale(0.1) + arc.shift(point) + self.add(arc) + if angle > np.pi/2 + np.pi/6: + vect_angle = interpolate(np.pi/2, angle, 0.5) + vect = rotate_vector(RIGHT, vect_angle) + theta.center() + theta.shift(point) + theta.shift(0.15*vect) + self.add(theta) + self.dither(self.frame_duration) + self.remove(arc) +class StraightLinesFastestInConstantMedium(PhotonScene): + def construct(self): + kwargs = {"size" : "\\Large"} + left = TextMobject("Speed of light is constant", **kwargs) + arrow = TexMobject("\\Rightarrow", **kwargs) + right = TextMobject("Staight path is fastest", **kwargs) + left.next_to(arrow, LEFT) + right.next_to(arrow, RIGHT) + squaggle, line = self.get_paths() + + self.play(*map(ShimmerIn, [left, arrow, right])) + self.play(ShowCreation(squaggle)) + self.play(Transform( + squaggle, line, + path_func = path_along_arc(np.pi) + )) + self.play(self.photon_run_along_path(line)) + self.dither() - -class MultilayeredGlassZoomIn(Scene): - def construct(self, layer_number): + def get_paths(self): + squaggle = ParametricFunction( + lambda t : (0.5*t+np.cos(t))*RIGHT+np.sin(t)*UP, + start = -np.pi, + end = 2*np.pi + ) + squaggle.shift(2*UP) + start, end = squaggle.points[0], squaggle.points[-1] + line = Line(start, end) + result = [squaggle, line] + for mob in result: + mob.highlight(BLUE_D) + return result + +class GlassAndAir(PhotonScene): + def construct(self): pass @@ -526,3 +673,5 @@ class MultilayeredGlassZoomIn(Scene): + + diff --git a/brachistochrone/metaphors.py b/brachistochrone/metaphors.py deleted file mode 100644 index 51ce4ca1..00000000 --- a/brachistochrone/metaphors.py +++ /dev/null @@ -1,62 +0,0 @@ -import numpy as np -import itertools as it - -from helpers import * - -from mobject.tex_mobject import TexMobject, TextMobject, Brace -from mobject import Mobject -from mobject.image_mobject import \ - MobjectFromRegion, ImageMobject, MobjectFromPixelArray -from topics.three_dimensions import Stars - -from animation import Animation -from animation.transform import \ - Transform, CounterclockwiseTransform, ApplyPointwiseFunction,\ - FadeIn, FadeOut, GrowFromCenter, ApplyFunction, ApplyMethod, \ - ShimmerIn -from animation.simple_animations import \ - ShowCreation, Homotopy, PhaseFlow, ApplyToCenters, DelayByOrder, \ - ShowPassingFlash -from animation.playground import TurnInsideOut, Vibrate -from topics.geometry import \ - Line, Circle, Square, Grid, Rectangle, Arrow, Dot, Point, \ - Arc, FilledRectangle -from topics.characters import Randolph, Mathematician -from topics.functions import ParametricFunction, FunctionGraph -from topics.number_line import NumberPlane -from mobject.region import Region, region_from_polygon_vertices -from scene import Scene - - -class OceanScene(Scene): - def construct(self): - self.rolling_waves() - - def rolling_waves(self): - if not hasattr(self, "ocean"): - self.setup_ocean() - for state in self.ocean_states: - self.play(Transform(self.ocean, state)) - - - def setup_ocean(self): - def func(points): - result = np.zeros(points.shape) - result[:,1] = 0.25 * np.sin(points[:,0]) * np.sin(points[:,1]) - return result - - self.ocean_states = [] - for unit in -1, 1: - ocean = FilledRectangle( - color = BLUE_D, - density = 25 - ) - nudges = unit*func(ocean.points) - ocean.points += nudges - alphas = nudges[:,1] - alphas -= np.min(alphas) - whites = np.ones(ocean.rgbs.shape) - thick_alphas = alphas.repeat(3).reshape((len(alphas), 3)) - ocean.rgbs = interpolate(ocean.rgbs, whites, thick_alphas) - self.ocean_states.append(ocean) - self.ocean = self.ocean_states[1].copy() \ No newline at end of file diff --git a/brachistochrone/misc.py b/brachistochrone/misc.py new file mode 100644 index 00000000..49e2f502 --- /dev/null +++ b/brachistochrone/misc.py @@ -0,0 +1,83 @@ +import numpy as np +import itertools as it + +from helpers import * + +from mobject.tex_mobject import TexMobject, TextMobject, Brace +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from topics.three_dimensions import Stars + +from animation import Animation +from animation.transform import \ + Transform, CounterclockwiseTransform, ApplyPointwiseFunction,\ + FadeIn, FadeOut, GrowFromCenter, ApplyFunction, ApplyMethod, \ + ShimmerIn +from animation.simple_animations import \ + ShowCreation, Homotopy, PhaseFlow, ApplyToCenters, DelayByOrder, \ + ShowPassingFlash +from animation.playground import TurnInsideOut, Vibrate +from topics.geometry import \ + Line, Circle, Square, Grid, Rectangle, Arrow, Dot, Point, \ + Arc, FilledRectangle +from topics.characters import Randolph, Mathematician +from topics.functions import ParametricFunction, FunctionGraph +from topics.number_line import NumberLine, NumberPlane +from mobject.region import Region, region_from_polygon_vertices +from scene import Scene + + +class PhysicalIntuition(Scene): + def construct(self): + n_terms = 4 + def func((x, y, ignore)): + z = complex(x, y) + if (np.abs(x%1 - 0.5)<0.01 and y < 0.01) or np.abs(z)<0.01: + return ORIGIN + out_z = 1./(2*np.tan(np.pi*z)*(z**2)) + return out_z.real*RIGHT - out_z.imag*UP + arrows = Mobject(*[ + Arrow(ORIGIN, np.sqrt(2)*point) + for point in compass_directions(4, RIGHT+UP) + ]) + arrows.highlight(YELLOW) + arrows.ingest_sub_mobjects() + all_arrows = Mobject(*[ + arrows.copy().scale(0.3/(x)).shift(x*RIGHT) + for x in range(1, n_terms+2) + ]) + terms = TexMobject([ + "\\dfrac{1}{%d^2} + "%(x+1) + for x in range(n_terms) + ]+["\\cdots"]) + terms.shift(2*UP) + plane = NumberPlane(color = BLUE_E) + axes = Mobject(NumberLine(), NumberLine().rotate(np.pi/2)) + axes.highlight(WHITE) + + for term in terms.split(): + self.play(ShimmerIn(term, run_time = 0.5)) + self.dither() + self.play(ShowCreation(plane), ShowCreation(axes)) + self.play(*[ + Transform(*pair) + for pair in zip(terms.split(), all_arrows.split()) + ]) + self.play(PhaseFlow( + func, plane, + run_time = 5, + virtual_time = 8 + )) + + + + + + + + + + + + + diff --git a/brachistochrone/wordplay.py b/brachistochrone/wordplay.py index aedc466e..f70fd541 100644 --- a/brachistochrone/wordplay.py +++ b/brachistochrone/wordplay.py @@ -7,7 +7,7 @@ from helpers import * from mobject.tex_mobject import TexMobject, TextMobject, Brace from mobject import Mobject from mobject.image_mobject import \ - MobjectFromRegion, ImageMobject, MobjectFromPixelArray + ImageMobject, MobjectFromPixelArray from topics.three_dimensions import Stars from animation import Animation @@ -100,17 +100,43 @@ class IntroduceSteve(Scene): cornell.replace(sample_size) cornell.next_to(name, DOWN) - Mobject(*logos+books+[cornell]).show() - self.add(name) self.play(FadeIn(cornell)) - self.play(ShimmerIn(contributions)) - for logo in logos: - self.play(FadeIn(logo)) self.play(ShimmerIn(books_word)) for book in books: book.shift(5*LEFT) self.play(ApplyMethod(book.shift, 5*RIGHT)) + self.play(ShimmerIn(contributions)) + for logo in logos: + self.play(FadeIn(logo)) + self.dither() + +class ShowTweets(Scene): + def construct(self): + tweets = [ + ImageMobject("tweet%d"%x, invert = False) + for x in range(1, 4) + ] + for tweet in tweets: + tweet.scale(0.4) + tweets[0].to_corner(UP+LEFT) + tweets[1].next_to(tweets[0], RIGHT, aligned_edge = UP) + tweets[2].next_to(tweets[1], DOWN) + + self.play(GrowFromCenter(tweets[0])) + for x in 1, 2: + self.play( + Transform(Point(tweets[x-1].get_center()), tweets[x]), + Animation(tweets[x-1]) + ) + self.dither() + +class LetsBeHonest(Scene): + def construct(self): + self.play(ShimmerIn(TextMobject(""" + Let's be honest about who benefits + from this collaboration... + """))) self.dither() @@ -201,6 +227,85 @@ class DisectBrachistochroneWord(Scene): ) self.dither() +class OneSolutionTwoInsights(Scene): + def construct(self): + one_solution = TextMobject(["One ", "solution"]) + two_insights = TextMobject(["Two ", " insights"]) + two, insights = two_insights.split() + johann = ImageMobject("Johann_Bernoulli2", invert = False) + mark = ImageMobject("Mark_Levi", invert = False) + for mob in johann, mark: + mob.scale(0.4) + johann.next_to(insights, LEFT) + mark.next_to(johann, RIGHT) + name = TextMobject("Mark Levi").to_edge(UP) + + self.play(*map(ShimmerIn, one_solution.split())) + self.dither() + for pair in zip(one_solution.split(), two_insights.split()): + self.play(Transform(*pair, path_func = path_along_arc(np.pi))) + self.dither() + self.clear() + self.add(two, insights) + for word, man in [(two, johann), (insights, mark)]: + self.play( + Transform(word, Point(word.get_left())), + GrowFromCenter(man) + ) + self.dither() + self.clear() + self.play(ApplyMethod(mark.center)) + self.play(ShimmerIn(name)) + self.dither() + +class CircleOfIdeas(Scene): + def construct(self): + words = map(TextMobject, [ + "optics", "calculus", "mechanics", "geometry", "history" + ]) + words[0].highlight(YELLOW) + words[1].highlight(BLUE_D) + words[2].highlight(GREY) + words[3].highlight(GREEN) + words[4].highlight(MAROON) + brachistochrone = TextMobject("Brachistochrone") + displayed_words = [] + for word in words: + anims = self.get_spinning_anims(displayed_words) + word.shift(3*RIGHT) + point = Point() + anims.append(Transform(point, word)) + self.play(*anims) + self.remove(point) + self.add(word) + displayed_words.append(word) + self.play(*self.get_spinning_anims(displayed_words)) + self.play(*[ + Transform( + word, word.copy().highlight(BLACK).center().scale(0.1), + path_func = path_along_arc(np.pi), + rate_func = None, + run_time = 2 + ) + for word in displayed_words + ]+[ + GrowFromCenter(brachistochrone) + ]) + self.dither() + + def get_spinning_anims(self, words, angle = np.pi/6): + anims = [] + for word in words: + old_center = word.get_center() + new_center = rotate_vector(old_center, angle) + vect = new_center-old_center + anims.append(ApplyMethod( + word.shift, vect, + path_func = path_along_arc(angle), + rate_func = None + )) + return anims + class FermatsPrincipleStatement(Scene): def construct(self): @@ -299,6 +404,10 @@ class VideoProgression(Scene): +class BalanceCompetingFactors(Scene): + def construct(self): + factor1 = TextMobject("Factor 1") + factor2 = TextMobject("Factor 2") diff --git a/camera.py b/camera.py index 18f3e775..ddc0b52c 100644 --- a/camera.py +++ b/camera.py @@ -151,10 +151,9 @@ class Camera(object): ]) def adjusted_thickness(self, thickness): - # big_shape = PRODUCTION_QUALITY_DISPLAY_CONFIG["pixel_shape"] - # factor = sum(big_shape)/sum(self.pixel_shape) - # return 1 + (thickness-1)/factor - return thickness + big_shape = PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_shape"] + factor = sum(big_shape)/sum(self.pixel_shape) + return 1 + (thickness-1)/factor def get_thickening_nudges(self, thickness): _range = range(-thickness/2+1, thickness/2+1) diff --git a/constants.py b/constants.py index 4b1ba6ff..dfd12186 100644 --- a/constants.py +++ b/constants.py @@ -6,15 +6,15 @@ DEFAULT_WIDTH = 2560 DEFAULT_FRAME_DURATION = 0.04 #There might be other configuration than pixel_shape later... -PRODUCTION_QUALITY_DISPLAY_CONFIG = { +PRODUCTION_QUALITY_CAMERA_CONFIG = { "pixel_shape" : (DEFAULT_HEIGHT, DEFAULT_WIDTH), } -MEDIUM_QUALITY_DISPLAY_CONFIG = { +MEDIUM_QUALITY_CAMERA_CONFIG = { "pixel_shape" : (720, 1280), } -LOW_QUALITY_DISPLAY_CONFIG = { +LOW_QUALITY_CAMERA_CONFIG = { "pixel_shape" : (576, 1024), } diff --git a/extract_scene.py b/extract_scene.py index c1cd580e..f737fb80 100644 --- a/extract_scene.py +++ b/extract_scene.py @@ -49,7 +49,7 @@ def get_configuration(sys_argv): "file" : None, "scene_name" : "", "args_extension" : "", - "display_config" : PRODUCTION_QUALITY_DISPLAY_CONFIG, + "camera_config" : PRODUCTION_QUALITY_CAMERA_CONFIG, "preview" : False, "write" : False, "save_image" : False, @@ -61,11 +61,11 @@ def get_configuration(sys_argv): print HELP_MESSAGE return elif opt == '-l': - config["display_config"] = LOW_QUALITY_DISPLAY_CONFIG + config["camera_config"] = LOW_QUALITY_CAMERA_CONFIG elif opt == '-m': - config["display_config"] = MEDIUM_QUALITY_DISPLAY_CONFIG + config["camera_config"] = MEDIUM_QUALITY_CAMERA_CONFIG elif opt == '-p': - config["display_config"] = LOW_QUALITY_DISPLAY_CONFIG + config["camera_config"] = LOW_QUALITY_CAMERA_CONFIG config["preview"] = True elif opt == '-w': config["write"] = True @@ -196,7 +196,7 @@ def main(): ) config["movie_prefix"] = config["file"].replace(".py", "") scene_kwargs = { - "camera" : Camera(**config["display_config"]) + "camera_config" : config["camera_config"] } for SceneClass in get_scene_classes(scene_names_to_classes, config): for args in get_scene_args(SceneClass, config): diff --git a/fluid_flow.py b/fluid_flow.py index 90d1e0eb..ea45d729 100644 --- a/fluid_flow.py +++ b/fluid_flow.py @@ -1,5 +1,4 @@ from mobject import Mobject -from mobject.image_mobject import MobjectFromRegion from mobject.tex_mobject import TextMobject from mobject.region import region_from_polygon_vertices from topics.geometry import Arrow, Dot, Circle, Line, FilledRectangle @@ -21,6 +20,7 @@ class FluidFlow(Scene): "dot_color" : BLUE_C, "text_color" : WHITE, "arrow_color" : GREEN_A, + "arrow_length" : 0.5, "points_height" : SPACE_HEIGHT, "points_width" : SPACE_WIDTH, } @@ -59,7 +59,7 @@ class FluidFlow(Scene): ) angles = map(angle_of_vector, map(self.function, points)) prototype = Arrow( - ORIGIN, RIGHT*self.arrow_spacing/2., + ORIGIN, RIGHT*self.arrow_length, color = self.arrow_color, tip_length = 0.1, buff = 0 @@ -112,6 +112,23 @@ class FluidFlow(Scene): self.remove(mob, rectangle) +class FluxArticleExample(FluidFlow): + CONFIG = { + "arrow_length" : 0.4, + "arrow_color" : BLUE_D, + "points_height" : SPACE_HEIGHT, + "points_width" : SPACE_WIDTH, + } + def construct(self): + self.use_function( + lambda (x, y, z) : (x**2+y**2)*((np.sin(x)**2)*RIGHT + np.cos(y)*UP) + ) + # self.add_plane() + self.add_arrows() + self.show_frame() + self.add_dots() + self.flow(run_time = 2, virtual_time = 0.1) + self.dither(2) class NegativeDivergenceExamlpe(FluidFlow): CONFIG = { diff --git a/mobject/image_mobject.py b/mobject/image_mobject.py index 98848950..4641471c 100644 --- a/mobject/image_mobject.py +++ b/mobject/image_mobject.py @@ -6,7 +6,6 @@ from random import random from helpers import * from mobject import Mobject -import displayer as disp class ImageMobject(Mobject): """ @@ -110,14 +109,4 @@ class MobjectFromPixelArray(ImageMobject): -class MobjectFromRegion(MobjectFromPixelArray): - def __init__(self, region, color = None, **kwargs): - MobjectFromPixelArray.__init__( - self, - disp.paint_region(region, color = color), - **kwargs - ) - - - diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index 8fc1062d..b2efc14a 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -83,7 +83,7 @@ def generate_tex_file(expression, size, template_tex_file): expression = tex_expression_list_as_string(expression) result = os.path.join(TEX_DIR, tex_hash(expression, size))+".tex" if not os.path.exists(result): - print "Writing %s at size %s to %s"%( + print "Writing \"%s\" at size %s to %s"%( "".join(expression), size, result ) with open(template_tex_file, "r") as infile: diff --git a/scene/scene.py b/scene/scene.py index 870c381d..00ece6bd 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -18,14 +18,13 @@ from mobject import Mobject class Scene(object): CONFIG = { - "camera" : None, + "camera_config" : {}, "frame_duration" : DEFAULT_FRAME_DURATION, "construct_args" : [], } def __init__(self, **kwargs): digest_config(self, kwargs) - if not self.camera: - self.camera = Camera() + self.camera = Camera(**self.camera_config) self.frames = [] self.mobjects = [] self.num_animations = 0 @@ -45,12 +44,22 @@ class Scene(object): self.name = name return self + ### Only these methods should touch the camera + def set_camera(self, camera): self.camera = camera def get_frame(self): return self.camera.get_image() + def update_frame(self, mobjects, background = None, **kwargs): + if background is not None: + self.camera.set_image(background) + else: + self.camera.reset() + self.camera.capture_mobjects(mobjects, **kwargs) + ### + def add(self, *mobjects): """ Mobjects will be displayed, from background to foreground, @@ -60,7 +69,6 @@ class Scene(object): raise Exception("Adding something which is not a mobject") old_mobjects = filter(lambda m : m not in mobjects, self.mobjects) self.mobjects = old_mobjects + list(mobjects) - self.camera.capture_mobjects(mobjects) return self def add_mobjects_among(self, values): @@ -79,7 +87,6 @@ class Scene(object): if len(mobjects) == 0: return self.mobjects = filter(lambda m : m not in mobjects, self.mobjects) - self.repaint_mojects() return self def bring_to_front(self, mobject): @@ -95,11 +102,6 @@ class Scene(object): self.mobjects = [] return self - def repaint_mojects(self): - self.camera.reset() - self.camera.capture_mobjects(self.mobjects) - return self - def align_run_times(self, *animations, **kwargs): if "run_time" in kwargs: run_time = kwargs["run_time"] @@ -132,13 +134,6 @@ class Scene(object): ])) return time_progression - def update_frame(self, moving_mobjects, static_image = None): - if static_image is not None: - self.camera.set_image(static_image) - else: - self.camera.reset() - self.camera.capture_mobjects(moving_mobjects) - def play(self, *animations, **kwargs): if len(animations) == 0: @@ -149,12 +144,11 @@ class Scene(object): animations = self.align_run_times(*animations, **kwargs) moving_mobjects, static_mobjects = \ self.separate_moving_and_static_mobjects(*animations) - self.camera.reset() - self.camera.capture_mobjects( + self.update_frame( static_mobjects, include_sub_mobjects = False ) - static_image = self.camera.get_image() + static_image = self.get_frame() for t in self.get_time_progression(animations): for animation in animations: @@ -183,15 +177,15 @@ class Scene(object): for animation in animations: animation.update((t-t0)/(t1 - t0)) index = int(t/self.frame_duration) - self.camera.set_image(self.frames[index]) - self.camera.capture_mobjects(moving_mobjects) - self.frames[index] = self.camera.get_image() + self.update_frame(moving_mobjects, self.frames[index]) + self.frames[index] = self.get_frame() for animation in animations: animation.clean_up() self.repaint_mojects() return self def dither(self, duration = DEFAULT_DITHER_TIME): + self.update_frame(self.mobjects) self.frames += [self.get_frame()]*int(duration / self.frame_duration) return self diff --git a/scene/test.py b/scene/test.py new file mode 100644 index 00000000..f893b475 --- /dev/null +++ b/scene/test.py @@ -0,0 +1,58 @@ +import numpy as np +import random + +class CowProblem(): + def __init__(self): + self.reset() + self.directions = [ + (1, 0), + (1, 1), + (0, 1), + (-1, 1), + (-1, 0), + (-1, -1), + (0, -1), + (1, -1), + ] + + def reset(self): + self.field = np.ones((11, 11), dtype = 'bool') + self.cow_x = 0 + self.cow_y = 0 + + def step(self): + valid_step = False + while not valid_step: + step_x, step_y = random.choice(self.directions) + if self.cow_x + step_x > 0 and self.cow_x + step_x < 11 and self.cow_y + step_y > 0 and self.cow_y + step_y < 11: + valid_step = True + self.cow_x += step_x + self.cow_y += step_y + self.field[self.cow_x, self.cow_y] = False + + def total_grass_after_n_steps(self, n): + for x in range(n): + self.step() + return sum(sum(self.field)) + + def num_steps_for_half_eaten(self): + result = 0 + while sum(sum(self.field)) > 121/2: + self.step() + result += 1 + return result + + def average_number_of_steps_for_half_eaten(self, sample_size): + run_times = [] + for x in range(sample_size): + run_times.append( + self.num_steps_for_half_eaten() + ) + self.reset() + return np.mean(run_times) + + + + + + diff --git a/scene/zoomed_scene.py b/scene/zoomed_scene.py index ab064652..b4ed94f8 100644 --- a/scene/zoomed_scene.py +++ b/scene/zoomed_scene.py @@ -30,6 +30,9 @@ class ZoomedScene(Scene): def get_zoomed_camera_mobject(self): return self.little_rectangle + def get_zoomed_screen(self): + return self.big_rectangle + def generate_big_rectangle(self): height, width = self.zoomed_canvas_space_shape self.big_rectangle = Rectangle( @@ -67,6 +70,8 @@ class ZoomedScene(Scene): background = self.zoomed_camera_background ) self.add(self.little_rectangle) + #TODO, is there a better way to hanld this? + self.zoomed_camera.adjusted_thickness = lambda x : x def get_frame(self): frame = Scene.get_frame(self) diff --git a/topics/geometry.py b/topics/geometry.py index 3b1681dc..88d8db62 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -246,10 +246,13 @@ class Square(Rectangle): "side_length" : 2.0, } def __init__(self, **kwargs): - digest_config(self, kwargs) - for arg in ["height", "width"]: - kwargs[arg] = self.side_length - Rectangle.__init__(self, **kwargs) + side_length = kwargs.pop("side_length") + Rectangle.__init__( + self, + height = side_length, + width = side_length, + **kwargs + ) @@ -262,8 +265,8 @@ class FilledRectangle(Mobject1D): def generate_points(self): self.add_points([ (x, y, 0) - for x in np.arange(-self.width, self.width, self.epsilon) - for y in np.arange(-self.height, self.height, self.epsilon) + for x in np.arange(-self.width/2, self.width/2, self.epsilon) + for y in np.arange(-self.height/2, self.height/2, self.epsilon) ])