First Commit on new Git-Base, yey!
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
build/
|
||||
.cache/
|
||||
Images-thingy/
|
||||
103
CMakeLists.txt
103
CMakeLists.txt
@@ -51,12 +51,35 @@ endif()
|
||||
|
||||
# ==================== Optional PhysX ====================
|
||||
option(MODULARITY_ENABLE_PHYSX "Enable PhysX physics integration" ON)
|
||||
option(MODULARITY_BUILD_EDITOR "Build the Modularity editor target" ON)
|
||||
|
||||
# ==================== Third-party libraries ====================
|
||||
|
||||
add_subdirectory(src/ThirdParty/glfw EXCLUDE_FROM_ALL)
|
||||
find_package(OpenGL REQUIRED)
|
||||
|
||||
# ==================== Mono (managed scripting) ====================
|
||||
option(MODULARITY_USE_MONO "Enable Mono embedding for managed scripts" ON)
|
||||
|
||||
if(MODULARITY_USE_MONO)
|
||||
set(MONO_ROOT ${PROJECT_SOURCE_DIR}/src/ThirdParty/mono CACHE PATH "Mono root directory")
|
||||
find_path(MONO_INCLUDE_DIR mono/jit/jit.h
|
||||
HINTS
|
||||
${MONO_ROOT}/include/mono-2.0
|
||||
${MONO_ROOT}/include
|
||||
)
|
||||
find_library(MONO_LIBRARY
|
||||
NAMES mono-2.0-sgen mono-2.0 monosgen-2.0
|
||||
HINTS
|
||||
${MONO_ROOT}/lib
|
||||
${MONO_ROOT}/lib64
|
||||
)
|
||||
if(NOT MONO_INCLUDE_DIR OR NOT MONO_LIBRARY)
|
||||
message(WARNING "Mono not found. Disabling MODULARITY_USE_MONO. Set MONO_ROOT to a Mono runtime with include/mono-2.0 and libmono-2.0-sgen.")
|
||||
set(MODULARITY_USE_MONO OFF CACHE BOOL "Enable Mono embedding for managed scripts" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# GLAD
|
||||
add_library(glad STATIC src/ThirdParty/glad/glad.c)
|
||||
target_include_directories(glad PUBLIC src/ThirdParty/glad)
|
||||
@@ -105,6 +128,7 @@ file(GLOB_RECURSE ENGINE_HEADERS CONFIGURE_DEPENDS
|
||||
|
||||
list(FILTER ENGINE_SOURCES EXCLUDE REGEX ".*/ThirdParty/assimp/.*")
|
||||
list(FILTER ENGINE_SOURCES EXCLUDE REGEX ".*/ThirdParty/PhysX/.*")
|
||||
list(FILTER ENGINE_SOURCES EXCLUDE REGEX ".*/main_player.cpp")
|
||||
list(FILTER ENGINE_HEADERS EXCLUDE REGEX ".*/ThirdParty/assimp/.*")
|
||||
list(FILTER ENGINE_HEADERS EXCLUDE REGEX ".*/ThirdParty/PhysX/.*")
|
||||
|
||||
@@ -122,8 +146,29 @@ target_include_directories(core PUBLIC
|
||||
${PROJECT_SOURCE_DIR}/src/ThirdParty/assimp/include
|
||||
)
|
||||
target_link_libraries(core PUBLIC glad glm imgui imguizmo)
|
||||
if(MODULARITY_USE_MONO)
|
||||
target_include_directories(core PUBLIC ${MONO_INCLUDE_DIR})
|
||||
target_link_libraries(core PUBLIC ${MONO_LIBRARY})
|
||||
endif()
|
||||
target_compile_definitions(core PUBLIC MODULARITY_USE_MONO=$<BOOL:${MODULARITY_USE_MONO}>)
|
||||
target_compile_options(core PRIVATE ${MODULARITY_WARNING_FLAGS})
|
||||
|
||||
add_library(core_player STATIC ${ENGINE_SOURCES} ${ENGINE_HEADERS})
|
||||
target_compile_definitions(core_player PUBLIC MODULARITY_PLAYER)
|
||||
target_link_libraries(core_player PUBLIC assimp)
|
||||
target_include_directories(core_player PUBLIC
|
||||
${PROJECT_SOURCE_DIR}/src
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${PROJECT_SOURCE_DIR}/src/ThirdParty/assimp/include
|
||||
)
|
||||
target_link_libraries(core_player PUBLIC glad glm imgui imguizmo)
|
||||
if(MODULARITY_USE_MONO)
|
||||
target_include_directories(core_player PUBLIC ${MONO_INCLUDE_DIR})
|
||||
target_link_libraries(core_player PUBLIC ${MONO_LIBRARY})
|
||||
endif()
|
||||
target_compile_definitions(core_player PUBLIC MODULARITY_USE_MONO=$<BOOL:${MODULARITY_USE_MONO}>)
|
||||
target_compile_options(core_player PRIVATE ${MODULARITY_WARNING_FLAGS})
|
||||
|
||||
if(MODULARITY_ENABLE_PHYSX)
|
||||
set(PHYSX_ROOT_DIR ${PROJECT_SOURCE_DIR}/src/ThirdParty/PhysX/physx CACHE PATH "PhysX root directory")
|
||||
set(TARGET_BUILD_PLATFORM "linux" CACHE STRING "PhysX build platform (linux/windows)")
|
||||
@@ -134,19 +179,47 @@ if(MODULARITY_ENABLE_PHYSX)
|
||||
target_include_directories(core PUBLIC ${PHYSX_ROOT_DIR}/include)
|
||||
target_compile_definitions(core PUBLIC MODULARITY_ENABLE_PHYSX PX_PHYSX_STATIC_LIB)
|
||||
target_link_libraries(core PUBLIC PhysX PhysXCommon PhysXFoundation PhysXExtensions PhysXCooking)
|
||||
target_include_directories(core_player PUBLIC ${PHYSX_ROOT_DIR}/include)
|
||||
target_compile_definitions(core_player PUBLIC MODULARITY_ENABLE_PHYSX PX_PHYSX_STATIC_LIB)
|
||||
target_link_libraries(core_player PUBLIC PhysX PhysXCommon PhysXFoundation PhysXExtensions PhysXCooking)
|
||||
endif()
|
||||
|
||||
# ==================== Executable ====================
|
||||
add_executable(Modularity src/main.cpp)
|
||||
target_compile_options(Modularity PRIVATE ${MODULARITY_WARNING_FLAGS})
|
||||
if(MODULARITY_BUILD_EDITOR)
|
||||
add_executable(Modularity src/main.cpp)
|
||||
target_compile_options(Modularity PRIVATE ${MODULARITY_WARNING_FLAGS})
|
||||
endif()
|
||||
add_executable(ModularityPlayer src/main_player.cpp)
|
||||
target_compile_options(ModularityPlayer PRIVATE ${MODULARITY_WARNING_FLAGS})
|
||||
|
||||
# Link order matters on Linux
|
||||
if(NOT WIN32)
|
||||
find_package(X11 REQUIRED)
|
||||
target_include_directories(Modularity PRIVATE ${X11_INCLUDE_DIR})
|
||||
if(MODULARITY_BUILD_EDITOR)
|
||||
target_include_directories(Modularity PRIVATE ${X11_INCLUDE_DIR})
|
||||
target_link_libraries(Modularity PRIVATE
|
||||
core
|
||||
imgui
|
||||
imguizmo
|
||||
glad
|
||||
glm
|
||||
glfw
|
||||
OpenGL::GL
|
||||
pthread
|
||||
dl
|
||||
${X11_LIBRARIES}
|
||||
Xrandr
|
||||
Xi
|
||||
Xinerama
|
||||
Xcursor
|
||||
)
|
||||
# Export symbols so runtime-loaded scripts can resolve ImGui/engine symbols.
|
||||
target_link_options(Modularity PRIVATE "-rdynamic")
|
||||
endif()
|
||||
|
||||
target_link_libraries(Modularity PRIVATE
|
||||
core
|
||||
target_include_directories(ModularityPlayer PRIVATE ${X11_INCLUDE_DIR})
|
||||
target_link_libraries(ModularityPlayer PRIVATE
|
||||
core_player
|
||||
imgui
|
||||
imguizmo
|
||||
glad
|
||||
@@ -161,15 +234,25 @@ if(NOT WIN32)
|
||||
Xinerama
|
||||
Xcursor
|
||||
)
|
||||
# Export symbols so runtime-loaded scripts can resolve ImGui/engine symbols.
|
||||
target_link_options(Modularity PRIVATE "-rdynamic")
|
||||
target_link_options(ModularityPlayer PRIVATE "-rdynamic")
|
||||
else()
|
||||
target_link_libraries(Modularity PRIVATE core glfw OpenGL::GL)
|
||||
if(MODULARITY_BUILD_EDITOR)
|
||||
target_link_libraries(Modularity PRIVATE core glfw OpenGL::GL)
|
||||
endif()
|
||||
target_link_libraries(ModularityPlayer PRIVATE core_player glfw OpenGL::GL)
|
||||
endif()
|
||||
|
||||
# ==================== Copy Resources folder after build ====================
|
||||
add_custom_command(TARGET Modularity POST_BUILD
|
||||
if(MODULARITY_BUILD_EDITOR)
|
||||
add_custom_command(TARGET Modularity POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/Resources
|
||||
$<TARGET_FILE_DIR:Modularity>/Resources
|
||||
)
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET ModularityPlayer POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/Resources
|
||||
$<TARGET_FILE_DIR:Modularity>/Resources
|
||||
$<TARGET_FILE_DIR:ModularityPlayer>/Resources
|
||||
)
|
||||
|
||||
BIN
Resources/Fonts/TheSunset.ttf
Normal file
BIN
Resources/Fonts/TheSunset.ttf
Normal file
Binary file not shown.
BIN
Resources/Fonts/Thesunsethd-Regular (1).ttf
Normal file
BIN
Resources/Fonts/Thesunsethd-Regular (1).ttf
Normal file
Binary file not shown.
44
Resources/Shaders/skinned_vert.glsl
Normal file
44
Resources/Shaders/skinned_vert.glsl
Normal file
@@ -0,0 +1,44 @@
|
||||
#version 330 core
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec3 aNormal;
|
||||
layout (location = 2) in vec2 aTexCoord;
|
||||
layout (location = 3) in ivec4 aBoneIds;
|
||||
layout (location = 4) in vec4 aBoneWeights;
|
||||
|
||||
out vec3 FragPos;
|
||||
out vec3 Normal;
|
||||
out vec2 TexCoord;
|
||||
|
||||
uniform mat4 model;
|
||||
uniform mat4 view;
|
||||
uniform mat4 projection;
|
||||
uniform mat4 bones[256];
|
||||
uniform int boneCount;
|
||||
uniform bool useSkinning;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 localPos = vec4(aPos, 1.0);
|
||||
vec3 localNormal = aNormal;
|
||||
if (useSkinning) {
|
||||
vec4 skinnedPos = vec4(0.0);
|
||||
vec3 skinnedNormal = vec3(0.0);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int id = aBoneIds[i];
|
||||
float w = aBoneWeights[i];
|
||||
if (w <= 0.0 || id < 0 || id >= boneCount) continue;
|
||||
mat4 b = bones[id];
|
||||
skinnedPos += (b * localPos) * w;
|
||||
skinnedNormal += mat3(b) * localNormal * w;
|
||||
}
|
||||
localPos = skinnedPos;
|
||||
localNormal = skinnedNormal;
|
||||
}
|
||||
|
||||
vec4 worldPos = model * localPos;
|
||||
FragPos = vec3(worldPos);
|
||||
Normal = mat3(transpose(inverse(model))) * localNormal;
|
||||
TexCoord = aTexCoord;
|
||||
|
||||
gl_Position = projection * view * worldPos;
|
||||
}
|
||||
125
Resources/anim.ini
Normal file
125
Resources/anim.ini
Normal file
@@ -0,0 +1,125 @@
|
||||
[Window][Debug##Default]
|
||||
Pos=60,60
|
||||
Size=400,400
|
||||
Collapsed=0
|
||||
|
||||
[Window][Modularity - Project Launcher]
|
||||
Pos=569,288
|
||||
Size=720,480
|
||||
Collapsed=0
|
||||
|
||||
[Window][New Project]
|
||||
Pos=679,403
|
||||
Size=500,250
|
||||
Collapsed=0
|
||||
|
||||
[Window][DockSpace]
|
||||
Pos=0,24
|
||||
Size=1000,776
|
||||
Collapsed=0
|
||||
|
||||
[Window][Viewport]
|
||||
Pos=304,48
|
||||
Size=329,752
|
||||
Collapsed=0
|
||||
DockId=0x0000000B,0
|
||||
|
||||
[Window][Hierarchy]
|
||||
Pos=0,48
|
||||
Size=304,617
|
||||
Collapsed=0
|
||||
DockId=0x0000000D,0
|
||||
|
||||
[Window][Inspector]
|
||||
Pos=633,48
|
||||
Size=367,557
|
||||
Collapsed=0
|
||||
DockId=0x00000001,0
|
||||
|
||||
[Window][File Browser]
|
||||
Pos=756,836
|
||||
Size=753,221
|
||||
Collapsed=0
|
||||
DockId=0xD71539A0,1
|
||||
|
||||
[Window][Console]
|
||||
Pos=0,665
|
||||
Size=304,135
|
||||
Collapsed=0
|
||||
DockId=0x0000000E,0
|
||||
|
||||
[Window][Project]
|
||||
Pos=633,605
|
||||
Size=367,195
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Window][Launcher]
|
||||
Pos=0,0
|
||||
Size=1000,800
|
||||
Collapsed=0
|
||||
|
||||
[Window][Camera]
|
||||
Pos=0,48
|
||||
Size=304,747
|
||||
Collapsed=0
|
||||
DockId=0x00000005,1
|
||||
|
||||
[Window][Environment]
|
||||
Pos=1553,48
|
||||
Size=347,747
|
||||
Collapsed=0
|
||||
DockId=0x00000005,1
|
||||
|
||||
[Window][Project Manager]
|
||||
Pos=787,785
|
||||
Size=784,221
|
||||
Collapsed=0
|
||||
DockId=0xD71539A0,1
|
||||
|
||||
[Window][Game Viewport]
|
||||
Pos=304,48
|
||||
Size=329,752
|
||||
Collapsed=0
|
||||
DockId=0x0000000B,1
|
||||
|
||||
[Window][Project Settings]
|
||||
Pos=633,48
|
||||
Size=367,557
|
||||
Collapsed=0
|
||||
DockId=0x00000001,1
|
||||
|
||||
[Window][Animation]
|
||||
Pos=304,804
|
||||
Size=1229,218
|
||||
Collapsed=0
|
||||
DockId=0x0000000C,0
|
||||
|
||||
[Window][Scripting]
|
||||
Pos=304,48
|
||||
Size=329,752
|
||||
Collapsed=0
|
||||
DockId=0x0000000B,2
|
||||
|
||||
[Table][0xFF88847C,2]
|
||||
RefScale=16
|
||||
Column 0 Width=220
|
||||
Column 1 Weight=1.0000
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0xD71539A0 Window=0x3DA2F1DE Pos=0,48 Size=1000,752 Split=Y
|
||||
DockNode ID=0x00000005 Parent=0xD71539A0 SizeRef=1249,733 Split=X Selected=0xC450F867
|
||||
DockNode ID=0x00000007 Parent=0x00000005 SizeRef=304,227 Split=Y Selected=0xBABDAE5E
|
||||
DockNode ID=0x0000000D Parent=0x00000007 SizeRef=304,799 Selected=0xBABDAE5E
|
||||
DockNode ID=0x0000000E Parent=0x00000007 SizeRef=304,175 Selected=0xEA83D666
|
||||
DockNode ID=0x00000008 Parent=0x00000005 SizeRef=1596,227 Split=X Selected=0xE9044848
|
||||
DockNode ID=0x00000003 Parent=0x00000008 SizeRef=1229,227 Split=Y Selected=0xE9044848
|
||||
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=1213,756 CentralNode=1 Selected=0xE9044848
|
||||
DockNode ID=0x0000000C Parent=0x00000003 SizeRef=1213,218 Selected=0x5921A509
|
||||
DockNode ID=0x00000004 Parent=0x00000008 SizeRef=367,227 Split=Y Selected=0x36DC96AB
|
||||
DockNode ID=0x00000001 Parent=0x00000004 SizeRef=797,722 Selected=0x3F1379AF
|
||||
DockNode ID=0x00000002 Parent=0x00000004 SizeRef=797,252 Selected=0x9C21DE82
|
||||
DockNode ID=0x00000006 Parent=0xD71539A0 SizeRef=1249,241 Split=X Selected=0x9C21DE82
|
||||
DockNode ID=0x00000009 Parent=0x00000006 SizeRef=383,278 Selected=0x9C21DE82
|
||||
DockNode ID=0x0000000A Parent=0x00000006 SizeRef=866,278 Selected=0xEA83D666
|
||||
|
||||
@@ -14,45 +14,45 @@ Size=500,250
|
||||
Collapsed=0
|
||||
|
||||
[Window][DockSpace]
|
||||
Pos=0,23
|
||||
Size=1920,983
|
||||
Pos=0,24
|
||||
Size=1900,998
|
||||
Collapsed=0
|
||||
|
||||
[Window][Viewport]
|
||||
Pos=306,46
|
||||
Size=1265,739
|
||||
Pos=304,48
|
||||
Size=1249,747
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
DockId=0x0000000F,0
|
||||
|
||||
[Window][Hierarchy]
|
||||
Pos=0,46
|
||||
Size=304,739
|
||||
Pos=0,48
|
||||
Size=304,747
|
||||
Collapsed=0
|
||||
DockId=0x00000001,0
|
||||
DockId=0x00000007,0
|
||||
|
||||
[Window][Inspector]
|
||||
Pos=1573,46
|
||||
Size=347,960
|
||||
Pos=1553,48
|
||||
Size=347,747
|
||||
Collapsed=0
|
||||
DockId=0x00000008,0
|
||||
DockId=0x00000010,0
|
||||
|
||||
[Window][File Browser]
|
||||
Pos=756,836
|
||||
Size=753,221
|
||||
Collapsed=0
|
||||
DockId=0x00000006,1
|
||||
DockId=0xD71539A0,1
|
||||
|
||||
[Window][Console]
|
||||
Pos=0,787
|
||||
Size=785,219
|
||||
Pos=939,795
|
||||
Size=961,227
|
||||
Collapsed=0
|
||||
DockId=0x00000005,0
|
||||
DockId=0x00000014,0
|
||||
|
||||
[Window][Project]
|
||||
Pos=787,787
|
||||
Size=784,219
|
||||
Pos=0,795
|
||||
Size=939,227
|
||||
Collapsed=0
|
||||
DockId=0x00000006,0
|
||||
DockId=0x00000013,0
|
||||
|
||||
[Window][Launcher]
|
||||
Pos=0,0
|
||||
@@ -60,43 +60,64 @@ Size=1000,800
|
||||
Collapsed=0
|
||||
|
||||
[Window][Camera]
|
||||
Pos=0,46
|
||||
Size=304,739
|
||||
Pos=0,48
|
||||
Size=304,747
|
||||
Collapsed=0
|
||||
DockId=0x00000001,1
|
||||
DockId=0x00000007,1
|
||||
|
||||
[Window][Environment]
|
||||
Pos=1573,46
|
||||
Size=347,960
|
||||
Pos=1553,48
|
||||
Size=347,747
|
||||
Collapsed=0
|
||||
DockId=0x00000008,1
|
||||
DockId=0x00000010,1
|
||||
|
||||
[Window][Project Manager]
|
||||
Pos=787,785
|
||||
Size=784,221
|
||||
Collapsed=0
|
||||
DockId=0x00000006,1
|
||||
DockId=0xD71539A0,1
|
||||
|
||||
[Window][Game Viewport]
|
||||
Pos=306,46
|
||||
Size=1265,739
|
||||
Pos=304,48
|
||||
Size=1249,747
|
||||
Collapsed=0
|
||||
DockId=0x00000002,1
|
||||
DockId=0x0000000F,1
|
||||
|
||||
[Window][Project Settings]
|
||||
Pos=306,46
|
||||
Size=1265,739
|
||||
Pos=304,48
|
||||
Size=1249,747
|
||||
Collapsed=0
|
||||
DockId=0x00000002,2
|
||||
DockId=0x0000000F,2
|
||||
|
||||
[Window][Animation]
|
||||
Pos=583,795
|
||||
Size=738,227
|
||||
Collapsed=0
|
||||
DockId=0x00000013,0
|
||||
|
||||
[Window][Scripting]
|
||||
Pos=304,48
|
||||
Size=1249,747
|
||||
Collapsed=0
|
||||
DockId=0x0000000F,3
|
||||
|
||||
[Table][0xFF88847C,2]
|
||||
RefScale=16
|
||||
Column 0 Width=220
|
||||
Column 1 Weight=1.0000
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0xD71539A0 Window=0x3DA2F1DE Pos=0,46 Size=1920,960 Split=X
|
||||
DockNode ID=0x00000007 Parent=0xD71539A0 SizeRef=1509,1015 Split=Y
|
||||
DockNode ID=0x00000003 Parent=0x00000007 SizeRef=1858,739 Split=X
|
||||
DockNode ID=0x00000001 Parent=0x00000003 SizeRef=304,758 Selected=0xBABDAE5E
|
||||
DockNode ID=0x00000002 Parent=0x00000003 SizeRef=694,758 CentralNode=1 Selected=0xC450F867
|
||||
DockNode ID=0x00000004 Parent=0x00000007 SizeRef=1858,219 Split=X Selected=0xEA83D666
|
||||
DockNode ID=0x00000005 Parent=0x00000004 SizeRef=929,221 Selected=0xEA83D666
|
||||
DockNode ID=0x00000006 Parent=0x00000004 SizeRef=927,221 Selected=0x9C21DE82
|
||||
DockNode ID=0x00000008 Parent=0xD71539A0 SizeRef=347,1015 Selected=0x36DC96AB
|
||||
DockSpace ID=0xD71539A0 Window=0x3DA2F1DE Pos=0,48 Size=1900,974 Split=Y
|
||||
DockNode ID=0x00000005 Parent=0xD71539A0 SizeRef=1249,733 Split=Y Selected=0xC450F867
|
||||
DockNode ID=0x00000001 Parent=0x00000005 SizeRef=1900,747 Split=X Selected=0xE9044848
|
||||
DockNode ID=0x00000007 Parent=0x00000001 SizeRef=304,486 Selected=0xBABDAE5E
|
||||
DockNode ID=0x00000008 Parent=0x00000001 SizeRef=1596,486 Split=X Selected=0xE9044848
|
||||
DockNode ID=0x0000000F Parent=0x00000008 SizeRef=1249,486 Selected=0xE9044848
|
||||
DockNode ID=0x00000010 Parent=0x00000008 SizeRef=347,486 Selected=0x36DC96AB
|
||||
DockNode ID=0x00000002 Parent=0x00000005 SizeRef=1900,227 Split=X Selected=0x3F1379AF
|
||||
DockNode ID=0x00000013 Parent=0x00000002 SizeRef=939,488 CentralNode=1 Selected=0x9C21DE82
|
||||
DockNode ID=0x00000014 Parent=0x00000002 SizeRef=961,488 Selected=0xEA83D666
|
||||
DockNode ID=0x00000006 Parent=0xD71539A0 SizeRef=1249,241 Split=X Selected=0x9C21DE82
|
||||
DockNode ID=0x00000009 Parent=0x00000006 SizeRef=383,278 Selected=0x9C21DE82
|
||||
DockNode ID=0x0000000A Parent=0x00000006 SizeRef=866,278 Selected=0xEA83D666
|
||||
|
||||
|
||||
121
Resources/scripter.ini
Normal file
121
Resources/scripter.ini
Normal file
@@ -0,0 +1,121 @@
|
||||
[Window][Debug##Default]
|
||||
Pos=60,60
|
||||
Size=400,400
|
||||
Collapsed=0
|
||||
|
||||
[Window][Modularity - Project Launcher]
|
||||
Pos=569,288
|
||||
Size=720,480
|
||||
Collapsed=0
|
||||
|
||||
[Window][New Project]
|
||||
Pos=679,403
|
||||
Size=500,250
|
||||
Collapsed=0
|
||||
|
||||
[Window][DockSpace]
|
||||
Pos=0,24
|
||||
Size=1900,998
|
||||
Collapsed=0
|
||||
|
||||
[Window][Viewport]
|
||||
Pos=260,48
|
||||
Size=741,772
|
||||
Collapsed=0
|
||||
DockId=0x00000003,0
|
||||
|
||||
[Window][Hierarchy]
|
||||
Pos=0,48
|
||||
Size=260,772
|
||||
Collapsed=0
|
||||
DockId=0x00000007,1
|
||||
|
||||
[Window][Inspector]
|
||||
Pos=1001,48
|
||||
Size=899,772
|
||||
Collapsed=0
|
||||
DockId=0x00000004,1
|
||||
|
||||
[Window][File Browser]
|
||||
Pos=756,836
|
||||
Size=753,221
|
||||
Collapsed=0
|
||||
DockId=0xD71539A0,1
|
||||
|
||||
[Window][Console]
|
||||
Pos=0,48
|
||||
Size=260,772
|
||||
Collapsed=0
|
||||
DockId=0x00000007,0
|
||||
|
||||
[Window][Project]
|
||||
Pos=0,820
|
||||
Size=1900,202
|
||||
Collapsed=0
|
||||
DockId=0x0000000C,0
|
||||
|
||||
[Window][Launcher]
|
||||
Pos=0,0
|
||||
Size=1900,1022
|
||||
Collapsed=0
|
||||
|
||||
[Window][Camera]
|
||||
Pos=0,48
|
||||
Size=304,747
|
||||
Collapsed=0
|
||||
DockId=0x00000005,1
|
||||
|
||||
[Window][Environment]
|
||||
Pos=1553,48
|
||||
Size=347,747
|
||||
Collapsed=0
|
||||
DockId=0x00000005,1
|
||||
|
||||
[Window][Project Manager]
|
||||
Pos=787,785
|
||||
Size=784,221
|
||||
Collapsed=0
|
||||
DockId=0xD71539A0,1
|
||||
|
||||
[Window][Game Viewport]
|
||||
Pos=260,48
|
||||
Size=741,772
|
||||
Collapsed=0
|
||||
DockId=0x00000003,1
|
||||
|
||||
[Window][Project Settings]
|
||||
Pos=1201,48
|
||||
Size=699,772
|
||||
Collapsed=0
|
||||
DockId=0x00000004,2
|
||||
|
||||
[Window][Animation]
|
||||
Pos=583,795
|
||||
Size=738,227
|
||||
Collapsed=0
|
||||
DockId=0x00000003,0
|
||||
|
||||
[Window][Scripting]
|
||||
Pos=1001,48
|
||||
Size=899,772
|
||||
Collapsed=0
|
||||
DockId=0x00000004,0
|
||||
|
||||
[Table][0xFF88847C,2]
|
||||
RefScale=16
|
||||
Column 0 Width=220
|
||||
Column 1 Weight=1.0000
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0xD71539A0 Window=0x3DA2F1DE Pos=0,48 Size=1900,974 Split=Y
|
||||
DockNode ID=0x00000005 Parent=0xD71539A0 SizeRef=1249,733 Split=Y Selected=0xC450F867
|
||||
DockNode ID=0x0000000B Parent=0x00000005 SizeRef=1900,772 Split=X Selected=0xE9044848
|
||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=260,114 Selected=0xBABDAE5E
|
||||
DockNode ID=0x00000008 Parent=0x0000000B SizeRef=1640,114 Split=X Selected=0xE9044848
|
||||
DockNode ID=0x00000003 Parent=0x00000008 SizeRef=741,114 CentralNode=1 Selected=0xC450F867
|
||||
DockNode ID=0x00000004 Parent=0x00000008 SizeRef=899,114 Selected=0xBC881222
|
||||
DockNode ID=0x0000000C Parent=0x00000005 SizeRef=1900,202 Selected=0x9C21DE82
|
||||
DockNode ID=0x00000006 Parent=0xD71539A0 SizeRef=1249,241 Split=X Selected=0x9C21DE82
|
||||
DockNode ID=0x00000009 Parent=0x00000006 SizeRef=383,278 Selected=0x9C21DE82
|
||||
DockNode ID=0x0000000A Parent=0x00000006 SizeRef=866,278 Selected=0xEA83D666
|
||||
|
||||
282
Scripts/AnimationWindow.cpp
Normal file
282
Scripts/AnimationWindow.cpp
Normal file
@@ -0,0 +1,282 @@
|
||||
#include "ScriptRuntime.h"
|
||||
#include "SceneObject.h"
|
||||
#include "ThirdParty/imgui/imgui.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
struct Keyframe {
|
||||
float time = 0.0f;
|
||||
glm::vec3 position = glm::vec3(0.0f);
|
||||
glm::vec3 rotation = glm::vec3(0.0f);
|
||||
glm::vec3 scale = glm::vec3(1.0f);
|
||||
};
|
||||
|
||||
int targetId = -1;
|
||||
char targetName[128] = "";
|
||||
std::vector<Keyframe> keyframes;
|
||||
int selectedKey = -1;
|
||||
|
||||
float clipLength = 2.0f;
|
||||
float currentTime = 0.0f;
|
||||
float playSpeed = 1.0f;
|
||||
bool isPlaying = false;
|
||||
bool loop = true;
|
||||
bool applyOnScrub = true;
|
||||
|
||||
glm::vec3 lerpVec3(const glm::vec3& a, const glm::vec3& b, float t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
float clampFloat(float value, float minValue, float maxValue) {
|
||||
return std::max(minValue, std::min(value, maxValue));
|
||||
}
|
||||
|
||||
SceneObject* resolveTarget(ScriptContext& ctx) {
|
||||
if (targetId >= 0) {
|
||||
if (auto* obj = ctx.FindObjectById(targetId)) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
if (targetName[0] != '\0') {
|
||||
if (auto* obj = ctx.FindObjectByName(targetName)) {
|
||||
targetId = obj->id;
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void syncTargetLabel(SceneObject* obj) {
|
||||
if (!obj) return;
|
||||
strncpy(targetName, obj->name.c_str(), sizeof(targetName) - 1);
|
||||
targetName[sizeof(targetName) - 1] = '\0';
|
||||
}
|
||||
|
||||
void captureKeyframe(SceneObject& obj, float time) {
|
||||
float clamped = clampFloat(time, 0.0f, clipLength);
|
||||
auto it = std::find_if(keyframes.begin(), keyframes.end(),
|
||||
[&](const Keyframe& k) { return std::abs(k.time - clamped) < 0.0001f; });
|
||||
if (it == keyframes.end()) {
|
||||
keyframes.push_back(Keyframe{clamped, obj.position, obj.rotation, obj.scale});
|
||||
} else {
|
||||
it->position = obj.position;
|
||||
it->rotation = obj.rotation;
|
||||
it->scale = obj.scale;
|
||||
}
|
||||
std::sort(keyframes.begin(), keyframes.end(),
|
||||
[](const Keyframe& a, const Keyframe& b) { return a.time < b.time; });
|
||||
}
|
||||
|
||||
void deleteKeyframe(int index) {
|
||||
if (index < 0 || index >= static_cast<int>(keyframes.size())) return;
|
||||
keyframes.erase(keyframes.begin() + index);
|
||||
if (selectedKey == index) selectedKey = -1;
|
||||
if (selectedKey > index) selectedKey--;
|
||||
}
|
||||
|
||||
void applyPoseAtTime(ScriptContext& ctx, SceneObject& obj, float time) {
|
||||
if (keyframes.empty()) return;
|
||||
|
||||
if (time <= keyframes.front().time) {
|
||||
ctx.SetPosition(keyframes.front().position);
|
||||
ctx.SetRotation(keyframes.front().rotation);
|
||||
ctx.SetScale(keyframes.front().scale);
|
||||
ctx.MarkDirty();
|
||||
return;
|
||||
}
|
||||
if (time >= keyframes.back().time) {
|
||||
ctx.SetPosition(keyframes.back().position);
|
||||
ctx.SetRotation(keyframes.back().rotation);
|
||||
ctx.SetScale(keyframes.back().scale);
|
||||
ctx.MarkDirty();
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i + 1 < keyframes.size(); ++i) {
|
||||
const Keyframe& a = keyframes[i];
|
||||
const Keyframe& b = keyframes[i + 1];
|
||||
if (time >= a.time && time <= b.time) {
|
||||
float span = b.time - a.time;
|
||||
float t = (span > 0.0f) ? (time - a.time) / span : 0.0f;
|
||||
ctx.SetPosition(lerpVec3(a.position, b.position, t));
|
||||
ctx.SetRotation(lerpVec3(a.rotation, b.rotation, t));
|
||||
ctx.SetScale(lerpVec3(a.scale, b.scale, t));
|
||||
ctx.MarkDirty();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void drawTimeline(float& time, float length, int& selection) {
|
||||
ImVec2 size = ImVec2(ImGui::GetContentRegionAvail().x, 70.0f);
|
||||
ImVec2 start = ImGui::GetCursorScreenPos();
|
||||
ImGui::InvisibleButton("Timeline", size);
|
||||
|
||||
ImDrawList* draw = ImGui::GetWindowDrawList();
|
||||
ImU32 bg = ImGui::GetColorU32(ImGuiCol_FrameBg);
|
||||
ImU32 border = ImGui::GetColorU32(ImGuiCol_Border);
|
||||
ImU32 accent = ImGui::GetColorU32(ImGuiCol_CheckMark);
|
||||
ImU32 keyColor = ImGui::GetColorU32(ImGuiCol_SliderGrab);
|
||||
|
||||
draw->AddRectFilled(start, ImVec2(start.x + size.x, start.y + size.y), bg, 6.0f);
|
||||
draw->AddRect(start, ImVec2(start.x + size.x, start.y + size.y), border, 6.0f);
|
||||
|
||||
float clamped = clampFloat(time, 0.0f, length);
|
||||
float playheadX = start.x + (length > 0.0f ? (clamped / length) * size.x : 0.0f);
|
||||
draw->AddLine(ImVec2(playheadX, start.y), ImVec2(playheadX, start.y + size.y), accent, 2.0f);
|
||||
|
||||
for (size_t i = 0; i < keyframes.size(); ++i) {
|
||||
float keyX = start.x + (length > 0.0f ? (keyframes[i].time / length) * size.x : 0.0f);
|
||||
ImVec2 center(keyX, start.y + size.y * 0.5f);
|
||||
float radius = (selection == static_cast<int>(i)) ? 6.0f : 4.5f;
|
||||
draw->AddCircleFilled(center, radius, keyColor);
|
||||
ImRect hit(ImVec2(center.x - 7.0f, center.y - 7.0f), ImVec2(center.x + 7.0f, center.y + 7.0f));
|
||||
if (ImGui::IsMouseHoveringRect(hit.Min, hit.Max) && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
selection = static_cast<int>(i);
|
||||
time = keyframes[i].time;
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemActive() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
float mouseX = ImGui::GetIO().MousePos.x;
|
||||
float t = (mouseX - start.x) / size.x;
|
||||
time = clampFloat(t * length, 0.0f, length);
|
||||
}
|
||||
}
|
||||
|
||||
void drawKeyframeTable() {
|
||||
if (keyframes.empty()) {
|
||||
ImGui::TextDisabled("No keyframes yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("KeyframeTable", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) {
|
||||
ImGui::TableSetupColumn("Time");
|
||||
ImGui::TableSetupColumn("Position");
|
||||
ImGui::TableSetupColumn("Rotation");
|
||||
ImGui::TableSetupColumn("Scale");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (size_t i = 0; i < keyframes.size(); ++i) {
|
||||
const auto& key = keyframes[i];
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
bool selected = selectedKey == static_cast<int>(i);
|
||||
std::string label = std::to_string(key.time);
|
||||
if (ImGui::Selectable(label.c_str(), selected, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
selectedKey = static_cast<int>(i);
|
||||
currentTime = key.time;
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%.2f, %.2f, %.2f", key.position.x, key.position.y, key.position.z);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%.2f, %.2f, %.2f", key.rotation.x, key.rotation.y, key.rotation.z);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%.2f, %.2f, %.2f", key.scale.x, key.scale.y, key.scale.z);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
extern "C" void RenderEditorWindow(ScriptContext& ctx) {
|
||||
ImGui::TextUnformatted("Simple Animation");
|
||||
ImGui::Separator();
|
||||
|
||||
SceneObject* selectedObj = ctx.object;
|
||||
SceneObject* targetObj = resolveTarget(ctx);
|
||||
|
||||
ImGui::TextDisabled("Select a GameObject to animate:");
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::InputText("##TargetName", targetName, sizeof(targetName));
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Use Selected") && selectedObj) {
|
||||
targetId = selectedObj->id;
|
||||
syncTargetLabel(selectedObj);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear")) {
|
||||
targetId = -1;
|
||||
targetName[0] = '\0';
|
||||
targetObj = nullptr;
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
if (ImGui::BeginTabBar("AnimModeTabs")) {
|
||||
if (ImGui::BeginTabItem("Pose Mode")) {
|
||||
ImGui::TextDisabled("Pose Editor");
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f);
|
||||
if (ImGui::Button("Key")) {
|
||||
if (targetObj) captureKeyframe(*targetObj, currentTime);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Delete") && selectedKey >= 0) {
|
||||
deleteKeyframe(selectedKey);
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::Spacing();
|
||||
drawTimeline(currentTime, clipLength, selectedKey);
|
||||
ImGui::SliderFloat("Time", ¤tTime, 0.0f, clipLength, "%.2fs");
|
||||
|
||||
ImGui::Spacing();
|
||||
drawKeyframeTable();
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Config Mode")) {
|
||||
ImGui::TextDisabled("Playback");
|
||||
ImGui::Separator();
|
||||
ImGui::Checkbox("Loop", &loop);
|
||||
ImGui::Checkbox("Apply On Scrub", &applyOnScrub);
|
||||
ImGui::SliderFloat("Length", &clipLength, 0.1f, 20.0f, "%.2fs");
|
||||
ImGui::SliderFloat("Speed", &playSpeed, 0.1f, 4.0f, "%.2fx");
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled("Transport");
|
||||
if (ImGui::Button(isPlaying ? "Pause" : "Play")) {
|
||||
isPlaying = !isPlaying;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Stop")) {
|
||||
isPlaying = false;
|
||||
currentTime = 0.0f;
|
||||
}
|
||||
|
||||
if (targetObj) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Target: %s", targetObj->name.c_str());
|
||||
} else {
|
||||
ImGui::TextDisabled("No target selected.");
|
||||
}
|
||||
|
||||
if (isPlaying && clipLength > 0.0f) {
|
||||
currentTime += ImGui::GetIO().DeltaTime * playSpeed;
|
||||
if (currentTime > clipLength) {
|
||||
if (loop) currentTime = std::fmod(currentTime, clipLength);
|
||||
else {
|
||||
currentTime = clipLength;
|
||||
isPlaying = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetObj && (isPlaying || applyOnScrub)) {
|
||||
applyPoseAtTime(ctx, *targetObj, currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void ExitRenderEditorWindow(ScriptContext& ctx) {
|
||||
(void)ctx;
|
||||
}
|
||||
297
Scripts/Managed/ModuCPP.cs
Normal file
297
Scripts/Managed/ModuCPP.cs
Normal file
@@ -0,0 +1,297 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace ModuCPP {
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void ScriptTickDelegate(IntPtr ctx, float deltaTime);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void ScriptInspectorDelegate(IntPtr ctx);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void SetNativeApiDelegate(IntPtr apiPtr);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vec3 {
|
||||
public float X;
|
||||
public float Y;
|
||||
public float Z;
|
||||
|
||||
public Vec3(float x, float y, float z) {
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
}
|
||||
|
||||
public static Vec3 operator +(Vec3 a, Vec3 b) => new Vec3(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
|
||||
public static Vec3 operator -(Vec3 a, Vec3 b) => new Vec3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
|
||||
public static Vec3 operator *(Vec3 a, float s) => new Vec3(a.X * s, a.Y * s, a.Z * s);
|
||||
}
|
||||
|
||||
public enum ConsoleMessageType {
|
||||
Info = 0,
|
||||
Warning = 1,
|
||||
Error = 2,
|
||||
Success = 3
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct NativeApi {
|
||||
public uint Version;
|
||||
public IntPtr GetObjectId;
|
||||
public IntPtr GetPosition;
|
||||
public IntPtr SetPosition;
|
||||
public IntPtr GetRotation;
|
||||
public IntPtr SetRotation;
|
||||
public IntPtr GetScale;
|
||||
public IntPtr SetScale;
|
||||
public IntPtr HasRigidbody;
|
||||
public IntPtr EnsureRigidbody;
|
||||
public IntPtr SetRigidbodyVelocity;
|
||||
public IntPtr GetRigidbodyVelocity;
|
||||
public IntPtr AddRigidbodyForce;
|
||||
public IntPtr AddRigidbodyImpulse;
|
||||
public IntPtr GetSettingFloat;
|
||||
public IntPtr GetSettingBool;
|
||||
public IntPtr GetSettingString;
|
||||
public IntPtr SetSettingFloat;
|
||||
public IntPtr SetSettingBool;
|
||||
public IntPtr SetSettingString;
|
||||
public IntPtr AddConsoleMessage;
|
||||
}
|
||||
|
||||
internal unsafe static class Native {
|
||||
public static NativeApi Api;
|
||||
public static GetObjectIdFn GetObjectId;
|
||||
public static GetPositionFn GetPosition;
|
||||
public static SetPositionFn SetPosition;
|
||||
public static GetRotationFn GetRotation;
|
||||
public static SetRotationFn SetRotation;
|
||||
public static GetScaleFn GetScale;
|
||||
public static SetScaleFn SetScale;
|
||||
public static HasRigidbodyFn HasRigidbody;
|
||||
public static EnsureRigidbodyFn EnsureRigidbody;
|
||||
public static SetRigidbodyVelocityFn SetRigidbodyVelocity;
|
||||
public static GetRigidbodyVelocityFn GetRigidbodyVelocity;
|
||||
public static AddRigidbodyForceFn AddRigidbodyForce;
|
||||
public static AddRigidbodyImpulseFn AddRigidbodyImpulse;
|
||||
public static GetSettingFloatFn GetSettingFloat;
|
||||
public static GetSettingBoolFn GetSettingBool;
|
||||
public static GetSettingStringFn GetSettingString;
|
||||
public static SetSettingFloatFn SetSettingFloat;
|
||||
public static SetSettingBoolFn SetSettingBool;
|
||||
public static SetSettingStringFn SetSettingString;
|
||||
public static AddConsoleMessageFn AddConsoleMessage;
|
||||
|
||||
public static void BindDelegates() {
|
||||
GetObjectId = Marshal.GetDelegateForFunctionPointer<GetObjectIdFn>(Api.GetObjectId);
|
||||
GetPosition = Marshal.GetDelegateForFunctionPointer<GetPositionFn>(Api.GetPosition);
|
||||
SetPosition = Marshal.GetDelegateForFunctionPointer<SetPositionFn>(Api.SetPosition);
|
||||
GetRotation = Marshal.GetDelegateForFunctionPointer<GetRotationFn>(Api.GetRotation);
|
||||
SetRotation = Marshal.GetDelegateForFunctionPointer<SetRotationFn>(Api.SetRotation);
|
||||
GetScale = Marshal.GetDelegateForFunctionPointer<GetScaleFn>(Api.GetScale);
|
||||
SetScale = Marshal.GetDelegateForFunctionPointer<SetScaleFn>(Api.SetScale);
|
||||
HasRigidbody = Marshal.GetDelegateForFunctionPointer<HasRigidbodyFn>(Api.HasRigidbody);
|
||||
EnsureRigidbody = Marshal.GetDelegateForFunctionPointer<EnsureRigidbodyFn>(Api.EnsureRigidbody);
|
||||
SetRigidbodyVelocity = Marshal.GetDelegateForFunctionPointer<SetRigidbodyVelocityFn>(Api.SetRigidbodyVelocity);
|
||||
GetRigidbodyVelocity = Marshal.GetDelegateForFunctionPointer<GetRigidbodyVelocityFn>(Api.GetRigidbodyVelocity);
|
||||
AddRigidbodyForce = Marshal.GetDelegateForFunctionPointer<AddRigidbodyForceFn>(Api.AddRigidbodyForce);
|
||||
AddRigidbodyImpulse = Marshal.GetDelegateForFunctionPointer<AddRigidbodyImpulseFn>(Api.AddRigidbodyImpulse);
|
||||
GetSettingFloat = Marshal.GetDelegateForFunctionPointer<GetSettingFloatFn>(Api.GetSettingFloat);
|
||||
GetSettingBool = Marshal.GetDelegateForFunctionPointer<GetSettingBoolFn>(Api.GetSettingBool);
|
||||
GetSettingString = Marshal.GetDelegateForFunctionPointer<GetSettingStringFn>(Api.GetSettingString);
|
||||
SetSettingFloat = Marshal.GetDelegateForFunctionPointer<SetSettingFloatFn>(Api.SetSettingFloat);
|
||||
SetSettingBool = Marshal.GetDelegateForFunctionPointer<SetSettingBoolFn>(Api.SetSettingBool);
|
||||
SetSettingString = Marshal.GetDelegateForFunctionPointer<SetSettingStringFn>(Api.SetSettingString);
|
||||
AddConsoleMessage = Marshal.GetDelegateForFunctionPointer<AddConsoleMessageFn>(Api.AddConsoleMessage);
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate int GetObjectIdFn(IntPtr ctx);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void GetPositionFn(IntPtr ctx, float* x, float* y, float* z);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void SetPositionFn(IntPtr ctx, float x, float y, float z);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void GetRotationFn(IntPtr ctx, float* x, float* y, float* z);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void SetRotationFn(IntPtr ctx, float x, float y, float z);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void GetScaleFn(IntPtr ctx, float* x, float* y, float* z);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void SetScaleFn(IntPtr ctx, float x, float y, float z);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate int HasRigidbodyFn(IntPtr ctx);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate int EnsureRigidbodyFn(IntPtr ctx, int useGravity, int kinematic);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate int SetRigidbodyVelocityFn(IntPtr ctx, float x, float y, float z);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate int GetRigidbodyVelocityFn(IntPtr ctx, float* x, float* y, float* z);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate int AddRigidbodyForceFn(IntPtr ctx, float x, float y, float z);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate int AddRigidbodyImpulseFn(IntPtr ctx, float x, float y, float z);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate float GetSettingFloatFn(IntPtr ctx, byte* key, float fallback);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate int GetSettingBoolFn(IntPtr ctx, byte* key, int fallback);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void GetSettingStringFn(IntPtr ctx, byte* key, byte* fallback, byte* outBuffer, int outBufferSize);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void SetSettingFloatFn(IntPtr ctx, byte* key, float value);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void SetSettingBoolFn(IntPtr ctx, byte* key, int value);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void SetSettingStringFn(IntPtr ctx, byte* key, byte* value);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void AddConsoleMessageFn(IntPtr ctx, byte* message, int type);
|
||||
}
|
||||
|
||||
public static unsafe class Host {
|
||||
public static void SetNativeApi(IntPtr apiPtr) {
|
||||
Native.Api = Marshal.PtrToStructure<NativeApi>(apiPtr);
|
||||
Native.BindDelegates();
|
||||
}
|
||||
}
|
||||
|
||||
public readonly unsafe struct Context {
|
||||
private readonly IntPtr handle;
|
||||
|
||||
public Context(IntPtr ctx) {
|
||||
handle = ctx;
|
||||
}
|
||||
|
||||
public int ObjectId => Native.GetObjectId(handle);
|
||||
|
||||
public Vec3 Position {
|
||||
get {
|
||||
float x = 0f, y = 0f, z = 0f;
|
||||
Native.GetPosition(handle, &x, &y, &z);
|
||||
return new Vec3(x, y, z);
|
||||
}
|
||||
set {
|
||||
Native.SetPosition(handle, value.X, value.Y, value.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public Vec3 Rotation {
|
||||
get {
|
||||
float x = 0f, y = 0f, z = 0f;
|
||||
Native.GetRotation(handle, &x, &y, &z);
|
||||
return new Vec3(x, y, z);
|
||||
}
|
||||
set {
|
||||
Native.SetRotation(handle, value.X, value.Y, value.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public Vec3 Scale {
|
||||
get {
|
||||
float x = 0f, y = 0f, z = 0f;
|
||||
Native.GetScale(handle, &x, &y, &z);
|
||||
return new Vec3(x, y, z);
|
||||
}
|
||||
set {
|
||||
Native.SetScale(handle, value.X, value.Y, value.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasRigidbody => Native.HasRigidbody(handle) != 0;
|
||||
|
||||
public bool EnsureRigidbody(bool useGravity = true, bool kinematic = false) {
|
||||
return Native.EnsureRigidbody(handle, useGravity ? 1 : 0, kinematic ? 1 : 0) != 0;
|
||||
}
|
||||
|
||||
public Vec3 RigidbodyVelocity {
|
||||
get {
|
||||
float x = 0f, y = 0f, z = 0f;
|
||||
if (Native.GetRigidbodyVelocity(handle, &x, &y, &z) == 0) {
|
||||
return new Vec3(0f, 0f, 0f);
|
||||
}
|
||||
return new Vec3(x, y, z);
|
||||
}
|
||||
set {
|
||||
Native.SetRigidbodyVelocity(handle, value.X, value.Y, value.Z);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRigidbodyForce(Vec3 force) {
|
||||
Native.AddRigidbodyForce(handle, force.X, force.Y, force.Z);
|
||||
}
|
||||
|
||||
public void AddRigidbodyImpulse(Vec3 impulse) {
|
||||
Native.AddRigidbodyImpulse(handle, impulse.X, impulse.Y, impulse.Z);
|
||||
}
|
||||
|
||||
public float GetSettingFloat(string key, float fallback = 0f) {
|
||||
byte[] keyBytes = Encoding.UTF8.GetBytes((key ?? string.Empty) + "\0");
|
||||
fixed (byte* keyPtr = keyBytes) {
|
||||
return Native.GetSettingFloat(handle, keyPtr, fallback);
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetSettingBool(string key, bool fallback = false) {
|
||||
byte[] keyBytes = Encoding.UTF8.GetBytes((key ?? string.Empty) + "\0");
|
||||
fixed (byte* keyPtr = keyBytes) {
|
||||
int value = Native.GetSettingBool(handle, keyPtr, fallback ? 1 : 0);
|
||||
return value != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetSettingString(string key, string fallback = "") {
|
||||
const int bufferSize = 256;
|
||||
byte[] keyBytes = Encoding.UTF8.GetBytes((key ?? string.Empty) + "\0");
|
||||
byte[] fallbackBytes = Encoding.UTF8.GetBytes((fallback ?? string.Empty) + "\0");
|
||||
byte* buffer = stackalloc byte[bufferSize];
|
||||
fixed (byte* keyPtr = keyBytes)
|
||||
fixed (byte* fallbackPtr = fallbackBytes) {
|
||||
Native.GetSettingString(handle, keyPtr, fallbackPtr, buffer, bufferSize);
|
||||
}
|
||||
return FromUtf8(buffer);
|
||||
}
|
||||
|
||||
public void SetSettingFloat(string key, float value) {
|
||||
byte[] keyBytes = Encoding.UTF8.GetBytes((key ?? string.Empty) + "\0");
|
||||
fixed (byte* keyPtr = keyBytes) {
|
||||
Native.SetSettingFloat(handle, keyPtr, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSettingBool(string key, bool value) {
|
||||
byte[] keyBytes = Encoding.UTF8.GetBytes((key ?? string.Empty) + "\0");
|
||||
fixed (byte* keyPtr = keyBytes) {
|
||||
Native.SetSettingBool(handle, keyPtr, value ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSettingString(string key, string value) {
|
||||
byte[] keyBytes = Encoding.UTF8.GetBytes((key ?? string.Empty) + "\0");
|
||||
byte[] valueBytes = Encoding.UTF8.GetBytes((value ?? string.Empty) + "\0");
|
||||
fixed (byte* keyPtr = keyBytes)
|
||||
fixed (byte* valuePtr = valueBytes) {
|
||||
Native.SetSettingString(handle, keyPtr, valuePtr);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddConsoleMessage(string message, ConsoleMessageType type = ConsoleMessageType.Info) {
|
||||
byte[] msgBytes = Encoding.UTF8.GetBytes((message ?? string.Empty) + "\0");
|
||||
fixed (byte* msgPtr = msgBytes) {
|
||||
Native.AddConsoleMessage(handle, msgPtr, (int)type);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FromUtf8(byte* ptr) {
|
||||
if (ptr == null) return string.Empty;
|
||||
int length = 0;
|
||||
while (ptr[length] != 0) {
|
||||
length++;
|
||||
}
|
||||
if (length == 0) return string.Empty;
|
||||
byte[] bytes = new byte[length];
|
||||
Marshal.Copy((IntPtr)ptr, bytes, 0, length);
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Scripts/Managed/ModuCPP.csproj
Normal file
10
Scripts/Managed/ModuCPP.csproj
Normal file
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<GenerateRuntimeConfigurationFiles>false</GenerateRuntimeConfigurationFiles>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
68
Scripts/Managed/SampleInspector.cs
Normal file
68
Scripts/Managed/SampleInspector.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
|
||||
namespace ModuCPP {
|
||||
public static class SampleInspector {
|
||||
private static bool autoRotate = false;
|
||||
private static Vec3 spinSpeed = new Vec3(0f, 45f, 0f);
|
||||
private static Vec3 offset = new Vec3(0f, 1f, 0f);
|
||||
private static string targetName = "MyTarget"; // Stored for parity; object lookup API not wired yet.
|
||||
|
||||
private static void LoadSettings(Context context) {
|
||||
autoRotate = context.GetSettingBool("autoRotate", autoRotate);
|
||||
spinSpeed = new Vec3(
|
||||
context.GetSettingFloat("spinSpeedX", spinSpeed.X),
|
||||
context.GetSettingFloat("spinSpeedY", spinSpeed.Y),
|
||||
context.GetSettingFloat("spinSpeedZ", spinSpeed.Z)
|
||||
);
|
||||
offset = new Vec3(
|
||||
context.GetSettingFloat("offsetX", offset.X),
|
||||
context.GetSettingFloat("offsetY", offset.Y),
|
||||
context.GetSettingFloat("offsetZ", offset.Z)
|
||||
);
|
||||
targetName = context.GetSettingString("targetName", targetName);
|
||||
}
|
||||
|
||||
private static void SaveSettings(Context context) {
|
||||
context.SetSettingBool("autoRotate", autoRotate);
|
||||
context.SetSettingFloat("spinSpeedX", spinSpeed.X);
|
||||
context.SetSettingFloat("spinSpeedY", spinSpeed.Y);
|
||||
context.SetSettingFloat("spinSpeedZ", spinSpeed.Z);
|
||||
context.SetSettingFloat("offsetX", offset.X);
|
||||
context.SetSettingFloat("offsetY", offset.Y);
|
||||
context.SetSettingFloat("offsetZ", offset.Z);
|
||||
context.SetSettingString("targetName", targetName);
|
||||
}
|
||||
|
||||
private static void ApplyAutoRotate(Context context, float deltaTime) {
|
||||
if (!autoRotate) return;
|
||||
context.Rotation = context.Rotation + (spinSpeed * deltaTime);
|
||||
}
|
||||
|
||||
public static void Script_Begin(IntPtr ctx, float deltaTime) {
|
||||
var context = new Context(ctx);
|
||||
LoadSettings(context);
|
||||
SaveSettings(context);
|
||||
context.EnsureRigidbody(useGravity: true, kinematic: false);
|
||||
context.AddConsoleMessage("Managed script begin (C#)", ConsoleMessageType.Info);
|
||||
}
|
||||
|
||||
public static void Script_OnInspector(IntPtr ctx) {
|
||||
var context = new Context(ctx);
|
||||
LoadSettings(context);
|
||||
SaveSettings(context);
|
||||
context.AddConsoleMessage("Managed inspector hook (no UI yet)", ConsoleMessageType.Info);
|
||||
}
|
||||
|
||||
public static void Script_Spec(IntPtr ctx, float deltaTime) {
|
||||
ApplyAutoRotate(new Context(ctx), deltaTime);
|
||||
}
|
||||
|
||||
public static void Script_TestEditor(IntPtr ctx, float deltaTime) {
|
||||
ApplyAutoRotate(new Context(ctx), deltaTime);
|
||||
}
|
||||
|
||||
public static void Script_TickUpdate(IntPtr ctx, float deltaTime) {
|
||||
ApplyAutoRotate(new Context(ctx), deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Scripts/Managed/SampleInspectorManaged.cs
Normal file
68
Scripts/Managed/SampleInspectorManaged.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
|
||||
namespace ModuCPP {
|
||||
public static class SampleInspectorManaged {
|
||||
private static bool autoRotate = false;
|
||||
private static Vec3 spinSpeed = new Vec3(0f, 45f, 0f);
|
||||
private static Vec3 offset = new Vec3(0f, 1f, 0f);
|
||||
private static string targetName = "MyTarget"; // Stored for parity; object lookup API not wired yet.
|
||||
|
||||
private static void LoadSettings(Context context) {
|
||||
autoRotate = context.GetSettingBool("autoRotate", autoRotate);
|
||||
spinSpeed = new Vec3(
|
||||
context.GetSettingFloat("spinSpeedX", spinSpeed.X),
|
||||
context.GetSettingFloat("spinSpeedY", spinSpeed.Y),
|
||||
context.GetSettingFloat("spinSpeedZ", spinSpeed.Z)
|
||||
);
|
||||
offset = new Vec3(
|
||||
context.GetSettingFloat("offsetX", offset.X),
|
||||
context.GetSettingFloat("offsetY", offset.Y),
|
||||
context.GetSettingFloat("offsetZ", offset.Z)
|
||||
);
|
||||
targetName = context.GetSettingString("targetName", targetName);
|
||||
}
|
||||
|
||||
private static void SaveSettings(Context context) {
|
||||
context.SetSettingBool("autoRotate", autoRotate);
|
||||
context.SetSettingFloat("spinSpeedX", spinSpeed.X);
|
||||
context.SetSettingFloat("spinSpeedY", spinSpeed.Y);
|
||||
context.SetSettingFloat("spinSpeedZ", spinSpeed.Z);
|
||||
context.SetSettingFloat("offsetX", offset.X);
|
||||
context.SetSettingFloat("offsetY", offset.Y);
|
||||
context.SetSettingFloat("offsetZ", offset.Z);
|
||||
context.SetSettingString("targetName", targetName);
|
||||
}
|
||||
|
||||
private static void ApplyAutoRotate(Context context, float deltaTime) {
|
||||
if (!autoRotate) return;
|
||||
context.Rotation = context.Rotation + (spinSpeed * deltaTime);
|
||||
}
|
||||
|
||||
public static void Script_Begin(IntPtr ctx, float deltaTime) {
|
||||
var context = new Context(ctx);
|
||||
LoadSettings(context);
|
||||
SaveSettings(context);
|
||||
context.EnsureRigidbody(useGravity: true, kinematic: false);
|
||||
context.AddConsoleMessage("Managed script begin (C#)", ConsoleMessageType.Info);
|
||||
}
|
||||
|
||||
public static void Script_OnInspector(IntPtr ctx) {
|
||||
var context = new Context(ctx);
|
||||
LoadSettings(context);
|
||||
SaveSettings(context);
|
||||
context.AddConsoleMessage("Managed inspector hook (no UI yet)", ConsoleMessageType.Info);
|
||||
}
|
||||
|
||||
public static void Script_Spec(IntPtr ctx, float deltaTime) {
|
||||
ApplyAutoRotate(new Context(ctx), deltaTime);
|
||||
}
|
||||
|
||||
public static void Script_TestEditor(IntPtr ctx, float deltaTime) {
|
||||
ApplyAutoRotate(new Context(ctx), deltaTime);
|
||||
}
|
||||
|
||||
public static void Script_TickUpdate(IntPtr ctx, float deltaTime) {
|
||||
ApplyAutoRotate(new Context(ctx), deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Scripts/Managed/bin/Debug/net10.0/ModuCPP.deps.json
Normal file
23
Scripts/Managed/bin/Debug/net10.0/ModuCPP.deps.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"runtimeTarget": {
|
||||
"name": ".NETCoreApp,Version=v10.0",
|
||||
"signature": ""
|
||||
},
|
||||
"compilationOptions": {},
|
||||
"targets": {
|
||||
".NETCoreApp,Version=v10.0": {
|
||||
"ModuCPP/1.0.0": {
|
||||
"runtime": {
|
||||
"ModuCPP.dll": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"ModuCPP/1.0.0": {
|
||||
"type": "project",
|
||||
"serviceable": false,
|
||||
"sha512": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Scripts/Managed/bin/Debug/net10.0/ModuCPP.dll
Normal file
BIN
Scripts/Managed/bin/Debug/net10.0/ModuCPP.dll
Normal file
Binary file not shown.
BIN
Scripts/Managed/bin/Debug/net10.0/ModuCPP.pdb
Normal file
BIN
Scripts/Managed/bin/Debug/net10.0/ModuCPP.pdb
Normal file
Binary file not shown.
14
Scripts/Managed/bin/Debug/net10.0/ModuCPP.runtimeconfig.json
Normal file
14
Scripts/Managed/bin/Debug/net10.0/ModuCPP.runtimeconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net10.0",
|
||||
"framework": {
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "10.0.0"
|
||||
},
|
||||
"configProperties": {
|
||||
"System.Runtime.InteropServices.EnableComHosting": false,
|
||||
"System.Runtime.InteropServices.BuiltInComInterop": false,
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Scripts/Managed/bin/Debug/netstandard2.0/ModuCPP.deps.json
Normal file
24
Scripts/Managed/bin/Debug/netstandard2.0/ModuCPP.deps.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"runtimeTarget": {
|
||||
"name": ".NETStandard,Version=v2.0/",
|
||||
"signature": ""
|
||||
},
|
||||
"compilationOptions": {},
|
||||
"targets": {
|
||||
".NETStandard,Version=v2.0": {},
|
||||
".NETStandard,Version=v2.0/": {
|
||||
"ModuCPP/1.0.0": {
|
||||
"runtime": {
|
||||
"ModuCPP.dll": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"ModuCPP/1.0.0": {
|
||||
"type": "project",
|
||||
"serviceable": false,
|
||||
"sha512": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Scripts/Managed/bin/Debug/netstandard2.0/ModuCPP.dll
Normal file
BIN
Scripts/Managed/bin/Debug/netstandard2.0/ModuCPP.dll
Normal file
Binary file not shown.
BIN
Scripts/Managed/bin/Debug/netstandard2.0/ModuCPP.pdb
Normal file
BIN
Scripts/Managed/bin/Debug/netstandard2.0/ModuCPP.pdb
Normal file
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")]
|
||||
22
Scripts/Managed/obj/Debug/net10.0/ModuCPP.AssemblyInfo.cs
Normal file
22
Scripts/Managed/obj/Debug/net10.0/ModuCPP.AssemblyInfo.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("ModuCPP")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+2061d588e7a10416f073bb34ad8bda8e068f291b")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("ModuCPP")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("ModuCPP")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
78499018a8a6914630a79de98a62c3a139d45e8a04deb724bf7e5060d9670375
|
||||
@@ -0,0 +1,17 @@
|
||||
is_global = true
|
||||
build_property.TargetFramework = net10.0
|
||||
build_property.TargetFrameworkIdentifier = .NETCoreApp
|
||||
build_property.TargetFrameworkVersion = v10.0
|
||||
build_property.TargetPlatformMinVersion =
|
||||
build_property.UsingMicrosoftNETSdkWeb =
|
||||
build_property.ProjectTypeGuids =
|
||||
build_property.InvariantGlobalization =
|
||||
build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = ModuCPP
|
||||
build_property.ProjectDir = /home/anemunt/Git-base/Modularity/Scripts/Managed/
|
||||
build_property.EnableComHosting = false
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 10.0
|
||||
build_property.EnableCodeStyleSeverity =
|
||||
@@ -0,0 +1,8 @@
|
||||
// <auto-generated/>
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Net.Http;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
BIN
Scripts/Managed/obj/Debug/net10.0/ModuCPP.assets.cache
Normal file
BIN
Scripts/Managed/obj/Debug/net10.0/ModuCPP.assets.cache
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
0579f849781bddafc2e55261290008c5a7fb6bddd064d155e3e9c2dd44aec502
|
||||
@@ -0,0 +1,14 @@
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/bin/Debug/net10.0/ModuCPP.deps.json
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/bin/Debug/net10.0/ModuCPP.runtimeconfig.json
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/bin/Debug/net10.0/ModuCPP.dll
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/bin/Debug/net10.0/ModuCPP.pdb
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/net10.0/ModuCPP.GeneratedMSBuildEditorConfig.editorconfig
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/net10.0/ModuCPP.AssemblyInfoInputs.cache
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/net10.0/ModuCPP.AssemblyInfo.cs
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/net10.0/ModuCPP.csproj.CoreCompileInputs.cache
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/net10.0/ModuCPP.sourcelink.json
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/net10.0/ModuCPP.dll
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/net10.0/refint/ModuCPP.dll
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/net10.0/ModuCPP.pdb
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/net10.0/ModuCPP.genruntimeconfig.cache
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/net10.0/ref/ModuCPP.dll
|
||||
BIN
Scripts/Managed/obj/Debug/net10.0/ModuCPP.dll
Normal file
BIN
Scripts/Managed/obj/Debug/net10.0/ModuCPP.dll
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
b14c7a505f46d8314ef755360e8bbee5cc4a67ee7d033805e0a7f8e8d9b71b40
|
||||
BIN
Scripts/Managed/obj/Debug/net10.0/ModuCPP.pdb
Normal file
BIN
Scripts/Managed/obj/Debug/net10.0/ModuCPP.pdb
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"documents":{"/home/anemunt/Git-base/Modularity/src/ThirdParty/PhysX/*":"https://raw.githubusercontent.com/NVIDIA-Omniverse/PhysX/09ff24f3279b735e672ff27b155cbf49f6296f4d/*"}}
|
||||
BIN
Scripts/Managed/obj/Debug/net10.0/ref/ModuCPP.dll
Normal file
BIN
Scripts/Managed/obj/Debug/net10.0/ref/ModuCPP.dll
Normal file
Binary file not shown.
BIN
Scripts/Managed/obj/Debug/net10.0/refint/ModuCPP.dll
Normal file
BIN
Scripts/Managed/obj/Debug/net10.0/refint/ModuCPP.dll
Normal file
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
|
||||
@@ -0,0 +1,22 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("ModuCPP")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+2061d588e7a10416f073bb34ad8bda8e068f291b")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("ModuCPP")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("ModuCPP")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
78499018a8a6914630a79de98a62c3a139d45e8a04deb724bf7e5060d9670375
|
||||
@@ -0,0 +1,8 @@
|
||||
is_global = true
|
||||
build_property.RootNamespace = ModuCPP
|
||||
build_property.ProjectDir = /home/anemunt/Git-base/Modularity/Scripts/Managed/
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.CsWinRTUseWindowsUIXamlProjections = false
|
||||
build_property.EffectiveAnalysisLevelStyle =
|
||||
build_property.EnableCodeStyleSeverity =
|
||||
@@ -0,0 +1,8 @@
|
||||
// <auto-generated/>
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Net.Http;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
BIN
Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.assets.cache
Normal file
BIN
Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.assets.cache
Normal file
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
12a173d9ad34d74a13f6f07a58c9a75f8033484b726d3271d6b9bdffb23c227b
|
||||
@@ -0,0 +1,11 @@
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.csproj.AssemblyReference.cache
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.GeneratedMSBuildEditorConfig.editorconfig
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.AssemblyInfoInputs.cache
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.AssemblyInfo.cs
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.csproj.CoreCompileInputs.cache
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.sourcelink.json
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/bin/Debug/netstandard2.0/ModuCPP.deps.json
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/bin/Debug/netstandard2.0/ModuCPP.dll
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/bin/Debug/netstandard2.0/ModuCPP.pdb
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.dll
|
||||
/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.pdb
|
||||
BIN
Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.dll
Normal file
BIN
Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.dll
Normal file
Binary file not shown.
BIN
Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.pdb
Normal file
BIN
Scripts/Managed/obj/Debug/netstandard2.0/ModuCPP.pdb
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"documents":{"/home/anemunt/Git-base/Modularity/src/ThirdParty/PhysX/*":"https://raw.githubusercontent.com/NVIDIA-Omniverse/PhysX/09ff24f3279b735e672ff27b155cbf49f6296f4d/*"}}
|
||||
70
Scripts/Managed/obj/ModuCPP.csproj.nuget.dgspec.json
Normal file
70
Scripts/Managed/obj/ModuCPP.csproj.nuget.dgspec.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"/home/anemunt/Git-base/Modularity/Scripts/Managed/ModuCPP.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"/home/anemunt/Git-base/Modularity/Scripts/Managed/ModuCPP.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "/home/anemunt/Git-base/Modularity/Scripts/Managed/ModuCPP.csproj",
|
||||
"projectName": "ModuCPP",
|
||||
"projectPath": "/home/anemunt/Git-base/Modularity/Scripts/Managed/ModuCPP.csproj",
|
||||
"packagesPath": "/home/anemunt/.nuget/packages/",
|
||||
"outputPath": "/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/",
|
||||
"projectStyle": "PackageReference",
|
||||
"configFilePaths": [
|
||||
"/home/anemunt/.nuget/NuGet/NuGet.Config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"netstandard2.0"
|
||||
],
|
||||
"sources": {
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"netstandard2.0": {
|
||||
"targetAlias": "netstandard2.0",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"netstandard2.0": {
|
||||
"targetAlias": "netstandard2.0",
|
||||
"dependencies": {
|
||||
"NETStandard.Library": {
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[2.0.3, )",
|
||||
"autoReferenced": true
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/10.0.100/RuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Scripts/Managed/obj/ModuCPP.csproj.nuget.g.props
Normal file
15
Scripts/Managed/obj/ModuCPP.csproj.nuget.g.props
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/anemunt/.nuget/packages/</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/anemunt/.nuget/packages/</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="/home/anemunt/.nuget/packages/" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
6
Scripts/Managed/obj/ModuCPP.csproj.nuget.g.targets
Normal file
6
Scripts/Managed/obj/ModuCPP.csproj.nuget.g.targets
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)netstandard.library/2.0.3/build/netstandard2.0/NETStandard.Library.targets" Condition="Exists('$(NuGetPackageRoot)netstandard.library/2.0.3/build/netstandard2.0/NETStandard.Library.targets')" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
247
Scripts/Managed/obj/project.assets.json
Normal file
247
Scripts/Managed/obj/project.assets.json
Normal file
@@ -0,0 +1,247 @@
|
||||
{
|
||||
"version": 3,
|
||||
"targets": {
|
||||
".NETStandard,Version=v2.0": {
|
||||
"Microsoft.NETCore.Platforms/1.1.0": {
|
||||
"type": "package",
|
||||
"compile": {
|
||||
"lib/netstandard1.0/_._": {}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard1.0/_._": {}
|
||||
}
|
||||
},
|
||||
"NETStandard.Library/2.0.3": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "1.1.0"
|
||||
},
|
||||
"compile": {
|
||||
"lib/netstandard1.0/_._": {}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard1.0/_._": {}
|
||||
},
|
||||
"build": {
|
||||
"build/netstandard2.0/NETStandard.Library.targets": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"Microsoft.NETCore.Platforms/1.1.0": {
|
||||
"sha512": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==",
|
||||
"type": "package",
|
||||
"path": "microsoft.netcore.platforms/1.1.0",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"ThirdPartyNotices.txt",
|
||||
"dotnet_library_license.txt",
|
||||
"lib/netstandard1.0/_._",
|
||||
"microsoft.netcore.platforms.1.1.0.nupkg.sha512",
|
||||
"microsoft.netcore.platforms.nuspec",
|
||||
"runtime.json"
|
||||
]
|
||||
},
|
||||
"NETStandard.Library/2.0.3": {
|
||||
"sha512": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
|
||||
"type": "package",
|
||||
"path": "netstandard.library/2.0.3",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"LICENSE.TXT",
|
||||
"THIRD-PARTY-NOTICES.TXT",
|
||||
"build/netstandard2.0/NETStandard.Library.targets",
|
||||
"build/netstandard2.0/ref/Microsoft.Win32.Primitives.dll",
|
||||
"build/netstandard2.0/ref/System.AppContext.dll",
|
||||
"build/netstandard2.0/ref/System.Collections.Concurrent.dll",
|
||||
"build/netstandard2.0/ref/System.Collections.NonGeneric.dll",
|
||||
"build/netstandard2.0/ref/System.Collections.Specialized.dll",
|
||||
"build/netstandard2.0/ref/System.Collections.dll",
|
||||
"build/netstandard2.0/ref/System.ComponentModel.Composition.dll",
|
||||
"build/netstandard2.0/ref/System.ComponentModel.EventBasedAsync.dll",
|
||||
"build/netstandard2.0/ref/System.ComponentModel.Primitives.dll",
|
||||
"build/netstandard2.0/ref/System.ComponentModel.TypeConverter.dll",
|
||||
"build/netstandard2.0/ref/System.ComponentModel.dll",
|
||||
"build/netstandard2.0/ref/System.Console.dll",
|
||||
"build/netstandard2.0/ref/System.Core.dll",
|
||||
"build/netstandard2.0/ref/System.Data.Common.dll",
|
||||
"build/netstandard2.0/ref/System.Data.dll",
|
||||
"build/netstandard2.0/ref/System.Diagnostics.Contracts.dll",
|
||||
"build/netstandard2.0/ref/System.Diagnostics.Debug.dll",
|
||||
"build/netstandard2.0/ref/System.Diagnostics.FileVersionInfo.dll",
|
||||
"build/netstandard2.0/ref/System.Diagnostics.Process.dll",
|
||||
"build/netstandard2.0/ref/System.Diagnostics.StackTrace.dll",
|
||||
"build/netstandard2.0/ref/System.Diagnostics.TextWriterTraceListener.dll",
|
||||
"build/netstandard2.0/ref/System.Diagnostics.Tools.dll",
|
||||
"build/netstandard2.0/ref/System.Diagnostics.TraceSource.dll",
|
||||
"build/netstandard2.0/ref/System.Diagnostics.Tracing.dll",
|
||||
"build/netstandard2.0/ref/System.Drawing.Primitives.dll",
|
||||
"build/netstandard2.0/ref/System.Drawing.dll",
|
||||
"build/netstandard2.0/ref/System.Dynamic.Runtime.dll",
|
||||
"build/netstandard2.0/ref/System.Globalization.Calendars.dll",
|
||||
"build/netstandard2.0/ref/System.Globalization.Extensions.dll",
|
||||
"build/netstandard2.0/ref/System.Globalization.dll",
|
||||
"build/netstandard2.0/ref/System.IO.Compression.FileSystem.dll",
|
||||
"build/netstandard2.0/ref/System.IO.Compression.ZipFile.dll",
|
||||
"build/netstandard2.0/ref/System.IO.Compression.dll",
|
||||
"build/netstandard2.0/ref/System.IO.FileSystem.DriveInfo.dll",
|
||||
"build/netstandard2.0/ref/System.IO.FileSystem.Primitives.dll",
|
||||
"build/netstandard2.0/ref/System.IO.FileSystem.Watcher.dll",
|
||||
"build/netstandard2.0/ref/System.IO.FileSystem.dll",
|
||||
"build/netstandard2.0/ref/System.IO.IsolatedStorage.dll",
|
||||
"build/netstandard2.0/ref/System.IO.MemoryMappedFiles.dll",
|
||||
"build/netstandard2.0/ref/System.IO.Pipes.dll",
|
||||
"build/netstandard2.0/ref/System.IO.UnmanagedMemoryStream.dll",
|
||||
"build/netstandard2.0/ref/System.IO.dll",
|
||||
"build/netstandard2.0/ref/System.Linq.Expressions.dll",
|
||||
"build/netstandard2.0/ref/System.Linq.Parallel.dll",
|
||||
"build/netstandard2.0/ref/System.Linq.Queryable.dll",
|
||||
"build/netstandard2.0/ref/System.Linq.dll",
|
||||
"build/netstandard2.0/ref/System.Net.Http.dll",
|
||||
"build/netstandard2.0/ref/System.Net.NameResolution.dll",
|
||||
"build/netstandard2.0/ref/System.Net.NetworkInformation.dll",
|
||||
"build/netstandard2.0/ref/System.Net.Ping.dll",
|
||||
"build/netstandard2.0/ref/System.Net.Primitives.dll",
|
||||
"build/netstandard2.0/ref/System.Net.Requests.dll",
|
||||
"build/netstandard2.0/ref/System.Net.Security.dll",
|
||||
"build/netstandard2.0/ref/System.Net.Sockets.dll",
|
||||
"build/netstandard2.0/ref/System.Net.WebHeaderCollection.dll",
|
||||
"build/netstandard2.0/ref/System.Net.WebSockets.Client.dll",
|
||||
"build/netstandard2.0/ref/System.Net.WebSockets.dll",
|
||||
"build/netstandard2.0/ref/System.Net.dll",
|
||||
"build/netstandard2.0/ref/System.Numerics.dll",
|
||||
"build/netstandard2.0/ref/System.ObjectModel.dll",
|
||||
"build/netstandard2.0/ref/System.Reflection.Extensions.dll",
|
||||
"build/netstandard2.0/ref/System.Reflection.Primitives.dll",
|
||||
"build/netstandard2.0/ref/System.Reflection.dll",
|
||||
"build/netstandard2.0/ref/System.Resources.Reader.dll",
|
||||
"build/netstandard2.0/ref/System.Resources.ResourceManager.dll",
|
||||
"build/netstandard2.0/ref/System.Resources.Writer.dll",
|
||||
"build/netstandard2.0/ref/System.Runtime.CompilerServices.VisualC.dll",
|
||||
"build/netstandard2.0/ref/System.Runtime.Extensions.dll",
|
||||
"build/netstandard2.0/ref/System.Runtime.Handles.dll",
|
||||
"build/netstandard2.0/ref/System.Runtime.InteropServices.RuntimeInformation.dll",
|
||||
"build/netstandard2.0/ref/System.Runtime.InteropServices.dll",
|
||||
"build/netstandard2.0/ref/System.Runtime.Numerics.dll",
|
||||
"build/netstandard2.0/ref/System.Runtime.Serialization.Formatters.dll",
|
||||
"build/netstandard2.0/ref/System.Runtime.Serialization.Json.dll",
|
||||
"build/netstandard2.0/ref/System.Runtime.Serialization.Primitives.dll",
|
||||
"build/netstandard2.0/ref/System.Runtime.Serialization.Xml.dll",
|
||||
"build/netstandard2.0/ref/System.Runtime.Serialization.dll",
|
||||
"build/netstandard2.0/ref/System.Runtime.dll",
|
||||
"build/netstandard2.0/ref/System.Security.Claims.dll",
|
||||
"build/netstandard2.0/ref/System.Security.Cryptography.Algorithms.dll",
|
||||
"build/netstandard2.0/ref/System.Security.Cryptography.Csp.dll",
|
||||
"build/netstandard2.0/ref/System.Security.Cryptography.Encoding.dll",
|
||||
"build/netstandard2.0/ref/System.Security.Cryptography.Primitives.dll",
|
||||
"build/netstandard2.0/ref/System.Security.Cryptography.X509Certificates.dll",
|
||||
"build/netstandard2.0/ref/System.Security.Principal.dll",
|
||||
"build/netstandard2.0/ref/System.Security.SecureString.dll",
|
||||
"build/netstandard2.0/ref/System.ServiceModel.Web.dll",
|
||||
"build/netstandard2.0/ref/System.Text.Encoding.Extensions.dll",
|
||||
"build/netstandard2.0/ref/System.Text.Encoding.dll",
|
||||
"build/netstandard2.0/ref/System.Text.RegularExpressions.dll",
|
||||
"build/netstandard2.0/ref/System.Threading.Overlapped.dll",
|
||||
"build/netstandard2.0/ref/System.Threading.Tasks.Parallel.dll",
|
||||
"build/netstandard2.0/ref/System.Threading.Tasks.dll",
|
||||
"build/netstandard2.0/ref/System.Threading.Thread.dll",
|
||||
"build/netstandard2.0/ref/System.Threading.ThreadPool.dll",
|
||||
"build/netstandard2.0/ref/System.Threading.Timer.dll",
|
||||
"build/netstandard2.0/ref/System.Threading.dll",
|
||||
"build/netstandard2.0/ref/System.Transactions.dll",
|
||||
"build/netstandard2.0/ref/System.ValueTuple.dll",
|
||||
"build/netstandard2.0/ref/System.Web.dll",
|
||||
"build/netstandard2.0/ref/System.Windows.dll",
|
||||
"build/netstandard2.0/ref/System.Xml.Linq.dll",
|
||||
"build/netstandard2.0/ref/System.Xml.ReaderWriter.dll",
|
||||
"build/netstandard2.0/ref/System.Xml.Serialization.dll",
|
||||
"build/netstandard2.0/ref/System.Xml.XDocument.dll",
|
||||
"build/netstandard2.0/ref/System.Xml.XPath.XDocument.dll",
|
||||
"build/netstandard2.0/ref/System.Xml.XPath.dll",
|
||||
"build/netstandard2.0/ref/System.Xml.XmlDocument.dll",
|
||||
"build/netstandard2.0/ref/System.Xml.XmlSerializer.dll",
|
||||
"build/netstandard2.0/ref/System.Xml.dll",
|
||||
"build/netstandard2.0/ref/System.dll",
|
||||
"build/netstandard2.0/ref/mscorlib.dll",
|
||||
"build/netstandard2.0/ref/netstandard.dll",
|
||||
"build/netstandard2.0/ref/netstandard.xml",
|
||||
"lib/netstandard1.0/_._",
|
||||
"netstandard.library.2.0.3.nupkg.sha512",
|
||||
"netstandard.library.nuspec"
|
||||
]
|
||||
}
|
||||
},
|
||||
"projectFileDependencyGroups": {
|
||||
".NETStandard,Version=v2.0": [
|
||||
"NETStandard.Library >= 2.0.3"
|
||||
]
|
||||
},
|
||||
"packageFolders": {
|
||||
"/home/anemunt/.nuget/packages/": {}
|
||||
},
|
||||
"project": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "/home/anemunt/Git-base/Modularity/Scripts/Managed/ModuCPP.csproj",
|
||||
"projectName": "ModuCPP",
|
||||
"projectPath": "/home/anemunt/Git-base/Modularity/Scripts/Managed/ModuCPP.csproj",
|
||||
"packagesPath": "/home/anemunt/.nuget/packages/",
|
||||
"outputPath": "/home/anemunt/Git-base/Modularity/Scripts/Managed/obj/",
|
||||
"projectStyle": "PackageReference",
|
||||
"configFilePaths": [
|
||||
"/home/anemunt/.nuget/NuGet/NuGet.Config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"netstandard2.0"
|
||||
],
|
||||
"sources": {
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"netstandard2.0": {
|
||||
"targetAlias": "netstandard2.0",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"netstandard2.0": {
|
||||
"targetAlias": "netstandard2.0",
|
||||
"dependencies": {
|
||||
"NETStandard.Library": {
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[2.0.3, )",
|
||||
"autoReferenced": true
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/10.0.100/RuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Scripts/Managed/obj/project.nuget.cache
Normal file
11
Scripts/Managed/obj/project.nuget.cache
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "iTrhv2TT9CE=",
|
||||
"success": true,
|
||||
"projectFilePath": "/home/anemunt/Git-base/Modularity/Scripts/Managed/ModuCPP.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"/home/anemunt/.nuget/packages/microsoft.netcore.platforms/1.1.0/microsoft.netcore.platforms.1.1.0.nupkg.sha512",
|
||||
"/home/anemunt/.nuget/packages/netstandard.library/2.0.3/netstandard.library.2.0.3.nupkg.sha512"
|
||||
],
|
||||
"logs": []
|
||||
}
|
||||
74
Scripts/TopDownMovement2D.cpp
Normal file
74
Scripts/TopDownMovement2D.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "ScriptRuntime.h"
|
||||
#include "SceneObject.h"
|
||||
#include "ThirdParty/imgui/imgui.h"
|
||||
|
||||
namespace {
|
||||
float walkSpeed = 4.0f;
|
||||
float runSpeed = 7.0f;
|
||||
float acceleration = 18.0f;
|
||||
float drag = 8.0f;
|
||||
bool useRigidbody2D = true;
|
||||
bool warnedMissingRb = false;
|
||||
} // namespace
|
||||
|
||||
extern "C" void Script_OnInspector(ScriptContext& ctx) {
|
||||
ctx.AutoSetting("walkSpeed", walkSpeed);
|
||||
ctx.AutoSetting("runSpeed", runSpeed);
|
||||
ctx.AutoSetting("acceleration", acceleration);
|
||||
ctx.AutoSetting("drag", drag);
|
||||
ctx.AutoSetting("useRigidbody2D", useRigidbody2D);
|
||||
|
||||
ImGui::TextUnformatted("Top Down Movement 2D");
|
||||
ImGui::Separator();
|
||||
ImGui::DragFloat("Walk Speed", &walkSpeed, 0.1f, 0.0f, 50.0f, "%.2f");
|
||||
ImGui::DragFloat("Run Speed", &runSpeed, 0.1f, 0.0f, 80.0f, "%.2f");
|
||||
ImGui::DragFloat("Acceleration", &acceleration, 0.1f, 0.0f, 200.0f, "%.2f");
|
||||
ImGui::DragFloat("Drag", &drag, 0.1f, 0.0f, 200.0f, "%.2f");
|
||||
ImGui::Checkbox("Use Rigidbody2D", &useRigidbody2D);
|
||||
}
|
||||
|
||||
void TickUpdate(ScriptContext& ctx, float dt) {
|
||||
if (!ctx.object || dt <= 0.0f) return;
|
||||
|
||||
glm::vec2 input(0.0f);
|
||||
if (ImGui::IsKeyDown(ImGuiKey_W)) input.y += 1.0f;
|
||||
if (ImGui::IsKeyDown(ImGuiKey_S)) input.y -= 1.0f;
|
||||
if (ImGui::IsKeyDown(ImGuiKey_D)) input.x += 1.0f;
|
||||
if (ImGui::IsKeyDown(ImGuiKey_A)) input.x -= 1.0f;
|
||||
if (glm::length(input) > 1e-3f) input = glm::normalize(input);
|
||||
|
||||
float speed = ctx.IsSprintDown() ? runSpeed : walkSpeed;
|
||||
glm::vec2 targetVel = input * speed;
|
||||
|
||||
if (useRigidbody2D) {
|
||||
if (!ctx.HasRigidbody2D()) {
|
||||
if (!warnedMissingRb) {
|
||||
ctx.AddConsoleMessage("TopDownMovement2D: add Rigidbody2D to use velocity-based motion.", ConsoleMessageType::Warning);
|
||||
warnedMissingRb = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
glm::vec2 vel(0.0f);
|
||||
ctx.GetRigidbody2DVelocity(vel);
|
||||
if (acceleration <= 0.0f) {
|
||||
vel = targetVel;
|
||||
} else {
|
||||
glm::vec2 dv = targetVel - vel;
|
||||
float maxDelta = acceleration * dt;
|
||||
float len = glm::length(dv);
|
||||
if (len > maxDelta && len > 1e-4f) {
|
||||
dv *= (maxDelta / len);
|
||||
}
|
||||
vel += dv;
|
||||
}
|
||||
if (glm::length(input) < 1e-3f && drag > 0.0f) {
|
||||
float damp = std::max(0.0f, 1.0f - drag * dt);
|
||||
vel *= damp;
|
||||
}
|
||||
ctx.SetRigidbody2DVelocity(vel);
|
||||
} else {
|
||||
glm::vec2 pos = ctx.object->ui.position;
|
||||
pos += targetVel * dt;
|
||||
ctx.SetPosition2D(pos);
|
||||
}
|
||||
}
|
||||
BIN
TheSunset.ttf
Normal file
BIN
TheSunset.ttf
Normal file
Binary file not shown.
BIN
Thesunsethd-Regular (1).ttf
Normal file
BIN
Thesunsethd-Regular (1).ttf
Normal file
Binary file not shown.
44
build.sh
44
build.sh
@@ -23,18 +23,56 @@ trap finish EXIT
|
||||
|
||||
echo -e "================================\n Modularity - Native Linux Builder\n================================"
|
||||
|
||||
clean_build=0
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "--clean" ]; then
|
||||
clean_build=1
|
||||
fi
|
||||
done
|
||||
|
||||
git submodule update --init --recursive
|
||||
|
||||
if [ -d "build" ]; then
|
||||
echo -e "[i]: Oh! We found an existing build directory.\nRemoving existing folder..."
|
||||
if [ -d "build" ] && [ $clean_build -eq 1 ]; then
|
||||
echo -e "[i]: Cleaning existing build directory..."
|
||||
rm -rf build/
|
||||
echo -e "[i]: Build Has been Removed\nContinuing build"
|
||||
fi
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake .. -DMONO_ROOT=/usr
|
||||
cmake --build . -- -j"$(nproc)"
|
||||
|
||||
mkdir -p Packages/ThirdParty
|
||||
find . -type f \( -name "*.a" -o -name "*.so" -o -name "*.dylib" -o -name "*.lib" \) \
|
||||
-not -path "./Packages/*" -exec cp -f {} Packages/ThirdParty/ \;
|
||||
|
||||
mkdir -p Packages/Engine
|
||||
find . -type f \( -name "libcore*" -o -name "core*.lib" -o -name "core*.dll" \) \
|
||||
-not -path "./Packages/*" -exec cp -f {} Packages/Engine/ \;
|
||||
|
||||
cd ..
|
||||
|
||||
player_cache_dir="build/player-cache"
|
||||
if [ $clean_build -eq 1 ] && [ -d "$player_cache_dir" ]; then
|
||||
echo -e "[i]: Cleaning player cache build directory..."
|
||||
rm -rf "$player_cache_dir"
|
||||
fi
|
||||
|
||||
mkdir -p "$player_cache_dir"
|
||||
cmake -S . -B "$player_cache_dir" -DMONO_ROOT=/usr -DCMAKE_BUILD_TYPE=Release -DMODULARITY_BUILD_EDITOR=OFF
|
||||
cmake --build "$player_cache_dir" --target ModularityPlayer -- -j"$(nproc)"
|
||||
|
||||
mkdir -p "$player_cache_dir/Packages/ThirdParty"
|
||||
find "$player_cache_dir" -type f \( -name "*.a" -o -name "*.so" -o -name "*.dylib" -o -name "*.lib" \) \
|
||||
-not -path "$player_cache_dir/Packages/*" -exec cp -f {} "$player_cache_dir/Packages/ThirdParty/" \;
|
||||
|
||||
mkdir -p "$player_cache_dir/Packages/Engine"
|
||||
find "$player_cache_dir" -type f \( -name "libcore*" -o -name "core*.lib" -o -name "core*.dll" \) \
|
||||
-not -path "$player_cache_dir/Packages/*" -exec cp -f {} "$player_cache_dir/Packages/Engine/" \;
|
||||
|
||||
cd build
|
||||
|
||||
cp -r ../Resources .
|
||||
cp Resources/imgui.ini .
|
||||
ln -sf build/compile_commands.json compile_commands.json
|
||||
@@ -12,6 +12,7 @@ Scripts in Modularity are native C++ code compiled into shared libraries and loa
|
||||
|
||||
## Table of contents
|
||||
- [Quickstart](#quickstart)
|
||||
- [C# managed scripting (experimental)](#c-managed-scripting-experimental)
|
||||
- [Scripts.modu](#scriptsmodu)
|
||||
- [How compilation works](#how-compilation-works)
|
||||
- [Lifecycle hooks](#lifecycle-hooks)
|
||||
@@ -36,6 +37,29 @@ Scripts in Modularity are native C++ code compiled into shared libraries and loa
|
||||
- In the Inspector’s script component menu, choose **Compile**.
|
||||
5. Implement a tick hook (`TickUpdate`) and observe behavior in play mode.
|
||||
|
||||
## C# managed scripting (experimental)
|
||||
Modularity can host managed C# scripts via the .NET runtime. This is an early, minimal integration
|
||||
intended for movement/transform tests and simple Rigidbody control.
|
||||
|
||||
1. Build the managed project (this now happens automatically when you compile a C# script):
|
||||
- `dotnet build Scripts/Managed/ModuCPP.csproj`
|
||||
2. In the Inspector, add a Script component and set:
|
||||
- `Language` = **C#**
|
||||
- `Assembly Path` = `Scripts/Managed/bin/Debug/net10.0/ModuCPP.dll` (or point at `Scripts/Managed/SampleInspector.cs`)
|
||||
- `Type` = `ModuCPP.SampleInspector`
|
||||
3. Enter play mode. The sample script will auto-rotate the object.
|
||||
|
||||
Notes:
|
||||
- The `ModuCPP.runtimeconfig.json` produced by `dotnet build` must sit next to the DLL.
|
||||
- The managed host currently expects the script assembly to also contain `ModuCPP.Host`
|
||||
(use the provided `Scripts/Managed/ModuCPP.csproj` as the entry assembly).
|
||||
- The managed API surface is tiny for now: position/rotation/scale, basic Rigidbody velocity/forces,
|
||||
settings, and console logging.
|
||||
- Requires a local .NET runtime (Windows/Linux). If the runtime is missing, the engine will fail to
|
||||
initialize managed scripts and report the error in the inspector.
|
||||
- Managed hooks should be exported as `Script_Begin`, `Script_TickUpdate`, etc. via
|
||||
`[UnmanagedCallersOnly]` in the C# script class.
|
||||
|
||||
## Scripts.modu
|
||||
Each project has a `Scripts.modu` file (auto-created if missing). It controls compilation.
|
||||
|
||||
|
||||
17
docs/mono-embedding.md
Normal file
17
docs/mono-embedding.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Mono Embedding Setup
|
||||
|
||||
This project uses Mono embedding for managed (C#) scripts.
|
||||
|
||||
Expected layout (vendored):
|
||||
`src/ThirdParty/mono/`
|
||||
- `include/mono-2.0/`
|
||||
- `lib/` (or `lib64/`) with `mono-2.0-sgen` library
|
||||
- `etc/mono/` (config files)
|
||||
- `lib/mono/4.5/` (framework assemblies)
|
||||
|
||||
You can override the runtime location at runtime with:
|
||||
`MODU_MONO_ROOT=/path/to/mono`
|
||||
|
||||
Build notes:
|
||||
- The CMake cache variable `MONO_ROOT` controls where headers/libs are found.
|
||||
- Managed scripts target `netstandard2.0` and are built with `dotnet build`.
|
||||
@@ -19,6 +19,7 @@ public:
|
||||
void setVec2(const std::string &name, const glm::vec2 &value) const;
|
||||
void setVec3(const std::string &name, const glm::vec3 &value) const;
|
||||
void setMat4(const std::string &name, const glm::mat4 &mat) const;
|
||||
void setMat4Array(const std::string &name, const glm::mat4 *data, int count) const;
|
||||
|
||||
private:
|
||||
std::string readShaderFile(const char* filePath);
|
||||
|
||||
@@ -2,10 +2,143 @@
|
||||
#include "../include/ThirdParty/miniaudio.h"
|
||||
#include "AudioSystem.h"
|
||||
#include <cmath>
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
|
||||
namespace {
|
||||
constexpr size_t kPreviewBuckets = 800;
|
||||
constexpr ma_uint32 kPreviewChunkFrames = 2048;
|
||||
constexpr float kReverbSmoothing = 0.12f;
|
||||
constexpr size_t kReverbCombCount = 4;
|
||||
constexpr size_t kReverbAllpassCount = 2;
|
||||
constexpr float kReverbPreDelayMaxSeconds = 0.2f;
|
||||
constexpr float kReverbReflectionsMaxSeconds = 0.1f;
|
||||
|
||||
float DbToLinear(float db) {
|
||||
return std::pow(10.0f, db / 20.0f);
|
||||
}
|
||||
|
||||
struct ReverbNodeVTable {
|
||||
ma_node_vtable vtable;
|
||||
};
|
||||
|
||||
static void reverb_node_process(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn,
|
||||
float** ppFramesOut, ma_uint32* pFrameCountOut) {
|
||||
if (!pNode || !ppFramesIn || !ppFramesOut) return;
|
||||
auto* node = reinterpret_cast<AudioSystem::SimpleReverbNode*>(pNode);
|
||||
(void)pFrameCountIn;
|
||||
const float* input = ppFramesIn[0];
|
||||
float* output = ppFramesOut[0];
|
||||
if (!input || !output) return;
|
||||
|
||||
ma_uint32 frameCount = *pFrameCountOut;
|
||||
int channels = node->channels;
|
||||
float decayTime = std::max(0.1f, node->decayTime);
|
||||
float diffusion = std::clamp(node->diffusion, 0.0f, 100.0f);
|
||||
float density = std::clamp(node->density, 0.0f, 100.0f);
|
||||
float preDelaySeconds = std::clamp(node->preDelaySeconds, 0.0f, kReverbPreDelayMaxSeconds);
|
||||
float reflectionsDelaySeconds = std::clamp(node->reflectionsDelaySeconds, 0.0f, kReverbReflectionsMaxSeconds);
|
||||
size_t preDelayFrames = static_cast<size_t>(preDelaySeconds * static_cast<float>(node->sampleRate));
|
||||
size_t reflectionsDelayFrames = static_cast<size_t>(reflectionsDelaySeconds * static_cast<float>(node->sampleRate));
|
||||
float wetGain = std::clamp(node->wetGain, 0.0f, 2.0f);
|
||||
float reflectionsGain = std::clamp(node->reflectionsGain, 0.0f, 2.0f);
|
||||
|
||||
float diffusionNorm = diffusion / 100.0f;
|
||||
float densityNorm = density / 100.0f;
|
||||
float allpassGain = 0.2f + 0.55f * diffusionNorm;
|
||||
float densityScale = 0.6f + 0.4f * densityNorm;
|
||||
float combGain = 1.0f / static_cast<float>(node->combBuffers.size());
|
||||
std::array<float, kReverbCombCount> combFeedback{};
|
||||
for (size_t i = 0; i < node->combBuffers.size(); ++i) {
|
||||
float delaySec = static_cast<float>(node->combBuffers[i].size() / channels) / static_cast<float>(node->sampleRate);
|
||||
combFeedback[i] = std::pow(10.0f, (-3.0f * delaySec) / decayTime) * densityScale;
|
||||
}
|
||||
|
||||
float cutoffHz = std::clamp(node->hfReference * node->decayHFRatio, 500.0f, 20000.0f);
|
||||
float lpAlpha = std::exp(-2.0f * PI * cutoffHz / static_cast<float>(node->sampleRate));
|
||||
|
||||
for (ma_uint32 frame = 0; frame < frameCount; ++frame) {
|
||||
size_t preReadIndex = node->preDelayMaxFrames > 0
|
||||
? (node->preDelayIndex + node->preDelayMaxFrames - preDelayFrames) % node->preDelayMaxFrames
|
||||
: 0;
|
||||
size_t reflectionsReadIndex = node->reflectionsMaxFrames > 0
|
||||
? (node->reflectionsIndex + node->reflectionsMaxFrames - reflectionsDelayFrames) % node->reflectionsMaxFrames
|
||||
: 0;
|
||||
|
||||
for (int ch = 0; ch < channels; ++ch) {
|
||||
float inSample = input[frame * channels + ch];
|
||||
float preSample = inSample;
|
||||
if (!node->preDelayBuffer.empty()) {
|
||||
size_t writeBase = node->preDelayIndex * channels;
|
||||
size_t readBase = preReadIndex * channels;
|
||||
preSample = node->preDelayBuffer[readBase + ch];
|
||||
node->preDelayBuffer[writeBase + ch] = inSample;
|
||||
}
|
||||
|
||||
float reflectionsSample = 0.0f;
|
||||
if (!node->reflectionsBuffer.empty()) {
|
||||
size_t writeBase = node->reflectionsIndex * channels;
|
||||
size_t readBase = reflectionsReadIndex * channels;
|
||||
reflectionsSample = node->reflectionsBuffer[readBase + ch];
|
||||
node->reflectionsBuffer[writeBase + ch] = preSample;
|
||||
}
|
||||
|
||||
float combSum = 0.0f;
|
||||
for (size_t i = 0; i < node->combBuffers.size(); ++i) {
|
||||
auto& buffer = node->combBuffers[i];
|
||||
size_t idx = node->combIndex[i];
|
||||
size_t base = idx * channels + ch;
|
||||
float y = buffer[base];
|
||||
buffer[base] = preSample + y * combFeedback[i];
|
||||
combSum += y;
|
||||
}
|
||||
|
||||
combSum *= combGain;
|
||||
float apOut = combSum;
|
||||
for (size_t i = 0; i < node->allpassBuffers.size(); ++i) {
|
||||
auto& buffer = node->allpassBuffers[i];
|
||||
size_t idx = node->allpassIndex[i];
|
||||
size_t base = idx * channels + ch;
|
||||
float buf = buffer[base];
|
||||
float y = -allpassGain * apOut + buf;
|
||||
buffer[base] = apOut + buf * allpassGain;
|
||||
apOut = y;
|
||||
}
|
||||
|
||||
float wetSample = apOut * wetGain + reflectionsSample * reflectionsGain;
|
||||
float lp = node->lpState.empty() ? wetSample : (lpAlpha * node->lpState[ch] + (1.0f - lpAlpha) * wetSample);
|
||||
if (!node->lpState.empty()) node->lpState[ch] = lp;
|
||||
output[frame * channels + ch] = lp;
|
||||
}
|
||||
|
||||
if (!node->preDelayBuffer.empty()) {
|
||||
node->preDelayIndex = (node->preDelayIndex + 1) % node->preDelayMaxFrames;
|
||||
}
|
||||
if (!node->reflectionsBuffer.empty()) {
|
||||
node->reflectionsIndex = (node->reflectionsIndex + 1) % node->reflectionsMaxFrames;
|
||||
}
|
||||
for (size_t i = 0; i < node->combIndex.size(); ++i) {
|
||||
node->combIndex[i] = (node->combIndex[i] + 1) % (node->combBuffers[i].size() / channels);
|
||||
}
|
||||
for (size_t i = 0; i < node->allpassIndex.size(); ++i) {
|
||||
node->allpassIndex[i] = (node->allpassIndex[i] + 1) % (node->allpassBuffers[i].size() / channels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ma_result reverb_node_get_required_input_frames(ma_node* pNode, ma_uint32 outputFrameCount, ma_uint32* pInputFrameCount) {
|
||||
(void)pNode;
|
||||
if (pInputFrameCount) *pInputFrameCount = outputFrameCount;
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
static ma_node_vtable g_reverb_node_vtable = {
|
||||
reverb_node_process,
|
||||
reverb_node_get_required_input_frames,
|
||||
1,
|
||||
1,
|
||||
0
|
||||
};
|
||||
}
|
||||
|
||||
bool AudioSystem::init() {
|
||||
@@ -15,6 +148,65 @@ bool AudioSystem::init() {
|
||||
std::cerr << "AudioSystem: failed to init miniaudio (" << res << ")\n";
|
||||
return false;
|
||||
}
|
||||
ma_uint32 channels = ma_engine_get_channels(&engine);
|
||||
ma_uint32 sampleRate = ma_engine_get_sample_rate(&engine);
|
||||
ma_splitter_node_config splitterConfig = ma_splitter_node_config_init(channels);
|
||||
res = ma_splitter_node_init(ma_engine_get_node_graph(&engine), &splitterConfig, nullptr, &reverbSplitter);
|
||||
if (res == MA_SUCCESS) {
|
||||
ma_node_config nodeConfig = ma_node_config_init();
|
||||
nodeConfig.vtable = &g_reverb_node_vtable;
|
||||
nodeConfig.pInputChannels = reinterpret_cast<const ma_uint32*>(&channels);
|
||||
nodeConfig.pOutputChannels = reinterpret_cast<const ma_uint32*>(&channels);
|
||||
res = ma_node_init(ma_engine_get_node_graph(&engine), &nodeConfig, nullptr, reinterpret_cast<ma_node*>(&reverbNode));
|
||||
if (res == MA_SUCCESS) {
|
||||
reverbNode.channels = static_cast<int>(channels);
|
||||
reverbNode.sampleRate = static_cast<int>(sampleRate);
|
||||
reverbNode.preDelayMaxFrames = static_cast<size_t>(kReverbPreDelayMaxSeconds * sampleRate);
|
||||
reverbNode.reflectionsMaxFrames = static_cast<size_t>(kReverbReflectionsMaxSeconds * sampleRate);
|
||||
reverbNode.preDelayBuffer.assign(reverbNode.preDelayMaxFrames * channels, 0.0f);
|
||||
reverbNode.reflectionsBuffer.assign(reverbNode.reflectionsMaxFrames * channels, 0.0f);
|
||||
reverbNode.lpState.assign(channels, 0.0f);
|
||||
|
||||
const float combDelayMs[kReverbCombCount] = { 29.7f, 37.1f, 41.1f, 43.7f };
|
||||
reverbNode.combBuffers.resize(kReverbCombCount);
|
||||
reverbNode.combIndex.assign(kReverbCombCount, 0);
|
||||
for (size_t i = 0; i < kReverbCombCount; ++i) {
|
||||
size_t frames = static_cast<size_t>((combDelayMs[i] / 1000.0f) * sampleRate);
|
||||
frames = std::max<size_t>(1, frames);
|
||||
reverbNode.combBuffers[i].assign(frames * channels, 0.0f);
|
||||
}
|
||||
|
||||
const float allpassDelayMs[kReverbAllpassCount] = { 5.0f, 1.7f };
|
||||
reverbNode.allpassBuffers.resize(kReverbAllpassCount);
|
||||
reverbNode.allpassIndex.assign(kReverbAllpassCount, 0);
|
||||
for (size_t i = 0; i < kReverbAllpassCount; ++i) {
|
||||
size_t frames = static_cast<size_t>((allpassDelayMs[i] / 1000.0f) * sampleRate);
|
||||
frames = std::max<size_t>(1, frames);
|
||||
reverbNode.allpassBuffers[i].assign(frames * channels, 0.0f);
|
||||
}
|
||||
|
||||
ma_node_attach_output_bus(reinterpret_cast<ma_node*>(&reverbSplitter), 0, ma_engine_get_endpoint(&engine), 0);
|
||||
ma_node_attach_output_bus(reinterpret_cast<ma_node*>(&reverbSplitter), 1, reinterpret_cast<ma_node*>(&reverbNode), 0);
|
||||
ma_node_attach_output_bus(reinterpret_cast<ma_node*>(&reverbNode), 0, ma_engine_get_endpoint(&engine), 0);
|
||||
ma_sound_group_config groupConfig = ma_sound_group_config_init_2(&engine);
|
||||
groupConfig.pInitialAttachment = reinterpret_cast<ma_node*>(&reverbSplitter);
|
||||
groupConfig.initialAttachmentInputBusIndex = 0;
|
||||
res = ma_sound_group_init_ex(&engine, &groupConfig, &reverbGroup);
|
||||
if (res == MA_SUCCESS) {
|
||||
reverbReady = true;
|
||||
ma_sound_group_set_spatialization_enabled(&reverbGroup, MA_FALSE);
|
||||
ma_sound_group_set_attenuation_model(&reverbGroup, ma_attenuation_model_none);
|
||||
ma_sound_group_start(&reverbGroup);
|
||||
ma_node_set_output_bus_volume(reinterpret_cast<ma_node*>(&reverbSplitter), 0, 1.0f);
|
||||
} else {
|
||||
ma_node_uninit(reinterpret_cast<ma_node*>(&reverbNode), nullptr);
|
||||
ma_splitter_node_uninit(&reverbSplitter, nullptr);
|
||||
}
|
||||
} else {
|
||||
ma_splitter_node_uninit(&reverbSplitter, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
return true;
|
||||
}
|
||||
@@ -22,6 +214,7 @@ bool AudioSystem::init() {
|
||||
void AudioSystem::shutdown() {
|
||||
stopPreview();
|
||||
destroyActiveSounds();
|
||||
shutdownReverbGraph();
|
||||
if (initialized) {
|
||||
ma_engine_uninit(&engine);
|
||||
initialized = false;
|
||||
@@ -81,7 +274,7 @@ bool AudioSystem::ensureSoundFor(const SceneObject& obj) {
|
||||
&engine,
|
||||
obj.audioSource.clipPath.c_str(),
|
||||
MA_SOUND_FLAG_STREAM,
|
||||
nullptr,
|
||||
reverbReady ? &reverbGroup : nullptr,
|
||||
nullptr,
|
||||
&snd->sound
|
||||
);
|
||||
@@ -104,6 +297,26 @@ void AudioSystem::refreshSoundParams(const SceneObject& obj, ActiveSound& snd) {
|
||||
ma_sound_set_looping(&snd.sound, obj.audioSource.loop ? MA_TRUE : MA_FALSE);
|
||||
ma_sound_set_volume(&snd.sound, obj.audioSource.volume);
|
||||
ma_sound_set_spatialization_enabled(&snd.sound, obj.audioSource.spatial ? MA_TRUE : MA_FALSE);
|
||||
if (obj.audioSource.spatial) {
|
||||
switch (obj.audioSource.rolloffMode) {
|
||||
case AudioRolloffMode::Linear:
|
||||
ma_sound_set_attenuation_model(&snd.sound, ma_attenuation_model_linear);
|
||||
break;
|
||||
case AudioRolloffMode::Exponential:
|
||||
ma_sound_set_attenuation_model(&snd.sound, ma_attenuation_model_exponential);
|
||||
break;
|
||||
case AudioRolloffMode::Custom:
|
||||
ma_sound_set_attenuation_model(&snd.sound, ma_attenuation_model_none);
|
||||
break;
|
||||
case AudioRolloffMode::Logarithmic:
|
||||
default:
|
||||
ma_sound_set_attenuation_model(&snd.sound, ma_attenuation_model_inverse);
|
||||
break;
|
||||
}
|
||||
ma_sound_set_rolloff(&snd.sound, std::max(0.01f, obj.audioSource.rolloff));
|
||||
} else {
|
||||
ma_sound_set_attenuation_model(&snd.sound, ma_attenuation_model_none);
|
||||
}
|
||||
ma_sound_set_min_distance(&snd.sound, minDist);
|
||||
ma_sound_set_max_distance(&snd.sound, maxDist);
|
||||
ma_sound_set_position(&snd.sound, obj.position.x, obj.position.y, obj.position.z);
|
||||
@@ -120,6 +333,7 @@ void AudioSystem::update(const std::vector<SceneObject>& objects, const Camera&
|
||||
ma_engine_listener_set_position(&engine, 0, listenerCamera.position.x, listenerCamera.position.y, listenerCamera.position.z);
|
||||
ma_engine_listener_set_direction(&engine, 0, listenerCamera.front.x, listenerCamera.front.y, listenerCamera.front.z);
|
||||
ma_engine_listener_set_world_up(&engine, 0, listenerCamera.up.x, listenerCamera.up.y, listenerCamera.up.z);
|
||||
updateReverb(objects, listenerCamera.position);
|
||||
|
||||
if (!playing) {
|
||||
destroyActiveSounds();
|
||||
@@ -144,6 +358,10 @@ void AudioSystem::update(const std::vector<SceneObject>& objects, const Camera&
|
||||
|
||||
if (ensureSoundFor(obj)) {
|
||||
refreshSoundParams(obj, *activeSounds[obj.id]);
|
||||
if (obj.audioSource.spatial && obj.audioSource.rolloffMode == AudioRolloffMode::Custom) {
|
||||
float attenuation = computeCustomAttenuation(obj, listenerCamera.position);
|
||||
ma_sound_set_volume(&activeSounds[obj.id]->sound, obj.audioSource.volume * attenuation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +471,152 @@ bool AudioSystem::setObjectVolume(const SceneObject& obj, float volume) {
|
||||
return true;
|
||||
}
|
||||
|
||||
float AudioSystem::computeCustomAttenuation(const SceneObject& obj, const glm::vec3& listenerPos) const {
|
||||
float minDist = std::max(0.1f, obj.audioSource.minDistance);
|
||||
float maxDist = std::max(obj.audioSource.maxDistance, minDist + 0.5f);
|
||||
float dist = glm::length(listenerPos - obj.position);
|
||||
if (dist <= minDist) return 1.0f;
|
||||
if (dist >= maxDist) return std::clamp(obj.audioSource.customEndGain, 0.0f, 1.0f);
|
||||
|
||||
float range = maxDist - minDist;
|
||||
float midRatio = std::clamp(obj.audioSource.customMidDistance, 0.0f, 1.0f);
|
||||
float midDist = minDist + range * midRatio;
|
||||
float midGain = std::clamp(obj.audioSource.customMidGain, 0.0f, 1.0f);
|
||||
float endGain = std::clamp(obj.audioSource.customEndGain, 0.0f, 1.0f);
|
||||
|
||||
if (dist <= midDist) {
|
||||
float t = (dist - minDist) / std::max(0.001f, midDist - minDist);
|
||||
return std::clamp(1.0f + (midGain - 1.0f) * t, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
float t = (dist - midDist) / std::max(0.001f, maxDist - midDist);
|
||||
return std::clamp(midGain + (endGain - midGain) * t, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
void AudioSystem::updateReverb(const std::vector<SceneObject>& objects, const glm::vec3& listenerPos) {
|
||||
if (!reverbReady) return;
|
||||
float blend = 0.0f;
|
||||
ReverbSettings target = getReverbTarget(objects, listenerPos, blend);
|
||||
applyReverbSettings(target, blend);
|
||||
}
|
||||
|
||||
AudioSystem::ReverbSettings AudioSystem::getReverbTarget(const std::vector<SceneObject>& objects, const glm::vec3& listenerPos, float& outBlend) const {
|
||||
ReverbSettings target{};
|
||||
float bestBlend = 0.0f;
|
||||
|
||||
for (const auto& obj : objects) {
|
||||
if (!obj.enabled || !obj.hasReverbZone || !obj.reverbZone.enabled) continue;
|
||||
const auto& zone = obj.reverbZone;
|
||||
float blend = 0.0f;
|
||||
|
||||
if (zone.shape == ReverbZoneShape::Sphere) {
|
||||
float minDist = std::max(0.0f, zone.minDistance);
|
||||
float maxDist = std::max(zone.maxDistance, minDist + 0.01f);
|
||||
float radius = std::max(0.01f, zone.radius);
|
||||
float dist = glm::length(listenerPos - obj.position);
|
||||
if (dist > radius) continue;
|
||||
maxDist = std::min(maxDist, radius);
|
||||
if (dist >= maxDist) continue;
|
||||
if (dist <= minDist) {
|
||||
blend = 1.0f;
|
||||
} else {
|
||||
blend = std::clamp((maxDist - dist) / (maxDist - minDist), 0.0f, 1.0f);
|
||||
}
|
||||
} else {
|
||||
glm::vec3 halfSize = glm::max(zone.boxSize * 0.5f, glm::vec3(0.01f));
|
||||
glm::vec3 delta = glm::abs(listenerPos - obj.position);
|
||||
if (delta.x > halfSize.x || delta.y > halfSize.y || delta.z > halfSize.z) continue;
|
||||
float edgeDistance = std::min({halfSize.x - delta.x, halfSize.y - delta.y, halfSize.z - delta.z});
|
||||
if (zone.blendDistance <= 0.001f) {
|
||||
blend = 1.0f;
|
||||
} else {
|
||||
blend = std::clamp(edgeDistance / zone.blendDistance, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (blend > bestBlend) {
|
||||
bestBlend = blend;
|
||||
target.room = zone.room;
|
||||
target.roomHF = zone.roomHF;
|
||||
target.roomLF = zone.roomLF;
|
||||
target.decayTime = zone.decayTime;
|
||||
target.decayHFRatio = zone.decayHFRatio;
|
||||
target.reflections = zone.reflections;
|
||||
target.reflectionsDelay = zone.reflectionsDelay;
|
||||
target.reverb = zone.reverb;
|
||||
target.reverbDelay = zone.reverbDelay;
|
||||
target.hfReference = zone.hfReference;
|
||||
target.lfReference = zone.lfReference;
|
||||
target.roomRolloffFactor = zone.roomRolloffFactor;
|
||||
target.diffusion = zone.diffusion;
|
||||
target.density = zone.density;
|
||||
}
|
||||
}
|
||||
|
||||
outBlend = bestBlend;
|
||||
return target;
|
||||
}
|
||||
|
||||
void AudioSystem::applyReverbSettings(const ReverbSettings& target, float blend) {
|
||||
ReverbSettings mixed{};
|
||||
mixed.room = target.room;
|
||||
mixed.roomHF = target.roomHF;
|
||||
mixed.roomLF = target.roomLF;
|
||||
mixed.decayTime = std::max(0.1f, target.decayTime);
|
||||
mixed.decayHFRatio = std::clamp(target.decayHFRatio, 0.1f, 2.0f);
|
||||
mixed.reflections = target.reflections;
|
||||
mixed.reflectionsDelay = std::clamp(target.reflectionsDelay, 0.0f, kReverbReflectionsMaxSeconds);
|
||||
mixed.reverb = target.reverb;
|
||||
mixed.reverbDelay = std::clamp(target.reverbDelay, 0.0f, kReverbPreDelayMaxSeconds);
|
||||
mixed.hfReference = std::clamp(target.hfReference, 1000.0f, 20000.0f);
|
||||
mixed.lfReference = std::clamp(target.lfReference, 20.0f, 1000.0f);
|
||||
mixed.roomRolloffFactor = std::max(0.0f, target.roomRolloffFactor);
|
||||
mixed.diffusion = std::clamp(target.diffusion, 0.0f, 100.0f);
|
||||
mixed.density = std::clamp(target.density, 0.0f, 100.0f);
|
||||
|
||||
currentReverb.room = currentReverb.room + (mixed.room - currentReverb.room) * kReverbSmoothing;
|
||||
currentReverb.roomHF = currentReverb.roomHF + (mixed.roomHF - currentReverb.roomHF) * kReverbSmoothing;
|
||||
currentReverb.roomLF = currentReverb.roomLF + (mixed.roomLF - currentReverb.roomLF) * kReverbSmoothing;
|
||||
currentReverb.decayTime = currentReverb.decayTime + (mixed.decayTime - currentReverb.decayTime) * kReverbSmoothing;
|
||||
currentReverb.decayHFRatio = currentReverb.decayHFRatio + (mixed.decayHFRatio - currentReverb.decayHFRatio) * kReverbSmoothing;
|
||||
currentReverb.reflections = currentReverb.reflections + (mixed.reflections - currentReverb.reflections) * kReverbSmoothing;
|
||||
currentReverb.reflectionsDelay = currentReverb.reflectionsDelay + (mixed.reflectionsDelay - currentReverb.reflectionsDelay) * kReverbSmoothing;
|
||||
currentReverb.reverb = currentReverb.reverb + (mixed.reverb - currentReverb.reverb) * kReverbSmoothing;
|
||||
currentReverb.reverbDelay = currentReverb.reverbDelay + (mixed.reverbDelay - currentReverb.reverbDelay) * kReverbSmoothing;
|
||||
currentReverb.hfReference = currentReverb.hfReference + (mixed.hfReference - currentReverb.hfReference) * kReverbSmoothing;
|
||||
currentReverb.lfReference = currentReverb.lfReference + (mixed.lfReference - currentReverb.lfReference) * kReverbSmoothing;
|
||||
currentReverb.roomRolloffFactor = currentReverb.roomRolloffFactor + (mixed.roomRolloffFactor - currentReverb.roomRolloffFactor) * kReverbSmoothing;
|
||||
currentReverb.diffusion = currentReverb.diffusion + (mixed.diffusion - currentReverb.diffusion) * kReverbSmoothing;
|
||||
currentReverb.density = currentReverb.density + (mixed.density - currentReverb.density) * kReverbSmoothing;
|
||||
|
||||
constexpr float kDbSoftening = 0.5f;
|
||||
constexpr float kWetScale = 0.25f;
|
||||
float reflectionsGain = DbToLinear((currentReverb.reflections + currentReverb.room) * kDbSoftening) * (blend * kWetScale);
|
||||
float reverbGain = DbToLinear((currentReverb.reverb + currentReverb.room) * kDbSoftening) * (blend * kWetScale);
|
||||
float dry = std::clamp(1.0f - blend * (currentReverb.roomRolloffFactor * 0.05f), 0.2f, 1.0f);
|
||||
|
||||
ma_node_set_output_bus_volume(reinterpret_cast<ma_node*>(&reverbSplitter), 0, dry);
|
||||
reverbNode.wetGain = std::clamp(reverbGain, 0.0f, 1.0f);
|
||||
reverbNode.reflectionsGain = std::clamp(reflectionsGain, 0.0f, 1.0f);
|
||||
reverbNode.decayTime = currentReverb.decayTime;
|
||||
reverbNode.decayHFRatio = currentReverb.decayHFRatio;
|
||||
reverbNode.diffusion = currentReverb.diffusion;
|
||||
reverbNode.density = currentReverb.density;
|
||||
reverbNode.hfReference = currentReverb.hfReference;
|
||||
reverbNode.preDelaySeconds = currentReverb.reverbDelay;
|
||||
reverbNode.reflectionsDelaySeconds = currentReverb.reflectionsDelay;
|
||||
}
|
||||
|
||||
void AudioSystem::shutdownReverbGraph() {
|
||||
if (reverbReady) {
|
||||
ma_sound_group_uninit(&reverbGroup);
|
||||
ma_node_uninit(reinterpret_cast<ma_node*>(&reverbNode), nullptr);
|
||||
ma_splitter_node_uninit(&reverbSplitter, nullptr);
|
||||
reverbReady = false;
|
||||
}
|
||||
currentReverb = ReverbSettings{};
|
||||
}
|
||||
|
||||
AudioClipPreview AudioSystem::loadPreview(const std::string& path) {
|
||||
AudioClipPreview preview;
|
||||
preview.path = path;
|
||||
|
||||
@@ -42,7 +42,51 @@ public:
|
||||
bool setObjectLoop(const SceneObject& obj, bool loop);
|
||||
bool setObjectVolume(const SceneObject& obj, float volume);
|
||||
|
||||
struct SimpleReverbNode {
|
||||
ma_node_base baseNode;
|
||||
int channels = 0;
|
||||
int sampleRate = 0;
|
||||
std::vector<std::vector<float>> combBuffers;
|
||||
std::vector<size_t> combIndex;
|
||||
std::vector<std::vector<float>> allpassBuffers;
|
||||
std::vector<size_t> allpassIndex;
|
||||
std::vector<float> preDelayBuffer;
|
||||
size_t preDelayIndex = 0;
|
||||
std::vector<float> reflectionsBuffer;
|
||||
size_t reflectionsIndex = 0;
|
||||
std::vector<float> lpState;
|
||||
float wetGain = 0.0f;
|
||||
float reflectionsGain = 0.0f;
|
||||
float decayTime = 1.5f;
|
||||
float decayHFRatio = 0.5f;
|
||||
float diffusion = 100.0f;
|
||||
float density = 100.0f;
|
||||
float hfReference = 5000.0f;
|
||||
float preDelaySeconds = 0.01f;
|
||||
float reflectionsDelaySeconds = 0.01f;
|
||||
size_t preDelayMaxFrames = 0;
|
||||
size_t reflectionsMaxFrames = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
struct ReverbSettings {
|
||||
float room = -10000.0f;
|
||||
float roomHF = -10000.0f;
|
||||
float roomLF = -10000.0f;
|
||||
float decayTime = 1.5f;
|
||||
float decayHFRatio = 0.5f;
|
||||
float reflections = -10000.0f;
|
||||
float reflectionsDelay = 0.01f;
|
||||
float reverb = -10000.0f;
|
||||
float reverbDelay = 0.01f;
|
||||
float hfReference = 5000.0f;
|
||||
float lfReference = 250.0f;
|
||||
float roomRolloffFactor = 0.0f;
|
||||
float diffusion = 100.0f;
|
||||
float density = 100.0f;
|
||||
float dry = 1.0f;
|
||||
};
|
||||
|
||||
struct ActiveSound {
|
||||
ma_sound sound;
|
||||
std::string clipPath;
|
||||
@@ -56,6 +100,12 @@ private:
|
||||
std::unordered_map<std::string, AudioClipPreview> previewCache;
|
||||
std::unordered_set<std::string> missingClips;
|
||||
|
||||
SimpleReverbNode reverbNode{};
|
||||
ma_splitter_node reverbSplitter{};
|
||||
ma_sound_group reverbGroup{};
|
||||
bool reverbReady = false;
|
||||
ReverbSettings currentReverb{};
|
||||
|
||||
ma_sound previewSound{};
|
||||
bool previewActive = false;
|
||||
std::string previewPath;
|
||||
@@ -63,5 +113,10 @@ private:
|
||||
void destroyActiveSounds();
|
||||
bool ensureSoundFor(const SceneObject& obj);
|
||||
void refreshSoundParams(const SceneObject& obj, ActiveSound& snd);
|
||||
float computeCustomAttenuation(const SceneObject& obj, const glm::vec3& listenerPos) const;
|
||||
AudioClipPreview loadPreview(const std::string& path);
|
||||
void updateReverb(const std::vector<SceneObject>& objects, const glm::vec3& listenerPos);
|
||||
ReverbSettings getReverbTarget(const std::vector<SceneObject>& objects, const glm::vec3& listenerPos, float& outBlend) const;
|
||||
void applyReverbSettings(const ReverbSettings& target, float blend);
|
||||
void shutdownReverbGraph();
|
||||
};
|
||||
|
||||
162
src/EditorUI.cpp
162
src/EditorUI.cpp
@@ -96,8 +96,9 @@ FileCategory FileBrowser::getFileCategory(const fs::directory_entry& entry) cons
|
||||
|
||||
// Model files
|
||||
if (ext == ".fbx" || ext == ".obj" || ext == ".gltf" || ext == ".glb" ||
|
||||
ext == ".dae" || ext == ".blend" || ext == ".3ds" || ext == ".ply" ||
|
||||
ext == ".stl" || ext == ".x" || ext == ".md5mesh" || ext == ".rmesh") {
|
||||
ext == ".dae" || ext == ".blend" || ext == ".3ds" || ext == ".b3d" ||
|
||||
ext == ".ply" || ext == ".stl" || ext == ".x" || ext == ".md5mesh" ||
|
||||
ext == ".rmesh") {
|
||||
return FileCategory::Model;
|
||||
}
|
||||
|
||||
@@ -189,6 +190,60 @@ bool FileBrowser::matchesFilter(const fs::directory_entry& entry) const {
|
||||
void applyModernTheme() {
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImVec4* colors = style.Colors;
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
const float fontSizeBase = 18.0f;
|
||||
const float fontSizeOffset = -2.5f;
|
||||
const float fontSize = std::max(1.0f, fontSizeBase + fontSizeOffset);
|
||||
ImFont* editorFont = nullptr;
|
||||
fs::path primaryFontPath;
|
||||
const fs::path fontCandidates[] = {
|
||||
fs::path("Resources") / "Fonts" / "TheSunset.ttf",
|
||||
fs::path("Resources") / "Fonts" / "Thesunsethd-Regular (1).ttf",
|
||||
fs::path("TheSunset.ttf"),
|
||||
fs::path("Thesunsethd-Regular (1).ttf")
|
||||
};
|
||||
for (const auto& fontPath : fontCandidates) {
|
||||
if (!fs::exists(fontPath)) {
|
||||
continue;
|
||||
}
|
||||
const std::string fontPathStr = fontPath.string();
|
||||
editorFont = io.Fonts->AddFontFromFileTTF(fontPathStr.c_str(), fontSize);
|
||||
if (editorFont) {
|
||||
primaryFontPath = fontPath;
|
||||
io.FontDefault = editorFont;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!editorFont) {
|
||||
std::cerr << "[WARN] Failed to load editor font (TheSunset) from Resources/Fonts."
|
||||
<< std::endl;
|
||||
} else {
|
||||
const fs::path fallbackCandidates[] = {
|
||||
fs::path("Resources") / "Fonts" / "TheSunset.ttf",
|
||||
fs::path("TheSunset.ttf")
|
||||
};
|
||||
if (primaryFontPath.filename() != "TheSunset.ttf") {
|
||||
for (const auto& fallbackPath : fallbackCandidates) {
|
||||
if (!fs::exists(fallbackPath)) {
|
||||
continue;
|
||||
}
|
||||
const std::string fallbackPathStr = fallbackPath.string();
|
||||
ImFontConfig mergeConfig;
|
||||
mergeConfig.MergeMode = true;
|
||||
ImFont* fallbackFont = io.Fonts->AddFontFromFileTTF(
|
||||
fallbackPathStr.c_str(),
|
||||
fontSize,
|
||||
&mergeConfig,
|
||||
io.Fonts->GetGlyphRangesDefault()
|
||||
);
|
||||
if (!fallbackFont) {
|
||||
std::cerr << "[WARN] Failed to merge fallback font: "
|
||||
<< fallbackPathStr << std::endl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImVec4 slate = ImVec4(0.10f, 0.11f, 0.16f, 1.00f);
|
||||
ImVec4 panel = ImVec4(0.14f, 0.15f, 0.21f, 1.00f);
|
||||
@@ -253,26 +308,113 @@ void applyModernTheme() {
|
||||
colors[ImGuiCol_NavHighlight] = accent;
|
||||
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.18f, 0.20f, 0.28f, 1.00f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.05f, 0.06f, 0.08f, 0.70f);
|
||||
applyEditorLayoutPreset(style);
|
||||
}
|
||||
|
||||
style.WindowRounding = 10.0f;
|
||||
void applyEditorLayoutPreset(ImGuiStyle& style) {
|
||||
style.WindowPadding = ImVec2(3.0f, 3.0f);
|
||||
style.FramePadding = ImVec2(4.0f, 4.0f);
|
||||
style.ItemSpacing = ImVec2(10.0f, 5.0f);
|
||||
style.ItemInnerSpacing = ImVec2(2.0f, 2.0f);
|
||||
style.CellPadding = ImVec2(4.0f, 2.0f);
|
||||
style.TouchExtraPadding = ImVec2(0.0f, 0.0f);
|
||||
style.IndentSpacing = 11.0f;
|
||||
style.GrabMinSize = 8.0f;
|
||||
|
||||
style.WindowBorderSize = 0.0f;
|
||||
style.ChildBorderSize = 1.0f;
|
||||
style.PopupBorderSize = 1.0f;
|
||||
style.FrameBorderSize = 0.0f;
|
||||
|
||||
style.WindowRounding = 12.0f;
|
||||
style.ChildRounding = 12.0f;
|
||||
style.FrameRounding = 10.0f;
|
||||
style.FrameRounding = 12.0f;
|
||||
style.PopupRounding = 12.0f;
|
||||
style.GrabRounding = 12.0f;
|
||||
|
||||
style.ScrollbarSize = 11.0f;
|
||||
style.ScrollbarRounding = 10.0f;
|
||||
style.GrabRounding = 8.0f;
|
||||
style.ScrollbarPadding = 1.0f;
|
||||
|
||||
style.TabBorderSize = 1.0f;
|
||||
style.TabBarBorderSize = 1.0f;
|
||||
style.TabBarOverlineSize = 1.0f;
|
||||
style.TabMinWidthBase = 1.0f;
|
||||
style.TabMinWidthShrink = 80.0f;
|
||||
style.TabCloseButtonMinWidthSelected = -1.0f;
|
||||
style.TabCloseButtonMinWidthUnselected = 0.0f;
|
||||
style.TabRounding = 10.0f;
|
||||
|
||||
style.WindowPadding = ImVec2(12.0f, 12.0f);
|
||||
style.FramePadding = ImVec2(10.0f, 6.0f);
|
||||
style.ItemSpacing = ImVec2(10.0f, 8.0f);
|
||||
style.ItemInnerSpacing = ImVec2(8.0f, 6.0f);
|
||||
style.IndentSpacing = 18.0f;
|
||||
style.TableAngledHeadersAngle = 35.0f;
|
||||
style.TableAngledHeadersTextAlign = ImVec2(0.50f, 0.00f);
|
||||
|
||||
style.TreeLinesFlags = ImGuiTreeNodeFlags_DrawLinesNone;
|
||||
style.TreeLinesSize = 1.0f;
|
||||
style.TreeLinesRounding = 0.0f;
|
||||
|
||||
style.WindowTitleAlign = ImVec2(0.50f, 0.50f);
|
||||
style.WindowBorderHoverPadding = 6.0f;
|
||||
style.WindowMenuButtonPosition = ImGuiDir_None;
|
||||
|
||||
style.ColorButtonPosition = ImGuiDir_Right;
|
||||
style.ButtonTextAlign = ImVec2(0.50f, 0.50f);
|
||||
style.SelectableTextAlign = ImVec2(0.00f, 0.00f);
|
||||
style.SeparatorTextBorderSize = 2.0f;
|
||||
style.SeparatorTextAlign = ImVec2(0.50f, 0.50f);
|
||||
style.SeparatorTextPadding = ImVec2(4.0f, 0.0f);
|
||||
style.LogSliderDeadzone = 4.0f;
|
||||
style.ImageBorderSize = 0.0f;
|
||||
|
||||
style.DockingNodeHasCloseButton = true;
|
||||
style.DockingSeparatorSize = 0.0f;
|
||||
|
||||
style.DisplayWindowPadding = ImVec2(19.0f, 19.0f);
|
||||
style.DisplaySafeAreaPadding = ImVec2(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
void applyPixelStyle(ImGuiStyle& style) {
|
||||
applyEditorLayoutPreset(style);
|
||||
style.WindowRounding = 0.0f;
|
||||
style.ChildRounding = 0.0f;
|
||||
style.FrameRounding = 0.0f;
|
||||
style.PopupRounding = 0.0f;
|
||||
style.ScrollbarRounding = 0.0f;
|
||||
style.GrabRounding = 0.0f;
|
||||
style.TabRounding = 0.0f;
|
||||
|
||||
style.WindowPadding = ImVec2(8.0f, 6.0f);
|
||||
style.FramePadding = ImVec2(6.0f, 4.0f);
|
||||
style.ItemSpacing = ImVec2(6.0f, 4.0f);
|
||||
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
|
||||
style.IndentSpacing = 14.0f;
|
||||
|
||||
style.WindowBorderSize = 1.0f;
|
||||
style.FrameBorderSize = 1.0f;
|
||||
style.PopupBorderSize = 1.0f;
|
||||
style.TabBorderSize = 1.0f;
|
||||
}
|
||||
|
||||
void applySuperRoundStyle(ImGuiStyle& style) {
|
||||
applyEditorLayoutPreset(style);
|
||||
style.WindowRounding = 18.0f;
|
||||
style.ChildRounding = 16.0f;
|
||||
style.FrameRounding = 16.0f;
|
||||
style.PopupRounding = 16.0f;
|
||||
style.ScrollbarRounding = 16.0f;
|
||||
style.GrabRounding = 14.0f;
|
||||
style.TabRounding = 16.0f;
|
||||
|
||||
style.WindowPadding = ImVec2(14.0f, 10.0f);
|
||||
style.FramePadding = ImVec2(12.0f, 8.0f);
|
||||
style.ItemSpacing = ImVec2(10.0f, 8.0f);
|
||||
style.ItemInnerSpacing = ImVec2(8.0f, 6.0f);
|
||||
style.IndentSpacing = 18.0f;
|
||||
|
||||
style.WindowBorderSize = 0.0f;
|
||||
style.FrameBorderSize = 0.0f;
|
||||
style.PopupBorderSize = 0.0f;
|
||||
style.TabBorderSize = 0.0f;
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Dockspace
|
||||
|
||||
@@ -69,6 +69,9 @@ public:
|
||||
|
||||
// Apply the modern dark theme to ImGui
|
||||
void applyModernTheme();
|
||||
void applyEditorLayoutPreset(ImGuiStyle& style);
|
||||
void applyPixelStyle(ImGuiStyle& style);
|
||||
void applySuperRoundStyle(ImGuiStyle& style);
|
||||
|
||||
// Setup ImGui dockspace for the editor
|
||||
void setupDockspace(const std::function<void()>& menuBarContent = nullptr);
|
||||
|
||||
554
src/EditorWindows/AnimationWindow.cpp
Normal file
554
src/EditorWindows/AnimationWindow.cpp
Normal file
@@ -0,0 +1,554 @@
|
||||
#include "Engine.h"
|
||||
#include "ThirdParty/imgui/imgui.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
void Engine::renderAnimationWindow() {
|
||||
if (!showAnimationWindow) return;
|
||||
|
||||
auto clampFloat = [](float value, float minValue, float maxValue) {
|
||||
return std::max(minValue, std::min(value, maxValue));
|
||||
};
|
||||
|
||||
auto lerpVec3 = [](const glm::vec3& a, const glm::vec3& b, float t) {
|
||||
return a + (b - a) * t;
|
||||
};
|
||||
|
||||
auto applyInterpolation = [](float t, AnimationInterpolation interpolation) {
|
||||
t = std::max(0.0f, std::min(1.0f, t));
|
||||
switch (interpolation) {
|
||||
case AnimationInterpolation::SmoothStep:
|
||||
return t * t * (3.0f - 2.0f * t);
|
||||
case AnimationInterpolation::EaseIn:
|
||||
return t * t;
|
||||
case AnimationInterpolation::EaseOut: {
|
||||
float inv = 1.0f - t;
|
||||
return 1.0f - inv * inv;
|
||||
}
|
||||
case AnimationInterpolation::EaseInOut:
|
||||
return (t < 0.5f) ? (2.0f * t * t) : (1.0f - 2.0f * (1.0f - t) * (1.0f - t));
|
||||
case AnimationInterpolation::Linear:
|
||||
default:
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
const char* interpLabels[] = { "Linear", "SmoothStep", "Ease In", "Ease Out", "Ease In Out" };
|
||||
const char* curveModeLabels[] = { "Preset", "Bezier" };
|
||||
|
||||
auto getInterpLabel = [&](AnimationInterpolation interpolation) {
|
||||
int idx = static_cast<int>(interpolation);
|
||||
if (idx < 0 || idx >= static_cast<int>(IM_ARRAYSIZE(interpLabels))) return "Linear";
|
||||
return interpLabels[idx];
|
||||
};
|
||||
|
||||
auto cubicBezier = [](float p0, float p1, float p2, float p3, float t) {
|
||||
float inv = 1.0f - t;
|
||||
return (inv * inv * inv * p0) +
|
||||
(3.0f * inv * inv * t * p1) +
|
||||
(3.0f * inv * t * t * p2) +
|
||||
(t * t * t * p3);
|
||||
};
|
||||
|
||||
auto cubicBezierDerivative = [](float p0, float p1, float p2, float p3, float t) {
|
||||
float inv = 1.0f - t;
|
||||
return (3.0f * inv * inv * (p1 - p0)) +
|
||||
(6.0f * inv * t * (p2 - p1)) +
|
||||
(3.0f * t * t * (p3 - p2));
|
||||
};
|
||||
|
||||
auto applyBezier = [&](float t, const glm::vec2& outCtrl, const glm::vec2& inCtrl) {
|
||||
t = std::max(0.0f, std::min(1.0f, t));
|
||||
float u = t;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
float x = cubicBezier(0.0f, outCtrl.x, inCtrl.x, 1.0f, u);
|
||||
float dx = cubicBezierDerivative(0.0f, outCtrl.x, inCtrl.x, 1.0f, u);
|
||||
if (std::abs(dx) < 0.0001f) break;
|
||||
u -= (x - t) / dx;
|
||||
u = std::max(0.0f, std::min(1.0f, u));
|
||||
}
|
||||
float xCheck = cubicBezier(0.0f, outCtrl.x, inCtrl.x, 1.0f, u);
|
||||
if (std::abs(xCheck - t) > 0.001f) {
|
||||
float lo = 0.0f;
|
||||
float hi = 1.0f;
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
float mid = (lo + hi) * 0.5f;
|
||||
float x = cubicBezier(0.0f, outCtrl.x, inCtrl.x, 1.0f, mid);
|
||||
if (x < t) lo = mid;
|
||||
else hi = mid;
|
||||
}
|
||||
u = (lo + hi) * 0.5f;
|
||||
}
|
||||
return cubicBezier(0.0f, outCtrl.y, inCtrl.y, 1.0f, u);
|
||||
};
|
||||
|
||||
auto captureKeyframe = [&](SceneObject& obj) {
|
||||
auto& anim = obj.animation;
|
||||
float clamped = clampFloat(animationCurrentTime, 0.0f, anim.clipLength);
|
||||
auto it = std::find_if(anim.keyframes.begin(), anim.keyframes.end(),
|
||||
[&](const AnimationKeyframe& k) { return std::abs(k.time - clamped) < 0.0001f; });
|
||||
if (it == anim.keyframes.end()) {
|
||||
AnimationKeyframe key;
|
||||
key.time = clamped;
|
||||
key.position = obj.position;
|
||||
key.rotation = obj.rotation;
|
||||
key.scale = obj.scale;
|
||||
key.interpolation = AnimationInterpolation::SmoothStep;
|
||||
key.curveMode = AnimationCurveMode::Preset;
|
||||
anim.keyframes.push_back(key);
|
||||
} else {
|
||||
it->position = obj.position;
|
||||
it->rotation = obj.rotation;
|
||||
it->scale = obj.scale;
|
||||
}
|
||||
std::sort(anim.keyframes.begin(), anim.keyframes.end(),
|
||||
[](const AnimationKeyframe& a, const AnimationKeyframe& b) { return a.time < b.time; });
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
};
|
||||
|
||||
auto deleteKeyframe = [&](SceneObject& obj) {
|
||||
auto& anim = obj.animation;
|
||||
if (animationSelectedKey < 0 || animationSelectedKey >= static_cast<int>(anim.keyframes.size())) return;
|
||||
anim.keyframes.erase(anim.keyframes.begin() + animationSelectedKey);
|
||||
if (animationSelectedKey >= static_cast<int>(anim.keyframes.size())) {
|
||||
animationSelectedKey = static_cast<int>(anim.keyframes.size()) - 1;
|
||||
}
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
};
|
||||
|
||||
auto applyPoseAtTime = [&](SceneObject& obj, float time) {
|
||||
auto& anim = obj.animation;
|
||||
if (anim.keyframes.empty()) return;
|
||||
|
||||
if (time <= anim.keyframes.front().time) {
|
||||
obj.position = anim.keyframes.front().position;
|
||||
obj.rotation = NormalizeEulerDegrees(anim.keyframes.front().rotation);
|
||||
obj.scale = anim.keyframes.front().scale;
|
||||
syncLocalTransform(obj);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
return;
|
||||
}
|
||||
if (time >= anim.keyframes.back().time) {
|
||||
obj.position = anim.keyframes.back().position;
|
||||
obj.rotation = NormalizeEulerDegrees(anim.keyframes.back().rotation);
|
||||
obj.scale = anim.keyframes.back().scale;
|
||||
syncLocalTransform(obj);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i + 1 < anim.keyframes.size(); ++i) {
|
||||
const auto& a = anim.keyframes[i];
|
||||
const auto& b = anim.keyframes[i + 1];
|
||||
if (time >= a.time && time <= b.time) {
|
||||
float span = b.time - a.time;
|
||||
float t = (span > 0.0f) ? (time - a.time) / span : 0.0f;
|
||||
if (a.curveMode == AnimationCurveMode::Bezier) {
|
||||
t = applyBezier(t, a.bezierOut, b.bezierIn);
|
||||
} else {
|
||||
t = applyInterpolation(t, a.interpolation);
|
||||
}
|
||||
obj.position = lerpVec3(a.position, b.position, t);
|
||||
obj.rotation = NormalizeEulerDegrees(lerpVec3(a.rotation, b.rotation, t));
|
||||
obj.scale = lerpVec3(a.scale, b.scale, t);
|
||||
syncLocalTransform(obj);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto drawTimeline = [&](AnimationComponent& anim) {
|
||||
ImVec2 size = ImVec2(ImGui::GetContentRegionAvail().x, 70.0f);
|
||||
ImVec2 start = ImGui::GetCursorScreenPos();
|
||||
ImGui::InvisibleButton("AnimationTimeline", size);
|
||||
|
||||
ImDrawList* draw = ImGui::GetWindowDrawList();
|
||||
ImU32 bg = ImGui::GetColorU32(ImGuiCol_FrameBg);
|
||||
ImU32 border = ImGui::GetColorU32(ImGuiCol_Border);
|
||||
ImU32 accent = ImGui::GetColorU32(ImGuiCol_CheckMark);
|
||||
ImU32 keyColor = ImGui::GetColorU32(ImGuiCol_SliderGrab);
|
||||
|
||||
draw->AddRectFilled(start, ImVec2(start.x + size.x, start.y + size.y), bg, 6.0f);
|
||||
draw->AddRect(start, ImVec2(start.x + size.x, start.y + size.y), border, 6.0f);
|
||||
|
||||
float clamped = clampFloat(animationCurrentTime, 0.0f, anim.clipLength);
|
||||
float playheadX = start.x + (anim.clipLength > 0.0f ? (clamped / anim.clipLength) * size.x : 0.0f);
|
||||
draw->AddLine(ImVec2(playheadX, start.y), ImVec2(playheadX, start.y + size.y), accent, 2.0f);
|
||||
|
||||
for (size_t i = 0; i < anim.keyframes.size(); ++i) {
|
||||
float keyX = start.x +
|
||||
(anim.clipLength > 0.0f ? (anim.keyframes[i].time / anim.clipLength) * size.x : 0.0f);
|
||||
ImVec2 center(keyX, start.y + size.y * 0.5f);
|
||||
float radius = (animationSelectedKey == static_cast<int>(i)) ? 6.0f : 4.5f;
|
||||
draw->AddCircleFilled(center, radius, keyColor);
|
||||
|
||||
ImRect hit(ImVec2(center.x - 7.0f, center.y - 7.0f), ImVec2(center.x + 7.0f, center.y + 7.0f));
|
||||
if (ImGui::IsMouseHoveringRect(hit.Min, hit.Max) && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
animationSelectedKey = static_cast<int>(i);
|
||||
animationCurrentTime = anim.keyframes[i].time;
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemActive() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
float mouseX = ImGui::GetIO().MousePos.x;
|
||||
float t = (mouseX - start.x) / size.x;
|
||||
animationCurrentTime = clampFloat(t * anim.clipLength, 0.0f, anim.clipLength);
|
||||
}
|
||||
};
|
||||
|
||||
auto* selectedObj = getSelectedObject();
|
||||
std::vector<SceneObject*> animTargets;
|
||||
animTargets.reserve(sceneObjects.size());
|
||||
for (auto& obj : sceneObjects) {
|
||||
if (obj.hasAnimation) animTargets.push_back(&obj);
|
||||
}
|
||||
|
||||
auto resolveTarget = [&]() -> SceneObject* {
|
||||
if (animationTargetId < 0) return nullptr;
|
||||
SceneObject* obj = findObjectById(animationTargetId);
|
||||
if (!obj || !obj->hasAnimation) return nullptr;
|
||||
return obj;
|
||||
};
|
||||
|
||||
SceneObject* targetObj = resolveTarget();
|
||||
if (!targetObj && !animTargets.empty()) {
|
||||
animationTargetId = animTargets.front()->id;
|
||||
animationSelectedKey = -1;
|
||||
animationLastAppliedTime = -1.0f;
|
||||
targetObj = resolveTarget();
|
||||
}
|
||||
|
||||
ImGui::Begin("Animation", &showAnimationWindow, ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
if (!ImGui::BeginTable("AnimatorLayout", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::TableSetupColumn("Targets", ImGuiTableColumnFlags_WidthFixed, 220.0f);
|
||||
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableNextRow();
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::BeginChild("AnimatorTargets", ImVec2(0, 0), true);
|
||||
ImGui::TextDisabled("Targets");
|
||||
ImGui::Spacing();
|
||||
ImGui::BeginDisabled(!selectedObj);
|
||||
if (ImGui::Button("Add Animation to Selected", ImVec2(-1, 0))) {
|
||||
if (selectedObj && !selectedObj->hasAnimation) {
|
||||
selectedObj->hasAnimation = true;
|
||||
selectedObj->animation = AnimationComponent{};
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
animationTargetId = selectedObj->id;
|
||||
animationSelectedKey = -1;
|
||||
animationLastAppliedTime = -1.0f;
|
||||
animTargets.push_back(selectedObj);
|
||||
} else if (selectedObj) {
|
||||
animationTargetId = selectedObj->id;
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (animTargets.empty()) {
|
||||
ImGui::TextDisabled("No Animation components yet.");
|
||||
} else {
|
||||
for (auto* obj : animTargets) {
|
||||
bool selected = (targetObj && obj->id == targetObj->id);
|
||||
if (ImGui::Selectable(obj->name.c_str(), selected)) {
|
||||
animationTargetId = obj->id;
|
||||
animationSelectedKey = -1;
|
||||
animationLastAppliedTime = -1.0f;
|
||||
animationCurrentTime = 0.0f;
|
||||
targetObj = obj;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::Text("Keyframes: %zu", obj->animation.keyframes.size());
|
||||
ImGui::Text("Length: %.2fs", obj->animation.clipLength);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::BeginChild("AnimatorEditor", ImVec2(0, 0), true);
|
||||
|
||||
if (!targetObj) {
|
||||
ImGui::TextDisabled("Select or add an Animation component to edit.");
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTable();
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
auto& anim = targetObj->animation;
|
||||
animationCurrentTime = clampFloat(animationCurrentTime, 0.0f, anim.clipLength);
|
||||
|
||||
ImGui::Text("Animator");
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Target: %s", targetObj->name.c_str());
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::BeginTabBar("AnimatorTabs")) {
|
||||
if (ImGui::BeginTabItem("Pose")) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f);
|
||||
ImGui::BeginDisabled(!anim.enabled);
|
||||
if (ImGui::Button("Key")) {
|
||||
captureKeyframe(*targetObj);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(animationSelectedKey < 0);
|
||||
if (ImGui::Button("Delete")) {
|
||||
deleteKeyframe(*targetObj);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(anim.keyframes.empty());
|
||||
if (ImGui::Button("Sort")) {
|
||||
std::sort(anim.keyframes.begin(), anim.keyframes.end(),
|
||||
[](const AnimationKeyframe& a, const AnimationKeyframe& b) { return a.time < b.time; });
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::Spacing();
|
||||
drawTimeline(anim);
|
||||
ImGui::SliderFloat("Time", &animationCurrentTime, 0.0f, anim.clipLength, "%.2fs");
|
||||
|
||||
if (animationSelectedKey >= 0 && animationSelectedKey < static_cast<int>(anim.keyframes.size())) {
|
||||
auto& key = anim.keyframes[animationSelectedKey];
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled("Blend");
|
||||
int modeIndex = static_cast<int>(key.curveMode);
|
||||
ImGui::SetNextItemWidth(200.0f);
|
||||
if (ImGui::Combo("Mode", &modeIndex, curveModeLabels, IM_ARRAYSIZE(curveModeLabels))) {
|
||||
key.curveMode = static_cast<AnimationCurveMode>(modeIndex);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Apply Mode To All")) {
|
||||
for (auto& k : anim.keyframes) {
|
||||
k.curveMode = key.curveMode;
|
||||
}
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
if (key.curveMode == AnimationCurveMode::Preset) {
|
||||
int interpIndex = static_cast<int>(key.interpolation);
|
||||
ImGui::SetNextItemWidth(200.0f);
|
||||
if (ImGui::Combo("Preset", &interpIndex, interpLabels, IM_ARRAYSIZE(interpLabels))) {
|
||||
key.interpolation = static_cast<AnimationInterpolation>(interpIndex);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Apply Preset To All")) {
|
||||
for (auto& k : anim.keyframes) {
|
||||
k.interpolation = key.interpolation;
|
||||
}
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled("Out Handle (to next)");
|
||||
ImGui::SetNextItemWidth(160.0f);
|
||||
if (ImGui::SliderFloat2("Out", &key.bezierOut.x, 0.0f, 1.0f, "%.2f")) {
|
||||
key.bezierOut.x = clampFloat(key.bezierOut.x, 0.0f, 1.0f);
|
||||
key.bezierOut.y = clampFloat(key.bezierOut.y, 0.0f, 1.0f);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
ImGui::TextDisabled("In Handle (from prev)");
|
||||
ImGui::SetNextItemWidth(160.0f);
|
||||
if (ImGui::SliderFloat2("In", &key.bezierIn.x, 0.0f, 1.0f, "%.2f")) {
|
||||
key.bezierIn.x = clampFloat(key.bezierIn.x, 0.0f, 1.0f);
|
||||
key.bezierIn.y = clampFloat(key.bezierIn.y, 0.0f, 1.0f);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
int nextIndex = animationSelectedKey + 1;
|
||||
if (nextIndex < static_cast<int>(anim.keyframes.size())) {
|
||||
static int activeHandle = -1;
|
||||
auto& nextKey = anim.keyframes[nextIndex];
|
||||
ImVec2 previewSize(260.0f, 110.0f);
|
||||
ImGui::TextDisabled("Curve Editor");
|
||||
ImGui::BeginChild("BezierPreview", previewSize, true, ImGuiWindowFlags_NoScrollbar);
|
||||
ImDrawList* draw = ImGui::GetWindowDrawList();
|
||||
ImVec2 p0 = ImGui::GetCursorScreenPos();
|
||||
ImVec2 p1(p0.x + previewSize.x, p0.y + previewSize.y);
|
||||
ImU32 grid = ImGui::GetColorU32(ImGuiCol_Border);
|
||||
ImU32 handleColor = ImGui::GetColorU32(ImGuiCol_SliderGrab);
|
||||
ImU32 lineColor = ImGui::GetColorU32(ImGuiCol_CheckMark);
|
||||
draw->AddRect(p0, p1, grid);
|
||||
|
||||
auto toScreen = [&](const glm::vec2& v) {
|
||||
return ImVec2(p0.x + v.x * previewSize.x, p0.y + (1.0f - v.y) * previewSize.y);
|
||||
};
|
||||
|
||||
ImVec2 outHandle = toScreen(key.bezierOut);
|
||||
ImVec2 inHandle = toScreen(nextKey.bezierIn);
|
||||
ImVec2 start = toScreen(glm::vec2(0.0f, 0.0f));
|
||||
ImVec2 end = toScreen(glm::vec2(1.0f, 1.0f));
|
||||
draw->AddLine(start, outHandle, grid, 1.0f);
|
||||
draw->AddLine(end, inHandle, grid, 1.0f);
|
||||
draw->AddCircleFilled(outHandle, 5.0f, handleColor);
|
||||
draw->AddCircleFilled(inHandle, 5.0f, handleColor);
|
||||
|
||||
const int samples = 32;
|
||||
ImVec2 last = start;
|
||||
for (int i = 0; i <= samples; ++i) {
|
||||
float t = static_cast<float>(i) / samples;
|
||||
float y = applyBezier(t, key.bezierOut, nextKey.bezierIn);
|
||||
ImVec2 cur(p0.x + t * previewSize.x, p0.y + (1.0f - y) * previewSize.y);
|
||||
if (i > 0) {
|
||||
draw->AddLine(last, cur, lineColor, 2.0f);
|
||||
}
|
||||
last = cur;
|
||||
}
|
||||
|
||||
ImRect outRect(ImVec2(outHandle.x - 7.0f, outHandle.y - 7.0f),
|
||||
ImVec2(outHandle.x + 7.0f, outHandle.y + 7.0f));
|
||||
ImRect inRect(ImVec2(inHandle.x - 7.0f, inHandle.y - 7.0f),
|
||||
ImVec2(inHandle.x + 7.0f, inHandle.y + 7.0f));
|
||||
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
if (ImGui::IsMouseHoveringRect(outRect.Min, outRect.Max)) activeHandle = 0;
|
||||
else if (ImGui::IsMouseHoveringRect(inRect.Min, inRect.Max)) activeHandle = 1;
|
||||
else activeHandle = -1;
|
||||
}
|
||||
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
||||
activeHandle = -1;
|
||||
}
|
||||
|
||||
if (activeHandle >= 0 && ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
||||
ImVec2 mouse = ImGui::GetIO().MousePos;
|
||||
float x = (mouse.x - p0.x) / previewSize.x;
|
||||
float y = 1.0f - (mouse.y - p0.y) / previewSize.y;
|
||||
glm::vec2 clamped(clampFloat(x, 0.0f, 1.0f), clampFloat(y, 0.0f, 1.0f));
|
||||
if (activeHandle == 0) {
|
||||
key.bezierOut = clamped;
|
||||
} else {
|
||||
nextKey.bezierIn = clamped;
|
||||
}
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
if (anim.keyframes.empty()) {
|
||||
ImGui::TextDisabled("No keyframes yet.");
|
||||
} else if (ImGui::BeginTable("AnimationKeyframeTable", 5,
|
||||
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) {
|
||||
ImGui::TableSetupColumn("Time");
|
||||
ImGui::TableSetupColumn("Blend");
|
||||
ImGui::TableSetupColumn("Position");
|
||||
ImGui::TableSetupColumn("Rotation");
|
||||
ImGui::TableSetupColumn("Scale");
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (size_t i = 0; i < anim.keyframes.size(); ++i) {
|
||||
const auto& key = anim.keyframes[i];
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
bool selected = animationSelectedKey == static_cast<int>(i);
|
||||
char label[32];
|
||||
std::snprintf(label, sizeof(label), "%.2f", key.time);
|
||||
if (ImGui::Selectable(label, selected, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
animationSelectedKey = static_cast<int>(i);
|
||||
animationCurrentTime = key.time;
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
if (key.curveMode == AnimationCurveMode::Bezier) {
|
||||
ImGui::TextUnformatted("Bezier");
|
||||
} else {
|
||||
ImGui::TextUnformatted(getInterpLabel(key.interpolation));
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%.2f, %.2f, %.2f", key.position.x, key.position.y, key.position.z);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%.2f, %.2f, %.2f", key.rotation.x, key.rotation.y, key.rotation.z);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%.2f, %.2f, %.2f", key.scale.x, key.scale.y, key.scale.z);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Config")) {
|
||||
if (ImGui::DragFloat("Clip Length", &anim.clipLength, 0.05f, 0.1f, 120.0f, "%.2f")) {
|
||||
anim.clipLength = std::max(0.1f, anim.clipLength);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
animationCurrentTime = clampFloat(animationCurrentTime, 0.0f, anim.clipLength);
|
||||
}
|
||||
if (ImGui::DragFloat("Play Speed", &anim.playSpeed, 0.05f, 0.05f, 8.0f, "%.2f")) {
|
||||
anim.playSpeed = std::max(0.05f, anim.playSpeed);
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
if (ImGui::Checkbox("Loop", &anim.loop)) {
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
if (ImGui::Checkbox("Apply On Scrub", &anim.applyOnScrub)) {
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
ImGui::Spacing();
|
||||
if (ImGui::Button("Clear Keyframes")) {
|
||||
anim.keyframes.clear();
|
||||
animationSelectedKey = -1;
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled("Transport");
|
||||
ImGui::BeginDisabled(!anim.enabled);
|
||||
if (ImGui::Button(animationIsPlaying ? "Pause" : "Play")) {
|
||||
animationIsPlaying = !animationIsPlaying;
|
||||
animationLastAppliedTime = -1.0f;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Stop")) {
|
||||
animationIsPlaying = false;
|
||||
animationCurrentTime = 0.0f;
|
||||
animationLastAppliedTime = -1.0f;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Time: %.2fs / %.2fs", animationCurrentTime, anim.clipLength);
|
||||
|
||||
if (animationIsPlaying && anim.clipLength > 0.0f) {
|
||||
animationCurrentTime += ImGui::GetIO().DeltaTime * anim.playSpeed;
|
||||
if (animationCurrentTime > anim.clipLength) {
|
||||
if (anim.loop) {
|
||||
animationCurrentTime = std::fmod(animationCurrentTime, anim.clipLength);
|
||||
} else {
|
||||
animationCurrentTime = anim.clipLength;
|
||||
animationIsPlaying = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (anim.enabled && (animationIsPlaying || anim.applyOnScrub)) {
|
||||
if (animationIsPlaying || std::abs(animationCurrentTime - animationLastAppliedTime) > 0.0001f) {
|
||||
applyPoseAtTime(*targetObj, animationCurrentTime);
|
||||
animationLastAppliedTime = animationCurrentTime;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTable();
|
||||
ImGui::End();
|
||||
}
|
||||
253
src/EditorWindows/BuildSettingsWindow.cpp
Normal file
253
src/EditorWindows/BuildSettingsWindow.cpp
Normal file
@@ -0,0 +1,253 @@
|
||||
#include "Engine.h"
|
||||
|
||||
void Engine::renderBuildSettingsWindow() {
|
||||
if (!showBuildSettings) return;
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(760, 520), ImGuiCond_FirstUseEver);
|
||||
if (!ImGui::Begin("Build Settings", &showBuildSettings)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!projectManager.currentProject.isLoaded) {
|
||||
ImGui::TextDisabled("No project loaded.");
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
|
||||
ImGui::BeginChild("BuildScenesList", ImVec2(0, 150), true);
|
||||
ImGui::Text("Scenes In Build");
|
||||
ImGui::Separator();
|
||||
for (int i = 0; i < static_cast<int>(buildSettings.scenes.size()); ++i) {
|
||||
BuildSceneEntry& entry = buildSettings.scenes[i];
|
||||
ImGui::PushID(i);
|
||||
bool enabled = entry.enabled;
|
||||
if (ImGui::Checkbox("##enabled", &enabled)) {
|
||||
entry.enabled = enabled;
|
||||
changed = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
bool selected = (buildSettingsSelectedIndex == i);
|
||||
if (ImGui::Selectable(entry.name.c_str(), selected, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
buildSettingsSelectedIndex = i;
|
||||
}
|
||||
float rightX = ImGui::GetWindowContentRegionMax().x;
|
||||
ImGui::SameLine(rightX - 24.0f);
|
||||
ImGui::TextDisabled("%d", i);
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
float buttonSpacing = ImGui::GetStyle().ItemSpacing.x;
|
||||
float addWidth = 150.0f;
|
||||
float removeWidth = 130.0f;
|
||||
float totalButtons = addWidth + removeWidth + buttonSpacing;
|
||||
float buttonStart = ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - totalButtons;
|
||||
if (buttonStart > ImGui::GetCursorPosX()) {
|
||||
ImGui::SetCursorPosX(buttonStart);
|
||||
}
|
||||
if (ImGui::Button("Remove Selected", ImVec2(removeWidth, 0.0f))) {
|
||||
if (buildSettingsSelectedIndex >= 0 &&
|
||||
buildSettingsSelectedIndex < static_cast<int>(buildSettings.scenes.size())) {
|
||||
buildSettings.scenes.erase(buildSettings.scenes.begin() + buildSettingsSelectedIndex);
|
||||
if (buildSettingsSelectedIndex >= static_cast<int>(buildSettings.scenes.size())) {
|
||||
buildSettingsSelectedIndex = static_cast<int>(buildSettings.scenes.size()) - 1;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Add Open Scenes", ImVec2(addWidth, 0.0f))) {
|
||||
if (addSceneToBuildSettings(projectManager.currentProject.currentSceneName, true)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("Platform");
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::BeginChild("BuildPlatforms", ImVec2(220, 0), true);
|
||||
ImGui::Selectable("Windows & Linux Standalone", true);
|
||||
ImGui::BeginDisabled(true);
|
||||
ImGui::Selectable("Android", false);
|
||||
ImGui::Selectable("Android | Meta Quest", false);
|
||||
ImGui::EndDisabled();
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginChild("BuildPlatformSettings", ImVec2(0, 0), true);
|
||||
ImGui::Text("Target Platform");
|
||||
const char* targets[] = {"Windows", "Linux"};
|
||||
int targetIndex = (buildSettings.platform == BuildPlatform::Linux) ? 1 : 0;
|
||||
if (ImGui::Combo("##target-platform", &targetIndex, targets, 2)) {
|
||||
buildSettings.platform = (targetIndex == 1) ? BuildPlatform::Linux : BuildPlatform::Windows;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::Text("Architecture");
|
||||
const char* arches[] = {"x86_64", "x86"};
|
||||
int archIndex = (buildSettings.architecture == "x86") ? 1 : 0;
|
||||
if (ImGui::Combo("##architecture", &archIndex, arches, 2)) {
|
||||
buildSettings.architecture = arches[archIndex];
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
if (ImGui::Checkbox("Server Build", &buildSettings.serverBuild)) changed = true;
|
||||
if (ImGui::Checkbox("Development Build", &buildSettings.developmentBuild)) changed = true;
|
||||
if (ImGui::Checkbox("Autoconnect Profiler", &buildSettings.autoConnectProfiler)) changed = true;
|
||||
if (ImGui::Checkbox("Deep Profiling Support", &buildSettings.deepProfiling)) changed = true;
|
||||
if (ImGui::Checkbox("Script Debugging", &buildSettings.scriptDebugging)) changed = true;
|
||||
if (ImGui::Checkbox("Scripts Only Build", &buildSettings.scriptsOnlyBuild)) changed = true;
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("Compression Method");
|
||||
const char* compressionOptions[] = {"Default", "None", "LZ4", "LZ4HC"};
|
||||
int compressionIndex = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (buildSettings.compressionMethod == compressionOptions[i]) {
|
||||
compressionIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ImGui::Combo("##compression", &compressionIndex, compressionOptions, 4)) {
|
||||
buildSettings.compressionMethod = compressionOptions[compressionIndex];
|
||||
changed = true;
|
||||
}
|
||||
ImGui::TextDisabled("Android support will unlock after OpenGLES is available.");
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::Separator();
|
||||
float buildWidth = 90.0f;
|
||||
float buildRunWidth = 120.0f;
|
||||
float buildTotal = buildWidth + buildRunWidth + buttonSpacing;
|
||||
float buildStart = ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - buildTotal;
|
||||
if (buildStart > ImGui::GetCursorPosX()) {
|
||||
ImGui::SetCursorPosX(buildStart);
|
||||
}
|
||||
if (ImGui::Button("Export Game", ImVec2(buildWidth, 0.0f))) {
|
||||
exportRunAfter = false;
|
||||
if (exportOutputPath[0] == '\0') {
|
||||
fs::path defaultOut = projectManager.currentProject.projectPath / "Builds";
|
||||
std::snprintf(exportOutputPath, sizeof(exportOutputPath), "%s", defaultOut.string().c_str());
|
||||
}
|
||||
showExportDialog = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Export & Run", ImVec2(buildRunWidth, 0.0f))) {
|
||||
exportRunAfter = true;
|
||||
if (exportOutputPath[0] == '\0') {
|
||||
fs::path defaultOut = projectManager.currentProject.projectPath / "Builds";
|
||||
std::snprintf(exportOutputPath, sizeof(exportOutputPath), "%s", defaultOut.string().c_str());
|
||||
}
|
||||
showExportDialog = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
saveBuildSettings();
|
||||
}
|
||||
|
||||
if (showExportDialog) {
|
||||
ImGui::SetNextWindowSize(ImVec2(720, 460), ImGuiCond_Appearing);
|
||||
ImGui::OpenPopup("Export Game");
|
||||
showExportDialog = false;
|
||||
}
|
||||
|
||||
bool exportPopupOpen = true;
|
||||
ImGuiWindowFlags popupFlags = ImGuiWindowFlags_NoDocking;
|
||||
bool exportActive = false;
|
||||
bool exportDone = false;
|
||||
bool exportSuccess = false;
|
||||
float exportProgress = 0.0f;
|
||||
std::string exportStatus;
|
||||
std::string exportLog;
|
||||
fs::path exportDir;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(exportMutex);
|
||||
exportActive = exportJob.active;
|
||||
exportDone = exportJob.done;
|
||||
exportSuccess = exportJob.success;
|
||||
exportProgress = exportJob.progress;
|
||||
exportStatus = exportJob.status;
|
||||
exportLog = exportJob.log;
|
||||
exportDir = exportJob.outputDir;
|
||||
}
|
||||
bool allowClose = !exportActive;
|
||||
if (ImGui::BeginPopupModal("Export Game", allowClose ? &exportPopupOpen : nullptr, popupFlags)) {
|
||||
ImGui::Text("Output Folder");
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::BeginDisabled(exportActive);
|
||||
ImGui::InputText("##ExportOutput", exportOutputPath, sizeof(exportOutputPath));
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (!exportActive) {
|
||||
if (ImGui::Button("Use Selected Folder")) {
|
||||
if (!fileBrowser.selectedFile.empty()) {
|
||||
fs::path selected = fileBrowser.selectedFile;
|
||||
fs::path folder = fs::is_directory(selected) ? selected : selected.parent_path();
|
||||
std::snprintf(exportOutputPath, sizeof(exportOutputPath), "%s", folder.string().c_str());
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Use Project Folder")) {
|
||||
fs::path folder = projectManager.currentProject.projectPath / "Builds";
|
||||
std::snprintf(exportOutputPath, sizeof(exportOutputPath), "%s", folder.string().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
if (exportActive || exportDone) {
|
||||
const char* statusLabel = exportStatus.empty() ? "Working..." : exportStatus.c_str();
|
||||
float barValue = exportActive ? exportProgress : 1.0f;
|
||||
if (barValue <= 0.0f) barValue = 0.02f;
|
||||
ImGui::ProgressBar(barValue, ImVec2(-1, 0), statusLabel);
|
||||
if (exportActive) {
|
||||
ImGui::TextDisabled("Build can take a while (PhysX/assimp). Output updates after each step finishes.");
|
||||
}
|
||||
ImGui::BeginChild("ExportLog", ImVec2(0, 180), true);
|
||||
if (exportLog.empty()) {
|
||||
ImGui::TextUnformatted("Waiting for build output...");
|
||||
} else {
|
||||
ImGui::TextUnformatted(exportLog.c_str());
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
if (!exportActive && !exportDone) {
|
||||
if (ImGui::Button("Start Export", ImVec2(120, 0))) {
|
||||
if (!exportOutputPath[0]) {
|
||||
addConsoleMessage("Please choose an export folder.", ConsoleMessageType::Warning);
|
||||
} else {
|
||||
startExportBuild(fs::path(exportOutputPath), exportRunAfter);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", ImVec2(100, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
} else if (!exportActive && exportDone) {
|
||||
if (exportSuccess && !exportDir.empty()) {
|
||||
ImGui::TextDisabled("Exported to: %s", exportDir.string().c_str());
|
||||
}
|
||||
if (ImGui::Button("Close", ImVec2(100, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
std::lock_guard<std::mutex> lock(exportMutex);
|
||||
exportJob = ExportJobState{};
|
||||
}
|
||||
} else {
|
||||
if (ImGui::Button("Cancel Export", ImVec2(140, 0))) {
|
||||
exportCancelRequested = true;
|
||||
std::lock_guard<std::mutex> lock(exportMutex);
|
||||
exportJob.status = "Cancelling...";
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
@@ -501,6 +501,7 @@ void Engine::renderFileBrowserPanel() {
|
||||
static fs::path pendingDeletePath;
|
||||
static fs::path pendingRenamePath;
|
||||
static char renameName[256] = "";
|
||||
bool settingsDirty = false;
|
||||
|
||||
auto openEntry = [&](const fs::directory_entry& entry) {
|
||||
if (entry.is_directory()) {
|
||||
@@ -536,6 +537,10 @@ void Engine::renderFileBrowserPanel() {
|
||||
logToConsole("Loaded scene: " + sceneName);
|
||||
return;
|
||||
}
|
||||
if (fileBrowser.getFileCategory(entry) == FileCategory::Script) {
|
||||
openScriptInEditor(entry.path());
|
||||
return;
|
||||
}
|
||||
openPathInShell(entry.path());
|
||||
};
|
||||
|
||||
@@ -624,6 +629,15 @@ void Engine::renderFileBrowserPanel() {
|
||||
return false;
|
||||
};
|
||||
|
||||
auto normalizePath = [](const fs::path& path) {
|
||||
std::error_code ec;
|
||||
fs::path canonical = fs::weakly_canonical(path, ec);
|
||||
if (!ec) {
|
||||
return canonical;
|
||||
}
|
||||
return path.lexically_normal();
|
||||
};
|
||||
|
||||
// Get colors for categories
|
||||
auto getCategoryColor = [](FileCategory cat) -> ImU32 {
|
||||
switch (cat) {
|
||||
@@ -742,16 +756,24 @@ void Engine::renderFileBrowserPanel() {
|
||||
ImGui::TextDisabled("Size");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(90);
|
||||
ImGui::SliderFloat("##IconScale", &fileBrowserIconScale, 0.6f, 2.0f, "%.1fx");
|
||||
if (ImGui::SliderFloat("##IconScale", &fileBrowserIconScale, 0.6f, 2.0f, "%.1fx")) {
|
||||
settingsDirty = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Icon Size: %.1fx", fileBrowserIconScale);
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
if (ImGui::Button(isGridMode ? "Grid" : "List", ImVec2(54, 0))) {
|
||||
fileBrowser.viewMode = isGridMode ? FileBrowserViewMode::List : FileBrowserViewMode::Grid;
|
||||
settingsDirty = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip(isGridMode ? "Switch to List View" : "Switch to Grid View");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(showFileBrowserSidebar ? "Side" : "Side", ImVec2(52, 0))) {
|
||||
showFileBrowserSidebar = !showFileBrowserSidebar;
|
||||
settingsDirty = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Toggle sidebar");
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleVar(2);
|
||||
@@ -766,6 +788,159 @@ void Engine::renderFileBrowserPanel() {
|
||||
contentBg.z = std::min(contentBg.z + 0.01f, 1.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, contentBg);
|
||||
ImGui::BeginChild("FileContent", ImVec2(0, 0), true);
|
||||
if (showFileBrowserSidebar) {
|
||||
float minSidebarWidth = 160.0f;
|
||||
float maxSidebarWidth = std::max(minSidebarWidth, ImGui::GetContentRegionAvail().x * 0.5f);
|
||||
fileBrowserSidebarWidth = std::clamp(fileBrowserSidebarWidth, minSidebarWidth, maxSidebarWidth);
|
||||
|
||||
ImGui::BeginChild("FileSidebar", ImVec2(fileBrowserSidebarWidth, 0), true);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 4.0f));
|
||||
ImGui::TextDisabled("Favorites");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("+")) {
|
||||
fs::path current = normalizePath(fileBrowser.currentPath);
|
||||
bool exists = false;
|
||||
for (const auto& fav : fileBrowserFavorites) {
|
||||
if (normalizePath(fav) == current) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
fileBrowserFavorites.push_back(current);
|
||||
settingsDirty = true;
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add current folder");
|
||||
|
||||
fs::path baseRoot = fileBrowser.projectRoot.empty()
|
||||
? projectManager.currentProject.projectPath
|
||||
: fileBrowser.projectRoot;
|
||||
fs::path normalizedCurrent = normalizePath(fileBrowser.currentPath);
|
||||
|
||||
for (size_t i = 0; i < fileBrowserFavorites.size(); ++i) {
|
||||
fs::path fav = fileBrowserFavorites[i];
|
||||
std::string label;
|
||||
std::error_code ec;
|
||||
fs::path rel = fs::relative(fav, baseRoot, ec);
|
||||
std::string relStr = rel.generic_string();
|
||||
if (!ec && !rel.empty() && relStr.find("..") != 0) {
|
||||
label = relStr;
|
||||
if (label.empty() || label == ".") {
|
||||
label = "Project";
|
||||
}
|
||||
} else {
|
||||
label = fav.filename().string();
|
||||
if (label.empty()) {
|
||||
label = fav.string();
|
||||
}
|
||||
}
|
||||
|
||||
bool exists = fs::exists(fav);
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
if (!exists) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
if (ImGui::Selectable(label.c_str(), normalizePath(fav) == normalizedCurrent)) {
|
||||
if (exists) {
|
||||
fileBrowser.navigateTo(fav);
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem("FavContext")) {
|
||||
if (ImGui::MenuItem("Remove")) {
|
||||
fileBrowserFavorites.erase(fileBrowserFavorites.begin() + static_cast<int>(i));
|
||||
settingsDirty = true;
|
||||
ImGui::EndPopup();
|
||||
ImGui::PopID();
|
||||
break;
|
||||
}
|
||||
if (exists && ImGui::MenuItem("Open in File Explorer")) {
|
||||
openPathInFileManager(fav);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled("Folders");
|
||||
ImGui::BeginChild("FolderTree", ImVec2(0, 0), false);
|
||||
|
||||
auto drawFolderTree = [&](auto&& self, const fs::path& path) -> void {
|
||||
if (!fs::exists(path)) {
|
||||
return;
|
||||
}
|
||||
std::string name = path.filename().string();
|
||||
if (name.empty()) {
|
||||
name = "Project";
|
||||
}
|
||||
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow |
|
||||
ImGuiTreeNodeFlags_OpenOnDoubleClick |
|
||||
ImGuiTreeNodeFlags_SpanFullWidth;
|
||||
if (fileBrowser.currentPath == path) {
|
||||
flags |= ImGuiTreeNodeFlags_Selected;
|
||||
}
|
||||
ImGui::PushID(path.string().c_str());
|
||||
bool open = ImGui::TreeNodeEx(name.c_str(), flags);
|
||||
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
|
||||
fileBrowser.navigateTo(path);
|
||||
}
|
||||
if (open) {
|
||||
std::vector<fs::path> dirs;
|
||||
std::error_code ec;
|
||||
for (const auto& entry : fs::directory_iterator(path, ec)) {
|
||||
if (ec) {
|
||||
break;
|
||||
}
|
||||
if (!entry.is_directory()) {
|
||||
continue;
|
||||
}
|
||||
std::string dirName = entry.path().filename().string();
|
||||
if (!fileBrowser.showHiddenFiles && !dirName.empty() && dirName[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
dirs.push_back(entry.path());
|
||||
}
|
||||
std::sort(dirs.begin(), dirs.end(), [](const fs::path& a, const fs::path& b) {
|
||||
return a.filename().string() < b.filename().string();
|
||||
});
|
||||
for (const auto& dir : dirs) {
|
||||
self(self, dir);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
ImGui::PopID();
|
||||
};
|
||||
|
||||
if (!baseRoot.empty()) {
|
||||
drawFolderTree(drawFolderTree, baseRoot);
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
float splitterHeight = ImGui::GetContentRegionAvail().y;
|
||||
if (splitterHeight < 1.0f) {
|
||||
splitterHeight = 1.0f;
|
||||
}
|
||||
ImGui::InvisibleButton("SidebarSplitter", ImVec2(4.0f, splitterHeight));
|
||||
if (ImGui::IsItemHovered() || ImGui::IsItemActive()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
|
||||
}
|
||||
if (ImGui::IsItemActive()) {
|
||||
fileBrowserSidebarWidth += ImGui::GetIO().MouseDelta.x;
|
||||
fileBrowserSidebarWidth = std::clamp(fileBrowserSidebarWidth, minSidebarWidth, maxSidebarWidth);
|
||||
settingsDirty = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
ImGui::BeginChild("FileMain", ImVec2(0, 0), false);
|
||||
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
|
||||
@@ -1168,9 +1343,14 @@ void Engine::renderFileBrowserPanel() {
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (settingsDirty) {
|
||||
saveEditorUserSettings();
|
||||
}
|
||||
|
||||
if (triggerDeletePopup) {
|
||||
ImGui::OpenPopup("Confirm Delete");
|
||||
triggerDeletePopup = false;
|
||||
|
||||
@@ -103,6 +103,34 @@ bool Spinner(const char* label, float radius, int thickness, const ImU32& color)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProgressCircle(const char* label, float radius, float thickness, float value,
|
||||
const ImU32& color, const ImU32& bgColor) {
|
||||
ImGuiWindow* window = GetCurrentWindow();
|
||||
if (window->SkipItems)
|
||||
return false;
|
||||
|
||||
ImGuiContext& g = *GImGui;
|
||||
const ImGuiStyle& style = g.Style;
|
||||
const ImGuiID id = window->GetID(label);
|
||||
|
||||
ImVec2 pos = window->DC.CursorPos;
|
||||
ImVec2 size((radius) * 2, (radius + style.FramePadding.y) * 2);
|
||||
const ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y));
|
||||
ItemSize(bb, style.FramePadding.y);
|
||||
if (!ItemAdd(bb, id))
|
||||
return false;
|
||||
|
||||
ImVec2 centre = ImVec2(pos.x + radius, pos.y + radius + style.FramePadding.y);
|
||||
float startAngle = -IM_PI * 0.5f;
|
||||
float endAngle = startAngle + IM_PI * 2.0f * ImClamp(value, 0.0f, 1.0f);
|
||||
|
||||
window->DrawList->AddCircle(centre, radius, bgColor, 32, thickness);
|
||||
window->DrawList->PathClear();
|
||||
window->DrawList->PathArcTo(centre, radius, startAngle, endAngle, 32);
|
||||
window->DrawList->PathStroke(color, false, thickness);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ImGui
|
||||
#pragma endregion
|
||||
|
||||
@@ -188,7 +216,7 @@ void Engine::renderLauncher() {
|
||||
ImGui::SetWindowFontScale(1.4f);
|
||||
ImGui::TextColored(ImVec4(0.95f, 0.96f, 0.98f, 1.0f), "Modularity");
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
ImGui::TextColored(ImVec4(0.70f, 0.73f, 0.80f, 1.0f), "Modularity | Beta V1.0");
|
||||
ImGui::TextColored(ImVec4(0.70f, 0.73f, 0.80f, 1.0f), "Modularity | Beta V6.3");
|
||||
|
||||
|
||||
ImGui::EndChild();
|
||||
@@ -352,7 +380,7 @@ void Engine::renderLauncher() {
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextDisabled("Modularity Engine - Beta V1.0");
|
||||
ImGui::TextDisabled("Modularity Engine - Beta V6.3");
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
@@ -365,7 +393,7 @@ void Engine::renderLauncher() {
|
||||
if (projectManager.showOpenProjectDialog)
|
||||
renderOpenProjectDialog();
|
||||
|
||||
if (projectLoadInProgress) {
|
||||
if (projectLoadInProgress || sceneLoadInProgress) {
|
||||
float elapsed = static_cast<float>(glfwGetTime() - projectLoadStartTime);
|
||||
if (elapsed > 0.15f) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
@@ -396,16 +424,32 @@ void Engine::renderLauncher() {
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoSavedSettings);
|
||||
|
||||
ImGui::TextColored(ImVec4(0.88f, 0.90f, 0.96f, 1.0f), "Loading project...");
|
||||
const char* headline = sceneLoadInProgress ? "Loading scene..." : "Loading project...";
|
||||
ImGui::TextColored(ImVec4(0.88f, 0.90f, 0.96f, 1.0f), "%s", headline);
|
||||
ImGui::Spacing();
|
||||
ImGui::TextDisabled("%s", projectLoadPath.c_str());
|
||||
if (sceneLoadInProgress && !sceneLoadStatus.empty()) {
|
||||
ImGui::TextDisabled("%s", sceneLoadStatus.c_str());
|
||||
} else if (!projectLoadPath.empty()) {
|
||||
ImGui::TextDisabled("%s", projectLoadPath.c_str());
|
||||
}
|
||||
ImGui::Spacing();
|
||||
ImGui::Spinner("##project_load_spinner", 16.0f, 4, ImGui::GetColorU32(ImGuiCol_ButtonHovered));
|
||||
ImGui::SameLine();
|
||||
ImGui::BufferingBar("##project_load_bar", std::fmod(elapsed * 0.25f, 1.0f),
|
||||
ImVec2(ImGui::GetContentRegionAvail().x - 40.0f, 8.0f),
|
||||
ImGui::GetColorU32(ImGuiCol_Button),
|
||||
ImGui::GetColorU32(ImGuiCol_ButtonHovered));
|
||||
if (sceneLoadInProgress) {
|
||||
ImGui::ProgressCircle("##project_load_circle", 16.0f, 4.0f, sceneLoadProgress,
|
||||
ImGui::GetColorU32(ImGuiCol_ButtonHovered),
|
||||
ImGui::GetColorU32(ImGuiCol_Button));
|
||||
ImGui::SameLine();
|
||||
ImGui::BufferingBar("##project_load_bar", sceneLoadProgress,
|
||||
ImVec2(ImGui::GetContentRegionAvail().x - 40.0f, 8.0f),
|
||||
ImGui::GetColorU32(ImGuiCol_Button),
|
||||
ImGui::GetColorU32(ImGuiCol_ButtonHovered));
|
||||
} else {
|
||||
ImGui::Spinner("##project_load_spinner", 16.0f, 4, ImGui::GetColorU32(ImGuiCol_ButtonHovered));
|
||||
ImGui::SameLine();
|
||||
ImGui::BufferingBar("##project_load_bar", std::fmod(elapsed * 0.25f, 1.0f),
|
||||
ImVec2(ImGui::GetContentRegionAvail().x - 40.0f, 8.0f),
|
||||
ImGui::GetColorU32(ImGuiCol_Button),
|
||||
ImGui::GetColorU32(ImGuiCol_ButtonHovered));
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
538
src/EditorWindows/ScriptingWindow.cpp
Normal file
538
src/EditorWindows/ScriptingWindow.cpp
Normal file
@@ -0,0 +1,538 @@
|
||||
#include "Engine.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace {
|
||||
static uint64_t hashBuffer(const std::string& text) {
|
||||
uint64_t hash = 1469598103934665603ull;
|
||||
for (unsigned char c : text) {
|
||||
hash ^= static_cast<uint64_t>(c);
|
||||
hash *= 1099511628211ull;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static std::string trimLeft(const std::string& value) {
|
||||
size_t start = value.find_first_not_of(" \t");
|
||||
if (start == std::string::npos) return "";
|
||||
return value.substr(start);
|
||||
}
|
||||
|
||||
static std::vector<std::string> buildSymbolList(const std::string& text) {
|
||||
std::vector<std::string> symbols;
|
||||
std::istringstream input(text);
|
||||
std::string line;
|
||||
while (std::getline(input, line)) {
|
||||
std::string trimmed = trimLeft(line);
|
||||
if (trimmed.empty()) continue;
|
||||
if (trimmed.rfind("//", 0) == 0) continue;
|
||||
|
||||
auto captureToken = [&](const std::string& prefix) -> bool {
|
||||
if (trimmed.rfind(prefix, 0) != 0) return false;
|
||||
size_t start = prefix.size();
|
||||
while (start < trimmed.size() && std::isspace(static_cast<unsigned char>(trimmed[start]))) {
|
||||
++start;
|
||||
}
|
||||
size_t end = start;
|
||||
while (end < trimmed.size() &&
|
||||
(std::isalnum(static_cast<unsigned char>(trimmed[end])) || trimmed[end] == '_' || trimmed[end] == ':')) {
|
||||
++end;
|
||||
}
|
||||
if (end > start) {
|
||||
symbols.emplace_back(trimmed.substr(0, end));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (captureToken("class ") || captureToken("struct ") || captureToken("enum ") || captureToken("namespace ")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.find('(') != std::string::npos && trimmed.find(')') != std::string::npos &&
|
||||
(trimmed.find('{') != std::string::npos || trimmed.back() == ';')) {
|
||||
static const char* kSkip[] = {"if", "for", "while", "switch", "catch"};
|
||||
bool skip = false;
|
||||
for (const char* keyword : kSkip) {
|
||||
if (trimmed.rfind(keyword, 0) == 0) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skip) continue;
|
||||
size_t paren = trimmed.find('(');
|
||||
if (paren != std::string::npos && paren > 0) {
|
||||
size_t end = paren;
|
||||
while (end > 0 && std::isspace(static_cast<unsigned char>(trimmed[end - 1]))) {
|
||||
--end;
|
||||
}
|
||||
size_t start = end;
|
||||
while (start > 0 &&
|
||||
(std::isalnum(static_cast<unsigned char>(trimmed[start - 1])) || trimmed[start - 1] == '_' ||
|
||||
trimmed[start - 1] == ':')) {
|
||||
--start;
|
||||
}
|
||||
if (end > start) {
|
||||
symbols.emplace_back(trimmed.substr(start, paren - start));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
|
||||
static std::vector<std::string> buildCompletionList(const std::vector<std::string>& pool,
|
||||
const std::string& prefix,
|
||||
size_t limit = 16) {
|
||||
std::vector<std::string> matches;
|
||||
if (prefix.empty()) return matches;
|
||||
for (const auto& entry : pool) {
|
||||
if (entry.rfind(prefix, 0) == 0) {
|
||||
matches.push_back(entry);
|
||||
if (matches.size() >= limit) break;
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
static const std::unordered_set<std::string>& cppKeywordSet() {
|
||||
static const std::unordered_set<std::string> kKeywords = {
|
||||
"auto", "bool", "break", "case", "catch", "char", "class", "const", "constexpr", "continue",
|
||||
"default", "delete", "do", "double", "else", "enum", "explicit", "extern", "false", "float",
|
||||
"for", "friend", "if", "inline", "int", "long", "mutable", "namespace", "new", "noexcept",
|
||||
"operator", "private", "protected", "public", "return", "short", "signed", "sizeof", "static",
|
||||
"struct", "switch", "template", "this", "throw", "true", "try", "typedef", "typename",
|
||||
"union", "unsigned", "using", "virtual", "void", "volatile", "while"
|
||||
};
|
||||
return kKeywords;
|
||||
}
|
||||
|
||||
static std::vector<std::string> extractIdentifiers(const std::string& text) {
|
||||
std::unordered_set<std::string> unique;
|
||||
const auto& keywords = cppKeywordSet();
|
||||
std::string token;
|
||||
token.reserve(64);
|
||||
auto flushToken = [&]() {
|
||||
if (token.size() >= 2 && keywords.find(token) == keywords.end()) {
|
||||
unique.insert(token);
|
||||
}
|
||||
token.clear();
|
||||
};
|
||||
for (char c : text) {
|
||||
if (std::isalnum(static_cast<unsigned char>(c)) || c == '_') {
|
||||
token.push_back(c);
|
||||
} else if (!token.empty()) {
|
||||
flushToken();
|
||||
}
|
||||
}
|
||||
if (!token.empty()) {
|
||||
flushToken();
|
||||
}
|
||||
std::vector<std::string> out(unique.begin(), unique.end());
|
||||
std::sort(out.begin(), out.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Engine::refreshScriptingFileList() {
|
||||
scriptingFileList.clear();
|
||||
scriptingCompletions.clear();
|
||||
if (!projectManager.currentProject.isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
fs::path configPath = resolveScriptsConfigPath(projectManager.currentProject);
|
||||
ScriptBuildConfig config;
|
||||
std::string error;
|
||||
if (!scriptCompiler.loadConfig(configPath, config, error)) {
|
||||
return;
|
||||
}
|
||||
fs::path scriptsRoot = config.scriptsDir;
|
||||
if (!scriptsRoot.is_absolute()) {
|
||||
scriptsRoot = projectManager.currentProject.projectPath / scriptsRoot;
|
||||
}
|
||||
std::error_code ec;
|
||||
if (!fs::exists(scriptsRoot, ec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::unordered_set<std::string> validExt = {
|
||||
".cpp", ".cc", ".cxx", ".c", ".hpp", ".h", ".inl"
|
||||
};
|
||||
|
||||
for (auto it = fs::recursive_directory_iterator(scriptsRoot, ec);
|
||||
it != fs::recursive_directory_iterator(); ++it) {
|
||||
if (it->is_directory()) continue;
|
||||
std::string ext = it->path().extension().string();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
||||
if (validExt.find(ext) == validExt.end()) continue;
|
||||
scriptingFileList.push_back(it->path());
|
||||
}
|
||||
|
||||
std::sort(scriptingFileList.begin(), scriptingFileList.end());
|
||||
|
||||
std::unordered_set<std::string> uniqueSymbols;
|
||||
for (const auto& scriptPath : scriptingFileList) {
|
||||
std::ifstream file(scriptPath);
|
||||
if (!file.is_open()) continue;
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
std::vector<std::string> symbols = buildSymbolList(buffer.str());
|
||||
for (auto& symbol : symbols) {
|
||||
uniqueSymbols.insert(symbol);
|
||||
}
|
||||
}
|
||||
scriptingCompletions.assign(uniqueSymbols.begin(), uniqueSymbols.end());
|
||||
std::sort(scriptingCompletions.begin(), scriptingCompletions.end());
|
||||
}
|
||||
|
||||
void Engine::openScriptInEditor(const fs::path& path) {
|
||||
if (path.empty()) return;
|
||||
std::error_code ec;
|
||||
fs::path absPath = fs::absolute(path, ec);
|
||||
fs::path normalized = (ec ? path : absPath).lexically_normal();
|
||||
|
||||
std::ifstream file(normalized);
|
||||
std::stringstream buffer;
|
||||
if (file.is_open()) {
|
||||
buffer << file.rdbuf();
|
||||
}
|
||||
scriptEditorState.filePath = normalized;
|
||||
scriptEditorState.buffer = buffer.str();
|
||||
if (!scriptTextEditorReady) {
|
||||
auto lang = TextEditor::LanguageDefinition::CPlusPlus();
|
||||
lang.mIdentifiers.insert({"Begin", {}});
|
||||
lang.mIdentifiers.insert({"TickUpdate", {}});
|
||||
lang.mIdentifiers.insert({"Spec", {}});
|
||||
lang.mIdentifiers.insert({"TestEditor", {}});
|
||||
lang.mIdentifiers.insert({"Update", {}});
|
||||
scriptTextEditor.SetLanguageDefinition(lang);
|
||||
auto palette = scriptTextEditor.GetPalette();
|
||||
palette[(int)TextEditor::PaletteIndex::KnownIdentifier] = IM_COL32(220, 180, 70, 255);
|
||||
scriptTextEditor.SetPalette(palette);
|
||||
scriptTextEditor.SetShowWhitespaces(true);
|
||||
scriptTextEditor.SetAllowTabInput(false);
|
||||
scriptTextEditor.SetSmartTabDelete(true);
|
||||
scriptTextEditorReady = true;
|
||||
}
|
||||
scriptTextEditor.SetText(scriptEditorState.buffer);
|
||||
scriptEditorState.dirty = false;
|
||||
scriptEditorState.hasWriteTime = false;
|
||||
if (fs::exists(normalized, ec)) {
|
||||
scriptEditorState.lastWriteTime = fs::last_write_time(normalized, ec);
|
||||
scriptEditorState.hasWriteTime = !ec;
|
||||
}
|
||||
showScriptingWindow = true;
|
||||
}
|
||||
|
||||
void Engine::renderScriptingWindow() {
|
||||
if (!showScriptingWindow) return;
|
||||
|
||||
if (scriptingFilesDirty) {
|
||||
refreshScriptingFileList();
|
||||
scriptingFilesDirty = false;
|
||||
}
|
||||
|
||||
ImGui::Begin("Scripting", &showScriptingWindow);
|
||||
if (!projectManager.currentProject.isLoaded) {
|
||||
ImGui::TextDisabled("Load a project to edit scripts.");
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
static std::vector<std::string> symbols;
|
||||
static uint64_t symbolsHash = 0;
|
||||
static std::vector<std::string> bufferIdentifiers;
|
||||
static uint64_t identifiersHash = 0;
|
||||
static std::vector<std::string> completionPool;
|
||||
static std::vector<std::string> activeSuggestions;
|
||||
static std::string activePrefix;
|
||||
static bool completionPanelOpen = true;
|
||||
|
||||
ImGui::TextDisabled("C++ Script Editor");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Refresh List")) {
|
||||
scriptingFilesDirty = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
float leftWidth = 240.0f;
|
||||
ImGui::BeginChild("ScriptingFiles", ImVec2(leftWidth, 0.0f), true);
|
||||
ImGui::TextDisabled("Scripts");
|
||||
ImGui::InputTextWithHint("##ScriptFilter", "Filter", scriptingFilter, sizeof(scriptingFilter));
|
||||
ImGui::Separator();
|
||||
|
||||
for (const auto& scriptPath : scriptingFileList) {
|
||||
std::string label = scriptPath.filename().string();
|
||||
std::string filter = scriptingFilter;
|
||||
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
|
||||
std::string lowerLabel = label;
|
||||
std::transform(lowerLabel.begin(), lowerLabel.end(), lowerLabel.begin(), ::tolower);
|
||||
if (!filter.empty() && lowerLabel.find(filter) == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
bool selected = (scriptEditorState.filePath == scriptPath);
|
||||
if (ImGui::Selectable(label.c_str(), selected)) {
|
||||
openScriptInEditor(scriptPath);
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginChild("ScriptingEditor", ImVec2(0.0f, 0.0f), false);
|
||||
ImGui::TextDisabled("Active File");
|
||||
ImGui::SameLine();
|
||||
std::string fileLabel = scriptEditorState.filePath.empty()
|
||||
? std::string("None")
|
||||
: scriptEditorState.filePath.filename().string();
|
||||
ImGui::TextUnformatted(fileLabel.c_str());
|
||||
|
||||
bool hasFile = !scriptEditorState.filePath.empty();
|
||||
ImGui::SameLine();
|
||||
if (!hasFile) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
if (ImGui::Button("Save")) {
|
||||
std::ofstream out(scriptEditorState.filePath);
|
||||
if (out.is_open()) {
|
||||
scriptEditorState.buffer = scriptTextEditor.GetText();
|
||||
out << scriptEditorState.buffer;
|
||||
scriptEditorState.dirty = false;
|
||||
std::error_code ec;
|
||||
scriptEditorState.lastWriteTime = fs::last_write_time(scriptEditorState.filePath, ec);
|
||||
scriptEditorState.hasWriteTime = !ec;
|
||||
if (scriptEditorState.autoCompileOnSave) {
|
||||
compileScriptFile(scriptEditorState.filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Compile")) {
|
||||
compileScriptFile(scriptEditorState.filePath);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Auto-compile on save", &scriptEditorState.autoCompileOnSave);
|
||||
if (!hasFile) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
bool canReload = false;
|
||||
if (hasFile && scriptEditorState.hasWriteTime) {
|
||||
std::error_code ec;
|
||||
if (fs::exists(scriptEditorState.filePath, ec)) {
|
||||
auto diskTime = fs::last_write_time(scriptEditorState.filePath, ec);
|
||||
if (!ec && diskTime > scriptEditorState.lastWriteTime) {
|
||||
canReload = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canReload) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.95f, 0.75f, 0.35f, 1.0f), "File changed on disk");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reload")) {
|
||||
openScriptInEditor(scriptEditorState.filePath);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::BeginTabBar("ScriptingTabs")) {
|
||||
if (ImGui::BeginTabItem("Editor")) {
|
||||
if (hasFile) {
|
||||
if (!scriptTextEditorReady) {
|
||||
auto lang = TextEditor::LanguageDefinition::CPlusPlus();
|
||||
lang.mIdentifiers.insert({"Begin", {}});
|
||||
lang.mIdentifiers.insert({"TickUpdate", {}});
|
||||
lang.mIdentifiers.insert({"Spec", {}});
|
||||
lang.mIdentifiers.insert({"TestEditor", {}});
|
||||
lang.mIdentifiers.insert({"Update", {}});
|
||||
scriptTextEditor.SetLanguageDefinition(lang);
|
||||
auto palette = scriptTextEditor.GetPalette();
|
||||
palette[(int)TextEditor::PaletteIndex::KnownIdentifier] = IM_COL32(220, 180, 70, 255);
|
||||
scriptTextEditor.SetPalette(palette);
|
||||
scriptTextEditor.SetShowWhitespaces(true);
|
||||
scriptTextEditor.SetAllowTabInput(false);
|
||||
scriptTextEditor.SetSmartTabDelete(true);
|
||||
scriptTextEditorReady = true;
|
||||
}
|
||||
completionPool.clear();
|
||||
std::unordered_set<std::string> poolSet;
|
||||
for (const auto& kw : cppKeywordSet()) {
|
||||
poolSet.insert(kw);
|
||||
}
|
||||
for (const auto& entry : scriptingCompletions) {
|
||||
poolSet.insert(entry);
|
||||
}
|
||||
for (const auto& entry : symbols) {
|
||||
poolSet.insert(entry);
|
||||
}
|
||||
uint64_t bufferHash = hashBuffer(scriptEditorState.buffer);
|
||||
if (bufferHash != identifiersHash) {
|
||||
identifiersHash = bufferHash;
|
||||
bufferIdentifiers = extractIdentifiers(scriptEditorState.buffer);
|
||||
}
|
||||
for (const auto& entry : bufferIdentifiers) {
|
||||
poolSet.insert(entry);
|
||||
}
|
||||
completionPool.assign(poolSet.begin(), poolSet.end());
|
||||
std::sort(completionPool.begin(), completionPool.end());
|
||||
|
||||
TextEditor::Coordinates cursorBefore = scriptTextEditor.GetCursorPosition();
|
||||
activePrefix = scriptTextEditor.GetWordAtPublic(cursorBefore);
|
||||
if (activePrefix.empty() && cursorBefore.mColumn > 0) {
|
||||
TextEditor::Coordinates prev(cursorBefore.mLine, cursorBefore.mColumn - 1);
|
||||
activePrefix = scriptTextEditor.GetWordAtPublic(prev);
|
||||
}
|
||||
if (!activePrefix.empty() && activePrefix.size() >= 2) {
|
||||
activeSuggestions = buildCompletionList(completionPool, activePrefix);
|
||||
} else {
|
||||
activeSuggestions.clear();
|
||||
}
|
||||
|
||||
bool tabPressed = ImGui::IsKeyPressed(ImGuiKey_Tab);
|
||||
bool canComplete = !activeSuggestions.empty() && !ImGui::GetIO().KeyShift;
|
||||
scriptTextEditor.SetAllowTabInput(!canComplete);
|
||||
|
||||
float completionHeight = completionPanelOpen ? 140.0f : 0.0f;
|
||||
float availHeight = ImGui::GetContentRegionAvail().y;
|
||||
float editorHeight = std::max(120.0f, availHeight - completionHeight - 12.0f);
|
||||
ImVec2 editorSize = ImVec2(0.0f, editorHeight);
|
||||
scriptTextEditor.Render("##ScriptEditor", editorSize, false);
|
||||
if (scriptTextEditor.IsTextChanged()) {
|
||||
scriptEditorState.dirty = true;
|
||||
scriptEditorState.buffer = scriptTextEditor.GetText();
|
||||
}
|
||||
uint64_t newHash = hashBuffer(scriptEditorState.buffer);
|
||||
if (newHash != symbolsHash) {
|
||||
symbolsHash = newHash;
|
||||
symbols = buildSymbolList(scriptEditorState.buffer);
|
||||
}
|
||||
|
||||
TextEditor::Coordinates cursorAfter = scriptTextEditor.GetCursorPosition();
|
||||
activePrefix = scriptTextEditor.GetWordAtPublic(cursorAfter);
|
||||
if (activePrefix.empty() && cursorAfter.mColumn > 0) {
|
||||
TextEditor::Coordinates prev(cursorAfter.mLine, cursorAfter.mColumn - 1);
|
||||
activePrefix = scriptTextEditor.GetWordAtPublic(prev);
|
||||
}
|
||||
if (!activePrefix.empty() && activePrefix.size() >= 2) {
|
||||
activeSuggestions = buildCompletionList(completionPool, activePrefix);
|
||||
} else {
|
||||
activeSuggestions.clear();
|
||||
}
|
||||
bool canCompleteNow = !activeSuggestions.empty() && !ImGui::GetIO().KeyShift;
|
||||
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
|
||||
tabPressed && canCompleteNow) {
|
||||
TextEditor::Coordinates cursor = scriptTextEditor.GetCursorPosition();
|
||||
TextEditor::Coordinates start(cursor.mLine,
|
||||
std::max(0, cursor.mColumn - static_cast<int>(activePrefix.size())));
|
||||
scriptTextEditor.SetSelection(start, cursor);
|
||||
scriptTextEditor.Delete();
|
||||
scriptTextEditor.InsertText(activeSuggestions.front().c_str());
|
||||
scriptEditorState.dirty = true;
|
||||
scriptEditorState.buffer = scriptTextEditor.GetText();
|
||||
}
|
||||
|
||||
if (completionPanelOpen) {
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled("Completions");
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Show", &completionPanelOpen);
|
||||
ImGui::BeginChild("CompletionList", ImVec2(0.0f, completionHeight), true);
|
||||
if (activeSuggestions.empty()) {
|
||||
ImGui::TextDisabled("No suggestions");
|
||||
} else {
|
||||
for (const auto& suggestion : activeSuggestions) {
|
||||
if (ImGui::Selectable(suggestion.c_str())) {
|
||||
TextEditor::Coordinates cursor = scriptTextEditor.GetCursorPosition();
|
||||
TextEditor::Coordinates start(cursor.mLine,
|
||||
std::max(0, cursor.mColumn - static_cast<int>(activePrefix.size())));
|
||||
scriptTextEditor.SetSelection(start, cursor);
|
||||
scriptTextEditor.Delete();
|
||||
scriptTextEditor.InsertText(suggestion.c_str());
|
||||
scriptEditorState.dirty = true;
|
||||
scriptEditorState.buffer = scriptTextEditor.GetText();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
if (canCompleteNow && scriptTextEditor.HasCursorScreenPosition()) {
|
||||
const std::string& suggestion = activeSuggestions.front();
|
||||
if (suggestion.size() > activePrefix.size() &&
|
||||
suggestion.rfind(activePrefix, 0) == 0) {
|
||||
std::string ghost = suggestion.substr(activePrefix.size());
|
||||
ImVec2 ghostPos = scriptTextEditor.GetCursorScreenPositionPublic();
|
||||
ImU32 ghostColor = IM_COL32(180, 180, 180, 110);
|
||||
ImGui::GetWindowDrawList()->AddText(ghostPos, ghostColor, ghost.c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled("Select a script file to start editing.");
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Intellisense")) {
|
||||
ScriptBuildConfig config;
|
||||
std::string error;
|
||||
fs::path configPath = resolveScriptsConfigPath(projectManager.currentProject);
|
||||
bool hasConfig = scriptCompiler.loadConfig(configPath, config, error);
|
||||
if (hasConfig) {
|
||||
packageManager.applyToBuildConfig(config);
|
||||
ImGui::TextDisabled("Compiler");
|
||||
ImGui::Text("Standard: %s", config.cppStandard.c_str());
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled("Include Dirs");
|
||||
for (const auto& includeDir : config.includeDirs) {
|
||||
ImGui::BulletText("%s", includeDir.string().c_str());
|
||||
}
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled("Defines");
|
||||
for (const auto& def : config.defines) {
|
||||
ImGui::BulletText("%s", def.c_str());
|
||||
}
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.95f, 0.55f, 0.55f, 1.0f), "Scripts.modu not loaded");
|
||||
if (!error.empty()) {
|
||||
ImGui::TextWrapped("%s", error.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled("Outline");
|
||||
if (!symbols.empty()) {
|
||||
for (const auto& symbol : symbols) {
|
||||
ImGui::BulletText("%s", symbol.c_str());
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled("No symbols detected.");
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Build")) {
|
||||
ImGui::TextDisabled("Compile Status");
|
||||
ImGui::Text("%s", lastCompileStatus.c_str());
|
||||
if (!lastCompileLog.empty()) {
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled("Output");
|
||||
ImGui::BeginChild("CompileLog", ImVec2(0.0f, 0.0f), true);
|
||||
ImGui::TextUnformatted(lastCompileLog.c_str());
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
3052
src/Engine.cpp
3052
src/Engine.cpp
File diff suppressed because it is too large
Load Diff
181
src/Engine.h
181
src/Engine.h
@@ -12,6 +12,8 @@
|
||||
#include "PhysicsSystem.h"
|
||||
#include "AudioSystem.h"
|
||||
#include "PackageManager.h"
|
||||
#include "ManagedScriptRuntime.h"
|
||||
#include "ThirdParty/ImGuiColorTextEdit/TextEditor.h"
|
||||
#include "../include/Window/Window.h"
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -22,6 +24,7 @@
|
||||
#include <thread>
|
||||
|
||||
void window_size_callback(GLFWwindow* window, int width, int height);
|
||||
fs::path resolveScriptsConfigPath(const Project& project);
|
||||
|
||||
class Engine {
|
||||
private:
|
||||
@@ -74,7 +77,15 @@ private:
|
||||
bool showConsole = true;
|
||||
bool showProjectBrowser = true; // Now merged into file browser
|
||||
bool showMeshBuilder = false;
|
||||
bool showBuildSettings = false;
|
||||
bool showStyleEditor = false;
|
||||
bool showScriptingWindow = false;
|
||||
bool firstFrame = true;
|
||||
bool playerMode = false;
|
||||
bool autoStartRequested = false;
|
||||
bool autoStartPlayerMode = false;
|
||||
std::string autoStartProjectPath;
|
||||
std::string autoStartSceneName;
|
||||
std::vector<std::string> consoleLog;
|
||||
int draggedObjectId = -1;
|
||||
|
||||
@@ -103,8 +114,34 @@ private:
|
||||
|
||||
char fileBrowserSearch[256] = "";
|
||||
float fileBrowserIconScale = 1.0f; // 0.5 to 2.0 range
|
||||
float fileBrowserSidebarWidth = 220.0f;
|
||||
bool showFileBrowserSidebar = true;
|
||||
std::vector<fs::path> fileBrowserFavorites;
|
||||
std::string uiStylePresetName = "Current";
|
||||
enum class UIAnimationMode {
|
||||
Off = 0,
|
||||
Snappy = 1,
|
||||
Fluid = 2
|
||||
};
|
||||
enum class WorkspaceMode {
|
||||
Default = 0,
|
||||
Animation = 1,
|
||||
Scripting = 2
|
||||
};
|
||||
UIAnimationMode uiAnimationMode = UIAnimationMode::Off;
|
||||
WorkspaceMode currentWorkspace = WorkspaceMode::Default;
|
||||
bool workspaceLayoutDirty = false;
|
||||
bool pendingWorkspaceReload = false;
|
||||
fs::path pendingWorkspaceIniPath;
|
||||
bool editorSettingsDirty = false;
|
||||
bool showEnvironmentWindow = true;
|
||||
bool showCameraWindow = true;
|
||||
bool showAnimationWindow = false;
|
||||
int animationTargetId = -1;
|
||||
int animationSelectedKey = -1;
|
||||
float animationCurrentTime = 0.0f;
|
||||
bool animationIsPlaying = false;
|
||||
float animationLastAppliedTime = -1.0f;
|
||||
bool hierarchyShowTexturePreview = false;
|
||||
bool hierarchyPreviewNearest = false;
|
||||
std::unordered_map<std::string, bool> texturePreviewFilterOverrides;
|
||||
@@ -121,6 +158,8 @@ private:
|
||||
bool gameViewportFocused = false;
|
||||
bool showGameProfiler = true;
|
||||
bool showCanvasOverlay = false;
|
||||
bool showUIWorldGrid = true;
|
||||
bool showSceneGrid3D = false;
|
||||
int gameViewportResolutionIndex = 0;
|
||||
int gameViewportCustomWidth = 1920;
|
||||
int gameViewportCustomHeight = 1080;
|
||||
@@ -139,10 +178,41 @@ private:
|
||||
std::vector<int> meshEditSelectedVertices;
|
||||
std::vector<int> meshEditSelectedEdges; // indices into generated edge list
|
||||
std::vector<int> meshEditSelectedFaces; // indices into mesh faces
|
||||
struct UIAnimationState {
|
||||
float hover = 0.0f;
|
||||
float active = 0.0f;
|
||||
float sliderValue = 0.0f;
|
||||
bool initialized = false;
|
||||
};
|
||||
std::unordered_map<int, UIAnimationState> uiAnimationStates;
|
||||
struct UIWorldCamera2D {
|
||||
glm::vec2 position = glm::vec2(0.0f);
|
||||
float zoom = 100.0f; // pixels per world unit
|
||||
glm::vec2 viewportSize = glm::vec2(0.0f);
|
||||
|
||||
glm::vec2 WorldToScreen(const glm::vec2& world) const {
|
||||
return glm::vec2(
|
||||
(world.x - position.x) * zoom + viewportSize.x * 0.5f,
|
||||
(position.y - world.y) * zoom + viewportSize.y * 0.5f
|
||||
);
|
||||
}
|
||||
|
||||
glm::vec2 ScreenToWorld(const glm::vec2& screen) const {
|
||||
return glm::vec2(
|
||||
(screen.x - viewportSize.x * 0.5f) / zoom + position.x,
|
||||
position.y - (screen.y - viewportSize.y * 0.5f) / zoom
|
||||
);
|
||||
}
|
||||
};
|
||||
bool uiWorldMode = false;
|
||||
bool uiWorldPanning = false;
|
||||
UIWorldCamera2D uiWorldCamera;
|
||||
bool consoleWrapText = true;
|
||||
enum class MeshEditSelectionMode { Vertex = 0, Edge = 1, Face = 2 };
|
||||
MeshEditSelectionMode meshEditSelectionMode = MeshEditSelectionMode::Vertex;
|
||||
ScriptCompiler scriptCompiler;
|
||||
ScriptRuntime scriptRuntime;
|
||||
ManagedScriptRuntime managedRuntime;
|
||||
PhysicsSystem physics;
|
||||
AudioSystem audio;
|
||||
bool showCompilePopup = false;
|
||||
@@ -151,8 +221,58 @@ private:
|
||||
bool lastCompileSuccess = false;
|
||||
std::string lastCompileStatus;
|
||||
std::string lastCompileLog;
|
||||
float compileProgress = 0.0f;
|
||||
std::string compileStage;
|
||||
enum class BuildPlatform {
|
||||
Windows = 0,
|
||||
Linux = 1,
|
||||
Android = 2
|
||||
};
|
||||
struct BuildSceneEntry {
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
};
|
||||
struct BuildSettings {
|
||||
BuildPlatform platform = BuildPlatform::Windows;
|
||||
std::string architecture = "x86_64";
|
||||
bool developmentBuild = false;
|
||||
bool autoConnectProfiler = false;
|
||||
bool scriptDebugging = false;
|
||||
bool deepProfiling = false;
|
||||
bool scriptsOnlyBuild = false;
|
||||
bool serverBuild = false;
|
||||
std::string compressionMethod = "Default";
|
||||
std::vector<BuildSceneEntry> scenes;
|
||||
};
|
||||
BuildSettings buildSettings;
|
||||
int buildSettingsSelectedIndex = -1;
|
||||
bool buildSettingsDirty = false;
|
||||
struct ExportJobResult {
|
||||
bool success = false;
|
||||
std::string message;
|
||||
fs::path outputDir;
|
||||
};
|
||||
struct ExportJobState {
|
||||
bool active = false;
|
||||
bool done = false;
|
||||
bool success = false;
|
||||
bool cancelled = false;
|
||||
float progress = 0.0f;
|
||||
std::string status;
|
||||
std::string log;
|
||||
fs::path outputDir;
|
||||
bool runAfter = false;
|
||||
std::future<ExportJobResult> future;
|
||||
};
|
||||
ExportJobState exportJob;
|
||||
std::atomic<bool> exportCancelRequested = false;
|
||||
std::mutex exportMutex;
|
||||
bool showExportDialog = false;
|
||||
bool exportRunAfter = false;
|
||||
char exportOutputPath[512] = "";
|
||||
struct ScriptCompileJobResult {
|
||||
bool success = false;
|
||||
bool isManaged = false;
|
||||
fs::path scriptPath;
|
||||
fs::path binaryPath;
|
||||
std::string compiledSource;
|
||||
@@ -168,6 +288,7 @@ private:
|
||||
std::unordered_map<std::string, fs::file_time_type> scriptLastAutoCompileTime;
|
||||
std::deque<fs::path> autoCompileQueue;
|
||||
std::unordered_set<std::string> autoCompileQueued;
|
||||
bool managedAutoCompileQueued = false;
|
||||
double scriptAutoCompileLastCheck = 0.0;
|
||||
double scriptAutoCompileInterval = 0.5;
|
||||
struct ProjectLoadResult {
|
||||
@@ -180,6 +301,16 @@ private:
|
||||
double projectLoadStartTime = 0.0;
|
||||
std::string projectLoadPath;
|
||||
std::future<ProjectLoadResult> projectLoadFuture;
|
||||
bool sceneLoadInProgress = false;
|
||||
float sceneLoadProgress = 0.0f;
|
||||
std::string sceneLoadStatus;
|
||||
std::string sceneLoadSceneName;
|
||||
std::vector<SceneObject> sceneLoadObjects;
|
||||
std::vector<size_t> sceneLoadAssetIndices;
|
||||
size_t sceneLoadAssetsDone = 0;
|
||||
int sceneLoadNextId = 0;
|
||||
int sceneLoadVersion = 9;
|
||||
float sceneLoadTimeOfDay = -1.0f;
|
||||
bool specMode = false;
|
||||
bool testMode = false;
|
||||
bool collisionWireframe = false;
|
||||
@@ -192,6 +323,21 @@ private:
|
||||
};
|
||||
std::vector<UIStylePreset> uiStylePresets;
|
||||
int uiStylePresetIndex = 0;
|
||||
struct ScriptEditorState {
|
||||
fs::path filePath;
|
||||
std::string buffer;
|
||||
bool dirty = false;
|
||||
bool autoCompileOnSave = true;
|
||||
bool hasWriteTime = false;
|
||||
fs::file_time_type lastWriteTime;
|
||||
};
|
||||
ScriptEditorState scriptEditorState;
|
||||
std::vector<fs::path> scriptingFileList;
|
||||
std::vector<std::string> scriptingCompletions;
|
||||
TextEditor scriptTextEditor;
|
||||
bool scriptTextEditorReady = false;
|
||||
char scriptingFilter[128] = "";
|
||||
bool scriptingFilesDirty = true;
|
||||
// Private methods
|
||||
SceneObject* getSelectedObject();
|
||||
glm::vec3 getSelectionCenterWorld(bool worldSpace) const;
|
||||
@@ -222,6 +368,7 @@ private:
|
||||
void renderPlayControlsBar();
|
||||
void renderEnvironmentWindow();
|
||||
void renderCameraWindow();
|
||||
void renderAnimationWindow();
|
||||
void renderHierarchyPanel();
|
||||
void renderObjectNode(SceneObject& obj, const std::string& filter,
|
||||
std::vector<bool>& ancestorHasNext, bool isLast, int depth);
|
||||
@@ -230,12 +377,16 @@ private:
|
||||
void renderInspectorPanel();
|
||||
void renderConsolePanel();
|
||||
void renderViewport();
|
||||
void renderPlayerViewport();
|
||||
void renderGameViewportWindow();
|
||||
void renderBuildSettingsWindow();
|
||||
void renderScriptingWindow();
|
||||
void renderDialogs();
|
||||
void updateCompileJob();
|
||||
void renderProjectBrowserPanel();
|
||||
void renderScriptEditorWindows();
|
||||
void refreshScriptEditorWindows();
|
||||
void refreshScriptingFileList();
|
||||
Camera makeCameraFromObject(const SceneObject& obj) const;
|
||||
void compileScriptFile(const fs::path& scriptPath);
|
||||
void updateAutoCompileScripts();
|
||||
@@ -244,13 +395,38 @@ private:
|
||||
void startProjectLoad(const std::string& path);
|
||||
void pollProjectLoad();
|
||||
void finishProjectLoad(ProjectLoadResult& result);
|
||||
void beginDeferredSceneLoad(const std::string& sceneName);
|
||||
void pollSceneLoad();
|
||||
void finalizeDeferredSceneLoad();
|
||||
void syncPlayerCamera();
|
||||
void updateScripts(float delta);
|
||||
void updatePlayerController(float delta);
|
||||
void updateRigidbody2D(float delta);
|
||||
void updateCameraFollow2D(float delta);
|
||||
void updateSkeletalAnimations(float delta);
|
||||
void updateSkinningMatrices();
|
||||
void rebuildSkeletalBindings();
|
||||
void initUIStylePresets();
|
||||
int findUIStylePreset(const std::string& name) const;
|
||||
const UIStylePreset* getUIStylePreset(const std::string& name) const;
|
||||
void registerUIStylePreset(const std::string& name, const ImGuiStyle& style, bool replace);
|
||||
bool applyUIStylePresetByName(const std::string& name);
|
||||
void applyWorkspacePreset(WorkspaceMode mode, bool rebuildLayout);
|
||||
void buildWorkspaceLayout(WorkspaceMode mode);
|
||||
fs::path getEditorUserSettingsPath() const;
|
||||
fs::path getEditorLayoutPath() const;
|
||||
fs::path getWorkspaceLayoutPath(WorkspaceMode mode) const;
|
||||
void loadEditorUserSettings();
|
||||
void saveEditorUserSettings() const;
|
||||
void exportEditorThemeLayout();
|
||||
void resetBuildSettings();
|
||||
void loadBuildSettings();
|
||||
void saveBuildSettings();
|
||||
bool addSceneToBuildSettings(const std::string& sceneName, bool enabled);
|
||||
void loadAutoStartConfig();
|
||||
void applyAutoStartMode();
|
||||
void startExportBuild(const fs::path& outputDir, bool runAfter);
|
||||
void pollExportBuild();
|
||||
|
||||
void renderFileBrowserToolbar();
|
||||
void renderFileBrowserBreadcrumb();
|
||||
@@ -258,6 +434,7 @@ private:
|
||||
void renderFileBrowserListView();
|
||||
void renderFileContextMenu(const fs::directory_entry& entry);
|
||||
void handleFileDoubleClick(const fs::directory_entry& entry);
|
||||
void openScriptInEditor(const fs::path& path);
|
||||
ImVec4 getFileCategoryColor(FileCategory category) const;
|
||||
const char* getFileCategoryIconText(FileCategory category) const;
|
||||
|
||||
@@ -308,6 +485,10 @@ public:
|
||||
SceneObject* findObjectByName(const std::string& name);
|
||||
SceneObject* findObjectById(int id);
|
||||
fs::path resolveScriptBinary(const fs::path& sourcePath);
|
||||
fs::path resolveManagedAssembly(const fs::path& sourcePath);
|
||||
fs::path getManagedProjectPath() const;
|
||||
fs::path getManagedOutputDll() const;
|
||||
void compileManagedScripts();
|
||||
void markProjectDirty();
|
||||
// Script-accessible logging wrapper
|
||||
void addConsoleMessageFromScript(const std::string& message, ConsoleMessageType type);
|
||||
|
||||
145
src/ManagedBindings.cpp
Normal file
145
src/ManagedBindings.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
#include "ManagedBindings.h"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
int modu_ctx_get_object_id(ScriptContext* ctx) {
|
||||
return (ctx && ctx->object) ? ctx->object->id : -1;
|
||||
}
|
||||
|
||||
void modu_ctx_get_position(ScriptContext* ctx, float* x, float* y, float* z) {
|
||||
if (!ctx || !ctx->object || !x || !y || !z) return;
|
||||
*x = ctx->object->position.x;
|
||||
*y = ctx->object->position.y;
|
||||
*z = ctx->object->position.z;
|
||||
}
|
||||
|
||||
void modu_ctx_set_position(ScriptContext* ctx, float x, float y, float z) {
|
||||
if (!ctx) return;
|
||||
ctx->SetPosition(glm::vec3(x, y, z));
|
||||
}
|
||||
|
||||
void modu_ctx_get_rotation(ScriptContext* ctx, float* x, float* y, float* z) {
|
||||
if (!ctx || !ctx->object || !x || !y || !z) return;
|
||||
*x = ctx->object->rotation.x;
|
||||
*y = ctx->object->rotation.y;
|
||||
*z = ctx->object->rotation.z;
|
||||
}
|
||||
|
||||
void modu_ctx_set_rotation(ScriptContext* ctx, float x, float y, float z) {
|
||||
if (!ctx) return;
|
||||
ctx->SetRotation(glm::vec3(x, y, z));
|
||||
}
|
||||
|
||||
void modu_ctx_get_scale(ScriptContext* ctx, float* x, float* y, float* z) {
|
||||
if (!ctx || !ctx->object || !x || !y || !z) return;
|
||||
*x = ctx->object->scale.x;
|
||||
*y = ctx->object->scale.y;
|
||||
*z = ctx->object->scale.z;
|
||||
}
|
||||
|
||||
void modu_ctx_set_scale(ScriptContext* ctx, float x, float y, float z) {
|
||||
if (!ctx) return;
|
||||
ctx->SetScale(glm::vec3(x, y, z));
|
||||
}
|
||||
|
||||
int modu_ctx_has_rigidbody(ScriptContext* ctx) {
|
||||
return (ctx && ctx->HasRigidbody()) ? 1 : 0;
|
||||
}
|
||||
|
||||
int modu_ctx_ensure_rigidbody(ScriptContext* ctx, int useGravity, int kinematic) {
|
||||
if (!ctx) return 0;
|
||||
return ctx->EnsureRigidbody(useGravity != 0, kinematic != 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
int modu_ctx_set_rigidbody_velocity(ScriptContext* ctx, float x, float y, float z) {
|
||||
if (!ctx) return 0;
|
||||
return ctx->SetRigidbodyVelocity(glm::vec3(x, y, z)) ? 1 : 0;
|
||||
}
|
||||
|
||||
int modu_ctx_get_rigidbody_velocity(ScriptContext* ctx, float* x, float* y, float* z) {
|
||||
if (!ctx || !x || !y || !z) return 0;
|
||||
glm::vec3 velocity(0.0f);
|
||||
if (!ctx->GetRigidbodyVelocity(velocity)) return 0;
|
||||
*x = velocity.x;
|
||||
*y = velocity.y;
|
||||
*z = velocity.z;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int modu_ctx_add_rigidbody_force(ScriptContext* ctx, float x, float y, float z) {
|
||||
if (!ctx) return 0;
|
||||
return ctx->AddRigidbodyForce(glm::vec3(x, y, z)) ? 1 : 0;
|
||||
}
|
||||
|
||||
int modu_ctx_add_rigidbody_impulse(ScriptContext* ctx, float x, float y, float z) {
|
||||
if (!ctx) return 0;
|
||||
return ctx->AddRigidbodyImpulse(glm::vec3(x, y, z)) ? 1 : 0;
|
||||
}
|
||||
|
||||
float modu_ctx_get_setting_float(ScriptContext* ctx, const char* key, float fallback) {
|
||||
if (!ctx || !key) return fallback;
|
||||
return ctx->GetSettingFloat(key, fallback);
|
||||
}
|
||||
|
||||
int modu_ctx_get_setting_bool(ScriptContext* ctx, const char* key, int fallback) {
|
||||
if (!ctx || !key) return fallback ? 1 : 0;
|
||||
return ctx->GetSettingBool(key, fallback != 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
void modu_ctx_get_setting_string(ScriptContext* ctx, const char* key, const char* fallback,
|
||||
char* outBuffer, int outBufferSize) {
|
||||
if (!outBuffer || outBufferSize <= 0) return;
|
||||
std::string value;
|
||||
if (!ctx || !key) {
|
||||
value = fallback ? fallback : "";
|
||||
} else {
|
||||
value = ctx->GetSetting(key, fallback ? fallback : "");
|
||||
}
|
||||
std::snprintf(outBuffer, static_cast<size_t>(outBufferSize), "%s", value.c_str());
|
||||
}
|
||||
|
||||
void modu_ctx_set_setting_float(ScriptContext* ctx, const char* key, float value) {
|
||||
if (!ctx || !key) return;
|
||||
ctx->SetSettingFloat(key, value);
|
||||
}
|
||||
|
||||
void modu_ctx_set_setting_bool(ScriptContext* ctx, const char* key, int value) {
|
||||
if (!ctx || !key) return;
|
||||
ctx->SetSettingBool(key, value != 0);
|
||||
}
|
||||
|
||||
void modu_ctx_set_setting_string(ScriptContext* ctx, const char* key, const char* value) {
|
||||
if (!ctx || !key) return;
|
||||
ctx->SetSetting(key, value ? value : "");
|
||||
}
|
||||
|
||||
void modu_ctx_add_console_message(ScriptContext* ctx, const char* message, int type) {
|
||||
if (!ctx || !message) return;
|
||||
ctx->AddConsoleMessage(message, static_cast<ConsoleMessageType>(type));
|
||||
}
|
||||
|
||||
ManagedNativeApi BuildManagedNativeApi() {
|
||||
ManagedNativeApi api;
|
||||
api.getObjectId = modu_ctx_get_object_id;
|
||||
api.getPosition = modu_ctx_get_position;
|
||||
api.setPosition = modu_ctx_set_position;
|
||||
api.getRotation = modu_ctx_get_rotation;
|
||||
api.setRotation = modu_ctx_set_rotation;
|
||||
api.getScale = modu_ctx_get_scale;
|
||||
api.setScale = modu_ctx_set_scale;
|
||||
api.hasRigidbody = modu_ctx_has_rigidbody;
|
||||
api.ensureRigidbody = modu_ctx_ensure_rigidbody;
|
||||
api.setRigidbodyVelocity = modu_ctx_set_rigidbody_velocity;
|
||||
api.getRigidbodyVelocity = modu_ctx_get_rigidbody_velocity;
|
||||
api.addRigidbodyForce = modu_ctx_add_rigidbody_force;
|
||||
api.addRigidbodyImpulse = modu_ctx_add_rigidbody_impulse;
|
||||
api.getSettingFloat = modu_ctx_get_setting_float;
|
||||
api.getSettingBool = modu_ctx_get_setting_bool;
|
||||
api.getSettingString = modu_ctx_get_setting_string;
|
||||
api.setSettingFloat = modu_ctx_set_setting_float;
|
||||
api.setSettingBool = modu_ctx_set_setting_bool;
|
||||
api.setSettingString = modu_ctx_set_setting_string;
|
||||
api.addConsoleMessage = modu_ctx_add_console_message;
|
||||
return api;
|
||||
}
|
||||
55
src/ManagedBindings.h
Normal file
55
src/ManagedBindings.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "ScriptRuntime.h"
|
||||
#include <cstdint>
|
||||
|
||||
extern "C" {
|
||||
int modu_ctx_get_object_id(ScriptContext* ctx);
|
||||
void modu_ctx_get_position(ScriptContext* ctx, float* x, float* y, float* z);
|
||||
void modu_ctx_set_position(ScriptContext* ctx, float x, float y, float z);
|
||||
void modu_ctx_get_rotation(ScriptContext* ctx, float* x, float* y, float* z);
|
||||
void modu_ctx_set_rotation(ScriptContext* ctx, float x, float y, float z);
|
||||
void modu_ctx_get_scale(ScriptContext* ctx, float* x, float* y, float* z);
|
||||
void modu_ctx_set_scale(ScriptContext* ctx, float x, float y, float z);
|
||||
int modu_ctx_has_rigidbody(ScriptContext* ctx);
|
||||
int modu_ctx_ensure_rigidbody(ScriptContext* ctx, int useGravity, int kinematic);
|
||||
int modu_ctx_set_rigidbody_velocity(ScriptContext* ctx, float x, float y, float z);
|
||||
int modu_ctx_get_rigidbody_velocity(ScriptContext* ctx, float* x, float* y, float* z);
|
||||
int modu_ctx_add_rigidbody_force(ScriptContext* ctx, float x, float y, float z);
|
||||
int modu_ctx_add_rigidbody_impulse(ScriptContext* ctx, float x, float y, float z);
|
||||
float modu_ctx_get_setting_float(ScriptContext* ctx, const char* key, float fallback);
|
||||
int modu_ctx_get_setting_bool(ScriptContext* ctx, const char* key, int fallback);
|
||||
void modu_ctx_get_setting_string(ScriptContext* ctx, const char* key, const char* fallback,
|
||||
char* outBuffer, int outBufferSize);
|
||||
void modu_ctx_set_setting_float(ScriptContext* ctx, const char* key, float value);
|
||||
void modu_ctx_set_setting_bool(ScriptContext* ctx, const char* key, int value);
|
||||
void modu_ctx_set_setting_string(ScriptContext* ctx, const char* key, const char* value);
|
||||
void modu_ctx_add_console_message(ScriptContext* ctx, const char* message, int type);
|
||||
}
|
||||
|
||||
struct ManagedNativeApi {
|
||||
uint32_t version = 1;
|
||||
int (*getObjectId)(ScriptContext* ctx) = nullptr;
|
||||
void (*getPosition)(ScriptContext* ctx, float* x, float* y, float* z) = nullptr;
|
||||
void (*setPosition)(ScriptContext* ctx, float x, float y, float z) = nullptr;
|
||||
void (*getRotation)(ScriptContext* ctx, float* x, float* y, float* z) = nullptr;
|
||||
void (*setRotation)(ScriptContext* ctx, float x, float y, float z) = nullptr;
|
||||
void (*getScale)(ScriptContext* ctx, float* x, float* y, float* z) = nullptr;
|
||||
void (*setScale)(ScriptContext* ctx, float x, float y, float z) = nullptr;
|
||||
int (*hasRigidbody)(ScriptContext* ctx) = nullptr;
|
||||
int (*ensureRigidbody)(ScriptContext* ctx, int useGravity, int kinematic) = nullptr;
|
||||
int (*setRigidbodyVelocity)(ScriptContext* ctx, float x, float y, float z) = nullptr;
|
||||
int (*getRigidbodyVelocity)(ScriptContext* ctx, float* x, float* y, float* z) = nullptr;
|
||||
int (*addRigidbodyForce)(ScriptContext* ctx, float x, float y, float z) = nullptr;
|
||||
int (*addRigidbodyImpulse)(ScriptContext* ctx, float x, float y, float z) = nullptr;
|
||||
float (*getSettingFloat)(ScriptContext* ctx, const char* key, float fallback) = nullptr;
|
||||
int (*getSettingBool)(ScriptContext* ctx, const char* key, int fallback) = nullptr;
|
||||
void (*getSettingString)(ScriptContext* ctx, const char* key, const char* fallback,
|
||||
char* outBuffer, int outBufferSize) = nullptr;
|
||||
void (*setSettingFloat)(ScriptContext* ctx, const char* key, float value) = nullptr;
|
||||
void (*setSettingBool)(ScriptContext* ctx, const char* key, int value) = nullptr;
|
||||
void (*setSettingString)(ScriptContext* ctx, const char* key, const char* value) = nullptr;
|
||||
void (*addConsoleMessage)(ScriptContext* ctx, const char* message, int type) = nullptr;
|
||||
};
|
||||
|
||||
ManagedNativeApi BuildManagedNativeApi();
|
||||
452
src/ManagedScriptRuntime.cpp
Normal file
452
src/ManagedScriptRuntime.cpp
Normal file
@@ -0,0 +1,452 @@
|
||||
#include "ManagedScriptRuntime.h"
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#if MODULARITY_USE_MONO
|
||||
#include <mono/jit/jit.h>
|
||||
#include <mono/metadata/assembly.h>
|
||||
#include <mono/metadata/debug-helpers.h>
|
||||
#include <mono/metadata/mono-config.h>
|
||||
#include <mono/metadata/threads.h>
|
||||
#endif
|
||||
|
||||
#if MODULARITY_USE_MONO
|
||||
namespace {
|
||||
std::string trim(std::string value) {
|
||||
auto is_space = [](unsigned char c) { return std::isspace(c) != 0; };
|
||||
while (!value.empty() && is_space(static_cast<unsigned char>(value.front()))) {
|
||||
value.erase(value.begin());
|
||||
}
|
||||
while (!value.empty() && is_space(static_cast<unsigned char>(value.back()))) {
|
||||
value.pop_back();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
std::string stripAssemblyQualifier(const std::string& typeName) {
|
||||
auto comma = typeName.find(',');
|
||||
if (comma == std::string::npos) {
|
||||
return trim(typeName);
|
||||
}
|
||||
return trim(typeName.substr(0, comma));
|
||||
}
|
||||
|
||||
bool splitNamespaceAndName(const std::string& fullName, std::string& nameSpace, std::string& name) {
|
||||
auto dot = fullName.rfind('.');
|
||||
if (dot == std::string::npos) {
|
||||
nameSpace.clear();
|
||||
name = fullName;
|
||||
return !name.empty();
|
||||
}
|
||||
nameSpace = fullName.substr(0, dot);
|
||||
name = fullName.substr(dot + 1);
|
||||
return !name.empty();
|
||||
}
|
||||
|
||||
std::string monoExceptionToString(MonoObject* exc) {
|
||||
if (!exc) return "Unknown managed exception";
|
||||
MonoString* strObj = mono_object_to_string(exc, nullptr);
|
||||
if (!strObj) return "Managed exception (no ToString)";
|
||||
char* utf8 = mono_string_to_utf8(strObj);
|
||||
std::string out = utf8 ? utf8 : "Managed exception (utf8 conversion failed)";
|
||||
mono_free(utf8);
|
||||
return out;
|
||||
}
|
||||
|
||||
fs::path resolveMonoRoot() {
|
||||
const char* env = std::getenv("MODU_MONO_ROOT");
|
||||
if (env && env[0] != '\0') {
|
||||
return fs::path(env);
|
||||
}
|
||||
|
||||
std::vector<fs::path> candidates;
|
||||
fs::path cwd = fs::current_path();
|
||||
candidates.push_back(cwd / "src" / "ThirdParty" / "mono");
|
||||
candidates.push_back(cwd / ".." / "src" / "ThirdParty" / "mono");
|
||||
candidates.push_back(cwd / "ThirdParty" / "mono");
|
||||
candidates.push_back(cwd / ".." / "ThirdParty" / "mono");
|
||||
|
||||
for (const auto& path : candidates) {
|
||||
std::error_code ec;
|
||||
if (fs::exists(path, ec)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void configureMonoFromRoot(const fs::path& root) {
|
||||
fs::path libDir = root / "lib";
|
||||
fs::path etcDir = root / "etc";
|
||||
mono_set_dirs(libDir.string().c_str(), etcDir.string().c_str());
|
||||
|
||||
fs::path assembliesDir = root / "lib" / "mono" / "4.5";
|
||||
if (fs::exists(assembliesDir)) {
|
||||
mono_set_assemblies_path(assembliesDir.string().c_str());
|
||||
}
|
||||
mono_config_parse(nullptr);
|
||||
}
|
||||
|
||||
std::string toKey(const fs::path& assemblyPath, const std::string& typeName) {
|
||||
return assemblyPath.lexically_normal().string() + "|" + typeName;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
struct ManagedScriptRuntime::MonoState {
|
||||
MonoDomain* rootDomain = nullptr;
|
||||
MonoDomain* scriptDomain = nullptr;
|
||||
fs::path monoRoot;
|
||||
};
|
||||
|
||||
ManagedScriptRuntime::~ManagedScriptRuntime() {
|
||||
unloadAll();
|
||||
monoState.reset();
|
||||
}
|
||||
|
||||
void ManagedScriptRuntime::MonoStateDeleter::operator()(MonoState* state) const {
|
||||
if (!state) return;
|
||||
if (state->rootDomain) {
|
||||
if (state->scriptDomain) {
|
||||
mono_domain_set(state->rootDomain, false);
|
||||
mono_domain_unload(state->scriptDomain);
|
||||
state->scriptDomain = nullptr;
|
||||
}
|
||||
mono_jit_cleanup(state->rootDomain);
|
||||
}
|
||||
delete state;
|
||||
}
|
||||
|
||||
bool ManagedScriptRuntime::ensureHost(const fs::path& assemblyPath) {
|
||||
(void)assemblyPath;
|
||||
if (monoState && monoState->rootDomain && monoState->scriptDomain) return true;
|
||||
|
||||
if (!monoState) {
|
||||
fs::path monoRoot = resolveMonoRoot();
|
||||
if (monoRoot.empty()) {
|
||||
lastError = "Mono root not found. Set MODU_MONO_ROOT or vendor Mono in src/ThirdParty/mono.";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* state = new MonoState();
|
||||
state->monoRoot = monoRoot;
|
||||
configureMonoFromRoot(monoRoot);
|
||||
state->rootDomain = mono_jit_init_version("Modularity", "v4.0.30319");
|
||||
if (!state->rootDomain) {
|
||||
delete state;
|
||||
lastError = "Failed to initialize Mono JIT";
|
||||
return false;
|
||||
}
|
||||
monoState.reset(state);
|
||||
|
||||
std::cerr << "[Managed] Mono root: " << monoRoot << std::endl;
|
||||
}
|
||||
|
||||
if (!monoState->scriptDomain) {
|
||||
monoState->scriptDomain = mono_domain_create_appdomain(const_cast<char*>("ModularityScripts"), nullptr);
|
||||
if (!monoState->scriptDomain) {
|
||||
lastError = "Failed to create Mono appdomain";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mono_domain_set(monoState->scriptDomain, false);
|
||||
mono_thread_attach(monoState->scriptDomain);
|
||||
return true;
|
||||
}
|
||||
|
||||
static MonoAssembly* loadAssembly(MonoDomain* domain, const fs::path& assemblyPath, MonoImage** outImage,
|
||||
std::string& error) {
|
||||
if (!outImage) {
|
||||
error = "Internal error: missing image output";
|
||||
return nullptr;
|
||||
}
|
||||
*outImage = nullptr;
|
||||
|
||||
std::error_code ec;
|
||||
if (!fs::exists(assemblyPath, ec)) {
|
||||
error = "Missing managed assembly: " + assemblyPath.string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mono_domain_set(domain, false);
|
||||
MonoAssembly* assembly = mono_domain_assembly_open(domain, assemblyPath.string().c_str());
|
||||
if (!assembly) {
|
||||
error = "Mono failed to load assembly: " + assemblyPath.string();
|
||||
return nullptr;
|
||||
}
|
||||
MonoImage* image = mono_assembly_get_image(assembly);
|
||||
if (!image) {
|
||||
error = "Mono failed to get image: " + assemblyPath.string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*outImage = image;
|
||||
return assembly;
|
||||
}
|
||||
|
||||
bool ManagedScriptRuntime::ensureApiInjected(const fs::path& assemblyPath) {
|
||||
if (apiInjected) return true;
|
||||
if (!monoState || !monoState->scriptDomain) return false;
|
||||
|
||||
std::string error;
|
||||
MonoImage* image = nullptr;
|
||||
MonoAssembly* assembly = loadAssembly(monoState->scriptDomain, assemblyPath, &image, error);
|
||||
if (!assembly || !image) {
|
||||
lastError = error;
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoClass* hostClass = mono_class_from_name(image, "ModuCPP", "Host");
|
||||
if (!hostClass) {
|
||||
lastError = "Managed class ModuCPP.Host not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoMethod* setApiMethod = mono_class_get_method_from_name(hostClass, "SetNativeApi", 1);
|
||||
if (!setApiMethod) {
|
||||
lastError = "Managed method ModuCPP.Host.SetNativeApi not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
mono_domain_set(monoState->scriptDomain, false);
|
||||
mono_thread_attach(monoState->scriptDomain);
|
||||
intptr_t apiPtr = reinterpret_cast<intptr_t>(&api);
|
||||
void* args[1] = { &apiPtr };
|
||||
MonoObject* exc = nullptr;
|
||||
mono_runtime_invoke(setApiMethod, nullptr, args, &exc);
|
||||
if (exc) {
|
||||
lastError = monoExceptionToString(exc);
|
||||
return false;
|
||||
}
|
||||
|
||||
apiInjected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ManagedScriptRuntime::loadModuleMethods(Module& mod, const fs::path& assemblyPath,
|
||||
const std::string& typeName) {
|
||||
if (!monoState || !monoState->scriptDomain || typeName.empty()) {
|
||||
lastError = "Managed script type is required";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string error;
|
||||
MonoImage* image = nullptr;
|
||||
MonoAssembly* assembly = loadAssembly(monoState->scriptDomain, assemblyPath, &image, error);
|
||||
if (!assembly || !image) {
|
||||
lastError = error;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string normalized = stripAssemblyQualifier(typeName);
|
||||
std::string nameSpace;
|
||||
std::string className;
|
||||
if (!splitNamespaceAndName(normalized, nameSpace, className)) {
|
||||
lastError = "Managed script type name is invalid";
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoClass* klass = mono_class_from_name(image, nameSpace.c_str(), className.c_str());
|
||||
if (!klass) {
|
||||
lastError = "Managed type not found: " + normalized;
|
||||
return false;
|
||||
}
|
||||
|
||||
MonoMethod* inspector = mono_class_get_method_from_name(klass, "Script_OnInspector", 1);
|
||||
MonoMethod* begin = mono_class_get_method_from_name(klass, "Script_Begin", 2);
|
||||
MonoMethod* spec = mono_class_get_method_from_name(klass, "Script_Spec", 2);
|
||||
MonoMethod* testEditor = mono_class_get_method_from_name(klass, "Script_TestEditor", 2);
|
||||
MonoMethod* update = mono_class_get_method_from_name(klass, "Script_Update", 2);
|
||||
MonoMethod* tickUpdate = mono_class_get_method_from_name(klass, "Script_TickUpdate", 2);
|
||||
|
||||
mod.inspectorMethod = inspector;
|
||||
mod.beginMethod = begin;
|
||||
mod.specMethod = spec;
|
||||
mod.testEditorMethod = testEditor;
|
||||
mod.updateMethod = update;
|
||||
mod.tickUpdateMethod = tickUpdate;
|
||||
|
||||
if (!inspector && !begin && !spec && !testEditor && !update && !tickUpdate) {
|
||||
lastError = "No managed script exports found";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ManagedScriptRuntime::Module* ManagedScriptRuntime::getModule(const fs::path& assemblyPath,
|
||||
const std::string& typeName) {
|
||||
lastError.clear();
|
||||
if (assemblyPath.empty()) {
|
||||
lastError = "Managed assembly path is empty";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string key = toKey(assemblyPath, typeName);
|
||||
auto it = modules.find(key);
|
||||
if (it != modules.end()) return &it->second;
|
||||
|
||||
if (!ensureHost(assemblyPath)) return nullptr;
|
||||
if (!ensureApiInjected(assemblyPath)) return nullptr;
|
||||
|
||||
Module mod;
|
||||
mod.assemblyPath = assemblyPath;
|
||||
mod.typeName = typeName;
|
||||
if (!loadModuleMethods(mod, assemblyPath, typeName)) return nullptr;
|
||||
|
||||
modules[key] = mod;
|
||||
return &modules[key];
|
||||
}
|
||||
|
||||
static bool invokeMonoMethod(MonoDomain* domain, MonoMethod* method, ScriptContext* ctx,
|
||||
float deltaTime, bool hasDelta, std::string& error) {
|
||||
if (!method) return false;
|
||||
mono_domain_set(domain, false);
|
||||
mono_thread_attach(domain);
|
||||
|
||||
intptr_t ctxPtr = reinterpret_cast<intptr_t>(ctx);
|
||||
void* argsWithDelta[2] = { &ctxPtr, &deltaTime };
|
||||
void* argsNoDelta[1] = { &ctxPtr };
|
||||
|
||||
MonoObject* exc = nullptr;
|
||||
mono_runtime_invoke(method, nullptr, hasDelta ? argsWithDelta : argsNoDelta, &exc);
|
||||
if (exc) {
|
||||
error = monoExceptionToString(exc);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ManagedScriptRuntime::invokeInspector(const fs::path& assemblyPath, const std::string& typeName,
|
||||
ScriptContext& ctx) {
|
||||
Module* mod = getModule(assemblyPath, typeName);
|
||||
if (!mod) return false;
|
||||
MonoMethod* inspector = reinterpret_cast<MonoMethod*>(mod->inspectorMethod);
|
||||
if (!inspector) {
|
||||
lastError.clear();
|
||||
return false;
|
||||
}
|
||||
std::string error;
|
||||
bool ok = invokeMonoMethod(monoState->scriptDomain, inspector, &ctx, 0.0f, false, error);
|
||||
if (!ok) {
|
||||
lastError = error;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool ManagedScriptRuntime::hasInspector(const fs::path& assemblyPath, const std::string& typeName) {
|
||||
Module* mod = getModule(assemblyPath, typeName);
|
||||
if (!mod) return false;
|
||||
if (!mod->inspectorMethod) {
|
||||
lastError.clear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ManagedScriptRuntime::tickModule(const fs::path& assemblyPath, const std::string& typeName,
|
||||
ScriptContext& ctx, float deltaTime,
|
||||
bool runSpec, bool runTest) {
|
||||
Module* mod = getModule(assemblyPath, typeName);
|
||||
if (!mod) return;
|
||||
|
||||
int objId = ctx.object ? ctx.object->id : -1;
|
||||
MonoMethod* begin = reinterpret_cast<MonoMethod*>(mod->beginMethod);
|
||||
if (objId >= 0 && begin && mod->beginCalledObjects.find(objId) == mod->beginCalledObjects.end()) {
|
||||
std::string error;
|
||||
if (!invokeMonoMethod(monoState->scriptDomain, begin, &ctx, deltaTime, true, error)) {
|
||||
lastError = error;
|
||||
return;
|
||||
}
|
||||
mod->beginCalledObjects.insert(objId);
|
||||
}
|
||||
|
||||
MonoMethod* tickUpdate = reinterpret_cast<MonoMethod*>(mod->tickUpdateMethod);
|
||||
MonoMethod* update = reinterpret_cast<MonoMethod*>(mod->updateMethod);
|
||||
if (tickUpdate || update) {
|
||||
std::string error;
|
||||
if (!invokeMonoMethod(monoState->scriptDomain, tickUpdate ? tickUpdate : update,
|
||||
&ctx, deltaTime, true, error)) {
|
||||
lastError = error;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (runSpec) {
|
||||
MonoMethod* spec = reinterpret_cast<MonoMethod*>(mod->specMethod);
|
||||
if (spec) {
|
||||
std::string error;
|
||||
if (!invokeMonoMethod(monoState->scriptDomain, spec, &ctx, deltaTime, true, error)) {
|
||||
lastError = error;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (runTest) {
|
||||
MonoMethod* test = reinterpret_cast<MonoMethod*>(mod->testEditorMethod);
|
||||
if (test) {
|
||||
std::string error;
|
||||
if (!invokeMonoMethod(monoState->scriptDomain, test, &ctx, deltaTime, true, error)) {
|
||||
lastError = error;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ManagedScriptRuntime::unloadAll() {
|
||||
modules.clear();
|
||||
apiInjected = false;
|
||||
lastError.clear();
|
||||
if (monoState && monoState->rootDomain && monoState->scriptDomain) {
|
||||
mono_domain_set(monoState->rootDomain, false);
|
||||
mono_domain_unload(monoState->scriptDomain);
|
||||
monoState->scriptDomain = nullptr;
|
||||
}
|
||||
}
|
||||
#else
|
||||
ManagedScriptRuntime::~ManagedScriptRuntime() = default;
|
||||
|
||||
struct ManagedScriptRuntime::MonoState {};
|
||||
|
||||
void ManagedScriptRuntime::MonoStateDeleter::operator()(MonoState* state) const {
|
||||
delete state;
|
||||
}
|
||||
|
||||
bool ManagedScriptRuntime::hasInspector(const fs::path& assemblyPath, const std::string& typeName) {
|
||||
(void)assemblyPath;
|
||||
(void)typeName;
|
||||
lastError = "Managed scripts disabled (Mono not built).";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ManagedScriptRuntime::invokeInspector(const fs::path& assemblyPath, const std::string& typeName, ScriptContext& ctx) {
|
||||
(void)assemblyPath;
|
||||
(void)typeName;
|
||||
(void)ctx;
|
||||
lastError = "Managed scripts disabled (Mono not built).";
|
||||
return false;
|
||||
}
|
||||
|
||||
void ManagedScriptRuntime::tickModule(const fs::path& assemblyPath, const std::string& typeName,
|
||||
ScriptContext& ctx, float deltaTime, bool runSpec, bool runTest) {
|
||||
(void)assemblyPath;
|
||||
(void)typeName;
|
||||
(void)ctx;
|
||||
(void)deltaTime;
|
||||
(void)runSpec;
|
||||
(void)runTest;
|
||||
lastError = "Managed scripts disabled (Mono not built).";
|
||||
}
|
||||
|
||||
void ManagedScriptRuntime::unloadAll() {
|
||||
modules.clear();
|
||||
monoState.reset();
|
||||
apiInjected = false;
|
||||
lastError.clear();
|
||||
}
|
||||
#endif
|
||||
50
src/ManagedScriptRuntime.h
Normal file
50
src/ManagedScriptRuntime.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "ManagedBindings.h"
|
||||
#include "ScriptRuntime.h"
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
class ManagedScriptRuntime {
|
||||
public:
|
||||
~ManagedScriptRuntime();
|
||||
|
||||
bool hasInspector(const fs::path& assemblyPath, const std::string& typeName);
|
||||
bool invokeInspector(const fs::path& assemblyPath, const std::string& typeName, ScriptContext& ctx);
|
||||
void tickModule(const fs::path& assemblyPath, const std::string& typeName,
|
||||
ScriptContext& ctx, float deltaTime, bool runSpec, bool runTest);
|
||||
void unloadAll();
|
||||
const std::string& getLastError() const { return lastError; }
|
||||
|
||||
struct Module {
|
||||
fs::path assemblyPath;
|
||||
std::string typeName;
|
||||
void* inspectorMethod = nullptr;
|
||||
void* beginMethod = nullptr;
|
||||
void* specMethod = nullptr;
|
||||
void* testEditorMethod = nullptr;
|
||||
void* updateMethod = nullptr;
|
||||
void* tickUpdateMethod = nullptr;
|
||||
std::unordered_set<int> beginCalledObjects;
|
||||
};
|
||||
|
||||
struct MonoState;
|
||||
struct MonoStateDeleter {
|
||||
void operator()(MonoState* state) const;
|
||||
};
|
||||
|
||||
private:
|
||||
Module* getModule(const fs::path& assemblyPath, const std::string& typeName);
|
||||
bool ensureHost(const fs::path& assemblyPath);
|
||||
bool ensureApiInjected(const fs::path& assemblyPath);
|
||||
bool loadModuleMethods(Module& mod, const fs::path& assemblyPath, const std::string& typeName);
|
||||
|
||||
std::unordered_map<std::string, Module> modules;
|
||||
std::string lastError;
|
||||
std::unique_ptr<MonoState, MonoStateDeleter> monoState;
|
||||
bool apiInjected = false;
|
||||
ManagedNativeApi api = BuildManagedNativeApi();
|
||||
};
|
||||
@@ -4,6 +4,10 @@
|
||||
#include <fstream>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <unordered_set>
|
||||
#include <functional>
|
||||
#include <assimp/material.h>
|
||||
#include "ThirdParty/glm/gtc/quaternion.hpp"
|
||||
|
||||
ModelLoader& ModelLoader::getInstance() {
|
||||
static ModelLoader instance;
|
||||
@@ -11,6 +15,13 @@ ModelLoader& ModelLoader::getInstance() {
|
||||
}
|
||||
|
||||
static void collectRawMeshData(aiNode* node, const aiScene* scene, const aiMatrix4x4& parentTransform, RawMeshAsset& out);
|
||||
static bool buildSceneMeshes(const std::string& filepath, const aiScene* scene,
|
||||
std::vector<OBJLoader::LoadedMesh>& loadedMeshes,
|
||||
ModelSceneData& out, std::string& errorMsg);
|
||||
static void buildSceneNodes(const aiScene* scene,
|
||||
const std::vector<int>& meshIndices,
|
||||
ModelSceneData& out);
|
||||
static glm::mat4 aiToGlm(const aiMatrix4x4& m);
|
||||
|
||||
ModelLoader& getModelLoader() {
|
||||
return ModelLoader::getInstance();
|
||||
@@ -91,6 +102,7 @@ ModelLoadResult ModelLoader::loadModel(const std::string& filepath) {
|
||||
if (loadedMeshes[i].path == filepath) {
|
||||
result.success = true;
|
||||
result.meshIndex = static_cast<int>(i);
|
||||
result.meshIndices.push_back(result.meshIndex);
|
||||
const auto& mesh = loadedMeshes[i];
|
||||
result.vertexCount = mesh.vertexCount;
|
||||
result.faceCount = mesh.faceCount;
|
||||
@@ -273,6 +285,92 @@ ModelLoadResult ModelLoader::loadModel(const std::string& filepath) {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ModelLoader::loadModelScene(const std::string& filepath, ModelSceneData& out, std::string& errorMsg) {
|
||||
out = ModelSceneData();
|
||||
|
||||
if (!isSupported(filepath)) {
|
||||
errorMsg = "Unsupported file format: " + fs::path(filepath).extension().string();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cached = cachedScenes.find(filepath);
|
||||
if (cached != cachedScenes.end()) {
|
||||
out = cached->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int importFlags =
|
||||
aiProcess_Triangulate |
|
||||
aiProcess_GenSmoothNormals |
|
||||
aiProcess_FlipUVs |
|
||||
aiProcess_CalcTangentSpace |
|
||||
aiProcess_JoinIdenticalVertices |
|
||||
aiProcess_SortByPType |
|
||||
aiProcess_ValidateDataStructure;
|
||||
|
||||
const aiScene* scene = importer.ReadFile(filepath, importFlags);
|
||||
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
|
||||
errorMsg = "Assimp error: " + std::string(importer.GetErrorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!buildSceneMeshes(filepath, scene, loadedMeshes, out, errorMsg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
buildSceneNodes(scene, out.meshIndices, out);
|
||||
|
||||
out.animations.clear();
|
||||
if (scene->mNumAnimations > 0) {
|
||||
out.animations.reserve(scene->mNumAnimations);
|
||||
for (unsigned int i = 0; i < scene->mNumAnimations; ++i) {
|
||||
aiAnimation* anim = scene->mAnimations[i];
|
||||
ModelSceneData::AnimationClip clip;
|
||||
clip.name = anim->mName.C_Str();
|
||||
if (clip.name.empty()) {
|
||||
clip.name = "Clip_" + std::to_string(i);
|
||||
}
|
||||
clip.duration = anim->mDuration;
|
||||
clip.ticksPerSecond = anim->mTicksPerSecond != 0.0 ? anim->mTicksPerSecond : 25.0;
|
||||
clip.channels.reserve(anim->mNumChannels);
|
||||
for (unsigned int c = 0; c < anim->mNumChannels; ++c) {
|
||||
aiNodeAnim* ch = anim->mChannels[c];
|
||||
ModelSceneData::AnimChannel channel;
|
||||
channel.nodeName = ch->mNodeName.C_Str();
|
||||
channel.positions.reserve(ch->mNumPositionKeys);
|
||||
for (unsigned int k = 0; k < ch->mNumPositionKeys; ++k) {
|
||||
const auto& key = ch->mPositionKeys[k];
|
||||
ModelSceneData::AnimVecKey vk;
|
||||
vk.time = static_cast<float>(key.mTime);
|
||||
vk.value = glm::vec3(key.mValue.x, key.mValue.y, key.mValue.z);
|
||||
channel.positions.push_back(vk);
|
||||
}
|
||||
channel.rotations.reserve(ch->mNumRotationKeys);
|
||||
for (unsigned int k = 0; k < ch->mNumRotationKeys; ++k) {
|
||||
const auto& key = ch->mRotationKeys[k];
|
||||
ModelSceneData::AnimQuatKey qk;
|
||||
qk.time = static_cast<float>(key.mTime);
|
||||
qk.value = glm::quat(key.mValue.w, key.mValue.x, key.mValue.y, key.mValue.z);
|
||||
channel.rotations.push_back(qk);
|
||||
}
|
||||
channel.scales.reserve(ch->mNumScalingKeys);
|
||||
for (unsigned int k = 0; k < ch->mNumScalingKeys; ++k) {
|
||||
const auto& key = ch->mScalingKeys[k];
|
||||
ModelSceneData::AnimVecKey sk;
|
||||
sk.time = static_cast<float>(key.mTime);
|
||||
sk.value = glm::vec3(key.mValue.x, key.mValue.y, key.mValue.z);
|
||||
channel.scales.push_back(sk);
|
||||
}
|
||||
clip.channels.push_back(std::move(channel));
|
||||
}
|
||||
out.animations.push_back(std::move(clip));
|
||||
}
|
||||
}
|
||||
|
||||
cachedScenes[filepath] = out;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModelLoader::exportRawMesh(const std::string& inputFile, const std::string& outputFile, std::string& errorMsg) {
|
||||
fs::path inPath(inputFile);
|
||||
if (!fs::exists(inPath)) {
|
||||
@@ -350,17 +448,51 @@ bool ModelLoader::loadRawMesh(const std::string& filepath, RawMeshAsset& out, st
|
||||
return false;
|
||||
}
|
||||
|
||||
in.seekg(0, std::ios::end);
|
||||
std::streamoff fileSize = in.tellg();
|
||||
in.seekg(sizeof(header), std::ios::beg);
|
||||
|
||||
in.read(reinterpret_cast<char*>(&out.boundsMin.x), sizeof(float) * 3);
|
||||
in.read(reinterpret_cast<char*>(&out.boundsMax.x), sizeof(float) * 3);
|
||||
|
||||
const std::streamoff payloadSize = fileSize - sizeof(header) - sizeof(float) * 6;
|
||||
const std::streamoff positionsSize = static_cast<std::streamoff>(sizeof(glm::vec3)) * header.vertexCount;
|
||||
const std::streamoff normalsSize = static_cast<std::streamoff>(sizeof(glm::vec3)) * header.vertexCount;
|
||||
const std::streamoff uvsSize = static_cast<std::streamoff>(sizeof(glm::vec2)) * header.vertexCount;
|
||||
const std::streamoff facesSize = static_cast<std::streamoff>(sizeof(glm::u32vec3)) * header.faceCount;
|
||||
|
||||
bool hasNormals = false;
|
||||
bool hasUVs = false;
|
||||
if (payloadSize == positionsSize + normalsSize + uvsSize + facesSize) {
|
||||
hasNormals = true;
|
||||
hasUVs = true;
|
||||
} else if (payloadSize == positionsSize + normalsSize + facesSize) {
|
||||
hasNormals = true;
|
||||
} else if (payloadSize == positionsSize + uvsSize + facesSize) {
|
||||
hasUVs = true;
|
||||
} else if (payloadSize == positionsSize + facesSize) {
|
||||
// legacy raw meshes without normals/uvs
|
||||
} else if (payloadSize < positionsSize + facesSize) {
|
||||
errorMsg = "Raw mesh data is truncated";
|
||||
return false;
|
||||
}
|
||||
|
||||
out.positions.resize(header.vertexCount);
|
||||
out.normals.resize(header.vertexCount);
|
||||
out.uvs.resize(header.vertexCount);
|
||||
out.faces.resize(header.faceCount);
|
||||
|
||||
in.read(reinterpret_cast<char*>(out.positions.data()), sizeof(glm::vec3) * out.positions.size());
|
||||
in.read(reinterpret_cast<char*>(out.normals.data()), sizeof(glm::vec3) * out.normals.size());
|
||||
in.read(reinterpret_cast<char*>(out.uvs.data()), sizeof(glm::vec2) * out.uvs.size());
|
||||
if (hasNormals) {
|
||||
out.normals.resize(header.vertexCount);
|
||||
in.read(reinterpret_cast<char*>(out.normals.data()), sizeof(glm::vec3) * out.normals.size());
|
||||
} else {
|
||||
out.normals.assign(header.vertexCount, glm::vec3(0.0f));
|
||||
}
|
||||
if (hasUVs) {
|
||||
out.uvs.resize(header.vertexCount);
|
||||
in.read(reinterpret_cast<char*>(out.uvs.data()), sizeof(glm::vec2) * out.uvs.size());
|
||||
} else {
|
||||
out.uvs.assign(header.vertexCount, glm::vec2(0.0f));
|
||||
}
|
||||
in.read(reinterpret_cast<char*>(out.faces.data()), sizeof(glm::u32vec3) * out.faces.size());
|
||||
|
||||
if (!in.good()) {
|
||||
@@ -435,6 +567,18 @@ bool ModelLoader::saveRawMesh(const RawMeshAsset& asset, const std::string& file
|
||||
outPath.replace_extension(".rmesh");
|
||||
}
|
||||
|
||||
std::vector<glm::vec3> normalsData;
|
||||
normalsData.resize(asset.positions.size(), glm::vec3(0.0f));
|
||||
if (asset.normals.size() == asset.positions.size()) {
|
||||
normalsData = asset.normals;
|
||||
}
|
||||
|
||||
std::vector<glm::vec2> uvsData;
|
||||
uvsData.resize(asset.positions.size(), glm::vec2(0.0f));
|
||||
if (asset.uvs.size() == asset.positions.size()) {
|
||||
uvsData = asset.uvs;
|
||||
}
|
||||
|
||||
struct Header {
|
||||
char magic[6] = {'R','M','E','S','H','\0'};
|
||||
uint32_t version = 1;
|
||||
@@ -455,8 +599,8 @@ bool ModelLoader::saveRawMesh(const RawMeshAsset& asset, const std::string& file
|
||||
out.write(reinterpret_cast<const char*>(&asset.boundsMin.x), sizeof(float) * 3);
|
||||
out.write(reinterpret_cast<const char*>(&asset.boundsMax.x), sizeof(float) * 3);
|
||||
out.write(reinterpret_cast<const char*>(asset.positions.data()), sizeof(glm::vec3) * asset.positions.size());
|
||||
out.write(reinterpret_cast<const char*>(asset.normals.data()), sizeof(glm::vec3) * asset.normals.size());
|
||||
out.write(reinterpret_cast<const char*>(asset.uvs.data()), sizeof(glm::vec2) * asset.uvs.size());
|
||||
out.write(reinterpret_cast<const char*>(normalsData.data()), sizeof(glm::vec3) * normalsData.size());
|
||||
out.write(reinterpret_cast<const char*>(uvsData.data()), sizeof(glm::vec2) * uvsData.size());
|
||||
out.write(reinterpret_cast<const char*>(asset.faces.data()), sizeof(glm::u32vec3) * asset.faces.size());
|
||||
|
||||
if (!out.good()) {
|
||||
@@ -544,6 +688,84 @@ bool ModelLoader::updateRawMesh(int meshIndex, const RawMeshAsset& asset, std::s
|
||||
return true;
|
||||
}
|
||||
|
||||
int ModelLoader::addRawMesh(const RawMeshAsset& asset, const std::string& sourcePath,
|
||||
const std::string& name, std::string& errorMsg) {
|
||||
if (asset.positions.empty() || asset.faces.empty()) {
|
||||
errorMsg = "Raw mesh is empty";
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<float> vertices;
|
||||
vertices.reserve(asset.faces.size() * 3 * 8);
|
||||
std::vector<glm::vec3> triPositions;
|
||||
triPositions.reserve(asset.faces.size() * 3);
|
||||
|
||||
auto getPos = [&](uint32_t idx) -> const glm::vec3& { return asset.positions[idx]; };
|
||||
auto getNorm = [&](uint32_t idx) -> glm::vec3 {
|
||||
if (idx < asset.normals.size()) return asset.normals[idx];
|
||||
return glm::vec3(0.0f);
|
||||
};
|
||||
auto getUV = [&](uint32_t idx) -> glm::vec2 {
|
||||
if (idx < asset.uvs.size()) return asset.uvs[idx];
|
||||
return glm::vec2(0.0f);
|
||||
};
|
||||
|
||||
for (const auto& face : asset.faces) {
|
||||
const uint32_t idx[3] = { face.x, face.y, face.z };
|
||||
glm::vec3 faceNormal(0.0f);
|
||||
if (!asset.hasNormals) {
|
||||
const glm::vec3& a = getPos(idx[0]);
|
||||
const glm::vec3& b = getPos(idx[1]);
|
||||
const glm::vec3& c = getPos(idx[2]);
|
||||
faceNormal = glm::normalize(glm::cross(b - a, c - a));
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
glm::vec3 pos = getPos(idx[i]);
|
||||
glm::vec3 n = asset.hasNormals ? getNorm(idx[i]) : faceNormal;
|
||||
glm::vec2 uv = asset.hasUVs ? getUV(idx[i]) : glm::vec2(0.0f);
|
||||
|
||||
triPositions.push_back(pos);
|
||||
vertices.push_back(pos.x);
|
||||
vertices.push_back(pos.y);
|
||||
vertices.push_back(pos.z);
|
||||
vertices.push_back(n.x);
|
||||
vertices.push_back(n.y);
|
||||
vertices.push_back(n.z);
|
||||
vertices.push_back(uv.x);
|
||||
vertices.push_back(uv.y);
|
||||
}
|
||||
}
|
||||
|
||||
if (vertices.empty()) {
|
||||
errorMsg = "No vertices generated for GPU upload";
|
||||
return -1;
|
||||
}
|
||||
|
||||
OBJLoader::LoadedMesh loaded;
|
||||
loaded.path = sourcePath;
|
||||
loaded.name = name.empty() ? "StaticBatch" : name;
|
||||
loaded.mesh = std::make_unique<Mesh>(vertices.data(), vertices.size() * sizeof(float));
|
||||
loaded.vertexCount = static_cast<int>(vertices.size() / 8);
|
||||
loaded.faceCount = static_cast<int>(asset.faces.size());
|
||||
loaded.hasNormals = asset.hasNormals;
|
||||
loaded.hasTexCoords = asset.hasUVs;
|
||||
loaded.boundsMin = asset.boundsMin;
|
||||
loaded.boundsMax = asset.boundsMax;
|
||||
loaded.triangleVertices = std::move(triPositions);
|
||||
loaded.positions = asset.positions;
|
||||
loaded.triangleIndices.clear();
|
||||
loaded.triangleIndices.reserve(asset.faces.size() * 3);
|
||||
for (const auto& face : asset.faces) {
|
||||
loaded.triangleIndices.push_back(face.x);
|
||||
loaded.triangleIndices.push_back(face.y);
|
||||
loaded.triangleIndices.push_back(face.z);
|
||||
}
|
||||
|
||||
int newIndex = static_cast<int>(loadedMeshes.size());
|
||||
loadedMeshes.push_back(std::move(loaded));
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
static glm::mat4 aiToGlm(const aiMatrix4x4& m) {
|
||||
return glm::mat4(
|
||||
m.a1, m.b1, m.c1, m.d1,
|
||||
@@ -553,6 +775,303 @@ static glm::mat4 aiToGlm(const aiMatrix4x4& m) {
|
||||
);
|
||||
}
|
||||
|
||||
static glm::vec3 quatToEulerDegrees(const aiQuaternion& q) {
|
||||
glm::quat gq(q.w, q.x, q.y, q.z);
|
||||
glm::vec3 euler = glm::degrees(glm::eulerAngles(gq));
|
||||
return euler;
|
||||
}
|
||||
|
||||
static bool buildSceneMeshes(const std::string& filepath, const aiScene* scene,
|
||||
std::vector<OBJLoader::LoadedMesh>& loadedMeshes,
|
||||
ModelSceneData& out, std::string& errorMsg) {
|
||||
out.meshIndices.assign(scene->mNumMeshes, -1);
|
||||
out.meshMaterialIndices.assign(scene->mNumMeshes, -1);
|
||||
|
||||
out.materials.clear();
|
||||
out.materials.reserve(scene->mNumMaterials);
|
||||
for (unsigned int i = 0; i < scene->mNumMaterials; ++i) {
|
||||
aiMaterial* mat = scene->mMaterials[i];
|
||||
ModelMaterialInfo info;
|
||||
info.name = mat->GetName().C_Str();
|
||||
if (info.name.empty()) {
|
||||
info.name = "Material_" + std::to_string(i);
|
||||
}
|
||||
|
||||
aiColor3D diffuse(1.0f, 1.0f, 1.0f);
|
||||
if (AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse)) {
|
||||
info.props.color = glm::vec3(diffuse.r, diffuse.g, diffuse.b);
|
||||
}
|
||||
|
||||
aiColor3D specular(0.0f, 0.0f, 0.0f);
|
||||
if (AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_SPECULAR, specular)) {
|
||||
float avg = (specular.r + specular.g + specular.b) / 3.0f;
|
||||
info.props.specularStrength = avg;
|
||||
}
|
||||
|
||||
float shininess = info.props.shininess;
|
||||
if (AI_SUCCESS == mat->Get(AI_MATKEY_SHININESS, shininess)) {
|
||||
info.props.shininess = shininess;
|
||||
}
|
||||
|
||||
aiString tex;
|
||||
if (AI_SUCCESS == mat->GetTexture(aiTextureType_DIFFUSE, 0, &tex)) {
|
||||
info.albedoPath = tex.C_Str();
|
||||
}
|
||||
if (AI_SUCCESS == mat->GetTexture(aiTextureType_NORMALS, 0, &tex)) {
|
||||
info.normalPath = tex.C_Str();
|
||||
} else if (AI_SUCCESS == mat->GetTexture(aiTextureType_HEIGHT, 0, &tex)) {
|
||||
info.normalPath = tex.C_Str();
|
||||
}
|
||||
|
||||
if (!info.albedoPath.empty()) {
|
||||
info.props.textureMix = 1.0f;
|
||||
}
|
||||
|
||||
out.materials.push_back(info);
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < scene->mNumMeshes; ++i) {
|
||||
aiMesh* mesh = scene->mMeshes[i];
|
||||
if (!mesh || mesh->mNumVertices == 0 || mesh->mNumFaces == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<float> vertices;
|
||||
struct BoneVertex {
|
||||
int ids[4];
|
||||
float weights[4];
|
||||
};
|
||||
std::vector<BoneVertex> boneVertices;
|
||||
std::vector<glm::ivec4> vertexBoneIds(mesh->mNumVertices, glm::ivec4(0));
|
||||
std::vector<glm::vec4> vertexBoneWeights(mesh->mNumVertices, glm::vec4(0.0f));
|
||||
std::vector<glm::vec3> triPositions;
|
||||
std::vector<glm::vec3> positions;
|
||||
std::vector<uint32_t> triangleIndices;
|
||||
vertices.reserve(mesh->mNumFaces * 3 * 8);
|
||||
boneVertices.reserve(mesh->mNumFaces * 3);
|
||||
triPositions.reserve(mesh->mNumFaces * 3);
|
||||
positions.reserve(mesh->mNumVertices);
|
||||
triangleIndices.reserve(mesh->mNumFaces * 3);
|
||||
|
||||
glm::vec3 boundsMin(FLT_MAX);
|
||||
glm::vec3 boundsMax(-FLT_MAX);
|
||||
|
||||
std::vector<std::string> boneNames;
|
||||
std::vector<glm::mat4> inverseBindMatrices;
|
||||
if (mesh->mNumBones > 0) {
|
||||
boneNames.reserve(mesh->mNumBones);
|
||||
inverseBindMatrices.reserve(mesh->mNumBones);
|
||||
for (unsigned int b = 0; b < mesh->mNumBones; ++b) {
|
||||
aiBone* bone = mesh->mBones[b];
|
||||
int boneIndex = static_cast<int>(boneNames.size());
|
||||
boneNames.push_back(bone->mName.C_Str());
|
||||
inverseBindMatrices.push_back(aiToGlm(bone->mOffsetMatrix));
|
||||
|
||||
for (unsigned int w = 0; w < bone->mNumWeights; ++w) {
|
||||
unsigned int vId = bone->mWeights[w].mVertexId;
|
||||
float weight = bone->mWeights[w].mWeight;
|
||||
if (vId >= vertexBoneWeights.size()) continue;
|
||||
|
||||
glm::vec4& weights = vertexBoneWeights[vId];
|
||||
glm::ivec4& ids = vertexBoneIds[vId];
|
||||
int replaceIndex = -1;
|
||||
float minWeight = weight;
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
if (weights[k] == 0.0f) {
|
||||
replaceIndex = k;
|
||||
break;
|
||||
}
|
||||
if (weights[k] < minWeight) {
|
||||
minWeight = weights[k];
|
||||
replaceIndex = k;
|
||||
}
|
||||
}
|
||||
if (replaceIndex >= 0) {
|
||||
weights[replaceIndex] = weight;
|
||||
ids[replaceIndex] = boneIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int v = 0; v < mesh->mNumVertices; ++v) {
|
||||
glm::vec3 pos(mesh->mVertices[v].x, mesh->mVertices[v].y, mesh->mVertices[v].z);
|
||||
positions.push_back(pos);
|
||||
boundsMin.x = std::min(boundsMin.x, pos.x);
|
||||
boundsMin.y = std::min(boundsMin.y, pos.y);
|
||||
boundsMin.z = std::min(boundsMin.z, pos.z);
|
||||
boundsMax.x = std::max(boundsMax.x, pos.x);
|
||||
boundsMax.y = std::max(boundsMax.y, pos.y);
|
||||
boundsMax.z = std::max(boundsMax.z, pos.z);
|
||||
}
|
||||
|
||||
for (unsigned int f = 0; f < mesh->mNumFaces; ++f) {
|
||||
const aiFace& face = mesh->mFaces[f];
|
||||
if (face.mNumIndices != 3) continue;
|
||||
|
||||
triangleIndices.push_back(static_cast<uint32_t>(face.mIndices[0]));
|
||||
triangleIndices.push_back(static_cast<uint32_t>(face.mIndices[1]));
|
||||
triangleIndices.push_back(static_cast<uint32_t>(face.mIndices[2]));
|
||||
|
||||
for (unsigned int j = 0; j < 3; ++j) {
|
||||
unsigned int index = face.mIndices[j];
|
||||
glm::vec3 pos(mesh->mVertices[index].x,
|
||||
mesh->mVertices[index].y,
|
||||
mesh->mVertices[index].z);
|
||||
vertices.push_back(pos.x);
|
||||
vertices.push_back(pos.y);
|
||||
vertices.push_back(pos.z);
|
||||
triPositions.push_back(pos);
|
||||
|
||||
if (mesh->mNormals) {
|
||||
glm::vec3 n(mesh->mNormals[index].x,
|
||||
mesh->mNormals[index].y,
|
||||
mesh->mNormals[index].z);
|
||||
vertices.push_back(n.x);
|
||||
vertices.push_back(n.y);
|
||||
vertices.push_back(n.z);
|
||||
} else {
|
||||
vertices.push_back(0.0f);
|
||||
vertices.push_back(1.0f);
|
||||
vertices.push_back(0.0f);
|
||||
}
|
||||
|
||||
if (mesh->mTextureCoords[0]) {
|
||||
vertices.push_back(mesh->mTextureCoords[0][index].x);
|
||||
vertices.push_back(mesh->mTextureCoords[0][index].y);
|
||||
} else {
|
||||
vertices.push_back(0.0f);
|
||||
vertices.push_back(0.0f);
|
||||
}
|
||||
|
||||
BoneVertex bv{};
|
||||
glm::ivec4 ids = vertexBoneIds[index];
|
||||
glm::vec4 weights = vertexBoneWeights[index];
|
||||
float weightSum = weights.x + weights.y + weights.z + weights.w;
|
||||
if (weightSum > 0.0f) {
|
||||
weights /= weightSum;
|
||||
}
|
||||
bv.ids[0] = ids.x;
|
||||
bv.ids[1] = ids.y;
|
||||
bv.ids[2] = ids.z;
|
||||
bv.ids[3] = ids.w;
|
||||
bv.weights[0] = weights.x;
|
||||
bv.weights[1] = weights.y;
|
||||
bv.weights[2] = weights.z;
|
||||
bv.weights[3] = weights.w;
|
||||
boneVertices.push_back(bv);
|
||||
}
|
||||
}
|
||||
|
||||
if (vertices.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
OBJLoader::LoadedMesh loaded;
|
||||
loaded.path = filepath;
|
||||
loaded.name = mesh->mName.C_Str();
|
||||
if (loaded.name.empty()) {
|
||||
loaded.name = fs::path(filepath).stem().string() + "_mesh" + std::to_string(i);
|
||||
}
|
||||
bool isSkinned = mesh->mNumBones > 0 && boneVertices.size() == vertices.size() / 8;
|
||||
if (isSkinned) {
|
||||
loaded.mesh = std::make_unique<Mesh>(vertices.data(), vertices.size() * sizeof(float), true,
|
||||
boneVertices.data(), boneVertices.size() * sizeof(BoneVertex));
|
||||
} else {
|
||||
loaded.mesh = std::make_unique<Mesh>(vertices.data(), vertices.size() * sizeof(float));
|
||||
}
|
||||
loaded.vertexCount = static_cast<int>(vertices.size() / 8);
|
||||
loaded.faceCount = static_cast<int>(mesh->mNumFaces);
|
||||
loaded.hasNormals = mesh->mNormals != nullptr;
|
||||
loaded.hasTexCoords = mesh->mTextureCoords[0] != nullptr;
|
||||
loaded.boundsMin = boundsMin;
|
||||
loaded.boundsMax = boundsMax;
|
||||
loaded.triangleVertices = std::move(triPositions);
|
||||
loaded.positions = std::move(positions);
|
||||
loaded.triangleIndices = std::move(triangleIndices);
|
||||
loaded.isSkinned = isSkinned;
|
||||
loaded.boneNames = std::move(boneNames);
|
||||
loaded.inverseBindMatrices = std::move(inverseBindMatrices);
|
||||
if (isSkinned) {
|
||||
loaded.boneIds.reserve(boneVertices.size());
|
||||
loaded.boneWeights.reserve(boneVertices.size());
|
||||
for (const auto& bv : boneVertices) {
|
||||
loaded.boneIds.emplace_back(bv.ids[0], bv.ids[1], bv.ids[2], bv.ids[3]);
|
||||
loaded.boneWeights.emplace_back(bv.weights[0], bv.weights[1], bv.weights[2], bv.weights[3]);
|
||||
}
|
||||
loaded.baseVertices = vertices;
|
||||
}
|
||||
|
||||
out.meshMaterialIndices[i] = mesh->mMaterialIndex < (int)out.materials.size()
|
||||
? static_cast<int>(mesh->mMaterialIndex)
|
||||
: -1;
|
||||
|
||||
out.meshIndices[i] = static_cast<int>(loadedMeshes.size());
|
||||
loadedMeshes.push_back(std::move(loaded));
|
||||
}
|
||||
|
||||
bool anyMesh = false;
|
||||
for (int idx : out.meshIndices) {
|
||||
if (idx >= 0) { anyMesh = true; break; }
|
||||
}
|
||||
if (!anyMesh) {
|
||||
errorMsg = "No meshes found in model file";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void buildSceneNodes(const aiScene* scene,
|
||||
const std::vector<int>& meshIndices,
|
||||
ModelSceneData& out) {
|
||||
std::unordered_set<std::string> boneNames;
|
||||
for (unsigned int i = 0; i < scene->mNumMeshes; ++i) {
|
||||
aiMesh* mesh = scene->mMeshes[i];
|
||||
for (unsigned int b = 0; b < mesh->mNumBones; ++b) {
|
||||
boneNames.insert(mesh->mBones[b]->mName.C_Str());
|
||||
}
|
||||
}
|
||||
|
||||
std::function<void(aiNode*, int)> walk = [&](aiNode* node, int parentIndex) {
|
||||
ModelNodeInfo info;
|
||||
info.name = node->mName.C_Str();
|
||||
if (info.name.empty()) {
|
||||
info.name = "Node_" + std::to_string(out.nodes.size());
|
||||
}
|
||||
info.parentIndex = parentIndex;
|
||||
info.isBone = boneNames.find(info.name) != boneNames.end();
|
||||
|
||||
aiVector3D scaling(1.0f, 1.0f, 1.0f);
|
||||
aiVector3D position(0.0f, 0.0f, 0.0f);
|
||||
aiQuaternion rotation;
|
||||
node->mTransformation.Decompose(scaling, rotation, position);
|
||||
|
||||
info.localPosition = glm::vec3(position.x, position.y, position.z);
|
||||
info.localScale = glm::vec3(scaling.x, scaling.y, scaling.z);
|
||||
info.localRotation = quatToEulerDegrees(rotation);
|
||||
|
||||
for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
|
||||
unsigned int meshIndex = node->mMeshes[i];
|
||||
if (meshIndex < meshIndices.size()) {
|
||||
info.meshIndices.push_back(static_cast<int>(meshIndex));
|
||||
}
|
||||
}
|
||||
|
||||
int thisIndex = static_cast<int>(out.nodes.size());
|
||||
out.nodes.push_back(info);
|
||||
|
||||
for (unsigned int c = 0; c < node->mNumChildren; ++c) {
|
||||
walk(node->mChildren[c], thisIndex);
|
||||
}
|
||||
};
|
||||
|
||||
out.nodes.clear();
|
||||
if (scene->mRootNode) {
|
||||
walk(scene->mRootNode, -1);
|
||||
}
|
||||
}
|
||||
|
||||
static void collectRawMeshData(aiNode* node, const aiScene* scene, const aiMatrix4x4& parentTransform, RawMeshAsset& out) {
|
||||
aiMatrix4x4 current = parentTransform * node->mTransformation;
|
||||
glm::mat4 gTransform = aiToGlm(current);
|
||||
@@ -608,6 +1127,89 @@ static void collectRawMeshData(aiNode* node, const aiScene* scene, const aiMatri
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelLoader::buildRawMeshFromScene(const std::string& filepath, RawMeshAsset& out, std::string& errorMsg,
|
||||
glm::vec3* outRootPos, glm::vec3* outRootRot, glm::vec3* outRootScale) {
|
||||
out = RawMeshAsset();
|
||||
|
||||
fs::path inPath(filepath);
|
||||
if (!fs::exists(inPath)) {
|
||||
errorMsg = "File not found: " + filepath;
|
||||
return false;
|
||||
}
|
||||
if (!isSupported(filepath)) {
|
||||
errorMsg = "Unsupported file format for raw mesh build";
|
||||
return false;
|
||||
}
|
||||
|
||||
Assimp::Importer localImporter;
|
||||
unsigned int importFlags =
|
||||
aiProcess_Triangulate |
|
||||
aiProcess_JoinIdenticalVertices |
|
||||
aiProcess_GenSmoothNormals |
|
||||
aiProcess_FlipUVs;
|
||||
|
||||
const aiScene* scene = localImporter.ReadFile(filepath, importFlags);
|
||||
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
|
||||
errorMsg = "Assimp error: " + std::string(localImporter.GetErrorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
aiMatrix4x4 parent;
|
||||
parent = aiMatrix4x4();
|
||||
if (scene->mRootNode && (outRootPos || outRootRot || outRootScale)) {
|
||||
aiVector3D scaling(1.0f, 1.0f, 1.0f);
|
||||
aiVector3D position(0.0f, 0.0f, 0.0f);
|
||||
aiQuaternion rotation;
|
||||
scene->mRootNode->mTransformation.Decompose(scaling, rotation, position);
|
||||
if (outRootPos) *outRootPos = glm::vec3(position.x, position.y, position.z);
|
||||
if (outRootScale) *outRootScale = glm::vec3(scaling.x, scaling.y, scaling.z);
|
||||
if (outRootRot) *outRootRot = quatToEulerDegrees(rotation);
|
||||
|
||||
aiMatrix4x4 rootTransform = scene->mRootNode->mTransformation;
|
||||
rootTransform.Inverse();
|
||||
parent = rootTransform;
|
||||
}
|
||||
|
||||
collectRawMeshData(scene->mRootNode, scene, parent, out);
|
||||
|
||||
if (out.positions.empty() || out.faces.empty()) {
|
||||
errorMsg = "No geometry found to build raw mesh";
|
||||
return false;
|
||||
}
|
||||
|
||||
out.hasNormals = false;
|
||||
for (const auto& n : out.normals) {
|
||||
if (glm::length(n) > 1e-4f) { out.hasNormals = true; break; }
|
||||
}
|
||||
|
||||
out.hasUVs = false;
|
||||
for (const auto& uv : out.uvs) {
|
||||
if (std::abs(uv.x) > 1e-6f || std::abs(uv.y) > 1e-6f) { out.hasUVs = true; break; }
|
||||
}
|
||||
|
||||
if (!out.hasNormals) {
|
||||
out.normals.assign(out.positions.size(), glm::vec3(0.0f));
|
||||
std::vector<glm::vec3> accum(out.positions.size(), glm::vec3(0.0f));
|
||||
for (const auto& face : out.faces) {
|
||||
const glm::vec3& a = out.positions[face.x];
|
||||
const glm::vec3& b = out.positions[face.y];
|
||||
const glm::vec3& c = out.positions[face.z];
|
||||
glm::vec3 n = glm::normalize(glm::cross(b - a, c - a));
|
||||
accum[face.x] += n;
|
||||
accum[face.y] += n;
|
||||
accum[face.z] += n;
|
||||
}
|
||||
for (size_t i = 0; i < accum.size(); i++) {
|
||||
if (glm::length(accum[i]) > 1e-6f) {
|
||||
out.normals[i] = glm::normalize(accum[i]);
|
||||
}
|
||||
}
|
||||
out.hasNormals = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModelLoader::processNode(aiNode* node, const aiScene* scene, const aiMatrix4x4& parentTransform,
|
||||
std::vector<float>& vertices, std::vector<glm::vec3>& triPositions,
|
||||
std::vector<glm::vec3>& positions, std::vector<uint32_t>& indices,
|
||||
@@ -720,6 +1322,7 @@ const std::vector<OBJLoader::LoadedMesh>& ModelLoader::getAllMeshes() const {
|
||||
|
||||
void ModelLoader::clear() {
|
||||
loadedMeshes.clear();
|
||||
cachedScenes.clear();
|
||||
}
|
||||
|
||||
size_t ModelLoader::getMeshCount() const {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "Common.h"
|
||||
#include "Rendering.h"
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <assimp/Importer.hpp>
|
||||
#include <assimp/scene.h>
|
||||
@@ -19,6 +20,7 @@ struct ModelFormat {
|
||||
struct ModelLoadResult {
|
||||
bool success = false;
|
||||
int meshIndex = -1;
|
||||
std::vector<int> meshIndices;
|
||||
std::string errorMessage;
|
||||
int vertexCount = 0;
|
||||
int faceCount = 0;
|
||||
@@ -41,6 +43,51 @@ struct RawMeshAsset {
|
||||
bool hasUVs = false;
|
||||
};
|
||||
|
||||
struct ModelMaterialInfo {
|
||||
std::string name;
|
||||
MaterialProperties props;
|
||||
std::string albedoPath;
|
||||
std::string normalPath;
|
||||
};
|
||||
|
||||
struct ModelNodeInfo {
|
||||
std::string name;
|
||||
int parentIndex = -1;
|
||||
std::vector<int> meshIndices;
|
||||
glm::vec3 localPosition = glm::vec3(0.0f);
|
||||
glm::vec3 localRotation = glm::vec3(0.0f);
|
||||
glm::vec3 localScale = glm::vec3(1.0f);
|
||||
bool isBone = false;
|
||||
};
|
||||
|
||||
struct ModelSceneData {
|
||||
std::vector<ModelNodeInfo> nodes;
|
||||
std::vector<ModelMaterialInfo> materials;
|
||||
std::vector<int> meshIndices;
|
||||
std::vector<int> meshMaterialIndices;
|
||||
struct AnimVecKey {
|
||||
float time = 0.0f;
|
||||
glm::vec3 value = glm::vec3(0.0f);
|
||||
};
|
||||
struct AnimQuatKey {
|
||||
float time = 0.0f;
|
||||
glm::quat value = glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
};
|
||||
struct AnimChannel {
|
||||
std::string nodeName;
|
||||
std::vector<AnimVecKey> positions;
|
||||
std::vector<AnimQuatKey> rotations;
|
||||
std::vector<AnimVecKey> scales;
|
||||
};
|
||||
struct AnimationClip {
|
||||
std::string name;
|
||||
double duration = 0.0;
|
||||
double ticksPerSecond = 0.0;
|
||||
std::vector<AnimChannel> channels;
|
||||
};
|
||||
std::vector<AnimationClip> animations;
|
||||
};
|
||||
|
||||
class ModelLoader {
|
||||
public:
|
||||
// Singleton access
|
||||
@@ -49,6 +96,9 @@ public:
|
||||
// Load a model file (FBX, OBJ, GLTF, etc.)
|
||||
ModelLoadResult loadModel(const std::string& filepath);
|
||||
|
||||
// Load a model scene with node hierarchy and per-mesh materials
|
||||
bool loadModelScene(const std::string& filepath, ModelSceneData& out, std::string& errorMsg);
|
||||
|
||||
// Get mesh by index
|
||||
Mesh* getMesh(int index);
|
||||
|
||||
@@ -73,6 +123,16 @@ public:
|
||||
// Update an already-loaded raw mesh in GPU memory
|
||||
bool updateRawMesh(int meshIndex, const RawMeshAsset& asset, std::string& errorMsg);
|
||||
|
||||
// Build a raw mesh asset from a model scene without writing to disk
|
||||
bool buildRawMeshFromScene(const std::string& filepath, RawMeshAsset& out, std::string& errorMsg,
|
||||
glm::vec3* outRootPos = nullptr,
|
||||
glm::vec3* outRootRot = nullptr,
|
||||
glm::vec3* outRootScale = nullptr);
|
||||
|
||||
// Add a raw mesh asset to the GPU cache and return its mesh index
|
||||
int addRawMesh(const RawMeshAsset& asset, const std::string& sourcePath,
|
||||
const std::string& name, std::string& errorMsg);
|
||||
|
||||
// Get list of supported formats
|
||||
static std::vector<ModelFormat> getSupportedFormats();
|
||||
|
||||
@@ -100,6 +160,7 @@ private:
|
||||
|
||||
// Storage for loaded meshes (reusing OBJLoader::LoadedMesh structure)
|
||||
std::vector<OBJLoader::LoadedMesh> loadedMeshes;
|
||||
std::unordered_map<std::string, ModelSceneData> cachedScenes;
|
||||
|
||||
// Assimp importer (kept for resource management)
|
||||
Assimp::Importer importer;
|
||||
|
||||
@@ -122,9 +122,9 @@ void PhysicsSystem::createGroundPlane() {
|
||||
|
||||
bool PhysicsSystem::gatherMeshData(const SceneObject& obj, std::vector<PxVec3>& vertices, std::vector<uint32_t>& indices) const {
|
||||
const OBJLoader::LoadedMesh* meshInfo = nullptr;
|
||||
if (obj.type == ObjectType::OBJMesh && obj.meshId >= 0) {
|
||||
if (obj.hasRenderer && obj.renderType == RenderType::OBJMesh && obj.meshId >= 0) {
|
||||
meshInfo = g_objLoader.getMeshInfo(obj.meshId);
|
||||
} else if (obj.type == ObjectType::Model && obj.meshId >= 0) {
|
||||
} else if (obj.hasRenderer && obj.renderType == RenderType::Model && obj.meshId >= 0) {
|
||||
meshInfo = getModelLoader().getMeshInfo(obj.meshId);
|
||||
}
|
||||
if (!meshInfo) {
|
||||
@@ -215,21 +215,21 @@ bool PhysicsSystem::attachPrimitiveShape(PxRigidActor* actor, const SceneObject&
|
||||
s->setRestOffset(rest);
|
||||
};
|
||||
|
||||
switch (obj.type) {
|
||||
case ObjectType::Cube: {
|
||||
switch (obj.renderType) {
|
||||
case RenderType::Cube: {
|
||||
PxVec3 halfExtents = ToPxVec3(glm::max(obj.scale * 0.5f, glm::vec3(0.01f)));
|
||||
shape = mPhysics->createShape(PxBoxGeometry(halfExtents), *mDefaultMaterial, true);
|
||||
tuneShape(shape, std::min({halfExtents.x, halfExtents.y, halfExtents.z}) * 2.0f, isDynamic);
|
||||
break;
|
||||
}
|
||||
case ObjectType::Sphere: {
|
||||
case RenderType::Sphere: {
|
||||
float radius = std::max({obj.scale.x, obj.scale.y, obj.scale.z}) * 0.5f;
|
||||
radius = std::max(radius, 0.01f);
|
||||
shape = mPhysics->createShape(PxSphereGeometry(radius), *mDefaultMaterial, true);
|
||||
tuneShape(shape, radius * 2.0f, isDynamic);
|
||||
break;
|
||||
}
|
||||
case ObjectType::Capsule: {
|
||||
case RenderType::Capsule: {
|
||||
float radius = std::max(obj.scale.x, obj.scale.z) * 0.5f;
|
||||
radius = std::max(radius, 0.01f);
|
||||
float cylHeight = std::max(0.05f, obj.scale.y - radius * 2.0f);
|
||||
@@ -242,21 +242,21 @@ bool PhysicsSystem::attachPrimitiveShape(PxRigidActor* actor, const SceneObject&
|
||||
tuneShape(shape, std::min(radius * 2.0f, halfHeight * 2.0f), isDynamic);
|
||||
break;
|
||||
}
|
||||
case ObjectType::Plane: {
|
||||
case RenderType::Plane: {
|
||||
glm::vec3 halfExtents = glm::max(obj.scale * 0.5f, glm::vec3(0.01f));
|
||||
halfExtents.z = std::max(halfExtents.z, 0.01f);
|
||||
shape = mPhysics->createShape(PxBoxGeometry(ToPxVec3(halfExtents)), *mDefaultMaterial, true);
|
||||
tuneShape(shape, std::min({halfExtents.x, halfExtents.y, halfExtents.z}) * 2.0f, isDynamic);
|
||||
break;
|
||||
}
|
||||
case ObjectType::Sprite: {
|
||||
case RenderType::Sprite: {
|
||||
glm::vec3 halfExtents = glm::max(obj.scale * 0.5f, glm::vec3(0.01f));
|
||||
halfExtents.z = std::max(halfExtents.z, 0.01f);
|
||||
shape = mPhysics->createShape(PxBoxGeometry(ToPxVec3(halfExtents)), *mDefaultMaterial, true);
|
||||
tuneShape(shape, std::min({halfExtents.x, halfExtents.y, halfExtents.z}) * 2.0f, isDynamic);
|
||||
break;
|
||||
}
|
||||
case ObjectType::Torus: {
|
||||
case RenderType::Torus: {
|
||||
float radius = std::max({obj.scale.x, obj.scale.y, obj.scale.z}) * 0.5f;
|
||||
radius = std::max(radius, 0.01f);
|
||||
shape = mPhysics->createShape(PxSphereGeometry(radius), *mDefaultMaterial, true);
|
||||
@@ -302,9 +302,9 @@ bool PhysicsSystem::attachColliderShape(PxRigidActor* actor, const SceneObject&
|
||||
minDim = std::min(radius * 2.0f, halfHeight * 2.0f);
|
||||
} else {
|
||||
const OBJLoader::LoadedMesh* meshInfo = nullptr;
|
||||
if (obj.type == ObjectType::OBJMesh && obj.meshId >= 0) {
|
||||
if (obj.hasRenderer && obj.renderType == RenderType::OBJMesh && obj.meshId >= 0) {
|
||||
meshInfo = g_objLoader.getMeshInfo(obj.meshId);
|
||||
} else if (obj.type == ObjectType::Model && obj.meshId >= 0) {
|
||||
} else if (obj.hasRenderer && obj.renderType == RenderType::Model && obj.meshId >= 0) {
|
||||
meshInfo = getModelLoader().getMeshInfo(obj.meshId);
|
||||
}
|
||||
if (!meshInfo) {
|
||||
@@ -491,7 +491,6 @@ void PhysicsSystem::onPlayStart(const std::vector<SceneObject>& objects) {
|
||||
if (!isReady()) return;
|
||||
|
||||
clearActors();
|
||||
createGroundPlane();
|
||||
|
||||
struct MeshCookInfo {
|
||||
std::string name;
|
||||
@@ -506,9 +505,9 @@ void PhysicsSystem::onPlayStart(const std::vector<SceneObject>& objects) {
|
||||
if (!obj.enabled || !obj.hasCollider || !obj.collider.enabled) continue;
|
||||
if (obj.collider.type == ColliderType::Box || obj.collider.type == ColliderType::Capsule) continue;
|
||||
const OBJLoader::LoadedMesh* meshInfo = nullptr;
|
||||
if (obj.type == ObjectType::OBJMesh && obj.meshId >= 0) {
|
||||
if (obj.hasRenderer && obj.renderType == RenderType::OBJMesh && obj.meshId >= 0) {
|
||||
meshInfo = g_objLoader.getMeshInfo(obj.meshId);
|
||||
} else if (obj.type == ObjectType::Model && obj.meshId >= 0) {
|
||||
} else if (obj.hasRenderer && obj.renderType == RenderType::Model && obj.meshId >= 0) {
|
||||
meshInfo = getModelLoader().getMeshInfo(obj.meshId);
|
||||
}
|
||||
if (!meshInfo) continue;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -56,10 +56,18 @@ class SceneSerializer {
|
||||
public:
|
||||
static bool saveScene(const fs::path& filePath,
|
||||
const std::vector<SceneObject>& objects,
|
||||
int nextId);
|
||||
int nextId,
|
||||
float timeOfDay);
|
||||
|
||||
static bool loadScene(const fs::path& filePath,
|
||||
std::vector<SceneObject>& objects,
|
||||
int& nextId,
|
||||
int& outVersion);
|
||||
int& outVersion,
|
||||
float* outTimeOfDay = nullptr);
|
||||
|
||||
static bool loadSceneDeferred(const fs::path& filePath,
|
||||
std::vector<SceneObject>& objects,
|
||||
int& nextId,
|
||||
int& outVersion,
|
||||
float* outTimeOfDay = nullptr);
|
||||
};
|
||||
|
||||
@@ -13,12 +13,12 @@ OBJLoader g_objLoader;
|
||||
// Cube vertex data
|
||||
float vertices[] = {
|
||||
// Back face (z = -0.5f)
|
||||
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
|
||||
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
|
||||
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
|
||||
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
|
||||
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
|
||||
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
|
||||
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
|
||||
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
|
||||
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
|
||||
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
|
||||
|
||||
// Front face (z = 0.5f)
|
||||
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
|
||||
@@ -37,12 +37,12 @@ float vertices[] = {
|
||||
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
|
||||
|
||||
// Right face (x = 0.5f)
|
||||
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
|
||||
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
|
||||
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
|
||||
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
|
||||
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
|
||||
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
|
||||
|
||||
// Bottom face (y = -0.5f)
|
||||
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
|
||||
@@ -54,11 +54,11 @@ float vertices[] = {
|
||||
|
||||
// Top face (y = 0.5f)
|
||||
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
|
||||
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
|
||||
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
|
||||
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
|
||||
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
|
||||
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
|
||||
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
|
||||
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f
|
||||
};
|
||||
|
||||
float mirrorPlaneVertices[] = {
|
||||
@@ -287,6 +287,7 @@ std::vector<float> generateTorus(int segments, int sides) {
|
||||
// Mesh implementation
|
||||
Mesh::Mesh(const float* vertexData, size_t dataSizeBytes) {
|
||||
vertexCount = dataSizeBytes / (8 * sizeof(float));
|
||||
strideFloats = 8;
|
||||
|
||||
glGenVertexArrays(1, &VAO);
|
||||
glGenBuffers(1, &VBO);
|
||||
@@ -308,9 +309,52 @@ Mesh::Mesh(const float* vertexData, size_t dataSizeBytes) {
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
Mesh::Mesh(const float* vertexData, size_t dataSizeBytes, bool dynamicUsage,
|
||||
const void* boneData, size_t boneDataBytes) {
|
||||
vertexCount = dataSizeBytes / (8 * sizeof(float));
|
||||
strideFloats = 8;
|
||||
dynamic = dynamicUsage;
|
||||
hasBones = boneData && boneDataBytes > 0;
|
||||
|
||||
glGenVertexArrays(1, &VAO);
|
||||
glGenBuffers(1, &VBO);
|
||||
|
||||
glBindVertexArray(VAO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, VBO);
|
||||
glBufferData(GL_ARRAY_BUFFER, dataSizeBytes, vertexData, dynamicUsage ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
|
||||
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, strideFloats * sizeof(float), (void*)0);
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, strideFloats * sizeof(float), (void*)(3 * sizeof(float)));
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, strideFloats * sizeof(float), (void*)(6 * sizeof(float)));
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
if (hasBones) {
|
||||
glGenBuffers(1, &boneVBO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, boneVBO);
|
||||
glBufferData(GL_ARRAY_BUFFER, boneDataBytes, boneData, GL_STATIC_DRAW);
|
||||
|
||||
glVertexAttribIPointer(3, 4, GL_INT, sizeof(int) * 4 + sizeof(float) * 4, (void*)0);
|
||||
glEnableVertexAttribArray(3);
|
||||
|
||||
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(int) * 4 + sizeof(float) * 4,
|
||||
(void*)(sizeof(int) * 4));
|
||||
glEnableVertexAttribArray(4);
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
Mesh::~Mesh() {
|
||||
glDeleteVertexArrays(1, &VAO);
|
||||
glDeleteBuffers(1, &VBO);
|
||||
if (boneVBO) {
|
||||
glDeleteBuffers(1, &boneVBO);
|
||||
}
|
||||
}
|
||||
|
||||
void Mesh::draw() const {
|
||||
@@ -319,6 +363,56 @@ void Mesh::draw() const {
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void Mesh::updateVertices(const float* vertexData, size_t dataSizeBytes) {
|
||||
if (!dynamic) return;
|
||||
glBindBuffer(GL_ARRAY_BUFFER, VBO);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, dataSizeBytes, vertexData);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
vertexCount = dataSizeBytes / (strideFloats * sizeof(float));
|
||||
}
|
||||
|
||||
static void applyCpuSkinning(OBJLoader::LoadedMesh& meshInfo, const std::vector<glm::mat4>& bones, int maxBones) {
|
||||
if (!meshInfo.mesh || !meshInfo.isSkinned) return;
|
||||
if (meshInfo.baseVertices.empty() || meshInfo.boneIds.empty() || meshInfo.boneWeights.empty()) return;
|
||||
if (!meshInfo.mesh->isDynamic()) return;
|
||||
|
||||
size_t vertexCount = meshInfo.baseVertices.size() / 8;
|
||||
if (vertexCount == 0 || meshInfo.boneIds.size() != vertexCount || meshInfo.boneWeights.size() != vertexCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<float> skinned = meshInfo.baseVertices;
|
||||
int boneLimit = std::min<int>(static_cast<int>(bones.size()), maxBones);
|
||||
for (size_t i = 0; i < vertexCount; ++i) {
|
||||
glm::vec3 basePos(skinned[i * 8 + 0], skinned[i * 8 + 1], skinned[i * 8 + 2]);
|
||||
glm::vec3 baseNorm(skinned[i * 8 + 3], skinned[i * 8 + 4], skinned[i * 8 + 5]);
|
||||
glm::ivec4 ids = meshInfo.boneIds[i];
|
||||
glm::vec4 weights = meshInfo.boneWeights[i];
|
||||
|
||||
glm::vec4 skinnedPos(0.0f);
|
||||
glm::vec3 skinnedNorm(0.0f);
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
int id = ids[k];
|
||||
float w = weights[k];
|
||||
if (w <= 0.0f || id < 0 || id >= boneLimit) continue;
|
||||
const glm::mat4& m = bones[id];
|
||||
skinnedPos += w * (m * glm::vec4(basePos, 1.0f));
|
||||
skinnedNorm += w * glm::mat3(m) * baseNorm;
|
||||
}
|
||||
skinned[i * 8 + 0] = skinnedPos.x;
|
||||
skinned[i * 8 + 1] = skinnedPos.y;
|
||||
skinned[i * 8 + 2] = skinnedPos.z;
|
||||
if (glm::length(skinnedNorm) > 1e-6f) {
|
||||
skinnedNorm = glm::normalize(skinnedNorm);
|
||||
}
|
||||
skinned[i * 8 + 3] = skinnedNorm.x;
|
||||
skinned[i * 8 + 4] = skinnedNorm.y;
|
||||
skinned[i * 8 + 5] = skinnedNorm.z;
|
||||
}
|
||||
|
||||
meshInfo.mesh->updateVertices(skinned.data(), skinned.size() * sizeof(float));
|
||||
}
|
||||
|
||||
// OBJLoader implementation
|
||||
int OBJLoader::loadOBJ(const std::string& filepath, std::string& errorMsg) {
|
||||
// Check if already loaded
|
||||
@@ -821,7 +915,7 @@ void Renderer::updateMirrorTargets(const Camera& camera, const std::vector<Scene
|
||||
};
|
||||
|
||||
for (const auto& obj : sceneObjects) {
|
||||
if (!obj.enabled || obj.type != ObjectType::Mirror) continue;
|
||||
if (!obj.enabled || !obj.hasRenderer || obj.renderType != RenderType::Mirror) continue;
|
||||
active.insert(obj.id);
|
||||
|
||||
RenderTarget& target = mirrorTargets[obj.id];
|
||||
@@ -1017,7 +1111,7 @@ void Renderer::renderObject(const SceneObject& obj) {
|
||||
shader->setFloat("specularStrength", obj.material.specularStrength);
|
||||
shader->setFloat("shininess", obj.material.shininess);
|
||||
shader->setFloat("mixAmount", obj.material.textureMix);
|
||||
shader->setBool("unlit", obj.type == ObjectType::Mirror || obj.type == ObjectType::Sprite);
|
||||
shader->setBool("unlit", obj.renderType == RenderType::Mirror || obj.renderType == RenderType::Sprite);
|
||||
|
||||
Texture* baseTex = texture1;
|
||||
if (!obj.albedoTexturePath.empty()) {
|
||||
@@ -1026,7 +1120,7 @@ void Renderer::renderObject(const SceneObject& obj) {
|
||||
if (baseTex) baseTex->Bind(GL_TEXTURE0);
|
||||
|
||||
bool overlayUsed = false;
|
||||
if (obj.type == ObjectType::Mirror) {
|
||||
if (obj.renderType == RenderType::Mirror) {
|
||||
auto it = mirrorTargets.find(obj.id);
|
||||
if (it != mirrorTargets.end() && it->second.texture != 0) {
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
@@ -1054,29 +1148,29 @@ void Renderer::renderObject(const SceneObject& obj) {
|
||||
}
|
||||
shader->setBool("hasNormalMap", normalUsed);
|
||||
|
||||
switch (obj.type) {
|
||||
case ObjectType::Cube:
|
||||
switch (obj.renderType) {
|
||||
case RenderType::Cube:
|
||||
cubeMesh->draw();
|
||||
break;
|
||||
case ObjectType::Sphere:
|
||||
case RenderType::Sphere:
|
||||
sphereMesh->draw();
|
||||
break;
|
||||
case ObjectType::Capsule:
|
||||
case RenderType::Capsule:
|
||||
capsuleMesh->draw();
|
||||
break;
|
||||
case ObjectType::Plane:
|
||||
case RenderType::Plane:
|
||||
if (planeMesh) planeMesh->draw();
|
||||
break;
|
||||
case ObjectType::Mirror:
|
||||
case RenderType::Mirror:
|
||||
if (planeMesh) planeMesh->draw();
|
||||
break;
|
||||
case ObjectType::Sprite:
|
||||
case RenderType::Sprite:
|
||||
if (planeMesh) planeMesh->draw();
|
||||
break;
|
||||
case ObjectType::Torus:
|
||||
case RenderType::Torus:
|
||||
if (torusMesh) torusMesh->draw();
|
||||
break;
|
||||
case ObjectType::OBJMesh:
|
||||
case RenderType::OBJMesh:
|
||||
if (obj.meshId >= 0) {
|
||||
Mesh* objMesh = g_objLoader.getMesh(obj.meshId);
|
||||
if (objMesh) {
|
||||
@@ -1084,7 +1178,7 @@ void Renderer::renderObject(const SceneObject& obj) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ObjectType::Model:
|
||||
case RenderType::Model:
|
||||
if (obj.meshId >= 0) {
|
||||
Mesh* modelMesh = getModelLoader().getMesh(obj.meshId);
|
||||
if (modelMesh) {
|
||||
@@ -1092,26 +1186,8 @@ void Renderer::renderObject(const SceneObject& obj) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ObjectType::PointLight:
|
||||
case ObjectType::SpotLight:
|
||||
case ObjectType::AreaLight:
|
||||
// Lights are not rendered as geometry
|
||||
break;
|
||||
case ObjectType::DirectionalLight:
|
||||
// Not rendered as geometry
|
||||
break;
|
||||
case ObjectType::Camera:
|
||||
// Cameras are editor helpers only
|
||||
break;
|
||||
case ObjectType::PostFXNode:
|
||||
break;
|
||||
case ObjectType::Sprite2D:
|
||||
case ObjectType::Canvas:
|
||||
case ObjectType::UIImage:
|
||||
case ObjectType::UISlider:
|
||||
case ObjectType::UIButton:
|
||||
case ObjectType::UIText:
|
||||
// UI types are rendered via ImGui, not here.
|
||||
case RenderType::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1157,8 +1233,8 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
||||
candidates.reserve(sceneObjects.size());
|
||||
|
||||
for (const auto& obj : sceneObjects) {
|
||||
if (!obj.enabled || !obj.light.enabled) continue;
|
||||
if (obj.type == ObjectType::DirectionalLight) {
|
||||
if (!obj.enabled || !obj.hasLight || !obj.light.enabled) continue;
|
||||
if (obj.light.type == LightType::Directional) {
|
||||
LightUniform l;
|
||||
l.type = 0;
|
||||
l.dir = forwardFromRotation(obj);
|
||||
@@ -1166,7 +1242,7 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
||||
l.intensity = obj.light.intensity;
|
||||
lights.push_back(l);
|
||||
if (lights.size() >= kMaxLights) break;
|
||||
} else if (obj.type == ObjectType::SpotLight) {
|
||||
} else if (obj.light.type == LightType::Spot) {
|
||||
LightUniform l;
|
||||
l.type = 2;
|
||||
l.pos = obj.position;
|
||||
@@ -1182,7 +1258,7 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
||||
c.distSq = glm::dot(delta, delta);
|
||||
c.id = obj.id;
|
||||
candidates.push_back(c);
|
||||
} else if (obj.type == ObjectType::PointLight) {
|
||||
} else if (obj.light.type == LightType::Point) {
|
||||
LightUniform l;
|
||||
l.type = 1;
|
||||
l.pos = obj.position;
|
||||
@@ -1195,7 +1271,7 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
||||
c.distSq = glm::dot(delta, delta);
|
||||
c.id = obj.id;
|
||||
candidates.push_back(c);
|
||||
} else if (obj.type == ObjectType::AreaLight) {
|
||||
} else if (obj.light.type == LightType::Area) {
|
||||
LightUniform l;
|
||||
l.type = 3; // area
|
||||
l.pos = obj.position;
|
||||
@@ -1227,23 +1303,48 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
||||
}
|
||||
}
|
||||
|
||||
glm::mat4 view = camera.getViewMatrix();
|
||||
glm::mat4 proj = glm::perspective(glm::radians(fovDeg), (float)width / (float)height, nearPlane, farPlane);
|
||||
|
||||
GLboolean cullFace = glIsEnabled(GL_CULL_FACE);
|
||||
GLint prevCullMode = GL_BACK;
|
||||
glGetIntegerv(GL_CULL_FACE_MODE, &prevCullMode);
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_BACK);
|
||||
|
||||
for (const auto& obj : sceneObjects) {
|
||||
if (!obj.enabled) continue;
|
||||
if (!drawMirrorObjects && obj.type == ObjectType::Mirror) continue;
|
||||
// Skip light gizmo-only types and camera helpers
|
||||
if (obj.type == ObjectType::PointLight || obj.type == ObjectType::SpotLight || obj.type == ObjectType::AreaLight || obj.type == ObjectType::Camera || obj.type == ObjectType::PostFXNode || obj.type == ObjectType::Canvas || obj.type == ObjectType::UIImage || obj.type == ObjectType::UISlider || obj.type == ObjectType::UIButton || obj.type == ObjectType::UIText || obj.type == ObjectType::Sprite2D) {
|
||||
continue;
|
||||
}
|
||||
if (!drawMirrorObjects && obj.hasRenderer && obj.renderType == RenderType::Mirror) continue;
|
||||
if (!HasRendererComponent(obj)) continue;
|
||||
|
||||
Shader* active = getShader(obj.vertexShaderPath, obj.fragmentShaderPath);
|
||||
glm::mat4 model = glm::mat4(1.0f);
|
||||
model = glm::translate(model, obj.position);
|
||||
model = glm::rotate(model, glm::radians(obj.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
model = glm::rotate(model, glm::radians(obj.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
model = glm::rotate(model, glm::radians(obj.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
model = glm::scale(model, obj.scale);
|
||||
|
||||
std::string vertPath = obj.vertexShaderPath;
|
||||
std::string fragPath = obj.fragmentShaderPath;
|
||||
int boneLimit = obj.skeletal.maxBones;
|
||||
int availableBones = static_cast<int>(obj.skeletal.finalMatrices.size());
|
||||
bool needsFallback = obj.hasSkeletalAnimation && obj.skeletal.enabled &&
|
||||
obj.skeletal.allowCpuFallback &&
|
||||
boneLimit > 0 && availableBones > boneLimit;
|
||||
bool wantsGpuSkinning = obj.hasSkeletalAnimation && obj.skeletal.enabled &&
|
||||
obj.skeletal.useGpuSkinning && !needsFallback;
|
||||
if (vertPath.empty() && wantsGpuSkinning) {
|
||||
vertPath = skinnedVertPath;
|
||||
}
|
||||
Shader* active = getShader(vertPath, fragPath);
|
||||
if (!active) continue;
|
||||
shader = active;
|
||||
shader->use();
|
||||
|
||||
shader->setMat4("view", camera.getViewMatrix());
|
||||
shader->setMat4("projection", glm::perspective(glm::radians(fovDeg), (float)width / (float)height, nearPlane, farPlane));
|
||||
shader->setMat4("view", view);
|
||||
shader->setMat4("projection", proj);
|
||||
shader->setVec3("viewPos", camera.position);
|
||||
shader->setBool("unlit", obj.type == ObjectType::Mirror);
|
||||
shader->setBool("unlit", obj.renderType == RenderType::Mirror);
|
||||
shader->setVec3("ambientColor", ambientColor);
|
||||
shader->setVec3("ambientColor", ambientColor);
|
||||
|
||||
@@ -1263,13 +1364,6 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
||||
shader->setFloat("lightAreaFadeArr" + idx, l.areaFade);
|
||||
}
|
||||
|
||||
glm::mat4 model = glm::mat4(1.0f);
|
||||
model = glm::translate(model, obj.position);
|
||||
model = glm::rotate(model, glm::radians(obj.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
model = glm::rotate(model, glm::radians(obj.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
model = glm::rotate(model, glm::radians(obj.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
model = glm::scale(model, obj.scale);
|
||||
|
||||
shader->setMat4("model", model);
|
||||
shader->setVec3("materialColor", obj.material.color);
|
||||
shader->setFloat("ambientStrength", obj.material.ambientStrength);
|
||||
@@ -1277,6 +1371,20 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
||||
shader->setFloat("shininess", obj.material.shininess);
|
||||
shader->setFloat("mixAmount", obj.material.textureMix);
|
||||
|
||||
if (obj.hasSkeletalAnimation && obj.skeletal.enabled) {
|
||||
int safeLimit = std::max(0, boneLimit);
|
||||
int boneCount = std::min<int>(availableBones, safeLimit);
|
||||
if (wantsGpuSkinning && boneCount > 0) {
|
||||
shader->setInt("boneCount", boneCount);
|
||||
shader->setMat4Array("bones", obj.skeletal.finalMatrices.data(), boneCount);
|
||||
shader->setBool("useSkinning", true);
|
||||
} else {
|
||||
shader->setBool("useSkinning", false);
|
||||
}
|
||||
} else {
|
||||
shader->setBool("useSkinning", false);
|
||||
}
|
||||
|
||||
Texture* baseTex = texture1;
|
||||
if (!obj.albedoTexturePath.empty()) {
|
||||
if (auto* t = getTexture(obj.albedoTexturePath)) baseTex = t;
|
||||
@@ -1284,7 +1392,7 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
||||
if (baseTex) baseTex->Bind(GL_TEXTURE0);
|
||||
|
||||
bool overlayUsed = false;
|
||||
if (obj.type == ObjectType::Mirror) {
|
||||
if (obj.renderType == RenderType::Mirror) {
|
||||
auto it = mirrorTargets.find(obj.id);
|
||||
if (it != mirrorTargets.end() && it->second.texture != 0) {
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
@@ -1313,31 +1421,51 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
||||
shader->setBool("hasNormalMap", normalUsed);
|
||||
|
||||
Mesh* meshToDraw = nullptr;
|
||||
if (obj.type == ObjectType::Cube) meshToDraw = cubeMesh;
|
||||
else if (obj.type == ObjectType::Sphere) meshToDraw = sphereMesh;
|
||||
else if (obj.type == ObjectType::Capsule) meshToDraw = capsuleMesh;
|
||||
else if (obj.type == ObjectType::Plane) meshToDraw = planeMesh;
|
||||
else if (obj.type == ObjectType::Mirror) meshToDraw = planeMesh;
|
||||
else if (obj.type == ObjectType::Sprite) meshToDraw = planeMesh;
|
||||
else if (obj.type == ObjectType::Torus) meshToDraw = torusMesh;
|
||||
else if (obj.type == ObjectType::OBJMesh && obj.meshId != -1) {
|
||||
if (obj.renderType == RenderType::Cube) meshToDraw = cubeMesh;
|
||||
else if (obj.renderType == RenderType::Sphere) meshToDraw = sphereMesh;
|
||||
else if (obj.renderType == RenderType::Capsule) meshToDraw = capsuleMesh;
|
||||
else if (obj.renderType == RenderType::Plane) meshToDraw = planeMesh;
|
||||
else if (obj.renderType == RenderType::Mirror) meshToDraw = planeMesh;
|
||||
else if (obj.renderType == RenderType::Sprite) meshToDraw = planeMesh;
|
||||
else if (obj.renderType == RenderType::Torus) meshToDraw = torusMesh;
|
||||
else if (obj.renderType == RenderType::OBJMesh && obj.meshId != -1) {
|
||||
meshToDraw = g_objLoader.getMesh(obj.meshId);
|
||||
} else if (obj.type == ObjectType::Model && obj.meshId != -1) {
|
||||
} else if (obj.renderType == RenderType::Model && obj.meshId != -1) {
|
||||
meshToDraw = getModelLoader().getMesh(obj.meshId);
|
||||
}
|
||||
|
||||
if (obj.renderType == RenderType::Model && obj.meshId != -1 &&
|
||||
obj.hasSkeletalAnimation && obj.skeletal.enabled && !wantsGpuSkinning) {
|
||||
const auto* meshInfo = getModelLoader().getMeshInfo(obj.meshId);
|
||||
if (meshInfo) {
|
||||
applyCpuSkinning(*const_cast<OBJLoader::LoadedMesh*>(meshInfo),
|
||||
obj.skeletal.finalMatrices,
|
||||
obj.skeletal.maxBones);
|
||||
}
|
||||
}
|
||||
|
||||
bool doubleSided = (obj.renderType == RenderType::Sprite || obj.renderType == RenderType::Mirror);
|
||||
if (doubleSided) {
|
||||
glDisable(GL_CULL_FACE);
|
||||
} else {
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_BACK);
|
||||
}
|
||||
|
||||
if (meshToDraw) {
|
||||
recordMeshDraw();
|
||||
meshToDraw->draw();
|
||||
}
|
||||
}
|
||||
|
||||
if (skybox) {
|
||||
glm::mat4 view = camera.getViewMatrix();
|
||||
glm::mat4 proj = glm::perspective(glm::radians(fovDeg),
|
||||
(float)width / height,
|
||||
nearPlane, farPlane);
|
||||
if (!cullFace) {
|
||||
glDisable(GL_CULL_FACE);
|
||||
} else {
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(prevCullMode);
|
||||
}
|
||||
|
||||
if (skybox) {
|
||||
recordDrawCall();
|
||||
skybox->draw(glm::value_ptr(view), glm::value_ptr(proj));
|
||||
}
|
||||
@@ -1351,7 +1479,7 @@ PostFXSettings Renderer::gatherPostFX(const std::vector<SceneObject>& sceneObjec
|
||||
PostFXSettings combined;
|
||||
combined.enabled = false;
|
||||
for (const auto& obj : sceneObjects) {
|
||||
if (obj.type != ObjectType::PostFXNode) continue;
|
||||
if (!obj.hasPostFX) continue;
|
||||
if (!obj.postFx.enabled) continue;
|
||||
combined = obj.postFx; // Last enabled node wins for now
|
||||
combined.enabled = true;
|
||||
@@ -1601,9 +1729,9 @@ void Renderer::renderCollisionOverlay(const Camera& camera, const std::vector<Sc
|
||||
break;
|
||||
case ColliderType::Mesh:
|
||||
case ColliderType::ConvexMesh:
|
||||
if (obj.type == ObjectType::OBJMesh && obj.meshId >= 0) {
|
||||
if (obj.hasRenderer && obj.renderType == RenderType::OBJMesh && obj.meshId >= 0) {
|
||||
meshToDraw = g_objLoader.getMesh(obj.meshId);
|
||||
} else if (obj.type == ObjectType::Model && obj.meshId >= 0) {
|
||||
} else if (obj.hasRenderer && obj.renderType == RenderType::Model && obj.meshId >= 0) {
|
||||
meshToDraw = getModelLoader().getMesh(obj.meshId);
|
||||
} else {
|
||||
meshToDraw = nullptr;
|
||||
@@ -1612,29 +1740,29 @@ void Renderer::renderCollisionOverlay(const Camera& camera, const std::vector<Sc
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (obj.type) {
|
||||
case ObjectType::Cube:
|
||||
switch (obj.renderType) {
|
||||
case RenderType::Cube:
|
||||
meshToDraw = cubeMesh;
|
||||
break;
|
||||
case ObjectType::Sphere:
|
||||
case RenderType::Sphere:
|
||||
meshToDraw = sphereMesh;
|
||||
break;
|
||||
case ObjectType::Capsule:
|
||||
case RenderType::Capsule:
|
||||
meshToDraw = capsuleMesh;
|
||||
break;
|
||||
case ObjectType::Plane:
|
||||
case RenderType::Plane:
|
||||
meshToDraw = planeMesh;
|
||||
break;
|
||||
case ObjectType::Sprite:
|
||||
case RenderType::Sprite:
|
||||
meshToDraw = planeMesh;
|
||||
break;
|
||||
case ObjectType::Torus:
|
||||
case RenderType::Torus:
|
||||
meshToDraw = sphereMesh;
|
||||
break;
|
||||
case ObjectType::OBJMesh:
|
||||
case RenderType::OBJMesh:
|
||||
if (obj.meshId >= 0) meshToDraw = g_objLoader.getMesh(obj.meshId);
|
||||
break;
|
||||
case ObjectType::Model:
|
||||
case RenderType::Model:
|
||||
if (obj.meshId >= 0) meshToDraw = getModelLoader().getMesh(obj.meshId);
|
||||
break;
|
||||
default:
|
||||
@@ -1673,31 +1801,30 @@ void Renderer::renderSelectionOutline(const Camera& camera, const std::vector<Sc
|
||||
}
|
||||
if (!selectedObj || !selectedObj->enabled) return;
|
||||
|
||||
if (selectedObj->type == ObjectType::PointLight ||
|
||||
selectedObj->type == ObjectType::SpotLight ||
|
||||
selectedObj->type == ObjectType::AreaLight ||
|
||||
selectedObj->type == ObjectType::Camera ||
|
||||
selectedObj->type == ObjectType::PostFXNode ||
|
||||
selectedObj->type == ObjectType::Canvas ||
|
||||
selectedObj->type == ObjectType::UIImage ||
|
||||
selectedObj->type == ObjectType::UISlider ||
|
||||
selectedObj->type == ObjectType::UIButton ||
|
||||
selectedObj->type == ObjectType::UIText ||
|
||||
selectedObj->type == ObjectType::Sprite2D) {
|
||||
if (!HasRendererComponent(*selectedObj)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool wantsGpuSkinning = selectedObj->hasSkeletalAnimation && selectedObj->skeletal.enabled &&
|
||||
selectedObj->skeletal.useGpuSkinning;
|
||||
int boneLimit = selectedObj->skeletal.maxBones;
|
||||
int availableBones = static_cast<int>(selectedObj->skeletal.finalMatrices.size());
|
||||
if (selectedObj->hasSkeletalAnimation && selectedObj->skeletal.enabled &&
|
||||
selectedObj->skeletal.allowCpuFallback && boneLimit > 0 && availableBones > boneLimit) {
|
||||
wantsGpuSkinning = false;
|
||||
}
|
||||
|
||||
Mesh* meshToDraw = nullptr;
|
||||
if (selectedObj->type == ObjectType::Cube) meshToDraw = cubeMesh;
|
||||
else if (selectedObj->type == ObjectType::Sphere) meshToDraw = sphereMesh;
|
||||
else if (selectedObj->type == ObjectType::Capsule) meshToDraw = capsuleMesh;
|
||||
else if (selectedObj->type == ObjectType::Plane) meshToDraw = planeMesh;
|
||||
else if (selectedObj->type == ObjectType::Mirror) meshToDraw = planeMesh;
|
||||
else if (selectedObj->type == ObjectType::Sprite) meshToDraw = planeMesh;
|
||||
else if (selectedObj->type == ObjectType::Torus) meshToDraw = torusMesh;
|
||||
else if (selectedObj->type == ObjectType::OBJMesh && selectedObj->meshId != -1) {
|
||||
if (selectedObj->renderType == RenderType::Cube) meshToDraw = cubeMesh;
|
||||
else if (selectedObj->renderType == RenderType::Sphere) meshToDraw = sphereMesh;
|
||||
else if (selectedObj->renderType == RenderType::Capsule) meshToDraw = capsuleMesh;
|
||||
else if (selectedObj->renderType == RenderType::Plane) meshToDraw = planeMesh;
|
||||
else if (selectedObj->renderType == RenderType::Mirror) meshToDraw = planeMesh;
|
||||
else if (selectedObj->renderType == RenderType::Sprite) meshToDraw = planeMesh;
|
||||
else if (selectedObj->renderType == RenderType::Torus) meshToDraw = torusMesh;
|
||||
else if (selectedObj->renderType == RenderType::OBJMesh && selectedObj->meshId != -1) {
|
||||
meshToDraw = g_objLoader.getMesh(selectedObj->meshId);
|
||||
} else if (selectedObj->type == ObjectType::Model && selectedObj->meshId != -1) {
|
||||
} else if (selectedObj->renderType == RenderType::Model && selectedObj->meshId != -1) {
|
||||
meshToDraw = getModelLoader().getMesh(selectedObj->meshId);
|
||||
}
|
||||
if (!meshToDraw) return;
|
||||
@@ -1764,6 +1891,16 @@ void Renderer::renderSelectionOutline(const Camera& camera, const std::vector<Sc
|
||||
baseModel = glm::rotate(baseModel, glm::radians(selectedObj->rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
baseModel = glm::scale(baseModel, selectedObj->scale);
|
||||
|
||||
if (selectedObj->renderType == RenderType::Model && selectedObj->meshId != -1 &&
|
||||
selectedObj->hasSkeletalAnimation && selectedObj->skeletal.enabled && !wantsGpuSkinning) {
|
||||
const auto* meshInfo = getModelLoader().getMeshInfo(selectedObj->meshId);
|
||||
if (meshInfo) {
|
||||
applyCpuSkinning(*const_cast<OBJLoader::LoadedMesh*>(meshInfo),
|
||||
selectedObj->skeletal.finalMatrices,
|
||||
selectedObj->skeletal.maxBones);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the object in the stencil buffer.
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glStencilMask(0xFF);
|
||||
|
||||
@@ -19,14 +19,23 @@ std::vector<float> generateTorus(int segments = 32, int sides = 16);
|
||||
class Mesh {
|
||||
private:
|
||||
unsigned int VAO, VBO;
|
||||
unsigned int boneVBO = 0;
|
||||
int vertexCount;
|
||||
int strideFloats = 8;
|
||||
bool dynamic = false;
|
||||
bool hasBones = false;
|
||||
|
||||
public:
|
||||
Mesh(const float* vertexData, size_t dataSizeBytes);
|
||||
Mesh(const float* vertexData, size_t dataSizeBytes, bool dynamicUsage,
|
||||
const void* boneData, size_t boneDataBytes);
|
||||
~Mesh();
|
||||
|
||||
void draw() const;
|
||||
void updateVertices(const float* vertexData, size_t dataSizeBytes);
|
||||
int getVertexCount() const { return vertexCount; }
|
||||
bool isDynamic() const { return dynamic; }
|
||||
bool usesBones() const { return hasBones; }
|
||||
};
|
||||
|
||||
class OBJLoader {
|
||||
@@ -44,6 +53,12 @@ public:
|
||||
std::vector<glm::vec3> triangleVertices; // positions duplicated per-triangle for picking
|
||||
std::vector<glm::vec3> positions; // unique vertex positions for physics
|
||||
std::vector<uint32_t> triangleIndices; // triangle indices into positions
|
||||
bool isSkinned = false;
|
||||
std::vector<std::string> boneNames;
|
||||
std::vector<glm::mat4> inverseBindMatrices;
|
||||
std::vector<glm::ivec4> boneIds;
|
||||
std::vector<glm::vec4> boneWeights;
|
||||
std::vector<float> baseVertices;
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -104,6 +119,7 @@ private:
|
||||
};
|
||||
std::unordered_map<std::string, ShaderEntry> shaderCache;
|
||||
std::string defaultVertPath = "Resources/Shaders/vert.glsl";
|
||||
std::string skinnedVertPath = "Resources/Shaders/skinned_vert.glsl";
|
||||
std::string defaultFragPath = "Resources/Shaders/frag.glsl";
|
||||
std::string postVertPath = "Resources/Shaders/postfx_vert.glsl";
|
||||
std::string postFragPath = "Resources/Shaders/postfx_frag.glsl";
|
||||
|
||||
@@ -23,7 +23,31 @@ enum class ObjectType {
|
||||
UIImage = 17,
|
||||
UISlider = 18,
|
||||
UIButton = 19,
|
||||
UIText = 20
|
||||
UIText = 20,
|
||||
Empty = 21
|
||||
};
|
||||
|
||||
enum class RenderType {
|
||||
None = 0,
|
||||
Cube = 1,
|
||||
Sphere = 2,
|
||||
Capsule = 3,
|
||||
OBJMesh = 4,
|
||||
Model = 5,
|
||||
Mirror = 6,
|
||||
Plane = 7,
|
||||
Torus = 8,
|
||||
Sprite = 9
|
||||
};
|
||||
|
||||
enum class UIElementType {
|
||||
None = 0,
|
||||
Canvas = 1,
|
||||
Image = 2,
|
||||
Slider = 3,
|
||||
Button = 4,
|
||||
Text = 5,
|
||||
Sprite2D = 6
|
||||
};
|
||||
|
||||
struct MaterialProperties {
|
||||
@@ -60,6 +84,76 @@ enum class UIButtonStyle {
|
||||
Outline = 1
|
||||
};
|
||||
|
||||
enum class ReverbPreset {
|
||||
Room = 0,
|
||||
LivingRoom = 1,
|
||||
Hall = 2,
|
||||
Forest = 3,
|
||||
Custom = 4
|
||||
};
|
||||
|
||||
enum class ReverbZoneShape {
|
||||
Box = 0,
|
||||
Sphere = 1
|
||||
};
|
||||
|
||||
enum class AudioRolloffMode {
|
||||
Logarithmic = 0,
|
||||
Linear = 1,
|
||||
Exponential = 2,
|
||||
Custom = 3
|
||||
};
|
||||
|
||||
enum class AnimationInterpolation {
|
||||
Linear = 0,
|
||||
SmoothStep = 1,
|
||||
EaseIn = 2,
|
||||
EaseOut = 3,
|
||||
EaseInOut = 4
|
||||
};
|
||||
|
||||
enum class AnimationCurveMode {
|
||||
Preset = 0,
|
||||
Bezier = 1
|
||||
};
|
||||
|
||||
struct AnimationKeyframe {
|
||||
float time = 0.0f;
|
||||
glm::vec3 position = glm::vec3(0.0f);
|
||||
glm::vec3 rotation = glm::vec3(0.0f);
|
||||
glm::vec3 scale = glm::vec3(1.0f);
|
||||
AnimationInterpolation interpolation = AnimationInterpolation::SmoothStep;
|
||||
AnimationCurveMode curveMode = AnimationCurveMode::Preset;
|
||||
glm::vec2 bezierIn = glm::vec2(0.25f, 0.0f);
|
||||
glm::vec2 bezierOut = glm::vec2(0.75f, 1.0f);
|
||||
};
|
||||
|
||||
struct AnimationComponent {
|
||||
bool enabled = true;
|
||||
float clipLength = 2.0f;
|
||||
float playSpeed = 1.0f;
|
||||
bool loop = true;
|
||||
bool applyOnScrub = true;
|
||||
std::vector<AnimationKeyframe> keyframes;
|
||||
};
|
||||
|
||||
struct SkeletalAnimationComponent {
|
||||
bool enabled = true;
|
||||
bool useGpuSkinning = true;
|
||||
bool allowCpuFallback = true;
|
||||
bool useAnimation = true;
|
||||
int clipIndex = 0;
|
||||
float time = 0.0f;
|
||||
float playSpeed = 1.0f;
|
||||
bool loop = true;
|
||||
int skeletonRootId = -1;
|
||||
int maxBones = 128;
|
||||
std::vector<std::string> boneNames;
|
||||
std::vector<int> boneNodeIds;
|
||||
std::vector<glm::mat4> inverseBindMatrices;
|
||||
std::vector<glm::mat4> finalMatrices;
|
||||
};
|
||||
|
||||
struct LightComponent {
|
||||
LightType type = LightType::Point;
|
||||
glm::vec3 color = glm::vec3(1.0f);
|
||||
@@ -85,6 +179,8 @@ struct CameraComponent {
|
||||
float nearClip = NEAR_PLANE;
|
||||
float farClip = FAR_PLANE;
|
||||
bool applyPostFX = true;
|
||||
bool use2D = false;
|
||||
float pixelsPerUnit = 100.0f;
|
||||
};
|
||||
|
||||
struct PostFXSettings {
|
||||
@@ -122,9 +218,16 @@ struct ScriptSetting {
|
||||
std::string value;
|
||||
};
|
||||
|
||||
enum class ScriptLanguage {
|
||||
Cpp = 0,
|
||||
CSharp = 1
|
||||
};
|
||||
|
||||
struct ScriptComponent {
|
||||
bool enabled = true;
|
||||
ScriptLanguage language = ScriptLanguage::Cpp;
|
||||
std::string path;
|
||||
std::string managedType;
|
||||
std::vector<ScriptSetting> settings;
|
||||
std::string lastBinaryPath;
|
||||
std::vector<void*> activeIEnums; // function pointers registered via IEnum_Start
|
||||
@@ -169,9 +272,11 @@ struct PlayerControllerComponent {
|
||||
};
|
||||
|
||||
struct UIElementComponent {
|
||||
UIElementType type = UIElementType::None;
|
||||
UIAnchor anchor = UIAnchor::Center;
|
||||
glm::vec2 position = glm::vec2(0.0f); // offset in pixels from anchor
|
||||
glm::vec2 size = glm::vec2(160.0f, 40.0f);
|
||||
float rotation = 0.0f;
|
||||
float sliderValue = 0.5f;
|
||||
float sliderMin = 0.0f;
|
||||
float sliderMax = 1.0f;
|
||||
@@ -193,6 +298,37 @@ struct Rigidbody2DComponent {
|
||||
glm::vec2 velocity = glm::vec2(0.0f);
|
||||
};
|
||||
|
||||
enum class Collider2DType {
|
||||
Box = 0,
|
||||
Polygon = 1,
|
||||
Edge = 2
|
||||
};
|
||||
|
||||
struct Collider2DComponent {
|
||||
bool enabled = true;
|
||||
Collider2DType type = Collider2DType::Box;
|
||||
glm::vec2 boxSize = glm::vec2(1.0f);
|
||||
std::vector<glm::vec2> points;
|
||||
bool closed = false;
|
||||
float edgeThickness = 0.05f;
|
||||
};
|
||||
|
||||
struct ParallaxLayer2DComponent {
|
||||
bool enabled = true;
|
||||
int order = 0;
|
||||
float factor = 1.0f; // 1 = world locked, 0 = camera locked
|
||||
bool repeatX = false;
|
||||
bool repeatY = false;
|
||||
glm::vec2 repeatSpacing = glm::vec2(0.0f);
|
||||
};
|
||||
|
||||
struct CameraFollow2DComponent {
|
||||
bool enabled = true;
|
||||
int targetId = -1;
|
||||
glm::vec2 offset = glm::vec2(0.0f);
|
||||
float smoothTime = 0.0f; // seconds; 0 snaps to target
|
||||
};
|
||||
|
||||
struct AudioSourceComponent {
|
||||
bool enabled = true;
|
||||
std::string clipPath;
|
||||
@@ -202,6 +338,36 @@ struct AudioSourceComponent {
|
||||
bool spatial = true;
|
||||
float minDistance = 1.0f;
|
||||
float maxDistance = 25.0f;
|
||||
AudioRolloffMode rolloffMode = AudioRolloffMode::Logarithmic;
|
||||
float rolloff = 1.0f;
|
||||
float customMidDistance = 0.5f;
|
||||
float customMidGain = 0.6f;
|
||||
float customEndGain = 0.0f;
|
||||
};
|
||||
|
||||
struct ReverbZoneComponent {
|
||||
bool enabled = true;
|
||||
ReverbPreset preset = ReverbPreset::Room;
|
||||
ReverbZoneShape shape = ReverbZoneShape::Box;
|
||||
glm::vec3 boxSize = glm::vec3(6.0f);
|
||||
float radius = 6.0f;
|
||||
float blendDistance = 1.0f;
|
||||
float minDistance = 1.0f;
|
||||
float maxDistance = 15.0f;
|
||||
float room = -1000.0f; // dB
|
||||
float roomHF = -100.0f; // dB
|
||||
float roomLF = 0.0f; // dB
|
||||
float decayTime = 1.49f; // s
|
||||
float decayHFRatio = 0.83f; // 0.1..2
|
||||
float reflections = -2602.0f; // dB
|
||||
float reflectionsDelay = 0.007f; // s
|
||||
float reverb = 200.0f; // dB
|
||||
float reverbDelay = 0.011f; // s
|
||||
float hfReference = 5000.0f; // Hz
|
||||
float lfReference = 250.0f; // Hz
|
||||
float roomRolloffFactor = 0.0f;
|
||||
float diffusion = 100.0f; // 0..100
|
||||
float density = 100.0f; // 0..100
|
||||
};
|
||||
|
||||
class SceneObject {
|
||||
@@ -211,6 +377,12 @@ public:
|
||||
bool enabled = true;
|
||||
int layer = 0;
|
||||
std::string tag = "Untagged";
|
||||
bool hasRenderer = false;
|
||||
RenderType renderType = RenderType::None;
|
||||
bool hasLight = false;
|
||||
bool hasCamera = false;
|
||||
bool hasPostFX = false;
|
||||
bool hasUI = false;
|
||||
glm::vec3 position;
|
||||
glm::vec3 rotation;
|
||||
glm::vec3 scale;
|
||||
@@ -224,6 +396,7 @@ public:
|
||||
bool isExpanded = true;
|
||||
std::string meshPath; // Path to imported model file
|
||||
int meshId = -1; // Index into loaded mesh caches (OBJLoader / ModelLoader)
|
||||
int meshSourceIndex = -1; // Source mesh index for multi-mesh models
|
||||
MaterialProperties material;
|
||||
std::string materialPath; // Optional external material asset
|
||||
std::string albedoTexturePath;
|
||||
@@ -241,12 +414,24 @@ public:
|
||||
RigidbodyComponent rigidbody;
|
||||
bool hasRigidbody2D = false;
|
||||
Rigidbody2DComponent rigidbody2D;
|
||||
bool hasCollider2D = false;
|
||||
Collider2DComponent collider2D;
|
||||
bool hasParallaxLayer2D = false;
|
||||
ParallaxLayer2DComponent parallaxLayer2D;
|
||||
bool hasCameraFollow2D = false;
|
||||
CameraFollow2DComponent cameraFollow2D;
|
||||
bool hasCollider = false;
|
||||
ColliderComponent collider;
|
||||
bool hasPlayerController = false;
|
||||
PlayerControllerComponent playerController;
|
||||
bool hasAudioSource = false;
|
||||
AudioSourceComponent audioSource;
|
||||
bool hasReverbZone = false;
|
||||
ReverbZoneComponent reverbZone;
|
||||
bool hasAnimation = false;
|
||||
AnimationComponent animation;
|
||||
bool hasSkeletalAnimation = false;
|
||||
SkeletalAnimationComponent skeletal;
|
||||
UIElementComponent ui;
|
||||
|
||||
SceneObject(const std::string& name, ObjectType type, int id)
|
||||
@@ -261,3 +446,11 @@ public:
|
||||
localInitialized(true),
|
||||
id(id) {}
|
||||
};
|
||||
|
||||
inline bool HasRendererComponent(const SceneObject& obj) {
|
||||
return obj.hasRenderer && obj.renderType != RenderType::None;
|
||||
}
|
||||
|
||||
inline bool HasUIComponent(const SceneObject& obj) {
|
||||
return obj.hasUI && obj.ui.type != UIElementType::None;
|
||||
}
|
||||
|
||||
@@ -173,6 +173,16 @@ bool ScriptCompiler::makeCommands(const ScriptBuildConfig& config, const fs::pat
|
||||
relToScripts.clear();
|
||||
}
|
||||
|
||||
auto hasDotDot = [](const fs::path& path) {
|
||||
for (const auto& part : path) {
|
||||
if (part == "..") return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (relToScripts.empty() || relToScripts.is_absolute() || hasDotDot(relToScripts)) {
|
||||
relToScripts.clear();
|
||||
}
|
||||
|
||||
fs::path relativeParent = relToScripts.has_parent_path() ? relToScripts.parent_path() : fs::path();
|
||||
std::string baseName = scriptAbs.stem().string();
|
||||
fs::path objectPath = config.outDir / relativeParent / (baseName + ".o");
|
||||
@@ -246,10 +256,24 @@ bool ScriptCompiler::makeCommands(const ScriptBuildConfig& config, const fs::pat
|
||||
FunctionSpec testEditorSpec = detectFunction(scriptSource, "TestEditor");
|
||||
FunctionSpec updateSpec = detectFunction(scriptSource, "Update");
|
||||
FunctionSpec tickUpdateSpec = detectFunction(scriptSource, "TickUpdate");
|
||||
FunctionSpec inspectorSpec = detectFunction(scriptSource, "Script_OnInspector");
|
||||
|
||||
auto hasExternCInspector = [&]() {
|
||||
try {
|
||||
std::regex direct(R"(extern\s+"C"\s+void\s+Script_OnInspector\s*\()");
|
||||
if (std::regex_search(scriptSource, direct)) return true;
|
||||
std::regex block(R"(extern\s+"C"\s*\{[\s\S]*?\bScript_OnInspector\b)");
|
||||
return std::regex_search(scriptSource, block);
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
bool inspectorExtern = hasExternCInspector();
|
||||
bool needsInspectorWrap = inspectorSpec.present && !inspectorExtern;
|
||||
|
||||
fs::path wrapperPath;
|
||||
bool useWrapper = beginSpec.present || specSpec.present || testEditorSpec.present
|
||||
|| updateSpec.present || tickUpdateSpec.present;
|
||||
|| updateSpec.present || tickUpdateSpec.present || needsInspectorWrap;
|
||||
fs::path sourceToCompile = scriptAbs;
|
||||
|
||||
if (useWrapper) {
|
||||
@@ -264,8 +288,15 @@ bool ScriptCompiler::makeCommands(const ScriptBuildConfig& config, const fs::pat
|
||||
}
|
||||
|
||||
std::string includePath = scriptAbs.lexically_normal().generic_string();
|
||||
if (needsInspectorWrap) {
|
||||
wrapper << "#define Script_OnInspector Script_OnInspector_Impl\n";
|
||||
}
|
||||
wrapper << "#include \"ScriptRuntime.h\"\n";
|
||||
wrapper << "#include \"" << includePath << "\"\n\n";
|
||||
wrapper << "#include \"" << includePath << "\"\n";
|
||||
if (needsInspectorWrap) {
|
||||
wrapper << "#undef Script_OnInspector\n";
|
||||
}
|
||||
wrapper << "\n";
|
||||
wrapper << "extern \"C\" {\n";
|
||||
|
||||
auto emitWrapper = [&](const char* exportedName, const char* implName,
|
||||
@@ -293,6 +324,16 @@ bool ScriptCompiler::makeCommands(const ScriptBuildConfig& config, const fs::pat
|
||||
emitWrapper("Script_TestEditor", "TestEditor", testEditorSpec);
|
||||
emitWrapper("Script_Update", "Update", updateSpec);
|
||||
emitWrapper("Script_TickUpdate", "TickUpdate", tickUpdateSpec);
|
||||
if (needsInspectorWrap) {
|
||||
wrapper << "void Script_OnInspector(ScriptContext& ctx) {\n";
|
||||
if (inspectorSpec.takesContext) {
|
||||
wrapper << " Script_OnInspector_Impl(ctx);\n";
|
||||
} else {
|
||||
wrapper << " (void)ctx;\n";
|
||||
wrapper << " Script_OnInspector_Impl();\n";
|
||||
}
|
||||
wrapper << "}\n\n";
|
||||
}
|
||||
|
||||
wrapper << "}\n";
|
||||
sourceToCompile = wrapperPath;
|
||||
|
||||
@@ -43,13 +43,8 @@ std::string trimString(const std::string& input) {
|
||||
return input.substr(start, end - start);
|
||||
}
|
||||
|
||||
bool isUIObjectType(ObjectType type) {
|
||||
return type == ObjectType::Canvas ||
|
||||
type == ObjectType::UIImage ||
|
||||
type == ObjectType::UISlider ||
|
||||
type == ObjectType::UIButton ||
|
||||
type == ObjectType::UIText ||
|
||||
type == ObjectType::Sprite2D;
|
||||
bool isUIObject(const SceneObject* obj) {
|
||||
return obj && HasUIComponent(*obj);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +340,7 @@ void ScriptContext::TickStandaloneMovement(StandaloneMovementState& state, Stand
|
||||
}
|
||||
|
||||
bool ScriptContext::IsUIButtonPressed() const {
|
||||
return object && object->type == ObjectType::UIButton && object->ui.buttonPressed;
|
||||
return object && object->hasUI && object->ui.type == UIElementType::Button && object->ui.buttonPressed;
|
||||
}
|
||||
|
||||
bool ScriptContext::IsUIInteractable() const {
|
||||
@@ -361,12 +356,12 @@ void ScriptContext::SetUIInteractable(bool interactable) {
|
||||
}
|
||||
|
||||
float ScriptContext::GetUISliderValue() const {
|
||||
if (!object || object->type != ObjectType::UISlider) return 0.0f;
|
||||
if (!object || !object->hasUI || object->ui.type != UIElementType::Slider) return 0.0f;
|
||||
return object->ui.sliderValue;
|
||||
}
|
||||
|
||||
void ScriptContext::SetUISliderValue(float value) {
|
||||
if (!object || object->type != ObjectType::UISlider) return;
|
||||
if (!object || !object->hasUI || object->ui.type != UIElementType::Slider) return;
|
||||
float clamped = std::clamp(value, object->ui.sliderMin, object->ui.sliderMax);
|
||||
if (object->ui.sliderValue != clamped) {
|
||||
object->ui.sliderValue = clamped;
|
||||
@@ -375,7 +370,7 @@ void ScriptContext::SetUISliderValue(float value) {
|
||||
}
|
||||
|
||||
void ScriptContext::SetUISliderRange(float minValue, float maxValue) {
|
||||
if (!object || object->type != ObjectType::UISlider) return;
|
||||
if (!object || !object->hasUI || object->ui.type != UIElementType::Slider) return;
|
||||
if (maxValue < minValue) std::swap(minValue, maxValue);
|
||||
object->ui.sliderMin = minValue;
|
||||
object->ui.sliderMax = maxValue;
|
||||
@@ -400,12 +395,12 @@ void ScriptContext::SetUIColor(const glm::vec4& color) {
|
||||
}
|
||||
|
||||
float ScriptContext::GetUITextScale() const {
|
||||
if (!object || object->type != ObjectType::UIText) return 1.0f;
|
||||
if (!object || !object->hasUI || object->ui.type != UIElementType::Text) return 1.0f;
|
||||
return object->ui.textScale;
|
||||
}
|
||||
|
||||
void ScriptContext::SetUITextScale(float scale) {
|
||||
if (!object || object->type != ObjectType::UIText) return;
|
||||
if (!object || !object->hasUI || object->ui.type != UIElementType::Text) return;
|
||||
float clamped = std::max(0.1f, scale);
|
||||
if (object->ui.textScale != clamped) {
|
||||
object->ui.textScale = clamped;
|
||||
@@ -414,7 +409,7 @@ void ScriptContext::SetUITextScale(float scale) {
|
||||
}
|
||||
|
||||
void ScriptContext::SetUISliderStyle(UISliderStyle style) {
|
||||
if (!object || object->type != ObjectType::UISlider) return;
|
||||
if (!object || !object->hasUI || object->ui.type != UIElementType::Slider) return;
|
||||
if (object->ui.sliderStyle != style) {
|
||||
object->ui.sliderStyle = style;
|
||||
MarkDirty();
|
||||
@@ -422,7 +417,7 @@ void ScriptContext::SetUISliderStyle(UISliderStyle style) {
|
||||
}
|
||||
|
||||
void ScriptContext::SetUIButtonStyle(UIButtonStyle style) {
|
||||
if (!object || object->type != ObjectType::UIButton) return;
|
||||
if (!object || !object->hasUI || object->ui.type != UIElementType::Button) return;
|
||||
if (object->ui.buttonStyle != style) {
|
||||
object->ui.buttonStyle = style;
|
||||
MarkDirty();
|
||||
@@ -454,7 +449,7 @@ bool ScriptContext::HasRigidbody() const {
|
||||
}
|
||||
|
||||
bool ScriptContext::HasRigidbody2D() const {
|
||||
return object && isUIObjectType(object->type) && object->hasRigidbody2D && object->rigidbody2D.enabled;
|
||||
return isUIObject(object) && object->hasRigidbody2D && object->rigidbody2D.enabled;
|
||||
}
|
||||
|
||||
bool ScriptContext::EnsureCapsuleCollider(float height, float radius) {
|
||||
|
||||
@@ -138,3 +138,9 @@ void Shader::setMat4(const std::string &name, const glm::mat4 &mat) const
|
||||
{
|
||||
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, glm::value_ptr(mat));
|
||||
}
|
||||
|
||||
void Shader::setMat4Array(const std::string &name, const glm::mat4 *data, int count) const
|
||||
{
|
||||
if (count <= 0 || !data) return;
|
||||
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), count, GL_FALSE, glm::value_ptr(data[0]));
|
||||
}
|
||||
|
||||
3224
src/ThirdParty/ImGuiColorTextEdit/TextEditor.cpp
vendored
Normal file
3224
src/ThirdParty/ImGuiColorTextEdit/TextEditor.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
401
src/ThirdParty/ImGuiColorTextEdit/TextEditor.h
vendored
Normal file
401
src/ThirdParty/ImGuiColorTextEdit/TextEditor.h
vendored
Normal file
@@ -0,0 +1,401 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include "imgui.h"
|
||||
|
||||
class TextEditor
|
||||
{
|
||||
public:
|
||||
enum class PaletteIndex
|
||||
{
|
||||
Default,
|
||||
Keyword,
|
||||
Number,
|
||||
String,
|
||||
CharLiteral,
|
||||
Punctuation,
|
||||
Preprocessor,
|
||||
Identifier,
|
||||
KnownIdentifier,
|
||||
PreprocIdentifier,
|
||||
Comment,
|
||||
MultiLineComment,
|
||||
Background,
|
||||
Cursor,
|
||||
Selection,
|
||||
ErrorMarker,
|
||||
Breakpoint,
|
||||
LineNumber,
|
||||
CurrentLineFill,
|
||||
CurrentLineFillInactive,
|
||||
CurrentLineEdge,
|
||||
Max
|
||||
};
|
||||
|
||||
enum class SelectionMode
|
||||
{
|
||||
Normal,
|
||||
Word,
|
||||
Line
|
||||
};
|
||||
|
||||
struct Breakpoint
|
||||
{
|
||||
int mLine;
|
||||
bool mEnabled;
|
||||
std::string mCondition;
|
||||
|
||||
Breakpoint()
|
||||
: mLine(-1)
|
||||
, mEnabled(false)
|
||||
{}
|
||||
};
|
||||
|
||||
// Represents a character coordinate from the user's point of view,
|
||||
// i. e. consider an uniform grid (assuming fixed-width font) on the
|
||||
// screen as it is rendered, and each cell has its own coordinate, starting from 0.
|
||||
// Tabs are counted as [1..mTabSize] count empty spaces, depending on
|
||||
// how many space is necessary to reach the next tab stop.
|
||||
// For example, coordinate (1, 5) represents the character 'B' in a line "\tABC", when mTabSize = 4,
|
||||
// because it is rendered as " ABC" on the screen.
|
||||
struct Coordinates
|
||||
{
|
||||
int mLine, mColumn;
|
||||
Coordinates() : mLine(0), mColumn(0) {}
|
||||
Coordinates(int aLine, int aColumn) : mLine(aLine), mColumn(aColumn)
|
||||
{
|
||||
assert(aLine >= 0);
|
||||
assert(aColumn >= 0);
|
||||
}
|
||||
static Coordinates Invalid() { static Coordinates invalid(-1, -1); return invalid; }
|
||||
|
||||
bool operator ==(const Coordinates& o) const
|
||||
{
|
||||
return
|
||||
mLine == o.mLine &&
|
||||
mColumn == o.mColumn;
|
||||
}
|
||||
|
||||
bool operator !=(const Coordinates& o) const
|
||||
{
|
||||
return
|
||||
mLine != o.mLine ||
|
||||
mColumn != o.mColumn;
|
||||
}
|
||||
|
||||
bool operator <(const Coordinates& o) const
|
||||
{
|
||||
if (mLine != o.mLine)
|
||||
return mLine < o.mLine;
|
||||
return mColumn < o.mColumn;
|
||||
}
|
||||
|
||||
bool operator >(const Coordinates& o) const
|
||||
{
|
||||
if (mLine != o.mLine)
|
||||
return mLine > o.mLine;
|
||||
return mColumn > o.mColumn;
|
||||
}
|
||||
|
||||
bool operator <=(const Coordinates& o) const
|
||||
{
|
||||
if (mLine != o.mLine)
|
||||
return mLine < o.mLine;
|
||||
return mColumn <= o.mColumn;
|
||||
}
|
||||
|
||||
bool operator >=(const Coordinates& o) const
|
||||
{
|
||||
if (mLine != o.mLine)
|
||||
return mLine > o.mLine;
|
||||
return mColumn >= o.mColumn;
|
||||
}
|
||||
};
|
||||
|
||||
struct Identifier
|
||||
{
|
||||
Coordinates mLocation;
|
||||
std::string mDeclaration;
|
||||
};
|
||||
|
||||
typedef std::string String;
|
||||
typedef std::unordered_map<std::string, Identifier> Identifiers;
|
||||
typedef std::unordered_set<std::string> Keywords;
|
||||
typedef std::map<int, std::string> ErrorMarkers;
|
||||
typedef std::unordered_set<int> Breakpoints;
|
||||
typedef std::array<ImU32, (unsigned)PaletteIndex::Max> Palette;
|
||||
typedef uint8_t Char;
|
||||
|
||||
struct Glyph
|
||||
{
|
||||
Char mChar;
|
||||
PaletteIndex mColorIndex = PaletteIndex::Default;
|
||||
bool mComment : 1;
|
||||
bool mMultiLineComment : 1;
|
||||
bool mPreprocessor : 1;
|
||||
|
||||
Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex),
|
||||
mComment(false), mMultiLineComment(false), mPreprocessor(false) {}
|
||||
};
|
||||
|
||||
typedef std::vector<Glyph> Line;
|
||||
typedef std::vector<Line> Lines;
|
||||
|
||||
struct LanguageDefinition
|
||||
{
|
||||
typedef std::pair<std::string, PaletteIndex> TokenRegexString;
|
||||
typedef std::vector<TokenRegexString> TokenRegexStrings;
|
||||
typedef bool(*TokenizeCallback)(const char * in_begin, const char * in_end, const char *& out_begin, const char *& out_end, PaletteIndex & paletteIndex);
|
||||
|
||||
std::string mName;
|
||||
Keywords mKeywords;
|
||||
Identifiers mIdentifiers;
|
||||
Identifiers mPreprocIdentifiers;
|
||||
std::string mCommentStart, mCommentEnd, mSingleLineComment;
|
||||
char mPreprocChar;
|
||||
bool mAutoIndentation;
|
||||
|
||||
TokenizeCallback mTokenize;
|
||||
|
||||
TokenRegexStrings mTokenRegexStrings;
|
||||
|
||||
bool mCaseSensitive;
|
||||
|
||||
LanguageDefinition()
|
||||
: mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true)
|
||||
{
|
||||
}
|
||||
|
||||
static const LanguageDefinition& CPlusPlus();
|
||||
static const LanguageDefinition& HLSL();
|
||||
static const LanguageDefinition& GLSL();
|
||||
static const LanguageDefinition& C();
|
||||
static const LanguageDefinition& SQL();
|
||||
static const LanguageDefinition& AngelScript();
|
||||
static const LanguageDefinition& Lua();
|
||||
};
|
||||
|
||||
TextEditor();
|
||||
~TextEditor();
|
||||
|
||||
void SetLanguageDefinition(const LanguageDefinition& aLanguageDef);
|
||||
const LanguageDefinition& GetLanguageDefinition() const { return mLanguageDefinition; }
|
||||
|
||||
const Palette& GetPalette() const { return mPaletteBase; }
|
||||
void SetPalette(const Palette& aValue);
|
||||
|
||||
void SetErrorMarkers(const ErrorMarkers& aMarkers) { mErrorMarkers = aMarkers; }
|
||||
void SetBreakpoints(const Breakpoints& aMarkers) { mBreakpoints = aMarkers; }
|
||||
|
||||
void Render(const char* aTitle, const ImVec2& aSize = ImVec2(), bool aBorder = false);
|
||||
void SetText(const std::string& aText);
|
||||
std::string GetText() const;
|
||||
|
||||
void SetTextLines(const std::vector<std::string>& aLines);
|
||||
std::vector<std::string> GetTextLines() const;
|
||||
|
||||
std::string GetSelectedText() const;
|
||||
std::string GetCurrentLineText()const;
|
||||
|
||||
int GetTotalLines() const { return (int)mLines.size(); }
|
||||
bool IsOverwrite() const { return mOverwrite; }
|
||||
|
||||
void SetReadOnly(bool aValue);
|
||||
bool IsReadOnly() const { return mReadOnly; }
|
||||
bool IsTextChanged() const { return mTextChanged; }
|
||||
bool IsCursorPositionChanged() const { return mCursorPositionChanged; }
|
||||
|
||||
bool IsColorizerEnabled() const { return mColorizerEnabled; }
|
||||
void SetColorizerEnable(bool aValue);
|
||||
|
||||
Coordinates GetCursorPosition() const { return GetActualCursorCoordinates(); }
|
||||
void SetCursorPosition(const Coordinates& aPosition);
|
||||
|
||||
inline void SetHandleMouseInputs (bool aValue){ mHandleMouseInputs = aValue;}
|
||||
inline bool IsHandleMouseInputsEnabled() const { return mHandleKeyboardInputs; }
|
||||
|
||||
inline void SetHandleKeyboardInputs (bool aValue){ mHandleKeyboardInputs = aValue;}
|
||||
inline bool IsHandleKeyboardInputsEnabled() const { return mHandleKeyboardInputs; }
|
||||
inline void SetAllowTabInput(bool aValue) { mAllowTabInput = aValue; }
|
||||
inline bool IsTabInputAllowed() const { return mAllowTabInput; }
|
||||
inline void SetSmartTabDelete(bool aValue) { mSmartTabDelete = aValue; }
|
||||
inline bool IsSmartTabDeleteEnabled() const { return mSmartTabDelete; }
|
||||
|
||||
inline void SetImGuiChildIgnored (bool aValue){ mIgnoreImGuiChild = aValue;}
|
||||
inline bool IsImGuiChildIgnored() const { return mIgnoreImGuiChild; }
|
||||
|
||||
inline void SetShowWhitespaces(bool aValue) { mShowWhitespaces = aValue; }
|
||||
inline bool IsShowingWhitespaces() const { return mShowWhitespaces; }
|
||||
|
||||
void SetTabSize(int aValue);
|
||||
inline int GetTabSize() const { return mTabSize; }
|
||||
|
||||
void InsertText(const std::string& aValue);
|
||||
void InsertText(const char* aValue);
|
||||
|
||||
void MoveUp(int aAmount = 1, bool aSelect = false);
|
||||
void MoveDown(int aAmount = 1, bool aSelect = false);
|
||||
void MoveLeft(int aAmount = 1, bool aSelect = false, bool aWordMode = false);
|
||||
void MoveRight(int aAmount = 1, bool aSelect = false, bool aWordMode = false);
|
||||
void MoveTop(bool aSelect = false);
|
||||
void MoveBottom(bool aSelect = false);
|
||||
void MoveHome(bool aSelect = false);
|
||||
void MoveEnd(bool aSelect = false);
|
||||
|
||||
void SetSelectionStart(const Coordinates& aPosition);
|
||||
void SetSelectionEnd(const Coordinates& aPosition);
|
||||
void SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode = SelectionMode::Normal);
|
||||
std::string GetWordUnderCursorPublic() const;
|
||||
std::string GetWordAtPublic(const Coordinates& aCoords) const;
|
||||
ImVec2 GetCursorScreenPositionPublic() const { return mCursorScreenPos; }
|
||||
bool HasCursorScreenPosition() const { return mCursorScreenPosValid; }
|
||||
void SelectWordUnderCursor();
|
||||
void SelectAll();
|
||||
bool HasSelection() const;
|
||||
|
||||
void Copy();
|
||||
void Cut();
|
||||
void Paste();
|
||||
void Delete();
|
||||
|
||||
bool CanUndo() const;
|
||||
bool CanRedo() const;
|
||||
void Undo(int aSteps = 1);
|
||||
void Redo(int aSteps = 1);
|
||||
|
||||
static const Palette& GetDarkPalette();
|
||||
static const Palette& GetLightPalette();
|
||||
static const Palette& GetRetroBluePalette();
|
||||
|
||||
private:
|
||||
typedef std::vector<std::pair<std::regex, PaletteIndex>> RegexList;
|
||||
|
||||
struct EditorState
|
||||
{
|
||||
Coordinates mSelectionStart;
|
||||
Coordinates mSelectionEnd;
|
||||
Coordinates mCursorPosition;
|
||||
};
|
||||
|
||||
class UndoRecord
|
||||
{
|
||||
public:
|
||||
UndoRecord() {}
|
||||
~UndoRecord() {}
|
||||
|
||||
UndoRecord(
|
||||
const std::string& aAdded,
|
||||
const TextEditor::Coordinates aAddedStart,
|
||||
const TextEditor::Coordinates aAddedEnd,
|
||||
|
||||
const std::string& aRemoved,
|
||||
const TextEditor::Coordinates aRemovedStart,
|
||||
const TextEditor::Coordinates aRemovedEnd,
|
||||
|
||||
TextEditor::EditorState& aBefore,
|
||||
TextEditor::EditorState& aAfter);
|
||||
|
||||
void Undo(TextEditor* aEditor);
|
||||
void Redo(TextEditor* aEditor);
|
||||
|
||||
std::string mAdded;
|
||||
Coordinates mAddedStart;
|
||||
Coordinates mAddedEnd;
|
||||
|
||||
std::string mRemoved;
|
||||
Coordinates mRemovedStart;
|
||||
Coordinates mRemovedEnd;
|
||||
|
||||
EditorState mBefore;
|
||||
EditorState mAfter;
|
||||
};
|
||||
|
||||
typedef std::vector<UndoRecord> UndoBuffer;
|
||||
|
||||
void ProcessInputs();
|
||||
void Colorize(int aFromLine = 0, int aCount = -1);
|
||||
void ColorizeRange(int aFromLine = 0, int aToLine = 0);
|
||||
void ColorizeInternal();
|
||||
float TextDistanceToLineStart(const Coordinates& aFrom) const;
|
||||
void EnsureCursorVisible();
|
||||
int GetPageSize() const;
|
||||
std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const;
|
||||
Coordinates GetActualCursorCoordinates() const;
|
||||
Coordinates SanitizeCoordinates(const Coordinates& aValue) const;
|
||||
void Advance(Coordinates& aCoordinates) const;
|
||||
void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd);
|
||||
int InsertTextAt(Coordinates& aWhere, const char* aValue);
|
||||
void AddUndo(UndoRecord& aValue);
|
||||
Coordinates ScreenPosToCoordinates(const ImVec2& aPosition) const;
|
||||
Coordinates FindWordStart(const Coordinates& aFrom) const;
|
||||
Coordinates FindWordEnd(const Coordinates& aFrom) const;
|
||||
Coordinates FindNextWord(const Coordinates& aFrom) const;
|
||||
int GetCharacterIndex(const Coordinates& aCoordinates) const;
|
||||
int GetCharacterColumn(int aLine, int aIndex) const;
|
||||
int GetLineCharacterCount(int aLine) const;
|
||||
int GetLineMaxColumn(int aLine) const;
|
||||
bool IsOnWordBoundary(const Coordinates& aAt) const;
|
||||
void RemoveLine(int aStart, int aEnd);
|
||||
void RemoveLine(int aIndex);
|
||||
Line& InsertLine(int aIndex);
|
||||
void EnterCharacter(ImWchar aChar, bool aShift);
|
||||
void Backspace();
|
||||
void DeleteSelection();
|
||||
std::string GetWordUnderCursor() const;
|
||||
std::string GetWordAt(const Coordinates& aCoords) const;
|
||||
ImU32 GetGlyphColor(const Glyph& aGlyph) const;
|
||||
|
||||
void HandleKeyboardInputs();
|
||||
void HandleMouseInputs();
|
||||
void Render();
|
||||
|
||||
float mLineSpacing;
|
||||
Lines mLines;
|
||||
EditorState mState;
|
||||
UndoBuffer mUndoBuffer;
|
||||
int mUndoIndex;
|
||||
|
||||
int mTabSize;
|
||||
bool mOverwrite;
|
||||
bool mReadOnly;
|
||||
bool mWithinRender;
|
||||
bool mScrollToCursor;
|
||||
bool mScrollToTop;
|
||||
bool mTextChanged;
|
||||
bool mColorizerEnabled;
|
||||
float mTextStart; // position (in pixels) where a code line starts relative to the left of the TextEditor.
|
||||
int mLeftMargin;
|
||||
bool mCursorPositionChanged;
|
||||
int mColorRangeMin, mColorRangeMax;
|
||||
SelectionMode mSelectionMode;
|
||||
bool mHandleKeyboardInputs;
|
||||
bool mHandleMouseInputs;
|
||||
bool mAllowTabInput;
|
||||
bool mSmartTabDelete;
|
||||
ImVec2 mCursorScreenPos;
|
||||
bool mCursorScreenPosValid;
|
||||
bool mIgnoreImGuiChild;
|
||||
bool mShowWhitespaces;
|
||||
|
||||
Palette mPaletteBase;
|
||||
Palette mPalette;
|
||||
LanguageDefinition mLanguageDefinition;
|
||||
RegexList mRegexList;
|
||||
|
||||
bool mCheckComments;
|
||||
Breakpoints mBreakpoints;
|
||||
ErrorMarkers mErrorMarkers;
|
||||
ImVec2 mCharAdvance;
|
||||
Coordinates mInteractiveStart, mInteractiveEnd;
|
||||
std::string mLineBuffer;
|
||||
uint64_t mStartTime;
|
||||
|
||||
float mLastClick;
|
||||
};
|
||||
145
src/ThirdParty/imgui/imgui.cpp
vendored
145
src/ThirdParty/imgui/imgui.cpp
vendored
@@ -1288,6 +1288,7 @@ static const float DOCKING_TRANSPARENT_PAYLOAD_ALPHA = 0.50f; // For u
|
||||
static void SetCurrentWindow(ImGuiWindow* window);
|
||||
static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags);
|
||||
static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window);
|
||||
static float DockAnimEaseSmooth(float t);
|
||||
|
||||
static void AddWindowToSortBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window);
|
||||
|
||||
@@ -4603,6 +4604,12 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NUL
|
||||
FontWindowScale = FontWindowScaleParents = 1.0f;
|
||||
SettingsOffset = -1;
|
||||
DockOrder = -1;
|
||||
DockAnimStartTime = 0.0f;
|
||||
DockAnimDuration = 0.0f;
|
||||
DockAnimActive = false;
|
||||
DockAnimOvershoot = false;
|
||||
DockAnimGrabRatio = ImVec2(0.5f, 0.5f);
|
||||
DockAnimOvershootStrength = 1.0f;
|
||||
DrawList = &DrawListInst;
|
||||
DrawList->_OwnerName = Name;
|
||||
DrawList->_SetDrawListSharedData(&Ctx->DrawListSharedData);
|
||||
@@ -5252,6 +5259,12 @@ ImDrawListSharedData* ImGui::GetDrawListSharedData()
|
||||
return &GImGui->DrawListSharedData;
|
||||
}
|
||||
|
||||
namespace ImGui
|
||||
{
|
||||
ImGuiDockNode* DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node);
|
||||
void DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window);
|
||||
}
|
||||
|
||||
void ImGui::StartMouseMovingWindow(ImGuiWindow* window)
|
||||
{
|
||||
// Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate hover on other windows.
|
||||
@@ -5294,7 +5307,12 @@ void ImGui::StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* nod
|
||||
const bool clicked = IsMouseClicked(0);
|
||||
const bool dragging = IsMouseDragging(0);
|
||||
if (can_undock_node && dragging)
|
||||
DockContextQueueUndockNode(&g, node); // Will lead to DockNodeStartMouseMovingWindow() -> StartMouseMovingWindow() being called next frame
|
||||
{
|
||||
ImGuiDockNode* undocked = DockContextProcessUndockNode(&g, node);
|
||||
if (undocked && undocked->Windows.Size > 0)
|
||||
DockNodeStartMouseMovingWindow(undocked, undocked->Windows[0]);
|
||||
return;
|
||||
}
|
||||
else if (!can_undock_node && (clicked || dragging) && g.MovingWindow != window)
|
||||
StartMouseMovingWindow(window);
|
||||
}
|
||||
@@ -5348,7 +5366,30 @@ void ImGui::UpdateMouseMovingWindowNewFrame()
|
||||
const bool window_disappeared = (!moving_window->WasActive && !moving_window->Active);
|
||||
if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos) && !window_disappeared)
|
||||
{
|
||||
ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
|
||||
ImVec2 pos_target = g.IO.MousePos - g.ActiveIdClickOffset;
|
||||
ImVec2 pos = pos_target;
|
||||
if (moving_window->DockAnimActive && moving_window->DockAnimOvershoot)
|
||||
{
|
||||
const float elapsed = (float)(g.Time - moving_window->DockAnimStartTime);
|
||||
const float duration = ImMax(0.001f, moving_window->DockAnimDuration);
|
||||
float t = ImSaturate(elapsed / duration);
|
||||
const float ease_out = DockAnimEaseSmooth(t);
|
||||
const float s = 1.70158f * moving_window->DockAnimOvershootStrength;
|
||||
const float t1 = t - 1.0f;
|
||||
float ease_t = 1.0f + (t1 * t1) * ((s + 1.0f) * t1 + s);
|
||||
ImVec2 anim_size = ImLerp(moving_window->DockAnimFromSize, moving_window->DockAnimToSize, ease_t);
|
||||
if (anim_size.x > 0.0f && anim_size.y > 0.0f)
|
||||
{
|
||||
moving_window->SizeFull = anim_size;
|
||||
moving_window->Size = anim_size;
|
||||
ImVec2 pos_target_anim = g.IO.MousePos - moving_window->DockAnimGrabRatio * anim_size;
|
||||
pos = ImLerp(moving_window->DockAnimFromPos, pos_target_anim, ease_out);
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = ImLerp(moving_window->DockAnimFromPos, pos_target, ease_out);
|
||||
}
|
||||
}
|
||||
if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y)
|
||||
{
|
||||
SetWindowPos(moving_window, pos, ImGuiCond_Always);
|
||||
@@ -8141,6 +8182,29 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
|
||||
}
|
||||
window->Pos = ImTrunc(window->Pos);
|
||||
|
||||
if (window->DockAnimActive)
|
||||
{
|
||||
const float elapsed = (float)(g.Time - window->DockAnimStartTime);
|
||||
const float duration = ImMax(0.001f, window->DockAnimDuration);
|
||||
float t = ImSaturate(elapsed / duration);
|
||||
float ease_t = DockAnimEaseSmooth(t);
|
||||
if (window->DockAnimOvershoot)
|
||||
{
|
||||
const float s = 1.70158f * window->DockAnimOvershootStrength;
|
||||
const float t1 = t - 1.0f;
|
||||
ease_t = 1.0f + (t1 * t1) * ((s + 1.0f) * t1 + s);
|
||||
}
|
||||
const ImVec2 from_center = window->DockAnimFromPos + window->DockAnimFromSize * 0.5f;
|
||||
const ImVec2 to_center = window->DockAnimToPos + window->DockAnimToSize * 0.5f;
|
||||
const ImVec2 center = ImLerp(from_center, to_center, t);
|
||||
const ImVec2 size = ImLerp(window->DockAnimFromSize, window->DockAnimToSize, ease_t);
|
||||
window->SizeFull = size;
|
||||
window->Pos = center - size * 0.5f;
|
||||
window->Size = (window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow)) ? window->TitleBarRect().GetSize() : window->SizeFull;
|
||||
if (t >= 1.0f)
|
||||
window->DockAnimActive = false;
|
||||
}
|
||||
|
||||
// Lock window rounding for the frame (so that altering them doesn't cause inconsistencies)
|
||||
// Large values tend to lead to variety of artifacts and are not recommended.
|
||||
if (window->ViewportOwned || window->DockIsActive)
|
||||
@@ -17540,6 +17604,7 @@ namespace ImGui
|
||||
static void DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node);
|
||||
static void DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node);
|
||||
static void DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req);
|
||||
ImGuiDockNode* DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node);
|
||||
static void DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx);
|
||||
static ImGuiDockNode* DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window);
|
||||
static void DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count);
|
||||
@@ -17562,7 +17627,7 @@ namespace ImGui
|
||||
static void DockNodeRemoveTabBar(ImGuiDockNode* node);
|
||||
static void DockNodeWindowMenuUpdate(ImGuiDockNode* node, ImGuiTabBar* tab_bar);
|
||||
static void DockNodeUpdateVisibleFlag(ImGuiDockNode* node);
|
||||
static void DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window);
|
||||
void DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window);
|
||||
static bool DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* payload_window);
|
||||
static void DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDockPreviewData* preview_data, bool is_explicit_target, bool is_outer_docking);
|
||||
static void DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, const ImGuiDockPreviewData* preview_data);
|
||||
@@ -18003,6 +18068,27 @@ void ImGui::DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode*
|
||||
req.Type = ImGuiDockRequestType_None;
|
||||
}
|
||||
|
||||
static float DockAnimEaseSmooth(float t)
|
||||
{
|
||||
t = ImSaturate(t);
|
||||
return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
|
||||
}
|
||||
|
||||
static void DockWindowQueueScaleAnim(ImGuiContext* ctx, ImGuiWindow* window, const ImVec2& from_pos, const ImVec2& from_size, const ImVec2& to_pos, const ImVec2& to_size, bool overshoot, float duration, float overshoot_strength)
|
||||
{
|
||||
if (to_size.x <= 0.0f || to_size.y <= 0.0f)
|
||||
return;
|
||||
window->DockAnimFromPos = from_pos;
|
||||
window->DockAnimFromSize = from_size;
|
||||
window->DockAnimToPos = to_pos;
|
||||
window->DockAnimToSize = to_size;
|
||||
window->DockAnimStartTime = (float)ctx->Time;
|
||||
window->DockAnimDuration = duration;
|
||||
window->DockAnimActive = true;
|
||||
window->DockAnimOvershoot = overshoot;
|
||||
window->DockAnimOvershootStrength = overshoot_strength;
|
||||
}
|
||||
|
||||
void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req)
|
||||
{
|
||||
IM_ASSERT((req->Type == ImGuiDockRequestType_Dock && req->DockPayload != NULL) || (req->Type == ImGuiDockRequestType_Split && req->DockPayload == NULL));
|
||||
@@ -18014,6 +18100,15 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req)
|
||||
ImGuiWindow* payload_window = req->DockPayload; // Optional
|
||||
ImGuiWindow* target_window = req->DockTargetWindow;
|
||||
ImGuiDockNode* node = req->DockTargetNode;
|
||||
ImVector<ImGuiWindow*> payload_windows;
|
||||
if (payload_window)
|
||||
{
|
||||
payload_windows.push_back(payload_window);
|
||||
}
|
||||
else if (req->DockTargetNode == NULL && req->DockTargetWindow != NULL && req->DockSplitDir != ImGuiDir_None)
|
||||
{
|
||||
// no-op, payload_node path handled below
|
||||
}
|
||||
if (payload_window)
|
||||
IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessDock node 0x%08X target '%s' dock window '%s', split_dir %d\n", node ? node->ID : 0, target_window ? target_window->Name : "NULL", payload_window->Name, req->DockSplitDir);
|
||||
else
|
||||
@@ -18030,6 +18125,11 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req)
|
||||
next_selected_id = payload_node->TabBar->NextSelectedTabId ? payload_node->TabBar->NextSelectedTabId : payload_node->TabBar->SelectedTabId;
|
||||
if (payload_node == NULL)
|
||||
next_selected_id = payload_window->TabId;
|
||||
if (payload_node)
|
||||
{
|
||||
for (ImGuiWindow* window : payload_node->Windows)
|
||||
payload_windows.push_back(window);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME-DOCK: When we are trying to dock an existing single-window node into a loose window, transfer Node ID as well
|
||||
@@ -18138,6 +18238,24 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req)
|
||||
// Update selection immediately
|
||||
if (ImGuiTabBar* tab_bar = node->TabBar)
|
||||
tab_bar->NextSelectedTabId = next_selected_id;
|
||||
const ImVec2 target_pos = node->Pos;
|
||||
const ImVec2 target_size = node->Size;
|
||||
for (ImGuiWindow* window : payload_windows)
|
||||
{
|
||||
const ImVec2 from_pos = window->Pos;
|
||||
const ImVec2 from_size = window->SizeFull;
|
||||
const ImVec2 size_delta = target_size - from_size;
|
||||
const ImVec2 pos_delta = target_pos - from_pos;
|
||||
const float dist = size_delta.x * size_delta.x + size_delta.y * size_delta.y + pos_delta.x * pos_delta.x + pos_delta.y * pos_delta.y;
|
||||
if (dist > 1.0f)
|
||||
DockWindowQueueScaleAnim(ctx, window, from_pos, from_size, target_pos, target_size, true, 0.20f, 0.75f);
|
||||
else
|
||||
{
|
||||
const ImVec2 scaled = target_size * 0.92f;
|
||||
const ImVec2 centered = target_pos + (target_size - scaled) * 0.5f;
|
||||
DockWindowQueueScaleAnim(ctx, window, centered, scaled, target_pos, target_size, true, 0.20f, 0.75f);
|
||||
}
|
||||
}
|
||||
MarkIniSettingsDirty();
|
||||
}
|
||||
|
||||
@@ -18168,6 +18286,12 @@ void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* windo
|
||||
{
|
||||
ImGuiContext& g = *ctx;
|
||||
IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessUndockWindow window '%s', clear_persistent_docking_ref = %d\n", window->Name, clear_persistent_docking_ref);
|
||||
const ImVec2 from_pos = window->Pos;
|
||||
const ImVec2 from_size = window->SizeFull;
|
||||
if (from_size.x > 0.0f && from_size.y > 0.0f)
|
||||
window->DockAnimGrabRatio = g.ActiveIdClickOffset / from_size;
|
||||
else
|
||||
window->DockAnimGrabRatio = ImVec2(0.5f, 0.5f);
|
||||
if (window->DockNode)
|
||||
DockNodeRemoveWindow(window->DockNode, window, clear_persistent_docking_ref ? 0 : window->DockId);
|
||||
else
|
||||
@@ -18176,11 +18300,17 @@ void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* windo
|
||||
window->DockIsActive = false;
|
||||
window->DockNodeIsVisible = window->DockTabIsVisible = false;
|
||||
window->Size = window->SizeFull = FixLargeWindowsWhenUndocking(window->SizeFull, window->Viewport);
|
||||
ImVec2 to_pos = window->Pos;
|
||||
ImVec2 to_size = window->SizeFull;
|
||||
ImVec2 size_delta = to_size - from_size;
|
||||
ImVec2 pos_delta = to_pos - from_pos;
|
||||
const float dist = size_delta.x * size_delta.x + size_delta.y * size_delta.y + pos_delta.x * pos_delta.x + pos_delta.y * pos_delta.y;
|
||||
DockWindowQueueScaleAnim(ctx, window, from_pos, from_size, to_pos, to_size, true, 0.28f, 1.05f);
|
||||
|
||||
MarkIniSettingsDirty();
|
||||
}
|
||||
|
||||
void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
|
||||
ImGuiDockNode* ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
|
||||
{
|
||||
ImGuiContext& g = *ctx;
|
||||
IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessUndockNode node %08X\n", node->ID);
|
||||
@@ -18219,6 +18349,7 @@ void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
|
||||
node->Size = FixLargeWindowsWhenUndocking(node->Size, node->Windows[0]->Viewport);
|
||||
node->WantMouseMove = true;
|
||||
MarkIniSettingsDirty();
|
||||
return node;
|
||||
}
|
||||
|
||||
// This is mostly used for automation.
|
||||
@@ -18669,7 +18800,7 @@ static void ImGui::DockNodeUpdateVisibleFlag(ImGuiDockNode* node)
|
||||
node->IsVisible = is_visible;
|
||||
}
|
||||
|
||||
static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window)
|
||||
void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
IM_ASSERT(node->WantMouseMove == true);
|
||||
@@ -19711,7 +19842,9 @@ static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDock
|
||||
overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(root_payload->Viewport);
|
||||
|
||||
// Draw main preview rectangle
|
||||
const ImU32 overlay_col_main = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.60f : 0.40f);
|
||||
const float pulse = 0.5f + 0.5f * sinf((float)g.Time * 3.2f);
|
||||
const float pulse_alpha = ImLerp(0.65f, 1.35f, pulse);
|
||||
const ImU32 overlay_col_main = GetColorU32(ImGuiCol_DockingPreview, (is_transparent_payload ? 0.60f : 0.40f) * pulse_alpha);
|
||||
const ImU32 overlay_col_drop = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.90f : 0.70f);
|
||||
const ImU32 overlay_col_drop_hovered = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 1.20f : 1.00f);
|
||||
const ImU32 overlay_col_lines = GetColorU32(ImGuiCol_NavWindowingHighlight, is_transparent_payload ? 0.80f : 0.60f);
|
||||
|
||||
15
src/ThirdParty/imgui/imgui_internal.h
vendored
15
src/ThirdParty/imgui/imgui_internal.h
vendored
@@ -2838,6 +2838,16 @@ struct IMGUI_API ImGuiWindow
|
||||
ImVec2 Pos; // Position (always rounded-up to nearest pixel)
|
||||
ImVec2 Size; // Current size (==SizeFull or collapsed title bar size)
|
||||
ImVec2 SizeFull; // Size when non collapsed
|
||||
ImVec2 DockAnimFromPos; // Docking animation start position
|
||||
ImVec2 DockAnimFromSize; // Docking animation start size
|
||||
ImVec2 DockAnimToPos; // Docking animation target position
|
||||
ImVec2 DockAnimToSize; // Docking animation target size
|
||||
ImVec2 DockAnimGrabRatio; // Docking animation grab ratio during undock drag
|
||||
float DockAnimStartTime; // Docking animation start time
|
||||
float DockAnimDuration; // Docking animation duration
|
||||
bool DockAnimActive; // Docking animation active
|
||||
bool DockAnimOvershoot; // Docking animation overshoot (undock)
|
||||
float DockAnimOvershootStrength; // Docking animation overshoot strength
|
||||
ImVec2 ContentSize; // Size of contents/scrollable client area (calculated from the extents reach of the cursor) from previous frame. Does not include window decoration or window padding.
|
||||
ImVec2 ContentSizeIdeal;
|
||||
ImVec2 ContentSizeExplicit; // Size of contents/scrollable client area explicitly request by the user via SetNextWindowContentSize().
|
||||
@@ -3005,6 +3015,7 @@ struct ImGuiTabItem
|
||||
int LastFrameVisible;
|
||||
int LastFrameSelected; // This allows us to infer an ordered list of the last activated tabs with little maintenance
|
||||
float Offset; // Position relative to beginning of tab
|
||||
float OffsetAnim; // Animated position relative to beginning of tab
|
||||
float Width; // Width currently displayed
|
||||
float ContentWidth; // Width of label + padding, stored during BeginTabItem() call (misnamed as "Content" would normally imply width of label only)
|
||||
float RequestedWidth; // Width optionally requested by caller, -1.0f is unused
|
||||
@@ -3013,7 +3024,7 @@ struct ImGuiTabItem
|
||||
ImS16 IndexDuringLayout; // Index only used during TabBarLayout(). Tabs gets reordered so 'Tabs[n].IndexDuringLayout == n' but may mismatch during additions.
|
||||
bool WantClose; // Marked as closed by SetTabItemClosed()
|
||||
|
||||
ImGuiTabItem() { memset(this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; RequestedWidth = -1.0f; NameOffset = -1; BeginOrder = IndexDuringLayout = -1; }
|
||||
ImGuiTabItem() { memset(this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; RequestedWidth = -1.0f; NameOffset = -1; BeginOrder = IndexDuringLayout = -1; OffsetAnim = -1.0f; }
|
||||
};
|
||||
|
||||
// Storage for a tab bar (sizeof() 160 bytes)
|
||||
@@ -3670,7 +3681,7 @@ namespace ImGui
|
||||
IMGUI_API void DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window);
|
||||
IMGUI_API void DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node);
|
||||
IMGUI_API void DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref = true);
|
||||
IMGUI_API void DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node);
|
||||
IMGUI_API ImGuiDockNode* DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node);
|
||||
IMGUI_API bool DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDir split_dir, bool split_outer, ImVec2* out_pos);
|
||||
IMGUI_API ImGuiDockNode*DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id);
|
||||
IMGUI_API void DockNodeWindowMenuHandler_Default(ImGuiContext* ctx, ImGuiDockNode* node, ImGuiTabBar* tab_bar);
|
||||
|
||||
17
src/ThirdParty/imgui/imgui_widgets.cpp
vendored
17
src/ThirdParty/imgui/imgui_widgets.cpp
vendored
@@ -9724,6 +9724,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
tab_bar->WantLayout = false;
|
||||
const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
|
||||
|
||||
// Track selected tab when resizing our parent down
|
||||
const bool scroll_to_selected_tab = (tab_bar->BarRectPrevWidth > tab_bar->BarRect.GetWidth());
|
||||
@@ -9927,11 +9928,20 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
|
||||
section_tab_index += section->TabCount;
|
||||
}
|
||||
|
||||
const float anim_t = ImSaturate(g.IO.DeltaTime * 14.0f);
|
||||
for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
|
||||
{
|
||||
ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
|
||||
if (tab->OffsetAnim < 0.0f || tab_bar_appearing)
|
||||
tab->OffsetAnim = tab->Offset;
|
||||
else
|
||||
tab->OffsetAnim = ImLerp(tab->OffsetAnim, tab->Offset, anim_t);
|
||||
}
|
||||
|
||||
// Clear name buffers
|
||||
tab_bar->TabsNames.Buf.resize(0);
|
||||
|
||||
// If we have lost the selected tab, select the next most recently active one
|
||||
const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
|
||||
if (found_selected_tab_id == false && !tab_bar_appearing)
|
||||
tab_bar->SelectedTabId = 0;
|
||||
if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
|
||||
@@ -10551,10 +10561,11 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
|
||||
// Layout
|
||||
const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
|
||||
size.x = tab->Width;
|
||||
const float render_offset = (tab->OffsetAnim >= 0.0f) ? tab->OffsetAnim : tab->Offset;
|
||||
if (is_central_section)
|
||||
window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_TRUNC(tab->Offset - tab_bar->ScrollingAnim), 0.0f);
|
||||
window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_TRUNC(render_offset - tab_bar->ScrollingAnim), 0.0f);
|
||||
else
|
||||
window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f);
|
||||
window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(render_offset, 0.0f);
|
||||
ImVec2 pos = window->DC.CursorPos;
|
||||
ImRect bb(pos, pos + size);
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ GLFWwindow *Window::makeWindow() {
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
glfwSwapInterval(0);
|
||||
|
||||
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
|
||||
std::cerr << "Failed to initialize GLAD\n";
|
||||
|
||||
52
src/main_player.cpp
Normal file
52
src/main_player.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "Engine.h"
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <mach-o/dyld.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static std::filesystem::path getExecutableDir() {
|
||||
#if defined(_WIN32)
|
||||
char pathBuf[MAX_PATH] = {};
|
||||
DWORD len = GetModuleFileNameA(nullptr, pathBuf, MAX_PATH);
|
||||
if (len == 0 || len == MAX_PATH) {return {};}
|
||||
return std::filesystem::path(pathBuf).parent_path();
|
||||
#elif defined(__APPLE__)
|
||||
uint32_t size = 0;
|
||||
if (_NSGetExecutablePath(nullptr, &size) != -1 || size == 0) {return {};}
|
||||
std::string buf(size, '\0');
|
||||
if (_NSGetExecutablePath(buf.data(), &size) != 0) {return {};}
|
||||
return std::filesystem::path(buf).lexically_normal().parent_path();
|
||||
#else
|
||||
std::vector<char> buf(4096, '\0');
|
||||
ssize_t len = readlink("/proc/self/exe", buf.data(), buf.size() - 1);
|
||||
if (len <= 0) {return {};}
|
||||
buf[static_cast<size_t>(len)] = '\0';
|
||||
return std::filesystem::path(buf.data()).parent_path();
|
||||
#endif
|
||||
}
|
||||
|
||||
int main() {
|
||||
if (auto exeDir = getExecutableDir(); !exeDir.empty()) {
|
||||
std::error_code ec;
|
||||
std::filesystem::current_path(exeDir, ec);
|
||||
if (ec) {
|
||||
std::cerr << "[WARN] Failed to set working dir to executable: "
|
||||
<< ec.message() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
Engine engine;
|
||||
if (!engine.init()) {return -1;}
|
||||
|
||||
engine.run();
|
||||
engine.shutdown();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user