diff --git a/manimlib/mobject/functions.py b/manimlib/mobject/functions.py index e67abeee..dafede7b 100644 --- a/manimlib/mobject/functions.py +++ b/manimlib/mobject/functions.py @@ -1,7 +1,6 @@ from manimlib.constants import * from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.utils.config_ops import digest_config -from manimlib.utils.space_ops import get_norm class ParametricCurve(VMobject): @@ -11,7 +10,6 @@ class ParametricCurve(VMobject): "epsilon": 1e-8, # TODO, automatically figure out discontinuities "discontinuities": [], - "smoothing": True, } def __init__(self, t_func, t_range=None, **kwargs): @@ -42,8 +40,7 @@ class ParametricCurve(VMobject): points = np.array([self.t_func(t) for t in t_range]) self.start_new_path(points[0]) self.add_points_as_corners(points[1:]) - if self.smoothing: - self.make_smooth() + self.make_smooth(true_smooth=False) return self diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index a5d64413..905546ae 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -9,6 +9,7 @@ from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Point from manimlib.utils.bezier import bezier from manimlib.utils.bezier import get_smooth_quadratic_bezier_handle_points +from manimlib.utils.bezier import get_smooth_cubic_bezier_handle_points from manimlib.utils.bezier import get_quadratic_approximation_of_cubic from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import integer_interpolate @@ -402,13 +403,13 @@ class VMobject(Mobject): ]) return self - def set_points_smoothly(self, points): + def set_points_smoothly(self, points, true_smooth=False): self.set_points_as_corners(points) - self.make_smooth() + self.make_smooth(true_smooth) return self def change_anchor_mode(self, mode): - assert(mode in ["jagged", "smooth"]) + assert(mode in ["jagged", "approx_smooth", "true_smooth"]) nppc = self.n_points_per_curve for submob in self.family_members_with_points(): subpaths = submob.get_subpaths() @@ -416,17 +417,27 @@ class VMobject(Mobject): for subpath in subpaths: anchors = np.vstack([subpath[::nppc], subpath[-1:]]) new_subpath = np.array(subpath) - if mode == "smooth": + if mode == "approx_smooth": new_subpath[1::nppc] = get_smooth_quadratic_bezier_handle_points(anchors) + elif mode == "true_smooth": + h1, h2 = get_smooth_cubic_bezier_handle_points(anchors) + new_subpath = get_quadratic_approximation_of_cubic(anchors[:-1], h1, h2, anchors[1:]) elif mode == "jagged": new_subpath[1::nppc] = 0.5 * (anchors[:-1] + anchors[1:]) submob.append_points(new_subpath) submob.refresh_triangulation() return self - def make_smooth(self): - # TODO, Change this to not rely on a cubic-to-quadratic conversion - return self.change_anchor_mode("smooth") + def make_smooth(self, true_smooth=True): + """ + If true_smooth is set to True, the number of points + in the mobject will double, but the effect will be + a genuinely smooth (C2) curve. Otherwise, it may not + becomes perfectly smooth, but the number of points + will stay the same. + """ + mode = "true_smooth" if true_smooth else "approx_smooth" + return self.change_anchor_mode(mode) def make_jagged(self): return self.change_anchor_mode("jagged") @@ -824,7 +835,7 @@ class VMobject(Mobject): def apply_function(self, function): super().apply_function(function) if self.make_smooth_after_applying_functions: - self.make_smooth() + self.make_smooth(true_smooth=False) return self @triggers_refreshed_triangulation diff --git a/manimlib/utils/bezier.py b/manimlib/utils/bezier.py index 23acc4ef..8a3f6692 100644 --- a/manimlib/utils/bezier.py +++ b/manimlib/utils/bezier.py @@ -134,8 +134,14 @@ def get_smooth_quadratic_bezier_handle_points(points): 0.25 * ps[0:-2] + ps[1:-1] - 0.25 * ps[2:] for ps in (points, points[::-1]) ] - handles = 0.5 * np.vstack([smooth_to_right, [smooth_to_left[0]]]) - handles += 0.5 * np.vstack([smooth_to_right[0], smooth_to_left[::-1]]) + if np.isclose(points[0], points[-1]).all(): + last_str = 0.25 * points[-2] + points[-1] - 0.25 * points[1] + last_stl = 0.25 * points[1] + points[0] - 0.25 * points[-2] + else: + last_str = smooth_to_left[0] + last_stl = smooth_to_right[0] + handles = 0.5 * np.vstack([smooth_to_right, [last_str]]) + handles += 0.5 * np.vstack([last_stl, smooth_to_left[::-1]]) return handles