diff --git a/animation.py b/animation.py index 5a8e3f82..748ad68c 100644 --- a/animation.py +++ b/animation.py @@ -11,7 +11,7 @@ from images2gif import writeGif from helpers import * from constants import * -from mobject import Mobject +from mobject import Mobject, Point class Animation(object): def __init__(self, @@ -179,6 +179,9 @@ class Transform(Animation): run_time = DEFAULT_TRANSFORM_RUN_TIME, *args, **kwargs): count1, count2 = mobject1.get_num_points(), mobject2.get_num_points() + if count2 == 0: + mobject2 = Point((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) self.ending_mobject = mobject2 @@ -189,11 +192,13 @@ class Transform(Animation): if count2 < count1: #Ensure redundant pixels fade to black - indices = self.non_redundant_m2_indices = \ - np.arange(0, count1-1, float(count1) / count2).astype('int') + indices = np.arange( + 0, count1-1, float(count1) / count2 + ).astype('int') temp = np.zeros(mobject2.points.shape) temp[indices] = mobject2.rgbs[indices] mobject2.rgbs = temp + self.non_redundant_m2_indices = indices def update_mobject(self, alpha): Mobject.interpolate( @@ -222,6 +227,17 @@ class FadeToColor(Transform): target = copy.deepcopy(mobject).highlight(color) Transform.__init__(self, mobject, target, *args, **kwargs) +class Highlight(FadeToColor): + def __init__(self, mobject, color = "red", + run_time = DEFAULT_ANIMATION_RUN_TIME, + alpha_func = there_and_back, *args, **kwargs): + FadeToColor.__init__( + self, mobject, color, + run_time = run_time, + alpha_func = alpha_func, + *args, **kwargs + ) + class ScaleInPlace(Transform): def __init__(self, mobject, scale_factor, *args, **kwargs): target = copy.deepcopy(mobject) diff --git a/constants.py b/constants.py index 8b725318..9729190c 100644 --- a/constants.py +++ b/constants.py @@ -1,7 +1,7 @@ import os PRODUCTION_QUALITY = True -GENERALLY_BUFF_POINTS = True +GENERALLY_BUFF_POINTS = PRODUCTION_QUALITY DEFAULT_POINT_DENSITY_2D = 25 #if PRODUCTION_QUALITY else 20 DEFAULT_POINT_DENSITY_1D = 150 #if PRODUCTION_QUALITY else 50 diff --git a/helpers.py b/helpers.py index fd060610..397a083e 100644 --- a/helpers.py +++ b/helpers.py @@ -59,6 +59,8 @@ def make_even_by_cycling(iterable_1, iterable_2): def sigmoid(x): return 1.0/(1 + np.exp(-x)) +### Alpha Functions ### + def high_inflection_0_to_1(t, inflection = 10.0): error = sigmoid(-inflection / 2) return (sigmoid(inflection*(t - 0.5)) - error) / (1 - 2*error) @@ -67,6 +69,10 @@ def there_and_back(t, inflection = 10.0): new_t = 2*t if t < 0.5 else 2*(1 - t) return high_inflection_0_to_1(new_t, inflection) +def not_quite_there(t, proportion = 0.7): + return proportion*high_inflection_0_to_1(t) + +### Functional Functions ### def composition(func_list): """ @@ -124,8 +130,7 @@ def z_to_vector(vector): return np.dot(rotation_about_z(theta), phi_down) def rotate_vector(vector, angle, axis): - #Slightly hacky, changes vector in place - vector[:3] = np.dot(rotation_matrix(angle, axis), vector) + return np.dot(rotation_matrix(angle, axis), vector) def angle_between(v1, v2): return np.arccos(np.dot( diff --git a/image_mobject.py b/image_mobject.py index b3cdd072..c0e419bc 100644 --- a/image_mobject.py +++ b/image_mobject.py @@ -86,7 +86,7 @@ class VideoIcon(ImageMobject): ImageMobject.__init__(self, "video_icon", *args, **kwargs) self.scale(0.3) - +#Purely redundant function to make singulars and plurals sensible def tex_mobject(expression, size = "\HUGE"): return tex_mobjects(expression, size) diff --git a/mobject.py b/mobject.py index 812ddcc1..9a496b79 100644 --- a/mobject.py +++ b/mobject.py @@ -89,13 +89,14 @@ class Mobject(object): self.shift(-self.get_center()) return self - def get_center(self): - return np.apply_along_axis(np.mean, 0, self.points) - 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) + def add(self, *mobjects): for mobject in mobjects: self.add_points(mobject.points, mobject.rgbs) @@ -146,6 +147,17 @@ class Mobject(object): self.rgbs = self.rgbs[to_eliminate] return self + ### Getters ### + def get_center(self): + return np.apply_along_axis(np.mean, 0, self.points) + + def get_width(self): + return np.max(self.points[:, 0]) - np.min(self.points[:, 0]) + + def get_height(self): + return np.max(self.points[:, 1]) - np.min(self.points[:, 1]) + + ### Stuff subclasses should deal with ### def should_buffer_points(self): # potentially changed in subclasses return GENERALLY_BUFF_POINTS @@ -228,6 +240,7 @@ class Point(Mobject): self.rgbs = np.array(self.color.get_rgb()).reshape(1, 3) class Arrow(Mobject1D): + DEFAULT_COLOR = "white" NUNGE_DISTANCE = 0.1 def __init__(self, point = (0, 0, 0), direction = (-1, 1, 0), length = 1, tip_length = 0.25, @@ -246,7 +259,7 @@ class Arrow(Mobject1D): ]) tips_dir = np.array(-self.direction), np.array(-self.direction) for i, sgn in zip([0, 1], [-1, 1]): - rotate_vector(tips_dir[i], sgn * np.pi / 4, self.normal) + tips_dir[i] = rotate_vector(tips_dir[i], sgn * np.pi / 4, self.normal) self.add_points([ [x, x, x] * tips_dir[i] + self.point for x in np.arange(0, self.tip_length, self.epsilon) @@ -271,13 +284,13 @@ class Dot(Mobject1D): #Use 1D density, even though 2D raise Exception("Center must have 2 or 3 coordinates!") elif center.size == 2: center = np.append(center, [0]) - self.center = center + self.center_point = center self.radius = radius Mobject1D.__init__(self, *args, **kwargs) def generate_points(self): self.add_points([ - np.array((t*np.cos(theta), t*np.sin(theta), 0)) + self.center + np.array((t*np.cos(theta), t*np.sin(theta), 0)) + self.center_point for t in np.arange(0, self.radius, self.epsilon) for theta in np.arange(0, 2 * np.pi, self.epsilon) ]) @@ -305,6 +318,18 @@ class Line(Mobject1D): for t in np.arange(0, 1, self.epsilon) ]) +class CurvedLine(Line): + def generate_points(self): + equidistant_point = rotate_vector( + self.end - self.start, + np.pi/3, [0,0,1] + ) + self.start + self.add_points([ + (1 - t*(1-t))*(t*self.end + (1-t)*self.start) \ + + t*(1-t)*equidistant_point + for t in np.arange(0, 1, self.epsilon) + ]) + self.ep = equidistant_point class CubeWithFaces(Mobject2D): def generate_points(self): diff --git a/moser/graphs.py b/moser/graphs.py new file mode 100644 index 00000000..31190500 --- /dev/null +++ b/moser/graphs.py @@ -0,0 +1,112 @@ +import itertools as it +import numpy as np + +CUBE_GRAPH = { + "name" : "CubeGraph", + # 6 4 + # 20 + # 31 + # 7 5 + "vertices" : [ + (x, y, 0) + for r in (1, 2) + for x, y in it.product([-r,r], [-r, r]) + ], + "edges" : [ + (0, 1), + (0, 2), + (3, 1), + (3, 2), + (4, 5), + (4, 6), + (7, 5), + (7, 6), + (0, 4), + (1, 5), + (2, 6), + (3, 7), + ], + "region_cycles" : [ + [0, 2, 3, 1], + [4, 0, 1, 5], + [4, 6, 2, 0], + [6, 7, 3, 2], + [7, 5, 1, 3], + [4, 6, 7, 5],#By convention, last region will be "outside" + ] +} + +SAMPLE_GRAPH = { + "name" : "SampleGraph", + # 4 2 3 + # 0 1 + # + # 5 + "vertices" :[ + ( 0, 0, 0), + ( 2, 0, 0), + ( 1, 1, 0), + ( 3, 1, 0), + (-1, 1, 0), + (-2,-2, 0), + ], + "edges" : [ + (0, 1), + (1, 2), + (1, 3), + (3, 2), + (2, 4), + (4, 0), + (2, 0), + (4, 5), + (0, 5), + (1, 5), + ], + "region_cycles" : [ + (0, 1, 2), + (1, 3, 2), + (2, 4, 0), + (4, 5, 0), + (0, 5, 1), + (4, 5, 1, 3), + ] + +} + +OCTOHEDRON_GRAPH = { + "name" : "OctohedronGraph", + # 3 + # + # 1 0 + # 2 + #4 5 + "vertices" : [ + (r*np.cos(angle), r*np.sin(angle)-1, 0) + for r, s in [(1, 0), (3, 3)] + for angle in (np.pi/6) * np.array([s, 4 + s, 8 + s]) + ], + "edges" : [ + (0, 1), + (1, 2), + (2, 0), + (5, 0), + (0, 3), + (3, 5), + (3, 1), + (3, 4), + (1, 4), + (4, 2), + (4, 5), + (5, 2), + ], + "region_cycles" : [ + (0, 1, 2), + (0, 5, 3), + (3, 1, 0), + (3, 4, 1), + (1, 4, 2), + (2, 4, 5), + (5, 0, 2), + (3, 4, 5), + ] +} diff --git a/moser/intro.py b/moser/intro.py index db3ae295..b851c3a5 100644 --- a/moser/intro.py +++ b/moser/intro.py @@ -28,7 +28,7 @@ def logo_to_circle(): ) big_circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS) sc.add(small_circle) - sc.dither() + sc.dither()` sc.animate(Transform(small_circle, big_circle)) return sc diff --git a/moser/main.py b/moser/main.py index 134d413c..64595401 100644 --- a/moser/main.py +++ b/moser/main.py @@ -4,6 +4,7 @@ import numpy as np import itertools as it import operator as op from copy import deepcopy +from random import random from animation import * @@ -14,10 +15,14 @@ from region import * from scene import Scene from moser_helpers import * +from graphs import * RADIUS = SPACE_HEIGHT - 0.1 CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS +movie_prefix = "moser/" + +############################################ class CircleScene(Scene): def __init__(self, radians, *args, **kwargs): @@ -32,6 +37,34 @@ class CircleScene(Scene): self.lines = [Line(p1, p2) for p1, p2 in it.combinations(self.points, 2)] self.add(self.circle, *self.dots + self.lines) +class GraphScene(Scene): + #Note, the placement of vertices in this is pretty hard coded, be + #warned if you want to change it. + def __init__(self, graph, *args, **kwargs): + Scene.__init__(self, *args, **kwargs) + #See CUBE_GRAPH above for format of graph + self.graph = graph + self.points = map(np.array, graph["vertices"]) + self.vertices = self.dots = [Dot(p) for p in self.points] + self.edges = [ + Line(self.points[i], self.points[j]) + for i, j in graph["edges"] + ] + self.add(*self.dots + self.edges) + + def generate_regions(self): + regions = [ + region_from_line_boundary(*[ + [ + self.points[rc[i]], + self.points[rc[(i+1)%len(rc)]] + ] + for i in range(len(rc)) + ]) + for rc in self.graph["region_cycles"] + ] + regions[-1].complement()#Outer region painted outwardly... + self.regions = regions ################################################## @@ -72,7 +105,7 @@ def count_lines(*radians): else: anims.append(FadeOut(mob)) sc.animate(*anims, run_time = 1) - return sc + sc.write_to_movie(movie_prefix + "CountLines%dPoints"%len(radians)) def count_intersection_points(*radians): @@ -84,7 +117,7 @@ def count_intersection_points(*radians): for p in it.combinations(sc.points, 4) ] intersection_dots = [Dot(point) for point in intersection_points] - text_center = (sc.radius + 1, sc.radius -0.5, 0) + text_center = (sc.radius + 0.5, sc.radius -0.5, 0) size = r"\large" scale_factor = 0.4 text = tex_mobject(r"\text{How Many Intersection Points?}", size = size) @@ -107,7 +140,7 @@ def count_intersection_points(*radians): # ]) sc.add(text) - sc.count(intersection_dots, "show_creation", num_offset = (0, 0, 0)) + sc.count(intersection_dots, "show", num_offset = (0, 0, 0)) sc.dither() # sc.animate(Transform(intersection_dots, new_dots)) anims = [] @@ -116,9 +149,11 @@ def count_intersection_points(*radians): anims.append(Transform(mob, answer)) else: anims.append(FadeOut(mob)) - anims.append(FadeIn(formula)) #Put here to so they are foreground + anims.append(Animation(formula)) sc.animate(*anims, run_time = 1) - return sc + + name = "CountIntersectionPoints%dPoints"%len(radians) + sc.write_to_movie(movie_prefix + name) def non_general_position(): radians = np.arange(1, 7) @@ -151,7 +186,7 @@ def non_general_position(): Transform(mob1, mob2, run_time = DEFAULT_ANIMATION_RUN_TIME) for mob1, mob2 in zip(sc1.mobjects, sc2.mobjects) ]) - return sc1 + sc1.write_to_movie(movie_prefix + "NonGeneralPosition") def line_corresponds_with_pair(radians, r1, r2): sc = CircleScene(radians) @@ -165,40 +200,319 @@ def line_corresponds_with_pair(radians, r1, r2): sc.dots.remove(dot0) sc.dots.remove(dot1) sc.dither() - sc.animate(*[FadeOut(mob) for mob in sc.lines + sc.dots]) + sc.animate(*[ + FadeOut(mob, alpha_func = not_quite_there) + for mob in sc.lines + sc.dots + ]) sc.add(sc.circle) sc.animate(*[ ScaleInPlace(mob, 3, alpha_func = there_and_back) for mob in (dot0, dot1) ]) sc.animate(Transform(line, dot0)) - return sc + name = "LineCorrspondsWithPair%d%d"%(dot0_index, dot1_index) + sc.write_to_movie(movie_prefix + name) -def illustrate_n_choose_2(n): - #TODO, maybe make this snazzy +def illustrate_n_choose_k(n, k): sc = Scene() nrange = range(1, n+1) - nrange_im = tex_mobject(str(nrange)) - pairs_str = str(list(it.combinations(nrange, 2))) - exp = tex_mobject(r"{{%d \choose 2} = %d \text{ total pairs}}"%(n, choose(n, 2))) - pairs_im = tex_mobject(r"\underbrace{%s}"%pairs_str, size=r"\tiny") - nrange_im.shift((0, 2, 0)) - pairs_im.scale(0.7) - exp.shift((0, -2, 0)) - sc.add(nrange_im) + tuples = list(it.combinations(nrange, k)) + nrange_mobs = tex_mobjects([str(n) + r'\;' for n in nrange]) + tuple_mobs = tex_mobjects( + [ + (r'\\&' if c%(20//k) == 0 else r'\;\;') + str(p) + for p, c in zip(tuples, it.count()) + ], + size = r"\small" + ) + tuple_terms = { + 2 : "pairs", + 3 : "triplets", + 4 : "quadruplets", + } + tuple_term = tuple_terms[k] if k in tuple_terms else "tuples" + form1, count, form2 = tex_mobject([ + r"{%d \choose %d} = "%(n, k), + "%d"%choose(n, k), + r" \text{ total %s}"%tuple_term + ]) + for mob in nrange_mobs: + mob.shift((0, 2, 0)) + for mob in form1, count, form2: + mob.shift((0, -SPACE_HEIGHT + 1, 0)) + count_center = count.get_center() + for mob in tuple_mobs: + mob.scale(0.6) + + sc.add(*nrange_mobs) sc.dither() - sc.animate(FadeIn(pairs_im), FadeIn(exp)) - sc.add(pairs_im) - return sc - + run_time = 6.0 + frame_time = run_time / len(tuples) + for tup, count in zip(tuples, it.count()): + count_mob = tex_mobject(str(count+1)) + count_mob.center().shift(count_center) + sc.add(count_mob) + tuple_copy = CompoundMobject(*[nrange_mobs[index-1] for index in tup]) + tuple_copy.highlight() + sc.add(tuple_copy) + sc.add(tuple_mobs[count]) + sc.dither(frame_time) + sc.remove(count_mob) + sc.remove(tuple_copy) + sc.add(count_mob) + sc.animate(FadeIn(CompoundMobject(form1, form2))) + sc.write_to_movie(movie_prefix + "Illustrate%dChoose%d"%(n, k)) + +def intersection_point_correspondances(radians, indices): + assert(len(indices) == 4) + indices.sort() + sc = CircleScene(radians) + intersection_point = intersection( + (sc.points[indices[0]], sc.points[indices[2]]), + (sc.points[indices[1]], sc.points[indices[3]]) + ) + intersection_point = tuple(list(intersection_point) + [0]) + intersection_dot = Dot(intersection_point) + intersection_dot_arrow = Arrow(intersection_point).nudge() + sc.add(intersection_dot) + pairs = list(it.combinations(range(len(radians)), 2)) + lines_to_save = [ + sc.lines[pairs.index((indices[p0], indices[p1]))] + for p0, p1 in [(0, 2), (1, 3)] + ] + dots_to_save = [ + sc.dots[p] + for p in indices + ] + line_statement = tex_mobject(r"\text{Pair of Lines}") + dots_statement = tex_mobject(r"&\text{Quadruplet of} \\ &\text{outer dots}") + for mob in line_statement, dots_statement: + mob.center() + mob.scale(0.7) + mob.shift((SPACE_WIDTH-2, SPACE_HEIGHT - 1, 0)) + fade_outs = [] + line_highlights = [] + dot_highlights = [] + dot_pointers = [] + for mob in sc.mobjects: + if mob in lines_to_save: + line_highlights.append(Highlight(mob)) + elif mob in dots_to_save: + dot_highlights.append(Highlight(mob)) + dot_pointers.append(Arrow(mob.get_center()).nudge()) + elif mob != intersection_dot: + fade_outs.append(FadeOut(mob, alpha_func = not_quite_there)) + + sc.add(intersection_dot_arrow) + sc.animate(Highlight(intersection_dot)) + sc.remove(intersection_dot_arrow) + sc.animate(*fade_outs) + sc.dither() + sc.add(line_statement) + sc.animate(*line_highlights) + sc.remove(line_statement) + sc.dither() + sc.add(dots_statement, *dot_pointers) + sc.animate(*dot_highlights) + sc.remove(dots_statement, *dot_pointers) + + name = "IntersectionPointCorrespondances" + for ind in indices: + name += str(ind) + sc.write_to_movie(movie_prefix + name) + +def lines_intersect_outside(radians, indices): + assert(len(indices) == 4) + indices.sort() + sc = CircleScene(radians) + intersection_point = intersection( + (sc.points[indices[0]], sc.points[indices[1]]), + (sc.points[indices[2]], sc.points[indices[3]]) + ) + intersection_point = tuple(list(intersection_point) + [0]) + intersection_dot = Dot(intersection_point) + pairs = list(it.combinations(range(len(radians)), 2)) + lines_to_save = [ + sc.lines[pairs.index((indices[p0], indices[p1]))] + for p0, p1 in [(0, 1), (2, 3)] + ] + sc.animate(*[ + FadeOut(mob, alpha_func = not_quite_there) + for mob in sc.mobjects if mob not in lines_to_save + ]) + sc.animate(*[ + Transform( + Line(sc.points[indices[p0]], sc.points[indices[p1]]), + Line(sc.points[indices[p0]], intersection_point)) + for p0, p1 in [(0, 1), (3, 2)] + ] + [ShowCreation(intersection_dot)]) + + name = "LinesIntersectOutside" + for ind in indices: + name += str(ind) + sc.write_to_movie(movie_prefix + name) + +def quadruplets_to_intersections(*radians): + sc = CircleScene(radians) + quadruplets = it.combinations(range(len(radians)), 4) + frame_time = 1.0 + for quad in quadruplets: + intersection_dot = Dot(intersection( + (sc.points[quad[0]], sc.points[quad[2]]), + (sc.points[quad[1]], sc.points[quad[3]]) + )).repeat(3) + dot_quad = [deepcopy(sc.dots[i]) for i in quad] + for dot in dot_quad: + dot.scale_in_place(2) + # arrows = [Arrow(d.get_center()) for d in dot_quad] + dot_quad = CompoundMobject(*dot_quad) + # arrows = CompoundMobject(*arrows) + dot_quad.highlight() + # sc.add(arrows) + sc.add(dot_quad) + sc.dither(frame_time / 3) + sc.animate(Transform( + dot_quad, + intersection_dot, + run_time = 3*frame_time/2 + )) + # sc.remove(arrows) + + name = "QuadrupletsToIntersections" + len(radians) + sc.write_to_movie(movie_prefix + name) + +def defining_graph(graph): + gs = GraphScene(graph) + dots, lines = gs.vertices, gs.edges + gs.remove(*dots + lines) + all_dots = CompoundMobject(*dots) + gs.animate(ShowCreation(all_dots)) + gs.remove(all_dots) + gs.add(*dots) + gs.dither() + gs.animate(*[ + ShowCreation(line) for line in lines + ]) + + #Move to new graph + new_graph = deepcopy(graph) + new_graph["vertices"] = [ + (v[0] + 3*random(), v[1] + 3*random(), 0) + for v in new_graph["vertices"] + ] + ngs = GraphScene(new_graph) + gs.animate(*[ + Transform(m[0], m[1]) + for m in zip(gs.mobjects, ngs.mobjects) + ], run_time = 7.0) + + name = "DefiningGraph" + graph["name"] + gs.write_to_movie(movie_prefix + name) + +def doubled_edges(graph): + gs = GraphScene(graph) + lines_to_double = gs.edges[:9:3] + crazy_lines = [ + ( + line, + Line(line.end, line.start), + CurvedLine(line.start, line.end) , + CurvedLine(line.end, line.start) + ) + for line in lines_to_double + ] + anims = [] + outward_curved_lines = [] + kwargs = {"run_time" : 3.0} + for straight, backwards, inward, outward in crazy_lines: + anims += [ + Transform(straight, inward, **kwargs), + Transform(backwards, outward, **kwargs), + ] + outward_curved_lines.append(outward) + gs.animate(*anims) + gs.dither() + gs.remove(*outward_curved_lines) + + name = "DoubledEdges" + graph["name"] + gs.write_to_movie(movie_prefix + name) + + +def eulers_formula(graph): + gs = GraphScene(graph) + terms = "V - E + F =2".split(" ") + form = dict([ + (key, mob) + for key, mob in zip(terms, tex_mobjects(terms)) + ]) + for mob in form.values(): + mob.shift((0, SPACE_HEIGHT-1.5, 0)) + formula = CompoundMobject(*form.values()) + new_form = dict([ + (key, deepcopy(mob).shift((0, -0.7, 0))) + for key, mob in zip(form.keys(), form.values()) + ]) + gs.add(formula) + colored_dots = [ + deepcopy(d).scale_in_place(1.5).highlight("red") + for d in gs.dots + ] + colored_edges = [ + deepcopy(e).highlight("red") + for e in gs.edges + ] + frame_time = 0.3 + + gs.generate_regions() + parameters = [ + (colored_dots, "V", "mobject", "-", "show_creation"), + (colored_edges, "E", "mobject", "+", "show_creation"), + (gs.regions, "F", "region", "=2", "show_all") + ] + for items, letter, item_type, symbol, mode in parameters: + gs.count( + items, + item_type = item_type, + mode = mode, + num_offset = new_form[letter].get_center(), + run_time = frame_time*len(items) + ) + gs.dither() + if item_type == "mobject": + gs.remove(*items) + gs.add(new_form[symbol]) + gs.reset_background() + + name = "EulersFormula" + graph["name"] + gs.write_to_movie(movie_prefix + name) + ################################################## if __name__ == '__main__': radians = np.arange(0, 6, 6.0/7) - # count_lines(*radians).write_to_movie("moser/CountLines") - count_intersection_points(*radians).write_to_movie("moser/CountIntersectionPoints") - # non_general_position().write_to_movie("moser/NonGeneralPosition") - # line_corresponds_with_pair(radians, radians[3], radians[4]).write_to_movie("moser/LineCorrespondsWithPair34") - # line_corresponds_with_pair(radians, radians[2], radians[5]).write_to_movie("moser/LineCorrespondsWithPair25") - # illustrate_n_choose_2(6).write_to_movie("moser/IllustrateNChoose2with6") + # count_lines(*radians) + # count_lines(*radians[:4]) + # count_intersection_points(*radians[:4]) + # count_intersection_points(*radians[:6]) + # count_intersection_points(*radians) + # non_general_position() + # line_corresponds_with_pair(radians, radians[3], radians[4]) + # line_corresponds_with_pair(radians, radians[2], radians[5]) + # illustrate_n_choose_k(7, 2) + # illustrate_n_choose_k(6, 4) + # intersection_point_correspondances(radians, range(0, 7, 2)) + # lines_intersect_outside(radians, [2, 4, 5, 6]) + quadruplets_to_intersections(*radians[:6]) + # defining_graph(SAMPLE_GRAPH) + # doubled_edges(CUBE_GRAPH) + # eulers_formula(CUBE_GRAPH) + # eulers_formula(SAMPLE_GRAPH) + # eulers_formula(OCTOHEDRON_GRAPH) + + + + + + + diff --git a/region.py b/region.py index a04a8dca..45cfc772 100644 --- a/region.py +++ b/region.py @@ -76,6 +76,12 @@ class HalfPlane(Region): return (x1 - x0)*(y - y0) > (y1 - y0)*(x - x0) Region.__init__(self, condition, *args, **kwargs) +def region_from_line_boundary(*lines): + reg = Region() + for line in lines: + reg.intersect(HalfPlane(line)) + return reg + def plane_partition(*lines): """ A 'line' is a pair of points [(x0, y0,...), (x1, y1,...)] diff --git a/scene.py b/scene.py index 8a9919fd..600e74e9 100644 --- a/scene.py +++ b/scene.py @@ -16,6 +16,9 @@ from image_mobject import * from animation import * import displayer as disp +DEFAULT_COUNT_NUM_OFFSET = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0) +DEFAULT_COUNT_RUN_TIME = 5.0 + class Scene(object): def __init__(self, name = None, @@ -99,19 +102,29 @@ class Scene(object): self.add(*moving_mobjects) progress_bar.finish() - def count(self, mobjects, mode = "highlight", - color = "red", - num_offset = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0), - run_time = 5.0): + def count(self, items, item_type = "mobject", *args, **kwargs): + if item_type == "mobject": + self.count_mobjects(items, *args, **kwargs) + elif item_type == "region": + self.count_regions(items, *args, **kwargs) + + def count_mobjects( + self, mobjects, mode = "highlight", + color = "red", + num_offset = DEFAULT_COUNT_NUM_OFFSET, + run_time = DEFAULT_COUNT_RUN_TIME): """ Note: Leaves scene with a "number" attribute - for the final number mobject + for the final number mobject. + + mode can be "highlight", "show_creation" or "show", otherwise + a warning is given and nothing is animating during the count """ if len(mobjects) > 50: #TODO raise Exception("I don't know if you should be counting \ too many mobjects...") - if mode not in ["highlight", "show_creation"]: - raise Exception("Invalid mode") + if mode not in ["highlight", "show_creation", "show"]: + raise Warning("Unknown mode") frame_time = run_time / len(mobjects) if mode == "highlight": self.add(*mobjects) @@ -126,10 +139,34 @@ class Scene(object): mob.highlight(original_color) if mode == "show_creation": self.animate(ShowCreation(mob, run_time = frame_time)) + if mode == "show": + self.add(mob) + self.dither(frame_time) self.remove(num_mob) self.add(num_mob) self.number = num_mob + def count_regions(self, regions, + mode = "one_at_a_time", + num_offset = DEFAULT_COUNT_NUM_OFFSET, + run_time = DEFAULT_COUNT_RUN_TIME, + **unused_kwargsn): + if mode not in ["one_at_a_time", "show_all"]: + raise Warning("Unknown mode") + frame_time = run_time / (len(regions)) + for region, count in zip(regions, it.count(1)): + num_mob = tex_mobject(str(count)) + num_mob.center().shift(num_offset) + self.add(num_mob) + self.highlight_region(region) + self.dither(frame_time) + if mode == "one_at_a_time": + self.reset_background() + self.remove(num_mob) + self.add(num_mob) + self.number = num_mob + + def get_frame(self): frame = self.background for mob in self.mobjects: @@ -147,7 +184,7 @@ class Scene(object): self.dither(end_dither_time) disp.write_to_movie(self, name or str(self)) - def show_frame(self): + def show(self): Image.fromarray(self.get_frame()).show()