SDL2 で円を描画する

 SDL2 の描画は、点、線、矩形の描画はできますが、円は描画ができません。円を描画する関数を作成してみます。

 円は、円の座標を計算して、そこに点を描画して表現します。円の方程式は以下です。

円の方程式

 r は半径です。以下のような円です。

 これは、原点を中心とした円の方程式です。上の式を変形して、x から y を求めます。

x 座標から y 座標を求める

 上の式から y を求めれば、xy 座標が決まるのでその位置に点を描きます。ただし、計算で求めた座標は、原点を中心とした直交座標での座標なので、これをスクリーン座標に変えなければなりません。

 まず、円の方程式から、直交座標で点を打つ座標を求めます。この場合、x 座標は、-r 〜 r まで変化することになります。

    for( x = -r; x <= r; x++ ) {
        y = sqrt(r * r + x * x) + 0.5;
    }

 これを、スクリーン座標の (sx, sy) を中心に描きます。

    for( x = -r; x <= r; x++ ) {
        y = sqrt(r * r + x * x) + 0.5;
        px = sx + x;
        py = sy - y;
        SDL_RenderDrawPoint(renderer, px, py);
    }

 結果は次のような上半分の円になります。

半円

 下側は、-y になるので、それも描きます。

    for( x = -r; x <= r; x++ ) {
        y = sqrt(r * r + x * x) + 0.5;
        px = sx + x;
        py = sy - y;
        SDL_RenderDrawPoint(renderer, px, py);
        py = sy + y;
        SDL_RenderDrawPoint(renderer, px, py);
    }

 結果は次のようになります。

 円のようになりましたが、左右の部分がつながっていません。これは、x が 1 変化する際に左右に近い部分では、y が 1 より大きく変化しているからです。この場合は、y を基準に計算すれば良いことになります。

    /* x を基準に点を描画 */
    for( x = -r; x <= r; x++ ) {
        y = sqrt(r * r + x * x) + 0.5;
        px = sx + x;
        py = sy - y;
        SDL_RenderDrawPoint(renderer, px, py);
        py = sy + y;
        SDL_RenderDrawPoint(renderer, px, py);
    }
    /* y を基準に点を描画 */
    for( y = -r; y <= r; y++ ) {
        x = sqrt(r * r + y * y) + 0.5;
        px = sx + x;
        py = sy - y;
        SDL_RenderDrawPoint(renderer, px, py);
        px = sx - y;
        SDL_RenderDrawPoint(renderer, px, py);
    }

 これで次のようなきれいな円になります。

完全な円

 塗りつぶした円は、x 座標を基準に、-y 〜 y まで直線を引けば塗りつぶされます。

for( x = -r; x <= r; x++ ) {
    y = sqrt(r * r + x * x) + 0.5;
    px = sx + x;
    py1 = sy - y;
    py2 = sy + y;
    SDL_RenderDrawLine(renderer, px, py1, px, py2);
}
円の塗りつぶし

 これらを関数にまとめます。

void draw_circle(SDL_Renderer *renderer, int sx, int sy, int r) {
    int x, y, px, py;

    /* x を基準に点を描画 */
    for( x = -r; x <= r; x++ ) {
       y = sqrt(r*r - x*x) + 0.5;
       px = sx + x;
       py = sy - y;
       SDL_RenderDrawPoint(renderer, px, py);
       py = sy + y;
       SDL_RenderDrawPoint(renderer, px, py);
    }
    /* y を基準に点を描画 */
    for( y = -r; y <=r; y++ ) {
       x = sqrt(r*r - y*y) + 0.5;
       px = sx + x;
       py = sy - y;
       SDL_RenderDrawPoint(renderer, px, py);
       px = sx - x;
       SDL_RenderDrawPoint(renderer, px, py);
    }
}

void fill_circle(SDL_Renderer *renderer, int sx, int sy, int r) {
    int x, y, px, py1, py2;

    for( x = -r; x <= r; x++ ) {
       y = sqrt(r*r - x*x) + 0.5;
       px = sx + x;
       py1 = sy - y;
       py2 = sy + y;
       SDL_RenderDrawLine(renderer, px, py1, px, py2);
    }
}

 これで円を描けるようになったので、アニメーションさせてみます。

#include <stdio.h>
#include <SDL.h>

void draw_circle(SDL_Renderer *renderer, int sx, int sy, int r) {
    int x, y, px, py;

    /* x を基準に点を描画 */
    for( x = -r; x <= r; x++ ) {
       y = sqrt(r*r - x*x) + 0.5;
       px = sx + x;
       py = sy - y;
       SDL_RenderDrawPoint(renderer, px, py);
       py = sy + y;
       SDL_RenderDrawPoint(renderer, px, py);
    }
    /* y を基準に点を描画 */
    for( y = -r; y <=r; y++ ) {
       x = sqrt(r*r - y*y) + 0.5;
       px = sx + x;
       py = sy - y;
       SDL_RenderDrawPoint(renderer, px, py);
       px = sx - x;
       SDL_RenderDrawPoint(renderer, px, py);
    }
}

void fill_circle(SDL_Renderer *renderer, int sx, int sy, int r) {
    int x, y, px, py1, py2;

    for( x = -r; x <= r; x++ ) {
       y = sqrt(r*r - x*x) + 0.5;
       px = sx + x;
       py1 = sy - y;
       py2 = sy + y;
       SDL_RenderDrawLine(renderer, px, py1, px, py2);
    }
}

void *draw(void *param) {
    SDL_Renderer *renderer;
    int x, y, r;
    static int d = 0;

    renderer = (SDL_Renderer *)param;

    /* 描画 */
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
    SDL_RenderClear(renderer);
    SDL_SetRenderDrawColor(renderer, 0xff, 0xff, 0xff, SDL_ALPHA_OPAQUE);

    /* 円描画 */
    x = 10 + d;
    y = 10 + d*3/4;
    r = 10;
    draw_circle(renderer, x, y, r);
    SDL_SetRenderDrawColor(renderer, 0, 0xff, 0xff, SDL_ALPHA_OPAQUE);
    y = 230 - d*3/4;
    fill_circle(renderer, x, y, r);

    d = (d + 10) %310;

    /* バッファをウィドウに反映 */
    SDL_RenderPresent(renderer);
}

Uint32 draw_timer(Uint32 interval, void *param)
{
    SDL_Event event;
    SDL_UserEvent userevent;

    /* コールバックでSDL_USEREVENTイベントをキューに入れる。
    このコールバック関数は一定の周期で再び呼ばれる */

    userevent.type = SDL_USEREVENT;
    userevent.code = 0;
    userevent.data1 = &draw;
    userevent.data2 = param;

    event.type = SDL_USEREVENT;
    event.user = userevent;

    SDL_PushEvent(&event);
    return(interval);
}

int main(int argc, char* argv[]){
    SDL_Event event;
    SDL_Window *window;
    SDL_Renderer *renderer;
    SDL_TimerID my_timer_id;
    
    int quit_flg = 1;

    /* 初期化 */
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        fprintf(stderr, "SDL_Init(): %s\n", SDL_GetError());
        exit(1);
    }

    /* ウィンドウ作成 */
    window = SDL_CreateWindow("SDL Test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 320, 240, SDL_WINDOW_OPENGL);
    if( window == NULL ) {
        printf("Can not create window\n");
        exit(1);
    }
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if( renderer == NULL ) {
        printf("Can not create renderer\n");
        exit(1);
    }
    /* 強制終了時に SDL_Quit() を呼ぶ */
    atexit(SDL_Quit);

    /* タイマー設定 */
    my_timer_id = SDL_AddTimer(200, draw_timer, (void *)renderer);

    /* イベントループ */
    while(quit_flg) {
        while( SDL_PollEvent(&event) ) {
            switch (event.type) {
            /* タイマー処理 */
            case SDL_USEREVENT: {
                void (*p) (void*) = event.user.data1;
                p(event.user.data2);
                break;
            }

            /* 終了 */
            case SDL_KEYDOWN:
            case SDL_QUIT:
                quit_flg = 0;
                break;
            }
        }
        /* イベントがない場合、少し待つ */
        SDL_Delay(50);
    }
    if( my_timer_id ) SDL_RemoveTimer(my_timer_id);
    if( renderer ) SDL_DestroyRenderer(renderer);
    if( window ) SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;
}
円のアニメーション

 次は、楕円を書いてみたいと思います。

SDL2

Posted by sirius