Compare commits
No commits in common. "master" and "feature/studio-tools" have entirely different histories.
master
...
feature/st
17
.gitignore
vendored
17
.gitignore
vendored
|
@ -1,14 +1,19 @@
|
||||||
/bin/
|
bin/
|
||||||
/lib/
|
lib/
|
||||||
/build/
|
CMakeFiles/
|
||||||
|
cmake_install.cmake
|
||||||
|
CMakeCache.txt
|
||||||
|
Makefile
|
||||||
|
|
||||||
# Qt
|
# Qt
|
||||||
/*.pro.user*
|
.lupdate/
|
||||||
/CMakeLists.txt.user*
|
*.pro.user*
|
||||||
|
CMakeLists.txt.user*
|
||||||
|
*_autogen/
|
||||||
|
|
||||||
# Clangd
|
# Clangd
|
||||||
/compile_commands.json
|
/compile_commands.json
|
||||||
/.cache
|
/.cache
|
||||||
|
|
||||||
# Gdb
|
# Gdb
|
||||||
/.gdb_history
|
.gdb_history
|
|
@ -1,11 +1,11 @@
|
||||||
cmake_minimum_required(VERSION 3.31..)
|
cmake_minimum_required(VERSION 3.5.0)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
project(openblocks VERSION 0.1.0)
|
project(openblocks VERSION 0.1.0)
|
||||||
set(OpenGL_GL_PREFERENCE "GLVND")
|
set(OpenGL_GL_PREFERENCE "GLVND")
|
||||||
|
|
||||||
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
|
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
|
||||||
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
|
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)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,6 @@ uniform int numPointLights;
|
||||||
uniform DirLight sunLight;
|
uniform DirLight sunLight;
|
||||||
uniform Material material;
|
uniform Material material;
|
||||||
uniform sampler2DArray studs;
|
uniform sampler2DArray studs;
|
||||||
uniform float transparency;
|
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
|
|
||||||
|
@ -73,7 +72,7 @@ void main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
vec4 studPx = texture(studs, vec3(vTexCoords, vSurfaceZ));
|
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) {
|
vec3 calculateDirectionalLight(DirLight light) {
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
find_package(SDL2 REQUIRED)
|
|
||||||
include_directories(${SDL2_INCLUDE_DIRS})
|
|
||||||
|
|
||||||
find_package(glfw3 REQUIRED)
|
find_package(glfw3 REQUIRED)
|
||||||
|
|
||||||
add_executable(client "src/main.cpp")
|
add_executable(client "src/main.cpp")
|
||||||
target_link_libraries(client PRIVATE ${SDL2_LIBRARIES} openblocks glfw)
|
target_link_libraries(client PRIVATE openblocks glfw)
|
|
@ -1,15 +1,24 @@
|
||||||
find_package(OpenGL REQUIRED COMPONENTS OpenGL)
|
find_package(OpenGL REQUIRED COMPONENTS OpenGL)
|
||||||
|
|
||||||
|
find_package(SDL2 REQUIRED)
|
||||||
|
include_directories(${SDL2_INCLUDE_DIRS})
|
||||||
|
|
||||||
find_package(GLEW REQUIRED)
|
find_package(GLEW REQUIRED)
|
||||||
include_directories(${GLEW_INCLUDE_DIRS})
|
include_directories(${GLEW_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
find_package(GLUT REQUIRED)
|
||||||
|
include_directories(${GLUT_INCLUDE_DIRS})
|
||||||
|
|
||||||
find_package(OpenGL)
|
find_package(OpenGL)
|
||||||
find_package(glm CONFIG REQUIRED)
|
find_package(glm CONFIG REQUIRED)
|
||||||
|
# find_package(assimp REQUIRED)
|
||||||
find_package(ReactPhysics3D REQUIRED)
|
find_package(ReactPhysics3D REQUIRED)
|
||||||
find_package(pugixml REQUIRED)
|
find_package(pugixml REQUIRED)
|
||||||
|
|
||||||
|
file(MAKE_DIRECTORY bin)
|
||||||
|
|
||||||
file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h")
|
file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h")
|
||||||
add_library(openblocks ${SOURCES})
|
add_library(openblocks ${SOURCES})
|
||||||
set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks")
|
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")
|
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) {} \
|
#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::~CLASS_NAME() = default; \
|
||||||
Data::CLASS_NAME::operator const WRAPPED_TYPE() const { return value; } \
|
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; }; \
|
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())); }
|
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());
|
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 {
|
const Data::String Data::Int::ToString() const {
|
||||||
return Data::String(std::to_string(value));
|
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());
|
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 {
|
const Data::String Data::Float::ToString() const {
|
||||||
return Data::String(std::to_string(value));
|
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());
|
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 {
|
const Data::String Data::String::ToString() const {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
@ -83,7 +68,3 @@ const Data::String Data::String::ToString() const {
|
||||||
Data::Variant Data::String::Deserialize(pugi::xml_node* node) {
|
Data::Variant Data::String::Deserialize(pugi::xml_node* node) {
|
||||||
return Data::String(node->text().as_string());
|
return Data::String(node->text().as_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
Data::Variant Data::String::FromString(std::string string) {
|
|
||||||
return Data::String(string);
|
|
||||||
}
|
|
|
@ -16,18 +16,15 @@ public: \
|
||||||
virtual const Data::String ToString() const override; \
|
virtual const Data::String ToString() const override; \
|
||||||
virtual void Serialize(pugi::xml_node* node) const override; \
|
virtual void Serialize(pugi::xml_node* node) const override; \
|
||||||
static Data::Variant Deserialize(pugi::xml_node* node); \
|
static Data::Variant Deserialize(pugi::xml_node* node); \
|
||||||
static Data::Variant FromString(std::string); \
|
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class Variant;
|
class Variant;
|
||||||
typedef std::function<Data::Variant(pugi::xml_node*)> Deserializer;
|
typedef std::function<Data::Variant(pugi::xml_node*)> Deserializer;
|
||||||
typedef std::function<Data::Variant(std::string)> FromString;
|
|
||||||
|
|
||||||
struct TypeInfo {
|
struct TypeInfo {
|
||||||
std::string name;
|
std::string name;
|
||||||
Deserializer deserializer;
|
Deserializer deserializer;
|
||||||
FromString fromString;
|
|
||||||
TypeInfo(const TypeInfo&) = delete;
|
TypeInfo(const TypeInfo&) = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
// #include "meta.h" // IWYU pragma: keep
|
// #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::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)
|
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)
|
: translation(x, y, z)
|
||||||
|
|
|
@ -31,7 +31,6 @@ namespace Data {
|
||||||
~CFrame();
|
~CFrame();
|
||||||
|
|
||||||
static const CFrame IDENTITY;
|
static const CFrame IDENTITY;
|
||||||
static const CFrame YToZ;
|
|
||||||
|
|
||||||
virtual const TypeInfo& GetType() const override;
|
virtual const TypeInfo& GetType() const override;
|
||||||
static const TypeInfo TYPE;
|
static const TypeInfo TYPE;
|
||||||
|
|
|
@ -67,9 +67,8 @@ std::optional<HandleFace> Handles::RaycastHandle(rp3d::Ray ray) {
|
||||||
for (HandleFace face : HandleFace::Faces) {
|
for (HandleFace face : HandleFace::Faces) {
|
||||||
Data::CFrame cframe = GetCFrameOfHandle(face);
|
Data::CFrame cframe = GetCFrameOfHandle(face);
|
||||||
// Implement manual detection via boxes instead of... this shit
|
// 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(cframe);
|
||||||
rp3d::RigidBody* body = world->createRigidBody(Data::CFrame::IDENTITY + cframe.Position());
|
body->addCollider(common.createBoxShape(Data::Vector3(.5, .5, .5)), rp3d::Transform::identity());
|
||||||
body->addCollider(common.createBoxShape(cframe.Rotation() * Data::Vector3(HandleSize(face) / 2.f)), rp3d::Transform::identity());
|
|
||||||
|
|
||||||
rp3d::RaycastInfo info;
|
rp3d::RaycastInfo info;
|
||||||
if (body->raycast(ray, info)) {
|
if (body->raycast(ray, info)) {
|
||||||
|
@ -82,9 +81,3 @@ std::optional<HandleFace> Handles::RaycastHandle(rp3d::Ray ray) {
|
||||||
|
|
||||||
return std::nullopt;
|
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,20 +24,12 @@ class HandleFace {
|
||||||
static std::array<HandleFace, 6> Faces;
|
static std::array<HandleFace, 6> Faces;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum HandlesType {
|
|
||||||
MoveHandles,
|
|
||||||
ScaleHandles,
|
|
||||||
RotateHandles,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Handles : public Instance {
|
class Handles : public Instance {
|
||||||
public:
|
public:
|
||||||
const static InstanceType TYPE;
|
const static InstanceType TYPE;
|
||||||
|
|
||||||
bool active;
|
bool active;
|
||||||
std::optional<std::weak_ptr<Part>> adornee;
|
std::optional<std::weak_ptr<Part>> adornee;
|
||||||
HandlesType handlesType;
|
|
||||||
|
|
||||||
// inline std::optional<std::weak_ptr<Part>> GetAdornee() { return adornee; }
|
// inline std::optional<std::weak_ptr<Part>> GetAdornee() { return adornee; }
|
||||||
// inline void SetAdornee(std::optional<std::weak_ptr<Part>> newAdornee) { this->adornee = newAdornee; updateAdornee(); };
|
// inline void SetAdornee(std::optional<std::weak_ptr<Part>> newAdornee) { this->adornee = newAdornee; updateAdornee(); };
|
||||||
|
|
||||||
|
@ -47,7 +39,6 @@ public:
|
||||||
bool worldMode = false;
|
bool worldMode = false;
|
||||||
Data::CFrame GetCFrameOfHandle(HandleFace face);
|
Data::CFrame GetCFrameOfHandle(HandleFace face);
|
||||||
Data::CFrame PartCFrameFromHandlePos(HandleFace face, Data::Vector3 newPos);
|
Data::CFrame PartCFrameFromHandlePos(HandleFace face, Data::Vector3 newPos);
|
||||||
Data::Vector3 HandleSize(HandleFace face);
|
|
||||||
std::optional<HandleFace> RaycastHandle(rp3d::Ray ray);
|
std::optional<HandleFace> RaycastHandle(rp3d::Ray ray);
|
||||||
|
|
||||||
static inline std::shared_ptr<Handles> New() { return std::make_shared<Handles>(); };
|
static inline std::shared_ptr<Handles> New() { return std::make_shared<Handles>(); };
|
||||||
|
|
|
@ -95,10 +95,6 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(par
|
||||||
.backingField = &color,
|
.backingField = &color,
|
||||||
.type = &Data::Color3::TYPE,
|
.type = &Data::Color3::TYPE,
|
||||||
.codec = fieldCodecOf<Data::Color3>(),
|
.codec = fieldCodecOf<Data::Color3>(),
|
||||||
}}, { "Transparency", {
|
|
||||||
.backingField = &transparency,
|
|
||||||
.type = &Data::Float::TYPE,
|
|
||||||
.codec = fieldCodecOf<Data::Float, float>(),
|
|
||||||
} }
|
} }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,7 +32,6 @@ public:
|
||||||
Data::CFrame cframe;
|
Data::CFrame cframe;
|
||||||
glm::vec3 size;
|
glm::vec3 size;
|
||||||
Data::Color3 color;
|
Data::Color3 color;
|
||||||
float transparency = 0.f;
|
|
||||||
bool selected = false;
|
bool selected = false;
|
||||||
|
|
||||||
bool anchored = false;
|
bool anchored = false;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -67,11 +67,6 @@ void renderInit(GLFWwindow* window, int width, int height) {
|
||||||
|
|
||||||
void renderParts() {
|
void renderParts() {
|
||||||
glDepthMask(GL_TRUE);
|
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
|
// Use shader
|
||||||
shader->use();
|
shader->use();
|
||||||
|
@ -116,17 +111,12 @@ void renderParts() {
|
||||||
// Pass in the camera position
|
// Pass in the camera position
|
||||||
shader->set("viewPos", camera.cameraPos);
|
shader->set("viewPos", camera.cameraPos);
|
||||||
|
|
||||||
// Sort by nearest
|
// TODO: Same as todo in src/physics/simulation.cpp
|
||||||
std::map<float, std::shared_ptr<Part>> sorted;
|
|
||||||
for (InstanceRef inst : workspace()->GetChildren()) {
|
for (InstanceRef inst : workspace()->GetChildren()) {
|
||||||
if (inst->GetClass()->className != "Part") continue;
|
if (inst->GetClass()->className != "Part") continue;
|
||||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
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;
|
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);
|
model = glm::scale(model, part->size);
|
||||||
shader->set("model", model);
|
shader->set("model", model);
|
||||||
shader->set("material", Material {
|
shader->set("material", Material {
|
||||||
|
@ -137,39 +127,14 @@ void renderParts() {
|
||||||
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
|
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
|
||||||
shader->set("normalMatrix", normalMatrix);
|
shader->set("normalMatrix", normalMatrix);
|
||||||
shader->set("texScale", part->size);
|
shader->set("texScale", part->size);
|
||||||
shader->set("transparency", part->transparency);
|
|
||||||
|
|
||||||
CUBE_MESH->bind();
|
CUBE_MESH->bind();
|
||||||
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
|
glDrawArrays(GL_TRIANGLES, 0, 36);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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() {
|
void renderSkyBox() {
|
||||||
glDepthMask(GL_FALSE);
|
glDepthMask(GL_FALSE);
|
||||||
glCullFace(GL_FRONT);
|
|
||||||
|
|
||||||
skyboxShader->use();
|
skyboxShader->use();
|
||||||
|
|
||||||
|
@ -190,7 +155,6 @@ void renderHandles() {
|
||||||
if (!editorToolHandles->adornee.has_value() || !editorToolHandles->active) return;
|
if (!editorToolHandles->adornee.has_value() || !editorToolHandles->active) return;
|
||||||
|
|
||||||
glDepthMask(GL_TRUE);
|
glDepthMask(GL_TRUE);
|
||||||
glCullFace(GL_BACK);
|
|
||||||
|
|
||||||
// Use shader
|
// Use shader
|
||||||
handleShader->use();
|
handleShader->use();
|
||||||
|
@ -213,7 +177,7 @@ void renderHandles() {
|
||||||
|
|
||||||
for (auto face : HandleFace::Faces) {
|
for (auto face : HandleFace::Faces) {
|
||||||
glm::mat4 model = editorToolHandles->GetCFrameOfHandle(face);
|
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("model", model);
|
||||||
handleShader->set("material", Material {
|
handleShader->set("material", Material {
|
||||||
.diffuse = glm::abs(face.normal),
|
.diffuse = glm::abs(face.normal),
|
||||||
|
@ -223,18 +187,11 @@ void renderHandles() {
|
||||||
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
|
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
|
||||||
handleShader->set("normalMatrix", normalMatrix);
|
handleShader->set("normalMatrix", normalMatrix);
|
||||||
|
|
||||||
if (editorToolHandles->handlesType == HandlesType::MoveHandles) {
|
|
||||||
ARROW_MESH->bind();
|
ARROW_MESH->bind();
|
||||||
glDrawArrays(GL_TRIANGLES, 0, ARROW_MESH->vertexCount);
|
glDrawArrays(GL_TRIANGLES, 0, ARROW_MESH->vertexCount);
|
||||||
} else {
|
|
||||||
SPHERE_MESH->bind();
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, SPHERE_MESH->vertexCount);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2d square overlay
|
// 2d square overlay
|
||||||
glDisable(GL_CULL_FACE);
|
|
||||||
|
|
||||||
identityShader->use();
|
identityShader->use();
|
||||||
identityShader->set("aColor", glm::vec3(0.f, 1.f, 1.f));
|
identityShader->set("aColor", glm::vec3(0.f, 1.f, 1.f));
|
||||||
|
|
||||||
|
|
9
deps.txt
9
deps.txt
|
@ -1,9 +1,10 @@
|
||||||
opengl (Linux: glvnd, Windows: [built-in/none])
|
|
||||||
glfw
|
|
||||||
glew
|
|
||||||
glm
|
glm
|
||||||
|
opengl
|
||||||
|
assimp
|
||||||
sdl2
|
sdl2
|
||||||
stb
|
glfw
|
||||||
|
glut
|
||||||
|
glew
|
||||||
qt6
|
qt6
|
||||||
reactphysics3d
|
reactphysics3d
|
||||||
pugixml
|
pugixml
|
|
@ -1,4 +1,4 @@
|
||||||
cmake_minimum_required(VERSION 3.31..)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project(editor VERSION 0.1 LANGUAGES CXX)
|
project(editor VERSION 0.1 LANGUAGES CXX)
|
||||||
|
|
||||||
|
@ -65,16 +65,6 @@ endif()
|
||||||
target_include_directories(editor PUBLIC "../core/src" "../include")
|
target_include_directories(editor PUBLIC "../core/src" "../include")
|
||||||
target_link_libraries(editor PRIVATE openblocks Qt${QT_VERSION_MAJOR}::Widgets)
|
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.
|
# 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
|
# If you are developing for iOS or macOS you should consider setting an
|
||||||
# explicit, fixed bundle identifier manually though.
|
# explicit, fixed bundle identifier manually though.
|
||||||
|
@ -96,8 +86,6 @@ install(TARGETS editor
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
install(FILES $<TARGET_RUNTIME_DLLS:editor> TYPE BIN)
|
|
||||||
|
|
||||||
if(QT_VERSION_MAJOR EQUAL 6)
|
if(QT_VERSION_MAJOR EQUAL 6)
|
||||||
qt_finalize_executable(editor)
|
qt_finalize_executable(editor)
|
||||||
endif()
|
endif()
|
|
@ -138,9 +138,7 @@ void MainGLWidget::handleHandleDrag(QMouseEvent* evt) {
|
||||||
|
|
||||||
// Apply snapping in the current frame
|
// Apply snapping in the current frame
|
||||||
glm::vec3 diff = centerPoint - (glm::vec3)editorToolHandles->adornee->lock()->position();
|
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 * (glm::round(glm::vec3(frame.Inverse() * diff) / snappingFactor()) * snappingFactor());
|
||||||
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) {
|
switch (mainWindow()->selectedTool) {
|
||||||
case SelectedTool::SELECT: break;
|
case SelectedTool::SELECT: break;
|
||||||
|
|
|
@ -12,20 +12,15 @@
|
||||||
#include <QAbstractItemView>
|
#include <QAbstractItemView>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <qglobal.h>
|
|
||||||
#include <qwindowdefs.h>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "editorcommon.h"
|
#include "editorcommon.h"
|
||||||
#include "objects/base/instance.h"
|
#include "objects/base/instance.h"
|
||||||
#include "objects/datamodel.h"
|
#include "objects/datamodel.h"
|
||||||
#include "objects/handles.h"
|
|
||||||
#include "physics/simulation.h"
|
#include "physics/simulation.h"
|
||||||
#include "objects/part.h"
|
#include "objects/part.h"
|
||||||
#include "qfiledialog.h"
|
#include "qfiledialog.h"
|
||||||
#include "qclipboard.h"
|
#include "qitemselectionmodel.h"
|
||||||
#include "qmimedata.h"
|
|
||||||
#include "qobject.h"
|
#include "qobject.h"
|
||||||
#include "qsysinfo.h"
|
#include "qsysinfo.h"
|
||||||
|
|
||||||
|
@ -41,7 +36,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
timer.start(33, this);
|
timer.start(33, this);
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
|
|
||||||
ui->explorerView->buildContextMenu();
|
ConnectSelectionChangeHandler();
|
||||||
|
|
||||||
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = SelectedTool::SELECT; updateToolbars(); });
|
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(); });
|
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, [&]() {
|
connect(ui->actionSave, &QAction::triggered, this, [&]() {
|
||||||
std::optional<std::string> path;
|
std::optional<std::string> path;
|
||||||
if (!dataModel->HasFile())
|
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;
|
if (path == "") return;
|
||||||
|
|
||||||
dataModel->SaveToFile(path);
|
dataModel->SaveToFile(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ui->actionOpen, &QAction::triggered, this, [&]() {
|
connect(ui->actionOpen, &QAction::triggered, this, [&]() {
|
||||||
std::optional<std::string> path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptOpen);
|
std::string path = QFileDialog::getOpenFileName(this, "Load file", "", "*.obl").toStdString();
|
||||||
if (!path) return;
|
if (path == "") return;
|
||||||
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value());
|
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path);
|
||||||
dataModel = newModel;
|
dataModel = newModel;
|
||||||
ui->explorerView->updateRoot(newModel);
|
delete ui->explorerView->selectionModel();
|
||||||
});
|
ui->explorerView->reset();
|
||||||
|
ui->explorerView->setModel(new ExplorerModel(dataModel));
|
||||||
connect(ui->actionDelete, &QAction::triggered, this, [&]() {
|
ConnectSelectionChangeHandler();
|
||||||
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
|
// Update handles
|
||||||
|
@ -234,6 +126,17 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
syncPartPhysics(ui->mainWidget->lastPart);
|
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();
|
static std::chrono::time_point lastTime = std::chrono::steady_clock::now();
|
||||||
void MainWindow::timerEvent(QTimerEvent* evt) {
|
void MainWindow::timerEvent(QTimerEvent* evt) {
|
||||||
if (evt->timerId() != timer.timerId()) {
|
if (evt->timerId() != timer.timerId()) {
|
||||||
|
@ -264,23 +167,6 @@ void MainWindow::updateToolbars() {
|
||||||
if (selectedTool == SelectedTool::MOVE) editorToolHandles->worldMode = true;
|
if (selectedTool == SelectedTool::MOVE) editorToolHandles->worldMode = true;
|
||||||
if (selectedTool == SelectedTool::SCALE) editorToolHandles->worldMode = false;
|
if (selectedTool == SelectedTool::SCALE) editorToolHandles->worldMode = false;
|
||||||
editorToolHandles->active = selectedTool != SelectedTool::SELECT;
|
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()
|
MainWindow::~MainWindow()
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include "qmenu.h"
|
#include "qmenu.h"
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <qfiledialog.h>
|
|
||||||
|
|
||||||
enum SelectedTool {
|
enum SelectedTool {
|
||||||
SELECT,
|
SELECT,
|
||||||
|
@ -45,7 +44,6 @@ private:
|
||||||
|
|
||||||
void updateToolbars();
|
void updateToolbars();
|
||||||
void timerEvent(QTimerEvent*) override;
|
void timerEvent(QTimerEvent*) override;
|
||||||
|
void ConnectSelectionChangeHandler();
|
||||||
std::optional<std::string> openFileDialog(QString filter, QString defaultExtension, QFileDialog::AcceptMode acceptMode, QString title = "");
|
|
||||||
};
|
};
|
||||||
#endif // MAINWINDOW_H
|
#endif // MAINWINDOW_H
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1027</width>
|
<width>1027</width>
|
||||||
<height>30</height>
|
<height>22</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuFile">
|
<widget class="QMenu" name="menuFile">
|
||||||
|
@ -115,12 +115,6 @@
|
||||||
<addaction name="actionToolScale"/>
|
<addaction name="actionToolScale"/>
|
||||||
<addaction name="actionToolRotate"/>
|
<addaction name="actionToolRotate"/>
|
||||||
<addaction name="separator"/>
|
<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="actionGridSnap1"/>
|
||||||
<addaction name="actionGridSnap05"/>
|
<addaction name="actionGridSnap05"/>
|
||||||
<addaction name="actionGridSnapOff"/>
|
<addaction name="actionGridSnapOff"/>
|
||||||
|
@ -321,113 +315,6 @@
|
||||||
<enum>QAction::MenuRole::NoRole</enum>
|
<enum>QAction::MenuRole::NoRole</enum>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</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>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|
|
@ -234,12 +234,6 @@ bool ExplorerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExplorerModel::updateRoot(InstanceRef newRoot) {
|
|
||||||
beginResetModel();
|
|
||||||
rootItem = newRoot;
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
QMimeData* ExplorerModel::mimeData(const QModelIndexList& indexes) const {
|
QMimeData* ExplorerModel::mimeData(const QModelIndexList& indexes) const {
|
||||||
// application/x-openblocks-instance-pointers
|
// application/x-openblocks-instance-pointers
|
||||||
DragDropSlot* slot = new DragDropSlot();
|
DragDropSlot* slot = new DragDropSlot();
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "objects/base/instance.h"
|
#include "objects/base/instance.h"
|
||||||
|
#include "objects/part.h"
|
||||||
#include "qabstractitemmodel.h"
|
#include "qabstractitemmodel.h"
|
||||||
#include "qevent.h"
|
#include "qevent.h"
|
||||||
|
#include "qmenu.h"
|
||||||
#include "qnamespace.h"
|
#include "qnamespace.h"
|
||||||
|
#include "qtreeview.h"
|
||||||
#include <QOpenGLWidget>
|
#include <QOpenGLWidget>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// #ifndef EXPLORERMODEL_H
|
||||||
|
// #define EXPLORERMODEL_H
|
||||||
|
|
||||||
class ExplorerModel : public QAbstractItemModel {
|
class ExplorerModel : public QAbstractItemModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -36,8 +42,6 @@ public:
|
||||||
Qt::DropActions supportedDropActions() const override;
|
Qt::DropActions supportedDropActions() const override;
|
||||||
InstanceRef fromIndex(const QModelIndex index) const;
|
InstanceRef fromIndex(const QModelIndex index) const;
|
||||||
QModelIndex ObjectToIndex(InstanceRef item);
|
QModelIndex ObjectToIndex(InstanceRef item);
|
||||||
|
|
||||||
void updateRoot(InstanceRef newRoot);
|
|
||||||
private:
|
private:
|
||||||
InstanceRef rootItem;
|
InstanceRef rootItem;
|
||||||
QModelIndex toIndex(InstanceRef item);
|
QModelIndex toIndex(InstanceRef item);
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
#include "explorerview.h"
|
#include "explorerview.h"
|
||||||
#include "explorermodel.h"
|
#include "explorermodel.h"
|
||||||
#include "mainwindow.h"
|
|
||||||
#include "../ui_mainwindow.h"
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "objects/base/instance.h"
|
#include "objects/base/instance.h"
|
||||||
|
#include "objects/workspace.h"
|
||||||
#include "qabstractitemmodel.h"
|
#include "qabstractitemmodel.h"
|
||||||
#include <qaction.h>
|
#include "qaction.h"
|
||||||
#include <qnamespace.h>
|
#include "qnamespace.h"
|
||||||
#include <qitemselectionmodel.h>
|
#include <qitemselectionmodel.h>
|
||||||
|
|
||||||
#define M_mainWindow dynamic_cast<MainWindow*>(window())
|
|
||||||
|
|
||||||
ExplorerView::ExplorerView(QWidget* parent):
|
ExplorerView::ExplorerView(QWidget* parent):
|
||||||
QTreeView(parent),
|
QTreeView(parent),
|
||||||
model(ExplorerModel(std::dynamic_pointer_cast<Instance>(dataModel))) {
|
model(ExplorerModel(std::dynamic_pointer_cast<Instance>(dataModel))) {
|
||||||
|
@ -60,6 +57,8 @@ ExplorerView::ExplorerView(QWidget* parent):
|
||||||
this->selectionModel()->select(index, QItemSelectionModel::SelectionFlag::Select);
|
this->selectionModel()->select(index, QItemSelectionModel::SelectionFlag::Select);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
buildContextMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
ExplorerView::~ExplorerView() {
|
ExplorerView::~ExplorerView() {
|
||||||
|
@ -68,23 +67,19 @@ ExplorerView::~ExplorerView() {
|
||||||
void ExplorerView::keyPressEvent(QKeyEvent* event) {
|
void ExplorerView::keyPressEvent(QKeyEvent* event) {
|
||||||
switch (event->key()) {
|
switch (event->key()) {
|
||||||
case Qt::Key_Delete:
|
case Qt::Key_Delete:
|
||||||
M_mainWindow->ui->actionDelete->trigger();
|
actionDelete->trigger();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExplorerView::buildContextMenu() {
|
void ExplorerView::buildContextMenu() {
|
||||||
contextMenu.addAction(M_mainWindow->ui->actionDelete);
|
// This will leak memory. Anyway...
|
||||||
contextMenu.addSeparator();
|
contextMenu.addAction(this->actionDelete = new QAction(QIcon("assets/icons/editor/delete"), "Delete"));
|
||||||
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) {
|
connect(actionDelete, &QAction::triggered, this, [&]() {
|
||||||
model.updateRoot(newRoot);
|
QModelIndexList selectedIndexes = this->selectionModel()->selectedIndexes();
|
||||||
|
for (QModelIndex index : selectedIndexes) {
|
||||||
|
model.fromIndex(index)->SetParent(std::nullopt);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
|
@ -20,10 +20,18 @@ public:
|
||||||
|
|
||||||
void keyPressEvent(QKeyEvent*) override;
|
void keyPressEvent(QKeyEvent*) override;
|
||||||
// void dropEvent(QDropEvent*) override;
|
// void dropEvent(QDropEvent*) override;
|
||||||
|
|
||||||
void buildContextMenu();
|
|
||||||
void updateRoot(InstanceRef newRoot);
|
|
||||||
private:
|
private:
|
||||||
ExplorerModel model;
|
ExplorerModel model;
|
||||||
QMenu contextMenu;
|
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) {
|
switch (role) {
|
||||||
case Qt::EditRole:
|
case Qt::EditRole:
|
||||||
if (!meta.type->fromString)
|
if (meta.type != &Data::String::TYPE)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
selectedItem->SetPropertyValue(propertyName, meta.type->fromString(value.toString().toStdString()));
|
selectedItem->SetPropertyValue(propertyName, value.toString().toStdString());
|
||||||
return true;
|
return true;
|
||||||
case Qt::CheckStateRole:
|
case Qt::CheckStateRole:
|
||||||
if (meta.type != &Data::Bool::TYPE)
|
if (meta.type != &Data::Bool::TYPE)
|
||||||
|
|
4
run.sh
4
run.sh
|
@ -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" = "-debug" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=Debug
|
||||||
[ "$2" = "-release" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=Release
|
[ "$2" = "-release" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=Release
|
||||||
[ "$2" = "-reldbg" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=RelWithDebInfo
|
[ "$2" = "-reldbg" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
|
|
||||||
[ "$3" = "-gdb" ] && PRE_COMMAND="gdb -ex run "
|
[ "$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
|
||||||
|
|
Loading…
Reference in a new issue