Compare commits

..

No commits in common. "master" and "feature/instance" have entirely different histories.

107 changed files with 1399 additions and 7346 deletions

11
.clangd Normal file
View file

@ -0,0 +1,11 @@
CompileFlags: # Tweak the parse settings, example directory given to show format
Add:
- "--include-directory=../../src"
- "--include-directory=../src"
- "--include-directory=./editor_autogen/include"
- "--include-directory=../.."
- "--include-directory=/usr/include/qt6/QtWidgets"
- "--include-directory=/usr/include/qt6/QtOpenGLWidgets"
- "--include-directory=/usr/include/qt6"
- "--include-directory=/usr/include/qt6/QtGui"
- "--include-directory=/usr/include/qt6/QtCore"

22
.gitignore vendored
View file

@ -1,14 +1,12 @@
/bin/
/lib/
/build/
bin/
lib/
CMakeFiles/
cmake_install.cmake
CMakeCache.txt
Makefile
# Qt
/*.pro.user*
/CMakeLists.txt.user*
# Clangd
/compile_commands.json
/.cache
# Gdb
/.gdb_history
.lupdate/
*.pro.user*
CMakeLists.txt.user*
*_autogen/

16
.vscode/launch.json vendored
View file

@ -1,16 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/bin/editor",
"args": [],
"cwd": "${workspaceFolder}",
}
]
}

View file

@ -1,14 +1,43 @@
cmake_minimum_required(VERSION 3.31..)
cmake_minimum_required(VERSION 3.5.0)
set(CMAKE_CXX_STANDARD 17)
project(openblocks VERSION 0.1.0)
set(OpenGL_GL_PREFERENCE "GLVND")
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib )
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(OpenGL REQUIRED COMPONENTS OpenGL)
add_subdirectory(core)
add_subdirectory(client)
add_subdirectory(editor)
find_package(SDL2 REQUIRED)
include_directories(${SDL2_INCLUDE_DIRS})
find_package(GLEW REQUIRED)
include_directories(${GLEW_INCLUDE_DIRS})
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)
file(MAKE_DIRECTORY bin)
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)
# add_executable(client "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)
add_subdirectory("client")
add_subdirectory("editor")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 865 B

View file

@ -1,109 +0,0 @@
#version 330 core
// Implements the Phong lighting model with respect to materials and lighting materials
// Structs
struct Material {
vec3 diffuse;
vec3 specular;
float shininess;
};
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
struct PointLight {
vec3 position;
// vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
// I/O
in vec3 vPos;
in vec3 vNormal;
in vec2 vTexCoords;
out vec4 FragColor;
#define NR_POINT_LIGHTS 4
uniform vec3 viewPos;
uniform PointLight pointLights[NR_POINT_LIGHTS];
uniform int numPointLights;
uniform DirLight sunLight;
uniform Material material;
// Functions
vec3 calculateDirectionalLight(DirLight light);
vec3 calculatePointLight(PointLight light);
// Main
void main() {
vec3 result = vec3(0.0);
result += calculateDirectionalLight(sunLight);
for (int i = 0; i < numPointLights; i++) {
result += calculatePointLight(pointLights[i]);
}
FragColor = vec4(result, 1);
}
vec3 calculateDirectionalLight(DirLight light) {
// Calculate diffuse
vec3 norm = normalize(vNormal);
vec3 lightDir = normalize(-light.direction);
float diff = max(dot(norm, lightDir), 0.0);
// Calculate specular
vec3 viewDir = normalize(viewPos - vPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 ambient = light.ambient * material.diffuse;
vec3 diffuse = light.diffuse * diff * material.diffuse;
vec3 specular = light.specular * spec * material.specular;
return (ambient + diffuse + specular);
}
vec3 calculatePointLight(PointLight light) {
// Calculate ambient light
// Calculate diffuse light
vec3 norm = normalize(vNormal);
vec3 lightDir = normalize(light.position - vPos);
float diff = max(dot(norm, lightDir), 0.0);
// Calculate specular
vec3 viewDir = normalize(viewPos - vPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
// Calculate attenuation
float distance = length(light.position - vPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
vec3 ambient = light.ambient * material.diffuse;
vec3 diffuse = light.diffuse * diff * material.diffuse;
vec3 specular = light.specular * spec * material.specular;
return (ambient + diffuse + specular) * attenuation;
}

View file

@ -1,20 +0,0 @@
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
out vec3 vPos;
out vec3 vNormal;
out vec2 vTexCoords;
uniform mat4 model;
uniform mat3 normalMatrix;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
vPos = vec3(model * vec4(aPos, 1.0));
vNormal = normalMatrix * aNormal;
}

View file

@ -1,13 +0,0 @@
#version 330 core
// I/O
out vec4 FragColor;
uniform vec3 aColor;
// Main
void main() {
FragColor = vec4(aColor, 1);
}

View file

@ -1,8 +0,0 @@
#version 330 core
layout (location = 0) in vec3 aPos;
out vec3 vPos;
void main()
{
gl_Position = vec4(aPos, 1.0);
}

View file

@ -53,7 +53,6 @@ uniform int numPointLights;
uniform DirLight sunLight;
uniform Material material;
uniform sampler2DArray studs;
uniform float transparency;
// Functions
@ -73,7 +72,7 @@ void main() {
}
vec4 studPx = texture(studs, vec3(vTexCoords, vSurfaceZ));
FragColor = vec4(mix(result, vec3(studPx), studPx.w), 1) * (1-transparency);
FragColor = vec4(mix(result, vec3(studPx), studPx.w), 1);
}
vec3 calculateDirectionalLight(DirLight light) {

View file

@ -1,7 +1,4 @@
find_package(SDL2 REQUIRED)
include_directories(${SDL2_INCLUDE_DIRS})
find_package(glfw3 REQUIRED)
add_executable(client "src/main.cpp")
target_link_libraries(client PRIVATE ${SDL2_LIBRARIES} openblocks glfw)
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)

View file

@ -24,7 +24,6 @@ void processInput(GLFWwindow* window);
void mouseCallback(GLFWwindow* window, double xpos, double ypos);
// void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods);
void resizeCallback(GLFWwindow* window, int width, int height);
std::shared_ptr<Part> lastPart;
@ -36,32 +35,38 @@ int main() {
glfwSetKeyCallback(window, keyCallback);
glfwSetMouseButtonCallback(window, mouseButtonCallback);
glfwSetCursorPosCallback(window, mouseCallback);
glfwSetFramebufferSizeCallback(window, resizeCallback);
glfwMakeContextCurrent(window);
glewInit();
dataModel->Init();
simulationInit();
renderInit(window, 1200, 900);
renderInit(window);
// Baseplate
workspace()->AddChild(Part::New({
workspace->AddChild(Part::New({
.position = glm::vec3(0, -5, 0),
.rotation = glm::vec3(0),
.size = glm::vec3(512, 1.2, 512),
.color = glm::vec3(0.388235, 0.372549, 0.384314),
.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,
},
.anchored = true,
}));
workspace()->AddChild(lastPart = Part::New({
workspace->AddChild(lastPart = Part::New({
.position = glm::vec3(0),
.rotation = glm::vec3(0),
.size = glm::vec3(4, 1.2, 2),
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
.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,
}
}));
for (InstanceRef inst : workspace()->GetChildren()) {
for (InstanceRef inst : workspace->GetChildren()) {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
syncPartPhysics(part);
@ -110,15 +115,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);
}
}
@ -154,11 +159,15 @@ void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
if (key == GLFW_KEY_F && action == GLFW_PRESS) {
workspace()->AddChild(lastPart = Part::New({
workspace->AddChild(lastPart = Part::New({
.position = camera.cameraPos + camera.cameraFront * glm::vec3(3),
.rotation = glm::vec3(0),
.size = glm::vec3(1, 1, 1),
.color = glm::vec3(1.0f, 0.5f, 0.31f),
.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,
}
}));
syncPartPhysics(lastPart);
}
@ -166,28 +175,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->size.x += shiftFactor;
lastPart->scale.x += shiftFactor;
syncPartPhysics(lastPart);
}
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
lastPart->size.y += shiftFactor;
lastPart->scale.y += shiftFactor;
syncPartPhysics(lastPart);
}
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
lastPart->size.z += shiftFactor;
lastPart->scale.z += shiftFactor;
syncPartPhysics(lastPart);
}
}
@ -196,8 +205,3 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods
if (key == GLFW_KEY_E && action == GLFW_PRESS) mode = 1; // Enlarge
if (key == GLFW_KEY_R && action == GLFW_PRESS) mode = 2;
}
void resizeCallback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
setViewport(width, height);
}

View file

@ -1,15 +0,0 @@
find_package(OpenGL REQUIRED COMPONENTS OpenGL)
find_package(GLEW REQUIRED)
include_directories(${GLEW_INCLUDE_DIRS})
find_package(OpenGL)
find_package(glm CONFIG REQUIRED)
find_package(ReactPhysics3D REQUIRED)
find_package(pugixml REQUIRED)
file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h")
add_library(openblocks ${SOURCES})
set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks")
target_link_libraries(openblocks ${GLEW_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
target_include_directories(openblocks PUBLIC "src" "../include")

View file

@ -1,31 +0,0 @@
// TEMPORARY COMMON DATA FOR DIFFERENT INTERNAL COMPONENTS
#include "common.h"
#include <memory>
Camera camera(glm::vec3(0.0, 0.0, 3.0));
//std::vector<Part> parts;
std::shared_ptr<DataModel> dataModel = DataModel::New();
std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
std::shared_ptr<Handles> editorToolHandles = Handles::New();
std::vector<InstanceRefWeak> currentSelection;
std::vector<SelectionUpdateHandler> selectionUpdateHandlers;
void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer) {
for (SelectionUpdateHandler handler : selectionUpdateHandlers) {
handler(currentSelection, newSelection, fromExplorer);
}
currentSelection = newSelection;
}
const std::vector<InstanceRefWeak> getSelection() {
return currentSelection;
}
void addSelectionListener(SelectionUpdateHandler handler) {
selectionUpdateHandlers.push_back(handler);
}

View file

@ -1,89 +0,0 @@
#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 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())); }
Data::Base::~Base() {};
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::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::FromString(std::string string) {
return Data::Bool(string[0] == 't' || string[0] == 'T' || string[0] == '1' || string[0] == 'y' || string[0] == 'Y');
}
const Data::String Data::Int::ToString() const {
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::FromString(std::string string) {
return Data::Int(std::stoi(string));
}
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::FromString(std::string string) {
return Data::Float(std::stof(string));
}
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::FromString(std::string string) {
return Data::String(string);
}

View file

@ -1,62 +0,0 @@
#pragma once
#include <string>
#include <functional>
#include <pugixml.hpp>
#define DEF_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE) class CLASS_NAME : public Data::Base { \
const WRAPPED_TYPE value; \
public: \
CLASS_NAME(WRAPPED_TYPE); \
~CLASS_NAME(); \
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); \
static Data::Variant FromString(std::string); \
};
namespace Data {
class Variant;
typedef std::function<Data::Variant(pugi::xml_node*)> Deserializer;
typedef std::function<Data::Variant(std::string)> FromString;
struct TypeInfo {
std::string name;
Deserializer deserializer;
FromString fromString;
TypeInfo(const TypeInfo&) = delete;
};
class String;
class Base {
public:
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 {
public:
Null();
~Null();
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);
};
DEF_WRAPPER_CLASS(Bool, bool)
DEF_WRAPPER_CLASS(Int, int)
DEF_WRAPPER_CLASS(Float, float)
DEF_WRAPPER_CLASS(String, std::string)
};
#undef DEF_WRAPPER_CLASS

View file

@ -1,145 +0,0 @@
#include "cframe.h"
#include "datatypes/vector.h"
#include "physics/util.h"
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/matrix.hpp>
#include <reactphysics3d/mathematics/Transform.h>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/euler_angles.hpp>
// #include "meta.h" // IWYU pragma: keep
const Data::CFrame Data::CFrame::IDENTITY(glm::vec3(0, 0, 0), glm::mat3(1.f));
const Data::CFrame Data::CFrame::YToZ(glm::vec3(0, 0, 0), glm::mat3(glm::vec3(1, 0, 0), glm::vec3(0, 0, 1), glm::vec3(0, 1, 0)));
Data::CFrame::CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22)
: 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/linalg/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(f);
return { s, u, f };
}
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
}
Data::CFrame Data::CFrame::Inverse() const {
return CFrame { -translation * glm::transpose(glm::inverse(rotation)), glm::inverse(rotation) };
}
// Operators
Data::CFrame Data::CFrame::operator *(Data::CFrame otherFrame) const {
return CFrame { this->translation + this->rotation * otherFrame.translation, this->rotation * otherFrame.rotation };
}
Data::Vector3 Data::CFrame::operator *(Data::Vector3 vector) const {
return this->translation + this->rotation * vector;
}
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()
);
}

View file

@ -1,67 +0,0 @@
#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 <glm/gtc/matrix_inverse.hpp>
#include <glm/matrix.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();
static const CFrame IDENTITY;
static const CFrame YToZ;
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 }; }
CFrame Inverse() const;
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::CFrame) const;
Data::Vector3 operator *(Data::Vector3) const;
Data::CFrame operator +(Data::Vector3) const;
Data::CFrame operator -(Data::Vector3) const;
};
}

View file

@ -1,52 +0,0 @@
#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());
}

View file

@ -1,37 +0,0 @@
#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,37 +0,0 @@
#include "meta.h"
#include "datatypes/base.h"
#include "datatypes/cframe.h"
#include <variant>
Data::String Data::Variant::ToString() const {
return std::visit([](auto&& it) {
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,43 +0,0 @@
#pragma once
#include <variant>
#include <map>
#include "base.h"
#include "datatypes/color3.h"
#include "vector.h"
#include "cframe.h"
// #define __VARIANT_TYPE std::variant< \
// Null, \
// Bool, \
// Int, \
// Float, \
// String \
// >
namespace Data {
typedef std::variant<
Null,
Bool,
Int,
Float,
String,
Vector3,
CFrame,
Color3
> __VARIANT_TYPE;
class Variant {
__VARIANT_TYPE wrapped;
public:
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;
}

View file

@ -1,66 +0,0 @@
#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 *(float scale) const {
return Data::Vector3(this->X() * scale, this->Y() * scale, this->Z() * scale);
}
Data::Vector3 Data::Vector3::operator /(float scale) const {
return Data::Vector3(this->X() / scale, this->Y() / scale, this->Z() / scale);
}
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());
}

View file

@ -1,49 +0,0 @@
#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 *(float) const;
Data::Vector3 operator /(float) const;
Data::Vector3 operator +(Data::Vector3) const;
Data::Vector3 operator -(Data::Vector3) const;
Data::Vector3 operator -() const;
};
}

View file

@ -1,103 +0,0 @@
#include "math_helper.h"
#define CMP_EPSILON 0.00001
// After a long time researching, I was able to use and adapt Godot's implementation of movable handles (godot/editor/plugins/gizmos/gizmo_3d_helper.cpp)
// All thanks goes to them and David Eberly for his algorithm.
void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3 &p_p1, const glm::vec3 &p_q0, const glm::vec3 &p_q1, glm::vec3 &r_ps, glm::vec3 &r_qt) {
// Based on David Eberly's Computation of Distance Between Line Segments algorithm.
glm::vec3 p = p_p1 - p_p0;
glm::vec3 q = p_q1 - p_q0;
glm::vec3 r = p_p0 - p_q0;
float a = glm::dot(p, p);
float b = glm::dot(p, q);
float c = glm::dot(q, q);
float d = glm::dot(p, r);
float e = glm::dot(q, r);
float s = 0.0f;
float t = 0.0f;
float det = a * c - b * b;
if (det > CMP_EPSILON) {
// Non-parallel segments
float bte = b * e;
float ctd = c * d;
if (bte <= ctd) {
// s <= 0.0f
if (e <= 0.0f) {
// t <= 0.0f
s = (-d >= a ? 1 : (-d > 0.0f ? -d / a : 0.0f));
t = 0.0f;
} else if (e < c) {
// 0.0f < t < 1
s = 0.0f;
t = e / c;
} else {
// t >= 1
s = (b - d >= a ? 1 : (b - d > 0.0f ? (b - d) / a : 0.0f));
t = 1;
}
} else {
// s > 0.0f
s = bte - ctd;
if (s >= det) {
// s >= 1
if (b + e <= 0.0f) {
// t <= 0.0f
s = (-d <= 0.0f ? 0.0f : (-d < a ? -d / a : 1));
t = 0.0f;
} else if (b + e < c) {
// 0.0f < t < 1
s = 1;
t = (b + e) / c;
} else {
// t >= 1
s = (b - d <= 0.0f ? 0.0f : (b - d < a ? (b - d) / a : 1));
t = 1;
}
} else {
// 0.0f < s < 1
float ate = a * e;
float btd = b * d;
if (ate <= btd) {
// t <= 0.0f
s = (-d <= 0.0f ? 0.0f : (-d >= a ? 1 : -d / a));
t = 0.0f;
} else {
// t > 0.0f
t = ate - btd;
if (t >= det) {
// t >= 1
s = (b - d <= 0.0f ? 0.0f : (b - d >= a ? 1 : (b - d) / a));
t = 1;
} else {
// 0.0f < t < 1
s /= det;
t /= det;
}
}
}
}
} else {
// Parallel segments
if (e <= 0.0f) {
s = (-d <= 0.0f ? 0.0f : (-d >= a ? 1 : -d / a));
t = 0.0f;
} else if (e >= c) {
s = (b - d <= 0.0f ? 0.0f : (b - d >= a ? 1 : (b - d) / a));
t = 1;
} else {
s = 0.0f;
t = e / c;
}
}
r_ps = (1 - s) * p_p0 + s * p_p1;
r_qt = (1 - t) * p_q0 + t * p_q1;
}

View file

@ -1,5 +0,0 @@
#pragma once
#include <glm/glm.hpp>
// From godot/editor/plugins/gizmos/gizmo_3d_helper.h
void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3 &p_p1, const glm::vec3 &p_q0, const glm::vec3 &p_q1, glm::vec3 &r_ps, glm::vec3 &r_qt);

View file

@ -1,202 +0,0 @@
#include "instance.h"
#include "../../common.h"
#include "../../datatypes/meta.h"
#include "datatypes/base.h"
#include "objects/base/member.h"
#include "objects/meta.h"
#include <algorithm>
#include <cstddef>
#include <cstdio>
#include <memory>
#include <optional>
// Static so that this variable name is "local" to this source file
const InstanceType Instance::TYPE = {
.super = NULL,
.className = "Instance",
.constructor = NULL, // Instance is abstract and therefore not creatable
.explorerIcon = "instance",
};
// Instance is abstract, so it should not implement GetClass directly
// InstanceType* Instance::GetClass() {
// return &TYPE_;
// }
Instance::Instance(const InstanceType* type) {
this->name = type->className;
this->memberMap = std::make_unique<MemberMap>( MemberMap {
.super = std::nullopt,
.members = {
{ "Name", { .backingField = &name, .type = &Data::String::TYPE, .codec = fieldCodecOf<Data::String, std::string>() } }
}
});
}
Instance::~Instance () {
}
// 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()) {
if (currentParent.value() == this->shared_from_this())
return false;
}
return true;
}
bool Instance::SetParent(std::optional<std::shared_ptr<Instance>> newParent) {
if (this->parentLocked || !ancestryContinuityCheck(newParent))
return false;
auto lastParent = GetParent();
if (hierarchyPreUpdateHandler.has_value()) hierarchyPreUpdateHandler.value()(this->shared_from_this(), lastParent, newParent);
// If we currently have a parent, remove ourselves from it before adding ourselves to the new one
if (this->parent.has_value() && !this->parent.value().expired()) {
auto oldParent = this->parent.value().lock();
oldParent->children.erase(std::find(oldParent->children.begin(), oldParent->children.end(), this->shared_from_this()));
}
// Add ourselves to the new parent
if (newParent.has_value()) {
newParent.value()->children.push_back(this->shared_from_this());
}
this->parent = newParent;
// TODO: Add code for sending signals for parent updates
// TODO: Yeahhh maybe this isn't the best way of doing this?
if (hierarchyPostUpdateHandler.has_value()) hierarchyPostUpdateHandler.value()(this->shared_from_this(), lastParent, newParent);
this->OnParentUpdated(lastParent, newParent);
return true;
}
std::optional<std::shared_ptr<Instance>> Instance::GetParent() {
if (!parent.has_value()) return std::nullopt;
if (parent.value().expired()) return std::nullopt;
return parent.value().lock();
}
bool Instance::IsParentLocked() {
return this->parentLocked;
}
void Instance::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
// Empty stub
}
// Properties
tl::expected<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
auto meta = GetPropertyMeta(name);
if (!meta) return tl::make_unexpected(MemberNotFound());
return meta->codec.read(meta->backingField);
}
tl::expected<void, MemberNotFound> Instance::SetPropertyValue(std::string name, Data::Variant value) {
auto meta = GetPropertyMeta(name);
if (!meta) return tl::make_unexpected(MemberNotFound());
meta->codec.write(value, meta->backingField);
if (meta->updateCallback) meta->updateCallback.value()(name);
return {};
}
tl::expected<PropertyMeta, MemberNotFound> Instance::GetPropertyMeta(std::string name) {
MemberMap* current = &*memberMap;
while (true) {
// We look for the property in current member map
auto it = current->members.find(name);
// It is found, return it
if (it != current->members.end())
return it->second;
// It is not found, If there are no other maps to search, return null
if (!current->super.has_value())
return tl::make_unexpected(MemberNotFound());
// Search in the parent
current = current->super->get();
}
}
std::vector<std::string> Instance::GetProperties() {
if (cachedMemberList.has_value()) return cachedMemberList.value();
std::vector<std::string> memberList;
MemberMap* current = &*memberMap;
do {
for (auto const& [key, _] : current->members) {
// Don't add it if it's already in the list
if (std::find(memberList.begin(), memberList.end(), key) == memberList.end())
memberList.push_back(key);
}
if (!current->super.has_value())
break;
current = &*current->super.value();
} while (true);
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

@ -1,80 +0,0 @@
#pragma once
#include <vector>
#include <memory>
#include <optional>
#include <string>
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include <map>
#include <vector>
// #include <../../include/expected.hpp>
#include <expected.hpp>
#include <pugixml.hpp>
#include "member.h"
class Instance;
typedef std::shared_ptr<Instance>(*InstanceConstructor)();
// Struct describing information about an instance
struct InstanceType {
const InstanceType* super; // May be null
std::string className;
InstanceConstructor constructor;
std::string explorerIcon = "";
};
// Base class for all instances in the data model
// Note: enable_shared_from_this HAS to be public or else its field will not be populated
// Maybe this could be replaced with a friendship? But that seems unnecessary.
// https://stackoverflow.com/q/56415222/16255372
class Instance : public std::enable_shared_from_this<Instance> {
private:
std::optional<std::weak_ptr<Instance>> parent;
std::vector<std::shared_ptr<Instance>> children;
std::optional<std::vector<std::string>> cachedMemberList;
bool ancestryContinuityCheck(std::optional<std::shared_ptr<Instance>> newParent);
protected:
bool parentLocked = false;
std::unique_ptr<MemberMap> memberMap;
Instance(const InstanceType*);
virtual ~Instance();
virtual void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent);
template <typename T> inline std::shared_ptr<T> shared() { return std::dynamic_pointer_cast<T>(this->shared_from_this()); }
public:
const static InstanceType TYPE;
std::string name;
// Instance is abstract, so it should not implement GetClass directly
virtual const InstanceType* GetClass() = 0;
bool SetParent(std::optional<std::shared_ptr<Instance>> newParent);
std::optional<std::shared_ptr<Instance>> GetParent();
bool IsParentLocked();
inline const std::vector<std::shared_ptr<Instance>> GetChildren() { return children; }
// Utility functions
inline void AddChild(std::shared_ptr<Instance> object) { object->SetParent(this->shared_from_this()); }
// Properties
// Do I like using expected?
tl::expected<Data::Variant, MemberNotFound> GetPropertyValue(std::string name);
tl::expected<void, MemberNotFound> SetPropertyValue(std::string name, Data::Variant value);
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;
typedef std::weak_ptr<Instance> InstanceRefWeak;

View file

@ -1,67 +0,0 @@
#pragma once
#include "../../datatypes/base.h"
#include "datatypes/meta.h"
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <variant>
class Instance;
struct FieldCodec {
void (*write)(Data::Variant source, void* destination);
Data::Variant (*read)(void* source);
};
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);
},
};
}
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;
},
};
}
template <typename T>
std::function<void(std::string name)> memberFunctionOf(void(T::*func)(std::string), T* obj) {
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;
struct MemberMap {
std::optional<std::unique_ptr<MemberMap>> super;
std::map<std::string, PropertyMeta> members;
};
struct MemberNotFound {};

View file

@ -1,9 +0,0 @@
#include "service.h"
#include <memory>
Service::Service(const InstanceType* type, std::weak_ptr<DataModel> root) : Instance(type), dataModel(root) {}
void Service::InitService() {
SetParent(dataModel.lock());
parentLocked = true;
}

View file

@ -1,15 +0,0 @@
#pragma once
// Services are top-level singletons and belong to a specific DataModel
// They serve one specific task and can be accessed using game:GetService
#include "objects/datamodel.h"
#include <memory>
class Service : public Instance {
protected:
std::weak_ptr<DataModel> dataModel;
Service(const InstanceType* type, std::weak_ptr<DataModel> root);
virtual void InitService();
friend class DataModel;
};

View file

@ -1,120 +0,0 @@
#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,
.className = "DataModel",
.constructor = nullptr,
};
const InstanceType* DataModel::GetClass() {
return &TYPE;
}
DataModel::DataModel()
: Instance(&TYPE) {
this->name = "Place";
}
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>());
// 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

@ -1,48 +0,0 @@
#pragma once
#include "base.h"
#include <memory>
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:
void DeserializeService(pugi::xml_node* node);
public:
const static InstanceType TYPE;
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);
};

View file

@ -1,90 +0,0 @@
#include "handles.h"
#include "common.h"
#include "datatypes/cframe.h"
#include "datatypes/vector.h"
#include <optional>
#include <reactphysics3d/collision/RaycastInfo.h>
#include <reactphysics3d/engine/PhysicsCommon.h>
#include <reactphysics3d/engine/PhysicsWorld.h>
#include <reactphysics3d/mathematics/Transform.h>
HandleFace HandleFace::XPos(0, glm::vec3(1,0,0));
HandleFace HandleFace::XNeg(1, glm::vec3(-1,0,0));
HandleFace HandleFace::YPos(2, glm::vec3(0,1,0));
HandleFace HandleFace::YNeg(3, glm::vec3(0,-1,0));
HandleFace HandleFace::ZPos(4, glm::vec3(0,0,1));
HandleFace HandleFace::ZNeg(5, glm::vec3(0,0,-1));
std::array<HandleFace, 6> HandleFace::Faces { HandleFace::XPos, HandleFace::XNeg, HandleFace::YPos, HandleFace::YNeg, HandleFace::ZPos, HandleFace::ZNeg };
// Shitty solution
static rp3d::PhysicsCommon common;
static rp3d::PhysicsWorld* world = common.createPhysicsWorld();
const InstanceType Handles::TYPE = {
.super = &Instance::TYPE,
.className = "Handles",
// .constructor = &Workspace::Create,
// .explorerIcon = "",
};
const InstanceType* Handles::GetClass() {
return &TYPE;
}
Handles::Handles(): Instance(&TYPE) {
}
Data::CFrame Handles::GetCFrameOfHandle(HandleFace face) {
if (!adornee.has_value() || adornee->expired()) return Data::CFrame(glm::vec3(0,0,0), (Data::Vector3)glm::vec3(0,0,0));
Data::CFrame localFrame = worldMode ? Data::CFrame::IDENTITY + adornee->lock()->position() : adornee->lock()->cframe;
// We don't want this to align with local * face.normal, or else we have problems.
glm::vec3 upAxis(0, 0, 1);
if (glm::abs(glm::dot(glm::vec3(localFrame.Rotation() * face.normal), upAxis)) > 0.9999f)
upAxis = glm::vec3(0, 1, 0);
Data::Vector3 handlePos = localFrame * ((2.f + adornee->lock()->size * 0.5f) * face.normal);
Data::CFrame cframe(handlePos, handlePos + localFrame.Rotation() * face.normal, upAxis);
return cframe;
}
Data::CFrame Handles::PartCFrameFromHandlePos(HandleFace face, Data::Vector3 newPos) {
if (!adornee.has_value() || adornee->expired()) return Data::CFrame(glm::vec3(0,0,0), (Data::Vector3)glm::vec3(0,0,0));
Data::CFrame localFrame = worldMode ? Data::CFrame::IDENTITY + adornee->lock()->position() : adornee->lock()->cframe;
Data::CFrame inverseFrame = localFrame.Inverse();
Data::Vector3 handlePos = localFrame * ((2.f + adornee->lock()->size * 0.5f) * face.normal);
// glm::vec3 localPos = inverseFrame * newPos;
glm::vec3 newPartPos = newPos - localFrame.Rotation() * ((2.f + adornee->lock()->size * 0.5f) * face.normal);
return adornee->lock()->cframe.Rotation() + newPartPos;
}
std::optional<HandleFace> Handles::RaycastHandle(rp3d::Ray ray) {
for (HandleFace face : HandleFace::Faces) {
Data::CFrame cframe = GetCFrameOfHandle(face);
// Implement manual detection via boxes instead of... this shit
// This code also hardly works, and is not good at all... Hooo nope.
rp3d::RigidBody* body = world->createRigidBody(Data::CFrame::IDENTITY + cframe.Position());
body->addCollider(common.createBoxShape(cframe.Rotation() * Data::Vector3(HandleSize(face) / 2.f)), rp3d::Transform::identity());
rp3d::RaycastInfo info;
if (body->raycast(ray, info)) {
world->destroyRigidBody(body);
return face;
}
world->destroyRigidBody(body);
}
return std::nullopt;
}
Data::Vector3 Handles::HandleSize(HandleFace face) {
if (handlesType == HandlesType::MoveHandles)
return glm::vec3(0.5f, 0.5f, 2.f);
return glm::vec3(1,1,1);
}

View file

@ -1,55 +0,0 @@
#pragma once
#include "base.h"
#include "datatypes/cframe.h"
#include "objects/base/service.h"
#include "objects/part.h"
#include <array>
#include <memory>
#include <reactphysics3d/body/RigidBody.h>
class HandleFace {
HandleFace(int index, glm::vec3 normal) : index(index), normal(normal){}
public:
int index;
glm::vec3 normal;
static HandleFace XPos;
static HandleFace XNeg;
static HandleFace YPos;
static HandleFace YNeg;
static HandleFace ZPos;
static HandleFace ZNeg;
static std::array<HandleFace, 6> Faces;
};
enum HandlesType {
MoveHandles,
ScaleHandles,
RotateHandles,
};
class Handles : public Instance {
public:
const static InstanceType TYPE;
bool active;
std::optional<std::weak_ptr<Part>> adornee;
HandlesType handlesType;
// inline std::optional<std::weak_ptr<Part>> GetAdornee() { return adornee; }
// inline void SetAdornee(std::optional<std::weak_ptr<Part>> newAdornee) { this->adornee = newAdornee; updateAdornee(); };
Handles();
// World-space handles vs local-space handles
bool worldMode = false;
Data::CFrame GetCFrameOfHandle(HandleFace face);
Data::CFrame PartCFrameFromHandlePos(HandleFace face, Data::Vector3 newPos);
Data::Vector3 HandleSize(HandleFace face);
std::optional<HandleFace> RaycastHandle(rp3d::Ray ray);
static inline std::shared_ptr<Handles> New() { return std::make_shared<Handles>(); };
virtual const InstanceType* GetClass() override;
};

View file

@ -1,10 +0,0 @@
#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 },
};

View file

@ -1,8 +0,0 @@
#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,125 +0,0 @@
#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",
.constructor = &Part::CreateGeneric,
.explorerIcon = "part",
};
const InstanceType* Part::GetClass() {
return &TYPE;
}
Part::Part(): Part(PartConstructParams { .color = Data::Color3(0.639216f, 0.635294f, 0.647059f) }) {
}
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)
}}, { "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>(),
}}, { "Transparency", {
.backingField = &transparency,
.type = &Data::Float::TYPE,
.codec = fieldCodecOf<Data::Float, float>(),
}}
}
});
}
// 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);
}
void Part::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
if (this->rigidBody)
this->rigidBody->setIsActive(newParent.has_value());
// 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()));
}

View file

@ -1,15 +0,0 @@
#include "workspace.h"
const InstanceType Workspace::TYPE = {
.super = &Instance::TYPE,
.className = "Workspace",
// .constructor = &Workspace::Create,
.explorerIcon = "workspace",
};
const InstanceType* Workspace::GetClass() {
return &TYPE;
}
Workspace::Workspace(std::weak_ptr<DataModel> dataModel): Service(&TYPE, dataModel) {
}

View file

@ -1,17 +0,0 @@
#pragma once
#include "base.h"
#include "objects/base/service.h"
#include <memory>
class Workspace : public Service {
//private:
public:
const static InstanceType TYPE;
Workspace(std::weak_ptr<DataModel> dataModel);
// 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); };
virtual const InstanceType* GetClass() override;
};

View file

@ -1,140 +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 (InstanceRef obj : workspace()->GetChildren()) {
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);

File diff suppressed because it is too large Load diff

View file

@ -1,270 +0,0 @@
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <GL/gl.h>
#include <cstdio>
#include <glm/ext.hpp>
#include <glm/ext/matrix_float4x4.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/ext/vector_float3.hpp>
#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"
#include "../camera.h"
#include "../common.h"
#include "../objects/part.h"
#include "skybox.h"
#include "surface.h"
#include "texture3d.h"
#include "renderer.h"
Shader* shader = NULL;
Shader* skyboxShader = NULL;
Shader* handleShader = NULL;
Shader* identityShader = NULL;
extern Camera camera;
Skybox* skyboxTexture = NULL;
Texture3D* studsTexture = NULL;
static int viewportWidth, viewportHeight;
void renderInit(GLFWwindow* window, int width, int height) {
viewportWidth = width, viewportHeight = height;
glViewport(0, 0, width, height);
initMeshes();
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
skyboxTexture = new Skybox({
"assets/textures/skybox/null_plainsky512_lf.jpg",
"assets/textures/skybox/null_plainsky512_rt.jpg",
"assets/textures/skybox/null_plainsky512_up.jpg",
"assets/textures/skybox/null_plainsky512_dn.jpg",
"assets/textures/skybox/null_plainsky512_ft.jpg",
"assets/textures/skybox/null_plainsky512_bk.jpg",
}, GL_RGB);
studsTexture = new Texture3D("assets/textures/studs.png", 128, 128, 6, GL_RGBA);
// Compile shaders
shader = new Shader("assets/shaders/phong.vs", "assets/shaders/phong.fs");
skyboxShader = new Shader("assets/shaders/skybox.vs", "assets/shaders/skybox.fs");
handleShader = new Shader("assets/shaders/handle.vs", "assets/shaders/handle.fs");
identityShader = new Shader("assets/shaders/identity.vs", "assets/shaders/identity.fs");
}
void renderParts() {
glDepthMask(GL_TRUE);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CW);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Use shader
shader->use();
// shader->set("objectColor", glm::vec3(1.0f, 0.5f, 0.31f));
// shader->set("lightColor", glm::vec3(1.0f, 1.0f, 1.0f));
// view/projection transformations
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)viewportWidth / (float)viewportHeight, 0.1f, 1000.0f);
glm::mat4 view = camera.getLookAt();
shader->set("projection", projection);
shader->set("view", view);
// shader->set("material", Material {
// // .ambient = glm::vec3(1.0f, 0.5f, 0.31f),
// .diffuse = glm::vec3(0.639216f, 0.635294f, 0.647059f),
// .specular = glm::vec3(0.5f, 0.5f, 0.5f),
// .shininess = 16.0f,
// });
shader->set("sunLight", DirLight {
.direction = glm::vec3(-0.2f, -1.0f, -0.3f),
.ambient = glm::vec3(0.2f, 0.2f, 0.2f),
.diffuse = glm::vec3(0.5f, 0.5f, 0.5f),
.specular = glm::vec3(1.0f, 1.0f, 1.0f),
});
shader->set("numPointLights", 0);
// shader->set("pointLights[0]", PointLight {
// .position = lightPos,
// .ambient = glm::vec3(0.4f, 0.4f, 0.4f),
// .diffuse = glm::vec3(1.0f, 1.0f, 1.0f),
// .specular = glm::vec3(1.0f, 1.0f, 1.0f),
// .constant = 1.0,
// .linear = 0.9,
// .quadratic = 0.32,
// });
studsTexture->activate(0);
shader->set("studs", 0);
// shader->set("surfaces[1]", SurfaceStuds);
shader->set("surfaces[1]", SurfaceStuds);
shader->set("surfaces[4]", SurfaceInlets);
// Pre-calculate the normal matrix for the shader
// Pass in the camera position
shader->set("viewPos", camera.cameraPos);
// Sort by nearest
std::map<float, std::shared_ptr<Part>> sorted;
for (InstanceRef inst : workspace()->GetChildren()) {
if (inst->GetClass()->className != "Part") continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
if (part->transparency > 0.00001) {
float distance = glm::length(glm::vec3(Data::Vector3(camera.cameraPos) - part->position()));
sorted[distance] = part;
} else {
glm::mat4 model = part->cframe;
if (part->name == "camera") model = camera.getLookAt();
model = glm::scale(model, part->size);
shader->set("model", model);
shader->set("material", Material {
.diffuse = part->color,
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 16.0f,
});
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
shader->set("normalMatrix", normalMatrix);
shader->set("texScale", part->size);
shader->set("transparency", part->transparency);
CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
}
}
// TODO: Same as todo in src/physics/simulation.cpp
// According to LearnOpenGL, std::map automatically sorts its contents.
for (std::map<float, std::shared_ptr<Part>>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); it++) {
std::shared_ptr<Part> part = it->second;
glm::mat4 model = part->cframe;
if (part->name == "camera") model = camera.getLookAt();
model = glm::scale(model, part->size);
shader->set("model", model);
shader->set("material", Material {
.diffuse = part->color,
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 16.0f,
});
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
shader->set("normalMatrix", normalMatrix);
shader->set("texScale", part->size);
shader->set("transparency", part->transparency);
CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
}
}
void renderSkyBox() {
glDepthMask(GL_FALSE);
glCullFace(GL_FRONT);
skyboxShader->use();
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)viewportWidth / (float)viewportHeight, 0.1f, 1000.0f);
// Remove translation component of view, making us always at (0, 0, 0)
glm::mat4 view = glm::mat4(glm::mat3(camera.getLookAt()));
skyboxShader->set("projection", projection);
skyboxShader->set("view", view);
skyboxShader->set("uTexture", 0);
CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, 36);
}
void renderHandles() {
if (!editorToolHandles->adornee.has_value() || !editorToolHandles->active) return;
glDepthMask(GL_TRUE);
glCullFace(GL_BACK);
// Use shader
handleShader->use();
// view/projection transformations
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)viewportWidth / (float)viewportHeight, 0.1f, 1000.0f);
glm::mat4 view = camera.getLookAt();
handleShader->set("projection", projection);
handleShader->set("view", view);
handleShader->set("sunLight", DirLight {
.direction = glm::vec3(-0.2f, -1.0f, -0.3f),
.ambient = glm::vec3(0.2f, 0.2f, 0.2f),
.diffuse = glm::vec3(0.5f, 0.5f, 0.5f),
.specular = glm::vec3(1.0f, 1.0f, 1.0f),
});
handleShader->set("numPointLights", 0);
// Pass in the camera position
handleShader->set("viewPos", camera.cameraPos);
for (auto face : HandleFace::Faces) {
glm::mat4 model = editorToolHandles->GetCFrameOfHandle(face);
model = glm::scale(model, (glm::vec3)editorToolHandles->HandleSize(face));
handleShader->set("model", model);
handleShader->set("material", Material {
.diffuse = glm::abs(face.normal),
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 16.0f,
});
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
handleShader->set("normalMatrix", normalMatrix);
if (editorToolHandles->handlesType == HandlesType::MoveHandles) {
ARROW_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, ARROW_MESH->vertexCount);
} else {
SPHERE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, SPHERE_MESH->vertexCount);
}
}
// 2d square overlay
glDisable(GL_CULL_FACE);
identityShader->use();
identityShader->set("aColor", glm::vec3(0.f, 1.f, 1.f));
for (auto face : HandleFace::Faces) {
Data::CFrame cframe = editorToolHandles->GetCFrameOfHandle(face);
glm::vec4 screenPos = projection * view * glm::vec4((glm::vec3)cframe.Position(), 1.0f);
glm::vec3 ndcCoords = screenPos / screenPos.w;
float rad = 5;
float xRad = rad * 1/viewportWidth;
float yRad = rad * 1/viewportHeight;
glBegin(GL_QUADS);
glVertex3f(ndcCoords.x - xRad, ndcCoords.y - yRad, 0);
glVertex3f(ndcCoords.x + xRad, ndcCoords.y - yRad, 0);
glVertex3f(ndcCoords.x + xRad, ndcCoords.y + yRad, 0);
glVertex3f(ndcCoords.x - xRad, ndcCoords.y + yRad, 0);
glEnd();
}
}
void render(GLFWwindow* window) {
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderSkyBox();
renderHandles();
renderParts();
}
void setViewport(int width, int height) {
viewportWidth = width, viewportHeight = height;
}

View file

@ -1,6 +0,0 @@
#pragma once
#include <GLFW/glfw3.h>
void renderInit(GLFWwindow* window, int width, int height);
void render(GLFWwindow* window);
void setViewport(int width, int height);

View file

@ -1,9 +0,0 @@
opengl (Linux: glvnd, Windows: [built-in/none])
glfw
glew
glm
sdl2
stb
qt6
reactphysics3d
pugixml

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.31..)
cmake_minimum_required(VERSION 3.16)
project(editor VERSION 0.1 LANGUAGES CXX)
@ -14,6 +14,8 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)
include_directories("../src")
set(TS_FILES editor_en_US.ts)
set(PROJECT_SOURCES
@ -27,10 +29,6 @@ set(PROJECT_SOURCES
panes/explorerview.cpp
panes/explorermodel.h
panes/explorermodel.cpp
panes/propertiesview.h
panes/propertiesview.cpp
panes/propertiesmodel.h
panes/propertiesmodel.cpp
${TS_FILES}
)
@ -38,6 +36,7 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(editor
MANUAL_FINALIZATION
${PROJECT_SOURCES}
$<TARGET_OBJECTS:openblocks>
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET editor APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
@ -56,24 +55,14 @@ else()
add_executable(editor
${PROJECT_SOURCES}
mainglwidget.h mainglwidget.cpp
$<TARGET_OBJECTS:openblocks>
)
endif()
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
endif()
target_include_directories(editor PUBLIC "../core/src" "../include")
target_link_libraries(editor PRIVATE openblocks Qt${QT_VERSION_MAJOR}::Widgets)
# Qt6 does not include QOpenGLWidgets as part of Widgets base anymore, so
# we have to include it manually
if (${QT_VERSION} GREATER_EQUAL 6)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS OpenGL OpenGLWidgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS OpenGL OpenGLWidgets)
target_include_directories(editor PUBLIC Qt6::OpenGL Qt6::OpenGLWidgets)
target_link_libraries(editor PRIVATE Qt6::OpenGL Qt6::OpenGLWidgets)
endif()
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)
# 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
@ -96,8 +85,6 @@ install(TARGETS editor
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(FILES $<TARGET_RUNTIME_DLLS:editor> TYPE BIN)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(editor)
endif()

View file

@ -1 +0,0 @@
#pragma once

View file

@ -2,251 +2,59 @@
#include <chrono>
#include <QMouseEvent>
#include <glm/common.hpp>
#include <glm/ext/matrix_projection.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/ext/vector_float3.hpp>
#include <glm/geometric.hpp>
#include <glm/gtc/round.hpp>
#include <glm/matrix.hpp>
#include <memory>
#include <optional>
#include <reactphysics3d/collision/RaycastInfo.h>
#include <vector>
#include "datatypes/cframe.h"
#include "editorcommon.h"
#include "mainwindow.h"
#include "objects/handles.h"
#include "physics/util.h"
#include "GLFW/glfw3.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 "../core/src/rendering/defaultmeshes.h"
#include "math_helper.h"
MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent) {
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
setMouseTracking(true);
}
void MainGLWidget::initializeGL() {
glewInit();
renderInit(NULL, width(), height());
renderInit(NULL);
}
extern int vpx, vpy;
void MainGLWidget::resizeGL(int w, int h) {
// Update projection matrix and other size related settings:
// m_projection.setToIdentity();
// m_projection.perspective(45.0f, w / float(h), 0.01f, 100.0f);
// ...
// glViewport(0, 0, w, h);
setViewport(w, h);
}
glm::vec2 firstPoint;
glm::vec2 secondPoint;
extern std::optional<std::weak_ptr<Part>> draggingObject;
extern std::optional<HandleFace> draggingHandle;
extern Shader* shader;
void MainGLWidget::paintGL() {
::render(NULL);
}
bool isMouseRightDragging = false;
bool isMouseDragging = false;
QPoint lastMousePos;
void MainGLWidget::handleCameraRotate(QMouseEvent* evt) {
if (!isMouseRightDragging) return;
void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
// if (!(evt->buttons() & Qt::RightButton)) return;
if (!isMouseDragging) return;
camera.processRotation(evt->pos().x() - lastMousePos.x(), evt->pos().y() - lastMousePos.y());
lastMousePos = evt->pos();
// QCursor::setPos(lastMousePos);
}
bool isMouseDragging = false;
std::optional<std::weak_ptr<Part>> draggingObject;
std::optional<HandleFace> draggingHandle;
void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
if (!isMouseDragging || !draggingObject) return;
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) {
return (part == draggingObject->lock()) ? FilterResult::PASS : FilterResult::TARGET;
});
if (!rayHit) return;
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());
}
inline glm::vec3 vec3fy(glm::vec4 vec) {
return vec / vec.w;
}
QPoint lastPoint;
void MainGLWidget::handleHandleDrag(QMouseEvent* evt) {
QPoint cLastPoint = lastPoint;
lastPoint = evt->pos();
if (!isMouseDragging || !draggingHandle || !editorToolHandles->adornee || !editorToolHandles->active) return;
QPoint position = evt->pos();
auto part = editorToolHandles->adornee->lock();
// This was actually quite a difficult problem to solve, managing to get the handle to go underneath the cursor
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
pointDir = glm::normalize(pointDir);
Data::CFrame handleCFrame = editorToolHandles->GetCFrameOfHandle(draggingHandle.value());
// Current frame. Identity frame if worldMode == true, selected object's frame if worldMode == false
Data::CFrame frame = editorToolHandles->worldMode ? Data::CFrame::IDENTITY + part->position() : part->cframe.Rotation();
// Segment from axis stretching -4096 to +4096 rel to handle's position
glm::vec3 axisSegment0 = handleCFrame.Position() + (-handleCFrame.LookVector() * 4096.0f);
glm::vec3 axisSegment1 = handleCFrame.Position() + (-handleCFrame.LookVector() * -4096.0f);
// Segment from camera stretching 4096 forward
glm::vec3 mouseSegment0 = camera.cameraPos;
glm::vec3 mouseSegment1 = camera.cameraPos + pointDir * 4096.0f;
// Closest point on the axis segment between the two segments
glm::vec3 handlePoint, rb;
get_closest_points_between_segments(axisSegment0, axisSegment1, mouseSegment0, mouseSegment1, handlePoint, rb);
// Find new part position
glm::vec3 centerPoint = editorToolHandles->PartCFrameFromHandlePos(draggingHandle.value(), handlePoint).Position();
// Apply snapping in the current frame
glm::vec3 diff = centerPoint - (glm::vec3)editorToolHandles->adornee->lock()->position();
// printf("\n=======\nPre-snap: (%f, %f, %f)\n", diff.x, diff.y, diff.z);
if (snappingFactor()) diff = frame.Rotation() * (glm::round(glm::vec3(frame.Inverse().Rotation() * diff) / snappingFactor()) * snappingFactor());
// printf("Post-snap: (%f, %f, %f)\n", diff.x, diff.y, diff.z);
switch (mainWindow()->selectedTool) {
case SelectedTool::SELECT: break;
case SelectedTool::MOVE: {
// Add difference
editorToolHandles->adornee->lock()->cframe = editorToolHandles->adornee->lock()->cframe + diff;
} break;
case SelectedTool::SCALE: {
// Find local difference
glm::vec3 localDiff = frame.Inverse() * diff;
// Find outwarwd difference
localDiff = localDiff * glm::sign(draggingHandle->normal);
// Add local difference to size
part->size += localDiff;
// If ctrl is not pressed, offset the part by half the size difference to keep the other bound where it was originally
if (!(evt->modifiers() & Qt::ControlModifier))
part->cframe = part->cframe + diff * 0.5f;
} break;
case SelectedTool::ROTATE: {
// TODO: Implement rotation
} break;
}
syncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock()));
}
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
if (!editorToolHandles->adornee.has_value() || !editorToolHandles->active) return std::nullopt;
return editorToolHandles->RaycastHandle(rp3d::Ray(glmToRp(camera.cameraPos), glmToRp(glm::normalize(pointDir)) * 50000));
}
void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
QPoint position = evt->pos();
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
if (raycastHandle(pointDir)) {
setCursor(Qt::OpenHandCursor);
return;
};
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000);
if (rayHit && partFromBody(rayHit->body)->name != "Baseplate") {
setCursor(Qt::OpenHandCursor);
return;
}
setCursor(Qt::ArrowCursor);
}
void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
handleCameraRotate(evt);
handleObjectDrag(evt);
handleHandleDrag(evt);
handleCursorChange(evt);
}
void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
switch(evt->button()) {
// Camera drag
case Qt::RightButton: {
lastMousePos = evt->pos();
isMouseRightDragging = true;
return;
// Clicking on objects
} case Qt::LeftButton: {
QPoint position = evt->pos();
if (evt->button() != Qt::RightButton) return;
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
// raycast handles
auto handle = raycastHandle(pointDir);
if (handle.has_value()) {
isMouseDragging = true;
draggingHandle = handle;
return;
}
// raycast part
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000);
if (!rayHit || !partFromBody(rayHit->body)) return;
std::shared_ptr<Part> part = partFromBody(rayHit->body);
if (part->name == "Baseplate") return;
//part.selected = true;
isMouseDragging = true;
draggingObject = part;
setSelection(std::vector<InstanceRefWeak> { part });
// Disable bit so that we can ignore the part while raycasting
// part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10);
return;
} default:
return;
}
lastMousePos = evt->pos();
isMouseDragging = true;
}
void MainGLWidget::mouseReleaseEvent(QMouseEvent* evt) {
// if (isMouseDragging) draggingObject->lock()->rigidBody->getCollider(0)->setCollisionCategoryBits(0b11);
isMouseRightDragging = false;
isMouseDragging = false;
draggingObject = std::nullopt;
draggingHandle = std::nullopt;
}
static int moveZ = 0;
@ -272,11 +80,15 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
else if (evt->key() == Qt::Key_D) moveX = -1;
if (evt->key() == Qt::Key_F) {
workspace()->AddChild(lastPart = Part::New({
workspace->AddChild(lastPart = Part::New({
.position = camera.cameraPos + camera.cameraFront * glm::vec3(3),
.rotation = glm::vec3(0),
.size = glm::vec3(1, 1, 1),
.color = glm::vec3(1.0f, 0.5f, 0.31f),
.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,
}
}));
syncPartPhysics(lastPart);
}
@ -286,16 +98,3 @@ void MainGLWidget::keyReleaseEvent(QKeyEvent* evt) {
if (evt->key() == Qt::Key_W || evt->key() == Qt::Key_S) moveZ = 0;
else if (evt->key() == Qt::Key_A || evt->key() == Qt::Key_D) moveX = 0;
}
MainWindow* MainGLWidget::mainWindow() {
return dynamic_cast<MainWindow*>(window());
}
float MainGLWidget::snappingFactor() {
switch (mainWindow()->snappingMode) {
case GridSnappingMode::SNAP_1_STUD: return 1;
case GridSnappingMode::SNAP_05_STUDS: return 0.5;
case GridSnappingMode::SNAP_OFF: return 0;
}
return 0;
}

View file

@ -1,15 +1,12 @@
#ifndef MAINGLWIDGET_H
#define MAINGLWIDGET_H
#include "mainwindow.h"
#include "objects/part.h"
#include "qevent.h"
#include <QOpenGLWidget>
#include <QWidget>
#include <memory>
class HandleFace;
class MainGLWidget : public QOpenGLWidget {
public:
MainGLWidget(QWidget *parent = nullptr);
@ -21,20 +18,11 @@ protected:
void resizeGL(int w, int h) override;
void paintGL() override;
void handleCameraRotate(QMouseEvent* evt);
void handleObjectDrag(QMouseEvent* evt);
void handleHandleDrag(QMouseEvent* evt);
void handleCursorChange(QMouseEvent* evt);
std::optional<HandleFace> raycastHandle(glm::vec3 pointDir);
void mouseMoveEvent(QMouseEvent* evt) override;
void mousePressEvent(QMouseEvent* evt) override;
void mouseReleaseEvent(QMouseEvent* evt) override;
void keyPressEvent(QKeyEvent* evt) override;
void keyReleaseEvent(QKeyEvent* evt) override;
MainWindow* mainWindow();
float snappingFactor();
};
#endif // MAINGLWIDGET_H

View file

@ -10,226 +10,48 @@
#include <QWidget>
#include <QTreeView>
#include <QAbstractItemView>
#include <memory>
#include <optional>
#include <qglobal.h>
#include <qwindowdefs.h>
#include <sstream>
#include "common.h"
#include "editorcommon.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"
bool simulationPlaying = false;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
dataModel->Init();
ui->setupUi(this);
timer.start(33, this);
setMouseTracking(true);
ui->explorerView->buildContextMenu();
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = SelectedTool::SELECT; updateToolbars(); });
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::MOVE : SelectedTool::SELECT; updateToolbars(); });
connect(ui->actionToolScale, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::SCALE : SelectedTool::SELECT; updateToolbars(); });
connect(ui->actionToolRotate, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::ROTATE : SelectedTool::SELECT; updateToolbars(); });
ui->actionToolSelect->setChecked(true);
selectedTool = SelectedTool::SELECT;
connect(ui->actionGridSnap1, &QAction::triggered, this, [&]() { snappingMode = GridSnappingMode::SNAP_1_STUD; updateToolbars(); });
connect(ui->actionGridSnap05, &QAction::triggered, this, [&]() { snappingMode = GridSnappingMode::SNAP_05_STUDS; updateToolbars(); });
connect(ui->actionGridSnapOff, &QAction::triggered, this, [&]() { snappingMode = GridSnappingMode::SNAP_OFF; updateToolbars(); });
ui->actionGridSnap1->setChecked(true);
snappingMode = GridSnappingMode::SNAP_1_STUD;
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 = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + dataModel->name));
if (path == "") return;
dataModel->SaveToFile(path);
});
connect(ui->actionOpen, &QAction::triggered, this, [&]() {
std::optional<std::string> path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptOpen);
if (!path) return;
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value());
dataModel = newModel;
ui->explorerView->updateRoot(newModel);
});
connect(ui->actionDelete, &QAction::triggered, this, [&]() {
for (InstanceRefWeak inst : getSelection()) {
if (inst.expired()) continue;
inst.lock()->SetParent(std::nullopt);
}
setSelection(std::vector<InstanceRefWeak> {});
});
connect(ui->actionCopy, &QAction::triggered, this, [&]() {
pugi::xml_document rootDoc;
for (InstanceRefWeak inst : getSelection()) {
if (inst.expired()) continue;
inst.lock()->Serialize(&rootDoc);
}
std::ostringstream encoded;
rootDoc.save(encoded);
QMimeData* mimeData = new QMimeData;
mimeData->setData("application/xml", QByteArray::fromStdString(encoded.str()));
QApplication::clipboard()->setMimeData(mimeData);
});
connect(ui->actionCut, &QAction::triggered, this, [&]() {
pugi::xml_document rootDoc;
for (InstanceRefWeak inst : getSelection()) {
if (inst.expired()) continue;
inst.lock()->Serialize(&rootDoc);
inst.lock()->SetParent(std::nullopt);
}
std::ostringstream encoded;
rootDoc.save(encoded);
QMimeData* mimeData = new QMimeData;
mimeData->setData("application/xml", QByteArray::fromStdString(encoded.str()));
QApplication::clipboard()->setMimeData(mimeData);
});
connect(ui->actionPaste, &QAction::triggered, this, [&]() {
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
if (!mimeData || !mimeData->hasFormat("application/xml")) return;
QByteArray bytes = mimeData->data("application/xml");
std::string encoded = bytes.toStdString();
pugi::xml_document rootDoc;
rootDoc.load_string(encoded.c_str());
for (pugi::xml_node instNode : rootDoc.children()) {
InstanceRef inst = Instance::Deserialize(&instNode);
workspace()->AddChild(inst);
}
});
connect(ui->actionPasteInto, &QAction::triggered, this, [&]() {
if (getSelection().size() != 1 || getSelection()[0].expired()) return;
InstanceRef selectedParent = getSelection()[0].lock();
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
if (!mimeData || !mimeData->hasFormat("application/xml")) return;
QByteArray bytes = mimeData->data("application/xml");
std::string encoded = bytes.toStdString();
pugi::xml_document rootDoc;
rootDoc.load_string(encoded.c_str());
for (pugi::xml_node instNode : rootDoc.children()) {
InstanceRef inst = Instance::Deserialize(&instNode);
selectedParent->AddChild(inst);
}
});
connect(ui->actionSaveModel, &QAction::triggered, this, [&]() {
std::optional<std::string> path = openFileDialog("Openblocks Model (*.obm)", ".obm", QFileDialog::AcceptSave);
if (!path) return;
std::ofstream outStream(path.value());
// Serialized XML for exporting
pugi::xml_document modelDoc;
pugi::xml_node modelRoot = modelDoc.append_child("openblocks");
for (InstanceRefWeak inst : getSelection()) {
if (inst.expired()) continue;
inst.lock()->Serialize(&modelRoot);
}
modelDoc.save(outStream);
});
connect(ui->actionInsertModel, &QAction::triggered, this, [&]() {
if (getSelection().size() != 1 || getSelection()[0].expired()) return;
InstanceRef selectedParent = getSelection()[0].lock();
std::optional<std::string> path = openFileDialog("Openblocks Model (*.obm)", ".obm", QFileDialog::AcceptOpen);
if (!path) return;
std::ifstream inStream(path.value());
pugi::xml_document modelDoc;
modelDoc.load(inStream);
for (pugi::xml_node instNode : modelDoc.child("openblocks").children("Item")) {
InstanceRef inst = Instance::Deserialize(&instNode);
selectedParent->AddChild(inst);
}
});
// Update handles
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
editorToolHandles->adornee = std::nullopt;
if (newSelection.size() == 0) return;
InstanceRef inst = newSelection[0].lock();
if (inst->GetClass() != &Part::TYPE) return;
editorToolHandles->adornee = std::dynamic_pointer_cast<Part>(inst);
});
// Update properties
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
if (newSelection.size() == 0) return;
if (newSelection.size() > 1)
ui->propertiesView->setSelected(std::nullopt);
ui->propertiesView->setSelected(newSelection[0].lock());
});
// ui->explorerView->Init(ui);
simulationInit();
// Baseplate
workspace()->AddChild(ui->mainWidget->lastPart = Part::New({
workspace->AddChild(ui->mainWidget->lastPart = Part::New({
.position = glm::vec3(0, -5, 0),
.rotation = glm::vec3(0),
.size = glm::vec3(512, 1.2, 512),
.color = glm::vec3(0.388235, 0.372549, 0.384314),
.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,
},
.anchored = true,
}));
ui->mainWidget->lastPart->name = "Baseplate";
syncPartPhysics(ui->mainWidget->lastPart);
workspace()->AddChild(ui->mainWidget->lastPart = Part::New({
workspace->AddChild(ui->mainWidget->lastPart = Part::New({
.position = glm::vec3(0),
.rotation = glm::vec3(0.5, 2, 1),
.size = glm::vec3(4, 1.2, 2),
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
.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,
}
}));
syncPartPhysics(ui->mainWidget->lastPart);
}
@ -244,45 +66,11 @@ 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();
if (simulationPlaying)
physicsStep(deltaTime);
physicsStep(deltaTime);
ui->mainWidget->update();
ui->mainWidget->updateCycle();
}
void MainWindow::updateToolbars() {
ui->actionToolSelect->setChecked(selectedTool == SelectedTool::SELECT);
ui->actionToolMove->setChecked(selectedTool == SelectedTool::MOVE);
ui->actionToolScale->setChecked(selectedTool == SelectedTool::SCALE);
ui->actionToolRotate->setChecked(selectedTool == SelectedTool::ROTATE);
ui->actionGridSnap1->setChecked(snappingMode == GridSnappingMode::SNAP_1_STUD);
ui->actionGridSnap05->setChecked(snappingMode == GridSnappingMode::SNAP_05_STUDS);
ui->actionGridSnapOff->setChecked(snappingMode == GridSnappingMode::SNAP_OFF);
// editorToolHandles->worldMode = false;
if (selectedTool == SelectedTool::MOVE) editorToolHandles->worldMode = true;
if (selectedTool == SelectedTool::SCALE) editorToolHandles->worldMode = false;
editorToolHandles->active = selectedTool != SelectedTool::SELECT;
editorToolHandles->handlesType =
selectedTool == SelectedTool::MOVE ? HandlesType::MoveHandles
: selectedTool == SelectedTool::SCALE ? HandlesType::ScaleHandles
: selectedTool == SelectedTool::ROTATE ? HandlesType::RotateHandles
: HandlesType::ScaleHandles;
}
std::optional<std::string> MainWindow::openFileDialog(QString filter, QString defaultExtension, QFileDialog::AcceptMode acceptMode, QString title) {
QFileDialog dialog(this);
if (title != "") dialog.setWindowTitle(title);
dialog.setNameFilters(QStringList { filter, "All Files (*)" });
dialog.setDefaultSuffix(defaultExtension);
dialog.setAcceptMode(acceptMode);
if (!dialog.exec())
return std::nullopt;
return dialog.selectedFiles().front().toStdString();
}
MainWindow::~MainWindow()
{
delete ui;

View file

@ -7,20 +7,6 @@
#include "qmenu.h"
#include <QMainWindow>
#include <QLineEdit>
#include <qfiledialog.h>
enum SelectedTool {
SELECT,
MOVE,
SCALE,
ROTATE,
};
enum GridSnappingMode {
SNAP_1_STUD,
SNAP_05_STUDS,
SNAP_OFF,
};
QT_BEGIN_NAMESPACE
namespace Ui {
@ -36,16 +22,10 @@ public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
SelectedTool selectedTool;
GridSnappingMode snappingMode;
Ui::MainWindow *ui;
private:
QBasicTimer timer;
void updateToolbars();
void timerEvent(QTimerEvent*) override;
std::optional<std::string> openFileDialog(QString filter, QString defaultExtension, QFileDialog::AcceptMode acceptMode, QString title = "");
};
#endif // MAINWINDOW_H

View file

@ -41,7 +41,7 @@
<x>0</x>
<y>0</y>
<width>1027</width>
<height>30</height>
<height>29</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@ -66,368 +66,13 @@
<number>2</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout_3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="ExplorerView" name="explorerView"/>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="propertiesWidget">
<property name="windowTitle">
<string>Properties</string>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="PropertiesView" name="propertiesView"/>
</item>
</layout>
</widget>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionNew"/>
<addaction name="actionOpen"/>
<addaction name="actionSave"/>
<addaction name="separator"/>
<addaction name="actionAddPart"/>
<addaction name="separator"/>
<addaction name="actionToolSelect"/>
<addaction name="actionToolMove"/>
<addaction name="actionToolScale"/>
<addaction name="actionToolRotate"/>
<addaction name="separator"/>
<addaction name="actionDelete"/>
<addaction name="actionCopy"/>
<addaction name="actionCut"/>
<addaction name="actionPaste"/>
<addaction name="actionPasteInto"/>
<addaction name="separator"/>
<addaction name="actionGridSnap1"/>
<addaction name="actionGridSnap05"/>
<addaction name="actionGridSnapOff"/>
<addaction name="separator"/>
<addaction name="actionToggleSimulation"/>
</widget>
<action name="actionAddPart">
<property name="icon">
<iconset>
<normaloff>assets/icons/part.png</normaloff>assets/icons/part.png</iconset>
</property>
<property name="text">
<string>Add Part</string>
</property>
<property name="toolTip">
<string>Add a part to the workspace</string>
</property>
</action>
<action name="actionNew">
<property name="icon">
<iconset>
<normaloff>assets/icons/editor/new.png</normaloff>assets/icons/editor/new.png</iconset>
</property>
<property name="text">
<string>New</string>
</property>
<property name="toolTip">
<string>New document</string>
</property>
<property name="shortcut">
<string>Ctrl+N</string>
</property>
</action>
<action name="actionOpen">
<property name="icon">
<iconset>
<normaloff>assets/icons/editor/open.png</normaloff>assets/icons/editor/open.png</iconset>
</property>
<property name="text">
<string>Open</string>
</property>
<property name="toolTip">
<string>Open document</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
</property>
</action>
<action name="actionSave">
<property name="icon">
<iconset>
<normaloff>assets/icons/editor/save.png</normaloff>assets/icons/editor/save.png</iconset>
</property>
<property name="text">
<string>Save</string>
</property>
<property name="toolTip">
<string>Save document</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="actionToolSelect">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset theme="edit-select">
<normaloff>assets/icons/editor/drag.png</normaloff>assets/icons/editor/drag.png</iconset>
</property>
<property name="text">
<string>Select Objects</string>
</property>
<property name="toolTip">
<string>Select objects in the workspace</string>
</property>
<property name="shortcut">
<string>1</string>
</property>
</action>
<action name="actionToolMove">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset theme="transform-move"/>
</property>
<property name="text">
<string>Move Objects</string>
</property>
<property name="toolTip">
<string>Move objects in the workspace</string>
</property>
<property name="shortcut">
<string>2</string>
</property>
</action>
<action name="actionToolScale">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset theme="transform-scale"/>
</property>
<property name="text">
<string>Scale Objects</string>
</property>
<property name="toolTip">
<string>Scale objects in the workspace</string>
</property>
<property name="shortcut">
<string>3</string>
</property>
</action>
<action name="actionToolRotate">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset theme="transform-rotate"/>
</property>
<property name="text">
<string>Rotate Objects</string>
</property>
<property name="toolTip">
<string>Rotate objects in the workspace</string>
</property>
<property name="shortcut">
<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>
<action name="actionGridSnap1">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<normaloff>assets/icons/editor/snap1.png</normaloff>assets/icons/editor/snap1.png</iconset>
</property>
<property name="text">
<string>1-Stud Snapping</string>
</property>
<property name="toolTip">
<string>Set grid snapping to 1 stud</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionGridSnap05">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<normaloff>assets/icons/editor/snap05.png</normaloff>assets/icons/editor/snap05.png</iconset>
</property>
<property name="text">
<string>1/2-Stud Snapping</string>
</property>
<property name="toolTip">
<string>Set grid snapping to 1/2 studs</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionGridSnapOff">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<normaloff>assets/icons/editor/snapoff.png</normaloff>assets/icons/editor/snapoff.png</iconset>
</property>
<property name="text">
<string>No Grid Snapping</string>
</property>
<property name="toolTip">
<string>Turn grid snapping off</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionCopy">
<property name="icon">
<iconset theme="edit-copy"/>
</property>
<property name="text">
<string>Copy</string>
</property>
<property name="toolTip">
<string>Copy objects to clipboard</string>
</property>
<property name="shortcut">
<string>Ctrl+C</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionCut">
<property name="icon">
<iconset theme="edit-cut"/>
</property>
<property name="text">
<string>Cut</string>
</property>
<property name="toolTip">
<string>Cut objects into clipboard</string>
</property>
<property name="shortcut">
<string>Ctrl+X</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionPaste">
<property name="icon">
<iconset theme="edit-paste"/>
</property>
<property name="text">
<string>Paste</string>
</property>
<property name="toolTip">
<string>Paste objects from clipboard</string>
</property>
<property name="shortcut">
<string>Ctrl+V</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionPasteInto">
<property name="icon">
<iconset theme="edit-paste"/>
</property>
<property name="text">
<string>Paste Into</string>
</property>
<property name="toolTip">
<string>Paste objects from clipboard into selected object</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+V</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionDelete">
<property name="icon">
<iconset theme="edit-delete"/>
</property>
<property name="text">
<string>Delete Object</string>
</property>
<property name="toolTip">
<string>Delete selected objects</string>
</property>
<property name="shortcut">
<string>Del</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionSaveModel">
<property name="text">
<string>Save Model to File</string>
</property>
<property name="toolTip">
<string>Saves objects to file as XML model</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionInsertModel">
<property name="text">
<string>Insert Model from File</string>
</property>
<property name="toolTip">
<string>Insert model from XML file</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
@ -440,11 +85,6 @@
<extends>QTreeView</extends>
<header>panes/explorerview.h</header>
</customwidget>
<customwidget>
<class>PropertiesView</class>
<extends>QTreeView</extends>
<header>panes/propertiesview.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View file

@ -5,14 +5,11 @@
#include "qimage.h"
#include "qnamespace.h"
#include "qobject.h"
#include "qstringview.h"
#include "qwidget.h"
#include "qmimedata.h"
#include "common.h"
#include <algorithm>
#include <cstdio>
#include <optional>
#include <vector>
#include "objects/base/instance.h"
// https://doc.qt.io/qt-6/qtwidgets-itemviews-simpletreemodel-example.html#testing-the-model
@ -26,7 +23,7 @@ ExplorerModel::ExplorerModel(InstanceRef dataRoot, QWidget *parent)
hierarchyPreUpdateHandler = [&](InstanceRef object, std::optional<InstanceRef> oldParent, std::optional<InstanceRef> newParent) {
if (oldParent.has_value()) {
auto children = oldParent.value()->GetChildren();
size_t idx = std::find(children.begin(), children.end(), object) - children.begin();
size_t idx = std::find(children.begin(), children.end(), object) - children.end();
beginRemoveRows(toIndex(oldParent.value()), idx, idx);
}
@ -60,7 +57,7 @@ QModelIndex ExplorerModel::index(int row, int column, const QModelIndex &parent)
}
QModelIndex ExplorerModel::toIndex(InstanceRef item) {
if (item == rootItem || !item->GetParent().has_value())
if (item == rootItem)
return {};
InstanceRef parentItem = item->GetParent().value();
@ -71,10 +68,6 @@ QModelIndex ExplorerModel::toIndex(InstanceRef item) {
return QModelIndex{};
}
QModelIndex ExplorerModel::ObjectToIndex(InstanceRef item) {
return toIndex(item);
}
QModelIndex ExplorerModel::parent(const QModelIndex &index) const {
if (!index.isValid())
return {};
@ -116,7 +109,6 @@ QVariant ExplorerModel::data(const QModelIndex &index, int role) const {
Instance *item = static_cast<Instance*>(index.internalPointer());
switch (role) {
case Qt::EditRole:
case Qt::DisplayRole:
return QString::fromStdString(item->name);
case Qt::DecorationRole:
@ -144,14 +136,14 @@ Qt::ItemFlags ExplorerModel::flags(const QModelIndex &index) const
//return index.isValid()
// ? QAbstractItemModel::flags(index) : Qt::ItemFlags(Qt::NoItemFlags);
return index.isValid()
? QAbstractItemModel::flags(index) | Qt::ItemIsEditable | (!fromIndex(index)->IsParentLocked() ? Qt::ItemIsDragEnabled : Qt::NoItemFlags) | Qt::ItemIsDropEnabled
? QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled
: Qt::NoItemFlags | Qt::ItemIsDropEnabled;
}
QImage ExplorerModel::iconOf(const InstanceType* type) const {
QImage ExplorerModel::iconOf(InstanceType* type) const {
if (instanceIconCache.count(type->className)) return instanceIconCache[type->className];
const InstanceType* currentClass = type;
InstanceType* currentClass = type;
while (currentClass->explorerIcon.empty()) currentClass = currentClass->super;
QImage icon("assets/icons/" + QString::fromStdString(currentClass->explorerIcon));
@ -160,8 +152,8 @@ QImage ExplorerModel::iconOf(const InstanceType* type) const {
}
bool ExplorerModel::moveRows(const QModelIndex &sourceParentIdx, int sourceRow, int count, const QModelIndex &destinationParentIdx, int destinationChild) {
Instance* sourceParent = sourceParentIdx.isValid() ? static_cast<Instance*>(sourceParentIdx.internalPointer()) : rootItem.get();
Instance* destinationParent = destinationParentIdx.isValid() ? static_cast<Instance*>(destinationParentIdx.internalPointer()) : rootItem.get();
Instance* sourceParent = sourceParentIdx.isValid() ? static_cast<Instance*>(sourceParentIdx.internalPointer()) : workspace.get();
Instance* destinationParent = destinationParentIdx.isValid() ? static_cast<Instance*>(destinationParentIdx.internalPointer()) : workspace.get();
printf("Moved %d from %s\n", count, sourceParent->name.c_str());
@ -178,7 +170,7 @@ bool ExplorerModel::moveRows(const QModelIndex &sourceParentIdx, int sourceRow,
}
bool ExplorerModel::removeRows(int row, int count, const QModelIndex& parentIdx) {
Instance* parent = parentIdx.isValid() ? static_cast<Instance*>(parentIdx.internalPointer()) : rootItem.get();
Instance* parent = parentIdx.isValid() ? static_cast<Instance*>(parentIdx.internalPointer()) : workspace.get();
for (int i = row; i < (row + count); i++) {
//parent->GetChildren()[i]->SetParent(nullptr);
@ -205,56 +197,7 @@ Qt::DropActions ExplorerModel::supportedDropActions() const {
}
InstanceRef ExplorerModel::fromIndex(const QModelIndex index) const {
if (!index.isValid()) return rootItem;
InstanceRef ExplorerModel::fromIndex(const QModelIndex index) {
if (!index.isValid()) return workspace;
return static_cast<Instance*>(index.internalPointer())->shared_from_this();
}
struct DragDropSlot {
std::vector<InstanceRef> instances;
};
bool ExplorerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) {
// if (action != Qt::InternalMove) return;
QByteArray byteData = data->data("application/x-openblocks-instance-pointers");
uintptr_t slotPtr = byteData.toULongLong();
DragDropSlot* slot = (DragDropSlot*)slotPtr;
if (!parent.isValid()) {
delete slot;
return true;
}
InstanceRef parentInst = fromIndex(parent);
for (InstanceRef instance : slot->instances) {
instance->SetParent(parentInst);
}
delete slot;
return true;
}
void ExplorerModel::updateRoot(InstanceRef newRoot) {
beginResetModel();
rootItem = newRoot;
endResetModel();
}
QMimeData* ExplorerModel::mimeData(const QModelIndexList& indexes) const {
// application/x-openblocks-instance-pointers
DragDropSlot* slot = new DragDropSlot();
for (const QModelIndex& index : indexes) {
slot->instances.push_back(fromIndex(index));
}
// uintptr_t ptr = (uintptr_t)&slot;
QMimeData* mimeData = new QMimeData();
mimeData->setData("application/x-openblocks-instance-pointers", QByteArray::number((qulonglong)slot));
return mimeData;
}
QStringList ExplorerModel::mimeTypes() const {
return QStringList("application/x-openblocks-instance-pointers");
}

View file

@ -1,12 +1,18 @@
#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>
// #ifndef EXPLORERMODEL_H
// #define EXPLORERMODEL_H
class ExplorerModel : public QAbstractItemModel {
Q_OBJECT
@ -29,19 +35,13 @@ public:
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override;
bool removeRows(int row, int count, const QModelIndex & parent = QModelIndex()) override;
bool insertRows(int row, int count, const QModelIndex & parent = QModelIndex()) override;
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
QMimeData* mimeData(const QModelIndexList& indexes) const override;
QStringList mimeTypes() const override;
Qt::DropActions supportedDragActions() const override;
Qt::DropActions supportedDropActions() const override;
InstanceRef fromIndex(const QModelIndex index) const;
QModelIndex ObjectToIndex(InstanceRef item);
void updateRoot(InstanceRef newRoot);
InstanceRef fromIndex(const QModelIndex index);
private:
InstanceRef rootItem;
QModelIndex toIndex(InstanceRef item);
QImage iconOf(const InstanceType* type) const;
QImage iconOf(InstanceType* type) const;
};
// #endif

View file

@ -1,26 +1,15 @@
#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>
#define M_mainWindow dynamic_cast<MainWindow*>(window())
#include "qaction.h"
#include "qnamespace.h"
ExplorerView::ExplorerView(QWidget* parent):
QTreeView(parent),
model(ExplorerModel(std::dynamic_pointer_cast<Instance>(dataModel))) {
model(ExplorerModel(std::dynamic_pointer_cast<Instance>(workspace))) {
this->setModel(&model);
// Disabling the root decoration will cause the expand/collapse chevrons to be hidden too, we don't want that
// https://stackoverflow.com/a/4687016/16255372
// this->setRootIsDecorated(false);
// The branches can be customized like this if you want:
// this->setStyleSheet(QString("QTreeView::branch { border: none; }"));
this->setRootIsDecorated(false);
this->setDragDropMode(QAbstractItemView::InternalMove);
this->setSelectionMode(QAbstractItemView::ExtendedSelection);
this->setDragEnabled(true);
@ -28,38 +17,12 @@ ExplorerView::ExplorerView(QWidget* parent):
this->setDropIndicatorShown(true);
this->setContextMenuPolicy(Qt::CustomContextMenu);
// Expand workspace
this->expand(model.ObjectToIndex(workspace()));
connect(this, &QTreeView::customContextMenuRequested, this, [&](const QPoint& point) {
QModelIndex index = this->indexAt(point);
contextMenu.exec(this->viewport()->mapToGlobal(point));
});
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) {
std::vector<InstanceRefWeak> selectedInstances;
selectedInstances.reserve(selected.count()); // This doesn't reserve everything, but should enhance things anyway
for (auto range : selected) {
for (auto index : range.indexes()) {
selectedInstances.push_back(reinterpret_cast<Instance*>(index.internalPointer())->weak_from_this());
}
}
::setSelection(selectedInstances, true);
});
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
// It's from us, ignore it.
if (fromExplorer) return;
this->clearSelection();
for (InstanceRefWeak inst : newSelection) {
if (inst.expired()) continue;
QModelIndex index = this->model.ObjectToIndex(inst.lock());
this->selectionModel()->select(index, QItemSelectionModel::SelectionFlag::Select);
}
});
buildContextMenu();
}
ExplorerView::~ExplorerView() {
@ -68,23 +31,19 @@ ExplorerView::~ExplorerView() {
void ExplorerView::keyPressEvent(QKeyEvent* event) {
switch (event->key()) {
case Qt::Key_Delete:
M_mainWindow->ui->actionDelete->trigger();
actionDelete->trigger();
break;
}
}
void ExplorerView::buildContextMenu() {
contextMenu.addAction(M_mainWindow->ui->actionDelete);
contextMenu.addSeparator();
contextMenu.addAction(M_mainWindow->ui->actionCopy);
contextMenu.addAction(M_mainWindow->ui->actionCut);
contextMenu.addAction(M_mainWindow->ui->actionPaste);
contextMenu.addAction(M_mainWindow->ui->actionPasteInto);
contextMenu.addSeparator();
contextMenu.addAction(M_mainWindow->ui->actionSaveModel);
contextMenu.addAction(M_mainWindow->ui->actionInsertModel);
}
// This will leak memory. Anyway...
contextMenu.addAction(this->actionDelete = new QAction(QIcon("assets/icons/editor/delete"), "Delete"));
void ExplorerView::updateRoot(InstanceRef newRoot) {
model.updateRoot(newRoot);
connect(actionDelete, &QAction::triggered, this, [&]() {
QModelIndexList selectedIndexes = this->selectionModel()->selectedIndexes();
for (QModelIndex index : selectedIndexes) {
model.fromIndex(index)->SetParent(std::nullopt);
}
});
}

View file

@ -17,13 +17,19 @@ class ExplorerView : public QTreeView {
public:
ExplorerView(QWidget* parent = nullptr);
~ExplorerView() override;
void keyPressEvent(QKeyEvent*) override;
// void dropEvent(QDropEvent*) override;
void buildContextMenu();
void updateRoot(InstanceRef newRoot);
void keyPressEvent(QKeyEvent* evt) override;
private:
ExplorerModel model;
QMenu contextMenu;
// TODO: Move these to a separate top-level namespace so these can be
// accessed from multiple locations
QAction* actionDelete;
QAction* actionCopy;
QAction* actionCut;
QAction* actionPaste;
QAction* actionPasteInto;
QAction* actionSelectChildren;
void buildContextMenu();
};

View file

@ -1,118 +0,0 @@
#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.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;
QVariant PropertiesModel::data(const QModelIndex &index, int role) const {
if (!index.isValid())
return {};
std::string propertyName = propertiesList[index.row()];
PropertyMeta meta = selectedItem->GetPropertyMeta(propertyName).value();
switch (role) {
case Qt::EditRole:
case Qt::DisplayRole:
if (index.column() == 0)
return QString::fromStdString(propertyName);
else if (index.column() == 1 && meta.type != &Data::Bool::TYPE) {
return QString::fromStdString(selectedItem->GetPropertyValue(propertyName).value().ToString());
}
return {};
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>() ? Qt::Checked : Qt::Unchecked;
return {};
// case Qt::DecorationRole:
// return iconOf(item->GetClass());
}
return {};
}
bool PropertiesModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (index.column() != 1) return false;
std::string propertyName = propertiesList[index.row()];
PropertyMeta meta = selectedItem->GetPropertyMeta(propertyName).value();
switch (role) {
case Qt::EditRole:
if (!meta.type->fromString)
return false;
selectedItem->SetPropertyValue(propertyName, meta.type->fromString(value.toString().toStdString()));
return true;
case Qt::CheckStateRole:
if (meta.type != &Data::Bool::TYPE)
return false;
selectedItem->SetPropertyValue(propertyName, Data::Bool(value.toBool()));
return true;
}
return false;
}
Qt::ItemFlags PropertiesModel::flags(const QModelIndex &index) const {
if (!index.isValid())
return Qt::NoItemFlags;
if (index.column() == 0)
return Qt::ItemIsEnabled;
std::string propertyName = propertiesList[index.row()];
PropertyMeta meta = selectedItem->GetPropertyMeta(propertyName).value();
if (index.column() == 1) {
if (meta.type == &Data::Bool::TYPE)
return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
else
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
return Qt::NoItemFlags;
};
QVariant PropertiesModel::headerData(int section, Qt::Orientation orientation,
int role) const {
return QString("");
}
QModelIndex PropertiesModel::index(int row, int column,
const QModelIndex &parent) const {
if (!hasIndex(row, column, parent))
return {};
return createIndex(row, column);
}
QModelIndex PropertiesModel::parent(const QModelIndex &index) const {
return {};
}
int PropertiesModel::rowCount(const QModelIndex &parent) const {
return !parent.isValid() ? this->propertiesList.size() : 0;
}
int PropertiesModel::columnCount(const QModelIndex &parent) const {
return 2;
}

View file

@ -1,36 +0,0 @@
#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
public:
Q_DISABLE_COPY_MOVE(PropertiesModel)
explicit PropertiesModel(InstanceRef selectedItem, QWidget *parent = nullptr);
~PropertiesModel() override;
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column,
const QModelIndex &parent = {}) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = {}) const override;
int columnCount(const QModelIndex &parent = {}) const override;
private:
InstanceRef selectedItem;
std::vector<std::string> propertiesList;
};

View file

@ -1,20 +0,0 @@
#include "propertiesview.h"
#include "propertiesmodel.h"
#include "qaction.h"
PropertiesView::PropertiesView(QWidget* parent):
QTreeView(parent) {
this->setStyleSheet(QString("QTreeView::branch { border: none; }"));
}
PropertiesView::~PropertiesView() {
}
void PropertiesView::setSelected(std::optional<InstanceRef> instance) {
if (instance.has_value()) {
this->setModel(new PropertiesModel(instance.value()));
} else {
if (this->model()) delete this->model();
this->setModel(nullptr);
}
}

View file

@ -1,22 +0,0 @@
#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;
class PropertiesView : public QTreeView {
public:
PropertiesView(QWidget* parent = nullptr);
~PropertiesView() override;
void setSelected(std::optional<InstanceRef> instance);
};

View file

@ -0,0 +1,649 @@
/* Generated by wayland-scanner */
#ifndef POINTER_CONSTRAINTS_UNSTABLE_V1_CLIENT_PROTOCOL_H
#define POINTER_CONSTRAINTS_UNSTABLE_V1_CLIENT_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include "wayland-client.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @page page_pointer_constraints_unstable_v1 The pointer_constraints_unstable_v1 protocol
* protocol for constraining pointer motions
*
* @section page_desc_pointer_constraints_unstable_v1 Description
*
* This protocol specifies a set of interfaces used for adding constraints to
* the motion of a pointer. Possible constraints include confining pointer
* motions to a given region, or locking it to its current position.
*
* In order to constrain the pointer, a client must first bind the global
* interface "wp_pointer_constraints" which, if a compositor supports pointer
* constraints, is exposed by the registry. Using the bound global object, the
* client uses the request that corresponds to the type of constraint it wants
* to make. See wp_pointer_constraints for more details.
*
* Warning! The protocol described in this file is experimental and backward
* incompatible changes may be made. Backward compatible changes may be added
* together with the corresponding interface version bump. Backward
* incompatible changes are done by bumping the version number in the protocol
* and interface names and resetting the interface version. Once the protocol
* is to be declared stable, the 'z' prefix and the version number in the
* protocol and interface names are removed and the interface version number is
* reset.
*
* @section page_ifaces_pointer_constraints_unstable_v1 Interfaces
* - @subpage page_iface_zwp_pointer_constraints_v1 - constrain the movement of a pointer
* - @subpage page_iface_zwp_locked_pointer_v1 - receive relative pointer motion events
* - @subpage page_iface_zwp_confined_pointer_v1 - confined pointer object
* @section page_copyright_pointer_constraints_unstable_v1 Copyright
* <pre>
*
* Copyright © 2014 Jonas Ådahl
* Copyright © 2015 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* </pre>
*/
struct wl_pointer;
struct wl_region;
struct wl_surface;
struct zwp_confined_pointer_v1;
struct zwp_locked_pointer_v1;
struct zwp_pointer_constraints_v1;
/**
* @page page_iface_zwp_pointer_constraints_v1 zwp_pointer_constraints_v1
* @section page_iface_zwp_pointer_constraints_v1_desc Description
*
* The global interface exposing pointer constraining functionality. It
* exposes two requests: lock_pointer for locking the pointer to its
* position, and confine_pointer for locking the pointer to a region.
*
* The lock_pointer and confine_pointer requests create the objects
* wp_locked_pointer and wp_confined_pointer respectively, and the client can
* use these objects to interact with the lock.
*
* For any surface, only one lock or confinement may be active across all
* wl_pointer objects of the same seat. If a lock or confinement is requested
* when another lock or confinement is active or requested on the same surface
* and with any of the wl_pointer objects of the same seat, an
* 'already_constrained' error will be raised.
* @section page_iface_zwp_pointer_constraints_v1_api API
* See @ref iface_zwp_pointer_constraints_v1.
*/
/**
* @defgroup iface_zwp_pointer_constraints_v1 The zwp_pointer_constraints_v1 interface
*
* The global interface exposing pointer constraining functionality. It
* exposes two requests: lock_pointer for locking the pointer to its
* position, and confine_pointer for locking the pointer to a region.
*
* The lock_pointer and confine_pointer requests create the objects
* wp_locked_pointer and wp_confined_pointer respectively, and the client can
* use these objects to interact with the lock.
*
* For any surface, only one lock or confinement may be active across all
* wl_pointer objects of the same seat. If a lock or confinement is requested
* when another lock or confinement is active or requested on the same surface
* and with any of the wl_pointer objects of the same seat, an
* 'already_constrained' error will be raised.
*/
extern const struct wl_interface zwp_pointer_constraints_v1_interface;
/**
* @page page_iface_zwp_locked_pointer_v1 zwp_locked_pointer_v1
* @section page_iface_zwp_locked_pointer_v1_desc Description
*
* The wp_locked_pointer interface represents a locked pointer state.
*
* While the lock of this object is active, the wl_pointer objects of the
* associated seat will not emit any wl_pointer.motion events.
*
* This object will send the event 'locked' when the lock is activated.
* Whenever the lock is activated, it is guaranteed that the locked surface
* will already have received pointer focus and that the pointer will be
* within the region passed to the request creating this object.
*
* To unlock the pointer, send the destroy request. This will also destroy
* the wp_locked_pointer object.
*
* If the compositor decides to unlock the pointer the unlocked event is
* sent. See wp_locked_pointer.unlock for details.
*
* When unlocking, the compositor may warp the cursor position to the set
* cursor position hint. If it does, it will not result in any relative
* motion events emitted via wp_relative_pointer.
*
* If the surface the lock was requested on is destroyed and the lock is not
* yet activated, the wp_locked_pointer object is now defunct and must be
* destroyed.
* @section page_iface_zwp_locked_pointer_v1_api API
* See @ref iface_zwp_locked_pointer_v1.
*/
/**
* @defgroup iface_zwp_locked_pointer_v1 The zwp_locked_pointer_v1 interface
*
* The wp_locked_pointer interface represents a locked pointer state.
*
* While the lock of this object is active, the wl_pointer objects of the
* associated seat will not emit any wl_pointer.motion events.
*
* This object will send the event 'locked' when the lock is activated.
* Whenever the lock is activated, it is guaranteed that the locked surface
* will already have received pointer focus and that the pointer will be
* within the region passed to the request creating this object.
*
* To unlock the pointer, send the destroy request. This will also destroy
* the wp_locked_pointer object.
*
* If the compositor decides to unlock the pointer the unlocked event is
* sent. See wp_locked_pointer.unlock for details.
*
* When unlocking, the compositor may warp the cursor position to the set
* cursor position hint. If it does, it will not result in any relative
* motion events emitted via wp_relative_pointer.
*
* If the surface the lock was requested on is destroyed and the lock is not
* yet activated, the wp_locked_pointer object is now defunct and must be
* destroyed.
*/
extern const struct wl_interface zwp_locked_pointer_v1_interface;
/**
* @page page_iface_zwp_confined_pointer_v1 zwp_confined_pointer_v1
* @section page_iface_zwp_confined_pointer_v1_desc Description
*
* The wp_confined_pointer interface represents a confined pointer state.
*
* This object will send the event 'confined' when the confinement is
* activated. Whenever the confinement is activated, it is guaranteed that
* the surface the pointer is confined to will already have received pointer
* focus and that the pointer will be within the region passed to the request
* creating this object. It is up to the compositor to decide whether this
* requires some user interaction and if the pointer will warp to within the
* passed region if outside.
*
* To unconfine the pointer, send the destroy request. This will also destroy
* the wp_confined_pointer object.
*
* If the compositor decides to unconfine the pointer the unconfined event is
* sent. The wp_confined_pointer object is at this point defunct and should
* be destroyed.
* @section page_iface_zwp_confined_pointer_v1_api API
* See @ref iface_zwp_confined_pointer_v1.
*/
/**
* @defgroup iface_zwp_confined_pointer_v1 The zwp_confined_pointer_v1 interface
*
* The wp_confined_pointer interface represents a confined pointer state.
*
* This object will send the event 'confined' when the confinement is
* activated. Whenever the confinement is activated, it is guaranteed that
* the surface the pointer is confined to will already have received pointer
* focus and that the pointer will be within the region passed to the request
* creating this object. It is up to the compositor to decide whether this
* requires some user interaction and if the pointer will warp to within the
* passed region if outside.
*
* To unconfine the pointer, send the destroy request. This will also destroy
* the wp_confined_pointer object.
*
* If the compositor decides to unconfine the pointer the unconfined event is
* sent. The wp_confined_pointer object is at this point defunct and should
* be destroyed.
*/
extern const struct wl_interface zwp_confined_pointer_v1_interface;
#ifndef ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM
#define ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM
/**
* @ingroup iface_zwp_pointer_constraints_v1
* wp_pointer_constraints error values
*
* These errors can be emitted in response to wp_pointer_constraints
* requests.
*/
enum zwp_pointer_constraints_v1_error {
/**
* pointer constraint already requested on that surface
*/
ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED = 1,
};
#endif /* ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM */
#ifndef ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM
#define ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM
/**
* @ingroup iface_zwp_pointer_constraints_v1
* the pointer constraint may reactivate
*
* A persistent pointer constraint may again reactivate once it has
* been deactivated. See the corresponding deactivation event
* (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for
* details.
*/
enum zwp_pointer_constraints_v1_lifetime {
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT = 1,
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT = 2,
};
#endif /* ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM */
#define ZWP_POINTER_CONSTRAINTS_V1_DESTROY 0
#define ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER 1
#define ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER 2
/**
* @ingroup iface_zwp_pointer_constraints_v1
*/
#define ZWP_POINTER_CONSTRAINTS_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zwp_pointer_constraints_v1
*/
#define ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER_SINCE_VERSION 1
/**
* @ingroup iface_zwp_pointer_constraints_v1
*/
#define ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER_SINCE_VERSION 1
/** @ingroup iface_zwp_pointer_constraints_v1 */
static inline void
zwp_pointer_constraints_v1_set_user_data(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwp_pointer_constraints_v1, user_data);
}
/** @ingroup iface_zwp_pointer_constraints_v1 */
static inline void *
zwp_pointer_constraints_v1_get_user_data(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwp_pointer_constraints_v1);
}
static inline uint32_t
zwp_pointer_constraints_v1_get_version(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zwp_pointer_constraints_v1);
}
/**
* @ingroup iface_zwp_pointer_constraints_v1
*
* Used by the client to notify the server that it will no longer use this
* pointer constraints object.
*/
static inline void
zwp_pointer_constraints_v1_destroy(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1)
{
wl_proxy_marshal((struct wl_proxy *) zwp_pointer_constraints_v1,
ZWP_POINTER_CONSTRAINTS_V1_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zwp_pointer_constraints_v1);
}
/**
* @ingroup iface_zwp_pointer_constraints_v1
*
* The lock_pointer request lets the client request to disable movements of
* the virtual pointer (i.e. the cursor), effectively locking the pointer
* to a position. This request may not take effect immediately; in the
* future, when the compositor deems implementation-specific constraints
* are satisfied, the pointer lock will be activated and the compositor
* sends a locked event.
*
* The protocol provides no guarantee that the constraints are ever
* satisfied, and does not require the compositor to send an error if the
* constraints cannot ever be satisfied. It is thus possible to request a
* lock that will never activate.
*
* There may not be another pointer constraint of any kind requested or
* active on the surface for any of the wl_pointer objects of the seat of
* the passed pointer when requesting a lock. If there is, an error will be
* raised. See general pointer lock documentation for more details.
*
* The intersection of the region passed with this request and the input
* region of the surface is used to determine where the pointer must be
* in order for the lock to activate. It is up to the compositor whether to
* warp the pointer or require some kind of user interaction for the lock
* to activate. If the region is null the surface input region is used.
*
* A surface may receive pointer focus without the lock being activated.
*
* The request creates a new object wp_locked_pointer which is used to
* interact with the lock as well as receive updates about its state. See
* the the description of wp_locked_pointer for further information.
*
* Note that while a pointer is locked, the wl_pointer objects of the
* corresponding seat will not emit any wl_pointer.motion events, but
* relative motion events will still be emitted via wp_relative_pointer
* objects of the same seat. wl_pointer.axis and wl_pointer.button events
* are unaffected.
*/
static inline struct zwp_locked_pointer_v1 *
zwp_pointer_constraints_v1_lock_pointer(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1, struct wl_surface *surface, struct wl_pointer *pointer, struct wl_region *region, uint32_t lifetime)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) zwp_pointer_constraints_v1,
ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER, &zwp_locked_pointer_v1_interface, NULL, surface, pointer, region, lifetime);
return (struct zwp_locked_pointer_v1 *) id;
}
/**
* @ingroup iface_zwp_pointer_constraints_v1
*
* The confine_pointer request lets the client request to confine the
* pointer cursor to a given region. This request may not take effect
* immediately; in the future, when the compositor deems implementation-
* specific constraints are satisfied, the pointer confinement will be
* activated and the compositor sends a confined event.
*
* The intersection of the region passed with this request and the input
* region of the surface is used to determine where the pointer must be
* in order for the confinement to activate. It is up to the compositor
* whether to warp the pointer or require some kind of user interaction for
* the confinement to activate. If the region is null the surface input
* region is used.
*
* The request will create a new object wp_confined_pointer which is used
* to interact with the confinement as well as receive updates about its
* state. See the the description of wp_confined_pointer for further
* information.
*/
static inline struct zwp_confined_pointer_v1 *
zwp_pointer_constraints_v1_confine_pointer(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1, struct wl_surface *surface, struct wl_pointer *pointer, struct wl_region *region, uint32_t lifetime)
{
struct wl_proxy *id;
id = wl_proxy_marshal_constructor((struct wl_proxy *) zwp_pointer_constraints_v1,
ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER, &zwp_confined_pointer_v1_interface, NULL, surface, pointer, region, lifetime);
return (struct zwp_confined_pointer_v1 *) id;
}
/**
* @ingroup iface_zwp_locked_pointer_v1
* @struct zwp_locked_pointer_v1_listener
*/
struct zwp_locked_pointer_v1_listener {
/**
* lock activation event
*
* Notification that the pointer lock of the seat's pointer is
* activated.
*/
void (*locked)(void *data,
struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1);
/**
* lock deactivation event
*
* Notification that the pointer lock of the seat's pointer is no
* longer active. If this is a oneshot pointer lock (see
* wp_pointer_constraints.lifetime) this object is now defunct and
* should be destroyed. If this is a persistent pointer lock (see
* wp_pointer_constraints.lifetime) this pointer lock may again
* reactivate in the future.
*/
void (*unlocked)(void *data,
struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1);
};
/**
* @ingroup iface_zwp_locked_pointer_v1
*/
static inline int
zwp_locked_pointer_v1_add_listener(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1,
const struct zwp_locked_pointer_v1_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zwp_locked_pointer_v1,
(void (**)(void)) listener, data);
}
#define ZWP_LOCKED_POINTER_V1_DESTROY 0
#define ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT 1
#define ZWP_LOCKED_POINTER_V1_SET_REGION 2
/**
* @ingroup iface_zwp_locked_pointer_v1
*/
#define ZWP_LOCKED_POINTER_V1_LOCKED_SINCE_VERSION 1
/**
* @ingroup iface_zwp_locked_pointer_v1
*/
#define ZWP_LOCKED_POINTER_V1_UNLOCKED_SINCE_VERSION 1
/**
* @ingroup iface_zwp_locked_pointer_v1
*/
#define ZWP_LOCKED_POINTER_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zwp_locked_pointer_v1
*/
#define ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT_SINCE_VERSION 1
/**
* @ingroup iface_zwp_locked_pointer_v1
*/
#define ZWP_LOCKED_POINTER_V1_SET_REGION_SINCE_VERSION 1
/** @ingroup iface_zwp_locked_pointer_v1 */
static inline void
zwp_locked_pointer_v1_set_user_data(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwp_locked_pointer_v1, user_data);
}
/** @ingroup iface_zwp_locked_pointer_v1 */
static inline void *
zwp_locked_pointer_v1_get_user_data(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwp_locked_pointer_v1);
}
static inline uint32_t
zwp_locked_pointer_v1_get_version(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zwp_locked_pointer_v1);
}
/**
* @ingroup iface_zwp_locked_pointer_v1
*
* Destroy the locked pointer object. If applicable, the compositor will
* unlock the pointer.
*/
static inline void
zwp_locked_pointer_v1_destroy(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1)
{
wl_proxy_marshal((struct wl_proxy *) zwp_locked_pointer_v1,
ZWP_LOCKED_POINTER_V1_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zwp_locked_pointer_v1);
}
/**
* @ingroup iface_zwp_locked_pointer_v1
*
* Set the cursor position hint relative to the top left corner of the
* surface.
*
* If the client is drawing its own cursor, it should update the position
* hint to the position of its own cursor. A compositor may use this
* information to warp the pointer upon unlock in order to avoid pointer
* jumps.
*
* The cursor position hint is double buffered. The new hint will only take
* effect when the associated surface gets it pending state applied. See
* wl_surface.commit for details.
*/
static inline void
zwp_locked_pointer_v1_set_cursor_position_hint(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1, wl_fixed_t surface_x, wl_fixed_t surface_y)
{
wl_proxy_marshal((struct wl_proxy *) zwp_locked_pointer_v1,
ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT, surface_x, surface_y);
}
/**
* @ingroup iface_zwp_locked_pointer_v1
*
* Set a new region used to lock the pointer.
*
* The new lock region is double-buffered. The new lock region will
* only take effect when the associated surface gets its pending state
* applied. See wl_surface.commit for details.
*
* For details about the lock region, see wp_locked_pointer.
*/
static inline void
zwp_locked_pointer_v1_set_region(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1, struct wl_region *region)
{
wl_proxy_marshal((struct wl_proxy *) zwp_locked_pointer_v1,
ZWP_LOCKED_POINTER_V1_SET_REGION, region);
}
/**
* @ingroup iface_zwp_confined_pointer_v1
* @struct zwp_confined_pointer_v1_listener
*/
struct zwp_confined_pointer_v1_listener {
/**
* pointer confined
*
* Notification that the pointer confinement of the seat's
* pointer is activated.
*/
void (*confined)(void *data,
struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1);
/**
* pointer unconfined
*
* Notification that the pointer confinement of the seat's
* pointer is no longer active. If this is a oneshot pointer
* confinement (see wp_pointer_constraints.lifetime) this object is
* now defunct and should be destroyed. If this is a persistent
* pointer confinement (see wp_pointer_constraints.lifetime) this
* pointer confinement may again reactivate in the future.
*/
void (*unconfined)(void *data,
struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1);
};
/**
* @ingroup iface_zwp_confined_pointer_v1
*/
static inline int
zwp_confined_pointer_v1_add_listener(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1,
const struct zwp_confined_pointer_v1_listener *listener, void *data)
{
return wl_proxy_add_listener((struct wl_proxy *) zwp_confined_pointer_v1,
(void (**)(void)) listener, data);
}
#define ZWP_CONFINED_POINTER_V1_DESTROY 0
#define ZWP_CONFINED_POINTER_V1_SET_REGION 1
/**
* @ingroup iface_zwp_confined_pointer_v1
*/
#define ZWP_CONFINED_POINTER_V1_CONFINED_SINCE_VERSION 1
/**
* @ingroup iface_zwp_confined_pointer_v1
*/
#define ZWP_CONFINED_POINTER_V1_UNCONFINED_SINCE_VERSION 1
/**
* @ingroup iface_zwp_confined_pointer_v1
*/
#define ZWP_CONFINED_POINTER_V1_DESTROY_SINCE_VERSION 1
/**
* @ingroup iface_zwp_confined_pointer_v1
*/
#define ZWP_CONFINED_POINTER_V1_SET_REGION_SINCE_VERSION 1
/** @ingroup iface_zwp_confined_pointer_v1 */
static inline void
zwp_confined_pointer_v1_set_user_data(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1, void *user_data)
{
wl_proxy_set_user_data((struct wl_proxy *) zwp_confined_pointer_v1, user_data);
}
/** @ingroup iface_zwp_confined_pointer_v1 */
static inline void *
zwp_confined_pointer_v1_get_user_data(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1)
{
return wl_proxy_get_user_data((struct wl_proxy *) zwp_confined_pointer_v1);
}
static inline uint32_t
zwp_confined_pointer_v1_get_version(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1)
{
return wl_proxy_get_version((struct wl_proxy *) zwp_confined_pointer_v1);
}
/**
* @ingroup iface_zwp_confined_pointer_v1
*
* Destroy the confined pointer object. If applicable, the compositor will
* unconfine the pointer.
*/
static inline void
zwp_confined_pointer_v1_destroy(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1)
{
wl_proxy_marshal((struct wl_proxy *) zwp_confined_pointer_v1,
ZWP_CONFINED_POINTER_V1_DESTROY);
wl_proxy_destroy((struct wl_proxy *) zwp_confined_pointer_v1);
}
/**
* @ingroup iface_zwp_confined_pointer_v1
*
* Set a new region used to confine the pointer.
*
* The new confine region is double-buffered. The new confine region will
* only take effect when the associated surface gets its pending state
* applied. See wl_surface.commit for details.
*
* If the confinement is active when the new confinement region is applied
* and the pointer ends up outside of newly applied region, the pointer may
* warped to a position within the new confinement region. If warped, a
* wl_pointer.motion event will be emitted, but no
* wp_relative_pointer.relative_motion event.
*
* The compositor may also, instead of using the new region, unconfine the
* pointer.
*
* For details about the confine region, see wp_confined_pointer.
*/
static inline void
zwp_confined_pointer_v1_set_region(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1, struct wl_region *region)
{
wl_proxy_marshal((struct wl_proxy *) zwp_confined_pointer_v1,
ZWP_CONFINED_POINTER_V1_SET_REGION, region);
}
#ifdef __cplusplus
}
#endif
#endif

File diff suppressed because it is too large Load diff

4
run.sh
View file

@ -1,9 +1,7 @@
if [ $# -eq 0 ] || ([ "$1" != "editor" ] && [ "$1" != "client" ]); then echo "Argument missing, must be 'client' or 'editor'"; exit; fi
[ "$2" = "-debug" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=Debug
[ "$2" = "-release" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=Release
[ "$2" = "-reldbg" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=RelWithDebInfo
[ "$3" = "-gdb" ] && PRE_COMMAND="gdb -ex run "
cmake -Bbuild $CMAKE_OPTS . && (cd build; cmake --build .; cd ..) && $PRE_COMMAND ./build/bin/$1
cmake $CMAKE_OPTS . && cmake --build . && $PRE_COMMAND ./bin/$1

View file

@ -1,5 +1,4 @@
#include "camera.h"
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
Camera::Camera(glm::vec3 initalPosition) {
@ -54,19 +53,3 @@ void Camera::processRotation(float deltaX, float deltaY) {
if(pitch < -89.0f)
pitch = -89.0f;
}
glm::vec3 Camera::getScreenDirection(glm::vec2 screenPos, glm::vec2 screenSize) {
// VVV Thank goodness for this person's answer
// https://stackoverflow.com/a/30005258/16255372
// glm::vec3 worldPos = camera.cameraPos + glm::vec3(glm::vec4(float(position.x()) / width() - 0.5f, float(position.y()) / height() - 0.5f, 0, 0) * camera.getLookAt());
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)screenSize.x / (float)screenSize.y, 0.1f, 100.0f);
glm::mat4 view = glm::lookAt(glm::vec3(0), this->cameraFront, this->cameraUp);
glm::mat4 inverseViewport = glm::inverse(projection * view);
glm::vec2 ndc = glm::vec2(screenPos.x / screenSize.x * 2.f - 1.f, -screenPos.y / screenSize.y * 2.f + 1.f);
glm::vec4 world = glm::normalize(inverseViewport * glm::vec4(ndc, 1, 1));
//glm::vec3 flat = glm::vec3(world) / world.w; // https://stackoverflow.com/a/68870587/16255372
return glm::vec3(world);
}

View file

@ -24,8 +24,7 @@ public:
Camera(glm::vec3 initialPosition);
glm::mat4 getLookAt();
/** Converts a set of screen coords to a direction from the camera's pos */
glm::vec3 getScreenDirection(glm::vec2 screenPos, glm::vec2 screenSize);
void processRotation(float deltaX, float deltaY);
void processMovement(Direction direction, float deltaTime);
};

10
src/common.cpp Normal file
View file

@ -0,0 +1,10 @@
// TEMPORARY COMMON DATA FOR DIFFERENT INTERNAL COMPONENTS
#include "common.h"
#include <memory>
Camera camera(glm::vec3(0.0, 0.0, 3.0));
//std::vector<Part> parts;
std::shared_ptr<Workspace> workspace = Workspace::New();
std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;

View file

@ -1,6 +1,4 @@
#pragma once
#include "objects/base/instance.h"
#include "objects/handles.h"
#include "objects/workspace.h"
#include "camera.h"
#include <functional>
@ -10,17 +8,10 @@ class Instance;
// typedef std::function<void(std::shared_ptr<Instance> element, std::optional<std::shared_ptr<Instance>> newParent)> HierarchyUpdateHandler;
typedef std::function<void(InstanceRef object, std::optional<InstanceRef> oldParent, std::optional<InstanceRef> newParent)> HierarchyPreUpdateHandler;
typedef std::function<void(InstanceRef object, std::optional<InstanceRef> oldParent, std::optional<InstanceRef> newParent)> HierarchyPostUpdateHandler;
typedef std::function<void(std::vector<InstanceRefWeak> oldSelection, std::vector<InstanceRefWeak> newSelection, bool fromExplorer)> SelectionUpdateHandler;
// TEMPORARY COMMON DATA FOR VARIOUS INTERNAL COMPONENTS
// TEMPORARY COMMON DATA FOR DIFFERENT INTERNAL COMPONENTS
extern Camera camera;
extern std::shared_ptr<DataModel> dataModel;
inline std::shared_ptr<Workspace> workspace() { return std::dynamic_pointer_cast<Workspace>(dataModel->services["Workspace"]); }
extern std::shared_ptr<Workspace> workspace;
extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
extern std::shared_ptr<Handles> editorToolHandles;
void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer = false);
const std::vector<InstanceRefWeak> getSelection();
void addSelectionListener(SelectionUpdateHandler handler);

View file

@ -0,0 +1,11 @@
#include "primitives.h"
#define IMPL_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE) CLASS_NAME::CLASS_NAME(WRAPPED_TYPE in) : wrapped(in) {}\
CLASS_NAME::operator WRAPPED_TYPE() { return wrapped; }
VoidData::VoidData() {};
IMPL_WRAPPER_CLASS(BoolData, bool)
IMPL_WRAPPER_CLASS(IntData, int)
IMPL_WRAPPER_CLASS(FloatData, float)
IMPL_WRAPPER_CLASS(StringData, std::string)

View file

@ -0,0 +1,22 @@
#pragma once
#include <string>
#define DEF_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE) class CLASS_NAME {\
WRAPPED_TYPE wrapped;\
public:\
CLASS_NAME(WRAPPED_TYPE); \
operator WRAPPED_TYPE(); \
};
class VoidData {
public:
VoidData();
};
DEF_WRAPPER_CLASS(BoolData, bool)
DEF_WRAPPER_CLASS(IntData, int)
DEF_WRAPPER_CLASS(FloatData, float)
DEF_WRAPPER_CLASS(StringData, std::string)
#undef DEF_WRAPPER_CLASS

View file

@ -1,3 +1,4 @@
#pragma once
#include "base/metadata.h"
#include "base/instance.h"

View file

@ -0,0 +1,58 @@
#include "instance.h"
#include "../../common.h"
#include <algorithm>
#include <memory>
#include <optional>
// Static so that this variable name is "local" to this source file
static InstanceType TYPE_ {
.super = NULL,
.className = "Instance",
.constructor = NULL, // Instance is abstract and therefore not creatable
.explorerIcon = "instance",
};
InstanceType* Instance::TYPE = &TYPE_;
// Instance is abstract, so it should not implement GetClass directly
// InstanceType* Instance::GetClass() {
// return &TYPE_;
// }
Instance::Instance(InstanceType* type) {
this->name = type->className;
}
Instance::~Instance () {
}
void Instance::SetParent(std::optional<std::shared_ptr<Instance>> newParent) {
auto lastParent = GetParent();
if (hierarchyPreUpdateHandler.has_value()) hierarchyPreUpdateHandler.value()(this->shared_from_this(), lastParent, newParent);
// If we currently have a parent, remove ourselves from it before adding ourselves to the new one
if (this->parent.has_value() && !this->parent.value().expired()) {
auto oldParent = this->parent.value().lock();
oldParent->children.erase(std::find(oldParent->children.begin(), oldParent->children.end(), this->shared_from_this()));
}
// Add ourselves to the new parent
if (newParent.has_value()) {
newParent.value()->children.push_back(this->shared_from_this());
}
this->parent = newParent;
// TODO: Add code for sending signals for parent updates
// TODO: Yeahhh maybe this isn't the best way of doing this?
if (hierarchyPostUpdateHandler.has_value()) hierarchyPostUpdateHandler.value()(this->shared_from_this(), lastParent, newParent);
this->OnParentUpdated(lastParent, newParent);
}
std::optional<std::shared_ptr<Instance>> Instance::GetParent() {
if (!parent.has_value()) return std::nullopt;
if (parent.value().expired()) return std::nullopt;
return parent.value().lock();
}
void Instance::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
// Empty stub
}

View file

@ -0,0 +1,44 @@
#pragma once
#include "metadata.h"
#include <memory>
#include <optional>
#include <string>
// Struct describing information about an instance
struct InstanceType {
InstanceType* super; // May be null
std::string className;
InstanceConstructor constructor;
std::string explorerIcon = "";
};
// Base class for all instances in the data model
// Note: enable_shared_from_this HAS to be public or else its field will not be populated
// Maybe this could be replaced with a friendship? But that seems unnecessary.
// https://stackoverflow.com/q/56415222/16255372
class Instance : public std::enable_shared_from_this<Instance> {
private:
std::optional<std::weak_ptr<Instance>> parent;
std::vector<std::shared_ptr<Instance>> children;
protected:
Instance(InstanceType*);
virtual ~Instance();
virtual void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent);
public:
static InstanceType* TYPE;
std::string name;
// Instance is abstract, so it should not implement GetClass directly
virtual InstanceType* GetClass() = 0;
void SetParent(std::optional<std::shared_ptr<Instance>> newParent);
std::optional<std::shared_ptr<Instance>> GetParent();
inline const std::vector<std::shared_ptr<Instance>> GetChildren() { return children; }
// Utility functions
inline void AddChild(std::shared_ptr<Instance> object) { object->SetParent(this->shared_from_this()); }
};
typedef std::shared_ptr<Instance> InstanceRef;
typedef std::weak_ptr<Instance> InstanceRefWeak;

View file

@ -0,0 +1,60 @@
#pragma once
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include <map>
#include <vector>
#include "../../datatype.h"
class Instance;
typedef std::shared_ptr<Instance>(*InstanceConstructor)();
const uint INST_NOT_CREATABLE = 1;
// const uint INST_SINGLETON = 2;
typedef uint InstanceClassFlags;
struct InstanceClassDescriptor {
std::string className;
InstanceConstructor constructor;
InstanceClassFlags flags;
};
//
const uint PROP_READONLY = 1;
const uint PROP_NOSCRIPT = 2; // Cannot be read or written to by unpriveleged scripts
const uint PROP_NOSAVE = 4; // Property should not be serialized by the engine
const uint PROP_CLIENTONLY = 8; // Only accessible by the client
typedef uint PropertyFlags;
typedef DataValue(*InstancePropertyGetter)();
typedef void(*InstancePropertySetter)(DataValue);
// Properties may either have a backing field directly accessed by Instance,
// or, for more granular control may define a getter (and setter if writable).
struct MemberPropertyDescriptor {
DataType dataType;
PropertyFlags flags;
std::optional<DataValue*> backingField;
std::optional<InstancePropertyGetter> getter;
std::optional<InstancePropertySetter> setter;
};
//
typedef DataValue(*InstanceMethodHandler)(std::vector<DataValue>);
struct MemberMethodDescriptor {
DataType returnType;
// This may need to be converted into a vector in the future for overloaded methods
std::vector<DataType> parameters;
InstanceMethodHandler handler;
};
// TODO: Add MemberCallbackDescriptor
typedef std::map<std::string, std::variant<MemberPropertyDescriptor, MemberMethodDescriptor /*, MemberEventDescriptor */ /* , MemberCallbackDescriptor */>> InstanceMemberTable;

37
src/objects/part.cpp Normal file
View file

@ -0,0 +1,37 @@
#include "part.h"
#include "base/instance.h"
static InstanceType TYPE_ {
.super = Instance::TYPE,
.className = "Part",
.constructor = &Part::CreateGeneric,
.explorerIcon = "part",
};
InstanceType* Part::TYPE = &TYPE_;
InstanceType* Part::GetClass() {
return &TYPE_;
}
Part::Part(): Instance(&TYPE_) {
}
Part::Part(PartConstructParams params): Instance(&TYPE_), position(params.position), rotation(params.rotation),
scale(params.scale), material(params.material), anchored(params.anchored) {
}
// 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() {
world->destroyRigidBody(rigidBody);
Instance::~Instance();
}
void Part::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
if (this->rigidBody)
this->rigidBody->setIsActive(newParent.has_value());
// TODO: Sleeping bodies that touch this one also need to be updated
}

View file

@ -5,9 +5,6 @@
#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;
@ -16,8 +13,8 @@ namespace rp = reactphysics3d;
struct PartConstructParams {
glm::vec3 position;
glm::quat rotation = glm::identity<glm::quat>();
glm::vec3 size;
Data::Color3 color;
glm::vec3 scale;
Material material;
bool anchored = false;
};
@ -25,15 +22,14 @@ 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 onUpdated(std::string);
public:
const static InstanceType TYPE;
static InstanceType* TYPE;
Data::CFrame cframe;
glm::vec3 size;
Data::Color3 color;
float transparency = 0.f;
bool selected = false;
// TODO: Switch these over to our dedicated datatypes
glm::vec3 position;
glm::quat rotation = glm::identity<glm::quat>();
glm::vec3 scale;
Material material;
bool anchored = false;
rp::RigidBody* rigidBody = nullptr;
@ -45,7 +41,5 @@ public:
static inline std::shared_ptr<Part> New() { return std::make_shared<Part>(); };
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(); }
virtual InstanceType* GetClass() override;
};

16
src/objects/workspace.cpp Normal file
View file

@ -0,0 +1,16 @@
#include "workspace.h"
static InstanceType TYPE_ {
.super = Instance::TYPE,
.className = "Workspace",
.constructor = &Workspace::Create,
};
InstanceType* Workspace::TYPE = &TYPE_;
InstanceType* Workspace::GetClass() {
return &TYPE_;
}
Workspace::Workspace(): Instance(&TYPE_) {
}

16
src/objects/workspace.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include "base.h"
#include <memory>
class Workspace : public Instance {
//private:
public:
static InstanceType* TYPE;
Workspace();
static inline std::shared_ptr<Workspace> New() { return std::make_shared<Workspace>(); };
static inline InstanceRef Create() { return std::make_shared<Workspace>(); };
virtual InstanceType* GetClass() override;
};

View file

@ -0,0 +1,76 @@
#include <cstdio>
#include <glm/ext/matrix_float3x3.hpp>
#include <glm/gtc/quaternion.hpp>
#include <memory>
#include <reactphysics3d/collision/shapes/BoxShape.h>
#include <reactphysics3d/collision/shapes/CollisionShape.h>
#include <reactphysics3d/components/RigidBodyComponents.h>
#include <reactphysics3d/engine/EventListener.h>
#include <reactphysics3d/mathematics/Quaternion.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 "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() {
world = physicsCommon.createPhysicsWorld();
world->setGravity(rp::Vector3(0, -196.2, 0));
}
void syncPartPhysics(std::shared_ptr<Part> part) {
glm::mat4 rotMat = glm::mat4(1.0f);
rp::Transform transform(glmToRp(part->position), glmToRp(part->rotation));
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)));
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);
world->setEventListener(&eventListener);
}
void physicsStep(float deltaTime) {
// Step the simulation a few steps
world->update(deltaTime / 2);
// 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 (InstanceRef obj : workspace->GetChildren()) {
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());
}
}

8
src/physics/simulation.h Normal file
View file

@ -0,0 +1,8 @@
#pragma once
#include "../objects/part.h"
#include <memory>
void simulationInit();
void syncPartPhysics(std::shared_ptr<Part> part);
void physicsStep(float deltaTime);

View file

@ -1,13 +1,9 @@
#pragma once
#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>
#include <glm/ext.hpp>
#include "objects/part.h"
namespace rp = reactphysics3d;
@ -19,10 +15,6 @@ 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);
}
@ -30,10 +22,3 @@ inline glm::vec3 rpToGlm(rp::Vector3 vec) {
inline glm::quat rpToGlm(rp::Quaternion quat) {
return glm::quat(quat.w, quat.x, quat.y, quat.z);
}
// Make this std::optional
inline std::shared_ptr<Part> partFromBody(rp::Body* body) {
Part* raw = reinterpret_cast<Part*>(body->getUserData());
std::shared_ptr<Part> shared = std::dynamic_pointer_cast<Part>(raw->shared_from_this());
return shared;
}

View file

@ -0,0 +1,47 @@
#include "defaultmeshes.h"
Mesh* CUBE_MESH;
void initMeshes() {
static float vertices[] = {
// positions // normals // texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
};
CUBE_MESH = new Mesh(36, vertices);
}

View file

@ -2,7 +2,5 @@
#include "mesh.h"
extern Mesh* CUBE_MESH;
extern Mesh* SPHERE_MESH;
extern Mesh* ARROW_MESH;
void initMeshes();

View file

@ -3,7 +3,7 @@
#include "mesh.h"
Mesh::Mesh(int vertexCount, float *vertices) : vertexCount(vertexCount) {
Mesh::Mesh(int vertexCount, float *vertices) {
// Generate buffers
glGenBuffers(1, &VBO);
glGenVertexArrays(1, &VAO);

View file

@ -4,8 +4,6 @@ class Mesh {
unsigned int VBO, VAO;
public:
int vertexCount;
Mesh(int vertexCount, float* vertices);
~Mesh();
void bind();

148
src/rendering/renderer.cpp Normal file
View file

@ -0,0 +1,148 @@
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <GL/gl.h>
#include <cstdio>
#include <glm/ext.hpp>
#include <glm/ext/matrix_float4x4.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/glm.hpp>
#include <glm/trigonometric.hpp>
#include <memory>
#include <vector>
#include "shader.h"
#include "mesh.h"
#include "defaultmeshes.h"
#include "../camera.h"
#include "../common.h"
#include "../objects/part.h"
#include "skybox.h"
#include "surface.h"
#include "texture3d.h"
#include "renderer.h"
Shader *shader = NULL;
Shader *skyboxShader = NULL;
extern Camera camera;
Skybox* skyboxTexture = NULL;
Texture3D* studsTexture = NULL;
void renderInit(GLFWwindow* window) {
glViewport(0, 0, 1200, 900);
initMeshes();
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
skyboxTexture = new Skybox({
"assets/textures/skybox/null_plainsky512_lf.jpg",
"assets/textures/skybox/null_plainsky512_rt.jpg",
"assets/textures/skybox/null_plainsky512_up.jpg",
"assets/textures/skybox/null_plainsky512_dn.jpg",
"assets/textures/skybox/null_plainsky512_ft.jpg",
"assets/textures/skybox/null_plainsky512_bk.jpg",
}, GL_RGB);
studsTexture = new Texture3D("assets/textures/studs.png", 128, 128, 6, GL_RGBA);
// Compile shader
shader = new Shader("assets/shaders/phong.vs", "assets/shaders/phong.fs");
skyboxShader = new Shader("assets/shaders/skybox.vs", "assets/shaders/skybox.fs");
}
void renderParts() {
glDepthMask(GL_TRUE);
// Use shader
shader->use();
// shader->set("objectColor", glm::vec3(1.0f, 0.5f, 0.31f));
// shader->set("lightColor", glm::vec3(1.0f, 1.0f, 1.0f));
// view/projection transformations
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)1200 / (float)900, 0.1f, 100.0f);
glm::mat4 view = camera.getLookAt();
shader->set("projection", projection);
shader->set("view", view);
// shader->set("material", Material {
// // .ambient = glm::vec3(1.0f, 0.5f, 0.31f),
// .diffuse = glm::vec3(0.639216f, 0.635294f, 0.647059f),
// .specular = glm::vec3(0.5f, 0.5f, 0.5f),
// .shininess = 16.0f,
// });
shader->set("sunLight", DirLight {
.direction = glm::vec3(-0.2f, -1.0f, -0.3f),
.ambient = glm::vec3(0.2f, 0.2f, 0.2f),
.diffuse = glm::vec3(0.5f, 0.5f, 0.5f),
.specular = glm::vec3(1.0f, 1.0f, 1.0f),
});
shader->set("numPointLights", 0);
// shader->set("pointLights[0]", PointLight {
// .position = lightPos,
// .ambient = glm::vec3(0.4f, 0.4f, 0.4f),
// .diffuse = glm::vec3(1.0f, 1.0f, 1.0f),
// .specular = glm::vec3(1.0f, 1.0f, 1.0f),
// .constant = 1.0,
// .linear = 0.9,
// .quadratic = 0.32,
// });
studsTexture->activate(0);
shader->set("studs", 0);
// shader->set("surfaces[1]", SurfaceStuds);
shader->set("surfaces[1]", SurfaceStuds);
shader->set("surfaces[4]", SurfaceInlets);
// Pre-calculate the normal matrix for the shader
// Pass in the camera position
shader->set("viewPos", camera.cameraPos);
// TODO: Same as todo in src/physics/simulation.cpp
for (InstanceRef inst : workspace->GetChildren()) {
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);
shader->set("model", model);
shader->set("material", part->material);
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
shader->set("normalMatrix", normalMatrix);
shader->set("texScale", part->scale);
CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, 36);
}
}
void renderSkyBox() {
glDepthMask(GL_FALSE);
skyboxShader->use();
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)1200 / (float)900, 0.1f, 100.0f);
// Remove translation component of view, making us always at (0, 0, 0)
glm::mat4 view = glm::mat4(glm::mat3(camera.getLookAt()));
skyboxShader->set("projection", projection);
skyboxShader->set("view", view);
skyboxShader->set("uTexture", 0);
CUBE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, 36);
}
void render(GLFWwindow* window) {
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderSkyBox();
renderParts();
}

5
src/rendering/renderer.h Normal file
View file

@ -0,0 +1,5 @@
#pragma once
#include <GLFW/glfw3.h>
void renderInit(GLFWwindow* window);
void render(GLFWwindow* window);

Some files were not shown because too many files have changed in this diff Show more