906 lines
35 KiB
C++
906 lines
35 KiB
C++
#include "ViewportWidget.h"
|
|
#include "Camera.h"
|
|
#include "ViewCube.h"
|
|
#include "SketchGrid.h"
|
|
#include "Document.h"
|
|
#include "FeatureBrowser.h"
|
|
#include "SketchFeature.h"
|
|
#include "SketchLine.h"
|
|
#include "SketchObject.h"
|
|
#include "ApplicationController.h"
|
|
#include <QMouseEvent>
|
|
#include <QKeyEvent>
|
|
#include <QWheelEvent>
|
|
#include <QApplication>
|
|
#include <QPainter>
|
|
#include <QPixmap>
|
|
#include <QImage>
|
|
#include <QSvgRenderer>
|
|
#include <QWheelEvent>
|
|
#include <QApplication>
|
|
#include <QPropertyAnimation>
|
|
#include <QParallelAnimationGroup>
|
|
#include <cmath>
|
|
#include <QtMath>
|
|
#include <QOpenGLShaderProgram>
|
|
#include <QVector>
|
|
#include <map>
|
|
|
|
struct PntComparator {
|
|
bool operator()(const gp_Pnt& a, const gp_Pnt& b) const {
|
|
// A tolerance is needed for floating point comparisons
|
|
double tol = 1e-9;
|
|
if (std::abs(a.X() - b.X()) > tol) return a.X() < b.X();
|
|
if (std::abs(a.Y() - b.Y()) > tol) return a.Y() < b.Y();
|
|
if (std::abs(a.Z() - b.Z()) > tol) return a.Z() < b.Z();
|
|
return false; // They are considered equal
|
|
}
|
|
};
|
|
|
|
ViewportWidget::ViewportWidget(QWidget *parent)
|
|
: QOpenGLWidget(parent)
|
|
{
|
|
m_camera = new Camera(this);
|
|
connect(m_camera, &Camera::cameraChanged, this, QOverload<>::of(&QWidget::update));
|
|
m_viewCube = new ViewCube();
|
|
m_sketchGrid = new SketchGrid();
|
|
m_featureBrowser = new FeatureBrowser();
|
|
setMouseTracking(true);
|
|
setFocusPolicy(Qt::StrongFocus);
|
|
|
|
m_currentPlane = SketchPlane::XY;
|
|
|
|
m_toolIcons.insert(static_cast<int>(ApplicationController::ToolType::Line), new QSvgRenderer(QString(":/icons/line.svg"), this));
|
|
m_toolIcons.insert(static_cast<int>(ApplicationController::ToolType::Rectangle), new QSvgRenderer(QString(":/icons/rectangle.svg"), this));
|
|
m_toolIcons.insert(static_cast<int>(ApplicationController::ToolType::Circle), new QSvgRenderer(QString(":/icons/circle.svg"), this));
|
|
|
|
m_cursorRenderer = new QSvgRenderer(QString(":/icons/cursor.svg"), this);
|
|
}
|
|
|
|
ViewportWidget::~ViewportWidget()
|
|
{
|
|
makeCurrent();
|
|
delete m_shaderProgram;
|
|
delete m_viewCube;
|
|
delete m_sketchGrid;
|
|
m_vbo.destroy();
|
|
m_vao.destroy();
|
|
doneCurrent();
|
|
|
|
delete m_featureBrowser;
|
|
}
|
|
|
|
void ViewportWidget::setDocument(Document* document)
|
|
{
|
|
m_document = document;
|
|
m_featureBrowser->setDocument(document);
|
|
}
|
|
|
|
void ViewportWidget::initializeGL()
|
|
{
|
|
initializeOpenGLFunctions();
|
|
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
|
|
|
|
initShaders();
|
|
|
|
m_vao.create();
|
|
QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);
|
|
m_vbo.create();
|
|
m_vbo.bind();
|
|
m_vbo.allocate(nullptr, 0); // Allocate when drawing
|
|
|
|
// Position attribute
|
|
m_shaderProgram->enableAttributeArray(0);
|
|
m_shaderProgram->setAttributeBuffer(0, GL_FLOAT, 0, 3, 0);
|
|
|
|
m_vbo.release();
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
m_viewCube->initializeGL();
|
|
m_sketchGrid->initializeGL();
|
|
}
|
|
|
|
void ViewportWidget::paintGL()
|
|
{
|
|
// Main scene rendering
|
|
glViewport(0, 0, width(), height());
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
if (!m_shaderProgram || !m_shaderProgram->isLinked()) {
|
|
return;
|
|
}
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
glEnable(GL_CULL_FACE);
|
|
|
|
QMatrix4x4 model = m_camera->modelViewMatrix();
|
|
m_shaderProgram->bind();
|
|
m_shaderProgram->setUniformValue(m_projMatrixLoc, projection);
|
|
m_shaderProgram->setUniformValue(m_mvMatrixLoc, model);
|
|
|
|
QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);
|
|
|
|
// Sketch grid rendering
|
|
if (m_isSelectingPlane) {
|
|
if (m_highlightedPlane != SketchPlane::NONE) {
|
|
m_sketchGrid->paintGL(static_cast<SketchGrid::SketchPlane>(m_highlightedPlane), m_shaderProgram, m_colorLoc);
|
|
}
|
|
}
|
|
if (m_currentPlane != SketchPlane::NONE) {
|
|
m_sketchGrid->paintGL(static_cast<SketchGrid::SketchPlane>(m_currentPlane), m_shaderProgram, m_colorLoc);
|
|
}
|
|
|
|
if (m_isSelectingPlane) {
|
|
drawSelectionPlanes();
|
|
}
|
|
|
|
if (m_document) {
|
|
for (Feature* feature : m_document->features()) {
|
|
if (auto sketch = dynamic_cast<SketchFeature*>(feature)) {
|
|
drawSketch(sketch);
|
|
}
|
|
}
|
|
}
|
|
|
|
QVector<GLfloat> vertices;
|
|
if (m_isSnappingOrigin) {
|
|
const float rectSize = 0.0075f * -m_camera->zoom();
|
|
if (m_currentPlane == SketchPlane::XY) {
|
|
vertices << -rectSize << 0 << -rectSize << rectSize << 0 << -rectSize;
|
|
vertices << rectSize << 0 << -rectSize << rectSize << 0 << rectSize;
|
|
vertices << rectSize << 0 << rectSize << -rectSize << 0 << rectSize;
|
|
vertices << -rectSize << 0 << rectSize << -rectSize << 0 << -rectSize;
|
|
} else if (m_currentPlane == SketchPlane::XZ) {
|
|
vertices << -rectSize << -rectSize << 0 << rectSize << -rectSize << 0;
|
|
vertices << rectSize << -rectSize << 0 << rectSize << rectSize << 0;
|
|
vertices << rectSize << rectSize << 0 << -rectSize << rectSize << 0;
|
|
vertices << -rectSize << rectSize << 0 << -rectSize << -rectSize << 0;
|
|
} else if (m_currentPlane == SketchPlane::YZ) {
|
|
vertices << 0 << -rectSize << -rectSize << 0 << rectSize << -rectSize;
|
|
vertices << 0 << rectSize << -rectSize << 0 << rectSize << rectSize;
|
|
vertices << 0 << rectSize << rectSize << 0 << -rectSize << rectSize;
|
|
vertices << 0 << -rectSize << rectSize << 0 << -rectSize << -rectSize;
|
|
}
|
|
m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 0.0f, 0.5f));
|
|
m_vbo.bind();
|
|
m_vbo.allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glDrawArrays(GL_LINES, 0, vertices.size() / 3);
|
|
glDisable(GL_BLEND);
|
|
} else if (m_isSnappingVertex) {
|
|
const float rectSize = 0.0075f * -m_camera->zoom();
|
|
const auto& v = m_snapVertex;
|
|
if (m_currentPlane == SketchPlane::XY) {
|
|
vertices << v.X() - rectSize << v.Y() << v.Z() - rectSize << v.X() + rectSize << v.Y() << v.Z() - rectSize;
|
|
vertices << v.X() + rectSize << v.Y() << v.Z() - rectSize << v.X() + rectSize << v.Y() << v.Z() + rectSize;
|
|
vertices << v.X() + rectSize << v.Y() << v.Z() + rectSize << v.X() - rectSize << v.Y() << v.Z() + rectSize;
|
|
vertices << v.X() - rectSize << v.Y() << v.Z() + rectSize << v.X() - rectSize << v.Y() << v.Z() - rectSize;
|
|
} else if (m_currentPlane == SketchPlane::XZ) {
|
|
vertices << v.X() - rectSize << v.Y() - rectSize << v.Z() << v.X() + rectSize << v.Y() - rectSize << v.Z();
|
|
vertices << v.X() + rectSize << v.Y() - rectSize << v.Z() << v.X() + rectSize << v.Y() + rectSize << v.Z();
|
|
vertices << v.X() + rectSize << v.Y() + rectSize << v.Z() << v.X() - rectSize << v.Y() + rectSize << v.Z();
|
|
vertices << v.X() - rectSize << v.Y() + rectSize << v.Z() << v.X() - rectSize << v.Y() - rectSize << v.Z();
|
|
} else if (m_currentPlane == SketchPlane::YZ) {
|
|
vertices << v.X() << v.Y() - rectSize << v.Z() - rectSize << v.X() << v.Y() + rectSize << v.Z() - rectSize;
|
|
vertices << v.X() << v.Y() + rectSize << v.Z() - rectSize << v.X() << v.Y() + rectSize << v.Z() + rectSize;
|
|
vertices << v.X() << v.Y() + rectSize << v.Z() + rectSize << v.X() << v.Y() - rectSize << v.Z() + rectSize;
|
|
vertices << v.X() << v.Y() - rectSize << v.Z() + rectSize << v.X() << v.Y() - rectSize << v.Z() - rectSize;
|
|
}
|
|
m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 0.0f, 0.5f));
|
|
m_vbo.bind();
|
|
m_vbo.allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glDrawArrays(GL_LINES, 0, vertices.size() / 3);
|
|
glDisable(GL_BLEND);
|
|
}
|
|
|
|
if (m_isDefiningLine && m_activeTool == static_cast<int>(ApplicationController::ToolType::Line)) {
|
|
vertices.clear();
|
|
QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane);
|
|
if (m_isSnappingOrigin) {
|
|
worldPos.setX(0); worldPos.setY(0); worldPos.setZ(0);
|
|
} else if (m_isSnappingVertex) {
|
|
worldPos.setX(m_snapVertex.X()); worldPos.setY(m_snapVertex.Y()); worldPos.setZ(m_snapVertex.Z());
|
|
} else if (m_isSnappingHorizontal) {
|
|
if (m_currentPlane == SketchPlane::XY) worldPos.setZ(m_firstLinePoint.Z());
|
|
else if (m_currentPlane == SketchPlane::XZ) worldPos.setY(m_firstLinePoint.Y());
|
|
else if (m_currentPlane == SketchPlane::YZ) worldPos.setZ(m_firstLinePoint.Z());
|
|
} else if (m_isSnappingVertical) {
|
|
if (m_currentPlane == SketchPlane::XY) worldPos.setX(m_firstLinePoint.X());
|
|
else if (m_currentPlane == SketchPlane::XZ) worldPos.setX(m_firstLinePoint.X());
|
|
else if (m_currentPlane == SketchPlane::YZ) worldPos.setY(m_firstLinePoint.Y());
|
|
}
|
|
vertices << m_firstLinePoint.X() << m_firstLinePoint.Y() << m_firstLinePoint.Z();
|
|
vertices << worldPos.x() << worldPos.y() << worldPos.z();
|
|
|
|
m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 0.0f, 1.0f));
|
|
m_vbo.bind();
|
|
m_vbo.allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
|
|
glDrawArrays(GL_LINES, 0, 2);
|
|
|
|
if (m_isSnappingHorizontal || m_isSnappingVertical) {
|
|
vertices.clear();
|
|
QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
|
|
QVector3D midPoint = (startPos + worldPos) / 2.0;
|
|
const float indicatorSize = 0.02f * -m_camera->zoom();
|
|
const float indicatorOffset = 0.02f * -m_camera->zoom();
|
|
if (m_isSnappingHorizontal) {
|
|
if (m_currentPlane == SketchPlane::XY) {
|
|
vertices << midPoint.x() - indicatorSize << midPoint.y() << midPoint.z() + indicatorOffset;
|
|
vertices << midPoint.x() + indicatorSize << midPoint.y() << midPoint.z() + indicatorOffset;
|
|
} else if (m_currentPlane == SketchPlane::XZ) {
|
|
vertices << midPoint.x() - indicatorSize << midPoint.y() + indicatorOffset << midPoint.z();
|
|
vertices << midPoint.x() + indicatorSize << midPoint.y() + indicatorOffset << midPoint.z();
|
|
} else if (m_currentPlane == SketchPlane::YZ) {
|
|
vertices << midPoint.x() << midPoint.y() - indicatorSize << midPoint.z() + indicatorOffset;
|
|
vertices << midPoint.x() << midPoint.y() + indicatorSize << midPoint.z() + indicatorOffset;
|
|
}
|
|
} else { // m_isSnappingVertical
|
|
if (m_currentPlane == SketchPlane::XY) {
|
|
vertices << midPoint.x() + indicatorOffset << midPoint.y() << midPoint.z() - indicatorSize;
|
|
vertices << midPoint.x() + indicatorOffset << midPoint.y() << midPoint.z() + indicatorSize;
|
|
} else if (m_currentPlane == SketchPlane::XZ) {
|
|
vertices << midPoint.x() + indicatorOffset << midPoint.y() - indicatorSize << midPoint.z();
|
|
vertices << midPoint.x() + indicatorOffset << midPoint.y() + indicatorSize << midPoint.z();
|
|
} else if (m_currentPlane == SketchPlane::YZ) {
|
|
vertices << midPoint.x() << midPoint.y() + indicatorOffset << midPoint.z() - indicatorSize;
|
|
vertices << midPoint.x() << midPoint.y() + indicatorOffset << midPoint.z() + indicatorSize;
|
|
}
|
|
}
|
|
m_vbo.bind();
|
|
m_vbo.allocate(vertices.constData(), vertices.size() * sizeof(GLfloat));
|
|
glDrawArrays(GL_LINES, 0, 2);
|
|
}
|
|
}
|
|
|
|
m_shaderProgram->release();
|
|
|
|
// View cube rendering
|
|
QMatrix4x4 viewCubeModel;
|
|
viewCubeModel.rotate(m_camera->xRotation() / 16.0f, 1, 0, 0);
|
|
viewCubeModel.rotate(m_camera->yRotation() / 16.0f, 0, 1, 0);
|
|
m_viewCube->paintGL(m_shaderProgram, m_colorLoc, viewCubeModel, width(), height());
|
|
|
|
glViewport(0, 0, width(), height());
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_CULL_FACE);
|
|
|
|
QPainter painter(this);
|
|
if (m_currentPlane != SketchPlane::NONE) {
|
|
drawAxisLabels(painter, model, projection);
|
|
}
|
|
m_featureBrowser->paint(painter, width(), height());
|
|
|
|
painter.end();
|
|
}
|
|
|
|
void ViewportWidget::resizeGL(int w, int h)
|
|
{
|
|
projection.setToIdentity();
|
|
projection.perspective(45.0f, w / float(h), 0.01f, 100.0f);
|
|
}
|
|
|
|
void ViewportWidget::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
if (event->button() == Qt::LeftButton) {
|
|
if (m_isSelectingPlane) {
|
|
if (m_highlightedPlane != SketchPlane::NONE) {
|
|
emit planeSelected(m_highlightedPlane);
|
|
m_isSelectingPlane = false;
|
|
m_highlightedPlane = SketchPlane::NONE;
|
|
update();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (m_currentPlane != SketchPlane::NONE && m_activeTool == static_cast<int>(ApplicationController::ToolType::Line)) {
|
|
gp_Pnt p;
|
|
if (m_isSnappingOrigin) {
|
|
p.SetCoord(0, 0, 0);
|
|
} else if (m_isSnappingVertex) {
|
|
p = m_snapVertex;
|
|
} else {
|
|
QVector3D worldPos = unproject(event->pos(), m_currentPlane);
|
|
if (m_isSnappingHorizontal) {
|
|
if (m_currentPlane == SketchPlane::XY) worldPos.setZ(m_firstLinePoint.Z());
|
|
else if (m_currentPlane == SketchPlane::XZ) worldPos.setY(m_firstLinePoint.Y());
|
|
else if (m_currentPlane == SketchPlane::YZ) worldPos.setZ(m_firstLinePoint.Z());
|
|
} else if (m_isSnappingVertical) {
|
|
if (m_currentPlane == SketchPlane::XY) worldPos.setX(m_firstLinePoint.X());
|
|
else if (m_currentPlane == SketchPlane::XZ) worldPos.setX(m_firstLinePoint.X());
|
|
else if (m_currentPlane == SketchPlane::YZ) worldPos.setY(m_firstLinePoint.Y());
|
|
}
|
|
p.SetCoord(worldPos.x(), worldPos.y(), worldPos.z());
|
|
}
|
|
|
|
if (!m_isDefiningLine) {
|
|
m_firstLinePoint = p;
|
|
m_isDefiningLine = true;
|
|
} else {
|
|
emit lineAdded(m_firstLinePoint, p);
|
|
m_firstLinePoint = p;
|
|
}
|
|
update();
|
|
}
|
|
} else {
|
|
lastPos = event->pos();
|
|
}
|
|
}
|
|
|
|
void ViewportWidget::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
m_currentMousePos = event->pos();
|
|
|
|
if (m_isSelectingPlane) {
|
|
SketchPlane newHighlight = checkPlaneSelection(m_currentMousePos);
|
|
if (newHighlight != m_highlightedPlane) {
|
|
m_highlightedPlane = newHighlight;
|
|
update();
|
|
}
|
|
}
|
|
|
|
bool shouldSnap = false;
|
|
if (m_currentPlane != SketchPlane::NONE && m_activeTool != static_cast<int>(ApplicationController::ToolType::None)) {
|
|
QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane);
|
|
const float snapRectHalfSize = 0.0075f * -m_camera->zoom();
|
|
|
|
switch (m_currentPlane) {
|
|
case SketchPlane::XY:
|
|
shouldSnap = qAbs(worldPos.x()) < snapRectHalfSize && qAbs(worldPos.z()) < snapRectHalfSize;
|
|
break;
|
|
case SketchPlane::XZ:
|
|
shouldSnap = qAbs(worldPos.x()) < snapRectHalfSize && qAbs(worldPos.y()) < snapRectHalfSize;
|
|
break;
|
|
case SketchPlane::YZ:
|
|
shouldSnap = qAbs(worldPos.y()) < snapRectHalfSize && qAbs(worldPos.z()) < snapRectHalfSize;
|
|
break;
|
|
case SketchPlane::NONE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (shouldSnap != m_isSnappingOrigin) {
|
|
m_isSnappingOrigin = shouldSnap;
|
|
if (m_isSnappingOrigin) {
|
|
m_isSnappingVertex = false;
|
|
}
|
|
update();
|
|
}
|
|
|
|
bool oldIsSnappingVertex = m_isSnappingVertex;
|
|
m_isSnappingVertex = false;
|
|
if (!m_isSnappingOrigin && m_document && m_currentPlane != SketchPlane::NONE && m_activeTool != static_cast<int>(ApplicationController::ToolType::None)) {
|
|
QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane);
|
|
const float snapRectHalfSize = 0.0075f * -m_camera->zoom();
|
|
|
|
for (Feature* feature : m_document->features()) {
|
|
if (auto sketch = dynamic_cast<SketchFeature*>(feature)) {
|
|
for (const auto& obj : sketch->objects()) {
|
|
if (obj->type() == SketchObject::ObjectType::Line) {
|
|
auto line = static_cast<const SketchLine*>(obj);
|
|
const gp_Pnt vertices[] = {line->startPoint(), line->endPoint()};
|
|
for (const auto& vertex : vertices) {
|
|
bool isClose = false;
|
|
switch (m_currentPlane) {
|
|
case SketchPlane::XY:
|
|
isClose = qAbs(worldPos.x() - vertex.X()) < snapRectHalfSize && qAbs(worldPos.z() - vertex.Z()) < snapRectHalfSize;
|
|
break;
|
|
case SketchPlane::XZ:
|
|
isClose = qAbs(worldPos.x() - vertex.X()) < snapRectHalfSize && qAbs(worldPos.y() - vertex.Y()) < snapRectHalfSize;
|
|
break;
|
|
case SketchPlane::YZ:
|
|
isClose = qAbs(worldPos.y() - vertex.Y()) < snapRectHalfSize && qAbs(worldPos.z() - vertex.Z()) < snapRectHalfSize;
|
|
break;
|
|
case SketchPlane::NONE:
|
|
break;
|
|
}
|
|
|
|
if (isClose) {
|
|
m_isSnappingVertex = true;
|
|
m_snapVertex = vertex;
|
|
goto end_snap_check;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
end_snap_check:;
|
|
}
|
|
|
|
if (oldIsSnappingVertex != m_isSnappingVertex) {
|
|
update();
|
|
}
|
|
|
|
bool oldIsSnappingHorizontal = m_isSnappingHorizontal;
|
|
bool oldIsSnappingVertical = m_isSnappingVertical;
|
|
m_isSnappingHorizontal = false;
|
|
m_isSnappingVertical = false;
|
|
|
|
if (m_isDefiningLine && !m_isSnappingOrigin && !m_isSnappingVertex) {
|
|
QVector3D worldPos = unproject(m_currentMousePos, m_currentPlane);
|
|
QVector3D startPos(m_firstLinePoint.X(), m_firstLinePoint.Y(), m_firstLinePoint.Z());
|
|
QVector3D delta = worldPos - startPos;
|
|
|
|
if (delta.length() > 1e-6) {
|
|
const double snapAngleThreshold = qDegreesToRadians(2.0);
|
|
double angle = 0;
|
|
|
|
if (m_currentPlane == SketchPlane::XY) {
|
|
angle = atan2(delta.z(), delta.x());
|
|
} else if (m_currentPlane == SketchPlane::XZ) {
|
|
angle = atan2(delta.y(), delta.x());
|
|
} else if (m_currentPlane == SketchPlane::YZ) {
|
|
angle = atan2(delta.z(), delta.y());
|
|
}
|
|
|
|
if (qAbs(sin(angle)) < sin(snapAngleThreshold)) {
|
|
m_isSnappingHorizontal = true;
|
|
} else if (qAbs(cos(angle)) < sin(snapAngleThreshold)) {
|
|
m_isSnappingVertical = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (oldIsSnappingHorizontal != m_isSnappingHorizontal || oldIsSnappingVertical != m_isSnappingVertical) {
|
|
update();
|
|
}
|
|
|
|
if (event->buttons() & Qt::MiddleButton) {
|
|
m_camera->processMouseMovement(event, lastPos);
|
|
}
|
|
lastPos = event->pos();
|
|
update();
|
|
}
|
|
|
|
void ViewportWidget::wheelEvent(QWheelEvent *event)
|
|
{
|
|
m_camera->processWheel(event);
|
|
}
|
|
|
|
void ViewportWidget::keyPressEvent(QKeyEvent *event)
|
|
{
|
|
if (event->key() == Qt::Key_Escape) {
|
|
if (m_isDefiningLine) {
|
|
m_isDefiningLine = false;
|
|
emit toolDeactivated();
|
|
update();
|
|
return;
|
|
}
|
|
if (m_isSelectingPlane) {
|
|
m_isSelectingPlane = false;
|
|
m_highlightedPlane = SketchPlane::NONE;
|
|
m_currentPlane = SketchPlane::XY;
|
|
update();
|
|
return;
|
|
}
|
|
}
|
|
QOpenGLWidget::keyPressEvent(event);
|
|
}
|
|
|
|
void ViewportWidget::onSketchModeStarted(SketchPlane plane)
|
|
{
|
|
m_currentPlane = plane;
|
|
|
|
m_camera->saveState();
|
|
|
|
float targetXRot = m_camera->xRotation();
|
|
float targetYRot = m_camera->yRotation();
|
|
switch (plane) {
|
|
case SketchPlane::XY: // Top view
|
|
targetXRot = 90 * 16;
|
|
targetYRot = 0;
|
|
break;
|
|
case SketchPlane::XZ: // Front view
|
|
targetXRot = 0;
|
|
targetYRot = 0;
|
|
break;
|
|
case SketchPlane::YZ: // Right view
|
|
targetXRot = 0;
|
|
targetYRot = -90 * 16;
|
|
break;
|
|
case SketchPlane::NONE:
|
|
break;
|
|
}
|
|
|
|
auto* animGroup = new QParallelAnimationGroup(this);
|
|
|
|
auto* xRotAnim = new QPropertyAnimation(m_camera, "xRotation");
|
|
xRotAnim->setDuration(300);
|
|
xRotAnim->setStartValue(m_camera->xRotation());
|
|
xRotAnim->setEndValue(targetXRot);
|
|
xRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(xRotAnim);
|
|
|
|
auto* yRotAnim = new QPropertyAnimation(m_camera, "yRotation");
|
|
yRotAnim->setDuration(300);
|
|
yRotAnim->setStartValue(m_camera->yRotation());
|
|
yRotAnim->setEndValue(targetYRot);
|
|
yRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(yRotAnim);
|
|
|
|
auto* panXAnim = new QPropertyAnimation(m_camera, "panX");
|
|
panXAnim->setDuration(300);
|
|
panXAnim->setStartValue(m_camera->panX());
|
|
panXAnim->setEndValue(0.0f);
|
|
panXAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(panXAnim);
|
|
|
|
auto* panYAnim = new QPropertyAnimation(m_camera, "panY");
|
|
panYAnim->setDuration(300);
|
|
panYAnim->setStartValue(m_camera->panY());
|
|
panYAnim->setEndValue(0.0f);
|
|
panYAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(panYAnim);
|
|
|
|
auto* zoomAnim = new QPropertyAnimation(m_camera, "zoom");
|
|
zoomAnim->setDuration(300);
|
|
zoomAnim->setStartValue(m_camera->zoom());
|
|
zoomAnim->setEndValue(-20.0f);
|
|
zoomAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(zoomAnim);
|
|
|
|
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
|
|
}
|
|
|
|
void ViewportWidget::onPlaneSelectionModeStarted()
|
|
{
|
|
m_isSelectingPlane = true;
|
|
m_highlightedPlane = SketchPlane::NONE;
|
|
m_currentPlane = SketchPlane::NONE;
|
|
update();
|
|
}
|
|
|
|
void ViewportWidget::onSketchModeEnded()
|
|
{
|
|
auto* animGroup = new QParallelAnimationGroup(this);
|
|
|
|
auto* xRotAnim = new QPropertyAnimation(m_camera, "xRotation");
|
|
xRotAnim->setDuration(300);
|
|
xRotAnim->setStartValue(m_camera->xRotation());
|
|
xRotAnim->setEndValue(m_camera->savedXRot());
|
|
xRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(xRotAnim);
|
|
|
|
auto* yRotAnim = new QPropertyAnimation(m_camera, "yRotation");
|
|
yRotAnim->setDuration(300);
|
|
yRotAnim->setStartValue(m_camera->yRotation());
|
|
yRotAnim->setEndValue(m_camera->savedYRot());
|
|
yRotAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(yRotAnim);
|
|
|
|
auto* panXAnim = new QPropertyAnimation(m_camera, "panX");
|
|
panXAnim->setDuration(300);
|
|
panXAnim->setStartValue(m_camera->panX());
|
|
panXAnim->setEndValue(m_camera->savedPanX());
|
|
panXAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(panXAnim);
|
|
|
|
auto* panYAnim = new QPropertyAnimation(m_camera, "panY");
|
|
panYAnim->setDuration(300);
|
|
panYAnim->setStartValue(m_camera->panY());
|
|
panYAnim->setEndValue(m_camera->savedPanY());
|
|
panYAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(panYAnim);
|
|
|
|
auto* zoomAnim = new QPropertyAnimation(m_camera, "zoom");
|
|
zoomAnim->setDuration(300);
|
|
zoomAnim->setStartValue(m_camera->zoom());
|
|
zoomAnim->setEndValue(m_camera->savedZoom());
|
|
zoomAnim->setEasingCurve(QEasingCurve::InOutQuad);
|
|
animGroup->addAnimation(zoomAnim);
|
|
|
|
connect(animGroup, &QParallelAnimationGroup::finished, this, [this]() {
|
|
// Return to showing the base XY grid when not in a sketch
|
|
m_currentPlane = SketchPlane::XY;
|
|
update();
|
|
});
|
|
|
|
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
|
|
}
|
|
|
|
QVector3D ViewportWidget::project(const QVector3D& worldCoord, const QMatrix4x4& modelView, const QMatrix4x4& projection, const QRect& viewport)
|
|
{
|
|
QVector4D clipCoord = projection * modelView * QVector4D(worldCoord, 1.0);
|
|
if (qFuzzyCompare(clipCoord.w(), 0.0f)) {
|
|
return QVector3D(-1, -1, -1);
|
|
}
|
|
|
|
QVector3D ndc(clipCoord.x() / clipCoord.w(), clipCoord.y() / clipCoord.w(), clipCoord.z() / clipCoord.w());
|
|
|
|
float winX = viewport.x() + viewport.width() * (ndc.x() + 1.0) / 2.0;
|
|
float winY = viewport.y() + viewport.height() * (1.0 - (ndc.y() + 1.0) / 2.0); // Y is inverted
|
|
|
|
return QVector3D(winX, winY, (ndc.z() + 1.0) / 2.0);
|
|
}
|
|
|
|
void ViewportWidget::drawAxisLabels(QPainter& painter, const QMatrix4x4& modelView, const QMatrix4x4& projection)
|
|
{
|
|
painter.setPen(Qt::white);
|
|
painter.setFont(QFont("Arial", 10));
|
|
|
|
const int range = 50;
|
|
const int step = 5;
|
|
|
|
auto drawLabelsForAxis = [&](int axis_idx) {
|
|
for (int i = -range; i <= range; i += step) {
|
|
if (i == 0) continue;
|
|
QVector3D worldCoord;
|
|
worldCoord[axis_idx] = i;
|
|
|
|
QVector3D screenPos = project(worldCoord, modelView, projection, rect());
|
|
if (screenPos.z() < 1.0f) { // Not clipped
|
|
painter.drawText(screenPos.toPoint(), QString::number(i));
|
|
}
|
|
}
|
|
};
|
|
|
|
if (m_currentPlane == SketchPlane::XY) {
|
|
drawLabelsForAxis(0); // X
|
|
drawLabelsForAxis(2); // Y
|
|
} else if (m_currentPlane == SketchPlane::XZ) {
|
|
drawLabelsForAxis(0); // X
|
|
drawLabelsForAxis(1); // Z
|
|
} else if (m_currentPlane == SketchPlane::YZ) {
|
|
drawLabelsForAxis(1); // Y
|
|
drawLabelsForAxis(2); // Z
|
|
}
|
|
}
|
|
|
|
void ViewportWidget::onActiveToolChanged(int tool)
|
|
{
|
|
m_activeTool = tool;
|
|
m_isDefiningLine = false;
|
|
|
|
if (tool == static_cast<int>(ApplicationController::ToolType::None)) {
|
|
unsetCursor();
|
|
} else {
|
|
if (m_toolIcons.contains(tool)) {
|
|
QSvgRenderer* renderer = m_toolIcons.value(tool);
|
|
if (renderer && renderer->isValid()) {
|
|
const QSize cursorSize(48, 48);
|
|
QPixmap cursorPixmap(cursorSize);
|
|
cursorPixmap.fill(Qt::transparent);
|
|
QPainter painter(&cursorPixmap);
|
|
painter.setRenderHint(QPainter::Antialiasing);
|
|
|
|
// Render arrow cursor from SVG
|
|
if (m_cursorRenderer && m_cursorRenderer->isValid()) {
|
|
m_cursorRenderer->render(&painter, QRectF(0, 0, 14, 22));
|
|
}
|
|
|
|
// Render tool icon
|
|
const QSize iconSize(32, 32);
|
|
QPixmap iconPixmap(iconSize);
|
|
iconPixmap.fill(Qt::transparent);
|
|
QPainter iconPainter(&iconPixmap);
|
|
renderer->render(&iconPainter);
|
|
iconPainter.end();
|
|
|
|
// Invert and draw icon onto cursor pixmap
|
|
QImage iconImage = iconPixmap.toImage();
|
|
iconImage.invertPixels(QImage::InvertRgb);
|
|
painter.drawImage(QRect(12, 12, iconSize.width(), iconSize.height()), iconImage);
|
|
painter.end();
|
|
|
|
setCursor(QCursor(cursorPixmap, 0, 0));
|
|
}
|
|
} else {
|
|
unsetCursor();
|
|
}
|
|
}
|
|
}
|
|
|
|
QVector3D ViewportWidget::unproject(const QPoint& screenPos, SketchPlane plane)
|
|
{
|
|
QMatrix4x4 model = m_camera->modelViewMatrix();
|
|
|
|
bool invertible;
|
|
QMatrix4x4 inv = (projection * model).inverted(&invertible);
|
|
if (!invertible) {
|
|
return QVector3D();
|
|
}
|
|
|
|
float ndcX = (2.0f * screenPos.x()) / width() - 1.0f;
|
|
float ndcY = 1.0f - (2.0f * screenPos.y()) / height();
|
|
|
|
QVector4D nearPoint_ndc(ndcX, ndcY, -1.0f, 1.0f);
|
|
QVector4D farPoint_ndc(ndcX, ndcY, 1.0f, 1.0f);
|
|
|
|
QVector4D nearPoint_world = inv * nearPoint_ndc;
|
|
QVector4D farPoint_world = inv * farPoint_ndc;
|
|
|
|
if (qFuzzyCompare(nearPoint_world.w(), 0.0f) || qFuzzyCompare(farPoint_world.w(), 0.0f)) {
|
|
return QVector3D();
|
|
}
|
|
|
|
nearPoint_world /= nearPoint_world.w();
|
|
farPoint_world /= farPoint_world.w();
|
|
|
|
QVector3D rayOrigin(nearPoint_world);
|
|
QVector3D rayDir = (QVector3D(farPoint_world) - rayOrigin).normalized();
|
|
|
|
QVector3D planeNormal;
|
|
switch (plane) {
|
|
case SketchPlane::XY: planeNormal = QVector3D(0, 1, 0); break;
|
|
case SketchPlane::XZ: planeNormal = QVector3D(0, 0, 1); break;
|
|
case SketchPlane::YZ: planeNormal = QVector3D(1, 0, 0); break;
|
|
case SketchPlane::NONE: return QVector3D();
|
|
}
|
|
|
|
float denom = QVector3D::dotProduct(planeNormal, rayDir);
|
|
if (qAbs(denom) > 1e-6) {
|
|
QVector3D p0(0,0,0);
|
|
float t = QVector3D::dotProduct(p0 - rayOrigin, planeNormal) / denom;
|
|
return rayOrigin + t * rayDir;
|
|
}
|
|
|
|
return QVector3D();
|
|
}
|
|
|
|
void ViewportWidget::drawSketch(const SketchFeature* sketch)
|
|
{
|
|
glDisable(GL_DEPTH_TEST);
|
|
glLineWidth(2.0f);
|
|
|
|
QVector<GLfloat> lineVertices;
|
|
std::map<gp_Pnt, int, PntComparator> vertexCounts;
|
|
|
|
for (const auto& obj : sketch->objects()) {
|
|
if (obj->type() == SketchObject::ObjectType::Line) {
|
|
auto line = static_cast<const SketchLine*>(obj);
|
|
const auto& start = line->startPoint();
|
|
const auto& end = line->endPoint();
|
|
lineVertices << start.X() << start.Y() << start.Z();
|
|
lineVertices << end.X() << end.Y() << end.Z();
|
|
|
|
vertexCounts[start]++;
|
|
vertexCounts[end]++;
|
|
}
|
|
}
|
|
|
|
QVector<GLfloat> circleVertices;
|
|
const float radius = 0.005f * -m_camera->zoom();
|
|
const int numSegments = 16;
|
|
|
|
QMatrix4x4 invModelView = m_camera->modelViewMatrix().inverted();
|
|
QVector3D rightVec = invModelView.column(0).toVector3D();
|
|
QVector3D upVec = invModelView.column(1).toVector3D();
|
|
|
|
for (const auto& pair : vertexCounts) {
|
|
if (pair.second == 1) {
|
|
const gp_Pnt& centerPnt = pair.first;
|
|
QVector3D center(centerPnt.X(), centerPnt.Y(), centerPnt.Z());
|
|
|
|
for (int i = 0; i < numSegments; ++i) {
|
|
float angle1 = (2.0f * M_PI * float(i)) / float(numSegments);
|
|
float angle2 = (2.0f * M_PI * float(i + 1)) / float(numSegments);
|
|
|
|
QVector3D p1 = center + radius * (cos(angle1) * rightVec + sin(angle1) * upVec);
|
|
QVector3D p2 = center + radius * (cos(angle2) * rightVec + sin(angle2) * upVec);
|
|
|
|
circleVertices << p1.x() << p1.y() << p1.z();
|
|
circleVertices << p2.x() << p2.y() << p2.z();
|
|
}
|
|
}
|
|
}
|
|
|
|
m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 1.0f, 1.0f));
|
|
m_vbo.bind();
|
|
|
|
if (!lineVertices.isEmpty()) {
|
|
m_vbo.allocate(lineVertices.constData(), lineVertices.size() * sizeof(GLfloat));
|
|
glDrawArrays(GL_LINES, 0, lineVertices.size() / 3);
|
|
}
|
|
if (!circleVertices.isEmpty()) {
|
|
m_vbo.allocate(circleVertices.constData(), circleVertices.size() * sizeof(GLfloat));
|
|
glDrawArrays(GL_LINES, 0, circleVertices.size() / 3);
|
|
}
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
}
|
|
|
|
void ViewportWidget::initShaders()
|
|
{
|
|
m_shaderProgram = new QOpenGLShaderProgram(this);
|
|
|
|
if (!m_shaderProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/simple.vert")) {
|
|
qCritical() << "Vertex shader compilation failed:" << m_shaderProgram->log();
|
|
return;
|
|
}
|
|
if (!m_shaderProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/simple.frag")) {
|
|
qCritical() << "Fragment shader compilation failed:" << m_shaderProgram->log();
|
|
return;
|
|
}
|
|
if (!m_shaderProgram->link()) {
|
|
qCritical() << "Shader program linking failed:" << m_shaderProgram->log();
|
|
return;
|
|
}
|
|
|
|
m_projMatrixLoc = m_shaderProgram->uniformLocation("projectionMatrix");
|
|
m_mvMatrixLoc = m_shaderProgram->uniformLocation("modelViewMatrix");
|
|
m_colorLoc = m_shaderProgram->uniformLocation("objectColor");
|
|
}
|
|
|
|
void ViewportWidget::drawSelectionPlanes()
|
|
{
|
|
const float planeSize = 5.0f;
|
|
const float planeOffset = 1.0f;
|
|
|
|
glDisable(GL_CULL_FACE);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glLineWidth(2.0f);
|
|
glDisable(GL_DEPTH_TEST);
|
|
|
|
m_vbo.bind();
|
|
QVector<GLfloat> vertices;
|
|
|
|
auto drawPlane = [&](const QVector<GLfloat>& quadVerts, bool highlighted) {
|
|
// Draw fill
|
|
if (highlighted) {
|
|
m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 0.0f, 0.5f));
|
|
m_vbo.allocate(quadVerts.constData(), quadVerts.size() * sizeof(GLfloat));
|
|
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
|
}
|
|
// Draw outline
|
|
m_shaderProgram->setUniformValue(m_colorLoc, QVector4D(1.0f, 1.0f, 0.0f, 1.0f));
|
|
m_vbo.allocate(quadVerts.constData(), quadVerts.size() * sizeof(GLfloat));
|
|
glDrawArrays(GL_LINE_LOOP, 0, 4);
|
|
};
|
|
|
|
// XY Plane (Top)
|
|
QVector<GLfloat> xyQuad = { planeOffset, 0, planeOffset, planeOffset + planeSize, 0, planeOffset,
|
|
planeOffset + planeSize, 0, planeOffset + planeSize, planeOffset, 0, planeOffset + planeSize };
|
|
drawPlane(xyQuad, m_highlightedPlane == SketchPlane::XY);
|
|
|
|
// XZ Plane (Front)
|
|
QVector<GLfloat> xzQuad = { planeOffset, planeOffset, 0, planeOffset + planeSize, planeOffset, 0,
|
|
planeOffset + planeSize, planeOffset + planeSize, 0, planeOffset, planeOffset + planeSize, 0 };
|
|
drawPlane(xzQuad, m_highlightedPlane == SketchPlane::XZ);
|
|
|
|
// YZ Plane (Right)
|
|
QVector<GLfloat> yzQuad = { 0, planeOffset, planeOffset, 0, planeOffset + planeSize, planeOffset,
|
|
0, planeOffset + planeSize, planeOffset + planeSize, 0, planeOffset, planeOffset + planeSize };
|
|
drawPlane(yzQuad, m_highlightedPlane == SketchPlane::YZ);
|
|
|
|
glEnable(GL_CULL_FACE);
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDisable(GL_BLEND);
|
|
}
|
|
|
|
ViewportWidget::SketchPlane ViewportWidget::checkPlaneSelection(const QPoint& screenPos)
|
|
{
|
|
const float planeSize = 5.0f;
|
|
const float planeOffset = 1.0f;
|
|
|
|
QVector3D intersection;
|
|
|
|
// Check front to back to handle overlaps
|
|
// YZ plane (Right)
|
|
intersection = unproject(screenPos, SketchPlane::YZ);
|
|
if (intersection.y() >= planeOffset && intersection.y() <= planeOffset + planeSize &&
|
|
intersection.z() >= planeOffset && intersection.z() <= planeOffset + planeSize) {
|
|
return SketchPlane::YZ;
|
|
}
|
|
|
|
// XZ plane (Front)
|
|
intersection = unproject(screenPos, SketchPlane::XZ);
|
|
if (intersection.x() >= planeOffset && intersection.x() <= planeOffset + planeSize &&
|
|
intersection.y() >= planeOffset && intersection.y() <= planeOffset + planeSize) {
|
|
return SketchPlane::XZ;
|
|
}
|
|
|
|
// XY plane (Top)
|
|
intersection = unproject(screenPos, SketchPlane::XY);
|
|
if (intersection.x() >= planeOffset && intersection.x() <= planeOffset + planeSize &&
|
|
intersection.z() >= planeOffset && intersection.z() <= planeOffset + planeSize) {
|
|
return SketchPlane::XY;
|
|
}
|
|
|
|
return SketchPlane::NONE;
|
|
}
|
|
|