지오지브라와 파이썬의 만남 - Part 3

파이썬 스크립트가 어떤 식으로 동작하는지 확인했으니, 이제는 다듬어봅시다.

Locus 와 Trace

전에는 Locus 로 점 Q 의 자취를 그렸습니다. 작은 원이 큰 원의 둘레를 구르면서, 그 위의 점 QLocus 위를 지나가는 모습을 보여줍니다. 하지만 이 자취가 미리 그려져 있기 때문에 시각적인 효과가 그리 만족스럽지는 못합니다. 자취가 Q 가 지나온 자리까지만 그려진다면 더 좋겠습니다. 한번 바꿔봅시다.

지오지브라에서 이런 목적으로 Trace 라는 기능을 제공합니다만, 이 역시 시각적으로는 별로입니다. 점 Q 의 Trace 를 켜면, 듬성듬성 Q 가 지나온 지점들이 점으로 남을 뿐이고, 이마저 화면을 확대, 축소하면 지워져 버리고 맙니다. Trace 는 우리가 원하는 게 아닙니다.

움직이는 Locus

착상은 이렇습니다. 예전에 자취를 그릴 때 Locus[Q,t] 라는 명령을 사용했습니다. 이렇게 하면 슬라이더 t 가 변하는 범위 내에서, 점 Q 가 지나는 자리를 그려줍니다. 이 때문에 t 를 사용한 애니메이션에서 앞으로 Q 가 지나갈 모든 경로가 미리 그려질 수 밖에 없게 됩니다.

하지만 만약 또 하나의 슬라이더 s 를 도입한다면 어떨까요? 이 슬라이더는 Slider[0,T,0.05] 가 아니라 Slider[0,t,0.05] 와 같이 만들어서, $0 <= s <= t$ 범위에서 정의되도록 하는 것입니다. Locus 를 슬라이더 t 대신에 슬라이더 s를 사용해서 만든다면, 현재 t 가 진행된 지점까지만 Locus 가 만들어지게 됩니다.

하지만 간단하지는 않습니다. Locus 를 만들 때는 슬라이더뿐만 아니라 점 Q 도 사용합니다. Qt 에만 영향 받을 뿐, s 와는 무관합니다. 그냥 Q를 넘긴다면 자취가 생길 수 없습니다. 그렇다고 Qs 에 따라 바뀌게 만든다면, 애초에 계획한 t 를 사용한 애니메이션을 보여줄 수가 없습니다. 때문에 Q 와 유사하지만 s 에 대해 정의된 점이 또 하나 필요하게 됩니다.

매개변수를 사용한 도형

이 때문에 에피사이클로이드를 그리는 과정 전체가 이중화될 필요가 있습니다. 이제 스크립트가 제 역할을 하는 순간이 왔습니다. 에피사이클로이드를 그리는 파이썬 함수를 정의합시다.

def epicycloid(slider):
    values = {'slider':slider}
    ggbApplet.evalCommand('P_%(slider)s := Rotate[(10,0),%(slider)s,O]' % values)
    P = $['P_'+slider]
    ray = Ray(geo.O, P)
    c1 = Circle(P, 3)
    dummy, X = Intersect(c1, ray)
    $['X_'+slider] = X
    B = Circle(X, 3)
    $['B_'+slider] = B
    ggbApplet.evalCommand('Q_%(slider)s := Rotate[P_%(slider)s,10/3*%(slider)s,X_%(slider)s]' % values)
    Q = $['Q_'+slider]

슬라이더 slider 를 매개변수로 정의해서, 어떤 슬라이더를 사용할 것인지 지정할 수 있도록 합니다. 이를 위해 각 지오지브라 객체들의 이름에 슬라이더 이름을 첨자로 붙여서 서로 구분되게 하려고 합니다. 가령 점 P 의 경우 P_sP_t 를 사용하려고 합니다. 매개변수로 어떤 이름이 올지 불확실하므로 지오지브라 객체를 지칭할 때 문자열을 사용하는 방식이 쓸모 있습니다. 때문에 $['P_'+slider] 와 같은 표현을 사용합니다. ggbApplet.evalCommand() 도 포맷 스트링을 사용해서 이름을 치환합니다.

이제 이런 코드로 움직이는 Locus 를 만들 수 있습니다.

ggbApplet.evalCommand('t := Slider[0,T,0.05]')
epicycloid('t')
ggbApplet.evalCommand('s := Slider[0,t,0.05]')
epicycloid('s')
ggbApplet.evalCommand('l = Locus[Q_s,s]')

이벤트 처리기

아직 한가지 문제가 남았습니다. t로 애니메이션 할 때 Locus l 이 보이지 않는 것입니다. 지오지브라가 바뀐 t 값에 대해 Locus 를 다시 계산하지 않기 때문입니다. 때문에 t 값이 변할 때마다 Locus 를 다시 계산하도록 지시할 필요가 있습니다.

def update(target):
    ggbApplet.evalCommand('UpdateConstruction[]')

geo.t.onupdate = update

t 값이 변할 때마다 실행되는 코드를 넣으려면, onupdate 에 이벤트 처리기 함수를 대입해주면 됩니다. 이렇게 하면 이벤트 처리기는 t 값이 변할 때마다 호출되게 되는데, 이벤트가 발생한 객체가 인자(target)로 전달됩니다. 지오지브라가 지원하는 이벤트는 이 외에도 여러 가지가 있는데, 아마도 onupdate 가 가장 유용할 겁니다.

Locus 를 다시 계산하도록 하는 방법은 UpdateConstruction[] 명령입니다. 이 명령은 모든 지오지브라 객체를 다시 계산하도록 만듭니다.

화장

이제 색깔을 입혀 곱게 꾸며봅시다.

geo.A.color = Color(0,0,255)
geo.l.color = geo.Q_t.color = Color(255,0,0)

Color 는 RGB 값을 0 에서 255 사이의 숫자로 지정합니다.

필요 없는 것들은 visible 속성을 False 로 줘서 감추고, 나머지 것들도 label_visible 속성을 Flase로 지정해서 이름들이 보이지 않게 합니다.

마지막으로 원이 구르는 모습이 더 잘 보이게 하기 위해, 원의 중심에서 점 Q 까지 선분을 그어줍니다.

R = Segment(X,Q)

Script 탭

지금 까지 준비한 코드의 최종 버전은 이렇습니다.

import math

def update(target):
    ggbApplet.evalCommand('UpdateConstruction[]')

def epicycloid(slider):
    values = {'slider':slider}
    ggbApplet.evalCommand('P_%(slider)s := Rotate[(10,0),%(slider)s,O]' % values)
    P = $['P_'+slider]
    ray = Ray(geo.O, P)
    c1 = Circle(P, 3)
    dummy, X = Intersect(c1, ray)
    $['X_'+slider] = X
    B = Circle(X, 3)
    $['B_'+slider] = B
    ggbApplet.evalCommand('Q_%(slider)s := Rotate[P_%(slider)s,10/3*%(slider)s,X_%(slider)s]' % values)
    Q = $['Q_'+slider]
    R = Segment(X,Q)
    $['R_'+slider] = R
    for x in (P, ray, c1, dummy): x.visible = False
    for x in (X, B, Q, R): x.label_visible = False

geo.O = (0,0)
geo.A = Circle(geo.O, 10)
geo.T = 6 * math.pi
ggbApplet.evalCommand('t := Slider[0,T,0.05]')
geo.t.onupdate = update
epicycloid('t')

ggbApplet.evalCommand('s := Slider[0,t,0.05]')
epicycloid('s')
for x in (geo.X_s, geo.B_s, geo.Q_s, geo.R_s): x.visible = False

ggbApplet.evalCommand('l = Locus[Q_s,s]')

geo.O.visible = False
geo.A.label_visible = False
geo.A.color = Color(0,0,255)
geo.l.color = geo.Q_t.color = Color(255,0,0)

ggbApplet.setAnimating('t', True)
ggbApplet.setAnimationSpeed('t', 0.1)
ggbApplet.startAnimation()

지오지브라의 파일 > 새로 만들기로 새 파일을 시작한 후에 파이썬 창의 Script 탭에 이 코드를 복사합니다. 스크립트를 실행하지 않은 상태에서 이 파일을 적당한 이름으로 저장합니다. 지오지브라를 다시 시작한 후에 이 파일을 로드 하면 자동 실행되고 애니메이션이 시작됩니다.

Epicycloid

Epicycloid

마치며

현실적인 수준의 예제를 통해 파이썬 스크립팅을 소개하려다 보니 좀 어렵게 느끼시는 분들도 있을 것 같습니다. 지오지브라에 익숙하지 않으신 분들은 지오지브라의 기능에 대한 설명이 부족하다 느낄 것 같고, 파이썬을 처음 만나시는 분들은 문법이 생소할 수 있을 듯 합니다. 파이썬을 배우시고 싶으신 분들은 Think Python 을 참고하시면 좋을 것 같습니다.

시작할 때는 지오지브라를 위한 클래스 라이브러리까지 가려고 했습니다만 일단은 여기서 멈춥니다. 일단 다른 이야기들을 끝낸 후에 다시 살펴보기로 하지요.