fix: smoother scaling and moving
This commit is contained in:
parent
fd1037d76a
commit
32964df4c3
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -13,4 +13,7 @@ CMakeLists.txt.user*
|
|||
|
||||
# Clangd
|
||||
/compile_commands.json
|
||||
/.cache
|
||||
/.cache
|
||||
|
||||
# Gdb
|
||||
.gdb_history
|
|
@ -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
103
core/src/math_helper.cpp
Normal 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
5
core/src/math_helper.h
Normal 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);
|
|
@ -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);
|
||||
|
|
|
@ -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>(); };
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()));
|
||||
|
|
Loading…
Reference in a new issue