QOpenGLWidget自定义控件— 2D点云显示(支持平移、放缩、绘制网格)

自定义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;
};
相关推荐
hellokandy1 小时前
QT Windows 资源管理器的排序规则
windows·qt·sort·qcollator
蜡笔小欣丫13 小时前
USB导出功能(QT)
开发语言·qt
真的想上岸啊14 小时前
学习C++、QT---27(QT中实现记事本项目实现行列显示、优化保存文件的功能的讲解)
c++·qt·学习
feiyangqingyun21 小时前
Qt视音频推流/监控推流/自动重连推流/推流同时保存录像文件到本地/网页打开webrtc预览
qt·webrtc·qt推流·qt保存录像文件
赤鸢QAQ1 天前
Qt小组件 - 6 异步运行函数
开发语言·python·qt·pyqt
Mr_Xuhhh1 天前
窗口(6)-QMessageBox
c语言·开发语言·数据库·c++·qt
丁劲犇2 天前
Qt Graphs 模块拟取代 charts 和 data visualization还有很长的路要走
c++·qt·qml·visualization·charts·graphs
寄思~2 天前
PyQt5—Qt QDialog 学习笔记
开发语言·笔记·python·qt·学习
天堂陌客2 天前
RK3562 OTA 方法
qt·ota·rk3562