用簡單線性插值實現有趣的曲線與動畫

                <<用簡單線性插值實現有趣的曲線與動畫>>
                Tags: opengl, 3d, c, linux

1. 要實現什麼?

    爲了更好說明, 先看一下我們要實現的是什麼, 既然是圖形的效效果, 自然看圖最能
    說明問題, 清楚明瞭.

    1)  要畫出的曲線:

        ./bmp_line/sshot_20.bmp

                   


    2)  要實現的動畫:

        ./bmp_line/animated.gif

                    


2. 幾何分析

    ./lerp.note.bmp

             


    從圖中我們可以看到, 我們最終要畫出的是紅色的曲線, 而該紅色曲線是顯然是由一
    些線段連接而成的. 要確定一條線段不外乎於找出其兩個端點. 好了, 我們現在的問
    題已經很明確了: 找端點. 怎麼找呢? 聯繫上圖, 可知這些端點是各個斜線與橫線的
    交點. 所以這一問題被轉化爲: 求兩直線的交點.


3. 兩個直線相關的函數

3.1 已知兩點 求經過這兩點的直線

    直線方程是:     y = k*x + b

    現已知兩點(x1, y1)(x2, y2), 求k和b:

        y1 = k*x1 + b \       / k = (y2 - y1) /(x2 - x1)
                       | ==> |
        y2 = k*x2 + b /       \ b = y1 - k*x1

3.2 求兩直線的交點

    直線方程是:     y = k*x + b

    現已知兩條直線,
        y = k1*x + b1
        y = k2*x + b2
    求它們的交點(x, y):

        y = k1*x + b1 \      / x = (b2 -b1) / (k1 - k2)
                       | ==> |
        y = k2*x + b2 /      \ y = k1*x + b1

3.3 代碼實現

/*
 * y = k*x + b
 *
 * y1 = k*x1 + b \       / k =(y2 - y1) / (x2 - x1)
 *                | ==> |
 * y2 = k*x2 + b /       \ b = y1 - k*x1
 *
 * */
int  N3D_lineConstruct(float x1, float y1, float x2, float y2,
        float *k, float *b)
{
    assert( !(x1 == x2 && y1 == y2) );

    if ( x1 == x2 )
    {
        return 0;
    }

    *k = (y2 - y1) / (x2 - x1);
    *b = y1 - (*k) * x1;

    return 1;
}

/*
 * y = k*x + b
 *
 * y = k1*x + b1 \      / x =(b2 -b1) / (k1 - k2)
 *                | ==> |
 * y = k2*x + b2 /      \ y = k1*x + b1
 *
 * */
int  N3D_lineInsertPos(float k1, float b1, float k2, float b2,
        float *x, float *y)
{
    if ( k1 == k2 )
    {
        return 0;
    }

    *x = (b2 - b1) / (k1 - k2);
    *y = k1 * (*x) + b1;

    return 1;
}




4. 開始實現我們要實現的東東

4.1 曲線(靜態的)
typedef struct N3D_Vertex
{
    float fX;
    float fY;
    float fZ;
    float fS;
    float fT;
} N3D_Vertex;

typedef struct N3D_GodPos
{
    float mfTarX;       // 控制軸(圖中的黑實線)的底端端點的x座標
    float mfTarY;       // 控制軸(圖中的黑實線)的底端端點的y座標
    float mfTarW;       // 控制軸寬
    float mfTarH;       // 控制軸高

    int   mnFramExpend; // 動畫後半部分的幀數
    int   mnDivY;       // 動畫前半部分的幀數

    N3D_Vertex  *mvVex;     // 頂點數據
    float       *mvHelpX;   // 用於輔助動畫的後半部分
} N3D_GodPos;

N3D_GodPos      g_godPos = {
    0.0,
    -1.0,
    0.0,
    1.0,

    10,
    20,

    NULL,
    NULL,
};


先定義並初始化一個我們要實現的曲線: g_godPos !


void N3D_godUpdatePos(N3D_GodPos *god)
{
    assert(god != NULL && god->mvVex != NULL && god->mnDivY > 0);

    int     i;
    float   fCcen = god->mfTarH / (float)god->mnDivY;
    float   fEcen = 2.0 / (float)god->mnDivY;
    float   fTexCen = 1.0 / (float)god->mnDivY;

    for ( i = 0; i <= god->mnDivY; i++ )
    {
        N3D_godSTcalCurvePos(god, i, fCcen, fEcen, fTexCen);
    }
}



    圖: ./bmp_point/sshot_20.bmp

         


我們先實現一個用於求畫出最終曲線的頂點數據的一個函數: N3D_godUpdatePos,按行求,
(god->mnDivY + 1)行, 其最終調用的是 N3D_godSTcalCurvePos,
static void N3D_godSTcalCurvePos(N3D_GodPos *god, int i, float fCcen,
        float fEcen, float fTexCen)
{
    assert(god != NULL && god->mvVex != NULL && god->mnDivY > 0);
    assert(i >= 0 && i <= god->mnDivY);

    // Normal Rect: [(-1.0, 1.0), (1.0, -1.0)], size is 2.0
    const float     rectLTX = -1.0;
    const float     rectLTY =  1.0;
    const float     rectBRX =  1.0;
    const float     rectBRY = -1.0;
    float Cx1 = god->mfTarX;
    float Cy1 = god->mfTarY + fCcen * i;
    float CxL = rectLTX;
    float CyL = rectLTY;
    float CxR = rectBRX;
    float CyR = rectLTY;
    float Ex1 = -1.0;
    float Ey1 = rectBRY + fEcen * i;
    float Ex2 =  0.0;
    float Ey2 = Ey1;
    float Ck, Cb, Ek, Eb, insPosXL, insPosYL, insPosXR, insPosYR;
    N3D_Vertex  *pV = god->mvVex;

    if ( Cx1 == CxL ) CxL += 0.00001;
    N3D_lineConstruct(Cx1, Cy1, CxL, CyL, &Ck, &Cb);
    N3D_lineConstruct(Ex1, Ey1, Ex2, Ey2, &Ek, &Eb);
    if ( Ck == Ek ) Ek += 0.00001;
    N3D_lineInsertPos(Ck, Cb, Ek, Eb, &insPosXL, &insPosYL);

    Cx1 += god->mfTarW;
    if ( Cx1 == CxR ) CxR += 0.00001;
    N3D_lineConstruct(Cx1, Cy1, CxR, CyR, &Ck, &Cb);
    if ( Ck == Ek ) Ek += 0.00001;
    N3D_lineInsertPos(Ck, Cb, Ek, Eb, &insPosXR, &insPosYR);

    pV[2*i].fX = insPosXL;
    pV[2*i].fY = insPosYL;
    pV[2*i].fZ = 0.0;
    pV[2*i].fS = 0.0;
    pV[2*i].fT = fTexCen * i;

    pV[2*i+1].fX = insPosXR;
    pV[2*i+1].fY = insPosYR;
    pV[2*i+1].fZ = 0.0;
    pV[2*i+1].fS = 1.0;
    pV[2*i+1].fT = fTexCen * i;
}



用求兩直線的交點的方法求出交點並保存到god->mvVex中.

接下來, 就是渲染了, 有了頂點數據, 渲染就是很明瞭的事:
void N3D_godDraw(N3D_GodPos *god)
{
    assert(god != NULL && god->mvVex != NULL && god->mvHelpX != NULL
            && god->mnDivY > 0);

    int i;
    N3D_Vertex *pV = god->mvVex;

    glBegin(GL_TRIANGLE_STRIP); {
        for ( i = 0; i <= god->mnDivY; i++ )
        {
            glTexCoord2f(pV[2*i].fS, pV[2*i].fT);
            glVertex3f(pV[2*i].fX, pV[2*i].fY, pV[2*i].fZ);
            glTexCoord2f(pV[2*i+1].fS, pV[2*i+1].fT);
            glVertex3f(pV[2*i+1].fX, pV[2*i+1].fY, pV[2*i+1].fZ);
        }
    } glEnd();
}




4.2 動畫

有了我們前面的分析與實現, 動畫也就很容易地實現了. 先看一下原理:

    對整個過程共分(god->mnDivY+1)幀渲染, 第一幀畫最前面一行, 第二幀畫最前面兩
    行, ...

static void N3D_godSTdrawAminDemo(N3D_GodPos *god)
{
    assert(god != NULL && god->mvVex != NULL && god->mvHelpX != NULL
            && god->mnDivY > 0 && god->mnFramExpend > 0);

    int j;

    N3D_godinit(&g_godPos, 0);
    for ( j = 0; j <= god->mnDivY + god->mnFramExpend; j++ )
    {
        N3D_godClear();

        glPushMatrix(); {
            glEnable(GL_TEXTURE_2D);
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            glPointSize(3);
            glColor4f(1.0, 1.0, 0.0, 1.0);
            glScalef(0.5, 0.5, 0.5);
            N3D_godDrawAminByLine(god, j);
        } glPopMatrix();

        N3D_godFlush();
        usleep(20 * 1000);
    }
}

void N3D_godDrawAminByLine(N3D_GodPos *god, int curLine)
{
    assert(god != NULL && god->mvVex != NULL && god->mvHelpX != NULL
            && god->mnDivY > 0 && god->mnFramExpend > 0);
    assert(curLine >= 0 && curLine <= god->mnDivY + god->mnFramExpend);

    if ( curLine <= god->mnDivY )
    {
        N3D_godUpdatePosByLine(god, curLine);
    }
    else
    {
        N3D_godSTupdateFramExpendPos(god, curLine);
    }
    N3D_godDrawByLine(god, curLine);
}




5. 其它曲線

線性插值的好處就是, 不用先知道曲線的方程, 就能畫出來, 並且其計算量也明顯沒有那
麼大.

N3D_GodPos g_godPos = {  0.0, -1.0,  0.3,  1.0,   10, 20, NULL};  // 樹型

    圖: ./animated-tree.gif

        


N3D_GodPos g_godPos = {  0.0, -0.4,  0.3,  1.0,   10, 20, NULL};  // 交叉型

    圖: ./animated-cross.gif

         


N3D_GodPos g_godPos = {  0.0, -1.0,  0.3, -3.0,   10, 20, NULL};  // 碗型

    圖: ./animated-bowl.gif

        


N3D_GodPos g_godPos = {  0.0, -0.1,  0.1, -3.0,   10, 20, NULL};  // 酒杯型

    圖: ./animated-glass.gif

               



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章