refactor(handles): some refactoring with handles

This commit is contained in:
maelstrom 2025-05-17 22:26:53 +02:00
parent 882a215b36
commit 18b573db97
8 changed files with 94 additions and 106 deletions

View file

@ -11,7 +11,7 @@ std::shared_ptr<DataModel> gDataModel = DataModel::New();
std::shared_ptr<DataModel> editModeDataModel = gDataModel;
std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
std::shared_ptr<Handles> editorToolHandles = Handles::New();
Handles editorToolHandles;
std::vector<InstanceRefWeak> currentSelection;

View file

@ -1,6 +1,6 @@
#pragma once
#include "objects/base/instance.h"
#include "objects/handles.h"
#include "handles.h"
#include "objects/workspace.h"
#include "objects/datamodel.h"
#include "camera.h"
@ -22,7 +22,7 @@ extern std::shared_ptr<DataModel> editModeDataModel;
inline std::shared_ptr<Workspace> gWorkspace() { return std::dynamic_pointer_cast<Workspace>(gDataModel->services["Workspace"]); }
extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
extern std::shared_ptr<Handles> editorToolHandles;
extern Handles editorToolHandles;
void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer = false);
const std::vector<InstanceRefWeak> getSelection();

View file

@ -3,6 +3,7 @@
#include "datatypes/cframe.h"
#include "datatypes/vector.h"
#include <glm/ext/scalar_common.hpp>
#include <memory>
#include <optional>
#include <reactphysics3d/collision/RaycastInfo.h>
#include <reactphysics3d/engine/PhysicsCommon.h>
@ -23,16 +24,23 @@ static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0,
static rp3d::PhysicsCommon common;
static rp3d::PhysicsWorld* world = common.createPhysicsWorld();
Handles::Handles(): Instance(&TYPE) {
std::shared_ptr<Part> getHandleAdornee() {
for (std::weak_ptr<Instance> inst : getSelection()) {
if (!inst.expired() && inst.lock()->IsA<Part>())
return inst.lock()->CastTo<Part>().expect();
}
return {};
}
CFrame Handles::GetCFrameOfHandle(HandleFace face) {
if (adornee.expired()) return CFrame(glm::vec3(0,0,0), (Vector3)glm::vec3(0,0,0));
CFrame getCFrameOfHandle(HandleFace face) {
auto adornee = getHandleAdornee();
if (adornee == nullptr) return CFrame(glm::vec3(0,0,0), (Vector3)glm::vec3(0,0,0));
CFrame localFrame = worldMode ? CFrame::IDENTITY + adornee.lock()->position() : adornee.lock()->cframe;
CFrame localFrame = editorToolHandles.worldMode ? CFrame::IDENTITY + adornee->position() : adornee->cframe;
Vector3 handleNormal = face.normal;
if (nixAxes)
if (editorToolHandles.nixAxes)
handleNormal = XYZToZXY * face.normal;
// We don't want this to align with local * face.normal, or else we have problems.
@ -40,35 +48,36 @@ CFrame Handles::GetCFrameOfHandle(HandleFace face) {
if (glm::abs(glm::dot(glm::vec3(localFrame.Rotation() * handleNormal), upAxis)) > 0.9999f)
upAxis = glm::vec3(0, 1, 0);
glm::vec3 partSize = handlesType == HandlesType::RotateHandles ? glm::vec3(glm::max(adornee.lock()->size.x, adornee.lock()->size.y, adornee.lock()->size.z)) : adornee.lock()->size;
Vector3 handleOffset = this->worldMode ? ((Vector3::ONE * 2.f) + adornee.lock()->GetAABB() * 0.5f) : Vector3(2.f + partSize * 0.5f);
glm::vec3 partSize = editorToolHandles.handlesType == HandlesType::RotateHandles ? glm::vec3(glm::max(adornee->size.x, adornee->size.y, adornee->size.z)) : adornee->size;
Vector3 handleOffset = editorToolHandles.worldMode ? ((Vector3::ONE * 2.f) + adornee->GetAABB() * 0.5f) : Vector3(2.f + partSize * 0.5f);
Vector3 handlePos = localFrame * (handleOffset * handleNormal);
CFrame cframe(handlePos, handlePos + localFrame.Rotation() * -handleNormal, upAxis);
return cframe;
}
CFrame Handles::PartCFrameFromHandlePos(HandleFace face, Vector3 newPos) {
if (adornee.expired()) return CFrame(glm::vec3(0,0,0), (Vector3)glm::vec3(0,0,0));
CFrame partCFrameFromHandlePos(HandleFace face, Vector3 newPos) {
auto adornee = getHandleAdornee();
if (adornee == nullptr) return CFrame(glm::vec3(0,0,0), (Vector3)glm::vec3(0,0,0));
CFrame localFrame = worldMode ? CFrame::IDENTITY + adornee.lock()->position() : adornee.lock()->cframe;
CFrame localFrame = editorToolHandles.worldMode ? CFrame::IDENTITY + adornee->position() : adornee->cframe;
CFrame inverseFrame = localFrame.Inverse();
Vector3 handleOffset = this->worldMode ? ((Vector3::ONE * 2.f) + adornee.lock()->GetAABB() * 0.5f) : Vector3(2.f + adornee.lock()->size * 0.5f);
Vector3 handleOffset = editorToolHandles.worldMode ? ((Vector3::ONE * 2.f) + adornee->GetAABB() * 0.5f) : Vector3(2.f + adornee->size * 0.5f);
Vector3 handlePos = localFrame * (handleOffset * face.normal);
// glm::vec3 localPos = inverseFrame * newPos;
glm::vec3 newPartPos = newPos - localFrame.Rotation() * (handleOffset * face.normal);
return adornee.lock()->cframe.Rotation() + newPartPos;
return adornee->cframe.Rotation() + newPartPos;
}
std::optional<HandleFace> Handles::RaycastHandle(rp3d::Ray ray) {
std::optional<HandleFace> raycastHandle(rp3d::Ray ray) {
for (HandleFace face : HandleFace::Faces) {
CFrame cframe = GetCFrameOfHandle(face);
CFrame cframe = getCFrameOfHandle(face);
// Implement manual detection via boxes instead of... this shit
// This code also hardly works, and is not good at all... Hooo nope.
rp3d::RigidBody* body = world->createRigidBody(CFrame::IDENTITY + cframe.Position());
body->addCollider(common.createBoxShape(cframe.Rotation() * Vector3(HandleSize(face) / 2.f)), rp3d::Transform::identity());
body->addCollider(common.createBoxShape(cframe.Rotation() * Vector3(handleSize(face) / 2.f)), rp3d::Transform::identity());
rp3d::RaycastInfo info;
if (body->raycast(ray, info)) {
@ -82,8 +91,8 @@ std::optional<HandleFace> Handles::RaycastHandle(rp3d::Ray ray) {
return std::nullopt;
}
Vector3 Handles::HandleSize(HandleFace face) {
if (handlesType == HandlesType::MoveHandles)
Vector3 handleSize(HandleFace face) {
if (editorToolHandles.handlesType == HandlesType::MoveHandles)
return glm::vec3(0.5f, 0.5f, 2.f);
return glm::vec3(1,1,1);
}

43
core/src/handles.h Normal file
View file

@ -0,0 +1,43 @@
#pragma once
#include "datatypes/cframe.h"
#include "objects/part.h"
#include <array>
#include <memory>
class HandleFace {
HandleFace(int index, glm::vec3 normal) : index(index), normal(normal){}
public:
int index;
glm::vec3 normal;
static HandleFace XPos;
static HandleFace XNeg;
static HandleFace YPos;
static HandleFace YNeg;
static HandleFace ZPos;
static HandleFace ZNeg;
static std::array<HandleFace, 6> Faces;
};
enum HandlesType {
MoveHandles,
ScaleHandles,
RotateHandles,
};
struct Handles {
bool nixAxes = false; // XYZ -> ZXY, used with rotation
bool active;
HandlesType handlesType;
// World-space handles vs local-space handles
bool worldMode = false;
};
std::shared_ptr<Part> getHandleAdornee();
CFrame getCFrameOfHandle(HandleFace face);
CFrame partCFrameFromHandlePos(HandleFace face, Vector3 newPos);
Vector3 handleSize(HandleFace face);
std::optional<HandleFace> raycastHandle(rp3d::Ray ray);

View file

@ -1,55 +0,0 @@
#pragma once
#include "base.h"
#include "datatypes/cframe.h"
#include "objects/annotation.h"
#include "objects/base/service.h"
#include "objects/part.h"
#include <array>
#include <memory>
#include <reactphysics3d/body/RigidBody.h>
class HandleFace {
HandleFace(int index, glm::vec3 normal) : index(index), normal(normal){}
public:
int index;
glm::vec3 normal;
static HandleFace XPos;
static HandleFace XNeg;
static HandleFace YPos;
static HandleFace YNeg;
static HandleFace ZPos;
static HandleFace ZNeg;
static std::array<HandleFace, 6> Faces;
};
enum HandlesType {
MoveHandles,
ScaleHandles,
RotateHandles,
};
class DEF_INST_(abstract) Handles : public Instance {
AUTOGEN_PREAMBLE
public:
bool nixAxes = false; // XYZ -> ZXY, used with rotation
bool active;
std::weak_ptr<Part> adornee;
HandlesType handlesType;
// inline std::weak_ptr<Part> GetAdornee() { return adornee; }
// inline void SetAdornee(std::weak_ptr<Part> newAdornee) { this->adornee = newAdornee; updateAdornee(); };
Handles();
// World-space handles vs local-space handles
bool worldMode = false;
CFrame GetCFrameOfHandle(HandleFace face);
CFrame PartCFrameFromHandlePos(HandleFace face, Vector3 newPos);
Vector3 HandleSize(HandleFace face);
std::optional<HandleFace> RaycastHandle(rp3d::Ray ray);
static inline std::shared_ptr<Handles> New() { return std::make_shared<Handles>(); };
};

View file

@ -16,7 +16,7 @@
#include "datatypes/cframe.h"
#include "datatypes/color3.h"
#include "objects/handles.h"
#include "handles.h"
#include "rendering/torus.h"
#include "shader.h"
#include "mesh.h"
@ -266,7 +266,7 @@ void renderSkyBox() {
}
void renderHandles() {
if (editorToolHandles->adornee.expired() || !editorToolHandles->active) return;
if (!editorToolHandles.active) return;
glDepthMask(GL_TRUE);
glCullFace(GL_BACK);
@ -292,8 +292,8 @@ void renderHandles() {
handleShader->set("viewPos", camera.cameraPos);
for (auto face : HandleFace::Faces) {
glm::mat4 model = editorToolHandles->GetCFrameOfHandle(face);
model = glm::scale(model, (glm::vec3)editorToolHandles->HandleSize(face));
glm::mat4 model = getCFrameOfHandle(face);
model = glm::scale(model, (glm::vec3)handleSize(face));
handleShader->set("model", model);
handleShader->set("material", Material {
.diffuse = glm::abs(face.normal),
@ -303,7 +303,7 @@ void renderHandles() {
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
handleShader->set("normalMatrix", normalMatrix);
if (editorToolHandles->handlesType == HandlesType::MoveHandles) {
if (editorToolHandles.handlesType == HandlesType::MoveHandles) {
ARROW_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, ARROW_MESH->vertexCount);
} else {
@ -319,7 +319,7 @@ void renderHandles() {
identityShader->set("aColor", glm::vec3(0.f, 1.f, 1.f));
for (auto face : HandleFace::Faces) {
CFrame cframe = editorToolHandles->GetCFrameOfHandle(face);
CFrame cframe = getCFrameOfHandle(face);
glm::vec4 screenPos = projection * view * glm::vec4((glm::vec3)cframe.Position(), 1.0f);
if (screenPos.z < 0) continue;
@ -458,7 +458,7 @@ void renderOutlines() {
}
void renderRotationArcs() {
if (editorToolHandles->adornee.expired() || editorToolHandles->handlesType != HandlesType::RotateHandles) return;
if (!editorToolHandles.active || editorToolHandles.handlesType != HandlesType::RotateHandles) return;
glDepthMask(GL_TRUE);
glEnable(GL_CULL_FACE);

View file

@ -5,6 +5,7 @@
#include <qsoundeffect.h>
#include <string>
#include "mainglwidget.h"
#include "handles.h"
#include "logger.h"
#include "mainwindow.h"
#include "common.h"
@ -171,21 +172,21 @@ inline glm::vec3 vec3fy(glm::vec4 vec) {
// Taken from Godot's implementation of moving handles (godot/editor/plugins/gizmos/gizmo_3d_helper.cpp)
void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
if (!isMouseDragging || !draggingHandle|| editorToolHandles->adornee.expired() || !editorToolHandles->active) return;
if (!isMouseDragging || !draggingHandle|| !editorToolHandles.active) return;
QPoint position = evt->pos();
auto part = editorToolHandles->adornee.lock();
auto part = getHandleAdornee();
// This was actually quite a difficult problem to solve, managing to get the handle to go underneath the cursor
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
pointDir = glm::normalize(pointDir);
CFrame handleCFrame = editorToolHandles->GetCFrameOfHandle(draggingHandle.value());
CFrame handleCFrame = getCFrameOfHandle(draggingHandle.value());
// Current frame. Identity frame if worldMode == true, selected object's frame if worldMode == false
CFrame frame = editorToolHandles->worldMode ? CFrame::IDENTITY + part->position() : part->cframe.Rotation();
CFrame frame = editorToolHandles.worldMode ? CFrame::IDENTITY + part->position() : part->cframe.Rotation();
// Segment from axis stretching -4096 to +4096 rel to handle's position
glm::vec3 axisSegment0 = handleCFrame.Position() + (-handleCFrame.LookVector() * 4096.0f);
@ -200,7 +201,7 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
get_closest_points_between_segments(axisSegment0, axisSegment1, mouseSegment0, mouseSegment1, handlePoint, rb);
// Find new part position
glm::vec3 centerPoint = editorToolHandles->PartCFrameFromHandlePos(draggingHandle.value(), handlePoint).Position();
glm::vec3 centerPoint = partCFrameFromHandlePos(draggingHandle.value(), handlePoint).Position();
// Apply snapping in the current frame
glm::vec3 diff = centerPoint - (glm::vec3)part->position();
@ -258,10 +259,10 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
// Also implemented based on Godot: [c7ea8614](godot/editor/plugins/canvas_item_editor_plugin.cpp#L1490)
glm::vec2 startPoint;
void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
if (!isMouseDragging || !draggingHandle || editorToolHandles->adornee.expired() || !editorToolHandles->active) return;
if (!isMouseDragging || !draggingHandle || !editorToolHandles.active) return;
glm::vec2 destPoint = glm::vec2(evt->pos().x(), evt->pos().y());
auto part = editorToolHandles->adornee.lock();
auto part = getHandleAdornee();
// Calculate part pos as screen point
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)width() / (float)height(), 0.1f, 1000.0f);
@ -299,8 +300,8 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
}
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
if (editorToolHandles->adornee.expired() || !editorToolHandles->active) return std::nullopt;
return editorToolHandles->RaycastHandle(rp3d::Ray(glmToRp(camera.cameraPos), glmToRp(glm::normalize(pointDir)) * 50000));
if (!editorToolHandles.active) return std::nullopt;
return ::raycastHandle(rp3d::Ray(glmToRp(camera.cameraPos), glmToRp(glm::normalize(pointDir)) * 50000));
}
void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
@ -363,7 +364,7 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
auto handle = raycastHandle(pointDir);
if (handle.has_value()) {
startPoint = glm::vec2(evt->pos().x(), evt->pos().y());
initialFrame = editorToolHandles->adornee.lock()->cframe;
initialFrame = getHandleAdornee()->cframe;
isMouseDragging = true;
draggingHandle = handle;
return;

View file

@ -100,16 +100,6 @@ MainWindow::MainWindow(QWidget *parent)
});
connectActionHandlers();
// Update handles
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
editorToolHandles->adornee = {};
if (newSelection.size() == 0) return;
InstanceRef inst = newSelection[0].lock();
if (inst->GetClass() != &Part::TYPE) return;
editorToolHandles->adornee = std::dynamic_pointer_cast<Part>(inst);
});
// Update properties
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
@ -456,11 +446,11 @@ void MainWindow::updateToolbars() {
ui->actionGridSnap05->setChecked(snappingMode == GridSnappingMode::SNAP_05_STUDS);
ui->actionGridSnapOff->setChecked(snappingMode == GridSnappingMode::SNAP_OFF);
editorToolHandles->worldMode = (selectedTool == TOOL_SCALE || selectedTool == TOOL_ROTATE) ? false : worldSpaceTransforms;
editorToolHandles->nixAxes = selectedTool == TOOL_ROTATE;
editorToolHandles.worldMode = (selectedTool == TOOL_SCALE || selectedTool == TOOL_ROTATE) ? false : worldSpaceTransforms;
editorToolHandles.nixAxes = selectedTool == TOOL_ROTATE;
editorToolHandles->active = selectedTool > TOOL_SELECT && selectedTool < TOOL_SMOOTH;
editorToolHandles->handlesType =
editorToolHandles.active = selectedTool > TOOL_SELECT && selectedTool < TOOL_SMOOTH;
editorToolHandles.handlesType =
selectedTool == TOOL_MOVE ? HandlesType::MoveHandles
: selectedTool == TOOL_SCALE ? HandlesType::ScaleHandles
: selectedTool == TOOL_ROTATE ? HandlesType::RotateHandles