Compare commits

..

No commits in common. "master" and "feature/studio-tools" have entirely different histories.

37 changed files with 1110 additions and 1549 deletions

17
.gitignore vendored
View file

@ -1,14 +1,19 @@
/bin/
/lib/
/build/
bin/
lib/
CMakeFiles/
cmake_install.cmake
CMakeCache.txt
Makefile
# Qt
/*.pro.user*
/CMakeLists.txt.user*
.lupdate/
*.pro.user*
CMakeLists.txt.user*
*_autogen/
# Clangd
/compile_commands.json
/.cache
# Gdb
/.gdb_history
.gdb_history

View file

@ -1,19 +1,14 @@
cmake_minimum_required(VERSION 3.30.0)
set(CMAKE_CXX_STANDARD 20)
cmake_minimum_required(VERSION 3.5.0)
set(CMAKE_CXX_STANDARD 17)
project(openblocks VERSION 0.1.0)
set(OpenGL_GL_PREFERENCE "GLVND")
set( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" )
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib )
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_subdirectory(core)
add_subdirectory(client)
add_subdirectory(editor)
install(FILES $<TARGET_RUNTIME_DLLS:editor> TYPE BIN)

View file

@ -1,13 +0,0 @@
{
"version": 2,
"configurePresets": [
{
"name": "vcpkg",
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
}
]
}

View file

@ -1,13 +0,0 @@
{
"version": 2,
"configurePresets": [
{
"name": "default",
"inherits": "vcpkg",
"environment": {
"VCPKG_ROOT": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\vcpkg\\vcpkg.exe"
}
}
]
}

View file

@ -53,7 +53,6 @@ uniform int numPointLights;
uniform DirLight sunLight;
uniform Material material;
uniform sampler2DArray studs;
uniform float transparency;
// Functions
@ -73,7 +72,7 @@ void main() {
}
vec4 studPx = texture(studs, vec3(vTexCoords, vSurfaceZ));
FragColor = vec4(mix(result, vec3(studPx), studPx.w), 1) * (1-transparency);
FragColor = vec4(mix(result, vec3(studPx), studPx.w), 1);
}
vec3 calculateDirectionalLight(DirLight light) {

View file

@ -1,7 +1,4 @@
find_package(SDL2 REQUIRED)
include_directories(${SDL2_INCLUDE_DIRS})
find_package(glfw3 REQUIRED)
add_executable(client "src/main.cpp")
target_link_libraries(client PRIVATE ${SDL2_LIBRARIES} openblocks glfw)
target_link_libraries(client PRIVATE openblocks glfw)

View file

@ -1,30 +0,0 @@
# Distributed under the OSI-approved BSD 3-Clause License.
# Copyright Stefano Sinigardi
#.rst:
# FindStb
# ------------
#
# Find the Stb include headers.
#
# Result Variables
# ^^^^^^^^^^^^^^^^
#
# This module defines the following variables:
#
# ``Stb_FOUND``
# True if Stb library found
#
# ``Stb_INCLUDE_DIR``
# Location of Stb headers
#
include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
include(${CMAKE_ROOT}/Modules/SelectLibraryConfigurations.cmake)
if(NOT Stb_INCLUDE_DIR)
find_path(Stb_INCLUDE_DIR NAMES stb_image.h PATHS ${Stb_DIR} PATH_SUFFIXES include stb include/stb)
endif()
find_package_handle_standard_args(Stb DEFAULT_MSG Stb_INCLUDE_DIR)
mark_as_advanced(Stb_INCLUDE_DIR)

View file

@ -1,18 +1,24 @@
find_package(OpenGL REQUIRED COMPONENTS OpenGL)
find_package(SDL2 REQUIRED)
include_directories(${SDL2_INCLUDE_DIRS})
find_package(GLEW REQUIRED)
include_directories(${GLEW_INCLUDE_DIRS})
find_package(GLUT REQUIRED)
include_directories(${GLUT_INCLUDE_DIRS})
find_package(OpenGL)
find_package(glm CONFIG REQUIRED)
# find_package(assimp REQUIRED)
find_package(ReactPhysics3D REQUIRED)
find_package(pugixml 1.15 REQUIRED)
find_package(pugixml REQUIRED)
find_package(Stb REQUIRED)
include_directories(${Stb_INCLUDE_DIR})
file(MAKE_DIRECTORY bin)
file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h")
add_library(openblocks STATIC ${SOURCES})
add_library(openblocks ${SOURCES})
set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks")
target_link_libraries(openblocks ${GLEW_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
target_link_libraries(openblocks ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
target_include_directories(openblocks PUBLIC "src" "../include")

View file

@ -4,7 +4,7 @@
#define IMPL_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE, TYPE_NAME) Data::CLASS_NAME::CLASS_NAME(WRAPPED_TYPE in) : value(in) {} \
Data::CLASS_NAME::~CLASS_NAME() = default; \
Data::CLASS_NAME::operator const WRAPPED_TYPE() const { return value; } \
const Data::TypeInfo Data::CLASS_NAME::TYPE = { .name = TYPE_NAME, .deserializer = &Data::CLASS_NAME::Deserialize, .fromString = &Data::CLASS_NAME::FromString }; \
const Data::TypeInfo Data::CLASS_NAME::TYPE = { .name = TYPE_NAME, .deserializer = &Data::CLASS_NAME::Deserialize }; \
const Data::TypeInfo& Data::CLASS_NAME::GetType() const { return Data::CLASS_NAME::TYPE; }; \
void Data::CLASS_NAME::Serialize(pugi::xml_node* node) const { node->text().set(std::string(this->ToString())); }
@ -45,11 +45,6 @@ Data::Variant Data::Bool::Deserialize(pugi::xml_node* node) {
return Data::Bool(node->text().as_bool());
}
Data::Variant Data::Bool::FromString(std::string string) {
return Data::Bool(string[0] == 't' || string[0] == 'T' || string[0] == '1' || string[0] == 'y' || string[0] == 'Y');
}
const Data::String Data::Int::ToString() const {
return Data::String(std::to_string(value));
}
@ -58,11 +53,6 @@ Data::Variant Data::Int::Deserialize(pugi::xml_node* node) {
return Data::Int(node->text().as_int());
}
Data::Variant Data::Int::FromString(std::string string) {
return Data::Int(std::stoi(string));
}
const Data::String Data::Float::ToString() const {
return Data::String(std::to_string(value));
}
@ -71,11 +61,6 @@ Data::Variant Data::Float::Deserialize(pugi::xml_node* node) {
return Data::Float(node->text().as_float());
}
Data::Variant Data::Float::FromString(std::string string) {
return Data::Float(std::stof(string));
}
const Data::String Data::String::ToString() const {
return *this;
}
@ -83,7 +68,3 @@ const Data::String Data::String::ToString() const {
Data::Variant Data::String::Deserialize(pugi::xml_node* node) {
return Data::String(node->text().as_string());
}
Data::Variant Data::String::FromString(std::string string) {
return Data::String(string);
}

View file

@ -16,18 +16,16 @@ public: \
virtual const Data::String ToString() const override; \
virtual void Serialize(pugi::xml_node* node) const override; \
static Data::Variant Deserialize(pugi::xml_node* node); \
static Data::Variant FromString(std::string); \
};
namespace Data {
class Variant;
typedef std::function<Data::Variant(pugi::xml_node*)> Deserializer;
typedef std::function<Data::Variant(std::string)> FromString;
struct TypeInfo {
std::string name;
Deserializer deserializer;
FromString fromString;
TypeInfo(const TypeInfo&) = delete;
};
class String;

View file

@ -10,7 +10,6 @@
// #include "meta.h" // IWYU pragma: keep
const Data::CFrame Data::CFrame::IDENTITY(glm::vec3(0, 0, 0), glm::mat3(1.f));
const Data::CFrame Data::CFrame::YToZ(glm::vec3(0, 0, 0), glm::mat3(glm::vec3(1, 0, 0), glm::vec3(0, 0, 1), glm::vec3(0, 1, 0)));
Data::CFrame::CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22)
: translation(x, y, z)

View file

@ -31,7 +31,6 @@ namespace Data {
~CFrame();
static const CFrame IDENTITY;
static const CFrame YToZ;
virtual const TypeInfo& GetType() const override;
static const TypeInfo TYPE;

View file

@ -67,9 +67,8 @@ 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
// This code also hardly works, and is not good at all... Hooo nope.
rp3d::RigidBody* body = world->createRigidBody(Data::CFrame::IDENTITY + cframe.Position());
body->addCollider(common.createBoxShape(cframe.Rotation() * Data::Vector3(HandleSize(face) / 2.f)), rp3d::Transform::identity());
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)) {
@ -82,9 +81,3 @@ std::optional<HandleFace> Handles::RaycastHandle(rp3d::Ray ray) {
return std::nullopt;
}
Data::Vector3 Handles::HandleSize(HandleFace face) {
if (handlesType == HandlesType::MoveHandles)
return glm::vec3(0.5f, 0.5f, 2.f);
return glm::vec3(1,1,1);
}

View file

@ -24,20 +24,12 @@ class HandleFace {
static std::array<HandleFace, 6> Faces;
};
enum HandlesType {
MoveHandles,
ScaleHandles,
RotateHandles,
};
class Handles : public Instance {
public:
const static InstanceType TYPE;
bool active;
std::optional<std::weak_ptr<Part>> adornee;
HandlesType handlesType;
// inline std::optional<std::weak_ptr<Part>> GetAdornee() { return adornee; }
// inline void SetAdornee(std::optional<std::weak_ptr<Part>> newAdornee) { this->adornee = newAdornee; updateAdornee(); };
@ -47,7 +39,6 @@ public:
bool worldMode = false;
Data::CFrame GetCFrameOfHandle(HandleFace face);
Data::CFrame PartCFrameFromHandlePos(HandleFace face, Data::Vector3 newPos);
Data::Vector3 HandleSize(HandleFace face);
std::optional<HandleFace> RaycastHandle(rp3d::Ray ray);
static inline std::shared_ptr<Handles> New() { return std::make_shared<Handles>(); };

View file

@ -95,10 +95,6 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(par
.backingField = &color,
.type = &Data::Color3::TYPE,
.codec = fieldCodecOf<Data::Color3>(),
}}, { "Transparency", {
.backingField = &transparency,
.type = &Data::Float::TYPE,
.codec = fieldCodecOf<Data::Float, float>(),
} }
}
});

View file

@ -32,7 +32,6 @@ public:
Data::CFrame cframe;
glm::vec3 size;
Data::Color3 color;
float transparency = 0.f;
bool selected = false;
bool anchored = false;

File diff suppressed because it is too large Load diff

View file

@ -67,11 +67,6 @@ void renderInit(GLFWwindow* window, int width, int height) {
void renderParts() {
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);
// Use shader
shader->use();
@ -116,17 +111,12 @@ void renderParts() {
// Pass in the camera position
shader->set("viewPos", camera.cameraPos);
// Sort by nearest
std::map<float, std::shared_ptr<Part>> sorted;
// TODO: Same as todo in src/physics/simulation.cpp
for (InstanceRef inst : workspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
if (part->transparency > 0.00001) {
float distance = glm::length(glm::vec3(Data::Vector3(camera.cameraPos) - part->position()));
sorted[distance] = part;
} else {
glm::mat4 model = part->cframe;
if (part->name == "camera") model = camera.getLookAt();
if (inst->name == "camera") model = camera.getLookAt();
model = glm::scale(model, part->size);
shader->set("model", model);
shader->set("material", Material {
@ -137,39 +127,14 @@ void renderParts() {
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
shader->set("normalMatrix", normalMatrix);
shader->set("texScale", part->size);
shader->set("transparency", part->transparency);
CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
}
}
// TODO: Same as todo in src/physics/simulation.cpp
// According to LearnOpenGL, std::map automatically sorts its contents.
for (std::map<float, std::shared_ptr<Part>>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); it++) {
std::shared_ptr<Part> part = it->second;
glm::mat4 model = part->cframe;
if (part->name == "camera") model = camera.getLookAt();
model = glm::scale(model, part->size);
shader->set("model", model);
shader->set("material", Material {
.diffuse = part->color,
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 16.0f,
});
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
shader->set("normalMatrix", normalMatrix);
shader->set("texScale", part->size);
shader->set("transparency", part->transparency);
CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
}
void renderSkyBox() {
glDepthMask(GL_FALSE);
glCullFace(GL_FRONT);
skyboxShader->use();
@ -190,7 +155,6 @@ void renderHandles() {
if (!editorToolHandles->adornee.has_value() || !editorToolHandles->active) return;
glDepthMask(GL_TRUE);
glCullFace(GL_BACK);
// Use shader
handleShader->use();
@ -213,7 +177,7 @@ void renderHandles() {
for (auto face : HandleFace::Faces) {
glm::mat4 model = editorToolHandles->GetCFrameOfHandle(face);
model = glm::scale(model, (glm::vec3)editorToolHandles->HandleSize(face));
model = glm::scale(model, glm::vec3(1,1,1));
handleShader->set("model", model);
handleShader->set("material", Material {
.diffuse = glm::abs(face.normal),
@ -223,18 +187,11 @@ void renderHandles() {
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
handleShader->set("normalMatrix", normalMatrix);
if (editorToolHandles->handlesType == HandlesType::MoveHandles) {
ARROW_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, ARROW_MESH->vertexCount);
} else {
SPHERE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, SPHERE_MESH->vertexCount);
}
}
// 2d square overlay
glDisable(GL_CULL_FACE);
identityShader->use();
identityShader->set("aColor", glm::vec3(0.f, 1.f, 1.f));

View file

@ -1,6 +1,6 @@
#include <GL/glew.h>
#include <GL/gl.h>
#include <stb_image.h>
#include <stb/stb_image.h>
#include "skybox.h"

View file

@ -2,7 +2,7 @@
#include <GL/glew.h>
#include <GL/gl.h>
#include <stb_image.h>
#include <stb/stb_image.h>
Texture::Texture(const char* texturePath, unsigned int format, bool noMipMaps) {
glGenTextures(1, &this->ID);

View file

@ -2,7 +2,7 @@
#include <GL/glew.h>
#include <GL/gl.h>
#include <stb_image.h>
#include <stb/stb_image.h>
Texture3D::Texture3D(const char* texturePath, unsigned int tileWidth, unsigned int tileHeight, unsigned int tileCount, unsigned int format) {
glGenTextures(1, &this->ID);

View file

@ -1,2 +1,2 @@
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include <stb/stb_image.h>

View file

@ -1,9 +1,10 @@
opengl (Linux: glvnd, Windows: [built-in/none])
glfw
glew
glm
opengl
assimp
sdl2
stb
glfw
glut
glew
qt6
reactphysics3d
pugixml

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.30.0)
cmake_minimum_required(VERSION 3.16)
project(editor VERSION 0.1 LANGUAGES CXX)
@ -6,7 +6,7 @@ set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@ -14,7 +14,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)
set(TS_FILES editor_en_US.ts)
set(PROJECT_SOURCES
@ -66,42 +65,6 @@ endif()
target_include_directories(editor PUBLIC "../core/src" "../include")
target_link_libraries(editor PRIVATE openblocks Qt${QT_VERSION_MAJOR}::Widgets)
# Qt6 does not include QOpenGLWidgets as part of Widgets base anymore, so
# we have to include it manually
if (${QT_VERSION} GREATER_EQUAL 6)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS OpenGL OpenGLWidgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS OpenGL OpenGLWidgets)
target_include_directories(editor PUBLIC Qt6::OpenGL Qt6::OpenGLWidgets)
target_link_libraries(editor PRIVATE Qt6::OpenGL Qt6::OpenGLWidgets)
endif()
# Copy assets
add_custom_command(
TARGET editor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets
$<TARGET_FILE_DIR:editor>/assets)
# Copy Qt files
if (WIN32)
#include("${QT_DEPLOY_SUPPORT}")
# TODO: Add other translations
add_custom_command(
TARGET editor POST_BUILD
COMMAND ${WINDEPLOYQT_EXECUTABLE} $<TARGET_FILE:editor> --translations en --no-compiler-runtime --no-opengl-sw --no-system-d3d-compiler --plugindir $<TARGET_FILE_DIR:editor>/qtplugins
)
# No sense adding opengl-sw given that hardware acceleration is necessary, anyway
# Also don't want to clutter with plugins, add only needed ones
# Copy qt.conf to override default plugins location
add_custom_command(
TARGET editor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/qt.conf $<TARGET_FILE_DIR:editor>/qt.conf
)
endif ()
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
@ -123,8 +86,6 @@ install(TARGETS editor
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(FILES $<TARGET_RUNTIME_DLLS:editor> TYPE BIN)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(editor)
endif()

View file

@ -138,9 +138,7 @@ void MainGLWidget::handleHandleDrag(QMouseEvent* evt) {
// Apply snapping in the current frame
glm::vec3 diff = centerPoint - (glm::vec3)editorToolHandles->adornee->lock()->position();
// printf("\n=======\nPre-snap: (%f, %f, %f)\n", diff.x, diff.y, diff.z);
if (snappingFactor()) diff = frame.Rotation() * (glm::round(glm::vec3(frame.Inverse().Rotation() * diff) / snappingFactor()) * snappingFactor());
// printf("Post-snap: (%f, %f, %f)\n", diff.x, diff.y, diff.z);
if (snappingFactor()) diff = frame * (glm::round(glm::vec3(frame.Inverse() * diff) / snappingFactor()) * snappingFactor());
switch (mainWindow()->selectedTool) {
case SelectedTool::SELECT: break;

View file

@ -12,20 +12,15 @@
#include <QAbstractItemView>
#include <memory>
#include <optional>
#include <qglobal.h>
#include <qwindowdefs.h>
#include <sstream>
#include "common.h"
#include "editorcommon.h"
#include "objects/base/instance.h"
#include "objects/datamodel.h"
#include "objects/handles.h"
#include "physics/simulation.h"
#include "objects/part.h"
#include "qfiledialog.h"
#include "qclipboard.h"
#include "qmimedata.h"
#include "qitemselectionmodel.h"
#include "qobject.h"
#include "qsysinfo.h"
@ -41,7 +36,7 @@ MainWindow::MainWindow(QWidget *parent)
timer.start(33, this);
setMouseTracking(true);
ui->explorerView->buildContextMenu();
ConnectSelectionChangeHandler();
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = SelectedTool::SELECT; updateToolbars(); });
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::MOVE : SelectedTool::SELECT; updateToolbars(); });
@ -72,124 +67,21 @@ MainWindow::MainWindow(QWidget *parent)
connect(ui->actionSave, &QAction::triggered, this, [&]() {
std::optional<std::string> path;
if (!dataModel->HasFile())
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + dataModel->name));
path = QFileDialog::getSaveFileName(this, QString::fromStdString("Save " + dataModel->name), "", "*.obl").toStdString();
if (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;
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value());
std::string path = QFileDialog::getOpenFileName(this, "Load file", "", "*.obl").toStdString();
if (path == "") return;
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path);
dataModel = newModel;
ui->explorerView->updateRoot(newModel);
});
connect(ui->actionDelete, &QAction::triggered, this, [&]() {
for (InstanceRefWeak inst : getSelection()) {
if (inst.expired()) continue;
inst.lock()->SetParent(std::nullopt);
}
setSelection(std::vector<InstanceRefWeak> {});
});
connect(ui->actionCopy, &QAction::triggered, this, [&]() {
pugi::xml_document rootDoc;
for (InstanceRefWeak inst : getSelection()) {
if (inst.expired()) continue;
inst.lock()->Serialize(&rootDoc);
}
std::ostringstream encoded;
rootDoc.save(encoded);
QMimeData* mimeData = new QMimeData;
mimeData->setData("application/xml", QByteArray::fromStdString(encoded.str()));
QApplication::clipboard()->setMimeData(mimeData);
});
connect(ui->actionCut, &QAction::triggered, this, [&]() {
pugi::xml_document rootDoc;
for (InstanceRefWeak inst : getSelection()) {
if (inst.expired()) continue;
inst.lock()->Serialize(&rootDoc);
inst.lock()->SetParent(std::nullopt);
}
std::ostringstream encoded;
rootDoc.save(encoded);
QMimeData* mimeData = new QMimeData;
mimeData->setData("application/xml", QByteArray::fromStdString(encoded.str()));
QApplication::clipboard()->setMimeData(mimeData);
});
connect(ui->actionPaste, &QAction::triggered, this, [&]() {
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
if (!mimeData || !mimeData->hasFormat("application/xml")) return;
QByteArray bytes = mimeData->data("application/xml");
std::string encoded = bytes.toStdString();
pugi::xml_document rootDoc;
rootDoc.load_string(encoded.c_str());
for (pugi::xml_node instNode : rootDoc.children()) {
InstanceRef inst = Instance::Deserialize(&instNode);
workspace()->AddChild(inst);
}
});
connect(ui->actionPasteInto, &QAction::triggered, this, [&]() {
if (getSelection().size() != 1 || getSelection()[0].expired()) return;
InstanceRef selectedParent = getSelection()[0].lock();
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
if (!mimeData || !mimeData->hasFormat("application/xml")) return;
QByteArray bytes = mimeData->data("application/xml");
std::string encoded = bytes.toStdString();
pugi::xml_document rootDoc;
rootDoc.load_string(encoded.c_str());
for (pugi::xml_node instNode : rootDoc.children()) {
InstanceRef inst = Instance::Deserialize(&instNode);
selectedParent->AddChild(inst);
}
});
connect(ui->actionSaveModel, &QAction::triggered, this, [&]() {
std::optional<std::string> path = openFileDialog("Openblocks Model (*.obm)", ".obm", QFileDialog::AcceptSave);
if (!path) return;
std::ofstream outStream(path.value());
// Serialized XML for exporting
pugi::xml_document modelDoc;
pugi::xml_node modelRoot = modelDoc.append_child("openblocks");
for (InstanceRefWeak inst : getSelection()) {
if (inst.expired()) continue;
inst.lock()->Serialize(&modelRoot);
}
modelDoc.save(outStream);
});
connect(ui->actionInsertModel, &QAction::triggered, this, [&]() {
if (getSelection().size() != 1 || getSelection()[0].expired()) return;
InstanceRef selectedParent = getSelection()[0].lock();
std::optional<std::string> path = openFileDialog("Openblocks Model (*.obm)", ".obm", QFileDialog::AcceptOpen);
if (!path) return;
std::ifstream inStream(path.value());
pugi::xml_document modelDoc;
modelDoc.load(inStream);
for (pugi::xml_node instNode : modelDoc.child("openblocks").children("Item")) {
InstanceRef inst = Instance::Deserialize(&instNode);
selectedParent->AddChild(inst);
}
delete ui->explorerView->selectionModel();
ui->explorerView->reset();
ui->explorerView->setModel(new ExplorerModel(dataModel));
ConnectSelectionChangeHandler();
});
// Update handles
@ -234,6 +126,17 @@ MainWindow::MainWindow(QWidget *parent)
syncPartPhysics(ui->mainWidget->lastPart);
}
void MainWindow::ConnectSelectionChangeHandler() {
// connect(ui->explorerView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) {
// if (selected.count() == 0) return;
// std::optional<InstanceRef> inst = selected.count() == 0 ? std::nullopt
// : std::make_optional(((Instance*)selected.indexes()[0].internalPointer())->shared_from_this());
// ui->propertiesView->setSelected(inst);
// });
}
static std::chrono::time_point lastTime = std::chrono::steady_clock::now();
void MainWindow::timerEvent(QTimerEvent* evt) {
if (evt->timerId() != timer.timerId()) {
@ -264,23 +167,6 @@ void MainWindow::updateToolbars() {
if (selectedTool == SelectedTool::MOVE) editorToolHandles->worldMode = true;
if (selectedTool == SelectedTool::SCALE) editorToolHandles->worldMode = false;
editorToolHandles->active = selectedTool != SelectedTool::SELECT;
editorToolHandles->handlesType =
selectedTool == SelectedTool::MOVE ? HandlesType::MoveHandles
: selectedTool == SelectedTool::SCALE ? HandlesType::ScaleHandles
: selectedTool == SelectedTool::ROTATE ? HandlesType::RotateHandles
: HandlesType::ScaleHandles;
}
std::optional<std::string> MainWindow::openFileDialog(QString filter, QString defaultExtension, QFileDialog::AcceptMode acceptMode, QString title) {
QFileDialog dialog(this);
if (title != "") dialog.setWindowTitle(title);
dialog.setNameFilters(QStringList { filter, "All Files (*)" });
dialog.setDefaultSuffix(defaultExtension);
dialog.setAcceptMode(acceptMode);
if (!dialog.exec())
return std::nullopt;
return dialog.selectedFiles().front().toStdString();
}
MainWindow::~MainWindow()

View file

@ -7,7 +7,6 @@
#include "qmenu.h"
#include <QMainWindow>
#include <QLineEdit>
#include <qfiledialog.h>
enum SelectedTool {
SELECT,
@ -45,7 +44,6 @@ private:
void updateToolbars();
void timerEvent(QTimerEvent*) override;
std::optional<std::string> openFileDialog(QString filter, QString defaultExtension, QFileDialog::AcceptMode acceptMode, QString title = "");
void ConnectSelectionChangeHandler();
};
#endif // MAINWINDOW_H

View file

@ -41,7 +41,7 @@
<x>0</x>
<y>0</y>
<width>1027</width>
<height>30</height>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@ -115,12 +115,6 @@
<addaction name="actionToolScale"/>
<addaction name="actionToolRotate"/>
<addaction name="separator"/>
<addaction name="actionDelete"/>
<addaction name="actionCopy"/>
<addaction name="actionCut"/>
<addaction name="actionPaste"/>
<addaction name="actionPasteInto"/>
<addaction name="separator"/>
<addaction name="actionGridSnap1"/>
<addaction name="actionGridSnap05"/>
<addaction name="actionGridSnapOff"/>
@ -321,113 +315,6 @@
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionCopy">
<property name="icon">
<iconset theme="edit-copy"/>
</property>
<property name="text">
<string>Copy</string>
</property>
<property name="toolTip">
<string>Copy objects to clipboard</string>
</property>
<property name="shortcut">
<string>Ctrl+C</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionCut">
<property name="icon">
<iconset theme="edit-cut"/>
</property>
<property name="text">
<string>Cut</string>
</property>
<property name="toolTip">
<string>Cut objects into clipboard</string>
</property>
<property name="shortcut">
<string>Ctrl+X</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionPaste">
<property name="icon">
<iconset theme="edit-paste"/>
</property>
<property name="text">
<string>Paste</string>
</property>
<property name="toolTip">
<string>Paste objects from clipboard</string>
</property>
<property name="shortcut">
<string>Ctrl+V</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionPasteInto">
<property name="icon">
<iconset theme="edit-paste"/>
</property>
<property name="text">
<string>Paste Into</string>
</property>
<property name="toolTip">
<string>Paste objects from clipboard into selected object</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+V</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionDelete">
<property name="icon">
<iconset theme="edit-delete"/>
</property>
<property name="text">
<string>Delete Object</string>
</property>
<property name="toolTip">
<string>Delete selected objects</string>
</property>
<property name="shortcut">
<string>Del</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionSaveModel">
<property name="text">
<string>Save Model to File</string>
</property>
<property name="toolTip">
<string>Saves objects to file as XML model</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionInsertModel">
<property name="text">
<string>Insert Model from File</string>
</property>
<property name="toolTip">
<string>Insert model from XML file</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View file

@ -234,12 +234,6 @@ bool ExplorerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
return true;
}
void ExplorerModel::updateRoot(InstanceRef newRoot) {
beginResetModel();
rootItem = newRoot;
endResetModel();
}
QMimeData* ExplorerModel::mimeData(const QModelIndexList& indexes) const {
// application/x-openblocks-instance-pointers
DragDropSlot* slot = new DragDropSlot();

View file

@ -1,12 +1,18 @@
#pragma once
#include "objects/base/instance.h"
#include "objects/part.h"
#include "qabstractitemmodel.h"
#include "qevent.h"
#include "qmenu.h"
#include "qnamespace.h"
#include "qtreeview.h"
#include <QOpenGLWidget>
#include <QWidget>
#include <memory>
// #ifndef EXPLORERMODEL_H
// #define EXPLORERMODEL_H
class ExplorerModel : public QAbstractItemModel {
Q_OBJECT
@ -36,8 +42,6 @@ public:
Qt::DropActions supportedDropActions() const override;
InstanceRef fromIndex(const QModelIndex index) const;
QModelIndex ObjectToIndex(InstanceRef item);
void updateRoot(InstanceRef newRoot);
private:
InstanceRef rootItem;
QModelIndex toIndex(InstanceRef item);

View file

@ -1,16 +1,13 @@
#include "explorerview.h"
#include "explorermodel.h"
#include "mainwindow.h"
#include "../ui_mainwindow.h"
#include "common.h"
#include "objects/base/instance.h"
#include "objects/workspace.h"
#include "qabstractitemmodel.h"
#include <qaction.h>
#include <qnamespace.h>
#include "qaction.h"
#include "qnamespace.h"
#include <qitemselectionmodel.h>
#define M_mainWindow dynamic_cast<MainWindow*>(window())
ExplorerView::ExplorerView(QWidget* parent):
QTreeView(parent),
model(ExplorerModel(std::dynamic_pointer_cast<Instance>(dataModel))) {
@ -60,6 +57,8 @@ ExplorerView::ExplorerView(QWidget* parent):
this->selectionModel()->select(index, QItemSelectionModel::SelectionFlag::Select);
}
});
buildContextMenu();
}
ExplorerView::~ExplorerView() {
@ -68,23 +67,19 @@ ExplorerView::~ExplorerView() {
void ExplorerView::keyPressEvent(QKeyEvent* event) {
switch (event->key()) {
case Qt::Key_Delete:
M_mainWindow->ui->actionDelete->trigger();
actionDelete->trigger();
break;
}
}
void ExplorerView::buildContextMenu() {
contextMenu.addAction(M_mainWindow->ui->actionDelete);
contextMenu.addSeparator();
contextMenu.addAction(M_mainWindow->ui->actionCopy);
contextMenu.addAction(M_mainWindow->ui->actionCut);
contextMenu.addAction(M_mainWindow->ui->actionPaste);
contextMenu.addAction(M_mainWindow->ui->actionPasteInto);
contextMenu.addSeparator();
contextMenu.addAction(M_mainWindow->ui->actionSaveModel);
contextMenu.addAction(M_mainWindow->ui->actionInsertModel);
}
// This will leak memory. Anyway...
contextMenu.addAction(this->actionDelete = new QAction(QIcon("assets/icons/editor/delete"), "Delete"));
void ExplorerView::updateRoot(InstanceRef newRoot) {
model.updateRoot(newRoot);
connect(actionDelete, &QAction::triggered, this, [&]() {
QModelIndexList selectedIndexes = this->selectionModel()->selectedIndexes();
for (QModelIndex index : selectedIndexes) {
model.fromIndex(index)->SetParent(std::nullopt);
}
});
}

View file

@ -20,10 +20,18 @@ public:
void keyPressEvent(QKeyEvent*) override;
// void dropEvent(QDropEvent*) override;
void buildContextMenu();
void updateRoot(InstanceRef newRoot);
private:
ExplorerModel model;
QMenu contextMenu;
// TODO: Move these to a separate top-level namespace so these can be
// accessed from multiple locations
QAction* actionDelete;
QAction* actionCopy;
QAction* actionCut;
QAction* actionPaste;
QAction* actionPasteInto;
QAction* actionSelectChildren;
void buildContextMenu();
};

View file

@ -56,10 +56,10 @@ bool PropertiesModel::setData(const QModelIndex &index, const QVariant &value, i
switch (role) {
case Qt::EditRole:
if (!meta.type->fromString)
if (meta.type != &Data::String::TYPE)
return false;
selectedItem->SetPropertyValue(propertyName, meta.type->fromString(value.toString().toStdString()));
selectedItem->SetPropertyValue(propertyName, value.toString().toStdString());
return true;
case Qt::CheckStateRole:
if (meta.type != &Data::Bool::TYPE)

View file

@ -1,2 +0,0 @@
[Paths]
Plugins = "qtplugins"

4
run.sh
View file

@ -1,9 +1,7 @@
if [ $# -eq 0 ] || ([ "$1" != "editor" ] && [ "$1" != "client" ]); then echo "Argument missing, must be 'client' or 'editor'"; exit; fi
[ "$2" = "-debug" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=Debug
[ "$2" = "-release" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=Release
[ "$2" = "-reldbg" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=RelWithDebInfo
[ "$3" = "-gdb" ] && PRE_COMMAND="gdb -ex run "
cmake -Bbuild $CMAKE_OPTS . && (cd build; cmake --build .; cd ..) && $PRE_COMMAND ./build/bin/$1
cmake $CMAKE_OPTS . && cmake --build . && $PRE_COMMAND ./bin/$1

View file

@ -1,14 +0,0 @@
{
"default-registry": {
"kind": "git",
"baseline": "0c4cf19224a049cf82f4521e29e39f7bd680440c",
"repository": "https://github.com/microsoft/vcpkg"
},
"registries": [
{
"kind": "artifact",
"location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip",
"name": "microsoft"
}
]
}

View file

@ -1,10 +0,0 @@
{
"dependencies": [
"glew",
"glfw3",
"glm",
{ "name": "pugixml", "version>=": "1.15" },
"sdl2",
"stb"
]
}