diff --git a/files.cmake b/files.cmake index 8f62b9346c..e91415b3be 100644 --- a/files.cmake +++ b/files.cmake @@ -1344,7 +1344,7 @@ set(DUSK_FILES src/dusk/extras.cpp src/dusk/frame_interpolation.cpp src/dusk/globals.cpp - src/dusk/gyro_aim.cpp + src/dusk/gyro.cpp src/dusk/io.cpp src/dusk/layout.cpp src/dusk/logging.cpp diff --git a/include/dusk/gyro.h b/include/dusk/gyro.h new file mode 100644 index 0000000000..5636d34b05 --- /dev/null +++ b/include/dusk/gyro.h @@ -0,0 +1,17 @@ +#ifndef DUSK_GYRO_H +#define DUSK_GYRO_H + +namespace dusk::gyro { +void read(float dt); +void consumeAimDeltas(float& out_yaw_rad, float& out_pitch_rad); +bool queryGyroAimItemContext(); + +void rollgoalTick(bool play_active, s16 camera_yaw); +void rollgoalTableOffset(s16& out_add_x, s16& out_add_z); + +extern bool s_sensor_keep_alive; +bool get_sensor_keep_alive(); +void set_sensor_keep_alive(bool value); +} // namespace dusk::gyro + +#endif diff --git a/include/dusk/gyro_aim.h b/include/dusk/gyro_aim.h deleted file mode 100644 index aafc926811..0000000000 --- a/include/dusk/gyro_aim.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef DUSK_GYRO_AIM_H -#define DUSK_GYRO_AIM_H - -namespace dusk::gyro_aim { -void read(float dt, bool context_active); -void consumeAimDeltas(float& out_yaw_rad, float& out_pitch_rad); -bool queryGyroAimItemContext(); -} // namespace dusk::gyro_aim - -#endif diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 67dce3c067..7bfc65a312 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -81,8 +81,10 @@ struct UserSettings { // Input ConfigVar enableGyroAim; + ConfigVar enableGyroRollgoal; ConfigVar gyroAimSensitivityX; ConfigVar gyroAimSensitivityY; + ConfigVar gyroRollgoalSensitivity; ConfigVar gyroAimInvertPitch; ConfigVar gyroAimInvertYaw; diff --git a/src/d/actor/d_a_alink_link.inc b/src/d/actor/d_a_alink_link.inc index 4f8f5fa7b6..6bf9ae23dc 100644 --- a/src/d/actor/d_a_alink_link.inc +++ b/src/d/actor/d_a_alink_link.inc @@ -11,7 +11,7 @@ #include "d/actor/d_a_tag_mhint.h" #if TARGET_PC -#include "dusk/gyro_aim.h" +#include "dusk/gyro.h" #endif bool daAlink_c::checkNoSubjectModeCamera() { @@ -142,7 +142,7 @@ BOOL daAlink_c::setBodyAngleToCamera() { f32 gy_yaw = 0.f; f32 gy_pitch = 0.f; - dusk::gyro_aim::consumeAimDeltas(gy_yaw, gy_pitch); + dusk::gyro::consumeAimDeltas(gy_yaw, gy_pitch); if (dusk::getSettings().game.gyroAimInvertPitch) { gy_pitch = -gy_pitch; diff --git a/src/d/actor/d_a_mg_fshop.cpp b/src/d/actor/d_a_mg_fshop.cpp index cb9cc12dea..1c93648eac 100644 --- a/src/d/actor/d_a_mg_fshop.cpp +++ b/src/d/actor/d_a_mg_fshop.cpp @@ -14,6 +14,10 @@ #include "d/d_s_play.h" #include "Z2AudioLib/Z2Instances.h" +#if TARGET_PC +#include "dusk/gyro.h" +#endif + enum koro2_parts { KORO2_PART_BOX = 1, KORO2_PART_CURVE_A_U_L, @@ -723,6 +727,14 @@ static void koro2_game(fshop_class* i_this) { cLib_addCalcAngleS2(&i_this->field_0x4020.x, 0, 2, 0x200); cLib_addCalcAngleS2(&i_this->field_0x4020.z, 0, 2, 0x200); case 2: +#if TARGET_PC + if (dusk::getSettings().game.enableGyroRollgoal) { + if (!dusk::gyro::get_sensor_keep_alive()) { + dusk::gyro::set_sensor_keep_alive(true); + } + } +#endif + actor->scale.x = 10.0f; if (i_this->field_0x4010 == 2) { static f32 old_stick_x = 0.0f; @@ -739,6 +751,11 @@ static void koro2_game(fshop_class* i_this) { old_stick_x = mDoCPd_c::getSubStickX(PAD_1); cLib_addCalcAngleS2(&i_this->field_0x4060, i_this->field_0x4062, 4, 0x1000); +#if TARGET_PC + if (dusk::getSettings().game.enableGyroRollgoal) { + dusk::gyro::rollgoalTick(true, i_this->field_0x4060); + } +#endif cMtx_YrotS(*calc_mtx, -i_this->field_0x4060); sp5C.x = mDoCPd_c::getStickX3D(PAD_1); @@ -765,9 +782,26 @@ static void koro2_game(fshop_class* i_this) { reg_f30 = 0.0f; } +#if TARGET_PC + if (dusk::getSettings().game.enableGyroRollgoal) { + s16 rg_add_x; + s16 rg_add_z; + dusk::gyro::rollgoalTableOffset(rg_add_x, rg_add_z); + s16 tgt_x = static_cast(reg_f30 * (-6000.0f + JREG_F(7))) + rg_add_x; + s16 tgt_z = static_cast(reg_f31 * (-6000.0f + JREG_F(8))) + rg_add_z; + cLib_addCalcAngleS2(&i_this->field_0x4020.x, tgt_x, 4, 0x200); + cLib_addCalcAngleS2(&i_this->field_0x4020.z, tgt_z, 4, 0x200); + } +#else cLib_addCalcAngleS2(&i_this->field_0x4020.x, reg_f30 * (-6000.0f + JREG_F(7)), 4, 0x200); cLib_addCalcAngleS2(&i_this->field_0x4020.z, reg_f31 * (-6000.0f + JREG_F(8)), 4, 0x200); +#endif } +#if TARGET_PC + if (i_this->field_0x4010 != 2) { + dusk::gyro::rollgoalTick(false, i_this->field_0x4060); + } +#endif break; } @@ -1145,6 +1179,12 @@ static int daFshop_Delete(fshop_class* i_this) { } } +#if TARGET_PC + if (dusk::getSettings().game.enableGyroRollgoal) { + dusk::gyro::set_sensor_keep_alive(false); + } +#endif + return 1; } diff --git a/src/dusk/gyro.cpp b/src/dusk/gyro.cpp new file mode 100644 index 0000000000..a2b026713a --- /dev/null +++ b/src/dusk/gyro.cpp @@ -0,0 +1,123 @@ +#include "dusk/gyro.h" +#include "d/actor/d_a_alink.h" + +namespace dusk::gyro { +namespace { +// TODO: Make deadband and smoothing configurable +constexpr float kDeadbandRadS = 0.04f; +constexpr float kSmoothAlpha = 0.35f; +constexpr s32 kRollgoalTableMaxOffset = 12000; + +bool s_sensor_enabled = false; +float s_smooth_gx = 0.0f; +float s_smooth_gy = 0.0f; +float s_smooth_gz = 0.0f; +float s_yaw_rad = 0.0f; +float s_yaw_rad_pending = 0.0f; +float s_pitch_rad = 0.0f; +float s_pitch_rad_pending = 0.0f; +float s_roll_rad = 0.0f; +s32 s_rollgoal_ax = 0; +s32 s_rollgoal_az = 0; + +void reset_filter_state() { + s_smooth_gx = s_smooth_gy = s_smooth_gz = 0.0f; + s_yaw_rad_pending = s_pitch_rad_pending = s_roll_rad = 0.0f; + s_rollgoal_ax = s_rollgoal_az = 0; +} + +float apply_deadband(float v) { + if (v > -kDeadbandRadS && v < kDeadbandRadS) { + return 0.0f; + } + return v; +} +} // namespace + +bool s_sensor_keep_alive = false; + +bool get_sensor_keep_alive() { return s_sensor_keep_alive; } +void set_sensor_keep_alive(bool value) { s_sensor_keep_alive = value; } + +bool queryGyroAimItemContext() { + if (!static_cast(dusk::getSettings().game.enableGyroAim)) { + return false; + } + + daAlink_c* link = daAlink_getAlinkActorClass(); + if (link == nullptr) { + return false; + } + + return link->checkGyroAimItemContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10); +} + +void read(float dt) { + if (!s_sensor_keep_alive && !(dusk::getSettings().game.enableGyroAim && queryGyroAimItemContext())) { + if (s_sensor_enabled) { + PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_GYRO, FALSE); + s_sensor_enabled = false; + } + reset_filter_state(); + return; + } + + if (!s_sensor_enabled) { + if (!PADHasSensor(PAD_CHAN0, PAD_SENSOR_GYRO)) { + return; + } + if (!PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_GYRO, TRUE)) { + return; + } + s_sensor_enabled = true; + } + + f32 gyro[3]; + if (!PADGetSensorData(PAD_CHAN0, PAD_SENSOR_GYRO, gyro, 3)) { + return; + } + + s_smooth_gx += kSmoothAlpha * (gyro[0] - s_smooth_gx); + s_smooth_gy += kSmoothAlpha * (gyro[1] - s_smooth_gy); + s_smooth_gz += kSmoothAlpha * (gyro[2] - s_smooth_gz); + + s_pitch_rad = apply_deadband(s_smooth_gx) * dt * dusk::getSettings().game.gyroAimSensitivityX; + s_yaw_rad = apply_deadband(s_smooth_gy) * dt * dusk::getSettings().game.gyroAimSensitivityY; + s_roll_rad = apply_deadband(s_smooth_gz) * dt * dusk::getSettings().game.gyroAimSensitivityX; // GYRO NOTE: Exposing Z sensitivity seems unusual, so I'm just using X + + s_pitch_rad_pending += s_pitch_rad; + s_yaw_rad_pending += s_yaw_rad; +} + +void consumeAimDeltas(float& out_yaw_rad, float& out_pitch_rad) { + out_yaw_rad = s_yaw_rad_pending; + out_pitch_rad = s_pitch_rad_pending; + s_yaw_rad_pending = s_pitch_rad_pending = 0.0f; +} + +void rollgoalTick(bool play_active, s16 camera_yaw) { + if (!play_active) { + reset_filter_state(); + return; + } + + const float pitch_rad = s_pitch_rad * dusk::getSettings().game.gyroRollgoalSensitivity; + const float roll_rad = s_roll_rad * dusk::getSettings().game.gyroRollgoalSensitivity; + + s_rollgoal_az += cM_rad2s(roll_rad); + cXyz in(roll_rad, 0.0f, pitch_rad); + cXyz out; + cMtx_YrotS(*calc_mtx, static_cast(-camera_yaw)); + MtxPosition(&in, &out); + + s_rollgoal_ax += cM_rad2s(out.z); + s_rollgoal_az += cM_rad2s(out.x); + s_rollgoal_ax = std::clamp(s_rollgoal_ax, -kRollgoalTableMaxOffset, kRollgoalTableMaxOffset); + s_rollgoal_az = std::clamp(s_rollgoal_az, -kRollgoalTableMaxOffset, kRollgoalTableMaxOffset); +} + +void rollgoalTableOffset(s16& out_add_x, s16& out_add_z) { + out_add_x = static_cast(s_rollgoal_ax); + out_add_z = static_cast(s_rollgoal_az); +} +} // namespace dusk::gyro diff --git a/src/dusk/gyro_aim.cpp b/src/dusk/gyro_aim.cpp deleted file mode 100644 index 48edd640b3..0000000000 --- a/src/dusk/gyro_aim.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "dusk/gyro_aim.h" - -#include -#include "d/actor/d_a_alink.h" - -namespace dusk::gyro_aim { -namespace { -// TODO: Make deadband and smoothing configurable -constexpr float kDeadbandRadS = 0.04f; -constexpr float kSmoothAlpha = 0.35f; -bool s_sensor_enabled = false; -float s_smooth_gx = 0.0f; -float s_smooth_gy = 0.0f; -float s_pending_yaw_rad = 0.0f; -float s_pending_pitch_rad = 0.0f; - -void reset_filter_state() { - s_smooth_gx = s_smooth_gy = 0.0f; - s_pending_yaw_rad = s_pending_pitch_rad = 0.0f; -} - -float apply_deadband(float v) { - if (v > -kDeadbandRadS && v < kDeadbandRadS) { - return 0.0f; - } - return v; -} -} // namespace - -void read(float dt, bool context_active) { - if (!context_active || !static_cast(dusk::getSettings().game.enableGyroAim)) { - SDL_Gamepad* pad = SDL_GetGamepadFromPlayerIndex(0); - if (pad != nullptr && s_sensor_enabled) { - SDL_SetGamepadSensorEnabled(pad, SDL_SENSOR_GYRO, false); - s_sensor_enabled = false; - } - reset_filter_state(); - return; - } - - SDL_Gamepad* pad = SDL_GetGamepadFromPlayerIndex(0); - if (pad == nullptr || !SDL_GamepadHasSensor(pad, SDL_SENSOR_GYRO)) { - return; - } - - if (!s_sensor_enabled) { - if (!SDL_SetGamepadSensorEnabled(pad, SDL_SENSOR_GYRO, true)) { - return; - } - s_sensor_enabled = true; - reset_filter_state(); - } - - float gyro[3]; - if (!SDL_GetGamepadSensorData(pad, SDL_SENSOR_GYRO, gyro, 3)) { - return; - } - - s_smooth_gx += kSmoothAlpha * (gyro[0] - s_smooth_gx); - s_smooth_gy += kSmoothAlpha * (gyro[1] - s_smooth_gy); - float yaw_rate = apply_deadband(s_smooth_gy); - float pitch_rate = apply_deadband(s_smooth_gx); - - s_pending_yaw_rad += yaw_rate * dt * dusk::getSettings().game.gyroAimSensitivityX; - s_pending_pitch_rad += pitch_rate * dt * dusk::getSettings().game.gyroAimSensitivityY; -} - -void consumeAimDeltas(float& out_yaw_rad, float& out_pitch_rad) { - out_yaw_rad = s_pending_yaw_rad; - out_pitch_rad = s_pending_pitch_rad; - s_pending_yaw_rad = 0.0f; - s_pending_pitch_rad = 0.0f; -} - -bool queryGyroAimItemContext() { - if (!static_cast(dusk::getSettings().game.enableGyroAim)) { - return false; - } - - daAlink_c* link = daAlink_getAlinkActorClass(); - if (link == nullptr) { - return false; - } - - return link->checkGyroAimItemContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10); -} -} // namespace dusk::gyro_aim diff --git a/src/dusk/imgui/ImGuiControllerOverlay.cpp b/src/dusk/imgui/ImGuiControllerOverlay.cpp index 8c24063375..dd0a15806c 100644 --- a/src/dusk/imgui/ImGuiControllerOverlay.cpp +++ b/src/dusk/imgui/ImGuiControllerOverlay.cpp @@ -5,7 +5,7 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuGame.hpp" -#include +#include namespace dusk { void ImGuiMenuGame::windowInputViewer() { @@ -260,31 +260,33 @@ namespace dusk { size.y = 130 * scale; ImGui::Dummy(size); - SDL_Gamepad* pad = SDL_GetGamepadFromPlayerIndex(0); - if (pad != nullptr && SDL_GamepadHasSensor(pad, SDL_SENSOR_GYRO)) { + if (PADHasSensor(PAD_1, PAD_SENSOR_GYRO) == TRUE) { ImGui::Separator(); - ImGui::TextUnformatted("Gyro"); + ImGui::Checkbox("Gyro Values", &m_showInputViewerGyro); + if (m_showInputViewerGyro) { + ImGui::TextUnformatted("Gyro"); - constexpr float kBarScale = 4.0f; - auto bar = [kBarScale](const char* label, float v) { - const float a = std::fabs(v); - const float t = std::min(1.f, a / kBarScale); - char overlay[32]; - snprintf(overlay, sizeof(overlay), "%s %+.3f", label, v); - ImGui::ProgressBar(t, ImVec2(-1, 0), overlay); - }; + constexpr float kBarScale = 4.0f; + auto bar = [kBarScale](const char* label, float v) { + const float a = std::fabs(v); + const float t = std::min(1.f, a / kBarScale); + char overlay[32]; + snprintf(overlay, sizeof(overlay), "%s %+.3f", label, v); + ImGui::ProgressBar(t, ImVec2(-1, 0), overlay); + }; - if (SDL_SetGamepadSensorEnabled(pad, SDL_SENSOR_GYRO, true)) { - float gyro[3]; - if (SDL_GetGamepadSensorData(pad, SDL_SENSOR_GYRO, gyro, 3)) { - bar("X", gyro[0]); - bar("Y", gyro[1]); - bar("Z", gyro[2]); + if (PADSetSensorEnabled(PAD_1, PAD_SENSOR_GYRO, TRUE) == TRUE) { + f32 gyro[3]; + if (PADGetSensorData(PAD_1, PAD_SENSOR_GYRO, gyro, 3) == TRUE) { + bar("X", gyro[0]); + bar("Y", gyro[1]); + bar("Z", gyro[2]); + } + } else { + bar("X", 0.f); + bar("Y", 0.f); + bar("Z", 0.f); } - } else { - bar("X", 0.f); - bar("Y", 0.f); - bar("Z", 0.f); } } diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index b7d75fea2f..516b924680 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -148,10 +148,29 @@ namespace dusk { "Slingshot, Gale Boomerang, Hero's Bow, Clawshot(s), Ball and Chain, and Dominion Rod."); } - config::ImGuiSliderFloat("Gyro Pitch Sensitivity", getSettings().game.gyroAimSensitivityY, 0.25f, 4.0f, "%.2f"); - config::ImGuiSliderFloat("Gyro Yaw Sensitivity", getSettings().game.gyroAimSensitivityX, 0.25f, 4.0f, "%.2f"); - config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroAimInvertPitch); - config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroAimInvertYaw); + config::ImGuiCheckbox("Gyro Rollgoal", getSettings().game.enableGyroRollgoal); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Enables the gyroscope on supported controllers to\n" + "tilt the Rollgoal table in Hena's Cabin."); + } + + if (getSettings().game.enableGyroAim) { + config::ImGuiSliderFloat("Gyro Pitch Sensitivity", getSettings().game.gyroAimSensitivityY, 0.25f, 4.0f, "%.2f"); + config::ImGuiSliderFloat("Gyro Yaw Sensitivity", getSettings().game.gyroAimSensitivityX, 0.25f, 4.0f, "%.2f"); + } + + if (getSettings().game.enableGyroRollgoal) { + config::ImGuiSliderFloat("Rollgoal Sensitivity", getSettings().game.gyroRollgoalSensitivity, 0.25f, 4.0f, "%.2f"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Additional multiplier for scaling how strongly\n" + "the gyroscope affects the Rollgoal table."); + } + } + + if (getSettings().game.enableGyroAim) { + config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroAimInvertPitch); + config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroAimInvertYaw); + } ImGui::SeparatorText("Tools"); diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index 77e5952d1a..4d51cbc865 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -30,6 +30,7 @@ namespace dusk { bool m_showControllerConfig = false; bool m_showInputViewer = false; + bool m_showInputViewerGyro = false; int m_inputOverlayCorner = 3; std::string m_controllerName; }; diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 99ff183e79..fd6569ed2c 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -55,8 +55,10 @@ UserSettings g_userSettings = { // Input .enableGyroAim {"game.enableGyroAim", false}, + .enableGyroRollgoal {"game.enableGyroRollgoal", false}, .gyroAimSensitivityX {"game.gyroAimSensitivityX", 1.0f}, .gyroAimSensitivityY {"game.gyroAimSensitivityY", 1.0f}, + .gyroRollgoalSensitivity {"game.gyroRollgoalSensitivity", 1.0f}, .gyroAimInvertPitch {"game.gyroAimInvertPitch", false}, .gyroAimInvertYaw {"game.gyroAimInvertYaw", false}, @@ -134,8 +136,10 @@ void registerSettings() { Register(g_userSettings.game.fastSpinner); Register(g_userSettings.game.enableFrameInterpolation); Register(g_userSettings.game.enableGyroAim); + Register(g_userSettings.game.enableGyroRollgoal); Register(g_userSettings.game.gyroAimSensitivityX); Register(g_userSettings.game.gyroAimSensitivityY); + Register(g_userSettings.game.gyroRollgoalSensitivity); Register(g_userSettings.game.gyroAimInvertPitch); Register(g_userSettings.game.gyroAimInvertYaw); diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 7e34770f11..715f9d3c84 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -51,7 +51,7 @@ #include "dusk/crash_reporting.h" #include "dusk/dusk.h" #include "dusk/frame_interpolation.h" -#include "dusk/gyro_aim.h" +#include "dusk/gyro.h" #include "dusk/imgui/ImGuiEngine.hpp" #include "dusk/logging.h" #include "dusk/main.h" @@ -245,9 +245,7 @@ void main01(void) { dusk::frame_interp::notify_presentation_frame(); while (accumulator >= kSimStepSeconds) { mDoCPd_c::read(); - if (dusk::getSettings().game.enableGyroAim) { - dusk::gyro_aim::read(static_cast(kSimStepSeconds), dusk::gyro_aim::queryGyroAimItemContext()); - } + dusk::gyro::read(kSimStepSeconds); fapGm_Execute(); mDoAud_Execute(); accumulator -= kSimStepSeconds; @@ -262,9 +260,7 @@ void main01(void) { // Game Inputs mDoCPd_c::read(); - if (dusk::getSettings().game.enableGyroAim) { - dusk::gyro_aim::read(static_cast(frame_seconds), dusk::gyro_aim::queryGyroAimItemContext()); - } + dusk::gyro::read(frame_seconds); // EXECUTE GAME LOGIC & RENDER // This calls mDoGph_Painter -> JFWDisplay -> GX Functions