diff --git a/editor/editorcommon.h b/editor/editorcommon.h new file mode 100644 index 0000000..2d5d08d --- /dev/null +++ b/editor/editorcommon.h @@ -0,0 +1,10 @@ +#pragma once + +enum SelectedTool { + SELECT, + MOVE, + SCALE, + ROTATE, +}; + +extern SelectedTool selectedTool; \ No newline at end of file diff --git a/editor/mainglwidget.cpp b/editor/mainglwidget.cpp index d9f0d9d..439ada7 100644 --- a/editor/mainglwidget.cpp +++ b/editor/mainglwidget.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include @@ -25,6 +27,7 @@ MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent) { setFocusPolicy(Qt::FocusPolicy::ClickFocus); + setMouseTracking(true); } void MainGLWidget::initializeGL() { @@ -47,50 +50,72 @@ void MainGLWidget::paintGL() { ::render(NULL); } -bool isMouseDragging = false; +bool isMouseRightDragging = false; QPoint lastMousePos; -void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) { - // if (!(evt->buttons() & Qt::RightButton)) return; - if (!isMouseDragging) return; +void MainGLWidget::handleCameraRotate(QMouseEvent* evt) { + if (!isMouseRightDragging) return; camera.processRotation(evt->pos().x() - lastMousePos.x(), evt->pos().y() - lastMousePos.y()); lastMousePos = evt->pos(); + // QCursor::setPos(lastMousePos); } -class FirstRayHit : public rp::RaycastCallback { - rp::Body** target; +bool isMouseDragging = false; +std::optional> draggingObject; +void MainGLWidget::handleObjectDrag(QMouseEvent* evt) { + if (!isMouseDragging) return; - virtual rp::decimal notifyRaycastHit(const rp::RaycastInfo& raycastInfo) override { - if (reinterpret_cast(raycastInfo.body->getUserData())->name == "Baseplate") return 1; + QPoint position = evt->pos(); - *target = raycastInfo.body; - return 0; - } + glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height())); + std::optional rayHit = castRayNearest(camera.cameraPos, pointDir, 50000, [](std::shared_ptr part) { + return (part == draggingObject->lock()) ? FilterResult::PASS : FilterResult::TARGET; + }); + + if (!rayHit) return; + draggingObject->lock()->position = rpToGlm(rayHit->worldPoint); + draggingObject->lock()->position += rpToGlm(rayHit->worldNormal) * draggingObject->lock()->scale / 2.f; + syncPartPhysics(draggingObject->lock()); +} -public: - FirstRayHit(rp::Body** target) : target(target) {} -}; +void MainGLWidget::handleCursorChange(QMouseEvent* evt) { + QPoint position = evt->pos(); + + glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height())); + std::optional rayHit = castRayNearest(camera.cameraPos, pointDir, 50000); + setCursor((rayHit && partFromBody(rayHit->body)->name != "Baseplate") ? Qt::OpenHandCursor : Qt::ArrowCursor); +} + +void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) { + handleCameraRotate(evt); + handleObjectDrag(evt); + handleCursorChange(evt); +} void MainGLWidget::mousePressEvent(QMouseEvent* evt) { switch(evt->button()) { // Camera drag case Qt::RightButton: { lastMousePos = evt->pos(); - isMouseDragging = true; + isMouseRightDragging = true; return; // Clicking on objects } case Qt::LeftButton: { QPoint position = evt->pos(); - rp::Body* rayHitTarget = NULL; glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height())); - castRay(camera.cameraPos, pointDir, 50000, new FirstRayHit(&rayHitTarget)); - if (!rayHitTarget) return; - std::shared_ptr part = partFromBody(rayHitTarget); + std::optional rayHit = castRayNearest(camera.cameraPos, pointDir, 50000); + if (!rayHit || !partFromBody(rayHit->body)) return; + std::shared_ptr part = partFromBody(rayHit->body); + if (part->name == "Baseplate") return; //part.selected = true; + isMouseDragging = true; + draggingObject = part; setSelection(std::vector { part }); + // Disable bit so that we can ignore the part while raycasting + // part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10); return; } default: @@ -99,7 +124,10 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) { } void MainGLWidget::mouseReleaseEvent(QMouseEvent* evt) { + // if (isMouseDragging) draggingObject->lock()->rigidBody->getCollider(0)->setCollisionCategoryBits(0b11); + isMouseRightDragging = false; isMouseDragging = false; + draggingObject = std::nullopt; } static int moveZ = 0; diff --git a/editor/mainglwidget.h b/editor/mainglwidget.h index 1824780..4f54c4c 100644 --- a/editor/mainglwidget.h +++ b/editor/mainglwidget.h @@ -18,6 +18,10 @@ protected: void resizeGL(int w, int h) override; void paintGL() override; + void handleCameraRotate(QMouseEvent* evt); + void handleObjectDrag(QMouseEvent* evt); + void handleCursorChange(QMouseEvent* evt); + void mouseMoveEvent(QMouseEvent* evt) override; void mousePressEvent(QMouseEvent* evt) override; void mouseReleaseEvent(QMouseEvent* evt) override; diff --git a/editor/mainwindow.cpp b/editor/mainwindow.cpp index ced9974..2043434 100644 --- a/editor/mainwindow.cpp +++ b/editor/mainwindow.cpp @@ -13,11 +13,14 @@ #include #include "common.h" +#include "editorcommon.h" #include "physics/simulation.h" #include "objects/part.h" #include "qitemselectionmodel.h" #include "qobject.h" +SelectedTool selectedTool; + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) @@ -35,6 +38,12 @@ MainWindow::MainWindow(QWidget *parent) ui->propertiesView->setSelected(inst); }); + connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = SelectedTool::SELECT; updateSelectedTool(); }); + connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::MOVE : SelectedTool::SELECT; updateSelectedTool(); }); + connect(ui->actionToolScale, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::SCALE : SelectedTool::SELECT; updateSelectedTool(); }); + connect(ui->actionToolRotate, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::ROTATE : SelectedTool::SELECT; updateSelectedTool(); }); + ui->actionToolSelect->setChecked(true); + // ui->explorerView->Init(ui); simulationInit(); @@ -82,6 +91,13 @@ void MainWindow::timerEvent(QTimerEvent* evt) { ui->mainWidget->updateCycle(); } +void MainWindow::updateSelectedTool() { + ui->actionToolSelect->setChecked(selectedTool == SelectedTool::SELECT); + ui->actionToolMove->setChecked(selectedTool == SelectedTool::MOVE); + ui->actionToolScale->setChecked(selectedTool == SelectedTool::SCALE); + ui->actionToolRotate->setChecked(selectedTool == SelectedTool::ROTATE); +} + MainWindow::~MainWindow() { delete ui; diff --git a/editor/mainwindow.h b/editor/mainwindow.h index d9c9b76..2779d5b 100644 --- a/editor/mainwindow.h +++ b/editor/mainwindow.h @@ -26,6 +26,7 @@ public: private: QBasicTimer timer; + void updateSelectedTool(); void timerEvent(QTimerEvent*) override; }; #endif // MAINWINDOW_H diff --git a/src/physics/simulation.cpp b/src/physics/simulation.cpp index d86e4c4..6e0c439 100644 --- a/src/physics/simulation.cpp +++ b/src/physics/simulation.cpp @@ -61,6 +61,7 @@ void syncPartPhysics(std::shared_ptr part) { if (part->rigidBody->getNbColliders() == 0) part->rigidBody->addCollider(shape, rp::Transform()); part->rigidBody->setType(part->anchored ? rp::BodyType::STATIC : rp::BodyType::DYNAMIC); + part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b11); part->rigidBody->setUserData(&*part); } @@ -81,7 +82,56 @@ void physicsStep(float deltaTime) { } } -void castRay(glm::vec3 point, glm::vec3 rotation, float maxLength, rp::RaycastCallback* callback) { +RaycastResult::RaycastResult(const rp::RaycastInfo& raycastInfo) + : worldPoint(raycastInfo.worldPoint) + , worldNormal(raycastInfo.worldNormal) + , hitFraction(raycastInfo.hitFraction) + , triangleIndex(raycastInfo.triangleIndex) + , body(raycastInfo.body) + , collider(raycastInfo.collider) {} + +class NearestRayHit : public rp::RaycastCallback { + rp::Vector3 startPos; + std::optional filter; + + std::optional nearestHit; + float nearestHitDistance = -1; + + // Order is not guaranteed, so we have to figure out the nearest object using a more sophisticated algorith, + rp::decimal notifyRaycastHit(const rp::RaycastInfo& raycastInfo) override { + // If the detected object is further away than the nearest object, continue. + int distance = (raycastInfo.worldPoint - startPos).length(); + if (nearestHitDistance != -1 && distance >= nearestHitDistance) + return 1; + + if (!filter) { + nearestHit = raycastInfo; + nearestHitDistance = distance; + return 1; + } + + std::shared_ptr part = partFromBody(raycastInfo.body); + FilterResult result = filter.value()(part); + if (result == FilterResult::BLOCK) { + nearestHit = std::nullopt; + nearestHitDistance = distance; + return 1; + } else if (result == FilterResult::TARGET) { + nearestHit = raycastInfo; + nearestHitDistance = distance; + return 1; + } + return 1; + }; + +public: + NearestRayHit(rp::Vector3 startPos, std::optional filter = std::nullopt) : startPos(startPos), filter(filter) {} + std::optional getNearestHit() { return nearestHit; }; +}; + +std::optional castRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional filter, unsigned short categoryMaskBits) { rp::Ray ray(glmToRp(point), glmToRp(glm::normalize(rotation)) * maxLength); - world->raycast(ray, callback); + NearestRayHit rayHit(glmToRp(point), filter); + world->raycast(ray, &rayHit, categoryMaskBits); + return rayHit.getNearestHit(); } \ No newline at end of file diff --git a/src/physics/simulation.h b/src/physics/simulation.h index 203671d..41dc100 100644 --- a/src/physics/simulation.h +++ b/src/physics/simulation.h @@ -5,7 +5,26 @@ #include #include +struct RaycastResult { + rp::Vector3 worldPoint; + rp::Vector3 worldNormal; + rp::decimal hitFraction; + int triangleIndex; + rp::Body* body; + rp::Collider* collider; + + RaycastResult(const rp::RaycastInfo& raycastInfo); +}; + +enum FilterResult { + TARGET, // The object is captured + BLOCK, // The object blocks any objects behind it, but is not captured + PASS, // The object is transparent, ignore it +}; + +typedef std::function)> RaycastFilter; + void simulationInit(); void syncPartPhysics(std::shared_ptr part); void physicsStep(float deltaTime); -void castRay(glm::vec3 point, glm::vec3 rotation, float maxLength, rp::RaycastCallback* callback); \ No newline at end of file +std::optional castRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional filter = std::nullopt, unsigned short categoryMaskBits = 0xFFFF); \ No newline at end of file diff --git a/src/physics/util.h b/src/physics/util.h index 82e487f..351e553 100644 --- a/src/physics/util.h +++ b/src/physics/util.h @@ -26,6 +26,7 @@ inline glm::quat rpToGlm(rp::Quaternion quat) { return glm::quat(quat.w, quat.x, quat.y, quat.z); } +// Make this std::optional inline std::shared_ptr partFromBody(rp::Body* body) { Part* raw = reinterpret_cast(body->getUserData()); std::shared_ptr shared = std::dynamic_pointer_cast(raw->shared_from_this());