diff options
| author | Andrew Meadows <leviathan@lindenlab.com> | 2026-10-16 19:01:06 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-10-16 19:01:06 -0700 |
| commit | 923f645a923e7f061f7ff2790d8510aa4b5ced36 (patch) | |
| tree | 0f1b8a6434c6ea5fb2c80b52f4afa695b4383d93 /indra/newview/llagentcamera.cpp | |
| parent | 51f2817d3ab3b9454ec2848042420dfcc17d7bb6 (diff) | |
| parent | d4270de9eac1f79f267c2fae75c57c2464dc6bbd (diff) | |
Merge pull request #6202 from secondlife/project/game-control
Diffstat (limited to 'indra/newview/llagentcamera.cpp')
| -rw-r--r-- | indra/newview/llagentcamera.cpp | 551 |
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() |
