From 542dc37c97944d27f4111881bdccf4ac3f88f415 Mon Sep 17 00:00:00 2001 From: Angeldark Raymaker Date: Sat, 5 Oct 2024 19:45:18 +0100 Subject: [PATCH] FIRE-30873: Rework undo, add redo Now affected by mirror/symp, undo is now joint specific, and has undo-depth of last 20 changes --- indra/newview/fsfloaterposer.cpp | 65 ++++++- indra/newview/fsfloaterposer.h | 2 + indra/newview/fsposeranimator.cpp | 76 ++++++++ indra/newview/fsposeranimator.h | 22 +++ indra/newview/fsposingmotion.cpp | 5 - indra/newview/fsposingmotion.h | 163 ++++++++++++++++-- indra/newview/fsvirtualtrackpad.cpp | 12 +- indra/newview/fsvirtualtrackpad.h | 7 - .../skins/default/xui/en/floater_poser.xml | 62 ++++--- 9 files changed, 351 insertions(+), 63 deletions(-) diff --git a/indra/newview/fsfloaterposer.cpp b/indra/newview/fsfloaterposer.cpp index 635bd808bf..aab3defd68 100644 --- a/indra/newview/fsfloaterposer.cpp +++ b/indra/newview/fsfloaterposer.cpp @@ -69,6 +69,7 @@ static const std::string POSER_AVATAR_SLIDER_ROLL_NAME = "limb_roll"; // your ea static const std::string POSER_AVATAR_TOGGLEBUTTON_TRACKPADSENSITIVITY = "button_toggleTrackPadSensitivity"; static const std::string POSER_AVATAR_TOGGLEBUTTON_MIRROR = "button_toggleMirrorRotation"; static const std::string POSER_AVATAR_TOGGLEBUTTON_SYMPATH = "button_toggleSympatheticRotation"; +static const std::string POSER_AVATAR_BUTTON_REDO = "button_redo_change"; static const std::string POSER_AVATAR_SLIDER_POSX_NAME = "av_position_inout"; static const std::string POSER_AVATAR_SLIDER_POSY_NAME = "av_position_leftright"; static const std::string POSER_AVATAR_SLIDER_POSZ_NAME = "av_position_updown"; @@ -118,6 +119,7 @@ FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key) mCommitCallbackRegistrar.add("Poser.ToggleAdvancedPanel", boost::bind(&FSFloaterPoser::onToggleAdvancedPanel, this)); mCommitCallbackRegistrar.add("Poser.UndoLastRotation", boost::bind(&FSFloaterPoser::onUndoLastRotation, this)); + mCommitCallbackRegistrar.add("Poser.RedoLastRotation", boost::bind(&FSFloaterPoser::onRedoLastRotation, this)); mCommitCallbackRegistrar.add("Poser.ToggleMirrorChanges", boost::bind(&FSFloaterPoser::onToggleMirrorChange, this)); mCommitCallbackRegistrar.add("Poser.ToggleSympatheticChanges", boost::bind(&FSFloaterPoser::onToggleSympatheticChange, this)); mCommitCallbackRegistrar.add("Poser.ToggleTrackPadSensitivity", boost::bind(&FSFloaterPoser::refreshTrackpadCursor, this)); @@ -936,12 +938,67 @@ void FSFloaterPoser::setRotationChangeButtons(bool togglingMirror, bool toggling void FSFloaterPoser::onUndoLastRotation() { - FSVirtualTrackpad *trackBall = getChild(POSER_AVATAR_TRACKBALL_NAME); - if (!trackBall) + LLVOAvatar* avatar = getUiSelectedAvatar(); + if (!avatar) return; - trackBall->undoLastValue(); - onLimbTrackballChanged(); + if (!_poserAnimator.isPosingAvatar(avatar)) + return; + + auto selectedJoints = getUiSelectedPoserJoints(); + if (selectedJoints.size() < 1) + return; + + bool shouldEnableRedoButton = false; + for (auto item : selectedJoints) + { + bool currentlyPosing = _poserAnimator.isPosingAvatarJoint(avatar, *item); + if (currentlyPosing) + _poserAnimator.undoLastJointRotation(avatar, *item, getUiSelectedBoneDeflectionStyle()); + + shouldEnableRedoButton |= _poserAnimator.canRedoJointRotation(avatar, *item); + } + + enableOrDisableRedoButton(shouldEnableRedoButton); + refreshRotationSliders(); + refreshTrackpadCursor(); +} + +void FSFloaterPoser::onRedoLastRotation() +{ + LLVOAvatar* avatar = getUiSelectedAvatar(); + if (!avatar) + return; + + if (!_poserAnimator.isPosingAvatar(avatar)) + return; + + auto selectedJoints = getUiSelectedPoserJoints(); + if (selectedJoints.size() < 1) + return; + + bool shouldEnableRedoButton = false; + for (auto item : selectedJoints) + { + bool currentlyPosing = _poserAnimator.isPosingAvatarJoint(avatar, *item); + if (currentlyPosing) + _poserAnimator.redoLastJointRotation(avatar, *item, getUiSelectedBoneDeflectionStyle()); + + shouldEnableRedoButton |= _poserAnimator.canRedoJointRotation(avatar, *item); + } + + enableOrDisableRedoButton(shouldEnableRedoButton); + refreshRotationSliders(); + refreshTrackpadCursor(); +} + +void FSFloaterPoser::enableOrDisableRedoButton(bool shouldEnable) +{ + LLButton* redoButton = getChild(POSER_AVATAR_BUTTON_REDO); + if (!redoButton) + return; + + redoButton->setEnabled(shouldEnable); } void FSFloaterPoser::onOpenSetAdvancedPanel() diff --git a/indra/newview/fsfloaterposer.h b/indra/newview/fsfloaterposer.h index a114ac4790..759b382263 100644 --- a/indra/newview/fsfloaterposer.h +++ b/indra/newview/fsfloaterposer.h @@ -210,6 +210,8 @@ class FSFloaterPoser : public LLFloater void onToggleSympatheticChange(); void setRotationChangeButtons(bool mirror, bool sympathetic); void onUndoLastRotation(); + void onRedoLastRotation(); + void enableOrDisableRedoButton(bool shouldEnable); void onPoseStartStop(); void onLimbTrackballChanged(); void onLimbYawPitchRollChanged(); diff --git a/indra/newview/fsposeranimator.cpp b/indra/newview/fsposeranimator.cpp index a5f6109bc8..52b8a72594 100644 --- a/indra/newview/fsposeranimator.cpp +++ b/indra/newview/fsposeranimator.cpp @@ -92,11 +92,87 @@ void FSPoserAnimator::resetAvatarJoint(LLVOAvatar *avatar, FSPoserJoint joint) FSPosingMotion::FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); if (!jointPose) + return; jointPose->setTargetPosition(jointPose->getBeginningPosition()); jointPose->setTargetRotation(jointPose->getBeginningRotation()); } +void FSPoserAnimator::undoLastJointRotation(LLVOAvatar* avatar, FSPoserJoint joint, E_BoneDeflectionStyles style) +{ + if (!isAvatarSafeToUse(avatar)) + return; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return; + + if (posingMotion->isStopped()) + return; + + FSPosingMotion::FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); + if (!jointPose) + return; + + jointPose->undoLastRotationSet(); + + if (style == NONE) + return; + + FSPosingMotion::FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint.mirrorJointName()); + if (!oppositeJointPose) + return; + + oppositeJointPose->undoLastRotationSet(); +} + +bool FSPoserAnimator::canRedoJointRotation(LLVOAvatar* avatar, FSPoserJoint joint) +{ + if (!isAvatarSafeToUse(avatar)) + return false; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return false; + + if (posingMotion->isStopped()) + return false; + + FSPosingMotion::FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); + if (!jointPose) + return false; + + return jointPose->canRedo(); +} + +void FSPoserAnimator::redoLastJointRotation(LLVOAvatar* avatar, FSPoserJoint joint, E_BoneDeflectionStyles style) +{ + if (!isAvatarSafeToUse(avatar)) + return; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return; + + if (posingMotion->isStopped()) + return; + + FSPosingMotion::FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); + if (!jointPose) + return; + + jointPose->redoLastRotationSet(); + + if (style == NONE) + return; + + FSPosingMotion::FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint.mirrorJointName()); + if (!oppositeJointPose) + return; + + oppositeJointPose->redoLastRotationSet(); +} + LLVector3 FSPoserAnimator::getJointPosition(LLVOAvatar *avatar, FSPoserJoint joint) { LLVector3 pos; diff --git a/indra/newview/fsposeranimator.h b/indra/newview/fsposeranimator.h index 90a38bde0f..69c772a25a 100644 --- a/indra/newview/fsposeranimator.h +++ b/indra/newview/fsposeranimator.h @@ -252,6 +252,28 @@ public: /// The joint to be reset. void resetAvatarJoint(LLVOAvatar *avatar, FSPoserJoint joint); + /// + /// Undoes the last applied rotation to the supplied PoserJoint. + /// + /// The avatar having the joint to which we refer. + /// The joint with the rotation to undo. + void undoLastJointRotation(LLVOAvatar* avatar, FSPoserJoint joint, E_BoneDeflectionStyles style); + + /// + /// Determines if a redo action is currently permitted for the supplied joint. + /// + /// The avatar having the joint to which we refer. + /// The joint to query. + /// True if a redo action is available, otherwise false. + bool canRedoJointRotation(LLVOAvatar* avatar, FSPoserJoint joint); + + /// + /// Re-does the last undone rotation to the supplied PoserJoint. + /// + /// The avatar having the joint to which we refer. + /// The joint with the rotation to redo. + void redoLastJointRotation(LLVOAvatar* avatar, FSPoserJoint joint, E_BoneDeflectionStyles style); + /// /// Gets the position of a joint for the supplied avatar. /// diff --git a/indra/newview/fsposingmotion.cpp b/indra/newview/fsposingmotion.cpp index a726d2233c..4f0ea13899 100644 --- a/indra/newview/fsposingmotion.cpp +++ b/indra/newview/fsposingmotion.cpp @@ -63,7 +63,6 @@ bool FSPosingMotion::onUpdate(F32 time, U8* joint_mask) LLQuaternion currentRotation; LLVector3 currentPosition; LLVector3 targetPosition; - F32 poseTransitionAmount = 0.0f; // when we change from one position/rotation to another, we do so over time; this documents the amount of transition. for (FSJointPose jointPose : _jointPoses) { @@ -76,7 +75,6 @@ bool FSPosingMotion::onUpdate(F32 time, U8* joint_mask) targetRotation = jointPose.getTargetRotation(); targetPosition = jointPose.getTargetPosition(); - poseTransitionAmount = llclamp(_interpolationTimer.getElapsedTimeF32() / _interpolationTime, 0.0f, 1.0f); if (currentPosition != targetPosition) { currentPosition = lerp(currentPosition, targetPosition, _interpolationTime); @@ -90,9 +88,6 @@ bool FSPosingMotion::onUpdate(F32 time, U8* joint_mask) } } - if (_interpolationTimer.getStarted() && poseTransitionAmount >= 1.0f) - _interpolationTimer.stop(); - return true; } diff --git a/indra/newview/fsposingmotion.h b/indra/newview/fsposingmotion.h index 274cfb30fe..a337e28651 100644 --- a/indra/newview/fsposingmotion.h +++ b/indra/newview/fsposingmotion.h @@ -24,8 +24,8 @@ * $/LicenseInfo$ */ -#ifndef LL_FSPOSINGMOTION_H -#define LL_FSPOSINGMOTION_H +#ifndef FS_POSINGMOTION_H +#define FS_POSINGMOTION_H //----------------------------------------------------------------------------- // Header files @@ -52,12 +52,65 @@ public: /// class FSJointPose { - std::string _jointName = ""; // expected to be a match to LLJoint.getName() for a joint implementation. - LLQuaternion _beginningRotation; - LLQuaternion _targetRotation; + const size_t MaximumUndoQueueLength = 20; + + /// + /// The constant time interval, in seconds, + /// + std::chrono::duration const _undoUpdateInterval = std::chrono::duration(0.3); + + std::string _jointName = ""; // expected to be a match to LLJoint.getName() for a joint implementation. + LLPointer _jointState; + + LLQuaternion _targetRotation; + LLQuaternion _beginningRotation; + std::deque _lastSetRotations; + size_t _undoneRotationIndex = 0; + std::chrono::system_clock::time_point _timeLastUpdatedRotation = std::chrono::system_clock::now(); + LLVector3 _targetPosition; LLVector3 _beginningPosition; - LLPointer _jointState; + std::deque _lastSetPositions; + size_t _undonePositionIndex = 0; + std::chrono::system_clock::time_point _timeLastUpdatedPosition = std::chrono::system_clock::now(); + + /// + /// Adds a last position to the deque. + /// + void addLastPositionToUndo() + { + if (_undonePositionIndex > 0) + { + for (int i = 0; i < _undonePositionIndex; i++) + _lastSetPositions.pop_front(); + + _undonePositionIndex = 0; + } + + _lastSetPositions.push_front(_targetPosition); + + while (_lastSetPositions.size() > MaximumUndoQueueLength) + _lastSetPositions.pop_back(); + } + + /// + /// Adds a last rotation to the deque. + /// + void addLastRotationToUndo() + { + if (_undoneRotationIndex > 0) + { + for (int i = 0; i < _undoneRotationIndex; i++) + _lastSetRotations.pop_front(); + + _undoneRotationIndex = 0; + } + + _lastSetRotations.push_front(_targetRotation); + + while (_lastSetRotations.size() > MaximumUndoQueueLength) + _lastSetRotations.pop_back(); + } public: /// @@ -65,6 +118,8 @@ public: /// std::string jointName() const { return _jointName; } + bool canRedo() const { return _undoneRotationIndex > 0; } + /// /// Gets the position the joint was in when the animation was initialized. /// @@ -78,7 +133,15 @@ public: /// /// Sets the position the animator wishes the joint to be in. /// - void setTargetPosition(const LLVector3& pos) { _targetPosition.set(pos) ; } + void setTargetPosition(const LLVector3& pos) + { + auto timeIntervalSinceLastRotationChange = std::chrono::system_clock::now() - _timeLastUpdatedPosition; + if (timeIntervalSinceLastRotationChange > _undoUpdateInterval) + addLastPositionToUndo(); + + _timeLastUpdatedPosition = std::chrono::system_clock::now(); + _targetPosition.set(pos); + } /// /// Gets the rotation the joint was in when the animation was initialized. @@ -93,7 +156,82 @@ public: /// /// Sets the rotation the animator wishes the joint to be in. /// - void setTargetRotation(const LLQuaternion& rot) { _targetRotation.set(rot); } + void setTargetRotation(const LLQuaternion& rot) + { + auto timeIntervalSinceLastRotationChange = std::chrono::system_clock::now() - _timeLastUpdatedRotation; + if (timeIntervalSinceLastRotationChange > _undoUpdateInterval) + addLastRotationToUndo(); + + _timeLastUpdatedRotation = std::chrono::system_clock::now(); + _targetRotation.set(rot); + } + + /// + /// Undoes the last position set, if any. + /// + void undoLastPositionSet() + { + if (_lastSetPositions.empty()) + return; + + if (_undonePositionIndex == 0) // at the top of the queue add the current + addLastPositionToUndo(); + + _undonePositionIndex++; + _undonePositionIndex = llclamp(_undonePositionIndex, 0, _lastSetPositions.size() - 1); + _targetPosition.set(_lastSetPositions[_undonePositionIndex]); + } + + /// + /// Undoes the last position set, if any. + /// + void redoLastPositionSet() + { + if (_lastSetPositions.empty()) + return; + + _undonePositionIndex--; + _undonePositionIndex = llclamp(_undonePositionIndex, 0, _lastSetPositions.size() - 1); + + _targetPosition.set(_lastSetPositions[_undonePositionIndex]); + if (_undonePositionIndex == 0) + _lastSetRotations.pop_front(); + } + + /// + /// Undoes the last rotation set, if any. + /// Ordinarily the queue does not contain the current rotation, because we rely on time to add, and not button-up. + /// When we undo, if we are at the top of the queue, we need to add the current rotation so we can redo back to it. + /// Thus when we start undoing, _undoneRotationIndex points at the current rotation. + /// + void undoLastRotationSet() + { + if (_lastSetRotations.empty()) + return; + + if (_undoneRotationIndex == 0) // at the top of the queue add the current + addLastRotationToUndo(); + + _undoneRotationIndex++; + _undoneRotationIndex = llclamp(_undoneRotationIndex, 0, _lastSetRotations.size() - 1); + _targetRotation.set(_lastSetRotations[_undoneRotationIndex]); + } + + /// + /// Undoes the last rotation set, if any. + /// + void redoLastRotationSet() + { + if (_lastSetRotations.empty()) + return; + + _undoneRotationIndex--; + _undoneRotationIndex = llclamp(_undoneRotationIndex, 0, _lastSetRotations.size() - 1); + + _targetRotation.set(_lastSetRotations[_undoneRotationIndex]); + if (_undoneRotationIndex == 0) + _lastSetRotations.pop_front(); + } /// /// Gets the pointer to the jointstate for the joint this represents. @@ -193,19 +331,16 @@ private: /// /// The amount of time, in seconds, we use for transitioning between one animation-state to another; this affects the 'fluidity' /// of motion between changed to a joint. + /// Use caution making this larger than the perceptual amount of time between adjusting a joint and then choosing to use 'undo'. + /// Undo-function waits this amount of time after the last user-incited joint change to add a 'restore point'. /// const F32 _interpolationTime = 0.25f; - /// - /// The timer used to smoothly transition from one joint position or rotation to another. - /// - LLFrameTimer _interpolationTimer; - /// /// The collection of joint poses this motion uses to pose the joints of the character this is animating. /// std::vector _jointPoses; }; -#endif // LL_LLKEYFRAMEMOTION_H +#endif // FS_POSINGMOTION_H diff --git a/indra/newview/fsvirtualtrackpad.cpp b/indra/newview/fsvirtualtrackpad.cpp index ac186e7142..fb938ad71d 100644 --- a/indra/newview/fsvirtualtrackpad.cpp +++ b/indra/newview/fsvirtualtrackpad.cpp @@ -57,8 +57,8 @@ FSVirtualTrackpad::FSVirtualTrackpad(const FSVirtualTrackpad::Params &p) mInfiniteScrollMode(p.infinite_scroll_mode) { LLRect border_rect = getLocalRect(); - _valueX = _lastValueX = _pinchValueX = _lastPinchValueX = border_rect.getCenterX(); - _valueY = _lastValueY = _pinchValueY = _lastPinchValueY = border_rect.getCenterY(); + _valueX = _pinchValueX = border_rect.getCenterX(); + _valueY = _pinchValueY = border_rect.getCenterY(); _thumbClickOffsetX = _thumbClickOffsetY = _pinchThumbClickOffsetX = _pinchThumbClickOffsetY = 0; _valueWheelClicks = _pinchValueWheelClicks = 0; @@ -246,10 +246,6 @@ void FSVirtualTrackpad::setValue(F32 x, F32 y) { convertNormalizedToPixelPos(x, void FSVirtualTrackpad::setPinchValue(F32 x, F32 y) { convertNormalizedToPixelPos(x, y, &_pinchValueX, &_pinchValueY); } -void FSVirtualTrackpad::undoLastValue() { setValueAndCommit(_lastValueX, _lastValueY); } - -void FSVirtualTrackpad::undoLastSetPinchValue() { setPinchValueAndCommit(_lastPinchValueX, _lastPinchValueY); } - void FSVirtualTrackpad::setValueAndCommit(const S32 x, const S32 y) { _valueX = x; @@ -405,8 +401,6 @@ bool FSVirtualTrackpad::handleMouseDown(S32 x, S32 y, MASK mask) determineThumbClickError(x, y); updateClickErrorIfInfiniteScrolling(); _valueWheelClicks = 0; - _lastValueX = _valueX; - _lastValueY = _valueY; _wheelClicksSinceMouseDown = 0; gFocusMgr.setMouseCapture(this); @@ -440,8 +434,6 @@ bool FSVirtualTrackpad::handleRightMouseDown(S32 x, S32 y, MASK mask) determineThumbClickErrorForPinch(x, y); updateClickErrorIfInfiniteScrollingForPinch(); _pinchValueWheelClicks = 0; - _lastPinchValueX = _pinchValueX; - _lastPinchValueY = _pinchValueY; _wheelClicksSinceMouseDown = 0; doingPinchMode = true; gFocusMgr.setMouseCapture(this); diff --git a/indra/newview/fsvirtualtrackpad.h b/indra/newview/fsvirtualtrackpad.h index 45b0c94473..c37fe6406f 100644 --- a/indra/newview/fsvirtualtrackpad.h +++ b/indra/newview/fsvirtualtrackpad.h @@ -70,9 +70,6 @@ public: /// The y-axis (top/bottom) position to set; expected range 1..-1; top = 1 void setValue(F32 x, F32 y); - void undoLastValue(); - void undoLastSetPinchValue(); - /// /// Sets the position of the second cursor. /// @@ -140,14 +137,10 @@ private: S32 _valueX; S32 _valueY; S32 _valueWheelClicks; - S32 _lastValueX; - S32 _lastValueY; S32 _pinchValueX; S32 _pinchValueY; S32 _pinchValueWheelClicks; - S32 _lastPinchValueX; - S32 _lastPinchValueY; /// /// Rolling the wheel is pioneering a 'delta' mode: where changes are handled by the control-owner in a relative way. diff --git a/indra/newview/skins/default/xui/en/floater_poser.xml b/indra/newview/skins/default/xui/en/floater_poser.xml index a3b5ea0eef..42105c2f6c 100644 --- a/indra/newview/skins/default/xui/en/floater_poser.xml +++ b/indra/newview/skins/default/xui/en/floater_poser.xml @@ -612,16 +612,47 @@ width="565"> follows="top|left" layout="topleft" label="" - image_overlay="Refresh_Off" + image_overlay="Script_Undo" image_unselected="Toolbar_Middle_Off" - name="refresh_avatars" + name="undo_change" tool_tip="Undo the last rotation change" - width="20" + width="18" top_pad="0" left="4"> + + - @@ -685,7 +701,7 @@ width="565"> height="21" is_toggle="true" layout="topleft" - label="Symp." + label="Sym." image_hover_unselected="Toolbar_Middle_Over" image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" @@ -697,7 +713,7 @@ width="565"> tool_tip="Change the opposite joint, but in the same way." left_pad="1" top_delta="0" - width="45" > + width="38" >