summaryrefslogtreecommitdiff
path: root/indra/newview/llagentcamera.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llagentcamera.cpp')
-rw-r--r--indra/newview/llagentcamera.cpp551
1 files changed, 535 insertions, 16 deletions
diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp
index 0fe5f622e3..13878f9448 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<bool> 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())
{
@@ -2095,14 +2162,67 @@ LLVector3 LLAgentCamera::getAvatarRootPosition()
//-----------------------------------------------------------------------------
void LLAgentCamera::handleScrollWheel(S32 clicks)
{
+ if (isUsingFlycam())
+ {
+ // Flycam preempts updateCamera() entirely (see the comment on
+ // mUsingFlycam in llagentcamera.h), so none of the mCameraMode-based
+ // zoom logic below applies to it; drive the flycam's own FOV-based
+ // "zoom" (LLFlycam::mView, also fed by the FLYCAM_ZOOM game-control
+ // channel in updateFlycam()) instead.
+ // Negative 'clicks' (forward/up scroll) narrows the FOV to zoom in,
+ // matching the sign convention used by the non-flycam paths below.
+ constexpr F32 FLYCAM_SCROLL_ZOOM_FACTOR_PER_CLICK = 0.9f;
+ F32 zoom_factor = (F32)pow(FLYCAM_SCROLL_ZOOM_FACTOR_PER_CLICK, -clicks);
+ mFlycam.setView(mFlycam.getView() * zoom_factor);
+ 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);
+ }
}
}
}
@@ -2181,7 +2301,7 @@ void LLAgentCamera::resetCamera()
void LLAgentCamera::changeCameraToMouselook(bool animate)
{
if (!gSavedSettings.getBOOL("EnableMouselook")
- || LLViewerJoystick::getInstance()->getOverrideCamera())
+ || gAgent.isCameraExternallyDriven())
{
return;
}
@@ -2216,7 +2336,6 @@ void LLAgentCamera::changeCameraToMouselook(bool animate)
updateLastCamera();
mCameraMode = CAMERA_MODE_MOUSELOOK;
- const U32 old_flags = gAgent.getControlFlags();
gAgent.setControlFlags(AGENT_CONTROL_MOUSELOOK);
if (animate)
@@ -2237,7 +2356,7 @@ void LLAgentCamera::changeCameraToMouselook(bool animate)
//-----------------------------------------------------------------------------
void LLAgentCamera::changeCameraToDefault()
{
- if (LLViewerJoystick::getInstance()->getOverrideCamera())
+ if (gAgent.isCameraExternallyDriven())
{
return;
}
@@ -2263,7 +2382,7 @@ void LLAgentCamera::changeCameraToDefault()
//-----------------------------------------------------------------------------
void LLAgentCamera::changeCameraToFollow(bool animate)
{
- if (LLViewerJoystick::getInstance()->getOverrideCamera())
+ if (gAgent.isCameraExternallyDriven())
{
return;
}
@@ -2319,7 +2438,7 @@ void LLAgentCamera::changeCameraToFollow(bool animate)
//-----------------------------------------------------------------------------
void LLAgentCamera::changeCameraToThirdPerson(bool animate)
{
- if (LLViewerJoystick::getInstance()->getOverrideCamera())
+ if (gAgent.isCameraExternallyDriven())
{
return;
}
@@ -2390,7 +2509,7 @@ void LLAgentCamera::changeCameraToThirdPerson(bool animate)
//-----------------------------------------------------------------------------
void LLAgentCamera::changeCameraToCustomizeAvatar()
{
- if (LLViewerJoystick::getInstance()->getOverrideCamera() || !isAgentAvatarValid())
+ if (gAgent.isCameraExternallyDriven() || !isAgentAvatarValid())
{
return;
}
@@ -2471,6 +2590,7 @@ void LLAgentCamera::switchCameraPreset(ECameraPreset preset)
resetPanDiff();
resetOrbitDiff();
+ resetCameraRoll();
gSavedSettings.setU32("CameraPresetType", mCameraPreset);
}
@@ -2675,9 +2795,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 );
@@ -2888,6 +3008,403 @@ 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();
+ // Flycam starts from the current view, which in mouselook is inside the
+ // avatar's head: keep the avatar hidden until it's out of view.
+ mFlycamHidingAvatar = cameraMouselook();
+ }
+ else
+ {
+ bool was_revealed = !mFlycamHidingAvatar;
+ mFlycamHidingAvatar = false;
+ if (was_revealed && cameraMouselook() && isAgentAvatarValid())
+ {
+ // Back to the mouselook view: re-hide the attachments
+ // updateFlycamAvatarVisibility() revealed.
+ gAgentAvatarp->updateAttachmentVisibility(CAMERA_MODE_MOUSELOOK);
+ }
+ }
+}
+
+void LLAgentCamera::updateFlycamCursorVisibility(LLGameControl::AgentControlMode mode)
+{
+ // Seconds until the cursor hides again after the mouse moves...
+ constexpr F64 FLYCAM_CURSOR_MOUSE_TIMEOUT = 5.0;
+ // ...shortened to at most this while the flycam camera itself is moving.
+ constexpr F64 FLYCAM_CURSOR_CAMERA_TIMEOUT = 1.0;
+ // Tolerance when comparing mouse-move timestamps reconstructed from
+ // LLUI's F32 mouse idle time.
+ constexpr F64 MOUSE_MOVE_EPSILON = 0.005;
+
+ if (!gViewerWindow)
+ {
+ return;
+ }
+
+ // Mouselook (which flycam can be engaged from) manages its own hidden
+ // cursor, so leave the cursor alone while it is active.
+ bool mouselook = cameraMouselook();
+ bool want_hiding = mode == LLGameControl::CONTROL_MODE_FLYCAM
+ && LLGameControl::isFlycamHideCursorEnabled()
+ && !mouselook;
+
+ if (!want_hiding)
+ {
+ if (mFlycamCursorHidden && !mouselook)
+ {
+ gViewerWindow->showCursor();
+ }
+ mFlycamCursorHiding = false;
+ mFlycamCursorHidden = false;
+ return;
+ }
+
+ F64 now = LLFrameTimer::getElapsedSeconds();
+ // LLViewerWindow::handleMouseMove() resets LLUI's mouse idle timer
+ // whenever the mouse moves, so this is when it last moved.
+ F64 mouse_move_time = now - (F64)LLUI::getInstance()->getMouseIdleTime();
+
+ // Detect flycam camera motion by comparing its transform (global, so
+ // unaffected by region-origin rebases) and view angle against the
+ // previous frame's.
+ LLVector3d flycam_position;
+ LLQuaternion flycam_rotation;
+ mFlycam.getTransform(flycam_position, flycam_rotation);
+ F32 flycam_view = mFlycam.getView();
+ bool flycam_moved = flycam_position != mFlycamCursorLastPosition
+ || flycam_rotation != mFlycamCursorLastRotation
+ || flycam_view != mFlycamCursorLastView;
+ mFlycamCursorLastPosition = flycam_position;
+ mFlycamCursorLastRotation = flycam_rotation;
+ mFlycamCursorLastView = flycam_view;
+
+ if (!mFlycamCursorHiding)
+ {
+ // Just entered FlyCam mode: hide immediately.
+ mFlycamCursorHiding = true;
+ mFlycamCursorHidden = true;
+ mFlycamCursorMouseMoveTime = mouse_move_time;
+ gViewerWindow->hideCursor();
+ return;
+ }
+
+ if (mFlycamCursorHidden && !gViewerWindow->getCursorHidden())
+ {
+ // Something else re-showed the cursor behind our back: treat it as
+ // visible, with a fresh timeout.
+ mFlycamCursorHidden = false;
+ mFlycamCursorHideTime = now + FLYCAM_CURSOR_MOUSE_TIMEOUT;
+ }
+
+ if (mouse_move_time > mFlycamCursorMouseMoveTime + MOUSE_MOVE_EPSILON)
+ {
+ // The mouse moved: show the cursor (if hidden) and restart the timeout.
+ mFlycamCursorMouseMoveTime = mouse_move_time;
+ mFlycamCursorHideTime = mouse_move_time + FLYCAM_CURSOR_MOUSE_TIMEOUT;
+ if (mFlycamCursorHidden)
+ {
+ mFlycamCursorHidden = false;
+ gViewerWindow->showCursor();
+ }
+ }
+
+ if (mFlycamCursorHidden)
+ {
+ return;
+ }
+
+ if (flycam_moved)
+ {
+ mFlycamCursorHideTime = llmin(mFlycamCursorHideTime, now + FLYCAM_CURSOR_CAMERA_TIMEOUT);
+ }
+
+ if (now >= mFlycamCursorHideTime)
+ {
+ mFlycamCursorHidden = true;
+ gViewerWindow->hideCursor();
+ }
+}
+
+bool LLAgentCamera::isHidingAvatarForFirstPerson() const
+{
+ return cameraMouselook() && (!mUsingFlycam || mFlycamHidingAvatar);
+}
+
+//-----------------------------------------------------------------------------
+// updateFlycamAvatarVisibility()
+//-----------------------------------------------------------------------------
+void LLAgentCamera::updateFlycamAvatarVisibility()
+{
+ if (!mFlycamHidingAvatar)
+ {
+ return;
+ }
+ if (!cameraMouselook() || !isAgentAvatarValid())
+ {
+ // Nothing left to hide from (e.g. the camera mode changed underneath).
+ mFlycamHidingAvatar = false;
+ return;
+ }
+
+ // Bounding sphere of the avatar, including its attachments.
+ LLVector3 center = gAgentAvatarp->getRenderPosition();
+ F32 radius = 0.f;
+ if (gAgentAvatarp->mDrawable.notNull())
+ {
+ const LLVector4a* extents = gAgentAvatarp->mDrawable->getSpatialExtents();
+ LLVector3 min(extents[0].getF32ptr());
+ LLVector3 max(extents[1].getF32ptr());
+ center = (min + max) * 0.5f;
+ radius = (max - min).magVec() * 0.5f;
+ }
+ // Pad for stale extents and animation beyond the bounds.
+ constexpr F32 MIN_AVATAR_RADIUS = 1.f; // meters
+ constexpr F32 AVATAR_RADIUS_PADDING = 1.25f;
+ radius = llmax(radius * AVATAR_RADIUS_PADDING, MIN_AVATAR_RADIUS);
+
+ // Conservative test: the avatar is out of view if its bounding sphere lies
+ // entirely outside the cone that encloses the view frustum (half-angle to
+ // the frustum's corners).
+ const LLViewerCamera* camera = LLViewerCamera::getInstance();
+ LLVector3 to_avatar = center - camera->getOrigin();
+ F32 distance = to_avatar.normVec();
+ if (distance <= radius)
+ {
+ return; // camera is inside the bounds
+ }
+ F32 tan_half_view = tanf(0.5f * camera->getView());
+ F32 frustum_half_angle = atanf(tan_half_view * sqrtf(1.f + camera->getAspect() * camera->getAspect()));
+ F32 avatar_half_angle = asinf(radius / distance);
+ F32 angle_to_avatar = acosf(llclamp(to_avatar * camera->getAtAxis(), -1.f, 1.f));
+ if (angle_to_avatar > frustum_half_angle + avatar_half_angle)
+ {
+ mFlycamHidingAvatar = false;
+ gAgentAvatarp->updateAttachmentVisibility(getCameraMode());
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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());
+
+ updateFlycamAvatarVisibility();
+}
+
+//-----------------------------------------------------------------------------
+// updateFlycam()
+//-----------------------------------------------------------------------------
+void LLAgentCamera::updateFlycam(F32 delta_time)
+{
+ // Note: flycam_inputs arrive in range [-1,1]
+ std::vector<F32> flycam_inputs;
+ U32 flycam_misc_actions = 0;
+ U32 flycam_modifiers = 0;
+ LLGameControl::getFlycamInputs(flycam_inputs, flycam_misc_actions, flycam_modifiers);
+
+ // 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;
+ }
+
+ 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
+ // 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_unroll_requested = mFlycamKeyboardUnrollRequested;
+ mFlycamKeyboardUnrollRequested = false;
+
+ // Unroll only undoes roll, and roll is already kept at zero every frame
+ // (LLFlycam::setRollRate()/integrate()) whenever roll is disallowed, so
+ // treat an Unroll request as a no-op rather than kicking off a pointless
+ // startReset() lerp while "Allow Roll" is unchecked.
+ if (LLGameControl::isFlycamRollAllowed()
+ && ((flycam_misc_actions & LLGameControl::FLYCAM_ACTION_UNROLL) || flycam_key_unroll_requested))
+ {
+ // 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;
+ LLQuaternion current_rotation;
+ mFlycam.getTransform(current_position, current_rotation);
+
+ LLMatrix3 level(current_rotation);
+ 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);
+ 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
+ // 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);
+ }
+
+ // Must precede setLinearVelocity()/setPitchRate()/setYawRate()/setRollRate():
+ // those setters apply mSpeedFactor/mAllowRoll immediately rather than at
+ // integrate() time.
+ mFlycam.setSpeedFactor(LLGameControl::getSpeedFactor(LLGameControl::getModeName(LLGameControl::CONTROL_MODE_FLYCAM)));
+ mFlycam.setAllowRoll(LLGameControl::isFlycamRollAllowed());
+
+ 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(yaw_input);
+ 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());
+
+ updateFlycamAvatarVisibility();
+}
+
bool LLAgentCamera::setPointAt(EPointAtType target_type, LLViewerObject *object, LLVector3 position)
{
// disallow pointing at attachments and avatars
@@ -2950,6 +3467,8 @@ void LLAgentCamera::clearOrbitKeys()
mOrbitDownKey = 0.f;
mOrbitInKey = 0.f;
mOrbitOutKey = 0.f;
+ mRollLeftKey = 0.f;
+ mRollRightKey = 0.f;
}
void LLAgentCamera::clearPanKeys()