pythonmanim

How can I create a closed loop curve in Manim?


I'm trying to make a curved shape that passes through a set of points and forms a closed loop. I was able to create a VMobject with the points and use .make_smooth() to form the curve, but the first point has a sharp angle in it. Is there a way to make the entire curve smooth?

Here's the script:

from manim import *

class Help(Scene):
    def construct(self):
        points = ((-3, 2, 0), (-1, 0, 0), (-4, -2, 0), (-5, 0, 0))
        redCurve = VMobject(color=RED)
        redCurve.set_points_as_corners(points)
        redCurve.close_path()
        redCurve.make_smooth()

        self.play(Create(redCurve))
        self.wait(3)

Closed loop with a sharp angle in it

Thanks!


Solution

  • The reason the Bezier curve has a sharp angle in it is because the make_smooth() function doesn't consider whether the anchors (points) form a closed loop or not. As a result, it has one less segment in it than it should. The solution is to calculate the handles for the loop using one of the utils Manim provides (the incredibly verbose get_smooth_closed_cubic_bezier_handle_points()) and set them on the VMobject directly:

    class Help(Scene):
        def construct(self):
            # The starting point is also added to the end here to form a loop
            points = np.array([(-3, 2, 0), (-1, 0, 0), (-4, -2, 0), (-5, 0, 0), (-3, 2, 0)])
            (handles1, handles2) = get_smooth_closed_cubic_bezier_handle_points(points)
            redCurve = VMobject(color=RED)
            # Arguments are in the form of startPoint[], firstHandle[], secondHandle[], endPoint[]
            redCurve.set_anchors_and_handles(points[:-1], handles1, handles2, points[1:])
    
            self.play(Create(redCurve))
            self.wait(3)
    

    Smooth loop

    You can also visualize what the get_smooth_closed...() function is doing to make the shape smooth:

    class Help(Scene):
        def construct(self):
            points = np.array([(-3, 2, 0), (-1, 0, 0), (-4, -2, 0), (-5, 0, 0), (-3, 2, 0)])
            (handles1, handles2) = get_smooth_closed_cubic_bezier_handle_points(points)
            redCurve = VMobject(color=RED)
            redCurve.set_anchors_and_handles(points[:-1], handles1, handles2, points[1:])
    
            # Gets the VMobject's parameters in the form of startPoint[], firstHandle[], secondHandle[], endPoint[]
            for anchors in zip(*redCurve.get_anchors_and_handles()):
                print("control points of this bezier segment: ", *anchors)
                for point in anchors:
                    self.add(Dot(point, radius=0.05))
            
            self.play(Create(redCurve))
            self.wait(3)
    
    control points of this bezier segment:  [-3.  2.  0.] [-2.  2.  0.] [-0.75  1.    0.  ] [-1.  0.  0.]
    control points of this bezier segment:  [-1.  0.  0.] [-1.25 -1.    0.  ] [-3. -2.  0.] [-4. -2.  0.]
    control points of this bezier segment:  [-4. -2.  0.] [-5. -2.  0.] [-5.25 -1.    0.  ] [-5.  0.  0.]
    control points of this bezier segment:  [-5.  0.  0.] [-4.75  1.    0.  ] [-4.  2.  0.] [-3.  2.  0.]
    

    Smooth loop with control points plotted