Compare commits
25 commits
feature/se
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
086a2ca39f | ||
|
7e6261fcf3 | ||
|
f81964e395 | ||
|
bfaab69daa | ||
|
75be686e48 | ||
|
45f39c3f49 | ||
|
12a4ed76b4 | ||
|
186d64f28e | ||
|
940109b0b9 | ||
|
b80eb03f9d | ||
|
28ed11fb53 | ||
|
23ac7ee634 | ||
|
ac89dea966 | ||
|
4c51ee6904 | ||
|
5081e18ea7 | ||
|
2895124778 | ||
|
a2fd9be6e3 | ||
|
e22bb74850 | ||
|
e082428314 | ||
|
a346b1d9a8 | ||
|
4bfca68fb0 | ||
|
32964df4c3 | ||
|
fd1037d76a | ||
|
64fa46d496 | ||
|
81d172900b |
11
.clangd
11
.clangd
|
@ -1,11 +0,0 @@
|
||||||
CompileFlags: # Tweak the parse settings, example directory given to show format
|
|
||||||
Add:
|
|
||||||
- "--include-directory=../../src"
|
|
||||||
- "--include-directory=../src"
|
|
||||||
- "--include-directory=./editor_autogen/include"
|
|
||||||
- "--include-directory=../.."
|
|
||||||
- "--include-directory=/usr/include/qt6/QtWidgets"
|
|
||||||
- "--include-directory=/usr/include/qt6/QtOpenGLWidgets"
|
|
||||||
- "--include-directory=/usr/include/qt6"
|
|
||||||
- "--include-directory=/usr/include/qt6/QtGui"
|
|
||||||
- "--include-directory=/usr/include/qt6/QtCore"
|
|
22
.gitignore
vendored
22
.gitignore
vendored
|
@ -1,12 +1,14 @@
|
||||||
bin/
|
/bin/
|
||||||
lib/
|
/lib/
|
||||||
CMakeFiles/
|
/build/
|
||||||
cmake_install.cmake
|
|
||||||
CMakeCache.txt
|
|
||||||
Makefile
|
|
||||||
|
|
||||||
# Qt
|
# Qt
|
||||||
.lupdate/
|
/*.pro.user*
|
||||||
*.pro.user*
|
/CMakeLists.txt.user*
|
||||||
CMakeLists.txt.user*
|
|
||||||
*_autogen/
|
# Clangd
|
||||||
|
/compile_commands.json
|
||||||
|
/.cache
|
||||||
|
|
||||||
|
# Gdb
|
||||||
|
/.gdb_history
|
|
@ -1,50 +1,14 @@
|
||||||
cmake_minimum_required(VERSION 3.5.0)
|
cmake_minimum_required(VERSION 3.31..)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
project(openblocks VERSION 0.1.0)
|
project(openblocks VERSION 0.1.0)
|
||||||
set(OpenGL_GL_PREFERENCE "GLVND")
|
set(OpenGL_GL_PREFERENCE "GLVND")
|
||||||
|
|
||||||
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
|
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
|
||||||
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
|
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
|
||||||
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib )
|
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
|
||||||
|
|
||||||
SET(BASEPATH "${CMAKE_SOURCE_DIR}")
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
include_directories("${BASEPATH}")
|
|
||||||
include_directories("src")
|
|
||||||
|
|
||||||
find_package(OpenGL REQUIRED COMPONENTS OpenGL)
|
add_subdirectory(core)
|
||||||
|
add_subdirectory(client)
|
||||||
find_package(SDL2 REQUIRED)
|
add_subdirectory(editor)
|
||||||
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)
|
|
||||||
find_package(pugixml REQUIRED)
|
|
||||||
|
|
||||||
# PkgConfig packages
|
|
||||||
# find_package(PkgConfig REQUIRED)
|
|
||||||
# pkg_check_modules(PUGIXML REQUIRED pugixml)
|
|
||||||
|
|
||||||
file(MAKE_DIRECTORY bin)
|
|
||||||
|
|
||||||
include_directories("include")
|
|
||||||
|
|
||||||
file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h")
|
|
||||||
add_library(openblocks ${SOURCES})
|
|
||||||
set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks")
|
|
||||||
target_link_libraries(openblocks ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} ${PUGIXML_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
|
|
||||||
|
|
||||||
# add_executable(client "client/src/main.cpp" $<TARGET_OBJECTS:openblocks>)
|
|
||||||
# include_directories("src")
|
|
||||||
# target_link_libraries(client ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D)
|
|
||||||
|
|
||||||
add_subdirectory("client")
|
|
||||||
add_subdirectory("editor")
|
|
BIN
assets/icons/editor/snap05.png
Normal file
BIN
assets/icons/editor/snap05.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 868 B |
BIN
assets/icons/editor/snap1.png
Normal file
BIN
assets/icons/editor/snap1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 836 B |
BIN
assets/icons/editor/snapoff.png
Normal file
BIN
assets/icons/editor/snapoff.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 865 B |
109
assets/shaders/handle.fs
Normal file
109
assets/shaders/handle.fs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
#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;
|
||||||
|
}
|
20
assets/shaders/handle.vs
Normal file
20
assets/shaders/handle.vs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#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;
|
||||||
|
}
|
13
assets/shaders/identity.fs
Normal file
13
assets/shaders/identity.fs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
// I/O
|
||||||
|
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
uniform vec3 aColor;
|
||||||
|
|
||||||
|
// Main
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
FragColor = vec4(aColor, 1);
|
||||||
|
}
|
8
assets/shaders/identity.vs
Normal file
8
assets/shaders/identity.vs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#version 330 core
|
||||||
|
layout (location = 0) in vec3 aPos;
|
||||||
|
|
||||||
|
out vec3 vPos;
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = vec4(aPos, 1.0);
|
||||||
|
}
|
|
@ -53,6 +53,7 @@ uniform int numPointLights;
|
||||||
uniform DirLight sunLight;
|
uniform DirLight sunLight;
|
||||||
uniform Material material;
|
uniform Material material;
|
||||||
uniform sampler2DArray studs;
|
uniform sampler2DArray studs;
|
||||||
|
uniform float transparency;
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ void main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
vec4 studPx = texture(studs, vec3(vTexCoords, vSurfaceZ));
|
vec4 studPx = texture(studs, vec3(vTexCoords, vSurfaceZ));
|
||||||
FragColor = vec4(mix(result, vec3(studPx), studPx.w), 1);
|
FragColor = vec4(mix(result, vec3(studPx), studPx.w), 1) * (1-transparency);
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 calculateDirectionalLight(DirLight light) {
|
vec3 calculateDirectionalLight(DirLight light) {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
find_package(SDL2 REQUIRED)
|
||||||
|
include_directories(${SDL2_INCLUDE_DIRS})
|
||||||
|
|
||||||
add_executable(client "src/main.cpp" $<TARGET_OBJECTS:openblocks>)
|
find_package(glfw3 REQUIRED)
|
||||||
include_directories("../src")
|
|
||||||
target_link_libraries(client ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
|
add_executable(client "src/main.cpp")
|
||||||
|
target_link_libraries(client PRIVATE ${SDL2_LIBRARIES} openblocks glfw)
|
15
core/CMakeLists.txt
Normal file
15
core/CMakeLists.txt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
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")
|
|
@ -8,6 +8,7 @@ Camera camera(glm::vec3(0.0, 0.0, 3.0));
|
||||||
std::shared_ptr<DataModel> dataModel = DataModel::New();
|
std::shared_ptr<DataModel> dataModel = DataModel::New();
|
||||||
std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
||||||
std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
||||||
|
std::shared_ptr<Handles> editorToolHandles = Handles::New();
|
||||||
|
|
||||||
|
|
||||||
std::vector<InstanceRefWeak> currentSelection;
|
std::vector<InstanceRefWeak> currentSelection;
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "objects/base/instance.h"
|
#include "objects/base/instance.h"
|
||||||
|
#include "objects/handles.h"
|
||||||
#include "objects/workspace.h"
|
#include "objects/workspace.h"
|
||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
@ -18,6 +19,7 @@ extern std::shared_ptr<DataModel> dataModel;
|
||||||
inline std::shared_ptr<Workspace> workspace() { return std::dynamic_pointer_cast<Workspace>(dataModel->services["Workspace"]); }
|
inline std::shared_ptr<Workspace> workspace() { return std::dynamic_pointer_cast<Workspace>(dataModel->services["Workspace"]); }
|
||||||
extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
||||||
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
||||||
|
extern std::shared_ptr<Handles> editorToolHandles;
|
||||||
|
|
||||||
void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer = false);
|
void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer = false);
|
||||||
const std::vector<InstanceRefWeak> getSelection();
|
const std::vector<InstanceRefWeak> getSelection();
|
|
@ -4,7 +4,7 @@
|
||||||
#define IMPL_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE, TYPE_NAME) Data::CLASS_NAME::CLASS_NAME(WRAPPED_TYPE in) : value(in) {} \
|
#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::~CLASS_NAME() = default; \
|
||||||
Data::CLASS_NAME::operator const WRAPPED_TYPE() const { return value; } \
|
Data::CLASS_NAME::operator const WRAPPED_TYPE() const { return value; } \
|
||||||
const Data::TypeInfo Data::CLASS_NAME::TYPE = { .name = TYPE_NAME, .deserializer = &Data::CLASS_NAME::Deserialize }; \
|
const Data::TypeInfo Data::CLASS_NAME::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; }; \
|
const Data::TypeInfo& Data::CLASS_NAME::GetType() const { return Data::CLASS_NAME::TYPE; }; \
|
||||||
void Data::CLASS_NAME::Serialize(pugi::xml_node* node) const { node->text().set(std::string(this->ToString())); }
|
void Data::CLASS_NAME::Serialize(pugi::xml_node* node) const { node->text().set(std::string(this->ToString())); }
|
||||||
|
|
||||||
|
@ -45,6 +45,11 @@ Data::Variant Data::Bool::Deserialize(pugi::xml_node* node) {
|
||||||
return Data::Bool(node->text().as_bool());
|
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 {
|
const Data::String Data::Int::ToString() const {
|
||||||
return Data::String(std::to_string(value));
|
return Data::String(std::to_string(value));
|
||||||
}
|
}
|
||||||
|
@ -53,6 +58,11 @@ Data::Variant Data::Int::Deserialize(pugi::xml_node* node) {
|
||||||
return Data::Int(node->text().as_int());
|
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 {
|
const Data::String Data::Float::ToString() const {
|
||||||
return Data::String(std::to_string(value));
|
return Data::String(std::to_string(value));
|
||||||
}
|
}
|
||||||
|
@ -61,10 +71,19 @@ Data::Variant Data::Float::Deserialize(pugi::xml_node* node) {
|
||||||
return Data::Float(node->text().as_float());
|
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 {
|
const Data::String Data::String::ToString() const {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Data::Variant Data::String::Deserialize(pugi::xml_node* node) {
|
Data::Variant Data::String::Deserialize(pugi::xml_node* node) {
|
||||||
return Data::String(node->text().as_string());
|
return Data::String(node->text().as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::Variant Data::String::FromString(std::string string) {
|
||||||
|
return Data::String(string);
|
||||||
}
|
}
|
|
@ -16,15 +16,18 @@ public: \
|
||||||
virtual const Data::String ToString() const override; \
|
virtual const Data::String ToString() const override; \
|
||||||
virtual void Serialize(pugi::xml_node* node) const override; \
|
virtual void Serialize(pugi::xml_node* node) const override; \
|
||||||
static Data::Variant Deserialize(pugi::xml_node* node); \
|
static Data::Variant Deserialize(pugi::xml_node* node); \
|
||||||
|
static Data::Variant FromString(std::string); \
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class Variant;
|
class Variant;
|
||||||
typedef std::function<Data::Variant(pugi::xml_node*)> Deserializer;
|
typedef std::function<Data::Variant(pugi::xml_node*)> Deserializer;
|
||||||
|
typedef std::function<Data::Variant(std::string)> FromString;
|
||||||
|
|
||||||
struct TypeInfo {
|
struct TypeInfo {
|
||||||
std::string name;
|
std::string name;
|
||||||
Deserializer deserializer;
|
Deserializer deserializer;
|
||||||
|
FromString fromString;
|
||||||
TypeInfo(const TypeInfo&) = delete;
|
TypeInfo(const TypeInfo&) = delete;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,11 +2,16 @@
|
||||||
#include "datatypes/vector.h"
|
#include "datatypes/vector.h"
|
||||||
#include "physics/util.h"
|
#include "physics/util.h"
|
||||||
#include <glm/ext/matrix_transform.hpp>
|
#include <glm/ext/matrix_transform.hpp>
|
||||||
|
#include <glm/gtc/matrix_inverse.hpp>
|
||||||
|
#include <glm/matrix.hpp>
|
||||||
#include <reactphysics3d/mathematics/Transform.h>
|
#include <reactphysics3d/mathematics/Transform.h>
|
||||||
#define GLM_ENABLE_EXPERIMENTAL
|
#define GLM_ENABLE_EXPERIMENTAL
|
||||||
#include <glm/gtx/euler_angles.hpp>
|
#include <glm/gtx/euler_angles.hpp>
|
||||||
// #include "meta.h" // IWYU pragma: keep
|
// #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)
|
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)
|
: translation(x, y, z)
|
||||||
, rotation({
|
, rotation({
|
||||||
|
@ -33,17 +38,13 @@ Data::CFrame::CFrame(const rp::Transform& transform) : Data::CFrame::CFrame(rpTo
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::mat3 lookAt(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3 up) {
|
glm::mat3 lookAt(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3 up) {
|
||||||
// https://github.com/sgorsten/glm/issues/29#issuecomment-743989030
|
// https://github.com/sgorsten/linalg/issues/29#issuecomment-743989030
|
||||||
Data::Vector3 f = (lookAt - position).Unit(); // Forward/Look
|
Data::Vector3 f = (lookAt - position).Unit(); // Forward/Look
|
||||||
Data::Vector3 u = up.Unit(); // Up
|
Data::Vector3 u = up.Unit(); // Up
|
||||||
Data::Vector3 s = f.Cross(u).Unit(); // Right
|
Data::Vector3 s = f.Cross(u).Unit(); // Right
|
||||||
u = s.Cross(u);
|
u = s.Cross(f);
|
||||||
|
|
||||||
return {
|
return { s, u, f };
|
||||||
{ s.X(), u.X(), -f.X() },
|
|
||||||
{ s.Y(), u.Y(), -f.Y() },
|
|
||||||
{ s.Z(), u.Z(), -f.Z() },
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Data::CFrame::CFrame(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3 up)
|
Data::CFrame::CFrame(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3 up)
|
||||||
|
@ -85,8 +86,21 @@ Data::CFrame Data::CFrame::FromEulerAnglesXYZ(Data::Vector3 vector) {
|
||||||
return Data::CFrame(Data::Vector3::ZERO, glm::column(mat, 2), (Data::Vector3)glm::column(mat, 1)); // Getting LookAt (3rd) and Up (2nd) vectors
|
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
|
// 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 {
|
Data::CFrame Data::CFrame::operator +(Data::Vector3 vector) const {
|
||||||
return CFrame { this->translation + glm::vec3(vector), this->rotation };
|
return CFrame { this->translation + glm::vec3(vector), this->rotation };
|
||||||
}
|
}
|
|
@ -7,6 +7,8 @@
|
||||||
#include <glm/ext/vector_float3.hpp>
|
#include <glm/ext/vector_float3.hpp>
|
||||||
#include <glm/fwd.hpp>
|
#include <glm/fwd.hpp>
|
||||||
#include <glm/gtc/matrix_access.hpp>
|
#include <glm/gtc/matrix_access.hpp>
|
||||||
|
#include <glm/gtc/matrix_inverse.hpp>
|
||||||
|
#include <glm/matrix.hpp>
|
||||||
#include <reactphysics3d/mathematics/Transform.h>
|
#include <reactphysics3d/mathematics/Transform.h>
|
||||||
#include <reactphysics3d/reactphysics3d.h>
|
#include <reactphysics3d/reactphysics3d.h>
|
||||||
|
|
||||||
|
@ -28,6 +30,9 @@ namespace Data {
|
||||||
CFrame(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3 up = Data::Vector3(0, 1, 0));
|
CFrame(Data::Vector3 position, Data::Vector3 lookAt, Data::Vector3 up = Data::Vector3(0, 1, 0));
|
||||||
~CFrame();
|
~CFrame();
|
||||||
|
|
||||||
|
static const CFrame IDENTITY;
|
||||||
|
static const CFrame YToZ;
|
||||||
|
|
||||||
virtual const TypeInfo& GetType() const override;
|
virtual const TypeInfo& GetType() const override;
|
||||||
static const TypeInfo TYPE;
|
static const TypeInfo TYPE;
|
||||||
|
|
||||||
|
@ -41,18 +46,21 @@ namespace Data {
|
||||||
//inline static CFrame identity() { }
|
//inline static CFrame identity() { }
|
||||||
inline Vector3 Position() const { return translation; }
|
inline Vector3 Position() const { return translation; }
|
||||||
inline CFrame Rotation() const { return CFrame { glm::vec3(0, 0, 0), rotation }; }
|
inline CFrame Rotation() const { return CFrame { glm::vec3(0, 0, 0), rotation }; }
|
||||||
|
CFrame Inverse() const;
|
||||||
inline float X() const { return translation.x; }
|
inline float X() const { return translation.x; }
|
||||||
inline float Y() const { return translation.y; }
|
inline float Y() const { return translation.y; }
|
||||||
inline float Z() const { return translation.z; }
|
inline float Z() const { return translation.z; }
|
||||||
|
|
||||||
inline Vector3 RightVector() { return glm::column(rotation, 0); }
|
inline Vector3 RightVector() { return glm::column(rotation, 0); }
|
||||||
inline Vector3 UpVector() { return glm::column(rotation, 1); }
|
inline Vector3 UpVector() { return glm::column(rotation, 1); }
|
||||||
inline Vector3 LookVector() { return glm::column(rotation, 2); }
|
inline Vector3 LookVector() { return -glm::column(rotation, 2); }
|
||||||
|
|
||||||
Vector3 ToEulerAnglesXYZ();
|
Vector3 ToEulerAnglesXYZ();
|
||||||
static CFrame FromEulerAnglesXYZ(Data::Vector3);
|
static CFrame FromEulerAnglesXYZ(Data::Vector3);
|
||||||
|
|
||||||
// Operators
|
// 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;
|
||||||
Data::CFrame operator -(Data::Vector3) const;
|
Data::CFrame operator -(Data::Vector3) const;
|
||||||
};
|
};
|
|
@ -25,6 +25,14 @@ Data::Vector3::operator glm::vec3() const { return vector; };
|
||||||
Data::Vector3::operator rp::Vector3() const { return rp::Vector3(X(), Y(), Z()); };
|
Data::Vector3::operator rp::Vector3() const { return rp::Vector3(X(), Y(), Z()); };
|
||||||
|
|
||||||
// Operators
|
// 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 {
|
Data::Vector3 Data::Vector3::operator +(Data::Vector3 other) const {
|
||||||
return Data::Vector3(this->X() + other.X(), this->Y() + other.Y(), this->Z() + other.Z());
|
return Data::Vector3(this->X() + other.X(), this->Y() + other.Y(), this->Z() + other.Z());
|
||||||
}
|
}
|
|
@ -40,6 +40,8 @@ namespace Data {
|
||||||
float Dot(Data::Vector3) const;
|
float Dot(Data::Vector3) const;
|
||||||
|
|
||||||
// Operators
|
// 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 -(Data::Vector3) const;
|
Data::Vector3 operator -(Data::Vector3) const;
|
||||||
Data::Vector3 operator -() const;
|
Data::Vector3 operator -() const;
|
103
core/src/math_helper.cpp
Normal file
103
core/src/math_helper.cpp
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
#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;
|
||||||
|
}
|
5
core/src/math_helper.h
Normal file
5
core/src/math_helper.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#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);
|
|
@ -10,7 +10,8 @@
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <../include/expected.hpp>
|
// #include <../../include/expected.hpp>
|
||||||
|
#include <expected.hpp>
|
||||||
#include <pugixml.hpp>
|
#include <pugixml.hpp>
|
||||||
|
|
||||||
#include "member.h"
|
#include "member.h"
|
90
core/src/objects/handles.cpp
Normal file
90
core/src/objects/handles.cpp
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
#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);
|
||||||
|
}
|
55
core/src/objects/handles.h
Normal file
55
core/src/objects/handles.h
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
#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;
|
||||||
|
};
|
|
@ -69,33 +69,37 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(par
|
||||||
.type = &Data::Bool::TYPE,
|
.type = &Data::Bool::TYPE,
|
||||||
.codec = fieldCodecOf<Data::Bool, bool>(),
|
.codec = fieldCodecOf<Data::Bool, bool>(),
|
||||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this)
|
.updateCallback = memberFunctionOf(&Part::onUpdated, this)
|
||||||
} }, { "Position", {
|
}}, { "Position", {
|
||||||
.backingField = &cframe,
|
.backingField = &cframe,
|
||||||
.type = &Data::Vector3::TYPE,
|
.type = &Data::Vector3::TYPE,
|
||||||
.codec = cframePositionCodec(),
|
.codec = cframePositionCodec(),
|
||||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
|
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
|
||||||
.flags = PropertyFlags::PROP_NOSAVE
|
.flags = PropertyFlags::PROP_NOSAVE
|
||||||
} }, { "Rotation", {
|
}}, { "Rotation", {
|
||||||
.backingField = &cframe,
|
.backingField = &cframe,
|
||||||
.type = &Data::Vector3::TYPE,
|
.type = &Data::Vector3::TYPE,
|
||||||
.codec = cframeRotationCodec(),
|
.codec = cframeRotationCodec(),
|
||||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
|
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
|
||||||
.flags = PropertyFlags::PROP_NOSAVE
|
.flags = PropertyFlags::PROP_NOSAVE
|
||||||
} }, { "CFrame", {
|
}}, { "CFrame", {
|
||||||
.backingField = &cframe,
|
.backingField = &cframe,
|
||||||
.type = &Data::CFrame::TYPE,
|
.type = &Data::CFrame::TYPE,
|
||||||
.codec = fieldCodecOf<Data::CFrame>(),
|
.codec = fieldCodecOf<Data::CFrame>(),
|
||||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
|
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
|
||||||
} }, { "Size", {
|
}}, { "Size", {
|
||||||
.backingField = &size,
|
.backingField = &size,
|
||||||
.type = &Data::Vector3::TYPE,
|
.type = &Data::Vector3::TYPE,
|
||||||
.codec = fieldCodecOf<Data::Vector3, glm::vec3>(),
|
.codec = fieldCodecOf<Data::Vector3, glm::vec3>(),
|
||||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this)
|
.updateCallback = memberFunctionOf(&Part::onUpdated, this)
|
||||||
} }, { "Color", {
|
}}, { "Color", {
|
||||||
.backingField = &color,
|
.backingField = &color,
|
||||||
.type = &Data::Color3::TYPE,
|
.type = &Data::Color3::TYPE,
|
||||||
.codec = fieldCodecOf<Data::Color3>(),
|
.codec = fieldCodecOf<Data::Color3>(),
|
||||||
} }
|
}}, { "Transparency", {
|
||||||
|
.backingField = &transparency,
|
||||||
|
.type = &Data::Float::TYPE,
|
||||||
|
.codec = fieldCodecOf<Data::Float, float>(),
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -32,6 +32,7 @@ public:
|
||||||
Data::CFrame cframe;
|
Data::CFrame cframe;
|
||||||
glm::vec3 size;
|
glm::vec3 size;
|
||||||
Data::Color3 color;
|
Data::Color3 color;
|
||||||
|
float transparency = 0.f;
|
||||||
bool selected = false;
|
bool selected = false;
|
||||||
|
|
||||||
bool anchored = false;
|
bool anchored = false;
|
|
@ -6,6 +6,7 @@
|
||||||
#include <reactphysics3d/collision/shapes/BoxShape.h>
|
#include <reactphysics3d/collision/shapes/BoxShape.h>
|
||||||
#include <reactphysics3d/collision/shapes/CollisionShape.h>
|
#include <reactphysics3d/collision/shapes/CollisionShape.h>
|
||||||
#include <reactphysics3d/components/RigidBodyComponents.h>
|
#include <reactphysics3d/components/RigidBodyComponents.h>
|
||||||
|
#include <reactphysics3d/configuration.h>
|
||||||
#include <reactphysics3d/engine/EventListener.h>
|
#include <reactphysics3d/engine/EventListener.h>
|
||||||
#include <reactphysics3d/engine/PhysicsCommon.h>
|
#include <reactphysics3d/engine/PhysicsCommon.h>
|
||||||
#include <reactphysics3d/mathematics/Quaternion.h>
|
#include <reactphysics3d/mathematics/Quaternion.h>
|
||||||
|
@ -39,6 +40,9 @@ void simulationInit() {
|
||||||
world = physicsCommon->createPhysicsWorld();
|
world = physicsCommon->createPhysicsWorld();
|
||||||
|
|
||||||
world->setGravity(rp::Vector3(0, -196.2, 0));
|
world->setGravity(rp::Vector3(0, -196.2, 0));
|
||||||
|
// world->setContactsPositionCorrectionTechnique(rp3d::ContactsPositionCorrectionTechnique::BAUMGARTE_CONTACTS);
|
||||||
|
world->setNbIterationsPositionSolver(2000);
|
||||||
|
world->setNbIterationsVelocitySolver(2000);
|
||||||
|
|
||||||
world->setEventListener(&eventListener);
|
world->setEventListener(&eventListener);
|
||||||
}
|
}
|
||||||
|
@ -69,7 +73,7 @@ void syncPartPhysics(std::shared_ptr<Part> part) {
|
||||||
|
|
||||||
void physicsStep(float deltaTime) {
|
void physicsStep(float deltaTime) {
|
||||||
// Step the simulation a few steps
|
// Step the simulation a few steps
|
||||||
world->update(deltaTime / 2);
|
world->update(std::min(deltaTime / 2, (1/60.f)));
|
||||||
|
|
||||||
// Naive implementation. Parts are only considered so if they are just under Workspace
|
// 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
|
// TODO: Add list of tracked parts in workspace based on their ancestry using inWorkspace property of Instance
|
1195
core/src/rendering/defaultmeshes.cpp
Normal file
1195
core/src/rendering/defaultmeshes.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -2,5 +2,7 @@
|
||||||
#include "mesh.h"
|
#include "mesh.h"
|
||||||
|
|
||||||
extern Mesh* CUBE_MESH;
|
extern Mesh* CUBE_MESH;
|
||||||
|
extern Mesh* SPHERE_MESH;
|
||||||
|
extern Mesh* ARROW_MESH;
|
||||||
|
|
||||||
void initMeshes();
|
void initMeshes();
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
#include "mesh.h"
|
#include "mesh.h"
|
||||||
|
|
||||||
Mesh::Mesh(int vertexCount, float *vertices) {
|
Mesh::Mesh(int vertexCount, float *vertices) : vertexCount(vertexCount) {
|
||||||
// Generate buffers
|
// Generate buffers
|
||||||
glGenBuffers(1, &VBO);
|
glGenBuffers(1, &VBO);
|
||||||
glGenVertexArrays(1, &VAO);
|
glGenVertexArrays(1, &VAO);
|
|
@ -4,6 +4,8 @@ class Mesh {
|
||||||
unsigned int VBO, VAO;
|
unsigned int VBO, VAO;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
int vertexCount;
|
||||||
|
|
||||||
Mesh(int vertexCount, float* vertices);
|
Mesh(int vertexCount, float* vertices);
|
||||||
~Mesh();
|
~Mesh();
|
||||||
void bind();
|
void bind();
|
|
@ -5,11 +5,13 @@
|
||||||
#include <glm/ext.hpp>
|
#include <glm/ext.hpp>
|
||||||
#include <glm/ext/matrix_float4x4.hpp>
|
#include <glm/ext/matrix_float4x4.hpp>
|
||||||
#include <glm/ext/matrix_transform.hpp>
|
#include <glm/ext/matrix_transform.hpp>
|
||||||
|
#include <glm/ext/vector_float3.hpp>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/trigonometric.hpp>
|
#include <glm/trigonometric.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "datatypes/cframe.h"
|
||||||
#include "physics/util.h"
|
#include "physics/util.h"
|
||||||
#include "shader.h"
|
#include "shader.h"
|
||||||
#include "mesh.h"
|
#include "mesh.h"
|
||||||
|
@ -23,8 +25,10 @@
|
||||||
|
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
|
|
||||||
Shader *shader = NULL;
|
Shader* shader = NULL;
|
||||||
Shader *skyboxShader = NULL;
|
Shader* skyboxShader = NULL;
|
||||||
|
Shader* handleShader = NULL;
|
||||||
|
Shader* identityShader = NULL;
|
||||||
extern Camera camera;
|
extern Camera camera;
|
||||||
Skybox* skyboxTexture = NULL;
|
Skybox* skyboxTexture = NULL;
|
||||||
Texture3D* studsTexture = NULL;
|
Texture3D* studsTexture = NULL;
|
||||||
|
@ -54,13 +58,20 @@ void renderInit(GLFWwindow* window, int width, int height) {
|
||||||
|
|
||||||
studsTexture = new Texture3D("assets/textures/studs.png", 128, 128, 6, GL_RGBA);
|
studsTexture = new Texture3D("assets/textures/studs.png", 128, 128, 6, GL_RGBA);
|
||||||
|
|
||||||
// Compile shader
|
// Compile shaders
|
||||||
shader = new Shader("assets/shaders/phong.vs", "assets/shaders/phong.fs");
|
shader = new Shader("assets/shaders/phong.vs", "assets/shaders/phong.fs");
|
||||||
skyboxShader = new Shader("assets/shaders/skybox.vs", "assets/shaders/skybox.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() {
|
void renderParts() {
|
||||||
glDepthMask(GL_TRUE);
|
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
|
// Use shader
|
||||||
shader->use();
|
shader->use();
|
||||||
|
@ -68,7 +79,7 @@ void renderParts() {
|
||||||
// shader->set("lightColor", glm::vec3(1.0f, 1.0f, 1.0f));
|
// shader->set("lightColor", glm::vec3(1.0f, 1.0f, 1.0f));
|
||||||
|
|
||||||
// view/projection transformations
|
// view/projection transformations
|
||||||
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)viewportWidth / (float)viewportHeight, 0.1f, 100.0f);
|
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)viewportWidth / (float)viewportHeight, 0.1f, 1000.0f);
|
||||||
glm::mat4 view = camera.getLookAt();
|
glm::mat4 view = camera.getLookAt();
|
||||||
shader->set("projection", projection);
|
shader->set("projection", projection);
|
||||||
shader->set("view", view);
|
shader->set("view", view);
|
||||||
|
@ -105,27 +116,40 @@ void renderParts() {
|
||||||
// Pass in the camera position
|
// Pass in the camera position
|
||||||
shader->set("viewPos", camera.cameraPos);
|
shader->set("viewPos", camera.cameraPos);
|
||||||
|
|
||||||
// TODO: Same as todo in src/physics/simulation.cpp
|
// Sort by nearest
|
||||||
|
std::map<float, std::shared_ptr<Part>> sorted;
|
||||||
for (InstanceRef inst : workspace()->GetChildren()) {
|
for (InstanceRef inst : workspace()->GetChildren()) {
|
||||||
if (inst->GetClass()->className != "Part") continue;
|
if (inst->GetClass()->className != "Part") continue;
|
||||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
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);
|
||||||
|
|
||||||
// if (inst->name == "Target") printf("(%f,%f,%f):(%f,%f,%f;%f,%f,%f;%f,%f,%f)\n",
|
CUBE_MESH->bind();
|
||||||
// part->cframe.X(),
|
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
|
||||||
// part->cframe.Y(),
|
}
|
||||||
// part->cframe.Z(),
|
}
|
||||||
// part->cframe.RightVector().X(),
|
|
||||||
// part->cframe.UpVector().X(),
|
|
||||||
// part->cframe.LookVector().X(),
|
|
||||||
// part->cframe.RightVector().Y(),
|
|
||||||
// part->cframe.UpVector().Y(),
|
|
||||||
// part->cframe.LookVector().Y(),
|
|
||||||
// part->cframe.RightVector().Z(),
|
|
||||||
// part->cframe.UpVector().Z(),
|
|
||||||
// part->cframe.LookVector().Z()
|
|
||||||
// );
|
|
||||||
|
|
||||||
|
// 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;
|
glm::mat4 model = part->cframe;
|
||||||
|
if (part->name == "camera") model = camera.getLookAt();
|
||||||
model = glm::scale(model, part->size);
|
model = glm::scale(model, part->size);
|
||||||
shader->set("model", model);
|
shader->set("model", model);
|
||||||
shader->set("material", Material {
|
shader->set("material", Material {
|
||||||
|
@ -136,18 +160,20 @@ void renderParts() {
|
||||||
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
|
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
|
||||||
shader->set("normalMatrix", normalMatrix);
|
shader->set("normalMatrix", normalMatrix);
|
||||||
shader->set("texScale", part->size);
|
shader->set("texScale", part->size);
|
||||||
|
shader->set("transparency", part->transparency);
|
||||||
|
|
||||||
CUBE_MESH->bind();
|
CUBE_MESH->bind();
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 36);
|
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderSkyBox() {
|
void renderSkyBox() {
|
||||||
glDepthMask(GL_FALSE);
|
glDepthMask(GL_FALSE);
|
||||||
|
glCullFace(GL_FRONT);
|
||||||
|
|
||||||
skyboxShader->use();
|
skyboxShader->use();
|
||||||
|
|
||||||
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)viewportWidth / (float)viewportHeight, 0.1f, 100.0f);
|
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)
|
// Remove translation component of view, making us always at (0, 0, 0)
|
||||||
glm::mat4 view = glm::mat4(glm::mat3(camera.getLookAt()));
|
glm::mat4 view = glm::mat4(glm::mat3(camera.getLookAt()));
|
||||||
|
|
||||||
|
@ -160,11 +186,82 @@ void renderSkyBox() {
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 36);
|
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) {
|
void render(GLFWwindow* window) {
|
||||||
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
renderSkyBox();
|
renderSkyBox();
|
||||||
|
renderHandles();
|
||||||
renderParts();
|
renderParts();
|
||||||
}
|
}
|
||||||
|
|
9
deps.txt
9
deps.txt
|
@ -1,10 +1,9 @@
|
||||||
glm
|
opengl (Linux: glvnd, Windows: [built-in/none])
|
||||||
opengl
|
|
||||||
assimp
|
|
||||||
sdl2
|
|
||||||
glfw
|
glfw
|
||||||
glut
|
|
||||||
glew
|
glew
|
||||||
|
glm
|
||||||
|
sdl2
|
||||||
|
stb
|
||||||
qt6
|
qt6
|
||||||
reactphysics3d
|
reactphysics3d
|
||||||
pugixml
|
pugixml
|
|
@ -1,4 +1,4 @@
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.31..)
|
||||||
|
|
||||||
project(editor VERSION 0.1 LANGUAGES CXX)
|
project(editor VERSION 0.1 LANGUAGES CXX)
|
||||||
|
|
||||||
|
@ -14,8 +14,6 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
|
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
|
||||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)
|
||||||
|
|
||||||
include_directories("../src")
|
|
||||||
|
|
||||||
set(TS_FILES editor_en_US.ts)
|
set(TS_FILES editor_en_US.ts)
|
||||||
|
|
||||||
set(PROJECT_SOURCES
|
set(PROJECT_SOURCES
|
||||||
|
@ -40,7 +38,6 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||||
qt_add_executable(editor
|
qt_add_executable(editor
|
||||||
MANUAL_FINALIZATION
|
MANUAL_FINALIZATION
|
||||||
${PROJECT_SOURCES}
|
${PROJECT_SOURCES}
|
||||||
$<TARGET_OBJECTS:openblocks>
|
|
||||||
)
|
)
|
||||||
# Define target properties for Android with Qt 6 as:
|
# Define target properties for Android with Qt 6 as:
|
||||||
# set_property(TARGET editor APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
# set_property(TARGET editor APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||||
|
@ -59,14 +56,24 @@ else()
|
||||||
add_executable(editor
|
add_executable(editor
|
||||||
${PROJECT_SOURCES}
|
${PROJECT_SOURCES}
|
||||||
mainglwidget.h mainglwidget.cpp
|
mainglwidget.h mainglwidget.cpp
|
||||||
$<TARGET_OBJECTS:openblocks>
|
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
|
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(editor PRIVATE Qt${QT_VERSION_MAJOR}::Widgets ${SDL2_LIBRARIES} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} OpenGL::GL OpenGL::GLU glfw glm::glm assimp ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
|
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.
|
# 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
|
# If you are developing for iOS or macOS you should consider setting an
|
||||||
|
@ -89,6 +96,8 @@ install(TARGETS editor
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
install(FILES $<TARGET_RUNTIME_DLLS:editor> TYPE BIN)
|
||||||
|
|
||||||
if(QT_VERSION_MAJOR EQUAL 6)
|
if(QT_VERSION_MAJOR EQUAL 6)
|
||||||
qt_finalize_executable(editor)
|
qt_finalize_executable(editor)
|
||||||
endif()
|
endif()
|
|
@ -1,10 +1 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
enum SelectedTool {
|
|
||||||
SELECT,
|
|
||||||
MOVE,
|
|
||||||
SCALE,
|
|
||||||
ROTATE,
|
|
||||||
};
|
|
||||||
|
|
||||||
extern SelectedTool selectedTool;
|
|
|
@ -2,15 +2,22 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
|
#include <glm/common.hpp>
|
||||||
#include <glm/ext/matrix_projection.hpp>
|
#include <glm/ext/matrix_projection.hpp>
|
||||||
|
#include <glm/ext/matrix_transform.hpp>
|
||||||
#include <glm/ext/vector_float3.hpp>
|
#include <glm/ext/vector_float3.hpp>
|
||||||
#include <glm/geometric.hpp>
|
#include <glm/geometric.hpp>
|
||||||
|
#include <glm/gtc/round.hpp>
|
||||||
#include <glm/matrix.hpp>
|
#include <glm/matrix.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <reactphysics3d/collision/RaycastInfo.h>
|
#include <reactphysics3d/collision/RaycastInfo.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "datatypes/cframe.h"
|
||||||
|
#include "editorcommon.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
#include "objects/handles.h"
|
||||||
#include "physics/util.h"
|
#include "physics/util.h"
|
||||||
#include "qcursor.h"
|
#include "qcursor.h"
|
||||||
#include "qevent.h"
|
#include "qevent.h"
|
||||||
|
@ -21,8 +28,11 @@
|
||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "rendering/shader.h"
|
||||||
|
|
||||||
#include "mainglwidget.h"
|
#include "mainglwidget.h"
|
||||||
|
#include "../core/src/rendering/defaultmeshes.h"
|
||||||
|
#include "math_helper.h"
|
||||||
|
|
||||||
MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent) {
|
MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent) {
|
||||||
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
|
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
|
||||||
|
@ -45,6 +55,12 @@ void MainGLWidget::resizeGL(int w, int h) {
|
||||||
setViewport(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() {
|
void MainGLWidget::paintGL() {
|
||||||
::render(NULL);
|
::render(NULL);
|
||||||
}
|
}
|
||||||
|
@ -62,8 +78,9 @@ void MainGLWidget::handleCameraRotate(QMouseEvent* evt) {
|
||||||
|
|
||||||
bool isMouseDragging = false;
|
bool isMouseDragging = false;
|
||||||
std::optional<std::weak_ptr<Part>> draggingObject;
|
std::optional<std::weak_ptr<Part>> draggingObject;
|
||||||
|
std::optional<HandleFace> draggingHandle;
|
||||||
void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
|
void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
|
||||||
if (!isMouseDragging) return;
|
if (!isMouseDragging || !draggingObject) return;
|
||||||
|
|
||||||
QPoint position = evt->pos();
|
QPoint position = evt->pos();
|
||||||
|
|
||||||
|
@ -79,17 +96,109 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
|
||||||
syncPartPhysics(draggingObject->lock());
|
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) {
|
void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
|
||||||
QPoint position = evt->pos();
|
QPoint position = evt->pos();
|
||||||
|
|
||||||
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
|
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);
|
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000);
|
||||||
setCursor((rayHit && partFromBody(rayHit->body)->name != "Baseplate") ? Qt::OpenHandCursor : Qt::ArrowCursor);
|
if (rayHit && partFromBody(rayHit->body)->name != "Baseplate") {
|
||||||
|
setCursor(Qt::OpenHandCursor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCursor(Qt::ArrowCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
|
void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
|
||||||
handleCameraRotate(evt);
|
handleCameraRotate(evt);
|
||||||
handleObjectDrag(evt);
|
handleObjectDrag(evt);
|
||||||
|
handleHandleDrag(evt);
|
||||||
handleCursorChange(evt);
|
handleCursorChange(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +214,15 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
||||||
QPoint position = evt->pos();
|
QPoint position = evt->pos();
|
||||||
|
|
||||||
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
|
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);
|
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000);
|
||||||
if (!rayHit || !partFromBody(rayHit->body)) return;
|
if (!rayHit || !partFromBody(rayHit->body)) return;
|
||||||
std::shared_ptr<Part> part = partFromBody(rayHit->body);
|
std::shared_ptr<Part> part = partFromBody(rayHit->body);
|
||||||
|
@ -128,6 +246,7 @@ void MainGLWidget::mouseReleaseEvent(QMouseEvent* evt) {
|
||||||
isMouseRightDragging = false;
|
isMouseRightDragging = false;
|
||||||
isMouseDragging = false;
|
isMouseDragging = false;
|
||||||
draggingObject = std::nullopt;
|
draggingObject = std::nullopt;
|
||||||
|
draggingHandle = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int moveZ = 0;
|
static int moveZ = 0;
|
||||||
|
@ -166,4 +285,17 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
|
||||||
void MainGLWidget::keyReleaseEvent(QKeyEvent* evt) {
|
void MainGLWidget::keyReleaseEvent(QKeyEvent* evt) {
|
||||||
if (evt->key() == Qt::Key_W || evt->key() == Qt::Key_S) moveZ = 0;
|
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;
|
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,12 +1,15 @@
|
||||||
#ifndef MAINGLWIDGET_H
|
#ifndef MAINGLWIDGET_H
|
||||||
#define MAINGLWIDGET_H
|
#define MAINGLWIDGET_H
|
||||||
|
|
||||||
|
#include "mainwindow.h"
|
||||||
#include "objects/part.h"
|
#include "objects/part.h"
|
||||||
#include "qevent.h"
|
#include "qevent.h"
|
||||||
#include <QOpenGLWidget>
|
#include <QOpenGLWidget>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
class HandleFace;
|
||||||
|
|
||||||
class MainGLWidget : public QOpenGLWidget {
|
class MainGLWidget : public QOpenGLWidget {
|
||||||
public:
|
public:
|
||||||
MainGLWidget(QWidget *parent = nullptr);
|
MainGLWidget(QWidget *parent = nullptr);
|
||||||
|
@ -20,13 +23,18 @@ protected:
|
||||||
|
|
||||||
void handleCameraRotate(QMouseEvent* evt);
|
void handleCameraRotate(QMouseEvent* evt);
|
||||||
void handleObjectDrag(QMouseEvent* evt);
|
void handleObjectDrag(QMouseEvent* evt);
|
||||||
|
void handleHandleDrag(QMouseEvent* evt);
|
||||||
void handleCursorChange(QMouseEvent* evt);
|
void handleCursorChange(QMouseEvent* evt);
|
||||||
|
std::optional<HandleFace> raycastHandle(glm::vec3 pointDir);
|
||||||
|
|
||||||
void mouseMoveEvent(QMouseEvent* evt) override;
|
void mouseMoveEvent(QMouseEvent* evt) override;
|
||||||
void mousePressEvent(QMouseEvent* evt) override;
|
void mousePressEvent(QMouseEvent* evt) override;
|
||||||
void mouseReleaseEvent(QMouseEvent* evt) override;
|
void mouseReleaseEvent(QMouseEvent* evt) override;
|
||||||
void keyPressEvent(QKeyEvent* evt) override;
|
void keyPressEvent(QKeyEvent* evt) override;
|
||||||
void keyReleaseEvent(QKeyEvent* evt) override;
|
void keyReleaseEvent(QKeyEvent* evt) override;
|
||||||
|
|
||||||
|
MainWindow* mainWindow();
|
||||||
|
float snappingFactor();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MAINGLWIDGET_H
|
#endif // MAINGLWIDGET_H
|
||||||
|
|
|
@ -10,21 +10,25 @@
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <QAbstractItemView>
|
#include <QAbstractItemView>
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <qglobal.h>
|
||||||
|
#include <qwindowdefs.h>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "editorcommon.h"
|
#include "editorcommon.h"
|
||||||
#include "objects/base/instance.h"
|
#include "objects/base/instance.h"
|
||||||
#include "objects/datamodel.h"
|
#include "objects/datamodel.h"
|
||||||
|
#include "objects/handles.h"
|
||||||
#include "physics/simulation.h"
|
#include "physics/simulation.h"
|
||||||
#include "objects/part.h"
|
#include "objects/part.h"
|
||||||
#include "qfiledialog.h"
|
#include "qfiledialog.h"
|
||||||
#include "qitemselectionmodel.h"
|
#include "qclipboard.h"
|
||||||
|
#include "qmimedata.h"
|
||||||
#include "qobject.h"
|
#include "qobject.h"
|
||||||
#include "qsysinfo.h"
|
#include "qsysinfo.h"
|
||||||
|
|
||||||
SelectedTool selectedTool;
|
|
||||||
|
|
||||||
bool simulationPlaying = false;
|
bool simulationPlaying = false;
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent)
|
MainWindow::MainWindow(QWidget *parent)
|
||||||
|
@ -37,13 +41,20 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
timer.start(33, this);
|
timer.start(33, this);
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
|
|
||||||
ConnectSelectionChangeHandler();
|
ui->explorerView->buildContextMenu();
|
||||||
|
|
||||||
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = SelectedTool::SELECT; updateSelectedTool(); });
|
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = SelectedTool::SELECT; updateToolbars(); });
|
||||||
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::MOVE : SelectedTool::SELECT; updateSelectedTool(); });
|
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; updateSelectedTool(); });
|
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; updateSelectedTool(); });
|
connect(ui->actionToolRotate, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::ROTATE : SelectedTool::SELECT; updateToolbars(); });
|
||||||
ui->actionToolSelect->setChecked(true);
|
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, [&]() {
|
connect(ui->actionToggleSimulation, &QAction::triggered, this, [&]() {
|
||||||
simulationPlaying = !simulationPlaying;
|
simulationPlaying = !simulationPlaying;
|
||||||
|
@ -61,23 +72,144 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
connect(ui->actionSave, &QAction::triggered, this, [&]() {
|
connect(ui->actionSave, &QAction::triggered, this, [&]() {
|
||||||
std::optional<std::string> path;
|
std::optional<std::string> path;
|
||||||
if (!dataModel->HasFile())
|
if (!dataModel->HasFile())
|
||||||
path = QFileDialog::getSaveFileName(this, QString::fromStdString("Save " + dataModel->name), "", "*.obl").toStdString();
|
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + dataModel->name));
|
||||||
if (path == "") return;
|
if (path == "") return;
|
||||||
|
|
||||||
dataModel->SaveToFile(path);
|
dataModel->SaveToFile(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ui->actionOpen, &QAction::triggered, this, [&]() {
|
connect(ui->actionOpen, &QAction::triggered, this, [&]() {
|
||||||
std::string path = QFileDialog::getOpenFileName(this, "Load file", "", "*.obl").toStdString();
|
std::optional<std::string> path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptOpen);
|
||||||
if (path == "") return;
|
if (!path) return;
|
||||||
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path);
|
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value());
|
||||||
dataModel = newModel;
|
dataModel = newModel;
|
||||||
delete ui->explorerView->selectionModel();
|
ui->explorerView->updateRoot(newModel);
|
||||||
ui->explorerView->reset();
|
});
|
||||||
ui->explorerView->setModel(new ExplorerModel(dataModel));
|
|
||||||
ConnectSelectionChangeHandler();
|
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);
|
// ui->explorerView->Init(ui);
|
||||||
|
|
||||||
simulationInit();
|
simulationInit();
|
||||||
|
@ -95,24 +227,13 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
|
|
||||||
workspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
workspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||||
.position = glm::vec3(0),
|
.position = glm::vec3(0),
|
||||||
.rotation = glm::vec3(0),
|
.rotation = glm::vec3(0.5, 2, 1),
|
||||||
.size = glm::vec3(4, 1.2, 2),
|
.size = glm::vec3(4, 1.2, 2),
|
||||||
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
|
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
|
||||||
}));
|
}));
|
||||||
syncPartPhysics(ui->mainWidget->lastPart);
|
syncPartPhysics(ui->mainWidget->lastPart);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::ConnectSelectionChangeHandler() {
|
|
||||||
connect(ui->explorerView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) {
|
|
||||||
if (selected.count() == 0) return;
|
|
||||||
|
|
||||||
std::optional<InstanceRef> inst = selected.count() == 0 ? std::nullopt
|
|
||||||
: std::make_optional(((Instance*)selected.indexes()[0].internalPointer())->shared_from_this());
|
|
||||||
|
|
||||||
ui->propertiesView->setSelected(inst);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::chrono::time_point lastTime = std::chrono::steady_clock::now();
|
static std::chrono::time_point lastTime = std::chrono::steady_clock::now();
|
||||||
void MainWindow::timerEvent(QTimerEvent* evt) {
|
void MainWindow::timerEvent(QTimerEvent* evt) {
|
||||||
if (evt->timerId() != timer.timerId()) {
|
if (evt->timerId() != timer.timerId()) {
|
||||||
|
@ -129,11 +250,37 @@ void MainWindow::timerEvent(QTimerEvent* evt) {
|
||||||
ui->mainWidget->updateCycle();
|
ui->mainWidget->updateCycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::updateSelectedTool() {
|
void MainWindow::updateToolbars() {
|
||||||
ui->actionToolSelect->setChecked(selectedTool == SelectedTool::SELECT);
|
ui->actionToolSelect->setChecked(selectedTool == SelectedTool::SELECT);
|
||||||
ui->actionToolMove->setChecked(selectedTool == SelectedTool::MOVE);
|
ui->actionToolMove->setChecked(selectedTool == SelectedTool::MOVE);
|
||||||
ui->actionToolScale->setChecked(selectedTool == SelectedTool::SCALE);
|
ui->actionToolScale->setChecked(selectedTool == SelectedTool::SCALE);
|
||||||
ui->actionToolRotate->setChecked(selectedTool == SelectedTool::ROTATE);
|
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()
|
MainWindow::~MainWindow()
|
||||||
|
|
|
@ -7,6 +7,20 @@
|
||||||
#include "qmenu.h"
|
#include "qmenu.h"
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
|
#include <qfiledialog.h>
|
||||||
|
|
||||||
|
enum SelectedTool {
|
||||||
|
SELECT,
|
||||||
|
MOVE,
|
||||||
|
SCALE,
|
||||||
|
ROTATE,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum GridSnappingMode {
|
||||||
|
SNAP_1_STUD,
|
||||||
|
SNAP_05_STUDS,
|
||||||
|
SNAP_OFF,
|
||||||
|
};
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
@ -22,12 +36,16 @@ public:
|
||||||
MainWindow(QWidget *parent = nullptr);
|
MainWindow(QWidget *parent = nullptr);
|
||||||
~MainWindow();
|
~MainWindow();
|
||||||
|
|
||||||
|
SelectedTool selectedTool;
|
||||||
|
GridSnappingMode snappingMode;
|
||||||
|
|
||||||
Ui::MainWindow *ui;
|
Ui::MainWindow *ui;
|
||||||
private:
|
private:
|
||||||
QBasicTimer timer;
|
QBasicTimer timer;
|
||||||
|
|
||||||
void updateSelectedTool();
|
void updateToolbars();
|
||||||
void timerEvent(QTimerEvent*) override;
|
void timerEvent(QTimerEvent*) override;
|
||||||
void ConnectSelectionChangeHandler();
|
|
||||||
|
std::optional<std::string> openFileDialog(QString filter, QString defaultExtension, QFileDialog::AcceptMode acceptMode, QString title = "");
|
||||||
};
|
};
|
||||||
#endif // MAINWINDOW_H
|
#endif // MAINWINDOW_H
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1027</width>
|
<width>1027</width>
|
||||||
<height>29</height>
|
<height>30</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuFile">
|
<widget class="QMenu" name="menuFile">
|
||||||
|
@ -115,6 +115,16 @@
|
||||||
<addaction name="actionToolScale"/>
|
<addaction name="actionToolScale"/>
|
||||||
<addaction name="actionToolRotate"/>
|
<addaction name="actionToolRotate"/>
|
||||||
<addaction name="separator"/>
|
<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"/>
|
<addaction name="actionToggleSimulation"/>
|
||||||
</widget>
|
</widget>
|
||||||
<action name="actionAddPart">
|
<action name="actionAddPart">
|
||||||
|
@ -257,6 +267,167 @@
|
||||||
<string>F5</string>
|
<string>F5</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</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>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|
|
@ -234,6 +234,12 @@ bool ExplorerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExplorerModel::updateRoot(InstanceRef newRoot) {
|
||||||
|
beginResetModel();
|
||||||
|
rootItem = newRoot;
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
QMimeData* ExplorerModel::mimeData(const QModelIndexList& indexes) const {
|
QMimeData* ExplorerModel::mimeData(const QModelIndexList& indexes) const {
|
||||||
// application/x-openblocks-instance-pointers
|
// application/x-openblocks-instance-pointers
|
||||||
DragDropSlot* slot = new DragDropSlot();
|
DragDropSlot* slot = new DragDropSlot();
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "objects/base/instance.h"
|
#include "objects/base/instance.h"
|
||||||
#include "objects/part.h"
|
|
||||||
#include "qabstractitemmodel.h"
|
#include "qabstractitemmodel.h"
|
||||||
#include "qevent.h"
|
#include "qevent.h"
|
||||||
#include "qmenu.h"
|
|
||||||
#include "qnamespace.h"
|
#include "qnamespace.h"
|
||||||
#include "qtreeview.h"
|
|
||||||
#include <QOpenGLWidget>
|
#include <QOpenGLWidget>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
// #ifndef EXPLORERMODEL_H
|
|
||||||
// #define EXPLORERMODEL_H
|
|
||||||
|
|
||||||
class ExplorerModel : public QAbstractItemModel {
|
class ExplorerModel : public QAbstractItemModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -42,6 +36,8 @@ public:
|
||||||
Qt::DropActions supportedDropActions() const override;
|
Qt::DropActions supportedDropActions() const override;
|
||||||
InstanceRef fromIndex(const QModelIndex index) const;
|
InstanceRef fromIndex(const QModelIndex index) const;
|
||||||
QModelIndex ObjectToIndex(InstanceRef item);
|
QModelIndex ObjectToIndex(InstanceRef item);
|
||||||
|
|
||||||
|
void updateRoot(InstanceRef newRoot);
|
||||||
private:
|
private:
|
||||||
InstanceRef rootItem;
|
InstanceRef rootItem;
|
||||||
QModelIndex toIndex(InstanceRef item);
|
QModelIndex toIndex(InstanceRef item);
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
#include "explorerview.h"
|
#include "explorerview.h"
|
||||||
#include "explorermodel.h"
|
#include "explorermodel.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
#include "../ui_mainwindow.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "objects/base/instance.h"
|
#include "objects/base/instance.h"
|
||||||
#include "objects/workspace.h"
|
|
||||||
#include "qabstractitemmodel.h"
|
#include "qabstractitemmodel.h"
|
||||||
#include "qaction.h"
|
#include <qaction.h>
|
||||||
#include "qnamespace.h"
|
#include <qnamespace.h>
|
||||||
|
#include <qitemselectionmodel.h>
|
||||||
|
|
||||||
|
#define M_mainWindow dynamic_cast<MainWindow*>(window())
|
||||||
|
|
||||||
ExplorerView::ExplorerView(QWidget* parent):
|
ExplorerView::ExplorerView(QWidget* parent):
|
||||||
QTreeView(parent),
|
QTreeView(parent),
|
||||||
|
@ -32,7 +36,23 @@ ExplorerView::ExplorerView(QWidget* parent):
|
||||||
contextMenu.exec(this->viewport()->mapToGlobal(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) {
|
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
|
||||||
|
// It's from us, ignore it.
|
||||||
|
if (fromExplorer) return;
|
||||||
|
|
||||||
this->clearSelection();
|
this->clearSelection();
|
||||||
for (InstanceRefWeak inst : newSelection) {
|
for (InstanceRefWeak inst : newSelection) {
|
||||||
if (inst.expired()) continue;
|
if (inst.expired()) continue;
|
||||||
|
@ -40,8 +60,6 @@ ExplorerView::ExplorerView(QWidget* parent):
|
||||||
this->selectionModel()->select(index, QItemSelectionModel::SelectionFlag::Select);
|
this->selectionModel()->select(index, QItemSelectionModel::SelectionFlag::Select);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
buildContextMenu();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExplorerView::~ExplorerView() {
|
ExplorerView::~ExplorerView() {
|
||||||
|
@ -50,19 +68,23 @@ ExplorerView::~ExplorerView() {
|
||||||
void ExplorerView::keyPressEvent(QKeyEvent* event) {
|
void ExplorerView::keyPressEvent(QKeyEvent* event) {
|
||||||
switch (event->key()) {
|
switch (event->key()) {
|
||||||
case Qt::Key_Delete:
|
case Qt::Key_Delete:
|
||||||
actionDelete->trigger();
|
M_mainWindow->ui->actionDelete->trigger();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExplorerView::buildContextMenu() {
|
void ExplorerView::buildContextMenu() {
|
||||||
// This will leak memory. Anyway...
|
contextMenu.addAction(M_mainWindow->ui->actionDelete);
|
||||||
contextMenu.addAction(this->actionDelete = new QAction(QIcon("assets/icons/editor/delete"), "Delete"));
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
connect(actionDelete, &QAction::triggered, this, [&]() {
|
void ExplorerView::updateRoot(InstanceRef newRoot) {
|
||||||
QModelIndexList selectedIndexes = this->selectionModel()->selectedIndexes();
|
model.updateRoot(newRoot);
|
||||||
for (QModelIndex index : selectedIndexes) {
|
|
||||||
model.fromIndex(index)->SetParent(std::nullopt);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
|
@ -20,18 +20,10 @@ public:
|
||||||
|
|
||||||
void keyPressEvent(QKeyEvent*) override;
|
void keyPressEvent(QKeyEvent*) override;
|
||||||
// void dropEvent(QDropEvent*) override;
|
// void dropEvent(QDropEvent*) override;
|
||||||
|
|
||||||
|
void buildContextMenu();
|
||||||
|
void updateRoot(InstanceRef newRoot);
|
||||||
private:
|
private:
|
||||||
ExplorerModel model;
|
ExplorerModel model;
|
||||||
QMenu contextMenu;
|
QMenu contextMenu;
|
||||||
|
|
||||||
// TODO: Move these to a separate top-level namespace so these can be
|
|
||||||
// accessed from multiple locations
|
|
||||||
QAction* actionDelete;
|
|
||||||
QAction* actionCopy;
|
|
||||||
QAction* actionCut;
|
|
||||||
QAction* actionPaste;
|
|
||||||
QAction* actionPasteInto;
|
|
||||||
QAction* actionSelectChildren;
|
|
||||||
|
|
||||||
void buildContextMenu();
|
|
||||||
};
|
};
|
|
@ -56,10 +56,10 @@ bool PropertiesModel::setData(const QModelIndex &index, const QVariant &value, i
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::EditRole:
|
case Qt::EditRole:
|
||||||
if (meta.type != &Data::String::TYPE)
|
if (!meta.type->fromString)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
selectedItem->SetPropertyValue(propertyName, value.toString().toStdString());
|
selectedItem->SetPropertyValue(propertyName, meta.type->fromString(value.toString().toStdString()));
|
||||||
return true;
|
return true;
|
||||||
case Qt::CheckStateRole:
|
case Qt::CheckStateRole:
|
||||||
if (meta.type != &Data::Bool::TYPE)
|
if (meta.type != &Data::Bool::TYPE)
|
||||||
|
|
4
run.sh
4
run.sh
|
@ -1,7 +1,9 @@
|
||||||
|
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" = "-debug" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=Debug
|
||||||
[ "$2" = "-release" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=Release
|
[ "$2" = "-release" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=Release
|
||||||
[ "$2" = "-reldbg" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=RelWithDebInfo
|
[ "$2" = "-reldbg" ] && CMAKE_OPTS=-DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
|
|
||||||
[ "$3" = "-gdb" ] && PRE_COMMAND="gdb -ex run "
|
[ "$3" = "-gdb" ] && PRE_COMMAND="gdb -ex run "
|
||||||
|
|
||||||
cmake $CMAKE_OPTS . && cmake --build . && $PRE_COMMAND ./bin/$1
|
cmake -Bbuild $CMAKE_OPTS . && (cd build; cmake --build .; cd ..) && $PRE_COMMAND ./build/bin/$1
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
#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);
|
|
||||||
}
|
|
78
tools/genmesh.py
Executable file
78
tools/genmesh.py
Executable file
|
@ -0,0 +1,78 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
# Default mesh generator
|
||||||
|
# Input from wavefront obj file
|
||||||
|
|
||||||
|
# Usage: ./genmesh.py mesh.obj > mesh.cpp
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
HEADER = """
|
||||||
|
static float CUBE_VERTICES[] = {
|
||||||
|
// positions // normals // texture coords
|
||||||
|
"""
|
||||||
|
|
||||||
|
FOOTER = """
|
||||||
|
};"""
|
||||||
|
|
||||||
|
file = open(sys.argv[1], "r")
|
||||||
|
|
||||||
|
vert_coords = []
|
||||||
|
vert_norms = []
|
||||||
|
vert_uvs = []
|
||||||
|
|
||||||
|
out_vertices = []
|
||||||
|
|
||||||
|
min_coords: tuple[float, float, float] | None = None
|
||||||
|
max_coords: tuple[float, float, float] | None = None
|
||||||
|
|
||||||
|
def normalize(x, y, z):
|
||||||
|
assert min_coords
|
||||||
|
assert max_coords
|
||||||
|
return ((x-max_coords[0])/(max_coords[0]-min_coords[0])+0.5, (y-max_coords[1])/(max_coords[1]-min_coords[1])+0.5, (z-max_coords[2])/(max_coords[2]-min_coords[2])+0.5)
|
||||||
|
|
||||||
|
for line in file:
|
||||||
|
if line.startswith('v '):
|
||||||
|
coords = line.split(' ')[1:]
|
||||||
|
coords = (float(coords[0]), float(coords[1]), float(coords[2]))
|
||||||
|
vert_coords.append(coords)
|
||||||
|
|
||||||
|
if not min_coords: min_coords = coords
|
||||||
|
if not max_coords: max_coords = coords
|
||||||
|
|
||||||
|
if coords[0] > max_coords[0]: max_coords = (coords[0], max_coords[1], max_coords[2])
|
||||||
|
if coords[1] > max_coords[1]: max_coords = (max_coords[0], coords[1], max_coords[2])
|
||||||
|
if coords[2] > max_coords[2]: max_coords = (max_coords[0], max_coords[1], coords[2])
|
||||||
|
|
||||||
|
if coords[0] < min_coords[0]: min_coords = (coords[0], min_coords[1], min_coords[2])
|
||||||
|
if coords[1] < min_coords[1]: min_coords = (min_coords[0], coords[1], min_coords[2])
|
||||||
|
if coords[2] < min_coords[2]: min_coords = (min_coords[0], min_coords[1], coords[2])
|
||||||
|
|
||||||
|
if line.startswith('vn '):
|
||||||
|
coords = line.split(' ')[1:]
|
||||||
|
vert_norms.append((float(coords[0]), float(coords[1]), float(coords[2])))
|
||||||
|
|
||||||
|
if line.startswith('vt '):
|
||||||
|
coords = line.split(' ')[1:]
|
||||||
|
vert_uvs.append((float(coords[0]), float(coords[1])))
|
||||||
|
|
||||||
|
if line.startswith('f '):
|
||||||
|
verts = line.split(' ')[1:]
|
||||||
|
for vert in verts:
|
||||||
|
coords, uv, normal = vert.split('/')
|
||||||
|
coords, uv, normal = int(coords), int(uv), int(normal)
|
||||||
|
coords, uv, normal = vert_coords[coords-1], vert_uvs[uv-1], vert_norms[normal-1]
|
||||||
|
|
||||||
|
coords = normalize(*coords)
|
||||||
|
# for coord in [*normal]:
|
||||||
|
# if coord > greatest_coord: greatest_coord = coord
|
||||||
|
# if coord < least_coord: least_coord = coord
|
||||||
|
|
||||||
|
out_vertices.append((coords, normal, uv))
|
||||||
|
|
||||||
|
print(HEADER)
|
||||||
|
|
||||||
|
for coords, normal, uv in out_vertices:
|
||||||
|
print(f"\t{coords[0]}, {coords[1]}, {coords[2]},\t{normal[0]}, {normal[1]}, {normal[2]},\t{uv[0]}, {uv[1]},")
|
||||||
|
|
||||||
|
print(FOOTER)
|
Loading…
Reference in a new issue