Compare commits

...

9 commits

39 changed files with 616 additions and 520 deletions

49
BUILD.md Normal file
View file

@ -0,0 +1,49 @@
# Building on Linux
For pre-requisite packages, check [deps.txt](./deps.txt)
If your distribution does not provide ReactPhysics3D, you will have to build (and install) it yourself prior to this step
Use the following to generate the build files:
cmake -Bbuild .
Then build the project using:
cmake --build build
# Building on Windows
The project will be built using VCPKG and MSVC
## Pre-requisites
* Visual Studio 17.13.3 or higher, with MSVC v143/14.42.34438.0 or higher
* Qt 6.8.3 or higher, with MSVC toolchain
* CMake
* Git (for cloning the repo, optional)
* ReactPhysics3D prebuilt library
You will have to build and install ReactPhysics3D yourself. Check the [build instructions on ReactPhysics3D](https://www.reactphysics3d.com/documentation/index.html#building) for help
To start, clone the repository:
git clone https://git.maelstrom.dev/maelstrom/openblocks
Then, launch the *x64 Native Tools Command Prompt for VS 2022*, and cd into the directory that you cloned the project into
Once in the directory, add Qt to the `CMAKE_PREFIX_PATH` variable (for instance, if you install Qt to `C:\Qt\6.8.3`):
set CMAKE_PREFIX_PATH=C:\Qt\6.8.3\msvc2022_64
Now, generate the build files with cmake via the vcpkg preset:
cmake -Bbuild . --preset vcpkg
Then, finally, build in release mode:
cmake --build build --config Release
The compiled binaries should then be placed in `./build/bin/` and should be ready for redistribution without any further work.
If any of the compilation steps fail, or the binaries fail to execute, please create an issue so that this can be corrected.

5
README.md Normal file
View file

@ -0,0 +1,5 @@
# Openblocks
[ Docs are work in progress! ]
For build instructions, see [BUILD.md](./BUILD.md)

View file

@ -1,20 +1,7 @@
#include <GL/glew.h> #include <GL/glew.h>
#include <GL/gl.h>
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <glm/ext/matrix_transform.hpp>
#include <glm/ext/quaternion_trigonometric.hpp>
#include <glm/ext/vector_float3.hpp>
#include <glm/gtc/quaternion.hpp>
#include <memory>
#include <stdio.h>
#include <format>
#include "logger.h"
#include "objects/part.h" #include "objects/part.h"
#include "rendering/renderer.h" #include "rendering/renderer.h"
#include "physics/simulation.h"
#include "camera.h"
#include "common.h" #include "common.h"
void errorCatcher(int id, const char* str); void errorCatcher(int id, const char* str);
@ -46,7 +33,6 @@ int main() {
glewInit(); glewInit();
gDataModel->Init(); gDataModel->Init();
simulationInit();
renderInit(window, 1200, 900); renderInit(window, 1200, 900);
// Baseplate // Baseplate
@ -68,7 +54,7 @@ int main() {
for (InstanceRef inst : gWorkspace()->GetChildren()) { for (InstanceRef inst : gWorkspace()->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);
syncPartPhysics(part); gWorkspace()->SyncPartPhysics(part);
} }
float lastTime = glfwGetTime(); float lastTime = glfwGetTime();
@ -77,7 +63,7 @@ int main() {
lastTime = glfwGetTime(); lastTime = glfwGetTime();
processInput(window); processInput(window);
physicsStep(deltaTime); gWorkspace()->PhysicsStep(deltaTime);
render(window); render(window);
glfwSwapBuffers(window); glfwSwapBuffers(window);
@ -115,15 +101,15 @@ void processInput(GLFWwindow* window) {
shiftFactor *= deltaTime; shiftFactor *= deltaTime;
if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS) { if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS) {
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(1, 0, 0)); // lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(1, 0, 0));
syncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
if (glfwGetKey(window, GLFW_KEY_Y) == GLFW_PRESS) { if (glfwGetKey(window, GLFW_KEY_Y) == GLFW_PRESS) {
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 1, 0)); // lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 1, 0));
syncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS) { if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS) {
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 0, 1)); // lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 0, 1));
syncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
} }
} }
@ -164,35 +150,35 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods
.size = glm::vec3(1, 1, 1), .size = glm::vec3(1, 1, 1),
.color = glm::vec3(1.0f, 0.5f, 0.31f), .color = glm::vec3(1.0f, 0.5f, 0.31f),
})); }));
syncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
float shiftFactor = (mods & GLFW_MOD_SHIFT) ? -0.2 : 0.2; float shiftFactor = (mods & GLFW_MOD_SHIFT) ? -0.2 : 0.2;
if (mode == 0) { if (mode == 0) {
if (key == GLFW_KEY_X && action == GLFW_PRESS) { if (key == GLFW_KEY_X && action == GLFW_PRESS) {
// lastPart->position.x += shiftFactor; // lastPart->position.x += shiftFactor;
syncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
if (key == GLFW_KEY_Y && action == GLFW_PRESS) { if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
// lastPart->position.y += shiftFactor; // lastPart->position.y += shiftFactor;
syncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
if (key == GLFW_KEY_Z && action == GLFW_PRESS) { if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
// lastPart->position.z += shiftFactor; // lastPart->position.z += shiftFactor;
syncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
} else if (mode == 1) { } else if (mode == 1) {
if (key == GLFW_KEY_X && action == GLFW_PRESS) { if (key == GLFW_KEY_X && action == GLFW_PRESS) {
lastPart->size.x += shiftFactor; lastPart->size.x += shiftFactor;
syncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
if (key == GLFW_KEY_Y && action == GLFW_PRESS) { if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
lastPart->size.y += shiftFactor; lastPart->size.y += shiftFactor;
syncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
if (key == GLFW_KEY_Z && action == GLFW_PRESS) { if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
lastPart->size.z += shiftFactor; lastPart->size.z += shiftFactor;
syncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
} }

View file

@ -6,7 +6,7 @@ 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, .fromString = &Data::CLASS_NAME::FromString }; \
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())); }
Data::Base::~Base() {}; Data::Base::~Base() {};
@ -22,11 +22,11 @@ const Data::String Data::Null::ToString() const {
return Data::String("null"); return Data::String("null");
} }
void Data::Null::Serialize(pugi::xml_node* node) const { void Data::Null::Serialize(pugi::xml_node node) const {
node->text().set("null"); node.text().set("null");
} }
Data::Variant Data::Null::Deserialize(pugi::xml_node* node) { Data::Variant Data::Null::Deserialize(pugi::xml_node node) {
return Data::Null(); return Data::Null();
} }
@ -41,8 +41,8 @@ const Data::String Data::Bool::ToString() const {
return Data::String(value ? "true" : "false"); return Data::String(value ? "true" : "false");
} }
Data::Variant Data::Bool::Deserialize(pugi::xml_node* node) { 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) { Data::Variant Data::Bool::FromString(std::string string) {
@ -54,8 +54,8 @@ const Data::String Data::Int::ToString() const {
return Data::String(std::to_string(value)); return Data::String(std::to_string(value));
} }
Data::Variant Data::Int::Deserialize(pugi::xml_node* node) { 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) { Data::Variant Data::Int::FromString(std::string string) {
@ -67,8 +67,8 @@ const Data::String Data::Float::ToString() const {
return Data::String(std::to_string(value)); return Data::String(std::to_string(value));
} }
Data::Variant Data::Float::Deserialize(pugi::xml_node* node) { 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) { Data::Variant Data::Float::FromString(std::string string) {
@ -80,8 +80,8 @@ const Data::String Data::String::ToString() const {
return *this; return *this;
} }
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) { Data::Variant Data::String::FromString(std::string string) {

View file

@ -14,14 +14,14 @@ public: \
static const TypeInfo TYPE; \ static const TypeInfo TYPE; \
\ \
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); \ 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; typedef std::function<Data::Variant(std::string)> FromString;
struct TypeInfo { struct TypeInfo {
@ -36,7 +36,7 @@ namespace Data {
virtual ~Base(); virtual ~Base();
virtual const TypeInfo& GetType() const = 0; virtual const TypeInfo& GetType() const = 0;
virtual const Data::String ToString() const = 0; virtual const Data::String ToString() const = 0;
virtual void Serialize(pugi::xml_node* node) const = 0; virtual void Serialize(pugi::xml_node node) const = 0;
}; };
class Null : Base { class Null : Base {
@ -47,8 +47,8 @@ namespace Data {
static const TypeInfo TYPE; static const TypeInfo TYPE;
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);
}; };
DEF_WRAPPER_CLASS(Bool, bool) DEF_WRAPPER_CLASS(Bool, bool)

View file

@ -112,35 +112,35 @@ Data::CFrame Data::CFrame::operator -(Data::Vector3 vector) const {
// Serialization // Serialization
void Data::CFrame::Serialize(pugi::xml_node* node) const { void Data::CFrame::Serialize(pugi::xml_node node) const {
node->append_child("X").text().set(std::to_string(this->X())); node.append_child("X").text().set(std::to_string(this->X()));
node->append_child("Y").text().set(std::to_string(this->Y())); node.append_child("Y").text().set(std::to_string(this->Y()));
node->append_child("Z").text().set(std::to_string(this->Z())); node.append_child("Z").text().set(std::to_string(this->Z()));
node->append_child("R00").text().set(std::to_string(this->rotation[0][0])); node.append_child("R00").text().set(std::to_string(this->rotation[0][0]));
node->append_child("R01").text().set(std::to_string(this->rotation[1][0])); node.append_child("R01").text().set(std::to_string(this->rotation[1][0]));
node->append_child("R02").text().set(std::to_string(this->rotation[2][0])); node.append_child("R02").text().set(std::to_string(this->rotation[2][0]));
node->append_child("R10").text().set(std::to_string(this->rotation[0][1])); node.append_child("R10").text().set(std::to_string(this->rotation[0][1]));
node->append_child("R11").text().set(std::to_string(this->rotation[1][1])); node.append_child("R11").text().set(std::to_string(this->rotation[1][1]));
node->append_child("R12").text().set(std::to_string(this->rotation[2][1])); node.append_child("R12").text().set(std::to_string(this->rotation[2][1]));
node->append_child("R20").text().set(std::to_string(this->rotation[0][2])); node.append_child("R20").text().set(std::to_string(this->rotation[0][2]));
node->append_child("R21").text().set(std::to_string(this->rotation[1][2])); node.append_child("R21").text().set(std::to_string(this->rotation[1][2]));
node->append_child("R22").text().set(std::to_string(this->rotation[2][2])); node.append_child("R22").text().set(std::to_string(this->rotation[2][2]));
} }
Data::Variant Data::CFrame::Deserialize(pugi::xml_node* node) { Data::Variant Data::CFrame::Deserialize(pugi::xml_node node) {
return Data::CFrame( return Data::CFrame(
node->child("X").text().as_float(), node.child("X").text().as_float(),
node->child("Y").text().as_float(), node.child("Y").text().as_float(),
node->child("Z").text().as_float(), node.child("Z").text().as_float(),
node->child("R00").text().as_float(), node.child("R00").text().as_float(),
node->child("R01").text().as_float(), node.child("R01").text().as_float(),
node->child("R02").text().as_float(), node.child("R02").text().as_float(),
node->child("R10").text().as_float(), node.child("R10").text().as_float(),
node->child("R11").text().as_float(), node.child("R11").text().as_float(),
node->child("R12").text().as_float(), node.child("R12").text().as_float(),
node->child("R20").text().as_float(), node.child("R20").text().as_float(),
node->child("R21").text().as_float(), node.child("R21").text().as_float(),
node->child("R22").text().as_float() node.child("R22").text().as_float()
); );
} }

View file

@ -2,12 +2,8 @@
#include "base.h" #include "base.h"
#include "datatypes/vector.h" #include "datatypes/vector.h"
#include <glm/ext/matrix_float3x3.hpp> #include <glm/ext/quaternion_float.hpp>
#include <glm/ext/quaternion_geometric.hpp>
#include <glm/ext/vector_float3.hpp>
#include <glm/fwd.hpp>
#include <glm/gtc/matrix_access.hpp> #include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/matrix.hpp> #include <glm/matrix.hpp>
#include <reactphysics3d/mathematics/Transform.h> #include <reactphysics3d/mathematics/Transform.h>
#include <reactphysics3d/reactphysics3d.h> #include <reactphysics3d/reactphysics3d.h>
@ -37,8 +33,8 @@ namespace Data {
static const TypeInfo TYPE; static const TypeInfo TYPE;
virtual const Data::String ToString() const override; virtual const Data::String ToString() const override;
virtual void Serialize(pugi::xml_node* parent) const override; virtual void Serialize(pugi::xml_node parent) const override;
static Data::Variant Deserialize(pugi::xml_node* node); static Data::Variant Deserialize(pugi::xml_node node);
operator glm::mat4() const; operator glm::mat4() const;
operator rp::Transform() const; operator rp::Transform() const;

View file

@ -1,9 +1,4 @@
#include "color3.h" #include "color3.h"
#include <algorithm>
#include <glm/ext/quaternion_geometric.hpp>
#include <iomanip>
#include <ios>
#include <string>
#include "meta.h" // IWYU pragma: keep #include "meta.h" // IWYU pragma: keep
Data::Color3::Color3(float r, float g, float b) : r(std::clamp(r, 0.f, 1.f)), g(std::clamp(g, 0.f, 1.f)), b(std::clamp(b, 0.f, 1.f)) {}; Data::Color3::Color3(float r, float g, float b) : r(std::clamp(r, 0.f, 1.f)), g(std::clamp(g, 0.f, 1.f)), b(std::clamp(b, 0.f, 1.f)) {};
@ -43,10 +38,10 @@ Data::Color3 Data::Color3::FromHex(std::string hex) {
// Serialization // Serialization
void Data::Color3::Serialize(pugi::xml_node* node) const { void Data::Color3::Serialize(pugi::xml_node node) const {
node->text().set(this->ToHex()); node.text().set(this->ToHex());
} }
Data::Variant Data::Color3::Deserialize(pugi::xml_node* node) { Data::Variant Data::Color3::Deserialize(pugi::xml_node node) {
return Color3::FromHex(node->text().get()); return Color3::FromHex(node.text().get());
} }

View file

@ -25,8 +25,8 @@ namespace Data {
virtual const Data::String ToString() const override; virtual const Data::String ToString() const override;
std::string ToHex() const; std::string ToHex() const;
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);
operator glm::vec3() const; operator glm::vec3() const;

View file

@ -11,19 +11,19 @@ Data::String Data::Variant::ToString() const {
}, this->wrapped); }, this->wrapped);
} }
void Data::Variant::Serialize(pugi::xml_node* node) const { void Data::Variant::Serialize(pugi::xml_node node) const {
std::visit([&](auto&& it) { std::visit([&](auto&& it) {
it.Serialize(node); it.Serialize(node);
}, this->wrapped); }, this->wrapped);
} }
Data::Variant Data::Variant::Deserialize(pugi::xml_node* node) { Data::Variant Data::Variant::Deserialize(pugi::xml_node node) {
if (Data::TYPE_MAP.count(node->name()) == 0) { if (Data::TYPE_MAP.count(node.name()) == 0) {
Logger::fatalErrorf("Unknown type for instance: '%s'", node->name()); Logger::fatalErrorf("Unknown type for instance: '%s'", node.name());
panic(); panic();
} }
const Data::TypeInfo* type = Data::TYPE_MAP[node->name()]; const Data::TypeInfo* type = Data::TYPE_MAP[node.name()];
return type->deserializer(node); return type->deserializer(node);
} }

View file

@ -34,8 +34,8 @@ namespace Data {
template <typename T> T get() { return std::get<T>(wrapped); } template <typename T> T get() { return std::get<T>(wrapped); }
Data::String ToString() const; Data::String ToString() const;
void Serialize(pugi::xml_node* node) const; void Serialize(pugi::xml_node node) const;
static Data::Variant Deserialize(pugi::xml_node* node); static Data::Variant Deserialize(pugi::xml_node node);
}; };
// Map of all data types to their type names // Map of all data types to their type names

View file

@ -65,12 +65,12 @@ float Data::Vector3::Dot(Data::Vector3 other) const {
// Serialization // Serialization
void Data::Vector3::Serialize(pugi::xml_node* node) const { void Data::Vector3::Serialize(pugi::xml_node node) const {
node->append_child("X").text().set(std::to_string(this->X())); node.append_child("X").text().set(std::to_string(this->X()));
node->append_child("Y").text().set(std::to_string(this->Y())); node.append_child("Y").text().set(std::to_string(this->Y()));
node->append_child("Z").text().set(std::to_string(this->Z())); node.append_child("Z").text().set(std::to_string(this->Z()));
} }
Data::Variant Data::Vector3::Deserialize(pugi::xml_node* node) { Data::Variant Data::Vector3::Deserialize(pugi::xml_node node) {
return Data::Vector3(node->child("X").text().as_float(), node->child("Y").text().as_float(), node->child("Z").text().as_float()); return Data::Vector3(node.child("X").text().as_float(), node.child("Y").text().as_float(), node.child("Z").text().as_float());
} }

View file

@ -24,8 +24,8 @@ namespace Data {
static Data::Vector3 ONE; static Data::Vector3 ONE;
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);
operator glm::vec3() const; operator glm::vec3() const;
operator rp::Vector3() const; operator rp::Vector3() const;

17
core/src/error/error.cpp Normal file
View file

@ -0,0 +1,17 @@
#include "error.h"
#include "logger.h"
Error::Error(std::string message) : message(message) {
}
std::string Error::getMessage() {
return this->message;
}
void Error::logMessage(Logger::LogLevel logLevel) {
Logger::log(this->message, logLevel);
}
void Error::logMessageFatal() {
Logger::fatalError(this->message);
}

17
core/src/error/error.h Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include "logger.h"
#include <string>
// Base class for all errors
class Error {
std::string message;
protected:
Error(std::string message);
public:
std::string getMessage();
void logMessage(Logger::LogLevel logLevel = Logger::LogLevel::ERROR);
void logMessageFatal();
};

59
core/src/error/result.h Normal file
View file

@ -0,0 +1,59 @@
#pragma once
#include "error.h"
#include "logger.h"
#include "panic.h"
#include <optional>
#include <string>
#include <variant>
struct DUMMY_VALUE {};
template <typename Result, typename ...E>
class [[nodiscard]] result {
struct ErrorContainer {
std::variant<E...> error;
};
struct SuccessContainer {
Result success;
};
std::variant<SuccessContainer, ErrorContainer> value;
public:
result(Result success) : value(SuccessContainer { success }) {}
result(std::variant<E...> error) : value(ErrorContainer { error }) {}
// Expects the result to be successful, otherwise panic with error message
Result expect(std::string errMsg = "Unwrapped a result with failure value") {
if (is_success())
return std::get<SuccessContainer>(value).success;
Logger::fatalError(errMsg);
panic();
}
bool is_success() { return std::holds_alternative<SuccessContainer>(value); }
bool is_error() { return std::holds_alternative<ErrorContainer>(value); }
std::optional<Result> success() { return is_success() ? std::get<SuccessContainer>(value).success : std::nullopt; }
std::optional<std::variant<E...>> error() { return is_error() ? std::make_optional(std::get<ErrorContainer>(value).error) : std::nullopt; }
void logError(Logger::LogLevel logLevel = Logger::LogLevel::ERROR) {
if (is_success()) return;
std::visit([&](auto&& it) {
it.logMessage(logLevel);
}, error().value());
}
// Equivalent to .success
operator std::optional<Result>() { return success(); }
operator bool() { return is_success(); }
bool operator !() { return is_error(); }
};
template <typename ...E>
class fallible : public result<DUMMY_VALUE, E...> {
public:
fallible() : result<DUMMY_VALUE, E...>(DUMMY_VALUE {}) {}
fallible(std::variant<E...> error) : result<DUMMY_VALUE, E...>(error) {}
};

View file

@ -39,6 +39,12 @@ Instance::Instance(const InstanceType* type) {
Instance::~Instance () { Instance::~Instance () {
} }
template <typename T>
bool operator ==(std::optional<std::weak_ptr<T>> a, std::optional<std::weak_ptr<T>> b) {
return (!a.has_value() || a.value().expired()) && (!b.has_value() || b.value().expired())
|| (a.has_value() && !a.value().expired()) && (b.has_value() && !b.value().expired()) && a.value().lock() == b.value().lock();
}
// TODO: Test this // TODO: Test this
bool Instance::ancestryContinuityCheck(std::optional<std::shared_ptr<Instance>> newParent) { bool Instance::ancestryContinuityCheck(std::optional<std::shared_ptr<Instance>> newParent) {
for (std::optional<std::shared_ptr<Instance>> currentParent = newParent; currentParent.has_value(); currentParent = currentParent.value()->GetParent()) { for (std::optional<std::shared_ptr<Instance>> currentParent = newParent; currentParent.has_value(); currentParent = currentParent.value()->GetParent()) {
@ -69,37 +75,47 @@ bool Instance::SetParent(std::optional<std::shared_ptr<Instance>> newParent) {
if (hierarchyPostUpdateHandler.has_value()) hierarchyPostUpdateHandler.value()(this->shared_from_this(), lastParent, newParent); if (hierarchyPostUpdateHandler.has_value()) hierarchyPostUpdateHandler.value()(this->shared_from_this(), lastParent, newParent);
this->OnParentUpdated(lastParent, newParent); this->OnParentUpdated(lastParent, newParent);
auto oldDataModel = _dataModel;
auto oldWorkspace = _workspace;
// Update parent data model and workspace, if applicable
if (newParent) {
this->_dataModel = newParent->get()->GetClass() == &DataModel::TYPE ? std::make_optional(std::dynamic_pointer_cast<DataModel>(newParent.value())) : newParent.value()->dataModel();
this->_workspace = newParent->get()->GetClass() == &Workspace::TYPE ? std::make_optional(std::dynamic_pointer_cast<Workspace>(newParent.value())) : newParent.value()->workspace();
} else {
this->_dataModel = std::nullopt;
this->_workspace = std::nullopt;
}
updateAncestry(this->shared<Instance>(), newParent);
return true; return true;
} }
std::optional<std::shared_ptr<DataModel>> Instance::dataModel() { void Instance::updateAncestry(std::optional<std::shared_ptr<Instance>> updatedChild, std::optional<std::shared_ptr<Instance>> newParent) {
// TODO: This algorithm will defer calculations to every time the root data model if (GetParent()) {
// is accessed from any instance. This is inefficient as this can happen many times this->_dataModel = GetParent().value()->GetClass() == &DataModel::TYPE ? std::make_optional(std::dynamic_pointer_cast<DataModel>(GetParent().value())) : GetParent().value()->dataModel();
// a tick. A better option is to cache these values and only update them if the ancestry this->_workspace = GetParent().value()->GetClass() == &Workspace::TYPE ? std::make_optional(std::dynamic_pointer_cast<Workspace>(GetParent().value())) : GetParent().value()->workspace();
// changes, as that happens way less often. } else {
this->_dataModel = std::nullopt;
std::optional<std::shared_ptr<Instance>> currentParent = GetParent(); this->_workspace = std::nullopt;
while (currentParent) {
if (currentParent.value()->GetClass() == &DataModel::TYPE)
return std::dynamic_pointer_cast<DataModel>(currentParent.value());
currentParent = currentParent.value()->GetParent();
} }
return std::nullopt; OnAncestryChanged(updatedChild, newParent);
// Update ancestry in descendants
for (InstanceRef child : children) {
child->updateAncestry(updatedChild, newParent);
}
}
std::optional<std::shared_ptr<DataModel>> Instance::dataModel() {
return (!_dataModel || _dataModel->expired()) ? std::nullopt : std::make_optional(_dataModel.value().lock());
} }
std::optional<std::shared_ptr<Workspace>> Instance::workspace() { std::optional<std::shared_ptr<Workspace>> Instance::workspace() {
// See comment in above function return (!_workspace || _workspace->expired()) ? std::nullopt : std::make_optional(_workspace.value().lock());
std::optional<std::shared_ptr<Instance>> currentParent = GetParent();
while (currentParent) {
if (currentParent.value()->GetClass() == &DataModel::TYPE)
return std::dynamic_pointer_cast<Workspace>(currentParent.value());
currentParent = currentParent.value()->GetParent();
}
return std::nullopt;
} }
std::optional<std::shared_ptr<Instance>> Instance::GetParent() { std::optional<std::shared_ptr<Instance>> Instance::GetParent() {
@ -125,6 +141,10 @@ void Instance::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParen
// Empty stub // Empty stub
} }
void Instance::OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent) {
// Empty stub
}
// Properties // Properties
tl::expected<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) { tl::expected<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
@ -186,8 +206,8 @@ std::vector<std::string> Instance::GetProperties() {
// Serialization // Serialization
void Instance::Serialize(pugi::xml_node* parent) { void Instance::Serialize(pugi::xml_node parent) {
pugi::xml_node node = parent->append_child("Item"); pugi::xml_node node = parent.append_child("Item");
node.append_attribute("class").set_value(this->GetClass()->className); node.append_attribute("class").set_value(this->GetClass()->className);
// Add properties // Add properties
@ -198,20 +218,19 @@ void Instance::Serialize(pugi::xml_node* parent) {
pugi::xml_node propertyNode = propertiesNode.append_child(meta.type->name); pugi::xml_node propertyNode = propertiesNode.append_child(meta.type->name);
propertyNode.append_attribute("name").set_value(name); propertyNode.append_attribute("name").set_value(name);
GetPropertyValue(name)->Serialize(&propertyNode); GetPropertyValue(name)->Serialize(propertyNode);
} }
// Add children // Add children
for (InstanceRef child : this->children) { for (InstanceRef child : this->children) {
child->Serialize(&node); child->Serialize(node);
} }
} }
InstanceRef Instance::Deserialize(pugi::xml_node* node) { result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) {
std::string className = node->attribute("class").value(); std::string className = node.attribute("class").value();
if (INSTANCE_MAP.count(className) == 0) { if (INSTANCE_MAP.count(className) == 0) {
Logger::fatalErrorf("Unknown type for instance: '%s'", className.c_str()); return std::variant<NoSuchInstance>(NoSuchInstance(className));
panic();
} }
// This will error if an abstract instance is used in the file. Oh well, not my prob rn. // This will error if an abstract instance is used in the file. Oh well, not my prob rn.
// printf("What are you? A %s sandwich\n", className.c_str()); // printf("What are you? A %s sandwich\n", className.c_str());
@ -221,7 +240,7 @@ InstanceRef Instance::Deserialize(pugi::xml_node* node) {
// const InstanceType* type = INSTANCE_MAP.at(className); // const InstanceType* type = INSTANCE_MAP.at(className);
// Read properties // Read properties
pugi::xml_node propertiesNode = node->child("Properties"); pugi::xml_node propertiesNode = node.child("Properties");
for (pugi::xml_node propertyNode : propertiesNode) { for (pugi::xml_node propertyNode : propertiesNode) {
std::string propertyName = propertyNode.attribute("name").value(); std::string propertyName = propertyNode.attribute("name").value();
auto meta_ = object->GetPropertyMeta(propertyName); auto meta_ = object->GetPropertyMeta(propertyName);
@ -229,14 +248,18 @@ InstanceRef Instance::Deserialize(pugi::xml_node* node) {
Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str()); Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str());
continue; continue;
} }
Data::Variant value = Data::Variant::Deserialize(&propertyNode); Data::Variant value = Data::Variant::Deserialize(propertyNode);
object->SetPropertyValue(propertyName, value); object->SetPropertyValue(propertyName, value);
} }
// Read children // Read children
for (pugi::xml_node childNode : node->children("Item")) { for (pugi::xml_node childNode : node.children("Item")) {
InstanceRef child = Instance::Deserialize(&childNode); result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode);
object->AddChild(child); if (child.is_error()) {
std::get<NoSuchInstance>(child.error().value()).logMessage();
continue;
}
object->AddChild(child.expect());
} }
return object; return object;

View file

@ -8,13 +8,13 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <variant>
#include <map>
#include <vector> #include <vector>
// #include <../../include/expected.hpp> // #include <../../include/expected.hpp>
#include <expected.hpp> #include <expected.hpp>
#include <pugixml.hpp> #include <pugixml.hpp>
#include "error/error.h"
#include "error/result.h"
#include "member.h" #include "member.h"
class Instance; class Instance;
@ -23,12 +23,25 @@ typedef std::shared_ptr<Instance>(*InstanceConstructor)();
class DataModel; class DataModel;
class Workspace; class Workspace;
typedef int InstanceFlags;
// This instance should only be instantiated in special circumstances (i.e. by DataModel) and should be creatable directly via any API
const InstanceFlags INSTANCE_NOTCREATABLE = (InstanceFlags)0x1;
// This instance is a service
const InstanceFlags INSTANCE_SERVICE = (InstanceFlags)0x2;
// Struct describing information about an instance // Struct describing information about an instance
struct InstanceType { struct InstanceType {
const InstanceType* super; // May be null const InstanceType* super; // May be null
std::string className; std::string className;
InstanceConstructor constructor; InstanceConstructor constructor;
std::string explorerIcon = ""; std::string explorerIcon = "";
InstanceFlags flags;
};
// Errors
class NoSuchInstance : public Error {
public:
inline NoSuchInstance(std::string className) : Error("Cannot create instance of unknown type " + className) {}
}; };
class DescendantsIterator; class DescendantsIterator;
@ -44,7 +57,11 @@ private:
std::optional<std::vector<std::string>> cachedMemberList; std::optional<std::vector<std::string>> cachedMemberList;
std::optional<std::weak_ptr<DataModel>> _dataModel;
std::optional<std::weak_ptr<Workspace>> _workspace;
bool ancestryContinuityCheck(std::optional<std::shared_ptr<Instance>> newParent); bool ancestryContinuityCheck(std::optional<std::shared_ptr<Instance>> newParent);
void updateAncestry(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent);
protected: protected:
bool parentLocked = false; bool parentLocked = false;
std::unique_ptr<MemberMap> memberMap; std::unique_ptr<MemberMap> memberMap;
@ -53,6 +70,7 @@ protected:
virtual ~Instance(); virtual ~Instance();
virtual void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent); virtual void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent);
virtual void OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent);
// The root data model this object is a descendant of // The root data model this object is a descendant of
std::optional<std::shared_ptr<DataModel>> dataModel(); std::optional<std::shared_ptr<DataModel>> dataModel();
@ -87,8 +105,8 @@ public:
std::vector<std::string> GetProperties(); std::vector<std::string> GetProperties();
// Serialization // Serialization
void Serialize(pugi::xml_node* parent); void Serialize(pugi::xml_node parent);
static std::shared_ptr<Instance> Deserialize(pugi::xml_node* node); static result<std::shared_ptr<Instance>, NoSuchInstance> Deserialize(pugi::xml_node node);
}; };
typedef std::shared_ptr<Instance> InstanceRef; typedef std::shared_ptr<Instance> InstanceRef;

View file

@ -1,9 +1,20 @@
#include "service.h" #include "service.h"
#include "logger.h"
#include "panic.h"
#include <memory> #include <memory>
Service::Service(const InstanceType* type, std::weak_ptr<DataModel> root) : Instance(type), dataModel(root) {} Service::Service(const InstanceType* type) : Instance(type){}
void Service::InitService() { // Fail if parented to non-datamodel, otherwise lock parent
SetParent(dataModel.lock()); void Service::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
if (!newParent || newParent.value()->GetClass() != &DataModel::TYPE) {
Logger::fatalErrorf("Service %s was parented to object of type %s", GetClass()->className, newParent ? newParent.value()->GetClass()->className : "NULL");
panic();
}
// Prevent parent from being updated
parentLocked = true; parentLocked = true;
} }
void Service::InitService() {
}

View file

@ -6,10 +6,10 @@
#include <memory> #include <memory>
class Service : public Instance { class Service : public Instance {
protected: protected:
std::weak_ptr<DataModel> dataModel; Service(const InstanceType* type);
Service(const InstanceType* type, std::weak_ptr<DataModel> root);
virtual void InitService(); virtual void InitService();
void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) override;
friend class DataModel; friend class DataModel;
}; };

View file

@ -2,19 +2,13 @@
#include "base/service.h" #include "base/service.h"
#include "objects/base/instance.h" #include "objects/base/instance.h"
#include "objects/base/service.h" #include "objects/base/service.h"
#include "objects/meta.h"
#include "workspace.h" #include "workspace.h"
#include "logger.h" #include "logger.h"
#include "panic.h" #include "panic.h"
#include <cstdio> #include <cstdio>
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <functional>
// TODO: Move this to a different file
// ONLY add services here, all types are expected to inherit from Service, or errors WILL occur
std::map<std::string, std::function<std::shared_ptr<Service>(std::weak_ptr<DataModel>)>> SERVICE_CONSTRUCTORS = {
{ "Workspace", &Workspace::Create },
};
const InstanceType DataModel::TYPE = { const InstanceType DataModel::TYPE = {
.super = &Instance::TYPE, .super = &Instance::TYPE,
@ -33,8 +27,10 @@ DataModel::DataModel()
void DataModel::Init() { void DataModel::Init() {
// Create the workspace if it doesn't exist // Create the workspace if it doesn't exist
if (this->services.count("Workspace") == 0) if (this->services.count("Workspace") == 0) {
this->services["Workspace"] = std::make_shared<Workspace>(shared<DataModel>()); this->services["Workspace"] = std::make_shared<Workspace>();
AddChild(this->services["Workspace"]);
}
// Init all services // Init all services
for (auto [_, service] : this->services) { for (auto [_, service] : this->services) {
@ -56,7 +52,7 @@ void DataModel::SaveToFile(std::optional<std::string> path) {
pugi::xml_node root = doc.append_child("openblocks"); pugi::xml_node root = doc.append_child("openblocks");
for (InstanceRef child : this->GetChildren()) { for (InstanceRef child : this->GetChildren()) {
child->Serialize(&root); child->Serialize(root);
} }
doc.save(outStream); doc.save(outStream);
@ -65,11 +61,11 @@ void DataModel::SaveToFile(std::optional<std::string> path) {
Logger::info("Place saved successfully"); Logger::info("Place saved successfully");
} }
void DataModel::DeserializeService(pugi::xml_node* node) { void DataModel::DeserializeService(pugi::xml_node node) {
std::string className = node->attribute("class").value(); std::string className = node.attribute("class").value();
if (SERVICE_CONSTRUCTORS.count(className) == 0) { if (INSTANCE_MAP.count(className) == 0) {
Logger::fatalErrorf("Unknown service: '%s'", className.c_str()); Logger::fatalErrorf("Unknown service: '%s'", className.c_str());
panic(); return;
} }
if (services.count(className) != 0) { if (services.count(className) != 0) {
@ -78,10 +74,11 @@ void DataModel::DeserializeService(pugi::xml_node* node) {
} }
// This will error if an abstract instance is used in the file. Oh well, not my prob rn. // This will error if an abstract instance is used in the file. Oh well, not my prob rn.
InstanceRef object = SERVICE_CONSTRUCTORS[className](shared<DataModel>()); InstanceRef object = INSTANCE_MAP[className]->constructor();
AddChild(object);
// Read properties // Read properties
pugi::xml_node propertiesNode = node->child("Properties"); pugi::xml_node propertiesNode = node.child("Properties");
for (pugi::xml_node propertyNode : propertiesNode) { for (pugi::xml_node propertyNode : propertiesNode) {
std::string propertyName = propertyNode.attribute("name").value(); std::string propertyName = propertyNode.attribute("name").value();
auto meta_ = object->GetPropertyMeta(propertyName); auto meta_ = object->GetPropertyMeta(propertyName);
@ -89,14 +86,18 @@ void DataModel::DeserializeService(pugi::xml_node* node) {
Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str()); Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str());
continue; continue;
} }
Data::Variant value = Data::Variant::Deserialize(&propertyNode); Data::Variant value = Data::Variant::Deserialize(propertyNode);
object->SetPropertyValue(propertyName, value); object->SetPropertyValue(propertyName, value);
} }
// Add children // Add children
for (pugi::xml_node childNode : node->children("Item")) { for (pugi::xml_node childNode : node.children("Item")) {
InstanceRef child = Instance::Deserialize(&childNode); result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode);
object->AddChild(child); if (child.is_error()) {
std::get<NoSuchInstance>(child.error().value()).logMessage();
continue;
}
object->AddChild(child.expect());
} }
// We add the service to the list // We add the service to the list
@ -113,7 +114,7 @@ std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
std::shared_ptr<DataModel> newModel = std::make_shared<DataModel>(); std::shared_ptr<DataModel> newModel = std::make_shared<DataModel>();
for (pugi::xml_node childNode : rootNode.children("Item")) { for (pugi::xml_node childNode : rootNode.children("Item")) {
newModel->DeserializeService(&childNode); newModel->DeserializeService(childNode);
} }
newModel->Init(); newModel->Init();

View file

@ -1,18 +1,33 @@
#pragma once #pragma once
#include "base.h" #include "base.h"
#include "error/result.h"
#include "logger.h"
#include "objects/base/instance.h"
#include "objects/meta.h"
#include "panic.h"
#include <memory> #include <memory>
#include <variant>
class Workspace; class Workspace;
class DataModel; class DataModel;
class Service; class Service;
extern std::map<std::string, std::function<std::shared_ptr<Service>(std::weak_ptr<DataModel>)>> SERVICE_CONSTRUCTORS;
class NoSuchService : public Error {
public:
inline NoSuchService(std::string className) : Error("Cannot insert service of unknown type " + className) {}
};
class ServiceAlreadyExists : public Error {
public:
inline ServiceAlreadyExists(std::string className) : Error("Service " + className + " is already inserted") {}
};
// The root instance to all objects in the hierarchy // The root instance to all objects in the hierarchy
class DataModel : public Instance { class DataModel : public Instance {
private: private:
void DeserializeService(pugi::xml_node* node); void DeserializeService(pugi::xml_node node);
public: public:
const static InstanceType TYPE; const static InstanceType TYPE;
@ -26,12 +41,36 @@ public:
static inline std::shared_ptr<DataModel> New() { return std::make_shared<DataModel>(); }; static inline std::shared_ptr<DataModel> New() { return std::make_shared<DataModel>(); };
virtual const InstanceType* GetClass() override; virtual const InstanceType* GetClass() override;
// Inserts a service if it doesn't already exist
fallible<ServiceAlreadyExists, NoSuchService> InsertService(std::string name) {
if (services.count(name) != 0)
return fallible<ServiceAlreadyExists, NoSuchService>(ServiceAlreadyExists(name));
if (!INSTANCE_MAP[name] || (INSTANCE_MAP[name]->flags ^ (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) != 0) {
Logger::fatalErrorf("Attempt to create instance of unknown type %s", name);
panic();
}
services[name] = std::dynamic_pointer_cast<Service>(INSTANCE_MAP[name]->constructor());
AddChild(std::dynamic_pointer_cast<Instance>(services[name]));
return {};
}
template <typename T> template <typename T>
std::shared_ptr<T> GetService(std::string name) { result<std::shared_ptr<T>, NoSuchService> GetService(std::string name) {
if (services.count(name) != 0) if (services.count(name) != 0)
return services[name]; return services[name];
// TODO: Validate name
services[name] = SERVICE_CONSTRUCTORS[name](shared<DataModel>()); // TODO: Replace this with a result return type
if (!INSTANCE_MAP[name] || (INSTANCE_MAP[name]->flags ^ (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) != 0) {
return NoSuchService(name);
}
services[name] = std::dynamic_pointer_cast<Service>(INSTANCE_MAP[name]->constructor());
AddChild(std::dynamic_pointer_cast<Instance>(services[name]));
return services[name];
} }
template <typename T> template <typename T>

View file

@ -1,5 +1,6 @@
#include "part.h" #include "part.h"
#include "base/instance.h" #include "base/instance.h"
#include "common.h"
#include "datatypes/base.h" #include "datatypes/base.h"
#include "datatypes/cframe.h" #include "datatypes/cframe.h"
#include "datatypes/color3.h" #include "datatypes/color3.h"
@ -7,7 +8,6 @@
#include "objects/base/member.h" #include "objects/base/member.h"
#include <memory> #include <memory>
#include <optional> #include <optional>
#include "physics/simulation.h"
using Data::Vector3; using Data::Vector3;
@ -106,24 +106,26 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(par
}); });
} }
// This feels wrong. Get access to PhysicsWorld somehow else? Part will need access to this often though, most likely...
extern rp::PhysicsWorld* world;
Part::~Part() { Part::~Part() {
// This relies on physicsCommon still existing. Be very careful. // This relies on physicsCommon still existing. Be very careful.
if (this->rigidBody) if (this->rigidBody && workspace())
world->destroyRigidBody(rigidBody); workspace().value()->DestroyRigidBody(rigidBody);
} }
void Part::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) { void Part::OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent) {
if (this->rigidBody) if (this->rigidBody)
this->rigidBody->setIsActive(newParent.has_value()); this->rigidBody->setIsActive(workspace().has_value());
if (workspace())
workspace().value()->SyncPartPhysics(std::dynamic_pointer_cast<Part>(this->shared_from_this()));
// TODO: Sleeping bodies that touch this one also need to be updated // TODO: Sleeping bodies that touch this one also need to be updated
} }
void Part::onUpdated(std::string property) { void Part::onUpdated(std::string property) {
syncPartPhysics(std::dynamic_pointer_cast<Part>(this->shared_from_this())); if (workspace())
workspace().value()->SyncPartPhysics(std::dynamic_pointer_cast<Part>(this->shared_from_this()));
} }
// Expands provided extents to fit point // Expands provided extents to fit point

View file

@ -1,13 +1,12 @@
#pragma once #pragma once
#include "base.h"
#include <memory> #include <memory>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/ext.hpp> #include <glm/ext.hpp>
#include "../rendering/material.h"
#include "datatypes/cframe.h" #include "datatypes/cframe.h"
#include "datatypes/color3.h" #include "datatypes/color3.h"
#include "datatypes/vector.h" #include "datatypes/vector.h"
#include "objects/base/instance.h"
#include "rendering/surface.h" #include "rendering/surface.h"
#include <reactphysics3d/reactphysics3d.h> #include <reactphysics3d/reactphysics3d.h>
@ -25,7 +24,7 @@ struct PartConstructParams {
class Part : public Instance { class Part : public Instance {
protected: protected:
void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) override; void OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent) override;
void onUpdated(std::string); void onUpdated(std::string);
public: public:
const static InstanceType TYPE; const static InstanceType TYPE;

View file

@ -1,15 +1,147 @@
#include "workspace.h" #include "workspace.h"
#include "objects/base/instance.h"
#include "physics/util.h"
const InstanceType Workspace::TYPE = { const InstanceType Workspace::TYPE = {
.super = &Instance::TYPE, .super = &Instance::TYPE,
.className = "Workspace", .className = "Workspace",
// .constructor = &Workspace::Create, .constructor = &Workspace::Create,
.explorerIcon = "workspace", .explorerIcon = "workspace",
.flags = INSTANCE_NOTCREATABLE | INSTANCE_SERVICE,
}; };
const InstanceType* Workspace::GetClass() { const InstanceType* Workspace::GetClass() {
return &TYPE; return &TYPE;
} }
Workspace::Workspace(std::weak_ptr<DataModel> dataModel): Service(&TYPE, dataModel) { static rp::PhysicsCommon physicsCommon;
Workspace::Workspace(): Service(&TYPE) {
}
Workspace::~Workspace() {
if (physicsWorld)
physicsCommon.destroyPhysicsWorld(physicsWorld);
}
void Workspace::InitService() {
if (initialized) return;
initialized = true;
physicsWorld = physicsCommon.createPhysicsWorld();
physicsWorld->setGravity(rp::Vector3(0, -196.2, 0));
// world->setContactsPositionCorrectionTechnique(rp3d::ContactsPositionCorrectionTechnique::BAUMGARTE_CONTACTS);
physicsWorld->setNbIterationsPositionSolver(2000);
physicsWorld->setNbIterationsVelocitySolver(2000);
// physicsWorld->setEventListener(&eventListener);
// Sync all parts
for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) {
InstanceRef obj = *it;
if (obj->GetClass()->className != "Part") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(obj);
this->SyncPartPhysics(part);
}
}
void Workspace::SyncPartPhysics(std::shared_ptr<Part> part) {
if (!physicsWorld) return;
glm::mat4 rotMat = glm::mat4(1.0f);
rp::Transform transform = part->cframe;
if (!part->rigidBody) {
part->rigidBody = physicsWorld->createRigidBody(transform);
} else {
part->rigidBody->setTransform(transform);
}
rp::BoxShape* shape = physicsCommon.createBoxShape(glmToRp(part->size * glm::vec3(0.5f)));
if (part->rigidBody->getNbColliders() > 0) {
part->rigidBody->removeCollider(part->rigidBody->getCollider(0));
}
if (part->rigidBody->getNbColliders() == 0)
part->rigidBody->addCollider(shape, rp::Transform());
part->rigidBody->setType(part->anchored ? rp::BodyType::STATIC : rp::BodyType::DYNAMIC);
part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b11);
part->rigidBody->setUserData(&*part);
}
void Workspace::PhysicsStep(float deltaTime) {
// Step the simulation a few steps
physicsWorld->update(std::min(deltaTime / 2, (1/60.f)));
// Naive implementation. Parts are only considered so if they are just under Workspace
// TODO: Add list of tracked parts in workspace based on their ancestry using inWorkspace property of Instance
for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) {
InstanceRef obj = *it;
if (obj->GetClass()->className != "Part") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(obj);
const rp::Transform& transform = part->rigidBody->getTransform();
part->cframe = Data::CFrame(transform);
}
}
RaycastResult::RaycastResult(const rp::RaycastInfo& raycastInfo)
: worldPoint(raycastInfo.worldPoint)
, worldNormal(raycastInfo.worldNormal)
, hitFraction(raycastInfo.hitFraction)
, triangleIndex(raycastInfo.triangleIndex)
, body(raycastInfo.body)
, collider(raycastInfo.collider) {}
class NearestRayHit : public rp::RaycastCallback {
rp::Vector3 startPos;
std::optional<RaycastFilter> filter;
std::optional<RaycastResult> nearestHit;
float nearestHitDistance = -1;
// Order is not guaranteed, so we have to figure out the nearest object using a more sophisticated algorith,
rp::decimal notifyRaycastHit(const rp::RaycastInfo& raycastInfo) override {
// If the detected object is further away than the nearest object, continue.
int distance = (raycastInfo.worldPoint - startPos).length();
if (nearestHitDistance != -1 && distance >= nearestHitDistance)
return 1;
if (!filter) {
nearestHit = raycastInfo;
nearestHitDistance = distance;
return 1;
}
std::shared_ptr<Part> part = partFromBody(raycastInfo.body);
FilterResult result = filter.value()(part);
if (result == FilterResult::BLOCK) {
nearestHit = std::nullopt;
nearestHitDistance = distance;
return 1;
} else if (result == FilterResult::TARGET) {
nearestHit = raycastInfo;
nearestHitDistance = distance;
return 1;
}
return 1;
};
public:
NearestRayHit(rp::Vector3 startPos, std::optional<RaycastFilter> filter = std::nullopt) : startPos(startPos), filter(filter) {}
std::optional<const RaycastResult> getNearestHit() { return nearestHit; };
};
std::optional<const RaycastResult> Workspace::CastRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter, unsigned short categoryMaskBits) {
rp::Ray ray(glmToRp(point), glmToRp(glm::normalize(rotation)) * maxLength);
NearestRayHit rayHit(glmToRp(point), filter);
physicsWorld->raycast(ray, &rayHit, categoryMaskBits);
return rayHit.getNearestHit();
}
void Workspace::DestroyRigidBody(rp::RigidBody* rigidBody) {
physicsWorld->destroyRigidBody(rigidBody);
} }

View file

@ -1,17 +1,52 @@
#pragma once #pragma once
#include "base.h"
#include "objects/base/service.h" #include "objects/base/service.h"
#include <memory> #include <memory>
#include <reactphysics3d/body/RigidBody.h>
#include <reactphysics3d/engine/PhysicsCommon.h>
#include <reactphysics3d/engine/PhysicsWorld.h>
struct RaycastResult {
rp::Vector3 worldPoint;
rp::Vector3 worldNormal;
rp::decimal hitFraction;
int triangleIndex;
rp::Body* body;
rp::Collider* collider;
RaycastResult(const rp::RaycastInfo& raycastInfo);
};
enum FilterResult {
TARGET, // The object is captured
BLOCK, // The object blocks any objects behind it, but is not captured
PASS, // The object is transparent, ignore it
};
class Part;
typedef std::function<FilterResult(std::shared_ptr<Part>)> RaycastFilter;
class Workspace : public Service { class Workspace : public Service {
//private: rp::PhysicsWorld *physicsWorld = nullptr;
protected:
void InitService() override;
bool initialized = false;
public: public:
const static InstanceType TYPE; const static InstanceType TYPE;
Workspace(std::weak_ptr<DataModel> dataModel); Workspace();
~Workspace();
// static inline std::shared_ptr<Workspace> New() { return std::make_shared<Workspace>(); }; // static inline std::shared_ptr<Workspace> New() { return std::make_shared<Workspace>(); };
static inline std::shared_ptr<Service> Create(std::weak_ptr<DataModel> parent) { return std::make_shared<Workspace>(parent); }; static inline std::shared_ptr<Instance> Create() { return std::make_shared<Workspace>(); };
virtual const InstanceType* GetClass() override; virtual const InstanceType* GetClass() override;
void SyncPartPhysics(std::shared_ptr<Part> part);
void DestroyRigidBody(rp::RigidBody* rigidBody);
void PhysicsStep(float deltaTime);
std::optional<const RaycastResult> CastRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter = std::nullopt, unsigned short categoryMaskBits = 0xFFFF);
}; };

View file

@ -5,4 +5,4 @@
// before shutting down. // before shutting down.
// If this process fails, or the panic function is called within itself, it will hard-abort // If this process fails, or the panic function is called within itself, it will hard-abort
void panic(); [[noreturn]] void panic();

View file

@ -1,141 +0,0 @@
#include <cstdio>
#include <glm/ext/matrix_float3x3.hpp>
#include <glm/gtc/quaternion.hpp>
#include <memory>
#include <reactphysics3d/collision/RaycastInfo.h>
#include <reactphysics3d/collision/shapes/BoxShape.h>
#include <reactphysics3d/collision/shapes/CollisionShape.h>
#include <reactphysics3d/components/RigidBodyComponents.h>
#include <reactphysics3d/configuration.h>
#include <reactphysics3d/engine/EventListener.h>
#include <reactphysics3d/engine/PhysicsCommon.h>
#include <reactphysics3d/mathematics/Quaternion.h>
#include <reactphysics3d/mathematics/Ray.h>
#include <reactphysics3d/mathematics/Transform.h>
#include <reactphysics3d/mathematics/Vector3.h>
#include <reactphysics3d/memory/DefaultAllocator.h>
#include <reactphysics3d/memory/MemoryAllocator.h>
#include <reactphysics3d/reactphysics3d.h>
#include "../common.h"
#include "../objects/part.h"
#include "datatypes/cframe.h"
#include "util.h"
#include "simulation.h"
namespace rp = reactphysics3d;
class PhysicsListener : public rp::EventListener {
void onContact(const CollisionCallback::CallbackData& /*callbackData*/) override {
// printf("Collision occurred!\n");
}
};
rp::PhysicsCommon* physicsCommon;
rp::PhysicsWorld* world;
PhysicsListener eventListener;
void simulationInit() {
physicsCommon = new rp::PhysicsCommon; // I allocate this on the heap to ensure it exists while Parts are getting destructed. This is probably not great
world = physicsCommon->createPhysicsWorld();
world->setGravity(rp::Vector3(0, -196.2, 0));
// world->setContactsPositionCorrectionTechnique(rp3d::ContactsPositionCorrectionTechnique::BAUMGARTE_CONTACTS);
world->setNbIterationsPositionSolver(2000);
world->setNbIterationsVelocitySolver(2000);
world->setEventListener(&eventListener);
}
void syncPartPhysics(std::shared_ptr<Part> part) {
glm::mat4 rotMat = glm::mat4(1.0f);
rp::Transform transform = part->cframe;
if (!part->rigidBody) {
part->rigidBody = world->createRigidBody(transform);
} else {
part->rigidBody->setTransform(transform);
}
rp::BoxShape* shape = physicsCommon->createBoxShape(glmToRp(part->size * glm::vec3(0.5f)));
if (part->rigidBody->getNbColliders() > 0) {
part->rigidBody->removeCollider(part->rigidBody->getCollider(0));
}
if (part->rigidBody->getNbColliders() == 0)
part->rigidBody->addCollider(shape, rp::Transform());
part->rigidBody->setType(part->anchored ? rp::BodyType::STATIC : rp::BodyType::DYNAMIC);
part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b11);
part->rigidBody->setUserData(&*part);
}
void physicsStep(float deltaTime) {
// Step the simulation a few steps
world->update(std::min(deltaTime / 2, (1/60.f)));
// Naive implementation. Parts are only considered so if they are just under Workspace
// TODO: Add list of tracked parts in workspace based on their ancestry using inWorkspace property of Instance
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
InstanceRef obj = *it;
if (obj->GetClass()->className != "Part") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(obj);
const rp::Transform& transform = part->rigidBody->getTransform();
part->cframe = Data::CFrame(transform);
}
}
RaycastResult::RaycastResult(const rp::RaycastInfo& raycastInfo)
: worldPoint(raycastInfo.worldPoint)
, worldNormal(raycastInfo.worldNormal)
, hitFraction(raycastInfo.hitFraction)
, triangleIndex(raycastInfo.triangleIndex)
, body(raycastInfo.body)
, collider(raycastInfo.collider) {}
class NearestRayHit : public rp::RaycastCallback {
rp::Vector3 startPos;
std::optional<RaycastFilter> filter;
std::optional<RaycastResult> nearestHit;
float nearestHitDistance = -1;
// Order is not guaranteed, so we have to figure out the nearest object using a more sophisticated algorith,
rp::decimal notifyRaycastHit(const rp::RaycastInfo& raycastInfo) override {
// If the detected object is further away than the nearest object, continue.
int distance = (raycastInfo.worldPoint - startPos).length();
if (nearestHitDistance != -1 && distance >= nearestHitDistance)
return 1;
if (!filter) {
nearestHit = raycastInfo;
nearestHitDistance = distance;
return 1;
}
std::shared_ptr<Part> part = partFromBody(raycastInfo.body);
FilterResult result = filter.value()(part);
if (result == FilterResult::BLOCK) {
nearestHit = std::nullopt;
nearestHitDistance = distance;
return 1;
} else if (result == FilterResult::TARGET) {
nearestHit = raycastInfo;
nearestHitDistance = distance;
return 1;
}
return 1;
};
public:
NearestRayHit(rp::Vector3 startPos, std::optional<RaycastFilter> filter = std::nullopt) : startPos(startPos), filter(filter) {}
std::optional<const RaycastResult> getNearestHit() { return nearestHit; };
};
std::optional<const RaycastResult> castRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter, unsigned short categoryMaskBits) {
rp::Ray ray(glmToRp(point), glmToRp(glm::normalize(rotation)) * maxLength);
NearestRayHit rayHit(glmToRp(point), filter);
world->raycast(ray, &rayHit, categoryMaskBits);
return rayHit.getNearestHit();
}

View file

@ -1,30 +0,0 @@
#pragma once
#include "../objects/part.h"
#include <glm/ext/vector_float3.hpp>
#include <memory>
#include <reactphysics3d/collision/RaycastInfo.h>
struct RaycastResult {
rp::Vector3 worldPoint;
rp::Vector3 worldNormal;
rp::decimal hitFraction;
int triangleIndex;
rp::Body* body;
rp::Collider* collider;
RaycastResult(const rp::RaycastInfo& raycastInfo);
};
enum FilterResult {
TARGET, // The object is captured
BLOCK, // The object blocks any objects behind it, but is not captured
PASS, // The object is transparent, ignore it
};
typedef std::function<FilterResult(std::shared_ptr<Part>)> RaycastFilter;
void simulationInit();
void syncPartPhysics(std::shared_ptr<Part> part);
void physicsStep(float deltaTime);
std::optional<const RaycastResult> castRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter = std::nullopt, unsigned short categoryMaskBits = 0xFFFF);

View file

@ -9,10 +9,8 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/trigonometric.hpp> #include <glm/trigonometric.hpp>
#include <memory> #include <memory>
#include <vector>
#include "datatypes/cframe.h" #include "datatypes/cframe.h"
#include "physics/util.h"
#include "shader.h" #include "shader.h"
#include "mesh.h" #include "mesh.h"
#include "defaultmeshes.h" #include "defaultmeshes.h"

View file

@ -1,45 +1,11 @@
#include <GL/glew.h> #include <GL/glew.h>
#include <chrono>
#include <QMouseEvent>
#include <glm/common.hpp>
#include <glm/ext/matrix_projection.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/ext/scalar_constants.hpp>
#include <glm/ext/vector_float3.hpp>
#include <glm/geometric.hpp>
#include <glm/gtc/round.hpp>
#include <glm/matrix.hpp>
#include <glm/trigonometric.hpp>
#include <memory>
#include <numbers>
#include <optional>
#include <qsoundeffect.h> #include <qsoundeffect.h>
#include <reactphysics3d/collision/RaycastInfo.h>
#include <vector>
#include <QSoundEffect>
#include "datatypes/cframe.h"
#include "datatypes/vector.h"
#include "editorcommon.h"
#include "logger.h"
#include "mainwindow.h"
#include "objects/handles.h"
#include "physics/util.h"
#include "qcursor.h"
#include "qevent.h"
#include "qnamespace.h"
#include "qwindowdefs.h"
#include "rendering/renderer.h"
#include "physics/simulation.h"
#include "camera.h"
#include "common.h"
#include "rendering/shader.h"
#include "mainglwidget.h" #include "mainglwidget.h"
#include "common.h"
#include "math_helper.h" #include "math_helper.h"
#include "rendering/surface.h" #include "physics/util.h"
#include "rendering/renderer.h"
#include "rendering/shader.h"
static Data::CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1)); static Data::CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
@ -145,7 +111,7 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
QPoint position = evt->pos(); QPoint position = evt->pos();
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height())); glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000, [](std::shared_ptr<Part> part) { std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000, [](std::shared_ptr<Part> part) {
return (part == draggingObject->lock()) ? FilterResult::PASS : FilterResult::TARGET; return (part == draggingObject->lock()) ? FilterResult::PASS : FilterResult::TARGET;
}); });
@ -178,7 +144,7 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
draggingObject->lock()->cframe = newFrame + unsinkOffset; draggingObject->lock()->cframe = newFrame + unsinkOffset;
syncPartPhysics(draggingObject->lock()); gWorkspace()->SyncPartPhysics(draggingObject->lock());
} }
inline glm::vec3 vec3fy(glm::vec4 vec) { inline glm::vec3 vec3fy(glm::vec4 vec) {
@ -255,7 +221,7 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
if (mainWindow()->editSoundEffects && (oldSize != part->size) && QFile::exists("./assets/excluded/switch.wav")) if (mainWindow()->editSoundEffects && (oldSize != part->size) && QFile::exists("./assets/excluded/switch.wav"))
playSound("./assets/excluded/switch.wav"); playSound("./assets/excluded/switch.wav");
syncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock())); gWorkspace()->SyncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock()));
} }
// Also implemented based on Godot: [c7ea8614](godot/editor/plugins/canvas_item_editor_plugin.cpp#L1490) // Also implemented based on Godot: [c7ea8614](godot/editor/plugins/canvas_item_editor_plugin.cpp#L1490)
@ -297,7 +263,7 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
part->cframe = initialFrame * Data::CFrame::FromEulerAnglesXYZ(-angles); part->cframe = initialFrame * Data::CFrame::FromEulerAnglesXYZ(-angles);
syncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock())); gWorkspace()->SyncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock()));
} }
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) { std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
@ -315,7 +281,7 @@ void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
return; return;
}; };
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000); std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000);
if (rayHit && partFromBody(rayHit->body)->name != "Baseplate") { if (rayHit && partFromBody(rayHit->body)->name != "Baseplate") {
setCursor(Qt::OpenHandCursor); setCursor(Qt::OpenHandCursor);
return; return;
@ -372,7 +338,7 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
} }
// raycast part // raycast part
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000); std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000);
if (!rayHit || !partFromBody(rayHit->body)) return; if (!rayHit || !partFromBody(rayHit->body)) return;
std::shared_ptr<Part> part = partFromBody(rayHit->body); std::shared_ptr<Part> part = partFromBody(rayHit->body);
if (part->name == "Baseplate") return; if (part->name == "Baseplate") return;
@ -449,7 +415,7 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
.size = glm::vec3(1, 1, 1), .size = glm::vec3(1, 1, 1),
.color = glm::vec3(1.0f, 0.5f, 0.31f), .color = glm::vec3(1.0f, 0.5f, 0.31f),
})); }));
syncPartPhysics(lastPart); gWorkspace()->SyncPartPhysics(lastPart);
} }
if (evt->key() == Qt::Key_U) if (evt->key() == Qt::Key_U)

View file

@ -1,43 +1,10 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "./ui_mainwindow.h" #include "./ui_mainwindow.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QProcess>
#include <QThread>
#include <QTimerEvent>
#include <QMouseEvent>
#include <QWidget>
#include <QTreeView>
#include <QAbstractItemView>
#include <functional>
#include <memory>
#include <optional>
#include <qcoreapplication.h>
#include <qglobal.h>
#include <qguiapplication.h>
#include <qicon.h>
#include <qmessagebox.h>
#include <qnamespace.h>
#include <qwindowdefs.h>
#include <reactphysics3d/engine/PhysicsCommon.h>
#include <reactphysics3d/engine/PhysicsWorld.h>
#include <sstream>
#include <QStyleHints>
#include "common.h" #include "common.h"
#include "editorcommon.h" #include <qclipboard.h>
#include "logger.h" #include <qmessagebox.h>
#include "objects/base/instance.h" #include <qmimedata.h>
#include "objects/datamodel.h" #include <qstylehints.h>
#include "objects/handles.h"
#include "physics/simulation.h"
#include "objects/part.h"
#include "qfiledialog.h"
#include "qclipboard.h"
#include "qmimedata.h"
#include "qobject.h"
#include "qsysinfo.h"
#ifdef _NDEBUG #ifdef _NDEBUG
#define NDEBUG #define NDEBUG
@ -192,9 +159,6 @@ MainWindow::MainWindow(QWidget *parent)
gDataModel->Init(); gDataModel->Init();
ui->explorerView->updateRoot(gDataModel); ui->explorerView->updateRoot(gDataModel);
// TODO: Remove this and use a proper fix. This *WILL* cause a leak and memory issues in the future
simulationInit();
// Baseplate // Baseplate
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({ gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
.position = glm::vec3(0, -5, 0), .position = glm::vec3(0, -5, 0),
@ -204,14 +168,14 @@ MainWindow::MainWindow(QWidget *parent)
.anchored = true, .anchored = true,
})); }));
ui->mainWidget->lastPart->name = "Baseplate"; ui->mainWidget->lastPart->name = "Baseplate";
syncPartPhysics(ui->mainWidget->lastPart); gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
}); });
connect(ui->actionSave, &QAction::triggered, this, [&]() { connect(ui->actionSave, &QAction::triggered, this, [&]() {
std::optional<std::string> path; std::optional<std::string> path;
if (!gDataModel->HasFile()) if (!gDataModel->HasFile())
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + gDataModel->name)); path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + gDataModel->name));
if (!path || path == "") return; if (!gDataModel->HasFile() && (!path || path == "")) return;
gDataModel->SaveToFile(path); gDataModel->SaveToFile(path);
}); });
@ -233,6 +197,7 @@ MainWindow::MainWindow(QWidget *parent)
// simulationInit(); // simulationInit();
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value()); std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value());
gDataModel = newModel; gDataModel = newModel;
newModel->Init();
ui->explorerView->updateRoot(newModel); ui->explorerView->updateRoot(newModel);
}); });
@ -248,7 +213,7 @@ MainWindow::MainWindow(QWidget *parent)
pugi::xml_document rootDoc; pugi::xml_document rootDoc;
for (InstanceRefWeak inst : getSelection()) { for (InstanceRefWeak inst : getSelection()) {
if (inst.expired()) continue; if (inst.expired()) continue;
inst.lock()->Serialize(&rootDoc); inst.lock()->Serialize(rootDoc);
} }
std::ostringstream encoded; std::ostringstream encoded;
@ -262,7 +227,7 @@ MainWindow::MainWindow(QWidget *parent)
pugi::xml_document rootDoc; pugi::xml_document rootDoc;
for (InstanceRefWeak inst : getSelection()) { for (InstanceRefWeak inst : getSelection()) {
if (inst.expired()) continue; if (inst.expired()) continue;
inst.lock()->Serialize(&rootDoc); inst.lock()->Serialize(rootDoc);
inst.lock()->SetParent(std::nullopt); inst.lock()->SetParent(std::nullopt);
} }
@ -284,8 +249,9 @@ MainWindow::MainWindow(QWidget *parent)
rootDoc.load_string(encoded.c_str()); rootDoc.load_string(encoded.c_str());
for (pugi::xml_node instNode : rootDoc.children()) { for (pugi::xml_node instNode : rootDoc.children()) {
InstanceRef inst = Instance::Deserialize(&instNode); result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
gWorkspace()->AddChild(inst); if (!inst) { inst.logError(); continue; }
gWorkspace()->AddChild(inst.expect());
} }
}); });
@ -303,8 +269,9 @@ MainWindow::MainWindow(QWidget *parent)
rootDoc.load_string(encoded.c_str()); rootDoc.load_string(encoded.c_str());
for (pugi::xml_node instNode : rootDoc.children()) { for (pugi::xml_node instNode : rootDoc.children()) {
InstanceRef inst = Instance::Deserialize(&instNode); result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
selectedParent->AddChild(inst); if (!inst) { inst.logError(); continue; }
selectedParent->AddChild(inst.expect());
} }
}); });
@ -319,7 +286,7 @@ MainWindow::MainWindow(QWidget *parent)
for (InstanceRefWeak inst : getSelection()) { for (InstanceRefWeak inst : getSelection()) {
if (inst.expired()) continue; if (inst.expired()) continue;
inst.lock()->Serialize(&modelRoot); inst.lock()->Serialize(modelRoot);
} }
modelDoc.save(outStream); modelDoc.save(outStream);
@ -337,8 +304,9 @@ MainWindow::MainWindow(QWidget *parent)
modelDoc.load(inStream); modelDoc.load(inStream);
for (pugi::xml_node instNode : modelDoc.child("openblocks").children("Item")) { for (pugi::xml_node instNode : modelDoc.child("openblocks").children("Item")) {
InstanceRef inst = Instance::Deserialize(&instNode); result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
selectedParent->AddChild(inst); if (!inst) { inst.logError(); continue; }
selectedParent->AddChild(inst.expect());
} }
}); });
@ -362,8 +330,6 @@ MainWindow::MainWindow(QWidget *parent)
// ui->explorerView->Init(ui); // ui->explorerView->Init(ui);
simulationInit();
// Baseplate // Baseplate
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({ gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
.position = glm::vec3(0, -5, 0), .position = glm::vec3(0, -5, 0),
@ -373,7 +339,7 @@ MainWindow::MainWindow(QWidget *parent)
.anchored = true, .anchored = true,
})); }));
ui->mainWidget->lastPart->name = "Baseplate"; ui->mainWidget->lastPart->name = "Baseplate";
syncPartPhysics(ui->mainWidget->lastPart); gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({ gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
.position = glm::vec3(0), .position = glm::vec3(0),
@ -381,7 +347,7 @@ MainWindow::MainWindow(QWidget *parent)
.size = glm::vec3(4, 1.2, 2), .size = glm::vec3(4, 1.2, 2),
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f), .color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
})); }));
syncPartPhysics(ui->mainWidget->lastPart); gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
} }
void MainWindow::closeEvent(QCloseEvent* evt) { void MainWindow::closeEvent(QCloseEvent* evt) {
@ -428,7 +394,7 @@ void MainWindow::timerEvent(QTimerEvent* evt) {
lastTime = std::chrono::steady_clock::now(); lastTime = std::chrono::steady_clock::now();
if (simulationPlaying) if (simulationPlaying)
physicsStep(deltaTime); gWorkspace()->PhysicsStep(deltaTime);
ui->mainWidget->update(); ui->mainWidget->update();
ui->mainWidget->updateCycle(); ui->mainWidget->updateCycle();
} }

View file

@ -2,10 +2,8 @@
#define MAINWINDOW_H #define MAINWINDOW_H
#include "logger.h" #include "logger.h"
#include "panes/explorerview.h"
#include "qbasictimer.h" #include "qbasictimer.h"
#include "qcoreevent.h" #include "qcoreevent.h"
#include "qmenu.h"
#include <QMainWindow> #include <QMainWindow>
#include <QLineEdit> #include <QLineEdit>
#include <qfiledialog.h> #include <qfiledialog.h>

View file

@ -1,20 +1,7 @@
#include "explorermodel.h" #include "explorermodel.h"
#include "objects/base/instance.h"
#include "qabstractitemmodel.h"
#include "qcontainerfwd.h"
#include "qimage.h"
#include "qnamespace.h"
#include "qobject.h"
#include "qstringview.h"
#include "qwidget.h"
#include "qmimedata.h"
#include "common.h" #include "common.h"
#include "logger.h" #include <qmimedata.h>
#include <algorithm> #include <QWidget>
#include <cstdio>
#include <optional>
#include <vector>
#include "objects/base/instance.h"
// https://doc.qt.io/qt-6/qtwidgets-itemviews-simpletreemodel-example.html#testing-the-model // https://doc.qt.io/qt-6/qtwidgets-itemviews-simpletreemodel-example.html#testing-the-model

View file

@ -2,11 +2,6 @@
#include "objects/base/instance.h" #include "objects/base/instance.h"
#include "qabstractitemmodel.h" #include "qabstractitemmodel.h"
#include "qevent.h"
#include "qnamespace.h"
#include <QOpenGLWidget>
#include <QWidget>
class ExplorerModel : public QAbstractItemModel { class ExplorerModel : public QAbstractItemModel {
Q_OBJECT Q_OBJECT

View file

@ -1,13 +1,6 @@
#include "explorerview.h" #include "explorerview.h"
#include "explorermodel.h"
#include "mainwindow.h"
#include "../ui_mainwindow.h"
#include "common.h" #include "common.h"
#include "objects/base/instance.h" #include "../ui_mainwindow.h"
#include "qabstractitemmodel.h"
#include <qaction.h>
#include <qnamespace.h>
#include <qitemselectionmodel.h>
#define M_mainWindow dynamic_cast<MainWindow*>(window()) #define M_mainWindow dynamic_cast<MainWindow*>(window())

View file

@ -1,15 +1,8 @@
#pragma once #pragma once
#include "objects/base/instance.h" #include "panes/explorermodel.h"
#include "objects/part.h" #include <qmenu.h>
#include "qevent.h" #include <qtreeview.h>
#include "qmenu.h"
#include "qnamespace.h"
#include "qtreeview.h"
#include <QOpenGLWidget>
#include <QWidget>
#include <memory>
#include "explorermodel.h"
class Ui_MainWindow; class Ui_MainWindow;

View file

@ -1,15 +1,10 @@
#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 "qmenu.h"
#include "qnamespace.h" #include "qnamespace.h"
#include "qtreeview.h"
#include <QOpenGLWidget> #include <QOpenGLWidget>
#include <QWidget> #include <QWidget>
#include <memory>
class PropertiesModel : public QAbstractItemModel { class PropertiesModel : public QAbstractItemModel {
Q_OBJECT Q_OBJECT

View file

@ -1,15 +1,7 @@
#pragma once #pragma once
#include "objects/base/instance.h" #include "objects/base/instance.h"
#include "objects/part.h"
#include "qevent.h"
#include "qmenu.h"
#include "qnamespace.h"
#include "qtreeview.h" #include "qtreeview.h"
#include <QOpenGLWidget>
#include <QWidget>
#include <memory>
#include "explorermodel.h"
class Ui_MainWindow; class Ui_MainWindow;