Yeah! PhysX!!!
This commit is contained in:
@@ -5,6 +5,34 @@ set(CMAKE_CXX_STANDARD 17)
|
|||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
# ==================== Compiler cache (ccache / sccache) ====================
|
||||||
|
|
||||||
|
option(MODULARITY_USE_COMPILER_CACHE "Enable compiler cache if available" ON)
|
||||||
|
|
||||||
|
if(MODULARITY_USE_COMPILER_CACHE)
|
||||||
|
find_program(CCACHE_PROGRAM ccache)
|
||||||
|
find_program(SCCACHE_PROGRAM sccache)
|
||||||
|
|
||||||
|
if(CCACHE_PROGRAM)
|
||||||
|
message(STATUS "Using compiler cache: ccache (${CCACHE_PROGRAM})")
|
||||||
|
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||||
|
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||||
|
|
||||||
|
elseif(SCCACHE_PROGRAM)
|
||||||
|
message(STATUS "Using compiler cache: sccache (${SCCACHE_PROGRAM})")
|
||||||
|
set(CMAKE_C_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}")
|
||||||
|
set(CMAKE_CXX_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}")
|
||||||
|
|
||||||
|
# Optional (helps some MSVC setups): ensure environment is used consistently
|
||||||
|
# set(ENV{SCCACHE_IDLE_TIMEOUT} "0")
|
||||||
|
|
||||||
|
else()
|
||||||
|
message(STATUS "Compiler cache enabled, but neither ccache nor sccache was found.")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(STATUS "Compiler cache disabled (MODULARITY_USE_COMPILER_CACHE=OFF).")
|
||||||
|
endif()
|
||||||
|
|
||||||
# ==================== WINDOWS FIXES (only active on Windows) ====================
|
# ==================== WINDOWS FIXES (only active on Windows) ====================
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
add_compile_definitions(
|
add_compile_definitions(
|
||||||
@@ -16,11 +44,14 @@ endif()
|
|||||||
|
|
||||||
# ==================== Compiler flags ====================
|
# ==================== Compiler flags ====================
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
add_compile_options(/W4 /O2 /permissive- /MP)
|
set(MODULARITY_WARNING_FLAGS /W4 /O2 /permissive- /MP)
|
||||||
else()
|
else()
|
||||||
add_compile_options(-Wall -Wextra -Wpedantic -O2)
|
set(MODULARITY_WARNING_FLAGS -Wall -Wextra -Wpedantic -O2)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# ==================== Optional PhysX ====================
|
||||||
|
option(MODULARITY_ENABLE_PHYSX "Enable PhysX physics integration" ON)
|
||||||
|
|
||||||
# ==================== Third-party libraries ====================
|
# ==================== Third-party libraries ====================
|
||||||
|
|
||||||
add_subdirectory(src/ThirdParty/glfw EXCLUDE_FROM_ALL)
|
add_subdirectory(src/ThirdParty/glfw EXCLUDE_FROM_ALL)
|
||||||
@@ -73,7 +104,9 @@ file(GLOB_RECURSE ENGINE_HEADERS CONFIGURE_DEPENDS
|
|||||||
)
|
)
|
||||||
|
|
||||||
list(FILTER ENGINE_SOURCES EXCLUDE REGEX ".*/ThirdParty/assimp/.*")
|
list(FILTER ENGINE_SOURCES EXCLUDE REGEX ".*/ThirdParty/assimp/.*")
|
||||||
|
list(FILTER ENGINE_SOURCES EXCLUDE REGEX ".*/ThirdParty/PhysX/.*")
|
||||||
list(FILTER ENGINE_HEADERS EXCLUDE REGEX ".*/ThirdParty/assimp/.*")
|
list(FILTER ENGINE_HEADERS EXCLUDE REGEX ".*/ThirdParty/assimp/.*")
|
||||||
|
list(FILTER ENGINE_HEADERS EXCLUDE REGEX ".*/ThirdParty/PhysX/.*")
|
||||||
|
|
||||||
add_library(core STATIC ${ENGINE_SOURCES} ${ENGINE_HEADERS})
|
add_library(core STATIC ${ENGINE_SOURCES} ${ENGINE_HEADERS})
|
||||||
set(ASSIMP_WARNINGS_AS_ERRORS OFF CACHE BOOL "Disable Assimp warnings as errors" FORCE)
|
set(ASSIMP_WARNINGS_AS_ERRORS OFF CACHE BOOL "Disable Assimp warnings as errors" FORCE)
|
||||||
@@ -85,10 +118,23 @@ target_include_directories(core PUBLIC
|
|||||||
${PROJECT_SOURCE_DIR}/src/ThirdParty/assimp/include
|
${PROJECT_SOURCE_DIR}/src/ThirdParty/assimp/include
|
||||||
)
|
)
|
||||||
target_link_libraries(core PUBLIC glad glm imgui imguizmo)
|
target_link_libraries(core PUBLIC glad glm imgui imguizmo)
|
||||||
|
target_compile_options(core 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)")
|
||||||
|
# PhysX build system expects output locations when using the GameWorks layout.
|
||||||
|
set(PX_OUTPUT_LIB_DIR ${CMAKE_BINARY_DIR}/physx CACHE PATH "PhysX output lib directory")
|
||||||
|
set(PX_OUTPUT_BIN_DIR ${CMAKE_BINARY_DIR}/physx/bin CACHE PATH "PhysX output bin directory")
|
||||||
|
add_subdirectory(${PHYSX_ROOT_DIR}/compiler/public ${CMAKE_BINARY_DIR}/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)
|
||||||
|
endif()
|
||||||
|
|
||||||
# ==================== Executable ====================
|
# ==================== Executable ====================
|
||||||
add_executable(Modularity src/main.cpp)
|
add_executable(Modularity src/main.cpp)
|
||||||
|
target_compile_options(Modularity PRIVATE ${MODULARITY_WARNING_FLAGS})
|
||||||
|
|
||||||
# Link order matters on Linux
|
# Link order matters on Linux
|
||||||
if(NOT WIN32)
|
if(NOT WIN32)
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ Size=784,221
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,1
|
DockId=0x00000006,1
|
||||||
|
|
||||||
|
[Window][Game Viewport]
|
||||||
|
Pos=306,46
|
||||||
|
Size=1265,737
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000002,1
|
||||||
|
|
||||||
[Docking][Data]
|
[Docking][Data]
|
||||||
DockSpace ID=0xD71539A0 Window=0x3DA2F1DE Pos=0,46 Size=1920,960 Split=X
|
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=0x00000007 Parent=0xD71539A0 SizeRef=1509,1015 Split=Y
|
||||||
|
|||||||
@@ -127,6 +127,5 @@ void ViewportController::update(GLFWwindow* window, bool& cursorLocked) {
|
|||||||
viewportFocused = false;
|
viewportFocused = false;
|
||||||
manualUnfocus = true;
|
manualUnfocus = true;
|
||||||
cursorLocked = false;
|
cursorLocked = false;
|
||||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
210
src/Engine.cpp
210
src/Engine.cpp
@@ -273,17 +273,6 @@ void Engine::run() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce cursor lock state every frame to avoid backends restoring it.
|
|
||||||
int desiredMode = cursorLocked ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
|
|
||||||
if (glfwGetInputMode(editorWindow, GLFW_CURSOR) != desiredMode) {
|
|
||||||
glfwSetInputMode(editorWindow, GLFW_CURSOR, desiredMode);
|
|
||||||
if (cursorLocked && glfwRawMouseMotionSupported()) {
|
|
||||||
glfwSetInputMode(editorWindow, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
|
||||||
} else if (!cursorLocked && glfwRawMouseMotionSupported()) {
|
|
||||||
glfwSetInputMode(editorWindow, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float currentFrame = glfwGetTime();
|
float currentFrame = glfwGetTime();
|
||||||
deltaTime = currentFrame - lastFrame;
|
deltaTime = currentFrame - lastFrame;
|
||||||
lastFrame = currentFrame;
|
lastFrame = currentFrame;
|
||||||
@@ -295,12 +284,13 @@ void Engine::run() {
|
|||||||
handleKeyboardShortcuts();
|
handleKeyboardShortcuts();
|
||||||
}
|
}
|
||||||
|
|
||||||
viewportController.update(editorWindow, cursorLocked);
|
if (gameViewCursorLocked) {
|
||||||
|
|
||||||
if (!viewportController.isViewportFocused() && cursorLocked) {
|
|
||||||
cursorLocked = false;
|
cursorLocked = false;
|
||||||
glfwSetInputMode(editorWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
viewportController.setFocused(false);
|
||||||
camera.firstMouse = true;
|
}
|
||||||
|
viewportController.update(editorWindow, cursorLocked);
|
||||||
|
if (!isPlaying) {
|
||||||
|
gameViewCursorLocked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll-wheel speed adjustment while freelook is active
|
// Scroll-wheel speed adjustment while freelook is active
|
||||||
@@ -318,10 +308,22 @@ void Engine::run() {
|
|||||||
camera.processKeyboard(deltaTime, editorWindow);
|
camera.processKeyboard(deltaTime, editorWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run script tick/update even when the object is not selected.
|
// Run scripts only in play/spec/test modes to avoid edit-time side effects (e.g., cursor grabs)
|
||||||
if (projectManager.currentProject.isLoaded) {
|
if (projectManager.currentProject.isLoaded) {
|
||||||
|
bool runScripts = isPlaying || specMode || testMode;
|
||||||
|
if (runScripts) {
|
||||||
updateScripts(deltaTime);
|
updateScripts(deltaTime);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool simulatePhysics = physics.isReady() && ((isPlaying && !isPaused) || (!isPlaying && specMode));
|
||||||
|
if (simulatePhysics) {
|
||||||
|
physics.simulate(deltaTime, sceneObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPlaying) {
|
||||||
|
updatePlayerController(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
if (!showLauncher && projectManager.currentProject.isLoaded && rendererInitialized) {
|
if (!showLauncher && projectManager.currentProject.isLoaded && rendererInitialized) {
|
||||||
glm::mat4 view = camera.getViewMatrix();
|
glm::mat4 view = camera.getViewMatrix();
|
||||||
@@ -329,9 +331,19 @@ void Engine::run() {
|
|||||||
if (aspect <= 0.0f) aspect = 1.0f;
|
if (aspect <= 0.0f) aspect = 1.0f;
|
||||||
glm::mat4 proj = glm::perspective(glm::radians(FOV), aspect, NEAR_PLANE, FAR_PLANE);
|
glm::mat4 proj = glm::perspective(glm::radians(FOV), aspect, NEAR_PLANE, FAR_PLANE);
|
||||||
|
|
||||||
|
#ifdef GL_POLYGON_MODE
|
||||||
|
GLint prevPoly[2] = { GL_FILL, GL_FILL };
|
||||||
|
glGetIntegerv(GL_POLYGON_MODE, prevPoly);
|
||||||
|
glPolygonMode(GL_FRONT_AND_BACK, collisionWireframe ? GL_LINE : GL_FILL);
|
||||||
|
#endif
|
||||||
|
|
||||||
renderer.beginRender(view, proj, camera.position);
|
renderer.beginRender(view, proj, camera.position);
|
||||||
renderer.renderScene(camera, sceneObjects, selectedObjectId);
|
renderer.renderScene(camera, sceneObjects, selectedObjectId);
|
||||||
renderer.endRender();
|
renderer.endRender();
|
||||||
|
|
||||||
|
#ifdef GL_POLYGON_MODE
|
||||||
|
glPolygonMode(GL_FRONT_AND_BACK, prevPoly[0]);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstFrame) {
|
if (firstFrame) {
|
||||||
@@ -367,6 +379,7 @@ void Engine::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderViewport();
|
renderViewport();
|
||||||
|
if (showGameViewport) renderGameViewportWindow();
|
||||||
renderDialogs();
|
renderDialogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,6 +404,18 @@ void Engine::run() {
|
|||||||
glfwMakeContextCurrent(backup_current_context);
|
glfwMakeContextCurrent(backup_current_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enforce cursor lock state at the end of the frame based on latest flags.
|
||||||
|
bool anyLock = cursorLocked || gameViewCursorLocked;
|
||||||
|
int desiredMode = anyLock ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
|
||||||
|
if (glfwGetInputMode(editorWindow, GLFW_CURSOR) != desiredMode) {
|
||||||
|
glfwSetInputMode(editorWindow, GLFW_CURSOR, desiredMode);
|
||||||
|
if (anyLock && glfwRawMouseMotionSupported()) {
|
||||||
|
glfwSetInputMode(editorWindow, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
||||||
|
} else if (!anyLock && glfwRawMouseMotionSupported()) {
|
||||||
|
glfwSetInputMode(editorWindow, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
glfwSwapBuffers(editorWindow);
|
glfwSwapBuffers(editorWindow);
|
||||||
|
|
||||||
if (firstFrame) {
|
if (firstFrame) {
|
||||||
@@ -407,6 +432,9 @@ void Engine::shutdown() {
|
|||||||
saveCurrentScene();
|
saveCurrentScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
physics.onPlayStop();
|
||||||
|
physics.shutdown();
|
||||||
|
|
||||||
ImGui_ImplOpenGL3_Shutdown();
|
ImGui_ImplOpenGL3_Shutdown();
|
||||||
ImGui_ImplGlfw_Shutdown();
|
ImGui_ImplGlfw_Shutdown();
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
@@ -656,7 +684,11 @@ void Engine::handleKeyboardShortcuts() {
|
|||||||
ctrlNPressed = false;
|
ctrlNPressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cameraActive = cursorLocked || viewportController.isViewportFocused() && cursorLocked;
|
bool cameraActive = cursorLocked || (viewportController.isViewportFocused() && cursorLocked);
|
||||||
|
if (!isPlaying && gameViewCursorLocked) {
|
||||||
|
// Prevent edit-mode freelook from conflicting with game view capture
|
||||||
|
gameViewCursorLocked = false;
|
||||||
|
}
|
||||||
if (!cameraActive) {
|
if (!cameraActive) {
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_Q)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
|
if (ImGui::IsKeyPressed(ImGuiKey_Q)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
|
||||||
if (ImGui::IsKeyPressed(ImGuiKey_W)) mCurrentGizmoOperation = ImGuizmo::ROTATE;
|
if (ImGui::IsKeyPressed(ImGuiKey_W)) mCurrentGizmoOperation = ImGuizmo::ROTATE;
|
||||||
@@ -669,6 +701,11 @@ void Engine::handleKeyboardShortcuts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_3)) {
|
||||||
|
collisionWireframe = !collisionWireframe;
|
||||||
|
addConsoleMessage(std::string("Collision wireframe ") + (collisionWireframe ? "enabled" : "disabled"), ConsoleMessageType::Info);
|
||||||
|
}
|
||||||
|
|
||||||
static bool snapPressed = false;
|
static bool snapPressed = false;
|
||||||
static bool snapHeldByCtrl = false;
|
static bool snapHeldByCtrl = false;
|
||||||
static bool snapStateBeforeCtrl = false;
|
static bool snapStateBeforeCtrl = false;
|
||||||
@@ -709,6 +746,10 @@ void Engine::handleKeyboardShortcuts() {
|
|||||||
if (glfwGetKey(editorWindow, GLFW_KEY_Y) == GLFW_RELEASE) {
|
if (glfwGetKey(editorWindow, GLFW_KEY_Y) == GLFW_RELEASE) {
|
||||||
redoPressed = false;
|
redoPressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_Escape) && gameViewCursorLocked) {
|
||||||
|
gameViewCursorLocked = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::updateScripts(float delta) {
|
void Engine::updateScripts(float delta) {
|
||||||
@@ -729,6 +770,113 @@ void Engine::updateScripts(float delta) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::updatePlayerController(float delta) {
|
||||||
|
if (!isPlaying) return;
|
||||||
|
|
||||||
|
SceneObject* player = nullptr;
|
||||||
|
for (auto& obj : sceneObjects) {
|
||||||
|
if (obj.hasPlayerController && obj.playerController.enabled) {
|
||||||
|
player = &obj;
|
||||||
|
activePlayerId = obj.id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!player) {
|
||||||
|
activePlayerId = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& pc = player->playerController;
|
||||||
|
// Maintain capsule sizing and collider defaults
|
||||||
|
if (pc.pitch == 0.0f && pc.yaw == 0.0f && (glm::length(player->rotation) > 0.01f)) {
|
||||||
|
pc.pitch = player->rotation.x;
|
||||||
|
pc.yaw = player->rotation.y;
|
||||||
|
}
|
||||||
|
player->hasCollider = true;
|
||||||
|
player->collider.type = ColliderType::Capsule;
|
||||||
|
player->collider.convex = true;
|
||||||
|
player->collider.boxSize = glm::vec3(pc.radius * 2.0f, pc.height, pc.radius * 2.0f);
|
||||||
|
player->hasRigidbody = true;
|
||||||
|
player->rigidbody.enabled = true;
|
||||||
|
player->rigidbody.useGravity = true;
|
||||||
|
player->rigidbody.isKinematic = false;
|
||||||
|
|
||||||
|
// Mouse look when game viewport is focused
|
||||||
|
if (gameViewportFocused || gameViewCursorLocked) {
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
pc.yaw -= io.MouseDelta.x * 50.0f * pc.lookSensitivity * delta;
|
||||||
|
pc.pitch -= io.MouseDelta.y * 50.0f * pc.lookSensitivity * delta;
|
||||||
|
pc.pitch = std::clamp(pc.pitch, -89.0f, 89.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Movement input aligned to camera facing (-Z forward convention)
|
||||||
|
auto key = [&](int k) { return glfwGetKey(editorWindow, k) == GLFW_PRESS; };
|
||||||
|
glm::quat q = glm::quat(glm::radians(glm::vec3(pc.pitch, pc.yaw, 0.0f)));
|
||||||
|
glm::vec3 forward = glm::normalize(q * glm::vec3(0.0f, 0.0f, -1.0f));
|
||||||
|
glm::vec3 right = glm::normalize(q * glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
glm::vec3 planarForward = glm::normalize(glm::vec3(forward.x, 0.0f, forward.z));
|
||||||
|
glm::vec3 planarRight = glm::normalize(glm::vec3(right.x, 0.0f, right.z));
|
||||||
|
if (!std::isfinite(planarForward.x) || glm::length(planarForward) < 1e-3f) {
|
||||||
|
planarForward = glm::vec3(0, 0, -1);
|
||||||
|
}
|
||||||
|
if (!std::isfinite(planarRight.x) || glm::length(planarRight) < 1e-3f) {
|
||||||
|
planarRight = glm::vec3(1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 move(0.0f);
|
||||||
|
if (key(GLFW_KEY_W)) move += planarForward;
|
||||||
|
if (key(GLFW_KEY_S)) move -= planarForward;
|
||||||
|
if (key(GLFW_KEY_D)) move += planarRight;
|
||||||
|
if (key(GLFW_KEY_A)) move -= planarRight;
|
||||||
|
if (glm::length(move) > 0.001f) move = glm::normalize(move);
|
||||||
|
|
||||||
|
float targetSpeed = pc.moveSpeed;
|
||||||
|
glm::vec3 velocity(move * targetSpeed);
|
||||||
|
|
||||||
|
// Simple gravity and jump
|
||||||
|
float capsuleHalf = std::max(0.1f, pc.height * 0.5f);
|
||||||
|
glm::vec3 physVel;
|
||||||
|
bool havePhysVel = physics.getLinearVelocity(player->id, physVel);
|
||||||
|
if (havePhysVel) pc.verticalVelocity = physVel.y;
|
||||||
|
|
||||||
|
// Ground check via PhysX scene query so mesh colliders work, not just the plane
|
||||||
|
glm::vec3 hitPos;
|
||||||
|
glm::vec3 hitNormal;
|
||||||
|
float hitDist = 0.0f;
|
||||||
|
float probeDist = capsuleHalf + 0.4f;
|
||||||
|
glm::vec3 rayStart = player->position + glm::vec3(0.0f, 0.1f, 0.0f);
|
||||||
|
bool hitGround = physics.raycastClosest(rayStart, glm::vec3(0.0f, -1.0f, 0.0f), probeDist,
|
||||||
|
player->id, &hitPos, &hitNormal, &hitDist);
|
||||||
|
bool grounded = hitGround && hitNormal.y > 0.25f && hitDist <= capsuleHalf + 0.2f && pc.verticalVelocity <= 0.35f;
|
||||||
|
if (!hitGround) {
|
||||||
|
// Fallback to simple height check to avoid regressions if queries fail
|
||||||
|
grounded = player->position.y <= capsuleHalf + 0.12f && pc.verticalVelocity <= 0.35f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grounded) {
|
||||||
|
pc.verticalVelocity = 0.0f;
|
||||||
|
if (hitGround) {
|
||||||
|
player->position.y = std::max(player->position.y, hitPos.y + capsuleHalf);
|
||||||
|
} else {
|
||||||
|
player->position.y = capsuleHalf;
|
||||||
|
}
|
||||||
|
if (key(GLFW_KEY_SPACE)) {
|
||||||
|
pc.verticalVelocity = pc.jumpStrength;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pc.verticalVelocity += -9.81f * delta;
|
||||||
|
}
|
||||||
|
velocity.y = pc.verticalVelocity;
|
||||||
|
velocity.y = std::clamp(velocity.y, -30.0f, 30.0f);
|
||||||
|
|
||||||
|
// Apply yaw to physics actor and keep collider aligned
|
||||||
|
physics.setActorYaw(player->id, pc.yaw);
|
||||||
|
player->rotation = glm::vec3(pc.pitch, pc.yaw, 0.0f);
|
||||||
|
|
||||||
|
if (!physics.setLinearVelocity(player->id, velocity)) {
|
||||||
|
player->position += velocity * delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
void Engine::OpenProjectPath(const std::string& path) {
|
void Engine::OpenProjectPath(const std::string& path) {
|
||||||
try {
|
try {
|
||||||
if (projectManager.loadProject(path)) {
|
if (projectManager.loadProject(path)) {
|
||||||
@@ -746,6 +894,10 @@ void Engine::OpenProjectPath(const std::string& path) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!physics.isReady() && !physics.init()) {
|
||||||
|
addConsoleMessage("Warning: PhysX failed to initialize; physics disabled for this session", ConsoleMessageType::Warning);
|
||||||
|
}
|
||||||
|
|
||||||
loadRecentScenes();
|
loadRecentScenes();
|
||||||
fileBrowser.setProjectRoot(projectManager.currentProject.projectPath);
|
fileBrowser.setProjectRoot(projectManager.currentProject.projectPath);
|
||||||
fileBrowser.currentPath = projectManager.currentProject.projectPath;
|
fileBrowser.currentPath = projectManager.currentProject.projectPath;
|
||||||
@@ -779,6 +931,10 @@ void Engine::createNewProject(const char* name, const char* location) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!physics.isReady() && !physics.init()) {
|
||||||
|
addConsoleMessage("Warning: PhysX failed to initialize; physics disabled for this session", ConsoleMessageType::Warning);
|
||||||
|
}
|
||||||
|
|
||||||
sceneObjects.clear();
|
sceneObjects.clear();
|
||||||
clearSelection();
|
clearSelection();
|
||||||
nextObjectId = 0;
|
nextObjectId = 0;
|
||||||
@@ -950,6 +1106,12 @@ void Engine::duplicateSelected() {
|
|||||||
newObj.light = it->light;
|
newObj.light = it->light;
|
||||||
newObj.camera = it->camera;
|
newObj.camera = it->camera;
|
||||||
newObj.postFx = it->postFx;
|
newObj.postFx = it->postFx;
|
||||||
|
newObj.hasRigidbody = it->hasRigidbody;
|
||||||
|
newObj.rigidbody = it->rigidbody;
|
||||||
|
newObj.hasCollider = it->hasCollider;
|
||||||
|
newObj.collider = it->collider;
|
||||||
|
newObj.hasPlayerController = it->hasPlayerController;
|
||||||
|
newObj.playerController = it->playerController;
|
||||||
|
|
||||||
sceneObjects.push_back(newObj);
|
sceneObjects.push_back(newObj);
|
||||||
setPrimarySelection(id);
|
setPrimarySelection(id);
|
||||||
@@ -1071,6 +1233,18 @@ void Engine::markProjectDirty() {
|
|||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Engine::setRigidbodyVelocityFromScript(int id, const glm::vec3& velocity) {
|
||||||
|
return physics.setLinearVelocity(id, velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Engine::getRigidbodyVelocityFromScript(int id, glm::vec3& outVelocity) {
|
||||||
|
return physics.getLinearVelocity(id, outVelocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Engine::teleportPhysicsActorFromScript(int id, const glm::vec3& position, const glm::vec3& rotationDeg) {
|
||||||
|
return physics.setActorPose(id, position, rotationDeg);
|
||||||
|
}
|
||||||
|
|
||||||
void Engine::compileScriptFile(const fs::path& scriptPath) {
|
void Engine::compileScriptFile(const fs::path& scriptPath) {
|
||||||
if (!projectManager.currentProject.isLoaded) {
|
if (!projectManager.currentProject.isLoaded) {
|
||||||
addConsoleMessage("No project is loaded", ConsoleMessageType::Warning);
|
addConsoleMessage("No project is loaded", ConsoleMessageType::Warning);
|
||||||
|
|||||||
13
src/Engine.h
13
src/Engine.h
@@ -9,6 +9,7 @@
|
|||||||
#include "MeshBuilder.h"
|
#include "MeshBuilder.h"
|
||||||
#include "ScriptCompiler.h"
|
#include "ScriptCompiler.h"
|
||||||
#include "ScriptRuntime.h"
|
#include "ScriptRuntime.h"
|
||||||
|
#include "PhysicsSystem.h"
|
||||||
#include "../include/Window/Window.h"
|
#include "../include/Window/Window.h"
|
||||||
|
|
||||||
void window_size_callback(GLFWwindow* window, int width, int height);
|
void window_size_callback(GLFWwindow* window, int width, int height);
|
||||||
@@ -90,7 +91,11 @@ private:
|
|||||||
bool isPlaying = false;
|
bool isPlaying = false;
|
||||||
bool isPaused = false;
|
bool isPaused = false;
|
||||||
bool showViewOutput = true;
|
bool showViewOutput = true;
|
||||||
|
bool showGameViewport = true;
|
||||||
int previewCameraId = -1;
|
int previewCameraId = -1;
|
||||||
|
bool gameViewCursorLocked = false;
|
||||||
|
bool gameViewportFocused = false;
|
||||||
|
int activePlayerId = -1;
|
||||||
MeshBuilder meshBuilder;
|
MeshBuilder meshBuilder;
|
||||||
char meshBuilderPath[260] = "";
|
char meshBuilderPath[260] = "";
|
||||||
char meshBuilderFaceInput[128] = "";
|
char meshBuilderFaceInput[128] = "";
|
||||||
@@ -105,12 +110,14 @@ private:
|
|||||||
MeshEditSelectionMode meshEditSelectionMode = MeshEditSelectionMode::Vertex;
|
MeshEditSelectionMode meshEditSelectionMode = MeshEditSelectionMode::Vertex;
|
||||||
ScriptCompiler scriptCompiler;
|
ScriptCompiler scriptCompiler;
|
||||||
ScriptRuntime scriptRuntime;
|
ScriptRuntime scriptRuntime;
|
||||||
|
PhysicsSystem physics;
|
||||||
bool showCompilePopup = false;
|
bool showCompilePopup = false;
|
||||||
bool lastCompileSuccess = false;
|
bool lastCompileSuccess = false;
|
||||||
std::string lastCompileStatus;
|
std::string lastCompileStatus;
|
||||||
std::string lastCompileLog;
|
std::string lastCompileLog;
|
||||||
bool specMode = false;
|
bool specMode = false;
|
||||||
bool testMode = false;
|
bool testMode = false;
|
||||||
|
bool collisionWireframe = false;
|
||||||
|
|
||||||
// Private methods
|
// Private methods
|
||||||
SceneObject* getSelectedObject();
|
SceneObject* getSelectedObject();
|
||||||
@@ -141,11 +148,13 @@ private:
|
|||||||
void renderInspectorPanel();
|
void renderInspectorPanel();
|
||||||
void renderConsolePanel();
|
void renderConsolePanel();
|
||||||
void renderViewport();
|
void renderViewport();
|
||||||
|
void renderGameViewportWindow();
|
||||||
void renderDialogs();
|
void renderDialogs();
|
||||||
void renderProjectBrowserPanel();
|
void renderProjectBrowserPanel();
|
||||||
Camera makeCameraFromObject(const SceneObject& obj) const;
|
Camera makeCameraFromObject(const SceneObject& obj) const;
|
||||||
void compileScriptFile(const fs::path& scriptPath);
|
void compileScriptFile(const fs::path& scriptPath);
|
||||||
void updateScripts(float delta);
|
void updateScripts(float delta);
|
||||||
|
void updatePlayerController(float delta);
|
||||||
|
|
||||||
void renderFileBrowserToolbar();
|
void renderFileBrowserToolbar();
|
||||||
void renderFileBrowserBreadcrumb();
|
void renderFileBrowserBreadcrumb();
|
||||||
@@ -206,4 +215,8 @@ public:
|
|||||||
void markProjectDirty();
|
void markProjectDirty();
|
||||||
// Script-accessible logging wrapper
|
// Script-accessible logging wrapper
|
||||||
void addConsoleMessageFromScript(const std::string& message, ConsoleMessageType type);
|
void addConsoleMessageFromScript(const std::string& message, ConsoleMessageType type);
|
||||||
|
// Script-accessible physics helpers
|
||||||
|
bool setRigidbodyVelocityFromScript(int id, const glm::vec3& velocity);
|
||||||
|
bool getRigidbodyVelocityFromScript(int id, glm::vec3& outVelocity);
|
||||||
|
bool teleportPhysicsActorFromScript(int id, const glm::vec3& position, const glm::vec3& rotationDeg);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -296,6 +296,60 @@ namespace FileIcons {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::renderGameViewportWindow() {
|
||||||
|
gameViewportFocused = false;
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 6.0f));
|
||||||
|
ImGui::Begin("Game Viewport", &showGameViewport, ImGuiWindowFlags_NoScrollbar);
|
||||||
|
|
||||||
|
bool windowFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
|
||||||
|
ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||||
|
int width = std::max(160, (int)avail.x);
|
||||||
|
int height = std::max(120, (int)avail.y);
|
||||||
|
|
||||||
|
const SceneObject* playerCam = nullptr;
|
||||||
|
for (const auto& obj : sceneObjects) {
|
||||||
|
if (obj.type == ObjectType::Camera && obj.camera.type == SceneCameraType::Player) {
|
||||||
|
playerCam = &obj;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPlaying) {
|
||||||
|
gameViewCursorLocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerCam && rendererInitialized) {
|
||||||
|
unsigned int tex = renderer.renderScenePreview(
|
||||||
|
makeCameraFromObject(*playerCam),
|
||||||
|
sceneObjects,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
playerCam->camera.fov,
|
||||||
|
playerCam->camera.nearClip,
|
||||||
|
playerCam->camera.farClip
|
||||||
|
);
|
||||||
|
|
||||||
|
ImGui::Image((void*)(intptr_t)tex, ImVec2((float)width, (float)height), ImVec2(0, 1), ImVec2(1, 0));
|
||||||
|
bool hovered = ImGui::IsItemHovered();
|
||||||
|
bool clicked = hovered && isPlaying && ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||||
|
|
||||||
|
if (clicked && !gameViewCursorLocked) {
|
||||||
|
gameViewCursorLocked = true;
|
||||||
|
}
|
||||||
|
if (gameViewCursorLocked && (!isPlaying || !windowFocused || ImGui::IsKeyPressed(ImGuiKey_Escape))) {
|
||||||
|
gameViewCursorLocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameViewportFocused = windowFocused && gameViewCursorLocked;
|
||||||
|
ImGui::TextDisabled(gameViewCursorLocked ? "Camera captured (ESC to release)" : "Click to capture");
|
||||||
|
} else {
|
||||||
|
ImGui::TextDisabled("No player camera found (Camera Type: Player).");
|
||||||
|
gameViewportFocused = ImGui::IsWindowFocused();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
}
|
||||||
void Engine::renderFileBrowserPanel() {
|
void Engine::renderFileBrowserPanel() {
|
||||||
ImGui::Begin("Project", &showFileBrowser);
|
ImGui::Begin("Project", &showFileBrowser);
|
||||||
ImGuiStyle& style = ImGui::GetStyle();
|
ImGuiStyle& style = ImGui::GetStyle();
|
||||||
@@ -1307,7 +1361,23 @@ void Engine::renderMainMenuBar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::BeginMenu("Scripts")) {
|
if (ImGui::BeginMenu("Scripts")) {
|
||||||
ImGui::MenuItem("Spec Mode (run Script_Spec)", nullptr, &specMode);
|
auto toggleSpec = [&](bool enabled) {
|
||||||
|
if (specMode == enabled) return;
|
||||||
|
if (enabled && !physics.isReady() && !physics.init()) {
|
||||||
|
addConsoleMessage("PhysX failed to initialize; spec mode disabled", ConsoleMessageType::Warning);
|
||||||
|
specMode = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
specMode = enabled;
|
||||||
|
if (!isPlaying) {
|
||||||
|
if (specMode) physics.onPlayStart(sceneObjects);
|
||||||
|
else physics.onPlayStop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
bool specValue = specMode;
|
||||||
|
if (ImGui::MenuItem("Spec Mode (run Script_Spec)", nullptr, &specValue)) {
|
||||||
|
toggleSpec(specValue);
|
||||||
|
}
|
||||||
ImGui::MenuItem("Test Mode (run Script_TestEditor)", nullptr, &testMode);
|
ImGui::MenuItem("Test Mode (run Script_TestEditor)", nullptr, &testMode);
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
@@ -1353,18 +1423,43 @@ void Engine::renderMainMenuBar() {
|
|||||||
bool playPressed = ImGui::Button(isPlaying ? "Stop" : "Play");
|
bool playPressed = ImGui::Button(isPlaying ? "Stop" : "Play");
|
||||||
ImGui::SameLine(0.0f, 6.0f);
|
ImGui::SameLine(0.0f, 6.0f);
|
||||||
bool pausePressed = ImGui::Button(isPaused ? "Resume" : "Pause");
|
bool pausePressed = ImGui::Button(isPaused ? "Resume" : "Pause");
|
||||||
|
ImGui::SameLine(0.0f, 6.0f);
|
||||||
|
bool specPressed = ImGui::Button(specMode ? "Spec On" : "Spec Mode");
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
if (playPressed) {
|
if (playPressed) {
|
||||||
isPlaying = !isPlaying;
|
bool newState = !isPlaying;
|
||||||
if (!isPlaying) {
|
if (newState) {
|
||||||
isPaused = false;
|
if (physics.isReady() || physics.init()) {
|
||||||
|
physics.onPlayStart(sceneObjects);
|
||||||
|
} else {
|
||||||
|
addConsoleMessage("PhysX failed to initialize; physics disabled for play mode", ConsoleMessageType::Warning);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
physics.onPlayStop();
|
||||||
|
isPaused = false;
|
||||||
|
if (specMode && (physics.isReady() || physics.init())) {
|
||||||
|
physics.onPlayStart(sceneObjects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isPlaying = newState;
|
||||||
}
|
}
|
||||||
if (pausePressed) {
|
if (pausePressed) {
|
||||||
isPaused = !isPaused;
|
isPaused = !isPaused;
|
||||||
if (isPaused) isPlaying = true; // placeholder: pausing implies we’re in play mode
|
if (isPaused) isPlaying = true; // placeholder: pausing implies we’re in play mode
|
||||||
}
|
}
|
||||||
|
if (specPressed) {
|
||||||
|
bool enable = !specMode;
|
||||||
|
if (enable && !physics.isReady() && !physics.init()) {
|
||||||
|
addConsoleMessage("PhysX failed to initialize; spec mode disabled", ConsoleMessageType::Warning);
|
||||||
|
enable = false;
|
||||||
|
}
|
||||||
|
specMode = enable;
|
||||||
|
if (!isPlaying) {
|
||||||
|
if (specMode) physics.onPlayStart(sceneObjects);
|
||||||
|
else physics.onPlayStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float rightX = ImGui::GetWindowWidth() - 220.0f;
|
float rightX = ImGui::GetWindowWidth() - 220.0f;
|
||||||
if (rightX > ImGui::GetCursorPosX()) {
|
if (rightX > ImGui::GetCursorPosX()) {
|
||||||
@@ -1882,6 +1977,170 @@ void Engine::renderInspectorPanel() {
|
|||||||
|
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
|
if (obj.hasCollider) {
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.35f, 0.5f, 0.35f, 1.0f));
|
||||||
|
if (ImGui::CollapsingHeader("Collider", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
|
ImGui::Indent(10.0f);
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
if (ImGui::Checkbox("Enabled", &obj.collider.enabled)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* colliderTypes[] = { "Box", "Mesh", "Convex Mesh", "Capsule" };
|
||||||
|
int colliderType = static_cast<int>(obj.collider.type);
|
||||||
|
if (ImGui::Combo("Type", &colliderType, colliderTypes, IM_ARRAYSIZE(colliderTypes))) {
|
||||||
|
obj.collider.type = static_cast<ColliderType>(colliderType);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.collider.type == ColliderType::Box) {
|
||||||
|
if (ImGui::DragFloat3("Box Size", &obj.collider.boxSize.x, 0.01f, 0.01f, 1000.0f, "%.3f")) {
|
||||||
|
obj.collider.boxSize.x = std::max(0.01f, obj.collider.boxSize.x);
|
||||||
|
obj.collider.boxSize.y = std::max(0.01f, obj.collider.boxSize.y);
|
||||||
|
obj.collider.boxSize.z = std::max(0.01f, obj.collider.boxSize.z);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (ImGui::SmallButton("Match Object Scale")) {
|
||||||
|
obj.collider.boxSize = glm::max(obj.scale, glm::vec3(0.01f));
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
} else if (obj.collider.type == ColliderType::Capsule) {
|
||||||
|
float radius = std::max(0.05f, std::max(obj.collider.boxSize.x, obj.collider.boxSize.z) * 0.5f);
|
||||||
|
float height = std::max(0.1f, obj.collider.boxSize.y);
|
||||||
|
if (ImGui::DragFloat("Radius", &radius, 0.01f, 0.05f, 5.0f, "%.3f")) {
|
||||||
|
obj.collider.boxSize.x = obj.collider.boxSize.z = radius * 2.0f;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (ImGui::DragFloat("Height", &height, 0.01f, 0.1f, 10.0f, "%.3f")) {
|
||||||
|
obj.collider.boxSize.y = height;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::TextDisabled("Capsule aligned to Y axis.");
|
||||||
|
} else {
|
||||||
|
if (ImGui::Checkbox("Use Convex Hull (required for Rigidbody)", &obj.collider.convex)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::TextDisabled("Uses mesh from the object (OBJ/Model). Non-convex is static-only.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
if (ImGui::Button("Remove Collider", ImVec2(-1, 0))) {
|
||||||
|
obj.hasCollider = false;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
|
ImGui::Unindent(10.0f);
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.hasPlayerController) {
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.35f, 0.45f, 0.7f, 1.0f));
|
||||||
|
if (ImGui::CollapsingHeader("Player Controller", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
|
ImGui::Indent(10.0f);
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
if (ImGui::Checkbox("Enabled", &obj.playerController.enabled)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (ImGui::DragFloat("Move Speed", &obj.playerController.moveSpeed, 0.1f, 0.1f, 100.0f, "%.2f")) {
|
||||||
|
obj.playerController.moveSpeed = std::max(0.1f, obj.playerController.moveSpeed);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (ImGui::DragFloat("Look Sensitivity", &obj.playerController.lookSensitivity, 0.01f, 0.01f, 2.0f, "%.2f")) {
|
||||||
|
obj.playerController.lookSensitivity = std::clamp(obj.playerController.lookSensitivity, 0.01f, 2.0f);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (ImGui::DragFloat("Height", &obj.playerController.height, 0.01f, 0.5f, 3.0f, "%.2f")) {
|
||||||
|
obj.playerController.height = std::clamp(obj.playerController.height, 0.5f, 3.0f);
|
||||||
|
obj.scale.y = obj.playerController.height;
|
||||||
|
obj.collider.boxSize.y = obj.playerController.height;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (ImGui::DragFloat("Radius", &obj.playerController.radius, 0.01f, 0.2f, 1.2f, "%.2f")) {
|
||||||
|
obj.playerController.radius = std::clamp(obj.playerController.radius, 0.2f, 1.2f);
|
||||||
|
obj.scale.x = obj.scale.z = obj.playerController.radius * 2.0f;
|
||||||
|
obj.collider.boxSize.x = obj.collider.boxSize.z = obj.playerController.radius * 2.0f;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (ImGui::DragFloat("Jump Strength", &obj.playerController.jumpStrength, 0.1f, 0.1f, 30.0f, "%.1f")) {
|
||||||
|
obj.playerController.jumpStrength = std::max(0.1f, obj.playerController.jumpStrength);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button("Remove Player Controller", ImVec2(-1, 0))) {
|
||||||
|
obj.hasPlayerController = false;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
obj.hasCollider = true;
|
||||||
|
obj.collider.type = ColliderType::Capsule;
|
||||||
|
obj.collider.convex = true;
|
||||||
|
obj.hasRigidbody = true;
|
||||||
|
obj.rigidbody.enabled = true;
|
||||||
|
obj.rigidbody.useGravity = true;
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
|
ImGui::Unindent(10.0f);
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.hasRigidbody) {
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.45f, 0.45f, 0.25f, 1.0f));
|
||||||
|
if (ImGui::CollapsingHeader("Rigidbody", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
|
ImGui::Indent(10.0f);
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
if (ImGui::Checkbox("Enabled", &obj.rigidbody.enabled)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginDisabled(true);
|
||||||
|
ImGui::Checkbox("Collider (mesh type)", &obj.rigidbody.enabled); // placeholder label to hint geometry from mesh
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
if (ImGui::DragFloat("Mass", &obj.rigidbody.mass, 0.05f, 0.01f, 1000.0f, "%.2f")) {
|
||||||
|
obj.rigidbody.mass = std::max(0.01f, obj.rigidbody.mass);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (ImGui::Checkbox("Use Gravity", &obj.rigidbody.useGravity)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (ImGui::Checkbox("Kinematic", &obj.rigidbody.isKinematic)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (ImGui::DragFloat("Linear Damping", &obj.rigidbody.linearDamping, 0.01f, 0.0f, 10.0f)) {
|
||||||
|
obj.rigidbody.linearDamping = std::clamp(obj.rigidbody.linearDamping, 0.0f, 10.0f);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (ImGui::DragFloat("Angular Damping", &obj.rigidbody.angularDamping, 0.01f, 0.0f, 10.0f)) {
|
||||||
|
obj.rigidbody.angularDamping = std::clamp(obj.rigidbody.angularDamping, 0.0f, 10.0f);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
if (ImGui::Button("Remove Rigidbody", ImVec2(-1, 0))) {
|
||||||
|
obj.hasRigidbody = false;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
|
ImGui::Unindent(10.0f);
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
if (obj.type == ObjectType::Camera) {
|
if (obj.type == ObjectType::Camera) {
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.45f, 0.35f, 0.65f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.45f, 0.35f, 0.65f, 1.0f));
|
||||||
@@ -2230,6 +2489,51 @@ void Engine::renderInspectorPanel() {
|
|||||||
ImGui::OpenPopup("AddComponentPopup");
|
ImGui::OpenPopup("AddComponentPopup");
|
||||||
}
|
}
|
||||||
if (ImGui::BeginPopup("AddComponentPopup")) {
|
if (ImGui::BeginPopup("AddComponentPopup")) {
|
||||||
|
if (!obj.hasRigidbody && ImGui::MenuItem("Rigidbody")) {
|
||||||
|
obj.hasRigidbody = true;
|
||||||
|
obj.rigidbody = RigidbodyComponent{};
|
||||||
|
materialChanged = true;
|
||||||
|
}
|
||||||
|
if (!obj.hasPlayerController && ImGui::MenuItem("Player Controller")) {
|
||||||
|
obj.hasPlayerController = true;
|
||||||
|
obj.playerController = PlayerControllerComponent{};
|
||||||
|
obj.hasCollider = true;
|
||||||
|
obj.collider.type = ColliderType::Capsule;
|
||||||
|
obj.collider.boxSize = glm::vec3(obj.playerController.radius * 2.0f, obj.playerController.height, obj.playerController.radius * 2.0f);
|
||||||
|
obj.collider.convex = true;
|
||||||
|
obj.hasRigidbody = true;
|
||||||
|
obj.rigidbody.enabled = true;
|
||||||
|
obj.rigidbody.useGravity = true;
|
||||||
|
obj.rigidbody.isKinematic = false;
|
||||||
|
obj.scale = glm::vec3(obj.playerController.radius * 2.0f, obj.playerController.height, obj.playerController.radius * 2.0f);
|
||||||
|
materialChanged = true;
|
||||||
|
}
|
||||||
|
if (!obj.hasCollider && ImGui::BeginMenu("Collider")) {
|
||||||
|
if (ImGui::MenuItem("Box Collider")) {
|
||||||
|
obj.hasCollider = true;
|
||||||
|
obj.collider = ColliderComponent{};
|
||||||
|
obj.collider.boxSize = glm::max(obj.scale, glm::vec3(0.01f));
|
||||||
|
materialChanged = true;
|
||||||
|
addComponentButtonShown = true;
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Mesh Collider (Triangle)")) {
|
||||||
|
obj.hasCollider = true;
|
||||||
|
obj.collider = ColliderComponent{};
|
||||||
|
obj.collider.type = ColliderType::Mesh;
|
||||||
|
obj.collider.convex = false;
|
||||||
|
materialChanged = true;
|
||||||
|
addComponentButtonShown = true;
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Mesh Collider (Convex)")) {
|
||||||
|
obj.hasCollider = true;
|
||||||
|
obj.collider = ColliderComponent{};
|
||||||
|
obj.collider.type = ColliderType::ConvexMesh;
|
||||||
|
obj.collider.convex = true;
|
||||||
|
materialChanged = true;
|
||||||
|
addComponentButtonShown = true;
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
if (ImGui::MenuItem("Script")) {
|
if (ImGui::MenuItem("Script")) {
|
||||||
obj.scripts.push_back(ScriptComponent{});
|
obj.scripts.push_back(ScriptComponent{});
|
||||||
materialChanged = true;
|
materialChanged = true;
|
||||||
@@ -2435,6 +2739,52 @@ void Engine::renderInspectorPanel() {
|
|||||||
ImGui::OpenPopup("AddComponentPopup");
|
ImGui::OpenPopup("AddComponentPopup");
|
||||||
}
|
}
|
||||||
if (ImGui::BeginPopup("AddComponentPopup")) {
|
if (ImGui::BeginPopup("AddComponentPopup")) {
|
||||||
|
if (!obj.hasRigidbody && ImGui::MenuItem("Rigidbody")) {
|
||||||
|
obj.hasRigidbody = true;
|
||||||
|
obj.rigidbody = RigidbodyComponent{};
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
|
if (!obj.hasPlayerController && ImGui::MenuItem("Player Controller")) {
|
||||||
|
obj.hasPlayerController = true;
|
||||||
|
obj.playerController = PlayerControllerComponent{};
|
||||||
|
obj.hasCollider = true;
|
||||||
|
obj.collider.type = ColliderType::Capsule;
|
||||||
|
obj.collider.boxSize = glm::vec3(obj.playerController.radius * 2.0f, obj.playerController.height, obj.playerController.radius * 2.0f);
|
||||||
|
obj.collider.convex = true;
|
||||||
|
obj.hasRigidbody = true;
|
||||||
|
obj.rigidbody.enabled = true;
|
||||||
|
obj.rigidbody.useGravity = true;
|
||||||
|
obj.rigidbody.isKinematic = false;
|
||||||
|
obj.scale = glm::vec3(obj.playerController.radius * 2.0f, obj.playerController.height, obj.playerController.radius * 2.0f);
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
addComponentButtonShown = true;
|
||||||
|
}
|
||||||
|
if (!obj.hasCollider && ImGui::BeginMenu("Collider")) {
|
||||||
|
if (ImGui::MenuItem("Box Collider")) {
|
||||||
|
obj.hasCollider = true;
|
||||||
|
obj.collider = ColliderComponent{};
|
||||||
|
obj.collider.boxSize = glm::max(obj.scale, glm::vec3(0.01f));
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
addComponentButtonShown = true;
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Mesh Collider (Triangle)")) {
|
||||||
|
obj.hasCollider = true;
|
||||||
|
obj.collider = ColliderComponent{};
|
||||||
|
obj.collider.type = ColliderType::Mesh;
|
||||||
|
obj.collider.convex = false;
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
addComponentButtonShown = true;
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Mesh Collider (Convex)")) {
|
||||||
|
obj.hasCollider = true;
|
||||||
|
obj.collider = ColliderComponent{};
|
||||||
|
obj.collider.type = ColliderType::ConvexMesh;
|
||||||
|
obj.collider.convex = true;
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
addComponentButtonShown = true;
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
if (ImGui::MenuItem("Script")) {
|
if (ImGui::MenuItem("Script")) {
|
||||||
obj.scripts.push_back(ScriptComponent{});
|
obj.scripts.push_back(ScriptComponent{});
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
@@ -3919,19 +4269,11 @@ void Engine::renderViewport() {
|
|||||||
if (mouseOverViewportImage && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
if (mouseOverViewportImage && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
viewportController.setFocused(true);
|
viewportController.setFocused(true);
|
||||||
cursorLocked = true;
|
cursorLocked = true;
|
||||||
glfwSetInputMode(editorWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
|
||||||
if (glfwRawMouseMotionSupported()) {
|
|
||||||
glfwSetInputMode(editorWindow, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
|
||||||
}
|
|
||||||
camera.firstMouse = true;
|
camera.firstMouse = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursorLocked && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
|
if (cursorLocked && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
|
||||||
cursorLocked = false;
|
cursorLocked = false;
|
||||||
glfwSetInputMode(editorWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
|
||||||
if (glfwRawMouseMotionSupported()) {
|
|
||||||
glfwSetInputMode(editorWindow, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE);
|
|
||||||
}
|
|
||||||
camera.firstMouse = true;
|
camera.firstMouse = true;
|
||||||
}
|
}
|
||||||
if (cursorLocked) {
|
if (cursorLocked) {
|
||||||
|
|||||||
529
src/PhysicsSystem.cpp
Normal file
529
src/PhysicsSystem.cpp
Normal file
@@ -0,0 +1,529 @@
|
|||||||
|
#include "PhysicsSystem.h"
|
||||||
|
|
||||||
|
#ifdef MODULARITY_ENABLE_PHYSX
|
||||||
|
#include "PxPhysicsAPI.h"
|
||||||
|
#include "ModelLoader.h"
|
||||||
|
#include <numeric>
|
||||||
|
#include <algorithm>
|
||||||
|
#include "extensions/PxRigidBodyExt.h"
|
||||||
|
|
||||||
|
using namespace physx;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
PxVec3 ToPxVec3(const glm::vec3& v) {
|
||||||
|
return PxVec3(v.x, v.y, v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
PxQuat ToPxQuat(const glm::vec3& eulerDeg) {
|
||||||
|
glm::vec3 radians = glm::radians(eulerDeg);
|
||||||
|
glm::quat q = glm::quat(radians);
|
||||||
|
return PxQuat(q.x, q.y, q.z, q.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 ToGlmVec3(const PxVec3& v) {
|
||||||
|
return glm::vec3(v.x, v.y, v.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 ToGlmEulerDeg(const PxQuat& q) {
|
||||||
|
glm::quat gq(q.w, q.x, q.y, q.z);
|
||||||
|
return glm::degrees(glm::eulerAngles(gq));
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct IgnoreActorFilter : PxQueryFilterCallback {
|
||||||
|
PxRigidActor* ignore = nullptr;
|
||||||
|
explicit IgnoreActorFilter(PxRigidActor* actor) : ignore(actor) {}
|
||||||
|
|
||||||
|
PxQueryHitType::Enum preFilter(const PxFilterData&,
|
||||||
|
const PxShape* shape,
|
||||||
|
const PxRigidActor* actor,
|
||||||
|
PxHitFlags&) override {
|
||||||
|
if (actor == ignore) return PxQueryHitType::eNONE;
|
||||||
|
// Keep default blocking behaviour
|
||||||
|
if (shape && shape->getFlags().isSet(PxShapeFlag::eTRIGGER_SHAPE)) {
|
||||||
|
return PxQueryHitType::eNONE;
|
||||||
|
}
|
||||||
|
return PxQueryHitType::eBLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
PxQueryHitType::Enum postFilter(const PxFilterData&,
|
||||||
|
const PxQueryHit&,
|
||||||
|
const PxShape*,
|
||||||
|
const PxRigidActor*) override {
|
||||||
|
return PxQueryHitType::eBLOCK;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool PhysicsSystem::init() {
|
||||||
|
if (isReady()) return true;
|
||||||
|
|
||||||
|
mFoundation = PxCreateFoundation(PX_PHYSICS_VERSION, mAllocator, mErrorCallback);
|
||||||
|
if (!mFoundation) return false;
|
||||||
|
|
||||||
|
mPhysics = PxCreatePhysics(PX_PHYSICS_VERSION, *mFoundation, PxTolerancesScale(), true, nullptr);
|
||||||
|
if (!mPhysics) return false;
|
||||||
|
|
||||||
|
mDispatcher = PxDefaultCpuDispatcherCreate(2);
|
||||||
|
if (!mDispatcher) return false;
|
||||||
|
|
||||||
|
PxTolerancesScale scale = mPhysics->getTolerancesScale();
|
||||||
|
mCookParams = PxCookingParams(scale);
|
||||||
|
mCookParams.meshPreprocessParams |= PxMeshPreprocessingFlag::eDISABLE_ACTIVE_EDGES_PRECOMPUTE;
|
||||||
|
mCookParams.meshPreprocessParams |= PxMeshPreprocessingFlag::eWELD_VERTICES;
|
||||||
|
|
||||||
|
PxSceneDesc sceneDesc(mPhysics->getTolerancesScale());
|
||||||
|
sceneDesc.gravity = PxVec3(0.0f, -9.81f, 0.0f);
|
||||||
|
sceneDesc.cpuDispatcher = mDispatcher;
|
||||||
|
sceneDesc.filterShader = PxDefaultSimulationFilterShader;
|
||||||
|
sceneDesc.flags |= PxSceneFlag::eENABLE_CCD;
|
||||||
|
mScene = mPhysics->createScene(sceneDesc);
|
||||||
|
if (!mScene) return false;
|
||||||
|
|
||||||
|
mDefaultMaterial = mPhysics->createMaterial(0.9f, 0.9f, 0.0f);
|
||||||
|
|
||||||
|
return mDefaultMaterial != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhysicsSystem::isReady() const {
|
||||||
|
return mFoundation && mPhysics && mScene && mDefaultMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsSystem::createGroundPlane() {
|
||||||
|
if (!isReady()) return;
|
||||||
|
if (mGroundPlane) {
|
||||||
|
mScene->removeActor(*mGroundPlane);
|
||||||
|
mGroundPlane->release();
|
||||||
|
mGroundPlane = nullptr;
|
||||||
|
}
|
||||||
|
mGroundPlane = PxCreatePlane(*mPhysics, PxPlane(0.0f, 1.0f, 0.0f, 0.0f), *mDefaultMaterial);
|
||||||
|
if (mGroundPlane) {
|
||||||
|
mScene->addActor(*mGroundPlane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
meshInfo = g_objLoader.getMeshInfo(obj.meshId);
|
||||||
|
} else if (obj.type == ObjectType::Model && obj.meshId >= 0) {
|
||||||
|
meshInfo = getModelLoader().getMeshInfo(obj.meshId);
|
||||||
|
}
|
||||||
|
if (!meshInfo || meshInfo->triangleVertices.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices.reserve(meshInfo->triangleVertices.size());
|
||||||
|
indices.resize(meshInfo->triangleVertices.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < meshInfo->triangleVertices.size(); ++i) {
|
||||||
|
const glm::vec3& v = meshInfo->triangleVertices[i];
|
||||||
|
vertices.emplace_back(v.x, v.y, v.z);
|
||||||
|
indices[i] = static_cast<uint32_t>(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !vertices.empty() && (indices.size() % 3 == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
PxTriangleMesh* PhysicsSystem::cookTriangleMesh(const std::vector<PxVec3>& vertices,
|
||||||
|
const std::vector<uint32_t>& indices) const {
|
||||||
|
if (vertices.empty() || indices.size() < 3) return nullptr;
|
||||||
|
|
||||||
|
PxTriangleMeshDesc desc;
|
||||||
|
desc.points.count = static_cast<uint32_t>(vertices.size());
|
||||||
|
desc.points.stride = sizeof(PxVec3);
|
||||||
|
desc.points.data = vertices.data();
|
||||||
|
desc.triangles.count = static_cast<uint32_t>(indices.size() / 3);
|
||||||
|
desc.triangles.stride = 3 * sizeof(uint32_t);
|
||||||
|
desc.triangles.data = indices.data();
|
||||||
|
|
||||||
|
PxDefaultMemoryOutputStream buf;
|
||||||
|
if (!PxCookTriangleMesh(mCookParams, desc, buf)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
PxDefaultMemoryInputData input(buf.getData(), buf.getSize());
|
||||||
|
return mPhysics->createTriangleMesh(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
PxConvexMesh* PhysicsSystem::cookConvexMesh(const std::vector<PxVec3>& vertices) const {
|
||||||
|
if (vertices.size() < 4) return nullptr;
|
||||||
|
|
||||||
|
PxConvexMeshDesc desc;
|
||||||
|
desc.points.count = static_cast<uint32_t>(vertices.size());
|
||||||
|
desc.points.stride = sizeof(PxVec3);
|
||||||
|
desc.points.data = vertices.data();
|
||||||
|
desc.flags = PxConvexFlag::eCOMPUTE_CONVEX | PxConvexFlag::eCHECK_ZERO_AREA_TRIANGLES;
|
||||||
|
desc.vertexLimit = 255;
|
||||||
|
|
||||||
|
PxDefaultMemoryOutputStream buf;
|
||||||
|
if (!PxCookConvexMesh(mCookParams, desc, buf)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
PxDefaultMemoryInputData input(buf.getData(), buf.getSize());
|
||||||
|
return mPhysics->createConvexMesh(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhysicsSystem::attachPrimitiveShape(PxRigidActor* actor, const SceneObject& obj, bool isDynamic) const {
|
||||||
|
(void)isDynamic;
|
||||||
|
if (!actor) return false;
|
||||||
|
PxShape* shape = nullptr;
|
||||||
|
auto tuneShape = [](PxShape* s, float minDim, bool /*swept*/) {
|
||||||
|
if (!s) return;
|
||||||
|
float contact = std::clamp(minDim * 0.2f, 0.02f, 0.2f);
|
||||||
|
float rest = contact * 0.15f;
|
||||||
|
s->setContactOffset(contact);
|
||||||
|
s->setRestOffset(rest);
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (obj.type) {
|
||||||
|
case ObjectType::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: {
|
||||||
|
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: {
|
||||||
|
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);
|
||||||
|
float halfHeight = cylHeight * 0.5f;
|
||||||
|
shape = mPhysics->createShape(PxCapsuleGeometry(radius, halfHeight), *mDefaultMaterial, true);
|
||||||
|
if (shape) {
|
||||||
|
// PhysX capsules default to the X axis; rotate to align with Y (character up)
|
||||||
|
shape->setLocalPose(PxTransform(PxQuat(PxHalfPi, PxVec3(0, 0, 1))));
|
||||||
|
}
|
||||||
|
tuneShape(shape, std::min(radius * 2.0f, halfHeight * 2.0f), isDynamic);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shape) return false;
|
||||||
|
actor->attachShape(*shape);
|
||||||
|
shape->release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhysicsSystem::attachColliderShape(PxRigidActor* actor, const SceneObject& obj, bool isDynamic) const {
|
||||||
|
if (!actor || !obj.hasCollider || !obj.collider.enabled) return false;
|
||||||
|
|
||||||
|
PxShape* shape = nullptr;
|
||||||
|
auto tuneShape = [](PxShape* s, float minDim, bool /*swept*/) {
|
||||||
|
if (!s) return;
|
||||||
|
float contact = std::clamp(minDim * 0.12f, 0.015f, 0.12f);
|
||||||
|
float rest = contact * 0.2f;
|
||||||
|
s->setContactOffset(contact);
|
||||||
|
s->setRestOffset(rest);
|
||||||
|
};
|
||||||
|
float minDim = 0.1f;
|
||||||
|
if (obj.collider.type == ColliderType::Box) {
|
||||||
|
glm::vec3 half = glm::max(obj.collider.boxSize * 0.5f, glm::vec3(0.01f));
|
||||||
|
shape = mPhysics->createShape(PxBoxGeometry(ToPxVec3(half)), *mDefaultMaterial, true);
|
||||||
|
minDim = std::min({half.x, half.y, half.z}) * 2.0f;
|
||||||
|
} else if (obj.collider.type == ColliderType::Capsule) {
|
||||||
|
float radius = std::max({obj.collider.boxSize.x, obj.collider.boxSize.z}) * 0.5f;
|
||||||
|
radius = std::max(radius, 0.01f);
|
||||||
|
float cylHeight = std::max(0.05f, obj.collider.boxSize.y - radius * 2.0f);
|
||||||
|
float halfHeight = cylHeight * 0.5f;
|
||||||
|
shape = mPhysics->createShape(PxCapsuleGeometry(radius, halfHeight), *mDefaultMaterial, true);
|
||||||
|
if (shape) {
|
||||||
|
// Rotate capsule so its axis matches the engine's Y-up expectation
|
||||||
|
shape->setLocalPose(PxTransform(PxQuat(PxHalfPi, PxVec3(0, 0, 1))));
|
||||||
|
}
|
||||||
|
minDim = std::min(radius * 2.0f, halfHeight * 2.0f);
|
||||||
|
} else {
|
||||||
|
std::vector<PxVec3> verts;
|
||||||
|
std::vector<uint32_t> indices;
|
||||||
|
if (!gatherMeshData(obj, verts, indices)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool useConvex = obj.collider.convex || obj.collider.type == ColliderType::ConvexMesh || isDynamic;
|
||||||
|
glm::vec3 boundsMin(FLT_MAX);
|
||||||
|
glm::vec3 boundsMax(-FLT_MAX);
|
||||||
|
for (auto& v : verts) {
|
||||||
|
boundsMin.x = std::min(boundsMin.x, v.x * obj.scale.x);
|
||||||
|
boundsMin.y = std::min(boundsMin.y, v.y * obj.scale.y);
|
||||||
|
boundsMin.z = std::min(boundsMin.z, v.z * obj.scale.z);
|
||||||
|
boundsMax.x = std::max(boundsMax.x, v.x * obj.scale.x);
|
||||||
|
boundsMax.y = std::max(boundsMax.y, v.y * obj.scale.y);
|
||||||
|
boundsMax.z = std::max(boundsMax.z, v.z * obj.scale.z);
|
||||||
|
}
|
||||||
|
minDim = std::max(0.01f, std::min({boundsMax.x - boundsMin.x, boundsMax.y - boundsMin.y, boundsMax.z - boundsMin.z}));
|
||||||
|
if (useConvex) {
|
||||||
|
PxConvexMesh* convex = cookConvexMesh(verts);
|
||||||
|
if (!convex) return false;
|
||||||
|
PxConvexMeshGeometry geom(convex, PxMeshScale(ToPxVec3(obj.scale), PxQuat(PxIdentity)));
|
||||||
|
shape = mPhysics->createShape(geom, *mDefaultMaterial, true);
|
||||||
|
convex->release();
|
||||||
|
} else {
|
||||||
|
PxTriangleMesh* tri = cookTriangleMesh(verts, indices);
|
||||||
|
if (!tri) return false;
|
||||||
|
PxTriangleMeshGeometry geom(tri, PxMeshScale(ToPxVec3(obj.scale), PxQuat(PxIdentity)));
|
||||||
|
shape = mPhysics->createShape(geom, *mDefaultMaterial, true);
|
||||||
|
tri->release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tuneShape(shape, std::max(0.01f, minDim), isDynamic || obj.hasPlayerController);
|
||||||
|
|
||||||
|
if (!shape) return false;
|
||||||
|
actor->attachShape(*shape);
|
||||||
|
shape->release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsSystem::ActorRecord PhysicsSystem::createActorFor(const SceneObject& obj) const {
|
||||||
|
ActorRecord record;
|
||||||
|
|
||||||
|
const bool wantsDynamic = obj.hasRigidbody && obj.rigidbody.enabled;
|
||||||
|
const bool wantsCollider = obj.hasCollider && obj.collider.enabled;
|
||||||
|
if (!wantsDynamic && !wantsCollider) {
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
PxTransform transform(ToPxVec3(obj.position), ToPxQuat(obj.rotation));
|
||||||
|
|
||||||
|
PxRigidActor* actor = wantsDynamic
|
||||||
|
? static_cast<PxRigidActor*>(mPhysics->createRigidDynamic(transform))
|
||||||
|
: static_cast<PxRigidActor*>(mPhysics->createRigidStatic(transform));
|
||||||
|
|
||||||
|
if (!actor) return record;
|
||||||
|
|
||||||
|
record.actor = actor;
|
||||||
|
record.isDynamic = wantsDynamic;
|
||||||
|
record.isKinematic = wantsDynamic && obj.rigidbody.isKinematic;
|
||||||
|
|
||||||
|
bool attached = false;
|
||||||
|
// Keep actor facing initial yaw (ignore pitch/roll)
|
||||||
|
if (PxRigidDynamic* dyn = actor->is<PxRigidDynamic>()) {
|
||||||
|
PxTransform pose = dyn->getGlobalPose();
|
||||||
|
pose.q = PxQuat(static_cast<float>(glm::radians(obj.rotation.y)), PxVec3(0, 1, 0));
|
||||||
|
dyn->setGlobalPose(pose);
|
||||||
|
} else {
|
||||||
|
PxTransform pose = actor->getGlobalPose();
|
||||||
|
pose.q = PxQuat(static_cast<float>(glm::radians(obj.rotation.y)), PxVec3(0, 1, 0));
|
||||||
|
actor->setGlobalPose(pose);
|
||||||
|
}
|
||||||
|
if (wantsCollider) {
|
||||||
|
attached = attachColliderShape(actor, obj, wantsDynamic);
|
||||||
|
}
|
||||||
|
if (!attached) {
|
||||||
|
attached = attachPrimitiveShape(actor, obj, wantsDynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!attached) {
|
||||||
|
actor->release();
|
||||||
|
record.actor = nullptr;
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PxRigidDynamic* dyn = actor->is<PxRigidDynamic>()) {
|
||||||
|
dyn->setAngularDamping(obj.rigidbody.angularDamping);
|
||||||
|
dyn->setLinearDamping(obj.rigidbody.linearDamping);
|
||||||
|
dyn->setRigidBodyFlag(PxRigidBodyFlag::eKINEMATIC, obj.rigidbody.isKinematic);
|
||||||
|
dyn->setActorFlag(PxActorFlag::eDISABLE_GRAVITY, !obj.rigidbody.useGravity);
|
||||||
|
dyn->setRigidDynamicLockFlags(PxRigidDynamicLockFlag::eLOCK_ANGULAR_X | PxRigidDynamicLockFlag::eLOCK_ANGULAR_Z);
|
||||||
|
if (obj.hasPlayerController) {
|
||||||
|
dyn->setRigidBodyFlag(PxRigidBodyFlag::eENABLE_CCD, true);
|
||||||
|
dyn->setMaxDepenetrationVelocity(1.5f);
|
||||||
|
}
|
||||||
|
if (!obj.rigidbody.isKinematic) {
|
||||||
|
PxRigidBodyExt::updateMassAndInertia(*dyn, std::max(0.01f, obj.rigidbody.mass));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsSystem::clearActors() {
|
||||||
|
for (auto& [id, rec] : mActors) {
|
||||||
|
if (rec.actor && mScene) {
|
||||||
|
mScene->removeActor(*rec.actor);
|
||||||
|
rec.actor->release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mActors.clear();
|
||||||
|
|
||||||
|
if (mGroundPlane && mScene) {
|
||||||
|
mScene->removeActor(*mGroundPlane);
|
||||||
|
mGroundPlane->release();
|
||||||
|
mGroundPlane = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsSystem::onPlayStart(const std::vector<SceneObject>& objects) {
|
||||||
|
if (!isReady()) return;
|
||||||
|
|
||||||
|
clearActors();
|
||||||
|
createGroundPlane();
|
||||||
|
|
||||||
|
for (const auto& obj : objects) {
|
||||||
|
ActorRecord rec = createActorFor(obj);
|
||||||
|
if (!rec.actor) continue;
|
||||||
|
mScene->addActor(*rec.actor);
|
||||||
|
mActors[obj.id] = rec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsSystem::onPlayStop() {
|
||||||
|
clearActors();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhysicsSystem::setLinearVelocity(int id, const glm::vec3& velocity) {
|
||||||
|
#ifdef MODULARITY_ENABLE_PHYSX
|
||||||
|
auto it = mActors.find(id);
|
||||||
|
if (it == mActors.end()) return false;
|
||||||
|
ActorRecord& rec = it->second;
|
||||||
|
if (!rec.actor || !rec.isDynamic) return false;
|
||||||
|
if (PxRigidDynamic* dyn = rec.actor->is<PxRigidDynamic>()) {
|
||||||
|
dyn->setLinearVelocity(ToPxVec3(velocity));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhysicsSystem::setActorYaw(int id, float yawDegrees) {
|
||||||
|
#ifdef MODULARITY_ENABLE_PHYSX
|
||||||
|
auto it = mActors.find(id);
|
||||||
|
if (it == mActors.end()) return false;
|
||||||
|
ActorRecord& rec = it->second;
|
||||||
|
if (!rec.actor) return false;
|
||||||
|
PxTransform pose = rec.actor->getGlobalPose();
|
||||||
|
PxQuat yawQuat(static_cast<float>(glm::radians(yawDegrees)), PxVec3(0, 1, 0));
|
||||||
|
pose.q = yawQuat;
|
||||||
|
rec.actor->setGlobalPose(pose);
|
||||||
|
if (PxRigidDynamic* dyn = rec.actor->is<PxRigidDynamic>()) {
|
||||||
|
dyn->setRigidDynamicLockFlags(PxRigidDynamicLockFlag::eLOCK_ANGULAR_X | PxRigidDynamicLockFlag::eLOCK_ANGULAR_Z);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhysicsSystem::getLinearVelocity(int id, glm::vec3& outVelocity) const {
|
||||||
|
#ifdef MODULARITY_ENABLE_PHYSX
|
||||||
|
auto it = mActors.find(id);
|
||||||
|
if (it == mActors.end()) return false;
|
||||||
|
const ActorRecord& rec = it->second;
|
||||||
|
if (!rec.actor || !rec.isDynamic) return false;
|
||||||
|
if (const PxRigidDynamic* dyn = rec.actor->is<PxRigidDynamic>()) {
|
||||||
|
PxVec3 v = dyn->getLinearVelocity();
|
||||||
|
outVelocity = glm::vec3(v.x, v.y, v.z);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhysicsSystem::setActorPose(int id, const glm::vec3& position, const glm::vec3& rotationDeg) {
|
||||||
|
#ifdef MODULARITY_ENABLE_PHYSX
|
||||||
|
auto it = mActors.find(id);
|
||||||
|
if (it == mActors.end()) return false;
|
||||||
|
ActorRecord& rec = it->second;
|
||||||
|
if (!rec.actor) return false;
|
||||||
|
PxTransform pose(ToPxVec3(position), ToPxQuat(rotationDeg));
|
||||||
|
rec.actor->setGlobalPose(pose);
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
(void)id; (void)position; (void)rotationDeg;
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PhysicsSystem::raycastClosest(const glm::vec3& origin, const glm::vec3& dir, float distance,
|
||||||
|
int ignoreId, glm::vec3* hitPos, glm::vec3* hitNormal, float* hitDistance) const {
|
||||||
|
#ifdef MODULARITY_ENABLE_PHYSX
|
||||||
|
if (!isReady() || distance <= 0.0f) return false;
|
||||||
|
PxVec3 unitDir = ToPxVec3(glm::normalize(dir));
|
||||||
|
if (!unitDir.isFinite()) return false;
|
||||||
|
|
||||||
|
PxRaycastBuffer hit;
|
||||||
|
PxQueryFilterData fd(PxQueryFlag::eSTATIC | PxQueryFlag::eDYNAMIC | PxQueryFlag::ePREFILTER);
|
||||||
|
IgnoreActorFilter cb(nullptr);
|
||||||
|
|
||||||
|
auto it = mActors.find(ignoreId);
|
||||||
|
if (it != mActors.end()) {
|
||||||
|
cb.ignore = it->second.actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = mScene->raycast(ToPxVec3(origin), unitDir, distance, hit,
|
||||||
|
PxHitFlag::ePOSITION | PxHitFlag::eNORMAL,
|
||||||
|
fd, cb.ignore ? &cb : nullptr);
|
||||||
|
if (!result || !hit.hasBlock) return false;
|
||||||
|
|
||||||
|
if (hitPos) *hitPos = ToGlmVec3(hit.block.position);
|
||||||
|
if (hitNormal) *hitNormal = ToGlmVec3(hit.block.normal);
|
||||||
|
if (hitDistance) *hitDistance = hit.block.distance;
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
(void)origin; (void)dir; (void)distance; (void)ignoreId; (void)hitPos; (void)hitNormal; (void)hitDistance;
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsSystem::simulate(float deltaTime, std::vector<SceneObject>& objects) {
|
||||||
|
if (!isReady() || deltaTime <= 0.0f) return;
|
||||||
|
|
||||||
|
// Sync actors to authoring transforms before stepping
|
||||||
|
for (auto& [id, rec] : mActors) {
|
||||||
|
if (!rec.actor) continue;
|
||||||
|
auto it = std::find_if(objects.begin(), objects.end(), [id](const SceneObject& o) { return o.id == id; });
|
||||||
|
if (it == objects.end()) continue;
|
||||||
|
if (PxRigidDynamic* dyn = rec.actor->is<PxRigidDynamic>()) {
|
||||||
|
if (dyn->getRigidBodyFlags().isSet(PxRigidBodyFlag::eKINEMATIC)) {
|
||||||
|
dyn->setKinematicTarget(PxTransform(ToPxVec3(it->position), ToPxQuat(it->rotation)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Static actors follow their authoring transform so scripted moves/rotations take effect
|
||||||
|
rec.actor->setGlobalPose(PxTransform(ToPxVec3(it->position), ToPxQuat(it->rotation)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mScene->simulate(deltaTime);
|
||||||
|
mScene->fetchResults(true);
|
||||||
|
|
||||||
|
for (auto& [id, rec] : mActors) {
|
||||||
|
if (!rec.actor || !rec.isDynamic || rec.isKinematic) continue;
|
||||||
|
PxTransform pose = rec.actor->getGlobalPose();
|
||||||
|
auto it = std::find_if(objects.begin(), objects.end(), [id](const SceneObject& o) { return o.id == id; });
|
||||||
|
if (it == objects.end()) continue;
|
||||||
|
|
||||||
|
it->position = ToGlmVec3(pose.p);
|
||||||
|
it->rotation.y = ToGlmEulerDeg(pose.q).y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsSystem::shutdown() {
|
||||||
|
clearActors();
|
||||||
|
|
||||||
|
if (mScene) { mScene->release(); mScene = nullptr; }
|
||||||
|
if (mDispatcher) { mDispatcher->release(); mDispatcher = nullptr; }
|
||||||
|
if (mPhysics) { mPhysics->release(); mPhysics = nullptr; }
|
||||||
|
if (mFoundation) { mFoundation->release(); mFoundation = nullptr; }
|
||||||
|
mDefaultMaterial = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // MODULARITY_ENABLE_PHYSX
|
||||||
|
|
||||||
|
bool PhysicsSystem::init() { return false; }
|
||||||
|
void PhysicsSystem::shutdown() {}
|
||||||
|
bool PhysicsSystem::isReady() const { return false; }
|
||||||
|
bool PhysicsSystem::setLinearVelocity(int, const glm::vec3&) { return false; }
|
||||||
|
bool PhysicsSystem::setActorYaw(int, float) { return false; }
|
||||||
|
bool PhysicsSystem::getLinearVelocity(int, glm::vec3&) const { return false; }
|
||||||
|
void PhysicsSystem::onPlayStart(const std::vector<SceneObject>&) {}
|
||||||
|
void PhysicsSystem::onPlayStop() {}
|
||||||
|
void PhysicsSystem::simulate(float, std::vector<SceneObject>&) {}
|
||||||
|
|
||||||
|
#endif
|
||||||
60
src/PhysicsSystem.h
Normal file
60
src/PhysicsSystem.h
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include "SceneObject.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef MODULARITY_ENABLE_PHYSX
|
||||||
|
#include "PxPhysicsAPI.h"
|
||||||
|
#include "cooking/PxCooking.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class PhysicsSystem {
|
||||||
|
public:
|
||||||
|
bool init();
|
||||||
|
void shutdown();
|
||||||
|
bool isReady() const;
|
||||||
|
bool setLinearVelocity(int id, const glm::vec3& velocity);
|
||||||
|
bool setActorYaw(int id, float yawDegrees);
|
||||||
|
bool getLinearVelocity(int id, glm::vec3& outVelocity) const;
|
||||||
|
bool setActorPose(int id, const glm::vec3& position, const glm::vec3& rotationDeg);
|
||||||
|
bool raycastClosest(const glm::vec3& origin, const glm::vec3& dir, float distance,
|
||||||
|
int ignoreId, glm::vec3* hitPos = nullptr,
|
||||||
|
glm::vec3* hitNormal = nullptr, float* hitDistance = nullptr) const;
|
||||||
|
|
||||||
|
void onPlayStart(const std::vector<SceneObject>& objects);
|
||||||
|
void onPlayStop();
|
||||||
|
void simulate(float deltaTime, std::vector<SceneObject>& objects);
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef MODULARITY_ENABLE_PHYSX
|
||||||
|
struct ActorRecord {
|
||||||
|
physx::PxRigidActor* actor = nullptr;
|
||||||
|
bool isDynamic = false;
|
||||||
|
bool isKinematic = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
physx::PxDefaultAllocator mAllocator;
|
||||||
|
physx::PxDefaultErrorCallback mErrorCallback;
|
||||||
|
physx::PxFoundation* mFoundation = nullptr;
|
||||||
|
physx::PxPhysics* mPhysics = nullptr;
|
||||||
|
physx::PxDefaultCpuDispatcher* mDispatcher = nullptr;
|
||||||
|
physx::PxScene* mScene = nullptr;
|
||||||
|
physx::PxMaterial* mDefaultMaterial = nullptr;
|
||||||
|
physx::PxRigidStatic* mGroundPlane = nullptr;
|
||||||
|
physx::PxCookingParams mCookParams{physx::PxTolerancesScale()};
|
||||||
|
|
||||||
|
std::unordered_map<int, ActorRecord> mActors;
|
||||||
|
|
||||||
|
void clearActors();
|
||||||
|
void createGroundPlane();
|
||||||
|
ActorRecord createActorFor(const SceneObject& obj) const;
|
||||||
|
bool attachColliderShape(physx::PxRigidActor* actor, const SceneObject& obj, bool isDynamic) const;
|
||||||
|
bool attachPrimitiveShape(physx::PxRigidActor* actor, const SceneObject& obj, bool isDynamic) const;
|
||||||
|
bool gatherMeshData(const SceneObject& obj, std::vector<physx::PxVec3>& vertices, std::vector<uint32_t>& indices) const;
|
||||||
|
physx::PxTriangleMesh* cookTriangleMesh(const std::vector<physx::PxVec3>& vertices,
|
||||||
|
const std::vector<uint32_t>& indices) const;
|
||||||
|
physx::PxConvexMesh* cookConvexMesh(const std::vector<physx::PxVec3>& vertices) const;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
@@ -258,7 +258,7 @@ bool SceneSerializer::saveScene(const fs::path& filePath,
|
|||||||
if (!file.is_open()) return false;
|
if (!file.is_open()) return false;
|
||||||
|
|
||||||
file << "# Scene File\n";
|
file << "# Scene File\n";
|
||||||
file << "version=4\n";
|
file << "version=7\n";
|
||||||
file << "nextId=" << nextId << "\n";
|
file << "nextId=" << nextId << "\n";
|
||||||
file << "objectCount=" << objects.size() << "\n";
|
file << "objectCount=" << objects.size() << "\n";
|
||||||
file << "\n";
|
file << "\n";
|
||||||
@@ -272,6 +272,31 @@ bool SceneSerializer::saveScene(const fs::path& filePath,
|
|||||||
file << "position=" << obj.position.x << "," << obj.position.y << "," << obj.position.z << "\n";
|
file << "position=" << obj.position.x << "," << obj.position.y << "," << obj.position.z << "\n";
|
||||||
file << "rotation=" << obj.rotation.x << "," << obj.rotation.y << "," << obj.rotation.z << "\n";
|
file << "rotation=" << obj.rotation.x << "," << obj.rotation.y << "," << obj.rotation.z << "\n";
|
||||||
file << "scale=" << obj.scale.x << "," << obj.scale.y << "," << obj.scale.z << "\n";
|
file << "scale=" << obj.scale.x << "," << obj.scale.y << "," << obj.scale.z << "\n";
|
||||||
|
file << "hasRigidbody=" << (obj.hasRigidbody ? 1 : 0) << "\n";
|
||||||
|
if (obj.hasRigidbody) {
|
||||||
|
file << "rbEnabled=" << (obj.rigidbody.enabled ? 1 : 0) << "\n";
|
||||||
|
file << "rbMass=" << obj.rigidbody.mass << "\n";
|
||||||
|
file << "rbUseGravity=" << (obj.rigidbody.useGravity ? 1 : 0) << "\n";
|
||||||
|
file << "rbKinematic=" << (obj.rigidbody.isKinematic ? 1 : 0) << "\n";
|
||||||
|
file << "rbLinearDamping=" << obj.rigidbody.linearDamping << "\n";
|
||||||
|
file << "rbAngularDamping=" << obj.rigidbody.angularDamping << "\n";
|
||||||
|
}
|
||||||
|
file << "hasCollider=" << (obj.hasCollider ? 1 : 0) << "\n";
|
||||||
|
if (obj.hasCollider) {
|
||||||
|
file << "colliderEnabled=" << (obj.collider.enabled ? 1 : 0) << "\n";
|
||||||
|
file << "colliderType=" << static_cast<int>(obj.collider.type) << "\n";
|
||||||
|
file << "colliderBox=" << obj.collider.boxSize.x << "," << obj.collider.boxSize.y << "," << obj.collider.boxSize.z << "\n";
|
||||||
|
file << "colliderConvex=" << (obj.collider.convex ? 1 : 0) << "\n";
|
||||||
|
}
|
||||||
|
file << "hasPlayerController=" << (obj.hasPlayerController ? 1 : 0) << "\n";
|
||||||
|
if (obj.hasPlayerController) {
|
||||||
|
file << "pcEnabled=" << (obj.playerController.enabled ? 1 : 0) << "\n";
|
||||||
|
file << "pcMoveSpeed=" << obj.playerController.moveSpeed << "\n";
|
||||||
|
file << "pcLookSensitivity=" << obj.playerController.lookSensitivity << "\n";
|
||||||
|
file << "pcHeight=" << obj.playerController.height << "\n";
|
||||||
|
file << "pcRadius=" << obj.playerController.radius << "\n";
|
||||||
|
file << "pcJumpStrength=" << obj.playerController.jumpStrength << "\n";
|
||||||
|
}
|
||||||
file << "materialColor=" << obj.material.color.r << "," << obj.material.color.g << "," << obj.material.color.b << "\n";
|
file << "materialColor=" << obj.material.color.r << "," << obj.material.color.g << "," << obj.material.color.b << "\n";
|
||||||
file << "materialAmbient=" << obj.material.ambientStrength << "\n";
|
file << "materialAmbient=" << obj.material.ambientStrength << "\n";
|
||||||
file << "materialSpecular=" << obj.material.specularStrength << "\n";
|
file << "materialSpecular=" << obj.material.specularStrength << "\n";
|
||||||
@@ -433,6 +458,47 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
|||||||
¤tObj->scale.x,
|
¤tObj->scale.x,
|
||||||
¤tObj->scale.y,
|
¤tObj->scale.y,
|
||||||
¤tObj->scale.z);
|
¤tObj->scale.z);
|
||||||
|
} else if (key == "hasRigidbody") {
|
||||||
|
currentObj->hasRigidbody = std::stoi(value) != 0;
|
||||||
|
} else if (key == "rbEnabled") {
|
||||||
|
currentObj->rigidbody.enabled = std::stoi(value) != 0;
|
||||||
|
} else if (key == "rbMass") {
|
||||||
|
currentObj->rigidbody.mass = std::stof(value);
|
||||||
|
} else if (key == "rbUseGravity") {
|
||||||
|
currentObj->rigidbody.useGravity = std::stoi(value) != 0;
|
||||||
|
} else if (key == "rbKinematic") {
|
||||||
|
currentObj->rigidbody.isKinematic = std::stoi(value) != 0;
|
||||||
|
} else if (key == "rbLinearDamping") {
|
||||||
|
currentObj->rigidbody.linearDamping = std::stof(value);
|
||||||
|
} else if (key == "rbAngularDamping") {
|
||||||
|
currentObj->rigidbody.angularDamping = std::stof(value);
|
||||||
|
} else if (key == "hasCollider") {
|
||||||
|
currentObj->hasCollider = std::stoi(value) != 0;
|
||||||
|
} else if (key == "colliderEnabled") {
|
||||||
|
currentObj->collider.enabled = std::stoi(value) != 0;
|
||||||
|
} else if (key == "colliderType") {
|
||||||
|
currentObj->collider.type = static_cast<ColliderType>(std::stoi(value));
|
||||||
|
} else if (key == "colliderBox") {
|
||||||
|
sscanf(value.c_str(), "%f,%f,%f",
|
||||||
|
¤tObj->collider.boxSize.x,
|
||||||
|
¤tObj->collider.boxSize.y,
|
||||||
|
¤tObj->collider.boxSize.z);
|
||||||
|
} else if (key == "colliderConvex") {
|
||||||
|
currentObj->collider.convex = std::stoi(value) != 0;
|
||||||
|
} else if (key == "hasPlayerController") {
|
||||||
|
currentObj->hasPlayerController = std::stoi(value) != 0;
|
||||||
|
} else if (key == "pcEnabled") {
|
||||||
|
currentObj->playerController.enabled = std::stoi(value) != 0;
|
||||||
|
} else if (key == "pcMoveSpeed") {
|
||||||
|
currentObj->playerController.moveSpeed = std::stof(value);
|
||||||
|
} else if (key == "pcLookSensitivity") {
|
||||||
|
currentObj->playerController.lookSensitivity = std::stof(value);
|
||||||
|
} else if (key == "pcHeight") {
|
||||||
|
currentObj->playerController.height = std::stof(value);
|
||||||
|
} else if (key == "pcRadius") {
|
||||||
|
currentObj->playerController.radius = std::stof(value);
|
||||||
|
} else if (key == "pcJumpStrength") {
|
||||||
|
currentObj->playerController.jumpStrength = std::stof(value);
|
||||||
} else if (key == "materialColor") {
|
} else if (key == "materialColor") {
|
||||||
sscanf(value.c_str(), "%f,%f,%f",
|
sscanf(value.c_str(), "%f,%f,%f",
|
||||||
¤tObj->material.color.r,
|
¤tObj->material.color.r,
|
||||||
|
|||||||
@@ -98,6 +98,41 @@ struct ScriptComponent {
|
|||||||
std::vector<void*> activeIEnums; // function pointers registered via IEnum_Start
|
std::vector<void*> activeIEnums; // function pointers registered via IEnum_Start
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct RigidbodyComponent {
|
||||||
|
bool enabled = true;
|
||||||
|
float mass = 1.0f;
|
||||||
|
bool useGravity = true;
|
||||||
|
bool isKinematic = false;
|
||||||
|
float linearDamping = 0.05f;
|
||||||
|
float angularDamping = 0.05f;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ColliderType {
|
||||||
|
Box = 0,
|
||||||
|
Mesh = 1,
|
||||||
|
ConvexMesh = 2,
|
||||||
|
Capsule = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ColliderComponent {
|
||||||
|
bool enabled = true;
|
||||||
|
ColliderType type = ColliderType::Box;
|
||||||
|
glm::vec3 boxSize = glm::vec3(1.0f);
|
||||||
|
bool convex = true; // For mesh colliders: true = convex hull, false = triangle mesh (static only)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PlayerControllerComponent {
|
||||||
|
bool enabled = true;
|
||||||
|
float moveSpeed = 6.0f;
|
||||||
|
float lookSensitivity = 0.12f;
|
||||||
|
float height = 1.8f;
|
||||||
|
float radius = 0.4f;
|
||||||
|
float jumpStrength = 6.5f;
|
||||||
|
float verticalVelocity = 0.0f;
|
||||||
|
float pitch = 0.0f;
|
||||||
|
float yaw = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
class SceneObject {
|
class SceneObject {
|
||||||
public:
|
public:
|
||||||
std::string name;
|
std::string name;
|
||||||
@@ -124,6 +159,12 @@ public:
|
|||||||
PostFXSettings postFx; // Only used when type is PostFXNode
|
PostFXSettings postFx; // Only used when type is PostFXNode
|
||||||
std::vector<ScriptComponent> scripts;
|
std::vector<ScriptComponent> scripts;
|
||||||
std::vector<std::string> additionalMaterialPaths;
|
std::vector<std::string> additionalMaterialPaths;
|
||||||
|
bool hasRigidbody = false;
|
||||||
|
RigidbodyComponent rigidbody;
|
||||||
|
bool hasCollider = false;
|
||||||
|
ColliderComponent collider;
|
||||||
|
bool hasPlayerController = false;
|
||||||
|
PlayerControllerComponent playerController;
|
||||||
|
|
||||||
SceneObject(const std::string& name, ObjectType type, int id)
|
SceneObject(const std::string& name, ObjectType type, int id)
|
||||||
: name(name), type(type), position(0.0f), rotation(0.0f), scale(1.0f), id(id) {}
|
: name(name), type(type), position(0.0f), rotation(0.0f), scale(1.0f), id(id) {}
|
||||||
|
|||||||
@@ -39,6 +39,28 @@ void ScriptContext::SetScale(const glm::vec3& scl) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ScriptContext::HasRigidbody() const {
|
||||||
|
return object && object->hasRigidbody && object->rigidbody.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptContext::SetRigidbodyVelocity(const glm::vec3& velocity) {
|
||||||
|
if (!engine || !object || !HasRigidbody()) return false;
|
||||||
|
return engine->setRigidbodyVelocityFromScript(object->id, velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptContext::GetRigidbodyVelocity(glm::vec3& outVelocity) const {
|
||||||
|
if (!engine || !object || !HasRigidbody()) return false;
|
||||||
|
return engine->getRigidbodyVelocityFromScript(object->id, outVelocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScriptContext::TeleportRigidbody(const glm::vec3& pos, const glm::vec3& rotDeg) {
|
||||||
|
if (!engine || !object) return false;
|
||||||
|
object->position = pos;
|
||||||
|
object->rotation = NormalizeEulerDegrees(rotDeg);
|
||||||
|
MarkDirty();
|
||||||
|
return engine->teleportPhysicsActorFromScript(object->id, pos, object->rotation);
|
||||||
|
}
|
||||||
|
|
||||||
std::string ScriptContext::GetSetting(const std::string& key, const std::string& fallback) const {
|
std::string ScriptContext::GetSetting(const std::string& key, const std::string& fallback) const {
|
||||||
if (!script) return fallback;
|
if (!script) return fallback;
|
||||||
auto it = std::find_if(script->settings.begin(), script->settings.end(),
|
auto it = std::find_if(script->settings.begin(), script->settings.end(),
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ struct ScriptContext {
|
|||||||
void SetPosition(const glm::vec3& pos);
|
void SetPosition(const glm::vec3& pos);
|
||||||
void SetRotation(const glm::vec3& rot);
|
void SetRotation(const glm::vec3& rot);
|
||||||
void SetScale(const glm::vec3& scl);
|
void SetScale(const glm::vec3& scl);
|
||||||
|
bool HasRigidbody() const;
|
||||||
|
bool SetRigidbodyVelocity(const glm::vec3& velocity);
|
||||||
|
bool GetRigidbodyVelocity(glm::vec3& outVelocity) const;
|
||||||
|
bool TeleportRigidbody(const glm::vec3& pos, const glm::vec3& rotDeg);
|
||||||
// Settings helpers (auto-mark dirty)
|
// Settings helpers (auto-mark dirty)
|
||||||
std::string GetSetting(const std::string& key, const std::string& fallback = "") const;
|
std::string GetSetting(const std::string& key, const std::string& fallback = "") const;
|
||||||
void SetSetting(const std::string& key, const std::string& value);
|
void SetSetting(const std::string& key, const std::string& value);
|
||||||
|
|||||||
Reference in New Issue
Block a user