[CORE][ADD] Click selection test

- Hover detection
- Click detection
- Move / Scale / Rotate(have bug)
- Bounding box display
- Gesture done
This commit is contained in:
Linloir 2022-12-18 23:48:20 +08:00
parent 5dd1200a6e
commit 64f65a2555
No known key found for this signature in database
GPG Key ID: 58EEB209A0F2C366
5 changed files with 258 additions and 40 deletions

View File

@ -38,6 +38,9 @@ public:
public: public:
inline glm::vec3 front() const { return _front; } 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 fovy() const { return _fovy; }
inline float nearPlane() const { return _nearPlane; } inline float nearPlane() const { return _nearPlane; }
@ -48,7 +51,6 @@ public:
Ray generateRay(glm::vec2 mouseRelativePosition, float aspectRatio) const; Ray generateRay(glm::vec2 mouseRelativePosition, float aspectRatio) const;
inline glm::vec3 position() const { return _position; }
public: public:
inline void move(glm::vec2 offset); inline void move(glm::vec2 offset);

View File

@ -61,6 +61,8 @@ uniform DirLight dirlights[MAX_DIR_LIGHTS];
uniform PointLight pointlights[MAX_POINT_LIGHTS]; uniform PointLight pointlights[MAX_POINT_LIGHTS];
uniform SpotLight spotlights[MAX_SPOT_LIGHTS]; uniform SpotLight spotlights[MAX_SPOT_LIGHTS];
uniform vec3 selColor;
// function prototypes // function prototypes
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir); vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir); vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
@ -79,10 +81,13 @@ void main()
// phase 2: point lights // phase 2: point lights
for (int i = 0; i < pointlightnr; i++) for (int i = 0; i < pointlightnr; i++)
result += CalcPointLight(pointlights[i], norm, FragPos, viewDir); result += CalcPointLight(pointlights[i], norm, FragPos, viewDir);
// // phase 3: spot light // phase 3: spot light
// for (int i = 0; i < spotlightnr; i++) for (int i = 0; i < spotlightnr; i++)
// result += CalcSpotLight(spotlights[i], norm, FragPos, viewDir); result += CalcSpotLight(spotlights[i], norm, FragPos, viewDir);
if (selColor != vec3(0.0))
result += selColor;
FragColor = vec4(result, 1.0); FragColor = vec4(result, 1.0);
} }

View File

@ -8,6 +8,8 @@
<file>skyboxfragmentshader.glsl</file> <file>skyboxfragmentshader.glsl</file>
<file>boundfragmentshader.glsl</file> <file>boundfragmentshader.glsl</file>
<file>boundvertexshader.glsl</file> <file>boundvertexshader.glsl</file>
<file>thumbnailfragmentshader.glsl</file>
<file>thumbnailvertexshader.glsl</file>
</qresource> </qresource>
<qresource prefix="/fonts"> <qresource prefix="/fonts">
<file>font_awesome_6_regular_free.otf</file> <file>font_awesome_6_regular_free.otf</file>

View File

@ -13,6 +13,11 @@ SceneViewer::SceneViewer(QWidget* parent)
{ {
// Set mouse tracking // Set mouse tracking
setMouseTracking(true); setMouseTracking(true);
// Set key tracking
setFocusPolicy(Qt::StrongFocus);
// Set the focus
setFocus();
// OpenGL initialize // OpenGL initialize
QSurfaceFormat format; QSurfaceFormat format;
format.setProfile(QSurfaceFormat::CoreProfile); format.setProfile(QSurfaceFormat::CoreProfile);
@ -61,13 +66,16 @@ void SceneViewer::extractShaderResource(const QString& shaderName) {
QFile::setPermissions(shaderTempPath, QFile::ReadOwner | QFile::WriteOwner); QFile::setPermissions(shaderTempPath, QFile::ReadOwner | QFile::WriteOwner);
} }
void SceneViewer::hitTest(const Ray& ray) { Renderable* SceneViewer::hitTest(const Ray& ray) {
HitRecord newRecord = HitRecord(); HitRecord newRecord = HitRecord();
Renderable* newObject = nullptr; Renderable* newObject = nullptr;
int newObjectIndex = -1;
for (int i = 0; i < _objects.size(); i++) { for (int i = 0; i < _objects.size(); i++) {
Logger::debug("Testing object " + std::to_string(i)); Logger::debug("Testing object " + std::to_string(i));
Renderable* obj = _objects[i]; Renderable* obj = _objects[i];
if (obj == _operatingObject) {
// Ignore current operating Object
continue;
}
HitRecord hitRecord = obj->hit(ray); HitRecord hitRecord = obj->hit(ray);
if (hitRecord.hitted()) { if (hitRecord.hitted()) {
Logger::debug("Hitted object " + std::to_string(i)); 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()) { if (hitRecord.hitted() && hitRecord.t() < newRecord.t()) {
newRecord = hitRecord; newRecord = hitRecord;
newObject = obj; newObject = obj;
newObjectIndex = i;
} }
} }
if (newRecord.hitted()) {
Logger::debug("Hit test hitted object with index " + std::to_string(newObjectIndex));
}
_hitRecord = newRecord; _hitRecord = newRecord;
_hoveredObject = newObject; return newObject;
} }
void SceneViewer::initializeGL() { void SceneViewer::initializeGL() {
@ -191,12 +195,31 @@ void SceneViewer::paintGL() {
_shaderProgram.setUniform("dirlightnr", _dirLight != nullptr ? 1 : 0); _shaderProgram.setUniform("dirlightnr", _dirLight != nullptr ? 1 : 0);
for (auto object : _objects) { 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); object->render(_shaderProgram);
} }
_shaderProgram.unbind(); _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.bind();
_boundShader.setUniform("view", view); _boundShader.setUniform("view", view);
_boundShader.setUniform("projection", projection); _boundShader.setUniform("projection", projection);
@ -212,58 +235,128 @@ void SceneViewer::paintGL() {
} }
void SceneViewer::mousePressEvent(QMouseEvent* event) { 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) { if (event->button() == Qt::LeftButton) {
// TODO: Hit test on objects _pressedObject = _hoveredObject;
} }
else { else {
_lastMousePosition = event->pos(); _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) { void SceneViewer::mouseMoveEvent(QMouseEvent* event) {
// Check the type of button pressed // Check the type of button pressed
switch (event->buttons()) { switch (event->buttons()) {
case Qt::LeftButton: { case Qt::LeftButton: {
// Move the selected object
if (_selectedObject != nullptr) { 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; break;
} }
case Qt::RightButton: { case Qt::RightButton: {
// Move the camera // Set dragged
float xoffset = event->x() - _lastMousePosition.x(); _dragged = true;
float yoffset = _lastMousePosition.y() - event->y(); // reversed since y-coordinates go from bottom to top moveCamera(event);
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));
break; break;
} }
case Qt::MiddleButton: { case Qt::MiddleButton: {
// Rotate the camera if (_controlPressed && _selectedObject != nullptr) {
float xoffset = event->x() - _lastMousePosition.x(); // Set dragged
float yoffset = _lastMousePosition.y() - event->y(); // reversed since y-coordinates go from bottom to top _dragged = true;
// Calculate pitch angle // Hide boundary
float pitch = yoffset * _cameraRotationSpeed; _hideBound = true;
// Calculate yaw angle // Scale object
float yaw = xoffset * _cameraRotationSpeed; glm::vec2 delta = glm::vec2(event->x() - _lastMousePosition.x(), event->y() - _lastMousePosition.y());
_camera.rotate(_rotateCenter, pitch, -yaw); _selectedObject->scale(-delta.y * 0.01f);
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)); else {
// Set dragged
_dragged = true;
rotateCamera(event);
}
break; break;
} }
case Qt::NoButton: { 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 relX = (float)event->x() / (float)width();
float relY = 1 - (float)event->y() / (float)height(); float relY = 1 - (float)event->y() / (float)height();
Ray ray = _camera.generateRay(glm::vec2(relX, relY), (float)width() / (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; break;
} }
default: { default: {
@ -290,3 +383,101 @@ void SceneViewer::wheelEvent(QWheelEvent* event) {
// Update the view // Update the view
parentWidget()->update(); 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());
}
}

View File

@ -43,8 +43,13 @@ private:
// User Interaction flags section--------------------- // User Interaction flags section---------------------
QPoint _lastMousePosition; QPoint _lastMousePosition;
bool _controlPressed = false;
bool _dragged = false;
bool _hideBound = false;
Renderable* _hoveredObject = nullptr; Renderable* _hoveredObject = nullptr;
Renderable* _pressedObject = nullptr;
Renderable* _selectedObject = nullptr; Renderable* _selectedObject = nullptr;
Renderable* _operatingObject = nullptr;
HitRecord _hitRecord; HitRecord _hitRecord;
// UI interface control // UI interface control
@ -57,7 +62,12 @@ public:
private: private:
void extractShaderResource(const QString& shaderName); 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: protected:
// OpenGL functions // OpenGL functions
@ -67,6 +77,14 @@ protected:
// Mouse events // Mouse events
virtual void mousePressEvent(QMouseEvent* event) override; virtual void mousePressEvent(QMouseEvent* event) override;
virtual void mouseReleaseEvent(QMouseEvent* event) override;
virtual void mouseMoveEvent(QMouseEvent* event) override; virtual void mouseMoveEvent(QMouseEvent* event) override;
virtual void wheelEvent(QWheelEvent* 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);
}; };