文章目录
1. 介绍
本文章将以CPT205 计算机图形学的第一次大作业为例,进行OpenGL的2D实践。
这次作业是要求学生制作一个2D的生日贺卡,从而检验学生对OpenGl的2D掌握程度。这里涉及的知识点主要是3-5章的内容(相关知识点参考链接CPT205计算机图形学Pt.1)。
考察不仅需要学生能够展现出你对知识的掌握程度,即你要尽量尝试所学的所有东西,另一方面你要尝试做出好看的作品,毕竟这门功课是计算机图形学,图形学的发展正是为了给人们展现出他们想要表达的提供媒介。
我将以我自己的作业为例,介绍我是怎么做的,并指出我一些做的不好的地方,希望帮助到大家。
2. 设计
我们需要知道OpenGL是一个像素一个像素地绘制图形,最后的程序是我们先定义了一个投影矩阵,这个投影矩阵可以理解成有一个相机对着画布,我们规定了相机能看到画布的哪一部分。然后这个画布上将有很多对象和对象的变体,这些对象是基本图元组成的(比如2D的一个房子可以是一些三角形和矩形构成的)。
上面我们说的其实就是对应了图形学简介告诉你的想要展示物体的三个阶段。我们在建模阶段造出了房子这个对象,然后再根据这个对象变化成多个不一样的变体,这一步就是transformation,最后我们按照projection(投影)从而决定用户如何去看这个虚拟的世界。这就是制作一个项目的过程,但是我们提到OpenGL是一个像素一个像素地绘制图形,学会使用OpenGL肯定不是我们现在就能做到比如一些动漫之类的水平。里面人物之类的线条都很复杂,我们很难一个点一个点地去计算,这次的任务是贺卡所以不是很难,传统意义上的贺卡是我们用一张卡纸,对折一下,然后上面写上祝福的话,所以我们没有刚刚说的2D动画那么复杂。
那如何做的好看呢?我们可能会想到以前小时候上幼儿园画画,我们会在纸的左上角或者右上角用红色画一个四分之一的圆,然后画上几条线,那就是我们的太阳。然后我们用浅蓝色画几个波浪线,正着画一些,反着画一些,正着的和反着的连一起就是云。然后我们还会画树,还会画草。但肯定这样我们画出的结果放到现在的眼光来看肯定不是一个好看的作业。
我想的是一些好看的2D动画角色,一些简单的,比如任天堂的星之卡比,这个角色可爱又简单,很适合用来作为这次作业的核心元素。于是我想着做一个2D游戏的开场动画一样的生日贺卡作业。大概是用户是游戏的主角,当用户过生日了,卡比是用户的好朋友,所以卡比坐着自己的星星来到用户给用户递上自己的生日礼物(这里就可以是生日贺卡)。所以现在我们这里可以说是有三个场景,第一个场景用户看着天空上有一个星星(载着卡比的)划落天际,所以现在卡比就来到地球了。然后用户不断往星星坠落的地方走去,和卡比进行碰头,这是第二个场景。最后由于卡比成功和用户碰头,所以卡比将贺卡交给了用户,用户打开贺卡开始看贺卡有哪些内容,这是最后一个场景。我们便使用三个场景讲述了一个关于生日的小故事,并且完成了作业要求的2D生日贺卡。
下图展示了这三个场景。
第一个场景。
第二个场景。
第三个场景。
3. 准备阶段
首先,根据前面的设计方案,我们的这个项目的主角是星之卡比,而且星之卡比可能出现了不止一次,所以最好我们的卡比是一个可以复用的组件。而且选择星之卡比的一个优势是,星之卡比画起来跟别的可爱角色(如Hello Kitty、皮卡丘)相比要简单不少,星之卡比的身体是一个圆,手和脚是椭圆,眼睛也是椭圆,它还可以有脸颊,这也是椭圆。嘴巴的话得看卡比是什么表情,如果卡比的表情是惊讶,那就是个圆,但是这个表情很明显,不能一直用,卡比过来给用户过生日,应该是开心的表情,在相关作品中,一般是卡比张嘴的表情,张嘴的时候,卡比的嘴巴可以理解成多个椭圆在一起,如果我们把它们都当作水平的椭圆来看的话,上半部分的短轴要小于下半部分的短轴。
但从我们刚刚的一些举例中,我们发现不仅是我们可以复用卡比的部件,卡比是由很多重复的基础图元组成的,这些图元如果我们写成对应的方法,我们也可以复用很多次,从而减少重复的代码工作。比如我们可以写一个关于圆的方法,这个方法会接受两个参数参数从而确定圆的圆心,并且接受一个参数作为圆的半径,这样我们只需要调用这个方法并且搭配对应的参数我们就可以在我们想要的地方都可以画圆。在OpenGL中,我们在调用这个方法之前再指定一下颜色,就可以画出对应颜色与位置的圆了。
下面的代码演示了这样的一个方法。
//This function is used to draw a solid circle with a radius of "r" at positions "x0" and "y0"
void solidCircle(float x0, float y0, float r)
{
glBegin(GL_POLYGON);
for (int i = 0; i < 360; i++) {
float theta = angleRadian * float(i);
float x = r * cosf(theta);
float y = r * sinf(theta);
glVertex2f(x0 + x, y0 + y);
}
glEnd();
}
对应的应用如下。
//This function is used to draw the mouth of Kirby's stickers
void kirbyMouthShouting(float x0, float y0)
{
glColor3f(133 / 255.0f, 32 / 255.0f, 78 / 255.0f);
solidCircle(x0, y0 - 8, 3);
}
到这里有的人可能会想,比如前面展示的第二个场景中的卡比,这个卡比它的手和身体相连更像是一体的,两只脚更像是与身体是分开的,分开的好理解,前面连着的手和身体怎么做呢。我们可以将这里的建模过程想象成在一块画布上画画,或者我们打开ppt,我们会发现这个2D空间有很多图层,在图层上面的东西会盖住图层下面的东西,所以如果图层上面的东西比图层下面的东西大的话,图层下面的东西就会被图层上面的东西全部覆盖住。如果图层上面的东西比下面的东西小,那我们可以看到这两个在一起组合的结果。我们就将用这样的方式去完成这个卡比。
我们可以发现,这个卡比的左脚被身体遮挡了一部分,但是我为了方便起见,我这里用的不是卡比自身的方向,而是我们画面的方向,因此这个是右脚。画完右脚后我们可以画手,因为整个卡比都用线条进行了描边,如果我们先画身体再画手的话,那就会多出线条,因此我们先画手,在画身体,之后是卡比的眼睛,眼睛里的瞳孔或者说高亮也是同样的解决方案,先画外面的再花里面的,从而里面的置于外面的之上。嘴巴也是同样的道理,先画外面的嘴巴,再画里面的舌头。眼睛、脸颊、嘴巴三个部分都在身体之上,而这三个互相没有遮挡关系,所以前后顺序无所谓。然后左脚在最后画上去。这个卡比基本就完成了,我这里将身体和手之间再点了一个粉色小圆圈从而将手和身体连在了一起,这样看上去更自然,而且由于前面我们的准备工作,这一步很简单,但是效果很好,所以我建议你加上。
最后一个卡比就通过调用多个方法就能画成了,如下所示。
//Using the previous functions to draw a size of "size" Kirby at positions "x0" and "y0"
void kirby(float x0, float y0, float size)
{
kirbyRightFoot(x0, y0);
kirbyHands(x0, y0);
glColor3f(254 / 255.0f, 184 / 255.0f, 196 / 255.0f);
solidCircle(x0, y0, size);
glColor3f(128 / 255.0f, 94 / 255.0f, 93 / 255.0f);
hollowCircle(x0, y0, size, 2);
kirbyEyes(x0, y0);
kirbyHandsConnections(x0, y0);
kirbyFace(x0, y0);
kirbyMouth(x0, y0);
kirbyLeftFoot(x0, y0);
}
在我的代码中,我将准备工作中这些基础图元分成了两类,有填充(实心,solid)、无填充(空心,hollow)。比如卡比的描边是无填充在外描边,而用有填充将描边的线里面填充起来。无填充使用的是GL_LINE_LOOP,而有填充使用的是GL_POLYGON。
//This function is used to draw a hollow circle with a width of "lineWidth" and a radius of "r" at positions "x0" and "y0"
void hollowCircle(float x0, float y0, float r, float lineWidth)
{
glLineWidth(lineWidth);
glBegin(GL_LINE_LOOP);
for (int i = 0; i < 360; i++) {
float theta = angleRadian * float(i);
float x = r * cosf(theta);
float y = r * sinf(theta);
glVertex2f(x0 + x, y0 + y);
}
glEnd();
}
//This function is used to draw a solid circle with a radius of "r" at positions "x0" and "y0"
void solidCircle(float x0, float y0, float r)
{
glBegin(GL_POLYGON);
for (int i = 0; i < 360; i++) {
float theta = angleRadian * float(i);
float x = r * cosf(theta);
float y = r * sinf(theta);
glVertex2f(x0 + x, y0 + y);
}
glEnd();
}
当然你也可以使用前面说的方法,只用一个有填充的实心圆,用描边的颜色画一个大圆,然后再用填充的颜色画一个小点的圆。我觉得那么做有点浪费运行效率,所以我将它拆开来了。你可以有你自己的想法。
像前面说的嘴巴是由两个半个椭圆组成的,所以我关于圆之类的方法还有一个版本它会接受额外两个参数,从而确定它是从哪个角度开始,到哪个角度结束。
如下面的无填充圆所示。
//This function is used to draw a not whole hollow circle with a width of "lineWidth" and radius of "r" at positions "x0" and "y0", it will beign with "angleStart" and end with "angleEnd"
void holidCircleAngle(float x0, float y0, float r, int angleIStart, int angleEnd, float lineWidth)
{
glLineWidth(lineWidth);
glBegin(GL_LINE_STRIP);
for (int i = angleIStart; i < angleEnd; i++) {
float theta = angleRadian * float(i);
float x = r * cosf(theta);
float y = r * sinf(theta);
glVertex2f(x0 + x, y0 + y);
}
glEnd();
}
对于椭圆部分,我采用的依旧是建模的方法,其实也可以采取变换的方式,我们使用transformation里的scaling将其中x和y的变化取不同的值就可以让一个圆变成椭圆。
下面展示的是建模方法。
//This function is used to draw a hollow circle with a width of "lineWidth" ,"a" major axis, and "b" minor axis at positions "x0" and "y0"
void hollowEllipse(float x0, float y0, float a, float b, float lineWidth)
{
glLineWidth(lineWidth);
glBegin(GL_LINE_LOOP);
for (int i = 0; i < 360; i++) {
float theta = angleRadian * float(i);
glVertex2f(x0 + a * cosf(theta), y0 + b * sinf(theta));
}
glEnd();
}
这就是准备工作的大致介绍,我准备了一些基础图元的方法或者说建模的方法,这些方法将被调用以绘制具体的图形,然后我们将这些图形最后组合在一起形成最终的场景。
4. 场景切换
在正式开始搭建场景之前,我想做一个场景切换的功能,这个功能类似于PPT里的动画功能,当你从一页切换到下一页,然后一个动画效果,你来到了下一页。同理,我们本来在第一个场景,我们只需要按下切换的键后,我们就来到了下一个场景,但是突然出现下一个场景会很突兀,我们可以来一个场景切换的动画。由于我们的整个主题是星之卡比的,所以一个有关星星的动画是非常好的选择。
如下图所示,在第二个场景到第三个场景,会有一个星星先从大到小从而让整个画面清空(变为黑色),然后再从中间一个星星慢慢变大展现出下一个场景的全部信息。
我采用的依旧是建模的方式,只要找到五角星外面的五个点,和里面的五个点的参数方程,然后将这些参数方程与其他的点相连接组成一个多边形,这五个多边形相互遮挡,形成的剩余空间便是这个五角星。这个方法其实效果不是很好,而且需要进行计算以确定心仪的大小,效率不是很高,我在CW2中对这一方法进行了改进,使用了transformation将这五个遮挡部分使用了rotation和translation进行实现。
//This function is the first transmition animation
//Parameters "speed" and "transitionTime" are used to control the animation time
void starTransition(float speed, float transitionTime)
{
float changeLength = speed * transitionTime;
float R = 300 - changeLength;
float r = R / (sinf(36.0f * 3.1415926 / 180.0f) * cosf(18.0f * 3.1415926 / 180.0f) / sinf(18.0f * 3.1415926 / 180.0f) + cosf(36.0f * 3.1415926 / 180.0f));
glColor3f(0.0, 0.0, 0.0);
glBegin(GL_POLYGON);
glVertex2f(400 - r * sinf(36.0f * angleRadian), 300 + r * cosf(36.0f * angleRadian));
glVertex2f(400, 300 + R);
glVertex2f(400, 600);
glVertex2f(0, 600);
glVertex2f(400 - R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400 + r * sinf(36.0f * angleRadian), 300 + r * cosf(36.0f * angleRadian));
glVertex2f(400, 300 + R);
glVertex2f(400, 600);
glVertex2f(800, 600);
glVertex2f(400 + R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400, 300 - r);
glVertex2f(400 + R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glVertex2f(800, 0);
glVertex2f(0, 0);
glVertex2f(400 - R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400 + r * cosf(18.0f * angleRadian), 300 - r * sinf(18.0f * angleRadian));
glVertex2f(400 + R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glVertex2f(800, 600);
glVertex2f(800, 0);
glVertex2f(400 + R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400 - r * cosf(18.0f * angleRadian), 300 - r * sinf(18.0f * angleRadian));
glVertex2f(400 - R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glVertex2f(0, 600);
glVertex2f(0, 0);
glVertex2f(400 - R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glEnd();
//The first animation is end so it call the "transitionBackStart" to start the second animation
if (R < 0)
{
transitionBackStart();
}
}
//This function is the second transmition animation
//Parameters "speed" and "transitionTime" are used to control the animation time
void starTransitionBack(float speed, float transitionTime)
{
float changeLength = speed * transitionTime;
float R = changeLength;
float r = R / (sinf(36.0f * 3.1415926 / 180.0f) * cosf(18.0f * 3.1415926 / 180.0f) / sinf(18.0f * 3.1415926 / 180.0f) + cosf(36.0f * 3.1415926 / 180.0f));
glColor3f(0.0, 0.0, 0.0);
glBegin(GL_POLYGON);
glVertex2f(400 - r * sinf(36.0f * angleRadian), 300 + r * cosf(36.0f * angleRadian));
glVertex2f(400, 300 + R);
glVertex2f(400, 600);
glVertex2f(0, 600);
glVertex2f(400 - R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400 + r * sinf(36.0f * angleRadian), 300 + r * cosf(36.0f * angleRadian));
glVertex2f(400, 300 + R);
glVertex2f(400, 600);
glVertex2f(800, 600);
glVertex2f(400 + R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400, 300 - r);
glVertex2f(400 + R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glVertex2f(800, 0);
glVertex2f(0, 0);
glVertex2f(400 - R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400 + r * cosf(18.0f * angleRadian), 300 - r * sinf(18.0f * angleRadian));
glVertex2f(400 + R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glVertex2f(800, 600);
glVertex2f(800, 0);
glVertex2f(400 + R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400 - r * cosf(18.0f * angleRadian), 300 - r * sinf(18.0f * angleRadian));
glVertex2f(400 - R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glVertex2f(0, 600);
glVertex2f(0, 0);
glVertex2f(400 - R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glEnd();
//The second animation is end so it control the "transitionState" back to 0
if (R >= 300)
{
transitionState = 0;
}
}
我为了控制两个动画时间上一致,我使用了相同的参数,即第一个动画结束后,清空值后再运行第二个动画。所以这么做了,这么做很明显比较麻烦,这里可以简化成一个函数,即用一个相同的参数方程,然后控制参数在不同条件下采取不同的变化也可以,或者将这个方法里R的值在不同的条件下用不同的参数方程表示也可以。所以说我的方案并不是最佳的,还是有进步和改进的空间。
既然聊到了场景切换,那我们谈一下具体的场景展现函数上的细节。我们知道OpenGL呈现画面是由display函数控制的,display函数我们可以理解为画板,我们在这个画板上可以切换画布,每个画布上可以有很多图层。这些画布就是我们刚刚设计时构思的场景,然后画布上有多个图层从而构建出了我们想要的虚拟世界,这个画布也可以是我们刚刚做的动画。用这个移动的星星覆盖在原来的画布之上,从而完成这个动画效果。
具体代码如下。
//This function controls all the stages and animations will be shown in the windows
void display(void)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 800, 0, 600);
glMatrixMode(GL_MODELVIEW);
glClearColor(0, 50 / 255.0f, 99 / 255.0f, 1);
glClear(GL_COLOR_BUFFER_BIT);
//Show the stages according to the variable "stages"
if (stages == 0)
{
stage0();
}
if (stages == 1)
{
stage1();
}
if (stages == 2)
{
stage2();
}
//Show the transition animation according to the variable "transitionState"
if (transitionState == 1) {
starTransition(10, transitionTime);
}
if (transitionState == 2) {
starTransitionBack(10, transitionBackTime);
}
//Show the star following the mouse
mouseStar(mouseX, mouseY);
glutSwapBuffers();
}
一些基础的display设置这里就不再介绍,颜色的那两行可以理解成我在整个画板上放了一个蓝色的画布作为整个故事的背景。后面我用stages和transitionState两个变量作为flag从而控制了画布的控制。这个方法的思想和前面说的动画改进方案的第二种方案类似。这里还有一个星星跟随鼠标的设计,这一部分将在第八节(交互部分)进行介绍。
这里场景变换涉及到交互方法也将在第八节(交互部分)进行介绍,其余的一些更新变化的代码如下,比较简单。
//It change the variable "stages' to change the stage which should between the first transition animation and the second transition animation
//This function will be called when the first transition animation is over which is in the function "transitionBackStart"
//It also change the variable for the shining stars animation for the third stage
void stagesChange(void)
{
stages += 1;
stayTime = 0.0f;
if (stages == 2)
{
shining = true;
shiningStars = 0.0f;
}
}
//It change the variable "transitionState' to change the transition animation
//There are 3 value for the variable "transitionState'
//value 0 means no transition animation
//value 1 means the first transition animation
//value 2 means the second transition animation
void transitionStatesChange(void)
{
//To avoid skipping the second transition animation
if (transitionState == 0)
{
transitionState += 1;
}
}
//Update some values for the animation
void update(int value)
{
//It change the value for showing the texts;
stayTime += 0.2f;
//It change the values for stars moving and rotation which will be used for all the stages
kirbyStarPositionX -= 1.2f;
kirbyStarPositionY -= 0.9f;
for (int i = 0; i < starsNumber; i++) {
starsRotationAngle[i] += 0.05f;
}
for (int i = 0; i < starsNumber; i++) {
starsPositionChange[i] -= 0.4f;
starsHeights[i] -= 0.3f;
if (starsPositionChange[i] < 0 || starsHeights[i] < 0) {
if (i < 2) {
starsPositionChange[i] = 800;
starsHeights[i] = 400;
}
else if (i < 4) {
starsPositionChange[i] = 800;
starsHeights[i] = 300;
}
else if (i < 5) {
starsPositionChange[i] = 800;
starsHeights[i] = 350;
}
else if (i < 7) {
starsPositionChange[i] = 640;
starsHeights[i] = 600;
}
else if (i < 9) {
starsPositionChange[i] = 480;
starsHeights[i] = 600;
}
else if (i < 11) {
starsPositionChange[i] = 320;
starsHeights[i] = 600;
}
}
}
//It change the values for stars shining for the second stage
//This animation will control the stars shown to their biggest size and then narrow to unseen
if (stages == 1)
{
if (shiningStars < 18.0f)
{
shiningStars += 0.2f;
}
else
{
shiningTime += 0.2f;
}
if (shiningTime >= 18.0f)
{
shiningStars -= 0.4f;
}
if (shiningStars < 0)
{
shining = false;
}
}
//It change the values for stars shining for the third stage
//This animation will control the stars shining which alternates between enlargement and contraction
if (stages == 2)
{
if (shining)
{
shiningStars += 0.2f;
}
else
{
shiningStars -= 0.2f;
}
if (shiningStars <= 0)
{
shining = true;
}
if (shiningStars > 2)
{
shining = false;
}
}
//It change the time for showing the transition animations
if (transitionState == 1) {
transitionTime += 1.0f;
}
if (transitionState == 2) {
transitionBackTime += 1.0f;
}
glutPostRedisplay();
//Set 60fps
glutTimerFunc(1000 / 60, update, 0);
}
5. 第一个场景
第一个场景在设计的计划中是卡比乘坐星星来到用户的星球给用户庆祝生日,所以这一个场景其实可以不用出现卡比的,而是出现卡比的星星表示卡比坐着它的星星来到了用户所在的星球上。而星星首先肯定是动态的,我觉得流星之类的从右上角飞到左下角比较习惯,所以我是这么设计的。然后卡比的星星跟别的星星不是一样的星星,不仅是造型和大小不一样,而且这个星星有彩虹的尾巴效果,或者说这个星星划过去的时候会在原地留下彩虹。如下图所示。
这个地方同样用到了前面说的图层技巧,彩虹的这个左下移动点可以是直接一样的x坐标,然后我们用一个大的星星盖在上面,这样就可以减少我们的计算量。附近的星星也不是真的随机,而是提前设定好的几个位置与轨道(或者说斜率)从而移动的。
相关的代码如下。
//This function is showing the first stage
void stage0(void)
{
//Draw the static stars
starsScene();
//Draw the moving stars
movingStarsScne();
//Draw the rainbow of kirbystar
glColor3f(252 / 255.0f, 1 / 255.0f, 0 / 255.0f);
line(kirbyStarPositionX - 72 / 5, kirbyStarPositionY + 96 / 5, 800 - 72 / 5, 600 + 96 / 5, 12);
line(kirbyStarPositionX - 60 / 5, kirbyStarPositionY + 80 / 5, 800 - 60 / 5, 600 + 80 / 5, 12);
glColor3f(251 / 255.0f, 153 / 255.0f, 1 / 255.0f);
line(kirbyStarPositionX - 48 / 5, kirbyStarPositionY + 64 / 5, 800 - 48 / 5, 600 + 64 / 5, 12);
line(kirbyStarPositionX - 36 / 5, kirbyStarPositionY + 48 / 5, 800 - 36 / 5, 600 + 48 / 5, 12);
glColor3f(255 / 255.0f, 251 / 255.0f, 6 / 255.0f);
line(kirbyStarPositionX - 24 / 5, kirbyStarPositionY + 32 / 5, 800 - 24 / 5, 600 + 32 / 5, 12);
line(kirbyStarPositionX - 12 / 5, kirbyStarPositionY + 16 / 5, 800 - 12 / 5, 600 + 16 / 5, 12);
glColor3f(51 / 255.0f, 252 / 255.0f, 5 / 255.0f);
line(kirbyStarPositionX, kirbyStarPositionY, 800, 600, 12);
line(kirbyStarPositionX + 12 / 5, kirbyStarPositionY - 16 / 5, 800 + 12 / 5, 600 - 16 / 5, 12);
glColor3f(34 / 255.0f, 223 / 255.0f, 201 / 255.0f);
line(kirbyStarPositionX + 24 / 5, kirbyStarPositionY - 32 / 5, 800 + 24 / 5, 600 - 32 / 5, 12);
line(kirbyStarPositionX + 36 / 5, kirbyStarPositionY - 48 / 5, 800 + 36 / 5, 600 - 48 / 5, 12);
glColor3f(0 / 255.0f, 153 / 255.0f, 255 / 255.0f);
line(kirbyStarPositionX + 48 / 5, kirbyStarPositionY - 64 / 5, 800 + 48 / 5, 600 - 64 / 5, 12);
line(kirbyStarPositionX + 60 / 5, kirbyStarPositionY - 80 / 5, 800 + 60 / 5, 600 - 80 / 5, 12);
glColor3f(89 / 255.0f, 55 / 255.0f, 235 / 255.0f);
line(kirbyStarPositionX + 72 / 5, kirbyStarPositionY - 96 / 5, 800 + 72 / 5, 600 - 96 / 5, 12);
line(kirbyStarPositionX + 84 / 5, kirbyStarPositionY - 112 / 5, 800 + 84 / 5, 600 - 112 / 5, 12);
//Draw the Kirbystar
glColor3f(255 / 255.0f, 240 / 255.0f, 121 / 255.0f);
roundStars(kirbyStarPositionX, kirbyStarPositionY, 40);
//Show the text
if (stayTime > 20.0f)
{
glColor3f(1, 1, 1);
selectFont(48, ANSI_CHARSET, "Comic Sans MS");
glRasterPos2f(180, 75);
drawString("Click or Press \"e\" to continue");
}
}
其中动画再加载一段时间后才会出现下面的字Click or Press “e” to continue,而不是上来就展现,我觉得这样的设计更有沉浸感和交互感。可以让用户有更多时间去感受这个场景,提升沉浸感。然后这个字的提示告诉了用户交互方式,提供了基础的交互指引。
具体的交互将在第八节(交互部分)进行介绍。
6. 第二个场景
第二个场景设计的是卡比已经坐自己的星星来到了用户所在的星球,但是用户过去后仍和卡比有一段距离,卡比摆出了欢迎的姿势,而用户可以这时候走过去靠近卡比,从而与卡比进行到下一步,也就是卡比交给用户自己的礼物,然后用户打开这个贺卡进行查看。
所以先是卡比的一个登场,这里会有卡比的特殊的星星放大的特效,其实这个星星要是还有个旋转就更好。然后下面的字可以帮助用户了解这里的背景故事,原来是前面是卡比坐着自己的星星来了,所以就引入了下一步,用户走向卡比。
当卡比的星星特效没有之后,下面的字幕提示当然也是变化成了这里的操作提示,用户可以按w键从而靠近卡比。因此同理用户也可以按s键从而原理卡比,这里我做的也不是逐渐变化而是分层变化。逐渐变化就可能跟我们现在玩的一些wasd操作角色移动的游戏一样,我们按键会朝那个方向按我们按键的时间而决定移动的距离。我做的是按的次数,我认为这样有一种旧游戏的感觉。这里当然你也可以做成逐渐变化,甚至你的交互里有a和d键的移动。由于我采用的是分层变化,所以我这里依旧用前面的建模方法来负责这里的动画效果。其实如果做成刚刚我说的wasd逐渐变化的移动方式,使用OpenGL的transformation来操控这里的动画变化是更好的方式。
当我们按下w键后,下面的字幕提示就会自动消失,从而不影响用户体验,或者说下面的字是提示用户怎么交互,当用户学会了,这个提示就没有必要出现了,出现反而会破坏用户的沉浸感所以消失了。
这里将模型放大从而模拟出近大远小的透视关系。但这里依然有一个问题就是我并没有给卡比放大,但其实可以使用OpenGL的transformation来进行放大,所以这也是我的这个程序一个可以优化的点。
当然这里用户也可以像前面学习的那样用e直接切换到下一场景,这就是提供了多种交互方式给用户进行选择,提高了程序的交互性。
相关代码如下。
//This function is showing the second stage
void stage1(void)
{
//Draw the static stars
starsScene();
//Draw the moving stars
movingStarsScne();
//Draw the grass and flowers
grassScene();
flower(270 - kirbyDistance / 3, 290 - kirbyDistance, 40 + kirbyDistance / 5);
flower(560 + kirbyDistance / 3, 280 - kirbyDistance, 40 + kirbyDistance / 5);
flower(210 - kirbyDistance / 3, 270 - kirbyDistance, 40 + kirbyDistance / 5);
flower(100 - kirbyDistance / 3, 150 - kirbyDistance, 40 + kirbyDistance / 5);
flower(80 - kirbyDistance / 3, 280 - kirbyDistance, 40 + kirbyDistance / 5);
flower(600 + kirbyDistance / 3, 100 - kirbyDistance, 40 + kirbyDistance / 5);
flower(650 + kirbyDistance / 3, 200 - kirbyDistance, 40 + kirbyDistance / 5);
flower(720 + kirbyDistance / 3, 240 - kirbyDistance, 40 + kirbyDistance / 5);
//Draw Kirby
kirby(400, 300 - kirbyDistance, 50);
//Draw the shining stars
if (shining)
{
glColor3f(253 / 255.0f, 199 / 255.0f, 53 / 255.0f);
roundStars(600, 450 - kirbyDistance, shiningStars + 2);
roundStars(600, 150 - kirbyDistance, shiningStars + 2);
roundStars(200, 450 - kirbyDistance, shiningStars + 2);
roundStars(200, 150 - kirbyDistance, shiningStars + 2);
glColor3f(255 / 255.0f, 240 / 255.0f, 121 / 255.0f);
roundStars(600, 450 - kirbyDistance, shiningStars);
roundStars(600, 150 - kirbyDistance, shiningStars);
roundStars(200, 450 - kirbyDistance, shiningStars);
roundStars(200, 150 - kirbyDistance, shiningStars);
}
//Show the text
if (stayTime < 30.0f)
{
glColor3f(1, 1, 1);
selectFont(48, ANSI_CHARSET, "Comic Sans MS");
glRasterPos2f(310, 75);
drawString("Oh! Kirby!");
}
else if (textShown)
{
glColor3f(1, 1, 1);
selectFont(48, ANSI_CHARSET, "Comic Sans MS");
glRasterPos2f(120, 75);
drawString("Try to use \"w\" to get closer to Kirby");
}
}
7. 第三个场景
第三个场景就是第二个场景中卡比将礼物交给了用户,然后用户打开这个礼物或者说贺卡从而查看贺卡里面的内容。这一部分对于这个作业来说是一个强化主题的过程。贺卡是必须的,上面得有生日祝福的文字,或者说有个蛋糕,我还做了一个卡比的贴纸在上面,这个贴纸也是可以互动的。我们的鼠标(图中的特殊星星)如果高于卡比那卡比就会张嘴或者说是开心地笑,如果低于卡比那卡比就会表现出疑惑的表情,如下面两张图所示。这也是为什么我全部场景里的鼠标会有个星星跟随,这样做是为了提高交互性,而且这个场景可以理解成当我们这个星星在卡比的面前时候,卡比就会被逗得很开心。如果卡比找不到这个星星,卡比就会疑惑,不知道星星去哪里了。
但是我们的这个鼠标有星星跟随的方案有一个bug,那便是如果我们修改窗口大小,这个窗口的投影大小和实际大小就会有误差从而导致了这个星星无法再跟随鼠标了,这个方案其实可以像之前的JavaFX里的方法来解决,那便是用变量而非常量来规定程序里的所有值,但这样的工作量很大,所以我并没有修改,这个点因此也可以后续改进。
我的JavaFX作业的相关分享连接:JavaFX桑基图
如果是游玩过星之卡比系列的,或者是对星之卡比设定比较了解的应该都知道,卡比可以将东西吸入从而获取对应的能力,这个时候卡比也会变身,变成不同的形态。我这里同样保留了这样的设计。它被我设计成一个彩蛋,但是为了防止用户不知道,当用户没有触发彩蛋达到一定的时间时,下面也会有字幕提示,告诉用户尝试用蛋糕去喂给卡比。如下图所示。
这时候用户可以点击蛋糕,这样蛋糕就会被用户拿起来,即蛋糕一直跟着鼠标移动,这里跟前面的星星同理,然后如果蛋糕到卡比附近,卡比就会吸入这个蛋糕从而变身成“蛋糕卡比”,周围还有星星特效。这样做同样富有交互性以及趣味性。
当然这一部分,主要是因为我们前面设计的时候我们将基础图元组成了卡比的组件,然后组件在一起再组成卡比。所以这里这些组件可以重新再一起组成卡比贴纸以及后续的“蛋糕卡比”。这也是CPT203软件工程里提到的软件过程模型中的集成与配置。
相关的代码如下。
//This function is showing the third stage
void stage2(void)
{
//Draw the static stars
starsScene();
//Draw the moving stars
movingStarsScne();
//Draw the birthday card
bithdayCard();
//Show the text
if (stayTime > 100.0f && kirbyCakeStickers != true)
{
glColor3f(1, 1, 1);
selectFont(48, ANSI_CHARSET, "Comic Sans MS");
glRasterPos2f(250, 75);
drawString("Feed cake to Kirby");
}
}
8. 交互部分
交互部分是作业明确提出要着重说明的一个点,这里专门作为一个部分去阐述这里的细节。
首先Lab课上教了以下几种交互方式:1. 键盘交互 2. 鼠标交互 3. 鼠标点击出多级菜单进行交互 4. 屏幕上的文字交互。
当然除了这四个部分,我们前面很多地方的涉及也能体现这里的交互感,这里主要提一些明显的课上内容。
8.1 键盘交互
键盘交互主要有两个部分,第一部分是使用键盘进行场景的切换以及程序的退出,这里的代码比较简单,因为这里的交互没有条件限制。但是由于我每个场景的切换都有切换的动画,所以我设计成每一次不在这个动画的时候可以进行切换,从而调用切换动画的方法完成场景的切换。
而第二部分是使用键盘在第二个场景移动角色从而靠近卡比,这里就需要加一个条件以确定是在这一个地方才能起作用,而不会影响其他地方,因此具体的代码如下。
//This function controls the interaction with keyboard
void keyboardInteraction(unsigned char key, int x, int y)
{
if (key == 'q' || key == 'Q')
exit(0);
//Press "e" to start transition animation;
if (transitionState == 0 && (key == 'e' || key == 'E'))
transitionStatesChange();
//Press "w" to get closer to Kirby in the second stage
if (key == 'w' || key == 'W')
{
if (stages == 1 && kirbyDistance <= 50)
{
textShown = false;
kirbyDistance += 10;
}
else
{
transitionStatesChange();
}
}
//Press "s" to get away to Kirby in the second stage
if (key == 's' || key == 'S')
{
if (stages == 1 && kirbyDistance > 0)
{
textShown = false;
kirbyDistance -= 10;
}
}
}
当然你可以像前文说的一样对于这后半部分进行修改,我这里当往前移动到最够近的距离会自动跳转到下一场景,而且给用户活动的区域进行了限制。
8.2 鼠标交互
鼠标交互即有鼠标点击就能切换到下一个场景的交互,又有前文中为了第三个场景而将一个特殊的星星设计为跟随鼠标移动的交互以及第三个场景中点击蛋糕的互动。
所以这里的代码与前面的代码相似,但是需要规定在第三个场景中场景不切换而是固定在这个场景中,这里与前面的键盘交互不同。然后当蛋糕被的区域被点击,蛋糕便和星星一样跟着鼠标移动,所以这个蛋糕有和星星一样的问题。
相关的代码如下。
//This function controls the interaction with mouse
void mouseInteraction(int button, int state, int x, int y)
{
//Click to start transition animation;
if (transitionState == 0 && stages != 2 && button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
transitionStatesChange();
}
//If click the cake,then cake will be followed with the mouse
if (stages == 2 && button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
if (abs(cakeX - x) < 50 && abs(cakeY - (600 - y)) < 50) {
isDraggingCake = true;
}
}
}
这里鼠标上的星星用的方法不太一样,是在建模的时候专门用transformation进行相关的旋转等操作,所以如果前面我说的transformation之类的改进方法不理解,也可以看这里的代码,然后用鼠标的xy坐标作为参数传入这个方法。
相关的代码如下。
//Using the previous functions and variables to draw a star following the mouse
void mouseStar(float x0, float y0)
{
glPushMatrix();
glTranslatef(x0, y0, 0);
glScalef(1, 1, 0);
glRotatef(30, 0, 0, 1);
glTranslatef(-x0, -y0, 0);
glColor3f(255 / 255.0f, 240 / 255.0f, 121 / 255.0f);
roundStars(x0, y0, 12);
glPopMatrix();
}
8.3 鼠标点击出多级菜单进行交互
这一部分其实我的这个2D作业也没有涉及,但是在3D作业中我添加了这一部分,这一部分学习的地方靠后,我并没有纳入到这次作业之中。
8.4 屏幕上的文字交互
屏幕上的文字交互主要是使用文字进行一定的提示以及最后生日贺卡上的文字。这些都是Lab上的内容,前面准备工作时候提到过,这里再专门放出一遍。
//This function is used to creat a new font and this font will be used with the function "drawString" to create the strings on the screen
void selectFont(int size, int charset, const char* face) {
HFONT hFont = CreateFontA(size, 0, 0, 0, FW_MEDIUM, 0, 0, 0, charset, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, face);
HFONT hOldFont = (HFONT)SelectObject(wglGetCurrentDC(), hFont);
DeleteObject(hOldFont);
}
//This function is used to render the string on the screen using the font made by the function "selectFont"
void drawString(const char* str) {
static GLuint lists;
lists = glGenLists(MAX_CHAR);
wglUseFontBitmaps(wglGetCurrentDC(), 0, MAX_CHAR, lists);
for (; *str != '\0'; ++str) {
glCallList(lists + *str);
}
}
9. 缺点与问题
这个代码有很多缺点和问题可以改进。这里再一一列举出来。
9.1 没有充分使用OpenGL的特性
这里大部分地方的建模和使用都是进行数学计算再建模,但其实可以像前文的鼠标星星一样使用Transformation就会方便很多。包括星星动画,过场动画,卡比等。这里的鼠标星星是一种改进方案的展示,以及3D作业中我也使用了Transformation快速地制作了这里的过场动画。
9.2 一些场景的伪装效果
比如场景1-3的背景都使用了相同的背景,但这里的星星是固定路径而不是随机出现不够自然,可以改进。但是需要注意的是有时候随机出来的结果可能是一些分散或重合,会丑陋。所以完全的随机也并非最好的方案。
再比如如果你使用了大量transformation方法在建模过程中,那么对于第二个场景中,你就可以使用transformation将卡比以及花进行放大之类的操作从而营造出透视效果。
同样在第二个场景中,为了让草地有透视效果,我采用多种绿色然后分层上色,你也可以尝试使用不同的场景,比如b站上有很多在马路上开车的代码讲解,你也可以通过那些代码去学习如何呈现透视效果。
9.3 代码有些臃肿
代码其实很多地方都可以精简,比如如果准备工作里有的代码的工作可以被其他方法替代。以及动画切换的时候的方法,动画可以采用一个动画的正放和倒放,这样都可以让代码精简一些。
9.4 没有窗口大小自适应
一般来说我们的OpenGL不像JavaFX需要使用Listener或者继承类的方法来实现窗口大小自适应。但是如果涉及到一些方法可能会出现窗口大小无法自适应的问题,我们可以使用前面说的JavaFX的继承类的方法来实现窗口大小自适应,从而解决前面我说的鼠标星星和蛋糕的偏移问题。
10. 完整代码
前面很多地方只是展示了最相关的代码,以及部分示例代码,没有展示这些代码中调用的一些方法,以及其他的代码。这里展示全部代码,希望能帮助到大家。
#define FREEGLUT_STATIC
#include <GL/freeglut.h>
#include <math.h>
#include <cmath>
#define MAX_CHAR 128
//There are some consts and variables for the first stage
const int starsNumber = 11;
const float angleRadian = 3.1415926 / 180.0f;
float starsRotationAngle[starsNumber] = {
0.0f, 36.0f, 72.0f, 18.0f, 36.0f, 20.0f, 18.0f, 18.0f, 36.0f, 20.0f, 18.0f };
float starsPositionChange[starsNumber] = {
400.0f , 600.0f, 700.0f, 500.0f, 550.0f, 600.0f, 280.0f, 400.0f, 200.0f, 300.0f, 250.0f };
float starsHeights[starsNumber] = {
200.0f, 300.0f, 262.5f, 187.5f, 240.625f, 562.5f, 262.5f, 500.0f, 250.0f, 562.5f, 468.75f };
float kirbyStarPositionX = 800.0f;
float kirbyStarPositionY = 600.0f;
//There are some consts and variables for the second stage
//The shining stars animation will also be used in the third stage
float kirbyDistance = 0.0f;
float shiningStars = 0.0f;
float shiningTime = 0.0f;
bool shining = true;
//There are some consts and variables for the third stage
float cakeX = 447;
float cakeY = 300;
bool isDraggingCake = false;
float kirbyStickersX = 530;
float kirbyStickersY = 300;
bool kirbyCakeStickers = false;
//These variables are used to record the position of the mouse
int mouseX = 0;
int mouseY = 0;
//These variables are used for transtion animation
int transitionState = 0;
float transitionTime = 0.0f;
float transitionBackTime = 0.0f;
//These variables are used for showing the text
float stayTime = 0.0f;
bool textShown = true;
//It is used to change the stage
int stages = 0;
//This function is used for update the position of the mouse to draw the star following the mouse
//It also records the position when drag the cake in the third stage
void mouseMove(int x, int y) {
mouseX = x;
mouseY = 600 - y;
if (isDraggingCake)
{
cakeX = x;
cakeY = 600 - y;
if (abs(cakeX - kirbyStickersX) < 10 && abs(cakeY - kirbyStickersY) < 10)
{
kirbyCakeStickers = true;
}
}
}
//This function is used to creat a new font and this font will be used with the function "drawString" to create the strings on the screen
void selectFont(int size, int charset, const char* face) {
HFONT hFont = CreateFontA(size, 0, 0, 0, FW_MEDIUM, 0, 0, 0, charset, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, face);
HFONT hOldFont = (HFONT)SelectObject(wglGetCurrentDC(), hFont);
DeleteObject(hOldFont);
}
//This function is used to render the string on the screen using the font made by the function "selectFont"
void drawString(const char* str) {
static GLuint lists;
lists = glGenLists(MAX_CHAR);
wglUseFontBitmaps(wglGetCurrentDC(), 0, MAX_CHAR, lists);
for (; *str != '\0'; ++str) {
glCallList(lists + *str);
}
}
//It change the variable "stages' to change the stage which should between the first transition animation and the second transition animation
//This function will be called when the first transition animation is over which is in the function "transitionBackStart"
//It also change the variable for the shining stars animation for the third stage
void stagesChange(void)
{
stages += 1;
stayTime = 0.0f;
if (stages == 2)
{
shining = true;
shiningStars = 0.0f;
}
}
//It change the variable "transitionState' to change the transition animation
//There are 3 value for the variable "transitionState'
//value 0 means no transition animation
//value 1 means the first transition animation
//value 2 means the second transition animation
void transitionStatesChange(void)
{
//To avoid skipping the second transition animation
if (transitionState == 0)
{
transitionState += 1;
}
}
//Update some values for the animation
void update(int value)
{
//It change the value for showing the texts;
stayTime += 0.2f;
//It change the values for stars moving and rotation which will be used for all the stages
kirbyStarPositionX -= 1.2f;
kirbyStarPositionY -= 0.9f;
for (int i = 0; i < starsNumber; i++) {
starsRotationAngle[i] += 0.05f;
}
for (int i = 0; i < starsNumber; i++) {
starsPositionChange[i] -= 0.4f;
starsHeights[i] -= 0.3f;
if (starsPositionChange[i] < 0 || starsHeights[i] < 0) {
if (i < 2) {
starsPositionChange[i] = 800;
starsHeights[i] = 400;
}
else if (i < 4) {
starsPositionChange[i] = 800;
starsHeights[i] = 300;
}
else if (i < 5) {
starsPositionChange[i] = 800;
starsHeights[i] = 350;
}
else if (i < 7) {
starsPositionChange[i] = 640;
starsHeights[i] = 600;
}
else if (i < 9) {
starsPositionChange[i] = 480;
starsHeights[i] = 600;
}
else if (i < 11) {
starsPositionChange[i] = 320;
starsHeights[i] = 600;
}
}
}
//It change the values for stars shining for the second stage
//This animation will control the stars shown to their biggest size and then narrow to unseen
if (stages == 1)
{
if (shiningStars < 18.0f)
{
shiningStars += 0.2f;
}
else
{
shiningTime += 0.2f;
}
if (shiningTime >= 18.0f)
{
shiningStars -= 0.4f;
}
if (shiningStars < 0)
{
shining = false;
}
}
//It change the values for stars shining for the third stage
//This animation will control the stars shining which alternates between enlargement and contraction
if (stages == 2)
{
if (shining)
{
shiningStars += 0.2f;
}
else
{
shiningStars -= 0.2f;
}
if (shiningStars <= 0)
{
shining = true;
}
if (shiningStars > 2)
{
shining = false;
}
}
//It change the time for showing the transition animations
if (transitionState == 1) {
transitionTime += 1.0f;
}
if (transitionState == 2) {
transitionBackTime += 1.0f;
}
glutPostRedisplay();
//Set 60fps
glutTimerFunc(1000 / 60, update, 0);
}
//The follwing functions are the basic functions for drawing the elements in this project
//This function is used to draw a hollow circle with a width of "lineWidth" and a radius of "r" at positions "x0" and "y0"
void hollowCircle(float x0, float y0, float r, float lineWidth)
{
glLineWidth(lineWidth);
glBegin(GL_LINE_LOOP);
for (int i = 0; i < 360; i++) {
float theta = angleRadian * float(i);
float x = r * cosf(theta);
float y = r * sinf(theta);
glVertex2f(x0 + x, y0 + y);
}
glEnd();
}
//This function is used to draw a not whole hollow circle with a width of "lineWidth" and radius of "r" at positions "x0" and "y0", it will beign with "angleStart" and end with "angleEnd"
void holidCircleAngle(float x0, float y0, float r, int angleIStart, int angleEnd, float lineWidth)
{
glLineWidth(lineWidth);
glBegin(GL_LINE_STRIP);
for (int i = angleIStart; i < angleEnd; i++) {
float theta = angleRadian * float(i);
float x = r * cosf(theta);
float y = r * sinf(theta);
glVertex2f(x0 + x, y0 + y);
}
glEnd();
}
//This function is used to draw a solid circle with a radius of "r" at positions "x0" and "y0"
void solidCircle(float x0, float y0, float r)
{
glBegin(GL_POLYGON);
for (int i = 0; i < 360; i++) {
float theta = angleRadian * float(i);
float x = r * cosf(theta);
float y = r * sinf(theta);
glVertex2f(x0 + x, y0 + y);
}
glEnd();
}
//This function is used to draw a hollow circle with a width of "lineWidth" ,"a" major axis, and "b" minor axis at positions "x0" and "y0"
void hollowEllipse(float x0, float y0, float a, float b, float lineWidth)
{
glLineWidth(lineWidth);
glBegin(GL_LINE_LOOP);
for (int i = 0; i < 360; i++) {
float theta = angleRadian * float(i);
glVertex2f(x0 + a * cosf(theta), y0 + b * sinf(theta));
}
glEnd();
}
//This function is used to draw a hollow circle with a width of "lineWidth" ,"a" major axis, and "b" minor axis at positions "x0" and "y0" and it will rotate for "angle" degrees
void hollowEllipseOblique(float x0, float y0, float a, float b, int angle, float lineWidth)
{
glLineWidth(lineWidth);
glBegin(GL_LINE_LOOP);
for (int i = 0; i < 360; i++) {
float theta = angleRadian * float(i);
float omiga = angleRadian * float(angle);
glVertex2f(x0 + a * cosf(theta) * cosf(omiga) - b * sinf(theta) * sinf(omiga), y0 + b * sinf(theta) * cos(omiga) + a * cosf(theta) * sinf(omiga));
}
glEnd();
}
//This function is used to draw a solid circle with "a" major axis, and "b" minor axis at positions "x0" and "y0"
void solidEllipse(float x0, float y0, float a, float b)
{
glBegin(GL_POLYGON);
for (int i = 0; i < 360; i++) {
float theta = angleRadian * float(i);
glVertex2f(x0 + a * cosf(theta), y0 + b * sinf(theta));
}
glEnd();
}
//This function is used to draw a solid circle with "a" major axis, and "b" minor axis at positions "x0" and "y0", it will beign with "angleStart" and end with "angleEnd"
void solidEllipseAngle(float x0, float y0, float a, float b, int angleStart, int angleEnd)
{
glBegin(GL_POLYGON);
for (int i = angleStart; i < angleEnd; i++) {
float theta = angleRadian * float(i);
glVertex2f(x0 + a * cosf(theta), y0 + b * sinf(theta));
}
glEnd();
}
//This function is used to draw a solid circle with "a" major axis, and "b" minor axis at positions "x0" and "y0" and it will rotate for "angle" degrees
void solidEllipseOblique(float x0, float y0, float a, float b, int angle)
{
glBegin(GL_POLYGON);
for (int i = 0; i < 360; i++) {
float theta = angleRadian * float(i);
float omiga = angleRadian * float(angle);
glVertex2f(x0 + a * cosf(theta) * cosf(omiga) - b * sinf(theta) * sinf(omiga), y0 + b * sinf(theta) * cos(omiga) + a * cosf(theta) * sinf(omiga));
}
glEnd();
}
//This function is used to draw a line between the points ("x0" , "y0") and ("x1" , "y1") with a width of "lineWidth"
void line(float x0, float y0, float x1, float y1, float lineWidth)
{
glLineWidth(lineWidth);
glBegin(GL_LINES);
glVertex2f(x0, y0);
glVertex2f(x1, y1);
glEnd();
}
//This function is used to draw a rectangle with four points ("x0" , "y0"),("x1" , "y1"),("x2" , "y2") and ("x3" , "y3")
void rectangle(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3)
{
glBegin(GL_QUADS);
glVertex2f(x0, y0);
glVertex2f(x1, y1);
glVertex2f(x2, y2);
glVertex2f(x3, y3);
glEnd();
}
//This function is used to draw a triangle with three points ("x0" , "y0"),("x1" , "y1")and("x2" , "y2")
void triangle(float x0, float y0, float x1, float y1, float x2, float y2)
{
glBegin(GL_TRIANGLES);
glVertex2f(x0, y0);
glVertex2f(x1, y1);
glVertex2f(x2, y2);
glEnd();
}
//Now use the previous basic functions to draw some components for the elements in this project
//This function is used to draw the eyes of Kirby
void kirbyEyes(float x0, float y0)
{
solidEllipse(x0 - 12, y0 + 22, 7, 14);
solidEllipse(x0 + 12, y0 + 22, 7, 14);
glColor3f(1, 1, 1);
solidEllipse(x0 - 12, y0 + 29, 4, 6);
solidEllipse(x0 + 12, y0 + 29, 4, 6);
}
//This function is used to draw the body of Kirby's stickers
void kirbyEyesStickers(float x0, float y0)
{
glColor3f(54 / 255.0f, 137 / 255.0f, 191 / 255.0f);
solidEllipse(x0 - 8, y0 + 8, 4, 10);
solidEllipse(x0 + 8, y0 + 8, 4, 10);
glColor3f(41 / 255.0f, 35 / 255.0f, 142 / 255.0f);
solidEllipse(x0 - 8, y0 + 11, 3, 7);
solidEllipse(x0 + 8, y0 + 11, 3, 7);
glColor3f(1, 1, 1);
solidEllipse(x0 - 8, y0 + 14, 2, 4);
solidEllipse(x0 + 8, y0 + 14, 2, 4);
}
//This function is used to draw the face of Kirby
void kirbyFace(float x0, float y0)
{
glColor3f(246 / 255.0f, 130 / 255.0f, 156 / 255.0f);
solidEllipse(x0 - 22, y0 + 5, 7, 5);
solidEllipse(x0 + 22, y0 + 5, 7, 5);
}
//This function is used to draw the face of Kirby's stickers
void kirbyFaceStickers(float x0, float y0)
{
glColor3f(247 / 255.0f, 107 / 255.0f, 149 / 255.0f);
solidEllipse(x0 - 20, y0, 5, 3);
solidEllipse(x0 + 20, y0, 5, 3);
}
//This function is used to draw the mouth of Kirby
void kirbyMouth(float x0, float y0)
{
glColor3f(128 / 255.0f, 94 / 255.0f, 93 / 255.0f);
solidEllipse(x0, y0 - 5, 10, 3);
solidEllipseAngle(x0, y0 - 5, 10, 12, 180, 360);
glColor3f(246 / 255.0f, 130 / 255.0f, 156 / 255.0f);
solidEllipse(x0, y0 - 8, 8, 3);
solidEllipseAngle(x0, y0 - 8, 8, 8, 180, 360);
}
//This function is used to draw the mouth of Kirby's stickers
void kirbyMouthShouting(float x0, float y0)
{
glColor3f(133 / 255.0f, 32 / 255.0f, 78 / 255.0f);
solidCircle(x0, y0 - 8, 3);
}
//This function is used to draw the hands of Kirby
void kirbyHands(float x0, float y0)
{
glColor3f(254 / 255.0f, 184 / 255.0f, 196 / 255.0f);
solidEllipseOblique(x0 + 45 * cosf(40.0f * 3.1415926 / 180.0f), y0 + 45 * sinf(40.0f * 3.1415926 / 180.0f), 20, 15, 45);
solidEllipseOblique(x0 - 45, y0, 20, 15, 15);
glColor3f(128 / 255.0f, 94 / 255.0f, 93 / 255.0f);
hollowEllipseOblique(x0 + 45 * cosf(40.0f * 3.1415926 / 180.0f), y0 + 45 * sinf(40.0f * 3.1415926 / 180.0f), 20, 15, 45, 2);
hollowEllipseOblique(x0 - 45, y0, 20, 15, 15, 2);
}
//This function is used to draw the connections between the body and the hands for Kirby
void kirbyHandsConnections(float x0, float y0)
{
glColor3f(254 / 255.0f, 184 / 255.0f, 196 / 255.0f);
solidCircle(x0 + 50 * cosf(40.0f * 3.1415926 / 180.0f), y0 + 50 * sinf(40.0f * 3.1415926 / 180.0f), 8);
solidCircle(x0 - 50, y0, 8);
}
//This function is used to draw the right foot of Kirby
void kirbyRightFoot(float x0, float y0)
{
glColor3f(228 / 255.0f, 95 / 255.0f, 138 / 255.0f);
solidEllipse(x0 + 25, y0 - 40, 30, 20);
glColor3f(128 / 255.0f, 94 / 255.0f, 93 / 255.0f);
hollowEllipse(x0 + 25, y0 - 40, 30, 20, 2);
glColor3f(247 / 255.0f, 129 / 255.0f, 160 / 255.0f);
solidCircle(x0 + 50, y0 - 40, 3);
}
//This function is used to draw the left foot of Kirby
void kirbyLeftFoot(float x0, float y0)
{
glColor3f(228 / 255.0f, 95 / 255.0f, 138 / 255.0f);
solidEllipseOblique(x0 - 50 * sinf(45.0f * 3.1415926 / 180.0f), y0 - 50 * cosf(45.0f * 3.1415926 / 180.0f), 30, 20, 315);
glColor3f(128 / 255.0f, 94 / 255.0f, 93 / 255.0f);
hollowEllipseOblique(x0 - 50 * sinf(45.0f * 3.1415926 / 180.0f), y0 - 50 * cosf(45.0f * 3.1415926 / 180.0f), 30, 20, 315, 2);
glColor3f(247 / 255.0f, 129 / 255.0f, 160 / 255.0f);
solidCircle(x0 - 50 * cosf(20.0f * 3.1415926 / 180.0f), y0 - 50 * sinf(20.0f * 3.1415926 / 180.0f), 3);
}
//Using the previous functions to draw a size of "size" Kirby at positions "x0" and "y0"
void kirby(float x0, float y0, float size)
{
kirbyRightFoot(x0, y0);
kirbyHands(x0, y0);
glColor3f(254 / 255.0f, 184 / 255.0f, 196 / 255.0f);
solidCircle(x0, y0, size);
glColor3f(128 / 255.0f, 94 / 255.0f, 93 / 255.0f);
hollowCircle(x0, y0, size, 2);
kirbyEyes(x0, y0);
kirbyHandsConnections(x0, y0);
kirbyFace(x0, y0);
kirbyMouth(x0, y0);
kirbyLeftFoot(x0, y0);
}
//Using the previous functions to draw a size of "size" Kirby's stickers at positions "x0" and "y0"
void kirbyStickers(float x0, float y0, float size)
{
glColor3f(245 / 255.0f, 146 / 255.0f, 191 / 255.0f);
solidCircle(x0, y0, size + 5);
glColor3f(255 / 255.0f, 174 / 255.0f, 215 / 255.0f);
solidCircle(x0, y0, size);
glColor3f(133 / 255.0f, 32 / 255.0f, 78 / 255.0f);
hollowCircle(x0, y0, size, 2);
kirbyEyesStickers(x0, y0);
kirbyFaceStickers(x0, y0);
//If the mouse is higher than Kirby, Kirby will laught
//If the mouse is lower than Kirby, Kirby will shout
if (mouseX < 800 && mouseY > 300) {
kirbyMouth(x0, y0 - 5);
}
else {
kirbyMouthShouting(x0, y0);
}
}
//Using the previous functions to draw a size of "R" star at positions "x0" and "y0"
void stars(float x0, float y0, float R)
{
float r = R / (sinf(36.0f * 3.1415926 / 180.0f) * cosf(18.0f * 3.1415926 / 180.0f) / sinf(18.0f * 3.1415926 / 180.0f) + cosf(36.0f * 3.1415926 / 180.0f));
glBegin(GL_TRIANGLE_FAN);
glVertex2f(x0, y0);
for (int i = 0; i < 5; i++) {
float theta = i * 72.0f * 3.1415926 / 180.0f;
glVertex2f(x0 + R * sinf(theta), y0 + R * cosf(theta));
glVertex2f(x0 + r * sinf(theta + 36.0f), y0 + r * cosf(theta + 36.0f));
}
glEnd();
glBegin(GL_TRIANGLE_FAN);
glVertex2f(x0, y0);
for (int i = 0; i < 5; i++) {
float theta = i * 72.0f * 3.1415926 / 180.0f;
glVertex2f(x0 + R * sinf(theta), y0 + R * cosf(theta));
glVertex2f(x0 + r * sinf(theta - 36.0f), y0 + r * cosf(theta - 36.0f));
}
glEnd();
}
//Using the previous functions to draw a size of "R" round star at positions "x0" and "y0"
void roundStars(float x0, float y0, float R)
{
float r = R / (sinf(36.0f * 3.1415926 / 180.0f) * cosf(18.0f * 3.1415926 / 180.0f) / sinf(18.0f * 3.1415926 / 180.0f) + cosf(36.0f * 3.1415926 / 180.0f));
for (int i = 0; i < 5; i++) {
float theta = 90.0f - 72.0f * i;
float omiga = i * 72.0f * 3.1415926 / 180.0f;
solidEllipseOblique(x0 + R / 2 * sinf(omiga), y0 + R / 2 * cosf(omiga), R / 1.8, r, theta);
}
}
//Using the precious functions and variable to draw a size of "R" round star at positions "x0" and "y0" and it will rotate for "obliqueAngle" degrees
void movingStars(float x0, float y0, float R, float obliqueAngle)
{
float r = R / (sinf(36.0f * 3.1415926 / 180.0f) * cosf(18.0f * 3.1415926 / 180.0f) / sinf(18.0f * 3.1415926 / 180.0f) + cosf(36.0f * 3.1415926 / 180.0f));
glBegin(GL_TRIANGLE_FAN);
glVertex2f(x0, y0);
for (int i = 0; i < 5; i++) {
float theta = i * 72.0f * 3.1415926 / 180.0f + obliqueAngle;
glVertex2f(x0 + R * sinf(theta), y0 + R * cosf(theta));
glVertex2f(x0 + r * sinf(theta + 36.0f), y0 + r * cosf(theta + 36.0f));
}
glEnd();
glBegin(GL_TRIANGLE_FAN);
glVertex2f(x0, y0);
for (int i = 0; i < 5; i++) {
float theta = i * 72.0f * 3.1415926 / 180.0f + obliqueAngle;
glVertex2f(x0 + R * sinf(theta), y0 + R * cosf(theta));
glVertex2f(x0 + r * sinf(theta - 36.0f), y0 + r * cosf(theta - 36.0f));
}
glEnd();
}
//Using the previous functions to draw a size of "size" round flower at positions "x0" and "y0"
void flower(float x0, float y0, float size)
{
float R = size / 4;
float r = size / 10;
glColor3f(193 / 255.0f, 215 / 255.0f, 107 / 255.0f);
line(x0, y0, x0, y0 + size - r, 2);
solidEllipseOblique(x0 + size / 8, y0 + size / 8, size / 5, size / 9, 45);
solidEllipseOblique(x0 - size / 8, y0 + size / 2, size / 5, size / 9, -45);
glColor3f(254 / 255.0f, 174 / 255.0f, 174 / 255.0f);
roundStars(x0, y0 + size, R);
glColor3f(255 / 255.0f, 249 / 255.0f, 215 / 255.0f);
solidCircle(x0, y0 + size, r);
glColor3f(193 / 255.0f, 215 / 255.0f, 107 / 255.0f);
}
//Using the previous functions to draw a cake at positions "x0" and "y0"
void cake(float x0, float y0)
{
glColor3f(144 / 255.0f, 57 / 255.0f, 44 / 255.0f);
glBegin(GL_POLYGON);
glVertex2f(x0 - 25, y0 + 10);
glVertex2f(x0 - 15, y0 - 40);
glVertex2f(x0 + 15, y0 - 40);
glVertex2f(x0 + 25, y0 + 10);
glEnd();
glColor3f(1, 1, 1);
solidCircle(x0 - 10, y0 + 25, 15);
solidCircle(x0 + 10, y0 + 25, 15);
solidCircle(x0, y0 + 10, 15);
solidCircle(x0 - 25, y0 + 10, 15);
solidCircle(x0 + 25, y0 + 10, 15);
glColor3f(0, 0, 0);
holidCircleAngle(x0 - 10, y0 + 25, 15, 90, 180, 2);
holidCircleAngle(x0 + 10, y0 + 25, 15, 0, 90, 2);
holidCircleAngle(x0 - 25, y0 + 10, 15, 90, 330, 2);
holidCircleAngle(x0, y0 + 10, 15, 210, 330, 2);
holidCircleAngle(x0 + 25, y0 + 10, 15, 210, 450, 2);
glColor3f(187 / 255.0f, 2 / 255.0f, 19 / 255.0f);
solidCircle(x0, y0 + 40, 12);
}
//Using the previous functions to draw a birthday card
void bithdayCard(void)
{
glColor3f(228 / 255.0f, 224 / 255.0f, 199 / 255.0f);
rectangle(150, 150, 650, 150, 650, 450, 150, 450);
glColor3f(225 / 255.0f, 71 / 255.0f, 51 / 255.0f);
rectangle(198, 198, 602, 198, 602, 402, 198, 402);
glColor3f(223 / 255.0f, 224 / 255.0f, 218 / 255.0f);
rectangle(200, 200, 600, 200, 600, 400, 200, 400);
glColor3f(0, 0, 0);
selectFont(48, ANSI_CHARSET, "Comic Sans MS");
glRasterPos2f(220, 320);
drawString("Happy");
glRasterPos2f(260, 280);
drawString("Birthday!");
//If the cake is closed to the Kirby's sticker,Kirby will become to cake Kirby
if (kirbyCakeStickers == false)
{
kirbyStickers(kirbyStickersX, kirbyStickersY, 35);
cake(cakeX, cakeY);
}
else
{
cake(kirbyStickersX, kirbyStickersY);
kirbyEyesStickers(kirbyStickersX, kirbyStickersY + 5);
kirbyFaceStickers(kirbyStickersX, kirbyStickersY + 5);
kirbyMouth(kirbyStickersX, kirbyStickersY - 5);
glColor3f(253 / 255.0f, 199 / 255.0f, 53 / 255.0f);
roundStars(kirbyStickersX - 40, kirbyStickersY + 40, 10 + shiningStars);
roundStars(kirbyStickersX + 40, kirbyStickersY + 40, 10 + shiningStars);
roundStars(kirbyStickersX - 40, kirbyStickersY - 40, 10 + shiningStars);
roundStars(kirbyStickersX + 40, kirbyStickersY - 40, 10 + shiningStars);
glColor3f(255 / 255.0f, 240 / 255.0f, 121 / 255.0f);
roundStars(kirbyStickersX - 40, kirbyStickersY + 40, 10);
roundStars(kirbyStickersX + 40, kirbyStickersY + 40, 10);
roundStars(kirbyStickersX - 40, kirbyStickersY - 40, 10);
roundStars(kirbyStickersX + 40, kirbyStickersY - 40, 10);
}
}
//Using the previous functions and variables to draw a star following the mouse
void mouseStar(float x0, float y0)
{
glPushMatrix();
glTranslatef(x0, y0, 0);
glScalef(1, 1, 0);
glRotatef(30, 0, 0, 1);
glTranslatef(-x0, -y0, 0);
glColor3f(255 / 255.0f, 240 / 255.0f, 121 / 255.0f);
roundStars(x0, y0, 12);
glPopMatrix();
}
//Using the "movingStars" to draw the scene of the moving stars and use the "triangle" to draw the tail of the moving stars
void movingStarsScne(void)
{
for (int i = 0; i < starsNumber; i++)
{
glColor3f(253 / 255.0f, 199 / 255.0f, 53 / 255.0f);
movingStars(starsPositionChange[i], starsHeights[i], 20, starsRotationAngle[i]);
glColor3f(255 / 255.0f, 240 / 255.0f, 121 / 255.0f);
movingStars(starsPositionChange[i], starsHeights[i], 18, starsRotationAngle[i]);
triangle(starsPositionChange[i] + 30 * cosf(36.87f * angleRadian), starsHeights[i] + 30 * sinf(36.87f * angleRadian), starsPositionChange[i] + 4, starsHeights[i] - 5, starsPositionChange[i] - 4, starsHeights[i] + 5);
}
}
//Using the "solidCircle" to draw the scene of the static stars
void starsScene(void)
{
glColor3f(255 / 255.0f, 240 / 255.0f, 121 / 255.0f);
solidCircle(50, 50, 2);
solidCircle(200, 100, 2);
solidCircle(700, 500, 2);
solidCircle(400, 120, 2);
solidCircle(750, 420, 2);
solidCircle(310, 480, 2);
solidCircle(70, 350, 2);
solidCircle(40, 440, 2);
solidCircle(510, 520, 2);
solidCircle(730, 70, 2);
solidCircle(36, 530, 2);
solidCircle(500, 120, 2);
solidCircle(270, 510, 2);
}
//Using the "rectangles" to draw the scene of the grass
void grassScene(void)
{
glColor3f(136 / 255.0f, 200 / 255.0f, 121 / 255.0f);
rectangle(0, 0, 800, 0, 800, 300, 0, 300);
glColor3f(78 / 255.0f, 121 / 255.0f, 88 / 255.0f);
rectangle(0, 298, 800, 298, 800, 300, 0, 300);
glColor3f(138 / 255.0f, 190 / 255.0f, 117 / 255.0f);
rectangle(0, 298, 800, 298, 800, 295, 0, 295);
glColor3f(138 / 255.0f, 192 / 255.0f, 118 / 255.0f);
rectangle(0, 290, 800, 290, 800, 295, 0, 295);
glColor3f(138 / 255.0f, 194 / 255.0f, 119 / 255.0f);;
rectangle(0, 290, 800, 290, 800, 280, 0, 280);
glColor3f(138 / 255.0f, 195 / 255.0f, 120 / 255.0f);
rectangle(0, 270, 800, 270, 800, 280, 0, 280);
glColor3f(137 / 255.0f, 195 / 255.0f, 120 / 255.0f);
rectangle(0, 270, 800, 270, 800, 260, 0, 260);
glColor3f(136 / 255.0f, 195 / 255.0f, 120 / 255.0f);
rectangle(0, 250, 800, 250, 800, 260, 0, 260);
glColor3f(136 / 255.0f, 200 / 255.0f, 120 / 255.0f);
rectangle(0, 250, 800, 250, 800, 240, 0, 240);
}
//This function shift the "transitionState" to start the first transition animation
void transitionStart(void)
{
transitionState = 1;
transitionTime = 0;
transitionBackTime = 0;
}
//This function shift the "transitionState" to start the second transition animation
//Before showing the second transition animation,it changes the stages to load the next stages in advance
void transitionBackStart(void)
{
stagesChange();
transitionState = 2;
transitionTime = 0;
transitionBackTime = 0;
}
//This function is the first transmition animation
//Parameters "speed" and "transitionTime" are used to control the animation time
void starTransition(float speed, float transitionTime)
{
float changeLength = speed * transitionTime;
float R = 300 - changeLength;
float r = R / (sinf(36.0f * 3.1415926 / 180.0f) * cosf(18.0f * 3.1415926 / 180.0f) / sinf(18.0f * 3.1415926 / 180.0f) + cosf(36.0f * 3.1415926 / 180.0f));
glColor3f(0.0, 0.0, 0.0);
glBegin(GL_POLYGON);
glVertex2f(400 - r * sinf(36.0f * angleRadian), 300 + r * cosf(36.0f * angleRadian));
glVertex2f(400, 300 + R);
glVertex2f(400, 600);
glVertex2f(0, 600);
glVertex2f(400 - R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400 + r * sinf(36.0f * angleRadian), 300 + r * cosf(36.0f * angleRadian));
glVertex2f(400, 300 + R);
glVertex2f(400, 600);
glVertex2f(800, 600);
glVertex2f(400 + R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400, 300 - r);
glVertex2f(400 + R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glVertex2f(800, 0);
glVertex2f(0, 0);
glVertex2f(400 - R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400 + r * cosf(18.0f * angleRadian), 300 - r * sinf(18.0f * angleRadian));
glVertex2f(400 + R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glVertex2f(800, 600);
glVertex2f(800, 0);
glVertex2f(400 + R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400 - r * cosf(18.0f * angleRadian), 300 - r * sinf(18.0f * angleRadian));
glVertex2f(400 - R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glVertex2f(0, 600);
glVertex2f(0, 0);
glVertex2f(400 - R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glEnd();
//The first animation is end so it call the "transitionBackStart" to start the second animation
if (R < 0)
{
transitionBackStart();
}
}
//This function is the second transmition animation
//Parameters "speed" and "transitionTime" are used to control the animation time
void starTransitionBack(float speed, float transitionTime)
{
float changeLength = speed * transitionTime;
float R = changeLength;
float r = R / (sinf(36.0f * 3.1415926 / 180.0f) * cosf(18.0f * 3.1415926 / 180.0f) / sinf(18.0f * 3.1415926 / 180.0f) + cosf(36.0f * 3.1415926 / 180.0f));
glColor3f(0.0, 0.0, 0.0);
glBegin(GL_POLYGON);
glVertex2f(400 - r * sinf(36.0f * angleRadian), 300 + r * cosf(36.0f * angleRadian));
glVertex2f(400, 300 + R);
glVertex2f(400, 600);
glVertex2f(0, 600);
glVertex2f(400 - R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400 + r * sinf(36.0f * angleRadian), 300 + r * cosf(36.0f * angleRadian));
glVertex2f(400, 300 + R);
glVertex2f(400, 600);
glVertex2f(800, 600);
glVertex2f(400 + R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400, 300 - r);
glVertex2f(400 + R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glVertex2f(800, 0);
glVertex2f(0, 0);
glVertex2f(400 - R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400 + r * cosf(18.0f * angleRadian), 300 - r * sinf(18.0f * angleRadian));
glVertex2f(400 + R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glVertex2f(800, 600);
glVertex2f(800, 0);
glVertex2f(400 + R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glEnd();
glBegin(GL_POLYGON);
glVertex2f(400 - r * cosf(18.0f * angleRadian), 300 - r * sinf(18.0f * angleRadian));
glVertex2f(400 - R * cosf(18.0f * angleRadian), 300 + R * sinf(18.0f * angleRadian));
glVertex2f(0, 600);
glVertex2f(0, 0);
glVertex2f(400 - R * sinf(36.0f * angleRadian), 300 - R * cosf(36.0f * angleRadian));
glEnd();
//The second animation is end so it control the "transitionState" back to 0
if (R >= 300)
{
transitionState = 0;
}
}
//This function controls the interaction with keyboard
void keyboardInteraction(unsigned char key, int x, int y)
{
if (key == 'q' || key == 'Q')
exit(0);
//Press "e" to start transition animation;
if (transitionState == 0 && (key == 'e' || key == 'E'))
transitionStatesChange();
//Press "w" to get closer to Kirby in the second stage
if (key == 'w' || key == 'W')
{
if (stages == 1 && kirbyDistance <= 50)
{
textShown = false;
kirbyDistance += 10;
}
else
{
transitionStatesChange();
}
}
//Press "s" to get away to Kirby in the second stage
if (key == 's' || key == 'S')
{
if (stages == 1 && kirbyDistance > 0)
{
textShown = false;
kirbyDistance -= 10;
}
}
}
//This function controls the interaction with mouse
void mouseInteraction(int button, int state, int x, int y)
{
//Click to start transition animation;
if (transitionState == 0 && stages != 2 && button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
transitionStatesChange();
}
//If click the cake,then cake will be followed with the mouse
if (stages == 2 && button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
if (abs(cakeX - x) < 50 && abs(cakeY - (600 - y)) < 50) {
isDraggingCake = true;
}
}
}
//This function is showing the first stage
void stage0(void)
{
//Draw the static stars
starsScene();
//Draw the moving stars
movingStarsScne();
//Draw the rainbow of kirbystar
glColor3f(252 / 255.0f, 1 / 255.0f, 0 / 255.0f);
line(kirbyStarPositionX - 72 / 5, kirbyStarPositionY + 96 / 5, 800 - 72 / 5, 600 + 96 / 5, 12);
line(kirbyStarPositionX - 60 / 5, kirbyStarPositionY + 80 / 5, 800 - 60 / 5, 600 + 80 / 5, 12);
glColor3f(251 / 255.0f, 153 / 255.0f, 1 / 255.0f);
line(kirbyStarPositionX - 48 / 5, kirbyStarPositionY + 64 / 5, 800 - 48 / 5, 600 + 64 / 5, 12);
line(kirbyStarPositionX - 36 / 5, kirbyStarPositionY + 48 / 5, 800 - 36 / 5, 600 + 48 / 5, 12);
glColor3f(255 / 255.0f, 251 / 255.0f, 6 / 255.0f);
line(kirbyStarPositionX - 24 / 5, kirbyStarPositionY + 32 / 5, 800 - 24 / 5, 600 + 32 / 5, 12);
line(kirbyStarPositionX - 12 / 5, kirbyStarPositionY + 16 / 5, 800 - 12 / 5, 600 + 16 / 5, 12);
glColor3f(51 / 255.0f, 252 / 255.0f, 5 / 255.0f);
line(kirbyStarPositionX, kirbyStarPositionY, 800, 600, 12);
line(kirbyStarPositionX + 12 / 5, kirbyStarPositionY - 16 / 5, 800 + 12 / 5, 600 - 16 / 5, 12);
glColor3f(34 / 255.0f, 223 / 255.0f, 201 / 255.0f);
line(kirbyStarPositionX + 24 / 5, kirbyStarPositionY - 32 / 5, 800 + 24 / 5, 600 - 32 / 5, 12);
line(kirbyStarPositionX + 36 / 5, kirbyStarPositionY - 48 / 5, 800 + 36 / 5, 600 - 48 / 5, 12);
glColor3f(0 / 255.0f, 153 / 255.0f, 255 / 255.0f);
line(kirbyStarPositionX + 48 / 5, kirbyStarPositionY - 64 / 5, 800 + 48 / 5, 600 - 64 / 5, 12);
line(kirbyStarPositionX + 60 / 5, kirbyStarPositionY - 80 / 5, 800 + 60 / 5, 600 - 80 / 5, 12);
glColor3f(89 / 255.0f, 55 / 255.0f, 235 / 255.0f);
line(kirbyStarPositionX + 72 / 5, kirbyStarPositionY - 96 / 5, 800 + 72 / 5, 600 - 96 / 5, 12);
line(kirbyStarPositionX + 84 / 5, kirbyStarPositionY - 112 / 5, 800 + 84 / 5, 600 - 112 / 5, 12);
//Draw the Kirbystar
glColor3f(255 / 255.0f, 240 / 255.0f, 121 / 255.0f);
roundStars(kirbyStarPositionX, kirbyStarPositionY, 40);
//Show the text
if (stayTime > 20.0f)
{
glColor3f(1, 1, 1);
selectFont(48, ANSI_CHARSET, "Comic Sans MS");
glRasterPos2f(180, 75);
drawString("Click or Press \"e\" to continue");
}
}
//This function is showing the second stage
void stage1(void)
{
//Draw the static stars
starsScene();
//Draw the moving stars
movingStarsScne();
//Draw the grass and flowers
grassScene();
flower(270 - kirbyDistance / 3, 290 - kirbyDistance, 40 + kirbyDistance / 5);
flower(560 + kirbyDistance / 3, 280 - kirbyDistance, 40 + kirbyDistance / 5);
flower(210 - kirbyDistance / 3, 270 - kirbyDistance, 40 + kirbyDistance / 5);
flower(100 - kirbyDistance / 3, 150 - kirbyDistance, 40 + kirbyDistance / 5);
flower(80 - kirbyDistance / 3, 280 - kirbyDistance, 40 + kirbyDistance / 5);
flower(600 + kirbyDistance / 3, 100 - kirbyDistance, 40 + kirbyDistance / 5);
flower(650 + kirbyDistance / 3, 200 - kirbyDistance, 40 + kirbyDistance / 5);
flower(720 + kirbyDistance / 3, 240 - kirbyDistance, 40 + kirbyDistance / 5);
//Draw Kirby
kirby(400, 300 - kirbyDistance, 50);
//Draw the shining stars
if (shining)
{
glColor3f(253 / 255.0f, 199 / 255.0f, 53 / 255.0f);
roundStars(600, 450 - kirbyDistance, shiningStars + 2);
roundStars(600, 150 - kirbyDistance, shiningStars + 2);
roundStars(200, 450 - kirbyDistance, shiningStars + 2);
roundStars(200, 150 - kirbyDistance, shiningStars + 2);
glColor3f(255 / 255.0f, 240 / 255.0f, 121 / 255.0f);
roundStars(600, 450 - kirbyDistance, shiningStars);
roundStars(600, 150 - kirbyDistance, shiningStars);
roundStars(200, 450 - kirbyDistance, shiningStars);
roundStars(200, 150 - kirbyDistance, shiningStars);
}
//Show the text
if (stayTime < 30.0f)
{
glColor3f(1, 1, 1);
selectFont(48, ANSI_CHARSET, "Comic Sans MS");
glRasterPos2f(310, 75);
drawString("Oh! Kirby!");
}
else if (textShown)
{
glColor3f(1, 1, 1);
selectFont(48, ANSI_CHARSET, "Comic Sans MS");
glRasterPos2f(120, 75);
drawString("Try to use \"w\" to get closer to Kirby");
}
}
//This function is showing the third stage
void stage2(void)
{
//Draw the static stars
starsScene();
//Draw the moving stars
movingStarsScne();
//Draw the birthday card
bithdayCard();
//Show the text
if (stayTime > 100.0f && kirbyCakeStickers != true)
{
glColor3f(1, 1, 1);
selectFont(48, ANSI_CHARSET, "Comic Sans MS");
glRasterPos2f(250, 75);
drawString("Feed cake to Kirby");
}
}
//This function controls all the stages and animations will be shown in the windows
void display(void)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 800, 0, 600);
glMatrixMode(GL_MODELVIEW);
glClearColor(0, 50 / 255.0f, 99 / 255.0f, 1);
glClear(GL_COLOR_BUFFER_BIT);
//Show the stages according to the variable "stages"
if (stages == 0)
{
stage0();
}
if (stages == 1)
{
stage1();
}
if (stages == 2)
{
stage2();
}
//Show the transition animation according to the variable "transitionState"
if (transitionState == 1) {
starTransition(10, transitionTime);
}
if (transitionState == 2) {
starTransitionBack(10, transitionBackTime);
}
//Show the star following the mouse
mouseStar(mouseX, mouseY);
glutSwapBuffers();
}
//Set the windown size and some callback function
int main(int argc, char* argv[])
{
glutInit(&argc, (char**)argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100, 100);
glutInitWindowSize(800, 600);
glutCreateWindow("Happy Birthday");
glutDisplayFunc(display);
glutPassiveMotionFunc(mouseMove);
glutTimerFunc(0, update, 0);
glutKeyboardFunc(keyboardInteraction);
glutMouseFunc(mouseInteraction);
glutMainLoop();
return 0;
}