Compare commits

...

8 commits

34 changed files with 954 additions and 120 deletions

View file

@ -23,14 +23,15 @@ find_package(GLUT REQUIRED)
include_directories(${GLUT_INCLUDE_DIRS})
find_package(glfw3 REQUIRED)
find_package(OpenGL)
find_package(glm CONFIG REQUIRED)
find_package(assimp REQUIRED)
find_package(ReactPhysics3D REQUIRED)
find_package(pugixml REQUIRED)
# PkgConfig packages
# find_package(PkgConfig REQUIRED)
# pkg_check_modules(PUGIXML REQUIRED pugixml)
file(MAKE_DIRECTORY bin)
@ -39,7 +40,7 @@ include_directories("include")
file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h")
add_library(openblocks ${SOURCES})
set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks")
target_link_libraries(openblocks ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D)
target_link_libraries(openblocks ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} ${PUGIXML_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
# add_executable(client "client/src/main.cpp" $<TARGET_OBJECTS:openblocks>)
# include_directories("src")

View file

@ -1,4 +1,4 @@
add_executable(client "src/main.cpp" $<TARGET_OBJECTS:openblocks>)
include_directories("../src")
target_link_libraries(client ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D)
target_link_libraries(client ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D pugixml::pugixml)

View file

@ -49,24 +49,16 @@ int main() {
workspace()->AddChild(Part::New({
.position = glm::vec3(0, -5, 0),
.rotation = glm::vec3(0),
.scale = glm::vec3(512, 1.2, 512),
.material = Material {
.diffuse = glm::vec3(0.388235, 0.372549, 0.384314),
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 32.0f,
},
.size = glm::vec3(512, 1.2, 512),
.color = glm::vec3(0.388235, 0.372549, 0.384314),
.anchored = true,
}));
workspace()->AddChild(lastPart = Part::New({
.position = glm::vec3(0),
.rotation = glm::vec3(0),
.scale = glm::vec3(4, 1.2, 2),
.material = Material {
.diffuse = glm::vec3(0.639216f, 0.635294f, 0.647059f),
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 32.0f,
}
.size = glm::vec3(4, 1.2, 2),
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
}));
for (InstanceRef inst : workspace()->GetChildren()) {
@ -118,15 +110,15 @@ void processInput(GLFWwindow* window) {
float shiftFactor = (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) ? -0.5 : 0.5;
shiftFactor *= deltaTime;
if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS) {
lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(1, 0, 0));
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(1, 0, 0));
syncPartPhysics(lastPart);
}
if (glfwGetKey(window, GLFW_KEY_Y) == GLFW_PRESS) {
lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 1, 0));
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 1, 0));
syncPartPhysics(lastPart);
}
if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS) {
lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 0, 1));
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 0, 1));
syncPartPhysics(lastPart);
}
}
@ -165,12 +157,8 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods
workspace()->AddChild(lastPart = Part::New({
.position = camera.cameraPos + camera.cameraFront * glm::vec3(3),
.rotation = glm::vec3(0),
.scale = glm::vec3(1, 1, 1),
.material = Material {
.diffuse = glm::vec3(1.0f, 0.5f, 0.31f),
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 32.0f,
}
.size = glm::vec3(1, 1, 1),
.color = glm::vec3(1.0f, 0.5f, 0.31f),
}));
syncPartPhysics(lastPart);
}
@ -178,28 +166,28 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods
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;
// lastPart->position.x += shiftFactor;
syncPartPhysics(lastPart);
}
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
lastPart->position.y += shiftFactor;
// lastPart->position.y += shiftFactor;
syncPartPhysics(lastPart);
}
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
lastPart->position.z += shiftFactor;
// lastPart->position.z += shiftFactor;
syncPartPhysics(lastPart);
}
} else if (mode == 1) {
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
lastPart->scale.x += shiftFactor;
lastPart->size.x += shiftFactor;
syncPartPhysics(lastPart);
}
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
lastPart->scale.y += shiftFactor;
lastPart->size.y += shiftFactor;
syncPartPhysics(lastPart);
}
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
lastPart->scale.z += shiftFactor;
lastPart->size.z += shiftFactor;
syncPartPhysics(lastPart);
}
}

10
deps.txt Normal file
View file

@ -0,0 +1,10 @@
glm
opengl
assimp
sdl2
glfw
glut
glew
qt6
reactphysics3d
pugixml

View file

@ -66,7 +66,7 @@ else()
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
endif()
target_link_libraries(editor PRIVATE Qt${QT_VERSION_MAJOR}::Widgets ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D)
target_link_libraries(editor PRIVATE Qt${QT_VERSION_MAJOR}::Widgets ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an

View file

@ -11,7 +11,6 @@
#include <reactphysics3d/collision/RaycastInfo.h>
#include <vector>
#include "GLFW/glfw3.h"
#include "physics/util.h"
#include "qcursor.h"
#include "qevent.h"
@ -74,8 +73,9 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
});
if (!rayHit) return;
draggingObject->lock()->position = rpToGlm(rayHit->worldPoint);
draggingObject->lock()->position += rpToGlm(rayHit->worldNormal) * draggingObject->lock()->scale / 2.f;
Data::Vector3 vec = rayHit->worldPoint;
vec = vec + Data::Vector3(rpToGlm(rayHit->worldNormal) * draggingObject->lock()->size / 2.f);
draggingObject->lock()->cframe = draggingObject->lock()->cframe.Rotation() + vec;
syncPartPhysics(draggingObject->lock());
}
@ -156,12 +156,8 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
workspace()->AddChild(lastPart = Part::New({
.position = camera.cameraPos + camera.cameraFront * glm::vec3(3),
.rotation = glm::vec3(0),
.scale = glm::vec3(1, 1, 1),
.material = Material {
.diffuse = glm::vec3(1.0f, 0.5f, 0.31f),
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 32.0f,
}
.size = glm::vec3(1, 1, 1),
.color = glm::vec3(1.0f, 0.5f, 0.31f),
}));
syncPartPhysics(lastPart);
}

View file

@ -14,14 +14,19 @@
#include "common.h"
#include "editorcommon.h"
#include "objects/base/instance.h"
#include "objects/datamodel.h"
#include "physics/simulation.h"
#include "objects/part.h"
#include "qfiledialog.h"
#include "qitemselectionmodel.h"
#include "qobject.h"
#include "qsysinfo.h"
SelectedTool selectedTool;
bool simulationPlaying = false;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
@ -32,14 +37,7 @@ MainWindow::MainWindow(QWidget *parent)
timer.start(33, this);
setMouseTracking(true);
connect(ui->explorerView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) {
if (selected.count() == 0) return;
std::optional<InstanceRef> inst = selected.count() == 0 ? std::nullopt
: std::make_optional(((Instance*)selected.indexes()[0].internalPointer())->shared_from_this());
ui->propertiesView->setSelected(inst);
});
ConnectSelectionChangeHandler();
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = SelectedTool::SELECT; updateSelectedTool(); });
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::MOVE : SelectedTool::SELECT; updateSelectedTool(); });
@ -47,6 +45,39 @@ MainWindow::MainWindow(QWidget *parent)
connect(ui->actionToolRotate, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::ROTATE : SelectedTool::SELECT; updateSelectedTool(); });
ui->actionToolSelect->setChecked(true);
connect(ui->actionToggleSimulation, &QAction::triggered, this, [&]() {
simulationPlaying = !simulationPlaying;
if (simulationPlaying) {
ui->actionToggleSimulation->setText("Pause simulation");
ui->actionToggleSimulation->setToolTip("Pause the simulation");
ui->actionToggleSimulation->setIcon(QIcon::fromTheme("media-playback-pause"));
} else {
ui->actionToggleSimulation->setText("Resume simulation");
ui->actionToggleSimulation->setToolTip("Resume the simulation");
ui->actionToggleSimulation->setIcon(QIcon::fromTheme("media-playback-start"));
}
});
connect(ui->actionSave, &QAction::triggered, this, [&]() {
std::optional<std::string> path;
if (!dataModel->HasFile())
path = QFileDialog::getSaveFileName(this, QString::fromStdString("Save " + dataModel->name), "", "*.obl").toStdString();
if (path == "") return;
dataModel->SaveToFile(path);
});
connect(ui->actionOpen, &QAction::triggered, this, [&]() {
std::string path = QFileDialog::getOpenFileName(this, "Load file", "", "*.obl").toStdString();
if (path == "") return;
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path);
dataModel = newModel;
delete ui->explorerView->selectionModel();
ui->explorerView->reset();
ui->explorerView->setModel(new ExplorerModel(dataModel));
ConnectSelectionChangeHandler();
});
// ui->explorerView->Init(ui);
simulationInit();
@ -55,12 +86,8 @@ MainWindow::MainWindow(QWidget *parent)
workspace()->AddChild(ui->mainWidget->lastPart = Part::New({
.position = glm::vec3(0, -5, 0),
.rotation = glm::vec3(0),
.scale = glm::vec3(512, 1.2, 512),
.material = Material {
.diffuse = glm::vec3(0.388235, 0.372549, 0.384314),
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 32.0f,
},
.size = glm::vec3(512, 1.2, 512),
.color = glm::vec3(0.388235, 0.372549, 0.384314),
.anchored = true,
}));
ui->mainWidget->lastPart->name = "Baseplate";
@ -69,16 +96,23 @@ MainWindow::MainWindow(QWidget *parent)
workspace()->AddChild(ui->mainWidget->lastPart = Part::New({
.position = glm::vec3(0),
.rotation = glm::vec3(0),
.scale = glm::vec3(4, 1.2, 2),
.material = Material {
.diffuse = glm::vec3(0.639216f, 0.635294f, 0.647059f),
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 32.0f,
}
.size = glm::vec3(4, 1.2, 2),
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
}));
syncPartPhysics(ui->mainWidget->lastPart);
}
void MainWindow::ConnectSelectionChangeHandler() {
connect(ui->explorerView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) {
if (selected.count() == 0) return;
std::optional<InstanceRef> inst = selected.count() == 0 ? std::nullopt
: std::make_optional(((Instance*)selected.indexes()[0].internalPointer())->shared_from_this());
ui->propertiesView->setSelected(inst);
});
}
static std::chrono::time_point lastTime = std::chrono::steady_clock::now();
void MainWindow::timerEvent(QTimerEvent* evt) {
if (evt->timerId() != timer.timerId()) {
@ -89,7 +123,8 @@ void MainWindow::timerEvent(QTimerEvent* evt) {
float deltaTime = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - lastTime).count();
lastTime = std::chrono::steady_clock::now();
physicsStep(deltaTime);
if (simulationPlaying)
physicsStep(deltaTime);
ui->mainWidget->update();
ui->mainWidget->updateCycle();
}

View file

@ -28,5 +28,6 @@ private:
void updateSelectedTool();
void timerEvent(QTimerEvent*) override;
void ConnectSelectionChangeHandler();
};
#endif // MAINWINDOW_H

View file

@ -114,6 +114,8 @@
<addaction name="actionToolMove"/>
<addaction name="actionToolScale"/>
<addaction name="actionToolRotate"/>
<addaction name="separator"/>
<addaction name="actionToggleSimulation"/>
</widget>
<action name="actionAddPart">
<property name="icon">
@ -241,6 +243,20 @@
<string>4</string>
</property>
</action>
<action name="actionToggleSimulation">
<property name="icon">
<iconset theme="media-playback-start"/>
</property>
<property name="text">
<string>Start Simulation</string>
</property>
<property name="toolTip">
<string>Start the simulation</string>
</property>
<property name="shortcut">
<string>F5</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View file

@ -1,12 +1,20 @@
#include "propertiesmodel.h"
#include "datatypes/base.h"
#include "datatypes/cframe.h"
#include "objects/base/member.h"
#include "qnamespace.h"
PropertiesModel::PropertiesModel(InstanceRef selectedItem, QWidget *parent)
: QAbstractItemModel(parent)
, selectedItem(selectedItem) {
this->propertiesList = selectedItem->GetProperties();
this->propertiesList.reserve(selectedItem->GetProperties().size());
for (std::string name : selectedItem->GetProperties()) {
PropertyMeta meta = selectedItem->GetPropertyMeta(name).value();
// Don't show CFrames in properties
if (meta.type == &Data::CFrame::TYPE) continue;
this->propertiesList.push_back(name);
}
}
PropertiesModel::~PropertiesModel() = default;
@ -31,7 +39,7 @@ QVariant PropertiesModel::data(const QModelIndex &index, int role) const {
case Qt::CheckStateRole:
if (index.column() == 0) return {};
else if (index.column() == 1 && meta.type == &Data::Bool::TYPE)
return selectedItem->GetPropertyValue(propertyName)->get<Data::Bool>().value ? Qt::Checked : Qt::Unchecked;
return selectedItem->GetPropertyValue(propertyName)->get<Data::Bool>() ? Qt::Checked : Qt::Unchecked;
return {};
// case Qt::DecorationRole:
// return iconOf(item->GetClass());
@ -102,7 +110,7 @@ QModelIndex PropertiesModel::parent(const QModelIndex &index) const {
}
int PropertiesModel::rowCount(const QModelIndex &parent) const {
return !parent.isValid() ? selectedItem->GetProperties().size() : 0;
return !parent.isValid() ? this->propertiesList.size() : 0;
}
int PropertiesModel::columnCount(const QModelIndex &parent) const {

View file

@ -15,7 +15,7 @@ typedef std::function<void(std::vector<InstanceRefWeak> oldSelection, std::vecto
extern Camera camera;
extern std::shared_ptr<DataModel> dataModel;
inline std::shared_ptr<Workspace> workspace() { return dataModel->workspace; }
inline std::shared_ptr<Workspace> workspace() { return std::dynamic_pointer_cast<Workspace>(dataModel->services["Workspace"]); }
extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;

View file

@ -1,10 +1,12 @@
#include "base.h"
#include "meta.h"
#define IMPL_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE, TYPE_NAME) Data::CLASS_NAME::CLASS_NAME(WRAPPED_TYPE in) : value(in) {} \
Data::CLASS_NAME::~CLASS_NAME() = default; \
Data::CLASS_NAME::operator WRAPPED_TYPE() { return value; } \
const Data::TypeInfo Data::CLASS_NAME::TYPE = { .name = TYPE_NAME, }; \
const Data::TypeInfo& Data::CLASS_NAME::GetType() const { return Data::CLASS_NAME::TYPE; };
Data::CLASS_NAME::operator const WRAPPED_TYPE() const { return value; } \
const Data::TypeInfo Data::CLASS_NAME::TYPE = { .name = TYPE_NAME, .deserializer = &Data::CLASS_NAME::Deserialize }; \
const Data::TypeInfo& Data::CLASS_NAME::GetType() const { return Data::CLASS_NAME::TYPE; }; \
void Data::CLASS_NAME::Serialize(pugi::xml_node* node) const { node->text().set(std::string(this->ToString())); }
Data::Base::~Base() {};
@ -12,30 +14,57 @@ Data::Null::Null() {};
Data::Null::~Null() = default;
const Data::TypeInfo Data::Null::TYPE = {
.name = "null",
.deserializer = &Data::Null::Deserialize,
};
const Data::TypeInfo& Data::Null::GetType() const { return Data::Null::TYPE; };
const Data::String Data::Null::ToString() const {
return Data::String("null");
}
void Data::Null::Serialize(pugi::xml_node* node) const {
node->text().set("null");
}
Data::Variant Data::Null::Deserialize(pugi::xml_node* node) {
return Data::Null();
}
//
IMPL_WRAPPER_CLASS(Bool, bool, "bool")
IMPL_WRAPPER_CLASS(Int, int, "int")
IMPL_WRAPPER_CLASS(Float, float, "float")
IMPL_WRAPPER_CLASS(String, std::string, "string")
const Data::String Data::Null::ToString() const {
return Data::String("null");
}
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());
}
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());
}
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());
}
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());
}

View file

@ -1,23 +1,30 @@
#pragma once
#include <string>
#include <functional>
#include <pugixml.hpp>
#define DEF_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE) class CLASS_NAME : public Data::Base { \
public: \
const WRAPPED_TYPE value; \
public: \
CLASS_NAME(WRAPPED_TYPE); \
~CLASS_NAME(); \
operator WRAPPED_TYPE(); \
operator const WRAPPED_TYPE() const; \
virtual const TypeInfo& GetType() const override; \
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); \
};
namespace Data {
class Variant;
typedef std::function<Data::Variant(pugi::xml_node*)> Deserializer;
struct TypeInfo {
std::string name;
Deserializer deserializer;
TypeInfo(const TypeInfo&) = delete;
};
@ -27,6 +34,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;
};
class Null : Base {
@ -37,6 +45,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);
};
DEF_WRAPPER_CLASS(Bool, bool)

131
src/datatypes/cframe.cpp Normal file
View file

@ -0,0 +1,131 @@
#include "cframe.h"
#include "datatypes/vector.h"
#include "physics/util.h"
#include <glm/ext/matrix_transform.hpp>
#include <reactphysics3d/mathematics/Transform.h>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/euler_angles.hpp>
// #include "meta.h" // IWYU pragma: keep
Data::CFrame::CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22)
: translation(x, y, z)
, rotation({
// { R00, R01, R02 },
// { R10, R11, R12 },
// { R20, R21, R22 },
{ R00, R10, R20 },
{ R01, R11, R21 },
{ R02, R12, R22 },
}) {
}
Data::CFrame::CFrame(glm::vec3 translation, glm::mat3 rotation)
: translation(translation)
, rotation(rotation) {
}
Data::CFrame::CFrame(Data::Vector3 position, glm::quat quat)
: translation(position)
, rotation(quat) {
}
Data::CFrame::CFrame(const rp::Transform& transform) : Data::CFrame::CFrame(rpToGlm(transform.getPosition()), rpToGlm(transform.getOrientation())) {
}
glm::mat3 lookAt(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3 up) {
// https://github.com/sgorsten/glm/issues/29#issuecomment-743989030
Data::Vector3 f = (lookAt - position).Unit(); // Forward/Look
Data::Vector3 u = up.Unit(); // Up
Data::Vector3 s = f.Cross(u).Unit(); // Right
u = s.Cross(u);
return {
{ s.X(), u.X(), -f.X() },
{ s.Y(), u.Y(), -f.Y() },
{ s.Z(), u.Z(), -f.Z() },
};
}
Data::CFrame::CFrame(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3 up)
: translation(position)
, rotation(::lookAt(position, lookAt, up)) {
}
Data::CFrame::~CFrame() = default;
const Data::TypeInfo Data::CFrame::TYPE = {
.name = "CoordinateFrame",
.deserializer = &Data::CFrame::Deserialize,
};
const Data::TypeInfo& Data::CFrame::GetType() const { return Data::Vector3::TYPE; };
const Data::String Data::CFrame::ToString() const {
return std::to_string(X()) + ", " + std::to_string(Y()) + ", " + std::to_string(Z());
}
Data::CFrame::operator glm::mat4() const {
// Always make sure to translate the position first, then rotate. Matrices work backwards
return glm::translate(glm::mat4(1.0f), this->translation) * glm::mat4(this->rotation);
}
Data::CFrame::operator rp::Transform() const {
return rp::Transform(glmToRp(translation), glmToRp(rotation));
}
Data::Vector3 Data::CFrame::ToEulerAnglesXYZ() {
float x;
float y;
float z;
glm::extractEulerAngleXYZ(glm::mat4(this->rotation), x, y, z);
return Data::Vector3(x, y, z);
}
Data::CFrame Data::CFrame::FromEulerAnglesXYZ(Data::Vector3 vector) {
glm::mat3 mat = glm::eulerAngleXYZ(vector.X(), vector.Y(), vector.Z());
return Data::CFrame(Data::Vector3::ZERO, glm::column(mat, 2), (Data::Vector3)glm::column(mat, 1)); // Getting LookAt (3rd) and Up (2nd) vectors
}
// Operators
Data::CFrame Data::CFrame::operator +(Data::Vector3 vector) const {
return CFrame { this->translation + glm::vec3(vector), this->rotation };
}
Data::CFrame Data::CFrame::operator -(Data::Vector3 vector) const {
return *this + -vector;
}
// 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]));
}
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()
);
}

59
src/datatypes/cframe.h Normal file
View file

@ -0,0 +1,59 @@
#pragma once
#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/gtc/matrix_access.hpp>
#include <reactphysics3d/mathematics/Transform.h>
#include <reactphysics3d/reactphysics3d.h>
namespace rp = reactphysics3d;
namespace Data {
class CFrame : Base {
glm::vec3 translation;
glm::mat3 rotation;
CFrame(glm::vec3, glm::mat3);
public:
// CFrame(float x, float y, float z);
// CFrame(const glm::vec3&);
// CFrame(const rp::Vector3&);
CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22);
CFrame(const rp::Transform&);
CFrame(Data::Vector3 position, glm::quat quat);
CFrame(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3 up = Data::Vector3(0, 1, 0));
~CFrame();
virtual const TypeInfo& GetType() const override;
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);
operator glm::mat4() const;
operator rp::Transform() const;
//inline static CFrame identity() { }
inline Vector3 Position() const { return translation; }
inline CFrame Rotation() const { return CFrame { glm::vec3(0, 0, 0), rotation }; }
inline float X() const { return translation.x; }
inline float Y() const { return translation.y; }
inline float Z() const { return translation.z; }
inline Vector3 RightVector() { return glm::column(rotation, 0); }
inline Vector3 UpVector() { return glm::column(rotation, 1); }
inline Vector3 LookVector() { return glm::column(rotation, 2); }
Vector3 ToEulerAnglesXYZ();
static CFrame FromEulerAnglesXYZ(Data::Vector3);
// Operators
Data::CFrame operator +(Data::Vector3) const;
Data::CFrame operator -(Data::Vector3) const;
};
}

52
src/datatypes/color3.cpp Normal file
View file

@ -0,0 +1,52 @@
#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)) {};
Data::Color3::Color3(const glm::vec3& vec) : r(std::clamp(vec.x, 0.f, 1.f)), g(std::clamp(vec.y, 0.f, 1.f)), b(std::clamp(vec.z, 0.f, 1.f)) {};
Data::Color3::~Color3() = default;
const Data::TypeInfo Data::Color3::TYPE = {
.name = "Color3",
.deserializer = &Data::Color3::Deserialize,
};
const Data::TypeInfo& Data::Color3::GetType() const { return Data::Color3::TYPE; };
const Data::String Data::Color3::ToString() const {
return std::to_string(r) + ", " + std::to_string(g) + ", " + std::to_string(b);
}
Data::Color3::operator glm::vec3() const { return glm::vec3(r, g, b); };
std::string Data::Color3::ToHex() const {
std::stringstream ss;
ss << "FF" << std::hex << std::uppercase << std::setfill('0')
<< std::setw(2) << int(r*255)
<< std::setw(2) << int(g*255)
<< std::setw(2) << int(b*255);
return ss.str();
}
Data::Color3 Data::Color3::FromHex(std::string hex) {
float r = float(std::stoi(hex.substr(2, 2), nullptr, 16)) / 255;
float g = float(std::stoi(hex.substr(4, 2), nullptr, 16)) / 255;
float b = float(std::stoi(hex.substr(6, 2), nullptr, 16)) / 255;
return Color3(r, g, b);
}
// Serialization
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());
}

37
src/datatypes/color3.h Normal file
View file

@ -0,0 +1,37 @@
#pragma once
#include "base.h"
#include <glm/ext/quaternion_geometric.hpp>
#include <glm/ext/vector_float3.hpp>
#include <reactphysics3d/reactphysics3d.h>
namespace rp = reactphysics3d;
namespace Data {
class Color3 : Base {
float r;
float g;
float b;
public:
Color3(float r, float g, float b);
Color3(const glm::vec3&);
~Color3();
static Color3 FromHex(std::string hex);
virtual const TypeInfo& GetType() const override;
static const TypeInfo TYPE;
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);
operator glm::vec3() const;
inline float R() const { return r; }
inline float G() const { return g; }
inline float B() const { return b; }
};
}

View file

@ -1,4 +1,6 @@
#include "meta.h"
#include "datatypes/base.h"
#include "datatypes/cframe.h"
#include <variant>
Data::String Data::Variant::ToString() const {
@ -6,3 +8,30 @@ Data::String Data::Variant::ToString() const {
return it.ToString();
}, this->wrapped);
}
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) {
fprintf(stderr, "Unknown type for instance: '%s'\n", node->name());
abort();
}
const Data::TypeInfo* type = Data::TYPE_MAP[node->name()];
return type->deserializer(node);
}
std::map<std::string, const Data::TypeInfo*> Data::TYPE_MAP = {
{ "null", &Data::Null::TYPE },
{ "bool", &Data::Bool::TYPE },
{ "int", &Data::Int::TYPE },
{ "float", &Data::Float::TYPE },
{ "string", &Data::String::TYPE },
{ "Vector3", &Data::Vector3::TYPE },
{ "CoordinateFrame", &Data::CFrame::TYPE },
{ "Color3", &Data::Color3::TYPE },
};

View file

@ -1,7 +1,11 @@
#pragma once
#include "base.h"
#include <variant>
#include <map>
#include "base.h"
#include "datatypes/color3.h"
#include "vector.h"
#include "cframe.h"
// #define __VARIANT_TYPE std::variant< \
// Null, \
@ -17,7 +21,10 @@ namespace Data {
Bool,
Int,
Float,
String
String,
Vector3,
CFrame,
Color3
> __VARIANT_TYPE;
class Variant {
@ -26,5 +33,11 @@ namespace Data {
template <typename T> Variant(T obj) : wrapped(obj) {}
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);
};
// Map of all data types to their type names
extern std::map<std::string, const TypeInfo*> TYPE_MAP;
}

58
src/datatypes/vector.cpp Normal file
View file

@ -0,0 +1,58 @@
#include "vector.h"
#include <glm/ext/quaternion_geometric.hpp>
#include "meta.h" // IWYU pragma: keep
Data::Vector3::Vector3(const glm::vec3& src) : vector(src) {};
Data::Vector3::Vector3(const rp::Vector3& src) : vector(glm::vec3(src.x, src.y, src.z)) {};
Data::Vector3::Vector3(float x, const float y, float z) : vector(glm::vec3(x, y, z)) {};
Data::Vector3::~Vector3() = default;
const Data::TypeInfo Data::Vector3::TYPE = {
.name = "Vector3",
.deserializer = &Data::Vector3::Deserialize,
};
const Data::TypeInfo& Data::Vector3::GetType() const { return Data::Vector3::TYPE; };
Data::Vector3 Data::Vector3::ZERO(0, 0, 0);
Data::Vector3 Data::Vector3::ONE(1, 1, 1);
const Data::String Data::Vector3::ToString() const {
return std::to_string(X()) + ", " + std::to_string(Y()) + ", " + std::to_string(Z());
}
Data::Vector3::operator glm::vec3() const { return vector; };
Data::Vector3::operator rp::Vector3() const { return rp::Vector3(X(), Y(), Z()); };
// Operators
Data::Vector3 Data::Vector3::operator +(Data::Vector3 other) const {
return Data::Vector3(this->X() + other.X(), this->Y() + other.Y(), this->Z() + other.Z());
}
Data::Vector3 Data::Vector3::operator -(Data::Vector3 other) const {
return Data::Vector3(this->X() - other.X(), this->Y() - other.Y(), this->Z() - other.Z());
}
Data::Vector3 Data::Vector3::operator -() const {
return Data::Vector3(-this->X(), -this->Y(), -this->Z());
}
Data::Vector3 Data::Vector3::Cross(Data::Vector3 other) const {
return glm::cross(this->vector, other.vector);
}
float Data::Vector3::Dot(Data::Vector3 other) const {
return glm::dot(this->vector, other.vector);
}
// 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()));
}
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());
}

47
src/datatypes/vector.h Normal file
View file

@ -0,0 +1,47 @@
#pragma once
#include "base.h"
#include <glm/ext/quaternion_geometric.hpp>
#include <glm/ext/vector_float3.hpp>
#include <reactphysics3d/reactphysics3d.h>
namespace rp = reactphysics3d;
namespace Data {
class Vector3 : Base {
glm::vec3 vector;
public:
Vector3(float x, float y, float z);
Vector3(const glm::vec3&);
Vector3(const rp::Vector3&);
~Vector3();
virtual const TypeInfo& GetType() const override;
static const TypeInfo TYPE;
static Data::Vector3 ZERO;
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);
operator glm::vec3() const;
operator rp::Vector3() const;
inline float X() const { return vector.x; }
inline float Y() const { return vector.y; }
inline float Z() const { return vector.z; }
inline float Magnitude() const { return glm::length(vector); }
inline Data::Vector3 Unit() const { return glm::normalize(vector); }
Data::Vector3 Cross(Data::Vector3) const;
float Dot(Data::Vector3) const;
// Operators
Data::Vector3 operator +(Data::Vector3) const;
Data::Vector3 operator -(Data::Vector3) const;
Data::Vector3 operator -() const;
};
}

View file

@ -3,6 +3,7 @@
#include "../../datatypes/meta.h"
#include "datatypes/base.h"
#include "objects/base/member.h"
#include "objects/meta.h"
#include <algorithm>
#include <cstddef>
#include <cstdio>
@ -141,3 +142,61 @@ std::vector<std::string> Instance::GetProperties() {
cachedMemberList = memberList;
return memberList;
}
// Serialization
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
pugi::xml_node propertiesNode = node.append_child("Properties");
for (std::string name : GetProperties()) {
PropertyMeta meta = GetPropertyMeta(name).value();
if (meta.flags & PropertyFlags::PROP_NOSAVE) continue; // This property should not be serialized. Skip...
pugi::xml_node propertyNode = propertiesNode.append_child(meta.type->name);
propertyNode.append_attribute("name").set_value(name);
GetPropertyValue(name)->Serialize(&propertyNode);
}
// Add children
for (InstanceRef child : this->children) {
child->Serialize(&node);
}
}
InstanceRef Instance::Deserialize(pugi::xml_node* node) {
std::string className = node->attribute("class").value();
if (INSTANCE_MAP.count(className) == 0) {
fprintf(stderr, "Unknown type for instance: '%s'\n", className.c_str());
abort();
}
// 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());
InstanceRef object = INSTANCE_MAP[className]->constructor();
object->GetChildren();
// const InstanceType* type = INSTANCE_MAP.at(className);
// Read 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);
if (!meta_.has_value()) {
fprintf(stderr, "Attempt to set unknown property '%s' of %s\n", propertyName.c_str(), object->GetClass()->className.c_str());
continue;
}
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);
}
return object;
}

View file

@ -11,6 +11,7 @@
#include <map>
#include <vector>
#include <../include/expected.hpp>
#include <pugixml.hpp>
#include "member.h"
@ -68,6 +69,10 @@ public:
tl::expected<PropertyMeta, MemberNotFound> GetPropertyMeta(std::string name);
// Returning a list of property names feels kinda janky. Is this really the way to go?
std::vector<std::string> GetProperties();
// Serialization
void Serialize(pugi::xml_node* parent);
static std::shared_ptr<Instance> Deserialize(pugi::xml_node* node);
};
typedef std::shared_ptr<Instance> InstanceRef;

View file

@ -15,21 +15,27 @@ struct FieldCodec {
Data::Variant (*read)(void* source);
};
template <typename T, typename U>
void _writeCodec(Data::Variant source, void* destination) {
*(U*)destination = source.get<T>().value;
}
template <typename T, typename U>
Data::Variant _readCodec(void* source) {
return T(*(U*)source);
}
template <typename T, typename U>
constexpr FieldCodec fieldCodecOf() {
return FieldCodec {
.write = &_writeCodec<T, U>,
.read = &_readCodec<T, U>,
.write = [](Data::Variant source, void* destination) {
*(U*)destination = (U)source.get<T>();
},
.read = [](void* source) -> Data::Variant {
return T(*(U*)source);
},
};
}
template <typename T>
constexpr FieldCodec fieldCodecOf() {
return FieldCodec {
.write = [](Data::Variant source, void* destination) {
*(T*)destination = source.get<T>();
},
.read = [](void* source) -> Data::Variant {
return *(T*)source;
},
};
}
@ -38,11 +44,17 @@ std::function<void(std::string name)> memberFunctionOf(void(T::*func)(std::strin
return std::bind(func, obj, std::placeholders::_1);
}
enum PropertyFlags {
PROP_HIDDEN = 1 << 0, // Hidden from the editor
PROP_NOSAVE = 1 << 1, // Do not serialize
};
struct PropertyMeta {
void* backingField;
const Data::TypeInfo* type;
FieldCodec codec;
std::optional<std::function<void(std::string name)>> updateCallback;
PropertyFlags flags;
};
typedef std::variant<PropertyMeta> MemberMeta;

View file

@ -1,6 +1,18 @@
#include "datamodel.h"
#include "base/service.h"
#include "objects/base/instance.h"
#include "objects/base/service.h"
#include "workspace.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,
@ -14,9 +26,95 @@ const InstanceType* DataModel::GetClass() {
DataModel::DataModel()
: Instance(&TYPE) {
this->name = "Place";
}
void DataModel::Init() {
this->workspace = std::make_shared<Workspace>(shared<DataModel>());
this->workspace->InitService();
// Create the workspace if it doesn't exist
if (this->services.count("Workspace") == 0)
this->services["Workspace"] = std::make_shared<Workspace>(shared<DataModel>());
// Init all services
for (auto [_, service] : this->services) {
service->InitService();
}
}
void DataModel::SaveToFile(std::optional<std::string> path) {
if (!path.has_value() && !this->currentFile.has_value()) {
fprintf(stderr, "Cannot save DataModel because no path was provided.\n");
abort();
}
std::string target = path.has_value() ? path.value() : this->currentFile.value();
std::ofstream outStream(target);
pugi::xml_document doc;
pugi::xml_node root = doc.append_child("openblocks");
for (InstanceRef child : this->GetChildren()) {
child->Serialize(&root);
}
doc.save(outStream);
currentFile = target;
name = target;
printf("Place saved succesfully\n");
}
void DataModel::DeserializeService(pugi::xml_node* node) {
std::string className = node->attribute("class").value();
if (SERVICE_CONSTRUCTORS.count(className) == 0) {
fprintf(stderr, "Unknown service: '%s'\n", className.c_str());
abort();
}
if (services.count(className) != 0) {
fprintf(stderr, "Service %s defined multiple times in file\n", className.c_str());
return;
}
// 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>());
// Read 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);
if (!meta_.has_value()) {
fprintf(stderr, "Attempt to set unknown property '%s' of %s\n", propertyName.c_str(), object->GetClass()->className.c_str());
continue;
}
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);
}
// We add the service to the list
// All services get init'd at once in InitServices
this->services[className] = std::dynamic_pointer_cast<Service>(object);
}
std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
std::ifstream inStream(path);
pugi::xml_document doc;
doc.load(inStream);
pugi::xml_node rootNode = doc.child("openblocks");
std::shared_ptr<DataModel> newModel = std::make_shared<DataModel>();
for (pugi::xml_node childNode : rootNode.children("Item")) {
newModel->DeserializeService(&childNode);
}
newModel->Init();
return newModel;
}

View file

@ -5,17 +5,44 @@
class Workspace;
class DataModel;
class Service;
extern std::map<std::string, std::function<std::shared_ptr<Service>(std::weak_ptr<DataModel>)>> SERVICE_CONSTRUCTORS;
// The root instance to all objects in the hierarchy
class DataModel : public Instance {
//private:
private:
void DeserializeService(pugi::xml_node* node);
public:
const static InstanceType TYPE;
std::shared_ptr<Workspace> workspace;
std::map<std::string, std::shared_ptr<Service>> services;
std::optional<std::string> currentFile;
DataModel();
void Init();
static inline std::shared_ptr<DataModel> New() { return std::make_shared<DataModel>(); };
virtual const InstanceType* GetClass() override;
template <typename T>
std::shared_ptr<T> GetService(std::string name) {
if (services.count(name) != 0)
return services[name];
// TODO: Validate name
services[name] = SERVICE_CONSTRUCTORS[name](shared<DataModel>());
}
template <typename T>
std::optional<std::shared_ptr<T>> FindService() {
if (services.count(name) != 0)
return services[name];
return std::nullopt;
}
// Saving/loading
inline bool HasFile() { return this->currentFile.has_value(); }
void SaveToFile(std::optional<std::string> path = std::nullopt);
static std::shared_ptr<DataModel> LoadFromFile(std::string path);
};

10
src/objects/meta.cpp Normal file
View file

@ -0,0 +1,10 @@
#include "meta.h"
#include "objects/part.h"
#include "objects/workspace.h"
std::map<std::string, const InstanceType*> INSTANCE_MAP = {
{ "Instance", &Instance::TYPE },
{ "Part", &Part::TYPE },
{ "Workspace", &Workspace::TYPE },
{ "DataModel", &DataModel::TYPE },
};

8
src/objects/meta.h Normal file
View file

@ -0,0 +1,8 @@
#pragma once
#include <string>
#include <map>
#include "objects/base/instance.h"
// Map of all instance types to their class names
extern std::map<std::string, const InstanceType*> INSTANCE_MAP;

View file

@ -1,11 +1,50 @@
#include "part.h"
#include "base/instance.h"
#include "datatypes/base.h"
#include "datatypes/cframe.h"
#include "datatypes/color3.h"
#include "datatypes/vector.h"
#include "objects/base/member.h"
#include <memory>
#include <optional>
#include "physics/simulation.h"
// template <typename T, typename U>
// constexpr FieldCodec fieldCodecOf() {
// return FieldCodec {
// .write = [](Data::Variant source, void* destination) {
// *(U*)destination = (U)source.get<T>();
// },
// .read = [](void* source) -> Data::Variant {
// return T(*(U*)source);
// },
// };
// }
constexpr FieldCodec cframePositionCodec() {
return FieldCodec {
.write = [](Data::Variant source, void* destination) {
Data::CFrame* cframe = static_cast<Data::CFrame*>(destination);
*cframe = cframe->Rotation() + source.get<Data::Vector3>();
},
.read = [](void* source) -> Data::Variant {
return *static_cast<Data::CFrame*>(source);
},
};
}
constexpr FieldCodec cframeRotationCodec() {
return FieldCodec {
.write = [](Data::Variant source, void* destination) {
Data::CFrame* cframe = static_cast<Data::CFrame*>(destination);
*cframe = Data::CFrame::FromEulerAnglesXYZ(source.get<Data::Vector3>()) + cframe->Position();
},
.read = [](void* source) -> Data::Variant {
return static_cast<Data::CFrame*>(source)->ToEulerAnglesXYZ();
},
};
}
const InstanceType Part::TYPE = {
.super = &Instance::TYPE,
.className = "Part",
@ -17,15 +56,46 @@ const InstanceType* Part::GetClass() {
return &TYPE;
}
Part::Part(): Part(PartConstructParams {}) {
Part::Part(): Part(PartConstructParams { .color = Data::Color3(0.639216f, 0.635294f, 0.647059f) }) {
}
Part::Part(PartConstructParams params): Instance(&TYPE), position(params.position), rotation(params.rotation),
scale(params.scale), material(params.material), anchored(params.anchored) {
Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(params.position, params.rotation)),
size(params.size), color(params.color), anchored(params.anchored) {
this->memberMap = std::make_unique<MemberMap>(MemberMap {
.super = std::move(this->memberMap),
.members = {
{ "Anchored", { .backingField = &anchored, .type = &Data::Bool::TYPE, .codec = fieldCodecOf<Data::Bool, bool>(), .updateCallback = memberFunctionOf(&Part::onUpdated, this) } }
{ "Anchored", {
.backingField = &anchored,
.type = &Data::Bool::TYPE,
.codec = fieldCodecOf<Data::Bool, bool>(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this)
} }, { "Position", {
.backingField = &cframe,
.type = &Data::Vector3::TYPE,
.codec = cframePositionCodec(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
.flags = PropertyFlags::PROP_NOSAVE
} }, { "Rotation", {
.backingField = &cframe,
.type = &Data::Vector3::TYPE,
.codec = cframeRotationCodec(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
.flags = PropertyFlags::PROP_NOSAVE
} }, { "CFrame", {
.backingField = &cframe,
.type = &Data::CFrame::TYPE,
.codec = fieldCodecOf<Data::CFrame>(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
} }, { "Size", {
.backingField = &size,
.type = &Data::Vector3::TYPE,
.codec = fieldCodecOf<Data::Vector3, glm::vec3>(),
.updateCallback = memberFunctionOf(&Part::onUpdated, this)
} }, { "Color", {
.backingField = &color,
.type = &Data::Color3::TYPE,
.codec = fieldCodecOf<Data::Color3>(),
} }
}
});
}

View file

@ -5,6 +5,9 @@
#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 <reactphysics3d/reactphysics3d.h>
namespace rp = reactphysics3d;
@ -13,8 +16,8 @@ namespace rp = reactphysics3d;
struct PartConstructParams {
glm::vec3 position;
glm::quat rotation = glm::identity<glm::quat>();
glm::vec3 scale;
Material material;
glm::vec3 size;
Data::Color3 color;
bool anchored = false;
};
@ -26,11 +29,9 @@ protected:
public:
const static InstanceType TYPE;
// TODO: Switch these over to our dedicated datatypes
glm::vec3 position;
glm::quat rotation = glm::identity<glm::quat>();
glm::vec3 scale;
Material material;
Data::CFrame cframe;
glm::vec3 size;
Data::Color3 color;
bool selected = false;
bool anchored = false;
@ -44,4 +45,6 @@ public:
static inline std::shared_ptr<Part> New(PartConstructParams params) { return std::make_shared<Part>(params); };
static inline InstanceRef CreateGeneric() { return std::make_shared<Part>(); };
virtual const InstanceType* GetClass() override;
inline Data::Vector3 position() { return cframe.Position(); }
};

View file

@ -12,6 +12,6 @@ public:
Workspace(std::weak_ptr<DataModel> dataModel);
// static inline std::shared_ptr<Workspace> New() { return std::make_shared<Workspace>(); };
// static inline InstanceRef Create() { return std::make_shared<Workspace>(); };
static inline std::shared_ptr<Service> Create(std::weak_ptr<DataModel> parent) { return std::make_shared<Workspace>(parent); };
virtual const InstanceType* GetClass() override;
};

View file

@ -17,6 +17,7 @@
#include <reactphysics3d/reactphysics3d.h>
#include "../common.h"
#include "../objects/part.h"
#include "datatypes/cframe.h"
#include "util.h"
#include "simulation.h"
@ -45,14 +46,14 @@ void simulationInit() {
void syncPartPhysics(std::shared_ptr<Part> part) {
glm::mat4 rotMat = glm::mat4(1.0f);
rp::Transform transform(glmToRp(part->position), glmToRp(part->rotation));
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->scale * glm::vec3(0.5f)));
rp::BoxShape* shape = physicsCommon->createBoxShape(glmToRp(part->size * glm::vec3(0.5f)));
if (part->rigidBody->getNbColliders() > 0) {
part->rigidBody->removeCollider(part->rigidBody->getCollider(0));
@ -76,9 +77,7 @@ void physicsStep(float deltaTime) {
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->position = rpToGlm(transform.getPosition());
// part.rotation = glm::eulerAngles(rpToGlm(transform.getOrientation()));
part->rotation = rpToGlm(transform.getOrientation());
part->cframe = Data::CFrame(transform);
}
}

View file

@ -2,6 +2,7 @@
#include <glm/ext/vector_float3.hpp>
#include <memory>
#include <reactphysics3d/body/Body.h>
#include <reactphysics3d/mathematics/Matrix3x3.h>
#include <reactphysics3d/mathematics/Quaternion.h>
#include <reactphysics3d/mathematics/Vector3.h>
#include <reactphysics3d/mathematics/mathematics.h>
@ -18,6 +19,10 @@ inline rp::Quaternion glmToRp(glm::quat quat) {
return rp::Quaternion(quat.w, rp::Vector3(quat.x, quat.y, quat.z));
}
// inline rp::Matrix3x3 glmToRp(glm::mat3 mat) {
// return (rp::Quaternion)glmToRp((glm::quat)mat);
// }
inline glm::vec3 rpToGlm(rp::Vector3 vec) {
return glm::vec3(vec.x, vec.y, vec.z);
}

View file

@ -10,6 +10,7 @@
#include <memory>
#include <vector>
#include "physics/util.h"
#include "shader.h"
#include "mesh.h"
#include "defaultmeshes.h"
@ -109,15 +110,32 @@ void renderParts() {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, part->position);
model = model * glm::mat4_cast(part->rotation);
model = glm::scale(model, part->scale);
// if (inst->name == "Target") printf("(%f,%f,%f):(%f,%f,%f;%f,%f,%f;%f,%f,%f)\n",
// part->cframe.X(),
// part->cframe.Y(),
// part->cframe.Z(),
// part->cframe.RightVector().X(),
// part->cframe.UpVector().X(),
// part->cframe.LookVector().X(),
// part->cframe.RightVector().Y(),
// part->cframe.UpVector().Y(),
// part->cframe.LookVector().Y(),
// part->cframe.RightVector().Z(),
// part->cframe.UpVector().Z(),
// part->cframe.LookVector().Z()
// );
glm::mat4 model = part->cframe;
model = glm::scale(model, part->size);
shader->set("model", model);
shader->set("material", part->material);
shader->set("material", Material {
.diffuse = part->color,
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 16.0f,
});
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
shader->set("normalMatrix", normalMatrix);
shader->set("texScale", part->scale);
shader->set("texScale", part->size);
CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, 36);