动画是让我们认为一个物体真的在屏幕上移动的错觉。但在它们下面只是更新和绘制不同对象的复杂算法。
目标: 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
每个对象的旋转中心都存储在一个单独的文件中:
- 中心点
由于所有文件都是手工创建的,因此在以下文件中更正了一些错误:
- 偏移恐龙
注意:请在运行程序前下载上述所有文件并保存在同一目录中。
每个人都有自己的对象存储以下内容:
- 对象的所有行在一个大数组中
- 行数
- 当前翻译量
- 旋转中心
- 偏移量
- 当前旋转量
- 旋转方向
该程序将按以下顺序运行:
- OpenGL 窗口将启动
- 所有文件将被读取并存储在各自的对象中
- 将启动无限循环
- 屏幕将被清除
- 将画一条线描绘草原
- 所有部分都会更新
- 将绘制所有零件
- 身体平移值会递减,如果恐龙在窗外,则会重置。
- 循环将进入下一次迭代
在对象更新期间,检查旋转状态并
如果超过旋转阈值,则反转旋转方向。
如果不旋转,则旋转状态为 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();
}
输出:
这是一个示例屏幕截图:
在运行程序之前不要忘记下载文件。