isle-portable/extensions/src/thirdpersoncamera.cpp
Christian Semmler 74271aa189
Fix mobile camera zoom/transition and name bubble issues
- 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) <noreply@anthropic.com>
2026-03-13 19:10:49 -07:00

262 lines
6.0 KiB
C++

#include "extensions/thirdpersoncamera.h"
#include "extensions/common/charactercustomizer.h"
#include "extensions/thirdpersoncamera/controller.h"
#include "islepathactor.h"
#include "legoeventnotificationparam.h"
#include "legoinputmanager.h"
#include "legonavcontroller.h"
#include "legopathactor.h"
#include "legovideomanager.h"
#include "misc.h"
#include "mxcore.h"
#include "mxmisc.h"
#include "mxticklemanager.h"
#include "realtime/vector.h"
#include "roi/legoroi.h"
#include <SDL3/SDL_stdinc.h>
using namespace Extensions;
using namespace Extensions::Common;
std::map<std::string, std::string> ThirdPersonCameraExt::options;
bool ThirdPersonCameraExt::enabled = false;
ThirdPersonCamera::Controller* ThirdPersonCameraExt::s_camera = nullptr;
bool ThirdPersonCameraExt::s_registered = false;
bool ThirdPersonCameraExt::s_inIsleWorld = false;
namespace Extensions
{
namespace ThirdPersonCamera
{
class TickleAdapter : public MxCore {
public:
TickleAdapter(Controller* p_camera) : m_camera(p_camera) {}
MxResult Tickle() override
{
if (m_camera) {
m_camera->Tick(0.016f);
}
return SUCCESS;
}
const char* ClassName() const override { return "ThirdPersonCamera::TickleAdapter"; }
private:
Controller* m_camera;
};
} // namespace ThirdPersonCamera
} // namespace Extensions
static Extensions::ThirdPersonCamera::TickleAdapter* s_tickleAdapter = nullptr;
void ThirdPersonCameraExt::Initialize()
{
if (!s_camera) {
s_camera = new ThirdPersonCamera::Controller();
}
s_camera->Enable();
}
void ThirdPersonCameraExt::HandleCreate()
{
if (!s_registered && s_camera) {
s_tickleAdapter = new Extensions::ThirdPersonCamera::TickleAdapter(s_camera);
TickleManager()->RegisterClient(s_tickleAdapter, 10);
s_registered = true;
}
}
MxBool ThirdPersonCameraExt::HandleWorldEnable(LegoWorld* p_world, MxBool p_enable)
{
if (!s_camera) {
return FALSE;
}
if (p_enable) {
s_camera->OnWorldEnabled(p_world);
s_inIsleWorld = true;
}
else {
s_camera->OnWorldDisabled(p_world);
s_inIsleWorld = false;
}
return TRUE;
}
void ThirdPersonCameraExt::HandleActorEnter(IslePathActor* p_actor)
{
if (s_camera) {
s_camera->OnActorEnter(p_actor);
}
}
void ThirdPersonCameraExt::HandleActorExit(IslePathActor* p_actor)
{
if (s_camera) {
s_camera->OnActorExit(p_actor);
}
}
void ThirdPersonCameraExt::HandleCamAnimEnd(LegoPathActor* p_actor)
{
if (s_camera) {
s_camera->OnCamAnimEnd(p_actor);
}
}
void ThirdPersonCameraExt::OnSDLEvent(SDL_Event* p_event)
{
if (!s_camera || !s_inIsleWorld) {
return;
}
s_camera->HandleSDLEventImpl(p_event);
if (s_camera->ConsumeAutoDisable()) {
s_camera->Disable(/*p_preserveTouch=*/true);
}
else if (s_camera->ConsumeAutoEnable()) {
// 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();
}
}
MxBool ThirdPersonCameraExt::IsThirdPersonCameraActive()
{
if (s_camera && s_camera->IsActive()) {
return TRUE;
}
return FALSE;
}
MxBool ThirdPersonCameraExt::HandleTouchInput(SDL_Event* p_event)
{
if (!s_camera || !s_camera->IsActive()) {
return FALSE;
}
switch (p_event->type) {
case SDL_EVENT_FINGER_DOWN:
if (s_camera->TryClaimFinger(p_event->tfinger)) {
return TRUE;
}
return FALSE;
case SDL_EVENT_FINGER_MOTION:
if (s_camera->IsFingerTracked(p_event->tfinger.fingerID)) {
return TRUE;
}
return FALSE;
case SDL_EVENT_FINGER_UP:
case SDL_EVENT_FINGER_CANCELED:
if (s_camera->TryReleaseFinger(p_event->tfinger.fingerID)) {
return TRUE;
}
return FALSE;
default:
return FALSE;
}
}
MxBool ThirdPersonCameraExt::HandleNavOverride(
LegoNavController* p_nav,
const Vector3& p_curPos,
const Vector3& p_curDir,
Vector3& p_newPos,
Vector3& p_newDir,
float p_deltaTime
)
{
if (!s_camera) {
return FALSE;
}
if (!s_camera->IsActive()) {
return FALSE;
}
return s_camera->HandleCameraRelativeMovement(p_nav, p_curPos, p_curDir, p_newPos, p_newDir, p_deltaTime);
}
MxBool ThirdPersonCameraExt::HandleROIClick(LegoROI* p_rootROI, LegoEventNotificationParam& p_param)
{
if (!s_camera) {
return FALSE;
}
if (!s_camera->GetDisplayROI() || s_camera->GetDisplayROI() != p_rootROI) {
return FALSE;
}
uint8_t changeType;
int partIndex;
if (!CharacterCustomizer::ResolveClickChangeType(changeType, partIndex, p_param.GetROI())) {
return TRUE;
}
s_camera->ApplyCustomizeChange(changeType, static_cast<uint8_t>(partIndex >= 0 ? partIndex : 0xFF));
LegoROI* effectROI = s_camera->GetDisplayROI();
if (effectROI) {
CharacterCustomizer::PlayClickSound(effectROI, s_camera->GetCustomizeState(), changeType == CHANGE_MOOD);
if (!s_camera->IsInVehicle() && !s_camera->IsInMultiPartEmote()) {
s_camera->StopClickAnimation();
MxU32 clickAnimId = CharacterCustomizer::PlayClickAnimation(effectROI, s_camera->GetCustomizeState());
s_camera->SetClickAnimObjectId(clickAnimId);
}
}
return TRUE;
}
MxBool ThirdPersonCameraExt::IsClonedCharacter(const char* p_name)
{
if (!s_camera) {
return FALSE;
}
if (s_camera->GetDisplayROI() != nullptr && !SDL_strcasecmp(s_camera->GetDisplayROI()->GetName(), p_name)) {
return TRUE;
}
return FALSE;
}
ThirdPersonCamera::Controller* ThirdPersonCameraExt::GetCamera()
{
return s_camera;
}
void ThirdPersonCameraExt::HandleSDLEvent(SDL_Event* p_event)
{
Extension<ThirdPersonCameraExt>::Call(TP::HandleSDLEvent, p_event);
}