diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 36f959f7..dacffdb1 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -52,7 +52,7 @@ class Rotating(Animation): alpha_func = alpha_func, *args, **kwargs ) - self.axes = [axis] if axis else axes + self.axes = [axis] if axis is not None else axes self.radians = radians def update_mobject(self, alpha): diff --git a/animation/transform.py b/animation/transform.py index f3fd2b93..79198445 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -39,7 +39,7 @@ class Transform(Animation): self.interpolation_function = interpolation_function count1, count2 = mobject1.get_num_points(), mobject2.get_num_points() if count2 == 0: - mobject2 = Point((SPACE_WIDTH, SPACE_HEIGHT, 0)) + mobject2.add_points([(SPACE_WIDTH, SPACE_HEIGHT, 0)]) count2 = mobject2.get_num_points() Mobject.align_data(mobject1, mobject2) Animation.__init__(self, mobject1, run_time = run_time, *args, **kwargs) diff --git a/mobject/image_mobject.py b/mobject/image_mobject.py index 6a881f59..e03f1db2 100644 --- a/mobject/image_mobject.py +++ b/mobject/image_mobject.py @@ -16,12 +16,14 @@ class ImageMobject(Mobject2D): image_file, filter_color = "black", invert = True, + use_cache = True, *args, **kwargs): Mobject2D.__init__(self, *args, **kwargs) self.filter_rgb = 255 * np.array(Color(filter_color).get_rgb()).astype('uint8') self.name = to_cammel_case( os.path.split(image_file)[-1].split(".")[0] ) + self.use_cache = use_cache possible_paths = [ image_file, os.path.join(IMAGE_DIR, image_file), @@ -35,7 +37,7 @@ class ImageMobject(Mobject2D): raise IOError("File not Found") def generate_points_from_file(self, path, invert): - if self.read_in_cached_attrs(path, invert): + if self.use_cache and self.read_in_cached_attrs(path, invert): return image = Image.open(path).convert('RGB') if invert: diff --git a/mobject/mobject.py b/mobject/mobject.py index f4efc411..b0fccccc 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -32,13 +32,16 @@ class Mobject(object): if not hasattr(self, "name"): self.name = name or self.__class__.__name__ self.has_normals = hasattr(self, 'unit_normal') + self.init_points() + self.generate_points() + if center: + self.center().shift(center) + + def init_points(self): self.points = np.zeros((0, 3)) self.rgbs = np.zeros((0, 3)) if self.has_normals: self.unit_normals = np.zeros((0, 3)) - self.generate_points() - if center: - self.center().shift(center) def __str__(self): return self.name @@ -79,6 +82,7 @@ class Mobject(object): self.add_points(mobject.points, mobject.rgbs) return self + def repeat(self, count): #Can make transition animations nicer points, rgbs = deepcopy(self.points), deepcopy(self.rgbs) @@ -86,6 +90,13 @@ class Mobject(object): self.add_points(points, rgbs) return self + def do_in_place(self, method, *args, **kwargs): + center = self.get_center() + self.shift(-center) + method(*args, **kwargs) + self.shift(center) + return self + def rotate(self, angle, axis = OUT): t_rotation_matrix = np.transpose(rotation_matrix(angle, axis)) self.points = np.dot(self.points, t_rotation_matrix) @@ -94,10 +105,7 @@ class Mobject(object): return self def rotate_in_place(self, angle, axis = OUT): - center = self.get_center() - self.shift(-center) - self.rotate(angle, axis) - self.shift(center) + self.do_in_place(self.rotate, angle, axis) return self def shift(self, vector): @@ -144,13 +152,21 @@ class Mobject(object): self.shift(shift_val) return self + def next_to(self, mobject, direction = RIGHT, buff = EDGE_BUFFER): + self.shift( + mobject.get_edge_center(direction) - \ + self.get_edge_center(-direction) + \ + buff * direction + ) + return self + def scale(self, scale_factor): self.points *= scale_factor return self def scale_in_place(self, scale_factor): - center = self.get_center() - return self.center().scale(scale_factor).shift(center) + self.do_in_place(self.scale, scale_factor) + return self def stretch(self, factor, dim): self.points[:,dim] *= factor diff --git a/mobject/three_dimensional_mobjects.py b/mobject/three_dimensional_mobjects.py index 4382c5f9..66d43e53 100644 --- a/mobject/three_dimensional_mobjects.py +++ b/mobject/three_dimensional_mobjects.py @@ -9,9 +9,12 @@ from helpers import * class Stars(Mobject): DEFAULT_COLOR = "white" SHOULD_BUFF_POINTS = False - def __init__(self, num_points = DEFAULT_NUM_STARS, + def __init__(self, + radius = SPACE_WIDTH, + num_points = DEFAULT_NUM_STARS, *args, **kwargs): self.num_points = num_points + self.radius = radius Mobject.__init__(self, *args, **kwargs) def generate_points(self): @@ -23,7 +26,7 @@ class Stars(Mobject): ) for x in range(self.num_points) for r, phi, theta in [[ - max(SPACE_HEIGHT, SPACE_WIDTH) * random(), + self.radius * random(), np.pi * random(), 2 * np.pi * random(), ]] diff --git a/sample_script.py b/sample_script.py index 23fcf79c..aff80a97 100644 --- a/sample_script.py +++ b/sample_script.py @@ -10,29 +10,13 @@ from animation import * from mobject import * from constants import * from region import * -from scene import Scene, SceneFromVideo +from scene import Scene from script_wrapper import command_line_create_scene -class SampleScene(SceneFromVideo): +class SampleScene(Scene): def construct(self): - path = os.path.join(MOVIE_DIR, "EdgeDetectedCountingInBinary35-75.mp4") - SceneFromVideo.construct(self, path, time_range = (3, 5)) - self.apply_gaussian_blur(sigmaX = 10) - self.make_all_black_or_white() - self.name = "BlurEdgeBlurBold" - - # self.animate_over_time_range( - # 0, 3, - # ApplyMethod(Dot().to_edge(LEFT).to_edge, RIGHT) - # ) - - def make_all_black_or_white(self): - self.frames = [ - 255*(frame != 0).astype('uint8') - for frame in self.frames - ] - + pass if __name__ == "__main__": command_line_create_scene() \ No newline at end of file diff --git a/scripts/music_and_measure.py b/scripts/music_and_measure.py new file mode 100644 index 00000000..d2fe8e7b --- /dev/null +++ b/scripts/music_and_measure.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python + +import numpy as np +import itertools as it +from copy import deepcopy +import sys +from fractions import Fraction, gcd + +from animation import * +from mobject import * +from constants import * +from region import * +from scene import Scene +from script_wrapper import command_line_create_scene + +import random + +MOVIE_PREFIX = "music_and_measure/" + +INTERVAL_RADIUS = 6 +NUM_INTERVAL_TICKS = 16 +TICK_STRETCH_FACTOR = 4 +INTERVAL_COLOR_PALETTE = [ + "yellow", + "green", + "skyblue", + "#AD1457", + "#6A1B9A", + "#26C6DA", + "#FF8F00", +] + +def rationals(): + curr = Fraction(1, 2) + numerator, denominator = 1, 2 + while True: + yield curr + if curr.numerator < curr.denominator - 1: + new_numerator = curr.numerator + 1 + while gcd(new_numerator, curr.denominator) != 1: + new_numerator += 1 + curr = Fraction(new_numerator, curr.denominator) + else: + curr = Fraction(1, curr.denominator + 1) + +def fraction_mobject(fraction): + n, d = fraction.numerator, fraction.denominator + return tex_mobject("\\frac{%d}{%d}"%(n, d)) + + +def zero_to_one_interval(): + interval = NumberLine( + radius = INTERVAL_RADIUS, + interval_size = 2.0*INTERVAL_RADIUS/NUM_INTERVAL_TICKS + ) + interval.elongate_tick_at(-INTERVAL_RADIUS, TICK_STRETCH_FACTOR) + interval.elongate_tick_at(INTERVAL_RADIUS, TICK_STRETCH_FACTOR) + interval.add(tex_mobject("0").shift(INTERVAL_RADIUS*LEFT+DOWN)) + interval.add(tex_mobject("1").shift(INTERVAL_RADIUS*RIGHT+DOWN)) + return interval + +class OpenInterval(Mobject): + def __init__(self, center = ORIGIN, width = 2, **kwargs): + Mobject.__init__(self, **kwargs) + self.add(tex_mobject("(").shift(LEFT)) + self.add(tex_mobject(")").shift(RIGHT)) + scale_factor = width / 2.0 + self.stretch(scale_factor, 0) + self.stretch(0.5+0.5*scale_factor, 1) + self.shift(center) + +class VibratingString(Animation): + def __init__(self, + num_periods = 1, + overtones = 4, + amplitude = 0.5, + radius = INTERVAL_RADIUS, + center = ORIGIN, + color = "white", + run_time = 3.0, + alpha_func = None, + **kwargs): + self.radius = radius + self.center = center + def func(x, t): + return sum([ + (amplitude/((k+1)**2))*np.sin(2*mult*t)*np.sin(k*mult*x) + for k in range(overtones) + for mult in [(num_periods+k)*np.pi] + ]) + self.func = func + kwargs["run_time"] = run_time + kwargs["alpha_func"] = alpha_func + Animation.__init__(self, Mobject1D(color = color), **kwargs) + + def update_mobject(self, alpha): + self.mobject.init_points() + self.mobject.add_points([ + [x*self.radius, self.func(x, alpha*self.run_time), 0] + for x in np.arange(-1, 1, self.mobject.epsilon/self.radius) + ]) + self.mobject.shift(self.center) + + +class IntervalScene(Scene): + def construct(self): + self.interval = zero_to_one_interval() + self.add(self.interval) + + def show_all_fractions(self, + num_fractions = 27, + pause_time = 1.0, + remove_as_you_go = True): + shrink = not remove_as_you_go + for fraction, count in zip(rationals(), range(num_fractions)): + frac_mob, tick = self.add_fraction(fraction, shrink) + self.dither(pause_time) + if remove_as_you_go: + self.remove(frac_mob, tick) + + def add_fraction(self, fraction, shrink = False): + point = self.num_to_point(fraction) + tick_rad = self.interval.tick_size*TICK_STRETCH_FACTOR + frac_mob = fraction_mobject(fraction) + if shrink: + scale_factor = 2.0/fraction.denominator + frac_mob.scale(scale_factor) + tick_rad *= scale_factor + frac_mob.shift(point + frac_mob.get_height()*UP) + tick = Line(point + DOWN*tick_rad, point + UP*tick_rad) + tick.highlight("yellow") + self.add(frac_mob, tick) + return frac_mob, tick + + def cover_fractions(self, + epsilon = 0.3, + num_fractions = 9, + run_time_per_interval = 0.5): + for fraction, count in zip(rationals(), range(num_fractions)): + self.add_open_interval( + fraction, + epsilon / 2**(count+1), + run_time = run_time_per_interval + ) + + def add_open_interval(self, num, width, color = None, run_time = 0): + width *= 2*self.interval.radius + center_point = self.num_to_point(num) + open_interval = OpenInterval(center_point, width) + if color: + open_interval.highlight(color) + interval_line = Line(open_interval.get_left(), open_interval.get_right()) + interval_line.scale_in_place(0.9)#Silliness + interval_line.do_in_place(interval_line.sort_points, np.linalg.norm) + interval_line.highlight("yellow") + if run_time > 0: + squished_interval = deepcopy(open_interval).stretch_to_fit_width(0) + self.animate( + Transform(squished_interval, open_interval), + ShowCreation(interval_line), + run_time = run_time + ) + self.remove(squished_interval) + self.add(open_interval, interval_line) + return open_interval, interval_line + + def num_to_point(self, num): + assert(num <= 1 and num >= 0) + radius = self.interval.radius + return (num*2*radius - radius)*RIGHT + + +class TwoChallenges(Scene): + def construct(self): + two_challenges = text_mobject("Two Challenges", size = "\\Huge").to_edge(UP) + one, two = map(text_mobject, ["1.", "2."]) + one.shift(UP).to_edge(LEFT) + two.shift(DOWN).to_edge(LEFT) + notes = ImageMobject("musical_notes").scale(0.3) + notes.next_to(one) + notes.highlight("blue") + measure = text_mobject("Measure Theory").next_to(two) + probability = text_mobject("Probability") + probability.next_to(measure).shift(DOWN+RIGHT) + integration = tex_mobject("\\int") + integration.next_to(measure).shift(UP+RIGHT) + arrow_to_prob = Arrow(measure, probability) + arrow_to_int = Arrow(measure, integration) + for arrow in arrow_to_prob, arrow_to_int: + arrow.highlight("yellow") + + + self.add(two_challenges) + self.dither() + self.add(one, notes) + self.dither() + self.add(two, measure) + self.dither() + self.animate(ShowCreation(arrow_to_int)) + self.add(integration) + self.dither() + self.animate(ShowCreation(arrow_to_prob)) + self.add(probability) + self.dither() + +class MeasureTheoryToHarmony(IntervalScene): + def construct(self): + IntervalScene.construct(self) + self.cover_fractions() + self.dither() + all_mobs = CompoundMobject(*self.mobjects) + all_mobs.sort_points() + self.clear() + radius = self.interval.radius + line = Line(radius*LEFT, radius*RIGHT).highlight("white") + self.animate(DelayByOrder(Transform(all_mobs, line))) + self.clear() + self.animate(VibratingString(alpha_func = smooth)) + self.clear() + self.add(line) + self.dither() + + +class ChallengeOne(Scene): + def construct(self): + title = text_mobject("Challenge #1").to_edge(UP) + bottom_vibration = VibratingString( + num_periods = 1, run_time = 1.0, + center = DOWN, color = "blue" + ) + top_vibration = VibratingString( + num_periods = 2, run_time = 1.0, + center = 2*UP, color = "lightgreen" + ) + freq_num1 = text_mobject("220 Hz") + freq_num2 = text_mobject("$r\\times$220 Hz") + freq_num1.shift(1.5*DOWN) + freq_num2.shift(1.5*UP) + r_constraint = tex_mobject("1