refactor(editor): multi-object move support + more handle jank refactor
This commit is contained in:
parent
49f29b6af1
commit
18b12ea1ad
6 changed files with 229 additions and 55 deletions
|
@ -5,6 +5,7 @@ uniform vec3 scale;
|
|||
in vec3 vPos;
|
||||
|
||||
out vec4 fColor;
|
||||
uniform vec3 color;
|
||||
|
||||
void main() {
|
||||
// float thickness = 0.2;
|
||||
|
@ -20,5 +21,6 @@ void main() {
|
|||
// else
|
||||
// fColor = vec4(0);
|
||||
|
||||
fColor = vec4(0.204, 0.584, 0.922, 1);
|
||||
// fColor = vec4(0.204, 0.584, 0.922, 1);
|
||||
fColor = vec4(color, 1);
|
||||
}
|
86
core/src/partassembly.cpp
Normal file
86
core/src/partassembly.cpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
#include "partassembly.h"
|
||||
#include "common.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "math_helper.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/part.h"
|
||||
#include <glm/common.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
PartAssembly::PartAssembly(std::vector<std::shared_ptr<Part>> parts, bool worldMode) : parts(parts) {
|
||||
if (parts.size() == 0) return;
|
||||
if (parts.size() == 1 && !worldMode) {
|
||||
_assemblyOrigin = parts[0]->cframe;
|
||||
_bounds = parts[0]->size;
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 min = parts[0]->position(), max = parts[0]->position();
|
||||
|
||||
for (auto part : parts) {
|
||||
Vector3 aabbSize = part->GetAABB();
|
||||
expandAABB(min, max, part->position() - aabbSize / 2.f);
|
||||
expandAABB(min, max, part->position() + aabbSize / 2.f);
|
||||
}
|
||||
|
||||
glm::vec3 pos, size;
|
||||
getAABBCoords(pos, size, min, max);
|
||||
|
||||
_assemblyOrigin = CFrame() + pos;
|
||||
_bounds = size;
|
||||
}
|
||||
|
||||
PartAssembly PartAssembly::FromSelection(std::vector<std::weak_ptr<Instance>> newSelection) {
|
||||
std::vector<std::shared_ptr<Part>> selection;
|
||||
|
||||
for (std::weak_ptr<Instance> obj : newSelection) {
|
||||
if (obj.expired() || !obj.lock()->IsA<Part>()) continue;
|
||||
|
||||
selection.push_back(obj.lock()->CastTo<Part>().expect());
|
||||
}
|
||||
|
||||
return PartAssembly(selection, editorToolHandles.worldMode);
|
||||
}
|
||||
|
||||
void PartAssembly::SetOrigin(CFrame newOrigin) {
|
||||
for (auto part : parts) {
|
||||
part->cframe = newOrigin * (_assemblyOrigin.Inverse() * part->cframe);
|
||||
part->UpdateProperty("CFrame");
|
||||
}
|
||||
|
||||
_assemblyOrigin = newOrigin;
|
||||
}
|
||||
|
||||
void PartAssembly::TransformBy(CFrame transform) {
|
||||
for (auto part : parts) {
|
||||
part->cframe = transform * part->cframe;
|
||||
part->UpdateProperty("CFrame");
|
||||
}
|
||||
|
||||
_assemblyOrigin = transform * _assemblyOrigin;
|
||||
}
|
||||
|
||||
void PartAssembly::Scale(Vector3 newSize, bool scaleUp) {
|
||||
if (parts.size() == 1) {
|
||||
parts[0]->size = newSize;
|
||||
parts[0]->UpdateProperty("Size");
|
||||
_bounds = newSize;
|
||||
return;
|
||||
}
|
||||
|
||||
float sx = newSize.X() / _bounds.X(), sy = newSize.Y() / _bounds.Y(), sz = newSize.Z() / _bounds.Z();
|
||||
float factor = scaleUp ? glm::max(sx, sy, sz) : glm::min(sx, sy, sz);
|
||||
|
||||
for (auto part : parts) {
|
||||
Vector3 localOff = _assemblyOrigin.Inverse() * part->cframe.Position();
|
||||
localOff = localOff * factor;
|
||||
part->cframe = part->cframe.Rotation() + _assemblyOrigin * localOff;
|
||||
part->UpdateProperty("CFrame");
|
||||
part->size *= factor;
|
||||
part->UpdateProperty("Size");
|
||||
}
|
||||
|
||||
_bounds = _bounds * factor;
|
||||
}
|
35
core/src/partassembly.h
Normal file
35
core/src/partassembly.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "datatypes/cframe.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class Part;
|
||||
class Instance;
|
||||
|
||||
const std::vector<std::weak_ptr<Instance>> getSelection();
|
||||
|
||||
class PartAssembly {
|
||||
CFrame _assemblyOrigin;
|
||||
Vector3 _bounds;
|
||||
|
||||
std::vector<std::shared_ptr<Part>> parts;
|
||||
public:
|
||||
PartAssembly(std::vector<std::shared_ptr<Part>>, bool worldMode = false);
|
||||
|
||||
static PartAssembly FromSelection(std::vector<std::weak_ptr<Instance>> selection = getSelection());
|
||||
|
||||
inline CFrame assemblyOrigin() { return _assemblyOrigin; };
|
||||
inline Vector3 bounds() { return _bounds; };
|
||||
|
||||
// Transforms the assembly such that newOrigin is now this assembly's new assemblyOrigin
|
||||
void SetOrigin(CFrame newOrigin);
|
||||
|
||||
// Rotates and translates the assembly by the transformation
|
||||
void TransformBy(CFrame transform);
|
||||
|
||||
// Scales the assembly to the desired size
|
||||
// If multiple parts are selected, finds the greatest scale factor of each component pair, and
|
||||
// scales it up by that amount
|
||||
void Scale(Vector3 newSize, bool scaleUp = true);
|
||||
};
|
|
@ -16,8 +16,10 @@
|
|||
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/color3.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "handles.h"
|
||||
#include "math_helper.h"
|
||||
#include "partassembly.h"
|
||||
#include "rendering/torus.h"
|
||||
#include "shader.h"
|
||||
#include "mesh.h"
|
||||
|
@ -442,10 +444,11 @@ void renderOutlines() {
|
|||
outlineShader->set("viewPos", camera.cameraPos);
|
||||
outlineShader->set("thickness", 0.4f);
|
||||
|
||||
// outlineShader->set("color", glm::vec3(1.f, 0.f, 0.f));
|
||||
outlineShader->set("color", glm::vec3(0.204, 0.584, 0.922));
|
||||
|
||||
glm::vec3 min, max;
|
||||
bool first = true;
|
||||
int count = 0;
|
||||
|
||||
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
|
||||
InstanceRef inst = *it;
|
||||
|
@ -453,6 +456,7 @@ void renderOutlines() {
|
|||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
if (!part->selected) continue;
|
||||
|
||||
count++;
|
||||
if (first)
|
||||
min = part->position(), max = part->position();
|
||||
first = false;
|
||||
|
@ -471,7 +475,7 @@ void renderOutlines() {
|
|||
}
|
||||
|
||||
// Render AABB of selected parts
|
||||
if (first) return;
|
||||
if (count <= 1) return;
|
||||
|
||||
glm::vec3 outlineSize, outlinePos;
|
||||
outlineSize = (max - min);
|
||||
|
@ -487,6 +491,41 @@ void renderOutlines() {
|
|||
glDrawArrays(GL_TRIANGLES, 0, OUTLINE_MESH->vertexCount);
|
||||
}
|
||||
|
||||
void renderSelectionAssembly() {
|
||||
glDepthMask(GL_TRUE);
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_BACK);
|
||||
glFrontFace(GL_CCW);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
PartAssembly selectionAssembly = PartAssembly::FromSelection();
|
||||
|
||||
// Use shader
|
||||
outlineShader->use();
|
||||
|
||||
// view/projection transformations
|
||||
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)viewportWidth / (float)viewportHeight, 0.1f, 1000.0f);
|
||||
glm::mat4 view = camera.getLookAt();
|
||||
outlineShader->set("projection", projection);
|
||||
outlineShader->set("view", view);
|
||||
|
||||
// Pass in the camera position
|
||||
outlineShader->set("viewPos", camera.cameraPos);
|
||||
outlineShader->set("thickness", 0.4f);
|
||||
|
||||
outlineShader->set("color", glm::vec3(1.f, 0.f, 0.f));
|
||||
|
||||
glm::mat4 model = selectionAssembly.assemblyOrigin();
|
||||
model = glm::scale(model, (glm::vec3)selectionAssembly.bounds() + glm::vec3(0.1));
|
||||
outlineShader->set("model", model);
|
||||
outlineShader->set("scale", (glm::vec3)selectionAssembly.bounds() + glm::vec3(0.05));
|
||||
outlineShader->set("thickness", 0.2f);
|
||||
|
||||
OUTLINE_MESH->bind();
|
||||
glDrawArrays(GL_TRIANGLES, 0, OUTLINE_MESH->vertexCount);
|
||||
}
|
||||
|
||||
void renderRotationArcs() {
|
||||
if (!editorToolHandles.active || editorToolHandles.handlesType != HandlesType::RotateHandles) return;
|
||||
|
||||
|
@ -599,6 +638,7 @@ void render(GLFWwindow* window) {
|
|||
renderParts();
|
||||
renderSurfaceExtras();
|
||||
renderOutlines();
|
||||
// renderSelectionAssembly();
|
||||
renderRotationArcs();
|
||||
if (wireframeRendering)
|
||||
renderWireframe();
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
#include <qsoundeffect.h>
|
||||
#include <string>
|
||||
#include "mainglwidget.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "handles.h"
|
||||
#include "logger.h"
|
||||
#include "mainwindow.h"
|
||||
#include "common.h"
|
||||
#include "math_helper.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "partassembly.h"
|
||||
#include "physics/util.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "rendering/shader.h"
|
||||
|
@ -171,6 +173,7 @@ inline glm::vec3 vec3fy(glm::vec4 vec) {
|
|||
}
|
||||
|
||||
// Taken from Godot's implementation of moving handles (godot/editor/plugins/gizmos/gizmo_3d_helper.cpp)
|
||||
Vector3 dragStartHandleOffset;
|
||||
void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
|
||||
if (!isMouseDragging || !draggingHandle|| !editorToolHandles.active) return;
|
||||
|
||||
|
@ -183,7 +186,9 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
|
|||
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
|
||||
pointDir = glm::normalize(pointDir);
|
||||
|
||||
CFrame handleCFrame = getHandleCFrame(draggingHandle.value());
|
||||
// We use lastDragStartPos instead to consider the mouse's actual position, rather than the center
|
||||
// of the handle. That way, transformations are "smoother" and do not jump the first movement
|
||||
CFrame handleCFrame = getHandleCFrame(draggingHandle.value()) + dragStartHandleOffset;
|
||||
|
||||
// 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();
|
||||
|
@ -200,60 +205,64 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
|
|||
glm::vec3 handlePoint, rb;
|
||||
get_closest_points_between_segments(axisSegment0, axisSegment1, mouseSegment0, mouseSegment1, handlePoint, rb);
|
||||
|
||||
// Find new part position
|
||||
glm::vec3 centerPoint = partCFrameFromHandlePos(draggingHandle.value(), handlePoint).Position();
|
||||
// We transform the handlePoint to the handle's cframe, and get it's length (Z)
|
||||
float diff = (handleCFrame.Inverse() * handlePoint).Z();
|
||||
// Vector3 absDiff = ((Vector3)handlePoint - handleCFrame.Position()); // Commented out because it is functionally identical to the below
|
||||
Vector3 absDiff = handleCFrame.Rotation() * Vector3(0, 0, diff);
|
||||
|
||||
// Apply snapping in the current frame
|
||||
glm::vec3 diff = centerPoint - (glm::vec3)part->position();
|
||||
if (snappingFactor()) diff = frame.Rotation() * (glm::round(glm::vec3(frame.Inverse().Rotation() * diff) / snappingFactor()) * snappingFactor());
|
||||
|
||||
Vector3 oldSize = part->size;
|
||||
|
||||
switch (mainWindow()->selectedTool) {
|
||||
case TOOL_MOVE: {
|
||||
// Add difference
|
||||
part->cframe = part->cframe + diff;
|
||||
} break;
|
||||
|
||||
case TOOL_SCALE: {
|
||||
// Find local difference
|
||||
glm::vec3 localDiff = frame.Inverse() * diff;
|
||||
// Find outwarwd difference
|
||||
localDiff = localDiff * glm::sign(draggingHandle->normal);
|
||||
|
||||
// Special case: minimum size to size mod snapping factor
|
||||
if (snappingFactor() > 0 && glm::all(glm::lessThan((part->size + localDiff) * glm::abs(draggingHandle->normal), glm::vec3(0.01f)))) {
|
||||
// I tried something fancy here, but honestly I'm not smart enough. Return;
|
||||
// glm::vec3 finalSize = part->size + localDiff;
|
||||
// finalSize = glm::mod(finalSize * glm::abs(draggingHandle->normal), snappingFactor()) + finalSize * (glm::vec3(1) - glm::abs(draggingHandle->normal));
|
||||
// localDiff = finalSize - part->size;
|
||||
return;
|
||||
}
|
||||
|
||||
// Minimum size of 0.01f
|
||||
localDiff = glm::max(part->size + localDiff, 0.01f) - part->size;
|
||||
diff = frame * (localDiff * glm::sign(draggingHandle->normal));
|
||||
|
||||
// Add local difference to size
|
||||
part->size += localDiff;
|
||||
|
||||
// If ctrl is not pressed, offset the part by half the size difference to keep the other bound where it was originally
|
||||
if (!(evt->modifiers() & Qt::ControlModifier))
|
||||
part->cframe = part->cframe + diff * 0.5f;
|
||||
} break;
|
||||
|
||||
default:
|
||||
Logger::error("Invalid tool was set to be handled by handleLinearTransform\n");
|
||||
// Apply snapping
|
||||
if (snappingFactor() > 0) {
|
||||
diff = round(diff / snappingFactor()) * snappingFactor();
|
||||
absDiff = handleCFrame.Rotation() * Vector3(0, 0, diff);
|
||||
}
|
||||
|
||||
if (snappingFactor() != 0 && mainWindow()->editSoundEffects && (oldSize != part->size) && QFile::exists("./assets/excluded/switch.wav"))
|
||||
playSound("./assets/excluded/switch.wav");
|
||||
PartAssembly selectionAssembly = PartAssembly::FromSelection();
|
||||
|
||||
gWorkspace()->SyncPartPhysics(part);
|
||||
part->UpdateProperty("Position");
|
||||
part->UpdateProperty("Size");
|
||||
sendPropertyUpdatedSignal(part, "Position", part->position());
|
||||
sendPropertyUpdatedSignal(part, "Size", Vector3(part->size));
|
||||
if (editorToolHandles.handlesType == MoveHandles) {
|
||||
selectionAssembly.TransformBy(CFrame() + absDiff);
|
||||
} else if (editorToolHandles.handlesType == ScaleHandles) {
|
||||
if (evt->modifiers() & Qt::AltModifier) {
|
||||
// If size gets too small, don't
|
||||
if (glm::any(glm::lessThan(glm::vec3(selectionAssembly.bounds() + abs(draggingHandle->normal) * diff * 2.f), glm::vec3(0.001f))))
|
||||
return;
|
||||
|
||||
selectionAssembly.Scale(selectionAssembly.bounds() + abs(draggingHandle->normal) * diff * 2.f, diff > 0);
|
||||
} else {
|
||||
// If size gets too small, don't
|
||||
if (glm::any(glm::lessThan(glm::vec3(selectionAssembly.bounds() + abs(draggingHandle->normal) * diff), glm::vec3(0.001f))))
|
||||
return;
|
||||
|
||||
selectionAssembly.TransformBy(CFrame() + absDiff * 0.5f);
|
||||
selectionAssembly.Scale(selectionAssembly.bounds() + abs(draggingHandle->normal) * diff, diff > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainGLWidget::startLinearTransform(QMouseEvent* evt) {
|
||||
if (!editorToolHandles.active) return;
|
||||
|
||||
QPoint position = evt->pos();
|
||||
|
||||
// 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 = getHandleCFrame(draggingHandle.value());
|
||||
|
||||
// Segment from axis stretching -4096 to +4096 rel to handle's position
|
||||
glm::vec3 axisSegment0 = handleCFrame.Position() + (-handleCFrame.LookVector() * 4096.0f);
|
||||
glm::vec3 axisSegment1 = handleCFrame.Position() + (-handleCFrame.LookVector() * -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);
|
||||
|
||||
dragStartHandleOffset = (Vector3)handlePoint - handleCFrame.Position();
|
||||
}
|
||||
|
||||
// Also implemented based on Godot: [c7ea8614](godot/editor/plugins/canvas_item_editor_plugin.cpp#L1490)
|
||||
|
@ -369,6 +378,7 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
|||
initialFrame = getHandleAdornee()->cframe;
|
||||
isMouseDragging = true;
|
||||
draggingHandle = handle;
|
||||
startLinearTransform(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -501,4 +511,4 @@ float MainGLWidget::snappingFactor() {
|
|||
case GridSnappingMode::SNAP_OFF: return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ protected:
|
|||
void handleLinearTransform(QMouseEvent* evt);
|
||||
void handleRotationalTransform(QMouseEvent* evt);
|
||||
void handleCursorChange(QMouseEvent* evt);
|
||||
void startLinearTransform(QMouseEvent* evt);
|
||||
std::optional<HandleFace> raycastHandle(glm::vec3 pointDir);
|
||||
|
||||
void wheelEvent(QWheelEvent* evt) override;
|
||||
|
|
Loading…
Add table
Reference in a new issue