feat: dragging objects
This commit is contained in:
parent
9437faadaa
commit
7cf7343406
8 changed files with 151 additions and 22 deletions
10
editor/editorcommon.h
Normal file
10
editor/editorcommon.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
enum SelectedTool {
|
||||
SELECT,
|
||||
MOVE,
|
||||
SCALE,
|
||||
ROTATE,
|
||||
};
|
||||
|
||||
extern SelectedTool selectedTool;
|
|
@ -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;
|
||||
});
|
||||
|
||||
public:
|
||||
FirstRayHit(rp::Body** target) : target(target) {}
|
||||
};
|
||||
if (!rayHit) return;
|
||||
draggingObject->lock()->position = rpToGlm(rayHit->worldPoint);
|
||||
draggingObject->lock()->position += rpToGlm(rayHit->worldNormal) * draggingObject->lock()->scale / 2.f;
|
||||
syncPartPhysics(draggingObject->lock());
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -26,6 +26,7 @@ public:
|
|||
private:
|
||||
QBasicTimer timer;
|
||||
|
||||
void updateSelectedTool();
|
||||
void timerEvent(QTimerEvent*) override;
|
||||
};
|
||||
#endif // MAINWINDOW_H
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
|
@ -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());
|
||||
|
|
Loading…
Add table
Reference in a new issue