본문 바로가기

Programming/Manim Project

사인 파의 진폭을 변형시키면서 사인파 이동시키기

반응형

만들려는 것은, 이동하는 사인파를 애니메이션 하는 것인데, 동시에 사인파의 진폭(높낮이)을 바꿔가면서 이동하는 것을 애니메이션 해보고자 한다.

 

 

진폭 변동없이 그냥 이동시키는 애니메이션에 대한 코딩은 여기 참조.

 

핵심 구상

사인파를 화면에 그리고 이동하는 듯한 애니메이션을 구현하는 것은 여기  설명한 것과 동일.

 

사인파의 진폭을 변화시키는 것이 다른 것인데, 시간의 흐름에 따라 변화되게 하자

 

  • 0~1초 : 진폭 1로 진행
  • 1~2초: 진폭 1에서 2로 서서히 커지게
  • 2~3초: 진폭 2에서 1로 서서히 작아지게
  • 3~ : 진폭 1로 진행

 

진폭은, 사인 곡선을 만들어내는 원의 반지름에 비례한다. 

원의 반지름이 1이면 진폭이 1이고, 원이 반지름이 커지면 진폭이 증가하고, 반지름이 작아지면 진폭도 감소한다. 

 

 

따라서, 시간의 흐름에 따라 원의 크기를 조절해 주고, 또한 사인파의 크기를 조절하면서 새로 그려주면 되겠다.

 

원의 크기를 조절하는 것은, 해당 반지름 크기의 원을 새로 만들어서 교체하면 되고,

            circle = Circle(radius=self.radius)
            circle.move_to(self.circle.get_center())
            mob.become(circle)

사인 파도 진폭이 다른 사인파를 만들어서 새로 그려주면 되겠다.

        def get_sine_curve(dx=0):
            amp = self.radius
            # print("amp:",amp)
            return FunctionGraph(
                lambda x: amp * np.sin(x-self.curve_start[0]+dx),
                x_min=self.x_start[0], x_max=self.curve_end[0],
            )

 

코딩

1. 축, 원 그리기

가로, 세로축, 그리고 원이 놓이게 되는 좌표를 설계해 보자.

이에 대한 코드는 다음과 같다.

    def show_axis(self):
        self.x_start = np.array([-6,0,0])
        x_axis = Line(self.x_start, np.array([6, 0, 0]))
        y_axis = Line(np.array([-4, -2, 0]), np.array([-4, 2, 0]))

        self.add(x_axis, y_axis)

        self.origin_point = np.array([-4, 0, 0])
        self.curve_start = np.array([-3, 0, 0])

        self.one_cycle_length = 2 * PI
        self.curve_end = np.array([self.curve_start[0] + self.one_cycle_length, 0, 0])


    def show_circle_dot(self):
        circle = Circle(radius=1)
        circle.move_to(self.origin_point)

        dot = Dot(radius=0.08, color=YELLOW)
        dot.move_to(circle.point_from_proportion(0))

        self.add(circle, dot)
        self.circle = circle
        self.dot = dot

 

2. 점, 막대, 점에서 커브, 사인 그래프를 그린다.

진폭이 변하는 것이기에, 원의 반지름과 사인 그래프의 진폭이 바뀌어야 한다. 

 

시간의 흐름을 나타내는 self.t_offset을 이용해서, 시간에 따른 원의 반지름 크기를 결정하는 변수인 self.radius의 값이 변경되도록 한다. 그리고, 그 radius에 해당하는 Circle을 만들어서 기존의 원을 업데이트한다.

        def change_circle_size(mob, dt):
            if self.t_offset > 1 and self.t_offset <= 2:
                self.radius = self.t_offset
            elif self.t_offset > 2 and self.t_offset <= 3:
                self.radius = 4- self.t_offset
            else:
                self.radius = 1

            circle = Circle(radius=self.radius)
            circle.move_to(self.circle.get_center())
            mob.become(circle)

 

사인파의 진폭은 원의 반지름과 같다. 따라서, 사인파의 진폭을 결정하는 변수인 amp를 self.radius가 되게 한 후, 새롭게 사인파를 그려서 기존의 사인파를 업데이트하면 되겠다.

        def get_sine_curve(dx=0):
            amp = self.radius
            # print("amp:",amp)
            return FunctionGraph(
                lambda x: amp * np.sin(x-self.curve_start[0]+dx),
                x_min=self.x_start[0], x_max=self.curve_end[0],
            )

        def get_updated_sine_curve():
            return get_sine_curve(dx=self.t_offset * 2 * PI)

 


 

전체 완성된 코드는, 

class ChangeAmp_SineCurve(Scene):
    def construct(self):
        self.show_axis()
        self.show_circle_dot()
        self.draw_several_cycle()

        self.wait()

    def show_axis(self):
        self.x_start = np.array([-6,0,0])
        x_axis = Line(self.x_start, np.array([6, 0, 0]))
        y_axis = Line(np.array([-4, -2, 0]), np.array([-4, 2, 0]))

        self.add(x_axis, y_axis)

        self.origin_point = np.array([-4, 0, 0])
        self.curve_start = np.array([-3, 0, 0])

        self.one_cycle_length = 2 * PI
        self.curve_end = np.array([self.curve_start[0] + self.one_cycle_length, 0, 0])


    def show_circle_dot(self):
        circle = Circle(radius=1)
        circle.move_to(self.origin_point)

        dot = Dot(radius=0.08, color=YELLOW)
        dot.move_to(circle.point_from_proportion(0))

        self.add(circle, dot)
        self.circle = circle
        self.dot = dot

    def draw_several_cycle(self):
        dot = self.dot
        orgin_point = self.origin_point

        self.t_offset = 0
        rate = 0.25
        one_cycle_time= (1 / rate) + (1/self.camera.frame_rate) * rate
        self.radius = 1

        def change_circle_size(mob, dt):
            if self.t_offset > 1 and self.t_offset <= 2:
                self.radius = self.t_offset
            elif self.t_offset > 2 and self.t_offset <= 3:
                self.radius = 4- self.t_offset
            else:
                self.radius = 1

            circle = Circle(radius=self.radius)
            circle.move_to(self.circle.get_center())
            mob.become(circle)

        def go_around_circle(mob, dt):
            self.t_offset += (dt * rate)
            # print(self.t_offset)
            mob.move_to(self.circle.point_from_proportion(self.t_offset % 1))

        def get_line_to_circle():
            return Line(orgin_point, dot.get_center(), color=BLUE)

        def get_line_to_curve():
            x = self.curve_end[0]  # fixing to the end of the curve
            y = dot.get_center()[1]
            return Line(dot.get_center(), np.array([x,y,0]), color=YELLOW_A, stroke_width=2 )

        def get_sine_curve(dx=0):
            amp = self.radius
            # print("amp:",amp)
            return FunctionGraph(
                lambda x: amp * np.sin(x-self.curve_start[0]+dx),
                x_min=self.x_start[0], x_max=self.curve_end[0],
            )

        def get_updated_sine_curve():
            return get_sine_curve(dx=self.t_offset * 2 * PI)


        dot.add_updater(go_around_circle)
        self.circle.add_updater(change_circle_size)

        origin_to_circle_line = always_redraw(get_line_to_circle)
        dot_to_curve_line = always_redraw(get_line_to_curve)
        sine_curve = always_redraw(get_updated_sine_curve)

        self.add(self.circle)
        self.add(origin_to_circle_line, dot_to_curve_line, sine_curve)
        self.wait(one_cycle_time*4)

        dot.remove_updater(go_around_circle)
        self.circle.remove_updater(change_circle_size)

 

실행되는 동영상은,

 

 

-끝-

 

 

반응형