summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorleviathan <leviathan@lindenlab.com>2026-09-24 16:04:55 -0700
committerleviathan <leviathan@lindenlab.com>2026-10-16 13:14:38 -0700
commitdfca45c8ae47ef1f56ef0af591a8f456fcd64784 (patch)
treebeaa98f300550e63a5f79a90d94e82cd153d10df
parentf9f14adc36f792d1f885a421f888a93a8072873f (diff)
add 'scroll' game-control action for changing follocam dist
-rwxr-xr-xindra/llwindow/llgamecontrol.cpp53
-rw-r--r--indra/llwindow/llgamecontrol.h14
-rw-r--r--indra/newview/llagent.cpp12
-rw-r--r--indra/newview/llagentcamera.cpp60
-rw-r--r--indra/newview/llagentcamera.h7
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_game_control.xml9
6 files changed, 131 insertions, 24 deletions
diff --git a/indra/llwindow/llgamecontrol.cpp b/indra/llwindow/llgamecontrol.cpp
index 7c4a45f083..644bf2adf8 100755
--- a/indra/llwindow/llgamecontrol.cpp
+++ b/indra/llwindow/llgamecontrol.cpp
@@ -692,6 +692,9 @@ namespace
// Unbound by default: every physical axis is already claimed above (the
// trigger pair covers Fly up/down), same as FlyCam's Roll axis below.
avatar_axes["Zoom +/-"] = "AXIS_NONE";
+ // Camera distance from the avatar (see AgentActions::mScrollAmplitude);
+ // client-side only, so it has no ModeAxes/ModeButtons slot.
+ avatar_axes["Scroll +/-"] = "AXIS_NONE";
// Buttons: action label -> button input
LLSD avatar_buttons;
@@ -708,6 +711,9 @@ namespace
avatar_buttons["Advance back"] = "BUTTON_DPAD_DOWN";
avatar_buttons["Strafe left"] = "BUTTON_DPAD_LEFT";
avatar_buttons["Strafe right"] = "BUTTON_DPAD_RIGHT";
+ // Unbound by default, same as the "Scroll +/-" axis above.
+ avatar_buttons["Scroll +"] = "BUTTON_NONE";
+ avatar_buttons["Scroll -"] = "BUTTON_NONE";
// Default per-axis-action Invert flags
LLSD avatar_axes_invert;
@@ -1923,6 +1929,18 @@ namespace
return bridge;
}
+ // Avatar-mode button label -> full-deflection contribution to the same scroll
+ // (camera distance) accumulator as the "Scroll +/-" axis, same pattern as
+ // avatarZoomButtonBridge() above.
+ const std::map<std::string, F32>& avatarScrollButtonBridge()
+ {
+ static const std::map<std::string, F32> bridge = {
+ { "Scroll +", 1.f },
+ { "Scroll -", -1.f },
+ };
+ return bridge;
+ }
+
// Cursor mode's digital (button) equivalent of "Mouse left/right"/"Mouse up/down":
// a discrete alternative/addition to the analog stick for driving the on-screen
// cursor, e.g. so the D-Pad can be rebound to nudge the cursor instead of moving
@@ -2170,6 +2188,7 @@ LLGameControl::AgentActions LLGameControllerManager::computeAgentActions()
S32 mouse_dx = 0;
S32 mouse_dy = 0;
S32 zoom_value = 0;
+ S32 scroll_value = 0;
for (U8 axis = 0; axis < LLGameControl::NUM_AXES; ++axis)
{
const AxisActionBinding& binding = mAxisActionBindings[axis];
@@ -2217,6 +2236,23 @@ LLGameControl::AgentActions LLGameControllerManager::computeAgentActions()
continue;
}
+ // "Scroll +/-" drives the camera's distance from the avatar, captured
+ // separately for the same reason as "Zoom +/-" above.
+ if (binding.label == "Scroll +/-")
+ {
+ S32 deflection = (S32)g_innerState.mAxes[axis * 2] - (S32)g_innerState.mAxes[axis * 2 + 1];
+ S32 value = binding.half == HALF_NEGATIVE ? -deflection : deflection;
+ if (binding.invert)
+ {
+ value = -value;
+ }
+ if (std::abs(value) > std::abs(scroll_value))
+ {
+ scroll_value = value;
+ }
+ continue;
+ }
+
auto it = axis_bridge.find(binding.label);
if (it == axis_bridge.end())
{
@@ -2302,6 +2338,7 @@ LLGameControl::AgentActions LLGameControllerManager::computeAgentActions()
const auto& misc_button_bridge = avatarMiscButtonBridge();
const auto& mouse_cursor_button_bridge = mouseCursorButtonBridge();
const auto& zoom_button_bridge = avatarZoomButtonBridge();
+ const auto& scroll_button_bridge = avatarScrollButtonBridge();
U32 pressed_edges = g_innerState.mButtons & ~g_innerState.mPrevButtons;
for (U8 btn = 0; btn < LLGameControl::NUM_BUTTONS; ++btn)
{
@@ -2372,6 +2409,16 @@ LLGameControl::AgentActions LLGameControllerManager::computeAgentActions()
}
continue;
}
+ auto scroll_it = scroll_button_bridge.find(label);
+ if (scroll_it != scroll_button_bridge.end())
+ {
+ S32 full = (S32)(scroll_it->second * 32767.f);
+ if (std::abs(full) > std::abs(scroll_value))
+ {
+ scroll_value = full;
+ }
+ continue;
+ }
auto mit = misc_button_bridge.find(label);
if (mit != misc_button_bridge.end() && (pressed_edges & (1U << btn)))
{
@@ -2386,6 +2433,7 @@ LLGameControl::AgentActions LLGameControllerManager::computeAgentActions()
result.mMouseCursorDX = std::clamp((F32)mouse_dx / 32767.f, -1.f, 1.f);
result.mMouseCursorDY = std::clamp((F32)mouse_dy / 32767.f, -1.f, 1.f);
result.mZoomAmplitude = std::clamp((F32)zoom_value / 32767.f, -1.f, 1.f);
+ result.mScrollAmplitude = std::clamp((F32)scroll_value / 32767.f, -1.f, 1.f);
return result;
}
@@ -2520,8 +2568,9 @@ namespace
|| label == "Mouse left/right" || label == "Mouse up/down";
}
// Avatar, Mouselook, and Captive share the avatar axis actions,
- // and "Zoom +/-" (camera zoom rate; see computeAgentActions()).
- return avatarAxisBridge().count(label) > 0 || label == "Zoom +/-";
+ // plus "Zoom +/-" (camera FOV zoom rate) and "Scroll +/-" (camera
+ // distance); see computeAgentActions().
+ return avatarAxisBridge().count(label) > 0 || label == "Zoom +/-" || label == "Scroll +/-";
}
std::string defaultAxisActionForInput(LLGameControl::AgentControlMode mode, const std::string& input)
diff --git a/indra/llwindow/llgamecontrol.h b/indra/llwindow/llgamecontrol.h
index cd76987943..bbbf8c6774 100644
--- a/indra/llwindow/llgamecontrol.h
+++ b/indra/llwindow/llgamecontrol.h
@@ -597,10 +597,18 @@ public:
// Signed magnitude ([-1, 1]) of "Zoom +/-" (Avatar/Mouselook/Captive only),
// combined with the full-deflection contribution of "Zoom +"/"Zoom -" --
- // drives the camera zoom rate rather than an AGENT_CONTROL_* bit; consumed
- // by LLAgent::applyExternalActions() via LLAgentCamera::setOrbitInKey/
- // setOrbitOutKey(). Positive zooms in (camera moves closer).
+ // drives the camera field-of-view zoom rate rather than an AGENT_CONTROL_*
+ // bit; consumed by LLAgent::applyExternalActions(). Positive zooms in
+ // (narrows the FOV).
F32 mZoomAmplitude { 0.f };
+
+ // Signed magnitude ([-1, 1]) of "Scroll +/-" (Avatar/Captive only),
+ // combined with the full-deflection contribution of "Scroll +"/"Scroll -"
+ // -- drives the camera's distance from the avatar (unlike Zoom, which changes
+ // the FOV). Client-side only: not reported in GameControlData's semantic
+ // data. Consumed by LLAgent::applyExternalActions() via
+ // LLAgentCamera::scrollCamera(). Positive scrolls in (camera moves closer).
+ F32 mScrollAmplitude { 0.f };
};
// Keyboard presses produce action_flags which can be translated into State
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index 7dd11e85bc..443b64ea99 100644
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -5320,6 +5320,18 @@ void LLAgent::applyExternalActions(const LLGameControl::AgentActions& actions)
gSavedSettings.setF32("CameraAngle", camera->getView());
}
+ // actions.mScrollAmplitude ([-1, 1], from "Scroll +/-"/"Scroll +"/"Scroll -")
+ // moves the camera toward/away from the avatar the same way the mousewheel does,
+ // so the third-person/follow-cam distance persists once the avatar moves (unlike
+ // the keyboard's camera in/out keys, which unlock the view from the avatar).
+ // Positive amplitude scrolls in (camera moves closer); scrollCamera()'s clicks
+ // are positive-out like the mousewheel's.
+ if (actions.mScrollAmplitude != 0.f)
+ {
+ constexpr F32 SCROLL_CLICKS_PER_SECOND = 8.f;
+ gAgentCamera.scrollCamera(-actions.mScrollAmplitude * SCROLL_CLICKS_PER_SECOND * g_deltaTime);
+ }
+
if (mExternalActionFlags & AGENT_CONTROL_STOP)
{
setControlFlags(AGENT_CONTROL_STOP);
diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp
index 1d61dcea6a..c3b2d6ad2c 100644
--- a/indra/newview/llagentcamera.cpp
+++ b/indra/newview/llagentcamera.cpp
@@ -2177,14 +2177,52 @@ void LLAgentCamera::handleScrollWheel(S32 clicks)
return;
}
+ // The Shift/Ctrl offset tweaks are wheel-only; everything else is shared with
+ // gamepad "Scroll" input via scrollCamera().
+ if (mFocusOnAvatar && mCameraMode == CAMERA_MODE_THIRD_PERSON && !mCameraAnimating)
+ {
+ LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection();
+ if (!(selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD))
+ {
+ 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;
+ }
+ }
+ }
+
+ scrollCamera((F32)clicks);
+}
+
+void LLAgentCamera::scrollCamera(F32 clicks)
+{
if (mCameraMode == CAMERA_MODE_FOLLOW && getFocusOnAvatar())
{
if (!mFollowCam.getPositionLocked()) // not if the followCam position is locked in place
{
- mFollowCam.zoom(clicks);
- if (mFollowCam.isZoomedToMinimumDistance())
+ // LLFollowCam::zoom() only takes whole clicks (and enforces a minimum
+ // step per call), so accumulate fractional ones until a click is due.
+ mFollowCamScrollClicks += clicks;
+ S32 whole_clicks = (S32)mFollowCamScrollClicks;
+ if (whole_clicks != 0)
{
- changeCameraToMouselook(false);
+ mFollowCamScrollClicks -= (F32)whole_clicks;
+ mFollowCam.zoom(whole_clicks);
+ if (mFollowCam.isZoomedToMinimumDistance())
+ {
+ changeCameraToMouselook(false);
+ }
}
}
}
@@ -2206,22 +2244,6 @@ 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"));
diff --git a/indra/newview/llagentcamera.h b/indra/newview/llagentcamera.h
index b44dd54755..c4db5f3e6a 100644
--- a/indra/newview/llagentcamera.h
+++ b/indra/newview/llagentcamera.h
@@ -178,6 +178,9 @@ public:
void notifyFollowCamParamsCleared();
private:
LLFollowCam mFollowCam; // Ventrella
+ // Fractional scrollCamera() clicks not yet applied to mFollowCam, whose
+ // zoom() only takes whole clicks.
+ F32 mFollowCamScrollClicks { 0.f };
//--------------------------------------------------------------------
// Flycam
@@ -331,6 +334,10 @@ public:
//--------------------------------------------------------------------
public:
void handleScrollWheel(S32 clicks); // Mousewheel driven zoom
+ // Mousewheel-equivalent camera distance change, minus the wheel-only Shift/Ctrl
+ // offset tweaks; 'clicks' may be fractional (e.g. per-frame gamepad "Scroll"
+ // input). Positive moves the camera out, same sign as handleScrollWheel().
+ void scrollCamera(F32 clicks);
void cameraZoomIn(const F32 factor); // Zoom in by fraction of current distance
F32 getCameraZoomFraction(bool get_third_person = false); // Get camera zoom as fraction of minimum and maximum zoom
void setCameraZoomFraction(F32 fraction); // Set camera zoom as fraction of minimum and maximum zoom
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 1039bfcb26..e65c016792 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
@@ -610,6 +610,9 @@
label="Zoom -/+"
value="Zoom +/-"/>
<combo_box.item
+ label="Scroll -/+"
+ value="Scroll +/-"/>
+ <combo_box.item
label="None"
value="None"/>
</combo_box>
@@ -845,6 +848,12 @@
label="Zoom -"
value="Zoom -"/>
<combo_box.item
+ label="Scroll +"
+ value="Scroll +"/>
+ <combo_box.item
+ label="Scroll -"
+ value="Scroll -"/>
+ <combo_box.item
label="None"
value="None"/>
</combo_box>