c++
复制代码
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLShaderProgram>
#include <QMatrix4x4>
#include <QVector3D>
#include <QVector>
#include <QColor>
#include <QOpenGLFunctions_3_3_Core>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QString>
// 3D渲染控件
class GLWidget : public QOpenGLWidget
{
Q_OBJECT
public:
explicit GLWidget(QWidget* parent = nullptr);
~GLWidget() override;
// 基础功能接口
void setBackgroundColor(const QColor& color);
// 清除所有导入的模型
void clearAllModels();
// 设置模型颜色
void setModelColor(int modelIndex, const QColor& color);
bool loadModel(const QString &filePath);
protected:
// OpenGL生命周期函数
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
// 鼠标交互事件
void mousePressEvent(QMouseEvent* e) override;
void mouseMoveEvent(QMouseEvent* e) override;
void wheelEvent(QWheelEvent* e) override;
void mouseReleaseEvent(QMouseEvent* e) override;
private:
// -------------------------- 基础渲染核心(全局共享)--------------------------
QOpenGLShaderProgram* m_mainProgram; // 主着色器程序(坐标轴+模型共用)
QOpenGLFunctions_3_3_Core* m_glFunc; // OpenGL 3.3核心函数对象
QColor m_backgroundColor; // 背景色
QMatrix4x4 m_projection; // 投影矩阵
bool m_isGLInitialized = false; // OpenGL初始化标记
// -------------------------- 相机交互参数 --------------------------
float m_cameraDistance = 10.0f;
float m_cameraAngleX = 30.0f;
float m_cameraAngleY = 45.0f;
QPoint m_lastMousePos;
bool m_isDragging = false;
// -------------------------- 坐标轴模块(独立封装)--------------------------
struct AxisModule {
QVector<QVector3D> vertices; // 坐标轴顶点数据
GLuint vao = 0; // 坐标轴VAO
GLuint vbo = 0; // 坐标轴VBO
const float axisLen = 3.0f; // 轴长
const float arrowLen = 0.2f; // 箭头长度
const float arrowHalfWidth = 0.16f; // 箭头半宽(arrowLen * 0.8)
// 初始化坐标轴数据和GPU资源
void init(QOpenGLFunctions_3_3_Core* glFunc, QOpenGLShaderProgram* program);
// 绘制坐标轴
void draw(QOpenGLFunctions_3_3_Core* glFunc, QOpenGLShaderProgram* program);
// 释放GPU资源
void cleanup(QOpenGLFunctions_3_3_Core* glFunc);
} m_axisModule;
// -------------------------- 模型模块(支持多模型,预留OBJ导入)--------------------------
// 单个模型的数据结构
struct ModelData {
QVector<QVector3D> vertices; // 模型顶点(OBJ解析后存储这里)
QVector<QVector3D> normals; // 模型法向量(后续光照用)
QColor color; // 模型颜色
GLuint vao = 0; // 模型VAO
GLuint vbo = 0; // 顶点VBO
GLuint normalVBO = 0; // 法向量VBO(预留)
bool isInitialized = false; // 模型GPU资源是否初始化
};
QVector<ModelData> m_models; // 所有导入的模型集合
// -------------------------- 核心辅助函数 --------------------------
// 初始化OpenGL基础环境(着色器、全局设置)
bool initOpenGLContext();
// 编译链接着色器程序(支持顶点+片段着色器,适配坐标轴和模型)
bool compileMainShaderProgram();
// 构建视图矩阵(供所有渲染对象使用)
QMatrix4x4 buildViewMatrix() const;
// -------------------------- 模型辅助函数(OBJ导入相关)--------------------------
// 初始化单个模型的GPU资源(VAO/VBO)
bool initModelGPUResources(ModelData& model);
// 解析OBJ文件(后续实现:从文件读取顶点、法向量等数据)
bool parseOBJFile(const QString& filePath, QVector<QVector3D>& outVertices, QVector<QVector3D>& outNormals);
};
#endif // GLWIDGET_H
#include "glwidget.h"
#include <QDebug>
#include <QFile>
#include <QTextStream>
#include <cmath>
#include<QFileInfo>
// -------------------------- 构造/析构函数 --------------------------
GLWidget::GLWidget(QWidget* parent) : QOpenGLWidget(parent)
{
// 1. 配置OpenGL版本
QSurfaceFormat format;
format.setVersion(3, 3);
format.setProfile(QSurfaceFormat::CoreProfile);
format.setDepthBufferSize(24);
format.setSamples(4);
setFormat(format);
// 2. 初始化背景色(黑色)
m_backgroundColor = QColor(0, 0, 0);
setBackgroundColor(m_backgroundColor);
}
GLWidget::~GLWidget()
{
makeCurrent();
// 释放模型资源
clearAllModels();
// 释放坐标轴资源
m_axisModule.cleanup(m_glFunc);
// 释放着色器程序
if (m_mainProgram != nullptr) {
delete m_mainProgram;
m_mainProgram = nullptr;
}
doneCurrent();
}
// 统一模型加载接口
bool GLWidget::loadModel(const QString& filePath)
{
if (!m_isGLInitialized) {
qCritical() << "[ERROR] OpenGL未初始化,无法加载模型!";
return false;
}
// 1. 获取文件后缀名(判断模型格式)
QFileInfo fileInfo(filePath);
QString suffix = fileInfo.suffix().toLower();
// 2. 根据格式调用对应解析函数(当前仅实现OBJ)
QVector<QVector3D> vertices;
QVector<QVector3D> normals;
bool parseSuccess = false;
if (suffix == "obj") {
parseSuccess = parseOBJFile(filePath, vertices, normals); // 解析OBJ
} else if (suffix == "fbx" || suffix == "gltf" || suffix == "glb" || suffix == "3ds") {
qWarning() << "[WARNING] " << suffix << "格式暂未支持,仅支持OBJ!";
return false;
} else {
qCritical() << "[ERROR] 不支持的模型格式:" << suffix;
return false;
}
// 3. 验证解析结果
if (!parseSuccess || vertices.size() < 3) {
qCritical() << "[ERROR] 模型解析失败或顶点数量不足!";
return false;
}
// 创建模型数据(使用默认颜色:浅灰色 QColor(200, 200, 200))
ModelData newModel;
newModel.vertices = vertices;
newModel.normals = normals;
newModel.color = QColor(0, 0, 250); // 固定默认颜色,不再通过参数传入
newModel.isInitialized = false;
// 关键修复:手动激活OpenGL上下文(动态创建资源必须加!)
makeCurrent();
// 5. 初始化GPU资源并添加到模型集合
if (!initModelGPUResources(newModel)) {
qCritical() << "[ERROR] 模型GPU资源初始化失败!";
return false;
}
m_models.append(newModel);
qDebug() << "[INFO] 模型加载成功:" << filePath
<< " 顶点数:" << vertices.size()
<< " 格式:" << suffix
<< " 颜色:浅灰色(200,200,200)";
doneCurrent(); // 释放上下文,避免占用
update();
return true;
}
// -------------------------- 基础功能接口 --------------------------
void GLWidget::setBackgroundColor(const QColor& color)
{
m_backgroundColor = color;
update();
}
void GLWidget::clearAllModels()
{
makeCurrent();
// 释放每个模型的GPU资源
for (auto& model : m_models) {
if (model.isInitialized && m_glFunc != nullptr) {
m_glFunc->glDeleteVertexArrays(1, &model.vao);
m_glFunc->glDeleteBuffers(1, &model.vbo);
m_glFunc->glDeleteBuffers(1, &model.normalVBO);
}
}
m_models.clear();
qDebug() << "[INFO] 所有模型已清除!";
doneCurrent();
update();
}
void GLWidget::setModelColor(int modelIndex, const QColor& color)
{
if (modelIndex >= 0 && modelIndex < m_models.size()) {
m_models[modelIndex].color = color;
update();
} else {
qWarning() << "[WARNING] 模型索引无效:" << modelIndex;
}
}
// -------------------------- OpenGL生命周期函数 --------------------------
void GLWidget::initializeGL()
{
// 初始化OpenGL基础环境(着色器、全局设置)
if (!initOpenGLContext()) {
qCritical() << "[ERROR] OpenGL初始化失败!";
return;
}
// 初始化坐标轴模块
m_axisModule.init(m_glFunc, m_mainProgram);
m_isGLInitialized = true;
qDebug() << "[INFO] GLWidget初始化完成(支持坐标轴+OBJ模型导入)!";
}
void GLWidget::resizeGL(int w, int h)
{
if (m_glFunc == nullptr) return;
// 设置视口
m_glFunc->glViewport(0, 0, w, h);
// 更新投影矩阵(透视投影)
m_projection.setToIdentity();
m_projection.perspective(
45.0f,
(float)w / qMax(h, 1),
0.1f,
100.0f
);
}
void GLWidget::paintGL()
{
if (!m_isGLInitialized) return;
// 1. 清空缓冲区
m_glFunc->glClearColor(
m_backgroundColor.redF(),
m_backgroundColor.greenF(),
m_backgroundColor.blueF(),
m_backgroundColor.alphaF()
);
m_glFunc->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 绑定主着色器程序
if (!m_mainProgram->bind()) {
qWarning() << "[WARNING] 着色器程序绑定失败!";
return;
}
// 传递全局矩阵(所有渲染对象共用)
QMatrix4x4 view = buildViewMatrix();
QMatrix4x4 modelMatrix; // 模型矩阵(后续可用于模型平移旋转)
modelMatrix.setToIdentity();
m_mainProgram->setUniformValue("projection", m_projection);
m_mainProgram->setUniformValue("view", view);
m_mainProgram->setUniformValue("model", modelMatrix);
// 4. 绘制坐标轴
m_axisModule.draw(m_glFunc, m_mainProgram);
// 5. 绘制所有导入的模型
for (const ModelData& model : m_models) {
if (!model.isInitialized) continue;
// 传递模型颜色
m_mainProgram->setUniformValue("color", QVector3D(
model.color.redF(),
model.color.greenF(),
model.color.blueF()
));
// 绑定模型VAO并绘制(暂时用GL_TRIANGLES,后续可根据OBJ索引调整)
m_glFunc->glBindVertexArray(model.vao);
m_glFunc->glDrawArrays(GL_TRIANGLES, 0, model.vertices.size());
}
// 解绑资源
m_glFunc->glBindVertexArray(0);
m_mainProgram->release();
m_glFunc->glLineWidth(1.0f);
}
// -------------------------- 鼠标交互事件 --------------------------
void GLWidget::mousePressEvent(QMouseEvent* e)
{
if (e->button() == Qt::LeftButton) {
m_lastMousePos = e->pos();
m_isDragging = true;
setCursor(Qt::ClosedHandCursor);
}
QOpenGLWidget::mousePressEvent(e);
}
void GLWidget::mouseMoveEvent(QMouseEvent* e)
{
if (!m_isDragging) {
QOpenGLWidget::mouseMoveEvent(e);
return;
}
int dx = e->x() - m_lastMousePos.x();
int dy = e->y() - m_lastMousePos.y();
m_cameraAngleY += dx * 0.5f;
m_cameraAngleX += dy * 0.5f;
m_cameraAngleX = qBound(-89.0f, m_cameraAngleX, 89.0f);
m_lastMousePos = e->pos();
update();
QOpenGLWidget::mouseMoveEvent(e);
}
void GLWidget::wheelEvent(QWheelEvent* e)
{
float wheelDelta = e->angleDelta().y() * 0.01f;
m_cameraDistance -= wheelDelta;
m_cameraDistance = qMax(5.0f, m_cameraDistance);
update();
QOpenGLWidget::wheelEvent(e);
}
void GLWidget::mouseReleaseEvent(QMouseEvent* e)
{
if (e->button() == Qt::LeftButton) {
m_isDragging = false;
setCursor(Qt::ArrowCursor);
}
QOpenGLWidget::mouseReleaseEvent(e);
}
// -------------------------- 核心辅助函数 --------------------------
bool GLWidget::initOpenGLContext()
{
// 1. 获取OpenGL函数对象
m_glFunc = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>();
if (m_glFunc == nullptr) {
qCritical() << "[ERROR] 设备不支持OpenGL 3.3核心模式!";
return false;
}
m_glFunc->initializeOpenGLFunctions();
// 2. 启用全局渲染设置
m_glFunc->glEnable(GL_DEPTH_TEST);
m_glFunc->glEnable(GL_LINE_SMOOTH);
m_glFunc->glEnable(GL_BLEND);
m_glFunc->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
m_glFunc->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // 关键:启用面填充(默认可能是线框)
// 3. 编译着色器程序
if (!compileMainShaderProgram()) {
qCritical() << "[ERROR] 着色器程序编译失败!";
return false;
}
return true;
}
bool GLWidget::compileMainShaderProgram()
{
m_mainProgram = new QOpenGLShaderProgram(this);
// 顶点着色器(支持顶点位置+法向量,预留光照接口)
const char* vertexShaderSrc = R"(
#version 330 core
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec3 aNormal; // 法向量输入(预留)
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
out vec3 fragNormal; // 传递法向量到片段着色器(预留)
void main()
{
gl_Position = projection * view * model * vec4(aPosition, 1.0);
fragNormal = aNormal; // 暂时传递,后续光照用
}
)";
if (!m_mainProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc)) {
qCritical() << "[ERROR] 顶点着色器编译失败:" << m_mainProgram->log();
return false;
}
// 片段着色器(支持统一颜色,后续可扩展纹理/光照)
const char* fragmentShaderSrc = R"(
#version 330 core
uniform vec3 color;
in vec3 fragNormal; // 接收法向量(预留)
out vec4 FragColor;
void main()
{
FragColor = vec4(color, 1.0); // 暂时用统一颜色,后续可加光照计算
}
)";
if (!m_mainProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc)) {
qCritical() << "[ERROR] 片段着色器编译失败:" << m_mainProgram->log();
return false;
}
// 链接着色器
if (!m_mainProgram->link()) {
qCritical() << "[ERROR] 着色器链接失败:" << m_mainProgram->log();
return false;
}
return true;
}
QMatrix4x4 GLWidget::buildViewMatrix() const
{
QMatrix4x4 view;
view.setToIdentity();
view.translate(0.0f, 0.0f, -m_cameraDistance);
view.rotate(m_cameraAngleX, 1.0f, 0.0f, 0.0f);
view.rotate(m_cameraAngleY, 0.0f, 1.0f, 0.0f);
return view;
}
// -------------------------- 模型辅助函数 --------------------------
bool GLWidget::initModelGPUResources(ModelData& model)
{
if (m_glFunc == nullptr || m_mainProgram == nullptr) {
qCritical() << "[ERROR] OpenGL资源未初始化!";
return false;
}
// 生成VAO/VBO
m_glFunc->glGenVertexArrays(1, &model.vao);
m_glFunc->glGenBuffers(1, &model.vbo);
m_glFunc->glGenBuffers(1, &model.normalVBO);
// 绑定VAO
m_glFunc->glBindVertexArray(model.vao);
// 绑定顶点VBO
m_glFunc->glBindBuffer(GL_ARRAY_BUFFER, model.vbo);
m_glFunc->glBufferData(
GL_ARRAY_BUFFER,
model.vertices.size() * sizeof(QVector3D),
model.vertices.data(),
GL_STATIC_DRAW
);
// 配置顶点属性(location=0)
int posLoc = m_mainProgram->attributeLocation("aPosition");
if (posLoc == -1) {
qCritical() << "[ERROR] 未找到顶点属性aPosition!";
return false;
}
m_mainProgram->enableAttributeArray(posLoc);
m_glFunc->glVertexAttribPointer(
posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(QVector3D), nullptr
);
// 配置法向量属性(location=1,预留)
if (!model.normals.isEmpty()) {
m_glFunc->glBindBuffer(GL_ARRAY_BUFFER, model.normalVBO);
m_glFunc->glBufferData(
GL_ARRAY_BUFFER,
model.normals.size() * sizeof(QVector3D),
model.normals.data(),
GL_STATIC_DRAW
);
int normalLoc = m_mainProgram->attributeLocation("aNormal");
if (normalLoc != -1) {
m_mainProgram->enableAttributeArray(normalLoc);
m_glFunc->glVertexAttribPointer(
normalLoc, 3, GL_FLOAT, GL_FALSE, sizeof(QVector3D), nullptr
);
}
}
// 解绑资源
m_glFunc->glBindBuffer(GL_ARRAY_BUFFER, 0);
m_glFunc->glBindVertexArray(0);
model.isInitialized = true;
return true;
}
bool GLWidget::parseOBJFile(const QString& filePath, QVector<QVector3D>& outVertices, QVector<QVector3D>& outNormals)
{
// -------------------------- 后续实现OBJ解析核心逻辑 --------------------------
// 目前仅为占位,返回一个简单的三角形模型用于测试
outVertices.clear();
outNormals.clear();
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCritical() << "[ERROR] 无法打开OBJ文件:" << filePath;
return false;
}
QTextStream in(&file);
QVector<QVector3D> tempVerts; // 临时存储v指令的顶点
QVector<QVector3D> tempNorms; // 临时存储vn指令的法向量
QString line;
while (!in.atEnd()) {
line = in.readLine().trimmed();
QStringList parts = line.split(QRegExp("\\s+"));
if (parts.isEmpty()) continue;
// 解析顶点(v x y z)
if (parts[0] == "v") {
if (parts.size() >= 4) {
float x = parts[1].toFloat();
float y = parts[2].toFloat();
float z = parts[3].toFloat();
tempVerts.append(QVector3D(x, y, z));
}
}
// 解析法向量(vn x y z)
else if (parts[0] == "vn") {
if (parts.size() >= 4) {
float x = parts[1].toFloat();
float y = parts[2].toFloat();
float z = parts[3].toFloat();
tempNorms.append(QVector3D(x, y, z));
}
}
// 解析面(f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ...)
else if (parts[0] == "f") {
for (int i = 1; i < parts.size(); ++i) {
QStringList comps = parts[i].split('/');
if (comps.size() >= 1) {
// 顶点索引(OBJ索引从1开始,转换为0开始)
int vertIdx = comps[0].toInt() - 1;
if (vertIdx >= 0 && vertIdx < tempVerts.size()) {
outVertices.append(tempVerts[vertIdx]);
}
}
if (comps.size() >= 3) {
// 法向量索引
int normIdx = comps[2].toInt() - 1;
if (normIdx >= 0 && normIdx < tempNorms.size()) {
outNormals.append(tempNorms[normIdx]);
}
}
}
}
}
file.close();
return !outVertices.isEmpty();
}
// -------------------------- 坐标轴模块实现 --------------------------
void GLWidget::AxisModule::init(QOpenGLFunctions_3_3_Core* glFunc, QOpenGLShaderProgram* program)
{
// 1. 生成坐标轴顶点数据
vertices.clear();
// X轴(红色)
vertices.append(QVector3D(0.0f, 0.0f, 0.0f));
vertices.append(QVector3D(axisLen, 0.0f, 0.0f));
vertices.append(QVector3D(axisLen - arrowLen, arrowHalfWidth, 0.0f));
vertices.append(QVector3D(axisLen, 0.0f, 0.0f));
vertices.append(QVector3D(axisLen - arrowLen, -arrowHalfWidth, 0.0f));
// Y轴(绿色)
vertices.append(QVector3D(0.0f, 0.0f, 0.0f));
vertices.append(QVector3D(0.0f, axisLen, 0.0f));
vertices.append(QVector3D(arrowHalfWidth, axisLen - arrowLen, 0.0f));
vertices.append(QVector3D(0.0f, axisLen, 0.0f));
vertices.append(QVector3D(-arrowHalfWidth, axisLen - arrowLen, 0.0f));
// Z轴(蓝色)
vertices.append(QVector3D(0.0f, 0.0f, 0.0f));
vertices.append(QVector3D(0.0f, 0.0f, axisLen));
vertices.append(QVector3D(0.0f, arrowHalfWidth, axisLen - arrowLen));
vertices.append(QVector3D(0.0f, 0.0f, axisLen));
vertices.append(QVector3D(0.0f, -arrowHalfWidth, axisLen - arrowLen));
// 2. 创建VAO/VBO
glFunc->glGenVertexArrays(1, &vao);
glFunc->glGenBuffers(1, &vbo);
glFunc->glBindVertexArray(vao);
glFunc->glBindBuffer(GL_ARRAY_BUFFER, vbo);
glFunc->glBufferData(
GL_ARRAY_BUFFER,
vertices.size() * sizeof(QVector3D),
vertices.data(),
GL_STATIC_DRAW
);
// 配置顶点属性
int posLoc = program->attributeLocation("aPosition");
if (posLoc != -1) {
program->enableAttributeArray(posLoc);
glFunc->glVertexAttribPointer(
posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(QVector3D), nullptr
);
}
glFunc->glBindBuffer(GL_ARRAY_BUFFER, 0);
glFunc->glBindVertexArray(0);
qDebug() << "[INFO] 坐标轴模块初始化完成,顶点数:" << vertices.size();
}
void GLWidget::AxisModule::draw(QOpenGLFunctions_3_3_Core* glFunc, QOpenGLShaderProgram* program)
{
glFunc->glLineWidth(3.0f);
// 绘制X轴(红色)
program->setUniformValue("color", QVector3D(1.0f, 0.0f, 0.0f));
glFunc->glBindVertexArray(vao);
glFunc->glDrawArrays(GL_LINE_STRIP, 0, 5);
// 绘制Y轴(绿色)
program->setUniformValue("color", QVector3D(0.0f, 1.0f, 0.0f));
glFunc->glDrawArrays(GL_LINE_STRIP, 5, 5);
// 绘制Z轴(蓝色)
program->setUniformValue("color", QVector3D(0.0f, 0.0f, 1.0f));
glFunc->glDrawArrays(GL_LINE_STRIP, 10, 5);
glFunc->glBindVertexArray(0);
}
void GLWidget::AxisModule::cleanup(QOpenGLFunctions_3_3_Core* glFunc)
{
if (vao != 0) {
glFunc->glDeleteVertexArrays(1, &vao);
vao = 0;
}
if (vbo != 0) {
glFunc->glDeleteBuffers(1, &vbo);
vbo = 0;
}
vertices.clear();
}
#pragma once
#include <QMainWindow>
class GLWidget;
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
private slots:
void onOpenModel();
private:
GLWidget *m_glWidget;
};
#include "MainWindow.h"
#include "GLWidget/glwidget.h"
#include <QMenuBar>
#include <QFileDialog>
#include <QStatusBar>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) {
m_glWidget = new GLWidget(this);
setCentralWidget(m_glWidget);
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
QAction *openAction = fileMenu->addAction(tr("&Open Model..."));
connect(openAction, &QAction::triggered, this, &MainWindow::onOpenModel);
fileMenu->addSeparator();
QAction *exitAction = fileMenu->addAction(tr("E&xit"));
connect(exitAction, &QAction::triggered, this, &QWidget::close);
statusBar()->showMessage(tr("Ready"));
}
void MainWindow::onOpenModel() {
QString file = QFileDialog::getOpenFileName(this, tr("Open Model"), QString(), tr("Model Files (*.obj *.fbx *.gltf *.glb *.3ds);;All Files (*)"));
if (file.isEmpty()) return;
bool ok = m_glWidget->loadModel(file);
m_glWidget->setModelColor(0, QColor(255, 255,255));
if (!ok) {
statusBar()->showMessage(tr("Failed to load model"));
} else {
statusBar()->showMessage(tr("Loaded: %1").arg(file));
}
}
#include <QApplication>
#include "MainWindow.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow w;
w.resize(1280, 800);
w.show();
return app.exec();
}