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/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);
}
}

View file

@ -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) {

View file

@ -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)

View file

@ -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()
);
}

View file

@ -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;

View file

@ -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());
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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

View file

@ -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());
}

View file

@ -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
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 () {
}
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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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();

View file

@ -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>

View file

@ -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

View file

@ -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;

View file

@ -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);
}

View file

@ -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);
};

View file

@ -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();

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/trigonometric.hpp>
#include <memory>
#include <vector>
#include "datatypes/cframe.h"
#include "physics/util.h"
#include "shader.h"
#include "mesh.h"
#include "defaultmeshes.h"

View file

@ -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)

View file

@ -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();
}

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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())

View file

@ -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;

View file

@ -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

View file

@ -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;