Compare commits

...

7 commits

9 changed files with 116 additions and 28 deletions

View file

@ -3,6 +3,7 @@
#include "physics/util.h"
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/matrix.hpp>
#include <reactphysics3d/mathematics/Transform.h>
#define GLM_ENABLE_EXPERIMENTAL
@ -83,7 +84,7 @@ Data::Vector3 Data::CFrame::ToEulerAnglesXYZ() {
Data::CFrame Data::CFrame::FromEulerAnglesXYZ(Data::Vector3 vector) {
glm::mat3 mat = glm::eulerAngleXYZ(vector.X(), vector.Y(), vector.Z());
return Data::CFrame(Data::Vector3::ZERO, glm::column(mat, 2), (Data::Vector3)glm::column(mat, 1)); // Getting LookAt (3rd) and Up (2nd) vectors
return Data::CFrame((glm::vec3)Data::Vector3::ZERO, mat);
}
Data::CFrame Data::CFrame::Inverse() const {

View file

@ -51,3 +51,7 @@ namespace Data {
bool operator ==(Data::Vector3) const;
};
}
inline void printVec(Data::Vector3 vec) {
printf("(%f, %f, %f)\n", vec.X(), vec.Y(), vec.Z());
}

View file

@ -62,7 +62,7 @@ void DataModel::SaveToFile(std::optional<std::string> path) {
doc.save(outStream);
currentFile = target;
name = target;
Logger::info("Place saved succesfully");
Logger::info("Place saved successfully");
}
void DataModel::DeserializeService(pugi::xml_node* node) {

View file

@ -16,6 +16,8 @@ HandleFace HandleFace::ZPos(4, glm::vec3(0,0,1));
HandleFace HandleFace::ZNeg(5, glm::vec3(0,0,-1));
std::array<HandleFace, 6> HandleFace::Faces { HandleFace::XPos, HandleFace::XNeg, HandleFace::YPos, HandleFace::YNeg, HandleFace::ZPos, HandleFace::ZNeg };
static Data::CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
// Shitty solution
static rp3d::PhysicsCommon common;
static rp3d::PhysicsWorld* world = common.createPhysicsWorld();
@ -39,14 +41,18 @@ Data::CFrame Handles::GetCFrameOfHandle(HandleFace face) {
Data::CFrame localFrame = worldMode ? Data::CFrame::IDENTITY + adornee->lock()->position() : adornee->lock()->cframe;
Data::Vector3 handleNormal = face.normal;
if (nixAxes)
handleNormal = XYZToZXY * face.normal;
// We don't want this to align with local * face.normal, or else we have problems.
glm::vec3 upAxis(0, 0, 1);
if (glm::abs(glm::dot(glm::vec3(localFrame.Rotation() * face.normal), upAxis)) > 0.9999f)
if (glm::abs(glm::dot(glm::vec3(localFrame.Rotation() * handleNormal), upAxis)) > 0.9999f)
upAxis = glm::vec3(0, 1, 0);
Data::Vector3 handleOffset = this->worldMode ? ((Data::Vector3::ONE * 2.f) + adornee->lock()->GetAABB() * 0.5f) : Data::Vector3(2.f + adornee->lock()->size * 0.5f);
Data::Vector3 handlePos = localFrame * (handleOffset * face.normal);
Data::CFrame cframe(handlePos, handlePos + localFrame.Rotation() * -face.normal, upAxis);
Data::Vector3 handlePos = localFrame * (handleOffset * handleNormal);
Data::CFrame cframe(handlePos, handlePos + localFrame.Rotation() * -handleNormal, upAxis);
return cframe;
}

View file

@ -34,6 +34,7 @@ class Handles : public Instance {
public:
const static InstanceType TYPE;
bool nixAxes = false; // XYZ -> ZXY, used with rotation
bool active;
std::optional<std::weak_ptr<Part>> adornee;
HandlesType handlesType;

View file

@ -10,12 +10,14 @@
#include <glm/geometric.hpp>
#include <glm/gtc/round.hpp>
#include <glm/matrix.hpp>
#include <glm/trigonometric.hpp>
#include <memory>
#include <optional>
#include <reactphysics3d/collision/RaycastInfo.h>
#include <vector>
#include "datatypes/cframe.h"
#include "datatypes/vector.h"
#include "editorcommon.h"
#include "logger.h"
#include "mainwindow.h"
@ -33,9 +35,10 @@
#include "rendering/shader.h"
#include "mainglwidget.h"
#include "../core/src/rendering/defaultmeshes.h"
#include "math_helper.h"
static Data::CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent) {
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
setMouseTracking(true);
@ -141,17 +144,18 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
Data::CFrame targetFrame = partFromBody(rayHit->body)->cframe;
Data::Vector3 surfaceNormal = targetFrame.Inverse().Rotation() * rayHit->worldNormal;
// The part being dragged's frame local to the hit target's frame, but without its position component
Data::CFrame localFrame = (targetFrame.Inverse() * draggingObject->lock()->cframe).Rotation();
// To find a world vector local to the new frame, use newFrame, not localFrame, as localFrame is localFrame is local to targetFrame in itself
Data::CFrame localFrame = (targetFrame.Inverse() * (draggingObject->lock()->cframe.Rotation() + vec));
// Snap axis
localFrame = snapCFrame(localFrame);
Data::CFrame newFrame = targetFrame * localFrame;
// Unsink the object
// Get the normal of the surface relative to the part's frame, and get the size along that vector
Data::Vector3 partOffset = localFrame * ((localFrame.Inverse() * rayHit->worldNormal) * draggingObject->lock()->size / 2);
Data::Vector3 unsinkOffset = newFrame.Rotation() * ((newFrame.Rotation().Inverse() * rayHit->worldNormal) * draggingObject->lock()->size / 2);
Data::CFrame newFrame = targetFrame.Rotation() * localFrame;
draggingObject->lock()->cframe = newFrame + vec + partOffset;
draggingObject->lock()->cframe = newFrame + unsinkOffset;
syncPartPhysics(draggingObject->lock());
}
@ -160,11 +164,8 @@ inline glm::vec3 vec3fy(glm::vec4 vec) {
return vec / vec.w;
}
QPoint lastPoint;
void MainGLWidget::handleHandleDrag(QMouseEvent* evt) {
QPoint cLastPoint = lastPoint;
lastPoint = evt->pos();
// 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 || !editorToolHandles->active) return;
QPoint position = evt->pos();
@ -203,7 +204,6 @@ void MainGLWidget::handleHandleDrag(QMouseEvent* evt) {
// printf("Post-snap: (%f, %f, %f)\n", diff.x, diff.y, diff.z);
switch (mainWindow()->selectedTool) {
case SelectedTool::SELECT: break;
case SelectedTool::MOVE: {
// Add difference
editorToolHandles->adornee->lock()->cframe = editorToolHandles->adornee->lock()->cframe + diff;
@ -227,14 +227,58 @@ void MainGLWidget::handleHandleDrag(QMouseEvent* evt) {
part->cframe = part->cframe + diff * 0.5f;
} break;
case SelectedTool::ROTATE: {
// TODO: Implement rotation
} break;
default:
Logger::error("Invalid tool was set to be handled by handleLinearTransform\n");
}
syncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock()));
}
// Also implemented based on Godot: [c7ea8614](godot/editor/plugins/canvas_item_editor_plugin.cpp#L1490)
glm::vec2 startPoint;
QPoint _lastPoint;
void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
QPoint lastPoint = _lastPoint;
_lastPoint = evt->pos();
glm::vec2 startPoint(lastPoint.x(), lastPoint.y());
if (!isMouseDragging || !draggingHandle || !editorToolHandles->adornee || !editorToolHandles->active) return;
glm::vec2 destPoint = glm::vec2(evt->pos().x(), evt->pos().y());
auto part = editorToolHandles->adornee->lock();
// Calculate part pos as screen point
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)width() / (float)height(), 0.1f, 1000.0f);
glm::mat4 view = camera.getLookAt();
glm::vec4 screenPos = projection * view * glm::vec4((glm::vec3)part->cframe.Position(), 1.f);
screenPos /= screenPos.w * 2;
screenPos += 0.5f;
screenPos = glm::vec4(screenPos.x, 1-screenPos.y, 0, 0);
screenPos *= glm::vec4(width(), height(), 0, 0);
// https://wumbo.net/formulas/angle-between-two-vectors-2d/
glm::vec2 initVec = glm::normalize(startPoint - (glm::vec2)screenPos);
glm::vec2 destVec = glm::normalize(destPoint - (glm::vec2)screenPos);
float angle = atan2f(initVec.x * destVec.y - initVec.y * destVec.x, initVec.x * destVec.x + initVec.y * destVec.y);
// Yes, it's messy. But it works so I don't wanna hear it.
// Maybe I'll clean it up next week.
// TODO: ?
glm::vec4 pos1 = projection * view * glm::vec4((glm::vec3)part->cframe.Position(), 1.f);
pos1 /= pos1.w;
glm::vec4 otherVec = projection * view * glm::vec4((glm::vec3)(part->cframe * glm::abs(draggingHandle->normal)), 1.f);
otherVec /= otherVec.w;
glm::vec4 signVec = glm::normalize(otherVec - pos1);
float sign = glm::sign(signVec.z);
glm::vec3 angles = glm::abs(draggingHandle->normal) * -sign * glm::vec3(angle);
part->cframe = part->cframe * Data::CFrame::FromEulerAnglesXYZ(-angles);
syncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock()));
}
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
if (!editorToolHandles->adornee.has_value() || !editorToolHandles->active) return std::nullopt;
return editorToolHandles->RaycastHandle(rp3d::Ray(glmToRp(camera.cameraPos), glmToRp(glm::normalize(pointDir)) * 50000));
@ -262,8 +306,19 @@ void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
handleCameraRotate(evt);
handleObjectDrag(evt);
handleHandleDrag(evt);
handleCursorChange(evt);
switch (mainWindow()->selectedTool) {
case SelectedTool::MOVE:
case SelectedTool::SCALE:
handleLinearTransform(evt);
break;
case SelectedTool::ROTATE:
handleRotationalTransform(evt);
break;
default:
break;
}
}
void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
@ -281,6 +336,7 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
// raycast handles
auto handle = raycastHandle(pointDir);
if (handle.has_value()) {
startPoint = glm::vec2(evt->pos().x(), evt->pos().y());
isMouseDragging = true;
draggingHandle = handle;
return;

View file

@ -23,7 +23,8 @@ protected:
void handleCameraRotate(QMouseEvent* evt);
void handleObjectDrag(QMouseEvent* evt);
void handleHandleDrag(QMouseEvent* evt);
void handleLinearTransform(QMouseEvent* evt);
void handleRotationalTransform(QMouseEvent* evt);
void handleCursorChange(QMouseEvent* evt);
std::optional<HandleFace> raycastHandle(glm::vec3 pointDir);

View file

@ -118,14 +118,21 @@ MainWindow::MainWindow(QWidget *parent)
std::optional<std::string> path;
if (!dataModel->HasFile())
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + dataModel->name));
if (path == "") return;
if (!path || path == "") return;
dataModel->SaveToFile(path);
});
connect(ui->actionSaveAs, &QAction::triggered, this, [&]() {
std::optional<std::string> path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save as " + dataModel->name));
if (!path || path == "") return;
dataModel->SaveToFile(path);
});
connect(ui->actionOpen, &QAction::triggered, this, [&]() {
std::optional<std::string> path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptOpen);
if (!path) return;
if (!path || path == "") return;
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value());
dataModel = newModel;
ui->explorerView->updateRoot(newModel);
@ -316,11 +323,8 @@ void MainWindow::updateToolbars() {
ui->actionGridSnap05->setChecked(snappingMode == GridSnappingMode::SNAP_05_STUDS);
ui->actionGridSnapOff->setChecked(snappingMode == GridSnappingMode::SNAP_OFF);
// editorToolHandles->worldMode = false;
if (selectedTool == SelectedTool::SCALE)
editorToolHandles->worldMode = false;
else
editorToolHandles->worldMode = worldSpaceTransforms;
editorToolHandles->worldMode = selectedTool == SelectedTool::SCALE ? false : worldSpaceTransforms;
editorToolHandles->nixAxes = selectedTool == SelectedTool::ROTATE;
editorToolHandles->active = selectedTool != SelectedTool::SELECT;
editorToolHandles->handlesType =

View file

@ -51,6 +51,7 @@
<addaction name="actionNew"/>
<addaction name="actionOpen"/>
<addaction name="actionSave"/>
<addaction name="actionSaveAs"/>
<addaction name="separator"/>
<addaction name="actionQuit"/>
</widget>
@ -480,6 +481,20 @@
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionSaveAs">
<property name="icon">
<iconset theme="document-save-as"/>
</property>
<property name="text">
<string>Save As...</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+S</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
</widget>
<customwidgets>
<customwidget>