📜  在 OpenGL 中使用变换创建动画

📅  最后修改于: 2021-10-21 05:27:21             🧑  作者: Mango

动画是让我们认为一个物体真的在屏幕上移动的错觉。但在它们下面只是更新和绘制不同对象的复杂算法。

目标: 2D 中行走的恐龙的复杂动画。
方法:使用单个身体部位的变换。
看这个视频:

OPENGL 中

算法

计算机图形学中有 3 种主要变换——平移、旋转和缩放。所有这些都可以使用非常简单的数学来实现。

Translation: X = x + tx,    tx is the amount of translation in x-axis
             Y = y + ty,    ty is the amount of translation in y-axis

Rotation: X = xcosA - ysinA,   A is the angle of rotation.
          Y = xsinA + ycosA

Scaling: X = x*Sx,      Sx is Scaling Factor
         Y = y*Sy,    Sy is Scaling Factor

对于绘制图形,将使用 Bresenham 的 Line 绘制算法与上述等式一起根据需要绘制每条线。

执行

恐龙的身体分为8个主要部分——
Head、upperBody、Tail、downBody 和四条腿

零件存储为带有逗号分隔坐标的文本文件,在程序运行期间导入:

  • 头迪诺
  • 保利迪诺
  • 尾巴恐龙
  • 后腿FDino
  • 后腿RDino
  • 迪诺
  • 迪诺
  • 前腿FDino
  • 前腿RDino

每个对象的旋转中心都存储在一个单独的文件中:

  • 中心点

由于所有文件都是手工创建的,因此在以下文件中更正了一些错误:

  • 偏移恐龙

注意:请在运行程序前下载上述所有文件并保存在同一目录中。
每个人都有自己的对象存储以下内容:

  • 对象的所有行在一个大数组中
  • 行数
  • 当前翻译量
  • 旋转中心
  • 偏移量
  • 当前旋转量
  • 旋转方向

该程序将按以下顺序运行:

  1. OpenGL 窗口将启动
  2. 所有文件将被读取并存储在各自的对象中
  3. 将启动无限循环
  4. 屏幕将被清除
  5. 将画一条线描绘草原
  6. 所有部分都会更新
  7. 将绘制所有零件
  8. 身体平移值会递减,如果恐龙在窗外,则会重置。
  9. 循环将进入下一次迭代

在对象更新期间,检查旋转状态并
如果超过旋转阈值,则反转旋转方向。
如果不旋转,则旋转状态为 0。

#include 
#include 
#include 
  
// these are the parameters
#define maxHt 800
#define maxWd 600
#define maxLns 10000
#define transSpeed 1
#define rotSpeed 0.02
#define rotateLimit 0.2
#define boundLimitL -200
#define boundLimitR 500
#define grasslandy 230
  
// Structure for storing lines
typedef struct lines {
    int x1, x2, y1, y2;
} LINE;
  
// Object type structure for storing each body part
typedef struct objects {
    LINE edge[maxLns];
    int translation, cx, cy, xoffset, yoffset;
    float theta;
    int rotationState;
    int EdgeCount;
} Object;
  
// the different objects
Object Head, upBody, Tail, downBody, FlegF, FlegB, BlegF, BlegB;
// global
int dinoTranslate = 0;
  
// basic init function for OPENGL
void myInit(void)
{
    glClearColor(1.0, 1.0, 1.0, 0.0);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, maxHt, 0, maxWd);
    glClear(GL_COLOR_BUFFER_BIT);
}
  
// this function translates, and rotates a point according to an object and draws it
void rotateandshiftPt(int px, int py, Object obbj)
{
    int xf, yf;
  
    xf = obbj.cx + (int)((float)(px - obbj.cx) * cos(obbj.theta)) - ((float)(py - obbj.cy) * sin(obbj.theta));
    yf = obbj.cy + (int)((float)(px - obbj.cx) * sin(obbj.theta)) + ((float)(py - obbj.cy) * cos(obbj.theta));
    glBegin(GL_POINTS);
    glVertex2i(obbj.translation + xf + obbj.xoffset, yf + obbj.yoffset);
    glEnd();
}
  
// this function draws a line using Bresenhams
void drawLineBresenham(int x1, int y1, int x2, int y2, Object obbj)
{
    int Dx, Dy, Dxmul2, Dymul2, Pk, xtempi, ytempi;
    float lineSlope, xtemp, ytemp;
    Dx = abs(x2 - x1);
    Dy = abs(y2 - y1);
    Dxmul2 = 2 * Dx;
    Dymul2 = 2 * Dy;
    ytemp = (float)(y2 - y1);
    xtemp = (float)(x2 - x1);
    lineSlope = (ytemp / xtemp);
  
    if (lineSlope >= -1.0 && lineSlope <= 1.0) {
        Pk = Dymul2 - Dx;
        if (x1 > x2) {
            xtempi = x2;
            x2 = x1;
            x1 = xtempi;
            ytempi = y2;
            y2 = y1;
            y1 = ytempi;
        }
        for (xtempi = x1, ytempi = y1; xtempi <= x2; xtempi++) {
            rotateandshiftPt(xtempi, ytempi, obbj);
            if (Pk < 0) {
                Pk = Pk + Dymul2;
            } else {
                Pk = Pk + Dymul2 - Dxmul2;
                if (lineSlope >= 0.0 && lineSlope <= 1.0)
                    ytempi = ytempi + 1;
                else if (lineSlope < 0.0 && lineSlope >= -1.0)
                    ytempi = ytempi - 1;
            }
        }
    } else {
        Pk = Dxmul2 - Dy;
        if (y1 > y2) {
            xtempi = x2;
            x2 = x1;
            x1 = xtempi;
            ytempi = y2;
            y2 = y1;
            y1 = ytempi;
        }
        for (xtempi = x1, ytempi = y1; ytempi <= y2; ytempi++) {
            rotateandshiftPt(xtempi, ytempi, obbj);
            if (Pk < 0) {
                Pk = Pk + Dxmul2;
            } else {
                Pk = Pk + Dxmul2 - Dymul2;
                if (lineSlope > 1.0)
                    xtempi = xtempi + 1;
                else if (lineSlope < -1.0)
                    xtempi = xtempi - 1;
            }
        }
    }
}
// here all the edges are iterated and drawn
void drawObj(Object obbj)
{
    int i;
    for (i = 0; i < obbj.EdgeCount; i++) {
        drawLineBresenham(obbj.edge[i].x1, obbj.edge[i].y1, obbj.edge[i].x2, obbj.edge[i].y2, obbj);
    }
}
  
// in this function, an object is updated
void updateObj(Object* obbj)
{
    obbj->translation = dinoTranslate;
  
    if (obbj->rotationState == 1) {
        obbj->theta = obbj->theta + rotSpeed;
        if (obbj->theta >= (3.14159))
            obbj->theta = obbj->theta - (2.0 * 3.14159);
        if (obbj->theta > rotateLimit)
            obbj->rotationState = -1;
  
    } else if (obbj->rotationState == -1) {
        obbj->theta = obbj->theta - rotSpeed;
  
        if (obbj->theta <= (-3.14159))
            obbj->theta = (2.0 * 3.14159) + obbj->theta;
  
        if (obbj->theta < -rotateLimit)
            obbj->rotationState = 1;
    }
}
  
// The actual function where the Dinosaur is drawn
void drawDino(void)
{
    // an infinite while loop for moving the dinosaur
    while (1) {
        glClear(GL_COLOR_BUFFER_BIT);
        // draw grassland
        glLineWidth(5.0);
        glColor3f(0.0f, 1.0f, 0.3f);
        glBegin(GL_LINES);
        glVertex2i(0, grasslandy);
        glVertex2i(maxHt, grasslandy);
        glEnd();
        glPointSize(3.0);
        glColor3f(0.9f, 0.5f, 0.6f);
        // update all parts
  
        updateObj(&Head);
        updateObj(&upBody);
        updateObj(&Tail);
        updateObj(&downBody);
        updateObj(&FlegF);
        updateObj(&FlegB);
        updateObj(&BlegF);
        updateObj(&BlegB);
  
        // draw all parts, also draw joining parts
        drawObj(Head);
        drawObj(upBody);
        drawObj(Tail);
        drawObj(downBody);
        drawObj(FlegF);
        drawObj(FlegB);
        drawObj(BlegF);
        drawObj(BlegB);
  
        dinoTranslate--; // decreased because moving forward
        if (dinoTranslate <= boundLimitL) {
            dinoTranslate = boundLimitR;
            printf("\ntranslate %d", dinoTranslate);
        }
        printf("\ntranslate %d", dinoTranslate);
        glFlush();
    }
}
  
// TAn object is stored using this function
void storeObj(char* str, Object* obbj)
{
    obbj->theta = 0.0;
  
    FILE* fp;
    fp = fopen(str, "r");
    if (fp == NULL) {
        printf("Could not open file");
        return;
    }
    obbj->EdgeCount = 0;
    int count = 0, x1, y1, x2, y2;
    while (!feof(fp)) {
        count++;
        if (count > 2) {
            x1 = x2;
            y1 = y2;
            count = 2;
        }
        if (count == 1) {
            fscanf(fp, "%d, %d", &x1, &y1);
        } else {
            fscanf(fp, "%d, %d", &x2, &y2);
            printf("\n%d, %d", x2, y2);
            obbj->edge[obbj->EdgeCount].x1 = x1;
            obbj->edge[obbj->EdgeCount].y1 = y1;
            obbj->edge[obbj->EdgeCount].x2 = x2;
            obbj->edge[obbj->EdgeCount].y2 = y2;
            obbj->EdgeCount++;
        }
    }
  
    // printf("\nPolygon stored!");
    fclose(fp);
}
  
// All parts are stored.
void storeAllParts()
{
    FILE* fp, *fp2;
    int cx, cy;
    fp = fopen("centrePts.txt", "r");
    fp2 = fopen("offsetDino.txt", "r");
    if (fp == NULL || fp2 == NULL) {
        printf("Could not open file");
        return;
    }
    // parts
    //----------------
    // head+neck
    storeObj("headDino.txt", &Head);
    fscanf(fp, "%d, %d", &cx, &cy);
    Head.cx = cx;
    Head.cy = cy;
    fscanf(fp2, "%d, %d", &cx, &cy);
    Head.xoffset = cx;
    Head.yoffset = cy;
    Head.rotationState = 1;
  
    // upper body boundary(only translation)
    storeObj("bodyupDino.txt", &upBody);
    upBody.cx = 0;
    upBody.cy = 0;
    fscanf(fp2, "%d, %d", &cx, &cy);
    upBody.xoffset = cx;
    upBody.yoffset = cy;
    upBody.rotationState = 0;
  
    // tail
    storeObj("tailDino.txt", &Tail);
    fscanf(fp, "%d, %d", &cx, &cy);
    Tail.cx = cx;
    Tail.cy = cy;
    fscanf(fp2, "%d, %d", &cx, &cy);
    Tail.xoffset = cx;
    Tail.yoffset = cy;
    Tail.rotationState = -1;
  
    // back leg front
    storeObj("backlegFDino.txt", &BlegF);
    fscanf(fp, "%d, %d", &cx, &cy);
    BlegF.cx = cx;
    BlegF.cy = cy;
    fscanf(fp2, "%d, %d", &cx, &cy);
    BlegF.xoffset = cx;
    BlegF.yoffset = cy;
    BlegF.rotationState = -1;
  
    // back leg rear
    storeObj("backlegRDino.txt", &BlegB);
    fscanf(fp, "%d, %d", &cx, &cy);
    BlegB.cx = cx;
    BlegB.cy = cy;
    fscanf(fp2, "%d, %d", &cx, &cy);
    BlegB.xoffset = cx;
    BlegB.yoffset = cy;
    BlegB.rotationState = 1;
  
    // lower body boundary(only translation)
    storeObj("bodydownDino.txt", &downBody);
    downBody.cx = 0;
    downBody.cy = 0;
    fscanf(fp2, "%d, %d", &cx, &cy);
    downBody.xoffset = cx;
    downBody.yoffset = cy;
    downBody.rotationState = 0;
  
    // front leg rear
    storeObj("frontlegRDino.txt", &FlegB);
    fscanf(fp, "%d, %d", &cx, &cy);
    FlegB.cx = cx;
    FlegB.cy = cy;
    fscanf(fp2, "%d, %d", &cx, &cy);
    FlegB.xoffset = cx;
    FlegB.yoffset = cy;
    FlegB.rotationState = -1;
  
    // front leg front
    storeObj("frontlegFDino.txt", &FlegF);
    fscanf(fp, "%d, %d", &cx, &cy);
    FlegF.cx = cx;
    FlegF.cy = cy;
    fscanf(fp2, "%d, %d", &cx, &cy);
    FlegF.xoffset = cx;
    FlegF.yoffset = cy;
    FlegF.rotationState = 1;
  
    //------------------------
    fclose(fp);
}
  
void main(int argc, char** argv)
{
  
    storeAllParts();
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize(maxHt, maxWd);
    glutInitWindowPosition(0, 0);
    glutCreateWindow("Walking dinosaur");
    myInit();
    glutDisplayFunc(drawDino); // actual loop call
    glutMainLoop();
}

输出:
这是一个示例屏幕截图:

在运行程序之前不要忘记下载文件。