
自定义QOpenGLWidget控件实现2D点云(XOZ平面内的点)绘制,支持
(1)放缩;
(2)平移;
(3)绘制网格;
PointCloudViewer.h
cpp
#ifndef POINTCLOUDVIEWER_H
#define POINTCLOUDVIEWER_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QVector3D>
#include <QMatrix4x4>
#include <QMouseEvent>
#include <QWheelEvent>
#include <vector>
#include<glm/glm.hpp>
#include <QOpenGLVertexArrayObject>
#include<MouseManager.h>
class PointCloudViewer : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit PointCloudViewer(QWidget* parent = nullptr);
~PointCloudViewer();
void setPointCloud(const std::vector<QVector3D>& points);
void setGridSpacing(float spacing);
void setGridColor(const QColor& color);
void setPointSize(float size);
void setPointColor(const QColor& color);
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void wheelEvent(QWheelEvent* event) override;
private:
void initShaders();
void updateProjection();
void updateView();
void calculateBoundingBox();
QVector2D worldToScreen(const QVector3D& worldPos) const;
QVector3D screenToWorld(const QVector2D& screenPos, float y = 0.0f) const;
void printContextInformation();
void drawGrids();
bool isTranslating() const;
float window_ratio = 1.0;
bool rotating = false;
bool translating = false;
bool selecting = false;
float left_plane = 0; // = -1000.0f * SCR_RATIO;
float right_plane = 0;// = 1000.0f * SCR_RATIO;
float top_plane = 1000.0f;
float bottom_plane = -1000.0f;
float near_plane = 0.1;
float far_plane = 10.0f;
float x_span = 0;
float y_span = 0;
float z_span = 0;
ViewPort view_whole;
MouseManager mouse_manager;
// OpenGL resources
QOpenGLShaderProgram m_pointProgram;
QOpenGLShaderProgram m_gridProgram;
QOpenGLBuffer m_pointBuffer;
QOpenGLBuffer m_gridBuffer;
// Data
std::vector<QVector3D> m_points;
QVector3D m_minBound;
QVector3D m_maxBound;
float m_gridSpacing;
// Appearance
float m_pointSize;
QColor m_pointColor;
QColor m_gridColor;
// View control
QMatrix4x4 m_projection;
QMatrix4x4 m_view;
QVector3D m_translation;
float m_scale;
QPoint m_lastMousePos;
bool m_dragging;
};
#endif // POINTCLOUDVIEWER_H
PointCloudViewer.cpp
cpp
#include "PointCloudViewer.h"
#include <QOpenGLContext>
#include <QDebug>
#include <QOpenGLShader>
#include <QPainter>
#include <cmath>
static const GLchar* vertexShader_point = "#version 330 core\n"
"layout(location = 0) in vec3 position;\n"
"uniform mat4 mvp_matrix;\n"
"uniform float pointSize;\n"
"void main()\n"
"{\n"
" gl_Position = mvp_matrix * vec4(position, 1.0);\n"
" gl_PointSize = pointSize;\n"
"}\n";
static const GLchar* vertexShader_grid="#version 330 core\n"
"layout(location = 0) in vec3 position;\n"
"uniform mat4 mvp_matrix;\n"
"void main()\n"
"{\n"
" gl_Position = mvp_matrix * vec4(position, 1.0);\n"
"}\n";
static const GLchar* fragmentShader_common = "#version 330 core\n"
"uniform vec4 color;\n"
"out vec4 fragColor;\n"
"void main()\n"
"{\n"
" fragColor = color;\n"
"}\n";
inline void GLClearError() {
while (glGetError() != GL_NO_ERROR);
}
inline bool GLLogCall(const char* function, const char* file, int line) {
GLenum error;
while (error = glGetError()) {
QString errorStr;
switch (error) {
case GL_INVALID_ENUM: errorStr = "GL_INVALID_ENUM"; break;
case GL_INVALID_VALUE: errorStr = "GL_INVALID_VALUE"; break;
case GL_INVALID_OPERATION: errorStr = "GL_INVALID_OPERATION"; break;
case GL_STACK_OVERFLOW: errorStr = "GL_STACK_OVERFLOW"; break;
case GL_STACK_UNDERFLOW: errorStr = "GL_STACK_UNDERFLOW"; break;
case GL_OUT_OF_MEMORY: errorStr = "GL_OUT_OF_MEMORY"; break;
case GL_INVALID_FRAMEBUFFER_OPERATION: errorStr = "GL_INVALID_FRAMEBUFFER_OPERATION"; break;
default: errorStr = QString::number(error); break;
}
qCritical() << "[OpenGL Error] " << errorStr
<< " in " << function
<< " (" << file << ":" << line << ")";
return false;
}
return true;
}
#define GL_CALL(x) \
GLClearError(); \
x; \
if(!GLLogCall(#x, __FILE__, __LINE__)) { \
qFatal("OpenGL error detected, aborting"); \
}
PointCloudViewer::PointCloudViewer(QWidget* parent)
: QOpenGLWidget(parent),
m_gridSpacing(1.0f),
m_pointSize(3.0f),
m_pointColor(Qt::blue),
m_gridColor(Qt::lightGray),
m_translation(0, 0, 0),
m_scale(1.0f),
m_dragging(false)
{
setFocusPolicy(Qt::StrongFocus);
//QSurfaceFormat format;
//format.setVersion(3, 3);
//format.setProfile(QSurfaceFormat::CoreProfile);
//format.setDepthBufferSize(24);
//QSurfaceFormat::setDefaultFormat(format);
}
PointCloudViewer::~PointCloudViewer()
{
makeCurrent();
m_pointBuffer.destroy();
m_gridBuffer.destroy();
doneCurrent();
}
void PointCloudViewer::setPointCloud(const std::vector<QVector3D>& points)
{
m_points = points;
calculateBoundingBox();
update();
}
void PointCloudViewer::setGridSpacing(float spacing)
{
m_gridSpacing = spacing;
update();
}
void PointCloudViewer::setGridColor(const QColor& color)
{
m_gridColor = color;
update();
}
void PointCloudViewer::setPointSize(float size)
{
m_pointSize = size;
update();
}
void PointCloudViewer::setPointColor(const QColor& color)
{
m_pointColor = color;
update();
}
// 打印相关信息,调试用
void PointCloudViewer::printContextInformation()
{
QString glType;
QString glVersion;
QString glProfile;
// 获取版本信息
glType = (context()->isOpenGLES()) ? "OpenGL ES" : "OpenGL";
glVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
// 获取 Profile 信息
#define CASE(c) \
case QSurfaceFormat::c: \
glProfile = #c; \
break
switch (format().profile()) {
CASE(NoProfile);
CASE(CoreProfile);
CASE(CompatibilityProfile);
}
#undef CASE
qDebug() << qPrintable(glType) << qPrintable(glVersion) << "(" << qPrintable(glProfile) << ")";
}
void PointCloudViewer::drawGrids()
{
// Draw grid
if (!m_pointProgram.bind()) {
qWarning() << "Failed to bind point program";
}
m_gridProgram.setUniformValue("mvp_matrix", m_projection * m_view);
m_gridProgram.setUniformValue("color", m_gridColor);
// Generate grid lines
float left = m_minBound.x() - 1.0f;
float right = m_maxBound.x() + 1.0f;
float bottom = m_minBound.z() - 1.0f;
float top = m_maxBound.z() + 1.0f;
// Calculate visible grid range based on current view
QVector3D worldBottomLeft = screenToWorld(QVector2D(0, height()));
QVector3D worldTopRight = screenToWorld(QVector2D(width(), 0));
left = worldBottomLeft.x();
right = worldTopRight.x();
bottom = worldBottomLeft.z();
top = worldTopRight.z();
// Adjust grid spacing based on zoom level
float dynamicGridSpacing = m_gridSpacing;
float minSpacing = 0.1f;
float maxSpacing = 100.0f;
// Find appropriate grid spacing
float viewWidth = right - left;
float targetGridCount = 10.0f; // Aim for about 10 grid lines visible
dynamicGridSpacing = viewWidth / targetGridCount;
// Round to nearest power of 10 multiplied by 1, 2 or 5
float exponent = floor(log10(dynamicGridSpacing));
float fraction = dynamicGridSpacing / pow(10.0f, exponent);
if (fraction < 1.5f) {
dynamicGridSpacing = pow(10.0f, exponent);
}
else if (fraction < 3.0f) {
dynamicGridSpacing = 2.0f * pow(10.0f, exponent);
}
else if (fraction < 7.0f) {
dynamicGridSpacing = 5.0f * pow(10.0f, exponent);
}
else {
dynamicGridSpacing = 10.0f * pow(10.0f, exponent);
}
dynamicGridSpacing = qBound(minSpacing, dynamicGridSpacing, maxSpacing);
// Generate grid lines
std::vector<QVector3D> gridLines;
// Vertical lines
float startX = floor(left / dynamicGridSpacing) * dynamicGridSpacing;
for (float x = startX; x <= right; x += dynamicGridSpacing) {
gridLines.emplace_back(x, 0.0f, bottom);
gridLines.emplace_back(x, 0.0f, top);
}
// Horizontal lines
float startZ = floor(bottom / dynamicGridSpacing) * dynamicGridSpacing;
for (float z = startZ; z <= top; z += dynamicGridSpacing) {
gridLines.emplace_back(left, 0.0f, z);
gridLines.emplace_back(right, 0.0f, z);
}
// Update grid buffer
m_gridBuffer.bind();
m_gridBuffer.allocate(gridLines.data(), gridLines.size() * sizeof(QVector3D));
m_gridBuffer.release();
glLineWidth(1.0f);
m_gridBuffer.bind();
m_gridProgram.enableAttributeArray(0);
m_gridProgram.setAttributeBuffer(0, GL_FLOAT, 0, 3, sizeof(QVector3D));
glDrawArrays(GL_LINES, 0, gridLines.size());
m_gridBuffer.release();
m_gridProgram.release();
// Draw scale markers using QPainter
QPainter painter(this);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
painter.setPen(QPen(m_gridColor, 1));
// Calculate scale factor (world units per pixel)
float scaleFactor = (right - left) / width();
// Draw horizontal scale (top)
float textHeight = painter.fontMetrics().height();
float markerLength = 5.0f;
float startMarkerX = floor(left / dynamicGridSpacing) * dynamicGridSpacing;
for (float x = startMarkerX; x <= right; x += dynamicGridSpacing) {
QVector2D screenPos = worldToScreen(QVector3D(x, 0.0f, top));
painter.drawLine(screenPos.x(), 0, screenPos.x(), markerLength);
QString label = QString::number(x, 'f', dynamicGridSpacing < 1.0f ? 2 : 1);
painter.drawText(screenPos.x() - 20, markerLength + textHeight, 40, textHeight,
Qt::AlignCenter, label);
}
// Draw vertical scale (left)
float startMarkerZ = floor(bottom / dynamicGridSpacing) * dynamicGridSpacing;
for (float z = startMarkerZ; z <= top; z += dynamicGridSpacing) {
QVector2D screenPos = worldToScreen(QVector3D(left, 0.0f, z));
painter.drawLine(0, screenPos.y(), markerLength, screenPos.y());
QString label = QString::number(z, 'f', dynamicGridSpacing < 1.0f ? 2 : 1);
painter.drawText(markerLength + 2,
screenPos.y() - textHeight / 2,
40, textHeight, Qt::AlignLeft | Qt::AlignVCenter, label);
}
painter.end();
}
void PointCloudViewer::initializeGL()
{
initializeOpenGLFunctions();
makeCurrent();
printContextInformation();
QSurfaceFormat format;
format.setSamples(4);//启用4x多重采样
this->setFormat(format);
GL_CALL(glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // 背景色
glClearDepth(1.0); // 深度缓存
initShaders();
// Initialize point buffer
m_pointBuffer.create();
if (!m_points.empty()) {
m_pointBuffer.bind();
m_pointBuffer.allocate(m_points.data(), m_points.size() * sizeof(QVector3D));
m_pointBuffer.release();
}
// Grid buffer will be updated in paintGL
m_gridBuffer.create();
qDebug() << "Renderer:" << (const char*)glGetString(GL_RENDERER);
glDisable(GL_DEPTH_TEST); // 2D绘制通常不需要深度测试
glEnable(GL_PROGRAM_POINT_SIZE); // 启用可编程点大小
}
void PointCloudViewer::resizeGL(int w, int h)
{
view_whole.init(0, 0, w, h);
mouse_manager.setViewPort(&view_whole);
mouse_manager.setScreenSize(w, h);
updateProjection();
}
void PointCloudViewer::paintGL()
{
glViewport(0, 0, width(), height());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, width(), height());
//GL_CALL(glClearColor(1.0f, 0.0f, 0.0f, 1.0f)); // 使用红色便于识别
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw points
if (!m_points.empty()) {
m_pointProgram.bind();
m_pointProgram.setUniformValue("mvp_matrix", m_projection * m_view);
m_pointProgram.setUniformValue("pointSize", m_pointSize);
m_pointProgram.setUniformValue("color", m_pointColor);
glPointSize(m_pointSize);
m_pointBuffer.bind();
m_pointProgram.enableAttributeArray(0);
m_pointProgram.setAttributeBuffer(0, GL_FLOAT, 0, 3, sizeof(QVector3D));
glDrawArrays(GL_POINTS, 0, m_points.size());
m_pointBuffer.release();
m_pointProgram.release();
}
drawGrids();
}
void PointCloudViewer::mousePressEvent(QMouseEvent* event)
{
auto pos = event->pos();
if (event->button() == Qt::LeftButton) {
mouse_manager.recordOneClick(pos.x(), pos.y());
// 通过设置双击回调函数,这里可以打包成一个接口
if (mouse_manager.isDoubleClick())
{
}
mouse_manager.finishClick();
if (!isTranslating())
translating = true;
}
}
void PointCloudViewer::mouseReleaseEvent(QMouseEvent* event)
{
mouse_manager.releaseLeftBtn();
translating = false;
}
bool PointCloudViewer::isTranslating() const
{
return translating;
}
void PointCloudViewer::mouseMoveEvent(QMouseEvent* event)
{
auto pos = event->pos();
glm::vec2 offsetPos = mouse_manager.updateMousePos(pos.x(), pos.y());
glm::dvec2 lastMousePos = mouse_manager.lastPos();
if (isTranslating())
{
left_plane -= offsetPos.x / (float)view_whole.w * x_span;
top_plane -= offsetPos.y / (float)view_whole.h * y_span;
}
updateProjection();
updateView();
update();
}
void PointCloudViewer::wheelEvent(QWheelEvent* event)
{
auto pos = event->pos();
mouse_manager.recordOneClick(pos.x(), pos.y());
glm::vec2 lastMousePos = mouse_manager.lastPos();
if (event->angleDelta().y() < 0)
{
left_plane -= lastMousePos.x / (float)view_whole.w * x_span * 0.25;
top_plane += lastMousePos.y / (float)view_whole.h * y_span * 0.25;
x_span *= 1.25f;
y_span *= 1.25f;
}
else if (event->angleDelta().y() > 0)
{
left_plane += lastMousePos.x / (float)view_whole.w * x_span * 0.2;
top_plane -= lastMousePos.y / (float)view_whole.h * y_span * 0.2;
x_span *= 0.8f;
y_span *= 0.8f;
}
updateProjection();
updateView();
update();
}
void PointCloudViewer::initShaders()
{
// Point shader
if (!m_pointProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShader_point))
qWarning() << "Could not compile vertex shader:" << m_pointProgram.log();
if (!m_pointProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShader_common))
qWarning() << "Could not compile fragment shader:" << m_pointProgram.log();
if (!m_pointProgram.link())
qWarning() << "Could not link shader program:" << m_pointProgram.log();
// Grid shader
if (!m_gridProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShader_grid))
qWarning() << "Could not compile vertex shader:" << m_gridProgram.log();
if (!m_gridProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShader_common))
qWarning() << "Could not compile fragment shader:" << m_gridProgram.log();
if (!m_gridProgram.link())
qWarning() << "Could not link shader program:" << m_gridProgram.log();
}
void PointCloudViewer::updateProjection()
{
m_projection.setToIdentity();
m_projection.ortho(left_plane,
left_plane + x_span,
top_plane - y_span,
top_plane,
near_plane,
far_plane);
}
void PointCloudViewer::updateView()
{
m_view.setToIdentity();
// 调整视图矩阵,确保正确观察XOZ平面
m_view.lookAt(QVector3D(0, -1, 0), // 摄像机位置 (在Y轴上方)
QVector3D(0, 0, 0), // 观察点 (原点)
QVector3D(0, 0, 1)); // 上向量 (Z轴方向)
//m_view.translate(m_translation);
//m_view.scale(m_scale);
}
void PointCloudViewer::calculateBoundingBox()
{
if (m_points.empty()) {
m_minBound = QVector3D(-1, -1, -1);
m_maxBound = QVector3D(1, 1, 1);
return;
}
m_minBound = m_points[0];
m_maxBound = m_points[0];
for (const auto& point : m_points) {
m_minBound.setX(qMin(m_minBound.x(), point.x()));
m_minBound.setY(qMin(m_minBound.y(), point.y()));
m_minBound.setZ(qMin(m_minBound.z(), point.z()));
m_maxBound.setX(qMax(m_maxBound.x(), point.x()));
m_maxBound.setY(qMax(m_maxBound.y(), point.y()));
m_maxBound.setZ(qMax(m_maxBound.z(), point.z()));
}
// Ensure non-zero bounds
if (qFuzzyCompare(m_maxBound.x(), m_minBound.x())) {
m_maxBound.setX(m_minBound.x() + 1.0f);
}
if (qFuzzyCompare(m_maxBound.z(), m_minBound.z())) {
m_maxBound.setZ(m_minBound.z() + 1.0f);
}
x_span = m_maxBound.x() - m_minBound.x();
y_span = x_span / window_ratio;
z_span = far_plane - near_plane;
window_ratio = (float)width() / (float)height();
left_plane = - x_span / 2.0;
top_plane = - y_span / 2.0;
//right_plane = 1000.0f * window_ratio;
updateProjection();
updateView();
update();
}
QVector2D PointCloudViewer::worldToScreen(const QVector3D& worldPos) const
{
QVector4D clipPos = m_projection * m_view * QVector4D(worldPos, 1.0f);
QVector3D ndcPos = clipPos.toVector3D() / clipPos.w();
float x = (ndcPos.x() + 1.0f) * 0.5f * width();
float y = (1.0f - ndcPos.y()) * 0.5f * height(); // Note: using Z for Y in 2D projection
return QVector2D(x, y);
}
QVector3D PointCloudViewer::screenToWorld(const QVector2D& screenPos, float y) const
{
float x = (2.0f * screenPos.x() / width()) - 1.0f;
float z = 1.0f - (2.0f * screenPos.y() / height());
QVector4D clipPos(x, z, 0.0f, 1.0f);
QVector4D worldPos = (m_projection * m_view).inverted() * clipPos;
return QVector3D(worldPos.x() / worldPos.w(), y, worldPos.z() / worldPos.w());
}
MouseManager.h
cpp
#pragma once
#include<glm/glm.hpp>
#include<opencv2/opencv.hpp>
// 注意ViewPort的坐标系原点是左下角点
struct ViewPort
{
int w;
int h;
int x0;
int y0;
ViewPort() { w = h = x0 = y0 = 0; }
ViewPort(const int& x0_, const int& y0_, const int& w_, const int& h_)
{
w = w_;
h = h_;
x0 = x0_;
y0 = y0_;
}
void init(const int& x0_, const int& y0_, const int& w_, const int& h_)
{
w = w_;
h = h_;
x0 = x0_;
y0 = y0_;
}
glm::ivec2 size()
{
return glm::ivec2(w, h);
}
glm::ivec2 bl()
{
return glm::ivec2(x0, y0);
}
glm::ivec2 tl()
{
return glm::ivec2(x0, y0 + h);
}
glm::ivec2 tr()
{
return glm::ivec2(x0 + w, y0 + h);
}
glm::ivec2 br()
{
return glm::ivec2(x0 + w, y0);
}
int midX()
{
return x0 + w / 2;
}
int midY()
{
return y0 + h / 2;
}
bool contain(int x, int y)
{
if (x < x0 || x >= x0 + w || y < y0 || y >= y0 + h)
return false;
return true;
}
glm::vec2 NDC(const glm::vec2& inputInCurView)
{
glm::vec2 result;
result.x = (float)inputInCurView.x / w * 2.0f - 1.0f;
result.y = (float)inputInCurView.y / h * 2.0f - 1.0f;
return result;
}
// 输入x,y为当前窗口相对坐标,不是全局窗口的坐标
// 归一化到[-1,1]
glm::vec2 NDC(float xInCurView, float yInCurView)
{
glm::vec2 result;
result.x = xInCurView / w * 2.0f - 1.0f;
result.y = yInCurView / h * 2.0f - 1.0f;
return result;
}
glm::vec2 relativeCoord(int x, int y)
{
return glm::vec2(x - x0, y - y0);
}
// 在整个窗口中的坐标
// p是视口的相对坐标
glm::vec2 coordInWholeView(glm::vec2 p)
{
return glm::vec2(p.x + x0, p.y + y0);
}
// 给出归一化坐标X 返回绝对值坐标X
float absoluteCoordX(float xc)
{
return ((xc + 1.0f) / 2.0f * w + x0);
}
glm::ivec2 absoluteCoord(glm::vec2 normPos)
{
return glm::ivec2(
x0 + normPos.x * w,
y0 + normPos.y * h
);
}
// 输入-1,1 返回视口内坐标 以视口的左下角点为原点
glm::vec2 viewCoord(glm::vec2 p)
{
return glm::vec2((p.x + 1.0) / 2 * w, (p.y + 1.0) / 2 * h);
}
float absoluteCoordY(float yc)
{
return ((yc + 1.0f) / 2.0f * h + y0);
}
ViewPort& operator=(const ViewPort& v)
{
if (this == &v) return *this;
this->x0 = v.x0;
this->y0 = v.y0;
this->w = v.w;
this->h = v.h;
return *this;
}
float ratio()
{
return (static_cast<float>(w)) / (static_cast<float>(h));
}
void use()
{
glViewport(x0, y0, w, h);
}
};
class MouseManager
{
public:
// 这里的x,y是viewport内坐标系的坐标值,而不是渲染窗口的坐标值
struct MouseButton {
bool IsPressed = false;
int x;
int y;
};
void setViewPort(ViewPort* viewport)
{
_viewport = viewport;
}
bool isInArea(int x, int y)
{
if (_viewport == nullptr)
return false;
return _viewport->contain(x, _scr_height - y);
}
bool isInArea()
{
if (_viewport == nullptr)
return false;
return _viewport->contain(lastX + _viewport->x0, _viewport->h - lastY + _viewport->y0);
}
void releaseLeftBtn()
{
leftBtn.IsPressed = false;
}
void recordOneClick(int x, int y)
{
x -= (_viewport->tl().x);
y -= (_scr_height - _viewport->tl().y);
clickStamp1 = cv::getTickCount();
leftBtn.x = x;
leftBtn.y = y;
lastX = x;
lastY = y;
}
bool isDoubleClick(double thresh = 200.0)
{
// 判断是否双击
if (clickStamp2 != -1)
{
double duration = (clickStamp1 - clickStamp2) / cv::getTickFrequency() * 1000.0;
printf("duration = %lf\n", duration);
if (duration < thresh)
{
return true;
}
}
return false;
}
void finishClick()
{
if (clickStamp1 != -1)
{
clickStamp2 = clickStamp1;
}
leftBtn.IsPressed = true;
}
// 返回左键单击点的纹理坐标,ViewPort的左下角点为坐标原点
glm::ivec2 getLeftBtnTextureCoord()
{
return glm::ivec2(leftBtn.x,
_viewport->h - leftBtn.y - 1);
}
glm::vec2 updateMousePos(double x, double y)
{
x -= (_viewport->tl().x);
y -= (_scr_height - _viewport->tl().y);
if (firstMouse) // this bool variable is initially set to true
{
lastX = x;
lastY = y;
firstMouse = false;
}
float xoffset = x - lastX;
float yoffset = lastY - y; // reversed since y-coordinates range from bottom to top
lastX = x;
lastY = y;
leftBtn.x = x;
leftBtn.y = y;
//m_leftMouseButton.x = xpos;
//m_leftMouseButton.y = ypos;
//patternCam.setMousePos(glm::vec2(lastX, lastY));
return glm::vec2(xoffset, yoffset);
}
// lastX lastY以_viewport的左上角点为坐标系原点
glm::dvec2 lastPos()
{
return glm::dvec2(lastX, lastY);
}
void setScreenSize(int scr_width, int scr_height)
{
_scr_width = scr_width;
_scr_height = scr_height;
}
MouseButton leftBtn, rightBtn;
private:
bool firstMouse = true;
ViewPort* _viewport = nullptr;
int _scr_width, _scr_height;
// lastX lastY以_viewport的左上角点为坐标系原点
double lastX = 0;
double lastY = 0;
// 用于判断是否是双击
int clickStamp1 = -1, clickStamp2 = -1;
};