Search code examples
manim

move object along measured curve in manim


I'm trying to find a sensible way to move an Mobject along a path defined by the n-length vectors ts,xs,ys,(zs).

The solution I have now is by using ParametricFunction and MoveAlongPath. I can then define a rate function to make sure the timing adds up. This is extremely backwards and not quite reliable in my experience.

I feel like I'm probably missing some builtin function but I can't find it.

# This function takes a path defined by arrays and returns a function
# ts is assumed to be strictly increasing
def manim_curve(ts,xs,ys):
    ts,xs,ys = map(np.array,(ts,xs,ys))

    # Calculate the total distance traveled over the curve
    dist = np.cumsum(np.abs(np.diff(xs+1j*ys,prepend=0))) 

    # Normalize to a time range of [0,1]
    nts   = ts   / ts[-1]
    ndist = dist / dist[-1]

    # Create a function that can be passed `ParametricFunction`
    def f(t):
        n = np.abs(nts-t).argmin() # Find index from t
        return (xs[n],ys[n],0)
    
    # Create a rate function for `MoveAlongPath`
    def rate(t):
        n = np.abs(nts-t).argmin() # Find index from t
        return ndist[n]
    
    # Create manim curve
    curve = ParametricFunction(function=f)

    return curve,rate

# Animation class to move along a discretely defined path  
class MoveAlongMeasuredPath(MoveAlongPath):
    def __init__(self,object,ts,xs,ys,**kwargs):
        ts,xs,ys   = map(np.array,(ts,xs,ys))
        curve,rate = manim_curve(ts,xs,ys)
        super().__init__(object,curve,
                         run_time  = ts[-1],
                         rate_func = rate,
                         **kwargs)


Solution

  • I dug a little deeper in the code and realized there is a simple solution. The class below is only a slight alteration of the MoveAlongPath class source code:

    class MoveAlongTXYZPath(Animation):
        def __init__(
            self,
            mobject: Mobject,
            ts:NDArray,
            points:NDArray,
            is_sorted:bool=False,
            suspend_mobject_updating: bool = False,
            **kwargs,
        ) -> None:
            assert np.all(ts>=0), "no negative t_values allowed"
            assert len(ts)==len(points), "vectors have to be the same length"
    
            # Sort if unsorted in t
            if not is_sorted:
                ts,points = map(np.array,zip(*sorted([*zip(ts,points)])))
            self.points = points
            run_time = np.max(ts)
            self.alphas = ts/run_time
            super().__init__( mobject, 
                              suspend_mobject_updating=suspend_mobject_updating,
                              run_time=run_time,
                              rate_func=linear,
                              **kwargs)
    
        def interpolate_mobject(self, alpha: float) -> None:
            index = np.searchsorted(self.alphas,alpha)
            point = self.points[index]
            self.mobject.move_to(point)