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