feat: basic moving and resizing support

This commit is contained in:
maelstrom 2025-02-18 22:50:16 +01:00
parent 81d172900b
commit 64fa46d496
13 changed files with 404 additions and 21 deletions

109
assets/shaders/handle.fs Normal file
View file

@ -0,0 +1,109 @@
#version 330 core
// Implements the Phong lighting model with respect to materials and lighting materials
// Structs
struct Material {
vec3 diffuse;
vec3 specular;
float shininess;
};
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
struct PointLight {
vec3 position;
// vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
// I/O
in vec3 vPos;
in vec3 vNormal;
in vec2 vTexCoords;
out vec4 FragColor;
#define NR_POINT_LIGHTS 4
uniform vec3 viewPos;
uniform PointLight pointLights[NR_POINT_LIGHTS];
uniform int numPointLights;
uniform DirLight sunLight;
uniform Material material;
// Functions
vec3 calculateDirectionalLight(DirLight light);
vec3 calculatePointLight(PointLight light);
// Main
void main() {
vec3 result = vec3(0.0);
result += calculateDirectionalLight(sunLight);
for (int i = 0; i < numPointLights; i++) {
result += calculatePointLight(pointLights[i]);
}
FragColor = vec4(result, 1);
}
vec3 calculateDirectionalLight(DirLight light) {
// Calculate diffuse
vec3 norm = normalize(vNormal);
vec3 lightDir = normalize(-light.direction);
float diff = max(dot(norm, lightDir), 0.0);
// Calculate specular
vec3 viewDir = normalize(viewPos - vPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 ambient = light.ambient * material.diffuse;
vec3 diffuse = light.diffuse * diff * material.diffuse;
vec3 specular = light.specular * spec * material.specular;
return (ambient + diffuse + specular);
}
vec3 calculatePointLight(PointLight light) {
// Calculate ambient light
// Calculate diffuse light
vec3 norm = normalize(vNormal);
vec3 lightDir = normalize(light.position - vPos);
float diff = max(dot(norm, lightDir), 0.0);
// Calculate specular
vec3 viewDir = normalize(viewPos - vPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// Calculate attenuation
float distance = length(light.position - vPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
vec3 ambient = light.ambient * material.diffuse;
vec3 diffuse = light.diffuse * diff * material.diffuse;
vec3 specular = light.specular * spec * material.specular;
return (ambient + diffuse + specular) * attenuation;
}

20
assets/shaders/handle.vs Normal file
View file

@ -0,0 +1,20 @@
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
out vec3 vPos;
out vec3 vNormal;
out vec2 vTexCoords;
uniform mat4 model;
uniform mat3 normalMatrix;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
vPos = vec3(model * vec4(aPos, 1.0));
vNormal = normalMatrix * aNormal;
}

View file

@ -0,0 +1,13 @@
#version 330 core
// I/O
out vec4 FragColor;
uniform vec3 aColor;
// Main
void main() {
FragColor = vec4(aColor, 1);
}

View file

@ -0,0 +1,8 @@
#version 330 core
layout (location = 0) in vec3 aPos;
out vec3 vPos;
void main()
{
gl_Position = vec4(aPos, 1.0);
}

View file

@ -8,6 +8,7 @@ Camera camera(glm::vec3(0.0, 0.0, 3.0));
std::shared_ptr<DataModel> dataModel = DataModel::New();
std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
std::shared_ptr<Handles> editorToolHandles = Handles::New();
std::vector<InstanceRefWeak> currentSelection;

View file

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

View file

@ -0,0 +1,62 @@
#include "handles.h"
#include "datatypes/cframe.h"
#include "datatypes/vector.h"
#include <optional>
#include <reactphysics3d/collision/RaycastInfo.h>
#include <reactphysics3d/engine/PhysicsCommon.h>
#include <reactphysics3d/engine/PhysicsWorld.h>
#include <reactphysics3d/mathematics/Transform.h>
HandleFace HandleFace::XPos(0, glm::vec3(1,0,0));
HandleFace HandleFace::XNeg(1, glm::vec3(-1,0,0));
HandleFace HandleFace::YPos(2, glm::vec3(0,1,0));
HandleFace HandleFace::YNeg(3, glm::vec3(0,-1,0));
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 };
// Shitty solution
static rp3d::PhysicsCommon common;
static rp3d::PhysicsWorld* world = common.createPhysicsWorld();
const InstanceType Handles::TYPE = {
.super = &Instance::TYPE,
.className = "Handles",
// .constructor = &Workspace::Create,
// .explorerIcon = "",
};
const InstanceType* Handles::GetClass() {
return &TYPE;
}
Handles::Handles(): Instance(&TYPE) {
}
Data::CFrame Handles::GetCFrameOfHandle(HandleFace face) {
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 + (adornee->lock()->size * 0.5f * face.normal) + face.normal * 5.f;
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));
}
std::optional<HandleFace> Handles::RaycastHandle(rp3d::Ray ray) {
for (HandleFace face : HandleFace::Faces) {
Data::CFrame cframe = GetCFrameOfHandle(face);
// Implement manual detection via boxes instead of... this shit
rp3d::RigidBody* body = world->createRigidBody(cframe);
body->addCollider(common.createBoxShape(Data::Vector3(.5, .5, .5)), rp3d::Transform::identity());
rp3d::RaycastInfo info;
if (body->raycast(ray, info)) {
world->destroyRigidBody(body);
return face;
}
world->destroyRigidBody(body);
}
return std::nullopt;
}

View file

@ -0,0 +1,44 @@
#pragma once
#include "base.h"
#include "datatypes/cframe.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;
};
class Handles : public Instance {
public:
const static InstanceType TYPE;
std::optional<std::weak_ptr<Part>> adornee;
// inline std::optional<std::weak_ptr<Part>> GetAdornee() { return adornee; }
// inline void SetAdornee(std::optional<std::weak_ptr<Part>> newAdornee) { this->adornee = newAdornee; updateAdornee(); };
Handles();
// World-space handles vs local-space handles
bool worldMode = false;
Data::CFrame GetCFrameOfHandle(HandleFace face);
std::optional<HandleFace> RaycastHandle(rp3d::Ray ray);
static inline std::shared_ptr<Handles> New() { return std::make_shared<Handles>(); };
virtual const InstanceType* GetClass() override;
};

View file

@ -6,6 +6,7 @@
#include <reactphysics3d/collision/shapes/BoxShape.h>
#include <reactphysics3d/collision/shapes/CollisionShape.h>
#include <reactphysics3d/components/RigidBodyComponents.h>
#include <reactphysics3d/configuration.h>
#include <reactphysics3d/engine/EventListener.h>
#include <reactphysics3d/engine/PhysicsCommon.h>
#include <reactphysics3d/mathematics/Quaternion.h>
@ -39,6 +40,9 @@ void simulationInit() {
world = physicsCommon->createPhysicsWorld();
world->setGravity(rp::Vector3(0, -196.2, 0));
// world->setContactsPositionCorrectionTechnique(rp3d::ContactsPositionCorrectionTechnique::BAUMGARTE_CONTACTS);
world->setNbIterationsPositionSolver(2000);
world->setNbIterationsVelocitySolver(2000);
world->setEventListener(&eventListener);
}

View file

@ -5,11 +5,13 @@
#include <glm/ext.hpp>
#include <glm/ext/matrix_float4x4.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/ext/vector_float3.hpp>
#include <glm/glm.hpp>
#include <glm/trigonometric.hpp>
#include <memory>
#include <vector>
#include "datatypes/cframe.h"
#include "physics/util.h"
#include "shader.h"
#include "mesh.h"
@ -23,8 +25,9 @@
#include "renderer.h"
Shader *shader = NULL;
Shader *skyboxShader = NULL;
Shader* shader = NULL;
Shader* skyboxShader = NULL;
Shader* handleShader;
extern Camera camera;
Skybox* skyboxTexture = NULL;
Texture3D* studsTexture = NULL;
@ -54,9 +57,10 @@ void renderInit(GLFWwindow* window, int width, int height) {
studsTexture = new Texture3D("assets/textures/studs.png", 128, 128, 6, GL_RGBA);
// Compile shader
// Compile shaders
shader = new Shader("assets/shaders/phong.vs", "assets/shaders/phong.fs");
skyboxShader = new Shader("assets/shaders/skybox.vs", "assets/shaders/skybox.fs");
handleShader = new Shader("assets/shaders/handle.vs", "assets/shaders/handle.fs");
}
void renderParts() {
@ -109,22 +113,6 @@ void renderParts() {
for (InstanceRef inst : workspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
// if (inst->name == "Target") printf("(%f,%f,%f):(%f,%f,%f;%f,%f,%f;%f,%f,%f)\n",
// part->cframe.X(),
// part->cframe.Y(),
// part->cframe.Z(),
// part->cframe.RightVector().X(),
// part->cframe.UpVector().X(),
// part->cframe.LookVector().X(),
// part->cframe.RightVector().Y(),
// part->cframe.UpVector().Y(),
// part->cframe.LookVector().Y(),
// part->cframe.RightVector().Z(),
// part->cframe.UpVector().Z(),
// part->cframe.LookVector().Z()
// );
glm::mat4 model = part->cframe;
model = glm::scale(model, part->size);
shader->set("model", model);
@ -160,12 +148,56 @@ void renderSkyBox() {
glDrawArrays(GL_TRIANGLES, 0, 36);
}
void renderHandles() {
// if (getSelection().size() == 0) return;
// if (getSelection()[0].lock()->GetClass() != &Part::TYPE) return;
if (!editorToolHandles->adornee.has_value()) return;
glDepthMask(GL_TRUE);
// Use shader
handleShader->use();
// view/projection transformations
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)viewportWidth / (float)viewportHeight, 0.1f, 100.0f);
glm::mat4 view = camera.getLookAt();
handleShader->set("projection", projection);
handleShader->set("view", view);
handleShader->set("sunLight", DirLight {
.direction = glm::vec3(-0.2f, -1.0f, -0.3f),
.ambient = glm::vec3(0.2f, 0.2f, 0.2f),
.diffuse = glm::vec3(0.5f, 0.5f, 0.5f),
.specular = glm::vec3(1.0f, 1.0f, 1.0f),
});
handleShader->set("numPointLights", 0);
// Pass in the camera position
handleShader->set("viewPos", camera.cameraPos);
for (auto face : HandleFace::Faces) {
glm::mat4 model = editorToolHandles->GetCFrameOfHandle(face);
model = glm::scale(model, glm::vec3(1,1,1));
handleShader->set("model", model);
handleShader->set("material", Material {
.diffuse = glm::abs(face.normal),
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 16.0f,
});
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
handleShader->set("normalMatrix", normalMatrix);
CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, 36);
}
}
void render(GLFWwindow* window) {
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderSkyBox();
renderParts();
renderHandles();
}
void setViewport(int width, int height) {

View file

@ -11,6 +11,7 @@
#include <reactphysics3d/collision/RaycastInfo.h>
#include <vector>
#include "editorcommon.h"
#include "physics/util.h"
#include "qcursor.h"
#include "qevent.h"
@ -21,6 +22,7 @@
#include "camera.h"
#include "common.h"
#include "rendering/shader.h"
#include "mainglwidget.h"
@ -29,9 +31,13 @@ MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent) {
setMouseTracking(true);
}
Shader* identityShader;
void MainGLWidget::initializeGL() {
glewInit();
renderInit(NULL, width(), height());
identityShader = new Shader("assets/shaders/identity.vs", "assets/shaders/identity.fs");
}
extern int vpx, vpy;
@ -45,6 +51,10 @@ void MainGLWidget::resizeGL(int w, int h) {
setViewport(w, h);
}
glm::vec2 firstPoint;
glm::vec2 secondPoint;
extern std::optional<std::weak_ptr<Part>> draggingObject;
void MainGLWidget::paintGL() {
::render(NULL);
}
@ -62,8 +72,9 @@ void MainGLWidget::handleCameraRotate(QMouseEvent* evt) {
bool isMouseDragging = false;
std::optional<std::weak_ptr<Part>> draggingObject;
std::optional<HandleFace> draggingHandle;
void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
if (!isMouseDragging) return;
if (!isMouseDragging || !draggingObject) return;
QPoint position = evt->pos();
@ -79,17 +90,62 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
syncPartPhysics(draggingObject->lock());
}
QPoint lastPoint;
void MainGLWidget::handleHandleDrag(QMouseEvent* evt) {
QPoint cLastPoint = lastPoint;
lastPoint = evt->pos();
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);
if (selectedTool == SelectedTool::MOVE)
editorToolHandles->adornee->lock()->cframe = editorToolHandles->adornee->lock()->cframe + draggingHandle->normal * changeBy;
else if (selectedTool == SelectedTool::SCALE)
editorToolHandles->adornee->lock()->size += glm::abs(draggingHandle->normal) * changeBy;
syncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock()));
}
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
if (!editorToolHandles->adornee.has_value()) return std::nullopt;
return editorToolHandles->RaycastHandle(rp3d::Ray(glmToRp(camera.cameraPos), glmToRp(glm::normalize(pointDir)) * 50000));
}
void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
QPoint position = evt->pos();
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
if (raycastHandle(pointDir)) {
setCursor(Qt::OpenHandCursor);
return;
};
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000);
setCursor((rayHit && partFromBody(rayHit->body)->name != "Baseplate") ? Qt::OpenHandCursor : Qt::ArrowCursor);
if (rayHit && partFromBody(rayHit->body)->name != "Baseplate") {
setCursor(Qt::OpenHandCursor);
return;
}
setCursor(Qt::ArrowCursor);
}
void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
handleCameraRotate(evt);
handleObjectDrag(evt);
handleHandleDrag(evt);
handleCursorChange(evt);
}
@ -105,6 +161,15 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
QPoint position = evt->pos();
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
// raycast handles
auto handle = raycastHandle(pointDir);
if (handle.has_value()) {
isMouseDragging = true;
draggingHandle = handle;
return;
}
// raycast part
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000);
if (!rayHit || !partFromBody(rayHit->body)) return;
std::shared_ptr<Part> part = partFromBody(rayHit->body);
@ -128,6 +193,7 @@ void MainGLWidget::mouseReleaseEvent(QMouseEvent* evt) {
isMouseRightDragging = false;
isMouseDragging = false;
draggingObject = std::nullopt;
draggingHandle = std::nullopt;
}
static int moveZ = 0;

View file

@ -7,6 +7,8 @@
#include <QWidget>
#include <memory>
class HandleFace;
class MainGLWidget : public QOpenGLWidget {
public:
MainGLWidget(QWidget *parent = nullptr);
@ -20,7 +22,9 @@ protected:
void handleCameraRotate(QMouseEvent* evt);
void handleObjectDrag(QMouseEvent* evt);
void handleHandleDrag(QMouseEvent* evt);
void handleCursorChange(QMouseEvent* evt);
std::optional<HandleFace> raycastHandle(glm::vec3 pointDir);
void mouseMoveEvent(QMouseEvent* evt) override;
void mousePressEvent(QMouseEvent* evt) override;

View file

@ -10,6 +10,7 @@
#include <QWidget>
#include <QTreeView>
#include <QAbstractItemView>
#include <memory>
#include <optional>
#include "common.h"
@ -78,6 +79,10 @@ MainWindow::MainWindow(QWidget *parent)
ConnectSelectionChangeHandler();
});
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
updateSelectedTool();
});
// ui->explorerView->Init(ui);
simulationInit();
@ -134,6 +139,19 @@ void MainWindow::updateSelectedTool() {
ui->actionToolMove->setChecked(selectedTool == SelectedTool::MOVE);
ui->actionToolScale->setChecked(selectedTool == SelectedTool::SCALE);
ui->actionToolRotate->setChecked(selectedTool == SelectedTool::ROTATE);
if (selectedTool == SelectedTool::MOVE) editorToolHandles->worldMode = true;
if (selectedTool == SelectedTool::SCALE) editorToolHandles->worldMode = false;
// This code sucks. A lot
if (selectedTool == SelectedTool::SELECT) return;
if (getSelection().size() == 0) { editorToolHandles->adornee = std::nullopt; return; };
InstanceRef inst = getSelection()[0].lock();
if (inst->GetClass() != &Part::TYPE) { editorToolHandles->adornee = std::nullopt; return; };
editorToolHandles->adornee = std::dynamic_pointer_cast<Part>(inst);
// editorToolHandles->adornee = std::nullopt;
}
MainWindow::~MainWindow()