From 743aca7ee4c7eede8da0dcf142327d0f3b31aa94 Mon Sep 17 00:00:00 2001 From: Angeldark Raymaker Date: Mon, 4 Aug 2025 22:20:35 +0100 Subject: [PATCH] FIRE-35686: Update BVH save state - add optional unlock for mPelvis (for partial-pose BVH) - add settings option for mPelvis BVH lock state - T-pose now only BVH locks Body tab; face and hands are now BVH-unlocked by default --- indra/newview/app_settings/settings.xml | 11 +++++++ indra/newview/fsfloaterposer.cpp | 13 +++++++-- indra/newview/fsfloaterposer.h | 1 + indra/newview/fsjointpose.cpp | 9 +++--- indra/newview/fsjointpose.h | 29 +++++++++++-------- indra/newview/fsposeranimator.cpp | 25 ++++++++++++---- indra/newview/fsposeranimator.h | 3 +- indra/newview/fsposingmotion.cpp | 5 ++++ indra/newview/fsposingmotion.h | 9 ++++++ .../skins/default/xui/en/floater_fs_poser.xml | 11 +++++++ 10 files changed, 92 insertions(+), 24 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 2bd9e8a094..450bc40028 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8100,6 +8100,17 @@ Value 0 + FSPoserPelvisUnlockedForBvhSave + + Comment + Whether the mPelvis joint should be position/rotationally locked when a BVH is created. + Persist + 1 + Type + Boolean + Value + 0 + FSPoserOnSaveConfirmOverwrite Comment diff --git a/indra/newview/fsfloaterposer.cpp b/indra/newview/fsfloaterposer.cpp index d49b9e6a2e..7594f57960 100644 --- a/indra/newview/fsfloaterposer.cpp +++ b/indra/newview/fsfloaterposer.cpp @@ -62,6 +62,7 @@ constexpr std::string_view POSER_TRACKPAD_SENSITIVITY_SAVE_KEY = "FSPoserTrackpa constexpr std::string_view POSER_STOPPOSINGWHENCLOSED_SAVE_KEY = "FSPoserStopPosingWhenClosed"; constexpr std::string_view POSER_SAVEEXTERNALFORMAT_SAVE_KEY = "FSPoserSaveExternalFileAlso"; constexpr std::string_view POSER_SAVECONFIRMREQUIRED_SAVE_KEY = "FSPoserOnSaveConfirmOverwrite"; +constexpr std::string_view POSER_UNLOCKPELVISINBVH_SAVE_KEY = "FSPoserPelvisUnlockedForBvhSave"; constexpr char ICON_SAVE_OK[] = "icon_rotation_is_own_work"; constexpr char ICON_SAVE_FAILED[] = "icon_save_failed_button"; @@ -212,6 +213,8 @@ bool FSFloaterPoser::postBuild() mMiscJointsPnl = getChild("misc_joints_panel"); mCollisionVolumesPnl = getChild("collision_volumes_panel"); + mUnlockPelvisInBvhSaveCbx = getChild("unlock_pelvis_for_bvh_save_checkbox"); + mUnlockPelvisInBvhSaveCbx->setVisible(getSavingToBvh()); mAlsoSaveBvhCbx = getChild("also_save_bvh_checkbox"); mAlsoSaveBvhCbx->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickSavingToBvh(); }); @@ -2587,7 +2590,9 @@ void FSFloaterPoser::writeBvhMotion(llofstream* fileStream, LLVOAvatar* avatar, if (!joint) return; - auto rotation = mPoserAnimator.getJointExportRotation(avatar, *joint); + bool lockPelvisJoint = gSavedSettings.getBOOL(POSER_UNLOCKPELVISINBVH_SAVE_KEY); + + auto rotation = mPoserAnimator.getJointExportRotation(avatar, *joint, !lockPelvisJoint); auto position = mPoserAnimator.getJointPosition(avatar, *joint); switch (joint->boneType()) @@ -2684,7 +2689,11 @@ bool FSFloaterPoser::getSavingToBvh() return gSavedSettings.getBOOL(POSER_SAVEEXTERNALFORMAT_SAVE_KEY); } -void FSFloaterPoser::onClickSavingToBvh() { refreshTextHighlightingOnJointScrollLists(); } +void FSFloaterPoser::onClickSavingToBvh() +{ + mUnlockPelvisInBvhSaveCbx->setVisible(getSavingToBvh()); + refreshTextHighlightingOnJointScrollLists(); +} void FSFloaterPoser::onClickLockWorldRotBtn() { diff --git a/indra/newview/fsfloaterposer.h b/indra/newview/fsfloaterposer.h index 6ff2d2b98c..69c96a769c 100644 --- a/indra/newview/fsfloaterposer.h +++ b/indra/newview/fsfloaterposer.h @@ -513,6 +513,7 @@ public: LLPanel* mPosesLoadSavePnl{ nullptr }; LLCheckBoxCtrl* mAlsoSaveBvhCbx{ nullptr }; + LLCheckBoxCtrl* mUnlockPelvisInBvhSaveCbx{ nullptr }; LLUICtrl* mTrackpadSensitivitySpnr{ nullptr }; LLUICtrl* mYawSpnr{ nullptr }; diff --git a/indra/newview/fsjointpose.cpp b/indra/newview/fsjointpose.cpp index 2e4bab90ba..e1acbce4d0 100644 --- a/indra/newview/fsjointpose.cpp +++ b/indra/newview/fsjointpose.cpp @@ -62,7 +62,7 @@ void FSJointPose::setPublicRotation(bool zeroBase, const LLQuaternion& rot) addStateToUndo(FSJointState(mCurrentState)); if (zeroBase) - zeroBaseRotation(); + zeroBaseRotation(true); mCurrentState.mRotation.set(rot); } @@ -208,12 +208,13 @@ void FSJointPose::reflectRotation() mCurrentState.reflectRotation(); } -void FSJointPose::zeroBaseRotation() +void FSJointPose::zeroBaseRotation(bool lockInBvh) { if (mIsCollisionVolume) return; mCurrentState.zeroBaseRotation(); + mCurrentState.mUserSpecifiedBaseZero = lockInBvh; } bool FSJointPose::isBaseRotationZero() const @@ -233,12 +234,12 @@ void FSJointPose::purgeUndoQueue() mLastSetJointStates.clear(); } -bool FSJointPose::userHaseSetBaseRotationToZero() const +bool FSJointPose::userHasSetBaseRotationToZero() const { if (mIsCollisionVolume) return false; - return mCurrentState.userSetBaseRotationToZero(); + return mCurrentState.mUserSpecifiedBaseZero; } bool FSJointPose::getWorldRotationLockState() const diff --git a/indra/newview/fsjointpose.h b/indra/newview/fsjointpose.h index 1428b930b5..53cad2e6e5 100644 --- a/indra/newview/fsjointpose.h +++ b/indra/newview/fsjointpose.h @@ -107,7 +107,8 @@ class FSJointPose /// /// Sets the private rotation of the represented joint to zero. /// - void zeroBaseRotation(); + /// Whether the joint should be locked if exported to BVH. + void zeroBaseRotation(bool lockInBvh); /// /// Queries whether the represented joint is zero. @@ -173,7 +174,7 @@ class FSJointPose /// Gets whether the user has specified the base rotation of a joint to be zero. /// /// True if the user performed some action to specify zero rotation as the base, otherwise false. - bool userHaseSetBaseRotationToZero() const; + bool userHasSetBaseRotationToZero() const; /// /// Gets whether the rotation of a joint has been 'locked' so that its world rotation can remain constant while parent joints change. @@ -230,11 +231,9 @@ class FSJointPose { mBaseRotation.set(otherState.mBaseRotation); mRotation.set(otherState.mRotation); - mUserSpecifiedBaseZero = otherState.userSetBaseRotationToZero(); + mUserSpecifiedBaseZero = otherState.mUserSpecifiedBaseZero; } - bool userSetBaseRotationToZero() const { return mUserSpecifiedBaseZero; } - bool baseRotationIsZero() const { return mBaseRotation == LLQuaternion::DEFAULT; } void resetJoint() @@ -247,11 +246,7 @@ class FSJointPose mScale.setZero(); } - void zeroBaseRotation() - { - mBaseRotation = LLQuaternion::DEFAULT; - mUserSpecifiedBaseZero = true; - } + void zeroBaseRotation() { mBaseRotation = LLQuaternion::DEFAULT; } void revertJointToBase(LLJoint* joint) const { @@ -294,7 +289,7 @@ class FSJointPose mRotation.set(state->mRotation); mPosition.set(state->mPosition); mScale.set(state->mScale); - mUserSpecifiedBaseZero = state->userSetBaseRotationToZero(); + mUserSpecifiedBaseZero = state->mUserSpecifiedBaseZero; mRotationIsWorldLocked = state->mRotationIsWorldLocked; } @@ -304,12 +299,22 @@ class FSJointPose LLVector3 mScale; bool mRotationIsWorldLocked = false; + /// + /// A value indicating whether the user has explicitly set the base rotation to zero. + /// + /// + /// The base-rotation, representing any 'current animation' state when posing starts, may become zero for several reasons. + /// Loading a Pose, editing a rotation intended to save to BVH, or setting to 'T-Pose' being examples. + /// If a user intends on creating a BVH, zero-rotation has a special meaning upon upload: the joint is free (is not animated by that BVH). + /// This value represents the explicit intent to have that joint be 'free' in BVH (which is sometimes undesireable). + /// + bool mUserSpecifiedBaseZero = false; + private: LLQuaternion mStartingRotation; LLQuaternion mBaseRotation; LLVector3 mBasePosition; LLVector3 mBaseScale; - bool mUserSpecifiedBaseZero = false; }; private: diff --git a/indra/newview/fsposeranimator.cpp b/indra/newview/fsposeranimator.cpp index c8b968f393..ba91cf356f 100644 --- a/indra/newview/fsposeranimator.cpp +++ b/indra/newview/fsposeranimator.cpp @@ -318,7 +318,7 @@ bool FSPoserAnimator::exportRotationWillLockJoint(LLVOAvatar* avatar, const FSPo if (!jointPose) return false; - if (!jointPose->userHaseSetBaseRotationToZero()) + if (!jointPose->userHasSetBaseRotationToZero()) return false; F32 rot_threshold = ROTATION_KEYFRAME_THRESHOLD / llmax((F32)getChildJointDepth(&joint, 0) * 0.33f, 1.f); @@ -344,7 +344,7 @@ bool FSPoserAnimator::userSetBaseRotationToZero(LLVOAvatar* avatar, const FSPose if (!jointPose) return false; - return jointPose->userHaseSetBaseRotationToZero(); + return jointPose->userHasSetBaseRotationToZero(); } bool FSPoserAnimator::allBaseRotationsAreZero(LLVOAvatar* avatar) const @@ -373,6 +373,21 @@ void FSPoserAnimator::setAllAvatarStartingRotationsToZero(LLVOAvatar* avatar) return; posingMotion->setAllRotationsToZeroAndClearUndo(); + + for (size_t index = 0; index != PoserJoints.size(); ++index) + { + auto boneType = PoserJoints[index].boneType(); + bool setBvhToLock = boneType == BODY || boneType == WHOLEAVATAR; + if (setBvhToLock) + continue; // setAllRotationsToZeroAndClearUndo specified this is the default behaviour + + FSJointPose* jointPose = posingMotion->getJointPoseByJointName(PoserJoints[index].jointName()); + if (!jointPose) + continue; + + posingMotion->setJointBvhLock(jointPose, false); + } + } void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation) @@ -434,7 +449,7 @@ void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoi } } -LLVector3 FSPoserAnimator::getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint) const +LLVector3 FSPoserAnimator::getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, bool lockWholeAvatar) const { auto rotation = getJointRotation(avatar, joint, SWAP_NOTHING, NEGATE_NOTHING); if (exportRotationWillLockJoint(avatar, joint)) @@ -452,10 +467,10 @@ LLVector3 FSPoserAnimator::getJointExportRotation(LLVOAvatar* avatar, const FSPo if (!jointPose) return vec3; - if (!jointPose->userHaseSetBaseRotationToZero()) + if (!jointPose->userHasSetBaseRotationToZero()) return vec3; - if (joint.boneType() == WHOLEAVATAR) + if (lockWholeAvatar && joint.boneType() == WHOLEAVATAR) return LLVector3(DEG_TO_RAD * 0.295f, 0.f, 0.f); F32 minimumRotation = DEG_TO_RAD * 0.65f / llmax((F32)getChildJointDepth(&joint, 0) * 0.33f, 1.f); diff --git a/indra/newview/fsposeranimator.h b/indra/newview/fsposeranimator.h index 3e81d0c2f4..853b15d308 100644 --- a/indra/newview/fsposeranimator.h +++ b/indra/newview/fsposeranimator.h @@ -497,12 +497,13 @@ public: /// /// The avatar whose joint is being queried. /// The joint to determine the rotation for. + /// Whether the whole avatar should be rotation/position locked in the BVH export. /// The rotation of the requested joint for export. /// /// The BVH export format requires some minimal amount of rotation so it animates the joint on upload. /// The WHOLEAVATAR joint (mPelvis) never exports as 'free'. /// - LLVector3 getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint) const; + LLVector3 getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, bool lockWholeAvatar) const; /// /// Sets the rotation of a joint for the supplied avatar. diff --git a/indra/newview/fsposingmotion.cpp b/indra/newview/fsposingmotion.cpp index 900f26b195..15fc7d6bb8 100644 --- a/indra/newview/fsposingmotion.cpp +++ b/indra/newview/fsposingmotion.cpp @@ -264,6 +264,11 @@ void FSPosingMotion::setAllRotationsToZeroAndClearUndo() } } +void FSPosingMotion::setJointBvhLock(FSJointPose* joint, bool lockInBvh) +{ + joint->zeroBaseRotation(lockInBvh); +} + bool FSPosingMotion::vectorsNotQuiteEqual(LLVector3 v1, LLVector3 v2) const { if (vectorAxesAlmostEqual(v1.mV[VX], v2.mV[VX]) && diff --git a/indra/newview/fsposingmotion.h b/indra/newview/fsposingmotion.h index 9d862ffbef..7e7b8fd9f4 100644 --- a/indra/newview/fsposingmotion.h +++ b/indra/newview/fsposingmotion.h @@ -121,8 +121,17 @@ public: /// /// Sets all of the non-Collision Volume base-and-delta rotations to zero, and clears the undo/redo queue. /// + /// + /// By default, sets the joint to lock in BVH export. + /// void setAllRotationsToZeroAndClearUndo(); + /// + /// Sets the BVH export state for the supplied joint. + /// + /// Whether the joint should be locked if exported to BVH. + void setJointBvhLock(FSJointPose* joint, bool lockInBvh); + private: /// /// The axial difference considered close enough to be the same. diff --git a/indra/newview/skins/default/xui/en/floater_fs_poser.xml b/indra/newview/skins/default/xui/en/floater_fs_poser.xml index c170ac8265..0f93f4d2a0 100644 --- a/indra/newview/skins/default/xui/en/floater_fs_poser.xml +++ b/indra/newview/skins/default/xui/en/floater_fs_poser.xml @@ -553,6 +553,17 @@ width="430"> function="Poser.CommitSpinner" parameter="2"/> +