diff --git a/assets/shaders/handle.fs b/assets/shaders/handle.fs new file mode 100644 index 0000000..07295e7 --- /dev/null +++ b/assets/shaders/handle.fs @@ -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; +} \ No newline at end of file diff --git a/assets/shaders/handle.vs b/assets/shaders/handle.vs new file mode 100644 index 0000000..075cf4d --- /dev/null +++ b/assets/shaders/handle.vs @@ -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; +} diff --git a/assets/shaders/identity.fs b/assets/shaders/identity.fs new file mode 100644 index 0000000..40174ca --- /dev/null +++ b/assets/shaders/identity.fs @@ -0,0 +1,13 @@ +#version 330 core + +// I/O + +out vec4 FragColor; + +uniform vec3 aColor; + +// Main + +void main() { + FragColor = vec4(aColor, 1); +} \ No newline at end of file diff --git a/assets/shaders/identity.vs b/assets/shaders/identity.vs new file mode 100644 index 0000000..9820f3e --- /dev/null +++ b/assets/shaders/identity.vs @@ -0,0 +1,8 @@ +#version 330 core +layout (location = 0) in vec3 aPos; + +out vec3 vPos; +void main() +{ + gl_Position = vec4(aPos, 1.0); +} diff --git a/core/src/common.cpp b/core/src/common.cpp index 61c4be9..33068e2 100644 --- a/core/src/common.cpp +++ b/core/src/common.cpp @@ -8,6 +8,7 @@ Camera camera(glm::vec3(0.0, 0.0, 3.0)); std::shared_ptr dataModel = DataModel::New(); std::optional hierarchyPreUpdateHandler; std::optional hierarchyPostUpdateHandler; +std::shared_ptr editorToolHandles = Handles::New(); std::vector currentSelection; diff --git a/core/src/common.h b/core/src/common.h index 2c341b0..484e96d 100644 --- a/core/src/common.h +++ b/core/src/common.h @@ -1,5 +1,6 @@ #pragma once #include "objects/base/instance.h" +#include "objects/handles.h" #include "objects/workspace.h" #include "camera.h" #include @@ -18,6 +19,7 @@ extern std::shared_ptr dataModel; inline std::shared_ptr workspace() { return std::dynamic_pointer_cast(dataModel->services["Workspace"]); } extern std::optional hierarchyPreUpdateHandler; extern std::optional hierarchyPostUpdateHandler; +extern std::shared_ptr editorToolHandles; void setSelection(std::vector newSelection, bool fromExplorer = false); const std::vector getSelection(); diff --git a/core/src/objects/handles.cpp b/core/src/objects/handles.cpp new file mode 100644 index 0000000..c1b58d6 --- /dev/null +++ b/core/src/objects/handles.cpp @@ -0,0 +1,62 @@ +#include "handles.h" +#include "datatypes/cframe.h" +#include "datatypes/vector.h" +#include +#include +#include +#include +#include + +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::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 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; +} \ No newline at end of file diff --git a/core/src/objects/handles.h b/core/src/objects/handles.h new file mode 100644 index 0000000..ac9860d --- /dev/null +++ b/core/src/objects/handles.h @@ -0,0 +1,44 @@ +#pragma once + +#include "base.h" +#include "datatypes/cframe.h" +#include "objects/base/service.h" +#include "objects/part.h" +#include +#include +#include + +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 Faces; +}; + +class Handles : public Instance { +public: + const static InstanceType TYPE; + + std::optional> adornee; + // inline std::optional> GetAdornee() { return adornee; } + // inline void SetAdornee(std::optional> newAdornee) { this->adornee = newAdornee; updateAdornee(); }; + + Handles(); + + // World-space handles vs local-space handles + bool worldMode = false; + Data::CFrame GetCFrameOfHandle(HandleFace face); + std::optional RaycastHandle(rp3d::Ray ray); + + static inline std::shared_ptr New() { return std::make_shared(); }; + virtual const InstanceType* GetClass() override; +}; \ No newline at end of file diff --git a/core/src/physics/simulation.cpp b/core/src/physics/simulation.cpp index 78d2fd8..693d80a 100644 --- a/core/src/physics/simulation.cpp +++ b/core/src/physics/simulation.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -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); } diff --git a/core/src/rendering/renderer.cpp b/core/src/rendering/renderer.cpp index e311483..cfcd800 100644 --- a/core/src/rendering/renderer.cpp +++ b/core/src/rendering/renderer.cpp @@ -5,11 +5,13 @@ #include #include #include +#include #include #include #include #include +#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 = std::dynamic_pointer_cast(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) { diff --git a/editor/mainglwidget.cpp b/editor/mainglwidget.cpp index f3e51aa..888bfd3 100644 --- a/editor/mainglwidget.cpp +++ b/editor/mainglwidget.cpp @@ -11,6 +11,7 @@ #include #include +#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> draggingObject; void MainGLWidget::paintGL() { ::render(NULL); } @@ -62,8 +72,9 @@ void MainGLWidget::handleCameraRotate(QMouseEvent* evt) { bool isMouseDragging = false; std::optional> draggingObject; +std::optional 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(editorToolHandles->adornee->lock())); +} + +std::optional 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 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 rayHit = castRayNearest(camera.cameraPos, pointDir, 50000); if (!rayHit || !partFromBody(rayHit->body)) return; std::shared_ptr 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; diff --git a/editor/mainglwidget.h b/editor/mainglwidget.h index 4f54c4c..c596f65 100644 --- a/editor/mainglwidget.h +++ b/editor/mainglwidget.h @@ -7,6 +7,8 @@ #include #include +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 raycastHandle(glm::vec3 pointDir); void mouseMoveEvent(QMouseEvent* evt) override; void mousePressEvent(QMouseEvent* evt) override; diff --git a/editor/mainwindow.cpp b/editor/mainwindow.cpp index ed69f38..a53befe 100644 --- a/editor/mainwindow.cpp +++ b/editor/mainwindow.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #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(inst); + + // editorToolHandles->adornee = std::nullopt; } MainWindow::~MainWindow()