Compare commits
No commits in common. "master" and "prototype_/roblox-looks" have entirely different histories.
master
...
prototype_
19
.gitignore
vendored
|
@ -1,14 +1,5 @@
|
|||
/bin/
|
||||
/lib/
|
||||
/build/
|
||||
|
||||
# Qt
|
||||
/*.pro.user*
|
||||
/CMakeLists.txt.user*
|
||||
|
||||
# Clangd
|
||||
/compile_commands.json
|
||||
/.cache
|
||||
|
||||
# Gdb
|
||||
/.gdb_history
|
||||
bin/
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
CMakeCache.txt
|
||||
Makefile
|
||||
|
|
16
.vscode/launch.json
vendored
|
@ -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}",
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,14 +1,35 @@
|
|||
cmake_minimum_required(VERSION 3.31..)
|
||||
cmake_minimum_required(VERSION 3.5.0)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
project(openblocks VERSION 0.1.0)
|
||||
project(GLTest 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_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_executable(${PROJECT_NAME} ${SOURCES})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "gltest")
|
||||
target_link_libraries(${PROJECT_NAME} ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D)
|
|
@ -1,22 +0,0 @@
|
|||
Silk icon set 1.3
|
||||
|
||||
_________________________________________
|
||||
Mark James
|
||||
http://www.famfamfam.com/lab/icons/silk/
|
||||
_________________________________________
|
||||
|
||||
This work is licensed under a
|
||||
Creative Commons Attribution 2.5 License.
|
||||
[ http://creativecommons.org/licenses/by/2.5/ ]
|
||||
|
||||
This means you may use it for any purpose,
|
||||
and make any changes you like.
|
||||
All I ask is that you include a link back
|
||||
to this page in your credits.
|
||||
|
||||
Are you using this icon set? Send me an email
|
||||
(including a link or picture if available) to
|
||||
mjames@gmail.com
|
||||
|
||||
Any other questions about this icon set please
|
||||
contact mjames@gmail.com
|
Before Width: | Height: | Size: 715 B |
Before Width: | Height: | Size: 354 B |
Before Width: | Height: | Size: 294 B |
Before Width: | Height: | Size: 688 B |
Before Width: | Height: | Size: 774 B |
Before Width: | Height: | Size: 868 B |
Before Width: | Height: | Size: 836 B |
Before Width: | Height: | Size: 865 B |
Before Width: | Height: | Size: 353 B |
Before Width: | Height: | Size: 452 B |
Before Width: | Height: | Size: 748 B |
Before Width: | Height: | Size: 923 B |
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
#version 330 core
|
||||
|
||||
// I/O
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
uniform vec3 aColor;
|
||||
|
||||
// Main
|
||||
|
||||
void main() {
|
||||
FragColor = vec4(aColor, 1);
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#version 330 core
|
||||
layout (location = 0) in vec3 aPos;
|
||||
|
||||
out vec3 vPos;
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(aPos, 1.0);
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
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)
|
|
@ -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")
|
|
@ -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);
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
#pragma once
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/handles.h"
|
||||
#include "objects/workspace.h"
|
||||
#include "camera.h"
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
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
|
||||
|
||||
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::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);
|
|
@ -1,14 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
#include "datatypes/primitives.h"
|
||||
|
||||
typedef std::variant<VoidData, BoolData, StringData, IntData, FloatData> DataValue;
|
||||
|
||||
enum class DataType {
|
||||
VOID = 0,
|
||||
BOOL = 1,
|
||||
STRING = 2,
|
||||
INT = 3,
|
||||
FLOAT = 4
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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()
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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());
|
||||
}
|
|
@ -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; }
|
||||
};
|
||||
}
|
|
@ -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 },
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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());
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "base/instance.h"
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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 {};
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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 },
|
||||
};
|
|
@ -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;
|
|
@ -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()));
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include <memory>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/ext.hpp>
|
||||
#include "../rendering/material.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/color3.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include <reactphysics3d/reactphysics3d.h>
|
||||
|
||||
namespace rp = reactphysics3d;
|
||||
|
||||
// For easy construction from C++. Maybe should be removed?
|
||||
struct PartConstructParams {
|
||||
glm::vec3 position;
|
||||
glm::quat rotation = glm::identity<glm::quat>();
|
||||
glm::vec3 size;
|
||||
Data::Color3 color;
|
||||
|
||||
bool anchored = false;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
Data::CFrame cframe;
|
||||
glm::vec3 size;
|
||||
Data::Color3 color;
|
||||
float transparency = 0.f;
|
||||
bool selected = false;
|
||||
|
||||
bool anchored = false;
|
||||
rp::RigidBody* rigidBody = nullptr;
|
||||
|
||||
Part();
|
||||
Part(PartConstructParams params);
|
||||
~Part() override;
|
||||
|
||||
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(); }
|
||||
};
|
|
@ -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) {
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
9
deps.txt
|
@ -1,9 +0,0 @@
|
|||
opengl (Linux: glvnd, Windows: [built-in/none])
|
||||
glfw
|
||||
glew
|
||||
glm
|
||||
sdl2
|
||||
stb
|
||||
qt6
|
||||
reactphysics3d
|
||||
pugixml
|
|
@ -1,103 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.31..)
|
||||
|
||||
project(editor VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
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)
|
||||
|
||||
set(TS_FILES editor_en_US.ts)
|
||||
|
||||
set(PROJECT_SOURCES
|
||||
main.cpp
|
||||
mainwindow.cpp
|
||||
mainwindow.h
|
||||
mainwindow.ui
|
||||
mainglwidget.h
|
||||
mainglwidget.cpp
|
||||
panes/explorerview.h
|
||||
panes/explorerview.cpp
|
||||
panes/explorermodel.h
|
||||
panes/explorermodel.cpp
|
||||
panes/propertiesview.h
|
||||
panes/propertiesview.cpp
|
||||
panes/propertiesmodel.h
|
||||
panes/propertiesmodel.cpp
|
||||
${TS_FILES}
|
||||
)
|
||||
|
||||
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||
qt_add_executable(editor
|
||||
MANUAL_FINALIZATION
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
# Define target properties for Android with Qt 6 as:
|
||||
# set_property(TARGET editor APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
|
||||
|
||||
qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
|
||||
else()
|
||||
if(ANDROID)
|
||||
add_library(editor SHARED
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
# Define properties for Android with Qt 5 after find_package() calls as:
|
||||
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
|
||||
else()
|
||||
add_executable(editor
|
||||
${PROJECT_SOURCES}
|
||||
mainglwidget.h mainglwidget.cpp
|
||||
)
|
||||
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()
|
||||
|
||||
# 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
|
||||
# explicit, fixed bundle identifier manually though.
|
||||
if(${QT_VERSION} VERSION_LESS 6.1.0)
|
||||
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.qtbasic)
|
||||
endif()
|
||||
set_target_properties(editor PROPERTIES
|
||||
${BUNDLE_ID_OPTION}
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
||||
MACOSX_BUNDLE TRUE
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS editor
|
||||
BUNDLE DESTINATION .
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
install(FILES $<TARGET_RUNTIME_DLLS:editor> TYPE BIN)
|
||||
|
||||
if(QT_VERSION_MAJOR EQUAL 6)
|
||||
qt_finalize_executable(editor)
|
||||
endif()
|
|
@ -1 +0,0 @@
|
|||
#pragma once
|
|
@ -1,20 +0,0 @@
|
|||
#include "mainwindow.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QLocale>
|
||||
#include <QTranslator>
|
||||
#include <QStyleFactory>
|
||||
#include <QBasicTimer>
|
||||
|
||||
#include "physics/simulation.h"
|
||||
#include "qcoreevent.h"
|
||||
#include "qobject.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
|
||||
MainWindow w;
|
||||
w.show();
|
||||
return a.exec();
|
||||
}
|
|
@ -1,301 +0,0 @@
|
|||
#include <GL/glew.h>
|
||||
#include <chrono>
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <glm/common.hpp>
|
||||
#include <glm/ext/matrix_projection.hpp>
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/ext/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 "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());
|
||||
}
|
||||
|
||||
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;
|
||||
QPoint lastMousePos;
|
||||
void MainGLWidget::handleCameraRotate(QMouseEvent* evt) {
|
||||
if (!isMouseRightDragging) 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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
static int moveX = 0;
|
||||
|
||||
static std::chrono::time_point lastTime = std::chrono::steady_clock::now();
|
||||
void MainGLWidget::updateCycle() {
|
||||
float deltaTime = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - lastTime).count();
|
||||
lastTime = std::chrono::steady_clock::now();
|
||||
|
||||
if (moveZ)
|
||||
camera.processMovement(moveZ == 1 ? DIRECTION_FORWARD : DIRECTION_BACKWARDS, deltaTime);
|
||||
if (moveX)
|
||||
camera.processMovement(moveX == 1 ? DIRECTION_LEFT : DIRECTION_RIGHT, deltaTime);
|
||||
|
||||
}
|
||||
|
||||
void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
|
||||
if (evt->key() == Qt::Key_W) moveZ = 1;
|
||||
else if (evt->key() == Qt::Key_S) moveZ = -1;
|
||||
|
||||
if (evt->key() == Qt::Key_A) moveX = 1;
|
||||
else if (evt->key() == Qt::Key_D) moveX = -1;
|
||||
|
||||
if (evt->key() == Qt::Key_F) {
|
||||
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),
|
||||
}));
|
||||
syncPartPhysics(lastPart);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
#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);
|
||||
void updateCycle();
|
||||
std::shared_ptr<Part> lastPart;
|
||||
|
||||
protected:
|
||||
void initializeGL() override;
|
||||
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
|
|
@ -1,289 +0,0 @@
|
|||
#include "mainwindow.h"
|
||||
#include "./ui_mainwindow.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
#include <QThread>
|
||||
#include <QTimerEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QWidget>
|
||||
#include <QTreeView>
|
||||
#include <QAbstractItemView>
|
||||
#include <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({
|
||||
.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),
|
||||
.anchored = true,
|
||||
}));
|
||||
ui->mainWidget->lastPart->name = "Baseplate";
|
||||
syncPartPhysics(ui->mainWidget->lastPart);
|
||||
|
||||
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),
|
||||
}));
|
||||
syncPartPhysics(ui->mainWidget->lastPart);
|
||||
}
|
||||
|
||||
static std::chrono::time_point lastTime = std::chrono::steady_clock::now();
|
||||
void MainWindow::timerEvent(QTimerEvent* evt) {
|
||||
if (evt->timerId() != timer.timerId()) {
|
||||
QWidget::timerEvent(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include "panes/explorerview.h"
|
||||
#include "qbasictimer.h"
|
||||
#include "qcoreevent.h"
|
||||
#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 {
|
||||
class MainWindow;
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
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
|
|
@ -1,451 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1027</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="MainGLWidget" name="mainWidget"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1027</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
<property name="title">
|
||||
<string>File</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuEdit">
|
||||
<property name="title">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuEdit"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
<widget class="QDockWidget" name="explorerWidget">
|
||||
<property name="windowTitle">
|
||||
<string>Explorer</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<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>
|
||||
<class>MainGLWidget</class>
|
||||
<extends>QOpenGLWidget</extends>
|
||||
<header>mainglwidget.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ExplorerView</class>
|
||||
<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/>
|
||||
</ui>
|
|
@ -1,260 +0,0 @@
|
|||
#include "explorermodel.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "qabstractitemmodel.h"
|
||||
#include "qcontainerfwd.h"
|
||||
#include "qimage.h"
|
||||
#include "qnamespace.h"
|
||||
#include "qobject.h"
|
||||
#include "qstringview.h"
|
||||
#include "qwidget.h"
|
||||
#include "qmimedata.h"
|
||||
#include "common.h"
|
||||
#include <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
|
||||
|
||||
std::map<std::string, QImage> instanceIconCache;
|
||||
|
||||
ExplorerModel::ExplorerModel(InstanceRef dataRoot, QWidget *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, rootItem(dataRoot) {
|
||||
// TODO: Don't use lambdas and handlers like that
|
||||
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();
|
||||
beginRemoveRows(toIndex(oldParent.value()), idx, idx);
|
||||
}
|
||||
|
||||
if (newParent.has_value()) {
|
||||
size_t size = newParent.value()->GetChildren().size();
|
||||
beginInsertRows(toIndex(newParent.value()), size, size);
|
||||
} else {
|
||||
// TODO:
|
||||
}
|
||||
};
|
||||
|
||||
hierarchyPostUpdateHandler = [&](InstanceRef object, std::optional<InstanceRef> oldParent, std::optional<InstanceRef> newParent) {
|
||||
if (newParent.has_value()) endInsertRows();
|
||||
if (oldParent.has_value()) endRemoveRows();
|
||||
};
|
||||
}
|
||||
|
||||
ExplorerModel::~ExplorerModel() = default;
|
||||
|
||||
QModelIndex ExplorerModel::index(int row, int column, const QModelIndex &parent) const {
|
||||
if (!hasIndex(row, column, parent))
|
||||
return {};
|
||||
|
||||
Instance* parentItem = parent.isValid()
|
||||
? static_cast<Instance*>(parent.internalPointer())
|
||||
: rootItem.get();
|
||||
|
||||
if (parentItem->GetChildren().size() >= row)
|
||||
return createIndex(row, column, parentItem->GetChildren()[row].get());
|
||||
return {};
|
||||
}
|
||||
|
||||
QModelIndex ExplorerModel::toIndex(InstanceRef item) {
|
||||
if (item == rootItem || !item->GetParent().has_value())
|
||||
return {};
|
||||
|
||||
InstanceRef parentItem = item->GetParent().value();
|
||||
// Check above ensures this item is not root, so value() must be valid
|
||||
for (int i = 0; i < parentItem->GetChildren().size(); i++)
|
||||
if (parentItem->GetChildren()[i] == item)
|
||||
return createIndex(i, 0, item.get());
|
||||
return QModelIndex{};
|
||||
}
|
||||
|
||||
QModelIndex ExplorerModel::ObjectToIndex(InstanceRef item) {
|
||||
return toIndex(item);
|
||||
}
|
||||
|
||||
QModelIndex ExplorerModel::parent(const QModelIndex &index) const {
|
||||
if (!index.isValid())
|
||||
return {};
|
||||
|
||||
Instance* childItem = static_cast<Instance*>(index.internalPointer());
|
||||
// NORISK: The parent must exist if the child was obtained from it during this frame
|
||||
InstanceRef parentItem = childItem->GetParent().value();
|
||||
|
||||
if (parentItem == rootItem)
|
||||
return {};
|
||||
|
||||
// Check above ensures this item is not root, so value() must be valid
|
||||
InstanceRef parentParent = parentItem->GetParent().value();
|
||||
for (int i = 0; i < parentParent->GetChildren().size(); i++)
|
||||
if (parentParent->GetChildren()[i] == parentItem)
|
||||
return createIndex(i, 0, parentItem.get());
|
||||
return QModelIndex{};
|
||||
}
|
||||
|
||||
int ExplorerModel::rowCount(const QModelIndex &parent) const {
|
||||
if (parent.column() > 0)
|
||||
return 0;
|
||||
|
||||
Instance* parentItem = parent.isValid()
|
||||
? static_cast<Instance*>(parent.internalPointer())
|
||||
: rootItem.get();
|
||||
|
||||
return parentItem->GetChildren().size();
|
||||
}
|
||||
|
||||
int ExplorerModel::columnCount(const QModelIndex &parent) const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
QVariant ExplorerModel::data(const QModelIndex &index, int role) const {
|
||||
if (!index.isValid())
|
||||
return {};
|
||||
|
||||
Instance *item = static_cast<Instance*>(index.internalPointer());
|
||||
|
||||
switch (role) {
|
||||
case Qt::EditRole:
|
||||
case Qt::DisplayRole:
|
||||
return QString::fromStdString(item->name);
|
||||
case Qt::DecorationRole:
|
||||
return iconOf(item->GetClass());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ExplorerModel::setData(const QModelIndex &index, const QVariant &value, int role) {
|
||||
if (!index.isValid() || role != Qt::EditRole) return false;
|
||||
|
||||
Instance* inst = static_cast<Instance*>(index.internalPointer());
|
||||
inst->name = value.toString().toStdString();
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant ExplorerModel::headerData(int section, Qt::Orientation orientation,
|
||||
int role) const
|
||||
{
|
||||
return QString("Idk lol \u00AF\u005C\u005F\u0028\u30C4\u0029\u005F\u002F\u00AF");
|
||||
}
|
||||
|
||||
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
|
||||
: Qt::NoItemFlags | Qt::ItemIsDropEnabled;
|
||||
}
|
||||
|
||||
QImage ExplorerModel::iconOf(const InstanceType* type) const {
|
||||
if (instanceIconCache.count(type->className)) return instanceIconCache[type->className];
|
||||
|
||||
const InstanceType* currentClass = type;
|
||||
while (currentClass->explorerIcon.empty()) currentClass = currentClass->super;
|
||||
|
||||
QImage icon("assets/icons/" + QString::fromStdString(currentClass->explorerIcon));
|
||||
instanceIconCache[type->className] = icon;
|
||||
return icon;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
printf("Moved %d from %s\n", count, sourceParent->name.c_str());
|
||||
|
||||
if ((sourceRow + count) >= sourceParent->GetChildren().size()) {
|
||||
fprintf(stderr, "Attempt to move rows %d-%d from %s (%s) while it only has %zu children.\n", sourceRow, sourceRow + count, sourceParent->name.c_str(), sourceParent->GetClass()->className.c_str(), sourceParent->GetChildren().size());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = sourceRow; i < (sourceRow + count); i++) {
|
||||
sourceParent->GetChildren()[i]->SetParent(destinationParent->shared_from_this());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExplorerModel::removeRows(int row, int count, const QModelIndex& parentIdx) {
|
||||
Instance* parent = parentIdx.isValid() ? static_cast<Instance*>(parentIdx.internalPointer()) : rootItem.get();
|
||||
|
||||
for (int i = row; i < (row + count); i++) {
|
||||
//parent->GetChildren()[i]->SetParent(nullptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExplorerModel::insertRows(int row, int count, const QModelIndex & parentIdx) {
|
||||
//Instance* parent = parentIdx.isValid() ? static_cast<Instance*>(parentIdx.internalPointer()) : workspace.get();
|
||||
//beginInsertRows(parentIdx, parent->GetChildren().size(), parent->GetChildren().size() + count);
|
||||
//for ()
|
||||
//endInsertRows();
|
||||
//return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
Qt::DropActions ExplorerModel::supportedDragActions() const {
|
||||
return Qt::DropAction::MoveAction;
|
||||
}
|
||||
|
||||
Qt::DropActions ExplorerModel::supportedDropActions() const {
|
||||
return Qt::DropAction::MoveAction;
|
||||
}
|
||||
|
||||
|
||||
InstanceRef ExplorerModel::fromIndex(const QModelIndex index) const {
|
||||
if (!index.isValid()) return rootItem;
|
||||
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");
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "objects/base/instance.h"
|
||||
#include "qabstractitemmodel.h"
|
||||
#include "qevent.h"
|
||||
#include "qnamespace.h"
|
||||
#include <QOpenGLWidget>
|
||||
#include <QWidget>
|
||||
|
||||
|
||||
class ExplorerModel : public QAbstractItemModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_DISABLE_COPY_MOVE(ExplorerModel)
|
||||
|
||||
explicit ExplorerModel(InstanceRef dataRoot, QWidget *parent = nullptr);
|
||||
~ExplorerModel() 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;
|
||||
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);
|
||||
private:
|
||||
InstanceRef rootItem;
|
||||
QModelIndex toIndex(InstanceRef item);
|
||||
QImage iconOf(const InstanceType* type) const;
|
||||
};
|
||||
|
||||
// #endif
|
|
@ -1,90 +0,0 @@
|
|||
#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())
|
||||
|
||||
ExplorerView::ExplorerView(QWidget* parent):
|
||||
QTreeView(parent),
|
||||
model(ExplorerModel(std::dynamic_pointer_cast<Instance>(dataModel))) {
|
||||
|
||||
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->setDragDropMode(QAbstractItemView::InternalMove);
|
||||
this->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
this->setDragEnabled(true);
|
||||
this->setAcceptDrops(true);
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ExplorerView::~ExplorerView() {
|
||||
}
|
||||
|
||||
void ExplorerView::keyPressEvent(QKeyEvent* event) {
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Delete:
|
||||
M_mainWindow->ui->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);
|
||||
}
|
||||
|
||||
void ExplorerView::updateRoot(InstanceRef newRoot) {
|
||||
model.updateRoot(newRoot);
|
||||
}
|
|
@ -1,29 +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 ExplorerView : public QTreeView {
|
||||
public:
|
||||
ExplorerView(QWidget* parent = nullptr);
|
||||
~ExplorerView() override;
|
||||
|
||||
void keyPressEvent(QKeyEvent*) override;
|
||||
// void dropEvent(QDropEvent*) override;
|
||||
|
||||
void buildContextMenu();
|
||||
void updateRoot(InstanceRef newRoot);
|
||||
private:
|
||||
ExplorerModel model;
|
||||
QMenu contextMenu;
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
};
|
2476
include/expected.hpp
9
run.sh
|
@ -1,9 +0,0 @@
|
|||
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
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
||||
};
|
|
@ -5,18 +5,19 @@
|
|||
#include <glm/ext/quaternion_trigonometric.hpp>
|
||||
#include <glm/ext/vector_float3.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <memory>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
#include "objects/part.h"
|
||||
#include "part.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "physics/simulation.h"
|
||||
#include "camera.h"
|
||||
|
||||
#include "common.h"
|
||||
|
||||
void errorCatcher(int id, const char* str);
|
||||
|
||||
Camera camera(glm::vec3(0.0, 0.0, 3.0));
|
||||
std::vector<Part> parts;
|
||||
|
||||
int mode = 0;
|
||||
|
||||
void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
|
||||
|
@ -24,48 +25,47 @@ 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;
|
||||
|
||||
int main() {
|
||||
glfwSetErrorCallback(errorCatcher);
|
||||
|
||||
glfwInit();
|
||||
GLFWwindow *window = glfwCreateWindow(1200, 900, "OpenBlocks Client ALPHA", NULL, NULL);
|
||||
GLFWwindow *window = glfwCreateWindow(1200, 900, "GLTest", NULL, NULL);
|
||||
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({
|
||||
parts.push_back(Part {
|
||||
.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,
|
||||
}));
|
||||
});
|
||||
syncPartPhysics(parts.back());
|
||||
|
||||
workspace()->AddChild(lastPart = Part::New({
|
||||
parts.push_back(Part {
|
||||
.position = glm::vec3(0),
|
||||
.rotation = glm::vec3(0),
|
||||
.size = glm::vec3(4, 1.2, 2),
|
||||
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
|
||||
}));
|
||||
|
||||
for (InstanceRef inst : workspace()->GetChildren()) {
|
||||
if (inst->GetClass()->className != "Part") continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
syncPartPhysics(part);
|
||||
}
|
||||
.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(parts.back());
|
||||
|
||||
float lastTime = glfwGetTime();
|
||||
do {
|
||||
|
@ -110,16 +110,16 @@ 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));
|
||||
syncPartPhysics(lastPart);
|
||||
parts.back().rotation *= glm::angleAxis(shiftFactor, glm::vec3(1, 0, 0));
|
||||
syncPartPhysics(parts.back());
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_Y) == GLFW_PRESS) {
|
||||
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 1, 0));
|
||||
syncPartPhysics(lastPart);
|
||||
parts.back().rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 1, 0));
|
||||
syncPartPhysics(parts.back());
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS) {
|
||||
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 0, 1));
|
||||
syncPartPhysics(lastPart);
|
||||
parts.back().rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 0, 1));
|
||||
syncPartPhysics(parts.back());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,50 +154,49 @@ 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({
|
||||
parts.push_back(Part {
|
||||
.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),
|
||||
}));
|
||||
syncPartPhysics(lastPart);
|
||||
.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(parts.back());
|
||||
}
|
||||
|
||||
float shiftFactor = (mods & GLFW_MOD_SHIFT) ? -0.2 : 0.2;
|
||||
if (mode == 0) {
|
||||
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
|
||||
// lastPart->position.x += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
parts.back().position.x += shiftFactor;
|
||||
syncPartPhysics(parts.back());
|
||||
}
|
||||
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
|
||||
// lastPart->position.y += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
parts.back().position.y += shiftFactor;
|
||||
syncPartPhysics(parts.back());
|
||||
}
|
||||
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
|
||||
// lastPart->position.z += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
parts.back().position.z += shiftFactor;
|
||||
syncPartPhysics(parts.back());
|
||||
}
|
||||
} else if (mode == 1) {
|
||||
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
|
||||
lastPart->size.x += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
parts.back().scale.x += shiftFactor;
|
||||
syncPartPhysics(parts.back());
|
||||
}
|
||||
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
|
||||
lastPart->size.y += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
parts.back().scale.y += shiftFactor;
|
||||
syncPartPhysics(parts.back());
|
||||
}
|
||||
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
|
||||
lastPart->size.z += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
parts.back().scale.z += shiftFactor;
|
||||
syncPartPhysics(parts.back());
|
||||
}
|
||||
}
|
||||
|
||||
if (key == GLFW_KEY_M && action == GLFW_PRESS) mode = 0;
|
||||
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);
|
||||
}
|
20
src/part.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/fwd.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <reactphysics3d/body/RigidBody.h>
|
||||
#include "rendering/material.h"
|
||||
|
||||
namespace rp = reactphysics3d;
|
||||
|
||||
struct Part {
|
||||
glm::vec3 position;
|
||||
glm::quat rotation = glm::identity<glm::quat>();
|
||||
glm::vec3 scale;
|
||||
Material material;
|
||||
|
||||
bool anchored = false;
|
||||
rp::RigidBody* rigidBody = nullptr;
|
||||
|
||||
void syncTransforms();
|
||||
};
|
67
src/physics/simulation.cpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
#include <cstdio>
|
||||
#include <glm/ext/matrix_float3x3.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <reactphysics3d/collision/shapes/BoxShape.h>
|
||||
#include <reactphysics3d/collision/shapes/CollisionShape.h>
|
||||
#include <reactphysics3d/components/RigidBodyComponents.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 <vector>
|
||||
#include "../part.h"
|
||||
#include "util.h"
|
||||
|
||||
#include "simulation.h"
|
||||
|
||||
namespace rp = reactphysics3d;
|
||||
|
||||
extern std::vector<Part> parts;
|
||||
|
||||
rp::PhysicsCommon physicsCommon;
|
||||
rp::PhysicsWorld* world;
|
||||
|
||||
void simulationInit() {
|
||||
world = physicsCommon.createPhysicsWorld();
|
||||
|
||||
rp::Vector3 position(0, 20, 0);
|
||||
rp::Quaternion orientation = rp::Quaternion::identity();
|
||||
rp::Transform transform(position, orientation);
|
||||
rp::RigidBody* body = world->createRigidBody(transform);
|
||||
world->setGravity(rp::Vector3(0, -196.2, 0));
|
||||
}
|
||||
|
||||
void syncPartPhysics(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);
|
||||
}
|
||||
|
||||
void physicsStep(float deltaTime) {
|
||||
// Step the simulation a few steps
|
||||
world->update(deltaTime);
|
||||
|
||||
for (Part& part : parts) {
|
||||
const rp::Transform& transform = part.rigidBody->getTransform();
|
||||
part.position = rpToGlm(transform.getPosition());
|
||||
// part.rotation = glm::eulerAngles(rpToGlm(transform.getOrientation()));
|
||||
part.rotation = rpToGlm(transform.getOrientation());
|
||||
}
|
||||
}
|
7
src/physics/simulation.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "../part.h"
|
||||
|
||||
void simulationInit();
|
||||
void syncPartPhysics(Part& part);
|
||||
void physicsStep(float deltaTime);
|
|
@ -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,21 +15,10 @@ inline rp::Quaternion glmToRp(glm::quat quat) {
|
|||
return rp::Quaternion(quat.w, rp::Vector3(quat.x, quat.y, quat.z));
|
||||
}
|
||||
|
||||
// inline rp::Matrix3x3 glmToRp(glm::mat3 mat) {
|
||||
// return (rp::Quaternion)glmToRp((glm::quat)mat);
|
||||
// }
|
||||
|
||||
inline glm::vec3 rpToGlm(rp::Vector3 vec) {
|
||||
return glm::vec3(vec.x, vec.y, vec.z);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
47
src/rendering/defaultmeshes.cpp
Normal 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);
|
||||
}
|
|
@ -2,7 +2,5 @@
|
|||
#include "mesh.h"
|
||||
|
||||
extern Mesh* CUBE_MESH;
|
||||
extern Mesh* SPHERE_MESH;
|
||||
extern Mesh* ARROW_MESH;
|
||||
|
||||
void initMeshes();
|
|
@ -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);
|
|
@ -4,8 +4,6 @@ class Mesh {
|
|||
unsigned int VBO, VAO;
|
||||
|
||||
public:
|
||||
int vertexCount;
|
||||
|
||||
Mesh(int vertexCount, float* vertices);
|
||||
~Mesh();
|
||||
void bind();
|
143
src/rendering/renderer.cpp
Normal file
|
@ -0,0 +1,143 @@
|
|||
#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 <vector>
|
||||
|
||||
#include "shader.h"
|
||||
#include "mesh.h"
|
||||
#include "defaultmeshes.h"
|
||||
#include "../camera.h"
|
||||
#include "../part.h"
|
||||
#include "skybox.h"
|
||||
#include "surface.h"
|
||||
#include "texture3d.h"
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
Shader *shader = NULL;
|
||||
Shader *skyboxShader = NULL;
|
||||
extern Camera camera;
|
||||
extern std::vector<Part> parts;
|
||||
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);
|
||||
|
||||
for (Part part : parts) {
|
||||
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
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
void renderInit(GLFWwindow* window);
|
||||
void render(GLFWwindow* window);
|