diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 7531ea03c2..c2635e1933 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8111,10 +8111,10 @@ Value 0 - FSPoserResetBaseRotationOnEdit + FSPoserPelvisUnlockedForBvhSave Comment - Whether to reset the base-rotation of a joint to zero when a user edits it. + Whether the mPelvis joint should be position/rotationally locked when a BVH is created. Persist 1 Type diff --git a/indra/newview/fsfloaterposer.cpp b/indra/newview/fsfloaterposer.cpp index 1fd1bdbd32..7594f57960 100644 --- a/indra/newview/fsfloaterposer.cpp +++ b/indra/newview/fsfloaterposer.cpp @@ -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("FlipJoint_avatar"); mRecaptureBtn = getChild("button_RecaptureParts"); mTogglePosingBonesBtn = getChild("toggle_PosingSelectedBones"); + mToggleLockWorldRotBtn = getChild("toggle_LockWorldRotation"); + mToggleLockWorldRotBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onClickLockWorldRotBtn(); }); mToggleMirrorRotationBtn = getChild("button_toggleMirrorRotation"); mToggleSympatheticRotationBtn = getChild("button_toggleSympatheticRotation"); @@ -211,9 +213,10 @@ 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"); - mResetBaseRotCbx = getChild("reset_base_rotation_on_edit_checkbox"); - mResetBaseRotCbx->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickSetBaseRotZero(); }); + mAlsoSaveBvhCbx->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickSavingToBvh(); }); mTrackpadSensitivitySpnr = getChild("trackpad_sensitivity_spinner"); mYawSpnr = getChild("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(listItem->getUserdata()); - if (!userData) + FSPoserAnimator::FSPoserJoint *poserJoint = static_cast(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(); +} diff --git a/indra/newview/fsfloaterposer.h b/indra/newview/fsfloaterposer.h index 3603ca659b..69c96a769c 100644 --- a/indra/newview/fsfloaterposer.h +++ b/indra/newview/fsfloaterposer.h @@ -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); /// - /// 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. /// - /// - /// 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. - /// - bool getWhetherToResetBaseRotationOnEdit(); + /// The avatar owning the supplied joint. + /// The joint to query. + /// A string naming an icon to present with the joint. + std::string getScrollListIconForJoint(LLVOAvatar* avatar, FSPoserAnimator::FSPoserJoint joint); + + /// + /// Tries to get the named string from the XUI. + /// + /// The name of the string. + /// The named string, if it exists, otherwise an empty string. + std::string tryGetString(std::string name); /// /// 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 }; diff --git a/indra/newview/fsjointpose.cpp b/indra/newview/fsjointpose.cpp index 608e737aba..e1acbce4d0 100644 --- a/indra/newview/fsjointpose.cpp +++ b/indra/newview/fsjointpose.cpp @@ -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()) diff --git a/indra/newview/fsjointpose.h b/indra/newview/fsjointpose.h index 05c96475ff..e4b91f29b6 100644 --- a/indra/newview/fsjointpose.h +++ b/indra/newview/fsjointpose.h @@ -77,6 +77,11 @@ class FSJointPose /// void redoLastChange(); + /// + /// Resets the joint to its conditions when posing started. + /// + void resetJoint(); + /// /// Gets the 'public' rotation of the joint. /// @@ -85,12 +90,14 @@ class FSJointPose /// /// Sets the 'public' rotation of the joint. /// + /// Whether to zero the base rotation on setting the supplied rotation. + /// The change in rotation to apply. /// /// '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. /// - void setPublicRotation(const LLQuaternion& rot); + void setPublicRotation(bool zeroBase, const LLQuaternion& rot); /// /// Reflects the base and delta rotation of the represented joint left-right. @@ -100,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. @@ -153,13 +161,33 @@ class FSJointPose /// /// Recalculates the delta reltive to the base for a new rotation. /// - void recaptureJointAsDelta(); + /// Whether to zero the base rotation on setting the supplied rotation. + /// The rotation of the public difference between before and after recapture. + LLQuaternion recaptureJointAsDelta(bool zeroBase); /// /// Clears the undo/redo deque. /// void purgeUndoQueue(); + /// + /// 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 userHasSetBaseRotationToZero() const; + + /// + /// Gets whether the rotation of a joint has been 'locked' so that its world rotation can remain constant while parent joints change. + /// + /// True if the joint is rotationally locked to the world, otherwise false. + bool getWorldRotationLockState() const; + + /// + /// 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. + /// + /// The new state for the world-rotation lock. + void setWorldRotationLockState(bool newState); + /// /// 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; + + /// + /// 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; diff --git a/indra/newview/fsposeranimator.cpp b/indra/newview/fsposeranimator.cpp index 62d9030c85..ba91cf356f 100644 --- a/indra/newview/fsposeranimator.cpp +++ b/indra/newview/fsposeranimator.cpp @@ -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); + } +} diff --git a/indra/newview/fsposeranimator.h b/indra/newview/fsposeranimator.h index 8e34b9ba59..853b15d308 100644 --- a/indra/newview/fsposeranimator.h +++ b/indra/newview/fsposeranimator.h @@ -392,7 +392,7 @@ public: /// /// The name of the joint to match. /// The matching joint if found, otherwise nullptr - const FSPoserJoint* getPoserJointByName(const std::string& jointName); + const FSPoserJoint* getPoserJointByName(const std::string& jointName) const; /// /// Tries to start posing the supplied avatar. @@ -492,6 +492,19 @@ public: /// The rotation of the requested joint, if determinable, otherwise a default vector. LLVector3 getJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation) const; + /// + /// Gets the rotation of a joint for the supplied avatar for export. + /// + /// 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, bool lockWholeAvatar) const; + /// /// Sets the rotation of a joint for the supplied avatar. /// @@ -559,8 +572,9 @@ public: /// /// The avatar whose joint is to be recaptured. /// The joint to recapture. + /// Whether to set the base rotation to zero on setting the rotation. /// Any ancilliary action to be taken with the change to be made. - void recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint* joint, E_BoneDeflectionStyles style); + void recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint* joint, bool resetBaseRotationToZero, E_BoneDeflectionStyles style); /// /// Sets all of the joint rotations of the supplied avatar to zero. @@ -574,7 +588,34 @@ public: /// The avatar owning the supplied joint. /// The joint to query. /// True if the supplied joint has a 'base' rotation of zero (thus user-supplied change only), otherwise false. - bool baseRotationIsZero(LLVOAvatar* avatar, const FSPoserJoint& joint) const; + bool userSetBaseRotationToZero(LLVOAvatar* avatar, const FSPoserJoint& joint) const; + + /// + /// Gets whether the supplied joints position will be set in an export. + /// + /// The avatar owning the supplied joint. + /// The joint to query. + /// True if the export will 'lock' the joint, otherwise false. + /// + /// BVH import leaves a joint 'free' if its rotation is less than something arbitrary. + /// + bool exportRotationWillLockJoint(LLVOAvatar* avatar, const FSPoserJoint& joint) const; + + /// + /// Gets whether the supplied joint for the supplied avatar is rotationally locked to the world. + /// + /// The avatar owning the supplied joint. + /// The joint to query. + /// True if the joint is maintaining a fixed-rotation in world, otherwise false. + bool getRotationIsWorldLocked(LLVOAvatar* avatar, const FSPoserJoint& joint) const; + + /// + /// Sets the world-rotation-lock status for supplied joint for the supplied avatar. + /// + /// The avatar owning the supplied joint. + /// The joint to query. + /// The lock state to apply. + void setRotationIsWorldLocked(LLVOAvatar* avatar, const FSPoserJoint& joint, bool newState); /// /// Determines if the kind of save to perform should be a 'delta' save, or a complete save. @@ -692,6 +733,53 @@ public: /// True if the avatar is safe to manipulate, otherwise false. bool isAvatarSafeToUse(LLVOAvatar* avatar) const; + /// + /// Gets the depth of descendant joints for the supplied joint. + /// + /// The joint to determine the depth for. + /// The depth of the supplied joint. + /// The number of generations of descendents the joint has, if none, then zero. + int getChildJointDepth(const FSPoserJoint* joint, int depth) const; + + /// + /// Derotates the first world-locked child joint to the supplied joint. + /// + /// The edited joint, whose children may be world-locked. + /// The posing motion. + /// The rotation the supplied joint was/is being changed by. + /// + /// 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. + /// + void deRotateWorldLockedDescendants(const FSPoserJoint* joint, FSPosingMotion* posingMotion, LLQuaternion rotation); + + /// + /// Recursively tests the supplied joint and all its children for their world-locked status, and applies a de-rotation if it is world-locked. + /// + /// The edited joint, whose children may be world-locked. + /// The posing motion. + /// The world-rotation of the joint that was edited. + /// The rotation the joint was edit is being changed by. + void deRotateJointOrFirstLockedChild(const FSPoserJoint* joint, FSPosingMotion* posingMotion, LLQuaternion parentWorldRot, + LLQuaternion rotation); + + /// + /// Performs an undo or redo of an edit to the supplied joints world-locked descendants. + /// + /// The edited joint, whose children may be world-locked. + /// The posing motion. + /// Whether to redo the edit, otherwise the edit is undone. + void undoOrRedoWorldLockedDescendants(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo); + + /// + /// 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. + /// + /// The joint which will have the undo or redo performed, if it is world locked. + /// The posing motion. + /// Whether to redo the edit, otherwise the edit is undone. + void undoOrRedoJointOrFirstLockedChild(const FSPoserJoint& joint, FSPosingMotion* posingMotion, bool redo); + /// /// 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. diff --git a/indra/newview/fsposingmotion.cpp b/indra/newview/fsposingmotion.cpp index 841ab3a452..15fc7d6bb8 100644 --- a/indra/newview/fsposingmotion.cpp +++ b/indra/newview/fsposingmotion.cpp @@ -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]) && 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 dc6646c554..a2d35e8384 100644 --- a/indra/newview/skins/default/xui/en/floater_fs_poser.xml +++ b/indra/newview/skins/default/xui/en/floater_fs_poser.xml @@ -11,6 +11,11 @@ width="430"> Inv_BodyShape Inv_Object + Script_Running + Script_NotRunning + Script_Error + Locked_Icon + Conv_toolbar_close Check_Mark Icon_Dock_Foreground Parcel_Exp_Color @@ -548,6 +553,17 @@ width="430"> function="Poser.CommitSpinner" parameter="2"/> + 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" /> - 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" /> left="5" tool_tip="Show small indicators to aid joint selection when visually posing." top_pad="5" - width="134" /> + width="134" /> + + @@ -1594,7 +1623,7 @@ width="430"> tool_tip="Copy changes to the opposite joint." left_pad="1" top_delta="0" - width="52" > + width="43" >