Compare commits
9 commits
b31e90ef68
...
6a017b2238
Author | SHA1 | Date | |
---|---|---|---|
6a017b2238 | |||
35f49b8a45 | |||
dbc02e2a4f | |||
83c6d210e2 | |||
eeaaef8c88 | |||
22fcd1e55e | |||
595f3ebbe9 | |||
36397c1119 | |||
923c1c99b4 |
39 changed files with 616 additions and 520 deletions
49
BUILD.md
Normal file
49
BUILD.md
Normal 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
5
README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Openblocks
|
||||
|
||||
[ Docs are work in progress! ]
|
||||
|
||||
For build instructions, see [BUILD.md](./BUILD.md)
|
|
@ -1,20 +1,7 @@
|
|||
#include <GL/glew.h>
|
||||
#include <GL/gl.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 "rendering/renderer.h"
|
||||
#include "physics/simulation.h"
|
||||
#include "camera.h"
|
||||
|
||||
#include "common.h"
|
||||
|
||||
void errorCatcher(int id, const char* str);
|
||||
|
@ -46,7 +33,6 @@ int main() {
|
|||
glewInit();
|
||||
|
||||
gDataModel->Init();
|
||||
simulationInit();
|
||||
renderInit(window, 1200, 900);
|
||||
|
||||
// Baseplate
|
||||
|
@ -68,7 +54,7 @@ int main() {
|
|||
for (InstanceRef inst : gWorkspace()->GetChildren()) {
|
||||
if (inst->GetClass()->className != "Part") continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
syncPartPhysics(part);
|
||||
gWorkspace()->SyncPartPhysics(part);
|
||||
}
|
||||
|
||||
float lastTime = glfwGetTime();
|
||||
|
@ -77,7 +63,7 @@ int main() {
|
|||
lastTime = glfwGetTime();
|
||||
|
||||
processInput(window);
|
||||
physicsStep(deltaTime);
|
||||
gWorkspace()->PhysicsStep(deltaTime);
|
||||
render(window);
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
|
@ -115,15 +101,15 @@ void processInput(GLFWwindow* window) {
|
|||
shiftFactor *= deltaTime;
|
||||
if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS) {
|
||||
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(1, 0, 0));
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_Y) == GLFW_PRESS) {
|
||||
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 1, 0));
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS) {
|
||||
// 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),
|
||||
.color = glm::vec3(1.0f, 0.5f, 0.31f),
|
||||
}));
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
|
||||
float shiftFactor = (mods & GLFW_MOD_SHIFT) ? -0.2 : 0.2;
|
||||
if (mode == 0) {
|
||||
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
|
||||
// lastPart->position.x += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
|
||||
// lastPart->position.y += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
|
||||
// lastPart->position.z += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
} else if (mode == 1) {
|
||||
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
|
||||
lastPart->size.x += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
|
||||
lastPart->size.y += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
|
||||
lastPart->size.z += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ Data::CLASS_NAME::~CLASS_NAME() = default; \
|
|||
Data::CLASS_NAME::operator const WRAPPED_TYPE() const { return value; } \
|
||||
const Data::TypeInfo Data::CLASS_NAME::TYPE = { .name = TYPE_NAME, .deserializer = &Data::CLASS_NAME::Deserialize, .fromString = &Data::CLASS_NAME::FromString }; \
|
||||
const Data::TypeInfo& Data::CLASS_NAME::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() {};
|
||||
|
||||
|
@ -22,11 +22,11 @@ const Data::String Data::Null::ToString() const {
|
|||
return Data::String("null");
|
||||
}
|
||||
|
||||
void Data::Null::Serialize(pugi::xml_node* node) const {
|
||||
node->text().set("null");
|
||||
void Data::Null::Serialize(pugi::xml_node node) const {
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -41,8 +41,8 @@ const Data::String Data::Bool::ToString() const {
|
|||
return Data::String(value ? "true" : "false");
|
||||
}
|
||||
|
||||
Data::Variant Data::Bool::Deserialize(pugi::xml_node* node) {
|
||||
return Data::Bool(node->text().as_bool());
|
||||
Data::Variant Data::Bool::Deserialize(pugi::xml_node node) {
|
||||
return Data::Bool(node.text().as_bool());
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
Data::Variant Data::Int::Deserialize(pugi::xml_node* node) {
|
||||
return Data::Int(node->text().as_int());
|
||||
Data::Variant Data::Int::Deserialize(pugi::xml_node node) {
|
||||
return Data::Int(node.text().as_int());
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
Data::Variant Data::Float::Deserialize(pugi::xml_node* node) {
|
||||
return Data::Float(node->text().as_float());
|
||||
Data::Variant Data::Float::Deserialize(pugi::xml_node node) {
|
||||
return Data::Float(node.text().as_float());
|
||||
}
|
||||
|
||||
Data::Variant Data::Float::FromString(std::string string) {
|
||||
|
@ -80,8 +80,8 @@ const Data::String Data::String::ToString() const {
|
|||
return *this;
|
||||
}
|
||||
|
||||
Data::Variant Data::String::Deserialize(pugi::xml_node* node) {
|
||||
return Data::String(node->text().as_string());
|
||||
Data::Variant Data::String::Deserialize(pugi::xml_node node) {
|
||||
return Data::String(node.text().as_string());
|
||||
}
|
||||
|
||||
Data::Variant Data::String::FromString(std::string string) {
|
||||
|
|
|
@ -14,14 +14,14 @@ public: \
|
|||
static const TypeInfo TYPE; \
|
||||
\
|
||||
virtual const Data::String ToString() const override; \
|
||||
virtual void Serialize(pugi::xml_node* node) const override; \
|
||||
static Data::Variant Deserialize(pugi::xml_node* node); \
|
||||
virtual void Serialize(pugi::xml_node node) const override; \
|
||||
static Data::Variant Deserialize(pugi::xml_node node); \
|
||||
static Data::Variant FromString(std::string); \
|
||||
};
|
||||
|
||||
namespace Data {
|
||||
class Variant;
|
||||
typedef std::function<Data::Variant(pugi::xml_node*)> Deserializer;
|
||||
typedef std::function<Data::Variant(pugi::xml_node)> Deserializer;
|
||||
typedef std::function<Data::Variant(std::string)> FromString;
|
||||
|
||||
struct TypeInfo {
|
||||
|
@ -36,7 +36,7 @@ namespace Data {
|
|||
virtual ~Base();
|
||||
virtual const TypeInfo& GetType() 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 {
|
||||
|
@ -47,8 +47,8 @@ namespace Data {
|
|||
static const TypeInfo TYPE;
|
||||
|
||||
virtual const Data::String ToString() const override;
|
||||
virtual void Serialize(pugi::xml_node* node) const override;
|
||||
static Data::Variant Deserialize(pugi::xml_node* node);
|
||||
virtual void Serialize(pugi::xml_node node) const override;
|
||||
static Data::Variant Deserialize(pugi::xml_node node);
|
||||
};
|
||||
|
||||
DEF_WRAPPER_CLASS(Bool, bool)
|
||||
|
|
|
@ -112,35 +112,35 @@ Data::CFrame Data::CFrame::operator -(Data::Vector3 vector) const {
|
|||
|
||||
// Serialization
|
||||
|
||||
void Data::CFrame::Serialize(pugi::xml_node* node) const {
|
||||
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("Z").text().set(std::to_string(this->Z()));
|
||||
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("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("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("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("R22").text().set(std::to_string(this->rotation[2][2]));
|
||||
void Data::CFrame::Serialize(pugi::xml_node node) const {
|
||||
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("Z").text().set(std::to_string(this->Z()));
|
||||
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("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("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("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("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(
|
||||
node->child("X").text().as_float(),
|
||||
node->child("Y").text().as_float(),
|
||||
node->child("Z").text().as_float(),
|
||||
node->child("R00").text().as_float(),
|
||||
node->child("R01").text().as_float(),
|
||||
node->child("R02").text().as_float(),
|
||||
node->child("R10").text().as_float(),
|
||||
node->child("R11").text().as_float(),
|
||||
node->child("R12").text().as_float(),
|
||||
node->child("R20").text().as_float(),
|
||||
node->child("R21").text().as_float(),
|
||||
node->child("R22").text().as_float()
|
||||
node.child("X").text().as_float(),
|
||||
node.child("Y").text().as_float(),
|
||||
node.child("Z").text().as_float(),
|
||||
node.child("R00").text().as_float(),
|
||||
node.child("R01").text().as_float(),
|
||||
node.child("R02").text().as_float(),
|
||||
node.child("R10").text().as_float(),
|
||||
node.child("R11").text().as_float(),
|
||||
node.child("R12").text().as_float(),
|
||||
node.child("R20").text().as_float(),
|
||||
node.child("R21").text().as_float(),
|
||||
node.child("R22").text().as_float()
|
||||
);
|
||||
}
|
|
@ -2,12 +2,8 @@
|
|||
|
||||
#include "base.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include <glm/ext/matrix_float3x3.hpp>
|
||||
#include <glm/ext/quaternion_geometric.hpp>
|
||||
#include <glm/ext/vector_float3.hpp>
|
||||
#include <glm/fwd.hpp>
|
||||
#include <glm/ext/quaternion_float.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/gtc/matrix_inverse.hpp>
|
||||
#include <glm/matrix.hpp>
|
||||
#include <reactphysics3d/mathematics/Transform.h>
|
||||
#include <reactphysics3d/reactphysics3d.h>
|
||||
|
@ -37,8 +33,8 @@ namespace Data {
|
|||
static const TypeInfo TYPE;
|
||||
|
||||
virtual const Data::String ToString() const override;
|
||||
virtual void Serialize(pugi::xml_node* parent) const override;
|
||||
static Data::Variant Deserialize(pugi::xml_node* node);
|
||||
virtual void Serialize(pugi::xml_node parent) const override;
|
||||
static Data::Variant Deserialize(pugi::xml_node node);
|
||||
|
||||
operator glm::mat4() const;
|
||||
operator rp::Transform() const;
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
#include "color3.h"
|
||||
#include <algorithm>
|
||||
#include <glm/ext/quaternion_geometric.hpp>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <string>
|
||||
#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)) {};
|
||||
|
@ -43,10 +38,10 @@ Data::Color3 Data::Color3::FromHex(std::string hex) {
|
|||
|
||||
// Serialization
|
||||
|
||||
void Data::Color3::Serialize(pugi::xml_node* node) const {
|
||||
node->text().set(this->ToHex());
|
||||
void Data::Color3::Serialize(pugi::xml_node node) const {
|
||||
node.text().set(this->ToHex());
|
||||
}
|
||||
|
||||
Data::Variant Data::Color3::Deserialize(pugi::xml_node* node) {
|
||||
return Color3::FromHex(node->text().get());
|
||||
Data::Variant Data::Color3::Deserialize(pugi::xml_node node) {
|
||||
return Color3::FromHex(node.text().get());
|
||||
}
|
||||
|
|
|
@ -25,8 +25,8 @@ namespace Data {
|
|||
|
||||
virtual const Data::String ToString() const override;
|
||||
std::string ToHex() const;
|
||||
virtual void Serialize(pugi::xml_node* node) const override;
|
||||
static Data::Variant Deserialize(pugi::xml_node* node);
|
||||
virtual void Serialize(pugi::xml_node node) const override;
|
||||
static Data::Variant Deserialize(pugi::xml_node node);
|
||||
|
||||
operator glm::vec3() const;
|
||||
|
||||
|
|
|
@ -11,19 +11,19 @@ Data::String Data::Variant::ToString() const {
|
|||
}, this->wrapped);
|
||||
}
|
||||
|
||||
void Data::Variant::Serialize(pugi::xml_node* node) const {
|
||||
void Data::Variant::Serialize(pugi::xml_node node) const {
|
||||
std::visit([&](auto&& it) {
|
||||
it.Serialize(node);
|
||||
}, this->wrapped);
|
||||
}
|
||||
|
||||
Data::Variant Data::Variant::Deserialize(pugi::xml_node* node) {
|
||||
if (Data::TYPE_MAP.count(node->name()) == 0) {
|
||||
Logger::fatalErrorf("Unknown type for instance: '%s'", node->name());
|
||||
Data::Variant Data::Variant::Deserialize(pugi::xml_node node) {
|
||||
if (Data::TYPE_MAP.count(node.name()) == 0) {
|
||||
Logger::fatalErrorf("Unknown type for instance: '%s'", node.name());
|
||||
panic();
|
||||
}
|
||||
|
||||
const Data::TypeInfo* type = Data::TYPE_MAP[node->name()];
|
||||
const Data::TypeInfo* type = Data::TYPE_MAP[node.name()];
|
||||
return type->deserializer(node);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ namespace Data {
|
|||
template <typename T> T get() { return std::get<T>(wrapped); }
|
||||
Data::String ToString() const;
|
||||
|
||||
void Serialize(pugi::xml_node* node) const;
|
||||
static Data::Variant Deserialize(pugi::xml_node* node);
|
||||
void Serialize(pugi::xml_node node) const;
|
||||
static Data::Variant Deserialize(pugi::xml_node node);
|
||||
};
|
||||
|
||||
// Map of all data types to their type names
|
||||
|
|
|
@ -65,12 +65,12 @@ float Data::Vector3::Dot(Data::Vector3 other) const {
|
|||
|
||||
// Serialization
|
||||
|
||||
void Data::Vector3::Serialize(pugi::xml_node* node) const {
|
||||
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("Z").text().set(std::to_string(this->Z()));
|
||||
void Data::Vector3::Serialize(pugi::xml_node node) const {
|
||||
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("Z").text().set(std::to_string(this->Z()));
|
||||
}
|
||||
|
||||
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());
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -24,8 +24,8 @@ namespace Data {
|
|||
static Data::Vector3 ONE;
|
||||
|
||||
virtual const Data::String ToString() const override;
|
||||
virtual void Serialize(pugi::xml_node* node) const override;
|
||||
static Data::Variant Deserialize(pugi::xml_node* node);
|
||||
virtual void Serialize(pugi::xml_node node) const override;
|
||||
static Data::Variant Deserialize(pugi::xml_node node);
|
||||
|
||||
operator glm::vec3() const;
|
||||
operator rp::Vector3() const;
|
||||
|
|
17
core/src/error/error.cpp
Normal file
17
core/src/error/error.cpp
Normal 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
17
core/src/error/error.h
Normal 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
59
core/src/error/result.h
Normal 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) {}
|
||||
};
|
|
@ -39,6 +39,12 @@ Instance::Instance(const InstanceType* type) {
|
|||
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
|
||||
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()) {
|
||||
|
@ -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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<DataModel>> Instance::dataModel() {
|
||||
// TODO: This algorithm will defer calculations to every time the root data model
|
||||
// is accessed from any instance. This is inefficient as this can happen many times
|
||||
// a tick. A better option is to cache these values and only update them if the ancestry
|
||||
// changes, as that happens way less often.
|
||||
|
||||
std::optional<std::shared_ptr<Instance>> currentParent = GetParent();
|
||||
while (currentParent) {
|
||||
if (currentParent.value()->GetClass() == &DataModel::TYPE)
|
||||
return std::dynamic_pointer_cast<DataModel>(currentParent.value());
|
||||
|
||||
currentParent = currentParent.value()->GetParent();
|
||||
void Instance::updateAncestry(std::optional<std::shared_ptr<Instance>> updatedChild, std::optional<std::shared_ptr<Instance>> newParent) {
|
||||
if (GetParent()) {
|
||||
this->_dataModel = GetParent().value()->GetClass() == &DataModel::TYPE ? std::make_optional(std::dynamic_pointer_cast<DataModel>(GetParent().value())) : GetParent().value()->dataModel();
|
||||
this->_workspace = GetParent().value()->GetClass() == &Workspace::TYPE ? std::make_optional(std::dynamic_pointer_cast<Workspace>(GetParent().value())) : GetParent().value()->workspace();
|
||||
} else {
|
||||
this->_dataModel = std::nullopt;
|
||||
this->_workspace = std::nullopt;
|
||||
}
|
||||
|
||||
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() {
|
||||
// See comment in above function
|
||||
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;
|
||||
return (!_workspace || _workspace->expired()) ? std::nullopt : std::make_optional(_workspace.value().lock());
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<Instance>> Instance::GetParent() {
|
||||
|
@ -125,6 +141,10 @@ void Instance::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParen
|
|||
// Empty stub
|
||||
}
|
||||
|
||||
void Instance::OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent) {
|
||||
// Empty stub
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
tl::expected<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
|
||||
|
@ -186,8 +206,8 @@ std::vector<std::string> Instance::GetProperties() {
|
|||
|
||||
// Serialization
|
||||
|
||||
void Instance::Serialize(pugi::xml_node* parent) {
|
||||
pugi::xml_node node = parent->append_child("Item");
|
||||
void Instance::Serialize(pugi::xml_node parent) {
|
||||
pugi::xml_node node = parent.append_child("Item");
|
||||
node.append_attribute("class").set_value(this->GetClass()->className);
|
||||
|
||||
// Add properties
|
||||
|
@ -198,20 +218,19 @@ void Instance::Serialize(pugi::xml_node* parent) {
|
|||
|
||||
pugi::xml_node propertyNode = propertiesNode.append_child(meta.type->name);
|
||||
propertyNode.append_attribute("name").set_value(name);
|
||||
GetPropertyValue(name)->Serialize(&propertyNode);
|
||||
GetPropertyValue(name)->Serialize(propertyNode);
|
||||
}
|
||||
|
||||
// Add children
|
||||
for (InstanceRef child : this->children) {
|
||||
child->Serialize(&node);
|
||||
child->Serialize(node);
|
||||
}
|
||||
}
|
||||
|
||||
InstanceRef Instance::Deserialize(pugi::xml_node* node) {
|
||||
std::string className = node->attribute("class").value();
|
||||
result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) {
|
||||
std::string className = node.attribute("class").value();
|
||||
if (INSTANCE_MAP.count(className) == 0) {
|
||||
Logger::fatalErrorf("Unknown type for instance: '%s'", className.c_str());
|
||||
panic();
|
||||
return std::variant<NoSuchInstance>(NoSuchInstance(className));
|
||||
}
|
||||
// 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());
|
||||
|
@ -221,7 +240,7 @@ InstanceRef Instance::Deserialize(pugi::xml_node* node) {
|
|||
// const InstanceType* type = INSTANCE_MAP.at(className);
|
||||
|
||||
// Read properties
|
||||
pugi::xml_node propertiesNode = node->child("Properties");
|
||||
pugi::xml_node propertiesNode = node.child("Properties");
|
||||
for (pugi::xml_node propertyNode : propertiesNode) {
|
||||
std::string propertyName = propertyNode.attribute("name").value();
|
||||
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());
|
||||
continue;
|
||||
}
|
||||
Data::Variant value = Data::Variant::Deserialize(&propertyNode);
|
||||
Data::Variant value = Data::Variant::Deserialize(propertyNode);
|
||||
object->SetPropertyValue(propertyName, value);
|
||||
}
|
||||
|
||||
// Read children
|
||||
for (pugi::xml_node childNode : node->children("Item")) {
|
||||
InstanceRef child = Instance::Deserialize(&childNode);
|
||||
object->AddChild(child);
|
||||
for (pugi::xml_node childNode : node.children("Item")) {
|
||||
result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode);
|
||||
if (child.is_error()) {
|
||||
std::get<NoSuchInstance>(child.error().value()).logMessage();
|
||||
continue;
|
||||
}
|
||||
object->AddChild(child.expect());
|
||||
}
|
||||
|
||||
return object;
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
// #include <../../include/expected.hpp>
|
||||
#include <expected.hpp>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include "error/error.h"
|
||||
#include "error/result.h"
|
||||
#include "member.h"
|
||||
|
||||
class Instance;
|
||||
|
@ -23,12 +23,25 @@ typedef std::shared_ptr<Instance>(*InstanceConstructor)();
|
|||
class DataModel;
|
||||
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 InstanceType {
|
||||
const InstanceType* super; // May be null
|
||||
std::string className;
|
||||
InstanceConstructor constructor;
|
||||
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;
|
||||
|
@ -44,7 +57,11 @@ private:
|
|||
|
||||
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);
|
||||
void updateAncestry(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent);
|
||||
protected:
|
||||
bool parentLocked = false;
|
||||
std::unique_ptr<MemberMap> memberMap;
|
||||
|
@ -53,6 +70,7 @@ protected:
|
|||
virtual ~Instance();
|
||||
|
||||
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
|
||||
std::optional<std::shared_ptr<DataModel>> dataModel();
|
||||
|
@ -87,8 +105,8 @@ public:
|
|||
std::vector<std::string> GetProperties();
|
||||
|
||||
// Serialization
|
||||
void Serialize(pugi::xml_node* parent);
|
||||
static std::shared_ptr<Instance> Deserialize(pugi::xml_node* node);
|
||||
void Serialize(pugi::xml_node parent);
|
||||
static result<std::shared_ptr<Instance>, NoSuchInstance> Deserialize(pugi::xml_node node);
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Instance> InstanceRef;
|
||||
|
|
|
@ -1,9 +1,20 @@
|
|||
#include "service.h"
|
||||
#include "logger.h"
|
||||
#include "panic.h"
|
||||
#include <memory>
|
||||
|
||||
Service::Service(const InstanceType* type, std::weak_ptr<DataModel> root) : Instance(type), dataModel(root) {}
|
||||
Service::Service(const InstanceType* type) : Instance(type){}
|
||||
|
||||
// Fail if parented to non-datamodel, otherwise lock parent
|
||||
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;
|
||||
}
|
||||
|
||||
void Service::InitService() {
|
||||
SetParent(dataModel.lock());
|
||||
parentLocked = true;
|
||||
}
|
|
@ -6,10 +6,10 @@
|
|||
#include <memory>
|
||||
class Service : public Instance {
|
||||
protected:
|
||||
std::weak_ptr<DataModel> dataModel;
|
||||
|
||||
Service(const InstanceType* type, std::weak_ptr<DataModel> root);
|
||||
Service(const InstanceType* type);
|
||||
virtual void InitService();
|
||||
|
||||
void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) override;
|
||||
|
||||
friend class DataModel;
|
||||
};
|
|
@ -2,19 +2,13 @@
|
|||
#include "base/service.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/base/service.h"
|
||||
#include "objects/meta.h"
|
||||
#include "workspace.h"
|
||||
#include "logger.h"
|
||||
#include "panic.h"
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#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 = {
|
||||
.super = &Instance::TYPE,
|
||||
|
@ -33,8 +27,10 @@ DataModel::DataModel()
|
|||
|
||||
void DataModel::Init() {
|
||||
// Create the workspace if it doesn't exist
|
||||
if (this->services.count("Workspace") == 0)
|
||||
this->services["Workspace"] = std::make_shared<Workspace>(shared<DataModel>());
|
||||
if (this->services.count("Workspace") == 0) {
|
||||
this->services["Workspace"] = std::make_shared<Workspace>();
|
||||
AddChild(this->services["Workspace"]);
|
||||
}
|
||||
|
||||
// Init all 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");
|
||||
|
||||
for (InstanceRef child : this->GetChildren()) {
|
||||
child->Serialize(&root);
|
||||
child->Serialize(root);
|
||||
}
|
||||
|
||||
doc.save(outStream);
|
||||
|
@ -65,11 +61,11 @@ void DataModel::SaveToFile(std::optional<std::string> path) {
|
|||
Logger::info("Place saved successfully");
|
||||
}
|
||||
|
||||
void DataModel::DeserializeService(pugi::xml_node* node) {
|
||||
std::string className = node->attribute("class").value();
|
||||
if (SERVICE_CONSTRUCTORS.count(className) == 0) {
|
||||
void DataModel::DeserializeService(pugi::xml_node node) {
|
||||
std::string className = node.attribute("class").value();
|
||||
if (INSTANCE_MAP.count(className) == 0) {
|
||||
Logger::fatalErrorf("Unknown service: '%s'", className.c_str());
|
||||
panic();
|
||||
return;
|
||||
}
|
||||
|
||||
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.
|
||||
InstanceRef object = SERVICE_CONSTRUCTORS[className](shared<DataModel>());
|
||||
InstanceRef object = INSTANCE_MAP[className]->constructor();
|
||||
AddChild(object);
|
||||
|
||||
// Read properties
|
||||
pugi::xml_node propertiesNode = node->child("Properties");
|
||||
pugi::xml_node propertiesNode = node.child("Properties");
|
||||
for (pugi::xml_node propertyNode : propertiesNode) {
|
||||
std::string propertyName = propertyNode.attribute("name").value();
|
||||
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());
|
||||
continue;
|
||||
}
|
||||
Data::Variant value = Data::Variant::Deserialize(&propertyNode);
|
||||
Data::Variant value = Data::Variant::Deserialize(propertyNode);
|
||||
object->SetPropertyValue(propertyName, value);
|
||||
}
|
||||
|
||||
// Add children
|
||||
for (pugi::xml_node childNode : node->children("Item")) {
|
||||
InstanceRef child = Instance::Deserialize(&childNode);
|
||||
object->AddChild(child);
|
||||
for (pugi::xml_node childNode : node.children("Item")) {
|
||||
result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode);
|
||||
if (child.is_error()) {
|
||||
std::get<NoSuchInstance>(child.error().value()).logMessage();
|
||||
continue;
|
||||
}
|
||||
object->AddChild(child.expect());
|
||||
}
|
||||
|
||||
// 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>();
|
||||
|
||||
for (pugi::xml_node childNode : rootNode.children("Item")) {
|
||||
newModel->DeserializeService(&childNode);
|
||||
newModel->DeserializeService(childNode);
|
||||
}
|
||||
|
||||
newModel->Init();
|
||||
|
|
|
@ -1,18 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#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 <variant>
|
||||
|
||||
class Workspace;
|
||||
|
||||
class DataModel;
|
||||
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
|
||||
class DataModel : public Instance {
|
||||
private:
|
||||
void DeserializeService(pugi::xml_node* node);
|
||||
void DeserializeService(pugi::xml_node node);
|
||||
public:
|
||||
const static InstanceType TYPE;
|
||||
|
||||
|
@ -26,12 +41,36 @@ public:
|
|||
static inline std::shared_ptr<DataModel> New() { return std::make_shared<DataModel>(); };
|
||||
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>
|
||||
std::shared_ptr<T> GetService(std::string name) {
|
||||
result<std::shared_ptr<T>, NoSuchService> GetService(std::string name) {
|
||||
if (services.count(name) != 0)
|
||||
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>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "part.h"
|
||||
#include "base/instance.h"
|
||||
#include "common.h"
|
||||
#include "datatypes/base.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/color3.h"
|
||||
|
@ -7,7 +8,6 @@
|
|||
#include "objects/base/member.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include "physics/simulation.h"
|
||||
|
||||
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() {
|
||||
// This relies on physicsCommon still existing. Be very careful.
|
||||
if (this->rigidBody)
|
||||
world->destroyRigidBody(rigidBody);
|
||||
if (this->rigidBody && workspace())
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include <memory>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/ext.hpp>
|
||||
#include "../rendering/material.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/color3.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "rendering/surface.h"
|
||||
#include <reactphysics3d/reactphysics3d.h>
|
||||
|
||||
|
@ -25,7 +24,7 @@ struct PartConstructParams {
|
|||
|
||||
class Part : public Instance {
|
||||
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);
|
||||
public:
|
||||
const static InstanceType TYPE;
|
||||
|
|
|
@ -1,15 +1,147 @@
|
|||
#include "workspace.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "physics/util.h"
|
||||
|
||||
const InstanceType Workspace::TYPE = {
|
||||
.super = &Instance::TYPE,
|
||||
.className = "Workspace",
|
||||
// .constructor = &Workspace::Create,
|
||||
.constructor = &Workspace::Create,
|
||||
.explorerIcon = "workspace",
|
||||
.flags = INSTANCE_NOTCREATABLE | INSTANCE_SERVICE,
|
||||
};
|
||||
|
||||
const InstanceType* Workspace::GetClass() {
|
||||
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);
|
||||
}
|
|
@ -1,17 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include "objects/base/service.h"
|
||||
#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 {
|
||||
//private:
|
||||
rp::PhysicsWorld *physicsWorld = nullptr;
|
||||
|
||||
protected:
|
||||
void InitService() override;
|
||||
bool initialized = false;
|
||||
|
||||
public:
|
||||
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<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;
|
||||
|
||||
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);
|
||||
|
||||
};
|
|
@ -5,4 +5,4 @@
|
|||
// before shutting down.
|
||||
|
||||
// If this process fails, or the panic function is called within itself, it will hard-abort
|
||||
void panic();
|
||||
[[noreturn]] void panic();
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
|
@ -9,10 +9,8 @@
|
|||
#include <glm/glm.hpp>
|
||||
#include <glm/trigonometric.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "datatypes/cframe.h"
|
||||
#include "physics/util.h"
|
||||
#include "shader.h"
|
||||
#include "mesh.h"
|
||||
#include "defaultmeshes.h"
|
||||
|
|
|
@ -1,45 +1,11 @@
|
|||
#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 <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 "common.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));
|
||||
|
||||
|
@ -145,7 +111,7 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
|
|||
QPoint position = evt->pos();
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
|
@ -178,7 +144,7 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
|
|||
|
||||
draggingObject->lock()->cframe = newFrame + unsinkOffset;
|
||||
|
||||
syncPartPhysics(draggingObject->lock());
|
||||
gWorkspace()->SyncPartPhysics(draggingObject->lock());
|
||||
}
|
||||
|
||||
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"))
|
||||
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)
|
||||
|
@ -297,7 +263,7 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
|
|||
|
||||
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) {
|
||||
|
@ -315,7 +281,7 @@ void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
|
|||
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") {
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
return;
|
||||
|
@ -372,7 +338,7 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
|||
}
|
||||
|
||||
// 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;
|
||||
std::shared_ptr<Part> part = partFromBody(rayHit->body);
|
||||
if (part->name == "Baseplate") return;
|
||||
|
@ -449,7 +415,7 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
|
|||
.size = glm::vec3(1, 1, 1),
|
||||
.color = glm::vec3(1.0f, 0.5f, 0.31f),
|
||||
}));
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
|
||||
if (evt->key() == Qt::Key_U)
|
||||
|
|
|
@ -1,43 +1,10 @@
|
|||
#include "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 "editorcommon.h"
|
||||
#include "logger.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/datamodel.h"
|
||||
#include "objects/handles.h"
|
||||
#include "physics/simulation.h"
|
||||
#include "objects/part.h"
|
||||
#include "qfiledialog.h"
|
||||
#include "qclipboard.h"
|
||||
#include "qmimedata.h"
|
||||
#include "qobject.h"
|
||||
#include "qsysinfo.h"
|
||||
#include <qclipboard.h>
|
||||
#include <qmessagebox.h>
|
||||
#include <qmimedata.h>
|
||||
#include <qstylehints.h>
|
||||
|
||||
#ifdef _NDEBUG
|
||||
#define NDEBUG
|
||||
|
@ -192,9 +159,6 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
gDataModel->Init();
|
||||
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
|
||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
.position = glm::vec3(0, -5, 0),
|
||||
|
@ -204,14 +168,14 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
.anchored = true,
|
||||
}));
|
||||
ui->mainWidget->lastPart->name = "Baseplate";
|
||||
syncPartPhysics(ui->mainWidget->lastPart);
|
||||
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
|
||||
});
|
||||
|
||||
connect(ui->actionSave, &QAction::triggered, this, [&]() {
|
||||
std::optional<std::string> path;
|
||||
if (!gDataModel->HasFile())
|
||||
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + gDataModel->name));
|
||||
if (!path || path == "") return;
|
||||
if (!gDataModel->HasFile() && (!path || path == "")) return;
|
||||
|
||||
gDataModel->SaveToFile(path);
|
||||
});
|
||||
|
@ -233,6 +197,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
// simulationInit();
|
||||
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value());
|
||||
gDataModel = newModel;
|
||||
newModel->Init();
|
||||
ui->explorerView->updateRoot(newModel);
|
||||
});
|
||||
|
||||
|
@ -248,7 +213,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
pugi::xml_document rootDoc;
|
||||
for (InstanceRefWeak inst : getSelection()) {
|
||||
if (inst.expired()) continue;
|
||||
inst.lock()->Serialize(&rootDoc);
|
||||
inst.lock()->Serialize(rootDoc);
|
||||
}
|
||||
|
||||
std::ostringstream encoded;
|
||||
|
@ -262,7 +227,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
pugi::xml_document rootDoc;
|
||||
for (InstanceRefWeak inst : getSelection()) {
|
||||
if (inst.expired()) continue;
|
||||
inst.lock()->Serialize(&rootDoc);
|
||||
inst.lock()->Serialize(rootDoc);
|
||||
inst.lock()->SetParent(std::nullopt);
|
||||
}
|
||||
|
||||
|
@ -284,8 +249,9 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
rootDoc.load_string(encoded.c_str());
|
||||
|
||||
for (pugi::xml_node instNode : rootDoc.children()) {
|
||||
InstanceRef inst = Instance::Deserialize(&instNode);
|
||||
gWorkspace()->AddChild(inst);
|
||||
result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
|
||||
if (!inst) { inst.logError(); continue; }
|
||||
gWorkspace()->AddChild(inst.expect());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -303,8 +269,9 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
rootDoc.load_string(encoded.c_str());
|
||||
|
||||
for (pugi::xml_node instNode : rootDoc.children()) {
|
||||
InstanceRef inst = Instance::Deserialize(&instNode);
|
||||
selectedParent->AddChild(inst);
|
||||
result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
|
||||
if (!inst) { inst.logError(); continue; }
|
||||
selectedParent->AddChild(inst.expect());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -319,7 +286,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
|
||||
for (InstanceRefWeak inst : getSelection()) {
|
||||
if (inst.expired()) continue;
|
||||
inst.lock()->Serialize(&modelRoot);
|
||||
inst.lock()->Serialize(modelRoot);
|
||||
}
|
||||
|
||||
modelDoc.save(outStream);
|
||||
|
@ -337,8 +304,9 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
modelDoc.load(inStream);
|
||||
|
||||
for (pugi::xml_node instNode : modelDoc.child("openblocks").children("Item")) {
|
||||
InstanceRef inst = Instance::Deserialize(&instNode);
|
||||
selectedParent->AddChild(inst);
|
||||
result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
|
||||
if (!inst) { inst.logError(); continue; }
|
||||
selectedParent->AddChild(inst.expect());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -362,8 +330,6 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
|
||||
// ui->explorerView->Init(ui);
|
||||
|
||||
simulationInit();
|
||||
|
||||
// Baseplate
|
||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
.position = glm::vec3(0, -5, 0),
|
||||
|
@ -373,7 +339,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
.anchored = true,
|
||||
}));
|
||||
ui->mainWidget->lastPart->name = "Baseplate";
|
||||
syncPartPhysics(ui->mainWidget->lastPart);
|
||||
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
|
||||
|
||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
.position = glm::vec3(0),
|
||||
|
@ -381,7 +347,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
.size = glm::vec3(4, 1.2, 2),
|
||||
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
|
||||
}));
|
||||
syncPartPhysics(ui->mainWidget->lastPart);
|
||||
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent* evt) {
|
||||
|
@ -428,7 +394,7 @@ void MainWindow::timerEvent(QTimerEvent* evt) {
|
|||
lastTime = std::chrono::steady_clock::now();
|
||||
|
||||
if (simulationPlaying)
|
||||
physicsStep(deltaTime);
|
||||
gWorkspace()->PhysicsStep(deltaTime);
|
||||
ui->mainWidget->update();
|
||||
ui->mainWidget->updateCycle();
|
||||
}
|
||||
|
|
|
@ -2,10 +2,8 @@
|
|||
#define MAINWINDOW_H
|
||||
|
||||
#include "logger.h"
|
||||
#include "panes/explorerview.h"
|
||||
#include "qbasictimer.h"
|
||||
#include "qcoreevent.h"
|
||||
#include "qmenu.h"
|
||||
#include <QMainWindow>
|
||||
#include <QLineEdit>
|
||||
#include <qfiledialog.h>
|
||||
|
|
|
@ -1,20 +1,7 @@
|
|||
#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 "logger.h"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "objects/base/instance.h"
|
||||
#include <qmimedata.h>
|
||||
#include <QWidget>
|
||||
|
||||
// https://doc.qt.io/qt-6/qtwidgets-itemviews-simpletreemodel-example.html#testing-the-model
|
||||
|
||||
|
|
|
@ -2,11 +2,6 @@
|
|||
|
||||
#include "objects/base/instance.h"
|
||||
#include "qabstractitemmodel.h"
|
||||
#include "qevent.h"
|
||||
#include "qnamespace.h"
|
||||
#include <QOpenGLWidget>
|
||||
#include <QWidget>
|
||||
|
||||
|
||||
class ExplorerModel : public QAbstractItemModel {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
#include "explorerview.h"
|
||||
#include "explorermodel.h"
|
||||
#include "mainwindow.h"
|
||||
#include "../ui_mainwindow.h"
|
||||
#include "common.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "qabstractitemmodel.h"
|
||||
#include <qaction.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qitemselectionmodel.h>
|
||||
#include "../ui_mainwindow.h"
|
||||
|
||||
#define M_mainWindow dynamic_cast<MainWindow*>(window())
|
||||
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/part.h"
|
||||
#include "qevent.h"
|
||||
#include "qmenu.h"
|
||||
#include "qnamespace.h"
|
||||
#include "qtreeview.h"
|
||||
#include <QOpenGLWidget>
|
||||
#include <QWidget>
|
||||
#include <memory>
|
||||
#include "explorermodel.h"
|
||||
#include "panes/explorermodel.h"
|
||||
#include <qmenu.h>
|
||||
#include <qtreeview.h>
|
||||
|
||||
class Ui_MainWindow;
|
||||
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/part.h"
|
||||
#include "qabstractitemmodel.h"
|
||||
#include "qevent.h"
|
||||
#include "qmenu.h"
|
||||
#include "qnamespace.h"
|
||||
#include "qtreeview.h"
|
||||
#include <QOpenGLWidget>
|
||||
#include <QWidget>
|
||||
#include <memory>
|
||||
|
||||
class PropertiesModel : public QAbstractItemModel {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/part.h"
|
||||
#include "qevent.h"
|
||||
#include "qmenu.h"
|
||||
#include "qnamespace.h"
|
||||
#include "qtreeview.h"
|
||||
#include <QOpenGLWidget>
|
||||
#include <QWidget>
|
||||
#include <memory>
|
||||
#include "explorermodel.h"
|
||||
|
||||
class Ui_MainWindow;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue