用opengl绘制winxp版扫雷的墨镜笑脸

 
Category: GUI

写在前面

最近看看OpenGL的东西, 学了一些基础的语法, 所以用来做点东西, 用基本的点线多边形等来绘制一个Windows XP中经典的游戏扫雷里面的标志性笑脸, 如下图:

minesweeper-1

思路

这个笑脸有四种形态,分别是初始状态的笑脸, 点击时候的o型嘴, 赢的时候的墨镜脸和输了时候的伤心脸, 下面通过简单的直线和圆来绘制, 都是简单图形, 本质上其实都是点的绘制, 这里我封装了一下函数, 调用会方便一些, 分别是通过vector<GLint>表示的点, 这样用initializer_list时候会方便很多.

其次就是一些小细节, 例如圆的绘制中的角度问题(这里就是嘴的绘制), 还有键鼠事件的交互, 我觉得熟悉了这个流程之后, 主要的思路就都放在点的坐标上了, 这里我建议可以通过Geogebra这款软件进行草图的绘制, 找到待绘制的点的坐标之后会方便很多.

代码(C++)

下面是代码, 这里分成几个部分分别说:

首先是导言区, 导入相应的模块和常量.

#include <GL/glut.h>
#include <vector>
//#include <iostream>
#include <cmath>

using namespace std;
const int w = 400, h = 400;

const GLfloat pi = 3.1415926536f;

void init() {
    glClearColor(1, 0, 1, 0);//黑色背景
    glMatrixMode(GL_PROJECTION);//正投影
    glLoadIdentity();
    gluOrtho2D(-w, w, -h, h);
}

然后是一些自定义的函数, 分别是画圆画直线和画多边形(这里是四边形)的, 用起来比直接读取点要方便一些.

void drawCircle(GLint x, GLint y, GLint r,
                vector<GLfloat> rgb = {0, 0, 0},
                GLint cir = 2, int mode = GL_POLYGON,
                GLfloat lw = 1,
                const int n = 10000) {
    glColor3f(rgb[0], rgb[1], rgb[2]);
    glLineWidth(lw);
    glBegin(mode);
    for (int i = 0; i < n; i++) {
        glVertex2i(x + r * cos(cir * pi / n * i),
                   y + r * sin(cir * pi / n * i));
    }
    glEnd();
}

void drawLine(vector<GLint> p1, vector<GLint> p2, GLfloat lw = 1,
              vector<GLfloat> rgb = {0, 0, 0},//default:black
              int mode = GL_LINE_STRIP) {
    glColor3f(rgb[0], rgb[1], rgb[2]);
    glLineWidth(lw);
    glBegin(mode);
    glVertex2i(p1[0], p1[1]);
    glVertex2i(p2[0], p2[1]);
    glEnd();
}

void drawPolygon(vector<GLint> p1, vector<GLint> p2,
                 vector<GLint> p3, vector<GLint> p4,
                 vector<GLfloat> rgb = {0, 0, 0},//default:black
                 int mode = GL_POLYGON,
                 GLfloat lw = 1) {
    glColor3f(rgb[0], rgb[1], rgb[2]);
    glLineWidth(lw);
    glBegin(mode);
    glVertex2i(p1[0], p1[1]);
    glVertex2i(p2[0], p2[1]);
    glVertex2i(p3[0], p3[1]);
    glVertex2i(p4[0], p4[1]);
    glEnd();
}

最后就是主要的绘制了,

void display() {// 绘制基本的笑脸
    drawCircle(0, 0, 50, {1, 1, 0});//face
    drawCircle(-20, 15, 6);//left eye
    drawCircle(20, 15, 6);//right eye
    drawCircle(0, -10, 20, {0, 0, 0}, -1, GL_LINE_STRIP, 3);//mouth
    glFlush();
}

void openMouth() {//点击时候的张嘴
    drawCircle(-20, 15, 8);//left eye
    drawCircle(20, 15, 8);//right eye
    drawCircle(0, -10, 20, {1, 1, 0}, -1, GL_LINE_STRIP, 3);//cover old mouth
    drawCircle(0, -18, 10, {0, 0, 0}, 2, GL_LINE_STRIP, 3);//mouth
    glFlush();
}

void coolFace() {//赢时候的墨镜表情
    drawPolygon({-40, 25}, {-10, 25}, {-10, 8}, {-40, 8});//left eye
    drawPolygon({40, 25}, {10, 25}, {10, 8}, {40, 8});//right eye
    drawLine({-10, 23}, {10, 23}, 3);//glasses middle
    drawLine({-45, 20}, {-40, 25}, 3);//glasses left
    drawLine({45, 20}, {40, 25}, 3);//glasses right
    glFlush();
}

void dieFace() {//输了时候的伤心脸
    drawCircle(0, 0, 50, {1, 1, 0});//face
    drawLine({-12, 7}, {-28, 23}, 3);
    drawLine({-12, 23}, {-28, 7}, 3);//left eye
    drawLine({12, 7}, {28, 23}, 3);
    drawLine({12, 23}, {28, 7}, 3);//right eye
    drawCircle(0, -10, 20, {0, 0, 0}, 1, GL_LINE_STRIP, 3);//mouth
    glFlush();
}

// 这里是键盘交互, 通过按下q或者esc键实现退出, 按d实现伤心脸, 按w实现墨镜脸
void Key(unsigned char key, int x, int y) {
    switch (key) {
        case 27:
        case 'q':
            exit(0);
        case 'd':
            dieFace();
            break;
        case 'w':
            coolFace();
            break;
    }
}

// 鼠标事件, 点击笑脸变形为O型嘴
void Mouse(int btn, int state, int x, int y) {
    // 如果在圆内, 点击生效
    if (x * x + y * y - w * x - w * y + (w / 2) * w < 50 * 50) {
        if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
//            cout << x << " " << y << endl;
            openMouth();
        } else {
            display();
        }
    }
}


int main(int argc, char **argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
    glutInitWindowPosition(200, 200);
    glutInitWindowSize(w, h);
    glutCreateWindow("MineSweeper Smiling Face");
    init();
    glutDisplayFunc(display);
    glutKeyboardFunc(Key);
    glutMouseFunc(Mouse);
    glutMainLoop();
    return 0;
}

最后实现结果:

<img src=”https://s2.loli.net/2022/08/03/XDSeTwQIdNH3UOc.gif” alt=”aa” width=300px; />

顺便附带一下cmake文件:(我的测试环境是Mac, 在Windows中需要配置一下路径)

cmake_minimum_required(VERSION 3.21)
project(minesweeper-smile)

set(CMAKE_CXX_STANDARD 17)
set(program_SOURCES main.cpp)
link_directories("/opt/homebrew/lib")
include_directories("/opt/homebrew/include")

find_library(Cocoa_Library Cocoa)
find_library(OpenGl_Library OpenGL)
find_library(GLUT_Library glut)
add_executable(${PROJECT_NAME} ${program_SOURCES})
target_link_libraries(${PROJECT_NAME}
        ${Cocoa_Library}
        ${OpenGl_Library}
        ${GLUT_Library}
        )