Compare commits
42 commits
96c82dbf22
...
077b153953
Author | SHA1 | Date | |
---|---|---|---|
077b153953 | |||
024d6a9243 | |||
b33ae784ca | |||
c0deba3b2b | |||
c14dfbea32 | |||
e57a781c01 | |||
9d72d7f47a | |||
6d733e82d4 | |||
0ca65b1306 | |||
c7b9e873ee | |||
0c4ef35ac7 | |||
d5e24bf3ca | |||
2b650c0fed | |||
253c617d19 | |||
f5931c746d | |||
5f726ad92b | |||
5f3bed1c58 | |||
6a58aa7fbd | |||
1f296e5fe4 | |||
46856a06e2 | |||
53b1788588 | |||
10d69ce7ac | |||
5149e34723 | |||
0f44012e33 | |||
bb67a246e2 | |||
f9fc8c8cae | |||
5f6ff971d2 | |||
c8880e0edc | |||
14b0667fc9 | |||
e5c8bdd2e2 | |||
80ec1a9132 | |||
1af34a21fa | |||
964c733f53 | |||
19b6489476 | |||
497a3f783c | |||
215fa141b6 | |||
f6d5ebd7c7 | |||
8b7fef624f | |||
3a3b2d12c9 | |||
778a5e35a4 | |||
18b12ea1ad | |||
49f29b6af1 |
103 changed files with 2403 additions and 1246 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
|||
/bin/
|
||||
/lib/
|
||||
/build/
|
||||
/build-rel
|
||||
/autogen/build
|
||||
|
||||
# Qt
|
||||
|
@ -14,4 +15,4 @@
|
|||
/.gdb_history
|
||||
|
||||
# Excluded assets
|
||||
/assets/excluded
|
||||
/assets/excluded
|
||||
|
|
9
BUILD.md
9
BUILD.md
|
@ -22,6 +22,7 @@ The project will be built using VCPKG and MSVC
|
|||
* Qt 6.8.3 or higher, with MSVC toolchain
|
||||
* CMake
|
||||
* Git (for cloning the repo, optional)
|
||||
* QScintilla already built (see [docs/qscintilla.md](./docs/qscintilla.md)) *\*likely temporary\**
|
||||
|
||||
To start, clone the repository:
|
||||
|
||||
|
@ -37,10 +38,14 @@ Now, generate the build files with cmake via the vcpkg preset:
|
|||
|
||||
cmake -Bbuild . --preset vcpkg
|
||||
|
||||
Then, finally, build in release mode:
|
||||
Then, finally, build in release mode\*:
|
||||
|
||||
cmake --build build --config Release
|
||||
|
||||
The compiled binaries should then be placed in `./build/bin/` and should be ready for redistribution without any further work.
|
||||
|
||||
If any of the compilation steps fail, or the binaries fail to execute, please create an issue so that this can be corrected.
|
||||
If any of the compilation steps fail, or the binaries fail to execute, please create an issue so that this can be corrected.
|
||||
|
||||
\* Release mode is necessary as debug mode copies DLLs that are not linked to the output binary
|
||||
|
||||
DEVELOPER NOTE: AKA Not for you. If you get CUSTOM COMMAND BUILD errors just keep rerunning build
|
|
@ -3,6 +3,12 @@ set(CMAKE_CXX_STANDARD 20)
|
|||
project(openblocks VERSION 0.1.0)
|
||||
set(OpenGL_GL_PREFERENCE "GLVND")
|
||||
|
||||
if (MSVC)
|
||||
add_compile_options(/W4)
|
||||
else()
|
||||
add_compile_options(-Wall -Wextra -pedantic -Wno-unused-parameter)
|
||||
endif()
|
||||
|
||||
set( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" )
|
||||
|
||||
add_subdirectory(autogen)
|
||||
|
|
BIN
assets/icons/folder.png
Normal file
BIN
assets/icons/folder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 537 B |
BIN
assets/icons/model.png
Normal file
BIN
assets/icons/model.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 825 B |
BIN
assets/icons/server-scripts.png
Normal file
BIN
assets/icons/server-scripts.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 802 B |
BIN
assets/icons/server-storage.png
Normal file
BIN
assets/icons/server-storage.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 774 B |
|
@ -5,6 +5,7 @@ uniform vec3 scale;
|
|||
in vec3 vPos;
|
||||
|
||||
out vec4 fColor;
|
||||
uniform vec3 color;
|
||||
|
||||
void main() {
|
||||
// float thickness = 0.2;
|
||||
|
@ -20,5 +21,6 @@ void main() {
|
|||
// else
|
||||
// fColor = vec4(0);
|
||||
|
||||
fColor = vec4(0.204, 0.584, 0.922, 1);
|
||||
// fColor = vec4(0.204, 0.584, 0.922, 1);
|
||||
fColor = vec4(color, 1);
|
||||
}
|
|
@ -8,8 +8,10 @@ add_executable(autogen
|
|||
src/object/codegen.cpp
|
||||
src/data/analysis.cpp
|
||||
src/data/codegen.cpp
|
||||
src/enum/analysis.cpp
|
||||
src/enum/codegen.cpp
|
||||
)
|
||||
|
||||
set_target_properties(autogen PROPERTIES OUTPUT_NAME "autogen")
|
||||
target_link_libraries(autogen -lclang)
|
||||
target_include_directories(autogen PUBLIC "src" ${CLANG_INCLUDE_DIRS})
|
||||
target_link_libraries(autogen ${CLANG_LIBRARY})
|
||||
target_include_directories(autogen PUBLIC "src" ${CLANG_INCLUDE_DIR})
|
|
@ -10,8 +10,6 @@
|
|||
using namespace data;
|
||||
|
||||
static std::string toStaticName(std::string orig) {
|
||||
bool isSnakeCase = orig.find('_') == -1;
|
||||
|
||||
std::string newName = "";
|
||||
int wordStart = 0;
|
||||
for (char c : orig) {
|
||||
|
@ -46,7 +44,6 @@ static void processConstructor(CXCursor cur, ClassAnalysis* state) {
|
|||
|
||||
auto result = parseAnnotationString(propertyDef.value());
|
||||
std::string symbolName = x_clang_toString(clang_getCursorSpelling(cur));
|
||||
CXType retType = clang_getCursorResultType(cur);
|
||||
|
||||
anly.name = result["name"];
|
||||
anly.functionName = "__ctor";
|
||||
|
@ -174,6 +171,25 @@ static bool hasMethod(CXCursor cur, std::string methodName) {
|
|||
return found;
|
||||
}
|
||||
|
||||
static bool hasGenericMethod(CXCursor cur, std::string methodName) {
|
||||
bool found = false;
|
||||
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
|
||||
CXCursorKind kind = clang_getCursorKind(cur);
|
||||
if (kind != CXCursor_CXXMethod) return CXChildVisit_Continue;
|
||||
|
||||
if (x_clang_toString(clang_getCursorSpelling(cur)) != methodName) return CXChildVisit_Continue;
|
||||
|
||||
int numArgs = clang_Cursor_getNumArguments(cur);
|
||||
CXCursor lastParam = clang_Cursor_getArgument(cur, numArgs - 1);
|
||||
std::string lastParamType = x_clang_toString(clang_getTypeSpelling(clang_getCursorType(lastParam)));
|
||||
if (lastParamType != "const TypeMeta") return CXChildVisit_Continue;
|
||||
|
||||
found = true;
|
||||
return CXChildVisit_Break;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
static void processClass(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) {
|
||||
ClassAnalysis anly;
|
||||
|
||||
|
@ -184,6 +200,8 @@ static void processClass(CXCursor cur, AnalysisState* state, std::string classNa
|
|||
anly.serializedName = result["name"];
|
||||
anly.hasFromString = hasMethod(cur, "FromString");
|
||||
anly.isSerializable = hasMethod(cur, "Serialize") && hasMethod(cur, "Deserialize");
|
||||
anly.hasGenericDeserializer = hasGenericMethod(cur, "Deserialize");
|
||||
anly.hasGenericFromString = hasGenericMethod(cur, "FromString");
|
||||
|
||||
if (anly.serializedName == "")
|
||||
anly.serializedName = className;
|
||||
|
|
|
@ -37,6 +37,8 @@ struct ClassAnalysis {
|
|||
std::string headerPath;
|
||||
bool hasFromString;
|
||||
bool isSerializable;
|
||||
bool hasGenericDeserializer;
|
||||
bool hasGenericFromString;
|
||||
std::vector<PropertyAnalysis> properties;
|
||||
std::vector<MethodAnalysis> methods;
|
||||
std::vector<PropertyAnalysis> staticProperties;
|
||||
|
|
|
@ -9,11 +9,6 @@
|
|||
using namespace data;
|
||||
|
||||
static std::map<std::string, std::string> MAPPED_TYPE = {
|
||||
{ "bool", "Data::Bool" },
|
||||
{ "int", "Data::Int" },
|
||||
{ "float", "Data::Float" },
|
||||
{ "std::string", "Data::String" },
|
||||
{ "glm::vec3", "Vector3" },
|
||||
};
|
||||
|
||||
static std::map<std::string, std::string> LUA_CHECK_FUNCS = {
|
||||
|
@ -30,16 +25,36 @@ static std::map<std::string, std::string> LUA_TEST_FUNCS = {
|
|||
{ "std::string", "lua_isstring" },
|
||||
};
|
||||
|
||||
static std::map<std::string, std::string> LUA_PUSH_FUNCS = {
|
||||
{ "bool", "lua_pushboolean" },
|
||||
{ "int", "lua_pushinteger" },
|
||||
{ "float", "lua_pushnumber" },
|
||||
// Handled specially
|
||||
// { "std::string", "lua_pushstring" },
|
||||
};
|
||||
|
||||
static std::string getLuaMethodFqn(std::string className, std::string methodName) {
|
||||
return "__lua_impl__" + className + "__" + methodName;
|
||||
}
|
||||
|
||||
static std::string getMtName(std::string type) {
|
||||
if (type.starts_with("Data::"))
|
||||
return "__mt_" + type.substr(6);
|
||||
// if (type.starts_with("Data::"))
|
||||
// return "__mt_" + type.substr(6);
|
||||
return "__mt_" + type;
|
||||
}
|
||||
|
||||
static std::string pushLuaValue(std::string type, std::string expr) {
|
||||
if (type == "std::string")
|
||||
return "lua_pushstring(L, " + expr + ".c_str())";
|
||||
std::string mappedType = MAPPED_TYPE[type];
|
||||
if (mappedType != "")
|
||||
return mappedType + "(" + expr + ").PushLuaValue(L)";
|
||||
std::string pushFunc = LUA_PUSH_FUNCS[type];
|
||||
if (pushFunc != "")
|
||||
return pushFunc + "(L, " + expr + ")";
|
||||
return expr + ".PushLuaValue(L)";
|
||||
}
|
||||
|
||||
static void writeLuaGetArgument(std::ofstream& out, std::string type, int narg, bool member) {
|
||||
std::string varname = "arg" + std::to_string(narg);
|
||||
narg += 1; // Arguments start at 1
|
||||
|
@ -69,7 +84,7 @@ static void writeLuaTestArgument(std::ofstream& out, std::string type, int narg,
|
|||
}
|
||||
|
||||
static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
|
||||
std::string fqn = "Data::" + state.name;
|
||||
std::string fqn = "" + state.name;
|
||||
|
||||
// Collect all method names to account for overloaded functions
|
||||
std::map<std::string, std::vector<MethodAnalysis>> methods;
|
||||
|
@ -102,14 +117,14 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
|
|||
// Check number of arguments
|
||||
out << "n == " << std::to_string(methodImpl.parameters.size() + 1); // Account for first argument as 'this'
|
||||
|
||||
for (int i = 0; i < methodImpl.parameters.size(); i++) {
|
||||
for (size_t i = 0; i < methodImpl.parameters.size(); i++) {
|
||||
out << " && ";
|
||||
writeLuaTestArgument(out, methodImpl.parameters[i].type, i, true);
|
||||
}
|
||||
|
||||
out << ") {\n"; // End if condition, start if body
|
||||
|
||||
for (int i = 0; i < methodImpl.parameters.size(); i++) {
|
||||
for (size_t i = 0; i < methodImpl.parameters.size(); i++) {
|
||||
writeLuaGetArgument(out, methodImpl.parameters[i].type, i, true);
|
||||
}
|
||||
|
||||
|
@ -122,7 +137,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
|
|||
// Call function
|
||||
out << "this_->" << methodImpl.functionName << "(";
|
||||
|
||||
for (int i = 0; i < methodImpl.parameters.size(); i++) {
|
||||
for (size_t i = 0; i < methodImpl.parameters.size(); i++) {
|
||||
std::string varname = "arg" + std::to_string(i);
|
||||
if (i != 0) out << ", ";
|
||||
out << varname;
|
||||
|
@ -132,11 +147,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
|
|||
|
||||
// Return result
|
||||
if (methodImpl.returnType != "void") {
|
||||
std::string mappedType = MAPPED_TYPE[methodImpl.returnType];
|
||||
if (mappedType == "")
|
||||
out << " result.PushLuaValue(L);\n";
|
||||
else
|
||||
out << " " << mappedType << "(result).PushLuaValue(L);\n";
|
||||
out << " " << pushLuaValue(methodImpl.returnType, "result") << ";\n";
|
||||
}
|
||||
|
||||
if (methodImpl.returnType == "void")
|
||||
|
@ -171,7 +182,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
|
|||
// Check number of arguments
|
||||
out << "n == " << std::to_string(methodImpl.parameters.size());
|
||||
|
||||
for (int i = 0; i < methodImpl.parameters.size(); i++) {
|
||||
for (size_t i = 0; i < methodImpl.parameters.size(); i++) {
|
||||
out << " && ";
|
||||
writeLuaTestArgument(out, methodImpl.parameters[i].type, i, false);
|
||||
}
|
||||
|
@ -179,7 +190,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
|
|||
out << ") {\n"; // End if condition, start if body
|
||||
|
||||
// Get the arguments
|
||||
for (int i = 0; i < methodImpl.parameters.size(); i++) {
|
||||
for (size_t i = 0; i < methodImpl.parameters.size(); i++) {
|
||||
writeLuaGetArgument(out, methodImpl.parameters[i].type, i, false);
|
||||
}
|
||||
|
||||
|
@ -195,7 +206,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
|
|||
else
|
||||
out << fqn << "::" << methodImpl.functionName << "(";
|
||||
|
||||
for (int i = 0; i < methodImpl.parameters.size(); i++) {
|
||||
for (size_t i = 0; i < methodImpl.parameters.size(); i++) {
|
||||
std::string varname = "arg" + std::to_string(i);
|
||||
if (i != 0) out << ", ";
|
||||
out << varname;
|
||||
|
@ -205,11 +216,7 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
|
|||
|
||||
// Return result
|
||||
if (methodImpl.returnType != "void") {
|
||||
std::string mappedType = MAPPED_TYPE[methodImpl.returnType];
|
||||
if (mappedType == "")
|
||||
out << " result.PushLuaValue(L);\n";
|
||||
else
|
||||
out << " " << mappedType << "(result).PushLuaValue(L);\n";
|
||||
out << " " << pushLuaValue(methodImpl.returnType, "result") << ";\n";
|
||||
}
|
||||
|
||||
if (methodImpl.returnType == "void")
|
||||
|
@ -228,45 +235,42 @@ static void writeLuaMethodImpls(std::ofstream& out, ClassAnalysis& state) {
|
|||
}
|
||||
|
||||
static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
|
||||
std::string fqn = "Data::" + state.name;
|
||||
std::string fqn = state.name;
|
||||
|
||||
out << "static int data_gc(lua_State*);\n"
|
||||
"static int data_index(lua_State*);\n"
|
||||
"static int data_tostring(lua_State*);\n"
|
||||
"static const struct luaL_Reg metatable [] = {\n"
|
||||
" {\"__gc\", data_gc},\n"
|
||||
" {\"__index\", data_index},\n"
|
||||
" {\"__tostring\", data_tostring},\n"
|
||||
out << "static int data_" << state.name << "_gc(lua_State*);\n"
|
||||
"static int data_" << state.name << "_index(lua_State*);\n"
|
||||
"static int data_" << state.name << "_tostring(lua_State*);\n"
|
||||
"static const struct luaL_Reg " << state.name << "_metatable [] = {\n"
|
||||
" {\"__gc\", data_" << state.name << "_gc},\n"
|
||||
" {\"__index\", data_" << state.name << "_index},\n"
|
||||
" {\"__tostring\", data_" << state.name << "_tostring},\n"
|
||||
" {NULL, NULL} /* end of array */\n"
|
||||
"};\n\n";
|
||||
|
||||
out << "void Data::" << state.name << "::PushLuaValue(lua_State* L) const {\n"
|
||||
out << "void " << state.name << "::PushLuaValue(lua_State* L) const {\n"
|
||||
" int n = lua_gettop(L);\n"
|
||||
|
||||
// " // I'm torn... should this be Data::Variant, or Data::Base?\n"
|
||||
// " // If I ever decouple typing from Data::Base, I'll switch it to variant,\n"
|
||||
// " // otherwise, it doesn't make much sense to represent it as one\n"
|
||||
" " << fqn << "** userdata = (" << fqn << "**)lua_newuserdata(L, sizeof(" << fqn << "));\n"
|
||||
" *userdata = new " << fqn << "(*this);\n"
|
||||
|
||||
" // Create the library's metatable\n"
|
||||
" luaL_newmetatable(L, \"__mt_" << state.name << "\");\n"
|
||||
" luaL_register(L, NULL, metatable);\n"
|
||||
" luaL_register(L, NULL, " << state.name << "_metatable);\n"
|
||||
|
||||
" lua_setmetatable(L, n+1);\n"
|
||||
"}\n\n";
|
||||
|
||||
|
||||
out << "result<Data::Variant, LuaCastError> Data::" << state.name << "::FromLuaValue(lua_State* L, int idx) {\n"
|
||||
out << "result<Variant, LuaCastError> " << state.name << "::FromLuaValue(lua_State* L, int idx) {\n"
|
||||
" " << fqn << "** userdata = (" << fqn << "**) luaL_testudata(L, idx, \"" << getMtName(state.name) << "\");\n"
|
||||
" if (userdata == nullptr)\n"
|
||||
" return LuaCastError(lua_typename(L, idx), \"" << state.name << "\");\n"
|
||||
" return Data::Variant(**userdata);\n"
|
||||
" return Variant(**userdata);\n"
|
||||
"}\n\n";
|
||||
|
||||
// Indexing methods and properties
|
||||
|
||||
out << "static int data_index(lua_State* L) {\n"
|
||||
out << "static int data_" << state.name << "_index(lua_State* L) {\n"
|
||||
" " << fqn << "* this_ = *(" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n"
|
||||
"\n"
|
||||
" std::string key(lua_tostring(L, 2));\n"
|
||||
|
@ -292,7 +296,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
|
|||
valueExpr = "this_->" + prop.backingSymbol + "()";
|
||||
|
||||
// This largely depends on the type
|
||||
out << " " << type << "(" << valueExpr << ").PushLuaValue(L);\n";
|
||||
out << " " << pushLuaValue(type, valueExpr) << ";\n";
|
||||
out << " return 1;\n";
|
||||
|
||||
out << " }";
|
||||
|
@ -318,7 +322,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
|
|||
|
||||
// ToString
|
||||
|
||||
out << "\nint data_tostring(lua_State* L) {\n"
|
||||
out << "\nint data_" << state.name << "_tostring(lua_State* L) {\n"
|
||||
" " << fqn << "* this_ = *(" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n"
|
||||
" lua_pushstring(L, std::string(this_->ToString()).c_str());\n"
|
||||
" return 1;\n"
|
||||
|
@ -326,7 +330,7 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
|
|||
|
||||
// Destructor
|
||||
|
||||
out << "\nint data_gc(lua_State* L) {\n"
|
||||
out << "\nint data_" << state.name << "_gc(lua_State* L) {\n"
|
||||
" " << fqn << "** userdata = (" << fqn << "**)luaL_checkudata(L, 1, \"__mt_" << state.name << "\");\n"
|
||||
" delete *userdata;\n"
|
||||
" return 0;\n"
|
||||
|
@ -334,15 +338,20 @@ static void writeLuaValueGenerator(std::ofstream& out, ClassAnalysis& state) {
|
|||
}
|
||||
|
||||
static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
|
||||
std::string fqn = "Data::" + state.name;
|
||||
std::string fqn = state.name;
|
||||
|
||||
out << "static int lib_index(lua_State*);\n"
|
||||
"static const struct luaL_Reg lib_metatable [] = {\n"
|
||||
" {\"__index\", lib_index},\n"
|
||||
// If there are no static methods or properties, no need to create a library
|
||||
if (state.staticMethods.size() == 0 && state.staticProperties.size() == 0) return;
|
||||
|
||||
out << "static int lib_" << state.name << "_index(lua_State*);\n"
|
||||
"static int lib_" << state.name << "_tostring(lua_State*);\n"
|
||||
"static const struct luaL_Reg lib_" << state.name << "_metatable [] = {\n"
|
||||
" {\"__index\", lib_" << state.name << "_index},\n"
|
||||
" {\"__tostring\", lib_" << state.name << "_tostring},\n"
|
||||
" {NULL, NULL} /* end of array */\n"
|
||||
"};\n\n";
|
||||
|
||||
out << "void Data::" << state.name << "::PushLuaLibrary(lua_State* L) {\n"
|
||||
out << "void " << state.name << "::PushLuaLibrary(lua_State* L) {\n"
|
||||
" lua_getglobal(L, \"_G\");\n"
|
||||
" lua_pushstring(L, \"" << state.name << "\");\n"
|
||||
"\n"
|
||||
|
@ -350,16 +359,23 @@ static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
|
|||
"\n"
|
||||
" // Create the library's metatable\n"
|
||||
" luaL_newmetatable(L, \"__mt_lib_" << state.name << "\");\n"
|
||||
" luaL_register(L, NULL, lib_metatable);\n"
|
||||
" luaL_register(L, NULL, lib_" << state.name << "_metatable);\n"
|
||||
" lua_setmetatable(L, -2);\n"
|
||||
"\n"
|
||||
" lua_rawset(L, -3);\n"
|
||||
" lua_pop(L, 1);\n"
|
||||
"}\n\n";
|
||||
|
||||
// tostring
|
||||
|
||||
out << "\nint lib_" << state.name << "_tostring(lua_State* L) {\n"
|
||||
" lua_pushstring(L, \"" << state.name << "\");\n"
|
||||
" return 1;\n"
|
||||
"}\n\n";
|
||||
|
||||
// Indexing methods and properties
|
||||
|
||||
out << "static int lib_index(lua_State* L) {\n"
|
||||
out << "static int lib_" << state.name << "_index(lua_State* L) {\n"
|
||||
" std::string key(lua_tostring(L, 2));\n"
|
||||
" lua_pop(L, 2);\n"
|
||||
"\n";
|
||||
|
@ -382,7 +398,7 @@ static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
|
|||
else if (prop.backingType == PropertyBackingType::Method)
|
||||
valueExpr = fqn + "::" + prop.backingSymbol + "()";
|
||||
|
||||
out << " " << type << "(" << valueExpr << ").PushLuaValue(L);\n";
|
||||
out << " " << pushLuaValue(type, valueExpr) << ";\n";
|
||||
out << " return 1;\n";
|
||||
|
||||
out << " }";
|
||||
|
@ -408,22 +424,29 @@ static void writeLuaLibraryGenerator(std::ofstream& out, ClassAnalysis& state) {
|
|||
}
|
||||
|
||||
void data::writeCodeForClass(std::ofstream& out, std::string headerPath, ClassAnalysis& state) {
|
||||
std::string fqn = "Data::" + state.name;
|
||||
|
||||
out << "#define __AUTOGEN_EXTRA_INCLUDES__\n";
|
||||
out << "#include \"" << headerPath << "\"\n\n";
|
||||
out << "#include \"datatypes/meta.h\"\n";
|
||||
out << "#include \"datatypes/variant.h\"\n";
|
||||
out << "#include <pugixml.hpp>\n";
|
||||
out << "#include \"lua.h\"\n\n";
|
||||
out << "const Data::TypeInfo " << fqn << "::TYPE = {\n"
|
||||
<< " .name = \"" << state.serializedName << "\",\n"
|
||||
<< " .deserializer = &" << fqn << "::Deserialize,\n";
|
||||
if (state.hasFromString) out << " .fromString = &" << fqn << "::FromString,\n";
|
||||
out << " .fromLuaValue = &" << fqn << "::FromLuaValue,\n"
|
||||
<< "};\n\n";
|
||||
|
||||
out << "const Data::TypeInfo& " << fqn << "::GetType() const {\n"
|
||||
<< " return TYPE;\n"
|
||||
out << "const TypeDesc " << state.name << "::TYPE = {\n"
|
||||
<< " .name = \"" << state.serializedName << "\",\n";
|
||||
if (state.isSerializable) {
|
||||
out << " .serialize = toVariantFunction(&" << state.name << "::Serialize),";
|
||||
if (state.hasGenericDeserializer)
|
||||
out << " .deserialize = toVariantGenerator(&" << state.name << "::Deserialize),\n";
|
||||
else
|
||||
out << " .deserialize = toVariantGeneratorNoMeta(&" << state.name << "::Deserialize),\n";
|
||||
}
|
||||
out << " .toString = toVariantFunction(&" << state.name << "::ToString),";
|
||||
if (state.hasFromString) {
|
||||
if (state.hasGenericFromString)
|
||||
out << " .fromString = toVariantGenerator(&" << state.name << "::FromString),\n";
|
||||
else
|
||||
out << " .fromString = toVariantGeneratorNoMeta(&" << state.name << "::FromString),\n";
|
||||
}
|
||||
out << " .pushLuaValue = toVariantFunction(&" << state.name << "::PushLuaValue),"
|
||||
<< " .fromLuaValue = toVariantGenerator(&" << state.name << "::FromLuaValue),\n"
|
||||
<< "};\n\n";
|
||||
|
||||
writeLuaMethodImpls(out, state);
|
||||
|
|
89
autogen/src/enum/analysis.cpp
Normal file
89
autogen/src/enum/analysis.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
#include "analysis.h"
|
||||
#include "../util.h"
|
||||
#include <clang-c/CXFile.h>
|
||||
#include <clang-c/CXSourceLocation.h>
|
||||
#include <clang-c/Index.h>
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
|
||||
using namespace enum_;
|
||||
|
||||
static int findEnumEntryValue(CXCursor cur, bool* success = nullptr) {
|
||||
int ret = -1;
|
||||
if (success != nullptr) *success = false;
|
||||
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
|
||||
CXCursorKind kind = clang_getCursorKind(cur);
|
||||
|
||||
if (kind != CXCursor_IntegerLiteral) return CXChildVisit_Recurse;
|
||||
|
||||
// https://stackoverflow.com/a/63859988/16255372
|
||||
auto res = clang_Cursor_Evaluate(cur);
|
||||
ret = clang_EvalResult_getAsInt(res);
|
||||
clang_EvalResult_dispose(res);
|
||||
|
||||
if (success != nullptr) *success = true;
|
||||
return CXChildVisit_Break;
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void processEnumEntry(CXCursor cur, EnumAnalysis* state, int* lastValue) {
|
||||
EnumEntry anly;
|
||||
|
||||
std::string name = x_clang_toString(clang_getCursorSpelling(cur));
|
||||
bool success;
|
||||
int value = findEnumEntryValue(cur, &success);
|
||||
|
||||
// Default to lastValue + 1
|
||||
if (!success) value = ++(*lastValue);
|
||||
*lastValue = value;
|
||||
|
||||
anly.name = name;
|
||||
anly.value = value;
|
||||
|
||||
state->entries.push_back(anly);
|
||||
}
|
||||
|
||||
static void processEnum(CXCursor cur, AnalysisState* state, std::string className, std::string srcRoot) {
|
||||
EnumAnalysis anly;
|
||||
|
||||
anly.name = className;
|
||||
|
||||
int lastValue = -1;
|
||||
|
||||
x_clang_visitChildren(cur, [&](CXCursor cur, CXCursor parent) {
|
||||
CXCursorKind kind = clang_getCursorKind(cur);
|
||||
|
||||
if (kind == CXCursor_EnumConstantDecl) {
|
||||
processEnumEntry(cur, &anly, &lastValue);
|
||||
}
|
||||
|
||||
return CXChildVisit_Continue;
|
||||
});
|
||||
|
||||
state->classes[className] = anly;
|
||||
}
|
||||
|
||||
bool enum_::analyzeClasses(CXCursor cursor, std::string srcRoot, AnalysisState* state) {
|
||||
// Search for classes
|
||||
x_clang_visitChildren(cursor, [&](CXCursor cur, CXCursor parent) {
|
||||
CXCursorKind kind = clang_getCursorKind(cur);
|
||||
if (kind == CXCursor_Namespace) return CXChildVisit_Recurse;
|
||||
if (kind != CXCursor_EnumDecl) return CXChildVisit_Continue;
|
||||
|
||||
CXSourceLocation loc = clang_getCursorLocation(cur);
|
||||
if (!clang_Location_isFromMainFile(loc)) return CXChildVisit_Continue; // This class is not from this header. Skip
|
||||
|
||||
std::string className = x_clang_toString(clang_getCursorDisplayName(cur));
|
||||
// Forward-decls can slip through the cracks, this prevents that, but also allows us to filter non-instance classes in the src/objects directory
|
||||
if (!findAnnotation(cur, "OB::def_enum")) return CXChildVisit_Continue; // Class is not "primary" declaration/is not instance, skip
|
||||
if (state->classes.count(className) > 0) return CXChildVisit_Continue; // Class has already been analyzed, skip...
|
||||
|
||||
processEnum(cur, state, className, srcRoot);
|
||||
|
||||
return CXChildVisit_Continue;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
26
autogen/src/enum/analysis.h
Normal file
26
autogen/src/enum/analysis.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <clang-c/Index.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace enum_ {
|
||||
|
||||
struct EnumEntry {
|
||||
std::string name;
|
||||
int value;
|
||||
};
|
||||
|
||||
struct EnumAnalysis {
|
||||
std::string name;
|
||||
std::vector<EnumEntry> entries;
|
||||
};
|
||||
|
||||
struct AnalysisState {
|
||||
std::map<std::string, EnumAnalysis> classes;
|
||||
};
|
||||
|
||||
bool analyzeClasses(CXCursor cursor, std::string srcRoot, AnalysisState* state);
|
||||
|
||||
}
|
31
autogen/src/enum/codegen.cpp
Normal file
31
autogen/src/enum/codegen.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include "codegen.h"
|
||||
#include "analysis.h"
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace enum_;
|
||||
|
||||
void enum_::writeCodeForClass(std::ofstream& out, std::string headerPath, EnumAnalysis& state) {
|
||||
out << "#include \"datatypes/enum.h\"\n\n";
|
||||
|
||||
out << "static std::pair<int, std::string> __values_" << state.name << "[] = {\n";
|
||||
|
||||
for (auto entry : state.entries) {
|
||||
out << " { " << entry.value << ", \"" << entry.name << "\" },\n";
|
||||
}
|
||||
|
||||
out << "};\n\n";
|
||||
|
||||
out << "static _EnumData __data_" << state.name << " = {\n"
|
||||
<< " \"" << state.name << "\",\n"
|
||||
<< " __values_" << state.name << ",\n"
|
||||
<< " " << state.entries.size() << "\n"
|
||||
<< "};\n\n";
|
||||
|
||||
out << "namespace EnumType {\n"
|
||||
// extern is necessary here too to prevent "const" from marking Enum as implicitly static
|
||||
// https://stackoverflow.com/questions/2190919/mixing-extern-and-const#comment2509591_2190981
|
||||
<< " extern const Enum " << state.name << "(&__data_" << state.name << ");\n"
|
||||
<< "}\n\n";
|
||||
}
|
10
autogen/src/enum/codegen.h
Normal file
10
autogen/src/enum/codegen.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "analysis.h"
|
||||
#include <fstream>
|
||||
|
||||
namespace enum_ {
|
||||
|
||||
void writeCodeForClass(std::ofstream& out, std::string headerPath, EnumAnalysis& state);
|
||||
|
||||
}
|
|
@ -6,10 +6,13 @@
|
|||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include "enum/analysis.h"
|
||||
#include "enum/codegen.h"
|
||||
#include "object/analysis.h"
|
||||
#include "object/codegen.h"
|
||||
#include "data/analysis.h"
|
||||
#include "data/codegen.h"
|
||||
#include "util.h"
|
||||
|
||||
// namespace data {
|
||||
// #include "data/analysis.h"
|
||||
|
@ -20,13 +23,17 @@ namespace fs = std::filesystem;
|
|||
|
||||
// https://clang.llvm.org/docs/LibClang.html
|
||||
int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
|
||||
const char* cargs[] = { "-x", "c++", "-I", srcRoot.c_str(), "-D__AUTOGEN__", 0 };
|
||||
std::string srcRootStr = string_of(srcRoot);
|
||||
std::string srcPathStr = string_of(srcPath);
|
||||
std::string outPathStr = string_of(outPath);
|
||||
|
||||
const char* cargs[] = { "-xc++", "-std=c++17", "-I", srcRootStr.c_str(), "-D__AUTOGEN__", 0 };
|
||||
// THANK YOU SO MUCH THIS STACKOVERFLOW ANSWER IS SO HELPFUL
|
||||
// https://stackoverflow.com/a/59206378/16255372
|
||||
CXIndex index = clang_createIndex(0, 0);
|
||||
CXTranslationUnit unit = clang_parseTranslationUnit(
|
||||
index,
|
||||
srcPath.c_str(), cargs, 5,
|
||||
srcPathStr.c_str(), cargs, 5,
|
||||
nullptr, 0,
|
||||
CXTranslationUnit_None);
|
||||
|
||||
|
@ -50,16 +57,19 @@ int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
|
|||
|
||||
object::AnalysisState objectAnlyState;
|
||||
data::AnalysisState dataAnlyState;
|
||||
enum_::AnalysisState enumAnlyState;
|
||||
|
||||
fs::path relpath = fs::relative(srcPath, srcRoot);
|
||||
printf("[AUTOGEN] Processing file %s...\n", relpath.c_str());
|
||||
object::analyzeClasses(cursor, srcRoot, &objectAnlyState);
|
||||
data::analyzeClasses(cursor, srcRoot, &dataAnlyState);
|
||||
std::string relpathStr = string_of(relpath);
|
||||
printf("[AUTOGEN] Processing file %s...\n", relpathStr.c_str());
|
||||
object::analyzeClasses(cursor, srcRootStr, &objectAnlyState);
|
||||
data::analyzeClasses(cursor, srcRootStr, &dataAnlyState);
|
||||
enum_::analyzeClasses(cursor, srcRootStr, &enumAnlyState);
|
||||
|
||||
fs::create_directories(outPath.parent_path()); // Make sure generated dir exists before we try writing to it
|
||||
|
||||
printf("[AUTOGEN] Generating file %s...\n", relpath.c_str());
|
||||
std::ofstream outStream(outPath);
|
||||
printf("[AUTOGEN] Generating file %s...\n", relpathStr.c_str());
|
||||
std::ofstream outStream(outPathStr);
|
||||
|
||||
if (!objectAnlyState.classes.empty() || !dataAnlyState.classes.empty()) {
|
||||
outStream << "/////////////////////////////////////////////////////////////////////////////////////////\n";
|
||||
|
@ -68,11 +78,15 @@ int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
|
|||
}
|
||||
|
||||
for (auto& [_, clazz] : objectAnlyState.classes) {
|
||||
object::writeCodeForClass(outStream, relpath, clazz);
|
||||
object::writeCodeForClass(outStream, relpathStr, clazz);
|
||||
}
|
||||
|
||||
for (auto& [_, clazz] : dataAnlyState.classes) {
|
||||
data::writeCodeForClass(outStream, relpath, clazz);
|
||||
data::writeCodeForClass(outStream, relpathStr, clazz);
|
||||
}
|
||||
|
||||
for (auto& [_, clazz] : enumAnlyState.classes) {
|
||||
enum_::writeCodeForClass(outStream, relpathStr, clazz);
|
||||
}
|
||||
|
||||
outStream.close();
|
||||
|
|
|
@ -80,7 +80,13 @@ static void processField(CXCursor cur, ClassAnalysis* state) {
|
|||
anly.flags = anly.flags | PropertyFlags::PropertyFlag_Readonly;
|
||||
|
||||
CXType type = clang_getCursorType(cur);
|
||||
anly.backingFieldType = x_clang_toString(clang_getTypeSpelling(type)).c_str();
|
||||
anly.backingFieldType = x_clang_toString(clang_getTypeSpelling(type));
|
||||
CXCursor typeCur = clang_getTypeDeclaration(type);
|
||||
bool isEnum = findAnnotation(typeCur, "OB::def_enum").has_value();
|
||||
if (isEnum) {
|
||||
anly.backingFieldType = "EnumItem";
|
||||
anly.backingFieldEnum = x_clang_toString(clang_getTypeSpelling(type));
|
||||
}
|
||||
|
||||
state->properties.push_back(anly);
|
||||
|
||||
|
@ -156,7 +162,7 @@ static void processClass(CXCursor cur, AnalysisState* state, std::string classNa
|
|||
|
||||
anly.name = className;
|
||||
anly.baseClass = baseClass;
|
||||
anly.headerPath = headerPath;
|
||||
anly.headerPath = string_of(headerPath);
|
||||
|
||||
// Add misc flags and options
|
||||
auto instanceDef = findAnnotation(cur, "OB::def_inst");
|
||||
|
|
|
@ -31,6 +31,7 @@ struct PropertyAnalysis {
|
|||
std::string fieldName;
|
||||
CFrameMember cframeMember = CFrameMember_None; // for cframe_position_prop etc.
|
||||
std::string backingFieldType;
|
||||
std::string backingFieldEnum;
|
||||
std::string onUpdateCallback;
|
||||
std::string category;
|
||||
PropertyFlags flags = (PropertyFlags)0;
|
||||
|
|
|
@ -16,11 +16,13 @@ static std::map<std::string, std::string> CATEGORY_STR = {
|
|||
};
|
||||
|
||||
static std::map<std::string, std::string> MAPPED_TYPE = {
|
||||
{ "bool", "Data::Bool" },
|
||||
{ "int", "Data::Int" },
|
||||
{ "float", "Data::Float" },
|
||||
{ "std::string", "Data::String" },
|
||||
{ "glm::vec3", "Vector3" },
|
||||
};
|
||||
|
||||
static std::map<std::string, std::string> TYPEINFO_REFS = {
|
||||
{ "bool", "BOOL_TYPE" },
|
||||
{ "int", "INT_TYPE" },
|
||||
{ "float", "FLOAT_TYPE" },
|
||||
{ "std::string", "STRING_TYPE" },
|
||||
};
|
||||
|
||||
static std::map<std::string, std::monostate> ENUM_TYPES = {
|
||||
|
@ -40,7 +42,7 @@ static std::string parseWeakPtr(std::string weakPtrType) {
|
|||
static std::string castFromVariant(std::string valueStr, std::string fieldType) {
|
||||
// Manual exception for now, enums will get their own system eventually
|
||||
if (fieldType == "SurfaceType") {
|
||||
return "(SurfaceType)(int)" + valueStr + ".get<Data::Int>()";
|
||||
return "(SurfaceType)(int)" + valueStr + ".get<int>()";
|
||||
}
|
||||
|
||||
std::string mappedType = MAPPED_TYPE[fieldType];
|
||||
|
@ -50,13 +52,13 @@ static std::string castFromVariant(std::string valueStr, std::string fieldType)
|
|||
static std::string castToVariant(std::string valueStr, std::string fieldType) {
|
||||
// Manual exception for now, enums will get their own system eventually
|
||||
if (fieldType == "SurfaceType") {
|
||||
return "Data::Int((int)" + valueStr + ")";
|
||||
return "(int)" + valueStr;
|
||||
}
|
||||
|
||||
// InstanceRef
|
||||
// std::shared_ptr<Instance>
|
||||
std::string subtype = parseWeakPtr(fieldType);
|
||||
if (!subtype.empty()) {
|
||||
return "Data::Variant(" + valueStr + ".expired() ? Data::InstanceRef() : Data::InstanceRef(std::dynamic_pointer_cast<Instance>(" + valueStr + ".lock())))";
|
||||
return "Variant(" + valueStr + ".expired() ? InstanceRef() : InstanceRef(std::dynamic_pointer_cast<Instance>(" + valueStr + ".lock())))";
|
||||
}
|
||||
|
||||
std::string mappedType = MAPPED_TYPE[fieldType];
|
||||
|
@ -67,13 +69,13 @@ static std::string castToVariant(std::string valueStr, std::string fieldType) {
|
|||
}
|
||||
|
||||
static void writePropertySetHandler(std::ofstream& out, ClassAnalysis state) {
|
||||
out << "fallible<MemberNotFound, AssignToReadOnlyMember> " << state.name << "::InternalSetPropertyValue(std::string name, Data::Variant value) {";
|
||||
out << "fallible<MemberNotFound, AssignToReadOnlyMember> " << state.name << "::InternalSetPropertyValue(std::string name, Variant value) {";
|
||||
|
||||
out << "\n ";
|
||||
bool first = true;
|
||||
for (auto& prop : state.properties) {
|
||||
out << (first ? "" : " else ") << "if (name == \"" << prop.name << "\") {";
|
||||
// InstanceRef
|
||||
// std::shared_ptr<Instance>
|
||||
std::string subtype = parseWeakPtr(prop.backingFieldType);
|
||||
|
||||
if (prop.flags & PropertyFlag_Readonly) {
|
||||
|
@ -83,8 +85,10 @@ static void writePropertySetHandler(std::ofstream& out, ClassAnalysis state) {
|
|||
} else if (prop.cframeMember == CFrameMember_Rotation) {
|
||||
out << "\n this->" << prop.fieldName << " = CFrame::FromEulerAnglesXYZ(value.get<Vector3>()) + this->" << prop.fieldName << ".Position();";
|
||||
} else if (!subtype.empty()) {
|
||||
out << "\n std::weak_ptr<Instance> ref = value.get<Data::InstanceRef>();"
|
||||
out << "\n std::weak_ptr<Instance> ref = value.get<InstanceRef>();"
|
||||
<< "\n this->" << prop.fieldName << " = ref.expired() ? std::weak_ptr<" << subtype << ">() : std::dynamic_pointer_cast<" << subtype << ">(ref.lock());";
|
||||
} else if (prop.backingFieldType == "EnumItem") {
|
||||
out << "\n this->" << prop.fieldName << " = (" << prop.backingFieldEnum << ")value.get<EnumItem>().Value();";
|
||||
} else {
|
||||
out << "\n this->" << prop.fieldName << " = " << castFromVariant("value", prop.backingFieldType) << ";";
|
||||
}
|
||||
|
@ -128,7 +132,7 @@ static void writePropertyUpdateHandler(std::ofstream& out, ClassAnalysis state)
|
|||
}
|
||||
|
||||
static void writePropertyGetHandler(std::ofstream& out, ClassAnalysis state) {
|
||||
out << "result<Data::Variant, MemberNotFound> " << state.name << "::InternalGetPropertyValue(std::string name) {";
|
||||
out << "result<Variant, MemberNotFound> " << state.name << "::InternalGetPropertyValue(std::string name) {";
|
||||
|
||||
out << "\n ";
|
||||
bool first = true;
|
||||
|
@ -136,11 +140,13 @@ static void writePropertyGetHandler(std::ofstream& out, ClassAnalysis state) {
|
|||
out << (first ? "" : " else ") << "if (name == \"" << prop.name << "\") {";
|
||||
|
||||
if (prop.cframeMember == CFrameMember_Position) {
|
||||
out << "\n return Data::Variant(" << prop.fieldName << ".Position());";
|
||||
out << "\n return Variant(" << prop.fieldName << ".Position());";
|
||||
} else if (prop.cframeMember == CFrameMember_Rotation) {
|
||||
out << "\n return Data::Variant(" << prop.fieldName << ".ToEulerAnglesXYZ());";
|
||||
out << "\n return Variant(" << prop.fieldName << ".ToEulerAnglesXYZ());";
|
||||
} else if (prop.backingFieldType == "EnumItem") {
|
||||
out << "\n return Variant(EnumType::" << prop.backingFieldEnum << ".FromValueInternal((int)" << prop.fieldName << "));";
|
||||
} else {
|
||||
out << "\n return Data::Variant(" << castToVariant(prop.fieldName, prop.backingFieldType) << ");";
|
||||
out << "\n return Variant(" << castToVariant(prop.fieldName, prop.backingFieldType) << ");";
|
||||
}
|
||||
|
||||
out << "\n }";
|
||||
|
@ -153,7 +159,7 @@ static void writePropertyGetHandler(std::ofstream& out, ClassAnalysis state) {
|
|||
for (auto& signal : state.signals) {
|
||||
out << (first ? "" : " else ") << "if (name == \"" << signal.name << "\") {";
|
||||
|
||||
out << "\n return Data::Variant(Data::SignalRef(" << signal.sourceFieldName << "));";
|
||||
out << "\n return Variant(SignalRef(" << signal.sourceFieldName << "));";
|
||||
|
||||
out << "\n }";
|
||||
first = false;
|
||||
|
@ -186,10 +192,10 @@ static void writePropertyMetaHandler(std::ofstream& out, ClassAnalysis state) {
|
|||
for (auto& prop : state.properties) {
|
||||
out << (first ? "" : " else ") << "if (name == \"" << prop.name << "\") {";
|
||||
|
||||
std::string type = MAPPED_TYPE[prop.backingFieldType];
|
||||
if (type.empty()) type = prop.backingFieldType;
|
||||
if (type == "SurfaceType") type = "Data::Int";
|
||||
if (!parseWeakPtr(prop.backingFieldType).empty()) type = "Data::InstanceRef";
|
||||
std::string typeInfo = TYPEINFO_REFS[prop.backingFieldType];
|
||||
if (typeInfo.empty()) typeInfo = prop.backingFieldType + "::TYPE";
|
||||
if (!parseWeakPtr(prop.backingFieldType).empty()) typeInfo = "InstanceRef::TYPE";
|
||||
if (prop.backingFieldType == "EnumItem") typeInfo = "EnumType::" + prop.backingFieldEnum;
|
||||
|
||||
std::string strFlags;
|
||||
if (prop.flags & PropertyFlag_Readonly)
|
||||
|
@ -206,7 +212,7 @@ static void writePropertyMetaHandler(std::ofstream& out, ClassAnalysis state) {
|
|||
std::string category = CATEGORY_STR[prop.category];
|
||||
if (category.empty()) category = "PROP_CATEGORY_DATA";
|
||||
|
||||
out << "\n return PropertyMeta { &" << type << "::TYPE, " << strFlags << ", " << category << " };";
|
||||
out << "\n return PropertyMeta { &" << typeInfo << ", " << strFlags << ", " << category << " };";
|
||||
|
||||
out << "\n }";
|
||||
first = false;
|
||||
|
@ -220,8 +226,8 @@ static void writePropertyMetaHandler(std::ofstream& out, ClassAnalysis state) {
|
|||
|
||||
std::string strFlags;
|
||||
strFlags += "PROP_READONLY";
|
||||
strFlags += "| PROP_HIDDEN";
|
||||
strFlags += "| PROP_NOSAVE";
|
||||
strFlags += " | PROP_HIDDEN";
|
||||
strFlags += " | PROP_NOSAVE";
|
||||
|
||||
out << "\n return PropertyMeta { &SignalRef::TYPE, " << strFlags << " };";
|
||||
|
||||
|
@ -252,7 +258,8 @@ void object::writeCodeForClass(std::ofstream& out, std::string headerPath, Class
|
|||
|
||||
out << "#define __AUTOGEN_EXTRA_INCLUDES__\n";
|
||||
out << "#include \"" << state.headerPath << "\"\n\n";
|
||||
out << "#include \"datatypes/meta.h\"\n\n";
|
||||
out << "#include \"datatypes/variant.h\"\n";
|
||||
out << "#include \"datatypes/primitives.h\"\n";
|
||||
out << "const InstanceType " << state.name << "::TYPE = {\n"
|
||||
<< " .super = &" << state.baseClass << "::TYPE,\n"
|
||||
<< " .className = \"" << state.name << "\",\n"
|
||||
|
|
|
@ -25,11 +25,11 @@ std::map<std::string, std::string> parseAnnotationString(std::string src) {
|
|||
int stage = 0;
|
||||
bool quoted = false;
|
||||
|
||||
int i = 0;
|
||||
size_t i = 0;
|
||||
for (; i < src.length(); i++) {
|
||||
if (src[i] == ' ' && (stage != 2 || !quoted)) continue; // Ignore spaces if not in stage 2 and quoted
|
||||
if (src[i] == ',' && stage == 0) continue; // Let empty commas slip by
|
||||
if (stage < 2 && (src[i] >= 'a' && src[i] <= 'z' || src[i] >= 'A' && src[i] <= 'Z' || src[i] >= '0' && src[i] <= '9' || src[i] == '_')) {
|
||||
if (stage < 2 && ((src[i] >= 'a' && src[i] <= 'z') || (src[i] >= 'A' && src[i] <= 'Z') || (src[i] >= '0' && src[i] <= '9') || src[i] == '_')) {
|
||||
currentIdent += src[i];
|
||||
stage = 1;
|
||||
continue;
|
||||
|
@ -63,7 +63,7 @@ std::map<std::string, std::string> parseAnnotationString(std::string src) {
|
|||
currentValue += src[i];
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr, "Unexpected symbol: %c at index %d\n", src[i], i);
|
||||
fprintf(stderr, "Unexpected symbol: %c at index %zu\n", src[i], i);
|
||||
fprintf(stderr, "\t%s\n", src.c_str());
|
||||
fprintf(stderr, "\t%s^\n", i > 0 ? std::string(i, '~').c_str() : "");
|
||||
abort();
|
||||
|
@ -108,4 +108,8 @@ std::optional<std::string> findAnnotation(CXCursor cur, std::string annotationNa
|
|||
return CXChildVisit_Break;
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string string_of(std::filesystem::path path) {
|
||||
return path.string();
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
typedef std::function<CXChildVisitResult(CXCursor cursor, CXCursor parent)> X_CXCursorVisitor;
|
||||
|
||||
|
@ -19,4 +20,6 @@ std::string x_clang_toString(CXString string);
|
|||
// "name": "Hello!", "world": "Test", "read_only": ""
|
||||
std::map<std::string, std::string> parseAnnotationString(std::string src);
|
||||
|
||||
std::optional<std::string> findAnnotation(CXCursor cur, std::string annotationName);
|
||||
std::optional<std::string> findAnnotation(CXCursor cur, std::string annotationName);
|
||||
|
||||
std::string string_of(std::filesystem::path path);
|
|
@ -4,4 +4,5 @@ include_directories(${SDL2_INCLUDE_DIRS})
|
|||
find_package(glfw3 REQUIRED)
|
||||
|
||||
add_executable(client "src/main.cpp")
|
||||
target_link_libraries(client PRIVATE ${SDL2_LIBRARIES} openblocks glfw)
|
||||
target_link_libraries(client PRIVATE ${SDL2_LIBRARIES} openblocks glfw)
|
||||
add_dependencies(client openblocks)
|
|
@ -52,7 +52,7 @@ int main() {
|
|||
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
|
||||
}));
|
||||
|
||||
for (InstanceRef inst : gWorkspace()->GetChildren()) {
|
||||
for (std::shared_ptr<Instance> inst : gWorkspace()->GetChildren()) {
|
||||
if (inst->GetClass()->className != "Part") continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
gWorkspace()->SyncPartPhysics(part);
|
||||
|
@ -170,15 +170,15 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods
|
|||
}
|
||||
} else if (mode == 1) {
|
||||
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
|
||||
lastPart->size.x += shiftFactor;
|
||||
lastPart->size += Vector3(1, 0, 0) * shiftFactor;
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
|
||||
lastPart->size.y += shiftFactor;
|
||||
lastPart->size += Vector3(0, 1, 0) * shiftFactor;
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
|
||||
lastPart->size.z += shiftFactor;
|
||||
lastPart->size += Vector3(0, 0, 1) * shiftFactor;
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
}
|
||||
|
|
42
cmake/FindClang.cmake
Normal file
42
cmake/FindClang.cmake
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Modified from QGIS' FindQScintilla.cmake by Thomas Moenicke, Larry Schaffer
|
||||
|
||||
add_library(Clang::Clang UNKNOWN IMPORTED)
|
||||
|
||||
FIND_PATH(CLANG_INCLUDE_DIR
|
||||
NAMES clang-c/Index.h
|
||||
PATHS
|
||||
$ENV{LIB_DIR}/include
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
${VCPKG_INSTALLED_DIR}/x64-windows/include
|
||||
"C:/Program Files/LLVM/include"
|
||||
PATH_SUFFIXES ${CLANG_PATH_SUFFIXES}
|
||||
)
|
||||
|
||||
set(CLANG_LIBRARY_NAMES
|
||||
libclang
|
||||
clang
|
||||
)
|
||||
|
||||
find_library(CLANG_LIBRARY
|
||||
NAMES ${CLANG_LIBRARY_NAMES}
|
||||
PATHS
|
||||
$ENV{LIB_DIR}/lib
|
||||
/usr/local/lib
|
||||
/usr/lib
|
||||
${VCPKG_INSTALLED_DIR}/x64-windows/lib
|
||||
"C:/Program Files/LLVM/lib"
|
||||
)
|
||||
|
||||
get_filename_component(CLANG_LIB_DIR ${CLANG_LIBRARY} DIRECTORY)
|
||||
list(TRANSFORM CLANG_LIBRARY_NAMES APPEND ".dll" OUTPUT_VARIABLE CLANG_DLL_NAMES)
|
||||
|
||||
find_file(CLANG_DLLS
|
||||
NAMES ${CLANG_DLL_NAMES}
|
||||
PATHS
|
||||
$ENV{LIB_DIR}/bin
|
||||
/usr/local/bin
|
||||
/usr/bin
|
||||
${VCPKG_INSTALLED_DIR}/x64-windows/bin
|
||||
"C:/Program Files/LLVM/bin"
|
||||
)
|
|
@ -1,5 +1,12 @@
|
|||
# Modified from QGIS' FindQScintilla.cmake by Thomas Moenicke, Larry Schaffer
|
||||
|
||||
add_library(QScintilla::QScintilla UNKNOWN IMPORTED)
|
||||
|
||||
### NECESSARY TO PREVENT staticMetaObject ERROR!!! See qscintilla.prf AKA qmake config
|
||||
if(WIN32)
|
||||
add_compile_definitions(QSCINTILLA_DLL)
|
||||
endif()
|
||||
|
||||
FIND_PATH(QSCINTILLA_INCLUDE_DIR
|
||||
NAMES Qsci/qsciglobal.h
|
||||
PATHS
|
||||
|
@ -7,6 +14,7 @@ FIND_PATH(QSCINTILLA_INCLUDE_DIR
|
|||
$ENV{LIB_DIR}/include
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
${VCPKG_INSTALLED_DIR}/x64-windows/include
|
||||
PATH_SUFFIXES ${QSCINTILLA_PATH_SUFFIXES}
|
||||
)
|
||||
|
||||
|
@ -29,4 +37,20 @@ PATHS
|
|||
/usr/local/lib
|
||||
/usr/local/lib/qt${QT_VERSION_MAJOR}
|
||||
/usr/lib
|
||||
${VCPKG_INSTALLED_DIR}/x64-windows/lib
|
||||
)
|
||||
|
||||
get_filename_component(QSCINTILLA_LIB_DIR ${QSCINTILLA_LIBRARY} DIRECTORY)
|
||||
list(TRANSFORM QSCINTILLA_LIBRARY_NAMES APPEND ".dll" OUTPUT_VARIABLE QSCINTILLA_DLL_NAMES)
|
||||
|
||||
find_file(QSCINTILLA_DLLS
|
||||
NAMES ${QSCINTILLA_DLL_NAMES}
|
||||
PATHS
|
||||
"${QT_LIBRARY_DIR}"
|
||||
$ENV{LIB_DIR}/lib
|
||||
/usr/local/lib
|
||||
/usr/local/lib/qt${QT_VERSION_MAJOR}
|
||||
/usr/lib
|
||||
${QSCINTILLA_LIB_DIR}
|
||||
${VCPKG_INSTALLED_DIR}/x64-windows/lib
|
||||
)
|
|
@ -14,9 +14,10 @@ include_directories(${Stb_INCLUDE_DIR})
|
|||
# PkgConfig packages
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LUAJIT REQUIRED luajit)
|
||||
link_directories(${LUAJIT_LIBRARY_DIRS})
|
||||
|
||||
# Run autogen
|
||||
file(GLOB_RECURSE AUTOGEN_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src" "src/objects/*.h" "src/datatypes/*.h")
|
||||
file(GLOB_RECURSE AUTOGEN_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src" "src/objects/*.h" "src/datatypes/*.h" "src/enum/*.h")
|
||||
|
||||
# https://cmake.org/cmake/help/book/mastering-cmake/chapter/Custom%20Commands.html
|
||||
foreach (SRC ${AUTOGEN_SOURCES})
|
||||
|
@ -26,8 +27,9 @@ foreach (SRC ${AUTOGEN_SOURCES})
|
|||
|
||||
add_custom_command(
|
||||
OUTPUT "${OUT_PATH}"
|
||||
DEPENDS autogen
|
||||
DEPENDS "${SRC_PATH}"
|
||||
COMMAND "${CMAKE_BINARY_DIR}/autogen/autogen" "${CMAKE_CURRENT_SOURCE_DIR}/src" "${SRC_PATH}" "${OUT_PATH}"
|
||||
COMMAND "$<TARGET_FILE:autogen>" "${CMAKE_CURRENT_SOURCE_DIR}/src" "${SRC_PATH}" "${OUT_PATH}"
|
||||
)
|
||||
|
||||
list(APPEND AUTOGEN_OUTS "${OUT_PATH}")
|
||||
|
@ -41,8 +43,10 @@ file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h")
|
|||
list(APPEND SOURCES ${AUTOGEN_OUTS})
|
||||
add_library(openblocks STATIC ${SOURCES})
|
||||
set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks")
|
||||
target_link_directories(openblocks PUBLIC ${LUAJIT_LIBRARY_DIRS})
|
||||
target_link_libraries(openblocks ${GLEW_LIBRARIES} ${LUAJIT_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml)
|
||||
target_include_directories(openblocks PUBLIC "src" "../include" ${LUAJIT_INCLUDE_DIR})
|
||||
target_include_directories(openblocks PUBLIC "src" "../include" ${LUAJIT_INCLUDE_DIRS})
|
||||
add_dependencies(openblocks autogen_build autogen)
|
||||
|
||||
# Windows-specific dependencies
|
||||
if(WIN32)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// TEMPORARY COMMON DATA FOR DIFFERENT INTERNAL COMPONENTS
|
||||
|
||||
#include "objects/datamodel.h"
|
||||
#include "datatypes/meta.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "common.h"
|
||||
#include <memory>
|
||||
|
||||
|
@ -14,11 +14,11 @@ std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
|||
Handles editorToolHandles;
|
||||
|
||||
|
||||
std::vector<InstanceRefWeak> currentSelection;
|
||||
std::vector<std::shared_ptr<Instance>> currentSelection;
|
||||
std::vector<SelectionUpdateHandler> selectionUpdateListeners;
|
||||
std::vector<PropertyUpdateHandler> propertyUpdatelisteners;
|
||||
|
||||
void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer) {
|
||||
void setSelection(std::vector<std::shared_ptr<Instance>> newSelection, bool fromExplorer) {
|
||||
for (SelectionUpdateHandler handler : selectionUpdateListeners) {
|
||||
handler(currentSelection, newSelection, fromExplorer);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer)
|
|||
currentSelection = newSelection;
|
||||
}
|
||||
|
||||
const std::vector<InstanceRefWeak> getSelection() {
|
||||
const std::vector<std::shared_ptr<Instance>> getSelection() {
|
||||
return currentSelection;
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ void addSelectionListener(SelectionUpdateHandler handler) {
|
|||
selectionUpdateListeners.push_back(handler);
|
||||
}
|
||||
|
||||
void sendPropertyUpdatedSignal(InstanceRef instance, std::string property, Data::Variant newValue) {
|
||||
void sendPropertyUpdatedSignal(std::shared_ptr<Instance> instance, std::string property, Variant newValue) {
|
||||
for (PropertyUpdateHandler handler : propertyUpdatelisteners) {
|
||||
handler(instance, property, newValue);
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
|
||||
class Instance;
|
||||
// typedef std::function<void(std::shared_ptr<Instance> element, std::optional<std::shared_ptr<Instance>> newParent)> HierarchyUpdateHandler;
|
||||
typedef std::function<void(InstanceRef object, std::optional<InstanceRef> oldParent, std::optional<InstanceRef> newParent)> HierarchyPreUpdateHandler;
|
||||
typedef std::function<void(InstanceRef object, std::optional<InstanceRef> oldParent, std::optional<InstanceRef> newParent)> HierarchyPostUpdateHandler;
|
||||
typedef std::function<void(std::vector<InstanceRefWeak> oldSelection, std::vector<InstanceRefWeak> newSelection, bool fromExplorer)> SelectionUpdateHandler;
|
||||
typedef std::function<void(InstanceRef instance, std::string property, Data::Variant newValue)> PropertyUpdateHandler;
|
||||
typedef std::function<void(std::shared_ptr<Instance> object, std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent)> HierarchyPreUpdateHandler;
|
||||
typedef std::function<void(std::shared_ptr<Instance> object, std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent)> HierarchyPostUpdateHandler;
|
||||
typedef std::function<void(std::vector<std::shared_ptr<Instance>> oldSelection, std::vector<std::shared_ptr<Instance>> newSelection, bool fromExplorer)> SelectionUpdateHandler;
|
||||
typedef std::function<void(std::shared_ptr<Instance> instance, std::string property, Variant newValue)> PropertyUpdateHandler;
|
||||
|
||||
// TEMPORARY COMMON DATA FOR VARIOUS INTERNAL COMPONENTS
|
||||
|
||||
|
@ -24,9 +24,9 @@ extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
|||
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
||||
extern Handles editorToolHandles;
|
||||
|
||||
void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer = false);
|
||||
const std::vector<InstanceRefWeak> getSelection();
|
||||
void setSelection(std::vector<std::shared_ptr<Instance>> newSelection, bool fromExplorer = false);
|
||||
const std::vector<std::shared_ptr<Instance>> getSelection();
|
||||
void addSelectionListener(SelectionUpdateHandler handler);
|
||||
|
||||
void sendPropertyUpdatedSignal(InstanceRef instance, std::string property, Data::Variant newValue);
|
||||
void sendPropertyUpdatedSignal(std::shared_ptr<Instance> instance, std::string property, Variant newValue);
|
||||
void addPropertyUpdateListener(PropertyUpdateHandler handler);
|
|
@ -26,8 +26,7 @@
|
|||
|
||||
#define AUTOGEN_PREAMBLE_DATA \
|
||||
public: \
|
||||
virtual const TypeInfo& GetType() const override; \
|
||||
static const TypeInfo TYPE; \
|
||||
virtual void PushLuaValue(lua_State*) const override; \
|
||||
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx); \
|
||||
static const TypeDesc TYPE; \
|
||||
virtual void PushLuaValue(lua_State*) const; \
|
||||
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx); \
|
||||
private:
|
||||
|
|
|
@ -1,162 +0,0 @@
|
|||
#include "base.h"
|
||||
#include "error/data.h"
|
||||
#include "meta.h"
|
||||
#include <ios>
|
||||
#include <sstream>
|
||||
#include <pugixml.hpp>
|
||||
#include "lua.h"
|
||||
|
||||
#define IMPL_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE, TYPE_NAME) Data::CLASS_NAME::CLASS_NAME(WRAPPED_TYPE in) : value(in) {} \
|
||||
Data::CLASS_NAME::~CLASS_NAME() = default; \
|
||||
Data::CLASS_NAME::operator const WRAPPED_TYPE() const { return value; } \
|
||||
const Data::TypeInfo Data::CLASS_NAME::TYPE = { \
|
||||
.name = TYPE_NAME, \
|
||||
.deserializer = &Data::CLASS_NAME::Deserialize, \
|
||||
.fromString = &Data::CLASS_NAME::FromString, \
|
||||
.fromLuaValue = &Data::CLASS_NAME::FromLuaValue, \
|
||||
}; \
|
||||
const Data::TypeInfo& Data::CLASS_NAME::GetType() const { return Data::CLASS_NAME::TYPE; }; \
|
||||
void Data::CLASS_NAME::Serialize(pugi::xml_node node) const { node.text().set(std::string(this->ToString())); }
|
||||
|
||||
Data::Base::~Base() {};
|
||||
|
||||
Data::Null::Null() {};
|
||||
Data::Null::~Null() = default;
|
||||
const Data::TypeInfo Data::Null::TYPE = {
|
||||
.name = "null",
|
||||
.deserializer = &Data::Null::Deserialize,
|
||||
};
|
||||
const Data::TypeInfo& Data::Null::GetType() const { return Data::Null::TYPE; };
|
||||
|
||||
const Data::String Data::Null::ToString() const {
|
||||
return Data::String("null");
|
||||
}
|
||||
|
||||
void Data::Null::Serialize(pugi::xml_node node) const {
|
||||
node.text().set("null");
|
||||
}
|
||||
|
||||
Data::Variant Data::Null::Deserialize(pugi::xml_node node) {
|
||||
return Data::Null();
|
||||
}
|
||||
|
||||
void Data::Null::PushLuaValue(lua_State* L) const {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
|
||||
result<Data::Variant, LuaCastError> Data::Null::FromLuaValue(lua_State* L, int idx) {
|
||||
return Data::Variant(Data::Null());
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
IMPL_WRAPPER_CLASS(Bool, bool, "bool")
|
||||
IMPL_WRAPPER_CLASS(Int, int, "int")
|
||||
IMPL_WRAPPER_CLASS(Float, float, "float")
|
||||
IMPL_WRAPPER_CLASS(String, std::string, "string")
|
||||
|
||||
// ToString
|
||||
|
||||
const Data::String Data::Bool::ToString() const {
|
||||
return Data::String(value ? "true" : "false");
|
||||
}
|
||||
|
||||
const Data::String Data::Int::ToString() const {
|
||||
return Data::String(std::to_string(value));
|
||||
}
|
||||
|
||||
const Data::String Data::Float::ToString() const {
|
||||
std::stringstream stream;
|
||||
stream << std::noshowpoint << value;
|
||||
return Data::String(stream.str());
|
||||
}
|
||||
|
||||
const Data::String Data::String::ToString() const {
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Deserialize
|
||||
|
||||
Data::Variant Data::Bool::Deserialize(pugi::xml_node node) {
|
||||
return Data::Bool(node.text().as_bool());
|
||||
}
|
||||
|
||||
Data::Variant Data::Int::Deserialize(pugi::xml_node node) {
|
||||
return Data::Int(node.text().as_int());
|
||||
}
|
||||
|
||||
Data::Variant Data::Float::Deserialize(pugi::xml_node node) {
|
||||
return Data::Float(node.text().as_float());
|
||||
}
|
||||
|
||||
Data::Variant Data::String::Deserialize(pugi::xml_node node) {
|
||||
return Data::String(node.text().as_string());
|
||||
}
|
||||
|
||||
// FromString
|
||||
|
||||
std::optional<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');
|
||||
}
|
||||
|
||||
std::optional<Data::Variant> Data::Int::FromString(std::string string) {
|
||||
char* endPos;
|
||||
int value = (int)std::strtol(string.c_str(), &endPos, 10);
|
||||
if (endPos == string.c_str()) return std::nullopt;
|
||||
return Data::Int(value);
|
||||
}
|
||||
|
||||
std::optional<Data::Variant> Data::Float::FromString(std::string string) {
|
||||
char* endPos;
|
||||
float value = std::strtof(string.c_str(), &endPos);
|
||||
if (endPos == string.c_str()) return std::nullopt;
|
||||
return Data::Float(value);
|
||||
}
|
||||
|
||||
std::optional<Data::Variant> Data::String::FromString(std::string string) {
|
||||
return Data::String(string);
|
||||
}
|
||||
|
||||
// PushLuaValue
|
||||
|
||||
void Data::Bool::PushLuaValue(lua_State* L) const {
|
||||
lua_pushboolean(L, *this);
|
||||
}
|
||||
|
||||
void Data::Int::PushLuaValue(lua_State* L) const {
|
||||
lua_pushinteger(L, *this);
|
||||
}
|
||||
|
||||
void Data::Float::PushLuaValue(lua_State* L) const {
|
||||
lua_pushnumber(L, *this);
|
||||
}
|
||||
|
||||
void Data::String::PushLuaValue(lua_State* L) const {
|
||||
lua_pushstring(L, value.c_str());
|
||||
}
|
||||
|
||||
// FromLuaValue
|
||||
|
||||
result<Data::Variant, LuaCastError> Data::Bool::FromLuaValue(lua_State* L, int idx) {
|
||||
if (!lua_isboolean(L, idx))
|
||||
return LuaCastError(lua_typename(L, idx), "boolean");
|
||||
return Data::Variant(Data::Bool(lua_toboolean(L, idx)));
|
||||
}
|
||||
|
||||
result<Data::Variant, LuaCastError> Data::Int::FromLuaValue(lua_State* L, int idx) {
|
||||
if (!lua_isnumber(L, idx))
|
||||
return LuaCastError(lua_typename(L, idx), "integer");
|
||||
return Data::Variant(Data::Int((int)lua_tonumber(L, idx)));
|
||||
}
|
||||
|
||||
result<Data::Variant, LuaCastError> Data::Float::FromLuaValue(lua_State* L, int idx) {
|
||||
if (!lua_isnumber(L, idx))
|
||||
return LuaCastError(lua_typename(L, idx), "float");
|
||||
return Data::Variant(Data::Float((float)lua_tonumber(L, idx)));
|
||||
}
|
||||
|
||||
result<Data::Variant, LuaCastError> Data::String::FromLuaValue(lua_State* L, int idx) {
|
||||
if (!lua_tostring(L, idx))
|
||||
return LuaCastError(lua_typename(L, idx), "string");
|
||||
return Data::Variant(Data::String(lua_tostring(L, idx)));
|
||||
}
|
|
@ -10,67 +10,39 @@ extern "C" { typedef struct lua_State lua_State; }
|
|||
|
||||
namespace pugi { class xml_node; };
|
||||
|
||||
#define DEF_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE) class CLASS_NAME : public Data::Base { \
|
||||
WRAPPED_TYPE value; \
|
||||
public: \
|
||||
CLASS_NAME(WRAPPED_TYPE); \
|
||||
~CLASS_NAME(); \
|
||||
operator const WRAPPED_TYPE() const; \
|
||||
virtual const TypeInfo& GetType() const override; \
|
||||
static const TypeInfo TYPE; \
|
||||
\
|
||||
virtual const Data::String ToString() const override; \
|
||||
virtual void Serialize(pugi::xml_node node) const override; \
|
||||
virtual void PushLuaValue(lua_State*) const override; \
|
||||
\
|
||||
static Data::Variant Deserialize(pugi::xml_node node); \
|
||||
static std::optional<Data::Variant> FromString(std::string); \
|
||||
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx); \
|
||||
class Variant;
|
||||
struct TypeMeta;
|
||||
|
||||
typedef std::function<void(Variant, pugi::xml_node)> Serialize;
|
||||
typedef std::function<result<Variant, DataParseError>(pugi::xml_node, const TypeMeta)> Deserialize;
|
||||
typedef std::function<std::string(Variant)> ToString;
|
||||
typedef std::function<result<Variant, DataParseError>(std::string, const TypeMeta)> FromString;
|
||||
typedef std::function<result<Variant, LuaCastError>(lua_State*, int idx)> FromLuaValue;
|
||||
typedef std::function<void(Variant self, lua_State*)> PushLuaValue;
|
||||
|
||||
// Describes a concrete type
|
||||
struct TypeDesc {
|
||||
std::string name;
|
||||
Serialize serialize;
|
||||
Deserialize deserialize;
|
||||
ToString toString;
|
||||
FromString fromString;
|
||||
PushLuaValue pushLuaValue;
|
||||
FromLuaValue fromLuaValue;
|
||||
};
|
||||
|
||||
namespace Data {
|
||||
class Variant;
|
||||
typedef std::function<Data::Variant(pugi::xml_node)> Deserializer;
|
||||
typedef std::function<std::optional<Data::Variant>(std::string)> FromString;
|
||||
typedef std::function<result<Data::Variant, LuaCastError>(lua_State*, int idx)> FromLuaValue;
|
||||
class Enum;
|
||||
struct InstanceType;
|
||||
|
||||
struct TypeInfo {
|
||||
std::string name;
|
||||
Deserializer deserializer;
|
||||
FromString fromString;
|
||||
FromLuaValue fromLuaValue;
|
||||
// Describes a meta-type, which consists of a concrete type, and some generic argument.
|
||||
struct TypeMeta {
|
||||
const TypeDesc* descriptor;
|
||||
union {
|
||||
const Enum* enum_; // Applicable for EnumItem
|
||||
const InstanceType* instType; // Applicable for InstanceRef
|
||||
};
|
||||
|
||||
class String;
|
||||
class Base {
|
||||
public:
|
||||
virtual ~Base();
|
||||
virtual const TypeInfo& GetType() const = 0;
|
||||
virtual const Data::String ToString() const = 0;
|
||||
virtual void Serialize(pugi::xml_node node) const = 0;
|
||||
virtual void PushLuaValue(lua_State*) const = 0;
|
||||
};
|
||||
|
||||
class Null : Base {
|
||||
public:
|
||||
Null();
|
||||
~Null();
|
||||
virtual const TypeInfo& GetType() const override;
|
||||
static const TypeInfo TYPE;
|
||||
|
||||
virtual const Data::String ToString() const override;
|
||||
virtual void Serialize(pugi::xml_node node) const override;
|
||||
virtual void PushLuaValue(lua_State*) const override;
|
||||
|
||||
static Data::Variant Deserialize(pugi::xml_node node);
|
||||
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
|
||||
};
|
||||
|
||||
DEF_WRAPPER_CLASS(Bool, bool)
|
||||
DEF_WRAPPER_CLASS(Int, int)
|
||||
DEF_WRAPPER_CLASS(Float, float)
|
||||
DEF_WRAPPER_CLASS(String, std::string)
|
||||
};
|
||||
|
||||
|
||||
#undef DEF_WRAPPER_CLASS
|
||||
inline TypeMeta(const TypeDesc* descriptor) : descriptor(descriptor) {}
|
||||
TypeMeta(const Enum*);
|
||||
TypeMeta(const InstanceType*);
|
||||
};
|
|
@ -1,23 +1,24 @@
|
|||
#include "cframe.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "error/data.h"
|
||||
#include "physics/util.h"
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/gtc/matrix_inverse.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/matrix.hpp>
|
||||
#include <reactphysics3d/mathematics/Transform.h>
|
||||
#include "datatypes/meta.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include <pugixml.hpp>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/euler_angles.hpp>
|
||||
// #include "meta.h" // IWYU pragma: keep
|
||||
// #include "variant.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)));
|
||||
const CFrame CFrame::IDENTITY(glm::vec3(0, 0, 0), glm::mat3(1.f));
|
||||
const CFrame 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() : Data::CFrame::CFrame(glm::vec3(0, 0, 0), glm::mat3(1.f)) {}
|
||||
CFrame::CFrame() : CFrame::CFrame(glm::vec3(0, 0, 0), glm::mat3(1.f)) {}
|
||||
|
||||
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)
|
||||
CFrame::CFrame(float x, float y, float z, float R00, float R01, float R02, float R10, float R11, float R12, float R20, float R21, float R22)
|
||||
: translation(x, y, z)
|
||||
, rotation({
|
||||
// { R00, R01, R02 },
|
||||
|
@ -29,17 +30,17 @@ Data::CFrame::CFrame(float x, float y, float z, float R00, float R01, float R02,
|
|||
}) {
|
||||
}
|
||||
|
||||
Data::CFrame::CFrame(glm::vec3 translation, glm::mat3 rotation)
|
||||
CFrame::CFrame(glm::vec3 translation, glm::mat3 rotation)
|
||||
: translation(translation)
|
||||
, rotation(rotation) {
|
||||
}
|
||||
|
||||
Data::CFrame::CFrame(Vector3 position, glm::quat quat)
|
||||
CFrame::CFrame(Vector3 position, glm::quat quat)
|
||||
: translation(position)
|
||||
, rotation(quat) {
|
||||
}
|
||||
|
||||
Data::CFrame::CFrame(const rp::Transform& transform) : Data::CFrame::CFrame(rpToGlm(transform.getPosition()), rpToGlm(transform.getOrientation())) {
|
||||
CFrame::CFrame(const rp::Transform& transform) : CFrame::CFrame(rpToGlm(transform.getPosition()), rpToGlm(transform.getOrientation())) {
|
||||
}
|
||||
|
||||
glm::mat3 lookAt(Vector3 position, Vector3 lookAt, Vector3 up) {
|
||||
|
@ -52,35 +53,35 @@ glm::mat3 lookAt(Vector3 position, Vector3 lookAt, Vector3 up) {
|
|||
return { s, u, -f };
|
||||
}
|
||||
|
||||
Data::CFrame::CFrame(Vector3 position, Vector3 lookAt, Vector3 up)
|
||||
CFrame::CFrame(Vector3 position, Vector3 lookAt, Vector3 up)
|
||||
: translation(position)
|
||||
, rotation(::lookAt(position, lookAt, up)) {
|
||||
}
|
||||
|
||||
Data::CFrame::~CFrame() = default;
|
||||
CFrame::~CFrame() = default;
|
||||
|
||||
const Data::String Data::CFrame::ToString() const {
|
||||
const std::string CFrame::ToString() const {
|
||||
return std::to_string(X()) + ", " + std::to_string(Y()) + ", " + std::to_string(Z());
|
||||
}
|
||||
|
||||
Data::CFrame Data::CFrame::pointToward(Vector3 position, Vector3 toward) {
|
||||
return Data::CFrame(position, position + toward, (abs(toward.Dot(Vector3(0, 1, 0))) > 0.999) ? Vector3(0, 0, 1) : Vector3(0, 1, 0));
|
||||
CFrame CFrame::pointToward(Vector3 position, Vector3 toward) {
|
||||
return CFrame(position, position + toward, (abs(toward.Dot(Vector3(0, 1, 0))) > 0.999) ? Vector3(0, 0, 1) : Vector3(0, 1, 0));
|
||||
}
|
||||
|
||||
Data::CFrame Data::CFrame::pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right) {
|
||||
return Data::CFrame(position, position + toward, (abs(toward.Dot(up)) > 0.999) ? right : up);
|
||||
CFrame CFrame::pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right) {
|
||||
return CFrame(position, position + toward, (abs(toward.Dot(up)) > 0.999) ? right : up);
|
||||
}
|
||||
|
||||
Data::CFrame::operator glm::mat4() const {
|
||||
CFrame::operator glm::mat4() const {
|
||||
// Always make sure to translate the position first, then rotate. Matrices work backwards
|
||||
return glm::translate(glm::mat4(1.0f), this->translation) * glm::mat4(this->rotation);
|
||||
}
|
||||
|
||||
Data::CFrame::operator rp::Transform() const {
|
||||
CFrame::operator rp::Transform() const {
|
||||
return rp::Transform(glmToRp(translation), glmToRp(rotation));
|
||||
}
|
||||
|
||||
Vector3 Data::CFrame::ToEulerAnglesXYZ() {
|
||||
Vector3 CFrame::ToEulerAnglesXYZ() {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
|
@ -88,37 +89,37 @@ Vector3 Data::CFrame::ToEulerAnglesXYZ() {
|
|||
return Vector3(x, y, z);
|
||||
}
|
||||
|
||||
Data::CFrame Data::CFrame::FromEulerAnglesXYZ(Vector3 vector) {
|
||||
CFrame CFrame::FromEulerAnglesXYZ(Vector3 vector) {
|
||||
glm::mat3 mat = glm::eulerAngleXYZ(vector.X(), vector.Y(), vector.Z());
|
||||
return Data::CFrame((glm::vec3)Vector3::ZERO, mat);
|
||||
return CFrame((glm::vec3)Vector3::ZERO, mat);
|
||||
}
|
||||
|
||||
Data::CFrame Data::CFrame::Inverse() const {
|
||||
CFrame CFrame::Inverse() const {
|
||||
return CFrame { -translation * glm::transpose(glm::inverse(rotation)), glm::inverse(rotation) };
|
||||
}
|
||||
|
||||
|
||||
// Operators
|
||||
|
||||
Data::CFrame Data::CFrame::operator *(Data::CFrame otherFrame) const {
|
||||
CFrame CFrame::operator *(CFrame otherFrame) const {
|
||||
return CFrame { this->translation + this->rotation * otherFrame.translation, this->rotation * otherFrame.rotation };
|
||||
}
|
||||
|
||||
Vector3 Data::CFrame::operator *(Vector3 vector) const {
|
||||
Vector3 CFrame::operator *(Vector3 vector) const {
|
||||
return this->translation + this->rotation * vector;
|
||||
}
|
||||
|
||||
Data::CFrame Data::CFrame::operator +(Vector3 vector) const {
|
||||
CFrame CFrame::operator +(Vector3 vector) const {
|
||||
return CFrame { this->translation + glm::vec3(vector), this->rotation };
|
||||
}
|
||||
|
||||
Data::CFrame Data::CFrame::operator -(Vector3 vector) const {
|
||||
CFrame CFrame::operator -(Vector3 vector) const {
|
||||
return *this + -vector;
|
||||
}
|
||||
|
||||
// Serialization
|
||||
|
||||
void Data::CFrame::Serialize(pugi::xml_node node) const {
|
||||
void CFrame::Serialize(pugi::xml_node node) const {
|
||||
node.append_child("X").text().set(std::to_string(this->X()));
|
||||
node.append_child("Y").text().set(std::to_string(this->Y()));
|
||||
node.append_child("Z").text().set(std::to_string(this->Z()));
|
||||
|
@ -134,8 +135,8 @@ void Data::CFrame::Serialize(pugi::xml_node node) const {
|
|||
}
|
||||
|
||||
|
||||
Data::Variant Data::CFrame::Deserialize(pugi::xml_node node) {
|
||||
return Data::CFrame(
|
||||
result<CFrame, DataParseError> CFrame::Deserialize(pugi::xml_node node) {
|
||||
return CFrame(
|
||||
node.child("X").text().as_float(),
|
||||
node.child("Y").text().as_float(),
|
||||
node.child("Z").text().as_float(),
|
||||
|
|
|
@ -3,72 +3,69 @@
|
|||
#include "base.h"
|
||||
#include "datatypes/annotation.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "error/data.h"
|
||||
#include <glm/ext/quaternion_float.hpp>
|
||||
#include <glm/gtc/matrix_access.hpp>
|
||||
#include <glm/matrix.hpp>
|
||||
|
||||
namespace reactphysics3d { class Transform; };
|
||||
|
||||
namespace Data {
|
||||
class DEF_DATA_(name="CoordinateFrame") CFrame : public Base {
|
||||
AUTOGEN_PREAMBLE_DATA
|
||||
class DEF_DATA_(name="CoordinateFrame") CFrame {
|
||||
AUTOGEN_PREAMBLE_DATA
|
||||
|
||||
glm::vec3 translation;
|
||||
glm::mat3 rotation;
|
||||
|
||||
CFrame(glm::vec3, glm::mat3);
|
||||
public:
|
||||
// CFrame(float x, float y, float z);
|
||||
// CFrame(const glm::vec3&);
|
||||
// CFrame(const rp::Vector3&);
|
||||
DEF_DATA_CTOR CFrame();
|
||||
DEF_DATA_CTOR 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);
|
||||
DEF_DATA_CTOR CFrame(Vector3 , Vector3 lookAt, Vector3 up = Vector3(0, 1, 0));
|
||||
CFrame(const reactphysics3d::Transform&);
|
||||
CFrame(Vector3 position, glm::quat quat);
|
||||
~CFrame();
|
||||
glm::vec3 translation;
|
||||
glm::mat3 rotation;
|
||||
|
||||
// Same as CFrame(position, position + toward), but makes sure that up and toward are not linearly dependant
|
||||
static CFrame pointToward(Vector3 position, Vector3 toward);
|
||||
// Creates a cframe looking at position + toward, whilst aligning its up to up.
|
||||
// If up and toward are approximately linearly dependent (their absolute dot product > 0.999),
|
||||
// then the right is used instead
|
||||
// Up and right must NOT be linearly dependent
|
||||
static CFrame pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right);
|
||||
CFrame(glm::vec3, glm::mat3);
|
||||
public:
|
||||
// CFrame(float x, float y, float z);
|
||||
// CFrame(const glm::vec3&);
|
||||
// CFrame(const rp::Vector3&);
|
||||
DEF_DATA_CTOR CFrame();
|
||||
DEF_DATA_CTOR 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);
|
||||
DEF_DATA_CTOR CFrame(Vector3 , Vector3 lookAt, Vector3 up = Vector3(0, 1, 0));
|
||||
CFrame(const reactphysics3d::Transform&);
|
||||
CFrame(Vector3 position, glm::quat quat);
|
||||
virtual ~CFrame();
|
||||
|
||||
DEF_DATA_PROP static const CFrame IDENTITY;
|
||||
static const CFrame YToZ;
|
||||
// Same as CFrame(position, position + toward), but makes sure that up and toward are not linearly dependant
|
||||
static CFrame pointToward(Vector3 position, Vector3 toward);
|
||||
// Creates a cframe looking at position + toward, whilst aligning its up to up.
|
||||
// If up and toward are approximately linearly dependent (their absolute dot product > 0.999),
|
||||
// then the right is used instead
|
||||
// Up and right must NOT be linearly dependent
|
||||
static CFrame pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right);
|
||||
|
||||
virtual const Data::String ToString() const override;
|
||||
virtual void Serialize(pugi::xml_node parent) const override;
|
||||
static Data::Variant Deserialize(pugi::xml_node node);
|
||||
DEF_DATA_PROP static const CFrame IDENTITY;
|
||||
static const CFrame YToZ;
|
||||
|
||||
static void PushLuaLibrary(lua_State*);
|
||||
virtual const std::string ToString() const;
|
||||
virtual void Serialize(pugi::xml_node parent) const;
|
||||
static result<CFrame, DataParseError> Deserialize(pugi::xml_node node);
|
||||
|
||||
operator glm::mat4() const;
|
||||
operator reactphysics3d::Transform() const;
|
||||
static void PushLuaLibrary(lua_State*);
|
||||
|
||||
//inline static CFrame identity() { }
|
||||
DEF_DATA_PROP inline Vector3 Position() const { return translation; }
|
||||
DEF_DATA_PROP inline CFrame Rotation() const { return CFrame { glm::vec3(0, 0, 0), rotation }; }
|
||||
DEF_DATA_METHOD CFrame Inverse() const;
|
||||
DEF_DATA_PROP inline float X() const { return translation.x; }
|
||||
DEF_DATA_PROP inline float Y() const { return translation.y; }
|
||||
DEF_DATA_PROP inline float Z() const { return translation.z; }
|
||||
operator glm::mat4() const;
|
||||
operator reactphysics3d::Transform() const;
|
||||
|
||||
DEF_DATA_PROP inline Vector3 RightVector() { return glm::column(rotation, 0); }
|
||||
DEF_DATA_PROP inline Vector3 UpVector() { return glm::column(rotation, 1); }
|
||||
DEF_DATA_PROP inline Vector3 LookVector() { return -glm::column(rotation, 2); }
|
||||
//inline static CFrame identity() { }
|
||||
DEF_DATA_PROP inline Vector3 Position() const { return translation; }
|
||||
DEF_DATA_PROP inline CFrame Rotation() const { return CFrame { glm::vec3(0, 0, 0), rotation }; }
|
||||
DEF_DATA_METHOD CFrame Inverse() const;
|
||||
DEF_DATA_PROP inline float X() const { return translation.x; }
|
||||
DEF_DATA_PROP inline float Y() const { return translation.y; }
|
||||
DEF_DATA_PROP inline float Z() const { return translation.z; }
|
||||
|
||||
DEF_DATA_METHOD Vector3 ToEulerAnglesXYZ();
|
||||
DEF_DATA_METHOD static CFrame FromEulerAnglesXYZ(Vector3);
|
||||
DEF_DATA_PROP inline Vector3 RightVector() { return glm::column(rotation, 0); }
|
||||
DEF_DATA_PROP inline Vector3 UpVector() { return glm::column(rotation, 1); }
|
||||
DEF_DATA_PROP inline Vector3 LookVector() { return -glm::column(rotation, 2); }
|
||||
|
||||
// Operators
|
||||
DEF_DATA_OP Data::CFrame operator *(Data::CFrame) const;
|
||||
DEF_DATA_OP Vector3 operator *(Vector3) const;
|
||||
DEF_DATA_OP Data::CFrame operator +(Vector3) const;
|
||||
DEF_DATA_OP Data::CFrame operator -(Vector3) const;
|
||||
};
|
||||
}
|
||||
DEF_DATA_METHOD Vector3 ToEulerAnglesXYZ();
|
||||
DEF_DATA_METHOD static CFrame FromEulerAnglesXYZ(Vector3);
|
||||
|
||||
using Data::CFrame;
|
||||
// Operators
|
||||
DEF_DATA_OP CFrame operator *(CFrame) const;
|
||||
DEF_DATA_OP Vector3 operator *(Vector3) const;
|
||||
DEF_DATA_OP CFrame operator +(Vector3) const;
|
||||
DEF_DATA_OP CFrame operator -(Vector3) const;
|
||||
};
|
|
@ -1,21 +1,23 @@
|
|||
#include "color3.h"
|
||||
#include "datatypes/meta.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "error/data.h"
|
||||
#include <pugixml.hpp>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
Data::Color3::Color3(float r, float g, float b) : r(std::clamp(r, 0.f, 1.f)), g(std::clamp(g, 0.f, 1.f)), b(std::clamp(b, 0.f, 1.f)) {};
|
||||
Data::Color3::Color3(const glm::vec3& vec) : r(std::clamp(vec.x, 0.f, 1.f)), g(std::clamp(vec.y, 0.f, 1.f)), b(std::clamp(vec.z, 0.f, 1.f)) {};
|
||||
Color3::Color3(float r, float g, float b) : r(std::clamp(r, 0.f, 1.f)), g(std::clamp(g, 0.f, 1.f)), b(std::clamp(b, 0.f, 1.f)) {};
|
||||
Color3::Color3(const glm::vec3& vec) : r(std::clamp(vec.x, 0.f, 1.f)), g(std::clamp(vec.y, 0.f, 1.f)), b(std::clamp(vec.z, 0.f, 1.f)) {};
|
||||
|
||||
Data::Color3::~Color3() = default;
|
||||
Color3::~Color3() = default;
|
||||
|
||||
const Data::String Data::Color3::ToString() const {
|
||||
const std::string Color3::ToString() const {
|
||||
return std::to_string(int(r*256)) + ", " + std::to_string(int(g*256)) + ", " + std::to_string(int(b*256));
|
||||
}
|
||||
|
||||
Data::Color3::operator glm::vec3() const { return glm::vec3(r, g, b); };
|
||||
Color3::operator glm::vec3() const { return glm::vec3(r, g, b); };
|
||||
|
||||
std::string Data::Color3::ToHex() const {
|
||||
std::string Color3::ToHex() const {
|
||||
std::stringstream ss;
|
||||
ss << "FF" << std::hex << std::uppercase << std::setfill('0')
|
||||
<< std::setw(2) << int(r*255)
|
||||
|
@ -25,7 +27,7 @@ std::string Data::Color3::ToHex() const {
|
|||
}
|
||||
|
||||
|
||||
Data::Color3 Data::Color3::FromHex(std::string hex) {
|
||||
Color3 Color3::FromHex(std::string hex) {
|
||||
float r = float(std::stoi(hex.substr(2, 2), nullptr, 16)) / 255;
|
||||
float g = float(std::stoi(hex.substr(4, 2), nullptr, 16)) / 255;
|
||||
float b = float(std::stoi(hex.substr(6, 2), nullptr, 16)) / 255;
|
||||
|
@ -35,10 +37,10 @@ Data::Color3 Data::Color3::FromHex(std::string hex) {
|
|||
|
||||
// Serialization
|
||||
|
||||
void Data::Color3::Serialize(pugi::xml_node node) const {
|
||||
void Color3::Serialize(pugi::xml_node node) const {
|
||||
node.text().set(this->ToHex());
|
||||
}
|
||||
|
||||
Data::Variant Data::Color3::Deserialize(pugi::xml_node node) {
|
||||
result<Color3, DataParseError> Color3::Deserialize(pugi::xml_node node) {
|
||||
return Color3::FromHex(node.text().get());
|
||||
}
|
|
@ -2,36 +2,33 @@
|
|||
|
||||
#include "base.h"
|
||||
#include "datatypes/annotation.h"
|
||||
#include "error/data.h"
|
||||
#include <glm/ext/vector_float3.hpp>
|
||||
|
||||
namespace Data {
|
||||
class DEF_DATA Color3 : public Base {
|
||||
AUTOGEN_PREAMBLE_DATA
|
||||
class DEF_DATA Color3 {
|
||||
AUTOGEN_PREAMBLE_DATA
|
||||
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
|
||||
public:
|
||||
DEF_DATA_CTOR Color3(float r, float g, float b);
|
||||
Color3(const glm::vec3&);
|
||||
~Color3();
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
|
||||
DEF_DATA_METHOD static Color3 FromHex(std::string hex);
|
||||
public:
|
||||
DEF_DATA_CTOR Color3(float r, float g, float b);
|
||||
Color3(const glm::vec3&);
|
||||
virtual ~Color3();
|
||||
|
||||
virtual const Data::String ToString() const override;
|
||||
DEF_DATA_METHOD std::string ToHex() const;
|
||||
virtual void Serialize(pugi::xml_node node) const override;
|
||||
static Data::Variant Deserialize(pugi::xml_node node);
|
||||
DEF_DATA_METHOD static Color3 FromHex(std::string hex);
|
||||
|
||||
static void PushLuaLibrary(lua_State*);
|
||||
virtual const std::string ToString() const;
|
||||
DEF_DATA_METHOD std::string ToHex() const;
|
||||
virtual void Serialize(pugi::xml_node node) const;
|
||||
static result<Color3, DataParseError> Deserialize(pugi::xml_node node);
|
||||
|
||||
operator glm::vec3() const;
|
||||
static void PushLuaLibrary(lua_State*);
|
||||
|
||||
DEF_DATA_PROP inline float R() const { return r; }
|
||||
DEF_DATA_PROP inline float G() const { return g; }
|
||||
DEF_DATA_PROP inline float B() const { return b; }
|
||||
};
|
||||
}
|
||||
operator glm::vec3() const;
|
||||
|
||||
using Data::Color3;
|
||||
DEF_DATA_PROP inline float R() const { return r; }
|
||||
DEF_DATA_PROP inline float G() const { return g; }
|
||||
DEF_DATA_PROP inline float B() const { return b; }
|
||||
};
|
72
core/src/datatypes/enum.cpp
Normal file
72
core/src/datatypes/enum.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#include "enum.h"
|
||||
#include "datatypes/base.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "error/data.h"
|
||||
#include <pugixml.hpp>
|
||||
|
||||
TypeMeta::TypeMeta(const Enum* enum_) : descriptor(&EnumItem::TYPE), enum_(enum_) {}
|
||||
|
||||
Enum::Enum(_EnumData* data) : data(data) {}
|
||||
|
||||
std::vector<EnumItem> Enum::GetEnumItems() const {
|
||||
std::vector<EnumItem> enumItems;
|
||||
|
||||
for (int i = 0; i < data->count; i++) {
|
||||
enumItems.push_back(EnumItem(data, data->values[i].second, data->values[i].first));
|
||||
}
|
||||
|
||||
return enumItems;
|
||||
}
|
||||
|
||||
std::optional<EnumItem> Enum::FromName(std::string name) const {
|
||||
for (int i = 0; i < data->count; i++) {
|
||||
if (data->values[i].second == name)
|
||||
return EnumItem(data, name, data->values[i].first);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<EnumItem> Enum::FromValue(int value) const {
|
||||
for (int i = 0; i < data->count; i++) {
|
||||
if (data->values[i].first == value)
|
||||
return EnumItem(data, data->values[i].second, value);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
EnumItem Enum::FromValueInternal(int value) const {
|
||||
auto result = this->FromValue(value);
|
||||
if (!result) return EnumItem(data, "", value);
|
||||
return result.value();
|
||||
}
|
||||
|
||||
EnumItem::EnumItem(_EnumData* parentData, std::string name, int value) : parentData(parentData), name(name), value(value) {}
|
||||
|
||||
//
|
||||
|
||||
std::string Enum::ToString() const {
|
||||
return "Enum." + this->data->name;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
std::string EnumItem::ToString() const {
|
||||
return "Enum." + parentData->name + "." + name;
|
||||
}
|
||||
|
||||
void EnumItem::Serialize(pugi::xml_node node) const {
|
||||
node.set_name("token");
|
||||
node.text().set(value);
|
||||
}
|
||||
|
||||
result<EnumItem, DataParseError> EnumItem::Deserialize(pugi::xml_node node, const TypeMeta info) {
|
||||
auto result = info.enum_->FromValue(node.text().as_int());
|
||||
if (result.has_value()) return result.value();
|
||||
return DataParseError(node.text().as_string(), "EnumItem");
|
||||
}
|
||||
|
||||
result<EnumItem, DataParseError> EnumItem::FromString(std::string string, const TypeMeta info) {
|
||||
auto result = info.enum_->FromName(string);
|
||||
if (result.has_value()) return result.value();
|
||||
return DataParseError(string, "EnumItem");
|
||||
}
|
59
core/src/datatypes/enum.h
Normal file
59
core/src/datatypes/enum.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "datatypes/annotation.h"
|
||||
#include "error/data.h"
|
||||
#include "lua.h"
|
||||
|
||||
struct _EnumData {
|
||||
std::string name;
|
||||
std::pair<int, std::string>* values;
|
||||
int count;
|
||||
};
|
||||
|
||||
class EnumItem;
|
||||
|
||||
class DEF_DATA Enum {
|
||||
_EnumData* data;
|
||||
public:
|
||||
Enum(_EnumData*);
|
||||
virtual ~Enum() = default;
|
||||
|
||||
static const TypeDesc TYPE;
|
||||
|
||||
inline _EnumData* InternalType() const { return this->data; };
|
||||
std::vector<EnumItem> GetEnumItems() const;
|
||||
std::optional<EnumItem> FromName(std::string) const;
|
||||
std::optional<EnumItem> FromValue(int) const;
|
||||
|
||||
EnumItem FromValueInternal(int) const;
|
||||
|
||||
std::string ToString() const;
|
||||
void PushLuaValue(lua_State*) const;
|
||||
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int);
|
||||
};
|
||||
|
||||
class DEF_DATA EnumItem {
|
||||
_EnumData* parentData;
|
||||
std::string name;
|
||||
int value;
|
||||
public:
|
||||
EnumItem(_EnumData*, std::string, int);
|
||||
virtual ~EnumItem() = default;
|
||||
|
||||
static const TypeDesc TYPE;
|
||||
|
||||
inline std::string Name() const { return this->name; }
|
||||
inline int Value() const { return this->value; }
|
||||
inline Enum EnumType() const { return Enum(this->parentData); }
|
||||
|
||||
static result<EnumItem, DataParseError> FromString(std::string, const TypeMeta);
|
||||
std::string ToString() const;
|
||||
void Serialize(pugi::xml_node) const;
|
||||
static result<EnumItem, DataParseError> Deserialize(pugi::xml_node, const TypeMeta);
|
||||
void PushLuaValue(lua_State*) const;
|
||||
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int);
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
#include "meta.h"
|
||||
#include "datatypes/base.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/ref.h"
|
||||
#include "logger.h"
|
||||
#include "panic.h"
|
||||
#include <pugixml.hpp>
|
||||
#include <variant>
|
||||
|
||||
Data::String Data::Variant::ToString() const {
|
||||
return std::visit([](auto&& it) {
|
||||
return it.ToString();
|
||||
}, this->wrapped);
|
||||
}
|
||||
|
||||
void Data::Variant::Serialize(pugi::xml_node node) const {
|
||||
std::visit([&](auto&& it) {
|
||||
it.Serialize(node);
|
||||
}, this->wrapped);
|
||||
}
|
||||
|
||||
void Data::Variant::PushLuaValue(lua_State* state) const {
|
||||
return std::visit([&](auto&& it) {
|
||||
return it.PushLuaValue(state);
|
||||
}, this->wrapped);
|
||||
}
|
||||
|
||||
Data::Variant Data::Variant::Deserialize(pugi::xml_node node) {
|
||||
if (Data::TYPE_MAP.count(node.name()) == 0) {
|
||||
Logger::fatalErrorf("Unknown type for property: '%s'", node.name());
|
||||
panic();
|
||||
}
|
||||
|
||||
const Data::TypeInfo* type = Data::TYPE_MAP[node.name()];
|
||||
return type->deserializer(node);
|
||||
}
|
||||
|
||||
std::map<std::string, const Data::TypeInfo*> Data::TYPE_MAP = {
|
||||
{ "null", &Data::Null::TYPE },
|
||||
{ "bool", &Data::Bool::TYPE },
|
||||
{ "int", &Data::Int::TYPE },
|
||||
{ "float", &Data::Float::TYPE },
|
||||
{ "string", &Data::String::TYPE },
|
||||
{ "Vector3", &Data::Vector3::TYPE },
|
||||
{ "CoordinateFrame", &Data::CFrame::TYPE },
|
||||
{ "Color3", &Data::Color3::TYPE },
|
||||
{ "Ref", &Data::InstanceRef::TYPE },
|
||||
};
|
|
@ -1,49 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
#include <map>
|
||||
#include "base.h"
|
||||
#include "datatypes/color3.h"
|
||||
#include "datatypes/ref.h"
|
||||
#include "datatypes/signal.h"
|
||||
#include "vector.h"
|
||||
#include "cframe.h"
|
||||
|
||||
// #define __VARIANT_TYPE std::variant< \
|
||||
// Null, \
|
||||
// Bool, \
|
||||
// Int, \
|
||||
// Float, \
|
||||
// String \
|
||||
// >
|
||||
|
||||
namespace Data {
|
||||
typedef std::variant<
|
||||
Null,
|
||||
Bool,
|
||||
Int,
|
||||
Float,
|
||||
String,
|
||||
Vector3,
|
||||
CFrame,
|
||||
Color3,
|
||||
InstanceRef,
|
||||
SignalRef,
|
||||
SignalConnectionRef
|
||||
> __VARIANT_TYPE;
|
||||
|
||||
class Variant {
|
||||
__VARIANT_TYPE wrapped;
|
||||
public:
|
||||
template <typename T> Variant(T obj) : wrapped(obj) {}
|
||||
template <typename T> T get() { return std::get<T>(wrapped); }
|
||||
Data::String ToString() const;
|
||||
|
||||
void Serialize(pugi::xml_node node) const;
|
||||
void PushLuaValue(lua_State* state) const;
|
||||
static Data::Variant Deserialize(pugi::xml_node node);
|
||||
};
|
||||
|
||||
// Map of all data types to their type names
|
||||
extern std::map<std::string, const TypeInfo*> TYPE_MAP;
|
||||
}
|
212
core/src/datatypes/primitives.cpp
Normal file
212
core/src/datatypes/primitives.cpp
Normal file
|
@ -0,0 +1,212 @@
|
|||
#include "primitives.h"
|
||||
#include "error/data.h"
|
||||
#include "variant.h"
|
||||
#include <pugixml.hpp>
|
||||
#include "lua.h"
|
||||
#include <sstream>
|
||||
|
||||
// null
|
||||
|
||||
void Null_Serialize(Variant self, pugi::xml_node node) {
|
||||
node.text().set("null");
|
||||
}
|
||||
|
||||
result<Variant, DataParseError> Null_Deserialize(pugi::xml_node node, const TypeMeta) {
|
||||
return std::monostate();
|
||||
}
|
||||
|
||||
const std::string Null_ToString(Variant self) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
result<Variant, DataParseError> Null_FromString(std::string string, const TypeMeta) {
|
||||
return std::monostate();
|
||||
}
|
||||
|
||||
void Null_PushLuaValue(Variant self, lua_State* L) {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
|
||||
result<Variant, LuaCastError> Null_FromLuaValue(lua_State* L, int idx) {
|
||||
return Variant(std::monostate());
|
||||
}
|
||||
|
||||
const TypeDesc NULL_TYPE {
|
||||
"null",
|
||||
Null_Serialize,
|
||||
Null_Deserialize,
|
||||
Null_ToString,
|
||||
Null_FromString,
|
||||
Null_PushLuaValue,
|
||||
Null_FromLuaValue,
|
||||
};
|
||||
|
||||
// /null
|
||||
|
||||
// bool
|
||||
|
||||
void Bool_Serialize(Variant self, pugi::xml_node node) {
|
||||
node.text().set(self.get<bool>() ? "true" : "false");
|
||||
}
|
||||
|
||||
result<Variant, DataParseError> Bool_Deserialize(pugi::xml_node node, const TypeMeta) {
|
||||
return node.text().as_bool();
|
||||
}
|
||||
|
||||
const std::string Bool_ToString(Variant self) {
|
||||
return self.get<bool>() ? "true" : "false";
|
||||
}
|
||||
|
||||
result<Variant, DataParseError> Bool_FromString(std::string string, const TypeMeta) {
|
||||
return string[0] == 't' || string[0] == 'T' || string[0] == '1' || string[0] == 'y' || string[0] == 'Y';
|
||||
}
|
||||
|
||||
void Bool_PushLuaValue(Variant self, lua_State* L) {
|
||||
lua_pushboolean(L, self.get<bool>());
|
||||
}
|
||||
|
||||
result<Variant, LuaCastError> Bool_FromLuaValue(lua_State* L, int idx) {
|
||||
if (!lua_isboolean(L, idx))
|
||||
return LuaCastError(lua_typename(L, idx), "boolean");
|
||||
return Variant(lua_toboolean(L, idx));
|
||||
}
|
||||
|
||||
const TypeDesc BOOL_TYPE {
|
||||
"bool",
|
||||
Bool_Serialize,
|
||||
Bool_Deserialize,
|
||||
Bool_ToString,
|
||||
Bool_FromString,
|
||||
Bool_PushLuaValue,
|
||||
Bool_FromLuaValue,
|
||||
};
|
||||
|
||||
// /bool
|
||||
|
||||
// int
|
||||
|
||||
void Int_Serialize(Variant self, pugi::xml_node node) {
|
||||
node.text().set(self.get<int>());
|
||||
}
|
||||
|
||||
result<Variant, DataParseError> Int_Deserialize(pugi::xml_node node, const TypeMeta) {
|
||||
return node.text().as_int();
|
||||
}
|
||||
|
||||
const std::string Int_ToString(Variant self) {
|
||||
return std::to_string(self.get<int>());
|
||||
}
|
||||
|
||||
result<Variant, DataParseError> Int_FromString(std::string string, const TypeMeta) {
|
||||
char* endPos;
|
||||
int value = (int)std::strtol(string.c_str(), &endPos, 10);
|
||||
if (endPos == string.c_str()) return DataParseError(string, "int");
|
||||
return value;
|
||||
}
|
||||
|
||||
void Int_PushLuaValue(Variant self, lua_State* L) {
|
||||
lua_pushinteger(L, self.get<int>());
|
||||
}
|
||||
|
||||
result<Variant, LuaCastError> Int_FromLuaValue(lua_State* L, int idx) {
|
||||
if (!lua_isnumber(L, idx))
|
||||
return LuaCastError(lua_typename(L, idx), "integer");
|
||||
return Variant((int)lua_tonumber(L, idx));
|
||||
}
|
||||
|
||||
const TypeDesc INT_TYPE {
|
||||
"int",
|
||||
Int_Serialize,
|
||||
Int_Deserialize,
|
||||
Int_ToString,
|
||||
Int_FromString,
|
||||
Int_PushLuaValue,
|
||||
Int_FromLuaValue,
|
||||
};
|
||||
|
||||
// /int
|
||||
|
||||
// float
|
||||
|
||||
void Float_Serialize(Variant self, pugi::xml_node node) {
|
||||
node.text().set(self.get<float>());
|
||||
}
|
||||
|
||||
result<Variant, DataParseError> Float_Deserialize(pugi::xml_node node, const TypeMeta) {
|
||||
return node.text().as_float();
|
||||
}
|
||||
|
||||
const std::string Float_ToString(Variant self) {
|
||||
std::stringstream stream;
|
||||
stream << std::noshowpoint << self.get<float>();
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
result<Variant, DataParseError> Float_FromString(std::string string, const TypeMeta) {
|
||||
char* endPos;
|
||||
float value = std::strtof(string.c_str(), &endPos);
|
||||
if (endPos == string.c_str()) return DataParseError(string, "float");
|
||||
return value;
|
||||
}
|
||||
|
||||
void Float_PushLuaValue(Variant self, lua_State* L) {
|
||||
lua_pushnumber(L, self.get<float>());
|
||||
}
|
||||
|
||||
result<Variant, LuaCastError> Float_FromLuaValue(lua_State* L, int idx) {
|
||||
if (!lua_isnumber(L, idx))
|
||||
return LuaCastError(lua_typename(L, idx), "float");
|
||||
return Variant((float)lua_tonumber(L, idx));
|
||||
}
|
||||
|
||||
const TypeDesc FLOAT_TYPE {
|
||||
"float",
|
||||
Float_Serialize,
|
||||
Float_Deserialize,
|
||||
Float_ToString,
|
||||
Float_FromString,
|
||||
Float_PushLuaValue,
|
||||
Float_FromLuaValue,
|
||||
};
|
||||
|
||||
// /float
|
||||
|
||||
// string
|
||||
|
||||
void String_Serialize(Variant self, pugi::xml_node node) {
|
||||
node.text().set(self.get<std::string>());
|
||||
}
|
||||
|
||||
result<Variant, DataParseError> String_Deserialize(pugi::xml_node node, const TypeMeta) {
|
||||
return node.text().as_string();
|
||||
}
|
||||
|
||||
const std::string String_ToString(Variant self) {
|
||||
return self.get<std::string>();
|
||||
}
|
||||
|
||||
result<Variant, DataParseError> String_FromString(std::string string, const TypeMeta) {
|
||||
return string;
|
||||
}
|
||||
|
||||
void String_PushLuaValue(Variant self, lua_State* L) {
|
||||
lua_pushstring(L, self.get<std::string>().c_str());
|
||||
}
|
||||
|
||||
result<Variant, LuaCastError> String_FromLuaValue(lua_State* L, int idx) {
|
||||
if (!lua_tostring(L, idx))
|
||||
return LuaCastError(lua_typename(L, idx), "string");
|
||||
return Variant(lua_tostring(L, idx));
|
||||
}
|
||||
|
||||
const TypeDesc STRING_TYPE {
|
||||
"string",
|
||||
String_Serialize,
|
||||
String_Deserialize,
|
||||
String_ToString,
|
||||
String_FromString,
|
||||
String_PushLuaValue,
|
||||
String_FromLuaValue,
|
||||
};
|
||||
|
||||
// /string
|
9
core/src/datatypes/primitives.h
Normal file
9
core/src/datatypes/primitives.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
extern const TypeDesc NULL_TYPE;
|
||||
extern const TypeDesc BOOL_TYPE;
|
||||
extern const TypeDesc INT_TYPE;
|
||||
extern const TypeDesc FLOAT_TYPE;
|
||||
extern const TypeDesc STRING_TYPE;
|
|
@ -2,7 +2,7 @@
|
|||
#include "datatypes/base.h"
|
||||
#include "error/data.h"
|
||||
#include "logger.h"
|
||||
#include "meta.h" // IWYU pragma: keep
|
||||
#include "variant.h" // IWYU pragma: keep
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include "objects/base/instance.h"
|
||||
|
@ -10,47 +10,55 @@
|
|||
#include "objects/base/member.h"
|
||||
#include <pugixml.hpp>
|
||||
|
||||
Data::InstanceRef::InstanceRef() {};
|
||||
Data::InstanceRef::InstanceRef(std::weak_ptr<Instance> instance) : ref(instance) {};
|
||||
Data::InstanceRef::~InstanceRef() = default;
|
||||
TypeMeta::TypeMeta(const InstanceType* instType) : descriptor(&InstanceRef::TYPE), instType(instType) {}
|
||||
|
||||
const Data::TypeInfo Data::InstanceRef::TYPE = {
|
||||
InstanceRef::InstanceRef() {};
|
||||
InstanceRef::InstanceRef(std::weak_ptr<Instance> instance) : ref(instance) {};
|
||||
InstanceRef::~InstanceRef() = default;
|
||||
|
||||
const TypeDesc InstanceRef::TYPE = {
|
||||
.name = "Ref",
|
||||
.deserializer = &Data::InstanceRef::Deserialize,
|
||||
.fromLuaValue = &Data::InstanceRef::FromLuaValue,
|
||||
.serialize = toVariantFunction(&InstanceRef::Serialize),
|
||||
.deserialize = toVariantGeneratorNoMeta(&InstanceRef::Deserialize),
|
||||
.toString = toVariantFunction(&InstanceRef::ToString),
|
||||
.fromString = nullptr,
|
||||
.pushLuaValue = toVariantFunction(&InstanceRef::PushLuaValue),
|
||||
.fromLuaValue = &InstanceRef::FromLuaValue,
|
||||
};
|
||||
|
||||
const Data::TypeInfo& Data::InstanceRef::GetType() const { return Data::InstanceRef::TYPE; };
|
||||
|
||||
const Data::String Data::InstanceRef::ToString() const {
|
||||
const std::string InstanceRef::ToString() const {
|
||||
return ref.expired() ? "" : ref.lock()->name;
|
||||
}
|
||||
|
||||
Data::InstanceRef::operator std::weak_ptr<Instance>() {
|
||||
InstanceRef::operator std::weak_ptr<Instance>() {
|
||||
return ref;
|
||||
}
|
||||
|
||||
// Serialization
|
||||
|
||||
void Data::InstanceRef::Serialize(pugi::xml_node node) const {
|
||||
// node.text().set(this->ToHex());
|
||||
void InstanceRef::Serialize(pugi::xml_node node) const {
|
||||
// Handled by Instance
|
||||
panic();
|
||||
}
|
||||
|
||||
Data::Variant Data::InstanceRef::Deserialize(pugi::xml_node node) {
|
||||
return Data::InstanceRef();
|
||||
result<InstanceRef, DataParseError> InstanceRef::Deserialize(pugi::xml_node node) {
|
||||
// Handled by Instance
|
||||
panic();
|
||||
}
|
||||
|
||||
static int inst_gc(lua_State*);
|
||||
static int inst_index(lua_State*);
|
||||
static int inst_newindex(lua_State*);
|
||||
static int inst_tostring(lua_State*);
|
||||
static const struct luaL_Reg metatable [] = {
|
||||
{"__gc", inst_gc},
|
||||
{"__index", inst_index},
|
||||
{"__newindex", inst_newindex},
|
||||
{"__tostring", inst_tostring},
|
||||
{NULL, NULL} /* end of array */
|
||||
};
|
||||
|
||||
void Data::InstanceRef::PushLuaValue(lua_State* L) const {
|
||||
void InstanceRef::PushLuaValue(lua_State* L) const {
|
||||
if (ref.expired()) return lua_pushnil(L);
|
||||
|
||||
int n = lua_gettop(L);
|
||||
|
@ -68,14 +76,14 @@ void Data::InstanceRef::PushLuaValue(lua_State* L) const {
|
|||
lua_setmetatable(L, n+1);
|
||||
}
|
||||
|
||||
result<Data::Variant, LuaCastError> Data::InstanceRef::FromLuaValue(lua_State* L, int idx) {
|
||||
result<Variant, LuaCastError> InstanceRef::FromLuaValue(lua_State* L, int idx) {
|
||||
if (lua_isnil(L, idx))
|
||||
return Data::Variant(Data::InstanceRef());
|
||||
return Variant(InstanceRef());
|
||||
if (!lua_isuserdata(L, idx))
|
||||
return LuaCastError(lua_typename(L, idx), "Instance");
|
||||
// TODO: overhaul this to support other types
|
||||
auto userdata = (std::shared_ptr<Instance>**)lua_touserdata(L, idx);
|
||||
return Data::Variant(Data::InstanceRef(**userdata));
|
||||
return Variant(InstanceRef(**userdata));
|
||||
}
|
||||
|
||||
static int inst_gc(lua_State* L) {
|
||||
|
@ -97,7 +105,7 @@ static int inst_index(lua_State* L) {
|
|||
// Read property
|
||||
std::optional<PropertyMeta> meta = inst->GetPropertyMeta(key);
|
||||
if (meta) {
|
||||
Data::Variant value = inst->GetPropertyValue(key).expect();
|
||||
Variant value = inst->GetPropertyValue(key).expect();
|
||||
value.PushLuaValue(L);
|
||||
return 1;
|
||||
}
|
||||
|
@ -105,7 +113,7 @@ static int inst_index(lua_State* L) {
|
|||
// Look for child
|
||||
std::optional<std::shared_ptr<Instance>> child = inst->FindFirstChild(key);
|
||||
if (child) {
|
||||
Data::InstanceRef(child.value()).PushLuaValue(L);
|
||||
InstanceRef(child.value()).PushLuaValue(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -127,11 +135,21 @@ static int inst_newindex(lua_State* L) {
|
|||
if (key == "Parent" && inst->IsParentLocked())
|
||||
return luaL_error(L, "Cannot set property Parent (%s) of %s, parent is locked", inst->GetParent() ? inst->GetParent().value()->name.c_str() : "NULL", inst->GetClass()->className.c_str());
|
||||
|
||||
result<Data::Variant, LuaCastError> value = meta->type->fromLuaValue(L, -1);
|
||||
// TODO: Make this work for enums, this is not a solution!!
|
||||
result<Variant, LuaCastError> value = meta->type.descriptor->fromLuaValue(L, -1);
|
||||
lua_pop(L, 3);
|
||||
|
||||
if (value.isError())
|
||||
return luaL_error(L, "%s", value.errorMessage().value().c_str());
|
||||
inst->SetPropertyValue(key, value.expect()).expect();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int inst_tostring(lua_State* L) {
|
||||
auto userdata = (std::shared_ptr<Instance>**)lua_touserdata(L, 1);
|
||||
std::shared_ptr<Instance> inst = **userdata;
|
||||
|
||||
lua_pushstring(L, inst->name.c_str());
|
||||
|
||||
return 1;
|
||||
}
|
|
@ -6,23 +6,20 @@
|
|||
|
||||
class Instance;
|
||||
|
||||
namespace Data {
|
||||
class InstanceRef : public Base {
|
||||
std::weak_ptr<Instance> ref;
|
||||
public:
|
||||
InstanceRef();
|
||||
InstanceRef(std::weak_ptr<Instance>);
|
||||
~InstanceRef();
|
||||
class InstanceRef {
|
||||
std::weak_ptr<Instance> ref;
|
||||
public:
|
||||
InstanceRef();
|
||||
InstanceRef(std::weak_ptr<Instance>);
|
||||
virtual ~InstanceRef();
|
||||
|
||||
virtual const TypeInfo& GetType() const override;
|
||||
static const TypeInfo TYPE;
|
||||
static const TypeDesc TYPE;
|
||||
|
||||
operator std::weak_ptr<Instance>();
|
||||
operator std::weak_ptr<Instance>();
|
||||
|
||||
virtual const Data::String ToString() const override;
|
||||
virtual void Serialize(pugi::xml_node node) const override;
|
||||
virtual void PushLuaValue(lua_State*) const override;
|
||||
static Data::Variant Deserialize(pugi::xml_node node);
|
||||
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
|
||||
};
|
||||
}
|
||||
virtual const std::string ToString() const;
|
||||
virtual void Serialize(pugi::xml_node node) const;
|
||||
virtual void PushLuaValue(lua_State*) const;
|
||||
static result<InstanceRef, DataParseError> Deserialize(pugi::xml_node node);
|
||||
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
|
||||
};
|
|
@ -1,7 +1,8 @@
|
|||
#include "signal.h"
|
||||
#include "datatypes/base.h"
|
||||
#include "meta.h"
|
||||
#include "variant.h"
|
||||
#include "lua.h"
|
||||
#include <cstdio>
|
||||
#include <pugixml.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
@ -21,24 +22,44 @@ LuaSignalConnection::LuaSignalConnection(lua_State* L, std::weak_ptr<Signal> par
|
|||
|
||||
// https://stackoverflow.com/a/31952046/16255372
|
||||
|
||||
// Save function so it doesn't get GC'd
|
||||
// Save function and current thread so they don't get GC'd
|
||||
function = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
lua_pop(L, 1);
|
||||
lua_pushthread(L);
|
||||
thread = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
|
||||
LuaSignalConnection::~LuaSignalConnection() {
|
||||
// Remove LuaSignalConnectionthread so that it can get properly GC'd
|
||||
luaL_unref(state, LUA_REGISTRYINDEX, function);
|
||||
luaL_unref(state, LUA_REGISTRYINDEX, thread);
|
||||
}
|
||||
|
||||
void LuaSignalConnection::Call(std::vector<Data::Variant> args) {
|
||||
#if 0
|
||||
static void stackdump(lua_State* L) {
|
||||
printf("%d\n", lua_gettop(L));
|
||||
fflush(stdout);
|
||||
lua_getfield(L, LUA_GLOBALSINDEX, "tostring");
|
||||
for (int i = lua_gettop(L)-1; i >= 1; i--) {
|
||||
lua_pushvalue(L, -1);
|
||||
lua_pushvalue(L, i);
|
||||
lua_call(L, 1, 1);
|
||||
const char* str = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
printf("%s: %s\n", lua_typename(L, lua_type(L, i)), str);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
printf("\n\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
|
||||
void LuaSignalConnection::Call(std::vector<Variant> args) {
|
||||
lua_State* thread = lua_newthread(state);
|
||||
|
||||
// Push function
|
||||
lua_rawgeti(thread, LUA_REGISTRYINDEX, function);
|
||||
luaL_unref(thread, LUA_REGISTRYINDEX, function);
|
||||
|
||||
for (Data::Variant arg : args) {
|
||||
for (Variant arg : args) {
|
||||
arg.PushLuaValue(thread);
|
||||
}
|
||||
|
||||
|
@ -47,15 +68,17 @@ void LuaSignalConnection::Call(std::vector<Data::Variant> args) {
|
|||
Logger::error(lua_tostring(thread, -1));
|
||||
lua_pop(thread, 1); // Pop return value
|
||||
}
|
||||
|
||||
lua_pop(state, 1); // Pop thread
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
CSignalConnection::CSignalConnection(std::function<void(std::vector<Data::Variant>)> func, std::weak_ptr<Signal> parent) : SignalConnection(parent) {
|
||||
CSignalConnection::CSignalConnection(std::function<void(std::vector<Variant>)> func, std::weak_ptr<Signal> parent) : SignalConnection(parent) {
|
||||
this->function = func;
|
||||
}
|
||||
|
||||
void CSignalConnection::Call(std::vector<Data::Variant> args) {
|
||||
void CSignalConnection::Call(std::vector<Variant> args) {
|
||||
function(args);
|
||||
}
|
||||
|
||||
|
@ -63,7 +86,7 @@ void CSignalConnection::Call(std::vector<Data::Variant> args) {
|
|||
|
||||
SignalConnectionHolder::SignalConnectionHolder() : heldConnection() {}
|
||||
SignalConnectionHolder::SignalConnectionHolder(std::shared_ptr<SignalConnection> connection) : heldConnection(connection) {}
|
||||
SignalConnectionHolder::SignalConnectionHolder(Data::SignalConnectionRef other) : heldConnection(other) {}
|
||||
SignalConnectionHolder::SignalConnectionHolder(SignalConnectionRef other) : heldConnection(other) {}
|
||||
|
||||
SignalConnectionHolder::~SignalConnectionHolder() {
|
||||
// printf("Prediscon!\n");
|
||||
|
@ -74,7 +97,7 @@ SignalConnectionHolder::~SignalConnectionHolder() {
|
|||
|
||||
//
|
||||
|
||||
SignalConnectionRef Signal::Connect(std::function<void(std::vector<Data::Variant>)> callback) {
|
||||
SignalConnectionRef Signal::Connect(std::function<void(std::vector<Variant>)> callback) {
|
||||
auto conn = std::dynamic_pointer_cast<SignalConnection>(std::make_shared<CSignalConnection>(callback, weak_from_this()));
|
||||
connections.push_back(conn);
|
||||
return SignalConnectionRef(conn);
|
||||
|
@ -86,7 +109,7 @@ SignalConnectionRef Signal::Connect(lua_State* state) {
|
|||
return SignalConnectionRef(conn);
|
||||
}
|
||||
|
||||
SignalConnectionRef Signal::Once(std::function<void(std::vector<Data::Variant>)> callback) {
|
||||
SignalConnectionRef Signal::Once(std::function<void(std::vector<Variant>)> callback) {
|
||||
auto conn = std::dynamic_pointer_cast<SignalConnection>(std::make_shared<CSignalConnection>(callback, weak_from_this()));
|
||||
onceConnections.push_back(conn);
|
||||
return SignalConnectionRef(conn);
|
||||
|
@ -109,7 +132,7 @@ int Signal::Wait(lua_State* thread) {
|
|||
return lua_yield(thread, 0);
|
||||
}
|
||||
|
||||
void Signal::Fire(std::vector<Data::Variant> args) {
|
||||
void Signal::Fire(std::vector<Variant> args) {
|
||||
for (std::shared_ptr<SignalConnection> connection : connections) {
|
||||
connection->Call(args);
|
||||
}
|
||||
|
@ -125,7 +148,7 @@ void Signal::Fire(std::vector<Data::Variant> args) {
|
|||
auto prevThreads = std::move(waitingThreads);
|
||||
waitingThreads = std::vector<std::pair<int, lua_State*>>();
|
||||
for (auto& [threadId, thread] : prevThreads) {
|
||||
for (Data::Variant arg : args) {
|
||||
for (Variant arg : args) {
|
||||
arg.PushLuaValue(thread);
|
||||
}
|
||||
|
||||
|
@ -142,7 +165,7 @@ void Signal::Fire(std::vector<Data::Variant> args) {
|
|||
}
|
||||
|
||||
void Signal::Fire() {
|
||||
return Fire(std::vector<Data::Variant> {});
|
||||
return Fire(std::vector<Variant> {});
|
||||
}
|
||||
|
||||
void Signal::DisconnectAll() {
|
||||
|
@ -199,29 +222,32 @@ static const struct luaL_Reg signal_metatable [] = {
|
|||
{NULL, NULL} /* end of array */
|
||||
};
|
||||
|
||||
Data::SignalRef::SignalRef(std::weak_ptr<Signal> ref) : signal(ref) {}
|
||||
Data::SignalRef::~SignalRef() = default;
|
||||
SignalRef::SignalRef(std::weak_ptr<Signal> ref) : signal(ref) {}
|
||||
SignalRef::~SignalRef() = default;
|
||||
|
||||
const Data::TypeInfo Data::SignalRef::TYPE = {
|
||||
const TypeDesc SignalRef::TYPE = {
|
||||
.name = "Signal",
|
||||
.fromLuaValue = &Data::SignalRef::FromLuaValue,
|
||||
.serialize = nullptr,
|
||||
.deserialize = nullptr,
|
||||
.toString = toVariantFunction(&SignalRef::ToString),
|
||||
.fromString = nullptr,
|
||||
.pushLuaValue = toVariantFunction(&SignalRef::PushLuaValue),
|
||||
.fromLuaValue = &SignalRef::FromLuaValue,
|
||||
};
|
||||
|
||||
const Data::TypeInfo& Data::SignalRef::GetType() const { return Data::SignalRef::TYPE; };
|
||||
|
||||
const Data::String Data::SignalRef::ToString() const {
|
||||
return Data::String("Signal");
|
||||
const std::string SignalRef::ToString() const {
|
||||
return "Signal";
|
||||
}
|
||||
|
||||
Data::SignalRef::operator std::weak_ptr<Signal>() {
|
||||
SignalRef::operator std::weak_ptr<Signal>() {
|
||||
return signal;
|
||||
}
|
||||
|
||||
void Data::SignalRef::Serialize(pugi::xml_node node) const {
|
||||
void SignalRef::Serialize(pugi::xml_node node) const {
|
||||
// Not serializable
|
||||
}
|
||||
|
||||
void Data::SignalRef::PushLuaValue(lua_State* L) const {
|
||||
void SignalRef::PushLuaValue(lua_State* L) const {
|
||||
int n = lua_gettop(L);
|
||||
|
||||
auto userdata = (std::weak_ptr<Signal>**)lua_newuserdata(L, sizeof(std::weak_ptr<Signal>));
|
||||
|
@ -234,10 +260,10 @@ void Data::SignalRef::PushLuaValue(lua_State* L) const {
|
|||
lua_setmetatable(L, n+1);
|
||||
}
|
||||
|
||||
result<Data::Variant, LuaCastError> Data::SignalRef::FromLuaValue(lua_State* L, int idx) {
|
||||
result<Variant, LuaCastError> SignalRef::FromLuaValue(lua_State* L, int idx) {
|
||||
auto userdata = (std::weak_ptr<Signal>**)luaL_checkudata(L, 1, "__mt_signal");
|
||||
lua_pop(L, 1);
|
||||
return Data::Variant(Data::SignalRef(**userdata));
|
||||
return Variant(SignalRef(**userdata));
|
||||
}
|
||||
|
||||
static int signal_gc(lua_State* L) {
|
||||
|
@ -320,29 +346,32 @@ static const struct luaL_Reg signalconnection_metatable [] = {
|
|||
{NULL, NULL} /* end of array */
|
||||
};
|
||||
|
||||
Data::SignalConnectionRef::SignalConnectionRef(std::weak_ptr<SignalConnection> ref) : signalConnection(ref) {}
|
||||
Data::SignalConnectionRef::~SignalConnectionRef() = default;
|
||||
SignalConnectionRef::SignalConnectionRef(std::weak_ptr<SignalConnection> ref) : signalConnection(ref) {}
|
||||
SignalConnectionRef::~SignalConnectionRef() = default;
|
||||
|
||||
const Data::TypeInfo Data::SignalConnectionRef::TYPE = {
|
||||
const TypeDesc SignalConnectionRef::TYPE = {
|
||||
.name = "Signal",
|
||||
.fromLuaValue = &Data::SignalConnectionRef::FromLuaValue,
|
||||
.serialize = nullptr,
|
||||
.deserialize = nullptr,
|
||||
.toString = toVariantFunction(&SignalConnectionRef::ToString),
|
||||
.fromString = nullptr,
|
||||
.pushLuaValue = toVariantFunction(&SignalConnectionRef::PushLuaValue),
|
||||
.fromLuaValue = &SignalConnectionRef::FromLuaValue,
|
||||
};
|
||||
|
||||
const Data::TypeInfo& Data::SignalConnectionRef::GetType() const { return Data::SignalConnectionRef::TYPE; };
|
||||
|
||||
const Data::String Data::SignalConnectionRef::ToString() const {
|
||||
return Data::String("Connection");
|
||||
const std::string SignalConnectionRef::ToString() const {
|
||||
return "Connection";
|
||||
}
|
||||
|
||||
Data::SignalConnectionRef::operator std::weak_ptr<SignalConnection>() {
|
||||
SignalConnectionRef::operator std::weak_ptr<SignalConnection>() {
|
||||
return signalConnection;
|
||||
}
|
||||
|
||||
void Data::SignalConnectionRef::Serialize(pugi::xml_node node) const {
|
||||
void SignalConnectionRef::Serialize(pugi::xml_node node) const {
|
||||
// Not serializable
|
||||
}
|
||||
|
||||
void Data::SignalConnectionRef::PushLuaValue(lua_State* L) const {
|
||||
void SignalConnectionRef::PushLuaValue(lua_State* L) const {
|
||||
int n = lua_gettop(L);
|
||||
|
||||
auto userdata = (std::weak_ptr<SignalConnection>**)lua_newuserdata(L, sizeof(std::weak_ptr<SignalConnection>));
|
||||
|
@ -355,10 +384,10 @@ void Data::SignalConnectionRef::PushLuaValue(lua_State* L) const {
|
|||
lua_setmetatable(L, n+1);
|
||||
}
|
||||
|
||||
result<Data::Variant, LuaCastError> Data::SignalConnectionRef::FromLuaValue(lua_State* L, int idx) {
|
||||
result<Variant, LuaCastError> SignalConnectionRef::FromLuaValue(lua_State* L, int idx) {
|
||||
auto userdata = (std::weak_ptr<SignalConnection>**)luaL_checkudata(L, 1, "__mt_signalconnection");
|
||||
lua_pop(L, 1);
|
||||
return Data::Variant(Data::SignalConnectionRef(**userdata));
|
||||
return Variant(SignalConnectionRef(**userdata));
|
||||
}
|
||||
|
||||
static int signalconnection_tostring(lua_State* L) {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
class Instance;
|
||||
class Signal;
|
||||
|
||||
namespace Data { class SignalConnectionRef; }
|
||||
class SignalConnectionRef;
|
||||
|
||||
class SignalConnection : public std::enable_shared_from_this<SignalConnection> {
|
||||
protected:
|
||||
|
@ -23,7 +23,7 @@ protected:
|
|||
|
||||
SignalConnection(std::weak_ptr<Signal> parent);
|
||||
|
||||
virtual void Call(std::vector<Data::Variant>) = 0;
|
||||
virtual void Call(std::vector<Variant>) = 0;
|
||||
friend Signal;
|
||||
public:
|
||||
inline bool Connected() { return !parentSignal.expired(); };
|
||||
|
@ -33,27 +33,28 @@ public:
|
|||
};
|
||||
|
||||
class CSignalConnection : public SignalConnection {
|
||||
std::function<void(std::vector<Data::Variant>)> function;
|
||||
std::function<void(std::vector<Variant>)> function;
|
||||
|
||||
friend Signal;
|
||||
protected:
|
||||
void Call(std::vector<Data::Variant>) override;
|
||||
void Call(std::vector<Variant>) override;
|
||||
public:
|
||||
CSignalConnection(std::function<void(std::vector<Data::Variant>)>, std::weak_ptr<Signal> parent);
|
||||
CSignalConnection(std::function<void(std::vector<Variant>)>, std::weak_ptr<Signal> parent);
|
||||
virtual ~CSignalConnection() = default;
|
||||
};
|
||||
|
||||
class LuaSignalConnection : public SignalConnection {
|
||||
lua_State* state;
|
||||
int function;
|
||||
int function, thread;
|
||||
|
||||
friend Signal;
|
||||
protected:
|
||||
void Call(std::vector<Data::Variant>) override;
|
||||
void Call(std::vector<Variant>) override;
|
||||
public:
|
||||
LuaSignalConnection(lua_State*, std::weak_ptr<Signal> parent);
|
||||
LuaSignalConnection (const LuaSignalConnection&) = delete;
|
||||
LuaSignalConnection& operator= (const LuaSignalConnection&) = delete;
|
||||
~LuaSignalConnection();
|
||||
virtual ~LuaSignalConnection();
|
||||
};
|
||||
|
||||
// Holds a signal connection such that when the holder is deleted (either via its parent object being deleted, or being overwritten),
|
||||
|
@ -63,7 +64,7 @@ class SignalConnectionHolder {
|
|||
public:
|
||||
SignalConnectionHolder();
|
||||
SignalConnectionHolder(std::shared_ptr<SignalConnection>);
|
||||
SignalConnectionHolder(Data::SignalConnectionRef other);
|
||||
SignalConnectionHolder(SignalConnectionRef other);
|
||||
~SignalConnectionHolder();
|
||||
|
||||
// Prevent SignalConnectionHolder being accidentally copied, making it useless
|
||||
|
@ -90,12 +91,12 @@ public:
|
|||
Signal& operator= (const Signal&) = delete;
|
||||
|
||||
void DisconnectAll();
|
||||
void Fire(std::vector<Data::Variant> args);
|
||||
void Fire(std::vector<Variant> args);
|
||||
void Fire();
|
||||
Data::SignalConnectionRef Connect(std::function<void(std::vector<Data::Variant>)> callback);
|
||||
Data::SignalConnectionRef Connect(lua_State*);
|
||||
Data::SignalConnectionRef Once(std::function<void(std::vector<Data::Variant>)> callback);
|
||||
Data::SignalConnectionRef Once(lua_State*);
|
||||
SignalConnectionRef Connect(std::function<void(std::vector<Variant>)> callback);
|
||||
SignalConnectionRef Connect(lua_State*);
|
||||
SignalConnectionRef Once(std::function<void(std::vector<Variant>)> callback);
|
||||
SignalConnectionRef Once(lua_State*);
|
||||
int Wait(lua_State*);
|
||||
};
|
||||
|
||||
|
@ -105,43 +106,36 @@ public:
|
|||
virtual ~SignalSource();
|
||||
};
|
||||
|
||||
namespace Data {
|
||||
class SignalRef : public Data::Base {
|
||||
std::weak_ptr<Signal> signal;
|
||||
class SignalRef {
|
||||
std::weak_ptr<Signal> signal;
|
||||
|
||||
public:
|
||||
SignalRef(std::weak_ptr<Signal>);
|
||||
~SignalRef();
|
||||
public:
|
||||
SignalRef(std::weak_ptr<Signal>);
|
||||
virtual ~SignalRef();
|
||||
|
||||
virtual const TypeInfo& GetType() const override;
|
||||
static const TypeInfo TYPE;
|
||||
static const TypeDesc TYPE;
|
||||
|
||||
operator std::weak_ptr<Signal>();
|
||||
operator std::weak_ptr<Signal>();
|
||||
|
||||
virtual const Data::String ToString() const override;
|
||||
virtual void Serialize(pugi::xml_node node) const override;
|
||||
virtual void PushLuaValue(lua_State*) const override;
|
||||
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
|
||||
};
|
||||
virtual const std::string ToString() const;
|
||||
virtual void Serialize(pugi::xml_node node) const;
|
||||
virtual void PushLuaValue(lua_State*) const;
|
||||
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
|
||||
};
|
||||
|
||||
class SignalConnectionRef : public Data::Base {
|
||||
std::weak_ptr<SignalConnection> signalConnection;
|
||||
class SignalConnectionRef {
|
||||
std::weak_ptr<SignalConnection> signalConnection;
|
||||
|
||||
public:
|
||||
SignalConnectionRef(std::weak_ptr<SignalConnection>);
|
||||
~SignalConnectionRef();
|
||||
public:
|
||||
SignalConnectionRef(std::weak_ptr<SignalConnection>);
|
||||
virtual ~SignalConnectionRef();
|
||||
|
||||
virtual const TypeInfo& GetType() const override;
|
||||
static const TypeInfo TYPE;
|
||||
static const TypeDesc TYPE;
|
||||
|
||||
operator std::weak_ptr<SignalConnection>();
|
||||
operator std::weak_ptr<SignalConnection>();
|
||||
|
||||
virtual const Data::String ToString() const override;
|
||||
virtual void Serialize(pugi::xml_node node) const override;
|
||||
virtual void PushLuaValue(lua_State*) const override;
|
||||
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
|
||||
};
|
||||
}
|
||||
|
||||
using Data::SignalRef;
|
||||
using Data::SignalConnectionRef;
|
||||
virtual const std::string ToString() const;
|
||||
virtual void Serialize(pugi::xml_node node) const;
|
||||
virtual void PushLuaValue(lua_State*) const;
|
||||
static result<Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
|
||||
};
|
88
core/src/datatypes/variant.cpp
Normal file
88
core/src/datatypes/variant.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
#include "variant.h"
|
||||
#include "datatypes/base.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/enum.h"
|
||||
#include "datatypes/primitives.h"
|
||||
#include "datatypes/ref.h"
|
||||
#include "datatypes/signal.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "error/data.h"
|
||||
#include "logger.h"
|
||||
#include "panic.h"
|
||||
#include <pugixml.hpp>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
[[noreturn]] inline void unreachable() {
|
||||
#if defined(_MSC_VER) && !defined(__clang__) // MSVC
|
||||
__assume(false);
|
||||
#else // GCC, Clang
|
||||
__builtin_unreachable();
|
||||
#endif
|
||||
}
|
||||
|
||||
const TypeDesc* VARIANT_TYPES[] {
|
||||
&NULL_TYPE,
|
||||
&BOOL_TYPE,
|
||||
&INT_TYPE,
|
||||
&FLOAT_TYPE,
|
||||
&STRING_TYPE,
|
||||
&Vector3::TYPE,
|
||||
&CFrame::TYPE,
|
||||
&Color3::TYPE,
|
||||
&InstanceRef::TYPE,
|
||||
&SignalRef::TYPE,
|
||||
&SignalConnectionRef::TYPE,
|
||||
&Enum::TYPE,
|
||||
&EnumItem::TYPE,
|
||||
};
|
||||
|
||||
const TypeMeta Variant::GetTypeMeta() const {
|
||||
return VARIANT_TYPES[wrapped.index()];
|
||||
}
|
||||
|
||||
const TypeDesc* Variant::GetType() const {
|
||||
return VARIANT_TYPES[wrapped.index()];
|
||||
}
|
||||
|
||||
std::string Variant::ToString() const {
|
||||
if (!VARIANT_TYPES[wrapped.index()]->pushLuaValue) {
|
||||
Logger::fatalErrorf("Data type %s does not implement toString", VARIANT_TYPES[wrapped.index()]->name.c_str());
|
||||
}
|
||||
return VARIANT_TYPES[wrapped.index()]->toString(*this);
|
||||
}
|
||||
|
||||
void Variant::Serialize(pugi::xml_node node) const {
|
||||
if (!VARIANT_TYPES[wrapped.index()]->pushLuaValue) {
|
||||
Logger::fatalErrorf("Data type %s does not implement serializer", VARIANT_TYPES[wrapped.index()]->name.c_str());
|
||||
}
|
||||
VARIANT_TYPES[wrapped.index()]->serialize(*this, node);
|
||||
}
|
||||
|
||||
void Variant::PushLuaValue(lua_State* state) const {
|
||||
if (!VARIANT_TYPES[wrapped.index()]->pushLuaValue) {
|
||||
Logger::fatalErrorf("Data type %s does not implement pushLuaValue", VARIANT_TYPES[wrapped.index()]->name.c_str());
|
||||
}
|
||||
VARIANT_TYPES[wrapped.index()]->pushLuaValue(*this, state);
|
||||
}
|
||||
|
||||
result<Variant, DataParseError> Variant::Deserialize(pugi::xml_node node, const TypeMeta type) {
|
||||
if (!type.descriptor->deserialize) {
|
||||
Logger::fatalErrorf("Data type %s does not implement deserialize", type.descriptor->name.c_str());
|
||||
return DataParseError(node.text().as_string(), type.descriptor->name);
|
||||
}
|
||||
return type.descriptor->deserialize(node, type);
|
||||
}
|
||||
|
||||
std::map<std::string, const TypeDesc*> TYPE_MAP = {
|
||||
{ "null", &NULL_TYPE },
|
||||
{ "bool", &BOOL_TYPE },
|
||||
{ "int", &INT_TYPE },
|
||||
{ "float", &FLOAT_TYPE },
|
||||
{ "string", &STRING_TYPE },
|
||||
{ "Vector3", &Vector3::TYPE },
|
||||
{ "CoordinateFrame", &CFrame::TYPE },
|
||||
{ "Color3", &Color3::TYPE },
|
||||
{ "Ref", &InstanceRef::TYPE },
|
||||
{ "token", &EnumItem::TYPE },
|
||||
};
|
94
core/src/datatypes/variant.h
Normal file
94
core/src/datatypes/variant.h
Normal file
|
@ -0,0 +1,94 @@
|
|||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
#include <map>
|
||||
#include "base.h"
|
||||
#include "datatypes/color3.h"
|
||||
#include "datatypes/enum.h"
|
||||
#include "datatypes/ref.h"
|
||||
#include "datatypes/signal.h"
|
||||
#include "error/data.h"
|
||||
#include "vector.h"
|
||||
#include "cframe.h"
|
||||
|
||||
// #define __VARIANT_TYPE std::variant< \
|
||||
// Null, \
|
||||
// Bool, \
|
||||
// Int, \
|
||||
// Float, \
|
||||
// String \
|
||||
// >
|
||||
|
||||
typedef std::variant<
|
||||
std::monostate,
|
||||
bool,
|
||||
int,
|
||||
float,
|
||||
std::string,
|
||||
Vector3,
|
||||
CFrame,
|
||||
Color3,
|
||||
InstanceRef,
|
||||
SignalRef,
|
||||
SignalConnectionRef,
|
||||
Enum,
|
||||
EnumItem
|
||||
> __VARIANT_TYPE;
|
||||
|
||||
class Variant {
|
||||
__VARIANT_TYPE wrapped;
|
||||
public:
|
||||
template <typename T, std::enable_if_t<std::is_constructible_v<__VARIANT_TYPE, T>, int> = 0> Variant(T obj) : wrapped(obj) {}
|
||||
template <typename T, std::enable_if_t<std::is_constructible_v<__VARIANT_TYPE, T>, int> = 0> T get() { return std::get<T>(wrapped); }
|
||||
std::string ToString() const;
|
||||
|
||||
const TypeMeta GetTypeMeta() const;
|
||||
const TypeDesc* GetType() const;
|
||||
|
||||
void Serialize(pugi::xml_node node) const;
|
||||
void PushLuaValue(lua_State* state) const;
|
||||
static result<Variant, DataParseError> Deserialize(pugi::xml_node node, const TypeMeta);
|
||||
};
|
||||
|
||||
template <typename T, typename R, typename ...Args>
|
||||
std::function<R(Variant, Args...)> toVariantFunction(R(T::*f)(Args...)) {
|
||||
return [f](Variant var, Args... args) {
|
||||
return (var.get<T>().*f)(args...);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T, typename R, typename ...Args>
|
||||
std::function<R(Variant, Args...)> toVariantFunction(R(T::*f)(Args...) const) {
|
||||
return [f](Variant var, Args... args) {
|
||||
return (var.get<T>().*f)(args...);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T, typename ...Args>
|
||||
std::function<Variant(Args...)> toVariantGenerator(T(f)(Args...)) {
|
||||
return [f](Args... args) {
|
||||
return (Variant)f(args...);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T, typename ...Args, typename ...E>
|
||||
std::function<result<Variant, E...>(Args...)> toVariantGenerator(result<T, E...>(f)(Args...)) {
|
||||
return [f](Args... args) -> result<Variant, E...> {
|
||||
auto result = f(args...);
|
||||
if (result.isSuccess()) return (Variant)(result.success().value());
|
||||
return result.error().value();
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T, typename ...Args, typename ...E>
|
||||
std::function<result<Variant, E...>(Args..., const TypeMeta)> toVariantGeneratorNoMeta(result<T, E...>(f)(Args...)) {
|
||||
return [f](Args... args, const TypeMeta) -> result<Variant, E...> {
|
||||
auto result = f(args...);
|
||||
if (result.isSuccess()) return (Variant)(result.success().value());
|
||||
return result.error().value();
|
||||
};
|
||||
}
|
||||
|
||||
// Map of all data types to their type names
|
||||
extern std::map<std::string, const TypeDesc*> TYPE_MAP;
|
|
@ -7,107 +7,112 @@
|
|||
#include <string>
|
||||
#include <pugixml.hpp>
|
||||
#include "datatypes/base.h"
|
||||
#include "datatypes/meta.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "error/data.h"
|
||||
#include <sstream>
|
||||
|
||||
namespace rp = reactphysics3d;
|
||||
|
||||
Data::Vector3::Vector3() : vector(glm::vec3(0, 0, 0)) {};
|
||||
Data::Vector3::Vector3(const glm::vec3& src) : vector(src) {};
|
||||
Data::Vector3::Vector3(const rp::Vector3& src) : vector(glm::vec3(src.x, src.y, src.z)) {};
|
||||
Data::Vector3::Vector3(float x, const float y, float z) : vector(glm::vec3(x, y, z)) {};
|
||||
Vector3::Vector3() : vector(glm::vec3(0, 0, 0)) {};
|
||||
Vector3::Vector3(const glm::vec3& src) : vector(src) {};
|
||||
Vector3::Vector3(const rp::Vector3& src) : vector(glm::vec3(src.x, src.y, src.z)) {};
|
||||
Vector3::Vector3(float x, const float y, float z) : vector(glm::vec3(x, y, z)) {};
|
||||
|
||||
Data::Vector3::~Vector3() = default;
|
||||
Vector3::~Vector3() = default;
|
||||
|
||||
Data::Vector3 Data::Vector3::ZERO(0, 0, 0);
|
||||
Data::Vector3 Data::Vector3::ONE(1, 1, 1);
|
||||
Vector3 Vector3::ZERO(0, 0, 0);
|
||||
Vector3 Vector3::ONE(1, 1, 1);
|
||||
|
||||
const Data::String Data::Vector3::ToString() const {
|
||||
const std::string Vector3::ToString() const {
|
||||
// https://stackoverflow.com/a/46424921/16255372
|
||||
std::stringstream stream;
|
||||
stream << std::setprecision(8) << std::noshowpoint << X() << ", " << Y() << ", " << Z();
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
Data::Vector3::operator glm::vec3() const { return vector; };
|
||||
Data::Vector3::operator rp::Vector3() const { return rp::Vector3(X(), Y(), Z()); };
|
||||
Vector3::operator glm::vec3() const { return vector; };
|
||||
Vector3::operator rp::Vector3() const { return rp::Vector3(X(), Y(), Z()); };
|
||||
|
||||
// Operators
|
||||
|
||||
Data::Vector3 Data::Vector3::operator *(float scale) const {
|
||||
return Data::Vector3(this->X() * scale, this->Y() * scale, this->Z() * scale);
|
||||
Vector3 Vector3::operator *(float scale) const {
|
||||
return 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);
|
||||
Vector3 Vector3::operator /(float scale) const {
|
||||
return Vector3(this->X() / scale, this->Y() / scale, this->Z() / scale);
|
||||
}
|
||||
|
||||
// Component-wise
|
||||
Data::Vector3 Data::Vector3::operator *(Data::Vector3 other) const {
|
||||
return Data::Vector3(this->X() * other.X(), this->Y() * other.Y(), this->Z() * other.Z());
|
||||
Vector3 Vector3::operator *(Vector3 other) const {
|
||||
return Vector3(this->X() * other.X(), this->Y() * other.Y(), this->Z() * other.Z());
|
||||
}
|
||||
|
||||
Data::Vector3 Data::Vector3::operator +(Data::Vector3 other) const {
|
||||
return Data::Vector3(this->X() + other.X(), this->Y() + other.Y(), this->Z() + other.Z());
|
||||
Vector3 Vector3::operator /(Vector3 other) const {
|
||||
return Vector3(this->X() / other.X(), this->Y() / other.Y(), this->Z() / other.Z());
|
||||
}
|
||||
|
||||
Data::Vector3 Data::Vector3::operator -(Data::Vector3 other) const {
|
||||
return Data::Vector3(this->X() - other.X(), this->Y() - other.Y(), this->Z() - other.Z());
|
||||
Vector3 Vector3::operator +(Vector3 other) const {
|
||||
return Vector3(this->X() + other.X(), this->Y() + other.Y(), this->Z() + other.Z());
|
||||
}
|
||||
|
||||
Data::Vector3 Data::Vector3::operator -() const {
|
||||
return Data::Vector3(-this->X(), -this->Y(), -this->Z());
|
||||
Vector3 Vector3::operator -(Vector3 other) const {
|
||||
return Vector3(this->X() - other.X(), this->Y() - other.Y(), this->Z() - other.Z());
|
||||
}
|
||||
|
||||
bool Data::Vector3::operator ==(Data::Vector3 other) const {
|
||||
Vector3 Vector3::operator -() const {
|
||||
return Vector3(-this->X(), -this->Y(), -this->Z());
|
||||
}
|
||||
|
||||
bool Vector3::operator ==(Vector3 other) const {
|
||||
return this->X() == other.X() && this->Y() == other.Y() && this->Z() == other.Z();
|
||||
}
|
||||
|
||||
bool Data::Vector3::operator <(Data::Vector3 other) const {
|
||||
bool Vector3::operator <(Vector3 other) const {
|
||||
return X() < other.X() && Y() < other.Y() && Z() < other.Z();
|
||||
}
|
||||
|
||||
bool Data::Vector3::operator >(Data::Vector3 other) const {
|
||||
bool Vector3::operator >(Vector3 other) const {
|
||||
return X() > other.X() && Y() > other.Y() && Z() > other.Z();
|
||||
}
|
||||
|
||||
Data::Vector3 Data::Vector3::Cross(Data::Vector3 other) const {
|
||||
Vector3 Vector3::Cross(Vector3 other) const {
|
||||
return glm::cross(this->vector, other.vector);
|
||||
}
|
||||
|
||||
float Data::Vector3::Dot(Data::Vector3 other) const {
|
||||
float Vector3::Dot(Vector3 other) const {
|
||||
return glm::dot(this->vector, other.vector);
|
||||
}
|
||||
|
||||
// Serialization
|
||||
|
||||
void Data::Vector3::Serialize(pugi::xml_node node) const {
|
||||
void Vector3::Serialize(pugi::xml_node node) const {
|
||||
node.append_child("X").text().set(std::to_string(this->X()));
|
||||
node.append_child("Y").text().set(std::to_string(this->Y()));
|
||||
node.append_child("Z").text().set(std::to_string(this->Z()));
|
||||
}
|
||||
|
||||
Data::Variant Data::Vector3::Deserialize(pugi::xml_node node) {
|
||||
return Data::Vector3(node.child("X").text().as_float(), node.child("Y").text().as_float(), node.child("Z").text().as_float());
|
||||
result<Vector3, DataParseError> Vector3::Deserialize(pugi::xml_node node) {
|
||||
return Vector3(node.child("X").text().as_float(), node.child("Y").text().as_float(), node.child("Z").text().as_float());
|
||||
}
|
||||
|
||||
std::optional<Data::Variant> Data::Vector3::FromString(std::string string) {
|
||||
result<Vector3, DataParseError> Vector3::FromString(std::string string) {
|
||||
float components[3];
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (string.length() == 0) return std::nullopt;
|
||||
if (string.length() == 0) return DataParseError(string, "Vector3");
|
||||
while (string[0] == ' ' && string.length() > 0) string.erase(0, 1);
|
||||
size_t nextPos = string.find(",");
|
||||
if (nextPos == -1) nextPos = string.length();
|
||||
if (nextPos == std::string::npos) nextPos = string.length();
|
||||
std::string term = string.substr(0, nextPos);
|
||||
string.erase(0, nextPos+1);
|
||||
|
||||
char* cpos;
|
||||
float value = std::strtof(term.c_str(), &cpos);
|
||||
if (cpos == term.c_str()) return std::nullopt;
|
||||
if (cpos == term.c_str()) return DataParseError(string, "Vector3");
|
||||
|
||||
components[i] = value;
|
||||
}
|
||||
|
||||
return Data::Vector3(components[0], components[1], components[2]);
|
||||
return Vector3(components[0], components[1], components[2]);
|
||||
}
|
|
@ -2,64 +2,72 @@
|
|||
|
||||
#include "base.h"
|
||||
#include "datatypes/annotation.h"
|
||||
#include "error/data.h"
|
||||
#include <glm/ext/vector_float3.hpp>
|
||||
#include <glm/geometric.hpp>
|
||||
#include <reactphysics3d/mathematics/Vector3.h>
|
||||
|
||||
namespace reactphysics3d { class Vector3; };
|
||||
// namespace reactphysics3d { class Vector3; };
|
||||
|
||||
namespace Data {
|
||||
class DEF_DATA Vector3 : public Base {
|
||||
AUTOGEN_PREAMBLE_DATA
|
||||
glm::vec3 vector;
|
||||
class DEF_DATA Vector3 {
|
||||
AUTOGEN_PREAMBLE_DATA
|
||||
glm::vec3 vector;
|
||||
|
||||
public:
|
||||
DEF_DATA_CTOR Vector3();
|
||||
DEF_DATA_CTOR Vector3(float x, float y, float z);
|
||||
inline Vector3(float value) : Vector3(value, value, value) {}
|
||||
Vector3(const glm::vec3&);
|
||||
Vector3(const reactphysics3d::Vector3&);
|
||||
virtual ~Vector3();
|
||||
|
||||
DEF_DATA_PROP static Vector3 ZERO;
|
||||
DEF_DATA_PROP static Vector3 ONE;
|
||||
|
||||
virtual const std::string ToString() const;
|
||||
virtual void Serialize(pugi::xml_node node) const;
|
||||
|
||||
static result<Vector3, DataParseError> Deserialize(pugi::xml_node node);
|
||||
static result<Vector3, DataParseError> FromString(std::string);
|
||||
|
||||
public:
|
||||
DEF_DATA_CTOR Vector3();
|
||||
DEF_DATA_CTOR Vector3(float x, float y, float z);
|
||||
Vector3(const glm::vec3&);
|
||||
Vector3(const reactphysics3d::Vector3&);
|
||||
~Vector3();
|
||||
static void PushLuaLibrary(lua_State*);
|
||||
|
||||
DEF_DATA_PROP static Data::Vector3 ZERO;
|
||||
DEF_DATA_PROP static Data::Vector3 ONE;
|
||||
operator glm::vec3() const;
|
||||
operator reactphysics3d::Vector3() const;
|
||||
|
||||
virtual const Data::String ToString() const override;
|
||||
virtual void Serialize(pugi::xml_node node) const override;
|
||||
DEF_DATA_PROP inline float X() const { return vector.x; }
|
||||
DEF_DATA_PROP inline float Y() const { return vector.y; }
|
||||
DEF_DATA_PROP inline float Z() const { return vector.z; }
|
||||
DEF_DATA_METHOD inline float Magnitude() const { return glm::length(vector); }
|
||||
DEF_DATA_METHOD inline Vector3 Unit() const { return glm::normalize(vector); }
|
||||
DEF_DATA_METHOD inline Vector3 Abs() const { return glm::abs(vector); }
|
||||
|
||||
static Data::Variant Deserialize(pugi::xml_node node);
|
||||
static std::optional<Data::Variant> FromString(std::string);
|
||||
|
||||
static void PushLuaLibrary(lua_State*);
|
||||
DEF_DATA_METHOD Vector3 Cross(Vector3) const;
|
||||
DEF_DATA_METHOD float Dot(Vector3) const;
|
||||
|
||||
operator glm::vec3() const;
|
||||
operator reactphysics3d::Vector3() const;
|
||||
// Operators
|
||||
DEF_DATA_OP Vector3 operator *(float) const;
|
||||
DEF_DATA_OP Vector3 operator /(float) const;
|
||||
DEF_DATA_OP Vector3 operator *(Vector3) const; // Component-wise
|
||||
DEF_DATA_OP Vector3 operator /(Vector3) const; // Component-wise
|
||||
DEF_DATA_OP Vector3 operator +(Vector3) const;
|
||||
DEF_DATA_OP Vector3 operator -(Vector3) const;
|
||||
DEF_DATA_OP Vector3 operator -() const;
|
||||
|
||||
DEF_DATA_PROP inline float X() const { return vector.x; }
|
||||
DEF_DATA_PROP inline float Y() const { return vector.y; }
|
||||
DEF_DATA_PROP inline float Z() const { return vector.z; }
|
||||
DEF_DATA_METHOD inline float Magnitude() const { return glm::length(vector); }
|
||||
DEF_DATA_METHOD inline Data::Vector3 Unit() const { return glm::normalize(vector); }
|
||||
DEF_DATA_METHOD inline Data::Vector3 Abs() const { return glm::abs(vector); }
|
||||
DEF_DATA_OP bool operator <(Vector3) const;
|
||||
DEF_DATA_OP bool operator >(Vector3) const;
|
||||
|
||||
DEF_DATA_METHOD Data::Vector3 Cross(Data::Vector3) const;
|
||||
DEF_DATA_METHOD float Dot(Data::Vector3) const;
|
||||
|
||||
// Operators
|
||||
DEF_DATA_OP Data::Vector3 operator *(float) const;
|
||||
DEF_DATA_OP Data::Vector3 operator /(float) const;
|
||||
DEF_DATA_OP Data::Vector3 operator *(Data::Vector3) const; // Component-wise
|
||||
DEF_DATA_OP Data::Vector3 operator +(Data::Vector3) const;
|
||||
DEF_DATA_OP Data::Vector3 operator -(Data::Vector3) const;
|
||||
DEF_DATA_OP Data::Vector3 operator -() const;
|
||||
DEF_DATA_OP bool operator ==(Vector3) const;
|
||||
|
||||
DEF_DATA_OP bool operator <(Data::Vector3) const;
|
||||
DEF_DATA_OP bool operator >(Data::Vector3) const;
|
||||
// Augmented shorthands
|
||||
inline Vector3 operator *=(float factor) const { return *this * factor; }
|
||||
inline Vector3 operator /=(float factor) const { return *this / factor; }
|
||||
inline Vector3 operator *=(Vector3 factor) const { return *this * factor; }
|
||||
inline Vector3 operator /=(Vector3 factor) const { return *this / factor; }
|
||||
inline Vector3 operator +=(Vector3 vector) const { return *this + vector; }
|
||||
inline Vector3 operator -=(Vector3 vector) const { return *this + vector; }
|
||||
};
|
||||
|
||||
DEF_DATA_OP bool operator ==(Data::Vector3) const;
|
||||
};
|
||||
}
|
||||
|
||||
using Data::Vector3;
|
||||
|
||||
inline void printVec(Data::Vector3 vec) {
|
||||
inline void printVec(Vector3 vec) {
|
||||
printf("(%f, %f, %f)\n", vec.X(), vec.Y(), vec.Z());
|
||||
}
|
13
core/src/enum/annotation.h
Normal file
13
core/src/enum/annotation.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
// Markers for the autogen engine to generate getters, setters, lua, etc.
|
||||
|
||||
// Base macros
|
||||
#ifdef __AUTOGEN__
|
||||
#define def_enum(...) clang::annotate("OB::def_enum", #__VA_ARGS__)
|
||||
#else
|
||||
#define def_enum(...)
|
||||
#endif
|
||||
|
||||
// Helper macros
|
||||
#define DEF_ENUM [[ def_enum() ]]
|
3
core/src/enum/meta.h
Normal file
3
core/src/enum/meta.h
Normal file
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
#include "surface.h"
|
33
core/src/enum/surface.h
Normal file
33
core/src/enum/surface.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include "datatypes/enum.h"
|
||||
#include "enum/annotation.h"
|
||||
|
||||
enum DEF_ENUM NormalId {
|
||||
Right = 0,
|
||||
Top = 1,
|
||||
Back = 2,
|
||||
Left = 3,
|
||||
Bottom = 4,
|
||||
Front = 5
|
||||
};
|
||||
|
||||
enum class DEF_ENUM SurfaceType {
|
||||
Smooth = 0,
|
||||
Glue = 1,
|
||||
Weld = 2,
|
||||
Studs = 3,
|
||||
Inlet = 4,
|
||||
Universal = 5,
|
||||
Hinge = 6,
|
||||
Motor = 7,
|
||||
};
|
||||
|
||||
namespace EnumType {
|
||||
extern const Enum NormalId;
|
||||
extern const Enum SurfaceType;
|
||||
};
|
||||
|
||||
class Vector3;
|
||||
NormalId faceFromNormal(Vector3);
|
||||
Vector3 normalFromFace(NormalId);
|
|
@ -4,5 +4,10 @@
|
|||
|
||||
class LuaCastError : public Error {
|
||||
public:
|
||||
inline LuaCastError(std::string sourceType, std::string targetType) : Error("InstanceCastError", "Attempt to cast " + sourceType + " to " + targetType) {}
|
||||
inline LuaCastError(std::string sourceType, std::string targetType) : Error("LuaCastError", "Attempt to cast " + sourceType + " to " + targetType) {}
|
||||
};
|
||||
|
||||
class DataParseError : public Error {
|
||||
public:
|
||||
inline DataParseError(std::string parsedString, std::string targetType) : Error("DataParseError", "Failed to parse '" + parsedString + "' into value of type " + targetType) {}
|
||||
};
|
|
@ -24,6 +24,8 @@ class [[nodiscard]] result {
|
|||
|
||||
std::variant<success_state, error_state> value;
|
||||
public:
|
||||
// Helper for std::variant, etc.
|
||||
template <typename ...T_Args, std::enable_if_t<std::is_constructible_v<T_Result, T_Args...>, int> = 0> result(T_Args... args) : value(success_state { T_Result(args...) }) {}
|
||||
result(T_Result success) : value(success_state { success }) {}
|
||||
result(std::variant<T_Errors...> error) : value(error_state { error }) {}
|
||||
template <typename T_Error, std::enable_if_t<std::disjunction_v<std::is_same<T_Error, T_Errors>...>, int> = 0>
|
||||
|
@ -61,7 +63,7 @@ public:
|
|||
|
||||
// Equivalent to .success
|
||||
operator std::optional<T_Result>() { return success(); }
|
||||
operator bool() { return isSuccess(); }
|
||||
explicit operator bool() const { return isSuccess(); } // Explicity is necessary to prevent auto-casting from result to Variant, for instance
|
||||
bool operator !() { return isError(); }
|
||||
};
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ CFrame partCFrameFromHandlePos(HandleFace face, Vector3 newPos) {
|
|||
|
||||
CFrame localFrame = editorToolHandles.worldMode ? CFrame::IDENTITY + adornee->position() : adornee->cframe;
|
||||
CFrame inverseFrame = localFrame.Inverse();
|
||||
Vector3 handleOffset = editorToolHandles.worldMode ? ((Vector3::ONE * 2.f) + adornee->GetAABB() * 0.5f) : Vector3(2.f + adornee->size * 0.5f);
|
||||
Vector3 handleOffset = editorToolHandles.worldMode ? ((Vector3::ONE * 2.f) + adornee->GetAABB() * 0.5f) : Vector3(2.f) + adornee->size * 0.5f;
|
||||
|
||||
Vector3 handlePos = localFrame * (handleOffset * face.normal);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
extern "C" {
|
||||
#include <luajit-2.1/luajit.h>
|
||||
#include <luajit-2.1/lauxlib.h>
|
||||
#include <luajit-2.1/lualib.h>
|
||||
#include <luajit-2.1/lua.h>
|
||||
#include <luajit.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
#include <lua.h>
|
||||
}
|
|
@ -37,8 +37,8 @@
|
|||
#define AUTOGEN_PREAMBLE \
|
||||
protected: \
|
||||
virtual result<PropertyMeta, MemberNotFound> InternalGetPropertyMeta(std::string name) override; \
|
||||
virtual fallible<MemberNotFound, AssignToReadOnlyMember> InternalSetPropertyValue(std::string name, Data::Variant value) override; \
|
||||
virtual result<Data::Variant, MemberNotFound> InternalGetPropertyValue(std::string name) override; \
|
||||
virtual fallible<MemberNotFound, AssignToReadOnlyMember> InternalSetPropertyValue(std::string name, Variant value) override; \
|
||||
virtual result<Variant, MemberNotFound> InternalGetPropertyValue(std::string name) override; \
|
||||
virtual void InternalUpdateProperty(std::string name) override; \
|
||||
virtual std::vector<std::string> InternalGetProperties() override; \
|
||||
public: \
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "instance.h"
|
||||
#include "common.h"
|
||||
#include "datatypes/meta.h"
|
||||
#include "datatypes/primitives.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "datatypes/base.h"
|
||||
#include "datatypes/ref.h"
|
||||
#include "error/instance.h"
|
||||
|
@ -11,6 +12,7 @@
|
|||
#include "logger.h"
|
||||
#include "panic.h"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
@ -27,6 +29,7 @@ const InstanceType Instance::TYPE = {
|
|||
.className = "Instance",
|
||||
.constructor = NULL, // Instance is abstract and therefore not creatable
|
||||
.explorerIcon = "instance",
|
||||
.flags = 0
|
||||
};
|
||||
|
||||
// Instance is abstract, so it should not implement GetClass directly
|
||||
|
@ -96,7 +99,7 @@ void Instance::updateAncestry(std::optional<std::shared_ptr<Instance>> updatedCh
|
|||
}
|
||||
|
||||
OnAncestryChanged(updatedChild, newParent);
|
||||
AncestryChanged->Fire({updatedChild.has_value() ? Data::InstanceRef(updatedChild.value()) : Data::InstanceRef(), newParent.has_value() ? Data::InstanceRef(newParent.value()) : Data::InstanceRef()});
|
||||
AncestryChanged->Fire({updatedChild.has_value() ? InstanceRef(updatedChild.value()) : InstanceRef(), newParent.has_value() ? InstanceRef(newParent.value()) : InstanceRef()});
|
||||
|
||||
// Old workspace used to exist, and workspaces differ
|
||||
if (!oldWorkspace.expired() && oldWorkspace != _workspace) {
|
||||
|
@ -109,7 +112,7 @@ void Instance::updateAncestry(std::optional<std::shared_ptr<Instance>> updatedCh
|
|||
}
|
||||
|
||||
// Update ancestry in descendants
|
||||
for (InstanceRef child : children) {
|
||||
for (std::shared_ptr<Instance> child : children) {
|
||||
child->updateAncestry(updatedChild, newParent);
|
||||
}
|
||||
}
|
||||
|
@ -179,11 +182,13 @@ void Instance::OnWorkspaceRemoved(std::shared_ptr<Workspace> oldWorkspace) {
|
|||
|
||||
// Properties
|
||||
|
||||
result<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
|
||||
result<Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
|
||||
name[0] = toupper(name[0]); // Ignore case of first character
|
||||
return InternalGetPropertyValue(name);
|
||||
}
|
||||
|
||||
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::SetPropertyValue(std::string name, Data::Variant value, bool sendUpdateEvent) {
|
||||
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::SetPropertyValue(std::string name, Variant value, bool sendUpdateEvent) {
|
||||
name[0] = toupper(name[0]); // Ignore case of first character
|
||||
auto result = InternalSetPropertyValue(name, value);
|
||||
if (result.isSuccess() && sendUpdateEvent) {
|
||||
InternalUpdateProperty(name);
|
||||
|
@ -193,37 +198,38 @@ fallible<MemberNotFound, AssignToReadOnlyMember> Instance::SetPropertyValue(std:
|
|||
}
|
||||
|
||||
result<PropertyMeta, MemberNotFound> Instance::GetPropertyMeta(std::string name) {
|
||||
name[0] = toupper(name[0]); // Ignore case of first character
|
||||
return InternalGetPropertyMeta(name);
|
||||
}
|
||||
|
||||
|
||||
result<Data::Variant, MemberNotFound> Instance::InternalGetPropertyValue(std::string name) {
|
||||
result<Variant, MemberNotFound> Instance::InternalGetPropertyValue(std::string name) {
|
||||
if (name == "Name") {
|
||||
return Data::Variant(Data::String(this->name));
|
||||
return Variant(this->name);
|
||||
} else if (name == "Parent") {
|
||||
return Data::Variant(Data::InstanceRef(this->parent));
|
||||
return Variant(InstanceRef(this->parent));
|
||||
} else if (name == "ClassName") {
|
||||
return Data::Variant(Data::String(GetClass()->className));
|
||||
return Variant(GetClass()->className);
|
||||
}
|
||||
return MemberNotFound(GetClass()->className, name);
|
||||
}
|
||||
|
||||
result<PropertyMeta, MemberNotFound> Instance::InternalGetPropertyMeta(std::string name) {
|
||||
if (name == "Name") {
|
||||
return PropertyMeta { &Data::String::TYPE };
|
||||
return PropertyMeta { &STRING_TYPE, 0 };
|
||||
} else if (name == "Parent") {
|
||||
return PropertyMeta { &Data::InstanceRef::TYPE, };
|
||||
return PropertyMeta { &InstanceRef::TYPE, PROP_NOSAVE };
|
||||
} else if (name == "ClassName") {
|
||||
return PropertyMeta { &Data::String::TYPE, PROP_NOSAVE | PROP_READONLY };
|
||||
return PropertyMeta { &STRING_TYPE, PROP_NOSAVE | PROP_READONLY };
|
||||
}
|
||||
return MemberNotFound(GetClass()->className, name);
|
||||
}
|
||||
|
||||
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::InternalSetPropertyValue(std::string name, Data::Variant value) {
|
||||
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::InternalSetPropertyValue(std::string name, Variant value) {
|
||||
if (name == "Name") {
|
||||
this->name = (std::string)value.get<Data::String>();
|
||||
this->name = (std::string)value.get<std::string>();
|
||||
} else if (name == "Parent") {
|
||||
std::weak_ptr<Instance> ref = value.get<Data::InstanceRef>();
|
||||
std::weak_ptr<Instance> ref = value.get<InstanceRef>();
|
||||
SetParent(ref.expired() ? std::nullopt : std::make_optional(ref.lock()));
|
||||
} else if (name == "ClassName") {
|
||||
return AssignToReadOnlyMember(GetClass()->className, name);
|
||||
|
@ -259,7 +265,8 @@ std::vector<std::string> Instance::GetProperties() {
|
|||
|
||||
// Serialization
|
||||
|
||||
void Instance::Serialize(pugi::xml_node parent) {
|
||||
void Instance::Serialize(pugi::xml_node parent, RefStateSerialize state) {
|
||||
if (state == nullptr) state = std::make_shared<__RefStateSerialize>();
|
||||
pugi::xml_node node = parent.append_child("Item");
|
||||
node.append_attribute("class").set_value(this->GetClass()->className);
|
||||
|
||||
|
@ -269,24 +276,65 @@ void Instance::Serialize(pugi::xml_node parent) {
|
|||
PropertyMeta meta = GetPropertyMeta(name).expect("Meta of declared property is missing");
|
||||
if (meta.flags & (PROP_NOSAVE | PROP_READONLY)) continue; // This property should not be serialized. Skip...
|
||||
|
||||
pugi::xml_node propertyNode = propertiesNode.append_child(meta.type->name);
|
||||
#if 1
|
||||
// Special consideration for Part.Size
|
||||
// It should be serialized as "size" to be compatible with rbxl files
|
||||
// This is optional, as they can still be opened otherwise, but I opted
|
||||
// to keep this enabled
|
||||
if (IsA("Part") && name == "Size")
|
||||
name = "size";
|
||||
#endif
|
||||
|
||||
pugi::xml_node propertyNode = propertiesNode.append_child(meta.type.descriptor->name);
|
||||
propertyNode.append_attribute("name").set_value(name);
|
||||
GetPropertyValue(name).expect("Declared property is missing").Serialize(propertyNode);
|
||||
|
||||
// Update std::shared_ptr<Instance> properties using map above
|
||||
if (meta.type.descriptor == &InstanceRef::TYPE) {
|
||||
std::weak_ptr<Instance> refWeak = GetPropertyValue(name).expect("Declared property is missing").get<InstanceRef>();
|
||||
if (refWeak.expired()) continue;
|
||||
|
||||
auto ref = refWeak.lock();
|
||||
auto remappedRef = state->remappedInstances[ref]; // TODO: I think this is okay? Maybe?? Add null check?
|
||||
|
||||
if (remappedRef != "") {
|
||||
// If the instance has already been remapped, set the new value
|
||||
propertyNode.text().set(remappedRef);
|
||||
} else {
|
||||
// Otheriise, queue this property to be updated later, and keep its current value
|
||||
auto& refs = state->refsAwaitingRemap[ref];
|
||||
refs.push_back(propertyNode);
|
||||
state->refsAwaitingRemap[ref] = refs;
|
||||
}
|
||||
} else {
|
||||
GetPropertyValue(name).expect("Declared property is missing").Serialize(propertyNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Remap self
|
||||
std::string remappedId = "OB" + std::to_string(state->count++);
|
||||
state->remappedInstances[shared_from_this()] = remappedId;
|
||||
node.append_attribute("referent").set_value(remappedId);
|
||||
|
||||
// Remap queued properties
|
||||
for (pugi::xml_node ref : state->refsAwaitingRemap[shared_from_this()]) {
|
||||
ref.text().set(remappedId);
|
||||
}
|
||||
state->refsAwaitingRemap[shared_from_this()].clear();
|
||||
|
||||
// Add children
|
||||
for (InstanceRef child : this->children) {
|
||||
child->Serialize(node);
|
||||
for (std::shared_ptr<Instance> child : this->children) {
|
||||
child->Serialize(node, state);
|
||||
}
|
||||
}
|
||||
|
||||
result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) {
|
||||
result<std::shared_ptr<Instance>, NoSuchInstance> Instance::Deserialize(pugi::xml_node node, RefStateDeserialize state) {
|
||||
if (state == nullptr) state = std::make_shared<__RefStateDeserialize>();
|
||||
std::string className = node.attribute("class").value();
|
||||
if (INSTANCE_MAP.count(className) == 0) {
|
||||
return NoSuchInstance(className);
|
||||
}
|
||||
// This will error if an abstract instance is used in the file. Oh well, not my prob rn.
|
||||
InstanceRef object = INSTANCE_MAP[className]->constructor();
|
||||
std::shared_ptr<Instance> object = INSTANCE_MAP[className]->constructor();
|
||||
object->GetChildren();
|
||||
|
||||
// const InstanceType* type = INSTANCE_MAP.at(className);
|
||||
|
@ -300,13 +348,51 @@ result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) {
|
|||
Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str());
|
||||
continue;
|
||||
}
|
||||
Data::Variant value = Data::Variant::Deserialize(propertyNode);
|
||||
object->SetPropertyValue(propertyName, value).expect("Declared property was missing");
|
||||
auto meta = meta_.expect();
|
||||
|
||||
// Update std::shared_ptr<Instance> properties using map above
|
||||
if (meta.type.descriptor == &InstanceRef::TYPE) {
|
||||
if (propertyNode.text().empty())
|
||||
continue;
|
||||
|
||||
std::string refId = propertyNode.text().as_string();
|
||||
auto remappedRef = state->remappedInstances[refId]; // TODO: I think this is okay? Maybe?? Add null check?
|
||||
|
||||
if (remappedRef) {
|
||||
// If the instance has already been remapped, set the new value
|
||||
object->SetPropertyValue(propertyName, InstanceRef(remappedRef)).expect();
|
||||
} else {
|
||||
// Otheriise, queue this property to be updated later, and keep its current value
|
||||
auto& refs = state->refsAwaitingRemap[refId];
|
||||
refs.push_back(std::make_pair(object, propertyName));
|
||||
state->refsAwaitingRemap[refId] = refs;
|
||||
|
||||
object->SetPropertyValue(propertyName, InstanceRef()).expect();
|
||||
}
|
||||
} else {
|
||||
auto valueResult = Variant::Deserialize(propertyNode, meta.type);
|
||||
if (valueResult.isError()) {
|
||||
valueResult.logError();
|
||||
continue;
|
||||
}
|
||||
auto value = valueResult.expect();
|
||||
object->SetPropertyValue(propertyName, value).expect("Declared property was missing");
|
||||
}
|
||||
}
|
||||
|
||||
// Remap self
|
||||
std::string remappedId = node.attribute("referent").value();
|
||||
state->remappedInstances[remappedId] = object;
|
||||
|
||||
// Remap queued properties
|
||||
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[remappedId]) {
|
||||
ref.first->SetPropertyValue(ref.second, InstanceRef(object)).expect();
|
||||
}
|
||||
state->refsAwaitingRemap[remappedId].clear();
|
||||
|
||||
// Read children
|
||||
for (pugi::xml_node childNode : node.children("Item")) {
|
||||
result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode);
|
||||
result<std::shared_ptr<Instance>, NoSuchInstance> child = Instance::Deserialize(childNode, state);
|
||||
if (child.isError()) {
|
||||
std::get<NoSuchInstance>(child.error().value()).logMessage();
|
||||
continue;
|
||||
|
@ -319,7 +405,7 @@ result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) {
|
|||
|
||||
// DescendantsIterator
|
||||
|
||||
DescendantsIterator::DescendantsIterator(std::shared_ptr<Instance> current) : current(current), root(current == DUMMY_INSTANCE ? DUMMY_INSTANCE : current->GetParent()), siblingIndex { 0 } { }
|
||||
DescendantsIterator::DescendantsIterator(std::shared_ptr<Instance> current) : root(current == DUMMY_INSTANCE ? DUMMY_INSTANCE : current->GetParent()), current(current), siblingIndex { 0 } { }
|
||||
|
||||
DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
|
||||
// If the current item is dummy, an error has occurred, this is not supposed to happen.
|
||||
|
@ -344,7 +430,7 @@ DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
|
|||
}
|
||||
|
||||
// If we've hit the end of this item's children, move one up
|
||||
while (current->GetParent() && current->GetParent().value()->GetChildren().size() <= (siblingIndex.back() + 1)) {
|
||||
while (current->GetParent() && current->GetParent().value()->GetChildren().size() <= size_t(siblingIndex.back() + 1)) {
|
||||
siblingIndex.pop_back();
|
||||
current = current->GetParent().value();
|
||||
|
||||
|
@ -362,7 +448,8 @@ DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
|
|||
return *this;
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePropertyCell> state) {
|
||||
std::optional<std::shared_ptr<Instance>> Instance::Clone(RefStateClone state) {
|
||||
if (state == nullptr) state = std::make_shared<__RefStateClone>();
|
||||
std::shared_ptr<Instance> newInstance = GetClass()->constructor();
|
||||
|
||||
// Copy properties
|
||||
|
@ -371,9 +458,9 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
|
|||
|
||||
if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue;
|
||||
|
||||
// Update InstanceRef properties using map above
|
||||
if (meta.type == &Data::InstanceRef::TYPE) {
|
||||
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<Data::InstanceRef>();
|
||||
// Update std::shared_ptr<Instance> properties using map above
|
||||
if (meta.type.descriptor == &InstanceRef::TYPE) {
|
||||
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<InstanceRef>();
|
||||
if (refWeak.expired()) continue;
|
||||
|
||||
auto ref = refWeak.lock();
|
||||
|
@ -381,17 +468,17 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
|
|||
|
||||
if (remappedRef) {
|
||||
// If the instance has already been remapped, set the new value
|
||||
newInstance->SetPropertyValue(property, Data::InstanceRef(remappedRef)).expect();
|
||||
newInstance->SetPropertyValue(property, InstanceRef(remappedRef)).expect();
|
||||
} else {
|
||||
// Otheriise, queue this property to be updated later, and keep its current value
|
||||
auto& refs = state->refsAwaitingRemap[ref];
|
||||
refs.push_back(std::make_pair(newInstance, property));
|
||||
state->refsAwaitingRemap[ref] = refs;
|
||||
|
||||
newInstance->SetPropertyValue(property, Data::InstanceRef(ref)).expect();
|
||||
newInstance->SetPropertyValue(property, InstanceRef(ref)).expect();
|
||||
}
|
||||
} else {
|
||||
Data::Variant value = GetPropertyValue(property).expect();
|
||||
Variant value = GetPropertyValue(property).expect();
|
||||
newInstance->SetPropertyValue(property, value).expect();
|
||||
}
|
||||
}
|
||||
|
@ -401,8 +488,9 @@ std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePrope
|
|||
|
||||
// Remap queued properties
|
||||
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) {
|
||||
ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newInstance)).expect();
|
||||
ref.first->SetPropertyValue(ref.second, InstanceRef(newInstance)).expect();
|
||||
}
|
||||
state->refsAwaitingRemap[shared_from_this()].clear();
|
||||
|
||||
// Clone children
|
||||
for (std::shared_ptr<Instance> child : GetChildren()) {
|
||||
|
@ -421,9 +509,9 @@ std::vector<std::pair<std::string, std::shared_ptr<Instance>>> Instance::GetRefe
|
|||
|
||||
for (std::string property : propertyNames) {
|
||||
PropertyMeta meta = GetPropertyMeta(property).expect();
|
||||
if (meta.type != &Data::InstanceRef::TYPE) continue;
|
||||
if (meta.type.descriptor != &InstanceRef::TYPE) continue;
|
||||
|
||||
std::weak_ptr<Instance> ref = GetPropertyValue(property).expect().get<Data::InstanceRef>();
|
||||
std::weak_ptr<Instance> ref = GetPropertyValue(property).expect().get<InstanceRef>();
|
||||
if (ref.expired()) continue;
|
||||
referenceProperties.push_back(std::make_pair(property, ref.lock()));
|
||||
}
|
||||
|
|
|
@ -42,8 +42,6 @@ struct InstanceType {
|
|||
InstanceFlags flags;
|
||||
};
|
||||
|
||||
typedef std::pair<std::shared_ptr<Instance>, std::string> _RefStatePropertyCell;
|
||||
|
||||
class DescendantsIterator;
|
||||
class JointInstance;
|
||||
|
||||
|
@ -71,8 +69,8 @@ protected:
|
|||
Instance(const InstanceType*);
|
||||
virtual ~Instance();
|
||||
|
||||
virtual result<Data::Variant, MemberNotFound> InternalGetPropertyValue(std::string name);
|
||||
virtual fallible<MemberNotFound, AssignToReadOnlyMember> InternalSetPropertyValue(std::string name, Data::Variant value);
|
||||
virtual result<Variant, MemberNotFound> InternalGetPropertyValue(std::string name);
|
||||
virtual fallible<MemberNotFound, AssignToReadOnlyMember> InternalSetPropertyValue(std::string name, Variant value);
|
||||
virtual result<PropertyMeta, MemberNotFound> InternalGetPropertyMeta(std::string name);
|
||||
virtual void InternalUpdateProperty(std::string name);
|
||||
virtual std::vector<std::string> InternalGetProperties();
|
||||
|
@ -116,8 +114,8 @@ public:
|
|||
std::string GetFullName();
|
||||
|
||||
// Properties
|
||||
result<Data::Variant, MemberNotFound> GetPropertyValue(std::string name);
|
||||
fallible<MemberNotFound, AssignToReadOnlyMember> SetPropertyValue(std::string name, Data::Variant value, bool sendUpdateEvent = true);
|
||||
result<Variant, MemberNotFound> GetPropertyValue(std::string name);
|
||||
fallible<MemberNotFound, AssignToReadOnlyMember> SetPropertyValue(std::string name, Variant value, bool sendUpdateEvent = true);
|
||||
result<PropertyMeta, MemberNotFound> GetPropertyMeta(std::string name);
|
||||
// Manually trigger the update of a property. Useful internally when setting properties directly
|
||||
void UpdateProperty(std::string name);
|
||||
|
@ -135,14 +133,11 @@ public:
|
|||
}
|
||||
|
||||
// Serialization
|
||||
void Serialize(pugi::xml_node parent);
|
||||
static result<std::shared_ptr<Instance>, NoSuchInstance> Deserialize(pugi::xml_node node);
|
||||
std::optional<std::shared_ptr<Instance>> Clone(RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>());
|
||||
void Serialize(pugi::xml_node parent, RefStateSerialize state = {});
|
||||
static result<std::shared_ptr<Instance>, NoSuchInstance> Deserialize(pugi::xml_node node, RefStateDeserialize state = {});
|
||||
std::optional<std::shared_ptr<Instance>> Clone(RefStateClone state = {});
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Instance> InstanceRef;
|
||||
typedef std::weak_ptr<Instance> InstanceRefWeak;
|
||||
|
||||
// https://gist.github.com/jeetsukumaran/307264
|
||||
class DescendantsIterator {
|
||||
public:
|
||||
|
@ -154,7 +149,7 @@ public:
|
|||
typedef int difference_type;
|
||||
|
||||
DescendantsIterator(std::shared_ptr<Instance> current);
|
||||
inline self_type operator++() { self_type i = *this; ++*this; return i; }
|
||||
inline self_type operator++() { (*this)++; return (*this); }
|
||||
inline std::shared_ptr<Instance> operator*() { return current; }
|
||||
inline std::shared_ptr<Instance> operator->() { return current; }
|
||||
inline bool operator==(const self_type& rhs) { return current == rhs.current; }
|
||||
|
|
|
@ -24,7 +24,7 @@ enum PropertyCategory {
|
|||
const int PROPERTY_CATEGORY_MAX = PROP_CATEGORY_SURFACE_INPUT;
|
||||
|
||||
struct PropertyMeta {
|
||||
const Data::TypeInfo* type;
|
||||
const TypeMeta type;
|
||||
PropertyFlags flags;
|
||||
PropertyCategory category = PROP_CATEGORY_DATA;
|
||||
};
|
||||
|
|
|
@ -2,16 +2,26 @@
|
|||
|
||||
// Helper struct used for remapping reference when cloning/serializing
|
||||
|
||||
#include "datatypes/base.h"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
class Instance;
|
||||
|
||||
template <typename T>
|
||||
template <typename T, typename U, typename K>
|
||||
struct __RefState {
|
||||
std::map<std::shared_ptr<Instance>, std::shared_ptr<Instance>> remappedInstances;
|
||||
std::map<std::shared_ptr<Instance>, std::vector<T>> refsAwaitingRemap;
|
||||
std::map<K, U> remappedInstances;
|
||||
std::map<K, std::vector<T>> refsAwaitingRemap;
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using RefState = std::shared_ptr<__RefState<T>>;
|
||||
template <typename T, typename U, typename K>
|
||||
using RefState = std::shared_ptr<__RefState<T, U, K>>;
|
||||
|
||||
typedef __RefState<std::pair<std::shared_ptr<Instance>, std::string>, std::shared_ptr<Instance>, std::shared_ptr<Instance>> __RefStateClone;
|
||||
typedef __RefState<pugi::xml_node, std::string, std::shared_ptr<Instance>> __RefStateSerialize;
|
||||
typedef __RefState<std::pair<std::shared_ptr<Instance>, std::string>, std::shared_ptr<Instance>, std::string> __RefStateDeserialize;
|
||||
|
||||
typedef std::shared_ptr<__RefStateClone> RefStateClone;
|
||||
typedef std::shared_ptr<__RefStateSerialize> RefStateSerialize;
|
||||
typedef std::shared_ptr<__RefStateDeserialize> RefStateDeserialize;
|
|
@ -5,7 +5,7 @@
|
|||
#include "objects/base/service.h"
|
||||
#include "objects/meta.h"
|
||||
#include "objects/script/serverscriptservice.h"
|
||||
#include "datatypes/meta.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "workspace.h"
|
||||
#include "logger.h"
|
||||
#include "panic.h"
|
||||
|
@ -49,7 +49,7 @@ void DataModel::SaveToFile(std::optional<std::string> path) {
|
|||
pugi::xml_document doc;
|
||||
pugi::xml_node root = doc.append_child("openblocks");
|
||||
|
||||
for (InstanceRef child : this->GetChildren()) {
|
||||
for (std::shared_ptr<Instance> child : this->GetChildren()) {
|
||||
child->Serialize(root);
|
||||
}
|
||||
|
||||
|
@ -59,50 +59,6 @@ void DataModel::SaveToFile(std::optional<std::string> path) {
|
|||
Logger::info("Place saved successfully");
|
||||
}
|
||||
|
||||
void DataModel::DeserializeService(pugi::xml_node node) {
|
||||
std::string className = node.attribute("class").value();
|
||||
if (INSTANCE_MAP.count(className) == 0) {
|
||||
Logger::fatalErrorf("Unknown service: '%s'", className.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (services.count(className) != 0) {
|
||||
Logger::fatalErrorf("Service %s defined multiple times in file", className.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// This will error if an abstract instance is used in the file. Oh well, not my prob rn.
|
||||
InstanceRef object = INSTANCE_MAP[className]->constructor();
|
||||
AddChild(object);
|
||||
|
||||
// Read properties
|
||||
pugi::xml_node propertiesNode = node.child("Properties");
|
||||
for (pugi::xml_node propertyNode : propertiesNode) {
|
||||
std::string propertyName = propertyNode.attribute("name").value();
|
||||
auto meta_ = object->GetPropertyMeta(propertyName);
|
||||
if (!meta_) {
|
||||
Logger::fatalErrorf("Attempt to set unknown property '%s' of %s", propertyName.c_str(), object->GetClass()->className.c_str());
|
||||
continue;
|
||||
}
|
||||
Data::Variant value = Data::Variant::Deserialize(propertyNode);
|
||||
object->SetPropertyValue(propertyName, value).expect();
|
||||
}
|
||||
|
||||
// Add children
|
||||
for (pugi::xml_node childNode : node.children("Item")) {
|
||||
result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode);
|
||||
if (child.isError()) {
|
||||
std::get<NoSuchInstance>(child.error().value()).logMessage();
|
||||
continue;
|
||||
}
|
||||
object->AddChild(child.expect());
|
||||
}
|
||||
|
||||
// We add the service to the list
|
||||
// All services get init'd at once in InitServices
|
||||
this->services[className] = std::dynamic_pointer_cast<Service>(object);
|
||||
}
|
||||
|
||||
std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
|
||||
std::ifstream inStream(path);
|
||||
pugi::xml_document doc;
|
||||
|
@ -110,11 +66,31 @@ std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
|
|||
|
||||
pugi::xml_node rootNode = doc.child("openblocks");
|
||||
std::shared_ptr<DataModel> newModel = std::make_shared<DataModel>();
|
||||
RefStateDeserialize state = std::make_shared<__RefStateDeserialize>();
|
||||
|
||||
for (pugi::xml_node childNode : rootNode.children("Item")) {
|
||||
newModel->DeserializeService(childNode);
|
||||
// Make sure the class hasn't already been deserialized
|
||||
std::string className = childNode.attribute("class").value();
|
||||
|
||||
// TODO: Make this push its children into the first service, or however it is actually done in the thing
|
||||
// for parity
|
||||
if (newModel->services.count(className) != 0) {
|
||||
Logger::fatalErrorf("Service %s defined multiple times in file", className.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto result = Instance::Deserialize(childNode, state);
|
||||
if (result.isError()) {
|
||||
Logger::errorf("Failed to deserialize service: %s", result.errorMessage()->c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto service = result.expect();
|
||||
newModel->AddChild(service);
|
||||
newModel->services[className] = std::dynamic_pointer_cast<Service>(service);
|
||||
}
|
||||
|
||||
newModel->currentFile = path;
|
||||
newModel->Init();
|
||||
|
||||
return newModel;
|
||||
|
@ -146,7 +122,7 @@ result<std::optional<std::shared_ptr<Service>>, NoSuchService> DataModel::FindSe
|
|||
}
|
||||
|
||||
std::shared_ptr<DataModel> DataModel::CloneModel() {
|
||||
RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>();
|
||||
RefStateClone state = std::make_shared<__RefStateClone>();
|
||||
std::shared_ptr<DataModel> newModel = DataModel::New();
|
||||
|
||||
// Copy properties
|
||||
|
@ -155,9 +131,9 @@ std::shared_ptr<DataModel> DataModel::CloneModel() {
|
|||
|
||||
if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue;
|
||||
|
||||
// Update InstanceRef properties using map above
|
||||
if (meta.type == &Data::InstanceRef::TYPE) {
|
||||
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<Data::InstanceRef>();
|
||||
// Update std::shared_ptr<Instance> properties using map above
|
||||
if (meta.type.descriptor == &InstanceRef::TYPE) {
|
||||
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<InstanceRef>();
|
||||
if (refWeak.expired()) continue;
|
||||
|
||||
auto ref = refWeak.lock();
|
||||
|
@ -165,17 +141,17 @@ std::shared_ptr<DataModel> DataModel::CloneModel() {
|
|||
|
||||
if (remappedRef) {
|
||||
// If the instance has already been remapped, set the new value
|
||||
newModel->SetPropertyValue(property, Data::InstanceRef(remappedRef)).expect();
|
||||
newModel->SetPropertyValue(property, InstanceRef(remappedRef)).expect();
|
||||
} else {
|
||||
// Otheriise, queue this property to be updated later, and keep its current value
|
||||
auto& refs = state->refsAwaitingRemap[ref];
|
||||
refs.push_back(std::make_pair(newModel, property));
|
||||
state->refsAwaitingRemap[ref] = refs;
|
||||
|
||||
newModel->SetPropertyValue(property, Data::InstanceRef(ref)).expect();
|
||||
newModel->SetPropertyValue(property, InstanceRef(ref)).expect();
|
||||
}
|
||||
} else {
|
||||
Data::Variant value = GetPropertyValue(property).expect();
|
||||
Variant value = GetPropertyValue(property).expect();
|
||||
newModel->SetPropertyValue(property, value).expect();
|
||||
}
|
||||
}
|
||||
|
@ -185,7 +161,7 @@ std::shared_ptr<DataModel> DataModel::CloneModel() {
|
|||
|
||||
// Remap queued properties
|
||||
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) {
|
||||
ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newModel)).expect();
|
||||
ref.first->SetPropertyValue(ref.second, InstanceRef(newModel)).expect();
|
||||
}
|
||||
|
||||
// Clone services
|
||||
|
|
|
@ -16,8 +16,8 @@ class Service;
|
|||
class DEF_INST_(abstract) DataModel : public Instance {
|
||||
AUTOGEN_PREAMBLE
|
||||
private:
|
||||
void DeserializeService(pugi::xml_node node);
|
||||
static void cloneService(std::shared_ptr<DataModel> target, std::shared_ptr<Service>, RefState<_RefStatePropertyCell>);
|
||||
// void DeserializeService(pugi::xml_node node, RefStateDeserialize);
|
||||
static void cloneService(std::shared_ptr<DataModel> target, std::shared_ptr<Service>, RefStateClone);
|
||||
public:
|
||||
std::map<std::string, std::shared_ptr<Service>> services;
|
||||
|
||||
|
|
4
core/src/objects/folder.cpp
Normal file
4
core/src/objects/folder.cpp
Normal file
|
@ -0,0 +1,4 @@
|
|||
#include "folder.h"
|
||||
|
||||
Folder::Folder(): Instance(&TYPE) {}
|
||||
Folder::~Folder() = default;
|
19
core/src/objects/folder.h
Normal file
19
core/src/objects/folder.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include "objects/annotation.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include <memory>
|
||||
|
||||
// The simplest instance
|
||||
// Has no functionality of its own, used purely for organizational/grouping purposes
|
||||
|
||||
class DEF_INST_(explorer_icon="folder") Folder : public Instance {
|
||||
AUTOGEN_PREAMBLE
|
||||
|
||||
public:
|
||||
Folder();
|
||||
~Folder();
|
||||
|
||||
static inline std::shared_ptr<Folder> New() { return std::make_shared<Folder>(); };
|
||||
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Folder>(); };
|
||||
};
|
|
@ -1,9 +1,11 @@
|
|||
#include "meta.h"
|
||||
#include "objects/folder.h"
|
||||
#include "objects/joint/jointinstance.h"
|
||||
#include "objects/joint/rotate.h"
|
||||
#include "objects/joint/rotatev.h"
|
||||
#include "objects/joint/weld.h"
|
||||
#include "objects/jointsservice.h"
|
||||
#include "objects/model.h"
|
||||
#include "objects/part.h"
|
||||
#include "objects/joint/snap.h"
|
||||
#include "objects/script.h"
|
||||
|
@ -23,6 +25,8 @@ std::map<std::string, const InstanceType*> INSTANCE_MAP = {
|
|||
{ "RotateV", &RotateV::TYPE },
|
||||
{ "JointInstance", &JointInstance::TYPE },
|
||||
{ "Script", &Script::TYPE },
|
||||
{ "Model", &Model::TYPE },
|
||||
// { "Folder", &Folder::TYPE },
|
||||
|
||||
// Services
|
||||
|
||||
|
|
4
core/src/objects/model.cpp
Normal file
4
core/src/objects/model.cpp
Normal file
|
@ -0,0 +1,4 @@
|
|||
#include "model.h"
|
||||
|
||||
Model::Model(): Instance(&TYPE) {}
|
||||
Model::~Model() = default;
|
18
core/src/objects/model.h
Normal file
18
core/src/objects/model.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include "objects/annotation.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include <memory>
|
||||
|
||||
// Group object for Parts
|
||||
|
||||
class DEF_INST_(explorer_icon="model") Model : public Instance {
|
||||
AUTOGEN_PREAMBLE
|
||||
|
||||
public:
|
||||
Model();
|
||||
~Model();
|
||||
|
||||
static inline std::shared_ptr<Model> New() { return std::make_shared<Model>(); };
|
||||
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Model>(); };
|
||||
};
|
|
@ -13,7 +13,7 @@
|
|||
#include "objects/joint/jointinstance.h"
|
||||
#include "objects/joint/snap.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "rendering/surface.h"
|
||||
#include "enum/surface.h"
|
||||
#include <cstdio>
|
||||
#include <glm/common.hpp>
|
||||
#include <memory>
|
||||
|
@ -83,7 +83,7 @@ Vector3 Part::GetAABB() {
|
|||
Vector3 min(0, 0, 0);
|
||||
Vector3 max(0, 0, 0);
|
||||
for (Vector3 vert : verts) {
|
||||
Vector3 worldVert = this->cframe.Rotation() * ((Vector3)this->size * vert);
|
||||
Vector3 worldVert = this->cframe.Rotation() * (this->size * vert);
|
||||
expandMaxExtents(&min, &max, worldVert);
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ SurfaceType Part::surfaceFromFace(NormalId face) {
|
|||
case Front: return frontSurface;
|
||||
case Back: return backSurface;
|
||||
}
|
||||
return SurfaceSmooth; // Unreachable
|
||||
return SurfaceType::Smooth; // Unreachable
|
||||
}
|
||||
|
||||
float Part::GetSurfaceParamA(Vector3 face) {
|
||||
|
@ -199,14 +199,14 @@ bool Part::checkSurfacesTouching(CFrame surfaceFrame, Vector3 size, Vector3 myFa
|
|||
}
|
||||
|
||||
std::optional<std::shared_ptr<JointInstance>> makeJointFromSurfaces(SurfaceType a, SurfaceType b) {
|
||||
if (a == SurfaceWeld || b == SurfaceWeld || a == SurfaceGlue || b == SurfaceGlue) return Weld::New();
|
||||
if ((a == SurfaceStuds && (b == SurfaceInlets || b == SurfaceUniversal))
|
||||
|| (a == SurfaceInlets && (b == SurfaceStuds || b == SurfaceUniversal))
|
||||
|| (a == SurfaceUniversal && (b == SurfaceStuds || b == SurfaceInlets || b == SurfaceUniversal)))
|
||||
if (a == SurfaceType::Weld || b == SurfaceType::Weld || a == SurfaceType::Glue || b == SurfaceType::Glue) return Weld::New();
|
||||
if ((a == SurfaceType::Studs && (b == SurfaceType::Inlet || b == SurfaceType::Universal))
|
||||
|| (a == SurfaceType::Inlet && (b == SurfaceType::Studs || b == SurfaceType::Universal))
|
||||
|| (a == SurfaceType::Universal && (b == SurfaceType::Studs || b == SurfaceType::Inlet || b == SurfaceType::Universal)))
|
||||
return Snap::New();
|
||||
if (a == SurfaceHinge)
|
||||
if (a == SurfaceType::Hinge)
|
||||
return Rotate::New();
|
||||
if (a == SurfaceMotor)
|
||||
if (a == SurfaceType::Motor)
|
||||
return RotateV::New();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -224,7 +224,7 @@ void Part::MakeJoints() {
|
|||
// TEMPORARY
|
||||
// TODO: Use more efficient algorithm to *actually* find nearby parts)
|
||||
for (auto it = workspace().value()->GetDescendantsStart(); it != workspace().value()->GetDescendantsEnd(); it++) {
|
||||
InstanceRef obj = *it;
|
||||
std::shared_ptr<Instance> obj = *it;
|
||||
if (obj == shared_from_this()) continue; // Skip ourselves
|
||||
if (obj->GetClass()->className != "Part") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly
|
||||
std::shared_ptr<Part> otherPart = obj->CastTo<Part>().expect();
|
||||
|
@ -251,7 +251,7 @@ void Part::MakeJoints() {
|
|||
SurfaceType otherSurface = surfaceFromFace(faceFromNormal(otherFace));
|
||||
|
||||
// If it is a hinge, only attach if actually touching the "hinge"
|
||||
if ((mySurface == SurfaceHinge || mySurface == SurfaceMotor) && !checkSurfacesTouching(surfaceFrame, Vector3(0.4, 0.4, 0.4), myFace, otherFace, otherPart)) continue;
|
||||
if ((mySurface == SurfaceType::Hinge || mySurface == SurfaceType::Motor) && !checkSurfacesTouching(surfaceFrame, Vector3(0.4, 0.4, 0.4), myFace, otherFace, otherPart)) continue;
|
||||
|
||||
// Create contacts
|
||||
// Contact always occurs at the center of Part0's surface (even if that point does not overlap both surfaces)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include "datatypes/signal.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "rendering/surface.h"
|
||||
#include "enum/surface.h"
|
||||
#include <optional>
|
||||
#include <reactphysics3d/reactphysics3d.h>
|
||||
#include <vector>
|
||||
|
@ -18,9 +18,9 @@ namespace rp = reactphysics3d;
|
|||
|
||||
// For easy construction from C++. Maybe should be removed?
|
||||
struct PartConstructParams {
|
||||
glm::vec3 position;
|
||||
glm::vec3 rotation;
|
||||
glm::vec3 size;
|
||||
Vector3 position;
|
||||
Vector3 rotation;
|
||||
Vector3 size;
|
||||
Color3 color;
|
||||
|
||||
bool anchored = false;
|
||||
|
@ -58,7 +58,9 @@ public:
|
|||
CFrame cframe;
|
||||
|
||||
DEF_PROP_CATEGORY(PART)
|
||||
DEF_PROP_(on_update=onUpdated) glm::vec3 size;
|
||||
// Special compatibility changes for this property were made in
|
||||
// Instance::Serialize
|
||||
DEF_PROP_(on_update=onUpdated) Vector3 size;
|
||||
|
||||
DEF_PROP_CATEGORY(APPEARANCE)
|
||||
DEF_PROP Color3 color;
|
||||
|
@ -70,12 +72,12 @@ public:
|
|||
DEF_PROP bool locked = false;
|
||||
|
||||
DEF_PROP_CATEGORY(SURFACE)
|
||||
DEF_PROP SurfaceType topSurface = SurfaceType::SurfaceStuds;
|
||||
DEF_PROP SurfaceType bottomSurface = SurfaceType::SurfaceInlets;
|
||||
DEF_PROP SurfaceType leftSurface = SurfaceType::SurfaceSmooth;
|
||||
DEF_PROP SurfaceType rightSurface = SurfaceType::SurfaceSmooth;
|
||||
DEF_PROP SurfaceType frontSurface = SurfaceType::SurfaceSmooth;
|
||||
DEF_PROP SurfaceType backSurface = SurfaceType::SurfaceSmooth;
|
||||
DEF_PROP SurfaceType topSurface = SurfaceType::Studs;
|
||||
DEF_PROP SurfaceType bottomSurface = SurfaceType::Inlet;
|
||||
DEF_PROP SurfaceType leftSurface = SurfaceType::Smooth;
|
||||
DEF_PROP SurfaceType rightSurface = SurfaceType::Smooth;
|
||||
DEF_PROP SurfaceType frontSurface = SurfaceType::Smooth;
|
||||
DEF_PROP SurfaceType backSurface = SurfaceType::Smooth;
|
||||
|
||||
DEF_PROP_CATEGORY(SURFACE_INPUT)
|
||||
DEF_PROP float topParamA = -0.5;
|
||||
|
@ -107,7 +109,7 @@ public:
|
|||
|
||||
static inline std::shared_ptr<Part> New() { return std::make_shared<Part>(); };
|
||||
static inline std::shared_ptr<Part> New(PartConstructParams params) { return std::make_shared<Part>(params); };
|
||||
static inline InstanceRef Create() { return std::make_shared<Part>(); };
|
||||
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Part>(); };
|
||||
|
||||
inline Vector3 position() { return cframe.Position(); }
|
||||
|
||||
|
|
|
@ -15,15 +15,7 @@ int script_wait(lua_State*);
|
|||
int script_delay(lua_State*);
|
||||
|
||||
Script::Script(): Instance(&TYPE) {
|
||||
source = "workspace.Part.Touched:Connect(function(otherPart)\n"
|
||||
" print(\"Touched by: \", otherPart.Name)\n"
|
||||
"end)\n"
|
||||
"\n"
|
||||
"workspace.Part.TouchEnded:Connect(function(otherPart)\n"
|
||||
" print(\"Touched ended with: \", otherPart.Name)\n"
|
||||
"end)\n"
|
||||
"\n"
|
||||
"error(\"Test\")";
|
||||
source = "print(\"Hello, world!\")";
|
||||
}
|
||||
|
||||
Script::~Script() {
|
||||
|
@ -40,11 +32,14 @@ void Script::Run() {
|
|||
|
||||
// Initialize script globals
|
||||
lua_getglobal(Lt, "_G");
|
||||
|
||||
InstanceRef(shared_from_this()).PushLuaValue(Lt);
|
||||
lua_setfield(Lt, -2, "script");
|
||||
|
||||
Data::InstanceRef(dataModel().value()).PushLuaValue(Lt);
|
||||
InstanceRef(dataModel().value()).PushLuaValue(Lt);
|
||||
lua_setfield(Lt, -2, "game");
|
||||
|
||||
Data::InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(Lt);
|
||||
InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(Lt);
|
||||
lua_setfield(Lt, -2, "workspace");
|
||||
|
||||
lua_pushlightuserdata(Lt, scriptContext.get());
|
||||
|
@ -117,6 +112,8 @@ int script_delay(lua_State* L) {
|
|||
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||
|
||||
lua_State* Lt = lua_newthread(L); // Create a new thread
|
||||
// I think this is memory abuse??
|
||||
// Wouldn't popping the thread in this case make it eligible for garbage collection?
|
||||
lua_pop(L, 1); // pop the newly created thread so that xmove moves func instead of it into itself
|
||||
lua_xmove(L, Lt, 1); // move func
|
||||
lua_pop(L, 1); // pop secs
|
||||
|
|
|
@ -43,9 +43,9 @@ void ScriptContext::InitService() {
|
|||
// luaopen_debug(state);
|
||||
luaopen_bit(state);
|
||||
|
||||
Data::Vector3::PushLuaLibrary(state);
|
||||
Data::CFrame::PushLuaLibrary(state);
|
||||
Data::Color3::PushLuaLibrary(state);
|
||||
Vector3::PushLuaLibrary(state);
|
||||
CFrame::PushLuaLibrary(state);
|
||||
Color3::PushLuaLibrary(state);
|
||||
|
||||
// TODO: custom os library
|
||||
|
||||
|
@ -94,7 +94,7 @@ void ScriptContext::PushThreadSleep(lua_State* thread, float delay) {
|
|||
}
|
||||
|
||||
void ScriptContext::RunSleepingThreads() {
|
||||
for (int i = 0; i < sleepingThreads.size();) {
|
||||
for (size_t i = 0; i < sleepingThreads.size();) {
|
||||
bool deleted = false;
|
||||
|
||||
SleepingThread sleep = sleepingThreads[i];
|
||||
|
@ -162,6 +162,7 @@ static int g_print(lua_State* L) {
|
|||
const char* str = lua_tostring(L, -1); // convert result into c-string
|
||||
lua_pop(L, 1); // pop result
|
||||
|
||||
if (i > 1) buf += '\t';
|
||||
buf += str;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
// Container class for server scripts
|
||||
// Also handles/manages running server scripts on run
|
||||
class DEF_INST_SERVICE ServerScriptService : public Service {
|
||||
class DEF_INST_SERVICE_(explorer_icon="server-scripts") ServerScriptService : public Service {
|
||||
AUTOGEN_PREAMBLE
|
||||
protected:
|
||||
void InitService() override;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "workspace.h"
|
||||
#include "datatypes/meta.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "datatypes/ref.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "objects/base/instance.h"
|
||||
|
@ -24,7 +24,7 @@ Workspace::~Workspace() {
|
|||
PhysicsEventListener::PhysicsEventListener(Workspace* parent) : workspace(parent) {}
|
||||
|
||||
void PhysicsEventListener::onContact(const rp::CollisionCallback::CallbackData& data) {
|
||||
for (int i = 0; i < data.getNbContactPairs(); i++) {
|
||||
for (size_t i = 0; i < data.getNbContactPairs(); i++) {
|
||||
auto pair = data.getContactPair(i);
|
||||
auto type = pair.getEventType();
|
||||
if (type == rp::CollisionCallback::ContactPair::EventType::ContactStay) continue;
|
||||
|
@ -33,11 +33,11 @@ void PhysicsEventListener::onContact(const rp::CollisionCallback::CallbackData&
|
|||
auto part1 = reinterpret_cast<Part*>(pair.getBody2()->getUserData())->shared<Part>();
|
||||
|
||||
if (type == reactphysics3d::CollisionCallback::ContactPair::EventType::ContactStart) {
|
||||
part0->Touched->Fire({ (Data::Variant)Data::InstanceRef(part1) });
|
||||
part1->Touched->Fire({ (Data::Variant)Data::InstanceRef(part0) });
|
||||
part0->Touched->Fire({ (Variant)InstanceRef(part1) });
|
||||
part1->Touched->Fire({ (Variant)InstanceRef(part0) });
|
||||
} else if (type == reactphysics3d::CollisionCallback::ContactPair::EventType::ContactExit) {
|
||||
part0->TouchEnded->Fire({ (Data::Variant)Data::InstanceRef(part1) });
|
||||
part1->TouchEnded->Fire({ (Data::Variant)Data::InstanceRef(part0) });
|
||||
part0->TouchEnded->Fire({ (Variant)InstanceRef(part1) });
|
||||
part1->TouchEnded->Fire({ (Variant)InstanceRef(part0) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ void Workspace::InitService() {
|
|||
|
||||
// Sync all parts
|
||||
for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) {
|
||||
InstanceRef obj = *it;
|
||||
std::shared_ptr<Instance> obj = *it;
|
||||
if (!obj->IsA<Part>()) continue;
|
||||
std::shared_ptr<Part> part = obj->CastTo<Part>().expect();
|
||||
this->SyncPartPhysics(part);
|
||||
|
@ -68,7 +68,7 @@ void Workspace::InitService() {
|
|||
|
||||
// Activate all joints
|
||||
for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) {
|
||||
InstanceRef obj = *it;
|
||||
std::shared_ptr<Instance> obj = *it;
|
||||
if (!obj->IsA<JointInstance>()) continue;
|
||||
std::shared_ptr<JointInstance> joint = obj->CastTo<JointInstance>().expect();
|
||||
joint->UpdateProperty("Part0");
|
||||
|
@ -84,8 +84,6 @@ void Workspace::InitService() {
|
|||
void Workspace::SyncPartPhysics(std::shared_ptr<Part> part) {
|
||||
if (!physicsWorld) return;
|
||||
|
||||
glm::mat4 rotMat = glm::mat4(1.0f);
|
||||
|
||||
rp::Transform transform = part->cframe;
|
||||
if (!part->rigidBody) {
|
||||
part->rigidBody = physicsWorld->createRigidBody(transform);
|
||||
|
@ -127,17 +125,19 @@ void Workspace::PhysicsStep(float deltaTime) {
|
|||
// Step the simulation a few steps
|
||||
physicsWorld->update(std::min(deltaTime / 2, (1/60.f)));
|
||||
|
||||
// Naive implementation. Parts are only considered so if they are just under Workspace
|
||||
// TODO: Add list of tracked parts in workspace based on their ancestry using inWorkspace property of Instance
|
||||
for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) {
|
||||
InstanceRef obj = *it;
|
||||
if (obj->GetClass()->className != "Part") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly
|
||||
std::shared_ptr<Instance> obj = *it;
|
||||
if (!obj->IsA<Part>()) continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(obj);
|
||||
|
||||
// Sync properties
|
||||
const rp::Transform& transform = part->rigidBody->getTransform();
|
||||
part->cframe = CFrame(transform);
|
||||
part->velocity = part->rigidBody->getLinearVelocity();
|
||||
|
||||
// part->rigidBody->enableGravity(true);
|
||||
// RotateV/Motor joint
|
||||
for (auto& joint : part->secondaryJoints) {
|
||||
if (joint.expired() || !joint.lock()->IsA("RotateV")) continue;
|
||||
|
||||
|
@ -146,6 +146,16 @@ void Workspace::PhysicsStep(float deltaTime) {
|
|||
// part->rigidBody->enableGravity(false);
|
||||
part->rigidBody->setAngularVelocity(-(motor->part0.lock()->cframe * motor->c0).LookVector() * rate);
|
||||
}
|
||||
|
||||
// Destroy fallen parts
|
||||
if (part->cframe.Position().Y() < this->fallenPartsDestroyHeight) {
|
||||
auto parent = part->GetParent();
|
||||
part->Destroy();
|
||||
|
||||
// If the parent of the part is a Model, destroy it too
|
||||
if (parent.has_value() && parent.value()->IsA("Model"))
|
||||
parent.value()->Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@ public:
|
|||
Workspace();
|
||||
~Workspace();
|
||||
|
||||
DEF_PROP float fallenPartsDestroyHeight = -500;
|
||||
|
||||
// static inline std::shared_ptr<Workspace> New() { return std::make_shared<Workspace>(); };
|
||||
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Workspace>(); };
|
||||
|
||||
|
|
100
core/src/partassembly.cpp
Normal file
100
core/src/partassembly.cpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
#include "partassembly.h"
|
||||
#include "common.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "math_helper.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/part.h"
|
||||
#include <glm/common.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
PartAssembly::PartAssembly(std::vector<std::shared_ptr<Part>> parts, bool worldMode) : parts(parts) {
|
||||
if (parts.size() == 0) return;
|
||||
if (parts.size() == 1 && !worldMode) {
|
||||
_assemblyOrigin = parts[0]->cframe;
|
||||
_bounds = parts[0]->size;
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 min = parts[0]->position(), max = parts[0]->position();
|
||||
|
||||
for (auto part : parts) {
|
||||
Vector3 aabbSize = part->GetAABB();
|
||||
expandAABB(min, max, part->position() - aabbSize / 2.f);
|
||||
expandAABB(min, max, part->position() + aabbSize / 2.f);
|
||||
}
|
||||
|
||||
glm::vec3 pos, size;
|
||||
getAABBCoords(pos, size, min, max);
|
||||
|
||||
_assemblyOrigin = CFrame() + pos;
|
||||
_bounds = size;
|
||||
}
|
||||
|
||||
PartAssembly PartAssembly::FromSelection(std::vector<std::shared_ptr<Instance>> newSelection) {
|
||||
std::vector<std::shared_ptr<Part>> selection;
|
||||
|
||||
for (std::weak_ptr<Instance> obj : newSelection) {
|
||||
if (obj.expired() || !obj.lock()->IsA<Part>()) continue;
|
||||
|
||||
selection.push_back(obj.lock()->CastTo<Part>().expect());
|
||||
}
|
||||
|
||||
return PartAssembly(selection, editorToolHandles.worldMode);
|
||||
}
|
||||
|
||||
void PartAssembly::SetOrigin(CFrame newOrigin) {
|
||||
for (auto part : parts) {
|
||||
part->cframe = newOrigin * (_assemblyOrigin.Inverse() * part->cframe);
|
||||
part->UpdateProperty("CFrame");
|
||||
// sendPropertyUpdatedSignal(part, "CFrame", Variant(part->cframe));
|
||||
}
|
||||
|
||||
_assemblyOrigin = newOrigin;
|
||||
}
|
||||
|
||||
void PartAssembly::TransformBy(CFrame transform) {
|
||||
for (auto part : parts) {
|
||||
part->cframe = transform * part->cframe;
|
||||
part->UpdateProperty("CFrame");
|
||||
part->UpdateProperty("Position");
|
||||
part->UpdateProperty("Rotation");
|
||||
sendPropertyUpdatedSignal(part, "CFrame", Variant(part->cframe));
|
||||
sendPropertyUpdatedSignal(part, "Position", Variant(part->cframe));
|
||||
sendPropertyUpdatedSignal(part, "Rotation", Variant(part->cframe));
|
||||
}
|
||||
|
||||
_assemblyOrigin = transform * _assemblyOrigin;
|
||||
}
|
||||
|
||||
void PartAssembly::Scale(Vector3 newSize, bool scaleUp) {
|
||||
if (parts.size() == 1) {
|
||||
parts[0]->size = newSize;
|
||||
parts[0]->UpdateProperty("Size");
|
||||
sendPropertyUpdatedSignal(parts[0], "Size", Variant(parts[0]->size));
|
||||
_bounds = newSize;
|
||||
return;
|
||||
}
|
||||
|
||||
float sx = newSize.X() / _bounds.X(), sy = newSize.Y() / _bounds.Y(), sz = newSize.Z() / _bounds.Z();
|
||||
float factor = scaleUp ? glm::max(sx, sy, sz) : glm::min(sx, sy, sz);
|
||||
|
||||
for (auto part : parts) {
|
||||
Vector3 localOff = _assemblyOrigin.Inverse() * part->cframe.Position();
|
||||
localOff = localOff * factor;
|
||||
part->cframe = part->cframe.Rotation() + _assemblyOrigin * localOff;
|
||||
part->UpdateProperty("CFrame");
|
||||
part->UpdateProperty("Position");
|
||||
part->UpdateProperty("Rotation");
|
||||
sendPropertyUpdatedSignal(part, "CFrame", Variant(part->cframe));
|
||||
sendPropertyUpdatedSignal(part, "Position", Variant(part->cframe));
|
||||
sendPropertyUpdatedSignal(part, "Rotation", Variant(part->cframe));
|
||||
part->size *= factor;
|
||||
part->UpdateProperty("Size");
|
||||
sendPropertyUpdatedSignal(part, "Size", Variant(part->size));
|
||||
}
|
||||
|
||||
_bounds = _bounds * factor;
|
||||
}
|
35
core/src/partassembly.h
Normal file
35
core/src/partassembly.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "datatypes/cframe.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class Part;
|
||||
class Instance;
|
||||
|
||||
const std::vector<std::shared_ptr<Instance>> getSelection();
|
||||
|
||||
class PartAssembly {
|
||||
CFrame _assemblyOrigin;
|
||||
Vector3 _bounds;
|
||||
|
||||
std::vector<std::shared_ptr<Part>> parts;
|
||||
public:
|
||||
PartAssembly(std::vector<std::shared_ptr<Part>>, bool worldMode = false);
|
||||
|
||||
static PartAssembly FromSelection(std::vector<std::shared_ptr<Instance>> selection = getSelection());
|
||||
|
||||
inline CFrame assemblyOrigin() { return _assemblyOrigin; };
|
||||
inline Vector3 bounds() { return _bounds; };
|
||||
|
||||
// Transforms the assembly such that newOrigin is now this assembly's new assemblyOrigin
|
||||
void SetOrigin(CFrame newOrigin);
|
||||
|
||||
// Rotates and translates the assembly by the transformation
|
||||
void TransformBy(CFrame transform);
|
||||
|
||||
// Scales the assembly to the desired size
|
||||
// If multiple parts are selected, finds the greatest scale factor of each component pair, and
|
||||
// scales it up by that amount
|
||||
void Scale(Vector3 newSize, bool scaleUp = true);
|
||||
};
|
|
@ -4,7 +4,7 @@
|
|||
#include "panic.h"
|
||||
|
||||
// GNU/Linux implementation
|
||||
#if defined(_POSIX_VERSION) || defined(__linux) || defined(__linux__)
|
||||
#if defined(_POSIX_VERSION) || defined(__linux) || defined(__linux__) || defined(__unix__)
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
|
||||
template <typename T>
|
||||
bool operator ==(std::optional<std::weak_ptr<T>> a, std::optional<std::weak_ptr<T>> b) {
|
||||
return (!a.has_value() || a.value().expired()) && (!b.has_value() || b.value().expired())
|
||||
|| (a.has_value() && !a.value().expired()) && (b.has_value() && !b.value().expired()) && a.value().lock() == b.value().lock();
|
||||
return ((!a.has_value() || a.value().expired()) && (!b.has_value() || b.value().expired()))
|
||||
|| ((a.has_value() && !a.value().expired()) && (b.has_value() && !b.value().expired()) && a.value().lock() == b.value().lock());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator ==(std::weak_ptr<T> a, std::weak_ptr<T> b) {
|
||||
return a.expired() && b.expired() || (!a.expired() && !b.expired() && a.lock() == b.lock());
|
||||
return (a.expired() && b.expired()) || (!a.expired() && !b.expired() && a.lock() == b.lock());
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
#include "defaultmeshes.h"
|
||||
|
||||
#pragma warning( disable : 4305 )
|
||||
|
||||
static float CUBE_VERTICES[] = {
|
||||
// positions // normals // texture coords
|
||||
0.5, -0.5, -0.5, -0.0, -0.0, -1.0, 1.0, 0.0,
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/color3.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "handles.h"
|
||||
#include "math_helper.h"
|
||||
#include "partassembly.h"
|
||||
#include "rendering/torus.h"
|
||||
#include "shader.h"
|
||||
#include "mesh.h"
|
||||
|
@ -26,7 +28,7 @@
|
|||
#include "../common.h"
|
||||
#include "../objects/part.h"
|
||||
#include "skybox.h"
|
||||
#include "surface.h"
|
||||
#include "enum/surface.h"
|
||||
#include "texture3d.h"
|
||||
|
||||
#include "renderer.h"
|
||||
|
@ -50,8 +52,6 @@ void renderInit(GLFWwindow* window, int width, int height) {
|
|||
viewportWidth = width, viewportHeight = height;
|
||||
glViewport(0, 0, width, height);
|
||||
|
||||
int argc = 1;
|
||||
char* argv = const_cast<char*>("");
|
||||
initMeshes();
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
@ -131,7 +131,7 @@ void renderParts() {
|
|||
// Sort by nearest
|
||||
std::map<float, std::shared_ptr<Part>> sorted;
|
||||
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
|
||||
InstanceRef inst = *it;
|
||||
std::shared_ptr<Instance> inst = *it;
|
||||
if (inst->GetClass()->className != "Part") continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
if (part->transparency > 0.00001) {
|
||||
|
@ -140,7 +140,7 @@ void renderParts() {
|
|||
} else {
|
||||
glm::mat4 model = part->cframe;
|
||||
// if (part->name == "camera") model = camera.getLookAt();
|
||||
model = glm::scale(model, part->size);
|
||||
model = glm::scale(model, (glm::vec3)part->size);
|
||||
shader->set("model", model);
|
||||
shader->set("material", Material {
|
||||
.diffuse = part->color,
|
||||
|
@ -152,12 +152,12 @@ void renderParts() {
|
|||
shader->set("texScale", part->size);
|
||||
shader->set("transparency", part->transparency);
|
||||
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", part->rightSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", part->topSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", part->backSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", part->leftSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", part->bottomSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", part->frontSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", (int)part->rightSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", (int)part->topSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", (int)part->backSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", (int)part->leftSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", (int)part->bottomSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", (int)part->frontSurface);
|
||||
|
||||
CUBE_MESH->bind();
|
||||
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
|
||||
|
@ -170,7 +170,7 @@ void renderParts() {
|
|||
std::shared_ptr<Part> part = it->second;
|
||||
glm::mat4 model = part->cframe;
|
||||
// if (part->name == "camera") model = camera.getLookAt();
|
||||
model = glm::scale(model, part->size);
|
||||
model = glm::scale(model, (glm::vec3)part->size);
|
||||
shader->set("model", model);
|
||||
shader->set("material", Material {
|
||||
.diffuse = part->color,
|
||||
|
@ -182,12 +182,12 @@ void renderParts() {
|
|||
shader->set("texScale", part->size);
|
||||
shader->set("transparency", part->transparency);
|
||||
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", part->rightSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", part->topSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", part->backSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", part->leftSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", part->bottomSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", part->frontSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", (int)part->rightSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", (int)part->topSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", (int)part->backSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", (int)part->leftSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", (int)part->bottomSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", (int)part->frontSurface);
|
||||
|
||||
CUBE_MESH->bind();
|
||||
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
|
||||
|
@ -225,20 +225,19 @@ void renderSurfaceExtras() {
|
|||
ghostShader->set("viewPos", camera.cameraPos);
|
||||
|
||||
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
|
||||
InstanceRef inst = *it;
|
||||
std::shared_ptr<Instance> inst = *it;
|
||||
if (!inst->IsA("Part")) continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
NormalId face = (NormalId)i;
|
||||
SurfaceType type = part->GetSurfaceFromFace(face);
|
||||
if (type <= SurfaceType::SurfaceUniversal) continue;
|
||||
if (type <= SurfaceType::Universal) continue;
|
||||
|
||||
Vector3 surfaceCenter = part->cframe * (normalFromFace(face) * part->size / 2.f);
|
||||
|
||||
glm::mat4 model = CFrame::pointToward(surfaceCenter, part->cframe.Rotation() * normalFromFace(face));
|
||||
model = glm::scale(model, glm::vec3(0.4,0.4,0.4));
|
||||
ghostShader->set("model", model);
|
||||
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
|
||||
|
||||
CYLINDER_MESH->bind();
|
||||
glDrawArrays(GL_TRIANGLES, 0, CYLINDER_MESH->vertexCount);
|
||||
|
@ -271,6 +270,9 @@ static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0,
|
|||
void renderHandles() {
|
||||
if (!editorToolHandles.active) return;
|
||||
|
||||
auto assembly = PartAssembly::FromSelection();
|
||||
if (assembly.bounds() == Vector3::ZERO) return;
|
||||
|
||||
glDepthMask(GL_TRUE);
|
||||
glCullFace(GL_BACK);
|
||||
glFrontFace(GL_CCW); // This is right... Probably.....
|
||||
|
@ -365,7 +367,7 @@ void renderAABB() {
|
|||
ghostShader->set("color", glm::vec3(1.f, 0.f, 0.f));
|
||||
|
||||
// Sort by nearest
|
||||
for (InstanceRef inst : gWorkspace()->GetChildren()) {
|
||||
for (std::shared_ptr<Instance> inst : gWorkspace()->GetChildren()) {
|
||||
if (inst->GetClass()->className != "Part") continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
glm::mat4 model = CFrame::IDENTITY + part->cframe.Position();
|
||||
|
@ -405,7 +407,7 @@ void renderWireframe() {
|
|||
wireframeShader->set("color", glm::vec3(1.f, 0.f, 0.f));
|
||||
|
||||
// Sort by nearest
|
||||
for (InstanceRef inst : gWorkspace()->GetChildren()) {
|
||||
for (std::shared_ptr<Instance> inst : gWorkspace()->GetChildren()) {
|
||||
if (inst->GetClass()->className != "Part") continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
glm::mat4 model = part->cframe;
|
||||
|
@ -442,17 +444,19 @@ void renderOutlines() {
|
|||
outlineShader->set("viewPos", camera.cameraPos);
|
||||
outlineShader->set("thickness", 0.4f);
|
||||
|
||||
// outlineShader->set("color", glm::vec3(1.f, 0.f, 0.f));
|
||||
outlineShader->set("color", glm::vec3(0.204, 0.584, 0.922));
|
||||
|
||||
glm::vec3 min, max;
|
||||
bool first = true;
|
||||
int count = 0;
|
||||
|
||||
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
|
||||
InstanceRef inst = *it;
|
||||
std::shared_ptr<Instance> inst = *it;
|
||||
if (inst->GetClass() != &Part::TYPE) continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
if (!part->selected) continue;
|
||||
|
||||
count++;
|
||||
if (first)
|
||||
min = part->position(), max = part->position();
|
||||
first = false;
|
||||
|
@ -471,7 +475,7 @@ void renderOutlines() {
|
|||
}
|
||||
|
||||
// Render AABB of selected parts
|
||||
if (first) return;
|
||||
if (count <= 1) return;
|
||||
|
||||
glm::vec3 outlineSize, outlinePos;
|
||||
outlineSize = (max - min);
|
||||
|
@ -487,6 +491,41 @@ void renderOutlines() {
|
|||
glDrawArrays(GL_TRIANGLES, 0, OUTLINE_MESH->vertexCount);
|
||||
}
|
||||
|
||||
void renderSelectionAssembly() {
|
||||
glDepthMask(GL_TRUE);
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_BACK);
|
||||
glFrontFace(GL_CCW);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
PartAssembly selectionAssembly = PartAssembly::FromSelection();
|
||||
|
||||
// Use shader
|
||||
outlineShader->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();
|
||||
outlineShader->set("projection", projection);
|
||||
outlineShader->set("view", view);
|
||||
|
||||
// Pass in the camera position
|
||||
outlineShader->set("viewPos", camera.cameraPos);
|
||||
outlineShader->set("thickness", 0.4f);
|
||||
|
||||
outlineShader->set("color", glm::vec3(1.f, 0.f, 0.f));
|
||||
|
||||
glm::mat4 model = selectionAssembly.assemblyOrigin();
|
||||
model = glm::scale(model, (glm::vec3)selectionAssembly.bounds() + glm::vec3(0.1));
|
||||
outlineShader->set("model", model);
|
||||
outlineShader->set("scale", (glm::vec3)selectionAssembly.bounds() + glm::vec3(0.05));
|
||||
outlineShader->set("thickness", 0.2f);
|
||||
|
||||
OUTLINE_MESH->bind();
|
||||
glDrawArrays(GL_TRIANGLES, 0, OUTLINE_MESH->vertexCount);
|
||||
}
|
||||
|
||||
void renderRotationArcs() {
|
||||
if (!editorToolHandles.active || editorToolHandles.handlesType != HandlesType::RotateHandles) return;
|
||||
|
||||
|
@ -517,14 +556,14 @@ void renderRotationArcs() {
|
|||
// Pass in the camera position
|
||||
handleShader->set("viewPos", camera.cameraPos);
|
||||
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(getSelection()[0].lock());
|
||||
PartAssembly assembly = PartAssembly::FromSelection();
|
||||
|
||||
for (HandleFace face : HandleFace::Faces) {
|
||||
if (glm::any(glm::lessThan(face.normal, glm::vec3(0)))) continue;
|
||||
glm::mat4 model = part->cframe * CFrame(glm::vec3(0), face.normal, glm::vec3(0, 1.01, 0.1));
|
||||
glm::mat4 model = assembly.assemblyOrigin() * CFrame(glm::vec3(0), face.normal, glm::vec3(0, 1.01, 0.1));
|
||||
handleShader->set("model", model);
|
||||
|
||||
float radius = glm::max(part->size.x, part->size.y, part->size.z) / 2.f + 2.f;
|
||||
float radius = glm::max(assembly.bounds().X(), assembly.bounds().Y(), assembly.bounds().Z()) / 2.f + 2.f;
|
||||
|
||||
handleShader->set("material", Material {
|
||||
.diffuse = glm::abs(face.normal),
|
||||
|
@ -599,6 +638,7 @@ void render(GLFWwindow* window) {
|
|||
renderParts();
|
||||
renderSurfaceExtras();
|
||||
renderOutlines();
|
||||
// renderSelectionAssembly();
|
||||
renderRotationArcs();
|
||||
if (wireframeRendering)
|
||||
renderWireframe();
|
||||
|
|
|
@ -8,5 +8,5 @@ namespace Data { class CFrame; class Color3; };
|
|||
void renderInit(GLFWwindow* window, int width, int height);
|
||||
void render(GLFWwindow* window);
|
||||
void setViewport(int width, int height);
|
||||
void addDebugRenderCFrame(Data::CFrame);
|
||||
void addDebugRenderCFrame(Data::CFrame, Data::Color3);
|
||||
void addDebugRenderCFrame(CFrame);
|
||||
void addDebugRenderCFrame(CFrame, Color3);
|
|
@ -1,25 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
enum NormalId {
|
||||
Right = 0,
|
||||
Top = 1,
|
||||
Back = 2,
|
||||
Left = 3,
|
||||
Bottom = 4,
|
||||
Front = 5
|
||||
};
|
||||
|
||||
enum SurfaceType {
|
||||
SurfaceSmooth = 0,
|
||||
SurfaceGlue = 1,
|
||||
SurfaceWeld = 2,
|
||||
SurfaceStuds = 3,
|
||||
SurfaceInlets = 4,
|
||||
SurfaceUniversal = 5,
|
||||
SurfaceHinge = 6,
|
||||
SurfaceMotor = 7,
|
||||
};
|
||||
|
||||
namespace Data { class Vector3; } using Data::Vector3;
|
||||
NormalId faceFromNormal(Vector3);
|
||||
Vector3 normalFromFace(NormalId);
|
15
docs/qscintilla.md
Normal file
15
docs/qscintilla.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
In order to build openblocks on Windows, qscintilla will need to already be installed.
|
||||
|
||||
To do this, first download the source archive from [`https://www.riverbankcomputing.com/static/Downloads/QScintilla/2.14.1/QScintilla_src-2.14.1.tar.gz`](https://www.riverbankcomputing.com/static/Downloads/QScintilla/2.14.1/QScintilla_src-2.14.1.tar.gz)
|
||||
|
||||
Next, launch the *x64 Native Tools Command Prompt for VS 2022*, and cd into the directory that you extracted the archive to
|
||||
|
||||
Now, run `qmake` from your Qt's bin directory to configure it
|
||||
|
||||
Once that's done, build and install the project using `nmake install`
|
||||
|
||||
The library should now automatically be installed into your Qt installed directory
|
||||
|
||||
---
|
||||
|
||||
To uninstall the library, run `nmake uninstall`
|
|
@ -69,6 +69,7 @@ endif()
|
|||
|
||||
target_include_directories(editor PUBLIC "../core/src" "../include" ${QSCINTILLA_INCLUDE_DIR})
|
||||
target_link_libraries(editor PRIVATE openblocks Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Multimedia ${QSCINTILLA_LIBRARY})
|
||||
add_dependencies(editor openblocks)
|
||||
|
||||
# Qt6 does not include QOpenGLWidgets as part of Widgets base anymore, so
|
||||
# we have to include it manually
|
||||
|
@ -99,6 +100,13 @@ if (WIN32)
|
|||
# No sense adding opengl-sw given that hardware acceleration is necessary, anyway
|
||||
# Also don't want to clutter with plugins, add only needed ones
|
||||
|
||||
# Copy over QScintilla DLLs
|
||||
# TODO: Use a better approach?
|
||||
add_custom_command(
|
||||
TARGET editor POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${QSCINTILLA_DLLS} $<TARGET_FILE_DIR:editor>
|
||||
)
|
||||
|
||||
# Copy qt.conf to override default plugins location
|
||||
add_custom_command(
|
||||
TARGET editor POST_BUILD
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "error/data.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include "logger.h"
|
||||
|
|
|
@ -4,23 +4,27 @@
|
|||
#include <qnamespace.h>
|
||||
#include <qsoundeffect.h>
|
||||
#include <string>
|
||||
#include "./ui_mainwindow.h"
|
||||
#include "mainglwidget.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "handles.h"
|
||||
#include "logger.h"
|
||||
#include "mainwindow.h"
|
||||
#include "common.h"
|
||||
#include "math_helper.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "partassembly.h"
|
||||
#include "physics/util.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "rendering/shader.h"
|
||||
#include "datatypes/meta.h"
|
||||
#include "datatypes/variant.h"
|
||||
|
||||
#define PI 3.14159
|
||||
#define M_mainWindow dynamic_cast<MainWindow*>(window())
|
||||
|
||||
static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
|
||||
|
||||
MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent) {
|
||||
MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent), contextMenu(this) {
|
||||
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
@ -113,12 +117,14 @@ CFrame snapCFrame(CFrame frame) {
|
|||
return CFrame(frame.Position(), frame.Position() + closestVec1, closestVec2);
|
||||
}
|
||||
|
||||
bool tryMouseContextMenu = false;
|
||||
bool isMouseDragging = false;
|
||||
std::weak_ptr<Part> draggingObject;
|
||||
std::optional<HandleFace> draggingHandle;
|
||||
Vector3 initialHitPos;
|
||||
Vector3 initialHitNormal;
|
||||
CFrame initialFrame;
|
||||
PartAssembly initialAssembly({});
|
||||
void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
|
||||
if (!isMouseDragging || draggingObject.expired() || mainWindow()->selectedTool >= TOOL_SMOOTH) return;
|
||||
|
||||
|
@ -171,6 +177,7 @@ inline glm::vec3 vec3fy(glm::vec4 vec) {
|
|||
}
|
||||
|
||||
// Taken from Godot's implementation of moving handles (godot/editor/plugins/gizmos/gizmo_3d_helper.cpp)
|
||||
Vector3 dragStartHandleOffset;
|
||||
void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
|
||||
if (!isMouseDragging || !draggingHandle|| !editorToolHandles.active) return;
|
||||
|
||||
|
@ -183,7 +190,9 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
|
|||
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
|
||||
pointDir = glm::normalize(pointDir);
|
||||
|
||||
CFrame handleCFrame = getHandleCFrame(draggingHandle.value());
|
||||
// We use lastDragStartPos instead to consider the mouse's actual position, rather than the center
|
||||
// of the handle. That way, transformations are "smoother" and do not jump the first movement
|
||||
CFrame handleCFrame = getHandleCFrame(draggingHandle.value()) + dragStartHandleOffset;
|
||||
|
||||
// Current frame. Identity frame if worldMode == true, selected object's frame if worldMode == false
|
||||
CFrame frame = editorToolHandles.worldMode ? CFrame::IDENTITY + part->position() : part->cframe.Rotation();
|
||||
|
@ -200,60 +209,64 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
|
|||
glm::vec3 handlePoint, rb;
|
||||
get_closest_points_between_segments(axisSegment0, axisSegment1, mouseSegment0, mouseSegment1, handlePoint, rb);
|
||||
|
||||
// Find new part position
|
||||
glm::vec3 centerPoint = partCFrameFromHandlePos(draggingHandle.value(), handlePoint).Position();
|
||||
// We transform the handlePoint to the handle's cframe, and get it's length (Z)
|
||||
float diff = (handleCFrame.Inverse() * handlePoint).Z();
|
||||
// Vector3 absDiff = ((Vector3)handlePoint - handleCFrame.Position()); // Commented out because it is functionally identical to the below
|
||||
Vector3 absDiff = handleCFrame.Rotation() * Vector3(0, 0, diff);
|
||||
|
||||
// Apply snapping in the current frame
|
||||
glm::vec3 diff = centerPoint - (glm::vec3)part->position();
|
||||
if (snappingFactor()) diff = frame.Rotation() * (glm::round(glm::vec3(frame.Inverse().Rotation() * diff) / snappingFactor()) * snappingFactor());
|
||||
|
||||
Vector3 oldSize = part->size;
|
||||
|
||||
switch (mainWindow()->selectedTool) {
|
||||
case TOOL_MOVE: {
|
||||
// Add difference
|
||||
part->cframe = part->cframe + diff;
|
||||
} break;
|
||||
|
||||
case TOOL_SCALE: {
|
||||
// Find local difference
|
||||
glm::vec3 localDiff = frame.Inverse() * diff;
|
||||
// Find outwarwd difference
|
||||
localDiff = localDiff * glm::sign(draggingHandle->normal);
|
||||
|
||||
// Special case: minimum size to size mod snapping factor
|
||||
if (snappingFactor() > 0 && glm::all(glm::lessThan((part->size + localDiff) * glm::abs(draggingHandle->normal), glm::vec3(0.01f)))) {
|
||||
// I tried something fancy here, but honestly I'm not smart enough. Return;
|
||||
// glm::vec3 finalSize = part->size + localDiff;
|
||||
// finalSize = glm::mod(finalSize * glm::abs(draggingHandle->normal), snappingFactor()) + finalSize * (glm::vec3(1) - glm::abs(draggingHandle->normal));
|
||||
// localDiff = finalSize - part->size;
|
||||
return;
|
||||
}
|
||||
|
||||
// Minimum size of 0.01f
|
||||
localDiff = glm::max(part->size + localDiff, 0.01f) - part->size;
|
||||
diff = frame * (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;
|
||||
|
||||
default:
|
||||
Logger::error("Invalid tool was set to be handled by handleLinearTransform\n");
|
||||
// Apply snapping
|
||||
if (snappingFactor() > 0) {
|
||||
diff = round(diff / snappingFactor()) * snappingFactor();
|
||||
absDiff = handleCFrame.Rotation() * Vector3(0, 0, diff);
|
||||
}
|
||||
|
||||
if (snappingFactor() != 0 && mainWindow()->editSoundEffects && (oldSize != part->size) && QFile::exists("./assets/excluded/switch.wav"))
|
||||
playSound("./assets/excluded/switch.wav");
|
||||
PartAssembly selectionAssembly = PartAssembly::FromSelection();
|
||||
|
||||
gWorkspace()->SyncPartPhysics(part);
|
||||
part->UpdateProperty("Position");
|
||||
part->UpdateProperty("Size");
|
||||
sendPropertyUpdatedSignal(part, "Position", part->position());
|
||||
sendPropertyUpdatedSignal(part, "Size", Vector3(part->size));
|
||||
if (editorToolHandles.handlesType == MoveHandles) {
|
||||
selectionAssembly.TransformBy(CFrame() + absDiff);
|
||||
} else if (editorToolHandles.handlesType == ScaleHandles) {
|
||||
if (evt->modifiers() & Qt::AltModifier) {
|
||||
// If size gets too small, don't
|
||||
if (glm::any(glm::lessThan(glm::vec3(selectionAssembly.bounds() + abs(draggingHandle->normal) * diff * 2.f), glm::vec3(0.001f))))
|
||||
return;
|
||||
|
||||
selectionAssembly.Scale(selectionAssembly.bounds() + abs(draggingHandle->normal) * diff * 2.f, diff > 0);
|
||||
} else {
|
||||
// If size gets too small, don't
|
||||
if (glm::any(glm::lessThan(glm::vec3(selectionAssembly.bounds() + abs(draggingHandle->normal) * diff), glm::vec3(0.001f))))
|
||||
return;
|
||||
|
||||
selectionAssembly.TransformBy(CFrame() + absDiff * 0.5f);
|
||||
selectionAssembly.Scale(selectionAssembly.bounds() + abs(draggingHandle->normal) * diff, diff > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainGLWidget::startLinearTransform(QMouseEvent* evt) {
|
||||
if (!editorToolHandles.active) return;
|
||||
|
||||
QPoint position = evt->pos();
|
||||
|
||||
// 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);
|
||||
|
||||
CFrame handleCFrame = getHandleCFrame(draggingHandle.value());
|
||||
|
||||
// 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);
|
||||
|
||||
dragStartHandleOffset = (Vector3)handlePoint - handleCFrame.Position();
|
||||
}
|
||||
|
||||
// Also implemented based on Godot: [c7ea8614](godot/editor/plugins/canvas_item_editor_plugin.cpp#L1490)
|
||||
|
@ -262,7 +275,6 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
|
|||
if (!isMouseDragging || !draggingHandle || !editorToolHandles.active) return;
|
||||
|
||||
glm::vec2 destPoint = glm::vec2(evt->pos().x(), evt->pos().y());
|
||||
auto part = getHandleAdornee();
|
||||
|
||||
// Calculate part pos as screen point
|
||||
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)width() / (float)height(), 0.1f, 1000.0f);
|
||||
|
@ -294,11 +306,8 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
|
|||
|
||||
glm::vec3 angles = handleNormal * sign * glm::vec3(angle);
|
||||
|
||||
part->cframe = initialFrame * CFrame::FromEulerAnglesXYZ(-angles);
|
||||
|
||||
gWorkspace()->SyncPartPhysics(part);
|
||||
part->UpdateProperty("Rotation");
|
||||
sendPropertyUpdatedSignal(part, "Rotation", part->cframe.ToEulerAnglesXYZ());
|
||||
CFrame newFrame = initialFrame * CFrame::FromEulerAnglesXYZ(-angles);
|
||||
initialAssembly.SetOrigin(newFrame);
|
||||
}
|
||||
|
||||
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
|
||||
|
@ -333,6 +342,7 @@ void MainGLWidget::wheelEvent(QWheelEvent* evt) {
|
|||
}
|
||||
|
||||
void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
|
||||
tryMouseContextMenu = false;
|
||||
handleCameraRotate(evt);
|
||||
handleObjectDrag(evt);
|
||||
handleCursorChange(evt);
|
||||
|
@ -351,6 +361,7 @@ void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
|
|||
}
|
||||
|
||||
void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
||||
tryMouseContextMenu = evt->button() == Qt::RightButton;
|
||||
switch(evt->button()) {
|
||||
// Camera drag
|
||||
case Qt::RightButton: {
|
||||
|
@ -366,17 +377,19 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
|||
auto handle = raycastHandle(pointDir);
|
||||
if (handle.has_value()) {
|
||||
startPoint = glm::vec2(evt->pos().x(), evt->pos().y());
|
||||
initialFrame = getHandleAdornee()->cframe;
|
||||
initialAssembly = PartAssembly::FromSelection();
|
||||
initialFrame = initialAssembly.assemblyOrigin();
|
||||
isMouseDragging = true;
|
||||
draggingHandle = handle;
|
||||
startLinearTransform(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
// raycast part
|
||||
std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000);
|
||||
if (!rayHit || !partFromBody(rayHit->body)) return;
|
||||
if (!rayHit || !partFromBody(rayHit->body)) { setSelection({}); return; }
|
||||
std::shared_ptr<Part> part = partFromBody(rayHit->body);
|
||||
if (part->locked) return;
|
||||
if (part->locked) { setSelection({}); return; }
|
||||
initialFrame = part->cframe;
|
||||
initialHitPos = rayHit->worldPoint;
|
||||
initialHitNormal = rayHit->worldNormal;
|
||||
|
@ -407,10 +420,10 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
|||
isMouseDragging = true;
|
||||
draggingObject = part;
|
||||
if (evt->modifiers() & Qt::ControlModifier) {
|
||||
std::vector<InstanceRefWeak> currentSelection = getSelection();
|
||||
for (int i = 0; i < currentSelection.size(); i++) {
|
||||
InstanceRefWeak inst = currentSelection[i];
|
||||
if (!inst.expired() && inst.lock() == part) {
|
||||
std::vector<std::shared_ptr<Instance>> currentSelection = getSelection();
|
||||
for (size_t i = 0; i < currentSelection.size(); i++) {
|
||||
std::shared_ptr<Instance> inst = currentSelection[i];
|
||||
if (inst == part) {
|
||||
currentSelection.erase(currentSelection.begin() + i);
|
||||
goto skipAddPart;
|
||||
}
|
||||
|
@ -418,8 +431,8 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
|||
currentSelection.push_back(part);
|
||||
skipAddPart:
|
||||
setSelection(currentSelection);
|
||||
}else
|
||||
setSelection(std::vector<InstanceRefWeak> { part });
|
||||
} else
|
||||
setSelection({ part });
|
||||
// Disable bit so that we can ignore the part while raycasting
|
||||
// part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10);
|
||||
|
||||
|
@ -435,6 +448,23 @@ void MainGLWidget::mouseReleaseEvent(QMouseEvent* evt) {
|
|||
isMouseDragging = false;
|
||||
draggingObject = {};
|
||||
draggingHandle = std::nullopt;
|
||||
|
||||
// Open context menu
|
||||
if (tryMouseContextMenu)
|
||||
contextMenu.exec(QCursor::pos());
|
||||
tryMouseContextMenu = false;
|
||||
}
|
||||
|
||||
void MainGLWidget::buildContextMenu() {
|
||||
contextMenu.addAction(M_mainWindow->ui->actionDelete);
|
||||
contextMenu.addSeparator();
|
||||
contextMenu.addAction(M_mainWindow->ui->actionCopy);
|
||||
contextMenu.addAction(M_mainWindow->ui->actionCut);
|
||||
contextMenu.addAction(M_mainWindow->ui->actionPaste);
|
||||
contextMenu.addAction(M_mainWindow->ui->actionPasteInto);
|
||||
contextMenu.addSeparator();
|
||||
contextMenu.addAction(M_mainWindow->ui->actionSaveModel);
|
||||
contextMenu.addAction(M_mainWindow->ui->actionInsertModel);
|
||||
}
|
||||
|
||||
static int moveZ = 0;
|
||||
|
@ -478,11 +508,11 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
|
|||
if (evt->key() == Qt::Key_O)
|
||||
Logger::error("error message");
|
||||
|
||||
if (evt->key() == Qt::Key_C && getSelection().size() > 0 && !getSelection()[0].expired())
|
||||
getSelection()[0].lock()->Clone().value()->SetParent(gWorkspace());
|
||||
if (evt->key() == Qt::Key_C && getSelection().size() > 0)
|
||||
getSelection()[0]->Clone().value()->SetParent(gWorkspace());
|
||||
|
||||
if (evt->key() == Qt::Key_H && getSelection().size() > 0 && !getSelection()[0].expired())
|
||||
Logger::infof("Object at: 0x%x\n", getSelection()[0].lock().get());
|
||||
if (evt->key() == Qt::Key_H && getSelection().size() > 0)
|
||||
Logger::infof("Object at: 0x%x\n", getSelection()[0].get());
|
||||
}
|
||||
|
||||
void MainGLWidget::keyReleaseEvent(QKeyEvent* evt) {
|
||||
|
@ -501,4 +531,4 @@ float MainGLWidget::snappingFactor() {
|
|||
case GridSnappingMode::SNAP_OFF: return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
#include <QOpenGLWidget>
|
||||
#include <QWidget>
|
||||
#include <memory>
|
||||
#include <qmenu.h>
|
||||
|
||||
class HandleFace;
|
||||
class MainWindow;
|
||||
|
@ -16,6 +17,7 @@ public:
|
|||
void updateCycle();
|
||||
std::shared_ptr<Part> lastPart;
|
||||
|
||||
void buildContextMenu();
|
||||
protected:
|
||||
void initializeGL() override;
|
||||
void resizeGL(int w, int h) override;
|
||||
|
@ -26,6 +28,7 @@ protected:
|
|||
void handleLinearTransform(QMouseEvent* evt);
|
||||
void handleRotationalTransform(QMouseEvent* evt);
|
||||
void handleCursorChange(QMouseEvent* evt);
|
||||
void startLinearTransform(QMouseEvent* evt);
|
||||
std::optional<HandleFace> raycastHandle(glm::vec3 pointDir);
|
||||
|
||||
void wheelEvent(QWheelEvent* evt) override;
|
||||
|
@ -35,6 +38,8 @@ protected:
|
|||
void keyPressEvent(QKeyEvent* evt) override;
|
||||
void keyReleaseEvent(QKeyEvent* evt) override;
|
||||
|
||||
QMenu contextMenu;
|
||||
|
||||
MainWindow* mainWindow();
|
||||
float snappingFactor();
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "common.h"
|
||||
#include "logger.h"
|
||||
#include "objects/datamodel.h"
|
||||
#include "objects/model.h"
|
||||
#include "placedocument.h"
|
||||
#include "script/scriptdocument.h"
|
||||
#include <cstdio>
|
||||
|
@ -19,6 +20,7 @@
|
|||
#include <pugixml.hpp>
|
||||
#include <qtextcursor.h>
|
||||
#include <qtextedit.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _NDEBUG
|
||||
#define NDEBUG
|
||||
|
@ -71,7 +73,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
if (isDarkMode())
|
||||
QIcon::setFallbackThemeName("editor-dark");
|
||||
else
|
||||
QIcon::setFallbackThemeName("editor");
|
||||
QIcon::setThemeName("editor");
|
||||
|
||||
// qApp->setStyle(QStyleFactory::create("fusion"));
|
||||
defaultMessageHandler = qInstallMessageHandler(logQtMessage);
|
||||
|
@ -83,6 +85,8 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
this->close();
|
||||
});
|
||||
|
||||
ui->explorerView->buildContextMenu();
|
||||
|
||||
connectActionHandlers();
|
||||
|
||||
// Update properties
|
||||
|
@ -90,17 +94,17 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
if (newSelection.size() == 0) return;
|
||||
if (newSelection.size() > 1)
|
||||
ui->propertiesView->setSelected(std::nullopt);
|
||||
ui->propertiesView->setSelected(newSelection[0].lock());
|
||||
ui->propertiesView->setSelected(newSelection[0]);
|
||||
});
|
||||
|
||||
addSelectionListener([&](auto oldSelection, auto newSelection, bool __) {
|
||||
for (InstanceRefWeak inst : oldSelection) {
|
||||
for (std::weak_ptr<Instance> inst : oldSelection) {
|
||||
if (inst.expired() || inst.lock()->GetClass() != &Part::TYPE) continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst.lock());
|
||||
part->selected = false;
|
||||
}
|
||||
|
||||
for (InstanceRefWeak inst : newSelection) {
|
||||
for (std::weak_ptr<Instance> inst : newSelection) {
|
||||
if (inst.expired() || inst.lock()->GetClass() != &Part::TYPE) continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst.lock());
|
||||
part->selected = true;
|
||||
|
@ -141,10 +145,6 @@ void MainWindow::closeEvent(QCloseEvent* evt) {
|
|||
}
|
||||
|
||||
void MainWindow::connectActionHandlers() {
|
||||
// Explorer View
|
||||
|
||||
ui->explorerView->buildContextMenu();
|
||||
|
||||
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = TOOL_SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_MOVE : TOOL_SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolScale, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_SCALE : TOOL_SELECT; updateToolbars(); });
|
||||
|
@ -291,16 +291,16 @@ void MainWindow::connectActionHandlers() {
|
|||
});
|
||||
|
||||
connect(ui->actionDelete, &QAction::triggered, this, [&]() {
|
||||
for (InstanceRefWeak inst : getSelection()) {
|
||||
for (std::weak_ptr<Instance> inst : getSelection()) {
|
||||
if (inst.expired()) continue;
|
||||
inst.lock()->SetParent(std::nullopt);
|
||||
}
|
||||
setSelection(std::vector<InstanceRefWeak> {});
|
||||
setSelection({});
|
||||
});
|
||||
|
||||
connect(ui->actionCopy, &QAction::triggered, this, [&]() {
|
||||
pugi::xml_document rootDoc;
|
||||
for (InstanceRefWeak inst : getSelection()) {
|
||||
for (std::weak_ptr<Instance> inst : getSelection()) {
|
||||
if (inst.expired()) continue;
|
||||
inst.lock()->Serialize(rootDoc);
|
||||
}
|
||||
|
@ -314,7 +314,7 @@ void MainWindow::connectActionHandlers() {
|
|||
});
|
||||
connect(ui->actionCut, &QAction::triggered, this, [&]() {
|
||||
pugi::xml_document rootDoc;
|
||||
for (InstanceRefWeak inst : getSelection()) {
|
||||
for (std::weak_ptr<Instance> inst : getSelection()) {
|
||||
if (inst.expired()) continue;
|
||||
inst.lock()->Serialize(rootDoc);
|
||||
inst.lock()->SetParent(std::nullopt);
|
||||
|
@ -338,16 +338,16 @@ void MainWindow::connectActionHandlers() {
|
|||
rootDoc.load_string(encoded.c_str());
|
||||
|
||||
for (pugi::xml_node instNode : rootDoc.children()) {
|
||||
result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
|
||||
result<std::shared_ptr<Instance>, NoSuchInstance> inst = Instance::Deserialize(instNode);
|
||||
if (!inst) { inst.logError(); continue; }
|
||||
gWorkspace()->AddChild(inst.expect());
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->actionPasteInto, &QAction::triggered, this, [&]() {
|
||||
if (getSelection().size() != 1 || getSelection()[0].expired()) return;
|
||||
if (getSelection().size() != 1) return;
|
||||
|
||||
InstanceRef selectedParent = getSelection()[0].lock();
|
||||
std::shared_ptr<Instance> selectedParent = getSelection()[0];
|
||||
|
||||
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
|
||||
if (!mimeData || !mimeData->hasFormat("application/xml")) return;
|
||||
|
@ -358,12 +358,50 @@ void MainWindow::connectActionHandlers() {
|
|||
rootDoc.load_string(encoded.c_str());
|
||||
|
||||
for (pugi::xml_node instNode : rootDoc.children()) {
|
||||
result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
|
||||
result<std::shared_ptr<Instance>, NoSuchInstance> inst = Instance::Deserialize(instNode);
|
||||
if (!inst) { inst.logError(); continue; }
|
||||
selectedParent->AddChild(inst.expect());
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->actionGroupObjects, &QAction::triggered, this, [&]() {
|
||||
auto model = Model::New();
|
||||
std::shared_ptr<Instance> firstParent;
|
||||
|
||||
for (auto object : getSelection()) {
|
||||
if (firstParent == nullptr && object->GetParent().has_value()) firstParent = object->GetParent().value();
|
||||
object->SetParent(model);
|
||||
}
|
||||
|
||||
if (model->GetChildren().size() == 0)
|
||||
return;
|
||||
|
||||
// Technically not how it works in the actual studio, but it's not an API-breaking change
|
||||
// and I think this implementation is more useful so I'm sticking with it
|
||||
if (firstParent == nullptr) firstParent = gWorkspace();
|
||||
model->SetParent(firstParent);
|
||||
|
||||
setSelection({ model });
|
||||
});
|
||||
|
||||
connect(ui->actionUngroupObjects, &QAction::triggered, this, [&]() {
|
||||
std::vector<std::shared_ptr<Instance>> newSelection;
|
||||
|
||||
for (auto model : getSelection()) {
|
||||
// Not a model, skip
|
||||
if (!model->IsA<Model>()) { newSelection.push_back(model); continue; }
|
||||
|
||||
for (auto object : model->GetChildren()) {
|
||||
object->SetParent(model->GetParent());
|
||||
newSelection.push_back(object);
|
||||
}
|
||||
|
||||
model->Destroy();
|
||||
}
|
||||
|
||||
setSelection(newSelection);
|
||||
});
|
||||
|
||||
connect(ui->actionSaveModel, &QAction::triggered, this, [&]() {
|
||||
std::optional<std::string> path = openFileDialog("Openblocks Model (*.obm)", ".obm", QFileDialog::AcceptSave);
|
||||
if (!path) return;
|
||||
|
@ -373,7 +411,7 @@ void MainWindow::connectActionHandlers() {
|
|||
pugi::xml_document modelDoc;
|
||||
pugi::xml_node modelRoot = modelDoc.append_child("openblocks");
|
||||
|
||||
for (InstanceRefWeak inst : getSelection()) {
|
||||
for (std::weak_ptr<Instance> inst : getSelection()) {
|
||||
if (inst.expired()) continue;
|
||||
inst.lock()->Serialize(modelRoot);
|
||||
}
|
||||
|
@ -382,8 +420,8 @@ void MainWindow::connectActionHandlers() {
|
|||
});
|
||||
|
||||
connect(ui->actionInsertModel, &QAction::triggered, this, [&]() {
|
||||
if (getSelection().size() != 1 || getSelection()[0].expired()) return;
|
||||
InstanceRef selectedParent = getSelection()[0].lock();
|
||||
if (getSelection().size() != 1) return;
|
||||
std::shared_ptr<Instance> selectedParent = getSelection()[0];
|
||||
|
||||
std::optional<std::string> path = openFileDialog("Openblocks Model (*.obm)", ".obm", QFileDialog::AcceptOpen);
|
||||
if (!path) return;
|
||||
|
@ -393,13 +431,46 @@ void MainWindow::connectActionHandlers() {
|
|||
modelDoc.load(inStream);
|
||||
|
||||
for (pugi::xml_node instNode : modelDoc.child("openblocks").children("Item")) {
|
||||
result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
|
||||
result<std::shared_ptr<Instance>, NoSuchInstance> inst = Instance::Deserialize(instNode);
|
||||
if (!inst) { inst.logError(); continue; }
|
||||
selectedParent->AddChild(inst.expect());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::openFile(std::string path) {
|
||||
// Don't ask for confirmation if running a debug build (makes development easier)
|
||||
#ifdef NDEBUG
|
||||
// Ask if the user wants to save their changes
|
||||
// https://stackoverflow.com/a/33890731
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText("Save changes before creating new document?");
|
||||
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
|
||||
msgBox.setDefaultButton(QMessageBox::Save);
|
||||
int result = msgBox.exec();
|
||||
|
||||
if (result == QMessageBox::Cancel) return;
|
||||
if (result == QMessageBox::Save) {
|
||||
std::optional<std::string> path;
|
||||
if (!gDataModel->HasFile())
|
||||
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + gDataModel->name));
|
||||
if (!path || path == "") return;
|
||||
|
||||
gDataModel->SaveToFile(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path);
|
||||
editModeDataModel = newModel;
|
||||
gDataModel = newModel;
|
||||
newModel->Init();
|
||||
ui->explorerView->updateRoot(newModel);
|
||||
|
||||
// Reset running state
|
||||
placeDocument->setRunState(RUN_STOPPED);
|
||||
updateToolbars();
|
||||
}
|
||||
|
||||
void MainWindow::updateToolbars() {
|
||||
ui->actionToolSelect->setChecked(selectedTool == TOOL_SELECT);
|
||||
ui->actionToolMove->setChecked(selectedTool == TOOL_MOVE);
|
||||
|
|
|
@ -57,6 +57,8 @@ public:
|
|||
void openScriptDocument(std::shared_ptr<Script>);
|
||||
void closeScriptDocument(std::shared_ptr<Script>);
|
||||
|
||||
void openFile(std::string path);
|
||||
|
||||
Ui::MainWindow *ui;
|
||||
private:
|
||||
PlaceDocument* placeDocument;
|
||||
|
|
|
@ -176,6 +176,8 @@
|
|||
<addaction name="actionCut"/>
|
||||
<addaction name="actionPaste"/>
|
||||
<addaction name="actionPasteInto"/>
|
||||
<addaction name="actionGroupObjects"/>
|
||||
<addaction name="actionUngroupObjects"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="snappingOptions">
|
||||
<property name="windowTitle">
|
||||
|
@ -768,6 +770,40 @@
|
|||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGroupObjects">
|
||||
<property name="icon">
|
||||
<iconset theme="object-group"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Group Objects</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Group objects under a Model</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+G</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUngroupObjects">
|
||||
<property name="icon">
|
||||
<iconset theme="object-ungroup"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Ungroup Objects</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Ungroup objects inside selected Model</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+U</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
|
||||
std::map<std::string, QIcon> instanceIconCache;
|
||||
|
||||
ExplorerModel::ExplorerModel(InstanceRef dataRoot, QWidget *parent)
|
||||
ExplorerModel::ExplorerModel(std::shared_ptr<Instance> dataRoot, QWidget *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, rootItem(dataRoot) {
|
||||
// TODO: Don't use lambdas and handlers like that
|
||||
hierarchyPreUpdateHandler = [&](InstanceRef object, std::optional<InstanceRef> oldParent, std::optional<InstanceRef> newParent) {
|
||||
hierarchyPreUpdateHandler = [&](std::shared_ptr<Instance> object, std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
|
||||
if (oldParent.has_value()) {
|
||||
auto children = oldParent.value()->GetChildren();
|
||||
size_t idx = std::find(children.begin(), children.end(), object) - children.begin();
|
||||
|
@ -29,7 +29,7 @@ ExplorerModel::ExplorerModel(InstanceRef dataRoot, QWidget *parent)
|
|||
}
|
||||
};
|
||||
|
||||
hierarchyPostUpdateHandler = [&](InstanceRef object, std::optional<InstanceRef> oldParent, std::optional<InstanceRef> newParent) {
|
||||
hierarchyPostUpdateHandler = [&](std::shared_ptr<Instance> object, std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
|
||||
if (newParent.has_value()) endInsertRows();
|
||||
if (oldParent.has_value()) endRemoveRows();
|
||||
};
|
||||
|
@ -45,24 +45,24 @@ QModelIndex ExplorerModel::index(int row, int column, const QModelIndex &parent)
|
|||
? static_cast<Instance*>(parent.internalPointer())
|
||||
: rootItem.get();
|
||||
|
||||
if (parentItem->GetChildren().size() >= row && !(parentItem->GetChildren()[row]->GetClass()->flags & INSTANCE_HIDDEN))
|
||||
if (parentItem->GetChildren().size() >= (size_t)row && !(parentItem->GetChildren()[row]->GetClass()->flags & INSTANCE_HIDDEN))
|
||||
return createIndex(row, column, parentItem->GetChildren()[row].get());
|
||||
return {};
|
||||
}
|
||||
|
||||
QModelIndex ExplorerModel::toIndex(InstanceRef item) {
|
||||
QModelIndex ExplorerModel::toIndex(std::shared_ptr<Instance> item) {
|
||||
if (item == rootItem || !item->GetParent().has_value())
|
||||
return {};
|
||||
|
||||
InstanceRef parentItem = item->GetParent().value();
|
||||
std::shared_ptr<Instance> parentItem = item->GetParent().value();
|
||||
// Check above ensures this item is not root, so value() must be valid
|
||||
for (int i = 0; i < parentItem->GetChildren().size(); i++)
|
||||
for (size_t i = 0; i < parentItem->GetChildren().size(); i++)
|
||||
if (parentItem->GetChildren()[i] == item)
|
||||
return createIndex(i, 0, item.get());
|
||||
return QModelIndex{};
|
||||
}
|
||||
|
||||
QModelIndex ExplorerModel::ObjectToIndex(InstanceRef item) {
|
||||
QModelIndex ExplorerModel::ObjectToIndex(std::shared_ptr<Instance> item) {
|
||||
return toIndex(item);
|
||||
}
|
||||
|
||||
|
@ -72,14 +72,14 @@ QModelIndex ExplorerModel::parent(const QModelIndex &index) const {
|
|||
|
||||
Instance* childItem = static_cast<Instance*>(index.internalPointer());
|
||||
// NORISK: The parent must exist if the child was obtained from it during this frame
|
||||
InstanceRef parentItem = childItem->GetParent().value();
|
||||
std::shared_ptr<Instance> parentItem = childItem->GetParent().value();
|
||||
|
||||
if (parentItem == rootItem)
|
||||
return {};
|
||||
|
||||
// Check above ensures this item is not root, so value() must be valid
|
||||
InstanceRef parentParent = parentItem->GetParent().value();
|
||||
for (int i = 0; i < parentParent->GetChildren().size(); i++)
|
||||
std::shared_ptr<Instance> parentParent = parentItem->GetParent().value();
|
||||
for (size_t i = 0; i < parentParent->GetChildren().size(); i++)
|
||||
if (parentParent->GetChildren()[i] == parentItem)
|
||||
return createIndex(i, 0, parentItem.get());
|
||||
return QModelIndex{};
|
||||
|
@ -156,7 +156,7 @@ bool ExplorerModel::moveRows(const QModelIndex &sourceParentIdx, int sourceRow,
|
|||
|
||||
Logger::infof("Moved %d from %s", count, sourceParent->name.c_str());
|
||||
|
||||
if ((sourceRow + count) >= sourceParent->GetChildren().size()) {
|
||||
if (size_t(sourceRow + count) >= sourceParent->GetChildren().size()) {
|
||||
Logger::fatalErrorf("Attempt to move rows %d-%d from %s (%s) while it only has %zu children.", sourceRow, sourceRow + count, sourceParent->name.c_str(), sourceParent->GetClass()->className.c_str(), sourceParent->GetChildren().size());
|
||||
return false;
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ bool ExplorerModel::moveRows(const QModelIndex &sourceParentIdx, int sourceRow,
|
|||
}
|
||||
|
||||
bool ExplorerModel::removeRows(int row, int count, const QModelIndex& parentIdx) {
|
||||
Instance* parent = parentIdx.isValid() ? static_cast<Instance*>(parentIdx.internalPointer()) : rootItem.get();
|
||||
// Instance* parent = parentIdx.isValid() ? static_cast<Instance*>(parentIdx.internalPointer()) : rootItem.get();
|
||||
|
||||
for (int i = row; i < (row + count); i++) {
|
||||
//parent->GetChildren()[i]->SetParent(nullptr);
|
||||
|
@ -196,13 +196,13 @@ Qt::DropActions ExplorerModel::supportedDropActions() const {
|
|||
}
|
||||
|
||||
|
||||
InstanceRef ExplorerModel::fromIndex(const QModelIndex index) const {
|
||||
std::shared_ptr<Instance> ExplorerModel::fromIndex(const QModelIndex index) const {
|
||||
if (!index.isValid()) return rootItem;
|
||||
return static_cast<Instance*>(index.internalPointer())->shared_from_this();
|
||||
}
|
||||
|
||||
struct DragDropSlot {
|
||||
std::vector<InstanceRef> instances;
|
||||
std::vector<std::shared_ptr<Instance>> instances;
|
||||
};
|
||||
|
||||
bool ExplorerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) {
|
||||
|
@ -216,8 +216,8 @@ bool ExplorerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
|
|||
return true;
|
||||
}
|
||||
|
||||
InstanceRef parentInst = fromIndex(parent);
|
||||
for (InstanceRef instance : slot->instances) {
|
||||
std::shared_ptr<Instance> parentInst = fromIndex(parent);
|
||||
for (std::shared_ptr<Instance> instance : slot->instances) {
|
||||
instance->SetParent(parentInst);
|
||||
}
|
||||
|
||||
|
@ -225,7 +225,7 @@ bool ExplorerModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i
|
|||
return true;
|
||||
}
|
||||
|
||||
void ExplorerModel::updateRoot(InstanceRef newRoot) {
|
||||
void ExplorerModel::updateRoot(std::shared_ptr<Instance> newRoot) {
|
||||
beginResetModel();
|
||||
rootItem = newRoot;
|
||||
endResetModel();
|
||||
|
|
|
@ -8,7 +8,7 @@ class ExplorerModel : public QAbstractItemModel {
|
|||
public:
|
||||
Q_DISABLE_COPY_MOVE(ExplorerModel)
|
||||
|
||||
explicit ExplorerModel(InstanceRef dataRoot, QWidget *parent = nullptr);
|
||||
explicit ExplorerModel(std::shared_ptr<Instance> dataRoot, QWidget *parent = nullptr);
|
||||
~ExplorerModel() override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
@ -29,15 +29,15 @@ public:
|
|||
QStringList mimeTypes() const override;
|
||||
Qt::DropActions supportedDragActions() const override;
|
||||
Qt::DropActions supportedDropActions() const override;
|
||||
InstanceRef fromIndex(const QModelIndex index) const;
|
||||
QModelIndex ObjectToIndex(InstanceRef item);
|
||||
std::shared_ptr<Instance> fromIndex(const QModelIndex index) const;
|
||||
QModelIndex ObjectToIndex(std::shared_ptr<Instance> item);
|
||||
|
||||
QIcon iconOf(const InstanceType* type) const;
|
||||
|
||||
void updateRoot(InstanceRef newRoot);
|
||||
void updateRoot(std::shared_ptr<Instance> newRoot);
|
||||
private:
|
||||
InstanceRef rootItem;
|
||||
QModelIndex toIndex(InstanceRef item);
|
||||
std::shared_ptr<Instance> rootItem;
|
||||
QModelIndex toIndex(std::shared_ptr<Instance> item);
|
||||
};
|
||||
|
||||
// #endif
|
|
@ -33,16 +33,15 @@ ExplorerView::ExplorerView(QWidget* parent):
|
|||
this->expand(model.ObjectToIndex(gWorkspace()));
|
||||
|
||||
connect(this, &QTreeView::customContextMenuRequested, this, [&](const QPoint& point) {
|
||||
QModelIndex index = this->indexAt(point);
|
||||
contextMenu.exec(this->viewport()->mapToGlobal(point));
|
||||
});
|
||||
|
||||
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) {
|
||||
std::vector<InstanceRefWeak> selectedInstances;
|
||||
std::vector<std::shared_ptr<Instance>> selectedInstances;
|
||||
selectedInstances.reserve(selectedIndexes().count()); // This doesn't reserve everything, but should enhance things anyway
|
||||
|
||||
for (auto index : selectedIndexes()) {
|
||||
selectedInstances.push_back(reinterpret_cast<Instance*>(index.internalPointer())->weak_from_this());
|
||||
selectedInstances.push_back(reinterpret_cast<Instance*>(index.internalPointer())->shared_from_this());
|
||||
}
|
||||
|
||||
::setSelection(selectedInstances, true);
|
||||
|
@ -53,7 +52,7 @@ ExplorerView::ExplorerView(QWidget* parent):
|
|||
if (fromExplorer) return;
|
||||
|
||||
this->clearSelection();
|
||||
for (InstanceRefWeak inst : newSelection) {
|
||||
for (std::weak_ptr<Instance> inst : newSelection) {
|
||||
if (inst.expired()) continue;
|
||||
QModelIndex index = this->model.ObjectToIndex(inst.lock());
|
||||
this->selectionModel()->select(index, QItemSelectionModel::SelectionFlag::Select);
|
||||
|
@ -104,14 +103,14 @@ void ExplorerView::buildContextMenu() {
|
|||
QAction* instAction = new QAction(model.iconOf(type), QString::fromStdString(type->className));
|
||||
insertObjectMenu->addAction(instAction);
|
||||
connect(instAction, &QAction::triggered, this, [&]() {
|
||||
if (getSelection().size() == 0 || getSelection()[0].expired()) return;
|
||||
std::shared_ptr<Instance> instParent = getSelection()[0].lock();
|
||||
if (getSelection().size() == 0) return;
|
||||
std::shared_ptr<Instance> instParent = getSelection()[0];
|
||||
std::shared_ptr<Instance> newInst = type->constructor();
|
||||
newInst->SetParent(instParent);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ExplorerView::updateRoot(InstanceRef newRoot) {
|
||||
void ExplorerView::updateRoot(std::shared_ptr<Instance> newRoot) {
|
||||
model.updateRoot(newRoot);
|
||||
}
|
|
@ -16,7 +16,7 @@ public:
|
|||
// void dropEvent(QDropEvent*) override;
|
||||
|
||||
void buildContextMenu();
|
||||
void updateRoot(InstanceRef newRoot);
|
||||
void updateRoot(std::shared_ptr<Instance> newRoot);
|
||||
private:
|
||||
ExplorerModel model;
|
||||
QMenu contextMenu;
|
||||
|
|
|
@ -1,30 +1,36 @@
|
|||
#include "panes/propertiesview.h"
|
||||
#include "common.h"
|
||||
#include "datatypes/base.h"
|
||||
#include "datatypes/meta.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "datatypes/primitives.h"
|
||||
#include "error/data.h"
|
||||
#include "objects/base/member.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QComboBox>
|
||||
#include <QLineEdit>
|
||||
#include <QSpinBox>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QPainter>
|
||||
#include <QTime>
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <qapplication.h>
|
||||
#include <qcombobox.h>
|
||||
#include <qevent.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qtreewidget.h>
|
||||
|
||||
class PropertiesItemDelegate : public QStyledItemDelegate {
|
||||
PropertiesView* view;
|
||||
public:
|
||||
PropertiesItemDelegate(PropertiesView* parent) : view(parent), QStyledItemDelegate(parent) {}
|
||||
PropertiesItemDelegate(PropertiesView* parent) : QStyledItemDelegate(parent), view(parent) {}
|
||||
|
||||
void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override {
|
||||
// https://stackoverflow.com/a/76645757/16255372
|
||||
// https://stackoverflow.com/a/70078448/16255372
|
||||
|
||||
int indent = dynamic_cast<PropertiesView*>(parent())->indentation();
|
||||
|
||||
QStyledItemDelegate::initStyleOption(option, index);
|
||||
|
||||
if (!index.parent().isValid()) {
|
||||
|
@ -38,7 +44,7 @@ public:
|
|||
if (index.column() == 0) return nullptr;
|
||||
|
||||
if (!index.parent().isValid() || view->currentInstance.expired()) return nullptr;
|
||||
InstanceRef inst = view->currentInstance.lock();
|
||||
std::shared_ptr<Instance> inst = view->currentInstance.lock();
|
||||
|
||||
// If the property is deeper than 1 layer, then it is considered composite
|
||||
// Handle specially
|
||||
|
@ -49,10 +55,10 @@ public:
|
|||
std::string propertyName = !isComposite ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString()
|
||||
: view->itemFromIndex(index.parent())->data(0, Qt::DisplayRole).toString().toStdString();
|
||||
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
|
||||
Data::Variant currentValue = inst->GetPropertyValue(propertyName).expect();
|
||||
Variant currentValue = inst->GetPropertyValue(propertyName).expect();
|
||||
|
||||
if (isComposite) {
|
||||
if (meta.type == &Vector3::TYPE) {
|
||||
if (meta.type.descriptor == &Vector3::TYPE) {
|
||||
Vector3 vector = currentValue.get<Vector3>();
|
||||
float value = componentName == "X" ? vector.X() : componentName == "Y" ? vector.Y() : componentName == "Z" ? vector.Z() : 0;
|
||||
|
||||
|
@ -65,9 +71,11 @@ public:
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (meta.type == &Data::Float::TYPE) {
|
||||
if (meta.type.descriptor == &FLOAT_TYPE) {
|
||||
QDoubleSpinBox* spinBox = new QDoubleSpinBox(parent);
|
||||
spinBox->setValue(currentValue.get<Data::Float>());
|
||||
spinBox->setValue(currentValue.get<float>());
|
||||
spinBox->setMinimum(-INFINITY);
|
||||
spinBox->setMaximum(INFINITY);
|
||||
|
||||
if (meta.flags & PROP_UNIT_FLOAT) {
|
||||
spinBox->setMinimum(0);
|
||||
|
@ -76,24 +84,45 @@ public:
|
|||
}
|
||||
|
||||
return spinBox;
|
||||
} else if (meta.type == &Data::Int::TYPE) {
|
||||
} else if (meta.type.descriptor == &INT_TYPE) {
|
||||
QSpinBox* spinBox = new QSpinBox(parent);
|
||||
spinBox->setValue(currentValue.get<Data::Int>());
|
||||
spinBox->setMinimum(INT_MIN);
|
||||
spinBox->setMaximum(INT_MAX);
|
||||
spinBox->setValue(currentValue.get<int>());
|
||||
|
||||
return spinBox;
|
||||
} else if (meta.type == &Data::String::TYPE) {
|
||||
} else if (meta.type.descriptor == &STRING_TYPE) {
|
||||
QLineEdit* lineEdit = new QLineEdit(parent);
|
||||
lineEdit->setText(QString::fromStdString(currentValue.get<Data::String>()));
|
||||
lineEdit->setText(QString::fromStdString(currentValue.get<std::string>()));
|
||||
|
||||
return lineEdit;
|
||||
} else if (meta.type == &Color3::TYPE) {
|
||||
} else if (meta.type.descriptor == &Color3::TYPE) {
|
||||
QColorDialog* colorDialog = new QColorDialog(parent->window());
|
||||
|
||||
Color3 color = currentValue.get<Color3>();
|
||||
colorDialog->setCurrentColor(QColor::fromRgbF(color.R(), color.G(), color.B()));
|
||||
|
||||
return colorDialog;
|
||||
} else if (meta.type->fromString) {
|
||||
} else if (meta.type.descriptor == &EnumItem::TYPE) {
|
||||
QComboBox* comboBox = new QComboBox(parent);
|
||||
|
||||
EnumItem enumItem = currentValue.get<EnumItem>();
|
||||
std::vector<EnumItem> siblingItems = meta.type.enum_->GetEnumItems();
|
||||
for (size_t i = 0; i < siblingItems.size(); i++) {
|
||||
comboBox->addItem(QString::fromStdString(siblingItems[i].Name()));
|
||||
if (siblingItems[i].Value() == enumItem.Value())
|
||||
comboBox->setCurrentIndex(i);
|
||||
}
|
||||
|
||||
// If a selection is made
|
||||
// https://forum.qt.io/post/426902
|
||||
connect(comboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this, comboBox, index]() {
|
||||
setModelData(comboBox, view->model(), index);
|
||||
view->closeEditor(comboBox, QAbstractItemDelegate::EndEditHint::NoHint);
|
||||
});
|
||||
|
||||
return comboBox;
|
||||
} else if (meta.type.descriptor->fromString) {
|
||||
QLineEdit* lineEdit = new QLineEdit(parent);
|
||||
lineEdit->setText(QString::fromStdString(currentValue.ToString()));
|
||||
|
||||
|
@ -107,7 +136,7 @@ public:
|
|||
if (index.column() == 0) return;
|
||||
|
||||
if (!index.parent().isValid() || view->currentInstance.expired()) return;
|
||||
InstanceRef inst = view->currentInstance.lock();
|
||||
std::shared_ptr<Instance> inst = view->currentInstance.lock();
|
||||
|
||||
bool isComposite = index.parent().parent().isValid();
|
||||
std::string componentName = isComposite ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString() : "";
|
||||
|
@ -115,10 +144,10 @@ public:
|
|||
std::string propertyName = !index.parent().parent().isValid() ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString()
|
||||
: view->itemFromIndex(index.parent())->data(0, Qt::DisplayRole).toString().toStdString();
|
||||
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
|
||||
Data::Variant currentValue = inst->GetPropertyValue(propertyName).expect();
|
||||
Variant currentValue = inst->GetPropertyValue(propertyName).expect();
|
||||
|
||||
if (isComposite) {
|
||||
if (meta.type == &Vector3::TYPE) {
|
||||
if (meta.type.descriptor == &Vector3::TYPE) {
|
||||
Vector3 vector = currentValue.get<Vector3>();
|
||||
float value = componentName == "X" ? vector.X() : componentName == "Y" ? vector.Y() : componentName == "Z" ? vector.Z() : 0;
|
||||
|
||||
|
@ -131,24 +160,33 @@ public:
|
|||
return;
|
||||
}
|
||||
|
||||
if (meta.type == &Data::Float::TYPE) {
|
||||
if (meta.type.descriptor == &FLOAT_TYPE) {
|
||||
QDoubleSpinBox* spinBox = dynamic_cast<QDoubleSpinBox*>(editor);
|
||||
|
||||
spinBox->setValue(currentValue.get<Data::Float>());
|
||||
} else if (meta.type == &Data::Int::TYPE) {
|
||||
spinBox->setValue(currentValue.get<float>());
|
||||
} else if (meta.type.descriptor == &INT_TYPE) {
|
||||
QSpinBox* spinBox = dynamic_cast<QSpinBox*>(editor);
|
||||
|
||||
spinBox->setValue(currentValue.get<Data::Int>());
|
||||
} else if (meta.type == &Data::String::TYPE) {
|
||||
spinBox->setValue(currentValue.get<int>());
|
||||
} else if (meta.type.descriptor == &STRING_TYPE) {
|
||||
QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor);
|
||||
|
||||
lineEdit->setText(QString::fromStdString((std::string)currentValue.get<Data::String>()));
|
||||
} else if (meta.type == &Color3::TYPE) {
|
||||
lineEdit->setText(QString::fromStdString((std::string)currentValue.get<std::string>()));
|
||||
} else if (meta.type.descriptor == &Color3::TYPE) {
|
||||
QColorDialog* colorDialog = dynamic_cast<QColorDialog*>(editor);
|
||||
|
||||
Color3 color = currentValue.get<Color3>();
|
||||
colorDialog->setCurrentColor(QColor::fromRgbF(color.R(), color.G(), color.B()));
|
||||
} else if (meta.type->fromString) {
|
||||
} else if (meta.type.descriptor == &EnumItem::TYPE) {
|
||||
QComboBox* comboBox = dynamic_cast<QComboBox*>(editor);
|
||||
|
||||
EnumItem enumItem = currentValue.get<EnumItem>();
|
||||
std::vector<EnumItem> siblingItems = meta.type.enum_->GetEnumItems();
|
||||
for (size_t i = 0; i < siblingItems.size(); i++) {
|
||||
if (siblingItems[i].Value() == enumItem.Value())
|
||||
comboBox->setCurrentIndex(i);
|
||||
}
|
||||
} else if (meta.type.descriptor->fromString) {
|
||||
QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor);
|
||||
|
||||
lineEdit->setText(QString::fromStdString((std::string)currentValue.ToString()));
|
||||
|
@ -159,7 +197,7 @@ public:
|
|||
if (index.column() == 0) return;
|
||||
|
||||
if (!index.parent().isValid() || view->currentInstance.expired()) return;
|
||||
InstanceRef inst = view->currentInstance.lock();
|
||||
std::shared_ptr<Instance> inst = view->currentInstance.lock();
|
||||
|
||||
bool isComposite = index.parent().parent().isValid();
|
||||
std::string componentName = isComposite ? view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString() : "";
|
||||
|
@ -169,7 +207,7 @@ public:
|
|||
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
|
||||
|
||||
if (isComposite) {
|
||||
if (meta.type == &Vector3::TYPE) {
|
||||
if (meta.type.descriptor == &Vector3::TYPE) {
|
||||
QDoubleSpinBox* spinBox = dynamic_cast<QDoubleSpinBox*>(editor);
|
||||
float value = spinBox->value();
|
||||
|
||||
|
@ -186,22 +224,22 @@ public:
|
|||
return;
|
||||
}
|
||||
|
||||
if (meta.type == &Data::Float::TYPE) {
|
||||
if (meta.type.descriptor == &FLOAT_TYPE) {
|
||||
QDoubleSpinBox* spinBox = dynamic_cast<QDoubleSpinBox*>(editor);
|
||||
|
||||
inst->SetPropertyValue(propertyName, Data::Float((float)spinBox->value())).expect();
|
||||
inst->SetPropertyValue(propertyName, (float)spinBox->value()).expect();
|
||||
model->setData(index, spinBox->value());
|
||||
} else if (meta.type == &Data::Int::TYPE) {
|
||||
} else if (meta.type.descriptor == &INT_TYPE) {
|
||||
QSpinBox* spinBox = dynamic_cast<QSpinBox*>(editor);
|
||||
|
||||
inst->SetPropertyValue(propertyName, Data::Int((float)spinBox->value())).expect();
|
||||
inst->SetPropertyValue(propertyName, (int)spinBox->value()).expect();
|
||||
model->setData(index, spinBox->value());
|
||||
} else if (meta.type == &Data::String::TYPE) {
|
||||
} else if (meta.type.descriptor == &STRING_TYPE) {
|
||||
QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor);
|
||||
|
||||
inst->SetPropertyValue(propertyName, Data::String(lineEdit->text().toStdString())).expect();
|
||||
inst->SetPropertyValue(propertyName, lineEdit->text().toStdString()).expect();
|
||||
model->setData(index, lineEdit->text());
|
||||
} else if (meta.type == &Color3::TYPE) {
|
||||
} else if (meta.type.descriptor == &Color3::TYPE) {
|
||||
QColorDialog* colorDialog = dynamic_cast<QColorDialog*>(editor);
|
||||
|
||||
QColor color = colorDialog->currentColor();
|
||||
|
@ -209,14 +247,22 @@ public:
|
|||
inst->SetPropertyValue(propertyName, color3).expect();
|
||||
model->setData(index, QString::fromStdString(color3.ToString()), Qt::DisplayRole);
|
||||
model->setData(index, color, Qt::DecorationRole);
|
||||
} else if (meta.type->fromString) {
|
||||
} else if (meta.type.descriptor == &EnumItem::TYPE) {
|
||||
QComboBox* comboBox = dynamic_cast<QComboBox*>(editor);
|
||||
|
||||
std::vector<EnumItem> siblingItems = meta.type.enum_->GetEnumItems();
|
||||
EnumItem newItem = siblingItems[comboBox->currentIndex()];
|
||||
inst->SetPropertyValue(propertyName, newItem).expect("Failed to set enum value in properties pane");
|
||||
model->setData(index, QString::fromStdString(newItem.Name()), Qt::DisplayRole);
|
||||
} else if (meta.type.descriptor->fromString) {
|
||||
QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(editor);
|
||||
|
||||
std::optional<Data::Variant> parsedResult = meta.type->fromString(lineEdit->text().toStdString());
|
||||
result<Variant, DataParseError> parsedResult = meta.type.descriptor->fromString(lineEdit->text().toStdString(), meta.type);
|
||||
if (!parsedResult) return;
|
||||
inst->SetPropertyValue(propertyName, parsedResult.value()).expect();
|
||||
model->setData(index, QString::fromStdString(parsedResult.value().ToString()));
|
||||
view->rebuildCompositeProperty(view->itemFromIndex(index), meta.type, parsedResult.value());
|
||||
Variant parsedValue = parsedResult.expect();
|
||||
inst->SetPropertyValue(propertyName, parsedValue).expect();
|
||||
model->setData(index, QString::fromStdString(parsedValue.ToString()));
|
||||
view->rebuildCompositeProperty(view->itemFromIndex(index), meta.type.descriptor, parsedValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -268,11 +314,11 @@ void PropertiesView::drawBranches(QPainter *painter, const QRect &rect, const QM
|
|||
QTreeWidget::drawBranches(painter, rect, index);
|
||||
}
|
||||
|
||||
void PropertiesView::setSelected(std::optional<InstanceRef> instance) {
|
||||
void PropertiesView::setSelected(std::optional<std::shared_ptr<Instance>> instance) {
|
||||
clear();
|
||||
currentInstance = {};
|
||||
if (!instance) return;
|
||||
InstanceRef inst = instance.value();
|
||||
std::shared_ptr<Instance> inst = instance.value();
|
||||
currentInstance = inst;
|
||||
|
||||
std::map<PropertyCategory, QTreeWidgetItem*> propertyCategories;
|
||||
|
@ -296,35 +342,37 @@ void PropertiesView::setSelected(std::optional<InstanceRef> instance) {
|
|||
|
||||
for (std::string property : properties) {
|
||||
PropertyMeta meta = inst->GetPropertyMeta(property).expect();
|
||||
Data::Variant currentValue = inst->GetPropertyValue(property).expect();
|
||||
Variant currentValue = inst->GetPropertyValue(property).expect();
|
||||
|
||||
if (meta.type == &CFrame::TYPE || meta.flags & PROP_HIDDEN) continue;
|
||||
if (meta.type.descriptor == &CFrame::TYPE || meta.flags & PROP_HIDDEN) continue;
|
||||
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem;
|
||||
item->setFlags(item->flags() | Qt::ItemIsEditable | Qt::ItemIsSelectable);
|
||||
item->setData(0, Qt::DisplayRole, QString::fromStdString(property));
|
||||
|
||||
if (meta.type == &Data::Bool::TYPE) {
|
||||
item->setCheckState(1, (bool)currentValue.get<Data::Bool>() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
|
||||
} else if (meta.type == &Color3::TYPE) {
|
||||
if (meta.type.descriptor == &BOOL_TYPE) {
|
||||
item->setCheckState(1, (bool)currentValue.get<bool>() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
|
||||
} else if (meta.type.descriptor == &Color3::TYPE) {
|
||||
Color3 color = currentValue.get<Color3>();
|
||||
item->setData(1, Qt::DecorationRole, QColor::fromRgbF(color.R(), color.G(), color.B()));
|
||||
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
|
||||
} else if (meta.type == &Vector3::TYPE) {
|
||||
} else if (meta.type.descriptor == &Vector3::TYPE) {
|
||||
Vector3 vector = currentValue.get<Vector3>();
|
||||
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
|
||||
// } else if (meta.type == &CFrame::TYPE) {
|
||||
// } else if (meta.type.descriptor == &CFrame::TYPE) {
|
||||
// Vector3 vector = currentValue.get<CFrame>().Position();
|
||||
// item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
|
||||
} else {
|
||||
} else if (meta.type.descriptor == &EnumItem::TYPE) {
|
||||
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.get<EnumItem>().Name()));
|
||||
} else {
|
||||
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
|
||||
}
|
||||
|
||||
if (meta.type != &Color3::TYPE && (!meta.type->fromString || meta.flags & PROP_READONLY)) {
|
||||
if (meta.type.descriptor != &Color3::TYPE && (!meta.type.descriptor->fromString || meta.flags & PROP_READONLY)) {
|
||||
item->setDisabled(true);
|
||||
}
|
||||
|
||||
rebuildCompositeProperty(item, meta.type, currentValue);
|
||||
rebuildCompositeProperty(item, meta.type.descriptor, currentValue);
|
||||
|
||||
propertyCategories[meta.category]->addChild(item);
|
||||
propertyCategories[meta.category]->setExpanded(true);
|
||||
|
@ -347,17 +395,17 @@ void PropertiesView::propertyChanged(QTreeWidgetItem *item, int column) {
|
|||
// Necessary because otherwise this will catch setCheckState from onPropertyUpdated
|
||||
if (ignorePropertyUpdates) return;
|
||||
if (!item->parent() || (item->parent() && item->parent()->parent()) || currentInstance.expired()) return;
|
||||
InstanceRef inst = currentInstance.lock();
|
||||
std::shared_ptr<Instance> inst = currentInstance.lock();
|
||||
|
||||
std::string propertyName = item->data(0, Qt::DisplayRole).toString().toStdString();
|
||||
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
|
||||
|
||||
if (meta.type == &Data::Bool::TYPE) {
|
||||
inst->SetPropertyValue(propertyName, Data::Bool(item->checkState(1) == Qt::Checked)).expect();
|
||||
if (meta.type.descriptor == &BOOL_TYPE) {
|
||||
inst->SetPropertyValue(propertyName, item->checkState(1) == Qt::Checked).expect();
|
||||
}
|
||||
}
|
||||
|
||||
void PropertiesView::rebuildCompositeProperty(QTreeWidgetItem *item, const Data::TypeInfo* type, Data::Variant value) {
|
||||
void PropertiesView::rebuildCompositeProperty(QTreeWidgetItem *item, const TypeDesc* type, Variant value) {
|
||||
if (type == &Vector3::TYPE) {
|
||||
// https://forum.qt.io/post/266837
|
||||
foreach(auto i, item->takeChildren()) delete i;
|
||||
|
@ -384,15 +432,15 @@ void PropertiesView::rebuildCompositeProperty(QTreeWidgetItem *item, const Data:
|
|||
}
|
||||
|
||||
// static auto lastUpdateTime = std::chrono::steady_clock::now();
|
||||
void PropertiesView::onPropertyUpdated(InstanceRef inst, std::string property, Data::Variant newValue) {
|
||||
void PropertiesView::onPropertyUpdated(std::shared_ptr<Instance> inst, std::string property, Variant newValue) {
|
||||
// if (!currentInstance || currentInstance->expired() || instance != currentInstance->lock()) return;
|
||||
// if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - lastUpdateTime).count() < 1000) return;
|
||||
// lastUpdateTime = std::chrono::steady_clock::now();
|
||||
|
||||
PropertyMeta meta = inst->GetPropertyMeta(property).expect();
|
||||
Data::Variant currentValue = inst->GetPropertyValue(property).expect();
|
||||
Variant currentValue = inst->GetPropertyValue(property).expect();
|
||||
|
||||
if (meta.type == &CFrame::TYPE) return;
|
||||
if (meta.type.descriptor == &CFrame::TYPE) return;
|
||||
|
||||
for (int categoryItemIdx = 0; categoryItemIdx < topLevelItemCount(); categoryItemIdx++) {
|
||||
QTreeWidgetItem* categoryItem = topLevelItem(categoryItemIdx);
|
||||
|
@ -401,27 +449,27 @@ void PropertiesView::onPropertyUpdated(InstanceRef inst, std::string property, D
|
|||
|
||||
if (item->data(0, Qt::DisplayRole).toString().toStdString() != property) continue;
|
||||
|
||||
if (meta.type == &Data::Bool::TYPE) {
|
||||
if (meta.type.descriptor == &BOOL_TYPE) {
|
||||
// This is done because otherwise propertyChanged will catch this change erroneously
|
||||
ignorePropertyUpdates = true;
|
||||
item->setCheckState(1, (bool)currentValue.get<Data::Bool>() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
|
||||
item->setCheckState(1, (bool)currentValue.get<bool>() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
|
||||
ignorePropertyUpdates = false;
|
||||
} else if (meta.type == &Color3::TYPE) {
|
||||
} else if (meta.type.descriptor == &Color3::TYPE) {
|
||||
Color3 color = currentValue.get<Color3>();
|
||||
item->setData(1, Qt::DecorationRole, QColor::fromRgbF(color.R(), color.G(), color.B()));
|
||||
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
|
||||
} else if (meta.type == &Vector3::TYPE) {
|
||||
} else if (meta.type.descriptor == &Vector3::TYPE) {
|
||||
Vector3 vector = currentValue.get<Vector3>();
|
||||
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
|
||||
} else {
|
||||
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
|
||||
}
|
||||
|
||||
if (meta.type != &Color3::TYPE && (!meta.type->fromString || meta.flags & PROP_READONLY)) {
|
||||
if (meta.type.descriptor != &Color3::TYPE && (!meta.type.descriptor->fromString || meta.flags & PROP_READONLY)) {
|
||||
item->setDisabled(true);
|
||||
}
|
||||
|
||||
rebuildCompositeProperty(item, meta.type, currentValue);
|
||||
rebuildCompositeProperty(item, meta.type.descriptor, currentValue);
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -12,11 +12,11 @@ class PropertiesView : public QTreeWidget {
|
|||
Q_DECLARE_PRIVATE(QTreeView)
|
||||
|
||||
bool ignorePropertyUpdates = false;
|
||||
InstanceRefWeak currentInstance;
|
||||
std::weak_ptr<Instance> currentInstance;
|
||||
void propertyChanged(QTreeWidgetItem *item, int column);
|
||||
void activateProperty(QTreeWidgetItem *item, int column);
|
||||
void rebuildCompositeProperty(QTreeWidgetItem *item, const Data::TypeInfo*, Data::Variant);
|
||||
void onPropertyUpdated(InstanceRef instance, std::string property, Data::Variant newValue);
|
||||
void rebuildCompositeProperty(QTreeWidgetItem *item, const TypeDesc*, Variant);
|
||||
void onPropertyUpdated(std::shared_ptr<Instance> instance, std::string property, Variant newValue);
|
||||
|
||||
friend PropertiesItemDelegate;
|
||||
protected:
|
||||
|
@ -26,5 +26,5 @@ public:
|
|||
PropertiesView(QWidget* parent = nullptr);
|
||||
~PropertiesView() override;
|
||||
|
||||
void setSelected(std::optional<InstanceRef> instance);
|
||||
void setSelected(std::optional<std::shared_ptr<Instance>> instance);
|
||||
};
|
|
@ -5,18 +5,21 @@
|
|||
#include "objects/joint/snap.h"
|
||||
#include "objects/script.h"
|
||||
#include "objects/script/scriptcontext.h"
|
||||
#include "rendering/surface.h"
|
||||
#include "enum/surface.h"
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <qboxlayout.h>
|
||||
#include <qdebug.h>
|
||||
#include <qevent.h>
|
||||
#include <qmargins.h>
|
||||
#include <qmdisubwindow.h>
|
||||
#include <qlayout.h>
|
||||
#include <qmimedata.h>
|
||||
|
||||
PlaceDocument::PlaceDocument(QWidget* parent):
|
||||
QMdiSubWindow(parent) {
|
||||
placeWidget = new MainGLWidget;
|
||||
setAcceptDrops(true);
|
||||
setWidget(placeWidget);
|
||||
setWindowTitle("Place");
|
||||
|
||||
|
@ -74,6 +77,7 @@ void PlaceDocument::timerEvent(QTimerEvent* evt) {
|
|||
|
||||
void PlaceDocument::init() {
|
||||
timer.start(33, this);
|
||||
placeWidget->buildContextMenu();
|
||||
|
||||
std::shared_ptr<Part> lastPart;
|
||||
// Baseplate
|
||||
|
@ -89,49 +93,28 @@ void PlaceDocument::init() {
|
|||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
|
||||
gWorkspace()->AddChild(lastPart = Part::New({
|
||||
.position = glm::vec3(0),
|
||||
.rotation = glm::vec3(-2.6415927, 1.1415926, 2.57075),
|
||||
.position = glm::vec3(-3.8),
|
||||
.rotation = glm::vec3(0),
|
||||
.size = glm::vec3(4, 1.2, 2),
|
||||
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
|
||||
}));
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
auto part0 = lastPart;
|
||||
|
||||
gWorkspace()->AddChild(lastPart = Part::New({
|
||||
.position = glm::vec3(1.7610925, 0.48568499, -0.82623518),
|
||||
// .rotation = glm::vec3(0.5, 2, 1),
|
||||
.rotation = glm::vec3(-2.6415927, 1.1415926, -2.141639),
|
||||
.size = glm::vec3(4, 1.2, 2),
|
||||
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
|
||||
}));
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
auto part1 = lastPart;
|
||||
|
||||
lastPart = Part::New();
|
||||
shit = part1;
|
||||
}
|
||||
|
||||
part0->anchored = true;
|
||||
part0->UpdateProperty("Anchored");
|
||||
void PlaceDocument::dragEnterEvent(QDragEnterEvent* evt) {
|
||||
// https://stackoverflow.com/a/14895393/16255372
|
||||
if (evt->mimeData()->hasUrls()) {
|
||||
evt->acceptProposedAction();
|
||||
}
|
||||
}
|
||||
|
||||
// auto snap = Snap::New();
|
||||
// snap->part0 = part0;
|
||||
// snap->part1 = part1;
|
||||
// snap->c0 = part1->cframe;
|
||||
// snap->c1 = part0->cframe;
|
||||
|
||||
// gWorkspace()->AddChild(snap);
|
||||
// snap->UpdateProperty("Part0");
|
||||
// snap->UpdateProperty("Part1");
|
||||
|
||||
// part0->backSurface = SurfaceWeld;
|
||||
// part1->frontSurface = SurfaceWeld;
|
||||
|
||||
// part0->backSurface = SurfaceHinge;
|
||||
part0->backSurface = SurfaceMotor;
|
||||
// part1->frontSurface = SurfaceHinge;
|
||||
|
||||
std::shared_ptr<Script> script = Script::New();
|
||||
gWorkspace()->AddChild(script);
|
||||
void PlaceDocument::dropEvent(QDropEvent* evt) {
|
||||
auto urls = evt->mimeData()->urls();
|
||||
if (urls.size() == 0) return;
|
||||
QString fileName = urls[0].toLocalFile();
|
||||
MainWindow* mainWnd = dynamic_cast<MainWindow*>(window());
|
||||
// mainWnd->openScriptDocument(script);
|
||||
mainWnd->openFile(fileName.toStdString());
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue