feat: dragging objects

This commit is contained in:
maelstrom 2025-02-03 00:23:25 +01:00
parent 9437faadaa
commit 7cf7343406
8 changed files with 151 additions and 22 deletions

10
editor/editorcommon.h Normal file
View file

@ -0,0 +1,10 @@
#pragma once
enum SelectedTool {
SELECT,
MOVE,
SCALE,
ROTATE,
};
extern SelectedTool selectedTool;

View file

@ -6,6 +6,8 @@
#include <glm/ext/vector_float3.hpp>
#include <glm/geometric.hpp>
#include <glm/matrix.hpp>
#include <memory>
#include <optional>
#include <reactphysics3d/collision/RaycastInfo.h>
#include <vector>
@ -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<std::weak_ptr<Part>> draggingObject;
void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
if (!isMouseDragging) return;
virtual rp::decimal notifyRaycastHit(const rp::RaycastInfo& raycastInfo) override {
if (reinterpret_cast<Part*>(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<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000, [](std::shared_ptr<Part> 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<const RaycastResult> 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> part = partFromBody(rayHitTarget);
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000);
if (!rayHit || !partFromBody(rayHit->body)) return;
std::shared_ptr<Part> part = partFromBody(rayHit->body);
if (part->name == "Baseplate") return;
//part.selected = true;
isMouseDragging = true;
draggingObject = part;
setSelection(std::vector<InstanceRefWeak> { 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;

View file

@ -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;

View file

@ -13,11 +13,14 @@
#include <optional>
#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;

View file

@ -26,6 +26,7 @@ public:
private:
QBasicTimer timer;
void updateSelectedTool();
void timerEvent(QTimerEvent*) override;
};
#endif // MAINWINDOW_H

View file

@ -61,6 +61,7 @@ void syncPartPhysics(std::shared_ptr<Part> 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<RaycastFilter> filter;
std::optional<RaycastResult> 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> 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<RaycastFilter> filter = std::nullopt) : startPos(startPos), filter(filter) {}
std::optional<const RaycastResult> getNearestHit() { return nearestHit; };
};
std::optional<const RaycastResult> castRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> 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();
}

View file

@ -5,7 +5,26 @@
#include <memory>
#include <reactphysics3d/collision/RaycastInfo.h>
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<FilterResult(std::shared_ptr<Part>)> RaycastFilter;
void simulationInit();
void syncPartPhysics(std::shared_ptr<Part> part);
void physicsStep(float deltaTime);
void castRay(glm::vec3 point, glm::vec3 rotation, float maxLength, rp::RaycastCallback* callback);
std::optional<const RaycastResult> castRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter = std::nullopt, unsigned short categoryMaskBits = 0xFFFF);

View file

@ -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<Part> partFromBody(rp::Body* body) {
Part* raw = reinterpret_cast<Part*>(body->getUserData());
std::shared_ptr<Part> shared = std::dynamic_pointer_cast<Part>(raw->shared_from_this());