diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index b6bb4be4..94c111eb 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -338,15 +338,15 @@ class Camera(object): return ctx.new_path() - subpaths = vmobject.get_subpaths_from_points(points) + subpaths = vmobject.gen_subpaths_from_points_2d(points) for subpath in subpaths: - quads = vmobject.get_cubic_bezier_tuples_from_points(subpath) + quads = vmobject.gen_cubic_bezier_tuples_from_points(subpath) ctx.new_sub_path() start = subpath[0] ctx.move_to(*start[:2]) for p0, p1, p2, p3 in quads: ctx.curve_to(*p1[:2], *p2[:2], *p3[:2]) - if vmobject.consider_points_equals(subpath[0], subpath[-1]): + if vmobject.consider_points_equals_2d(subpath[0], subpath[-1]): ctx.close_path() return self @@ -549,7 +549,7 @@ class Camera(object): def transform_points_pre_display(self, mobject, points): # Subclasses (like ThreeDCamera) may want to # adjust points futher before they're shown - if np.any(np.isnan(points)) or np.any(points == np.inf): + if not np.all(np.isfinite(points)): # TODO, print some kind of warning about # mobject having invalid points? points = np.zeros((1, 3)) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 29b91e54..25ba527d 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -595,35 +595,69 @@ class VMobject(Mobject): atol=self.tolerance_for_point_equality ) + def consider_points_equals_2d(self, p0, p1): + """ + Determine if two points are close enough to be considered equal. + + This uses the algorithm from np.isclose(), but expanded here for the + 2D point case. NumPy is overkill for such a small question. + """ + rtol = 1.e-5 # default from np.isclose() + atol = self.tolerance_for_point_equality + if abs(p0[0] - p1[0]) > atol + rtol * abs(p1[0]): + return False + if abs(p0[1] - p1[1]) > atol + rtol * abs(p1[1]): + return False + return True + # Information about line def get_cubic_bezier_tuples_from_points(self, points): + return np.array(list(self.gen_cubic_bezier_tuples_from_points(points))) + + def gen_cubic_bezier_tuples_from_points(self, points): + """ + Get a generator for the cubic bezier tuples of this object. + + Generator to not materialize a list or np.array needlessly. + """ nppcc = VMobject.CONFIG["n_points_per_cubic_curve"] remainder = len(points) % nppcc points = points[:len(points) - remainder] - return np.array([ + return ( points[i:i + nppcc] for i in range(0, len(points), nppcc) - ]) + ) def get_cubic_bezier_tuples(self): return self.get_cubic_bezier_tuples_from_points( self.get_points() ) - def get_subpaths_from_points(self, points): + def _gen_subpaths_from_points(self, points, filter_func): nppcc = self.n_points_per_cubic_curve - split_indices = filter( - lambda n: not self.consider_points_equals( - points[n - 1], points[n] - ), - range(nppcc, len(points), nppcc) - ) + split_indices = filter(filter_func, range(nppcc, len(points), nppcc)) split_indices = [0] + list(split_indices) + [len(points)] - return [ + return ( points[i1:i2] for i1, i2 in zip(split_indices, split_indices[1:]) if (i2 - i1) >= nppcc - ] + ) + + def get_subpaths_from_points(self, points): + return list( + self._gen_subpaths_from_points( + points, + lambda n: not self.consider_points_equals( + points[n - 1], points[n] + )) + ) + + def gen_subpaths_from_points_2d(self, points): + return self._gen_subpaths_from_points( + points, + lambda n: not self.consider_points_equals_2d( + points[n - 1], points[n] + )) def get_subpaths(self): return self.get_subpaths_from_points(self.get_points()) diff --git a/perf_scenes.py b/perf_scenes.py new file mode 100644 index 00000000..80d468e7 --- /dev/null +++ b/perf_scenes.py @@ -0,0 +1,88 @@ +from manimlib.imports import * + +""" +A set of scenes to be used for performance testing of Manim. +""" + + +class Perf1(GraphScene): + """ + A simple scene of two animations from the end of a video on recursion. + + - Uses a graph in 1/4 of the scene. + - First fades in multiple lines of text and equations, and the graph axes. + - Next animates creation of two graphs and the creation of their text + labels. + """ + CONFIG = { + "x_axis_label": + "$n$", + "y_axis_label": + "$time$", + "x_axis_width": + FRAME_HEIGHT, + "y_axis_height": + FRAME_HEIGHT / 2, + "y_max": + 50, + "y_min": + 0, + "x_max": + 100, + "x_min": + 0, + "x_labeled_nums": [50, 100], + "y_labeled_nums": + range(0, 51, 10), + "y_tick_frequency": + 10, + "x_tick_frequency": + 10, + "axes_color": + BLUE, + "graph_origin": + np.array( + (-FRAME_X_RADIUS + LARGE_BUFF, -FRAME_Y_RADIUS + LARGE_BUFF, 0)) + } + + def construct(self): + t1 = TextMobject( + "Dividing a problem in half over and over means\\\\" + "the work done is proportional to $\\log_2{n}$").to_edge(UP) + + t2 = TextMobject( + '\\textit{This is one of our\\\\favorite things to do in CS!}') + t2.to_edge(RIGHT) + + t3 = TextMobject( + 'The new \\texttt{power(x,n)} is \\underline{much}\\\\better than the old!' + ) + t3.scale(0.8) + p1f = TexMobject('x^n=x \\times x^{n-1}').set_color(ORANGE) + t4 = TextMobject('\\textit{vs.}').scale(0.8) + p2f = TexMobject( + 'x^n=x^{\\frac{n}{2}} \\times x^{\\frac{n}{2}}').set_color(GREEN) + p1v2g = VGroup(t3, p1f, t4, p2f).arrange(DOWN).center().to_edge(RIGHT) + + self.setup_axes() + o_n = self.get_graph(lambda x: x, color=ORANGE, x_min=1, x_max=50) + o_log2n = self.get_graph(lambda x: math.log2(x), + color=GREEN, + x_min=2, + x_max=90) + onl = TexMobject('O(n)') + olog2nl = TexMobject('O(\\log_2{n})') + onl.next_to(o_n.get_point_from_function(0.6), UL) + olog2nl.next_to(o_log2n.get_point_from_function(0.8), UP) + self.play( + FadeIn(t1), + FadeIn(self.axes), + # FadeInFromDown(t2), + FadeIn(p1v2g), + ) + self.play(ShowCreation(o_n), + ShowCreation(o_log2n), + ShowCreation(onl), + ShowCreation(olog2nl), + run_time=3) + self.wait(duration=5)