Compare commits
13 commits
feature/st
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
086a2ca39f | ||
|
7e6261fcf3 | ||
|
f81964e395 | ||
|
bfaab69daa | ||
|
75be686e48 | ||
|
45f39c3f49 | ||
|
12a4ed76b4 | ||
|
186d64f28e | ||
|
940109b0b9 | ||
|
b80eb03f9d | ||
|
28ed11fb53 | ||
|
23ac7ee634 | ||
|
ac89dea966 |
17
.gitignore
vendored
17
.gitignore
vendored
|
@ -1,19 +1,14 @@
|
|||
bin/
|
||||
lib/
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
CMakeCache.txt
|
||||
Makefile
|
||||
/bin/
|
||||
/lib/
|
||||
/build/
|
||||
|
||||
# Qt
|
||||
.lupdate/
|
||||
*.pro.user*
|
||||
CMakeLists.txt.user*
|
||||
*_autogen/
|
||||
/*.pro.user*
|
||||
/CMakeLists.txt.user*
|
||||
|
||||
# Clangd
|
||||
/compile_commands.json
|
||||
/.cache
|
||||
|
||||
# Gdb
|
||||
.gdb_history
|
||||
/.gdb_history
|
|
@ -1,11 +1,11 @@
|
|||
cmake_minimum_required(VERSION 3.5.0)
|
||||
cmake_minimum_required(VERSION 3.31..)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
project(openblocks VERSION 0.1.0)
|
||||
set(OpenGL_GL_PREFERENCE "GLVND")
|
||||
|
||||
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
|
||||
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
|
||||
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib )
|
||||
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ uniform int numPointLights;
|
|||
uniform DirLight sunLight;
|
||||
uniform Material material;
|
||||
uniform sampler2DArray studs;
|
||||
uniform float transparency;
|
||||
|
||||
// Functions
|
||||
|
||||
|
@ -72,7 +73,7 @@ void main() {
|
|||
}
|
||||
|
||||
vec4 studPx = texture(studs, vec3(vTexCoords, vSurfaceZ));
|
||||
FragColor = vec4(mix(result, vec3(studPx), studPx.w), 1);
|
||||
FragColor = vec4(mix(result, vec3(studPx), studPx.w), 1) * (1-transparency);
|
||||
}
|
||||
|
||||
vec3 calculateDirectionalLight(DirLight light) {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
find_package(SDL2 REQUIRED)
|
||||
include_directories(${SDL2_INCLUDE_DIRS})
|
||||
|
||||
find_package(glfw3 REQUIRED)
|
||||
|
||||
add_executable(client "src/main.cpp")
|
||||
target_link_libraries(client PRIVATE openblocks glfw)
|
||||
target_link_libraries(client PRIVATE ${SDL2_LIBRARIES} openblocks glfw)
|
|
@ -1,24 +1,15 @@
|
|||
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 REQUIRED)
|
||||
|
||||
file(MAKE_DIRECTORY bin)
|
||||
|
||||
file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h")
|
||||
add_library(openblocks ${SOURCES})
|
||||
set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks")
|
||||
target_link_libraries(openblocks ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
|
||||
target_link_libraries(openblocks ${GLEW_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
|
||||
target_include_directories(openblocks PUBLIC "src" "../include")
|
|
@ -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 }; \
|
||||
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::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,6 +45,11 @@ 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));
|
||||
}
|
||||
|
@ -53,6 +58,11 @@ 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));
|
||||
}
|
||||
|
@ -61,6 +71,11 @@ 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;
|
||||
}
|
||||
|
@ -68,3 +83,7 @@ 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);
|
||||
}
|
|
@ -16,15 +16,18 @@ 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;
|
||||
};
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// #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)
|
||||
|
|
|
@ -31,6 +31,7 @@ namespace Data {
|
|||
~CFrame();
|
||||
|
||||
static const CFrame IDENTITY;
|
||||
static const CFrame YToZ;
|
||||
|
||||
virtual const TypeInfo& GetType() const override;
|
||||
static const TypeInfo TYPE;
|
||||
|
|
|
@ -67,8 +67,9 @@ 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());
|
||||
// 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::RaycastInfo info;
|
||||
if (body->raycast(ray, info)) {
|
||||
|
@ -81,3 +82,9 @@ 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);
|
||||
}
|
|
@ -24,12 +24,20 @@ 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(); };
|
||||
|
||||
|
@ -39,6 +47,7 @@ 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>(); };
|
||||
|
|
|
@ -69,33 +69,37 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(par
|
|||
.type = &Data::Bool::TYPE,
|
||||
.codec = fieldCodecOf<Data::Bool, bool>(),
|
||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this)
|
||||
} }, { "Position", {
|
||||
}}, { "Position", {
|
||||
.backingField = &cframe,
|
||||
.type = &Data::Vector3::TYPE,
|
||||
.codec = cframePositionCodec(),
|
||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
|
||||
.flags = PropertyFlags::PROP_NOSAVE
|
||||
} }, { "Rotation", {
|
||||
}}, { "Rotation", {
|
||||
.backingField = &cframe,
|
||||
.type = &Data::Vector3::TYPE,
|
||||
.codec = cframeRotationCodec(),
|
||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
|
||||
.flags = PropertyFlags::PROP_NOSAVE
|
||||
} }, { "CFrame", {
|
||||
}}, { "CFrame", {
|
||||
.backingField = &cframe,
|
||||
.type = &Data::CFrame::TYPE,
|
||||
.codec = fieldCodecOf<Data::CFrame>(),
|
||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
|
||||
} }, { "Size", {
|
||||
}}, { "Size", {
|
||||
.backingField = &size,
|
||||
.type = &Data::Vector3::TYPE,
|
||||
.codec = fieldCodecOf<Data::Vector3, glm::vec3>(),
|
||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this)
|
||||
} }, { "Color", {
|
||||
}}, { "Color", {
|
||||
.backingField = &color,
|
||||
.type = &Data::Color3::TYPE,
|
||||
.codec = fieldCodecOf<Data::Color3>(),
|
||||
} }
|
||||
}}, { "Transparency", {
|
||||
.backingField = &transparency,
|
||||
.type = &Data::Float::TYPE,
|
||||
.codec = fieldCodecOf<Data::Float, float>(),
|
||||
}}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ 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
|
@ -67,6 +67,11 @@ 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();
|
||||
|
@ -111,12 +116,17 @@ void renderParts() {
|
|||
// Pass in the camera position
|
||||
shader->set("viewPos", camera.cameraPos);
|
||||
|
||||
// TODO: Same as todo in src/physics/simulation.cpp
|
||||
// Sort by nearest
|
||||
std::map<float, std::shared_ptr<Part>> sorted;
|
||||
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 (inst->name == "camera") model = camera.getLookAt();
|
||||
if (part->name == "camera") model = camera.getLookAt();
|
||||
model = glm::scale(model, part->size);
|
||||
shader->set("model", model);
|
||||
shader->set("material", Material {
|
||||
|
@ -127,14 +137,39 @@ 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, 36);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void renderSkyBox() {
|
||||
glDepthMask(GL_FALSE);
|
||||
glCullFace(GL_FRONT);
|
||||
|
||||
skyboxShader->use();
|
||||
|
||||
|
@ -155,6 +190,7 @@ void renderHandles() {
|
|||
if (!editorToolHandles->adornee.has_value() || !editorToolHandles->active) return;
|
||||
|
||||
glDepthMask(GL_TRUE);
|
||||
glCullFace(GL_BACK);
|
||||
|
||||
// Use shader
|
||||
handleShader->use();
|
||||
|
@ -177,7 +213,7 @@ void renderHandles() {
|
|||
|
||||
for (auto face : HandleFace::Faces) {
|
||||
glm::mat4 model = editorToolHandles->GetCFrameOfHandle(face);
|
||||
model = glm::scale(model, glm::vec3(1,1,1));
|
||||
model = glm::scale(model, (glm::vec3)editorToolHandles->HandleSize(face));
|
||||
handleShader->set("model", model);
|
||||
handleShader->set("material", Material {
|
||||
.diffuse = glm::abs(face.normal),
|
||||
|
@ -187,11 +223,18 @@ 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));
|
||||
|
||||
|
|
9
deps.txt
9
deps.txt
|
@ -1,10 +1,9 @@
|
|||
glm
|
||||
opengl
|
||||
assimp
|
||||
sdl2
|
||||
opengl (Linux: glvnd, Windows: [built-in/none])
|
||||
glfw
|
||||
glut
|
||||
glew
|
||||
glm
|
||||
sdl2
|
||||
stb
|
||||
qt6
|
||||
reactphysics3d
|
||||
pugixml
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
cmake_minimum_required(VERSION 3.31..)
|
||||
|
||||
project(editor VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
|
@ -65,6 +65,16 @@ 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()
|
||||
|
||||
# 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.
|
||||
|
@ -86,6 +96,8 @@ 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()
|
|
@ -138,7 +138,9 @@ void MainGLWidget::handleHandleDrag(QMouseEvent* evt) {
|
|||
|
||||
// Apply snapping in the current frame
|
||||
glm::vec3 diff = centerPoint - (glm::vec3)editorToolHandles->adornee->lock()->position();
|
||||
if (snappingFactor()) diff = frame * (glm::round(glm::vec3(frame.Inverse() * diff) / snappingFactor()) * snappingFactor());
|
||||
// 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);
|
||||
|
||||
switch (mainWindow()->selectedTool) {
|
||||
case SelectedTool::SELECT: break;
|
||||
|
|
|
@ -12,15 +12,20 @@
|
|||
#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 "qitemselectionmodel.h"
|
||||
#include "qclipboard.h"
|
||||
#include "qmimedata.h"
|
||||
#include "qobject.h"
|
||||
#include "qsysinfo.h"
|
||||
|
||||
|
@ -36,7 +41,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
timer.start(33, this);
|
||||
setMouseTracking(true);
|
||||
|
||||
ConnectSelectionChangeHandler();
|
||||
ui->explorerView->buildContextMenu();
|
||||
|
||||
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(); });
|
||||
|
@ -67,21 +72,124 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
connect(ui->actionSave, &QAction::triggered, this, [&]() {
|
||||
std::optional<std::string> path;
|
||||
if (!dataModel->HasFile())
|
||||
path = QFileDialog::getSaveFileName(this, QString::fromStdString("Save " + dataModel->name), "", "*.obl").toStdString();
|
||||
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + dataModel->name));
|
||||
if (path == "") return;
|
||||
|
||||
dataModel->SaveToFile(path);
|
||||
});
|
||||
|
||||
connect(ui->actionOpen, &QAction::triggered, this, [&]() {
|
||||
std::string path = QFileDialog::getOpenFileName(this, "Load file", "", "*.obl").toStdString();
|
||||
if (path == "") return;
|
||||
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path);
|
||||
std::optional<std::string> path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptOpen);
|
||||
if (!path) return;
|
||||
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value());
|
||||
dataModel = newModel;
|
||||
delete ui->explorerView->selectionModel();
|
||||
ui->explorerView->reset();
|
||||
ui->explorerView->setModel(new ExplorerModel(dataModel));
|
||||
ConnectSelectionChangeHandler();
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
// Update handles
|
||||
|
@ -126,17 +234,6 @@ 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()) {
|
||||
|
@ -167,6 +264,23 @@ 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()
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "qmenu.h"
|
||||
#include <QMainWindow>
|
||||
#include <QLineEdit>
|
||||
#include <qfiledialog.h>
|
||||
|
||||
enum SelectedTool {
|
||||
SELECT,
|
||||
|
@ -44,6 +45,7 @@ private:
|
|||
|
||||
void updateToolbars();
|
||||
void timerEvent(QTimerEvent*) override;
|
||||
void ConnectSelectionChangeHandler();
|
||||
|
||||
std::optional<std::string> openFileDialog(QString filter, QString defaultExtension, QFileDialog::AcceptMode acceptMode, QString title = "");
|
||||
};
|
||||
#endif // MAINWINDOW_H
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1027</width>
|
||||
<height>22</height>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
|
@ -115,6 +115,12 @@
|
|||
<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"/>
|
||||
|
@ -315,6 +321,113 @@
|
|||
<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>
|
||||
|
|
|
@ -234,6 +234,12 @@ 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();
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
#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
|
||||
|
@ -42,6 +36,8 @@ 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);
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
#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))) {
|
||||
|
@ -57,8 +60,6 @@ ExplorerView::ExplorerView(QWidget* parent):
|
|||
this->selectionModel()->select(index, QItemSelectionModel::SelectionFlag::Select);
|
||||
}
|
||||
});
|
||||
|
||||
buildContextMenu();
|
||||
}
|
||||
|
||||
ExplorerView::~ExplorerView() {
|
||||
|
@ -67,19 +68,23 @@ ExplorerView::~ExplorerView() {
|
|||
void ExplorerView::keyPressEvent(QKeyEvent* event) {
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Delete:
|
||||
actionDelete->trigger();
|
||||
M_mainWindow->ui->actionDelete->trigger();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ExplorerView::buildContextMenu() {
|
||||
// This will leak memory. Anyway...
|
||||
contextMenu.addAction(this->actionDelete = new QAction(QIcon("assets/icons/editor/delete"), "Delete"));
|
||||
|
||||
connect(actionDelete, &QAction::triggered, this, [&]() {
|
||||
QModelIndexList selectedIndexes = this->selectionModel()->selectedIndexes();
|
||||
for (QModelIndex index : selectedIndexes) {
|
||||
model.fromIndex(index)->SetParent(std::nullopt);
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
|
||||
void ExplorerView::updateRoot(InstanceRef newRoot) {
|
||||
model.updateRoot(newRoot);
|
||||
}
|
|
@ -20,18 +20,10 @@ 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();
|
||||
};
|
|
@ -56,10 +56,10 @@ bool PropertiesModel::setData(const QModelIndex &index, const QVariant &value, i
|
|||
|
||||
switch (role) {
|
||||
case Qt::EditRole:
|
||||
if (meta.type != &Data::String::TYPE)
|
||||
if (!meta.type->fromString)
|
||||
return false;
|
||||
|
||||
selectedItem->SetPropertyValue(propertyName, value.toString().toStdString());
|
||||
selectedItem->SetPropertyValue(propertyName, meta.type->fromString(value.toString().toStdString()));
|
||||
return true;
|
||||
case Qt::CheckStateRole:
|
||||
if (meta.type != &Data::Bool::TYPE)
|
||||
|
|
4
run.sh
4
run.sh
|
@ -1,7 +1,9 @@
|
|||
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 $CMAKE_OPTS . && cmake --build . && $PRE_COMMAND ./bin/$1
|
||||
cmake -Bbuild $CMAKE_OPTS . && (cd build; cmake --build .; cd ..) && $PRE_COMMAND ./build/bin/$1
|
||||
|
|
Loading…
Reference in a new issue