From 64f65a2555cf4f3c39246eac35b51e1ee8ce7f04 Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Sun, 18 Dec 2022 23:48:20 +0800 Subject: [PATCH] [CORE][ADD] Click selection test - Hover detection - Click detection - Move / Scale / Rotate(have bug) - Bounding box display - Gesture done --- FinalProject/camera.h | 4 +- FinalProject/fragmentshader.glsl | 11 +- FinalProject/mainwindow.qrc | 2 + FinalProject/sceneviewer.cpp | 261 ++++++++++++++++++++++++++----- FinalProject/sceneviewer.h | 20 ++- 5 files changed, 258 insertions(+), 40 deletions(-) diff --git a/FinalProject/camera.h b/FinalProject/camera.h index 7c0a2a5..ea1d1e8 100644 --- a/FinalProject/camera.h +++ b/FinalProject/camera.h @@ -38,6 +38,9 @@ public: public: inline glm::vec3 front() const { return _front; } + inline glm::vec3 right() const { return _right; } + inline glm::vec3 up() const { return _up; } + inline glm::vec3 position() const { return _position; } inline float fovy() const { return _fovy; } inline float nearPlane() const { return _nearPlane; } @@ -48,7 +51,6 @@ public: Ray generateRay(glm::vec2 mouseRelativePosition, float aspectRatio) const; - inline glm::vec3 position() const { return _position; } public: inline void move(glm::vec2 offset); diff --git a/FinalProject/fragmentshader.glsl b/FinalProject/fragmentshader.glsl index 414b2a8..648e6c4 100644 --- a/FinalProject/fragmentshader.glsl +++ b/FinalProject/fragmentshader.glsl @@ -61,6 +61,8 @@ uniform DirLight dirlights[MAX_DIR_LIGHTS]; uniform PointLight pointlights[MAX_POINT_LIGHTS]; uniform SpotLight spotlights[MAX_SPOT_LIGHTS]; +uniform vec3 selColor; + // function prototypes vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir); vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir); @@ -79,10 +81,13 @@ void main() // phase 2: point lights for (int i = 0; i < pointlightnr; i++) result += CalcPointLight(pointlights[i], norm, FragPos, viewDir); - // // phase 3: spot light - // for (int i = 0; i < spotlightnr; i++) - // result += CalcSpotLight(spotlights[i], norm, FragPos, viewDir); + // phase 3: spot light + for (int i = 0; i < spotlightnr; i++) + result += CalcSpotLight(spotlights[i], norm, FragPos, viewDir); + if (selColor != vec3(0.0)) + result += selColor; + FragColor = vec4(result, 1.0); } diff --git a/FinalProject/mainwindow.qrc b/FinalProject/mainwindow.qrc index 47f7923..9b8663a 100644 --- a/FinalProject/mainwindow.qrc +++ b/FinalProject/mainwindow.qrc @@ -8,6 +8,8 @@ skyboxfragmentshader.glsl boundfragmentshader.glsl boundvertexshader.glsl + thumbnailfragmentshader.glsl + thumbnailvertexshader.glsl font_awesome_6_regular_free.otf diff --git a/FinalProject/sceneviewer.cpp b/FinalProject/sceneviewer.cpp index 145bce4..edcaec6 100644 --- a/FinalProject/sceneviewer.cpp +++ b/FinalProject/sceneviewer.cpp @@ -13,6 +13,11 @@ SceneViewer::SceneViewer(QWidget* parent) { // Set mouse tracking setMouseTracking(true); + // Set key tracking + setFocusPolicy(Qt::StrongFocus); + // Set the focus + setFocus(); + // OpenGL initialize QSurfaceFormat format; format.setProfile(QSurfaceFormat::CoreProfile); @@ -61,13 +66,16 @@ void SceneViewer::extractShaderResource(const QString& shaderName) { QFile::setPermissions(shaderTempPath, QFile::ReadOwner | QFile::WriteOwner); } -void SceneViewer::hitTest(const Ray& ray) { +Renderable* SceneViewer::hitTest(const Ray& ray) { HitRecord newRecord = HitRecord(); Renderable* newObject = nullptr; - int newObjectIndex = -1; for (int i = 0; i < _objects.size(); i++) { Logger::debug("Testing object " + std::to_string(i)); Renderable* obj = _objects[i]; + if (obj == _operatingObject) { + // Ignore current operating Object + continue; + } HitRecord hitRecord = obj->hit(ray); if (hitRecord.hitted()) { Logger::debug("Hitted object " + std::to_string(i)); @@ -78,14 +86,10 @@ void SceneViewer::hitTest(const Ray& ray) { if (hitRecord.hitted() && hitRecord.t() < newRecord.t()) { newRecord = hitRecord; newObject = obj; - newObjectIndex = i; } } - if (newRecord.hitted()) { - Logger::debug("Hit test hitted object with index " + std::to_string(newObjectIndex)); - } _hitRecord = newRecord; - _hoveredObject = newObject; + return newObject; } void SceneViewer::initializeGL() { @@ -191,12 +195,31 @@ void SceneViewer::paintGL() { _shaderProgram.setUniform("dirlightnr", _dirLight != nullptr ? 1 : 0); for (auto object : _objects) { + if (object == _pressedObject) { + _shaderProgram.setUniform("selColor", glm::vec3(0.22f)); + } + else if (object == _operatingObject) { + _shaderProgram.setUniform("selColor", glm::vec3(0.1f)); + } + else if (object == _hoveredObject) { + _shaderProgram.setUniform("selColor", glm::vec3(0.2f)); + } + else { + _shaderProgram.setUniform("selColor", glm::vec3(0.0f)); + } object->render(_shaderProgram); } _shaderProgram.unbind(); - if (_hoveredObject != nullptr) { + if (_selectedObject != nullptr && !_hideBound) { + _boundShader.bind(); + _boundShader.setUniform("view", view); + _boundShader.setUniform("projection", projection); + _selectedObject->boundary().render(); + _boundShader.unbind(); + } + if (_hoveredObject != nullptr && _hoveredObject != _selectedObject) { _boundShader.bind(); _boundShader.setUniform("view", view); _boundShader.setUniform("projection", projection); @@ -212,58 +235,128 @@ void SceneViewer::paintGL() { } void SceneViewer::mousePressEvent(QMouseEvent* event) { - Logger::debug("Mouse pressed at: " + std::to_string(event->x()) + ", " + std::to_string(event->y())); if (event->button() == Qt::LeftButton) { - // TODO: Hit test on objects + _pressedObject = _hoveredObject; } else { _lastMousePosition = event->pos(); } + + parentWidget()->update(); + setFocus(); +} + +void SceneViewer::mouseReleaseEvent(QMouseEvent* event) { + // State transfer + bool startOperatingObject = false; + if (_operatingObject != nullptr) { + // Click when having an operating object + _operatingObject->updateBoundary(); + if (!_dragged) { + // if haven't changed since last mouse press, it's a submission click + _operatingObject = nullptr; + _hideBound = false; + } + else { + // dragged, keep it operational + _dragged = false; + _hideBound = true; + _operatingObject = _operatingObject; + } + } + else if (_pressedObject != nullptr && _pressedObject == _selectedObject) { + // Double select on an object, set in operating mode + _operatingObject = _selectedObject; + _hideBound = true; + startOperatingObject = true; + } + else if (_dragged) { + _dragged = false; + _hideBound = false; + if (_selectedObject != nullptr) { + _selectedObject->updateBoundary(); + } + } + else { + _selectedObject = _pressedObject; + _hideBound = false; + } + + // Reset pressed object + _pressedObject = nullptr; + + // Update hover object + float relX = (float)event->x() / (float)width(); + float relY = 1 - (float)event->y() / (float)height(); + Ray ray = _camera.generateRay(glm::vec2(relX, relY), (float)width() / (float)height()); + _hoveredObject = hitTest(ray); + + if (startOperatingObject) { + // If just setted to operating mode, move the object + moveOperatingObject(ray); + } + + // Update the view + parentWidget()->update(); } void SceneViewer::mouseMoveEvent(QMouseEvent* event) { // Check the type of button pressed switch (event->buttons()) { case Qt::LeftButton: { - // Move the selected object if (_selectedObject != nullptr) { - // TODO: move the selected object + // Set dragged + _dragged = true; + // Hide boundary + _hideBound = true; + // Rotate around camera up + glm::vec2 delta = glm::vec2(event->x() - _lastMousePosition.x(), event->y() - _lastMousePosition.y()); + _selectedObject->rotate(_camera.up(), delta.x * 0.01f); + // Rotate around camera right + _selectedObject->rotate(_camera.right(), delta.y * 0.01f); } break; } case Qt::RightButton: { - // Move the camera - float xoffset = event->x() - _lastMousePosition.x(); - float yoffset = _lastMousePosition.y() - event->y(); // reversed since y-coordinates go from bottom to top - float xmovement = xoffset * _cameraMovementSpeed; - float ymovement = yoffset * _cameraMovementSpeed; - glm::vec3 cameraPrevPos = _camera.position(); - _camera.move({ -xmovement, -ymovement }); - glm::vec3 cameraNewPos = _camera.position(); - _rotateCenter += cameraNewPos - cameraPrevPos; - Logger::debug("Camera moved to: " + std::to_string(_camera.position().x) + ", " + std::to_string(_camera.position().y) + ", " + std::to_string(_camera.position().z)); - Logger::debug("New center: " + std::to_string(_rotateCenter.x) + ", " + std::to_string(_rotateCenter.y) + ", " + std::to_string(_rotateCenter.z)); + // Set dragged + _dragged = true; + moveCamera(event); break; } case Qt::MiddleButton: { - // Rotate the camera - float xoffset = event->x() - _lastMousePosition.x(); - float yoffset = _lastMousePosition.y() - event->y(); // reversed since y-coordinates go from bottom to top - // Calculate pitch angle - float pitch = yoffset * _cameraRotationSpeed; - // Calculate yaw angle - float yaw = xoffset * _cameraRotationSpeed; - _camera.rotate(_rotateCenter, pitch, -yaw); - Logger::debug("Camera rotated to: " + std::to_string(_camera.position().x) + ", " + std::to_string(_camera.position().y) + ", " + std::to_string(_camera.position().z)); - Logger::debug("Center at: " + std::to_string(_rotateCenter.x) + ", " + std::to_string(_rotateCenter.y) + ", " + std::to_string(_rotateCenter.z)); + if (_controlPressed && _selectedObject != nullptr) { + // Set dragged + _dragged = true; + // Hide boundary + _hideBound = true; + // Scale object + glm::vec2 delta = glm::vec2(event->x() - _lastMousePosition.x(), event->y() - _lastMousePosition.y()); + _selectedObject->scale(-delta.y * 0.01f); + } + else { + // Set dragged + _dragged = true; + rotateCamera(event); + } break; } case Qt::NoButton: { - // If no button pressed, do hit test and move the current object if selected float relX = (float)event->x() / (float)width(); float relY = 1 - (float)event->y() / (float)height(); Ray ray = _camera.generateRay(glm::vec2(relX, relY), (float)width() / (float)height()); - hitTest(ray); + if (_operatingObject == nullptr) { + // If no button pressed, do hit test and move the current object if selected + _hoveredObject = hitTest(ray); + if (_hoveredObject != nullptr) { + setCursor(Qt::PointingHandCursor); + } + else { + setCursor(Qt::ArrowCursor); + } + } + else { + moveOperatingObject(ray); + } break; } default: { @@ -290,3 +383,101 @@ void SceneViewer::wheelEvent(QWheelEvent* event) { // Update the view parentWidget()->update(); } + +void SceneViewer::moveCamera(QMouseEvent* event) { + // Move the camera + float xoffset = event->x() - _lastMousePosition.x(); + float yoffset = _lastMousePosition.y() - event->y(); // reversed since y-coordinates go from bottom to top + float xmovement = xoffset * _cameraMovementSpeed; + float ymovement = yoffset * _cameraMovementSpeed; + glm::vec3 cameraPrevPos = _camera.position(); + _camera.move({ -xmovement, -ymovement }); + glm::vec3 cameraNewPos = _camera.position(); + _rotateCenter += cameraNewPos - cameraPrevPos; + Logger::debug("Camera moved to: " + std::to_string(_camera.position().x) + ", " + std::to_string(_camera.position().y) + ", " + std::to_string(_camera.position().z)); + Logger::debug("New center: " + std::to_string(_rotateCenter.x) + ", " + std::to_string(_rotateCenter.y) + ", " + std::to_string(_rotateCenter.z)); + if (_operatingObject != nullptr) { + float relX = (float)event->x() / (float)width(); + float relY = 1 - (float)event->y() / (float)height(); + Ray ray = _camera.generateRay(glm::vec2(relX, relY), (float)width() / (float)height()); + moveOperatingObject(ray); + } +} + +void SceneViewer::rotateCamera(QMouseEvent* event) { + // Rotate the camera + float xoffset = event->x() - _lastMousePosition.x(); + float yoffset = _lastMousePosition.y() - event->y(); // reversed since y-coordinates go from bottom to top + // Calculate pitch angle + float pitch = yoffset * _cameraRotationSpeed; + // Calculate yaw angle + float yaw = xoffset * _cameraRotationSpeed; + _camera.rotate(_rotateCenter, pitch, -yaw); + Logger::debug("Camera rotated to: " + std::to_string(_camera.position().x) + ", " + std::to_string(_camera.position().y) + ", " + std::to_string(_camera.position().z)); + Logger::debug("Center at: " + std::to_string(_rotateCenter.x) + ", " + std::to_string(_rotateCenter.y) + ", " + std::to_string(_rotateCenter.z)); + if (_operatingObject != nullptr) { + float relX = (float)event->x() / (float)width(); + float relY = 1 - (float)event->y() / (float)height(); + Ray ray = _camera.generateRay(glm::vec2(relX, relY), (float)width() / (float)height()); + moveOperatingObject(ray); + } +} + +void SceneViewer::keyPressEvent(QKeyEvent* event) { + Logger::debug("Detect keypress " + std::to_string(event->key())); + // If ctrl pressed + if (event->modifiers().testFlag(Qt::ControlModifier)) { + Logger::debug("Control pressed"); + _controlPressed = true; + } +} + +void SceneViewer::keyReleaseEvent(QKeyEvent* event) { + // If no control pressed + if (!(event->modifiers().testFlag(Qt::ControlModifier))) { + Logger::debug("Control released"); + _controlPressed = false; + } +} + +void SceneViewer::moveOperatingObject(const Ray& ray) { + // Current moving object + hitTest(ray); + if (!_hitRecord.hitted()) { + // Move to the direction of current ray + glm::vec3 target = _camera.position() + ray.direction() * 15.0f; + _operatingObject->setPosition(target); + _operatingObject->updateBoundary(); + } + // Move the object so that the bottom center of the object is at the hit point + else if (_stickToSurface) { + // Stick the bottom center of the model to the surface + + // Clear current translation and rotation while keeping scale + _operatingObject->setPosition(glm::vec3(0.0f)); + _operatingObject->setRotation(glm::vec3(0.0f, 0.0f, 0.0f), 0.0f); + _operatingObject->updateBoundary(); + + // Set the bottom center of the model at local origin + glm::vec3 bottomCenter = _operatingObject->boundary().bottomCenterPoint(); + _operatingObject->move(-bottomCenter); + + // Rotate the model to align with the surface normal + glm::vec3 normal = _hitRecord.normal(); + glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); + glm::vec3 axis = glm::cross(up, normal); + float angle = glm::acos(glm::dot(up, normal)); + _operatingObject->rotate(axis, angle); + + // Move the model to the hit point + glm::vec3 hitPoint = _hitRecord.position(); + _operatingObject->move(hitPoint); + + // Update boundary + _operatingObject->updateBoundary(); + } + else { + // Move the object to the hit point + _operatingObject->setPosition(_hitRecord.position()); + } +} diff --git a/FinalProject/sceneviewer.h b/FinalProject/sceneviewer.h index c434a22..362cf95 100644 --- a/FinalProject/sceneviewer.h +++ b/FinalProject/sceneviewer.h @@ -43,8 +43,13 @@ private: // User Interaction flags section--------------------- QPoint _lastMousePosition; + bool _controlPressed = false; + bool _dragged = false; + bool _hideBound = false; Renderable* _hoveredObject = nullptr; + Renderable* _pressedObject = nullptr; Renderable* _selectedObject = nullptr; + Renderable* _operatingObject = nullptr; HitRecord _hitRecord; // UI interface control @@ -57,7 +62,12 @@ public: private: void extractShaderResource(const QString& shaderName); - void hitTest(const Ray& ray); + Renderable* hitTest(const Ray& ray); + +private: + void moveCamera(QMouseEvent* event); + void rotateCamera(QMouseEvent* event); + void moveOperatingObject(const Ray& ray); protected: // OpenGL functions @@ -67,6 +77,14 @@ protected: // Mouse events virtual void mousePressEvent(QMouseEvent* event) override; + virtual void mouseReleaseEvent(QMouseEvent* event) override; virtual void mouseMoveEvent(QMouseEvent* event) override; virtual void wheelEvent(QWheelEvent* event) override; + virtual void keyPressEvent(QKeyEvent* event) override; + virtual void keyReleaseEvent(QKeyEvent* event) override; + +signals: + void onHover(Renderable* object); + void onSelect(Renderable* object); + void onUpdate(Renderable* object); }; \ No newline at end of file