Merge remote-tracking branch 'origin/PoserManipSave'

master
Beq 2025-04-16 22:00:11 +01:00
commit 08a9250eba
11 changed files with 612 additions and 404 deletions

View File

@ -8080,17 +8080,6 @@
<key>Value</key>
<integer>0</integer>
</map>
<key>FSPoserAdvancedWindowState</key>
<map>
<key>Comment</key>
<string>Whether the 'advanced' pane is shown when opening the Avatar/Animesh Poser.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>FSPoserSaveExternalFileAlso</key>
<map>
<key>Comment</key>
@ -8113,6 +8102,17 @@
<key>Value</key>
<integer>0</integer>
</map>
<key>FSPoserOnSaveConfirmOverwrite</key>
<map>
<key>Comment</key>
<string>Whether to confirm overwriting a save file.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>FSPoserStopPosingWhenClosed</key>
<map>
<key>Comment</key>

View File

@ -61,6 +61,7 @@ constexpr std::string_view POSER_TRACKPAD_SENSITIVITY_SAVE_KEY = "FSPoserTrackpa
constexpr std::string_view POSER_STOPPOSINGWHENCLOSED_SAVE_KEY = "FSPoserStopPosingWhenClosed";
constexpr std::string_view POSER_RESETBASEROTONEDIT_SAVE_KEY = "FSPoserResetBaseRotationOnEdit";
constexpr std::string_view POSER_SAVEEXTERNALFORMAT_SAVE_KEY = "FSPoserSaveExternalFileAlso";
constexpr std::string_view POSER_SAVECONFIRMREQUIRED_SAVE_KEY = "FSPoserOnSaveConfirmOverwrite";
} // namespace
/// <summary>
@ -83,11 +84,10 @@ FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key)
mCommitCallbackRegistrar.add("Poser.ToggleSympatheticChanges", [this](LLUICtrl*, const LLSD&) { onToggleSympatheticChange(); });
mCommitCallbackRegistrar.add("Poser.AdjustTrackPadSensitivity", [this](LLUICtrl*, const LLSD&) { onAdjustTrackpadSensitivity(); });
mCommitCallbackRegistrar.add("Poser.PositionSet", [this](LLUICtrl*, const LLSD&) { onAvatarPositionSet(); });
mCommitCallbackRegistrar.add("Poser.PositionSet", [this](LLUICtrl*, const LLSD&) { onPositionSet(); });
mCommitCallbackRegistrar.add("Poser.SetToTPose", [this](LLUICtrl*, const LLSD&) { onSetAvatarToTpose(); });
mCommitCallbackRegistrar.add("Poser.Advanced.PositionSet", [this](LLUICtrl*, const LLSD&) { onAdvancedPositionSet(); });
mCommitCallbackRegistrar.add("Poser.Advanced.ScaleSet", [this](LLUICtrl*, const LLSD&) { onAdvancedScaleSet(); });
mCommitCallbackRegistrar.add("Poser.Advanced.ScaleSet", [this](LLUICtrl*, const LLSD&) { onScaleSet(); });
mCommitCallbackRegistrar.add("Poser.UndoLastPosition", [this](LLUICtrl*, const LLSD&) { onUndoLastChange(); });
mCommitCallbackRegistrar.add("Poser.RedoLastPosition", [this](LLUICtrl*, const LLSD&) { onRedoLastChange(); });
mCommitCallbackRegistrar.add("Poser.ResetJoint", [this](LLUICtrl*, const LLSD& data) { onResetJoint(data); });
@ -104,13 +104,14 @@ FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key)
mCommitCallbackRegistrar.add("Poser.TogglePosingSelectedBones", [this](LLUICtrl*, const LLSD&) { onClickToggleSelectedBoneEnabled(); });
mCommitCallbackRegistrar.add("Poser.CommitSpinner", [this](LLUICtrl* spinner, const LLSD& data) { onCommitSpinner(spinner, data); });
mCommitCallbackRegistrar.add("Poser.CommitSlider", [this](LLUICtrl* slider, const LLSD& data) { onCommitSlider(slider, data); });
mCommitCallbackRegistrar.add("Poser.Symmetrize", [this](LLUICtrl*, const LLSD& data) { onClickSymmetrize(data); });
}
bool FSFloaterPoser::postBuild()
{
mAvatarTrackball = getChild<FSVirtualTrackpad>("limb_rotation");
mAvatarTrackball->setCommitCallback([this](LLUICtrl *, const LLSD &) { onLimbTrackballChanged(); });
mAvatarTrackball->setCommitCallback([this](LLUICtrl *, const LLSD &) { onTrackballChanged(); });
mJointsTabs = getChild<LLTabContainer>("joints_tabs");
mJointsTabs->setCommitCallback(
@ -164,6 +165,10 @@ bool FSFloaterPoser::postBuild()
mPosYSlider = getChild<LLSliderCtrl>("av_position_leftright");
mPosZSlider = getChild<LLSliderCtrl>("av_position_updown");
mAdvRotXSlider = getChild<LLSliderCtrl>("limb_pitch_slider");
mAdvRotYSlider = getChild<LLSliderCtrl>("limb_yaw_slider");
mAdvRotZSlider = getChild<LLSliderCtrl>("limb_roll_slider");
mAdvPosXSlider = getChild<LLSliderCtrl>("Advanced_Position_X");
mAdvPosYSlider = getChild<LLSliderCtrl>("Advanced_Position_Y");
mAdvPosZSlider = getChild<LLSliderCtrl>("Advanced_Position_Z");
@ -178,6 +183,7 @@ bool FSFloaterPoser::postBuild()
mBrowserFolderBtn = getChild<LLButton>("open_poseDir_button");
mLoadPosesBtn = getChild<LLButton>("load_poses_button");
mSavePosesBtn = getChild<LLButton>("save_poses_button");
mSavePosesBtn->setMouseLeaveCallback([this](LLUICtrl*, const LLSD&) { onMouseLeaveSavePoseBtn(); });
mFlipPoseBtn = getChild<LLButton>("FlipPose_avatar");
mFlipJointBtn = getChild<LLButton>("FlipJoint_avatar");
@ -188,6 +194,7 @@ bool FSFloaterPoser::postBuild()
mToggleSympatheticRotationBtn = getChild<LLButton>("button_toggleSympatheticRotation");
mToggleDeltaModeBtn = getChild<LLButton>("delta_mode_toggle");
mRedoChangeBtn = getChild<LLButton>("button_redo_change");
mUndoChangeBtn = getChild<LLButton>("undo_change");
mSetToTposeButton = getChild<LLButton>("set_t_pose_button");
mJointsParentPnl = getChild<LLPanel>("joints_parent_panel");
@ -252,15 +259,23 @@ void FSFloaterPoser::onFocusLost()
LLEditMenuHandler::gEditMenuHandler = nullptr;
}
}
void FSFloaterPoser::enableVisualManipulators()
{
if (!gAgentAvatarp || gAgentAvatarp.isNull())
{
mToggleVisualManipulators->setToggleState(false);
return;
}
if (LLToolMgr::getInstance()->getCurrentToolset() != gCameraToolset)
{
mLastToolset = LLToolMgr::getInstance()->getCurrentToolset();
}
LLToolMgr::getInstance()->setCurrentToolset(gPoserToolset);
LLToolMgr::getInstance()->getCurrentToolset()->selectTool(FSToolCompPose::getInstance());
FSToolCompPose::getInstance()->setAvatar( gAgentAvatarp);
FSToolCompPose::getInstance()->setAvatar(gAgentAvatarp);
}
void FSFloaterPoser::disableVisualManipulators()
@ -347,16 +362,22 @@ void FSFloaterPoser::onPoseFileSelect()
mPoseSaveNameEditor->setText(name);
bool isDeltaSave = !poseFileStartsFromTeePose(name);
if (isDeltaSave)
if (isDeltaSave && hasString("LoadDiffLabel"))
mLoadPosesBtn->setLabel(getString("LoadDiffLabel"));
else
else if (hasString("LoadPoseLabel"))
mLoadPosesBtn->setLabel(getString("LoadPoseLabel"));
}
void FSFloaterPoser::onClickPoseSave()
{
std::string filename = mPoseSaveNameEditor->getValue().asString();
if (filename.empty())
if (filename.empty() && hasString("icon_save_failed_button"))
{
mSavePosesBtn->setImageOverlay(getString("icon_save_failed_button"), mSavePosesBtn->getImageOverlayHAlign());
return;
}
if (confirmFileOverwrite(filename))
return;
LLVOAvatar* avatar = getUiSelectedAvatar();
@ -372,8 +393,55 @@ void FSFloaterPoser::onClickPoseSave()
if (getSavingToBvh())
savePoseToBvh(avatar, filename);
// TODO: provide feedback for save
if (hasString("icon_rotation_is_own_work"))
mSavePosesBtn->setImageOverlay(getString("icon_rotation_is_own_work"), mSavePosesBtn->getImageOverlayHAlign());
setSavePosesButtonText(!mPoserAnimator.allBaseRotationsAreZero(avatar));
}
else
{
if (hasString("icon_save_failed_button"))
mSavePosesBtn->setImageOverlay(getString("icon_save_failed_button"), mSavePosesBtn->getImageOverlayHAlign());
}
}
bool FSFloaterPoser::confirmFileOverwrite(std::string fileName)
{
if (fileName.empty())
return false;
if (!gSavedSettings.getBOOL(POSER_SAVECONFIRMREQUIRED_SAVE_KEY))
return false;
if (!hasString("icon_save_query"))
return false;
if (mSavePosesBtn->getImageOverlay().notNull() && mSavePosesBtn->getImageOverlay()->getName() == getString("icon_save_query"))
return false;
std::string fullSavePath =
gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_SAVE_SUBDIRECTORY, fileName + POSE_INTERNAL_FORMAT_FILE_EXT);
if (!gDirUtilp->fileExists(fullSavePath))
return false;
mSavePosesBtn->setImageOverlay(getString("icon_save_query"), mSavePosesBtn->getImageOverlayHAlign());
if (hasString("OverWriteLabel"))
mSavePosesBtn->setLabel(getString("OverWriteLabel"));
return true;
}
void FSFloaterPoser::onMouseLeaveSavePoseBtn()
{
if (hasString("icon_save_button"))
mSavePosesBtn->setImageOverlay(getString("icon_save_button"), mSavePosesBtn->getImageOverlayHAlign());
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
return;
setSavePosesButtonText(!mPoserAnimator.allBaseRotationsAreZero(avatar));
}
void FSFloaterPoser::createUserPoseDirectoryIfNeeded()
@ -552,6 +620,7 @@ void FSFloaterPoser::onClickFlipSelectedJoints()
}
refreshRotationSlidersAndSpinners();
enableOrDisableRedoAndUndoButton();
refreshTrackpadCursor();
}
@ -596,13 +665,11 @@ void FSFloaterPoser::onClickRecaptureSelectedBones()
refreshRotationSlidersAndSpinners();
refreshTrackpadCursor();
refreshTextHighlightingOnJointScrollLists();
enableOrDisableRedoAndUndoButton();
}
void FSFloaterPoser::updatePosedBones()
{
auto selectedJoints = getUiSelectedPoserJoints();
if (selectedJoints.size() < 1)
return;
void FSFloaterPoser::updatePosedBones(const std::string& jointName)
{
LLVOAvatar *avatar = getUiSelectedAvatar();
if (!avatar)
return;
@ -610,18 +677,17 @@ void FSFloaterPoser::updatePosedBones()
if (!mPoserAnimator.isPosingAvatar(avatar))
return;
for (auto item : selectedJoints)
{
bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item);
if (!currentlyPosing)
continue;
const FSPoserAnimator::FSPoserJoint* poserJoint = mPoserAnimator.getPoserJointByName(jointName);
if (!poserJoint)
return;
mPoserAnimator.recaptureJointAsDelta(avatar, *item, getJointTranslation(item->jointName()), getJointNegation(item->jointName()));
}
mPoserAnimator.recaptureJointAsDelta(avatar, poserJoint, getUiSelectedBoneDeflectionStyle());
setSavePosesButtonText(true);
refreshRotationSlidersAndSpinners();
refreshPositionSlidersAndSpinners();
refreshScaleSlidersAndSpinners();
refreshTrackpadCursor();
enableOrDisableRedoAndUndoButton();
refreshTextHighlightingOnJointScrollLists();
}
@ -633,11 +699,8 @@ void FSFloaterPoser::onClickBrowsePoseCache()
gViewerWindow->getWindow()->openFile(pathname);
}
void FSFloaterPoser::onClickSymmetrize(S32 ID)
void FSFloaterPoser::onClickSymmetrize(const S32 ID)
{
if (notDoubleClicked())
return;
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
return;
@ -648,40 +711,35 @@ void FSFloaterPoser::onClickSymmetrize(S32 ID)
mPoserAnimator.symmetrizeLeftToRightOrRightToLeft(avatar, ID == 2);
refreshRotationSlidersAndSpinners();
enableOrDisableRedoAndUndoButton();
refreshTrackpadCursor();
}
void FSFloaterPoser::onCommitSpinner(LLUICtrl* spinner, S32 id)
void FSFloaterPoser::onCommitSpinner(const LLUICtrl* spinner, const S32 id)
{
if (!spinner)
return;
auto activeTab = mJointsTabs->getCurrentPanel();
if (!activeTab)
return;
bool changingBodyPosition = activeTab == mPositionRotationPnl;
F32 value = (F32)spinner->getValue().asReal();
switch (id)
{
case 0: // av_position_updown_spinner
{
mPosZSlider->setValue(value);
onAvatarPositionSet();
mAdvPosZSpnr->setValue(value);
onPositionSet();
break;
}
case 1: // av_position_leftright
{
mPosYSlider->setValue(value);
onAvatarPositionSet();
mAdvPosYSpnr->setValue(value);
onPositionSet();
break;
}
case 2: // av_position_inout_spinner
{
mPosXSlider->setValue(value);
onAvatarPositionSet();
mAdvPosXSpnr->setValue(value);
onPositionSet();
break;
}
case 3: // trackpad_sensitivity_spinner
@ -690,50 +748,100 @@ void FSFloaterPoser::onCommitSpinner(LLUICtrl* spinner, S32 id)
break;
}
case 7: // adv_posx_spinner
{
if (changingBodyPosition)
mPosXSlider->setValue(value);
mAdvPosXSlider->setValue(value);
onAdvancedPositionSet();
break;
}
case 8: // adv_posy_spinner
{
if (changingBodyPosition)
mPosYSlider->setValue(value);
mAdvPosYSlider->setValue(value);
onAdvancedPositionSet();
break;
}
case 9: // adv_posz_spinner
{
if (changingBodyPosition)
mPosZSlider->setValue(value);
mAdvPosZSlider->setValue(value);
onAdvancedPositionSet();
onPositionSet();
break;
}
case 10: // adv_scalex_spinner
{
mAdvScaleXSlider->setValue(value);
onAdvancedScaleSet();
break;
}
case 11: // adv_scaley_spinner
{
mAdvScaleYSlider->setValue(value);
onAdvancedScaleSet();
break;
}
case 12: // adv_scalez_spinner
{
mAdvScaleZSlider->setValue(value);
onAdvancedScaleSet();
onScaleSet();
break;
}
default:
LL_WARNS("Posing") << "onCommitSpinner passed invalid parameter: " << id << LL_ENDL;
break;
}
}
void FSFloaterPoser::onCommitSlider(const LLUICtrl* slider, const S32 id)
{
if (!slider)
return;
F32 value = (F32)slider->getValue().asReal();
switch (id)
{
case 0: // av_position_updown
case 9: // Advanced_Position_Z
{
mAdvPosZSpnr->setValue(value);
onPositionSet();
break;
}
case 1: // av_position_leftright
case 8: // Advanced_Position_Y
{
mAdvPosYSpnr->setValue(value);
onPositionSet();
break;
}
case 2: // av_position_inout
case 7: // Advanced_Position_X
{
mAdvPosXSpnr->setValue(value);
onPositionSet();
break;
}
case 4: // limb_pitch_slider
{
mPitchSpnr->setValue(value);
onYawPitchRollChanged();
break;
}
case 5: // limb_yaw_slider
{
mYawSpnr->setValue(value);
onYawPitchRollChanged();
break;
}
case 6: // limb_roll_slider
{
mRollSpnr->setValue(value);
onYawPitchRollChanged();
break;
}
case 10: // Advanced_Scale_X
{
mScaleXSpnr->setValue(value);
onScaleSet();
break;
}
case 11: // Advanced_Scale_Y
{
mScaleYSpnr->setValue(value);
onScaleSet();
break;
}
case 12: // Advanced_Scale_Z
{
mScaleZSpnr->setValue(value);
onScaleSet();
break;
}
default:
LL_WARNS("Posing") << "onCommitSlider passed invalid parameter: " << id << LL_ENDL;
break;
}
}
@ -779,27 +887,13 @@ void FSFloaterPoser::onPoseMenuAction(const LLSD& param)
setSavePosesButtonText(!mPoserAnimator.allBaseRotationsAreZero(avatar));
}
bool FSFloaterPoser::notDoubleClicked()
{
auto timeIntervalSinceLastExecution = std::chrono::system_clock::now() - mTimeLastExecutedDoubleClickMethod;
mTimeLastExecutedDoubleClickMethod = std::chrono::system_clock::now();
return timeIntervalSinceLastExecution > mDoubleClickInterval;
}
void FSFloaterPoser::onClickLoadLeftHandPose()
{
if (notDoubleClicked())
return;
onClickLoadHandPose(false);
}
void FSFloaterPoser::onClickLoadRightHandPose()
{
if (notDoubleClicked())
return;
onClickLoadHandPose(true);
}
@ -1295,19 +1389,15 @@ void FSFloaterPoser::onUndoLastChange()
mPoserAnimator.undoLastJointChange(avatar, *item, getUiSelectedBoneDeflectionStyle());
}
enableOrDisableRedoButton();
enableOrDisableRedoAndUndoButton();
refreshRotationSlidersAndSpinners();
refreshTrackpadCursor();
refreshPositionSlidersAndSpinners();
refreshAvatarPositionSlidersAndSpinners();
refreshScaleSlidersAndSpinners();
refreshTrackpadCursor();
}
void FSFloaterPoser::onSetAvatarToTpose()
{
if (notDoubleClicked())
return;
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
return;
@ -1315,13 +1405,11 @@ void FSFloaterPoser::onSetAvatarToTpose()
setSavePosesButtonText(false);
mPoserAnimator.setAllAvatarStartingRotationsToZero(avatar);
refreshTextHighlightingOnJointScrollLists();
enableOrDisableRedoAndUndoButton();
}
void FSFloaterPoser::onResetJoint(const LLSD data)
{
if (notDoubleClicked())
return;
int resetType = data.asInteger();
LLVOAvatar* avatar = getUiSelectedAvatar();
@ -1345,10 +1433,10 @@ void FSFloaterPoser::onResetJoint(const LLSD data)
}
refreshRotationSlidersAndSpinners();
refreshTrackpadCursor();
refreshAvatarPositionSlidersAndSpinners();
refreshPositionSlidersAndSpinners();
refreshScaleSlidersAndSpinners();
refreshTrackpadCursor();
enableOrDisableRedoAndUndoButton();
}
void FSFloaterPoser::onRedoLastChange()
@ -1371,16 +1459,18 @@ void FSFloaterPoser::onRedoLastChange()
mPoserAnimator.redoLastJointChange(avatar, *item, getUiSelectedBoneDeflectionStyle());
}
enableOrDisableRedoButton();
enableOrDisableRedoAndUndoButton();
refreshRotationSlidersAndSpinners();
refreshTrackpadCursor();
refreshScaleSlidersAndSpinners();
refreshPositionSlidersAndSpinners();
refreshAvatarPositionSlidersAndSpinners();
}
void FSFloaterPoser::enableOrDisableRedoButton()
void FSFloaterPoser::enableOrDisableRedoAndUndoButton()
{
mRedoChangeBtn->setEnabled(false);
mUndoChangeBtn->setEnabled(false);
LLVOAvatar* avatar = getUiSelectedAvatar();
if (!avatar)
return;
@ -1393,14 +1483,20 @@ void FSFloaterPoser::enableOrDisableRedoButton()
return;
bool shouldEnableRedoButton = false;
bool shouldEnableUndoButton = false;
for (auto item : selectedJoints)
{
bool currentlyPosing = mPoserAnimator.isPosingAvatarJoint(avatar, *item);
if (currentlyPosing)
shouldEnableRedoButton |= mPoserAnimator.canRedoJointChange(avatar, *item);
if (!currentlyPosing)
continue;
shouldEnableRedoButton |= mPoserAnimator.canRedoOrUndoJointChange(avatar, *item);
shouldEnableUndoButton |= mPoserAnimator.canRedoOrUndoJointChange(avatar, *item, true);
}
mRedoChangeBtn->setEnabled(shouldEnableRedoButton);
mUndoChangeBtn->setEnabled(shouldEnableUndoButton);
}
void FSFloaterPoser::onToggleVisualManipulators()
@ -1504,6 +1600,7 @@ LLScrollListCtrl* FSFloaterPoser::getScrollListForTab(LLPanel * tabPanel) const
LL_WARNS() << "Unknown tab panel: " << tabPanel << LL_ENDL;
return nullptr;
}
std::vector<FSPoserAnimator::FSPoserJoint*> FSFloaterPoser::getUiSelectedPoserJoints() const
{
std::vector<FSPoserAnimator::FSPoserJoint*> joints;
@ -1542,21 +1639,23 @@ std::vector<FSPoserAnimator::FSPoserJoint*> FSFloaterPoser::getUiSelectedPoserJo
joints.push_back(userData);
}
}
auto avatarp = getUiSelectedAvatar();
if (avatarp)
{
if(joints.size() >= 1)
{
FSToolCompPose::getInstance()->setJoint( gAgentAvatarp->getJoint( JointKey::construct(joints[0]->jointName())) );
}
else
{
FSToolCompPose::getInstance()->setJoint( nullptr );
}
}
updateManipWithFirstSelectedJoint(joints);
return joints;
}
void FSFloaterPoser::updateManipWithFirstSelectedJoint(std::vector<FSPoserAnimator::FSPoserJoint*> joints)
{
if (!gAgentAvatarp || gAgentAvatarp.isNull())
return;
if (joints.size() >= 1)
FSToolCompPose::getInstance()->setJoint(gAgentAvatarp->getJoint(JointKey::construct(joints[0]->jointName())));
else
FSToolCompPose::getInstance()->setJoint(nullptr);
}
E_RotationStyle FSFloaterPoser::getUiSelectedBoneRotationStyle(const std::string& jointName) const
{
if (jointName.empty())
@ -1663,130 +1762,100 @@ LLVOAvatar* FSFloaterPoser::getAvatarByUuid(const LLUUID& avatarToFind) const
return nullptr;
}
void FSFloaterPoser::onAdvancedPositionSet()
void FSFloaterPoser::onPositionSet()
{
F32 posX = mAdvPosXSlider->getValueF32();
F32 posY = mAdvPosYSlider->getValueF32();
F32 posZ = mAdvPosZSlider->getValueF32();
mAdvPosXSpnr->setValue(posX);
F32 posX = (F32)mAdvPosXSpnr->getValue().asReal();
F32 posY = (F32)mAdvPosYSpnr->getValue().asReal();
F32 posZ = (F32)mAdvPosZSpnr->getValue().asReal();
mInOutSpnr->setValue(posX);
mAdvPosYSpnr->setValue(posY);
mLeftRightSpnr->setValue(posY);
mAdvPosZSpnr->setValue(posZ);
mUpDownSpnr->setValue(posZ);
setSelectedJointsPosition(posX, posY, posZ);
refreshAvatarPositionSlidersAndSpinners();
}
void FSFloaterPoser::onAdvancedScaleSet()
{
F32 scX = mAdvScaleXSlider->getValueF32();
F32 scY = mAdvScaleYSlider->getValueF32();
F32 scZ = mAdvScaleZSlider->getValueF32();
mScaleXSpnr->setValue(scX);
mScaleYSpnr->setValue(scY);
mScaleZSpnr->setValue(scZ);
setSelectedJointsScale(scX, scY, scZ);
}
void FSFloaterPoser::onAvatarPositionSet()
{
F32 posX = mPosXSlider->getValueF32();
F32 posY = mPosYSlider->getValueF32();
F32 posZ = mPosZSlider->getValueF32();
mAdvPosXSpnr->setValue(posX);
mInOutSpnr->setValue(posX);
mAdvPosYSpnr->setValue(posY);
mLeftRightSpnr->setValue(posY);
mAdvPosZSpnr->setValue(posZ);
mUpDownSpnr->setValue(posZ);
mAdvPosXSlider->setValue(posX);
mAdvPosYSlider->setValue(posY);
mAdvPosZSlider->setValue(posZ);
mPosXSlider->setValue(posX);
mPosYSlider->setValue(posY);
mPosZSlider->setValue(posZ);
setSelectedJointsPosition(posX, posY, posZ);
refreshPositionSlidersAndSpinners();
enableOrDisableRedoAndUndoButton();
}
void FSFloaterPoser::onLimbTrackballChanged()
void FSFloaterPoser::onScaleSet()
{
LLVector3 trackPadPos, trackPadDeltaPos;
LLSD position = mAvatarTrackball->getValue();
F32 scX = (F32)mScaleXSpnr->getValue().asReal();
F32 scY = (F32)mScaleYSpnr->getValue().asReal();
F32 scZ = (F32)mScaleZSpnr->getValue().asReal();
mAdvScaleXSlider->setValue(scX);
mAdvScaleYSlider->setValue(scY);
mAdvScaleZSlider->setValue(scZ);
setSelectedJointsScale(scX, scY, scZ);
refreshScaleSlidersAndSpinners();
enableOrDisableRedoAndUndoButton();
}
void FSFloaterPoser::onTrackballChanged()
{
LLVector3 trackPadDeltaPos;
LLSD deltaPosition = mAvatarTrackball->getValueDelta();
if (position.isArray() && position.size() == 3 && deltaPosition.isArray() && deltaPosition.size() == 3)
{
trackPadPos.setValue(position);
if (deltaPosition.isArray() && deltaPosition.size() == 3)
trackPadDeltaPos.setValue(deltaPosition);
}
else
return;
F32 trackPadSensitivity = llmax(gSavedSettings.getF32(POSER_TRACKPAD_SENSITIVITY_SAVE_KEY), 0.0001f);
trackPadPos.mV[VX] *= trackPadSensitivity;
trackPadPos.mV[VY] *= trackPadSensitivity;
trackPadDeltaPos[VX] *= NormalTrackpadRangeInRads * trackPadSensitivity * RAD_TO_DEG;
trackPadDeltaPos[VY] *= NormalTrackpadRangeInRads * trackPadSensitivity * RAD_TO_DEG;
trackPadDeltaPos[VZ] *= NormalTrackpadRangeInRads * RAD_TO_DEG;
trackPadPos.mV[VX] = unWrapScale(trackPadPos.mV[VX]) * NormalTrackpadRangeInRads;
trackPadPos.mV[VY] = unWrapScale(trackPadPos.mV[VY]) * NormalTrackpadRangeInRads;
trackPadPos.mV[VZ] = unWrapScale(trackPadPos.mV[VZ]) * NormalTrackpadRangeInRads;
F32 axis1 = clipRange((F32)mYawSpnr->getValue().asReal() + trackPadDeltaPos[VX]);
F32 axis2 = (F32)mPitchSpnr->getValue().asReal() + trackPadDeltaPos[VY];
F32 axis3 = (F32)mRollSpnr->getValue().asReal() + trackPadDeltaPos[VZ];
trackPadDeltaPos[VX] *= NormalTrackpadRangeInRads * trackPadSensitivity;
trackPadDeltaPos[VY] *= NormalTrackpadRangeInRads * trackPadSensitivity;
trackPadDeltaPos[VZ] *= NormalTrackpadRangeInRads;
mYawSpnr->setValue(axis1);
mPitchSpnr->setValue(axis2);
mRollSpnr->setValue(axis3);
setSelectedJointsRotation(trackPadPos, trackPadDeltaPos);
// WARNING!
// as tempting as it is to refactor the following to refreshRotationSliders(), don't.
// getRotationOfFirstSelectedJoint/setSelectedJointsRotation are
// not necessarily symmetric functions (see their remarks).
mYawSpnr->setValue(trackPadPos.mV[VX] *= RAD_TO_DEG);
mPitchSpnr->setValue(trackPadPos.mV[VY] *= RAD_TO_DEG);
mRollSpnr->setValue(trackPadPos.mV[VZ] *= RAD_TO_DEG);
onYawPitchRollChanged(true);
}
F32 FSFloaterPoser::unWrapScale(F32 scale)
F32 FSFloaterPoser::clipRange(F32 value)
{
if (scale > -1.f && scale < 1.f)
return scale;
F32 result = fmodf(scale, 100.f); // to avoid time consuming while loops
while (result > 1)
result -= 2;
while (result < -1)
result += 2;
F32 result = fmodf(value, 3600.f); // to avoid time consuming while loops
while (result > 180.f)
result -= 360.f;
while (result < -180.f)
result += 360.f;
return result;
}
void FSFloaterPoser::onYawPitchRollChanged()
void FSFloaterPoser::onYawPitchRollChanged(bool skipUpdateTrackpad)
{
LLVector3 absoluteRotation, deltaRotation;
absoluteRotation.mV[VX] = (F32)mYawSpnr->getValue().asReal() * DEG_TO_RAD;
absoluteRotation.mV[VY] = (F32)mPitchSpnr->getValue().asReal() * DEG_TO_RAD;
absoluteRotation.mV[VZ] = (F32)mRollSpnr->getValue().asReal() * DEG_TO_RAD;
absoluteRotation.mV[VX] = (F32)mYawSpnr->getValue().asReal();
absoluteRotation.mV[VY] = (F32)mPitchSpnr->getValue().asReal();
absoluteRotation.mV[VZ] = (F32)mRollSpnr->getValue().asReal();
mAdvRotXSlider->setValue(absoluteRotation.mV[VY]);
mAdvRotYSlider->setValue(absoluteRotation.mV[VX]);
mAdvRotZSlider->setValue(absoluteRotation.mV[VZ]);
absoluteRotation *= DEG_TO_RAD;
deltaRotation = absoluteRotation - mLastSliderRotation;
mLastSliderRotation = absoluteRotation;
setSelectedJointsRotation(absoluteRotation, deltaRotation);
enableOrDisableRedoAndUndoButton();
// WARNING!
// as tempting as it is to refactor the following to refreshTrackpadCursor(), don't.
// getRotationOfFirstSelectedJoint/setSelectedJointsRotation are
// not necessarily symmetric functions (see their remarks).
F32 trackPadSensitivity = llmax(gSavedSettings.getF32(POSER_TRACKPAD_SENSITIVITY_SAVE_KEY), 0.0001f);
absoluteRotation.mV[VX] /= trackPadSensitivity;
absoluteRotation.mV[VY] /= trackPadSensitivity;
absoluteRotation.mV[VX] /= NormalTrackpadRangeInRads;
absoluteRotation.mV[VY] /= NormalTrackpadRangeInRads;
absoluteRotation.mV[VZ] /= NormalTrackpadRangeInRads;
mAvatarTrackball->setValue(absoluteRotation.getValue());
if (!skipUpdateTrackpad)
refreshTrackpadCursor();
}
void FSFloaterPoser::onAdjustTrackpadSensitivity()
@ -1796,38 +1865,14 @@ void FSFloaterPoser::onAdjustTrackpadSensitivity()
void FSFloaterPoser::refreshTrackpadCursor()
{
F32 axis1 = (F32)mYawSpnr->getValue().asReal() * DEG_TO_RAD / NormalTrackpadRangeInRads;
F32 axis2 = (F32)mPitchSpnr->getValue().asReal() * DEG_TO_RAD / NormalTrackpadRangeInRads;
F32 trackPadSensitivity = llmax(gSavedSettings.getF32(POSER_TRACKPAD_SENSITIVITY_SAVE_KEY), 0.0001f);
F32 axis1 = (F32)mYawSpnr->getValue().asReal() * DEG_TO_RAD / NormalTrackpadRangeInRads / trackPadSensitivity;
F32 axis2 = (F32)mPitchSpnr->getValue().asReal() * DEG_TO_RAD / NormalTrackpadRangeInRads / trackPadSensitivity;
F32 axis3 = (F32)mRollSpnr->getValue().asReal() * DEG_TO_RAD / NormalTrackpadRangeInRads;
F32 trackPadSensitivity = llmax(gSavedSettings.getF32(POSER_TRACKPAD_SENSITIVITY_SAVE_KEY), 0.0001f);
axis1 /= trackPadSensitivity;
axis2 /= trackPadSensitivity;
mAvatarTrackball->setValue(axis1, axis2, axis3);
}
/// <summary>
/// This only sets the position sliders of the 'basic' view (not the advanced sliders).
/// </summary>
void FSFloaterPoser::refreshAvatarPositionSlidersAndSpinners()
{
auto activeTab = mJointsTabs->getCurrentPanel();
if (!activeTab)
return;
if (activeTab != mPositionRotationPnl)
return; // if the active tab isn't the av position one, don't set anything.
LLVector3 position = getPositionOfFirstSelectedJoint();
mPosXSlider->setValue(position.mV[VX]);
mInOutSpnr->setValue(position.mV[VX]);
mPosYSlider->setValue(position.mV[VY]);
mLeftRightSpnr->setValue(position.mV[VY]);
mPosZSlider->setValue(position.mV[VZ]);
mUpDownSpnr->setValue(position.mV[VZ]);
}
void FSFloaterPoser::refreshRotationSlidersAndSpinners()
{
LLVector3 rotation = getRotationOfFirstSelectedJoint();
@ -1836,6 +1881,9 @@ void FSFloaterPoser::refreshRotationSlidersAndSpinners()
mYawSpnr->setValue(rotation.mV[VX] *= RAD_TO_DEG);
mPitchSpnr->setValue(rotation.mV[VY] *= RAD_TO_DEG);
mRollSpnr->setValue(rotation.mV[VZ] *= RAD_TO_DEG);
mAdvRotXSlider->setValue(rotation.mV[VY]);
mAdvRotYSlider->setValue(rotation.mV[VX]);
mAdvRotZSlider->setValue(rotation.mV[VZ]);
}
void FSFloaterPoser::refreshPositionSlidersAndSpinners()
@ -1848,6 +1896,20 @@ void FSFloaterPoser::refreshPositionSlidersAndSpinners()
mAdvPosYSpnr->setValue(position.mV[VY]);
mAdvPosZSlider->setValue(position.mV[VZ]);
mAdvPosZSpnr->setValue(position.mV[VZ]);
auto activeTab = mJointsTabs->getCurrentPanel();
if (!activeTab)
return;
if (activeTab != mPositionRotationPnl)
return; // if the active tab isn't the av position one, don't set anything.
mPosXSlider->setValue(position.mV[VX]);
mInOutSpnr->setValue(position.mV[VX]);
mPosYSlider->setValue(position.mV[VY]);
mLeftRightSpnr->setValue(position.mV[VY]);
mPosZSlider->setValue(position.mV[VZ]);
mUpDownSpnr->setValue(position.mV[VZ]);
}
void FSFloaterPoser::refreshScaleSlidersAndSpinners()
@ -2003,11 +2065,10 @@ LLVector3 FSFloaterPoser::getScaleOfFirstSelectedJoint() const
void FSFloaterPoser::onJointTabSelect()
{
refreshAvatarPositionSlidersAndSpinners();
refreshPositionSlidersAndSpinners();
refreshRotationSlidersAndSpinners();
refreshTrackpadCursor();
enableOrDisableRedoButton();
refreshPositionSlidersAndSpinners();
enableOrDisableRedoAndUndoButton();
refreshScaleSlidersAndSpinners();
onClickSetBaseRotZero();
}
@ -2276,7 +2337,8 @@ void FSFloaterPoser::refreshTextHighlightingOnJointScrollLists()
void FSFloaterPoser::setSavePosesButtonText(bool setAsSaveDiff)
{
setAsSaveDiff ? mSavePosesBtn->setLabel("Save Diff") : mSavePosesBtn->setLabel("Save Pose");
if (hasString("SavePoseLabel") && hasString("SaveDiffLabel"))
setAsSaveDiff ? mSavePosesBtn->setLabel(getString("SaveDiffLabel")) : mSavePosesBtn->setLabel(getString("SavePoseLabel"));
}
void FSFloaterPoser::addBoldToScrollList(LLScrollListCtrl* list, LLVOAvatar* avatar)
@ -2571,8 +2633,8 @@ 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()

View File

@ -78,13 +78,13 @@ class FSFloaterPoser : public LLFloater, public LLEditMenuHandler
{
friend class LLFloaterReg;
FSFloaterPoser(const LLSD &key);
public:
void updatePosedBones();
public:
void updatePosedBones(const std::string& jointName);
void selectJointByName(const std::string& jointName);
void undo() override { onUndoLastChange(); };
bool canUndo() const override { return true; }
void redo() override { onRedoLastChange(); };
bool canRedo() const override { return true; }
void undo() override { onUndoLastChange(); };
bool canUndo() const override { return true; }
void redo() override { onRedoLastChange(); };
bool canRedo() const override { return true; }
private:
bool postBuild() override;
void onOpen(const LLSD& key) override;
@ -132,6 +132,12 @@ public:
/// <returns>The selected joints</returns>
std::vector<FSPoserAnimator::FSPoserJoint*> getUiSelectedPoserJoints() const;
/// <summary>
/// Updates the visual with the first selected joint from the supplied collection, if any.
/// </summary>
/// <param name="joints">The collection of selected joints.</param>
static void updateManipWithFirstSelectedJoint(std::vector<FSPoserAnimator::FSPoserJoint*> joints);
/// <summary>
/// Gets a detectable avatar by its UUID.
/// </summary>
@ -214,6 +220,7 @@ public:
void createUserPoseDirectoryIfNeeded();
void onToggleLoadSavePanel();
void onClickPoseSave();
void onMouseLeaveSavePoseBtn();
void onPoseFileSelect();
bool savePoseToXml(LLVOAvatar* avatar, const std::string& posePath);
bool savePoseToBvh(LLVOAvatar* avatar, const std::string& posePath);
@ -223,31 +230,30 @@ public:
bool poseFileStartsFromTeePose(const std::string& poseFileName);
void setPoseSaveFileTextBoxToUiSelectedAvatarSaveFileName();
void setUiSelectedAvatarSaveFileName(const std::string& saveFileName);
bool confirmFileOverwrite(std::string fileName);
void startPosingSelf();
void stopPosingAllAvatars();
// visual manipulators control
void enableVisualManipulators();
void disableVisualManipulators();
// UI Event Handlers:
// UI Event Handlers
void onAvatarsRefresh();
void onAvatarSelect();
void onJointTabSelect();
void onToggleMirrorChange();
void onToggleSympatheticChange();
void onToggleVisualManipulators();
void onToggleVisualManipulators();
void setRotationChangeButtons(bool mirror, bool sympathetic);
void onUndoLastChange();
void onRedoLastChange();
void onResetJoint(const LLSD data);
void onSetAvatarToTpose();
void enableOrDisableRedoButton();
void onPoseStartStop();
void startPosingSelf();
void stopPosingAllAvatars();
void onLimbTrackballChanged();
void onYawPitchRollChanged();
void onAvatarPositionSet();
void onAdvancedPositionSet();
void onAdvancedScaleSet();
void onTrackballChanged();
void onYawPitchRollChanged(bool skipUpdateTrackpad = false);
void onPositionSet();
void onScaleSet();
void onClickToggleSelectedBoneEnabled();
void onClickRecaptureSelectedBones();
void onClickFlipPose();
@ -257,15 +263,16 @@ public:
void onClickLoadRightHandPose();
void onClickLoadHandPose(bool isRightHand);
void onClickSetBaseRotZero();
void onCommitSpinner(LLUICtrl* spinner, S32 ID);
void onClickSymmetrize(S32 ID);
void onCommitSpinner(const LLUICtrl* spinner, const S32 ID);
void onCommitSlider(const LLUICtrl* slider, const S32 id);
void onClickSymmetrize(const S32 ID);
// UI Refreshments
void refreshRotationSlidersAndSpinners();
void refreshAvatarPositionSlidersAndSpinners();
void refreshTrackpadCursor();
void refreshPositionSlidersAndSpinners();
void refreshScaleSlidersAndSpinners();
void refreshTrackpadCursor();
void enableOrDisableRedoAndUndoButton();
/// <summary>
/// Determines if we have permission to animate the supplied avatar.
@ -341,12 +348,6 @@ public:
/// <param name="avatar">The avatar to whom the list is relevant.</param>
void addBoldToScrollList(LLScrollListCtrl* list, LLVOAvatar* avatar);
/// <summary>
/// Determines if the user has run this method twice within mDoubleClickInterval.
/// </summary>
/// <returns>true if this method has executed since mDoubleClickInterval seconds ago, otherwise false.</returns>
bool notDoubleClicked();
/// <summary>
/// Gets whether the user wishes to reset the base-rotation to zero when they start editing a joint.
/// </summary>
@ -426,26 +427,15 @@ public:
std::string static vec3ToXYZString(const LLVector3& val);
/// <summary>
/// The time when the last click of a button was made.
/// Utilized for controls needing a 'double click do' function.
/// Performs an angle module of the supplied value to between -180 & 180 (degrees).
/// </summary>
std::chrono::system_clock::time_point mTimeLastExecutedDoubleClickMethod = std::chrono::system_clock::now();
/// <summary>
/// The constant time interval, in seconds, a user must execute the notDoubleClicked twice to successfully 'double-click' a button.
/// </summary>
std::chrono::duration<double> const mDoubleClickInterval = std::chrono::duration<double>(0.3);
/// <summary>
/// Unwraps a normalized value from the trackball to a slider value.
/// </summary>
/// <param name="scale">The scale value from the trackball.</param>
/// <returns>A value appropriate for fitting a slider.</returns>
/// <param name="value">The value to modulo.</param>
/// <returns>The modulo value.</returns>
/// <remarks>
/// If the trackpad is in 'infinite scroll' mode, it can produce normalized-values outside the range of the sliders.
/// This method ensures whatever value the trackpad produces, they work with the sliders.
/// If the trackpad is in 'infinite scroll' mode, it can produce normalized-values outside the range of the spinners.
/// This method ensures whatever value the trackpad produces, they work with the spinners.
/// </remarks>
static F32 unWrapScale(F32 scale);
static F32 clipRange(F32 value);
LLToolset* mLastToolset{ nullptr };
LLTool* mJointRotTool{ nullptr };
@ -458,6 +448,9 @@ public:
LLSliderCtrl* mPosXSlider{ nullptr };
LLSliderCtrl* mPosYSlider{ nullptr };
LLSliderCtrl* mPosZSlider{ nullptr };
LLSliderCtrl* mAdvRotXSlider{ nullptr };
LLSliderCtrl* mAdvRotYSlider{ nullptr };
LLSliderCtrl* mAdvRotZSlider{ nullptr };
LLSliderCtrl* mAdvPosXSlider{ nullptr };
LLSliderCtrl* mAdvPosYSlider{ nullptr };
LLSliderCtrl* mAdvPosZSlider{ nullptr };
@ -492,6 +485,7 @@ public:
LLButton* mToggleSympatheticRotationBtn{ nullptr };
LLButton* mToggleDeltaModeBtn{ nullptr };
LLButton* mRedoChangeBtn{ nullptr };
LLButton* mUndoChangeBtn{ nullptr };
LLButton* mSetToTposeButton{ nullptr };
LLButton* mBtnJointRotate{ nullptr };

View File

@ -144,21 +144,15 @@ void FSJointPose::recaptureJoint()
addStateToUndo(FSJointState(mCurrentState));
mCurrentState = FSJointState(joint);
}
void FSJointPose::recaptureJointAsDelta()
{
if (mIsCollisionVolume)
{
return;
}
LLJoint* joint = mJointState->getJoint();
if (!joint)
{
return;
}
addStateToUndo(mCurrentState);
mCurrentState = FSJointState(joint);
addStateToUndo(FSJointState(mCurrentState));
mCurrentState.updateFromJoint(joint);
}
void FSJointPose::swapRotationWith(FSJointPose* oppositeJoint)
@ -209,6 +203,9 @@ void FSJointPose::zeroBaseRotation()
if (mIsCollisionVolume)
return;
if (!isBaseRotationZero())
purgeUndoQueue();
mCurrentState.zeroBaseRotation();
}
@ -219,3 +216,22 @@ bool FSJointPose::isBaseRotationZero() const
return mCurrentState.baseRotationIsZero();
}
void FSJointPose::purgeUndoQueue()
{
mUndoneJointStatesIndex = 0;
mLastSetJointStates.clear();
}
bool FSJointPose::canPerformUndo() const
{
switch (mLastSetJointStates.size())
{
case 0: // nothing to undo
return false;
case 1: // there is only one change
return true;
default: // current state is not the bottom of the deque
return mUndoneJointStatesIndex != (mLastSetJointStates.size() - 1);
}
}

View File

@ -108,6 +108,12 @@ class FSJointPose
/// <returns>True if the represented joint is zero, otherwise false.</returns>
bool isBaseRotationZero() const;
/// <summary>
/// Gets whether an undo of this joint may be performed.
/// </summary>
/// <returns>true if the joint may have a undo applied, otherwise false.</returns>
bool canPerformUndo() const;
/// <summary>
/// Gets whether a redo of this joint may be performed.
/// </summary>
@ -143,11 +149,16 @@ class FSJointPose
/// Resets the beginning properties of the joint this represents.
/// </summary>
void recaptureJoint();
/// <summary>
/// Recalculates the delta reltive to the base for a new rotation.
/// </summary>
void recaptureJointAsDelta();
/// <summary>
/// Clears the undo/redo deque.
/// </summary>
void purgeUndoQueue();
/// <summary>
/// Reverts the position/rotation/scale to their values when the animation begun.
@ -185,7 +196,6 @@ class FSJointPose
inv_base.conjugate();
mDeltaRotation = newRotation * inv_base;
};
void reflectRotation()
{
@ -215,6 +225,18 @@ class FSJointPose
joint->setScale(mBaseScale);
}
void updateFromJoint(LLJoint* joint)
{
if (!joint)
return;
LLQuaternion invRot = mBaseRotation;
invRot.conjugate();
mRotation = joint->getRotation() * invRot;
mPosition.set(joint->getPosition() - mBasePosition);
mScale.set(joint->getScale() - mBaseScale);
}
private:
FSJointState(FSJointState* state)
{

View File

@ -1012,9 +1012,9 @@ bool FSManipRotateJoint::handleMouseUp(S32 x, S32 y, MASK mask)
if (hasMouseCapture())
{
// Update the UI, by causing it to read back the position of the selected joints and aply those relative to the base rot
if (poser)
if (poser && mJoint)
{
poser->updatePosedBones();
poser->updatePosedBones(mJoint->getName());
}
// Release mouse

View File

@ -138,7 +138,7 @@ void FSPoserAnimator::resetJoint(LLVOAvatar* avatar, const FSPoserJoint& joint,
oppositeJointPose->setPublicScale(LLVector3());
}
bool FSPoserAnimator::canRedoJointChange(LLVOAvatar* avatar, const FSPoserJoint& joint)
bool FSPoserAnimator::canRedoOrUndoJointChange(LLVOAvatar* avatar, const FSPoserJoint& joint, bool canUndo)
{
if (!isAvatarSafeToUse(avatar))
return false;
@ -154,6 +154,9 @@ bool FSPoserAnimator::canRedoJointChange(LLVOAvatar* avatar, const FSPoserJoint&
if (!jointPose)
return false;
if (canUndo)
return jointPose->canPerformUndo();
return jointPose->canPerformRedo();
}
@ -303,7 +306,7 @@ void FSPoserAnimator::setAllAvatarStartingRotationsToZero(LLVOAvatar* avatar)
if (!posingMotion)
return;
posingMotion->setAllRotationsToZero();
posingMotion->setAllRotationsToZeroAndClearUndo();
}
void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation)
@ -322,7 +325,8 @@ void FSPoserAnimator::recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joi
jointPose->recaptureJoint();
setPosingAvatarJoint(avatar, joint, true);
}
void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation)
void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint* joint, E_BoneDeflectionStyles style)
{
if (!isAvatarSafeToUse(avatar))
return;
@ -331,12 +335,34 @@ void FSPoserAnimator::recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoi
if (!posingMotion)
return;
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName());
FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint->jointName());
if (!jointPose)
return;
jointPose->recaptureJointAsDelta();
setPosingAvatarJoint(avatar, joint, true);
if (style == NONE || style == DELTAMODE)
return;
FSJointPose* oppositeJointPose = posingMotion->getJointPoseByJointName(joint->mirrorJointName());
if (!oppositeJointPose)
return;
switch (style)
{
case SYMPATHETIC:
case SYMPATHETIC_DELTA:
oppositeJointPose->cloneRotationFrom(jointPose);
break;
case MIRROR:
case MIRROR_DELTA:
oppositeJointPose->mirrorRotationFrom(jointPose);
break;
default:
break;
}
}
LLVector3 FSPoserAnimator::getJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation) const

View File

@ -417,8 +417,9 @@ public:
/// </summary>
/// <param name="avatar">The avatar having the joint to which we refer.</param>
/// <param name="joint">The joint to query.</param>
/// <returns>True if a redo action is available, otherwise false.</returns>
bool canRedoJointChange(LLVOAvatar* avatar, const FSPoserJoint& joint);
/// <param name="canUndo">Supply true to query if we can perform an Undo, otherwise query redo.</param>
/// <returns>True if an undo or redo action is available, otherwise false.</returns>
bool canRedoOrUndoJointChange(LLVOAvatar* avatar, const FSPoserJoint& joint, bool canUndo = false);
/// <summary>
/// Re-does the last undone change (rotation, position or scale) to the supplied PoserJoint.
@ -503,20 +504,27 @@ public:
/// <summary>
/// Symmetrizes the rotations of the joints from one side of the supplied avatar to the other.
/// </summary>
/// <param name="avatar">The avatar whose joints to symmetrizet.</param>
/// <param name="avatar">The avatar to symmetrize.</param>
/// <param name="rightToLeft">Whether to symmetrize rotations from right to left, otherwise symmetrize left to right.</param>
void symmetrizeLeftToRightOrRightToLeft(LLVOAvatar* avatar, bool rightToLeft);
/// <summary>
/// Recaptures the rotation, position and scale state of the supplied joint for the supplied avatar.
/// AsDelta variant retians the original base and creates a delta relative to it.
/// AsDelta variant retains the original base and creates a delta relative to it.
/// </summary>
/// <param name="avatar">The avatar whose joint is to be recaptured.</param>
/// <param name="joint">The joint to recapture.</param>
/// <param name="translation">The axial translation form the supplied joint.</param>
/// <param name="negation">The style of negation to apply to the recapture.</param>
void recaptureJoint(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation);
void recaptureJointAsDelta(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation);
/// <summary>
/// Recaptures any change in joint state.
/// </summary>
/// <param name="avatar">The avatar whose joint is to be recaptured.</param>
/// <param name="joint">The joint to recapture.</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);
/// <summary>
/// Sets all of the joint rotations of the supplied avatar to zero.
@ -525,14 +533,11 @@ public:
void setAllAvatarStartingRotationsToZero(LLVOAvatar* avatar);
/// <summary>
/// Determines if the kind of save to perform should be a 'delta' save, or a complete save.
/// Determines if the supplied joint has a base rotation of zero.
/// </summary>
/// <param name="avatar">The avatar whose pose-rotations are being considered for saving.</param>
/// <returns>True if the save should save only 'deltas' to the rotation, otherwise false.</returns>
/// <remarks>
/// A save of the rotation 'deltas' facilitates a user saving their changes to an existing animation.
/// Thus the save represents 'nothing other than the changes the user made', to some other pose which they may have limited rights to.
/// </remarks>
/// <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;
/// <summary>

View File

@ -255,7 +255,7 @@ bool FSPosingMotion::allStartingRotationsAreZero() const
return true;
}
void FSPosingMotion::setAllRotationsToZero()
void FSPosingMotion::setAllRotationsToZeroAndClearUndo()
{
for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter)
{

View File

@ -119,9 +119,9 @@ public:
bool allStartingRotationsAreZero() const;
/// <summary>
/// Sets all of the non-Collision Volume rotations to zero.
/// Sets all of the non-Collision Volume base-and-delta rotations to zero, and clears the undo/redo queue.
/// </summary>
void setAllRotationsToZero();
void setAllRotationsToZeroAndClearUndo();
private:
/// <summary>

View File

@ -11,6 +11,9 @@ width="430">
<string name="icon_bone" translate="false"></string>
<string name="icon_object" translate="false">Inv_Object</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>
<string name="icon_save_query" translate="false">Info</string>
<!-- Provides for axis-swapping per joint for slider/trackpad control -->
<!-- Begins with joint_transform_ then has an internal joint name, ONE swap choice of:
@ -278,7 +281,10 @@ width="430">
<string name="bvh_joint_transform_mHead" translate="false">SWAP_X2Y_Y2Z_Z2X</string>
<string name="LoadPoseLabel">Load Pose</string>
<string name="SavePoseLabel">Save Pose</string>
<string name="LoadDiffLabel">Load Diff</string>
<string name="SaveDiffLabel">Save Diff</string>
<string name="OverWriteLabel">Overwrite?</string>
<!-- The layout is a vertical stack of 3 rows, and each row a horizontal stack of panels -->
<layout_stack
@ -369,7 +375,8 @@ width="430">
tool_tip="Move the selected avatar up or down"
can_edit_text="true">
<slider.commit_callback
function="Poser.PositionSet"/>
function="Poser.CommitSlider"
parameter="0"/>
</slider>
<spinner
height="16"
@ -413,7 +420,8 @@ width="430">
tool_tip="Move the selected avatar left or right"
can_edit_text="true">
<slider.commit_callback
function="Poser.PositionSet"/>
function="Poser.CommitSlider"
parameter="1"/>
</slider>
<spinner
height="16"
@ -457,7 +465,8 @@ width="430">
tool_tip="Move the selected avatar in or out"
can_edit_text="true">
<slider.commit_callback
function="Poser.PositionSet"/>
function="Poser.CommitSlider"
parameter="2"/>
</slider>
<spinner
height="16"
@ -637,7 +646,7 @@ width="430">
layout="topleft"
label="Set Left"
name="button_loadHandPoseLeft"
tool_tip="Double click to set your left hand to the selected preset"
tool_tip="Click to set your left hand to the selected preset"
top_delta="238"
left_pad="1"
left="2"
@ -651,7 +660,7 @@ width="430">
layout="topleft"
label="Set Right"
name="button_loadHandPoseRight"
tool_tip="Double click to set your right hand to the selected preset"
tool_tip="Click to set your right hand to the selected preset"
left_pad="2"
top_delta="0"
width="85" >
@ -819,7 +828,7 @@ width="430">
label="Set to T-Pose"
image_unselected="Toolbar_Middle_Off"
name="set_t_pose_button"
tool_tip="Double-click to set the selected avatar to a 'T-Pose'"
tool_tip="Click to set the selected avatar to a 'T-Pose'"
width="172">
<button.commit_callback
function="Poser.SetToTPose"/>
@ -922,6 +931,16 @@ width="430">
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"
width="134" />
<check_box
control_name="FSPoserOnSaveConfirmOverwrite"
name="confirm_overwrite_on_save_checkbox"
height="16"
label="Confirm Overwrite"
follows="left|top"
left="5"
tool_tip="When you save a pose, if the file already exists, you need to click the save button again to confirm you are sure you want to overwrite."
top_pad="5"
width="134" />
</panel>
</tab_container>
<button
@ -956,7 +975,7 @@ width="430">
<tab_container
follows="all"
halign="center"
height="235"
height="263"
layout="topleft"
name="modifier_tabs"
tab_height="20"
@ -967,7 +986,7 @@ width="430">
width="215">
<panel
follows="left|right|top|bottom"
height="290"
height="265"
background_visible="false"
layout="topleft"
mouse_opaque="false"
@ -981,9 +1000,9 @@ width="430">
name="limb_rotation"
follows="left|right|top"
top="2"
height="170"
width="170"
left="10"
height="160"
width="160"
left="15"
tool_tip="Change the rotation of the currently selected body part(s). Hold Ctrl to move slow. Roll the wheel to adjust the 3rd axis. Use Shift or Alt to swap which rotations change"
pinch_mode="false"
infinite_scroll_mode="true"/>
@ -992,75 +1011,132 @@ width="430">
name="limb_pitch_label"
height="10"
layout="topleft"
left="2"
left="5"
top_pad="2"
width="57">
width="200">
Up/Down:
</text>
<slider
label=""
label_width="0"
follows="left|top"
height="14"
increment="0.1"
initial_value="0"
layout="topleft"
show_text="false"
left="3"
min_val="-180"
max_val="180"
name="limb_pitch_slider"
top_pad="2"
width="130" >
<slider.commit_callback
function="Poser.CommitSlider"
parameter="4"/>
</slider>
<spinner
height="16"
decimal_digits="2"
follows="left|top"
increment="0.1"
top_pad="-19"
left_pad="0"
name="limb_pitch_spinner"
min_val="-180"
max_val="180"
width="53">
<spinner.commit_callback
function="Poser.CommitSpinner"
parameter="4"/>
</spinner>
<text
follows="left|top"
name="limb_yaw_label"
height="10"
layout="topleft"
left_pad="5"
top_pad="-10"
width="57">
left="5"
top_pad="2"
width="200">
Left/Right:
</text>
<slider
label=""
label_width="0"
follows="left|top"
height="14"
increment="0.1"
initial_value="0"
layout="topleft"
show_text="false"
left="3"
min_val="-180"
max_val="180"
name="limb_yaw_slider"
top_pad="2"
width="130" >
<slider.commit_callback
function="Poser.CommitSlider"
parameter="5"/>
</slider>
<spinner
height="16"
decimal_digits="2"
follows="left|top"
increment="0.1"
top_pad="-19"
left_pad="0"
name="limb_yaw_spinner"
min_val="-180"
max_val="180"
width="53">
<spinner.commit_callback
function="Poser.CommitSpinner"
parameter="5"/>
</spinner>
<text
follows="left|top"
name="limb_roll_label"
height="10"
layout="topleft"
left_pad="5"
top_pad="-10"
left="5"
top_pad="2"
width="200">
Roll:
</text>
<slider
label=""
label_width="0"
follows="left|top"
height="14"
increment="0.1"
initial_value="0"
layout="topleft"
show_text="false"
left="3"
min_val="-180"
max_val="180"
name="limb_roll_slider"
top_pad="2"
width="130" >
<slider.commit_callback
function="Poser.CommitSlider"
parameter="6"/>
</slider>
<spinner
height="16"
decimal_digits="2"
follows="left|top"
increment="0.1"
left="2"
top_pad="5"
name="limb_pitch_spinner"
min_val="-180"
max_val="180"
width="57">
height="16"
decimal_digits="2"
follows="left|top"
increment="0.1"
top_pad="-19"
left_pad="0"
name="limb_roll_spinner"
min_val="-180"
max_val="180"
width="53">
<spinner.commit_callback
function="Poser.CommitSpinner"
parameter="4"/>
</spinner>
<spinner
height="16"
decimal_digits="2"
follows="left|top"
increment="0.1"
top_pad="-16"
left_pad="5"
name="limb_yaw_spinner"
min_val="-180"
max_val="180"
width="57">
<spinner.commit_callback
function="Poser.CommitSpinner"
parameter="5"/>
</spinner>
<spinner
height="16"
decimal_digits="2"
follows="left|top"
increment="0.1"
top_pad="-16"
left_pad="5"
name="limb_roll_spinner"
min_val="-180"
max_val="180"
width="57">
<spinner.commit_callback
function="Poser.CommitSpinner"
parameter="6"/>
function="Poser.CommitSpinner"
parameter="6"/>
</spinner>
</panel>
<panel
@ -1099,19 +1175,20 @@ width="430">
top_pad="2"
width="130" >
<slider.commit_callback
function="Poser.Advanced.PositionSet"/>
function="Poser.CommitSlider"
parameter="7"/>
</slider>
<spinner
height="20"
decimal_digits="2"
decimal_digits="3"
follows="left|top"
increment="0.01"
increment="0.001"
top_pad="-19"
left_pad="2"
left_pad="0"
name="adv_posx_spinner"
min_val="-1.5"
max_val="1.5"
width="47">
width="53">
<spinner.commit_callback
function="Poser.CommitSpinner"
parameter="7"/>
@ -1142,19 +1219,20 @@ width="430">
top_pad="2"
width="130" >
<slider.commit_callback
function="Poser.Advanced.PositionSet"/>
function="Poser.CommitSlider"
parameter="8"/>
</slider>
<spinner
height="20"
decimal_digits="2"
decimal_digits="3"
follows="left|top"
increment="0.01"
increment="0.001"
top_pad="-19"
left_pad="2"
left_pad="0"
name="adv_posy_spinner"
min_val="-1.5"
max_val="1.5"
width="47">
width="53">
<spinner.commit_callback
function="Poser.CommitSpinner"
parameter="8"/>
@ -1185,19 +1263,20 @@ width="430">
top_pad="2"
width="130" >
<slider.commit_callback
function="Poser.Advanced.PositionSet"/>
function="Poser.CommitSlider"
parameter="9"/>
</slider>
<spinner
height="20"
decimal_digits="2"
decimal_digits="3"
follows="left|top"
increment="0.01"
increment="0.001"
top_pad="-19"
left_pad="2"
left_pad="0"
name="adv_posz_spinner"
min_val="-1.5"
max_val="1.5"
width="47">
width="53">
<spinner.commit_callback
function="Poser.CommitSpinner"
parameter="9"/>
@ -1228,7 +1307,8 @@ width="430">
top_pad="2"
width="130" >
<slider.commit_callback
function="Poser.Advanced.ScaleSet"/>
function="Poser.CommitSlider"
parameter="10"/>
</slider>
<spinner
height="20"
@ -1271,7 +1351,8 @@ width="430">
top_pad="2"
width="130" >
<slider.commit_callback
function="Poser.Advanced.ScaleSet"/>
function="Poser.CommitSlider"
parameter="11"/>
</slider>
<spinner
height="20"
@ -1314,7 +1395,8 @@ width="430">
top_pad="2"
width="130" >
<slider.commit_callback
function="Poser.Advanced.ScaleSet"/>
function="Poser.CommitSlider"
parameter="12"/>
</slider>
<spinner
height="20"
@ -1379,7 +1461,7 @@ width="430">
image_overlay="Inv_TrashOpen"
image_unselected="Toolbar_Middle_Off"
name="poser_joint_reset"
tool_tip="Double click to reset the selected body part(s) to when you first started posing"
tool_tip="Click to reset the selected body part(s) to when you first started posing"
width="18"
top_delta="0"
left_pad="1">
@ -1443,12 +1525,13 @@ width="430">
</button>
<button
follows="left|top"
height="21"
height="18"
layout="topleft"
label="Copy L &gt; R"
name="button_symmetrize_left_to_right"
tool_tip="Double click to copy change from left side to right side."
tool_tip="Click to copy change from left side to right side."
left="14"
top_pad="0"
width="70" >
<button.commit_callback
function="Poser.Symmetrize"
@ -1456,11 +1539,11 @@ width="430">
</button>
<button
follows="left|top"
height="21"
height="19"
layout="topleft"
label="Copy R &gt; L"
name="button_symmetrize_right_to_left"
tool_tip="Double click to copy change from right side to left side."
tool_tip="Click to copy change from right side to left side."
left_pad="23"
top_delta="0"
width="70" >