Merge pull request #155 from FirestormViewer/FIRE-35686

PR: FIRE-35686 & FIRE-35769
master
Angeldark Raymaker 2025-08-23 17:39:31 +01:00 committed by GitHub
commit 452deed657
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 667 additions and 129 deletions

View File

@ -8111,10 +8111,10 @@
<key>Value</key>
<integer>0</integer>
</map>
<key>FSPoserResetBaseRotationOnEdit</key>
<key>FSPoserPelvisUnlockedForBvhSave</key>
<map>
<key>Comment</key>
<string>Whether to reset the base-rotation of a joint to zero when a user edits it.</string>
<string>Whether the mPelvis joint should be position/rotationally locked when a BVH is created.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>

View File

@ -60,9 +60,9 @@ 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 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";
@ -193,6 +193,8 @@ bool FSFloaterPoser::postBuild()
mFlipJointBtn = getChild<LLButton>("FlipJoint_avatar");
mRecaptureBtn = getChild<LLButton>("button_RecaptureParts");
mTogglePosingBonesBtn = getChild<LLButton>("toggle_PosingSelectedBones");
mToggleLockWorldRotBtn = getChild<LLButton>("toggle_LockWorldRotation");
mToggleLockWorldRotBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onClickLockWorldRotBtn(); });
mToggleMirrorRotationBtn = getChild<LLButton>("button_toggleMirrorRotation");
mToggleSympatheticRotationBtn = getChild<LLButton>("button_toggleSympatheticRotation");
@ -211,9 +213,10 @@ bool FSFloaterPoser::postBuild()
mMiscJointsPnl = getChild<LLPanel>("misc_joints_panel");
mCollisionVolumesPnl = getChild<LLPanel>("collision_volumes_panel");
mUnlockPelvisInBvhSaveCbx = getChild<LLCheckBoxCtrl>("unlock_pelvis_for_bvh_save_checkbox");
mUnlockPelvisInBvhSaveCbx->setVisible(getSavingToBvh());
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 +369,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 +389,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 +444,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)
@ -523,6 +522,7 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi
{
std::string bone_name = pj.jointName();
bool posingThisJoint = mPoserAnimator.isPosingAvatarJoint(avatar, pj);
bool jointRotLocked = mPoserAnimator.getRotationIsWorldLocked(avatar, pj);
record[bone_name] = bone_name;
record[bone_name]["enabled"] = posingThisJoint;
@ -538,9 +538,10 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi
continue;
record[bone_name]["jointBaseRotationIsZero"] = baseRotationIsZero;
record[bone_name]["rotation"] = rotation.getValue();
record[bone_name]["position"] = position.getValue();
record[bone_name]["scale"] = scale.getValue();
record[bone_name]["rotation"] = rotation.getValue();
record[bone_name]["position"] = position.getValue();
record[bone_name]["scale"] = scale.getValue();
record[bone_name]["worldLocked"] = jointRotLocked;
}
std::string fullSavePath =
@ -691,7 +692,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 +725,9 @@ void FSFloaterPoser::onClickSymmetrize(const S32 ID)
refreshRotationSlidersAndSpinners();
enableOrDisableRedoAndUndoButton();
refreshTrackpadCursor();
if (getSavingToBvh())
refreshTextHighlightingOnJointScrollLists();
}
void FSFloaterPoser::onCommitSpinner(const LLUICtrl* spinner, const S32 id)
@ -964,6 +969,8 @@ void FSFloaterPoser::onClickLoadHandPose(bool isRightHand)
mPoserAnimator.loadJointRotation(avatar, poserJoint, true, vec3);
}
}
addBoldToScrollList(mHandJointsScrollList, avatar);
}
catch ( const std::exception& e )
{
@ -1041,6 +1048,7 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose
LLQuaternion quat;
bool enabled;
bool setJointBaseRotationToZero;
bool worldLocked;
S32 version = 0;
bool startFromZeroRot = true;
@ -1116,6 +1124,9 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose
vec3.setValue(control_map["scale"]);
mPoserAnimator.loadJointScale(avatar, poserJoint, loadPositionsAndScalesAsDeltas, vec3);
}
worldLocked = control_map.has("worldLocked") ? control_map["worldLocked"].asBoolean() : false;
mPoserAnimator.setRotationIsWorldLocked(avatar, *poserJoint, worldLocked);
}
}
}
@ -1310,8 +1321,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 +1415,8 @@ void FSFloaterPoser::onUndoLastChange()
refreshPositionSlidersAndSpinners();
refreshScaleSlidersAndSpinners();
refreshTrackpadCursor();
if (getSavingToBvh())
refreshTextHighlightingOnJointScrollLists();
}
void FSFloaterPoser::onSetAvatarToTpose()
@ -1447,6 +1460,7 @@ void FSFloaterPoser::onResetJoint(const LLSD data)
refreshScaleSlidersAndSpinners();
refreshTrackpadCursor();
enableOrDisableRedoAndUndoButton();
refreshTextHighlightingOnJointScrollLists();
}
void FSFloaterPoser::onRedoLastChange()
@ -1474,6 +1488,8 @@ void FSFloaterPoser::onRedoLastChange()
refreshTrackpadCursor();
refreshScaleSlidersAndSpinners();
refreshPositionSlidersAndSpinners();
if (getSavingToBvh())
refreshTextHighlightingOnJointScrollLists();
}
void FSFloaterPoser::enableOrDisableRedoAndUndoButton()
@ -1967,7 +1983,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 +2097,6 @@ void FSFloaterPoser::onJointTabSelect()
refreshTrackpadCursor();
enableOrDisableRedoAndUndoButton();
refreshScaleSlidersAndSpinners();
onClickSetBaseRotZero();
}
E_BoneAxisTranslation FSFloaterPoser::getJointTranslation(const std::string& jointName) const
@ -2348,8 +2363,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 +2375,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));
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 (!getSavingToBvh())
return "";
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 +2590,9 @@ void FSFloaterPoser::writeBvhMotion(llofstream* fileStream, LLVOAvatar* avatar,
if (!joint)
return;
auto rotation = mPoserAnimator.getJointRotation(avatar, *joint, SWAP_NOTHING, NEGATE_NOTHING);
bool lockPelvisJoint = gSavedSettings.getBOOL(POSER_UNLOCKPELVISINBVH_SAVE_KEY);
auto rotation = mPoserAnimator.getJointExportRotation(avatar, *joint, !lockPelvisJoint);
auto position = mPoserAnimator.getJointPosition(avatar, *joint);
switch (joint->boneType())
@ -2644,12 +2684,39 @@ 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()
{
mUnlockPelvisInBvhSaveCbx->setVisible(getSavingToBvh());
refreshTextHighlightingOnJointScrollLists();
}
void FSFloaterPoser::onClickLockWorldRotBtn()
{
auto selectedJoints = getUiSelectedPoserJoints();
if (selectedJoints.size() < 1)
return;
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
return;
if (!mPoserAnimator.isPosingAvatar(avatar))
return;
for (auto item : selectedJoints)
{
bool currentlyPosingJoint = mPoserAnimator.isPosingAvatarJoint(avatar, *item);
if (!currentlyPosingJoint)
continue;
bool newLockState = !mPoserAnimator.getRotationIsWorldLocked(avatar, *item);
mPoserAnimator.setRotationIsWorldLocked(avatar, *item, newLockState);
}
refreshTextHighlightingOnJointScrollLists();
}

View File

@ -264,10 +264,11 @@ 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);
void onClickLockWorldRotBtn();
// UI Refreshments
void refreshRotationSlidersAndSpinners();
@ -351,12 +352,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.
@ -483,6 +491,7 @@ public:
LLButton* mFlipJointBtn{ nullptr };
LLButton* mRecaptureBtn{ nullptr };
LLButton* mTogglePosingBonesBtn{ nullptr };
LLButton* mToggleLockWorldRotBtn{ nullptr };
LLButton* mToggleMirrorRotationBtn{ nullptr };
LLButton* mToggleSympatheticRotationBtn{ nullptr };
LLButton* mToggleDeltaModeBtn{ nullptr };
@ -503,8 +512,8 @@ public:
LLPanel* mCollisionVolumesPnl{ nullptr };
LLPanel* mPosesLoadSavePnl{ nullptr };
LLCheckBoxCtrl* mResetBaseRotCbx{ nullptr };
LLCheckBoxCtrl* mAlsoSaveBvhCbx{ nullptr };
LLCheckBoxCtrl* mUnlockPelvisInBvhSaveCbx{ nullptr };
LLUICtrl* mTrackpadSensitivitySpnr{ nullptr };
LLUICtrl* mYawSpnr{ 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(true);
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()
LLQuaternion FSJointPose::recaptureJointAsDelta(bool zeroBase)
{
LLJoint* joint = mJointState->getJoint();
if (!joint)
return;
return LLQuaternion::DEFAULT;
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.updateFromJoint(joint);
return mCurrentState.updateFromJoint(joint, zeroBase);
}
void FSJointPose::swapRotationWith(FSJointPose* oppositeJoint)
@ -198,15 +208,13 @@ void FSJointPose::reflectRotation()
mCurrentState.reflectRotation();
}
void FSJointPose::zeroBaseRotation()
void FSJointPose::zeroBaseRotation(bool lockInBvh)
{
if (mIsCollisionVolume)
return;
if (!isBaseRotationZero())
purgeUndoQueue();
mCurrentState.zeroBaseRotation();
mCurrentState.mUserSpecifiedBaseZero = lockInBvh;
}
bool FSJointPose::isBaseRotationZero() const
@ -219,10 +227,37 @@ bool FSJointPose::isBaseRotationZero() const
void FSJointPose::purgeUndoQueue()
{
if (mIsCollisionVolume)
return;
mUndoneJointStatesIndex = 0;
mLastSetJointStates.clear();
}
bool FSJointPose::userHasSetBaseRotationToZero() const
{
if (mIsCollisionVolume)
return false;
return mCurrentState.mUserSpecifiedBaseZero;
}
bool FSJointPose::getWorldRotationLockState() const
{
if (mIsCollisionVolume)
return false;
return mCurrentState.mRotationIsWorldLocked;
}
void FSJointPose::setWorldRotationLockState(bool newState)
{
if (mIsCollisionVolume)
return;
mCurrentState.mRotationIsWorldLocked = newState;
}
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.
@ -100,7 +107,8 @@ class FSJointPose
/// <summary>
/// Sets the private rotation of the represented joint to zero.
/// </summary>
void zeroBaseRotation();
/// <param name="lockInBvh">Whether the joint should be locked if exported to BVH.</param>
void zeroBaseRotation(bool lockInBvh);
/// <summary>
/// Queries whether the represented joint is zero.
@ -153,13 +161,33 @@ 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>
/// <returns>The rotation of the public difference between before and after recapture.</returns>
LLQuaternion 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 userHasSetBaseRotationToZero() const;
/// <summary>
/// Gets whether the rotation of a joint has been 'locked' so that its world rotation can remain constant while parent joints change.
/// </summary>
/// <returns>True if the joint is rotationally locked to the world, otherwise false.</returns>
bool getWorldRotationLockState() const;
/// <summary>
/// Sets whether the world-rotation of a joint has been 'locked' so that as its parent joints change rotation or position, this joint keeps a constant world rotation.
/// </summary>
/// <param name="newState">The new state for the world-rotation lock.</param>
void setWorldRotationLockState(bool newState);
/// <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,22 +208,16 @@ class FSJointPose
public:
FSJointState(LLJoint* joint)
{
mStartingRotation.set(joint->getRotation());
mBaseRotation.set(joint->getRotation());
mBasePosition.set(joint->getPosition());
mBaseScale.set(joint->getScale());
}
FSJointState() = default;
LLQuaternion mDeltaRotation;
LLQuaternion getTargetRotation() const { return mRotation * mBaseRotation; }
LLVector3 getTargetPosition() const { return mPosition + mBasePosition; }
LLVector3 getTargetScale() const { return mScale + mBaseScale; }
void updateRotation(const LLQuaternion& newRotation)
{
auto inv_base = mBaseRotation;
inv_base.conjugate();
mDeltaRotation = newRotation * inv_base;
};
void reflectRotation()
{
@ -209,10 +231,21 @@ class FSJointPose
{
mBaseRotation.set(otherState.mBaseRotation);
mRotation.set(otherState.mRotation);
mUserSpecifiedBaseZero = otherState.mUserSpecifiedBaseZero;
}
bool baseRotationIsZero() const { return mBaseRotation == LLQuaternion::DEFAULT; }
void resetJoint()
{
mUserSpecifiedBaseZero = false;
mRotationIsWorldLocked = false;
mBaseRotation.set(mStartingRotation);
mRotation.set(LLQuaternion::DEFAULT);
mPosition.setZero();
mScale.setZero();
}
void zeroBaseRotation() { mBaseRotation = LLQuaternion::DEFAULT; }
void revertJointToBase(LLJoint* joint) const
@ -225,21 +258,33 @@ class FSJointPose
joint->setScale(mBaseScale);
}
void updateFromJoint(LLJoint* joint)
LLQuaternion updateFromJoint(LLJoint* joint, bool zeroBase)
{
if (!joint)
return;
return LLQuaternion::DEFAULT;
LLQuaternion initalPublicRot = mRotation;
LLQuaternion invRot = mBaseRotation;
invRot.conjugate();
mRotation = joint->getRotation() * invRot;
LLQuaternion newPublicRot = joint->getRotation() * invRot;
if (zeroBase)
{
mUserSpecifiedBaseZero = zeroBase;
zeroBaseRotation();
}
mRotation.set(newPublicRot);
mPosition.set(joint->getPosition() - mBasePosition);
mScale.set(joint->getScale() - mBaseScale);
return newPublicRot *= ~initalPublicRot;
}
private:
FSJointState(FSJointState* state)
{
mStartingRotation.set(state->mStartingRotation);
mBaseRotation.set(state->mBaseRotation);
mBasePosition.set(state->mBasePosition);
mBaseScale.set(state->mBaseScale);
@ -247,14 +292,29 @@ class FSJointPose
mRotation.set(state->mRotation);
mPosition.set(state->mPosition);
mScale.set(state->mScale);
mUserSpecifiedBaseZero = state->mUserSpecifiedBaseZero;
mRotationIsWorldLocked = state->mRotationIsWorldLocked;
}
public:
LLQuaternion mRotation;
LLVector3 mPosition;
LLVector3 mScale;
bool mRotationIsWorldLocked = false;
/// <summary>
/// A value indicating whether the user has explicitly set the base rotation to zero.
/// </summary>
/// <remarks>
/// 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).
/// </remarks>
bool mUserSpecifiedBaseZero = false;
private:
LLQuaternion mStartingRotation;
LLQuaternion mBaseRotation;
LLVector3 mBasePosition;
LLVector3 mBaseScale;

View File

@ -95,6 +95,7 @@ void FSPoserAnimator::undoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint
return;
jointPose->undoLastChange();
undoOrRedoWorldLockedDescendants(joint, posingMotion, false);
if (style == NONE || style == DELTAMODE)
return;
@ -104,6 +105,10 @@ void FSPoserAnimator::undoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint
return;
oppositeJointPose->undoLastChange();
auto oppositePoserJoint = getPoserJointByName(joint.mirrorJointName());
if (oppositePoserJoint)
undoOrRedoWorldLockedDescendants(*oppositePoserJoint, posingMotion, false);
}
void FSPoserAnimator::resetJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneDeflectionStyles style)
@ -122,9 +127,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 +136,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)
@ -177,6 +178,7 @@ void FSPoserAnimator::redoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint
return;
jointPose->redoLastChange();
undoOrRedoWorldLockedDescendants(joint, posingMotion, true);
if (style == NONE || style == DELTAMODE)
return;
@ -186,6 +188,10 @@ void FSPoserAnimator::redoLastJointChange(LLVOAvatar* avatar, const FSPoserJoint
return;
oppositeJointPose->redoLastChange();
auto oppositePoserJoint = getPoserJointByName(joint.mirrorJointName());
if (oppositePoserJoint)
undoOrRedoWorldLockedDescendants(*oppositePoserJoint, posingMotion, true);
}
LLVector3 FSPoserAnimator::getJointPosition(LLVOAvatar* avatar, const FSPoserJoint& joint) const
@ -265,7 +271,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 +284,67 @@ bool FSPoserAnimator::baseRotationIsZero(LLVOAvatar* avatar, const FSPoserJoint&
if (!jointPose)
return false;
return jointPose->isBaseRotationZero();
return jointPose->getWorldRotationLockState();
}
void FSPoserAnimator::setRotationIsWorldLocked(LLVOAvatar* avatar, const FSPoserJoint& joint, bool newState)
{
if (!isAvatarSafeToUse(avatar))
return;
FSPosingMotion* posingMotion = getPosingMotion(avatar);
if (!posingMotion)
return;
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName());
if (!jointPose)
return;
jointPose->setWorldRotationLockState(newState);
}
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->userHasSetBaseRotationToZero())
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->userHasSetBaseRotationToZero();
}
bool FSPoserAnimator::allBaseRotationsAreZero(LLVOAvatar* avatar) const
@ -307,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)
@ -326,7 +407,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 +421,9 @@ void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoi
if (!jointPose)
return;
jointPose->recaptureJointAsDelta();
LLQuaternion deltaRot = jointPose->recaptureJointAsDelta(resetBaseRotationToZero);
deRotateWorldLockedDescendants(joint, posingMotion, deltaRot);
if (style == NONE || style == DELTAMODE)
return;
@ -365,6 +449,35 @@ void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoi
}
}
LLVector3 FSPoserAnimator::getJointExportRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, bool lockWholeAvatar) 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->userHasSetBaseRotationToZero())
return vec3;
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);
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 +512,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,53 +519,65 @@ 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());
deRotateWorldLockedDescendants(joint, posingMotion, deltaRot);
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);
deRotateWorldLockedDescendants(joint, posingMotion, deltaRot);
return;
}
deRotateWorldLockedDescendants(joint, posingMotion, deltaRot);
auto oppositePoserJoint = getPoserJointByName(joint->mirrorJointName());
FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint->mirrorJointName());
if (!oppositeJointPose)
return;
LLQuaternion inv_quat;
LLQuaternion inv_quat = LLQuaternion(-deltaRot.mQ[VX], deltaRot.mQ[VY], -deltaRot.mQ[VZ], deltaRot.mQ[VW]);
switch (deflectionStyle)
{
case SYMPATHETIC:
oppositeJointPose->cloneRotationFrom(jointPose);
if (oppositePoserJoint)
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, deltaRot);
break;
case SYMPATHETIC_DELTA:
oppositeJointPose->setPublicRotation(deltaRot * oppositeJointPose->getPublicRotation());
oppositeJointPose->setPublicRotation(resetBaseRotationToZero, deltaRot * oppositeJointPose->getPublicRotation());
if (oppositePoserJoint)
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, deltaRot);
break;
case MIRROR:
oppositeJointPose->mirrorRotationFrom(jointPose);
if (oppositePoserJoint)
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, inv_quat);
break;
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());
if (oppositePoserJoint)
deRotateWorldLockedDescendants(oppositePoserJoint, posingMotion, inv_quat);
break;
default:
@ -757,11 +879,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 +923,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 +1020,118 @@ 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;
}
void FSPoserAnimator::deRotateWorldLockedDescendants(const FSPoserJoint* joint, FSPosingMotion* posingMotion, LLQuaternion rotationChange)
{
size_t numberOfBvhChildNodes = joint->bvhChildren().size();
if (numberOfBvhChildNodes < 1)
return;
FSJointPose* parentJoint = posingMotion->getJointPoseByJointName(joint->jointName());
if (!parentJoint)
return;
LLJoint* pJoint = parentJoint->getJointState()->getJoint();
for (size_t index = 0; index != numberOfBvhChildNodes; ++index)
{
auto nextJoint = getPoserJointByName(joint->bvhChildren()[index]);
if (!nextJoint)
continue;
deRotateJointOrFirstLockedChild(nextJoint, posingMotion, pJoint->getWorldRotation(), rotationChange);
}
}
void FSPoserAnimator::deRotateJointOrFirstLockedChild(const FSPoserJoint* joint, FSPosingMotion* posingMotion, LLQuaternion rotatedParentWorldRot, LLQuaternion rotationChange)
{
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint->jointName());
if (!jointPose)
return;
if (jointPose->getWorldRotationLockState())
{
LLQuaternion worldRotOfThisJoint = jointPose->getJointState()->getJoint()->getWorldRotation();
LLQuaternion differenceInWorldRot = worldRotOfThisJoint * ~rotatedParentWorldRot;
LLQuaternion rotDiffInChildFrame = differenceInWorldRot * rotationChange * ~differenceInWorldRot;
rotDiffInChildFrame.conjugate();
jointPose->setPublicRotation(false, rotDiffInChildFrame * jointPose->getPublicRotation());
return;
}
size_t numberOfBvhChildNodes = joint->bvhChildren().size();
if (numberOfBvhChildNodes < 1)
return;
for (size_t index = 0; index != numberOfBvhChildNodes; ++index)
{
auto nextJoint = getPoserJointByName(joint->bvhChildren()[index]);
if (!nextJoint)
continue;
deRotateJointOrFirstLockedChild(nextJoint, posingMotion, rotatedParentWorldRot, rotationChange);
}
}
void FSPoserAnimator::undoOrRedoWorldLockedDescendants(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo)
{
size_t numberOfBvhChildNodes = joint.bvhChildren().size();
if (numberOfBvhChildNodes < 1)
return;
for (size_t index = 0; index != numberOfBvhChildNodes; ++index)
{
auto nextJoint = getPoserJointByName(joint.bvhChildren()[index]);
if (!nextJoint)
continue;
undoOrRedoJointOrFirstLockedChild(*nextJoint, posingMotion, redo);
}
}
void FSPoserAnimator::undoOrRedoJointOrFirstLockedChild(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo)
{
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName());
if (!jointPose)
return;
if (jointPose->getWorldRotationLockState())
{
redo ? jointPose->redoLastChange() : jointPose->undoLastChange();
return;
}
size_t numberOfBvhChildNodes = joint.bvhChildren().size();
if (numberOfBvhChildNodes < 1)
return;
for (size_t index = 0; index != numberOfBvhChildNodes; ++index)
{
auto nextJoint = getPoserJointByName(joint.bvhChildren()[index]);
if (!nextJoint)
continue;
undoOrRedoJointOrFirstLockedChild(*nextJoint, posingMotion, redo);
}
}

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,19 @@ 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>
/// <param name="lockWholeAvatar">Whether the whole avatar should be rotation/position locked in the BVH export.</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, bool lockWholeAvatar) const;
/// <summary>
/// Sets the rotation of a joint for the supplied avatar.
/// </summary>
@ -559,8 +572,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 +588,34 @@ 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>
/// Sets the world-rotation-lock status for supplied joint for the supplied avatar.
/// </summary>
/// <param name="avatar">The avatar owning the supplied joint.</param>
/// <param name="joint">The joint to query.</param>
/// <param name="newState">The lock state to apply.</param>
void setRotationIsWorldLocked(LLVOAvatar* avatar, const FSPoserJoint& joint, bool newState);
/// <summary>
/// Determines if the kind of save to perform should be a 'delta' save, or a complete save.
@ -692,6 +733,53 @@ 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>
/// Derotates the first world-locked child joint to the supplied joint.
/// </summary>
/// <param name="joint">The edited joint, whose children may be world-locked.</param>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="rotation">The rotation the supplied joint was/is being changed by.</param>
/// <remarks>
/// There are two ways to resolve this problem: before the rotation is applied in the PosingMotion (the animation) or after.
/// If performed after, a feedback loop is created, because you're noting the world-rotation in one frame, then correcting it back to that in another.
/// This implementation works by applying an opposing-rotation to the locked child joint which is corrected for the relative world-rotations of parent and child.
/// </remarks>
void deRotateWorldLockedDescendants(const FSPoserJoint* joint, FSPosingMotion* posingMotion, LLQuaternion rotation);
/// <summary>
/// Recursively tests the supplied joint and all its children for their world-locked status, and applies a de-rotation if it is world-locked.
/// </summary>
/// <param name="joint">The edited joint, whose children may be world-locked.</param>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="parentWorldRot">The world-rotation of the joint that was edited.</param>
/// <param name="rotation">The rotation the joint was edit is being changed by.</param>
void deRotateJointOrFirstLockedChild(const FSPoserJoint* joint, FSPosingMotion* posingMotion, LLQuaternion parentWorldRot,
LLQuaternion rotation);
/// <summary>
/// Performs an undo or redo of an edit to the supplied joints world-locked descendants.
/// </summary>
/// <param name="joint">The edited joint, whose children may be world-locked.</param>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="redo">Whether to redo the edit, otherwise the edit is undone.</param>
void undoOrRedoWorldLockedDescendants(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo);
/// <summary>
/// Recursively tests the supplied joint and all its children for their world-locked status, and applies an undo or redo if it is world-locked.
/// </summary>
/// <param name="joint">The joint which will have the undo or redo performed, if it is world locked.</param>
/// <param name="posingMotion">The posing motion.</param>
/// <param name="redo">Whether to redo the edit, otherwise the edit is undone.</param>
void undoOrRedoJointOrFirstLockedChild(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo);
/// <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,11 +259,16 @@ 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);
}
}
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]) &&

View File

@ -121,8 +121,17 @@ public:
/// <summary>
/// Sets all of the non-Collision Volume base-and-delta rotations to zero, and clears the undo/redo queue.
/// </summary>
/// <remarks>
/// By default, sets the joint to lock in BVH export.
/// </remarks>
void setAllRotationsToZeroAndClearUndo();
/// <summary>
/// Sets the BVH export state for the supplied joint.
/// </summary>
/// <param name="lockInBvh">Whether the joint should be locked if exported to BVH.</param>
void setJointBvhLock(FSJointPose* joint, bool lockInBvh);
private:
/// <summary>
/// The axial difference considered close enough to be the same.

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>
@ -548,6 +553,17 @@ width="430">
function="Poser.CommitSpinner"
parameter="2"/>
</spinner>
<check_box
control_name="FSPoserPelvisUnlockedForBvhSave"
name="unlock_pelvis_for_bvh_save_checkbox"
visible="false"
height="16"
label="Unlock Pelvis in BVH"
follows="left|top"
left="5"
tool_tip="When you save this pose to BVH, the Pelvis will not be 'locked'. This means other animations in-world can 'move' the whole avatar. Use this if your pose is meant to play 'on top of' another pose."
top_pad="15"
width="134" />
<!-- to make this panel behaves like the others in code-behind, it has an invisible list -->
<scroll_list
visible="false"
@ -965,26 +981,15 @@ 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**"
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."
top_pad="2"
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="5"
width="134" />
<check_box
control_name="FSPoserOnSaveConfirmOverwrite"
@ -1005,7 +1010,7 @@ width="430">
left="5"
tool_tip="The skeleton has unnatural joint rotations by default. This complicates posing. When checked, the joints will rotate in a more natural way."
top_pad="5"
width="134" />
width="134" />
<check_box
control_name="FSManipShowJointMarkers"
name="show_joint_markers_checkbox"
@ -1015,7 +1020,17 @@ width="430">
left="5"
tool_tip="Show small indicators to aid joint selection when visually posing."
top_pad="5"
width="134" />
width="134" />
<check_box
control_name="RenderDepthOfFieldInEditMode"
name="dof_edit_mode_checkbox"
height="16"
label="Enable DoF in Edit Mode"
follows="left|top"
left="5"
tool_tip="When using the Manipulator, Depth of Field normally disables automatically. This keeps Depth of Field on when editing poses or prims."
top_pad="5"
width="134" />
</panel>
</tab_container>
<button
@ -1556,6 +1571,20 @@ width="430">
top_delta="0"
left_pad="1">
</button>
<button
follows="left|top"
height="21"
layout="topleft"
image_overlay="Locked_Icon"
image_hover_unselected="Toolbar_Middle_Over"
image_selected="Toolbar_Middle_Selected"
image_unselected="Toolbar_Middle_Off"
name="toggle_LockWorldRotation"
left_pad="1"
top_delta="0"
tool_tip="Lock rotation in the world for the selected limb(s). Limbs that are world-locked keep the same rotation in-world when their parent-limbs move. Eg: lock your eyes and turn your head, your eyes keep looking (mostly) in the same direction."
width="18" >
</button>
<button
follows="left|top"
height="21"
@ -1573,7 +1602,7 @@ width="430">
tool_tip="Mirror changes to the opposite joint."
left_pad="1"
top_delta="0"
width="52" >
width="43" >
<button.commit_callback
function="Poser.ToggleMirrorChanges"/>
</button>
@ -1594,7 +1623,7 @@ width="430">
tool_tip="Copy changes to the opposite joint."
left_pad="1"
top_delta="0"
width="52" >
width="43" >
<button.commit_callback
function="Poser.ToggleSympatheticChanges"/>
</button>