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;
};
相关推荐
用户805533698032 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner2 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz7 天前
QML Hello World 入门示例
qt
xcyxiner10 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner11 天前
DicomViewer (添加模型类)3
qt
xcyxiner12 天前
DicomViewer (目录调整) 2
qt
xcyxiner12 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能14 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G14 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt