feat(editor): selection lasso
This commit is contained in:
parent
6c2650c0f7
commit
1d6c931b86
7 changed files with 164 additions and 79 deletions
|
|
@ -66,4 +66,19 @@ void Workspace::PhysicsStep(float deltaTime) {
|
|||
parent->Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Instance>> Workspace::CastFrustum(Frustum frustum) {
|
||||
std::vector<std::shared_ptr<Instance>> parts;
|
||||
|
||||
for (auto it = GetDescendantsStart(); it != GetDescendantsEnd(); it++) {
|
||||
if (!it->IsA<BasePart>()) continue;
|
||||
std::shared_ptr<BasePart> part = std::dynamic_pointer_cast<BasePart>(*it);
|
||||
|
||||
if (!part->locked && frustum.checkAABB(part->position(), part->GetAABB())) {
|
||||
parts.push_back(part);
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
#include "objects/base/service.h"
|
||||
#include "objects/joint/jointinstance.h"
|
||||
#include "physics/world.h"
|
||||
#include "rendering/frustum.h"
|
||||
#include <glm/ext/vector_float3.hpp>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
|
@ -61,4 +62,5 @@ public:
|
|||
|
||||
void PhysicsStep(float deltaTime);
|
||||
inline std::optional<const RaycastResult> CastRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter = std::nullopt, unsigned short categoryMaskBits = 0xFFFF) { return physicsWorld->castRay(point, rotation, maxLength, filter, categoryMaskBits); }
|
||||
std::vector<std::shared_ptr<Instance>> CastFrustum(Frustum frustum);
|
||||
};
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
// https://stackoverflow.com/q/66227192/16255372
|
||||
FrustumPlane::FrustumPlane(Vector3 point, Vector3 normal) : normal(normal.Unit()), distance(normal.Unit().Dot(point)) {}
|
||||
|
||||
Frustum::Frustum() {}
|
||||
|
||||
Frustum::Frustum(const Camera cam, float aspect, float fovY, float zNear, float zFar) {
|
||||
const float halfVSide = zFar * tanf(fovY * 0.5f);
|
||||
const float halfHSide = halfVSide * aspect;
|
||||
|
|
@ -27,6 +29,35 @@ Frustum::Frustum(const Camera cam, float aspect, float fovY, float zNear, float
|
|||
glm::cross(frontMultFar + trueCamUp * halfVSide, camRight) };
|
||||
}
|
||||
|
||||
Frustum Frustum::createSliced(const Camera cam, float width, float height, float left, float right, float top, float bottom, float fovY, float zNear, float zFar) {
|
||||
Frustum frustum;
|
||||
|
||||
float aspect = width / height;
|
||||
float halfVSide = zFar * tanf(fovY * 0.5f);
|
||||
float halfHSide = halfVSide * aspect;
|
||||
const glm::vec3 frontMultFar = zFar * -cam.cameraFront;
|
||||
|
||||
float leftSide = -halfHSide * (left / width * 2 - 1);
|
||||
float rightSide = halfHSide * (right / width * 2 - 1);
|
||||
float topSide = -halfVSide * (top / height * 2 - 1);
|
||||
float bottomSide = halfVSide * (bottom / height * 2 - 1);
|
||||
|
||||
// Don't forget to normalize!!!
|
||||
glm::vec3 camRight = glm::normalize(glm::cross(cam.cameraFront, cam.cameraUp)); // Technically this is left, but whatever
|
||||
glm::vec3 trueCamUp = glm::cross(-cam.cameraFront, camRight);
|
||||
frustum.near = { cam.cameraPos + zNear * -cam.cameraFront, -cam.cameraFront };
|
||||
frustum.far = { cam.cameraPos + frontMultFar, cam.cameraFront };
|
||||
frustum.right = { cam.cameraPos,
|
||||
glm::cross(frontMultFar - camRight * rightSide, trueCamUp) };
|
||||
frustum.left = { cam.cameraPos,
|
||||
glm::cross(trueCamUp,frontMultFar + camRight * leftSide) };
|
||||
frustum.top = { cam.cameraPos,
|
||||
glm::cross(camRight, frontMultFar - trueCamUp * topSide) };
|
||||
frustum.bottom = { cam.cameraPos,
|
||||
glm::cross(frontMultFar + trueCamUp * bottomSide, camRight) };
|
||||
return frustum;
|
||||
}
|
||||
|
||||
bool FrustumPlane::checkPointForward(Vector3 point) {
|
||||
return (normal.Dot(point) - distance) > 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ struct Frustum {
|
|||
FrustumPlane bottom;
|
||||
|
||||
Frustum(const Camera cam, float aspect, float fovY, float zNear, float zFar);
|
||||
static Frustum createSliced(const Camera cam, float width, float height, float left, float right, float top, float bottom, float fovY, float zNear, float zFar);
|
||||
bool checkPoint(Vector3);
|
||||
bool checkAABB(Vector3 center, Vector3 extents);
|
||||
|
||||
private:
|
||||
Frustum();
|
||||
};
|
||||
|
|
@ -666,6 +666,8 @@ void render() {
|
|||
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
// For some reason this is unset by QPainter, so we override it here
|
||||
glEnable(GL_MULTISAMPLE);
|
||||
|
||||
renderSkyBox();
|
||||
renderHandles();
|
||||
|
|
@ -684,6 +686,13 @@ void render() {
|
|||
// renderAABB();
|
||||
|
||||
renderTime = tu_clock_micros() - startTime;
|
||||
|
||||
identityShader->use();
|
||||
identityShader->set("aColor", glm::vec4(1,0,0,1));
|
||||
// Unbinding both is important or else it will mess up QPainter
|
||||
// https://stackoverflow.com/a/47417780/16255372
|
||||
glBindVertexArray(0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER,0);
|
||||
}
|
||||
|
||||
void drawRect(int x, int y, int width, int height, glm::vec4 color) {
|
||||
|
|
|
|||
|
|
@ -1,29 +1,17 @@
|
|||
#include <glad/gl.h>
|
||||
#include <glm/common.hpp>
|
||||
#include <glm/vector_relational.hpp>
|
||||
#include <memory>
|
||||
#include <miniaudio.h>
|
||||
#include <qcursorconstraints.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qguiapplication.h>
|
||||
#include <string>
|
||||
#include <QPainter>
|
||||
|
||||
#include "./ui_mainwindow.h"
|
||||
#include "mainglwidget.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "enum/surface.h"
|
||||
#include "handles.h"
|
||||
#include "logger.h"
|
||||
#include "mainwindow.h"
|
||||
#include "common.h"
|
||||
#include "mainwindow.h"
|
||||
#include "math_helper.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/pvinstance.h"
|
||||
#include "objects/service/selection.h"
|
||||
#include "partassembly.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "rendering/shader.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "undohistory.h"
|
||||
#include "mainglwidget.h"
|
||||
|
||||
#define PI 3.14159
|
||||
#define M_mainWindow dynamic_cast<MainWindow*>(window())
|
||||
|
|
@ -68,9 +56,16 @@ glm::vec2 secondPoint;
|
|||
|
||||
extern std::weak_ptr<BasePart> draggingObject;
|
||||
extern std::optional<HandleFace> draggingHandle;
|
||||
extern Shader* shader;
|
||||
void MainGLWidget::paintGL() {
|
||||
QPainter painter(this);
|
||||
|
||||
painter.beginNativePainting();
|
||||
::render();
|
||||
painter.endNativePainting();
|
||||
|
||||
painter.setPen(QColor(200, 200, 200));
|
||||
painter.drawRect(selectionLasso);
|
||||
painter.end();
|
||||
}
|
||||
|
||||
bool isMouseRightDragging = false;
|
||||
|
|
@ -326,7 +321,7 @@ std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
|
|||
}
|
||||
|
||||
void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
|
||||
if (isMouseRightDragging) return; // Don't change the cursor while it is intentionally blank
|
||||
if (isMouseRightDragging || selectionLasso != QRect{0,0,0,0}) return; // Don't change the cursor while it is intentionally blank
|
||||
QPoint position = evt->pos();
|
||||
|
||||
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
|
||||
|
|
@ -369,6 +364,85 @@ void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (selectionLasso != QRect {0,0,0,0}) {
|
||||
selectionLasso = {selectionLasso.topLeft(), evt->pos()};
|
||||
|
||||
float left = std::min(selectionLasso.left(), selectionLasso.right());
|
||||
float right = std::max(selectionLasso.left(), selectionLasso.right());
|
||||
float top = std::min(selectionLasso.top(), selectionLasso.bottom());
|
||||
float bottom = std::max(selectionLasso.top(), selectionLasso.bottom());
|
||||
|
||||
Frustum selectionFrustum = Frustum::createSliced(camera, width(), height(), left, right, top, bottom, glm::radians(45.f), 0.1f, 1000.0f);
|
||||
|
||||
std::vector<std::shared_ptr<Instance>> castedParts = gWorkspace()->CastFrustum(selectionFrustum);
|
||||
gDataModel->GetService<Selection>()->Set(castedParts);
|
||||
}
|
||||
}
|
||||
|
||||
bool MainGLWidget::handlePartClick(QMouseEvent* evt) {
|
||||
QPoint position = evt->pos();
|
||||
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
|
||||
|
||||
// raycast part
|
||||
std::shared_ptr<Selection> selection = gDataModel->GetService<Selection>();
|
||||
std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000);
|
||||
if (!rayHit || !rayHit->hitPart) { selection->Set({}); return false; }
|
||||
std::shared_ptr<BasePart> part = rayHit->hitPart;
|
||||
if (part->locked) { selection->Set({}); return false; }
|
||||
|
||||
std::shared_ptr<PVInstance> selObject = part;
|
||||
|
||||
// Traverse to the root model
|
||||
if (~evt->modifiers() & Qt::AltModifier) {
|
||||
nullable std::shared_ptr<Instance> nextParent = selObject->GetParent();
|
||||
while (nextParent && nextParent->IsA("Model")) {
|
||||
selObject = std::dynamic_pointer_cast<PVInstance>(nextParent); nextParent = selObject->GetParent();
|
||||
}
|
||||
}
|
||||
|
||||
initialAssembly = PartAssembly::FromSelection({selObject});
|
||||
initialFrame = initialAssembly.assemblyOrigin();
|
||||
initialHitPos = rayHit->worldPoint;
|
||||
initialHitNormal = rayHit->worldNormal;
|
||||
|
||||
// Handle surface tool
|
||||
if (mainWindow()->selectedTool >= TOOL_SMOOTH) {
|
||||
Vector3 localNormal = part->cframe.Inverse().Rotation() * rayHit->worldNormal;
|
||||
NormalId face = faceFromNormal(localNormal);
|
||||
SurfaceType surface = SurfaceType(mainWindow()->selectedTool - TOOL_SMOOTH);
|
||||
std::string surfacePropertyName = EnumType::NormalId.FromValue(face)->Name() + "Surface";
|
||||
|
||||
// Get old surface and set new surface
|
||||
EnumItem newSurface = EnumType::SurfaceType.FromValue((int)surface).value();
|
||||
EnumItem oldSurface = part->GetProperty(surfacePropertyName).expect().get<EnumItem>();
|
||||
part->SetProperty(surfacePropertyName, newSurface).expect();
|
||||
|
||||
M_mainWindow->undoManager.PushState({UndoStatePropertyChanged { part, surfacePropertyName, oldSurface, newSurface }});
|
||||
|
||||
if (mainWindow()->editSoundEffects && QFile::exists("./assets/excluded/electronicpingshort.wav"))
|
||||
playSound("./assets/excluded/electronicpingshort.wav");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//part.selected = true;
|
||||
isMouseDragging = true;
|
||||
draggingObject = part;
|
||||
initialTransforms = PartAssembly::FromSelection({part}).GetCurrentTransforms();
|
||||
if (evt->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier)) {
|
||||
auto sel = selection->Get();
|
||||
if (std::find(sel.begin(), sel.end(), selObject) == sel.end())
|
||||
selection->Add({ selObject });
|
||||
else
|
||||
selection->Remove({ selObject });
|
||||
} else {
|
||||
selection->Set({ selObject });
|
||||
}
|
||||
// Disable bit so that we can ignore the part while raycasting
|
||||
// part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
||||
|
|
@ -400,63 +474,9 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
|||
return;
|
||||
}
|
||||
|
||||
// raycast part
|
||||
std::shared_ptr<Selection> selection = gDataModel->GetService<Selection>();
|
||||
std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000);
|
||||
if (!rayHit || !rayHit->hitPart) { selection->Set({}); return; }
|
||||
std::shared_ptr<BasePart> part = rayHit->hitPart;
|
||||
if (part->locked) { selection->Set({}); return; }
|
||||
if (handlePartClick(evt)) return;
|
||||
|
||||
std::shared_ptr<PVInstance> selObject = part;
|
||||
|
||||
// Traverse to the root model
|
||||
if (~evt->modifiers() & Qt::AltModifier) {
|
||||
nullable std::shared_ptr<Instance> nextParent = selObject->GetParent();
|
||||
while (nextParent && nextParent->IsA("Model")) {
|
||||
selObject = std::dynamic_pointer_cast<PVInstance>(nextParent); nextParent = selObject->GetParent();
|
||||
}
|
||||
}
|
||||
|
||||
initialAssembly = PartAssembly::FromSelection({selObject});
|
||||
initialFrame = initialAssembly.assemblyOrigin();
|
||||
initialHitPos = rayHit->worldPoint;
|
||||
initialHitNormal = rayHit->worldNormal;
|
||||
|
||||
// Handle surface tool
|
||||
if (mainWindow()->selectedTool >= TOOL_SMOOTH) {
|
||||
Vector3 localNormal = part->cframe.Inverse().Rotation() * rayHit->worldNormal;
|
||||
NormalId face = faceFromNormal(localNormal);
|
||||
SurfaceType surface = SurfaceType(mainWindow()->selectedTool - TOOL_SMOOTH);
|
||||
std::string surfacePropertyName = EnumType::NormalId.FromValue(face)->Name() + "Surface";
|
||||
|
||||
// Get old surface and set new surface
|
||||
EnumItem newSurface = EnumType::SurfaceType.FromValue((int)surface).value();
|
||||
EnumItem oldSurface = part->GetProperty(surfacePropertyName).expect().get<EnumItem>();
|
||||
part->SetProperty(surfacePropertyName, newSurface).expect();
|
||||
|
||||
M_mainWindow->undoManager.PushState({UndoStatePropertyChanged { part, surfacePropertyName, oldSurface, newSurface }});
|
||||
|
||||
if (mainWindow()->editSoundEffects && QFile::exists("./assets/excluded/electronicpingshort.wav"))
|
||||
playSound("./assets/excluded/electronicpingshort.wav");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//part.selected = true;
|
||||
isMouseDragging = true;
|
||||
draggingObject = part;
|
||||
initialTransforms = PartAssembly::FromSelection({part}).GetCurrentTransforms();
|
||||
if (evt->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier)) {
|
||||
auto sel = selection->Get();
|
||||
if (std::find(sel.begin(), sel.end(), selObject) == sel.end())
|
||||
selection->Add({ selObject });
|
||||
else
|
||||
selection->Remove({ selObject });
|
||||
} else {
|
||||
selection->Set({ selObject });
|
||||
}
|
||||
// Disable bit so that we can ignore the part while raycasting
|
||||
// part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10);
|
||||
selectionLasso = {position, QSize {0, 0}};
|
||||
|
||||
return;
|
||||
} default:
|
||||
|
|
@ -471,6 +491,7 @@ void MainGLWidget::mouseReleaseEvent(QMouseEvent* evt) {
|
|||
isMouseDragging = false;
|
||||
draggingObject = {};
|
||||
draggingHandle = std::nullopt;
|
||||
selectionLasso = {0,0,0,0};
|
||||
setCursor(Qt::ArrowCursor);
|
||||
|
||||
if (!initialTransforms.empty()) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
#ifndef MAINGLWIDGET_H
|
||||
#define MAINGLWIDGET_H
|
||||
|
||||
#include "objects/part/part.h"
|
||||
#include "qevent.h"
|
||||
#include <QOpenGLWidget>
|
||||
#include <QWidget>
|
||||
#include <glm/fwd.hpp>
|
||||
#include <memory>
|
||||
#include <qmenu.h>
|
||||
#include <QEvent>
|
||||
#include <QOpenGLWidget>
|
||||
#include <QMenu>
|
||||
|
||||
class BasePart;
|
||||
class HandleFace;
|
||||
class MainWindow;
|
||||
|
||||
|
|
@ -28,6 +28,7 @@ protected:
|
|||
void handleLinearTransform(QMouseEvent* evt);
|
||||
void handleRotationalTransform(QMouseEvent* evt);
|
||||
void handleCursorChange(QMouseEvent* evt);
|
||||
bool handlePartClick(QMouseEvent* evt);
|
||||
void startLinearTransform(QMouseEvent* evt);
|
||||
std::optional<HandleFace> raycastHandle(glm::vec3 pointDir);
|
||||
|
||||
|
|
@ -42,6 +43,8 @@ protected:
|
|||
|
||||
MainWindow* mainWindow();
|
||||
float snappingFactor();
|
||||
|
||||
QRect selectionLasso;
|
||||
};
|
||||
|
||||
#endif // MAINGLWIDGET_H
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue