← 과제 목록

[과제] 2주차) Basic Programming 김민찬

@mnchnk
  • #과제

`#include “Example.h”

struct MyExample : tvgexam::Example { tvg::Shape* hourHand = nullptr; tvg::Shape* minHand = nullptr; tvg::Shape* secHand = nullptr; tvg::Scene* pendulum = nullptr;

static constexpr float PI = 3.141592f;

bool content(tvg::Canvas* canvas, uint32_t w, uint32_t h) override
{
    //background
    auto bg = tvg::Shape::gen();
    bg->appendRect(0, 0, w, h);
    bg->fill(250, 248, 240);
    canvas->add(bg);

    float clockCx = float(w) * 0.5f;
    float clockCy = float(h) * 0.28f;
    float clockR = (w < h ? float(w) : float(h)) * 0.22f;

    //clock case
    auto face = tvg::Shape::gen();
    face->appendCircle(clockCx, clockCy, clockR, clockR);
    face->fill(255, 255, 255);
    face->strokeFill(60, 40, 20);
    face->strokeWidth(6);
    canvas->add(face);

    //hour tick marks
    for (int i = 0; i < 12; i++) {
        float angle = float(i) * 30.0f * (PI / 180.0f);
        float x1 = clockCx + sinf(angle) * (clockR - 8);
        float y1 = clockCy - cosf(angle) * (clockR - 8);
        float x2 = clockCx + sinf(angle) * (clockR - 20);
        float y2 = clockCy - cosf(angle) * (clockR - 20);

        auto tick = tvg::Shape::gen();
        tick->moveTo(x1, y1);
        tick->lineTo(x2, y2);
        tick->strokeFill(60, 40, 20);
        tick->strokeWidth(3);
        canvas->add(tick);
    }

    //hour
    hourHand = tvg::Shape::gen();
    hourHand->moveTo(0, 0);
    hourHand->lineTo(0, -clockR * 0.45f);
    hourHand->strokeFill(30, 30, 30);
    hourHand->strokeWidth(6);
    hourHand->strokeCap(tvg::StrokeCap::Round);
    hourHand->translate(clockCx, clockCy);
    canvas->add(hourHand);

    //minute
    minHand = tvg::Shape::gen();
    minHand->moveTo(0, 0);
    minHand->lineTo(0, -clockR * 0.7f);
    minHand->strokeFill(30, 30, 30);
    minHand->strokeWidth(4);
    minHand->strokeCap(tvg::StrokeCap::Round);
    minHand->translate(clockCx, clockCy);
    canvas->add(minHand);

    //second
    secHand = tvg::Shape::gen();
    secHand->moveTo(0, 0);
    secHand->lineTo(0, -clockR * 0.85f);
    secHand->strokeFill(200, 30, 30);
    secHand->strokeWidth(2);
    secHand->strokeCap(tvg::StrokeCap::Round);
    secHand->translate(clockCx, clockCy);
    canvas->add(secHand);

    //center pin
    auto pin = tvg::Shape::gen();
    pin->appendCircle(clockCx, clockCy, 6, 6);
    pin->fill(30, 30, 30);
    canvas->add(pin);

    float pivotX = clockCx;
    float pivotY = clockCy + clockR + 10;
    float pendLen = clockR * 1.6f;

    pendulum = tvg::Scene::gen();

    auto rod = tvg::Shape::gen();
    rod->moveTo(0, 0);
    rod->lineTo(0, pendLen);
    rod->strokeFill(120, 120, 120);
    rod->strokeWidth(4);
    pendulum->add(rod);

    auto bob = tvg::Shape::gen();
    bob->appendCircle(0, pendLen, 20, 20);
    bob->fill(200, 160, 40);
    bob->strokeFill(120, 90, 20);
    bob->strokeWidth(2);
    pendulum->add(bob);

    pendulum->translate(pivotX, pivotY);
    canvas->add(pendulum);

    return true;
}

bool update(tvg::Canvas* canvas, uint32_t elapsed) override
{
    float t = float(elapsed) * 0.001f;
    pendulum->rotate(20.0f * sinf(t * 2.0f));

    secHand->rotate(fmodf(t * 60.0f, 360.0f));   //1 turn / 6 sec
    minHand->rotate(fmodf(t * 6.0f, 360.0f));    //1 turn / 60 sec
    hourHand->rotate(fmodf(t * 0.5f, 360.0f));   //1 turn / 720 sec

    canvas->update();

    return true;
}

};

int main(int argc, char **argv) { return tvgexam::main(new MyExample, argc, argv, true, 400, 600); }`

content()에서 도형을 한 번만 생성해 캔버스에 등록하고, update()에서 매 프레임 각도만 갱신하는 tvgexam::Example의 애니메이션 패턴을 사용했습니다.

Paint::rotate()는 항상 도형 로컬 좌표의 (0,0)을 축으로 회전한다는 점을 이용해, 시침/분침/초침을 원점에서 위로 뻗은 모양으로 그린 뒤 translate()로 시계 중심 위치로 옮겼습니다. 이렇게 하면 rotate() 호출만으로 시계 중심을 축으로 도는 바늘을 만들 수 있습니다.

추(진자)는 막대와 원(bob) 두 도형을 하나의 Scene으로 묶어, 같은 축을 기준으로 함께 회전하도록 했습니다.

추의 왕복 운동은 20.0f * sinf(t * 2.0f)로 구현했습니다. sin 함수가 -11 사이를 부드럽게 오가는 성질을 이용해 -20도+20도 사이를 계속 왕복시켜, 실제 진자의 조화진동에 가까운 움직임을 만들었습니다.

시침/분침/초침은 실제 시:분:초 비율(60배속 관계)은 유지하되, 데모로 빠르게 보이도록 속도를 임의로 올려서 애니메이션했습니다. 도형 속성을 바꾼 뒤에는 반드시 canvas->update()를 호출해야 변경 사항이 화면에 반영됩니다.

처음에는 회전축을 Picture 전용 API인 origin()으로 잡으려다 컴파일 에러(has no member named ‘origin’)가 나서, API 문서를 다시 확인해 translate() + 로컬 좌표 방식으로 수정했습니다.

https://github.com/user-attachments/assets/db0fcbec-041c-4c50-957f-63de4afb99df https://github.com/mnchn-prog/thorvg.example

댓글

Discussion 원문