FIRE-35686: Add icon to each bone for BVH export state

Also:
- correct an issue where Manip changes would not reset base rot, required for BVH
- always 'lock' mPelvis for BVH export
- Copy L/R and loads can now 'undo'
- removed two check-boxes for BVH: now tick-and go
master
Angeldark Raymaker 2025-07-25 20:40:22 +01:00
parent adb9ce2a5c
commit 3bd58efda2
9 changed files with 314 additions and 115 deletions

View File

@ -8100,17 +8100,6 @@
<key>Value</key>
<integer>0</integer>
</map>
<key>FSPoserResetBaseRotationOnEdit</key>
<map>
<key>Comment</key>
<string>Whether to reset the base-rotation of a joint to zero when a user edits it.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>FSPoserOnSaveConfirmOverwrite</key>
<map>
<key>Comment</key>

View File

@ -60,7 +60,6 @@ constexpr char XML_JOINT_DELTAROT_STRING_PREFIX[] = "joint_delta_ro
constexpr char BVH_JOINT_TRANSFORM_STRING_PREFIX[] = "bvh_joint_transform_";
constexpr std::string_view POSER_TRACKPAD_SENSITIVITY_SAVE_KEY = "FSPoserTrackpadSensitivity";
constexpr std::string_view POSER_STOPPOSINGWHENCLOSED_SAVE_KEY = "FSPoserStopPosingWhenClosed";
constexpr std::string_view POSER_RESETBASEROTONEDIT_SAVE_KEY = "FSPoserResetBaseRotationOnEdit";
constexpr std::string_view POSER_SAVEEXTERNALFORMAT_SAVE_KEY = "FSPoserSaveExternalFileAlso";
constexpr std::string_view POSER_SAVECONFIRMREQUIRED_SAVE_KEY = "FSPoserOnSaveConfirmOverwrite";
constexpr char ICON_SAVE_OK[] = "icon_rotation_is_own_work";
@ -212,8 +211,7 @@ bool FSFloaterPoser::postBuild()
mCollisionVolumesPnl = getChild<LLPanel>("collision_volumes_panel");
mAlsoSaveBvhCbx = getChild<LLCheckBoxCtrl>("also_save_bvh_checkbox");
mResetBaseRotCbx = getChild<LLCheckBoxCtrl>("reset_base_rotation_on_edit_checkbox");
mResetBaseRotCbx->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickSetBaseRotZero(); });
mAlsoSaveBvhCbx->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickSavingToBvh(); });
mTrackpadSensitivitySpnr = getChild<LLUICtrl>("trackpad_sensitivity_spinner");
mYawSpnr = getChild<LLUICtrl>("limb_yaw_spinner");
@ -366,10 +364,10 @@ void FSFloaterPoser::onPoseFileSelect()
mPoseSaveNameEditor->setText(name);
bool isDeltaSave = !poseFileStartsFromTeePose(name);
if (isDeltaSave && hasString("LoadDiffLabel"))
mLoadPosesBtn->setLabel(getString("LoadDiffLabel"));
else if (hasString("LoadPoseLabel"))
mLoadPosesBtn->setLabel(getString("LoadPoseLabel"));
if (isDeltaSave)
mLoadPosesBtn->setLabel(tryGetString("LoadDiffLabel"));
else
mLoadPosesBtn->setLabel(tryGetString("LoadPoseLabel"));
}
void FSFloaterPoser::doPoseSave(LLVOAvatar* avatar, const std::string& filename)
@ -386,24 +384,21 @@ void FSFloaterPoser::doPoseSave(LLVOAvatar* avatar, const std::string& filename)
if (getSavingToBvh())
savePoseToBvh(avatar, filename);
if (hasString(ICON_SAVE_OK))
mSavePosesBtn->setImageOverlay(getString(ICON_SAVE_OK), mSavePosesBtn->getImageOverlayHAlign());
mSavePosesBtn->setImageOverlay(tryGetString(ICON_SAVE_OK), mSavePosesBtn->getImageOverlayHAlign());
setSavePosesButtonText(!mPoserAnimator.allBaseRotationsAreZero(avatar));
}
else
{
if (hasString(ICON_SAVE_FAILED))
mSavePosesBtn->setImageOverlay(getString(ICON_SAVE_FAILED), mSavePosesBtn->getImageOverlayHAlign());
}
mSavePosesBtn->setImageOverlay(tryGetString(ICON_SAVE_FAILED), mSavePosesBtn->getImageOverlayHAlign());
}
}
void FSFloaterPoser::onClickPoseSave()
{
std::string filename = mPoseSaveNameEditor->getValue().asString();
if (filename.empty() && hasString(ICON_SAVE_FAILED))
if (filename.empty())
{
mSavePosesBtn->setImageOverlay(getString(ICON_SAVE_FAILED), mSavePosesBtn->getImageOverlayHAlign());
mSavePosesBtn->setImageOverlay(tryGetString(ICON_SAVE_FAILED), mSavePosesBtn->getImageOverlayHAlign());
return;
}
@ -444,8 +439,7 @@ void FSFloaterPoser::onClickPoseSave()
void FSFloaterPoser::onMouseLeaveSavePoseBtn()
{
if (hasString("icon_save_button"))
mSavePosesBtn->setImageOverlay(getString("icon_save_button"), mSavePosesBtn->getImageOverlayHAlign());
mSavePosesBtn->setImageOverlay(tryGetString("icon_save_button"), mSavePosesBtn->getImageOverlayHAlign());
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
@ -691,7 +685,8 @@ void FSFloaterPoser::updatePosedBones(const std::string& jointName)
if (!poserJoint)
return;
mPoserAnimator.recaptureJointAsDelta(avatar, poserJoint, getUiSelectedBoneDeflectionStyle());
bool savingToExternal = getSavingToBvh();
mPoserAnimator.recaptureJointAsDelta(avatar, poserJoint, savingToExternal, getUiSelectedBoneDeflectionStyle());
refreshRotationSlidersAndSpinners();
refreshPositionSlidersAndSpinners();
@ -723,6 +718,9 @@ void FSFloaterPoser::onClickSymmetrize(const S32 ID)
refreshRotationSlidersAndSpinners();
enableOrDisableRedoAndUndoButton();
refreshTrackpadCursor();
if (getSavingToBvh())
refreshTextHighlightingOnJointScrollLists();
}
void FSFloaterPoser::onCommitSpinner(const LLUICtrl* spinner, const S32 id)
@ -964,6 +962,8 @@ void FSFloaterPoser::onClickLoadHandPose(bool isRightHand)
mPoserAnimator.loadJointRotation(avatar, poserJoint, true, vec3);
}
}
addBoldToScrollList(mHandJointsScrollList, avatar);
}
catch ( const std::exception& e )
{
@ -1310,8 +1310,8 @@ LLSD FSFloaterPoser::createRowForJoint(const std::string& jointName, bool isHead
return NULL;
std::string headerValue = "";
if (isHeaderRow && hasString("icon_category"))
headerValue = getString("icon_category");
if (isHeaderRow)
headerValue = tryGetString("icon_category");
std::string jointValue = jointName;
std::string parameterName = (isHeaderRow ? XML_LIST_HEADER_STRING_PREFIX : XML_LIST_TITLE_STRING_PREFIX) + jointName;
@ -1404,6 +1404,8 @@ void FSFloaterPoser::onUndoLastChange()
refreshPositionSlidersAndSpinners();
refreshScaleSlidersAndSpinners();
refreshTrackpadCursor();
if (getSavingToBvh())
refreshTextHighlightingOnJointScrollLists();
}
void FSFloaterPoser::onSetAvatarToTpose()
@ -1447,6 +1449,8 @@ void FSFloaterPoser::onResetJoint(const LLSD data)
refreshScaleSlidersAndSpinners();
refreshTrackpadCursor();
enableOrDisableRedoAndUndoButton();
if (getSavingToBvh())
refreshTextHighlightingOnJointScrollLists();
}
void FSFloaterPoser::onRedoLastChange()
@ -1474,6 +1478,8 @@ void FSFloaterPoser::onRedoLastChange()
refreshTrackpadCursor();
refreshScaleSlidersAndSpinners();
refreshPositionSlidersAndSpinners();
if (getSavingToBvh())
refreshTextHighlightingOnJointScrollLists();
}
void FSFloaterPoser::enableOrDisableRedoAndUndoButton()
@ -1967,7 +1973,7 @@ void FSFloaterPoser::setSelectedJointsRotation(const LLVector3& absoluteRot, con
return;
auto selectedJoints = getUiSelectedPoserJoints();
bool savingToExternal = getWhetherToResetBaseRotationOnEdit();
bool savingToExternal = getSavingToBvh();
E_BoneDeflectionStyles defl = getUiSelectedBoneDeflectionStyle();
for (auto item : selectedJoints)
@ -2081,7 +2087,6 @@ void FSFloaterPoser::onJointTabSelect()
refreshTrackpadCursor();
enableOrDisableRedoAndUndoButton();
refreshScaleSlidersAndSpinners();
onClickSetBaseRotZero();
}
E_BoneAxisTranslation FSFloaterPoser::getJointTranslation(const std::string& jointName) const
@ -2348,8 +2353,7 @@ void FSFloaterPoser::refreshTextHighlightingOnJointScrollLists()
void FSFloaterPoser::setSavePosesButtonText(bool setAsSaveDiff)
{
if (hasString("SavePoseLabel") && hasString("SaveDiffLabel"))
setAsSaveDiff ? mSavePosesBtn->setLabel(getString("SaveDiffLabel")) : mSavePosesBtn->setLabel(getString("SavePoseLabel"));
setAsSaveDiff ? mSavePosesBtn->setLabel(tryGetString("SaveDiffLabel")) : mSavePosesBtn->setLabel(tryGetString("SavePoseLabel"));
}
void FSFloaterPoser::addBoldToScrollList(LLScrollListCtrl* list, LLVOAvatar* avatar)
@ -2361,32 +2365,56 @@ void FSFloaterPoser::addBoldToScrollList(LLScrollListCtrl* list, LLVOAvatar* ava
return;
std::string iconValue = "";
bool considerExternalFormatSaving = getWhetherToResetBaseRotationOnEdit();
if (considerExternalFormatSaving && hasString("icon_rotation_is_own_work"))
iconValue = getString("icon_rotation_is_own_work");
bool considerExternalFormatSaving = getSavingToBvh();
for (auto listItem : list->getAllData())
{
FSPoserAnimator::FSPoserJoint *userData = static_cast<FSPoserAnimator::FSPoserJoint *>(listItem->getUserdata());
if (!userData)
FSPoserAnimator::FSPoserJoint *poserJoint = static_cast<FSPoserAnimator::FSPoserJoint *>(listItem->getUserdata());
if (!poserJoint)
continue;
if (considerExternalFormatSaving)
{
if (mPoserAnimator.baseRotationIsZero(avatar, *userData))
((LLScrollListText*) listItem->getColumn(COL_ICON))->setValue(iconValue);
else
((LLScrollListText*) listItem->getColumn(COL_ICON))->setValue("");
}
((LLScrollListText*)listItem->getColumn(COL_ICON))->setValue(getScrollListIconForJoint(avatar, *poserJoint));
else
((LLScrollListText*)listItem->getColumn(COL_ICON))->setValue("");
if (mPoserAnimator.isPosingAvatarJoint(avatar, *userData))
if (mPoserAnimator.isPosingAvatarJoint(avatar, *poserJoint))
((LLScrollListText *) listItem->getColumn(COL_NAME))->setFontStyle(LLFontGL::BOLD);
else
((LLScrollListText *) listItem->getColumn(COL_NAME))->setFontStyle(LLFontGL::NORMAL);
}
}
std::string FSFloaterPoser::getScrollListIconForJoint(LLVOAvatar* avatar, FSPoserAnimator::FSPoserJoint joint)
{
if (!avatar)
return "";
if (mPoserAnimator.getRotationIsWorldLocked(avatar, joint))
return tryGetString("icon_rotation_is_world_locked");
if (joint.boneType() == COL_VOLUMES)
return tryGetString("icon_rotation_does_not_export");
if (mPoserAnimator.userSetBaseRotationToZero(avatar, joint))
{
if (mPoserAnimator.exportRotationWillLockJoint(avatar, joint))
return tryGetString("icon_rotation_bvh_locked_edited");
else
return tryGetString("icon_rotation_bvh_locked_unedited");
}
else
return tryGetString("icon_rotation_bvh_unlocked");
}
std::string FSFloaterPoser::tryGetString(std::string name)
{
if (name.empty())
return "";
return hasString(name) ? getString(name) : "";
}
bool FSFloaterPoser::savePoseToBvh(LLVOAvatar* avatar, const std::string& poseFileName)
{
if (poseFileName.empty())
@ -2552,7 +2580,7 @@ void FSFloaterPoser::writeBvhMotion(llofstream* fileStream, LLVOAvatar* avatar,
if (!joint)
return;
auto rotation = mPoserAnimator.getJointRotation(avatar, *joint, SWAP_NOTHING, NEGATE_NOTHING);
auto rotation = mPoserAnimator.getJointExportRotation(avatar, *joint);
auto position = mPoserAnimator.getJointPosition(avatar, *joint);
switch (joint->boneType())
@ -2644,12 +2672,9 @@ S32 FSFloaterPoser::getBvhJointNegation(const std::string& jointName) const
return result;
}
bool FSFloaterPoser::getWhetherToResetBaseRotationOnEdit() { return gSavedSettings.getBOOL(POSER_RESETBASEROTONEDIT_SAVE_KEY); }
void FSFloaterPoser::onClickSetBaseRotZero() { mAlsoSaveBvhCbx->setEnabled(getWhetherToResetBaseRotationOnEdit()); }
bool FSFloaterPoser::getSavingToBvh()
{
return getWhetherToResetBaseRotationOnEdit() && gSavedSettings.getBOOL(POSER_RESETBASEROTONEDIT_SAVE_KEY);
return gSavedSettings.getBOOL(POSER_SAVEEXTERNALFORMAT_SAVE_KEY);
}
void FSFloaterPoser::onClickSavingToBvh() { refreshTextHighlightingOnJointScrollLists(); }

View File

@ -264,7 +264,7 @@ public:
void onClickLoadLeftHandPose();
void onClickLoadRightHandPose();
void onClickLoadHandPose(bool isRightHand);
void onClickSetBaseRotZero();
void onClickSavingToBvh();
void onCommitSpinner(const LLUICtrl* spinner, const S32 ID);
void onCommitSlider(const LLUICtrl* slider, const S32 id);
void onClickSymmetrize(const S32 ID);
@ -351,12 +351,19 @@ public:
void addBoldToScrollList(LLScrollListCtrl* list, LLVOAvatar* avatar);
/// <summary>
/// Gets whether the user wishes to reset the base-rotation to zero when they start editing a joint.
/// Gets a string for a joint on a scroll-list.
/// </summary>
/// <remarks>
/// If a joint has a base-rotation of zero, the rotation then appears to be the user's work and qualifies to save to a re-importable format.
/// </remarks>
bool getWhetherToResetBaseRotationOnEdit();
/// <param name="avatar">The avatar owning the supplied joint.</param>
/// <param name="joint">The joint to query.</param>
/// <returns>A string naming an icon to present with the joint.</returns>
std::string getScrollListIconForJoint(LLVOAvatar* avatar, FSPoserAnimator::FSPoserJoint joint);
/// <summary>
/// Tries to get the named string from the XUI.
/// </summary>
/// <param name="name">The name of the string.</param>
/// <returns>The named string, if it exists, otherwise an empty string.</returns>
std::string tryGetString(std::string name);
/// <summary>
/// Gets the name of an item from the supplied object ID.
@ -503,7 +510,6 @@ public:
LLPanel* mCollisionVolumesPnl{ nullptr };
LLPanel* mPosesLoadSavePnl{ nullptr };
LLCheckBoxCtrl* mResetBaseRotCbx{ nullptr };
LLCheckBoxCtrl* mAlsoSaveBvhCbx{ nullptr };
LLUICtrl* mTrackpadSensitivitySpnr{ nullptr };

View File

@ -57,9 +57,13 @@ void FSJointPose::setPublicPosition(const LLVector3& pos)
mCurrentState.mPosition.set(pos);
}
void FSJointPose::setPublicRotation(const LLQuaternion& rot)
void FSJointPose::setPublicRotation(bool zeroBase, const LLQuaternion& rot)
{
addStateToUndo(FSJointState(mCurrentState));
if (zeroBase)
zeroBaseRotation();
mCurrentState.mRotation.set(rot);
}
@ -79,6 +83,12 @@ void FSJointPose::redoLastChange()
mCurrentState = redoLastStateChange(FSJointState(mCurrentState));
}
void FSJointPose::resetJoint()
{
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.resetJoint();
}
void FSJointPose::addStateToUndo(FSJointState stateToAddToUndo)
{
auto timeIntervalSinceLastChange = std::chrono::system_clock::now() - mTimeLastUpdatedCurrentState;
@ -125,7 +135,7 @@ FSJointPose::FSJointState FSJointPose::redoLastStateChange(FSJointState thingToS
mUndoneJointStatesIndex -= 1;
mUndoneJointStatesIndex = llclamp(mUndoneJointStatesIndex, 0, mLastSetJointStates.size() - 1);
auto result = mLastSetJointStates.at(mUndoneJointStatesIndex);
FSJointState result = mLastSetJointStates.at(mUndoneJointStatesIndex);
if (mUndoneJointStatesIndex == 0)
mLastSetJointStates.pop_front();
@ -145,14 +155,14 @@ void FSJointPose::recaptureJoint()
mCurrentState = FSJointState(joint);
}
void FSJointPose::recaptureJointAsDelta()
void FSJointPose::recaptureJointAsDelta(bool zeroBase)
{
LLJoint* joint = mJointState->getJoint();
if (!joint)
return;
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.updateFromJoint(joint);
mCurrentState.updateFromJoint(joint, zeroBase);
}
void FSJointPose::swapRotationWith(FSJointPose* oppositeJoint)
@ -203,9 +213,6 @@ void FSJointPose::zeroBaseRotation()
if (mIsCollisionVolume)
return;
if (!isBaseRotationZero())
purgeUndoQueue();
mCurrentState.zeroBaseRotation();
}
@ -219,10 +226,21 @@ bool FSJointPose::isBaseRotationZero() const
void FSJointPose::purgeUndoQueue()
{
if (mIsCollisionVolume)
return;
mUndoneJointStatesIndex = 0;
mLastSetJointStates.clear();
}
bool FSJointPose::userHaseSetBaseRotationToZero() const
{
if (mIsCollisionVolume)
return false;
return mCurrentState.userSetBaseRotationToZero();
}
bool FSJointPose::canPerformUndo() const
{
switch (mLastSetJointStates.size())

View File

@ -77,6 +77,11 @@ class FSJointPose
/// </summary>
void redoLastChange();
/// <summary>
/// Resets the joint to its conditions when posing started.
/// </summary>
void resetJoint();
/// <summary>
/// Gets the 'public' rotation of the joint.
/// </summary>
@ -85,12 +90,14 @@ class FSJointPose
/// <summary>
/// Sets the 'public' rotation of the joint.
/// </summary>
/// <param name="zeroBase">Whether to zero the base rotation on setting the supplied rotation.</param>
/// <param name="rot">The change in rotation to apply.</param>
/// <remarks>
/// 'Public rotation' is the amount of rotation the user has added to the initial state.
/// Public rotation is what a user may save to an external format (such as BVH).
/// This distinguishes 'private' rotation, which is the state inherited from something like a pose in-world.
/// </remarks>
void setPublicRotation(const LLQuaternion& rot);
void setPublicRotation(bool zeroBase, const LLQuaternion& rot);
/// <summary>
/// Reflects the base and delta rotation of the represented joint left-right.
@ -153,13 +160,20 @@ class FSJointPose
/// <summary>
/// Recalculates the delta reltive to the base for a new rotation.
/// </summary>
void recaptureJointAsDelta();
/// <param name="zeroBase">Whether to zero the base rotation on setting the supplied rotation.</param>
void recaptureJointAsDelta(bool zeroBase);
/// <summary>
/// Clears the undo/redo deque.
/// </summary>
void purgeUndoQueue();
/// <summary>
/// Gets whether the user has specified the base rotation of a joint to be zero.
/// </summary>
/// <returns>True if the user performed some action to specify zero rotation as the base, otherwise false.</returns>
bool userHaseSetBaseRotationToZero() const;
/// <summary>
/// Reverts the position/rotation/scale to their values when the animation begun.
/// This treatment is required for certain joints, particularly Collision Volumes and those bones not commonly animated by an AO.
@ -180,6 +194,7 @@ class FSJointPose
public:
FSJointState(LLJoint* joint)
{
mStartingRotation.set(joint->getRotation());
mBaseRotation.set(joint->getRotation());
mBasePosition.set(joint->getPosition());
mBaseScale.set(joint->getScale());
@ -209,11 +224,27 @@ class FSJointPose
{
mBaseRotation.set(otherState.mBaseRotation);
mRotation.set(otherState.mRotation);
mUserSpecifiedBaseZero = otherState.userSetBaseRotationToZero();
}
bool userSetBaseRotationToZero() const { return mUserSpecifiedBaseZero; }
bool baseRotationIsZero() const { return mBaseRotation == LLQuaternion::DEFAULT; }
void zeroBaseRotation() { mBaseRotation = LLQuaternion::DEFAULT; }
void resetJoint()
{
mUserSpecifiedBaseZero = false;
mBaseRotation.set(mStartingRotation);
mRotation.set(LLQuaternion::DEFAULT);
mPosition.setZero();
mScale.setZero();
}
void zeroBaseRotation()
{
mBaseRotation = LLQuaternion::DEFAULT;
mUserSpecifiedBaseZero = true;
}
void revertJointToBase(LLJoint* joint) const
{
@ -225,7 +256,7 @@ class FSJointPose
joint->setScale(mBaseScale);
}
void updateFromJoint(LLJoint* joint)
void updateFromJoint(LLJoint* joint, bool zeroBase)
{
if (!joint)
return;
@ -233,6 +264,10 @@ class FSJointPose
LLQuaternion invRot = mBaseRotation;
invRot.conjugate();
mRotation = joint->getRotation() * invRot;
if (zeroBase)
zeroBaseRotation();
mPosition.set(joint->getPosition() - mBasePosition);
mScale.set(joint->getScale() - mBaseScale);
}
@ -240,6 +275,7 @@ class FSJointPose
private:
FSJointState(FSJointState* state)
{
mStartingRotation.set(state->mStartingRotation);
mBaseRotation.set(state->mBaseRotation);
mBasePosition.set(state->mBasePosition);
mBaseScale.set(state->mBaseScale);
@ -247,6 +283,7 @@ class FSJointPose
mRotation.set(state->mRotation);
mPosition.set(state->mPosition);
mScale.set(state->mScale);
mUserSpecifiedBaseZero = state->userSetBaseRotationToZero();
}
public:
@ -255,9 +292,11 @@ class FSJointPose
LLVector3 mScale;
private:
LLQuaternion mStartingRotation;
LLQuaternion mBaseRotation;
LLVector3 mBasePosition;
LLVector3 mBaseScale;
bool mUserSpecifiedBaseZero = false;
};
private:

View File

@ -122,9 +122,7 @@ void FSPoserAnimator::resetJoint(LLVOAvatar* avatar, const FSPoserJoint& joint,
if (!jointPose)
return;
jointPose->setPublicRotation(LLQuaternion());
jointPose->setPublicPosition(LLVector3());
jointPose->setPublicScale(LLVector3());
jointPose->resetJoint();
if (style == NONE || style == DELTAMODE)
return;
@ -133,9 +131,7 @@ void FSPoserAnimator::resetJoint(LLVOAvatar* avatar, const FSPoserJoint& joint,
if (!oppositeJointPose)
return;
oppositeJointPose->setPublicRotation(LLQuaternion());
oppositeJointPose->setPublicPosition(LLVector3());
oppositeJointPose->setPublicScale(LLVector3());
oppositeJointPose->resetJoint();
}
bool FSPoserAnimator::canRedoOrUndoJointChange(LLVOAvatar* avatar, const FSPoserJoint& joint, bool canUndo)
@ -265,7 +261,7 @@ void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* j
}
}
bool FSPoserAnimator::baseRotationIsZero(LLVOAvatar* avatar, const FSPoserJoint& joint) const
bool FSPoserAnimator::getRotationIsWorldLocked(LLVOAvatar* avatar, const FSPoserJoint& joint) const
{
if (!isAvatarSafeToUse(avatar))
return false;
@ -278,7 +274,53 @@ bool FSPoserAnimator::baseRotationIsZero(LLVOAvatar* avatar, const FSPoserJoint&
if (!jointPose)
return false;
return jointPose->isBaseRotationZero();
// TODO: FIRE-35769
return false;
}
bool FSPoserAnimator::exportRotationWillLockJoint(LLVOAvatar* avatar, const FSPoserJoint& joint) const
{
const F32 ROTATION_KEYFRAME_THRESHOLD = 0.01f; // this is a guestimate: see BVH loader
if (!isAvatarSafeToUse(avatar))
return false;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return false;
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName());
if (!jointPose)
return false;
if (!jointPose->userHaseSetBaseRotationToZero())
return false;
F32 rot_threshold = ROTATION_KEYFRAME_THRESHOLD / llmax((F32)getChildJointDepth(&joint, 0) * 0.33f, 1.f);
LLQuaternion rotToExport = jointPose->getPublicRotation();
F32 x_delta = dist_vec(LLVector3::x_axis * LLQuaternion::DEFAULT, LLVector3::x_axis * rotToExport); // when exporting multiple frames this will need to compare frames.
F32 y_delta = dist_vec(LLVector3::y_axis * LLQuaternion::DEFAULT, LLVector3::y_axis * rotToExport);
F32 rot_test = x_delta + y_delta;
return rot_test > rot_threshold;
}
bool FSPoserAnimator::userSetBaseRotationToZero(LLVOAvatar* avatar, const FSPoserJoint& joint) const
{
if (!isAvatarSafeToUse(avatar))
return false;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return false;
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName());
if (!jointPose)
return false;
return jointPose->userHaseSetBaseRotationToZero();
}
bool FSPoserAnimator::allBaseRotationsAreZero(LLVOAvatar* avatar) const
@ -326,7 +368,8 @@ void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joi
setPosingAvatarJoint(avatar, joint, true);
}
void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint* joint, E_BoneDeflectionStyles style)
void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero,
E_BoneDeflectionStyles style)
{
if (!isAvatarSafeToUse(avatar))
return;
@ -339,7 +382,7 @@ void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoi
if (!jointPose)
return;
jointPose->recaptureJointAsDelta();
jointPose->recaptureJointAsDelta(resetBaseRotationToZero);
if (style == NONE || style == DELTAMODE)
return;
@ -365,6 +408,35 @@ void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoi
}
}
LLVector3 FSPoserAnimator::getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint) const
{
auto rotation = getJointRotation(avatar, joint, SWAP_NOTHING, NEGATE_NOTHING);
if (exportRotationWillLockJoint(avatar, joint))
return rotation;
LLVector3 vec3;
if (!isAvatarSafeToUse(avatar))
return vec3;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return vec3;
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName());
if (!jointPose)
return vec3;
if (!jointPose->userHaseSetBaseRotationToZero())
return vec3;
if (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);
return LLVector3(minimumRotation, 0.f, 0.f);
}
LLVector3 FSPoserAnimator::getJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation) const
{
LLVector3 vec3;
@ -399,9 +471,6 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j
if (!jointPose)
return;
if (resetBaseRotationToZero)
jointPose->zeroBaseRotation();
LLQuaternion absRot = translateRotationToQuaternion(translation, negation, absRotation);
LLQuaternion deltaRot = translateRotationToQuaternion(translation, negation, deltaRotation);
switch (deflectionStyle)
@ -409,27 +478,27 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j
case SYMPATHETIC:
case MIRROR:
if (rotationStyle == DELTAIC_ROT)
jointPose->setPublicRotation(deltaRot * jointPose->getPublicRotation());
jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation());
else
jointPose->setPublicRotation(absRot);
jointPose->setPublicRotation(resetBaseRotationToZero, absRot);
break;
case SYMPATHETIC_DELTA:
case MIRROR_DELTA:
jointPose->setPublicRotation(deltaRot * jointPose->getPublicRotation());
jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation());
break;
case DELTAMODE:
jointPose->setPublicRotation(deltaRot * jointPose->getPublicRotation());
jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation());
return;
case NONE:
default:
if (rotationStyle == DELTAIC_ROT)
jointPose->setPublicRotation(deltaRot * jointPose->getPublicRotation());
jointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * jointPose->getPublicRotation());
else
jointPose->setPublicRotation(absRot);
jointPose->setPublicRotation(resetBaseRotationToZero, absRot);
return;
}
@ -446,7 +515,7 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j
break;
case SYMPATHETIC_DELTA:
oppositeJointPose->setPublicRotation(deltaRot * oppositeJointPose->getPublicRotation());
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * oppositeJointPose->getPublicRotation());
break;
case MIRROR:
@ -455,7 +524,7 @@ void FSPoserAnimator::setJointRotation(LLVOAvatar* avatar, const FSPoserJoint* j
case MIRROR_DELTA:
inv_quat = LLQuaternion(-deltaRot.mQ[VX], deltaRot.mQ[VY], -deltaRot.mQ[VZ], deltaRot.mQ[VW]);
oppositeJointPose->setPublicRotation(inv_quat * oppositeJointPose->getPublicRotation());
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, inv_quat * oppositeJointPose->getPublicRotation());
break;
default:
@ -757,11 +826,10 @@ void FSPoserAnimator::loadJointRotation(LLVOAvatar* avatar, const FSPoserJoint*
if (!jointPose)
return;
if (setBaseToZero)
jointPose->zeroBaseRotation();
jointPose->purgeUndoQueue();
LLQuaternion rot = translateRotationToQuaternion(SWAP_NOTHING, NEGATE_NOTHING, rotation);
jointPose->setPublicRotation(rot);
jointPose->setPublicRotation(setBaseToZero, rot);
}
void FSPoserAnimator::loadJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadPositionAsDelta, LLVector3 position)
@ -802,7 +870,7 @@ void FSPoserAnimator::loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joi
jointPose->setPublicScale(scale);
}
const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByName(const std::string& jointName)
const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByName(const std::string& jointName) const
{
for (size_t index = 0; index != PoserJoints.size(); ++index)
{
@ -899,3 +967,23 @@ bool FSPoserAnimator::isAvatarSafeToUse(LLVOAvatar* avatar) const
return true;
}
int FSPoserAnimator::getChildJointDepth(const FSPoserJoint* joint, int depth) const
{
size_t numberOfBvhChildNodes = joint->bvhChildren().size();
if (numberOfBvhChildNodes < 1)
return depth;
depth++;
for (size_t index = 0; index != numberOfBvhChildNodes; ++index)
{
auto nextJoint = getPoserJointByName(joint->bvhChildren()[index]);
if (!nextJoint)
continue;
depth = llmax(depth, getChildJointDepth(nextJoint, depth));
}
return depth;
}

View File

@ -392,7 +392,7 @@ public:
/// </summary>
/// <param name="jointName">The name of the joint to match.</param>
/// <returns>The matching joint if found, otherwise nullptr</returns>
const FSPoserJoint* getPoserJointByName(const std::string& jointName);
const FSPoserJoint* getPoserJointByName(const std::string& jointName) const;
/// <summary>
/// Tries to start posing the supplied avatar.
@ -492,6 +492,18 @@ public:
/// <returns>The rotation of the requested joint, if determinable, otherwise a default vector.</returns>
LLVector3 getJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation) const;
/// <summary>
/// Gets the rotation of a joint for the supplied avatar for export.
/// </summary>
/// <param name="avatar">The avatar whose joint is being queried.</param>
/// <param name="joint">The joint to determine the rotation for.</param>
/// <returns>The rotation of the requested joint for export.</returns>
/// <remarks>
/// 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'.
/// </remarks>
LLVector3 getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint) const;
/// <summary>
/// Sets the rotation of a joint for the supplied avatar.
/// </summary>
@ -559,8 +571,9 @@ public:
/// </summary>
/// <param name="avatar">The avatar whose joint is to be recaptured.</param>
/// <param name="joint">The joint to recapture.</param>
/// <param name="resetBaseRotationToZero">Whether to set the base rotation to zero on setting the rotation.</param>
/// <param name="style">Any ancilliary action to be taken with the change to be made.</param>
void recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint* joint, E_BoneDeflectionStyles style);
void recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero, E_BoneDeflectionStyles style);
/// <summary>
/// Sets all of the joint rotations of the supplied avatar to zero.
@ -574,7 +587,26 @@ public:
/// <param name="avatar">The avatar owning the supplied joint.</param>
/// <param name="joint">The joint to query.</param>
/// <returns>True if the supplied joint has a 'base' rotation of zero (thus user-supplied change only), otherwise false.</returns>
bool baseRotationIsZero(LLVOAvatar* avatar, const FSPoserJoint& joint) const;
bool userSetBaseRotationToZero(LLVOAvatar* avatar, const FSPoserJoint& joint) const;
/// <summary>
/// Gets whether the supplied joints position will be set in an export.
/// </summary>
/// <param name="avatar">The avatar owning the supplied joint.</param>
/// <param name="joint">The joint to query.</param>
/// <returns>True if the export will 'lock' the joint, otherwise false.</returns>
/// <remarks>
/// BVH import leaves a joint 'free' if its rotation is less than something arbitrary.
/// </remarks>
bool exportRotationWillLockJoint(LLVOAvatar* avatar, const FSPoserJoint& joint) const;
/// <summary>
/// Gets whether the supplied joint for the supplied avatar is rotationally locked to the world.
/// </summary>
/// <param name="avatar">The avatar owning the supplied joint.</param>
/// <param name="joint">The joint to query.</param>
/// <returns>True if the joint is maintaining a fixed-rotation in world, otherwise false.</returns>
bool getRotationIsWorldLocked(LLVOAvatar* avatar, const FSPoserJoint& joint) const;
/// <summary>
/// Determines if the kind of save to perform should be a 'delta' save, or a complete save.
@ -692,6 +724,14 @@ public:
/// <returns>True if the avatar is safe to manipulate, otherwise false.</returns>
bool isAvatarSafeToUse(LLVOAvatar* avatar) const;
/// <summary>
/// Gets the depth of descendant joints for the supplied joint.
/// </summary>
/// <param name="joint">The joint to determine the depth for.</param>
/// <param name="depth">The depth of the supplied joint.</param>
/// <returns>The number of generations of descendents the joint has, if none, then zero.</returns>
int getChildJointDepth(const FSPoserJoint* joint, int depth) const;
/// <summary>
/// Maps the avatar's ID to the animation registered to them.
/// Thus we start/stop the same animation, and get/set the same rotations etc.

View File

@ -259,8 +259,8 @@ void FSPosingMotion::setAllRotationsToZeroAndClearUndo()
{
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
{
poserJoint_iter->zeroBaseRotation();
poserJoint_iter->setPublicRotation(LLQuaternion::DEFAULT);
poserJoint_iter->purgeUndoQueue();
poserJoint_iter->setPublicRotation(true, LLQuaternion::DEFAULT);
}
}

View File

@ -11,6 +11,11 @@ width="430">
<string name="icon_category" translate="false">Inv_BodyShape</string>
<string name="icon_bone" translate="false"></string>
<string name="icon_object" translate="false">Inv_Object</string>
<string name="icon_rotation_bvh_locked_edited" translate="false">Script_Running</string>
<string name="icon_rotation_bvh_locked_unedited" translate="false">Script_NotRunning</string>
<string name="icon_rotation_bvh_unlocked" translate="false">Script_Error</string>
<string name="icon_rotation_is_world_locked" translate="false">Locked_Icon</string>
<string name="icon_rotation_does_not_export" translate="false">Conv_toolbar_close</string>
<string name="icon_rotation_is_own_work" translate="false">Check_Mark</string>
<string name="icon_save_button" translate="false">Icon_Dock_Foreground</string>
<string name="icon_save_failed_button" translate="false">Parcel_Exp_Color</string>
@ -965,25 +970,14 @@ width="430">
tool_tip="Not stopping your pose can be helpful if you do a lot of work, and don't want to accidentally lose it."
top_pad="10"
width="134" />
<check_box
control_name="FSPoserResetBaseRotationOnEdit"
name="reset_base_rotation_on_edit_checkbox"
height="16"
label="Reset base-rotation on edit"
follows="left|top"
left="5"
tool_tip="When you first edit a rotation, reset it to zero. This means your work can save a pose (and not a diff see load/save). A green tick appears next each joint you have zero-ed export."
top_pad="5"
width="134" />
<check_box
control_name="FSPoserSaveExternalFileAlso"
name="also_save_bvh_checkbox"
height="16"
enabled="false"
label="Write BVH when saving**"
follows="left|top"
left="15"
tool_tip="When you save your pose, also write a BVH file, which can be uploaded via the 'Build > Upload > Animation' to pose yourself or others in-world. This needs joints to reset their 'base' to zero, because BVH requires original work."
left="5"
tool_tip="When you save your pose, also write a BVH file. BVH can be uploaded via the 'Build > Upload > Animation' to pose yourself or others in-world. This needs joints to reset their 'base' to zero, because BVH requires original work."
top_pad="2"
width="134" />
<check_box