Compare commits

...

4 commits

13 changed files with 309 additions and 98 deletions

11
assets/shaders/outline.fs Normal file
View file

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

15
assets/shaders/outline.vs Normal file
View file

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

View file

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

View file

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

View file

@ -42,6 +42,7 @@ int main() {
.size = glm::vec3(512, 1.2, 512),
.color = glm::vec3(0.388235, 0.372549, 0.384314),
.anchored = true,
.locked = true,
}));
gWorkspace()->AddChild(lastPart = Part::New({

View file

@ -62,7 +62,7 @@ Part::Part(): Part(PartConstructParams { .color = Data::Color3(0.639216f, 0.6352
}
Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(params.position, params.rotation)),
size(params.size), color(params.color), anchored(params.anchored) {
size(params.size), color(params.color), anchored(params.anchored), locked(params.locked) {
this->memberMap = std::make_unique<MemberMap>(MemberMap {
.super = std::move(this->memberMap),
.members = {
@ -71,6 +71,10 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(par
.type = &Data::Bool::TYPE,
.codec = fieldCodecOf<Data::Bool, bool>(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this)
}}, { "Locked", {
.backingField = &locked,
.type = &Data::Bool::TYPE,
.codec = fieldCodecOf<Data::Bool, bool>(),
}}, { "Position", {
.backingField = &cframe,
.type = &Vector3::TYPE,

View file

@ -20,6 +20,7 @@ struct PartConstructParams {
Data::Color3 color;
bool anchored = false;
bool locked = false;
};
class Part : public Instance {
@ -36,6 +37,7 @@ public:
bool selected = false;
bool anchored = false;
bool locked = false;
rp::RigidBody* rigidBody = nullptr;
SurfaceType topSurface = SurfaceType::SurfaceStuds;

View file

@ -28,10 +28,14 @@ Shader* skyboxShader = NULL;
Shader* handleShader = NULL;
Shader* identityShader = NULL;
Shader* ghostShader = NULL;
Shader* wireframeShader = NULL;
Shader* outlineShader = NULL;
extern Camera camera;
Skybox* skyboxTexture = NULL;
Texture3D* studsTexture = NULL;
bool wireframeRendering = false;
static int viewportWidth, viewportHeight;
void renderInit(GLFWwindow* window, int width, int height) {
@ -45,7 +49,6 @@ void renderInit(GLFWwindow* window, int width, int height) {
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
skyboxTexture = new Skybox({
"assets/textures/skybox/null_plainsky512_lf.jpg",
"assets/textures/skybox/null_plainsky512_rt.jpg",
@ -63,6 +66,8 @@ void renderInit(GLFWwindow* window, int width, int height) {
handleShader = new Shader("assets/shaders/handle.vs", "assets/shaders/handle.fs");
identityShader = new Shader("assets/shaders/identity.vs", "assets/shaders/identity.fs");
ghostShader = new Shader("assets/shaders/ghost.vs", "assets/shaders/ghost.fs");
wireframeShader = new Shader("assets/shaders/wireframe.vs", "assets/shaders/wireframe.fs");
outlineShader = new Shader("assets/shaders/outline.vs", "assets/shaders/outline.fs");
}
void renderParts() {
@ -308,6 +313,112 @@ void renderAABB() {
}
}
void renderWireframe() {
glDepthMask(GL_TRUE);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CW);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
// Use shader
wireframeShader->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();
wireframeShader->set("projection", projection);
wireframeShader->set("view", view);
// Pass in the camera position
wireframeShader->set("viewPos", camera.cameraPos);
wireframeShader->set("transparency", 0.5f);
wireframeShader->set("color", glm::vec3(1.f, 0.f, 0.f));
// Sort by nearest
for (InstanceRef inst : gWorkspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
glm::mat4 model = part->cframe;
model = glm::scale(model, (glm::vec3)part->size);
wireframeShader->set("model", model);
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
wireframeShader->set("normalMatrix", normalMatrix);
CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
}
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
}
void renderOutline() {
glDepthMask(GL_TRUE);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CW);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
glLineWidth(10.f);
// Use shader
wireframeShader->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();
wireframeShader->set("projection", projection);
wireframeShader->set("view", view);
// Pass in the camera position
wireframeShader->set("viewPos", camera.cameraPos);
wireframeShader->set("transparency", 0.5f);
wireframeShader->set("color", glm::vec3(0.204, 0.584, 0.922));
// Sort by nearest
for (InstanceRef inst : gWorkspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
if (!part->selected) continue;
glm::mat4 model = part->cframe;
model = glm::scale(model, (glm::vec3)part->size / glm::vec3(2) + glm::vec3(0.001));
wireframeShader->set("model", model);
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
wireframeShader->set("normalMatrix", normalMatrix);
// CUBE_MESH->bind();
// glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
glBegin(GL_LINES);
// Upper face
glVertex3f(-1., 1., -1.); glVertex3f(1., 1., -1.);
glVertex3f(-1., 1., 1.); glVertex3f(1., 1., 1.);
glVertex3f(-1., 1., -1.); glVertex3f(-1., 1., 1.);
glVertex3f(1., 1., -1.); glVertex3f(1., 1., 1.);
// Lower face
glVertex3f(-1., -1., -1.); glVertex3f(1., -1., -1.);
glVertex3f(-1., -1., 1.); glVertex3f(1., -1., 1.);
glVertex3f(-1., -1., -1.); glVertex3f(-1., -1., 1.);
glVertex3f(1., -1., -1.); glVertex3f(1., -1., 1.);
// Connecting vertical lines
glVertex3f(-1., -1., -1.); glVertex3f(-1., 1., -1.);
glVertex3f(1., -1., -1.); glVertex3f(1., 1., -1.);
glVertex3f(-1., -1., 1.); glVertex3f(-1., 1., 1.);
glVertex3f(1., -1., 1.); glVertex3f(1., 1., 1.);
glEnd();
}
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
}
void render(GLFWwindow* window) {
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@ -315,6 +426,9 @@ void render(GLFWwindow* window) {
renderSkyBox();
renderHandles();
renderParts();
renderOutline();
if (wireframeRendering)
renderWireframe();
// TODO: Make this a debug flag
// renderAABB();
}

View file

@ -1,6 +1,8 @@
#pragma once
#include <GLFW/glfw3.h>
extern bool wireframeRendering;
void renderInit(GLFWwindow* window, int width, int height);
void render(GLFWwindow* window);
void setViewport(int width, int height);

View file

@ -1,8 +1,10 @@
#include <GL/glew.h>
#include <qnamespace.h>
#include <qsoundeffect.h>
#include "mainglwidget.h"
#include "common.h"
#include "math_helper.h"
#include "objects/base/instance.h"
#include "physics/util.h"
#include "rendering/renderer.h"
#include "rendering/shader.h"
@ -282,7 +284,7 @@ void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
};
std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000);
if (rayHit && partFromBody(rayHit->body)->name != "Baseplate") {
if (rayHit && !partFromBody(rayHit->body)->locked) {
setCursor(Qt::OpenHandCursor);
return;
}
@ -341,7 +343,7 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000);
if (!rayHit || !partFromBody(rayHit->body)) return;
std::shared_ptr<Part> part = partFromBody(rayHit->body);
if (part->name == "Baseplate") return;
if (part->locked) return;
// Handle surface tool
if (mainWindow()->selectedTool >= TOOL_SMOOTH) {
@ -368,7 +370,20 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
//part.selected = true;
isMouseDragging = true;
draggingObject = part;
setSelection(std::vector<InstanceRefWeak> { part });
if (evt->modifiers() & Qt::ControlModifier) {
std::vector<InstanceRefWeak> currentSelection = getSelection();
for (int i = 0; i < currentSelection.size(); i++) {
InstanceRefWeak inst = currentSelection[i];
if (!inst.expired() && inst.lock() == part) {
currentSelection.erase(currentSelection.begin() + i);
goto skipAddPart;
}
}
currentSelection.push_back(part);
skipAddPart:
setSelection(currentSelection);
}else
setSelection(std::vector<InstanceRefWeak> { part });
// Disable bit so that we can ignore the part while raycasting
// part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10);

View file

@ -1,6 +1,7 @@
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "common.h"
#include <memory>
#include <qclipboard.h>
#include <qmessagebox.h>
#include <qmimedata.h>
@ -72,6 +73,113 @@ MainWindow::MainWindow(QWidget *parent)
delete menu;
});
connectActionHandlers();
// Update handles
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
editorToolHandles->adornee = std::nullopt;
if (newSelection.size() == 0) return;
InstanceRef inst = newSelection[0].lock();
if (inst->GetClass() != &Part::TYPE) return;
editorToolHandles->adornee = std::dynamic_pointer_cast<Part>(inst);
});
// Update properties
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
if (newSelection.size() == 0) return;
if (newSelection.size() > 1)
ui->propertiesView->setSelected(std::nullopt);
ui->propertiesView->setSelected(newSelection[0].lock());
});
addSelectionListener([&](auto oldSelection, auto newSelection, bool __) {
for (InstanceRefWeak inst : oldSelection) {
if (inst.expired() || inst.lock()->GetClass() != &Part::TYPE) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst.lock());
part->selected = false;
}
for (InstanceRefWeak inst : newSelection) {
if (inst.expired() || inst.lock()->GetClass() != &Part::TYPE) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst.lock());
part->selected = true;
}
});
// ui->explorerView->Init(ui);
// Baseplate
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
.position = glm::vec3(0, -5, 0),
.rotation = glm::vec3(0),
.size = glm::vec3(512, 1.2, 512),
.color = glm::vec3(0.388235, 0.372549, 0.384314),
.anchored = true,
.locked = true,
}));
ui->mainWidget->lastPart->name = "Baseplate";
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
.position = glm::vec3(0),
.rotation = glm::vec3(0.5, 2, 1),
.size = glm::vec3(4, 1.2, 2),
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
}));
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
}
void MainWindow::closeEvent(QCloseEvent* evt) {
#ifdef NDEBUG
// Ask if the user wants to save their changes
// https://stackoverflow.com/a/33890731
QMessageBox msgBox;
msgBox.setText("Save changes before creating new document?");
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int result = msgBox.exec();
if (result == QMessageBox::Cancel) return evt->ignore();
if (result == QMessageBox::Save) {
std::optional<std::string> path;
if (!gDataModel->HasFile())
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + gDataModel->name));
if (!path || path == "") return evt->ignore();
gDataModel->SaveToFile(path);
}
#endif
}
void MainWindow::handleLog(Logger::LogLevel logLevel, std::string message) {
if (logLevel == Logger::LogLevel::DEBUG) return;
if (logLevel == Logger::LogLevel::INFO)
ui->outputTextView->appendHtml(QString("<p>%1</p>").arg(QString::fromStdString(message)));
if (logLevel == Logger::LogLevel::WARNING)
ui->outputTextView->appendHtml(QString("<p style=\"color:rgb(255, 127, 0); font-weight: bold;\">%1</p>").arg(QString::fromStdString(message)));
if (logLevel == Logger::LogLevel::ERROR || logLevel == Logger::LogLevel::FATAL_ERROR)
ui->outputTextView->appendHtml(QString("<p style=\"color:rgb(255, 0, 0); font-weight: bold;\">%1</p>").arg(QString::fromStdString(message)));
}
static std::chrono::time_point lastTime = std::chrono::steady_clock::now();
void MainWindow::timerEvent(QTimerEvent* evt) {
if (evt->timerId() != timer.timerId()) {
QWidget::timerEvent(evt);
return;
}
float deltaTime = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - lastTime).count();
lastTime = std::chrono::steady_clock::now();
if (simulationPlaying)
gWorkspace()->PhysicsStep(deltaTime);
ui->mainWidget->update();
ui->mainWidget->updateCycle();
}
void MainWindow::connectActionHandlers() {
// Explorer View
ui->explorerView->buildContextMenu();
@ -166,6 +274,7 @@ MainWindow::MainWindow(QWidget *parent)
.size = glm::vec3(512, 1.2, 512),
.color = glm::vec3(0.388235, 0.372549, 0.384314),
.anchored = true,
.locked = true,
}));
ui->mainWidget->lastPart->name = "Baseplate";
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
@ -309,94 +418,6 @@ MainWindow::MainWindow(QWidget *parent)
selectedParent->AddChild(inst.expect());
}
});
// Update handles
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
editorToolHandles->adornee = std::nullopt;
if (newSelection.size() == 0) return;
InstanceRef inst = newSelection[0].lock();
if (inst->GetClass() != &Part::TYPE) return;
editorToolHandles->adornee = std::dynamic_pointer_cast<Part>(inst);
});
// Update properties
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
if (newSelection.size() == 0) return;
if (newSelection.size() > 1)
ui->propertiesView->setSelected(std::nullopt);
ui->propertiesView->setSelected(newSelection[0].lock());
});
// ui->explorerView->Init(ui);
// Baseplate
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
.position = glm::vec3(0, -5, 0),
.rotation = glm::vec3(0),
.size = glm::vec3(512, 1.2, 512),
.color = glm::vec3(0.388235, 0.372549, 0.384314),
.anchored = true,
}));
ui->mainWidget->lastPart->name = "Baseplate";
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
.position = glm::vec3(0),
.rotation = glm::vec3(0.5, 2, 1),
.size = glm::vec3(4, 1.2, 2),
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
}));
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
}
void MainWindow::closeEvent(QCloseEvent* evt) {
#ifdef NDEBUG
// Ask if the user wants to save their changes
// https://stackoverflow.com/a/33890731
QMessageBox msgBox;
msgBox.setText("Save changes before creating new document?");
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int result = msgBox.exec();
if (result == QMessageBox::Cancel) return evt->ignore();
if (result == QMessageBox::Save) {
std::optional<std::string> path;
if (!gDataModel->HasFile())
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + gDataModel->name));
if (!path || path == "") return evt->ignore();
gDataModel->SaveToFile(path);
}
#endif
}
void MainWindow::handleLog(Logger::LogLevel logLevel, std::string message) {
if (logLevel == Logger::LogLevel::DEBUG) return;
if (logLevel == Logger::LogLevel::INFO)
ui->outputTextView->appendHtml(QString("<p>%1</p>").arg(QString::fromStdString(message)));
if (logLevel == Logger::LogLevel::WARNING)
ui->outputTextView->appendHtml(QString("<p style=\"color:rgb(255, 127, 0); font-weight: bold;\">%1</p>").arg(QString::fromStdString(message)));
if (logLevel == Logger::LogLevel::ERROR || logLevel == Logger::LogLevel::FATAL_ERROR)
ui->outputTextView->appendHtml(QString("<p style=\"color:rgb(255, 0, 0); font-weight: bold;\">%1</p>").arg(QString::fromStdString(message)));
}
static std::chrono::time_point lastTime = std::chrono::steady_clock::now();
void MainWindow::timerEvent(QTimerEvent* evt) {
if (evt->timerId() != timer.timerId()) {
QWidget::timerEvent(evt);
return;
}
float deltaTime = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - lastTime).count();
lastTime = std::chrono::steady_clock::now();
if (simulationPlaying)
gWorkspace()->PhysicsStep(deltaTime);
ui->mainWidget->update();
ui->mainWidget->updateCycle();
}
void MainWindow::updateToolbars() {

View file

@ -54,6 +54,8 @@ private:
void timerEvent(QTimerEvent*) override;
void closeEvent(QCloseEvent* evt) override;
void handleLog(Logger::LogLevel, std::string);
void connectActionHandlers();
std::optional<std::string> openFileDialog(QString filter, QString defaultExtension, QFileDialog::AcceptMode acceptMode, QString title = "");
};

View file

@ -31,12 +31,10 @@ ExplorerView::ExplorerView(QWidget* parent):
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) {
std::vector<InstanceRefWeak> selectedInstances;
selectedInstances.reserve(selected.count()); // This doesn't reserve everything, but should enhance things anyway
selectedInstances.reserve(selectedIndexes().count()); // This doesn't reserve everything, but should enhance things anyway
for (auto range : selected) {
for (auto index : range.indexes()) {
selectedInstances.push_back(reinterpret_cast<Instance*>(index.internalPointer())->weak_from_this());
}
for (auto index : selectedIndexes()) {
selectedInstances.push_back(reinterpret_cast<Instance*>(index.internalPointer())->weak_from_this());
}
::setSelection(selectedInstances, true);