From 74271aa189031f16d695bb5914eeb99948199ccb Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 13 Mar 2026 19:10:49 -0700 Subject: [PATCH] Fix mobile camera zoom/transition and name bubble issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reduce pinch zoom sensitivity (15x → 6x multiplier) - Add cumulative deadzone threshold for 1st/3rd person transitions to prevent accidental mode switches from slight finger movement - Preserve camera touch tracking through 3rd→1st transition so the same fingers can pinch back without lifting (seamless round-trip) - On 1st→3rd transition, selectively clear only camera-owned fingers from LegoInputManager's touch scheme state, preserving any active left-side movement finger - Suppress camera gesture processing until finger positions re-sync after transition to prevent camera jumps from stale coordinates - Hide local player name bubble when transitioning to 1st person, restore on transition back to 3rd person Co-Authored-By: Claude Opus 4.6 (1M context) --- .../lego/legoomni/include/legoinputmanager.h | 1 + extensions/include/extensions/fwd.h | 1 + .../extensions/thirdpersoncamera/controller.h | 5 +- .../thirdpersoncamera/inputhandler.h | 6 +++ extensions/src/multiplayer/networkmanager.cpp | 9 ++++ extensions/src/thirdpersoncamera.cpp | 24 ++++++++- .../src/thirdpersoncamera/controller.cpp | 6 ++- .../src/thirdpersoncamera/inputhandler.cpp | 53 ++++++++++++++++--- 8 files changed, 94 insertions(+), 11 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index 79ca4edd..670b6373 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -180,6 +180,7 @@ class LegoInputManager : public MxPresenter { // LegoInputManager::`scalar deleting destructor' friend class Extensions::MultiplayerExt; + friend class Extensions::ThirdPersonCameraExt; private: void InitializeHaptics(); diff --git a/extensions/include/extensions/fwd.h b/extensions/include/extensions/fwd.h index 3b4d1fd9..e78bead5 100644 --- a/extensions/include/extensions/fwd.h +++ b/extensions/include/extensions/fwd.h @@ -4,6 +4,7 @@ namespace Extensions { class MultiplayerExt; +class ThirdPersonCameraExt; namespace Common { class CharacterCloner; diff --git a/extensions/include/extensions/thirdpersoncamera/controller.h b/extensions/include/extensions/thirdpersoncamera/controller.h index 136f752b..ae145a93 100644 --- a/extensions/include/extensions/thirdpersoncamera/controller.h +++ b/extensions/include/extensions/thirdpersoncamera/controller.h @@ -26,7 +26,7 @@ class Controller { Controller(); void Enable(); - void Disable(); + void Disable(bool p_preserveTouch = false); bool IsEnabled() const { return m_enabled; } bool IsActive() const { return m_active; } @@ -77,10 +77,13 @@ class Controller { float GetOrbitDistance() const { return m_orbit.GetOrbitDistance(); } void SetOrbitDistance(float p_distance) { m_orbit.SetOrbitDistance(p_distance); } void ResetTouchState() { m_input.ResetTouchState(); } + void SuppressGestures() { m_input.SuppressGestures(); } bool TryClaimFinger(const SDL_TouchFingerEvent& event) { return m_input.TryClaimFinger(event, m_active); } bool TryReleaseFinger(SDL_FingerID id) { return m_input.TryReleaseFinger(id); } bool IsFingerTracked(SDL_FingerID id) const { return m_input.IsFingerTracked(id); } + int GetTouchCount() const { return m_input.GetTouchCount(); } + SDL_FingerID GetFingerID(int idx) const { return m_input.GetFingerID(idx); } void FreezeDisplayActor() { m_display.FreezeDisplayActor(); } void UnfreezeDisplayActor() { m_display.UnfreezeDisplayActor(); } diff --git a/extensions/include/extensions/thirdpersoncamera/inputhandler.h b/extensions/include/extensions/thirdpersoncamera/inputhandler.h index ae493e0d..120aa06f 100644 --- a/extensions/include/extensions/thirdpersoncamera/inputhandler.h +++ b/extensions/include/extensions/thirdpersoncamera/inputhandler.h @@ -18,20 +18,26 @@ class InputHandler { bool TryClaimFinger(const SDL_TouchFingerEvent& p_event, bool p_active); bool TryReleaseFinger(SDL_FingerID p_id); bool IsFingerTracked(SDL_FingerID p_id) const; + int GetTouchCount() const { return m_touch.count; } + SDL_FingerID GetFingerID(int p_idx) const { return m_touch.id[p_idx]; } bool ConsumeAutoDisable(); bool ConsumeAutoEnable(); void ResetTouchState() { m_touch = {}; } + void SuppressGestures(); static constexpr float CAMERA_ZONE_X = 0.5f; + static constexpr float PINCH_TRANSITION_THRESHOLD = 0.03f; private: struct TouchState { SDL_FingerID id[2]; float x[2], y[2]; + bool synced[2]; int count; float initialPinchDist; + float gesturePinchDist; } m_touch; bool m_wantsAutoDisable; diff --git a/extensions/src/multiplayer/networkmanager.cpp b/extensions/src/multiplayer/networkmanager.cpp index d128ae25..bf9e45ad 100644 --- a/extensions/src/multiplayer/networkmanager.cpp +++ b/extensions/src/multiplayer/networkmanager.cpp @@ -75,6 +75,15 @@ MxResult NetworkManager::Tickle() if (cameraEnabled != m_lastCameraEnabled) { m_lastCameraEnabled = cameraEnabled; NotifyThirdPersonChanged(cameraEnabled); + + if (m_localNameBubble) { + if (!cameraEnabled) { + m_localNameBubble->SetVisible(false); + } + else if (m_showNameBubbles) { + m_localNameBubble->SetVisible(true); + } + } } // Create local name bubble when display ROI becomes available diff --git a/extensions/src/thirdpersoncamera.cpp b/extensions/src/thirdpersoncamera.cpp index 754aefa0..d789512e 100644 --- a/extensions/src/thirdpersoncamera.cpp +++ b/extensions/src/thirdpersoncamera.cpp @@ -4,6 +4,7 @@ #include "extensions/thirdpersoncamera/controller.h" #include "islepathactor.h" #include "legoeventnotificationparam.h" +#include "legoinputmanager.h" #include "legonavcontroller.h" #include "legopathactor.h" #include "legovideomanager.h" @@ -118,10 +119,29 @@ void ThirdPersonCameraExt::OnSDLEvent(SDL_Event* p_event) s_camera->HandleSDLEventImpl(p_event); if (s_camera->ConsumeAutoDisable()) { - s_camera->Disable(); + s_camera->Disable(/*p_preserveTouch=*/true); } else if (s_camera->ConsumeAutoEnable()) { - s_camera->ResetTouchState(); + // Clear the movement system's touch state for camera-owned fingers only, + // so any virtual thumbstick input from 1st-person mode is zeroed while + // leaving a left-side movement finger intact. + LegoInputManager* im = InputManager(); + if (im) { + for (int i = 0; i < s_camera->GetTouchCount(); i++) { + SDL_FingerID fid = s_camera->GetFingerID(i); + if (im->m_touchFinger == fid) { + im->m_touchFinger = 0; + im->m_touchVirtualThumb = {0, 0}; + im->m_touchVirtualThumbOrigin = {0, 0}; + } + im->m_touchFlags.erase(fid); + } + } + + // Suppress camera gestures until finger positions re-sync to avoid + // a camera jump from stale positions carried through the transition. + s_camera->SuppressGestures(); + s_camera->SetOrbitDistance(ThirdPersonCamera::Controller::MIN_DISTANCE); s_camera->Enable(); } diff --git a/extensions/src/thirdpersoncamera/controller.cpp b/extensions/src/thirdpersoncamera/controller.cpp index d32878f8..b20ddec6 100644 --- a/extensions/src/thirdpersoncamera/controller.cpp +++ b/extensions/src/thirdpersoncamera/controller.cpp @@ -39,7 +39,7 @@ void Controller::Enable() ReinitForCharacter(); } -void Controller::Disable() +void Controller::Disable(bool p_preserveTouch) { m_enabled = false; @@ -60,7 +60,9 @@ void Controller::Disable() m_animator.ClearAll(); m_orbit.ResetOrbitState(); - m_input.ResetTouchState(); + if (!p_preserveTouch) { + m_input.ResetTouchState(); + } } void Controller::OnActorEnter(IslePathActor* p_actor) diff --git a/extensions/src/thirdpersoncamera/inputhandler.cpp b/extensions/src/thirdpersoncamera/inputhandler.cpp index 70e93f09..2a6fbbc9 100644 --- a/extensions/src/thirdpersoncamera/inputhandler.cpp +++ b/extensions/src/thirdpersoncamera/inputhandler.cpp @@ -21,12 +21,14 @@ bool InputHandler::TryClaimFinger(const SDL_TouchFingerEvent& p_event, bool p_ac m_touch.id[idx] = p_event.fingerID; m_touch.x[idx] = p_event.x; m_touch.y[idx] = p_event.y; + m_touch.synced[idx] = true; m_touch.count++; if (m_touch.count == 2) { float dx = m_touch.x[1] - m_touch.x[0]; float dy = m_touch.y[1] - m_touch.y[0]; m_touch.initialPinchDist = SDL_sqrtf(dx * dx + dy * dy); + m_touch.gesturePinchDist = m_touch.initialPinchDist; } return true; @@ -40,9 +42,11 @@ bool InputHandler::TryReleaseFinger(SDL_FingerID p_id) m_touch.id[0] = m_touch.id[1]; m_touch.x[0] = m_touch.x[1]; m_touch.y[0] = m_touch.y[1]; + m_touch.synced[0] = m_touch.synced[1]; } m_touch.count--; m_touch.initialPinchDist = 0.0f; + m_touch.gesturePinchDist = 0.0f; return true; } } @@ -69,6 +73,14 @@ bool InputHandler::ConsumeAutoEnable() return std::exchange(m_wantsAutoEnable, false); } +void InputHandler::SuppressGestures() +{ + m_touch.synced[0] = false; + m_touch.synced[1] = false; + m_touch.initialPinchDist = 0.0f; + m_touch.gesturePinchDist = 0.0f; +} + void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool p_active) { switch (p_event->type) { @@ -117,12 +129,14 @@ void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool m_touch.id[idx] = p_event->tfinger.fingerID; m_touch.x[idx] = p_event->tfinger.x; m_touch.y[idx] = p_event->tfinger.y; + m_touch.synced[idx] = true; m_touch.count++; if (m_touch.count == 2) { float dx = m_touch.x[1] - m_touch.x[0]; float dy = m_touch.y[1] - m_touch.y[0]; m_touch.initialPinchDist = SDL_sqrtf(dx * dx + dy * dy); + m_touch.gesturePinchDist = m_touch.initialPinchDist; } } break; @@ -134,6 +148,13 @@ void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool break; } if (m_touch.id[0] == p_event->tfinger.fingerID) { + if (!m_touch.synced[0]) { + m_touch.x[0] = p_event->tfinger.x; + m_touch.y[0] = p_event->tfinger.y; + m_touch.synced[0] = true; + break; + } + float oldX = m_touch.x[0]; float oldY = m_touch.y[0]; m_touch.x[0] = p_event->tfinger.x; @@ -158,6 +179,20 @@ void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool break; } + if (!m_touch.synced[idx]) { + m_touch.x[idx] = p_event->tfinger.x; + m_touch.y[idx] = p_event->tfinger.y; + m_touch.synced[idx] = true; + + if (m_touch.synced[0] && m_touch.synced[1]) { + float dx = m_touch.x[1] - m_touch.x[0]; + float dy = m_touch.y[1] - m_touch.y[0]; + m_touch.initialPinchDist = SDL_sqrtf(dx * dx + dy * dy); + m_touch.gesturePinchDist = m_touch.initialPinchDist; + } + break; + } + float oldX = m_touch.x[idx]; float oldY = m_touch.y[idx]; m_touch.x[idx] = p_event->tfinger.x; @@ -171,20 +206,26 @@ void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool float pinchDelta = m_touch.initialPinchDist - newDist; if (!p_active) { - if (pinchDelta > 0) { + float totalDelta = m_touch.gesturePinchDist - newDist; + if (totalDelta > PINCH_TRANSITION_THRESHOLD) { m_wantsAutoEnable = true; + m_touch.gesturePinchDist = newDist; } m_touch.initialPinchDist = newDist; break; } - if (p_orbit.GetOrbitDistance() <= OrbitCamera::MIN_DISTANCE && pinchDelta < 0) { - m_wantsAutoDisable = true; - m_touch.initialPinchDist = newDist; - break; + if (p_orbit.GetOrbitDistance() <= OrbitCamera::MIN_DISTANCE) { + float totalDelta = newDist - m_touch.gesturePinchDist; + if (totalDelta > PINCH_TRANSITION_THRESHOLD) { + m_wantsAutoDisable = true; + m_touch.initialPinchDist = newDist; + m_touch.gesturePinchDist = newDist; + break; + } } - p_orbit.AdjustDistance(pinchDelta * 15.0f); + p_orbit.AdjustDistance(pinchDelta * 6.0f); p_orbit.ClampDistance(); m_touch.initialPinchDist = newDist; }