fix: smoother scaling and moving

This commit is contained in:
maelstrom 2025-02-23 13:41:28 +01:00
parent fd1037d76a
commit 32964df4c3
8 changed files with 201 additions and 16 deletions

5
.gitignore vendored
View file

@ -13,4 +13,7 @@ CMakeLists.txt.user*
# Clangd
/compile_commands.json
/.cache
/.cache
# Gdb
.gdb_history

View file

@ -33,7 +33,7 @@ Data::CFrame::CFrame(const rp::Transform& transform) : Data::CFrame::CFrame(rpTo
}
glm::mat3 lookAt(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3 up) {
// https://github.com/sgorsten/glm/issues/29#issuecomment-743989030
// https://github.com/sgorsten/linalg/issues/29#issuecomment-743989030
Data::Vector3 f = (lookAt - position).Unit(); // Forward/Look
Data::Vector3 u = up.Unit(); // Up
Data::Vector3 s = f.Cross(u).Unit(); // Right

103
core/src/math_helper.cpp Normal file
View file

@ -0,0 +1,103 @@
#include "math_helper.h"
#define CMP_EPSILON 0.00001
// After a long time researching, I was able to use and adapt Godot's implementation of movable handles (godot/editor/plugins/gizmos/gizmo_3d_helper.cpp)
// All thanks goes to them and David Eberly for his algorithm.
void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3 &p_p1, const glm::vec3 &p_q0, const glm::vec3 &p_q1, glm::vec3 &r_ps, glm::vec3 &r_qt) {
// Based on David Eberly's Computation of Distance Between Line Segments algorithm.
glm::vec3 p = p_p1 - p_p0;
glm::vec3 q = p_q1 - p_q0;
glm::vec3 r = p_p0 - p_q0;
float a = glm::dot(p, p);
float b = glm::dot(p, q);
float c = glm::dot(q, q);
float d = glm::dot(p, r);
float e = glm::dot(q, r);
float s = 0.0f;
float t = 0.0f;
float det = a * c - b * b;
if (det > CMP_EPSILON) {
// Non-parallel segments
float bte = b * e;
float ctd = c * d;
if (bte <= ctd) {
// s <= 0.0f
if (e <= 0.0f) {
// t <= 0.0f
s = (-d >= a ? 1 : (-d > 0.0f ? -d / a : 0.0f));
t = 0.0f;
} else if (e < c) {
// 0.0f < t < 1
s = 0.0f;
t = e / c;
} else {
// t >= 1
s = (b - d >= a ? 1 : (b - d > 0.0f ? (b - d) / a : 0.0f));
t = 1;
}
} else {
// s > 0.0f
s = bte - ctd;
if (s >= det) {
// s >= 1
if (b + e <= 0.0f) {
// t <= 0.0f
s = (-d <= 0.0f ? 0.0f : (-d < a ? -d / a : 1));
t = 0.0f;
} else if (b + e < c) {
// 0.0f < t < 1
s = 1;
t = (b + e) / c;
} else {
// t >= 1
s = (b - d <= 0.0f ? 0.0f : (b - d < a ? (b - d) / a : 1));
t = 1;
}
} else {
// 0.0f < s < 1
float ate = a * e;
float btd = b * d;
if (ate <= btd) {
// t <= 0.0f
s = (-d <= 0.0f ? 0.0f : (-d >= a ? 1 : -d / a));
t = 0.0f;
} else {
// t > 0.0f
t = ate - btd;
if (t >= det) {
// t >= 1
s = (b - d <= 0.0f ? 0.0f : (b - d >= a ? 1 : (b - d) / a));
t = 1;
} else {
// 0.0f < t < 1
s /= det;
t /= det;
}
}
}
}
} else {
// Parallel segments
if (e <= 0.0f) {
s = (-d <= 0.0f ? 0.0f : (-d >= a ? 1 : -d / a));
t = 0.0f;
} else if (e >= c) {
s = (b - d <= 0.0f ? 0.0f : (b - d >= a ? 1 : (b - d) / a));
t = 1;
} else {
s = 0.0f;
t = e / c;
}
}
r_ps = (1 - s) * p_p0 + s * p_p1;
r_qt = (1 - t) * p_q0 + t * p_q1;
}

5
core/src/math_helper.h Normal file
View file

@ -0,0 +1,5 @@
#pragma once
#include <glm/glm.hpp>
// From godot/editor/plugins/gizmos/gizmo_3d_helper.h
void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3 &p_p1, const glm::vec3 &p_q0, const glm::vec3 &p_q1, glm::vec3 &r_ps, glm::vec3 &r_qt);

View file

@ -42,6 +42,15 @@ Data::CFrame Handles::GetCFrameOfHandle(HandleFace face) {
return adornee->lock()->cframe + glm::vec3(glm::mat4(adornee->lock()->cframe.Rotation()) * glm::vec4((adornee->lock()->size * 0.5f * face.normal) + face.normal * 5.f, 0));
}
Data::CFrame Handles::PartCFrameFromHandlePos(HandleFace face, Data::Vector3 newPos) {
if (!adornee.has_value() || adornee->expired()) return Data::CFrame(glm::vec3(0,0,0), (Data::Vector3)glm::vec3(0,0,0));
// return adornee->lock()->cframe + face.normal * 5.f;
if (worldMode)
return adornee->lock()->cframe.Rotation() + newPos - (adornee->lock()->size * 0.5f * face.normal) - face.normal * 5.f;
return adornee->lock()->cframe.Rotation() + newPos - glm::vec3(glm::mat4(adornee->lock()->cframe.Rotation()) * glm::vec4((adornee->lock()->size * 0.5f * face.normal) + face.normal * 5.f, 0));
}
std::optional<HandleFace> Handles::RaycastHandle(rp3d::Ray ray) {
for (HandleFace face : HandleFace::Faces) {
Data::CFrame cframe = GetCFrameOfHandle(face);

View file

@ -37,6 +37,7 @@ public:
// World-space handles vs local-space handles
bool worldMode = false;
Data::CFrame GetCFrameOfHandle(HandleFace face);
Data::CFrame PartCFrameFromHandlePos(HandleFace face, Data::Vector3 newPos);
std::optional<HandleFace> RaycastHandle(rp3d::Ray ray);
static inline std::shared_ptr<Handles> New() { return std::make_shared<Handles>(); };

View file

@ -114,6 +114,7 @@ void renderParts() {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
glm::mat4 model = part->cframe;
if (inst->name == "camera") model = camera.getLookAt();
model = glm::scale(model, part->size);
shader->set("model", model);
shader->set("material", Material {
@ -196,8 +197,8 @@ void render(GLFWwindow* window) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderSkyBox();
renderParts();
renderHandles();
renderParts();
}
void setViewport(int width, int height) {

View file

@ -3,6 +3,7 @@
#include <QMouseEvent>
#include <glm/ext/matrix_projection.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/ext/vector_float3.hpp>
#include <glm/geometric.hpp>
#include <glm/matrix.hpp>
@ -11,7 +12,9 @@
#include <reactphysics3d/collision/RaycastInfo.h>
#include <vector>
#include "datatypes/cframe.h"
#include "editorcommon.h"
#include "objects/handles.h"
#include "physics/util.h"
#include "qcursor.h"
#include "qevent.h"
@ -25,6 +28,8 @@
#include "rendering/shader.h"
#include "mainglwidget.h"
#include "../core/src/rendering/defaultmeshes.h"
#include "math_helper.h"
MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent) {
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
@ -55,8 +60,47 @@ glm::vec2 firstPoint;
glm::vec2 secondPoint;
extern std::optional<std::weak_ptr<Part>> draggingObject;
extern std::optional<HandleFace> draggingHandle;
extern Shader* shader;
void MainGLWidget::paintGL() {
::render(NULL);
if (!editorToolHandles->adornee) return;
for (HandleFace face : HandleFace::Faces) {
// Ignore negatives (for now)
if (face.normal != glm::abs(face.normal)) continue;
glm::vec3 axisNormal = face.normal;
// // glm::vec3 planeNormal = camera.cameraFront;
glm::vec3 planeRight = glm::cross(axisNormal, glm::normalize(camera.cameraFront));
glm::vec3 planeNormal = glm::cross(glm::normalize(planeRight), axisNormal);
auto a = axisNormal;
auto b = planeRight;
auto c = planeNormal;
glm::mat3 matrix = {
axisNormal,
planeRight,
-planeNormal,
};
glm::mat4 model = glm::translate(glm::mat4(1.0f), (glm::vec3)editorToolHandles->adornee->lock()->position()) * glm::mat4(matrix);
model = glm::scale(model, glm::vec3(5, 5, 0.2));
shader->set("model", model);
shader->set("material", Material {
.diffuse = glm::vec3(0.5, 1.f, 0.5),
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 16.0f,
});
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
shader->set("normalMatrix", normalMatrix);
CUBE_MESH->bind();
// glDrawArrays(GL_TRIANGLES, 0, 36);
}
}
bool isMouseRightDragging = false;
@ -90,6 +134,10 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
syncPartPhysics(draggingObject->lock());
}
inline glm::vec3 vec3fy(glm::vec4 vec) {
return vec / vec.w;
}
QPoint lastPoint;
void MainGLWidget::handleHandleDrag(QMouseEvent* evt) {
QPoint cLastPoint = lastPoint;
@ -97,24 +145,39 @@ void MainGLWidget::handleHandleDrag(QMouseEvent* evt) {
if (!isMouseDragging || !draggingHandle || !editorToolHandles->adornee) return;
// https://stackoverflow.com/a/57380616/16255372
QPoint position = evt->pos();
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)width() / (float)height(), 0.1f, 100.0f);
glm::mat4 view = camera.getLookAt();
glm::mat4 worldToScreen = projection * view;
glm::vec4 a = worldToScreen * glm::vec4((glm::vec3)editorToolHandles->adornee->lock()->position(), 1);
glm::vec4 b = worldToScreen * glm::vec4((glm::vec3)editorToolHandles->adornee->lock()->position() + draggingHandle->normal, 1);
glm::vec2 screenDir = b / b.w - a / a.w;
QPoint mouseDelta_ = evt->pos() - cLastPoint;
glm::vec2 mouseDelta(mouseDelta_.x(), mouseDelta_.y() * -1.f);
float changeBy = glm::dot(mouseDelta, screenDir);
// 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);
Data::CFrame handleCFrame = editorToolHandles->GetCFrameOfHandle(draggingHandle.value());
// Segment from axis stretching -4096 to +4096 rel to handle's position
glm::vec3 axisSegment0 = handleCFrame.Position() + glm::abs(draggingHandle->normal) * 4096.0f;
glm::vec3 axisSegment1 = handleCFrame.Position() + glm::abs(draggingHandle->normal) * -4096.0f;
// Segment from camera stretching 4096 forward
glm::vec3 mouseSegment0 = camera.cameraPos;
glm::vec3 mouseSegment1 = camera.cameraPos + pointDir * 4096.0f;
// Closest point on the axis segment between the two segments
glm::vec3 handlePoint, rb;
get_closest_points_between_segments(axisSegment0, axisSegment1, mouseSegment0, mouseSegment1, handlePoint, rb);
if (selectedTool == SelectedTool::MOVE)
editorToolHandles->adornee->lock()->cframe = editorToolHandles->adornee->lock()->cframe + draggingHandle->normal * changeBy;
editorToolHandles->adornee->lock()->cframe = editorToolHandles->PartCFrameFromHandlePos(draggingHandle.value(), handlePoint);
else if (selectedTool == SelectedTool::SCALE) {
if (!(evt->modifiers() & Qt::ControlModifier)) editorToolHandles->adornee->lock()->cframe = editorToolHandles->adornee->lock()->cframe + draggingHandle->normal * changeBy * 0.5f;
editorToolHandles->adornee->lock()->size += glm::abs(draggingHandle->normal) * changeBy;
glm::vec3 handlePos = editorToolHandles->PartCFrameFromHandlePos(draggingHandle.value(), handlePoint).Position();
// Find change in handles, and negate difference in sign between axes
glm::vec3 diff = handlePos - glm::vec3(editorToolHandles->adornee->lock()->position());
editorToolHandles->adornee->lock()->size += diff * glm::sign(draggingHandle->normal);
// If alt is not pressed, also reposition the part such that only the dragged side gets lengthened
if (!(evt->modifiers() & Qt::ControlModifier))
editorToolHandles->adornee->lock()->cframe = editorToolHandles->adornee->lock()->cframe + (diff / 2.0f);
}
syncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock()));