diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index d489801f48..b63da471a3 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8056,6 +8056,17 @@ Value 0 + FSPoserStopPosingWhenClosed + + Comment + Whether to stop animating with the poser when the poser window is closed. + Persist + 1 + Type + Boolean + Value + 1 + FSPoserTrackpadSensitivity Comment diff --git a/indra/newview/fsfloaterposer.cpp b/indra/newview/fsfloaterposer.cpp index 7b315f733d..9f058c8cc3 100644 --- a/indra/newview/fsfloaterposer.cpp +++ b/indra/newview/fsfloaterposer.cpp @@ -40,6 +40,7 @@ #include "llviewercontrol.h" #include "llviewerwindow.h" #include "llwindow.h" +#include "llvoavatarself.h" namespace { @@ -52,6 +53,7 @@ constexpr char XML_LIST_TITLE_STRING_PREFIX[] = "title_"; constexpr char XML_JOINT_TRANSFORM_STRING_PREFIX[] = "joint_transform_"; constexpr std::string_view POSER_ADVANCEDWINDOWSTATE_SAVE_KEY = "FSPoserAdvancedWindowState"; constexpr std::string_view POSER_TRACKPAD_SENSITIVITY_SAVE_KEY = "FSPoserTrackpadSensitivity"; +constexpr std::string_view POSER_STOPPOSINGWHENCLOSED_SAVE_KEY = "FSPoserStopPosingWhenClosed"; } // namespace /// @@ -59,6 +61,7 @@ constexpr std::string_view POSER_TRACKPAD_SENSITIVITY_SAVE_KEY = "FSPoserTrackpa /// The trackpad ordinarily has a range of +1..-1; multiplied by PI, gives PI to -PI, or all 360 degrees of deflection. /// constexpr F32 NormalTrackpadRangeInRads = F_PI; +bool FSFloaterPoser::mDisableRecaptureUntilStopPosing; FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key) { @@ -76,6 +79,7 @@ FSFloaterPoser::FSFloaterPoser(const LLSD& key) : LLFloater(key) mCommitCallbackRegistrar.add("Poser.AdjustTrackPadSensitivity", [this](LLUICtrl*, const LLSD&) { onAdjustTrackpadSensitivity(); }); mCommitCallbackRegistrar.add("Poser.PositionSet", [this](LLUICtrl*, const LLSD&) { onAvatarPositionSet(); }); + 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(); }); @@ -154,9 +158,7 @@ bool FSFloaterPoser::postBuild() mToggleAdvancedPanelBtn = getChild("toggleAdvancedPanel"); if (gSavedSettings.getBOOL(POSER_ADVANCEDWINDOWSTATE_SAVE_KEY)) - { mToggleAdvancedPanelBtn->setValue(true); - } mTrackpadSensitivitySlider = getChild("trackpad_sensitivity_slider"); mTrackpadSensitivitySlider->setValue(gSavedSettings.getF32(POSER_TRACKPAD_SENSITIVITY_SAVE_KEY)); @@ -195,6 +197,9 @@ bool FSFloaterPoser::postBuild() mToggleSympatheticRotationBtn = getChild("button_toggleSympatheticRotation"); mToggleDeltaModeBtn = getChild("delta_mode_toggle"); mRedoChangeBtn = getChild("button_redo_change"); + mSetToTposeButton = getChild("set_t_pose_button"); + mRecaptureJointsButton = getChild("button_RecaptureParts"); + mRecaptureJointsButton->setEnabled(!mDisableRecaptureUntilStopPosing); mJointsParentPnl = getChild("joints_parent_panel"); mAdvancedParentPnl = getChild("advanced_parent_panel"); @@ -207,6 +212,10 @@ bool FSFloaterPoser::postBuild() mMiscJointsPnl = getChild("misc_joints_panel"); mCollisionVolumesPnl = getChild("collision_volumes_panel"); + mStopPosingWhenClosed = getChild("stop_posing_on_close_checkbox"); + if (gSavedSettings.getBOOL(POSER_STOPPOSINGWHENCLOSED_SAVE_KEY)) + mStopPosingWhenClosed->set(true); + return true; } @@ -217,13 +226,21 @@ void FSFloaterPoser::onOpen(const LLSD& key) onJointSelect(); onOpenSetAdvancedPanel(); refreshPoseScroll(mHandPresetsScrollList, POSE_PRESETS_HANDS_SUBDIRECTORY); + startPosingSelf(); LLFloater::onOpen(key); } void FSFloaterPoser::onClose(bool app_quitting) { - gSavedSettings.setBOOL(POSER_ADVANCEDWINDOWSTATE_SAVE_KEY, mToggleAdvancedPanelBtn->getValue().asBoolean()); + if (mToggleAdvancedPanelBtn) + gSavedSettings.setBOOL(POSER_ADVANCEDWINDOWSTATE_SAVE_KEY, mToggleAdvancedPanelBtn->getValue().asBoolean()); + + if (mStopPosingWhenClosed) + gSavedSettings.setBOOL(POSER_STOPPOSINGWHENCLOSED_SAVE_KEY, mStopPosingWhenClosed->getValue()); + + if (gSavedSettings.getBOOL(POSER_STOPPOSINGWHENCLOSED_SAVE_KEY)) + stopPosingSelf(); LLFloater::onClose(app_quitting); } @@ -331,26 +348,25 @@ bool FSFloaterPoser::savePoseToXml(LLVOAvatar* avatar, const std::string& poseFi std::string fullSavePath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, POSE_SAVE_SUBDIRECTORY, poseFileName + POSE_INTERNAL_FORMAT_FILE_EXT); + bool fromZeroRot = mPoserAnimator.posingStartedFromZeroRotations(avatar); + LLSD record; - S32 version = 3; - record["version"]["value"] = version; + record["version"]["value"] = (S32)4; + record["startFromTeePose"]["value"] = fromZeroRot; + + LLVector3 rotation, position, scale; for (const FSPoserAnimator::FSPoserJoint& pj : mPoserAnimator.PoserJoints) { - std::string bone_name = pj.jointName(); + std::string bone_name = pj.jointName(); + if (!mPoserAnimator.tryGetJointSaveVectors(avatar, pj, &rotation, &position, &scale)) + continue; - LLVector3 vec3 = mPoserAnimator.getJointRotation(avatar, pj, SWAP_NOTHING, NEGATE_NOTHING); - - record[bone_name] = pj.jointName(); - record[bone_name]["rotation"] = vec3.getValue(); - - vec3 = mPoserAnimator.getJointPosition(avatar, pj); - record[bone_name]["position"] = vec3.getValue(); - - vec3 = mPoserAnimator.getJointScale(avatar, pj); - record[bone_name]["scale"] = vec3.getValue(); - - record[bone_name]["enabled"] = mPoserAnimator.isPosingAvatarJoint(avatar, pj); + record[bone_name] = pj.jointName(); + record[bone_name]["enabled"] = mPoserAnimator.isPosingAvatarJoint(avatar, pj); + record[bone_name]["rotation"] = rotation.getValue(); + record[bone_name]["position"] = position.getValue(); + record[bone_name]["scale"] = scale.getValue(); } llofstream file; @@ -394,7 +410,7 @@ void FSFloaterPoser::onClickToggleSelectedBoneEnabled() refreshRotationSliders(); refreshTrackpadCursor(); - refreshTextEmbiggeningOnAllScrollLists(); + refreshTextHighlightingOnAllScrollLists(); } void FSFloaterPoser::onClickFlipSelectedJoints() @@ -458,6 +474,9 @@ void FSFloaterPoser::onClickFlipPose() void FSFloaterPoser::onClickRecaptureSelectedBones() { + if (mDisableRecaptureUntilStopPosing) + return; + auto selectedJoints = getUiSelectedPoserJoints(); if (selectedJoints.size() < 1) return; @@ -476,7 +495,7 @@ void FSFloaterPoser::onClickRecaptureSelectedBones() continue; LLVector3 newRotation = mPoserAnimator.getJointRotation(avatar, *item, getJointTranslation(item->jointName()), - getJointNegation(item->jointName()), true); + getJointNegation(item->jointName()), CURRENTROTATION); LLVector3 newPosition = mPoserAnimator.getJointPosition(avatar, *item, true); LLVector3 newScale = mPoserAnimator.getJointScale(avatar, *item, true); @@ -490,7 +509,7 @@ void FSFloaterPoser::onClickRecaptureSelectedBones() refreshRotationSliders(); refreshTrackpadCursor(); - refreshTextEmbiggeningOnAllScrollLists(); + refreshTextHighlightingOnAllScrollLists(); } void FSFloaterPoser::onClickBrowsePoseCache() @@ -673,14 +692,16 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose loadMethod == ROT_POS_AND_SCALES; bool loadScales = loadMethod == SCALES || loadMethod == POSITIONS_AND_SCALES || loadMethod == ROTATIONS_AND_SCALES || loadMethod == ROT_POS_AND_SCALES; - bool loadHandsOnly = loadMethod == HAND_RIGHT || loadMethod == HAND_LEFT; try { - LLSD pose; - llifstream infile; - LLVector3 vec3; - bool enabled; + LLSD pose; + llifstream infile; + LLVector3 vec3; + LLQuaternion quat; + bool enabled; + S32 version = 0; + bool startFromZeroRot = false; infile.open(fullPath); if (!infile.is_open()) @@ -697,40 +718,48 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose for (LLSD::map_const_iterator itr = pose.beginMap(); itr != pose.endMap(); ++itr) { - std::string const &name = itr->first; - LLSD const &control_map = itr->second; + std::string const& name = itr->first; + LLSD const& control_map = itr->second; - if (loadHandsOnly && name.find("Hand") == std::string::npos) - continue; + if (name == "startFromTeePose") + startFromZeroRot = control_map["value"].asBoolean(); + + if (name == "version") + version = (S32)control_map["value"].asInteger(); + } + + bool loadPositionsAndScalesAsDeltas = false; + if (version > 3) + loadPositionsAndScalesAsDeltas = true; + + if (startFromZeroRot) // old save formats will always start from T-Pose, for better or worse. + mPoserAnimator.setAllAvatarStartingRotationsToZero(avatar); + + for (LLSD::map_const_iterator itr = pose.beginMap(); itr != pose.endMap(); ++itr) + { + std::string const& name = itr->first; + LLSD const& control_map = itr->second; const FSPoserAnimator::FSPoserJoint *poserJoint = mPoserAnimator.getPoserJointByName(name); if (!poserJoint) continue; - if (loadHandsOnly && control_map.has("rotation")) - { - vec3.setValue(control_map["rotation"]); - - mPoserAnimator.setJointRotation(avatar, poserJoint, vec3, NONE, SWAP_NOTHING, NEGATE_NOTHING); - continue; - } - if (loadRotations && control_map.has("rotation")) { vec3.setValue(control_map["rotation"]); - mPoserAnimator.setJointRotation(avatar, poserJoint, vec3, NONE, SWAP_NOTHING, NEGATE_NOTHING); // If we keep defaults BD poses mostly load, except fingers + mPoserAnimator.loadJointRotation(avatar, poserJoint, vec3); } if (loadPositions && control_map.has("position")) { vec3.setValue(control_map["position"]); - mPoserAnimator.setJointPosition(avatar, poserJoint, vec3, NONE); + mPoserAnimator.loadJointPosition(avatar, poserJoint, loadPositionsAndScalesAsDeltas, vec3); } if (loadScales && control_map.has("scale")) { vec3.setValue(control_map["scale"]); - mPoserAnimator.setJointScale(avatar, poserJoint, vec3, NONE); + mPoserAnimator.loadJointScale(avatar, poserJoint, loadPositionsAndScalesAsDeltas, vec3); } if (control_map.has("enabled")) @@ -749,6 +778,35 @@ void FSFloaterPoser::loadPoseFromXml(LLVOAvatar* avatar, const std::string& pose onJointSelect(); } +void FSFloaterPoser::startPosingSelf() +{ + setUiSelectedAvatar(gAgentAvatarp->getID()); + LLVOAvatar* avatar = getAvatarByUuid(gAgentAvatarp->getID()); + if (!avatar) + return; + + bool arePosingSelected = mPoserAnimator.isPosingAvatar(avatar); + if (!arePosingSelected && couldAnimateAvatar(avatar)) + mPoserAnimator.tryPosingAvatar(avatar); + + onAvatarSelect(); +} + +void FSFloaterPoser::stopPosingSelf() +{ + LLVOAvatar* avatar = getAvatarByUuid(gAgentAvatarp->getID()); + if (!avatar) + return; + + bool arePosingSelected = mPoserAnimator.isPosingAvatar(avatar); + if (!arePosingSelected) + return; + + mPoserAnimator.stopPosingAvatar(avatar); + onAvatarSelect(); + reEnableRecaptureIfAllowed(); +} + void FSFloaterPoser::onPoseStartStop() { LLVOAvatar* avatar = getUiSelectedAvatar(); @@ -759,6 +817,7 @@ void FSFloaterPoser::onPoseStartStop() if (arePosingSelected) { mPoserAnimator.stopPosingAvatar(avatar); + reEnableRecaptureIfAllowed(); } else { @@ -799,7 +858,6 @@ bool FSFloaterPoser::havePermissionToAnimateAvatar(LLVOAvatar *avatar) const void FSFloaterPoser::poseControlsEnable(bool enable) { - mJointsParentPnl->setEnabled(enable); mAdvancedParentPnl->setEnabled(enable); mTrackballPnl->setEnabled(enable); mFlipPoseBtn->setEnabled(enable); @@ -1060,6 +1118,24 @@ void FSFloaterPoser::onUndoLastScale() refreshAdvancedScaleSliders(); } +void FSFloaterPoser::onSetAvatarToTpose() +{ + auto timeIntervalSinceLastClick = std::chrono::system_clock::now() - mTimeLastClickedJointReset; + mTimeLastClickedJointReset = std::chrono::system_clock::now(); + if (timeIntervalSinceLastClick > mDoubleClickInterval) + return; + + LLVOAvatar* avatar = getUiSelectedAvatar(); + if (!avatar) + return; + + mRecaptureJointsButton->setEnabled(false); + mSavePosesBtn->setLabel("Save Pose"); + mDisableRecaptureUntilStopPosing = true; + + mPoserAnimator.setAllAvatarStartingRotationsToZero(avatar); +} + void FSFloaterPoser::onResetPosition() { // This is a double-click function: it needs to run twice within some amount of time to complete. @@ -1342,6 +1418,23 @@ LLVOAvatar* FSFloaterPoser::getUiSelectedAvatar() const return getAvatarByUuid(selectedAvatarId); } +void FSFloaterPoser::setUiSelectedAvatar(const LLUUID& avatarToSelect) +{ + for (auto listItem : mAvatarSelectionScrollList->getAllData()) + { + LLScrollListCell* cell = listItem->getColumn(COL_UUID); + if (!cell) + continue; + + LLUUID avatarId = cell->getValue().asUUID(); + if (avatarId != avatarToSelect) + continue; + + listItem->setSelected(true); + break; + } +} + void FSFloaterPoser::setPoseSaveFileTextBoxToUiSelectedAvatarSaveFileName() { LLScrollListItem* item = mAvatarSelectionScrollList->getFirstSelected(); @@ -1665,7 +1758,7 @@ LLVector3 FSFloaterPoser::getRotationOfFirstSelectedJoint() const return rotation; rotation = mPoserAnimator.getJointRotation(avatar, *selectedJoints.front(), getJointTranslation(selectedJoints.front()->jointName()), - getJointNegation(selectedJoints.front()->jointName())); + getJointNegation(selectedJoints.front()->jointName()), TARGETROTATION); return rotation; } @@ -1781,8 +1874,9 @@ void FSFloaterPoser::onAvatarSelect() bool arePosingSelected = mPoserAnimator.isPosingAvatar(avatar); mStartStopPosingBtn->setValue(arePosingSelected); + mSetToTposeButton->setEnabled(arePosingSelected); poseControlsEnable(arePosingSelected); - refreshTextEmbiggeningOnAllScrollLists(); + refreshTextHighlightingOnAllScrollLists(); onJointSelect(); setPoseSaveFileTextBoxToUiSelectedAvatarSaveFileName(); } @@ -1794,7 +1888,7 @@ uuid_vec_t FSFloaterPoser::getNearbyAvatarsAndAnimeshes() const for (LLCharacter* character : LLCharacter::sInstances) { LLVOAvatar* avatar = dynamic_cast(character); - if (!couldAnimateAvatar(avatar)) + if (!havePermissionToAnimateAvatar(avatar)) continue; avatar_ids.emplace_back(character->getID()); @@ -1889,7 +1983,7 @@ void FSFloaterPoser::onAvatarsRefresh() row["columns"][COL_UUID]["value"] = uuid; row["columns"][COL_SAVE]["column"] = "saveFileName"; row["columns"][COL_SAVE]["value"] = ""; - LLScrollListItem* item = mAvatarSelectionScrollList->addElement(row); + LLScrollListItem* item = mAvatarSelectionScrollList->addElement(row); } // Add Animesh avatars @@ -1917,12 +2011,11 @@ void FSFloaterPoser::onAvatarsRefresh() } mAvatarSelectionScrollList->updateLayout(); - refreshTextEmbiggeningOnAllScrollLists(); + refreshTextHighlightingOnAllScrollLists(); } -void FSFloaterPoser::refreshTextEmbiggeningOnAllScrollLists() +void FSFloaterPoser::refreshTextHighlightingOnAllScrollLists() { - // the avatars for (auto listItem : mAvatarSelectionScrollList->getAllData()) { LLScrollListCell* cell = listItem->getColumn(COL_UUID); @@ -1946,6 +2039,34 @@ void FSFloaterPoser::refreshTextEmbiggeningOnAllScrollLists() addBoldToScrollList(mCollisionVolumesScrollList, avatar); } +void FSFloaterPoser::reEnableRecaptureIfAllowed() +{ + if (posingAnyoneOnScrollList()) + return; + + mRecaptureJointsButton->setEnabled(true); + mSavePosesBtn->setLabel("Save Diff"); + mDisableRecaptureUntilStopPosing = false; +} + +bool FSFloaterPoser::posingAnyoneOnScrollList() +{ + for (auto listItem : mAvatarSelectionScrollList->getAllData()) + { + LLScrollListCell* cell = listItem->getColumn(COL_UUID); + if (!cell) + continue; + + LLUUID selectedAvatarId = cell->getValue().asUUID(); + LLVOAvatar* listAvatar = getAvatarByUuid(selectedAvatarId); + + if (mPoserAnimator.isPosingAvatar(listAvatar)) + return true; + } + + return false; +} + void FSFloaterPoser::addBoldToScrollList(LLScrollListCtrl* list, LLVOAvatar* avatar) { if (!avatar) diff --git a/indra/newview/fsfloaterposer.h b/indra/newview/fsfloaterposer.h index a2362ba6bc..71c2fb4126 100644 --- a/indra/newview/fsfloaterposer.h +++ b/indra/newview/fsfloaterposer.h @@ -81,7 +81,8 @@ class FSFloaterPoser : public LLFloater void onOpen(const LLSD& key) override; void onClose(bool app_quitting) override; - + static bool mDisableRecaptureUntilStopPosing; + /// /// Refreshes the supplied pose list from the supplued subdirectory. /// @@ -136,6 +137,12 @@ class FSFloaterPoser : public LLFloater /// The currently selected avatar or animesh. LLVOAvatar* getUiSelectedAvatar() const; + /// + /// Sets the UI selection for avatar or animesh. + /// + /// The ID of the avatar to select, if found. + void setUiSelectedAvatar(const LLUUID& avatarToSelect); + /// /// Gets the current bone-deflection style: encapsulates 'anything else you want to do' while you're manipulating a joint. /// Such as: fiddle the opposite joint too. @@ -216,8 +223,11 @@ class FSFloaterPoser : public LLFloater void onRedoLastScale(); void onResetPosition(); void onResetScale(); + void onSetAvatarToTpose(); void enableOrDisableRedoButton(); void onPoseStartStop(); + void startPosingSelf(); + void stopPosingSelf(); void onLimbTrackballChanged(); void onLimbYawPitchRollChanged(); void onAvatarPositionSet(); @@ -284,9 +294,19 @@ class FSFloaterPoser : public LLFloater S32 getJointNegation(const std::string& jointName) const; /// - /// The smallest text embiggens the noble selection. + /// Refreshes the text on all scroll lists based on their state. /// - void refreshTextEmbiggeningOnAllScrollLists(); + void refreshTextHighlightingOnAllScrollLists(); + + /// + /// Recapture is be disabled if user is making their own pose (starting from a T-Pose). + /// + void reEnableRecaptureIfAllowed(); + + /// + /// Gets whether any avatar know by the UI is being posed. + /// + bool posingAnyoneOnScrollList(); /// /// Applies the appropriate font-face (such as bold) to the text of the supplied list, to indicate use. @@ -356,8 +376,10 @@ class FSFloaterPoser : public LLFloater LLButton* mToggleSympatheticRotationBtn{ nullptr }; LLButton* mToggleDeltaModeBtn{ nullptr }; LLButton* mRedoChangeBtn{ nullptr }; + LLButton* mSetToTposeButton{ nullptr }; + LLButton* mRecaptureJointsButton{ nullptr }; - LLCheckBoxCtrl* mAlsoSaveBvhCbx{ nullptr }; + LLCheckBoxCtrl* mStopPosingWhenClosed{ nullptr }; LLLineEditor* mPoseSaveNameEditor{ nullptr }; LLPanel* mAdvancedParentPnl{ nullptr }; diff --git a/indra/newview/fsposeranimator.cpp b/indra/newview/fsposeranimator.cpp index c7b7da8d65..4c9bb067aa 100644 --- a/indra/newview/fsposeranimator.cpp +++ b/indra/newview/fsposeranimator.cpp @@ -412,7 +412,35 @@ void FSPoserAnimator::setJointPosition(LLVOAvatar* avatar, const FSPoserJoint* j } } -LLVector3 FSPoserAnimator::getJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation, bool forRecapture) const +bool FSPoserAnimator::posingStartedFromZeroRotations(LLVOAvatar* avatar) const +{ + if (!isAvatarSafeToUse(avatar)) + return false; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return false; + + bool allStartingRotationsAreZero = posingMotion->allStartingRotationsAreZero(); + if (allStartingRotationsAreZero) + return true; + + return false; +} + +void FSPoserAnimator::setAllAvatarStartingRotationsToZero(LLVOAvatar* avatar) +{ + if (!isAvatarSafeToUse(avatar)) + return; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return; + + posingMotion->setAllRotationsToZero(); +} + +LLVector3 FSPoserAnimator::getJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation, E_BoneRotationType rotType) const { LLVector3 vec3; if (!isAvatarSafeToUse(avatar)) @@ -427,11 +455,18 @@ LLVector3 FSPoserAnimator::getJointRotation(LLVOAvatar* avatar, const FSPoserJoi return vec3; LLQuaternion rot; - if (forRecapture) - rot = jointPose->getCurrentRotation(); - else - rot = jointPose->getTargetRotation(); - + switch (rotType) + { + case TARGETROTATION: + rot = jointPose->getTargetRotation(); + break; + + case CURRENTROTATION: + default: + rot = jointPose->getCurrentRotation(); + break; + } + return translateRotationFromQuaternion(translation, negation, rot); } @@ -709,6 +744,85 @@ void FSPoserAnimator::setJointScale(LLVOAvatar* avatar, const FSPoserJoint* join oppositeJointPose->setTargetScale(scale); } +bool FSPoserAnimator::tryGetJointSaveVectors(LLVOAvatar* avatar, const FSPoserJoint& joint, LLVector3* rot, LLVector3* pos, LLVector3* scale) +{ + if (!rot || !pos || !scale) + return false; + + if (!isAvatarSafeToUse(avatar)) + return false; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return false; + + FSPosingMotion::FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint.jointName()); + if (!jointPose) + return false; + + LLQuaternion difference = jointPose->getTargetRotation() * jointPose->getBeginningRotation().conjugate(); // diff * q1 = q2 -> diff = q2 * inverse(q1) + + difference.getEulerAngles(&rot->mV[VX], &rot->mV[VY], &rot->mV[VZ]); + pos->set(jointPose->getTargetPosition() - jointPose->getBeginningPosition()); + scale->set(jointPose->getTargetScale() - jointPose->getBeginningScale()); + return true; +} + +void FSPoserAnimator::loadJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, LLVector3 rotation) +{ + if (!isAvatarSafeToUse(avatar) || !joint) + return; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return; + + FSPosingMotion::FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint->jointName()); + if (!jointPose) + return; + + LLQuaternion rot = translateRotationToQuaternion(SWAP_NOTHING, NEGATE_NOTHING, rotation); + jointPose->setTargetRotation(rot * jointPose->getBeginningRotation()); +} + +void FSPoserAnimator::loadJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadPositionAsDelta, LLVector3 position) +{ + if (!isAvatarSafeToUse(avatar) || !joint) + return; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return; + + FSPosingMotion::FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint->jointName()); + if (!jointPose) + return; + + if (loadPositionAsDelta) + jointPose->setTargetPosition(jointPose->getBeginningPosition() + position); + else + jointPose->setTargetPosition(position); +} + +void FSPoserAnimator::loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadScaleAsDelta, LLVector3 scale) +{ + if (!isAvatarSafeToUse(avatar) || !joint) + return; + + FSPosingMotion* posingMotion = getPosingMotion(avatar); + if (!posingMotion) + return; + + FSPosingMotion::FSJointPose* jointPose = posingMotion->getJointPoseByJointName(joint->jointName()); + if (!jointPose) + return; + + if (loadScaleAsDelta) + jointPose->setTargetScale(jointPose->getTargetScale() + scale); + else + jointPose->setTargetScale(scale); +} + const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByName(const std::string& jointName) { for (size_t index = 0; index != PoserJoints.size(); ++index) @@ -720,7 +834,7 @@ const FSPoserAnimator::FSPoserJoint* FSPoserAnimator::getPoserJointByName(const return nullptr; } -bool FSPoserAnimator::tryPosingAvatar(LLVOAvatar *avatar) +bool FSPoserAnimator::tryPosingAvatar(LLVOAvatar* avatar) { if (!isAvatarSafeToUse(avatar)) return false; @@ -781,20 +895,18 @@ FSPosingMotion* FSPoserAnimator::getPosingMotion(LLVOAvatar* avatar) const FSPosingMotion* FSPoserAnimator::findOrCreatePosingMotion(LLVOAvatar* avatar) { FSPosingMotion* motion = getPosingMotion(avatar); + if (motion) + return motion; - if (!motion) - { - LLTransactionID mTransactionID; - mTransactionID.generate(); - LLAssetID animationAssetId = mTransactionID.makeAssetID(gAgent.getSecureSessionID()); + LLTransactionID mTransactionID; + mTransactionID.generate(); + LLAssetID animationAssetId = mTransactionID.makeAssetID(gAgent.getSecureSessionID()); - if (avatar->registerMotion(animationAssetId, FSPosingMotion::create)) - sAvatarIdToRegisteredAnimationId[avatar->getID()] = animationAssetId; + if (avatar->registerMotion(animationAssetId, FSPosingMotion::create)) + sAvatarIdToRegisteredAnimationId[avatar->getID()] = animationAssetId; - return dynamic_cast(avatar->createMotion(animationAssetId)); - } + return dynamic_cast(avatar->createMotion(animationAssetId)); - return motion; } bool FSPoserAnimator::isAvatarSafeToUse(LLVOAvatar* avatar) const diff --git a/indra/newview/fsposeranimator.h b/indra/newview/fsposeranimator.h index 1a13026f9a..5348b67b03 100644 --- a/indra/newview/fsposeranimator.h +++ b/indra/newview/fsposeranimator.h @@ -56,6 +56,15 @@ typedef enum E_BoneDeflectionStyles DELTAMODE = 3, // each selected joint changes by the same supplied amount relative to their current } E_BoneDeflectionStyles; +/// +/// When getting the rotation of a joint, we can apply different considerations to the rotation. +/// +typedef enum E_BoneRotationType +{ + CURRENTROTATION = 0, // the current rotation the joint has + TARGETROTATION = 1, // the rotation the we want to achieve +} E_BoneRotationType; + /// /// When we're going from bone-rotation to the UI sliders, some of the axes need swapping so they make sense in UI-terms. /// eg: for one bone, the X-axis may mean up and down, but for another bone, the x-axis might be left-right. @@ -435,10 +444,9 @@ public: /// The joint to determine the rotation for. /// The joint to determine the rotation for. /// The style of negation to apply to the set. - /// Get the current non-poser rotation, for recapture opportunity. + /// The type of rotation to get from the supplied joint for the supplied avatar. /// The rotation of the requested joint, if determinable, otherwise a default vector. - LLVector3 getJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation, - bool forRecapture = false) const; + LLVector3 getJointRotation(LLVOAvatar* avatar, const FSPoserJoint& joint, E_BoneAxisTranslation translation, S32 negation, E_BoneRotationType rotType) const; /// /// Sets the rotation of a joint for the supplied avatar. @@ -481,6 +489,83 @@ public: /// The avatar whose pose should flip left-right. void flipEntirePose(LLVOAvatar* avatar); + /// + /// Sets all of the joint rotations of the supplied avatar to zero. + /// + /// The avatar whose joint rotations should be set to zero. + void setAllAvatarStartingRotationsToZero(LLVOAvatar* avatar); + + /// + /// Determines if the kind of save to perform should be a 'delta' save, or a complete save. + /// + /// The avatar whose pose-rotations are being considered for saving. + /// True if the save should save only 'deltas' to the rotation, otherwise false. + /// + /// 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. + /// + bool posingStartedFromZeroRotations(LLVOAvatar* avatar) const; + + /// + /// Tries to get the rotation, position and scale changes from initial conditions, to save in some export container. + /// + /// The avatar whose pose is being considered for saving. + /// The joint we are considering the save for. + /// The quaternion to store the rotation to save in. + /// The vector to store the position to save in. + /// The vector to store the scale to save in. + /// True if the joint should be saved, otherwise false. + /// + /// Our objective is to protect peoples novel work: the poses created with this, and poses from other sources, such as in-world. + /// In all scenarios, this yeilds 'deltas' of rotation/position/scale. + /// The deltas represent the user's novel work, and may be relative to some initial values (as from a pose), or to 'nothing' (such as all rotations == 0, or, the 'T-Pose'). + /// + bool tryGetJointSaveVectors(LLVOAvatar* avatar, const FSPoserJoint& joint, LLVector3* rot, LLVector3* pos, LLVector3* scale); + + /// + /// Loads a joint rotation for the supplied joint on the supplied avatar. + /// + /// The avatar to load the rotation for. + /// The joint to load the rotation for. + /// The rotation to load. + /// + /// All rotations we load are deltas to the current rotation the supplied joint has. + /// Whether the joint already has a rotation because some animation is playing (sp possibly a non-zero rotation), + /// or whether it is a rotation relative to zero, the result is always the same: just 'add' this rotation to the existing. + /// + void loadJointRotation(LLVOAvatar* avatar, const FSPoserJoint* joint, LLVector3 rotation); + + /// + /// Loads a joint position for the supplied joint on the supplied avatar. + /// + /// The avatar to load the position for. + /// The joint to load the position for. + /// Whether to the supplied position as a delta to the current position, or not. + /// The Position to apply to the supplied joint. + /// + /// A position is saved as an absolute if the user created the pose from 'scratch' (at present the 'T-Pose'). + /// Otherwise the position is saved as a delta. + /// The primary purpose is aesthetic: the numbers inside of a 'delta save file' have 'zeros everywhere'. + /// A delta-save thus accurately reflects what the user changed, and not what the original creator of the modified pose specified. + /// 'Legacy' (pre save format version-4) poses we expect to load as absolutes. + /// + void loadJointPosition(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadPositionAsDelta, LLVector3 position); + + /// + /// Loads a joint scale for the supplied joint on the supplied avatar. + /// + /// The avatar to load the scale for. + /// The joint to load the scale for. + /// Whether to the supplied scale as a delta to the current scale, or not. + /// The scale to apply to the supplied joint. + /// + /// A scale is saved as an absolute if the user created the pose from 'scratch' (at present the 'T-Pose'). + /// Otherwise the scale is saved as a delta. + /// The primary purpose is somewhat aesthetic: the numbers inside of a 'pose modification XML' has zeros everywhere. + /// A delta-save thus accurately reflects what the user changed, and not what the original creator of the modified pose specified. + /// + void loadJointScale(LLVOAvatar* avatar, const FSPoserJoint* joint, bool loadScaleAsDelta, LLVector3 scale); + private: /// /// Translates a rotation vector from the UI to a Quaternion for the bone. diff --git a/indra/newview/fsposingmotion.cpp b/indra/newview/fsposingmotion.cpp index 657dc662f3..718b5d25ca 100644 --- a/indra/newview/fsposingmotion.cpp +++ b/indra/newview/fsposingmotion.cpp @@ -249,6 +249,34 @@ bool FSPosingMotion::currentlyPosingJoint(LLJoint* joint) return (state & POSER_JOINT_STATE); } +bool FSPosingMotion::allStartingRotationsAreZero() const +{ + LLQuaternion zeroQuat; + for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter) + { + if (poserJoint_iter->jointName() == "mPelvis") + continue; + if (poserJoint_iter->isCollisionVolume()) + continue; + + LLQuaternion quat = poserJoint_iter->getBeginningRotation(); + if (quat != zeroQuat) + return false; + } + + return true; +} + +void FSPosingMotion::setAllRotationsToZero() +{ + for (auto poserJoint_iter = mJointPoses.begin(); poserJoint_iter != mJointPoses.end(); ++poserJoint_iter) + { + if (poserJoint_iter->isCollisionVolume()) + continue; + + poserJoint_iter->setRotationsToZero(); + } +} constexpr size_t MaximumUndoQueueLength = 20; @@ -489,6 +517,8 @@ void FSPosingMotion::FSJointPose::revertCollisionVolume() joint->setScale(mBeginningScale); } +void FSPosingMotion::FSJointPose::setRotationsToZero() { mBeginningRotation = mTargetRotation = LLQuaternion(); } + FSPosingMotion::FSJointPose::FSJointPose(LLJoint* joint, bool isCollisionVolume) { mJointState = new LLJointState; diff --git a/indra/newview/fsposingmotion.h b/indra/newview/fsposingmotion.h index f5215b2b0b..52d6905947 100644 --- a/indra/newview/fsposingmotion.h +++ b/indra/newview/fsposingmotion.h @@ -222,6 +222,11 @@ public: /// void revertCollisionVolume(); + /// + /// Sets all rotations to zero. + /// + void setRotationsToZero(); + /// /// Gets the pointer to the jointstate for the joint this represents. /// @@ -295,6 +300,17 @@ public: /// The unique, per-session, per-character motion identity. LLAssetID motionId() const { return mMotionID; } + /// + /// Gets whether all starting rotations are zero. + /// + /// True if all starting rotations are zero, otherwise false. + bool allStartingRotationsAreZero() const; + + /// + /// Sets all of the non-Collision Volume rotations to zero. + /// + void setAllRotationsToZero(); + private: /// /// The kind of joint state this animation is concerned with changing. 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 1cba3122c6..dca1006447 100644 --- a/indra/newview/skins/default/xui/en/floater_fs_poser.xml +++ b/indra/newview/skins/default/xui/en/floater_fs_poser.xml @@ -5,7 +5,7 @@ height="310" layout="topleft" name="floater_poser" title="Avatar and Animesh Poser" -width="565"> +width="403"> Inv_BodyShape Inv_Object @@ -256,7 +256,7 @@ width="565"> follows="top|left|right" height="425" layout="topleft" - left="7" + left="3" border_size="-4" close_time_constant="0.02" open_time_constant="0.02" @@ -273,68 +273,16 @@ width="565"> name="regular_controls_layout" height="290" width="607"> - - - - - - - - - enabled="true" name="joints_tabs" tab_height="20" - tab_width="50" + tab_width="55" tab_group="1" tab_position="left" top="0" @@ -545,6 +493,38 @@ width="565"> tab_position="top" top="0" width="235"> + + + + + + function="Poser.LoadRightHand"/> - - - - - - relative_width="0.9" /> + + + + + + + + + + + + + + Trackpad Sensitivity: + + + + + left="0" name="button_controls_panel" width="800"> - - @@ -1025,8 +1088,8 @@ width="565"> mouse_opaque="false" left_pad="0" top_delta="0" - name="advbutton_spacer_panel" - width="54"/> + name="button_spacer_panel" + width="36"/> @@ -1103,7 +1166,7 @@ width="565"> left_pad="0" top_delta="0" name="button_spacer_panel" - width="60"/> + width="58"/>