diff --git a/.github/workflows/build-package.yml b/.github/workflows/build-package.yml new file mode 100644 index 00000000..afbaf4a9 --- /dev/null +++ b/.github/workflows/build-package.yml @@ -0,0 +1,126 @@ +name: Build & Package BetterVR + +on: + workflow_dispatch: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: windows-2022 + + defaults: + run: + shell: pwsh + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up MSVC dev environment + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: Install Vulkan SDK (direct from LunarG) + run: | + $ErrorActionPreference = "Stop" + + $latest = Invoke-RestMethod "https://vulkan.lunarg.com/sdk/latest/windows.json" + $ver = $latest.windows + if (-not $ver) { throw "Failed to determine latest Vulkan SDK version." } + Write-Host "Latest Vulkan SDK version: $ver" + + $exe = "vulkansdk-windows-X64-$ver.exe" + $urlNew = "https://sdk.lunarg.com/sdk/download/$ver/windows/$exe" + $urlOld = "https://sdk.lunarg.com/sdk/download/$ver/windows/VulkanSDK-$ver-Installer.exe" + + try { + Write-Host "Downloading: $urlNew" + Invoke-WebRequest $urlNew -OutFile $exe + } catch { + Write-Host "New-name download failed; trying legacy filename..." + Write-Host "Downloading: $urlOld" + Invoke-WebRequest $urlOld -OutFile $exe + } + + $installRoot = "C:\VulkanSDK\$ver" + New-Item -ItemType Directory -Force -Path $installRoot | Out-Null + + & ".\$exe" --root $installRoot --accept-licenses --default-answer --confirm-command install com.lunarg.vulkan.debug copy_only=1 + + "VULKAN_SDK=$installRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + "VK_SDK_PATH=$installRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + "$installRoot\Bin" | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8 + + Write-Host "Installed Vulkan SDK to $installRoot" + + - name: Clone vcpkg + run: | + git clone https://github.com/microsoft/vcpkg "$env:GITHUB_WORKSPACE\vcpkg" + + - name: Bootstrap vcpkg + run: | + & "$env:GITHUB_WORKSPACE\vcpkg\bootstrap-vcpkg.bat" -disableMetrics + + - name: Export vcpkg environment for scripts + run: | + "VCPKG_ROOT=$env:GITHUB_WORKSPACE\vcpkg" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + "VCPKG_DEFAULT_TRIPLET=x64-windows-static-md" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + + - name: Install vcpkg dependencies (manifest mode) + run: | + $ErrorActionPreference = "Stop" + $vcpkg = "$env:GITHUB_WORKSPACE\vcpkg\vcpkg.exe" + + if (!(Test-Path "$env:GITHUB_WORKSPACE\vcpkg.json")) { + throw "vcpkg.json not found at repo root (expected manifest mode)." + } + + & $vcpkg install ` + --x-manifest-root "$env:GITHUB_WORKSPACE" ` + --triplet "x64-windows-static-md" + + - name: Patch CMakeUserPresets.json (set VCPKG_ROOT for all presets) + run: | + $presetPath = Join-Path $env:GITHUB_WORKSPACE "CMakeUserPresets.json" + if (!(Test-Path $presetPath)) { throw "CMakeUserPresets.json not found at repo root." } + + $vcpkgRoot = Join-Path $env:GITHUB_WORKSPACE "vcpkg" + $json = Get-Content $presetPath -Raw | ConvertFrom-Json + + if ($null -eq $json.configurePresets) { throw "configurePresets missing in CMakeUserPresets.json" } + + foreach ($p in $json.configurePresets) { + if ($null -eq $p.environment) { $p | Add-Member -NotePropertyName environment -NotePropertyValue (@{}) } + $p.environment.VCPKG_ROOT = $vcpkgRoot + } + + $json | ConvertTo-Json -Depth 50 | Set-Content $presetPath -Encoding UTF8 + Write-Host "Patched VCPKG_ROOT to: $vcpkgRoot" + + - name: Build (build_mod.bat) + run: | + if (!(Test-Path "$env:GITHUB_WORKSPACE\build_mod.bat")) { + throw "build_mod.bat not found at repo root." + } + cmd /c build_mod.bat RelWithDebInfo + + - name: Package (package_mod.bat) + run: | + if (!(Test-Path "$env:GITHUB_WORKSPACE\package_mod.bat")) { + throw "package_mod.bat not found at repo root." + } + cmd /c package_mod.bat + + - name: Upload package zip (no extra repackaging step) + uses: actions/upload-artifact@v4 + with: + name: BetterVR-package + path: | + *.zip + if-no-files-found: error diff --git a/build_mod.bat b/build_mod.bat index e40edead..c1331d8b 100644 --- a/build_mod.bat +++ b/build_mod.bat @@ -1,3 +1,14 @@ -REM rmdir /Q /S build -cmake -G "Visual Studio 17 2022" -A x64 --preset default -B ./build -cmake --build ./build --config Debug --target ALL_BUILD \ No newline at end of file +@echo off +setlocal + +set PRESET=%1 +if "%PRESET%"=="" set PRESET=RelWithDebInfo + +if exist build rmdir /Q /S build + +cmake --preset %PRESET% -B build + +REM Build default target (works for Ninja and VS) +cmake --build build --config %PRESET% + +endlocal \ No newline at end of file diff --git a/package_mod.bat b/package_mod.bat index 5ee3acd5..f8851fc2 100644 --- a/package_mod.bat +++ b/package_mod.bat @@ -1,5 +1,5 @@ @echo off -setlocal +setlocal EnableExtensions EnableDelayedExpansion set "ZIP_NAME=BetterVR.zip" set "TEMP_DIR=temp_package" @@ -12,74 +12,39 @@ REM Create directory structure mkdir "%TEMP_DIR%" mkdir "%TEMP_DIR%\BreathOfTheWild_BetterVR" -REM Copy DLL -if exist "Cemu\BetterVR_Layer.dll" ( - copy "Cemu\BetterVR_Layer.dll" "%TEMP_DIR%\" >nul - echo Copied BetterVR_Layer.dll -) else ( - echo Error: Cemu\BetterVR_Layer.dll not found. - goto :error -) - -REM Copy JSON -if exist "Cemu\BetterVR_Layer.json" ( - copy "Cemu\BetterVR_Layer.json" "%TEMP_DIR%\" >nul - echo Copied BetterVR_Layer.json -) else ( - echo Error: Cemu\BetterVR_Layer.json not found. - goto :error -) - -REM Copy LIB -if exist "Cemu\BetterVR_Layer.lib" ( - copy "Cemu\BetterVR_Layer.lib" "%TEMP_DIR%\" >nul - echo Copied BetterVR_Layer.lib -) else ( - echo Error: Cemu\BetterVR_Layer.lib not found. - goto :error -) - -REM Copy PDB -if exist "Cemu\BetterVR_Layer.pdb" ( - copy "Cemu\BetterVR_Layer.pdb" "%TEMP_DIR%\" >nul - echo Copied BetterVR_Layer.pdb -) else ( - echo Error: Cemu\BetterVR_Layer.pdb not found. - goto :error -) +REM ------------------------- +REM Helper: locate a file +REM Usage: call :findfile VAR "filename" +REM ------------------------- +call :findfile DLL_PATH "BetterVR_Layer.dll" +call :findfile JSON_PATH "BetterVR_Layer.json" +call :findfile LIB_PATH "BetterVR_Layer.lib" +call :findfile PDB_PATH "BetterVR_Layer.pdb" -REM Copy bat -if exist "resources\BetterVR LAUNCH CEMU IN VR.bat" ( - copy "resources\BetterVR LAUNCH CEMU IN VR.bat" "%TEMP_DIR%\" >nul - echo Copied BetterVR LAUNCH CEMU IN VR.bat -) else ( - echo Error: resources\BetterVR LAUNCH CEMU IN VR.bat not found. - goto :error -) +if not defined DLL_PATH (echo Error: BetterVR_Layer.dll not found.& goto :error) +if not defined JSON_PATH (echo Error: BetterVR_Layer.json not found.& goto :error) +if not defined LIB_PATH (echo Error: BetterVR_Layer.lib not found.& goto :error) +if not defined PDB_PATH (echo Error: BetterVR_Layer.pdb not found.& goto :error) -REM Copy compat bat -if exist "resources\BetterVR LAUNCH CEMU IN VR - COMPATIBILITY MODE.bat" ( - copy "resources\BetterVR LAUNCH CEMU IN VR - COMPATIBILITY MODE.bat" "%TEMP_DIR%\" >nul - echo Copied BetterVR LAUNCH CEMU IN VR - COMPATIBILITY MODE.bat -) else ( - echo Error: resources\BetterVR LAUNCH CEMU IN VR - COMPATIBILITY MODE.bat not found. - goto :error -) +echo Using: +echo DLL: "!DLL_PATH!" +echo JSON: "!JSON_PATH!" +echo LIB: "!LIB_PATH!" +echo PDB: "!PDB_PATH!" -REM Copy uninstall bat -if exist "resources\BetterVR UNINSTALL.bat" ( - copy "resources\BetterVR UNINSTALL.bat" "%TEMP_DIR%\" >nul - echo Copied BetterVR UNINSTALL.bat -) else ( - echo Error: resources\BetterVR UNINSTALL.bat not found. - goto :error -) +copy "!DLL_PATH!" "%TEMP_DIR%\" >nul || goto :error +copy "!JSON_PATH!" "%TEMP_DIR%\" >nul || goto :error +copy "!LIB_PATH!" "%TEMP_DIR%\" >nul || goto :error +copy "!PDB_PATH!" "%TEMP_DIR%\" >nul || goto :error +REM Copy launch scripts +call :copyreq "resources\BetterVR LAUNCH CEMU IN VR.bat" "%TEMP_DIR%\BetterVR LAUNCH CEMU IN VR.bat" +call :copyreq "resources\BetterVR LAUNCH CEMU IN VR - COMPATIBILITY MODE.bat" "%TEMP_DIR%\BetterVR LAUNCH CEMU IN VR - COMPATIBILITY MODE.bat" +call :copyreq "resources\BetterVR UNINSTALL.bat" "%TEMP_DIR%\BetterVR UNINSTALL.bat" REM Copy Graphic Packs if exist "resources\BreathOfTheWild_BetterVR" ( - xcopy /E /I /Y "resources\BreathOfTheWild_BetterVR" "%TEMP_DIR%\BreathOfTheWild_BetterVR" >nul - echo Copied BreathOfTheWild_BetterVR resources + xcopy /E /I /Y "resources\BreathOfTheWild_BetterVR" "%TEMP_DIR%\BreathOfTheWild_BetterVR" >nul || goto :error ) else ( echo Error: resources\BreathOfTheWild_BetterVR not found. goto :error @@ -87,16 +52,47 @@ if exist "resources\BreathOfTheWild_BetterVR" ( REM Create Zip echo Creating %ZIP_NAME%... -powershell -Command "Compress-Archive -Path '%TEMP_DIR%\*' -DestinationPath '%ZIP_NAME%'" +powershell -NoProfile -Command "Compress-Archive -Force -Path '%TEMP_DIR%\*' -DestinationPath '%ZIP_NAME%'" || goto :error REM Cleanup rmdir /S /Q "%TEMP_DIR%" echo Package created successfully: %ZIP_NAME% -PAUSE -goto :eof +exit /b 0 + +:copyreq +if exist "%~1" ( + copy "%~1" "%~2" >nul || exit /b 1 + exit /b 0 +) else ( + echo Error: %~1 not found. + exit /b 1 +) + +:findfile +set "%~1=" +set "FN=%~2" + +REM Preferred locations first (fast) +for %%P in ( + "Cemu\%FN%" + "bin\%FN%" + "build\bin\%FN%" + "build\%FN%" +) do ( + if exist "%%~P" ( + set "%~1=%%~P" + exit /b 0 + ) +) + +REM Fallback: search repo (slower, but robust) +for /f "delims=" %%F in ('dir /b /s "%FN%" 2^>nul') do ( + set "%~1=%%F" + exit /b 0 +) +exit /b 0 :error echo Failed to create package. -PAUSE if exist "%TEMP_DIR%" rmdir /S /Q "%TEMP_DIR%" -exit /b 1 \ No newline at end of file +exit /b 1 diff --git a/resources/BreathOfTheWild_BetterVR/content/Pack/Bootup.pack b/resources/BreathOfTheWild_BetterVR/content/Pack/Bootup.pack new file mode 100644 index 00000000..a4eeb309 Binary files /dev/null and b/resources/BreathOfTheWild_BetterVR/content/Pack/Bootup.pack differ diff --git a/resources/BreathOfTheWild_BetterVR/content/System/Resource/ResourceSizeTable.product.srsizetable b/resources/BreathOfTheWild_BetterVR/content/System/Resource/ResourceSizeTable.product.srsizetable new file mode 100644 index 00000000..4c259640 Binary files /dev/null and b/resources/BreathOfTheWild_BetterVR/content/System/Resource/ResourceSizeTable.product.srsizetable differ diff --git a/resources/BreathOfTheWild_BetterVR/patch_CTRL_ButtonInput.asm b/resources/BreathOfTheWild_BetterVR/patch_CTRL_ButtonInput.asm index 689def3b..6ecf0d08 100644 --- a/resources/BreathOfTheWild_BetterVR/patch_CTRL_ButtonInput.asm +++ b/resources/BreathOfTheWild_BetterVR/patch_CTRL_ButtonInput.asm @@ -44,7 +44,6 @@ li r3, 0 stw r3, 0x00(r7) li r3, 1 - exitHookInput: ; epilogue lwz r7, 0x18(r1) diff --git a/resources/BreathOfTheWild_BetterVR/patch_Misc.asm b/resources/BreathOfTheWild_BetterVR/patch_Misc.asm index 6d1a348b..f835ab9b 100644 --- a/resources/BreathOfTheWild_BetterVR/patch_Misc.asm +++ b/resources/BreathOfTheWild_BetterVR/patch_Misc.asm @@ -12,10 +12,12 @@ moduleMatches = 0x6267BFD0 ; disable AutoExposure ; 0x039D99A4 = li r3, 0 -; disable gyro controls -0x02E1905C = li r3, 0 ; always return 0 to signal disabled +; native controls toggle +; $nativeCtrlEnabled = 0 -> Use VR Controls +; $nativeCtrlEnabled = 1 -> Use Standard Controls +0x02E1905C = li r3, $nativeCtrlEnabled 0x02E19060 = blr -0x02E19064 = li r5, 0 ; always set 0 +0x02E19064 = li r5, $nativeCtrlEnabled ; disable inversed button controls 0x02E199AC = li r3, 0 ; return 0 to signal disabled @@ -136,4 +138,4 @@ moduleMatches = 0x6267BFD0 ;0x02C196A4 = li r3, 1 -;0x024B6F40 = li r12, 0 \ No newline at end of file +;0x024B6F40 = li r12, 0 diff --git a/resources/BreathOfTheWild_BetterVR/rules.txt b/resources/BreathOfTheWild_BetterVR/rules.txt index f53092e1..0f9bf125 100644 --- a/resources/BreathOfTheWild_BetterVR/rules.txt +++ b/resources/BreathOfTheWild_BetterVR/rules.txt @@ -8,6 +8,7 @@ default = 1 [Default] $enableDebugOverlay:int = 0 +$nativeCtrlEnabled:int = 0 $accessibilityUIColor:int = 0 $accessibilityUIColor_x = 0.000 @@ -15,6 +16,17 @@ $accessibilityUIColor_y = 0.804 $accessibilityUIColor_z = 1.000 $accessibilityUIColor_w = 1.000 +[Preset] +name = VR Controls +category = Input +default = 1 +$nativeCtrlEnabled:int = 0 + +[Preset] +name = Standard Controls +category = Input +$nativeCtrlEnabled:int = 1 + # Accessibility UI Color [Preset] @@ -39,4 +51,4 @@ $accessibilityUIColor:int = 1 $accessibilityUIColor_x = 0.6728 $accessibilityUIColor_y = 0.0 $accessibilityUIColor_z = 0.3272 -$accessibilityUIColor_w = 1.000 \ No newline at end of file +$accessibilityUIColor_w = 1.000 diff --git a/src/hooking/camera.cpp b/src/hooking/camera.cpp index f86c9381..6b64f740 100644 --- a/src/hooking/camera.cpp +++ b/src/hooking/camera.cpp @@ -71,6 +71,63 @@ static glm::mat4 calculateProjectionMatrix(float nearZ, float farZ, const XrFovf return dst; } +static glm::fvec3 ApplyCameraModeEyePosPolicy(glm::fvec3 eyePos, CameraMode cameraMode) { + if (cameraMode == CameraMode::ORIGINAL) { + eyePos.y = 0.0f; + + auto gameState = VRManager::instance().XR->m_gameState.load(); + if (gameState.is_riding_mount) { + eyePos.y += GetSettings().GetOriginalRidingVerticalOffset(); + } + } + + return eyePos; +} + +static glm::fquat ApplyCameraModeEyeRotPolicy(const glm::fquat& baseYaw, const glm::fquat& baseRot, const glm::fquat& eyeRot, CameraMode cameraMode) { + if (cameraMode == CameraMode::ORIGINAL) { + return baseRot * eyeRot; + } + + return baseYaw * eyeRot; +} + +static glm::fvec3 ApplyCameraModeEyeOffsetPolicy(const glm::fquat& baseYaw, const glm::fquat& baseRot, const glm::fvec3& eyePos, CameraMode cameraMode) { + if (cameraMode == CameraMode::ORIGINAL) { + return baseRot * eyePos; + } + + return baseYaw * eyePos; +} + +static float GetStereoDepthScaleForCurrentContext() { + if (CemuHooks::HasActiveCutscene()) { + return GetSettings().GetCutsceneStereoDepthScale(); + } + + return GetSettings().GetGameplayStereoDepthScale(); +} + +static glm::fvec3 ApplyStereoDepthScalePolicy(const glm::fvec3& eyePos) { + auto middlePoseOpt = VRManager::instance().XR->GetRenderer()->GetMiddlePose(); + if (!middlePoseOpt.has_value()) { + return eyePos; + } + + const glm::fvec3 middleEyePos = glm::fvec3(middlePoseOpt.value()[3]); + const float depthScale = GetStereoDepthScaleForCurrentContext(); + return middleEyePos + (eyePos - middleEyePos) * depthScale; +} + +static void SetLookAtCameraVectorsFromPose(BESeadLookAtCamera& camera, const glm::vec3& position, const glm::fquat& rotation) { + const glm::vec3 forward = glm::normalize(rotation * glm::fvec3(0.0f, 0.0f, -1.0f)); + const glm::vec3 up = glm::normalize(rotation * glm::fvec3(0.0f, 1.0f, 0.0f)); + + camera.pos = position; + camera.at = position + forward; + camera.up = up; +} + float hardcodedSwimOffset = 0.0f; float hardcodedRidingOffset = 0.65f; @@ -125,23 +182,25 @@ void CemuHooks::hook_UpdateCameraForGameplay(PPCInterpreter_t* hCPU) { s_wsCameraPosition = oldCameraPosition; s_wsCameraRotation = glm::quat_cast(glm::inverse(existingGameMtx)); - // rebase the rotation to the player position - if (IsFirstPerson()) { - // check if player is swimming - Player actor; - readMemory(s_playerAddress, &actor); + // check if player is swimming + Player actor; + readMemory(s_playerAddress, &actor); - PlayerMoveBitFlags moveBits = actor.moveBitFlags.getLE(); - s_isSwimming = HAS_FLAG(moveBits, PlayerMoveBitFlags::IS_SWIMMING_OR_CLIMBING | PlayerMoveBitFlags::IS_SWIMMING); - s_isCrouching = HAS_FLAG(moveBits, PlayerMoveBitFlags::IS_CROUCHING); + PlayerMoveBitFlags moveBits = actor.moveBitFlags.getLE(); + s_isSwimming = HAS_FLAG(moveBits, PlayerMoveBitFlags::IS_SWIMMING_OR_CLIMBING | PlayerMoveBitFlags::IS_SWIMMING); + s_isCrouching = HAS_FLAG(moveBits, PlayerMoveBitFlags::IS_CROUCHING); - // Todo: move those and their hooks in controls.cpp ? - auto gameState = VRManager::instance().XR->m_gameState.load(); - // Unreliable flag, need to investigate - gameState.is_climbing = HAS_FLAG(moveBits, PlayerMoveBitFlags::IS_SWIMMING_OR_CLIMBING | PlayerMoveBitFlags::IS_CLIMBING_WALL) || s_isLadderClimbing == 2; - gameState.is_riding_mount = (s_isRiding == 2 || s_isRidingSandSeal == 2) ? true : false; - gameState.is_paragliding = HAS_FLAG(moveBits, PlayerMoveBitFlags::IS_GLIDER_ACTIVE); - VRManager::instance().XR->m_gameState.store(gameState); + // Todo: move those and their hooks in controls.cpp ? + auto gameState = VRManager::instance().XR->m_gameState.load(); + // Unreliable flag, need to investigate + gameState.is_climbing = HAS_FLAG(moveBits, PlayerMoveBitFlags::IS_SWIMMING_OR_CLIMBING | PlayerMoveBitFlags::IS_CLIMBING_WALL) || s_isLadderClimbing == 2; + gameState.is_riding_mount = (s_isRiding == 2 || s_isRidingSandSeal == 2) ? true : false; + gameState.is_paragliding = HAS_FLAG(moveBits, PlayerMoveBitFlags::IS_GLIDER_ACTIVE); + VRManager::instance().XR->m_gameState.store(gameState); + + // rebase the rotation to the player position + if (IsFirstPerson()) { + auto now = std::chrono::steady_clock::now(); std::chrono::milliseconds crouchLerpDuration{ 150 }; @@ -192,12 +251,7 @@ void CemuHooks::hook_UpdateCameraForGameplay(PPCInterpreter_t* hCPU) { if (s_isLadderClimbing > 0) { s_isLadderClimbing--; } - if (s_isRiding > 0) { - s_isRiding--; - } - if (s_isRidingSandSeal > 0) { - s_isRidingSandSeal--; - } + s_wasCrouching = s_isCrouching; @@ -205,6 +259,13 @@ void CemuHooks::hook_UpdateCameraForGameplay(PPCInterpreter_t* hCPU) { existingGameMtx = playerMtx4; } + if (s_isRiding > 0) { + s_isRiding--; + } + if (s_isRidingSandSeal > 0) { + s_isRidingSandSeal--; + } + // current VR headset camera matrix auto viewsOpt = VRManager::instance().XR->GetRenderer()->GetMiddlePose(); if (!viewsOpt) { @@ -212,9 +273,14 @@ void CemuHooks::hook_UpdateCameraForGameplay(PPCInterpreter_t* hCPU) { return; } auto& views = viewsOpt.value(); + glm::mat4 viewsForGameplay = views; + + glm::fvec3 middleEyePos = glm::fvec3(viewsForGameplay[3]); + middleEyePos = ApplyCameraModeEyePosPolicy(middleEyePos, GetSettings().GetCameraMode()); + viewsForGameplay[3] = glm::fvec4(middleEyePos, viewsForGameplay[3].w); // calculate final camera matrix - glm::mat4 finalPose = glm::inverse(existingGameMtx) * views; + glm::mat4 finalPose = glm::inverse(existingGameMtx) * viewsForGameplay; // extract camera up, forward and position from the final matrix glm::fvec3 camPos = glm::fvec3(finalPose[3]); @@ -238,7 +304,7 @@ void CemuHooks::hook_UpdateCameraForGameplay(PPCInterpreter_t* hCPU) { void CemuHooks::hook_FixStaminaGaugeScreenPosition(PPCInterpreter_t* hCPU) { hCPU->instructionPointer = hCPU->sprNew.LR; - if (IsThirdPerson()) { + if (IsThirdPerson() && GetSettings().GetCameraMode() != CameraMode::ORIGINAL) { return; } @@ -264,7 +330,7 @@ void CemuHooks::hook_FixExtraStaminaGaugeIconPositions(PPCInterpreter_t* hCPU) { // original instruction that got replaced hCPU->fpr[29].fp0 = 1.0f; - if (IsThirdPerson()) { + if (IsThirdPerson() && GetSettings().GetCameraMode() != CameraMode::ORIGINAL) { return; } @@ -295,7 +361,7 @@ void CemuHooks::hook_GetRenderCamera(PPCInterpreter_t* hCPU) { glm::mat4x3 viewMatrix = camera.mtx.getLEMatrix(); glm::mat4 worldGame = glm::inverse(glm::mat4(viewMatrix)); glm::vec3 basePos = glm::vec3(worldGame[3]); - glm::quat baseRot = glm::quat_cast(worldGame); + glm::fquat baseRot = glm::quat_cast(worldGame); // overwrite with our stored camera pos/rot basePos = s_wsCameraPosition; @@ -340,8 +406,12 @@ void CemuHooks::hook_GetRenderCamera(PPCInterpreter_t* hCPU) { glm::fvec3 eyePos = ToGLM(currPoseOpt.value().position); glm::fquat eyeRot = ToGLM(currPoseOpt.value().orientation); - glm::vec3 newPos = basePos + (baseYaw * eyePos); - glm::fquat newRot = baseYaw * eyeRot; + CameraMode cameraMode = GetSettings().GetCameraMode(); + eyePos = ApplyStereoDepthScalePolicy(eyePos); + eyePos = ApplyCameraModeEyePosPolicy(eyePos, cameraMode); + + glm::vec3 newPos = basePos + ApplyCameraModeEyeOffsetPolicy(baseYaw, baseRot, eyePos, cameraMode); + glm::fquat newRot = ApplyCameraModeEyeRotPolicy(baseYaw, baseRot, eyeRot, cameraMode); glm::mat4 newWorldVR = glm::translate(glm::mat4(1.0f), newPos) * glm::mat4_cast(newRot); glm::mat4 newViewVR = glm::inverse(newWorldVR); @@ -362,15 +432,7 @@ void CemuHooks::hook_GetRenderCamera(PPCInterpreter_t* hCPU) { camera.mtx.setLEMatrix(newViewVR); - camera.pos = newPos; - - // Set look-at point by offsetting position in view direction - glm::vec3 viewDir = -glm::vec3(newViewVR[2]); // Forward direction is -Z in view space - camera.at = newPos + viewDir; - - // Transform world up vector by new rotation - glm::vec3 upDir = glm::vec3(newViewVR[1]); // Up direction is +Y in view space - camera.up = upDir; + SetLookAtCameraVectorsFromPose(camera, newPos, newRot); //glm::mat4 workingMtx = glm::inverse(glm::lookAtRH(newPos, newPos + glm::vec3(newViewVR[2]), glm::fvec3(0, 1, 0))); @@ -546,7 +608,7 @@ void CemuHooks::hook_ModifyProjectionUsingCamera(PPCInterpreter_t* hCPU) { glm::mat4x3 viewMatrix = camera.mtx.getLEMatrix(); glm::mat4 worldGame = glm::inverse(glm::mat4(viewMatrix)); glm::vec3 basePos = glm::vec3(worldGame[3]); - glm::quat baseRot = glm::quat_cast(worldGame); + glm::fquat baseRot = glm::quat_cast(worldGame); // ignore the current rotation since it is already changed by the gameplay camera hooking baseRot = s_wsCameraRotation; @@ -574,23 +636,19 @@ void CemuHooks::hook_ModifyProjectionUsingCamera(PPCInterpreter_t* hCPU) { glm::fvec3 eyePos = ToGLM(currPoseOpt.value().position); glm::fquat eyeRot = ToGLM(currPoseOpt.value().orientation); - glm::vec3 newPos = basePos + (baseYaw * eyePos); - glm::fquat newRot = baseYaw * eyeRot; + CameraMode cameraMode = GetSettings().GetCameraMode(); + eyePos = ApplyStereoDepthScalePolicy(eyePos); + eyePos = ApplyCameraModeEyePosPolicy(eyePos, cameraMode); + + glm::vec3 newPos = basePos + ApplyCameraModeEyeOffsetPolicy(baseYaw, baseRot, eyePos, cameraMode); + glm::fquat newRot = ApplyCameraModeEyeRotPolicy(baseYaw, baseRot, eyeRot, cameraMode); glm::mat4 newWorldVR = glm::translate(glm::mat4(1.0f), newPos) * glm::mat4_cast(newRot); glm::mat4 newViewVR = glm::inverse(newWorldVR); camera.mtx.setLEMatrix(newViewVR); - camera.pos = newPos; - - // Set look-at point by offsetting position in view direction - glm::vec3 viewDir = -glm::vec3(newViewVR[2]); // Forward direction is -Z in view space - camera.at = newPos + viewDir; - - // Transform world up vector by new rotation - glm::vec3 upDir = glm::vec3(newViewVR[1]); // Up direction is +Y in view space - camera.up = upDir; + SetLookAtCameraVectorsFromPose(camera, newPos, newRot); writeMemory(cameraPtr, &camera); } @@ -643,7 +701,7 @@ std::pair CemuHooks::CalculateVRWorldPose(const BESeadLoo glm::mat4x3 viewMatrix = camera.mtx.getLEMatrix(); glm::mat4 worldGame = glm::inverse(glm::mat4(viewMatrix)); glm::vec3 basePos = glm::vec3(worldGame[3]); - glm::quat baseRot = glm::quat_cast(worldGame); + glm::fquat baseRot = glm::quat_cast(worldGame); // overwrite with our stored camera pos/rot basePos = s_wsCameraPosition; @@ -685,8 +743,12 @@ std::pair CemuHooks::CalculateVRWorldPose(const BESeadLoo glm::fvec3 eyePos = ToGLM(currPoseOpt.value().position); glm::fquat eyeRot = ToGLM(currPoseOpt.value().orientation); - glm::vec3 newPos = basePos + (baseYaw * eyePos); - glm::fquat newRot = baseYaw * eyeRot; + CameraMode cameraMode = GetSettings().GetCameraMode(); + eyePos = ApplyStereoDepthScalePolicy(eyePos); + eyePos = ApplyCameraModeEyePosPolicy(eyePos, cameraMode); + + glm::vec3 newPos = basePos + ApplyCameraModeEyeOffsetPolicy(baseYaw, baseRot, eyePos, cameraMode); + glm::fquat newRot = ApplyCameraModeEyeRotPolicy(baseYaw, baseRot, eyeRot, cameraMode); return { newPos, newRot }; } @@ -788,7 +850,7 @@ void CemuHooks::hook_UseCameraDistance(PPCInterpreter_t* hCPU) { if (IsFirstPerson()) { hCPU->fpr[13].fp0 = 0.0f; } - else if (GetSettings().GetCameraMode() == CameraMode::THIRD_PERSON) { + else if (GetSettings().GetCameraMode() == CameraMode::THIRD_PERSON || GetSettings().GetCameraMode() == CameraMode::ORIGINAL) { hCPU->fpr[13].fp0 = GetSettings().thirdPlayerDistance; } else { @@ -1069,7 +1131,7 @@ void CemuHooks::hook_PlayerIsRiding(PPCInterpreter_t* hCPU) { hCPU->instructionPointer = hCPU->sprNew.LR; bool isRiding = hCPU->gpr[3] == 1; - if (isRiding && IsFirstPerson()) { + if (isRiding && (IsFirstPerson() || GetSettings().GetCameraMode() == CameraMode::ORIGINAL)) { s_isRiding = 2; } } @@ -1078,7 +1140,7 @@ void CemuHooks::hook_PlayerIsRidingSandSeal(PPCInterpreter_t* hCPU) { hCPU->instructionPointer = hCPU->sprNew.LR; bool isRiding = hCPU->gpr[3] == 1; - if (isRiding && IsFirstPerson()) { + if (isRiding && (IsFirstPerson() || GetSettings().GetCameraMode() == CameraMode::ORIGINAL)) { s_isRidingSandSeal = 2; } } diff --git a/src/hooking/controls.cpp b/src/hooking/controls.cpp index e9ecb703..0d71ba0f 100644 --- a/src/hooking/controls.cpp +++ b/src/hooking/controls.cpp @@ -893,7 +893,7 @@ void processJoystickInput(VPADButtons& oldXRStickHold, VPADButtons& newXRStickHo XrTime prev_sample = 0; -void updatePreviousValues(OpenXR::GameState& gameState, uint32_t& buttonHold, HandGestureState& leftGesture, HandGestureState& rightGesture, XrTime inputTime) +void updatePreviousValues(OpenXR::GameState& gameState, uint32_t buttonHold, HandGestureState& leftGesture, HandGestureState& rightGesture, XrTime inputTime) { // (re)set values for next frame gameState.previous_button_hold = buttonHold; @@ -1121,7 +1121,8 @@ void CemuHooks::hook_InjectXRInput(PPCInterpreter_t* hCPU) { // set r3 to 1 for hooked VPADRead function to return success hCPU->gpr[3] = 1; - updatePreviousValues(gameState, newXRBtnHold, leftGesture, rightGesture, inputs.shared.inputTime); + // Store the effective hold state (gamepad + XR) so downstream systems can detect active use inputs regardless of input source. + updatePreviousValues(gameState, combinedHold, leftGesture, rightGesture, inputs.shared.inputTime); VRManager::instance().XR->m_gameState.store(gameState); VRManager::instance().XR->m_input.store(inputs); @@ -1161,4 +1162,4 @@ void CemuHooks::hook_CreateNewActor(PPCInterpreter_t* hCPU) { // else { // hCPU->gpr[3] = 0; // } -} \ No newline at end of file +} diff --git a/src/hooking/openxr_motion_bridge.h b/src/hooking/openxr_motion_bridge.h index 5f64ff16..70c2e04b 100644 --- a/src/hooking/openxr_motion_bridge.h +++ b/src/hooking/openxr_motion_bridge.h @@ -180,6 +180,15 @@ class OpenXRMotionBridge { vpadStatus.dir.y = corrected * glm::vec3(0, 1, 0); vpadStatus.dir.z = corrected * glm::vec3(0, 0, 1); } + else if (GetSettings().GetCameraMode() == CameraMode::ORIGINAL) { + if (GetSettings().gyroFlipYZOriginal.Get()) { + const glm::fvec3 prevGyroChange = vpadStatus.gyroChange.getLE(); + const glm::fvec3 prevGyroOrientation = vpadStatus.gyroOrientation.getLE(); + + vpadStatus.gyroChange = glm::fvec3{ prevGyroChange.x, prevGyroChange.z, prevGyroChange.y }; + vpadStatus.gyroOrientation = glm::fvec3{ prevGyroOrientation.x, prevGyroOrientation.z, prevGyroOrientation.y }; + } + } else { vpadStatus.dir.x = glm::fvec3{ 1, 0, 0 }; vpadStatus.dir.y = glm::fvec3{ 0, 1, 0 }; diff --git a/src/hooking/weapon.cpp b/src/hooking/weapon.cpp index d5df0739..b051fc94 100644 --- a/src/hooking/weapon.cpp +++ b/src/hooking/weapon.cpp @@ -168,7 +168,7 @@ void CemuHooks::hook_ChangeWeaponMtx(PPCInterpreter_t* hCPU) { } } - if (IsThirdPerson()) { + if (IsThirdPerson() && GetSettings().GetCameraMode() != CameraMode::ORIGINAL) { return; } @@ -330,7 +330,7 @@ void CemuHooks::hook_DropEquipment(PPCInterpreter_t* hCPU) { void CemuHooks::hook_GetContactLayerOfAttack(PPCInterpreter_t* hCPU) { hCPU->instructionPointer = hCPU->sprNew.LR; - if (GetSettings().GetCameraMode() == CameraMode::THIRD_PERSON) { + if (GetSettings().GetCameraMode() == CameraMode::THIRD_PERSON || GetSettings().GetCameraMode() == CameraMode::ORIGINAL) { return; } @@ -372,7 +372,7 @@ void CemuHooks::hook_EnableWeaponAttackSensor(PPCInterpreter_t* hCPU) { hCPU->instructionPointer = hCPU->sprNew.LR; - if (GetSettings().GetCameraMode() == CameraMode::THIRD_PERSON) + if (GetSettings().GetCameraMode() == CameraMode::THIRD_PERSON || GetSettings().GetCameraMode() == CameraMode::ORIGINAL) return; uint32_t weaponPtr = hCPU->gpr[3]; diff --git a/src/rendering/d3d12.cpp b/src/rendering/d3d12.cpp index 90196f31..c541865b 100644 --- a/src/rendering/d3d12.cpp +++ b/src/rendering/d3d12.cpp @@ -235,14 +235,36 @@ void RND_D3D12::PresentPipeline::BindDepthTarget(ID3D12Resource* dstTextu } template -void RND_D3D12::PresentPipeline::BindSettings(float screenWidth, float screenHeight) { +void RND_D3D12::PresentPipeline::BindSettings( + float screenWidth, + float screenHeight, + float reticleEyeSign, + float reticlePixelOffsetPx, + float reticleRadiusPx, + float reticleThicknessPx, + float reticleOpacity, + float reticleEnabled, + float reticleColorR, + float reticleColorG, + float reticleColorB +) { + m_reticleEyeSign = reticleEyeSign; + m_reticlePixelOffsetPx = reticlePixelOffsetPx; + m_reticleRadiusPx = reticleRadiusPx; + m_reticleThicknessPx = reticleThicknessPx; + m_reticleOpacity = reticleOpacity; + m_reticleEnabled = reticleEnabled; + m_reticleColorR = reticleColorR; + m_reticleColorG = reticleColorG; + m_reticleColorB = reticleColorB; + ComPtr newSettingsStaging; ComPtr newSettingsAllocator; { ID3D12Device* device = VRManager::instance().D3D12->GetDevice(); ID3D12CommandQueue* queue = VRManager::instance().D3D12->GetCommandQueue(); device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&newSettingsAllocator)); - RND_D3D12::CommandContext uploadBufferContext(device, queue, newSettingsAllocator.Get(), [this, device, &newSettingsStaging, screenWidth, screenHeight](RND_D3D12::CommandContext* context) { + RND_D3D12::CommandContext uploadBufferContext(device, queue, newSettingsAllocator.Get(), [this, device, &newSettingsStaging, screenWidth, screenHeight, reticleEyeSign, reticlePixelOffsetPx, reticleRadiusPx, reticleThicknessPx, reticleOpacity, reticleEnabled, reticleColorR, reticleColorG, reticleColorB](RND_D3D12::CommandContext* context) { m_settingsBuffer = D3D12Utils::CreateConstantBuffer(device, D3D12_HEAP_TYPE_DEFAULT, sizeof(presentSettings)); newSettingsStaging = D3D12Utils::CreateConstantBuffer(device, D3D12_HEAP_TYPE_UPLOAD, sizeof(presentSettings)); @@ -372,7 +394,53 @@ void RND_D3D12::PresentPipeline::Render(ID3D12GraphicsCommandList* cmdLis cmdList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); cmdList->IASetIndexBuffer(&m_screenIndicesView); cmdList->DrawIndexedInstanced((UINT)std::size(screenIndices), 1, 0, 0, 0); + + // Safer stereo reticle path: draw with color clears in screen-space for depth pipeline only. + if constexpr (depth) { + if (m_reticleEnabled > 0.5f) { + const int width = (int)swapchain->GetDesc().Width; + const int height = (int)swapchain->GetDesc().Height; + + const float eyeSign = m_reticleEyeSign; + const float pxOffset = std::copysign(std::max(0.0f, m_reticlePixelOffsetPx), -eyeSign); + + const int cx = (int)std::lround(width * 0.5f + pxOffset); + const int cy = height / 2; + const int radius = (int)std::lround(std::max(1.0f, m_reticleRadiusPx)); + const int thickness = (int)std::lround(std::max(1.0f, m_reticleThicknessPx)); + const int halfThickness = std::max(1, thickness / 2); + const int crossHalf = std::max(1, radius / 2); + + auto clampi = [](int v, int lo, int hi) { return std::max(lo, std::min(v, hi)); }; + auto makeRect = [&](int l, int t, int r, int b) { + D3D12_RECT rc{}; + rc.left = clampi(l, 0, width); + rc.top = clampi(t, 0, height); + rc.right = clampi(r, 0, width); + rc.bottom = clampi(b, 0, height); + return rc; + }; + + std::array rects = { + makeRect(cx - crossHalf, cy - halfThickness, cx + crossHalf, cy + halfThickness), + makeRect(cx - halfThickness, cy - crossHalf, cx + halfThickness, cy + crossHalf), + }; + + float a = std::clamp(m_reticleOpacity, 0.0f, 1.0f); + float colorR = std::clamp(m_reticleColorR, 0.0f, 1.0f); + float colorG = std::clamp(m_reticleColorG, 0.0f, 1.0f); + float colorB = std::clamp(m_reticleColorB, 0.0f, 1.0f); + const float clearColor[4] = { + colorR * a, + colorG * a, + colorB * a, + 1.0f + }; + + cmdList->ClearRenderTargetView(m_targetHandles[0], clearColor, (UINT)rects.size(), rects.data()); + } + } } template class RND_D3D12::PresentPipeline; -template class RND_D3D12::PresentPipeline; \ No newline at end of file +template class RND_D3D12::PresentPipeline; diff --git a/src/rendering/d3d12.h b/src/rendering/d3d12.h index 05424c5c..865ff61d 100644 --- a/src/rendering/d3d12.h +++ b/src/rendering/d3d12.h @@ -43,7 +43,19 @@ class RND_D3D12 { void BindAttachment(uint32_t attachmentIdx, ID3D12Resource* srcTexture, DXGI_FORMAT overwriteFormat = DXGI_FORMAT_UNKNOWN); void BindTarget(uint32_t targetIdx, ID3D12Resource* dstTexture, DXGI_FORMAT overwriteFormat = DXGI_FORMAT_UNKNOWN); void BindDepthTarget(ID3D12Resource* dstTexture, DXGI_FORMAT overwriteFormat); - void BindSettings(float screenWidth, float screenHeight); + void BindSettings( + float screenWidth, + float screenHeight, + float reticleEyeSign = 0.0f, + float reticlePixelOffsetPx = 20.0f, + float reticleRadiusPx = 8.0f, + float reticleThicknessPx = 2.0f, + float reticleOpacity = 0.0f, + float reticleEnabled = 0.0f, + float reticleColorR = 1.0f, + float reticleColorG = 1.0f, + float reticleColorB = 1.0f + ); void Render(ID3D12GraphicsCommandList* commandList, ID3D12Resource* swapchain); private: @@ -67,6 +79,17 @@ class RND_D3D12 { ComPtr m_targetHeap; ComPtr m_depthHeap; std::array m_targetFormats = { DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_D32_FLOAT }; + + // Reticle parameters pushed from renderer-side settings. + float m_reticleEyeSign = 0.0f; + float m_reticlePixelOffsetPx = 20.0f; + float m_reticleRadiusPx = 8.0f; + float m_reticleThicknessPx = 2.0f; + float m_reticleOpacity = 0.0f; + float m_reticleEnabled = 0.0f; + float m_reticleColorR = 1.0f; + float m_reticleColorG = 1.0f; + float m_reticleColorB = 1.0f; }; template @@ -126,4 +149,4 @@ class RND_D3D12 { ComPtr m_queue; ComPtr m_allocator; ComPtr m_fence; -}; \ No newline at end of file +}; diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 8b9ac820..7cd9065a 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -266,7 +266,49 @@ void RND_Renderer::Layer3D::Render(OpenXR::EyeSide side, long frameIdx) { ID3D12CommandQueue* queue = VRManager::instance().D3D12->GetCommandQueue(); ID3D12CommandAllocator* allocator = VRManager::instance().D3D12->GetFrameAllocator(); - RND_D3D12::CommandContext renderSharedTexture(device, queue, allocator, [this, side, frameIdx](RND_D3D12::CommandContext* context) { + auto& settings = GetSettings(); + const auto gameState = VRManager::instance().XR->m_gameState.load(); + const uint32_t holdButtons = gameState.previous_button_hold; + const bool isThrowUseHeld = (holdButtons & VPAD_BUTTON_R) != 0; + + const bool isBowContext = + gameState.left_hand_current_equip_type == EquipType::Bow || + gameState.left_hand_previous_frame_equip_type == EquipType::Bow || + gameState.last_equip_type_held == EquipType::Bow; + + const bool isThrowContext = + gameState.is_throwable_object_held || + gameState.trigger_pressed_over_body_slot || + gameState.right_hand_current_equip_type == EquipType::Melee || + gameState.right_hand_previous_frame_equip_type == EquipType::Melee || + gameState.last_equip_type_held == EquipType::Melee; + + const bool isRuneOrPowerContext = + gameState.left_hand_current_equip_type == EquipType::SheikahSlate || + gameState.left_hand_previous_frame_equip_type == EquipType::SheikahSlate || + gameState.right_hand_current_equip_type == EquipType::MagnetGlove || + gameState.right_hand_previous_frame_equip_type == EquipType::MagnetGlove || + gameState.last_equip_type_held == EquipType::SheikahSlate; + + // Context-only gate for bow/runes, with throw additionally requiring the throw button hold. + const bool shouldShowReticle = CemuHooks::IsInGame() && !CemuHooks::HasActiveCutscene() && settings.enableStaticReticle.Get() && (isBowContext || (isThrowContext && isThrowUseHeld) || isRuneOrPowerContext); + const float reticleEyeSign = side == OpenXR::EyeSide::LEFT ? 1.0f : -1.0f; + const float reticleEnabled = shouldShowReticle ? 1.0f : 0.0f; + m_presentPipelines[side]->BindSettings( + (float)m_swapchains[side]->GetWidth(), + (float)m_swapchains[side]->GetHeight(), + reticleEyeSign, + std::max(0.0f, settings.staticReticlePixelOffsetPx.Get()), + settings.staticReticleRadiusPx.Get(), + settings.staticReticleThicknessPx.Get(), + std::clamp(settings.staticReticleOpacity.Get(), 0.0f, 1.0f), + reticleEnabled, + std::clamp(settings.staticReticleColorR.Get(), 0.0f, 1.0f), + std::clamp(settings.staticReticleColorG.Get(), 0.0f, 1.0f), + std::clamp(settings.staticReticleColorB.Get(), 0.0f, 1.0f) + ); + + RND_D3D12::CommandContext renderSharedTexture(device, queue, allocator, [this, side, frameIdx, reticleEyeSign](RND_D3D12::CommandContext* context) { context->GetRecordList()->SetName(L"RenderSharedTexture"); auto& texture = m_textures[side][frameIdx]; auto& depthTexture = m_depthTextures[side][frameIdx]; diff --git a/src/rendering/vulkan_imgui.cpp b/src/rendering/vulkan_imgui.cpp index 9434a6b2..6bd00d95 100644 --- a/src/rendering/vulkan_imgui.cpp +++ b/src/rendering/vulkan_imgui.cpp @@ -374,7 +374,7 @@ void RND_Renderer::ImGuiOverlay::Render(long frameIdx, bool renderBackground) { }; if (renderBackground || CemuHooks::UseBlackBarsDuringEvents()) { - const bool shouldCrop3DTo16_9 = GetSettings().cropFlatTo16x9 == 1; + const bool shouldCrop3DTo16_9 = GetSettings().ShouldFlatPreviewBeCroppedTo16x9(); bool shouldRender3DBackground = VRManager::instance().XR->GetRenderer()->IsRendering3D(frameIdx) || CemuHooks::UseBlackBarsDuringEvents(); bool shouldRenderHUDWithAlpha = shouldRender3DBackground && !CemuHooks::UseBlackBarsDuringEvents(); @@ -736,10 +736,18 @@ void RND_Renderer::ImGuiOverlay::DrawHelpMenu() { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive)); ImGui::Text("Camera / Player Options"); ImGui::PopStyleColor(); - if (cameraMode == CameraMode::THIRD_PERSON) { + if (cameraMode == CameraMode::THIRD_PERSON || cameraMode == CameraMode::ORIGINAL) { DrawSettingRow("Camera Distance", [&]() { - settings.thirdPlayerDistance.AddSliderToGUI(&changed, 0.5f, 0.65f); + settings.thirdPlayerDistance.AddSliderToGUI(&changed, 0.5f, 1.0f); }); + + if (cameraMode == CameraMode::ORIGINAL) { + DrawSettingRow("Riding Vertical Offset", [&]() { + settings.originalRidingVerticalOffset.AddToGUI(&changed, windowWidth.x, 0.0f, 1.0f, [](float value) { + return std::format("{:+.2f}m", value); + }); + }); + } } else { DrawSettingRow("Height Offset", [&]() { @@ -768,20 +776,30 @@ void RND_Renderer::ImGuiOverlay::DrawHelpMenu() { //}); } + DrawSettingRow("Stereo Depth In Gameplay", [&]() { + settings.gameplayStereoDepthScale.AddToGUI(&changed, windowWidth.x, 0.0f, 3.0f, [](float value) { return std::format("{:.2f}x", value); }); + }); + ImGui::Spacing(); ImGui::Separator(); ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive)); ImGui::Text("Cutscenes"); ImGui::PopStyleColor(); - if (cameraMode != CameraMode::THIRD_PERSON) { + if (cameraMode != CameraMode::THIRD_PERSON && cameraMode != CameraMode::ORIGINAL) { DrawSettingRow("Camera In Cutscenes", [&]() { settings.cutsceneCameraMode.AddComboToGUI(&changed, ModSettings::toDisplayString); }); } - DrawSettingRow("Black Bars In Third-Person Cutscenes", [&]() { - settings.useBlackBarsForCutscenes.AddToGUI(&changed); + if (cameraMode != CameraMode::ORIGINAL) { + DrawSettingRow("Black Bars In Third-Person Cutscenes", [&]() { + settings.useBlackBarsForCutscenes.AddToGUI(&changed); + }); + } + + DrawSettingRow("Stereo Depth In Cutscenes", [&]() { + settings.cutsceneStereoDepthScale.AddToGUI(&changed, windowWidth.x, 0.0f, 1.5f, [](float value) { return std::format("{:.2f}x", value); }); }); ImGui::Spacing(); @@ -789,13 +807,50 @@ void RND_Renderer::ImGuiOverlay::DrawHelpMenu() { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive)); ImGui::Text("UI"); ImGui::PopStyleColor(); - DrawSettingRow("UI Follows Where You Look", [&]() { - settings.uiFollowsGaze.AddToGUI(&changed); - }); + if (cameraMode != CameraMode::ORIGINAL) { + DrawSettingRow("UI Follows Where You Look", [&]() { + settings.uiFollowsGaze.AddToGUI(&changed); + }); + } ImGui::Spacing(); DrawLayerSettingsRow("Menu/HUD Distance & Size", &changed, settings.hudDistance, settings.hudSize); + ImGui::Spacing(); + DrawSettingRow("Enable 3D Static Reticle", [&]() { + settings.enableStaticReticle.AddToGUI(&changed); + }); + + if (settings.enableStaticReticle.Get()) { + DrawSettingRow("Reticle 3D Separation", [&]() { + settings.staticReticlePixelOffsetPx.AddToGUI(&changed, windowWidth.x, 0.0f, 500.0f, [](float value) { return std::format("{:.1f} px", value); }); + }); + + DrawSettingRow("Reticle Radius", [&]() { + settings.staticReticleRadiusPx.AddToGUI(&changed, windowWidth.x, 1.0f, 64.0f, [](float value) { return std::format("{:.1f} px", value); }); + }); + + DrawSettingRow("Reticle Thickness", [&]() { + settings.staticReticleThicknessPx.AddToGUI(&changed, windowWidth.x, 1.0f, 8.0f, [](float value) { return std::format("{:.1f} px", value); }); + }); + + DrawSettingRow("Reticle Opacity", [&]() { + settings.staticReticleOpacity.AddToGUI(&changed, windowWidth.x, 0.1f, 1.0f, [](float value) { return std::format("{:.2f}", value); }); + }); + + DrawSettingRow("Reticle Color R", [&]() { + settings.staticReticleColorR.AddToGUI(&changed, windowWidth.x, 0.0f, 1.0f, [](float value) { return std::format("{:.2f}", value); }); + }); + + DrawSettingRow("Reticle Color G", [&]() { + settings.staticReticleColorG.AddToGUI(&changed, windowWidth.x, 0.0f, 1.0f, [](float value) { return std::format("{:.2f}", value); }); + }); + + DrawSettingRow("Reticle Color B", [&]() { + settings.staticReticleColorB.AddToGUI(&changed, windowWidth.x, 0.0f, 1.0f, [](float value) { return std::format("{:.2f}", value); }); + }); + } + ImGui::Spacing(); if (cameraMode == CameraMode::FIRST_PERSON) { ImGui::Separator(); @@ -815,14 +870,22 @@ void RND_Renderer::ImGuiOverlay::DrawHelpMenu() { } if (ImGui::CollapsingHeader("Advanced Settings")) { - DrawSettingRow("Crop VR Image To 16:9 For Cemu Window", [&]() { - settings.cropFlatTo16x9.AddToGUI(&changed); - }); + if (cameraMode != CameraMode::ORIGINAL) { + DrawSettingRow("Crop VR Image To 16:9 For Cemu Window", [&]() { + settings.cropFlatTo16x9.AddToGUI(&changed); + }); + } DrawSettingRow("Show Debugging Overlays (for developers)", [&]() { settings.enableDebugOverlay.AddToGUI(&changed); }); + if (cameraMode == CameraMode::ORIGINAL) { + DrawSettingRow("Gyro Flip Y/Z", [&]() { + settings.gyroFlipYZOriginal.AddToGUI(&changed); + }); + } + if (VRManager::instance().XR->m_capabilities.isOculusLinkRuntime) { DrawSettingRow("Angular Velocity Fixer", [&]() { settings.buggyAngularVelocity.AddComboToGUI(&changed, ModSettings::toDisplayString); diff --git a/src/utils/mod_settings.h b/src/utils/mod_settings.h index e5b35ccb..670ac493 100644 --- a/src/utils/mod_settings.h +++ b/src/utils/mod_settings.h @@ -489,6 +489,7 @@ enum class EventMode : int32_t { enum class CameraMode : int32_t { THIRD_PERSON = 0, FIRST_PERSON = 1, + ORIGINAL = 2, }; enum class PlayMode : int32_t { @@ -543,6 +544,8 @@ struct ModSettings { return "THIRD_PERSON"; case CameraMode::FIRST_PERSON: return "FIRST_PERSON"; + case CameraMode::ORIGINAL: + return "OG"; default: return ""; } @@ -554,6 +557,8 @@ struct ModSettings { return "Third Person"; case CameraMode::FIRST_PERSON: return "First Person (Recommended)"; + case CameraMode::ORIGINAL: + return "OG"; default: return ""; } @@ -635,12 +640,21 @@ struct ModSettings { static constexpr float kDefaultAxisThreshold = 0.5f; static constexpr float kDefaultStickDeadzone = 0.15f; + static constexpr float kDefaultGameplayStereoDepthScale = 1.0f; + static constexpr float kDefaultCutsceneStereoDepthScale = 0.25f; + static constexpr float kDefaultReticlePixelOffsetPx = 100.0f; + static constexpr float kDefaultReticleRadiusPx = 15.0f; + static constexpr float kDefaultReticleThicknessPx = 2.0f; + static constexpr float kDefaultReticleOpacity = 0.65f; // playing mode settings - EnumSetting cameraMode = EnumSetting("CameraMode", CameraMode::FIRST_PERSON, ModSettings::toString, { CameraMode::FIRST_PERSON, CameraMode::THIRD_PERSON }); + EnumSetting cameraMode = EnumSetting("CameraMode", CameraMode::FIRST_PERSON, ModSettings::toString, { CameraMode::FIRST_PERSON, CameraMode::THIRD_PERSON, CameraMode::ORIGINAL }); EnumSetting playMode = EnumSetting("PlayMode", PlayMode::STANDING, ModSettings::toString, { PlayMode::STANDING, PlayMode::SEATED }); - FloatSetting thirdPlayerDistance = FloatSetting("ThirdPlayerDistance", 0.5f, 0.0f); + FloatSetting thirdPlayerDistance = FloatSetting("ThirdPlayerDistance", 0.5f, 0.0f, 1.0f); + FloatSetting originalRidingVerticalOffset = FloatSetting("OriginalRidingVerticalOffset", 0.4f, 0.0f, 1.0f); EnumSetting cutsceneCameraMode = EnumSetting("CutsceneCameraMode", EventMode::FOLLOW_DEFAULT_EVENT_SETTINGS, ModSettings::toString, { EventMode::ALWAYS_FIRST_PERSON, EventMode::FOLLOW_DEFAULT_EVENT_SETTINGS, EventMode::ALWAYS_THIRD_PERSON }); + FloatSetting gameplayStereoDepthScale = FloatSetting("GameplayStereoDepthScale", kDefaultGameplayStereoDepthScale, 0.0f, 3.0f); + FloatSetting cutsceneStereoDepthScale = FloatSetting("CutsceneStereoDepthScale", kDefaultCutsceneStereoDepthScale, 0.0f, 1.5f); BoolSetting useBlackBarsForCutscenes = BoolSetting("UseBlackBarsForCutscenes", false); // first-person settings @@ -653,6 +667,15 @@ struct ModSettings { // advanced settings BoolSetting enableDebugOverlay = BoolSetting("EnableDebugOverlay", false); + BoolSetting gyroFlipYZOriginal = BoolSetting("GyroFlipYZOriginal", false); + BoolSetting enableStaticReticle = BoolSetting("EnableStaticReticle", true); + FloatSetting staticReticlePixelOffsetPx = FloatSetting("StaticReticlePixelOffsetPx", kDefaultReticlePixelOffsetPx, 0.0f, 500.0f); + FloatSetting staticReticleRadiusPx = FloatSetting("StaticReticleRadiusPx", kDefaultReticleRadiusPx, 1.0f, 64.0f); + FloatSetting staticReticleThicknessPx = FloatSetting("StaticReticleThicknessPx", kDefaultReticleThicknessPx, 1.0f, 8.0f); + FloatSetting staticReticleOpacity = FloatSetting("StaticReticleOpacity", kDefaultReticleOpacity, 0.1f, 1.0f); + FloatSetting staticReticleColorR = FloatSetting("StaticReticleColorR", 0.0f, 0.0f, 1.0f); + FloatSetting staticReticleColorG = FloatSetting("StaticReticleColorG", 1.0f, 0.0f, 1.0f); + FloatSetting staticReticleColorB = FloatSetting("StaticReticleColorB", 1.0f, 0.0f, 1.0f); EnumSetting buggyAngularVelocity = EnumSetting("BuggyAngularVelocity", AngularVelocityFixerMode::AUTO, ModSettings::toString, { AngularVelocityFixerMode::AUTO, AngularVelocityFixerMode::FORCED_ON, AngularVelocityFixerMode::FORCED_OFF }); EnumSetting performanceOverlay = EnumSetting("PerformanceOverlay", PerformanceOverlayMode::DISABLE, ModSettings::toString, { PerformanceOverlayMode::DISABLE, PerformanceOverlayMode::WINDOW_ONLY, PerformanceOverlayMode::WINDOW_AND_VR }); UIntSetting performanceOverlayFrequency = UIntSetting("PerformanceOverlayFrequency", 90); @@ -667,7 +690,10 @@ struct ModSettings { &cameraMode, &playMode, &thirdPlayerDistance, + &originalRidingVerticalOffset, &cutsceneCameraMode, + &gameplayStereoDepthScale, + &cutsceneStereoDepthScale, &useBlackBarsForCutscenes, &playerHeightOffset, &leftHanded, @@ -676,6 +702,15 @@ struct ModSettings { &hudSize, &cropFlatTo16x9, &enableDebugOverlay, + &gyroFlipYZOriginal, + &enableStaticReticle, + &staticReticlePixelOffsetPx, + &staticReticleRadiusPx, + &staticReticleThicknessPx, + &staticReticleOpacity, + &staticReticleColorR, + &staticReticleColorG, + &staticReticleColorB, &buggyAngularVelocity, &performanceOverlay, &performanceOverlayFrequency, @@ -688,25 +723,43 @@ struct ModSettings { CameraMode GetCameraMode() const { return cameraMode; } PlayMode GetPlayMode() const { return playMode; } - bool DoesUIFollowGaze() const { return uiFollowsGaze; } + bool DoesUIFollowGaze() const { + if (GetCameraMode() == CameraMode::ORIGINAL) { + return false; + } + return uiFollowsGaze; + } bool IsLeftHanded() const { return leftHanded; } float GetPlayerHeightOffset() const { - // disable height offset in third-person mode - if (GetCameraMode() == CameraMode::THIRD_PERSON) { + // disable height offset in third-person-style modes + if (GetCameraMode() == CameraMode::THIRD_PERSON || GetCameraMode() == CameraMode::ORIGINAL) { return 0.0f; } return playerHeightOffset; } EventMode GetCutsceneCameraMode() const { - // if in third-person mode, always use third-person cutscene camera - if (GetCameraMode() == CameraMode::THIRD_PERSON) { + // if in third-person-style modes, always use third-person cutscene camera + if (GetCameraMode() == CameraMode::THIRD_PERSON || GetCameraMode() == CameraMode::ORIGINAL) { return EventMode::ALWAYS_THIRD_PERSON; } return cutsceneCameraMode; } - bool UseBlackBarsForCutscenes() const { return useBlackBarsForCutscenes; } - bool ShouldFlatPreviewBeCroppedTo16x9() const { return cropFlatTo16x9 == 1; } + float GetGameplayStereoDepthScale() const { return gameplayStereoDepthScale; } + float GetCutsceneStereoDepthScale() const { return cutsceneStereoDepthScale; } + float GetOriginalRidingVerticalOffset() const { return originalRidingVerticalOffset; } + bool UseBlackBarsForCutscenes() const { + if (GetCameraMode() == CameraMode::ORIGINAL) { + return false; + } + return useBlackBarsForCutscenes; + } + bool ShouldFlatPreviewBeCroppedTo16x9() const { + if (GetCameraMode() == CameraMode::ORIGINAL) { + return true; + } + return cropFlatTo16x9 == 1; + } bool ShowDebugOverlay() const { return enableDebugOverlay; } AngularVelocityFixerMode AngularVelocityFixer_GetMode() const { return buggyAngularVelocity; } @@ -718,12 +771,22 @@ struct ModSettings { std::string ToString() const { std::string buffer = ""; std::format_to(std::back_inserter(buffer), " - Camera Mode: {}\n", toDisplayString(GetCameraMode())); + std::format_to(std::back_inserter(buffer), " - Original Riding Vertical Offset: {:.2f}m\n", GetOriginalRidingVerticalOffset()); std::format_to(std::back_inserter(buffer), " - Left Handed: {}\n", IsLeftHanded() ? "Yes" : "No"); std::format_to(std::back_inserter(buffer), " - GUI Follow Setting: {}\n", DoesUIFollowGaze() ? "Follow Looking Direction" : "Fixed"); std::format_to(std::back_inserter(buffer), " - Player Height: {} meters\n", GetPlayerHeightOffset()); std::format_to(std::back_inserter(buffer), " - Crop Flat to 16:9: {}\n", ShouldFlatPreviewBeCroppedTo16x9() ? "Yes" : "No"); std::format_to(std::back_inserter(buffer), " - Debug Overlay: {}\n", ShowDebugOverlay() ? "Enabled" : "Disabled"); + std::format_to(std::back_inserter(buffer), " - Gyro Flip Y/Z (Original Camera): {}\n", gyroFlipYZOriginal.Get() ? "Enabled" : "Disabled"); + std::format_to(std::back_inserter(buffer), " - Static Reticle: {}\n", enableStaticReticle.Get() ? "Enabled" : "Disabled"); + std::format_to(std::back_inserter(buffer), " - Static Reticle Pixel Offset: {:.2f}px\n", staticReticlePixelOffsetPx.Get()); + std::format_to(std::back_inserter(buffer), " - Static Reticle Radius: {:.2f}px\n", staticReticleRadiusPx.Get()); + std::format_to(std::back_inserter(buffer), " - Static Reticle Thickness: {:.2f}px\n", staticReticleThicknessPx.Get()); + std::format_to(std::back_inserter(buffer), " - Static Reticle Opacity: {:.2f}\n", staticReticleOpacity.Get()); + std::format_to(std::back_inserter(buffer), " - Static Reticle Color: ({:.2f}, {:.2f}, {:.2f})\n", staticReticleColorR.Get(), staticReticleColorG.Get(), staticReticleColorB.Get()); std::format_to(std::back_inserter(buffer), " - Cutscene Camera Mode: {}\n", toDisplayString(GetCutsceneCameraMode())); + std::format_to(std::back_inserter(buffer), " - Stereo Depth (Gameplay): {:.2f}x\n", GetGameplayStereoDepthScale()); + std::format_to(std::back_inserter(buffer), " - Stereo Depth (Cutscenes): {:.2f}x\n", GetCutsceneStereoDepthScale()); std::format_to(std::back_inserter(buffer), " - Show Black Bars for Third-Person Cutscenes: {}\n", UseBlackBarsForCutscenes() ? "Yes" : "No"); std::format_to(std::back_inserter(buffer), " - Performance Overlay: {}\n", toDisplayString(performanceOverlay)); std::format_to(std::back_inserter(buffer), " - Performance Overlay Frequency: {} Hz\n", performanceOverlayFrequency.Get());