From 5f9fc17e90f3d5a219c52dc3cecf449275174ffc Mon Sep 17 00:00:00 2001 From: leviathan Date: Thu, 7 May 2026 22:52:05 -0700 Subject: overhaul game-control config and UI --- indra/newview/llagentcamera.cpp | 101 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 8 deletions(-) (limited to 'indra/newview/llagentcamera.cpp') diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 0fe5f622e3..ef269be0be 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -185,7 +185,9 @@ LLAgentCamera::LLAgentCamera() : mPanLeftKey(0.f), mPanRightKey(0.f), mPanInKey(0.f), - mPanOutKey(0.f) + mPanOutKey(0.f), + mRollLeftKey(0.f), + mRollRightKey(0.f) { mFollowCam.setMaxCameraDistantFromSubject( MAX_CAMERA_DISTANCE_FROM_AGENT ); @@ -195,6 +197,7 @@ LLAgentCamera::LLAgentCamera() : resetPanDiff(); resetOrbitDiff(); + resetCameraRoll(); } // Requires gSavedSettings to be initialized. @@ -331,7 +334,8 @@ void LLAgentCamera::resetView(bool reset_camera, bool change_camera) //Camera Tool is needed for Free Camera Control Mode if (!LLFloaterCamera::inFreeCameraMode()) { - LLFloaterReg::hideInstance("build"); + if (LLFloaterReg::instanceVisible("build")) + LLFloaterReg::hideInstance("build"); // Switch back to basic toolset LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); @@ -355,6 +359,8 @@ void LLAgentCamera::resetView(bool reset_camera, bool change_camera) setFocusOnAvatar(true, ANIMATE); mCameraFOVZoomFactor = 0.f; + + resetCameraRoll(); } resetPanDiff(); resetOrbitDiff(); @@ -905,6 +911,19 @@ void LLAgentCamera::cameraOrbitOver(const F32 angle) } } +//----------------------------------------------------------------------------- +// cameraRollOver() +//----------------------------------------------------------------------------- +void LLAgentCamera::cameraRollOver(const F32 angle) +{ + mRollAngle += fmod(angle, F_TWO_PI); +} + +void LLAgentCamera::resetCameraRoll() +{ + mRollAngle = 0.f; +} + void LLAgentCamera::resetCameraOrbit() { LLVector3 camera_offset_unit(mCameraFocusOffsetTarget); @@ -918,6 +937,7 @@ void LLAgentCamera::resetCameraOrbit() cameraZoomIn(1.f); resetOrbitDiff(); + resetCameraRoll(); } void LLAgentCamera::resetOrbitDiff() @@ -1282,6 +1302,7 @@ void LLAgentCamera::updateCamera() const F32 ORBIT_OVER_RATE = 90.f * DEG_TO_RAD; // radians per second const F32 ORBIT_AROUND_RATE = 90.f * DEG_TO_RAD; // radians per second const F32 PAN_RATE = 5.f; // meters per second + const F32 ROLL_RATE = 45.f * DEG_TO_RAD; // radians per second if (gAgentCamera.getOrbitUpKey() || gAgentCamera.getOrbitDownKey()) { @@ -1323,6 +1344,12 @@ void LLAgentCamera::updateCamera() cameraPanUp(input_rate * PAN_RATE / gFPSClamped ); } + if (getRollLeftKey() || getRollRightKey()) + { + F32 input_rate = getRollRightKey() - getRollLeftKey(); + cameraRollOver(input_rate * ROLL_RATE / gFPSClamped); + } + // Clear camera keyboard keys. gAgentCamera.clearOrbitKeys(); gAgentCamera.clearPanKeys(); @@ -1552,6 +1579,7 @@ void LLAgentCamera::updateCamera() LLVector3 chest_scale = chest_joint->getScale(); // shorten avatar skeleton to avoid foot interpenetration +#if 0 // This screws up mouselook attachments if (!gAgentAvatarp->mInAir) { LLVector3 chest_offset = LLVector3(0.f, 0.f, chest_joint->getPosition().mV[VZ]) * torso_joint->getWorldRotation(); @@ -1565,9 +1593,37 @@ void LLAgentCamera::updateCamera() chest_joint->setScale(LLVector3(1.f, 1.f, scale_factor)); diff.mV[VZ] = 0.f; } +#endif + static LLCachedControl useRealisticMouselook(gSavedSettings, "AlchemyRealisticMouselook", false); + if (useRealisticMouselook) + { + auto frame_agent = gAgent.getFrameAgent(); + LLQuaternion agent_rot(frame_agent.getQuaternion()); + auto avatarp_parent = (LLViewerObject*)gAgentAvatarp->getParent(); + if(avatarp_parent) + { + auto avatarp_root = (LLViewerObject*)gAgentAvatarp->getRoot(); + if(avatarp_root) + { + auto decoupled = avatarp_root->flagCameraDecoupled(); + if(decoupled) + { + agent_rot *= avatarp_parent->getRenderRotation(); + } - // SL-315 - gAgentAvatarp->mPelvisp->setPosition(gAgentAvatarp->mPelvisp->getPosition() + diff); + } + } + + LLViewerCamera::getInstance()->updateCameraLocation(head_pos, + mCameraUpVector, + gAgentAvatarp->mHeadp->getWorldPosition() + + LLVector3(1.0, 0.0, 0.0) * agent_rot); + } + else + { + // SL-315 + gAgentAvatarp->mPelvisp->setPosition(gAgentAvatarp->mPelvisp->getPosition() + diff); + } gAgentAvatarp->mRoot->updateWorldMatrixChildren(); @@ -1594,6 +1650,18 @@ void LLAgentCamera::updateCamera() torso_joint->setScale(torso_scale); chest_joint->setScale(chest_scale); } + + // We have do this at the very end to make sure it takes all previous calculations into + // account and then applies our roll on top of it, besides it wouldn't even work otherwise. + LLQuaternion rot_quat = LLViewerCamera::instance().getQuaternion(); + LLMatrix3 rot_mat(mRollAngle, 0.f, 0.f); + rot_quat = LLQuaternion(rot_mat)*rot_quat; + + LLMatrix3 mat(rot_quat); + + LLViewerCamera::instance().mXAxis = LLVector3(mat.mMatrix[0]); + LLViewerCamera::instance().mYAxis = LLVector3(mat.mMatrix[1]); + LLViewerCamera::instance().mZAxis = LLVector3(mat.mMatrix[2]); } void LLAgentCamera::updateLastCamera() @@ -1697,7 +1765,6 @@ LLVector3d LLAgentCamera::calcFocusPositionTargetGlobal() LLVector3d LLAgentCamera::calcThirdPersonFocusOffset() { // ...offset from avatar - LLVector3d focus_offset; LLQuaternion agent_rot = gAgent.getFrameAgent().getQuaternion(); if (isAgentAvatarValid() && gAgentAvatarp->getParent()) { @@ -2124,6 +2191,22 @@ void LLAgentCamera::handleScrollWheel(S32 clicks) } else if (mFocusOnAvatar && (mCameraMode == CAMERA_MODE_THIRD_PERSON)) { + MASK mask = gKeyboard->currentMask(true); + if (mask & MASK_SHIFT) + { + LLVector3d offset = gSavedSettings.getVector3d("FocusOffsetRearView"); + offset.mdV[VZ] += 0.1f * (F32)clicks; + gSavedSettings.setVector3d("FocusOffsetRearView", offset); + return; + } + else if (mask & MASK_CONTROL) + { + LLVector3 offset = gSavedSettings.getVector3("CameraOffsetRearView"); + offset.mV[VZ] += 0.1f * (F32)clicks; + gSavedSettings.setVector3("CameraOffsetRearView", offset); + return; + } + F32 camera_offset_initial_mag = getCameraOffsetInitial().magVec(); F32 current_zoom_fraction = mTargetCameraDistance / (camera_offset_initial_mag * gSavedSettings.getF32("CameraOffsetScale")); @@ -2216,7 +2299,6 @@ void LLAgentCamera::changeCameraToMouselook(bool animate) updateLastCamera(); mCameraMode = CAMERA_MODE_MOUSELOOK; - const U32 old_flags = gAgent.getControlFlags(); gAgent.setControlFlags(AGENT_CONTROL_MOUSELOOK); if (animate) @@ -2471,6 +2553,7 @@ void LLAgentCamera::switchCameraPreset(ECameraPreset preset) resetPanDiff(); resetOrbitDiff(); + resetCameraRoll(); gSavedSettings.setU32("CameraPresetType", mCameraPreset); } @@ -2675,9 +2758,9 @@ void LLAgentCamera::setCameraPosAndFocusGlobal(const LLVector3d& camera_pos, con if (mCameraAnimating) { - const F64 ANIM_METERS_PER_SECOND = 10.0; + const F64 ANIM_METERS_PER_SECOND = 25.0; const F64 MIN_ANIM_SECONDS = 0.5; - const F64 MAX_ANIM_SECONDS = 10.0; + const F64 MAX_ANIM_SECONDS = 1.0; F64 anim_duration = llmax( MIN_ANIM_SECONDS, sqrt(focus_delta_squared) / ANIM_METERS_PER_SECOND ); anim_duration = llmin( anim_duration, MAX_ANIM_SECONDS ); setAnimationDuration( (F32)anim_duration ); @@ -2950,6 +3033,8 @@ void LLAgentCamera::clearOrbitKeys() mOrbitDownKey = 0.f; mOrbitInKey = 0.f; mOrbitOutKey = 0.f; + mRollLeftKey = 0.f; + mRollRightKey = 0.f; } void LLAgentCamera::clearPanKeys() -- cgit v1.3 From 147a9d3ef29f786b6949b2efa54de98a7704b360 Mon Sep 17 00:00:00 2001 From: leviathan Date: Wed, 9 Sep 2026 09:00:47 -0700 Subject: unify camera-mode checks --- indra/newview/llagent.cpp | 5 +++++ indra/newview/llagent.h | 5 +++++ indra/newview/llagentcamera.cpp | 10 +++++----- indra/newview/lltool.cpp | 2 +- indra/newview/llviewermessage.cpp | 2 +- indra/newview/llviewerwindow.cpp | 2 +- indra/newview/pipeline.cpp | 2 +- 7 files changed, 19 insertions(+), 9 deletions(-) (limited to 'indra/newview/llagentcamera.cpp') diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 63c2d72cc1..90079cdcb0 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -5329,6 +5329,11 @@ void LLAgent::toggleFlycam() } } +bool LLAgent::isCameraExternallyDriven() const +{ + return mUsingFlycam || LLViewerJoystick::getInstance()->getOverrideCamera(); +} + void LLAgent::toggleMouseCursorMode() { mUsingMouseCursor = !mUsingMouseCursor; diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index 282b01f576..a1e2676bf0 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -494,6 +494,11 @@ public: bool isControlGrabbed(S32 control_index) const; bool isUsingFlycam() const { return mUsingFlycam; } void toggleFlycam(); + // True while any flycam-like system (this one, or the legacy NDOF + // joystick's own) is driving LLViewerCamera directly, for code that + // needs to know the camera isn't under its usual control (e.g. tool + // selection, teleport look-at, camera-follow rendering). + bool isCameraExternallyDriven() const; bool isUsingMouseCursor() const { return mUsingMouseCursor; } void toggleMouseCursorMode(); // Send message to simulator to force grabbed controls to be diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index ef269be0be..26ca3073dc 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -2264,7 +2264,7 @@ void LLAgentCamera::resetCamera() void LLAgentCamera::changeCameraToMouselook(bool animate) { if (!gSavedSettings.getBOOL("EnableMouselook") - || LLViewerJoystick::getInstance()->getOverrideCamera()) + || gAgent.isCameraExternallyDriven()) { return; } @@ -2319,7 +2319,7 @@ void LLAgentCamera::changeCameraToMouselook(bool animate) //----------------------------------------------------------------------------- void LLAgentCamera::changeCameraToDefault() { - if (LLViewerJoystick::getInstance()->getOverrideCamera()) + if (gAgent.isCameraExternallyDriven()) { return; } @@ -2345,7 +2345,7 @@ void LLAgentCamera::changeCameraToDefault() //----------------------------------------------------------------------------- void LLAgentCamera::changeCameraToFollow(bool animate) { - if (LLViewerJoystick::getInstance()->getOverrideCamera()) + if (gAgent.isCameraExternallyDriven()) { return; } @@ -2401,7 +2401,7 @@ void LLAgentCamera::changeCameraToFollow(bool animate) //----------------------------------------------------------------------------- void LLAgentCamera::changeCameraToThirdPerson(bool animate) { - if (LLViewerJoystick::getInstance()->getOverrideCamera()) + if (gAgent.isCameraExternallyDriven()) { return; } @@ -2472,7 +2472,7 @@ void LLAgentCamera::changeCameraToThirdPerson(bool animate) //----------------------------------------------------------------------------- void LLAgentCamera::changeCameraToCustomizeAvatar() { - if (LLViewerJoystick::getInstance()->getOverrideCamera() || !isAgentAvatarValid()) + if (gAgent.isCameraExternallyDriven() || !isAgentAvatarValid()) { return; } diff --git a/indra/newview/lltool.cpp b/indra/newview/lltool.cpp index c595fc42ca..49c5bf27cd 100644 --- a/indra/newview/lltool.cpp +++ b/indra/newview/lltool.cpp @@ -193,7 +193,7 @@ bool LLTool::handleKey(KEY key, MASK mask) LLTool* LLTool::getOverrideTool(MASK mask) { // NOTE: if in flycam mode, ALT-ZOOM camera should be disabled - if (LLViewerJoystick::getInstance()->getOverrideCamera()) + if (gAgent.isCameraExternallyDriven()) { return NULL; } diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 468b9866eb..40236f49c7 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -6161,7 +6161,7 @@ void process_teleport_local(LLMessageSystem *msg,void**) gAgent.setPositionAgent(pos); gAgentCamera.slamLookAt(look_at); - if ( !(gAgent.getTeleportKeepsLookAt() && LLViewerJoystick::getInstance()->getOverrideCamera()) ) + if ( !(gAgent.getTeleportKeepsLookAt() && gAgent.isCameraExternallyDriven()) ) { gAgentCamera.resetView(true, true); } diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 4501819958..b157fefb01 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -4704,7 +4704,7 @@ LLViewerObject* LLViewerWindow::cursorIntersect(S32 mouse_x, S32 mouse_y, F32 de LLVector3 mouse_world_start = mouse_point_global; LLVector3 mouse_world_end = mouse_point_global + mouse_direction_global * depth; - if (!LLViewerJoystick::getInstance()->getOverrideCamera()) + if (!gAgent.isCameraExternallyDriven()) { //always set raycast intersection to mouse_world_end unless //flycam is on (for DoF effect) gDebugRaycastIntersection.load3(mouse_world_end.mV); diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index ab7058b5df..686638a054 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -7869,7 +7869,7 @@ void LLPipeline::renderDoF(LLRenderTarget* src, LLRenderTarget* dst) if (focus_point.isExactlyZero()) { - if (LLViewerJoystick::getInstance()->getOverrideCamera()) + if (gAgent.isCameraExternallyDriven()) { // focus on point under cursor focus_point.set(gDebugRaycastIntersection.getF32ptr()); } -- cgit v1.3 From 487f9f2a3696fbb940ef57051e1e10870833f87f Mon Sep 17 00:00:00 2001 From: leviathan Date: Wed, 9 Sep 2026 13:17:23 -0700 Subject: move flycam ownership onto LLAgentCamera --- indra/newview/llagent.cpp | 121 +++++++--------------------------- indra/newview/llagent.h | 18 ++--- indra/newview/llagentcamera.cpp | 131 +++++++++++++++++++++++++++++++++++++ indra/newview/llagentcamera.h | 49 ++++++++++++++ indra/newview/llappviewer.cpp | 9 +-- indra/newview/llflycam.h | 18 ----- indra/newview/llviewerjoystick.cpp | 66 +++++++------------ indra/newview/llviewerjoystick.h | 22 +++---- 8 files changed, 251 insertions(+), 183 deletions(-) (limited to 'indra/newview/llagentcamera.cpp') diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 90079cdcb0..02a04f4b88 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -84,7 +84,6 @@ #include "llurlentry.h" #include "llviewercontrol.h" #include "llviewerdisplay.h" -#include "llviewerjoystick.h" #include "llviewermediafocus.h" #include "llviewermenu.h" #include "llviewerobjectlist.h" @@ -5005,9 +5004,9 @@ void LLAgent::updateGameControlMode() // Auto-derive the active mode from avatar/camera state. Cursor mode takes // precedence over Flycam: toggling the mouse cursor on while Flycam is engaged // reports CONTROL_MODE_CURSOR (so the left stick drives the on-screen cursor) - // while mUsingFlycam stays true underneath -- LLAgent::updateFlycam() keeps + // while isUsingFlycam() stays true underneath -- LLAgent::updateFlycam() keeps // driving the camera every frame regardless of the reported mode (it is gated - // on mUsingFlycam directly, not on AgentControlMode), and + // on LLAgentCamera's own flycam state directly, not on AgentControlMode), and // LLGameControl::setFlycamEngaged() below tells the input-mapping lookup to // let Flycam's own axis/button config (Pan/Tilt/Boom/...) keep driving // whatever physical inputs Cursor mode's own mouse-cursor actions don't claim. @@ -5022,7 +5021,7 @@ void LLAgent::updateGameControlMode() { mode = LLGameControl::CONTROL_MODE_CURSOR; } - else if (mUsingFlycam) + else if (isUsingFlycam()) { mode = LLGameControl::CONTROL_MODE_FLYCAM; } @@ -5038,7 +5037,7 @@ void LLAgent::updateGameControlMode() { mode = LLGameControl::CONTROL_MODE_AVATAR; } - LLGameControl::setFlycamEngaged(mUsingFlycam); + LLGameControl::setFlycamEngaged(isUsingFlycam()); LLGameControl::setAgentControlMode(mode); } @@ -5185,7 +5184,7 @@ void LLAgent::applyExternalActions(const LLGameControl::AgentActions& actions) g_deltaFrame = frame_count - g_lastUpdateFrame; g_lastUpdateFrame = frame_count; - if (mUsingFlycam) + if (isUsingFlycam()) { // Flycam will be updated later by exernal context return; @@ -5316,22 +5315,31 @@ void LLAgent::applyExternalActions(const LLGameControl::AgentActions& actions) } } +bool LLAgent::isUsingFlycam() const +{ + return gAgentCamera.isUsingFlycam(); +} + void LLAgent::toggleFlycam() { - mUsingFlycam = !mUsingFlycam; - if (mUsingFlycam) - { - // copy main camera transform to flycam - LLViewerCamera* camera = LLViewerCamera::getInstance(); - mFlycam.setTransform(getPosGlobalFromAgent(camera->getOrigin()), camera->getQuaternion()); - mFlycam.setView(camera->getView()); - mLastFlycamUpdate = LLFrameTimer::getTotalTime(); - } + gAgentCamera.toggleFlycam(); +} + +void LLAgent::setFlycamKeyInput(U8 channel, F32 value) +{ + gAgentCamera.setFlycamKeyInput(channel, value); +} + +void LLAgent::setFlycamKeyReset(bool reset) +{ + gAgentCamera.setFlycamKeyReset(reset); } bool LLAgent::isCameraExternallyDriven() const { - return mUsingFlycam || LLViewerJoystick::getInstance()->getOverrideCamera(); + // Both flycam input sources now share one toggle state on LLAgentCamera -- + // LLViewerJoystick::getOverrideCamera() forwards to the same flag. + return gAgentCamera.isUsingFlycam(); } void LLAgent::toggleMouseCursorMode() @@ -5352,86 +5360,7 @@ void LLAgent::releaseGameControlButton(U8 button_index) void LLAgent::updateFlycam() { - // Note: flycam_inputs arrive in range [-1,1] - std::vector flycam_inputs; - U32 flycam_misc_actions = 0; - LLGameControl::getFlycamInputs(flycam_inputs, flycam_misc_actions); - - // The channel order is defined by LLGameControl::FlycamChannel. - - // Defensive: getFlycamInputs() should return one value per channel. - if ((S32)flycam_inputs.size() < LLGameControl::FLYCAM_NUM_CHANNELS) - { - return; - } - - // Blend in keyboard-driven flycam input (independent of LLGameControl, - // which only sees a physical controller). mFlycamKeyInput is - // level-triggered by flycam_axis_key<> in llviewerinput.cpp -- it must be - // re-set every frame a key is held, so clear it here once consumed. - for (U8 i = 0; i < LLGameControl::FLYCAM_NUM_CHANNELS; ++i) - { - flycam_inputs[i] = llclamp(flycam_inputs[i] + mFlycamKeyInput[i], -1.f, 1.f); - } - mFlycamKeyInput.fill(0.f); - bool flycam_key_reset_requested = mFlycamKeyResetRequested; - mFlycamKeyResetRequested = false; - - if (((flycam_misc_actions & LLGameControl::FLYCAM_ACTION_RESET) || flycam_key_reset_requested) && isAgentAvatarValid()) - { - // Reorient the flycam as if it were the 3rd-person camera: above and - // behind the avatar, looking down. mFlycam.startReset() smoothly - // lerps into this transform (see LLFlycam::integrate()); flycam - // input has no effect until the lerp completes. - constexpr F32 FLYCAM_RESET_DURATION = 1.0f; // seconds; may be tuned later - constexpr F32 FLYCAM_RESET_FOCUS_HEIGHT = 1.0f; // meters above avatar root - - LLQuaternion avatar_rot = gAgentAvatarp->isSitting() - ? gAgentAvatarp->getRenderRotation() - : mFrameAgent.getQuaternion(); - LLVector3 avatar_pos = getPositionAgent(); - - // Reuse the same behind/above offset the real 3rd-person camera uses, - // rotated into the avatar's current facing. - F32 camera_offset_scale = gSavedSettings.getF32("CameraOffsetScale"); - LLVector3 local_offset = gAgentCamera.getCameraOffsetInitial() * camera_offset_scale; - LLVector3 target_position = avatar_pos + local_offset * avatar_rot; - - // Look toward the avatar (roughly torso height); since the camera - // sits above and behind, this naturally tilts the view down and - // toward the avatar's facing direction. - LLVector3 focus_point = avatar_pos + LLVector3(0.f, 0.f, FLYCAM_RESET_FOCUS_HEIGHT); - LLCoordFrame target_frame; - target_frame.lookAt(target_position, focus_point, LLVector3::z_axis); - - mFlycam.startReset(getPosGlobalFromAgent(target_position), target_frame.getQuaternion(), FLYCAM_RESET_DURATION); - } - - LLVector3 linear_velocity( - flycam_inputs[LLGameControl::FLYCAM_DOLLY], - flycam_inputs[LLGameControl::FLYCAM_TRUCK], - flycam_inputs[LLGameControl::FLYCAM_BOOM]); - constexpr F32 MAX_FLYCAM_SPEED = 10.0f; - mFlycam.setLinearVelocity(MAX_FLYCAM_SPEED * linear_velocity); - - mFlycam.setPitchRate(flycam_inputs[LLGameControl::FLYCAM_TILT]); - mFlycam.setYawRate(flycam_inputs[LLGameControl::FLYCAM_PAN]); - mFlycam.setRollRate(flycam_inputs[LLGameControl::FLYCAM_ROLL]); - mFlycam.setZoomRate(flycam_inputs[LLGameControl::FLYCAM_ZOOM]); - - mFlycam.integrate(g_deltaTime); - - LLVector3d pos_global; - LLQuaternion rot; - mFlycam.getTransform(pos_global, rot); - LLVector3 pos = getPosAgentFromGlobal(pos_global); - LLMatrix3 mat(rot); - LLViewerCamera::getInstance()->setOrigin(pos); - LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]); - LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]); - LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]); - - LLViewerCamera::getInstance()->setView(mFlycam.getView()); + gAgentCamera.updateFlycam(g_deltaTime); } /********************************************************************************/ diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index a1e2676bf0..3aeaba20e3 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -33,7 +33,6 @@ #include "llcharacter.h" #include "llcoordframe.h" // for mFrameAgent #include "llavatarappearancedefines.h" -#include "llflycam.h" #include "llgamecontrol.h" #include "llkeyboard.h" #include "llpermissionsflags.h" @@ -44,7 +43,6 @@ #include -#include #include extern const bool ANIMATE; @@ -492,7 +490,10 @@ public: void resetControlFlags(); bool anyControlGrabbed() const; // True iff a script has taken over a control bool isControlGrabbed(S32 control_index) const; - bool isUsingFlycam() const { return mUsingFlycam; } + // Flycam's state now lives on LLAgentCamera (shared with LLViewerJoystick, + // which also drives it); these forward there so existing callers don't + // need to change. + bool isUsingFlycam() const; void toggleFlycam(); // True while any flycam-like system (this one, or the legacy NDOF // joystick's own) is driving LLViewerCamera directly, for code that @@ -539,26 +540,21 @@ public: void releaseGameControlButton(U8 button); U32 getGameControlButtonsFromKeys() const { return mGameControlButtonsFromKeys; } - void setFlycamKeyInput(U8 channel, F32 value) { mFlycamKeyInput[channel] = value; } - void setFlycamKeyReset(bool reset) { mFlycamKeyResetRequested = reset; } + void setFlycamKeyInput(U8 channel, F32 value); + void setFlycamKeyReset(bool reset); private: - U64 mLastFlycamUpdate { 0 }; U32 mExternalActionFlags { 0 }; U32 mGameControlButtonsFromKeys { 0 }; // Previous frame's LLGameControl::AgentActions::mMouseButtonBits, used by // applyExternalActions() to find the press/release edges of the // level-triggered simulated mouse buttons. U32 mPrevMouseButtonBits { 0 }; - LLFlycam mFlycam; bool mToggleRun { true }; - bool mUsingFlycam { false }; - std::array mFlycamKeyInput {}; - bool mFlycamKeyResetRequested { false }; // CONTROL_MODE_CURSOR: toggled by AVATAR_ACTION_TOGGLE_MOUSE_CURSOR, same pattern - // as mUsingFlycam/toggleFlycam(). mLastMouseCursorUpdate is the previous frame's + // as LLAgentCamera's flycam toggle. mLastMouseCursorUpdate is the previous frame's // timestamp used to compute the cursor's per-frame movement delta; reset to 0 // whenever the mode isn't active so re-entering doesn't jump the cursor. bool mUsingMouseCursor { false }; diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 26ca3073dc..922f5f0662 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -2971,6 +2971,137 @@ void LLAgentCamera::notifyFollowCamParamsCleared() mLastValidFollowCamParamsTime = 0.0; } +//----------------------------------------------------------------------------- +// resetFlycamToCurrentView() +//----------------------------------------------------------------------------- +void LLAgentCamera::resetFlycamToCurrentView() +{ + LLViewerCamera* camera = LLViewerCamera::getInstance(); + mFlycam.setTransform(gAgent.getPosGlobalFromAgent(camera->getOrigin()), camera->getQuaternion()); + mFlycam.setView(camera->getView()); +} + +//----------------------------------------------------------------------------- +// toggleFlycam() +//----------------------------------------------------------------------------- +void LLAgentCamera::toggleFlycam() +{ + mUsingFlycam = !mUsingFlycam; + if (mUsingFlycam) + { + resetFlycamToCurrentView(); + } +} + +//----------------------------------------------------------------------------- +// applyNdofFlycamFrameDelta() +//----------------------------------------------------------------------------- +void LLAgentCamera::applyNdofFlycamFrameDelta(const F32 local_delta[7], + bool auto_level, F32 auto_level_fraction, + bool direct_view, F32 direct_view_value) +{ + mFlycam.applyFrameDelta(local_delta, auto_level, auto_level_fraction, direct_view, direct_view_value); + + LLVector3d pos_global; + LLQuaternion rot; + mFlycam.getTransform(pos_global, rot); + LLVector3 pos = gAgent.getPosAgentFromGlobal(pos_global); + LLMatrix3 mat(rot); + LLViewerCamera::getInstance()->setOrigin(pos); + LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]); + LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]); + LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]); + + LLViewerCamera::getInstance()->setView(mFlycam.getView()); +} + +//----------------------------------------------------------------------------- +// updateFlycam() +//----------------------------------------------------------------------------- +void LLAgentCamera::updateFlycam(F32 delta_time) +{ + // Note: flycam_inputs arrive in range [-1,1] + std::vector flycam_inputs; + U32 flycam_misc_actions = 0; + LLGameControl::getFlycamInputs(flycam_inputs, flycam_misc_actions); + + // The channel order is defined by LLGameControl::FlycamChannel. + + // Defensive: getFlycamInputs() should return one value per channel. + if ((S32)flycam_inputs.size() < LLGameControl::FLYCAM_NUM_CHANNELS) + { + return; + } + + // Blend in keyboard-driven flycam input (independent of LLGameControl, + // which only sees a physical controller). mFlycamKeyboardInput is + // level-triggered by flycam_axis_key<> in llviewerinput.cpp -- it must be + // re-set every frame a key is held, so clear it here once consumed. + for (U8 i = 0; i < LLGameControl::FLYCAM_NUM_CHANNELS; ++i) + { + flycam_inputs[i] = llclamp(flycam_inputs[i] + mFlycamKeyboardInput[i], -1.f, 1.f); + } + mFlycamKeyboardInput.fill(0.f); + bool flycam_key_reset_requested = mFlycamKeyboardResetRequested; + mFlycamKeyboardResetRequested = false; + + if (((flycam_misc_actions & LLGameControl::FLYCAM_ACTION_RESET) || flycam_key_reset_requested) && isAgentAvatarValid()) + { + // Reorient the flycam as if it were the 3rd-person camera: above and + // behind the avatar, looking down. mFlycam.startReset() smoothly + // lerps into this transform (see LLFlycam::integrate()); flycam + // input has no effect until the lerp completes. + constexpr F32 FLYCAM_RESET_DURATION = 1.0f; // seconds; may be tuned later + constexpr F32 FLYCAM_RESET_FOCUS_HEIGHT = 1.0f; // meters above avatar root + + LLQuaternion avatar_rot = gAgentAvatarp->isSitting() + ? gAgentAvatarp->getRenderRotation() + : gAgent.getFrameAgent().getQuaternion(); + LLVector3 avatar_pos = gAgent.getPositionAgent(); + + // Reuse the same behind/above offset the real 3rd-person camera uses, + // rotated into the avatar's current facing. + F32 camera_offset_scale = gSavedSettings.getF32("CameraOffsetScale"); + LLVector3 local_offset = getCameraOffsetInitial() * camera_offset_scale; + LLVector3 target_position = avatar_pos + local_offset * avatar_rot; + + // Look toward the avatar (roughly torso height); since the camera + // sits above and behind, this naturally tilts the view down and + // toward the avatar's facing direction. + LLVector3 focus_point = avatar_pos + LLVector3(0.f, 0.f, FLYCAM_RESET_FOCUS_HEIGHT); + LLCoordFrame target_frame; + target_frame.lookAt(target_position, focus_point, LLVector3::z_axis); + + mFlycam.startReset(gAgent.getPosGlobalFromAgent(target_position), target_frame.getQuaternion(), FLYCAM_RESET_DURATION); + } + + LLVector3 linear_velocity( + flycam_inputs[LLGameControl::FLYCAM_DOLLY], + flycam_inputs[LLGameControl::FLYCAM_TRUCK], + flycam_inputs[LLGameControl::FLYCAM_BOOM]); + constexpr F32 MAX_FLYCAM_SPEED = 10.0f; + mFlycam.setLinearVelocity(MAX_FLYCAM_SPEED * linear_velocity); + + mFlycam.setPitchRate(flycam_inputs[LLGameControl::FLYCAM_TILT]); + mFlycam.setYawRate(flycam_inputs[LLGameControl::FLYCAM_PAN]); + mFlycam.setRollRate(flycam_inputs[LLGameControl::FLYCAM_ROLL]); + mFlycam.setZoomRate(flycam_inputs[LLGameControl::FLYCAM_ZOOM]); + + mFlycam.integrate(delta_time); + + LLVector3d pos_global; + LLQuaternion rot; + mFlycam.getTransform(pos_global, rot); + LLVector3 pos = gAgent.getPosAgentFromGlobal(pos_global); + LLMatrix3 mat(rot); + LLViewerCamera::getInstance()->setOrigin(pos); + LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]); + LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]); + LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]); + + LLViewerCamera::getInstance()->setView(mFlycam.getView()); +} + bool LLAgentCamera::setPointAt(EPointAtType target_type, LLViewerObject *object, LLVector3 position) { // disallow pointing at attachments and avatars diff --git a/indra/newview/llagentcamera.h b/indra/newview/llagentcamera.h index ca82667426..57c3041a53 100644 --- a/indra/newview/llagentcamera.h +++ b/indra/newview/llagentcamera.h @@ -27,7 +27,11 @@ #ifndef LL_LLAGENTCAMERA_H #define LL_LLAGENTCAMERA_H +#include + +#include "llflycam.h" #include "llfollowcam.h" // Ventrella +#include "llgamecontrol.h" // LLGameControl::FLYCAM_NUM_CHANNELS #include "llhudeffectlookat.h" // EPointAtType #include "llhudeffectpointat.h" // ELookAtType @@ -175,6 +179,51 @@ public: private: LLFollowCam mFollowCam; // Ventrella + //-------------------------------------------------------------------- + // Flycam + //-------------------------------------------------------------------- + // Free-flying camera engine (mFlycam + toggle state below), shared by two + // independent input sources that each feed it differently: + // - game-control/keyboard: updateFlycam() + mFlycamKeyboard* state here. + // - legacy NDOF joystick: applyNdofFlycamFrameDelta() below, called + // from LLViewerJoystick, whose OWN per-axis tuning state + // (mJoystickFlycamDelta/mJoystickFlycamLastDelta) lives over there, + // not on this class. + // Orthogonal to mCameraMode above: while active it preempts + // updateCamera() entirely (driven by updateFlycam()/ + // applyNdofFlycamFrameDelta() instead, called directly from the main + // loop), rather than being a peer state in that mode's own + // animated-transition machinery. +public: + bool isUsingFlycam() const { return mUsingFlycam; } + void toggleFlycam(); + // Game-control/keyboard-driven update -- see LLViewerJoystick::moveFlycam() + // for the other (legacy NDOF joystick) input source, below. + void updateFlycam(F32 delta_time); + void setFlycamKeyInput(U8 channel, F32 value) { mFlycamKeyboardInput[channel] = value; } + void setFlycamKeyReset(bool reset) { mFlycamKeyboardResetRequested = reset; } + // Snapshots the live LLViewerCamera transform into the flycam. Used both + // when entering flycam (see toggleFlycam()) and whenever an input source + // (e.g. the NDOF joystick regaining focus) needs to recalibrate its own + // axis-delta state against the camera's current position/orientation. + void resetFlycamToCurrentView(); + // Entry point for input sources (the legacy NDOF joystick) that compute + // their own per-axis dead-zone/scale/feathering deltas and only need this + // class to own the resulting transform update + push to LLViewerCamera -- + // see LLFlycam::applyFrameDelta() for the delta format. + void applyNdofFlycamFrameDelta(const F32 local_delta[7], + bool auto_level, F32 auto_level_fraction, + bool direct_view, F32 direct_view_value); +private: + LLFlycam mFlycam; + bool mUsingFlycam { false }; + // Keyboard's contribution to the shared/game-control flycam channels + // (blended with LLGameControl::getFlycamInputs() each frame in + // updateFlycam()) -- unrelated to the legacy joystick's own tuning state, + // which lives on LLViewerJoystick (mJoystickFlycamDelta/LastDelta). + std::array mFlycamKeyboardInput {}; + bool mFlycamKeyboardResetRequested { false }; + //-------------------------------------------------------------------- // Sit //-------------------------------------------------------------------- diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index cecc9fcb4b..6f33e71d9a 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -5741,12 +5741,13 @@ void LLAppViewer::idle() { gAgentPilot.moveCamera(); } - else if (LLViewerJoystick::getInstance()->getOverrideCamera()) - { - LLViewerJoystick::getInstance()->moveFlycam(); - } else if (gAgent.isUsingFlycam()) { + // Flycam is now a single shared state (LLAgentCamera) driven by + // whichever input sources are actually present; each is a no-op + // when its device/mode isn't active, so it's safe to feed both + // every frame rather than picking one exclusively. + LLViewerJoystick::getInstance()->moveFlycam(); gAgent.updateFlycam(); } else diff --git a/indra/newview/llflycam.h b/indra/newview/llflycam.h index 2e5f168ed2..37bef8acdc 100644 --- a/indra/newview/llflycam.h +++ b/indra/newview/llflycam.h @@ -31,28 +31,12 @@ #include "v3math.h" #include "v3dmath.h" -// Modes the unified camera controller can be in. Only FREE_FLY is wired up -// today (legacy joystick + game-control flycam); the rest are populated as -// LLAgentCamera's other camera behaviors are migrated in. -enum class CameraControllerMode -{ - THIRD_PERSON, // avatar-anchored orbit; camera follows avatar body rotation - BUILD_FOCUS, // free orbit around a focus point/object - APPEARANCE_EDITOR, // orbit around avatar head - FOLLOW_CAM, // LSL-driven Ventrella follow cam - MOUSELOOK, // mouse-driven avatar/head look - FREE_FLY, // legacy joystick + game-control flycam -}; - class LLFlycam { public: LLFlycam() = default; - CameraControllerMode getMode() const { return mMode; } - void setMode(CameraControllerMode mode) { mMode = mode; } - // Note: position is in global (region-independent) coordinates, since it // must remain valid across the region-origin rebases that happen when the // agent crosses a region boundary while this transform is in use. @@ -93,8 +77,6 @@ public: bool direct_view, F32 direct_view_value); protected: - CameraControllerMode mMode { CameraControllerMode::FREE_FLY }; - LLVector3d mPosition; LLVector3 mLinearVelocity; LLQuaternion mRotation; diff --git a/indra/newview/llviewerjoystick.cpp b/indra/newview/llviewerjoystick.cpp index cd780a94f9..d3b4f0e2c2 100644 --- a/indra/newview/llviewerjoystick.cpp +++ b/indra/newview/llviewerjoystick.cpp @@ -256,25 +256,16 @@ void LLViewerJoystick::updateEnabled(bool autoenable) } if (!gSavedSettings.getBOOL("JoystickEnabled")) { - mOverrideCamera = false; + if (gAgentCamera.isUsingFlycam()) + { + gAgentCamera.toggleFlycam(); + } } } -void LLViewerJoystick::setOverrideCamera(bool val) +bool LLViewerJoystick::getOverrideCamera() { - if (!gSavedSettings.getBOOL("JoystickEnabled")) - { - mOverrideCamera = false; - } - else - { - mOverrideCamera = val; - } - - if (mOverrideCamera) - { - gAgentCamera.changeCameraToDefault(); - } + return gAgentCamera.isUsingFlycam(); } // ----------------------------------------------------------------------------- @@ -1178,15 +1169,14 @@ void LLViewerJoystick::moveFlycam(bool reset) bool in_build_mode = LLToolMgr::getInstance()->inBuildMode(); if (reset || mResetFlag) { - mFlycam.setTransform(gAgentCamera.getCameraPositionGlobal(), LLViewerCamera::getInstance()->getQuaternion()); - mFlycam.setView(LLViewerCamera::getInstance()->getView()); + gAgentCamera.resetFlycamToCurrentView(); for (U32 i = 0; i < 6; i++) { - mFlycamLastDelta[i] = -getJoystickAxis(axis[i]); - mFlycamDelta[i] = 0.f; + mJoystickFlycamLastDelta[i] = -getJoystickAxis(axis[i]); + mJoystickFlycamDelta[i] = 0.f; } - mFlycamLastDelta[6] = mFlycamDelta[6] = 0.f; + mJoystickFlycamLastDelta[6] = mJoystickFlycamDelta[6] = 0.f; mResetFlag = false; return; @@ -1235,9 +1225,9 @@ void LLViewerJoystick::moveFlycam(bool reset) F32 tmp = cur_delta[i]; if (absolute) { - cur_delta[i] = cur_delta[i] - mFlycamLastDelta[i]; + cur_delta[i] = cur_delta[i] - mJoystickFlycamLastDelta[i]; } - mFlycamLastDelta[i] = tmp; + mJoystickFlycamLastDelta[i] = tmp; if (cur_delta[i] > 0) { @@ -1267,7 +1257,7 @@ void LLViewerJoystick::moveFlycam(bool reset) cur_delta[i] *= time; } - mFlycamDelta[i] = mFlycamDelta[i] + (cur_delta[i]-mFlycamDelta[i])*time*feather; + mJoystickFlycamDelta[i] = mJoystickFlycamDelta[i] + (cur_delta[i]-mJoystickFlycamDelta[i])*time*feather; is_zero = is_zero && (cur_delta[i] == 0.f); @@ -1289,20 +1279,9 @@ void LLViewerJoystick::moveFlycam(bool reset) bool auto_level = gSavedSettings.getBOOL("AutoLeveling"); F32 auto_level_fraction = llmin(feather*time, 1.f); bool zoom_direct = gSavedSettings.getBOOL("ZoomDirect"); - F32 direct_view_value = mFlycamLastDelta[6]*axis_scale[6]+dead_zone[6]; + F32 direct_view_value = mJoystickFlycamLastDelta[6]*axis_scale[6]+dead_zone[6]; - mFlycam.applyFrameDelta(mFlycamDelta, auto_level, auto_level_fraction, zoom_direct, direct_view_value); - - LLVector3d pos_global; - LLQuaternion rot; - mFlycam.getTransform(pos_global, rot); - LLMatrix3 mat(rot); - - LLViewerCamera::getInstance()->setView(mFlycam.getView()); - LLViewerCamera::getInstance()->setOrigin(gAgent.getPosAgentFromGlobal(pos_global)); - LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]); - LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]); - LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]); + gAgentCamera.applyNdofFlycamFrameDelta(mJoystickFlycamDelta, auto_level, auto_level_fraction, zoom_direct, direct_view_value); } // ----------------------------------------------------------------------------- @@ -1310,11 +1289,14 @@ bool LLViewerJoystick::toggleFlycam() { if (!gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickFlycamEnabled")) { - mOverrideCamera = false; + if (gAgentCamera.isUsingFlycam()) + { + gAgentCamera.toggleFlycam(); + } return false; } - if (!mOverrideCamera) + if (!gAgentCamera.isUsingFlycam()) { gAgentCamera.changeCameraToDefault(); } @@ -1328,8 +1310,8 @@ bool LLViewerJoystick::toggleFlycam() gAwayTriggerTimer.reset(); } - mOverrideCamera = !mOverrideCamera; - if (mOverrideCamera) + gAgentCamera.toggleFlycam(); + if (gAgentCamera.isUsingFlycam()) { moveFlycam(true); @@ -1356,7 +1338,7 @@ void LLViewerJoystick::scanJoystick() #if LL_WINDOWS // On windows, the flycam is updated syncronously with a timer, so there is // no need to update the status of the joystick here. - if (!mOverrideCamera) + if (!getOverrideCamera()) #endif updateStatus(); @@ -1381,7 +1363,7 @@ void LLViewerJoystick::scanJoystick() toggle_flycam = 0; } - if (!mOverrideCamera && !(LLToolMgr::getInstance()->inBuildMode() && gSavedSettings.getBOOL("JoystickBuildEnabled"))) + if (!getOverrideCamera() && !(LLToolMgr::getInstance()->inBuildMode() && gSavedSettings.getBOOL("JoystickBuildEnabled"))) { moveAvatar(); } diff --git a/indra/newview/llviewerjoystick.h b/indra/newview/llviewerjoystick.h index b71faf34c3..b22ecd6441 100644 --- a/indra/newview/llviewerjoystick.h +++ b/indra/newview/llviewerjoystick.h @@ -28,7 +28,6 @@ #define LL_LLVIEWERJOYSTICK_H #include "stdtypes.h" -#include "llflycam.h" #if LIB_NDOF #if LL_DARWIN @@ -72,8 +71,9 @@ public: void setNeedsReset(bool reset = true) { mResetFlag = reset; } void setCameraNeedsUpdate(bool b) { mCameraUpdated = b; } bool getCameraNeedsUpdate() const { return mCameraUpdated; } - bool getOverrideCamera() { return mOverrideCamera; } - void setOverrideCamera(bool val); + // Flycam's state now lives on LLAgentCamera (shared with the game-control + // flycam); this just forwards. + bool getOverrideCamera(); bool toggleFlycam(); void setSNDefaults(); bool isDeviceUUIDSet(); @@ -115,20 +115,18 @@ private: U32 mJoystickRun { 0 }; bool mResetFlag { false }; bool mCameraUpdated { true }; - bool mOverrideCamera { false }; bool mDeviceIs3DConnexion { false }; static F32 sLastDelta[7]; static F32 sDelta[7]; - // Flycam's transform now lives on LLFlycam (shared with the game-control - // flycam's engine); mFlycamDelta/mFlycamLastDelta are this device's own - // feathered-delta state feeding LLFlycam::applyFrameDelta(), kept - // separate from sDelta/sLastDelta above (which moveAvatar()/moveObjects() - // still use for non-flycam axis handling). - LLFlycam mFlycam; - F32 mFlycamDelta[7] { 0,0,0,0,0,0,0 }; - F32 mFlycamLastDelta[7] { 0,0,0,0,0,0,0 }; + // This device's own feathered-delta state feeding + // LLAgentCamera::applyNdofFlycamFrameDelta() (which owns the shared flycam + // transform/engine, also used by the game-control flycam), kept separate + // from sDelta/sLastDelta above (which moveAvatar()/moveObjects() still + // use for non-flycam axis handling). + F32 mJoystickFlycamDelta[7] { 0,0,0,0,0,0,0 }; + F32 mJoystickFlycamLastDelta[7] { 0,0,0,0,0,0,0 }; }; #endif -- cgit v1.3 From e6db5b45e259d086fb0f6c554ef8921fab26ed50 Mon Sep 17 00:00:00 2001 From: leviathan Date: Wed, 9 Sep 2026 21:25:02 -0700 Subject: add orbit modifier for flycam --- indra/llwindow/llgamecontrol.cpp | 55 +++++--- indra/llwindow/llgamecontrol.h | 20 ++- indra/newview/app_settings/key_bindings.xml | 4 +- indra/newview/llagent.cpp | 4 +- indra/newview/llagent.h | 2 +- indra/newview/llagentcamera.cpp | 141 +++++++++++++++------ indra/newview/llagentcamera.h | 7 +- indra/newview/llflycam.cpp | 58 ++++++++- indra/newview/llflycam.h | 19 +++ indra/newview/llviewerinput.cpp | 6 +- .../skins/default/xui/en/floater_joystick.xml | 1 + .../xui/en/panel_preferences_game_control.xml | 7 +- 12 files changed, 249 insertions(+), 75 deletions(-) (limited to 'indra/newview/llagentcamera.cpp') diff --git a/indra/llwindow/llgamecontrol.cpp b/indra/llwindow/llgamecontrol.cpp index 70d8f2825b..25ad41e0cd 100755 --- a/indra/llwindow/llgamecontrol.cpp +++ b/indra/llwindow/llgamecontrol.cpp @@ -422,7 +422,7 @@ public: LLGameControl::ActionNameType getActionNameType(const std::string& action) const; LLGameControl::AgentActions computeAgentActions(); - void getFlycamInputs(std::vector& inputs_out, U32& misc_actions_out); + void getFlycamInputs(std::vector& inputs_out, U32& misc_actions_out, U32& modifiers_out); void setExternalInput(U32 action_flags, U32 buttons, bool is_running); void setMouseCursorPosition(S32 pixel_x, S32 pixel_y, S32 rect_width, S32 rect_height); @@ -789,18 +789,13 @@ namespace LLSD flycam_buttons; flycam_buttons["Zoom out"] = "BUTTON_SOUTH"; - flycam_buttons["Pan right"] = "BUTTON_EAST"; - flycam_buttons["Pan left"] = "BUTTON_WEST"; flycam_buttons["Zoom in"] = "BUTTON_NORTH"; flycam_buttons["Toggle mouse cursor"] = "BUTTON_SELECT"; flycam_buttons["Toggle flycam" ] = "BUTTON_RIGHT_STICK"; - flycam_buttons["Reset"] = "BUTTON_LEFT_STICK"; - flycam_buttons["Roll CCW"] = "BUTTON_LEFT_SHOULDER"; - flycam_buttons["Roll CW"] = "BUTTON_RIGHT_SHOULDER"; - flycam_buttons["Dolly forward"] = "BUTTON_DPAD_UP"; - flycam_buttons["Dolly back"] = "BUTTON_DPAD_DOWN"; - flycam_buttons["Truck left"] = "BUTTON_DPAD_LEFT"; - flycam_buttons["Truck right"] = "BUTTON_DPAD_RIGHT"; + flycam_buttons["Unroll"] = "BUTTON_LEFT_STICK"; + flycam_buttons["Roll CCW"] = "BUTTON_WEST"; + flycam_buttons["Roll CW"] = "BUTTON_EAST"; + flycam_buttons["Orbit"] = "BUTTON_RIGHT_SHOULDER"; LLSD flycam_axes_invert; flycam_axes_invert["Truck left/right"] = true; @@ -2262,11 +2257,11 @@ LLGameControl::AgentActions LLGameControllerManager::computeAgentActions() } // Analog "is running" hysteresis: engage once a movement axis is pushed past - // 60% of full deflection, release once every movement axis drops back below - // 40%. The dead zone between the two thresholds avoids flicker right at the + // 90% of full deflection, release once every movement axis drops back below + // 70%. The dead zone between the two thresholds avoids flicker right at the // boundary. - constexpr F32 RUN_ENGAGE_FRACTION = 0.6f; - constexpr F32 RUN_RELEASE_FRACTION = 0.4f; + constexpr F32 RUN_ENGAGE_FRACTION = 0.9f; + constexpr F32 RUN_RELEASE_FRACTION = 0.7f; F32 movement_fraction = (F32)movement_magnitude / 32767.f; if (movement_fraction > RUN_ENGAGE_FRACTION) { @@ -2447,7 +2442,19 @@ namespace const std::map& flycamMiscButtonBridge() { static const std::map bridge = { - { "Reset", LLGameControl::FLYCAM_ACTION_RESET }, + { "Unroll", LLGameControl::FLYCAM_ACTION_UNROLL }, + }; + return bridge; + } + + // FlyCam-mode button label -> level-triggered FlycamModifier bit. Unlike + // flycamMiscButtonBridge()'s one-shot commands, these are asserted every + // frame the bound button is held down, mirroring the plain (non-edge) + // button-held check flycamButtonBridge() itself uses. + const std::map& flycamModifierButtonBridge() + { + static const std::map bridge = { + { "Orbit", LLGameControl::FLYCAM_MODIFIER_ORBIT }, }; return bridge; } @@ -2772,9 +2779,10 @@ void LLGameControllerManager::computeSemanticState(LLGameControl::ServerState& s } } -void LLGameControllerManager::getFlycamInputs(std::vector& inputs, U32& misc_actions) +void LLGameControllerManager::getFlycamInputs(std::vector& inputs, U32& misc_actions, U32& modifiers) { misc_actions = 0; + modifiers = 0; // When FlyCam-mode conversion is disabled, produce no motion. This path // (LLAgent::updateFlycam) is not gated by willControlFlycam(), so gate it @@ -2829,9 +2837,12 @@ void LLGameControllerManager::getFlycamInputs(std::vector& inputs, U32& mis // deflection to its channel, every frame it's held (flycamButtonBridge()). // One-shot commands (flycamMiscButtonBridge()) fire only on the // not-pressed -> pressed edge instead, mirroring how computeAgentActions() - // splits avatarButtonBridge()/avatarMiscButtonBridge() above. + // splits avatarButtonBridge()/avatarMiscButtonBridge() above. Modifiers + // (flycamModifierButtonBridge()) are level-triggered like the channel + // buttons, just reported in their own bitmask instead of a DOF channel. const auto& button_bridge = flycamButtonBridge(); const auto& misc_button_bridge = flycamMiscButtonBridge(); + const auto& modifier_button_bridge = flycamModifierButtonBridge(); U32 pressed_edges = g_innerState.mButtons & ~g_innerState.mPrevButtons; for (U8 btn = 0; btn < LLGameControl::NUM_BUTTONS; ++btn) { @@ -2850,6 +2861,12 @@ void LLGameControllerManager::getFlycamInputs(std::vector& inputs, U32& mis dof[it->second.channel] += it->second.polarity; continue; } + auto mod_it = modifier_button_bridge.find(label); + if (mod_it != modifier_button_bridge.end()) + { + modifiers |= mod_it->second; + continue; + } auto mit = misc_button_bridge.find(label); if (mit != misc_button_bridge.end() && (pressed_edges & (1U << btn))) { @@ -3474,9 +3491,9 @@ LLGameControl::InputChannel LLGameControl::getActiveInputChannel() } // static -void LLGameControl::getFlycamInputs(std::vector& inputs_out, U32& misc_actions_out) +void LLGameControl::getFlycamInputs(std::vector& inputs_out, U32& misc_actions_out, U32& modifiers_out) { - return g_manager.getFlycamInputs(inputs_out, misc_actions_out); + return g_manager.getFlycamInputs(inputs_out, misc_actions_out, modifiers_out); } // static diff --git a/indra/llwindow/llgamecontrol.h b/indra/llwindow/llgamecontrol.h index 19cf427d4a..f0b439aaef 100644 --- a/indra/llwindow/llgamecontrol.h +++ b/indra/llwindow/llgamecontrol.h @@ -484,12 +484,15 @@ public: static InputChannel getActiveInputChannel(); // Packs the FlyCam-mode DOF channels (order per FlycamChannel) into - // 'inputs_out'. FlycamMiscAction commands (e.g. FLYCAM_ACTION_RESET) are + // 'inputs_out'. FlycamMiscAction commands (e.g. FLYCAM_ACTION_UNROLL) are // discrete one-shot commands rather than per-frame DOF contributions, so // they are reported separately via 'misc_actions_out': the bitmask of // FlycamMiscAction values whose bound button transitioned from - // not-pressed to pressed this frame. - static void getFlycamInputs(std::vector& inputs_out, U32& misc_actions_out); + // not-pressed to pressed this frame. FlycamModifier values (e.g. + // FLYCAM_MODIFIER_ORBIT) are level-triggered instead: 'modifiers_out' is + // the bitmask of FlycamModifier values whose bound button is held down + // this frame. + static void getFlycamInputs(std::vector& inputs_out, U32& misc_actions_out, U32& modifiers_out); // these methods for accepting input from keyboard static void setSendToServer(bool enable); @@ -550,7 +553,16 @@ public: enum FlycamMiscAction : U32 { - FLYCAM_ACTION_RESET = 1u << 0, + FLYCAM_ACTION_UNROLL = 1u << 0, + }; + + // Level-triggered (held == asserted) FlyCam-mode modifiers, reported via + // getFlycamInputs()'s 'modifiers_out'. Unlike FlycamMiscAction these are + // not one-shot commands: they change the meaning of other flycam channels + // for as long as they're held (see LLAgentCamera::updateFlycam()). + enum FlycamModifier : U32 + { + FLYCAM_MODIFIER_ORBIT = 1u << 0, }; // Simulated mouse button actions (press-n-hold) diff --git a/indra/newview/app_settings/key_bindings.xml b/indra/newview/app_settings/key_bindings.xml index c09196dfaf..86ba5b3f57 100644 --- a/indra/newview/app_settings/key_bindings.xml +++ b/indra/newview/app_settings/key_bindings.xml @@ -237,9 +237,9 @@ - + - + diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 02a04f4b88..073032aa63 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -5330,9 +5330,9 @@ void LLAgent::setFlycamKeyInput(U8 channel, F32 value) gAgentCamera.setFlycamKeyInput(channel, value); } -void LLAgent::setFlycamKeyReset(bool reset) +void LLAgent::setFlycamKeyUnroll(bool unroll) { - gAgentCamera.setFlycamKeyReset(reset); + gAgentCamera.setFlycamKeyUnroll(unroll); } bool LLAgent::isCameraExternallyDriven() const diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index 3aeaba20e3..431872557d 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -541,7 +541,7 @@ public: U32 getGameControlButtonsFromKeys() const { return mGameControlButtonsFromKeys; } void setFlycamKeyInput(U8 channel, F32 value); - void setFlycamKeyReset(bool reset); + void setFlycamKeyUnroll(bool unroll); private: diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 922f5f0662..fce809b17e 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -3023,7 +3023,8 @@ void LLAgentCamera::updateFlycam(F32 delta_time) // Note: flycam_inputs arrive in range [-1,1] std::vector flycam_inputs; U32 flycam_misc_actions = 0; - LLGameControl::getFlycamInputs(flycam_inputs, flycam_misc_actions); + U32 flycam_modifiers = 0; + LLGameControl::getFlycamInputs(flycam_inputs, flycam_misc_actions, flycam_modifiers); // The channel order is defined by LLGameControl::FlycamChannel. @@ -3042,48 +3043,110 @@ void LLAgentCamera::updateFlycam(F32 delta_time) flycam_inputs[i] = llclamp(flycam_inputs[i] + mFlycamKeyboardInput[i], -1.f, 1.f); } mFlycamKeyboardInput.fill(0.f); - bool flycam_key_reset_requested = mFlycamKeyboardResetRequested; - mFlycamKeyboardResetRequested = false; - - if (((flycam_misc_actions & LLGameControl::FLYCAM_ACTION_RESET) || flycam_key_reset_requested) && isAgentAvatarValid()) - { - // Reorient the flycam as if it were the 3rd-person camera: above and - // behind the avatar, looking down. mFlycam.startReset() smoothly - // lerps into this transform (see LLFlycam::integrate()); flycam - // input has no effect until the lerp completes. - constexpr F32 FLYCAM_RESET_DURATION = 1.0f; // seconds; may be tuned later - constexpr F32 FLYCAM_RESET_FOCUS_HEIGHT = 1.0f; // meters above avatar root - - LLQuaternion avatar_rot = gAgentAvatarp->isSitting() - ? gAgentAvatarp->getRenderRotation() - : gAgent.getFrameAgent().getQuaternion(); - LLVector3 avatar_pos = gAgent.getPositionAgent(); - - // Reuse the same behind/above offset the real 3rd-person camera uses, - // rotated into the avatar's current facing. - F32 camera_offset_scale = gSavedSettings.getF32("CameraOffsetScale"); - LLVector3 local_offset = getCameraOffsetInitial() * camera_offset_scale; - LLVector3 target_position = avatar_pos + local_offset * avatar_rot; - - // Look toward the avatar (roughly torso height); since the camera - // sits above and behind, this naturally tilts the view down and - // toward the avatar's facing direction. - LLVector3 focus_point = avatar_pos + LLVector3(0.f, 0.f, FLYCAM_RESET_FOCUS_HEIGHT); - LLCoordFrame target_frame; - target_frame.lookAt(target_position, focus_point, LLVector3::z_axis); - - mFlycam.startReset(gAgent.getPosGlobalFromAgent(target_position), target_frame.getQuaternion(), FLYCAM_RESET_DURATION); - } - - LLVector3 linear_velocity( - flycam_inputs[LLGameControl::FLYCAM_DOLLY], - flycam_inputs[LLGameControl::FLYCAM_TRUCK], - flycam_inputs[LLGameControl::FLYCAM_BOOM]); + bool flycam_key_unroll_requested = mFlycamKeyboardUnrollRequested; + mFlycamKeyboardUnrollRequested = false; + + if ((flycam_misc_actions & LLGameControl::FLYCAM_ACTION_UNROLL) || flycam_key_unroll_requested) + { + // Re-level the flycam in place: keep its current position and + // forward (local X) axis unchanged, but roll it around that forward + // axis so the left axis becomes horizontal (equivalently, the up + // axis moves into the forward/world-up plane). mFlycam.startReset() + // smoothly lerps into this transform (see LLFlycam::integrate()); + // flycam input has no effect until the lerp completes. + constexpr F32 FLYCAM_UNROLL_DURATION = 1.0f; // seconds; may be tuned later + + LLVector3d current_position; + LLQuaternion current_rotation; + mFlycam.getTransform(current_position, current_rotation); + + LLMatrix3 level(current_rotation); + LLVector3 forward(level.getFwdRow()); + LLVector3 left(level.getLeftRow()); + LLVector3 up(level.getUpRow()); + left.mV[VZ] = 0.f; + left.normVec(); + level.setRows(forward, left, up); + level.orthogonalize(); + + mFlycam.startReset(current_position, LLQuaternion(level), FLYCAM_UNROLL_DURATION); + } + + // Orbit modifier: while held, Truck stops meaning strafe and instead adds + // into Pan's yaw (both sweep the camera left/right around the focal + // point), and Dolly stops meaning advance and instead adjusts the radius + // (see LLFlycam::setOrbitRadialRate()/integrate()). Tilt, Roll, Boom, + // and Zoom keep their normal meaning either way: Tilt already rotates the + // camera about its own local left axis, which is exactly what swings it + // up/down around the focal point once position is derived from + // orientation; Roll still passes straight through (it just changes which + // region-frame axis Tilt's local axis happens to be next frame). + bool flycam_orbit_held = (flycam_modifiers & LLGameControl::FLYCAM_MODIFIER_ORBIT) != 0; + constexpr F32 FLYCAM_ORBIT_DEFAULT_FOCAL_DISTANCE = 20.0f; // meters; hard-coded for now, may be tuned later + constexpr F32 FLYCAM_ORBIT_MAX_TRACE_DISTANCE = 512.0f; // meters; matches the general world-raycast convention used elsewhere (e.g. LLViewerWindow::cursorIntersect) + F32 flycam_orbit_focal_distance = FLYCAM_ORBIT_DEFAULT_FOCAL_DISTANCE; + if (flycam_orbit_held && !mFlycam.isOrbitEngaged()) + { + // Rising edge: ray-trace forward from the flycam to see what it's + // pointing at, so Orbit pivots around that instead of an arbitrary + // fixed distance whenever there's something in view; falls back to + // the default distance if nothing is hit within range. + LLVector3d camera_pos_global; + LLQuaternion camera_rot; + mFlycam.getTransform(camera_pos_global, camera_rot); + LLVector3 camera_pos = gAgent.getPosAgentFromGlobal(camera_pos_global); + LLVector3 forward(LLMatrix3(camera_rot).getFwdRow()); + LLVector3 trace_end = camera_pos + forward * FLYCAM_ORBIT_MAX_TRACE_DISTANCE; + + LLVector4a start, end; + start.load3(camera_pos.mV); + end.load3(trace_end.mV); + + LLVector4a intersection; + S32 face_hit = -1; + LLViewerObject* hit_object = gPipeline.lineSegmentIntersectInWorld( + start, end, + /*pick_transparent=*/ false, + /*pick_rigged=*/ true, + /*pick_unselectable=*/ true, + /*pick_reflection_probe=*/ false, + &face_hit, nullptr, nullptr, + &intersection); + if (hit_object) + { + LLVector3 hit_pos; + hit_pos.set(intersection.getF32ptr()); + flycam_orbit_focal_distance = (hit_pos - camera_pos).length(); + } + } + mFlycam.setOrbitEngaged(flycam_orbit_held, flycam_orbit_focal_distance); + + F32 yaw_input = flycam_inputs[LLGameControl::FLYCAM_PAN]; + F32 truck_input = flycam_inputs[LLGameControl::FLYCAM_TRUCK]; + F32 dolly_input = flycam_inputs[LLGameControl::FLYCAM_DOLLY]; + if (flycam_orbit_held) + { + // Truck/Dolly are negated here (Pan/Tilt/Roll/Boom/Zoom are not) -- + // determined empirically to match the expected orbit sweep/radial + // directions. + truck_input = -truck_input; + dolly_input = -dolly_input; + yaw_input = llclamp(yaw_input + truck_input, -1.f, 1.f); + truck_input = 0.f; + mFlycam.setOrbitRadialRate(dolly_input); + dolly_input = 0.f; + } + else + { + mFlycam.setOrbitRadialRate(0.f); + } + + LLVector3 linear_velocity(dolly_input, truck_input, flycam_inputs[LLGameControl::FLYCAM_BOOM]); constexpr F32 MAX_FLYCAM_SPEED = 10.0f; mFlycam.setLinearVelocity(MAX_FLYCAM_SPEED * linear_velocity); mFlycam.setPitchRate(flycam_inputs[LLGameControl::FLYCAM_TILT]); - mFlycam.setYawRate(flycam_inputs[LLGameControl::FLYCAM_PAN]); + mFlycam.setYawRate(yaw_input); mFlycam.setRollRate(flycam_inputs[LLGameControl::FLYCAM_ROLL]); mFlycam.setZoomRate(flycam_inputs[LLGameControl::FLYCAM_ZOOM]); diff --git a/indra/newview/llagentcamera.h b/indra/newview/llagentcamera.h index 57c3041a53..b44dd54755 100644 --- a/indra/newview/llagentcamera.h +++ b/indra/newview/llagentcamera.h @@ -197,16 +197,18 @@ private: public: bool isUsingFlycam() const { return mUsingFlycam; } void toggleFlycam(); + // Game-control/keyboard-driven update -- see LLViewerJoystick::moveFlycam() // for the other (legacy NDOF joystick) input source, below. void updateFlycam(F32 delta_time); void setFlycamKeyInput(U8 channel, F32 value) { mFlycamKeyboardInput[channel] = value; } - void setFlycamKeyReset(bool reset) { mFlycamKeyboardResetRequested = reset; } + void setFlycamKeyUnroll(bool unroll) { mFlycamKeyboardUnrollRequested = unroll; } // Snapshots the live LLViewerCamera transform into the flycam. Used both // when entering flycam (see toggleFlycam()) and whenever an input source // (e.g. the NDOF joystick regaining focus) needs to recalibrate its own // axis-delta state against the camera's current position/orientation. void resetFlycamToCurrentView(); + // Entry point for input sources (the legacy NDOF joystick) that compute // their own per-axis dead-zone/scale/feathering deltas and only need this // class to own the resulting transform update + push to LLViewerCamera -- @@ -217,12 +219,13 @@ public: private: LLFlycam mFlycam; bool mUsingFlycam { false }; + // Keyboard's contribution to the shared/game-control flycam channels // (blended with LLGameControl::getFlycamInputs() each frame in // updateFlycam()) -- unrelated to the legacy joystick's own tuning state, // which lives on LLViewerJoystick (mJoystickFlycamDelta/LastDelta). std::array mFlycamKeyboardInput {}; - bool mFlycamKeyboardResetRequested { false }; + bool mFlycamKeyboardUnrollRequested { false }; //-------------------------------------------------------------------- // Sit diff --git a/indra/newview/llflycam.cpp b/indra/newview/llflycam.cpp index 60c6ac86c5..01577ec9ef 100644 --- a/indra/newview/llflycam.cpp +++ b/indra/newview/llflycam.cpp @@ -91,6 +91,32 @@ void LLFlycam::setZoomRate(F32 zoom_rate) } +void LLFlycam::setOrbitEngaged(bool engaged, F32 focal_distance) +{ + if (engaged && !mOrbitEngaged) + { + // Rising edge: fix the focal point 'focal_distance' meters in front of + // the camera's current forward axis. It stays put in world space for + // as long as orbit stays engaged, even as Pan/Truck/Tilt/Roll rotate + // the camera around it (see integrate()). + LLVector3 forward(LLMatrix3(mRotation).getFwdRow()); + mOrbitFocalPoint = mPosition + LLVector3d(forward) * (F64)focal_distance; + } + mOrbitEngaged = engaged; +} + + +void LLFlycam::setOrbitRadialRate(F32 radial_rate) +{ + // Note: this math expects radial_rate to be in range [-1.0, 1.0]. The + // resulting rate is a FRACTION of the current distance-to-focal-point per + // second (not a fixed speed), so approach slows near the focal point and + // retreat speeds up far away from it. + constexpr F32 ORBIT_RADIAL_RATE_FACTOR = 1.0f; // fraction of distance/sec at full deflection; may be tuned later + mOrbitRadialRate = radial_rate * ORBIT_RADIAL_RATE_FACTOR; +} + + void LLFlycam::startReset(const LLVector3d& target_position, const LLQuaternion& target_rotation, F32 duration) { mResetStartPosition = mPosition; @@ -160,9 +186,39 @@ void LLFlycam::integrate(F32 delta_time) needs_renormalization = true; } + if (mOrbitEngaged) + { + // Re-derive position from the just-rotated forward axis, so the + // camera stays (radially adjusted) 'distance' meters from the fixed + // focal point, always facing it. Pan/Truck (yaw, above) and Tilt + // (pitch, above) therefore sweep/swing the camera around the focal + // point as a side effect of simply rotating the camera; only the + // radius itself is orbit-specific here. + constexpr F32 MIN_ORBIT_DISTANCE = 0.1f; // meters + F32 distance = (F32)(mPosition - mOrbitFocalPoint).length(); + distance = std::max(distance * (1.0f + delta_time * mOrbitRadialRate), MIN_ORBIT_DISTANCE); + // 'forward' points from the camera toward the focal point, so the + // camera itself sits behind the focal point along that axis. + LLVector3 forward(LLMatrix3(mRotation).getFwdRow()); + mPosition = mOrbitFocalPoint - LLVector3d(forward) * (F64)distance; + } + if (mLinearVelocity.lengthSquared() > 0.0f) { - mPosition += LLVector3d((delta_time * mLinearVelocity) * mRotation); + // Boom (local Z) still translates the camera directly here, on top of + // the orbit reposition above, regardless of mOrbitEngaged -- Truck/ + // Dolly's contributions are zeroed out by LLAgentCamera::updateFlycam() + // while orbiting, since they're redirected into yaw/radial rate instead. + LLVector3d translation((delta_time * mLinearVelocity) * mRotation); + mPosition += translation; + if (mOrbitEngaged) + { + // Boom doesn't reorient the camera or change its distance from + // the focal point -- drag the focal point along with it instead, + // so the next frame's orbit reposition still measures 'distance' + // from wherever boom just moved it to. + mOrbitFocalPoint += translation; + } } if (mZoomRate != 0.0f) diff --git a/indra/newview/llflycam.h b/indra/newview/llflycam.h index 37bef8acdc..936540b229 100644 --- a/indra/newview/llflycam.h +++ b/indra/newview/llflycam.h @@ -52,6 +52,19 @@ public: void setRollRate(F32 roll_rate); void setZoomRate(F32 zoom_rate); + // Orbit modifier: while engaged, position is re-derived every integrate() + // from the camera's own (pitch/yaw-rotated) forward axis so the camera + // stays 'focal_distance' meters from a focal point fixed in front of it + // the moment orbit engages -- Pan/Truck/Tilt/Roll keep rotating the + // camera exactly as they do outside orbit (see integrate()); only the + // radial (dolly) rate is orbit-specific. Boom still translates the camera + // directly (see integrate()), but doesn't reorient it or change its + // distance from the focal point: the same translation is applied to the + // focal point too, dragging it along with the camera. + void setOrbitEngaged(bool engaged, F32 focal_distance); + bool isOrbitEngaged() const { return mOrbitEngaged; } + void setOrbitRadialRate(F32 radial_rate); + // Begins a smooth transition from the current transform to // 'target_position'/'target_rotation', taking 'duration' seconds. While // the transition is in progress, integrate() ignores the rates set by @@ -86,6 +99,12 @@ protected: F32 mZoomRate { 0.0f }; F32 mView { DEFAULT_FIELD_OF_VIEW }; + // Orbit-in-progress state: see setOrbitEngaged()/setOrbitRadialRate() and + // the orbit branch in integrate(). + bool mOrbitEngaged { false }; + LLVector3d mOrbitFocalPoint; + F32 mOrbitRadialRate { 0.0f }; + // Reset-in-progress state: integrate() lerps mPosition/mRotation from // mResetStart* to mResetTarget* as mResetTimeRemaining counts down from // mResetDuration to zero. diff --git a/indra/newview/llviewerinput.cpp b/indra/newview/llviewerinput.cpp index 6846983312..2559f1065f 100644 --- a/indra/newview/llviewerinput.cpp +++ b/indra/newview/llviewerinput.cpp @@ -389,11 +389,11 @@ bool flycam_axis_key(const LLKeyPressState& s) return true; } -bool flycam_reset_key(const LLKeyPressState& s) +bool flycam_unroll_key(const LLKeyPressState& s) { if (KEYSTATE_DOWN == s.mState) { - gAgent.setFlycamKeyReset(true); + gAgent.setFlycamKeyUnroll(true); } return true; } @@ -1121,7 +1121,7 @@ REGISTER_KEYBOARD_ACTION("flycam_pitch_up", (flycam_axis_key)); REGISTER_KEYBOARD_ACTION("flycam_roll_left", (flycam_axis_key)); REGISTER_KEYBOARD_ACTION("flycam_roll_right", (flycam_axis_key)); -REGISTER_KEYBOARD_ACTION("flycam_reset", flycam_reset_key); +REGISTER_KEYBOARD_ACTION("flycam_unroll", flycam_unroll_key); REGISTER_KEYBOARD_ACTION("game_control_button_0", game_control_button<0>); REGISTER_KEYBOARD_ACTION("game_control_button_1", game_control_button<1>); REGISTER_KEYBOARD_ACTION("game_control_button_2", game_control_button<2>); diff --git a/indra/newview/skins/default/xui/en/floater_joystick.xml b/indra/newview/skins/default/xui/en/floater_joystick.xml index 623f282305..3cba75c08c 100644 --- a/indra/newview/skins/default/xui/en/floater_joystick.xml +++ b/indra/newview/skins/default/xui/en/floater_joystick.xml @@ -200,6 +200,7 @@ layout="topleft" left="289" name="JoystickFlycamEnabled" + tool_tip="Lets this device fly the camera." width="60" /> + label="Unroll" + value="Unroll"/> + -- cgit v1.3 From 80383a1a482b66530ad322a619262f4a26420ff0 Mon Sep 17 00:00:00 2001 From: leviathan Date: Mon, 14 Sep 2026 12:24:23 -0700 Subject: add ESC action to game-control --- indra/llwindow/llgamecontrol.cpp | 11 +++++++++++ indra/llwindow/llgamecontrol.h | 6 ++++++ indra/newview/llagent.cpp | 21 +++++++++++++++++++++ indra/newview/llagent.h | 7 +++++++ indra/newview/llagentcamera.cpp | 8 ++++++++ .../xui/en/panel_preferences_game_control.xml | 9 +++++++++ 6 files changed, 62 insertions(+) (limited to 'indra/newview/llagentcamera.cpp') diff --git a/indra/llwindow/llgamecontrol.cpp b/indra/llwindow/llgamecontrol.cpp index b4994f9558..f157112f93 100755 --- a/indra/llwindow/llgamecontrol.cpp +++ b/indra/llwindow/llgamecontrol.cpp @@ -726,6 +726,8 @@ namespace mouselook_buttons["Mouse click right"] = "BUTTON_RIGHT_SHOULDER"; mouselook_buttons["Toggle mouselook"] = "BUTTON_START"; mouselook_buttons["Toggle flycam"] = "BUTTON_RIGHT_STICK"; + // Unbound by default; see the "ESC" entry under flycam_buttons below. + mouselook_buttons["ESC"] = "BUTTON_NONE"; mouselook_buttons["Advance forward"] = "BUTTON_DPAD_UP"; mouselook_buttons["Advance back"] = "BUTTON_DPAD_DOWN"; mouselook_buttons["Strafe left"] = "BUTTON_DPAD_LEFT"; @@ -754,6 +756,8 @@ namespace cursor_buttons["Toggle speak"] = "BUTTON_HOME"; cursor_buttons["Toggle mouselook"] = "BUTTON_START"; cursor_buttons["Toggle flycam"] = "BUTTON_RIGHT_STICK"; + // Unbound by default; see the "ESC" entry under flycam_buttons below. + cursor_buttons["ESC"] = "BUTTON_NONE"; cursor_buttons["Mouse click left"] = "BUTTON_LEFT_SHOULDER"; cursor_buttons["Mouse click right"] = "BUTTON_RIGHT_SHOULDER"; cursor_buttons["Advance forward"] = "BUTTON_DPAD_UP"; @@ -796,6 +800,11 @@ namespace flycam_buttons["Roll CCW"] = "BUTTON_WEST"; flycam_buttons["Roll CW"] = "BUTTON_EAST"; flycam_buttons["Orbit"] = "BUTTON_RIGHT_SHOULDER"; + // Unbound by default -- every physical button above is already claimed, + // same reasoning as "Zoom +/-"/"Roll left/right" being unbound axes. + // Resets straight back to Avatar mode (see AVATAR_ACTION_ESCAPE/ + // FLYCAM_ACTION_ESCAPE and LLAgent::resetToAvatarMode()). + flycam_buttons["ESC"] = "BUTTON_NONE"; LLSD flycam_axes_invert; flycam_axes_invert["Truck left/right"] = true; @@ -1886,6 +1895,7 @@ namespace { "Toggle flycam", LLGameControl::AVATAR_ACTION_TOGGLE_FLYCAM }, { "Toggle mouselook", LLGameControl::AVATAR_ACTION_TOGGLE_MOUSELOOK }, { "Toggle mouse cursor", LLGameControl::AVATAR_ACTION_TOGGLE_MOUSE_CURSOR }, + { "ESC", LLGameControl::AVATAR_ACTION_ESCAPE }, }; return bridge; } @@ -2443,6 +2453,7 @@ namespace { static const std::map bridge = { { "Unroll", LLGameControl::FLYCAM_ACTION_UNROLL }, + { "ESC", LLGameControl::FLYCAM_ACTION_ESCAPE }, }; return bridge; } diff --git a/indra/llwindow/llgamecontrol.h b/indra/llwindow/llgamecontrol.h index 4650a572f2..7615543169 100644 --- a/indra/llwindow/llgamecontrol.h +++ b/indra/llwindow/llgamecontrol.h @@ -541,11 +541,17 @@ public: AVATAR_ACTION_TOGGLE_FLYCAM = 1u << 3, AVATAR_ACTION_TOGGLE_MOUSELOOK = 1u << 4, AVATAR_ACTION_TOGGLE_MOUSE_CURSOR = 1u << 5, + // Mouselook/Cursor-mode only (see buildDefaultModeMappings()): drops + // straight back to plain Avatar mode, clearing whichever of Mouselook/ + // Cursor (and FlyCam, via FLYCAM_ACTION_ESCAPE below) got it there. + AVATAR_ACTION_ESCAPE = 1u << 6, }; enum FlycamMiscAction : U32 { FLYCAM_ACTION_UNROLL = 1u << 0, + // FlyCam-mode counterpart of AVATAR_ACTION_ESCAPE above. + FLYCAM_ACTION_ESCAPE = 1u << 1, }; // Level-triggered (held == asserted) FlyCam-mode modifiers, reported via diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 3945b230c7..ff1f645e5f 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -5101,6 +5101,11 @@ void LLAgent::applyExternalActions(const LLGameControl::AgentActions& actions) toggleMouseCursorMode(); } + if (misc_actions & LLGameControl::AVATAR_ACTION_ESCAPE) + { + resetToAvatarMode(); + } + // CONTROL_MODE_CURSOR: drive the actual on-screen cursor from // actions.mMouseCursorDX/DY (the analog deflection of whatever's bound to "Mouse // left/right"/"Mouse up/down") BEFORE the click dispatch below, so a synthesized @@ -5354,6 +5359,22 @@ void LLAgent::toggleMouseCursorMode() mLastMouseCursorUpdate = 0; // avoid a jump from a stale delta-time baseline } +void LLAgent::resetToAvatarMode() +{ + if (mUsingMouseCursor) + { + toggleMouseCursorMode(); + } + if (isUsingFlycam()) + { + toggleFlycam(); + } + if (gAgentCamera.cameraMouselook()) + { + gAgentCamera.changeCameraToDefault(); + } +} + void LLAgent::pressGameControlButton(U8 button_index) { mGameControlButtonsFromKeys |= (1U << button_index); diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index 431872557d..c21f197b78 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -502,6 +502,13 @@ public: bool isCameraExternallyDriven() const; bool isUsingMouseCursor() const { return mUsingMouseCursor; } void toggleMouseCursorMode(); + + // Drops straight back to plain Avatar mode, unwinding whichever of + // Mouselook/FlyCam/Cursor mode is currently active (any combination -- + // see updateGameControlMode()). Bound to the game-control "ESC" action + // in those modes (see AVATAR_ACTION_ESCAPE/FLYCAM_ACTION_ESCAPE). + void resetToAvatarMode(); + // Send message to simulator to force grabbed controls to be // released, in case of a poorly written script. void forceReleaseControls(); diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index fce809b17e..a5b6ccaab9 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -3034,6 +3034,14 @@ void LLAgentCamera::updateFlycam(F32 delta_time) return; } + if (flycam_misc_actions & LLGameControl::FLYCAM_ACTION_ESCAPE) + { + // Bail out of FlyCam mode immediately; no point applying this frame's + // (about to be abandoned) flycam deltas below. + gAgent.resetToAvatarMode(); + return; + } + // Blend in keyboard-driven flycam input (independent of LLGameControl, // which only sees a physical controller). mFlycamKeyboardInput is // level-triggered by flycam_axis_key<> in llviewerinput.cpp -- it must be diff --git a/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml b/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml index 6789475a5e..5ef9023e9e 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml @@ -884,6 +884,9 @@ + @@ -1027,6 +1030,9 @@ + @@ -1115,6 +1121,9 @@ + -- cgit v1.3 From b761c9eab4cff22bdd8949f01b41f93e1df38cf0 Mon Sep 17 00:00:00 2001 From: leviathan Date: Mon, 14 Sep 2026 14:44:30 -0700 Subject: fix unroll to always orient toward 'up' --- indra/newview/llagentcamera.cpp | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) (limited to 'indra/newview/llagentcamera.cpp') diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index a5b6ccaab9..51362948aa 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -3056,12 +3056,13 @@ void LLAgentCamera::updateFlycam(F32 delta_time) if ((flycam_misc_actions & LLGameControl::FLYCAM_ACTION_UNROLL) || flycam_key_unroll_requested) { - // Re-level the flycam in place: keep its current position and - // forward (local X) axis unchanged, but roll it around that forward - // axis so the left axis becomes horizontal (equivalently, the up - // axis moves into the forward/world-up plane). mFlycam.startReset() - // smoothly lerps into this transform (see LLFlycam::integrate()); - // flycam input has no effect until the lerp completes. + // Re-orient the flycam in place: keep its current position and + // forward (local X) axis unchanged, but unroll it so its local vertical + // axis lies on the plane defined by forward axis and world-up, with positive + // dot-product with world-up. + // mFlycam.startReset() smoothly lerps into this transform (see + // LLFlycam::integrate()); flycam input has no effect until the lerp + // completes. constexpr F32 FLYCAM_UNROLL_DURATION = 1.0f; // seconds; may be tuned later LLVector3d current_position; @@ -3070,14 +3071,22 @@ void LLAgentCamera::updateFlycam(F32 delta_time) LLMatrix3 level(current_rotation); LLVector3 forward(level.getFwdRow()); - LLVector3 left(level.getLeftRow()); - LLVector3 up(level.getUpRow()); - left.mV[VZ] = 0.f; - left.normVec(); - level.setRows(forward, left, up); - level.orthogonalize(); - - mFlycam.startReset(current_position, LLQuaternion(level), FLYCAM_UNROLL_DURATION); + forward.normVec(); + + LLVector3 world_up = LLVector3::z_axis; + LLVector3 new_up = world_up - (world_up * forward) * forward; + constexpr F32 MIN_UNROLL_SIN = 0.01f; // ~0.6 degrees off vertical + if (new_up.lengthSquared() > MIN_UNROLL_SIN * MIN_UNROLL_SIN) + { + new_up.normVec(); + // Right-handed (forward, left, up) basis, per + // LLMatrix3::orthogonalize()'s up = forward % left: up % forward == left. + LLVector3 new_left = new_up % forward; + level.setRows(forward, new_left, new_up); + level.orthogonalize(); // numerical cleanup only; rows are already orthonormal by construction + + mFlycam.startReset(current_position, LLQuaternion(level), FLYCAM_UNROLL_DURATION); + } } // Orbit modifier: while held, Truck stops meaning strafe and instead adds -- cgit v1.3 From 7bc3606ab63e2bd0e58d4193666168691cd4da5c Mon Sep 17 00:00:00 2001 From: leviathan Date: Mon, 14 Sep 2026 20:40:58 -0700 Subject: add option to enable/disable flycam roll --- indra/llwindow/llgamecontrol.cpp | 26 ++++++++++++++++-- indra/llwindow/llgamecontrol.h | 6 ++++ indra/newview/llagentcamera.cpp | 1 + indra/newview/llflycam.cpp | 32 ++++++++++++++++++++-- indra/newview/llflycam.h | 8 ++++++ indra/newview/llpanelpreferencegamecontrol.cpp | 23 +++++++++++++++- indra/newview/llpanelpreferencegamecontrol.h | 1 + .../xui/en/panel_preferences_game_control.xml | 14 ++++++++-- 8 files changed, 104 insertions(+), 7 deletions(-) (limited to 'indra/newview/llagentcamera.cpp') diff --git a/indra/llwindow/llgamecontrol.cpp b/indra/llwindow/llgamecontrol.cpp index 8a7c56d0b5..29afc2f429 100755 --- a/indra/llwindow/llgamecontrol.cpp +++ b/indra/llwindow/llgamecontrol.cpp @@ -609,6 +609,9 @@ namespace // Per-mode flag gating whether game-control input is converted to that mode's // actions. When false the mode's mappings are locked and no actions fire. const std::string GC_ENABLED("Enabled"); + // FlyCam-only flag gating whether Roll input rotates the flycam about its + // forward axis (see LLFlycam::mAllowRoll). Absent/false means Roll is ignored. + const std::string GC_ALLOW_ROLL("AllowRoll"); const std::string GC_MODE_AVATAR("Avatar"); const std::string GC_MODE_MOUSELOOK("Mouselook"); const std::string GC_MODE_FLYCAM("FlyCam"); @@ -2438,8 +2441,8 @@ namespace { "Tilt down", { LLGameControl::FLYCAM_TILT, -1.f } }, { "Boom up", { LLGameControl::FLYCAM_BOOM, 1.f } }, { "Boom down", { LLGameControl::FLYCAM_BOOM, -1.f } }, - { "Roll CCW", { LLGameControl::FLYCAM_ROLL, 1.f } }, - { "Roll CW", { LLGameControl::FLYCAM_ROLL, -1.f } }, + { "Roll CCW", { LLGameControl::FLYCAM_ROLL, -1.f } }, + { "Roll CW", { LLGameControl::FLYCAM_ROLL, 1.f } }, { "Zoom in", { LLGameControl::FLYCAM_ZOOM, 1.f } }, { "Zoom out", { LLGameControl::FLYCAM_ZOOM, -1.f } }, }; @@ -3708,6 +3711,25 @@ void LLGameControl::setModeEnabled(const std::string& mode, bool enabled) // this flag live each frame, so no action-lookup rebuild is needed here. } +// static +bool LLGameControl::isFlycamRollAllowed() +{ + ensureGameControlSettings(); + const LLSD& mode_map = g_gameControlSettings[GC_MODEMAPPINGS][GC_MODE_FLYCAM]; + // Default to disallowed when the flag is absent (e.g. settings saved before + // this flag existed, or LLFlycam::mAllowRoll's own default). + return mode_map.isMap() && mode_map[GC_ALLOW_ROLL].asBoolean(); +} + +// static +void LLGameControl::setFlycamRollAllowed(bool allowed) +{ + ensureGameControlSettings(); + g_gameControlSettings[GC_MODEMAPPINGS][GC_MODE_FLYCAM][GC_ALLOW_ROLL] = allowed; + // The runtime reads this flag live each frame (LLAgentCamera::updateFlycam()), + // so no action-lookup rebuild is needed here. +} + // static std::string LLGameControl::getDeviceConfig(const std::string& guid) { diff --git a/indra/llwindow/llgamecontrol.h b/indra/llwindow/llgamecontrol.h index 7615543169..37799c9fb0 100644 --- a/indra/llwindow/llgamecontrol.h +++ b/indra/llwindow/llgamecontrol.h @@ -675,6 +675,12 @@ public: // the flag is absent. 'mode' is "Avatar"/"Mouselook"/"FlyCam"/"Captive"/"Cursor". static bool isModeEnabled(const std::string& mode); static void setModeEnabled(const std::string& mode, bool enabled); + + // FlyCam-only flag: whether Roll input is allowed to rotate the flycam about + // its forward axis (see LLFlycam::mAllowRoll). Defaults to false when absent. + static bool isFlycamRollAllowed(); + static void setFlycamRollAllowed(bool allowed); + static std::string getDeviceConfig(const std::string& guid); static void setDeviceConfig(const std::string& guid, const std::string& config); diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 51362948aa..add58f0f26 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -3164,6 +3164,7 @@ void LLAgentCamera::updateFlycam(F32 delta_time) mFlycam.setPitchRate(flycam_inputs[LLGameControl::FLYCAM_TILT]); mFlycam.setYawRate(yaw_input); + mFlycam.setAllowRoll(LLGameControl::isFlycamRollAllowed()); mFlycam.setRollRate(flycam_inputs[LLGameControl::FLYCAM_ROLL]); mFlycam.setZoomRate(flycam_inputs[LLGameControl::FLYCAM_ZOOM]); diff --git a/indra/newview/llflycam.cpp b/indra/newview/llflycam.cpp index 01577ec9ef..7a94ea2843 100644 --- a/indra/newview/llflycam.cpp +++ b/indra/newview/llflycam.cpp @@ -78,7 +78,7 @@ void LLFlycam::setRollRate(F32 roll_rate) { // Note: this math expects roll_rate to be in range [-1.0, 1.0] constexpr F32 ROLL_RATE_FACTOR = 0.90f; - mRollRate = roll_rate * ROLL_RATE_FACTOR; + mRollRate = mAllowRoll ? roll_rate * ROLL_RATE_FACTOR : 0.0f; } @@ -182,7 +182,35 @@ void LLFlycam::integrate(F32 delta_time) { LLQuaternion dQ; dQ.setAngleAxis(angle, 0.0f, 0.0f, 1.0f); - mRotation = mRotation * dQ; + if (mAllowRoll) + { + mRotation = dQ * mRotation; + } + else + { + mRotation = dQ * mRotation; + + // Un-roll: keep the just-applied forward axis, but rebuild "up" to + // lie in the plane spanned by forward and world-up, which removes + // any roll the pitch+yaw quaternion multiplications above may have + // introduced (they don't commute). + LLMatrix3 level(mRotation); + LLVector3 forward(level.getFwdRow()); + forward.normVec(); + + LLVector3 world_up = LLVector3::z_axis; + LLVector3 new_up = world_up - (world_up * forward) * forward; + constexpr F32 MIN_UNROLL_SIN = 0.01f; // ~0.6 degrees off vertical + if (new_up.lengthSquared() > MIN_UNROLL_SIN * MIN_UNROLL_SIN) + { + new_up.normVec(); + // Right-handed (forward, left, up) basis, per + // LLMatrix3::orthogonalize()'s up = forward % left: up % forward == left. + LLVector3 new_left = new_up % forward; + level.setRows(forward, new_left, new_up); + mRotation = LLQuaternion(level); + } + } needs_renormalization = true; } diff --git a/indra/newview/llflycam.h b/indra/newview/llflycam.h index 936540b229..0b076879c4 100644 --- a/indra/newview/llflycam.h +++ b/indra/newview/llflycam.h @@ -52,6 +52,13 @@ public: void setRollRate(F32 roll_rate); void setZoomRate(F32 zoom_rate); + // Whether Roll input is allowed to rotate the flycam about its forward + // axis; when false, setRollRate() discards its input and the flycam + // never rolls. Off by default; driven by the "GameControl" FlyCam + // settings' AllowRoll flag (see LLGameControl::isFlycamRollAllowed()). + void setAllowRoll(bool allow) { mAllowRoll = allow; } + bool getAllowRoll() const { return mAllowRoll; } + // Orbit modifier: while engaged, position is re-derived every integrate() // from the camera's own (pitch/yaw-rotated) forward axis so the camera // stays 'focal_distance' meters from a focal point fixed in front of it @@ -98,6 +105,7 @@ protected: F32 mRollRate { 0.0f }; F32 mZoomRate { 0.0f }; F32 mView { DEFAULT_FIELD_OF_VIEW }; + bool mAllowRoll { false }; // Orbit-in-progress state: see setOrbitEngaged()/setOrbitRadialRate() and // the orbit branch in integrate(). diff --git a/indra/newview/llpanelpreferencegamecontrol.cpp b/indra/newview/llpanelpreferencegamecontrol.cpp index 3d8863e708..e2b9478ce7 100755 --- a/indra/newview/llpanelpreferencegamecontrol.cpp +++ b/indra/newview/llpanelpreferencegamecontrol.cpp @@ -731,6 +731,10 @@ bool LLPanelPreferenceGameControl::postBuild() mCheckActionModeEnabled->setCommitCallback([this](LLUICtrl*, const LLSD&) { onModeEnabledToggled(mCheckActionModeEnabled->getValue()); }); + mCheckFlycamAllowRoll = getChild("flycam_allow_roll"); + mCheckFlycamAllowRoll->setCommitCallback([this](LLUICtrl*, const LLSD&) + { LLGameControl::setFlycamRollAllowed(mCheckFlycamAllowRoll->getValue()); }); + mRestoreActionsDefaults = getChild("restore_actions_defaults"); mRestoreActionsDefaults->setCommitCallback([this](LLUICtrl*, const LLSD&) { onResetActionsToDefaults(); }); @@ -1224,13 +1228,23 @@ void LLPanelPreferenceGameControl::onModeEnabledToggled(bool enabled) // the action tables (and Restore Defaults) so a disabled mode's mappings can't change. void LLPanelPreferenceGameControl::updateActionModeEnabledUI() { - bool enabled = LLGameControl::isModeEnabled(currentEditMode()); + std::string mode = currentEditMode(); + bool enabled = LLGameControl::isModeEnabled(mode); mCheckActionModeEnabled->set(enabled); // A disabled mode's tables grey out; onGridSelect() also refuses edits on a // disabled table, so this both signals and enforces the lock. mActionMappingsAxes->setEnabled(enabled); mActionMappingsButtons->setEnabled(enabled); mRestoreActionsDefaults->setEnabled(enabled); + + // Roll is only meaningful for FlyCam: hide the checkbox for every other mode. + bool is_flycam = (mode == "FlyCam"); + mCheckFlycamAllowRoll->setVisible(is_flycam); + if (is_flycam) + { + mCheckFlycamAllowRoll->set(LLGameControl::isFlycamRollAllowed()); + mCheckFlycamAllowRoll->setEnabled(enabled); + } } // Shows the live AgentControlMode independent of the mode being edited (mActionMode) @@ -1941,7 +1955,14 @@ void LLPanelPreferenceGameControl::onResetActionsToDefaults() LLGameControl::setModeMapping(mode, MODE_INPUT_TYPE_AXES, defaults[mode][MODE_INPUT_TYPE_AXES]); LLGameControl::setModeMapping(mode, MODE_INPUT_TYPE_BUTTONS, defaults[mode][MODE_INPUT_TYPE_BUTTONS]); LLGameControl::setModeMapping(mode, MODE_INPUT_TYPE_AXES_INVERT, defaults[mode][MODE_INPUT_TYPE_AXES_INVERT]); + if (mode == "FlyCam") + { + // AllowRoll lives outside the Axes/Buttons/AxesInvert mapping this + // function otherwise restores, so it needs to be reset explicitly. + LLGameControl::setFlycamRollAllowed(false); + } populateActionMappings(); + updateActionModeEnabledUI(); // syncs mCheckFlycamAllowRoll with the reset value above // Push the pending UI state into LLGameControl's runtime so the effect is // immediate. gSavedSettings is updated later via saveSettings() on OK. diff --git a/indra/newview/llpanelpreferencegamecontrol.h b/indra/newview/llpanelpreferencegamecontrol.h index abf4a10d13..8499aecc92 100644 --- a/indra/newview/llpanelpreferencegamecontrol.h +++ b/indra/newview/llpanelpreferencegamecontrol.h @@ -175,6 +175,7 @@ private: // Actions tab LLComboBox* mActionMode { nullptr }; // selects which mode's mappings are edited LLCheckBoxCtrl* mCheckActionModeEnabled { nullptr }; // enables/disables the selected mode + LLCheckBoxCtrl* mCheckFlycamAllowRoll { nullptr }; // FlyCam-only: allows Roll input; hidden for other modes LLButton* mRestoreActionsDefaults { nullptr }; LLTextBox* mCurrentModeIndicator { nullptr }; //mode indicator LLGameControl::AgentControlMode mLastDrawnControlMode { LLGameControl::CONTROL_MODE_NONE }; diff --git a/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml b/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml index 5ef9023e9e..2fd6f0c29d 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml @@ -66,6 +66,16 @@ height="16" width="70" tool_tip="Uncheck to lock this mode's mappings and stop converting game-control input to this mode's actions."/> +